diff --git a/changes.d/6554.feat.md b/changes.d/6554.feat.md
new file mode 100644
index 0000000000..1abebb4e36
--- /dev/null
+++ b/changes.d/6554.feat.md
@@ -0,0 +1 @@
+`cylc show` now displays when a task has been set to skip mode
diff --git a/cylc/flow/config.py b/cylc/flow/config.py
index dbf9316218..08653b78ca 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
@@ -2447,6 +2448,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 67d3c58ec8..0ab26c33b1 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,7 @@ 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()],
description=lambda x: RunMode(x.value).describe() if x else None,
)
diff --git a/cylc/flow/run_modes/__init__.py b/cylc/flow/run_modes/__init__.py
index 2f09463138..70f0d42ea8 100644
--- a/cylc/flow/run_modes/__init__.py
+++ b/cylc/flow/run_modes/__init__.py
@@ -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.value.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 c387a3f3fc..bb1665a2b8 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,
@@ -145,6 +146,7 @@
}
runtime {
completion
+ runMode
}
}
}
@@ -346,9 +348,13 @@ async def prereqs_and_outputs_query(
attrs.append("queued")
if t_proxy['isRunahead']:
attrs.append("runahead")
+ 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)})"
+
ansiprint(f'state: {state_msg}')
# flow numbers, if not just 1
diff --git a/tests/flakyfunctional/cylc-show/00-simple.t b/tests/flakyfunctional/cylc-show/00-simple.t
index d0a700dd86..471515de6b 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/scripts/test_show.py b/tests/integration/scripts/test_show.py
index ec000a75a1..7c9befbacc 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."""
@@ -59,8 +63,8 @@ def mod_my_conf():
'destroyedtheworldyet.com/'
),
'question': 'mutually exclusive',
- }
- }
+ },
+ },
},
}
@@ -128,6 +132,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',
@@ -170,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()
@@ -195,20 +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', None,
+ False, [1], 'state: waiting (run mode={})', None,
),
pytest.param(
- True, [1, 2], 'state: waiting (held,queued,runahead)', 'flows: [1,2]',
+ True,
+ [1, 2],
+ '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."""
@@ -225,9 +242,13 @@ async def test_task_instance_state_flows(
'scheduling': {
'graph': {'R1': 'a'},
},
- }
+ 'runtime': {
+ 'a': {'run mode': 'skip'}
+ }
+ },
),
- paused_start=True
+ paused_start=True,
+ run_mode=workflow_run_mode,
)
async with start(schd):
@@ -257,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 [
@@ -266,3 +287,47 @@ async def test_task_instance_state_flows(
] == [
expected_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,
+ )
+ schd = scheduler(
+ flow({'scheduling': {'graph': {'R1': 'a'}}}),
+ 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
diff --git a/tests/integration/tui/screenshots/test_show.success.html b/tests/integration/tui/screenshots/test_show.success.html
index c0aaf20a9e..40b4f77924 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 │
diff --git a/tests/unit/run_modes/test_run_modes.py b/tests/unit/run_modes/test_run_modes.py
index 57d245016d..6cdd3b0167 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()