From 036752248098e877eb1e4381e7d582519a87ec97 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Thu, 6 Nov 2025 17:51:52 -0700 Subject: [PATCH 1/4] Allow the allactive "component" to define system tests --- CIME/data/config/cesm/config_files.xml | 2 ++ CIME/utils.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CIME/data/config/cesm/config_files.xml b/CIME/data/config/cesm/config_files.xml index 3a728b7af84..ea9dd6f0857 100644 --- a/CIME/data/config/cesm/config_files.xml +++ b/CIME/data/config/cesm/config_files.xml @@ -112,6 +112,7 @@ $CIMEROOT/CIME/data/config/config_tests.xml + $SRCROOT/cime_config/config_tests.xml $COMP_ROOT_DIR_LND/cime_config/config_tests.xml $COMP_ROOT_DIR_LND/cime_config/config_tests.xml $COMP_ROOT_DIR_ATM/cime_config/config_tests.xml @@ -371,6 +372,7 @@ char $CIMEROOT/CIME/SystemTests + $SRCROOT/cime_config/SystemTests $COMP_ROOT_DIR_LND/cime_config/SystemTests $COMP_ROOT_DIR_ATM/cime_config/SystemTests $COMP_ROOT_DIR_OCN/cime_config/SystemTests diff --git a/CIME/utils.py b/CIME/utils.py index 2b48980b703..fad2c79bd13 100644 --- a/CIME/utils.py +++ b/CIME/utils.py @@ -2267,7 +2267,7 @@ def find_system_test(testname, case): if testname.startswith("TEST"): system_test_path = "CIME.SystemTests.system_tests_common.{}".format(testname) else: - components = ["any"] + components = ["any", "allactive"] components.extend(case.get_compset_components()) fdir = [] for component in components: From 466f7bcddc38591787f63922b6fda1266456d133 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Thu, 6 Nov 2025 17:52:49 -0700 Subject: [PATCH 2/4] Allow extensions of FUNIT test to add arguments to run_tests.py This is needed by the CESM_share extension to FUNIT --- CIME/SystemTests/funit.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CIME/SystemTests/funit.py b/CIME/SystemTests/funit.py index a7c21a06944..7681a6200d6 100644 --- a/CIME/SystemTests/funit.py +++ b/CIME/SystemTests/funit.py @@ -35,6 +35,13 @@ def get_test_spec_dir(self): """ return get_cime_root() + def get_extra_run_tests_args(self): + """ + Override this to return a string containing extra command-line arguments to + run_tests.py + """ + return "" + def run_phase(self): rundir = self._case.get_value("RUNDIR") @@ -51,8 +58,9 @@ def run_phase(self): get_cime_root(), "scripts", "fortran_unit_testing", "run_tests.py" ) ) - args = "--build-dir {} --test-spec-dir {} --machine {}".format( - exeroot, test_spec_dir, mach + extra_args = self.get_extra_run_tests_args() + args = "--build-dir {} --test-spec-dir {} --machine {} {}".format( + exeroot, test_spec_dir, mach, extra_args ) stat = run_cmd( From a5ec807bf1a2b6edf01e7ae0f35a3c11ffd21329 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Fri, 7 Nov 2025 17:02:29 -0700 Subject: [PATCH 3/4] Fix test_unit_xml_tests for new case.get_value call The previous commit introduced an extra call to case.get_value to get SYSTEM_TESTS_DIR for component=allactive. This broke the mock get_value returns. To make these returns more robust, I have changed the way they're done to be based on a dictionary lookup rather than an ordered list. This kind of replacement should probably be done for other case.get_value mocks. Note that I identified the argument values that needed to be handled with this code: for i, call in enumerate(case.get_value.call_args_list): print(f" Call {i}: {call}") --- CIME/tests/test_unit_xml_tests.py | 64 +++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/CIME/tests/test_unit_xml_tests.py b/CIME/tests/test_unit_xml_tests.py index a79bb3b9c0a..898e00bb0b8 100644 --- a/CIME/tests/test_unit_xml_tests.py +++ b/CIME/tests/test_unit_xml_tests.py @@ -32,16 +32,27 @@ def test_support_single_exe(self, _setup_cases_if_not_yet_done): case.get_compset_components.return_value = () - case.get_value.side_effect = ( - "SMS", - tdir, - f"{caseroot}", - "SMS.f19_g16.S", - "cpl", - "SMS.f19_g16.S", - f"{caseroot}", - "SMS.f19_g16.S", - ) + def fake_get_value(item, attribute=None): + simple_lookup = { + "TESTCASE": "SMS", + "CASEROOT": f"{caseroot}", + "CASEBASEID": "SMS.f19_g16.S", + "COMP_INTERFACE": "cpl", + "DRV_RESTART_POINTER": None, + } + if item in simple_lookup: + return simple_lookup[item] + elif item == "SYSTEM_TESTS_DIR": + if attribute["component"] == "any": + return tdir + else: + return None + + raise KeyError( + f"Unmocked call: case.get_value({item}, attribute={attribute})" + ) + + case.get_value.side_effect = fake_get_value tests = Tests() @@ -65,17 +76,28 @@ def test_support_single_exe_error(self, _setup_cases_if_not_yet_done): case.get_compset_components.return_value = () - case.get_value.side_effect = ( - "ERP", - tdir, - f"{caseroot}", - "ERP.f19_g16.S", - "cpl", - None, - "ERP.f19_g16.S", - f"{caseroot}", - "ERP.f19_g16.S", - ) + def fake_get_value(item, attribute=None): + simple_lookup = { + "TESTCASE": "ERP", + "CASEROOT": f"{caseroot}", + "CASEBASEID": "ERP.f19_g16.S", + "CASE": "ERP.f19_g16.S", + "COMP_INTERFACE": "cpl", + "DRV_RESTART_POINTER": None, + } + if item in simple_lookup: + return simple_lookup[item] + elif item == "SYSTEM_TESTS_DIR": + if attribute["component"] == "any": + return tdir + else: + return None + + raise KeyError( + f"Unmocked call: case.get_value({item}, attribute={attribute})" + ) + + case.get_value.side_effect = fake_get_value tests = Tests() From 2d1472364753d7c703dda4165462bf6379fe87d6 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Sat, 8 Nov 2025 11:35:07 -0700 Subject: [PATCH 4/4] Don't error if multiple CONFIG_TESTS_FILEs resolve to the same path Or if multiple SYSTEM_TESTS_DIRs resolve to the same path.... This happened when I introduced an entry for allactive: in a standalone checkout of CTSM, COMP_ROOT_DIR_LND is the same as SRCROOT, so the allactive and clm CONFIG_TESTS_FILEs and SYSTEM_TESTS_DIRs pointed to the same path, causing an error. --- CIME/XML/tests.py | 11 +++++++++-- CIME/utils.py | 5 +++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CIME/XML/tests.py b/CIME/XML/tests.py index 4a9eefc0fc4..2064bfab26f 100644 --- a/CIME/XML/tests.py +++ b/CIME/XML/tests.py @@ -22,13 +22,20 @@ def __init__(self, infile=None, files=None): files = Files() infile = files.get_value("CONFIG_TESTS_FILE") GenericXML.__init__(self, infile) - # append any component specific config_tests.xml files + + # Append any component-specific config_tests.xml files. We take care to only add a + # given file once, since adding a given file multiple times creates a "multiple + # matches" error. (This can happen if multiple CONFIG_TESTS_FILEs resolve to the + # same path.) + files_added = set() for comp in files.get_components("CONFIG_TESTS_FILE"): if comp is None: continue infile = files.get_value("CONFIG_TESTS_FILE", attribute={"component": comp}) - if os.path.isfile(infile): + infile_abspath = os.path.abspath(infile) + if os.path.isfile(infile) and infile_abspath not in files_added: self.read(infile) + files_added.add(infile_abspath) def support_single_exe(self, case): """Checks if case supports --single-exe. diff --git a/CIME/utils.py b/CIME/utils.py index fad2c79bd13..e05d5ad9e4a 100644 --- a/CIME/utils.py +++ b/CIME/utils.py @@ -2276,6 +2276,11 @@ def find_system_test(testname, case): ) if tdir is not None: tdir = os.path.abspath(tdir) + if tdir in fdir: + # This can happen if multiple SYSTEM_TESTS_DIRs resolve to the same + # path; in this case, we just want to handle the first occurrence and + # skip the rest. + continue system_test_file = os.path.join(tdir, "{}.py".format(testname.lower())) if os.path.isfile(system_test_file): fdir.append(tdir)