diff --git a/CIME/XML/archive_base.py b/CIME/XML/archive_base.py index 360424b734d..b66554201e9 100644 --- a/CIME/XML/archive_base.py +++ b/CIME/XML/archive_base.py @@ -157,7 +157,10 @@ def get_all_hist_files(self, casename, model, from_dir, suffix="", ref_case=None for ext in extensions: if ext.endswith("$") and has_suffix: ext = ext[:-1] - string = model + r"\d?_?(\d{4})?(_d\d{2})?\." + ext + # Anchor the component name at a filename boundary. Without this, a + # component name that happens to be a substring of another one (e.g. + # "eam" inside "scream") matches the other component's history files. + string = r"(?:^|[\.\-_])" + model + r"\d?_?(\d{4})?(_d\d{2})?\." + ext if has_suffix: if not suffix in string: string += r"\." + suffix + "$" diff --git a/CIME/tests/test_unit_xml_archive_base.py b/CIME/tests/test_unit_xml_archive_base.py index 98f58055c9c..ff4db6038dc 100644 --- a/CIME/tests/test_unit_xml_archive_base.py +++ b/CIME/tests/test_unit_xml_archive_base.py @@ -34,6 +34,46 @@ """ +# "eam" is a substring of "scream", so both specs are candidates for either +# component's files unless the component name is anchored at a name boundary. +SUBSTRING_TEST_CONFIG = r""" + + h\d*.*\.nc$ + + + (.*\.)?h\.(?!rhist\.).*\.nc$ + +""" + + +def test_component_name_is_not_matched_as_substring(tmp_path): + """History files of one component must not match a substring component name.""" + archiver = ArchiveBase() + archiver.read_fd(io.StringIO(SUBSTRING_TEST_CONFIG)) + + eam_file = "casename.eam.h0.0001-01.nc" + scream_file = "casename.scream.h.AVERAGE.nmonths_x1.0001-01.nc" + for name in (eam_file, scream_file): + (tmp_path / name).touch() + + assert archiver.get_all_hist_files("casename", "eam", str(tmp_path)) == [eam_file] + assert archiver.get_all_hist_files("casename", "scream", str(tmp_path)) == [ + scream_file + ] + + +def test_component_name_at_start_of_filename(tmp_path): + """A filename may start with the component name instead of the casename.""" + archiver = ArchiveBase() + archiver.read_fd(io.StringIO(SUBSTRING_TEST_CONFIG)) + + scream_file = "scream.h.AVERAGE.nmonths_x1.0001-01.nc" + (tmp_path / scream_file).touch() + + assert archiver.get_all_hist_files("casename", "scream", str(tmp_path)) == [ + scream_file + ] + class TestXMLArchiveBase(unittest.TestCase): @contextmanager