1*62c56f98SSadaf Ebrahimi#!/usr/bin/env python3 2*62c56f98SSadaf Ebrahimi 3*62c56f98SSadaf Ebrahimi"""Test helper for the Mbed TLS configuration file tool 4*62c56f98SSadaf Ebrahimi 5*62c56f98SSadaf EbrahimiRun config.py with various parameters and write the results to files. 6*62c56f98SSadaf Ebrahimi 7*62c56f98SSadaf EbrahimiThis is a harness to help regression testing, not a functional tester. 8*62c56f98SSadaf EbrahimiSample usage: 9*62c56f98SSadaf Ebrahimi 10*62c56f98SSadaf Ebrahimi test_config_script.py -d old 11*62c56f98SSadaf Ebrahimi ## Modify config.py and/or mbedtls_config.h ## 12*62c56f98SSadaf Ebrahimi test_config_script.py -d new 13*62c56f98SSadaf Ebrahimi diff -ru old new 14*62c56f98SSadaf Ebrahimi""" 15*62c56f98SSadaf Ebrahimi 16*62c56f98SSadaf Ebrahimi## Copyright The Mbed TLS Contributors 17*62c56f98SSadaf Ebrahimi## SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later 18*62c56f98SSadaf Ebrahimi## 19*62c56f98SSadaf Ebrahimi 20*62c56f98SSadaf Ebrahimiimport argparse 21*62c56f98SSadaf Ebrahimiimport glob 22*62c56f98SSadaf Ebrahimiimport os 23*62c56f98SSadaf Ebrahimiimport re 24*62c56f98SSadaf Ebrahimiimport shutil 25*62c56f98SSadaf Ebrahimiimport subprocess 26*62c56f98SSadaf Ebrahimi 27*62c56f98SSadaf EbrahimiOUTPUT_FILE_PREFIX = 'config-' 28*62c56f98SSadaf Ebrahimi 29*62c56f98SSadaf Ebrahimidef output_file_name(directory, stem, extension): 30*62c56f98SSadaf Ebrahimi return os.path.join(directory, 31*62c56f98SSadaf Ebrahimi '{}{}.{}'.format(OUTPUT_FILE_PREFIX, 32*62c56f98SSadaf Ebrahimi stem, extension)) 33*62c56f98SSadaf Ebrahimi 34*62c56f98SSadaf Ebrahimidef cleanup_directory(directory): 35*62c56f98SSadaf Ebrahimi """Remove old output files.""" 36*62c56f98SSadaf Ebrahimi for extension in []: 37*62c56f98SSadaf Ebrahimi pattern = output_file_name(directory, '*', extension) 38*62c56f98SSadaf Ebrahimi filenames = glob.glob(pattern) 39*62c56f98SSadaf Ebrahimi for filename in filenames: 40*62c56f98SSadaf Ebrahimi os.remove(filename) 41*62c56f98SSadaf Ebrahimi 42*62c56f98SSadaf Ebrahimidef prepare_directory(directory): 43*62c56f98SSadaf Ebrahimi """Create the output directory if it doesn't exist yet. 44*62c56f98SSadaf Ebrahimi 45*62c56f98SSadaf Ebrahimi If there are old output files, remove them. 46*62c56f98SSadaf Ebrahimi """ 47*62c56f98SSadaf Ebrahimi if os.path.exists(directory): 48*62c56f98SSadaf Ebrahimi cleanup_directory(directory) 49*62c56f98SSadaf Ebrahimi else: 50*62c56f98SSadaf Ebrahimi os.makedirs(directory) 51*62c56f98SSadaf Ebrahimi 52*62c56f98SSadaf Ebrahimidef guess_presets_from_help(help_text): 53*62c56f98SSadaf Ebrahimi """Figure out what presets the script supports. 54*62c56f98SSadaf Ebrahimi 55*62c56f98SSadaf Ebrahimi help_text should be the output from running the script with --help. 56*62c56f98SSadaf Ebrahimi """ 57*62c56f98SSadaf Ebrahimi # Try the output format from config.py 58*62c56f98SSadaf Ebrahimi hits = re.findall(r'\{([-\w,]+)\}', help_text) 59*62c56f98SSadaf Ebrahimi for hit in hits: 60*62c56f98SSadaf Ebrahimi words = set(hit.split(',')) 61*62c56f98SSadaf Ebrahimi if 'get' in words and 'set' in words and 'unset' in words: 62*62c56f98SSadaf Ebrahimi words.remove('get') 63*62c56f98SSadaf Ebrahimi words.remove('set') 64*62c56f98SSadaf Ebrahimi words.remove('unset') 65*62c56f98SSadaf Ebrahimi return words 66*62c56f98SSadaf Ebrahimi # Try the output format from config.pl 67*62c56f98SSadaf Ebrahimi hits = re.findall(r'\n +([-\w]+) +- ', help_text) 68*62c56f98SSadaf Ebrahimi if hits: 69*62c56f98SSadaf Ebrahimi return hits 70*62c56f98SSadaf Ebrahimi raise Exception("Unable to figure out supported presets. Pass the '-p' option.") 71*62c56f98SSadaf Ebrahimi 72*62c56f98SSadaf Ebrahimidef list_presets(options): 73*62c56f98SSadaf Ebrahimi """Return the list of presets to test. 74*62c56f98SSadaf Ebrahimi 75*62c56f98SSadaf Ebrahimi The list is taken from the command line if present, otherwise it is 76*62c56f98SSadaf Ebrahimi extracted from running the config script with --help. 77*62c56f98SSadaf Ebrahimi """ 78*62c56f98SSadaf Ebrahimi if options.presets: 79*62c56f98SSadaf Ebrahimi return re.split(r'[ ,]+', options.presets) 80*62c56f98SSadaf Ebrahimi else: 81*62c56f98SSadaf Ebrahimi help_text = subprocess.run([options.script, '--help'], 82*62c56f98SSadaf Ebrahimi check=False, # config.pl --help returns 255 83*62c56f98SSadaf Ebrahimi stdout=subprocess.PIPE, 84*62c56f98SSadaf Ebrahimi stderr=subprocess.STDOUT).stdout 85*62c56f98SSadaf Ebrahimi return guess_presets_from_help(help_text.decode('ascii')) 86*62c56f98SSadaf Ebrahimi 87*62c56f98SSadaf Ebrahimidef run_one(options, args, stem_prefix='', input_file=None): 88*62c56f98SSadaf Ebrahimi """Run the config script with the given arguments. 89*62c56f98SSadaf Ebrahimi 90*62c56f98SSadaf Ebrahimi Take the original content from input_file if specified, defaulting 91*62c56f98SSadaf Ebrahimi to options.input_file if input_file is None. 92*62c56f98SSadaf Ebrahimi 93*62c56f98SSadaf Ebrahimi Write the following files, where xxx contains stem_prefix followed by 94*62c56f98SSadaf Ebrahimi a filename-friendly encoding of args: 95*62c56f98SSadaf Ebrahimi * config-xxx.h: modified file. 96*62c56f98SSadaf Ebrahimi * config-xxx.out: standard output. 97*62c56f98SSadaf Ebrahimi * config-xxx.err: standard output. 98*62c56f98SSadaf Ebrahimi * config-xxx.status: exit code. 99*62c56f98SSadaf Ebrahimi 100*62c56f98SSadaf Ebrahimi Return ("xxx+", "path/to/config-xxx.h") which can be used as 101*62c56f98SSadaf Ebrahimi stem_prefix and input_file to call this function again with new args. 102*62c56f98SSadaf Ebrahimi """ 103*62c56f98SSadaf Ebrahimi if input_file is None: 104*62c56f98SSadaf Ebrahimi input_file = options.input_file 105*62c56f98SSadaf Ebrahimi stem = stem_prefix + '-'.join(args) 106*62c56f98SSadaf Ebrahimi data_filename = output_file_name(options.output_directory, stem, 'h') 107*62c56f98SSadaf Ebrahimi stdout_filename = output_file_name(options.output_directory, stem, 'out') 108*62c56f98SSadaf Ebrahimi stderr_filename = output_file_name(options.output_directory, stem, 'err') 109*62c56f98SSadaf Ebrahimi status_filename = output_file_name(options.output_directory, stem, 'status') 110*62c56f98SSadaf Ebrahimi shutil.copy(input_file, data_filename) 111*62c56f98SSadaf Ebrahimi # Pass only the file basename, not the full path, to avoid getting the 112*62c56f98SSadaf Ebrahimi # directory name in error messages, which would make comparisons 113*62c56f98SSadaf Ebrahimi # between output directories more difficult. 114*62c56f98SSadaf Ebrahimi cmd = [os.path.abspath(options.script), 115*62c56f98SSadaf Ebrahimi '-f', os.path.basename(data_filename)] 116*62c56f98SSadaf Ebrahimi with open(stdout_filename, 'wb') as out: 117*62c56f98SSadaf Ebrahimi with open(stderr_filename, 'wb') as err: 118*62c56f98SSadaf Ebrahimi status = subprocess.call(cmd + args, 119*62c56f98SSadaf Ebrahimi cwd=options.output_directory, 120*62c56f98SSadaf Ebrahimi stdin=subprocess.DEVNULL, 121*62c56f98SSadaf Ebrahimi stdout=out, stderr=err) 122*62c56f98SSadaf Ebrahimi with open(status_filename, 'w') as status_file: 123*62c56f98SSadaf Ebrahimi status_file.write('{}\n'.format(status)) 124*62c56f98SSadaf Ebrahimi return stem + "+", data_filename 125*62c56f98SSadaf Ebrahimi 126*62c56f98SSadaf Ebrahimi### A list of symbols to test with. 127*62c56f98SSadaf Ebrahimi### This script currently tests what happens when you change a symbol from 128*62c56f98SSadaf Ebrahimi### having a value to not having a value or vice versa. This is not 129*62c56f98SSadaf Ebrahimi### necessarily useful behavior, and we may not consider it a bug if 130*62c56f98SSadaf Ebrahimi### config.py stops handling that case correctly. 131*62c56f98SSadaf EbrahimiTEST_SYMBOLS = [ 132*62c56f98SSadaf Ebrahimi 'CUSTOM_SYMBOL', # does not exist 133*62c56f98SSadaf Ebrahimi 'MBEDTLS_AES_C', # set, no value 134*62c56f98SSadaf Ebrahimi 'MBEDTLS_MPI_MAX_SIZE', # unset, has a value 135*62c56f98SSadaf Ebrahimi 'MBEDTLS_NO_UDBL_DIVISION', # unset, in "System support" 136*62c56f98SSadaf Ebrahimi 'MBEDTLS_PLATFORM_ZEROIZE_ALT', # unset, in "Customisation configuration options" 137*62c56f98SSadaf Ebrahimi] 138*62c56f98SSadaf Ebrahimi 139*62c56f98SSadaf Ebrahimidef run_all(options): 140*62c56f98SSadaf Ebrahimi """Run all the command lines to test.""" 141*62c56f98SSadaf Ebrahimi presets = list_presets(options) 142*62c56f98SSadaf Ebrahimi for preset in presets: 143*62c56f98SSadaf Ebrahimi run_one(options, [preset]) 144*62c56f98SSadaf Ebrahimi for symbol in TEST_SYMBOLS: 145*62c56f98SSadaf Ebrahimi run_one(options, ['get', symbol]) 146*62c56f98SSadaf Ebrahimi (stem, filename) = run_one(options, ['set', symbol]) 147*62c56f98SSadaf Ebrahimi run_one(options, ['get', symbol], stem_prefix=stem, input_file=filename) 148*62c56f98SSadaf Ebrahimi run_one(options, ['--force', 'set', symbol]) 149*62c56f98SSadaf Ebrahimi (stem, filename) = run_one(options, ['set', symbol, 'value']) 150*62c56f98SSadaf Ebrahimi run_one(options, ['get', symbol], stem_prefix=stem, input_file=filename) 151*62c56f98SSadaf Ebrahimi run_one(options, ['--force', 'set', symbol, 'value']) 152*62c56f98SSadaf Ebrahimi run_one(options, ['unset', symbol]) 153*62c56f98SSadaf Ebrahimi 154*62c56f98SSadaf Ebrahimidef main(): 155*62c56f98SSadaf Ebrahimi """Command line entry point.""" 156*62c56f98SSadaf Ebrahimi parser = argparse.ArgumentParser(description=__doc__, 157*62c56f98SSadaf Ebrahimi formatter_class=argparse.RawDescriptionHelpFormatter) 158*62c56f98SSadaf Ebrahimi parser.add_argument('-d', metavar='DIR', 159*62c56f98SSadaf Ebrahimi dest='output_directory', required=True, 160*62c56f98SSadaf Ebrahimi help="""Output directory.""") 161*62c56f98SSadaf Ebrahimi parser.add_argument('-f', metavar='FILE', 162*62c56f98SSadaf Ebrahimi dest='input_file', default='include/mbedtls/mbedtls_config.h', 163*62c56f98SSadaf Ebrahimi help="""Config file (default: %(default)s).""") 164*62c56f98SSadaf Ebrahimi parser.add_argument('-p', metavar='PRESET,...', 165*62c56f98SSadaf Ebrahimi dest='presets', 166*62c56f98SSadaf Ebrahimi help="""Presets to test (default: guessed from --help).""") 167*62c56f98SSadaf Ebrahimi parser.add_argument('-s', metavar='FILE', 168*62c56f98SSadaf Ebrahimi dest='script', default='scripts/config.py', 169*62c56f98SSadaf Ebrahimi help="""Configuration script (default: %(default)s).""") 170*62c56f98SSadaf Ebrahimi options = parser.parse_args() 171*62c56f98SSadaf Ebrahimi prepare_directory(options.output_directory) 172*62c56f98SSadaf Ebrahimi run_all(options) 173*62c56f98SSadaf Ebrahimi 174*62c56f98SSadaf Ebrahimiif __name__ == '__main__': 175*62c56f98SSadaf Ebrahimi main() 176