diff --git a/cylc/flow/config.py b/cylc/flow/config.py index 4196568e78..11fadf831c 100644 --- a/cylc/flow/config.py +++ b/cylc/flow/config.py @@ -82,6 +82,7 @@ ) from cylc.flow.print_tree import print_tree from cylc.flow.task_qualifiers import ALT_QUALIFIERS +from cylc.flow.run_modes.simulation import configure_sim_mode from cylc.flow.run_modes.skip import skip_mode_validate from cylc.flow.subprocctx import SubFuncContext from cylc.flow.task_events_mgr import ( @@ -513,6 +514,11 @@ def __init__( self.process_runahead_limit() + run_mode = RunMode.get(self.options) + if run_mode in {RunMode.SIMULATION, RunMode.DUMMY}: + for taskdef in self.taskdefs.values(): + configure_sim_mode(taskdef.rtconfig, None, False) + self.configure_workflow_state_polling_tasks() self._check_task_event_handlers() diff --git a/cylc/flow/run_modes/simulation.py b/cylc/flow/run_modes/simulation.py index 047a4526ed..47f234b0b2 100644 --- a/cylc/flow/run_modes/simulation.py +++ b/cylc/flow/run_modes/simulation.py @@ -189,7 +189,7 @@ def __init__( self.timeout = started_time + self.simulated_run_length -def configure_sim_mode(rtc, fallback): +def configure_sim_mode(rtc, fallback, warnonly: bool = True): """Adjust task defs for simulation mode. Example: @@ -209,6 +209,13 @@ def configure_sim_mode(rtc, fallback): >>> rtc['platform'] 'localhost' """ + if not warnonly: + parse_fail_cycle_points( + rtc["simulation"]["fail cycle points"], + fallback, + warnonly + ) + return rtc['submission retry delays'] = [1] disable_platforms(rtc) @@ -220,7 +227,8 @@ def configure_sim_mode(rtc, fallback): "fail cycle points" ] = parse_fail_cycle_points( rtc["simulation"]["fail cycle points"], - fallback + fallback, + warnonly ) @@ -265,6 +273,7 @@ def disable_platforms( def parse_fail_cycle_points( fail_at_points_updated: List[str], fail_at_points_config, + warnonly: bool = True ) -> 'Union[None, List[PointBase]]': """Parse `[simulation][fail cycle points]`. @@ -302,8 +311,11 @@ def parse_fail_cycle_points( try: fail_at_points.append(get_point(point_str).standardise()) except PointParsingError as exc: - LOG.warning(exc.args[0]) - return fail_at_points_config + if warnonly: + LOG.warning(exc.args[0]) + return fail_at_points_config + else: + raise exc return fail_at_points diff --git a/cylc/flow/scheduler_cli.py b/cylc/flow/scheduler_cli.py index 739f26892c..0406a63c44 100644 --- a/cylc/flow/scheduler_cli.py +++ b/cylc/flow/scheduler_cli.py @@ -130,7 +130,7 @@ RUN_MODE = OptionSettings( ["-m", "--mode"], help=( - f"Run mode: {WORKFLOW_RUN_MODES} (default live)." + f"Run mode: {sorted(WORKFLOW_RUN_MODES)} (default live)." " Live mode executes the tasks as defined in the runtime" " section." " Simulation, skip and dummy modes ignore part of tasks'" diff --git a/cylc/flow/scripts/validate.py b/cylc/flow/scripts/validate.py index 443557375c..47bc5e8954 100755 --- a/cylc/flow/scripts/validate.py +++ b/cylc/flow/scripts/validate.py @@ -51,6 +51,7 @@ ICP_OPTION, ) from cylc.flow.profiler import Profiler +from cylc.flow.scheduler_cli import RUN_MODE from cylc.flow.task_proxy import TaskProxy from cylc.flow.templatevars import get_template_vars from cylc.flow.terminal import cli_function @@ -60,6 +61,8 @@ from cylc.flow.option_parsers import Values +VALIDATE_RUN_MODE = deepcopy(RUN_MODE) +VALIDATE_RUN_MODE.sources = {'validate'} VALIDATE_ICP_OPTION = deepcopy(ICP_OPTION) VALIDATE_ICP_OPTION.sources = {'validate'} VALIDATE_AGAINST_SOURCE_OPTION = deepcopy(AGAINST_SOURCE_OPTION) @@ -95,6 +98,7 @@ dest="profile_mode", sources={'validate'} ), + VALIDATE_RUN_MODE, VALIDATE_ICP_OPTION, ] @@ -149,6 +153,7 @@ async def run( src=True, constraint='workflows', ) + cfg = WorkflowConfig( workflow_id, flow_file, diff --git a/cylc/flow/task_events_mgr.py b/cylc/flow/task_events_mgr.py index 8ed2a89f25..e60adc88f7 100644 --- a/cylc/flow/task_events_mgr.py +++ b/cylc/flow/task_events_mgr.py @@ -1541,7 +1541,7 @@ def _insert_task_job( # And transient tasks, used for setting outputs and spawning children, # do not submit jobs. if ( - itask.run_mode in JOBLESS_MODES + itask.run_mode and itask.run_mode.value in JOBLESS_MODES or forced ): job_conf = {"submit_num": itask.submit_num} diff --git a/tests/integration/test_config.py b/tests/integration/test_config.py index e10ecf6b64..4a1a2dc11c 100644 --- a/tests/integration/test_config.py +++ b/tests/integration/test_config.py @@ -24,6 +24,7 @@ from cylc.flow.cfgspec.glbl_cfg import glbl_cfg from cylc.flow.cfgspec.globalcfg import GlobalConfig from cylc.flow.exceptions import ( + PointParsingError, ServiceFileError, WorkflowConfigError, XtriggerConfigError, @@ -628,3 +629,31 @@ def test_skip_forbidden_as_output(flow, validate): }) with pytest.raises(WorkflowConfigError, match='message for skip'): validate(wid) + + +def test_validate_workflow_run_mode(flow: Fixture, validate: Fixture, caplog: Fixture): + """Test that Cylc validate will only check simulation mode settings + if validate --mode simulation or dummy. + Discovered in: + https://github.com/cylc/cylc-flow/pull/6213#issuecomment-2225365825 + """ + wid = flow( + { + 'scheduling': {'graph': {'R1': 'mytask'}}, + 'runtime': { + 'mytask': { + 'simulation': {'fail cycle points': 'invalid'}, + } + }, + } + ) + + validate(wid) + + # It fails with run mode simulation: + with pytest.raises(PointParsingError, match='Incompatible value'): + validate(wid, run_mode='simulation') + + # It fails with run mode dummy: + with pytest.raises(PointParsingError, match='Incompatible value'): + validate(wid, run_mode='dummy') diff --git a/tests/integration/test_task_events_mgr.py b/tests/integration/test_task_events_mgr.py index 9d6585896f..ac1fb2f934 100644 --- a/tests/integration/test_task_events_mgr.py +++ b/tests/integration/test_task_events_mgr.py @@ -94,6 +94,7 @@ async def test__insert_task_job(flow, one_conf, scheduler, start, validate): itask = schd.pool.get_tasks()[0] itask.state.status = 'running' itask.submit_num += 1 + itask.run_mode = RunMode.SIMULATION # Not run _insert_task_job yet: assert not schd.data_store_mgr.added['jobs'].keys()