From 147020af4bab6f5d76a86337fead24d292c4df8b Mon Sep 17 00:00:00 2001 From: Tim Pillinger <26465611+wxtim@users.noreply.github.com> Date: Mon, 13 Jan 2025 15:33:33 +0000 Subject: [PATCH 01/14] add run mode skip to cylc show --- changes.d/6554.feat.md | 1 + cylc/flow/scripts/show.py | 7 +++++++ tests/integration/scripts/test_show.py | 15 +++++++++++---- 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 changes.d/6554.feat.md diff --git a/changes.d/6554.feat.md b/changes.d/6554.feat.md new file mode 100644 index 00000000000..61a78c2deea --- /dev/null +++ b/changes.d/6554.feat.md @@ -0,0 +1 @@ +Cylc show now displays that a task has been set to skip mode diff --git a/cylc/flow/scripts/show.py b/cylc/flow/scripts/show.py index c387a3f3fcf..248fb632558 100755 --- a/cylc/flow/scripts/show.py +++ b/cylc/flow/scripts/show.py @@ -145,6 +145,7 @@ } runtime { completion + runMode } } } @@ -346,9 +347,15 @@ async def prereqs_and_outputs_query( attrs.append("queued") if t_proxy['isRunahead']: attrs.append("runahead") + if ( + t_proxy['runtime']['runMode'] + and t_proxy['runtime']['runMode'] != 'Live' + ): + attrs.append(f"run mode={t_proxy['runtime']['runMode']}") state_msg = state if attrs: state_msg += f" ({','.join(attrs)})" + ansiprint(f'state: {state_msg}') # flow numbers, if not just 1 diff --git a/tests/integration/scripts/test_show.py b/tests/integration/scripts/test_show.py index ec000a75a16..9d01e65245a 100644 --- a/tests/integration/scripts/test_show.py +++ b/tests/integration/scripts/test_show.py @@ -59,8 +59,8 @@ def mod_my_conf(): 'destroyedtheworldyet.com/' ), 'question': 'mutually exclusive', - } - } + }, + }, }, } @@ -128,6 +128,7 @@ async def test_task_meta_query(mod_my_schd, capsys): ) assert ret == 0 out, err = capsys.readouterr() + assert out.splitlines() == [ 'title: Task Title', 'question: mutually exclusive', @@ -199,10 +200,13 @@ async def test_task_instance_query( 'attributes_bool, flow_nums, expected_state, expected_flows', [ pytest.param( - False, [1], 'state: waiting', None, + False, [1], 'state: waiting (skip)', None, ), pytest.param( - True, [1, 2], 'state: waiting (held,queued,runahead)', 'flows: [1,2]', + True, + [1, 2], + 'state: waiting (held,queued,runahead,skip)', + 'flows: [1,2]', ) ] ) @@ -225,6 +229,9 @@ async def test_task_instance_state_flows( 'scheduling': { 'graph': {'R1': 'a'}, }, + 'runtime': { + 'a': {'run mode': 'skip'} + } } ), paused_start=True From 818efc0cfcf7b6243b58744b232c26f312320f0a Mon Sep 17 00:00:00 2001 From: Tim Pillinger <26465611+wxtim@users.noreply.github.com> Date: Tue, 14 Jan 2025 11:20:33 +0000 Subject: [PATCH 02/14] test changing task mode with broadcast broaden the definition of task-mode Save workflow states to the runtime config object. --- cylc/flow/config.py | 8 +++ cylc/flow/network/schema.py | 5 +- tests/integration/scripts/test_show.py | 75 +++++++++++++++++++++++--- 3 files changed, 78 insertions(+), 10 deletions(-) diff --git a/cylc/flow/config.py b/cylc/flow/config.py index c438cd9105a..c7568d8d801 100644 --- a/cylc/flow/config.py +++ b/cylc/flow/config.py @@ -81,6 +81,7 @@ is_relative_to, ) from cylc.flow.task_qualifiers import ALT_QUALIFIERS +from cylc.flow.run_modes import WORKFLOW_ONLY_MODES 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 @@ -2448,6 +2449,13 @@ def _get_taskdef(self, name: str) -> TaskDef: try: rtcfg = self.cfg['runtime'][name] + + # If the workflow mode is simulation or dummy always + # override the task config: + workflow_run_mode = RunMode.get(self.options) + if workflow_run_mode.value in WORKFLOW_ONLY_MODES: + rtcfg['run mode'] = workflow_run_mode.value + except KeyError: raise WorkflowConfigError("Task not defined: %s" % name) from None # We may want to put in some handling for cases of changing the diff --git a/cylc/flow/network/schema.py b/cylc/flow/network/schema.py index 67d3c58ec84..0ca742452fa 100644 --- a/cylc/flow/network/schema.py +++ b/cylc/flow/network/schema.py @@ -72,7 +72,7 @@ ) from cylc.flow.id import Tokens from cylc.flow.run_modes import ( - TASK_CONFIG_RUN_MODES, WORKFLOW_RUN_MODES, RunMode) + WORKFLOW_RUN_MODES, RunMode) from cylc.flow.task_outputs import SORT_ORDERS from cylc.flow.task_state import ( TASK_STATUS_DESC, @@ -633,7 +633,8 @@ class Meta: # The run mode for the task. TaskRunMode = graphene.Enum( 'TaskRunMode', - [(m.capitalize(), m) for m in TASK_CONFIG_RUN_MODES], + [(k.capitalize(), k.lower()) for k in RunMode.__members__.keys()], + # [(m.capitalize(), m) for m in TASK_CONFIG_RUN_MODES], description=lambda x: RunMode(x.value).describe() if x else None, ) diff --git a/tests/integration/scripts/test_show.py b/tests/integration/scripts/test_show.py index 9d01e65245a..3ea1b83ecee 100644 --- a/tests/integration/scripts/test_show.py +++ b/tests/integration/scripts/test_show.py @@ -16,6 +16,7 @@ import json import pytest +import re from types import SimpleNamespace from colorama import init as colour_init @@ -26,6 +27,9 @@ ) +RE_STATE = re.compile('state:.*') + + @pytest.fixture(scope='module') def mod_my_conf(): """A workflow configuration with some workflow metadata.""" @@ -171,9 +175,9 @@ async def test_task_instance_query( 'scheduling': { 'graph': {'R1': 'zed & dog & cat & ant'}, }, - } + }, ), - paused_start=False + paused_start=False, ) async with start(schd): await schd.update_data_structure() @@ -196,23 +200,32 @@ async def test_task_instance_query( ] +@pytest.mark.parametrize( + 'workflow_run_mode, run_mode_info', + ( + ('live', 'Skip'), + ('dummy', 'Dummy'), + ('simulation', 'Simulation'), + ) +) @pytest.mark.parametrize( 'attributes_bool, flow_nums, expected_state, expected_flows', [ pytest.param( - False, [1], 'state: waiting (skip)', None, + False, [1], 'state: waiting (run mode={})', None, ), pytest.param( True, [1, 2], - 'state: waiting (held,queued,runahead,skip)', + 'state: waiting (held,queued,runahead,run mode={})', 'flows: [1,2]', ) ] ) async def test_task_instance_state_flows( flow, scheduler, start, capsys, - attributes_bool, flow_nums, expected_state, expected_flows + workflow_run_mode, run_mode_info, + attributes_bool, flow_nums, expected_state, expected_flows ): """It should print task instance state, attributes, and flows.""" @@ -232,9 +245,10 @@ async def test_task_instance_state_flows( 'runtime': { 'a': {'run mode': 'skip'} } - } + }, ), - paused_start=True + paused_start=True, + run_mode=workflow_run_mode, ) async with start(schd): @@ -264,7 +278,7 @@ async def test_task_instance_state_flows( line for line in out.splitlines() if line.startswith("state:") ] == [ - expected_state, + expected_state.format(run_mode_info), ] if expected_flows is not None: assert [ @@ -273,3 +287,48 @@ async def test_task_instance_state_flows( ] == [ expected_flows, ] + + +async def test_mode_changes(flow, scheduler, start, capsys): + """Broadcasting a change of run mode changes run mode shown by cylc show. + """ + opts = SimpleNamespace( + comms_timeout=5, + json=False, + task_defs=None, + list_prereqs=False, + ) + schd = scheduler( + flow({'scheduling': {'graph': {'R1': 'a'}}}), + paused_start=True, + run_mode='live' + ) + + async with start(schd): + # Control: No mode set, the Run Mode setting is not shown: + await schd.update_data_structure() + ret = await show( + schd.workflow, + [Tokens('//1/a')], + opts, + ) + assert ret == 0 + out, _ = capsys.readouterr() + state, = RE_STATE.findall(out) + assert 'waiting' in state + + # Broadcast change task to skip mode: + schd.broadcast_mgr.put_broadcast(['1'], ['a'], [{'run mode': 'skip'}]) + await schd.update_data_structure() + + # show now shows skip mode: + ret = await show( + schd.workflow, + [Tokens('//1/a')], + opts, + ) + assert ret == 0 + + out, _ = capsys.readouterr() + state, = RE_STATE.findall(out) + assert 'run mode=Skip' in state From 7c626a391498928cf6fe8ebb2bffce8eb41503fc Mon Sep 17 00:00:00 2001 From: Tim Pillinger <26465611+wxtim@users.noreply.github.com> Date: Thu, 16 Jan 2025 10:20:31 +0000 Subject: [PATCH 03/14] fixed a broken test fixed TUI test fix a broken functional test --- tests/flakyfunctional/cylc-show/00-simple.t | 5 ++++- tests/integration/run_modes/test_nonlive.py | 2 +- tests/integration/tui/screenshots/test_show.success.html | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/flakyfunctional/cylc-show/00-simple.t b/tests/flakyfunctional/cylc-show/00-simple.t index d0a700dd865..471515de6b0 100644 --- a/tests/flakyfunctional/cylc-show/00-simple.t +++ b/tests/flakyfunctional/cylc-show/00-simple.t @@ -113,7 +113,10 @@ cmp_json "${TEST_NAME}-taskinstance" "${TEST_NAME}-taskinstance" \ } } }, - "runtime": {"completion": "(started and succeeded)"}, + "runtime": { + "completion": "(started and succeeded)", + "runMode": "Live" + }, "prerequisites": [ { "expression": "0", diff --git a/tests/integration/run_modes/test_nonlive.py b/tests/integration/run_modes/test_nonlive.py index e62d250ce99..214a801f216 100644 --- a/tests/integration/run_modes/test_nonlive.py +++ b/tests/integration/run_modes/test_nonlive.py @@ -94,7 +94,7 @@ async def test_db_task_jobs( 'R1': '&'.join(KGO)}}, 'runtime': { mode: {'run mode': mode} for mode in KGO} - })) + }), run_mode='live') async with start(schd): # Reference all task proxies so we can examine them # at the end of the test: diff --git a/tests/integration/tui/screenshots/test_show.success.html b/tests/integration/tui/screenshots/test_show.success.html index 7f1caebe49d..d3e2e30c8af 100644 --- a/tests/integration/tui/screenshots/test_show.success.html +++ b/tests/integration/tui/screenshots/test_show.success.html @@ -13,7 +13,7 @@ description: The first metasyntactic variable. URL: (not given) - state: waiting (queued) + state: waiting (queued,run mode=Simulation) prerequisites: (None) outputs: ('⨯': not completed) ⨯ 1/foo expired From 4ca26a0f6d0bea5b7bedd3403c3ec093f53edb08 Mon Sep 17 00:00:00 2001 From: Tim Pillinger <26465611+wxtim@users.noreply.github.com> Date: Fri, 17 Jan 2025 08:11:18 +0000 Subject: [PATCH 04/14] Apply suggestions from code review Co-authored-by: Oliver Sanders --- cylc/flow/scripts/show.py | 3 ++- tests/integration/scripts/test_show.py | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cylc/flow/scripts/show.py b/cylc/flow/scripts/show.py index 248fb632558..01fa20da2d0 100755 --- a/cylc/flow/scripts/show.py +++ b/cylc/flow/scripts/show.py @@ -52,6 +52,7 @@ from cylc.flow.id import Tokens from cylc.flow.id_cli import parse_ids from cylc.flow.network.client_factory import get_client +from cylc.flow.run_modes import RunMode from cylc.flow.task_outputs import TaskOutputs from cylc.flow.task_state import ( TASK_STATUSES_ORDERED, @@ -349,7 +350,7 @@ async def prereqs_and_outputs_query( attrs.append("runahead") if ( t_proxy['runtime']['runMode'] - and t_proxy['runtime']['runMode'] != 'Live' + and t_proxy['runtime']['runMode'] != RunMode.LIVE.value ): attrs.append(f"run mode={t_proxy['runtime']['runMode']}") state_msg = state diff --git a/tests/integration/scripts/test_show.py b/tests/integration/scripts/test_show.py index 3ea1b83ecee..7c9befbacc6 100644 --- a/tests/integration/scripts/test_show.py +++ b/tests/integration/scripts/test_show.py @@ -289,7 +289,7 @@ async def test_task_instance_state_flows( ] -async def test_mode_changes(flow, scheduler, start, capsys): +async def test_task_run_mode_changes(flow, scheduler, start, capsys): """Broadcasting a change of run mode changes run mode shown by cylc show. """ opts = SimpleNamespace( @@ -300,7 +300,6 @@ async def test_mode_changes(flow, scheduler, start, capsys): ) schd = scheduler( flow({'scheduling': {'graph': {'R1': 'a'}}}), - paused_start=True, run_mode='live' ) From c397b61ac100950a8bf1d28b7e1255eb425ab377 Mon Sep 17 00:00:00 2001 From: Tim Pillinger <26465611+wxtim@users.noreply.github.com> Date: Thu, 23 Jan 2025 08:52:07 +0000 Subject: [PATCH 05/14] fix broken test --- cylc/flow/network/schema.py | 1 - cylc/flow/scripts/show.py | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cylc/flow/network/schema.py b/cylc/flow/network/schema.py index 0ca742452fa..0ab26c33b19 100644 --- a/cylc/flow/network/schema.py +++ b/cylc/flow/network/schema.py @@ -634,7 +634,6 @@ class Meta: TaskRunMode = graphene.Enum( 'TaskRunMode', [(k.capitalize(), k.lower()) for k in RunMode.__members__.keys()], - # [(m.capitalize(), m) for m in TASK_CONFIG_RUN_MODES], description=lambda x: RunMode(x.value).describe() if x else None, ) diff --git a/cylc/flow/scripts/show.py b/cylc/flow/scripts/show.py index 01fa20da2d0..0b0c735f8e0 100755 --- a/cylc/flow/scripts/show.py +++ b/cylc/flow/scripts/show.py @@ -350,7 +350,8 @@ async def prereqs_and_outputs_query( attrs.append("runahead") if ( t_proxy['runtime']['runMode'] - and t_proxy['runtime']['runMode'] != RunMode.LIVE.value + and t_proxy['runtime']['runMode'].lower() + != RunMode.LIVE.value ): attrs.append(f"run mode={t_proxy['runtime']['runMode']}") state_msg = state From c651f7099a221d4b2c5a0250f02b194a12994a0b Mon Sep 17 00:00:00 2001 From: Tim Pillinger <26465611+wxtim@users.noreply.github.com> Date: Thu, 23 Jan 2025 09:00:15 +0000 Subject: [PATCH 06/14] make run mode enum extend string to allow easier comparison, and allow it to digest strings in any state of capitalization --- cylc/flow/run_modes/__init__.py | 10 +++++++++- cylc/flow/scripts/show.py | 3 +-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/cylc/flow/run_modes/__init__.py b/cylc/flow/run_modes/__init__.py index 2f094631380..a0037daee79 100644 --- a/cylc/flow/run_modes/__init__.py +++ b/cylc/flow/run_modes/__init__.py @@ -40,7 +40,7 @@ ] -class RunMode(Enum): +class RunMode(str, Enum): """The possible run modes of a task/workflow.""" LIVE = 'live' @@ -85,6 +85,14 @@ def get(options: 'Values') -> "RunMode": return RunMode(run_mode) return RunMode.LIVE + @classmethod + def _missing_(cls, value): + value = value.lower() + for member in cls: + if member.lower() == value: + return member + return None + def get_submit_method(self) -> 'Optional[SubmissionInterface]': """Return the job submission method for this run mode. diff --git a/cylc/flow/scripts/show.py b/cylc/flow/scripts/show.py index 0b0c735f8e0..0f353c75a80 100755 --- a/cylc/flow/scripts/show.py +++ b/cylc/flow/scripts/show.py @@ -350,8 +350,7 @@ async def prereqs_and_outputs_query( attrs.append("runahead") if ( t_proxy['runtime']['runMode'] - and t_proxy['runtime']['runMode'].lower() - != RunMode.LIVE.value + and RunMode(t_proxy['runtime']['runMode']) != RunMode.LIVE ): attrs.append(f"run mode={t_proxy['runtime']['runMode']}") state_msg = state From 1bbecbbf90f1b7f005d7ec1f79a32be49cd3e8fd Mon Sep 17 00:00:00 2001 From: Tim Pillinger <26465611+wxtim@users.noreply.github.com> Date: Thu, 23 Jan 2025 14:33:55 +0000 Subject: [PATCH 07/14] remove run-mode inheriting str --- cylc/flow/run_modes/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cylc/flow/run_modes/__init__.py b/cylc/flow/run_modes/__init__.py index a0037daee79..ff7425bd6e0 100644 --- a/cylc/flow/run_modes/__init__.py +++ b/cylc/flow/run_modes/__init__.py @@ -40,7 +40,7 @@ ] -class RunMode(str, Enum): +class RunMode(Enum): """The possible run modes of a task/workflow.""" LIVE = 'live' From 37aa3e4c0c33c245652325d0a736b16a28604369 Mon Sep 17 00:00:00 2001 From: Tim Pillinger <26465611+wxtim@users.noreply.github.com> Date: Thu, 23 Jan 2025 14:38:36 +0000 Subject: [PATCH 08/14] added a test for RunMode._missing_ --- cylc/flow/run_modes/__init__.py | 2 +- tests/unit/run_modes/test_run_modes.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/cylc/flow/run_modes/__init__.py b/cylc/flow/run_modes/__init__.py index ff7425bd6e0..70f0d42ea83 100644 --- a/cylc/flow/run_modes/__init__.py +++ b/cylc/flow/run_modes/__init__.py @@ -89,7 +89,7 @@ def get(options: 'Values') -> "RunMode": def _missing_(cls, value): value = value.lower() for member in cls: - if member.lower() == value: + if member.value.lower() == value: return member return None diff --git a/tests/unit/run_modes/test_run_modes.py b/tests/unit/run_modes/test_run_modes.py index 57d245016d1..6cdd3b01672 100644 --- a/tests/unit/run_modes/test_run_modes.py +++ b/tests/unit/run_modes/test_run_modes.py @@ -16,6 +16,8 @@ """Tests for utilities supporting run modes. """ +import pytest + from cylc.flow.run_modes import RunMode @@ -28,3 +30,9 @@ def test_run_mode_desc(): def test_get_default_live(): """RunMode.get() => live""" assert RunMode.get({}) == RunMode.LIVE + + +@pytest.mark.parametrize('str_', ('LIVE', 'Dummy', 'SkIp', 'siMuLATioN')) +def test__missing_(str_): + """The RunMode enumeration works when fed a string in the wrong case""" + assert RunMode(str_).value == str_.lower() From d2b701dd59afbd8c37ce552b4e75989940eee1ab Mon Sep 17 00:00:00 2001 From: Tim Pillinger <26465611+wxtim@users.noreply.github.com> Date: Fri, 24 Jan 2025 09:15:48 +0000 Subject: [PATCH 09/14] Update cylc/flow/scripts/show.py Co-authored-by: Ronnie Dutta <61982285+MetRonnie@users.noreply.github.com> --- cylc/flow/scripts/show.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cylc/flow/scripts/show.py b/cylc/flow/scripts/show.py index 0f353c75a80..bb1665a2b8d 100755 --- a/cylc/flow/scripts/show.py +++ b/cylc/flow/scripts/show.py @@ -348,11 +348,9 @@ async def prereqs_and_outputs_query( attrs.append("queued") if t_proxy['isRunahead']: attrs.append("runahead") - if ( - t_proxy['runtime']['runMode'] - and RunMode(t_proxy['runtime']['runMode']) != RunMode.LIVE - ): - attrs.append(f"run mode={t_proxy['runtime']['runMode']}") + run_mode = t_proxy['runtime']['runMode'] + if run_mode and RunMode(run_mode) != RunMode.LIVE: + attrs.append(f"run mode={run_mode}") state_msg = state if attrs: state_msg += f" ({','.join(attrs)})" From a72e4b0b9399f9fabe5982f880391a68ebd54130 Mon Sep 17 00:00:00 2001 From: Tim Pillinger <26465611+wxtim@users.noreply.github.com> Date: Fri, 24 Jan 2025 09:16:02 +0000 Subject: [PATCH 10/14] Update changes.d/6554.feat.md Co-authored-by: Ronnie Dutta <61982285+MetRonnie@users.noreply.github.com> --- changes.d/6554.feat.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changes.d/6554.feat.md b/changes.d/6554.feat.md index 61a78c2deea..1abebb4e369 100644 --- a/changes.d/6554.feat.md +++ b/changes.d/6554.feat.md @@ -1 +1 @@ -Cylc show now displays that a task has been set to skip mode +`cylc show` now displays when a task has been set to skip mode From e0e4133ce9455b53064e6162eb044a75959d36f3 Mon Sep 17 00:00:00 2001 From: Tim Pillinger Date: Mon, 10 Mar 2025 09:46:28 +0000 Subject: [PATCH 11/14] use option_parsers to set up test. --- tests/integration/scripts/test_show.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/integration/scripts/test_show.py b/tests/integration/scripts/test_show.py index d523ab1db55..426ba82e24d 100644 --- a/tests/integration/scripts/test_show.py +++ b/tests/integration/scripts/test_show.py @@ -22,7 +22,9 @@ from colorama import init as colour_init from cylc.flow.id import Tokens +from cylc.flow.option_parsers import Options from cylc.flow.scripts.show import ( + get_option_parser, show, ) @@ -293,12 +295,7 @@ async def test_task_instance_state_flows( async def test_task_run_mode_changes(flow, scheduler, start, capsys): """Broadcasting a change of run mode changes run mode shown by cylc show. """ - opts = SimpleNamespace( - comms_timeout=5, - json=False, - task_defs=None, - list_prereqs=False, - ) + opts = Options(get_option_parser())() schd = scheduler( flow({'scheduling': {'graph': {'R1': 'a'}}}), run_mode='live' From eb855c045498adcc65a12fbb802fb6eba6367793 Mon Sep 17 00:00:00 2001 From: Tim Pillinger Date: Mon, 10 Mar 2025 10:41:18 +0000 Subject: [PATCH 12/14] Prevent broadcast manager doing anything with run mode = if workflow run mode is simulation or dummy. --- cylc/flow/broadcast_mgr.py | 18 +++++++++++++++++- cylc/flow/scheduler.py | 2 +- tests/integration/run_modes/test_simulation.py | 15 +++++++++++++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/cylc/flow/broadcast_mgr.py b/cylc/flow/broadcast_mgr.py index b9329114302..3738b730098 100644 --- a/cylc/flow/broadcast_mgr.py +++ b/cylc/flow/broadcast_mgr.py @@ -32,6 +32,8 @@ from cylc.flow.exceptions import PointParsingError from cylc.flow.parsec.util import listjoin, pdeepcopy, poverride from cylc.flow.parsec.validate import BroadcastConfigValidator +from cylc.flow.run_modes import WORKFLOW_ONLY_MODES + if TYPE_CHECKING: from cylc.flow.id import Tokens @@ -62,7 +64,8 @@ class BroadcastMgr: REC_SECTION = re.compile(r"\[([^\]]+)\]") - def __init__(self, workflow_db_mgr, data_store_mgr): + def __init__(self, workflow_db_mgr, data_store_mgr, run_mode): + self.workflow_run_mode = run_mode self.workflow_db_mgr = workflow_db_mgr self.data_store_mgr = data_store_mgr self.linearized_ancestors = {} @@ -289,6 +292,19 @@ def put_broadcast( SPEC['runtime']['__MANY__'], ) + # Skip and warn if a run mode is broadcast to a workflow + # running in simulation or dummy mode. + if ( + 'run mode' in coerced_setting + and self.workflow_run_mode.value in WORKFLOW_ONLY_MODES + ): + LOG.warning( + f'Broadcasting {coerced_setting} to a workflow' + f' running in {self.workflow_run_mode.value} mode' + ' will have no effect, and will not be actioned.' + ) + continue + for point_string in point_strings or []: # Standardise the point and check its validity. bad_point = False diff --git a/cylc/flow/scheduler.py b/cylc/flow/scheduler.py index 888280b2681..a1364bd6e47 100644 --- a/cylc/flow/scheduler.py +++ b/cylc/flow/scheduler.py @@ -403,7 +403,7 @@ async def initialise(self): """ self.data_store_mgr = DataStoreMgr(self) self.broadcast_mgr = BroadcastMgr( - self.workflow_db_mgr, self.data_store_mgr) + self.workflow_db_mgr, self.data_store_mgr, self.get_run_mode()) self.server = WorkflowRuntimeServer(self) diff --git a/tests/integration/run_modes/test_simulation.py b/tests/integration/run_modes/test_simulation.py index de8a3f53031..aee9f1585d0 100644 --- a/tests/integration/run_modes/test_simulation.py +++ b/tests/integration/run_modes/test_simulation.py @@ -16,6 +16,7 @@ """Test the workings of simulation mode""" +import logging from pathlib import Path import pytest from pytest import param @@ -371,7 +372,7 @@ async def test_settings_reload( async def test_settings_broadcast( - flow, scheduler, start, monkeytime + flow, scheduler, start, monkeytime, log_filter ): """Assert that broadcasting a change in the settings for a task affects subsequent psuedo-submissions. @@ -415,12 +416,22 @@ async def test_settings_broadcast( # Change a setting using broadcast: schd.broadcast_mgr.put_broadcast( ['1066'], ['one'], [{ - 'simulation': {'fail cycle points': ''} + 'simulation': {'fail cycle points': ''}, }]) # Submit again - result is different: schd.task_job_mgr.submit_nonlive_task_jobs([itask], RunMode.SIMULATION) assert itask.mode_settings.sim_task_fails is False + # Assert that setting run mode on a simulation mode task fails with + # warning: + schd.broadcast_mgr.put_broadcast( + ['1066'], ['one'], [{ + 'run mode': 'live', + }]) + record = log_filter(contains='will not be actioned')[0] + assert record[0] == logging.WARNING + assert 'run mode' not in schd.broadcast_mgr.broadcasts + # Assert Clearing the broadcast works schd.broadcast_mgr.clear_broadcast() schd.task_job_mgr.submit_nonlive_task_jobs([itask], RunMode.SIMULATION) From d645d45569c753e7933f8f4c7278749bfb4b3327 Mon Sep 17 00:00:00 2001 From: Tim Pillinger Date: Mon, 10 Mar 2025 11:24:08 +0000 Subject: [PATCH 13/14] trivial cov increase --- tests/unit/run_modes/test_run_modes.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/unit/run_modes/test_run_modes.py b/tests/unit/run_modes/test_run_modes.py index 6cdd3b01672..cf4587fc161 100644 --- a/tests/unit/run_modes/test_run_modes.py +++ b/tests/unit/run_modes/test_run_modes.py @@ -36,3 +36,12 @@ def test_get_default_live(): def test__missing_(str_): """The RunMode enumeration works when fed a string in the wrong case""" assert RunMode(str_).value == str_.lower() + + +def test__missing_still_doesnt_work(): + """ + The RunMode enumeration raises an error when fed a string + that is not a mode. + """ + with pytest.raises(ValueError): + RunMode('garbage') From 06e5b941e57a68e2a0cc6a5aed35d2ce5ca786f6 Mon Sep 17 00:00:00 2001 From: Tim Pillinger Date: Mon, 10 Mar 2025 13:41:46 +0000 Subject: [PATCH 14/14] response to reveiw --- tests/integration/scripts/test_show.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/integration/scripts/test_show.py b/tests/integration/scripts/test_show.py index 426ba82e24d..e4490164752 100644 --- a/tests/integration/scripts/test_show.py +++ b/tests/integration/scripts/test_show.py @@ -22,9 +22,8 @@ from colorama import init as colour_init from cylc.flow.id import Tokens -from cylc.flow.option_parsers import Options from cylc.flow.scripts.show import ( - get_option_parser, + ShowOptions, show, ) @@ -295,7 +294,7 @@ async def test_task_instance_state_flows( async def test_task_run_mode_changes(flow, scheduler, start, capsys): """Broadcasting a change of run mode changes run mode shown by cylc show. """ - opts = Options(get_option_parser())() + opts = ShowOptions() schd = scheduler( flow({'scheduling': {'graph': {'R1': 'a'}}}), run_mode='live'