From a027a8835f53e1d1eed559514ee9657c6d368282 Mon Sep 17 00:00:00 2001 From: Matt Shin Date: Thu, 13 Oct 2016 15:27:05 +0100 Subject: [PATCH] cylc show: multi-task interface * cylc show: can now show multiple task name and/or task ID. * cylc gui: fix view prerequisites for runahead tasks. --- bin/cylc-insert | 2 +- bin/cylc-show | 108 +++++++++----------- lib/cylc/gui/app_gcylc.py | 79 ++++++-------- lib/cylc/network/https/suite_info_server.py | 12 ++- lib/cylc/scheduler.py | 18 ++-- lib/cylc/task_id.py | 4 +- lib/cylc/task_pool.py | 103 ++++++++++--------- tests/authentication/03-full-read.t | 2 +- tests/authentication/04-shutdown.t | 2 +- tests/authentication/05-full-control.t | 2 +- tests/authentication/06-suite-override.t | 2 +- tests/authentication/07-sha-hash.t | 2 +- tests/cylc-show/04-multi.t | 84 +++++++++++++++ tests/cylc-show/04-multi/reference.log | 5 + tests/cylc-show/04-multi/suite.rc | 29 ++++++ 15 files changed, 280 insertions(+), 174 deletions(-) create mode 100644 tests/cylc-show/04-multi.t create mode 100644 tests/cylc-show/04-multi/reference.log create mode 100644 tests/cylc-show/04-multi/suite.rc diff --git a/bin/cylc-insert b/bin/cylc-insert index 082d8c5ad66..0bf7635eca1 100755 --- a/bin/cylc-insert +++ b/bin/cylc-insert @@ -76,7 +76,7 @@ def main(): else: items = args for i, item in enumerate(items): - if not TaskID.is_valid_id_for_insert(item): + if not TaskID.is_valid_id_2(item): sys.exit('ERROR: "%s": invalid task ID (argument %d)' % ( item, i + 1)) prompt('Insert %s in %s' % (items, suite), options.force) diff --git a/bin/cylc-show b/bin/cylc-show index d1facfd61f2..92621ede0bf 100755 --- a/bin/cylc-show +++ b/bin/cylc-show @@ -37,13 +37,16 @@ from cylc.task_id import TaskID def main(): + """Implement "cylc show" CLI.""" parser = COP( - __doc__, comms=True, noforce=True, - argdoc=[('REG', 'Suite name'), - ('[' + TaskID.SYNTAX_OPT_POINT + ']', 'Task name or ID')]) + __doc__, comms=True, noforce=True, multitask=True, + argdoc=[ + ('REG', 'Suite name'), + ('[TASKID ...]', 'Task names or identifiers')]) - (options, args) = parser.parse_args() + options, args = parser.parse_args() suite = args[0] + task_args = args[1:] pclient = SuiteInfoClient( suite, options.owner, options.host, options.comms_timeout, @@ -51,66 +54,53 @@ def main(): print_uuid=options.print_uuid ) - if len(args) == 1: + if not task_args: # Print suite info. suite_info = pclient.get_info('get_suite_info') for key, value in sorted(suite_info.items(), reverse=True): print '%s: %s' % (key, value or "(not given)") - sys.exit(0) - - point_string = None - arg = args[1] - try: - name, point_string = TaskID.split(arg) - except ValueError: - # Print task info. - name = arg - else: - # Print task instance info. - task_id = arg - - info = pclient.get_info('get_task_info', name=name) - if not info: - sys.exit("ERROR: task not found: %s" % name) - for key, value in sorted(info.items(), reverse=True): - print "%s: %s" % (key, value or "(not given)") - - if point_string is not None: - result = pclient.get_info('get_task_requisites', - name=name, - point_string=point_string) - if not result: - sys.exit("ERROR: task instance not found: %s" % task_id) - - for task_id in result.keys(): - [pre, out, extra_info] = result[task_id] - - print '\nprerequisites (- => not satisfied):' - if len(pre) == 0: - print ' (None)' - for item in sorted(pre): - [msg, state] = item - if state: - descr = ' + ' - else: - descr = ' - ' - print descr + msg - - print '\noutputs (- => not completed):' - if len(out) == 0: - print ' (None)' - for item in sorted(out): - [msg, state] = item - if state: - descr = ' + ' - else: - descr = ' - ' - print descr + msg - - if len(extra_info.keys()) > 0: + return + + task_names = [arg for arg in task_args if TaskID.is_valid_name(arg)] + task_ids = [arg for arg in task_args if TaskID.is_valid_id_2(arg)] + + if task_names: + results = pclient.get_info('get_task_info', names=task_names) + for task_name, result in sorted(results.items()): + if len(results) > 1: + print "----\nTASK NAME: %s" % task_name + for key, value in sorted(result.items(), reverse=True): + print "%s: %s" % (key, value or "(not given)") + + if task_ids: + results, bad_items = pclient.get_info( + 'get_task_requisites', items=task_ids) + for task_id, result in sorted(results.items()): + if len(results) > 1: + print "----\nTASK ID: %s" % task_id + for key, value in sorted( + result["descriptions"].items(), reverse=True): + print "%s: %s" % (key, value or "(not given)") + + for name, done in [ + ("prerequisites", "satisfied"), ("outputs", "completed")]: + print '\n%s (- => not %s):' % (name, done) + if not result[name]: + print ' (None)' + for msg, state in sorted(result[name]): + if state: + print ' + ' + msg + else: + print ' - ' + msg + + if result["extras"]: print '\nother:' - for item in extra_info: - print ' o ', item, '...', extra_info[item] + for key, value in result["extras"].items(): + print ' o %s ... %s' % (key, value) + for bad_item in bad_items: + sys.stderr.write("No matching tasks found: %s\n" % bad_item) + if bad_items: + sys.exit(1) if __name__ == "__main__": diff --git a/lib/cylc/gui/app_gcylc.py b/lib/cylc/gui/app_gcylc.py index aa524c1672e..ab60892c420 100644 --- a/lib/cylc/gui/app_gcylc.py +++ b/lib/cylc/gui/app_gcylc.py @@ -1553,18 +1553,17 @@ def update_tb(self, tb, line, tags=None): tb.insert(tb.get_end_iter(), line) def popup_requisites(self, w, e, task_id): + """Show prerequisites of task_id in a pop up window.""" name, point_string = TaskID.split(task_id) - result = self.get_comms_info( - 'get_task_requisites', name=name, point_string=point_string) - if result: - # (else no tasks were found at all -suite shutting down) - if task_id not in result: - warning_dialog( - "Task proxy " + task_id + - " not found in " + self.cfg.suite + - ".\nTasks are removed once they are no longer needed.", - self.window).warn() - return + results, bad_items = self.get_comms_info( + 'get_task_requisites', items=[task_id]) + if not results or task_id in bad_items: + warning_dialog( + "Task proxy " + task_id + + " not found in " + self.cfg.suite + + ".\nTasks are removed once they are no longer needed.", + self.window).warn() + return window = gtk.Window() window.set_title(task_id + " State") @@ -1595,45 +1594,27 @@ def popup_requisites(self, w, e, task_id): self.update_tb(tb, 'TASK ', [bold]) self.update_tb(tb, task_id, [bold, blue]) self.update_tb(tb, ' in SUITE ', [bold]) - self.update_tb(tb, self.cfg.suite + '\n\n', [bold, blue]) - - [pre, out, extra_info] = result[task_id] - - self.update_tb(tb, 'Prerequisites', [bold]) - self.update_tb(tb, ' (') - self.update_tb(tb, 'red', [red]) - self.update_tb(tb, '=> NOT satisfied)\n') - - if len(pre) == 0: - self.update_tb(tb, ' - (None)\n') - for item in pre: - [msg, state] = item - if state: - tags = None - else: - tags = [red] - self.update_tb(tb, ' - ' + msg + '\n', tags) - - self.update_tb(tb, '\nOutputs', [bold]) - self.update_tb(tb, ' (') - self.update_tb(tb, 'red', [red]) - self.update_tb(tb, '=> NOT completed)\n') - - if len(out) == 0: - self.update_tb(tb, ' - (None)\n') - for item in out: - [msg, state] = item - if state: - tags = [] - else: - tags = [red] - self.update_tb(tb, ' - ' + msg + '\n', tags) + self.update_tb(tb, self.cfg.suite + '\n', [bold, blue]) + + for name, done in [ + ("prerequisites", "satisfied"), ("outputs", "completed")]: + self.update_tb(tb, '\n' + name.title(), [bold]) + self.update_tb(tb, ' (') + self.update_tb(tb, 'red', [red]) + self.update_tb(tb, '=> NOT %s)\n' % done) + if not results[task_id][name]: + self.update_tb(tb, ' - (None)\n') + for msg, state in results[task_id][name]: + if state: + tags = None + else: + tags = [red] + self.update_tb(tb, ' - ' + msg + '\n', tags) - if len(extra_info.keys()) > 0: + if results[task_id]['extras']: self.update_tb(tb, '\nOther\n', [bold]) - for item in extra_info: - self.update_tb( - tb, ' - ' + item + ': ' + str(extra_info[item]) + '\n') + for key, value in results[task_id]['extras'].items(): + self.update_tb(tb, ' - %s: %s\n' % (key, value)) self.update_tb(tb, '\nNOTE: ', [bold]) self.update_tb( @@ -2180,7 +2161,7 @@ def insert_task(self, w, window, entry_task_ids, entry_stop_point): warning_dialog('Enter valid task/family IDs', self.window).warn() return for i, task_id in enumerate(task_ids): - if not TaskID.is_valid_id_for_insert(task_id): + if not TaskID.is_valid_id_2(task_id): warning_dialog( '"%s": invalid task ID (argument %d)' % (task_id, i + 1), self.window).warn() diff --git a/lib/cylc/network/https/suite_info_server.py b/lib/cylc/network/https/suite_info_server.py index 84a5313a826..4102179a646 100644 --- a/lib/cylc/network/https/suite_info_server.py +++ b/lib/cylc/network/https/suite_info_server.py @@ -63,8 +63,10 @@ def get_suite_info(self): @cherrypy.expose @cherrypy.tools.json_out() - def get_task_info(self, name): - return self._put("get_task_info", (name,)) + def get_task_info(self, names): + if not isinstance(names, list): + names = [names] + return self._put("get_task_info", (names,)) @cherrypy.expose @cherrypy.tools.json_out() @@ -114,8 +116,10 @@ def get_graph_raw(self, start_point_string, stop_point_string, @cherrypy.expose @cherrypy.tools.json_out() - def get_task_requisites(self, name, point_string): - return self._put("get_task_requisites", (name, point_string)) + def get_task_requisites(self, items): + if not isinstance(items, list): + items = [items] + return self._put("get_task_requisites", (items,)) def _put(self, command, command_args, command_kwargs=None): if command_args is None: diff --git a/lib/cylc/scheduler.py b/lib/cylc/scheduler.py index d0b7a30eec7..7e1280bf49c 100644 --- a/lib/cylc/scheduler.py +++ b/lib/cylc/scheduler.py @@ -770,12 +770,15 @@ def info_get_suite_info(self): return {'title': self.config.cfg['title'], 'description': self.config.cfg['description']} - def info_get_task_info(self, name): + def info_get_task_info(self, names): """Return info of a task.""" - try: - return self.config.describe(name) - except KeyError: - return {} + results = {} + for name in names: + try: + results[name] = self.config.describe(name) + except KeyError: + results[name] = {} + return results def info_get_all_families(self, exclude_root=False): """Return info of all families.""" @@ -805,10 +808,9 @@ def info_get_graph_raw(self, cto, ctn, group_nodes=None, rgraph, self.config.suite_polling_tasks, self.config.leaves, self.config.feet) - def info_get_task_requisites(self, name, point_string): + def info_get_task_requisites(self, items): """Return prerequisites of a task.""" - return self.pool.get_task_requisites( - TaskID.get(name, self.get_standardised_point_string(point_string))) + return self.pool.get_task_requisites(items) def command_set_stop_cleanly(self, kill_active_tasks=False): """Stop job submission and set the flag for clean shutdown.""" diff --git a/lib/cylc/task_id.py b/lib/cylc/task_id.py index 34030dd4312..b61da147c84 100644 --- a/lib/cylc/task_id.py +++ b/lib/cylc/task_id.py @@ -59,8 +59,8 @@ def is_valid_id(cls, id_str): point and cls.POINT_REC.match(point)) @classmethod - def is_valid_id_for_insert(cls, id_str): - """Return whether id_str is good as an insert client argument. + def is_valid_id_2(cls, id_str): + """Return whether id_str is good as a client argument for e.g. insert. Return True if "." or "/" appears once in the string. Cannot really do more as the string may have wildcards. diff --git a/lib/cylc/task_pool.py b/lib/cylc/task_pool.py index 8d704fc66d1..0311d895d0b 100644 --- a/lib/cylc/task_pool.py +++ b/lib/cylc/task_pool.py @@ -865,7 +865,7 @@ def poll_task_jobs(self, items=None): """ if self.run_mode == 'simulation': return - itasks, n_warnings = self._filter_task_proxies(items) + itasks, bad_items = self._filter_task_proxies(items) active_itasks = [] for itask in itasks: if itask.state.status in TASK_STATUSES_ACTIVE: @@ -875,7 +875,7 @@ def poll_task_jobs(self, items=None): '%s: skip poll, task not pollable' % itask.identity) self._run_job_cmd( self.JOBS_POLL, active_itasks, self.poll_task_jobs_callback) - return n_warnings + return len(bad_items) def poll_task_jobs_callback(self, ctx): """Callback when poll tasks command exits.""" @@ -895,7 +895,7 @@ def kill_task_jobs(self, items=None): If items is specified, kill active tasks matching given IDs. """ - itasks, n_warnings = self._filter_task_proxies(items) + itasks, bad_items = self._filter_task_proxies(items) active_itasks = [] for itask in itasks: is_active = itask.state.status in TASK_STATUSES_ACTIVE @@ -909,7 +909,7 @@ def kill_task_jobs(self, items=None): '%s: skip kill, task not killable' % itask.identity) self._run_job_cmd( self.JOBS_KILL, active_itasks, self.kill_task_jobs_callback) - return n_warnings + return len(bad_items) def kill_task_jobs_callback(self, ctx): """Callback when kill tasks command exits.""" @@ -983,17 +983,17 @@ def set_hold_point(self, point): def hold_tasks(self, items): """Hold tasks with IDs matching any item in "ids".""" - itasks, n_warnings = self._filter_task_proxies(items) + itasks, bad_items = self._filter_task_proxies(items) for itask in itasks: itask.state.reset_state(TASK_STATUS_HELD) - return n_warnings + return len(bad_items) def release_tasks(self, items): """Release held tasks with IDs matching any item in "ids".""" - itasks, n_warnings = self._filter_task_proxies(items) + itasks, bad_items = self._filter_task_proxies(items) for itask in itasks: itask.state.release() - return n_warnings + return len(bad_items) def hold_all_tasks(self): """Hold all tasks.""" @@ -1218,16 +1218,16 @@ def spawn_tasks(self, items): """Force tasks to spawn successors if they haven't already. """ - itasks, n_warnings = self._filter_task_proxies(items) + itasks, bad_items = self._filter_task_proxies(items) for itask in itasks: if not itask.has_spawned: itask.log(INFO, "forced spawning") self.force_spawn(itask) - return n_warnings + return len(bad_items) def reset_task_states(self, items, status): """Reset task states.""" - itasks, n_warnings = self._filter_task_proxies(items) + itasks, bad_items = self._filter_task_proxies(items) for itask in itasks: itask.log(INFO, "resetting state to %s" % status) if status == TASK_STATUS_READY: @@ -1244,20 +1244,21 @@ def reset_task_states(self, items, status): get_time_string_from_unix_time(time_)) else: itask.state.reset_state(status) - return n_warnings + return len(bad_items) def remove_tasks(self, items, spawn=False): """Remove tasks from pool.""" - itasks, n_warnings = self._filter_task_proxies(items) + itasks, bad_items = self._filter_task_proxies(items) for itask in itasks: if spawn: self.force_spawn(itask) self.remove(itask, 'by request') - return n_warnings + return len(bad_items) def trigger_tasks(self, items): """Trigger tasks.""" - itasks, n_warnings = self._filter_task_proxies(items) + itasks, bad_items = self._filter_task_proxies(items) + n_warnings = len(bad_items) for itask in itasks: if itask.state.status in TASK_STATUSES_ACTIVE: self.log.warning('%s: already triggered' % itask.identity) @@ -1270,7 +1271,8 @@ def trigger_tasks(self, items): def dry_run_task(self, items): """Create job file for "cylc trigger --edit".""" - itasks, n_warnings = self._filter_task_proxies(items) + itasks, bad_items = self._filter_task_proxies(items) + n_warnings = len(bad_items) if len(itasks) > 1: self.log.warning("Unique task match not found: %s" % items) n_warnings += 1 @@ -1394,33 +1396,41 @@ def get_task_jobfile_path(self, id_): return path, os.path.dirname(os.path.dirname(path)) return False, "task not found" - def get_task_requisites(self, taskid): - info = {} - found = False - for itask in self.get_tasks(): - id_ = itask.identity - if id_ == taskid: - found = True - extra_info = {} - if itask.tdef.clocktrigger_offset is not None: - extra_info['Clock trigger time reached'] = ( - itask.start_time_reached()) - extra_info['Triggers at'] = itask.delayed_start_str - for trig, satisfied in itask.state.external_triggers.items(): - if satisfied: - state = 'satisfied' - else: - state = 'NOT satisfied' - extra_info['External trigger "%s"' % trig] = state - - info[id_] = [ - itask.state.prerequisites_dump(), - itask.state.outputs.dump(), - extra_info, - ] - if not found: - self.log.warning('task state info request: task(s) not found') - return info + def get_task_requisites(self, items): + """Return task prerequisites. + + Result in a dict of a dict: + { + "task_id": { + "descriptions": {key: value, ...}, + "prerequisites": {key: value, ...}, + "outputs": {key: value, ...}, + "extras": {key: value, ...}, + }, + ... + } + """ + itasks, bad_items = self._filter_task_proxies(items) + results = {} + for itask in itasks: + extras = {} + if itask.tdef.clocktrigger_offset is not None: + extras['Clock trigger time reached'] = ( + itask.start_time_reached()) + extras['Triggers at'] = itask.delayed_start_str + for trig, satisfied in itask.state.external_triggers.items(): + if satisfied: + state = 'satisfied' + else: + state = 'NOT satisfied' + extras['External trigger "%s"' % trig] = state + + results[itask.identity] = { + "descriptions": itask.tdef.describe(), + "prerequisites": itask.state.prerequisites_dump(), + "outputs": itask.state.outputs.dump(), + "extras": extras} + return results, bad_items def match_ext_triggers(self): """See if any queued external event messages can trigger tasks.""" @@ -1486,6 +1496,7 @@ def put_rundb_task_pool(self): def _filter_task_proxies(self, items): """Return task proxies that match names, points, states in items. + Return (itasks, bad_items). In the new form, the arguments should look like: items -- a list of strings for matching task proxies, each with the general form name[.point][:state] or [point/]name[:state] @@ -1494,7 +1505,7 @@ def _filter_task_proxies(self, items): """ itasks = [] - n_warnings = 0 + bad_items = [] if not items: itasks += self.get_all_tasks() else: @@ -1519,8 +1530,8 @@ def _filter_task_proxies(self, items): tasks_found = True if not tasks_found: self.log.warning(self.ERR_PREFIX_TASKID_MATCH + item) - n_warnings += 1 - return itasks, n_warnings + bad_items.append(item) + return itasks, bad_items @classmethod def _parse_task_item(cls, item): diff --git a/tests/authentication/03-full-read.t b/tests/authentication/03-full-read.t index bce3ac43178..27f9cc3fb8e 100644 --- a/tests/authentication/03-full-read.t +++ b/tests/authentication/03-full-read.t @@ -63,7 +63,7 @@ grep_ok "\[client-command] get_suite_info ${USER}@.*:cylc-show" suite.log1 # "cylc show" (task info) OK. TEST_NAME="${TEST_NAME_BASE}-show2" -run_ok "${TEST_NAME}" cylc show "${SUITE_NAME}" foo.1 +run_ok "${TEST_NAME}" cylc show "${SUITE_NAME}" foo cylc log "${SUITE_NAME}" > suite.log2 grep_ok "\[client-command] get_task_info ${USER}@.*:cylc-show" suite.log2 diff --git a/tests/authentication/04-shutdown.t b/tests/authentication/04-shutdown.t index 2ba9ff975d0..d6e8db85917 100644 --- a/tests/authentication/04-shutdown.t +++ b/tests/authentication/04-shutdown.t @@ -62,7 +62,7 @@ grep_ok "\[client-command] get_suite_info ${USER}@.*:cylc-show" suite.log1 # "cylc show" (task info) OK. TEST_NAME="${TEST_NAME_BASE}-show2" -run_ok "${TEST_NAME}" cylc show "${SUITE_NAME}" foo.1 +run_ok "${TEST_NAME}" cylc show "${SUITE_NAME}" foo cylc log "${SUITE_NAME}" > suite.log2 grep_ok "\[client-command] get_task_info ${USER}@.*:cylc-show" suite.log2 diff --git a/tests/authentication/05-full-control.t b/tests/authentication/05-full-control.t index e380787ab1d..a4f5ddd58a7 100644 --- a/tests/authentication/05-full-control.t +++ b/tests/authentication/05-full-control.t @@ -60,7 +60,7 @@ grep_ok "\[client-command\] get_suite_info ${USER}@.*:cylc-show" suite.log1 # "cylc show" (task info) OK. TEST_NAME="${TEST_NAME_BASE}-show2" -run_ok "${TEST_NAME}" cylc show "${SUITE_NAME}" foo.1 +run_ok "${TEST_NAME}" cylc show "${SUITE_NAME}" foo cylc log "${SUITE_NAME}" > suite.log2 grep_ok "\[client-command\] get_task_info ${USER}@.*:cylc-show" suite.log2 diff --git a/tests/authentication/06-suite-override.t b/tests/authentication/06-suite-override.t index db0d0159447..1a794923a95 100644 --- a/tests/authentication/06-suite-override.t +++ b/tests/authentication/06-suite-override.t @@ -64,7 +64,7 @@ grep_ok "\[client-command] get_suite_info ${USER}@.*:cylc-show" suite.log1 # "cylc show" (task info) OK. TEST_NAME="${TEST_NAME_BASE}-show2" -run_ok "${TEST_NAME}" cylc show "${SUITE_NAME}" foo.1 +run_ok "${TEST_NAME}" cylc show "${SUITE_NAME}" foo cylc log "${SUITE_NAME}" > suite.log2 grep_ok "\[client-command] get_task_info ${USER}@.*:cylc-show" suite.log2 diff --git a/tests/authentication/07-sha-hash.t b/tests/authentication/07-sha-hash.t index 389cc1663f9..ff7dbbb6d52 100644 --- a/tests/authentication/07-sha-hash.t +++ b/tests/authentication/07-sha-hash.t @@ -63,7 +63,7 @@ grep_ok "\[client-command\] get_suite_info ${USER}@.*:cylc-show" suite.log1 # "cylc show" (task info) OK. TEST_NAME="${TEST_NAME_BASE}-show2" -run_ok "${TEST_NAME}" cylc show "${SUITE_NAME}" foo.1 +run_ok "${TEST_NAME}" cylc show "${SUITE_NAME}" foo cylc log "${SUITE_NAME}" > suite.log2 grep_ok "\[client-command\] get_task_info ${USER}@.*:cylc-show" suite.log2 diff --git a/tests/cylc-show/04-multi.t b/tests/cylc-show/04-multi.t new file mode 100644 index 00000000000..2acd3ffbbbe --- /dev/null +++ b/tests/cylc-show/04-multi.t @@ -0,0 +1,84 @@ +#!/bin/bash +# THIS FILE IS PART OF THE CYLC SUITE ENGINE. +# Copyright (C) 2008-2016 NIWA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +#------------------------------------------------------------------------------- +# Test cylc show multiple tasks +. "$(dirname "$0")/test_header" + +set_test_number 4 + +install_suite "${TEST_NAME_BASE}" "${TEST_NAME_BASE}" + +run_ok "${TEST_NAME_BASE}-validate" cylc validate "${SUITE_NAME}" +suite_run_ok "${TEST_NAME_BASE}-run" \ + cylc run --reference-test --debug "${SUITE_NAME}" + +RUND="$(cylc get-global-config --print-run-dir)/${SUITE_NAME}" +for FILE in "${RUND}/show1.txt" "${RUND}/show2.txt"; do + cmp_ok "${FILE}" <<'__TXT__' +---- +TASK ID: t1.2016 +title: (not given) +description: (not given) + +prerequisites (- => not satisfied): + (None) + +outputs (- => not completed): + + t1.2016 started + + t1.2016 submitted + - t1.2016 succeeded +---- +TASK ID: t1.2017 +title: (not given) +description: (not given) + +prerequisites (- => not satisfied): + + t1.2016 started + +outputs (- => not completed): + + t1.2017 started + + t1.2017 submitted + - t1.2017 succeeded +---- +TASK ID: t1.2018 +title: (not given) +description: (not given) + +prerequisites (- => not satisfied): + + t1.2017 started + +outputs (- => not completed): + + t1.2018 started + + t1.2018 submitted + - t1.2018 succeeded +---- +TASK ID: t1.2019 +title: (not given) +description: (not given) + +prerequisites (- => not satisfied): + - t1.2018 started + +outputs (- => not completed): + - t1.2019 started + - t1.2019 submitted + - t1.2019 succeeded +__TXT__ +done + +purge_suite "${SUITE_NAME}" +exit diff --git a/tests/cylc-show/04-multi/reference.log b/tests/cylc-show/04-multi/reference.log new file mode 100644 index 00000000000..ebe859573ff --- /dev/null +++ b/tests/cylc-show/04-multi/reference.log @@ -0,0 +1,5 @@ +2014-11-17T09:01:57Z INFO - Initial point: 2016 +2014-11-17T09:01:57Z INFO - Final point: 2018 +2014-11-17T09:01:58Z INFO - [t1.2016] -triggered off [] +2014-11-17T09:02:03Z INFO - [t1.2017] -triggered off ['t1.2016'] +2014-11-17T09:02:06Z INFO - [t1.2018] -triggered off ['t1.2017'] diff --git a/tests/cylc-show/04-multi/suite.rc b/tests/cylc-show/04-multi/suite.rc new file mode 100644 index 00000000000..920f51e8a4b --- /dev/null +++ b/tests/cylc-show/04-multi/suite.rc @@ -0,0 +1,29 @@ +#!jinja2 +[cylc] + cycle point format = %Y + UTC mode = True +[scheduling] + initial cycle point = 2016 + final cycle point = 2018 + [[dependencies]] + [[[P1Y]]] + graph = t1[-P1Y]:start => t1 +[runtime] + [[t1]] + script = """ +# Final task runs the show. The other wait after starting. +if [[ "${CYLC_TASK_CYCLE_POINT}" == '2018' ]]; then + # Ensure suite knows about current task started + wait "${CYLC_TASK_MESSAGE_STARTED_PID}" 2>/dev/null || true + sleep 5 + # Test alternate syntaxes + cylc show "${CYLC_SUITE_NAME}" 't1.*' >"${CYLC_SUITE_RUN_DIR}/show1.txt" + cylc show "${CYLC_SUITE_NAME}" '*/t1' >"${CYLC_SUITE_RUN_DIR}/show2.txt" +else + while [[ ! -s "${CYLC_SUITE_RUN_DIR}/show2.txt" ]]; do + sleep 1 + done +fi +""" + [[[job]]] + execution time limit = PT1M