From 2eaf382689601b4ef74f40c2f30c0ff1d4efebdd Mon Sep 17 00:00:00 2001 From: Thom Wiggers Date: Fri, 6 Sep 2019 12:01:44 +0200 Subject: [PATCH] Add support for specifying architecture and feature limits (#224) * Add support for specifying architecture and feature limits * cpuinfo not supported on ppc * Detect 32-bit python interpreter on 64-bit CPU * Fix bugs in isolated tests * Also support restricting operating system --- requirements.txt | 1 + test/Makefile | 2 +- test/pqclean.py | 54 ++++++++++++++++++++++++++++++ test/test_compile_lib.py | 4 +-- test/test_dynamic_memory.py | 4 +-- test/test_functest.py | 8 ++--- test/test_linter.py | 2 +- test/test_makefile_dependencies.py | 4 +-- test/test_makefiles_present.py | 17 ++++++++-- test/test_metadata.py | 25 ++++++++++++++ test/test_metadata_sizes.py | 2 +- test/test_nistkat.py | 4 +-- test/test_symbol_namespace.py | 4 +-- test/test_testvectors.py | 3 ++ test/test_valgrind.py | 4 +-- 15 files changed, 117 insertions(+), 21 deletions(-) diff --git a/requirements.txt b/requirements.txt index 15f0f2cd..c27304a2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ PyYAML pytest pytest-xdist pycparser +py-cpuinfo diff --git a/test/Makefile b/test/Makefile index 188ae827..bc89391c 100644 --- a/test/Makefile +++ b/test/Makefile @@ -69,7 +69,7 @@ $(DEST_DIR)/testvectors_$(SCHEME)_$(IMPLEMENTATION): build-scheme crypto_$(TYPE) mkdir -p $(DEST_DIR) $(CC) $(CFLAGS) -DPQCLEAN_NAMESPACE=PQCLEAN_$(SCHEME_UPPERCASE)_$(IMPLEMENTATION_UPPERCASE) -I$(SCHEME_DIR) crypto_$(TYPE)/testvectors.c $(COMMON_FILES) $(TEST_COMMON_DIR)/notrandombytes.c -o $@ -L$(SCHEME_DIR) -l$(SCHEME)_$(IMPLEMENTATION) -$(DEST_DIR)/printparams_$(SCHEME)_$(IMPLEMENTATION): build-scheme crypto_$(TYPE)/printparams.c +$(DEST_DIR)/printparams_$(SCHEME)_$(IMPLEMENTATION): crypto_$(TYPE)/printparams.c mkdir -p $(DEST_DIR) $(CC) $(CFLAGS) -DPQCLEAN_NAMESPACE=PQCLEAN_$(SCHEME_UPPERCASE)_$(IMPLEMENTATION_UPPERCASE) -I$(SCHEME_DIR) crypto_$(TYPE)/printparams.c -o $@ diff --git a/test/pqclean.py b/test/pqclean.py index 4a1cb35a..16da66fb 100644 --- a/test/pqclean.py +++ b/test/pqclean.py @@ -1,7 +1,9 @@ import glob import os +from typing import Optional import yaml +import platform class Scheme: @@ -37,6 +39,11 @@ class Scheme: implementations.extend(scheme.implementations) return implementations + @staticmethod + def all_supported_implementations(): + return [impl for impl in Scheme.all_implementations() + if impl.supported_on_current_platform()] + @staticmethod def all_schemes_of_type(type: str) -> list: schemes = [] @@ -111,10 +118,57 @@ class Implementation: implementations.append(Implementation(scheme, d)) return implementations + @staticmethod + def all_supported_implementations(scheme: Scheme) -> list: + return [impl for impl in Implementation.all_implementations(scheme) + if impl.supported_on_current_platform()] + def namespace_prefix(self): return '{}{}_'.format(self.scheme.namespace_prefix(), self.name.upper()).replace('-', '') + def supported_on_os(self, os: Optional[str] = None) -> bool: + """Check if we support the OS + + If no OS is specified, then we run on the current OS + """ + if os is None: + os = platform.system() + + for platform_ in self.metadata().get('supported_platforms', []): + if 'operating_systems' in platform_: + if os not in platform_['operating_systems']: + return False + + return True + + def supported_on_current_platform(self) -> bool: + if 'supported_platforms' not in self.metadata(): + return True + + if platform.machine() == 'ppc': + return False + + if not self.supported_on_os(): + return False + + if not hasattr(Implementation, 'CPUINFO'): + import cpuinfo + Implementation.CPUINFO = cpuinfo.get_cpu_info() + + CPUINFO = Implementation.CPUINFO + + for platform_ in self.metadata()['supported_platforms']: + if platform_['architecture'] == CPUINFO['arch'].lower(): + # Detect actually running on emulated i386 + if (platform_['architecture'] == 'x86_64' and + platform.architecture()[0] == '32bit'): + continue + if all([flag in CPUINFO['flags'] + for flag in platform_['required_flags']]): + return True + return False + def __str__(self): return "{} implementation of {}".format(self.name, self.scheme.name) diff --git a/test/test_compile_lib.py b/test/test_compile_lib.py index 3a6c247e..7d361855 100644 --- a/test/test_compile_lib.py +++ b/test/test_compile_lib.py @@ -11,8 +11,8 @@ import pqclean @pytest.mark.parametrize( 'implementation,test_dir,impl_dir, init, destr', [(impl, *helpers.isolate_test_files(impl.path(), 'test_functest_')) - for impl in pqclean.Scheme.all_implementations()], - ids=[str(impl) for impl in pqclean.Scheme.all_implementations()], + for impl in pqclean.Scheme.all_supported_implementations()], + ids=[str(impl) for impl in pqclean.Scheme.all_supported_implementations()], ) @helpers.filtered_test def test_compile_lib(implementation, test_dir, impl_dir, init, destr): diff --git a/test/test_dynamic_memory.py b/test/test_dynamic_memory.py index b43597de..756b14d1 100644 --- a/test/test_dynamic_memory.py +++ b/test/test_dynamic_memory.py @@ -11,8 +11,8 @@ import pqclean @pytest.mark.parametrize( 'implementation,test_dir,impl_path, init, destr', [(impl, *helpers.isolate_test_files(impl.path(), 'test_functest_')) - for impl in pqclean.Scheme.all_implementations()], - ids=[str(impl) for impl in pqclean.Scheme.all_implementations()], + for impl in pqclean.Scheme.all_supported_implementations()], + ids=[str(impl) for impl in pqclean.Scheme.all_supported_implementations()], ) @helpers.skip_windows() @helpers.filtered_test diff --git a/test/test_functest.py b/test/test_functest.py index e0b6c67d..a9144b98 100644 --- a/test/test_functest.py +++ b/test/test_functest.py @@ -16,8 +16,8 @@ import pqclean @pytest.mark.parametrize( 'implementation,test_dir,impl_path, init, destr', [(impl, *helpers.isolate_test_files(impl.path(), 'test_functest_')) - for impl in pqclean.Scheme.all_implementations()], - ids=[str(impl) for impl in pqclean.Scheme.all_implementations()], + for impl in pqclean.Scheme.all_supported_implementations()], + ids=[str(impl) for impl in pqclean.Scheme.all_supported_implementations()], ) @helpers.filtered_test def test_functest(implementation, impl_path, test_dir, @@ -45,8 +45,8 @@ def test_functest(implementation, impl_path, test_dir, 'implementation,test_dir,impl_path, init, destr', [(impl, *helpers.isolate_test_files(impl.path(), 'test_functest_sanitizers_')) - for impl in pqclean.Scheme.all_implementations()], - ids=[str(impl) for impl in pqclean.Scheme.all_implementations()], + for impl in pqclean.Scheme.all_supported_implementations()], + ids=[str(impl) for impl in pqclean.Scheme.all_supported_implementations()], ) @helpers.skip_windows() @helpers.filtered_test diff --git a/test/test_linter.py b/test/test_linter.py index d48e856f..204a4c35 100644 --- a/test/test_linter.py +++ b/test/test_linter.py @@ -13,7 +13,7 @@ additional_flags = [] #['-fix-errors'] @pytest.mark.parametrize( 'implementation', - pqclean.Scheme.all_implementations(), + pqclean.Scheme.all_supported_implementations(), ids=str, ) @helpers.skip_windows() diff --git a/test/test_makefile_dependencies.py b/test/test_makefile_dependencies.py index 6b255f37..40679f50 100644 --- a/test/test_makefile_dependencies.py +++ b/test/test_makefile_dependencies.py @@ -17,8 +17,8 @@ import pqclean 'implementation,test_dir,impl_path, init, destr', [(impl, *helpers.isolate_test_files(impl.path(), 'test_makefile_deps_')) - for impl in pqclean.Scheme.all_implementations()], - ids=[str(impl) for impl in pqclean.Scheme.all_implementations()], + for impl in pqclean.Scheme.all_supported_implementations()], + ids=[str(impl) for impl in pqclean.Scheme.all_supported_implementations()], ) @helpers.filtered_test def test_makefile_dependencies(implementation, impl_path, test_dir, diff --git a/test/test_makefiles_present.py b/test/test_makefiles_present.py index 4f54239f..27912fca 100644 --- a/test/test_makefiles_present.py +++ b/test/test_makefiles_present.py @@ -17,10 +17,23 @@ import pqclean ids=str, ) @helpers.filtered_test -def test_makefiles_present(implementation): +def test_makefile_present(implementation): p1 = os.path.join(implementation.path(), 'Makefile') + assert os.path.isfile(p1) + + +@pytest.mark.parametrize( + 'implementation', + pqclean.Scheme.all_implementations(), + ids=str, +) +@helpers.filtered_test +def test_microsoft_nmakefile_present(implementation): p2 = os.path.join(implementation.path(), 'Makefile.Microsoft_nmake') - assert(os.path.isfile(p1) and os.path.isfile(p2)) + if implementation.supported_on_os(os='Windows'): + assert os.path.isfile(p2) + else: + assert not os.path.isfile(p2), "Should not have an NMake file" if __name__ == '__main__': diff --git a/test/test_metadata.py b/test/test_metadata.py index af4eee85..15b3faf5 100644 --- a/test/test_metadata.py +++ b/test/test_metadata.py @@ -61,6 +61,31 @@ EXPECTED_FIELDS = { 'spec': { 'name': {'type': str}, 'version': {'type': str}, + 'supported_platforms': { + 'type': list, + 'optional': True, + 'elements': { + 'type': dict, + 'spec': { + 'architecture': { + 'type': str, + 'values': ['x86', 'x86_64', 'aarch64']}, + 'required_flags': { + 'type': list, + 'optional': True, + 'elements': {'type': str}, + }, + 'operating_systems': { + 'type': list, + 'optional': True, + 'elements': { + 'type': str, + 'values': ['Linux', 'Windows', 'Darwin'], + }, + }, + }, + }, + }, }, }, }, diff --git a/test/test_metadata_sizes.py b/test/test_metadata_sizes.py index 6e04562b..439310f0 100644 --- a/test/test_metadata_sizes.py +++ b/test/test_metadata_sizes.py @@ -9,7 +9,7 @@ import pqclean @pytest.mark.parametrize( 'implementation,test_dir,impl_path, init, destr', - [(impl, *helpers.isolate_test_files(impl.path(), 'test_functest_')) + [(impl, *helpers.isolate_test_files(impl.path(), 'test_printparams_')) for impl in pqclean.Scheme.all_implementations()], ids=[str(impl) for impl in pqclean.Scheme.all_implementations()], ) diff --git a/test/test_nistkat.py b/test/test_nistkat.py index e6c03609..776ec445 100644 --- a/test/test_nistkat.py +++ b/test/test_nistkat.py @@ -20,8 +20,8 @@ import pqclean @pytest.mark.parametrize( 'implementation,test_dir,impl_path, init, destr', [(impl, *helpers.isolate_test_files(impl.path(), 'test_functest_')) - for impl in pqclean.Scheme.all_implementations()], - ids=[str(impl) for impl in pqclean.Scheme.all_implementations()], + for impl in pqclean.Scheme.all_supported_implementations()], + ids=[str(impl) for impl in pqclean.Scheme.all_supported_implementations()], ) @helpers.filtered_test def test_nistkat(implementation, impl_path, test_dir, init, destr): diff --git a/test/test_symbol_namespace.py b/test/test_symbol_namespace.py index 123a18a1..477290bf 100644 --- a/test/test_symbol_namespace.py +++ b/test/test_symbol_namespace.py @@ -16,8 +16,8 @@ import pqclean 'implementation,test_dir,impl_path,init,destr', [(impl, *helpers.isolate_test_files(impl.path(), 'test_symbol_ns_')) - for impl in pqclean.Scheme.all_implementations()], - ids=[str(impl) for impl in pqclean.Scheme.all_implementations()], + for impl in pqclean.Scheme.all_supported_implementations()], + ids=[str(impl) for impl in pqclean.Scheme.all_supported_implementations()], ) @helpers.filtered_test def test_symbol_namespaces(implementation, impl_path, test_dir, init, destr): diff --git a/test/test_testvectors.py b/test/test_testvectors.py index 88cfc2c5..a6bc0855 100644 --- a/test/test_testvectors.py +++ b/test/test_testvectors.py @@ -5,6 +5,7 @@ the one provided in the META file for every scheme/implementation. import hashlib import os +import unittest import pytest @@ -22,6 +23,8 @@ import pqclean ) @helpers.filtered_test def test_testvectors(implementation, impl_path, test_dir, init, destr): + if not implementation.supported_on_current_platform(): + raise unittest.SkipTest("Not supported on current platform") init() dest_dir = os.path.join(test_dir, 'bin') helpers.make('testvectors', diff --git a/test/test_valgrind.py b/test/test_valgrind.py index c5ae250c..5d073024 100644 --- a/test/test_valgrind.py +++ b/test/test_valgrind.py @@ -27,8 +27,8 @@ def valgrind_supports_exit_early(): @pytest.mark.parametrize( 'implementation,test_dir,impl_path, init, destr', [(impl, *helpers.isolate_test_files(impl.path(), 'test_functest_')) - for impl in pqclean.Scheme.all_implementations()], - ids=[str(impl) for impl in pqclean.Scheme.all_implementations()], + for impl in pqclean.Scheme.all_supported_implementations()], + ids=[str(impl) for impl in pqclean.Scheme.all_supported_implementations()], ) @helpers.slow_test @helpers.filtered_test