""" Checks that every .c and .h file in an implementation is present as a dependency of that scheme's Makefile. """ import datetime import glob import os import pytest import helpers import pqclean @pytest.mark.parametrize( 'implementation,test_dir,impl_path, init, destr', [(impl, *helpers.isolate_test_files(impl.path(), 'test_makefile_deps_')) 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, init, destr): init() # initial build - want to have *all* files in place at beginning helpers.make('clean', working_dir=impl_path) helpers.make(working_dir=impl_path) # test case for each candidate file cfiles = glob.glob(os.path.join(impl_path, '*.c')) sfiles = glob.glob(os.path.join(impl_path, '*.[sS]')) incfiles = glob.glob(os.path.join(impl_path, '*.inc')) hfiles = glob.glob(os.path.join(impl_path, '*.h')) try: for file in (cfiles + hfiles + sfiles + incfiles): check_makefile_dependencies(implementation, impl_path, file) except Exception as e: print("Affected file: {}".format(file)) raise e finally: destr() def touch(time, *files): for path in files: times = (time.timestamp(), time.timestamp()) os.utime(path, times) def make_check(path, expect_error=False): makeflag = '-q' if os.name != 'nt' else '/Q' expected_returncode = 0 if expect_error: expected_returncode = 1 if os.name != 'nt' else 255 helpers.make(makeflag, working_dir=path, expected_returncode=expected_returncode) def check_makefile_dependencies(implementation, impl_path, file): cfiles = glob.glob(os.path.join(impl_path, '*.c')) sfiles = glob.glob(os.path.join(impl_path, '*.[sS]')) hfiles = glob.glob(os.path.join(impl_path, '*.h')) incfiles = glob.glob(os.path.join(impl_path, '*.inc')) o_ext = '*.o' if os.name != 'nt' else '*.obj' ofiles = glob.glob(os.path.join(impl_path, o_ext)) # handle dependency files: these also need to be set correctly commondir = os.path.join(impl_path, '..', '..', '..', 'common') cfiles += glob.glob(os.path.join(commondir, '*.c')) cfiles += glob.glob(os.path.join(commondir, '**', '*.c')) hfiles += glob.glob(os.path.join(commondir, '*.h')) hfiles += glob.glob(os.path.join(commondir, '**', '*.h')) incfiles.append(os.path.join(commondir, 'keccak4x', 'KeccakP-1600-unrolling.macros')) ofiles += glob.glob(os.path.join(commondir, o_ext)) ofiles += glob.glob(os.path.join(commondir, '**', o_ext)) libfile = os.path.join(impl_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 now = datetime.datetime.now() - datetime.timedelta(seconds=10) ago15 = now - datetime.timedelta(minutes=15) ago10 = now - datetime.timedelta(minutes=10) ago5 = now - datetime.timedelta(minutes=5) touch(ago15, *cfiles, *sfiles, *hfiles, *incfiles) touch(ago10, *ofiles) touch(ago5, libfile) # Sanity check: the scheme is up to date make_check(impl_path) # touch the candidate .c / .h file touch(now, file) # check if it needs to be rebuilt using make -q make_check(impl_path, expect_error=True) if __name__ == '__main__': import sys pytest.main(sys.argv)