1a8e1175bSopenharmony_ci#!/usr/bin/env python3 2a8e1175bSopenharmony_ci"""Describe the test coverage of PSA functions in terms of return statuses. 3a8e1175bSopenharmony_ci 4a8e1175bSopenharmony_ci1. Build Mbed TLS with -DRECORD_PSA_STATUS_COVERAGE_LOG 5a8e1175bSopenharmony_ci2. Run psa_collect_statuses.py 6a8e1175bSopenharmony_ci 7a8e1175bSopenharmony_ciThe output is a series of line of the form "psa_foo PSA_ERROR_XXX". Each 8a8e1175bSopenharmony_cifunction/status combination appears only once. 9a8e1175bSopenharmony_ci 10a8e1175bSopenharmony_ciThis script must be run from the top of an Mbed TLS source tree. 11a8e1175bSopenharmony_ciThe build command is "make -DRECORD_PSA_STATUS_COVERAGE_LOG", which is 12a8e1175bSopenharmony_cionly supported with make (as opposed to CMake or other build methods). 13a8e1175bSopenharmony_ci""" 14a8e1175bSopenharmony_ci 15a8e1175bSopenharmony_ci# Copyright The Mbed TLS Contributors 16a8e1175bSopenharmony_ci# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later 17a8e1175bSopenharmony_ci 18a8e1175bSopenharmony_ciimport argparse 19a8e1175bSopenharmony_ciimport os 20a8e1175bSopenharmony_ciimport subprocess 21a8e1175bSopenharmony_ciimport sys 22a8e1175bSopenharmony_ci 23a8e1175bSopenharmony_ciDEFAULT_STATUS_LOG_FILE = 'tests/statuses.log' 24a8e1175bSopenharmony_ciDEFAULT_PSA_CONSTANT_NAMES = 'programs/psa/psa_constant_names' 25a8e1175bSopenharmony_ci 26a8e1175bSopenharmony_ciclass Statuses: 27a8e1175bSopenharmony_ci """Information about observed return statues of API functions.""" 28a8e1175bSopenharmony_ci 29a8e1175bSopenharmony_ci def __init__(self): 30a8e1175bSopenharmony_ci self.functions = {} 31a8e1175bSopenharmony_ci self.codes = set() 32a8e1175bSopenharmony_ci self.status_names = {} 33a8e1175bSopenharmony_ci 34a8e1175bSopenharmony_ci def collect_log(self, log_file_name): 35a8e1175bSopenharmony_ci """Read logs from RECORD_PSA_STATUS_COVERAGE_LOG. 36a8e1175bSopenharmony_ci 37a8e1175bSopenharmony_ci Read logs produced by running Mbed TLS test suites built with 38a8e1175bSopenharmony_ci -DRECORD_PSA_STATUS_COVERAGE_LOG. 39a8e1175bSopenharmony_ci """ 40a8e1175bSopenharmony_ci with open(log_file_name) as log: 41a8e1175bSopenharmony_ci for line in log: 42a8e1175bSopenharmony_ci value, function, tail = line.split(':', 2) 43a8e1175bSopenharmony_ci if function not in self.functions: 44a8e1175bSopenharmony_ci self.functions[function] = {} 45a8e1175bSopenharmony_ci fdata = self.functions[function] 46a8e1175bSopenharmony_ci if value not in self.functions[function]: 47a8e1175bSopenharmony_ci fdata[value] = [] 48a8e1175bSopenharmony_ci fdata[value].append(tail) 49a8e1175bSopenharmony_ci self.codes.add(int(value)) 50a8e1175bSopenharmony_ci 51a8e1175bSopenharmony_ci def get_constant_names(self, psa_constant_names): 52a8e1175bSopenharmony_ci """Run psa_constant_names to obtain names for observed numerical values.""" 53a8e1175bSopenharmony_ci values = [str(value) for value in self.codes] 54a8e1175bSopenharmony_ci cmd = [psa_constant_names, 'status'] + values 55a8e1175bSopenharmony_ci output = subprocess.check_output(cmd).decode('ascii') 56a8e1175bSopenharmony_ci for value, name in zip(values, output.rstrip().split('\n')): 57a8e1175bSopenharmony_ci self.status_names[value] = name 58a8e1175bSopenharmony_ci 59a8e1175bSopenharmony_ci def report(self): 60a8e1175bSopenharmony_ci """Report observed return values for each function. 61a8e1175bSopenharmony_ci 62a8e1175bSopenharmony_ci The report is a series of line of the form "psa_foo PSA_ERROR_XXX". 63a8e1175bSopenharmony_ci """ 64a8e1175bSopenharmony_ci for function in sorted(self.functions.keys()): 65a8e1175bSopenharmony_ci fdata = self.functions[function] 66a8e1175bSopenharmony_ci names = [self.status_names[value] for value in fdata.keys()] 67a8e1175bSopenharmony_ci for name in sorted(names): 68a8e1175bSopenharmony_ci sys.stdout.write('{} {}\n'.format(function, name)) 69a8e1175bSopenharmony_ci 70a8e1175bSopenharmony_cidef collect_status_logs(options): 71a8e1175bSopenharmony_ci """Build and run unit tests and report observed function return statuses. 72a8e1175bSopenharmony_ci 73a8e1175bSopenharmony_ci Build Mbed TLS with -DRECORD_PSA_STATUS_COVERAGE_LOG, run the 74a8e1175bSopenharmony_ci test suites and display information about observed return statuses. 75a8e1175bSopenharmony_ci """ 76a8e1175bSopenharmony_ci rebuilt = False 77a8e1175bSopenharmony_ci if not options.use_existing_log and os.path.exists(options.log_file): 78a8e1175bSopenharmony_ci os.remove(options.log_file) 79a8e1175bSopenharmony_ci if not os.path.exists(options.log_file): 80a8e1175bSopenharmony_ci if options.clean_before: 81a8e1175bSopenharmony_ci subprocess.check_call(['make', 'clean'], 82a8e1175bSopenharmony_ci cwd='tests', 83a8e1175bSopenharmony_ci stdout=sys.stderr) 84a8e1175bSopenharmony_ci with open(os.devnull, 'w') as devnull: 85a8e1175bSopenharmony_ci make_q_ret = subprocess.call(['make', '-q', 'lib', 'tests'], 86a8e1175bSopenharmony_ci stdout=devnull, stderr=devnull) 87a8e1175bSopenharmony_ci if make_q_ret != 0: 88a8e1175bSopenharmony_ci subprocess.check_call(['make', 'RECORD_PSA_STATUS_COVERAGE_LOG=1'], 89a8e1175bSopenharmony_ci stdout=sys.stderr) 90a8e1175bSopenharmony_ci rebuilt = True 91a8e1175bSopenharmony_ci subprocess.check_call(['make', 'test'], 92a8e1175bSopenharmony_ci stdout=sys.stderr) 93a8e1175bSopenharmony_ci data = Statuses() 94a8e1175bSopenharmony_ci data.collect_log(options.log_file) 95a8e1175bSopenharmony_ci data.get_constant_names(options.psa_constant_names) 96a8e1175bSopenharmony_ci if rebuilt and options.clean_after: 97a8e1175bSopenharmony_ci subprocess.check_call(['make', 'clean'], 98a8e1175bSopenharmony_ci cwd='tests', 99a8e1175bSopenharmony_ci stdout=sys.stderr) 100a8e1175bSopenharmony_ci return data 101a8e1175bSopenharmony_ci 102a8e1175bSopenharmony_cidef main(): 103a8e1175bSopenharmony_ci parser = argparse.ArgumentParser(description=globals()['__doc__']) 104a8e1175bSopenharmony_ci parser.add_argument('--clean-after', 105a8e1175bSopenharmony_ci action='store_true', 106a8e1175bSopenharmony_ci help='Run "make clean" after rebuilding') 107a8e1175bSopenharmony_ci parser.add_argument('--clean-before', 108a8e1175bSopenharmony_ci action='store_true', 109a8e1175bSopenharmony_ci help='Run "make clean" before regenerating the log file)') 110a8e1175bSopenharmony_ci parser.add_argument('--log-file', metavar='FILE', 111a8e1175bSopenharmony_ci default=DEFAULT_STATUS_LOG_FILE, 112a8e1175bSopenharmony_ci help='Log file location (default: {})'.format( 113a8e1175bSopenharmony_ci DEFAULT_STATUS_LOG_FILE 114a8e1175bSopenharmony_ci )) 115a8e1175bSopenharmony_ci parser.add_argument('--psa-constant-names', metavar='PROGRAM', 116a8e1175bSopenharmony_ci default=DEFAULT_PSA_CONSTANT_NAMES, 117a8e1175bSopenharmony_ci help='Path to psa_constant_names (default: {})'.format( 118a8e1175bSopenharmony_ci DEFAULT_PSA_CONSTANT_NAMES 119a8e1175bSopenharmony_ci )) 120a8e1175bSopenharmony_ci parser.add_argument('--use-existing-log', '-e', 121a8e1175bSopenharmony_ci action='store_true', 122a8e1175bSopenharmony_ci help='Don\'t regenerate the log file if it exists') 123a8e1175bSopenharmony_ci options = parser.parse_args() 124a8e1175bSopenharmony_ci data = collect_status_logs(options) 125a8e1175bSopenharmony_ci data.report() 126a8e1175bSopenharmony_ci 127a8e1175bSopenharmony_ciif __name__ == '__main__': 128a8e1175bSopenharmony_ci main() 129