Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add run mode skip to cylc show #6554

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions changes.d/6554.feat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Cylc show now displays that a task has been set to skip mode
8 changes: 8 additions & 0 deletions cylc/flow/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Comment on lines +2452 to +2456
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI broadcasting a run mode of live or skip seems to override this

Copy link
Member

@oliver-sanders oliver-sanders Feb 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct right?

Copy link
Member

@MetRonnie MetRonnie Feb 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't be able to broadcast task run mode = live when the workflow is running in simulation or dummy mode? Not that it matters too much

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right, yes. Unfortunately rather hard to prevent but will result in run mode being set (and reported) with an erroneous 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
Expand Down
4 changes: 2 additions & 2 deletions cylc/flow/network/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
)

Expand Down
8 changes: 8 additions & 0 deletions cylc/flow/run_modes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@
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

Check warning on line 94 in cylc/flow/run_modes/__init__.py

View check run for this annotation

Codecov / codecov/patch

cylc/flow/run_modes/__init__.py#L94

Added line #L94 was not covered by tests

def get_submit_method(self) -> 'Optional[SubmissionInterface]':
"""Return the job submission method for this run mode.

Expand Down
8 changes: 8 additions & 0 deletions cylc/flow/scripts/show.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -145,6 +146,7 @@
}
runtime {
completion
runMode
}
}
}
Expand Down Expand Up @@ -346,9 +348,15 @@ 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']}")
state_msg = state
if attrs:
state_msg += f" ({','.join(attrs)})"

ansiprint(f'<bold>state:</bold> {state_msg}')

# flow numbers, if not just 1
Expand Down
5 changes: 4 additions & 1 deletion tests/flakyfunctional/cylc-show/00-simple.t
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/run_modes/test_nonlive.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
85 changes: 75 additions & 10 deletions tests/integration/scripts/test_show.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import json
import pytest
import re
from types import SimpleNamespace

from colorama import init as colour_init
Expand All @@ -26,6 +27,9 @@
)


RE_STATE = re.compile('state:.*')


@pytest.fixture(scope='module')
def mod_my_conf():
"""A workflow configuration with some workflow metadata."""
Expand Down Expand Up @@ -59,8 +63,8 @@ def mod_my_conf():
'destroyedtheworldyet.com/'
),
'question': 'mutually exclusive',
}
}
},
},
},
}

Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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()
Expand All @@ -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."""

Expand All @@ -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):

Expand Down Expand Up @@ -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 [
Expand All @@ -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(
Copy link
Member

@MetRonnie MetRonnie Feb 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have the ShowOptions object that can be used for this. That way you can omit defaults and it will ensure the options you provide are correct.

ShowOptions = Options(get_option_parser())

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
2 changes: 1 addition & 1 deletion tests/integration/tui/screenshots/test_show.success.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">│</span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">description: The first metasyntactic </span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">│</span><span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">│</span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">variable. </span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">│</span><span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">│</span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">URL: (not given) </span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">│</span><span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">│</span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">state: waiting (queued) </span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">│</span><span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">│</span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">state: waiting (queued,run mode=Simulation) </span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">│</span><span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">│</span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">prerequisites: (None) </span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">│</span><span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">│</span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">outputs: (&#x27;⨯&#x27;: not completed) </span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">│</span><span style="color:#000000;background:#e5e5e5"> </span>
<span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">│</span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5"> ⨯ 1/foo expired </span><span style="color:#000000;background:#e5e5e5"> </span><span style="color:#000000;background:#e5e5e5">│</span><span style="color:#000000;background:#e5e5e5"> </span>
Expand Down
8 changes: 8 additions & 0 deletions tests/unit/run_modes/test_run_modes.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"""Tests for utilities supporting run modes.
"""

import pytest

from cylc.flow.run_modes import RunMode


Expand All @@ -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()
Loading