blob: e230e3c87641e060cebdcb35514cbdca784882cd [file] [log] [blame]
Gilles Peskine878acd62019-08-01 23:32:38 +02001#!/usr/bin/env python3
2
3"""Test helper for the Mbed TLS configuration file tool
4
5Run config.py with various parameters and write the results to files.
6
7This is a harness to help regression testing, not a functional tester.
8Sample usage:
9
10 test_config_script.py -d old
Bence Szépkútibb0cfeb2021-05-28 09:42:25 +020011 ## Modify config.py and/or mbedtls_config.h ##
Gilles Peskine878acd62019-08-01 23:32:38 +020012 test_config_script.py -d new
13 diff -ru old new
14"""
15
Bence Szépkúti1e148272020-08-07 13:07:28 +020016## Copyright The Mbed TLS Contributors
Gilles Peskine878acd62019-08-01 23:32:38 +020017## SPDX-License-Identifier: Apache-2.0
18##
19## Licensed under the Apache License, Version 2.0 (the "License"); you may
20## not use this file except in compliance with the License.
21## You may obtain a copy of the License at
22##
23## http://www.apache.org/licenses/LICENSE-2.0
24##
25## Unless required by applicable law or agreed to in writing, software
26## distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
27## WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
28## See the License for the specific language governing permissions and
29## limitations under the License.
Gilles Peskine878acd62019-08-01 23:32:38 +020030
31import argparse
32import glob
33import os
34import re
35import shutil
36import subprocess
37
38OUTPUT_FILE_PREFIX = 'config-'
39
40def output_file_name(directory, stem, extension):
41 return os.path.join(directory,
42 '{}{}.{}'.format(OUTPUT_FILE_PREFIX,
43 stem, extension))
44
45def cleanup_directory(directory):
46 """Remove old output files."""
47 for extension in []:
48 pattern = output_file_name(directory, '*', extension)
49 filenames = glob.glob(pattern)
50 for filename in filenames:
51 os.remove(filename)
52
53def prepare_directory(directory):
54 """Create the output directory if it doesn't exist yet.
55
56 If there are old output files, remove them.
57 """
58 if os.path.exists(directory):
59 cleanup_directory(directory)
60 else:
61 os.makedirs(directory)
62
63def guess_presets_from_help(help_text):
64 """Figure out what presets the script supports.
65
66 help_text should be the output from running the script with --help.
67 """
68 # Try the output format from config.py
69 hits = re.findall(r'\{([-\w,]+)\}', help_text)
70 for hit in hits:
71 words = set(hit.split(','))
72 if 'get' in words and 'set' in words and 'unset' in words:
73 words.remove('get')
74 words.remove('set')
75 words.remove('unset')
76 return words
77 # Try the output format from config.pl
78 hits = re.findall(r'\n +([-\w]+) +- ', help_text)
79 if hits:
80 return hits
81 raise Exception("Unable to figure out supported presets. Pass the '-p' option.")
82
83def list_presets(options):
84 """Return the list of presets to test.
85
86 The list is taken from the command line if present, otherwise it is
87 extracted from running the config script with --help.
88 """
89 if options.presets:
90 return re.split(r'[ ,]+', options.presets)
91 else:
92 help_text = subprocess.run([options.script, '--help'],
Gilles Peskinee0c84ac2020-03-24 18:47:53 +010093 check=False, # config.pl --help returns 255
Gilles Peskine878acd62019-08-01 23:32:38 +020094 stdout=subprocess.PIPE,
95 stderr=subprocess.STDOUT).stdout
96 return guess_presets_from_help(help_text.decode('ascii'))
97
Gilles Peskine16a25e02019-09-19 12:19:24 +020098def run_one(options, args, stem_prefix='', input_file=None):
Gilles Peskine878acd62019-08-01 23:32:38 +020099 """Run the config script with the given arguments.
100
Gilles Peskine16a25e02019-09-19 12:19:24 +0200101 Take the original content from input_file if specified, defaulting
102 to options.input_file if input_file is None.
103
104 Write the following files, where xxx contains stem_prefix followed by
105 a filename-friendly encoding of args:
Gilles Peskine878acd62019-08-01 23:32:38 +0200106 * config-xxx.h: modified file.
107 * config-xxx.out: standard output.
108 * config-xxx.err: standard output.
109 * config-xxx.status: exit code.
Gilles Peskine16a25e02019-09-19 12:19:24 +0200110
111 Return ("xxx+", "path/to/config-xxx.h") which can be used as
112 stem_prefix and input_file to call this function again with new args.
Gilles Peskine878acd62019-08-01 23:32:38 +0200113 """
Gilles Peskine16a25e02019-09-19 12:19:24 +0200114 if input_file is None:
115 input_file = options.input_file
116 stem = stem_prefix + '-'.join(args)
Gilles Peskine878acd62019-08-01 23:32:38 +0200117 data_filename = output_file_name(options.output_directory, stem, 'h')
118 stdout_filename = output_file_name(options.output_directory, stem, 'out')
119 stderr_filename = output_file_name(options.output_directory, stem, 'err')
120 status_filename = output_file_name(options.output_directory, stem, 'status')
Gilles Peskine16a25e02019-09-19 12:19:24 +0200121 shutil.copy(input_file, data_filename)
Gilles Peskine878acd62019-08-01 23:32:38 +0200122 # Pass only the file basename, not the full path, to avoid getting the
123 # directory name in error messages, which would make comparisons
124 # between output directories more difficult.
125 cmd = [os.path.abspath(options.script),
126 '-f', os.path.basename(data_filename)]
127 with open(stdout_filename, 'wb') as out:
128 with open(stderr_filename, 'wb') as err:
129 status = subprocess.call(cmd + args,
130 cwd=options.output_directory,
131 stdin=subprocess.DEVNULL,
132 stdout=out, stderr=err)
133 with open(status_filename, 'w') as status_file:
134 status_file.write('{}\n'.format(status))
Gilles Peskine16a25e02019-09-19 12:19:24 +0200135 return stem + "+", data_filename
Gilles Peskine878acd62019-08-01 23:32:38 +0200136
Gilles Peskinefd7ad332019-09-19 12:18:23 +0200137### A list of symbols to test with.
138### This script currently tests what happens when you change a symbol from
139### having a value to not having a value or vice versa. This is not
140### necessarily useful behavior, and we may not consider it a bug if
141### config.py stops handling that case correctly.
Gilles Peskine878acd62019-08-01 23:32:38 +0200142TEST_SYMBOLS = [
Gilles Peskinefd7ad332019-09-19 12:18:23 +0200143 'CUSTOM_SYMBOL', # does not exist
144 'MBEDTLS_AES_C', # set, no value
145 'MBEDTLS_MPI_MAX_SIZE', # unset, has a value
146 'MBEDTLS_NO_UDBL_DIVISION', # unset, in "System support"
147 'MBEDTLS_PLATFORM_ZEROIZE_ALT', # unset, in "Customisation configuration options"
Gilles Peskine878acd62019-08-01 23:32:38 +0200148]
149
150def run_all(options):
151 """Run all the command lines to test."""
152 presets = list_presets(options)
153 for preset in presets:
154 run_one(options, [preset])
155 for symbol in TEST_SYMBOLS:
Gilles Peskine61695e72019-09-13 15:17:01 +0200156 run_one(options, ['get', symbol])
Gilles Peskine16a25e02019-09-19 12:19:24 +0200157 (stem, filename) = run_one(options, ['set', symbol])
158 run_one(options, ['get', symbol], stem_prefix=stem, input_file=filename)
Gilles Peskine878acd62019-08-01 23:32:38 +0200159 run_one(options, ['--force', 'set', symbol])
Gilles Peskine16a25e02019-09-19 12:19:24 +0200160 (stem, filename) = run_one(options, ['set', symbol, 'value'])
161 run_one(options, ['get', symbol], stem_prefix=stem, input_file=filename)
Gilles Peskine878acd62019-08-01 23:32:38 +0200162 run_one(options, ['--force', 'set', symbol, 'value'])
Gilles Peskinef6860422019-09-04 22:51:47 +0200163 run_one(options, ['unset', symbol])
Gilles Peskine878acd62019-08-01 23:32:38 +0200164
165def main():
166 """Command line entry point."""
167 parser = argparse.ArgumentParser(description=__doc__,
168 formatter_class=argparse.RawDescriptionHelpFormatter)
169 parser.add_argument('-d', metavar='DIR',
170 dest='output_directory', required=True,
171 help="""Output directory.""")
172 parser.add_argument('-f', metavar='FILE',
Bence Szépkútibb0cfeb2021-05-28 09:42:25 +0200173 dest='input_file', default='include/mbedtls/mbedtls_config.h',
Gilles Peskine878acd62019-08-01 23:32:38 +0200174 help="""Config file (default: %(default)s).""")
175 parser.add_argument('-p', metavar='PRESET,...',
176 dest='presets',
177 help="""Presets to test (default: guessed from --help).""")
178 parser.add_argument('-s', metavar='FILE',
179 dest='script', default='scripts/config.py',
180 help="""Configuration script (default: %(default)s).""")
181 options = parser.parse_args()
182 prepare_directory(options.output_directory)
183 run_all(options)
184
185if __name__ == '__main__':
186 main()