Skip to content
Open
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 56 additions & 36 deletions scripts/mbedtls_framework/psa_compliance.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
"""Run the PSA Crypto API compliance test suite.
Clone the repo and check out the commit specified by PSA_ARCH_TEST_REPO and PSA_ARCH_TEST_REF,
then compile and run the test suite. The clone is stored at <repository root>/psa-arch-tests.
Known defects in either the test suite or mbedtls / TF-PSA-Crypto - identified by their test
number - are ignored, while unexpected failures AND successes are reported as errors, to help

Clone the psa-arch-tests repo and check out the specified commit.
The clone is stored at <repository root>/psa-arch-tests.
Check out the commit specified by the calling script and apply patches if needed.
Compile the library and the compliance tests and run the test suite.

The calling script can specify a list of expected failures.
Unexpected failures and successes are reported as errors, to help
keep the list of known defects as up to date as possible.
"""

# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later

import argparse
import glob
import os
import re
import shutil
Expand All @@ -25,14 +28,17 @@

#pylint: disable=too-many-branches,too-many-statements,too-many-locals
def test_compliance(library_build_dir: str,
psa_arch_tests_repo: str,
psa_arch_tests_ref: str,
patch_files: List[str],
patch_files: List[Path],
expected_failures: List[int]) -> int:
"""Check out and run compliance tests.

library_build_dir: path where our library will be built.
psa_arch_tests_ref: tag or sha to use for the arch-tests.
patch: patch to apply to the arch-tests with ``patch -p1``.
psa_arch_tests_ref: tag or sha to use for the arch-tests
(empty=default/leave alone).
patch: patch to apply to the arch-tests with ``patch -p1``
(not if psa_arch_tests_ref is empty).
expected_failures: default list of expected failures.
"""
root_dir = os.getcwd()
Expand All @@ -41,6 +47,7 @@ def test_compliance(library_build_dir: str,
tmp_env['CC'] = 'gcc'
subprocess.check_call(['cmake', '.', '-GUnix Makefiles',
'-B' + library_build_dir,
'-DENABLE_TESTING=Off', '-DENABLE_PROGRAMS=Off',
'-DCMAKE_INSTALL_PREFIX=' + str(install_dir)],
env=tmp_env)
subprocess.check_call(['cmake', '--build', library_build_dir, '--target', 'install'])
Expand All @@ -57,15 +64,17 @@ def test_compliance(library_build_dir: str,

# Reuse existing local clone
subprocess.check_call(['git', 'init'])
subprocess.check_call(['git', 'fetch', PSA_ARCH_TESTS_REPO, psa_arch_tests_ref])
subprocess.check_call(['git', 'checkout', '--force', 'FETCH_HEAD'])
subprocess.check_call(['git', 'fetch', psa_arch_tests_repo, psa_arch_tests_ref])

if patch_files:
# Reuse existing working copy if psa_arch_tests_ref is empty.
# Otherwise check out and patch the specified ref.
if psa_arch_tests_ref:
subprocess.check_call(['git', 'checkout', '--force', 'FETCH_HEAD'])
subprocess.check_call(['git', 'reset', '--hard'])
for patch_file in patch_files:
with open(os.path.join(root_dir, patch_file), 'rb') as patch:
subprocess.check_call(['patch', '-p1'],
stdin=patch)
for patch_file in patch_files:
with open(os.path.join(root_dir, patch_file), 'rb') as patch:
subprocess.check_call(['patch', '-p1'],
stdin=patch)

build_dir = 'api-tests/build'
try:
Expand All @@ -89,16 +98,19 @@ def test_compliance(library_build_dir: str,
subprocess.check_call(['cmake', '--build', '.'])

proc = subprocess.Popen(['./psa-arch-tests-crypto'],
bufsize=1, stdout=subprocess.PIPE, universal_newlines=True)
bufsize=1,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True)

test_re = re.compile(
'^TEST: (?P<test_num>[0-9]*)|'
'^TEST RESULT: (?P<test_result>FAILED|PASSED)'
)
test = -1
unexpected_successes = expected_failures.copy()
expected_failures.clear()
unexpected_failures = [] # type: List[int]
unexpected_successes = set(expected_failures)
seen_expected_failures = set()
unexpected_failures = set()
if proc.stdout is None:
return 1

Expand All @@ -111,12 +123,12 @@ def test_compliance(library_build_dir: str,
if test_num is not None:
test = int(test_num)
elif groupdict['test_result'] == 'FAILED':
try:
if test in unexpected_successes:
unexpected_successes.remove(test)
expected_failures.append(test)
seen_expected_failures.add(test)
print('Expected failure, ignoring')
except KeyError:
unexpected_failures.append(test)
else:
unexpected_failures.add(test)
print('ERROR: Unexpected failure')
elif test in unexpected_successes:
print('ERROR: Unexpected success')
Expand All @@ -125,7 +137,7 @@ def test_compliance(library_build_dir: str,
print()
print('***** test_psa_compliance.py report ******')
print()
print('Expected failures:', ', '.join(str(i) for i in expected_failures))
print('Expected failures:', ', '.join(str(i) for i in seen_expected_failures))
print('Unexpected failures:', ', '.join(str(i) for i in unexpected_failures))
print('Unexpected successes:', ', '.join(str(i) for i in sorted(unexpected_successes)))
print()
Expand All @@ -150,26 +162,34 @@ def main(psa_arch_tests_ref: str,
psa_arch_tests_ref: tag or sha to use for the arch-tests.
expected_failures: default list of expected failures.
"""
build_dir = 'out_of_source_build'
default_patch_directory = os.path.join(build_tree.guess_project_root(),
'scripts/data_files/psa-arch-tests')

# pylint: disable=invalid-name
parser = argparse.ArgumentParser()
parser.add_argument('--build-dir', nargs=1,
help='path to Mbed TLS / TF-PSA-Crypto build directory')
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--build-dir',
default='out_of_source_build',
help=('path to Mbed TLS / TF-PSA-Crypto build directory '
'(default: %(default)s)'))
parser.add_argument('--expected-failures', nargs='+',
help='''set the list of test codes which are expected to fail
from the command line. If omitted the list given by
EXPECTED_FAILURES (inside the script) is used.''')
parser.add_argument('--patch-directory', nargs=1,
default=default_patch_directory,
help='Directory containing patches (*.patch) to apply to psa-arch-tests')
help=('Directory containing patches (*.patch) to apply '
'to psa-arch-tests (default: %(default)s)'))
parser.add_argument('--tests-ref', metavar='REF',
default=psa_arch_tests_ref,
help=('Commit (tag/branch/sha) to use for psa-arch-tests '
'(empty to use whatever is there and skip patching) '
'(default: %(default)s)'))
parser.add_argument('--tests-repo', metavar='URL',
default=PSA_ARCH_TESTS_REPO,
help=('Repository to clone for psa-arch-tests '
'(default: %(default)s)'))
args = parser.parse_args()

if args.build_dir is not None:
build_dir = args.build_dir[0]

if expected_failures is None:
expected_failures = []
if args.expected_failures is not None:
Expand All @@ -178,12 +198,12 @@ def main(psa_arch_tests_ref: str,
expected_failures_list = expected_failures

if args.patch_directory:
patch_file_glob = os.path.join(args.patch_directory, '*.patch')
patch_files = sorted(glob.glob(patch_file_glob))
patch_files = sorted(Path(args.patch_directory).glob('*.patch'))
else:
patch_files = []

sys.exit(test_compliance(build_dir,
psa_arch_tests_ref,
sys.exit(test_compliance(args.build_dir,
args.tests_repo,
args.tests_ref,
patch_files,
expected_failures_list))