diff --git a/reframe/utility/__init__.py b/reframe/utility/__init__.py index 1e3dccf3b9..470a98b13c 100644 --- a/reframe/utility/__init__.py +++ b/reframe/utility/__init__.py @@ -20,6 +20,7 @@ import reframe from collections import UserDict +from hashlib import sha256 from . import typecheck as typ @@ -87,7 +88,12 @@ def import_module_from_file(filename, force=False): module_name = _get_module_name(rel_filename) if rel_filename.startswith('..'): # We cannot use the standard Python import mechanism here, because the - # module to import is outside the top-level package + # module to import is outside the top-level package. We also mangle + # the name that we assign to the module, in order to avoid clashes + # with other modules loaded with a standard `import` or with multiple + # test files with the same name that reside in different directories. + module_hash = sha256(filename.encode('utf-8')).hexdigest()[:8] + module_name = f'{module_name}@{module_hash}' return _do_import_module_from_file(filename, module_name) # Extract module name if `filename` is under `site-packages/` or the diff --git a/unittests/test_loader.py b/unittests/test_loader.py index 8a182421f0..e85c9b5482 100644 --- a/unittests/test_loader.py +++ b/unittests/test_loader.py @@ -5,6 +5,7 @@ import os import pytest +import shutil import reframe as rfm from reframe.core.exceptions import ReframeSyntaxError @@ -24,6 +25,21 @@ def loader_with_path(): ) +@pytest.fixture +def loader_with_path_tmpdir(tmp_path): + test_dir_a = tmp_path / 'a' + test_dir_b = tmp_path / 'b' + os.mkdir(test_dir_a) + os.mkdir(test_dir_b) + test_a = 'unittests/resources/checks/emptycheck.py' + test_b = 'unittests/resources/checks/hellocheck.py' + shutil.copyfile(test_a, test_dir_a / 'test.py') + shutil.copyfile(test_b, test_dir_b / 'test.py') + return RegressionCheckLoader( + [test_dir_a.as_posix(), test_dir_b.as_posix()] + ) + + def test_load_file_relative(loader): checks = loader.load_from_file('unittests/resources/checks/emptycheck.py') assert 1 == len(checks) @@ -73,6 +89,19 @@ def test_load_fixtures(loader): assert 5 == len(tests) +def test_existing_module_name(loader, tmp_path): + test_file = tmp_path / 'os.py' + shutil.copyfile('unittests/resources/checks/emptycheck.py', test_file) + checks = loader.load_from_file(test_file) + assert 1 == len(checks) + assert checks[0].name == 'EmptyTest' + + +def test_same_filename_different_path(loader_with_path_tmpdir): + checks = loader_with_path_tmpdir.load_all() + assert 3 == len(checks) + + def test_special_test(): with pytest.raises(ReframeSyntaxError): @rfm.simple_test diff --git a/unittests/test_utility.py b/unittests/test_utility.py index 3b09a4e392..6e6ebff202 100644 --- a/unittests/test_utility.py +++ b/unittests/test_utility.py @@ -479,13 +479,15 @@ def test_import_from_file_load_abspath(): assert module is sys.modules.get('reframe') -def test_import_from_file_load_unknown_path(): - try: - util.import_module_from_file('/foo') - pytest.fail() - except ImportError as e: - assert 'foo' == e.name - assert '/foo' == e.path +def test_import_from_file_existing_module_name(tmp_path): + test_file = tmp_path / 'os.py' + with open(test_file, 'w') as fp: + print('var = 1', file=fp) + + module = util.import_module_from_file(test_file) + assert module.var == 1 + assert not hasattr(module, 'path') + assert hasattr(os, 'path') def test_import_from_file_load_directory_relative(): @@ -510,17 +512,6 @@ def test_import_from_file_load_relative(): assert module is sys.modules.get('reframe.utility.osext') -def test_import_from_file_load_outside_pkg(): - module = util.import_module_from_file(os.path.__file__) - - # os imports the OS-specific path libraries under the name `path`. Our - # importer will import the actual file, thus the module name should be - # the real one. - assert (module is sys.modules.get('posixpath') or - module is sys.modules.get('ntpath') or - module is sys.modules.get('macpath')) - - def test_import_from_file_load_twice(): filename = os.path.abspath('reframe') module1 = util.import_module_from_file(filename)