From a71d8100a627eff1cfc90ed3dbef9cc58c43d1f7 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Wed, 9 Jul 2025 13:52:59 +0200 Subject: [PATCH 1/3] Enhance test for finding related ECs --- test/framework/easyconfig.py | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/framework/easyconfig.py b/test/framework/easyconfig.py index e68c2b74a1..896ec367c3 100644 --- a/test/framework/easyconfig.py +++ b/test/framework/easyconfig.py @@ -3390,6 +3390,45 @@ def test_find_related_easyconfigs(self): res = [os.path.basename(x) for x in find_related_easyconfigs(test_easyconfigs, ec)] self.assertEqual(res, ['toy-0.0-deps.eb']) + # Expected order: + # 1. Same toolchain & version + # 2. Same toolchain, different version + # 3. Any toolchain + # For each: + # exact version, major/minor version, major version, any version + ec['version'] = '1.2.3' + ec['toolchain'] = {'name': 'GCC', 'version': '12'} + + # Use an empty folder to have full control over existing files + tmp_ec_dir = tempfile.mkdtemp() + files = [] + # Create list of files in desired order, + # so we can remove them one by one to check that the search still finds the correct one + # 1. Same toolchain incl. version + # 2. Same toolchain, different version + # 3. Different toolchain + for toolchain in ('GCC-12', 'GCC-11', 'Clang-12'): + # Secondary criteria: + # a. Same version + # b. Same major.minor version + # c. Same major version + # d. Different version + for version in ('1.2.3', '1.2', '1', '4'): + filepath = os.path.join(tmp_ec_dir, '-'.join((ec['name'], version, toolchain)) + '.eb') + write_file(filepath, f"name = '{ec['name']}'") + files.append(filepath) + + ec_name = f'{ec.name}-{ec.version}-{ec["toolchain"]["name"]}-{ec["toolchain"]["version"]}' + while files: + result = find_related_easyconfigs(tmp_ec_dir, ec) + # Only show basenames in error + result, expected, files_b = [[os.path.basename(f) for f in cur_files] + for cur_files in (result, [files[0]], files)] + self.assertEqual(result, expected, + msg='Found %s but expected %s when searching for "%s" in %s' + % (result, expected, ec_name, files_b)) + remove_file(files.pop(0)) + # no matches for unknown software name ec['name'] = 'nosuchsoftware' self.assertEqual(find_related_easyconfigs(test_easyconfigs, ec), []) From b3510f22f533d822a312dfdd701b02336f32c429 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Wed, 23 Jul 2025 13:46:35 +0200 Subject: [PATCH 2/3] Change test to prefer a matching version instead of toolchain --- test/framework/easyconfig.py | 73 ++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/test/framework/easyconfig.py b/test/framework/easyconfig.py index 896ec367c3..d9878a4e41 100644 --- a/test/framework/easyconfig.py +++ b/test/framework/easyconfig.py @@ -3390,40 +3390,67 @@ def test_find_related_easyconfigs(self): res = [os.path.basename(x) for x in find_related_easyconfigs(test_easyconfigs, ec)] self.assertEqual(res, ['toy-0.0-deps.eb']) - # Expected order: - # 1. Same toolchain & version - # 2. Same toolchain, different version - # 3. Any toolchain - # For each: - # exact version, major/minor version, major version, any version ec['version'] = '1.2.3' ec['toolchain'] = {'name': 'GCC', 'version': '12'} + # Create list of files in desired order, so we can remove them one by one + # to check that the search still finds the correct one. + # I.e. the file currently at the top should be returned, + # potentially with equally matching ones, given as values here. + # + # Expected order: + # 1. Same toolchain & version + # 2. Same toolchain, different version + # 3. Any toolchain + # Secondary criteria: + # a. Same toolchain incl. version + # b. Same toolchain, different version + # c. Different toolchain + testcases = { + # 1. Same version + 'toy-1.2.3-GCC-12.eb': [], + 'toy-1.2.3-GCC-11.eb': [], + 'toy-1.2.3-Clang-12.eb': [], + # 2. Different or no patch version + 'toy-1.2.0-GCC-12.eb': ['toy-1.2-GCC-12.eb'], + 'toy-1.2-GCC-12.eb': [], + 'toy-1.2.0-GCC-11.eb': ['toy-1.2-GCC-11.eb'], + 'toy-1.2-GCC-11.eb': [], + 'toy-1.2.0-Clang-12.eb': ['toy-1.2-Clang-12.eb'], + 'toy-1.2-Clang-12.eb': [], + # 3. Different or no minor version, optional patch version + 'toy-1.4.5-GCC-12.eb': ['toy-1.4-GCC-12.eb', 'toy-1-GCC-12.eb'], + 'toy-1.4-GCC-12.eb': ['toy-1-GCC-12.eb'], + 'toy-1-GCC-12.eb': [], + 'toy-1.4.5-GCC-11.eb': ['toy-1.4-GCC-11.eb', 'toy-1-GCC-11.eb'], + 'toy-1.4-GCC-11.eb': ['toy-1-GCC-11.eb'], + 'toy-1-GCC-11.eb': [], + 'toy-1.4.5-Clang-12.eb': ['toy-1.4-Clang-12.eb', 'toy-1-Clang-12.eb'], + 'toy-1.4-Clang-12.eb': ['toy-1-Clang-12.eb'], + 'toy-1-Clang-12.eb': [], + # 4 Different major version + 'toy-4.2.3-GCC-12.eb': ['toy-4.2-GCC-12.eb', 'toy-4-GCC-12.eb'], + 'toy-4.2-GCC-12.eb': ['toy-4-GCC-12.eb'], + 'toy-4-GCC-12.eb': [], + 'toy-4-GCC-11.eb': [], + 'toy-4-Clang-12.eb': [], + } + # Use an empty folder to have full control over existing files tmp_ec_dir = tempfile.mkdtemp() files = [] - # Create list of files in desired order, - # so we can remove them one by one to check that the search still finds the correct one - # 1. Same toolchain incl. version - # 2. Same toolchain, different version - # 3. Different toolchain - for toolchain in ('GCC-12', 'GCC-11', 'Clang-12'): - # Secondary criteria: - # a. Same version - # b. Same major.minor version - # c. Same major version - # d. Different version - for version in ('1.2.3', '1.2', '1', '4'): - filepath = os.path.join(tmp_ec_dir, '-'.join((ec['name'], version, toolchain)) + '.eb') - write_file(filepath, f"name = '{ec['name']}'") - files.append(filepath) + for filename in testcases: + filepath = os.path.join(tmp_ec_dir, filename) + write_file(filepath, f"name = '{ec['name']}'") + files.append(filepath) ec_name = f'{ec.name}-{ec.version}-{ec["toolchain"]["name"]}-{ec["toolchain"]["version"]}' while files: result = find_related_easyconfigs(tmp_ec_dir, ec) # Only show basenames in error - result, expected, files_b = [[os.path.basename(f) for f in cur_files] - for cur_files in (result, [files[0]], files)] + result, files_b = [[os.path.basename(f) for f in cur_files] for cur_files in (result, files)] + # first file in list should be the one found, followed by additional matches + expected = [files_b[0]] + testcases[files_b[0]] self.assertEqual(result, expected, msg='Found %s but expected %s when searching for "%s" in %s' % (result, expected, ec_name, files_b)) From 923b3fa9b66d4c32df67e3e3cd6bdf33d98a3d7a Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Wed, 23 Jul 2025 16:19:33 +0200 Subject: [PATCH 3/3] Treat missing sub-versions as different This allows finding '1.2.9' AND '1.2' when searching for '1.2.3' --- easybuild/framework/easyconfig/tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/easybuild/framework/easyconfig/tools.py b/easybuild/framework/easyconfig/tools.py index 2e548298ff..7919352964 100644 --- a/easybuild/framework/easyconfig/tools.py +++ b/easybuild/framework/easyconfig/tools.py @@ -478,9 +478,9 @@ def find_related_easyconfigs(path, ec): parsed_version = LooseVersion(version).version version_patterns = [version] # exact version match if len(parsed_version) >= 2: - version_patterns.append(r'%s\.%s\.\w+' % tuple(parsed_version[:2])) # major/minor version match + version_patterns.append(r'%s\.%s(\.\w+|(?![\d.]))' % tuple(parsed_version[:2])) # major/minor version match if parsed_version != parsed_version[0]: - version_patterns.append(r'%s\.[\d-]+(\.\w+)*' % parsed_version[0]) # major version match + version_patterns.append(r'%s(\.[\d-]+(\.\w+)*|(?![\d.]))' % parsed_version[0]) # major version match version_patterns.append(r'[\w.]+') # any version regexes = []