diff --git a/docs/sections/user_guide/cli/tools/rocoto/rocoto.yaml b/docs/sections/user_guide/cli/tools/rocoto/rocoto.yaml index eac2c7fa5..41614a40d 100644 --- a/docs/sections/user_guide/cli/tools/rocoto/rocoto.yaml +++ b/docs/sections/user_guide/cli/tools/rocoto/rocoto.yaml @@ -17,6 +17,7 @@ workflow: account: "&ACCOUNT;" command: "echo hello $person" jobname: hello + native: --reservation my_reservation nodes: 1:ppn=1 walltime: 00:01:00 envars: diff --git a/recipe/meta.json b/recipe/meta.json index 05052a59a..5b52efd27 100644 --- a/recipe/meta.json +++ b/recipe/meta.json @@ -32,5 +32,5 @@ "pyyaml =6.0.*" ] }, - "version": "2.3.2" + "version": "2.3.3" } diff --git a/src/uwtools/drivers/mpas.py b/src/uwtools/drivers/mpas.py index 0fb9988cb..b752f7841 100644 --- a/src/uwtools/drivers/mpas.py +++ b/src/uwtools/drivers/mpas.py @@ -48,7 +48,7 @@ def namelist_file(self): yield asset(path, path.is_file) yield None duration = timedelta(hours=self._driver_config["length"]) - str_duration = str(duration).replace(" days, ", "") + str_duration = str(duration).replace(" days, ", "_") try: namelist = self._driver_config["namelist"] except KeyError as e: diff --git a/src/uwtools/resources/info.json b/src/uwtools/resources/info.json index c5a6a6831..456c6a2e9 100644 --- a/src/uwtools/resources/info.json +++ b/src/uwtools/resources/info.json @@ -1,4 +1,4 @@ { - "version": "2.3.2", + "version": "2.3.3", "buildnum": "0" } diff --git a/src/uwtools/resources/jsonschema/rocoto.jsonschema b/src/uwtools/resources/jsonschema/rocoto.jsonschema index fded4633c..1b3bcbf0f 100644 --- a/src/uwtools/resources/jsonschema/rocoto.jsonschema +++ b/src/uwtools/resources/jsonschema/rocoto.jsonschema @@ -290,6 +290,23 @@ }, "task": { "additionalProperties": false, + "anyOf": [ + { + "required": [ + "cores" + ] + }, + { + "required": [ + "native" + ] + }, + { + "required": [ + "nodes" + ] + } + ], "dependentSchemas": { "exclusive": { "not": { @@ -328,23 +345,6 @@ ] } }, - "oneOf": [ - { - "required": [ - "cores" - ] - }, - { - "required": [ - "native" - ] - }, - { - "required": [ - "nodes" - ] - } - ], "properties": { "account": { "type": "string" diff --git a/src/uwtools/tests/drivers/test_mpas.py b/src/uwtools/tests/drivers/test_mpas.py index 257a352ba..172b4b943 100644 --- a/src/uwtools/tests/drivers/test_mpas.py +++ b/src/uwtools/tests/drivers/test_mpas.py @@ -135,7 +135,22 @@ def test_MPAS_namelist_file(caplog, driverobj): path = Path(refs(driverobj.namelist_file())) assert dst.is_file() assert logged(caplog, f"Wrote config to {path}") - assert isinstance(f90nml.read(dst), f90nml.Namelist) + nml = f90nml.read(dst) + assert isinstance(nml, f90nml.Namelist) + + +def test_MPAS_namelist_file_long_duration(caplog, config, cycle): + log.setLevel(logging.DEBUG) + config["mpas"]["length"] = 120 + driverobj = mpas.MPAS(config=config, cycle=cycle) + dst = driverobj._rundir / "namelist.atmosphere" + assert not dst.is_file() + path = Path(refs(driverobj.namelist_file())) + assert dst.is_file() + assert logged(caplog, f"Wrote config to {path}") + nml = f90nml.read(dst) + assert isinstance(nml, f90nml.Namelist) + assert nml["nhyd_model"]["config_run_duration"] == "5_0:00:00" def test_MPAS_namelist_file_fails_validation(caplog, driverobj): diff --git a/src/uwtools/tests/fixtures/hello_workflow.yaml b/src/uwtools/tests/fixtures/hello_workflow.yaml index b7d524ae5..be9a10607 100644 --- a/src/uwtools/tests/fixtures/hello_workflow.yaml +++ b/src/uwtools/tests/fixtures/hello_workflow.yaml @@ -23,6 +23,7 @@ workflow: attrs: offset: 01:00 value: hello-@Y@m@d@H + native: --reservation my_reservation nodes: 1:ppn=1 walltime: 00:01:00 envars: diff --git a/src/uwtools/tests/test_schemas.py b/src/uwtools/tests/test_schemas.py index eb1158815..2e425dab8 100644 --- a/src/uwtools/tests/test_schemas.py +++ b/src/uwtools/tests/test_schemas.py @@ -1021,6 +1021,18 @@ def test_schema_rocoto_workflow_cycledef(): assert "'foo' is not valid" in errors([{"attrs": {"activation_offset": "foo"}, "spec": spec}]) +def test_schema_rocoto_task_resources(): + errors = schema_validator("rocoto", "$defs", "task", "properties") + # Basic resource options + assert not errors([{"cores": 1}]) + assert not errors([{"native": "abc"}]) + assert not errors([{"native": {"cyclestr": {"value": "def"}}}]) + assert not errors([{"nodes": "1:ppn=12"}]) + # Combined valid resources + assert not errors([{"cores": 1, "native": "abc"}]) + assert not errors([{"native": "abc", "nodes": "1:ppn=12"}]) + + # sfc-climo-gen