Skip to content

Commit f81634e

Browse files
committed
Improve and refactor checking of initial, start, stop and final cycle points
1 parent e552763 commit f81634e

File tree

4 files changed

+157
-14
lines changed

4 files changed

+157
-14
lines changed

changes.d/6874.fix.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improved warnings for suspect start and stop cycle points.

cylc/flow/config.py

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,10 @@ def __init__(
471471
self.process_start_cycle_point()
472472
self.process_final_cycle_point()
473473
self.process_stop_cycle_point()
474+
if self.start_point:
475+
self.cycle_point_warning('start', 'before', 'initial')
476+
self.cycle_point_warning('start', 'after', 'final')
477+
self.cycle_point_warning('stop', 'before', 'start')
474478

475479
# Parse special task cycle point offsets, and replace family names.
476480
LOG.debug("Parsing [special tasks]")
@@ -797,6 +801,7 @@ def process_start_cycle_point(self) -> None:
797801
if self.options.startcp == 'now':
798802
self.options.startcp = get_current_time_string()
799803
self.start_point = get_point(self.options.startcp).standardise()
804+
800805
elif starttask:
801806
# Start from designated task(s).
802807
# Select the earliest start point for use in pre-initial ignore.
@@ -815,6 +820,25 @@ def process_start_cycle_point(self) -> None:
815820
# Start from the initial point.
816821
self.start_point = self.initial_point
817822

823+
def cycle_point_warning(self, label1, order, label2):
824+
"""Centralized logic for warning that start, stop, initial and
825+
final cycle points are not sensibly ordered.
826+
"""
827+
point1 = getattr(self, label1 + '_point')
828+
point2 = getattr(self, label2 + '_point')
829+
if (
830+
order == 'before' and point1 < point2
831+
or order == 'after' and point1 > point2
832+
):
833+
msg = (
834+
f"{label1} cycle point '{point1}' will have no effect as"
835+
f" it is {order} the {label2} cycle point '{point2}'."
836+
)
837+
# Capitalize() makes the ISO strings look very odd:
838+
LOG.warning(msg[0].upper() + msg[1:])
839+
return True
840+
return False
841+
818842
def process_final_cycle_point(self) -> None:
819843
"""Validate and set the final cycle point from flow.cylc or options.
820844
@@ -895,15 +919,15 @@ def process_stop_cycle_point(self) -> None:
895919
self.initial_point,
896920
).standardise()
897921
if (
898-
self.final_point is not None
899-
and self.stop_point is not None
900-
and self.stop_point > self.final_point
901-
):
902-
LOG.warning(
903-
f"Stop cycle point '{self.stop_point}' will have no "
904-
"effect as it is after the final cycle "
905-
f"point '{self.final_point}'."
922+
self.stop_point is not None
923+
and (
924+
(
925+
self.final_point is not None
926+
and self.cycle_point_warning('stop', 'after', 'final')
927+
)
928+
or self.cycle_point_warning('stop', 'before', 'initial')
906929
)
930+
):
907931
self.stop_point = None
908932
stopcp_str = str(self.stop_point) if self.stop_point else None
909933
self.cfg['scheduling']['stop after cycle point'] = stopcp_str

tests/integration/test_config.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,3 +670,119 @@ async def test_invalid_starttask(one_conf, flow, scheduler, start):
670670
with pytest.raises(InputError, match='a///b'):
671671
async with start(schd):
672672
pass
673+
674+
675+
@pytest.mark.parametrize(
676+
'a, b, c, d, validation_fail, err',
677+
(
678+
('initial', 'start', 'stop', 'final', True, False),
679+
(
680+
'initial', 'start', 'final', 'stop', True,
681+
"Stop cycle point '20030101T0000Z' will have no effect as it"
682+
" is after the final cycle point '20020101T0000Z'."
683+
),
684+
(
685+
'initial', 'stop', 'start', 'final', False,
686+
"Stop cycle point '20010101T0000Z' will have no effect as it"
687+
" is before the start cycle point '20020101T0000Z'."
688+
),
689+
(
690+
'initial', 'stop', 'final', 'start', False,
691+
"Start cycle point '20030101T0000Z' will have no effect as it"
692+
" is after the final cycle point '20020101T0000Z'."
693+
),
694+
(
695+
'initial', 'final', 'start', 'stop', True,
696+
"Stop cycle point '20030101T0000Z' will have no effect as it"
697+
" is after the final cycle point '20010101T0000Z'."
698+
),
699+
(
700+
'initial', 'final', 'stop', 'start', True,
701+
"Stop cycle point '20020101T0000Z' will have no effect as it"
702+
" is after the final cycle point '20010101T0000Z'."
703+
),
704+
(
705+
'start', 'initial', 'stop', 'final', False,
706+
"Start cycle point '20000101T0000Z' will have no effect as it"
707+
" is before the initial cycle point '20010101T0000Z'."
708+
),
709+
(
710+
'start', 'initial', 'final', 'stop', True,
711+
"Stop cycle point '20030101T0000Z' will have no effect as it"
712+
" is after the final cycle point '20020101T0000Z'."
713+
),
714+
(
715+
'start', 'stop', 'initial', 'final', True,
716+
"Stop cycle point '20010101T0000Z' will have no effect as it"
717+
" is before the initial cycle point '20020101T0000Z'."
718+
),
719+
('start', 'stop', 'final', 'initial', True, WorkflowConfigError),
720+
('start', 'final', 'initial', 'stop', True, WorkflowConfigError),
721+
('start', 'final', 'stop', 'initial', True, WorkflowConfigError),
722+
(
723+
'stop', 'initial', 'start', 'final', True,
724+
"Stop cycle point '20000101T0000Z' will have no effect as it"
725+
" is before the initial cycle point '20010101T0000Z'."
726+
),
727+
(
728+
'stop', 'initial', 'final', 'start', True,
729+
"Stop cycle point '20000101T0000Z' will have no effect as it"
730+
" is before the initial cycle point '20010101T0000Z'."
731+
),
732+
(
733+
'stop', 'start', 'initial', 'final', True,
734+
"Stop cycle point '20000101T0000Z' will have no effect as it"
735+
" is before the initial cycle point '20020101T0000Z'."
736+
),
737+
('stop', 'start', 'final', 'initial', True, WorkflowConfigError),
738+
('stop', 'final', 'initial', 'start', True, WorkflowConfigError),
739+
('stop', 'final', 'start', 'initial', True, WorkflowConfigError),
740+
('final', 'initial', 'start', 'stop', True, WorkflowConfigError),
741+
('final', 'initial', 'stop', 'start', True, WorkflowConfigError),
742+
('final', 'start', 'initial', 'stop', True, WorkflowConfigError),
743+
('final', 'start', 'stop', 'initial', True, WorkflowConfigError),
744+
('final', 'stop', 'initial', 'start', True, WorkflowConfigError),
745+
('final', 'stop', 'start', 'initial', True, WorkflowConfigError),
746+
)
747+
)
748+
async def test_milestone_cycle_points(
749+
a,
750+
b,
751+
c,
752+
d,
753+
validation_fail,
754+
err,
755+
flow,
756+
validate,
757+
scheduler,
758+
start,
759+
log_filter,
760+
caplog,
761+
):
762+
"""Ensure that all combinations of initial, start, stop and final cycle
763+
point return sensible warnings or errors.
764+
"""
765+
order = dict(zip((a, b, c, d), [2000, 2001, 2002, 2003]))
766+
767+
wid = flow({
768+
'scheduling': {
769+
'initial cycle point': order['initial'],
770+
'stop after cycle point': order['stop'],
771+
'final cycle point': order['final'],
772+
'graph': {'P1Y': 'foo'}
773+
},
774+
})
775+
if validation_fail:
776+
if not err:
777+
validate(wid)
778+
elif isinstance(err, str):
779+
validate(wid)
780+
assert err in caplog.messages
781+
else:
782+
with pytest.raises(err):
783+
validate(wid)
784+
785+
else:
786+
schd = scheduler(wid, startcp=str(order['start']))
787+
async with start(schd):
788+
assert err in caplog.messages

tests/unit/test_config.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414
# You should have received a copy of the GNU General Public License
1515
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1616

17+
from functools import partial
18+
import os
19+
from optparse import Values
20+
import pytest
1721
from contextlib import suppress
1822
import logging
19-
from optparse import Values
20-
import os
2123
from pathlib import Path
2224
from textwrap import dedent
2325
from types import SimpleNamespace
@@ -31,8 +33,6 @@
3133
Type,
3234
)
3335

34-
import pytest
35-
3636
from cylc.flow import (
3737
CYLC_LOG,
3838
flags,
@@ -710,7 +710,7 @@ def test_process_fcp(
710710
id="stopcp-beyond-fcp"
711711
),
712712
pytest.param(
713-
'+P12Y -P2Y', None, '2000', None, None,
713+
'+P12Y -P2Y', None, '1010', None, None,
714714
id="stopcp-relative-to-icp"
715715
),
716716
]
@@ -742,11 +742,13 @@ def test_process_stop_cycle_point(
742742
'stop after cycle point': cfg_stopcp
743743
}
744744
},
745-
initial_point=ISO8601Point('1990'),
745+
initial_point=ISO8601Point('1000'),
746746
final_point=fcp,
747747
stop_point=None,
748748
options=RunOptions(stopcp=options_stopcp),
749749
)
750+
mock_config.cycle_point_warning = partial(
751+
WorkflowConfig.cycle_point_warning, mock_config)
750752

751753
WorkflowConfig.process_stop_cycle_point(mock_config)
752754
assert str(mock_config.stop_point) == str(expected_value)

0 commit comments

Comments
 (0)