From 50801485f046a4bdcc8596edefa771a172d5d06a Mon Sep 17 00:00:00 2001 From: Douglas Stebila Date: Mon, 25 Feb 2019 23:28:37 -0500 Subject: [PATCH] Add test to check that every .c / .h file triggers a library rebuild --- test/helpers.py | 3 +- test/pqclean.py | 3 ++ test/test_makefile_dependencies.py | 63 ++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 test/test_makefile_dependencies.py diff --git a/test/helpers.py b/test/helpers.py index 0ad50da9..6b558e7e 100644 --- a/test/helpers.py +++ b/test/helpers.py @@ -1,7 +1,7 @@ import subprocess -def run_subprocess(command, working_dir, expected_returncode=0): +def run_subprocess(command, working_dir='.', expected_returncode=0): """ Helper function to run a shell command and report success/failure depending on the exit status of the shell command. @@ -15,6 +15,7 @@ def run_subprocess(command, working_dir, expected_returncode=0): stderr=subprocess.STDOUT, cwd=working_dir ) + print(working_dir + " > " + " ".join(command)) print(result.stdout.decode('utf-8')) assert(result.returncode == expected_returncode) return result.stdout.decode('utf-8') diff --git a/test/pqclean.py b/test/pqclean.py index 6a0e9e3a..ee63d821 100644 --- a/test/pqclean.py +++ b/test/pqclean.py @@ -69,6 +69,9 @@ class Implementation: def path(self, base='..') -> str: return os.path.join(self.scheme.path(), self.name) + def libname(self) -> str: + return "lib{}_{}.a".format(self.scheme.name, self.name) + @staticmethod def by_name(scheme_name, implementation_name): scheme = Scheme.by_name(scheme_name) diff --git a/test/test_makefile_dependencies.py b/test/test_makefile_dependencies.py new file mode 100644 index 00000000..fd2a888d --- /dev/null +++ b/test/test_makefile_dependencies.py @@ -0,0 +1,63 @@ +""" +Checks that every .c and .h file in an implementation is present as a +dependency of that scheme's Makefile. +""" + +import hashlib +import os +import pqclean +import helpers +import subprocess +import glob + + +def test_makefile_dependencies(): + for scheme in pqclean.Scheme.all_schemes(): + for implementation in scheme.implementations: + # initial build - want to have all files in place at beginning + helpers.run_subprocess(['make', 'clean'], implementation.path()) + helpers.run_subprocess(['make'], implementation.path()) + # test case for each candidate file + cfiles = glob.glob(os.path.join(implementation.path(), '*.c')) + hfiles = glob.glob(os.path.join(implementation.path(), '*.h')) + for file in cfiles + hfiles: + yield check_makefile_dependencies, scheme.name, implementation.name, file + +def check_makefile_dependencies(scheme_name, implementation_name, file): + scheme = pqclean.Scheme.by_name(scheme_name) + implementation = pqclean.Implementation.by_name(scheme_name, implementation_name) + + cfiles = glob.glob(os.path.join(implementation.path(), '*.c')) + hfiles = glob.glob(os.path.join(implementation.path(), '*.h')) + ofiles = glob.glob(os.path.join(implementation.path(), '*.o')) + + libfile = os.path.join(implementation.path(), implementation.libname()) + + # modification time-based calculations is tricky on a sub-second basis + # so we reset all the modification times to a known and "sensible" order + helpers.run_subprocess(['touch'] + cfiles + hfiles + ofiles) + helpers.run_subprocess(['touch', libfile]) + helpers.run_subprocess(['touch', '-A', '-15', '-m'] + cfiles + hfiles) + helpers.run_subprocess(['touch', '-A', '-10', '-m'] + ofiles) + helpers.run_subprocess(['touch', '-A', '-05', '-m', libfile]) + mtime_lib_orig = os.stat(libfile).st_mtime_ns + + # touch the candidate .c / .h file + helpers.run_subprocess(['touch', '-A', '15', '-m', file]) + + # rebuild + helpers.run_subprocess(['make'], implementation.path()) + + # make sure the libfile's modification time changed + mtime_lib_upd = os.stat(libfile).st_mtime_ns + if (mtime_lib_orig == mtime_lib_upd): + print("ERROR: Library was not updated after touching {}".format(file)) + assert(mtime_lib_orig != mtime_lib_upd) + +if __name__ == '__main__': + try: + import nose2 + nose2.main() + except ImportError: + import nose + nose.runmodule()