| #!/usr/bin/env python3 |
| """Describe the test coverage of PSA functions in terms of return statuses. |
| |
| 1. Build Mbed Crypto with -DRECORD_PSA_STATUS_COVERAGE_LOG |
| 2. Run psa_collect_statuses.py |
| |
| The output is a series of line of the form "psa_foo PSA_ERROR_XXX". Each |
| function/status combination appears only once. |
| |
| This script must be run from the top of an Mbed Crypto source tree. |
| The build command is "make -DRECORD_PSA_STATUS_COVERAGE_LOG", which is |
| only supported with make (as opposed to CMake or other build methods). |
| """ |
| |
| # Copyright The Mbed TLS Contributors |
| # SPDX-License-Identifier: Apache-2.0 |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may |
| # not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import argparse |
| import os |
| import subprocess |
| import sys |
| |
| DEFAULT_STATUS_LOG_FILE = 'tests/statuses.log' |
| DEFAULT_PSA_CONSTANT_NAMES = 'programs/psa/psa_constant_names' |
| |
| class Statuses: |
| """Information about observed return statues of API functions.""" |
| |
| def __init__(self): |
| self.functions = {} |
| self.codes = set() |
| self.status_names = {} |
| |
| def collect_log(self, log_file_name): |
| """Read logs from RECORD_PSA_STATUS_COVERAGE_LOG. |
| |
| Read logs produced by running Mbed Crypto test suites built with |
| -DRECORD_PSA_STATUS_COVERAGE_LOG. |
| """ |
| with open(log_file_name) as log: |
| for line in log: |
| value, function, tail = line.split(':', 2) |
| if function not in self.functions: |
| self.functions[function] = {} |
| fdata = self.functions[function] |
| if value not in self.functions[function]: |
| fdata[value] = [] |
| fdata[value].append(tail) |
| self.codes.add(int(value)) |
| |
| def get_constant_names(self, psa_constant_names): |
| """Run psa_constant_names to obtain names for observed numerical values.""" |
| values = [str(value) for value in self.codes] |
| cmd = [psa_constant_names, 'status'] + values |
| output = subprocess.check_output(cmd).decode('ascii') |
| for value, name in zip(values, output.rstrip().split('\n')): |
| self.status_names[value] = name |
| |
| def report(self): |
| """Report observed return values for each function. |
| |
| The report is a series of line of the form "psa_foo PSA_ERROR_XXX". |
| """ |
| for function in sorted(self.functions.keys()): |
| fdata = self.functions[function] |
| names = [self.status_names[value] for value in fdata.keys()] |
| for name in sorted(names): |
| sys.stdout.write('{} {}\n'.format(function, name)) |
| |
| def collect_status_logs(options): |
| """Build and run unit tests and report observed function return statuses. |
| |
| Build Mbed Crypto with -DRECORD_PSA_STATUS_COVERAGE_LOG, run the |
| test suites and display information about observed return statuses. |
| """ |
| rebuilt = False |
| if not options.use_existing_log and os.path.exists(options.log_file): |
| os.remove(options.log_file) |
| if not os.path.exists(options.log_file): |
| if options.clean_before: |
| subprocess.check_call(['make', 'clean'], |
| cwd='tests', |
| stdout=sys.stderr) |
| with open(os.devnull, 'w') as devnull: |
| make_q_ret = subprocess.call(['make', '-q', 'lib', 'tests'], |
| stdout=devnull, stderr=devnull) |
| if make_q_ret != 0: |
| subprocess.check_call(['make', 'RECORD_PSA_STATUS_COVERAGE_LOG=1'], |
| stdout=sys.stderr) |
| rebuilt = True |
| subprocess.check_call(['make', 'test'], |
| stdout=sys.stderr) |
| data = Statuses() |
| data.collect_log(options.log_file) |
| data.get_constant_names(options.psa_constant_names) |
| if rebuilt and options.clean_after: |
| subprocess.check_call(['make', 'clean'], |
| cwd='tests', |
| stdout=sys.stderr) |
| return data |
| |
| def main(): |
| parser = argparse.ArgumentParser(description=globals()['__doc__']) |
| parser.add_argument('--clean-after', |
| action='store_true', |
| help='Run "make clean" after rebuilding') |
| parser.add_argument('--clean-before', |
| action='store_true', |
| help='Run "make clean" before regenerating the log file)') |
| parser.add_argument('--log-file', metavar='FILE', |
| default=DEFAULT_STATUS_LOG_FILE, |
| help='Log file location (default: {})'.format( |
| DEFAULT_STATUS_LOG_FILE |
| )) |
| parser.add_argument('--psa-constant-names', metavar='PROGRAM', |
| default=DEFAULT_PSA_CONSTANT_NAMES, |
| help='Path to psa_constant_names (default: {})'.format( |
| DEFAULT_PSA_CONSTANT_NAMES |
| )) |
| parser.add_argument('--use-existing-log', '-e', |
| action='store_true', |
| help='Don\'t regenerate the log file if it exists') |
| options = parser.parse_args() |
| data = collect_status_logs(options) |
| data.report() |
| |
| if __name__ == '__main__': |
| main() |