From 2e3b3481dfcf7c657578ba7f110c8160f6985dc5 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Wed, 19 Feb 2025 14:31:27 +0100 Subject: [PATCH 1/3] Avoid processing the same EasyConfig multiple times When passing the same EasyConfig file multiple times on the commandline it will be parsed once and put into a cache. Every future call will get the same `EasyConfig` instance so any modification to it will be reflected in all future uses of supposedly "freshly parsed EasyConfigs". This leads to confusing failures when trying to build the same file twice which is likely a mistake anyway, especially when one file is a symlink to another one passed too. So just make sure each physical file is parsed only once. --- easybuild/framework/easyconfig/tools.py | 6 ++++++ test/framework/easyconfig.py | 26 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/easybuild/framework/easyconfig/tools.py b/easybuild/framework/easyconfig/tools.py index e3a93d652a..0deea2453b 100644 --- a/easybuild/framework/easyconfig/tools.py +++ b/easybuild/framework/easyconfig/tools.py @@ -404,9 +404,15 @@ def parse_easyconfigs(paths, validate=True): """ easyconfigs = [] generated_ecs = False + parsed_paths = [] for (path, generated) in paths: + # Avoid processing the same file multiple times path = os.path.abspath(path) + if any(os.path.samefile(path, p) for p in parsed_paths): + continue + parsed_paths.append(path) + # keep track of whether any files were generated generated_ecs |= generated if not os.path.exists(path): diff --git a/test/framework/easyconfig.py b/test/framework/easyconfig.py index 09801bde50..2b165a10ba 100644 --- a/test/framework/easyconfig.py +++ b/test/framework/easyconfig.py @@ -737,6 +737,32 @@ def test_tweaking(self): # cleanup os.remove(tweaked_fn) + def test_parse_easyconfig(self): + """Test parse_easyconfig function""" + self.contents = textwrap.dedent(""" + easyblock = "ConfigureMake" + name = "PI" + version = "3.14" + homepage = "http://example.com" + description = "test easyconfig" + toolchain = SYSTEM + """) + self.prep() + ecs, gen_ecs = parse_easyconfigs([(self.eb_file, False)]) + self.assertEqual(len(ecs), 1) + self.assertEqual(ecs[0]['spec'], self.eb_file) + self.assertIsInstance(ecs[0]['ec'], EasyConfig) + self.assertFalse(gen_ecs) + # Passing the same EC multiple times is ignored + ecs, gen_ecs = parse_easyconfigs([(self.eb_file, False), (self.eb_file, False)]) + self.assertEqual(len(ecs), 1) + # Similar for symlinks + linked_ec = os.path.join(self.test_prefix, 'linked.eb') + os.symlink(self.eb_file, linked_ec) + ecs, gen_ecs = parse_easyconfigs([(self.eb_file, False), (linked_ec, False)]) + self.assertEqual(len(ecs), 1) + self.assertEqual(ecs[0]['spec'], self.eb_file) + def test_alt_easyconfig_paths(self): """Test alt_easyconfig_paths function that collects list of additional paths for easyconfig files.""" From f1739437866a3451aa36cc1745227de0712a2438 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Tue, 4 Mar 2025 13:24:41 +0100 Subject: [PATCH 2/3] Allow test case filtering by class name It sometimes is useful to run either all tests from a class or a specific test from a class where the name might also be used in another class. Use the common convention of '.' and match aginst that allowing e.g. "TypeCheckingTest.test_check_type" as a filter. --- test/framework/utilities.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/framework/utilities.py b/test/framework/utilities.py index bf44ad828d..66718a53c5 100644 --- a/test/framework/utilities.py +++ b/test/framework/utilities.py @@ -430,7 +430,8 @@ def loadTestsFromTestCase(self, test_case_class, filters): retained_test_names = [] if len(filters) > 0: for test_case_name in test_case_names: - if any(filt in test_case_name for filt in filters): + full_test_case_name = '%s.%s' % (test_case_class.__name__, test_case_name) + if any(filt in full_test_case_name for filt in filters): retained_test_names.append(test_case_name) retained_tests = ', '.join(retained_test_names) From f1105184937b954f1dfcc3e02a03e1c1bf111e4a Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Thu, 6 Mar 2025 10:27:44 +0100 Subject: [PATCH 3/3] Also allow trailing whitespaces for examples and citing easyconfig parameters --- easybuild/framework/easyconfig/style.py | 2 +- test/framework/style.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/easybuild/framework/easyconfig/style.py b/easybuild/framework/easyconfig/style.py index f7f4d33b40..f087dc8205 100644 --- a/easybuild/framework/easyconfig/style.py +++ b/easybuild/framework/easyconfig/style.py @@ -100,7 +100,7 @@ def _eb_check_trailing_whitespace(physical_line, lines, line_number, checker_sta # if the warning is about the multiline string of description # we will not issue a warning - if checker_state.get('eb_last_key') == 'description': + if checker_state.get('eb_last_key') in ['description', 'examples', 'citing']: result = None return result diff --git a/test/framework/style.py b/test/framework/style.py index ce5ce1d317..2a8ebeb1e2 100644 --- a/test/framework/style.py +++ b/test/framework/style.py @@ -77,6 +77,12 @@ def test_check_trailing_whitespace(self): '''description = """start of long description, ''', # trailing whitespace, but allowed in description ''' continuation of long description ''', # trailing whitespace, but allowed in continued description ''' end of long description"""''', + '''citing = """start of long citing text, ''', # trailing whitespace, but allowed in citing + ''' continuation of long citing text ''', # trailing whitespace, but allowed in continued citing + ''' end of long citing text"""''', + '''examples = """start of long examples, ''', # trailing whitespace, but allowed in examples + ''' continuation of long examples ''', # trailing whitespace, but allowed in continued examples + ''' end of long examples"""''', "moduleclass = 'tools' ", # trailing whitespace '', ] @@ -89,6 +95,12 @@ def test_check_trailing_whitespace(self): None, None, None, + None, + None, + None, + None, + None, + None, (21, "W299 trailing whitespace"), ]