123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- #!/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()
|