From 50801485f046a4bdcc8596edefa771a172d5d06a Mon Sep 17 00:00:00 2001 From: Douglas Stebila Date: Mon, 25 Feb 2019 23:28:37 -0500 Subject: [PATCH 1/2] 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() From 2db9f22aac8ac2cb65fa0f56f75d5c0b60a88113 Mon Sep 17 00:00:00 2001 From: Douglas Stebila Date: Mon, 25 Feb 2019 23:42:48 -0500 Subject: [PATCH 2/2] Compute touch timestamps in Python --- test/test_makefile_dependencies.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test/test_makefile_dependencies.py b/test/test_makefile_dependencies.py index fd2a888d..a678a850 100644 --- a/test/test_makefile_dependencies.py +++ b/test/test_makefile_dependencies.py @@ -9,6 +9,7 @@ import pqclean import helpers import subprocess import glob +import datetime def test_makefile_dependencies(): @@ -35,15 +36,18 @@ def check_makefile_dependencies(scheme_name, implementation_name, file): # 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]) + now = datetime.datetime.now() + ago15 = now - datetime.timedelta(seconds=15) + ago10 = now - datetime.timedelta(seconds=10) + ago5 = now - datetime.timedelta(seconds=5) + formatstring = "%Y%m%d%H%M.%S" + helpers.run_subprocess(['touch', '-t', ago15.strftime(formatstring)] + cfiles + hfiles) + helpers.run_subprocess(['touch', '-t', ago10.strftime(formatstring)] + ofiles) + helpers.run_subprocess(['touch', '-t', ago5.strftime(formatstring), libfile]) mtime_lib_orig = os.stat(libfile).st_mtime_ns # touch the candidate .c / .h file - helpers.run_subprocess(['touch', '-A', '15', '-m', file]) + helpers.run_subprocess(['touch', '-t', now.strftime(formatstring), file]) # rebuild helpers.run_subprocess(['make'], implementation.path())