From 91483d68c77490c2798d5fd72f2d219c0bed41bb Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Mon, 23 Nov 2020 12:26:52 +0000 Subject: [PATCH 01/78] lib: remove obsolete functionality --- metomi/rose/run.py | 4 +- metomi/rose/stem.py | 497 ------------------------- metomi/rose/suite_clean.py | 206 ----------- metomi/rose/suite_control.py | 131 ------- metomi/rose/suite_log.py | 20 +- metomi/rose/suite_restart.py | 91 ----- metomi/rose/suite_run.py | 694 ----------------------------------- 7 files changed, 21 insertions(+), 1622 deletions(-) delete mode 100644 metomi/rose/stem.py delete mode 100644 metomi/rose/suite_clean.py delete mode 100644 metomi/rose/suite_control.py delete mode 100644 metomi/rose/suite_restart.py delete mode 100644 metomi/rose/suite_run.py diff --git a/metomi/rose/run.py b/metomi/rose/run.py index d1be7e3398..1be639be5b 100644 --- a/metomi/rose/run.py +++ b/metomi/rose/run.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with Rose. If not, see . # ----------------------------------------------------------------------------- -"""Shared utilities for app/suite/task run.""" +"""Shared utilities for app/task run.""" import os from metomi.rose.config_processor import ConfigProcessorsManager @@ -94,7 +94,7 @@ def __init__(self, **kwargs): class Runner(object): - """Invoke a Rose application or a Rose suite.""" + """Invoke a Rose application.""" CONF_NAME = None NAME = None diff --git a/metomi/rose/stem.py b/metomi/rose/stem.py deleted file mode 100644 index 8c777e8b1c..0000000000 --- a/metomi/rose/stem.py +++ /dev/null @@ -1,497 +0,0 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -# ----------------------------------------------------------------------------- -"""Implementation of 'rose stem'""" - -import os -import re -import sys - -import metomi.rose.config -from metomi.rose.fs_util import FileSystemUtil -from metomi.rose.host_select import HostSelector -from metomi.rose.opt_parse import RoseOptionParser -from metomi.rose.popen import RosePopener, RosePopenError -from metomi.rose.reporter import Reporter, Event -from metomi.rose.resource import ResourceLocator -from metomi.rose.suite_run import SuiteRunner - -DEFAULT_TEST_DIR = 'rose-stem' -OPTIONS = ['group', 'source', 'task', ] -ROSE_STEM_VERSION = 1 -SUITE_RC_PREFIX = '[jinja2:suite.rc]' - - -class ConfigVariableSetEvent(Event): - - """Event to report a particular variable has been set.""" - - LEVEL = Event.V - - def __repr__(self): - return "Variable %s set to %s" % (self.args[0], self.args[1]) - - __str__ = __repr__ - - -class ConfigSourceTreeSetEvent(Event): - - """Event to report a source tree for config files.""" - - LEVEL = Event.V - - def __repr__(self): - return "Using config files from source %s" % (self.args[0]) - - __str__ = __repr__ - - -class NameSetEvent(Event): - - """Event to report a name for the suite being set.""" - - LEVEL = Event.V - - def __repr__(self): - return "Suite is named %s" % (self.args[0]) - - __str__ = __repr__ - - -class ProjectNotFoundException(Exception): - - """Exception class when unable to determine project a source belongs to.""" - - def __init__(self, source, error=None): - Exception.__init__(self, source, error) - self.source = source - self.error = error - - def __repr__(self): - if self.error is not None: - return "Cannot ascertain project for source tree %s:\n%s" % ( - self.source, self.error) - else: - return "Cannot ascertain project for source tree %s" % ( - self.source) - - __str__ = __repr__ - - -class RoseStemVersionException(Exception): - - """Exception class when running the wrong metomi.rose-stem version.""" - - def __init__(self, version): - Exception.__init__(self, version) - if version is None: - self.suite_version = "not metomi.rose-stem compatible" - else: - self.suite_version = "at version %s" % (version) - - def __repr__(self): - return "Running metomi.rose-stem version %s but suite is %s" % ( - ROSE_STEM_VERSION, self.suite_version) - - __str__ = __repr__ - - -class RoseSuiteConfNotFoundException(Exception): - - """Exception class when unable to find metomi.rose-suite.conf.""" - - def __init__(self, location): - Exception.__init__(self, location) - self.location = location - - def __repr__(self): - if os.path.isdir(self.location): - return "\nCannot find a suite to run in directory %s" % ( - self.location) - else: - return "\nSuite directory %s is not a valid directory" % ( - self.location) - - __str__ = __repr__ - - -class SourceTreeAddedAsBranchEvent(Event): - - """Event to report a source tree has been added as a branch.""" - - LEVEL = Event.DEFAULT - - def __repr__(self): - return "Source tree %s added as branch" % (self.args[0]) - - __str__ = __repr__ - - -class SourceTreeAddedAsTrunkEvent(Event): - - """Event to report a source tree has been added as a trunk.""" - - LEVEL = Event.DEFAULT - - def __repr__(self): - return "Source tree %s added as trunk" % (self.args[0]) - - __str__ = __repr__ - - -class SuiteSelectionEvent(Event): - - """Event to report a source tree for config files.""" - - LEVEL = Event.DEFAULT - - def __repr__(self): - return "Will run suite from %s" % (self.args[0]) - - __str__ = __repr__ - - -class StemRunner(object): - - """Set up options for running a STEM job through Rose.""" - - def __init__(self, opts, reporter=None, popen=None, fs_util=None): - self.opts = opts - if reporter is None: - self.reporter = Reporter(opts.verbosity - opts.quietness) - else: - self.reporter = reporter - if popen is None: - self.popen = RosePopener(event_handler=self.reporter) - else: - self.popen = popen - if fs_util is None: - self.fs_util = FileSystemUtil(event_handler=self.reporter) - else: - self.fs_util = fs_util - self.host_selector = HostSelector(event_handler=self.reporter, - popen=self.popen) - - def _add_define_option(self, var, val): - """Add a define option passed to the SuiteRunner.""" - - if self.opts.defines: - self.opts.defines.append(SUITE_RC_PREFIX + var + '=' + val) - else: - self.opts.defines = [SUITE_RC_PREFIX + var + '=' + val] - self.reporter(ConfigVariableSetEvent(var, val)) - return - - def _get_base_dir(self, item): - """Given a source tree return the following from 'fcm loc-layout': - * url - * sub_tree - * peg_rev - * root - * project - """ - - ret_code, output, stderr = self.popen.run('fcm', 'loc-layout', item) - output = output.decode() - if ret_code != 0: - raise ProjectNotFoundException(item, stderr) - - ret = {} - for line in output.splitlines(): - if ":" not in line: - continue - key, value = line.split(":", 1) - if key: - if value: - ret[key] = value.strip() - - return ret - - def _get_project_from_url(self, source_dict): - """Run 'fcm keyword-print' to work out the project name.""" - - repo = source_dict['root'] - if source_dict['project']: - repo += '/' + source_dict['project'] - - kpoutput = self.popen.run('fcm', 'kp', source_dict['url'])[1] - - project = None - for line in kpoutput.splitlines(): - if line.rstrip().endswith(repo.encode('UTF-8')): - kpresult = re.search(r'^location{primary}\[(.*)\]', - line.decode()) - if kpresult: - project = kpresult.group(1) - break - return project - - def _deduce_mirror(self, source_dict, project): - """Deduce the mirror location of this source tree.""" - - # Root location for project - proj_root = source_dict['root'] + '/' + source_dict['project'] - - # Swap project to mirror - project = re.sub(r'\.x$', r'.xm', project) - mirror_repo = "fcm:" + project - - # Generate mirror location - mirror = re.sub(proj_root, mirror_repo, source_dict['url']) - - # Remove any sub-tree - mirror = re.sub(source_dict['sub_tree'], r'', mirror) - mirror = re.sub(r'/@', r'@', mirror) - - # Add forwards slash after .xm if missing - if '.xm/' not in mirror: - mirror = re.sub(r'\.xm', r'.xm/', mirror) - return mirror - - def _ascertain_project(self, item): - """Set the project name and top-level from 'fcm loc-layout'. - Returns: - * project name - * top-level location of the source tree with revision number - * top-level location of the source tree without revision number - * revision number - """ - - project = None - try: - project, item = item.split("=", 1) - except ValueError: - pass - - if re.search(r'^\.', item): - item = os.path.abspath(os.path.join(os.getcwd(), item)) - - if project is not None: - print("[WARN] Forcing project for '{0}' to be '{1}'".format( - item, project)) - return project, item, item, '', '' - - source_dict = self._get_base_dir(item) - project = self._get_project_from_url(source_dict) - if not project: - raise ProjectNotFoundException(item) - - mirror = self._deduce_mirror(source_dict, project) - - if 'peg_rev' in source_dict and '@' in item: - revision = '@' + source_dict['peg_rev'] - base = re.sub(r'@.*', r'', item) - else: - revision = '' - base = item - - # Remove subtree from base and item - if 'sub_tree' in source_dict: - item = re.sub( - r'(.*)%s/?$' % (source_dict['sub_tree']), r'\1', item, count=1) - base = re.sub( - r'(.*)%s/?$' % (source_dict['sub_tree']), r'\1', base, count=1) - - # Remove trailing forwards-slash - item = re.sub(r'/$', r'', item) - base = re.sub(r'/$', r'', base) - - # Remove anything after a point - project = re.sub(r'\..*', r'', project) - return project, item, base, revision, mirror - - def _generate_name(self): - """Generate a suite name from the name of the first source tree.""" - try: - basedir = self._ascertain_project(os.getcwd())[1] - except ProjectNotFoundException: - if self.opts.conf_dir: - basedir = os.path.abspath(self.opts.conf_dir) - else: - basedir = os.getcwd() - name = os.path.basename(basedir) - self.reporter(NameSetEvent(name)) - return name - - def _this_suite(self): - """Find the location of the suite in the first source tree.""" - - # Get base of first source - basedir = '' - if self.opts.source: - basedir = self.opts.source[0] - else: - basedir = self._ascertain_project(os.getcwd())[1] - - suitedir = os.path.join(basedir, DEFAULT_TEST_DIR) - suitefile = os.path.join(suitedir, "rose-suite.conf") - - if not os.path.isfile(suitefile): - raise RoseSuiteConfNotFoundException(suitedir) - - self._check_suite_version(suitefile) - - return suitedir - - def _read_auto_opts(self): - """Read the site metomi.rose.conf file.""" - return ResourceLocator.default().get_conf().get_value( - ["rose-stem", "automatic-options"]) - - def _check_suite_version(self, fname): - """Check the suite is compatible with this version of metomi.rose-stem. - """ - if not os.path.isfile(fname): - raise RoseSuiteConfNotFoundException(os.path.dirname(fname)) - config = metomi.rose.config.load(fname) - suite_rose_stem_version = config.get(['ROSE_STEM_VERSION']) - if suite_rose_stem_version: - suite_rose_stem_version = int(suite_rose_stem_version.value) - else: - suite_rose_stem_version = None - if not suite_rose_stem_version == ROSE_STEM_VERSION: - raise RoseStemVersionException(suite_rose_stem_version) - - def _prepend_localhost(self, url): - """Prepend the local hostname to urls which do not point to repository - locations.""" - if ':' not in url or url.split(':', 1)[0] not in ['svn', 'fcm', 'http', - 'https', 'svn+ssh']: - url = self.host_selector.get_local_host() + ':' + url - return url - - def process(self): - """Process STEM options into 'rose suite-run' options.""" - - # Generate options for source trees - repos = {} - repos_with_hosts = {} - if not self.opts.source: - self.opts.source = ['.'] - self.opts.project = list() - - for i, url in enumerate(self.opts.source): - project, url, base, rev, mirror = self._ascertain_project(url) - self.opts.source[i] = url - self.opts.project.append(project) - - # Versions of variables with hostname prepended for working copies - url_host = self._prepend_localhost(url) - base_host = self._prepend_localhost(base) - - if project in repos: - repos[project].append(url) - repos_with_hosts[project].append(url_host) - else: - repos[project] = [url] - repos_with_hosts[project] = [url_host] - self._add_define_option('SOURCE_' + project.upper() + '_REV', - '"' + rev + '"') - self._add_define_option('SOURCE_' + project.upper() + '_BASE', - '"' + base + '"') - self._add_define_option('HOST_SOURCE_' + project.upper() + - '_BASE', '"' + base_host + '"') - self._add_define_option('SOURCE_' + project.upper() + - '_MIRROR', '"' + mirror + '"') - self.reporter(SourceTreeAddedAsBranchEvent(url)) - for project, branches in repos.items(): - var = 'SOURCE_' + project.upper() - branchstring = RosePopener.list_to_shell_str(branches) - self._add_define_option(var, '"' + branchstring + '"') - for project, branches in repos_with_hosts.items(): - var_host = 'HOST_SOURCE_' + project.upper() - branchstring = RosePopener.list_to_shell_str(branches) - self._add_define_option(var_host, '"' + branchstring + '"') - - # Generate the variable containing tasks to run - if self.opts.group: - if not self.opts.defines: - self.opts.defines = [] - expanded_groups = [] - for i in self.opts.group: - expanded_groups.extend(i.split(',')) - self.opts.defines.append(SUITE_RC_PREFIX + 'RUN_NAMES=' + - str(expanded_groups)) - - # Load the config file and return any automatic-options - auto_opts = self._read_auto_opts() - if auto_opts: - automatic_options = auto_opts.split() - for option in automatic_options: - elements = option.split("=") - if len(elements) == 2: - self._add_define_option( - elements[0], '"' + elements[1] + '"') - - # Change into the suite directory - if self.opts.conf_dir: - self.reporter(SuiteSelectionEvent(self.opts.conf_dir)) - self._check_suite_version( - os.path.join(self.opts.conf_dir, 'rose-suite.conf')) - else: - thissuite = self._this_suite() - self.fs_util.chdir(thissuite) - self.reporter(SuiteSelectionEvent(thissuite)) - - # Create a default name for the suite; allow override by user - if not self.opts.name: - self.opts.name = self._generate_name() - - return self.opts - - -def main(): - """Launcher for command line invokation of metomi.rose stem.""" - - # Process options - opt_parser = RoseOptionParser() - - option_keys = SuiteRunner.OPTIONS + OPTIONS - opt_parser.add_my_options(*option_keys) - opts, args = opt_parser.parse_args() - - # Set up a runner instance and process the options - stem = StemRunner(opts) - if opts.debug_mode: - opts = stem.process() - else: - try: - opts = stem.process() - except Exception as exc: - stem.reporter(exc) - sys.exit(1) - - # Get the suiterunner object and execute - runner = SuiteRunner(event_handler=stem.reporter, - popen=stem.popen, - fs_util=stem.fs_util) - if opts.debug_mode: - sys.exit(runner(opts, args)) - try: - sys.exit(runner(opts, args)) - except Exception as exc: - runner.handle_event(exc) - if isinstance(exc, RosePopenError): - sys.exit(exc.ret_code) - else: - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/metomi/rose/suite_clean.py b/metomi/rose/suite_clean.py deleted file mode 100644 index 491895d495..0000000000 --- a/metomi/rose/suite_clean.py +++ /dev/null @@ -1,206 +0,0 @@ -# -*- coding: utf-8 -*- -# ---------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -# ---------------------------------------------------------------------------- -"""Implement the "rose suite-clean" command.""" - -import os -from pipes import quote -from metomi.rose.config import ConfigLoader, ConfigNode, ConfigSyntaxError -from metomi.rose.fs_util import FileSystemEvent -from metomi.rose.host_select import HostSelector -from metomi.rose.opt_parse import RoseOptionParser -from metomi.rose.popen import RosePopenError -from metomi.rose.reporter import Reporter -from metomi.rose.suite_engine_proc import ( - SuiteEngineProcessor, SuiteStillRunningError) -import sys -import traceback -from uuid import uuid4 -from functools import cmp_to_key - - -class SuiteRunCleaner(object): - - """Logic to remove items created by the previous runs of suites.""" - - CLEANABLE_PATHS = ["share", "share/cycle", "work"] - - def __init__(self, event_handler=None, host_selector=None, - suite_engine_proc=None): - if event_handler is None: - event_handler = Reporter() - self.event_handler = event_handler - if host_selector is None: - host_selector = HostSelector(event_handler=event_handler) - self.host_selector = host_selector - if suite_engine_proc is None: - suite_engine_proc = SuiteEngineProcessor.get_processor( - event_handler=event_handler) - self.suite_engine_proc = suite_engine_proc - - def clean(self, suite_name, only_items=None): - """Remove items created by the previous run of a suite. - - Change to user's $HOME for safety. - - """ - os.chdir(os.path.expanduser('~')) - self.suite_engine_proc.check_suite_not_running(suite_name) - self._clean(suite_name, only_items) - - def _clean(self, suite_name, only_items=None): - """Perform the cleaning operations.""" - engine = self.suite_engine_proc - suite_dir_rel = engine.get_suite_dir_rel(suite_name) - locs_file_path = engine.get_suite_dir( - suite_name, "log", "rose-suite-run.locs") - locs_conf = ConfigNode().set(["localhost"], {}) - try: - ConfigLoader().load(locs_file_path, locs_conf) - except IOError: - pass - items = self.CLEANABLE_PATHS + [""] - if only_items: - items = only_items - items.sort() - uuid_str = str(uuid4()) - for auth, node in sorted(locs_conf.value.items(), - key=cmp_to_key(self._auth_node_cmp)): - locs = [] - roots = set([""]) - for item in items: - if item: - locs.append(os.path.join(suite_dir_rel, item)) - else: - locs.append(suite_dir_rel) - if item and os.path.normpath(item) in self.CLEANABLE_PATHS: - item_root = node.get_value(["root-dir{" + item + "}"]) - if item_root is None: # backward compat - item_root = node.get_value(["root-dir-" + item]) - elif item == "": - item_root = node.get_value(["root-dir"]) - else: - continue - if item_root: - loc_rel = suite_dir_rel - if item: - loc_rel = os.path.join(suite_dir_rel, item) - locs.append(os.path.join(item_root, loc_rel)) - roots.add(item_root) - locs.reverse() - # Invoke bash as a login shell. The root location of a path may be - # in $DIR syntax, which can only be expanded correctly in a login - # shell. However, profile scripts invoked on login shell may print - # lots of junks. Hence we use a UUID here as a delimiter. Only - # output after the UUID lines are desirable lines. - command = ["bash", "-l", "-O", "extglob", "-c"] - sh_command = "cd; echo '%s'" % (uuid_str,) - if not self.host_selector.is_local_host(auth): - command = engine.popen.get_cmd("ssh", auth) + command - sh_command += "; ls -d -r %(locs)s; rm -fr %(locs)s" % { - "locs": engine.popen.list_to_shell_str(locs)} - if not only_items: - # Clean empty directories - # Change directory to root level to avoid cleaning them as - # well For cylc suites, e.g. it can clean up to an empty - # "cylc-run/" directory. - for root in roots: - names = [] - # Reverse sort to ensure that e.g. "share/cycle/" is - # cleaned before "share/" - for name in sorted(self.CLEANABLE_PATHS, reverse=True): - names.append(os.path.join(suite_dir_rel, name)) - if os.sep in suite_dir_rel: - names.append(os.path.dirname(suite_dir_rel)) - sh_command += ( - "; " + - "(cd %(root)s; " + - "rmdir -p %(names)s 2>/dev/null || true)" - ) % { - "root": root, - "names": engine.popen.list_to_shell_str(names), - } - if self.host_selector.is_local_host(auth): - command.append(sh_command) - else: - command.append(quote(sh_command)) - is_after_uuid_str = False - for line in engine.popen(*command)[0].splitlines(): - line = line.decode() - if is_after_uuid_str: - engine.handle_event(FileSystemEvent( - FileSystemEvent.DELETE, auth + ":" + line.strip())) - elif line == uuid_str: - is_after_uuid_str = True - - __call__ = clean - - @staticmethod - def _auth_node_cmp(item1, item2): - """Compare (auth1, node1) and (auth2, node2).""" - # This logic replicates output of the deprecated Python2 `cmp` builtin - ret = (item1 > item2) - (item1 < item2) - if ret: - if item1[0] == "localhost": - return -1 - elif item2[0] == "localhost": - return 1 - return ret - - -def main(): - """Implement the "rose suite-clean" command.""" - opt_parser = RoseOptionParser() - opt_parser.add_my_options("name", "non_interactive", "only_items") - opts, args = opt_parser.parse_args() - report = Reporter(opts.verbosity - opts.quietness) - cleaner = SuiteRunCleaner(event_handler=report) - if opts.name: - args.append(opts.name) - if not args: - args = [os.path.basename(os.getcwd())] - os.chdir(os.path.expanduser('~')) - n_done = 0 - for arg in args: - if not opts.non_interactive: - try: - answer = input("Clean %s? y/n (default n) " % arg) - except EOFError: - sys.exit(1) - if answer not in ["Y", "y"]: - continue - try: - cleaner.clean(arg, opts.only_items) - except ( - OSError, - IOError, - ConfigSyntaxError, - RosePopenError, - SuiteStillRunningError, - ) as exc: - report(exc) - if opts.debug_mode: - traceback.print_exc() - else: - n_done += 1 - sys.exit(len(args) - n_done) # Return 0 if everything done - - -if __name__ == "__main__": - main() diff --git a/metomi/rose/suite_control.py b/metomi/rose/suite_control.py deleted file mode 100644 index 7945fc2183..0000000000 --- a/metomi/rose/suite_control.py +++ /dev/null @@ -1,131 +0,0 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -# ----------------------------------------------------------------------------- -"""Launch suite engine's control commands.""" - -import os -from metomi.rose.fs_util import FileSystemUtil -from metomi.rose.opt_parse import RoseOptionParser -from metomi.rose.reporter import Reporter -from metomi.rose.suite_engine_proc import SuiteEngineProcessor -import sys - - -YES = "y" -PROMPT = "Really %s %s? [" + YES + " or n (default)] " - - -class SuiteControl(object): - """Launch suite engine's control commands from the correct suite host.""" - - def __init__(self, event_handler=None): - self.event_handler = event_handler - self.suite_engine_proc = SuiteEngineProcessor.get_processor( - event_handler=event_handler) - - def shutdown(self, suite_name, confirm=None, stderr=None, - stdout=None, *args): - """Shutdown the suite. - - suite_name: the name of the suite. - confirm: If specified, must be a callable with the interface - b = confirm("shutdown", suite_name, host). This method will - only issue the shutdown command to suite_name at host if b is - True. - stderr: A file handle for stderr, if relevant for suite engine. - stdout: A file handle for stdout, if relevant for suite engine. - args: extra arguments for the suite engine's shutdown command. - - """ - if confirm is None or confirm("shutdown", suite_name): - self.suite_engine_proc.shutdown(suite_name, args, stderr, stdout) - - -class SuiteNotFoundError(Exception): - - """An exception raised when a suite can't be found at or below cwd.""" - def __str__(self): - return ("%s - no suite found for this path." % self.args[0]) - - -def get_suite_name(event_handler=None): - """Find the top level of a suite directory structure""" - fs_util = FileSystemUtil(event_handler) - conf_dir = os.getcwd() - while True: - if os.path.basename(conf_dir) != "rose-stem": - for tail in [ - "rose-suite.conf", - "log/rose-suite-run.conf", - "rose-stem/rose-suite.conf"]: - conf = os.path.join(conf_dir, tail) - if os.path.exists(conf): - return os.path.basename(conf_dir) - up_dir = fs_util.dirname(conf_dir) - if up_dir == conf_dir: - raise SuiteNotFoundError(os.getcwd()) - conf_dir = up_dir - - -def prompt(action, suite_name, host): - """Prompt user to confirm action for suite_name at host.""" - if not host: - host = "localhost" - return input(PROMPT % (action, suite_name, host)).strip() in [YES] - - -def main(): - """Implement "rose suite-shutdown".""" - argv = sys.argv[1:] - method_name = argv.pop(0) - opt_parser = RoseOptionParser() - opt_parser.add_my_options("name", "non_interactive") - opts, args = opt_parser.parse_args(argv) - event_handler = Reporter(opts.verbosity - opts.quietness) - suite_control = SuiteControl(event_handler=event_handler) - method = getattr(suite_control, method_name) - confirm = None - suite_names = [] - if not opts.non_interactive: - confirm = prompt - else: - if opts.name: - suite_names.append(opts.name) - else: - try: - suite_name = get_suite_name(event_handler) - suite_names.append(suite_name) - except SuiteNotFoundError as exc: - event_handler(exc) - sys.exit(1) - - if opts.debug_mode: - for sname in suite_names: - method(sname, confirm, sys.stderr, sys.stdout, *args) - else: - for sname in suite_names: - try: - method(sname, confirm, sys.stderr, sys.stdout, *args) - except Exception as exc: - event_handler(exc) - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/metomi/rose/suite_log.py b/metomi/rose/suite_log.py index 150eeed738..29614cd105 100644 --- a/metomi/rose/suite_log.py +++ b/metomi/rose/suite_log.py @@ -24,7 +24,6 @@ from metomi.rose.opt_parse import RoseOptionParser from metomi.rose.reporter import Event, Reporter from metomi.rose.suite_engine_proc import SuiteEngineProcessor -from metomi.rose.suite_control import get_suite_name import sys from time import sleep import traceback @@ -57,6 +56,25 @@ def main(): sys.exit(1) +def get_suite_name(event_handler=None): + """Find the top level of a suite directory structure""" + fs_util = FileSystemUtil(event_handler) + conf_dir = os.getcwd() + while True: + if os.path.basename(conf_dir) != "rose-stem": + for tail in [ + "rose-suite.conf", + "log/rose-suite-run.conf", + "rose-stem/rose-suite.conf"]: + conf = os.path.join(conf_dir, tail) + if os.path.exists(conf): + return os.path.basename(conf_dir) + up_dir = fs_util.dirname(conf_dir) + if up_dir == conf_dir: + raise SuiteNotFoundError(os.getcwd()) + conf_dir = up_dir + + def suite_log_view(opts, args, event_handler=None): """Implement "rose suite-log" CLI functionality.""" suite_engine_proc = SuiteEngineProcessor.get_processor( diff --git a/metomi/rose/suite_restart.py b/metomi/rose/suite_restart.py deleted file mode 100644 index e5292721a0..0000000000 --- a/metomi/rose/suite_restart.py +++ /dev/null @@ -1,91 +0,0 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -# ----------------------------------------------------------------------------- -"""Implement "rose suite-restart-only".""" - -import os -import sys -import traceback - -from metomi.rose.opt_parse import RoseOptionParser -from metomi.rose.popen import RosePopenError -from metomi.rose.reporter import Reporter -from metomi.rose.suite_control import get_suite_name, SuiteNotFoundError -from metomi.rose.suite_engine_proc import SuiteEngineProcessor - - -class SuiteRestarter(object): - - """Wrap "cylc restart".""" - - def __init__(self, event_handler=None): - self.event_handler = event_handler - self.suite_engine_proc = SuiteEngineProcessor.get_processor( - event_handler=self.event_handler) - - def handle_event(self, *args, **kwargs): - """Handle event.""" - if callable(self.event_handler): - self.event_handler(*args, **kwargs) - - def restart(self, suite_name=None, host=None, args=None): - """Restart a "cylc" suite.""" - # Check suite engine specific compatibility - self.suite_engine_proc.check_global_conf_compat() - - if not suite_name: - suite_name = get_suite_name(self.event_handler) - - suite_dir = self.suite_engine_proc.get_suite_dir(suite_name) - if not os.path.exists(suite_dir): - raise SuiteNotFoundError(suite_dir) - - # Ensure suite is not running - self.suite_engine_proc.check_suite_not_running(suite_name) - - # Restart the suite - self.suite_engine_proc.run(suite_name, host, "restart", args) - - return - - -def main(): - """Launcher for the CLI.""" - opt_parser = RoseOptionParser() - opt_parser.add_my_options("host", "name") - opts, args = opt_parser.parse_args(sys.argv[1:]) - event_handler = Reporter(opts.verbosity - opts.quietness) - suite_restarter = SuiteRestarter(event_handler) - try: - sys.exit(suite_restarter.restart( - suite_name=opts.name, - host=opts.host, - args=args)) - except Exception as exc: - event_handler(exc) - if opts.debug_mode: - traceback.print_exc() - if isinstance(exc, RosePopenError): - sys.exit(exc.ret_code) - else: - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/metomi/rose/suite_run.py b/metomi/rose/suite_run.py deleted file mode 100644 index dff53bebaa..0000000000 --- a/metomi/rose/suite_run.py +++ /dev/null @@ -1,694 +0,0 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -# ----------------------------------------------------------------------------- -"""Implement "rose suite-run".""" - -import ast -from datetime import datetime -from fnmatch import fnmatchcase -from glob import glob -import os -import pipes -from metomi.rose.config import ConfigDumper, ConfigLoader, ConfigNode -from metomi.rose.env import env_var_process -from metomi.rose.host_select import HostSelector -from metomi.rose.opt_parse import RoseOptionParser -from metomi.rose.popen import RosePopenError -from metomi.rose.reporter import Event, Reporter, ReporterContext -from metomi.rose.resource import ResourceLocator -from metomi.rose.run import ConfigValueError, NewModeError, Runner -from metomi.rose.run_source_vc import write_source_vc_info -from metomi.rose.suite_clean import SuiteRunCleaner -import shutil -import sys -from tempfile import TemporaryFile, mkdtemp -from time import sleep, strftime, time -import traceback - - -class VersionMismatchError(Exception): - - """An exception raised when there is a version mismatch.""" - - def __str__(self): - return "Version expected=%s, actual=%s" % self.args - - -class SkipReloadEvent(Event): - - """An event raised to report that suite configuration reload is skipped.""" - - def __str__(self): - return "%s: reload complete. \"%s\" unchanged" % self.args - - -class SuiteLogArchiveEvent(Event): - - """An event raised to report the archiving of a suite log directory.""" - - def __str__(self): - return "%s <= %s" % self.args - - -class SuiteRunner(Runner): - - """Invoke a Rose suite.""" - - SLEEP_PIPE = 0.05 - NAME = "suite" - OPTIONS = [ - "conf_dir", - "defines", - "defines_suite", - "host", - "install_only_mode", - "local_install_only_mode", - "log_archive_mode", - "log_keep", - "log_name", - "name", - "new_mode", - "no_overwrite_mode", - "opt_conf_keys", - "reload_mode", - "remote", - "restart_mode", - "run_mode", - "strict_mode", - "validate_suite_only"] - - # Lists of rsync (always) exclude globs - SYNC_EXCLUDES = ( - "/.*", - "/cylc-suite.db", - "/log", - "/log.*", - "/state", - "/share", - "/work", - ) - - def __init__(self, *args, **kwargs): - Runner.__init__(self, *args, **kwargs) - self.host_selector = HostSelector(self.event_handler, self.popen) - self.suite_run_cleaner = SuiteRunCleaner( - event_handler=self.event_handler, - host_selector=self.host_selector, - suite_engine_proc=self.suite_engine_proc) - - def run_impl(self, opts, args, uuid, work_files): - # Log file, temporary - if hasattr(self.event_handler, "contexts"): - t_file = TemporaryFile() - log_context = ReporterContext(None, self.event_handler.VV, t_file) - self.event_handler.contexts[uuid] = log_context - - # Check suite engine specific compatibility - self.suite_engine_proc.check_global_conf_compat() - - # Suite name from the current working directory - if opts.conf_dir: - self.fs_util.chdir(opts.conf_dir) - opts.conf_dir = os.getcwd() - - # --remote=KEY=VALUE,... - if opts.remote: - # opts.name always set for remote. - return self._run_remote(opts, opts.name) - - conf_tree = self.config_load(opts) - self.fs_util.chdir(conf_tree.conf_dirs[0]) - - suite_name = opts.name - if not opts.name: - suite_name = os.path.basename(os.getcwd()) - - # Check suite.rc #! line for template scheme - templ_scheme = "jinja2" - if self.suite_engine_proc.SUITE_CONF in conf_tree.files: - suiterc_path = os.path.join( - conf_tree.files[self.suite_engine_proc.SUITE_CONF], - self.suite_engine_proc.SUITE_CONF) - with open(suiterc_path) as fh: - line = fh.readline() - if line.startswith("#!"): - templ_scheme = line[2:].strip().lower() - suite_section = (templ_scheme + ':' + - self.suite_engine_proc.SUITE_CONF) - - extra_defines = [] - if opts.defines_suite: - for define in opts.defines_suite: - extra_defines.append("[" + suite_section + "]" + define) - - # Automatic Rose constants - # ROSE_ORIG_HOST: originating host - # ROSE_VERSION: Rose version (not retained in run_mode=="reload") - # Suite engine version - my_rose_version = ResourceLocator.default().get_version() - suite_engine_key = self.suite_engine_proc.get_version_env_name() - if opts.run_mode in ["reload", "restart"]: - prev_config_path = self.suite_engine_proc.get_suite_dir( - suite_name, "log", "rose-suite-run.conf") - prev_config = ConfigLoader()(prev_config_path) - suite_engine_version = prev_config.get_value( - ["env", suite_engine_key]) - else: - suite_engine_version =\ - self.suite_engine_proc.get_version().decode() - resloc = ResourceLocator.default() - auto_items = [ - (suite_engine_key, suite_engine_version), - ("ROSE_ORIG_HOST", self.host_selector.get_local_host()), - ("ROSE_SITE", resloc.get_conf().get_value(['site'], '')), - ("ROSE_VERSION", resloc.get_version())] - for key, val in auto_items: - requested_value = conf_tree.node.get_value(["env", key]) - if requested_value: - if key == "ROSE_VERSION" and val != requested_value: - exc = VersionMismatchError(requested_value, val) - raise ConfigValueError(["env", key], requested_value, exc) - val = requested_value - else: - conf_tree.node.set(["env", key], val, - state=conf_tree.node.STATE_NORMAL) - extra_defines.append('[%s]%s="%s"' % (suite_section, key, val)) - - # Pass automatic Rose constants as suite defines - self.conf_tree_loader.node_loader.load(extra_defines, conf_tree.node) - - # See if suite is running or not - if opts.run_mode == "reload": - # Check suite is running - self.suite_engine_proc.get_suite_contact(suite_name) - else: - self.suite_engine_proc.check_suite_not_running(suite_name) - - # Install the suite to its run location - suite_dir_rel = self._suite_dir_rel(suite_name) - - # Unfortunately a large try/finally block to ensure a temporary folder - # created in validate only mode is cleaned up. Exceptions are not - # caught here - try: - # Process Environment Variables - environ = self.config_pm(conf_tree, "env") - - if opts.validate_suite_only_mode: - temp_dir = mkdtemp() - suite_dir = os.path.join(temp_dir, suite_dir_rel) - os.makedirs(suite_dir, 0o0700) - else: - suite_dir = os.path.join( - os.path.expanduser("~"), suite_dir_rel) - - suite_conf_dir = os.getcwd() - locs_conf = ConfigNode() - if opts.new_mode: - if os.getcwd() == suite_dir: - raise NewModeError("PWD", os.getcwd()) - elif opts.run_mode in ["reload", "restart"]: - raise NewModeError("--run", opts.run_mode) - self.suite_run_cleaner.clean(suite_name) - if os.getcwd() != suite_dir: - if opts.run_mode == "run": - self._run_init_dir(opts, suite_name, conf_tree, - locs_conf=locs_conf) - os.chdir(suite_dir) - - # Housekeep log files - now_str = None - if not opts.install_only_mode and not opts.local_install_only_mode: - now_str = datetime.utcnow().strftime("%Y%m%dT%H%M%SZ") - self._run_init_dir_log(opts, now_str) - self.fs_util.makedirs("log/suite") - - # Rose configuration and version logs - self.fs_util.makedirs("log/rose-conf") - run_mode = opts.run_mode - if run_mode not in ["reload", "restart", "run"]: - run_mode = "run" - mode = run_mode - if opts.validate_suite_only_mode: - mode = "validate-suite-only" - elif opts.install_only_mode: - mode = "install-only" - elif opts.local_install_only_mode: - mode = "local-install-only" - prefix = "rose-conf/%s-%s" % (strftime("%Y%m%dT%H%M%S"), mode) - - # Dump the actual configuration as rose-suite-run.conf - ConfigDumper()(conf_tree.node, "log/" + prefix + ".conf") - - # Install version information file - write_source_vc_info( - suite_conf_dir, "log/" + prefix + ".version", self.popen) - - # If run through rose-stem, install version information - # files for each source tree if they're a working copy - if hasattr(opts, 'source') and hasattr(opts, 'project'): - for i, url in enumerate(opts.source): - if os.path.isdir(url): - write_source_vc_info( - url, "log/" + opts.project[i] + "-" + str(i) + - ".version", self.popen) - - for ext in [".conf", ".version"]: - self.fs_util.symlink(prefix + ext, "log/rose-suite-run" + ext) - - # Move temporary log to permanent log - if hasattr(self.event_handler, "contexts"): - log_file_path = os.path.abspath( - os.path.join("log", "rose-suite-run.log")) - log_file = open(log_file_path, "ab") - temp_log_file = self.event_handler.contexts[uuid].handle - temp_log_file.seek(0) - log_file.write(temp_log_file.read()) - self.event_handler.contexts[uuid].handle = log_file - temp_log_file.close() - - # Process Files - cwd = os.getcwd() - for rel_path, conf_dir in conf_tree.files.items(): - if (conf_dir == cwd or - any(fnmatchcase(os.sep + rel_path, exclude) - for exclude in self.SYNC_EXCLUDES) or - conf_tree.node.get( - [templ_scheme + ":" + rel_path]) is not None): - continue - # No sub-directories, very slow otherwise - if os.sep in rel_path: - rel_path = rel_path.split(os.sep, 1)[0] - target_key = self.config_pm.get_handler( - "file").PREFIX + rel_path - target_node = conf_tree.node.get([target_key]) - if target_node is None: - conf_tree.node.set([target_key]) - target_node = conf_tree.node.get([target_key]) - elif target_node.is_ignored(): - continue - source_node = target_node.get("source") - if source_node is None: - target_node.set( - ["source"], os.path.join( - conf_dir, rel_path)) - elif source_node.is_ignored(): - continue - self.config_pm(conf_tree, "file", - no_overwrite_mode=opts.no_overwrite_mode) - - # Process suite configuration template header - # (e.g. Jinja2:suite.rc, EmPy:suite.rc) - self.config_pm(conf_tree, templ_scheme, environ=environ) - - # Ask suite engine to parse suite configuration - # and determine if it is up to date (unchanged) - if opts.validate_suite_only_mode: - suite_conf_unchanged = self.suite_engine_proc.cmp_suite_conf( - suite_dir, None, opts.strict_mode, - debug_mode=True) - else: - suite_conf_unchanged = self.suite_engine_proc.cmp_suite_conf( - suite_name, opts.run_mode, opts.strict_mode, - opts.debug_mode) - finally: - # Ensure the temporary directory created is cleaned up regardless - # of success or failure - if opts.validate_suite_only_mode and os.path.exists(temp_dir): - shutil.rmtree(temp_dir) - - # Only validating so finish now - if opts.validate_suite_only_mode: - return - - # Install share/work directories (local) - for name in ["share", "share/cycle", "work"]: - self._run_init_dir_work( - opts, suite_name, name, conf_tree, locs_conf=locs_conf) - - if opts.local_install_only_mode: - return - - # Install suite files to each remote [user@]host - for name in ["", "log/", "share/", "share/cycle/", "work/"]: - uuid_file = os.path.abspath(name + uuid) - open(uuid_file, "w").close() - work_files.append(uuid_file) - - # Install items to user@host - auths = self.suite_engine_proc.get_tasks_auths(suite_name) - proc_queue = [] # [[proc, command, "ssh"|"rsync", auth], ...] - for auth in sorted(auths): - host = auth - if "@" in auth: - host = auth.split("@", 1)[1] - # Remote shell - command = self.popen.get_cmd("ssh", "-n", auth) - # Provide ROSE_VERSION and CYLC_VERSION in the environment - shcommand = "env ROSE_VERSION=%s %s=%s" % ( - my_rose_version, suite_engine_key, suite_engine_version) - # Use login shell? - no_login_shell = self._run_conf( - "remote-no-login-shell", host=host, conf_tree=conf_tree) - if not no_login_shell or no_login_shell.lower() != "true": - shcommand += r""" bash -l -c '"$0" "$@"'""" - # Path to "rose" command, if applicable - rose_bin = self._run_conf( - "remote-rose-bin", host=host, conf_tree=conf_tree, - default="rose") - # Build remote "rose suite-run" command - shcommand += " %s suite-run -vv -n %s" % ( - rose_bin, suite_name) - for key in ["new", "debug", "install-only"]: - attr = key.replace("-", "_") + "_mode" - if getattr(opts, attr, None) is not None: - shcommand += " --%s" % key - if opts.log_keep: - shcommand += " --log-keep=%s" % opts.log_keep - if opts.log_name: - shcommand += " --log-name=%s" % opts.log_name - if not opts.log_archive_mode: - shcommand += " --no-log-archive" - shcommand += " --run=%s" % opts.run_mode - # Build --remote= option - shcommand += " --remote=uuid=%s" % uuid - if now_str is not None: - shcommand += ",now-str=%s" % now_str - host_confs = [ - "root-dir", - "root-dir{share}", - "root-dir{share/cycle}", - "root-dir{work}"] - locs_conf.set([auth]) - for key in host_confs: - value = self._run_conf(key, host=host, conf_tree=conf_tree) - if value is not None: - val = self.popen.list_to_shell_str([str(value)]) - shcommand += ",%s=%s" % (key, pipes.quote(val)) - locs_conf.set([auth, key], value) - command.append(shcommand) - proc = self.popen.run_bg(*command) - proc_queue.append([proc, command, "ssh", auth]) - - while proc_queue: - sleep(self.SLEEP_PIPE) - proc, command, command_name, auth = proc_queue.pop(0) - if proc.poll() is None: # put it back in proc_queue - proc_queue.append([proc, command, command_name, auth]) - continue - ret_code = proc.wait() - out, err = proc.communicate() - ret_code, out, err = [ - i.decode() if isinstance(i, bytes) else i for i in [ - ret_code, out, err]] - if ret_code: - raise RosePopenError(command, ret_code, out, err) - if command_name == "rsync": - self.handle_event(out, level=Event.VV) - continue - else: - self.handle_event(out, level=Event.VV, prefix="[%s] " % auth) - for line in out.split("\n"): - if "/" + uuid == line.strip(): - locs_conf.unset([auth]) - break - else: - filters = {"excludes": [], "includes": []} - for name in ["", "log/", "share/", "share/cycle/", "work/"]: - filters["excludes"].append(name + uuid) - target = auth + ":" + suite_dir_rel - cmd = self._get_cmd_rsync(target, **filters) - proc_queue.append( - [self.popen.run_bg(*cmd), cmd, "rsync", auth]) - - # Install ends - ConfigDumper()(locs_conf, os.path.join("log", "rose-suite-run.locs")) - if opts.install_only_mode: - return - elif opts.run_mode == "reload" and suite_conf_unchanged: - conf_name = self.suite_engine_proc.SUITE_CONF - self.handle_event(SkipReloadEvent(suite_name, conf_name)) - return - - # Start the suite - self.fs_util.chdir("log") - self.suite_engine_proc.run(suite_name, opts.host, opts.run_mode, args) - - # Disconnect log file handle, so monitoring tool command will no longer - # be associated with the log file. - self.event_handler.contexts[uuid].handle.close() - self.event_handler.contexts.pop(uuid) - - return 0 - - @classmethod - def _run_conf( - cls, key, default=None, host=None, conf_tree=None, r_opts=None): - """Return the value of a setting given by a key for a given host. If - r_opts is defined, we are already in a remote host, so there is no need - to do a host match. Otherwise, the setting may be found in the run time - configuration, or the default (i.e. site/user configuration). The value - of each setting in the configuration would be in a line delimited list - of PATTERN=VALUE pairs. - """ - if r_opts is not None: - return r_opts.get(key, default) - if host is None: - host = "localhost" - for conf, keys in [ - (conf_tree.node, []), - (ResourceLocator.default().get_conf(), ["rose-suite-run"])]: - if conf is None: - continue - node_value = conf.get_value(keys + [key]) - if node_value is None: - continue - for line in node_value.strip().splitlines(): - pattern, value = line.strip().split("=", 1) - if (pattern.startswith("jinja2:") or - pattern.startswith("empy:")): - section, name = pattern.rsplit(":", 1) - p_node = conf.get([section, name], no_ignore=True) - # Values in "jinja2:*" and "empy:*" sections are quoted. - pattern = ast.literal_eval(p_node.value) - if fnmatchcase(host, pattern): - return value.strip() - return default - - def _run_init_dir(self, opts, suite_name, conf_tree=None, r_opts=None, - locs_conf=None): - """Create the suite's directory.""" - suite_dir_rel = self._suite_dir_rel(suite_name) - home = os.path.expanduser("~") - suite_dir_root = self._run_conf("root-dir", conf_tree=conf_tree, - r_opts=r_opts) - if suite_dir_root: - if locs_conf is not None: - locs_conf.set(["localhost", "root-dir"], suite_dir_root) - suite_dir_root = env_var_process(suite_dir_root) - suite_dir_home = os.path.join(home, suite_dir_rel) - if (suite_dir_root and - os.path.realpath(home) != os.path.realpath(suite_dir_root)): - suite_dir_real = os.path.join(suite_dir_root, suite_dir_rel) - self.fs_util.makedirs(suite_dir_real) - self.fs_util.symlink(suite_dir_real, suite_dir_home, - opts.no_overwrite_mode) - else: - self.fs_util.makedirs(suite_dir_home) - - def _run_init_dir_log(self, opts, now_str=None): - """Create the suite's log/ directory. Housekeep, archive old ones.""" - # Do nothing in log append mode if log directory already exists - if opts.run_mode in ["reload", "restart"] and os.path.isdir("log"): - return - - # Log directory of this run - if now_str is None: - now_str = datetime.utcnow().strftime("%Y%m%dT%H%M%SZ") - now_log = "log." + now_str - self.fs_util.makedirs(now_log) - self.fs_util.symlink(now_log, "log") - now_log_name = getattr(opts, "log_name", None) - if now_log_name: - self.fs_util.symlink(now_log, "log." + now_log_name) - - # Keep log for this run and named logs - logs = set(glob("log.*") + ["log"]) - for log in list(logs): - if os.path.islink(log): - logs.remove(log) - log_link = os.readlink(log) - if log_link in logs: - logs.remove(log_link) - - # Housekeep old logs, if necessary - log_keep = getattr(opts, "log_keep", None) - if log_keep: - t_threshold = time() - abs(float(log_keep)) * 86400.0 - for log in list(logs): - if os.path.isfile(log): - if t_threshold > os.stat(log).st_mtime: - self.fs_util.delete(log) - logs.remove(log) - else: - for root, _, files in os.walk(log): - keep = False - for file_ in files: - path = os.path.join(root, file_) - if (os.path.exists(path) and - os.stat(path).st_mtime >= t_threshold): - keep = True - break - if keep: - break - else: - self.fs_util.delete(log) - logs.remove(log) - - # Archive old logs, if necessary - if getattr(opts, "log_archive_mode", True): - for log in list(logs): - if os.path.isfile(log): - continue - log_tar_gz = log + ".tar.gz" - try: - self.popen.run_simple("tar", "-czf", log_tar_gz, log) - except RosePopenError: - try: - self.fs_util.delete(log_tar_gz) - except OSError: - pass - raise - else: - self.handle_event(SuiteLogArchiveEvent(log_tar_gz, log)) - self.fs_util.delete(log) - - def _run_init_dir_work(self, opts, suite_name, name, conf_tree=None, - r_opts=None, locs_conf=None): - """Create a named suite's directory.""" - item_path = os.path.realpath(name) - item_path_source = item_path - key = "root-dir{" + name + "}" - item_root = self._run_conf(key, conf_tree=conf_tree, r_opts=r_opts) - if item_root is None: # backward compat - item_root = self._run_conf( - "root-dir-" + name, conf_tree=conf_tree, r_opts=r_opts) - if item_root: - if locs_conf is not None: - locs_conf.set(["localhost", key], item_root) - item_root = env_var_process(item_root) - suite_dir_rel = self._suite_dir_rel(suite_name) - if os.path.isabs(item_root): - item_path_source = os.path.join(item_root, suite_dir_rel, name) - else: - item_path_source = item_root - item_path_source = os.path.realpath(item_path_source) - if item_path == item_path_source: - if opts.new_mode: - self.fs_util.delete(name) - self.fs_util.makedirs(name) - else: - if opts.new_mode: - self.fs_util.delete(item_path_source) - self.fs_util.makedirs(item_path_source) - if os.sep in name: - dirname_of_name = os.path.dirname(name) - self.fs_util.makedirs(dirname_of_name) - item_path_source_rel = os.path.relpath( - item_path_source, os.path.realpath(dirname_of_name)) - else: - item_path_source_rel = os.path.relpath(item_path_source) - if len(item_path_source_rel) < len(item_path_source): - self.fs_util.symlink( - item_path_source_rel, name, opts.no_overwrite_mode) - else: - self.fs_util.symlink( - item_path_source, name, opts.no_overwrite_mode) - - def _run_remote(self, opts, suite_name): - """rose suite-run --remote=KEY=VALUE,...""" - suite_dir_rel = self._suite_dir_rel(suite_name) - r_opts = {} - for item in opts.remote.split(","): - key, val = item.split("=", 1) - r_opts[key] = val - uuid_file = os.path.join(suite_dir_rel, r_opts["uuid"]) - if os.path.exists(uuid_file): - self.handle_event("/" + r_opts["uuid"] + "\n", level=0) - elif opts.new_mode: - self.fs_util.delete(suite_dir_rel) - if opts.run_mode == "run" or not os.path.exists(suite_dir_rel): - self._run_init_dir(opts, suite_name, r_opts=r_opts) - os.chdir(suite_dir_rel) - for name in ["share", "share/cycle", "work"]: - uuid_file = os.path.join(name, r_opts["uuid"]) - if os.path.exists(uuid_file): - self.handle_event(name + "/" + r_opts["uuid"] + "\n", level=0) - else: - self._run_init_dir_work(opts, suite_name, name, r_opts=r_opts) - if not opts.install_only_mode: - uuid_file = os.path.join("log", r_opts["uuid"]) - if os.path.exists(uuid_file): - self.handle_event("log/" + r_opts["uuid"] + "\n", level=0) - else: - self._run_init_dir_log(opts, r_opts.get("now-str")) - self.fs_util.makedirs("log/suite") - - def _get_cmd_rsync(self, target, excludes=None, includes=None): - """rsync relevant suite items to target.""" - if excludes is None: - excludes = [] - if includes is None: - includes = [] - cmd = self.popen.get_cmd("rsync") - for exclude in excludes + list(self.SYNC_EXCLUDES): - cmd.append("--exclude=" + exclude) - for include in includes: - cmd.append("--include=" + include) - cmd.append("./") - cmd.append(target) - return cmd - - def _suite_dir_rel(self, suite_name): - """Return the relative path to the suite running directory.""" - return self.suite_engine_proc.get_suite_dir_rel(suite_name) - - -def main(): - """Launcher for the CLI.""" - opt_parser = RoseOptionParser() - option_keys = SuiteRunner.OPTIONS - opt_parser.add_my_options(*option_keys) - opts, args = opt_parser.parse_args(sys.argv[1:]) - event_handler = Reporter(opts.verbosity - opts.quietness) - runner = SuiteRunner(event_handler) - try: - sys.exit(runner(opts, args)) - except Exception as exc: - runner.handle_event(exc) - if opts.debug_mode: - traceback.print_exc() - if isinstance(exc, RosePopenError): - sys.exit(exc.ret_code) - else: - sys.exit(1) - - -if __name__ == "__main__": - main() From feff37cddd85850214600bae8f5528d53c4e6f04 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Mon, 23 Nov 2020 12:31:29 +0000 Subject: [PATCH 02/78] conf: remove obsolete configurations --- etc/rose.conf.example | 71 ------------------------------------------- 1 file changed, 71 deletions(-) diff --git a/etc/rose.conf.example b/etc/rose.conf.example index 3d0e279649..6c4cce4541 100644 --- a/etc/rose.conf.example +++ b/etc/rose.conf.example @@ -243,77 +243,6 @@ launcher-preopts.LAUNCHER=OPTIONS-TEMPLATE launcher-postopts.LAUNCHER=OPTIONS-TEMPLATE -# Configuration related to :ref:`command-rose-suite-run`. -[rose-suite-run] -# Hosts in the :rose:conf:`[rose-host-select]` section that can be used to -# run a suite e.g:: -# -# hosts=rose-vm -hosts=HOST-GROUP|HOST ... -# Hosts in the :rose:conf:`[rose-host-select]` section that can be scanned -# by rose utilities e.g:: -# -# scan-hosts=localhost rose-vm -scan-hosts=HOST-GROUP|HOST ... -# :default: false -# -# Don't use login shell to invoke ``rose suite-run --remote`` -# where ``HOST-GLOB`` is a glob for matching host names e.g:: -# -# remote-no-login-shell=myhpc*=true -# mycluster*=true -remote-no-login-shell=HOST-GLOB=true|false -# :default: rose -# -# Path to ``rose`` executable on remote hosts where: -# -# * ``HOST-GLOB`` is a glob for matching host names. -# * ``ROSE-BIN-PATH`` is the path to the ``rose`` executable. -# -# E.g:: -# -# remote-rose-bin=myhpc*=/opt/rose/bin/rose -# mycluster*=/usr/local/bin/rose -remote-rose-bin=HOST-GLOB=ROSE-BIN-PATH -# :default: $HOME -# -# Root location of a suite run directory where: -# -# * ``HOST-GLOB`` is a glob for matching host names. -# * ``HOST-DIR`` is the value of the root location for matching hosts. -# -# E.g:: -# -# root-dir=hpc*=$DATADIR -# =*=$HOME -root-dir=HOST-GLOB=$HOST-DIR -# :default: $HOME -# -# Root location of a suite run's ``share/`` directory. Syntax is the same -# as :rose:conf:`root-dir`. -# Multiple pairs can be specified by a new-line separated list. -# -# .. warning:: -# -# If a suite has previously been run changes to any of the ``root-dir`` -# settings will take effect on the next clean re-installation i.e: -# -# $ rose suite-run --new -root-dir{share}=HOST-GLOB=$HOST-DIR -# :default: $HOME -# -# Root location of a suite run's ``share/cycle/`` directory. Syntax is -# the same as :rose:conf:`root-dir`. -# Multiple pairs can be specified by a new-line separated list. -root-dir{share/cycle}=HOST-GLOB=$HOST-DIR -# :default: $HOME -# -# Root location of a suite run's ``work/`` directory. Syntax is the same -# as :rose:conf:`root-dir`. -# Multiple pairs can be specified by a new-line separated list. -root-dir{work}=HOST-GLOB=$HOST-DIR - - # Configuration related to :ref:`command-rose-task-run`. [rose-task-run] # Items to prepend to the ``PATH`` environment variable. From d64b9253f2459d38e4a5adef8a79d7d0d1e9da3e Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Mon, 23 Nov 2020 13:23:23 +0000 Subject: [PATCH 03/78] tests: remove tests for obsolete functionality --- metomi/rosie/suite_id.py | 27 +- t/rose-ana/00-run-basic.t | 4 +- t/rose-date/00-basic.t | 337 ------------------ t/rose-date/00-reference-time.t | 74 ---- t/rose-date/01-calendar.t | 114 ------ t/rose-date/test_header | 1 - t/rose-metadata-graph/00-null.t | 87 ----- t/rose-metadata-graph/01-data.t | 239 ------------- t/rose-metadata-graph/02-just-metadata.t | 98 ----- t/rose-metadata-graph/lib/rose-app.conf | 18 - t/rose-metadata-graph/lib/rose-meta.conf | 54 --- t/rose-metadata-graph/test_header | 48 --- t/rose-metadata-graph/test_header_extra | 58 --- t/rose-stem/00-run-basic.t | 250 ------------- t/rose-stem/00-run-basic/rose-suite.conf | 1 - t/rose-stem/00-run-basic/rose-suite2.conf | 1 - t/rose-stem/00-run-basic/suite.rc | 38 -- t/rose-stem/test_header | 1 - t/rose-suite-clean/00-normal | 1 - t/rose-suite-clean/00-normal.t | 90 ----- t/rose-suite-clean/01-running | 1 - t/rose-suite-clean/01-running.t | 100 ------ t/rose-suite-clean/02-only-1.t | 102 ------ t/rose-suite-clean/02-only-1/rose-suite.conf | 0 t/rose-suite-clean/02-only-1/suite.rc | 23 -- t/rose-suite-clean/03-only-2.t | 115 ------ t/rose-suite-clean/03-only-2/rose-suite.conf | 0 t/rose-suite-clean/03-only-2/suite.rc | 39 -- t/rose-suite-clean/04-only-3 | 1 - t/rose-suite-clean/04-only-3.t | 65 ---- t/rose-suite-clean/05-only-4 | 1 - t/rose-suite-clean/05-only-4.t | 62 ---- t/rose-suite-clean/06-only-5 | 1 - t/rose-suite-clean/06-only-5.t | 71 ---- t/rose-suite-clean/07-only-6 | 1 - t/rose-suite-clean/07-only-6.t | 69 ---- t/rose-suite-clean/08-rmdir.t | 79 ---- .../08-rmdir/app/t-1/rose-app.conf | 2 - t/rose-suite-clean/08-rmdir/rose-suite.conf | 2 - t/rose-suite-clean/08-rmdir/suite.rc | 30 -- t/rose-suite-clean/09-rmdir-2.t | 88 ----- .../09-rmdir-2/app/t-1/rose-app.conf | 2 - t/rose-suite-clean/09-rmdir-2/rose-suite.conf | 2 - t/rose-suite-clean/09-rmdir-2/suite.rc | 30 -- t/rose-suite-clean/10-only-7 | 1 - t/rose-suite-clean/10-only-7.t | 64 ---- t/rose-suite-clean/test_header | 1 - t/rose-suite-cmp-vc/00-basic.t | 47 --- t/rose-suite-cmp-vc/00-basic/rose-suite.conf | 0 t/rose-suite-cmp-vc/00-basic/suite.rc | 6 - t/rose-suite-cmp-vc/01-no-vc | 1 - t/rose-suite-cmp-vc/01-no-vc.t | 39 -- t/rose-suite-cmp-vc/test_header | 1 - t/rose-suite-log/00-update-task.t | 62 ---- .../00-update-task/rose-suite.conf | 0 t/rose-suite-log/00-update-task/suite.rc | 24 -- t/rose-suite-log/01-update-cycle.t | 92 ----- .../01-update-cycle/rose-suite.conf | 0 t/rose-suite-log/01-update-cycle/suite.rc | 27 -- t/rose-suite-log/02-update-force.t | 87 ----- .../02-update-force/rose-suite.conf | 0 t/rose-suite-log/02-update-force/suite.rc | 23 -- t/rose-suite-log/03-update-task-remote | 1 - t/rose-suite-log/03-update-task-remote.t | 1 - t/rose-suite-log/04-update-cycle-remote | 1 - t/rose-suite-log/04-update-cycle-remote.t | 1 - t/rose-suite-log/05-update-force-remote | 1 - t/rose-suite-log/05-update-force-remote.t | 1 - t/rose-suite-log/06-archive-star | 1 - t/rose-suite-log/06-archive-star.t | 97 ----- t/rose-suite-log/07-uninstalled.t | 34 -- t/rose-suite-log/test_header | 1 - t/rose-suite-restart/00-no-run.t | 54 --- t/rose-suite-restart/01-running.t | 74 ---- .../01-running/rose-suite.conf | 0 t/rose-suite-restart/01-running/suite.rc | 9 - t/rose-suite-restart/02-basic.t | 45 --- t/rose-suite-restart/02-basic/rose-suite.conf | 0 t/rose-suite-restart/02-basic/suite.rc | 8 - t/rose-suite-restart/03-host | 1 - t/rose-suite-restart/03-host.t | 55 --- t/rose-suite-restart/test_header | 1 - t/rose-suite-run/00-run-basic.t | 105 ------ t/rose-suite-run/00-run-basic/rose-suite.conf | 0 t/rose-suite-run/00-run-basic/suite.rc | 32 -- t/rose-suite-run/01-run-basic-conf | 1 - t/rose-suite-run/01-run-basic-conf.t | 1 - t/rose-suite-run/02-install.t | 108 ------ .../02-install/app/my_task_1/rose-app.conf | 2 - .../02-install/app/my_task_2/rose-app.conf | 2 - t/rose-suite-run/02-install/etc/junk | 0 t/rose-suite-run/02-install/rose-suite.conf | 0 t/rose-suite-run/02-install/suite.rc | 29 -- t/rose-suite-run/03-install-conf | 1 - t/rose-suite-run/03-install-conf.t | 1 - t/rose-suite-run/04-install-local | 1 - t/rose-suite-run/04-install-local.t | 1 - t/rose-suite-run/05-log.t | 86 ----- t/rose-suite-run/05-log/rose-suite.conf | 0 t/rose-suite-run/05-log/suite.rc | 16 - t/rose-suite-run/06-opt-conf.t | 70 ---- .../06-opt-conf/opt/rose-suite-earth.conf | 2 - .../opt/rose-suite-neutron-star.conf | 2 - t/rose-suite-run/06-opt-conf/rose-suite.conf | 2 - t/rose-suite-run/06-opt-conf/suite.rc | 13 - t/rose-suite-run/09-pgrep-conf | 1 - t/rose-suite-run/09-pgrep-conf.t | 1 - t/rose-suite-run/10-import-1.t | 69 ---- .../10-import-1/hello/app/hello/rose-app.conf | 2 - .../10-import-1/hello/bin/my-hello | 4 - .../10-import-1/hello/rose-suite.conf | 2 - t/rose-suite-run/10-import-1/hello/suite.rc | 28 -- .../10-import-1/hello_earth/rose-suite.conf | 4 - t/rose-suite-run/11-import-2.t | 69 ---- .../11-import-2/greet/rose-suite.conf | 4 - .../11-import-2/greet_earth/rose-suite.conf | 1 - t/rose-suite-run/12-run-dir-root.t | 97 ----- t/rose-suite-run/12-run-dir-root/suite.rc | 25 -- t/rose-suite-run/13-ignore-cylc-version.t | 46 --- .../13-ignore-cylc-version/rose-suite.conf | 2 - .../13-ignore-cylc-version/suite.rc | 12 - t/rose-suite-run/14-reload-null.t | 64 ---- .../app/t1/opt/rose-app-earth.conf | 2 - .../14-reload-null/app/t1/rose-app.conf | 9 - .../14-reload-null/rose-suite.conf | 2 - t/rose-suite-run/14-reload-null/suite.rc | 16 - t/rose-suite-run/15-reload-rc | 1 - t/rose-suite-run/15-reload-rc.t | 64 ---- t/rose-suite-run/16-suite-rc-not-writable.t | 38 -- .../16-suite-rc-not-writable/rose-suite.conf | 0 .../16-suite-rc-not-writable/suite.rc | 12 - t/rose-suite-run/17-install-overlap.t | 47 --- .../etc/foo/bar/egg/humpty.txt | 1 - .../17-install-overlap/rose-suite.conf | 2 - t/rose-suite-run/17-install-overlap/suite.rc | 12 - t/rose-suite-run/18-restart-init-dir.t | 47 --- .../18-restart-init-dir/rose-suite.conf | 0 t/rose-suite-run/18-restart-init-dir/suite.rc | 12 - t/rose-suite-run/19-restart-init-dir-remote.t | 83 ----- .../rose-suite.conf | 0 .../19-restart-init-dir-remote/suite.rc | 14 - t/rose-suite-run/21-sharecycle-back-compat.t | 54 --- .../app/t1/rose-app.conf | 10 - .../21-sharecycle-back-compat/rose-suite.conf | 0 .../21-sharecycle-back-compat/suite.rc | 15 - t/rose-suite-run/22-sharecycle.t | 54 --- .../22-sharecycle/app/t1/rose-app.conf | 10 - .../22-sharecycle/rose-suite.conf | 0 t/rose-suite-run/22-sharecycle/suite.rc | 15 - t/rose-suite-run/23-reload-host.t | 60 ---- .../23-reload-host/rose-suite.conf | 0 t/rose-suite-run/23-reload-host/suite.rc | 17 - t/rose-suite-run/24-host-log-timestamp.t | 63 ---- .../24-host-log-timestamp/rose-suite.conf | 0 .../24-host-log-timestamp/suite.rc | 14 - t/rose-suite-run/25-install-validate-only | 1 - t/rose-suite-run/25-install-validate-only.t | 1 - t/rose-suite-run/26-jinja2-insert.t | 94 ----- t/rose-suite-run/27-empy-insert.t | 99 ----- t/rose-suite-run/28-rose-site-env.t | 87 ----- t/rose-suite-run/29-install-host-localhost | 1 - t/rose-suite-run/29-install-host-localhost.t | 1 - t/rose-suite-run/test_header | 1 - t/rose-suite-shutdown/00-run-basic.t | 46 --- .../00-run-basic/rose-suite.conf | 0 t/rose-suite-shutdown/00-run-basic/suite.rc | 14 - t/rose-suite-shutdown/01-run-conf | 1 - t/rose-suite-shutdown/01-run-conf.t | 1 - t/rose-suite-shutdown/02-rose-stem-name | 1 - t/rose-suite-shutdown/02-rose-stem-name.t | 47 --- t/rose-suite-shutdown/03-normal-name | 1 - t/rose-suite-shutdown/03-normal-name.t | 43 --- t/rose-suite-shutdown/test_header | 1 - 173 files changed, 22 insertions(+), 5286 deletions(-) delete mode 100755 t/rose-date/00-basic.t delete mode 100755 t/rose-date/00-reference-time.t delete mode 100755 t/rose-date/01-calendar.t delete mode 120000 t/rose-date/test_header delete mode 100755 t/rose-metadata-graph/00-null.t delete mode 100755 t/rose-metadata-graph/01-data.t delete mode 100755 t/rose-metadata-graph/02-just-metadata.t delete mode 100644 t/rose-metadata-graph/lib/rose-app.conf delete mode 100644 t/rose-metadata-graph/lib/rose-meta.conf delete mode 100644 t/rose-metadata-graph/test_header delete mode 100644 t/rose-metadata-graph/test_header_extra delete mode 100755 t/rose-stem/00-run-basic.t delete mode 100644 t/rose-stem/00-run-basic/rose-suite.conf delete mode 100644 t/rose-stem/00-run-basic/rose-suite2.conf delete mode 100644 t/rose-stem/00-run-basic/suite.rc delete mode 120000 t/rose-stem/test_header delete mode 120000 t/rose-suite-clean/00-normal delete mode 100755 t/rose-suite-clean/00-normal.t delete mode 120000 t/rose-suite-clean/01-running delete mode 100755 t/rose-suite-clean/01-running.t delete mode 100755 t/rose-suite-clean/02-only-1.t delete mode 100644 t/rose-suite-clean/02-only-1/rose-suite.conf delete mode 100644 t/rose-suite-clean/02-only-1/suite.rc delete mode 100755 t/rose-suite-clean/03-only-2.t delete mode 100644 t/rose-suite-clean/03-only-2/rose-suite.conf delete mode 100644 t/rose-suite-clean/03-only-2/suite.rc delete mode 120000 t/rose-suite-clean/04-only-3 delete mode 100755 t/rose-suite-clean/04-only-3.t delete mode 120000 t/rose-suite-clean/05-only-4 delete mode 100755 t/rose-suite-clean/05-only-4.t delete mode 120000 t/rose-suite-clean/06-only-5 delete mode 100755 t/rose-suite-clean/06-only-5.t delete mode 120000 t/rose-suite-clean/07-only-6 delete mode 100755 t/rose-suite-clean/07-only-6.t delete mode 100755 t/rose-suite-clean/08-rmdir.t delete mode 100644 t/rose-suite-clean/08-rmdir/app/t-1/rose-app.conf delete mode 100644 t/rose-suite-clean/08-rmdir/rose-suite.conf delete mode 100644 t/rose-suite-clean/08-rmdir/suite.rc delete mode 100755 t/rose-suite-clean/09-rmdir-2.t delete mode 100644 t/rose-suite-clean/09-rmdir-2/app/t-1/rose-app.conf delete mode 100644 t/rose-suite-clean/09-rmdir-2/rose-suite.conf delete mode 100644 t/rose-suite-clean/09-rmdir-2/suite.rc delete mode 120000 t/rose-suite-clean/10-only-7 delete mode 100755 t/rose-suite-clean/10-only-7.t delete mode 120000 t/rose-suite-clean/test_header delete mode 100755 t/rose-suite-cmp-vc/00-basic.t delete mode 100644 t/rose-suite-cmp-vc/00-basic/rose-suite.conf delete mode 100644 t/rose-suite-cmp-vc/00-basic/suite.rc delete mode 120000 t/rose-suite-cmp-vc/01-no-vc delete mode 100755 t/rose-suite-cmp-vc/01-no-vc.t delete mode 120000 t/rose-suite-cmp-vc/test_header delete mode 100755 t/rose-suite-log/00-update-task.t delete mode 100644 t/rose-suite-log/00-update-task/rose-suite.conf delete mode 100644 t/rose-suite-log/00-update-task/suite.rc delete mode 100755 t/rose-suite-log/01-update-cycle.t delete mode 100644 t/rose-suite-log/01-update-cycle/rose-suite.conf delete mode 100644 t/rose-suite-log/01-update-cycle/suite.rc delete mode 100755 t/rose-suite-log/02-update-force.t delete mode 100644 t/rose-suite-log/02-update-force/rose-suite.conf delete mode 100644 t/rose-suite-log/02-update-force/suite.rc delete mode 120000 t/rose-suite-log/03-update-task-remote delete mode 120000 t/rose-suite-log/03-update-task-remote.t delete mode 120000 t/rose-suite-log/04-update-cycle-remote delete mode 120000 t/rose-suite-log/04-update-cycle-remote.t delete mode 120000 t/rose-suite-log/05-update-force-remote delete mode 120000 t/rose-suite-log/05-update-force-remote.t delete mode 120000 t/rose-suite-log/06-archive-star delete mode 100644 t/rose-suite-log/06-archive-star.t delete mode 100755 t/rose-suite-log/07-uninstalled.t delete mode 120000 t/rose-suite-log/test_header delete mode 100755 t/rose-suite-restart/00-no-run.t delete mode 100755 t/rose-suite-restart/01-running.t delete mode 100644 t/rose-suite-restart/01-running/rose-suite.conf delete mode 100644 t/rose-suite-restart/01-running/suite.rc delete mode 100755 t/rose-suite-restart/02-basic.t delete mode 100644 t/rose-suite-restart/02-basic/rose-suite.conf delete mode 100644 t/rose-suite-restart/02-basic/suite.rc delete mode 120000 t/rose-suite-restart/03-host delete mode 100755 t/rose-suite-restart/03-host.t delete mode 120000 t/rose-suite-restart/test_header delete mode 100755 t/rose-suite-run/00-run-basic.t delete mode 100644 t/rose-suite-run/00-run-basic/rose-suite.conf delete mode 100644 t/rose-suite-run/00-run-basic/suite.rc delete mode 120000 t/rose-suite-run/01-run-basic-conf delete mode 120000 t/rose-suite-run/01-run-basic-conf.t delete mode 100755 t/rose-suite-run/02-install.t delete mode 100644 t/rose-suite-run/02-install/app/my_task_1/rose-app.conf delete mode 100644 t/rose-suite-run/02-install/app/my_task_2/rose-app.conf delete mode 100644 t/rose-suite-run/02-install/etc/junk delete mode 100644 t/rose-suite-run/02-install/rose-suite.conf delete mode 100644 t/rose-suite-run/02-install/suite.rc delete mode 120000 t/rose-suite-run/03-install-conf delete mode 120000 t/rose-suite-run/03-install-conf.t delete mode 120000 t/rose-suite-run/04-install-local delete mode 120000 t/rose-suite-run/04-install-local.t delete mode 100755 t/rose-suite-run/05-log.t delete mode 100644 t/rose-suite-run/05-log/rose-suite.conf delete mode 100644 t/rose-suite-run/05-log/suite.rc delete mode 100755 t/rose-suite-run/06-opt-conf.t delete mode 100644 t/rose-suite-run/06-opt-conf/opt/rose-suite-earth.conf delete mode 100644 t/rose-suite-run/06-opt-conf/opt/rose-suite-neutron-star.conf delete mode 100644 t/rose-suite-run/06-opt-conf/rose-suite.conf delete mode 100644 t/rose-suite-run/06-opt-conf/suite.rc delete mode 120000 t/rose-suite-run/09-pgrep-conf delete mode 120000 t/rose-suite-run/09-pgrep-conf.t delete mode 100755 t/rose-suite-run/10-import-1.t delete mode 100644 t/rose-suite-run/10-import-1/hello/app/hello/rose-app.conf delete mode 100755 t/rose-suite-run/10-import-1/hello/bin/my-hello delete mode 100644 t/rose-suite-run/10-import-1/hello/rose-suite.conf delete mode 100644 t/rose-suite-run/10-import-1/hello/suite.rc delete mode 100644 t/rose-suite-run/10-import-1/hello_earth/rose-suite.conf delete mode 100755 t/rose-suite-run/11-import-2.t delete mode 100644 t/rose-suite-run/11-import-2/greet/rose-suite.conf delete mode 100644 t/rose-suite-run/11-import-2/greet_earth/rose-suite.conf delete mode 100755 t/rose-suite-run/12-run-dir-root.t delete mode 100644 t/rose-suite-run/12-run-dir-root/suite.rc delete mode 100755 t/rose-suite-run/13-ignore-cylc-version.t delete mode 100644 t/rose-suite-run/13-ignore-cylc-version/rose-suite.conf delete mode 100644 t/rose-suite-run/13-ignore-cylc-version/suite.rc delete mode 100755 t/rose-suite-run/14-reload-null.t delete mode 100644 t/rose-suite-run/14-reload-null/app/t1/opt/rose-app-earth.conf delete mode 100644 t/rose-suite-run/14-reload-null/app/t1/rose-app.conf delete mode 100644 t/rose-suite-run/14-reload-null/rose-suite.conf delete mode 100644 t/rose-suite-run/14-reload-null/suite.rc delete mode 120000 t/rose-suite-run/15-reload-rc delete mode 100755 t/rose-suite-run/15-reload-rc.t delete mode 100755 t/rose-suite-run/16-suite-rc-not-writable.t delete mode 100644 t/rose-suite-run/16-suite-rc-not-writable/rose-suite.conf delete mode 100644 t/rose-suite-run/16-suite-rc-not-writable/suite.rc delete mode 100755 t/rose-suite-run/17-install-overlap.t delete mode 100644 t/rose-suite-run/17-install-overlap/etc/foo/bar/egg/humpty.txt delete mode 100644 t/rose-suite-run/17-install-overlap/rose-suite.conf delete mode 100644 t/rose-suite-run/17-install-overlap/suite.rc delete mode 100755 t/rose-suite-run/18-restart-init-dir.t delete mode 100644 t/rose-suite-run/18-restart-init-dir/rose-suite.conf delete mode 100644 t/rose-suite-run/18-restart-init-dir/suite.rc delete mode 100755 t/rose-suite-run/19-restart-init-dir-remote.t delete mode 100644 t/rose-suite-run/19-restart-init-dir-remote/rose-suite.conf delete mode 100644 t/rose-suite-run/19-restart-init-dir-remote/suite.rc delete mode 100755 t/rose-suite-run/21-sharecycle-back-compat.t delete mode 100644 t/rose-suite-run/21-sharecycle-back-compat/app/t1/rose-app.conf delete mode 100644 t/rose-suite-run/21-sharecycle-back-compat/rose-suite.conf delete mode 100644 t/rose-suite-run/21-sharecycle-back-compat/suite.rc delete mode 100755 t/rose-suite-run/22-sharecycle.t delete mode 100644 t/rose-suite-run/22-sharecycle/app/t1/rose-app.conf delete mode 100644 t/rose-suite-run/22-sharecycle/rose-suite.conf delete mode 100644 t/rose-suite-run/22-sharecycle/suite.rc delete mode 100755 t/rose-suite-run/23-reload-host.t delete mode 100644 t/rose-suite-run/23-reload-host/rose-suite.conf delete mode 100644 t/rose-suite-run/23-reload-host/suite.rc delete mode 100755 t/rose-suite-run/24-host-log-timestamp.t delete mode 100644 t/rose-suite-run/24-host-log-timestamp/rose-suite.conf delete mode 100644 t/rose-suite-run/24-host-log-timestamp/suite.rc delete mode 120000 t/rose-suite-run/25-install-validate-only delete mode 120000 t/rose-suite-run/25-install-validate-only.t delete mode 100755 t/rose-suite-run/26-jinja2-insert.t delete mode 100755 t/rose-suite-run/27-empy-insert.t delete mode 100755 t/rose-suite-run/28-rose-site-env.t delete mode 120000 t/rose-suite-run/29-install-host-localhost delete mode 120000 t/rose-suite-run/29-install-host-localhost.t delete mode 120000 t/rose-suite-run/test_header delete mode 100755 t/rose-suite-shutdown/00-run-basic.t delete mode 100644 t/rose-suite-shutdown/00-run-basic/rose-suite.conf delete mode 100644 t/rose-suite-shutdown/00-run-basic/suite.rc delete mode 120000 t/rose-suite-shutdown/01-run-conf delete mode 120000 t/rose-suite-shutdown/01-run-conf.t delete mode 120000 t/rose-suite-shutdown/02-rose-stem-name delete mode 100755 t/rose-suite-shutdown/02-rose-stem-name.t delete mode 120000 t/rose-suite-shutdown/03-normal-name delete mode 100755 t/rose-suite-shutdown/03-normal-name.t delete mode 120000 t/rose-suite-shutdown/test_header diff --git a/metomi/rosie/suite_id.py b/metomi/rosie/suite_id.py index c406b0e245..754ed0d1d8 100644 --- a/metomi/rosie/suite_id.py +++ b/metomi/rosie/suite_id.py @@ -29,7 +29,14 @@ """ import os +from pathlib import Path import re +import shlex +import string +import sys +import traceback +import xml.parsers.expat + import metomi.rose.env from metomi.rose.loc_handlers.svn import SvnInfoXMLParser from metomi.rose.opt_parse import RoseOptionParser @@ -37,11 +44,6 @@ from metomi.rose.reporter import Reporter from metomi.rose.resource import ResourceLocator from metomi.rose.suite_engine_proc import SuiteEngineProcessor, NoSuiteLogError -import shlex -import string -import sys -import traceback -import xml.parsers.expat class SvnCaller(RosePopener): @@ -365,9 +367,20 @@ def _from_id_text(self, id_text): def _from_location(self, location): """Return the ID of a location (origin URL or local copy path).""" - # Is location a "~/cylc-run/$SUITE/" directory? - # Use a hacky way to read the "log/rose-suite-run.version" file suite_engine_proc = SuiteEngineProcessor.get_processor() + suite_dir_rel_root = getattr( + suite_engine_proc, "SUITE_DIR_REL_ROOT", None) + + # Cylc8 run directory + loc = Path(location) + sdrr = Path(suite_dir_rel_root) + if loc.relative_to(sdrr): + if (loc / 'rose-suite.info').exists(): + # TODO + raise SuiteIdLocationError(location) + + # Cylc7 run directory + # Use a hacky way to read the "log/rose-suite-run.version" file suite_dir_rel_root = getattr( suite_engine_proc, "SUITE_DIR_REL_ROOT", None) if suite_dir_rel_root and "/" + suite_dir_rel_root + "/" in location: diff --git a/t/rose-ana/00-run-basic.t b/t/rose-ana/00-run-basic.t index dadd37c4b0..862ce047bf 100644 --- a/t/rose-ana/00-run-basic.t +++ b/t/rose-ana/00-run-basic.t @@ -40,8 +40,8 @@ TEST_KEY=$TEST_KEY_BASE mkdir -p $HOME/cylc-run SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') NAME=$(basename $SUITE_RUN_DIR) -run_fail "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ +# run_fail "$TEST_KEY" \ + rose-suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ --host=localhost -- --no-detach --debug #------------------------------------------------------------------------------- # Test the output diff --git a/t/rose-date/00-basic.t b/t/rose-date/00-basic.t deleted file mode 100755 index 325de41ba7..0000000000 --- a/t/rose-date/00-basic.t +++ /dev/null @@ -1,337 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose date". -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header -#------------------------------------------------------------------------------- -tests 104 -#------------------------------------------------------------------------------- -# Produce the correct format for the current date/time. -TEST_KEY=$TEST_KEY_BASE-current-format -run_pass "$TEST_KEY" rose date -file_cmp "$TEST_KEY.err" "$TEST_KEY.err" = $T_START )) && (( $T_TEST <= $T_END )); then - pass "$TEST_KEY.out" -else - fail "$TEST_KEY.out" -fi -#------------------------------------------------------------------------------- -# Parse its own current date/time output. -TEST_KEY=$TEST_KEY_BASE-current-parse -run_pass "$TEST_KEY" rose date -file_cmp "$TEST_KEY.err" "$TEST_KEY.err" 3540 )) && (( T_OFFSET - T_START < 3660 )); then - pass "$TEST_KEY.out" -else - fail "$TEST_KEY.out" -fi -#------------------------------------------------------------------------------- -# Produce a print format from the current date/time. -TEST_KEY=$TEST_KEY_BASE-current-print-format -run_pass "$TEST_KEY" rose date --print-format="%D %R" now -file_cmp "$TEST_KEY.err" "$TEST_KEY.err" $TEST_KEY.out.truncated -echo "" >> $TEST_KEY.out.truncated -file_cmp "$TEST_KEY.out.truncated" "$TEST_KEY.out.truncated" <<'__OUT__' -0.01666666 -__OUT__ -#------------------------------------------------------------------------------- -# Test rose date --as-total=h P832DT23H12M45S -TEST_KEY=$TEST_KEY_BASE-as-total-P832DT23H12M45S-h -run_pass "$TEST_KEY" rose date --as-total=h P832DT23H12M45S -file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__OUT__' -19991.2125 -__OUT__ -#------------------------------------------------------------------------------- -# Test rose date --as-total=FORMAT fails for invalid format -TEST_KEY=$TEST_KEY_BASE-as-total-invalid-format -run_fail "$TEST_KEY" rose date --as-total=y PT1M -#------------------------------------------------------------------------------- -# Test rose date --as-total=FORMAT for negative durations -TEST_KEY=$TEST_KEY_BASE-as-total-negative -run_pass "$TEST_KEY" rose date --as-total=S \\-PT1M1S -file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__OUT__' --61.0 -__OUT__ -#------------------------------------------------------------------------------- -# Test rose date --as-total=FORMAT for use case 2 -TEST_KEY=$TEST_KEY_BASE-as-total-between-dates -run_pass "$TEST_KEY" rose date 2000-01-01T00:00:00 2000-01-01T01:00:00 --as-total=s -file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__OUT__' -3600.0 -__OUT__ -#------------------------------------------------------------------------------- -# Test rose date --as-total=FORMAT for use case 2 with an offset -if python3 -c "import argparse; argparse.ArgumentParser.parse_intermixed_args"; then - TEST_KEY=$TEST_KEY_BASE-as-total-between-dates-with-offset - run_pass "$TEST_KEY" rose date 2000-01-01T00:00:00 --offset=PT1H 2000-01-01T01:00:00 --as-total=s -else - run_pass "$TEST_KEY" rose date 2000-01-01T00:00:00 2000-01-01T01:00:00 --as-total=s --offset=PT1H -fi -file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__OUT__' -0.0 -__OUT__ -#------------------------------------------------------------------------------- -exit 0 diff --git a/t/rose-date/00-reference-time.t b/t/rose-date/00-reference-time.t deleted file mode 100755 index 9f3791b170..0000000000 --- a/t/rose-date/00-reference-time.t +++ /dev/null @@ -1,74 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose date". -# Test logic triggered by `if "-c" in sys.args`. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header -#------------------------------------------------------------------------------- -tests 15 -#------------------------------------------------------------------------------- -# Test -c option where ROSE_TASK_CYCLE_TIME is set -TEST_KEY=$TEST_KEY_BASE-ROSE_TASK_CYCLE_TIME-set -ROSE_TASK_CYCLE_TIME=20121225T0000Z \ - run_pass "$TEST_KEY" rose date -c -file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__OUT__' -20121225T0000Z -__OUT__ -file_cmp "$TEST_KEY.err" "$TEST_KEY.err" . -#------------------------------------------------------------------------------- -# Test "rose date calendar swapping". -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header -#------------------------------------------------------------------------------- -tests 28 -#------------------------------------------------------------------------------- -# Check correct setting of 360 day calendar via environment. -TEST_KEY=$TEST_KEY_BASE-360-env -ROSE_CYCLING_MODE=360day \ - run_pass "$TEST_KEY-back" rose date 20130301 --offset=-P1D -file_cmp "$TEST_KEY-back.out" "$TEST_KEY-back.out" <<__OUT__ -20130230 -__OUT__ -ROSE_CYCLING_MODE=360day \ - run_pass "$TEST_KEY-fwd" rose date 20130230 --offset=P1D -file_cmp "$TEST_KEY-fwd.out" "$TEST_KEY-fwd.out" <<__OUT__ -20130301 -__OUT__ -#------------------------------------------------------------------------------- -# Check that ROSE_CYCLING_MODE over-rides ISODATETIMECALENDAR -TEST_KEY=$TEST_KEY_BASE-360-env -ROSE_CYCLING_MODE=360day \ - ISODATETIMECALENDAR=366day\ - run_pass "$TEST_KEY-back" rose date 20130301 --offset=-P1D -file_cmp "$TEST_KEY-back.out" "$TEST_KEY-back.out" <<__OUT__ -20130230 -__OUT__ -ROSE_CYCLING_MODE=360day \ - ISODATETIMECALENDAR=366day\ - run_pass "$TEST_KEY-fwd" rose date 20130230 --offset=P1D -file_cmp "$TEST_KEY-fwd.out" "$TEST_KEY-fwd.out" <<__OUT__ -20130301 -__OUT__ -#------------------------------------------------------------------------------- -# Check that ROSE_CYCLING_MODE over-rides ISODATETIMECALENDAR -TEST_KEY=$TEST_KEY_BASE-360-env -ISODATETIMECALENDAR=360day\ - run_pass "$TEST_KEY-back" rose date 20130301 --offset=-P1D -file_cmp "$TEST_KEY-back.out" "$TEST_KEY-back.out" <<__OUT__ -20130230 -__OUT__ -ROSE_CYCLING_MODE=360day \ - ISODATETIMECALENDAR=366day\ - run_pass "$TEST_KEY-fwd" rose date 20130230 --offset=P1D -file_cmp "$TEST_KEY-fwd.out" "$TEST_KEY-fwd.out" <<__OUT__ -20130301 -__OUT__ -#------------------------------------------------------------------------------- -# Check correct setting of 360 day calendar via args. -TEST_KEY=$TEST_KEY_BASE-360-switch -run_pass "$TEST_KEY-back" rose date 20130301 --offset=-P1D --calendar=360day -file_cmp "$TEST_KEY-back.out" "$TEST_KEY-back.out" <<__OUT__ -20130230 -__OUT__ -run_pass "$TEST_KEY-fwd" rose date 20130230 --offset=P1D --calendar=360day -file_cmp "$TEST_KEY-fwd.out" "$TEST_KEY-fwd.out" <<__OUT__ -20130301 -__OUT__ -#------------------------------------------------------------------------------- -# Check correct setting of 365 day calendar via args. -TEST_KEY=$TEST_KEY_BASE-365-switch -run_pass "$TEST_KEY-back" rose date 20130301 --offset=-P1D --calendar=365day -file_cmp "$TEST_KEY-back.out" "$TEST_KEY-back.out" <<__OUT__ -20130228 -__OUT__ -run_pass "$TEST_KEY-fwd" rose date 20130228 --offset=P1D --calendar=365day -file_cmp "$TEST_KEY-fwd.out" "$TEST_KEY-fwd.out" <<__OUT__ -20130301 -__OUT__ -#------------------------------------------------------------------------------- -# Check correct setting of 366 day calendar via args. -TEST_KEY=$TEST_KEY_BASE-366-switch -run_pass "$TEST_KEY-back" rose date 20130301 --offset=-P1D --calendar=366day -file_cmp "$TEST_KEY-back.out" "$TEST_KEY-back.out" <<__OUT__ -20130229 -__OUT__ -run_pass "$TEST_KEY-fwd" rose date 20130229 --offset=P1D --calendar=366day -file_cmp "$TEST_KEY-fwd.out" "$TEST_KEY-fwd.out" <<__OUT__ -20130301 -__OUT__ -#------------------------------------------------------------------------------- -# Check args correctly override environment. -TEST_KEY=$TEST_KEY_BASE-gregorian-override -ROSE_CYCLING_MODE=gregorian \ - run_pass "$TEST_KEY-back" rose date 20130301 --offset=-P1D --calendar=360day -file_cmp "$TEST_KEY-back.out" "$TEST_KEY-back.out" <<__OUT__ -20130230 -__OUT__ -ROSE_CYCLING_MODE=gregorian \ - run_pass "$TEST_KEY-fwd" rose date 20130230 --offset=P1D --calendar=360day -file_cmp "$TEST_KEY-fwd.out" "$TEST_KEY-fwd.out" <<__OUT__ -20130301 -__OUT__ -#------------------------------------------------------------------------------- -exit 0 diff --git a/t/rose-date/test_header b/t/rose-date/test_header deleted file mode 120000 index 90bd5a36f9..0000000000 --- a/t/rose-date/test_header +++ /dev/null @@ -1 +0,0 @@ -../lib/bash/test_header \ No newline at end of file diff --git a/t/rose-metadata-graph/00-null.t b/t/rose-metadata-graph/00-null.t deleted file mode 100755 index 577a0e52ee..0000000000 --- a/t/rose-metadata-graph/00-null.t +++ /dev/null @@ -1,87 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose metadata-graph" in the absence of a rose configuration. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - -python3 -c "import pygraphviz" 2>/dev/null || \ - skip_all '"pygraphviz" not installed' - -init . -#------------------------------------------------------------------------------- -# Test "rose metadata-graph" against configuration and configuration metadata. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header_extra -. $(dirname $0)/test_header - -python3 -c "import pygraphviz" 2>/dev/null || \ - skip_all '"pygraphviz" not installed' -#------------------------------------------------------------------------------- -tests 12 -#------------------------------------------------------------------------------- -# Check full graphing of our example configuration & configuration metadata. -TEST_KEY=$TEST_KEY_BASE-ok-full -setup -init < $TEST_SOURCE_DIR/lib/rose-app.conf -init_meta < $TEST_SOURCE_DIR/lib/rose-meta.conf -CONFIG_PATH=$(cd ../config && pwd -P) -run_pass "$TEST_KEY" rose metadata-graph --debug --config=../config -filter_graphviz <"$TEST_KEY.out" >"$TEST_KEY.filtered.out" -sort "$TEST_KEY.filtered.out" >"$TEST_KEY.filtered.out.sorted" -sort >"$TEST_KEY.filtered.out.expected" <<__OUTPUT__ -"env=CONTROL" -> "env=CONTROL=None" [color=green, label=foo -"env=CONTROL" -> "env=CONTROL=bar" [color=red, label=foo -"env=CONTROL" -> "env=CONTROL=baz" [color=red, label=foo -"env=CONTROL" -> "env=CONTROL=foo" [color=green, label=foo -"env=CONTROL" [color=green -"env=CONTROL=None" -> "env=DEPENDENT3" [color=green -"env=CONTROL=None" [color=green, label=None, shape=box, style=filled -"env=CONTROL=bar" -> "env=DEPENDENT1" [color=red -"env=CONTROL=bar" -> "env=DEPENDENT2" [color=red -"env=CONTROL=bar" -> "env=DEPENDENT_MISSING1" [arrowhead=empty, color=red -"env=CONTROL=bar" [color=red, label=bar, shape=box, style=filled -"env=CONTROL=baz" -> "env=DEPENDENT1" [color=red -"env=CONTROL=baz" [color=red, label=baz, shape=box, style=filled -"env=CONTROL=foo" -> "env=DEPENDENT_MISSING1" [color=green -"env=CONTROL=foo" [color=green, label=foo, shape=box, style=filled -"env=CONTROL_NAMELIST_QUX" -> "env=CONTROL_NAMELIST_QUX=bar" [color=red, label=foo -"env=CONTROL_NAMELIST_QUX" [color=green -"env=CONTROL_NAMELIST_QUX=bar" -> "namelist:qux" [color=red -"env=CONTROL_NAMELIST_QUX=bar" [color=red, label=bar, shape=box, style=filled -"env=CONTROL_NAMELIST_WIBBLE" -> "env=CONTROL_NAMELIST_WIBBLE=bar" [color=green, label=bar -"env=CONTROL_NAMELIST_WIBBLE" [color=green -"env=CONTROL_NAMELIST_WIBBLE=bar" -> "namelist:wibble" [color=green -"env=CONTROL_NAMELIST_WIBBLE=bar" [color=green, label=bar, shape=box, style=filled -"env=CONTROL_NAMELIST_WIBBLE_WUBBLE" -> "env=CONTROL_NAMELIST_WIBBLE_WUBBLE=bar" [color=red, label=foo -"env=CONTROL_NAMELIST_WIBBLE_WUBBLE" [color=green -"env=CONTROL_NAMELIST_WIBBLE_WUBBLE=bar" -> "namelist:wibble=wubble" [color=red -"env=CONTROL_NAMELIST_WIBBLE_WUBBLE=bar" [color=red, label=bar, shape=box, style=filled -"env=DEPENDENT1" [color=red, label="!!env=DEPENDENT1" -"env=DEPENDENT2" [color=red, label="!!env=DEPENDENT2" -"env=DEPENDENT3" [color=green -"env=DEPENDENT_DEPENDENT1" [color=red, label="!!env=DEPENDENT_DEPENDENT1" -"env=DEPENDENT_MISSING1" -> "env=DEPENDENT_MISSING1=None" [color=grey -"env=DEPENDENT_MISSING1" [color=grey -"env=DEPENDENT_MISSING1=None" -> "env=DEPENDENT_DEPENDENT1" [color=grey -"env=DEPENDENT_MISSING1=None" [color=grey, label=None, shape=box, style=filled -"env=MISSING_VARIABLE" [color=grey -"env=USER_IGNORED" [color=orange, label="!env=USER_IGNORED" -"namelist:qux" [color=red, label="!!namelist:qux", shape=octagon -"namelist:qux=wobble" -> "namelist:qux=wobble=.true." [color=red, label=".false." -"namelist:qux=wobble" [color=red, label="^namelist:qux=wobble" -"namelist:qux=wobble=.true." -> "namelist:qux=wubble" [color=red -"namelist:qux=wobble=.true." [color=red, label=".true.", shape=box, style=filled -"namelist:qux=wubble" [color=red, label="^!!namelist:qux=wubble" -"namelist:wibble" [color=green, shape=octagon -"namelist:wibble=wobble" -> "namelist:wibble=wobble=.true." [color=green, label=".true." -"namelist:wibble=wobble" [color=green -"namelist:wibble=wobble=.true." -> "namelist:wibble=wubble" [color=green -"namelist:wibble=wobble=.true." [color=green, label=".true.", shape=box, style=filled -"namelist:wibble=wubble" [color=red, label="!!namelist:wibble=wubble" -env [color=green, shape=octagon -graph [label="$CONFIG_PATH", rankdir=LR -node [label="\N" -__OUTPUT__ -file_cmp "$TEST_KEY.out" \ - "$TEST_KEY.filtered.out.sorted" "$TEST_KEY.filtered.out.expected" -file_cmp "$TEST_KEY.err" "$TEST_KEY.err" "$TEST_KEY.filtered.out" -sort "$TEST_KEY.filtered.out" >"$TEST_KEY.filtered.out.sorted" -sort >"$TEST_KEY.filtered.out.expected" <<__OUTPUT__ -"env=CONTROL_NAMELIST_QUX" -> "env=CONTROL_NAMELIST_QUX=bar" [color=red, label=foo -"env=CONTROL_NAMELIST_QUX" [color=green, shape=rectangle -"env=CONTROL_NAMELIST_QUX=bar" -> "namelist:qux" [color=red -"env=CONTROL_NAMELIST_QUX=bar" [color=red, label=bar, shape=box, style=filled -"namelist:qux" [color=red, label="!!namelist:qux", shape=octagon -"namelist:qux=wobble" -> "namelist:qux=wobble=.true." [color=red, label=".false." -"namelist:qux=wobble" [color=red, label="^namelist:qux=wobble" -"namelist:qux=wobble=.true." -> "namelist:qux=wubble" [color=red -"namelist:qux=wobble=.true." [color=red, label=".true.", shape=box, style=filled -"namelist:qux=wubble" [color=red, label="^!!namelist:qux=wubble" -graph [label="$CONFIG_PATH: namelist:qux", rankdir=LR -node [label="\N" -__OUTPUT__ -file_cmp "$TEST_KEY.out" \ - "$TEST_KEY.filtered.out.sorted" "$TEST_KEY.filtered.out.expected" -file_cmp "$TEST_KEY.err" "$TEST_KEY.err" "$TEST_KEY.filtered.out" -sort "$TEST_KEY.filtered.out" >"$TEST_KEY.filtered.out.sorted" -sort >"$TEST_KEY.filtered.out.expected" <<__OUTPUT__ -"env=CONTROL" -> "env=CONTROL=None" [color=green, label=foo -"env=CONTROL" -> "env=CONTROL=bar" [color=red, label=foo -"env=CONTROL" -> "env=CONTROL=baz" [color=red, label=foo -"env=CONTROL" -> "env=CONTROL=foo" [color=green, label=foo -"env=CONTROL" [color=green -"env=CONTROL=None" -> "env=DEPENDENT3" [color=green -"env=CONTROL=None" [color=green, label=None, shape=box, style=filled -"env=CONTROL=bar" -> "env=DEPENDENT1" [color=red -"env=CONTROL=bar" -> "env=DEPENDENT2" [color=red -"env=CONTROL=bar" -> "env=DEPENDENT_MISSING1" [arrowhead=empty, color=red -"env=CONTROL=bar" [color=red, label=bar, shape=box, style=filled -"env=CONTROL=baz" -> "env=DEPENDENT1" [color=red -"env=CONTROL=baz" [color=red, label=baz, shape=box, style=filled -"env=CONTROL=foo" -> "env=DEPENDENT_MISSING1" [color=green -"env=CONTROL=foo" [color=green, label=foo, shape=box, style=filled -"env=CONTROL_NAMELIST_QUX" -> "env=CONTROL_NAMELIST_QUX=bar" [color=red, label=foo -"env=CONTROL_NAMELIST_QUX" [color=green -"env=CONTROL_NAMELIST_QUX=bar" -> "namelist:qux" [color=red -"env=CONTROL_NAMELIST_QUX=bar" [color=red, label=bar, shape=box, style=filled -"env=CONTROL_NAMELIST_WIBBLE" -> "env=CONTROL_NAMELIST_WIBBLE=bar" [color=green, label=bar -"env=CONTROL_NAMELIST_WIBBLE" [color=green -"env=CONTROL_NAMELIST_WIBBLE=bar" -> "namelist:wibble" [color=green -"env=CONTROL_NAMELIST_WIBBLE=bar" [color=green, label=bar, shape=box, style=filled -"env=CONTROL_NAMELIST_WIBBLE_WUBBLE" -> "env=CONTROL_NAMELIST_WIBBLE_WUBBLE=bar" [color=red, label=foo -"env=CONTROL_NAMELIST_WIBBLE_WUBBLE" [color=green -"env=CONTROL_NAMELIST_WIBBLE_WUBBLE=bar" -> "namelist:wibble=wubble" [color=red -"env=CONTROL_NAMELIST_WIBBLE_WUBBLE=bar" [color=red, label=bar, shape=box, style=filled -"env=DEPENDENT1" [color=red, label="!!env=DEPENDENT1" -"env=DEPENDENT2" [color=red, label="!!env=DEPENDENT2" -"env=DEPENDENT3" [color=green -"env=DEPENDENT_DEPENDENT1" [color=red, label="!!env=DEPENDENT_DEPENDENT1" -"env=DEPENDENT_MISSING1" -> "env=DEPENDENT_MISSING1=None" [color=grey -"env=DEPENDENT_MISSING1" [color=grey -"env=DEPENDENT_MISSING1=None" -> "env=DEPENDENT_DEPENDENT1" [color=grey -"env=DEPENDENT_MISSING1=None" [color=grey, label=None, shape=box, style=filled -"env=MISSING_VARIABLE" [color=grey -"env=USER_IGNORED" [color=orange, label="!env=USER_IGNORED" -"namelist:qux" [color=red, label="!!namelist:qux", shape=octagon -"namelist:qux=wobble" -> "namelist:qux=wobble=.true." [color=red, label=".false." -"namelist:qux=wobble" [color=red, label="^namelist:qux=wobble" -"namelist:qux=wobble=.true." -> "namelist:qux=wubble" [color=red -"namelist:qux=wobble=.true." [color=red, label=".true.", shape=box, style=filled -"namelist:qux=wubble" [color=red, label="^!!namelist:qux=wubble" -"namelist:wibble" [color=green, shape=octagon -"namelist:wibble=wobble" -> "namelist:wibble=wobble=.true." [color=green, label=".true." -"namelist:wibble=wobble" [color=green -"namelist:wibble=wobble=.true." -> "namelist:wibble=wubble" [color=green -"namelist:wibble=wobble=.true." [color=green, label=".true.", shape=box, style=filled -"namelist:wibble=wubble" [color=red, label="!!namelist:wibble=wubble" -env [color=green, shape=octagon -graph [label="$CONFIG_PATH (trigger)", rankdir=LR -node [label="\N" -__OUTPUT__ -file_cmp "$TEST_KEY.out" \ - "$TEST_KEY.filtered.out.sorted" "$TEST_KEY.filtered.out.expected" -file_cmp "$TEST_KEY.err" "$TEST_KEY.err" "$TEST_KEY.filtered.out" -sort "$TEST_KEY.filtered.out" >"$TEST_KEY.filtered.out.sorted" -sort >"$TEST_KEY.filtered.out.expected" <<__OUTPUT__ -"env=CONTROL" -> "env=CONTROL=None" [color=green, label=foo -"env=CONTROL" -> "env=CONTROL=bar" [color=red, label=foo -"env=CONTROL" -> "env=CONTROL=baz" [color=red, label=foo -"env=CONTROL" -> "env=CONTROL=foo" [color=green, label=foo -"env=CONTROL" [color=green -"env=CONTROL=None" -> "env=DEPENDENT3" [color=green -"env=CONTROL=None" [color=green, label=None, shape=box, style=filled -"env=CONTROL=bar" -> "env=DEPENDENT1" [color=red -"env=CONTROL=bar" -> "env=DEPENDENT2" [color=red -"env=CONTROL=bar" -> "env=DEPENDENT_MISSING1" [arrowhead=empty, color=red -"env=CONTROL=bar" [color=red, label=bar, shape=box, style=filled -"env=CONTROL=baz" -> "env=DEPENDENT1" [color=red -"env=CONTROL=baz" [color=red, label=baz, shape=box, style=filled -"env=CONTROL=foo" -> "env=DEPENDENT_MISSING1" [color=green -"env=CONTROL=foo" [color=green, label=foo, shape=box, style=filled -"env=CONTROL_NAMELIST_QUX" -> "env=CONTROL_NAMELIST_QUX=bar" [color=red, label=foo -"env=CONTROL_NAMELIST_QUX" [color=green -"env=CONTROL_NAMELIST_QUX=bar" -> "namelist:qux" [color=red -"env=CONTROL_NAMELIST_QUX=bar" [color=red, label=bar, shape=box, style=filled -"env=CONTROL_NAMELIST_WIBBLE" -> "env=CONTROL_NAMELIST_WIBBLE=bar" [color=green, label=bar -"env=CONTROL_NAMELIST_WIBBLE" [color=green -"env=CONTROL_NAMELIST_WIBBLE=bar" -> "namelist:wibble" [color=green -"env=CONTROL_NAMELIST_WIBBLE=bar" [color=green, label=bar, shape=box, style=filled -"env=CONTROL_NAMELIST_WIBBLE_WUBBLE" -> "env=CONTROL_NAMELIST_WIBBLE_WUBBLE=bar" [color=red, label=foo -"env=CONTROL_NAMELIST_WIBBLE_WUBBLE" [color=green -"env=CONTROL_NAMELIST_WIBBLE_WUBBLE=bar" -> "namelist:wibble=wubble" [color=red -"env=CONTROL_NAMELIST_WIBBLE_WUBBLE=bar" [color=red, label=bar, shape=box, style=filled -"env=DEPENDENT1" [color=red, label="!!env=DEPENDENT1" -"env=DEPENDENT2" [color=red, label="!!env=DEPENDENT2" -"env=DEPENDENT3" [color=green -"env=DEPENDENT_DEPENDENT1" [color=red, label="!!env=DEPENDENT_DEPENDENT1" -"env=DEPENDENT_MISSING1" -> "env=DEPENDENT_MISSING1=None" [color=grey -"env=DEPENDENT_MISSING1" [color=grey -"env=DEPENDENT_MISSING1=None" -> "env=DEPENDENT_DEPENDENT1" [color=grey -"env=DEPENDENT_MISSING1=None" [color=grey, label=None, shape=box, style=filled -"env=MISSING_VARIABLE" [color=grey -"env=USER_IGNORED" [color=orange, label="!env=USER_IGNORED" -"namelist:qux" [color=red, label="!!namelist:qux", shape=rectangle -"namelist:wibble" [color=green, shape=rectangle -"namelist:wibble=wubble" [color=red, label="!!namelist:wibble=wubble", shape=rectangle -env [color=green, shape=octagon -graph [label="$CONFIG_PATH: env (trigger)", rankdir=LR -node [label="\N" -__OUTPUT__ -file_cmp "$TEST_KEY.out" \ - "$TEST_KEY.filtered.out.sorted" "$TEST_KEY.filtered.out.expected" -file_cmp "$TEST_KEY.err" "$TEST_KEY.err" . -#------------------------------------------------------------------------------- -# Test "rose metadata-graph" against just configuration metadata. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header_extra -. $(dirname $0)/test_header - -python3 -c "import pygraphviz" 2>/dev/null || \ - skip_all '"pygraphviz" not installed' - -#------------------------------------------------------------------------------- -tests 3 -#------------------------------------------------------------------------------- -# Check full graphing. -TEST_KEY=$TEST_KEY_BASE-ok-full -setup -init < $TEST_SOURCE_DIR/lib/rose-app.conf -init_meta < $TEST_SOURCE_DIR/lib/rose-meta.conf -META_CONFIG_PATH=$(cd ../config/meta && pwd -P) -run_pass "$TEST_KEY" rose metadata-graph --debug --config=../config/meta -filter_graphviz <"$TEST_KEY.out" >"$TEST_KEY.filtered.out" -sort "$TEST_KEY.filtered.out" >"$TEST_KEY.filtered.out.sorted" -sort >"$TEST_KEY.filtered.out.expected" <<__OUTPUT__ -"env=CONTROL" -> "env=CONTROL=None" [color=grey -"env=CONTROL" -> "env=CONTROL=bar" [color=grey -"env=CONTROL" -> "env=CONTROL=baz" [color=grey -"env=CONTROL" -> "env=CONTROL=foo" [color=grey -"env=CONTROL" [ -"env=CONTROL=None" -> "env=DEPENDENT3" [color=grey -"env=CONTROL=None" [color=grey, label=None, shape=box, style=filled -"env=CONTROL=bar" -> "env=DEPENDENT1" [color=grey -"env=CONTROL=bar" -> "env=DEPENDENT2" [color=grey -"env=CONTROL=bar" -> "env=DEPENDENT_MISSING1" [color=grey -"env=CONTROL=bar" [color=grey, label=bar, shape=box, style=filled -"env=CONTROL=baz" -> "env=DEPENDENT1" [color=grey -"env=CONTROL=baz" [color=grey, label=baz, shape=box, style=filled -"env=CONTROL=foo" -> "env=DEPENDENT_MISSING1" [color=grey -"env=CONTROL=foo" [color=grey, label=foo, shape=box, style=filled -"env=CONTROL_NAMELIST_QUX" -> "env=CONTROL_NAMELIST_QUX=bar" [color=grey -"env=CONTROL_NAMELIST_QUX" [ -"env=CONTROL_NAMELIST_QUX=bar" -> "namelist:qux" [color=grey -"env=CONTROL_NAMELIST_QUX=bar" [color=grey, label=bar, shape=box, style=filled -"env=CONTROL_NAMELIST_WIBBLE" -> "env=CONTROL_NAMELIST_WIBBLE=bar" [color=grey -"env=CONTROL_NAMELIST_WIBBLE" [ -"env=CONTROL_NAMELIST_WIBBLE=bar" -> "namelist:wibble" [color=grey -"env=CONTROL_NAMELIST_WIBBLE=bar" [color=grey, label=bar, shape=box, style=filled -"env=CONTROL_NAMELIST_WIBBLE_WUBBLE" -> "env=CONTROL_NAMELIST_WIBBLE_WUBBLE=bar" [color=grey -"env=CONTROL_NAMELIST_WIBBLE_WUBBLE" [ -"env=CONTROL_NAMELIST_WIBBLE_WUBBLE=bar" -> "namelist:wibble=wubble" [color=grey -"env=CONTROL_NAMELIST_WIBBLE_WUBBLE=bar" [color=grey, label=bar, shape=box, style=filled -"env=DEPENDENT1" [ -"env=DEPENDENT2" [ -"env=DEPENDENT3" [ -"env=DEPENDENT_DEPENDENT1" [ -"env=DEPENDENT_MISSING1" -> "env=DEPENDENT_MISSING1=None" [color=grey -"env=DEPENDENT_MISSING1" [ -"env=DEPENDENT_MISSING1=None" -> "env=DEPENDENT_DEPENDENT1" [color=grey -"env=DEPENDENT_MISSING1=None" [color=grey, label=None, shape=box, style=filled -"env=MISSING_VARIABLE" [ -"env=USER_IGNORED" [ -"namelist:qux" [shape=octagon -"namelist:qux=wobble" -> "namelist:qux=wobble=.true." [color=grey -"namelist:qux=wobble" [ -"namelist:qux=wobble=.true." -> "namelist:qux=wubble" [color=grey -"namelist:qux=wobble=.true." [color=grey, label=".true.", shape=box, style=filled -"namelist:qux=wubble" [ -"namelist:wibble" [shape=octagon -"namelist:wibble=wobble" -> "namelist:wibble=wobble=.true." [color=grey -"namelist:wibble=wobble" [ -"namelist:wibble=wobble=.true." -> "namelist:wibble=wubble" [color=grey -"namelist:wibble=wobble=.true." [color=grey, label=".true.", shape=box, style=filled -"namelist:wibble=wubble" [ -env [shape=octagon -graph [label="$META_CONFIG_PATH", rankdir=LR -node [label="\N" -__OUTPUT__ -file_cmp "$TEST_KEY.out" \ - "$TEST_KEY.filtered.out.sorted" "$TEST_KEY.filtered.out.expected" -file_cmp "$TEST_KEY.err" "$TEST_KEY.err" . -#------------------------------------------------------------------------------- -# Provides init, setup and teardown functions for rose macro tests. -#------------------------------------------------------------------------------- -. $(dirname $0)/../lib/bash/test_header - -function init() { - mkdir -p $TEST_DIR/config - cat >$TEST_DIR/config/rose-app.conf -} - -function init_meta() { - mkdir -p $TEST_DIR/config/meta - cat >$TEST_DIR/config/meta/rose-meta.conf -} - -function init_macro() { - mkdir -p $TEST_DIR/config/meta/lib/python/macros/ - cat >$TEST_DIR/config/meta/lib/python/macros/$1 -} - -function setup() { - mkdir $TEST_DIR/run - cd $TEST_DIR/run -} - -function teardown() { - cd $TEST_DIR - rm -rf $TEST_DIR/run - rm -rf $TEST_DIR/config/meta -} diff --git a/t/rose-metadata-graph/test_header_extra b/t/rose-metadata-graph/test_header_extra deleted file mode 100644 index 52820e516d..0000000000 --- a/t/rose-metadata-graph/test_header_extra +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Provides extra functions for rose metadata-graph testing. -#------------------------------------------------------------------------------- - -function filter_graphviz() { - FILTER_TMP_FILE=$(mktemp) - cat >"$FILTER_TMP_FILE" - # Sort and filter out non-essential properties from the output file. - # Get rid of line-broken newer graphviz output (replace ",\n"). - # Sort the file. - python3 << __PYTHON__ -import re -filename = '$FILTER_TMP_FILE' -f = open(filename, 'r') -text = f.read() -f.close() -text = text.replace(",\n", ", ") -text = re.sub("\s+\[", " [", text) -lines = text.splitlines() -f = open(filename, 'w') -for line in lines: - if '[' not in line: - if line.startswith("\t") and line.strip() != "];": - f.write(line.lstrip() + '\n') - continue - props = dict([_.strip().split('=', 1) for _ in - re.split(', ', - line.split('[', 1)[1].replace('];', ''))]) - new_prop_string = '' - for key in ['arrowhead', 'color', 'label', 'rankdir', 'shape', 'style']: - if key in props: - new_prop_string += key + '=' + props[key] + ', ' - new_prop_string = new_prop_string.rstrip().rstrip(',') - new_line = line.lstrip().split('[')[0] + '[' + new_prop_string + '\n' - if new_line.strip() != 'graph [': - f.write(new_line) -__PYTHON__ - LANG=C sort "$FILTER_TMP_FILE" - rm "$FILTER_TMP_FILE" -} diff --git a/t/rose-stem/00-run-basic.t b/t/rose-stem/00-run-basic.t deleted file mode 100755 index a2427349d1..0000000000 --- a/t/rose-stem/00-run-basic.t +++ /dev/null @@ -1,250 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose stem" without site/user configuration -export ROSE_CONF_PATH= -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header -#------------------------------------------------------------------------------- -if ! fcm --version 1>/dev/null 2>&1; then - skip_all '"FCM" not installed' -fi -#------------------------------------------------------------------------------- -#Create repository to run on -REPO=$PWD/rose-test-battery-stemtest-repo -mkdir -p $REPO -svnadmin create $REPO/foo -URL=file://$REPO/foo -BASEINSTALL=$(mktemp -d --tmpdir=$PWD) -(cd $BASEINSTALL; mkdir -p trunk/rose-stem; svn import -q -m "" $URL) -#Keywords for the foo repository -mkdir -p conf -echo "location{primary}[foo.x]=$URL" >conf/keyword.cfg -export FCM_CONF_PATH=$PWD/conf -cd $TEST_DIR -#------------------------------------------------------------------------------- -#Check out a copy of the repository -WORKINGCOPY=$(mktemp -d --tmpdir=$PWD) -SUITENAME=$(basename $WORKINGCOPY) -fcm checkout -q fcm:foo.x_tr $WORKINGCOPY -#------------------------------------------------------------------------------- -#Copy suite into working copy -cp $TEST_SOURCE_DIR/00-run-basic/suite.rc $WORKINGCOPY/rose-stem -cp $TEST_SOURCE_DIR/00-run-basic/rose-suite.conf $WORKINGCOPY/rose-stem -touch $WORKINGCOPY/rose-stem/rose-suite.conf -#We should now have a valid rose-stem suite. -#------------------------------------------------------------------------------- -N_TESTS=56 -tests $N_TESTS -#------------------------------------------------------------------------------- -#Test for successful execution -TEST_KEY=$TEST_KEY_BASE-basic-check -run_pass "$TEST_KEY" \ - rose stem --group=earl_grey --task=milk,sugar --group=spoon,cup,milk \ - --source=$WORKINGCOPY --source=fcm:foo.x_tr@head \ - --name $SUITENAME -- --no-detach --debug -#Test output -OUTPUT=$HOME/cylc-run/$SUITENAME/log/job/1/my_task_1/01/job.out -TEST_KEY=$TEST_KEY_BASE-basic-groups-to-run -file_grep $TEST_KEY "RUN_NAMES=\[earl_grey, milk, sugar, spoon, cup, milk\]" \ - $OUTPUT -TEST_KEY=$TEST_KEY_BASE-basic-source -file_grep $TEST_KEY "SOURCE_FOO=$WORKINGCOPY fcm:foo.x_tr@head" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-basic-host-source -file_grep $TEST_KEY "HOST_SOURCE_FOO=$HOSTNAME:$WORKINGCOPY fcm:foo.x_tr@head" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-basic-source-base -file_grep $TEST_KEY "SOURCE_FOO_BASE=$WORKINGCOPY\$" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-basic-host-source-base -file_grep $TEST_KEY "SOURCE_FOO_BASE=$HOSTNAME:$WORKINGCOPY\$" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-basic-source-rev -file_grep $TEST_KEY "SOURCE_FOO_REV=\$" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-basic-source-mirror -file_grep $TEST_KEY "SOURCE_FOO_MIRROR=fcm:foo.xm/trunk@1\$" $OUTPUT -#------------------------------------------------------------------------------- -# Test using manual project override -TEST_KEY=$TEST_KEY_BASE-project-override -run_pass "$TEST_KEY" \ - rose stem --group=earl_grey --task=milk,sugar --group=spoon,cup,milk \ - --source=bar=$WORKINGCOPY --source=fcm:foo.x_tr@head \ - --name $SUITENAME -- --no-detach --debug -#Test output -OUTPUT=$HOME/cylc-run/$SUITENAME/log/job/1/my_task_1/01/job.out -TEST_KEY=$TEST_KEY_BASE-basic-groups-to-run -file_grep $TEST_KEY "RUN_NAMES=\[earl_grey, milk, sugar, spoon, cup, milk\]" \ - $OUTPUT -TEST_KEY=$TEST_KEY_BASE-project-override-source-foo -file_grep $TEST_KEY "SOURCE_FOO=fcm:foo.x_tr@head" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-project-override-host-source-foo -file_grep $TEST_KEY "HOST_SOURCE_FOO=fcm:foo.x_tr@head" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-project-override-source-bar -file_grep $TEST_KEY "SOURCE_BAR=$WORKINGCOPY" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-project-override-host-source-bar -file_grep $TEST_KEY "HOST_SOURCE_BAR=$HOSTNAME:$WORKINGCOPY" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-project-override-source-base-foo -file_grep $TEST_KEY "SOURCE_FOO_BASE=fcm:foo.x_tr" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-project-override-host-source-base-foo -file_grep $TEST_KEY "HOST_SOURCE_FOO_BASE=fcm:foo.x_tr" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-project-override-source-base-bar -file_grep $TEST_KEY "SOURCE_BAR_BASE=$WORKINGCOPY\$" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-project-override-source-base-bar -file_grep $TEST_KEY "HOST_SOURCE_BAR_BASE=$HOSTNAME:$WORKINGCOPY\$" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-project-override-source-rev-foo -file_grep $TEST_KEY "SOURCE_FOO_REV=@1" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-project-override-source-rev-bar -file_grep $TEST_KEY "SOURCE_BAR_REV=\$" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-project-override-source-mirror -file_grep $TEST_KEY "SOURCE_FOO_MIRROR=fcm:foo.xm/trunk@1\$" $OUTPUT -#------------------------------------------------------------------------------- -# Second test, using suite redirection -TEST_KEY=$TEST_KEY_BASE-suite-redirection -run_pass "$TEST_KEY" \ - rose stem --group=lapsang -C $WORKINGCOPY/rose-stem --source=fcm:foo.x_tr@head\ - --name $SUITENAME -- --no-detach --debug -#Test output -OUTPUT=$HOME/cylc-run/$SUITENAME/log/job/1/my_task_1/01/job.out -TEST_KEY=$TEST_KEY_BASE-suite-redirection-groups-to-run -file_grep $TEST_KEY "RUN_NAMES=\[lapsang\]" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-suite-redirection-source -file_grep $TEST_KEY "SOURCE_FOO=fcm:foo.x_tr@head\$" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-suite-redirection-source-base -file_grep $TEST_KEY "SOURCE_FOO_BASE=fcm:foo.x_tr\$" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-suite-redirection-source-rev -file_grep $TEST_KEY "SOURCE_FOO_REV=@1\$" $OUTPUT -#------------------------------------------------------------------------------- -# Third test, checking subdirectory is working -TEST_KEY=$TEST_KEY_BASE-subdirectory -run_pass "$TEST_KEY" \ - rose stem --group=assam --source=$WORKINGCOPY/rose-stem \ - --name $SUITENAME -- --no-detach --debug -#Test output -OUTPUT=$HOME/cylc-run/$SUITENAME/log/job/1/my_task_1/01/job.out -TEST_KEY=$TEST_KEY_BASE-subdirectory-groups-to-run -file_grep $TEST_KEY "RUN_NAMES=\[assam\]" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-subdirectory-source -file_grep $TEST_KEY "SOURCE_FOO=$WORKINGCOPY" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-subdirectory-host-source -file_grep $TEST_KEY "HOST_SOURCE_FOO=$HOSTNAME:$WORKINGCOPY" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-subdirectory-source-base -file_grep $TEST_KEY "SOURCE_FOO_BASE=$WORKINGCOPY\$" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-subdirectory-host-source-base -file_grep $TEST_KEY "HOST_SOURCE_FOO_BASE=$HOSTNAME:$WORKINGCOPY\$" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-subdirectory-source-rev -file_grep $TEST_KEY "SOURCE_FOO_REV=\$" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-subdirectory-source-mirror -file_grep $TEST_KEY "SOURCE_FOO_MIRROR=fcm:foo.xm/trunk@1\$" $OUTPUT -#------------------------------------------------------------------------------- -# Fourth test, checking relative path with -C is working -TEST_KEY=$TEST_KEY_BASE-relative-path -cd $WORKINGCOPY -run_pass "$TEST_KEY" \ - rose stem --group=ceylon -C rose-stem \ - --name $SUITENAME -- --no-detach --debug -#Test output -OUTPUT=$HOME/cylc-run/$SUITENAME/log/job/1/my_task_1/01/job.out -TEST_KEY=$TEST_KEY_BASE-relative-path-groups-to-run -file_grep $TEST_KEY "RUN_NAMES=\[ceylon\]" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-relative-path-source -file_grep $TEST_KEY "SOURCE_FOO=$WORKINGCOPY" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-relative-path-host-source -file_grep $TEST_KEY "HOST_SOURCE_FOO=$HOSTNAME:$WORKINGCOPY" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-relative-path-source-base -file_grep $TEST_KEY "SOURCE_FOO_BASE=$WORKINGCOPY\$" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-relative-path-host-source-base -file_grep $TEST_KEY "HOST_SOURCE_FOO_BASE=$HOSTNAME:$WORKINGCOPY\$" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-relative-path-source-rev -file_grep $TEST_KEY "SOURCE_FOO_REV=\$" $OUTPUT -#------------------------------------------------------------------------------- -cd $TEST_DIR -#------------------------------------------------------------------------------- -# Test "rose stem" with site/user configuration -export ROSE_CONF_PATH=$TEST_DIR -cat > rose.conf << EOF -[rose-stem] -automatic-options=MILK=true -EOF -#------------------------------------------------------------------------------- -# Fifth test - for successful execution with site/user configuration -TEST_KEY=$TEST_KEY_BASE-check-with-config -run_pass "$TEST_KEY" \ - rose stem --group=earl_grey --task=milk,sugar --group=spoon,cup,milk \ - --source=$WORKINGCOPY --source=fcm:foo.x_tr@head \ - --name $SUITENAME -- --no-detach --debug -#Test output -OUTPUT=$HOME/cylc-run/$SUITENAME/log/job/1/my_task_1/01/job.out -TEST_KEY=$TEST_KEY_BASE-check-with-config-groups-to-run -file_grep $TEST_KEY "RUN_NAMES=\[earl_grey, milk, sugar, spoon, cup, milk\]" \ - $OUTPUT -TEST_KEY=$TEST_KEY_BASE-check-with-config-source -file_grep $TEST_KEY "SOURCE_FOO=$WORKINGCOPY fcm:foo.x_tr@head" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-check-with-config-host-source -file_grep $TEST_KEY "HOST_SOURCE_FOO=$HOSTNAME:$WORKINGCOPY fcm:foo.x_tr@head" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-check-with-config-source-base -file_grep $TEST_KEY "SOURCE_FOO_BASE=$WORKINGCOPY\$" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-check-with-config-host-source-base -file_grep $TEST_KEY "HOST_SOURCE_FOO_BASE=$HOSTNAME:$WORKINGCOPY\$" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-check-with-config-source-rev -file_grep $TEST_KEY "SOURCE_FOO_REV=\$" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-check-with-config-single-auto-option -file_grep $TEST_KEY "MILK=true\$" $OUTPUT -#------------------------------------------------------------------------------- -# Sixth test - multiple automatic-options in the site/user configuration -cat > rose.conf << EOF -[rose-stem] -automatic-options=MILK=true TEA=darjeeling -EOF -#------------------------------------------------------------------------------- -TEST_KEY=$TEST_KEY_BASE-check-with-config -run_pass "$TEST_KEY" \ - rose stem --group=assam --source=$WORKINGCOPY/rose-stem \ - --name $SUITENAME -- --no-detach --debug -#Test output -OUTPUT=$HOME/cylc-run/$SUITENAME/log/job/1/my_task_1/01/job.out -TEST_KEY=$TEST_KEY_BASE-multi-auto-config-first -file_grep $TEST_KEY "MILK=true\$" $OUTPUT -TEST_KEY=$TEST_KEY_BASE-multi-auto-config-second -file_grep $TEST_KEY "TEA=darjeeling\$" $OUTPUT -#------------------------------------------------------------------------------- -# Seventh test - check incompatible rose-stem versions -cp $TEST_SOURCE_DIR/00-run-basic/rose-suite2.conf $WORKINGCOPY/rose-stem/rose-suite.conf -TEST_KEY=$TEST_KEY_BASE-incompatible_versions -run_fail "$TEST_KEY" \ - rose stem --group=earl_grey --task=milk,sugar --group=spoon,cup,milk \ - --source=$WORKINGCOPY --source=fcm:foo.x_tr@head \ - --name $SUITENAME -- --no-detach --debug -OUTPUT=$TEST_DIR/${TEST_KEY}.err -TEST_KEY=$TEST_KEY_BASE-incompatible-versions-correct_error -file_grep $TEST_KEY "Running metomi.rose-stem version 1 but suite is at version 0" $OUTPUT -#------------------------------------------------------------------------------- -# Eighth test - test error message when project not in keywords -# Remove the keywords file and re-copy the original rose-suite.conf file -unset FCM_CONF_PATH -cp $TEST_SOURCE_DIR/00-run-basic/rose-suite.conf $WORKINGCOPY/rose-stem -TEST_KEY=$TEST_KEY_BASE-project-not-in-keywords -run_fail "$TEST_KEY" \ - rose stem --group=earl_grey --task=milk,sugar --group=spoon,cup,milk \ - --source=$WORKINGCOPY --source=fcm:foo.x_tr@head \ - --name $SUITENAME -- --no-detach --debug -OUTPUT=$TEST_DIR/${TEST_KEY}.err -TEST_KEY=$TEST_KEY_BASE-project-not-in-keywords-correct_error -file_grep $TEST_KEY "Cannot ascertain project for source tree" $OUTPUT -#------------------------------------------------------------------------------- -#Clean suite -rose suite-clean -q -y $SUITENAME -#------------------------------------------------------------------------------- -exit 0 diff --git a/t/rose-stem/00-run-basic/rose-suite.conf b/t/rose-stem/00-run-basic/rose-suite.conf deleted file mode 100644 index e7818b8ecb..0000000000 --- a/t/rose-stem/00-run-basic/rose-suite.conf +++ /dev/null @@ -1 +0,0 @@ -ROSE_STEM_VERSION=1 diff --git a/t/rose-stem/00-run-basic/rose-suite2.conf b/t/rose-stem/00-run-basic/rose-suite2.conf deleted file mode 100644 index 787d9323aa..0000000000 --- a/t/rose-stem/00-run-basic/rose-suite2.conf +++ /dev/null @@ -1 +0,0 @@ -ROSE_STEM_VERSION=0 diff --git a/t/rose-stem/00-run-basic/suite.rc b/t/rose-stem/00-run-basic/suite.rc deleted file mode 100644 index 3efa350f6b..0000000000 --- a/t/rose-stem/00-run-basic/suite.rc +++ /dev/null @@ -1,38 +0,0 @@ -#!jinja2 -[cylc] - UTC mode=True - abort if any task fails=True - [[events]] - abort on timeout=True - timeout=PT2M -[scheduling] - [[dependencies]] - graph=my_task_1 - -[runtime] - [[my_task_1]] - script=""" -echo RUN_NAMES={{RUN_NAMES}} -echo SOURCE_FOO={{SOURCE_FOO}} -echo HOST_SOURCE_FOO={{HOST_SOURCE_FOO}} -echo SOURCE_FOO_BASE={{SOURCE_FOO_BASE}} -echo HOST_SOURCE_FOO_BASE={{HOST_SOURCE_FOO_BASE}} -echo SOURCE_FOO_REV={{SOURCE_FOO_REV}} -echo SOURCE_FOO_MIRROR={{SOURCE_FOO_MIRROR}} -{%- if TEA is defined %} -echo TEA={{TEA}} -{%- endif %} -{%- if MILK is defined %} -echo MILK={{MILK}} -{%- endif %} -{%- if SOURCE_BAR is defined%} -echo SOURCE_BAR={{SOURCE_BAR}} -echo HOST_SOURCE_BAR={{HOST_SOURCE_BAR}} -echo SOURCE_BAR_BASE={{SOURCE_BAR_BASE}} -echo HOST_SOURCE_BAR_BASE={{HOST_SOURCE_BAR_BASE}} -echo SOURCE_BAR_REV={{SOURCE_BAR_REV}} -{%- endif %} - -""" - [[[job]]] - execution time limit=PT1M diff --git a/t/rose-stem/test_header b/t/rose-stem/test_header deleted file mode 120000 index 90bd5a36f9..0000000000 --- a/t/rose-stem/test_header +++ /dev/null @@ -1 +0,0 @@ -../lib/bash/test_header \ No newline at end of file diff --git a/t/rose-suite-clean/00-normal b/t/rose-suite-clean/00-normal deleted file mode 120000 index af1978f616..0000000000 --- a/t/rose-suite-clean/00-normal +++ /dev/null @@ -1 +0,0 @@ -../rose-suite-run/02-install \ No newline at end of file diff --git a/t/rose-suite-clean/00-normal.t b/t/rose-suite-clean/00-normal.t deleted file mode 100755 index fc6c2c6800..0000000000 --- a/t/rose-suite-clean/00-normal.t +++ /dev/null @@ -1,90 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-clean", normal mode. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - - -install_suite() { - set -e - if [[ -n "$JOB_HOST" ]]; then - rose suite-run --new -q \ - -C $TEST_SOURCE_DIR/$TEST_KEY_BASE -i --name=$NAME \ - -S "HOST=\"$JOB_HOST\"" - ssh "$JOB_HOST" "ls -d cylc-run/$NAME 1>/dev/null" - else - rose suite-run --new -q \ - -C $TEST_SOURCE_DIR/$TEST_KEY_BASE -i --name=$NAME - fi - ls -d $HOME/cylc-run/$NAME 1>/dev/null - set +e -} -#------------------------------------------------------------------------------- -JOB_HOST=$(rose config --default= 't' 'job-host') -if [[ -n $JOB_HOST ]]; then - JOB_HOST=$(rose host-select -q $JOB_HOST) - tests 15 -else - tests 10 -fi -#------------------------------------------------------------------------------- -export ROSE_CONF_PATH= -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) -install_suite -#------------------------------------------------------------------------------- -TEST_KEY=$TEST_KEY_BASE-ans-empty -run_fail "$TEST_KEY" rose suite-clean $NAME <<<'' -run_pass "$TEST_KEY.locahost.ls" ls -d $HOME/cylc-run/$NAME -if [[ -n "$JOB_HOST" ]]; then - run_pass "$TEST_KEY.job-host.ls" ssh "$JOB_HOST" "ls -d cylc-run/$NAME" -fi -#------------------------------------------------------------------------------- -TEST_KEY=$TEST_KEY_BASE-ans-n -run_fail "$TEST_KEY" rose suite-clean $NAME <<<'n' -run_pass "$TEST_KEY.locahost.ls" ls -d $HOME/cylc-run/$NAME -if [[ -n "$JOB_HOST" ]]; then - run_pass "$TEST_KEY.job-host.ls" ssh "$JOB_HOST" "ls -d cylc-run/$NAME" -fi -#------------------------------------------------------------------------------- -TEST_KEY=$TEST_KEY_BASE--name-ans-n -run_fail "$TEST_KEY" rose suite-clean --name=$NAME <<<'n' -run_pass "$TEST_KEY.locahost.ls" ls -d $HOME/cylc-run/$NAME -if [[ -n "$JOB_HOST" ]]; then - run_pass "$TEST_KEY.job-host.ls" ssh "$JOB_HOST" "ls -d cylc-run/$NAME" -fi -#------------------------------------------------------------------------------- -TEST_KEY=$TEST_KEY_BASE-ans-y -run_pass "$TEST_KEY" rose suite-clean -v -v $NAME <<<'y' -run_fail "$TEST_KEY.locahost.ls" ls -d $HOME/cylc-run/$NAME -if [[ -n "$JOB_HOST" ]]; then - run_fail "$TEST_KEY.job-host.ls" ssh "$JOB_HOST" "ls -d cylc-run/$NAME" -fi -#------------------------------------------------------------------------------- -TEST_KEY=$TEST_KEY_BASE--name-ans-y -install_suite -run_pass "$TEST_KEY" rose suite-clean -v -v -n $NAME <<<'y' -run_fail "$TEST_KEY.locahost.ls" ls -d $HOME/cylc-run/$NAME -if [[ -n "$JOB_HOST" ]]; then - run_fail "$TEST_KEY.job-host.ls" ssh "$JOB_HOST" "ls -d cylc-run/$NAME" -fi -#------------------------------------------------------------------------------- -exit 0 diff --git a/t/rose-suite-clean/01-running b/t/rose-suite-clean/01-running deleted file mode 120000 index c6307c3aee..0000000000 --- a/t/rose-suite-clean/01-running +++ /dev/null @@ -1 +0,0 @@ -../rose-suite-run/00-run-basic \ No newline at end of file diff --git a/t/rose-suite-clean/01-running.t b/t/rose-suite-clean/01-running.t deleted file mode 100755 index a7b9794108..0000000000 --- a/t/rose-suite-clean/01-running.t +++ /dev/null @@ -1,100 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-clean", while the suite is running. -#------------------------------------------------------------------------------- -. "$(dirname "$0")/test_header" - - -#------------------------------------------------------------------------------- -tests 7 -#------------------------------------------------------------------------------- -export ROSE_CONF_PATH= -TEST_KEY="${TEST_KEY_BASE}" -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" -SUITE_RUN_DIR="$(readlink -f "${SUITE_RUN_DIR}")" -NAME="$(basename "${SUITE_RUN_DIR}")" -# Install suite, and prove that directories are created -rose suite-run --debug -q \ - -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - -- --no-detach --debug & -ROSE_SUITE_RUN_PID=$! -poll ! test -e "${SUITE_RUN_DIR}/log/job/2013010100/my_task_1/01/job" -CONTACT="${HOME}/cylc-run/${NAME}/.service/contact" -SUITE_HOST="$(sed -n 's/CYLC_SUITE_HOST=//p' "${CONTACT}")" -SUITE_OWNER="$(sed -n 's/CYLC_SUITE_OWNER=//p' "${CONTACT}")" -SUITE_PORT="$(sed -n 's/CYLC_SUITE_PORT=//p' "${CONTACT}")" -SUITE_PROCESS="$(sed -n 's/CYLC_SUITE_PROCESS=//p' "${CONTACT}")" -#------------------------------------------------------------------------------- -TEST_KEY="${TEST_KEY_BASE}-running" -run_fail "${TEST_KEY}" rose suite-clean -y "${NAME}" -# Note: Error file CYLC_SUITE_* lines contain \t characters -file_cmp "${TEST_KEY}.err" "${TEST_KEY}.err" <<__ERR__ -[FAIL] Suite "${NAME}" appears to be running: -[FAIL] Contact info from: "${CONTACT}" -[FAIL] CYLC_SUITE_HOST=${SUITE_HOST} -[FAIL] CYLC_SUITE_OWNER=${SUITE_OWNER} -[FAIL] CYLC_SUITE_PORT=${SUITE_PORT} -[FAIL] CYLC_SUITE_PROCESS=${SUITE_PROCESS} -[FAIL] Try "cylc stop '${NAME}'" first? -__ERR__ -if [[ ! -d "${HOME}/cylc-run/${NAME}" ]]; then - exit 1 -fi -#------------------------------------------------------------------------------- -TEST_KEY="${TEST_KEY_BASE}-running-name" -run_fail "${TEST_KEY}" rose suite-clean -y -n "${NAME}" -# Note: Error file CYLC_SUITE_* lines contain \t characters -file_cmp "${TEST_KEY}.err" "${TEST_KEY}.err" <<__ERR__ -[FAIL] Suite "${NAME}" appears to be running: -[FAIL] Contact info from: "${CONTACT}" -[FAIL] CYLC_SUITE_HOST=${SUITE_HOST} -[FAIL] CYLC_SUITE_OWNER=${SUITE_OWNER} -[FAIL] CYLC_SUITE_PORT=${SUITE_PORT} -[FAIL] CYLC_SUITE_PROCESS=${SUITE_PROCESS} -[FAIL] Try "cylc stop '${NAME}'" first? -__ERR__ -if [[ ! -d "${HOME}/cylc-run/${NAME}" ]]; then - exit 1 -fi -#------------------------------------------------------------------------------- -touch $SUITE_RUN_DIR/flag # let the suite stop -# Wait for the suite to complete -TIMEOUT=$(($(date +%s) + 120)) # wait 2 minutes -while [[ -e "${CONTACT}" ]] && (($(date +%s) < TIMEOUT)) -do - sleep 1 -done -if [[ -e "${CONTACT}" ]]; then - exit 1 -fi -wait "${ROSE_SUITE_RUN_PID}" -TEST_KEY="${TEST_KEY_BASE}-stopped" -run_pass "${TEST_KEY}" rose suite-clean -y "${NAME}" -sed -i '/\/\.cylc\//d' "$TEST_KEY.out" -file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__ -[INFO] delete: localhost:cylc-run/${NAME}/work -[INFO] delete: localhost:cylc-run/${NAME}/share/cycle -[INFO] delete: localhost:cylc-run/${NAME}/share -[INFO] delete: localhost:cylc-run/${NAME} -__OUT__ -file_cmp "${TEST_KEY}.err" "${TEST_KEY}.err" . -#------------------------------------------------------------------------------- -# Test "rose suite-clean", simple --only= modes. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - - -run_suite() { - set -e - if [[ -n "$JOB_HOST" ]]; then - rose suite-run --new -q \ - -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" -i --name="$NAME" \ - -S "HOST=\"$JOB_HOST\"" -- --no-detach --debug - ssh "$JOB_HOST" "ls -d cylc-run/$NAME 1>/dev/null" - else - rose suite-run --new -q \ - -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" -i --name=$NAME \ - -- --no-detach --debug - fi - ls -d $HOME/cylc-run/$NAME 1>/dev/null - set +e -} - -#------------------------------------------------------------------------------- -N_TESTS=6 -tests "$N_TESTS" -#------------------------------------------------------------------------------- -JOB_HOST=$(rose config --default= 't' 'job-host') -if [[ -n $JOB_HOST ]]; then - JOB_HOST=$(rose host-select -q $JOB_HOST) -fi -#------------------------------------------------------------------------------- -export ROSE_CONF_PATH= -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -SUITE_RUN_DIR=$(readlink -f "$SUITE_RUN_DIR") -NAME=$(basename $SUITE_RUN_DIR) -#------------------------------------------------------------------------------- -TEST_KEY="$TEST_KEY_BASE-share" -run_suite -run_pass "$TEST_KEY" rose suite-clean -y -n "$NAME" --only=share -if [[ -n "$JOB_HOST" ]]; then - file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__ -[INFO] delete: localhost:cylc-run/${NAME}/share -[INFO] delete: $JOB_HOST:cylc-run/$NAME/share -__OUT__ -else - file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__ -[INFO] delete: localhost:cylc-run/${NAME}/share -__OUT__ -fi -#------------------------------------------------------------------------------- -TEST_KEY="$TEST_KEY_BASE-work" -run_suite -run_pass "$TEST_KEY" rose suite-clean -y -n "$NAME" --only=work -if [[ -n "$JOB_HOST" ]]; then - file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__ -[INFO] delete: localhost:cylc-run/${NAME}/work -[INFO] delete: $JOB_HOST:cylc-run/$NAME/work -__OUT__ -else - file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__ -[INFO] delete: localhost:cylc-run/${NAME}/work -__OUT__ -fi -#------------------------------------------------------------------------------- -TEST_KEY="$TEST_KEY_BASE-both" -run_suite -run_pass "$TEST_KEY" rose suite-clean -y -n "$NAME" --only=share --only=work -if [[ -n "$JOB_HOST" ]]; then - file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__ -[INFO] delete: localhost:cylc-run/${NAME}/work -[INFO] delete: localhost:cylc-run/${NAME}/share -[INFO] delete: $JOB_HOST:cylc-run/$NAME/work -[INFO] delete: $JOB_HOST:cylc-run/$NAME/share -__OUT__ -else - file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__ -[INFO] delete: localhost:cylc-run/${NAME}/work -[INFO] delete: localhost:cylc-run/${NAME}/share -__OUT__ -fi -#------------------------------------------------------------------------------- -rose suite-clean -q -y --name="$NAME" -exit 0 diff --git a/t/rose-suite-clean/02-only-1/rose-suite.conf b/t/rose-suite-clean/02-only-1/rose-suite.conf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/t/rose-suite-clean/02-only-1/suite.rc b/t/rose-suite-clean/02-only-1/suite.rc deleted file mode 100644 index 2fab1e5d5a..0000000000 --- a/t/rose-suite-clean/02-only-1/suite.rc +++ /dev/null @@ -1,23 +0,0 @@ -#!jinja2 -[cylc] - UTC mode = True - [[events]] - abort on stalled = True -[scheduling] - [[dependencies]] - graph = """ -my_task_1 -{% if HOST is defined %} -my_task_2 -{% endif %} -""" - -[runtime] - [[root]] - script = true - [[my_task_1]] -{% if HOST is defined %} - [[my_task_2]] - [[[remote]]] - host = {{HOST}} -{% endif %} diff --git a/t/rose-suite-clean/03-only-2.t b/t/rose-suite-clean/03-only-2.t deleted file mode 100755 index 29a3edcab7..0000000000 --- a/t/rose-suite-clean/03-only-2.t +++ /dev/null @@ -1,115 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-clean", --only= mode with globs. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - - -run_suite() { - set -e - if [[ -n "$JOB_HOST" ]]; then - rose suite-run --new -q \ - -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" --name="$NAME" \ - -S "HOST=\"$JOB_HOST\"" -- --no-detach --debug - ssh "$JOB_HOST" "ls -d cylc-run/$NAME 1>/dev/null" - else - rose suite-run --new -q \ - -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" --name="$NAME" \ - -- --no-detach --debug - fi - ls -d $HOME/cylc-run/$NAME 1>/dev/null - set +e -} - -#------------------------------------------------------------------------------- -N_TESTS=4 -tests "$N_TESTS" -#------------------------------------------------------------------------------- -JOB_HOST=$(rose config --default= 't' 'job-host') -if [[ -n $JOB_HOST ]]; then - JOB_HOST=$(rose host-select -q $JOB_HOST) -fi -#------------------------------------------------------------------------------- -export CYLC_CONF_PATH= -export ROSE_CONF_PATH= -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -SUITE_RUN_DIR=$(readlink -f "$SUITE_RUN_DIR") -NAME=$(basename $SUITE_RUN_DIR) -#------------------------------------------------------------------------------- -run_suite -TEST_KEY="$TEST_KEY_BASE-share-tens" -run_pass "$TEST_KEY" \ - rose suite-clean -y -n "$NAME" --only=share/cycle/20?00101T0000Z -LANG=C sort "$TEST_KEY.out" >"$TEST_KEY.sorted.out" -if [[ -n "$JOB_HOST" ]]; then - # We do not know the relative sort order of $SUITE_RUN_DIR and $JOB_HOST. - LANG=C sort >"$TEST_KEY.expected.out" <<__OUT__ -[INFO] delete: localhost:cylc-run/${NAME}/share/cycle/20200101T0000Z -[INFO] delete: localhost:cylc-run/${NAME}/share/cycle/20100101T0000Z -[INFO] delete: localhost:cylc-run/${NAME}/share/cycle/20000101T0000Z -[INFO] delete: $JOB_HOST:cylc-run/$NAME/share/cycle/20200101T0000Z -[INFO] delete: $JOB_HOST:cylc-run/$NAME/share/cycle/20100101T0000Z -[INFO] delete: $JOB_HOST:cylc-run/$NAME/share/cycle/20000101T0000Z -__OUT__ -else - cat >"$TEST_KEY.expected.out" <<__OUT__ -[INFO] delete: localhost:cylc-run/${NAME}/share/cycle/20000101T0000Z -[INFO] delete: localhost:cylc-run/${NAME}/share/cycle/20100101T0000Z -[INFO] delete: localhost:cylc-run/${NAME}/share/cycle/20200101T0000Z -__OUT__ -fi -file_cmp "$TEST_KEY.sorted.out" "$TEST_KEY.sorted.out" "$TEST_KEY.expected.out" -TEST_KEY="$TEST_KEY_BASE-multiples" -run_pass "$TEST_KEY" \ - rose suite-clean -y -n "$NAME" \ - --only=share/cycle/*/hello-world.out \ - --only=etc/*/greet-earth.out -if [[ -n "$JOB_HOST" ]]; then - file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__ -[INFO] delete: localhost:cylc-run/${NAME}/share/cycle/20150101T0000Z/hello-world.out -[INFO] delete: localhost:cylc-run/${NAME}/share/cycle/20050101T0000Z/hello-world.out -[INFO] delete: localhost:cylc-run/${NAME}/etc/20200101T0000Z/greet-earth.out -[INFO] delete: localhost:cylc-run/${NAME}/etc/20150101T0000Z/greet-earth.out -[INFO] delete: localhost:cylc-run/${NAME}/etc/20100101T0000Z/greet-earth.out -[INFO] delete: localhost:cylc-run/${NAME}/etc/20050101T0000Z/greet-earth.out -[INFO] delete: localhost:cylc-run/${NAME}/etc/20000101T0000Z/greet-earth.out -[INFO] delete: $JOB_HOST:cylc-run/$NAME/share/cycle/20150101T0000Z/hello-world.out -[INFO] delete: $JOB_HOST:cylc-run/$NAME/share/cycle/20050101T0000Z/hello-world.out -[INFO] delete: $JOB_HOST:cylc-run/$NAME/etc/20200101T0000Z/greet-earth.out -[INFO] delete: $JOB_HOST:cylc-run/$NAME/etc/20150101T0000Z/greet-earth.out -[INFO] delete: $JOB_HOST:cylc-run/$NAME/etc/20100101T0000Z/greet-earth.out -[INFO] delete: $JOB_HOST:cylc-run/$NAME/etc/20050101T0000Z/greet-earth.out -[INFO] delete: $JOB_HOST:cylc-run/$NAME/etc/20000101T0000Z/greet-earth.out -__OUT__ -else - file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__ -[INFO] delete: localhost:cylc-run/${NAME}/share/cycle/20150101T0000Z/hello-world.out -[INFO] delete: localhost:cylc-run/${NAME}/share/cycle/20050101T0000Z/hello-world.out -[INFO] delete: localhost:cylc-run/${NAME}/etc/20200101T0000Z/greet-earth.out -[INFO] delete: localhost:cylc-run/${NAME}/etc/20150101T0000Z/greet-earth.out -[INFO] delete: localhost:cylc-run/${NAME}/etc/20100101T0000Z/greet-earth.out -[INFO] delete: localhost:cylc-run/${NAME}/etc/20050101T0000Z/greet-earth.out -[INFO] delete: localhost:cylc-run/${NAME}/etc/20000101T0000Z/greet-earth.out -__OUT__ -fi -#------------------------------------------------------------------------------- -rose suite-clean -q -y --name="$NAME" -exit 0 diff --git a/t/rose-suite-clean/03-only-2/rose-suite.conf b/t/rose-suite-clean/03-only-2/rose-suite.conf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/t/rose-suite-clean/03-only-2/suite.rc b/t/rose-suite-clean/03-only-2/suite.rc deleted file mode 100644 index 29b105cfbf..0000000000 --- a/t/rose-suite-clean/03-only-2/suite.rc +++ /dev/null @@ -1,39 +0,0 @@ -#!jinja2 -[cylc] - UTC mode = True - [[events]] - abort on stalled = True -[scheduling] - initial cycle point = 2000 - final cycle point = 2020 - [[dependencies]] - [[[P5Y]]] - graph = """ -my_task_1[-P5Y] => my_task_1 -{% if HOST is defined %} -my_task_2 -{% endif %} -""" - -[runtime] - [[root]] - env-script = eval $(rose task-env) - script = """ -mkdir -p "$ROSE_SUITE_DIR/etc" || true -mkdir -p "$ROSE_SUITE_DIR/etc/$ROSE_TASK_CYCLE_TIME" || true -for ITEM in 'hello-world.out:Hello World' 'greet-earth.out:Greet Earth'; do - FILE=${ITEM%:*} - CONTENT=${ITEM#*:} - tee \ - "$FILE" \ - "$ROSE_DATAC/$FILE" \ - "$ROSE_SUITE_DIR/etc/$ROSE_TASK_CYCLE_TIME/$FILE" \ - <<<"$CONTENT" -done -""" - [[my_task_1]] -{% if HOST is defined %} - [[my_task_2]] - [[[remote]]] - host = {{HOST}} -{% endif %} diff --git a/t/rose-suite-clean/04-only-3 b/t/rose-suite-clean/04-only-3 deleted file mode 120000 index 2b5e2dadf4..0000000000 --- a/t/rose-suite-clean/04-only-3 +++ /dev/null @@ -1 +0,0 @@ -02-only-1 \ No newline at end of file diff --git a/t/rose-suite-clean/04-only-3.t b/t/rose-suite-clean/04-only-3.t deleted file mode 100755 index f04c3a113a..0000000000 --- a/t/rose-suite-clean/04-only-3.t +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-clean", --only= mode and a localhost root-dir{*} setting. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - - -run_suite() { - set -e - rose suite-run --new -q \ - -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" --name="$NAME" \ - -- --no-detach --debug - ls -d $HOME/cylc-run/$NAME 1>/dev/null - set +e -} - -#------------------------------------------------------------------------------- -tests 2 -#------------------------------------------------------------------------------- -export ROSE_CONF_PATH=$PWD/conf -export CYLC_CONF_PATH= -export ROOT_DIR_WORK=$PWD/work - -mkdir 'conf' 'work' -cat >'conf/rose.conf' <<'__CONF__' -[rose-suite-run] -root-dir{work}=*=$ROOT_DIR_WORK -__CONF__ - -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -SUITE_RUN_DIR=$(readlink -f "$SUITE_RUN_DIR") -NAME=$(basename $SUITE_RUN_DIR) -#------------------------------------------------------------------------------- -run_suite -TEST_KEY="$TEST_KEY_BASE-work" -run_pass "$TEST_KEY" rose suite-clean -y -n "$NAME" --only=work -{ - LANG=C sort <<__OUT__ -[INFO] delete: localhost:${ROOT_DIR_WORK}/cylc-run/${NAME}/work -[INFO] delete: localhost:cylc-run/${NAME}/work -__OUT__ -} >"${TEST_KEY}.out.expected" -LANG=C sort "${TEST_KEY}.out" >"${TEST_KEY}.out.sorted" -file_cmp "${TEST_KEY}.out" "${TEST_KEY}.out.sorted" "${TEST_KEY}.out.expected" -#------------------------------------------------------------------------------- -rose suite-clean -q -y --name="$NAME" -exit 0 diff --git a/t/rose-suite-clean/05-only-4 b/t/rose-suite-clean/05-only-4 deleted file mode 120000 index f8e79ae6d5..0000000000 --- a/t/rose-suite-clean/05-only-4 +++ /dev/null @@ -1 +0,0 @@ -03-only-2 \ No newline at end of file diff --git a/t/rose-suite-clean/05-only-4.t b/t/rose-suite-clean/05-only-4.t deleted file mode 100755 index c8227d4b25..0000000000 --- a/t/rose-suite-clean/05-only-4.t +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-clean", --only= glob and a localhost root-dir{*} setting. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - - -run_suite() { - set -e - rose suite-run --new -q \ - -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" --name="$NAME" \ - -- --no-detach --debug - ls -d $HOME/cylc-run/$NAME 1>/dev/null - set +e -} - -#------------------------------------------------------------------------------- -tests 2 -#------------------------------------------------------------------------------- -export CYLC_CONF_PATH= -export ROSE_CONF_PATH=$PWD/conf -export ROOT_DIR_WORK=$PWD/work - -mkdir 'conf' 'work' -cat >'conf/rose.conf' <<'__CONF__' -[rose-suite-run] -root-dir{work}=*=$ROOT_DIR_WORK -__CONF__ - -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -SUITE_RUN_DIR=$(readlink -f "$SUITE_RUN_DIR") -NAME=$(basename $SUITE_RUN_DIR) -#------------------------------------------------------------------------------- -run_suite -TEST_KEY="$TEST_KEY_BASE-work" -run_pass "$TEST_KEY" rose suite-clean -y -n "$NAME" --only=work/20?00101T0000Z -file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__ -[INFO] delete: localhost:cylc-run/${NAME}/work/20200101T0000Z -[INFO] delete: localhost:cylc-run/${NAME}/work/20100101T0000Z -[INFO] delete: localhost:cylc-run/${NAME}/work/20000101T0000Z -__OUT__ -#------------------------------------------------------------------------------- -rose suite-clean -q -y --name="$NAME" -exit 0 diff --git a/t/rose-suite-clean/06-only-5 b/t/rose-suite-clean/06-only-5 deleted file mode 120000 index 2b5e2dadf4..0000000000 --- a/t/rose-suite-clean/06-only-5 +++ /dev/null @@ -1 +0,0 @@ -02-only-1 \ No newline at end of file diff --git a/t/rose-suite-clean/06-only-5.t b/t/rose-suite-clean/06-only-5.t deleted file mode 100755 index 2ae187d263..0000000000 --- a/t/rose-suite-clean/06-only-5.t +++ /dev/null @@ -1,71 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-clean", --only= mode and a remote host root-dir{*} setting. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - - -run_suite() { - set -e - rose suite-run --new -q \ - -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" --name="$NAME" \ - -S "HOST=\"$JOB_HOST\"" -- --no-detach --debug - ssh "$JOB_HOST" "ls -d cylc-run/$NAME 1>/dev/null" - ls -d $HOME/cylc-run/$NAME 1>/dev/null - set +e -} -JOB_HOST=$(rose config --default= 't' 'job-host') -JOB_HOST_WORK=$(rose config --default= 't' 'job-host-run-root') -if [[ -z "$JOB_HOST" || -z "$JOB_HOST_WORK" ]]; then - skip_all '"[t]job-host" or "[t]job-host-run-root" not defined' -fi -JOB_HOST=$(rose host-select -q $JOB_HOST) -#------------------------------------------------------------------------------- -tests 2 -#------------------------------------------------------------------------------- -export ROSE_CONF_PATH=$PWD/conf - -mkdir 'conf' -cat >'conf/rose.conf' <<__CONF__ -[rose-suite-run] -root-dir{work}=$JOB_HOST=$JOB_HOST_WORK -__CONF__ -JOB_HOST_WORK=$(ssh -oBatchMode=yes "$JOB_HOST" \ - "bash -l -c \"echo \\$JOB_HOST_WORK\"" | tail -1) - -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -SUITE_RUN_DIR=$(readlink -f "$SUITE_RUN_DIR") -NAME=$(basename $SUITE_RUN_DIR) -#------------------------------------------------------------------------------- -run_suite -TEST_KEY="$TEST_KEY_BASE-work" -run_pass "$TEST_KEY" rose suite-clean -y -n "$NAME" --only=work -{ - echo "[INFO] delete: localhost:cylc-run/${NAME}/work" - LANG=C sort -r <<__OUT__ -[INFO] delete: $JOB_HOST:cylc-run/$NAME/work -[INFO] delete: $JOB_HOST:$JOB_HOST_WORK/cylc-run/$NAME/work -__OUT__ -} >"$TEST_KEY.out.expected" -file_cmp "$TEST_KEY.out" "$TEST_KEY.out" "$TEST_KEY.out.expected" -#------------------------------------------------------------------------------- -rose suite-clean -q -y --name="$NAME" -exit 0 diff --git a/t/rose-suite-clean/07-only-6 b/t/rose-suite-clean/07-only-6 deleted file mode 120000 index f8e79ae6d5..0000000000 --- a/t/rose-suite-clean/07-only-6 +++ /dev/null @@ -1 +0,0 @@ -03-only-2 \ No newline at end of file diff --git a/t/rose-suite-clean/07-only-6.t b/t/rose-suite-clean/07-only-6.t deleted file mode 100755 index 9b392330b0..0000000000 --- a/t/rose-suite-clean/07-only-6.t +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-clean", --only= glob and a remote host root-dir{*} setting. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - - -run_suite() { - set -e - rose suite-run --new -q \ - -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" --name="$NAME" \ - -S "HOST=\"$JOB_HOST\"" -- --no-detach --debug - ssh "$JOB_HOST" "ls -d cylc-run/$NAME 1>/dev/null" - ls -d $HOME/cylc-run/$NAME 1>/dev/null - set +e -} -JOB_HOST=$(rose config --default= 't' 'job-host') -JOB_HOST_WORK=$(rose config --default= 't' 'job-host-run-root') -if [[ -z "$JOB_HOST" || -z "$JOB_HOST_WORK" ]]; then - skip_all '"[t]job-host" or "[t]job-host-run-root" not defined' -fi -JOB_HOST=$(rose host-select -q $JOB_HOST) -#------------------------------------------------------------------------------- -tests 2 -#------------------------------------------------------------------------------- -export ROSE_CONF_PATH=$PWD/conf - -mkdir 'conf' -cat >'conf/rose.conf' <<__CONF__ -[rose-suite-run] -root-dir{work}=$JOB_HOST=$JOB_HOST_WORK -__CONF__ - -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -SUITE_RUN_DIR=$(readlink -f "$SUITE_RUN_DIR") -NAME=$(basename $SUITE_RUN_DIR) -#------------------------------------------------------------------------------- -run_suite -TEST_KEY="$TEST_KEY_BASE-work" -run_pass "$TEST_KEY" rose suite-clean -y -n "$NAME" --only=work/20?00101T0000Z -file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__ -[INFO] delete: localhost:cylc-run/${NAME}/work/20200101T0000Z -[INFO] delete: localhost:cylc-run/${NAME}/work/20100101T0000Z -[INFO] delete: localhost:cylc-run/${NAME}/work/20000101T0000Z -[INFO] delete: $JOB_HOST:cylc-run/$NAME/work/20200101T0000Z -[INFO] delete: $JOB_HOST:cylc-run/$NAME/work/20100101T0000Z -[INFO] delete: $JOB_HOST:cylc-run/$NAME/work/20000101T0000Z -__OUT__ -#------------------------------------------------------------------------------- -rose suite-clean -q -y --name="$NAME" -exit 0 diff --git a/t/rose-suite-clean/08-rmdir.t b/t/rose-suite-clean/08-rmdir.t deleted file mode 100755 index adb36363c8..0000000000 --- a/t/rose-suite-clean/08-rmdir.t +++ /dev/null @@ -1,79 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-clean", normal mode. -#------------------------------------------------------------------------------- -. "$(dirname "$0")/test_header" - - -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" -NAME="$(basename "${SUITE_RUN_DIR}")" - -cp -pr "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" "${NAME}" - -JOB_HOST="$(rose config 't' 'job-host')" -JOB_HOST_RUN_ROOT="$(rose config 't' 'job-host-run-root')" -JOB_HOST_OPT= -if [[ -n "${JOB_HOST}" && -n "${JOB_HOST_RUN_ROOT}" ]]; then - export JOB_HOST="$(rose host-select -q "${JOB_HOST}")" - export JOB_HOST_RUN_ROOT - cat >>"${NAME}/rose-suite.conf" <<__CONF__ -root-dir{share/cycle}=${JOB_HOST}=${JOB_HOST_RUN_ROOT} - =*=\${ROSE_TEST_ROOT_DIR} -root-dir{work}=${JOB_HOST}=${JOB_HOST_RUN_ROOT} - =*=\${ROSE_TEST_ROOT_DIR} - -[jinja2:suite.rc] -JOB_HOST="${JOB_HOST}" -__CONF__ - tests 5 -else - tests 3 -fi - -# Run suite, create lots of directories -export CYLC_CONF_PATH= -export ROSE_CONF_PATH= -export ROSE_TEST_ROOT_DIR="${PWD}/root.d" -set -e -rose suite-run -q -C "${PWD}/${NAME}" --host='localhost' \ - -- --no-detach --debug -# Prove that the directories exist before clean -test -d "${HOME}/cylc-run/${NAME}" -test -d "${PWD}/root.d/cylc-run/${NAME}" -if [[ -n "${JOB_HOST}" && -n "${JOB_HOST_RUN_ROOT}" ]]; then - SSH='ssh -n -oBatchMode=yes' - ${SSH} "${JOB_HOST}" \ - "bash -l -c 'ls -d cylc-run/${NAME} ${JOB_HOST_RUN_ROOT}/cylc-run/${NAME}'" -fi -set +e - -TEST_KEY="${TEST_KEY_BASE}" -run_pass "${TEST_KEY}" rose suite-clean -y -v -v --debug "${NAME}" -run_fail "${TEST_KEY}-test-d-cylc-run" test -d "${HOME}/cylc-run/${NAME}" -run_fail "${TEST_KEY}-test-d-root-x" test -d "${PWD}/root.d/cylc-run/${NAME}" -if [[ -n "${JOB_HOST}" && -n "${JOB_HOST_RUN_ROOT}" ]]; then - run_pass "${TEST_KEY}-test-d-at-${JOB_HOST}" ${SSH} "${JOB_HOST}" \ - "bash -l -c '! test -d cylc-run/${NAME}'" - run_pass "${TEST_KEY}-test-d-at-${JOB_HOST}" ${SSH} "${JOB_HOST}" \ - "bash -l -c '! test -d ${JOB_HOST_RUN_ROOT}/cylc-run/${NAME}'" -fi -#------------------------------------------------------------------------------- -exit 0 diff --git a/t/rose-suite-clean/08-rmdir/app/t-1/rose-app.conf b/t/rose-suite-clean/08-rmdir/app/t-1/rose-app.conf deleted file mode 100644 index 8e971429ed..0000000000 --- a/t/rose-suite-clean/08-rmdir/app/t-1/rose-app.conf +++ /dev/null @@ -1,2 +0,0 @@ -[command] -default=echo 'Hello World' | tee 'hello.txt' "${ROSE_DATA}/hello.txt" "${ROSE_DATAC}/hello.txt" diff --git a/t/rose-suite-clean/08-rmdir/rose-suite.conf b/t/rose-suite-clean/08-rmdir/rose-suite.conf deleted file mode 100644 index b8d6a621dd..0000000000 --- a/t/rose-suite-clean/08-rmdir/rose-suite.conf +++ /dev/null @@ -1,2 +0,0 @@ -root-dir{share/cycle}=*=$ROSE_TEST_ROOT_DIR -root-dir{work}=*=$ROSE_TEST_ROOT_DIR diff --git a/t/rose-suite-clean/08-rmdir/suite.rc b/t/rose-suite-clean/08-rmdir/suite.rc deleted file mode 100644 index 1f4b7ca2fb..0000000000 --- a/t/rose-suite-clean/08-rmdir/suite.rc +++ /dev/null @@ -1,30 +0,0 @@ -#!jinja2 -[cylc] - UTC mode = True - abort if any task fails = True - [[events]] - abort on timeout = True - timeout = PT2M -[scheduling] - initial cycle point = 2020 - final cycle point = 2020 - [[dependencies]] - [[[R1]]] - graph = """ -t-1 -{% if JOB_HOST is defined %} -t-2 -{% endif %} -""" - -[runtime] - [[root]] - script = rose task-run --app-key=t-1 - [[[job]]] - execution time limit = PT1M - [[t-1]] -{% if JOB_HOST is defined %} - [[t-2]] - [[[remote]]] - host = {{JOB_HOST}} -{% endif %} diff --git a/t/rose-suite-clean/09-rmdir-2.t b/t/rose-suite-clean/09-rmdir-2.t deleted file mode 100755 index 51b31ab279..0000000000 --- a/t/rose-suite-clean/09-rmdir-2.t +++ /dev/null @@ -1,88 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-clean", clean up parent directories. -#------------------------------------------------------------------------------- -. "$(dirname "$0")/test_header" - - -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR0="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" -NAME0="$(basename "${SUITE_RUN_DIR0}")" -SUITE_RUN_DIR="${SUITE_RUN_DIR0}/foo/bar" -mkdir -p "${SUITE_RUN_DIR}" -NAME="${NAME0}/foo/bar" - -cp -pr "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" 'source' - -JOB_HOST="$(rose config 't' 'job-host')" -JOB_HOST_RUN_ROOT="$(rose config 't' 'job-host-run-root')" -JOB_HOST_OPT= -if [[ -n "${JOB_HOST}" && -n "${JOB_HOST_RUN_ROOT}" ]]; then - export JOB_HOST="$(rose host-select -q "${JOB_HOST}")" - export JOB_HOST_RUN_ROOT - cat >>'source/rose-suite.conf' <<__CONF__ -root-dir{share/cycle}=${JOB_HOST}=${JOB_HOST_RUN_ROOT} - =*=\${ROSE_TEST_ROOT_DIR} -root-dir{work}=${JOB_HOST}=${JOB_HOST_RUN_ROOT} - =*=\${ROSE_TEST_ROOT_DIR} - -[jinja2:suite.rc] -JOB_HOST="${JOB_HOST}" -__CONF__ - tests 9 -else - tests 5 -fi - -# Run suite, create lots of directories -export CYLC_CONF_PATH= -export ROSE_CONF_PATH= -export ROSE_TEST_ROOT_DIR="${PWD}/root.d" -set -e -rose suite-run --name="${NAME}" -q -C "${PWD}/source" \ - --host='localhost' -- --no-detach --debug -# Prove that the directories exist before clean -test -d "${HOME}/cylc-run/${NAME}" -test -d "${PWD}/root.d/cylc-run/${NAME}" -if [[ -n "${JOB_HOST}" && -n "${JOB_HOST_RUN_ROOT}" ]]; then - SSH='ssh -n -oBatchMode=yes' - ${SSH} "${JOB_HOST}" \ - "bash -l -c 'ls -d cylc-run/${NAME} ${JOB_HOST_RUN_ROOT}/cylc-run/${NAME}'" -fi -set +e - -TEST_KEY="${TEST_KEY_BASE}" -run_pass "${TEST_KEY}" rose suite-clean -y -v -v --debug "${NAME}" -run_fail "${TEST_KEY}-test-d-cylc-run" test -d "${HOME}/cylc-run/${NAME}" -run_fail "${TEST_KEY}-test-d-cylc-run-0" test -d "${HOME}/cylc-run/${NAME0}" -run_fail "${TEST_KEY}-test-d-root-x" test -d "${PWD}/root.d/cylc-run/${NAME}" -run_fail "${TEST_KEY}-test-d-root-x-0" test -d "${PWD}/root.d/cylc-run/${NAME0}" -if [[ -n "${JOB_HOST}" && -n "${JOB_HOST_RUN_ROOT}" ]]; then - run_pass "${TEST_KEY}-test-d-cylc-run-at-${JOB_HOST}" ${SSH} "${JOB_HOST}" \ - "bash -l -c '! test -d cylc-run/${NAME}'" - run_pass "${TEST_KEY}-test-d-cylc-run0-at-${JOB_HOST}" ${SSH} "${JOB_HOST}" \ - "bash -l -c '! test -d cylc-run/${NAME0}'" - run_pass "${TEST_KEY}-test-d-root-at-${JOB_HOST}" ${SSH} "${JOB_HOST}" \ - "bash -l -c '! test -d ${JOB_HOST_RUN_ROOT}/cylc-run/${NAME}'" - run_pass "${TEST_KEY}-test-d-root0-at-${JOB_HOST}" ${SSH} "${JOB_HOST}" \ - "bash -l -c '! test -d ${JOB_HOST_RUN_ROOT}/cylc-run/${NAME0}'" -fi -#------------------------------------------------------------------------------- -exit 0 diff --git a/t/rose-suite-clean/09-rmdir-2/app/t-1/rose-app.conf b/t/rose-suite-clean/09-rmdir-2/app/t-1/rose-app.conf deleted file mode 100644 index 8e971429ed..0000000000 --- a/t/rose-suite-clean/09-rmdir-2/app/t-1/rose-app.conf +++ /dev/null @@ -1,2 +0,0 @@ -[command] -default=echo 'Hello World' | tee 'hello.txt' "${ROSE_DATA}/hello.txt" "${ROSE_DATAC}/hello.txt" diff --git a/t/rose-suite-clean/09-rmdir-2/rose-suite.conf b/t/rose-suite-clean/09-rmdir-2/rose-suite.conf deleted file mode 100644 index b8d6a621dd..0000000000 --- a/t/rose-suite-clean/09-rmdir-2/rose-suite.conf +++ /dev/null @@ -1,2 +0,0 @@ -root-dir{share/cycle}=*=$ROSE_TEST_ROOT_DIR -root-dir{work}=*=$ROSE_TEST_ROOT_DIR diff --git a/t/rose-suite-clean/09-rmdir-2/suite.rc b/t/rose-suite-clean/09-rmdir-2/suite.rc deleted file mode 100644 index 1f4b7ca2fb..0000000000 --- a/t/rose-suite-clean/09-rmdir-2/suite.rc +++ /dev/null @@ -1,30 +0,0 @@ -#!jinja2 -[cylc] - UTC mode = True - abort if any task fails = True - [[events]] - abort on timeout = True - timeout = PT2M -[scheduling] - initial cycle point = 2020 - final cycle point = 2020 - [[dependencies]] - [[[R1]]] - graph = """ -t-1 -{% if JOB_HOST is defined %} -t-2 -{% endif %} -""" - -[runtime] - [[root]] - script = rose task-run --app-key=t-1 - [[[job]]] - execution time limit = PT1M - [[t-1]] -{% if JOB_HOST is defined %} - [[t-2]] - [[[remote]]] - host = {{JOB_HOST}} -{% endif %} diff --git a/t/rose-suite-clean/10-only-7 b/t/rose-suite-clean/10-only-7 deleted file mode 120000 index f8e79ae6d5..0000000000 --- a/t/rose-suite-clean/10-only-7 +++ /dev/null @@ -1 +0,0 @@ -03-only-2 \ No newline at end of file diff --git a/t/rose-suite-clean/10-only-7.t b/t/rose-suite-clean/10-only-7.t deleted file mode 100755 index 56a8798b8e..0000000000 --- a/t/rose-suite-clean/10-only-7.t +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-clean", --only=extglob and a localhost root-dir{*} setting. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - - -run_suite() { - set -e - rose suite-run --new -q \ - -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" --name="$NAME" \ - -- --no-detach --debug - ls -d $HOME/cylc-run/$NAME 1>/dev/null - set +e -} - -#------------------------------------------------------------------------------- -tests 2 -#------------------------------------------------------------------------------- -export CYLC_CONF_PATH= -export ROSE_CONF_PATH=$PWD/conf -export ROOT_DIR_WORK=$PWD/work - -mkdir 'conf' 'work' -cat >'conf/rose.conf' <<'__CONF__' -[rose-suite-run] -root-dir{work}=*=$ROOT_DIR_WORK -__CONF__ - -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -SUITE_RUN_DIR=$(readlink -f "$SUITE_RUN_DIR") -NAME=$(basename $SUITE_RUN_DIR) -#------------------------------------------------------------------------------- -run_suite -TEST_KEY="$TEST_KEY_BASE-work" -run_pass "$TEST_KEY" \ - rose suite-clean -y -n "${NAME}" --only='work/!(20100101T0000Z)' -file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__ -[INFO] delete: localhost:cylc-run/${NAME}/work/20200101T0000Z -[INFO] delete: localhost:cylc-run/${NAME}/work/20150101T0000Z -[INFO] delete: localhost:cylc-run/${NAME}/work/20050101T0000Z -[INFO] delete: localhost:cylc-run/${NAME}/work/20000101T0000Z -__OUT__ -#------------------------------------------------------------------------------- -rose suite-clean -q -y --name="$NAME" -exit 0 diff --git a/t/rose-suite-clean/test_header b/t/rose-suite-clean/test_header deleted file mode 120000 index 90bd5a36f9..0000000000 --- a/t/rose-suite-clean/test_header +++ /dev/null @@ -1 +0,0 @@ -../lib/bash/test_header \ No newline at end of file diff --git a/t/rose-suite-cmp-vc/00-basic.t b/t/rose-suite-cmp-vc/00-basic.t deleted file mode 100755 index c55897c19d..0000000000 --- a/t/rose-suite-cmp-vc/00-basic.t +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Basic tests for "rose suite-cmp-vc" with "svn". -#------------------------------------------------------------------------------- -. "$(dirname "$0")/test_header" - -if ! which svn 1>'/dev/null' 2>&1; then - skip_all '"svn" unavailable' -fi -tests 2 -#------------------------------------------------------------------------------- -export ROSE_CONF_PATH= -svnadmin create 'repos' -svn import -q -m'who cares' --non-interactive --no-auth-cache \ - "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}/" "file://${PWD}/repos" -svn checkout -q --non-interactive --no-auth-cache "file://${PWD}/repos" 'source' -mkdir -p "${HOME}/cylc-run" -RUND="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" -NAME="$(basename "${RUND}")" -rose suite-run -C './source' --debug -q --name="${NAME}" -l - -TEST_KEY="${TEST_KEY_BASE}-run-pass" -run_pass "${TEST_KEY}" rose suite-cmp-vc "${NAME}" - -TEST_KEY="${TEST_KEY_BASE}-run-fail" -sed -i 's/meow/miaow/' './source/suite.rc' -run_fail "${TEST_KEY}" rose suite-cmp-vc "${NAME}" -#------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" -exit diff --git a/t/rose-suite-cmp-vc/00-basic/rose-suite.conf b/t/rose-suite-cmp-vc/00-basic/rose-suite.conf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/t/rose-suite-cmp-vc/00-basic/suite.rc b/t/rose-suite-cmp-vc/00-basic/suite.rc deleted file mode 100644 index 5dd6a9dbe3..0000000000 --- a/t/rose-suite-cmp-vc/00-basic/suite.rc +++ /dev/null @@ -1,6 +0,0 @@ -[scheduling] - [[dependencies]] - graph = meow -[runtime] - [[meow]] - script = nice man cat head tail diff --git a/t/rose-suite-cmp-vc/01-no-vc b/t/rose-suite-cmp-vc/01-no-vc deleted file mode 120000 index 2293ed496a..0000000000 --- a/t/rose-suite-cmp-vc/01-no-vc +++ /dev/null @@ -1 +0,0 @@ -00-basic \ No newline at end of file diff --git a/t/rose-suite-cmp-vc/01-no-vc.t b/t/rose-suite-cmp-vc/01-no-vc.t deleted file mode 100755 index 78c1f80dcb..0000000000 --- a/t/rose-suite-cmp-vc/01-no-vc.t +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Tests for "rose suite-cmp-vc" with VC. -#------------------------------------------------------------------------------- -. "$(dirname "$0")/test_header" -tests 2 -#------------------------------------------------------------------------------- -export ROSE_CONF_PATH= -cp -pr "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}/" 'source' -mkdir -p "${HOME}/cylc-run" -RUND="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" -NAME="$(basename "${RUND}")" -rose suite-run -C './source' --debug -q --name="${NAME}" -l - -TEST_KEY="${TEST_KEY_BASE}" -run_fail "${TEST_KEY}" rose suite-cmp-vc "${NAME}" -file_cmp "${TEST_KEY}.err" "${TEST_KEY}.err" <<__ERR__ -[FAIL] ${NAME}: rose-suite-run.version: VC info not found -__ERR__ -#------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" -exit diff --git a/t/rose-suite-cmp-vc/test_header b/t/rose-suite-cmp-vc/test_header deleted file mode 120000 index 90bd5a36f9..0000000000 --- a/t/rose-suite-cmp-vc/test_header +++ /dev/null @@ -1 +0,0 @@ -../lib/bash/test_header \ No newline at end of file diff --git a/t/rose-suite-log/00-update-task.t b/t/rose-suite-log/00-update-task.t deleted file mode 100755 index b5166e4a77..0000000000 --- a/t/rose-suite-log/00-update-task.t +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-log --update TASK", without site/user configurations. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header -#------------------------------------------------------------------------------- -if [[ $TEST_KEY_BASE == *-remote* ]]; then - JOB_HOST=$(rose config 't' 'job-host') - if [[ -z $JOB_HOST ]]; then - skip_all '"[t]job-host" not defined' - fi - JOB_HOST=$(rose host-select -q $JOB_HOST) -fi -#------------------------------------------------------------------------------- -tests 4 -#------------------------------------------------------------------------------- -# Run the suite. -export CYLC_CONF_PATH= -export ROSE_CONF_PATH= -TEST_KEY=$TEST_KEY_BASE -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) -if [[ -n ${JOB_HOST:-} ]]; then - rose suite-run -q -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost \ - -D "[jinja2:suite.rc]HOST=\"$JOB_HOST\"" -- --no-detach --debug -else - rose suite-run -q -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost -- --no-detach --debug -fi -#------------------------------------------------------------------------------- -TEST_KEY=$TEST_KEY_BASE-before-log.out -if [[ -n ${JOB_HOST:-} ]]; then - run_fail "$TEST_KEY-log.out" \ - test -f $SUITE_RUN_DIR/log/job/1/my_task_2/01/job.out -else - pass "$TEST_KEY-log.out" -fi -TEST_KEY=$TEST_KEY_BASE-command -run_pass "$TEST_KEY" rose suite-log -n $NAME -U 'my_task_2' --debug -file_cmp "$TEST_KEY.err" "$TEST_KEY.err" . -#------------------------------------------------------------------------------- -# Test "rose suite-log --update CYCLE", without site/user configurations. -# Test "rose suite-log --archive CYCLE", without site/user configurations. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header -#------------------------------------------------------------------------------- -if [[ $TEST_KEY_BASE == *-remote* ]]; then - JOB_HOST=$(rose config 't' 'job-host') - if [[ -z $JOB_HOST ]]; then - skip_all '"[t]job-host" not defined' - fi - JOB_HOST=$(rose host-select -q $JOB_HOST) -fi -#------------------------------------------------------------------------------- -tests 14 -#------------------------------------------------------------------------------- -# Run the suite. -export CYLC_CONF_PATH= -export ROSE_CONF_PATH= -TEST_KEY=$TEST_KEY_BASE -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) -if [[ -n ${JOB_HOST:-} ]]; then - run_pass "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost \ - -D "[jinja2:suite.rc]HOST=\"$JOB_HOST\"" -- --no-detach --debug -else - run_pass "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost -- --no-detach --debug -fi -#------------------------------------------------------------------------------- -# Test --archive. -CYCLE=20130101T0000Z -TEST_KEY="$TEST_KEY_BASE-archive-$CYCLE" -(cd $SUITE_RUN_DIR/log; ls job/$CYCLE/*/01/{job,job-activity.log,job.err,job.out,job.xtrace}) \ - >"$TEST_KEY-list-job-logs-before.out" -if [[ -n ${JOB_HOST:-} ]]; then - ssh -oBatchMode=yes $JOB_HOST \ - test -f cylc-run/$NAME/log/job/$CYCLE/my_task_2/01/job.out - ! test -f $SUITE_RUN_DIR/log/job/$CYCLE/my_task_2/01/job.out -fi -# N_JOB_LOGS should be 4, my_task_1 script, err, out and my_task_2 script -N_JOB_LOGS=$(wc -l "$TEST_KEY-list-job-logs-before.out" | cut -d' ' -f1) -run_pass "$TEST_KEY-command" rose suite-log -n $NAME --archive $CYCLE --debug -run_fail "$TEST_KEY-list-job-logs-after" ls $SUITE_RUN_DIR/log/job/$CYCLE/* -if [[ -n ${JOB_HOST:-} ]]; then - ((N_JOB_LOGS += 3)) # my_task_2 job.out, job.err, job.xtrace - run_fail "$TEST_KEY-job-log.out-after-jobhost" \ - ssh -oBatchMode=yes $JOB_HOST \ - test -f cylc-run/$NAME/log/job/$CYCLE/my_task_2/01/job.out -else - pass "$TEST_KEY-job-log.out-after-jobhost" -fi -file_test "$TEST_KEY-tar-exist" $SUITE_RUN_DIR/log/job-$CYCLE.tar.gz -JOB_LOGS_ARCH=$(tar -tzf $SUITE_RUN_DIR/log/job-$CYCLE.tar.gz) -run_pass "$TEST_KEY-after-log.out" \ - grep -q "job/$CYCLE/my_task_2/../job.out" <<<"$JOB_LOGS_ARCH" -N_JOB_LOGS_ARCH=$(echo "$JOB_LOGS_ARCH" | wc -l | cut -d' ' -f1) -run_pass "$TEST_KEY-n-arch" test "${N_JOB_LOGS}" -eq "${N_JOB_LOGS_ARCH}" -file_cmp "$TEST_KEY-command.err" "$TEST_KEY-command.err" . -#------------------------------------------------------------------------------- -# Test "rose suite-log --force", without site/user configurations. -# Test "rose suite-log -U --prune-remote", without site/user configurations. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header -#------------------------------------------------------------------------------- -if [[ $TEST_KEY_BASE == *-remote* ]]; then - JOB_HOST=$(rose config 't' 'job-host') - if [[ -z $JOB_HOST ]]; then - skip_all '"[t]job-host" not defined' - fi - JOB_HOST=$(rose host-select -q $JOB_HOST) -fi -#------------------------------------------------------------------------------- -tests 15 -#------------------------------------------------------------------------------- -# Run the suite. -export ROSE_CONF_PATH= -TEST_KEY=$TEST_KEY_BASE -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) -if [[ -n ${JOB_HOST:-} ]]; then - run_pass "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost \ - -D "[jinja2:suite.rc]HOST=\"$JOB_HOST\"" -- --no-detach --debug -else - run_pass "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost -- --no-detach --debug -fi -#------------------------------------------------------------------------------- -# Test --force. -CYCLES='20130101T0000Z 20130101T1200Z 20130102T0000Z' -for CYCLE in $CYCLES; do - TEST_KEY="$TEST_KEY_BASE-before-$CYCLE" - run_fail "$TEST_KEY" \ - test -f "$HOME/cylc-run/$NAME/log/rose-suite-log-$CYCLE.json" -done -TEST_KEY="$TEST_KEY_BASE-command" -run_pass "$TEST_KEY" rose suite-log -n $NAME -f --debug -file_cmp "$TEST_KEY.err" "$TEST_KEY.err" "$TEST_KEY.out.expected" - file_cmp "$TEST_KEY.out" "$TEST_KEY.out.expected" <<__OUT__ -[INFO] delete: $JOB_HOST:log/job/20130101T0000Z/ -[INFO] delete: $JOB_HOST:log/job/20130101T1200Z/ -__OUT__ - ssh -oBatchMode=yes $JOB_HOST ls "~/cylc-run/$NAME/log/job" \ - | LANG=C sort >"$TEST_KEY.ls" - file_cmp "$TEST_KEY.ls" "$TEST_KEY.ls" <<<'20130102T0000Z' -else - skip 3 "$TEST_KEY: [t]job-host not defined" -fi -#------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME -exit 0 diff --git a/t/rose-suite-log/02-update-force/rose-suite.conf b/t/rose-suite-log/02-update-force/rose-suite.conf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/t/rose-suite-log/02-update-force/suite.rc b/t/rose-suite-log/02-update-force/suite.rc deleted file mode 100644 index a94b6b1e0e..0000000000 --- a/t/rose-suite-log/02-update-force/suite.rc +++ /dev/null @@ -1,23 +0,0 @@ -#!jinja2 -[cylc] - UTC mode = True - abort if any task fails = True - [[events]] - abort on timeout = True - timeout = PT1M -[scheduling] - initial cycle point = 20130101 - final cycle point = 20130102 - [[dependencies]] - [[[T00, T12]]] - graph = my_task_1 & my_task_2 - -[runtime] - [[my_task_1, my_task_2]] - script = echo Hello - [[[job]]] - execution time limit = PT1M -{% if HOST is defined %} - [[[remote]]] - host = {{HOST}} -{% endif %} diff --git a/t/rose-suite-log/03-update-task-remote b/t/rose-suite-log/03-update-task-remote deleted file mode 120000 index a6a559a8cc..0000000000 --- a/t/rose-suite-log/03-update-task-remote +++ /dev/null @@ -1 +0,0 @@ -00-update-task \ No newline at end of file diff --git a/t/rose-suite-log/03-update-task-remote.t b/t/rose-suite-log/03-update-task-remote.t deleted file mode 120000 index 857ea57cc6..0000000000 --- a/t/rose-suite-log/03-update-task-remote.t +++ /dev/null @@ -1 +0,0 @@ -00-update-task.t \ No newline at end of file diff --git a/t/rose-suite-log/04-update-cycle-remote b/t/rose-suite-log/04-update-cycle-remote deleted file mode 120000 index d3d43f00c2..0000000000 --- a/t/rose-suite-log/04-update-cycle-remote +++ /dev/null @@ -1 +0,0 @@ -01-update-cycle \ No newline at end of file diff --git a/t/rose-suite-log/04-update-cycle-remote.t b/t/rose-suite-log/04-update-cycle-remote.t deleted file mode 120000 index 08e1d67a72..0000000000 --- a/t/rose-suite-log/04-update-cycle-remote.t +++ /dev/null @@ -1 +0,0 @@ -01-update-cycle.t \ No newline at end of file diff --git a/t/rose-suite-log/05-update-force-remote b/t/rose-suite-log/05-update-force-remote deleted file mode 120000 index 65ae64cf60..0000000000 --- a/t/rose-suite-log/05-update-force-remote +++ /dev/null @@ -1 +0,0 @@ -02-update-force \ No newline at end of file diff --git a/t/rose-suite-log/05-update-force-remote.t b/t/rose-suite-log/05-update-force-remote.t deleted file mode 120000 index b09108744d..0000000000 --- a/t/rose-suite-log/05-update-force-remote.t +++ /dev/null @@ -1 +0,0 @@ -02-update-force.t \ No newline at end of file diff --git a/t/rose-suite-log/06-archive-star b/t/rose-suite-log/06-archive-star deleted file mode 120000 index d3d43f00c2..0000000000 --- a/t/rose-suite-log/06-archive-star +++ /dev/null @@ -1 +0,0 @@ -01-update-cycle \ No newline at end of file diff --git a/t/rose-suite-log/06-archive-star.t b/t/rose-suite-log/06-archive-star.t deleted file mode 100644 index bd7adea359..0000000000 --- a/t/rose-suite-log/06-archive-star.t +++ /dev/null @@ -1,97 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-log --archive *", without site/user configurations. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - -if [[ $TEST_KEY_BASE == *-remote* ]]; then - JOB_HOST=$(rose config 't' 'job-host') - if [[ -z $JOB_HOST ]]; then - skip_all '"[t]job-host" not defined' - fi - JOB_HOST=$(rose host-select -q $JOB_HOST) -fi -#------------------------------------------------------------------------------- -tests 12 -#------------------------------------------------------------------------------- -# Run the suite. -export ROSE_CONF_PATH= -TEST_KEY=$TEST_KEY_BASE -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) -if [[ -n ${JOB_HOST:-} ]]; then - run_pass "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost \ - -D "[jinja2:suite.rc]HOST=\"$JOB_HOST\"" -- --no-detach --debug -else - run_pass "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost -- --no-detach --debug -fi -#------------------------------------------------------------------------------- -# Test --archive. -TEST_KEY="$TEST_KEY_BASE" - -set -e -(cd $SUITE_RUN_DIR/log/job; ls */*/01/{job,job-activity.log,job.err,job.out,job.xtrace}) \ - >"$TEST_KEY-list-job-logs-before.out" -if [[ -n ${JOB_HOST:-} ]]; then - ssh -oBatchMode=yes $JOB_HOST \ - test -f cylc-run/$NAME/log/job/*/my_task_2/01/job.out - ! test -f $SUITE_RUN_DIR/log/job/*/my_task_2/01/job.out -fi -set +e - -N_JOB_LOGS=$(wc -l "$TEST_KEY-list-job-logs-before.out" | cut -d' ' -f1) -run_pass "$TEST_KEY-command" rose suite-log -n $NAME --archive '*' --debug -run_fail "$TEST_KEY-list-job-logs-after" ls $SUITE_RUN_DIR/log/job/* -if [[ -n ${JOB_HOST:-} ]]; then - ((N_JOB_LOGS += 4)) # job, job.activity.log, job.out, job.err, job.xtrace files - run_fail "$TEST_KEY-job-log.out-after-jobhost" \ - ssh -oBatchMode=yes $JOB_HOST \ - test -f cylc-run/$NAME/log/job/*/my_task_2/01/job.out -else - pass "$TEST_KEY-job-log.out-after-jobhost" -fi -ALL_JOB_LOGS_ARCH= -for CYCLE in 20130101T0000Z 20130101T1200Z 20130102T0000Z; do - file_test "$TEST_KEY-$CYCLE-tar-exist" $SUITE_RUN_DIR/log/job-$CYCLE.tar.gz - JOB_LOGS_ARCH=$(tar -tzf $SUITE_RUN_DIR/log/job-$CYCLE.tar.gz) - if [[ -n $ALL_JOB_LOGS_ARCH ]]; then - ALL_JOB_LOGS_ARCH=$(cat <<__LIST__ -${ALL_JOB_LOGS_ARCH} -${JOB_LOGS_ARCH} -__LIST__ - ) - else - ALL_JOB_LOGS_ARCH="$JOB_LOGS_ARCH" - fi - run_pass "$TEST_KEY-$CYCLE-after-log.out" \ - grep -q "job/$CYCLE/my_task_2/01/job.out" <<<"$JOB_LOGS_ARCH" -done - -N_JOB_LOGS_ARCH=$(echo "$ALL_JOB_LOGS_ARCH" | wc -l | cut -d' ' -f1) -run_pass "$TEST_KEY-n-arch" test "${N_JOB_LOGS}" -eq "${N_JOB_LOGS_ARCH}" - -file_cmp "$TEST_KEY-command.err" "$TEST_KEY-command.err" . -#------------------------------------------------------------------------------- -# Test "rose suite-log" on an uninstalled suite. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - -tests 3 -#------------------------------------------------------------------------------- -TEST_KEY="${TEST_KEY_BASE}" -NAME="$(uuidgen)" # Very unlikely to have a suite of this name installed -run_fail "${TEST_KEY}" rose suite-log "--name=${NAME}" -file_cmp "${TEST_KEY}.out" "${TEST_KEY}.out" . -#------------------------------------------------------------------------------- -# Test "rose suite-restart" on suites that don't exist. -#------------------------------------------------------------------------------- -. "$(dirname "$0")/test_header" - -tests 7 -#------------------------------------------------------------------------------- -TEST_KEY="${TEST_KEY_BASE}-pwd" -ROSE_CONF_PATH= run_fail "${TEST_KEY}" rose suite-restart -file_cmp "${TEST_KEY}.err" "${TEST_KEY}.err" <<__ERR__ -[FAIL] ${PWD} - no suite found for this path. -__ERR__ -#------------------------------------------------------------------------------- -TEST_KEY="${TEST_KEY_BASE}-name-uuid" -NAME="$(uuidgen)" -ROSE_CONF_PATH= run_fail "${TEST_KEY}" rose suite-restart -n "${NAME}" -file_cmp "${TEST_KEY}.err" "${TEST_KEY}.err" <<__ERR__ -[FAIL] ${HOME}/cylc-run/${NAME} - no suite found for this path. -__ERR__ -#------------------------------------------------------------------------------- -TEST_KEY="${TEST_KEY_BASE}-name-uuid-installed" -NAME="$(uuidgen)" -mkdir -p "${HOME}/cylc-run/${NAME}" -ROSE_CONF_PATH= run_fail "${TEST_KEY}" rose suite-restart -n "${NAME}" \ - -- --no-detach --debug -# N.B. This relies on output of "cylc restart" -head -1 "${TEST_KEY}.err" >"${TEST_KEY}.err.head" -file_cmp "${TEST_KEY}.err.head" "${TEST_KEY}.err.head" <<__ERR__ -[FAIL] cylc restart ${NAME} --no-detach --debug # return-code=1, stderr= -__ERR__ -file_grep "${TEST_KEY}.err.grep"\ - "cylc.flow.exceptions.SuiteServiceFileError: no suite.rc in ${HOME}/cylc-run/${NAME}" \ - "${TEST_KEY}.err" -rm -fr "${HOME}/cylc-run/${NAME}" -#------------------------------------------------------------------------------- -exit diff --git a/t/rose-suite-restart/01-running.t b/t/rose-suite-restart/01-running.t deleted file mode 100755 index b7dbd1221f..0000000000 --- a/t/rose-suite-restart/01-running.t +++ /dev/null @@ -1,74 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-restart" on suites that are still running. -#------------------------------------------------------------------------------- -. "$(dirname "$0")/test_header" - -tests 4 -#------------------------------------------------------------------------------- -export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" -SUITE_RUN_DIR="$(readlink -f ${SUITE_RUN_DIR})" -NAME="$(basename "${SUITE_RUN_DIR}")" -timeout 120 rose suite-run --debug -q \ - -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - -- --no-detach & -ROSE_SUITE_RUN_PID=$! -CONTACT="${HOME}/cylc-run/${NAME}/.service/contact" -poll ! test -e "${CONTACT}" -poll ! test -e "${HOME}/cylc-run/${NAME}/log/job/1/foo/NN/job.status" -SUITE_HOST="$(sed -n 's/CYLC_SUITE_HOST=//p' "${CONTACT}")" -SUITE_OWNER="$(sed -n 's/CYLC_SUITE_OWNER=//p' "${CONTACT}")" -SUITE_PORT="$(sed -n 's/CYLC_SUITE_PORT=//p' "${CONTACT}")" -SUITE_PROCESS="$(sed -n 's/CYLC_SUITE_PROCESS=//p' "${CONTACT}")" - -TEST_KEY="${TEST_KEY_BASE}-name" -run_fail "${TEST_KEY}" rose suite-restart --name="${NAME}" -# Note: Error file CYLC_SUITE_* lines contain \t characters -file_cmp "${TEST_KEY}.err" "${TEST_KEY}.err" <<__ERR__ -[FAIL] Suite "${NAME}" appears to be running: -[FAIL] Contact info from: "${CONTACT}" -[FAIL] CYLC_SUITE_HOST=${SUITE_HOST} -[FAIL] CYLC_SUITE_OWNER=${SUITE_OWNER} -[FAIL] CYLC_SUITE_PORT=${SUITE_PORT} -[FAIL] CYLC_SUITE_PROCESS=${SUITE_PROCESS} -[FAIL] Try "cylc stop '${NAME}'" first? -__ERR__ - -TEST_KEY="${TEST_KEY_BASE}-cwd" -run_fail "${TEST_KEY}" bash -c "cd '${SUITE_RUN_DIR}'; rose suite-restart" -# Note: Error file CYLC_SUITE_* lines contain \t characters -file_cmp "${TEST_KEY}.err" "${TEST_KEY}.err" <<__ERR__ -[FAIL] Suite "${NAME}" appears to be running: -[FAIL] Contact info from: "${CONTACT}" -[FAIL] CYLC_SUITE_HOST=${SUITE_HOST} -[FAIL] CYLC_SUITE_OWNER=${SUITE_OWNER} -[FAIL] CYLC_SUITE_PORT=${SUITE_PORT} -[FAIL] CYLC_SUITE_PROCESS=${SUITE_PROCESS} -[FAIL] Try "cylc stop '${NAME}'" first? -__ERR__ -#------------------------------------------------------------------------------- -rm -f "${SUITE_RUN_DIR}/work/1/foo/file" -wait "${ROSE_SUITE_RUN_PID}" -poll test -e "${CONTACT}" -rm -f "${CONTACT}" # In case suite takes long time and is killed by timeout -rose suite-clean -q -y "${NAME}" -exit diff --git a/t/rose-suite-restart/01-running/rose-suite.conf b/t/rose-suite-restart/01-running/rose-suite.conf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/t/rose-suite-restart/01-running/suite.rc b/t/rose-suite-restart/01-running/suite.rc deleted file mode 100644 index aea89f692b..0000000000 --- a/t/rose-suite-restart/01-running/suite.rc +++ /dev/null @@ -1,9 +0,0 @@ -[scheduling] - [[dependencies]] - graph = foo -[runtime] - [[foo]] - script = """ -touch 'file' -timeout 60 bash -c 'while test -e "file"; do sleep 1; done' -""" diff --git a/t/rose-suite-restart/02-basic.t b/t/rose-suite-restart/02-basic.t deleted file mode 100755 index 28654663e4..0000000000 --- a/t/rose-suite-restart/02-basic.t +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-restart", basic usage. -#------------------------------------------------------------------------------- -. "$(dirname "$0")/test_header" - -tests 2 -#------------------------------------------------------------------------------- -export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" -NAME="$(basename "${SUITE_RUN_DIR}")" -rose suite-run --debug -q \ - -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - -- --no-detach --debug - -TEST_KEY="${TEST_KEY_BASE}" -run_pass "${TEST_KEY}" \ - rose suite-restart --debug --name="${NAME}" \ - -- --no-detach --debug -# N.B. This relies on output from "cylc restart" -file_grep "${TEST_KEY}.log" \ - "\\[jobs-submit cmd\\] cylc jobs-submit --debug -- ${SUITE_RUN_DIR}/log/job 1/t2/01" \ - "${SUITE_RUN_DIR}/log/suite/log" -sed -i '/no HTTPS.* support/d' "$TEST_KEY.err" -#------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" -exit diff --git a/t/rose-suite-restart/02-basic/rose-suite.conf b/t/rose-suite-restart/02-basic/rose-suite.conf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/t/rose-suite-restart/02-basic/suite.rc b/t/rose-suite-restart/02-basic/suite.rc deleted file mode 100644 index 757f4bfa5f..0000000000 --- a/t/rose-suite-restart/02-basic/suite.rc +++ /dev/null @@ -1,8 +0,0 @@ -[scheduling] - [[dependencies]] - graph = "t1 => t2" -[runtime] - [[t1]] - script = cylc stop "${CYLC_SUITE_NAME}" - [[t2]] - script = true diff --git a/t/rose-suite-restart/03-host b/t/rose-suite-restart/03-host deleted file mode 120000 index 696ef1d9f8..0000000000 --- a/t/rose-suite-restart/03-host +++ /dev/null @@ -1 +0,0 @@ -02-basic \ No newline at end of file diff --git a/t/rose-suite-restart/03-host.t b/t/rose-suite-restart/03-host.t deleted file mode 100755 index 243203fb36..0000000000 --- a/t/rose-suite-restart/03-host.t +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-restart", --host=HOST option. -#------------------------------------------------------------------------------- -. "$(dirname "$0")/test_header" - - -HOSTS=$(rose config --default= 'rose-suite-run' 'hosts') -HOST= -if [[ -n "${HOSTS}" ]]; then - HOST="$(rose host-select -q "${HOSTS}")" -fi -if [[ -z "${HOST}" ]]; then - skip_all '"[rose-suite-run]hosts" not defined or no suite host available' -fi -tests 3 -#------------------------------------------------------------------------------- -export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" -NAME="$(basename "${SUITE_RUN_DIR}")" -rose suite-run --debug -q -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --name="${NAME}" --host="${HOST}" \ - -- --no-detach --debug - -TEST_KEY="${TEST_KEY_BASE}" -run_pass "${TEST_KEY}" rose suite-restart \ - -v -v --debug --name="${NAME}" --host="${HOST}" \ - -- --no-detach --debug -file_grep "${TEST_KEY}.out" \ - "cylc restart --host=${HOST} ${NAME} --no-detach --debug" "${TEST_KEY}.out" -# N.B. This relies on output from "cylc restart" -file_grep "${TEST_KEY}.log" \ - "\\[jobs-submit cmd\\] cylc jobs-submit --debug -- ${SUITE_RUN_DIR}/log/job 1/t2/01" \ - "${SUITE_RUN_DIR}/log/suite/log" -#------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" -exit diff --git a/t/rose-suite-restart/test_header b/t/rose-suite-restart/test_header deleted file mode 120000 index 90bd5a36f9..0000000000 --- a/t/rose-suite-restart/test_header +++ /dev/null @@ -1 +0,0 @@ -../lib/bash/test_header \ No newline at end of file diff --git a/t/rose-suite-run/00-run-basic.t b/t/rose-suite-run/00-run-basic.t deleted file mode 100755 index ea8596308f..0000000000 --- a/t/rose-suite-run/00-run-basic.t +++ /dev/null @@ -1,105 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run", with and without site/user configurations. -#------------------------------------------------------------------------------- -. "$(dirname "$0")/test_header" -#------------------------------------------------------------------------------- -# Run the suite. -if [[ "${TEST_KEY_BASE}" == *conf ]]; then - if ! rose config -q 'rose-suite-run' 'hosts'; then - skip_all '"[rose-suite-run]hosts" not defined' - fi -else - export ROSE_CONF_PATH= -fi - -get_host_fqdn() { - python3 - "$@" <<'__PYTHON__' -import socket -import sys -sys.stdout.write(socket.gethostbyname_ex(sys.argv[1])[0] + "\n") -__PYTHON__ -} -#------------------------------------------------------------------------------- -tests 11 -#------------------------------------------------------------------------------- -TEST_KEY="${TEST_KEY_BASE}" -mkdir -p "${HOME}/cylc-run" -RUND="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" -NAME="$(basename "${RUND}")" -run_pass "${TEST_KEY}" \ - rose suite-run -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - -CONTACT="${HOME}/cylc-run/${NAME}/.service/contact" -SUITE_HOST="$(sed -n 's/CYLC_SUITE_HOST=//p' "${CONTACT}")" -SUITE_OWNER="$(sed -n 's/CYLC_SUITE_OWNER=//p' "${CONTACT}")" -SUITE_PORT="$(sed -n 's/CYLC_SUITE_PORT=//p' "${CONTACT}")" -SUITE_PROCESS="$(sed -n 's/CYLC_SUITE_PROCESS=//p' "${CONTACT}")" -poll ! test -e "${RUND}/log/job/20130101T0000Z/my_task_1/01" -#------------------------------------------------------------------------------- -# "rose suite-run" should not work while suite is running. -# except --reload mode. -for OPTION in -i -l '' --restart; do - TEST_KEY="${TEST_KEY_BASE}-running${OPTION}" - run_fail "${TEST_KEY}" rose suite-run "${OPTION}" \ - -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" - file_cmp "${TEST_KEY}.err" "${TEST_KEY}.err" <<__ERR__ -[FAIL] Suite "${NAME}" appears to be running: -[FAIL] Contact info from: "${CONTACT}" -[FAIL] CYLC_SUITE_HOST=${SUITE_HOST} -[FAIL] CYLC_SUITE_OWNER=${SUITE_OWNER} -[FAIL] CYLC_SUITE_PORT=${SUITE_PORT} -[FAIL] CYLC_SUITE_PROCESS=${SUITE_PROCESS} -[FAIL] Try "cylc stop '${NAME}'" first? -__ERR__ -done -# Don't reload until tasks begin -TIMEOUT=$(($(date +%s) + 60)) # wait 1 minute -while (($(date +%s) < TIMEOUT)) && ! ( - cd "${RUND}/log/job/" - test -f "20130101T0000Z/my_task_1/01/job.out" && test -f "20130101T1200Z/my_task_1/01/job.out" -) -do - sleep 1 -done -TEST_KEY="${TEST_KEY_BASE}-running-reload" -run_pass "${TEST_KEY}" rose suite-run --reload \ - -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --debug -sleep 1 -#------------------------------------------------------------------------------- -# Wait for the suite to complete -TEST_KEY="${TEST_KEY_BASE}-suite-run-wait" -touch "${RUND}/flag" # allow the task to die -TIMEOUT=$(($(date +%s) + 60)) # wait 1 minute -CONTACT="${HOME}/cylc-run/${NAME}/.service/contact" -while [[ -e "${CONTACT}" ]] && (($(date +%s) < TIMEOUT)) -do - sleep 1 -done -if [[ -e "${CONTACT}" ]]; then - fail "${TEST_KEY}" - exit 1 -else - pass "${TEST_KEY}" -fi -#------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" -exit 0 diff --git a/t/rose-suite-run/00-run-basic/rose-suite.conf b/t/rose-suite-run/00-run-basic/rose-suite.conf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/t/rose-suite-run/00-run-basic/suite.rc b/t/rose-suite-run/00-run-basic/suite.rc deleted file mode 100644 index 3a7bf8fd74..0000000000 --- a/t/rose-suite-run/00-run-basic/suite.rc +++ /dev/null @@ -1,32 +0,0 @@ -#!jinja2 -[cylc] - UTC mode=True - abort if any task fails = True - [[events]] - abort on timeout = True - timeout=PT2M -[scheduling] - initial cycle point=20130101T00Z - final cycle point=20130102T12Z - [[dependencies]] - [[[T00, T12]]] - graph=my_task_1 - -[runtime] - [[my_task_1]] - script = """ -I=0 -OLD_I= -while [[ ! -e $CYLC_SUITE_RUN_DIR/flag ]]; do - sleep 1 - ((++I)) - if [[ -n $OLD_I ]]; then - echo -n $(tr '[:print:]' '\b' <<<"$OLD_I") - fi - echo -n $I - OLD_I=$I -done -echo -""" - [[[job]]] - execution time limit = PT1M diff --git a/t/rose-suite-run/01-run-basic-conf b/t/rose-suite-run/01-run-basic-conf deleted file mode 120000 index 6907e44de1..0000000000 --- a/t/rose-suite-run/01-run-basic-conf +++ /dev/null @@ -1 +0,0 @@ -00-run-basic \ No newline at end of file diff --git a/t/rose-suite-run/01-run-basic-conf.t b/t/rose-suite-run/01-run-basic-conf.t deleted file mode 120000 index a0a623ac5f..0000000000 --- a/t/rose-suite-run/01-run-basic-conf.t +++ /dev/null @@ -1 +0,0 @@ -00-run-basic.t \ No newline at end of file diff --git a/t/rose-suite-run/02-install.t b/t/rose-suite-run/02-install.t deleted file mode 100755 index 9837a5675e..0000000000 --- a/t/rose-suite-run/02-install.t +++ /dev/null @@ -1,108 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run", with and without site/user configurations. -#------------------------------------------------------------------------------- -. "$(dirname "$0")/test_header" - -#------------------------------------------------------------------------------- -JOB_HOST="$(rose config --default= 't' 'job-host')" -if [[ -n "${JOB_HOST}" ]]; then - JOB_HOST="$(rose host-select -q "${JOB_HOST}")" -fi -#------------------------------------------------------------------------------- -if [[ "${TEST_KEY_BASE}" == *conf ]]; then - if ! rose config -q 'rose-suite-run' 'hosts'; then - skip_all '"[rose-suite-run]hosts" not defined' - fi -else - export ROSE_CONF_PATH= -fi -#------------------------------------------------------------------------------- -tests 6 -#------------------------------------------------------------------------------- -TEST_KEY="${TEST_KEY_BASE}" -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" -NAME="$(basename "${SUITE_RUN_DIR}")" -case "${TEST_KEY_BASE}" in - *local*) - OPTION='-l';; - *validate*) - OPTION='--validate-suite-only';; - *host-localhost*) - OPTION='-i' - JOB_HOST=$HOSTNAME;; - *) - OPTION='-i';; -esac -mkdir "${TEST_KEY_BASE}" -cp -pr "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}/"* "${TEST_KEY_BASE}" -touch "${TEST_KEY_BASE}/colon:is:ok" -if [[ -n "${JOB_HOST}" ]]; then - run_pass "${TEST_KEY}" rose suite-run --debug \ - -C "${TEST_KEY_BASE}" "${OPTION}" \ - --name="${NAME}" \ - -S "HOST=\"${JOB_HOST}\"" -else - run_pass "${TEST_KEY}" rose suite-run --debug \ - -C "${TEST_KEY_BASE}" "${OPTION}" \ - --name="${NAME}" -fi -#------------------------------------------------------------------------------- -TEST_KEY="${TEST_KEY_BASE}-port-file" -run_fail "${TEST_KEY}" test -e "${HOME}/cylc-run/${NAME}/.service/contact" -#------------------------------------------------------------------------------- -TEST_KEY="${TEST_KEY_BASE}-items" -_run_mode=run_pass -if [[ "${TEST_KEY_BASE}" == *validate* ]]; then - _run_mode=run_fail -fi -"$_run_mode" "${TEST_KEY}" find "${SUITE_RUN_DIR}/"{app,colon:is:ok,etc} -type f -if [[ "${TEST_KEY_BASE}" != *validate* ]]; then - sort "${TEST_KEY}.out" >"${TEST_KEY}.out.sort" - file_cmp "${TEST_KEY}.out" "${TEST_KEY}.out.sort" <<__OUT__ -${SUITE_RUN_DIR}/app/my_task_1/rose-app.conf -${SUITE_RUN_DIR}/app/my_task_2/rose-app.conf -${SUITE_RUN_DIR}/colon:is:ok -${SUITE_RUN_DIR}/etc/junk -__OUT__ -fi -#------------------------------------------------------------------------------- -TEST_KEY="${TEST_KEY_BASE}-items-${JOB_HOST}" -if [[ "${TEST_KEY_BASE}" == *local* ]]; then - skip 2 "${TEST_KEY}: local-install-only" -elif [[ "${TEST_KEY_BASE}" == *validate* ]]; then - skip 3 "${TEST_KEY}: validate-suite-only" -elif [[ -n "${JOB_HOST}" ]]; then - run_pass "${TEST_KEY}" \ - ssh -oBatchMode=yes "${JOB_HOST}" \ - "find 'cylc-run/${NAME}/'{app,colon:is:ok,etc} -type f" - sort "${TEST_KEY}.out" >"${TEST_KEY}.out.sort" - file_cmp "${TEST_KEY}.out" "${TEST_KEY}.out.sort" <<__OUT__ -cylc-run/${NAME}/app/my_task_1/rose-app.conf -cylc-run/${NAME}/colon:is:ok -cylc-run/${NAME}/etc/junk -__OUT__ -else - skip 2 "${TEST_KEY_BASE}-items: [t]job-host not defined" -fi -#------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" -exit 0 diff --git a/t/rose-suite-run/02-install/app/my_task_1/rose-app.conf b/t/rose-suite-run/02-install/app/my_task_1/rose-app.conf deleted file mode 100644 index 7521154cea..0000000000 --- a/t/rose-suite-run/02-install/app/my_task_1/rose-app.conf +++ /dev/null @@ -1,2 +0,0 @@ -[command] -default=echo Hello World diff --git a/t/rose-suite-run/02-install/app/my_task_2/rose-app.conf b/t/rose-suite-run/02-install/app/my_task_2/rose-app.conf deleted file mode 100644 index a83a2918ff..0000000000 --- a/t/rose-suite-run/02-install/app/my_task_2/rose-app.conf +++ /dev/null @@ -1,2 +0,0 @@ -[command] -default=echo Hi World diff --git a/t/rose-suite-run/02-install/etc/junk b/t/rose-suite-run/02-install/etc/junk deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/t/rose-suite-run/02-install/rose-suite.conf b/t/rose-suite-run/02-install/rose-suite.conf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/t/rose-suite-run/02-install/suite.rc b/t/rose-suite-run/02-install/suite.rc deleted file mode 100644 index e71093f4d4..0000000000 --- a/t/rose-suite-run/02-install/suite.rc +++ /dev/null @@ -1,29 +0,0 @@ -#!jinja2 -[cylc] - UTC mode = True - abort if any task fails = True - [[events]] - abort on timeout = True - timeout = PT2M -[scheduling] - initial cycle point = 20130101 - final cycle point = 20130102 - [[dependencies]] - [[[T00, T12]]] - graph = my_task_1 & my_task_2 - -[runtime] - [[root]] - script = rose task-run - [[[job]]] - execution time limit = PT1M - [[my_task_1]] -{% if HOST is defined %} - [[[remote]]] - host = {{HOST}} -{% endif %} - [[my_task_2]] -{% if HOST is defined %} - [[[remote]]] - host = $(echo {{HOST}}) -{% endif %} diff --git a/t/rose-suite-run/03-install-conf b/t/rose-suite-run/03-install-conf deleted file mode 120000 index e1b7ad15e0..0000000000 --- a/t/rose-suite-run/03-install-conf +++ /dev/null @@ -1 +0,0 @@ -02-install \ No newline at end of file diff --git a/t/rose-suite-run/03-install-conf.t b/t/rose-suite-run/03-install-conf.t deleted file mode 120000 index 3d0cca2073..0000000000 --- a/t/rose-suite-run/03-install-conf.t +++ /dev/null @@ -1 +0,0 @@ -02-install.t \ No newline at end of file diff --git a/t/rose-suite-run/04-install-local b/t/rose-suite-run/04-install-local deleted file mode 120000 index e1b7ad15e0..0000000000 --- a/t/rose-suite-run/04-install-local +++ /dev/null @@ -1 +0,0 @@ -02-install \ No newline at end of file diff --git a/t/rose-suite-run/04-install-local.t b/t/rose-suite-run/04-install-local.t deleted file mode 120000 index 3d0cca2073..0000000000 --- a/t/rose-suite-run/04-install-local.t +++ /dev/null @@ -1 +0,0 @@ -02-install.t \ No newline at end of file diff --git a/t/rose-suite-run/05-log.t b/t/rose-suite-run/05-log.t deleted file mode 100755 index 637fd5290a..0000000000 --- a/t/rose-suite-run/05-log.t +++ /dev/null @@ -1,86 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test --*-log-* options of "rose suite-run". -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - - -#------------------------------------------------------------------------------- -tests 23 -export ROSE_CONF_PATH= -#------------------------------------------------------------------------------- -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) -ROSE_SUITE_RUN="rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE" -ROSE_SUITE_RUN="$ROSE_SUITE_RUN --name=$NAME" -ROSE_SUITE_RUN="$ROSE_SUITE_RUN " - -N_RUNS=6 -I_KEEP=$((RANDOM % N_RUNS)) # 0 to N_RUNS - 1 -I_NO_LOG_ARCHIVE=$((RANDOM % N_RUNS + 1)) -while ((I_KEEP == I_NO_LOG_ARCHIVE)); do - I_NO_LOG_ARCHIVE=$((RANDOM % N_RUNS + 1)) -done -OLD_LOG= -KEPT_LOG= -for I in $(seq 0 $N_RUNS); do - # Run the suite - TEST_KEY=$TEST_KEY_BASE-$I - if ((I == I_KEEP)); then - run_pass "$TEST_KEY-keep" $ROSE_SUITE_RUN --log-name=keep \ - -- --no-detach --debug - elif ((I == I_NO_LOG_ARCHIVE)); then - run_pass "$TEST_KEY-no-log-archive" \ - $ROSE_SUITE_RUN --no-log-archive -- --no-detach --debug - else - run_pass "$TEST_KEY" $ROSE_SUITE_RUN -- --no-detach --debug - fi - - TEST_KEY=$TEST_KEY_BASE-$I-log - file_test "$TEST_KEY" $SUITE_RUN_DIR/log -L - if ((I == I_KEEP)); then - file_test "$TEST_KEY-keep" $SUITE_RUN_DIR/log.keep -L - KEPT_LOG=$(readlink $SUITE_RUN_DIR/log.keep) - fi - if ((I - 1 == I_KEEP)) || ((I == I_NO_LOG_ARCHIVE)); then - file_test "$TEST_KEY-old-kept" $SUITE_RUN_DIR/$OLD_LOG - elif [[ -n $OLD_LOG ]]; then - file_test "$TEST_KEY-old" $SUITE_RUN_DIR/$OLD_LOG.tar.gz - fi - OLD_LOG=$(readlink $SUITE_RUN_DIR/log) -done - -TEST_KEY=$TEST_KEY_BASE-log-keep-0 -run_pass "$TEST_KEY" $ROSE_SUITE_RUN --log-keep=0 -- --no-detach --debug - -TEST_KEY=$TEST_KEY_BASE-log-keep-0-ls -ls -d $SUITE_RUN_DIR/log* >$TEST_KEY.out -THIS_LOG=$(readlink $SUITE_RUN_DIR/log) -file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__ -$SUITE_RUN_DIR/log -$SUITE_RUN_DIR/$KEPT_LOG -$SUITE_RUN_DIR/$THIS_LOG -$SUITE_RUN_DIR/log.keep -__OUT__ - -#------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME -exit 0 diff --git a/t/rose-suite-run/05-log/rose-suite.conf b/t/rose-suite-run/05-log/rose-suite.conf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/t/rose-suite-run/05-log/suite.rc b/t/rose-suite-run/05-log/suite.rc deleted file mode 100644 index 8aaeb128b9..0000000000 --- a/t/rose-suite-run/05-log/suite.rc +++ /dev/null @@ -1,16 +0,0 @@ -#!jinja2 -[cylc] - UTC mode = True - [[events]] - abort on timeout = True - timeout = PT1M -[scheduling] - initial cycle point = 20130101T00Z - final cycle point = 20130101T00Z - [[dependencies]] - [[[T00]]] - graph = my_task_1 - -[runtime] - [[my_task_1]] - script = true diff --git a/t/rose-suite-run/06-opt-conf.t b/t/rose-suite-run/06-opt-conf.t deleted file mode 100755 index 0808a310ba..0000000000 --- a/t/rose-suite-run/06-opt-conf.t +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run -O KEY". -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - - -#------------------------------------------------------------------------------- -tests 6 -export ROSE_CONF_PATH= -#------------------------------------------------------------------------------- -cat tests -mkdir -p $HOME/cylc-run -for OPT_KEY in world earth neutron-star; do - SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') - echo "$OPT_KEY $SUITE_RUN_DIR" >>tests -done -# Run the suites -while read OPT_KEY SUITE_RUN_DIR; do - NAME=$(basename $SUITE_RUN_DIR) - ROSE_SUITE_RUN="rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE" - ROSE_SUITE_RUN="$ROSE_SUITE_RUN --name=$NAME " - if [[ $OPT_KEY != 'world' ]]; then - ROSE_SUITE_RUN="$ROSE_SUITE_RUN -O $OPT_KEY" - fi - ROSE_SUITE_RUN="$ROSE_SUITE_RUN -- --no-detach --debug" - TEST_KEY=$TEST_KEY_BASE-$OPT_KEY - run_pass "$TEST_KEY" $ROSE_SUITE_RUN -done $CYLC_TASK_LOG_ROOT.txt""" diff --git a/t/rose-suite-run/09-pgrep-conf b/t/rose-suite-run/09-pgrep-conf deleted file mode 120000 index 6851810496..0000000000 --- a/t/rose-suite-run/09-pgrep-conf +++ /dev/null @@ -1 +0,0 @@ -08-pgrep \ No newline at end of file diff --git a/t/rose-suite-run/09-pgrep-conf.t b/t/rose-suite-run/09-pgrep-conf.t deleted file mode 120000 index d8388f0486..0000000000 --- a/t/rose-suite-run/09-pgrep-conf.t +++ /dev/null @@ -1 +0,0 @@ -08-pgrep.t \ No newline at end of file diff --git a/t/rose-suite-run/10-import-1.t b/t/rose-suite-run/10-import-1.t deleted file mode 100755 index a8c4ac22e7..0000000000 --- a/t/rose-suite-run/10-import-1.t +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run", import. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - -tests 5 -#------------------------------------------------------------------------------- -export ROSE_CONF_PATH= -#------------------------------------------------------------------------------- -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) -#------------------------------------------------------------------------------- -# Install the "hello_earth" suite -TEST_KEY="$TEST_KEY_BASE-local-install" -run_pass "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE/hello_earth -n $NAME -l -(cd ~/cylc-run/$NAME; find app bin -type f | LANG=C sort) >"$TEST_KEY.find" -file_cmp "$TEST_KEY.find" "$TEST_KEY.find" <<'__FIND__' -app/hello/rose-app.conf -bin/my-hello -__FIND__ -{ - CONF=$HOME/cylc-run/$NAME/log/rose-suite-run.conf - rose config -f $CONF jinja2:suite.rc hello - rose config -f $CONF jinja2:suite.rc worlds -} >"$TEST_KEY.conf" -file_cmp "$TEST_KEY.conf" "$TEST_KEY.conf" <<'__FIND__' -"Hello" -["Earth", "Moon"] -__FIND__ -#------------------------------------------------------------------------------- -# Start the "hello_earth" suite -TEST_KEY="$TEST_KEY_BASE-suite-run" -run_pass "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE/hello_earth \ - -n $NAME -- --no-detach --debug -#------------------------------------------------------------------------------- -TEST_KEY="$TEST_KEY_BASE-suite-run-my-hello.log" -LANG=C sort $SUITE_RUN_DIR/my-hello.log >"$TEST_KEY" -file_cmp "$TEST_KEY" "$TEST_KEY" <<'__LOG__' -[20130101T0000Z] Hello Earth -[20130101T0000Z] Hello Moon -[20130101T1200Z] Hello Earth -[20130101T1200Z] Hello Moon -[20130102T0000Z] Hello Earth -[20130102T0000Z] Hello Moon -__LOG__ -#------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME -exit 0 diff --git a/t/rose-suite-run/10-import-1/hello/app/hello/rose-app.conf b/t/rose-suite-run/10-import-1/hello/app/hello/rose-app.conf deleted file mode 100644 index acfcc551f6..0000000000 --- a/t/rose-suite-run/10-import-1/hello/app/hello/rose-app.conf +++ /dev/null @@ -1,2 +0,0 @@ -[command] -default=my-hello diff --git a/t/rose-suite-run/10-import-1/hello/bin/my-hello b/t/rose-suite-run/10-import-1/hello/bin/my-hello deleted file mode 100755 index 274565695a..0000000000 --- a/t/rose-suite-run/10-import-1/hello/bin/my-hello +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -set -eu -echo "[$ROSE_TASK_CYCLE_TIME] ${HELLO:-hello} ${ROSE_TASK_NAME#hello_}" \ - | tee -a $ROSE_SUITE_DIR/$(basename $0).log diff --git a/t/rose-suite-run/10-import-1/hello/rose-suite.conf b/t/rose-suite-run/10-import-1/hello/rose-suite.conf deleted file mode 100644 index 0b399a1463..0000000000 --- a/t/rose-suite-run/10-import-1/hello/rose-suite.conf +++ /dev/null @@ -1,2 +0,0 @@ -[jinja2:suite.rc] -hello="Hello" diff --git a/t/rose-suite-run/10-import-1/hello/suite.rc b/t/rose-suite-run/10-import-1/hello/suite.rc deleted file mode 100644 index 54b42898d2..0000000000 --- a/t/rose-suite-run/10-import-1/hello/suite.rc +++ /dev/null @@ -1,28 +0,0 @@ -#!jinja2 -[cylc] - UTC mode=True - abort if any task fails = True - [[events]] - abort on timeout = True - timeout=PT2M -[scheduling] - initial cycle point=20130101 - final cycle point=20130102 - [[dependencies]] - [[[T00, T12]]] - graph = """ -{% for world in worlds|default(["worlds"]) -%} -hello_{{world}} -{% endfor -%} -""" - -[runtime] - [[root]] - script=rose task-run --debug --app-key=hello - [[[environment]]] - HELLO={{hello|default("hello")}} - [[[job]]] - execution time limit = PT1M -{% for world in worlds -%} - [[hello_{{world}}]] -{% endfor -%} diff --git a/t/rose-suite-run/10-import-1/hello_earth/rose-suite.conf b/t/rose-suite-run/10-import-1/hello_earth/rose-suite.conf deleted file mode 100644 index a9e2a87c1c..0000000000 --- a/t/rose-suite-run/10-import-1/hello_earth/rose-suite.conf +++ /dev/null @@ -1,4 +0,0 @@ -import=hello - -[jinja2:suite.rc] -worlds=["Earth", "Moon"] diff --git a/t/rose-suite-run/11-import-2.t b/t/rose-suite-run/11-import-2.t deleted file mode 100755 index 2f5d111109..0000000000 --- a/t/rose-suite-run/11-import-2.t +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run", multiple imports. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - -tests 5 -#------------------------------------------------------------------------------- -export ROSE_CONF_PATH= -#------------------------------------------------------------------------------- -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) -#------------------------------------------------------------------------------- -# Install the "greet_earth" suite -TEST_KEY="$TEST_KEY_BASE-local-install" -run_pass "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE/greet_earth -n $NAME -l -(cd ~/cylc-run/$NAME; find app bin -type f | LANG=C sort) >"$TEST_KEY.find" -file_cmp "$TEST_KEY.find" "$TEST_KEY.find" <<'__FIND__' -app/hello/rose-app.conf -bin/my-hello -__FIND__ -{ - CONF=$HOME/cylc-run/$NAME/log/rose-suite-run.conf - rose config -f $CONF jinja2:suite.rc hello - rose config -f $CONF jinja2:suite.rc worlds -} >"$TEST_KEY.conf" -file_cmp "$TEST_KEY.conf" "$TEST_KEY.conf" <<'__FIND__' -"Greet" -["Earth", "Moon"] -__FIND__ -#------------------------------------------------------------------------------- -# Start the "greet_earth" suite -TEST_KEY="$TEST_KEY_BASE-suite-run" -run_pass "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE/greet_earth \ - -n $NAME -- --no-detach --debug -#------------------------------------------------------------------------------- -TEST_KEY="$TEST_KEY_BASE-suite-run-my-hello.log" -LANG=C sort $SUITE_RUN_DIR/my-hello.log >"$TEST_KEY" -file_cmp "$TEST_KEY" "$TEST_KEY" <<'__LOG__' -[20130101T0000Z] Greet Earth -[20130101T0000Z] Greet Moon -[20130101T1200Z] Greet Earth -[20130101T1200Z] Greet Moon -[20130102T0000Z] Greet Earth -[20130102T0000Z] Greet Moon -__LOG__ -#------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME -exit 0 diff --git a/t/rose-suite-run/11-import-2/greet/rose-suite.conf b/t/rose-suite-run/11-import-2/greet/rose-suite.conf deleted file mode 100644 index e8c25ca703..0000000000 --- a/t/rose-suite-run/11-import-2/greet/rose-suite.conf +++ /dev/null @@ -1,4 +0,0 @@ -import=../10-import-1/hello - -[jinja2:suite.rc] -hello="Greet" diff --git a/t/rose-suite-run/11-import-2/greet_earth/rose-suite.conf b/t/rose-suite-run/11-import-2/greet_earth/rose-suite.conf deleted file mode 100644 index 1135417119..0000000000 --- a/t/rose-suite-run/11-import-2/greet_earth/rose-suite.conf +++ /dev/null @@ -1 +0,0 @@ -import=greet ../10-import-1/hello_earth diff --git a/t/rose-suite-run/12-run-dir-root.t b/t/rose-suite-run/12-run-dir-root.t deleted file mode 100755 index af6ea3593d..0000000000 --- a/t/rose-suite-run/12-run-dir-root.t +++ /dev/null @@ -1,97 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run", modification of the suite run root directory. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - -if [[ -z ${TMPDIR:-} || -z ${USER:-} || $TMPDIR/$USER == $HOME ]]; then - skip_all '"TMPDIR" or "USER" not defined or "TMPDIR"/"USER" is "HOME"' -fi -#------------------------------------------------------------------------------- -N_TESTS=7 -tests $N_TESTS -#------------------------------------------------------------------------------- -cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* . -cat >rose-suite.conf <<__CONF__ -root-dir=*=\$TMPDIR/\$USER -__CONF__ -JOB_HOST=$(rose config 't' 'job-host') -JOB_HOST_RUN_ROOT=$(rose config 't' 'job-host-run-root') -JOB_HOST_OPT= -if [[ -n $JOB_HOST && -n $JOB_HOST_RUN_ROOT ]]; then - export JOB_HOST=$(rose host-select -q $JOB_HOST) - export JOB_HOST_RUN_ROOT - JOB_HOST_OPT='-O job-host' - mkdir opt - cat >opt/rose-suite-job-host.conf <<__CONF__ -root-dir=$JOB_HOST=$JOB_HOST_RUN_ROOT - =*=\$TMPDIR/\$USER - -[jinja2:suite.rc] -JOB_HOST="$JOB_HOST" -__CONF__ -fi -export ROSE_CONF_PATH= -#------------------------------------------------------------------------------- -TEST_KEY=$TEST_KEY_BASE-install -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) -run_pass "$TEST_KEY" rose suite-run -n $NAME -i $JOB_HOST_OPT -#------------------------------------------------------------------------------- -TEST_KEY="$TEST_KEY_BASE-locs" -rose config -f $SUITE_RUN_DIR/log/rose-suite-run.locs localhost root-dir \ - >"$TEST_KEY.localhost" -file_cmp "$TEST_KEY.localhost" "$TEST_KEY.localhost" <<<'$TMPDIR/$USER' -if [[ -n $JOB_HOST_OPT ]]; then - rose config -f $SUITE_RUN_DIR/log/rose-suite-run.locs $JOB_HOST root-dir \ - >"$TEST_KEY.$JOB_HOST" - file_cmp "$TEST_KEY.$JOB_HOST" "$TEST_KEY.$JOB_HOST" <<<$JOB_HOST_RUN_ROOT -else - skip 1 "[t]job-host not defined" -fi -#------------------------------------------------------------------------------- -TEST_KEY="$TEST_KEY_BASE-root-symlink" -if [[ $(readlink $HOME/cylc-run/$NAME) == $TMPDIR/$USER/cylc-run/$NAME ]]; then - pass "$TEST_KEY.localhost" -else - fail "$TEST_KEY.localhost" -fi -if [[ -n $JOB_HOST_OPT ]]; then - RUN_ROOT=$(ssh $JOB_HOST "bash -l -c echo\\ \\$JOB_HOST_RUN_ROOT" | tail -1) - RUN_DIR=$(ssh $JOB_HOST "readlink ~/cylc-run/$NAME" | tail -1) - if [[ $RUN_DIR == $RUN_ROOT/cylc-run/$NAME ]]; then - pass "$TEST_KEY.$JOB_HOST" - else - fail "$TEST_KEY.$JOB_HOST" - fi -else - skip 1 "[t]job-host not defined" -fi -#------------------------------------------------------------------------------- -TEST_KEY="$TEST_KEY_BASE-clean" -run_pass "$TEST_KEY" rose suite-clean -y $NAME -if [[ -e $HOME/cylc-run/$NAME || -e $TMPDIR/$USER/cylc-run/$NAME ]]; then - fail "$TEST_KEY.localhost" -else - pass "$TEST_KEY.localhost" -fi -#------------------------------------------------------------------------------- -exit 0 diff --git a/t/rose-suite-run/12-run-dir-root/suite.rc b/t/rose-suite-run/12-run-dir-root/suite.rc deleted file mode 100644 index d1d516a1a2..0000000000 --- a/t/rose-suite-run/12-run-dir-root/suite.rc +++ /dev/null @@ -1,25 +0,0 @@ -#!jinja2 -[cylc] -UTC mode=True - [[events]] - abort on timeout = True - timeout=PT1M - -[scheduling] - [[dependencies]] - graph=""" -my_task_1 -{% if JOB_HOST is defined %} -my_task_2 -{% endif %} -""" - -[runtime] - [[root]] - script=true - [[my_task_1]] -{% if JOB_HOST is defined %} - [[my_task_2]] - [[[remote]]] - host={{JOB_HOST}} -{% endif %} diff --git a/t/rose-suite-run/13-ignore-cylc-version.t b/t/rose-suite-run/13-ignore-cylc-version.t deleted file mode 100755 index f70418cd1c..0000000000 --- a/t/rose-suite-run/13-ignore-cylc-version.t +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run", reload with !CYLC_VERSION. -# See issue metomi/rose#1143. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - -tests 1 -export ROSE_CONF_PATH= -cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* . -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) -rose suite-run -q -n $NAME -# Wait for the only task to fail, before reload -ST_FILE=$SUITE_RUN_DIR/log/job/1/t1/01/job.status -TIMEOUT=$(($(date +%s) + 60)) # wait 1 minute -while (($(date +%s) < TIMEOUT)) \ - && ! grep -q 'CYLC_JOB_EXIT_TIME=' $ST_FILE 2>/dev/null -do - sleep 1 -done -#------------------------------------------------------------------------------- -TEST_KEY=$TEST_KEY_BASE -run_pass "$TEST_KEY" rose suite-run --reload -q -n $NAME -#------------------------------------------------------------------------------- -rose suite-stop -q -y -n $NAME -- --max-polls=12 --interval=5 -rose suite-clean -q -y $NAME -exit diff --git a/t/rose-suite-run/13-ignore-cylc-version/rose-suite.conf b/t/rose-suite-run/13-ignore-cylc-version/rose-suite.conf deleted file mode 100644 index b4b4dcb614..0000000000 --- a/t/rose-suite-run/13-ignore-cylc-version/rose-suite.conf +++ /dev/null @@ -1,2 +0,0 @@ -[env] -!CYLC_VERSION=5.4.8 diff --git a/t/rose-suite-run/13-ignore-cylc-version/suite.rc b/t/rose-suite-run/13-ignore-cylc-version/suite.rc deleted file mode 100644 index f2706b118e..0000000000 --- a/t/rose-suite-run/13-ignore-cylc-version/suite.rc +++ /dev/null @@ -1,12 +0,0 @@ -#!jinja2 -[cylc] - UTC mode=True - [[events]] - abort on timeout=True - timeout=PT2M -[scheduling] - [[dependencies]] - graph=t1 -[runtime] - [[t1]] - script=false diff --git a/t/rose-suite-run/14-reload-null.t b/t/rose-suite-run/14-reload-null.t deleted file mode 100755 index df4d718197..0000000000 --- a/t/rose-suite-run/14-reload-null.t +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run", null reloads. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - -tests 7 -export ROSE_CONF_PATH= -cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE src -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) -rose suite-run -q -n $NAME -C src -poll ! test -e "$SUITE_RUN_DIR/log/job/20130101T0000Z/t1/01/job.status" -#------------------------------------------------------------------------------- -TEST_KEY="$TEST_KEY_BASE-0" -run_pass "$TEST_KEY" rose suite-run --run=reload -n $NAME -C src -sed -n '/reload complete/p' "$TEST_KEY.out" >"$TEST_KEY.out.tail" -file_cmp "$TEST_KEY.out" "$TEST_KEY.out.tail" <<__OUT__ -[INFO] $NAME: reload complete. "suite.rc" unchanged -__OUT__ -file_cmp "$TEST_KEY.err" "$TEST_KEY.err" /dev/null -#------------------------------------------------------------------------------- -TEST_KEY="$TEST_KEY_BASE-1" -# Add file that allows the jobs to proceed -cat >"src/hello.txt" <<'__TXT__' -hello world -hello earth -__TXT__ -run_pass "$TEST_KEY" rose suite-run --run=reload -n $NAME -C src -sed -n '/hello\.txt/p; /reload complete/p' "$TEST_KEY.out" >"$TEST_KEY.out.tail" -file_cmp "$TEST_KEY.out" "$TEST_KEY.out.tail" <<__OUT__ -[INFO] install: hello.txt -[INFO] source: $PWD/src/hello.txt -[INFO] $NAME: reload complete. "suite.rc" unchanged -__OUT__ -file_cmp "$TEST_KEY.err" "$TEST_KEY.err" /dev/null -# Wait for the suite to complete -poll test -e "$HOME/cylc-run/$NAME/.service/contact" -grep '^hello ' $SUITE_RUN_DIR/log/job/*/t1/01/job.out >"$TEST_KEY.job.out" -file_cmp "$TEST_KEY.job.out" "$TEST_KEY.job.out" <<__OUT__ -$SUITE_RUN_DIR/log/job/20130101T0000Z/t1/01/job.out:hello world -$SUITE_RUN_DIR/log/job/20130101T1200Z/t1/01/job.out:hello world -__OUT__ -#------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME -exit diff --git a/t/rose-suite-run/14-reload-null/app/t1/opt/rose-app-earth.conf b/t/rose-suite-run/14-reload-null/app/t1/opt/rose-app-earth.conf deleted file mode 100644 index 3d2683f6b6..0000000000 --- a/t/rose-suite-run/14-reload-null/app/t1/opt/rose-app-earth.conf +++ /dev/null @@ -1,2 +0,0 @@ -[env] -WORLD=earth diff --git a/t/rose-suite-run/14-reload-null/app/t1/rose-app.conf b/t/rose-suite-run/14-reload-null/app/t1/rose-app.conf deleted file mode 100644 index baa3953fc2..0000000000 --- a/t/rose-suite-run/14-reload-null/app/t1/rose-app.conf +++ /dev/null @@ -1,9 +0,0 @@ -[command] -default=grep $WORLD $ROSE_SUITE_DIR/hello.txt - -[env] -WORLD=world - -[poll] -all-files=$ROSE_SUITE_DIR/hello.txt -delays=120*1s diff --git a/t/rose-suite-run/14-reload-null/rose-suite.conf b/t/rose-suite-run/14-reload-null/rose-suite.conf deleted file mode 100644 index 50dbc24094..0000000000 --- a/t/rose-suite-run/14-reload-null/rose-suite.conf +++ /dev/null @@ -1,2 +0,0 @@ -[jinja2:suite.rc] -ROSE_TASK_RUN_ARGS="" diff --git a/t/rose-suite-run/14-reload-null/suite.rc b/t/rose-suite-run/14-reload-null/suite.rc deleted file mode 100644 index c4b9b06837..0000000000 --- a/t/rose-suite-run/14-reload-null/suite.rc +++ /dev/null @@ -1,16 +0,0 @@ -#!jinja2 -[cylc] - UTC mode=True - abort if any task fails=True - [[events]] - abort on timeout=True - timeout=PT2M -[scheduling] - initial cycle point=20130101T00Z - final cycle point=20130101T12Z - [[dependencies]] - [[[T00, T12]]] - graph="""t1[-PT12H]=>t1""" -[runtime] - [[t1]] - script=rose task-run {{ROSE_TASK_RUN_ARGS}} diff --git a/t/rose-suite-run/15-reload-rc b/t/rose-suite-run/15-reload-rc deleted file mode 120000 index 56966bd3d1..0000000000 --- a/t/rose-suite-run/15-reload-rc +++ /dev/null @@ -1 +0,0 @@ -14-reload-null \ No newline at end of file diff --git a/t/rose-suite-run/15-reload-rc.t b/t/rose-suite-run/15-reload-rc.t deleted file mode 100755 index 0154c8858a..0000000000 --- a/t/rose-suite-run/15-reload-rc.t +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run", reload "suite.rc". -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - -tests 3 -export ROSE_CONF_PATH= -mkdir -p src -cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* src -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) -rose suite-run -q -n $NAME -C src -poll ! test -e "$SUITE_RUN_DIR/log/job/20130101T0000Z/t1/01/job.status" -#------------------------------------------------------------------------------- -TEST_KEY="$TEST_KEY_BASE" -cat >src/rose-suite.conf <<'__CONF__' -[jinja2:suite.rc] -ROSE_TASK_RUN_ARGS="-O earth" -__CONF__ -run_pass "$TEST_KEY" rose suite-run --run=reload -n $NAME -C src -sed -n '/\(delete\|install\): suite\.rc/p' "${TEST_KEY}.out" \ - >"${TEST_KEY}.out.edited" -file_cmp "${TEST_KEY}.out" "${TEST_KEY}.out.edited" <<'__OUT__' -[INFO] delete: suite.rc -[INFO] install: suite.rc -__OUT__ -poll ! grep -q \ - -e 'RELOADING.TASK.DEFINITION.FOR.t1\.20130101T0000Z' \ - -e 'RELOADING.TASK.DEFINITION.FOR.t1\.20130101T1200Z' \ - "$SUITE_RUN_DIR/log/suite/log" -# Add file that allows the jobs to proceed -cat >"$SUITE_RUN_DIR/hello.txt" <<'__TXT__' -hello world -hello earth -__TXT__ -# Wait for the suite to complete -poll test -e "$HOME/cylc-run/$NAME/.service/contact" -grep '^hello ' $SUITE_RUN_DIR/log/job/*/t1/01/job.out >"$TEST_KEY.job.out" -file_cmp "$TEST_KEY.job.out" "$TEST_KEY.job.out" <<__OUT__ -$SUITE_RUN_DIR/log/job/20130101T0000Z/t1/01/job.out:hello world -$SUITE_RUN_DIR/log/job/20130101T1200Z/t1/01/job.out:hello earth -__OUT__ -#------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME -exit diff --git a/t/rose-suite-run/16-suite-rc-not-writable.t b/t/rose-suite-run/16-suite-rc-not-writable.t deleted file mode 100755 index 4b48d85711..0000000000 --- a/t/rose-suite-run/16-suite-rc-not-writable.t +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run", should work whether "suite.rc" is writable or not. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - -tests 1 -export ROSE_CONF_PATH= -mkdir -p src -cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* src -chmod -w src/suite.rc -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) -#------------------------------------------------------------------------------- -TEST_KEY=$TEST_KEY_BASE -run_pass "$TEST_KEY" rose suite-run -q -n $NAME -C src \ - -- --no-detach --debug -#------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME -exit diff --git a/t/rose-suite-run/16-suite-rc-not-writable/rose-suite.conf b/t/rose-suite-run/16-suite-rc-not-writable/rose-suite.conf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/t/rose-suite-run/16-suite-rc-not-writable/suite.rc b/t/rose-suite-run/16-suite-rc-not-writable/suite.rc deleted file mode 100644 index 66d0769878..0000000000 --- a/t/rose-suite-run/16-suite-rc-not-writable/suite.rc +++ /dev/null @@ -1,12 +0,0 @@ -#!jinja2 -[cylc] - UTC mode=True - [[events]] - abort on timeout=True - timeout=PT1M -[scheduling] - [[dependencies]] - graph=t1 -[runtime] - [[t1]] - script=true diff --git a/t/rose-suite-run/17-install-overlap.t b/t/rose-suite-run/17-install-overlap.t deleted file mode 100755 index fd5cf0a752..0000000000 --- a/t/rose-suite-run/17-install-overlap.t +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run", file install targets overlap. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - -tests 3 -export ROSE_CONF_PATH= - -mkdir -p src -echo 'yummie' >src/bacon.txt - -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) -#------------------------------------------------------------------------------- -TEST_KEY=$TEST_KEY_BASE -export TEST_DIR -run_pass "$TEST_KEY-1" rose suite-run \ - -n $NAME -C $TEST_SOURCE_DIR/$TEST_KEY_BASE -i -run_pass "$TEST_KEY-2" rose suite-run \ - -n $NAME -C $TEST_SOURCE_DIR/$TEST_KEY_BASE -i -(cd $SUITE_RUN_DIR/etc; find -type f) | LANG=C sort >"$TEST_KEY.find" -file_cmp "$TEST_KEY.find" "$TEST_KEY.find" <<'__FIND__' -./foo/bar/baz/bacon.txt -./foo/bar/egg/humpty.txt -__FIND__ -#------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME -exit diff --git a/t/rose-suite-run/17-install-overlap/etc/foo/bar/egg/humpty.txt b/t/rose-suite-run/17-install-overlap/etc/foo/bar/egg/humpty.txt deleted file mode 100644 index 1e7074c4a8..0000000000 --- a/t/rose-suite-run/17-install-overlap/etc/foo/bar/egg/humpty.txt +++ /dev/null @@ -1 +0,0 @@ -Dumpty diff --git a/t/rose-suite-run/17-install-overlap/rose-suite.conf b/t/rose-suite-run/17-install-overlap/rose-suite.conf deleted file mode 100644 index c18d7b25e4..0000000000 --- a/t/rose-suite-run/17-install-overlap/rose-suite.conf +++ /dev/null @@ -1,2 +0,0 @@ -[file:etc/foo/bar/baz] -source=$TEST_DIR/src diff --git a/t/rose-suite-run/17-install-overlap/suite.rc b/t/rose-suite-run/17-install-overlap/suite.rc deleted file mode 100644 index 66d0769878..0000000000 --- a/t/rose-suite-run/17-install-overlap/suite.rc +++ /dev/null @@ -1,12 +0,0 @@ -#!jinja2 -[cylc] - UTC mode=True - [[events]] - abort on timeout=True - timeout=PT1M -[scheduling] - [[dependencies]] - graph=t1 -[runtime] - [[t1]] - script=true diff --git a/t/rose-suite-run/18-restart-init-dir.t b/t/rose-suite-run/18-restart-init-dir.t deleted file mode 100755 index e72fffb211..0000000000 --- a/t/rose-suite-run/18-restart-init-dir.t +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run --restart" does not re-initialise run directory. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - -tests 3 - -export ROSE_CONF_PATH= -cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE src -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) -rose suite-run -q -n $NAME -C src -- --no-detach --debug -cat >'src/rose-suite.conf' <<__CONF__ -root-dir=*=$PWD -__CONF__ -#------------------------------------------------------------------------------- -TEST_KEY="$TEST_KEY_BASE-restart" -run_pass "$TEST_KEY" \ - rose suite-run -q -n $NAME -C src --restart \ - -- --no-detach --debug -#------------------------------------------------------------------------------- -TEST_KEY="$TEST_KEY_BASE-dir" -run_pass "$TEST_KEY" test -d "$SUITE_RUN_DIR" -TEST_KEY="$TEST_KEY_BASE-symlink" -run_fail "$TEST_KEY" test -L "$SUITE_RUN_DIR" -#------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME -exit diff --git a/t/rose-suite-run/18-restart-init-dir/rose-suite.conf b/t/rose-suite-run/18-restart-init-dir/rose-suite.conf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/t/rose-suite-run/18-restart-init-dir/suite.rc b/t/rose-suite-run/18-restart-init-dir/suite.rc deleted file mode 100644 index 66d0769878..0000000000 --- a/t/rose-suite-run/18-restart-init-dir/suite.rc +++ /dev/null @@ -1,12 +0,0 @@ -#!jinja2 -[cylc] - UTC mode=True - [[events]] - abort on timeout=True - timeout=PT1M -[scheduling] - [[dependencies]] - graph=t1 -[runtime] - [[t1]] - script=true diff --git a/t/rose-suite-run/19-restart-init-dir-remote.t b/t/rose-suite-run/19-restart-init-dir-remote.t deleted file mode 100755 index 7ff294f8a3..0000000000 --- a/t/rose-suite-run/19-restart-init-dir-remote.t +++ /dev/null @@ -1,83 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run --restart" does not re-initialise run directory, -# on remote host. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - - -T_HOST=$(rose config --default= t job-host) -T_HOST_RUN_ROOT=$(rose config --default= t job-host-run-root) -if [[ -z "$T_HOST" || -z "$T_HOST_RUN_ROOT" ]]; then - skip_all '"[t]job-host" or "[t]job-host-run-root" not defined' -fi -T_HOST=$(rose host-select -q $T_HOST) -#------------------------------------------------------------------------------- -SSH='ssh -oBatchMode=yes' -function ssh_mkdtemp() { - local T_HOST=$1 - $SSH $T_HOST python3 - <<'__PYTHON__' -import os -from tempfile import mkdtemp -print mkdtemp(dir=os.path.expanduser("~"), prefix="rose-") -__PYTHON__ -} - -T_HOST_ROSE_HOME=$(ssh_mkdtemp $T_HOST) -rsync -a --exclude=*.pyc $ROSE_HOME/* $T_HOST:$T_HOST_ROSE_HOME/ - -mkdir -p 'conf' -cat >'conf/rose.conf' <<__CONF__ -[rose-suite-run] -remote-no-login-shell=${T_HOST}=true -remote-rose-bin=${T_HOST}=${T_HOST_ROSE_HOME}/bin/rose -__CONF__ -export ROSE_CONF_PATH="${PWD}/conf" - -tests 3 - -cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE src -cat >'src/rose-suite.conf' <<__CONF__ -[jinja2:suite.rc] -T_HOST="$T_HOST" -__CONF__ -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) -rose suite-run -q -n $NAME -C src -- --no-detach --debug -cat >'src/rose-suite.conf' <<__CONF__ -root-dir=$T_HOST=$T_HOST_RUN_ROOT -[jinja2:suite.rc] -T_HOST="$T_HOST" -__CONF__ -#------------------------------------------------------------------------------- -TEST_KEY="$TEST_KEY_BASE-restart" -run_pass "$TEST_KEY" \ - rose suite-run -q -n $NAME -C src --restart \ - -- --no-detach --debug -#------------------------------------------------------------------------------- -TEST_KEY="$TEST_KEY_BASE-dir" -run_pass "$TEST_KEY" ssh -oBatchMode=yes $T_HOST test -d "cylc-run/$NAME" -TEST_KEY="$TEST_KEY_BASE-symlink" -run_fail "$TEST_KEY" ssh -oBatchMode=yes $T_HOST test -L "cylc-run/$NAME" -#------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME -$SSH $T_HOST "rm -fr '$T_HOST_ROSE_HOME'" -exit diff --git a/t/rose-suite-run/19-restart-init-dir-remote/rose-suite.conf b/t/rose-suite-run/19-restart-init-dir-remote/rose-suite.conf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/t/rose-suite-run/19-restart-init-dir-remote/suite.rc b/t/rose-suite-run/19-restart-init-dir-remote/suite.rc deleted file mode 100644 index a226d2443b..0000000000 --- a/t/rose-suite-run/19-restart-init-dir-remote/suite.rc +++ /dev/null @@ -1,14 +0,0 @@ -#!jinja2 -[cylc] - UTC mode=True - [[events]] - abort on timeout = True - timeout=PT1M -[scheduling] - [[dependencies]] - graph=t1 -[runtime] - [[t1]] - script=true - [[[remote]]] - host={{T_HOST}} diff --git a/t/rose-suite-run/21-sharecycle-back-compat.t b/t/rose-suite-run/21-sharecycle-back-compat.t deleted file mode 100755 index f266c60eb4..0000000000 --- a/t/rose-suite-run/21-sharecycle-back-compat.t +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run" installation of share/cycle/ sub-directory using the -# backward compatable configuration setting of -# "root-dir{share/cycle}=HOST=share/data". -#------------------------------------------------------------------------------- -. "$(dirname "$0")/test_header" - - -tests 2 -#------------------------------------------------------------------------------- -mkdir -p 'conf' -cat >'conf/rose.conf' <<'__CONF__' -[rose-suite-run] -root-dir{share/cycle}=*=share/data -__CONF__ -export ROSE_CONF_PATH="${PWD}/conf" - -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" -NAME="$(basename "${SUITE_RUN_DIR}")" -rose suite-run -q \ - -n "${NAME}" -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" -- --no-detach --debug -# Check contents of files written to ROSE_DATAC is going to historic location -for DATETIME in '20200101T0000Z' '20200101T0000Z'; do - file_cmp "nl-${DATETIME}" \ - "${SUITE_RUN_DIR}/share/data/${DATETIME}/share.nl" <<__NL__ -&share_nl -date=${DATETIME}, -index=ftse, -market=lse, -/ -__NL__ -done -#------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" -exit diff --git a/t/rose-suite-run/21-sharecycle-back-compat/app/t1/rose-app.conf b/t/rose-suite-run/21-sharecycle-back-compat/app/t1/rose-app.conf deleted file mode 100644 index 16c26edf75..0000000000 --- a/t/rose-suite-run/21-sharecycle-back-compat/app/t1/rose-app.conf +++ /dev/null @@ -1,10 +0,0 @@ -[command] -default=true - -[file:$ROSE_DATAC/share.nl] -source=namelist:share_nl - -[namelist:share_nl] -market=lse -index=ftse -date=$ROSE_TASK_CYCLE_TIME diff --git a/t/rose-suite-run/21-sharecycle-back-compat/rose-suite.conf b/t/rose-suite-run/21-sharecycle-back-compat/rose-suite.conf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/t/rose-suite-run/21-sharecycle-back-compat/suite.rc b/t/rose-suite-run/21-sharecycle-back-compat/suite.rc deleted file mode 100644 index 883e0fde86..0000000000 --- a/t/rose-suite-run/21-sharecycle-back-compat/suite.rc +++ /dev/null @@ -1,15 +0,0 @@ -#!jinja2 -[cylc] - UTC mode = True - [[events]] - abort on timeout = True - timeout = PT1M -[scheduling] - initial cycle point = 2020 - final cycle point = 2021 - [[dependencies]] - [[[P1Y]]] - graph = t1 -[runtime] - [[t1]] - script = rose task-run diff --git a/t/rose-suite-run/22-sharecycle.t b/t/rose-suite-run/22-sharecycle.t deleted file mode 100755 index 643dd560c6..0000000000 --- a/t/rose-suite-run/22-sharecycle.t +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run" installation of share/cycle/ sub-directory. -#------------------------------------------------------------------------------- -. "$(dirname "$0")/test_header" - - -tests 2 -#------------------------------------------------------------------------------- -export CYLC_CONF_PATH= -export ROSE_CONF_PATH="${PWD}/conf" -export ROOT_DIR_WORK="${PWD}" -mkdir -p 'conf' -cat >'conf/rose.conf' <<'__CONF__' -[rose-suite-run] -root-dir{share/cycle}=*=$ROOT_DIR_WORK -__CONF__ - -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" -NAME="$(basename "${SUITE_RUN_DIR}")" -rose suite-run -q \ - -n "${NAME}" -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" -- --no-detach --debug -# Check contents of files written to ROSE_DATAC is going to $PWD/share/cycle/ -for DATETIME in '20200101T0000Z' '20200101T0000Z'; do - file_cmp "nl-${DATETIME}" \ - "${PWD}/cylc-run/${NAME}/share/cycle/${DATETIME}/share.nl" <<__NL__ -&share_nl -date=${DATETIME}, -index=ftse, -market=lse, -/ -__NL__ -done -#------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" -exit diff --git a/t/rose-suite-run/22-sharecycle/app/t1/rose-app.conf b/t/rose-suite-run/22-sharecycle/app/t1/rose-app.conf deleted file mode 100644 index 16c26edf75..0000000000 --- a/t/rose-suite-run/22-sharecycle/app/t1/rose-app.conf +++ /dev/null @@ -1,10 +0,0 @@ -[command] -default=true - -[file:$ROSE_DATAC/share.nl] -source=namelist:share_nl - -[namelist:share_nl] -market=lse -index=ftse -date=$ROSE_TASK_CYCLE_TIME diff --git a/t/rose-suite-run/22-sharecycle/rose-suite.conf b/t/rose-suite-run/22-sharecycle/rose-suite.conf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/t/rose-suite-run/22-sharecycle/suite.rc b/t/rose-suite-run/22-sharecycle/suite.rc deleted file mode 100644 index 883e0fde86..0000000000 --- a/t/rose-suite-run/22-sharecycle/suite.rc +++ /dev/null @@ -1,15 +0,0 @@ -#!jinja2 -[cylc] - UTC mode = True - [[events]] - abort on timeout = True - timeout = PT1M -[scheduling] - initial cycle point = 2020 - final cycle point = 2021 - [[dependencies]] - [[[P1Y]]] - graph = t1 -[runtime] - [[t1]] - script = rose task-run diff --git a/t/rose-suite-run/23-reload-host.t b/t/rose-suite-run/23-reload-host.t deleted file mode 100755 index faab76330c..0000000000 --- a/t/rose-suite-run/23-reload-host.t +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run", reload with a new job host. -#------------------------------------------------------------------------------- -. "$(dirname "$0")/test_header" - -JOB_HOST="$(rose config --default= 't' 'job-host')" -if [[ -n "${JOB_HOST}" ]]; then - JOB_HOST="$(rose host-select -q "${JOB_HOST}")" -fi -if [[ -z "${JOB_HOST}" ]]; then - skip_all '"[t]job-host" not defined' -fi -tests 2 -export ROSE_CONF_PATH= -rsync -a "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}/" '.' -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" -NAME="$(basename ${SUITE_RUN_DIR})" -rose suite-run --debug --name="${NAME}" \ - -S "HOST=\"${JOB_HOST}\"" -- --no-detach --debug --hold 1>'/dev/null' 2>&1 & -ROSE_SUITE_RUN_PID=$! - -timeout 60 bash -c \ - "while ! test -e '${HOME}/cylc-run/${NAME}/.service/contact'; do sleep 1; done" -sed -i "s/host = localhost/host = ${JOB_HOST}/" 'suite.rc' - -TEST_KEY="${TEST_KEY_BASE}" -run_pass "${TEST_KEY}" \ - rose suite-run --debug --reload --name="${NAME}" \ - -S "HOST=\"${JOB_HOST}\"" -sed -n '/\(delete\|install\): suite\.rc/p' \ - "${TEST_KEY}.out" >"${TEST_KEY}.out.edited" -file_cmp "${TEST_KEY}.out" "${TEST_KEY}.out.edited" <<'__OUT__' -[INFO] delete: suite.rc -[INFO] install: suite.rc -__OUT__ - -cylc release "${NAME}" - -wait "${ROSE_SUITE_RUN_PID}" -rose suite-clean -q -y "${NAME}" -exit 0 diff --git a/t/rose-suite-run/23-reload-host/rose-suite.conf b/t/rose-suite-run/23-reload-host/rose-suite.conf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/t/rose-suite-run/23-reload-host/suite.rc b/t/rose-suite-run/23-reload-host/suite.rc deleted file mode 100644 index da227cb1a9..0000000000 --- a/t/rose-suite-run/23-reload-host/suite.rc +++ /dev/null @@ -1,17 +0,0 @@ -#!jinja2 -[cylc] - UTC mode = True - [[events]] - abort on timeout = True - timeout = PT1M -[scheduling] - initial cycle point = 2020 - final cycle point = 2021 - [[dependencies]] - [[[P1Y]]] - graph = t1 -[runtime] - [[t1]] - script = true - [[[remote]]] - host = localhost diff --git a/t/rose-suite-run/24-host-log-timestamp.t b/t/rose-suite-run/24-host-log-timestamp.t deleted file mode 100755 index dafb746c79..0000000000 --- a/t/rose-suite-run/24-host-log-timestamp.t +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run", match TIMESTAMP of local and remote log.TIMESTAMP/ -#------------------------------------------------------------------------------- -. "$(dirname "$0")/test_header" - -JOB_HOST="$(rose config --default= 't' 'job-host')" -if [[ -n "${JOB_HOST}" ]]; then - JOB_HOST="$(rose host-select -q "${JOB_HOST}")" -fi -if [[ -z "${JOB_HOST}" ]]; then - skip_all '"[t]job-host" not defined' -fi -tests 3 -export ROSE_CONF_PATH="${PWD}/conf" -export PATH="${PWD}/bin:${PATH}" -mkdir 'bin' 'conf' -cat >'bin/myssh' <<'__BASH__' -#!/bin/bash -# Make sure that local and remote log cannot be created in the same second -sleep 1 -# Print arguments to log file -echo "$@" >"$(dirname "$0")/../myssh.log" -# Invoke real SSH -exec ssh "$@" -__BASH__ -chmod +x 'bin/myssh' -cat >'conf/rose.conf' <<'__CONF__' -[external] -ssh=myssh -oBatchMode=yes -oConnectTimeout=10 -__CONF__ -rsync -a "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}/" '.' -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" -NAME="$(basename ${SUITE_RUN_DIR})" -run_pass "${TEST_KEY_BASE}" \ - rose suite-run --debug --name="${NAME}" \ - -S "HOST=\"${JOB_HOST}\"" -- --no-detach -NOW_STR="$(sed 's/^.*now-str=\([^,]*\),\?.*$/\1/' 'myssh.log')" -run_pass "${TEST_KEY_BASE}-log-timestamp-local" \ - test -d "${SUITE_RUN_DIR}/log.${NOW_STR}" -run_pass "${TEST_KEY_BASE}-log-timestamp-remote" \ - ssh -n -oBatchMode='yes' "${JOB_HOST}" \ - "test -d cylc-run/${NAME}/log.${NOW_STR}" -rose suite-clean -q -y "${NAME}" -exit 0 diff --git a/t/rose-suite-run/24-host-log-timestamp/rose-suite.conf b/t/rose-suite-run/24-host-log-timestamp/rose-suite.conf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/t/rose-suite-run/24-host-log-timestamp/suite.rc b/t/rose-suite-run/24-host-log-timestamp/suite.rc deleted file mode 100644 index e37d6423c8..0000000000 --- a/t/rose-suite-run/24-host-log-timestamp/suite.rc +++ /dev/null @@ -1,14 +0,0 @@ -#!jinja2 -[cylc] - UTC mode = True - [[events]] - abort on timeout = True - timeout = PT1M -[scheduling] - [[dependencies]] - graph = t1 -[runtime] - [[t1]] - script = true - [[[remote]]] - host = {{HOST}} diff --git a/t/rose-suite-run/25-install-validate-only b/t/rose-suite-run/25-install-validate-only deleted file mode 120000 index e1b7ad15e0..0000000000 --- a/t/rose-suite-run/25-install-validate-only +++ /dev/null @@ -1 +0,0 @@ -02-install \ No newline at end of file diff --git a/t/rose-suite-run/25-install-validate-only.t b/t/rose-suite-run/25-install-validate-only.t deleted file mode 120000 index 3d0cca2073..0000000000 --- a/t/rose-suite-run/25-install-validate-only.t +++ /dev/null @@ -1 +0,0 @@ -02-install.t \ No newline at end of file diff --git a/t/rose-suite-run/26-jinja2-insert.t b/t/rose-suite-run/26-jinja2-insert.t deleted file mode 100755 index 7df1416b59..0000000000 --- a/t/rose-suite-run/26-jinja2-insert.t +++ /dev/null @@ -1,94 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run" in $HOME/cylc-run to ensure that insertion of jinja2 -# variable declarations to "suite.rc" do not get repeated. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - - -#------------------------------------------------------------------------------- -N_TESTS=3 -tests $N_TESTS -export ROSE_CONF_PATH= -#------------------------------------------------------------------------------- -TEST_KEY=$TEST_KEY_BASE -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -cat >$SUITE_RUN_DIR/rose-suite.conf <<__ROSE_SUITE_CONF__ -[jinja2:suite.rc] -foo="food store" -bar="barley drink" -__ROSE_SUITE_CONF__ -cat >"$SUITE_RUN_DIR/suite.rc" <<__SUITE_RC__ -#!jinja2 -{% set egg="egg sandwich" %} -{% set ham="hamburger" %} -[cylc] -UTC mode=True -[scheduling] -[[dependencies]] -graph=x -[runtime] -[[x]] -__SUITE_RC__ -NAME=$(basename $SUITE_RUN_DIR) -CYLC_VERSION=$(cylc --version) -ROSE_ORIG_HOST=$(hostname) -ROSE_VERSION=$(rose --version | cut -d' ' -f2) -for I in $(seq 1 $N_TESTS); do - rose suite-run -C$SUITE_RUN_DIR --name=$NAME -l -q --debug || break - file_cmp "${TEST_KEY}-${I}" "$SUITE_RUN_DIR/suite.rc" <<__SUITE_RC__ -#!jinja2 -{# Rose Configuration Insertion: Init #} -{% set CYLC_VERSION="$CYLC_VERSION" %} -{% set ROSE_ORIG_HOST="$ROSE_ORIG_HOST" %} -{% set ROSE_SITE="" %} -{% set ROSE_VERSION="$ROSE_VERSION" %} -{% set bar="barley drink" %} -{% set foo="food store" %} -{% set ROSE_SUITE_VARIABLES={ - 'CYLC_VERSION': CYLC_VERSION, - 'ROSE_ORIG_HOST': ROSE_ORIG_HOST, - 'ROSE_SITE': ROSE_SITE, - 'ROSE_VERSION': ROSE_VERSION, - 'bar': bar, - 'foo': foo, -} %} -[cylc] - [[environment]] - CYLC_VERSION=${CYLC_VERSION} - ROSE_ORIG_HOST=${ROSE_ORIG_HOST} - ROSE_SITE= - ROSE_VERSION=${ROSE_VERSION} -{# Rose Configuration Insertion: Done #} -{% set egg="egg sandwich" %} -{% set ham="hamburger" %} -[cylc] -UTC mode=True -[scheduling] -[[dependencies]] -graph=x -[runtime] -[[x]] -__SUITE_RC__ -done -#------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME --debug -exit 0 diff --git a/t/rose-suite-run/27-empy-insert.t b/t/rose-suite-run/27-empy-insert.t deleted file mode 100755 index e57b47b3bb..0000000000 --- a/t/rose-suite-run/27-empy-insert.t +++ /dev/null @@ -1,99 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run" in $HOME/cylc-run to ensure that insertion of jinja2 -# variable declarations to "suite.rc" do not get repeated. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - -if ! cylc check-software 2>/dev/null | grep '^Python:EmPy.*([^-]*)$' >/dev/null; then - skip_all '"EmPy" not installed' -fi -#------------------------------------------------------------------------------- -N_TESTS=3 -tests $N_TESTS -export ROSE_CONF_PATH= -#------------------------------------------------------------------------------- -TEST_KEY=$TEST_KEY_BASE -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -cat >$SUITE_RUN_DIR/rose-suite.conf <<__ROSE_SUITE_CONF__ -[empy:suite.rc] -foo="food store" -bar="barley drink" -__ROSE_SUITE_CONF__ -cat >"$SUITE_RUN_DIR/suite.rc" <<__SUITE_RC__ -#!empy -@# Rose Configuration Insertion: Init -# Anything here is to be replaced -@# Rose Configuration Insertion: Done -@{ egg="egg sandwich" }@ -@{ ham="hamburger" }@ -[cylc] -UTC mode=True -[scheduling] -[[dependencies]] -graph=x -[runtime] -[[x]] -__SUITE_RC__ -NAME=$(basename $SUITE_RUN_DIR) -CYLC_VERSION=$(cylc --version) -ROSE_ORIG_HOST=$(hostname) -ROSE_VERSION=$(rose --version | cut -d' ' -f2) -for I in $(seq 1 $N_TESTS); do - rose suite-run -C$SUITE_RUN_DIR --name=$NAME -l -q --debug -S "!bar" -S baz=True || break - file_cmp "$TEST_KEY" "$SUITE_RUN_DIR/suite.rc" <<__SUITE_RC__ -#!empy -@# Rose Configuration Insertion: Init -@{CYLC_VERSION="$CYLC_VERSION"}@ -@{ROSE_ORIG_HOST="$ROSE_ORIG_HOST"}@ -@{ROSE_SITE=""}@ -@{ROSE_VERSION="$ROSE_VERSION"}@ -@{baz=True}@ -@{foo="food store"}@ -@{ROSE_SUITE_VARIABLES={ - 'CYLC_VERSION': CYLC_VERSION, - 'ROSE_ORIG_HOST': ROSE_ORIG_HOST, - 'ROSE_SITE': ROSE_SITE, - 'ROSE_VERSION': ROSE_VERSION, - 'baz': baz, - 'foo': foo, -}}@ -[cylc] - [[environment]] - CYLC_VERSION=${CYLC_VERSION} - ROSE_ORIG_HOST=${ROSE_ORIG_HOST} - ROSE_SITE= - ROSE_VERSION=${ROSE_VERSION} -@# Rose Configuration Insertion: Done -@{ egg="egg sandwich" }@ -@{ ham="hamburger" }@ -[cylc] -UTC mode=True -[scheduling] -[[dependencies]] -graph=x -[runtime] -[[x]] -__SUITE_RC__ -done -#------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME --debug -exit 0 diff --git a/t/rose-suite-run/28-rose-site-env.t b/t/rose-suite-run/28-rose-site-env.t deleted file mode 100755 index 59d0a1d40a..0000000000 --- a/t/rose-suite-run/28-rose-site-env.t +++ /dev/null @@ -1,87 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run" with "site=SITE" setting in site/user conf. -#------------------------------------------------------------------------------- -. "$(dirname "$0")/test_header" - - -N_TESTS=3 -tests "${N_TESTS}" -#------------------------------------------------------------------------------- -export ROSE_CONF_PATH="${PWD}/conf" -export ROOT_DIR_WORK="${PWD}" -mkdir -p 'conf' -cat >'conf/rose.conf' <<'__CONF__' -site=my-site -__CONF__ - -#------------------------------------------------------------------------------- -TEST_KEY=$TEST_KEY_BASE -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -touch "${SUITE_RUN_DIR}/rose-suite.conf" -cat >"$SUITE_RUN_DIR/suite.rc" <<'__SUITE_RC__' -#!jinja2 -[cylc] -UTC mode=True -[scheduling] -[[dependencies]] -graph=x -[runtime] -[[x]] -__SUITE_RC__ -NAME="$(basename "${SUITE_RUN_DIR}")" -CYLC_VERSION="$(cylc --version)" -ROSE_ORIG_HOST="$(hostname)" -ROSE_VERSION="$(rose --version | cut -d' ' -f2)" -for I in $(seq 1 "${N_TESTS}"); do - rose suite-run -C"${SUITE_RUN_DIR}" --name="${NAME}" -l -q --debug || break - file_cmp "${TEST_KEY}-${I}" "${SUITE_RUN_DIR}/suite.rc" <<__SUITE_RC__ -#!jinja2 -{# Rose Configuration Insertion: Init #} -{% set CYLC_VERSION="${CYLC_VERSION}" %} -{% set ROSE_ORIG_HOST="${ROSE_ORIG_HOST}" %} -{% set ROSE_SITE="my-site" %} -{% set ROSE_VERSION="${ROSE_VERSION}" %} -{% set ROSE_SUITE_VARIABLES={ - 'CYLC_VERSION': CYLC_VERSION, - 'ROSE_ORIG_HOST': ROSE_ORIG_HOST, - 'ROSE_SITE': ROSE_SITE, - 'ROSE_VERSION': ROSE_VERSION, -} %} -[cylc] - [[environment]] - CYLC_VERSION=${CYLC_VERSION} - ROSE_ORIG_HOST=${ROSE_ORIG_HOST} - ROSE_SITE=my-site - ROSE_VERSION=${ROSE_VERSION} -{# Rose Configuration Insertion: Done #} -[cylc] -UTC mode=True -[scheduling] -[[dependencies]] -graph=x -[runtime] -[[x]] -__SUITE_RC__ -done -#------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" -exit diff --git a/t/rose-suite-run/29-install-host-localhost b/t/rose-suite-run/29-install-host-localhost deleted file mode 120000 index e1b7ad15e0..0000000000 --- a/t/rose-suite-run/29-install-host-localhost +++ /dev/null @@ -1 +0,0 @@ -02-install \ No newline at end of file diff --git a/t/rose-suite-run/29-install-host-localhost.t b/t/rose-suite-run/29-install-host-localhost.t deleted file mode 120000 index 3d0cca2073..0000000000 --- a/t/rose-suite-run/29-install-host-localhost.t +++ /dev/null @@ -1 +0,0 @@ -02-install.t \ No newline at end of file diff --git a/t/rose-suite-run/test_header b/t/rose-suite-run/test_header deleted file mode 120000 index 90bd5a36f9..0000000000 --- a/t/rose-suite-run/test_header +++ /dev/null @@ -1 +0,0 @@ -../lib/bash/test_header \ No newline at end of file diff --git a/t/rose-suite-shutdown/00-run-basic.t b/t/rose-suite-shutdown/00-run-basic.t deleted file mode 100755 index 7689283e8b..0000000000 --- a/t/rose-suite-shutdown/00-run-basic.t +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run", with and without site/user configurations. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header -#------------------------------------------------------------------------------- -if [[ $TEST_KEY_BASE == *conf ]]; then - if ! rose config -q 'rose-suite-run' 'hosts'; then - skip_all '"[rose-suite-run]hosts" not defined' - fi -else - export ROSE_CONF_PATH= -fi -#------------------------------------------------------------------------------- -set -e -N_TESTS=1 -tests $N_TESTS -#------------------------------------------------------------------------------- -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) -rose suite-run -q -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME -#------------------------------------------------------------------------------- -TEST_KEY=$TEST_KEY_BASE -sleep 1 -run_pass "$TEST_KEY" rose suite-stop -y -n $NAME -- --max-polls=12 --interval=5 -#------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME -exit 0 diff --git a/t/rose-suite-shutdown/00-run-basic/rose-suite.conf b/t/rose-suite-shutdown/00-run-basic/rose-suite.conf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/t/rose-suite-shutdown/00-run-basic/suite.rc b/t/rose-suite-shutdown/00-run-basic/suite.rc deleted file mode 100644 index 168bdc622e..0000000000 --- a/t/rose-suite-shutdown/00-run-basic/suite.rc +++ /dev/null @@ -1,14 +0,0 @@ -#!jinja2 -[cylc] - UTC mode=True - [[events]] - abort on timeout = True - timeout=PT1M -[scheduling] - [[dependencies]] - graph=my_task_1 - -[runtime] - [[root]] - script=false - [[my_task_1]] diff --git a/t/rose-suite-shutdown/01-run-conf b/t/rose-suite-shutdown/01-run-conf deleted file mode 120000 index 6907e44de1..0000000000 --- a/t/rose-suite-shutdown/01-run-conf +++ /dev/null @@ -1 +0,0 @@ -00-run-basic \ No newline at end of file diff --git a/t/rose-suite-shutdown/01-run-conf.t b/t/rose-suite-shutdown/01-run-conf.t deleted file mode 120000 index a0a623ac5f..0000000000 --- a/t/rose-suite-shutdown/01-run-conf.t +++ /dev/null @@ -1 +0,0 @@ -00-run-basic.t \ No newline at end of file diff --git a/t/rose-suite-shutdown/02-rose-stem-name b/t/rose-suite-shutdown/02-rose-stem-name deleted file mode 120000 index 6907e44de1..0000000000 --- a/t/rose-suite-shutdown/02-rose-stem-name +++ /dev/null @@ -1 +0,0 @@ -00-run-basic \ No newline at end of file diff --git a/t/rose-suite-shutdown/02-rose-stem-name.t b/t/rose-suite-shutdown/02-rose-stem-name.t deleted file mode 100755 index 31f9ad882c..0000000000 --- a/t/rose-suite-shutdown/02-rose-stem-name.t +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run", with and without site/user configurations. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header -set -e -#------------------------------------------------------------------------------- -tests 1 -#------------------------------------------------------------------------------- -export ROSE_CONF_PATH= -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) -rose suite-run -q -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME -#------------------------------------------------------------------------------- -TEST_KEY=$TEST_KEY_BASE -mkdir -p $NAME -ln -s $TEST_SOURCE_DIR/$TEST_KEY_BASE $NAME/rose-stem -cd $NAME -TIMEOUT=$(($(date +%s) + 60)) -while (($(date +%s) < $TIMEOUT)) \ - && [[ ! -e $SUITE_RUN_DIR/log/job/1/my_task_1/01/job.status ]] -do - sleep 1 -done -run_pass "$TEST_KEY" rose suite-stop -y -- --max-polls=12 --interval=5 -cd $OLDPWD -#------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME -exit 0 diff --git a/t/rose-suite-shutdown/03-normal-name b/t/rose-suite-shutdown/03-normal-name deleted file mode 120000 index 6907e44de1..0000000000 --- a/t/rose-suite-shutdown/03-normal-name +++ /dev/null @@ -1 +0,0 @@ -00-run-basic \ No newline at end of file diff --git a/t/rose-suite-shutdown/03-normal-name.t b/t/rose-suite-shutdown/03-normal-name.t deleted file mode 100755 index c59d3c829f..0000000000 --- a/t/rose-suite-shutdown/03-normal-name.t +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test "rose suite-run", with and without site/user configurations. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header -set -e -#------------------------------------------------------------------------------- -tests 1 -#------------------------------------------------------------------------------- -export ROSE_CONF_PATH= -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) -rose suite-run -q -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME -HOST="$(awk -F= '$1 == "CYLC_SUITE_HOST" {print $2}' "${SUITE_RUN_DIR}/.service/contact")" -#------------------------------------------------------------------------------- -TEST_KEY=$TEST_KEY_BASE -mkdir -p "$TEST_KEY/$NAME" -ln -s $TEST_SOURCE_DIR/$TEST_KEY_BASE/rose-suite.conf $TEST_KEY/$NAME/ -cd "$TEST_KEY/$NAME" -run_pass "$TEST_KEY" rose suite-stop -y -- --max-polls=12 --interval=5 -cd $OLDPWD -#------------------------------------------------------------------------------- -sleep 1 -rose suite-clean -q -y $NAME -exit 0 diff --git a/t/rose-suite-shutdown/test_header b/t/rose-suite-shutdown/test_header deleted file mode 120000 index 90bd5a36f9..0000000000 --- a/t/rose-suite-shutdown/test_header +++ /dev/null @@ -1 +0,0 @@ -../lib/bash/test_header \ No newline at end of file From 9800383b70494e1fcc6a25b99e1d16fa9f9852ad Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Mon, 21 Dec 2020 14:09:48 +0000 Subject: [PATCH 04/78] remove empy/jinja2 config processors --- metomi/rose/__init__.py | 9 -- metomi/rose/config_processors/empy.py | 36 ------- metomi/rose/config_processors/jinja2.py | 128 ------------------------ 3 files changed, 173 deletions(-) delete mode 100644 metomi/rose/config_processors/empy.py delete mode 100644 metomi/rose/config_processors/jinja2.py diff --git a/metomi/rose/__init__.py b/metomi/rose/__init__.py index 70bebfb1ed..9a4e33d635 100644 --- a/metomi/rose/__init__.py +++ b/metomi/rose/__init__.py @@ -56,15 +56,6 @@ "=type"] SUB_CONFIG_DEFAULT_META_IDS = ["=file-install-root", "=meta", "=mode", "=opts", "command", "file:", "poll"] -TOP_CONFIG_DEFAULT_META_IDS = [ - "file:", - "jinja2:suite.rc", - "=meta", - "=opts", - "=root-dir", - "=root-dir{share}", - "=root-dir{share/cycle}", - "=root-dir{work}"] CONFIG_SETTING_INDEX_DEFAULT = "1" diff --git a/metomi/rose/config_processors/empy.py b/metomi/rose/config_processors/empy.py deleted file mode 100644 index c14cd1a3e7..0000000000 --- a/metomi/rose/config_processors/empy.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -# ----------------------------------------------------------------------------- -"""Process a section in a metomi.rose.config.ConfigNode into a EmPy template. -""" - - -from metomi.rose.config_processors.jinja2 import ConfigProcessorForJinja2 - - -class ConfigProcessorForEmPy(ConfigProcessorForJinja2): - - """Processor for [empy:FILE] sections in a runtime configuration.""" - - SCHEME = "empy" - ASSIGN_TEMPL = "@{%s=%s}@\n" - COMMENT_TEMPL = "@# %s\n" - - -del ConfigProcessorForJinja2 # avoid loading it more than once diff --git a/metomi/rose/config_processors/jinja2.py b/metomi/rose/config_processors/jinja2.py deleted file mode 100644 index b9a964b749..0000000000 --- a/metomi/rose/config_processors/jinja2.py +++ /dev/null @@ -1,128 +0,0 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -# ----------------------------------------------------------------------------- -"""Process a section in a metomi.rose.config.ConfigNode into a Jinja2 template. -""" - -import filecmp -from metomi.rose.config_processor import ( - ConfigProcessError, ConfigProcessorBase) -from metomi.rose.env import env_var_process, UnboundEnvironmentVariableError -from metomi.rose.fs_util import FileSystemEvent -import os -from tempfile import NamedTemporaryFile - - -class ConfigProcessorForJinja2(ConfigProcessorBase): - - """Processor for [jinja2:FILE] sections in a runtime configuration.""" - - SCHEME = "jinja2" - ASSIGN_TEMPL = "{%% set %s=%s %%}\n" - COMMENT_TEMPL = "{# %s #}\n" - SCHEME_TEMPL = "#!%s\n" - MSG_DONE = "Rose Configuration Insertion: Done" - MSG_INIT = "Rose Configuration Insertion: Init" - - def process(self, conf_tree, item, orig_keys=None, orig_value=None, - **kwargs): - """Process [jinja2:*] in "conf_tree.node". - - Arguments: - conf_tree: - The relevant metomi.rose.config_tree.ConfigTree object with the - full configuration. - item: The current configuration item to process. - orig_keys: - The keys for locating the originating setting in conf_tree in a - recursive processing. None implies a top level call. - orig_value: The value of orig_keys in conf_tree. - **kwargs: - environ (dict): suite level environment variables. - """ - for s_key, s_node in sorted(conf_tree.node.value.items()): - if (s_node.is_ignored() or - not s_key.startswith(self.PREFIX) or - not s_node.value): - continue - target = s_key[len(self.PREFIX):] - source = os.path.join(conf_tree.files[target], target) - if not os.access(source, os.F_OK | os.R_OK): - continue - scheme_ln = self.SCHEME_TEMPL % self.SCHEME - msg_init_ln = self.COMMENT_TEMPL % self.MSG_INIT - msg_done_ln = self.COMMENT_TEMPL % self.MSG_DONE - tmp_file = NamedTemporaryFile() - tmp_file.write(scheme_ln.encode('UTF-8')) - tmp_file.write(msg_init_ln.encode('UTF-8')) - suite_variables = ['{'] - for key, node in sorted(s_node.value.items()): - if node.is_ignored(): - continue - try: - value = env_var_process(node.value) - except UnboundEnvironmentVariableError as exc: - raise ConfigProcessError([s_key, key], node.value, exc) - tmp_file.write( - (self.ASSIGN_TEMPL % (key, value)).encode('UTF-8')) - suite_variables.append(" '%s': %s," % (key, key)) - suite_variables.append('}') - suite_variables = self.ASSIGN_TEMPL % ('ROSE_SUITE_VARIABLES', - '\n'.join(suite_variables)) - tmp_file.write(suite_variables.encode('UTF-8')) - environ = kwargs.get("environ") - if environ: - tmp_file.write('[cylc]\n'.encode('UTF-8')) - tmp_file.write(' [[environment]]\n'.encode('UTF-8')) - for key, value in sorted(environ.items()): - tmp_file.write( - (' %s=%s\n' % (key, value)).encode('UTF-8')) - tmp_file.write(msg_done_ln.encode('UTF-8')) - line_n = 0 - is_in_old_insert = False - for line in open(source): - line_n += 1 - if line_n == 1 and line.strip().lower() == scheme_ln.strip(): - continue - elif line_n == 2 and line == msg_init_ln: - is_in_old_insert = True - continue - elif is_in_old_insert and line == msg_done_ln: - is_in_old_insert = False - continue - elif is_in_old_insert: - continue - tmp_file.write(line.encode('UTF-8')) - tmp_file.seek(0) - if os.access(target, os.F_OK | os.R_OK): - if filecmp.cmp(target, tmp_file.name): # identical - tmp_file.close() - continue - else: - self.manager.fs_util.delete(target) - # Write content to target - target_file = open(target, "w") - for line in tmp_file: - try: - target_file.write(line) - except TypeError: - target_file.write(line.decode()) - event = FileSystemEvent(FileSystemEvent.INSTALL, target) - self.manager.handle_event(event) - tmp_file.close() From 393275f49cf8697bea8850e5b401fa2f68db10a4 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Mon, 21 Dec 2020 14:36:11 +0000 Subject: [PATCH 05/78] remove obsolete rose2019 commands and functionalities bye bye: rose suite-hook bye bye: rose suite-log bye bye: rose cmp_source_vc bye bye: rose bush bye bye: bash auto-completion bye bye: root-dir --- .travis/shellchecker | 1 - ACKNOWLEDGEMENT.md | 5 - etc/rose-bash-completion | 460 ---------- etc/rose.conf.example | 42 - metomi/rose/bush_dao.py | 696 -------------- metomi/rose/cmp_source_vc.py | 113 --- metomi/rose/suite_hook.py | 149 --- metomi/rose/suite_log.py | 123 --- sbin/rosa-rpmbuild | 4 - sphinx/api/configuration/suite.rst | 56 -- sphinx/getting-started.rst | 23 - sphinx/glossary.rst | 1 - sphinx/installation.rst | 30 - t/lib/bash/test_header | 1 - t/rose-cli-bash-completion/00-static.t | 861 ------------------ t/rose-cli-bash-completion/01-suite-running.t | 88 -- .../01-suite-running/rose-suite.conf | 0 .../01-suite-running/suite.rc | 19 - t/rose-cli-bash-completion/test_header | 137 --- 19 files changed, 2809 deletions(-) delete mode 100644 etc/rose-bash-completion delete mode 100644 metomi/rose/bush_dao.py delete mode 100644 metomi/rose/cmp_source_vc.py delete mode 100644 metomi/rose/suite_hook.py delete mode 100644 metomi/rose/suite_log.py delete mode 100755 t/rose-cli-bash-completion/00-static.t delete mode 100755 t/rose-cli-bash-completion/01-suite-running.t delete mode 100644 t/rose-cli-bash-completion/01-suite-running/rose-suite.conf delete mode 100644 t/rose-cli-bash-completion/01-suite-running/suite.rc delete mode 100644 t/rose-cli-bash-completion/test_header diff --git a/.travis/shellchecker b/.travis/shellchecker index f6857d954b..829359cccf 100755 --- a/.travis/shellchecker +++ b/.travis/shellchecker @@ -77,7 +77,6 @@ main () { default () { # run a strict check on all "functional" scripts main . \ - --exclude etc/rose-bash-completion \ --exclude t \ -- -e SC1090 -e SC2119 -e SC2001 diff --git a/ACKNOWLEDGEMENT.md b/ACKNOWLEDGEMENT.md index bc7458284b..444eff5994 100644 --- a/ACKNOWLEDGEMENT.md +++ b/ACKNOWLEDGEMENT.md @@ -48,11 +48,6 @@ lib/html/static/js/bootstrap.min.js: released under the Apache 2.0 license. See . -lib/html/static/css/rose-bush.css -* Contains modified code from Twitter Bootstrap v2.3.1 released - under the Apache 2.0 licence. - See . - lib/html/static/css/jquery.dataTables.???: lib/html/static/images/sort_???: lib/html/static/js/jquery.dataTables.???: diff --git a/etc/rose-bash-completion b/etc/rose-bash-completion deleted file mode 100644 index cfdb7966b0..0000000000 --- a/etc/rose-bash-completion +++ /dev/null @@ -1,460 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# NAME -# rose-bash-completion -# -# SYNOPSIS -# . $ROSE_HOME/etc/rose-bash-completion -# -# DESCRIPTION -# Set Bash auto-completion for rose/rosie commands. -# -# Users should source this file in their ~/.bashrc, using something -# like this: -# if [[ $- =~ i && -f /path/to/rose-bash-completion ]]; then -# . /path/to/rose-bash-completion -# fi -# where /path/to/rose-bash-completion is replaced by the path to -# this file. -# -# Administrators may want to place this file in the -# /etc/bash_completion.d/ (or equivalent) directory. -#------------------------------------------------------------------------------- - -_rose() { - local current possible_subcommand rose_command ROSE_DIR ok_subcommands - local subcommand current_option_line current_option_arg_index - local current_option current_option_arg current_index suboption_lines - local suboption_sep_lines suboptions sub_args custom_function_name metavar - local formal_current_option - COMPREPLY=() - current="${COMP_WORDS[COMP_CWORD]}" - possible_subcommand="${COMP_WORDS[1]:-}" - rose_command="${COMP_WORDS[0]}" - rose --version 1>/dev/null 2>&1 || return 1 - if grep -q "bin/ros[^/]*$" <<<"$rose_command"; then - ROSE_DIR=$(dirname "$rose_command") - eval ROSE_DIR="$ROSE_DIR" - ROSE_DIR=$(dirname $(cd $ROSE_DIR && pwd -P)) - rose_command=$(basename "$rose_command") - else - ROSE_DIR=$(__rose_get_rose_dir) - fi - ok_subcommands=$(cd $ROSE_DIR/ && ls $rose_command-* | \ - sed "s/^$rose_command-//g") - - # Determine the subcommand (e.g. app-run for rose app-run) - subcommand= - if [[ -n $possible_subcommand ]]; then - for ok_subcommand in $ok_subcommands; do - if [[ $ok_subcommand == $possible_subcommand ]]; then - subcommand=$possible_subcommand - break - fi - done - fi - if [[ -n $subcommand && -L $ROSE_DIR/bin/rose-$subcommand ]]; then - subcommand=$(readlink $subcommand) - fi - - # Determine option or option argument completion. - current_option_line=$(printf "%s\n" "${COMP_WORDS[@]}" | grep -n "^-" | \ - tail -1) - if [[ -n $current_option_line ]]; then - current_option_arg_index=$( \ - grep -o "^[0-9][0-9]*" <<<"$current_option_line") - current_option=$(grep -o '\-.*$' <<<"$current_option_line") - current_option_arg="${COMP_WORDS[$current_option_arg_index]:-}" - if [[ $current_option_arg == "=" ]]; then - current_option_arg_index=$(( current_option_arg_index + 1 )) - current_option_arg="${COMP_WORDS[$current_option_arg_index]:-}" - fi - else - current_option_arg_index= - current_option_arg= - current_option= - fi - current_index=${#COMP_WORDS[@]} - current_index=$(( current_index - 1 )) - - # Supply any completion information if we can. - if [[ -n $subcommand ]]; then - suboption_lines=$(__${rose_command}_help $subcommand | \ - sed -n '/^ -/p') - suboption_sep_lines=$( \ - sed 's/^ *//g; s/, \(-\)/\n\1/g' <<<"$suboption_lines") - suboptions=$(sed 's/=/ /g' <<<"$suboption_sep_lines") - if grep -q '^'$current_option'$' <<<"$suboptions"; then - # The current option does not take an argument - forget about it. - current_option= - fi - if [[ -z $current_option ]] || \ - [[ $current_index -gt $current_option_arg_index && \ - -n $current_option_arg ]]; then - # No relevant current option - supply options and arguments. - custom_function_name="_${rose_command}_"${subcommand//-/_}_ARGS - sub_args= - if type -t $custom_function_name 1>/dev/null 2>&1; then - sub_args=$($custom_function_name "$current") - fi - suboptions=$( \ - sed 's/=.*$/=/g; s/ *.*$//g; s/ *$//g;' \ - <<<"$suboption_sep_lines") - COMPREPLY=( $(compgen -W "$suboptions $sub_args" -- "$current") ) - return 0 - fi - if grep -q "^$current_option\>" <<<"$suboptions" && \ - [[ $current != $current_option ]]; then - # The current string must be an argument to the option. - current_option_args= - if [[ $current != "=" ]]; then - current_option_args=$current - fi - metavar=$(sed -n "s/^$current_option \([^ ]*\)/\1/p" \ - <<<"$suboptions") - if [[ $metavar == "DIR" || $metavar == "PATH" || \ - $metavar = "FILE" ]]; then - return 0 - fi - formal_current_option=$( \ - sed -n "s/^ *\(-[^ =]*\).*, $current_option\b.*/\1/p" \ - <<<"$suboption_lines") - if [[ -n $formal_current_option ]]; then - current_option=$formal_current_option - fi - custom_function_name="_${rose_command}_"${subcommand//-/_} - custom_function_name+=${current_option//-/_} - if type -t $custom_function_name 1>/dev/null 2>&1; then - $custom_function_name "$current_option_args" - return $? - fi - COMPREPLY= - return 1 - fi - suboptions=$(sed 's/=.*$/=/g; s/ *.*$//g; s/ *$//g;' \ - <<<"$suboption_sep_lines") - COMPREPLY=( $(compgen -W "$suboptions" -- "$current") ) - return 0 - fi - # There is an incomplete subcommand or none at all - supply a list of them. - COMPREPLY=( $(compgen -W "$ok_subcommands" -- "$current") ) - return 0 -} - -_rose_app_run__app_mode() { - COMPREPLY=( $(compgen -W "fcm_make rose_ana rose_arch rose_prune" -- $1) ) -} - -_rose_app_run__command_key() { - local config_dir command_keys - config_dir=$(__rose_get_config_dir) - if [[ -d $config_dir ]]; then - command_keys=$(rose config --keys --file=$config_dir/rose-app.conf command 2>/dev/null) - COMPREPLY=( $(compgen -W "$command_keys" -- $1) ) - return 0 - fi - return 1 -} - -_rose_app_run__opt_conf_key() { - local config_dir opt_conf_keys - config_dir=$(__rose_get_config_dir) - if [[ -d $config_dir/opt ]]; then - opt_conf_keys=$(cd $config_dir/opt/ && ls rose-app-*.conf | \ - sed "s/rose-app-\(.*\).conf/\1/g") - COMPREPLY=( $(compgen -W "$opt_conf_keys" -- $1) ) - return 0 - fi - return 1 -} - -_rose_app_upgrade_ARGS() { - local config_dir meta_path meta_path_option all_versions_option - config_dir=$(__rose_get_config_dir) - meta_path=$(__rose_get_meta_path) - all_versions_option= - meta_path_option="--meta-path=$meta_path" - if [[ -z $meta_path ]]; then - meta_path_option= - fi - if printf "%s " "${COMP_WORDS[@]}" | \ - grep -q " --all-versions\| -a "; then - all_versions_option="--all-versions" - fi - rose app-upgrade $all_versions_option $meta_path_option \ - --config=$config_dir 2>/dev/null | sed 's/^. \(.*\)$/\1/g' -} - -_rose_host_select__rank_method() { - COMPREPLY=( $(compgen -W "load fs mem random" $1) ) -} - -_rose_macro_ARGS() { - local config_dir meta_path meta_path_option - config_dir=$(__rose_get_config_dir) - meta_path=$(__rose_get_meta_path) - meta_path_option="--meta-path=$meta_path" - if [[ -z $meta_path ]]; then - meta_path_option= - fi - rose macro -q $meta_path_option --config=$config_dir 2>/dev/null | \ - sed "s/^\[.*\] //g" -} - -_rose_metadata_graph_ARGS() { - local config_dir meta_path - config_dir=$(__rose_get_config_dir) - meta_path=$(__rose_get_meta_path) - if [[ -f $config_dir/rose-app.conf ]]; then - PATH=$PATH:$meta_path rose config --meta --keys \ - --file=$config_dir/rose-app.conf 2>/dev/null | \ - sed '/=/d' - else - rose config --keys --file=$config_dir/rose-meta.conf 2>/dev/null | \ - sed '/=/d' - fi -} - -_rose_metadata_graph__property() { - COMPREPLY=( $(compgen -W "trigger" -- $1) ) - return 0 -} - -_rose_stem__group() { - local config_dir groups - config_dir=$(__rose_stem_get_config_dir) - if [[ ! -e $config_dir/meta/rose-meta.conf ]]; then - return 1 - fi - groups=$(rose config --file=$config_dir/meta/rose-meta.conf \ - jinja2:suite.rc=RUN_NAMES widget[rose-config-edit] | \ - grep -o "\-\-choices.[^ ]*" | sed "s/--choices.//g;" | \ - tr '\n' ',' | tr ',' ' ') - tasks=$(rose config --file=$config_dir/meta/rose-meta.conf \ - jinja2:suite.rc=RUN_NAMES widget[rose-config-edit] | \ - tr ' ' '\n' | sed -n '/rose.config_editor.*/d; /^\([^-].*\)/p') - COMPREPLY=( $(compgen -W "$groups $tasks" -- $1) ) - return 0 -} - -# Should we separate this from _rose_stem__group? -_rose_stem__task() { - _rose_stem__group "$@" -} - -_rose_suite_hook__mail_cc() { - local users - prev_users=$1 - user_tail=${prev_users##*,} - user_head=${prev_users%,*} - users=$(__rose_get_users) - if [[ -n $user_tail ]] && grep -q "\<$user_tail\>" <<<"$users"; then - # A complete user name, now need a comma. - users=$(sed "s/^/$prev_users,/; s/ / $prev_users,/g" <<<"$users") - COMPREPLY=( $(compgen -W "$users" -- $prev_users) ) - return - fi - if [[ -n $user_head && $user_head != $prev_users ]]; then - users=$(sed "s/^/$user_head,/; s/ / $user_head,/g" <<<"$users") - fi - COMPREPLY=( $(compgen -W "$users" -- $prev_users) ) -} - -_rose_suite_log__name() { - local names - names=$(cylc print -xy 2>/dev/null) - COMPREPLY=( $(compgen -W "$names" -- $1) ) -} - -_rose_suite_log__user() { - local users - users=$(__rose_get_users) - COMPREPLY=( $(compgen -W "$users" -- $1) ) -} - -_rose_suite_run__host() { - local hosts - hosts=$(rose config rose-suite-run hosts 2>/dev/null) - hosts=$(__rose_get_expanded_hosts "$hosts") - COMPREPLY=( $(compgen -W "$hosts" -- $1) ) -} - -_rose_suite_run__opt_conf_key() { - local config_dir opt_conf_keys - config_dir=$(__rose_get_config_dir) - if [[ -d $config_dir/opt ]]; then - opt_conf_keys=$(cd $config_dir/opt/ && ls rose-suite-*.conf | \ - sed "s/rose-suite-\(.*\).conf/\1/g") - COMPREPLY=( $(compgen -W "$opt_conf_keys" -- $1) ) - return 0 - fi - return 1 -} - -_rose_suite_run__run() { - COMPREPLY=( $(compgen -W "reload restart run" -- $1) ) -} - -_rose_suite_shutdown__name() { - local names - names=$(cylc print -xy 2>/dev/null) - COMPREPLY=( $(compgen -W "$names" -- $1) ) -} - -_rose_suite_stop__name() { - _rose_suite_shutdown__name "$@" - return $? -} - -_rose_task_run__app_mode() { - _rose_app_run__app_mode "$@" - return $? -} - -_rose_task_run__command_key() { - _rose_app_run__command_key "$@" - return $? -} - -_rose_task_run__opt_conf_key() { - _rose_app_run__opt_conf_key "$@" - return $? -} - -_rose_test_battery_ARGS() { - cd $ROSE_DIR/t && ls -d ros* -} - -_rosie_create__prefix() { - __rosie_get_prefixes "$@" - return $? -} - -_rosie_go__prefix() { - __rosie_get_prefixes "$@" - return $? -} - -_rosie_lookup__prefix() { - __rosie_get_prefixes "$@" - return $? -} - -_rosie_ls__prefix() { - __rosie_get_prefixes "$@" - return $? -} - -__rose_get_config_dir() { - local config_dir - config_dir=$( \ - printf "%s " "${COMP_WORDS[@]}" | \ - sed -n "s/.*--config = \([^ ]*\).*/\1/p; s/.*-C *\([^ ]*\).*/\1/p") - if [[ -z $config_dir ]]; then - config_dir=$PWD - fi - eval config_dir="$config_dir" - cd $config_dir 1>/dev/null 2>&1 && pwd -P -} - -__rose_stem_get_config_dir() { - local config_dir - config_dir=$(__rose_get_config_dir) - if [[ ! -e $config_dir/rose-stem/suite.rc ]]; then - while [[ ! -d $config_dir/rose-stem ]] && \ - svn info $config_dir >/dev/null 2>&1; do - new_config_dir=$(cd $config_dir/.. && pwd -P) - if [[ $new_config_dir == $config_dir ]]; then - return 1 - fi - config_dir=$new_config_dir - done - if [[ ! -e $config_dir/rose-stem/suite.rc ]]; then - return 1 - fi - fi - config_dir=$config_dir/rose-stem - cd $config_dir 1>/dev/null 2>&1 && pwd -P -} - -__rose_get_expanded_hosts() { - for host in "$@"; do - rose config rose-host-select group{$host} || echo $host - done -} - -__rose_get_meta_path() { - local meta_path - meta_path=$( \ - printf "%s " "${COMP_WORDS[@]}" | \ - sed -n "s/.*--meta-path = \([^ ]*\).*/\1/p; s/.*-M *\([^ ]*\).*/\1/p") - if [[ -n $meta_path ]]; then - eval meta_path="$meta_path" - cd $meta_path 1>/dev/null 2>&1 && pwd -P - fi -} - -__rose_get_rose_dir() { - rose --version | sed "s/.*(\(.*\))/\1/" -} - -__rose_get_users() { - getent passwd | cut -d: -f1 | LANG=C sort -u -} - -__rose_help() { - local subcommand - subcommand=$1 - if type -t _rose_help__${subcommand//-/_}; then - _rose_help__${subcommand//-/_} | sed -n '/^OPTIONS$/,/^[^ ]$/p;' - return - fi - rose help $subcommand 2>/dev/null -} - -__rosie_get_prefixes() { - local prefixes - prefixes=$(rose config --keys rosie-id | \ - sed -n "s/^prefix-location\.\(.*\)/\1/p") - COMPREPLY=( $(compgen -W "$prefixes" -- $1) ) -} - -__rosie_help() { - local subcommand - subcommand=$1 - if type -t _rosie_help__${subcommand//-/_}; then - _rosie_help__${subcommand//-/_} - return - fi - rosie help $subcommand -} - -_rose_help__stem() { - rose help stem - rose help suite-run -} - -_rose_help__task_run() { - rose help task-run - rose help app-run - rose help task-env -} - -complete -o bashdefault -o default -o nospace -F _rose rose rosie diff --git a/etc/rose.conf.example b/etc/rose.conf.example index 6c4cce4541..d9089eb6c4 100644 --- a/etc/rose.conf.example +++ b/etc/rose.conf.example @@ -111,42 +111,6 @@ notification-from=EMAIL-ADDRESS user-tool=ldap|passwd -# Configuration for Rose Bush server. -[rose-bush] -# :default: 100 -# -# Cycles list view: default number of cycles per page. -cycles-per-page=NUMBER -# :default: The server's host name. -# -# An alternative host name. -host=NAME -# :default: 15 -# -# Job list view: default number of jobs per page. -jobs-per-page=NUMBER -# :default: 300 -# -# Job list view: maximum number of jobs per page. -jobs-per-page-max=NUMBER -# Image logo attributes, can be any HTML ```` tag attributes -# e.g: ``logo=src="http://server/my-rose-bush-logo.png" alt="My Rose Bush -# Logo"``. -logo=HTML-IMG-ATTRIBUTE ... -# :default: 100 -# -# Suites list view: default number of suites per page. -suites-per-page=NUMBER -# :default: "Rose Bush" -# -# An alternative service title. -title=TITLE -# :default: 10485760 -# -# File view: maximum viewable file size in bytes. -view-size-max=BYTES - - # Configuration specific to :ref:`command-rose-config-diff`. [rose-config-diff] # :default: "title,ns,description,help" @@ -211,12 +175,6 @@ timeout=FLOAT automatic-options=VARIABLE=VALUE -# Configuration related to :ref:`command-rose-suite-log`. -[rose-suite-log] -# URL to the site's Rose Bush web service. -rose-bush=URL - - # Configuration related to :ref:`command-rose-mpi-launch`. [rose-mpi-launch] # Specify a list of launcher commands e.g:: diff --git a/metomi/rose/bush_dao.py b/metomi/rose/bush_dao.py deleted file mode 100644 index fbe45abf9f..0000000000 --- a/metomi/rose/bush_dao.py +++ /dev/null @@ -1,696 +0,0 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -# ----------------------------------------------------------------------------- -"""Rose Bush: data access to cylc suite runtime databases.""" - -from fnmatch import fnmatch -from glob import glob -import os -import re -import tarfile - -from metomi.rose.suite_engine_procs.cylc import CylcProcessor, CylcSuiteDAO - - -class RoseBushDAO(object): - - """Rose Bush: data access to cylc suite runtime databases.""" - - CYCLE_ORDERS = {"time_desc": " DESC", "time_asc": " ASC"} - JOB_ORDERS = { - "time_desc": "time DESC, submit_num DESC, name DESC, cycle DESC", - "time_asc": "time ASC, submit_num ASC, name ASC, cycle ASC", - "cycle_desc_name_asc": "cycle DESC, name ASC, submit_num DESC", - "cycle_desc_name_desc": "cycle DESC, name DESC, submit_num DESC", - "cycle_asc_name_asc": "cycle ASC, name ASC, submit_num DESC", - "cycle_asc_name_desc": "cycle ASC, name DESC, submit_num DESC", - "name_asc_cycle_asc": "name ASC, cycle ASC, submit_num DESC", - "name_desc_cycle_asc": "name DESC, cycle ASC, submit_num DESC", - "name_asc_cycle_desc": "name ASC, cycle DESC, submit_num DESC", - "name_desc_cycle_desc": "name DESC, cycle DESC, submit_num DESC", - "time_submit_desc": ( - "time_submit DESC, submit_num DESC, name DESC, cycle DESC"), - "time_submit_asc": ( - "time_submit ASC, submit_num DESC, name DESC, cycle DESC"), - "time_run_desc": ( - "time_run DESC, submit_num DESC, name DESC, cycle DESC"), - "time_run_asc": ( - "time_run ASC, submit_num DESC, name DESC, cycle DESC"), - "time_run_exit_desc": ( - "time_run_exit DESC, submit_num DESC, name DESC, cycle DESC"), - "time_run_exit_asc": ( - "time_run_exit ASC, submit_num DESC, name DESC, cycle DESC"), - "duration_queue_desc": ( - "(CAST(strftime('%s', time_run) AS NUMERIC) -" + - " CAST(strftime('%s', time_submit) AS NUMERIC)) DESC, " + - "submit_num DESC, name DESC, cycle DESC"), - "duration_queue_asc": ( - "(CAST(strftime('%s', time_run) AS NUMERIC) -" + - " CAST(strftime('%s', time_submit) AS NUMERIC)) ASC, " + - "submit_num DESC, name DESC, cycle DESC"), - "duration_run_desc": ( - "(CAST(strftime('%s', time_run_exit) AS NUMERIC) -" + - " CAST(strftime('%s', time_run) AS NUMERIC)) DESC, " + - "submit_num DESC, name DESC, cycle DESC"), - "duration_run_asc": ( - "(CAST(strftime('%s', time_run_exit) AS NUMERIC) -" + - " CAST(strftime('%s', time_run) AS NUMERIC)) ASC, " + - "submit_num DESC, name DESC, cycle DESC"), - "duration_queue_run_desc": ( - "(CAST(strftime('%s', time_run_exit) AS NUMERIC) -" + - " CAST(strftime('%s', time_submit) AS NUMERIC)) DESC, " + - "submit_num DESC, name DESC, cycle DESC"), - "duration_queue_run_asc": ( - "(CAST(strftime('%s', time_run_exit) AS NUMERIC) -" + - " CAST(strftime('%s', time_submit) AS NUMERIC)) ASC, " + - "submit_num DESC, name DESC, cycle DESC"), - } - JOB_STATUS_COMBOS = { - "all": "", - "submitted": "submit_status == 0 AND time_run IS NULL", - "submitted,running": "submit_status == 0 AND run_status IS NULL", - "submission-failed": "submit_status == 1", - "submission-failed,failed": "submit_status == 1 OR run_status == 1", - "running": "time_run IS NOT NULL AND run_status IS NULL", - "running,succeeded,failed": "time_run IS NOT NULL", - "succeeded": "run_status == 0", - "succeeded,failed": "run_status IS NOT NULL", - "failed": "run_status == 1", - } - REC_CYCLE_QUERY_OP = re.compile(r"\A(before |after |[<>]=?)(.+)\Z") - REC_SEQ_LOG = re.compile(r"\A(.+\.)([^\.]+)(\.[^\.]+)\Z") - SUITE_CONF = CylcProcessor.SUITE_CONF - SUITE_DIR_REL_ROOT = CylcProcessor.SUITE_DIR_REL_ROOT - TASK_STATUS_GROUPS = { - "active": [ - "ready", "queued", "submitting", "submitted", "submit-retrying", - "running", "retrying"], - "fail": ["submission failed", "failed"], - "success": ["expired", "succeeded"]} - TASK_STATUSES = ( - "runahead", "waiting", "held", "queued", "ready", "expired", - "submitted", "submit-failed", "submit-retrying", "running", - "succeeded", "failed", "retrying") - - def __init__(self): - self.daos = {} - - def get_suite_broadcast_states(self, user_name, suite_name): - """Return broadcast states of a suite. - - [[point, name, key, value], ...] - - """ - # Check if "broadcast_states" table is available or not - if not self._db_has_table(user_name, suite_name, "broadcast_states"): - return - - broadcast_states = [] - for row in self._db_exec( - user_name, suite_name, - "SELECT point,namespace,key,value FROM broadcast_states" + - " ORDER BY point ASC, namespace ASC, key ASC"): - point, namespace, key, value = row - broadcast_states.append([point, namespace, key, value]) - return broadcast_states - - def get_suite_broadcast_events(self, user_name, suite_name): - """Return broadcast events of a suite. - - [[time, change, point, name, key, value], ...] - - """ - # Check if "broadcast_events" table is available or not - if not self._db_has_table(user_name, suite_name, "broadcast_events"): - return {} - - broadcast_events = [] - for row in self._db_exec( - user_name, suite_name, - "SELECT time,change,point,namespace,key,value" + - " FROM broadcast_events" + - " ORDER BY time DESC, point DESC, namespace DESC, key DESC"): - time_, change, point, namespace, key, value = row - broadcast_events.append( - (time_, change, point, namespace, key, value)) - return broadcast_events - - @staticmethod - def get_suite_dir_rel(suite_name, *paths): - """Return the relative path to the suite running directory. - - paths -- if specified, are added to the end of the path. - """ - return CylcProcessor.get_suite_dir_rel(suite_name, *paths) - - def get_suite_job_entries( - self, user_name, suite_name, cycles, tasks, task_status, - job_status, order, limit, offset): - """Query suite runtime database to return a listing of task jobs. - - user -- A string containing a valid user ID - suite -- A string containing a valid suite ID - cycles -- If specified, display only task jobs matching these cycles. - A value in the list can be a cycle, the string "before|after - CYCLE", or a glob to match cycles. - tasks -- If specified, display only jobs with task names matching - these names. Values can be a valid task name or a glob like - pattern for matching valid task names. - task_status -- If specified, it should be a list of task statuses. - Display only jobs in the specified list. If not - specified, display all jobs. - job_status -- If specified, must be a string matching a key in - RoseBushDAO.JOB_STATUS_COMBOS. Select jobs by their - statuses. - order -- Order search in a predetermined way. A valid value is one of - the keys in RoseBushDAO.ORDERS. - limit -- Limit number of returned entries - offset -- Offset entry number - - Return (entries, of_n_entries) where: - entries -- A list of matching entries - of_n_entries -- Total number of entries matching query - - Each entry is a dict: - {"cycle": cycle, "name": name, "submit_num": submit_num, - "events": [time_submit, time_init, time_exit], - "task_status": task_status, - "logs": {"script": {"path": path, "path_in_tar", path_in_tar, - "size": size, "mtime": mtime}, - "out": {...}, - "err": {...}, - ...}} - """ - where_expr, where_args = self._get_suite_job_entries_where( - cycles, tasks, task_status, job_status) - - # Get number of entries - of_n_entries = 0 - stmt = ("SELECT COUNT(*)" + - " FROM task_jobs JOIN task_states USING (name, cycle)" + - where_expr) - for row in self._db_exec(user_name, suite_name, stmt, where_args): - of_n_entries = row[0] - break - else: - self._db_close(user_name, suite_name) - return ([], 0) - - # Get entries - entries = [] - entry_of = {} - stmt = ("SELECT" + - " task_states.time_updated AS time," + - " cycle, name," + - " task_jobs.submit_num AS submit_num," + - " task_states.submit_num AS submit_num_max," + - " task_states.status AS task_status," + - " time_submit, submit_status," + - " time_run, time_run_exit, run_signal, run_status," + - " user_at_host, batch_sys_name, batch_sys_job_id" + - " FROM task_jobs JOIN task_states USING (cycle, name)" + - where_expr + - " ORDER BY " + - self.JOB_ORDERS.get(order, self.JOB_ORDERS["time_desc"])) - limit_args = [] - if limit: - stmt += " LIMIT ? OFFSET ?" - limit_args = [limit, offset] - for row in self._db_exec( - user_name, suite_name, stmt, where_args + limit_args): - ( - cycle, name, submit_num, submit_num_max, task_status, - time_submit, submit_status, - time_run, time_run_exit, run_signal, run_status, - user_at_host, batch_sys_name, batch_sys_job_id - ) = row[1:] - entry = { - "cycle": cycle, - "name": name, - "submit_num": submit_num, - "submit_num_max": submit_num_max, - "events": [time_submit, time_run, time_run_exit], - "task_status": task_status, - "submit_status": submit_status, - "run_signal": run_signal, - "run_status": run_status, - "host": user_at_host, - "submit_method": batch_sys_name, - "submit_method_id": batch_sys_job_id, - "logs": {}, - "seq_logs_indexes": {}} - entries.append(entry) - entry_of[(cycle, name, submit_num)] = entry - self._db_close(user_name, suite_name) - if entries: - self._get_job_logs(user_name, suite_name, entries, entry_of) - return (entries, of_n_entries) - - def _get_suite_job_entries_where( - self, cycles, tasks, task_status, job_status): - """Helper for get_suite_job_entries. - - Get query's "WHERE" expression and its arguments. - """ - where_exprs = [] - where_args = [] - if cycles: - cycle_where_exprs = [] - for cycle in cycles: - match = self.REC_CYCLE_QUERY_OP.match(cycle) - if match: - operator, operand = match.groups() - where_args.append(operand) - if operator == "before ": - cycle_where_exprs.append("cycle <= ?") - elif operator == "after ": - cycle_where_exprs.append("cycle >= ?") - else: - cycle_where_exprs.append("cycle %s ?" % operator) - else: - where_args.append(cycle) - cycle_where_exprs.append("cycle GLOB ?") - where_exprs.append(" OR ".join(cycle_where_exprs)) - if tasks: - where_exprs.append(" OR ".join(["name GLOB ?"] * len(tasks))) - where_args += tasks - if task_status: - task_status_where_exprs = [] - for item in task_status: - task_status_where_exprs.append("task_states.status == ?") - where_args.append(item) - where_exprs.append(" OR ".join(task_status_where_exprs)) - try: - job_status_where = self.JOB_STATUS_COMBOS[job_status] - except KeyError: - pass - else: - if job_status_where: - where_exprs.append(job_status_where) - if where_exprs: - return (" WHERE (" + ") AND (".join(where_exprs) + ")", where_args) - else: - return ("", where_args) - - def _get_job_logs(self, user_name, suite_name, entries, entry_of): - """Helper for "get_suite_job_entries". Get job logs. - - Recent job logs are likely to be in the file system, so we can get a - listing of the relevant "log/job/CYCLE/NAME/SUBMI_NUM/" directory. - Older job logs may be archived in "log/job-CYCLE.tar.gz", we should - only open each relevant TAR file once to obtain a listing for all - relevant entries of that cycle. - - Modify each entry in entries. - """ - prefix = "~" - if user_name: - prefix += user_name - user_suite_dir = os.path.expanduser(os.path.join( - prefix, self.get_suite_dir_rel(suite_name))) - try: - fs_log_cycles = os.listdir( - os.path.join(user_suite_dir, "log", "job")) - except OSError: - fs_log_cycles = [] - targzip_log_cycles = [] - for name in glob(os.path.join(user_suite_dir, "log", "job-*.tar.gz")): - targzip_log_cycles.append(os.path.basename(name)[4:-7]) - - relevant_targzip_log_cycles = [] - for entry in entries: - if entry["cycle"] in fs_log_cycles: - pathd = "log/job/%(cycle)s/%(name)s/%(submit_num)02d" % entry - try: - filenames = os.listdir(os.path.join(user_suite_dir, pathd)) - except OSError: - continue - for filename in filenames: - try: - stat = os.stat( - os.path.join(user_suite_dir, pathd, filename)) - except OSError: - pass - else: - entry["logs"][filename] = { - "path": "/".join([pathd, filename]), - "path_in_tar": None, - "mtime": int(stat.st_mtime), # int precise enough - "size": stat.st_size, - "exists": True, - "seq_key": None} - continue - if entry["cycle"] in targzip_log_cycles: - if entry["cycle"] not in relevant_targzip_log_cycles: - relevant_targzip_log_cycles.append(entry["cycle"]) - - for cycle in relevant_targzip_log_cycles: - path = os.path.join("log", "job-%s.tar.gz" % cycle) - tar = tarfile.open(os.path.join(user_suite_dir, path), "r:gz") - for member in tar.getmembers(): - # member.name expected to be "job/cycle/task/submit_num/*" - if not member.isfile(): - continue - try: - cycle_str, name, submit_num_str = ( - member.name.split("/", 4)[1:4]) - entry = entry_of[(cycle_str, name, int(submit_num_str))] - except (KeyError, ValueError): - continue - entry["logs"][os.path.basename(member.name)] = { - "path": path, - "path_in_tar": member.name, - "mtime": int(member.mtime), # too precise otherwise - "size": member.size, - "exists": True, - "seq_key": None} - - # Sequential logs - for entry in entries: - for filename, filename_items in entry["logs"].items(): - seq_log_match = self.REC_SEQ_LOG.match(filename) - if not seq_log_match: - continue - head, index_str, tail = seq_log_match.groups() - seq_key = head + "*" + tail - filename_items["seq_key"] = seq_key - if seq_key not in entry["seq_logs_indexes"]: - entry["seq_logs_indexes"][seq_key] = {} - entry["seq_logs_indexes"][seq_key][index_str] = filename - for seq_key, indexes in entry["seq_logs_indexes"].items(): - # Only one item, not a sequence - if len(indexes) <= 1: - entry["seq_logs_indexes"].pop(seq_key) - # All index_str are numbers, convert key to integer so - # the template can sort them as numbers - try: - int_indexes = {} - for index_str, filename in indexes.items(): - int_indexes[int(index_str)] = filename - entry["seq_logs_indexes"][seq_key] = int_indexes - except ValueError: - pass - for filename, log_dict in entry["logs"].items(): - # Unset seq_key for singular items - if log_dict["seq_key"] not in entry["seq_logs_indexes"]: - log_dict["seq_key"] = None - - def get_suite_logs_info(self, user_name, suite_name): - """Return the information of the suite logs. - - Return a tuple that looks like: - ("cylc-run", - {"err": {"path": "log/suite/err", "mtime": mtime, "size": size}, - "log": {"path": "log/suite/log", "mtime": mtime, "size": size}, - "out": {"path": "log/suite/out", "mtime": mtime, "size": size}}) - - """ - logs_info = {} - prefix = "~" - if user_name: - prefix += user_name - d_rel = self.get_suite_dir_rel(suite_name) - dir_ = os.path.expanduser(os.path.join(prefix, d_rel)) - # Get cylc files. - cylc_files = ["cylc-suite-env", "suite.rc", "suite.rc.processed"] - for key in cylc_files: - f_name = os.path.join(dir_, key) - if os.path.isfile(f_name): - f_stat = os.stat(f_name) - logs_info[key] = {"path": key, - "mtime": f_stat.st_mtime, - "size": f_stat.st_size} - # Get cylc suite log files. - log_files = ["log/suite/err", "log/suite/log", "log/suite/out"] - for key in log_files: - f_name = os.path.join(dir_, key) - if os.path.isfile(f_name): - try: - link_path = os.readlink(f_name) - except OSError: - link_path = f_name - old_logs = [] # Old log naming system. - new_logs = [] # New log naming system. - # TODO: Post migration to cylc this logic can be replaced by: - # `from cylc.suite_logging import get_logs` (superior) - for log in glob(f_name + '.*'): - log_name = os.path.basename(log) - if log_name == link_path: - continue - if len(log_name.split('.')[1]) > 3: - new_logs.append(os.path.join("log", "suite", log_name)) - else: - old_logs.append(os.path.join("log", "suite", log_name)) - new_logs.sort(reverse=True) - old_logs.sort() - f_stat = os.stat(f_name) - logs_info[key] = {"path": key, - "paths": [key] + new_logs + old_logs, - "mtime": f_stat.st_mtime, - "size": f_stat.st_size} - return ("cylc", logs_info) - - def get_suite_cycles_summary( - self, user_name, suite_name, order, limit, offset): - """Return a the state summary (of each cycle) of a user's suite. - - user -- A string containing a valid user ID - suite -- A string containing a valid suite ID - limit -- Limit number of returned entries - offset -- Offset entry number - - Return (entries, of_n_entries), where entries is a data structure that - looks like: - [ { "cycle": cycle, - "n_states": { - "active": N, "success": M, "fail": L, "job_fails": K, - }, - "max_time_updated": T2, - }, - # ... - ] - where: - * cycle is a date-time cycle label - * N, M, L, K are the numbers of tasks in given states - * T2 is the time when last update time of (a task in) the cycle - - and of_n_entries is the total number of entries. - - """ - of_n_entries = 0 - stmt = ("SELECT COUNT(DISTINCT cycle) FROM task_states WHERE " + - "submit_num > 0") - for row in self._db_exec(user_name, suite_name, stmt): - of_n_entries = row[0] - break - if not of_n_entries: - return ([], 0) - - # Not strictly correct, if cycle is in basic date-only format, - # but should not matter for most cases - integer_mode = False - stmt = "SELECT cycle FROM task_states LIMIT 1" - for row in self._db_exec(user_name, suite_name, stmt): - integer_mode = row[0].isdigit() - break - - prefix = "~" - if user_name: - prefix += user_name - user_suite_dir = os.path.expanduser(os.path.join( - prefix, self.get_suite_dir_rel(suite_name))) - targzip_log_cycles = [] - try: - for item in os.listdir(os.path.join(user_suite_dir, "log")): - if item.startswith("job-") and item.endswith(".tar.gz"): - targzip_log_cycles.append(item[4:-7]) - except OSError: - pass - - states_stmt = {} - for key, names in self.TASK_STATUS_GROUPS.items(): - states_stmt[key] = " OR ".join( - ["status=='%s'" % (name) for name in names]) - stmt = ( - "SELECT" + - " cycle," + - " max(time_updated)," + - " sum(" + states_stmt["active"] + ") AS n_active," + - " sum(" + states_stmt["success"] + ") AS n_success," - " sum(" + states_stmt["fail"] + ") AS n_fail" - " FROM task_states" + - " GROUP BY cycle") - if integer_mode: - stmt += " ORDER BY cast(cycle as number)" - else: - stmt += " ORDER BY cycle" - stmt += self.CYCLE_ORDERS.get(order, self.CYCLE_ORDERS["time_desc"]) - stmt_args = [] - if limit: - stmt += " LIMIT ? OFFSET ?" - stmt_args += [limit, offset] - entry_of = {} - entries = [] - for row in self._db_exec(user_name, suite_name, stmt, stmt_args): - cycle, max_time_updated, n_active, n_success, n_fail = row - if n_active or n_success or n_fail: - entry_of[cycle] = { - "cycle": cycle, - "has_log_job_tar_gz": cycle in targzip_log_cycles, - "max_time_updated": max_time_updated, - "n_states": { - "active": n_active, - "success": n_success, - "fail": n_fail, - "job_active": 0, - "job_success": 0, - "job_fail": 0, - }, - } - entries.append(entry_of[cycle]) - - # Check if "task_jobs" table is available or not. - # Note: A single query with a JOIN is probably a more elegant solution. - # However, timing tests suggest that it is cheaper with 2 queries. - # This 2nd query may return more results than is necessary, but should - # be a very cheap query as it does not have to do a lot of work. - if self._db_has_table(user_name, suite_name, "task_jobs"): - stmt = ( - "SELECT cycle," + - " sum(" + self.JOB_STATUS_COMBOS["submitted,running"] + - ") AS n_job_active," + - " sum(" + self.JOB_STATUS_COMBOS["succeeded"] + - ") AS n_job_success," + - " sum(" + self.JOB_STATUS_COMBOS["submission-failed,failed"] + - ") AS n_job_fail" + - " FROM task_jobs GROUP BY cycle") - else: - fail_events_stmt = " OR ".join( - ["event=='%s'" % (name) - for name in self.TASK_STATUS_GROUPS["fail"]]) - stmt = ( - "SELECT cycle," + - " sum(" + fail_events_stmt + ") AS n_job_fail" + - " FROM task_events GROUP BY cycle") - for cycle, n_job_active, n_job_success, n_job_fail in self._db_exec( - user_name, suite_name, stmt): - try: - entry_of[cycle]["n_states"]["job_active"] = n_job_active - entry_of[cycle]["n_states"]["job_success"] = n_job_success - entry_of[cycle]["n_states"]["job_fail"] = n_job_fail - except KeyError: - pass - else: - del entry_of[cycle] - if not entry_of: - break - self._db_close(user_name, suite_name) - - return entries, of_n_entries - - def get_suite_state_summary(self, user_name, suite_name): - """Return a the state summary of a user's suite. - - Return {"is_running": b, "is_failed": b, "server": s} - where: - * is_running is a boolean to indicate if the suite is running - * is_failed: a boolean to indicate if any tasks (submit) failed - * server: host:port of server, if available - - """ - ret = { - "is_running": False, - "is_failed": False, - "server": None} - dao = self._db_init(user_name, suite_name) - if not os.access(dao.db_f_name, os.F_OK | os.R_OK): - return ret - - port_file_path = os.path.expanduser( - os.path.join( - "~" + user_name, "cylc-run", suite_name, ".service", - "contact")) - try: - host = None - port_str = None - for line in open(port_file_path): - key, value = [item.strip() for item in line.split("=", 1)] - if key == "CYLC_SUITE_HOST": - host = value - elif key == "CYLC_SUITE_PORT": - port_str = value - except (IOError, ValueError): - pass - else: - if host and port_str: - ret["is_running"] = True - ret["server"] = host.split(".", 1)[0] + ":" + port_str - - stmt = "SELECT status FROM task_states WHERE status GLOB ? LIMIT 1" - stmt_args = ["*failed"] - for _ in self._db_exec(user_name, suite_name, stmt, stmt_args): - ret["is_failed"] = True - break - self._db_close(user_name, suite_name) - - return ret - - @staticmethod - def is_conf(path): - """Return "cylc-suite-rc" if path is a Cylc suite.rc file.""" - if fnmatch(os.path.basename(path), "suite*.rc*"): - return "cylc-suite-rc" - - @classmethod - def parse_job_log_rel_path(cls, f_name): - """Return (cycle, task, submit_num, ext).""" - return CylcProcessor.parse_job_log_rel_path(f_name) - - def _db_close(self, user_name, suite_name): - """Close a named database connection.""" - key = (user_name, suite_name) - if self.daos.get(key) is not None: - self.daos[key].close() - - def _db_exec(self, user_name, suite_name, stmt, stmt_args=None): - """Execute a query on a named database connection.""" - daos = self._db_init(user_name, suite_name) - return daos.execute(stmt, stmt_args) - - def _db_has_table(self, user_name, suite_name, table_name): - """Return True if table_name exists in the suite database.""" - cursor = self._db_exec( - user_name, suite_name, - "SELECT name FROM sqlite_master WHERE name==?", [table_name]) - return cursor.fetchone() is not None - - def _db_init(self, user_name, suite_name): - """Initialise a named database connection.""" - key = (user_name, suite_name) - if key not in self.daos: - prefix = "~" - if user_name: - prefix += user_name - for name in [os.path.join("log", "db"), "cylc-suite.db"]: - db_f_name = os.path.expanduser(os.path.join( - prefix, self.get_suite_dir_rel(suite_name, name))) - self.daos[key] = CylcSuiteDAO(db_f_name) - if os.path.exists(db_f_name): - break - return self.daos[key] diff --git a/metomi/rose/cmp_source_vc.py b/metomi/rose/cmp_source_vc.py deleted file mode 100644 index e08180995c..0000000000 --- a/metomi/rose/cmp_source_vc.py +++ /dev/null @@ -1,113 +0,0 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -# ----------------------------------------------------------------------------- -"""Tool to determine whether source of an installed suite has changed.""" - -from difflib import unified_diff -import os -from io import StringIO -import sys -import traceback - -from metomi.rose.opt_parse import RoseOptionParser -from metomi.rose.popen import RosePopener -from metomi.rose.reporter import Reporter -from metomi.rose.run_source_vc import write_source_vc_info -from metomi.rose.suite_engine_proc import SuiteEngineProcessor - - -class SuiteVCComparator(object): - """Tool to determine whether source of an installed suite has changed.""" - - def __init__(self, event_handler=None): - self.event_handler = event_handler - self.popen = RosePopener(self.event_handler) - self.suite_engine_proc = SuiteEngineProcessor.get_processor( - event_handler=self.event_handler, popen=self.popen) - - def cmp_source_vc_info(self, suite_name): - """Compare source VC with installed "log/rose-suite-run.version". - - Return (list): Result in unified diff format or None if irrelevant. - - Args: - suite_name (str): suite name. - """ - rund = self.suite_engine_proc.get_suite_dir(suite_name) - old_info_file_name = self.suite_engine_proc.get_suite_dir( - suite_name, 'log', 'rose-suite-run.version') - try: - old_info = open(old_info_file_name).read().splitlines() - except IOError: # Cannot find/read version file - return None - else: - if len(old_info) <= 1: # No VC information - return None - handle = StringIO() - write_source_vc_info(old_info[0], handle, self.popen) - new_info = handle.getvalue().splitlines() - return unified_diff( - old_info, new_info, - "installed @ %s" % rund, "latest @ %s" % old_info[0]) - - def handle_event(self, *args, **kwargs): - """Handle event.""" - if callable(self.event_handler): - self.event_handler(*args, **kwargs) - - -def main(): - """Launcher for the CLI.""" - opt_parser = RoseOptionParser() - opt_parser.add_my_options('name') - opts, args = opt_parser.parse_args(sys.argv[1:]) - event_handler = Reporter(opts.verbosity - opts.quietness) - suite_vc_cmp = SuiteVCComparator(event_handler) - suite_name = opts.name - if not suite_name and args: - suite_name = args[0] - if not suite_name: - suite_name = os.getenv(suite_vc_cmp.suite_engine_proc.SUITE_NAME_ENV) - if not suite_name: - opt_parser.print_usage(sys.stderr) - sys.exit(2) - try: - lines = suite_vc_cmp.cmp_source_vc_info(suite_name=suite_name) - except Exception as exc: - event_handler(exc) - traceback.print_exc() - sys.exit(2) - else: - if lines is None: - event_handler( - '%s: rose-suite-run.version: VC info not found' % ( - suite_name), - kind=Reporter.KIND_ERR, level=Reporter.FAIL) - sys.exit(2) - lines = list(line for line in lines) - for line in lines: - event_handler('%s\n' % line, prefix='') - if lines: - sys.exit(1) - else: - sys.exit(0) - - -if __name__ == "__main__": - main() diff --git a/metomi/rose/suite_hook.py b/metomi/rose/suite_hook.py deleted file mode 100644 index 306db4482b..0000000000 --- a/metomi/rose/suite_hook.py +++ /dev/null @@ -1,149 +0,0 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -# ----------------------------------------------------------------------------- - -"""Hook functionalities for a suite.""" - -from email.mime.text import MIMEText -import os -import pwd -from metomi.rose.opt_parse import RoseOptionParser -from metomi.rose.popen import RosePopener -from metomi.rose.reporter import Reporter -from metomi.rose.resource import ResourceLocator -from metomi.rose.suite_engine_proc import SuiteEngineProcessor -from smtplib import SMTP, SMTPException -import socket - - -class RoseSuiteHook(object): - - """Hook functionalities for a suite.""" - - def __init__(self, event_handler=None, popen=None, suite_engine_proc=None): - self.event_handler = event_handler - if popen is None: - popen = RosePopener(event_handler) - self.popen = popen - if suite_engine_proc is None: - suite_engine_proc = SuiteEngineProcessor.get_processor( - event_handler=event_handler, popen=popen) - self.suite_engine_proc = suite_engine_proc - - def handle_event(self, *args, **kwargs): - """Call self.event_handler if it is callabale.""" - if callable(self.event_handler): - return self.event_handler(*args, **kwargs) - - def run(self, suite_name, task_id, hook_event, hook_message=None, - should_mail=False, mail_cc_list=None, should_shutdown=False, - should_retrieve_job_logs=False): - """ - Invoke the hook for a suite. - - 1. For a task hook, if the task runs remotely, retrieve its log from - the remote host. - 2. If "should_mail", send an email notification to the current user, - and those in the "mail_cc_list". - 3. If "should_shutdown", shut down the suite. - - """ - # Retrieve log and populate job logs database - task_ids = [] - if task_id and should_retrieve_job_logs: - task_ids = [task_id] - self.suite_engine_proc.job_logs_pull_remote(suite_name, task_ids) - - # Send email notification if required - email_exc = None - if should_mail: - text = "" - if task_id: - text += "Task: %s\n" % task_id - if hook_message: - text += "Message: %s\n" % hook_message - url = self.suite_engine_proc.get_suite_log_url(None, suite_name) - text += "See: %s\n" % (url) - user = pwd.getpwuid(os.getuid()).pw_name - conf = ResourceLocator.default().get_conf() - host = conf.get_value(["rose-suite-hook", "email-host"], - default="localhost") - msg = MIMEText(text) - msg["From"] = user + "@" + host - msg["To"] = msg["From"] - if mail_cc_list: - mail_cc_addresses = [] - for mail_cc_address in mail_cc_list: - if "@" not in mail_cc_address: - mail_cc_address += "@" + host - mail_cc_addresses.append(mail_cc_address) - msg["Cc"] = ", ".join(mail_cc_addresses) - mail_cc_list = mail_cc_addresses - else: - mail_cc_list = [] - msg["Subject"] = "[%s] %s" % (hook_event, suite_name) - smtp_host = conf.get_value(["rose-suite-hook", "smtp-host"], - default="localhost") - try: - smtp = SMTP(smtp_host) - smtp.sendmail( - msg["From"], [msg["To"]] + mail_cc_list, msg.as_string()) - smtp.quit() - except (socket.error, SMTPException) as email_exc: - pass - - # Shut down if required - if should_shutdown: - self.suite_engine_proc.shutdown(suite_name, args=["--kill"]) - - if email_exc is not None: - raise - - __call__ = run - - -def main(): - """Implement "rose suite-hook" command.""" - opt_parser = RoseOptionParser() - opt_parser.add_my_options( - "mail_cc", "mail", "retrieve_job_logs", "shutdown") - opts, args = opt_parser.parse_args() - for key in ["mail_cc"]: - values = [] - if getattr(opts, key): - for value in getattr(opts, key): - values.extend(value.split(",")) - setattr(opts, key, values) - report = Reporter(opts.verbosity - opts.quietness - 1) # Reduced default - popen = RosePopener(event_handler=report) - suite_engine_proc = SuiteEngineProcessor.get_processor( - event_handler=report, popen=popen) - args = suite_engine_proc.process_suite_hook_args(*args, **vars(opts)) - hook = RoseSuiteHook(event_handler=report, - popen=popen, - suite_engine_proc=suite_engine_proc) - hook(*args, - should_mail=opts.mail, - mail_cc_list=opts.mail_cc, - should_shutdown=opts.shutdown, - should_retrieve_job_logs=opts.retrieve_job_logs) - - -if __name__ == "__main__": - main() diff --git a/metomi/rose/suite_log.py b/metomi/rose/suite_log.py deleted file mode 100644 index 29614cd105..0000000000 --- a/metomi/rose/suite_log.py +++ /dev/null @@ -1,123 +0,0 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -# ----------------------------------------------------------------------------- -"""Implement "rose suite-log" CLI.""" - -import os -import pwd -from metomi.rose.opt_parse import RoseOptionParser -from metomi.rose.reporter import Event, Reporter -from metomi.rose.suite_engine_proc import SuiteEngineProcessor -import sys -from time import sleep -import traceback - - -class RoseBushStartEvent(Event): - - """Event raised on "rose bush start".""" - - def __str__(self): - return ("""Rose bush started:\n%s""" % self.args[0] + - """Run "rose bush stop" when no longer required.""") - - -def main(): - """Implement "rose suite-log" CLI.""" - opt_parser = RoseOptionParser() - opt_parser.add_my_options("archive_mode", "force_mode", "name", - "non_interactive", "prune_remote_mode", - "update_mode", "user", "view_mode") - opts, args = opt_parser.parse_args() - report = Reporter(opts.verbosity - opts.quietness) - - try: - suite_log_view(opts, args, report) - except Exception as exc: - report(exc) - if opts.debug_mode: - traceback.print_exc() - sys.exit(1) - - -def get_suite_name(event_handler=None): - """Find the top level of a suite directory structure""" - fs_util = FileSystemUtil(event_handler) - conf_dir = os.getcwd() - while True: - if os.path.basename(conf_dir) != "rose-stem": - for tail in [ - "rose-suite.conf", - "log/rose-suite-run.conf", - "rose-stem/rose-suite.conf"]: - conf = os.path.join(conf_dir, tail) - if os.path.exists(conf): - return os.path.basename(conf_dir) - up_dir = fs_util.dirname(conf_dir) - if up_dir == conf_dir: - raise SuiteNotFoundError(os.getcwd()) - conf_dir = up_dir - - -def suite_log_view(opts, args, event_handler=None): - """Implement "rose suite-log" CLI functionality.""" - suite_engine_proc = SuiteEngineProcessor.get_processor( - event_handler=event_handler) - opts.update_mode = ( - opts.update_mode or opts.archive_mode or opts.force_mode) - if opts.force_mode: - args = ["*"] - if not opts.name: - opts.name = get_suite_name(event_handler) - if not opts.update_mode and not opts.user: - opts.user = pwd.getpwuid(os.stat(".").st_uid).pw_name - if opts.archive_mode: - suite_engine_proc.job_logs_archive(opts.name, args) - elif opts.update_mode: - suite_engine_proc.job_logs_pull_remote( - opts.name, args, opts.prune_remote_mode, opts.force_mode) - if opts.view_mode or not opts.update_mode: - n_tries_left = 1 - is_rose_bush_started = False - url = suite_engine_proc.get_suite_log_url(opts.user, opts.name) - if url.startswith("file://"): - if (opts.non_interactive or - input( - "Start rose bush? [y/n] (default=n) ") == "y"): - suite_engine_proc.popen.run_bg( - "rose", "bush", "start", preexec_fn=os.setpgrp) - is_rose_bush_started = True - n_tries_left = 5 # Give the server a chance to start - while n_tries_left: - n_tries_left -= 1 - if n_tries_left: - url = suite_engine_proc.get_suite_log_url(opts.user, opts.name) - if url.startswith("file://"): - sleep(1) - continue - suite_engine_proc.launch_suite_log_browser(opts.user, opts.name) - break - if is_rose_bush_started: - status = suite_engine_proc.popen("rose", "bush")[0] - event_handler(RoseBushStartEvent(status)) - return - - -if __name__ == "__main__": - main() diff --git a/sbin/rosa-rpmbuild b/sbin/rosa-rpmbuild index 4e46c34a8b..277f535e17 100755 --- a/sbin/rosa-rpmbuild +++ b/sbin/rosa-rpmbuild @@ -78,8 +78,6 @@ Rose: a framework for managing and running meteorological suites %install rm -fr %{buildroot} -mkdir -p %{buildroot}/etc/bash_completion.d %{buildroot}/opt %{buildroot}/usr/bin -cp -p %_sourcedir/$NAME-$REV_BASE_DOT/etc/rose-bash-completion %{buildroot}/etc/bash_completion.d cp -pr %_sourcedir/$NAME-$REV_BASE_DOT %{buildroot}/opt/$NAME python3 -m compileall %{buildroot}/opt/$NAME/lib cp -p %_sourcedir/$NAME-$REV_BASE_DOT/usr/bin/rose %{buildroot}/usr/bin/rose @@ -89,8 +87,6 @@ cp -p %_sourcedir/$NAME-$REV_BASE_DOT/usr/bin/rose %{buildroot}/usr/bin/rosie rm -fr %{buildroot} %files -/etc/bash_completion.d -/etc/bash_completion.d/rose-bash-completion /opt/$NAME /usr/bin/rose /usr/bin/rosie diff --git a/sphinx/api/configuration/suite.rst b/sphinx/api/configuration/suite.rst index ee7952b130..ac781d620c 100644 --- a/sphinx/api/configuration/suite.rst +++ b/sphinx/api/configuration/suite.rst @@ -107,62 +107,6 @@ A suite directory may contain the following: used by various Rose utilities, such as the config editor GUI. It can be used to specify the suite type. - .. rose:conf:: root-dir=LIST - - A new line delimited list of ``PATTERN=DIR`` pairs. The ``PATTERN`` - should be a glob-like pattern for matching a host name. The ``DIR`` - should be the root directory to install a suite run directory. E.g.: - - .. code-block:: rose - - root-dir=hpc*=$WORKDIR - =*=$DATADIR - - In this example, :ref:`command-rose-suite-run` of a suite with name - ``$NAME`` will create ``~/cylc-run/$NAME`` as a symbolic link to - ``$DATADIR/cylc-run/$NAME/`` on any machine, except those with their - hostnames matching ``hpc*``. In which case, it will create - ``~/cylc-run/$NAME`` as a symbolic link to ``$WORKDIR/cylc-run/$NAME/``. - - .. warning:: - - If a suite has previously been run changes to any of the ``root-dir`` - settings will take effect on the next clean re-installation i.e:: - - $ rose suite-run --new - - .. rose:conf:: root-dir{share}=LIST - - A new line delimited list of ``PATTERN=DIR`` pairs. The ``PATTERN`` should - be a glob-like pattern for matching a host name. The ``DIR`` should be the - root directory where the suite's ``share/`` directory should be created. - - .. rose:conf:: root-dir{share/cycle}=LIST - - A new line delimited list of ``PATTERN=DIR`` pairs. The ``PATTERN`` should - be a glob-like pattern for matching a host name. The ``DIR`` should be the - root directory where the suite's ``share/cycle/`` directory should be - be created. - - .. rose:conf:: root-dir{work}=LIST - - A new line delimited list of ``PATTERN=DIR`` pairs. The ``PATTERN`` should - be a glob-like pattern for matching a host name. The ``DIR`` should be the - root directory where the suite's ``work/`` directory for tasks should be - created. - - .. rose:conf:: root-dir-share=LIST - - .. deprecated:: 2015.04 - - Equivalent to :rose:conf:`root-dir{share}=LIST`. - - .. rose:conf:: root-dir-work=LIST - - .. deprecated:: 2015.04 - - Equivalent to :rose:conf:`root-dir{work}=LIST`. - .. rose:file:: rose-suite.info The suite information file :rose:file:`rose-suite.info` should contain the diff --git a/sphinx/getting-started.rst b/sphinx/getting-started.rst index 9f864ee5c6..f2f87703a5 100644 --- a/sphinx/getting-started.rst +++ b/sphinx/getting-started.rst @@ -77,31 +77,8 @@ Additionally there is a `Pygments`_ lexer located in rose version --long -Bash Auto-Completion --------------------- - -There is a Rose bash completion script that you can source to enhance the -Rose command line interface within an interactive Bash shell. - -The script allows you to tab-complete Rose commands, options, and arguments. - -You can find the script in the Rose installation ``etc/rose-bash-completion``. -The file contains the instructions for using it. - - Configuring Cylc ---------------- See the "Installation" and "User Config File" sections of the `Cylc User Guide`_. - -.. warning:: - - Do not modify the default values of the following cylc settings: - - * ``[hosts][HOST]run directory`` - * ``[hosts][HOST]work directory`` - - Equivalent functionalities are provided by the - :rose:conf:`rose.conf[rose-suite-run]root-dir` settings in the Rose - site/user configuration. diff --git a/sphinx/glossary.rst b/sphinx/glossary.rst index c5162be67d..40dc1777ee 100644 --- a/sphinx/glossary.rst +++ b/sphinx/glossary.rst @@ -828,7 +828,6 @@ Glossary * Environment variables to be provided to ``cylc run`` ( :rose:conf:`rose-suite.conf[env]`). * Installation configuration (e.g. - :rose:conf:`rose-suite.conf|root-dir`, :rose:conf:`rose-suite.conf[file:NAME]`). See also: diff --git a/sphinx/installation.rst b/sphinx/installation.rst index 90f50d45a4..bb3ee1603e 100644 --- a/sphinx/installation.rst +++ b/sphinx/installation.rst @@ -81,15 +81,6 @@ Connectivity requirements: * Must share user accounts and ``$HOME`` directories with the hosts running the Cylc suites. -Hosts For Running Rose Bush -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Installation requirements: - * Rose, Bash, Python, jinja2. - -Connectivity requirements: - * Must be able to access the home directories of users' Cylc run directories. - Hosts For Rosie Subversion Repositories And The Rosie Web Services ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -236,26 +227,6 @@ under ``/etc/rose-meta/`` at your site. You can do:: See also :ref:`app-meta-loc`. -Configuring Rose Bush ---------------------- - -Rose Bush provides an intranet web service at your site for users to -view their suite logs using a web browser. Depending on settings at -your site, you may or may not be able to set up this service. - -You can start an ad-hoc Rose Bush web server by running:: - - setsid /path/to/rose/bin/rose bush start 0/dev/null 2>&1 & - -You will find the access and error logs under ``~/.metomi/rose-bush*``. - -Alternatively you can run the Rose Bush web service under Apache -``mod_wsgi``. - -Use the Apache log at e.g. ``/var/log/httpd/`` to debug problems. -See also `Configuring a Rosie Server`_. - - Configuring a Rosie Server -------------------------- @@ -352,7 +323,6 @@ following (with the paths set appropriately):: WSGIScriptAlias /rosie /path/to/rose/lib/python/rosie/ws.py Use the Apache log at e.g. ``/var/log/httpd/`` to debug problems. -See also `Configuring Rose Bush`_. Hopefully, you should now have a working Rosie service server. Configure the client settings by editing the :rose:conf:`rose.conf[rosie-id]` diff --git a/t/lib/bash/test_header b/t/lib/bash/test_header index 2ec6db19bb..4041a6f775 100644 --- a/t/lib/bash/test_header +++ b/t/lib/bash/test_header @@ -86,7 +86,6 @@ # * TEST_ROSE_WS_PID - PID of the service server # * TEST_ROSE_WS_PORT - Port of the service server on localhost. # * TEST_ROSE_WS_URL - URL of service server. -# E.g. rose_ws_init rose bush # rose_ws_kill # Kill a web service server started by "rose_ws_init" and remove # generated log and status files. diff --git a/t/rose-cli-bash-completion/00-static.t b/t/rose-cli-bash-completion/00-static.t deleted file mode 100755 index 14b20e72fc..0000000000 --- a/t/rose-cli-bash-completion/00-static.t +++ /dev/null @@ -1,861 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Test the rose CLI bash completion script. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header -#------------------------------------------------------------------------------- -tests 193 -setup -#------------------------------------------------------------------------------- -# Source the script. -. $ROSE_TEST_HOME/etc/rose-bash-completion || exit 1 -#------------------------------------------------------------------------------- -# List Rose subcommands. -TEST_KEY=$TEST_KEY_BASE-rose-subcommands -COMP_WORDS=( rose "") -COMP_CWORD=1 -COMPREPLY= -run_pass "$TEST_KEY" _rose -compreply_test "$TEST_KEY.reply" "rose help" -file_cmp "$TEST_KEY.out" "$TEST_KEY.out" ok_users -compreply_cmp "$TEST_KEY.reply" < ok_users -file_cmp "$TEST_KEY.out" "$TEST_KEY.out" ok_users -compreply_cmp "$TEST_KEY.reply" < ok_users -file_cmp "$TEST_KEY.out" "$TEST_KEY.out" . -#------------------------------------------------------------------------------- -# Test the rose CLI bash completion script for running suites. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header -export ROSE_CONF_PATH= -tests 16 -#------------------------------------------------------------------------------- -# Source the script. -. $ROSE_TEST_HOME/etc/rose-bash-completion || exit 1 -#------------------------------------------------------------------------------- -# Run the suite. -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) -rose suite-run -q -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost -#------------------------------------------------------------------------------- -# rose suite-log -n -TEST_KEY=$TEST_KEY_BASE -TEST_KEY=$TEST_KEY_BASE-log-n -COMP_WORDS=( rose suite-log -n "" ) -COMP_CWORD=3 -COMPREPLY= -run_pass "$TEST_KEY" _rose -compreply_grep "$TEST_KEY.reply" '^'"$NAME"'$' -file_cmp "$TEST_KEY.out" "$TEST_KEY.out" . -#------------------------------------------------------------------------------- -# Provides init, setup and teardown functions for rose macro tests. -#------------------------------------------------------------------------------- -. $(dirname $0)/../lib/bash/test_header - -function init() { - mkdir -p $TEST_DIR/config - cat >$TEST_DIR/config/rose-app.conf -} - -function init_macro() { - mkdir -p $TEST_DIR/config/etc/rose-meta/$1/HEAD/lib/python/macros/ - touch $TEST_DIR/config/etc/rose-meta/$1/HEAD/rose-meta.conf - cat >$TEST_DIR/config/etc/rose-meta/$1/HEAD/lib/python/macros/$2 -} - -function init_meta() { - mkdir -p $TEST_DIR/config/meta - cat >$TEST_DIR/config/meta/rose-meta.conf -} - -function init_opt_app() { - mkdir -p $TEST_DIR/config/opt - touch $TEST_DIR/config/opt/rose-app-"$1".conf -} - -function init_opt_suite() { - mkdir -p $TEST_DIR/config/opt - touch $TEST_DIR/config/opt/rose-suite-"$1".conf -} - -function init_rose_stem_meta() { - svnadmin create --fs-type fsfs $TEST_DIR/test_repos 1>/dev/null 2>&1 - REPOS_URL="file://$TEST_DIR/test_repos" - svn mkdir -q $REPOS_URL/trunk -m "make trunk" - svn co -q $REPOS_URL/trunk $TEST_DIR/config - mkdir -p $TEST_DIR/config/rose-stem/meta - touch $TEST_DIR/config/rose-stem/suite.rc - cat >$TEST_DIR/config/rose-stem/meta/rose-meta.conf - svn add $TEST_DIR/config/rose-stem 1>/dev/null 2>&1 -} - -function init_suite() { - mkdir -p $TEST_DIR/config - cat >$TEST_DIR/config/rose-suite.conf -} - -function init_upgrade_macro() { - category=$1 - cat >$TEST_DIR/rose-meta/$category/versions.py -} - -function init_upgrade_meta() { - category=$1 - mkdir -p $TEST_DIR/rose-meta/$category/ - shift - for version; do - mkdir -p $TEST_DIR/rose-meta/$category/$version/ - touch $TEST_DIR/rose-meta/$category/$version/rose-meta.conf - done -} - -function setup() { - mkdir $TEST_DIR/run - cd $TEST_DIR/run -} - -function teardown() { - cd $TEST_DIR - rm -rf $TEST_DIR/run - rm -rf $TEST_DIR/config/meta - rm -rf $TEST_DIR/config/rose-stem - rm -rf $TEST_DIR/config/rose*conf - rm -rf $TEST_DIR/config/.svn - rm -rf $TEST_DIR/test_repos -} - -function compreply_cmp() { - local TEST_KEY=$1 - local ENV_ACTUAL_TEMP_FILE=$(mktemp) - if [[ -n ${COMPREPLY:-} ]]; then - printf "%s\n" "${COMPREPLY[@]}" > $ENV_ACTUAL_TEMP_FILE - fi - if cmp $ENV_ACTUAL_TEMP_FILE -; then - pass $TEST_KEY - return - fi - fail $TEST_KEY -} - -function compreply_grep() { - local TEST_KEY=$1 - local TEST_PATTERN=$2 - local ENV_ACTUAL_TEMP_FILE=$(mktemp) - if [[ -n ${COMPREPLY:-} ]]; then - printf "%s\n" "${COMPREPLY[@]}" > $ENV_ACTUAL_TEMP_FILE - fi - if grep -q "$TEST_PATTERN" "$ENV_ACTUAL_TEMP_FILE"; then - pass $TEST_KEY - return - fi - fail $TEST_KEY -} - -function compreply_test() { - local TEST_KEY=$1 - local TEST_CMD=$2 - if [[ -z ${COMPREPLY:-} ]]; then - fail $TEST_KEY - fi - for reply in "${COMPREPLY[@]}"; do - $TEST_CMD $reply 1>/dev/null 2>&1 - if [[ $? -ne 0 ]]; then - fail $TEST_KEY - return - fi - done - pass $TEST_KEY -} From 59c1fadf9b2e7dea03b5a65684e4113eec95e3e7 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Mon, 21 Dec 2020 15:10:12 +0000 Subject: [PATCH 06/78] suite_engine_procs: remove unused interfaces --- metomi/rose/apps/fcm_make.py | 1 + metomi/rose/suite_engine_proc.py | 95 ++--------- metomi/rose/suite_engine_procs/cylc.py | 224 +------------------------ 3 files changed, 17 insertions(+), 303 deletions(-) diff --git a/metomi/rose/apps/fcm_make.py b/metomi/rose/apps/fcm_make.py index f6098152ef..cd51eaf406 100644 --- a/metomi/rose/apps/fcm_make.py +++ b/metomi/rose/apps/fcm_make.py @@ -173,6 +173,7 @@ def _run_orig(self, app_runner, conf_tree, opts, args, uuid, task, # Determine the name of the continuation task task_name_cont = task.task_name.replace( orig_cont_map[ORIG], orig_cont_map[CONT]) + # TODO: get_task_auth currently does nothing auth = app_runner.suite_engine_proc.get_task_auth( task.suite_name, task_name_cont) if auth is not None: diff --git a/metomi/rose/suite_engine_proc.py b/metomi/rose/suite_engine_proc.py index 26993f27bf..8e26b7ea99 100644 --- a/metomi/rose/suite_engine_proc.py +++ b/metomi/rose/suite_engine_proc.py @@ -19,12 +19,14 @@ # ----------------------------------------------------------------------------- """Suite engine processor management.""" -from metomi.isodatetime.data import Duration -from metomi.isodatetime.parsers import DurationParser, ISO8601SyntaxError from glob import glob import os import pwd import re +import sys + +from metomi.isodatetime.data import Duration +from metomi.isodatetime.parsers import DurationParser, ISO8601SyntaxError from metomi.rose.date import RoseDateTimeOperator, OffsetValueError from metomi.rose.fs_util import FileSystemUtil from metomi.rose.host_select import HostSelector @@ -32,8 +34,6 @@ from metomi.rose.reporter import Event from metomi.rose.resource import ResourceLocator from metomi.rose.scheme_handler import SchemeHandlersManager -import sys -import webbrowser class NoSuiteLogError(Exception): @@ -331,48 +331,8 @@ def __init__(self, event_handler=None, popen=None, fs_util=None, self.host_selector = host_selector self.date_time_oper = RoseDateTimeOperator() - def check_global_conf_compat(self): - """Raise exception on suite engine specific incompatibity. - - Should raise SuiteEngineGlobalConfCompatError. - - """ - raise NotImplementedError() - - def check_suite_not_running(self, suite_name): - """Check that suite is not running. - - This method is not implemented. Sub-class should override. - - Arguments: - suite_name: name of suite to check. - - Raise: - SuiteStillRunningError: - Should raise SuiteStillRunningError if suite is still running. - """ - raise NotImplementedError() - - def cmp_suite_conf( - self, suite_name, run_mode, strict_mode=False, debug_mode=False): - """Compare current suite configuration with that in the previous run. - - An implementation of this method should: - * Raise an exception on failure. - * Return True if suite configuration is unmodified c.f. previous run. - * Return False otherwise. - - """ - raise NotImplementedError() - - def get_suite_contact(self, suite_name): - """Return suite contact information for a user suite. - - Return (dict): suite contact information. - """ - raise NotImplementedError() - def get_suite_dir(self, suite_name, *paths): + # $$$ KEEP """Return the path to the suite running directory. paths -- if specified, are added to the end of the path. @@ -382,6 +342,7 @@ def get_suite_dir(self, suite_name, *paths): self.get_suite_dir_rel(suite_name, *paths)) def get_suite_dir_rel(self, suite_name, *paths): + # $$$ KEEP """Return the relative path to the suite running directory. paths -- if specified, are added to the end of the path. @@ -390,6 +351,7 @@ def get_suite_dir_rel(self, suite_name, *paths): raise NotImplementedError() def get_suite_log_url(self, user_name, suite_name): + # $$$ KEEP """Return the "rose bush" URL for a user's suite.""" prefix = "~" if user_name: @@ -424,14 +386,12 @@ def get_suite_log_url(self, user_name, suite_name): ["taskjobs", user_name, suite_name]) def get_task_auth(self, suite_name, task_name): + # $$$ KEEP """Return [user@]host for a remote task in a suite.""" raise NotImplementedError() - def get_tasks_auths(self, suite_name): - """Return a list of [user@]host for remote tasks in a suite.""" - raise NotImplementedError() - def get_task_props(self, *args, **kwargs): + # $$$ KEEP """Return a TaskProps object containing suite task's attributes.""" calendar_mode = self.date_time_oper.get_calendar_mode() try: @@ -441,6 +401,7 @@ def get_task_props(self, *args, **kwargs): self.date_time_oper.set_calendar_mode(calendar_mode) def _get_task_props(self, *_, **kwargs): + # $$$ KEEP """Helper for get_task_props.""" tprops = TaskProps() # If suite_name and task_id are defined, we can assume that the rest @@ -531,24 +492,13 @@ def get_task_props_from_env(self): """ raise NotImplementedError() - def get_version(self): - """Return the version string of the suite engine.""" - raise NotImplementedError() - - def get_version_env_name(self): - """Return the name of the suite engine version environment variable.""" - return self.SCHEME.upper() + "_VERSION" - def handle_event(self, *args, **kwargs): """Call self.event_handler if it is callable.""" if callable(self.event_handler): return self.event_handler(*args, **kwargs) - def is_suite_registered(self, suite_name): - """Return whether or not a suite is registered.""" - raise NotImplementedError() - def job_logs_archive(self, suite_name, items): + # $$$ KEEP """Archive cycle job logs. suite_name -- The name of a suite. @@ -559,6 +509,7 @@ def job_logs_archive(self, suite_name, items): def job_logs_pull_remote(self, suite_name, items, prune_remote_mode=False, force_mode=False): + # $$$ KEEP """Pull and housekeep the job logs on remote task hosts. suite_name -- The name of a suite. @@ -570,6 +521,7 @@ def job_logs_pull_remote(self, suite_name, items, raise NotImplementedError() def job_logs_remove_on_server(self, suite_name, items): + # $$$ KEEP """Remove cycle job logs. suite_name -- The name of a suite. @@ -578,30 +530,11 @@ def job_logs_remove_on_server(self, suite_name, items): """ raise NotImplementedError() - def launch_suite_log_browser(self, user_name, suite_name): - """Launch web browser to view suite log. - - Return URL of suite log on success, None otherwise. - - """ - url = self.get_suite_log_url(user_name, suite_name) - browser = webbrowser.get() - browser.open(url, new=True, autoraise=True) - self.handle_event(WebBrowserEvent(browser.name, url)) - return url - def parse_job_log_rel_path(self, f_name): + # $$$ ??? """Return (cycle, task, submit_num, ext) for a job log rel path.""" raise NotImplementedError() - def run(self, suite_name, host=None, run_mode=None, args=None): - """Start a suite (in a specified host).""" - raise NotImplementedError() - - def shutdown(self, suite_name, args=None, stderr=None, stdout=None): - """Shut down the suite.""" - raise NotImplementedError() - def _get_offset_cycle_time(self, cycle, cycle_offset): """Return the actual date time of an BaseCycleOffset against cycle. diff --git a/metomi/rose/suite_engine_procs/cylc.py b/metomi/rose/suite_engine_procs/cylc.py index 0ec4fb84e8..797a2058b6 100644 --- a/metomi/rose/suite_engine_procs/cylc.py +++ b/metomi/rose/suite_engine_procs/cylc.py @@ -19,7 +19,6 @@ # ----------------------------------------------------------------------------- """Logic specific to the Cylc suite engine.""" -import filecmp from glob import glob import os import pwd @@ -28,7 +27,6 @@ import socket import sqlite3 import tarfile -from tempfile import mkstemp from time import sleep from uuid import uuid4 @@ -40,17 +38,10 @@ SuiteNotRunningError, SuiteStillRunningError, TaskProps) -_PORT_SCAN = "port-scan" - - class CylcProcessor(SuiteEngineProcessor): """Logic specific to the cylc suite engine.""" - CONTACT_KEYS = ( - "CYLC_SUITE_HOST", "CYLC_SUITE_OWNER", "CYLC_SUITE_PORT", - "CYLC_SUITE_PROCESS") - PGREP_CYLC_RUN = r"python.*/bin/cylc-(run|restart)( | .+ )%s( |$)" REC_CYCLE_TIME = re.compile( r"\A[\+\-]?\d+(?:W\d+)?(?:T\d+(?:Z|[+-]\d+)?)?\Z") # Good enough? SCHEME = "cylc" @@ -67,108 +58,6 @@ def __init__(self, *args, **kwargs): self.host = None self.user = None - def check_global_conf_compat(self): - """Raise exception on incompatible Cylc global configuration.""" - expected = os.path.join("~", self.SUITE_DIR_REL_ROOT) - expected = os.path.expanduser(expected) - for key in ["[hosts][localhost]run directory", - "[hosts][localhost]work directory"]: - out = self.popen("cylc", "get-global-config", "-i", key)[0] - lines = out.splitlines() - try: - lines[0] = lines[0].decode() - except AttributeError: - pass - if lines and lines[0] != expected: - raise SuiteEngineGlobalConfCompatError( - self.SCHEME, key, lines[0]) - - def check_suite_not_running(self, suite_name): - """Check if a suite is still running. - - Args: - suite_name (str): the name of the suite as known by Cylc. - Raise: - SuiteStillRunningError: if suite is still running. - """ - try: - contact_info = self.get_suite_contact(suite_name) - except SuiteNotRunningError: - return # No contact file, suite not running - else: - fname = self.get_suite_dir(suite_name, ".service", "contact") - extras = ["Contact info from: \"%s\"\n" % fname] - for key, value in sorted(contact_info.items()): - if key in self.CONTACT_KEYS: - extras.append(" %s=%s\n" % (key, value)) - extras.append("Try \"cylc stop '%s'\" first?" % suite_name) - raise SuiteStillRunningError(suite_name, extras) - - def cmp_suite_conf( - self, suite_name, run_mode, strict_mode=False, debug_mode=False): - """Parse and compare current "suite.rc" with that in the previous run. - - (Re-)register and validate the "suite.rc" file. - Raise RosePopenError on failure. - Return True if "suite.rc.processed" is unmodified c.f. previous run. - Return False otherwise. - - """ - suite_dir = self.get_suite_dir(suite_name) - if run_mode == "run": - self.popen.run_simple("cylc", "register", suite_name, suite_dir) - f_desc, new_suite_rc_processed = mkstemp() - os.close(f_desc) - command = ["cylc", "validate", "-o", new_suite_rc_processed] - if debug_mode: - command.append("--debug") - if strict_mode: - command.append("--strict") - command.append(suite_name) - old_suite_rc_processed = os.path.join(suite_dir, "suite.rc.processed") - try: - self.popen.run_simple(*command, stdout_level=Event.V) - return ( - os.path.exists(old_suite_rc_processed) and - filecmp.cmp(old_suite_rc_processed, new_suite_rc_processed)) - finally: - os.unlink(new_suite_rc_processed) - - def get_running_suites(self): - """Return a list containing the names of running suites.""" - rootd = os.path.join(os.path.expanduser('~'), self.SUITE_DIR_REL_ROOT) - sub_names = ['.service', 'log', 'share', 'work', self.SUITE_CONF] - items = [] - for dirpath, dnames, fnames in os.walk(rootd, followlinks=True): - if dirpath != rootd and any( - name in dnames + fnames for name in sub_names): - dnames[:] = [] - else: - continue - if os.path.exists(os.path.join(dirpath, '.service', 'contact')): - items.append(os.path.relpath(dirpath, rootd)) - return items - - def get_suite_contact(self, suite_name): - """Return suite contact information for a user suite. - - Return (dict): suite contact information. - """ - try: - # Note: low level directory open ensures that the file system - # containing the contact file is synchronised, e.g. in an NFS - # environment. - os.close(os.open( - self.get_suite_dir(suite_name, ".service"), os.O_DIRECTORY)) - ret = {} - for line in open( - self.get_suite_dir(suite_name, ".service", "contact")): - key, value = [item.strip() for item in line.split("=", 1)] - ret[key] = value - return ret - except (IOError, OSError, ValueError): - raise SuiteNotRunningError(suite_name) - @classmethod def get_suite_dir_rel(cls, suite_name, *paths): """Return the relative path to the suite running directory. @@ -211,50 +100,8 @@ def get_task_auth(self, suite_name, task_name): Or None if task does not run remotely. """ - try: - out = self.popen( - "cylc", "get-config", "-o", - "-i", "[runtime][%s][remote]owner" % task_name, - "-i", "[runtime][%s][remote]host" % task_name, - suite_name)[0] - except RosePopenError: - return - user, host = (None, None) - items = out.strip().split(None, 1) - if items: - user = items.pop(0).replace(b"*", b" ") - if items: - host = items.pop(0).replace(b"*", b" ") - return self._parse_user_host(user=user, host=host) - - def get_tasks_auths(self, suite_name): - """Return a list of unique [user@]host for remote tasks in a suite.""" - actual_hosts = {} - auths = [] - out = self.popen("cylc", "get-config", "-ao", - "-i", "[remote]owner", - "-i", "[remote]host", - suite_name)[0] - for line in out.splitlines(): - items = line.split(None, 2) - user, host = (None, None) - items.pop(0) - if items: - user = items.pop(0).decode().replace("*", " ") - if items: - host = items.pop(0).decode().replace("*", " ") - if host in actual_hosts: - host = str(actual_hosts[host]) - auth = self._parse_user_host(user=user, host=host) - else: - auth = self._parse_user_host(user=user, host=host) - if auth and "@" in auth: - actual_hosts[host] = auth.split("@", 1)[1] - else: - actual_hosts[host] = auth - if auth and auth not in auths: - auths.append(auth) - return auths + # TODO: reimplement for Cylc8? + return None def get_task_props_from_env(self): """Get attributes of a suite task from environment variables. @@ -288,17 +135,6 @@ def get_task_props_from_env(self): task_is_cold_start=task_is_cold_start, cycling_mode=cycling_mode) - def get_version(self): - """Return Cylc's version.""" - return self.popen("cylc", "--version")[0].strip() - - def is_suite_registered(self, suite_name): - """See if a suite is registered - Return True directory for a suite if it is registered - Return False otherwise - """ - return self.popen.run("cylc", "get-directory", suite_name)[0] == 0 - def job_logs_archive(self, suite_name, items): """Archive cycle job logs. @@ -503,62 +339,6 @@ def parse_job_log_rel_path(cls, f_name): """Return (cycle, task, submit_num, ext).""" return f_name.replace("log/job/", "").split("/", 3) - @staticmethod - def process_suite_hook_args(*args, **_): - """Rearrange args for TaskHook.run.""" - task = None - if len(args) == 3: - hook_event, suite, hook_message = args - else: - hook_event, suite, task, hook_message = args - return [suite, task, hook_event, hook_message] - - def run(self, suite_name, host=None, run_mode=None, args=None): - """Invoke "cylc run" (in a specified host). - - The current working directory is assumed to be the suite log directory. - - suite_name: the name of the suite. - host: the host to run the suite. "localhost" if None. - run_mode: call "cylc restart|reload" instead of "cylc run". - args: arguments to pass to "cylc run". - - """ - if run_mode not in ['reload', 'restart', 'run']: - run_mode = 'run' - cmd = ['cylc', run_mode] - if run_mode == 'reload': - cmd.append('--force') - if host and not self.host_selector.is_local_host(host): - cmd.append('--host=%s' % host) - cmd.append(suite_name) - cmd += args - out, err = self.popen(*cmd) - if err: - self.handle_event(err, kind=Event.KIND_ERR) - if out: - self.handle_event(out) - - def shutdown(self, suite_name, args=None, stderr=None, stdout=None): - """Shut down the suite. - - suite_name -- the name of the suite. - stderr -- A file handle for stderr, if relevant for suite engine. - stdout -- A file handle for stdout, if relevant for suite engine. - args -- extra arguments for "cylc shutdown". - - """ - contact_info = self.get_suite_contact(suite_name) - if not contact_info: - raise SuiteNotRunningError(suite_name) - environ = dict(os.environ) - environ.update({'CYLC_VERSION': contact_info['CYLC_VERSION']}) - command = ["cylc", "shutdown", suite_name, "--force"] - if args: - command += args - self.popen.run_simple( - *command, env=environ, stderr=stderr, stdout=stdout) - def _db_close(self, suite_name): """Close a named database connection.""" if self.daos.get(suite_name) is not None: From 3512bc657fb2cd1ce8ba1a35561304509a9dd454 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Mon, 21 Dec 2020 16:08:59 +0000 Subject: [PATCH 07/78] rosie id: remove --to-output option --- bin/rosie-id | 5 ----- metomi/rose/opt_parse.py | 4 ---- metomi/rose/suite_engine_proc.py | 35 -------------------------------- metomi/rosie/suite_id.py | 11 +--------- t/rosie-id/00-basic.t | 17 +--------------- 5 files changed, 2 insertions(+), 70 deletions(-) diff --git a/bin/rosie-id b/bin/rosie-id index d6db8e61fd..3ce8f23827 100755 --- a/bin/rosie-id +++ b/bin/rosie-id @@ -29,9 +29,6 @@ # # Print the local location of a given suite ID # rosie id --to-local-copy mo1-abc45 # -# # Print the output directory of a given suite ID -# rosie id --to-output mo1-abc45 -# # # Print the web URL of a given suite ID # rosie id --to-web mo1-abc45 # @@ -63,8 +60,6 @@ # Print the local location of a given suite ID # --to-origin # Print the repository URL of a given suite ID -# --to-output -# Print the output directory of a given suite ID # --to-web # Print the web URL of a given suite ID # --next diff --git a/metomi/rose/opt_parse.py b/metomi/rose/opt_parse.py index 139aa95374..a9b9bbe4d7 100644 --- a/metomi/rose/opt_parse.py +++ b/metomi/rose/opt_parse.py @@ -616,10 +616,6 @@ class RoseOptionParser(OptionParser): ["--to-origin"], {"action": "store_true", "help": "Convert ID to the origin URL"}], - "to_output": [ - ["--to-output"], - {"action": "store_true", - "help": "Get the ID output directory"}], "to_web": [ ["--to-web"], {"action": "store_true", diff --git a/metomi/rose/suite_engine_proc.py b/metomi/rose/suite_engine_proc.py index 8e26b7ea99..a70bb8046c 100644 --- a/metomi/rose/suite_engine_proc.py +++ b/metomi/rose/suite_engine_proc.py @@ -350,41 +350,6 @@ def get_suite_dir_rel(self, suite_name, *paths): """ raise NotImplementedError() - def get_suite_log_url(self, user_name, suite_name): - # $$$ KEEP - """Return the "rose bush" URL for a user's suite.""" - prefix = "~" - if user_name: - prefix += user_name - suite_d = os.path.join(prefix, self.get_suite_dir_rel(suite_name)) - suite_d = os.path.expanduser(suite_d) - if not os.path.isdir(suite_d): - raise NoSuiteLogError(user_name, suite_name) - rose_bush_url = None - for f_name in glob(os.path.expanduser("~/.metomi/rose-bush*.status")): - status = {} - for line in open(f_name): - key, value = line.strip().split("=", 1) - status[key] = value - if status.get("host"): - rose_bush_url = "http://" + status["host"] - if status.get("port"): - rose_bush_url += ":" + status["port"] - rose_bush_url += "/" - break - if not rose_bush_url: - conf = ResourceLocator.default().get_conf() - rose_bush_url = conf.get_value( - ["rose-suite-log", "rose-bush"]) - if not rose_bush_url: - return "file://" + suite_d - if not rose_bush_url.endswith("/"): - rose_bush_url += "/" - if not user_name: - user_name = pwd.getpwuid(os.getuid()).pw_name - return rose_bush_url + "/".join( - ["taskjobs", user_name, suite_name]) - def get_task_auth(self, suite_name, task_name): # $$$ KEEP """Return [user@]host for a remote task in a suite.""" diff --git a/metomi/rosie/suite_id.py b/metomi/rosie/suite_id.py index 754ed0d1d8..99b878cd30 100644 --- a/metomi/rosie/suite_id.py +++ b/metomi/rosie/suite_id.py @@ -549,17 +549,12 @@ def to_web(self): revision = self.REV_HEAD return url + self.FORMAT_VERSION % (branch, revision) - def to_output(self): - """Return the output directory for this suite.""" - suite_engine_proc = SuiteEngineProcessor.get_processor() - return suite_engine_proc.get_suite_log_url(None, str(self)) - def main(): """Implement the "rose suite-id" command.""" opt_parser = RoseOptionParser() opt_parser.add_my_options("latest", "next", "to_local_copy", "to_origin", - "to_output", "to_web") + "to_web") opts, args = opt_parser.parse_args() report = Reporter(opts.verbosity - opts.quietness) SuiteId.svn.event_handler = report # FIXME: ugly? @@ -575,10 +570,6 @@ def main(): for arg in args: report( str(SuiteId(id_text=arg).to_local_copy()) + "\n", level=0) - elif opts.to_output: - for arg in args: - url = SuiteId(id_text=arg).to_output() - report(str(url) + "\n", level=0) elif opts.to_web: for arg in args: report(str(SuiteId(id_text=arg).to_web()) + "\n", level=0) diff --git a/t/rosie-id/00-basic.t b/t/rosie-id/00-basic.t index 52e15f907c..a47af24a23 100755 --- a/t/rosie-id/00-basic.t +++ b/t/rosie-id/00-basic.t @@ -21,7 +21,7 @@ #------------------------------------------------------------------------------- . $(dirname $0)/test_header #------------------------------------------------------------------------------- -tests 45 +tests 39 #------------------------------------------------------------------------------- svnadmin create foo URL=file://$PWD/foo @@ -91,21 +91,6 @@ http://trac-host/foo/intertrac/source:/a/a/0/0/0/trunk@HEAD __OUT__ file_cmp "$TEST_KEY.err" "$TEST_KEY.err" Date: Mon, 21 Dec 2020 16:15:28 +0000 Subject: [PATCH 08/78] suite_engine_procs: cylc - update to Cylc8 * update to cylc8 * remove unused exceptions --- metomi/rose/suite_engine_proc.py | 41 -------------------------- metomi/rose/suite_engine_procs/cylc.py | 9 +++--- 2 files changed, 5 insertions(+), 45 deletions(-) diff --git a/metomi/rose/suite_engine_proc.py b/metomi/rose/suite_engine_proc.py index a70bb8046c..1a4f078941 100644 --- a/metomi/rose/suite_engine_proc.py +++ b/metomi/rose/suite_engine_proc.py @@ -19,9 +19,7 @@ # ----------------------------------------------------------------------------- """Suite engine processor management.""" -from glob import glob import os -import pwd import re import sys @@ -32,7 +30,6 @@ from metomi.rose.host_select import HostSelector from metomi.rose.popen import RosePopener from metomi.rose.reporter import Event -from metomi.rose.resource import ResourceLocator from metomi.rose.scheme_handler import SchemeHandlersManager @@ -163,35 +160,6 @@ def to_duration(self): return self.duration -class SuiteEngineGlobalConfCompatError(Exception): - - """An exception raised on incompatible global configuration.""" - - def __str__(self): - engine, key, value = self.args - return ("%s global configuration incompatible to Rose: %s=%s" % - (engine, key, value)) - - -class SuiteNotRunningError(Exception): - - """An exception raised when a suite is not running.""" - - def __str__(self): - return "%s: does not appear to be running" % (self.args) - - -class SuiteStillRunningError(Exception): - - """An exception raised when a suite is still running.""" - - FMT_HEAD = "Suite \"%(suite_name)s\" appears to be running:\n" - - def __str__(self): - suite_name, extras = self.args - return self.FMT_HEAD % {"suite_name": suite_name} + "".join(extras) - - class CycleOffsetError(ValueError): """Unrecognised cycle time offset format.""" @@ -332,7 +300,6 @@ def __init__(self, event_handler=None, popen=None, fs_util=None, self.date_time_oper = RoseDateTimeOperator() def get_suite_dir(self, suite_name, *paths): - # $$$ KEEP """Return the path to the suite running directory. paths -- if specified, are added to the end of the path. @@ -342,7 +309,6 @@ def get_suite_dir(self, suite_name, *paths): self.get_suite_dir_rel(suite_name, *paths)) def get_suite_dir_rel(self, suite_name, *paths): - # $$$ KEEP """Return the relative path to the suite running directory. paths -- if specified, are added to the end of the path. @@ -351,12 +317,10 @@ def get_suite_dir_rel(self, suite_name, *paths): raise NotImplementedError() def get_task_auth(self, suite_name, task_name): - # $$$ KEEP """Return [user@]host for a remote task in a suite.""" raise NotImplementedError() def get_task_props(self, *args, **kwargs): - # $$$ KEEP """Return a TaskProps object containing suite task's attributes.""" calendar_mode = self.date_time_oper.get_calendar_mode() try: @@ -366,7 +330,6 @@ def get_task_props(self, *args, **kwargs): self.date_time_oper.set_calendar_mode(calendar_mode) def _get_task_props(self, *_, **kwargs): - # $$$ KEEP """Helper for get_task_props.""" tprops = TaskProps() # If suite_name and task_id are defined, we can assume that the rest @@ -463,7 +426,6 @@ def handle_event(self, *args, **kwargs): return self.event_handler(*args, **kwargs) def job_logs_archive(self, suite_name, items): - # $$$ KEEP """Archive cycle job logs. suite_name -- The name of a suite. @@ -474,7 +436,6 @@ def job_logs_archive(self, suite_name, items): def job_logs_pull_remote(self, suite_name, items, prune_remote_mode=False, force_mode=False): - # $$$ KEEP """Pull and housekeep the job logs on remote task hosts. suite_name -- The name of a suite. @@ -486,7 +447,6 @@ def job_logs_pull_remote(self, suite_name, items, raise NotImplementedError() def job_logs_remove_on_server(self, suite_name, items): - # $$$ KEEP """Remove cycle job logs. suite_name -- The name of a suite. @@ -496,7 +456,6 @@ def job_logs_remove_on_server(self, suite_name, items): raise NotImplementedError() def parse_job_log_rel_path(self, f_name): - # $$$ ??? """Return (cycle, task, submit_num, ext) for a job log rel path.""" raise NotImplementedError() diff --git a/metomi/rose/suite_engine_procs/cylc.py b/metomi/rose/suite_engine_procs/cylc.py index 797a2058b6..a2127bd5bf 100644 --- a/metomi/rose/suite_engine_procs/cylc.py +++ b/metomi/rose/suite_engine_procs/cylc.py @@ -32,10 +32,11 @@ from metomi.rose.fs_util import FileSystemEvent from metomi.rose.popen import RosePopenError -from metomi.rose.reporter import Event, Reporter +from metomi.rose.reporter import Reporter from metomi.rose.suite_engine_proc import ( - SuiteEngineProcessor, SuiteEngineGlobalConfCompatError, - SuiteNotRunningError, SuiteStillRunningError, TaskProps) + SuiteEngineProcessor, + TaskProps +) class CylcProcessor(SuiteEngineProcessor): @@ -45,7 +46,7 @@ class CylcProcessor(SuiteEngineProcessor): REC_CYCLE_TIME = re.compile( r"\A[\+\-]?\d+(?:W\d+)?(?:T\d+(?:Z|[+-]\d+)?)?\Z") # Good enough? SCHEME = "cylc" - SUITE_CONF = "suite.rc" + SUITE_CONF = "flow.cylc" SUITE_NAME_ENV = "CYLC_SUITE_NAME" SUITE_DIR_REL_ROOT = "cylc-run" TASK_ID_DELIM = "." From baac35d55a54e463938916f9baaa6acf1944dc79 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Mon, 21 Dec 2020 16:40:57 +0000 Subject: [PATCH 09/78] remove unused assets and update ACKNOWLEDGMENTS --- ACKNOWLEDGEMENT.md | 46 ++++-------------- etc/images/rose-config-edit/gnome_add.png | Bin 409 -> 0 bytes .../rose-config-edit/gnome_add_errors.png | Bin 379 -> 0 bytes .../rose-config-edit/gnome_add_warnings.png | Bin 417 -> 0 bytes .../rose-config-edit/gnome_package_system.png | Bin 865 -> 0 bytes .../gnome_package_system_errors.png | Bin 697 -> 0 bytes .../gnome_package_system_warnings.png | Bin 792 -> 0 bytes etc/images/rose-splash-logo.png | Bin 19363 -> 0 bytes 8 files changed, 9 insertions(+), 37 deletions(-) delete mode 100644 etc/images/rose-config-edit/gnome_add.png delete mode 100644 etc/images/rose-config-edit/gnome_add_errors.png delete mode 100644 etc/images/rose-config-edit/gnome_add_warnings.png delete mode 100644 etc/images/rose-config-edit/gnome_package_system.png delete mode 100644 etc/images/rose-config-edit/gnome_package_system_errors.png delete mode 100644 etc/images/rose-config-edit/gnome_package_system_warnings.png delete mode 100644 etc/images/rose-splash-logo.png diff --git a/ACKNOWLEDGEMENT.md b/ACKNOWLEDGEMENT.md index 444eff5994..824c733bbf 100644 --- a/ACKNOWLEDGEMENT.md +++ b/ACKNOWLEDGEMENT.md @@ -3,64 +3,36 @@ Licences for non-Rose works included in this distribution can be found in the licences/ directory. -doc/rose-icon.png, etc/images/rose-icon.png, etc/images/rose-icon.svg, etc/images/rose-icon-trim.png, etc/images/rose-icon-trim.svg, etc/images/rose-logo.png, -etc/images/rose-splash-logo.png, etc/images/rosie-icon.png, etc/images/rosie-icon.svg, etc/images/rosie-icon-trim.png, etc/images/rosie-icon-trim.svg, -etc/metadata/all/etc/images/icon.png: +metomi/rose/etc/rose-all/etc/images/icon.png, +metomi/rose/etc/rose-meta/rose-all/etc/images/icon.png * These icons are all derived from the public domain image at . -etc/images/rose-config-edit/gnome_add.png, -etc/images/rose-config-edit/gnome_package_system.png: -* These icons are part of the GNOME icon theme 2.28.0, licensed under - GPLv2. This theme was developed by: - * Ulisse Perusin - * Riccardo Buzzotta - * Josef Vybíral - * Hylke Bons - * Ricardo González - * Lapo Calamandrei - * Rodney Dawes - * Luca Ferretti - * Tuomas Kuosmanen - * Andreas Nilsson - * Jakub Steiner - -etc/images/rose-config-edit/gnome_add_???, -etc/images/rose-config-edit/gnome_package_system_???: -* These icons are derivative works produced from - * etc/images/rose-config-edit/gnome_add.png or - * etc/images/rose-config-edit/gnome_package_system.png, and are - distributed in their preferred PNG form. - -lib/html/static/css/bootstrap-???, -lib/html/static/images/glyphicons-halflings-???, -lib/html/static/js/bootstrap.min.js: +metomi/rosie/lib/html/static/css/bootstrap.min.css +metomi/rosie/lib/html/static/js/bootstrap.min.js +metomi/rosie/lib/html/static/fonts/glyphicons-halflings*, * Unmodified external software library copyright 2013 Twitter Inc released under the Apache 2.0 license. See . -lib/html/static/css/jquery.dataTables.???: -lib/html/static/images/sort_???: -lib/html/static/js/jquery.dataTables.???: +metomi/rosie/lib/html/static/css/jquery.dataTables.css +metomi/rosie/lib/html/static/images/sort_* * Unmodified external software library released under GPLv2 and BSD. See . -lib/html/static/js/livestamp.min.js: +metomi/rosie/lib/html/static/js/livestamp.min.js * Unmodified external software library released under the MIT license. See . -lib/html/static/js/moment.min.js: +metomi/rosie/lib/html/static/js/moment.min.js * Unmodified external software library released under the MIT license. See - -lib/python/rose/tests/test_ancils/unicode.txt: -* Markus Kuhn - 2015-08-28 - CC BY 4.0 diff --git a/etc/images/rose-config-edit/gnome_add.png b/etc/images/rose-config-edit/gnome_add.png deleted file mode 100644 index 80985a300d73fc63427c9e39489e9a0e118d0b50..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 409 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6T^Rm@ z;DWu&Cj&(|3p^r=85p>QL70(Y)*K0-AbW|YuPgfWY!fpiZ{MpSg6EY z$Fk{CsI~R1nRl5zZ!USW%uRqLdf$8TdC#9FN{IPdT+w7`Xg>Ux$-z-bsp;)MKl{GN zNB!5VN>=q`_*r5UeB3>m;lw4AwMSQpRWm3&U3@L7G)=&DqSqvso$FS+2#TtGtMaox zSYWcCqht4W?}jb8?F<1z5%M=5)mlicVT+#^|IzzkjhX&bW|h_@7JbU_vRhE6l^NI_G{K!-|~d*Ctpvx z?>Qm$=6_Cx8oy=c5m5%m3f}JjFg<8fVl5-PUAB$uKKpyXaAWXv^>bP0l+XkKJ=>mk diff --git a/etc/images/rose-config-edit/gnome_add_errors.png b/etc/images/rose-config-edit/gnome_add_errors.png deleted file mode 100644 index ee4be143f864790b54aa8b40a14d879b104425f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 379 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6T^Rm@ z;DWu&Cj&(|3p^r=85p>QL70(Y)*K0-AbW|YuPgft+pm6yN%Is~m#{FIG<;t8_h$EjZOnCf&p%7} zs&W0O%e}DeVJAn2!hvw!TWh98cCmM4cidPI9$9lSHNb;y2P69ro-^}351eG*BYvyO zXj5o*l49kS2$^LXr}r*uPO`cyb;V0THA3$-|ED=|_amlmkS^aW$)NwZFzWr+2Mr6X zZoKVeYvz^Mk;%6yY@g0+nWtq(v#w??Se9Bf?b=lxl@HP}_io4UQL70(Y)*K0-AbW|YuPgfR+_j4)N@)U2s6l<) zX5E93f^z~6t5Q#Wjm`dhEKBb7+08S#PQH1z)Xm#^k<;^)GyAW~ePam}VBPR>V@-}4 zvo+t>H@`xcXRY9xbWbTl^@X#5`4NU?&OWT%H9Y$~LOw?2-h1qJ%5zKpCwIp!rANOn z6v?b>o%z)Ez52UvHM<{3o=rU4zQ#>oVh{VrgHCCZYv(c))bDy_wa%X{)_VW^a`!j9(U6g}Th-+R2*nbuATc79qwArPRURpNqTt)USs-MD4} zL^rT9GK@?@Ojt}ZOpI}1j1gnv&(6e^#t=Y7C2DGmlxQ1m>HpMu@3oYfzBgsw`>qQZ z(-cl}H#a9I_nvz$BO>GwE!YFXZ}#5TAr$cr267evb{{k2E;H8flDt zET#M+U&y^6h5Tx3$F_u~Y1xfj0FaOA=`Z&6Yrp(@uO8~@ZIDu8dS_E%-%LhC$QN>( z8DqtPBZEQ+fh*T8mpyOwG=R?^@{XCN`F>yT;fU{h_t)G6reyX{tIciyrNW_!6Wm#hJ-{Kz0IlsDd z$1W?GNP&o;>pB1gkGW2uX*!4q!y`l9-Ie7M5uN6o^B=~>|Evy=3{`yJM_XGQx^A@Q z3%S8{;TqI+y)_<>!}oo-OO74{L02Z7{ig0g8|VDe){gB1UHiMCR7jVveCw^Pt=$0t zUDLPqAAZSXj5FJ|@%KODzT?{0!!Ufg9weJF)@hn1N~ID~sT4bT{B_H5oV@@Li^br& zE=EU3v3%E6rfF_cw(18Es|)l9A!45AVRm-9d}nIHeEO-KA{vbX0OsfC;dvhZ8oOOp zq%H~}b~DEQAflzZz-SnTzfI3ft%YImQMpq7uqoQyGB-D?re-GZH$|IOD%ox-QaF3& z+}rD&WgEW{0N#1`;A zZKdGBgMv^NJeiY!fE0T4=1IliO6$=>57CQQe}P0#w&oxb(w4MVmk{jkc9Sk&XFTji zLKN`>!yKLupLz2<^Mq20ooa{hI1b^HRw;kBLwG*Xu6eCh%2#$N><%~^LboCgSgd(3 zv3Ng|t9<@yDM$WKz^zx9IM2-v6Gymh0*m}!^KzmUX$*OzRmzRj4k1KdkK9+Yn#d7H zW1K8G3o(L&Iq&Q6>0u^+W;kR4UnWfB-A9OBHt(SkQF69{oUY8KA?AbydMfbYQ*UZ+;c9~zIH^dzFb2QRB z8^VGQdGcevKS#e$xlB33S}DcObB47Q${&;s$};66wo>AYhnPG#Mrw?%+CNn{#)L}@ z*6C#CBfh@K*3yW;bV@3w+990S@wrY^OwFcbfDX6UJk~1ZvrtOW4q+y-aG2vyjb16| z7!0^Hrn>hfsz~?^%WQV|rsmjoKuTPCl({DE-n&;gaDZ#qMhG)AT)*x@UE3f}5Ld{V z=`^N%HgRVktMvA7ZdMOoxw5A@F0QYw>5t~Wm3cezK;%MZV>{rdnjcoURr5iY_nR~? zlYe2@Bp;&6I9bZ6|J;S~KAfN9xvLCnUP!#v;F$s8WGU57Z}e~T;+?D9!0A>g-`2d? f=grLV-QK?du2S{wY_VWP00000NkvXXu0mjfK)ymV diff --git a/etc/images/rose-config-edit/gnome_package_system_warnings.png b/etc/images/rose-config-edit/gnome_package_system_warnings.png deleted file mode 100644 index 22baf7727f13455cc499c7a67082f96bcc5c6639..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 792 zcmV+z1LypSP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iyr6 z3l%FKb^Ij&00NjvL_t(I%YBnOXp>PC$A9SG9Nlygv6`Valc0mT`35QCpjAP9wY4>B48`V4K9hKJuR~gqApXa5{^#)H zJcPBDzm-aejlg2ydNOURsf5_ABq?p^PNwYx>Llv}N^-X;#1$nONF~G$CAqP5E^VP8 zr}&3NJwboBC?DB?bMGC+tcSB_IYB1?w_fv28xAW;Qc6M<&52~%PO4Nw2q`!&1xvKy zqmmdYVDE}R*qSOr1_0=YRvWV(w>x8m-(;y&pbxmOfEM85+WE+QnV$=r2}uFgRGIL> z*ebCwZ0*1W$Q5BILlJlapn#kdJlBRb)22)w21oGx3Izbyp2p)FsccW?x+QiXUkG=8O0Umu|{Ma?Spl-NhTNe?JHGnZS%4eS_D0m<|PN#e<{x zxdK^Z>6O64p$w(L5ekJew9E?PI>b^5(e>AHb-50)w%I}aUy${fp$+SkX?sUlYe^-< zJSFMtoWp@F?O-hS=1Y9P!tfN;qC;zNr9gc3;MEs?W<4Hh!~W?8N=b&-#fe8;m>+c! zGv`1tQ$q;1Lh&mv#(pv3V;qHi{~cp2EozE#y9y93l;L9zuPN)hD_chc5x17ieIBDw zhM^24V>u@UE2Q9su}n-ic&V#AegB=ZHeAk^xD;`Tj7(tOW~oHNn8i_CW1(ljp84-D zOs(u*yqTTjKk&6-pOTyo1=;3T5IqCd{qHvdXv6vOA4~#glWF@{8x9uAoHUjL_1@o~ W8YEEyr)NI^0000Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01dGK01dGLuOho100007bV*G`2iyV{ z3otj$G*QX`03ZNKL_t(|+U&h|m?T$u?|;szT-7+>5{f9Uw7b$uo6~HV9DBNFI;YC{{Ql_O+0ja1N!Rx= ztmb|8>8ED9rn;v3{nY!O?|IK#5MvCMZP}LXTrsqK36^cymXThzWm~poq|YrA#me%S zW!aXIK6mWZ?N31nm#@RJ{h=ATdo%eEY)&kcjXK6&uK=<Nx z6|<5@4$m%MhGknu`kX;JYde%>E$80Lwv6<-VfrY`(7Rqf{D(g1Kds5*)Asv!TefAS z-;stfuqz}*oH&;J!#`QqbsfiX9LHW_j^nsXY?10^w$A}uR=nRT&j48S01^=3#9ZbN zGcrR6YoNNWTLZXe004v#gb*Qw5CQ-!ZrA=FLRkCt+UxSSy^Qobz#stnD1amY)`7jl zf3Q5)g0pFwhGA%$W*CNLS+#u#f*=S2AynI-X_}^KilRu8Tm-uY{IV@0{mw8e0{{dN zBvaKt9E=xp<{HelZEKn)Nm997&gF91Y%Y;V(+m}lMHreUNCF{(rYVMDg2ABI>lFln zWm%f0Ns=T<5@Wnb^>VmgM*3VgBT`;>%YJtq9~P0 zC7aERj!sOBPbQP8R7UFTUOAH}G9>i-J+5O`D`iMhu~@LXt1A|(_xXGtkB8@Zj^k=< z69lo$^D@%!05cPSMGydrA`c&$=;`VHZ;#T8qh-soOw*KQxm+&K&L)OO=S&mFKP)yCd6 zq!)90c-ofTVHxQ^a>D=^GZ>PmeHkAg{O>FatqEY7rd6xeYPFipW(&o#C`y@3UeaMp z*MOo|=4X#*vhxhXLC9E!DF~e7+5|yGV)gm?xuR64l=YzBU)LBmOLRq|W@d{si5*v6 zq4)Nza2&_)_fr&w5L%qph7i_-v_;K)*)Nun{^JbKjxjlzqLo+L!vp}(b{qi6p`rP` zdk**ZwFQIW#WeW;sh2N`VUFV%h9OB(p-`Niojo#m^yQa#w#ND{xa6wF)^-Pw9+sFK z-90{ZG#Ky#2$7{$Dsn8=Aj^h}HsSUQ59b*~kKazNSn#>c&M}GU{!R;>|oE$G;K>zGM zrtLfeLf|?W001y``v?BsbqJbvuXxwm^DkU^`yJQ*zXrQDM^)3na=F}zlOr?p8sa*d zTgnUshyH&7hV?eYnn{9hY-nz2Zi?15#A1!6t>w!LJlCY_D#K7T%{dkr z8he$cKz&`Hp+15LLf346ply77NK-Sn-F9nhYilqVWLb7u6Vqq>h!Lk%V5(EJaL+4m_%jOGJwQ_RgSU#7<4qM;orx?%B@bN;ia^b}n zm&yst)(H}5ifvn{ zteCQ7U<56*s_VI_iDQN-Gaj$wIEW;9+5>24Rz(`(rusTnH%-$?&Sgz=*0l80!qD{0 z!C0)FVR)A334*m#Y!eKR}l*y56iQdC8)N>=t90I$L}uSTu@wDww~dU3f(jnAbr!Qv4XHTK%OwQ+k*lRN8TE)%|t^x58U@BjS4qYocp z82a@jLkMi!HBEcnO72vY#(<<2>m^Cs)*XkSX$pX|91D^poSH0MwDq&kz3}aDB=ol3 zZn1LPG)+ZO=I0k4dE~ciR-b?2)@x@b)2zS;LyRv#DYBW%nZrXvb2F1(kAQ6gV{GUE z1K{)fbi0^J&AF~&7&>qPPZJa*fs1WJ%BSV*LV_S!kH^Pwg5yG6DLSs@I!1eEYhz>F zu>pmta9H4c;c~@1ba1dH#6+V}kHr}@Az#cEa}EHABs57H9UU1OKAOuUR`!I$ zVGbe$0Mt|iB18}fBCM+tB7ke#x?y4$TeefFDw1SXMWa;KUDqK<%H#3*L-DrOmd?)3 zwzd|MA}Es2&L`&*Ly}_GMb}Xbs#FqJz3YO`&W>O($S@25z_Kh=RSSipEX$TF$Ey5 z<=XIc(#7ccMm9>cty41BSE zTi5k!wMtQJch|}T2Zj=faSVWCnGENn7y%m^0%kNE3c>z(cDY8M0^0H7#JB9TsKWP;{2Rd#JR916>l3}~7nO}~fi z@9U1oV}c-%B;h))VObD>+7x#!4KWO(TCFZDWaiW5XhWY^uI6$lyc{Wt^5EccmT|AS z_Ud)(*4Ne51p)z{=WE0G#bQ{;acT@x6m{0xvc*t;&Qtx4MjAr+z%PIM=x>fP40#sk zu4{k)hab82`iss~L0V;ibpYA3#?T0%;h{urTM#^7`Pzp+{>dBu$49?6Jd{T0HHn|1 zIJdm_|2+NteQ(dJYDk-=SuU4pN{|(eqA2tP3E|^HRx@#WGEUP_*rZy(^rZ z*vnV9ZZ-${6p|t!1SA0qYTK^8E9d4?7jE6!)EwBo=ZTTb(yY?U{x$M3n$9vL;B ze_7B?C6`YdrsL)P45#Lc#m=U-%^NS6m^yKE@D)hn?(Xhfrnq$;?1-D2zGy)+YDInY&Kh$94Ee(@RLn!0K}@|%Z>rP7A=8=9M&{eJ&qcA)FJVd$n| z;#0*iEYAzIl9*bGz8Ja}7l)s#Y!hGm+SlH(A$oQ;b@%PxXE_?aM&yD`&4K5)e|uo{ ziZg>TjDbE1)WZNG001Ds1CP2@@stv7+vcqwxMFceEwihUq3)VR?4Mf z#?Z}pJZ_kxW>$DEpj2hsa1mtkS+Sxyxt#1eq{rh+CT8a63)!shxc=E>36i|+up|}c zIYCuQqhlu=i)JYL(EeA;>5O(2{fWEJvEgsRb$v3k%~Dle=~w8XZfFPfU)D zPbQNoiXv)@Sae<2b=@>g%d%cCxu1JfzjLEBgz!Um{4LLOXJOOZAAjWWZ=NkNKnTnf z0K^iZ8`hF>jPc}T`t@@yU;6Uh4GocReC;8Ip_ZsM;AcPGb@MHku3Xjqww(!MFq=#i zE25?<1PM7#C=~M~0k&`7p_$5tb(gH@S;f%2Dk=~=wxv%_CP&A-y*&ateRILKm0ie* z`4j;W$#Nc#KVK;RDq|JvTp=OeZQ*ITEd>Nv>SUvn&k&0Wig|bR^sm4AhC$isb5!tC^O92#03K2ovG_ zO8b2wK(5d{IOgSxz~Zne&IdiYx{F;ceTV>h0@f7=j=oDTy~^ti=kq1oa%c)>(#hGG@%}z$?LZhI zNFd1jx-_lVG!@s;FeZv6sag>=L#>p{j_q#Q=&NNGp#YfA7&CL6;{b>Nh4_}HMv~+Z zWb5Mf`NB*hH3OkDJ2Pb)c6}@c5mr@&Wjq|`%@=a2Y>HJ)5PSf_OnxpNt80kYtE$W} zq+wdRRy9polFHp(ZMtDps+CH$q#HWV3-xu;YPA9Yj>TgHNtQ}^MJ-W;DN0#Ik$t{q zuP@}-7$X9b0!fGF=0pes{y=zcZW@uGTrR1q?(_K<(xvIisnmRSb}lnAmMT}Rsp%Pj zEtX|!Z5|e<)0UAw7tD3tJ3jbrk|dXA#RP$$-~P80MK#5JU-;-9FYl?W-_R)t{3)G*fvWsl2j~} zvU7>4^=rKA*G6kdi>f!1X+p@!W)mdAB7!wE15u7BmSxGf_(HEB&^1?YZ14?_w_%JO z*YxpzMH5S9Nfg!jT&li4=DIcj21OD1OfFZdtXaLPp`ig{if6qp#-`yy2n<7#_Sad8q5Na@Vi=vsP<|fy5gZ==7$Z;%9uBcj-K*;Y4002o6Ikwr{)X8}wnWDLJ z?InE!n@5I^4-O9cysXdXt4*h!ldDAEp)IC<{4bA^h+X6vV{91qeLwh^AP5jbR;c^_ z-(6#rUfsOzbD#b6_tNPs0020Z-#L0AiRr0~ zb}z=SsZgOP;=b=Z{8onvwJHmaV$IFX z5ZSs}G^~Ow>FIR7Sg4qWZR(Dyz{&B2y{`^Zgs(2r+SJ%=8ahRzTrMY8<@7?XTq&ET zE=pC)bc%%vNwO5l@vP71^OK}oEElR(i6mKBQql`^np`zhiQ{Omz=@S&DPOD$*Xf#K zTjp$HZr}dBblv&n2WHIqmQ{>b``7gRJ!6ji?`fB+x~ASx=wpg_cjNQz*Da5RFkqv%paDqeKy zR)Fn*FJu``eWboF8ftEj8oCh()HOA=RjP7TthP5d@QkyN$<@dEFeLqffbCe8VYs$s z8#2WUg5Mts1sV+iTOy_AM?eOa&qVPCwqH)>gwuh z{g}?Zm1OTYq%p<^4jg;%fxRS2ED6!pzx>;uMWdlbW?0Ts$R=)muX5yAHI-)n1})LksYdHFG=zVg20zw zvGFbOyx6{zqNs2<=n*)Uq3i3zf`_MRiWhqt~sC z)Q~paa5>*BRm4KE$gqsy@#YFef52~b45hiT zpu_#Iea&@`v7tq+~5z8=;pd-y zESpPtyq@mvZjZ;abS2q21NJ*k@rDq-ymN@5siptZ*%ev2vimHAsOSfs(ieX61K;f* zD|+ZM24@#BE|s8*1^K>z)qnU?mSe#OZoiCKvg)H2_CNKR8^3(-FG+H-j1>I(S1*6# zuRs3gPkZrRz_M&86ryR`?+0Gg&Tb~D+u*Gt=`=GbOU zoUfUxC}Shz7`uuoA&RDG6JwOlPeO|x|r>+dkGl_JJW-SM+l9;P&Sy>+mn1&n) zc!5n#W#kEUBE?e0VlfbGjyJR@np!Rwq{{r^{qrY}?_arcbtT$ZA8n3=TL6?Onh1D> zXed%D%9T=XZe~i6tEE!5P{;#BkQ7OhWdF+Ecr2bs49l_-1%@K zGu7GD(9_d1Gm}WAQjthxv6S^(lD^-GlUNTvunhxhkx0jJ@3{NwGg>t`blNMs~xsf$%NwyZkS{ct1ff zi?;&DAtxuNJ3HIoe0->d=$aQIggA~11Olq6dc9tZaXOtV6szHA1IrMFLcYDD1v;){ zS-Nhx?ir-(>SL=n)Qyb|HAS1^@z|?-4qCQTDrSnM(z=1L*FysUXokQ<moxTzc1+b zMrV?V%zUOA)| zh9D3GG5{w*>K*_Yz;pw)8xUc@^#Cdg09e1VbLd2B@wZr(-uB$lPky@Nt!5dEOPXk! zW?7bD7>seLRN^=$5cCj;QB_Is2m}Elp_td>J%0Q!BF=o#nqf+o)xU0_S}Z1#sbJ8T z&u6pgX@Mgyx}dJs7gPv^_V4XZBn(s0ZQa5oiS;62t!yS;u9QiErAeI0PECv+E>}w3 z9f8~4*L~HMtzG?bS9P)(kz#<~M`d#I;IYE?T}PjPZeqoXE{0)drcxXq=p5)jIk>+n zl`q)ZaQm&T7hTfk^+i?D)wKMv6Q!U1G93zWAG~#7=breBFK(+UqA%n#T#(6RW~N73 z%6{LCEr0sHp8hpW&}3yrCrL$?)rm><>21=`*c?e&y&KlfPGx2%r-z4kW%6Z4@bvbs zUA=Zoch5jyM@ywtibk3zCT7#=OgtX8gKc3jJKOo#}Y!8gD6 zE7OyC+X6I0*JdjLpuN5`tFkx-eCJ2Vu#7*uhruR*r%Fu##-O>uedc}|!c#TW08ju@ z003Z40b>#X7hoFz1gP+rK2AKl9Zt=HqeE_?1ZWx*^3vO%t2ph2wO8A=b9t$yV|6k) z?KmpOk-&Ddr3F<{EXQ6SF3}BJ6{h7x_Q3JrKR7>gkjH<}D{b}c=7sUf1o?xSc zgMHigxg_tpid@$a_IU$}oV80vG!liFa2%Y=OpFd4t5$P2zNh^If6}^fbIb6U@s%$h znV6{P8ZIf-#(Mrkw|8H0dCP_8H$MIR%s>42*}D4f-W6-MY~Gy9&CJhDuIOYw_o<#O z=e3nfy}S4I?LK%25uSl=;%i@KDVm6cn|}51tR&7h)%iU_tEi}RGZXF2{Kr4sb>l57 zi`B0CAFv6UVF>T|*opHt;tMv>eSN;ie_MTFhq`aifzh$yVzC&F#@ah$E4unNtlvt~ z9uiSgb6HW=7+<|OKRY}!-rwJ6+x9uOa_Jp}bSjlRc5M2w-@F2#ZrcPxBuNRiJ9BDI zq9}%@f6sjyGBp0-PoeEtpZ_=o000nR3_4rzlRu&H+2ib`<81;!0sssgaqSAQwub}+ z00huQJTi$FiYI`62oOysjM3!T%J!Sl%xcTCR8`IA^SOL!!x%8cxnvqxOic1cJPpx%gj_oiDf7PnH$z}mtNd?-8CI`jg6Y!Hh8G@(B2F~K}Atp z8rw}nsaA`Yi9Ec=G%cTBNTp^cCPpN=@ZRg2?zyY0XJu@1hJNTl4H9ju254P(3dQ8? z%)&RnJ@kot+Hbk3{ZHT5LlW@bzuccl%odCDQnjGS<-2d|>Rl1iZT6SHD%torMbO2v z`20&GyqLcD0+10E#OTPv;L*vU<5SZ!MMJ|hLlXp|DdF|lya|dRP2+$58FpOv zi=RaRfZk5<$d4%OzNz0mGqSb;umQ`!b^Tx+RB`6V06R1!Fd4;Et4p_%T&oG2Nk%}w zH~p@42&gTeE|<%blat9*0b^%iU=>Xh#Zu9>Er=mRBq9hzpv6nHa;5wC-)2R506VUx znwEhXmUdl!ZoX;fflf@ed3b0TMOjod*+gz6;OAy$ve5OqE)>NI!=h3#TPUPgukwEQ zuAa8`0KgY5Vq_5Bz$1?V4cEiqQLS>Nz=F`svSyo_>1H zw#?QRe}7+qqDWIWhDK(>(NJ@1tKZ*z=uqm%_fK_pGzo&Yv$MnJWz(5-p`x3ndiQNz zmtWTG^#>pSHH46)Nzyf4U6&~29vwWIo~y3vzc?J`zVPY3(J?t3X=-ff;nTAK03ZNK zL_t*Pn#+@ywC3&~rE0b!i|#qPtoR&3+Hpa0ZsEj2L>%y) zfa(As3;9>0&9n#{Jfwbj?fM@B|0-IirtmSxNG)IO6U zQKs1X+_pMh%b8ZSr2)4$7a|cCB9}mkTyA#Xpx-daNYG0{+V2x)rbay;#7QG%%ZC-jA=|4CVMKLkHr&35ZGP7-4(zl#v zAc7=Fo*)oG5EM<)B#8)!2!J5aRNXKP2myjM0U7%J0_*2%Ki9^a0APS?8;c;Go~;4^ z06aTk+N#sN8FTF7jo|bK01%$~clsj$IJJH16+(c}(tQAg`1C7AwNW{c9OpR3M*;z2 z?b_zG>-$=}23GW~S(=jAwyo>>{QUgj;X~Q%JRq8KxwLKD)6+ASWt*l`txB!~TnFQt z;*9}>(6X$uC>C;g7uZ9?s-am|USeJUZp!Z`F5H4Unu|}qKut{~gW(mSkf)(BtjIFL zR6|Q0B1qeEMn+CfPtErC@*CIHQxrinL?+h=DW_O034Fk|0q^l5GF~cI8d_GOLkC7C zly@7ZKr&4Y{Kr3f$v^(f?uuBpEJxKW3@}NdPky}l&+py2edj)sU^tEqg?uC98qHIY za5R+~t%@3i=!%Q$8ykHDfyPGVbYf=zfmd?5l5Xg(>(*|Px(+1KgaDY9d-)~x`wz*P zmv$pWGl1{y>zx@Ntx5&QVN}^PH0{uV-K{OHt5*+j9Jkm;i>Kf zmZ?$BB(^fHo+OMBV3z;@0FnSe0HK!!05GpTfC*Id&41i%%Dwyp_QBth0YCiWHyw`s zFHlM`hK}PJhDi`kduQab%QrSQhWl4FZQ3-jxcqBz)VH`%i_-ag-s9){d)Ln=%0ZvE zD0p$`cNQxn7Th}DuQd5(Od^WAB<#lWN!eI^q07$N@`yxY2zPfi?clVlj ztUsO35mY|t3uJP6+s28crK%>+Gp6O<`hiP@=uIaF_n$m@U~)?9?!tfo4KeSJet7jm zkBt2EmnSkgyEWESDb{`E|5QUhW>k_Y<+32){vIF05RUE6C8n#@LrsmSp&<+bbO8nc zK!mW113{KR5Frqb3*i`<$vTIR?4Qew7NmjZh80zbV!RCJ4~*{FR>;ra_Q4O-*ViqU znw(1oWamiY4dG&^5kS!#00WR6Abh97D>n1U7zK728qa2=VJJl2e1rDW2U&{nM#Bw0KSPtQX-Rwc9!xGwWU_Ors`ji1 z(KHDm&<$W)pton;$&q8vZF~Ap-(MFAd&*TgpHHWf^MW9#Qb|$m2mr2&J$@LC#Mf=O zh@$zE6Z)FfZc`&h2>Jc&-FL3O`pS;&yD~4ms*wa=*VGY-1T~kOPRt6N+0Y=={`^a? z=vcX~)iN!x*r)^mh#*NIl0>S59MjDeY_Xj7c_Qz-`PNFcuw%!Lj<(!}P3JeYw)U@F zx%*|Vy}iA)wT0*TbFv`x+*Q2Kge3s!D4-)4KmfpRpW5?E_Mk- zJFWo%c<;^Uf9LN%^LqgPJ;GS6!-=Y@nx=7_*XIu&II#cczxda8zxxIN9Ya%m9h1JdFCbOT^Hq6bn1?cD2i-p@!$CY@4GJ3w!i8rR&CxJ zyXcbZ=Mt0U;*i%vLkKCF*mqd_o9`q|Q)6k0VJJ&82$E!3M%VRZY8FvuI#W~?nB8U{Hq@T zjya=!IgV@SmW!>9&bsv*Rx~z;&)eGf@UM3~|IARWk3T_B0D#7Z*qhed&)g$w?M-xD zul4jO7K@7N_$fBt&^SAjNF*jMz4RK0pr)x1*E%&|u!L#DG{Vt(kC)aoLp5<#Qkq&i zJsg`zEgU~-Y}x1%1ae)tel=Q1%VT4M#e8yf;%H(jMKRFtXGyBLSgbNMQPUoDU4QnO zC*tiFQM7mK=1VO@cWnLqE$gReleVP;*RU;p@v^wCgD8sR7~U6gF@+uH*_VT}^Xj_( zbR?v?7(j>`;`}YwIlJ~7Gcz*;f*5jj^^LWHeAjhX4Gi3M*A3-Lu3X6hfNj$f1VI$V zLcWM8&bIYHK-CnfS}E?^`(kHj|C5hDarfPK53E}G-S2&W_p2|Po6cLmVe{@?)rs+` z)dK^yHm1u+|8X0~c_6efU?7Af0Z9TF20pNVYSWhUAUN-IAhUs)!umL*OTbJ5D>*Z+ zU;iN+LiGBWiEVt>t+eYJU;91|1ekT}yE{8Vs|K1kZ0c)ii7#Eg`nga4Xeqju<@B#= zc&oE?wbpsHR^I7!dU|?#c6MG6qLL&Q3nD`JnWvsC6{UgI>uH7#ha+{7C_)5YqQ5f? zty^kEECxJL&L_y4g&@(?+Ua7j!Q^kVHFpDj$ zv99Y^U47x3hj}qX*V<^#&(9y&e>k;}@9tZxN=2HsSFajSWFz38P9%n0N8GsKJYe$# zv}l?jmlT8s{Jx%^-aWf3lj#MoCkO$Ffx`%h^Z163TQBTz-*=wh@|90img!T?|lm+tWMe}7>K z&j0|p`MkEbr2o}w#Z1$bWjT|{?B2bn_60$34%O) z_((RF<#<-$PbuEDSS^b3=%ubdvDMKt*a~XoLXciql=1xwjiw&_?_E>#w z4l>mGczrM&j>Ti$D|(umS{E|NG+c}!#}R#f?I(s096J1}iw)O>v1t47@YsAR?Yed@ zmkatGfu|6H1jEzJfG@HP^+i3jk1gp;Ri1kYH$9uuW3ny}?kxv=vDyjGjDvaR2t{sgopuy&m4{ zIVs%QR{;uMj}jbsbwT6$(DDw>}(?M`EsRRLb+3mQ^b0Vt($#@e@D!`Gl@p zr$jq|`@5zzajsM@7={TT)zR74*4CZLv6YGi0Jd%S>Z`gK7W~Jr@Bj5f4@^%_=W}9H zOGmL-o|&ChWU;f2;y8+7NrGz26&gfcm8DWB{u@VIMPHpbY{toFvt!a_!tb%ynYLQw#aiygyICdQAY5|f>+ z_QhKTuOI{i!C=rY$_nQR^siZe$-A!UU$w!uXrI@k>r!iLv&Z9k{UbY;k^Un!;@bDD zUwVf|2>tN;Pm5x8@%axw{+Gx6Uh{=ta<=W{jZ3b-`PRkfxx5i`E`V*{x$i@vF#5%# z{8zsv^2Q?=0DM}?{^IBVNf5X*c{sqfrQLS>wQn*?)Ku=p2Ue!j>G9E-`bb2s%%)N& z(%I=kaRCx0NokI2A%qB$%w{sBQqk`h=I0aH?7SeL+GyH!0k(>n^yuWo@nWgK3LHYz z$mqmlPdq(%aIY@qY04QLRky!XvMi^j)dvIi+SQ4%(ZOxoo*x?>OC;uL#$T$k$A@dG zySs5czpB53;mDU>emte@s*Q~TU+v$A1 zkk6*`xnwFebMp9+Lx=WcGnozRLf`$?mJi-`p4S^u6*W1RlNHBwsc5_--rTcx%cZw} z_%p5Tz2R{Eg%@5Z2tuv>*z#EVT*$pY_xYQjed?20NQYvaZ1d{2bV>=L1B<&#}3LjSkDA&;Qx_ zAKxE3bm-`_&pw$-%@<4gWMT>-=h~|ytNJ6JaL{!xE>^3S9#1MeiM)kr? z$%mhEPb5Ylb_#`gv0B=)G1SoLg+ypRTW{NiP$=YLha#!xcgoF;c05LTJnl``8ozu9 zkBm-Rwx&p;sz?CX@42S!)1T-I)vtN-$xtCbnoVbUPq0273HW2t`nF=Jlov%W7jQ7O za@DF>EVj6&@*Hc^|4!^Z1|bZEgC~xUq>`1Tb6lxZ86k7?mR0}$lVAM#r+fW9U-Ly) zQK$Qm?0#i1lMzpe)i(IZt-vdsp2x-x2;T`20swS%x7_ow>vYik*uxKBa>do@h3xxp z`kSS#yfMa>Z9MScmk}c0j2qP)uBLJq3Wa0GPNXyCmgWXYt{N7}Q;egE3}FI8#hPOi zu40%N6O)r;-5t!p%Ha81f_LB6-qRhTXrku7G=(m{FxC(kfiE<&q}kg)bWbSSGM%)$T6KXZ2m}cP+nSk8>-hSpr>(u7@%9VBfNe7ixv1ZxMM7pv zGwtDFzKD;Xm?#yi<#M*J4&8No+buVD*S9o7yj9FLku*~(RMVNcsmXDOmFwOe_C>p! z>aE_+Dt0VgD+`=;>4o7B-PX|4-zut?%%x&8v$L8Zd3=FDq<$f5BvNE&iyiQTpdSwO z8G^?Ncu<=wOg;d?)*$9cm1`0Nm0~UN&5fz)KxUizty=b!!UCB zyr_XIuleA@%uKa>oPnaGF6dI;=WWK2QBBd$w9_;zSCnut{DseVcK0@u6op+|1q4DK z41nvnh#)ABd(92aSHA~xTk{&&0cjW)!(Z$G_ZI6Nx*hp0H6ao!}1i1t7oShNhfVf zNum#aNPt5&yjE{&+)p-4jrBjM=1U%u(P z-+pSbuOI+`VW`i3>fdtN^4%Z4;Vh{72U>00rKmTmFk?DifBaM`bK`YiHT1KS^@b+? z^M8KlTg~)p+L$8ClBk?GIhs!wfJxYzK_ey_ZWM&5?f{O4n&C`OjT||+UsTke{Aztm zbBG{twOT;PUA3~AB7KHwk`$fKm%VOjoF$_)9v|(8o5aDPHB9|sDSu9(o*--Dx7PuE)2z0b_EBb=s_^#pM z69Vt?_%AqiQpuO-wzd{o8<{W8hNJb6q!Gb4w{`eEXmJ&-~l# zOH(Y%xc?Vlf7>HBjNL+Vibfoy8Px<1AOQ;UUSA@eo|u}NolBOgIoD7+nwqY-{Ib?n z>!3wij#3|IE!&t|Sb%mG5rjA3r8&fV0W!tZRPLUC{(2%cch$SDzVx!|hKHwf+1X$q zsH!qVuH&fKHAsTC3=%*m6phdftw?!M5(y|{S0}KpDmu@3EDKG~x`=8?CSx-*siOz? zrILY_t2a_CnJ!fQ{ub3v=5mGR=5EWR`uh3-B+|JCua~tfnIegFhM}lt!_a*mFJiD` zt25J+Gc(f=f?(M1)%q zlg~bVZ10}We&@cATyWvK8mQrLI1pq^{1c+@oW zC;$G*=H|w?ajLbJV;+x}qFr6e>#U#l@}(+Dq!PnpBZW*#SF0>b1j7Edcvo{>GXQQy zl#xYIBvw_8VQ5v;ZBw#sQ#DEP4>wUg3&oeJ> z-=-LDV{4D#3787SumQugjvpK8=vWyJM+t%`6f(s^PE}Rgf#Fa%lTN0Rb1lt{ri-)b zOu1YN2Eq^lMXpo|xx@Q*`8`5+S7)F;R$m_zC1ZXeD@kHi7ORyiFYqlbEtVM;JON3O z>SK+rOJfIDN-15F#43Zaiy-XmUJ;Gf(KI}mSqx@1qlXV+Hx>W3m96EqDrdnVuI$wF7&uAU^&03YPwN!or@37&vHJ~ zrU-_Jg#9$lD22*1&p)?mtJ~SNh9OB+uj+U_6QUcFW1Yu}ccST$RvM3R> zlP@P&+Lz1Ds**^<5CD#)iG@PZG!2sRA%=~{dMFZky)YDvluP+?c4lsDaC&xj>!nw; zcC2tPWci5Tt0!5CCS9-BEEXz;sf-LC^@bwh=1y-Ya%BImiSgqXU3lJNBf)dctMNJ7 zuH`Ld;F)yx>MOtGI>hVWf)JXf&GG2n*KVFmq>c|}AOxz0cRvE+bu<7#RLJ`tJa6ar zeaX2h$G!19r=}VA{_ppE>=S?THd5}|7i3wMq9~b6X6MeAcD}fKY;2^EpS$zU+b+K3 zCYtqN>=cRxmZbnDC8=!L+FUZ7%M?$ZIHsG{nhl#BmylHxo8@pgXjxW1m-Bjr72O?I zyzAYn0Z)$2Dyl<30|5jfp{Y7SlBQ{53{2B-9M?2WK)4h|(KJgE6h+Y#vL!L)+9fYb zdwHLY2?zLnp~h-8r>nVAYJ4g&D``fovC|)@_j^M=KTnd-G+a$LCAF+bl4DuewJC~1 zoX5)tXp*5Rq-tV%VK$S_TDI*vZe65qMR$L1Z*MFfBM5?`7{GXRO@qMIR5hDTO^**& zi|M0-2S;b8F+q}!!}4AW!@5{J8jiQdyP}Z@1lH)-k)e?jmJ1oi18gf+AN=&EKecx4 znudl3K@e(BmS0BtT%%f&#G7vTYGy$O;0>s&p=enuMF{|4RRg;ocA8==#(3K+?q|O0 zP!xD=|D!V|%gRrG{<+Jq*z$idgg&KsmrA9nsi|*%08LmF1U}vik@mA++&?un-Q7K)>8O|=^$0>Z6zl0<)7bF;w|8Z+bzJB9 z%$(V`yWdOlaus)q+NDUcEKBl|*s!J8DB2Wpg`|yJATH20K??MtK+)HtDB6b>D9}C? zMT?}V9}>4sEjN;4+L9$w68A;q<$5<>@@})woHKox_?o5^yH;GsrssPghI7xIb2vQT z`Iqyb|L+hL$+VpHTuHZFj7Uf#k^mwvIW8gy!n91sadlnOG#z*zLI1qiIz=%MQXC}$ zVA0TJX-Gj>)#|2A@M37QxLPT#wMF^+nYrb)mF=<`iX_|Gdo)c`6h%?wt%Q98XDAzSp*B#ro zOvAA)t6nYUGL~H{l!|q=u2pgyg{-RU4x$w$nQH26Z|#HxDR30a5r$DIm&@^JY807`#!3F>Tf`p!jb~?QXk{F-y zD2n(6OeDe)`bYoy7hU_>z%Q}$*~rK*Nm5Ho%d5Zt?K`JW&d%O{^JiCW+h+L?AV|k{ zTHAY%96#TbZYmop14+Y%vdDew_x_+zSgqCco9|2*s>Co{TUWOfiWIALsOk_xf&hf) zcpiq3_B@P%O*6FXI0WfXv?qu(Me!~|9zl2>MhNLz8Dng7@o+dwa}3R}7`gGdtXbyr z%95pR+eU3tH z45Px(R6LqWC8DAjIe6q$`@R91RU8jMk1**wTB} zTlSZK^@qRq>tlcNy?-ngOg{kO0|qV0AY@V$zw`YQ8K{#001FFAL_t)IPIwQQ6&_G^ z+2i2jREZn67`L2ywdBnhHk%Lx;2L>3kv2i1J#YxLs2{2T>4`kboq_7;!B0k489- ztLuhs>zk!bmSGh|1^`G(+_Chj@f+n*fgnLVl}60{;l5kfa_TqzaTbqz~L&kYSf-raZDu`G_1IKi2l8-44o ztB&cBAx{uQf~3mZbx29JYk35wDaIoS!!$iWx-PEOREi=bMT(}CuI>a+F?H2WHaES# zGF8`W>9!VDjO!L)#grIs)*S#S1U(1vj)`?h!$>6J7o%-Nj|G(8v#bUHoIZW@+Q`v= z{-@Xf?r(pzS+E!e?rQJ>hC(oN{{s;z7<-Om{>E>f`R*ToEf$M?QjvzfvF9h&@%?0} zRLZt(%d!q0v<$;gRW+N>PfgBd=BGNlhno|vuInOTt`{`yXr@sGBnFgRR%h)EhlU}n z*AQ}S>>-3SRYjgh*cw8vM(~Fsj$#+AF*wZ_##9Ca(qG(=} zqYT4xJl9B3w8x@B14{ow+x6wk&%b>6`D<5ie*a&uz44QA&!ZRyzW48czh!X)*EPU% zz;%&jo5zl~U;65qum9GUX_{-C0Qsbb0a2y8H8D9hJ8}2ekt3HbUK~DfKv5KoakW}qTFSh8>(-SwzHe%prm4Da zwYIc`Ls0~!LkAvfN$;~AlcZ_j!R4iywdMKq=g%BCFqF-e$|{b86Q*fENJ4?FTh($^ zb4qJX>AhVpo*u~gYP3O+-+XRJqF`a5^<3(B!!aOHwn&x?=B1;H)x?#DF>ABd& zM69jvLaKfG-tFqrbc`bw7Vo~cG8;>E_75Ey93D=^<1Tgyg7ii7-m6&|Q2HapH2~n; z`IG0)p9CHn9i1E*nVFu<%*-s6itmf*>HE}$r_x=W>CUdbe&KF;N@$A{N@pxPmMF4+&(E8NnavjSo2A*ATNEwG;YhuvZY_U=e5+LTPBLXlD>cje0eT=|cG zfi%(6J3KISvbAH76D8O43=JXHa}hRm!$qV^Leh0r)0&vPo6oHyghW}{%1>S07{4>t z90@fS^NRvY%96Y%6A1<$?~g4c0RXzL5B0u6LVo0jpgBQ<_)9?gif=pv;!_>Z&g(`c z9se|SKA*omdhfsf`+7dNa$tDq=+Q$w$7Zv65++yH7g&bk*;qItN|NGXkK=fPB)7Lp zH3Jn2d5mm=AY8{~1v!~Y0qp+t%4=Jt>^CmIeBr{m*4EZgD8z9bK@hI%`kKFGS%zVl zrm1O~s;V2=Ts~h|UEP>j$PtjORg0xuW^l0o<*$EjaBzTS*=n_V{bx59mi1Vo1-b6{ zog2$bqa=a4ySs*l1~)e=Yip}6qC$~WBAK>yZFO~OZRP&pU|(Nv&w4gjuGmMv^kpd= zvu$%}ZlY9NZcexL^c{9xFJH{fP2Mi%?k8g5!$%G(vXWU|&MYmKO2w`1vh5&QmcR3z zKl7Zyk8BMrfHUC1wruBq1m?WIF4mm9UUF@dVOnat9x?x-o$K1 zFaGv7FP}Q~rS|r=a5xMh2yVLLmboIQ`hE7oLAM6bhN9xxBJAF*Q4Kea3Yy?Bap`Ze&-kz4=JyaOryi1_B^Kfiu$ zb>!bca}0ydfB1-0!Jn=w;b%&)EXy=a$8j*me)@!Zy*@WL|IWyGQ@UeeF*7qgmdh?1 zy4v2}#xe{bNB{tY6fX$JjvbOEi6Dr=W@&L@DHe}CarUv^-tI&q>C5MaWb-}#C|&Yc9w~NnIGCelx}X0$73wZB7{^`Ef!0)S`A|aA(Umwbx~c_Fh(>* zg+ifZGA>E7Pg?(+xoz7RW4|J|BuRoG_}kIG{Srv&FFN+`@4J3&4FC^N@y`yQ!F$`+ zzT}~4dZ(pLQB-GV$I{aB)vK=;3OSmlo_+S|bW3wvTdOa1{rSx>jQ{IBJu8RO50XO&=G!PnRJp4QUd0>JdQmNE4&pbJNV83M= z5K`fA$Y-fP#eHu0#P+X!lK7d-7>4oN>?&#vcGLjz8RB2~G;W-8YLvNed_|UJhGD|t zFvb`{$Z?$Ch0l46G0*d|EF*+`n}VV!U)%Q=DM^wZOxD{NgZ~_VforU5jb@EiZ%_Kh z0!lv$o;Y{hH2)rWJfM@Gam(N5B*vI!Spa|^se%w92;xDaHfH&*fajgU|3hkk?7Da` zQ{PVV`%=``!y8LUqicUi48t_2=#OESM{w6;cP?s;`@25fbw8kV0000GMUmqO035*Y z|CAlwkH+c-A$^tDpWltmFz{aF+)gRFizJOUAFKCWv}x$-J0G?jyD6|341wd0zbrK?bkj5iqpB*`- From c119519af166e6991652f05992478a662eb139c6 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Mon, 21 Dec 2020 16:44:24 +0000 Subject: [PATCH 10/78] python3 upgrades * convert python calls from python => python3 * update to new-new-style classes * other misc changes --- .travis/cover.py | 3 --- .travis/sitecustomize.py | 3 --- .../meta/lib/python/macros/badenvswitch.py | 2 -- .../meta/lib/python/macros/envswitch.py | 2 -- .../meta/lib/python/macros/nl_add_remove.py | 2 -- .../meta/lib/python/macros/nl_ignore.py | 2 -- .../04-transform/meta/lib/python/macros/null.py | 2 -- .../05-validate/meta/lib/python/macros/fib.py | 2 -- .../05-validate/meta/lib/python/macros/null.py | 2 -- .../05-validate/meta/lib/python/macros/url.py | 2 -- .../cylc-forecasting-suite/bin/get-rainfall | 2 +- .../lib/python/mecator.py | 2 -- .../cylc-forecasting-suite/lib/python/util.py | 4 +--- .../widget/meta/lib/python/widget/username.py | 1 - lib/bash/rose_init | 2 +- metomi/rose/__init__.py | 3 --- metomi/rose/app_run.py | 7 ++----- metomi/rose/apps/__init__.py | 3 --- metomi/rose/apps/ana_builtin/grepper.py | 3 --- metomi/rose/apps/comparisons/cumf.py | 17 +++++++---------- metomi/rose/apps/comparisons/exact.py | 9 +++------ metomi/rose/apps/comparisons/mandatory.py | 7 ++----- metomi/rose/apps/comparisons/output_grepper.py | 5 +---- metomi/rose/apps/comparisons/prohibited.py | 7 ++----- metomi/rose/apps/comparisons/within.py | 9 +++------ metomi/rose/apps/fcm_make.py | 3 --- metomi/rose/apps/rose_ana.py | 5 +---- metomi/rose/apps/rose_ana_v1.py | 9 +++------ metomi/rose/apps/rose_arch.py | 9 +++------ .../apps/rose_arch_compressions/__init__.py | 3 --- .../rose_arch_compressions/rose_arch_gzip.py | 5 +---- .../rose_arch_compressions/rose_arch_tar.py | 5 +---- metomi/rose/apps/rose_bunch.py | 7 ++----- metomi/rose/apps/rose_prune.py | 3 --- metomi/rose/c3.py | 5 +---- metomi/rose/checksum.py | 3 --- metomi/rose/config.py | 11 ++++------- metomi/rose/config_cli.py | 3 --- metomi/rose/config_diff.py | 5 +---- metomi/rose/config_dump.py | 3 --- metomi/rose/config_processor.py | 5 +---- metomi/rose/config_processors/__init__.py | 3 --- metomi/rose/config_processors/env.py | 3 --- metomi/rose/config_processors/fileinstall.py | 9 +++------ metomi/rose/config_tree.py | 9 +++------ metomi/rose/date.py | 5 +---- metomi/rose/env.py | 3 --- metomi/rose/env_cat.py | 3 --- .../vn1.0/lib/python/macros/desoggy.py | 3 --- .../rose/etc/rose-demo-upgrade-null/versions.py | 2 -- metomi/rose/etc/rose-demo-upgrade/versions.py | 2 -- .../vn1.0/lib/python/macros/desoggy.py | 3 --- .../rose-demo-upgrade-null/versions.py | 2 -- .../etc/rose-meta/rose-demo-upgrade/versions.py | 2 -- metomi/rose/external.py | 3 --- metomi/rose/formats/__init__.py | 3 --- metomi/rose/formats/namelist.py | 11 ++++------- metomi/rose/fs_util.py | 5 +---- metomi/rose/host_select.py | 9 +++------ metomi/rose/job_runner.py | 9 +++------ metomi/rose/loc_handlers/__init__.py | 3 --- metomi/rose/loc_handlers/fs.py | 5 +---- metomi/rose/loc_handlers/namelist.py | 5 +---- metomi/rose/loc_handlers/rsync.py | 5 +---- metomi/rose/loc_handlers/svn.py | 7 ++----- metomi/rose/macro.py | 7 ++----- metomi/rose/macros/__init__.py | 3 --- metomi/rose/macros/compulsory.py | 3 --- metomi/rose/macros/duplicate.py | 3 --- metomi/rose/macros/format.py | 2 -- metomi/rose/macros/rule.py | 3 --- metomi/rose/macros/trigger.py | 3 --- metomi/rose/macros/value.py | 3 --- metomi/rose/meta_type.py | 5 +---- metomi/rose/metadata_check.py | 3 --- metomi/rose/metadata_gen.py | 3 --- metomi/rose/metadata_graph.py | 3 --- metomi/rose/namelist_dump.py | 3 --- metomi/rose/opt_parse.py | 3 --- metomi/rose/popen.py | 5 +---- metomi/rose/reporter.py | 9 +++------ metomi/rose/resource.py | 5 +---- metomi/rose/run.py | 7 ++----- metomi/rose/run_source_vc.py | 3 --- metomi/rose/scheme_handler.py | 5 +---- metomi/rose/section.py | 5 +---- metomi/rose/suite_engine_proc.py | 9 +++------ metomi/rose/suite_engine_procs/__init__.py | 3 --- metomi/rose/suite_engine_procs/cylc.py | 5 +---- metomi/rose/task_env.py | 3 --- metomi/rose/task_run.py | 3 --- metomi/rose/tests/config.py | 3 --- metomi/rose/tests/env.py | 3 --- metomi/rose/tests/popen.py | 3 --- metomi/rose/tests/trigger_file.py | 3 --- metomi/rose/tests/unicode_utils.py | 3 --- metomi/rose/unicode_utils.py | 3 --- metomi/rose/upgrade.py | 5 +---- metomi/rose/variable.py | 9 +++------ metomi/rosie/__init__.py | 3 --- metomi/rosie/db.py | 5 +---- metomi/rosie/db_create.py | 5 +---- metomi/rosie/graph.py | 3 --- metomi/rosie/suite_id.py | 5 +---- metomi/rosie/svn_post_commit.py | 2 +- metomi/rosie/usertools/__init__.py | 3 --- metomi/rosie/usertools/ldaptool.py | 2 +- metomi/rosie/usertools/passwdtool.py | 2 +- metomi/rosie/vc.py | 5 +---- metomi/rosie/ws.py | 3 --- metomi/rosie/ws_client.py | 7 ++----- metomi/rosie/ws_client_auth.py | 11 ++++------- metomi/rosie/ws_client_cli.py | 3 --- sphinx/conf.py | 3 --- sphinx/developing/autodoc.rst | 2 +- sphinx/ext/auto_cli_doc.py | 3 --- sphinx/ext/rose_domain.py | 3 --- sphinx/ext/rose_lang.py | 3 --- sphinx/ext/script_include.py | 3 --- sphinx/extract-pdf-documents.py | 3 --- t/rosa-svn-pre-commit/00-basic.t | 2 +- t/rosa-svn-pre-commit/01-rosie-create.t | 2 +- t/rosa-svn-pre-commit/02-passwd.t | 2 +- t/rosa-svn-pre-commit/03-meta.t | 2 +- t/rose-app-upgrade/lib/versions_cwd.py | 1 - t/rose-macro/lib/custom_macro_change.py | 4 ---- t/rose-macro/lib/custom_macro_change_arg.py | 4 ---- t/rose-macro/lib/custom_macro_change_bad.py | 4 ---- t/rose-macro/lib/custom_macro_check.py | 4 ---- t/rose-macro/lib/custom_macro_cwd.py | 1 - t/rose-macro/lib/custom_macro_prompt.py | 1 - t/rose-metadata-check/09-custom-macro.t | 2 +- t/rose-metadata-check/lib/custom_macro.py | 4 ---- .../lib/custom_macro_corrupt.py | 4 ---- t/rose.popen/00-self.t | 2 +- t/rose.variable/00-trigger.py | 4 ---- t/rose.variable/t_array_split.py | 4 ---- t/rosie.db/00-query.py | 4 ---- 138 files changed, 104 insertions(+), 468 deletions(-) diff --git a/.travis/cover.py b/.travis/cover.py index c88e58b372..64f55bf5c9 100644 --- a/.travis/cover.py +++ b/.travis/cover.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/.travis/sitecustomize.py b/.travis/sitecustomize.py index 57efa4a0a7..faed756e4c 100644 --- a/.travis/sitecustomize.py +++ b/.travis/sitecustomize.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/badenvswitch.py b/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/badenvswitch.py index 59380b8893..213b9e6605 100644 --- a/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/badenvswitch.py +++ b/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/badenvswitch.py @@ -1,6 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # ----------------------------------------------------------------------------- diff --git a/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/envswitch.py b/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/envswitch.py index c144c819a3..f975c1bca1 100644 --- a/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/envswitch.py +++ b/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/envswitch.py @@ -1,6 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # ----------------------------------------------------------------------------- diff --git a/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/nl_add_remove.py b/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/nl_add_remove.py index c62773d19f..dcd9c408b5 100644 --- a/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/nl_add_remove.py +++ b/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/nl_add_remove.py @@ -1,6 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # ----------------------------------------------------------------------------- diff --git a/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/nl_ignore.py b/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/nl_ignore.py index cbc427c900..716cb52bf6 100644 --- a/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/nl_ignore.py +++ b/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/nl_ignore.py @@ -1,6 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # ----------------------------------------------------------------------------- diff --git a/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/null.py b/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/null.py index fad4c9baf0..3bad8e5983 100644 --- a/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/null.py +++ b/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/null.py @@ -1,6 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # ----------------------------------------------------------------------------- diff --git a/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/fib.py b/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/fib.py index 3482750e15..86a17cdd7e 100644 --- a/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/fib.py +++ b/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/fib.py @@ -1,6 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # ----------------------------------------------------------------------------- diff --git a/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/null.py b/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/null.py index cc33318b15..f63c00416f 100644 --- a/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/null.py +++ b/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/null.py @@ -1,6 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # ----------------------------------------------------------------------------- diff --git a/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/url.py b/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/url.py index 887c59d9b5..f85692af9c 100644 --- a/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/url.py +++ b/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/url.py @@ -1,6 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # ----------------------------------------------------------------------------- diff --git a/etc/tutorial/cylc-forecasting-suite/bin/get-rainfall b/etc/tutorial/cylc-forecasting-suite/bin/get-rainfall index 4dd421cc44..12c7bddcb9 100755 --- a/etc/tutorial/cylc-forecasting-suite/bin/get-rainfall +++ b/etc/tutorial/cylc-forecasting-suite/bin/get-rainfall @@ -34,7 +34,7 @@ URL = ('http://datapoint.metoffice.gov.uk/public/data/layer/wxobs/' 'RADAR_UK_Composite_Highres/png?TIME={time}&key={api_key}') -class Rainfall(object): +class Rainfall: """Class for holding rainfall data. Args: diff --git a/etc/tutorial/cylc-forecasting-suite/lib/python/mecator.py b/etc/tutorial/cylc-forecasting-suite/lib/python/mecator.py index 35e6a14365..2812e737dc 100644 --- a/etc/tutorial/cylc-forecasting-suite/lib/python/mecator.py +++ b/etc/tutorial/cylc-forecasting-suite/lib/python/mecator.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors - GNU V3+. # This is illustrative code developed for tutorial purposes, it is not # intended for scientific use and is not guarantied to be accurate or correct. diff --git a/etc/tutorial/cylc-forecasting-suite/lib/python/util.py b/etc/tutorial/cylc-forecasting-suite/lib/python/util.py index 4bf2c83618..eb9f7fa3a2 100644 --- a/etc/tutorial/cylc-forecasting-suite/lib/python/util.py +++ b/etc/tutorial/cylc-forecasting-suite/lib/python/util.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors - GNU V3+. # This is illustrative code developed for tutorial purposes, it is not # intended for scientific use and is not guarantied to be accurate or correct. @@ -213,7 +211,7 @@ def get_grid_coordinates(lng, lat, domain, resolution): length_y - int((abs(lat - domain['lat1'])) // resolution)) -class SurfaceFitter(object): +class SurfaceFitter: """A 2D interpolation for random points. A standin for scipy.interpolate.interp2d diff --git a/etc/tutorial/widget/meta/lib/python/widget/username.py b/etc/tutorial/widget/meta/lib/python/widget/username.py index b2062c3cc3..4d7bc6a1a8 100644 --- a/etc/tutorial/widget/meta/lib/python/widget/username.py +++ b/etc/tutorial/widget/meta/lib/python/widget/username.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- """ This module contains value widgets for helping enter usernames. diff --git a/lib/bash/rose_init b/lib/bash/rose_init index 0c219bd4e5..e59bdcb193 100755 --- a/lib/bash/rose_init +++ b/lib/bash/rose_init @@ -36,7 +36,7 @@ function rose_init() { set -eu ROSE_NS=$(basename "$0") - ROSE_LIB="$(dirname "$(python -c "import metomi.rose; print(metomi.rose.__file__)")")" + ROSE_LIB="$(dirname "$(python3 -c "import metomi.rose; print(metomi.rose.__file__)")")" ROSE_HOME_BIN=$(dirname "$(command -v rose)") ROSE_VERSION="$(python3 -c "import metomi.rose; print(metomi.rose.__version__)")" export ROSE_LIB ROSE_HOME_BIN ROSE_NS ROSE_VERSION diff --git a/metomi/rose/__init__.py b/metomi/rose/__init__.py index 9a4e33d635..a944af6af5 100644 --- a/metomi/rose/__init__.py +++ b/metomi/rose/__init__.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/app_run.py b/metomi/rose/app_run.py index c9ed262a42..3195f5f7fb 100644 --- a/metomi/rose/app_run.py +++ b/metomi/rose/app_run.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -115,7 +112,7 @@ def __str__(self): ok_str, strftime("%Y-%m-%dT%H:%M:%S", localtime(sec)), test) -class Poller(object): +class Poller: """Handle the [poll] functionality for AppRunner.""" @@ -287,7 +284,7 @@ def _poll_file(self, file_, poll_file_test): return is_done -class BuiltinApp(object): +class BuiltinApp: """An abstract base class for a builtin application. diff --git a/metomi/rose/apps/__init__.py b/metomi/rose/apps/__init__.py index 8460776f05..f7b71f8284 100644 --- a/metomi/rose/apps/__init__.py +++ b/metomi/rose/apps/__init__.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/apps/ana_builtin/grepper.py b/metomi/rose/apps/ana_builtin/grepper.py index 5504ab5929..a7efb9ae7f 100644 --- a/metomi/rose/apps/ana_builtin/grepper.py +++ b/metomi/rose/apps/ana_builtin/grepper.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/apps/comparisons/cumf.py b/metomi/rose/apps/comparisons/cumf.py index 718b98b7ed..93195dc0c1 100644 --- a/metomi/rose/apps/comparisons/cumf.py +++ b/metomi/rose/apps/comparisons/cumf.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -30,7 +27,7 @@ HEADER = "differ, however the data fields are identical" -class Cumf(object): +class Cumf: """Analyse the output from the UM small exec cumf""" @@ -54,7 +51,7 @@ def run(self, task): return task -class CumfWarnHeader(object): +class CumfWarnHeader: """As cumf, but issue a warning if only the header has changed""" @@ -74,7 +71,7 @@ def run(self, task): return task -class CumfComparisonFailure(object): +class CumfComparisonFailure: """Class used if a cumf comparison fails.""" @@ -97,7 +94,7 @@ def __repr__(self): __str__ = __repr__ -class CumfComparisonSuccess(object): +class CumfComparisonSuccess: """Class used if a cumf comparison succeeds""" @@ -114,7 +111,7 @@ def __repr__(self): __str__ = __repr__ -class CumfComparisonHeaderWarning(object): +class CumfComparisonHeaderWarning: """Class used if cumf reports just the header of a file is different""" @@ -131,7 +128,7 @@ def __repr__(self): __str__ = __repr__ -class CumfSummaryNotFoundFailure(object): +class CumfSummaryNotFoundFailure: """Class used if there is a problem finding a cumf summary file""" @@ -148,7 +145,7 @@ def __repr__(self): __str__ = __repr__ -class CumfDiffNotFoundFailure(object): +class CumfDiffNotFoundFailure: """Class used if there is a problem finding a cumf diff file""" diff --git a/metomi/rose/apps/comparisons/exact.py b/metomi/rose/apps/comparisons/exact.py index 2ab46fb3f9..58a62588a2 100644 --- a/metomi/rose/apps/comparisons/exact.py +++ b/metomi/rose/apps/comparisons/exact.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -26,7 +23,7 @@ FAIL = "!=" -class Exact(object): +class Exact: def run(self, task): """Perform an exact comparison between the result and the KGO data""" failures = 0 @@ -46,7 +43,7 @@ def run(self, task): return task -class ExactComparisonFailure(object): +class ExactComparisonFailure: """Class used if results do not match the KGO""" @@ -79,7 +76,7 @@ def __repr__(self): __str__ = __repr__ -class ExactComparisonSuccess(object): +class ExactComparisonSuccess: """Class used if results match the KGO""" diff --git a/metomi/rose/apps/comparisons/mandatory.py b/metomi/rose/apps/comparisons/mandatory.py index aabc159914..5ccee40be5 100644 --- a/metomi/rose/apps/comparisons/mandatory.py +++ b/metomi/rose/apps/comparisons/mandatory.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -24,7 +21,7 @@ FAIL = "!~" -class Mandatory(object): +class Mandatory: def run(self, task): """Perform an exact comparison between the result and the KGO data""" failures = 0 @@ -35,7 +32,7 @@ def run(self, task): return task -class MandatoryStringResult(object): +class MandatoryStringResult: """Result of mandatory text examination.""" diff --git a/metomi/rose/apps/comparisons/output_grepper.py b/metomi/rose/apps/comparisons/output_grepper.py index 5c42a97c08..7a0ccb247f 100644 --- a/metomi/rose/apps/comparisons/output_grepper.py +++ b/metomi/rose/apps/comparisons/output_grepper.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -28,7 +25,7 @@ } -class OutputGrepper(object): +class OutputGrepper: def run(self, task, variable): """Return a list of values matching a regular expression.""" filevar = variable + "file" diff --git a/metomi/rose/apps/comparisons/prohibited.py b/metomi/rose/apps/comparisons/prohibited.py index 7b45f731e8..4c4e39b118 100644 --- a/metomi/rose/apps/comparisons/prohibited.py +++ b/metomi/rose/apps/comparisons/prohibited.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -24,7 +21,7 @@ FAIL = "~~" -class Prohibited(object): +class Prohibited: def run(self, task): """Perform an exact comparison between the result and the KGO data""" failures = 0 @@ -35,7 +32,7 @@ def run(self, task): return task -class ProhibitedStringResult(object): +class ProhibitedStringResult: """Result of prohibited text examination.""" diff --git a/metomi/rose/apps/comparisons/within.py b/metomi/rose/apps/comparisons/within.py index d88f484124..2fdec8c8a6 100644 --- a/metomi/rose/apps/comparisons/within.py +++ b/metomi/rose/apps/comparisons/within.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -34,7 +31,7 @@ FAIL = ">" -class Within(object): +class Within: def run(self, task): """Check that the results are within a specified tolerance.""" failures = 0 @@ -63,7 +60,7 @@ def run(self, task): return task -class WithinComparisonFailure(object): +class WithinComparisonFailure: """Class used if results are not within a certain amount of the KGO""" @@ -108,7 +105,7 @@ def __repr__(self): __str__ = __repr__ -class WithinComparisonSuccess(object): +class WithinComparisonSuccess: """Class used if results are within a certain amount of the KGO""" diff --git a/metomi/rose/apps/fcm_make.py b/metomi/rose/apps/fcm_make.py index cd51eaf406..0f444e1b89 100644 --- a/metomi/rose/apps/fcm_make.py +++ b/metomi/rose/apps/fcm_make.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ---------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/apps/rose_ana.py b/metomi/rose/apps/rose_ana.py index 0aae1cae0d..b3c780e206 100644 --- a/metomi/rose/apps/rose_ana.py +++ b/metomi/rose/apps/rose_ana.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -50,7 +47,7 @@ def timestamp(): return time.strftime("%H:%M:%S") -class KGODatabase(object): +class KGODatabase: """ KGO Database object, stores comparison information for metomi.rose_ana apps. diff --git a/metomi/rose/apps/rose_ana_v1.py b/metomi/rose/apps/rose_ana_v1.py index f55fe3e16e..0ca6c9cbee 100644 --- a/metomi/rose/apps/rose_ana_v1.py +++ b/metomi/rose/apps/rose_ana_v1.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -49,7 +46,7 @@ USRCOMPARISON_EXT = ".py" -class KGODatabase(object): +class KGODatabase: """ KGO Database object, stores comparison information for rose_ana apps. """ @@ -239,7 +236,7 @@ def __repr__(self): __str__ = __repr__ -class Analyse(object): +class Analyse: """A comparison engine for Rose.""" @@ -497,7 +494,7 @@ def write_config(self, filename, tasks): metomi.rose.config.dump(config, filename) -class AnalysisTask(object): +class AnalysisTask: """Class to completely describe an analysis task. diff --git a/metomi/rose/apps/rose_arch.py b/metomi/rose/apps/rose_arch.py index c31553b2b6..66edab7426 100644 --- a/metomi/rose/apps/rose_arch.py +++ b/metomi/rose/apps/rose_arch.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ---------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -404,7 +401,7 @@ def _get_conf(self, r_node, t_node, key, compulsory=False, default=None): return value -class RoseArchTarget(object): +class RoseArchTarget: """An archive target.""" @@ -435,7 +432,7 @@ def __ne__(self, other): return not self.__eq__(other) -class RoseArchSource(object): +class RoseArchSource: """An archive source.""" @@ -457,7 +454,7 @@ def __ne__(self, other): return not self.__eq__(other) -class RoseArchDAO(object): +class RoseArchDAO: """Data access object for incremental mode.""" diff --git a/metomi/rose/apps/rose_arch_compressions/__init__.py b/metomi/rose/apps/rose_arch_compressions/__init__.py index 8460776f05..f7b71f8284 100644 --- a/metomi/rose/apps/rose_arch_compressions/__init__.py +++ b/metomi/rose/apps/rose_arch_compressions/__init__.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/apps/rose_arch_compressions/rose_arch_gzip.py b/metomi/rose/apps/rose_arch_compressions/rose_arch_gzip.py index 7826021181..c79bd9eac4 100644 --- a/metomi/rose/apps/rose_arch_compressions/rose_arch_gzip.py +++ b/metomi/rose/apps/rose_arch_compressions/rose_arch_gzip.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -23,7 +20,7 @@ import os -class RoseArchGzip(object): +class RoseArchGzip: """Compress archive sources in gzip.""" diff --git a/metomi/rose/apps/rose_arch_compressions/rose_arch_tar.py b/metomi/rose/apps/rose_arch_compressions/rose_arch_tar.py index 8a3f869039..dfdfd64bdb 100644 --- a/metomi/rose/apps/rose_arch_compressions/rose_arch_tar.py +++ b/metomi/rose/apps/rose_arch_compressions/rose_arch_tar.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -24,7 +21,7 @@ from tempfile import mkstemp -class RoseArchTarGzip(object): +class RoseArchTarGzip: """Compress archive sources in tar.""" diff --git a/metomi/rose/apps/rose_bunch.py b/metomi/rose/apps/rose_bunch.py index a7dbc6f44e..f159caacd5 100644 --- a/metomi/rose/apps/rose_bunch.py +++ b/metomi/rose/apps/rose_bunch.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ---------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -372,7 +369,7 @@ def run(self, app_runner, conf_tree, opts, args, uuid, work_files): return 0 -class RoseBunchCmd(object): +class RoseBunchCmd: """A command instance to run.""" OUTPUT_TEMPLATE = "bunch.%s.%s" @@ -406,7 +403,7 @@ def get_log_prefix(self): return self.name -class RoseBunchDAO(object): +class RoseBunchDAO: """Database object for rose_bunch""" TABLE_COMMANDS = "commands" diff --git a/metomi/rose/apps/rose_prune.py b/metomi/rose/apps/rose_prune.py index 7da776a488..15481f1ff1 100644 --- a/metomi/rose/apps/rose_prune.py +++ b/metomi/rose/apps/rose_prune.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ---------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/c3.py b/metomi/rose/c3.py index d11d6ad708..bfa216d7bb 100644 --- a/metomi/rose/c3.py +++ b/metomi/rose/c3.py @@ -1,8 +1,5 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -105,7 +102,7 @@ def mro(target_name, get_base_names, *args, **kwargs): return results[target_name] -class _Test(object): +class _Test: """Self tests. Print results in TAP format. diff --git a/metomi/rose/checksum.py b/metomi/rose/checksum.py index 264e43edc3..4d74216675 100644 --- a/metomi/rose/checksum.py +++ b/metomi/rose/checksum.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/config.py b/metomi/rose/config.py index 2999deed24..62704c761f 100644 --- a/metomi/rose/config.py +++ b/metomi/rose/config.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -114,7 +111,7 @@ OPT_CONFIG_SETTING_COMMENT = " setting from opt config \"%s\" (%s)" -class ConfigNode(object): +class ConfigNode: """Represent a node in a configuration file. @@ -661,7 +658,7 @@ def __setstate__(self, state): self.comments = state["comments"] -class ConfigNodeDiff(object): +class ConfigNodeDiff: """Represent differences between two ConfigNode instances. @@ -1007,7 +1004,7 @@ def delete_removed(self): self._data[self.KEY_REMOVED] = {} -class ConfigDumper(object): +class ConfigDumper: """Dumper of a ConfigNode object in Rose INI format. @@ -1148,7 +1145,7 @@ def _comment_format(cls, comment): return "#%s\n" % (comment) -class ConfigLoader(object): +class ConfigLoader: """Loader of an INI format configuration into a ConfigNode object. diff --git a/metomi/rose/config_cli.py b/metomi/rose/config_cli.py index 4617118d50..864afb9dcd 100644 --- a/metomi/rose/config_cli.py +++ b/metomi/rose/config_cli.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/config_diff.py b/metomi/rose/config_diff.py index cd11f7d6d6..03d1da6693 100644 --- a/metomi/rose/config_diff.py +++ b/metomi/rose/config_diff.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ---------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -36,7 +33,7 @@ import metomi.rose.run -class ConfigDiffDefaults(object): +class ConfigDiffDefaults: """Store default settings for the rose config-diff command.""" diff --git a/metomi/rose/config_dump.py b/metomi/rose/config_dump.py index 48feaf0cba..3718b53794 100644 --- a/metomi/rose/config_dump.py +++ b/metomi/rose/config_dump.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/config_processor.py b/metomi/rose/config_processor.py index c29abe61f5..133cbbc9a8 100644 --- a/metomi/rose/config_processor.py +++ b/metomi/rose/config_processor.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -67,7 +64,7 @@ def __str__(self): return "%s: %s" % (setting_str, e_str) -class ConfigProcessorBase(object): +class ConfigProcessorBase: """Base class for a config processor.""" diff --git a/metomi/rose/config_processors/__init__.py b/metomi/rose/config_processors/__init__.py index 8460776f05..f7b71f8284 100644 --- a/metomi/rose/config_processors/__init__.py +++ b/metomi/rose/config_processors/__init__.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/config_processors/env.py b/metomi/rose/config_processors/env.py index 11bcd9deb6..0e9cb4310c 100644 --- a/metomi/rose/config_processors/env.py +++ b/metomi/rose/config_processors/env.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/config_processors/fileinstall.py b/metomi/rose/config_processors/fileinstall.py index 5f366635d8..f65e88cd48 100644 --- a/metomi/rose/config_processors/fileinstall.py +++ b/metomi/rose/config_processors/fileinstall.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ---------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -486,7 +483,7 @@ def __str__(self): return str(self.args[0]) -class Loc(object): +class Loc: """Represent a location. @@ -576,7 +573,7 @@ def __str__(self): return "%s <= %s, expected %s, got %s" % self.args -class LocSubPath(object): +class LocSubPath: """Represent a sub-path in a location.""" def __init__(self, name, checksum=None, access_mode=None): @@ -603,7 +600,7 @@ def __str__(self): return self.name -class LocDAO(object): +class LocDAO: """DAO for information for incremental updates.""" FILE_NAME = ".rose-config_processors-file.db" diff --git a/metomi/rose/config_tree.py b/metomi/rose/config_tree.py index 725ac86e98..ca12e070f7 100644 --- a/metomi/rose/config_tree.py +++ b/metomi/rose/config_tree.py @@ -1,8 +1,5 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -38,7 +35,7 @@ def __str__(self): return "Bad optional configuration key(s): " + ", ".join(self.args[0]) -class ConfigTree(object): +class ConfigTree: """A run time Rose configuration with linearised inheritance. @@ -77,7 +74,7 @@ def get_file_locs_of(self, key): os.path.join(file_loc, key) for file_loc in self.file_locs[key]] -class ConfigTreeLoader(object): +class ConfigTreeLoader: """Load a Rose configuration with inheritance.""" @@ -186,7 +183,7 @@ def _search(cls, conf_dir, conf_dir_paths): return os.path.abspath(os.path.join(conf_dir_paths[0], conf_dir)) -class _Test(object): +class _Test: """Self tests. Print results in TAP format.""" diff --git a/metomi/rose/date.py b/metomi/rose/date.py index df511c5789..0a24c8b0a6 100644 --- a/metomi/rose/date.py +++ b/metomi/rose/date.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -39,7 +36,7 @@ def __str__(self): return "%s: bad offset value" % self.args[0] -class RoseDateTimeOperator(object): +class RoseDateTimeOperator: """A class to parse and print date string with an offset.""" diff --git a/metomi/rose/env.py b/metomi/rose/env.py index cb9c3f4068..f879017448 100644 --- a/metomi/rose/env.py +++ b/metomi/rose/env.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ---------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/env_cat.py b/metomi/rose/env_cat.py index 903bd7c404..dd157df461 100644 --- a/metomi/rose/env_cat.py +++ b/metomi/rose/env_cat.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/etc/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py b/metomi/rose/etc/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py index 236963af89..5c91f2e76d 100644 --- a/metomi/rose/etc/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py +++ b/metomi/rose/etc/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/etc/rose-demo-upgrade-null/versions.py b/metomi/rose/etc/rose-demo-upgrade-null/versions.py index bc94de545c..3ed34249df 100644 --- a/metomi/rose/etc/rose-demo-upgrade-null/versions.py +++ b/metomi/rose/etc/rose-demo-upgrade-null/versions.py @@ -1,6 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # ----------------------------------------------------------------------------- """Module containing test upgrade macros""" diff --git a/metomi/rose/etc/rose-demo-upgrade/versions.py b/metomi/rose/etc/rose-demo-upgrade/versions.py index 33d66a7887..ce1cbf41a2 100644 --- a/metomi/rose/etc/rose-demo-upgrade/versions.py +++ b/metomi/rose/etc/rose-demo-upgrade/versions.py @@ -1,6 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # ----------------------------------------------------------------------------- """Module containing example macros for using rose app-upgrade. diff --git a/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py index 236963af89..5c91f2e76d 100644 --- a/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py +++ b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/etc/rose-meta/rose-demo-upgrade-null/versions.py b/metomi/rose/etc/rose-meta/rose-demo-upgrade-null/versions.py index bc94de545c..3ed34249df 100644 --- a/metomi/rose/etc/rose-meta/rose-demo-upgrade-null/versions.py +++ b/metomi/rose/etc/rose-meta/rose-demo-upgrade-null/versions.py @@ -1,6 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # ----------------------------------------------------------------------------- """Module containing test upgrade macros""" diff --git a/metomi/rose/etc/rose-meta/rose-demo-upgrade/versions.py b/metomi/rose/etc/rose-meta/rose-demo-upgrade/versions.py index 33d66a7887..ce1cbf41a2 100644 --- a/metomi/rose/etc/rose-meta/rose-demo-upgrade/versions.py +++ b/metomi/rose/etc/rose-meta/rose-demo-upgrade/versions.py @@ -1,6 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # ----------------------------------------------------------------------------- """Module containing example macros for using rose app-upgrade. diff --git a/metomi/rose/external.py b/metomi/rose/external.py index f646c44995..5fc887f31d 100644 --- a/metomi/rose/external.py +++ b/metomi/rose/external.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/formats/__init__.py b/metomi/rose/formats/__init__.py index 81dfa29fb6..2b23ec1e6b 100644 --- a/metomi/rose/formats/__init__.py +++ b/metomi/rose/formats/__init__.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ---------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/formats/namelist.py b/metomi/rose/formats/namelist.py index 0d267db58a..45b220a561 100644 --- a/metomi/rose/formats/namelist.py +++ b/metomi/rose/formats/namelist.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -95,7 +92,7 @@ def _rec(exp): [_rec(r"^([+-])0+(\d)"), r"\1\2"]] # +02.0 => +2.0, -000.5 => -0.5 -class NamelistGroup(object): +class NamelistGroup: """Represent a namelist group. It has the following attributes: @@ -120,7 +117,7 @@ def __repr__(self): return "&%s\n%s\n/\n" % (self.name, "\n".join(object_strings)) -class NamelistObject(object): +class NamelistObject: """Represent an object in a namelist group. An object can be an assignment or a key=value pair in a @@ -187,7 +184,7 @@ def get_rhs_as_string(self, min_repeats=5, wrapped=False, max_len=60): return "\n".join(lines) -class NamelistValue(object): +class NamelistValue: """Represent a value in a namelist object.""" def __init__(self, value_init, quote=False): @@ -232,7 +229,7 @@ def _tidy_real(self, value): return value -class _ParseContext(object): +class _ParseContext: """Convenient object for storing the parser's state.""" def __init__(self): diff --git a/metomi/rose/fs_util.py b/metomi/rose/fs_util.py index 830abf71e2..46c2ab9616 100644 --- a/metomi/rose/fs_util.py +++ b/metomi/rose/fs_util.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -54,7 +51,7 @@ def __str__(self): return "%s: %s" % (self.action, target) -class FileSystemUtil(object): +class FileSystemUtil: """File system utilities with event reporting.""" diff --git a/metomi/rose/host_select.py b/metomi/rose/host_select.py index caaaea325b..f8fbb646ed 100644 --- a/metomi/rose/host_select.py +++ b/metomi/rose/host_select.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -116,7 +113,7 @@ def __str__(self): return self.args[0] + ": (timed out)" -class HostSelector(object): +class HostSelector: """Select an available host machine by load of by random.""" @@ -419,7 +416,7 @@ def select(self, names=None, rank_method=None, thresholds=None, __call__ = select -class ScorerConf(object): +class ScorerConf: """Wrap a threshold/ranking scorer + extra configuration.""" @@ -444,7 +441,7 @@ def command_out_parser(self, out): return self.scorer.command_out_parser(out, self.method_arg) -class RandomScorer(object): +class RandomScorer: """Base class for threshold/ranking scorer. diff --git a/metomi/rose/job_runner.py b/metomi/rose/job_runner.py index 09b5dd6dee..0f0dfb18df 100644 --- a/metomi/rose/job_runner.py +++ b/metomi/rose/job_runner.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -35,7 +32,7 @@ def __str__(self): return str(self.args[0]) -class JobManager(object): +class JobManager: """Manage a set of JobProxy objects and their states.""" def __init__(self, jobs, names=None): @@ -110,7 +107,7 @@ def put_job(self, job_proxy): return job -class JobProxy(object): +class JobProxy: """Represent the state of the job.""" ST_DONE = "ST_DONE" @@ -148,7 +145,7 @@ def update(self, other): self.context.update(other.context) -class JobRunner(object): +class JobRunner: """Runs JobProxy objects with pool of workers.""" def __init__(self, job_processor, nproc=None): diff --git a/metomi/rose/loc_handlers/__init__.py b/metomi/rose/loc_handlers/__init__.py index ea8bb3646f..94490716c3 100644 --- a/metomi/rose/loc_handlers/__init__.py +++ b/metomi/rose/loc_handlers/__init__.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/loc_handlers/fs.py b/metomi/rose/loc_handlers/fs.py index ab9434da48..48da4ba8d9 100644 --- a/metomi/rose/loc_handlers/fs.py +++ b/metomi/rose/loc_handlers/fs.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -24,7 +21,7 @@ import os -class FileSystemLocHandler(object): +class FileSystemLocHandler: """Handler of file system locations.""" diff --git a/metomi/rose/loc_handlers/namelist.py b/metomi/rose/loc_handlers/namelist.py index b21f355cc4..0274bb826e 100644 --- a/metomi/rose/loc_handlers/namelist.py +++ b/metomi/rose/loc_handlers/namelist.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -37,7 +34,7 @@ class NamelistEvent(Event): LEVEL = Event.VV -class NamelistLocHandler(object): +class NamelistLocHandler: """Handler of namelists.""" SCHEME = "namelist" diff --git a/metomi/rose/loc_handlers/rsync.py b/metomi/rose/loc_handlers/rsync.py index a50988bd63..a952d82326 100644 --- a/metomi/rose/loc_handlers/rsync.py +++ b/metomi/rose/loc_handlers/rsync.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -29,7 +26,7 @@ ) -class RsyncLocHandler(object): +class RsyncLocHandler: """Handler of locations on remote hosts.""" SCHEME = "rsync" diff --git a/metomi/rose/loc_handlers/svn.py b/metomi/rose/loc_handlers/svn.py index 92502f43d6..872b865665 100644 --- a/metomi/rose/loc_handlers/svn.py +++ b/metomi/rose/loc_handlers/svn.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -24,7 +21,7 @@ import xml.parsers.expat -class SvnLocHandler(object): +class SvnLocHandler: """Handler of Subversion locations.""" FCM = "fcm" @@ -73,7 +70,7 @@ async def pull(self, loc, conf_tree): "svn", "export", "-q", loc.real_name, loc.cache) -class SvnInfoXMLParser(object): +class SvnInfoXMLParser: """An XML parser tailored for a single entry of "svn info --xml".""" def __init__(self): diff --git a/metomi/rose/macro.py b/metomi/rose/macro.py index 5342a0dd96..03abc8fb1c 100644 --- a/metomi/rose/macro.py +++ b/metomi/rose/macro.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -173,7 +170,7 @@ def __str__(self): return ERROR_LOAD_CONF_META_NODE -class MacroBase(object): +class MacroBase: """Base class for macros for validating or transforming configurations. @@ -497,7 +494,7 @@ def transform(self, config, meta_config=None): return config, self.reports -class MacroReport(object): +class MacroReport: """Class to hold information about a macro issue. diff --git a/metomi/rose/macros/__init__.py b/metomi/rose/macros/__init__.py index c6301abb3a..00975571ea 100644 --- a/metomi/rose/macros/__init__.py +++ b/metomi/rose/macros/__init__.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/macros/compulsory.py b/metomi/rose/macros/compulsory.py index fe09fe092a..cc82a990de 100644 --- a/metomi/rose/macros/compulsory.py +++ b/metomi/rose/macros/compulsory.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/macros/duplicate.py b/metomi/rose/macros/duplicate.py index a4248c530d..c6345c77c6 100644 --- a/metomi/rose/macros/duplicate.py +++ b/metomi/rose/macros/duplicate.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/macros/format.py b/metomi/rose/macros/format.py index f201662fd6..db860c2160 100644 --- a/metomi/rose/macros/format.py +++ b/metomi/rose/macros/format.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # ----------------------------------------------------------------------------- diff --git a/metomi/rose/macros/rule.py b/metomi/rose/macros/rule.py index 3254663aae..9cc506faaa 100644 --- a/metomi/rose/macros/rule.py +++ b/metomi/rose/macros/rule.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/macros/trigger.py b/metomi/rose/macros/trigger.py index 38c965dcc9..d14d641bf6 100644 --- a/metomi/rose/macros/trigger.py +++ b/metomi/rose/macros/trigger.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/macros/value.py b/metomi/rose/macros/value.py index 5ee7038fa3..4837b3fd55 100644 --- a/metomi/rose/macros/value.py +++ b/metomi/rose/macros/value.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/meta_type.py b/metomi/rose/meta_type.py index 2d234206ad..12e81deedc 100644 --- a/metomi/rose/meta_type.py +++ b/metomi/rose/meta_type.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -26,7 +23,7 @@ REC_CHARACTER = re.compile(r"'(?:[^']|'')*'$") -class MetaType(object): +class MetaType: KEY = None meta_type_classes = {} diff --git a/metomi/rose/metadata_check.py b/metomi/rose/metadata_check.py index 7deec314d3..a935ec3929 100644 --- a/metomi/rose/metadata_check.py +++ b/metomi/rose/metadata_check.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/metadata_gen.py b/metomi/rose/metadata_gen.py index 919f25c00b..70c6541320 100644 --- a/metomi/rose/metadata_gen.py +++ b/metomi/rose/metadata_gen.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/metadata_graph.py b/metomi/rose/metadata_graph.py index 11b556cf1d..3686b279e7 100644 --- a/metomi/rose/metadata_graph.py +++ b/metomi/rose/metadata_graph.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/namelist_dump.py b/metomi/rose/namelist_dump.py index 0f4b35bee4..5e11015883 100644 --- a/metomi/rose/namelist_dump.py +++ b/metomi/rose/namelist_dump.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/opt_parse.py b/metomi/rose/opt_parse.py index a9b9bbe4d7..df34a4b725 100644 --- a/metomi/rose/opt_parse.py +++ b/metomi/rose/opt_parse.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/popen.py b/metomi/rose/popen.py index 63b15ce104..4838c4da5a 100644 --- a/metomi/rose/popen.py +++ b/metomi/rose/popen.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -83,7 +80,7 @@ def __str__(self): return ret -class RosePopener(object): +class RosePopener: """Wrap Python's subprocess.Popen.""" diff --git a/metomi/rose/reporter.py b/metomi/rose/reporter.py index 633f893f7e..5c494e6eb5 100644 --- a/metomi/rose/reporter.py +++ b/metomi/rose/reporter.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -24,7 +21,7 @@ import time -class Reporter(object): +class Reporter: """Report diagnostic messages. @@ -181,7 +178,7 @@ def report(self, message, kind=None, level=None, prefix=None, clip=None): __call__ = report -class ReporterContext(object): +class ReporterContext: """A context for the reporter object. @@ -260,7 +257,7 @@ def _tty_colour_err(self, str_): return str_ -class Event(object): +class Event: """A base class for events suitable for feeding into a Reporter.""" diff --git a/metomi/rose/resource.py b/metomi/rose/resource.py index 57bf4050e2..d4b3542f75 100644 --- a/metomi/rose/resource.py +++ b/metomi/rose/resource.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -55,7 +52,7 @@ def __init__(self, key): Exception.__init__(self, "%s: resource not found." % key) -class ResourceLocator(object): +class ResourceLocator: """A class for searching resource files.""" diff --git a/metomi/rose/run.py b/metomi/rose/run.py index 1be639be5b..0f71074b80 100644 --- a/metomi/rose/run.py +++ b/metomi/rose/run.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -83,7 +80,7 @@ def __str__(self): return "%s=%s, --new mode not supported." % self.args -class Dummy(object): +class Dummy: """Convert a dict into an object.""" @@ -92,7 +89,7 @@ def __init__(self, **kwargs): setattr(self, key, value) -class Runner(object): +class Runner: """Invoke a Rose application.""" diff --git a/metomi/rose/run_source_vc.py b/metomi/rose/run_source_vc.py index 315fd7ca50..b43d725ed6 100644 --- a/metomi/rose/run_source_vc.py +++ b/metomi/rose/run_source_vc.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/scheme_handler.py b/metomi/rose/scheme_handler.py index ab1a509561..d193164af4 100644 --- a/metomi/rose/scheme_handler.py +++ b/metomi/rose/scheme_handler.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -26,7 +23,7 @@ import sys -class SchemeHandlersManager(object): +class SchemeHandlersManager: """Load and select from a group of related functional classes.""" CAN_HANDLE = "can_handle" diff --git a/metomi/rose/section.py b/metomi/rose/section.py index c2edf09194..746ffe1692 100644 --- a/metomi/rose/section.py +++ b/metomi/rose/section.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -25,7 +22,7 @@ import copy -class Section(object): +class Section: """This class stores the data and metadata of an input section. diff --git a/metomi/rose/suite_engine_proc.py b/metomi/rose/suite_engine_proc.py index 1a4f078941..454aa95636 100644 --- a/metomi/rose/suite_engine_proc.py +++ b/metomi/rose/suite_engine_proc.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -59,7 +56,7 @@ def __str__(self): return "%s %s" % self.args -class BaseCycleOffset(object): +class BaseCycleOffset: """Represent a cycle time offset.""" @@ -181,7 +178,7 @@ def __str__(self): return self.args[0] + ": unrecognised cycling mode." -class TaskProps(object): +class TaskProps: """Task properties. @@ -259,7 +256,7 @@ def __str__(self): return ret -class SuiteEngineProcessor(object): +class SuiteEngineProcessor: """An abstract suite engine processor.""" TASK_NAME_DELIM = {"prefix": "_", "suffix": "_"} diff --git a/metomi/rose/suite_engine_procs/__init__.py b/metomi/rose/suite_engine_procs/__init__.py index 8460776f05..f7b71f8284 100644 --- a/metomi/rose/suite_engine_procs/__init__.py +++ b/metomi/rose/suite_engine_procs/__init__.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/suite_engine_procs/cylc.py b/metomi/rose/suite_engine_procs/cylc.py index a2127bd5bf..1aefa6f394 100644 --- a/metomi/rose/suite_engine_procs/cylc.py +++ b/metomi/rose/suite_engine_procs/cylc.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -402,7 +399,7 @@ def _parse_user_host(self, auth=None, user=None, host=None): return auth -class CylcSuiteDAO(object): +class CylcSuiteDAO: """Generic SQLite Data Access Object.""" diff --git a/metomi/rose/task_env.py b/metomi/rose/task_env.py index 1157f605c5..1de0ee93e9 100644 --- a/metomi/rose/task_env.py +++ b/metomi/rose/task_env.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/task_run.py b/metomi/rose/task_run.py index cfe16f0a10..988df9406a 100644 --- a/metomi/rose/task_run.py +++ b/metomi/rose/task_run.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/tests/config.py b/metomi/rose/tests/config.py index 8ea4bd5914..6ac4ad9db9 100644 --- a/metomi/rose/tests/config.py +++ b/metomi/rose/tests/config.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/tests/env.py b/metomi/rose/tests/env.py index 39e8c3956a..8ca4f3d1c5 100644 --- a/metomi/rose/tests/env.py +++ b/metomi/rose/tests/env.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/tests/popen.py b/metomi/rose/tests/popen.py index f3a36e62bc..8bf0a61413 100644 --- a/metomi/rose/tests/popen.py +++ b/metomi/rose/tests/popen.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/tests/trigger_file.py b/metomi/rose/tests/trigger_file.py index 5bceeecb4e..e39899aee3 100644 --- a/metomi/rose/tests/trigger_file.py +++ b/metomi/rose/tests/trigger_file.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/tests/unicode_utils.py b/metomi/rose/tests/unicode_utils.py index cbe0105fec..371e222a73 100644 --- a/metomi/rose/tests/unicode_utils.py +++ b/metomi/rose/tests/unicode_utils.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/unicode_utils.py b/metomi/rose/unicode_utils.py index 7e1b6236b7..0572597014 100644 --- a/metomi/rose/unicode_utils.py +++ b/metomi/rose/unicode_utils.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rose/upgrade.py b/metomi/rose/upgrade.py index a5315646b2..8a22e57849 100644 --- a/metomi/rose/upgrade.py +++ b/metomi/rose/upgrade.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -474,7 +471,7 @@ def _get_section_option_from_keys(self, keys): return (keys + [None])[:2] -class MacroUpgradeManager(object): +class MacroUpgradeManager: """Manage the upgrades.""" diff --git a/metomi/rose/variable.py b/metomi/rose/variable.py index d8107ccffa..4f8fd20a48 100644 --- a/metomi/rose/variable.py +++ b/metomi/rose/variable.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -50,7 +47,7 @@ IGNORED_BY_USER = 'User ignored' -class Variable(object): +class Variable: """This class stores the data and metadata of an input variable. @@ -276,7 +273,7 @@ def get_value_from_metadata(meta_data): return var_value -class RangeSubFunction(object): +class RangeSubFunction: """Holds a checking function.""" @@ -304,7 +301,7 @@ def __repr__(self): self.operator, self.values) -class CombinedRangeSubFunction(object): +class CombinedRangeSubFunction: def __init__(self, *range_insts): self.range_insts = range_insts diff --git a/metomi/rosie/__init__.py b/metomi/rosie/__init__.py index f1b60f1a5d..e7f96eaf6b 100644 --- a/metomi/rosie/__init__.py +++ b/metomi/rosie/__init__.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rosie/db.py b/metomi/rosie/db.py index 9ff56b4e39..cc7640cb82 100644 --- a/metomi/rosie/db.py +++ b/metomi/rosie/db.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -59,7 +56,7 @@ def __str__(self): return "Failed to connect to DB '%s'." % self.bad_db_url -class DAO(object): +class DAO: """Retrieves data from the suite database. diff --git a/metomi/rosie/db_create.py b/metomi/rosie/db_create.py index 97bb7ea089..f710a214e9 100644 --- a/metomi/rosie/db_create.py +++ b/metomi/rosie/db_create.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -84,7 +81,7 @@ def __str__(self): return "%s: DB not loaded: %s" % self.args -class RosieDatabaseInitiator(object): +class RosieDatabaseInitiator: """Initiate a database file from the repository information.""" diff --git a/metomi/rosie/graph.py b/metomi/rosie/graph.py index 94aef55216..0912085393 100644 --- a/metomi/rosie/graph.py +++ b/metomi/rosie/graph.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rosie/suite_id.py b/metomi/rosie/suite_id.py index 99b878cd30..8555fc01ef 100644 --- a/metomi/rosie/suite_id.py +++ b/metomi/rosie/suite_id.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -110,7 +107,7 @@ def __str__(self): return "%s: invalid suite ID" % (self.args[0]) -class SuiteId(object): +class SuiteId: """Represent a suite ID.""" diff --git a/metomi/rosie/svn_post_commit.py b/metomi/rosie/svn_post_commit.py index b0569c9eb5..87c1449782 100644 --- a/metomi/rosie/svn_post_commit.py +++ b/metomi/rosie/svn_post_commit.py @@ -48,7 +48,7 @@ from metomi.rosie.svn_hook import RosieSvnHook, InfoFileError -class RosieWriteDAO(object): +class RosieWriteDAO: """Data Access Object for writing to the Rosie web service database.""" diff --git a/metomi/rosie/usertools/__init__.py b/metomi/rosie/usertools/__init__.py index f9c7376f07..b229a3d772 100644 --- a/metomi/rosie/usertools/__init__.py +++ b/metomi/rosie/usertools/__init__.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rosie/usertools/ldaptool.py b/metomi/rosie/usertools/ldaptool.py index c6426d17d4..2452e81ce8 100644 --- a/metomi/rosie/usertools/ldaptool.py +++ b/metomi/rosie/usertools/ldaptool.py @@ -27,7 +27,7 @@ from metomi.rose.resource import ResourceLocator -class LDAPUserTool(object): +class LDAPUserTool: """User information tool via LDAP.""" diff --git a/metomi/rosie/usertools/passwdtool.py b/metomi/rosie/usertools/passwdtool.py index 91cf262218..251c092d1e 100644 --- a/metomi/rosie/usertools/passwdtool.py +++ b/metomi/rosie/usertools/passwdtool.py @@ -22,7 +22,7 @@ from pwd import getpwnam -class PasswdUserTool(object): +class PasswdUserTool: """User information tool via Unix password info.""" diff --git a/metomi/rosie/vc.py b/metomi/rosie/vc.py index 5e67a3dcc2..bfccbb0edf 100644 --- a/metomi/rosie/vc.py +++ b/metomi/rosie/vc.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -163,7 +160,7 @@ def __str__(self): return "delete: %s" % id_.to_origin() -class RosieVCClient(object): +class RosieVCClient: """Client for version control functionalities.""" diff --git a/metomi/rosie/ws.py b/metomi/rosie/ws.py index 8e6bc9eaee..cb2c0a3d44 100644 --- a/metomi/rosie/ws.py +++ b/metomi/rosie/ws.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/metomi/rosie/ws_client.py b/metomi/rosie/ws_client.py index 649cb6ff16..c38fb5f252 100644 --- a/metomi/rosie/ws_client.py +++ b/metomi/rosie/ws_client.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -64,7 +61,7 @@ def __str__(self): return "Query syntax error: " + " ".join(self.args[0]) -class RosieWSClient(object): +class RosieWSClient: """A client for the Rosie web service. @@ -75,7 +72,7 @@ class RosieWSClient(object): credentials. Takes and returns the arguments username and password. popen (rose.popen.RosePopener): Use initiated RosePopener instance create a new one if ``None``. - event_handler (object): A callable object for reporting popen output, + event_handler : A callable object for reporting popen output, see :py:class:`rose.reporter.Reporter`. """ diff --git a/metomi/rosie/ws_client_auth.py b/metomi/rosie/ws_client_auth.py index 2ede0ec575..48273d4752 100644 --- a/metomi/rosie/ws_client_auth.py +++ b/metomi/rosie/ws_client_auth.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify @@ -63,7 +60,7 @@ def __str__(self): return message -class GnomekeyringStore(object): +class GnomekeyringStore: """Password management with gnomekeyring.""" @@ -121,7 +118,7 @@ def store_password(self, scheme, host, username, password): self.item_ids[(scheme, host, username)] = (None, item_id) -class GPGAgentStore(object): +class GPGAgentStore: """Password management with gpg-agent.""" @@ -232,7 +229,7 @@ def store_password(self, scheme, host, username, password): pass -class LibsecretStore(object): +class LibsecretStore: """Password management with libsecret.""" @@ -275,7 +272,7 @@ def store_password(self, scheme, host, username, password): pass -class RosieWSClientAuthManager(object): +class RosieWSClientAuthManager: """Manage authentication info for a Rosie web service client.""" diff --git a/metomi/rosie/ws_client_cli.py b/metomi/rosie/ws_client_cli.py index 878afe64a4..ab4d97afdd 100644 --- a/metomi/rosie/ws_client_cli.py +++ b/metomi/rosie/ws_client_cli.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/sphinx/conf.py b/sphinx/conf.py index 35389e5c4f..45f4eb4a6f 100644 --- a/sphinx/conf.py +++ b/sphinx/conf.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/sphinx/developing/autodoc.rst b/sphinx/developing/autodoc.rst index fa93ed0a4f..09940e2948 100644 --- a/sphinx/developing/autodoc.rst +++ b/sphinx/developing/autodoc.rst @@ -25,7 +25,7 @@ Some quick examples: .. code-block:: python - def Some_Class(object): + def Some_Class: """Some summary. Note __init__ methods are not autodocumented, specify constructor diff --git a/sphinx/ext/auto_cli_doc.py b/sphinx/ext/auto_cli_doc.py index 88c18e9831..dcea532c8d 100644 --- a/sphinx/ext/auto_cli_doc.py +++ b/sphinx/ext/auto_cli_doc.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/sphinx/ext/rose_domain.py b/sphinx/ext/rose_domain.py index 048b76b8d2..b35374caf1 100644 --- a/sphinx/ext/rose_domain.py +++ b/sphinx/ext/rose_domain.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/sphinx/ext/rose_lang.py b/sphinx/ext/rose_lang.py index 4e85e460c5..85f6025ae3 100644 --- a/sphinx/ext/rose_lang.py +++ b/sphinx/ext/rose_lang.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/sphinx/ext/script_include.py b/sphinx/ext/script_include.py index 067330fdc7..989869c5be 100644 --- a/sphinx/ext/script_include.py +++ b/sphinx/ext/script_include.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/sphinx/extract-pdf-documents.py b/sphinx/extract-pdf-documents.py index 1e97d81a5d..839d01d34f 100644 --- a/sphinx/extract-pdf-documents.py +++ b/sphinx/extract-pdf-documents.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/t/rosa-svn-pre-commit/00-basic.t b/t/rosa-svn-pre-commit/00-basic.t index e1672cc8ee..0923920d2e 100755 --- a/t/rosa-svn-pre-commit/00-basic.t +++ b/t/rosa-svn-pre-commit/00-basic.t @@ -34,7 +34,7 @@ mkdir repos svnadmin create repos/foo SVN_URL=file://$PWD/repos/foo ROSE_BIN=$(dirname $(command -v rose)) -ROSE_LIB=$(dirname $(python -c "import metomi.rose; print(metomi.rose.__file__)")) +ROSE_LIB=$(dirname $(python3 -c "import metomi.rose; print(metomi.rose.__file__)")) export ROSE_LIB ROSE_BIN cat >repos/foo/hooks/pre-commit <<__PRE_COMMIT__ #!/bin/bash diff --git a/t/rosa-svn-pre-commit/01-rosie-create.t b/t/rosa-svn-pre-commit/01-rosie-create.t index 38e8822705..98849dcba8 100755 --- a/t/rosa-svn-pre-commit/01-rosie-create.t +++ b/t/rosa-svn-pre-commit/01-rosie-create.t @@ -36,7 +36,7 @@ prefix-owner-default.foo=fred prefix-location.foo=$SVN_URL __ROSE_CONF__ ROSE_BIN_HOME=$(dirname $(command -v rose)) -ROSE_LIB=$(dirname $(python -c "import metomi.rose; print(metomi.rose.__file__)")) +ROSE_LIB=$(dirname $(python3 -c "import metomi.rose; print(metomi.rose.__file__)")) export ROSE_CONF_PATH=$PWD/conf cat >repos/foo/hooks/pre-commit <<__PRE_COMMIT__ #!/bin/bash diff --git a/t/rosa-svn-pre-commit/02-passwd.t b/t/rosa-svn-pre-commit/02-passwd.t index a55ec7ba23..0750a15a41 100755 --- a/t/rosa-svn-pre-commit/02-passwd.t +++ b/t/rosa-svn-pre-commit/02-passwd.t @@ -35,7 +35,7 @@ mkdir repos svnadmin create repos/foo SVN_URL=file://$PWD/repos/foo ROSE_BIN_HOME=$(dirname $(command -v rose)) -ROSE_LIB=$(dirname $(python -c "import metomi.rose; print(metomi.rose.__file__)")) +ROSE_LIB=$(dirname $(python3 -c "import metomi.rose; print(metomi.rose.__file__)")) cat >repos/foo/hooks/pre-commit <<__PRE_COMMIT__ #!/bin/bash export ROSE_CONF_PATH=$PWD/conf diff --git a/t/rosa-svn-pre-commit/03-meta.t b/t/rosa-svn-pre-commit/03-meta.t index 368701f5a5..11a909b2b7 100755 --- a/t/rosa-svn-pre-commit/03-meta.t +++ b/t/rosa-svn-pre-commit/03-meta.t @@ -41,7 +41,7 @@ mkdir 'repos' svnadmin create 'repos/foo' SVN_URL="file://${PWD}/repos/foo" ROSE_BIN_HOME=$(dirname $(command -v rose)) -ROSE_LIB=$(dirname $(python -c "import metomi.rose; print(metomi.rose.__file__)")) +ROSE_LIB=$(dirname $(python3 -c "import metomi.rose; print(metomi.rose.__file__)")) cat >'repos/foo/hooks/pre-commit' <<__PRE_COMMIT__ #!/bin/bash export ROSE_CONF_PATH=${PWD}/conf diff --git a/t/rose-app-upgrade/lib/versions_cwd.py b/t/rose-app-upgrade/lib/versions_cwd.py index 1d87ff94f7..c59974399d 100644 --- a/t/rose-app-upgrade/lib/versions_cwd.py +++ b/t/rose-app-upgrade/lib/versions_cwd.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- import os diff --git a/t/rose-macro/lib/custom_macro_change.py b/t/rose-macro/lib/custom_macro_change.py index 338b548e05..72d55a04a1 100644 --- a/t/rose-macro/lib/custom_macro_change.py +++ b/t/rose-macro/lib/custom_macro_change.py @@ -1,8 +1,4 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/t/rose-macro/lib/custom_macro_change_arg.py b/t/rose-macro/lib/custom_macro_change_arg.py index 83e9b9c0f0..8789369848 100644 --- a/t/rose-macro/lib/custom_macro_change_arg.py +++ b/t/rose-macro/lib/custom_macro_change_arg.py @@ -1,8 +1,4 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/t/rose-macro/lib/custom_macro_change_bad.py b/t/rose-macro/lib/custom_macro_change_bad.py index d210cd595e..48dd23b900 100644 --- a/t/rose-macro/lib/custom_macro_change_bad.py +++ b/t/rose-macro/lib/custom_macro_change_bad.py @@ -1,8 +1,4 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/t/rose-macro/lib/custom_macro_check.py b/t/rose-macro/lib/custom_macro_check.py index 102d48b72b..985280a259 100644 --- a/t/rose-macro/lib/custom_macro_check.py +++ b/t/rose-macro/lib/custom_macro_check.py @@ -1,8 +1,4 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. -# # This file is part of Rose, a framework for meteorological suites. # # Rose is free software: you can redistribute it and/or modify diff --git a/t/rose-macro/lib/custom_macro_cwd.py b/t/rose-macro/lib/custom_macro_cwd.py index b5fcd8ed80..977f601578 100644 --- a/t/rose-macro/lib/custom_macro_cwd.py +++ b/t/rose-macro/lib/custom_macro_cwd.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- import os diff --git a/t/rose-macro/lib/custom_macro_prompt.py b/t/rose-macro/lib/custom_macro_prompt.py index 9754fa5af8..e6712c31d0 100644 --- a/t/rose-macro/lib/custom_macro_prompt.py +++ b/t/rose-macro/lib/custom_macro_prompt.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- import metomi.rose.macro diff --git a/t/rose-metadata-check/09-custom-macro.t b/t/rose-metadata-check/09-custom-macro.t index 38d4b11e1c..3845f8ee02 100755 --- a/t/rose-metadata-check/09-custom-macro.t +++ b/t/rose-metadata-check/09-custom-macro.t @@ -121,7 +121,7 @@ file_cmp "$TEST_KEY.out" "$TEST_KEY.out" Date: Thu, 31 Dec 2020 11:06:26 +0000 Subject: [PATCH 11/78] rose_bunch: don't rerun for meaningless PATH changes --- metomi/rose/apps/rose_bunch.py | 47 +++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/metomi/rose/apps/rose_bunch.py b/metomi/rose/apps/rose_bunch.py index f159caacd5..3699ddbb6a 100644 --- a/metomi/rose/apps/rose_bunch.py +++ b/metomi/rose/apps/rose_bunch.py @@ -17,7 +17,7 @@ """Builtin application: rose_bunch: run multiple commands in parallel. """ - +from collections import Counter import itertools import os import shlex @@ -438,7 +438,7 @@ def connect(self): def create_tables(self): """Create tables as appropriate""" existing = [] - first_run = os.environ.get("CYLC_TASK_SUBMIT_NUMBER") == "1" + first_run = os.environ.get("CYLC_TASK_TRY_NUMBER") == "1" for row in self.conn.execute("SELECT name FROM sqlite_master " + "WHERE type=='table'"): @@ -540,7 +540,15 @@ def same_prev_config(self, current): unchanged = True current = self.flatten_config(current) for key, value in self.conn.execute(s_stmt): - if key in current: + if key == 'env_PATH': + # due to re-invocation the PATH may have changed in-between + # runs - only re-run jobs if the PATH has changed in a way + # that could actually make a difference + if simplify_path(current[key]) != simplify_path(value): + break + else: + current.pop(key) + elif key in current: if current[key] != value: break else: @@ -551,3 +559,36 @@ def same_prev_config(self, current): if current: unchanged = False return unchanged + + +def simplify_path(path): + """Removes duplication in paths whilst maintaining integrity. + + If duplicate items are present in a path this keeps the first item and + removes any subsequent duplicates. + + Examples: + >>> simplify_path('') + '' + >>> simplify_path('a') + 'a' + >>> simplify_path('a:a:a') + 'a' + >>> simplify_path('a:b:a') + 'a:b' + >>> simplify_path('a:b:b:a') + 'a:b' + >>> simplify_path('a:b:a:b:c:d:a:b:c:d:e') + 'a:b:c:d:e' + + """ + path = path.split(':') + counter = Counter(path) + for item, count in counter.items(): + ptr = len(path) - 1 + while count > 1: + if path[ptr] == item: + path.pop(ptr) + count -= 1 + ptr -= 1 + return ':'.join(path) From 6ca98af5976d9e9bd91caff555150acdfac4e048 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Thu, 31 Dec 2020 11:06:48 +0000 Subject: [PATCH 12/78] portability: POSIX compliance --- t/rose-task-run/30-app-arch-opt-source.t | 2 +- t/rose-task-run/32-app-arch-compressed.t | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/t/rose-task-run/30-app-arch-opt-source.t b/t/rose-task-run/30-app-arch-opt-source.t index 8e70fe4a59..4b4d225308 100755 --- a/t/rose-task-run/30-app-arch-opt-source.t +++ b/t/rose-task-run/30-app-arch-opt-source.t @@ -45,7 +45,7 @@ file_grep "${TEST_KEY}-archive2-02" \ 'CYLC_JOB_EXIT=SUCCEEDED' \ "${SUITE_RUN_DIR}/log/job/1/archive2/02/job.status" TEST_KEY="${TEST_KEY_BASE}-find" -(cd "${SUITE_RUN_DIR}/share/backup" && find -type f) | sort >"${TEST_KEY}.out" +(cd "${SUITE_RUN_DIR}/share/backup" && find . -type f) | sort >"${TEST_KEY}.out" file_cmp "${TEST_KEY}.out" "${TEST_KEY}.out" <<'__FIND__' ./archive1.d/2014.txt ./archive1.d/2016.txt diff --git a/t/rose-task-run/32-app-arch-compressed.t b/t/rose-task-run/32-app-arch-compressed.t index 75495834f8..d3cba2254c 100755 --- a/t/rose-task-run/32-app-arch-compressed.t +++ b/t/rose-task-run/32-app-arch-compressed.t @@ -39,7 +39,7 @@ file_grep "${TEST_KEY}-archive-01" \ 'CYLC_JOB_EXIT=SUCCEEDED' \ "${SUITE_RUN_DIR}/log/job/1/archive/01/job.status" TEST_KEY="${TEST_KEY_BASE}-find" -(cd "${SUITE_RUN_DIR}/share/backup" && find -type f) | sort >"${TEST_KEY}.out" +(cd "${SUITE_RUN_DIR}/share/backup" && find . -type f) | sort >"${TEST_KEY}.out" file_cmp "${TEST_KEY}.out" "${TEST_KEY}.out" <<'__FIND__' ./archive.d/2016.txt.gz ./archive.d/whatever.tar.gz From 18e9727e0e25a4e9e9c9c33339f5febb19df8c13 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Thu, 31 Dec 2020 12:05:48 +0000 Subject: [PATCH 13/78] rosie id: skip cylc8 installed flows for now --- metomi/rosie/suite_id.py | 21 +++++++++++++----- t/rosie-id/00-basic.t | 48 ++++++++++++++++++++++------------------ 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/metomi/rosie/suite_id.py b/metomi/rosie/suite_id.py index 8555fc01ef..7448e854a0 100644 --- a/metomi/rosie/suite_id.py +++ b/metomi/rosie/suite_id.py @@ -369,12 +369,23 @@ def _from_location(self, location): suite_engine_proc, "SUITE_DIR_REL_ROOT", None) # Cylc8 run directory + # TODO: extract version control information loc = Path(location) - sdrr = Path(suite_dir_rel_root) - if loc.relative_to(sdrr): - if (loc / 'rose-suite.info').exists(): - # TODO - raise SuiteIdLocationError(location) + sdrr = Path('~', suite_dir_rel_root).expanduser().resolve() + try: + if loc.relative_to(sdrr): + if (loc / 'rose-suite.info').exists(): + # This is an installed workflow with a rose-suite.info file + # (most likely a Cylc8 run directory) + + # TODO: extract version control information written by + # Cylc install, see: + # https://github.com/metomi/rose/issues/2432 + # https://github.com/cylc/cylc-flow/issues/3849 + raise SuiteIdLocationError(location) + except ValueError: + # Not an installed Cylc8 workflow run directory + pass # Cylc7 run directory # Use a hacky way to read the "log/rose-suite-run.version" file diff --git a/t/rosie-id/00-basic.t b/t/rosie-id/00-basic.t index a47af24a23..cff1894dff 100755 --- a/t/rosie-id/00-basic.t +++ b/t/rosie-id/00-basic.t @@ -21,7 +21,7 @@ #------------------------------------------------------------------------------- . $(dirname $0)/test_header #------------------------------------------------------------------------------- -tests 39 +tests 36 #------------------------------------------------------------------------------- svnadmin create foo URL=file://$PWD/foo @@ -114,26 +114,32 @@ foo-aa000 __OUT__ file_cmp "$TEST_KEY.err" "$TEST_KEY.err" 'foo-aa000/suite.rc' <<'__SUITE_RC__' -[scheduling] - [[dependencies]] - graph='t1' -[runtime] - [[t1]] -__SUITE_RC__ -rose suite-run -l -q -C "${PWD}/foo-aa000" --name="${SUITE_NAME}" -run_pass "$TEST_KEY" rosie id "${HOME}/cylc-run/${SUITE_NAME}" -file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__ -foo-aa000 -__OUT__ -file_cmp "$TEST_KEY.err" "$TEST_KEY.err" 'foo-aa000/suite.rc' <<'__SUITE_RC__' +#[scheduling] +# [[dependencies]] +# graph='t1' +#[runtime] +# [[t1]] +#__SUITE_RC__ +#cylc install \ +# -C "${PWD}/foo-aa000" \ +# --flow-name="${SUITE_NAME}" \ +# --no-run-name +#run_pass "$TEST_KEY" rosie id "${HOME}/cylc-run/${SUITE_NAME}" +#file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__ +#foo-aa000 +#__OUT__ +#file_cmp "$TEST_KEY.err" "$TEST_KEY.err" Date: Tue, 22 Dec 2020 13:16:17 +0000 Subject: [PATCH 14/78] tests: fix functional test failures * rose-task-env * rose-task-run * rose-ana * put cylc commands behind run_pass * sqlite3 changes * misc portability * portability for port scanning * reinstate rosie-graph test header --- t/lib/bash/test_header | 19 +++--- t/rose-ana/00-run-basic.t | 20 +++++-- t/rose-ana/01-run-basic-v1.t | 19 ++++-- t/rose-macro/00-null.t | 2 +- t/rose-macro/13-custom-check.t | 7 ++- t/rose-task-env/00-non-cycle.t | 15 ++++- t/rose-task-env/01-integer-cycling.t | 15 ++++- t/rose-task-env/02-360day-cycling.t | 15 ++++- t/rose-task-run/00-run-basic.t | 15 ++++- t/rose-task-run/01-run-basic-iso.t | 24 ++++---- t/rose-task-run/04-run-path-empty.t | 15 ++++- t/rose-task-run/06-app-prune-iso.t | 32 +++++----- t/rose-task-run/07-app-arch.t | 25 ++++++-- t/rose-task-run/08-app-fcm-make.t | 25 +++++--- t/rose-task-run/09-app-prune-work.t | 17 ++++-- t/rose-task-run/10-specify-cycle.t | 15 ++++- t/rose-task-run/11-specify-cycle-iso.t | 23 ++++---- t/rose-task-run/12-app-prune-integer.t | 34 +++++++---- t/rose-task-run/13-app-arch-cmd-out.t | 15 ++++- t/rose-task-run/14-app-prune-remove.t | 18 ++++-- t/rose-task-run/15-app-prune-extglob.t | 17 ++++-- t/rose-task-run/16-app-prune-point.t | 18 ++++-- .../17-app-prune-glob-as-cycle-fmt.t | 17 ++++-- t/rose-task-run/18-app-fcm-make-ctx-name.t | 20 +++++-- t/rose-task-run/20-app-fcm-make-dest.t | 20 +++++-- t/rose-task-run/24-app-fcm-make-fast.t | 19 ++++-- t/rose-task-run/25-app-fcm-make-new-mode.t | 18 ++++-- .../26-app-fcm-make-new-mode-with-cont.t | 19 ++++-- t/rose-task-run/28-env-path-run-path.t | 17 ++++-- t/rose-task-run/29-app-prune-extglob-remote.t | 20 +++++-- t/rose-task-run/30-app-arch-opt-source.t | 15 ++++- t/rose-task-run/31-app-bunch.t | 17 ++++-- t/rose-task-run/32-app-arch-compressed.t | 15 ++++- t/rose-task-run/33-app-prune-cycle-format.t | 17 ++++-- .../34-app-prune-hosts-sharing-fs.t | 21 +++++-- .../35-app-prune-log-on-hosts-sharing-fs.t | 21 +++++-- t/rose-task-run/36-app-arch-interrupted.t | 15 ++++- t/rose-task-run/37-app-bunch-rm-old.t | 27 +++++++-- t/rose-task-run/38-app-bunch-counts.t | 17 ++++-- t/rose-task-run/39-app-prune-cycle-host.t | 19 ++++-- t/rose-task-run/40-app-arch-duplicate.t | 15 ++++- .../41-app-bunch-default-command.t | 26 ++++++-- t/rosie-graph/test_header_extra | 59 ++++++++++++++++++- 43 files changed, 620 insertions(+), 219 deletions(-) mode change 120000 => 100644 t/rosie-graph/test_header_extra diff --git a/t/lib/bash/test_header b/t/lib/bash/test_header index 4041a6f775..62ac9fd810 100644 --- a/t/lib/bash/test_header +++ b/t/lib/bash/test_header @@ -351,14 +351,15 @@ poll() { } port_is_busy() { - local PORT="${1}" - if type -P netcat 1>/dev/null; then - HOSTNAME="${HOSTNAME:-"$(hostname)"}" - HOSTNAME="${HOSTNAME:-'localhost'}" - netcat -z "${HOSTNAME}" "${PORT}" - else - netstat -atun | grep -q "0.0.0.0:${PORT}" - fi + # use Python rather than netcat/netstat which aren't as portable + python3 -c ' +import socket +import sys + +print(sys.argv[1]) +with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + assert sock.connect_ex(("localhost", int(sys.argv[1]))) == 0 + ' "$1" 2>/dev/null } TEST_ROSE_WS_PID= @@ -445,7 +446,7 @@ DIFFTOOL="$(rose config '--default=diff -u' t difftool)" TEST_KEY_BASE="$(basename "$0" .t)" # shellcheck disable=SC2034 TEST_SOURCE_DIR="$(cd "$(dirname "$0")" && pwd)" -TEST_DIR="$(mktemp -d)" +TEST_DIR="$(realpath "$(mktemp -d)")" cd "$TEST_DIR" set +e diff --git a/t/rose-ana/00-run-basic.t b/t/rose-ana/00-run-basic.t index 862ce047bf..a15798b8e6 100644 --- a/t/rose-ana/00-run-basic.t +++ b/t/rose-ana/00-run-basic.t @@ -21,8 +21,7 @@ #------------------------------------------------------------------------------- . $(dirname $0)/test_header #------------------------------------------------------------------------------- -N_TESTS=81 -tests $N_TESTS +tests 82 #------------------------------------------------------------------------------- # The database test is only valid if the user has set things up to use it, so @@ -36,13 +35,22 @@ __CONF__ # Run the suite. export CYLC_CONF_PATH= export ROSE_CONF_PATH=$PWD/conf -TEST_KEY=$TEST_KEY_BASE mkdir -p $HOME/cylc-run SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') NAME=$(basename $SUITE_RUN_DIR) -# run_fail "$TEST_KEY" \ - rose-suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost -- --no-detach --debug +TEST_KEY="${TEST_KEY_BASE}-install" +run_pass "$TEST_KEY" \ + cylc install \ + -C $TEST_SOURCE_DIR/$TEST_KEY_BASE \ + --flow-name=$NAME \ + --no-run-name +TEST_KEY="${TEST_KEY_BASE}-run" +run_fail "$TEST_KEY" \ + cylc run \ + "${NAME}" \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- # Test the output # diff --git a/t/rose-ana/01-run-basic-v1.t b/t/rose-ana/01-run-basic-v1.t index a46e189035..c1233a57db 100644 --- a/t/rose-ana/01-run-basic-v1.t +++ b/t/rose-ana/01-run-basic-v1.t @@ -21,18 +21,27 @@ #------------------------------------------------------------------------------- . $(dirname $0)/test_header #------------------------------------------------------------------------------- -N_TESTS=36 +N_TESTS=37 tests $N_TESTS #------------------------------------------------------------------------------- # Run the suite. export ROSE_CONF_PATH= -TEST_KEY=$TEST_KEY_BASE mkdir -p $HOME/cylc-run SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') NAME=$(basename $SUITE_RUN_DIR) -run_fail "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost -- --no-detach --debug +TEST_KEY="${TEST_KEY_BASE}-install" +run_pass "$TEST_KEY" \ + cylc install \ + -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ + --flow-name="$NAME" \ + --no-run-name +TEST_KEY="${TEST_KEY_BASE}-run" +run_pass "$TEST_KEY" \ + cylc run \ + "${NAME}" \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- # Test the output OUTPUT=$HOME/cylc-run/$NAME/log/job/1/rose_ana_t1/01/job.out diff --git a/t/rose-macro/00-null.t b/t/rose-macro/00-null.t index 125673d14a..83843af936 100755 --- a/t/rose-macro/00-null.t +++ b/t/rose-macro/00-null.t @@ -31,7 +31,7 @@ setup run_fail "$TEST_KEY" rose macro file_cmp "$TEST_KEY.out" "$TEST_KEY.out" "$TEST_KEY_BASE-db.out" diff --git a/t/rose-task-env/01-integer-cycling.t b/t/rose-task-env/01-integer-cycling.t index d9db984d28..66c1958910 100755 --- a/t/rose-task-env/01-integer-cycling.t +++ b/t/rose-task-env/01-integer-cycling.t @@ -24,13 +24,22 @@ export ROSE_CONF_PATH= #------------------------------------------------------------------------------- -tests 7 +tests 9 #------------------------------------------------------------------------------- # Run the suite. SUITE_RUN_DIR=$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX') NAME=$(basename "${SUITE_RUN_DIR}") -rose suite-run -q -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host=localhost -- --no-detach --debug +run_pass "${TEST_KEY_BASE}-install" \ + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + --flow-name="${NAME}" \ + --no-run-name +run_pass "${TEST_KEY_BASE}-run" \ + cylc run \ + "${NAME}" \ + --host=localhost \ + --no-detach \ + --debug run_pass "$TEST_KEY_BASE-0" ls -d $HOME/cylc-run/${NAME}/share/cycle/0 run_pass "$TEST_KEY_BASE-1" ls -d $HOME/cylc-run/${NAME}/share/cycle/1 diff --git a/t/rose-task-env/02-360day-cycling.t b/t/rose-task-env/02-360day-cycling.t index eece22c5a6..54f4b0a872 100755 --- a/t/rose-task-env/02-360day-cycling.t +++ b/t/rose-task-env/02-360day-cycling.t @@ -23,12 +23,21 @@ export ROSE_CONF_PATH= -tests 1 +tests 3 RUN_DIR="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rtb-rose-task-env-02.XXXXXX')" NAME="$(basename "${RUN_DIR}")" -rose suite-run -q -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host='localhost' -- --no-detach --debug +run_pass "${TEST_KEY_BASE}-install" \ + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + --flow-name="${NAME}" \ + --no-run-name +run_pass "${TEST_KEY_BASE}-run" \ + cylc run \ + "${NAME}" \ + --host='localhost' \ + --no-detach \ + --debug for CYCLE in \ '20200227T0000Z' \ '20200228T0000Z' \ diff --git a/t/rose-task-run/00-run-basic.t b/t/rose-task-run/00-run-basic.t index 2cca47e1be..75e3af6785 100755 --- a/t/rose-task-run/00-run-basic.t +++ b/t/rose-task-run/00-run-basic.t @@ -24,13 +24,22 @@ export ROSE_CONF_PATH= #------------------------------------------------------------------------------- -tests 44 +tests 46 #------------------------------------------------------------------------------- # Run the suite. SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') NAME=$(basename $SUITE_RUN_DIR) -rose suite-run -q -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost -- --no-detach --debug +run_pass "${TEST_KEY_BASE}-install" \ + cylc install \ + -C $TEST_SOURCE_DIR/$TEST_KEY_BASE \ + --flow-name=$NAME \ + --no-run-name +run_pass "${TEST_KEY_BASE}-run" \ + cylc run \ + "${NAME}" \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- MY_PATH= for P in $(ls -d $SUITE_RUN_DIR/etc/my-path/*); do diff --git a/t/rose-task-run/01-run-basic-iso.t b/t/rose-task-run/01-run-basic-iso.t index 0098c17718..06ed0c3fbe 100755 --- a/t/rose-task-run/01-run-basic-iso.t +++ b/t/rose-task-run/01-run-basic-iso.t @@ -23,21 +23,23 @@ export ROSE_CONF_PATH= +tests 48 + #------------------------------------------------------------------------------- # Run the suite. SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') NAME=$(basename $SUITE_RUN_DIR) -rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME -l \ - 1>/dev/null 2>&1 -if (($? != 0)); then - skip_all "cylc version not compatible with ISO 8601" - exit 0 -fi -#------------------------------------------------------------------------------- -tests 46 -#------------------------------------------------------------------------------- -rose suite-run -q -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost -- --no-detach --debug +run_pass "${TEST_KEY_BASE}-install" \ + cylc install \ + -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ + --flow-name="$NAME" \ + --no-run-name +run_pass "${TEST_KEY_BASE}-run" \ + cylc run \ + "$NAME" \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- MY_PATH= for P in $(ls -d $SUITE_RUN_DIR/etc/my-path/*); do diff --git a/t/rose-task-run/04-run-path-empty.t b/t/rose-task-run/04-run-path-empty.t index cda05f7ce5..c3fa03480e 100755 --- a/t/rose-task-run/04-run-path-empty.t +++ b/t/rose-task-run/04-run-path-empty.t @@ -24,13 +24,22 @@ export ROSE_CONF_PATH= #------------------------------------------------------------------------------- -tests 3 +tests 5 #------------------------------------------------------------------------------- # Run the suite. SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') NAME=$(basename $SUITE_RUN_DIR) -rose suite-run -q -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost -- --no-detach --debug +run_pass "${TEST_KEY_BASE}-install" \ + cylc install \ + -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ + --flow-name="$NAME" \ + --no-run-name +run_pass "${TEST_KEY_BASE}-run" \ + cylc run \ + "${NAME}" \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- PREV_CYCLE= for CYCLE in 20130101T0000Z 20130101T1200Z 20130102T0000Z; do diff --git a/t/rose-task-run/06-app-prune-iso.t b/t/rose-task-run/06-app-prune-iso.t index dbefc972ba..34a37cfaae 100755 --- a/t/rose-task-run/06-app-prune-iso.t +++ b/t/rose-task-run/06-app-prune-iso.t @@ -26,7 +26,9 @@ # Test the suite. JOB_HOST=$(rose config --default= 't' 'job-host') if [[ -n $JOB_HOST ]]; then - JOB_HOST=$(rose host-select -q $JOB_HOST) + JOB_HOST="-S JOB_HOST='$(rose host-select -q "$JOB_HOST")'" +else + JOB_HOST= fi export CYLC_CONF_PATH= export ROSE_CONF_PATH= @@ -34,24 +36,22 @@ TEST_KEY=$TEST_KEY_BASE mkdir -p $HOME/cylc-run SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') NAME=$(basename $SUITE_RUN_DIR) -rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME -l \ - 1>/dev/null 2>&1 -if (($? != 0)); then - skip_all "cylc version not compatible with ISO 8601" - exit 0 -fi + #------------------------------------------------------------------------------- -tests 7 +tests 9 #------------------------------------------------------------------------------- -# Run the suite. -if [[ -n ${JOB_HOST:-} ]]; then - rose suite-run -q -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ +run_pass "${TEST_KEY_BASE}-install" \ + cylc install \ + -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ + --flow-name="$NAME" \ + --no-run-name \ + "$JOB_HOST" +run_pass "${TEST_KEY_BASE}-run" \ + cylc run \ + "$NAME" \ --host=localhost \ - -D "[jinja2:suite.rc]HOST=\"$JOB_HOST\"" -- --no-detach --debug -else - rose suite-run -q -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost -- --no-detach --debug -fi + --no-detach \ + --debug #------------------------------------------------------------------------------- TEST_KEY=$TEST_KEY_BASE-prune-log sed '/^\[INFO\] \(create\|delete\|update\)/!d; diff --git a/t/rose-task-run/07-app-arch.t b/t/rose-task-run/07-app-arch.t index bda24a78b6..6b5525b830 100755 --- a/t/rose-task-run/07-app-arch.t +++ b/t/rose-task-run/07-app-arch.t @@ -23,17 +23,26 @@ #------------------------------------------------------------------------------- -tests 48 +tests 49 #------------------------------------------------------------------------------- # Run the suite, and wait for it to complete export ROSE_CONF_PATH= -TEST_KEY=$TEST_KEY_BASE mkdir -p $HOME/cylc-run SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') NAME=$(basename $SUITE_RUN_DIR) +TEST_KEY="${TEST_KEY_BASE}-install" run_pass "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost -- --no-detach + cylc install \ + -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ + --flow-name="$NAME" \ + --no-run-name +TEST_KEY="${TEST_KEY_BASE}-run" +run_pass "$TEST_KEY" \ + cylc run \ + "$NAME" \ + --host=localhost \ + --no-detach \ + --debug cp ${TEST_KEY}.err ~/temp cp ${TEST_KEY}.out ~/temp #------------------------------------------------------------------------------- @@ -130,7 +139,12 @@ file_cmp "${TEST_KEY}.err" "${TEST_KEY}.err" <<__ERR__ [FAIL] ! hello/venus.txt (hello/venus.txt) __ERR__ TEST_KEY="$TEST_KEY_BASE-bad-archive-6" -sed '/^\[FAIL\] /!d; s/ \[compress.*\]$//' "$TEST_KEY.err" \ +# NOTE: /BASH_XTRACEFD/d is to remove any Cylc errors of the form: +# BASH_XTRACEFD: 19: invalid value for trace file descriptor +sed \ + -e '/^\[FAIL\] /!d; s/ \[compress.*\]$//' \ + -e '/BASH_XTRACEFD/d' \ + "$TEST_KEY.err" \ "${FILE_PREFIX}6/01/job.err" >"$TEST_KEY.err" file_cmp "$TEST_KEY.err" "$TEST_KEY.err" <<__ERR__ [FAIL] foo push foo://20130101T1200Z/hello/worlds/mars.txt.gz $SUITE_RUN_DIR/share/cycle/20130101T1200Z/hello/mars.txt # return-code=1, stderr= @@ -159,6 +173,7 @@ sed -e '/^\[FAIL\] /!d' \ -e 's/^\(\[FAIL\] my-bad-command\) .*\( # return-code=1, stderr=\)$/\1\2/' \ -e '/^\[FAIL\] \[my-bad-command\]/d' \ -e 's/ \[compress.*]$//' \ + -e '/BASH_XTRACEFD/d' \ "$SUITE_RUN_DIR/log/job/$CYCLE/archive_bad_9/01/job.err" >"$TEST_KEY.err" file_cmp "${TEST_KEY}.err" "${TEST_KEY}.err" <<__ERR__ [FAIL] ! foo://20130101T1200Z/planet-n.tar.gz diff --git a/t/rose-task-run/08-app-fcm-make.t b/t/rose-task-run/08-app-fcm-make.t index d23e732a70..2268488882 100755 --- a/t/rose-task-run/08-app-fcm-make.t +++ b/t/rose-task-run/08-app-fcm-make.t @@ -28,7 +28,7 @@ if ! gfortran --version 1>/dev/null 2>&1; then skip_all '"gfortran" unavailable' fi #------------------------------------------------------------------------------- -tests 8 +tests 10 #------------------------------------------------------------------------------- JOB_HOST=$(rose config --default= 't' 'job-host') if [[ -n $JOB_HOST ]]; then @@ -40,16 +40,23 @@ export ROSE_CONF_PATH= mkdir -p $HOME/cylc-run SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') NAME=$(basename $SUITE_RUN_DIR) +OPTS=( + "--flow-name=${NAME}" + "--no-run-name" +) if [[ -n ${JOB_HOST:-} ]]; then - timeout 60 rose suite-run -q \ - -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host='localhost' \ - -D "[jinja2:suite.rc]HOST=\"$JOB_HOST\"" -- --no-detach --debug -else - timeout 60 rose suite-run -q \ - -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host='localhost' -- --no-detach --debug + OPTS+=(-S "HOST='$JOB_HOST'") fi +run_pass "${TEST_KEY_BASE}-install" \ + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + "${OPTS[@]}" +run_pass "${TEST_KEY_BASE}-run" \ + cylc run \ + "${NAME}" \ + --host='localhost' \ + --no-detach \ + --debug #------------------------------------------------------------------------------- TEST_KEY="$TEST_KEY_BASE-status" sqlite3 $SUITE_RUN_DIR/log/db \ diff --git a/t/rose-task-run/09-app-prune-work.t b/t/rose-task-run/09-app-prune-work.t index 04a6a44082..72aaff5aee 100755 --- a/t/rose-task-run/09-app-prune-work.t +++ b/t/rose-task-run/09-app-prune-work.t @@ -23,16 +23,25 @@ #------------------------------------------------------------------------------- -tests 2 +tests 3 #------------------------------------------------------------------------------- export ROSE_CONF_PATH= -TEST_KEY=$TEST_KEY_BASE mkdir -p $HOME/cylc-run SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') NAME=$(basename $SUITE_RUN_DIR) +TEST_KEY="${TEST_KEY_BASE}-install" run_pass "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost -- --no-detach --debug + cylc install \ + -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ + --flow-name="$NAME" \ + --no-run-name +TEST_KEY="${TEST_KEY_BASE}-run" +run_pass "$TEST_KEY" \ + cylc run \ + "${NAME}" \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- TEST_KEY="$TEST_KEY_BASE-prune.log" sed 's/[0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0-9]*/YYYY-MM-DDTHHMM/g'\ diff --git a/t/rose-task-run/10-specify-cycle.t b/t/rose-task-run/10-specify-cycle.t index 7a9e91fc0d..448c6f6980 100755 --- a/t/rose-task-run/10-specify-cycle.t +++ b/t/rose-task-run/10-specify-cycle.t @@ -24,14 +24,23 @@ export ROSE_CONF_PATH= #------------------------------------------------------------------------------- -tests 1 +tests 3 #------------------------------------------------------------------------------- # Run the suite. TEST_KEY=$TEST_KEY_BASE SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') NAME=$(basename $SUITE_RUN_DIR) -rose suite-run -q -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost -- --no-detach --debug +run_pass "${TEST_KEY_BASE}-install" \ + cylc install \ + -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ + --flow-name=$NAME \ + --no-run-name +run_pass "${TEST_KEY_BASE}-run" \ + cylc run \ + "${NAME}" \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- file_cmp "$TEST_KEY" "$SUITE_RUN_DIR/file" <<<'20121231T1200Z' rose suite-clean -q -y $NAME diff --git a/t/rose-task-run/11-specify-cycle-iso.t b/t/rose-task-run/11-specify-cycle-iso.t index 73aab90d7b..7fbc7a55dc 100755 --- a/t/rose-task-run/11-specify-cycle-iso.t +++ b/t/rose-task-run/11-specify-cycle-iso.t @@ -25,20 +25,21 @@ export ROSE_CONF_PATH= #------------------------------------------------------------------------------- # Run the suite. +tests 3 TEST_KEY=$TEST_KEY_BASE SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') NAME=$(basename $SUITE_RUN_DIR) -rose suite-run -q -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME -l \ - 1>/dev/null 2>&1 -if (($? != 0)); then - skip_all "cylc version not compatible with ISO 8601" - exit 0 -fi -#------------------------------------------------------------------------------- -tests 1 -#------------------------------------------------------------------------------- -rose suite-run -q -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost -- --no-detach --debug +run_pass "${TEST_KEY_BASE}-install" \ + cylc install \ + -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ + --flow-name=$NAME \ + --no-run-name +run_pass "${TEST_KEY_BASE}-run" \ + cylc run \ + "${NAME}" \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- file_cmp "$TEST_KEY" "$SUITE_RUN_DIR/file" <<<'20121231T1200Z' rose suite-clean -q -y $NAME diff --git a/t/rose-task-run/12-app-prune-integer.t b/t/rose-task-run/12-app-prune-integer.t index 11ec65c684..987e931200 100755 --- a/t/rose-task-run/12-app-prune-integer.t +++ b/t/rose-task-run/12-app-prune-integer.t @@ -25,30 +25,38 @@ JOB_HOST=$(rose config --default= 't' 'job-host') if [[ -n $JOB_HOST ]]; then JOB_HOST=$(rose host-select -q $JOB_HOST) - tests 15 + tests 16 else - tests 12 + tests 13 fi #------------------------------------------------------------------------------- # Run the suite. export CYLC_CONF_PATH= export ROSE_CONF_PATH= -TEST_KEY=$TEST_KEY_BASE mkdir -p $HOME/cylc-run SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') NAME=$(basename $SUITE_RUN_DIR) +OPTS=( + "--flow-name=$NAME" + --no-run-name +) if [[ -n ${JOB_HOST:-} ]]; then - run_pass "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost \ - -D "[jinja2:suite.rc]HOST=\"$JOB_HOST\"" \ - -- --no-detach --debug -else - run_pass "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost \ - -- --no-detach --debug + OPTS+=( + -S "HOST='$JOB_HOST'" + ) fi +TEST_KEY="${TEST_KEY_BASE}-install" +run_pass "${TEST_KEY}" \ + cylc install \ + -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ + "${OPTS[@]}" +TEST_KEY="${TEST_KEY_BASE}-run" +run_pass "${TEST_KEY}" \ + cylc run \ + "${NAME}" \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- TEST_KEY=$TEST_KEY_BASE-work run_fail "$TEST_KEY.1" ls -d $HOME/cylc-run/$NAME/work/1 diff --git a/t/rose-task-run/13-app-arch-cmd-out.t b/t/rose-task-run/13-app-arch-cmd-out.t index a55d778901..c873d76565 100755 --- a/t/rose-task-run/13-app-arch-cmd-out.t +++ b/t/rose-task-run/13-app-arch-cmd-out.t @@ -23,7 +23,7 @@ #------------------------------------------------------------------------------- -tests 2 +tests 4 #------------------------------------------------------------------------------- # Run the suite, and wait for it to complete export CYLC_CONF_PATH= @@ -31,8 +31,17 @@ export ROSE_CONF_PATH= mkdir -p "${HOME}/cylc-run" SUITE_RUN_DIR="$(mktemp -d "${HOME}/cylc-run/rose-test-battery.XXXXXX")" NAME="$(basename "${SUITE_RUN_DIR}")" -rose suite-run -q -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host=localhost -- --no-detach --debug +run_pass "${TEST_KEY_BASE}-install" \ + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + --flow-name="${NAME}" \ + --no-run-name +run_pass "${TEST_KEY_BASE}-run" \ + cylc run \ + "${NAME}" \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- FOO_UUID=$(<"${SUITE_RUN_DIR}/foo-uuid") grep -F "${FOO_UUID}" "${SUITE_RUN_DIR}/log/job/1/archive/NN/job.out" \ diff --git a/t/rose-task-run/14-app-prune-remove.t b/t/rose-task-run/14-app-prune-remove.t index cedbce1118..f367b21391 100755 --- a/t/rose-task-run/14-app-prune-remove.t +++ b/t/rose-task-run/14-app-prune-remove.t @@ -23,19 +23,27 @@ #------------------------------------------------------------------------------- JOB_HOST=$(rose config --default= 't' 'job-host') -tests 10 +tests 11 #------------------------------------------------------------------------------- # Run the suite. export CYLC_CONF_PATH= export ROSE_CONF_PATH= -TEST_KEY=$TEST_KEY_BASE mkdir -p $HOME/cylc-run SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') NAME=$(basename $SUITE_RUN_DIR) +TEST_KEY="${TEST_KEY_BASE}-install" run_pass "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost \ - -- --no-detach --debug + cylc install \ + -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ + --flow-name="$NAME" \ + --no-run-name +TEST_KEY="${TEST_KEY_BASE}-run" +run_pass "$TEST_KEY" \ + cylc run \ + "${NAME}" \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- TEST_KEY=$TEST_KEY_BASE-log run_fail "$TEST_KEY.1" ls -d $HOME/cylc-run/$NAME/log/job/20100101T0000Z diff --git a/t/rose-task-run/15-app-prune-extglob.t b/t/rose-task-run/15-app-prune-extglob.t index dbdeb0153c..7b0e29829c 100755 --- a/t/rose-task-run/15-app-prune-extglob.t +++ b/t/rose-task-run/15-app-prune-extglob.t @@ -21,17 +21,26 @@ #------------------------------------------------------------------------------- . $(dirname $0)/test_header -tests 2 +tests 3 export ROSE_CONF_PATH= mkdir -p "${HOME}/cylc-run" SUITE_RUN_DIR=$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX') NAME="$(basename "${SUITE_RUN_DIR}")" #------------------------------------------------------------------------------- -TEST_KEY="${TEST_KEY_BASE}" +TEST_KEY="${TEST_KEY_BASE}-install" run_pass "${TEST_KEY}" \ - rose suite-run -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host='localhost' -- --no-detach --debug + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + --flow-name="${NAME}" \ + --no-run-name +TEST_KEY="${TEST_KEY_BASE}-run" +run_pass "${TEST_KEY}" \ + cylc run \ + "${NAME}" \ + --host='localhost' \ + --no-detach \ + --debug TEST_KEY="${TEST_KEY_BASE}-prune.log" sed 's/[0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0-9]*/YYYY-MM-DDTHHMM/g'\ "${SUITE_RUN_DIR}/prune.log" > stamp-removed.log diff --git a/t/rose-task-run/16-app-prune-point.t b/t/rose-task-run/16-app-prune-point.t index d2a3e6d381..ddf7551c91 100755 --- a/t/rose-task-run/16-app-prune-point.t +++ b/t/rose-task-run/16-app-prune-point.t @@ -23,17 +23,27 @@ #------------------------------------------------------------------------------- . $(dirname $0)/test_header -tests 2 +tests 3 export ROSE_CONF_PATH= mkdir -p "${HOME}/cylc-run" SUITE_RUN_DIR=$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX') NAME="$(basename "${SUITE_RUN_DIR}")" #------------------------------------------------------------------------------- -TEST_KEY="${TEST_KEY_BASE}" +TEST_KEY="${TEST_KEY_BASE}-install" run_pass "${TEST_KEY}" \ - rose suite-run -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host='localhost' --debug -- --no-detach --debug + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + --flow-name="${NAME}" \ + --no-run-name + +TEST_KEY="${TEST_KEY_BASE}-run" +run_pass "${TEST_KEY}" \ + cylc run \ + "${NAME}" \ + --host='localhost' \ + --debug \ + --no-detach TEST_KEY="${TEST_KEY_BASE}-prune.log" sed 's/[0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0-9]*/YYYY-MM-DDTHHMM/g'\ diff --git a/t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t b/t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t index cc17f56aca..2c9bb9228a 100755 --- a/t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t +++ b/t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t @@ -22,17 +22,26 @@ #------------------------------------------------------------------------------- . $(dirname $0)/test_header -tests 2 +tests 3 export ROSE_CONF_PATH= mkdir -p "${HOME}/cylc-run" SUITE_RUN_DIR=$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX') NAME="$(basename "${SUITE_RUN_DIR}")" #------------------------------------------------------------------------------- -TEST_KEY="${TEST_KEY_BASE}" +TEST_KEY="${TEST_KEY_BASE}-install" run_pass "${TEST_KEY}" \ - rose suite-run -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host='localhost' --debug -- --no-detach --debug + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + --flow-name="${NAME}" \ + --no-run-name +TEST_KEY="${TEST_KEY_BASE}-run" +run_pass "${TEST_KEY}" \ + cylc run \ + "${NAME}" \ + --host='localhost' \ + --debug \ + --no-detach TEST_KEY="${TEST_KEY_BASE}-prune.log" sed 's/[0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0:9]*/YYYY-MM-DDTHHMM/g'\ diff --git a/t/rose-task-run/18-app-fcm-make-ctx-name.t b/t/rose-task-run/18-app-fcm-make-ctx-name.t index 6261591a02..108af357de 100755 --- a/t/rose-task-run/18-app-fcm-make-ctx-name.t +++ b/t/rose-task-run/18-app-fcm-make-ctx-name.t @@ -45,17 +45,25 @@ if [[ -z "${JOB_HOST}" ]]; then skip_all '"[t]job-host" not defined or not available' fi #------------------------------------------------------------------------------- -tests 1 +tests 3 export ROSE_CONF_PATH= mkdir -p "${HOME}/cylc-run" #------------------------------------------------------------------------------- SUITE_RUN_DIR="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" NAME="$(basename "${SUITE_RUN_DIR}")" -timeout 120 rose suite-run -v -v --debug \ - -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host='localhost' \ - -D "[jinja2:suite.rc]HOST=\"${JOB_HOST}\"" \ - -- --no-detach --debug 1>'/dev/null' 2>&1 +run_pass "${TEST_KEY_BASE}-install" \ + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + --flow-name="${NAME}" \ + --no-run-name \ + -S "HOST='${JOB_HOST}'" +run_pass "${TEST_KEY_BASE}-run" \ + timeout 120 \ + cylc run \ + "${NAME}" \ + --host='localhost' \ + --no-detach \ + --debug #------------------------------------------------------------------------------- ssh -n -oBatchMode=yes "${JOB_HOST}" \ cat "cylc-run/${NAME}/share/hello.txt" >'hello.txt' diff --git a/t/rose-task-run/20-app-fcm-make-dest.t b/t/rose-task-run/20-app-fcm-make-dest.t index 45c388ec11..07048d58ef 100755 --- a/t/rose-task-run/20-app-fcm-make-dest.t +++ b/t/rose-task-run/20-app-fcm-make-dest.t @@ -48,16 +48,26 @@ else GREET= fi #------------------------------------------------------------------------------- -tests 1 +tests 3 export ROSE_CONF_PATH= mkdir -p "${HOME}/cylc-run" #------------------------------------------------------------------------------- SUITE_RUN_DIR="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" NAME="$(basename "${SUITE_RUN_DIR}")" -timeout 120 rose suite-run -q --debug \ - -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host='localhost' \ - -S "HOST=\"${JOB_HOST}\"" -S "GREET=\"${GREET}\"" -- --no-detach --debug +run_pass "${TEST_KEY_BASE}-install" \ + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + --flow-name="${NAME}" \ + --no-run-name + -S "HOST=\"${JOB_HOST}\"" \ + -S "GREET=\"${GREET}\"" +run_pass "${TEST_KEY_BASE}-run" \ + timeout 120 \ + cylc run \ + "${NAME}" \ + --no-detach \ + --debug \ + --host='localhost' #------------------------------------------------------------------------------- JOB_HOST_HOME=$(ssh -n -oBatchMode=yes "${JOB_HOST}" 'echo "${HOME}"' | tail -1) ssh -n -oBatchMode=yes "${JOB_HOST}" \ diff --git a/t/rose-task-run/24-app-fcm-make-fast.t b/t/rose-task-run/24-app-fcm-make-fast.t index f408e92205..67b9ba8ce2 100755 --- a/t/rose-task-run/24-app-fcm-make-fast.t +++ b/t/rose-task-run/24-app-fcm-make-fast.t @@ -32,7 +32,7 @@ if ! gfortran --version 1>/dev/null 2>&1; then skip_all '"gfortran" unavailable' fi #------------------------------------------------------------------------------- -tests 5 +tests 7 export CYLC_CONF_PATH= export ROSE_CONF_PATH= mkdir -p "${HOME}/cylc-run" @@ -41,10 +41,19 @@ MTIME_OF_FAST_BEFORE=$(stat '-c%y' 'fast') #------------------------------------------------------------------------------- SUITE_RUN_DIR="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" NAME="$(basename "${SUITE_RUN_DIR}")" -timeout 120 rose suite-run -q --debug \ - -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host='localhost' -S "FAST_DEST_ROOT=\"${PWD}/fast\"" \ - -- --no-detach --debug +run_pass "${TEST_KEY_BASE}-install" \ + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + --flow-name="${NAME}" \ + -S "FAST_DEST_ROOT='${PWD}/fast'" \ + --no-run-name +run_pass "${TEST_KEY_BASE}-run" \ + timeout 120 \ + cylc run \ + "${NAME}" \ + --host='localhost' \ + --no-detach \ + --debug #------------------------------------------------------------------------------- # Permission modes of make directory should be the same as a normal directory mkdir "${SUITE_RUN_DIR}/share/hello-make-perm-mode-test" diff --git a/t/rose-task-run/25-app-fcm-make-new-mode.t b/t/rose-task-run/25-app-fcm-make-new-mode.t index f1741bd6fd..af50814558 100755 --- a/t/rose-task-run/25-app-fcm-make-new-mode.t +++ b/t/rose-task-run/25-app-fcm-make-new-mode.t @@ -32,7 +32,7 @@ if ! gfortran --version 1>/dev/null 2>&1; then skip_all '"gfortran" unavailable' fi #------------------------------------------------------------------------------- -tests 3 +tests 5 export ROSE_CONF_PATH= mkdir -p "${HOME}/cylc-run" @@ -42,10 +42,18 @@ NAME="$(basename "${SUITE_RUN_DIR}")" # Add some garbage before running the suite mkdir -p "${SUITE_RUN_DIR}/share/hello-make/junk2" touch "${SUITE_RUN_DIR}/share/hello-make/junk1" - -timeout 120 rose suite-run -q --debug \ - -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host='localhost' -- --no-detach --debug +run_pass "${TEST_KEY_BASE}-install" \ + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + --flow-name="${NAME}" \ + --no-run-name +run_pass "${TEST_KEY_BASE}-run" \ + timeout 120 \ + cylc run \ + "${NAME}" \ + --host='localhost' \ + --no-detach \ + --debug #------------------------------------------------------------------------------- file_cmp "${TEST_KEY_BASE}" "${SUITE_RUN_DIR}/share/hello.txt" <<__TXT__ ${SUITE_RUN_DIR}/share/hello-make/build/bin/hello diff --git a/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t b/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t index cbc313d77d..503322521d 100755 --- a/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t +++ b/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t @@ -43,7 +43,7 @@ if [[ -z "${JOB_HOST}" ]]; then skip_all '"[t]job-host" not defined or not available' fi #------------------------------------------------------------------------------- -tests 2 +tests 4 export ROSE_CONF_PATH= mkdir -p "${HOME}/cylc-run" @@ -56,10 +56,19 @@ ssh -n -oBatchMode=yes "${JOB_HOST}" \ ssh -n -oBatchMode=yes "${JOB_HOST}" \ "touch 'cylc-run/${NAME}/share/hello-make/junk1'" -timeout 120 rose suite-run -q --debug \ - -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host='localhost' \ - -D "[jinja2:suite.rc]HOST=\"${JOB_HOST}\"" -- --no-detach --debug +run_pass "${TEST_KEY_BASE}-install" \ + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + --flow-name="${NAME}" \ + --no-run-name +run_pass "${TEST_KEY_BASE}-run" \ + timeout 120 \ + cylc run \ + "${NAME}" \ + --host='localhost' \ + -S "HOST='${JOB_HOST}'" \ + --no-detach \ + --debug #------------------------------------------------------------------------------- ssh -n -oBatchMode=yes "${JOB_HOST}" \ cat "cylc-run/${NAME}/share/hello.txt" >'hello.txt' diff --git a/t/rose-task-run/28-env-path-run-path.t b/t/rose-task-run/28-env-path-run-path.t index e01fbf8771..acbd9d7c8e 100755 --- a/t/rose-task-run/28-env-path-run-path.t +++ b/t/rose-task-run/28-env-path-run-path.t @@ -21,17 +21,26 @@ #------------------------------------------------------------------------------- . "$(dirname "$0")/test_header" -tests 2 +tests 3 export ROSE_CONF_PATH= mkdir -p "${HOME}/cylc-run" SUITE_RUN_DIR=$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX') NAME="$(basename "${SUITE_RUN_DIR}")" #------------------------------------------------------------------------------- -TEST_KEY="${TEST_KEY_BASE}" +TEST_KEY="${TEST_KEY_BASE}-install" run_pass "${TEST_KEY}" \ - rose suite-run -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host='localhost' -- --no-detach --debug + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + --flow-name="${NAME}" \ + --no-run-name +TEST_KEY="${TEST_KEY_BASE}-run" +run_pass "${TEST_KEY}" \ + cylc run \ + "${NAME}" \ + --host='localhost' \ + --no-detach \ + --debug TEST_KEY="${TEST_KEY_BASE}-hello.txt" file_cmp "${TEST_KEY}" "${SUITE_RUN_DIR}/hello.txt" <<__HELLO__ Hello Earth! diff --git a/t/rose-task-run/29-app-prune-extglob-remote.t b/t/rose-task-run/29-app-prune-extglob-remote.t index 8a3fab9583..27d5b37f20 100755 --- a/t/rose-task-run/29-app-prune-extglob-remote.t +++ b/t/rose-task-run/29-app-prune-extglob-remote.t @@ -21,7 +21,6 @@ #------------------------------------------------------------------------------- . $(dirname $0)/test_header - JOB_HOST_OPT= if [[ "${TEST_KEY_BASE}" == *-remote ]]; then JOB_HOST=$(rose config --default= 't' 'job-host') @@ -35,18 +34,27 @@ if [[ "${TEST_KEY_BASE}" == *-remote ]]; then JOB_HOST_OPT="-S JOB_HOST=\"${JOB_HOST}\"" fi -tests 2 +tests 3 export ROSE_CONF_PATH= mkdir -p "${HOME}/cylc-run" SUITE_RUN_DIR=$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX') NAME="$(basename "${SUITE_RUN_DIR}")" #------------------------------------------------------------------------------- -TEST_KEY="${TEST_KEY_BASE}" +TEST_KEY="${TEST_KEY_BASE}-install" run_pass "${TEST_KEY}" \ - rose suite-run -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host='localhost' ${JOB_HOST_OPT} -- --no-detach --debug - + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + --flow-name="${NAME}" \ + --no-run-name +TEST_KEY="${TEST_KEY_BASE}-run" +run_pass "${TEST_KEY}" \ + cylc run \ + "${NAME}" \ + --host='localhost' \ + ${JOB_HOST_OPT} \ + --no-detach \ + --debug TEST_KEY="${TEST_KEY_BASE}-prune.log" sed 's/[0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0-9]*/YYYY-MM-DDTHHMM/g'\ "${SUITE_RUN_DIR}/prune.log" > stamp-removed.log diff --git a/t/rose-task-run/30-app-arch-opt-source.t b/t/rose-task-run/30-app-arch-opt-source.t index 4b4d225308..9a9cfd140d 100755 --- a/t/rose-task-run/30-app-arch-opt-source.t +++ b/t/rose-task-run/30-app-arch-opt-source.t @@ -23,7 +23,7 @@ #------------------------------------------------------------------------------- -tests 6 +tests 8 #------------------------------------------------------------------------------- # Run the suite, and wait for it to complete export CYLC_CONF_PATH= @@ -31,8 +31,17 @@ export ROSE_CONF_PATH= mkdir -p "${HOME}/cylc-run" SUITE_RUN_DIR="$(mktemp -d "${HOME}/cylc-run/rose-test-battery.XXXXXX")" NAME="$(basename "${SUITE_RUN_DIR}")" -rose suite-run -q -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host=localhost -- --no-detach --debug +run_pass "${TEST_KEY_BASE}-install" \ + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + --flow-name="${NAME}" \ + --no-run-name +run_pass "${TEST_KEY_BASE}-run" \ + cylc run \ + "${NAME}" \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- TEST_KEY="${TEST_KEY_BASE}-job.status" file_grep "${TEST_KEY}-archive1-01" \ diff --git a/t/rose-task-run/31-app-bunch.t b/t/rose-task-run/31-app-bunch.t index b5abd89c78..2829354973 100755 --- a/t/rose-task-run/31-app-bunch.t +++ b/t/rose-task-run/31-app-bunch.t @@ -22,7 +22,7 @@ . $(dirname $0)/test_header #------------------------------------------------------------------------------- -tests 74 +tests 75 #------------------------------------------------------------------------------- # Define some constant patterns FAIL_PATTERN="\[FAIL\] [0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0:9]*" @@ -33,13 +33,22 @@ SKIP_PATTERN="\[SKIP\] [0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0:9]*" #------------------------------------------------------------------------------- # Run the suite, and wait for it to complete export ROSE_CONF_PATH= -TEST_KEY=$TEST_KEY_BASE mkdir -p $HOME/cylc-run SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') NAME=$(basename $SUITE_RUN_DIR) +TEST_KEY="${TEST_KEY_BASE}-install" run_pass "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost -- --no-detach --debug + cylc install \ + -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ + --flow-name="${NAME}" \ + --no-run-name +TEST_KEY="${TEST_KEY_BASE}-run" +run_pass "$TEST_KEY" \ + cylc run \ + "${NAME}" \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- CYCLE=20100101T0000Z LOG_DIR="$SUITE_RUN_DIR/log/job/$CYCLE" diff --git a/t/rose-task-run/32-app-arch-compressed.t b/t/rose-task-run/32-app-arch-compressed.t index d3cba2254c..a026922cfc 100755 --- a/t/rose-task-run/32-app-arch-compressed.t +++ b/t/rose-task-run/32-app-arch-compressed.t @@ -23,7 +23,7 @@ #------------------------------------------------------------------------------- -tests 2 +tests 4 #------------------------------------------------------------------------------- # Run the suite, and wait for it to complete export CYLC_CONF_PATH= @@ -31,8 +31,17 @@ export ROSE_CONF_PATH= mkdir -p "${HOME}/cylc-run" SUITE_RUN_DIR="$(mktemp -d "${HOME}/cylc-run/rose-test-battery.XXXXXX")" NAME="$(basename "${SUITE_RUN_DIR}")" -rose suite-run -q -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host=localhost -- --no-detach --debug +run_pass "${TEST_KEY_BASE}-install" \ + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + --flow-name="${NAME}" \ + --no-run-name +run_pass "${TEST_KEY_BASE}-run" \ + cylc run \ + "${NAME}" \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- TEST_KEY="${TEST_KEY_BASE}-job.status" file_grep "${TEST_KEY}-archive-01" \ diff --git a/t/rose-task-run/33-app-prune-cycle-format.t b/t/rose-task-run/33-app-prune-cycle-format.t index 84c8022ace..f1bdd07148 100755 --- a/t/rose-task-run/33-app-prune-cycle-format.t +++ b/t/rose-task-run/33-app-prune-cycle-format.t @@ -22,17 +22,26 @@ #------------------------------------------------------------------------------- . $(dirname $0)/test_header -tests 2 +tests 3 export ROSE_CONF_PATH= mkdir -p "${HOME}/cylc-run" SUITE_RUN_DIR=$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX') NAME="$(basename "${SUITE_RUN_DIR}")" #------------------------------------------------------------------------------- -TEST_KEY="${TEST_KEY_BASE}" +TEST_KEY="${TEST_KEY_BASE}-install" run_pass "${TEST_KEY}" \ - rose suite-run -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host='localhost' --debug -- --no-detach --debug + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + --flow-name="${NAME}" \ + --no-run-name +TEST_KEY="${TEST_KEY_BASE}-run" +run_pass "${TEST_KEY}" \ + cylc run \ + "${NAME}" \ + --host='localhost' \ + --debug \ + --no-detach TEST_KEY="${TEST_KEY_BASE}-prune.log" sed 's/[0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0-9]*/YYYY-MM-DDTHHMM/g'\ diff --git a/t/rose-task-run/34-app-prune-hosts-sharing-fs.t b/t/rose-task-run/34-app-prune-hosts-sharing-fs.t index 03455a1992..5f6f41ee20 100755 --- a/t/rose-task-run/34-app-prune-hosts-sharing-fs.t +++ b/t/rose-task-run/34-app-prune-hosts-sharing-fs.t @@ -31,19 +31,28 @@ if [[ -z "${JOB_HOST_1}" || -z "${JOB_HOST_2}" ]]; then skip_all '"[t]job-hosts-sharing-fs" not defined with 2 host names' fi -tests 4 +tests 5 export ROSE_CONF_PATH= mkdir -p "${HOME}/cylc-run" SUITE_RUN_DIR=$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX') NAME="$(basename "${SUITE_RUN_DIR}")" #------------------------------------------------------------------------------- -TEST_KEY="${TEST_KEY_BASE}" +TEST_KEY="${TEST_KEY_BASE}-install" run_pass "${TEST_KEY}" \ - rose suite-run -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host='localhost' --debug \ - -S "JOB_HOST_1=\"${JOB_HOST_1}\"" -S "JOB_HOST_2=\"${JOB_HOST_2}\"" \ - -- --no-detach --debug + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + --flow-name="${NAME}" \ + --no-run-name \ + -S "JOB_HOST_1=\"${JOB_HOST_1}\"" \ + -S "JOB_HOST_2=\"${JOB_HOST_2}\"" +TEST_KEY="${TEST_KEY_BASE}-run" +run_pass "${TEST_KEY}" \ + cylc run \ + "${NAME}" \ + --host='localhost' \ + --debug \ + --no-detach TEST_KEY="${TEST_KEY_BASE}-prune.log" grep \ diff --git a/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t b/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t index a65e29e797..0462ec0b54 100755 --- a/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t +++ b/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t @@ -31,19 +31,28 @@ if [[ -z "${JOB_HOST_1}" || -z "${JOB_HOST_2}" ]]; then skip_all '"[t]job-hosts-sharing-fs" not defined with 2 host names' fi -tests 4 +tests 5 export ROSE_CONF_PATH= mkdir -p "${HOME}/cylc-run" SUITE_RUN_DIR=$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX') NAME="$(basename "${SUITE_RUN_DIR}")" #------------------------------------------------------------------------------- -TEST_KEY="${TEST_KEY_BASE}" +TEST_KEY="${TEST_KEY_BASE}-install" run_pass "${TEST_KEY}" \ - rose suite-run -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host='localhost' --debug \ - -S "JOB_HOST_1=\"${JOB_HOST_1}\"" -S "JOB_HOST_2=\"${JOB_HOST_2}\"" \ - -- --no-detach --debug + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + --flow-name="${NAME}" \ + --no-run-name \ + -S "JOB_HOST_1=\"${JOB_HOST_1}\"" \ + -S "JOB_HOST_2=\"${JOB_HOST_2}\"" +TEST_KEY="${TEST_KEY_BASE}-run" +run_pass "${TEST_KEY}" \ + cylc run \ + "${NAME}" \ + --host='localhost' \ + --debug \ + --no-detach TEST_KEY="${TEST_KEY_BASE}-prune.log" grep \ diff --git a/t/rose-task-run/36-app-arch-interrupted.t b/t/rose-task-run/36-app-arch-interrupted.t index c436bcce26..0bc8c5453e 100755 --- a/t/rose-task-run/36-app-arch-interrupted.t +++ b/t/rose-task-run/36-app-arch-interrupted.t @@ -23,7 +23,7 @@ #------------------------------------------------------------------------------- -tests 3 +tests 5 #------------------------------------------------------------------------------- # Run the suite, and wait for it to complete export CYLC_CONF_PATH= @@ -31,8 +31,17 @@ export ROSE_CONF_PATH= mkdir -p "${HOME}/cylc-run" SUITE_RUN_DIR="$(mktemp -d "${HOME}/cylc-run/rose-test-battery.XXXXXX")" NAME="$(basename "${SUITE_RUN_DIR}")" -rose suite-run -q -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host=localhost -- --no-detach --debug +run_pass "${TEST_KEY_BASE}-install" \ + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + --flow-name="${NAME}" \ + --no-run-name +run_pass "${TEST_KEY_BASE}-run" \ + cylc run \ + "${NAME}" \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- TEST_KEY="${TEST_KEY_BASE}-job.status" file_grep "${TEST_KEY}-archive-01" \ diff --git a/t/rose-task-run/37-app-bunch-rm-old.t b/t/rose-task-run/37-app-bunch-rm-old.t index 291fdfecce..216fbf8a61 100755 --- a/t/rose-task-run/37-app-bunch-rm-old.t +++ b/t/rose-task-run/37-app-bunch-rm-old.t @@ -22,17 +22,26 @@ . $(dirname $0)/test_header #------------------------------------------------------------------------------- -tests 14 +tests 15 #------------------------------------------------------------------------------- # Run the suite, and wait for it to complete export ROSE_CONF_PATH= -TEST_KEY=$TEST_KEY_BASE mkdir -p $HOME/cylc-run SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') NAME=$(basename $SUITE_RUN_DIR) +TEST_KEY="${TEST_KEY_BASE}-install" run_pass "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost -- --no-detach --debug + cylc install \ + -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ + --flow-name=$NAME \ + --no-run-name +TEST_KEY="${TEST_KEY_BASE}-run" +run_pass "$TEST_KEY" \ + cylc run \ + "${NAME}" \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- CYCLE=20100101T0000Z LOG_DIR="$SUITE_RUN_DIR/log/job/$CYCLE" @@ -61,9 +70,15 @@ for ARGVALUE in 0 1 2 3; do done #------------------------------------------------------------------------------- # Run suite a second time +# TODO: replace with cylc run --re-run / cylc clean +rm -rf "${HOME}/cylc-run/${NAME}/log" +rm -rf "${HOME}/cylc-run/${NAME}/.serivce/db" run_pass "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost -- --no-detach --debug + cylc run \ + "${NAME}" \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- # Confirm launching set of commands TEST_KEY_PREFIX=launch-ok-2nd-run diff --git a/t/rose-task-run/38-app-bunch-counts.t b/t/rose-task-run/38-app-bunch-counts.t index 5518371635..87276a278a 100755 --- a/t/rose-task-run/38-app-bunch-counts.t +++ b/t/rose-task-run/38-app-bunch-counts.t @@ -22,17 +22,26 @@ . $(dirname $0)/test_header #------------------------------------------------------------------------------- -tests 16 +tests 17 #------------------------------------------------------------------------------- # Run the suite, and wait for it to complete export ROSE_CONF_PATH= -TEST_KEY=$TEST_KEY_BASE mkdir -p $HOME/cylc-run SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') NAME=$(basename $SUITE_RUN_DIR) +TEST_KEY="${TEST_KEY_BASE}-install" run_pass "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost -- --no-detach --debug + cylc install \ + -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ + --flow-name="$NAME" \ + --no-run-name \ +TEST_KEY="${TEST_KEY_BASE}-run" +run_pass "$TEST_KEY" \ + cylc run \ + "${NAME}" \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- CYCLE=1 LOG_DIR="$SUITE_RUN_DIR/log/job/$CYCLE" diff --git a/t/rose-task-run/39-app-prune-cycle-host.t b/t/rose-task-run/39-app-prune-cycle-host.t index 2065629d42..4d1ea050c2 100755 --- a/t/rose-task-run/39-app-prune-cycle-host.t +++ b/t/rose-task-run/39-app-prune-cycle-host.t @@ -37,12 +37,21 @@ mkdir -p "${HOME}/cylc-run" RUND="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" NAME="$(basename "${RUND}")" #------------------------------------------------------------------------------- -TEST_KEY="${TEST_KEY_BASE}" +TEST_KEY="${TEST_KEY_BASE}-install" run_pass "${TEST_KEY}" \ - rose suite-run -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host='localhost' --debug \ - -S "JOB_HOST_1=\"${JOB_HOST_1}\"" -S "JOB_HOST_2=\"${JOB_HOST_2}\"" \ - -- --no-detach --debug + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + --flow-name="${NAME}" \ + --no-run-name \ + -S "JOB_HOST_1=\"${JOB_HOST_1}\"" \ + -S "JOB_HOST_2=\"${JOB_HOST_2}\"" +TEST_KEY="${TEST_KEY_BASE}-run" +run_pass "${TEST_KEY}" \ + cylc run \ + "${NAME}" \ + --host='localhost' \ + --debug \ + --no-detach TEST_KEY="${TEST_KEY_BASE}-prune.log" run_pass "${TEST_KEY}-ssh-1" \ diff --git a/t/rose-task-run/40-app-arch-duplicate.t b/t/rose-task-run/40-app-arch-duplicate.t index 16d09c1a35..eb2b574c8a 100644 --- a/t/rose-task-run/40-app-arch-duplicate.t +++ b/t/rose-task-run/40-app-arch-duplicate.t @@ -23,7 +23,7 @@ #------------------------------------------------------------------------------- -tests 1 +tests 3 #------------------------------------------------------------------------------- # Run the suite, and wait for it to complete export CYLC_CONF_PATH= @@ -31,8 +31,17 @@ export ROSE_CONF_PATH= mkdir -p "${HOME}/cylc-run" SUITE_RUN_DIR="$(mktemp -d "${HOME}/cylc-run/rose-test-battery.XXXXXX")" NAME="$(basename "${SUITE_RUN_DIR}")" -rose suite-run -q -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" --name="${NAME}" \ - --host=localhost -- --no-detach +run_pass "${TEST_KEY_BASE}-install" \ + cylc install \ + -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ + --flow-name="${NAME}" \ + --no-run-name +run_pass "${TEST_KEY_BASE}-run" \ + cylc run \ + "${NAME}" \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- TEST_KEY="${TEST_KEY_BASE}" file_grep "${TEST_KEY}" 'duplicate archive target: "foo"' \ diff --git a/t/rose-task-run/41-app-bunch-default-command.t b/t/rose-task-run/41-app-bunch-default-command.t index 89cc2556b9..f702e6edd6 100755 --- a/t/rose-task-run/41-app-bunch-default-command.t +++ b/t/rose-task-run/41-app-bunch-default-command.t @@ -22,17 +22,26 @@ . $(dirname $0)/test_header #------------------------------------------------------------------------------- -tests 26 +tests 27 #------------------------------------------------------------------------------- # Run the suite, and wait for it to complete export ROSE_CONF_PATH= -TEST_KEY=$TEST_KEY_BASE mkdir -p $HOME/cylc-run SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') NAME=$(basename $SUITE_RUN_DIR) +TEST_KEY="${TEST_KEY_BASE}-install" run_pass "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost -- --no-detach --debug + cylc install \ + -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ + --flow-name="$NAME" \ + --no-run-name +TEST_KEY="${TEST_KEY_BASE}-run" +run_pass "$TEST_KEY" \ + cylc run \ + "${NAME}" \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- CYCLE=20100101T0000Z LOG_DIR="$SUITE_RUN_DIR/log/job/$CYCLE" @@ -65,9 +74,14 @@ for TASK in buncher_default buncher_import; do done #------------------------------------------------------------------------------- # Run suite a second time +# TODO: replace with cylc run --rerun / cylc clean +rm -rf "${HOME}/cylc-run/${NAME}/log" +rm -rf "${HOME}/cylc-run/${NAME}/.service/db" run_pass "$TEST_KEY" \ - rose suite-run -C $TEST_SOURCE_DIR/$TEST_KEY_BASE --name=$NAME \ - --host=localhost -- --no-detach --debug + cylc run "${NAME}" \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- # Testing successful rerun #------------------------------------------------------------------------------- diff --git a/t/rosie-graph/test_header_extra b/t/rosie-graph/test_header_extra deleted file mode 120000 index 338abfca27..0000000000 --- a/t/rosie-graph/test_header_extra +++ /dev/null @@ -1 +0,0 @@ -../rose-metadata-graph/test_header_extra \ No newline at end of file diff --git a/t/rosie-graph/test_header_extra b/t/rosie-graph/test_header_extra new file mode 100644 index 0000000000..ff23178ec7 --- /dev/null +++ b/t/rosie-graph/test_header_extra @@ -0,0 +1,58 @@ +#!/bin/bash +#------------------------------------------------------------------------------- +# Copyright (C) 2012-2019 British Crown (Met Office) & Contributors. +# +# This file is part of Rose, a framework for meteorological suites. +# +# Rose 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. +# +# Rose 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 Rose. If not, see . +#------------------------------------------------------------------------------- +# Provides extra functions for rose metadata-graph testing. +#------------------------------------------------------------------------------- + +function filter_graphviz() { + FILTER_TMP_FILE=$(mktemp) + cat >"$FILTER_TMP_FILE" + # Sort and filter out non-essential properties from the output file. + # Get rid of line-broken newer graphviz output (replace ",\n"). + # Sort the file. + python3 << __PYTHON__ +import re +filename = '$FILTER_TMP_FILE' +f = open(filename, 'r') +text = f.read() +f.close() +text = text.replace(",\n", ", ") +text = re.sub("\s+\[", " [", text) +lines = text.splitlines() +f = open(filename, 'w') +for line in lines: + if '[' not in line: + if line.startswith("\t") and line.strip() != "];": + f.write(line.lstrip() + '\n') + continue + props = dict([_.strip().split('=', 1) for _ in + re.split(', ', + line.split('[', 1)[1].replace('];', ''))]) + new_prop_string = '' + for key in ['arrowhead', 'color', 'label', 'rankdir', 'shape', 'style']: + if key in props: + new_prop_string += key + '=' + props[key] + ', ' + new_prop_string = new_prop_string.rstrip().rstrip(',') + new_line = line.lstrip().split('[')[0] + '[' + new_prop_string + '\n' + if new_line.strip() != 'graph [': + f.write(new_line) +__PYTHON__ + LANG=C sort "$FILTER_TMP_FILE" + rm "$FILTER_TMP_FILE" +} From a65141cf25a2c79ae2dc5a24848dc1c65cf9f318 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Thu, 31 Dec 2020 13:13:35 +0000 Subject: [PATCH 15/78] eslint: update and package --- .eslintrc.js | 33 + .eslintrc.json | 11 - .gitignore | 1 + package.json | 20 + sphinx/_static/js/versioning.js | 101 +-- t/syntax/00-eslint.t | 34 - t/syntax/test_header | 1 - yarn.lock | 1200 +++++++++++++++++++++++++++++++ 8 files changed, 1308 insertions(+), 93 deletions(-) create mode 100644 .eslintrc.js delete mode 100644 .eslintrc.json create mode 100644 package.json delete mode 100644 t/syntax/00-eslint.t delete mode 120000 t/syntax/test_header create mode 100644 yarn.lock diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000000..9079951da7 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,33 @@ +/** + * Copyright (C) NIWA & British Crown (Met Office) & Contributors. + * + * 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 . + */ + +module.exports = { + root: true, + env: { + browser: true + }, + extends: [ + 'standard', + 'eslint:recommended' + ], + rules: { + 'operator-linebreak': ['error', 'before'] + }, + globals: { + '$': 'readonly' + } +} diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index ac5ee369f7..0000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "eslint:recommended", - "parserOptions": { - "ecmaVersion": 6 - }, - "env": { - "browser": true, - "es6": true, - "jquery": true - } -} diff --git a/.gitignore b/.gitignore index 901998cab0..a01daf43ab 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ doc venv metomi_rose.egg-info dist +node_modules # coverage .coverage diff --git a/package.json b/package.json new file mode 100644 index 0000000000..f8a8f7fc2d --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "metomi-rose", + "private": true, + "scripts": { + "lint": "eslint -c .eslintrc.js sphinx/" + }, + "dependencies": { + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^4.2.1", + "eslint-plugin-standard": "^5.0.0" + }, + "devDependencies": { + "eslint": "^7.14.0", + "eslint-config-standard": "^14.1.1" + }, + "bugs": { + "url": "https://github.com/metomi/rose/issues" + } +} diff --git a/sphinx/_static/js/versioning.js b/sphinx/_static/js/versioning.js index 12b2b61d77..672d58fa49 100644 --- a/sphinx/_static/js/versioning.js +++ b/sphinx/_static/js/versioning.js @@ -1,52 +1,59 @@ /* global root_dir current_builder current_page_name current_version */ +/* eslint camelcase: "off" */ // global vars not in camel case -$(document).ready(function() { - $.ajax({ - 'async': false, - 'type': 'GET', - 'url': root_dir + 'versions.json', - dataType: 'json', - success: function (versions) { - // the DOM element to append version and format selectors to - var ele = $('#version-selector'); +$(document).ready(function () { + $.ajax({ + async: false, + type: 'GET', + url: root_dir + 'versions.json', + dataType: 'json', + success: function (versions) { + // the DOM element to append version and format selectors to + var ele = $('#version-selector') - // construct version selector - var ver = ele.append($('
')); - $(ver).append($('
').append('Versions')); - for (let version of Object.keys(versions).sort().reverse()) { - $(ver).append($('
') - .append($('') - .attr({'href': root_dir + version + '/' + - current_builder + '/' + - current_page_name + '.html'}) - .append(version) - ) - ); - } + // construct version selector + var ver = ele.append($('
')) + $(ver).append($('
').append('Versions')) + for (const version of Object.keys(versions).sort().reverse()) { + $(ver).append($('
') + .append($('') + .attr({ + href: root_dir + + version + '/' + + current_builder + + '/' + + current_page_name + '.html' + }) + .append(version) + ) + ) + } - // construct format selector - var bui = ele.append($('
')); - $(bui).append($('
').append('Formats')); - var href; - for (let builder_for_version of versions[current_version].sort()) { - href = root_dir + current_version + '/' + builder_for_version + - '/'; - if (['html', 'slides'].indexOf(builder_for_version) >= 0) { - // format has compatible document structure - href += current_page_name + '.html'; - } else { - // structure different, link to the index.html page - href += 'index.html'; - } - - - $(bui).append($('
') - .append($('') - .attr({'href': href}) - .append(builder_for_version) - ) - ); - } + // construct format selector + var bui = ele.append($('
')) + $(bui).append($('
').append('Formats')) + var href + for (const builderForVersion of versions[current_version].sort()) { + href = root_dir + + current_version + + '/' + + builderForVersion + + '/' + if (['html', 'slides'].indexOf(builderForVersion) >= 0) { + // format has compatible document structure + href += current_page_name + '.html' + } else { + // structure different, link to the index.html page + href += 'index.html' } - }); -}); + + $(bui).append($('
') + .append($('') + .attr({ href: href }) + .append(builderForVersion) + ) + ) + } + } + }) +}) diff --git a/t/syntax/00-eslint.t b/t/syntax/00-eslint.t deleted file mode 100644 index 4a4ff2d46f..0000000000 --- a/t/syntax/00-eslint.t +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -. "$(dirname "$0")/test_header" - -if ! eslint --version 1>'/dev/null' 2>&1; then - skip_all '"eslint" command not available' -fi - -tests 3 - -TEST_KEY="${TEST_KEY_BASE}-docs" -run_pass "${TEST_KEY}" eslint --env browser --env jquery --env es6 \ - --parser-options=ecmaVersion:6 sphinx/_static/js -file_cmp "${TEST_KEY}.out" "${TEST_KEY}.out" <'/dev/null' -file_cmp "${TEST_KEY}.err" "${TEST_KEY}.err" <'/dev/null' - -exit diff --git a/t/syntax/test_header b/t/syntax/test_header deleted file mode 120000 index 90bd5a36f9..0000000000 --- a/t/syntax/test_header +++ /dev/null @@ -1 +0,0 @@ -../lib/bash/test_header \ No newline at end of file diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000000..fb938cf97a --- /dev/null +++ b/yarn.lock @@ -0,0 +1,1200 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/helper-validator-identifier@^7.10.4": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" + integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== + +"@babel/highlight@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" + integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@eslint/eslintrc@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.2.2.tgz#d01fc791e2fc33e88a29d6f3dc7e93d0cd784b76" + integrity sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + lodash "^4.17.19" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + +acorn-jsx@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" + integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== + +acorn@^7.4.0: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +ajv@^6.10.0, ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +array-includes@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.2.tgz#a8db03e0b88c8c6aeddc49cb132f9bcab4ebf9c8" + integrity sha512-w2GspexNQpx+PutG3QpT437/BenZBj0M/MZGn5mzv/MofYqo0xmRHzn4lFsoDlWJ+THYsGJmFlW68WlDFx7VRw== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + get-intrinsic "^1.0.1" + is-string "^1.0.5" + +array.prototype.flat@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz#6ef638b43312bd401b4c6199fdec7e2dc9e9a123" + integrity sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +call-bind@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.0.tgz#24127054bb3f9bdcb4b1fb82418186072f77b8ce" + integrity sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.0" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= + +cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^4.0.1, debug@^4.1.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + +deep-is@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +doctrine@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +enquirer@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + +error-ex@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.18.0-next.1: + version "1.18.0-next.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" + integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.2" + is-negative-zero "^2.0.0" + is-regex "^1.1.1" + object-inspect "^1.8.0" + object-keys "^1.1.1" + object.assign "^4.1.1" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +eslint-config-standard@^14.1.1: + version "14.1.1" + resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz#830a8e44e7aef7de67464979ad06b406026c56ea" + integrity sha512-Z9B+VR+JIXRxz21udPTL9HpFMyoMUEeX1G251EQ6e05WD9aPVtVBn09XUmZ259wCMlCDmYDSZG62Hhm+ZTJcUg== + +eslint-import-resolver-node@^0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" + integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== + dependencies: + debug "^2.6.9" + resolve "^1.13.1" + +eslint-module-utils@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" + integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== + dependencies: + debug "^2.6.9" + pkg-dir "^2.0.0" + +eslint-plugin-es@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893" + integrity sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ== + dependencies: + eslint-utils "^2.0.0" + regexpp "^3.0.0" + +eslint-plugin-import@^2.22.1: + version "2.22.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz#0896c7e6a0cf44109a2d97b95903c2bb689d7702" + integrity sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw== + dependencies: + array-includes "^3.1.1" + array.prototype.flat "^1.2.3" + contains-path "^0.1.0" + debug "^2.6.9" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.4" + eslint-module-utils "^2.6.0" + has "^1.0.3" + minimatch "^3.0.4" + object.values "^1.1.1" + read-pkg-up "^2.0.0" + resolve "^1.17.0" + tsconfig-paths "^3.9.0" + +eslint-plugin-node@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d" + integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g== + dependencies: + eslint-plugin-es "^3.0.0" + eslint-utils "^2.0.0" + ignore "^5.1.1" + minimatch "^3.0.4" + resolve "^1.10.1" + semver "^6.1.0" + +eslint-plugin-promise@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a" + integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw== + +eslint-plugin-standard@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-5.0.0.tgz#c43f6925d669f177db46f095ea30be95476b1ee4" + integrity sha512-eSIXPc9wBM4BrniMzJRBm2uoVuXz2EPa+NXPk2+itrVt+r5SbKFERx/IgrK/HmfjddyKVz2f+j+7gBRvu19xLg== + +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^2.0.0, eslint-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint-visitor-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" + integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== + +eslint@^7.14.0: + version "7.16.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.16.0.tgz#a761605bf9a7b32d24bb7cde59aeb0fd76f06092" + integrity sha512-iVWPS785RuDA4dWuhhgXTNrGxHHK3a8HLSMBgbbU59ruJDubUraXN8N5rn7kb8tG6sjg74eE0RA3YWT51eusEw== + dependencies: + "@babel/code-frame" "^7.0.0" + "@eslint/eslintrc" "^0.2.2" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.0.1" + doctrine "^3.0.0" + enquirer "^2.3.5" + eslint-scope "^5.1.1" + eslint-utils "^2.1.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.1" + esquery "^1.2.0" + esutils "^2.0.2" + file-entry-cache "^6.0.0" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash "^4.17.19" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + progress "^2.0.0" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^6.0.4" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== + dependencies: + acorn "^7.4.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^1.3.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" + integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" + integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +file-entry-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.0.tgz#7921a89c391c6d93efec2169ac6bf300c527ea0a" + integrity sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA== + dependencies: + flat-cache "^3.0.4" + +find-up@^2.0.0, find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + dependencies: + locate-path "^2.0.0" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.0.tgz#a5d06b4a8b01e3a63771daa5cb7a1903e2e57067" + integrity sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +get-intrinsic@^1.0.0, get-intrinsic@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.0.2.tgz#6820da226e50b24894e08859469dc68361545d49" + integrity sha512-aeX0vrFm21ILl3+JpFFRNe9aUvp6VFZb2/CTbgLb8j75kOhvoNYjt9d8KA/tJG4gSo8nzEDedRl0h7vDmBYRVg== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + +glob-parent@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" + integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + dependencies: + is-glob "^4.0.1" + +glob@^7.1.3: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== + dependencies: + type-fest "^0.8.1" + +graceful-fs@^4.1.2: + version "4.2.4" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" + integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hosted-git-info@^2.1.4: + version "2.8.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" + integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignore@^5.1.1: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + +import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-callable@^1.1.4, is-callable@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" + integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== + +is-core-module@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" + integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== + dependencies: + has "^1.0.3" + +is-date-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.0, is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-negative-zero@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== + +is-regex@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" + integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== + dependencies: + has-symbols "^1.0.1" + +is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + +is-symbol@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + +isarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +lodash@^4.17.19, lodash@^4.17.20: + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" + integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +normalize-package-data@^2.3.2: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +object-inspect@^1.8.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" + integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== + +object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +object.values@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.2.tgz#7a2015e06fcb0f546bd652486ce8583a4731c731" + integrity sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + has "^1.0.3" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + dependencies: + p-limit "^1.1.0" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= + dependencies: + error-ex "^1.2.0" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= + dependencies: + pify "^2.0.0" + +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= + +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= + dependencies: + find-up "^2.1.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + +regexpp@^3.0.0, regexpp@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" + integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve@^1.10.0, resolve@^1.10.1, resolve@^1.13.1, resolve@^1.17.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" + integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== + dependencies: + is-core-module "^2.1.0" + path-parse "^1.0.6" + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +"semver@2 || 3 || 4 || 5": + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.1.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.2.1: + version "7.3.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" + integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + dependencies: + lru-cache "^6.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.7" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65" + integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +string-width@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" + integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + +string.prototype.trimend@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz#a22bd53cca5c7cf44d7c9d5c732118873d6cd18b" + integrity sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + +string.prototype.trimstart@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz#9b4cb590e123bb36564401d59824298de50fd5aa" + integrity sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +table@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/table/-/table-6.0.4.tgz#c523dd182177e926c723eb20e1b341238188aa0d" + integrity sha512-sBT4xRLdALd+NFBvwOz8bw4b15htyythha+q+DVZqy2RS08PPC8O2sZFgJYEY7bJvbCFKccs+WIZ/cd+xxTWCw== + dependencies: + ajv "^6.12.4" + lodash "^4.17.20" + slice-ansi "^4.0.0" + string-width "^4.2.0" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +tsconfig-paths@^3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" + integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +uri-js@^4.2.2: + version "4.4.0" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602" + integrity sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g== + dependencies: + punycode "^2.1.0" + +v8-compile-cache@^2.0.3: + version "2.2.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132" + integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q== + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== From 3155b6738d2779f924248e9d273c465dbf1db3e4 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Mon, 4 Jan 2021 11:17:58 +0000 Subject: [PATCH 16/78] host-select: convert to psutil implementation --- bin/rose-host-select-client | 52 +++++++++ metomi/rose/host_select.py | 168 +++++++++++++++++++----------- metomi/rose/host_select_client.py | 53 ++++++++++ setup.py | 9 +- t/rose-host-select/01-timeout.t | 4 +- t/rose-host-select/02-localhost.t | 6 +- 6 files changed, 222 insertions(+), 70 deletions(-) create mode 100644 bin/rose-host-select-client create mode 100644 metomi/rose/host_select_client.py diff --git a/bin/rose-host-select-client b/bin/rose-host-select-client new file mode 100644 index 0000000000..c5f97eade0 --- /dev/null +++ b/bin/rose-host-select-client @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +#------------------------------------------------------------------------------- +# Copyright (C) 2012-2019 British Crown (Met Office) & Contributors. +# +# This file is part of Rose, a framework for meteorological suites. +# +# Rose 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. +# +# Rose 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 Rose. If not, see . +#------------------------------------------------------------------------------- +# NAME +# rose host-select-client +# +# SYNOPSIS +# rose host-select-client +# +# DESCRIPTION +# Internal command for obtaining information about hosts using the +# Python psutil module. +# +# The names of psutil methods are passed to stdin along with any arguments +# as a JSON list. +# +# Examples: +# # return the virtual memory and cpu usage +# [["virual_memory"], ["cpu_percent"]] +# +# # return disk usage for the filesystem mounted at "/" +# [["disk_usage", "/"]] +# +# The input json should be preceded by \n**start**\n +# and followed by \n**end**\n to avoid issues with user profile scripts +# and stdin deadlock. +# +# $ rose host-select-client <<'__HERE__' +# > **start** +# > [["virtual_memory"]] +# > **end** +# > __HERE__ +# [{"total": 17179869184, "available": 6276612096, "percent": 63.5, ...}] +# +#------------------------------------------------------------------------------- +exec python3 -m metomi.rose.host_select_client "$@" diff --git a/metomi/rose/host_select.py b/metomi/rose/host_select.py index f8fbb646ed..325657a9fb 100644 --- a/metomi/rose/host_select.py +++ b/metomi/rose/host_select.py @@ -16,20 +16,29 @@ # ----------------------------------------------------------------------------- """Select an available host machine by load or by random.""" +from collections import namedtuple +from functools import lru_cache +import json import os from random import choice, random, shuffle -from metomi.rose.opt_parse import RoseOptionParser -from metomi.rose.popen import RosePopener -from metomi.rose.reporter import Reporter, Event -from metomi.rose.resource import ResourceLocator import shlex import signal from socket import ( - getaddrinfo, gethostbyname_ex, gethostname, getfqdn, error as SocketError) + getaddrinfo, + gethostbyname_ex, + gethostname, + getfqdn, + error as SocketError +) import sys from time import sleep, time import traceback +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.popen import RosePopener +from metomi.rose.reporter import Reporter, Event +from metomi.rose.resource import ResourceLocator + class NoHostError(Exception): @@ -348,40 +357,61 @@ def select(self, names=None, rank_method=None, thresholds=None, # ssh to each host to return its score(s). host_proc_dict = {} for host_name in sorted(host_names): + # build host-select-client command command = [] if not self.is_local_host(host_name): command_args = [] command_args.append(host_name) command = self.popen.get_cmd("ssh", *command_args) - command.append("bash") - stdin = rank_conf.get_command() + command.extend(["rose", "host-select-client"]) + + # build list of metrics to obtain for each host + metrics = rank_conf.get_command() for threshold_conf in threshold_confs: - stdin += threshold_conf.get_command() - stdin += "exit\n" - proc = self.popen.run_bg(*command, stdin=stdin, - preexec_fn=os.setpgrp) + for metric in threshold_conf.get_command(): + if metric not in metrics: + metrics.append(metric) + + # convert metrics list to JSON stdin + stdin = ( + '\n***start**\n' + + json.dumps(metrics) + + '\n**end**\n' + ) + + # fire off host-select-client processes + proc = self.popen.run_bg( + *command, + stdin=stdin, + preexec_fn=os.setpgrp + ) proc.stdin.write(stdin.encode('UTF-8')) proc.stdin.flush() - host_proc_dict[host_name] = proc + host_proc_dict[host_name] = (proc, metrics) # Retrieve score for each host name host_score_list = [] time0 = time() while host_proc_dict: sleep(self.SSH_CMD_POLL_DELAY) - for host_name, proc in list(host_proc_dict.items()): + for host_name, (proc, metrics) in list(host_proc_dict.items()): if proc.poll() is None: score = None elif proc.wait(): + stdout, stderr = (f.decode() for f in proc.communicate()) self.handle_event(DeadHostEvent(host_name)) host_proc_dict.pop(host_name) else: - out = proc.communicate()[0] + out = proc.communicate()[0].decode() + out = _deserialise(metrics, json.loads(out.strip())) + host_proc_dict.pop(host_name) for threshold_conf in threshold_confs: try: - is_bad = threshold_conf.check_threshold(out) - score = threshold_conf.command_out_parser(out) + score = threshold_conf.command_out_parser( + out, metrics + ) + is_bad = threshold_conf.check_threshold(score) except ValueError: is_bad = True score = None @@ -391,7 +421,7 @@ def select(self, names=None, rank_method=None, thresholds=None, break else: try: - score = rank_conf.command_out_parser(out) + score = rank_conf.command_out_parser(out, metrics) host_score_list.append((host_name, score)) except ValueError: score = None @@ -401,7 +431,7 @@ def select(self, names=None, rank_method=None, thresholds=None, break # Report timed out hosts - for host_name, proc in sorted(host_proc_dict.items()): + for host_name, (proc, _) in sorted(host_proc_dict.items()): self.handle_event(TimedOutHostEvent(host_name)) os.killpg(proc.pid, signal.SIGTERM) proc.wait() @@ -416,6 +446,40 @@ def select(self, names=None, rank_method=None, thresholds=None, __call__ = select +@lru_cache() +def _tuple_factory(name, params): + """Wrapper to namedtuple which caches results to prevent duplicates.""" + return namedtuple(name, params) + + +def _deserialise(metrics, data): + """Convert dict to named tuples. + + Examples: + >>> _deserialise( + ... [ + ... ['foo', 'bar'], + ... ['baz'] + ... ], + ... [ + ... {'a': 1, 'b': 2, 'c': 3}, + ... [1, 2, 3] + ... ] + ... ) + [foo(a=1, b=2, c=3), [1, 2, 3]] + + """ + for index, (metric, datum) in enumerate(zip(metrics, data)): + if isinstance(datum, dict): + data[index] = _tuple_factory( + metric[0], + tuple(datum.keys()) + )( + *datum.values() + ) + return data + + class ScorerConf: """Wrap a threshold/ranking scorer + extra configuration.""" @@ -430,15 +494,22 @@ def get_command(self): """Return a shell command to get the info for scoring a host.""" return self.scorer.get_command(self.method_arg) - def check_threshold(self, out): + def check_threshold(self, score): """Parse command output. Return True if threshold not met.""" - score = self.command_out_parser(out) return (float(score) * self.scorer.SIGN > float(self.value) * self.scorer.SIGN) - def command_out_parser(self, out): + def command_out_parser(self, out, metrics): """Parse command output to return a numeric score.""" - return self.scorer.command_out_parser(out, self.method_arg) + results = self.get_results(out, metrics) + return self.scorer.command_out_parser(results, self.method_arg) + + def get_results(self, out, metrics): + """Return list of results for the requested metrics.""" + return [ + out[metrics.index(metric)] + for metric in self.scorer.get_command(self.method_arg) + ] class RandomScorer: @@ -451,17 +522,13 @@ class RandomScorer: ARG = None KEY = "random" - CMD = "true\n" + CMD = ['cpu_count'] # fetch an arbitrary metric CMD_IS_FORMAT = False SIGN = 1 # Positive def get_command(self, method_arg=None): """Return a shell command to get the info for scoring a host.""" - - if self.CMD_IS_FORMAT: - return self.CMD % {"method_arg": method_arg} - else: - return self.CMD + return list(self.CMD) @classmethod def command_out_parser(cls, out, method_arg=None): @@ -471,7 +538,6 @@ def command_out_parser(cls, out, method_arg=None): returned by the command run on the remote host. Otherwise, this method returns a random number. """ - return random() @@ -481,24 +547,13 @@ class LoadScorer(RandomScorer): ARG = "15" KEY = "load" - INDEX_OF = {"1": 1, "5": 2, "15": 3} - CMD = ("echo nproc=$((cat /proc/cpuinfo || lscfg) | grep -ic processor)\n" - "echo uptime=$(uptime)\n") + VALUES = ('1', '5', '15') # 1, 5, 15 min average values + CMD = [["getloadavg"], ["cpu_count"]] def command_out_parser(self, out, method_arg=None): - if method_arg is None: - method_arg = self.ARG - nprocs = None - load = None - for line in out.splitlines(): - if line.startswith(b"nproc="): - nprocs = line.split(b"=", 1)[1] - elif line.startswith(b"uptime="): - idx = self.INDEX_OF[method_arg] - load = line.rsplit(None, 3)[idx].rstrip(b",") - if load is None or not nprocs: - return None - return float(load) / float(nprocs) + load = out[0][self.VALUES.index(method_arg or self.ARG)] + cpus = out[1] + return load / cpus class MemoryScorer(RandomScorer): @@ -506,34 +561,25 @@ class MemoryScorer(RandomScorer): """Score host by amount of free memory""" KEY = "mem" - CMD = """echo mem=$(free -m | sed '3!d; s/^.* \\. +# ----------------------------------------------------------------------------- +import json +import sys + +import psutil + + +def main(): + # read metrics from stdin + line = True + metrics = '' + while True: + line = sys.stdin.readline().strip() + if '**start**' in line: + continue + if '**end**' in line: + break + metrics += f'\n{line}' + metrics = json.loads(metrics) + + # extract metrics using psutil + ret = [ + getattr(psutil, key[0])(*key[1:]) + for key in metrics + ] + + # serialise results + for ind, item in enumerate(ret): + if hasattr(item, '_asdict'): + ret[ind] = item._asdict() + + # output results as json + print(json.dumps(ret)) + + +if __name__ == '__main__': + main() diff --git a/setup.py b/setup.py index 34b1175aa6..73bf043235 100644 --- a/setup.py +++ b/setup.py @@ -49,13 +49,14 @@ def find_version(*file_paths): INSTALL_REQUIRES = [ - "jinja2>=2.10.1", "aiofiles", - "tornado", - "sqlalchemy", + "jinja2>=2.10.1", + "ldap3", "metomi-isodatetime", "requests", - "ldap3", + "sqlalchemy", + "tornado", + 'psutil>=5.6.0', ] EXTRAS_REQUIRE = { 'docs': [ diff --git a/t/rose-host-select/01-timeout.t b/t/rose-host-select/01-timeout.t index 465be75009..ddc8ecf06b 100755 --- a/t/rose-host-select/01-timeout.t +++ b/t/rose-host-select/01-timeout.t @@ -38,8 +38,8 @@ __ERR__ cut -f2- mock-ssh.out | LANG=C sort >mock-ssh.out.sorted # N.B. Tab between 1 and sleepy? file_cmp "$TEST_KEY.mock-ssh.out" mock-ssh.out.sorted <<'__OUT__' -sleepy1 bash -sleepy2 bash +sleepy1 rose host-select-client +sleepy2 rose host-select-client __OUT__ # Make sure there is no lingering processes run_fail "$TEST_KEY.ps" ps $(cut -f1 mock-ssh.out) diff --git a/t/rose-host-select/02-localhost.t b/t/rose-host-select/02-localhost.t index 3637b3ff15..aa83dc6285 100755 --- a/t/rose-host-select/02-localhost.t +++ b/t/rose-host-select/02-localhost.t @@ -27,7 +27,7 @@ MORE_HOST=$(rose config --default= 't' 'job-host-with-share') export ROSE_CONF_PATH= LOCAL_HOSTS='localhost 127.0.0.1' -for CMD in 'hostname -s' 'hostname' 'hostname --fqdn' 'hostname -I'; do +for CMD in 'hostname -s' 'hostname' 'hostname -f' 'hostname -I'; do if LOCAL_HOST=$(eval "$CMD"); then if ssh -n -q -oBatchMode=yes "${LOCAL_HOST}" true 1>'/dev/null' 2>&1 then @@ -47,9 +47,9 @@ run_pass "${TEST_KEY_BASE}" rose 'host-select' -v -v ${HOSTS} # 1 bash command sed 's/[0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0-9]*/YYYY-MM-DDTHHMM/g'\ "${TEST_KEY_BASE}.out" > stamp-removed.log -grep -F "[INFO] YYYY-MM-DDTHHMM bash" "stamp-removed.log" >"${TEST_KEY_BASE}.out.1" +grep -F "[INFO] YYYY-MM-DDTHHMM rose" "stamp-removed.log" >"${TEST_KEY_BASE}.out.1" file_cmp "${TEST_KEY_BASE}.out.1" "${TEST_KEY_BASE}.out.1" <<'__OUT__' -[INFO] YYYY-MM-DDTHHMM bash <<'__STDIN__' +[INFO] YYYY-MM-DDTHHMM rose host-select-client <<'__STDIN__' __OUT__ # 0 ssh LOCAL_HOST command From 11a0262feb702d28947432ecd4a9175d9f31e15b Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Mon, 4 Jan 2021 17:29:16 +0000 Subject: [PATCH 17/78] suite.rc => flow.cylc --- bin/rose-config-edit | 2 +- bin/rose-task-env | 2 +- .../demo_meta/{suite.rc => flow.cylc} | 0 etc/rose.conf.example | 2 +- .../{suite.rc => flow.cylc} | 0 etc/tutorial/cylc-forecasting-suite/.validate | 2 +- .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../queues-tutorial/{suite.rc => flow.cylc} | 0 .../retries-tutorial/{suite.rc => flow.cylc} | 0 etc/tutorial/rose-stem/.validate | 4 +-- .../rose-stem/{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../runtime-tutorial/{suite.rc => flow.cylc} | 0 sphinx/api/built-in/rose_arch.rst | 2 +- sphinx/api/configuration/suite.rst | 10 +++---- sphinx/cheat-sheet.rst | 4 +-- sphinx/ext/rose_domain.py | 2 +- sphinx/glossary.rst | 16 +++++----- .../tutorial/cylc/furthertopics/broadcast.rst | 2 +- .../furthertopics/clock-triggered-tasks.rst | 6 ++-- .../cylc/furthertopics/family-triggers.rst | 6 ++-- .../cylc/furthertopics/inheritance.rst | 4 +-- sphinx/tutorial/cylc/furthertopics/queues.rst | 2 +- .../tutorial/cylc/furthertopics/retries.rst | 2 +- .../cylc/furthertopics/suicide-triggers.rst | 2 +- .../configuration-consolidation/index.rst | 8 ++--- .../configuration-consolidation/jinja2.rst | 8 ++--- .../parameters.rst | 2 +- sphinx/tutorial/cylc/runtime/introduction.rst | 16 +++++----- .../cylc/runtime/runtime-configuration.rst | 8 ++--- .../cylc/scheduling/datetime-cycling.rst | 4 +-- sphinx/tutorial/cylc/scheduling/graphing.rst | 24 +++++++-------- .../cylc/scheduling/integer-cycling.rst | 8 ++--- sphinx/tutorial/rose/applications.rst | 8 ++--- sphinx/tutorial/rose/configurations.rst | 6 ++-- .../rose/furthertopics/command-keys.rst | 4 +-- .../tutorial/rose/furthertopics/polling.rst | 4 +-- .../tutorial/rose/furthertopics/rose-arch.rst | 2 +- .../rose/furthertopics/rose-bunch.rst | 4 +-- .../rose/furthertopics/rose-stem-tutorial.rst | 12 ++++---- sphinx/tutorial/rose/suites.rst | 30 +++++++++---------- sphinx/tutorial/rose/summary.rst | 10 +++---- t/docs/02-tutorial-suites.t | 2 +- .../00-run-basic/{suite.rc => flow.cylc} | 0 .../01-run-basic-v1/{suite.rc => flow.cylc} | 0 .../00-non-cycle/{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../02-360day-cycling/{suite.rc => flow.cylc} | 0 .../00-run-basic/{suite.rc => flow.cylc} | 0 .../01-run-basic-iso/{suite.rc => flow.cylc} | 0 .../02-env-basic/{suite.rc => flow.cylc} | 0 .../03-env-basic-iso/{suite.rc => flow.cylc} | 0 .../04-run-path-empty/{suite.rc => flow.cylc} | 0 .../06-app-prune-iso/{suite.rc => flow.cylc} | 0 .../07-app-arch/{suite.rc => flow.cylc} | 0 .../08-app-fcm-make/{suite.rc => flow.cylc} | 0 .../09-app-prune-work/{suite.rc => flow.cylc} | 0 .../10-specify-cycle/{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../31-app-bunch/{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 .../{suite.rc => flow.cylc} | 0 t/rosie-id/00-basic.t | 2 +- 87 files changed, 116 insertions(+), 116 deletions(-) rename demo/rose-config-edit/demo_meta/{suite.rc => flow.cylc} (100%) rename etc/tutorial/consolidation-tutorial/{suite.rc => flow.cylc} (100%) rename etc/tutorial/cylc-forecasting-suite/{suite.rc => flow.cylc} (100%) rename etc/tutorial/inheritance-tutorial/{suite.rc => flow.cylc} (100%) rename etc/tutorial/queues-tutorial/{suite.rc => flow.cylc} (100%) rename etc/tutorial/retries-tutorial/{suite.rc => flow.cylc} (100%) rename etc/tutorial/rose-stem/rose-stem/{suite.rc => flow.cylc} (100%) rename etc/tutorial/rose-suite-tutorial/{suite.rc => flow.cylc} (100%) rename etc/tutorial/rose-weather-forecasting-suite/{suite.rc => flow.cylc} (100%) rename etc/tutorial/runtime-introduction/{suite.rc => flow.cylc} (100%) rename etc/tutorial/runtime-tutorial/{suite.rc => flow.cylc} (100%) rename t/rose-ana/00-run-basic/{suite.rc => flow.cylc} (100%) rename t/rose-ana/01-run-basic-v1/{suite.rc => flow.cylc} (100%) rename t/rose-task-env/00-non-cycle/{suite.rc => flow.cylc} (100%) rename t/rose-task-env/01-integer-cycling/{suite.rc => flow.cylc} (100%) rename t/rose-task-env/02-360day-cycling/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/00-run-basic/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/01-run-basic-iso/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/02-env-basic/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/03-env-basic-iso/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/04-run-path-empty/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/06-app-prune-iso/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/07-app-arch/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/08-app-fcm-make/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/09-app-prune-work/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/10-specify-cycle/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/11-specify-cycle-iso/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/12-app-prune-integer/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/13-app-arch-cmd-out/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/14-app-prune-remove/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/15-app-prune-extglob/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/16-app-prune-point/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/17-app-prune-glob-as-cycle-fmt/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/18-app-fcm-make-ctx-name/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/20-app-fcm-make-dest/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/24-app-fcm-make-fast/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/25-app-fcm-make-new-mode/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/26-app-fcm-make-new-mode-with-cont/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/28-env-path-run-path/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/30-app-arch-opt-source/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/31-app-bunch/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/32-app-arch-compressed/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/33-app-prune-cycle-format/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/34-app-prune-hosts-sharing-fs/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/36-app-arch-interrupted/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/37-app-bunch-rm-old/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/38-app-bunch-counts/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/39-app-prune-cycle-host/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/40-app-arch-duplicate/{suite.rc => flow.cylc} (100%) rename t/rose-task-run/41-app-bunch-default-command/{suite.rc => flow.cylc} (100%) diff --git a/bin/rose-config-edit b/bin/rose-config-edit index 8ffe233cca..0e5e78483f 100755 --- a/bin/rose-config-edit +++ b/bin/rose-config-edit @@ -34,7 +34,7 @@ # A path to either: # # 1. a directory containing a suite with a file named -# `suite.rc` and a directory called `app` containing +# `flow.cylc` and a directory called `app` containing # subdirectories with files named `rose-app.conf`, # in the format specified in the Rose pages. # 2. a directory containing a single 'application' - a file named diff --git a/bin/rose-task-env b/bin/rose-task-env index 0f6fa472ec..ad53feebfe 100755 --- a/bin/rose-task-env +++ b/bin/rose-task-env @@ -136,7 +136,7 @@ # # USAGE IN SUITES # rose `task-env` can be used to make environment variables available to a -# suite by defining its `suite.rc` `env-script` option as +# suite by defining its `flow.cylc` `env-script` option as # `env-script = eval $(rose task-env)`. # #------------------------------------------------------------------------------- diff --git a/demo/rose-config-edit/demo_meta/suite.rc b/demo/rose-config-edit/demo_meta/flow.cylc similarity index 100% rename from demo/rose-config-edit/demo_meta/suite.rc rename to demo/rose-config-edit/demo_meta/flow.cylc diff --git a/etc/rose.conf.example b/etc/rose.conf.example index d9089eb6c4..6cc8437477 100644 --- a/etc/rose.conf.example +++ b/etc/rose.conf.example @@ -170,7 +170,7 @@ timeout=FLOAT [rose-stem] # Automatic options. These are added as if the user added them with # ``--define-suite`` on the command line and can be accessed as Jinja2 -# variables in the ``suite.rc`` file. E.g ``automatic-options=TEA=earl_grey`` +# variables in the ``flow.cylc`` file. E.g ``automatic-options=TEA=earl_grey`` # would set the Jinja2 variable ``TEA`` to be ``earl_grey``. automatic-options=VARIABLE=VALUE diff --git a/etc/tutorial/consolidation-tutorial/suite.rc b/etc/tutorial/consolidation-tutorial/flow.cylc similarity index 100% rename from etc/tutorial/consolidation-tutorial/suite.rc rename to etc/tutorial/consolidation-tutorial/flow.cylc diff --git a/etc/tutorial/cylc-forecasting-suite/.validate b/etc/tutorial/cylc-forecasting-suite/.validate index 9d13a3b35d..b0335dac54 100644 --- a/etc/tutorial/cylc-forecasting-suite/.validate +++ b/etc/tutorial/cylc-forecasting-suite/.validate @@ -1,3 +1,3 @@ rose tutorial "$(basename $TUT_DIR)" "${CYLC_RUN_DIR}/${REG}" -sed -i '1s;^;[cylc]\n abort if any task fails = True\n;' "${CYLC_RUN_DIR}/${REG}/suite.rc" +sed -i '1s;^;[cylc]\n abort if any task fails = True\n;' "${CYLC_RUN_DIR}/${REG}/flow.cylc" cylc run --no-detach "${REG}" 2>&1 diff --git a/etc/tutorial/cylc-forecasting-suite/suite.rc b/etc/tutorial/cylc-forecasting-suite/flow.cylc similarity index 100% rename from etc/tutorial/cylc-forecasting-suite/suite.rc rename to etc/tutorial/cylc-forecasting-suite/flow.cylc diff --git a/etc/tutorial/inheritance-tutorial/suite.rc b/etc/tutorial/inheritance-tutorial/flow.cylc similarity index 100% rename from etc/tutorial/inheritance-tutorial/suite.rc rename to etc/tutorial/inheritance-tutorial/flow.cylc diff --git a/etc/tutorial/queues-tutorial/suite.rc b/etc/tutorial/queues-tutorial/flow.cylc similarity index 100% rename from etc/tutorial/queues-tutorial/suite.rc rename to etc/tutorial/queues-tutorial/flow.cylc diff --git a/etc/tutorial/retries-tutorial/suite.rc b/etc/tutorial/retries-tutorial/flow.cylc similarity index 100% rename from etc/tutorial/retries-tutorial/suite.rc rename to etc/tutorial/retries-tutorial/flow.cylc diff --git a/etc/tutorial/rose-stem/.validate b/etc/tutorial/rose-stem/.validate index 736af32a69..6c9aff2276 100644 --- a/etc/tutorial/rose-stem/.validate +++ b/etc/tutorial/rose-stem/.validate @@ -1,4 +1,4 @@ mkdir "${CYLC_RUN_DIR}/${REG}" -echo -e '#!Jinja2\n{% set RUN_NAMES=["command_spaceship"] %}' > "${CYLC_RUN_DIR}/${REG}/suite.rc" -cat "$TUT_DIR/rose-stem/suite.rc" >> "${CYLC_RUN_DIR}/${REG}/suite.rc" +echo -e '#!Jinja2\n{% set RUN_NAMES=["command_spaceship"] %}' > "${CYLC_RUN_DIR}/${REG}/flow.cylc" +cat "$TUT_DIR/rose-stem/flow.cylc" >> "${CYLC_RUN_DIR}/${REG}/suite.rc" cylc validate "${CYLC_RUN_DIR}/${REG}" -s "SOURCE_SPACESHIP=foo" diff --git a/etc/tutorial/rose-stem/rose-stem/suite.rc b/etc/tutorial/rose-stem/rose-stem/flow.cylc similarity index 100% rename from etc/tutorial/rose-stem/rose-stem/suite.rc rename to etc/tutorial/rose-stem/rose-stem/flow.cylc diff --git a/etc/tutorial/rose-suite-tutorial/suite.rc b/etc/tutorial/rose-suite-tutorial/flow.cylc similarity index 100% rename from etc/tutorial/rose-suite-tutorial/suite.rc rename to etc/tutorial/rose-suite-tutorial/flow.cylc diff --git a/etc/tutorial/rose-weather-forecasting-suite/suite.rc b/etc/tutorial/rose-weather-forecasting-suite/flow.cylc similarity index 100% rename from etc/tutorial/rose-weather-forecasting-suite/suite.rc rename to etc/tutorial/rose-weather-forecasting-suite/flow.cylc diff --git a/etc/tutorial/runtime-introduction/suite.rc b/etc/tutorial/runtime-introduction/flow.cylc similarity index 100% rename from etc/tutorial/runtime-introduction/suite.rc rename to etc/tutorial/runtime-introduction/flow.cylc diff --git a/etc/tutorial/runtime-tutorial/suite.rc b/etc/tutorial/runtime-tutorial/flow.cylc similarity index 100% rename from etc/tutorial/runtime-tutorial/suite.rc rename to etc/tutorial/runtime-tutorial/flow.cylc diff --git a/sphinx/api/built-in/rose_arch.rst b/sphinx/api/built-in/rose_arch.rst index 1e8e1a3f93..d82bba7d23 100644 --- a/sphinx/api/built-in/rose_arch.rst +++ b/sphinx/api/built-in/rose_arch.rst @@ -42,7 +42,7 @@ In automatic selection mode, this built-in application will be invoked automatically if a task has a name that starts with ``rose_arch*``. This means that you can use Rose Arch with something like the example below -in your ``suite.rc``: +in your ``flow.cylc``: .. code-block:: cylc diff --git a/sphinx/api/configuration/suite.rst b/sphinx/api/configuration/suite.rst index ac781d620c..f2d9a3a153 100644 --- a/sphinx/api/configuration/suite.rst +++ b/sphinx/api/configuration/suite.rst @@ -8,7 +8,7 @@ Suite Configuration The configuration and functionality of a suite will usually be covered by the use of `Cylc`_. In which case, most of the suite configuration will live -in the Cylc ``suite.rc`` file. Otherwise, a suite is just a directory of +in the Cylc ``flow.cylc`` file. Otherwise, a suite is just a directory of files. A suite directory may contain the following: @@ -76,20 +76,20 @@ A suite directory may contain the following: .. rose:conf:: KEY=VALUE Define a `Jinja2`_ variable ``KEY`` with the value ``VALUE`` for use - in the ``suite.rc`` file. + in the ``flow.cylc`` file. The assignment will be inserted after the ``#!jinja2`` line of the - installed ``suite.rc`` file. + installed ``flow.cylc`` file. .. rose:conf:: empy:suite.rc .. rose:conf:: KEY=VALUE Define a `EmPy`_ variable ``KEY`` with the value ``VALUE`` for use - in the ``suite.rc`` file. + in the ``flow.cylc`` file. The assignment will be inserted after the ``#!empy`` line of the - installed ``suite.rc`` file. + installed ``flow.cylc`` file. .. rose:conf:: [file:NAME] diff --git a/sphinx/cheat-sheet.rst b/sphinx/cheat-sheet.rst index 73a122ddba..d8fab0c72a 100644 --- a/sphinx/cheat-sheet.rst +++ b/sphinx/cheat-sheet.rst @@ -173,8 +173,8 @@ Visualise A Suite's :term:`graph` cylc graph -View A Suite's ``suite.rc`` Configuration -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +View A Suite's ``flow.cylc`` Configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. list-table:: :class: grid-table diff --git a/sphinx/ext/rose_domain.py b/sphinx/ext/rose_domain.py index b35374caf1..d48b957078 100644 --- a/sphinx/ext/rose_domain.py +++ b/sphinx/ext/rose_domain.py @@ -73,7 +73,7 @@ .. rose:conf:: jinja2:suite.rc A section for specifying Jinja2 settings for use in the - ``suite.rc`` file. + ``flow.cylc`` file. Note that one ommits the square brackets for config sections. If :rose:conf: contains other :rose:conf:'s then it is implicitly a diff --git a/sphinx/glossary.rst b/sphinx/glossary.rst index 40dc1777ee..8a0e2c17b9 100644 --- a/sphinx/glossary.rst +++ b/sphinx/glossary.rst @@ -9,7 +9,7 @@ Glossary suite Cylc suite - A Cylc suite is a directory containing a ``suite.rc`` file which contains + A Cylc suite is a directory containing a ``flow.cylc`` file which contains :term:`graphing` representing a workflow. See also: @@ -20,7 +20,7 @@ Glossary suite directory The suite directory contains all of the configuration for a suite (e.g. - the ``suite.rc`` file and for Rose suites the :rose:file:`rose-suite.conf` + the ``flow.cylc`` file and for Rose suites the :rose:file:`rose-suite.conf` file). This is the directory which is registered using ``cylc reg`` or, for Rose @@ -83,7 +83,7 @@ Glossary graph string A graph string is a collection of dependencies which are placed under a - ``graph`` section in the ``suite.rc`` file. E.G: + ``graph`` section in the ``flow.cylc`` file. E.G: .. code-block:: cylc-graph @@ -213,7 +213,7 @@ Glossary been configured to use integer cycling. When a suite uses integer cycling integer :term:`recurrences ` may be used in the :term:`graph`, e.g. ``P3`` means every third cycle. This is configured by setting - ``[scheduling]cycling mode = integer`` in the ``suite.rc`` file. + ``[scheduling]cycling mode = integer`` in the ``flow.cylc`` file. See also: @@ -539,7 +539,7 @@ Glossary what a :term:`job's ` requirements are, e.g. how much memory it requires. - Directives are set in the ``suite.rc`` file in the ``[runtime]`` section + Directives are set in the ``flow.cylc`` file in the ``[runtime]`` section (``[runtime][][directives]``). See also: @@ -631,7 +631,7 @@ Glossary * :term:`reload` reload - Any changes made to the ``suite.rc`` file whilst the suite is running + Any changes made to the ``flow.cylc`` file whilst the suite is running will not have any effect until the suite is either: * :term:`shutdown` and :term:`rerun ` @@ -654,7 +654,7 @@ Glossary parameterisation Parameterisation is a way to consolidate configuration in the Cylc - ``suite.rc`` file by implicitly looping over a set of pre-defined + ``flow.cylc`` file by implicitly looping over a set of pre-defined variables e.g: .. code-block:: cylc @@ -823,7 +823,7 @@ Glossary with other optional files and directories which configure the way in which a :term:`Cylc suite` is run. E.g: - * Jinja2 variables to be passed into the ``suite.rc`` file ( + * Jinja2 variables to be passed into the ``flow.cylc`` file ( :rose:conf:`rose-suite.conf[jinja2:suite.rc]`). * Environment variables to be provided to ``cylc run`` ( :rose:conf:`rose-suite.conf[env]`). diff --git a/sphinx/tutorial/cylc/furthertopics/broadcast.rst b/sphinx/tutorial/cylc/furthertopics/broadcast.rst index 447b49c15b..dcd09b01d7 100644 --- a/sphinx/tutorial/cylc/furthertopics/broadcast.rst +++ b/sphinx/tutorial/cylc/furthertopics/broadcast.rst @@ -26,7 +26,7 @@ Create a new suite in the ``cylc-run`` directory called mkdir ~/cylc-run/tutorial-broadcast cd ~/cylc-run/tutorial-broadcast -Copy the following configuration into a ``suite.rc`` file: +Copy the following configuration into a ``flow.cylc`` file: .. code-block:: cylc diff --git a/sphinx/tutorial/cylc/furthertopics/clock-triggered-tasks.rst b/sphinx/tutorial/cylc/furthertopics/clock-triggered-tasks.rst index 43181eebf1..e5be426db0 100644 --- a/sphinx/tutorial/cylc/furthertopics/clock-triggered-tasks.rst +++ b/sphinx/tutorial/cylc/furthertopics/clock-triggered-tasks.rst @@ -47,7 +47,7 @@ Within your ``~/cylc-run`` directory create a new directory called mkdir ~/cylc-run/clock-trigger cd ~/cylc-run/clock-trigger -Paste the following code into a ``suite.rc`` file: +Paste the following code into a ``flow.cylc`` file: .. code-block:: cylc @@ -91,7 +91,7 @@ We want our clock to only ring in real-time rather than the simulated cycle time. To do this, add the following lines to the ``[scheduling]`` section of -your ``suite.rc``: +your ``flow.cylc``: .. code-block:: cylc @@ -123,7 +123,7 @@ Adding More Clock-Triggered Tasks We will now modify our suite to run tasks at quarter-past, half-past and quarter-to the hour. -Open your ``suite.rc`` and modify the ``[runtime]`` section by adding the +Open your ``flow.cylc`` and modify the ``[runtime]`` section by adding the following: .. code-block:: cylc diff --git a/sphinx/tutorial/cylc/furthertopics/family-triggers.rst b/sphinx/tutorial/cylc/furthertopics/family-triggers.rst index 29cd03bc6d..73522474dc 100644 --- a/sphinx/tutorial/cylc/furthertopics/family-triggers.rst +++ b/sphinx/tutorial/cylc/furthertopics/family-triggers.rst @@ -50,7 +50,7 @@ Create a new suite called ``tutorial-family-triggers``:: mkdir ~/cylc-run/tutorial-family-triggers cd ~/cylc-run/tutorial-family-triggers -Paste the following configuration into the ``suite.rc`` file: +Paste the following configuration into the ``flow.cylc`` file: .. code-block:: cylc @@ -108,7 +108,7 @@ to use the ``finish-all`` trigger to check for all members of the ``MINERS`` family finishing, and the ``succeed-any`` trigger to check for any of the tasks in the ``MINERS`` family succeeding. -Open your ``suite.rc`` file and change the ``[[dependencies]]`` to look like +Open your ``flow.cylc`` file and change the ``[[dependencies]]`` to look like this: .. code-block:: cylc @@ -143,7 +143,7 @@ all the miners have reported back and had time to discuss their findings. To do this we will make use of family triggers in a similar manner to before. -Open your ``suite.rc`` file and change the ``[[dependencies]]`` to look like +Open your ``flow.cylc`` file and change the ``[[dependencies]]`` to look like this: .. code-block:: cylc diff --git a/sphinx/tutorial/cylc/furthertopics/inheritance.rst b/sphinx/tutorial/cylc/furthertopics/inheritance.rst index c1bf03af3c..d9fa894e5f 100644 --- a/sphinx/tutorial/cylc/furthertopics/inheritance.rst +++ b/sphinx/tutorial/cylc/furthertopics/inheritance.rst @@ -16,7 +16,7 @@ Create a new suite by running the command:: rose tutorial inheritance-tutorial cd ~/cylc-run/inheritance-tutorial -You will now have a ``suite.rc`` file that defines two tasks each representing +You will now have a ``flow.cylc`` file that defines two tasks each representing a different aircraft, the Airbus A380 jumbo jet and the Robson R44 helicopter: .. image:: https://upload.wikimedia.org/wikipedia/commons/0/09/A6-EDY_A380_Emirates_31_jan_2013_jfk_%288442269364%29_%28cropped%29.jpg @@ -170,7 +170,7 @@ As the V-22 can be thought of as both a plane and a helicopter we want it to inherit from both the ``AIRPLANE`` and ``HELICOPTER`` families. In Cylc we can inherit from multiple families by separating their names with commas: -Add the following task to your ``suite.rc`` file. +Add the following task to your ``flow.cylc`` file. .. code-block:: cylc diff --git a/sphinx/tutorial/cylc/furthertopics/queues.rst b/sphinx/tutorial/cylc/furthertopics/queues.rst index a04dfa48cb..dafb9627bd 100644 --- a/sphinx/tutorial/cylc/furthertopics/queues.rst +++ b/sphinx/tutorial/cylc/furthertopics/queues.rst @@ -19,7 +19,7 @@ Create a new suite called ``queues-tutorial``:: rose tutorial queues-tutorial cd ~/cylc-run/queues-tutorial -You will now have a ``suite.rc`` file that looks like this: +You will now have a ``flow.cylc`` file that looks like this: .. code-block:: cylc diff --git a/sphinx/tutorial/cylc/furthertopics/retries.rst b/sphinx/tutorial/cylc/furthertopics/retries.rst index d412e0ea5c..e193339952 100644 --- a/sphinx/tutorial/cylc/furthertopics/retries.rst +++ b/sphinx/tutorial/cylc/furthertopics/retries.rst @@ -86,7 +86,7 @@ Configuring Retries ------------------- We need to tell Cylc to retry it a few times. To do this, add the following -to the end of the ``[[roll_doubles]]`` task section in the ``suite.rc`` file: +to the end of the ``[[roll_doubles]]`` task section in the ``flow.cylc`` file: .. code-block:: cylc diff --git a/sphinx/tutorial/cylc/furthertopics/suicide-triggers.rst b/sphinx/tutorial/cylc/furthertopics/suicide-triggers.rst index 067fb974d8..6e3cd02318 100644 --- a/sphinx/tutorial/cylc/furthertopics/suicide-triggers.rst +++ b/sphinx/tutorial/cylc/furthertopics/suicide-triggers.rst @@ -109,7 +109,7 @@ Create a new suite called ``suicide-triggers``:: mkdir -p ~/cylc-run/suicide-triggers cd ~/cylc-run/suicide-triggers -Paste the following code into the ``suite.rc`` file: +Paste the following code into the ``flow.cylc`` file: .. code-block:: cylc diff --git a/sphinx/tutorial/cylc/runtime/configuration-consolidation/index.rst b/sphinx/tutorial/cylc/runtime/configuration-consolidation/index.rst index 8cb159e1f0..abcf54b5b2 100644 --- a/sphinx/tutorial/cylc/runtime/configuration-consolidation/index.rst +++ b/sphinx/tutorial/cylc/runtime/configuration-consolidation/index.rst @@ -9,7 +9,7 @@ Consolidating Configuration .. ifnotslides:: - In the last section we wrote out the following code in the ``suite.rc`` file: + In the last section we wrote out the following code in the ``flow.cylc`` file: .. slide:: Weather Forecasting Suite :level: 2 @@ -92,12 +92,12 @@ The ``cylc get-config`` Command .. ifnotslides:: - The ``cylc get-config`` command reads in then prints out the ``suite.rc`` file + The ``cylc get-config`` command reads in then prints out the ``flow.cylc`` file to the terminal. Throughout this section we will be introducing methods for consolidating - the ``suite.rc`` file, the ``cylc get-config`` command can be used to - "expand" the ``suite.rc`` file back to its full form. + the ``flow.cylc`` file, the ``cylc get-config`` command can be used to + "expand" the ``flow.cylc`` file back to its full form. .. note:: diff --git a/sphinx/tutorial/cylc/runtime/configuration-consolidation/jinja2.rst b/sphinx/tutorial/cylc/runtime/configuration-consolidation/jinja2.rst index 6dc5963bb1..314332c713 100644 --- a/sphinx/tutorial/cylc/runtime/configuration-consolidation/jinja2.rst +++ b/sphinx/tutorial/cylc/runtime/configuration-consolidation/jinja2.rst @@ -34,11 +34,11 @@ the evaluation of the expression, e.g: .. code-block:: css+jinja - There are {{ foo }} methods for consolidating the suite.rc file + There are {{ foo }} methods for consolidating the flow.cylc file Would result in:: - There are 3 methods for consolidating the suite.rc file + There are 3 methods for consolidating the flow.cylc file .. nextslide:: @@ -60,7 +60,7 @@ Would result in: .. nextslide:: -To enable Jinja2 in the ``suite.rc`` file, add the following `shebang`_ to the +To enable Jinja2 in the ``flow.cylc`` file, add the following `shebang`_ to the top of the file: .. code-block:: cylc @@ -184,7 +184,7 @@ This would result in: ``get_observations`` and ``get_rainfall`` tasks. Rather than writing it out multiple times we will use Jinja2 to centralise this configuration. - At the top of the ``suite.rc`` file add the Jinja2 shebang line. Then + At the top of the ``flow.cylc`` file add the Jinja2 shebang line. Then copy the value of the ``API_KEY`` environment variable and use it to define an ``API_KEY`` Jinja2 variable: diff --git a/sphinx/tutorial/cylc/runtime/configuration-consolidation/parameters.rst b/sphinx/tutorial/cylc/runtime/configuration-consolidation/parameters.rst index c0329fdd15..8835051864 100644 --- a/sphinx/tutorial/cylc/runtime/configuration-consolidation/parameters.rst +++ b/sphinx/tutorial/cylc/runtime/configuration-consolidation/parameters.rst @@ -43,7 +43,7 @@ Cylc Parameters .. ifnotslides:: - When the ``suite.rc`` file is read by Cylc, the parameters will be expanded. + When the ``flow.cylc`` file is read by Cylc, the parameters will be expanded. For example the code above is equivalent to: .. code-block:: cylc diff --git a/sphinx/tutorial/cylc/runtime/introduction.rst b/sphinx/tutorial/cylc/runtime/introduction.rst index 36a1222d9c..7e5541c8f8 100644 --- a/sphinx/tutorial/cylc/runtime/introduction.rst +++ b/sphinx/tutorial/cylc/runtime/introduction.rst @@ -92,7 +92,7 @@ We can also call other scripts or executables in this way, e.g: echo 'Hello World!' .. code-block:: cylc - :caption: suite.rc + :caption: flow.cylc [runtime] [[hello_world]] @@ -112,7 +112,7 @@ We can also call other scripts or executables in this way, e.g: print('Hello World!') .. code-block:: cylc - :caption: suite.rc + :caption: flow.cylc [runtime] [[hello_world]] @@ -303,7 +303,7 @@ Running A Suite Here ```` is the path to the suite's location within the filesystem (so if we create a suite in ``~/cylc-run/foo`` we would put - ``~/cylc-run/foo/suite.rc``). + ``~/cylc-run/foo/flow.cylc``). Next we can run the suite using the ``cylc run`` command. @@ -357,8 +357,8 @@ Suite Files These files are written by Cylc as the suite is run and are useful for debugging purposes in the event of error. - ``suite.rc.processed`` - A copy of the ``suite.rc`` file made after any `Jinja2`_ has been + ``flow.cylc.processed`` + A copy of the ``flow.cylc`` file made after any `Jinja2`_ has been processed - we will cover this in the :ref:`tutorial-cylc-consolidating-configuration` section. ``share/`` @@ -374,7 +374,7 @@ Suite Files * ``log/db`` * ``log/job`` * ``log/suite`` - * ``suite.rc.processed`` + * ``flow.cylc.processed`` * ``share/`` * ``work/`` @@ -403,14 +403,14 @@ Suite Files rose tutorial runtime-introduction cd ~/cylc-run/runtime-introduction - In this directory we have the ``suite.rc`` file from the + In this directory we have the ``flow.cylc`` file from the :ref:`weather forecasting suite ` with some runtime configuration added to it. There is also a script called ``get-observations`` located in the bin directory. - Take a look at the ``[runtime]`` section in the ``suite.rc`` file. + Take a look at the ``[runtime]`` section in the ``flow.cylc`` file. #. **Run The Suite.** diff --git a/sphinx/tutorial/cylc/runtime/runtime-configuration.rst b/sphinx/tutorial/cylc/runtime/runtime-configuration.rst index 90f1dc5d75..91f33e4711 100644 --- a/sphinx/tutorial/cylc/runtime/runtime-configuration.rst +++ b/sphinx/tutorial/cylc/runtime/runtime-configuration.rst @@ -312,7 +312,7 @@ Start, Stop, Restart rose tutorial api-key - Add the following lines to the bottom of the ``suite.rc`` file replacing + Add the following lines to the bottom of the ``flow.cylc`` file replacing ``xxx...`` with your API key: .. code-block:: cylc @@ -360,7 +360,7 @@ Start, Stop, Restart SITE_ID = 3976 API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - Check the ``suite.rc`` file is valid by running the command: + Check the ``flow.cylc`` file is valid by running the command: .. code-block:: bash @@ -426,9 +426,9 @@ Start, Stop, Restart The runtime configuration for the remaining tasks has been written out for you in the ``runtime`` file which you will find in the :term:`suite directory`. Copy the code in the ``runtime`` file to the - bottom of the ``suite.rc`` file. + bottom of the ``flow.cylc`` file. - Check the ``suite.rc`` file is valid by running the command: + Check the ``flow.cylc`` file is valid by running the command: .. code-block:: bash diff --git a/sphinx/tutorial/cylc/scheduling/datetime-cycling.rst b/sphinx/tutorial/cylc/scheduling/datetime-cycling.rst index 7eb9e47470..1b60158b27 100644 --- a/sphinx/tutorial/cylc/scheduling/datetime-cycling.rst +++ b/sphinx/tutorial/cylc/scheduling/datetime-cycling.rst @@ -451,7 +451,7 @@ Putting It All Together mkdir ~/cylc-run/datetime-cycling cd ~/cylc-run/datetime-cycling - Create a ``suite.rc`` file and paste the following code into it: + Create a ``flow.cylc`` file and paste the following code into it: .. code-block:: cylc @@ -521,7 +521,7 @@ Putting It All Together .. code-block:: sub - cylc graph + cylc graph .. spoiler:: Solution warning diff --git a/sphinx/tutorial/cylc/scheduling/graphing.rst b/sphinx/tutorial/cylc/scheduling/graphing.rst index 87816438a6..6f634c2ef8 100644 --- a/sphinx/tutorial/cylc/scheduling/graphing.rst +++ b/sphinx/tutorial/cylc/scheduling/graphing.rst @@ -11,21 +11,21 @@ In this section we will cover writing basic workflows in cylc. .. _Cylc file format: -The ``suite.rc`` File Format ----------------------------- +The ``flow.cylc`` File Format +----------------------------- .. ifnotslides:: We refer to a Cylc workflow as a :term:`Cylc suite`. A Cylc suite is a - directory containing a ``suite.rc`` file. This configuration file is where - we define our workflow. The ``suite.rc`` file uses a nested `INI`_-based + directory containing a ``flow.cylc`` file. This configuration file is where + we define our workflow. The ``flow.cylc`` file uses a nested `INI`_-based format: .. ifslides:: * Cylc workflow == Cylc suite - * Cylc suite is a directory containing a ``suite.rc`` file - * The ``suite.rc`` file is written in a nested `INI`_-based format + * Cylc suite is a directory containing a ``flow.cylc`` file + * The ``flow.cylc`` file is written in a nested `INI`_-based format .. ifnotslides:: @@ -119,7 +119,7 @@ Duplicate settings get overwritten: Indentation ^^^^^^^^^^^ -It is advisable to indent ``suite.rc`` files. +It is advisable to indent ``flow.cylc`` files. However, Cylc ignores this indentation meaning the following two examples are equivalent: @@ -265,7 +265,7 @@ Cylc Graphs Cylc provides a GUI for visualising :term:`graphs `. It is run on the command line using the ``cylc graph `` command where the path ``path`` - is to the ``suite.rc`` file you wish to visualise. + is to the ``flow.cylc`` file you wish to visualise. When run, ``cylc graph`` will display a diagram similar to the ones you have seen so far. The number ``1`` which appears below each task is the @@ -311,7 +311,7 @@ Cylc Graphs #. **Create a Cylc suite.** - A Cylc suite is just a directory containing a ``suite.rc`` file. + A Cylc suite is just a directory containing a ``flow.cylc`` file. If you don't have one already, create a ``cylc-run`` directory in your user space i.e:: @@ -326,7 +326,7 @@ Cylc Graphs mkdir ~/cylc-run/graph-introduction cd ~/cylc-run/graph-introduction - Inside this directory create a ``suite.rc`` file and paste in the + Inside this directory create a ``flow.cylc`` file and paste in the following text: .. code-block:: cylc @@ -341,7 +341,7 @@ Cylc Graphs We now have a blank Cylc suite, next we need to define a workflow. - Edit your ``suite.rc`` file to add graph strings representing the + Edit your ``flow.cylc`` file to add graph strings representing the following graph: .. digraph:: graph_tutorial @@ -366,7 +366,7 @@ Cylc Graphs inside the :term:`suite directory` we can run ``cylc graph .``. If the results don't match the diagram above try going back to the - suite.rc file and making changes. + flow.cylc file and making changes. .. tip:: diff --git a/sphinx/tutorial/cylc/scheduling/integer-cycling.rst b/sphinx/tutorial/cylc/scheduling/integer-cycling.rst index db15986453..42cf105816 100644 --- a/sphinx/tutorial/cylc/scheduling/integer-cycling.rst +++ b/sphinx/tutorial/cylc/scheduling/integer-cycling.rst @@ -427,7 +427,7 @@ Recurrence Sections :term:`cycling suite `. If you have not completed the previous practical use the following code for - your ``suite.rc`` file. + your ``flow.cylc`` file. .. code-block:: cylc @@ -448,7 +448,7 @@ Recurrence Sections mkdir -p ~/cylc-run/integer-cycling cd ~/cylc-run/integer-cycling - Copy the above code into a ``suite.rc`` file in that directory. + Copy the above code into a ``flow.cylc`` file in that directory. #. **Make the suite cycle.** @@ -496,7 +496,7 @@ Recurrence Sections Suppose we wanted the ``qux`` task to run every *other* cycle as opposed to every cycle. We can do this by adding another recurrence. - Make the following changes to your ``suite.rc`` file. + Make the following changes to your ``flow.cylc`` file. .. code-block:: diff @@ -527,7 +527,7 @@ Recurrence Sections #. Between ``qux`` from the previous cycle and ``foo`` *every even cycle* (e.g. qux.1 => foo.2). - Have a go at adding inter-cycle dependencies to your ``suite.rc`` file to + Have a go at adding inter-cycle dependencies to your ``flow.cylc`` file to make your workflow match the diagram below. .. hint:: diff --git a/sphinx/tutorial/rose/applications.rst b/sphinx/tutorial/rose/applications.rst index f23df168b7..94109aedf1 100644 --- a/sphinx/tutorial/rose/applications.rst +++ b/sphinx/tutorial/rose/applications.rst @@ -9,7 +9,7 @@ Rose Applications .. ifnotslides:: - The Cylc ``suite.rc`` file allows us to define environment variables for + The Cylc ``flow.cylc`` file allows us to define environment variables for use by :term:`tasks ` e.g: .. slide:: Cylc Task Environment @@ -222,9 +222,9 @@ An application can be run using the :ref:`command-rose-app-run` command: rose tutorial test-data file/test-data - #. **Move environment variables defined in the** ``suite.rc`` **file.** + #. **Move environment variables defined in the** ``flow.cylc`` **file.** - In the ``[runtime][forecast][environment]`` section of the ``suite.rc`` + In the ``[runtime][forecast][environment]`` section of the ``flow.cylc`` file in the :ref:`weather-forecasting suite ` we set a few environment variables: @@ -263,7 +263,7 @@ An application can be run using the :ref:`command-rose-app-run` command: To make this application work outside of the weather forecasting suite we will also need to provide the ``DOMAIN`` and ``RESOLUTION`` environment variables defined - in the ``[runtime][root][environment]`` section of the ``suite.rc`` + in the ``[runtime][root][environment]`` section of the ``flow.cylc`` file as well as the ``CYLC_TASK_CYCLE_POINT`` environment variable provided by Cylc when it runs a task. diff --git a/sphinx/tutorial/rose/configurations.rst b/sphinx/tutorial/rose/configurations.rst index 94888a70c3..bfd8f8f100 100644 --- a/sphinx/tutorial/rose/configurations.rst +++ b/sphinx/tutorial/rose/configurations.rst @@ -28,7 +28,7 @@ behaviours such as: :term:`Rose suite configuration` A Rose configuration designed to run :term:`Cylc suites `. For instance it may be used to define Jinja2 variables for use in the - ``suite.rc`` file. + ``flow.cylc`` file. .. ifslides:: @@ -52,7 +52,7 @@ Rose Configuration Format .. ifslides:: - .. rubric:: Like the ``suite.rc`` format: + .. rubric:: Like the ``flow.cylc`` format: * Comments start with a ``#`` character. * Settings are written as ``key=value`` pairs. @@ -67,7 +67,7 @@ Rose Configuration Format .. ifslides:: - .. rubric:: Unlike the ``suite.rc`` format: + .. rubric:: Unlike the ``flow.cylc`` format: * Sections cannot be nested. * Settings should not be indented. diff --git a/sphinx/tutorial/rose/furthertopics/command-keys.rst b/sphinx/tutorial/rose/furthertopics/command-keys.rst index 25137711bd..daca00d2bd 100644 --- a/sphinx/tutorial/rose/furthertopics/command-keys.rst +++ b/sphinx/tutorial/rose/furthertopics/command-keys.rst @@ -20,7 +20,7 @@ Create a new Rose suite configuration called ``command-keys``:: mkdir -p ~/rose-tutorial/command-keys cd ~/rose-tutorial/command-keys -Create a blank :rose:file:`rose-suite.conf` and a ``suite.rc`` file that +Create a blank :rose:file:`rose-suite.conf` and a ``flow.cylc`` file that looks like this: .. code-block:: cylc @@ -74,7 +74,7 @@ Open the :rose:file:`rose-app.conf` file and edit to look like this: make_dough=sleep 8; echo 'dough for later' timed_bread=sleep 15; echo 'fresh bread when you want it' -Save your changes and open up your ``suite.rc`` file. Alter the +Save your changes and open up your ``flow.cylc`` file. Alter the ``[[breadmaker]]`` task to look like this: .. code-block:: cylc diff --git a/sphinx/tutorial/rose/furthertopics/polling.rst b/sphinx/tutorial/rose/furthertopics/polling.rst index 2c52274ee4..5e38b2dbd4 100644 --- a/sphinx/tutorial/rose/furthertopics/polling.rst +++ b/sphinx/tutorial/rose/furthertopics/polling.rst @@ -18,7 +18,7 @@ Create a new Rose suite configuration:: mkdir -p ~/rose-tutorial/polling cd ~/rose-tutorial/polling -Create a blank :rose:file:`rose-suite.conf` and a ``suite.rc`` +Create a blank :rose:file:`rose-suite.conf` and a ``flow.cylc`` file that looks like this: .. code-block:: cylc @@ -37,7 +37,7 @@ This is a simple suite which consists of the following: * A ``bob`` task which we will be using to poll with. * A ``read_letter`` task which will run once the polling task is complete. -It will need some runtime. Add the following to your ``suite.rc`` file: +It will need some runtime. Add the following to your ``flow.cylc`` file: .. code-block:: cylc diff --git a/sphinx/tutorial/rose/furthertopics/rose-arch.rst b/sphinx/tutorial/rose/furthertopics/rose-arch.rst index 516c753c2e..bfc3ed4e24 100644 --- a/sphinx/tutorial/rose/furthertopics/rose-arch.rst +++ b/sphinx/tutorial/rose/furthertopics/rose-arch.rst @@ -18,7 +18,7 @@ Create a new Rose suite configuration:: mkdir -p ~/rose-tutorial/rose-arch-tutorial -Create a blank :rose:file:`rose-suite.conf` and a ``suite.rc`` +Create a blank :rose:file:`rose-suite.conf` and a ``flow.cylc`` file that looks like this: .. code-block:: cylc diff --git a/sphinx/tutorial/rose/furthertopics/rose-bunch.rst b/sphinx/tutorial/rose/furthertopics/rose-bunch.rst index adf3e91b25..54ea5e10ef 100644 --- a/sphinx/tutorial/rose/furthertopics/rose-bunch.rst +++ b/sphinx/tutorial/rose/furthertopics/rose-bunch.rst @@ -49,7 +49,7 @@ Create a new Rose suite configuration:: mkdir -p ~/rose-tutorial/rose-bunch cd ~/rose-tutorial/rose-bunch -Create a blank :rose:file:`rose-suite.conf` and a ``suite.rc`` +Create a blank :rose:file:`rose-suite.conf` and a ``flow.cylc`` file that looks like this: .. code-block:: cylc @@ -134,7 +134,7 @@ directory of the lander app and running:: chmod +x land -Navigate to the top directory of your suite (where the ``suite.rc`` and +Navigate to the top directory of your suite (where the ``flow.cylc`` and :rose:file:`rose-suite.conf` files can be found) and run :ref:`command-rose-suite-run`. diff --git a/sphinx/tutorial/rose/furthertopics/rose-stem-tutorial.rst b/sphinx/tutorial/rose/furthertopics/rose-stem-tutorial.rst index c2a607a5cd..f97afeb35c 100644 --- a/sphinx/tutorial/rose/furthertopics/rose-stem-tutorial.rst +++ b/sphinx/tutorial/rose/furthertopics/rose-stem-tutorial.rst @@ -129,12 +129,12 @@ The ``$SOURCE_SPACESHIP`` environment variable will be set using the Jinja2 variable of the same name which is provided by Rose Stem. -The ``suite.rc`` file ---------------------- +The ``flow.cylc`` file +---------------------- -Next we will look at the ``rose-stem/suite.rc`` file. +Next we will look at the ``rose-stem/flow.cylc`` file. -The ``suite.rc`` file starts off with ``UTC mode = True``, which you +The ``flow.cylc`` file starts off with ``UTC mode = True``, which you should already be :ref:`familiar with `. The next part is a Jinja2 block which links the group names the user can specify with the :term:`graph ` for that group. In this @@ -285,7 +285,7 @@ Further Exercises ----------------- If you wish, you can try extending the suite to include the ``fire_lasers`` -group of tasks which was in the list of groups in the ``suite.rc`` file. +group of tasks which was in the list of groups in the ``flow.cylc`` file. Using the same technique as we've just demonstrated for piloting the spaceship, you should be able to aim and fire the ship's weapons. @@ -306,5 +306,5 @@ option on the ``rose stem`` command line. For example: automatic-options=GRAVITY=newtonian PLANET=jupiter sets the variable ``GRAVITY`` to have the value ``newtonian``, and -``PLANET`` to be ``jupiter``. These can then be used in the ``suite.rc`` +``PLANET`` to be ``jupiter``. These can then be used in the ``flow.cylc`` file as Jinja2 variables. diff --git a/sphinx/tutorial/rose/suites.rst b/sphinx/tutorial/rose/suites.rst index 3a6b6e9095..d9be7508e1 100644 --- a/sphinx/tutorial/rose/suites.rst +++ b/sphinx/tutorial/rose/suites.rst @@ -33,9 +33,9 @@ A Rose suite configuration is a Cylc :term:`suite directory` containing a :rose:conf:`rose-suite.conf[env]` Environment variables for use by the whole suite. :rose:conf:`rose-suite.conf[jinja2:suite.rc]` - `Jinja2`_ variables for use in the ``suite.rc`` file. + `Jinja2`_ variables for use in the ``flow.cylc`` file. :rose:conf:`rose-suite.conf[empy:suite.rc]` - `EmPy`_ variables for use in the ``suite.rc`` file. + `EmPy`_ variables for use in the ``flow.cylc`` file. :rose:conf:`rose-suite.conf[file:NAME]` Files and resources to be installed in the :term:`run directory` when the suite is run. @@ -52,7 +52,7 @@ A Rose suite configuration is a Cylc :term:`suite directory` containing a In the following example the environment variable ``GREETING`` and the Jinja2 variable ``WORLD`` are both set in the :rose:file:`rose-suite.conf` - file. These variables can then be used in the ``suite.rc`` file: + file. These variables can then be used in the ``flow.cylc`` file: .. code-block:: rose :caption: rose-suite.conf @@ -64,7 +64,7 @@ A Rose suite configuration is a Cylc :term:`suite directory` containing a WORLD=Earth .. code-block:: cylc - :caption: suite.rc + :caption: flow.cylc [scheduling] [[dependencies]] @@ -81,7 +81,7 @@ Suite Directory Vs Run Directory -------------------------------- :term:`suite directory` - The directory in which the suite is written. The ``suite.rc`` and + The directory in which the suite is written. The ``flow.cylc`` and :rose:file:`rose-suite.conf` files live here. :term:`run directory` The directory in which the suite runs. The ``work``, ``share`` and ``log`` @@ -137,7 +137,7 @@ Running Rose Suite Configurations it becomes the :term:`run directory`. #. Any files defined in the :rose:file:`rose-suite.conf` file are installed. #. Jinja2 variables defined in the :rose:file:`rose-suite.conf` file are added - to the top of the ``suite.rc`` file. + to the top of the ``flow.cylc`` file. #. The Cylc suite is validated. #. The Cylc suite is run. #. The Cylc GUI is launched. @@ -315,7 +315,7 @@ See the :ref:`Cheat Sheet` for more information. file does not need to have anything in it but it is required to run :ref:`command-rose-suite-run`. - There are three things defined in the ``suite.rc`` file which it might be + There are three things defined in the ``flow.cylc`` file which it might be useful to be able to configure: ``station`` @@ -377,9 +377,9 @@ See the :ref:`Cheat Sheet` for more information. This will contain the environment and Jinja2 variables we have just defined. - #. **Use Suite Variables In The** ``suite.rc`` **File.** + #. **Use Suite Variables In The** ``flow.cylc`` **File.** - Next we need to make use of these settings in the ``suite.rc`` file. + Next we need to make use of these settings in the ``flow.cylc`` file. We can delete the ``RESOLUTION`` and ``DOMAIN`` settings in the ``[runtime][root][environment]`` section which would otherwise override @@ -470,7 +470,7 @@ Rose Applications In Rose Suite Configurations .. nextslide:: .. code-block:: cylc - :caption: suite.rc + :caption: flow.cylc [runtime] [[hello]] @@ -492,7 +492,7 @@ Rose Applications In Rose Suite Configurations :term:`app ` in the task defined below. .. code-block:: cylc - :caption: suite.rc + :caption: flow.cylc [runtime] [[greetings]] @@ -600,12 +600,12 @@ Rose Applications In Rose Suite Configurations We have moved the map template file (``map-template.html``) into the ``forecast`` application so we can delete the ``MAP_TEMPLATE`` environment variable from the ``[runtime]forecast`` section of the - ``suite.rc`` file. + ``flow.cylc`` file. Copy the remaining environment variables defined in the ``forecast`` - task within the ``suite.rc`` file into the :rose:file:`rose-app.conf` + task within the ``flow.cylc`` file into the :rose:file:`rose-app.conf` file of the ``forecast`` application, replacing any values already - specified if necessary. Remove the lines from the ``suite.rc`` file + specified if necessary. Remove the lines from the ``flow.cylc`` file when you are done. Remember, in Rose configuration files: @@ -631,7 +631,7 @@ Rose Applications In Rose Suite Configurations Finally we need to change the ``forecast`` task to run :ref:`command-rose-task-run`. The ``[runtime]forecast`` section of the - ``suite.rc`` file should now look like this: + ``flow.cylc`` file should now look like this: .. code-block:: cylc diff --git a/sphinx/tutorial/rose/summary.rst b/sphinx/tutorial/rose/summary.rst index 8ec5e8abaf..508178089c 100644 --- a/sphinx/tutorial/rose/summary.rst +++ b/sphinx/tutorial/rose/summary.rst @@ -37,18 +37,18 @@ So far we have covered: fontsize = "20" fontcolor = "#5050aa" labelloc = "r" - "suite.rc" [fontsize="18", + "flow.cylc" [fontsize="18", fontname="mono", fontcolor="black"] "rcinfo" [label="Defines the workflow\nin terms of tasks\nand dependencies"] - "suite.rc" -- "rcinfo" + "flow.cylc" -- "rcinfo" subgraph cluster_2 { label = "Rose Suite Configuration" "rose-suite.conf" [fontsize="18", fontname="mono", fontcolor="black"] - "confinfo" [label="Defines Jinja2 variables for\nthe suite.rc and environment\nvariables for use throughout\nthe suite"] + "confinfo" [label="Defines Jinja2 variables for\nthe flow.cylc and environment\nvariables for use throughout\nthe suite"] "rose-suite.conf" -- "confinfo" subgraph cluster_3 { @@ -88,9 +88,9 @@ Suite Commands ``cylc graph`` Draws the suite's :term:`graph`. ``cylc get-config`` - Processes the ``suite.rc`` file and prints it back out. + Processes the ``flow.cylc`` file and prints it back out. ``cylc validate`` - Validates the Cylc ``suite.rc`` file to check for any obvious errors. + Validates the Cylc ``flow.cylc`` file to check for any obvious errors. ``cylc run`` Runs a suite. ``cylc stop`` diff --git a/t/docs/02-tutorial-suites.t b/t/docs/02-tutorial-suites.t index 4b524d3dba..d86f0709a4 100644 --- a/t/docs/02-tutorial-suites.t +++ b/t/docs/02-tutorial-suites.t @@ -53,7 +53,7 @@ for tutorial in $(ls -1 "${TUTORIALS_PATH}"); do done else # Tutorial has no validate file - run cylc validate. - TESTS+=('cylc validate "'"${tutorial_path}/suite.rc"'"') + TESTS+=('cylc validate "'"${tutorial_path}/flow.cylc"'"') TEST_KEYS+=("${tutorial}-0") TUT_DIRS+=( "$tutorial_path" ) continue diff --git a/t/rose-ana/00-run-basic/suite.rc b/t/rose-ana/00-run-basic/flow.cylc similarity index 100% rename from t/rose-ana/00-run-basic/suite.rc rename to t/rose-ana/00-run-basic/flow.cylc diff --git a/t/rose-ana/01-run-basic-v1/suite.rc b/t/rose-ana/01-run-basic-v1/flow.cylc similarity index 100% rename from t/rose-ana/01-run-basic-v1/suite.rc rename to t/rose-ana/01-run-basic-v1/flow.cylc diff --git a/t/rose-task-env/00-non-cycle/suite.rc b/t/rose-task-env/00-non-cycle/flow.cylc similarity index 100% rename from t/rose-task-env/00-non-cycle/suite.rc rename to t/rose-task-env/00-non-cycle/flow.cylc diff --git a/t/rose-task-env/01-integer-cycling/suite.rc b/t/rose-task-env/01-integer-cycling/flow.cylc similarity index 100% rename from t/rose-task-env/01-integer-cycling/suite.rc rename to t/rose-task-env/01-integer-cycling/flow.cylc diff --git a/t/rose-task-env/02-360day-cycling/suite.rc b/t/rose-task-env/02-360day-cycling/flow.cylc similarity index 100% rename from t/rose-task-env/02-360day-cycling/suite.rc rename to t/rose-task-env/02-360day-cycling/flow.cylc diff --git a/t/rose-task-run/00-run-basic/suite.rc b/t/rose-task-run/00-run-basic/flow.cylc similarity index 100% rename from t/rose-task-run/00-run-basic/suite.rc rename to t/rose-task-run/00-run-basic/flow.cylc diff --git a/t/rose-task-run/01-run-basic-iso/suite.rc b/t/rose-task-run/01-run-basic-iso/flow.cylc similarity index 100% rename from t/rose-task-run/01-run-basic-iso/suite.rc rename to t/rose-task-run/01-run-basic-iso/flow.cylc diff --git a/t/rose-task-run/02-env-basic/suite.rc b/t/rose-task-run/02-env-basic/flow.cylc similarity index 100% rename from t/rose-task-run/02-env-basic/suite.rc rename to t/rose-task-run/02-env-basic/flow.cylc diff --git a/t/rose-task-run/03-env-basic-iso/suite.rc b/t/rose-task-run/03-env-basic-iso/flow.cylc similarity index 100% rename from t/rose-task-run/03-env-basic-iso/suite.rc rename to t/rose-task-run/03-env-basic-iso/flow.cylc diff --git a/t/rose-task-run/04-run-path-empty/suite.rc b/t/rose-task-run/04-run-path-empty/flow.cylc similarity index 100% rename from t/rose-task-run/04-run-path-empty/suite.rc rename to t/rose-task-run/04-run-path-empty/flow.cylc diff --git a/t/rose-task-run/06-app-prune-iso/suite.rc b/t/rose-task-run/06-app-prune-iso/flow.cylc similarity index 100% rename from t/rose-task-run/06-app-prune-iso/suite.rc rename to t/rose-task-run/06-app-prune-iso/flow.cylc diff --git a/t/rose-task-run/07-app-arch/suite.rc b/t/rose-task-run/07-app-arch/flow.cylc similarity index 100% rename from t/rose-task-run/07-app-arch/suite.rc rename to t/rose-task-run/07-app-arch/flow.cylc diff --git a/t/rose-task-run/08-app-fcm-make/suite.rc b/t/rose-task-run/08-app-fcm-make/flow.cylc similarity index 100% rename from t/rose-task-run/08-app-fcm-make/suite.rc rename to t/rose-task-run/08-app-fcm-make/flow.cylc diff --git a/t/rose-task-run/09-app-prune-work/suite.rc b/t/rose-task-run/09-app-prune-work/flow.cylc similarity index 100% rename from t/rose-task-run/09-app-prune-work/suite.rc rename to t/rose-task-run/09-app-prune-work/flow.cylc diff --git a/t/rose-task-run/10-specify-cycle/suite.rc b/t/rose-task-run/10-specify-cycle/flow.cylc similarity index 100% rename from t/rose-task-run/10-specify-cycle/suite.rc rename to t/rose-task-run/10-specify-cycle/flow.cylc diff --git a/t/rose-task-run/11-specify-cycle-iso/suite.rc b/t/rose-task-run/11-specify-cycle-iso/flow.cylc similarity index 100% rename from t/rose-task-run/11-specify-cycle-iso/suite.rc rename to t/rose-task-run/11-specify-cycle-iso/flow.cylc diff --git a/t/rose-task-run/12-app-prune-integer/suite.rc b/t/rose-task-run/12-app-prune-integer/flow.cylc similarity index 100% rename from t/rose-task-run/12-app-prune-integer/suite.rc rename to t/rose-task-run/12-app-prune-integer/flow.cylc diff --git a/t/rose-task-run/13-app-arch-cmd-out/suite.rc b/t/rose-task-run/13-app-arch-cmd-out/flow.cylc similarity index 100% rename from t/rose-task-run/13-app-arch-cmd-out/suite.rc rename to t/rose-task-run/13-app-arch-cmd-out/flow.cylc diff --git a/t/rose-task-run/14-app-prune-remove/suite.rc b/t/rose-task-run/14-app-prune-remove/flow.cylc similarity index 100% rename from t/rose-task-run/14-app-prune-remove/suite.rc rename to t/rose-task-run/14-app-prune-remove/flow.cylc diff --git a/t/rose-task-run/15-app-prune-extglob/suite.rc b/t/rose-task-run/15-app-prune-extglob/flow.cylc similarity index 100% rename from t/rose-task-run/15-app-prune-extglob/suite.rc rename to t/rose-task-run/15-app-prune-extglob/flow.cylc diff --git a/t/rose-task-run/16-app-prune-point/suite.rc b/t/rose-task-run/16-app-prune-point/flow.cylc similarity index 100% rename from t/rose-task-run/16-app-prune-point/suite.rc rename to t/rose-task-run/16-app-prune-point/flow.cylc diff --git a/t/rose-task-run/17-app-prune-glob-as-cycle-fmt/suite.rc b/t/rose-task-run/17-app-prune-glob-as-cycle-fmt/flow.cylc similarity index 100% rename from t/rose-task-run/17-app-prune-glob-as-cycle-fmt/suite.rc rename to t/rose-task-run/17-app-prune-glob-as-cycle-fmt/flow.cylc diff --git a/t/rose-task-run/18-app-fcm-make-ctx-name/suite.rc b/t/rose-task-run/18-app-fcm-make-ctx-name/flow.cylc similarity index 100% rename from t/rose-task-run/18-app-fcm-make-ctx-name/suite.rc rename to t/rose-task-run/18-app-fcm-make-ctx-name/flow.cylc diff --git a/t/rose-task-run/20-app-fcm-make-dest/suite.rc b/t/rose-task-run/20-app-fcm-make-dest/flow.cylc similarity index 100% rename from t/rose-task-run/20-app-fcm-make-dest/suite.rc rename to t/rose-task-run/20-app-fcm-make-dest/flow.cylc diff --git a/t/rose-task-run/24-app-fcm-make-fast/suite.rc b/t/rose-task-run/24-app-fcm-make-fast/flow.cylc similarity index 100% rename from t/rose-task-run/24-app-fcm-make-fast/suite.rc rename to t/rose-task-run/24-app-fcm-make-fast/flow.cylc diff --git a/t/rose-task-run/25-app-fcm-make-new-mode/suite.rc b/t/rose-task-run/25-app-fcm-make-new-mode/flow.cylc similarity index 100% rename from t/rose-task-run/25-app-fcm-make-new-mode/suite.rc rename to t/rose-task-run/25-app-fcm-make-new-mode/flow.cylc diff --git a/t/rose-task-run/26-app-fcm-make-new-mode-with-cont/suite.rc b/t/rose-task-run/26-app-fcm-make-new-mode-with-cont/flow.cylc similarity index 100% rename from t/rose-task-run/26-app-fcm-make-new-mode-with-cont/suite.rc rename to t/rose-task-run/26-app-fcm-make-new-mode-with-cont/flow.cylc diff --git a/t/rose-task-run/28-env-path-run-path/suite.rc b/t/rose-task-run/28-env-path-run-path/flow.cylc similarity index 100% rename from t/rose-task-run/28-env-path-run-path/suite.rc rename to t/rose-task-run/28-env-path-run-path/flow.cylc diff --git a/t/rose-task-run/30-app-arch-opt-source/suite.rc b/t/rose-task-run/30-app-arch-opt-source/flow.cylc similarity index 100% rename from t/rose-task-run/30-app-arch-opt-source/suite.rc rename to t/rose-task-run/30-app-arch-opt-source/flow.cylc diff --git a/t/rose-task-run/31-app-bunch/suite.rc b/t/rose-task-run/31-app-bunch/flow.cylc similarity index 100% rename from t/rose-task-run/31-app-bunch/suite.rc rename to t/rose-task-run/31-app-bunch/flow.cylc diff --git a/t/rose-task-run/32-app-arch-compressed/suite.rc b/t/rose-task-run/32-app-arch-compressed/flow.cylc similarity index 100% rename from t/rose-task-run/32-app-arch-compressed/suite.rc rename to t/rose-task-run/32-app-arch-compressed/flow.cylc diff --git a/t/rose-task-run/33-app-prune-cycle-format/suite.rc b/t/rose-task-run/33-app-prune-cycle-format/flow.cylc similarity index 100% rename from t/rose-task-run/33-app-prune-cycle-format/suite.rc rename to t/rose-task-run/33-app-prune-cycle-format/flow.cylc diff --git a/t/rose-task-run/34-app-prune-hosts-sharing-fs/suite.rc b/t/rose-task-run/34-app-prune-hosts-sharing-fs/flow.cylc similarity index 100% rename from t/rose-task-run/34-app-prune-hosts-sharing-fs/suite.rc rename to t/rose-task-run/34-app-prune-hosts-sharing-fs/flow.cylc diff --git a/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs/suite.rc b/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs/flow.cylc similarity index 100% rename from t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs/suite.rc rename to t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs/flow.cylc diff --git a/t/rose-task-run/36-app-arch-interrupted/suite.rc b/t/rose-task-run/36-app-arch-interrupted/flow.cylc similarity index 100% rename from t/rose-task-run/36-app-arch-interrupted/suite.rc rename to t/rose-task-run/36-app-arch-interrupted/flow.cylc diff --git a/t/rose-task-run/37-app-bunch-rm-old/suite.rc b/t/rose-task-run/37-app-bunch-rm-old/flow.cylc similarity index 100% rename from t/rose-task-run/37-app-bunch-rm-old/suite.rc rename to t/rose-task-run/37-app-bunch-rm-old/flow.cylc diff --git a/t/rose-task-run/38-app-bunch-counts/suite.rc b/t/rose-task-run/38-app-bunch-counts/flow.cylc similarity index 100% rename from t/rose-task-run/38-app-bunch-counts/suite.rc rename to t/rose-task-run/38-app-bunch-counts/flow.cylc diff --git a/t/rose-task-run/39-app-prune-cycle-host/suite.rc b/t/rose-task-run/39-app-prune-cycle-host/flow.cylc similarity index 100% rename from t/rose-task-run/39-app-prune-cycle-host/suite.rc rename to t/rose-task-run/39-app-prune-cycle-host/flow.cylc diff --git a/t/rose-task-run/40-app-arch-duplicate/suite.rc b/t/rose-task-run/40-app-arch-duplicate/flow.cylc similarity index 100% rename from t/rose-task-run/40-app-arch-duplicate/suite.rc rename to t/rose-task-run/40-app-arch-duplicate/flow.cylc diff --git a/t/rose-task-run/41-app-bunch-default-command/suite.rc b/t/rose-task-run/41-app-bunch-default-command/flow.cylc similarity index 100% rename from t/rose-task-run/41-app-bunch-default-command/suite.rc rename to t/rose-task-run/41-app-bunch-default-command/flow.cylc diff --git a/t/rosie-id/00-basic.t b/t/rosie-id/00-basic.t index cff1894dff..7b9d942147 100755 --- a/t/rosie-id/00-basic.t +++ b/t/rosie-id/00-basic.t @@ -122,7 +122,7 @@ file_cmp "$TEST_KEY.err" "$TEST_KEY.err" 'foo-aa000/suite.rc' <<'__SUITE_RC__' +#cat >'foo-aa000/flow.cylc' <<'__SUITE_RC__' #[scheduling] # [[dependencies]] # graph='t1' From d7809170673019702084902a07ccf1037c82b3a8 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Mon, 4 Jan 2021 17:44:23 +0000 Subject: [PATCH 18/78] rose => metomi.rose --- .../meta/lib/python/macros/badenvswitch.py | 4 ++-- .../04-transform/meta/lib/python/macros/envswitch.py | 10 +++++----- .../meta/lib/python/macros/nl_add_remove.py | 4 ++-- .../04-transform/meta/lib/python/macros/nl_ignore.py | 9 ++++----- .../app/04-transform/meta/lib/python/macros/null.py | 5 ++--- .../app/05-validate/meta/lib/python/macros/fib.py | 9 ++++----- .../app/05-validate/meta/lib/python/macros/null.py | 5 ++--- .../app/05-validate/meta/lib/python/macros/url.py | 6 ++---- metomi/rose/etc/rose-demo-upgrade-null/versions.py | 4 ++-- metomi/rose/etc/rose-demo-upgrade/versions.py | 12 ++++++------ .../etc/rose-meta/rose-demo-upgrade-null/versions.py | 4 ++-- .../rose/etc/rose-meta/rose-demo-upgrade/versions.py | 12 ++++++------ pytest.ini | 7 +++++-- 13 files changed, 44 insertions(+), 47 deletions(-) diff --git a/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/badenvswitch.py b/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/badenvswitch.py index 213b9e6605..b0d2599c38 100644 --- a/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/badenvswitch.py +++ b/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/badenvswitch.py @@ -2,10 +2,10 @@ # Copyright (C) British Crown (Met Office) & Contributors. # ----------------------------------------------------------------------------- -import rose.macro +import metomi.rose.macro -class InvalidValueTransformer(rose.macro.MacroBase): +class InvalidValueTransformer(metomi.rose.macro.MacroBase): """Test class to return an invalid value.""" diff --git a/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/envswitch.py b/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/envswitch.py index f975c1bca1..3e5bd8605d 100644 --- a/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/envswitch.py +++ b/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/envswitch.py @@ -2,10 +2,10 @@ # Copyright (C) British Crown (Met Office) & Contributors. # ----------------------------------------------------------------------------- -import rose.macro +import metomi.rose.macro -class LogicalTransformer(rose.macro.MacroBase): +class LogicalTransformer(metomi.rose.macro.MacroBase): """Test class to change the value of a boolean environment variable.""" @@ -15,10 +15,10 @@ def transform(self, config, meta_config=None): """Perform the transform operation on the env switch.""" if config.get(["env", "TRANSFORM_SWITCH"]) is not None: value = config.get(["env", "TRANSFORM_SWITCH"]).value - if value == rose.TYPE_BOOLEAN_VALUE_FALSE: - new_value = rose.TYPE_BOOLEAN_VALUE_TRUE + if value == metomi.rose.TYPE_BOOLEAN_VALUE_FALSE: + new_value = metomi.rose.TYPE_BOOLEAN_VALUE_TRUE else: - new_value = rose.TYPE_BOOLEAN_VALUE_FALSE + new_value = metomi.rose.TYPE_BOOLEAN_VALUE_FALSE config.set(["env", "TRANSFORM_SWITCH"], new_value) info = self.WARNING_CHANGED_VALUE.format(value, new_value) self.add_report("env", "TRANSFORM_SWITCH", value, info) diff --git a/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/nl_add_remove.py b/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/nl_add_remove.py index dcd9c408b5..3210f17aad 100644 --- a/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/nl_add_remove.py +++ b/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/nl_add_remove.py @@ -2,10 +2,10 @@ # Copyright (C) British Crown (Met Office) & Contributors. # ----------------------------------------------------------------------------- -import rose.macro +import metomi.rose.macro -class NamelistAdderRemover(rose.macro.MacroBase): +class NamelistAdderRemover(metomi.rose.macro.MacroBase): """Test class to add and remove a section.""" diff --git a/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/nl_ignore.py b/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/nl_ignore.py index 716cb52bf6..fe0c075266 100644 --- a/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/nl_ignore.py +++ b/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/nl_ignore.py @@ -2,10 +2,10 @@ # Copyright (C) British Crown (Met Office) & Contributors. # ----------------------------------------------------------------------------- -import rose.macro +import metomi.rose.macro -class NamelistIgnorer(rose.macro.MacroBase): +class NamelistIgnorer(metomi.rose.macro.MacroBase): """Test class to ignore and enable a section.""" @@ -14,15 +14,14 @@ class NamelistIgnorer(rose.macro.MacroBase): def transform(self, config, meta_config=None): """Perform the transform operation on the section.""" - change_list = [] section = "namelist:ignore_nl" node = config.get([section]) if node is not None: if node.state: - node.state = rose.config.ConfigNode.STATE_NORMAL + node.state = metomi.rose.config.ConfigNode.STATE_NORMAL info = self.WARNING_ENABLED.format(section) else: - node.state = rose.config.ConfigNode.STATE_USER_IGNORED + node.state = metomi.rose.config.ConfigNode.STATE_USER_IGNORED info = self.WARNING_IGNORED.format(section) self.add_report(section, None, None, info) return config, self.reports diff --git a/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/null.py b/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/null.py index 3bad8e5983..7809e61bb2 100644 --- a/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/null.py +++ b/demo/rose-config-edit/demo_meta/app/04-transform/meta/lib/python/macros/null.py @@ -2,11 +2,10 @@ # Copyright (C) British Crown (Met Office) & Contributors. # ----------------------------------------------------------------------------- -import rose.macro -import rose.variable +import metomi.rose.macro -class NullTransformer(rose.macro.MacroBase): +class NullTransformer(metomi.rose.macro.MacroBase): """Class to report changes for missing or null settings.""" diff --git a/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/fib.py b/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/fib.py index 86a17cdd7e..774525bf37 100644 --- a/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/fib.py +++ b/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/fib.py @@ -2,11 +2,11 @@ # Copyright (C) British Crown (Met Office) & Contributors. # ----------------------------------------------------------------------------- -import rose.macro -import rose.variable +import metomi.rose.macro +import metomi.rose.variable -class FibonacciChecker(rose.macro.MacroBase): +class FibonacciChecker(metomi.rose.macro.MacroBase): """Class to check if an array matches a Fibonacci sequence.""" @@ -19,14 +19,13 @@ def validate(self, config, meta_config, starts_at_zero=False): seq = [0, 1] else: seq = [1, 1] - problem_list = [] section = "env" option = "INVALID_SEQUENCE" node = config.get([section, option]) if node is None: return [] value = node.value - elems = rose.variable.array_split(value) + elems = metomi.rose.variable.array_split(value) if all([w.isdigit() for w in elems]) and len(elems) > 1: int_elems = [int(w) for w in elems] if len(int_elems) >= 2 and int_elems[:2] != seq: diff --git a/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/null.py b/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/null.py index f63c00416f..d63a78e0fa 100644 --- a/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/null.py +++ b/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/null.py @@ -2,11 +2,10 @@ # Copyright (C) British Crown (Met Office) & Contributors. # ----------------------------------------------------------------------------- -import rose.macro -import rose.variable +import metomi.rose.macro -class NullChecker(rose.macro.MacroBase): +class NullChecker(metomi.rose.macro.MacroBase): """Class to report errors for missing or null settings.""" diff --git a/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/url.py b/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/url.py index f85692af9c..c96349863c 100644 --- a/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/url.py +++ b/demo/rose-config-edit/demo_meta/app/05-validate/meta/lib/python/macros/url.py @@ -4,10 +4,10 @@ import http.client -import rose.macro +import metomi.rose.macro -class URLChecker(rose.macro.MacroBase): +class URLChecker(metomi.rose.macro.MacroBase): """Class to check if a URL is valid.""" @@ -16,8 +16,6 @@ class URLChecker(rose.macro.MacroBase): def validate(self, config, meta_config): """Validate a string containing a URL.""" self.reports = [] - seq = [1, 1] - problem_list = [] for section in config.value.keys(): node = config.get([section]) if not isinstance(node.value, dict): diff --git a/metomi/rose/etc/rose-demo-upgrade-null/versions.py b/metomi/rose/etc/rose-demo-upgrade-null/versions.py index 3ed34249df..b4d4a97f7f 100644 --- a/metomi/rose/etc/rose-demo-upgrade-null/versions.py +++ b/metomi/rose/etc/rose-demo-upgrade-null/versions.py @@ -3,10 +3,10 @@ # ----------------------------------------------------------------------------- """Module containing test upgrade macros""" -import rose.upgrade +import metomi.rose.upgrade -class UpgradeNull01(rose.upgrade.MacroUpgrade): +class UpgradeNull01(metomi.rose.upgrade.MacroUpgrade): """Upgrade nothing...""" diff --git a/metomi/rose/etc/rose-demo-upgrade/versions.py b/metomi/rose/etc/rose-demo-upgrade/versions.py index ce1cbf41a2..62cecf036d 100644 --- a/metomi/rose/etc/rose-demo-upgrade/versions.py +++ b/metomi/rose/etc/rose-demo-upgrade/versions.py @@ -7,10 +7,10 @@ """ -import rose.upgrade +import metomi.rose.upgrade -class UpgradeGarden01(rose.upgrade.MacroUpgrade): +class UpgradeGarden01(metomi.rose.upgrade.MacroUpgrade): """'We want... a shrubbery!'""" @@ -26,7 +26,7 @@ def upgrade(self, config, meta_config=None): return config, self.reports -class UpgradeGarden02(rose.upgrade.MacroUpgrade): +class UpgradeGarden02(metomi.rose.upgrade.MacroUpgrade): """'...there is one small problem...'""" @@ -49,7 +49,7 @@ def upgrade(self, config, meta_config=None): return config, self.reports -class UpgradeGarden03(rose.upgrade.MacroUpgrade): +class UpgradeGarden03(metomi.rose.upgrade.MacroUpgrade): """'You must find... another shrubbery!'""" @@ -79,7 +79,7 @@ def _get_shrub_num(self, config): return shrub_num -class UpgradeGarden041(rose.upgrade.MacroUpgrade): +class UpgradeGarden041(metomi.rose.upgrade.MacroUpgrade): """'...the two-level effect with a little path running down the middle'""" @@ -95,7 +95,7 @@ def upgrade(self, config, meta_config=None): return config, self.reports -class UpgradeGarden09(rose.upgrade.MacroUpgrade): +class UpgradeGarden09(metomi.rose.upgrade.MacroUpgrade): """'cut down the mightiest tree in the forest... with... a herring!'""" diff --git a/metomi/rose/etc/rose-meta/rose-demo-upgrade-null/versions.py b/metomi/rose/etc/rose-meta/rose-demo-upgrade-null/versions.py index 3ed34249df..b4d4a97f7f 100644 --- a/metomi/rose/etc/rose-meta/rose-demo-upgrade-null/versions.py +++ b/metomi/rose/etc/rose-meta/rose-demo-upgrade-null/versions.py @@ -3,10 +3,10 @@ # ----------------------------------------------------------------------------- """Module containing test upgrade macros""" -import rose.upgrade +import metomi.rose.upgrade -class UpgradeNull01(rose.upgrade.MacroUpgrade): +class UpgradeNull01(metomi.rose.upgrade.MacroUpgrade): """Upgrade nothing...""" diff --git a/metomi/rose/etc/rose-meta/rose-demo-upgrade/versions.py b/metomi/rose/etc/rose-meta/rose-demo-upgrade/versions.py index ce1cbf41a2..62cecf036d 100644 --- a/metomi/rose/etc/rose-meta/rose-demo-upgrade/versions.py +++ b/metomi/rose/etc/rose-meta/rose-demo-upgrade/versions.py @@ -7,10 +7,10 @@ """ -import rose.upgrade +import metomi.rose.upgrade -class UpgradeGarden01(rose.upgrade.MacroUpgrade): +class UpgradeGarden01(metomi.rose.upgrade.MacroUpgrade): """'We want... a shrubbery!'""" @@ -26,7 +26,7 @@ def upgrade(self, config, meta_config=None): return config, self.reports -class UpgradeGarden02(rose.upgrade.MacroUpgrade): +class UpgradeGarden02(metomi.rose.upgrade.MacroUpgrade): """'...there is one small problem...'""" @@ -49,7 +49,7 @@ def upgrade(self, config, meta_config=None): return config, self.reports -class UpgradeGarden03(rose.upgrade.MacroUpgrade): +class UpgradeGarden03(metomi.rose.upgrade.MacroUpgrade): """'You must find... another shrubbery!'""" @@ -79,7 +79,7 @@ def _get_shrub_num(self, config): return shrub_num -class UpgradeGarden041(rose.upgrade.MacroUpgrade): +class UpgradeGarden041(metomi.rose.upgrade.MacroUpgrade): """'...the two-level effect with a little path running down the middle'""" @@ -95,7 +95,7 @@ def upgrade(self, config, meta_config=None): return config, self.reports -class UpgradeGarden09(rose.upgrade.MacroUpgrade): +class UpgradeGarden09(metomi.rose.upgrade.MacroUpgrade): """'cut down the mightiest tree in the forest... with... a herring!'""" diff --git a/pytest.ini b/pytest.ini index 520187d2d8..94c8001f4c 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,12 +1,15 @@ [pytest] -addopts = --verbose +addopts = + --verbose --doctest-modules --ignore=metomi/rosie --ignore=metomi/rose/ws.py --ignore=metomi/rose/metadata_graph.py + # these cause import issues + --ignore=metomi/rose/etc/ # these tests do IO, don't run them under sphinx-build rather than pytest: --ignore=metomi/rose/config.py --ignore=metomi/rose/macro.py testpaths = - metomi/rose/tests/* + metomi/ sphinx/ From 17968dac1d30c6ac9b2a17afa2528ac237bbcbf6 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Mon, 4 Jan 2021 17:58:08 +0000 Subject: [PATCH 19/78] namelist: fix regex deprecation warnings * inline flags are now deprecated * https://bugs.python.org/issue39394 --- metomi/rose/formats/namelist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metomi/rose/formats/namelist.py b/metomi/rose/formats/namelist.py index 45b220a561..46fa2a99ef 100644 --- a/metomi/rose/formats/namelist.py +++ b/metomi/rose/formats/namelist.py @@ -38,11 +38,11 @@ def _rec(exp): # Matches namelist literals for intrinsic types RE_INTEGER = r"[\+\-]?(?:" + RE_NATURAL + r")" REC_INTEGER = _rec(r"\A(?:" + RE_INTEGER + r")\Z") -RE_REAL = r"(?i)[\+\-]?(?:" + RE_FLOAT + r")(?:[de][\+\-]?\d+)?" +RE_REAL = r"[\+\-]?(?:" + RE_FLOAT + r")(?:[deDE][\+\-]?\d+)?" REC_REAL = _rec(r"\A(?:" + RE_REAL + r")\Z") RE_COMPLEX = r"\(\s*" + RE_REAL + r"\s*" + RE_SEP + r"\s*" + RE_REAL + r"\s*\)" REC_COMPLEX = _rec(r"\A(?:" + RE_COMPLEX + r")\Z") -RE_LOGICAL = r"(?i)\.(?:true|false)\." +RE_LOGICAL = r"\.(?:[Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])\." REC_LOGICAL = _rec(r"\A(?:" + RE_LOGICAL + r")\Z") RE_CHARACTER = r"'(?:[^']|'')*'|\"(?:[^\"]|\"\")*\"" REC_CHARACTER = _rec(r"\A(?:" + RE_CHARACTER + r")\Z") From 0bf388d717d0b0afdb518524ee0c9719fd4b3055 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Mon, 4 Jan 2021 18:09:12 +0000 Subject: [PATCH 20/78] /bin/bash => /usr/bin/env bash --- bin/rose-tutorial | 2 +- etc/deploy-docs | 2 +- lib/bash/rose_init | 2 +- lib/bash/rose_init_site.example | 2 +- lib/bash/rose_usage | 2 +- sbin/rosa-rpmbuild | 2 +- sphinx/installation.rst | 4 ++-- sphinx/tutorial/cylc/runtime/introduction.rst | 2 +- sphinx/tutorial/rose/furthertopics/rose-bunch.rst | 2 +- t/docs/01-doctest.t | 2 +- t/docs/02-tutorial-suites.t | 2 +- t/lib/bash/test_header | 2 +- t/rosa-db-create/00-basic.t | 4 ++-- t/rosa-svn-post-commit/00-basic.t | 4 ++-- t/rosa-svn-post-commit/01-mail-passwd.t | 4 ++-- t/rosa-svn-post-commit/03-unicode.t | 4 ++-- t/rosa-svn-pre-commit/00-basic.t | 4 ++-- t/rosa-svn-pre-commit/01-rosie-create.t | 4 ++-- t/rosa-svn-pre-commit/02-passwd.t | 4 ++-- t/rosa-svn-pre-commit/03-meta.t | 4 ++-- t/rose-ana/00-run-basic.t | 2 +- t/rose-ana/01-run-basic-v1.t | 2 +- t/rose-app-run/00-null.t | 2 +- t/rose-app-run/01-basic.t | 2 +- t/rose-app-run/02-command.t | 4 ++-- t/rose-app-run/03-env.t | 2 +- t/rose-app-run/04-stdin.t | 2 +- t/rose-app-run/05-file.t | 2 +- t/rose-app-run/06-namelist.t | 2 +- t/rose-app-run/07-opt.t | 2 +- t/rose-app-run/08-poll.t | 2 +- t/rose-app-run/09-file-incr-0.t | 2 +- t/rose-app-run/10-file-incr-1.t | 2 +- t/rose-app-run/11-file-incr-2.t | 2 +- t/rose-app-run/12-file-incr-3.t | 2 +- t/rose-app-run/13-file-source-tilde.t | 2 +- t/rose-app-run/14-file-source-optional.t | 2 +- t/rose-app-run/15-file-permission.t | 2 +- t/rose-app-run/16-file-mode-bad.t | 2 +- t/rose-app-run/17-file-db-bad.t | 2 +- t/rose-app-run/18-access-mode-change.t | 4 ++-- t/rose-app-run/19-file-unchanged.t | 2 +- t/rose-app-run/20-file-symlink-no-source.t | 2 +- t/rose-app-run/21-run-config-load-event.t | 2 +- t/rose-app-run/22-opt-conf-key-missing.t | 2 +- t/rose-app-run/23-file-both.t | 2 +- t/rose-app-run/24-app-unicode.t | 2 +- t/rose-app-run/25-file-symlink-plus.t | 2 +- t/rose-app-run/26-define-overrides.t | 2 +- t/rose-app-run/test_header | 2 +- t/rose-app-upgrade/00-null.t | 2 +- t/rose-app-upgrade/01-upgrade-basic.t | 2 +- t/rose-app-upgrade/02-downgrade-basic.t | 2 +- t/rose-app-upgrade/03-complex.t | 2 +- t/rose-app-upgrade/04-upgrade-trigger.t | 2 +- t/rose-app-upgrade/05-cwd.t | 2 +- t/rose-app-upgrade/06-broken.t | 2 +- t/rose-app-upgrade/07-add-existing.t | 2 +- t/rose-app-upgrade/08-category-is-package.t | 2 +- t/rose-app-upgrade/09-opt.t | 2 +- t/rose-app-upgrade/10-prettify-trigger-bug.t | 2 +- t/rose-app-upgrade/11-upgrade-basic-tree.t | 2 +- t/rose-app-upgrade/12-downgrade-basic-tree.t | 2 +- t/rose-app-upgrade/13-bad-upgrade-macro.t | 2 +- t/rose-app-upgrade/test_header | 2 +- t/rose-config-diff/00-basic.t | 2 +- t/rose-config-diff/test_header | 2 +- t/rose-config-dump/00-basic.t | 2 +- t/rose-config-dump/01-indices.t | 2 +- t/rose-config-dump/02-files.t | 2 +- t/rose-config-dump/03-prettyprint.t | 2 +- t/rose-config-dump/test_header | 2 +- t/rose-config/00-basic.t | 2 +- t/rose-config/01-opt.t | 2 +- t/rose-config/03-syntax-error.t | 2 +- t/rose-config/04-meta.t | 2 +- t/rose-config/05-opt-opt.t | 2 +- t/rose-config/test_header | 2 +- t/rose-env-cat/00-null.t | 2 +- t/rose-env-cat/01-basic.t | 2 +- t/rose-env-cat/02-braced.t | 2 +- t/rose-env-cat/test_header | 2 +- t/rose-help/00-alias.t | 2 +- t/rose-host-select/00-basic.t | 2 +- t/rose-host-select/01-timeout.t | 2 +- t/rose-host-select/01-timeout/bin/mock-ssh | 2 +- t/rose-host-select/02-localhost.t | 2 +- t/rose-macro/00-null.t | 2 +- t/rose-macro/01-value-type-check-ok.t | 2 +- t/rose-macro/02-value-type-check-err.t | 2 +- t/rose-macro/03-value-range-check-ok.t | 2 +- t/rose-macro/04-value-range-check-err.t | 2 +- t/rose-macro/05-value-values-check.t | 2 +- t/rose-macro/06-value-pattern-check.t | 2 +- t/rose-macro/07-value-change.t | 2 +- t/rose-macro/08-compulsory-check.t | 2 +- t/rose-macro/09-rule-check.t | 2 +- t/rose-macro/10-trigger-check.t | 2 +- t/rose-macro/11-trigger-change.t | 2 +- t/rose-macro/12-custom-change.t | 2 +- t/rose-macro/13-custom-check.t | 2 +- t/rose-macro/14-duplicate-check.t | 2 +- t/rose-macro/15-bad-custom-change.t | 2 +- t/rose-macro/16-cwd.t | 2 +- t/rose-macro/17-import.t | 2 +- t/rose-macro/18-opt-conf-check.t | 2 +- t/rose-macro/19-opt-conf-change-add.t | 2 +- t/rose-macro/20-opt-conf-change-modify.t | 2 +- t/rose-macro/21-opt-conf-change-remove.t | 2 +- t/rose-macro/22-repeats.t | 2 +- t/rose-macro/23-import-tree.t | 2 +- t/rose-macro/24-optional-config-name.t | 2 +- t/rose-macro/25-suite-level.t | 2 +- t/rose-macro/26-optional-config-name-2.t | 2 +- t/rose-macro/test_header | 2 +- t/rose-metadata-check/00-null.t | 2 +- t/rose-metadata-check/01-type-length.t | 2 +- t/rose-metadata-check/02-range.t | 2 +- t/rose-metadata-check/03-values.t | 2 +- t/rose-metadata-check/04-pattern.t | 2 +- t/rose-metadata-check/05-compulsory.t | 2 +- t/rose-metadata-check/06-rule.t | 2 +- t/rose-metadata-check/07-trigger.t | 2 +- t/rose-metadata-check/08-duplicate.t | 2 +- t/rose-metadata-check/09-custom-macro.t | 2 +- t/rose-metadata-check/11-invalid-settings.t | 2 +- t/rose-metadata-check/test_header | 2 +- t/rose-metadata-gen/00-null.t | 2 +- t/rose-metadata-gen/01-basic.t | 2 +- t/rose-metadata-gen/02-autotype.t | 2 +- t/rose-metadata-gen/03-propset.t | 2 +- t/rose-metadata-gen/test_header | 2 +- t/rose-mpi-launch/00-command.t | 2 +- t/rose-mpi-launch/01-command-inner.t | 2 +- t/rose-mpi-launch/02-file.t | 2 +- t/rose-mpi-launch/test_header | 2 +- t/rose-namelist-dump/00-null.t | 2 +- t/rose-namelist-dump/01-empty.t | 2 +- t/rose-namelist-dump/02-type.t | 2 +- t/rose-namelist-dump/test_header | 2 +- t/rose-task-env/00-non-cycle.t | 2 +- t/rose-task-env/01-integer-cycling.t | 2 +- t/rose-task-env/02-360day-cycling.t | 2 +- t/rose-task-run/00-run-basic.t | 2 +- t/rose-task-run/01-run-basic-iso.t | 2 +- t/rose-task-run/04-run-path-empty.t | 2 +- t/rose-task-run/06-app-prune-iso.t | 2 +- t/rose-task-run/07-app-arch.t | 2 +- .../07-app-arch/app/archive_bad_9/bin/my-bad-command | 2 +- t/rose-task-run/07-app-arch/app/install/bin/my-install | 2 +- t/rose-task-run/07-app-arch/bin/foo | 2 +- t/rose-task-run/08-app-fcm-make.t | 2 +- t/rose-task-run/09-app-prune-work.t | 2 +- t/rose-task-run/10-specify-cycle.t | 2 +- t/rose-task-run/11-specify-cycle-iso.t | 2 +- t/rose-task-run/12-app-prune-integer.t | 2 +- t/rose-task-run/13-app-arch-cmd-out.t | 2 +- t/rose-task-run/13-app-arch-cmd-out/bin/foo | 2 +- t/rose-task-run/14-app-prune-remove.t | 2 +- t/rose-task-run/15-app-prune-extglob.t | 2 +- t/rose-task-run/16-app-prune-point.t | 2 +- t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t | 2 +- t/rose-task-run/18-app-fcm-make-ctx-name.t | 2 +- t/rose-task-run/20-app-fcm-make-dest.t | 2 +- t/rose-task-run/24-app-fcm-make-fast.t | 2 +- t/rose-task-run/25-app-fcm-make-new-mode.t | 2 +- t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t | 2 +- t/rose-task-run/28-env-path-run-path.t | 2 +- t/rose-task-run/28-env-path-run-path/opt/earth/bin/hello | 2 +- t/rose-task-run/28-env-path-run-path/opt/world/bin/hello | 2 +- t/rose-task-run/29-app-prune-extglob-remote.t | 2 +- t/rose-task-run/30-app-arch-opt-source.t | 2 +- t/rose-task-run/30-app-arch-opt-source/bin/foo | 2 +- t/rose-task-run/31-app-bunch.t | 2 +- t/rose-task-run/32-app-arch-compressed.t | 2 +- t/rose-task-run/33-app-prune-cycle-format.t | 2 +- t/rose-task-run/34-app-prune-hosts-sharing-fs.t | 2 +- t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t | 2 +- t/rose-task-run/36-app-arch-interrupted.t | 2 +- t/rose-task-run/37-app-bunch-rm-old.t | 2 +- t/rose-task-run/38-app-bunch-counts.t | 2 +- t/rose-task-run/39-app-prune-cycle-host.t | 2 +- t/rose-task-run/40-app-arch-duplicate.t | 2 +- t/rose-task-run/41-app-bunch-default-command.t | 2 +- t/rose.env/00-export.t | 2 +- t/rose.popen/00-self.t | 2 +- t/rose.variable/00-trigger.t | 2 +- t/rose.variable/01-array-split.t | 2 +- t/rose.variable/test_header | 2 +- t/rosie-checkout/00-basic.t | 2 +- t/rosie-create/00-basic-edit | 2 +- t/rosie-create/00-basic.t | 2 +- t/rosie-create/01-suite-info-meta.t | 2 +- t/rosie-create/02-copy-inter.t | 2 +- t/rosie-delete/00-basic.t | 2 +- t/rosie-disco/00-basic.t | 2 +- t/rosie-graph/00-basic.t | 4 ++-- t/rosie-graph/test_header_extra | 2 +- t/rosie-hello/00-null.t | 2 +- t/rosie-id/00-basic.t | 2 +- t/rosie-id/01-final.t | 2 +- t/rosie-lookup/00-basic.t | 4 ++-- t/rosie-lookup/01-multi.t | 2 +- t/rosie-lookup/02-unicode.t | 2 +- t/rosie-ls/00-basic.t | 2 +- t/rosie-ls/01-many.t | 2 +- t/rosie.db/00-query.t | 2 +- usr/bin/rose | 2 +- 208 files changed, 221 insertions(+), 221 deletions(-) diff --git a/bin/rose-tutorial b/bin/rose-tutorial index 7a41346187..f71386eed7 100755 --- a/bin/rose-tutorial +++ b/bin/rose-tutorial @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/etc/deploy-docs b/etc/deploy-docs index 26deecfabf..1392c083c9 100755 --- a/etc/deploy-docs +++ b/etc/deploy-docs @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/lib/bash/rose_init b/lib/bash/rose_init index e59bdcb193..c30d44f835 100755 --- a/lib/bash/rose_init +++ b/lib/bash/rose_init @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/lib/bash/rose_init_site.example b/lib/bash/rose_init_site.example index a569cb26b6..fa0ba1e2d7 100644 --- a/lib/bash/rose_init_site.example +++ b/lib/bash/rose_init_site.example @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/lib/bash/rose_usage b/lib/bash/rose_usage index 47e1640f4b..864ee32c95 100644 --- a/lib/bash/rose_usage +++ b/lib/bash/rose_usage @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/sbin/rosa-rpmbuild b/sbin/rosa-rpmbuild index 277f535e17..87b77a2ac9 100755 --- a/sbin/rosa-rpmbuild +++ b/sbin/rosa-rpmbuild @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/sphinx/installation.rst b/sphinx/installation.rst index bb3ee1603e..3994296828 100644 --- a/sphinx/installation.rst +++ b/sphinx/installation.rst @@ -271,14 +271,14 @@ Add the following hook scripts to the repository: .. code-block:: sub - #!/bin/bash + #!/usr/bin/env bash exec /sbin/rosa svn-pre-commit "$@" * post-commit: .. code-block:: sub - #!/bin/bash + #!/usr/bin/env bash exec /sbin/rosa svn-post-commit "$@" You should replace ``/path/to/rose/`` with the location of your Rose diff --git a/sphinx/tutorial/cylc/runtime/introduction.rst b/sphinx/tutorial/cylc/runtime/introduction.rst index 7e5541c8f8..462ccbf38a 100644 --- a/sphinx/tutorial/cylc/runtime/introduction.rst +++ b/sphinx/tutorial/cylc/runtime/introduction.rst @@ -88,7 +88,7 @@ We can also call other scripts or executables in this way, e.g: .. code-block:: bash :caption: bin/hello_world - #!/usr/bin/bash + #!/usr/usr/bin/env bash echo 'Hello World!' .. code-block:: cylc diff --git a/sphinx/tutorial/rose/furthertopics/rose-bunch.rst b/sphinx/tutorial/rose/furthertopics/rose-bunch.rst index 54ea5e10ef..17fb0caa9d 100644 --- a/sphinx/tutorial/rose/furthertopics/rose-bunch.rst +++ b/sphinx/tutorial/rose/furthertopics/rose-bunch.rst @@ -99,7 +99,7 @@ In the ``app/lander/`` directory create a ``bin/`` directory:: Using your editor of choice, create a file named ``land`` under the ``bin`` directory and paste in these lines:: - #!/bin/bash + #!/usr/bin/env bash CLASS=$1 PASSENGERS=$2 diff --git a/t/docs/01-doctest.t b/t/docs/01-doctest.t index 50240744b2..56255ac1c3 100644 --- a/t/docs/01-doctest.t +++ b/t/docs/01-doctest.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/docs/02-tutorial-suites.t b/t/docs/02-tutorial-suites.t index d86f0709a4..62112c5990 100644 --- a/t/docs/02-tutorial-suites.t +++ b/t/docs/02-tutorial-suites.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/lib/bash/test_header b/t/lib/bash/test_header index 62ac9fd810..ac8cd65baa 100644 --- a/t/lib/bash/test_header +++ b/t/lib/bash/test_header @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rosa-db-create/00-basic.t b/t/rosa-db-create/00-basic.t index 1f601c28b0..fe5d49afb1 100755 --- a/t/rosa-db-create/00-basic.t +++ b/t/rosa-db-create/00-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # @@ -45,7 +45,7 @@ prefix-location.foo=$SVN_URL __ROSE_CONF__ export ROSE_CONF_PATH=$PWD/conf cat >repos/foo/hooks/post-commit <<__POST_COMMIT__ -#!/bin/bash +#!/usr/bin/env bash export ROSE_CONF_PATH=$ROSE_CONF_PATH rosa svn-post-commit --debug "\$@" \\ 1>$PWD/rosa-svn-post-commit.out 2>$PWD/rosa-svn-post-commit.err diff --git a/t/rosa-svn-post-commit/00-basic.t b/t/rosa-svn-post-commit/00-basic.t index b929cd9801..0e6d8a8e94 100755 --- a/t/rosa-svn-post-commit/00-basic.t +++ b/t/rosa-svn-post-commit/00-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # @@ -44,7 +44,7 @@ __ROSE_CONF__ export ROSE_CONF_PATH=$PWD/conf ROSE_BIN_HOME=$(dirname $(command -v rose)) cat >repos/foo/hooks/post-commit <<__POST_COMMIT__ -#!/bin/bash +#!/usr/bin/env bash export ROSE_CONF_PATH=${ROSE_CONF_PATH} export PATH=$PATH:${ROSE_BIN_HOME} rosa svn-post-commit --debug "\$@" \\ diff --git a/t/rosa-svn-post-commit/01-mail-passwd.t b/t/rosa-svn-post-commit/01-mail-passwd.t index de2fc409dc..4cff92d100 100755 --- a/t/rosa-svn-post-commit/01-mail-passwd.t +++ b/t/rosa-svn-post-commit/01-mail-passwd.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # @@ -73,7 +73,7 @@ export ROSE_CONF_PATH=$PWD/conf ROSE_BIN_HOME=$(dirname $(command -v rose)) cat >repos/foo/hooks/post-commit <<__POST_COMMIT__ -#!/bin/bash +#!/usr/bin/env bash export ROSE_CONF_PATH=$ROSE_CONF_PATH export PATH=$PATH:${ROSE_BIN_HOME} rosa svn-post-commit --debug "\$@" \\ diff --git a/t/rosa-svn-post-commit/03-unicode.t b/t/rosa-svn-post-commit/03-unicode.t index 653be518c6..4a97223e08 100755 --- a/t/rosa-svn-post-commit/03-unicode.t +++ b/t/rosa-svn-post-commit/03-unicode.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # @@ -45,7 +45,7 @@ __ROSE_CONF__ export ROSE_CONF_PATH="${PWD}/conf" ROSE_BIN_HOME=$(dirname $(command -v rose)) cat >'repos/foo/hooks/post-commit' <<__POST_COMMIT__ -#!/bin/bash +#!/usr/bin/env bash export ROSE_CONF_PATH="${ROSE_CONF_PATH}" export PATH=$PATH:${ROSE_BIN_HOME} rosa svn-post-commit --debug "\$@" \\ diff --git a/t/rosa-svn-pre-commit/00-basic.t b/t/rosa-svn-pre-commit/00-basic.t index 0923920d2e..3172038ca0 100755 --- a/t/rosa-svn-pre-commit/00-basic.t +++ b/t/rosa-svn-pre-commit/00-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # @@ -37,7 +37,7 @@ ROSE_BIN=$(dirname $(command -v rose)) ROSE_LIB=$(dirname $(python3 -c "import metomi.rose; print(metomi.rose.__file__)")) export ROSE_LIB ROSE_BIN cat >repos/foo/hooks/pre-commit <<__PRE_COMMIT__ -#!/bin/bash +#!/usr/bin/env bash export ROSE_CONF_PATH=$PWD/conf export PATH=$PATH:${ROSE_BIN} export ROSE_LIB=${ROSE_LIB} diff --git a/t/rosa-svn-pre-commit/01-rosie-create.t b/t/rosa-svn-pre-commit/01-rosie-create.t index 98849dcba8..8680a6fbcd 100755 --- a/t/rosa-svn-pre-commit/01-rosie-create.t +++ b/t/rosa-svn-pre-commit/01-rosie-create.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # @@ -39,7 +39,7 @@ ROSE_BIN_HOME=$(dirname $(command -v rose)) ROSE_LIB=$(dirname $(python3 -c "import metomi.rose; print(metomi.rose.__file__)")) export ROSE_CONF_PATH=$PWD/conf cat >repos/foo/hooks/pre-commit <<__PRE_COMMIT__ -#!/bin/bash +#!/usr/bin/env bash export ROSE_CONF_PATH=$ROSE_CONF_PATH export PATH=$PATH:${ROSE_BIN_HOME} export ROSE_LIB="${ROSE_LIB}" diff --git a/t/rosa-svn-pre-commit/02-passwd.t b/t/rosa-svn-pre-commit/02-passwd.t index 0750a15a41..0423c225bf 100755 --- a/t/rosa-svn-pre-commit/02-passwd.t +++ b/t/rosa-svn-pre-commit/02-passwd.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # @@ -37,7 +37,7 @@ SVN_URL=file://$PWD/repos/foo ROSE_BIN_HOME=$(dirname $(command -v rose)) ROSE_LIB=$(dirname $(python3 -c "import metomi.rose; print(metomi.rose.__file__)")) cat >repos/foo/hooks/pre-commit <<__PRE_COMMIT__ -#!/bin/bash +#!/usr/bin/env bash export ROSE_CONF_PATH=$PWD/conf export PATH=$PATH:${ROSE_BIN_HOME} export ROSE_LIB="${ROSE_LIB}" diff --git a/t/rosa-svn-pre-commit/03-meta.t b/t/rosa-svn-pre-commit/03-meta.t index 11a909b2b7..6c7b68edcc 100755 --- a/t/rosa-svn-pre-commit/03-meta.t +++ b/t/rosa-svn-pre-commit/03-meta.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # @@ -43,7 +43,7 @@ SVN_URL="file://${PWD}/repos/foo" ROSE_BIN_HOME=$(dirname $(command -v rose)) ROSE_LIB=$(dirname $(python3 -c "import metomi.rose; print(metomi.rose.__file__)")) cat >'repos/foo/hooks/pre-commit' <<__PRE_COMMIT__ -#!/bin/bash +#!/usr/bin/env bash export ROSE_CONF_PATH=${PWD}/conf export PATH=$PATH:${ROSE_BIN_HOME} export ROSE_LIB="${ROSE_LIB}" diff --git a/t/rose-ana/00-run-basic.t b/t/rose-ana/00-run-basic.t index a15798b8e6..e24e8309ea 100644 --- a/t/rose-ana/00-run-basic.t +++ b/t/rose-ana/00-run-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-ana/01-run-basic-v1.t b/t/rose-ana/01-run-basic-v1.t index c1233a57db..dd4d870dda 100644 --- a/t/rose-ana/01-run-basic-v1.t +++ b/t/rose-ana/01-run-basic-v1.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/00-null.t b/t/rose-app-run/00-null.t index 2fbcb5595f..ccd5f95a79 100755 --- a/t/rose-app-run/00-null.t +++ b/t/rose-app-run/00-null.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/01-basic.t b/t/rose-app-run/01-basic.t index 8c2fb4d9cf..6010bb36e6 100755 --- a/t/rose-app-run/01-basic.t +++ b/t/rose-app-run/01-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/02-command.t b/t/rose-app-run/02-command.t index b1b68fbf7d..1e6050666a 100755 --- a/t/rose-app-run/02-command.t +++ b/t/rose-app-run/02-command.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # @@ -31,7 +31,7 @@ hello-from-env = hello run by env __CONFIG__ mkdir $PWD/config/bin cat > $PWD/config/bin/hello <<'__SCRIPT__' -#!/bin/bash +#!/usr/bin/env bash echo "Hello ${@:-world}!" __SCRIPT__ chmod +x $PWD/config/bin/hello diff --git a/t/rose-app-run/03-env.t b/t/rose-app-run/03-env.t index 1375159a1e..234af9aadf 100755 --- a/t/rose-app-run/03-env.t +++ b/t/rose-app-run/03-env.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/04-stdin.t b/t/rose-app-run/04-stdin.t index 8819c84c73..9ecdb6a0f4 100755 --- a/t/rose-app-run/04-stdin.t +++ b/t/rose-app-run/04-stdin.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/05-file.t b/t/rose-app-run/05-file.t index 56f744cf7d..f1fa0d51af 100755 --- a/t/rose-app-run/05-file.t +++ b/t/rose-app-run/05-file.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/06-namelist.t b/t/rose-app-run/06-namelist.t index 59d9ab081c..d98c53cc54 100755 --- a/t/rose-app-run/06-namelist.t +++ b/t/rose-app-run/06-namelist.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/07-opt.t b/t/rose-app-run/07-opt.t index 641e0dd5f2..29faaf3028 100755 --- a/t/rose-app-run/07-opt.t +++ b/t/rose-app-run/07-opt.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/08-poll.t b/t/rose-app-run/08-poll.t index 921b76d7bc..14e53abe43 100755 --- a/t/rose-app-run/08-poll.t +++ b/t/rose-app-run/08-poll.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/09-file-incr-0.t b/t/rose-app-run/09-file-incr-0.t index c915409800..c0f4164e65 100755 --- a/t/rose-app-run/09-file-incr-0.t +++ b/t/rose-app-run/09-file-incr-0.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/10-file-incr-1.t b/t/rose-app-run/10-file-incr-1.t index 21164e49cb..74f83df4d8 100755 --- a/t/rose-app-run/10-file-incr-1.t +++ b/t/rose-app-run/10-file-incr-1.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/11-file-incr-2.t b/t/rose-app-run/11-file-incr-2.t index bd040726b3..3c83efff4f 100755 --- a/t/rose-app-run/11-file-incr-2.t +++ b/t/rose-app-run/11-file-incr-2.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/12-file-incr-3.t b/t/rose-app-run/12-file-incr-3.t index bdad378f34..3edf08eda2 100755 --- a/t/rose-app-run/12-file-incr-3.t +++ b/t/rose-app-run/12-file-incr-3.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/13-file-source-tilde.t b/t/rose-app-run/13-file-source-tilde.t index f14f9634d1..b328e5ed74 100755 --- a/t/rose-app-run/13-file-source-tilde.t +++ b/t/rose-app-run/13-file-source-tilde.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/14-file-source-optional.t b/t/rose-app-run/14-file-source-optional.t index 27b9ae8fd1..de8ae2857e 100755 --- a/t/rose-app-run/14-file-source-optional.t +++ b/t/rose-app-run/14-file-source-optional.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/15-file-permission.t b/t/rose-app-run/15-file-permission.t index 87588397d7..4eec25df91 100755 --- a/t/rose-app-run/15-file-permission.t +++ b/t/rose-app-run/15-file-permission.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/16-file-mode-bad.t b/t/rose-app-run/16-file-mode-bad.t index 60d3f960b8..b71dd3d136 100755 --- a/t/rose-app-run/16-file-mode-bad.t +++ b/t/rose-app-run/16-file-mode-bad.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/17-file-db-bad.t b/t/rose-app-run/17-file-db-bad.t index 0bc5e5017f..c5540d5730 100755 --- a/t/rose-app-run/17-file-db-bad.t +++ b/t/rose-app-run/17-file-db-bad.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/18-access-mode-change.t b/t/rose-app-run/18-access-mode-change.t index add74e7125..30db67c8f9 100644 --- a/t/rose-app-run/18-access-mode-change.t +++ b/t/rose-app-run/18-access-mode-change.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # @@ -33,7 +33,7 @@ __CONFIG__ mkdir "$TEST_DIR/bin" cat >"$TEST_DIR/bin/egg.sh" <<'__BASH__' -#!/bin/bash +#!/usr/bin/env bash echo 'Bash it, crack it, bin it.' __BASH__ diff --git a/t/rose-app-run/19-file-unchanged.t b/t/rose-app-run/19-file-unchanged.t index 8aff0a61db..201909a921 100755 --- a/t/rose-app-run/19-file-unchanged.t +++ b/t/rose-app-run/19-file-unchanged.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/20-file-symlink-no-source.t b/t/rose-app-run/20-file-symlink-no-source.t index 835fb39714..f14658aaa0 100755 --- a/t/rose-app-run/20-file-symlink-no-source.t +++ b/t/rose-app-run/20-file-symlink-no-source.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/21-run-config-load-event.t b/t/rose-app-run/21-run-config-load-event.t index 6e4ff6483a..a9790a85b8 100755 --- a/t/rose-app-run/21-run-config-load-event.t +++ b/t/rose-app-run/21-run-config-load-event.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/22-opt-conf-key-missing.t b/t/rose-app-run/22-opt-conf-key-missing.t index d7da502edf..c8b6f839cc 100755 --- a/t/rose-app-run/22-opt-conf-key-missing.t +++ b/t/rose-app-run/22-opt-conf-key-missing.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/23-file-both.t b/t/rose-app-run/23-file-both.t index 742d7c6c99..04aff3999b 100755 --- a/t/rose-app-run/23-file-both.t +++ b/t/rose-app-run/23-file-both.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/24-app-unicode.t b/t/rose-app-run/24-app-unicode.t index f31a33c280..03134d018d 100755 --- a/t/rose-app-run/24-app-unicode.t +++ b/t/rose-app-run/24-app-unicode.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/25-file-symlink-plus.t b/t/rose-app-run/25-file-symlink-plus.t index 76370adc3a..09fb288d76 100755 --- a/t/rose-app-run/25-file-symlink-plus.t +++ b/t/rose-app-run/25-file-symlink-plus.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/26-define-overrides.t b/t/rose-app-run/26-define-overrides.t index 03e7cb3a26..e6b7be424e 100644 --- a/t/rose-app-run/26-define-overrides.t +++ b/t/rose-app-run/26-define-overrides.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-run/test_header b/t/rose-app-run/test_header index 4ed5fab271..a6dc7f1efc 100644 --- a/t/rose-app-run/test_header +++ b/t/rose-app-run/test_header @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-upgrade/00-null.t b/t/rose-app-upgrade/00-null.t index 6a230b3b73..7c0c97db53 100755 --- a/t/rose-app-upgrade/00-null.t +++ b/t/rose-app-upgrade/00-null.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-upgrade/01-upgrade-basic.t b/t/rose-app-upgrade/01-upgrade-basic.t index b91d1999f3..1968035b1a 100755 --- a/t/rose-app-upgrade/01-upgrade-basic.t +++ b/t/rose-app-upgrade/01-upgrade-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-upgrade/02-downgrade-basic.t b/t/rose-app-upgrade/02-downgrade-basic.t index 54bb91794e..de1b794c22 100755 --- a/t/rose-app-upgrade/02-downgrade-basic.t +++ b/t/rose-app-upgrade/02-downgrade-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-upgrade/03-complex.t b/t/rose-app-upgrade/03-complex.t index f8baabd7ab..955593fad8 100755 --- a/t/rose-app-upgrade/03-complex.t +++ b/t/rose-app-upgrade/03-complex.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-upgrade/04-upgrade-trigger.t b/t/rose-app-upgrade/04-upgrade-trigger.t index 83e28f68f0..b3cab24c24 100755 --- a/t/rose-app-upgrade/04-upgrade-trigger.t +++ b/t/rose-app-upgrade/04-upgrade-trigger.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-upgrade/05-cwd.t b/t/rose-app-upgrade/05-cwd.t index 4f0d61a13f..aecca5af16 100755 --- a/t/rose-app-upgrade/05-cwd.t +++ b/t/rose-app-upgrade/05-cwd.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-upgrade/06-broken.t b/t/rose-app-upgrade/06-broken.t index 6f17e58d90..4ce1dd7fad 100755 --- a/t/rose-app-upgrade/06-broken.t +++ b/t/rose-app-upgrade/06-broken.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-upgrade/07-add-existing.t b/t/rose-app-upgrade/07-add-existing.t index 3d7f58d5a2..eca3ffe5ba 100755 --- a/t/rose-app-upgrade/07-add-existing.t +++ b/t/rose-app-upgrade/07-add-existing.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-upgrade/08-category-is-package.t b/t/rose-app-upgrade/08-category-is-package.t index 457c8c0f5b..9980cf1468 100755 --- a/t/rose-app-upgrade/08-category-is-package.t +++ b/t/rose-app-upgrade/08-category-is-package.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-upgrade/09-opt.t b/t/rose-app-upgrade/09-opt.t index f2edae6be2..032ec94e4c 100755 --- a/t/rose-app-upgrade/09-opt.t +++ b/t/rose-app-upgrade/09-opt.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-upgrade/10-prettify-trigger-bug.t b/t/rose-app-upgrade/10-prettify-trigger-bug.t index 5582146e0a..c530dbec13 100755 --- a/t/rose-app-upgrade/10-prettify-trigger-bug.t +++ b/t/rose-app-upgrade/10-prettify-trigger-bug.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-upgrade/11-upgrade-basic-tree.t b/t/rose-app-upgrade/11-upgrade-basic-tree.t index 623a91f962..266bd5fdf8 100755 --- a/t/rose-app-upgrade/11-upgrade-basic-tree.t +++ b/t/rose-app-upgrade/11-upgrade-basic-tree.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-upgrade/12-downgrade-basic-tree.t b/t/rose-app-upgrade/12-downgrade-basic-tree.t index 923caa674c..03431792cd 100755 --- a/t/rose-app-upgrade/12-downgrade-basic-tree.t +++ b/t/rose-app-upgrade/12-downgrade-basic-tree.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-upgrade/13-bad-upgrade-macro.t b/t/rose-app-upgrade/13-bad-upgrade-macro.t index 502f921c53..4017b4bbf7 100755 --- a/t/rose-app-upgrade/13-bad-upgrade-macro.t +++ b/t/rose-app-upgrade/13-bad-upgrade-macro.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-app-upgrade/test_header b/t/rose-app-upgrade/test_header index 23bd11cf5a..bafb1f8185 100644 --- a/t/rose-app-upgrade/test_header +++ b/t/rose-app-upgrade/test_header @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-config-diff/00-basic.t b/t/rose-config-diff/00-basic.t index f8dc251168..40c19d022a 100755 --- a/t/rose-config-diff/00-basic.t +++ b/t/rose-config-diff/00-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-config-diff/test_header b/t/rose-config-diff/test_header index 7b7c101ba6..21ab8e0f0f 100644 --- a/t/rose-config-diff/test_header +++ b/t/rose-config-diff/test_header @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-config-dump/00-basic.t b/t/rose-config-dump/00-basic.t index 2f38d062ab..efc093aba3 100755 --- a/t/rose-config-dump/00-basic.t +++ b/t/rose-config-dump/00-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-config-dump/01-indices.t b/t/rose-config-dump/01-indices.t index 91c7cac1ae..d4181ab135 100755 --- a/t/rose-config-dump/01-indices.t +++ b/t/rose-config-dump/01-indices.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-config-dump/02-files.t b/t/rose-config-dump/02-files.t index 5953342e4d..3493efb28f 100755 --- a/t/rose-config-dump/02-files.t +++ b/t/rose-config-dump/02-files.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-config-dump/03-prettyprint.t b/t/rose-config-dump/03-prettyprint.t index 0764c9c568..af48987ac4 100644 --- a/t/rose-config-dump/03-prettyprint.t +++ b/t/rose-config-dump/03-prettyprint.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-config-dump/test_header b/t/rose-config-dump/test_header index fa2e8f0a67..9cff165e94 100644 --- a/t/rose-config-dump/test_header +++ b/t/rose-config-dump/test_header @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-config/00-basic.t b/t/rose-config/00-basic.t index 5fa7514ab6..4b837fc158 100755 --- a/t/rose-config/00-basic.t +++ b/t/rose-config/00-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-config/01-opt.t b/t/rose-config/01-opt.t index ad68a5f30d..3ab4aea474 100755 --- a/t/rose-config/01-opt.t +++ b/t/rose-config/01-opt.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-config/03-syntax-error.t b/t/rose-config/03-syntax-error.t index 66803e7b5e..9cfdf0557e 100755 --- a/t/rose-config/03-syntax-error.t +++ b/t/rose-config/03-syntax-error.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-config/04-meta.t b/t/rose-config/04-meta.t index 4804f2d01b..2bbab14476 100755 --- a/t/rose-config/04-meta.t +++ b/t/rose-config/04-meta.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-config/05-opt-opt.t b/t/rose-config/05-opt-opt.t index c3c5e66e89..1d886aec2b 100755 --- a/t/rose-config/05-opt-opt.t +++ b/t/rose-config/05-opt-opt.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-config/test_header b/t/rose-config/test_header index fa2e8f0a67..9cff165e94 100644 --- a/t/rose-config/test_header +++ b/t/rose-config/test_header @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-env-cat/00-null.t b/t/rose-env-cat/00-null.t index d5c3c25fb4..2965ddfc76 100755 --- a/t/rose-env-cat/00-null.t +++ b/t/rose-env-cat/00-null.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-env-cat/01-basic.t b/t/rose-env-cat/01-basic.t index 90ece23d68..8d0539528c 100755 --- a/t/rose-env-cat/01-basic.t +++ b/t/rose-env-cat/01-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-env-cat/02-braced.t b/t/rose-env-cat/02-braced.t index adfaefb00c..0ad0824078 100644 --- a/t/rose-env-cat/02-braced.t +++ b/t/rose-env-cat/02-braced.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-env-cat/test_header b/t/rose-env-cat/test_header index fa2e8f0a67..9cff165e94 100644 --- a/t/rose-env-cat/test_header +++ b/t/rose-env-cat/test_header @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-help/00-alias.t b/t/rose-help/00-alias.t index 97ab167df8..702dacc783 100755 --- a/t/rose-help/00-alias.t +++ b/t/rose-help/00-alias.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-host-select/00-basic.t b/t/rose-host-select/00-basic.t index 07df8a469b..ccafa9b2b4 100755 --- a/t/rose-host-select/00-basic.t +++ b/t/rose-host-select/00-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-host-select/01-timeout.t b/t/rose-host-select/01-timeout.t index ddc8ecf06b..bca2ee3ea7 100755 --- a/t/rose-host-select/01-timeout.t +++ b/t/rose-host-select/01-timeout.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-host-select/01-timeout/bin/mock-ssh b/t/rose-host-select/01-timeout/bin/mock-ssh index 04858b4ad5..c919a20558 100755 --- a/t/rose-host-select/01-timeout/bin/mock-ssh +++ b/t/rose-host-select/01-timeout/bin/mock-ssh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash ME=$(basename $0) echo -e "$$\t$*" >>$ME.out sleep 10 # A bit longer, so lingering process can be tested diff --git a/t/rose-host-select/02-localhost.t b/t/rose-host-select/02-localhost.t index aa83dc6285..71a0a66a43 100755 --- a/t/rose-host-select/02-localhost.t +++ b/t/rose-host-select/02-localhost.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/00-null.t b/t/rose-macro/00-null.t index 83843af936..5c28f7f692 100755 --- a/t/rose-macro/00-null.t +++ b/t/rose-macro/00-null.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/01-value-type-check-ok.t b/t/rose-macro/01-value-type-check-ok.t index 76ead93f7c..7982d87680 100755 --- a/t/rose-macro/01-value-type-check-ok.t +++ b/t/rose-macro/01-value-type-check-ok.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/02-value-type-check-err.t b/t/rose-macro/02-value-type-check-err.t index 3ed9473b1e..1b2fe470ea 100755 --- a/t/rose-macro/02-value-type-check-err.t +++ b/t/rose-macro/02-value-type-check-err.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/03-value-range-check-ok.t b/t/rose-macro/03-value-range-check-ok.t index 8d92aab0c0..993dcb0539 100755 --- a/t/rose-macro/03-value-range-check-ok.t +++ b/t/rose-macro/03-value-range-check-ok.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/04-value-range-check-err.t b/t/rose-macro/04-value-range-check-err.t index 74f9bb8ce7..cc0b3dfa84 100755 --- a/t/rose-macro/04-value-range-check-err.t +++ b/t/rose-macro/04-value-range-check-err.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/05-value-values-check.t b/t/rose-macro/05-value-values-check.t index 710bf0cd19..62a72d4724 100755 --- a/t/rose-macro/05-value-values-check.t +++ b/t/rose-macro/05-value-values-check.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/06-value-pattern-check.t b/t/rose-macro/06-value-pattern-check.t index 6f84c9ade5..125c915462 100755 --- a/t/rose-macro/06-value-pattern-check.t +++ b/t/rose-macro/06-value-pattern-check.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/07-value-change.t b/t/rose-macro/07-value-change.t index 02f76f1068..e66a738b6c 100755 --- a/t/rose-macro/07-value-change.t +++ b/t/rose-macro/07-value-change.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/08-compulsory-check.t b/t/rose-macro/08-compulsory-check.t index 7187352ea1..644c89d45f 100755 --- a/t/rose-macro/08-compulsory-check.t +++ b/t/rose-macro/08-compulsory-check.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/09-rule-check.t b/t/rose-macro/09-rule-check.t index a562a02522..e7757f0dc7 100755 --- a/t/rose-macro/09-rule-check.t +++ b/t/rose-macro/09-rule-check.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/10-trigger-check.t b/t/rose-macro/10-trigger-check.t index ca12cdc1c1..fd9d77a4f1 100755 --- a/t/rose-macro/10-trigger-check.t +++ b/t/rose-macro/10-trigger-check.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/11-trigger-change.t b/t/rose-macro/11-trigger-change.t index 85722c59d8..3740081b15 100755 --- a/t/rose-macro/11-trigger-change.t +++ b/t/rose-macro/11-trigger-change.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/12-custom-change.t b/t/rose-macro/12-custom-change.t index 792b3e3d80..1921dfa3b6 100755 --- a/t/rose-macro/12-custom-change.t +++ b/t/rose-macro/12-custom-change.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/13-custom-check.t b/t/rose-macro/13-custom-check.t index 96ef815420..ebb413c6d1 100755 --- a/t/rose-macro/13-custom-check.t +++ b/t/rose-macro/13-custom-check.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/14-duplicate-check.t b/t/rose-macro/14-duplicate-check.t index 41c96f0505..2e847750b3 100755 --- a/t/rose-macro/14-duplicate-check.t +++ b/t/rose-macro/14-duplicate-check.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/15-bad-custom-change.t b/t/rose-macro/15-bad-custom-change.t index dc4b048b47..b295981089 100755 --- a/t/rose-macro/15-bad-custom-change.t +++ b/t/rose-macro/15-bad-custom-change.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/16-cwd.t b/t/rose-macro/16-cwd.t index 899324f0ef..38e8133a26 100644 --- a/t/rose-macro/16-cwd.t +++ b/t/rose-macro/16-cwd.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/17-import.t b/t/rose-macro/17-import.t index 69b4ea9ebe..fdc7233eef 100755 --- a/t/rose-macro/17-import.t +++ b/t/rose-macro/17-import.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/18-opt-conf-check.t b/t/rose-macro/18-opt-conf-check.t index 99e067c098..7abc851e70 100755 --- a/t/rose-macro/18-opt-conf-check.t +++ b/t/rose-macro/18-opt-conf-check.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/19-opt-conf-change-add.t b/t/rose-macro/19-opt-conf-change-add.t index 562a68a657..1e64c2c8f1 100755 --- a/t/rose-macro/19-opt-conf-change-add.t +++ b/t/rose-macro/19-opt-conf-change-add.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/20-opt-conf-change-modify.t b/t/rose-macro/20-opt-conf-change-modify.t index a584cced55..9b8ed7fd63 100755 --- a/t/rose-macro/20-opt-conf-change-modify.t +++ b/t/rose-macro/20-opt-conf-change-modify.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/21-opt-conf-change-remove.t b/t/rose-macro/21-opt-conf-change-remove.t index a5195b4f4d..a28f0fdf77 100755 --- a/t/rose-macro/21-opt-conf-change-remove.t +++ b/t/rose-macro/21-opt-conf-change-remove.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/22-repeats.t b/t/rose-macro/22-repeats.t index ad1617a109..d741ed5f04 100755 --- a/t/rose-macro/22-repeats.t +++ b/t/rose-macro/22-repeats.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/23-import-tree.t b/t/rose-macro/23-import-tree.t index f9a9fa3374..3e588662fb 100755 --- a/t/rose-macro/23-import-tree.t +++ b/t/rose-macro/23-import-tree.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/24-optional-config-name.t b/t/rose-macro/24-optional-config-name.t index e4f320a36c..25ff47e203 100755 --- a/t/rose-macro/24-optional-config-name.t +++ b/t/rose-macro/24-optional-config-name.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/25-suite-level.t b/t/rose-macro/25-suite-level.t index c1fad96ac3..e9d158056f 100755 --- a/t/rose-macro/25-suite-level.t +++ b/t/rose-macro/25-suite-level.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/26-optional-config-name-2.t b/t/rose-macro/26-optional-config-name-2.t index c6ead5b445..f8c0a0953f 100755 --- a/t/rose-macro/26-optional-config-name-2.t +++ b/t/rose-macro/26-optional-config-name-2.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-macro/test_header b/t/rose-macro/test_header index bab16a7d90..b26322b240 100644 --- a/t/rose-macro/test_header +++ b/t/rose-macro/test_header @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-metadata-check/00-null.t b/t/rose-metadata-check/00-null.t index 3e7467a122..1556bab45a 100755 --- a/t/rose-metadata-check/00-null.t +++ b/t/rose-metadata-check/00-null.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-metadata-check/01-type-length.t b/t/rose-metadata-check/01-type-length.t index 9bf649cd8b..85f3bbe65c 100755 --- a/t/rose-metadata-check/01-type-length.t +++ b/t/rose-metadata-check/01-type-length.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-metadata-check/02-range.t b/t/rose-metadata-check/02-range.t index b347d5055e..0c02750f8b 100755 --- a/t/rose-metadata-check/02-range.t +++ b/t/rose-metadata-check/02-range.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-metadata-check/03-values.t b/t/rose-metadata-check/03-values.t index 61daadae48..6f95a059c6 100755 --- a/t/rose-metadata-check/03-values.t +++ b/t/rose-metadata-check/03-values.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-metadata-check/04-pattern.t b/t/rose-metadata-check/04-pattern.t index 0d571dd528..79846b2d97 100755 --- a/t/rose-metadata-check/04-pattern.t +++ b/t/rose-metadata-check/04-pattern.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-metadata-check/05-compulsory.t b/t/rose-metadata-check/05-compulsory.t index 463a8e5cb3..121abd3c9f 100755 --- a/t/rose-metadata-check/05-compulsory.t +++ b/t/rose-metadata-check/05-compulsory.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-metadata-check/06-rule.t b/t/rose-metadata-check/06-rule.t index e46d1c0139..6da149f30c 100755 --- a/t/rose-metadata-check/06-rule.t +++ b/t/rose-metadata-check/06-rule.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-metadata-check/07-trigger.t b/t/rose-metadata-check/07-trigger.t index a4f5bf563d..9059a4604f 100755 --- a/t/rose-metadata-check/07-trigger.t +++ b/t/rose-metadata-check/07-trigger.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-metadata-check/08-duplicate.t b/t/rose-metadata-check/08-duplicate.t index c4f09fb795..c0b20c6955 100755 --- a/t/rose-metadata-check/08-duplicate.t +++ b/t/rose-metadata-check/08-duplicate.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-metadata-check/09-custom-macro.t b/t/rose-metadata-check/09-custom-macro.t index 3845f8ee02..00559ecb8a 100755 --- a/t/rose-metadata-check/09-custom-macro.t +++ b/t/rose-metadata-check/09-custom-macro.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-metadata-check/11-invalid-settings.t b/t/rose-metadata-check/11-invalid-settings.t index ab392b7999..130b56ccc0 100644 --- a/t/rose-metadata-check/11-invalid-settings.t +++ b/t/rose-metadata-check/11-invalid-settings.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-metadata-check/test_header b/t/rose-metadata-check/test_header index 5d564a63a9..8868558297 100644 --- a/t/rose-metadata-check/test_header +++ b/t/rose-metadata-check/test_header @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-metadata-gen/00-null.t b/t/rose-metadata-gen/00-null.t index eac2fc1b24..f9c61c9343 100755 --- a/t/rose-metadata-gen/00-null.t +++ b/t/rose-metadata-gen/00-null.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-metadata-gen/01-basic.t b/t/rose-metadata-gen/01-basic.t index 8a7efe5e23..584cfc85a0 100755 --- a/t/rose-metadata-gen/01-basic.t +++ b/t/rose-metadata-gen/01-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-metadata-gen/02-autotype.t b/t/rose-metadata-gen/02-autotype.t index 630d091b8b..52e054300f 100755 --- a/t/rose-metadata-gen/02-autotype.t +++ b/t/rose-metadata-gen/02-autotype.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-metadata-gen/03-propset.t b/t/rose-metadata-gen/03-propset.t index 9e466108fe..3355b836cb 100755 --- a/t/rose-metadata-gen/03-propset.t +++ b/t/rose-metadata-gen/03-propset.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-metadata-gen/test_header b/t/rose-metadata-gen/test_header index 5f24023657..02ed1b4909 100644 --- a/t/rose-metadata-gen/test_header +++ b/t/rose-metadata-gen/test_header @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-mpi-launch/00-command.t b/t/rose-mpi-launch/00-command.t index b0e41fb55a..57978ea5ba 100755 --- a/t/rose-mpi-launch/00-command.t +++ b/t/rose-mpi-launch/00-command.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-mpi-launch/01-command-inner.t b/t/rose-mpi-launch/01-command-inner.t index 162891278c..57c636000f 100755 --- a/t/rose-mpi-launch/01-command-inner.t +++ b/t/rose-mpi-launch/01-command-inner.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-mpi-launch/02-file.t b/t/rose-mpi-launch/02-file.t index 2daebf1f71..fe55c0cdb8 100755 --- a/t/rose-mpi-launch/02-file.t +++ b/t/rose-mpi-launch/02-file.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-mpi-launch/test_header b/t/rose-mpi-launch/test_header index 7382d101f7..4aedabcd75 100644 --- a/t/rose-mpi-launch/test_header +++ b/t/rose-mpi-launch/test_header @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-namelist-dump/00-null.t b/t/rose-namelist-dump/00-null.t index 7c34d2cb70..7c0e4625ad 100755 --- a/t/rose-namelist-dump/00-null.t +++ b/t/rose-namelist-dump/00-null.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-namelist-dump/01-empty.t b/t/rose-namelist-dump/01-empty.t index 485065b9b7..f2222c57a7 100755 --- a/t/rose-namelist-dump/01-empty.t +++ b/t/rose-namelist-dump/01-empty.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-namelist-dump/02-type.t b/t/rose-namelist-dump/02-type.t index f155eb49fd..f82b7e036e 100755 --- a/t/rose-namelist-dump/02-type.t +++ b/t/rose-namelist-dump/02-type.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-namelist-dump/test_header b/t/rose-namelist-dump/test_header index 646fb420a7..ba812932c2 100644 --- a/t/rose-namelist-dump/test_header +++ b/t/rose-namelist-dump/test_header @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-env/00-non-cycle.t b/t/rose-task-env/00-non-cycle.t index a6830cdc55..82e15098de 100755 --- a/t/rose-task-env/00-non-cycle.t +++ b/t/rose-task-env/00-non-cycle.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-env/01-integer-cycling.t b/t/rose-task-env/01-integer-cycling.t index 66c1958910..647e41f206 100755 --- a/t/rose-task-env/01-integer-cycling.t +++ b/t/rose-task-env/01-integer-cycling.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-env/02-360day-cycling.t b/t/rose-task-env/02-360day-cycling.t index 54f4b0a872..e51dc70b4f 100755 --- a/t/rose-task-env/02-360day-cycling.t +++ b/t/rose-task-env/02-360day-cycling.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/00-run-basic.t b/t/rose-task-run/00-run-basic.t index 75e3af6785..003e392e0d 100755 --- a/t/rose-task-run/00-run-basic.t +++ b/t/rose-task-run/00-run-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/01-run-basic-iso.t b/t/rose-task-run/01-run-basic-iso.t index 06ed0c3fbe..5ffa3b689e 100755 --- a/t/rose-task-run/01-run-basic-iso.t +++ b/t/rose-task-run/01-run-basic-iso.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/04-run-path-empty.t b/t/rose-task-run/04-run-path-empty.t index c3fa03480e..bfa5d6f0bd 100755 --- a/t/rose-task-run/04-run-path-empty.t +++ b/t/rose-task-run/04-run-path-empty.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/06-app-prune-iso.t b/t/rose-task-run/06-app-prune-iso.t index 34a37cfaae..c2b70875e0 100755 --- a/t/rose-task-run/06-app-prune-iso.t +++ b/t/rose-task-run/06-app-prune-iso.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/07-app-arch.t b/t/rose-task-run/07-app-arch.t index 6b5525b830..77a8e4afcb 100755 --- a/t/rose-task-run/07-app-arch.t +++ b/t/rose-task-run/07-app-arch.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/07-app-arch/app/archive_bad_9/bin/my-bad-command b/t/rose-task-run/07-app-arch/app/archive_bad_9/bin/my-bad-command index 6d7f602b62..7abc5524da 100755 --- a/t/rose-task-run/07-app-arch/app/archive_bad_9/bin/my-bad-command +++ b/t/rose-task-run/07-app-arch/app/archive_bad_9/bin/my-bad-command @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash THIS=$(basename $0) echo "[$THIS] $*" >&2 exit 1 diff --git a/t/rose-task-run/07-app-arch/app/install/bin/my-install b/t/rose-task-run/07-app-arch/app/install/bin/my-install index f821bbbe5f..4db149c402 100755 --- a/t/rose-task-run/07-app-arch/app/install/bin/my-install +++ b/t/rose-task-run/07-app-arch/app/install/bin/my-install @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -eu PREFIX= if [[ ${ROSE_TASK_CYCLE_TIME:-} ]]; then diff --git a/t/rose-task-run/07-app-arch/bin/foo b/t/rose-task-run/07-app-arch/bin/foo index 678b844e3f..532940fe39 100755 --- a/t/rose-task-run/07-app-arch/bin/foo +++ b/t/rose-task-run/07-app-arch/bin/foo @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/08-app-fcm-make.t b/t/rose-task-run/08-app-fcm-make.t index 2268488882..a31a63f23c 100755 --- a/t/rose-task-run/08-app-fcm-make.t +++ b/t/rose-task-run/08-app-fcm-make.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/09-app-prune-work.t b/t/rose-task-run/09-app-prune-work.t index 72aaff5aee..cb684ab109 100755 --- a/t/rose-task-run/09-app-prune-work.t +++ b/t/rose-task-run/09-app-prune-work.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/10-specify-cycle.t b/t/rose-task-run/10-specify-cycle.t index 448c6f6980..8db256dcad 100755 --- a/t/rose-task-run/10-specify-cycle.t +++ b/t/rose-task-run/10-specify-cycle.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/11-specify-cycle-iso.t b/t/rose-task-run/11-specify-cycle-iso.t index 7fbc7a55dc..421a0f6b30 100755 --- a/t/rose-task-run/11-specify-cycle-iso.t +++ b/t/rose-task-run/11-specify-cycle-iso.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/12-app-prune-integer.t b/t/rose-task-run/12-app-prune-integer.t index 987e931200..2d3456da2f 100755 --- a/t/rose-task-run/12-app-prune-integer.t +++ b/t/rose-task-run/12-app-prune-integer.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/13-app-arch-cmd-out.t b/t/rose-task-run/13-app-arch-cmd-out.t index c873d76565..aef3fe9276 100755 --- a/t/rose-task-run/13-app-arch-cmd-out.t +++ b/t/rose-task-run/13-app-arch-cmd-out.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/13-app-arch-cmd-out/bin/foo b/t/rose-task-run/13-app-arch-cmd-out/bin/foo index 8e0a6b2aba..585fea083d 100755 --- a/t/rose-task-run/13-app-arch-cmd-out/bin/foo +++ b/t/rose-task-run/13-app-arch-cmd-out/bin/foo @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/14-app-prune-remove.t b/t/rose-task-run/14-app-prune-remove.t index f367b21391..fb7e3e6c7b 100755 --- a/t/rose-task-run/14-app-prune-remove.t +++ b/t/rose-task-run/14-app-prune-remove.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/15-app-prune-extglob.t b/t/rose-task-run/15-app-prune-extglob.t index 7b0e29829c..7bb70343ec 100755 --- a/t/rose-task-run/15-app-prune-extglob.t +++ b/t/rose-task-run/15-app-prune-extglob.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/16-app-prune-point.t b/t/rose-task-run/16-app-prune-point.t index ddf7551c91..b206ef7480 100755 --- a/t/rose-task-run/16-app-prune-point.t +++ b/t/rose-task-run/16-app-prune-point.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t b/t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t index 2c9bb9228a..81fbd7b2bd 100755 --- a/t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t +++ b/t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/18-app-fcm-make-ctx-name.t b/t/rose-task-run/18-app-fcm-make-ctx-name.t index 108af357de..7beb2adab5 100755 --- a/t/rose-task-run/18-app-fcm-make-ctx-name.t +++ b/t/rose-task-run/18-app-fcm-make-ctx-name.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/20-app-fcm-make-dest.t b/t/rose-task-run/20-app-fcm-make-dest.t index 07048d58ef..54ac172d66 100755 --- a/t/rose-task-run/20-app-fcm-make-dest.t +++ b/t/rose-task-run/20-app-fcm-make-dest.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/24-app-fcm-make-fast.t b/t/rose-task-run/24-app-fcm-make-fast.t index 67b9ba8ce2..627f980489 100755 --- a/t/rose-task-run/24-app-fcm-make-fast.t +++ b/t/rose-task-run/24-app-fcm-make-fast.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/25-app-fcm-make-new-mode.t b/t/rose-task-run/25-app-fcm-make-new-mode.t index af50814558..7011b76c5a 100755 --- a/t/rose-task-run/25-app-fcm-make-new-mode.t +++ b/t/rose-task-run/25-app-fcm-make-new-mode.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t b/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t index 503322521d..9c0186850e 100755 --- a/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t +++ b/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/28-env-path-run-path.t b/t/rose-task-run/28-env-path-run-path.t index acbd9d7c8e..d0d86730c9 100755 --- a/t/rose-task-run/28-env-path-run-path.t +++ b/t/rose-task-run/28-env-path-run-path.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/28-env-path-run-path/opt/earth/bin/hello b/t/rose-task-run/28-env-path-run-path/opt/earth/bin/hello index 45fdb3d7e7..792a51b7dc 100755 --- a/t/rose-task-run/28-env-path-run-path/opt/earth/bin/hello +++ b/t/rose-task-run/28-env-path-run-path/opt/earth/bin/hello @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash echo "Hello Earth!" diff --git a/t/rose-task-run/28-env-path-run-path/opt/world/bin/hello b/t/rose-task-run/28-env-path-run-path/opt/world/bin/hello index 3b81b522ff..9bfbdeed86 100755 --- a/t/rose-task-run/28-env-path-run-path/opt/world/bin/hello +++ b/t/rose-task-run/28-env-path-run-path/opt/world/bin/hello @@ -1,2 +1,2 @@ -#!/bin/bash +#!/usr/bin/env bash echo 'Hello World!' diff --git a/t/rose-task-run/29-app-prune-extglob-remote.t b/t/rose-task-run/29-app-prune-extglob-remote.t index 27d5b37f20..53d5b7fa49 100755 --- a/t/rose-task-run/29-app-prune-extglob-remote.t +++ b/t/rose-task-run/29-app-prune-extglob-remote.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/30-app-arch-opt-source.t b/t/rose-task-run/30-app-arch-opt-source.t index 9a9cfd140d..88dbd67091 100755 --- a/t/rose-task-run/30-app-arch-opt-source.t +++ b/t/rose-task-run/30-app-arch-opt-source.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/30-app-arch-opt-source/bin/foo b/t/rose-task-run/30-app-arch-opt-source/bin/foo index 82ca6b086b..9afc5948f3 100755 --- a/t/rose-task-run/30-app-arch-opt-source/bin/foo +++ b/t/rose-task-run/30-app-arch-opt-source/bin/foo @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/31-app-bunch.t b/t/rose-task-run/31-app-bunch.t index 2829354973..ad81dfc660 100755 --- a/t/rose-task-run/31-app-bunch.t +++ b/t/rose-task-run/31-app-bunch.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/32-app-arch-compressed.t b/t/rose-task-run/32-app-arch-compressed.t index a026922cfc..1886e20427 100755 --- a/t/rose-task-run/32-app-arch-compressed.t +++ b/t/rose-task-run/32-app-arch-compressed.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/33-app-prune-cycle-format.t b/t/rose-task-run/33-app-prune-cycle-format.t index f1bdd07148..ff0f0044e9 100755 --- a/t/rose-task-run/33-app-prune-cycle-format.t +++ b/t/rose-task-run/33-app-prune-cycle-format.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/34-app-prune-hosts-sharing-fs.t b/t/rose-task-run/34-app-prune-hosts-sharing-fs.t index 5f6f41ee20..b802288f37 100755 --- a/t/rose-task-run/34-app-prune-hosts-sharing-fs.t +++ b/t/rose-task-run/34-app-prune-hosts-sharing-fs.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t b/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t index 0462ec0b54..1bab9e8014 100755 --- a/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t +++ b/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/36-app-arch-interrupted.t b/t/rose-task-run/36-app-arch-interrupted.t index 0bc8c5453e..cd6b32ddf9 100755 --- a/t/rose-task-run/36-app-arch-interrupted.t +++ b/t/rose-task-run/36-app-arch-interrupted.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/37-app-bunch-rm-old.t b/t/rose-task-run/37-app-bunch-rm-old.t index 216fbf8a61..9699e533fb 100755 --- a/t/rose-task-run/37-app-bunch-rm-old.t +++ b/t/rose-task-run/37-app-bunch-rm-old.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/38-app-bunch-counts.t b/t/rose-task-run/38-app-bunch-counts.t index 87276a278a..69d2276945 100755 --- a/t/rose-task-run/38-app-bunch-counts.t +++ b/t/rose-task-run/38-app-bunch-counts.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/39-app-prune-cycle-host.t b/t/rose-task-run/39-app-prune-cycle-host.t index 4d1ea050c2..bfaa9fa355 100755 --- a/t/rose-task-run/39-app-prune-cycle-host.t +++ b/t/rose-task-run/39-app-prune-cycle-host.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/40-app-arch-duplicate.t b/t/rose-task-run/40-app-arch-duplicate.t index eb2b574c8a..582cdd09ae 100644 --- a/t/rose-task-run/40-app-arch-duplicate.t +++ b/t/rose-task-run/40-app-arch-duplicate.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose-task-run/41-app-bunch-default-command.t b/t/rose-task-run/41-app-bunch-default-command.t index f702e6edd6..8865bcd395 100755 --- a/t/rose-task-run/41-app-bunch-default-command.t +++ b/t/rose-task-run/41-app-bunch-default-command.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose.env/00-export.t b/t/rose.env/00-export.t index 3a6f6ca114..2fbe5d3535 100755 --- a/t/rose.env/00-export.t +++ b/t/rose.env/00-export.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose.popen/00-self.t b/t/rose.popen/00-self.t index 4ea874983b..9f7f2f686c 100755 --- a/t/rose.popen/00-self.t +++ b/t/rose.popen/00-self.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose.variable/00-trigger.t b/t/rose.variable/00-trigger.t index c76fe2f239..c1edf4185b 100755 --- a/t/rose.variable/00-trigger.t +++ b/t/rose.variable/00-trigger.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose.variable/01-array-split.t b/t/rose.variable/01-array-split.t index 3086138488..0df940964f 100644 --- a/t/rose.variable/01-array-split.t +++ b/t/rose.variable/01-array-split.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rose.variable/test_header b/t/rose.variable/test_header index 9e0a51f95a..bd787a8632 100644 --- a/t/rose.variable/test_header +++ b/t/rose.variable/test_header @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rosie-checkout/00-basic.t b/t/rosie-checkout/00-basic.t index b4501cad3b..96ba6b4990 100755 --- a/t/rosie-checkout/00-basic.t +++ b/t/rosie-checkout/00-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rosie-create/00-basic-edit b/t/rosie-create/00-basic-edit index c7e96a4ae8..2bc98bf1cc 100755 --- a/t/rosie-create/00-basic-edit +++ b/t/rosie-create/00-basic-edit @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rosie-create/00-basic.t b/t/rosie-create/00-basic.t index a3e65b0aba..27e6d3a1ea 100755 --- a/t/rosie-create/00-basic.t +++ b/t/rosie-create/00-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rosie-create/01-suite-info-meta.t b/t/rosie-create/01-suite-info-meta.t index 20966a5520..bff29701a4 100755 --- a/t/rosie-create/01-suite-info-meta.t +++ b/t/rosie-create/01-suite-info-meta.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rosie-create/02-copy-inter.t b/t/rosie-create/02-copy-inter.t index ab5e963182..9ad7c997bd 100755 --- a/t/rosie-create/02-copy-inter.t +++ b/t/rosie-create/02-copy-inter.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rosie-delete/00-basic.t b/t/rosie-delete/00-basic.t index fed1948404..af9b262280 100755 --- a/t/rosie-delete/00-basic.t +++ b/t/rosie-delete/00-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rosie-disco/00-basic.t b/t/rosie-disco/00-basic.t index 6596eda2d4..7432fe3612 100755 --- a/t/rosie-disco/00-basic.t +++ b/t/rosie-disco/00-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rosie-graph/00-basic.t b/t/rosie-graph/00-basic.t index d126c775e3..f77b68f176 100755 --- a/t/rosie-graph/00-basic.t +++ b/t/rosie-graph/00-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # @@ -58,7 +58,7 @@ touch 'conf/opt/rose-port.conf' # Setup repository - create a suite. cat >repos/foo/hooks/post-commit <<__POST_COMMIT__ -#!/bin/bash +#!/usr/bin/env bash export ROSE_CONF_PATH=$ROSE_CONF_PATH rosa svn-post-commit --debug "\$@" \\ 1>$PWD/rosa-svn-post-commit.out 2>$PWD/rosa-svn-post-commit.err diff --git a/t/rosie-graph/test_header_extra b/t/rosie-graph/test_header_extra index ff23178ec7..70979161a7 100644 --- a/t/rosie-graph/test_header_extra +++ b/t/rosie-graph/test_header_extra @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) 2012-2019 British Crown (Met Office) & Contributors. # diff --git a/t/rosie-hello/00-null.t b/t/rosie-hello/00-null.t index 7507aadedf..5e7eeb4951 100644 --- a/t/rosie-hello/00-null.t +++ b/t/rosie-hello/00-null.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rosie-id/00-basic.t b/t/rosie-id/00-basic.t index 7b9d942147..93c334dcc6 100755 --- a/t/rosie-id/00-basic.t +++ b/t/rosie-id/00-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rosie-id/01-final.t b/t/rosie-id/01-final.t index ac093d04e5..2cdc4777cb 100755 --- a/t/rosie-id/01-final.t +++ b/t/rosie-id/01-final.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rosie-lookup/00-basic.t b/t/rosie-lookup/00-basic.t index 571a3d89d1..cee2358972 100755 --- a/t/rosie-lookup/00-basic.t +++ b/t/rosie-lookup/00-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # @@ -56,7 +56,7 @@ touch 'conf/opt/rose-port.conf' # Setup repository - create a suite. cat >repos/foo/hooks/post-commit <<__POST_COMMIT__ -#!/bin/bash +#!/usr/bin/env bash export ROSE_CONF_PATH=$ROSE_CONF_PATH rosa svn-post-commit --debug "\$@" \\ 1>$PWD/rosa-svn-post-commit.out 2>$PWD/rosa-svn-post-commit.err diff --git a/t/rosie-lookup/01-multi.t b/t/rosie-lookup/01-multi.t index 0d226c8eeb..f652ea9676 100755 --- a/t/rosie-lookup/01-multi.t +++ b/t/rosie-lookup/01-multi.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rosie-lookup/02-unicode.t b/t/rosie-lookup/02-unicode.t index 664001a3e0..01088a4f09 100755 --- a/t/rosie-lookup/02-unicode.t +++ b/t/rosie-lookup/02-unicode.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rosie-ls/00-basic.t b/t/rosie-ls/00-basic.t index 7a8ad3367e..090dc72676 100755 --- a/t/rosie-ls/00-basic.t +++ b/t/rosie-ls/00-basic.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rosie-ls/01-many.t b/t/rosie-ls/01-many.t index 3874f684b0..1a1de7563a 100755 --- a/t/rosie-ls/01-many.t +++ b/t/rosie-ls/01-many.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/t/rosie.db/00-query.t b/t/rosie.db/00-query.t index d9e07fa328..ef32e0fae2 100755 --- a/t/rosie.db/00-query.t +++ b/t/rosie.db/00-query.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # diff --git a/usr/bin/rose b/usr/bin/rose index 6391202f5f..03ac6a4cdb 100755 --- a/usr/bin/rose +++ b/usr/bin/rose @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # From 01d53dbebe99fedc5d6ab6cc25a543a0087ff109 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Mon, 4 Jan 2021 13:19:03 +0000 Subject: [PATCH 21/78] tests: fix remaining functional tests * portability - allow to pass where bin account is not present * portability - allow case-insensitive host names * rename test modules with leading test_ * fix rose-metadata-check/09-custom-macro.t * fix t/rosa-svn-post-commit/00-basic.t * fix t/rose-task-run/11-specify-cycle-iso.t --- metomi/rose/tests/{config.py => test_config.py} | 0 metomi/rose/tests/{env.py => test_env.py} | 0 metomi/rose/tests/{popen.py => test_popen.py} | 0 .../{trigger_file.py => test_trigger_file.py} | 0 .../{unicode_utils.py => test_unicode_utils.py} | 0 t/lib/bash/test_header | 7 ++++++- t/rosa-svn-post-commit/00-basic.t | 2 ++ t/rosa-svn-post-commit/01-mail-passwd.conf | 4 ++-- t/rosa-svn-post-commit/01-mail-passwd.t | 15 +++++++++++++-- t/rosa-svn-post-commit/02-mail-owner-passwd.conf | 4 ++-- t/rose-metadata-check/09-custom-macro.t | 2 +- t/rose-task-run/11-specify-cycle-iso.t | 1 + t/rosie-lookup/00-basic.t | 4 ++++ t/rosie-lookup/01-multi.t | 4 ++++ t/rosie-lookup/02-unicode.t | 4 ++++ 15 files changed, 39 insertions(+), 8 deletions(-) rename metomi/rose/tests/{config.py => test_config.py} (100%) rename metomi/rose/tests/{env.py => test_env.py} (100%) rename metomi/rose/tests/{popen.py => test_popen.py} (100%) rename metomi/rose/tests/{trigger_file.py => test_trigger_file.py} (100%) rename metomi/rose/tests/{unicode_utils.py => test_unicode_utils.py} (100%) diff --git a/metomi/rose/tests/config.py b/metomi/rose/tests/test_config.py similarity index 100% rename from metomi/rose/tests/config.py rename to metomi/rose/tests/test_config.py diff --git a/metomi/rose/tests/env.py b/metomi/rose/tests/test_env.py similarity index 100% rename from metomi/rose/tests/env.py rename to metomi/rose/tests/test_env.py diff --git a/metomi/rose/tests/popen.py b/metomi/rose/tests/test_popen.py similarity index 100% rename from metomi/rose/tests/popen.py rename to metomi/rose/tests/test_popen.py diff --git a/metomi/rose/tests/trigger_file.py b/metomi/rose/tests/test_trigger_file.py similarity index 100% rename from metomi/rose/tests/trigger_file.py rename to metomi/rose/tests/test_trigger_file.py diff --git a/metomi/rose/tests/unicode_utils.py b/metomi/rose/tests/test_unicode_utils.py similarity index 100% rename from metomi/rose/tests/unicode_utils.py rename to metomi/rose/tests/test_unicode_utils.py diff --git a/t/lib/bash/test_header b/t/lib/bash/test_header index ac8cd65baa..21b344571b 100644 --- a/t/lib/bash/test_header +++ b/t/lib/bash/test_header @@ -51,6 +51,7 @@ # Compare contents in $FILE_ACTUAL and $FILE_EXPECT. pass/fail # $TEST_KEY if contents are identical/different. If $FILE_EXPECT is "-" # or not defined, compare $FILE_ACTUAL with STDIN to this function. +# Uses `diff -i` if DIFF_CASE_INSENSITIVE is set. # file_cmp_any TEST_KEY FILE_ACTUAL [FILE_EXPECT] # As file_cmp, but FILE_EXPECT should consist of more than one # contents set to compare against, separated by a line matching @@ -181,7 +182,11 @@ file_cmp() { local TEST_KEY=$1 local FILE_ACTUAL=$2 local FILE_EXPECT=${3:--} - if diff -u "${FILE_EXPECT}" "${FILE_ACTUAL}" >&2; then + local diff_opts=(-u) + if [[ -n ${DIFF_CASE_INSENSITIVE:-} ]]; then + diff_opts+=(-i) + fi + if diff "${diff_opts[@]}" "${FILE_EXPECT}" "${FILE_ACTUAL}" >&2; then pass "$TEST_KEY" return fi diff --git a/t/rosa-svn-post-commit/00-basic.t b/t/rosa-svn-post-commit/00-basic.t index 0e6d8a8e94..a67afb03a8 100755 --- a/t/rosa-svn-post-commit/00-basic.t +++ b/t/rosa-svn-post-commit/00-basic.t @@ -73,6 +73,8 @@ file_cmp "$TEST_KEY-hook.err" $PWD/rosa-svn-post-commit.err "$TEST_KEY-main.out" file_cmp "$TEST_KEY-main.out" "$TEST_KEY-main.out" <<__OUT__ foo-aa000|trunk|1|ivy|hook|test post commit hook: create|$LOGNAME|A | diff --git a/t/rosa-svn-post-commit/01-mail-passwd.conf b/t/rosa-svn-post-commit/01-mail-passwd.conf index 3dbf160f1a..c09243b499 100644 --- a/t/rosa-svn-post-commit/01-mail-passwd.conf +++ b/t/rosa-svn-post-commit/01-mail-passwd.conf @@ -3,5 +3,5 @@ recips.new=$USER recips.new-access-list=$USER root recips.mod-owner=$USER root recips.mod-access-list=$USER root -recips.mod-access-list-2=$USER bin root -recips.del=$USER bin root +recips.mod-access-list-2=$USER $USER2 root +recips.del=$USER $USER2 root diff --git a/t/rosa-svn-post-commit/01-mail-passwd.t b/t/rosa-svn-post-commit/01-mail-passwd.t index 4cff92d100..3d350d6131 100755 --- a/t/rosa-svn-post-commit/01-mail-passwd.t +++ b/t/rosa-svn-post-commit/01-mail-passwd.t @@ -30,6 +30,17 @@ if [[ -z ${TEST_SMTPD_HOST:-} ]]; then skip_all "cannot start mock SMTP server" fi +# pick a second user account for testing +# (must exist else tests fail) +USERS=(bin nobody) +for user in "${USERS[@]}"; do + if grep "^$user" /etc/passwd; then + USER2="$user" + break + fi +done +export USER2 + TEST_CONF="${TEST_SOURCE_DIR}/${TEST_KEY_BASE}.conf" # Get recipients from configuration get_recips() { @@ -182,7 +193,7 @@ file_cmp "$TEST_KEY-rc" "$PWD/rosa-svn-post-commit.rc" <<<'0' TEST_KEY="$TEST_KEY_BASE-mod-access-list-2" rosie checkout -q foo-aa001 cat >$PWD/roses/foo-aa001/rose-suite.info <<__ROSE_SUITE_INFO -access-list=root bin +access-list=root $USER2 owner=$USER project=hook sub-project=post-commit @@ -198,7 +209,7 @@ file_grep "$TEST_KEY-smtpd.log.recips" "^recips: \[$RECIPS\]" "$TEST_SMTPD_LOG" file_grep "$TEST_KEY-smtpd.log.subject" \ "^Data: b'.*Subject: foo-aa001/trunk@6" "$TEST_SMTPD_LOG" file_grep "$TEST_KEY-smtpd.log.text" \ - "^Data: b'.*-access-list=\\*.*+access-list=root bin" "$TEST_SMTPD_LOG" + "^Data: b'.*-access-list=\\*.*+access-list=root $USER2" "$TEST_SMTPD_LOG" file_cmp "$TEST_KEY-rc" "$PWD/rosa-svn-post-commit.rc" <<<'0' #------------------------------------------------------------------------------- TEST_KEY="$TEST_KEY_BASE-del" diff --git a/t/rosa-svn-post-commit/02-mail-owner-passwd.conf b/t/rosa-svn-post-commit/02-mail-owner-passwd.conf index 3c9ab7afeb..4f02b2915d 100644 --- a/t/rosa-svn-post-commit/02-mail-owner-passwd.conf +++ b/t/rosa-svn-post-commit/02-mail-owner-passwd.conf @@ -3,5 +3,5 @@ recips.new= recips.new-access-list=root recips.mod-owner=root recips.mod-access-list=root -recips.mod-access-list-2=bin root -recips.del=bin root +recips.mod-access-list-2=$USER2 root +recips.del=$USER2 root diff --git a/t/rose-metadata-check/09-custom-macro.t b/t/rose-metadata-check/09-custom-macro.t index 00559ecb8a..417bb1a720 100755 --- a/t/rose-metadata-check/09-custom-macro.t +++ b/t/rose-metadata-check/09-custom-macro.t @@ -121,7 +121,7 @@ file_cmp "$TEST_KEY.out" "$TEST_KEY.out" Date: Tue, 5 Jan 2021 09:42:19 +0000 Subject: [PATCH 22/78] actions: use gh actions for ci testing --- .github/workflows/test.yml | 115 ++++++++++++++++++++++++++++++ .travis/now | 97 ++++++++++++++----------- {.travis => etc/bin}/shellchecker | 4 +- etc/conf/macos-patch | 13 ++++ 4 files changed, 188 insertions(+), 41 deletions(-) create mode 100644 .github/workflows/test.yml rename {.travis => etc/bin}/shellchecker (96%) create mode 100644 etc/conf/macos-patch diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..ea740cd2e9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,115 @@ +name: test + +on: + pull_request: + workflow_dispatch: + inputs: + branch: + description: The branch to open the PR against + required: false + default: 'master' + +jobs: + test: + runs-on: ${{ matrix.os }} + timeout-minutes: 15 + strategy: + matrix: + os: ['ubuntu-latest'] + python-version: ['3.7'] + #include: + # - os: 'macos-latest' + # python-version: '3.7' + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + ref: ${{ github.event.inputs.branch }} + + - name: Configure Python + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + + - name: Install + run: | + # temp: use cylc-install branch + pip install git+https://github.com/datamel/cylc-flow@cylc-install + # TODO: remove editable mode + # there are other Rose files (e.g. meta-all/rose-meta.conf) + # that are not yet installed as part of the Python package + # which need to be moved into metomi/rose/etc/ + pip install -e ."[all]" + pip install --no-deps git+https://github.com/cylc/cylc-rose.git + yarn install + + - name: Brew Install + if: startsWith(matrix.os, 'macos') + run: | + # apply DNS patch + hostuserutil="$(python3 -c ' + import cylc.flow.hostuserutil + print(cylc.flow.hostuserutil.__file__) + ')" + patch "${hostuserutil}" < etc/conf/macos-patch + + # install system deps + brew update + brew install bash coreutils gnu-sed shellcheck sqlite3 + + # old stuff, not sure if all needed + brew install \ + subversion + + # add GNU coreutils and sed to the user PATH + # (see instructions in brew install output) + cat >> "$HOME/.bashrc" <<__HERE__ + PATH="$(brew --prefix)/opt/coreutils/libexec/gnubin:$PATH" + PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH" + __HERE__ + + - name: Apt-Get Install + if: startsWith(matrix.os, 'ubuntu') + run: | + sudo apt-get update + sudo apt-get install -y shellcheck sqlite3 + + # old stuff, not sure if all needed + sudo apt-get install -y \ + subversion \ + build-essential \ + gfortran \ + libxml-parser-perl \ + libconfig-inifiles-perl \ + libdbi-perl \ + libdbd-sqlite3-perl + + # yet more old stuff, not sure if needed + sudo apt-get install -y at + sudo sh -c 'echo "deb http://opensource.wandisco.com/ubuntu \ + `lsb_release -cs` svn19" >> /etc/apt/sources.list.d/subversion19.list' + sudo wget -q http://opensource.wandisco.com/wandisco-debian.gpg -O- | \ + sudo apt-key add - + + - name: Style + run: | + pycodestyle + flake8 + etc/bin/shellchecker + yarn run lint + + - name: Tests + run: | + pytest + + - name: Debug + run: | + pip list | grep cylc + pip list | grep rose + cylc version --long + rose version --long + + - name: Functional Tests + run: | + etc/bin/rose-test-battery -j 4 diff --git a/.travis/now b/.travis/now index c32b4f1026..aeb708d532 100755 --- a/.travis/now +++ b/.travis/now @@ -39,9 +39,10 @@ fi set -eu APT=() -NPM=() PIP=() GIT=() +BREW=() +YARN=false PY_PATH=() RC_PATH=("${HOME}") RI_PATH=() @@ -70,7 +71,7 @@ _gh_extract () { # extract project from GitHub to $HOME # in-place installation if [[ -f "${DEST}/setup.py" ]]; then - pip install -e "${DEST}" + pip install "${DEST}" fi } @@ -80,13 +81,27 @@ _install_coverage () { } _install_rose () { - pip install -e . - PY_PATH+=("./metomi") - RC_PATH+=("./bin") + pip install . } _install_cylc () { APT+=(at) + BREW+=(bash coreutils gnu-sed) + if [[ "$OSTYPE" == darwin* ]]; then + # add GNU coreutils and sed to the user PATH + # (see instructions in brew install output) + # shellcheck disable=SC2016 + RC_PATH+=('$(brew --prefix)/opt/coreutils/libexec/gnubin:$PATH') + # shellcheck disable=SC2016 + RC_PATH+=('/usr/local/opt/gnu-sed/libexec/gnubin:$PATH') + # apply DNS patch + hostuserutil="$(python3 -c ' + import cylc.flow.hostuserutil + print(cylc.flow.hostuserutil.__file__) + ')" + patch "${hostuserutil}" < etc/conf/macos-patch + fi + #brew install bash coreutils gnu-sed shellcheck sqlite3 subversion } _install_fcm () { @@ -97,11 +112,6 @@ _install_fcm () { WANDISCO=true } -_install_pytest () { - # pytest and its extensions - PIP+=(pytest) -} - _install_rosie () { PIP+=(requests tornado sqlalchemy) RI_PATH+=("$(_path_for python)" "$(_path_for rose)") @@ -127,8 +137,8 @@ _join () { _install_linters () { APT+=(shellcheck) - PIP+=(pycodestyle) - NPM+=(eslint@6) + BREW+=(shellcheck) + YARN=true } _path_for () { @@ -136,24 +146,24 @@ _path_for () { dirname "$(command -v "${COMMAND}")" } -_test_units () { - pytest --cov-append metomi/rose/tests/* -} +#_test_units () { +# pytest --cov-append metomi/rose/tests/* +#} -_test_style () { - pycodestyle - eslint . - "$(dirname "$0")/shellchecker" -} +#_test_style () { +# pycodestyle +# eslint . +# "$(dirname "$0")/shellchecker" +#} -_test_battery () { - cp "./.travis/sitecustomize.py" ./lib/python - coverage run .travis/cover.py -} +#_test_battery () { +# cp "./.travis/sitecustomize.py" ./lib/python +# coverage run .travis/cover.py +#} -_test_docs () { - etc/bin/rose-make-docs --strict clean linkcheck doctest -} +#_test_docs () { +# etc/bin/rose-make-docs --strict clean linkcheck doctest +#} _wandisco_configure () { # extract Wandisco stuff # shellcheck disable=SC1004,SC2016 @@ -183,14 +193,21 @@ install () { pip install "${PIP[@]}" & fi - if [[ ${#NPM[@]} -gt 0 ]]; then - npm install -g "${NPM[@]}" & + if $YARN; then + yarn install fi - if [[ ${#APT[@]} -gt 0 ]]; then - sudo apt-get update - # shellcheck disable=SC155,2033 - sudo apt-get install -y "${APT[@]}" + if [[ "$OSTYPE" == darwin* ]]; then + if [[ ${#BREW[@]} -gt 0 ]]; then + brew update + brew install "${BREW[@]}" + fi + else + if [[ ${#APT[@]} -gt 0 ]]; then + sudo apt-get update + # shellcheck disable=SC155,2033 + sudo apt-get install -y "${APT[@]}" + fi fi if [[ ${#GIT[@]} -gt 0 ]]; then @@ -223,14 +240,14 @@ _report_coverage () { bash <(curl -s https://codecov.io/bash) } -_report_error() { - # don't bail out on error - set +eu +#_report_error() { +# # don't bail out on error +# set +eu - printenv PATH PYTHONPATH - rose check-software - cat /tmp/sphinx-err* >&2 # sphinx traceback -} +# printenv PATH PYTHONPATH +# rose check-software +# cat /tmp/sphinx-err* >&2 # sphinx traceback +#} report () { for arg in "$@"; do diff --git a/.travis/shellchecker b/etc/bin/shellchecker similarity index 96% rename from .travis/shellchecker rename to etc/bin/shellchecker index 829359cccf..601110eea6 100755 --- a/.travis/shellchecker +++ b/etc/bin/shellchecker @@ -21,7 +21,7 @@ # Wrapper for running `shellcheck` over projects. set -e -cd "$(dirname "$0")/../" +cd "$(dirname "$0")/../../" # find shell files under the specified directory find_files () { @@ -78,6 +78,8 @@ default () { # run a strict check on all "functional" scripts main . \ --exclude t \ + --exclude node_modules \ + --exclude .git \ -- -e SC1090 -e SC2119 -e SC2001 # run a lenient check on all test scripts diff --git a/etc/conf/macos-patch b/etc/conf/macos-patch new file mode 100644 index 0000000000..51ef158d55 --- /dev/null +++ b/etc/conf/macos-patch @@ -0,0 +1,13 @@ +diff --git a/cylc/flow/hostuserutil.py b/cylc/flow/hostuserutil.py +index 21e51735e..17917b8fc 100644 +--- a/cylc/flow/hostuserutil.py ++++ b/cylc/flow/hostuserutil.py +@@ -113,7 +113,7 @@ class HostUtil: + """Return the extended info of the current host.""" + if target not in self._host_exs: + if target is None: +- target = socket.getfqdn() ++ target = socket.gethostname() + try: + self._host_exs[target] = socket.gethostbyname_ex(target) + except IOError as exc: From 38614822b652869cd00202227288a0f856dde1db Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Tue, 5 Jan 2021 10:39:12 +0000 Subject: [PATCH 23/78] flake8 --- .github/workflows/test.yml | 1 - etc/tutorial/widget/meta/lib/python/widget/username.py | 2 +- metomi/rose/apps/comparisons/exact.py | 1 - metomi/rose/apps/comparisons/mandatory.py | 1 - metomi/rose/apps/comparisons/prohibited.py | 1 - metomi/rose/apps/comparisons/within.py | 1 - metomi/rose/apps/rose_ana_v1.py | 2 +- .../vn1.0/lib/python/macros/desoggy.py | 3 --- .../vn1.0/lib/python/macros/desoggy.py | 3 --- metomi/rose/formats/__init__.py | 1 + metomi/rose/run_source_vc.py | 1 - metomi/rosie/ws.py | 1 + setup.py | 9 ++++++++- sphinx/ext/auto_cli_doc.py | 2 +- sphinx/extract-pdf-documents.py | 1 - t/rose-macro/lib/custom_macro_check.py | 1 - t/rose-metadata-check/lib/custom_macro.py | 3 ++- tox.ini | 10 +--------- 18 files changed, 16 insertions(+), 28 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ea740cd2e9..ffa2fa8cff 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -94,7 +94,6 @@ jobs: - name: Style run: | - pycodestyle flake8 etc/bin/shellchecker yarn run lint diff --git a/etc/tutorial/widget/meta/lib/python/widget/username.py b/etc/tutorial/widget/meta/lib/python/widget/username.py index 4d7bc6a1a8..d381db4397 100644 --- a/etc/tutorial/widget/meta/lib/python/widget/username.py +++ b/etc/tutorial/widget/meta/lib/python/widget/username.py @@ -9,9 +9,9 @@ from functools import partial -import gobject import pygtk pygtk.require('2.0') +# flake8: noqa: E402 import gtk diff --git a/metomi/rose/apps/comparisons/exact.py b/metomi/rose/apps/comparisons/exact.py index 58a62588a2..d1df06cefb 100644 --- a/metomi/rose/apps/comparisons/exact.py +++ b/metomi/rose/apps/comparisons/exact.py @@ -26,7 +26,6 @@ class Exact: def run(self, task): """Perform an exact comparison between the result and the KGO data""" - failures = 0 if len(task.resultdata) != len(task.kgo1data): raise DataLengthError(task) location = 0 diff --git a/metomi/rose/apps/comparisons/mandatory.py b/metomi/rose/apps/comparisons/mandatory.py index 5ccee40be5..f3616f38ee 100644 --- a/metomi/rose/apps/comparisons/mandatory.py +++ b/metomi/rose/apps/comparisons/mandatory.py @@ -24,7 +24,6 @@ class Mandatory: def run(self, task): """Perform an exact comparison between the result and the KGO data""" - failures = 0 if len(task.resultdata) == 0: task.set_failure(MandatoryStringResult(task, FAIL)) else: diff --git a/metomi/rose/apps/comparisons/prohibited.py b/metomi/rose/apps/comparisons/prohibited.py index 4c4e39b118..82a796dc66 100644 --- a/metomi/rose/apps/comparisons/prohibited.py +++ b/metomi/rose/apps/comparisons/prohibited.py @@ -24,7 +24,6 @@ class Prohibited: def run(self, task): """Perform an exact comparison between the result and the KGO data""" - failures = 0 if len(task.resultdata) == 0: task.set_pass(ProhibitedStringResult(task, PASS)) else: diff --git a/metomi/rose/apps/comparisons/within.py b/metomi/rose/apps/comparisons/within.py index 2fdec8c8a6..c95412ba50 100644 --- a/metomi/rose/apps/comparisons/within.py +++ b/metomi/rose/apps/comparisons/within.py @@ -34,7 +34,6 @@ class Within: def run(self, task): """Check that the results are within a specified tolerance.""" - failures = 0 if len(task.resultdata) != len(task.kgo1data): raise DataLengthError(task) val_num = 0 diff --git a/metomi/rose/apps/rose_ana_v1.py b/metomi/rose/apps/rose_ana_v1.py index 0ca6c9cbee..6859c87fa3 100644 --- a/metomi/rose/apps/rose_ana_v1.py +++ b/metomi/rose/apps/rose_ana_v1.py @@ -385,7 +385,7 @@ def load_tasks(self): tasks = [] for task in self.config.value.keys(): - if task is "env": + if task == "env": continue if task.startswith("file:"): continue diff --git a/metomi/rose/etc/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py b/metomi/rose/etc/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py index 5c91f2e76d..99f9f03be1 100644 --- a/metomi/rose/etc/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py +++ b/metomi/rose/etc/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py @@ -20,9 +20,6 @@ """ -import re -import subprocess - import metomi.rose.macro diff --git a/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py index 5c91f2e76d..99f9f03be1 100644 --- a/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py +++ b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py @@ -20,9 +20,6 @@ """ -import re -import subprocess - import metomi.rose.macro diff --git a/metomi/rose/formats/__init__.py b/metomi/rose/formats/__init__.py index 2b23ec1e6b..02b8f84c4f 100644 --- a/metomi/rose/formats/__init__.py +++ b/metomi/rose/formats/__init__.py @@ -18,4 +18,5 @@ such as namelists. To add a new format, place it in this directory and add an import statement below. """ +# flake8: noqa: F401 from . import namelist diff --git a/metomi/rose/run_source_vc.py b/metomi/rose/run_source_vc.py index b43d725ed6..6eacff572f 100644 --- a/metomi/rose/run_source_vc.py +++ b/metomi/rose/run_source_vc.py @@ -19,7 +19,6 @@ import os from metomi.rose.popen import RosePopener import sys -import _io from metomi.rose.unicode_utils import write_safely diff --git a/metomi/rosie/ws.py b/metomi/rosie/ws.py index cb2c0a3d44..1fb24f12e3 100644 --- a/metomi/rosie/ws.py +++ b/metomi/rosie/ws.py @@ -42,6 +42,7 @@ from tornado.ioloop import IOLoop, PeriodicCallback import tornado.log import tornado.web +import wsgiref from metomi.isodatetime.data import get_timepoint_from_seconds_since_unix_epoch from metomi.rose.host_select import HostSelector diff --git a/setup.py b/setup.py index 73bf043235..56bde08490 100644 --- a/setup.py +++ b/setup.py @@ -68,7 +68,13 @@ def find_version(*file_paths): 'cylc-sphinx-extensions[all]>=1.2.0' ] } -EXTRAS_REQUIRE['all'] = list({y for x in EXTRAS_REQUIRE.values() for y in x}) +TESTS_REQUIRE = [ + 'pytest', + 'flake8' +] +EXTRAS_REQUIRE['all'] = list(set( + [y for x in EXTRAS_REQUIRE.values() for y in x] + TESTS_REQUIRE +)) setup( @@ -83,6 +89,7 @@ def find_version(*file_paths): + glob(join("lib", "bash", "*")), install_requires=INSTALL_REQUIRES, extras_require=EXTRAS_REQUIRE, + tests_require=TESTS_REQUIRE, package_data={ "metomi.rose": ["etc/.*"], "metomi.rosie": ["lib/*"] diff --git a/sphinx/ext/auto_cli_doc.py b/sphinx/ext/auto_cli_doc.py index dcea532c8d..e24659d698 100644 --- a/sphinx/ext/auto_cli_doc.py +++ b/sphinx/ext/auto_cli_doc.py @@ -18,7 +18,7 @@ from collections import OrderedDict import re -from subprocess import PIPE, check_output, CalledProcessError +from subprocess import check_output, CalledProcessError import sys from docutils import nodes diff --git a/sphinx/extract-pdf-documents.py b/sphinx/extract-pdf-documents.py index 839d01d34f..476731ddb4 100644 --- a/sphinx/extract-pdf-documents.py +++ b/sphinx/extract-pdf-documents.py @@ -21,7 +21,6 @@ """ -import errno import os import shutil import sys diff --git a/t/rose-macro/lib/custom_macro_check.py b/t/rose-macro/lib/custom_macro_check.py index 985280a259..7b0214e6b2 100644 --- a/t/rose-macro/lib/custom_macro_check.py +++ b/t/rose-macro/lib/custom_macro_check.py @@ -30,7 +30,6 @@ class URLChecker(metomi.rose.macro.MacroBase): def validate(self, config, meta_config): """Validate a string containing a URL.""" - seq = [1, 1] self.reports = [] for section in config.value.keys(): for option in config.get([section]).value.keys(): diff --git a/t/rose-metadata-check/lib/custom_macro.py b/t/rose-metadata-check/lib/custom_macro.py index 8f36e90480..e91cd2301e 100644 --- a/t/rose-metadata-check/lib/custom_macro.py +++ b/t/rose-metadata-check/lib/custom_macro.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Copyright (C) British Crown (Met Office) & Contributors. # This file is part of Rose, a framework for meteorological suites. # @@ -52,5 +53,5 @@ def validate(self, config, meta_config=None): if node is not None and node.value != \ metomi.rose.TYPE_BOOLEAN_VALUE_FALSE: info = self.ERROR_NOT_TRUE.format(node.value) - self.add_report("env", "TRANSFORM_SWITCH", value, info) + self.add_report("env", "TRANSFORM_SWITCH", node.value, info) return self.reports diff --git a/tox.ini b/tox.ini index 922db7b725..7ee23545dc 100644 --- a/tox.ini +++ b/tox.ini @@ -1,14 +1,6 @@ -[pycodestyle] -ignore= - ; module level import not at top of file - E402, - ; line break before binary operator - W503, - ; line break after binary operator - W504 +[flake8] exclude= .git, __pycache__, - .tox, ; purposely corrupt file t/rose-metadata-check/lib/custom_macro_corrupt.py From 1eed46c68dfd536cdfbc030036f1fe15d25f8c38 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Tue, 5 Jan 2021 10:44:28 +0000 Subject: [PATCH 24/78] secrets: broken import issue * revert 306c27b321a8b54bd24ae2d830eddaca94e17741 --- metomi/rosie/ws_client_auth.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/metomi/rosie/ws_client_auth.py b/metomi/rosie/ws_client_auth.py index 48273d4752..3c5996fdd5 100644 --- a/metomi/rosie/ws_client_auth.py +++ b/metomi/rosie/ws_client_auth.py @@ -15,15 +15,16 @@ # along with Rose. If not, see . # ----------------------------------------------------------------------------- """The authentication manager for the Rosie web service client.""" -GI_FLAG = False import ast from getpass import getpass import os import re +import socket import shlex import sys from urllib.parse import urlparse +import warnings import metomi.rose.config from metomi.rose.env import env_var_process @@ -31,7 +32,23 @@ from metomi.rose.reporter import Reporter from metomi.rose.resource import ResourceLocator -import socket + +try: + from gi import require_version, pygtkcompat + require_version('Gtk', '3.0') # For GTK+ >=v3 use PyGObject; v2 use PyGTK + require_version('Secret', '1') + from gi.repository import Secret + del pygtkcompat + GI_FLAG = True +except (ImportError, ValueError): + GI_FLAG = False +try: + with warnings.catch_warnings(): + warnings.simplefilter('ignore') + import gtk + import gnomekeyring +except ImportError: + pass class UndefinedRosiePrefixWS(Exception): From 91e647f5571ec970bb6178a582230dde01b35fc2 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Mon, 11 Jan 2021 15:43:20 +0000 Subject: [PATCH 25/78] actions: test docs and fix docs build * update namespaces (rose -> metomi.rose) * fix docstring types (cause errors with contemporary sphinx) * fix missing references * document missing modules * upgrade to sphinx>=2 --- .github/workflows/test.yml | 73 ++++++++++++++++--- etc/rose.conf.example | 19 +---- metomi/rose/apps/rose_ana.py | 8 +- metomi/rose/config.py | 73 +++++++++++-------- metomi/rose/macro.py | 6 +- metomi/rose/upgrade.py | 55 ++++++++------ metomi/rosie/ws_client.py | 2 +- sphinx/api/built-in/rose_ana.rst | 20 ++--- sphinx/api/configuration/api.rst | 4 +- sphinx/api/configuration/metadata.rst | 6 +- sphinx/api/other.rst | 11 +++ sphinx/api/rose-macro.rst | 19 ++--- sphinx/api/rose-upgrader-macros.rst | 7 +- sphinx/api/rosie-web.rst | 56 +++++++------- sphinx/conf.py | 13 ++++ sphinx/developing/building.rst | 3 +- sphinx/ext/rose_domain.py | 4 +- sphinx/tutorial/cylc/runtime/introduction.rst | 6 +- .../rose/furthertopics/macro-development.rst | 2 +- .../tutorial/rose/furthertopics/rose-stem.rst | 6 +- .../upgrading-macro-development.rst | 2 +- 21 files changed, 233 insertions(+), 162 deletions(-) create mode 100644 sphinx/api/other.rst diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ffa2fa8cff..3e72bd1f81 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,9 +17,9 @@ jobs: matrix: os: ['ubuntu-latest'] python-version: ['3.7'] - #include: - # - os: 'macos-latest' - # python-version: '3.7' + include: + - os: 'macos-latest' + python-version: '3.7' steps: - name: Checkout @@ -102,13 +102,66 @@ jobs: run: | pytest - - name: Debug + - name: Functional Tests + id: functest run: | - pip list | grep cylc - pip list | grep rose - cylc version --long - rose version --long + etc/bin/rose-test-battery -j 4 --state=save - - name: Functional Tests + - name: Re-Run Fails + if: steps.functest.conclusion == 'failure' + run: | + etc/bin/rose-test-battery -j 1 -v --state=save,failed + + - name: Upload + if: steps.functest.conclusion == 'failure' + uses: actions/upload-artifact@v2 + with: + name: Upload cylc-run artifact + path: cylc-run + + docs: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + ref: ${{ github.event.inputs.branch }} + + - name: Configure Python + uses: actions/setup-python@v1 + with: + python-version: 3.7 + + - name: install + run: | + sudo apt-get update + sudo apt-get install -y \ + latexmk \ + texlive \ + texlive-generic-extra \ + texlive-latex-extra \ + texlive-fonts-recommended \ + graphviz + pip install -e .[docs] + + - name: build (html) + run: | + make -C sphinx/ html SPHINXOPTS='-Wn' + + - name: build (slides) + run: | + make -C sphinx/ slides SPHINXOPTS='-Wn' + + - name: build (latexpdf) + run: | + make -C sphinx/ latexpdf SPHINXOPTS='-Wn' + + - name: build (linkcheck) + run: | + make -C sphinx/ linkcheck SPHINXOPTS='-Wn' + + - name: debug + if: failure() run: | - etc/bin/rose-test-battery -j 4 + cat /tmp/sphinx-err* >&2 diff --git a/etc/rose.conf.example b/etc/rose.conf.example index 6cc8437477..342290a9ba 100644 --- a/etc/rose.conf.example +++ b/etc/rose.conf.example @@ -122,20 +122,6 @@ properties=title,ns,description,help ignore{foo}=REGEX - -# Configuration specific to :ref:`command-rose-config-edit`. -[rose-config-edit] -# :default: /opt/cylc/images/icon.svg -# -# Path to an image containing the suite engine icon. -# See :py:mod:`rose.config_editor` for detail. -icon-path-scheduler=PATH -# :default: https://github.com/metomi/rose/ -# -# Hyperlink to the Rose project page. -project-url=URL - - # Configuration related to :ref:`command-rose-host-select`. [rose-host-select] # The default arguments to use for this command e.g. ``default=hpc``. @@ -220,9 +206,9 @@ method-path=/path/1 /path2 # `. kgo-database=.true. # Limits the number of lines printed when using the -# :py:mod:`rose.apps.ana_builtin.grepper` analysis class. +# :py:mod:`metomi.rose.apps.ana_builtin.grepper` analysis class. grepper-report-limit=42 -# Causes the :py:mod:`rose.apps.ana_builtin.grepper` class to pass +# Causes the :py:mod:`metomi.rose.apps.ana_builtin.grepper` class to pass # if all files to be compared are missing. skip-if-all-files-missing=.true. @@ -254,7 +240,6 @@ title=TITLE # Configuration related to the :ref:`command-rosie-go` GUI. -# See :py:mod:`rosie.browser` for detail. [rosie-go] # :default: /opt/cylc/images/icon.svg # diff --git a/metomi/rose/apps/rose_ana.py b/metomi/rose/apps/rose_ana.py index b3c780e206..07eafb5757 100644 --- a/metomi/rose/apps/rose_ana.py +++ b/metomi/rose/apps/rose_ana.py @@ -170,14 +170,14 @@ class AnalysisTask(object, metaclass=abc.ABCMeta): self.config: A dictionary containing any Rose Ana configuration options. self.reporter: - A reference to the :py:class:`rose.reporter.Reporter` instance used - by the parent app (for printing to stderr/stdout). + A reference to the :py:class:`metomi.rose.reporter.Reporter` + instance used by the parent app (for printing to stderr/stdout). self.kgo_db: A reference to the KGO database object created by the parent app (for adding entries to the database). self.popen: - A reference to the :py:class:`rose.popen.RosePopener` instance - used by the parent app (for spawning subprocesses). + A reference to the :py:class:`metomi.rose.popen.RosePopener` + instance used by the parent app (for spawning subprocesses). """ diff --git a/metomi/rose/config.py b/metomi/rose/config.py index 62704c761f..8ae578af12 100644 --- a/metomi/rose/config.py +++ b/metomi/rose/config.py @@ -299,10 +299,10 @@ def get(self, keys=None, no_ignore=False): """Return a node at the position of keys, if any. Args: - keys (list, optional): A list defining a hierarchy of + keys (list): A list defining a hierarchy of node.value 'keys'. If an entry in keys is the null string, it is skipped. - no_ignore (bool, optional): If True any ignored nodes will + no_ignore (bool): If True any ignored nodes will not be returned. Returns: @@ -381,12 +381,12 @@ def get_value(self, keys=None, default=None): If the node does not exist or is ignored, return None. Args: - keys (list, optional): A list defining a hierarchy of node.value + keys (list): A list defining a hierarchy of node.value 'keys'. If an entry in keys is the null string, it is skipped. - default (obj, optional): Return default if the value is not set. + default (object): Return default if the value is not set. Returns: - obj: The value of this ConfigNode at the position of keys or + object: The value of this ConfigNode at the position of keys or default if not set. Examples: @@ -425,7 +425,7 @@ def set(self, keys=None, value=None, state=None, comments=None): Arguments: keys (list): A list defining a hierarchy of node.value 'keys'. If an entry in keys is the null string, it is skipped. - value (obj): The node.value property to set at this position. + value (object): The node.value property to set at this position. state (str): The node.state property to set at this position. If None, the node.state property is unchanged. comments (str): The node.comments property to set at this position. @@ -616,7 +616,8 @@ def __sub__(self, other_config_node): """Produce a ConfigNodeDiff from another ConfigNode. Arguments: - other_config_node - The ConfigNode to be applied to this ConfigNode + other_config_node (ConfigNode): + The ConfigNode to be applied to this ConfigNode to produce the ConfigNodeDiff. Returns: @@ -789,9 +790,12 @@ def set_added_setting(self, keys, data): """Set a config setting to be "added" in this ConfigNodeDiff. Args: - keys (list/tuple): The position of the setting to add. - data (obj, str, str): A tuple (value, state, comments) for the - setting to add. + keys (list, tuple): + The position of the setting to add. + data (tuple): + A tuple of the form + ``(value: object, state: string, comments: string)`` + for the setting to add. Examples: >>> config_node_diff = ConfigNodeDiff() @@ -820,11 +824,14 @@ def set_modified_setting(self, keys, old_data, data): None then no change will be made to any pre-existing value. Args: - keys (list/tuple): The position of the setting to add. - old_data (obj, str, str): A tuple (value, state, comments) for - the "current" properties of the setting to modify. - data (obj, str, str): A tuple (value, state, comments) for "new" - properties to change this setting to. + keys (list, tuple): + The position of the setting to add. + old_data (tuple): + A tuple ``(value: object, state: str, comments: str)`` + for the "current" properties of the setting to modify. + data (object): + A tuple ``(value: object, state: str, comments: str)`` + for "new" properties to change this setting to. Examples: >>> # Create a ConfigNodeDiff. @@ -850,8 +857,10 @@ def set_removed_setting(self, keys, data): """Set a config setting to be "removed" in this ConfigNodeDiff. Arguments: - keys (list): The position of the setting to add. - data (obj, str, str): A tuple (value, state, comments) of the + keys (list): + The position of the setting to add. + data (tuple): + A tuple ``(value: object, state: str, comments: str)`` of the properties for the setting to remove. Example: @@ -926,10 +935,10 @@ def get_removed(self): set to None for sections. Returns: - list - A list of the form [(keys, data), ...]: - - keys - The position of an added setting. - - data - Tuple of the form (value, state, comments) of the - properties of the removed setting. + list: A list of the form ``[(keys, data), ...]``: + - keys - The position of an added setting. + - data - Tuple of the form (value, state, comments) of the + properties of the removed setting. Examples: >>> config_node_diff = ConfigNodeDiff() @@ -1037,17 +1046,17 @@ def dump(self, root, target=sys.stdout, sort_sections=None, Args: root (ConfigNode): The root config node. - target (str/file): An open file handle or a string containing a + target (object): An open file handle or a string containing a file path. If not specified, the result is written to sys.stdout. - sort_sections (fcn - optional): An optional argument that should be + sort_sections (Callable): An optional argument that should be a function for sorting a list of section keys. - sort_option_items (fcn - optional): An optional argument that + sort_option_items (Callable): An optional argument that should be a function for sorting a list of option (key, value) tuples in string values. - env_escape_ok (bool - optional): An optional argument to indicate + env_escape_ok (bool): An optional argument to indicate that $NAME and ${NAME} syntax in values should be escaped. - concat_mode (bool - optional): Switch on concatenation mode. If + concat_mode (bool): Switch on concatenation mode. If True, add [] before root level options. """ @@ -1207,25 +1216,25 @@ def load_with_opts(self, source, node=None, more_keys=None, Arguments: source (str): A file path. - node (ConfigNode - optional): A ConfigNode object if specified, + node (ConfigNode): A ConfigNode object if specified, otherwise one is created. - more_keys (list - optional): A list of additional optional + more_keys (list): A list of additional optional configuration names. If source is "rose-${TYPE}.conf", the file of each name should be "opt/rose-${TYPE}-${NAME}.conf". - used_keys (list - optional): If defined, it should be a list for + used_keys (list): If defined, it should be a list for this method to append to. The key of each successfully loaded optional configuration will be appended to the list (unless the key is already in the list). Missing optional configurations that are specified in more_keys will not raise an error. If not defined, any missing optional configuration will trigger an OSError. - mark_opt_configs (bool - optional): if True, add comments above any + mark_opt_configs (bool): if True, add comments above any settings which have been loaded from an optional config. - return_config_map (bool - optional): If True, construct and return + return_config_map (bool): If True, construct and return a dict (config_map) containing config names vs their uncombined nodes. Optional configurations use their opt keys as keys, and the main configuration uses 'None'. - defines (list - optional): A list of [SECTION]KEY=VALUE overrides. + defines (list): A list of [SECTION]KEY=VALUE overrides. Returns: tuple: node or (node, config_map): diff --git a/metomi/rose/macro.py b/metomi/rose/macro.py index 03abc8fb1c..4033dba023 100644 --- a/metomi/rose/macro.py +++ b/metomi/rose/macro.py @@ -338,7 +338,7 @@ def standard_format_config(self, config): def add_report(self, *args, **kwargs): """Add a metomi.rose.macro.MacroReport. - See :class:`rose.macro.MacroReport` for details of arguments. + See :class:`metomi.rose.macro.MacroReport` for details of arguments. Examples: >>> # An example validator macro which adds a report to the setting @@ -502,7 +502,7 @@ class MacroReport: section (str): The name of the section to attach this report to. option (str): The name of the option (within the section) to attach this report to. - value (obj): The value of the configuration associated with this + value (object): The value of the configuration associated with this report. info (str): Text information describing the nature of the report. is_warning (bool): If True then this report will be logged as a @@ -763,7 +763,7 @@ def get_macro_class_methods(macro_modules): macro_name = macro_module.__name__ contents = inspect.getmembers(macro_module) for obj_name, obj in contents: - if not inspect.isclass(obj): + if not inspect.isclass(object): continue for att_name in ALLOWED_MACRO_CLASS_METHODS: if (hasattr(obj, att_name) and diff --git a/metomi/rose/upgrade.py b/metomi/rose/upgrade.py index 8a22e57849..6afc681837 100644 --- a/metomi/rose/upgrade.py +++ b/metomi/rose/upgrade.py @@ -162,22 +162,24 @@ def add_setting(self, config, keys, value=None, forced=False, configuration. keys (list): A list defining a hierarchy of node.value 'keys'. A section will be a list of one keys, an option will have two. - value (string - optional): String denoting the new setting value. + value (str): String denoting the new setting value. Required for options but not for settings. - forced (bool - optional) + forced (bool) If True override value if the setting already exists. - state (str - optional): + state (str): The state of the new setting - should be one of the - ``rose.config.ConfigNode`` states e.g. - ``rose.config.ConfigNode.STATE_USER_IGNORED``. Defaults to - ``rose.config.ConfigNode.STATE_NORMAL``. - comments (list - optional): List of comment lines (strings) for + :py:class:`metomi.rose.config.ConfigNode` states e.g. + :py:data:`metomi.rose.config.ConfigNode.STATE_USER_IGNORED`. + Defaults to + :py:data:`metomi.rose.config.ConfigNode.STATE_NORMAL`. + comments (list): List of comment lines (strings) for the new setting or ``None``. - info (string - optional): A short string containing no new lines, + info (str): A short string containing no new lines, describing the addition of the setting. Returns: None + """ section, option = self._get_section_option_from_keys(keys) id_ = self._get_id_from_section_option(section, option) @@ -266,12 +268,12 @@ def change_setting_value(self, config, keys, value, forced=False, configuration. keys (list): A list defining a hierarchy of node.value 'keys'. A section will be a list of one keys, an option will have two. - value (string): The new value. Required for options, can be + value (str): The new value. Required for options, can be ``None`` for sections. forced (bool): Create the setting if it is not present in config. - comments (list - optional): List of comment lines (strings) for + comments (list): List of comment lines (strings) for the new setting or ``None``. - info (string - optional): A short string containing no new lines, + info (str): A short string containing no new lines, describing the addition of the setting. Returns: @@ -309,11 +311,12 @@ def get_setting_value(self, config, keys, no_ignore=False): configuration. keys (list): A list defining a hierarchy of node.value 'keys'. A section will be a list of one keys, an option will have two. - no_ignore (bool - optional): If ``True`` return ``None`` if the + no_ignore (bool): If ``True`` return ``None`` if the setting is ignored (else return the value). Returns: object - The setting value or ``None`` if not defined. + """ section, option = self._get_section_option_from_keys(keys) if config.get([section, option], no_ignore=no_ignore) is None: @@ -328,11 +331,12 @@ def remove_setting(self, config, keys, info=None): configuration. keys (list): A list defining a hierarchy of node.value 'keys'. A section will be a list of one keys, an option will have two. - info (string - optional): A short string containing no new lines, + info (str): A short string containing no new lines, describing the addition of the setting. Returns: None + """ section, option = self._get_section_option_from_keys(keys) if option is None: @@ -353,11 +357,12 @@ def rename_setting(self, config, keys, new_keys, info=None): keys (list): A list defining a hierarchy of node.value 'keys'. A section will be a list of one keys, an option will have two. new_keys (list): The new hierarchy of node.value 'keys'. - info (string - optional): A short string containing no new lines, + info (str): A short string containing no new lines, describing the addition of the setting. Returns: None + """ section, option = self._get_section_option_from_keys(keys) new_section, new_option = self._get_section_option_from_keys(new_keys) @@ -403,11 +408,12 @@ def enable_setting(self, config, keys, info=None): configuration. keys (list): A list defining a hierarchy of node.value 'keys'. A section will be a list of one keys, an option will have two. - info (string - optional): A short string containing no new lines, + info (str): A short string containing no new lines, describing the addition of the setting. Returns: False - if the setting's state is not changed else ``None``. + """ return self._ignore_setting( config, @@ -420,17 +426,22 @@ def ignore_setting(self, config, keys, info=None, """User-ignore a setting in the configuration. Args: - config (metomi.rose.config.ConfigNode): The application - configuration. - keys (list): A list defining a hierarchy of node.value 'keys'. + config (metomi.rose.config.ConfigNode): + The application configuration. + keys (list): + A list defining a hierarchy of node.value 'keys'. A section will be a list of one keys, an option will have two. - info (string - optional): A short string containing no new lines, - describing the addition of the setting. - state (string - optional): A ``rose.config.ConfigNode`` state. - ``STATE_USER_IGNORED`` by default. + info (str): + A short string containing no new lines, describing the addition + of the setting. + state (str): + A :py:class:`metomi.rose.config.ConfigNode` state. + :py:data:`metomi.rose.config.ConfigNode.STATE_USER_IGNORED` by + default. Returns: False - if the setting's state is not changed else ``None``. + """ return self._ignore_setting(config, list(keys), info=info, state=state) diff --git a/metomi/rosie/ws_client.py b/metomi/rosie/ws_client.py index c38fb5f252..3726aa44de 100644 --- a/metomi/rosie/ws_client.py +++ b/metomi/rosie/ws_client.py @@ -73,7 +73,7 @@ class RosieWSClient: popen (rose.popen.RosePopener): Use initiated RosePopener instance create a new one if ``None``. event_handler : A callable object for reporting popen output, - see :py:class:`rose.reporter.Reporter`. + see :py:class:`metomi.rose.reporter.Reporter`. """ MAX_LOCAL_QUERIES = 64 diff --git a/sphinx/api/built-in/rose_ana.rst b/sphinx/api/built-in/rose_ana.rst index d372e99c79..86b8a1d6cc 100644 --- a/sphinx/api/built-in/rose_ana.rst +++ b/sphinx/api/built-in/rose_ana.rst @@ -83,12 +83,12 @@ details. .. rose:conf:: grepper-report-limit Limits the number of lines printed when using the - :py:mod:`rose.apps.ana_builtin.grepper` analysis class. + :py:mod:`metomi.rose.apps.ana_builtin.grepper` analysis class. .. rose:conf:: skip-if-all-files-missing - Causes the :py:mod:`rose.apps.ana_builtin.grepper` class to pass - if all files to be compared are missing. + Causes the :py:mod:`metomi.rose.apps.ana_builtin.grepper` class + to pass if all files to be compared are missing. .. rose:conf:: kgo-database @@ -110,15 +110,5 @@ Analysis Classes There is one built-in module of analysis classes called ``grepper``. -.. To document everything: - - .. automodule:: metomi.rose.apps.ana_builtin.grepper - :members: - -.. autoclass:: metomi.rose.apps.ana_builtin.grepper.FileCommandPattern - -.. autoclass:: metomi.rose.apps.ana_builtin.grepper.FilePattern - -.. autoclass:: metomi.rose.apps.ana_builtin.grepper.SingleCommandPattern - -.. autoclass:: metomi.rose.apps.ana_builtin.grepper.SingleCommandStatus +.. automodule:: metomi.rose.apps.ana_builtin.grepper + :members: FileCommandPattern, FilePattern, SingleCommandPattern, SingleCommandStatus diff --git a/sphinx/api/configuration/api.rst b/sphinx/api/configuration/api.rst index 1206e66cc7..cda35b76e8 100644 --- a/sphinx/api/configuration/api.rst +++ b/sphinx/api/configuration/api.rst @@ -28,8 +28,8 @@ Python ------ Rose provides a Python API for loading, processing, editing and dumping Rose -configurations via the :py:mod:`rose.config` module located within the Rose -Python library. +configurations via the :py:mod:`metomi.rose.config` module located within the +Rose Python library. .. automodule:: metomi.rose.config :members: diff --git a/sphinx/api/configuration/metadata.rst b/sphinx/api/configuration/metadata.rst index 38eb860c5f..bfd5734101 100644 --- a/sphinx/api/configuration/metadata.rst +++ b/sphinx/api/configuration/metadata.rst @@ -779,9 +779,9 @@ The metadata options for a configuration fall into four categories: Another useful Rose built-in widget to use is the array element aligning page widget, - ``rose.config_editor.pagewidget.table.PageArrayTable``. You can set - this for a section or namespace to make sure each *n*-th variable value - element lines up horizontally. For example: + ``metomi.rose.config_editor.pagewidget.table.PageArrayTable``. You can + set this for a section or namespace to make sure each *n*-th variable + value element lines up horizontally. For example: .. code-block:: rose diff --git a/sphinx/api/other.rst b/sphinx/api/other.rst new file mode 100644 index 0000000000..5df6cf5b11 --- /dev/null +++ b/sphinx/api/other.rst @@ -0,0 +1,11 @@ +Other Python API Docs +===================== + + +.. automodule:: metomi.rose.reporter + :members: Reporter + +.. automodule:: metomi.rose.popen + :members: RosePopener + +.. automodule:: metomi.rose.macros diff --git a/sphinx/api/rose-macro.rst b/sphinx/api/rose-macro.rst index 24c0b780cf..54a0468e3c 100644 --- a/sphinx/api/rose-macro.rst +++ b/sphinx/api/rose-macro.rst @@ -46,7 +46,7 @@ A module containing macros should be stored under a directory should be a Python package. When developing macros for Rose internals, macros should be placed in the -:py:mod:`rose.macros` package in the Rose Python library. They should be +:py:mod:`metomi.rose.macros` package in the Rose Python library. They should be referenced by the ``lib/python/rose/macros/__init__.py`` classes and a call to them can be added in the ``lib/python/rose/config_editor/main.py`` module if they need to be run implicitly by the config editor. @@ -60,14 +60,15 @@ Writing Macros For basic usage see the :ref:`macro tutorial `. Validator, transformer and reporter macros are Python classes which subclass -from :py:class:`rose.macro.MacroBase` (:ref:`API `). +from :py:class:`metomi.rose.macro.MacroBase` +(:ref:`API `). These macros implement their behaviours by providing a ``validate``, ``transform`` or ``report`` method. A macro can contain any combination of these methods so, for example, a macro might be both a validator and a transformer. -These methods should accept two :py:class:`rose.config.ConfigNode` +These methods should accept two :py:class:`metomi.rose.config.ConfigNode` instances as arguments - one is the configuration, and one is the metadata configuration that provides information about the configuration items. @@ -92,11 +93,11 @@ A validator macro should look like: # Some check on config appends to self.reports using self.add_report return self.reports -The returned list should be a list of :py:class:`rose.macro.MacroReport` objects -containing the section, option, value, and warning strings (info) for each -setting that is in error. These are initialised behind the scenes by calling the -inherited method :py:meth:`rose.macro.MacroBase.add_report` via -:py:meth:`self.add_report`. This has the form: +The returned list should be a list of :py:class:`metomi.rose.macro.MacroReport` +objects containing the section, option, value, and warning strings (info) for +each setting that is in error. These are initialised behind the scenes by +calling the inherited method :py:meth:`metomi.rose.macro.MacroBase.add_report`. +This has the form: .. code-block:: python @@ -123,7 +124,7 @@ Validator macros have the option to give warnings, which do not count as formal errors in the Rose config editor GUI. These should be used when something *may* be wrong, such as warning when using an advanced-developer-only option. They are invoked by passing a 5th argument -to :py:meth:`self.add_report`, ``is_warning``, like so: +to :py:meth:`metomi.rose.macro.MacroBase.add_report`, ``is_warning``, like so: .. code-block:: python diff --git a/sphinx/api/rose-upgrader-macros.rst b/sphinx/api/rose-upgrader-macros.rst index 638ef99b0d..766cc5f36c 100644 --- a/sphinx/api/rose-upgrader-macros.rst +++ b/sphinx/api/rose-upgrader-macros.rst @@ -10,7 +10,7 @@ metadata versions. They are classes, very similar to * An ``upgrade`` method instead of a ``transform`` method * An optional ``downgrade`` method, identical in API to the ``upgrade`` method, but intended for performing the reverse operation -* A more helpful API via :py:class:`rose.upgrade.MacroUpgrade` methods +* A more helpful API via :py:class:`metomi.rose.upgrade.MacroUpgrade` methods * ``BEFORE_TAG`` and ``AFTER_TAG`` attributes - the version of metadata they apply to (``BEFORE_TAG``) and the version they upgrade to (``AFTER_TAG``) @@ -52,8 +52,9 @@ file - ``rose-meta/CATEGORY/versions.py``. these very modules carefully or use absolute or package level imports like this: ``from .versionXX_YY import FooBar``. -Upgrade macros are subclasses of :py:class:`rose.upgrade.MacroUpgrade`. They -have all the functionality of the :ref:`transformer macros `. +Upgrade macros are subclasses of :py:class:`metomi.rose.upgrade.MacroUpgrade`. +They have all the functionality of the +:ref:`transformer macros `. .. autoclass:: metomi.rose.upgrade.MacroUpgrade :members: diff --git a/sphinx/api/rosie-web.rst b/sphinx/api/rosie-web.rst index 49afc86fb1..8df2e002e2 100644 --- a/sphinx/api/rosie-web.rst +++ b/sphinx/api/rosie-web.rst @@ -29,13 +29,13 @@ supported format is JSON) and use a url that looks like: REST API -------- -.. http:get:: (str:prefix)/get_known_keys +.. http:get:: (prefix)/get_known_keys Return the main property names stored for suites (e.g. ``idx``, ``branch``, ``owner``) plus any additional names specified in the site config. - :arg str prefix: Repository prefix. - :param string format: Desired return format (``json`` or ``None``). + :arg prefix: Repository prefix. + :param format: Desired return format (``json`` or ``None``). Example Request .. code-block:: http @@ -48,13 +48,13 @@ REST API ["access-list", "idx", "branch", "owner", "project", "revision", "status", "title"] -.. http:get:: (str:prefix)/get_optional_keys +.. http:get:: (prefix)/get_optional_keys Return all unique optional or user-defined property names given in suite discovery information. - :arg str prefix: Repository prefix. - :param string format: Desired return format (``json`` or ``None``). + :arg prefix: Repository prefix. + :param format: Desired return format (``json`` or ``None``). Example Request .. code-block:: http @@ -67,13 +67,13 @@ REST API ["access-list", "description", "endgame_status", "operational_flag", "tag-list"] -.. http:get:: (str:prefix)/get_query_operators +.. http:get:: (prefix)/get_query_operators Returns all the SQL-like operators used to compare column values that you - may use in :http:get:`(str:prefix)/query` (e.g. ``eq``, ``ne``, ``contains``, ``like``). + may use in :http:get:`(prefix)/query` (e.g. ``eq``, ``ne``, ``contains``, ``like``). - :arg str prefix: Repository prefix. - :param string format: Desired return format (``json`` or ``None``). + :arg prefix: Repository prefix. + :param format: Desired return format (``json`` or ``None``). Example Request .. code-block:: http @@ -86,23 +86,21 @@ REST API ["eq", "ge", "gt", "le", "lt", "ne", "contains", "endswith", "ilike", "like", "match", "startswith"] -.. http:get:: (str:prefix)/query +.. http:get:: (prefix)/query Return a list of suites matching all search terms. - :arg str prefix: Repository prefix. - :param list q: List of queries. - :param string format: Desired return format (``json`` or ``None``). - :param flag all_revs: Switch on searching older revisions of current suites - and deleted suites. + :arg prefix: Repository prefix. + :param q: List of queries. + :param format: Desired return format (``json`` or ``None``). + :param all_revs: Switch on searching older revisions of current suites and deleted suites. - :queryparameter str CONJUNCTION: ``and`` or ``or``\ . - :queryparameter str OPEN_GROUP: optional, one or more ``(``\ . - :queryparameter str FIELD: e.g. ``idx`` or ``description``\ . - :queryparameter str OPERATOR: e.g. ``contains`` or ``between``, one of - the operators returned by :http:get:`(str:prefix)/get_query_operators`. - :queryparameter str VALUE: e.g. ``euro4m`` or ``200``\ . - :queryparameter str CLOSE_GROUP: optional, one or more ``)``\ . + :queryparameter CONJUNCTION: ``and`` or ``or``\ . + :queryparameter OPEN_GROUP: optional, one or more ``(``\ . + :queryparameter FIELD: e.g. ``idx`` or ``description``\ . + :queryparameter OPERATOR: e.g. ``contains`` or ``between``, one of the operators returned by :http:get:`(prefix)/get_query_operators`. + :queryparameter VALUE: e.g. ``euro4m`` or ``200``\ . + :queryparameter CLOSE_GROUP: optional, one or more ``)``\ . Query Format .. code-block:: none @@ -136,16 +134,14 @@ REST API "access-list": ["*"], "operational": "Y"}] -.. http:get:: (str:prefix)/search +.. http:get:: (prefix)/search Return a list of suites matching one or more search terms. - :arg str prefix: Repository prefix. - :param list s: List of queries in the same format as - :http:get:`(str:prefix)/query` - :param string format: Desired return format (``json`` or ``None``). - :param flag all_revs: Switch on searching older revisions of current suites - and deleted suites. + :arg prefix: Repository prefix. + :param s: List of queries in the same format as :http:get:`(prefix)/query` + :param format: Desired return format (``json`` or ``None``). + :param ll_revs: Switch on searching older revisions of current suites and deleted suites. Example Request Return suites which match ``var``, ``bob`` or ``nowcast``. diff --git a/sphinx/conf.py b/sphinx/conf.py index 45f4eb4a6f..1aca6f7907 100644 --- a/sphinx/conf.py +++ b/sphinx/conf.py @@ -34,6 +34,7 @@ 'sphinx.ext.autosummary', 'sphinx.ext.doctest', 'sphinx.ext.graphviz', + 'sphinx.ext.intersphinx', 'sphinx.ext.napoleon', 'sphinx.ext.viewcode', @@ -76,6 +77,18 @@ # vector graphics will be converted to bitmaps in all documents extensions.append('sphinx.ext.imgconverter') +# mapping to other Sphinx projects +# (allows us to reference objects from other projects) +cylc_version = 'current' +intersphinx_mapping = { + 'cylc': ( + f'https://cylc.github.io/cylc-doc/{cylc_version}/html/', None + ), + 'python': ( + 'https://docs.python.org/', None + ) +} + # Slide (hieroglyph) settings. slide_theme = 'single-level' slide_link_to_html = True diff --git a/sphinx/developing/building.rst b/sphinx/developing/building.rst index 9548f9d21a..ad1b6368e6 100644 --- a/sphinx/developing/building.rst +++ b/sphinx/developing/building.rst @@ -22,7 +22,8 @@ The following builders are useful for development: ``linkcheck`` Check external links (internal links are checked by a regular build). ``doctest`` - Run any doctests contained within documented code (e.g. see ``rose.config``). + Run any doctests contained within documented code + (e.g. see :py:mod:`metomi.rose.config`). Additionally, if you are not using an editor with a spellchecker you may wish to use aspell/ispell/hunspell to check any changed docs: diff --git a/sphinx/ext/rose_domain.py b/sphinx/ext/rose_domain.py index d48b957078..fbac87b6cf 100644 --- a/sphinx/ext/rose_domain.py +++ b/sphinx/ext/rose_domain.py @@ -59,7 +59,7 @@ Documentation can be auto-built from RST formatted comments in Rose configuration files using the ``autoconfig`` directive. - Note that due to the implementation of :py:mod:`rose.config` the + Note that due to the implementation of :py:mod:`metomi.rose.config` the autodocumenter will represent empty sections as top level configuration nodes. @@ -922,7 +922,7 @@ class RoseAutoDirective(Directive): """Directive for autodocumenting Rose configuration files. Uses RST formatted comments in Rose configuration files using - :py:mod:`rose.config`. + :py:mod:`metomi.rose.config`. Note the directive only documents config objects not the file itself. diff --git a/sphinx/tutorial/cylc/runtime/introduction.rst b/sphinx/tutorial/cylc/runtime/introduction.rst index 462ccbf38a..650aa1cf8b 100644 --- a/sphinx/tutorial/cylc/runtime/introduction.rst +++ b/sphinx/tutorial/cylc/runtime/introduction.rst @@ -73,8 +73,8 @@ We can also call other scripts or executables in this way, e.g: script = ~/foo/bar/baz/hello_world -:envvar:`PATH` and :envvar:`PYTHONPATH` ---------------------------------------- +``PATH`` and :envvar:`PYTHONPATH` +--------------------------------- .. ifnotslides:: @@ -82,7 +82,7 @@ We can also call other scripts or executables in this way, e.g: leaving them somewhere else on the system. If you create a ``bin/`` sub-directory within the :term:`suite directory` - Cylc will automatically prepend it to the :envvar:`PATH` environment + Cylc will automatically prepend it to the ``PATH`` environment variable when the task runs. .. code-block:: bash diff --git a/sphinx/tutorial/rose/furthertopics/macro-development.rst b/sphinx/tutorial/rose/furthertopics/macro-development.rst index 745240f531..2cac189c08 100644 --- a/sphinx/tutorial/rose/furthertopics/macro-development.rst +++ b/sphinx/tutorial/rose/furthertopics/macro-development.rst @@ -115,7 +115,7 @@ Open ``planet.py`` in a text editor and paste in the following code: return self.reports This is the bare bones of a Rose macro - a bit of Python that is a -subclass of :py:class:`rose.macro.MacroBase`. At the moment, it doesn't +subclass of :py:class:`metomi.rose.macro.MacroBase`. At the moment, it doesn't do anything. We need to check the value of the option (``env=WORLD``) in our diff --git a/sphinx/tutorial/rose/furthertopics/rose-stem.rst b/sphinx/tutorial/rose/furthertopics/rose-stem.rst index 970a88b0cb..494d34cb95 100644 --- a/sphinx/tutorial/rose/furthertopics/rose-stem.rst +++ b/sphinx/tutorial/rose/furthertopics/rose-stem.rst @@ -239,8 +239,8 @@ of precedence): variable. The only analysis module provided with Rose is -:py:mod:`rose.apps.ana_builtin.grepper`, it provides the following analysis -tasks and options: +:py:mod:`metomi.rose.apps.ana_builtin.grepper`, it provides the following +analysis tasks and options: .. autoclass:: metomi.rose.apps.ana_builtin.grepper.SingleCommandStatus :noindex: @@ -262,7 +262,7 @@ route to understanding how they should be arranged is likely to look at the built-in ``grepper`` module. But the key concepts are as follows. To be recognised as a valid analysis module, the Python file must contain at least one class which inherits and extends -:py:mod:`rose.apps.rose_ana.AnalysisTask`: +:py:mod:`metomi.rose.apps.rose_ana.AnalysisTask`: .. autoclass:: metomi.rose.apps.rose_ana.AnalysisTask diff --git a/sphinx/tutorial/rose/furthertopics/upgrading-macro-development.rst b/sphinx/tutorial/rose/furthertopics/upgrading-macro-development.rst index ed3863c800..72feab8b02 100644 --- a/sphinx/tutorial/rose/furthertopics/upgrading-macro-development.rst +++ b/sphinx/tutorial/rose/furthertopics/upgrading-macro-development.rst @@ -221,7 +221,7 @@ Upgrade macros are bits of Python code that essentially look like this: return config, self.reports They are sub-classes of a particular class, -:py:class:`rose.upgrade.MacroUpgrade`, +:py:class:`metomi.rose.upgrade.MacroUpgrade`, which means that some of the Python functionality is done 'under the hood' to make things easier. From 486be56573359353d78d570cdaab375f1d382c4c Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Tue, 12 Jan 2021 13:08:48 +0000 Subject: [PATCH 26/78] docs: fix links --- sphinx/hyperlinks.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/hyperlinks.rst b/sphinx/hyperlinks.rst index f537c2bcd1..bdc12a7889 100644 --- a/sphinx/hyperlinks.rst +++ b/sphinx/hyperlinks.rst @@ -11,7 +11,7 @@ .. _Cylc: https://cylc.github.io/ .. _Cylc User Guide: https://cylc.github.io/documentation/#the-cylc-user-guide -.. _Cylc Suite Design Guide: https://cylc.github.io/doc/built-sphinx/suite-design-guide/suite-design-guide-master.html#suite-design-guide +.. _Cylc Suite Design Guide: https://cylc.github.io/cylc-doc/current/html/suite-design-guide/suite-design-guide-master.html .. _EmPy: http://www.alcyone.com/software/empy/ .. _extglob pattern matching: https://www.gnu.org/software/bash/manual/html_node/Pattern-Matching.html#Pattern-Matching .. _FCM: https://metomi.github.io/fcm/doc/ From 39f438b189df14a459f14c5612f7a745eaa0c26a Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Wed, 13 Jan 2021 10:49:11 +0000 Subject: [PATCH 27/78] docs: update extensions to sphinx2 --- sphinx/ext/rose_lang.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/ext/rose_lang.py b/sphinx/ext/rose_lang.py index 85f6025ae3..8b975d99a5 100644 --- a/sphinx/ext/rose_lang.py +++ b/sphinx/ext/rose_lang.py @@ -154,4 +154,4 @@ class RoseLexer(RegexLexer): def setup(app): """Sphinx plugin setup function.""" - app.add_lexer('rose', RoseLexer()) + app.add_lexer('rose', RoseLexer) From b30d6e5f62b2afc799a55da3576059dae1d52362 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Wed, 13 Jan 2021 15:10:22 +0000 Subject: [PATCH 28/78] config paths & instllation instructions * change to new config file hierarchy * document config files * re-write installation instructions --- etc/rose.conf.example | 5 +- metomi/rose/resource.py | 55 ++++++-- metomi/rose/tests/conftest.py | 39 ++++++ metomi/rose/tests/test_resource_locator.py | 149 +++++++++++++++++++++ sphinx/conf.py | 2 +- sphinx/hyperlinks.rst | 2 + sphinx/installation.rst | 147 +++++++++----------- 7 files changed, 305 insertions(+), 94 deletions(-) create mode 100644 metomi/rose/tests/conftest.py create mode 100644 metomi/rose/tests/test_resource_locator.py diff --git a/etc/rose.conf.example b/etc/rose.conf.example index 342290a9ba..ae743c6e52 100644 --- a/etc/rose.conf.example +++ b/etc/rose.conf.example @@ -1,7 +1,8 @@ # The :rose:file:`rose.conf` file can be installed in: # -# * ``$ROSE_HOME/etc/`` for *site* configuration. -# * ``$HOME/.metomi/rose/`` for *user* configuration. +# * ``/etc/rose.conf`` +# * ``$ROSE_SITE_CONF_PATH/rose.conf`` +# * ``$HOME/.metomi/rose.conf`` # #.. This file documents the settings and their default values. # Values of settings in this file are mostly placeholders, diff --git a/metomi/rose/resource.py b/metomi/rose/resource.py index d4b3542f75..d73667e89a 100644 --- a/metomi/rose/resource.py +++ b/metomi/rose/resource.py @@ -19,12 +19,14 @@ """ import os -from metomi.rose.config import ConfigLoader, ConfigNode +from pathlib import Path import inspect import string import sys from importlib.machinery import SourceFileLoader +from metomi.rose.config import ConfigLoader, ConfigNode + ERROR_LOCATE_OBJECT = "Could not locate {0}" @@ -52,12 +54,29 @@ def __init__(self, key): Exception.__init__(self, "%s: resource not found." % key) +ROSE_CONF_PATH = 'ROSE_CONF_PATH' +ROSE_SITE_CONF_PATH = 'ROSE_SITE_CONF_PATH' + + class ResourceLocator: + """A class for searching resource files. + + Loads files in the following order: + + System: + /etc + Site: + $ROSE_SITE_CONF_PATH + User: + ~/.metomi - """A class for searching resource files.""" + If $ROSE_CONF_PATH is defined these files are skipped and configuration + found in $ROSE_CONF_PATH is loaded instead. - SITE_CONF_PATH = get_util_home("etc") - USER_CONF_PATH = os.path.join(os.path.expanduser("~"), ".metomi") + """ + + SYST_CONF_PATH = Path('/etc') + USER_CONF_PATH = Path('~/.metomi').expanduser() ROSE_CONF = "rose.conf" _DEFAULT_RESOURCE_LOCATOR = None @@ -83,19 +102,37 @@ def __init__(self, namespace=None, util=None, paths=None): def get_conf(self): """Return the site/user configuration root node.""" if self.conf is None: - paths = [self.SITE_CONF_PATH, self.USER_CONF_PATH] + # base system conf path + paths = [self.SYST_CONF_PATH] + + # add $ROSE_SITE_CONF_PATH if defined + if "ROSE_SITE_CONF_PATH" in os.environ: + path_str = os.environ["ROSE_SITE_CONF_PATH"].strip() + if path_str: + paths.append(Path(path_str)) + + # add user conf path + paths.append(self.USER_CONF_PATH) + + # use $ROSE_CONF_PATH (and ignore all others) if defined if "ROSE_CONF_PATH" in os.environ: paths_str = os.getenv("ROSE_CONF_PATH").strip() if paths_str: - paths = paths_str.split(os.pathsep) + paths = [ + Path(path) + for path in paths_str.split(os.pathsep) + ] else: paths = [] + + # load and cache config self.conf = ConfigNode() config_loader = ConfigLoader() for path in paths: - name = os.path.join(path, self.ROSE_CONF) - if os.path.isfile(name) and os.access(name, os.R_OK): - config_loader.load_with_opts(name, self.conf) + conffile = path / self.ROSE_CONF + if conffile.is_file() and os.access(conffile, os.R_OK): + config_loader.load_with_opts(str(conffile), self.conf) + return self.conf def get_doc_url(self): diff --git a/metomi/rose/tests/conftest.py b/metomi/rose/tests/conftest.py new file mode 100644 index 0000000000..db9f033850 --- /dev/null +++ b/metomi/rose/tests/conftest.py @@ -0,0 +1,39 @@ +# Copyright (C) British Crown (Met Office) & Contributors. +# This file is part of Rose, a framework for meteorological suites. +# +# Rose 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. +# +# Rose 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 Rose. If not, see . + +from pathlib import Path +from shutil import rmtree +from tempfile import TemporaryDirectory + +import pytest +from _pytest.monkeypatch import MonkeyPatch + + +@pytest.fixture(scope='module') +def mod_monkeypatch(): + """A monkeypatch fixture with module-level scope.""" + patch = MonkeyPatch() + yield patch + patch.undo() + + +@pytest.fixture(scope='module') +def mod_tmp_path(): + """A tmp_path fixture with module-level scope.""" + path = Path(TemporaryDirectory().name) + path.mkdir() + yield path + rmtree(path) diff --git a/metomi/rose/tests/test_resource_locator.py b/metomi/rose/tests/test_resource_locator.py new file mode 100644 index 0000000000..2b09d72bfe --- /dev/null +++ b/metomi/rose/tests/test_resource_locator.py @@ -0,0 +1,149 @@ +# Copyright (C) British Crown (Met Office) & Contributors. +# This file is part of Rose, a framework for meteorological suites. +# +# Rose 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. +# +# Rose 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 Rose. If not, see . + +from textwrap import dedent + +import pytest + +from metomi.rose.resource import ( + ROSE_CONF_PATH, + ROSE_SITE_CONF_PATH, + ResourceLocator +) + + +@pytest.fixture(scope='module') +def sys_site_user_config(mod_monkeypatch, mod_tmp_path): + """Creates system, site and user configurations. + + * Patches ResourceLocator to use the system and user configs. + * Unsets ROSE_*CONF_PATH envvars to prevent them effecting tests. + + """ + # unset ROSE_CONF_PATH env vars + mod_monkeypatch.delenv(ROSE_CONF_PATH, raising=False) + mod_monkeypatch.delenv(ROSE_SITE_CONF_PATH, raising=False) + + # create system, site and user configurations + syst = mod_tmp_path / 'syst' + site = mod_tmp_path / 'site' + user = mod_tmp_path / 'user' + syst.mkdir() + site.mkdir() + user.mkdir() + with open(syst / 'rose.conf', 'w+') as conf: + conf.write(dedent(''' + all=syst + syst=syst + ''')) + with open(site / 'rose.conf', 'w+') as conf: + conf.write(dedent(''' + all=site + site=site + ''')) + with open(user / 'rose.conf', 'w+') as conf: + conf.write(dedent(''' + all=user + user=user + ''')) + + # patch the ResourceLocator + mod_monkeypatch.setattr( + 'metomi.rose.resource.ResourceLocator.SYST_CONF_PATH', syst + ) + mod_monkeypatch.setattr( + 'metomi.rose.resource.ResourceLocator.USER_CONF_PATH', user + ) + + return tuple(map(str, (syst, site, user))) + + +@pytest.fixture +def resource_locator(): + """Return a ResourceLocator instance for testing. + + Wipes the cached instance after each use. + """ + resource_locator = ResourceLocator() + yield resource_locator + # prevent this instance being reused + del resource_locator + ResourceLocator._DEFAULT_RESOURCE_LOCATOR = None + + +def test_default(sys_site_user_config, resource_locator): + """It should pick up the system and user config by default.""" + conf = resource_locator.get_conf() + # both the syst and user config should have been loaded + assert conf.get(['syst']).value == 'syst' + assert conf.get(['user']).value == 'user' + # the syst config should have been loaded before the user config + assert conf.get(['all']).value == 'user' + assert set(conf.value) == {'syst', 'user', 'all'} + + +def test_skip_no_read(sys_site_user_config, resource_locator, monkeypatch): + """It should skip config files it can't read.""" + # make it look like all files are not readable. + monkeypatch.setattr( + 'os.access', + lambda x, y: False + ) + conf = resource_locator.get_conf() + assert conf.value == {} + + +def test_rose_conf_path_blank( + sys_site_user_config, + resource_locator, + monkeypatch +): + """Setting ROSE_CONF_PATH= should prevent any conf files being loaded.""" + monkeypatch.setenv(ROSE_CONF_PATH, '') + conf = resource_locator.get_conf() + assert conf.value == {} + + +def test_rose_conf_path( + sys_site_user_config, + resource_locator, + monkeypatch +): + """If ROSE_CONF_PATH is defined no other files should be loaded.""" + # set ROSE_CONF_PATH to point at the system config + syst, site, *_ = sys_site_user_config + monkeypatch.setenv(ROSE_CONF_PATH, syst) + # set the ROSE_SITE_CONF_PATH just to make sure it is ignored + monkeypatch.setenv(ROSE_SITE_CONF_PATH, site) + conf = resource_locator.get_conf() + assert conf.get(['syst']).value == 'syst' + assert set(conf.value) == {'syst', 'all'} + + +def test_rose_site_conf_path( + sys_site_user_config, + resource_locator, + monkeypatch +): + """If ROSE_SITE_CONF_PATH is defined it should be loaded.""" + # set ROSE_SITE_CONF_PATH to point at the system config + _, site, *_ = sys_site_user_config + monkeypatch.setenv(ROSE_SITE_CONF_PATH, site) + conf = resource_locator.get_conf() + assert conf.get(['site']).value == 'site' + assert conf.get(['user']).value == 'user' + assert conf.get(['all']).value == 'user' + assert set(conf.value) == {'syst', 'site', 'user', 'all'} diff --git a/sphinx/conf.py b/sphinx/conf.py index 1aca6f7907..1660a8430d 100644 --- a/sphinx/conf.py +++ b/sphinx/conf.py @@ -79,7 +79,7 @@ # mapping to other Sphinx projects # (allows us to reference objects from other projects) -cylc_version = 'current' +cylc_version = '8.0a2' intersphinx_mapping = { 'cylc': ( f'https://cylc.github.io/cylc-doc/{cylc_version}/html/', None diff --git a/sphinx/hyperlinks.rst b/sphinx/hyperlinks.rst index bdc12a7889..9129d67fc3 100644 --- a/sphinx/hyperlinks.rst +++ b/sphinx/hyperlinks.rst @@ -10,6 +10,8 @@ :start-line: 1 .. _Cylc: https://cylc.github.io/ +.. _Cylc Flow: https://github.com/cylc/cylc-flow +.. _Cylc Rose: https://github.com/cylc/cylc-rose .. _Cylc User Guide: https://cylc.github.io/documentation/#the-cylc-user-guide .. _Cylc Suite Design Guide: https://cylc.github.io/cylc-doc/current/html/suite-design-guide/suite-design-guide-master.html .. _EmPy: http://www.alcyone.com/software/empy/ diff --git a/sphinx/installation.rst b/sphinx/installation.rst index 3994296828..3d380ba311 100644 --- a/sphinx/installation.rst +++ b/sphinx/installation.rst @@ -1,113 +1,87 @@ .. include:: hyperlinks.rst :start-line: 1 + Installation ============ -.. _GitHub: https://github.com/metomi/rose -.. _archives: https://github.com/metomi/rose/tags +Rose runs on Unix-like systems including Linux and MacOS. -The source code for Rose is available via `GitHub`_, code -releases are available in ``zip`` and ``tar.gz`` `archives`_. +Quick Installation +------------------ -1. If you wish to do so create a new environment using Conda, Venv or some - other environment manager. -2. Install using ``pip install metomi-rose`` -3. Check system compatibility by running :ref:`command-rose-check-software`. +With ``conda`` (recommended):: + $ conda install metomi-rose -System Requirements -------------------- +With ``pip`` (you will need to ensure `Non Python Dependencies`_ are met):: -Rose runs on Unix/Linux systems and is known to run on RHEL6 and a number of -systems including those documented under `metomi-vms`_. + $ pip install metomi-rose -System compatibility can be tested using the :ref:`command-rose-check-software` -command. +Installing With Cylc +^^^^^^^^^^^^^^^^^^^^ -.. script-include:: rose check-software --rst +Rose does not require and is distributed independently of `Cylc`_. +To use Rose with Cylc you will need to install `Cylc Flow`_ and `Cylc Rose`_ +into the same Python environment as Rose. -Host Requirements ------------------ +With ``conda`` (recommended):: -Whilst you can install and use Rose & Cylc on a single host (i.e. machine), -most installations are likely to be more complex. There are five types of -installation: + $ conda install cylc-flow cylc-rose -Hosts For Running Cylc Suites -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +With ``pip`` (you will need to ensure `Non Python Dependencies`_ are met):: -Each user can just run their suites on the host where they issue the -:ref:`command-rose-suite-run` command. However, the local host may not meet the -necessary requirements for connectivity (to the hosts running the tasks) -or for availability (the host needs to remain up for the duration of -the suite). Therefore, Rose can be configured to run the suites on -remote hosts. If multiple hosts are available, by default the host with -the lowest system load average will be used. + $ pip install cylc-flow cylc-rose -Installation requirements: - * Rose, Cylc, Bash, Python, jinja2. - * Subversion & FCM *(only if you want* :ref:`command-rose-suite-run` *to - install files from Subversion using FCM keywords).* -Connectivity requirements: - * Must be able to submit tasks to the hosts which run the suite tasks, - either directly or via SSH access to another host. +.. note:: -Hosts For Running Tasks In Suites -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + `Cylc`_ is a distributed system, not all dependencies are required on all + platforms. -Installation requirements: - * Rose, Cylc, Bash, Python. - * Subversion & FCM *only if you want to use the Rose install utility - to install files from Subversion using FCM keywords*. + See the `Cylc`_ installation instructions for more information. -Connectivity requirements: - * Must be able to communicate back to the hosts running the Cylc suites - via HTTPS (preferred), HTTP or SSH. + .. TODO -Hosts For Running User Interactive Tools -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + This reference will pass once intersphinx has a more contemporary + version of cylc-doc to point at (see conf.py) -Installation requirements: - * Rose, Cylc, Bash, Python, requests, Subversion, FCM, - Pygraphviz (+ graphviz). + See the :ref:`Cylc installation instructions ` for more + information. -Connectivity requirements: - * Must have HTTP access to the hosts running the Rosie web service. - * Must have access to the Rosie Subversion repositories via the - appropriate protocol. - * Must have HTTP and SSH access to the hosts running the Cylc suites. - * Must share user accounts and ``$HOME`` directories with the hosts running - the Cylc suites. +Non Python Dependencies +^^^^^^^^^^^^^^^^^^^^^^^ -Hosts For Rosie Subversion Repositories And The Rosie Web Services -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The following packages are installed by ``conda`` but not by ``pip``: -Typically you will only need a single host but you can have multiple -repositories on different hosts if you require this. +* FCM +* Perl +* Python3 +* Subversion -Installation requirements: - * Rose, Bash, Python, jinja2, sqlalchemy, Subversion. +If installing via ``pip`` run :ref:`command-rose-check-software` to ensure +non-Python dependencies are satisfied. .. note:: - This section has assumed that you wish to use Rose & Cylc (including - their respective GUIs) and use Rosie to store your suites. However, - there are many ways of using Rose. For instance: + Subversion & FCM are required for installing files from Subversion using FCM + keywords by: - * You can use Rose without using cylc. - * You can use Rose & Cylc but not use Rosie. - * You can use Rose & Cylc from the command line without using their GUIs. + + ``rose app-run`` + + ``rose task-run`` + + ``cylc install`` Configuring Rose ---------------- -``etc/rose.conf`` - You should add/edit this file to meet the requirement of your site. - Examples can be found at the ``etc/rose.conf.example`` file in your - Rose distribution. See also :rose:file:`rose.conf` in the API reference. +Rose configuration files can be located in the following places: + +* ``/etc/.metomi/rose.conf`` +* ``$ROSE_SITE_CONF_PATH/.metomi/rose.conf`` +* ``$HOME/.metomi/rose.conf`` + +See :rose:file:`rose.conf` in the API reference for more information. Configuring Rosie Client @@ -121,6 +95,7 @@ a web interface for suite discovery and lookup. If users at your site are able to access Rosie services on the Internet or if someone else has already configured Rosie services at your site, all you need to do is configure the client to talk to the servers. + Refer to the `Configuring a Rosie Server`_ section if you need to configure a Rosie server for your site. @@ -281,7 +256,7 @@ Add the following hook scripts to the repository: #!/usr/bin/env bash exec /sbin/rosa svn-post-commit "$@" -You should replace ``/path/to/rose/`` with the location of your Rose +You should replace ```` with the location of your Rose installation. Make sure the hook scripts are executable. @@ -292,9 +267,11 @@ are committed to the repository. Edit the :rose:conf:`rose.conf[rosie-db]` settings to point to your host machine and provide relevant paths such as the location for your repository and database. -Once you have done that, create the Rosie database by running:: +Once you have done that, create the Rosie database by running: + +.. code-block:: sub - /path/to/rose/sbin/rosa db-create + /sbin/rosa db-create Make sure that the account that runs the repository hooks has read/write access to the database and database directory. @@ -302,13 +279,17 @@ access to the database and database directory. You can test that everything is working using the built-in web server. Edit the :rose:conf:`rose.conf[rosie-disco]` settings to configure the web server's log directory and port number. Start the web server -by running:: +by running: + +.. code-block:: sub - setsid /path/to/rose/bin/rosie disco start 0&1 & + setsid /bin/rosie disco start 0&1 & Check that the server is up and running using ``curl`` or a local web browser. E.g. If you have configured the server's port to be 1234, -you can do:: +you can do: + +.. code-block:: sub curl -I http://localhost:1234/ @@ -317,10 +298,12 @@ It should return a HTTP code 200. Alternatively you can run the Rosie web service under Apache ``mod_wsgi``. To do this you will need to set up an Apache module configuration file (typically in ``/etc/httpd/conf.d/rose-wsgi.conf``) containing the -following (with the paths set appropriately):: +following (with the paths set appropriately): + +.. code-block:: sub - WSGIPythonPath /path/to/rose/lib/python - WSGIScriptAlias /rosie /path/to/rose/lib/python/rosie/ws.py + WSGIPythonPath /lib/python + WSGIScriptAlias /rosie /lib/python/rosie/ws.py Use the Apache log at e.g. ``/var/log/httpd/`` to debug problems. From 6eb240c6ab41a0b8ea2df2408f61325cf564bc0a Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Wed, 13 Jan 2021 16:29:58 +0000 Subject: [PATCH 29/78] remove ROSE_HOME --- bin/rose-host-select | 8 ++- bin/rose-mpi-launch | 3 +- etc/rose-conf-mode.el | 7 ++- lib/bash/rose_usage | 2 +- metomi/rose/metadata_graph.py | 2 - metomi/rose/resource.py | 57 +++------------------- metomi/rosie/ws.py | 11 +++-- sphinx/api/configuration/metadata.rst | 2 +- sphinx/api/configuration/site-and-user.rst | 6 +-- sphinx/api/environment-variables.rst | 37 +++++++------- sphinx/api/rose-bash-library.rst | 2 +- t/lib/bash/test_header | 5 +- t/rose-app-run/16-file-mode-bad.t | 6 +-- 13 files changed, 48 insertions(+), 100 deletions(-) diff --git a/bin/rose-host-select b/bin/rose-host-select index e3b7b6db56..d3a342d5cc 100755 --- a/bin/rose-host-select +++ b/bin/rose-host-select @@ -27,9 +27,6 @@ # Select a host from a set of groups or names by load, by free memory # or by random. # -# Use settings in `$ROSE_HOME/etc/rose.conf` and `$HOME/.metomi/rose.conf` -# to determine the ranking method. -# # Print the selected host name. # # OPTIONS @@ -75,8 +72,9 @@ # # CONFIGURATION # The command reads its settings from the `[rose-host-select]` section in -# `$ROSE_HOME/etc/rose.conf` and `$HOME/.metomi/rose.conf`. All settings -# are optional. Type `rose config rose-host-select` to print settings. +# the Rose configuration. All settings are optional. Type +# `rose config rose-host-select` to print settings. +# # Valid settings are: # # default = GROUP/HOST ... diff --git a/bin/rose-mpi-launch b/bin/rose-mpi-launch index ad534df38b..053aa697f2 100755 --- a/bin/rose-mpi-launch +++ b/bin/rose-mpi-launch @@ -54,7 +54,8 @@ # # CONFIGURATION # The command reads from the `[rose-mpi-launch]` section in -# `$ROSE_HOME/etc/rose.conf` and `$HOME/.metomi/rose.conf`. +# the Rose configuration. +# # Valid settings are: # # launcher-list=LIST diff --git a/etc/rose-conf-mode.el b/etc/rose-conf-mode.el index b99ceb93ad..febdc4936c 100644 --- a/etc/rose-conf-mode.el +++ b/etc/rose-conf-mode.el @@ -21,10 +21,9 @@ ;; An emacs syntax highlighting mode for the various rose .conf files ;; ;; = Instructions = -;; Place this file in a directory on your emacs load path (or symlink it) -;; e.g. -;; mkdir -p ~/.emacs.d/lisp -;; ln -s $ROSE_HOME/etc/rose-conf-mode.el ~/.emacs.d/lisp/ +;; Place this file in a directory on your emacs load path. e.g: +;; +;; ~/.emacs.d/lisp ;; ;; and in your .emacs file: ;; diff --git a/lib/bash/rose_usage b/lib/bash/rose_usage index 864ee32c95..efee8a8105 100644 --- a/lib/bash/rose_usage +++ b/lib/bash/rose_usage @@ -45,7 +45,7 @@ function rose_usage() { } } } - }' "${ROSE_USAGE_FILE:-$0}" >&"$FD" + }' "$0" >&"$FD" if [[ -n $CODE ]]; then exit "$CODE" fi diff --git a/metomi/rose/metadata_graph.py b/metomi/rose/metadata_graph.py index 3686b279e7..a84b10cb01 100644 --- a/metomi/rose/metadata_graph.py +++ b/metomi/rose/metadata_graph.py @@ -264,8 +264,6 @@ def main(): if opts.conf_dir: os.chdir(opts.conf_dir) opts.conf_dir = os.getcwd() - sys.path.append( - metomi.rose.resource.ResourceLocator.default().get_util_home()) metomi.rose.macro.add_opt_meta_paths(opts.meta_path) config_file_path = os.path.join(opts.conf_dir, metomi.rose.SUB_CONFIG_NAME) diff --git a/metomi/rose/resource.py b/metomi/rose/resource.py index d73667e89a..b7725c37b6 100644 --- a/metomi/rose/resource.py +++ b/metomi/rose/resource.py @@ -25,27 +25,13 @@ import sys from importlib.machinery import SourceFileLoader +import metomi.rose from metomi.rose.config import ConfigLoader, ConfigNode ERROR_LOCATE_OBJECT = "Could not locate {0}" -def get_util_home(*args): - """Return ROSE_LIB or the dirname of the dirname of sys.argv[0]. - - If args are specified, they are added to the end of returned path. - - """ - try: - value = os.environ["ROSE_LIB"] - except KeyError: - value = os.path.abspath(__file__) - for _ in range(3): # assume __file__ under $ROSE_LIB/metomi/rose/ - value = os.path.dirname(value) - return os.path.join(value, *args) - - class ResourceError(Exception): """A named resource not found.""" @@ -56,6 +42,7 @@ def __init__(self, key): ROSE_CONF_PATH = 'ROSE_CONF_PATH' ROSE_SITE_CONF_PATH = 'ROSE_SITE_CONF_PATH' +ROSE_INSTALL_ROOT = Path(metomi.rose.__file__).parent class ResourceLocator: @@ -93,10 +80,10 @@ def __init__(self, namespace=None, util=None, paths=None): if paths: self.paths = list(paths) else: - home = self.get_util_home() - name = self.get_util_name("-") - self.paths = [os.path.join(home, "etc", name), - os.path.join(home, "etc")] + self.paths = [ + (ROSE_INSTALL_ROOT / 'etc') / self.get_util_name("-"), + ROSE_INSTALL_ROOT / 'etc' + ] self.conf = None def get_conf(self): @@ -137,7 +124,7 @@ def get_conf(self): def get_doc_url(self): """Return the URL of Rose documentation.""" - default = "file://%s/doc/" % self.get_util_home() + default = f"file://{ROSE_INSTALL_ROOT}/doc/" return self.get_conf().get_value(["rose-doc"], default=default) def get_synopsis(self): @@ -154,15 +141,6 @@ def get_synopsis(self): except IOError: return None - @classmethod - def get_util_home(cls, *args): - """Return ROSE_HOME or the dirname of the dirname of sys.argv[0]. - - If args are specified, they are added to the end of returned path. - - """ - return get_util_home(*args) - def get_util_name(self, separator=" "): """Return the name of the Rose utility, e.g. "rose app-run". @@ -181,27 +159,6 @@ def get_util_name(self, separator=" "): except KeyError: return os.path.basename(sys.argv[0]) - def get_version(self, ignore_environment=False): - """return the current metomi.rose_version number. - - By default pass through the value of the ``ROSE_VERSION`` environment - variable. - - Args: - ignore_environment (bool): Return the value extracted from the - ``rose-version`` file. - """ - version = None - if not ignore_environment: - version = os.getenv("ROSE_VERSION") - if not version: - for line in open(self.get_util_home("rose-version")): - if line.startswith("ROSE_VERSION="): - value = line.replace("ROSE_VERSION=", "") - version = value.strip(string.whitespace + "\";") - break - return version - def locate(self, key): """Return the location of the resource key.""" key = os.path.expanduser(key) diff --git a/metomi/rosie/ws.py b/metomi/rosie/ws.py index 1fb24f12e3..c09af0da4f 100644 --- a/metomi/rosie/ws.py +++ b/metomi/rosie/ws.py @@ -35,9 +35,10 @@ import json import logging import os +from pathlib import Path +import pkg_resources import pwd import signal -import pkg_resources from time import sleep from tornado.ioloop import IOLoop, PeriodicCallback import tornado.log @@ -45,6 +46,7 @@ import wsgiref from metomi.isodatetime.data import get_timepoint_from_seconds_since_unix_epoch +from metomi.rose import __version__ as ROSE_VERSION from metomi.rose.host_select import HostSelector from metomi.rose.opt_parse import RoseOptionParser from metomi.rose.resource import ResourceLocator @@ -80,7 +82,7 @@ def __init__(self, service_root_mode=False, *args, **kwargs): if self.props["host_name"] and "." in self.props["host_name"]: self.props["host_name"] = ( self.props["host_name"].split(".", 1)[0]) - self.props["rose_version"] = ResourceLocator.default().get_version() + self.props["rose_version"] = ROSE_VERSION # Get location of HTML files from package rosie_lib = os.path.join( @@ -137,8 +139,9 @@ def __init__(self, service_root_mode=False, *args, **kwargs): handlers = [root_handler] + prefix_handlers settings = dict( autoreload=True, - static_path=ResourceLocator.default().get_util_home( - "lib", "html", "static"), + static_path=str( + Path(metomi.rosie.__file__).parent / 'lib/html/static' + ) ) super( RosieDiscoServiceApplication, self).__init__(handlers, **settings) diff --git a/sphinx/api/configuration/metadata.rst b/sphinx/api/configuration/metadata.rst index bfd5734101..07f3792ed1 100644 --- a/sphinx/api/configuration/metadata.rst +++ b/sphinx/api/configuration/metadata.rst @@ -39,7 +39,7 @@ of precedence: See :ref:`app-meta-loc` for more details. The configuration metadata that controls default behaviour will be located in -``$ROSE_HOME/etc/rose-meta/``. +``etc/rose-meta/`` within the ``metomi.rose`` Python installation. Configuration Metadata File diff --git a/sphinx/api/configuration/site-and-user.rst b/sphinx/api/configuration/site-and-user.rst index 22451d075a..fe6715c1af 100644 --- a/sphinx/api/configuration/site-and-user.rst +++ b/sphinx/api/configuration/site-and-user.rst @@ -8,11 +8,7 @@ site configuration file and per user via the user configuration file. Any configuration in the site configuration overrides the default, and any configuration in the user configuration overrides the site configuration and the default. Rose expects these files to be in the modified INI format -described in :ref:`Rose Configuration Format`. Rose utilities search for its -site configuration at ``$ROSE_HOME/etc/rose.conf`` where -``$ROSE_HOME/bin/rose`` is the location of the ``rose`` command, and they -search for the user configuration at ``$HOME/.metomi/rose.conf`` where -``$HOME`` is the home directory of the current user. +described in :ref:`Rose Configuration Format`. You can also override many internal constants of :ref:`command-rose-config-edit` and diff --git a/sphinx/api/environment-variables.rst b/sphinx/api/environment-variables.rst index 6b53bd0d3e..19b7f7bc31 100644 --- a/sphinx/api/environment-variables.rst +++ b/sphinx/api/environment-variables.rst @@ -56,12 +56,26 @@ Rose Environment Variables .. envvar:: ROSE_CONF_PATH Description - Specify a colon (``:``) separated list of paths for searching and loading - site/user configuration. If this environment variable is not defined, the - normal behaviour is to search for and load :rose:file:`rose.conf` from - ``$ROSE_HOME/etc`` and then ``$HOME/.metomi``. + If defined this will override the default configuration search path. + + Provide a colon (``:``) separated list of paths to search for + ``rose.conf`` files. + + If set to an empty string no config files will be loaded. Used By * :ref:`command-rose-test-battery` + See also + * :envvar:`ROSE_SITE_CONF_PATH` + * :rose:file:`rose.conf` + +.. envvar:: ROSE_SITE_CONF_PATH + + Description + Defines the location of the "site" configuration. Configurations defined + here can be overridden by the "user" configuration. + See also + * :envvar:`ROSE_CONF_PATH` + * :rose:file:`rose.conf` .. envvar:: ROSE_CYCLING_MODE @@ -124,21 +138,6 @@ Rose Environment Variables * :ref:`command-rose-app-run` * :ref:`command-rose-task-run` -.. envvar:: ROSE_HOME - - Description - Specifies the path to the Rose home directory. - Used and Provided By - * ``rose`` - -.. envvar:: ROSE_HOME_BIN - - Description - Specifies the path to the ``bin/`` or ``sbin/`` directory of the current - Rose utility. - Used and Provided By - * ``rose`` - .. envvar:: ROSE_LAUNCHER Description diff --git a/sphinx/api/rose-bash-library.rst b/sphinx/api/rose-bash-library.rst index 9f806e2550..bc21a392e8 100644 --- a/sphinx/api/rose-bash-library.rst +++ b/sphinx/api/rose-bash-library.rst @@ -4,7 +4,7 @@ Rose Bash Library The Rose bash library lives in ``lib/bash/``. To import a module, load the file into your script. E.g. To load ``rose_usage``, you would do:: - . $ROSE_HOME/lib/bash/rose_usage + . lib/bash/rose_usage The modules are: diff --git a/t/lib/bash/test_header b/t/lib/bash/test_header index 21b344571b..36bc04b15e 100644 --- a/t/lib/bash/test_header +++ b/t/lib/bash/test_header @@ -21,15 +21,12 @@ # test_header # # SYNOPSIS -# . $ROSE_HOME/t/lib/bash/test_header +# . t/lib/bash/test_header # # DESCRIPTION # Provide bash shell functions for writing tests for "rose" commands to # output in Perl's TAP format. Add "set -eu". Create a temporary working # directory $TEST_DIR and change to it. Automatically increment test number. -# If $ROSE_HOME is not specified, set it to point to the "rose" source tree -# containing this script. Add $ROSE_HOME/bin to the front of $PATH. -# # FUNCTIONS # tests N # echo "1..$N". diff --git a/t/rose-app-run/16-file-mode-bad.t b/t/rose-app-run/16-file-mode-bad.t index b71dd3d136..8412e27fde 100755 --- a/t/rose-app-run/16-file-mode-bad.t +++ b/t/rose-app-run/16-file-mode-bad.t @@ -27,8 +27,8 @@ test_init <<'__CONFIG__' [command] default=true -[file:COPYING] -source=$ROSE_HOME/COPYING +[file:beef] +source=wellington # Oops, typos mode=5ym1ink __CONFIG__ @@ -38,7 +38,7 @@ test_setup run_fail "$TEST_KEY" rose app-run --config=../config -q file_cmp "$TEST_KEY.out" "$TEST_KEY.out" Date: Wed, 13 Jan 2021 17:25:12 +0000 Subject: [PATCH 30/78] tidy etc/ dir - add rose resource cmd --- bin/rose-resource | 40 ++++++++ etc/images/rose-config-edit/change_icon.xpm | 78 --------------- etc/images/rose-config-edit/error_icon.xpm | 96 ------------------- etc/images/rose-config-edit/null_icon.xpm | 23 ----- etc/opt.example/README | 8 -- etc/rose-config-edit/.gtkrc-2.0 | 1 - .../rose/etc/syntax}/rose-conf-mode.el | 0 .../rose/etc/syntax}/rose-conf.lang | 0 {etc => metomi/rose/etc/syntax}/rose-conf.vim | 0 {etc => metomi/rose/etc/syntax}/rose-conf.xml | 0 metomi/rose/macro.py | 4 +- metomi/rose/opt_parse.py | 12 ++- metomi/rose/resource.py | 48 ++++++++-- sphinx/ext/auto_cli_doc.py | 5 + sphinx/getting-started.rst | 23 ++--- 15 files changed, 106 insertions(+), 232 deletions(-) create mode 100755 bin/rose-resource delete mode 100644 etc/images/rose-config-edit/change_icon.xpm delete mode 100644 etc/images/rose-config-edit/error_icon.xpm delete mode 100644 etc/images/rose-config-edit/null_icon.xpm delete mode 100644 etc/opt.example/README delete mode 100644 etc/rose-config-edit/.gtkrc-2.0 rename {etc => metomi/rose/etc/syntax}/rose-conf-mode.el (100%) rename {etc => metomi/rose/etc/syntax}/rose-conf.lang (100%) rename {etc => metomi/rose/etc/syntax}/rose-conf.vim (100%) rename {etc => metomi/rose/etc/syntax}/rose-conf.xml (100%) diff --git a/bin/rose-resource b/bin/rose-resource new file mode 100755 index 0000000000..ac1be09419 --- /dev/null +++ b/bin/rose-resource @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +#------------------------------------------------------------------------------- +# Copyright (C) British Crown (Met Office) & Contributors. +# +# This file is part of Rose, a framework for meteorological suites. +# +# Rose 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. +# +# Rose 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 Rose. If not, see . +#------------------------------------------------------------------------------- +# NAME +# rose resource +# +# SYNOPSIS +# rose resource RESOURCE_PATH +# +# DESCRIPTION +# Display the path of resources in the Rose Python installation. +# +# * If the requested resource exists and is a file its path is printed. +# * If the requested resource exists and is a directory it is listed. +# +# Provide no arguments to see a list of top-level resources. +# +# OPTIONS +# --quiet, -q +# Decrement verbosity. +# --verbose, -v +# Increment verbosity. +#------------------------------------------------------------------------------- +exec python3 -m metomi.rose.resource "$@" diff --git a/etc/images/rose-config-edit/change_icon.xpm b/etc/images/rose-config-edit/change_icon.xpm deleted file mode 100644 index 8e731df3ba..0000000000 --- a/etc/images/rose-config-edit/change_icon.xpm +++ /dev/null @@ -1,78 +0,0 @@ -/* XPM */ -static char * change_icon_xpm[] = { -"12 19 56 1", -" c None", -". c #0E2171", -"+ c #1C3282", -"@ c #445CA3", -"# c #5268AC", -"$ c #435AA2", -"% c #1D3486", -"& c #1D3284", -"* c #647CBA", -"= c #97ABD4", -"- c #9EB1D8", -"; c #96AAD4", -"> c #637AB9", -", c #253E90", -"' c #122778", -") c #344E9E", -"! c #88A0D0", -"~ c #95ABD5", -"{ c #93A9D4", -"] c #859ECF", -"^ c #354E9F", -"/ c #485FA7", -"( c #14297A", -"_ c #334DA0", -": c #6985C3", -"< c #6E89C5", -"[ c #6D89C5", -"} c #6A87C4", -"| c #6481C1", -"1 c #304A9D", -"2 c #1B3285", -"3 c #132879", -"4 c #274198", -"5 c #3B56A8", -"6 c #415DAD", -"7 c #3F5BAC", -"8 c #3752A5", -"9 c #253E95", -"0 c #1B3185", -"a c #1D358A", -"b c #253F96", -"c c #29439A", -"d c #29449B", -"e c #223B92", -"f c #1D368B", -"g c #162D81", -"h c #1C348B", -"i c #1F388F", -"j c #1E368D", -"k c #1A3187", -"l c #1E3589", -"m c #273E8E", -"n c #20388C", -"o c #182E83", -"p c #1C3389", -"q c #22398A", -" ", -" ", -" ", -" ", -" ", -" . ", -" +@#$% ", -" &*=-;>, ", -" ')!~~{]^/ ", -" (_:<[}|12 ", -" 345667890 ", -" abcd4ef ", -" ghiijkl ", -" mnopq ", -" ", -" ", -" ", -" ", -" "}; diff --git a/etc/images/rose-config-edit/error_icon.xpm b/etc/images/rose-config-edit/error_icon.xpm deleted file mode 100644 index 993ed1bde2..0000000000 --- a/etc/images/rose-config-edit/error_icon.xpm +++ /dev/null @@ -1,96 +0,0 @@ -/* XPM */ -static char * error_icon_xpm[] = { -"14 20 73 1", -" c None", -". c #9D7A77", -"+ c #9D7773", -"@ c #B0A8A6", -"# c #F66662", -"$ c #F52121", -"% c #B4A4A2", -"& c #BB847D", -"* c #FF2127", -"= c #FF1D23", -"- c #BB3D3D", -"; c #AA9794", -"> c #F85E5A", -", c #B31919", -"' c #B22020", -") c #F73D3D", -"! c #AA8988", -"~ c #D1736B", -"{ c #FD2D2D", -"] c #1A0606", -"^ c #1B0909", -"/ c #FD5955", -"( c #CF504D", -"_ c #AA7F7C", -": c #FF4747", -"< c #FF3939", -"[ c #2A0F0E", -"} c #2B1817", -"| c #FE716A", -"1 c #FE6966", -"2 c #A67370", -"3 c #E65E5A", -"4 c #FF3131", -"5 c #FF4545", -"6 c #3D1917", -"7 c #3F2B27", -"8 c #FE807A", -"9 c #E6726C", -"0 c #AD6461", -"a c #FF3333", -"b c #FF524D", -"c c #A24A46", -"d c #A4655E", -"e c #FF8E85", -"f c #FF978F", -"g c #AD6A67", -"h c #F64040", -"i c #FF2929", -"j c #FF3D3D", -"k c #FF5551", -"l c #49201F", -"m c #4A3732", -"n c #FF9C91", -"o c #FEACA2", -"p c #F68F88", -"q c #AD4D4B", -"r c #FF191F", -"s c #FF5753", -"t c #F8716A", -"u c #F78F87", -"v c #FEAEA4", -"w c #FEB0A6", -"x c #FF9E93", -"y c #AC6764", -"z c #BF7171", -"A c #BF7979", -"B c #BF7F7F", -"C c #BF8785", -"D c #BF8F8D", -"E c #BF9693", -"F c #BF9D99", -"G c #BFA19D", -"H c #BFA39F", -" ", -" ", -" ", -" ", -" ", -" .+ ", -" @#$% ", -" &*=- ", -" ;>,')! ", -" ~{]^/( ", -" _:<[}|12 ", -" 34567889 ", -" 0a 1: + reporter.report('Only one argument accepted\n', level=Reporter.FAIL) + sys.exit(1) + if len(args) == 0: + key = ROSE_INSTALL_ROOT / 'etc' + path = ResourceLocator(paths=[ROSE_INSTALL_ROOT]).locate('etc') + is_top_level = True + else: + key = args[0] + try: + path = ResourceLocator().locate(key) + except ResourceError: + reporter.report('Resource not found\n', level=Reporter.FAIL) + sys.exit(1) + if path.is_file(): + print(path) + elif path.is_dir(): + print(f'{key}/') + for item in path.iterdir(): + if is_top_level: + item = item.relative_to(path) + else: + item = item.relative_to(path.parent) + print(f' {item}') + + +if __name__ == "__main__": + main() diff --git a/sphinx/ext/auto_cli_doc.py b/sphinx/ext/auto_cli_doc.py index e24659d698..7d805ef98c 100644 --- a/sphinx/ext/auto_cli_doc.py +++ b/sphinx/ext/auto_cli_doc.py @@ -125,7 +125,12 @@ def line_strip(lines): ... '' ... ]) ['a'] + >>> line_strip(['']) + [] + """ + if all(not line for line in lines): + return [] lines = list(lines) kill = [] for itt in range(0, len(lines)): diff --git a/sphinx/getting-started.rst b/sphinx/getting-started.rst index f2f87703a5..0d6f78b87f 100644 --- a/sphinx/getting-started.rst +++ b/sphinx/getting-started.rst @@ -56,25 +56,20 @@ Editor Syntax Highlighting -------------------------- There are ``gedit``, ``kate``, ``vim``, and ``emacs`` plugins for syntax -highlighting of Rose configuration files, located within the Rose installation: +highlighting of Rose configuration files, located within the Rose installation. -* ``etc/rose-conf.lang`` -* ``etc/rose-conf.xml`` -* ``etc/rose-conf.vim`` -* ``etc/rose-conf-mode.el`` +Run the following command to see the available syntax files and their +locations:: -The plugins contain setup instructions within. + $ rose resource syntax -.. _Pygments: https://pygments.org - -Additionally there is a `Pygments`_ lexer located in -``sphinx/ext/rose_lang.py``. +Each file contains setup instructions within. -.. hint:: - - You can locate your Rose installation using:: +.. _Pygments: https://pygments.org +.. _Rose Lang: https://github.com/metomi/rose/blob/master/sphinx/ext/rose_lang.py - rose version --long +Additionally there is a `Pygments`_ lexer located +`in the source code `_ Configuring Cylc From c538fcfac94f2d992e581fe2b87c23f9ace23ef4 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Thu, 14 Jan 2021 13:31:59 +0000 Subject: [PATCH 31/78] bash: remove rose_init --- .gitignore | 1 - bin/rose | 14 +++-- bin/rose-mpi-launch | 18 ++++--- bin/rose-tutorial | 5 +- etc/bin/rose-test-battery | 9 +--- lib/bash/rose_init | 55 -------------------- lib/bash/rose_init_site.example | 30 ----------- {lib => metomi/rose/etc/lib}/bash/rose_log | 2 +- {lib => metomi/rose/etc/lib}/bash/rose_usage | 2 +- metomi/rose/opt_parse.py | 2 +- metomi/rose/resource.py | 2 +- setup.py | 7 +-- sphinx/api/rose-bash-library.rst | 12 ++--- 13 files changed, 39 insertions(+), 120 deletions(-) delete mode 100755 lib/bash/rose_init delete mode 100644 lib/bash/rose_init_site.example rename {lib => metomi/rose/etc/lib}/bash/rose_log (98%) rename {lib => metomi/rose/etc/lib}/bash/rose_usage (97%) diff --git a/.gitignore b/.gitignore index a01daf43ab..1794ebeac4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ *.swp etc/rose.conf etc/opt -lib/bash/rose_init_site doc venv metomi_rose.egg-info diff --git a/bin/rose b/bin/rose index 7c9b1bd028..521add7c1a 100755 --- a/bin/rose +++ b/bin/rose @@ -37,10 +37,13 @@ if ${ROSE_DEBUG:-false}; then set -x fi -# shellcheck source=lib/bash/rose_init -# shellcheck source=lib/bash/rose_usage -. rose_init -rose_init +set -eu + +ROSE_NS=$(basename "$0") +ROSE_LIB="$(dirname "$(python3 -c "import metomi.rose; print(metomi.rose.__file__)")")" +ROSE_HOME_BIN=$(dirname "$(command -v rose)") +ROSE_VERSION="$(python3 -c "import metomi.rose; print(metomi.rose.__version__)")" +export ROSE_NS ROSE_LIB ROSE_HOME_BIN ROSE_VERSION # Print actual command of a command alias get_alias() { @@ -49,6 +52,7 @@ get_alias() { "$ROSE_HOME_BIN/$ROSE_NS-$NAME" } + # Print help for a given utility help_util() { local NAME=$1 @@ -118,6 +122,8 @@ help|h|?|--help|-h) if (($# == 0)); then { print_version + # shellcheck source=../metomi/rose/etc/lib/bash/rose_usage + . "$(rose resource lib/bash/rose_usage)" rose_usage echo echo "$ROSE_NS provides the following utilities:" diff --git a/bin/rose-mpi-launch b/bin/rose-mpi-launch index 053aa697f2..1bad013803 100755 --- a/bin/rose-mpi-launch +++ b/bin/rose-mpi-launch @@ -112,9 +112,13 @@ # DIAGNOSTICS # Return 0 on success, 1 or exit code of the launcher program on failure. #------------------------------------------------------------------------------- -# shellcheck source=lib/bash/rose_init -. rose_init -rose_init rose_log +set -eu +# shellcheck source=../metomi/rose/etc/lib/bash/rose_log +. "$(rose resource lib/bash/rose_log)" +# shellcheck source=../metomi/rose/etc/lib/bash/rose_usage +. "$(rose resource lib/bash/rose_usage)" + +ROSE_CMD="${ROSE_NS}-${ROSE_UTIL}" # ------------------------------------------------------------------------------ ROSE_COMMAND= @@ -201,7 +205,7 @@ else fi ROSE_LAUNCHER_LIST=${ROSE_LAUNCHER_LIST:-$( \ - rose config --default= "$ROSE_NS" launcher-list)} + rose config --default= "$ROSE_CMD" launcher-list)} export NPROC=${NPROC:-1} #------------------------------------------------------------------------------- @@ -245,7 +249,7 @@ if [[ -n $ROSE_COMMAND_FILE ]]; then fi ROSE_LAUNCHER_FILEOPTS=${ROSE_LAUNCHER_FILEOPTS:-$( \ rose config --default= \ - "$ROSE_NS" "launcher-fileopts.$ROSE_LAUNCHER_BASE")} + "$ROSE_CMD" "launcher-fileopts.$ROSE_LAUNCHER_BASE")} eval "info 2 exec $ROSE_LAUNCHER $ROSE_LAUNCHER_FILEOPTS $*" eval "exec $ROSE_LAUNCHER $ROSE_LAUNCHER_FILEOPTS $*" else @@ -256,9 +260,9 @@ else fi # Options ROSE_LAUNCHER_PREOPTS=${ROSE_LAUNCHER_PREOPTS:-$(rose config -E \ - --default= "$ROSE_NS" "launcher-preopts.$ROSE_LAUNCHER_BASE")} + --default= "$ROSE_CMD" "launcher-preopts.$ROSE_LAUNCHER_BASE")} ROSE_LAUNCHER_POSTOPTS=${ROSE_LAUNCHER_POSTOPTS:-$(rose config -E \ - --default= "$ROSE_NS" "launcher-postopts.$ROSE_LAUNCHER_BASE")} + --default= "$ROSE_CMD" "launcher-postopts.$ROSE_LAUNCHER_BASE")} else ROSE_LAUNCH_INNER= ROSE_LAUNCHER_PREOPTS= diff --git a/bin/rose-tutorial b/bin/rose-tutorial index f71386eed7..fdb8b4b795 100755 --- a/bin/rose-tutorial +++ b/bin/rose-tutorial @@ -30,8 +30,7 @@ # Make a copy of one of the tutorial SUITE in the cylc-run directory. # #------------------------------------------------------------------------------- -# shellcheck source=lib/bash/rose_init -. rose_init +set -eu mkdir -p "$HOME/cylc-run" usage () { @@ -86,7 +85,7 @@ fi # Copy files. echo -n "Copying tutorial files to ${DIRECTORY} ... " -rose_init 'rose_log' + mkdir -p "$(dirname "${DIRECTORY}")" if run rsync -rLptgoD "${ROSE_HOME}/etc/tutorial/$1/" "${DIRECTORY}" \ --exclude='.validate'; then diff --git a/etc/bin/rose-test-battery b/etc/bin/rose-test-battery index 79b09d20b7..a0d07bc923 100755 --- a/etc/bin/rose-test-battery +++ b/etc/bin/rose-test-battery @@ -48,14 +48,9 @@ # SEE ALSO # * `prove(1)` #------------------------------------------------------------------------------- -# shellcheck source=lib/bash/rose_init -# shellcheck source=lib/bash/rose_log -. rose_init -. rose_log - set -eu - -rose_init +# shellcheck source=../../metomi/rose/etc/lib/bash/rose_log +. "$(rose resource lib/bash/rose_log)" # Move to folder in which prove command should be run. TESTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" diff --git a/lib/bash/rose_init b/lib/bash/rose_init deleted file mode 100755 index c30d44f835..0000000000 --- a/lib/bash/rose_init +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# NAME -# rose_init -# -# SYNOPSIS -# . $ROSE_HOME/lib/bash/rose_init -# rose_init [FUNC ...] -# -# DESCRIPTION -# Initialise a bash script with the following: -# * "set -eu". -# * load the "rose_usage" function. -# * load any FUNC specified in the argument list. -#------------------------------------------------------------------------------- -# shellcheck source=lib/bash/rose_usage -. rose_usage - -function rose_init() { - set -eu - ROSE_NS=$(basename "$0") - ROSE_LIB="$(dirname "$(python3 -c "import metomi.rose; print(metomi.rose.__file__)")")" - ROSE_HOME_BIN=$(dirname "$(command -v rose)") - ROSE_VERSION="$(python3 -c "import metomi.rose; print(metomi.rose.__version__)")" - export ROSE_LIB ROSE_HOME_BIN ROSE_NS ROSE_VERSION - local LIB=$ROSE_LIB - if [[ -f $LIB/${FUNCNAME[0]}_site ]]; then - . "$LIB/${FUNCNAME[0]}_site" - fi - - local ITEM= - for ITEM in rose_usage "$@"; do - local FILE= - for FILE in $ITEM; do - . "$FILE" - done - done -} diff --git a/lib/bash/rose_init_site.example b/lib/bash/rose_init_site.example deleted file mode 100644 index fa0ba1e2d7..0000000000 --- a/lib/bash/rose_init_site.example +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# NAME -# rose_init_site -# -# SYNOPSIS -# . $ROSE_HOME/lib/bash/rose_init_site -# -# DESCRIPTION -# Site specified PATH, PYTHONPATH, etc. -#------------------------------------------------------------------------------- -#PYTHONPATH=/path/to/site/specific/python/lib:$PYTHONPATH -#PATH=/path/to/site/specific/bin:$PATH diff --git a/lib/bash/rose_log b/metomi/rose/etc/lib/bash/rose_log similarity index 98% rename from lib/bash/rose_log rename to metomi/rose/etc/lib/bash/rose_log index 034d26c2f9..ebfb721990 100644 --- a/lib/bash/rose_log +++ b/metomi/rose/etc/lib/bash/rose_log @@ -21,7 +21,7 @@ # rose_log # # SYNOPSIS -# . $ROSE_HOME/lib/bash/rose_log +# . rose_log # info 1 "Hello world" # info 2 "Hello world" # echo "Hello world" | out diff --git a/lib/bash/rose_usage b/metomi/rose/etc/lib/bash/rose_usage similarity index 97% rename from lib/bash/rose_usage rename to metomi/rose/etc/lib/bash/rose_usage index efee8a8105..761a6bcf0c 100644 --- a/lib/bash/rose_usage +++ b/metomi/rose/etc/lib/bash/rose_usage @@ -21,7 +21,7 @@ # rose_usage # # SYNOPSIS -# . $ROSE_HOME/lib/bash/rose_usage +# . $rose_usage # rose_usage [CODE] # # DESCRIPTION diff --git a/metomi/rose/opt_parse.py b/metomi/rose/opt_parse.py index ff11fb529b..dcbf878634 100644 --- a/metomi/rose/opt_parse.py +++ b/metomi/rose/opt_parse.py @@ -17,7 +17,7 @@ """Common option parser for Rose command utilities.""" from optparse import OptionParser -import metomi.rose.resource +import metomi.rose.resource class RoseOptionParser(OptionParser): diff --git a/metomi/rose/resource.py b/metomi/rose/resource.py index 42c94fb8ee..7d892f4979 100644 --- a/metomi/rose/resource.py +++ b/metomi/rose/resource.py @@ -80,7 +80,7 @@ def __init__(self, namespace=None, util=None, paths=None): self.namespace = namespace self.util = util if paths: - self.paths = list(paths) + self.paths = list(map(Path, paths)) else: self.paths = [ (ROSE_INSTALL_ROOT / 'etc') / self.get_util_name("-"), diff --git a/setup.py b/setup.py index 56bde08490..0dd3ac0397 100644 --- a/setup.py +++ b/setup.py @@ -84,9 +84,10 @@ def find_version(*file_paths): version=find_version("metomi", "rose", "__init__.py"), # Options - scripts=glob(join("bin", "*")) - + glob(join("sbin", "*")) - + glob(join("lib", "bash", "*")), + scripts=( + glob(join("bin", "*")) + + glob(join("sbin", "*")) + ), install_requires=INSTALL_REQUIRES, extras_require=EXTRAS_REQUIRE, tests_require=TESTS_REQUIRE, diff --git a/sphinx/api/rose-bash-library.rst b/sphinx/api/rose-bash-library.rst index bc21a392e8..17869cbb31 100644 --- a/sphinx/api/rose-bash-library.rst +++ b/sphinx/api/rose-bash-library.rst @@ -1,16 +1,16 @@ Rose Bash Library ================= -The Rose bash library lives in ``lib/bash/``. To import a module, load the file -into your script. E.g. To load ``rose_usage``, you would do:: +Rose includes some bash modules, they can be located by running:: - . lib/bash/rose_usage + $ rose resource lib/bash + +These modules can be invoked like so:: + + . "$(rose resource lib/bash/rose_log)" The modules are: -``rose_init`` - Called by ``rose`` on initialisation. This is not meant to be for general - use. ``rose_log`` Provide functions to print log messages. ``rose_usage`` From e95def82b1d2198fbc3d6e3aae6fcffb6a49d3da Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Thu, 14 Jan 2021 13:38:31 +0000 Subject: [PATCH 32/78] rose suite-clean => cylc clean --- t/rose-ana/00-run-basic.t | 2 +- t/rose-ana/01-run-basic-v1.t | 2 +- t/rose-task-env/00-non-cycle.t | 2 +- t/rose-task-env/01-integer-cycling.t | 2 +- t/rose-task-env/02-360day-cycling.t | 2 +- t/rose-task-run/00-run-basic.t | 2 +- t/rose-task-run/01-run-basic-iso.t | 2 +- t/rose-task-run/04-run-path-empty.t | 2 +- t/rose-task-run/06-app-prune-iso.t | 2 +- t/rose-task-run/07-app-arch.t | 2 +- t/rose-task-run/08-app-fcm-make.t | 2 +- t/rose-task-run/09-app-prune-work.t | 2 +- t/rose-task-run/10-specify-cycle.t | 2 +- t/rose-task-run/11-specify-cycle-iso.t | 2 +- t/rose-task-run/12-app-prune-integer.t | 2 +- t/rose-task-run/13-app-arch-cmd-out.t | 2 +- t/rose-task-run/14-app-prune-remove.t | 2 +- t/rose-task-run/15-app-prune-extglob.t | 2 +- t/rose-task-run/16-app-prune-point.t | 2 +- t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t | 2 +- t/rose-task-run/18-app-fcm-make-ctx-name.t | 2 +- t/rose-task-run/20-app-fcm-make-dest.t | 2 +- t/rose-task-run/24-app-fcm-make-fast.t | 2 +- t/rose-task-run/25-app-fcm-make-new-mode.t | 2 +- t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t | 2 +- t/rose-task-run/28-env-path-run-path.t | 2 +- t/rose-task-run/29-app-prune-extglob-remote.t | 2 +- t/rose-task-run/30-app-arch-opt-source.t | 2 +- t/rose-task-run/31-app-bunch.t | 2 +- t/rose-task-run/32-app-arch-compressed.t | 2 +- t/rose-task-run/33-app-prune-cycle-format.t | 2 +- t/rose-task-run/34-app-prune-hosts-sharing-fs.t | 2 +- t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t | 2 +- t/rose-task-run/36-app-arch-interrupted.t | 2 +- t/rose-task-run/37-app-bunch-rm-old.t | 2 +- t/rose-task-run/38-app-bunch-counts.t | 2 +- t/rose-task-run/39-app-prune-cycle-host.t | 2 +- t/rose-task-run/40-app-arch-duplicate.t | 2 +- t/rose-task-run/41-app-bunch-default-command.t | 2 +- t/rosie-id/00-basic.t | 2 +- 40 files changed, 40 insertions(+), 40 deletions(-) diff --git a/t/rose-ana/00-run-basic.t b/t/rose-ana/00-run-basic.t index e24e8309ea..ed5fcd910a 100644 --- a/t/rose-ana/00-run-basic.t +++ b/t/rose-ana/00-run-basic.t @@ -290,6 +290,6 @@ file_pcregrep "${TEST_KEY_BASE}-missing_skip_working" \ #------------------------------------------------------------------------------- #Clean suite -rose suite-clean -q -y $NAME +cylc clean $NAME #------------------------------------------------------------------------------- exit 0 diff --git a/t/rose-ana/01-run-basic-v1.t b/t/rose-ana/01-run-basic-v1.t index dd4d870dda..10e00e5339 100644 --- a/t/rose-ana/01-run-basic-v1.t +++ b/t/rose-ana/01-run-basic-v1.t @@ -218,6 +218,6 @@ file_grep_fail $TEST_KEY "$REGEXP" $OUTPUT #------------------------------------------------------------------------------- #Clean suite -rose suite-clean -q -y $NAME +cylc clean $NAME #------------------------------------------------------------------------------- exit 0 diff --git a/t/rose-task-env/00-non-cycle.t b/t/rose-task-env/00-non-cycle.t index 82e15098de..e6d1ba30e0 100755 --- a/t/rose-task-env/00-non-cycle.t +++ b/t/rose-task-env/00-non-cycle.t @@ -45,5 +45,5 @@ sqlite3 "${SUITE_RUN_DIR}/log/db" \ 'select distinct status from task_states;' >"$TEST_KEY_BASE-db.out" file_cmp "$TEST_KEY_BASE-db.out" "$TEST_KEY_BASE-db.out" <<<'succeeded' #------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME +cylc clean $NAME exit 0 diff --git a/t/rose-task-env/01-integer-cycling.t b/t/rose-task-env/01-integer-cycling.t index 647e41f206..0643fac13b 100755 --- a/t/rose-task-env/01-integer-cycling.t +++ b/t/rose-task-env/01-integer-cycling.t @@ -49,5 +49,5 @@ run_pass "$TEST_KEY_BASE-6" ls -d $HOME/cylc-run/${NAME}/share/cycle/6 run_pass "$TEST_KEY_BASE-7" ls -d $HOME/cylc-run/${NAME}/share/cycle/7 run_pass "$TEST_KEY_BASE-8" ls -d $HOME/cylc-run/${NAME}/share/cycle/8 #------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME +cylc clean $NAME exit 0 diff --git a/t/rose-task-env/02-360day-cycling.t b/t/rose-task-env/02-360day-cycling.t index e51dc70b4f..039d34c7ae 100755 --- a/t/rose-task-env/02-360day-cycling.t +++ b/t/rose-task-env/02-360day-cycling.t @@ -77,5 +77,5 @@ file_cmp "${TEST_KEY_BASE}-my-datac" 'expected-my-datac.txt' <<'__TXT__' /share/cycle/20200303T0000Z __TXT__ -rose suite-clean -q -y "${NAME}" +cylc clean "${NAME}" exit 0 diff --git a/t/rose-task-run/00-run-basic.t b/t/rose-task-run/00-run-basic.t index 003e392e0d..7e97c8933a 100755 --- a/t/rose-task-run/00-run-basic.t +++ b/t/rose-task-run/00-run-basic.t @@ -87,5 +87,5 @@ for CYCLE in 20130101T0000Z 20130101T1200Z 20130102T0000Z; do PREV_CYCLE=$CYCLE done #------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME +cylc clean $NAME exit 0 diff --git a/t/rose-task-run/01-run-basic-iso.t b/t/rose-task-run/01-run-basic-iso.t index 5ffa3b689e..c7c51d1864 100755 --- a/t/rose-task-run/01-run-basic-iso.t +++ b/t/rose-task-run/01-run-basic-iso.t @@ -99,5 +99,5 @@ for CYCLE in 20130102T0000Z 20130101T1200Z 20130101T0000Z; do NEXT_CYCLE="${CYCLE}" done #------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME +cylc clean $NAME exit 0 diff --git a/t/rose-task-run/04-run-path-empty.t b/t/rose-task-run/04-run-path-empty.t index bfa5d6f0bd..5e261dceb5 100755 --- a/t/rose-task-run/04-run-path-empty.t +++ b/t/rose-task-run/04-run-path-empty.t @@ -51,5 +51,5 @@ for CYCLE in 20130101T0000Z 20130101T1200Z 20130102T0000Z; do PREV_CYCLE=$CYCLE done #------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME +cylc clean $NAME exit 0 diff --git a/t/rose-task-run/06-app-prune-iso.t b/t/rose-task-run/06-app-prune-iso.t index c2b70875e0..c0e048b840 100755 --- a/t/rose-task-run/06-app-prune-iso.t +++ b/t/rose-task-run/06-app-prune-iso.t @@ -92,5 +92,5 @@ else skip 3 '[t]job-host not defined' fi #------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME +cylc clean $NAME exit 0 diff --git a/t/rose-task-run/07-app-arch.t b/t/rose-task-run/07-app-arch.t index 77a8e4afcb..668aaa1650 100755 --- a/t/rose-task-run/07-app-arch.t +++ b/t/rose-task-run/07-app-arch.t @@ -209,5 +209,5 @@ file_cmp "${TEST_KEY}.err" "${TEST_KEY}.err" <<__ERR__ __ERR__ #------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME +cylc clean $NAME exit 0 diff --git a/t/rose-task-run/08-app-fcm-make.t b/t/rose-task-run/08-app-fcm-make.t index a31a63f23c..756c410be6 100755 --- a/t/rose-task-run/08-app-fcm-make.t +++ b/t/rose-task-run/08-app-fcm-make.t @@ -117,5 +117,5 @@ else $SUITE_RUN_DIR/log/job/1/fcm_make2_t5/01/job.out fi #------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME +cylc clean $NAME exit 0 diff --git a/t/rose-task-run/09-app-prune-work.t b/t/rose-task-run/09-app-prune-work.t index cb684ab109..710a0e93a9 100755 --- a/t/rose-task-run/09-app-prune-work.t +++ b/t/rose-task-run/09-app-prune-work.t @@ -51,5 +51,5 @@ sed '/^\[INFO\] YYYY-MM-DDTHHMM export ROSE_TASK_CYCLE_TIME=/p; stamp-removed.log >edited-prune.log file_cmp "$TEST_KEY" "$TEST_SOURCE_DIR/$TEST_KEY_BASE.log" edited-prune.log #------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME +cylc clean $NAME exit 0 diff --git a/t/rose-task-run/10-specify-cycle.t b/t/rose-task-run/10-specify-cycle.t index 8db256dcad..682b2bc9da 100755 --- a/t/rose-task-run/10-specify-cycle.t +++ b/t/rose-task-run/10-specify-cycle.t @@ -43,5 +43,5 @@ run_pass "${TEST_KEY_BASE}-run" \ --debug #------------------------------------------------------------------------------- file_cmp "$TEST_KEY" "$SUITE_RUN_DIR/file" <<<'20121231T1200Z' -rose suite-clean -q -y $NAME +cylc clean $NAME exit 0 diff --git a/t/rose-task-run/11-specify-cycle-iso.t b/t/rose-task-run/11-specify-cycle-iso.t index 0658a8ae64..db1d0ee77d 100755 --- a/t/rose-task-run/11-specify-cycle-iso.t +++ b/t/rose-task-run/11-specify-cycle-iso.t @@ -43,5 +43,5 @@ run_pass "${TEST_KEY_BASE}-run" \ --debug #------------------------------------------------------------------------------- file_cmp "$TEST_KEY" "$SUITE_RUN_DIR/file" <<<'20121231T1200Z' -rose suite-clean -q -y $NAME +cylc clean $NAME exit 0 diff --git a/t/rose-task-run/12-app-prune-integer.t b/t/rose-task-run/12-app-prune-integer.t index 2d3456da2f..c879489e79 100755 --- a/t/rose-task-run/12-app-prune-integer.t +++ b/t/rose-task-run/12-app-prune-integer.t @@ -83,6 +83,6 @@ if [[ -n "$JOB_HOST" ]]; then run_pass "$TEST_KEY.3" ssh "$JOB_HOST" "ls -d ~/cylc-run/$NAME/log/job/3" fi #------------------------------------------------------------------------------- -rose suite-clean -y --name=$NAME +cylc clean $NAME #------------------------------------------------------------------------------- exit 0 diff --git a/t/rose-task-run/13-app-arch-cmd-out.t b/t/rose-task-run/13-app-arch-cmd-out.t index aef3fe9276..9921f0026f 100755 --- a/t/rose-task-run/13-app-arch-cmd-out.t +++ b/t/rose-task-run/13-app-arch-cmd-out.t @@ -58,5 +58,5 @@ MMXIV=2014, / __NL__ #------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" +cylc clean "${NAME}" exit 0 diff --git a/t/rose-task-run/14-app-prune-remove.t b/t/rose-task-run/14-app-prune-remove.t index fb7e3e6c7b..23386c067b 100755 --- a/t/rose-task-run/14-app-prune-remove.t +++ b/t/rose-task-run/14-app-prune-remove.t @@ -58,6 +58,6 @@ run_fail "$TEST_KEY.2" ls -d $HOME/cylc-run/$NAME/log/job-20100102T0000Z.tar.gz run_fail "$TEST_KEY.3" ls -d $HOME/cylc-run/$NAME/log/job-20100103T0000Z.tar.gz run_pass "$TEST_KEY.4" ls -d $HOME/cylc-run/$NAME/log/job-20100104T0000Z.tar.gz #------------------------------------------------------------------------------- -rose suite-clean -q -y --name=$NAME +cylc clean $NAME #------------------------------------------------------------------------------- exit 0 diff --git a/t/rose-task-run/15-app-prune-extglob.t b/t/rose-task-run/15-app-prune-extglob.t index 7bb70343ec..079b631710 100755 --- a/t/rose-task-run/15-app-prune-extglob.t +++ b/t/rose-task-run/15-app-prune-extglob.t @@ -59,5 +59,5 @@ file_cmp "${TEST_KEY}" 'edited-prune.log' <<__LOG__ [INFO] YYYY-MM-DDTHHMM delete: work/20150102T0000Z/creator/rose-app-run.conf __LOG__ #------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" +cylc clean "${NAME}" exit 0 diff --git a/t/rose-task-run/16-app-prune-point.t b/t/rose-task-run/16-app-prune-point.t index b206ef7480..c9f3f6854f 100755 --- a/t/rose-task-run/16-app-prune-point.t +++ b/t/rose-task-run/16-app-prune-point.t @@ -62,5 +62,5 @@ file_cmp "${TEST_KEY}" 'edited-prune.log' <<__LOG__ [INFO] YYYY-MM-DDTHHMM delete: work/19800101T0000Z __LOG__ #------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" +cylc clean "${NAME}" exit 0 diff --git a/t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t b/t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t index 81fbd7b2bd..fe45841eaf 100755 --- a/t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t +++ b/t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t @@ -56,5 +56,5 @@ file_cmp "${TEST_KEY}" 'edited-prune.log' <<__LOG__ [INFO] YYYY-MM-DDTHHMM delete: share/hello-venus-at-19700101T0000Z.txt __LOG__ #------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" +cylc clean "${NAME}" exit 0 diff --git a/t/rose-task-run/18-app-fcm-make-ctx-name.t b/t/rose-task-run/18-app-fcm-make-ctx-name.t index 7beb2adab5..880899db86 100755 --- a/t/rose-task-run/18-app-fcm-make-ctx-name.t +++ b/t/rose-task-run/18-app-fcm-make-ctx-name.t @@ -69,5 +69,5 @@ ssh -n -oBatchMode=yes "${JOB_HOST}" \ cat "cylc-run/${NAME}/share/hello.txt" >'hello.txt' file_cmp "${TEST_KEY_BASE}" 'hello.txt' <<<'Hello World!' #------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" +cylc clean "${NAME}" exit 0 diff --git a/t/rose-task-run/20-app-fcm-make-dest.t b/t/rose-task-run/20-app-fcm-make-dest.t index 54ac172d66..1470d56b1f 100755 --- a/t/rose-task-run/20-app-fcm-make-dest.t +++ b/t/rose-task-run/20-app-fcm-make-dest.t @@ -84,5 +84,5 @@ Hello World! __TXT__ fi #------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" +cylc clean "${NAME}" exit 0 diff --git a/t/rose-task-run/24-app-fcm-make-fast.t b/t/rose-task-run/24-app-fcm-make-fast.t index 627f980489..d049bc9ddc 100755 --- a/t/rose-task-run/24-app-fcm-make-fast.t +++ b/t/rose-task-run/24-app-fcm-make-fast.t @@ -79,5 +79,5 @@ MTIME_OF_FAST_AFTER=$(stat '-c%y' 'fast') run_pass "${TEST_KEY_BASE}-mtime-of-fast" \ test "${MTIME_OF_FAST_BEFORE}" '!=' "${MTIME_OF_FAST_AFTER}" #------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" +cylc clean "${NAME}" exit 0 diff --git a/t/rose-task-run/25-app-fcm-make-new-mode.t b/t/rose-task-run/25-app-fcm-make-new-mode.t index 7011b76c5a..c719081995 100755 --- a/t/rose-task-run/25-app-fcm-make-new-mode.t +++ b/t/rose-task-run/25-app-fcm-make-new-mode.t @@ -65,5 +65,5 @@ run_fail "${TEST_KEY_BASE}" ls \ "${SUITE_RUN_DIR}/share/hello-make/junk1" \ "${SUITE_RUN_DIR}/share/hello-make/junk2" #------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" +cylc clean "${NAME}" exit 0 diff --git a/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t b/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t index 9c0186850e..5414a4fc05 100755 --- a/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t +++ b/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t @@ -77,5 +77,5 @@ run_fail "${TEST_KEY_BASE}" \ ssh -n -oBatchMode=yes "${JOB_HOST}" \ "ls 'cylc-run/${NAME}/share/hello-make/junk'*" #------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" +cylc clean "${NAME}" exit 0 diff --git a/t/rose-task-run/28-env-path-run-path.t b/t/rose-task-run/28-env-path-run-path.t index d0d86730c9..5594b75f70 100755 --- a/t/rose-task-run/28-env-path-run-path.t +++ b/t/rose-task-run/28-env-path-run-path.t @@ -46,5 +46,5 @@ file_cmp "${TEST_KEY}" "${SUITE_RUN_DIR}/hello.txt" <<__HELLO__ Hello Earth! __HELLO__ #------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" +cylc clean "${NAME}" exit 0 diff --git a/t/rose-task-run/29-app-prune-extglob-remote.t b/t/rose-task-run/29-app-prune-extglob-remote.t index 53d5b7fa49..b395506a5b 100755 --- a/t/rose-task-run/29-app-prune-extglob-remote.t +++ b/t/rose-task-run/29-app-prune-extglob-remote.t @@ -73,5 +73,5 @@ file_cmp "${TEST_KEY}" 'edited-prune.log' <<__LOG__ [INFO] YYYY-MM-DDTHHMM delete: work/20150101T0000Z __LOG__ #------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" +cylc clean "${NAME}" exit 0 diff --git a/t/rose-task-run/30-app-arch-opt-source.t b/t/rose-task-run/30-app-arch-opt-source.t index 88dbd67091..1ca960222a 100755 --- a/t/rose-task-run/30-app-arch-opt-source.t +++ b/t/rose-task-run/30-app-arch-opt-source.t @@ -78,5 +78,5 @@ sort >'job.err.expected' <<__LOG__ __LOG__ file_cmp "${TEST_KEY}-job.err.sorted" 'job.err.sorted' 'job.err.expected' #------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" +cylc clean "${NAME}" exit 0 diff --git a/t/rose-task-run/31-app-bunch.t b/t/rose-task-run/31-app-bunch.t index ad81dfc660..94edb2f152 100755 --- a/t/rose-task-run/31-app-bunch.t +++ b/t/rose-task-run/31-app-bunch.t @@ -251,5 +251,5 @@ file_grep "$TEST_KEY_PREFIX-TOTAL-RAN" \ "$INFO_PATTERN TOTAL: 8" \ "$FILE" #------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME +cylc clean $NAME exit 0 diff --git a/t/rose-task-run/32-app-arch-compressed.t b/t/rose-task-run/32-app-arch-compressed.t index 1886e20427..cc1d7309bd 100755 --- a/t/rose-task-run/32-app-arch-compressed.t +++ b/t/rose-task-run/32-app-arch-compressed.t @@ -54,5 +54,5 @@ file_cmp "${TEST_KEY}.out" "${TEST_KEY}.out" <<'__FIND__' ./archive.d/whatever.tar.gz __FIND__ #------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" +cylc clean "${NAME}" exit 0 diff --git a/t/rose-task-run/33-app-prune-cycle-format.t b/t/rose-task-run/33-app-prune-cycle-format.t index ff0f0044e9..0fae8e8b37 100755 --- a/t/rose-task-run/33-app-prune-cycle-format.t +++ b/t/rose-task-run/33-app-prune-cycle-format.t @@ -57,5 +57,5 @@ file_cmp "${TEST_KEY}" 'edited-prune.log' <<__LOG__ [INFO] YYYY-MM-DDTHHMM delete: share/hello-venus-in-1970.txt __LOG__ #------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" +cylc clean "${NAME}" exit 0 diff --git a/t/rose-task-run/34-app-prune-hosts-sharing-fs.t b/t/rose-task-run/34-app-prune-hosts-sharing-fs.t index b802288f37..b555f5d136 100755 --- a/t/rose-task-run/34-app-prune-hosts-sharing-fs.t +++ b/t/rose-task-run/34-app-prune-hosts-sharing-fs.t @@ -76,5 +76,5 @@ else "${SUITE_RUN_DIR}/prune.log" fi #------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" +cylc clean "${NAME}" exit 0 diff --git a/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t b/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t index 1bab9e8014..4359ebc492 100755 --- a/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t +++ b/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t @@ -76,5 +76,5 @@ else "${SUITE_RUN_DIR}/prune.log" fi #------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" +cylc clean "${NAME}" exit 0 diff --git a/t/rose-task-run/36-app-arch-interrupted.t b/t/rose-task-run/36-app-arch-interrupted.t index cd6b32ddf9..02235ceeff 100755 --- a/t/rose-task-run/36-app-arch-interrupted.t +++ b/t/rose-task-run/36-app-arch-interrupted.t @@ -56,5 +56,5 @@ file_cmp "${TEST_KEY}.out" "${TEST_KEY}.out" <<'__FIND__' ./new_whatever __FIND__ #------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" +cylc clean "${NAME}" exit 0 diff --git a/t/rose-task-run/37-app-bunch-rm-old.t b/t/rose-task-run/37-app-bunch-rm-old.t index 9699e533fb..d4f06f8f41 100755 --- a/t/rose-task-run/37-app-bunch-rm-old.t +++ b/t/rose-task-run/37-app-bunch-rm-old.t @@ -91,5 +91,5 @@ for ARGVALUE in 0 1 2 3; do done #------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME +cylc clean $NAME exit 0 diff --git a/t/rose-task-run/38-app-bunch-counts.t b/t/rose-task-run/38-app-bunch-counts.t index 69d2276945..62c91320bf 100755 --- a/t/rose-task-run/38-app-bunch-counts.t +++ b/t/rose-task-run/38-app-bunch-counts.t @@ -113,5 +113,5 @@ TEST_KEY=${TEST_KEY_PREFIX}-TOTAL file_grep $TEST_KEY "\[INFO\] TOTAL: 5" $FILE #------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME +cylc clean $NAME exit 0 diff --git a/t/rose-task-run/39-app-prune-cycle-host.t b/t/rose-task-run/39-app-prune-cycle-host.t index bfaa9fa355..670fed19be 100755 --- a/t/rose-task-run/39-app-prune-cycle-host.t +++ b/t/rose-task-run/39-app-prune-cycle-host.t @@ -61,5 +61,5 @@ run_fail "${TEST_KEY}-ssh-2" \ grep -q "ssh .* ${JOB_HOST_2} .* share/cycle/19700101T0000Z;" \ "${RUND}/prune.log" #------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" +cylc clean "${NAME}" exit 0 diff --git a/t/rose-task-run/40-app-arch-duplicate.t b/t/rose-task-run/40-app-arch-duplicate.t index 582cdd09ae..3cb9edb78d 100644 --- a/t/rose-task-run/40-app-arch-duplicate.t +++ b/t/rose-task-run/40-app-arch-duplicate.t @@ -47,5 +47,5 @@ TEST_KEY="${TEST_KEY_BASE}" file_grep "${TEST_KEY}" 'duplicate archive target: "foo"' \ "${SUITE_RUN_DIR}/log/job/1/archive_fail_duplicate/NN/job.err" #------------------------------------------------------------------------------- -rose suite-clean -q -y "${NAME}" +cylc clean "${NAME}" exit 0 diff --git a/t/rose-task-run/41-app-bunch-default-command.t b/t/rose-task-run/41-app-bunch-default-command.t index 8865bcd395..27fde1dfa9 100755 --- a/t/rose-task-run/41-app-bunch-default-command.t +++ b/t/rose-task-run/41-app-bunch-default-command.t @@ -99,5 +99,5 @@ for TASK in buncher_default buncher_import; do #------------------------------------------------------------------------------- done #------------------------------------------------------------------------------- -rose suite-clean -q -y $NAME +cylc clean $NAME exit 0 diff --git a/t/rosie-id/00-basic.t b/t/rosie-id/00-basic.t index 93c334dcc6..8fcdedb7e9 100755 --- a/t/rosie-id/00-basic.t +++ b/t/rosie-id/00-basic.t @@ -138,7 +138,7 @@ file_cmp "$TEST_KEY.err" "$TEST_KEY.err" Date: Thu, 14 Jan 2021 16:40:21 +0000 Subject: [PATCH 33/78] source rose-meta via python --- bin/rose | 7 +++---- bin/rose-mpi-launch | 4 ++-- etc/bin/rose-test-battery | 2 +- etc/bin/shellchecker | 2 +- metomi/rose/macro.py | 6 ++++-- metomi/rose/upgrade.py | 1 - t/rose-mpi-launch/01-command-inner.t | 1 + 7 files changed, 12 insertions(+), 11 deletions(-) diff --git a/bin/rose b/bin/rose index 521add7c1a..7ad58f13bf 100755 --- a/bin/rose +++ b/bin/rose @@ -40,10 +40,9 @@ fi set -eu ROSE_NS=$(basename "$0") -ROSE_LIB="$(dirname "$(python3 -c "import metomi.rose; print(metomi.rose.__file__)")")" -ROSE_HOME_BIN=$(dirname "$(command -v rose)") +ROSE_HOME_BIN="$(dirname "$0")" ROSE_VERSION="$(python3 -c "import metomi.rose; print(metomi.rose.__version__)")" -export ROSE_NS ROSE_LIB ROSE_HOME_BIN ROSE_VERSION +export ROSE_NS ROSE_HOME_BIN ROSE_VERSION # Print actual command of a command alias get_alias() { @@ -122,7 +121,7 @@ help|h|?|--help|-h) if (($# == 0)); then { print_version - # shellcheck source=../metomi/rose/etc/lib/bash/rose_usage + # shellcheck source=metomi/rose/etc/lib/bash/rose_usage . "$(rose resource lib/bash/rose_usage)" rose_usage echo diff --git a/bin/rose-mpi-launch b/bin/rose-mpi-launch index 1bad013803..d035d29dfa 100755 --- a/bin/rose-mpi-launch +++ b/bin/rose-mpi-launch @@ -113,9 +113,9 @@ # Return 0 on success, 1 or exit code of the launcher program on failure. #------------------------------------------------------------------------------- set -eu -# shellcheck source=../metomi/rose/etc/lib/bash/rose_log +# shellcheck source=metomi/rose/etc/lib/bash/rose_log . "$(rose resource lib/bash/rose_log)" -# shellcheck source=../metomi/rose/etc/lib/bash/rose_usage +# shellcheck source=metomi/rose/etc/lib/bash/rose_usage . "$(rose resource lib/bash/rose_usage)" ROSE_CMD="${ROSE_NS}-${ROSE_UTIL}" diff --git a/etc/bin/rose-test-battery b/etc/bin/rose-test-battery index a0d07bc923..11f3f7e364 100755 --- a/etc/bin/rose-test-battery +++ b/etc/bin/rose-test-battery @@ -49,7 +49,7 @@ # * `prove(1)` #------------------------------------------------------------------------------- set -eu -# shellcheck source=../../metomi/rose/etc/lib/bash/rose_log +# shellcheck source=metomi/rose/etc/lib/bash/rose_log . "$(rose resource lib/bash/rose_log)" # Move to folder in which prove command should be run. diff --git a/etc/bin/shellchecker b/etc/bin/shellchecker index 601110eea6..96e3b55da1 100755 --- a/etc/bin/shellchecker +++ b/etc/bin/shellchecker @@ -80,7 +80,7 @@ default () { --exclude t \ --exclude node_modules \ --exclude .git \ - -- -e SC1090 -e SC2119 -e SC2001 + -- -e SC1090 -e SC2119 -e SC2001 \ # run a lenient check on all test scripts main t -- -S error -e SC1090 diff --git a/metomi/rose/macro.py b/metomi/rose/macro.py index 935d3f08cc..f8c527cd41 100644 --- a/metomi/rose/macro.py +++ b/metomi/rose/macro.py @@ -41,6 +41,7 @@ def test_cleanup(stuff_to_remove): import glob import inspect import os +from pathlib import Path import re import sys import traceback @@ -551,7 +552,9 @@ def add_site_meta_paths(): for path in path.split(os.pathsep): path = os.path.expanduser(os.path.expandvars(path)) sys.path.insert(0, os.path.abspath(path)) - sys.path.append(os.path.join(os.getenv("ROSE_LIB"), "etc/rose-meta")) + sys.path.append( + metomi.rose.resource.ResourceLocator.default().locate('rose-meta') + ) def add_env_meta_paths(): @@ -1630,7 +1633,6 @@ def main(): sys.exit(1) # Path manipulation. - sys.path.append(os.getenv("ROSE_LIB")) add_opt_meta_paths(opts.meta_path) # Run macros for each config. diff --git a/metomi/rose/upgrade.py b/metomi/rose/upgrade.py index 6afc681837..d1bcefb78f 100644 --- a/metomi/rose/upgrade.py +++ b/metomi/rose/upgrade.py @@ -720,7 +720,6 @@ def parse_upgrade_args(argv=None): opts.conf_dir = os.path.abspath(opts.conf_dir) if opts.output_dir is not None: opts.output_dir = os.path.abspath(opts.output_dir) - sys.path.append(os.getenv("ROSE_LIB")) metomi.rose.macro.add_opt_meta_paths(opts.meta_path) config_name = os.path.basename(opts.conf_dir) config_file_path = os.path.join(opts.conf_dir, diff --git a/t/rose-mpi-launch/01-command-inner.t b/t/rose-mpi-launch/01-command-inner.t index 57c636000f..9ca1d0f456 100755 --- a/t/rose-mpi-launch/01-command-inner.t +++ b/t/rose-mpi-launch/01-command-inner.t @@ -25,6 +25,7 @@ tests 12 #------------------------------------------------------------------------------- # Basic. TEST_KEY=$TEST_KEY_BASE +ROSE_HOME_BIN="$(rose version --long | sed 's/.*(\(.*\))/\1/')" ROSE_LAUNCHER_ULIMIT_OPTS='-a' \ run_pass "$TEST_KEY" rose mpi-launch echo hello world file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__ From 87b14c1b0ffa59a45ecf3b848d81d50a81901fa3 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Thu, 14 Jan 2021 18:06:39 +0000 Subject: [PATCH 34/78] packaging: fix installation of static resources * static resources were only correctly installed in with "editable" pip installs * possible that installing from a built wheel would work, however for testing is is convenient to `pip install .` * switched to using a manifest file which seems to be a more robust approach * https://packaging.python.org/guides/using-manifest-in/ --- .github/workflows/test.yml | 2 +- MANIFEST.in | 2 ++ metomi/rose/macro.py | 1 - setup.py | 5 +---- 4 files changed, 4 insertions(+), 6 deletions(-) create mode 100644 MANIFEST.in diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3e72bd1f81..3eb0b42c5a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,7 +40,7 @@ jobs: # there are other Rose files (e.g. meta-all/rose-meta.conf) # that are not yet installed as part of the Python package # which need to be moved into metomi/rose/etc/ - pip install -e ."[all]" + pip install ."[all]" pip install --no-deps git+https://github.com/cylc/cylc-rose.git yarn install diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000000..294cd4596f --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +recursive-include metomi/rose/etc/ * +recursive-include metomi/rosie/lib/ * diff --git a/metomi/rose/macro.py b/metomi/rose/macro.py index f8c527cd41..9010cc8864 100644 --- a/metomi/rose/macro.py +++ b/metomi/rose/macro.py @@ -41,7 +41,6 @@ def test_cleanup(stuff_to_remove): import glob import inspect import os -from pathlib import Path import re import sys import traceback diff --git a/setup.py b/setup.py index 0dd3ac0397..69f7b3486f 100644 --- a/setup.py +++ b/setup.py @@ -91,9 +91,6 @@ def find_version(*file_paths): install_requires=INSTALL_REQUIRES, extras_require=EXTRAS_REQUIRE, tests_require=TESTS_REQUIRE, - package_data={ - "metomi.rose": ["etc/.*"], - "metomi.rosie": ["lib/*"] - }, + include_package_data=True, packages=find_namespace_packages(include=["metomi.*"]), ) From 8f46056a2218e0a9b3959d9d96dc6c9d552dbaf0 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Tue, 12 Jan 2021 13:10:08 +0000 Subject: [PATCH 35/78] debug and actions ++ --- .github/workflows/test.yml | 31 ++++++++++++++++++---------- etc/bin/rose-test-battery | 1 + t/rose-mpi-launch/01-command-inner.t | 3 +++ t/rose-task-run/00-run-basic.t | 8 +++---- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3eb0b42c5a..fe73b89048 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ on: jobs: test: runs-on: ${{ matrix.os }} - timeout-minutes: 15 + timeout-minutes: 45 strategy: matrix: os: ['ubuntu-latest'] @@ -55,15 +55,20 @@ jobs: patch "${hostuserutil}" < etc/conf/macos-patch # install system deps - brew update + #brew update brew install bash coreutils gnu-sed shellcheck sqlite3 - # old stuff, not sure if all needed - brew install \ - subversion - # add GNU coreutils and sed to the user PATH # (see instructions in brew install output) + echo \ + "$(brew --prefix)/opt/coreutils/libexec/gnubin" \ + >> "${GITHUB_PATH}" + echo \ + "/usr/local/opt/gnu-sed/libexec/gnubin" \ + >> "${GITHUB_PATH}" + + # add GNU coreutils and sed to the user PATH (for Cylc jobs) + # (see instructions in brew install output) cat >> "$HOME/.bashrc" <<__HERE__ PATH="$(brew --prefix)/opt/coreutils/libexec/gnubin:$PATH" PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH" @@ -103,21 +108,25 @@ jobs: pytest - name: Functional Tests + timeout-minutes: 30 id: functest run: | + # rose tests should pass first time around etc/bin/rose-test-battery -j 4 --state=save - name: Re-Run Fails - if: steps.functest.conclusion == 'failure' + if: failure() && steps.functest.outcome == 'failure' run: | + # so we only re-run for debug purposes + cylc scan --state=all --color=never etc/bin/rose-test-battery -j 1 -v --state=save,failed - name: Upload - if: steps.functest.conclusion == 'failure' + if: failure() && steps.functest.outcome == 'failure' uses: actions/upload-artifact@v2 with: - name: Upload cylc-run artifact - path: cylc-run + name: cylc-run ${{ matrix.os }} + path: ~/cylc-run/ docs: runs-on: ubuntu-latest @@ -164,4 +173,4 @@ jobs: - name: debug if: failure() run: | - cat /tmp/sphinx-err* >&2 + cat /tmp/sphinx-err* >&2 || true diff --git a/etc/bin/rose-test-battery b/etc/bin/rose-test-battery index 11f3f7e364..9e01640e2b 100755 --- a/etc/bin/rose-test-battery +++ b/etc/bin/rose-test-battery @@ -56,6 +56,7 @@ set -eu TESTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" cd "$TESTDIR/../../" mkdir -p ~/.metomi +mkdir -p "${HOME}/cylc-run" # Recompile *.pyc files to ensure we are running the current code. # @TODO Consider if this is appropriate in new version diff --git a/t/rose-mpi-launch/01-command-inner.t b/t/rose-mpi-launch/01-command-inner.t index 9ca1d0f456..1b3a4582bf 100755 --- a/t/rose-mpi-launch/01-command-inner.t +++ b/t/rose-mpi-launch/01-command-inner.t @@ -21,6 +21,9 @@ #------------------------------------------------------------------------------- . $(dirname $0)/test_header #------------------------------------------------------------------------------- +if [[ "$OSTYPE" == darwin* ]]; then + skip_all 'Does not work on MacOS' +fi tests 12 #------------------------------------------------------------------------------- # Basic. diff --git a/t/rose-task-run/00-run-basic.t b/t/rose-task-run/00-run-basic.t index 7e97c8933a..83d8781aab 100755 --- a/t/rose-task-run/00-run-basic.t +++ b/t/rose-task-run/00-run-basic.t @@ -31,8 +31,8 @@ SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') NAME=$(basename $SUITE_RUN_DIR) run_pass "${TEST_KEY_BASE}-install" \ cylc install \ - -C $TEST_SOURCE_DIR/$TEST_KEY_BASE \ - --flow-name=$NAME \ + -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ + --flow-name="${NAME}" \ --no-run-name run_pass "${TEST_KEY_BASE}-run" \ cylc run \ @@ -60,7 +60,7 @@ PREV_CYCLE= for CYCLE in 20130101T0000Z 20130101T1200Z 20130102T0000Z; do TEST_KEY=$TEST_KEY_BASE-file-$CYCLE TASK=my_task_1 - FILE=$HOME/cylc-run/$NAME/log/job/$CYCLE/$TASK/01/job.txt + FILE="$HOME/cylc-run/$NAME/log/job/$CYCLE/$TASK/01/job.txt" file_test "$TEST_KEY" $FILE file_grep "$TEST_KEY-ROSE_SUITE_DIR" "ROSE_SUITE_DIR=$SUITE_RUN_DIR" $FILE file_grep "$TEST_KEY-ROSE_SUITE_DIR_REL" \ @@ -87,5 +87,5 @@ for CYCLE in 20130101T0000Z 20130101T1200Z 20130102T0000Z; do PREV_CYCLE=$CYCLE done #------------------------------------------------------------------------------- -cylc clean $NAME +cylc clean "$NAME" exit 0 From 83229c497721f0f1ef7f6935a3fbbbff90bba41b Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Mon, 23 Nov 2020 12:07:41 +0000 Subject: [PATCH 36/78] bin: remove obsolete and mark deprecated commands --- bin/rose | 64 ++++++++++++++++++- bin/rose-config-edit | 73 ---------------------- bin/rose-date | 5 ++ bin/rose-edit | 22 ------- bin/rose-metadata-graph | 3 +- bin/rose-slv | 22 ------- bin/rose-stem | 3 +- bin/rose-suite-clean | 53 ---------------- bin/rose-suite-cmp-vc | 53 ---------------- bin/rose-suite-hook | 54 ---------------- bin/rose-suite-init | 23 ------- bin/rose-suite-log | 67 -------------------- bin/rose-suite-log-view | 22 ------- bin/rose-suite-restart | 58 ------------------ bin/rose-suite-run | 132 ---------------------------------------- bin/rose-suite-scan | 29 --------- bin/rose-suite-shutdown | 57 ----------------- bin/rose-suite-stop | 22 ------- bin/rose-task-hook | 22 ------- t/rose-help/00-alias.t | 9 +-- 20 files changed, 73 insertions(+), 720 deletions(-) delete mode 100755 bin/rose-config-edit delete mode 100755 bin/rose-edit delete mode 100755 bin/rose-slv delete mode 100755 bin/rose-suite-clean delete mode 100755 bin/rose-suite-cmp-vc delete mode 100755 bin/rose-suite-hook delete mode 100755 bin/rose-suite-init delete mode 100755 bin/rose-suite-log delete mode 100755 bin/rose-suite-log-view delete mode 100755 bin/rose-suite-restart delete mode 100755 bin/rose-suite-run delete mode 100755 bin/rose-suite-scan delete mode 100755 bin/rose-suite-shutdown delete mode 100755 bin/rose-suite-stop delete mode 100755 bin/rose-task-hook diff --git a/bin/rose b/bin/rose index 7ad58f13bf..9b6ad55a7d 100755 --- a/bin/rose +++ b/bin/rose @@ -44,6 +44,49 @@ ROSE_HOME_BIN="$(dirname "$0")" ROSE_VERSION="$(python3 -c "import metomi.rose; print(metomi.rose.__version__)")" export ROSE_NS ROSE_HOME_BIN ROSE_VERSION +# NOTE: cannot use assiciative array due to bash version requirement +DEAD_ENDS=( + rose-config-edit + rose-edit + rose-suite-clean + rose-suite-cmp-vc + rose-suite-gcontrol + rose-sgc + rose-suite-hook + rose-task-hook + rose-suite-log-view + rose-suite-log + rose-slv + rose-suite-restart + rose-suite-run + rose-suite-init + rose-suite-scan + rose-suite-shutdown + rose-suite-stop +) + +# NOTE: indicies must match DEAD_ENDS array +# (be very careful not to jumble them) +DEAD_END_MSGS=( + 'The Rose configuration editor has been removed, use the Cylc GUI.' + 'The Rose configuration editor has been removed, use the Cylc GUI.' + 'This command has been replaced by: "cylc clean".' + 'This command is awaiting re-implementation in Cylc8' + 'This command has been removed: use the Cylc GUI.' + 'This command has been removed: use the Cylc GUI.' + 'Command obsolete, use Cylc event handlers' + 'Command obsolete, use Cylc event handlers' + 'This command has been removed: use the Cylc GUI.' + 'This command has been removed: use the Cylc GUI' + 'This command has been removed: use the Cylc GUI' + 'This command has been replaced by: "cylc restart".' + 'This command has been replaced by: "cylc install".' + 'This command has been replaced by: "cylc install".' + 'This command has been replaced by: "cylc scan".' + 'This command has been replaced by: "cylc stop".' + 'This command has been replaced by: "cylc stop".' +) + # Print actual command of a command alias get_alias() { local NAME="$1" @@ -134,8 +177,16 @@ help|h|?|--help|-h) echo " (=$ALIAS)" else echo " $NAME" - sed '1,/^# DESCRIPTION$/d;{s/^# / /;q;}' \ - "$ROSE_HOME_BIN/$U" + if [[ $U == 'rose-date' ]]; then + # hack an exception for this lone Python command + # shellcheck disable=SC2026 + echo ' '\ + 'Parse and print 1. a date time point '\ + 'or 2. a duration.' + else + sed '1,/^# DESCRIPTION$/d;{s/^# / /;q;}' \ + "$ROSE_HOME_BIN/$U" + fi fi done } | ${PAGER:-less} @@ -181,6 +232,14 @@ doc) :;; esac +CMD="${ROSE_NS}-${UTIL}" +for i in $(seq 0 $(( ${#DEAD_ENDS[@]} - 1 ))); do + if [[ "${DEAD_ENDS[$i]}" == "$CMD" ]]; then + echo "${DEAD_END_MSGS[$i]}" >&2 + exit 42 + fi +done + COMMAND="$(dirname "$0")/$ROSE_NS-$UTIL" if [[ ! -f $COMMAND || ! -x $COMMAND ]]; then echo "$ROSE_NS: $UTIL: unknown utility. Abort." >&2 @@ -193,4 +252,5 @@ if (($# > 0)) && [[ $1 == '--help' || $1 == '-h' ]]; then fi ROSE_UTIL=$UTIL export ROSE_UTIL + exec "$COMMAND" "$@" diff --git a/bin/rose-config-edit b/bin/rose-config-edit deleted file mode 100755 index 0e5e78483f..0000000000 --- a/bin/rose-config-edit +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env bash -#----------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#----------------------------------------------------------------------------- -# NAME -# rose config-edit -# -# SYNOPSIS -# rose config-edit [OPTIONS]... [PAGE_PATH]... -# -# DESCRIPTION -# Launch the GTK+ GUI to edit a suite or application configuration. -# -# If a suite contains more than 10 applications then they will only be -# loaded after startup, on demand. -# -# OPTIONS -# --config=DIR, -C DIR -# A path to either: -# -# 1. a directory containing a suite with a file named -# `flow.cylc` and a directory called `app` containing -# subdirectories with files named `rose-app.conf`, -# in the format specified in the Rose pages. -# 2. a directory containing a single 'application' - a file named -# `rose-app.conf` and an optional subdirectory called `file` -# with other application files. -# -# --load-all-apps -# Force loading of all applications on startup. -# --load-no-apps -# Load applications in the suite on demand. -# --meta-path=PATH, -M PATH -# Prepend `PATH` to the search path for metadata (look here first). -# This option can be used repeatedly to load multiple paths. -# --new -# Launch, ignoring any configuration. -# --no-metadata -# Launch with metadata switched off. -# --no-warn=WARNING-TYPE -# Suppress warnings of the provided type. `WARNING-TYPE` may be: -# -# `version` -# Suppress "Could not find metadata for app/version, using app/HEAD" -# warnings. -# -# ARGUMENTS -# PAGE_PATH -# One or more paths to open on load, pages may be full or partial -# namespaces e.g. `foo/bar/env` or `env`. -# -# NOTE: Opens the shortest namespace that matches the provided string. -# -# ENVIRONMENT VARIABLES -# optional ROSE_META_PATH -# Prepend `$ROSE_META_PATH` to the metadata search path. -#------------------------------------------------------------------------------- -exec python3 -m metomi.rose.config_editor.main "$@" diff --git a/bin/rose-date b/bin/rose-date index 83f75ab438..616f5ef394 100755 --- a/bin/rose-date +++ b/bin/rose-date @@ -182,6 +182,11 @@ from metomi.isodatetime.main import main as iso_main def main(): """Implement rose date.""" + print( + 'WARNING: "rose date" is depreacted, use the "isodatetime" command.', + file=sys.stderr + ) + # Handle Legacy Rose-date -c functionality if '-c' in sys.argv or '--use-task-cycle-time' in sys.argv: if os.getenv('ROSE_TASK_CYCLE_TIME'): diff --git a/bin/rose-edit b/bin/rose-edit deleted file mode 100755 index 80ec62fb6b..0000000000 --- a/bin/rose-edit +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Alias of "rose config-edit". -#------------------------------------------------------------------------------- -exec "$(dirname "$0")/rose-config-edit" "$@" diff --git a/bin/rose-metadata-graph b/bin/rose-metadata-graph index 2d4bd83421..bb703f8cfc 100755 --- a/bin/rose-metadata-graph +++ b/bin/rose-metadata-graph @@ -50,4 +50,5 @@ # optional ROSE_META_PATH # Prepend `$ROSE_META_PATH` to the metadata search path. #------------------------------------------------------------------------------- -exec python3 -m metomi.rose.metadata_graph "$@" +echo 'This command has been removed pending re-implementation' >&2 +exit 1 diff --git a/bin/rose-slv b/bin/rose-slv deleted file mode 100755 index 847b85b0a9..0000000000 --- a/bin/rose-slv +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Alias of "rose suite-log". -#------------------------------------------------------------------------------- -exec "$(dirname "$0")/rose-suite-log" "$@" diff --git a/bin/rose-stem b/bin/rose-stem index c88d5a76cb..1059dbdf85 100755 --- a/bin/rose-stem +++ b/bin/rose-stem @@ -82,4 +82,5 @@ # is intended to specify the revision of `fcm-make` config files. # #------------------------------------------------------------------------------- -exec python3 -m metomi.rose.stem "$@" +echo 'Awaiting re-implementation in cylc-rose.' >&2 +exit 1 diff --git a/bin/rose-suite-clean b/bin/rose-suite-clean deleted file mode 100755 index d310e066e9..0000000000 --- a/bin/rose-suite-clean +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# NAME -# rose suite-clean -# -# SYNOPSIS -# rose suite-clean [OPTIONS] [SUITE-NAME [...]] -# -# DESCRIPTION -# Remove items created by "rose suite-run". -# -# If no argument is specified, use the base-name of `$PWD` as suite name. -# -# Correctly remove runtime directories created by `rose suite-run` -# including those on remote job hosts. -# -# OPTIONS -# --name=NAME, -n NAME -# Append `NAME` to the argument list. -# --non-interactive, --yes, -y -# Switch off interactive prompting (=answer yes to everything) -# --only=ITEM -# If one or more `--only=ITEM` option is specified, only files and/or -# directories matching an `ITEM` will be removed. An `ITEM` should be a -# glob pattern (bash extglob) for matching a relative path in the run -# directory of the suite(s). -# --quiet, -q -# Decrement verbosity. -# --verbose, -v -# Increment verbosity. -# -# DIAGNOSTICS -# Return the difference between the number of arguments and number of -# successfully cleaned suites, i.e. 0 if all successful. -#------------------------------------------------------------------------------- -exec python3 -m metomi.rose.suite_clean "$@" diff --git a/bin/rose-suite-cmp-vc b/bin/rose-suite-cmp-vc deleted file mode 100755 index 277ac9cc87..0000000000 --- a/bin/rose-suite-cmp-vc +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# NAME -# rose suite-cmp-vc -# -# SYNOPSIS -# rose suite-cmp-vc NAME -# rose suite-cmp-vc --name=NAME -# -# # If CYLC_SUITE_NAME is exported, compare source info of the current -# # suite from within a suite task if no name is specified. -# rose suite-cmp-vc -# -# DESCRIPTION -# Compare VCS information of a suite source between installation and now. -# -# Version control system information of a suite is installed under -# log/rose-suite-run.version file. This command attempts to regenerate the -# information from the recorded source, and compare the original file with -# the latest information. -# -# Return 0 if no difference. Print unified diff and return 1 if difference -# found. Return 2 on other errors. -# -# ARGUMENTS -# Specify the suite NAME. -# -# OPTIONS -# --name=NAME, -n NAME -# Specify the suite NAME. -# --quiet, -q -# Decrement verbosity. -# --verbose, -v -# Increment verbosity. -#------------------------------------------------------------------------------- -exec python3 -m metomi.rose.cmp_source_vc "$@" diff --git a/bin/rose-suite-hook b/bin/rose-suite-hook deleted file mode 100755 index cb1e5b8c74..0000000000 --- a/bin/rose-suite-hook +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# NAME -# rose suite-hook -# -# SYNOPSIS -# # Cylc interface -# rose suite-hook [OPTIONS] EVENT SUITE MSG -# rose task-hook [OPTIONS] EVENT SUITE TASK_ID MSG -# -# DESCRIPTION -# Deprecated. Use cylc built-in event handlers instead. -# -# Provide a common event hook for cylc suites and tasks. -# -# * (Task event only) Retrieve remote task logs back to server host. -# * Email user if `--mail` specified. -# * Shutdown suite if `--shutdown` specified. -# -# OPTIONS -# --debug -# Switch on debug mode. -# --mail-cc=LIST -# Only useful if the `--mail` option is specified. Specify a -# comma separated list of additional addresses to email. -# --mail -# Trigger an email notification to the user. -# --retrieve-job-logs -# Retrieve remote task job logs. -# --shutdown -# Trigger a shutdown of the suite. -# --quiet, -q -# Decrement verbosity. -# --verbose, -v -# Increment verbosity. -#------------------------------------------------------------------------------- -exec python3 -m metomi.rose.suite_hook "$@" diff --git a/bin/rose-suite-init b/bin/rose-suite-init deleted file mode 100755 index 0ee6bc98d0..0000000000 --- a/bin/rose-suite-init +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Deprecated. -# Alias of "rose suite-run". -#------------------------------------------------------------------------------- -exec "$(dirname "$0")/rose-suite-run" "$@" diff --git a/bin/rose-suite-log b/bin/rose-suite-log deleted file mode 100755 index f27e50b44e..0000000000 --- a/bin/rose-suite-log +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# NAME -# rose suite-log -# -# SYNOPSIS -# 1. rose suite-log [--view] -# 2. rose suite-log --update [ITEM ...] -# rose suite-log --update '*' # all task jobs -# 3. rose suite-log --archive CYCLE ... -# rose suite-log --archive '*' # all cycles -# -# DESCRIPTION -# View or update suite log. -# -# 1. Launch web browser to view suite log. If "rose bush" is not -# configured, the command will offer to start it. -# 2. Pull back task job logs from any remote hosts for specified cycle -# times or task names or IDs. -# 3. Archive (tar-gzip) job logs at the specified cycle time. -# -# If `--name=SUITE-NAME` is not specified, the name will be determined by -# locating a `rose-suite.conf` file in `$PWD` or its nearest parent -# directories. In a normal suite, the basename of the (nearest parent) -# directory containing the `rose-suite.conf` file is assumed to be the -# suite name. In a project containing a rose stem suite, the basename of -# the (nearest parent) directory containing the `rose-stem/rose-suite.conf` -# file is assumed to be the suite name. -# -# OPTIONS -# --archive -# Archive (tar-gzip) job logs at specified cycle times. Implies -# `--update`. -# --force, -f -# Same as `rose suite-log --update '*'`. -# --name=SUITE-NAME, -n SUITE-NAME -# Specify the suite name, instead of using basename of `$PWD`. -# --prune-remote -# If specified, remove job logs from remote hosts after pulling them to -# suite host. -# --tidy-remote -# Deprecated. Use `--prune-remote` instead. -# --update, -U -# Update job logs for items specified in arguments. -# --user=USER-NAME, -u USER-NAME -# View mode only. View logs of a suite of a different user. -# --view -# Launch web browser to view suite log. -#------------------------------------------------------------------------------- -exec python3 -m metomi.rose.suite_log "$@" diff --git a/bin/rose-suite-log-view b/bin/rose-suite-log-view deleted file mode 100755 index 847b85b0a9..0000000000 --- a/bin/rose-suite-log-view +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Alias of "rose suite-log". -#------------------------------------------------------------------------------- -exec "$(dirname "$0")/rose-suite-log" "$@" diff --git a/bin/rose-suite-restart b/bin/rose-suite-restart deleted file mode 100755 index b05d670570..0000000000 --- a/bin/rose-suite-restart +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# NAME -# rose suite-restart -# -# SYNOPSIS -# rose suite-restart [OPTIONS] [[--] CYLC-RESTART-ARGS] -# -# # Restart cylc suite with name equal to basename of $PWD. -# rose suite-restart -# -# # Restart cylc suite NAME -# rose suite-restart --name=NAME -# -# # Restart cylc suite NAME with a given state dump -# rose suite-restart --name=NAME -- state.20141118T161121.195326Z -# -# # Restart cylc suite NAME on given host -# rose suite-restart --name=NAME --host=my-suite-host -# -# DESCRIPTION -# Restart a shutdown suite from its last known state without reinstalling -# it. -# -# If re-installation is required, use `rose suite-run --restart`. -# -# ARGUMENTS -# Arguments (and options after `--`) are passed to `cylc restart`. -# -# OPTIONS -# --host=HOST -# Specify a host for restarting the suite. -# --name=NAME, -n NAME -# Specify the suite `NAME` in cylc, instead of using the basename of -# the current working directory. -# --quiet, -q -# Decrement verbosity. -# --verbose, -v -# Increment verbosity. -#------------------------------------------------------------------------------- -exec python3 -m metomi.rose.suite_restart "$@" diff --git a/bin/rose-suite-run b/bin/rose-suite-run deleted file mode 100755 index 47b5175904..0000000000 --- a/bin/rose-suite-run +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# NAME -# rose suite-run -# -# SYNOPSIS -# rose suite-run [OPTIONS] [[--] CYLC-RUN-ARGS] -# -# # Install and run a Cylc suite in $PWD. -# rose suite-run -# -# # As above, but start the suite in simulation mode. -# rose suite-run -- --mode=simulation -# -# # Install and run the suite in $PWD, and register it as "my.suite". -# rose suite-run -n my.suite -# -# # Install and run suite in "/dir/to/my.suite". -# # Equivalent to (cd /dir/to/my.suite && rose suite-run). -# rose suite-run -C /dir/to/my.suite -# -# DESCRIPTION -# Install and run a Cylc suite. -# -# Install a suite (in `$PWD`), register it in Cylc using the basename of -# the configuration directory or the option specified in `--name=NAME`. -# Invoke `cylc run` on it. Arguments (and options after `--`) are passed -# to `cylc run`. -# -# OPTIONS -# --config=DIR, -C DIR -# Specify the configuration directory of the suite. (default=`$PWD`) -# --define=[SECTION]KEY=VALUE, -D [SECTION]KEY=VALUE -# Each of these overrides the `[SECTION]KEY` setting with a given -# `VALUE`. Can be used to disable a setting using the syntax -# `--define=[SECTION]!KEY` or even `--define=[!SECTION]`. -# See also `--define-suite`. -# --define-suite=KEY=VALUE, -S KEY=VALUE -# As `--define`, but with an implicit `[SECTION]` for suite variables. -# --host=HOST -# Specify a host for running the suite. -# --install-only, -i -# Install the suite. Do not run it. -# --local-install-only, -l -# Install the suite locally. Do not install to job hosts. Do not run -# it. -# --validate-suite-only -# Install the suite in a temporary location. Do not setup any directories. -# Do not run any jobs. Overrides --install-only and -# --local-install-only. -# --log-keep=DAYS -# Specify the number of days to keep log directories/archives. -# Do not housekeep if not specified. Named log directories (created by -# `--log-name=NAME` in previous runs) will not be housekept. -# --log-name=NAME -# Specify a name for the log directory of the current run. If -# specified, it will create a symbolic link `log.NAME` to point to the -# log directory of the current run. Named log directories will not be -# automatically archived or housekept. Only works with `--run=run`. -# --name=NAME, -n NAME -# Specify the suite `NAME` in Cylc, instead of using the basename of -# the configuration directory. -# --new, -N -# (Re-)create suite runtime locations. This option is equivalent to -# running `rose suite-clean -y && rose suite-run` and will remove -# previous runs. Users may want to take extra care when using this -# option. -# --no-log-archive -# Do not archive (tar-gzip) old log directories. -# --no-overwrite -# Do not overwrite existing files. -# --no-strict -# Do not validate (suite engine configuration) in strict mode. -# --opt-conf-key=KEY, -O KEY, --opt-conf-key='(KEY)', -O '(KEY)' -# Each of these switches on an optional configuration identified by -# `KEY`. The configurations are applied first-to-last. The `(KEY)` -# syntax denotes an optional configuration that can be missing. -# Otherwise, the optional configuration must exist. -# --quiet, -q -# Decrement verbosity. -# --reload -# Shorthand for `--run=reload`. -# --restart -# Shorthand for `--run=restart`. -# --run=reload|restart|run -# Invoke `cylc reload|restart|run` according to this option. -# (default=`run`) -# --verbose, -v -# Increment verbosity. -# -# ENVIRONMENT VARIABLES -# `rose suite-run` provides the following environment variables to the suite. -# -# ROSE_ORIG_HOST -# The name of the host where the `rose suite-run` command was invoked. -# optional ROSE_SUITE_OPT_CONF_KEYS -# Each `KEY` in this space delimited list switches on an optional -# configuration. The configurations are applied first-to-last. -# -# SUITE VARIABLES -# `rose suite-run` provides the following variables to the suite as -# template variables (i.e. Jinja2 or EmPy). -# -# ROSE_ORIG_HOST -# The name of the host where the `rose suite-run` command was invoked. -# ROSE_SUITE_VARIABLES -# Dictionary containing all suite variables inserted via -# `[jinja2:suite.rc]` or `[empy:suite.rc]` section. -# -# SEE ALSO -# * `cylc help gui` -# * `cylc help register` -# * `cylc help run` -#------------------------------------------------------------------------------- -exec python3 -m metomi.rose.suite_run "$@" diff --git a/bin/rose-suite-scan b/bin/rose-suite-scan deleted file mode 100755 index fdaba4d0e4..0000000000 --- a/bin/rose-suite-scan +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# NAME -# rose suite-scan -# -# SYNOPSIS -# rose suite-scan [...] -# -# DESCRIPTION -# Run `cylc scan [...]`. -#------------------------------------------------------------------------------- -exec cylc scan "$@" diff --git a/bin/rose-suite-shutdown b/bin/rose-suite-shutdown deleted file mode 100755 index 4cfd3ca1a1..0000000000 --- a/bin/rose-suite-shutdown +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# NAME -# rose suite-shutdown -# -# SYNOPSIS -# rose suite-shutdown [OPTIONS] [--name=SUITE-NAME] [[--] EXTRA-ARGS ...] -# -# DESCRIPTION -# Shutdown a running suite. -# -# If `--name=SUITE-NAME` is not specified, the name will be determined by -# locating a `rose-suite.conf` file in `$PWD` or its nearest parent -# directories. In a normal suite, the basename of the (nearest parent) -# directory containing the `rose-suite.conf` file is assumed to be the -# suite name. In a project containing a rose stem suite, the basename of -# the (nearest parent) directory containing the `rose-stem/rose-suite.conf` -# file is assumed to be the suite name. -# -# This wrapper also deals with the use case where a suite may be running on -# dedicated servers at a site. The wrapper will make an attempt to detect -# where the suite is running or last run. -# -# OPTIONS -# --all -# Shutdown all running suites. You will be prompted to confirm shutting -# down each affected suite. -# --name=SUITE-NAME -# Specify the suite name. -# --non-interactive, --yes, -y -# Switch off interactive prompting (=answer yes to everything) -# --quiet, -q -# Decrement verbosity. -# --verbose, -v -# Increment verbosity. -# -- --EXTRA-ARGS -# See the cylc documentation, cylc shutdown for options and details on -# `EXTRA-ARGS`. -#------------------------------------------------------------------------------- -exec python3 -m metomi.rose.suite_control shutdown "$@" diff --git a/bin/rose-suite-stop b/bin/rose-suite-stop deleted file mode 100755 index 30a76f43ff..0000000000 --- a/bin/rose-suite-stop +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Alias of "rose suite-shutdown". -#------------------------------------------------------------------------------- -exec "$(dirname "$0")/rose-suite-shutdown" "$@" diff --git a/bin/rose-task-hook b/bin/rose-task-hook deleted file mode 100755 index 34934cee3e..0000000000 --- a/bin/rose-task-hook +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Alias of "rose suite-hook". -#------------------------------------------------------------------------------- -exec "$(dirname "$0")/rose-suite-hook" "$@" diff --git a/t/rose-help/00-alias.t b/t/rose-help/00-alias.t index 702dacc783..3908781484 100755 --- a/t/rose-help/00-alias.t +++ b/t/rose-help/00-alias.t @@ -21,21 +21,16 @@ #------------------------------------------------------------------------------- . $(dirname $0)/test_header #------------------------------------------------------------------------------- -tests 12 +tests 4 #------------------------------------------------------------------------------- export PAGER=cat -cat >'rose-aliases.txt' <<'__TXT__' -config-edit edit -suite-hook task-hook -suite-log slv suite-log-view -__TXT__ cat >'rosie-aliases.txt' <<'__TXT__' checkout co create copy __TXT__ #------------------------------------------------------------------------------- -for PREFIX in 'rose' 'rosie'; do +for PREFIX in 'rosie'; do while read; do COMMAND=$(cut -d' ' -f 1 <<<"${REPLY}") "${PREFIX}" help "${COMMAND}" >'help.txt' From bfe43071638c6bee2ed25dd6d9d022ea60284460 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Fri, 15 Jan 2021 16:01:16 +0000 Subject: [PATCH 37/78] tests: silence test output --- t/rosie-disco/00-basic.t | 1 - 1 file changed, 1 deletion(-) diff --git a/t/rosie-disco/00-basic.t b/t/rosie-disco/00-basic.t index 7432fe3612..65a59f47ee 100755 --- a/t/rosie-disco/00-basic.t +++ b/t/rosie-disco/00-basic.t @@ -59,7 +59,6 @@ URL_FOO_Q="${URL_FOO}query?" TEST_KEY=$TEST_KEY_BASE-curl-root-trailing-slash run_pass "$TEST_KEY" curl -i "${TEST_ROSE_WS_URL}/" # note: slash at end -cat $TEST_KEY.out >&2 file_grep "$TEST_KEY.out" 'HTTP/.* 200 OK' "$TEST_KEY.out" # The app has been set-up so that a trailing slash, as in the test directly From ad8eed5c3de97a9402d712eec889ecfc27b97d49 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Thu, 21 Jan 2021 16:52:42 +0000 Subject: [PATCH 38/78] docs: remove cylc tutorial --- .../tutorial/cylc/furthertopics/broadcast.rst | 135 ---- .../furthertopics/clock-triggered-tasks.rst | 175 ----- .../cylc/furthertopics/family-triggers.rst | 208 ------ sphinx/tutorial/cylc/furthertopics/index.rst | 16 - .../cylc/furthertopics/inheritance.rst | 610 ---------------- sphinx/tutorial/cylc/furthertopics/queues.rst | 105 --- .../tutorial/cylc/furthertopics/retries.rst | 156 ---- .../cylc/furthertopics/suicide-triggers.rst | 676 ------------------ .../tutorial/cylc/img/cylc-graph-cluster.png | Bin 6668 -> 0 bytes .../tutorial/cylc/img/cylc-graph-refresh.png | Bin 6946 -> 0 bytes .../cylc/img/cylc-graph-reversible.svg | 77 -- sphinx/tutorial/cylc/img/cylc-graph.png | Bin 49286 -> 0 bytes sphinx/tutorial/cylc/img/cylc-gui-dot.png | Bin 35155 -> 0 bytes sphinx/tutorial/cylc/img/cylc-gui-graph.png | Bin 111441 -> 0 bytes .../cylc/img/cylc-gui-suite-start.png | Bin 18138 -> 0 bytes sphinx/tutorial/cylc/img/cylc-gui-tree.png | Bin 59606 -> 0 bytes .../tutorial/cylc/img/cylc-gui-view-log.png | Bin 19206 -> 0 bytes .../cylc/img/cylc-gui-view-selector.png | Bin 7023 -> 0 bytes sphinx/tutorial/cylc/img/cylc-gui.png | Bin 153532 -> 0 bytes sphinx/tutorial/cylc/img/gcylc-play.png | Bin 1939 -> 0 bytes sphinx/tutorial/cylc/img/iso8601-dates.svg | 265 ------- sphinx/tutorial/cylc/index.rst | 21 - sphinx/tutorial/cylc/introduction.rst | 94 --- .../configuration-consolidation/families.rst | 333 --------- .../configuration-consolidation/index.rst | 191 ----- .../configuration-consolidation/jinja2.rst | 236 ------ .../parameters.rst | 349 --------- sphinx/tutorial/cylc/runtime/index.rst | 18 - sphinx/tutorial/cylc/runtime/introduction.rst | 518 -------------- .../cylc/runtime/runtime-configuration.rst | 501 ------------- .../cylc/scheduling/datetime-cycling.rst | 632 ---------------- .../cylc/scheduling/further-scheduling.rst | 107 --- sphinx/tutorial/cylc/scheduling/graphing.rst | 408 ----------- sphinx/tutorial/cylc/scheduling/index.rst | 15 - .../cylc/scheduling/integer-cycling.rst | 612 ---------------- 35 files changed, 6458 deletions(-) delete mode 100644 sphinx/tutorial/cylc/furthertopics/broadcast.rst delete mode 100644 sphinx/tutorial/cylc/furthertopics/clock-triggered-tasks.rst delete mode 100644 sphinx/tutorial/cylc/furthertopics/family-triggers.rst delete mode 100644 sphinx/tutorial/cylc/furthertopics/index.rst delete mode 100644 sphinx/tutorial/cylc/furthertopics/inheritance.rst delete mode 100644 sphinx/tutorial/cylc/furthertopics/queues.rst delete mode 100644 sphinx/tutorial/cylc/furthertopics/retries.rst delete mode 100644 sphinx/tutorial/cylc/furthertopics/suicide-triggers.rst delete mode 100644 sphinx/tutorial/cylc/img/cylc-graph-cluster.png delete mode 100644 sphinx/tutorial/cylc/img/cylc-graph-refresh.png delete mode 100644 sphinx/tutorial/cylc/img/cylc-graph-reversible.svg delete mode 100644 sphinx/tutorial/cylc/img/cylc-graph.png delete mode 100644 sphinx/tutorial/cylc/img/cylc-gui-dot.png delete mode 100644 sphinx/tutorial/cylc/img/cylc-gui-graph.png delete mode 100644 sphinx/tutorial/cylc/img/cylc-gui-suite-start.png delete mode 100644 sphinx/tutorial/cylc/img/cylc-gui-tree.png delete mode 100644 sphinx/tutorial/cylc/img/cylc-gui-view-log.png delete mode 100644 sphinx/tutorial/cylc/img/cylc-gui-view-selector.png delete mode 100644 sphinx/tutorial/cylc/img/cylc-gui.png delete mode 100644 sphinx/tutorial/cylc/img/gcylc-play.png delete mode 100644 sphinx/tutorial/cylc/img/iso8601-dates.svg delete mode 100644 sphinx/tutorial/cylc/index.rst delete mode 100644 sphinx/tutorial/cylc/introduction.rst delete mode 100644 sphinx/tutorial/cylc/runtime/configuration-consolidation/families.rst delete mode 100644 sphinx/tutorial/cylc/runtime/configuration-consolidation/index.rst delete mode 100644 sphinx/tutorial/cylc/runtime/configuration-consolidation/jinja2.rst delete mode 100644 sphinx/tutorial/cylc/runtime/configuration-consolidation/parameters.rst delete mode 100644 sphinx/tutorial/cylc/runtime/index.rst delete mode 100644 sphinx/tutorial/cylc/runtime/introduction.rst delete mode 100644 sphinx/tutorial/cylc/runtime/runtime-configuration.rst delete mode 100644 sphinx/tutorial/cylc/scheduling/datetime-cycling.rst delete mode 100644 sphinx/tutorial/cylc/scheduling/further-scheduling.rst delete mode 100644 sphinx/tutorial/cylc/scheduling/graphing.rst delete mode 100644 sphinx/tutorial/cylc/scheduling/index.rst delete mode 100644 sphinx/tutorial/cylc/scheduling/integer-cycling.rst diff --git a/sphinx/tutorial/cylc/furthertopics/broadcast.rst b/sphinx/tutorial/cylc/furthertopics/broadcast.rst deleted file mode 100644 index dcd09b01d7..0000000000 --- a/sphinx/tutorial/cylc/furthertopics/broadcast.rst +++ /dev/null @@ -1,135 +0,0 @@ -Broadcast -========= - -This tutorial walks you through using ``cylc broadcast`` which can be used -to change :ref:`task runtime configuration ` in a -running suite, on-the-fly. - - -Purpose -------- - -``cylc broadcast`` can be used to change any ``[runtime]`` setting whilst the -suite is running. - -The standard use of ``cylc broadcast`` is to update the suite to an -unexpected change in configuration, for example modifying the host a task -runs on. - - -Standalone Example ------------------- - -Create a new suite in the ``cylc-run`` directory called -``tutorial-broadcast``:: - - mkdir ~/cylc-run/tutorial-broadcast - cd ~/cylc-run/tutorial-broadcast - -Copy the following configuration into a ``flow.cylc`` file: - -.. code-block:: cylc - - [scheduling] - initial cycle point = 1012 - [[dependencies]] - [[[R1]]] - graph = wipe_log => announce - [[[PT1H]]] - graph = announce[-PT1H] => announce - - [runtime] - [[wipe_log]] - # Delete any files in the suite's "share" directory. - script = rm "${CYLC_SUITE_SHARE_DIR}/knights" || true - - [[announce]] - script = echo "${CYLC_TASK_CYCLE_POINT} - ${MESSAGE}" >> "${FILE}" - [[[environment]]] - WORD = ni - MESSAGE = We are the knights who say \"${WORD}\"! - FILE = "${CYLC_SUITE_SHARE_DIR}/knights" - -We now have a suite with an ``announce`` task which runs every hour, writing a -message to a log file (``share/knights``) when it does so. For the first cycle -the log entry will look like this:: - - 10120101T0000Z - We are the knights who say "ni"! - -The ``cylc broadcast`` command enables us to change runtime configuration -whilst the suite is running. For instance we could change the value of the -``WORD`` environment variable using the command:: - - cylc broadcast tutorial-broadcast -n announce -s "[environment]WORD=it" - -* ``tutorial-broadcast`` is the name of the suite. -* ``-n announce`` tells Cylc we want to change the runtime configuration of the - ``announce`` task. -* ``-s "[environment]WORD=it"`` changes the value of the ``WORD`` environment - variable to ``it``. - -Run the suite then try using the ``cylc broadcast`` command to change the -message:: - - cylc run tutorial-broadcast - cylc broadcast tutorial-broadcast -n announce -s "[environment]WORD=it" - -Inspect the ``share/knights`` file, you should see the message change at -certain points. - -Stop the suite:: - - cylc stop tutorial-broadcast - - -In-Situ Example ---------------- - -We can call ``cylc broadcast`` from within a task's script. This effectively -provides the ability for tasks to communicate between themselves. - -It is almost always better for tasks to communicate using files but there are -some niche situations where communicating via ``cylc broadcast`` is justified. -This tutorial walks you through using ``cylc broadcast`` to communicate between -tasks. - -.. TODO - examples of this? - -Add the following recurrence to the ``dependencies`` section: - -.. code-block:: cylc - - [[[PT3H]]] - graph = announce[-PT1H] => change_word => announce - -The ``change_word`` task runs the ``cylc broadcast`` command to randomly -change the ``WORD`` environment variable used by the ``announce`` task. - -Add the following runtime configuration to the ``runtime`` section: - -.. code-block:: cylc - - [[change_word]] - script = """ - # Select random word. - IFS=',' read -r -a WORDS <<< $WORDS - WORD=${WORDS[$(date +%s) % ${#WORDS[@]}]} - - # Broadcast random word to the announce task. - cylc broadcast $CYLC_SUITE_NAME -n announce -s "[environment]WORD=${WORD}" - """ - [[[environment]]] - WORDS = ni, it, ekke ekke ptang zoo boing - -Run the suite and inspect the log. You should see the message change randomly -after every third entry (because the ``change_word`` task runs every 3 hours) -e.g:: - - 10120101T0000Z - We are the knights who say "ni"! - 10120101T0100Z - We are the knights who say "ni"! - 10120101T0200Z - We are the knights who say "ni"! - 10120101T0300Z - We are the knights who say "ekke ekke ptang zoo boing!" - -Stop the suite:: - - cylc stop tutorial-broadcast diff --git a/sphinx/tutorial/cylc/furthertopics/clock-triggered-tasks.rst b/sphinx/tutorial/cylc/furthertopics/clock-triggered-tasks.rst deleted file mode 100644 index e5be426db0..0000000000 --- a/sphinx/tutorial/cylc/furthertopics/clock-triggered-tasks.rst +++ /dev/null @@ -1,175 +0,0 @@ -.. include:: ../../../hyperlinks.rst - :start-line: 1 - -.. _tutorial-cylc-clock-trigger: - -Clock Triggered Tasks -===================== - -.. TODO - - After #2423 has been finalised and merged this tutorial should be - re-factored / re-written to incorporate the usage of ``cylc-graph``. - -In a :term:`datetime cycling` suite the time represented by the -:term:`cycle points ` bear no relation to the real-world time. -Using clock-triggers we can make tasks wait until their cycle point time before -running. - -Clock-triggering effectively enables us to tether the "cycle time" to the -"real world time" which we refer to as the :term:`wall-clock time`. - - -Clock Triggering ----------------- - -When clock-triggering tasks we can use different -:ref:`offsets ` to the cycle time as follows: - -.. code-block:: cylc - - clock-trigger = taskname(CYCLE_OFFSET) - -.. note:: - - Regardless of the offset used, the task still belongs to the cycle from - which the offset has been applied. - - -Example -------- - -Our example suite will simulate a clock chiming on the hour. - -Within your ``~/cylc-run`` directory create a new directory called -``clock-trigger``:: - - mkdir ~/cylc-run/clock-trigger - cd ~/cylc-run/clock-trigger - -Paste the following code into a ``flow.cylc`` file: - -.. code-block:: cylc - - [cylc] - UTC mode = True # Ignore DST - - [scheduling] - initial cycle point = TODO - final cycle point = +P1D # Run for one day - [[dependencies]] - [[[PT1H]]] - graph = bell - - [runtime] - [[root]] - [[[events]]] - mail events = failed - [[bell]] - env-script = eval $(rose task-env) - script = printf 'bong%.0s\n' $(seq 1 $(cylc cyclepoint --print-hour)) - -Change the initial cycle point to 00:00 this morning (e.g. if it was -the first of January 2000 we would write ``2000-01-01T00Z``). - -We now have a simple suite with a single task that prints "bong" a number -of times equal to the (cycle point) hour. - -Run your suite using:: - - cylc run clock-trigger - -Stop the suite after a few cycles using the :guilabel:`stop` button in the -``cylc gui``. Notice how the tasks run as soon as possible rather than -waiting for the actual time to be equal to the cycle point. - - -Clock-Triggering Tasks ----------------------- - -We want our clock to only ring in real-time rather than the simulated -cycle time. - -To do this, add the following lines to the ``[scheduling]`` section of -your ``flow.cylc``: - -.. code-block:: cylc - - [[special tasks]] - clock-trigger = bell(PT0M) - -This tells the suite to clock trigger the ``bell`` task with a cycle -offset of ``0`` hours. - -Save your changes and run your suite. - -Your suite should now be running the ``bell`` task in real-time. Any cycle times -that have already passed (such as the one defined by ``initial cycle time``) -will be run as soon as possible, while those in the future will wait for that -time to pass. - -At this point you may want to leave your suite running until the next hour -has passed in order to confirm the clock triggering is working correctly. -Once you are satisfied, stop your suite. - -By making the ``bell`` task a clock triggered task we have made it run in -real-time. Thus, when the wall-clock time caught up with the cycle time, the -``bell`` task triggered. - - -Adding More Clock-Triggered Tasks ---------------------------------- - -We will now modify our suite to run tasks at quarter-past, half-past and -quarter-to the hour. - -Open your ``flow.cylc`` and modify the ``[runtime]`` section by adding the -following: - -.. code-block:: cylc - - [[quarter_past, half_past, quarter_to]] - script = echo 'chimes' - -Edit the ``[[scheduling]]`` section to read: - -.. code-block:: cylc - - [[special tasks]] - clock-trigger = bell(PT0M), quarter_past(PT15M), half_past(PT30M), quarter_to(PT45M) - [[dependencies]] - [[[PT1H]]] - graph = """ - bell - quarter_past - half_past - quarter_to - """ - -Note the different values used for the cycle offsets of the clock-trigger tasks. - -Save your changes and run your suite using:: - - cylc run clock-trigger now - -.. note:: - - The ``now`` argument will run your suite using the current time for the - initial cycle point. - -Again, notice how the tasks trigger until the current time is reached. - -Leave your suite running for a while to confirm it is working as expected -and then shut it down using the :guilabel:`stop` button in the ``cylc gui``. - - -Summary -------- - -* Clock triggers are a type of :term:`dependency` which cause - :term:`tasks ` to wait for the :term:`wall-clock time` to reach the - :term:`cycle point` time. -* A clock trigger applies only to a single task. -* Clock triggers can only be used in datetime cycling suites. - -For more information see the `Cylc User Guide`_. diff --git a/sphinx/tutorial/cylc/furthertopics/family-triggers.rst b/sphinx/tutorial/cylc/furthertopics/family-triggers.rst deleted file mode 100644 index 73522474dc..0000000000 --- a/sphinx/tutorial/cylc/furthertopics/family-triggers.rst +++ /dev/null @@ -1,208 +0,0 @@ -.. _tutorial-cylc-family-triggers: - -Family Triggers -=============== - -To reduce duplication in the :term:`graph` is is possible to write -:term:`dependencies ` using collections of tasks called -:term:`families `). - -This tutorial walks you through writing such dependencies using family -:term:`triggers `. - - -Explanation ------------ - -Dependencies between tasks can be written using a :term:`qualifier` to describe -the :term:`task state` that the dependency refers to (e.g. ``succeed`` -``fail``, etc). If a dependency does not use a qualifier then it is assumed -that the dependency refers to the ``succeed`` state e.g: - -.. code-block:: cylc-graph - - bake_bread => sell_bread # sell_bread is dependent on bake_bread succeeding. - bake_bread:succeed => sell_bread # sell_bread is dependent on bake_bread succeeding. - sell_bread:fail => through_away # through_away is dependent on sell_bread failing. - -The left-hand side of a :term:`dependency` (e.g. ``sell_bread:fail``) is -referred to as the :term:`trigger `. - -When we write a trigger involving a family, special qualifiers are required -to specify whether the dependency is concerned with *all* or *any* of the tasks -in that family reaching the desired :term:`state ` e.g: - -* ``succeed-all`` -* ``succeed-any`` -* ``fail-all`` - -Such :term:`triggers ` are referred to as -:term:`family triggers ` - -Foo ``cylc gui`` bar - - -Example -------- - -Create a new suite called ``tutorial-family-triggers``:: - - mkdir ~/cylc-run/tutorial-family-triggers - cd ~/cylc-run/tutorial-family-triggers - -Paste the following configuration into the ``flow.cylc`` file: - -.. code-block:: cylc - - [cylc] - UTC mode = True # Ignore DST - [scheduling] - [[dependencies]] - graph = visit_mine => MINERS - [runtime] - [[visit_mine]] - script = sleep 5; echo 'off to work we go' - - [[MINERS]] - script = """ - sleep 5; - if (($RANDOM % 2)); then - echo 'Diamonds!'; true; - else - echo 'Nothing...'; false; - fi - """ - [[doc, grumpy, sleepy, happy, bashful, sneezy, dopey]] - inherit = MINERS - -You have now created a suite that: - -* Has a ``visit_mine`` task that sleeps for 5 seconds then outputs a - message. -* Contains a ``MINERS`` family with a command in it that randomly succeeds - or fails. -* Has 7 tasks that inherit from the ``MINERS`` family. - -Open the ``cylc gui`` then run the suite by pressing the "play" button -(top left hand corner) then clicking :guilabel:`Start`:: - - cylc gui tutorial-family-triggers & - -You should see the ``visit_mine`` task run, then trigger the members of the -``MINERS`` family. Note that some of the ``MINERS`` tasks may fail so you -will need to stop your suite using the "stop" button in the ``cylc gui`` in -order to allow it to shutdown. - - -Family Triggering: Success --------------------------- - -As you will have noticed by watching the suite run, some of the tasks in the -``MINERS`` family succeed and some fail. - -We would like to add a task to sell any diamonds we find, but wait for all -the miners to report back first so we only make the one trip. - -We can address this by using *family triggers*. In particular, we are going -to use the ``finish-all`` trigger to check for all members of the ``MINERS`` -family finishing, and the ``succeed-any`` trigger to check for any of the -tasks in the ``MINERS`` family succeeding. - -Open your ``flow.cylc`` file and change the ``[[dependencies]]`` to look like -this: - -.. code-block:: cylc - - [[dependencies]] - graph = """visit_mine => MINERS - MINERS:finish-all & MINERS:succeed-any => sell_diamonds""" - -Then, add the following task to the ``[runtime]`` section: - -.. code-block:: cylc - - [[sell_diamonds]] - script = sleep 5 - -These changes add a ``sell_diamonds`` task to the suite which is run once -all the ``MINERS`` tasks have finished and if any of them have succeeded. - -Save your changes and run your suite. You should see the new -``sell_diamonds`` task being run once all the miners have finished and at -least one of them has succeeded. As before, stop your suite using the "stop" -button in the ``cylc gui``. - - -Family Triggering: Failure --------------------------- - -Cylc also allows us to trigger off failure of tasks in a particular family. - -We would like to add another task to close down unproductive mineshafts once -all the miners have reported back and had time to discuss their findings. - -To do this we will make use of family triggers in a similar manner to before. - -Open your ``flow.cylc`` file and change the ``[[dependencies]]`` to look like -this: - -.. code-block:: cylc - - [[dependencies]] - graph = """visit_mine => MINERS - MINERS:finish-all & MINERS:succeed-any => sell_diamonds - MINERS:finish-all & MINERS:fail-any => close_shafts - close_shafts => !MINERS - """ - -Alter the ``[[sell_diamonds]]`` section to look like this: - -.. code-block:: cylc - - [[close_shafts, sell_diamonds]] - script = sleep 5 - -These changes add a ``close_shafts`` task which is run once all the -``MINERS`` tasks have finished and any of them have failed. On completion -it applies a *suicide trigger* to the ``MINERS`` family in order to allow -the suite to shutdown. - -Save your changes and run your suite. You should see the new -``close_shafts`` run should any of the ``MINERS`` tasks be in the failed -state once they have all finished. - -.. tip:: - - See the :ref:`tut-cylc-suicide-triggers` tutorial for handling task - failures. - - -Different Triggers ------------------- - -Other family :term:`qualifiers ` beyond those covered in the -example are also available. - -The following types of "all" qualifier are available: - -* ``:start-all`` - all the tasks in the family have started -* ``:succeed-all`` - all the tasks in the family have succeeded -* ``:fail-all`` - all the tasks in the family have failed -* ``:finish-all`` - all the tasks in the family have finished - -The following types of "any" qualifier are available: - -* ``:start-any`` - at least one task in the family has started -* ``:succeed-any`` - at least one task in the family has succeeded -* ``:fail-any`` - at least one task in the family has failed -* ``:finish-any`` - at least one task in the family has finished - - -Summary -------- - -* Family triggers allow you to write dependencies for collections of tasks. -* Like :term:`task triggers `, family triggers can be based on - success, failure, starting and finishing of tasks in a family. -* Family triggers can trigger off either *all* or *any* of the tasks in a - family. diff --git a/sphinx/tutorial/cylc/furthertopics/index.rst b/sphinx/tutorial/cylc/furthertopics/index.rst deleted file mode 100644 index 17e4126286..0000000000 --- a/sphinx/tutorial/cylc/furthertopics/index.rst +++ /dev/null @@ -1,16 +0,0 @@ -Further Topics -============== - -This section looks at further topics in cylc. - -.. toctree:: - :name: cylc-futher-topics - :maxdepth: 1 - - clock-triggered-tasks - broadcast - family-triggers - inheritance - queues - retries - suicide-triggers diff --git a/sphinx/tutorial/cylc/furthertopics/inheritance.rst b/sphinx/tutorial/cylc/furthertopics/inheritance.rst deleted file mode 100644 index d9fa894e5f..0000000000 --- a/sphinx/tutorial/cylc/furthertopics/inheritance.rst +++ /dev/null @@ -1,610 +0,0 @@ -Inheritance -=========== - -We have seen in the :ref:`runtime tutorial ` how -tasks can be grouped into families. - -In this tutorial we will look at nested families, inheritance order and -multiple inheritance. - - -Inheritance Hierarchy ---------------------- - -Create a new suite by running the command:: - - rose tutorial inheritance-tutorial - cd ~/cylc-run/inheritance-tutorial - -You will now have a ``flow.cylc`` file that defines two tasks each representing -a different aircraft, the Airbus A380 jumbo jet and the Robson R44 helicopter: - -.. image:: https://upload.wikimedia.org/wikipedia/commons/0/09/A6-EDY_A380_Emirates_31_jan_2013_jfk_%288442269364%29_%28cropped%29.jpg - :width: 49% - :alt: A380 - -.. image:: https://upload.wikimedia.org/wikipedia/commons/2/2f/Robinson-R44_1.jpg - :width: 49% - :alt: R44 - -.. code-block:: cylc - - [scheduling] - [[dependencies]] - graph = a380 & r44 - - [runtime] - [[VEHICLE]] - init-script = echo 'Boarding' - pre-script = echo 'Departing' - post-script = echo 'Arriving' - - [[AIR_VEHICLE]] - inherit = VEHICLE - [[[meta]]] - description = A vehicle which can fly. - [[AIRPLANE]] - inherit = AIR_VEHICLE - [[[meta]]] - description = An air vehicle with fixed wings. - [[[environment]]] - CAN_TAKE_OFF_VERTICALLY = false - [[HELICOPTER]] - inherit = AIR_VEHICLE - [[[meta]]] - description = An air vehicle with rotors. - [[[environment]]] - CAN_TAKE_OFF_VERTICALLY = true - - [[a380]] - inherit = AIRPLANE - [[[meta]]] - title = Airbus A380 Jumbo-Jet. - [[r44]] - inherit = HELICOPTER - [[[meta]]] - title = Robson R44 Helicopter. - -.. note:: - - The ``[meta]`` section is a freeform section where we can define metadata - to be associated with a task, family or the suite itself. - - This metadata should not be mistaken with Rose :ref:`conf-meta`. - -.. admonition:: Reminder - :class: hint - - By convention we write family names in upper case (with the exception of the - special ``root`` family) and task names in lower case. - -These two tasks sit at the bottom of an inheritance tree. The ``cylc graph`` -command has an option (``-n``) for drawing such inheritance hierarchies:: - - cylc graph -n . & - -Running this command will generate the following output: - -.. digraph:: Example - :align: center - - AIRPLANE [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - a380 [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - AIRPLANE -> a380 [color=royalblue]; - HELICOPTER [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - r44 [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - HELICOPTER -> r44 [color=royalblue]; - root [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - VEHICLE [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - root -> VEHICLE [color=royalblue]; - AIR_VEHICLE [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - VEHICLE -> AIR_VEHICLE [color=royalblue]; - AIR_VEHICLE -> AIRPLANE [color=royalblue]; - AIR_VEHICLE -> HELICOPTER [color=royalblue]; - -.. note:: - - The ``root`` family sits at the top of the inheritance tree as all - tasks/families automatically inherit it: - -Cylc handles inheritance by starting with the root family and working down the -inheritance tree applying each section in turn. - -To see the resulting configuration for the ``a380`` task use the -``cylc get-config`` command:: - - cylc get-config . --sparse -i "[runtime][a380]" - -You should see some settings which have been inherited from the ``VEHICLE`` and -``AIRPLANE`` families as well as a couple defined in the ``a380`` task. - -.. code-block:: cylc - - init-script = echo 'Boarding' # Inherited from VEHICLE - pre-script = echo 'Departing' # Inherited from VEHICLE - post-script = echo 'Arriving' # Inherited from VEHICLE - inherit = AIRPLANE # Defined in a380 - [[[meta]]] - description = An air vehicle with fixed wings. # Inherited from AIR_VEHICLE - overwritten by AIRPLANE - title = Airbus A380 Jumbo-Jet. # Defined in a380 - [[[environment]]] - CAN_TAKE_OFF_VERTICALLY = false # Inherited from AIRPLANE - -Note that the ``description`` setting is defined in the ``AIR_VEHICLE`` -family but is overwritten by the value specified in the ``AIRPLANE`` family. - - -Multiple Inheritance --------------------- - -Next we want to add a vehicle called the V-22 Osprey to the suite. The V-22 -is a cross between a plane and a helicopter - it has wings but can take-off and -land vertically. - -.. image:: https://upload.wikimedia.org/wikipedia/commons/e/e3/MV-22_mcas_Miramar_2014.JPG - :width: 300px - :align: center - -As the V-22 can be thought of as both a plane and a helicopter we want it to -inherit from both the ``AIRPLANE`` and ``HELICOPTER`` families. In Cylc we can -inherit from multiple families by separating their names with commas: - -Add the following task to your ``flow.cylc`` file. - -.. code-block:: cylc - - [[v22]] - inherit = AIRPLANE, HELICOPTER - [[[meta]]] - title = V-22 Osprey Military Aircraft. - -Refresh your ``cylc graph`` window or re-run the ``cylc graph`` command. - -The inheritance hierarchy should now look like this: - -.. digraph:: Example - :align: center - - AIRPLANE [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - v22 [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - AIRPLANE -> v22 [color=royalblue]; - a380 [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - AIRPLANE -> a380 [color=royalblue]; - HELICOPTER [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - HELICOPTER -> v22 [color=royalblue]; - r44 [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - HELICOPTER -> r44 [color=royalblue]; - root [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - VEHICLE [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - root -> VEHICLE [color=royalblue]; - AIR_VEHICLE [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - VEHICLE -> AIR_VEHICLE [color=royalblue]; - AIR_VEHICLE -> AIRPLANE [color=royalblue]; - AIR_VEHICLE -> HELICOPTER [color=royalblue]; - -Inspect the configuration of the ``v22`` task using the ``cylc get-config`` -command. - -.. spoiler:: Hint warning - - .. code-block:: bash - - cylc get-config . --sparse -i "[runtime][v22]" - -You should see that the ``CAN_TASK_OFF_VERTICALLY`` environment variable has -been set to ``false`` which isn't right. This is because of the order in which -inheritance is applied. - -Cylc handles multiple-inheritance by applying each family from right to left. -For the ``v22`` task we specified ``inherit = AIRPLANE, HELICOPTER`` so the -``HELICOPTER`` family will be applied first and the ``AIRPLANE`` family after. - -The inheritance order would be as follows: - -.. code-block:: bash - - root - VEHICLE - AIR_VEHICLE - HELICOPTER # sets "CAN_TAKE_OFF_VERTICALLY to "true" - AIRPLANE # sets "CAN_TAKE_OFF_VERTICALLY to "false" - v22 - -We could fix this problem by changing the order of inheritance: - -.. code-block:: cylc - - inherit = HELICOPTER, AIRPLANE - -Now the ``HELICOPTER`` family is applied second so its values will override any -in the ``AIRPLANE`` family. - -.. code-block:: bash - - root - VEHICLE - AIR_VEHICLE - AIRPLANE # sets "CAN_TAKE_OFF_VERTICALLY to "false" - HELICOPTER # sets "CAN_TAKE_OFF_VERTICALLY to "true" - v22 - -Inspect the configuration of the ``v22`` task using ``cylc get-config`` to -confirm this. - - -More Inheritance ----------------- - -We will now add some more families and tasks to the suite. - -Engine Type -^^^^^^^^^^^ - -Next we will define four families to represent three different types of engine. - -.. digraph:: Example - :align: center - - size = "5,5" - - ENGINE [color=royalblue, fillcolor=powderblue, shape=box, style=filled, - margin="0.3,0.055"] - TURBINE_ENGINE [color=royalblue, fillcolor=powderblue, shape=box, - style=filled, margin="0.3,0.055"] - INTERNAL_COMBUSTION_ENGINE [color=royalblue, fillcolor=powderblue, - shape=box, style=filled, margin="0.3,0.055"] - HUMAN_ENGINE [color=royalblue, fillcolor=powderblue, shape=box, - style=filled, margin="0.3,0.055"] - - "ENGINE" -> "TURBINE_ENGINE" - "ENGINE" -> "INTERNAL_COMBUSTION_ENGINE" - "ENGINE" -> "HUMAN_ENGINE" - -Each engine type should set an environment variable called ``FUEL`` which we -will assign to the following values: - -* Turbine - kerosene -* Internal Combustion - petrol -* Human - pizza - -Add lines to the ``runtime`` section to represent these four families. - -.. spoiler:: Solution warning - - .. code-block:: cylc - - [[ENGINE]] - [[TURBINE_ENGINE]] - inherit = ENGINE - [[[environment]]] - FUEL = kerosene - [[INTERNAL_COMBUSTION_ENGINE]] - inherit = ENGINE - [[[environment]]] - FUEL = petrol - [[HUMAN_ENGINE]] - inherit = ENGINE - [[[environment]]] - FUEL = pizza - -We now need to make the three aircraft inherit from one of the three engines. -The aircraft use the following types of engine: - -* A380 - turbine -* R44 - internal combustion -* V22 - turbine - -Modify the three tasks so that they inherit from the relevant engine families. - -.. spoiler:: Solution warning - - .. code-block:: cylc - - [[a380]] - inherit = AIRPLANE, TURBINE_ENGINE - [[[meta]]] - title = Airbus A380 Jumbo-Jet. - [[r44]] - inherit = HELICOPTER, INTERNAL_COMBUSTION_ENGINE - [[[meta]]] - title = Robson R44 Helicopter. - [[v22]] - inherit = AIRPLANE, HELICOPTER, TURBINE_ENGINE - [[[meta]]] - title = V-22 Ofsprey Military Aircraft. - -Penny Farthing -^^^^^^^^^^^^^^ - -Next we want to add a new type of vehicle, an old-fashioned bicycle called a -penny farthing. - -.. image:: https://upload.wikimedia.org/wikipedia/commons/a/a7/Ordinary_bicycle01.jpg - :width: 300px - :alt: Penny Farthing Bicycle - :align: center - -To do this we will need to add two new families, ``LAND_VEICHLE`` and -``BICYCLE`` as well as a new task, ``penny_farthing`` related in the -following manner: - -.. digraph:: Example - :align: center - - VEHICLE [color=royalblue, fillcolor=powderblue, shape=box, style=filled] - LAND_VEHICLE [color=royalblue, fillcolor=powderblue, shape=box, - style=filled] - BICYCLE [color=royalblue, fillcolor=powderblue, shape=box, style=filled] - HUMAN_ENGINE [color=royalblue, fillcolor=powderblue, shape=box, - style=filled, margin="0.3,0.055"] - penny_farthing [color=royalblue, fillcolor=powderblue, shape=box, - style=filled, margin="0.3,0.055"] - VEHICLE -> LAND_VEHICLE -> BICYCLE -> penny_farthing - HUMAN_ENGINE -> penny_farthing - -Add lines to the ``runtime`` section to represent the two new families and one -task outlined above. - -Add a description (``[meta]description``) to the ``LAND_VEHICLE`` and -``BICYCLE`` families and a title (``[meta]title``) to the ``penny_farthing`` -task. - -.. spoiler:: Solution warning - - .. code-block:: cylc - - [[LAND_VEHICLE]] - inherit = VEHICLE - [[[meta]]] - description = A vehicle which can travel over the ground. - - [[BICYCLE]] - inherit = LAND_VEHICLE - [[[meta]]] - description = A small two-wheeled vehicle. - - [[penny_farthing]] - inherit = BICYCLE, HUMAN_ENGINE - [[[meta]]] - title = An old-fashioned bicycle. - - -Using ``cylc get-config`` to inspect the configuration of the ``penny_farthing`` -task we can see that it inherits settings from the ``VEHICLE``, -``BICYCLE`` and ``HUMAN_ENGINE`` families. - -.. code-block:: cylc - - inherit = BICYCLE, HUMAN_ENGINE - init-script = echo 'Boarding' # Inherited from VEHICLE - pre-script = echo 'Departing' # Inherited from VEHICLE - post-script = echo 'Arriving' # Inherited from VEHICLE - [[[environment]]] - FUEL = pizza # Inherited from HUMAN_ENGINE - [[[meta]]] - description = A small two-wheeled vehicle. # Inherited from LAND_VEHICLE - overwritten by BICYCLE - title = An old-fashioned bicycle. # Defined in penny_farthing - -.. spoiler:: Hint hint - - .. code-block:: bash - - cylc get-config . --sparse -i "[runtime]penny_farthing" - -Hovercraft -^^^^^^^^^^ - -We will now add a hovercraft called the Hoverwork BHT130, better known to some -as the Isle Of Wight Ferry. - -.. image:: https://upload.wikimedia.org/wikipedia/commons/e/e7/Hovercraft_leaving_Ryde.JPG - :width: 300px - :align: center - :alt: Hoverwork BHT130 Hovercraft - -Hovercraft can move over both land and water and in some respects can be thought -of as flying vehicles. - -.. digraph:: Example - :align: center - - size = "7,5" - - VEHICLE [color=royalblue, fillcolor=powderblue, shape=box, style=filled] - AIR_VEHICLE [color=royalblue, fillcolor=powderblue, shape=box, style=filled] - LAND_VEHICLE [color=royalblue, fillcolor=powderblue, shape=box, - style=filled] - WATER_VEHICLE [color=royalblue, fillcolor=powderblue, shape=box, - style=filled] - HOVERCRAFT [color=royalblue, fillcolor=powderblue, shape=box, style=filled] - bht130 [color=royalblue, fillcolor=powderblue, shape=box, style=filled] - ENGINE [color=royalblue, fillcolor=powderblue, shape=box, style=filled] - INTERNAL_COMBUSTION_ENGINE [color=royalblue, fillcolor=powderblue, - shape=box, style=filled, margin="0.3,0.055"] - VEHICLE -> AIR_VEHICLE -> HOVERCRAFT - VEHICLE -> LAND_VEHICLE -> HOVERCRAFT - VEHICLE -> WATER_VEHICLE -> HOVERCRAFT - HOVERCRAFT -> bht130 - ENGINE -> INTERNAL_COMBUSTION_ENGINE -> bht130 - -Write new families and one new task to represent the above structure. - -Add a description (``[meta]description``) to the ``WATER_VEHICLE`` and -``HOVERCRAFT`` families and a title (``[meta]title``) to the ``bht130`` task. - -.. spoiler:: Solution warning - - .. code-block:: cylc - - [[WATER_VEHICLE]] - inherit = VEHICLE - [[[meta]]] - description = A vehicle which can travel over water. - - [[HOVERCRAFT]] - inherit = LAND_VEHICLE, AIR_VEHICLE, WATER_VEHICLE - [[[meta]]] - description = A vehicle which can travel over ground, water and ice. - - [[bht130]] - inherit = HOVERCRAFT, INTERNAL_COMBUSTION_ENGINE - [[[meta]]] - title = Griffon Hoverwork BHT130 (Isle Of Whight Ferry). - - -Finished Suite --------------- - -You should now have a suite with an inheritance hierarchy which looks like -this: - -.. digraph:: Example - - size = "7, 5" - - root [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - ENGINE [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - root -> ENGINE [color=royalblue]; - VEHICLE [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - root -> VEHICLE [color=royalblue]; - INTERNAL_COMBUSTION_ENGINE [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled, - margin="0.3,0.055"]; - ENGINE -> INTERNAL_COMBUSTION_ENGINE [color=royalblue]; - TURBINE_ENGINE [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled, - margin="0.3,0.055"]; - ENGINE -> TURBINE_ENGINE [color=royalblue]; - HUMAN_ENGINE [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled, - margin="0.3,0.055"]; - ENGINE -> HUMAN_ENGINE [color=royalblue]; - LAND_VEHICLE [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - VEHICLE -> LAND_VEHICLE [color=royalblue]; - WATER_VEHICLE [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - VEHICLE -> WATER_VEHICLE [color=royalblue]; - AIR_VEHICLE [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - VEHICLE -> AIR_VEHICLE [color=royalblue]; - r44 [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - INTERNAL_COMBUSTION_ENGINE -> r44 [color=royalblue]; - bht130 [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - INTERNAL_COMBUSTION_ENGINE -> bht130 [color=royalblue]; - v22 [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - TURBINE_ENGINE -> v22 [color=royalblue]; - a380 [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - TURBINE_ENGINE -> a380 [color=royalblue]; - penny_farthing [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled, - margin="0.3,0.055"]; - HUMAN_ENGINE -> penny_farthing [color=royalblue]; - AIRPLANE [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - AIRPLANE -> v22 [color=royalblue]; - AIRPLANE -> a380 [color=royalblue]; - HELICOPTER [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - HELICOPTER -> v22 [color=royalblue]; - HELICOPTER -> r44 [color=royalblue]; - HOVERCRAFT [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - HOVERCRAFT -> bht130 [color=royalblue]; - LAND_VEHICLE -> HOVERCRAFT [color=royalblue]; - BICYCLE [color=royalblue, - fillcolor=powderblue, - shape=box, - style=filled]; - LAND_VEHICLE -> BICYCLE [color=royalblue]; - WATER_VEHICLE -> HOVERCRAFT [color=royalblue]; - AIR_VEHICLE -> AIRPLANE [color=royalblue]; - AIR_VEHICLE -> HELICOPTER [color=royalblue]; - AIR_VEHICLE -> HOVERCRAFT [color=royalblue]; - BICYCLE -> penny_farthing [color=royalblue]; diff --git a/sphinx/tutorial/cylc/furthertopics/queues.rst b/sphinx/tutorial/cylc/furthertopics/queues.rst deleted file mode 100644 index dafb9627bd..0000000000 --- a/sphinx/tutorial/cylc/furthertopics/queues.rst +++ /dev/null @@ -1,105 +0,0 @@ -.. include:: ../../../hyperlinks.rst - :start-line: 1 - -Queues -====== - -Queues are used to put a limit on the number of tasks that will be active at -any one time, even if their dependencies are satisfied. This avoids swamping -systems with too many tasks at once. - - -Example -------- - -In this example, our suite manages a particularly understaffed restaurant. - -Create a new suite called ``queues-tutorial``:: - - rose tutorial queues-tutorial - cd ~/cylc-run/queues-tutorial - -You will now have a ``flow.cylc`` file that looks like this: - -.. code-block:: cylc - - [scheduling] - [[dependencies]] - graph = """ - open_restaurant => steak1 & steak2 & pasta1 & pasta2 & pasta3 & \ - pizza1 & pizza2 & pizza3 & pizza4 - steak1 => ice_cream1 - steak2 => cheesecake1 - pasta1 => ice_cream2 - pasta2 => sticky_toffee1 - pasta3 => cheesecake2 - pizza1 => ice_cream3 - pizza2 => ice_cream4 - pizza3 => sticky_toffee2 - pizza4 => ice_cream5 - """ - - [runtime] - [[open_restaurant]] - [[MAINS]] - [[DESSERT]] - [[steak1,steak2,pasta1,pasta2,pasta3,pizza1,pizza2,pizza3,pizza4]] - inherit = MAINS - [[ice_cream1,ice_cream2,ice_cream3,ice_cream4,ice_cream5]] - inherit = DESSERT - [[cheesecake1,cheesecake2,sticky_toffee1,sticky_toffee2]] - inherit = DESSERT - -.. note:: - - In graph sections backslash (``\``) is a line continuation character i.e. the - following two examples are equivalent: - - .. code-block:: cylc - - foo => bar & \ - baz - - .. code-block:: cylc - - foo => bar & baz - -Open the ``cylc gui`` then run the suite:: - - cylc gui queues-tutorial & - cylc run queues-tutorial - -You will see that all the ``steak``, ``pasta``, and ``pizza`` tasks are run -at once, swiftly followed by all the ``ice_cream``, ``cheesecake``, -``sticky_toffee`` tasks as the customers order from the dessert menu. - -This will overwhelm our restaurant staff! The chef responsible for ``MAINS`` -can only handle 3 tasks at any given time, and the ``DESSERT`` chef can only -handle 2. - -We need to add some queues. Add a ``[queues]`` section to the ``[scheduling]`` -section like so: - -.. code-block:: cylc - - [scheduling] - [[queues]] - [[[mains_chef_queue]]] - limit = 3 # Only 3 mains dishes at one time. - members = MAINS - [[[dessert_chef_queue]]] - limit = 2 # Only 2 dessert dishes at one time. - members = DESSERT - -Re-open the ``cylc gui`` if you have closed it and re-run the suite. - -You should see that there are now never more than 3 active ``MAINS`` tasks -running and never more than 2 active ``DESSERT`` tasks running. - -The customers will obviously have to wait! - - -Further Reading ---------------- - -For more information, see the `Cylc User Guide`_. diff --git a/sphinx/tutorial/cylc/furthertopics/retries.rst b/sphinx/tutorial/cylc/furthertopics/retries.rst deleted file mode 100644 index e193339952..0000000000 --- a/sphinx/tutorial/cylc/furthertopics/retries.rst +++ /dev/null @@ -1,156 +0,0 @@ -.. include:: ../../../hyperlinks.rst - :start-line: 1 - -Retries -======= - -Retries allow us to automatically re-submit tasks which have failed due to -failure in submission or execution. - - -Purpose -------- - -Retries can be useful for tasks that may occasionally fail due to external -events, and are routinely fixable when they do - an example would be a task -that is dependent on a system that experiences temporary outages. - -If a task fails, the Cylc retry mechanism can resubmit it after a -pre-determined delay. An environment variable, ``$CYLC_TASK_TRY_NUMBER`` -is incremented and passed into the task - this means you can write your -task script so that it changes behaviour accordingly. - - -Example -------- - -.. image:: https://upload.wikimedia.org/wikipedia/commons/7/73/Double-six-dice.jpg - :width: 200px - :align: right - :alt: Two dice both showing the number six - -Create a new suite by running the following commands:: - - rose tutorial retries-tutorial - cd retries-tutorial - -You will now have a suite with a ``roll_doubles`` task which simulates -trying to roll doubles using two dice: - -.. code-block:: cylc - - [cylc] - UTC mode = True # Ignore DST - - [scheduling] - [[dependencies]] - graph = start => roll_doubles => win - - [runtime] - [[start]] - [[win]] - [[roll_doubles]] - script = """ - sleep 10 - RANDOM=$$ # Seed $RANDOM - DIE_1=$((RANDOM%6 + 1)) - DIE_2=$((RANDOM%6 + 1)) - echo "Rolled $DIE_1 and $DIE_2..." - if (($DIE_1 == $DIE_2)); then - echo "doubles!" - else - exit 1 - fi - """ - - -Running Without Retries ------------------------ - -Let's see what happens when we run the suite as it is. Open the ``cylc gui``:: - - cylc gui retries-tutorial & - -Then run the suite:: - - cylc run retries-tutorial - -Unless you're lucky, the suite should fail at the roll_doubles task. - -Stop the suite:: - - cylc stop retries-tutorial - - -Configuring Retries -------------------- - -We need to tell Cylc to retry it a few times. To do this, add the following -to the end of the ``[[roll_doubles]]`` task section in the ``flow.cylc`` file: - -.. code-block:: cylc - - [[[job]]] - execution retry delays = 5*PT6S - -This means that if the ``roll_doubles`` task fails, Cylc expects to -retry running it 5 times before finally failing. Each retry will have -a delay of 6 seconds. - -We can apply multiple retry periods with the ``execution retry delays`` setting -by separating them with commas, for example the following line would tell Cylc -to retry a task four times, once after 15 seconds, then once after 10 minutes, -then once after one hour then once after three hours. - -.. code-block:: cylc - - execution retry delays = PT15S, PT10M, PT1H, PT3H - - -Running With Retries --------------------- - -If you closed it, re-open the ``cylc gui``:: - - cylc gui retries-tutorial & - -Re-run the suite:: - - cylc run retries-tutorial - -What you should see is Cylc retrying the ``roll_doubles`` task. Hopefully, -it will succeed (there is only about a about a 1 in 3 chance of every task -failing) and the suite will continue. - - -Altering Behaviour ------------------- - -We can alter the behaviour of the task based on the number of retries, using -``$CYLC_TASK_TRY_NUMBER``. - -Change the ``script`` setting for the ``roll_doubles`` task to this:: - - sleep 10 - RANDOM=$$ # Seed $RANDOM - DIE_1=$((RANDOM%6 + 1)) - DIE_2=$((RANDOM%6 + 1)) - echo "Rolled $DIE_1 and $DIE_2..." - if (($DIE_1 == $DIE_2)); then - echo "doubles!" - elif (($CYLC_TASK_TRY_NUMBER >= 2)); then - echo "look over there! ..." - echo "doubles!" # Cheat! - else - exit 1 - fi - -If your suite is still running, stop it, then run it again. - -This time, the task should definitely succeed before the third retry. - - -Further Reading ---------------- - -For more information see the `Cylc User Guide`_. diff --git a/sphinx/tutorial/cylc/furthertopics/suicide-triggers.rst b/sphinx/tutorial/cylc/furthertopics/suicide-triggers.rst deleted file mode 100644 index 6e3cd02318..0000000000 --- a/sphinx/tutorial/cylc/furthertopics/suicide-triggers.rst +++ /dev/null @@ -1,676 +0,0 @@ -.. include:: ../../../hyperlinks.rst - :start-line: 1 - - -.. _tut-cylc-suicide-triggers: - -Suicide Triggers -================ - -Suicide triggers allow us to remove a task from the suite's graph whilst the -suite is running. - -The main use of suicide triggers is for handling failures in the workflow. - - -Stalled Suites --------------- - -Imagine a bakery which has a workflow that involves making cake. - -.. minicylc:: - :snippet: - :theme: none - - make_cake_mixture => bake_cake => sell_cake - -There is a 50% chance that the cake will turn out fine, and a 50% chance that -it will get burnt. In the case that we burn the cake the workflow gets stuck. - -.. digraph:: Example - :align: center - - make_cake_mixture [style="filled" color="#ada5a5"] - bake_cake [style="filled" color="#ff0000" fontcolor="white"] - sell_cake [color="#88c6ff"] - - make_cake_mixture -> bake_cake -> sell_cake - -In this event the ``sell_cake`` task will be unable to run as it depends on -``bake_cake``. We would say that this suite has :term:`stalled `. -When Cylc detects that a suite has stalled it sends you an email to let you -know that the suite has got stuck and requires human intervention to proceed. - - -Handling Failures ------------------ - -In order to prevent the suite from entering a stalled state we need to handle -the failure of the ``bake_cake`` task. - -At the bakery if they burn a cake they eat it and make another. - -The following diagram outlines this workflow with its two possible pathways, -``:succeed`` in the event that ``bake_cake`` is successful and ``:fail`` -otherwise. - -.. digraph:: Example - :align: center - - make_cake_mixture - bake_cake - - subgraph cluster_1 { - label = ":succeed" - labelloc = "b" - color = "green" - fontcolor = "green" - style = "dashed" - sell_cake - } - - subgraph cluster_2 { - label = ":fail" - labelloc = "b" - color = "red" - fontcolor = "red" - style = "dashed" - eat_cake - } - - make_cake_mixture -> bake_cake - bake_cake -> sell_cake - bake_cake -> eat_cake - -We can add this logic to our workflow using the ``fail`` :term:`qualifier`. - -.. code-block:: cylc-graph - - bake_cake => sell_cake - bake_cake:fail => eat_cake - -.. admonition:: Reminder - :class: hint - - If you don't specify a qualifier Cylc assumes you mean ``:succeed`` so the - following two lines are equivalent: - - .. code-block:: cylc-graph - - foo => bar - foo:succeed => bar - - -Why Do We Need To Remove Tasks From The Graph? ----------------------------------------------- - -Create a new suite called ``suicide-triggers``:: - - mkdir -p ~/cylc-run/suicide-triggers - cd ~/cylc-run/suicide-triggers - -Paste the following code into the ``flow.cylc`` file: - -.. code-block:: cylc - - [scheduling] - cycling mode = integer - initial cycle point = 1 - [[dependencies]] - [[[P1]]] - graph = """ - make_cake_mixture => bake_cake => sell_cake - bake_cake:fail => eat_cake - """ - [runtime] - [[root]] - script = sleep 2 - [[bake_cake]] - # Random outcome 50% chance of success 50% chance of failure. - script = sleep 2; if (( $RANDOM % 2 )); then true; else false; fi - -Open the ``cylc gui`` and run the suite:: - - cylc gui suicide-triggers & - cylc run suicide-triggers - -The suite will run for three cycles then get stuck. You should see something -similar to the diagram below. As the ``bake_cake`` task fails randomly what -you see might differ slightly. You may receive a "suite stalled" email. - -.. digraph:: Example - :align: center - - size = "7,5" - - subgraph cluster_1 { - label = "1" - style = "dashed" - "make_cake_mixture.1" [ - label="make_cake_mixture\n1", - style="filled", - color="#ada5a5"] - "bake_cake.1" [ - label="bake_cake\n1", - style="filled", - color="#ada5a5"] - "sell_cake.1" [ - label="sell_cake\n1", - style="filled", - color="#ada5a5"] - "eat_cake.1" [ - label="eat_cake\1", - color="#88c6ff"] - } - - subgraph cluster_2 { - label = "2" - style = "dashed" - "make_cake_mixture.2" [ - label="make_cake_mixture\n2", - style="filled", - color="#ada5a5"] - "bake_cake.2" [ - label="bake_cake\n2", - style="filled", - color="#ff0000", - fontcolor="white"] - "sell_cake.2" [ - label="sell_cake\2", - color="#88c6ff"] - "eat_cake.2" [ - label="eat_cake\n2", - color="#888888", - fontcolor="#888888"] - } - - subgraph cluster_3 { - label = "3" - style = "dashed" - "make_cake_mixture.3" [ - label="make_cake_mixture\n3", - style="filled", - color="#ada5a5"] - "bake_cake.3" [ - label="bake_cake\n3", - style="filled", - color="#ff0000", - fontcolor="white"] - "sell_cake.3" [ - label="sell_cake\n3", - color="#888888", - fontcolor="#888888"] - "eat_cake.3" [ - label="eat_cake\3", - color="#888888", - fontcolor="#888888"] - } - - "make_cake_mixture.1" -> "bake_cake.1" -> "sell_cake.1" - "bake_cake.1" -> "eat_cake.1" - - "make_cake_mixture.2" -> "bake_cake.2" -> "sell_cake.2" - "bake_cake.2" -> "eat_cake.2" - - "make_cake_mixture.3" -> "bake_cake.3" -> "sell_cake.3" - "bake_cake.3" -> "eat_cake.3" - -The reason the suite stalls is that, by default, Cylc will run a maximum of -three cycles concurrently. As each cycle has at least one task which hasn't -either succeeded or failed Cylc cannot move onto the next cycle. - -.. tip:: - - For more information search ``max active cycle points`` in the - `Cylc User Guide`_. - -You will also notice that some of the tasks (e.g. ``eat_cake`` in cycle ``2`` -in the above example) are drawn in a faded gray. This is because these tasks -have not yet been run in earlier cycles and as such cannot run. - -.. TODO - Spawn On Demand! - - -Removing Tasks From The Graph ------------------------------ - -In order to get around these problems and prevent the suite from stalling we -must remove the tasks that are no longer needed. We do this using suicide -triggers. - -A suicide trigger is written like a normal dependency but with an exclamation -mark in-front of the task on the right-hand-side of the dependency meaning -*"remove the following task from the graph at the current cycle point."* - -For example the following :term:`graph string` would remove the task ``bar`` -from the graph if the task ``foo`` were to succeed. - -.. code-block:: cylc-graph - - foo => ! bar - -There are three cases where we would need to remove a task in the cake-making -example: - -#. If the ``bake_cake`` task succeeds we don't need the ``eat_cake`` task so - should remove it. - - .. code-block:: cylc-graph - - bake_cake => ! eat_cake - -#. If the ``bake_cake`` task fails we don't need the ``sell_cake`` task so - should remove it. - - .. code-block:: cylc-graph - - bake_cake:fail => ! sell_cake - -#. If the ``bake_cake`` task fails then we will need to remove it else the - suite will stall. We can do this after the ``eat_cake`` task has succeeded. - - .. code-block:: cylc-graph - - eat_cake => ! bake_cake - -Add the following three lines to the suite's graph: - -.. code-block:: cylc-graph - - bake_cake => ! eat_cake - bake_cake:fail => ! sell_cake - eat_cake => ! bake_cake - -We can view suicide triggers in ``cylc graph`` by un-selecting the -:guilabel:`Ignore Suicide Triggers` button in the toolbar. Suicide triggers -will then appear as dashed lines with circular endings. You should see -something like this: - -.. digraph:: Example - :align: center - - make_cake_mixture -> bake_cake - bake_cake -> sell_cake [style="dashed" arrowhead="dot"] - bake_cake -> eat_cake [style="dashed" arrowhead="dot"] - eat_cake -> bake_cake [style="dashed" arrowhead="dot"] - - -Downstream Dependencies ------------------------ - -If we wanted to make the cycles run in order we might write an -:term:`inter-cycle dependency` like this: - -.. code-block:: cylc-graph - - sell_cake[-P1] => make_cake_mixture - -In order to handle the event that the ``sell_cake`` task has been removed from -the graph by a suicide trigger we can write our dependency with an or -symbol ``|`` like so: - -.. code-block:: cylc-graph - - eat_cake[-P1] | sell_cake[-P1] => make_cake_mixture - -Now the ``make_cake_mixture`` task from the next cycle will run after whichever -of the ``sell_cake`` or ``eat_cake`` tasks is run. - -.. digraph:: Example - :align: center - - subgraph cluster_1 { - style="dashed" - label="1" - "make_cake_mixture.1" [label="make_cake_mixture\n1"] - "bake_cake.1" [label="bake_cake\n1"] - "make_cake_mixture.1" -> "bake_cake.1" - "bake_cake.1" -> "sell_cake.1" [style="dashed" arrowhead="dot"] - "bake_cake.1" -> "eat_cake.1" [style="dashed" arrowhead="dot"] - "eat_cake.1" -> "bake_cake.1" [style="dashed" arrowhead="dot"] - subgraph cluster_a { - label = ":fail" - fontcolor = "red" - color = "red" - style = "dashed" - "eat_cake.1" [label="eat_cake\n1" color="red" fontcolor="red"] - } - subgraph cluster_b { - label = ":success" - fontcolor = "green" - color = "green" - style = "dashed" - "sell_cake.1" [label="sell_cake\n1" color="green" fontcolor="green"] - } - } - - subgraph cluster_2 { - style="dashed" - label="2" - "make_cake_mixture.2" [label="make_cake_mixture\n2"] - "bake_cake.2" [label="bake_cake\n2"] - "make_cake_mixture.2" -> "bake_cake.2" - "bake_cake.2" -> "sell_cake.2" [style="dashed" arrowhead="dot"] - "bake_cake.2" -> "eat_cake.2" [style="dashed" arrowhead="dot"] - "eat_cake.2" -> "bake_cake.2" [style="dashed" arrowhead="dot"] - subgraph cluster_c { - label = ":fail" - fontcolor = "red" - color = "red" - style = "dashed" - "eat_cake.2" [label="eat_cake\n2" color="red" fontcolor="red"] - } - subgraph cluster_d { - label = ":success" - fontcolor = "green" - color = "green" - style = "dashed" - "sell_cake.2" [label="sell_cake\n2" color="green" fontcolor="green"] - } - } - - "eat_cake.1" -> "make_cake_mixture.2" [arrowhead="onormal"] - "sell_cake.1" -> "make_cake_mixture.2" [arrowhead="onormal"] - -Add the following :term:`graph string` to your suite. - -.. code-block:: cylc-graph - - eat_cake[-P1] | sell_cake[-P1] => make_cake_mixture - -Open the ``cylc gui`` and run the suite. You should see that if the -``bake_cake`` task fails both it and the ``sell_cake`` task disappear and -are replaced by the ``eat_cake`` task. - - -Comparing "Regular" and "Suicide" Triggers ------------------------------------------- - -In Cylc "regular" and "suicide" triggers both work in the same way. For example -the following graph lines implicitly combine using an ``&`` operator: - -.. highlight:: cylc-graph - -.. list-table:: - :class: grid-table - - * - :: - - foo => pub - bar => pub - - :: - - foo & bar => pub - -Suicide triggers combine in the same way: - -.. list-table:: - :class: grid-table - - * - :: - - foo => !pub - bar => !pub - - :: - - foo & bar => !pub - -.. highlight:: python - -This means that suicide triggers are treated as "invisible tasks" rather than -as "events". Suicide triggers can have pre-requisites just like a normal task. - - -Variations ----------- - -The following sections outline examples of how to use suicide triggers. - -Recovery Task -^^^^^^^^^^^^^ - -A common use case where a ``recover`` task is used to handle a task failure. - -.. digraph:: Example - :align: center - - subgraph cluster_1 { - label = ":fail" - color = "red" - fontcolor = "red" - style = "dashed" - recover - } - - foo -> bar - bar -> recover - recover -> baz [arrowhead="onormal"] - bar -> baz [arrowhead="onormal"] - -.. code-block:: cylc - - [scheduling] - [[dependencies]] - graph = """ - # Regular graph. - foo => bar - - # The fail case. - bar:fail => recover - - # Remove the "recover" task in the success case. - bar => ! recover - - # Remove the "bar" task in the fail case. - recover => ! bar - - # Downstream dependencies. - bar | recover => baz - """ - [runtime] - [[root]] - script = sleep 1 - [[bar]] - script = false - -Branched Workflow -^^^^^^^^^^^^^^^^^ - -A workflow where sub-graphs of tasks are to be run in the success and or fail -cases. - -.. digraph:: Example - :align: center - - foo -> bar - bar -> tar -> par - bar -> jar -> par - bar -> baz -> jaz - - subgraph cluster_1 { - label = ":success" - fontcolor = "green" - color = "green" - style = "dashed" - tar - jar - par - } - - subgraph cluster_2 { - label = ":fail" - fontcolor = "red" - color = "red" - style = "dashed" - baz - jaz - } - - tar -> pub [arrowhead="onormal"] - jaz -> pub [arrowhead="onormal"] - -.. code-block:: cylc - - [scheduling] - [[dependencies]] - graph = """ - # Regular graph. - foo => bar - - # Success case. - bar => tar & jar - - # Fail case. - bar:fail => baz => jaz - - # Remove tasks from the fail branch in the success case. - bar => ! baz & ! jaz - - # Remove tasks from the success branch in the fail case. - bar:fail => ! tar & ! jar & ! par - - # Remove the bar task in the fail case. - baz => ! bar - - # Downstream dependencies. - tar | jaz => pub - """ - [runtime] - [[root]] - script = sleep 1 - [[bar]] - script = true - -Triggering Based On Other States -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -In these examples we have been using suicide triggers to handle task failure. -The suicide trigger mechanism works with other qualifiers as well for example: - -.. code-block:: cylc-graph - - foo:start => ! bar - -Suicide triggers can also be used with custom outputs. In the following example -the task ``showdown`` produces one of three possible custom outputs, ``good``, -``bad`` or ``ugly``. - -.. TODO - link to custom task outputs / write an advanced tutorial for them. - -.. digraph:: Example - :align: center - - subgraph cluster_1 { - label = ":good" - color = "green" - fontcolor = "green" - style = "dashed" - good - } - subgraph cluster_2 { - label = ":bad" - color = "red" - fontcolor = "red" - style = "dashed" - bad - } - subgraph cluster_3 { - label = ":ugly" - color = "purple" - fontcolor = "purple" - style = "dashed" - ugly - } - showdown -> good - showdown -> bad - showdown -> ugly - good -> fin [arrowhead="onormal"] - bad -> fin [arrowhead="onormal"] - ugly -> fin [arrowhead="onormal"] - -.. code-block:: cylc - - [scheduling] - [[dependencies]] - graph = """ - # The "regular" dependencies - showdown:good => good - showdown:bad => bad - showdown:ugly => ugly - good | bad | ugly => fin - - # The "suicide" dependencies for each case - showdown:good | showdown:bad => ! ugly - showdown:bad | showdown:ugly => ! good - showdown:ugly | showdown:good => ! bad - """ - [runtime] - [[root]] - script = sleep 1 - [[showdown]] - # Randomly return one of the three custom outputs. - script = """ - SEED=$RANDOM - if ! (( $SEED % 3 )); then - cylc message 'The-Good' - elif ! (( ( $SEED + 1 ) % 3 )); then - cylc message 'The-Bad' - else - cylc message 'The-Ugly' - fi - """ - [[[outputs]]] - # Register the three custom outputs with cylc. - good = 'The-Good' - bad = 'The-Bad' - ugly = 'The-Ugly' - -Self-Suiciding Task -^^^^^^^^^^^^^^^^^^^ - -An example of a workflow where there are no tasks which are dependent on the -task to suicide trigger. - -.. digraph:: Example - :align: center - - subgraph cluster_1 { - label = "Faulty\nTask" - color = "orange" - fontcolor = "orange" - style = "dashed" - labelloc = "b" - pub - } - - foo -> bar -> baz - bar -> pub - - -It is possible for a task to suicide trigger itself e.g: - -.. code-block:: cylc-graph - - foo:fail => ! foo - -.. warning:: - - This is usually not recommended but in the case where there are no tasks - dependent on the one to remove it is an acceptable approach. - -.. code-block:: cylc - - [scheduling] - [[dependencies]] - graph = """ - foo => bar => baz - bar => pub - - # Remove the "pub" task in the event of failure. - pub:fail => ! pub - """ - [runtime] - [[root]] - script = sleep 1 - [[pub]] - script = false diff --git a/sphinx/tutorial/cylc/img/cylc-graph-cluster.png b/sphinx/tutorial/cylc/img/cylc-graph-cluster.png deleted file mode 100644 index 4fb6df22d7b1d6e8b50c1a616dc830c389ac27b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6668 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 |hJ(uIM5-|zQVRHpy(gCDGP=*<@W z+|XVrC4>-4XfB@{8X9t3r>nE8rMYGGWu<36A3kg6Pv$x}er|o%83;B@zjkI|=5Rs41m}VHk#CT9$2Dwq>Dw zf!B4>Fl<4v6xr1@PLf4Q6h%>%B%jZxCuHPDj!_g{q>d?O3 zuN*tIe$rpA#@arC=Z4M&U3=6UV2m_P)ijk7>bkD$5<*-;D4|3{fDkT=iy-hk$72pJ zwo*N;E6OD=Nz(dJsEVp8wSQ0#@3jvPzkN8g@Al7V!(I@6R3Z5oV^Lgwx?v?*_wM@Y z@!WII{dQ!8_gaAm54Q*qLS>&Ix_-$|ZLr`7^6q*MutLv|2M-=T0qvI+nNo^5j;)LM zgX_AQ&*z=};9<@h@QT2Lhg-z(kHzA%v$L-2&OLLehA#j{VRm*Vk!W&V$180QaMN>s zJb3W%2^nGthr(fw=O - - - - - -_anonymous_0 -cluster_1 - - -cluster_2 - - - -foo.1 - -foo - - -baz.1 - -baz - - -foo.1->baz.1 - - - - -bar.1 - -bar - - -bar.1->baz.1 - - - - -bar.2 - -bar - - -baz.2 - -baz - - -foo.2->baz.2 - - - - -foo.2 - -foo - - -bar.2->baz.2 - - - - - diff --git a/sphinx/tutorial/cylc/img/cylc-graph.png b/sphinx/tutorial/cylc/img/cylc-graph.png deleted file mode 100644 index 65e1ffa957921a8c5e7aacf40c9af66d674249ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49286 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�GKJE4 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 diff --git a/sphinx/tutorial/cylc/img/gcylc-play.png b/sphinx/tutorial/cylc/img/gcylc-play.png deleted file mode 100644 index 608436508b2069d64a92c594808c1a27e5411847..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1939 zcmV;E2WP)Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^Rc3Iz}&C50)9LI3~P!h!XB+= zM>F@{x$~QIe&=`Y#fuj&LfcwztPR2Tm;j(Q3fls78|Urv=p}ph*>7+8173dl$8D4W zKr}jZE1bI>Fty2Z>Fy&0|0NoKQcETVRPfJ!C&Qa z`MGn?9q&K>g~$4{*$&_LIp?i_LX#$1d%lRsu!Tj|`&Vy{PtFLL00IKd5E%o`7(Zvd7>)V&fWL(!p#H|f(&-3okrc^4ml!va5my`xl zfo0o(>H&3^jj@@`c((zN)V*REg7 zPv%db{t6=d(MvC;-So*PPGgkcz?qa&`HKKGsT zxm@n07k_x*;Qfz2@`&TOuH$U3G{A*)#~SgipWw=7Eh*j@_VxH#$K(wBy&1um<)O+< z_0NC*Xh)}K8X_wV?lZ<}^o(0V9asn2vZYK&DW#O0vwZ%R@B0@n48Hf?yMseRr%pZj z`3E1gENg=jaG9=XAdLF|BI2#JlMn1cT;!@{&kL&YAOAKMYr2&$Qw=8q08n+0Zbo(J zC3v+`w<(MOIODdRluMS7QWEK6sc`7fq5l5kU;Wx?X&Mhdd~EdyjpMHoJX8~SxFqEo zP$mK*0-&Y`T=yJ)^QRA^Sg*KfaQ4uF9>+HOkLO<2c?#y+R0p*pLVpvtpe*2wKO`08ykqwdWI@)xUJC z^vI3L{kyVmQUZYByatid#2M3?iqpZn!{bjpw08sYZq;qWr~z%+c0x+il!jq&&Qr-$ zKA#^L7&v<5=)iXe3?bq;)>?0tqY)6<5-m4`CAFTT34k*knWz9kPp6Zgs&sccJzZ{N zn=sPDW98m1Css6kZL)brTLWm*FdH~pa?VpJry2xWb~WgINROb6UR}t8f;16V!gmZWc*_Wp;~IDmXQYmAVQaKmuJJ$Y`Ckt zGdETF-Csxc>`Z&9gq1J=;Oa+}I8q;sy1m7 z&Yb!BGiRQO;{24WlUP>vnheG72Nc zIcIFM)Dg$=FJJ$a(khOkD2m|Twc2vsfxZsQU`nZsBRo4*nGMH_b5q4w6L7{fX(Grt z<|lhSH)$%R7gW-EfnMdUaTLZ;2>%Dh7(4vw9T`WeTFr%^5j}^wDYI5dMT!j041+Vq zIAe@+&Y2K`@upUcMSI6^ktFZSy0zsU(xkOsI1ttJ2m&H9#uzfj86x7s7Q_}5EdT4~ zroH;Q>pB!YLk?`KH - - - - - - - - - - - - image/svg+xml - - - - - - - - 1985-04-12 - 23:20:30Z - T - Date Components - Time Components - - - - - - - - - - - Separator - Year - Month - Day - Hour - Minute - Second - Time Zone - - - diff --git a/sphinx/tutorial/cylc/index.rst b/sphinx/tutorial/cylc/index.rst deleted file mode 100644 index c2d34aba8c..0000000000 --- a/sphinx/tutorial/cylc/index.rst +++ /dev/null @@ -1,21 +0,0 @@ -.. _Cylc Tutorial: - -Cylc Tutorial -============= - -Cylc is a workflow engine for running suites of inter-dependent jobs. - -.. image:: ../../img/cylc-logo.png - :width: 250px - :align: center - -This section will cover the Cylc framework and writing basic Cylc suites. - -.. toctree:: - :name: cylc-tutorial - :maxdepth: 2 - - introduction - scheduling/index - runtime/index - furthertopics/index diff --git a/sphinx/tutorial/cylc/introduction.rst b/sphinx/tutorial/cylc/introduction.rst deleted file mode 100644 index ca9ecc678a..0000000000 --- a/sphinx/tutorial/cylc/introduction.rst +++ /dev/null @@ -1,94 +0,0 @@ -.. _cylc-introduction: - -Introduction -============ - - -What Is A Workflow? -------------------- - -.. epigraph:: - - A workflow consists of an orchestrated and repeatable pattern of business - activity enabled by the systematic organization of resources into processes - that transform materials, provide services, or process information. - - -- Wikipedia - -.. ifnotslides:: - - In research, business and other fields we may have processes that we repeat - in the course of our work. At its simplest a workflow is a set of steps that - must be followed in a particular order to achieve some end goal. - - We can represent each "step" in a workflow as a oval and the order with - arrows. - -.. nextslide:: - -.. digraph:: bakery - :align: center - - "purchase ingredients" -> "make dough" -> "bake bread" -> "sell bread" - "bake bread" -> "clean oven" - "pre-heat oven" -> "bake bread" - - -What Is Cylc? -------------- - -.. ifnotslides:: - - Cylc (pronounced silk) is a workflow engine, a system that automatically - executes tasks according to their schedules and dependencies. - - In a Cylc workflow each step is a computational task, a script to execute. - Cylc runs each task as soon as it is appropriate to do so. - -.. minicylc:: - :align: center - :theme: demo - - a => b => c - b => d => f - e => f - -.. nextslide:: - -Cylc can automatically: - -- Submit tasks across computer systems and resource managers. -- Recover from failures. -- Repeat workflows. - -.. ifnotslides:: - - Cylc was originally developed at NIWA (The National Institute of Water and - Atmospheric Research - New Zealand) for running their weather forecasting - workflows. Cylc is now developed by an international partnership including - members from NIWA and the Met Office (UK). Though initially developed for - meteorological purposes Cylc is a general purpose tool as applicable in - business as in scientific research. - -.. nextslide:: - -.. ifslides:: - - * Originally developed at NIWA (New Zealand) - * Now developed by an international partnership including the - Met Office (UK). - * General purpose tool as applicable in business as in - scientific research. - -.. nextslide:: - -Cylc provides a variety of command line and GUI tools for visualising and -interacting with workflows. - -.. image:: img/cylc-gui.png - -.. nextslide:: - -.. ifslides:: - - :ref:`tutorial-cylc-graphing` diff --git a/sphinx/tutorial/cylc/runtime/configuration-consolidation/families.rst b/sphinx/tutorial/cylc/runtime/configuration-consolidation/families.rst deleted file mode 100644 index 48b8729342..0000000000 --- a/sphinx/tutorial/cylc/runtime/configuration-consolidation/families.rst +++ /dev/null @@ -1,333 +0,0 @@ -.. include:: ../../../../hyperlinks.rst - :start-line: 1 - - -.. _tutorial-cylc-families: - -Families -======== - -:term:`Families ` provide a way of grouping tasks together so they can -be treated as one. - - -Runtime -------- - -.. ifnotslides:: - - :term:`Families ` are groups of tasks which share a common - configuration. In the present example the common configuration is: - - .. code-block:: cylc - - script = get-observations - [[[environment]]] - API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - - We define a family as a new task consisting of the common configuration. By - convention families are named in upper case: - -.. code-block:: cylc - - [[GET_OBSERVATIONS]] - script = get-observations - [[[environment]]] - API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - -.. ifnotslides:: - - We "add" tasks to a family using the ``inherit`` setting: - -.. code-block:: cylc - - [[get_observations_heathrow]] - inherit = GET_OBSERVATIONS - [[[environment]]] - SITE_ID = 3772 - -.. ifnotslides:: - - When we add a task to a family in this way it :term:`inherits ` the configuration from the family, i.e. the above example is - equivalent to: - -.. code-block:: cylc - - [[get_observations_heathrow]] - script = get-observations - [[[environment]]] - API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - SITE_ID = 3772 - -.. nextslide:: - -.. ifnotslides:: - - It is possible to override inherited configuration within the task. For - example if we wanted the ``get_observations_heathrow`` task to use a - different API key we could write: - -.. code-block:: cylc - :emphasize-lines: 4 - - [[get_observations_heathrow]] - inherit = GET_OBSERVATIONS - [[[environment]]] - API_KEY = special-api-key - SITE_ID = 3772 - -.. nextslide:: - -.. ifnotslides:: - - Using families the ``get_observations`` tasks could be written like so: - -.. code-block:: cylc - - [runtime] - [[GET_OBSERVATIONS]] - script = get-observations - [[[environment]]] - API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - - [[get_observations_heathrow]] - inherit = GET_OBSERVATIONS - [[[environment]]] - SITE_ID = 3772 - [[get_observations_camborne]] - inherit = GET_OBSERVATIONS - [[[environment]]] - SITE_ID = 3808 - [[get_observations_shetland]] - inherit = GET_OBSERVATIONS - [[[environment]]] - SITE_ID = 3005 - [[get_observations_belmullet]] - inherit = GET_OBSERVATIONS - [[[environment]]] - SITE_ID = 3976 - - -Graphing --------- - -.. ifnotslides:: - - :term:`Families ` can be used in the suite's :term:`graph`, e.g: - -.. code-block:: cylc-graph - - GET_OBSERVATIONS:succeed-all => consolidate_observations - -.. ifnotslides:: - - The ``:succeed-all`` is a special :term:`qualifier` which in this example - means that the ``consolidate_observations`` task will run once *all* of the - members of the ``GET_OBSERVATIONS`` family have succeeded. This is - equivalent to: - -.. code-block:: cylc-graph - - get_observations_heathrow => consolidate_observations - get_observations_camborne => consolidate_observations - get_observations_shetland => consolidate_observations - get_observations_belmullet => consolidate_observations - -.. ifnotslides:: - - The ``GET_OBSERVATIONS:succeed-all`` part is referred to as a - :term:`family trigger`. Family triggers use special qualifiers which are - non-optional. The most commonly used ones are: - - ``succeed-all`` - Run if all of the members of the family have succeeded. - ``succeed-any`` - Run as soon as any one family member has succeeded. - ``finish-all`` - Run as soon as all of the family members have completed (i.e. have each - either succeeded or failed). - - For more information on family triggers see the `Cylc User Guide`_. - -.. ifslides:: - - * ``succeed-all`` - * ``succeed-any`` - * ``finish-all`` - - -The ``root`` Family -------------------- - -.. ifnotslides:: - - There is a special family called `root` (in lowercase) which is used only - in the runtime to provide configuration which will be inherited by all - tasks. - - In the following example the task ``bar`` will inherit the environment - variable ``FOO`` from the ``[root]`` section: - -.. code-block:: cylc - - [runtime] - [[root]] - [[[environment]]] - FOO = foo - [[bar]] - script = echo $FOO - - -Families and ``cylc graph`` ---------------------------- - -.. ifnotslides:: - - By default, ``cylc graph`` groups together all members of a family - in the :term:`graph`. To un-group a family right click on it and select - :menuselection:`UnGroup`. - - For instance if the tasks ``bar`` and ``baz`` both - inherited from ``BAR`` ``cylc graph`` would produce: - -.. digraph:: Example - :align: center - - subgraph cluster_1 { - label = "Grouped" - "foo.1" [label="foo"] - "BAR.1" [label="BAR", shape="doubleoctagon"] - } - - subgraph cluster_2 { - label = "Un-Grouped" - "foo.2" [label="foo"] - "bar.2" [label="bar"] - "baz.2" [label="baz"] - } - - "foo.1" -> "BAR.1" - "foo.2" -> "bar.2" - "foo.2" -> "baz.2" - -.. nextslide:: - -.. ifslides:: - - .. rubric:: In this practical we will consolidate the configuration of the - :ref:`weather-forecasting suite ` - from the previous section. - - Next section: :ref:`Jinja2 ` - - -.. _cylc-tutorial-families-practical: - -.. practical:: - - .. rubric:: In this practical we will consolidate the configuration of the - :ref:`weather-forecasting suite ` - from the previous section. - - 1. **Create A New Suite.** - - To make a new copy of the forecasting suite run the following commands: - - .. code-block:: bash - - rose tutorial consolidation-tutorial - cd ~/cylc-run/consolidation-tutorial - - 2. **Move Site-Wide Settings Into The** ``root`` **Family.** - - The following two environment variables are used by multiple tasks: - - .. code-block:: none - - RESOLUTION = 0.2 - DOMAIN = -12,48,5,61 # Do not change! - - Rather than manually adding them to each task individually we could put - them in the ``root`` family, making them accessible to all tasks. - - Add a ``root`` section containing these two environment variables. - Remove the variables from any other task's ``environment`` sections: - - .. code-block:: diff - - [runtime] - + [[root]] - + [[[environment]]] - + # The dimensions of each grid cell in degrees. - + RESOLUTION = 0.2 - + # The area to generate forecasts for (lng1, lat1, lng2, lat2). - + DOMAIN = -12,48,5,61 # Do not change! - - .. code-block:: diff - - [[consolidate_observations]] - script = consolidate-observations - - [[[environment]]] - - # The dimensions of each grid cell in degrees. - - RESOLUTION = 0.2 - - # The area to generate forecasts for (lng1, lat1, lng2, lat2). - - DOMAIN = -12,48,5,61 # Do not change! - - [[get_rainfall]] - script = get-rainfall - [[[environment]]] - # The key required to get weather data from the DataPoint service. - # To use archived data comment this line out. - API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - - # The dimensions of each grid cell in degrees. - - RESOLUTION = 0.2 - - # The area to generate forecasts for (lng1, lat1, lng2, lat2). - - DOMAIN = -12,48,5,61 # Do not change! - - [[forecast]] - script = forecast 60 5 # Generate 5 forecasts at 60 minute intervals. - [[[environment]]] - - # The dimensions of each grid cell in degrees. - - RESOLUTION = 0.2 - - # The area to generate forecasts for (lng1, lat1, lng2, lat2) - - DOMAIN = -12,48,5,61 # Do not change! - # The path to the files containing wind data (the {variables} will - # get substituted in the forecast script). - WIND_FILE_TEMPLATE = $CYLC_SUITE_WORK_DIR/{cycle}/consolidate_observations/wind_{xy}.csv - # List of cycle points to process wind data from. - WIND_CYCLES = 0, -3, -6 - - # The path to the rainfall file. - RAINFALL_FILE = $CYLC_SUITE_WORK_DIR/$CYLC_TASK_CYCLE_POINT/get_rainfall/rainfall.csv - # Create the html map file in the task's log directory. - MAP_FILE = "${CYLC_TASK_LOG_ROOT}-map.html" - # The path to the template file used to generate the html map. - MAP_TEMPLATE = "$CYLC_SUITE_RUN_DIR/lib/template/map.html" - - [[post_process_exeter]] - # Generate a forecast for Exeter 60 minutes into the future. - script = post-process exeter 60 - - [[[environment]]] - - # The dimensions of each grid cell in degrees. - - RESOLUTION = 0.2 - - # The area to generate forecasts for (lng1, lat1, lng2, lat2). - - DOMAIN = -12,48,5,61 # Do not change! - - To ensure that the environment variables are being inherited correctly - by the tasks, inspect the ``[runtime]`` section using ``cylc get-config`` - by running the following command: - - .. code-block:: bash - - cylc get-config . --sparse -i "[runtime]" - - You should see the environment variables from the ``[root]`` section - in the ``[environment]`` section for all tasks. - - .. tip:: - - You may find it easier to open the output of this command in a text - editor, e.g:: - - cylc get-config . --sparse -i "[runtime]" | gvim - diff --git a/sphinx/tutorial/cylc/runtime/configuration-consolidation/index.rst b/sphinx/tutorial/cylc/runtime/configuration-consolidation/index.rst deleted file mode 100644 index abcf54b5b2..0000000000 --- a/sphinx/tutorial/cylc/runtime/configuration-consolidation/index.rst +++ /dev/null @@ -1,191 +0,0 @@ -.. include:: ../../../../hyperlinks.rst - :start-line: 1 - - -.. _tutorial-cylc-consolidating-configuration: - -Consolidating Configuration -=========================== - -.. ifnotslides:: - - In the last section we wrote out the following code in the ``flow.cylc`` file: - -.. slide:: Weather Forecasting Suite - :level: 2 - :inline-contents: True - - .. code-block:: cylc - - [runtime] - [[get_observations_heathrow]] - script = get-observations - [[[environment]]] - SITE_ID = 3772 - API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - [[get_observations_camborne]] - script = get-observations - [[[environment]]] - SITE_ID = 3808 - API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - [[get_observations_shetland]] - script = get-observations - [[[environment]]] - SITE_ID = 3005 - API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - [[get_observations_belmullet]] - script = get-observations - [[[environment]]] - SITE_ID = 3976 - API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - -.. ifnotslides:: - - In this code the ``script`` item and the ``API_KEY`` environment variable have - been repeated for each task. This is bad practice as it makes the - configuration lengthy and making changes can become difficult. - - Likewise the graphing relating to the ``get_observations`` tasks is highly - repetitive: - -.. ifslides:: - - .. slide:: Weather Forecasting Suite - :level: 2 - - Repetition - - * ``script`` - * ``API_KEY`` - -.. slide:: Weather Forecasting Suite - :level: 2 - :inline-contents: True - - .. code-block:: cylc - - [scheduling] - [[dependencies]] - [[[T00/PT3H]]] - graph = """ - get_observations_belmullet => consolidate_observations - get_observations_camborne => consolidate_observations - get_observations_heathrow => consolidate_observations - get_observations_shetland => consolidate_observations - """ - -.. nextslide:: - -Cylc offers three ways of consolidating configurations to help improve the -structure of a suite and avoid duplication. - -.. toctree:: - :maxdepth: 1 - - families - jinja2 - parameters - - -The ``cylc get-config`` Command -------------------------------- - -.. ifnotslides:: - - The ``cylc get-config`` command reads in then prints out the ``flow.cylc`` file - to the terminal. - - Throughout this section we will be introducing methods for consolidating - the ``flow.cylc`` file, the ``cylc get-config`` command can be used to - "expand" the ``flow.cylc`` file back to its full form. - - .. note:: - - The main use of ``cylc get-config`` is inspecting the - ``[runtime]`` section of a suite. The ``cylc get-config`` command does not - expand :term:`parameterisations ` and - :term:`families ` in the suite's :term:`graph`. To inspect the - graphing use the ``cylc graph`` command. - - Call ``cylc get-config`` with the path of the suite (``.`` if you are already - in the :term:`suite directory`) and the ``--sparse`` option which hides - default values. - -.. code-block:: sub - - cylc get-config --sparse - -.. ifnotslides:: - - To view the configuration of a particular section or setting refer to it by - name using the ``-i`` option (see :ref:`Cylc file format` for details), e.g: - -.. code-block:: sub - - # Print the contents of the [scheduling] section. - cylc get-config --sparse -i '[scheduling]' - # Print the contents of the get_observations_heathrow task. - cylc get-config --sparse -i '[runtime][get_observations_heathrow]' - # Print the value of the script setting in the get_observations_heathrow task - cylc get-config --sparse -i '[runtime][get_observations_heathrow]script' - -.. nextslide:: - -.. ifslides:: - - Note that ``cylc get-config`` doesn't expand families or parameterisations - in the :term:`graph`. Use ``cylc graph`` to visualise these. - - .. TODO - Raise and issue for this, note cylc get-config and cylc view. - - -The Three Approaches --------------------- - -.. ifnotslides:: - - The next three sections cover the three consolidation approaches and how we - could use them to simplify the suite from the previous tutorial. *Work - through them in order!* - -* :ref:`families ` -* :ref:`jinja2 ` -* :ref:`parameters ` - - -.. _cylc-tutorial-consolidation-conclusion: - -Which Approach To Use ---------------------- - -.. ifnotslides:: - - Each approach has its uses. Cylc permits mixing approaches, allowing us to - use what works best for us. As a rule of thumb: - - * :term:`Families ` work best consolidating runtime configuration by - collecting tasks into broad groups, e.g. groups of tasks which run on a - particular machine or groups of tasks belonging to a particular system. - * `Jinja2`_ is good at configuring settings which apply to the entire suite - rather than just a single task, as we can define variables then use them - throughout the suite. - * :term:`Parameterisation ` works best for describing tasks - which are very similar but which have subtly different configurations - (e.g. different arguments or environment variables). - -.. ifslides:: - - As a rule of thumb each method works best for: - - Families - Collecting tasks into broad groups. - Jinja2 - Configuration settings which apply to the entire suite. - Parameterisation - Tasks which are similar. - -.. nextslide:: - -.. ifslides:: - - Next section: :ref:`Rose Tutorial ` diff --git a/sphinx/tutorial/cylc/runtime/configuration-consolidation/jinja2.rst b/sphinx/tutorial/cylc/runtime/configuration-consolidation/jinja2.rst deleted file mode 100644 index 314332c713..0000000000 --- a/sphinx/tutorial/cylc/runtime/configuration-consolidation/jinja2.rst +++ /dev/null @@ -1,236 +0,0 @@ -.. _shebang: https://en.wikipedia.org/wiki/Shebang_(Unix) - - -.. _tutorial-cylc-jinja2: - -Jinja2 -====== - -`Jinja2 `_ -is a templating language often used in web design with some -similarities to python. It can be used to make a suite definition more -dynamic. - - -The Jinja2 Language -------------------- - -In Jinja2 statements are wrapped with ``{%`` characters, i.e: - -.. code-block:: none - - {% ... %} - -Variables are initiated using the ``set`` statement, e.g: - -.. code-block:: css+jinja - - {% set foo = 3 %} - -.. nextslide:: - -Expressions wrapped with ``{{`` characters will be replaced with the value of -the evaluation of the expression, e.g: - -.. code-block:: css+jinja - - There are {{ foo }} methods for consolidating the flow.cylc file - -Would result in:: - - There are 3 methods for consolidating the flow.cylc file - -.. nextslide:: - -Loops are written with ``for`` statements, e.g: - -.. code-block:: css+jinja - - {% for x in range(foo) %} - {{ x }} - {% endfor %} - -Would result in: - -.. code-block:: none - - 0 - 1 - 2 - -.. nextslide:: - -To enable Jinja2 in the ``flow.cylc`` file, add the following `shebang`_ to the -top of the file: - -.. code-block:: cylc - - #!Jinja2 - -For more information see the -`Jinja2 Tutorial `_. - - -Example -------- - -To consolidate the configuration for the ``get_observations`` tasks we could -define a dictionary of station and ID pairs: - -.. code-block:: css+jinja - - {% set stations = {'belmullet': 3976, - 'camborne': 3808, - 'heathrow': 3772, - 'shetland': 3005} %} - -.. nextslide:: - -We could then loop over the stations like so: - -.. code-block:: css+jinja - - {% for station in stations %} - {{ station }} - {% endfor %} - -After processing, this would result in: - -.. code-block:: none - - belmullet - camborne - heathrow - shetland - -.. nextslide:: - -We could also loop over both the stations and corresponding IDs like so: - -.. code-block:: css+jinja - - {% for station, id in stations.items() %} - {{ station }} - {{ id }} - {% endfor %} - -This would result in: - -.. code-block:: none - - belmullet - 3976 - camborne - 3808 - heathrow - 3772 - shetland - 3005 - -.. nextslide:: - -.. ifnotslides:: - - Putting this all together, the ``get_observations`` configuration could be - written as follows: - -.. code-block:: cylc - - #!Jinja2 - - {% set stations = {'belmullet': 3976, - 'camborne': 3808, - 'heathrow': 3772, - 'shetland': 3005} %} - - [scheduling] - [[dependencies]] - [[[T00/PT3H]]] - graph = """ - {% for station in stations %} - get_observations_{{station}} => consolidate_observations - {% endfor %} - """ - -.. nextslide:: - -.. code-block:: cylc - - [runtime] - {% for station, id in stations.items() %} - [[get_observations_{{station}}]] - script = get-observations - [[[environment]]] - SITE_ID = {{ id }} - API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - - {% endfor %} - -.. nextslide:: - -.. ifslides:: - - .. rubric:: This practical continues on from the - :ref:`families practical `. - - Next section: :ref:`tutorial-cylc-parameterisation` - - -.. _cylc-tutorial-jinja2-practical: - -.. practical:: - - .. rubric:: This practical continues on from the - :ref:`families practical `. - - 3. **Use Jinja2 To Avoid Duplication.** - - The ``API_KEY`` environment variable is used by both the - ``get_observations`` and ``get_rainfall`` tasks. Rather than writing it - out multiple times we will use Jinja2 to centralise this configuration. - - At the top of the ``flow.cylc`` file add the Jinja2 shebang line. Then - copy the value of the ``API_KEY`` environment variable and use it to - define an ``API_KEY`` Jinja2 variable: - - .. code-block:: cylc - - #!Jinja2 - - {% set API_KEY = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' %} - - Next replace the key, where it appears in the suite, with - ``{{ API_KEY }}``: - - .. code-block:: diff - - [runtime] - [[get_observations_heathrow]] - script = get-observations - [[[environment]]] - SITE_ID = 3772 - - API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - + API_KEY = {{ API_KEY }} - [[get_observations_camborne]] - script = get-observations - [[[environment]]] - SITE_ID = 3808 - - API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - + API_KEY = {{ API_KEY }} - [[get_observations_shetland]] - script = get-observations - [[[environment]]] - SITE_ID = 3005 - - API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - + API_KEY = {{ API_KEY }} - [[get_observations_belmullet]] - script = get-observations - [[[environment]]] - SITE_ID = 3976 - - API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - + API_KEY = {{ API_KEY }} - [[get_rainfall]] - script = get-rainfall - [[[environment]]] - # The key required to get weather data from the DataPoint service. - # To use archived data comment this line out. - - API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - + API_KEY = {{ API_KEY }} - - Check the result with ``cylc get-config``. The Jinja2 will be processed - so you should not see any difference after making these changes. diff --git a/sphinx/tutorial/cylc/runtime/configuration-consolidation/parameters.rst b/sphinx/tutorial/cylc/runtime/configuration-consolidation/parameters.rst deleted file mode 100644 index 8835051864..0000000000 --- a/sphinx/tutorial/cylc/runtime/configuration-consolidation/parameters.rst +++ /dev/null @@ -1,349 +0,0 @@ -.. include:: ../../../../hyperlinks.rst - :start-line: 1 - -.. _tutorial-cylc-parameterisation: - - -Parameterised Tasks -=================== - -Parameterised tasks (see :term:`parameterisation`) provide a way of implicitly -looping over tasks without the need for Jinja2. - - -Cylc Parameters ---------------- - -.. ifnotslides:: - - Parameters are defined in their own section, e.g: - -.. code-block:: cylc - - [cylc] - [[parameters]] - world = Mercury, Venus, Earth - - -.. ifnotslides:: - - They can then be referenced by writing the name of the parameter in angle - brackets, e.g: - -.. code-block:: cylc - - [scheduling] - [[dependencies]] - graph = start => hello => end - [runtime] - [[hello]] - script = echo 'Hello World!' - -.. nextslide:: - -.. ifnotslides:: - - When the ``flow.cylc`` file is read by Cylc, the parameters will be expanded. - For example the code above is equivalent to: - -.. code-block:: cylc - - [scheduling] - [[dependencies]] - graph = """ - start => hello_Mercury => end - start => hello_Venus => end - start => hello_Earth => end - """ - [runtime] - [[hello_Mercury]] - script = echo 'Hello World!' - [[hello_Venus]] - script = echo 'Hello World!' - [[hello_Earth]] - script = echo 'Hello World!' - -.. nextslide:: - -.. ifnotslides:: - - We can refer to a specific parameter by writing it after an ``=`` sign: - -.. code-block:: cylc - - [runtime] - [[hello]] - script = echo 'Greetings Earth!' - - -Environment Variables ---------------------- - -.. ifnotslides:: - - The name of the parameter is provided to the job as an environment variable - called ``CYLC_TASK_PARAM_`` where ```` is the name of - the parameter (in the present case ``world``): - -.. code-block:: cylc - - [runtime] - [[hello]] - script = echo "Hello ${CYLC_TASK_PARAM_world}!" - - -Parameter Types ---------------- - -Parameters can be either words or integers: - -.. code-block:: cylc - - [cylc] - [[parameters]] - foo = 1..5 - bar = 1..5..2 - baz = pub, qux, bol - -.. nextslide:: - -.. hint:: - - Remember that Cylc automatically inserts an underscore between the task and - the parameter, e.g. the following lines are equivalent: - - .. code-block:: cylc-graph - - task - task_pub - -.. nextslide:: - -.. hint:: - - .. ifnotslides:: - - When using integer parameters, to prevent confusion, Cylc prefixes the - parameter value with the parameter name. For example: - - .. ifslides:: - - Cylc prefixes integer parameters with the parameter name: - - .. code-block:: cylc - - [scheduling] - [[dependencies]] - graph = """ - # task would result in: - task_bar1 - task_bar3 - task_bar5 - - # task would result in: - task_pub - task_qux - task_bol - """ - -.. nextslide:: - -.. ifnotslides:: - - Using parameters the ``get_observations`` configuration could be written like - so: - -.. code-block:: cylc - - [scheduling] - [[dependencies]] - [[[T00/PT3H]]] - graph = """ - get_observations => consolidate_observations - """ - [runtime] - [[get_observations]] - script = get-observations - [[[environment]]] - API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - - [[get_observations]] - [[[environment]]] - SITE_ID = 3976 - [[get_observations]] - [[[environment]]] - SITE_ID = 3808 - [[get_observations]] - [[[environment]]] - SITE_ID = 3772 - [[get_observations]] - [[[environment]]] - SITE_ID = 3005 - -.. nextslide:: - -.. ifnotslides:: - - For more information see the `Cylc User Guide`_. - -.. ifslides:: - - .. rubric:: This practical continues on from the - :ref:`Jinja2 practical `. - - Next section: :ref:`Which approach to use - ` - - -.. _cylc-tutorial-parameters-practical: - -.. practical:: - - .. rubric:: This practical continues on from the - :ref:`Jinja2 practical `. - - 4. **Use Parameterisation To Consolidate The** ``get_observations`` - **Tasks**. - - Next we will parameterise the ``get_observations`` tasks. - - Add a parameter called ``station``: - - .. code-block:: diff - - [cylc] - UTC mode = True - + [[parameters]] - + station = belmullet, camborne, heathrow, shetland - - Remove the four ``get_observations`` tasks and insert the following code - in their place: - - .. code-block:: cylc - - [[get_observations]] - script = get-observations - [[[environment]]] - API_KEY = {{ API_KEY }} - - Using ``cylc get-config`` you should see that Cylc replaces the - ```` with each of the stations in turn, creating a new task for - each: - - .. code-block:: bash - - cylc get-config . --sparse -i "[runtime]" - - The ``get_observations`` tasks are now missing the ``SITE_ID`` - environment variable. Add a new section for each station with a - ``SITE_ID``: - - .. code-block:: cylc - - [[get_observations]] - [[[environment]]] - SITE_ID = 3772 - - .. hint:: - - The relevant IDs are: - - * Belmullet - ``3976`` - * Camborne - ``3808`` - * Heathrow - ``3772`` - * Shetland - ``3005`` - - .. spoiler:: Solution warning - - .. code-block:: cylc - - [[get_observations]] - [[[environment]]] - SITE_ID = 3976 - [[get_observations]] - [[[environment]]] - SITE_ID = 3808 - [[get_observations]] - [[[environment]]] - SITE_ID = 3772 - [[get_observations]] - [[[environment]]] - SITE_ID = 3005 - - Using ``cylc get-config`` you should now see four ``get_observations`` - tasks, each with a ``script``, an ``API_KEY`` and a ``SITE_ID``: - - .. code-block:: bash - - cylc get-config . --sparse -i "[runtime]" - - Finally we can use this parameterisation to simplify the suite's - graphing. Replace the ``get_observations`` lines in the graph with - ``get_observations``: - - .. code-block:: diff - - [[[PT3H]]] - # Repeat every three hours starting at the initial cycle point. - graph = """ - - get_observations_belmullet => consolidate_observations - - get_observations_camborne => consolidate_observations - - get_observations_heathrow => consolidate_observations - - get_observations_shetland => consolidate_observations - + get_observations => consolidate_observations - """ - - .. hint:: - - The ``cylc get-config`` command does not expand parameters or families - in the graph so you must use ``cylc graph`` to inspect changes to the - graphing. - - #. **Use Parameterisation To Consolidate The** ``post_process`` **Tasks**. - - At the moment we only have one ``post_process`` task - (``post_process_exeter``), but suppose we wanted to add a second task for - Edinburgh. - - Create a new parameter called ``site`` and set it to contain ``exeter`` - and ``edinburgh``. Parameterise the ``post_process`` task using this - parameter. - - .. hint:: - - The first argument to the ``post-process`` task is the name of the - site. We can use the ``CYLC_TASK_PARAM_site`` environment variable to - avoid having to write out this section twice. - - .. TODO - use parameter environment templates instead of - CYLC_TASK_PARAM. - - .. spoiler:: Solution warning - - First we must create the ``site`` parameter: - - .. code-block:: diff - - [cylc] - UTC mode = True - [[parameters]] - station = belmullet, camborne, heathrow, shetland - + site = exeter, edinburgh - - Next we parameterise the task in the graph: - - .. code-block:: diff - - - -get_rainfall => forecast => post_process_exeter - +get_rainfall => forecast => post_process - - And also the runtime: - - .. code-block:: diff - - -[[post_process_exeter]] - +[[post_process]] - # Generate a forecast for Exeter 60 minutes in the future. - - script = post-process exeter 60 - + script = post-process $CYLC_TASK_PARAM_site 60 diff --git a/sphinx/tutorial/cylc/runtime/index.rst b/sphinx/tutorial/cylc/runtime/index.rst deleted file mode 100644 index 2bca48173a..0000000000 --- a/sphinx/tutorial/cylc/runtime/index.rst +++ /dev/null @@ -1,18 +0,0 @@ -.. _tutorial-runtime: - -Runtime -======= - -This section covers: - -* Associating tasks with scripts and executables. -* Providing executables with runtime configurations. -* Running Cylc suites. - -.. toctree:: - :name: rug-runtime-toc - :maxdepth: 2 - - introduction - runtime-configuration - configuration-consolidation/index diff --git a/sphinx/tutorial/cylc/runtime/introduction.rst b/sphinx/tutorial/cylc/runtime/introduction.rst deleted file mode 100644 index 650aa1cf8b..0000000000 --- a/sphinx/tutorial/cylc/runtime/introduction.rst +++ /dev/null @@ -1,518 +0,0 @@ -.. include:: ../../../hyperlinks.rst - :start-line: 1 - - -.. _tutorial-cylc-runtime-introduction: - -Introduction -============ - -.. ifnotslides:: - - So far we have been working with the ``[scheduling]`` section. This is where - the workflow is defined in terms of :term:`tasks ` and - :term:`dependencies `. - - In order to make the workflow runnable we must associate tasks with scripts - or binaries to be executed when the task runs. This means working with the - ``[runtime]`` section which determines what runs, as well as where and how - it runs. - -.. ifslides:: - - ``[scheduling]`` - Defines the workflow in terms of :term:`tasks ` and - :term:`dependencies `. - ``[runtime]`` - Defines what runs, where and how it runs. - - -The Task Section ----------------- - -.. ifnotslides:: - - The runtime settings for each task are stored in a sub-section of the - ``[runtime]`` section. E.g. for a task called ``hello_world`` we would write - settings inside the following section: - -.. code-block:: cylc - - [runtime] - [[hello_world]] - - -The ``script`` Setting ----------------------- - -.. ifnotslides:: - - We tell Cylc *what* to execute when a task is run using the ``script`` - setting. - - This setting is interpreted as a bash script. The following example defines a - task called ``hello_world`` which writes ``Hello World!`` to stdout upon - execution. - -.. code-block:: cylc - - [runtime] - [[hello_world]] - script = echo 'Hello World!' - -.. note:: - - If you do not set the ``script`` for a task then nothing will be run. - -We can also call other scripts or executables in this way, e.g: - -.. code-block:: cylc - - [runtime] - [[hello_world]] - script = ~/foo/bar/baz/hello_world - - -``PATH`` and :envvar:`PYTHONPATH` ---------------------------------- - -.. ifnotslides:: - - It is often a good idea to keep our scripts with the Cylc suite rather than - leaving them somewhere else on the system. - - If you create a ``bin/`` sub-directory within the :term:`suite directory` - Cylc will automatically prepend it to the ``PATH`` environment - variable when the task runs. - -.. code-block:: bash - :caption: bin/hello_world - - #!/usr/usr/bin/env bash - echo 'Hello World!' - -.. code-block:: cylc - :caption: flow.cylc - - [runtime] - [[hello_world]] - script = hello_world - -.. nextslide:: - -.. ifnotslides:: - - Similarly the ``lib/python/`` directory gets prepended to the - :envvar:`PYTHONPATH` variable. - -.. code-block:: python - :caption: lib/python/hello.py - - def world(): - print('Hello World!') - -.. code-block:: cylc - :caption: flow.cylc - - [runtime] - [[hello_world]] - script = python -c 'import hello; hello.world()' - - -.. _tutorial-tasks-and-jobs: - -Tasks And Jobs --------------- - -.. ifnotslides:: - - When a :term:`task` is "Run" it creates a :term:`job`. The job is a bash - file containing the script you have told the task to run along with - configuration specifications and a system for trapping errors. It is the - :term:`job` which actually gets executed and not the task itself. This - "job file" is called the :term:`job script`. - - During its life a typical :term:`task` goes through the following states: - - Waiting - :term:`Tasks ` wait for their dependencies to be satisfied before - running. In the meantime they are in the "Waiting" state. - Submitted - When a :term:`task's ` dependencies have been met it is ready for - submission. During this phase the :term:`job script` is created. - The :term:`job` is then submitted to the specified batch system. - There is more about this in the :ref:`next section - `. - Running - A :term:`task` is in the "Running" state as soon as the :term:`job` is - executed. - Succeeded - If the :term:`job` submitted by a :term:`task` has successfully - completed (i.e. there is zero return code) then it is said to have - succeeded. - - These descriptions, and a few more (e.g. failed), are called the - :term:`task states `. - -.. ifslides:: - - When a :term:`task` is "Run" it creates a :term:`job`. - - The life-cycle of a job: - - * Waiting - * Submitted - * Running - * Succeeded / Failed - - -The Cylc GUI ------------- - -.. ifnotslides:: - - To help you to keep track of a running suite Cylc has a graphical user - interface (the Cylc GUI) which can be used for monitoring and - interaction. - - The Cylc GUI looks quite like ``cylc graph`` but the tasks are colour-coded - to represent their state, as in the following diagram. - -.. digraph:: example - :align: center - - Waiting [color="#88c6ff"] - Running [style="filled" color="#00c410"] - Succeeded [style="filled" color="#ada5a5"] - -.. minicylc:: - :align: center - - a => b => c - b => d => f - e => f - -.. nextslide:: - -.. ifnotslides:: - - This is the "graph view". The Cylc GUI has two other views called "tree" and - "dot". - -.. figure:: ../img/cylc-gui-graph.png - :figwidth: 75% - :align: center - - Screenshot of the Cylc GUI in "Graph View" mode. - -.. nextslide:: - -.. figure:: ../img/cylc-gui-tree.png - :figwidth: 75% - :align: center - - Screenshot of the Cylc GUI in "Tree View" mode. - -.. nextslide:: - -.. figure:: ../img/cylc-gui-dot.png - :figwidth: 75% - :align: center - - Screenshot of the Cylc GUI in "Dot View" mode. - - -Where Do All The Files Go? --------------------------- - -.. ifnotslides:: - - The Work Directory - ^^^^^^^^^^^^^^^^^^ - - When a :term:`task` is run Cylc creates a directory for the :term:`job` to - run in. This is called the :term:`work directory`. - - By default the work directory is located in a directory structure - under the relevant :term:`cycle point` and :term:`task` name: - - .. code-block:: sub - - ~/cylc-run//work// - - The Job Log Directory - ^^^^^^^^^^^^^^^^^^^^^ - - When a task is run Cylc generates a :term:`job script` which is stored in the - :term:`job log directory` as the file ``job``. - - When the :term:`job script` is executed the stdout and stderr are redirected - into the ``job.out`` and ``job.err`` files which are also stored in the - :term:`job log directory`. - - The :term:`job log directory` lives in a directory structure under the - :term:`cycle point`, :term:`task` name and :term:`job submission number`: - - .. code-block:: sub - - ~/cylc-run//log/job//// - - The :term:`job submission number` starts at 1 and increments by 1 each time - a task is re-run. - - .. tip:: - - If a task has run and is still visible in the Cylc GUI you can view its - :term:`job log files ` by right-clicking on the task and - selecting "View". - - .. image:: ../img/cylc-gui-view-log.png - :align: center - :scale: 75% - -.. ifslides:: - - The Work Directory - .. code-block:: sub - - ~/cylc-run//work// - The Job Log Directory - .. code-block:: sub - - ~/cylc-run//log/job//// - - .. image:: ../img/cylc-gui-view-log.png - :align: center - :scale: 75% - - -Running A Suite ---------------- - -.. ifnotslides:: - - It is a good idea to check a suite for errors before running it. - Cylc provides a command which automatically checks for any obvious - configuration issues called ``cylc validate``, run via: - -.. code-block:: sub - - cylc validate - -.. ifnotslides:: - - Here ```` is the path to the suite's location within the - filesystem (so if we create a suite in ``~/cylc-run/foo`` we would put - ``~/cylc-run/foo/flow.cylc``). - - Next we can run the suite using the ``cylc run`` command. - -.. code-block:: sub - - cylc run - -.. ifnotslides:: - - The ``name`` is the name of the :term:`suite directory` (i.e. ```` - would be ``foo`` in the above example). - -.. note:: - - In this tutorial we are writing our suites in the ``cylc-run`` directory. - - It is possible to write them elsewhere on the system. If we do so we - must register the suite with Cylc before use. - - We do this using the ``cylc reg`` command which we supply with a name which - will be used to refer to the suite in place of the path i.e: - - .. code-block:: sub - - cylc reg - cylc validate - cylc run - - The ``cylc reg`` command will create a directory for the suite in the - ``cylc-run`` directory meaning that we will have separate - :term:`suite directories ` and - :term:`run directories `. - - -Suite Files ------------ - -.. ifnotslides:: - - Cylc generates files and directories when it runs a suite, namely: - - ``log/`` - Directory containing log files, including: - - ``log/db`` - The database which Cylc uses to record the state of the suite; - ``log/job`` - The directory where the :term:`job log files ` live; - ``log/suite`` - The directory where the :term:`suite log files ` live. - These files are written by Cylc as the suite is run and are useful for - debugging purposes in the event of error. - - ``flow.cylc.processed`` - A copy of the ``flow.cylc`` file made after any `Jinja2`_ has been - processed - we will cover this in the - :ref:`tutorial-cylc-consolidating-configuration` section. - ``share/`` - The :term:`share directory` is a place where :term:`tasks ` can - write files which are intended to be shared within that cycle. - ``work/`` - A directory hierarchy containing task's :term:`work directories - `. - -.. ifslides:: - - * ``log/`` - * ``log/db`` - * ``log/job`` - * ``log/suite`` - * ``flow.cylc.processed`` - * ``share/`` - * ``work/`` - - .. nextslide:: - - .. rubric:: In this practical we will add some scripts to, and run, the - :ref:`weather forecasting suite ` - from the :ref:`scheduling tutorial `. - - Next section: :ref:`tutorial-cylc-runtime-configuration` - - -.. practical:: - - .. rubric:: In this practical we will add some scripts to, and run, the - :ref:`weather forecasting suite ` - from the :ref:`scheduling tutorial `. - - #. **Create A New Suite.** - - The following command will copy some files for us to work with into - a new suite called ``runtime-introduction``: - - .. code-block:: bash - - rose tutorial runtime-introduction - cd ~/cylc-run/runtime-introduction - - In this directory we have the ``flow.cylc`` file from the - :ref:`weather forecasting suite ` - with some runtime configuration added to it. - - There is also a script called ``get-observations`` located in the bin - directory. - - Take a look at the ``[runtime]`` section in the ``flow.cylc`` file. - - #. **Run The Suite.** - - First validate the suite by running: - - .. code-block:: bash - - cylc validate . - - Open the Cylc GUI (in the background) by running the following command: - - .. code-block:: bash - - cylc gui runtime-introduction & - - Finally run the suite by executing: - - .. code-block:: bash - - cylc run runtime-introduction - - The tasks will start to run - you should see them going through the - "Waiting", "Running" and "Succeeded" states. - - When the suite reaches the final cycle point and all tasks have succeeded - it will shutdown automatically and the GUI will go blank. - - .. tip:: - - You can also run a suite from the Cylc GUI by pressing the "play" - button. - - .. image:: ../img/gcylc-play.png - :align: center - - A box will appear. Ensure that "Cold Start" is selected then press - "Start". - - .. image:: ../img/cylc-gui-suite-start.png - :align: center - - #. **Inspect A Job Log.** - - Try opening the file ``job.out`` for one of the - ``get_observations`` jobs in a text editor. The file will be - located within the :term:`job log directory`: - - .. code-block:: sub - - log/job//get_observations_heathrow/01/job.out - - You should see something like this: - - .. code-block:: none - - Suite : runtime-introduction - Task Job : 20000101T0000Z/get_observations_heathrow/01 (try 1) - User@Host: username@hostname - - Guessing Weather Conditions - Writing Out Wind Data - 1970-01-01T00:00:00Z NORMAL - started - 2038-01-19T03:14:08Z NORMAL - succeeded - - * The first three lines are information which Cylc has written to the file - to provide information about the job. - * The last two lines were also written by cylc. They provide timestamps - marking the stages in the job's life. - * The lines in the middle are the stdout of the job itself. - - #. **Inspect A Work Directory.** - - The ``get_rainfall`` task should create a file called ``rainfall`` in its - :term:`work directory`. Try opening this file, recalling that the - format of the relevant path from within the work directory will be: - - .. code-block:: sub - - work//get_rainfall/rainfall - - .. hint:: - - The ``get_rainfall`` task only runs every third cycle. - - #. **Extension: Explore The Cylc GUI** - - * Try re-running the suite. - - * Try changing the current view(s). - - .. tip:: - - You can do this from the "View" menu or from the toolbar: - - .. image:: ../img/cylc-gui-view-selector.png - :align: center - :scale: 75% - - * Try pressing the "Pause" button which is found in the top left-hand - corner of the GUI. - - * Try right-clicking on a task. From the right-click menu you could try: - - * "Trigger (run now)" - * "Reset State" diff --git a/sphinx/tutorial/cylc/runtime/runtime-configuration.rst b/sphinx/tutorial/cylc/runtime/runtime-configuration.rst deleted file mode 100644 index 91f33e4711..0000000000 --- a/sphinx/tutorial/cylc/runtime/runtime-configuration.rst +++ /dev/null @@ -1,501 +0,0 @@ -.. include:: ../../../hyperlinks.rst - :start-line: 1 - -.. _DataPoint: https://www.metoffice.gov.uk/services/data/datapoint - - -.. _tutorial-cylc-runtime-configuration: - -Runtime Configuration -===================== - -In the last section we associated tasks with scripts and ran a simple suite. In -this section we will look at how we can configure these tasks. - - -Environment Variables ---------------------- - -.. ifnotslides:: - - We can specify environment variables in a task's ``[environment]`` section. - These environment variables are then provided to :term:`jobs ` when they - run. - -.. code-block:: cylc - - [runtime] - [[countdown]] - script = seq $START_NUMBER - [[[environment]]] - START_NUMBER = 5 - -.. ifnotslides:: - - Each job is also provided with some standard environment variables e.g: - - ``CYLC_SUITE_RUN_DIR`` - The path to the suite's :term:`run directory` - *(e.g. ~/cylc-run/suite)*. - ``CYLC_TASK_WORK_DIR`` - The path to the associated task's :term:`work directory` - *(e.g. run-directory/work/cycle/task)*. - ``CYLC_TASK_CYCLE_POINT`` - The :term:`cycle point` for the associated task - *(e.g. 20171009T0950)*. - - There are many more environment variables - see the `Cylc User Guide`_ for more - information. - -.. ifslides:: - - * ``CYLC_SUITE_RUN_DIR`` - * ``CYLC_TASK_WORK_DIR`` - * ``CYLC_TASK_CYCLE_POINT`` - - -.. _tutorial-batch-system: - -Job Submission --------------- - -.. ifnotslides:: - - By default Cylc runs :term:`jobs ` on the machine where the suite is - running. We can tell Cylc to run jobs on other machines by setting the - ``[remote]host`` setting to the name of the host, e.g. to run a task on the - host ``computehost`` you might write: - -.. code-block:: cylc - - [runtime] - [[hello_computehost]] - script = echo "Hello Compute Host" - [[[remote]]] - host = computehost - -.. _background processes: https://en.wikipedia.org/wiki/Background_process -.. _job scheduler: https://en.wikipedia.org/wiki/Job_scheduler - -.. nextslide:: - -.. ifnotslides:: - - Cylc also executes jobs as `background processes`_ by default. - When we are running jobs on other compute hosts we will often want to - use a :term:`batch system` (`job scheduler`_) to submit our job. - Cylc supports the following :term:`batch systems `: - -* at -* loadleveler -* lsf -* pbs -* sge -* slurm -* moab - -.. nextslide:: - -.. ifnotslides:: - - :term:`Batch systems ` typically require - :term:`directives ` in some form. :term:`Directives ` - inform the :term:`batch system` of the requirements of a :term:`job`, for - example how much memory a given job requires or how many CPUs the job will - run on. For example: - -.. code-block:: cylc - - [runtime] - [[big_task]] - script = big-executable - - # Submit to the host "big-computer". - [[[remote]]] - host = big-computer - - # Submit the job using the "slurm" batch system. - [[[job]]] - batch system = slurm - - # Inform "slurm" that this job requires 500MB of RAM and 4 CPUs. - [[[directives]]] - --mem = 500 - --ntasks = 4 - - -Timeouts --------- - -.. ifnotslides:: - - We can specify a time limit after which a job will be terminated using the - ``[job]execution time limit`` setting. The value of the setting is an - :term:`ISO8601 duration`. Cylc automatically inserts this into a job's - directives as appropriate. - -.. code-block:: cylc - - [runtime] - [[some_task]] - script = some-executable - [[[job]]] - execution time limit = PT15M # 15 minutes. - - -Retries -------- - -Sometimes jobs fail. This can be caused by two factors: - -* Something going wrong with the job's execution e.g: - - * A bug; - * A system error; - * The job hitting the ``execution time limit``. - -* Something going wrong with the job submission e.g: - - * A network problem; - * The :term:`job host` becoming unavailable or overloaded; - * An issue with the directives. - -.. nextslide:: - -.. ifnotslides:: - - In the event of failure Cylc can automatically re-submit (retry) jobs. We - configure retries using the ``[job]execution retry delays`` and - ``[job]submission retry delays`` settings. These settings are both set to an - :term:`ISO8601 duration`, e.g. setting ``execution retry delays`` to ``PT10M`` - would cause the job to retry every 10 minutes in the event of execution - failure. - - We can limit the number of retries by writing a multiple in front of the - duration, e.g: - -.. code-block:: cylc - - [runtime] - [[some-task]] - script = some-script - [[[job]]] - # In the event of execution failure, retry a maximum - # of three times every 15 minutes. - execution retry delays = 3*PT15M - # In the event of submission failure, retry a maximum - # of two times every ten minutes and then every 30 - # minutes thereafter. - submission retry delays = 2*PT10M, PT30M - - -Start, Stop, Restart --------------------- - -.. ifnotslides:: - - We have seen how to start and stop Cylc suites with ``cylc run`` and - ``cylc stop`` respectively. The ``cylc stop`` command causes Cylc to wait - for all running jobs to finish before it stops the suite. There are two - options which change this behaviour: - - ``cylc stop --kill`` - When the ``--kill`` option is used Cylc will kill all running jobs - before stopping. *Cylc can kill jobs on remote hosts and uses the - appropriate command when a* :term:`batch system` *is used.* - ``cylc stop --now --now`` - When the ``--now`` option is used twice Cylc stops straight away, leaving - any jobs running. - - Once a suite has stopped it is possible to restart it using the - ``cylc restart`` command. When the suite restarts it picks up where it left - off and carries on as normal. - - .. code-block:: bash - - # Run the suite "name". - cylc run - # Stop the suite "name", killing any running tasks. - cylc stop --kill - # Restart the suite "name", picking up where it left off. - cylc restart - -.. ifslides:: - - .. code-block:: sub - - cylc run - cylc stop - cylc restart - - cylc stop --kill - cylc stop --now --now - - .. nextslide:: - - .. rubric:: In this practical we will add runtime configuration to the - :ref:`weather-forecasting suite ` - from the :ref:`scheduling tutorial `. - - Next section: :ref:`tutorial-cylc-consolidating-configuration` - - -.. _tutorial-cylc-runtime-forecasting-suite: - -.. practical:: - - .. rubric:: In this practical we will add runtime configuration to the - :ref:`weather-forecasting suite ` - from the :ref:`scheduling tutorial `. - - #. **Create A New Suite.** - - Create a new suite by running the command: - - .. code-block:: bash - - rose tutorial runtime-tutorial - cd ~/cylc-run/runtime-tutorial - - You will now have a copy of the weather-forecasting suite along with some - executables and python modules. - - #. **Set The Initial And Final Cycle Points.** - - We want the suite to run for 6 hours, starting at least 7 hours ago, on - the hour. - - We could work out the dates and times manually, or we could let Cylc do - the maths for us. - - Set the :term:`initial cycle point`: - - .. code-block:: cylc - - initial cycle point = previous(T-00) - PT7H - - * ``previous(T-00)`` returns the current time ignoring minutes and - seconds. - - *e.g. if the current time is 12:34 this will return 12:00* - - * ``-PT7H`` subtracts 7 hours from this value. - - Set the :term:`final cycle point`: - - .. code-block:: cylc - - final cycle point = +PT6H - - This sets the :term:`final cycle point` six hours after the - :term:`initial cycle point`. - - Run `cylc validate` to check for any errors:: - - cylc validate . - - #. **Add Runtime Configuration For The** ``get_observations`` **Tasks.** - - In the ``bin`` directory is a script called ``get-observations``. This - script gets weather data from the MetOffice `DataPoint`_ service. - It requires two environment variables: - - ``SITE_ID``: - A four digit numerical code which is used to identify a - weather station, e.g. ``3772`` is Heathrow Airport. - ``API_KEY``: - An authentication key required for access to the service. - - .. TODO: Add instructions for offline configuration - - Generate a Datapoint API key:: - - rose tutorial api-key - - Add the following lines to the bottom of the ``flow.cylc`` file replacing - ``xxx...`` with your API key: - - .. code-block:: cylc - - [runtime] - [[get_observations_heathrow]] - script = get-observations - [[[environment]]] - SITE_ID = 3772 - API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - - - Add three more ``get_observations`` tasks for each of the remaining - weather stations. - - You will need the codes for the other three weather stations, which are: - - * Camborne - ``3808`` - * Shetland - ``3005`` - * Belmullet - ``3976`` - - .. spoiler:: Solution warning - - .. code-block:: cylc - - [runtime] - [[get_observations_heathrow]] - script = get-observations - [[[environment]]] - SITE_ID = 3772 - API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - [[get_observations_camborne]] - script = get-observations - [[[environment]]] - SITE_ID = 3808 - API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - [[get_observations_shetland]] - script = get-observations - [[[environment]]] - SITE_ID = 3005 - API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - [[get_observations_belmullet]] - script = get-observations - [[[environment]]] - SITE_ID = 3976 - API_KEY = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - - Check the ``flow.cylc`` file is valid by running the command: - - .. code-block:: bash - - cylc validate . - - .. TODO: Add advice on what to do if the command fails. - - #. **Test The** ``get_observations`` **Tasks.** - - Next we will test the ``get_observations`` tasks. - - Open the Cylc GUI by running the following command: - - .. code-block:: bash - - cylc gui runtime-tutorial & - - Run the suite either by pressing the play button in the Cylc GUI or by - running the command: - - .. code-block:: bash - - cylc run runtime-tutorial - - If all goes well the suite will startup and the tasks will run and - succeed. Note that the tasks which do not have a ``[runtime]`` section - will still run though they will not do anything as they do not call any - scripts. - - Once the suite has reached the final cycle point and all tasks have - succeeded the suite will automatically shutdown. - - .. TODO: Advise on what to do if all does not go well. - - The ``get-observations`` script produces a file called ``wind.csv`` which - specifies the wind speed and direction. This file is written in the task's - :term:`work directory`. - - Try and open one of the ``wind.csv`` files. Note that the path to the - :term:`work directory` is: - - .. code-block:: sub - - work// - - You should find a file containing four numbers: - - * The longitude of the weather station; - * The latitude of the weather station; - * The wind direction (*the direction the wind is blowing towards*) - in degrees; - * The wind speed in miles per hour. - - .. spoiler:: Hint hint - - If you run ``ls work`` you should see a - list of cycles. Pick one of them and open the file:: - - work//get_observations_heathrow/wind.csv - - #. **Add runtime configuration for the other tasks.** - - The runtime configuration for the remaining tasks has been written out - for you in the ``runtime`` file which you will find in the - :term:`suite directory`. Copy the code in the ``runtime`` file to the - bottom of the ``flow.cylc`` file. - - Check the ``flow.cylc`` file is valid by running the command: - - .. code-block:: bash - - cylc validate . - - .. TODO: Add advice on what to do if the command fails. - - #. **Run The Suite.** - - Open the Cylc GUI (if not already open) and run the suite. - - .. spoiler:: Hint hint - - .. code-block:: bash - - cylc gui runtime-tutorial & - - Run the suite either by: - - * Pressing the play button in the Cylc GUI. Then, ensuring that - "Cold Start" is selected within the dialogue window, pressing the - "Start" button. - * Running the command ``cylc run runtime-tutorial``. - - #. **View The Forecast Summary.** - - The ``post_process_exeter`` task will produce a one-line summary of the - weather in Exeter, as forecast two hours ahead of time. This summary can - be found in the ``summary.txt`` file in the :term:`work directory`. - - Try opening the summary file - it will be in the last cycle. The path to - the :term:`work directory` is: - - .. code-block:: sub - - work// - - .. spoiler:: Hint hint - - * ``cycle-point`` - this will be the last cycle of the suite, - i.e. the final cycle point. - * ``task-name`` - set this to "post_process_exeter". - - #. **View The Rainfall Data.** - - .. TODO: Skip this if you don't have internet connection. - - The ``forecast`` task will produce a html page where the rainfall - data is rendered on a map. This html file is called ``job-map.html`` and - is saved alongside the :term:`job log`. - - Try opening this file in a web browser, e.g via: - - .. code-block:: sub - - firefox & - - The path to the :term:`job log directory` is: - - .. code-block:: sub - - log/job/// - - .. spoiler:: Hint hint - - * ``cycle-point`` - this will be the last cycle of the suite, - i.e. the final cycle point. - * ``task-name`` - set this to "forecast". - * ``submission-number`` - set this to "01". - diff --git a/sphinx/tutorial/cylc/scheduling/datetime-cycling.rst b/sphinx/tutorial/cylc/scheduling/datetime-cycling.rst deleted file mode 100644 index 1b60158b27..0000000000 --- a/sphinx/tutorial/cylc/scheduling/datetime-cycling.rst +++ /dev/null @@ -1,632 +0,0 @@ -.. include:: ../../../hyperlinks.rst - :start-line: 1 - -.. _nowcasting: https://www.metoffice.gov.uk/services/data/met-office-data-for-reuse/model - -.. _tutorial-datetime-cycling: - -Date-Time Cycling -================= - - -In the last section we looked at writing an :term:`integer cycling` workflow, -one where the :term:`cycle points ` are numbered. - -.. ifnotslides:: - - Typically workflows are repeated at a regular time interval, say every day - or every few hours. To make this easier Cylc has a date-time cycling mode - where the :term:`cycle points ` use date and time specifications - rather than numbers. - -.. admonition:: Reminder - :class: tip - - :term:`Cycle points ` are labels. Cylc runs tasks as soon as - their dependencies are met so cycles do not necessarily run in order. - - -.. _tutorial-iso8601: - -ISO8601 -------- - -In Cylc, dates, times and durations are written using the :term:`ISO8601` format -- an international standard for representing dates and times. - -.. _tutorial-iso8601-datetimes: - -ISO8601 Date-Times -^^^^^^^^^^^^^^^^^^ - -.. ifnotslides:: - - In ISO8601, datetimes are written from the largest unit to the smallest - (i.e: year, month, day, hour, minute, second in succession) with the ``T`` - character separating the date and time components. For example, midnight - on the 1st of January 2000 is written ``20000101T000000``. - - For brevity we may omit seconds (and minutes) from the time i.e: - ``20000101T0000`` (``20000101T00``). - - For readability we may add hyphen (``-``) characters between the date - components and colon (``:``) characters between the time components, i.e: - ``2000-01-01T00:00``. This is the "extended" format. - - Time-zone information can be added onto the end. UTC is written ``Z``, - UTC+1 is written ``+01``, etc. E.G: ``2000-01-01T00:00Z``. - -.. Diagram of an iso8601 datetime's components. - -.. image:: ../img/iso8601-dates.svg - :width: 75% - :align: center - -.. nextslide:: - -.. warning:: - - The "basic" (purely numeric except for ``T``) and "extended" (written with - hyphens and colons) formats cannot be mixed. For example the following - date-times are invalid: - - .. code-block:: none - - 2000-01-01T0000 - 20000101T00:00 - -.. _tutorial-iso8601-durations: - -ISO8601 Durations -^^^^^^^^^^^^^^^^^ - -.. ifnotslides:: - - In ISO8601, durations are prefixed with a ``P`` and are written with a - character following each unit: - -* ``Y`` for year. -* ``M`` for month. -* ``D`` for day. -* ``W`` for week. -* ``H`` for hour. -* ``M`` for minute. -* ``S`` for second. - -.. nextslide:: - -.. ifnotslides:: - - As with datetimes the components are written in order from largest to - smallest and the date and time components are separated by the ``T`` - character. E.G: - -* ``P1D``: one day. -* ``PT1H``: one hour. -* ``P1DT1H``: one day and one hour. -* ``PT1H30M``: one and a half hours. -* ``P1Y1M1DT1H1M1S``: a year and a month and a day and an hour and a - minute and a second. - - -Date-Time Recurrences ---------------------- - -In :term:`integer cycling`, suites' recurrences are written ``P1``, ``P2``, -etc. - -In :term:`date-time cycling ` there are two ways to write -recurrences: - -1. Using ISO8601 durations (e.g. ``P1D``, ``PT1H``). -2. Using ISO8601 date-times with inferred recurrence. - -Inferred Recurrence -^^^^^^^^^^^^^^^^^^^ - -.. ifnotslides:: - - A recurrence can be inferred from a date-time by omitting digits from the - front. For example, if the year is omitted then the recurrence can be - inferred to be annual. E.G: - -.. code-block:: sub - - 2000-01-01T00 # Datetime - midnight on the 1st of January 2000. - - 01-01T00 # Every year on the 1st of January. - 01T00 # Every month on the first of the month. - T00 # Every day at midnight. - T-00 # Every hour at zero minutes past (every hour on the hour). - -.. note:: - - To omit hours from a date time we must place a ``-`` after the - ``T`` character. - -Recurrence Formats -^^^^^^^^^^^^^^^^^^ - -.. ifnotslides:: - - As with integer cycling, recurrences start, by default, at the - :term:`initial cycle point`. We can override this in one of two ways: - -1. By defining an arbitrary cycle point (``datetime/recurrence``): - - * ``2000/P1Y``: every year starting with the year 2000. - * ``2000-01-01T00/T00``: every day at midnight starting on the 1st of January - 2000 - * ``2000-01-01T12/T00``: every day at midnight starting on the first midnight - after the 1st of January at 12:00 (i.e. ``2000-01-02T00``). - -.. nextslide:: - -.. _tutorial-cylc-datetime-offset-icp: - -2. By defining an offset from the initial cycle point (``offset/recurrence``). - This offset is an ISO8601 duration preceded by a plus character: - - * ``+P1Y/P1Y``: every year starting one year after the initial cycle point. - * ``+PT1H/T00``: every day starting on the first midnight after the point one - hour after the initial cycle point. - -Durations And The Initial Cycle Point -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -When using durations, beware that a change in the initial cycle point -might produce different results for the recurrences. - -.. nextslide:: - -.. list-table:: - :class: grid-table - - * - .. code-block:: cylc - :emphasize-lines: 2 - - [scheduling] - initial cycle point = 2000-01-01T00 - [[dependencies]] - [[[P1D]]] - graph = foo[-P1D] => foo - - - .. code-block:: cylc - :emphasize-lines: 2 - - [scheduling] - initial cycle point = 2000-01-01T12 - [[dependencies]] - [[[P1D]]] - graph = foo[-P1D] => foo - - * - .. digraph:: Example - :align: center - - size = "3,3" - - "foo.1" [label="foo\n2000-01-01T00"] - "foo.2" [label="foo\n2000-01-02T00"] - "foo.3" [label="foo\n2000-01-03T00"] - - "foo.1" -> "foo.2" -> "foo.3" - - - .. digraph:: Example - :align: center - - size = "3,3" - - "foo.1" [label="foo\n2000-01-01T12"] - "foo.2" [label="foo\n2000-01-02T12"] - "foo.3" [label="foo\n2000-01-03T12"] - - "foo.1" -> "foo.2" -> "foo.3" - -.. nextslide:: - -We could write the recurrence "every midnight" independent from the initial -cycle point by: - -* Use an `inferred recurrence`_ instead (i.e. ``T00``). -* Overriding the recurrence start point (i.e. ``T00/P1D``) -* Using the ``[scheduling]initial cycle point constraints`` setting to - constrain the initial cycle point (e.g. to a particular time of day). See - the `Cylc User Guide`_ for details. - -The Initial & Final Cycle Points -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. ifnotslides:: - - There are two special recurrences for the initial and final cycle points: - -* ``R1``: repeat once at the initial cycle point. -* ``R1/P0Y``: repeat once at the final cycle point. - -.. TODO - change terminology as done in the cylc user guide, "repeat" can be - confusing. Use occur? - -Inter-Cycle Dependencies -^^^^^^^^^^^^^^^^^^^^^^^^ - -.. ifnotslides:: - - Inter-cycle dependencies are written as ISO8601 durations, e.g: - -* ``foo[-P1D]``: the task ``foo`` from the cycle one day before. -* ``bar[-PT1H30M]``: the task ``bar`` from the cycle 1 hour 30 minutes before. - -.. ifnotslides:: - - The initial cycle point can be referenced using a caret character ``^``, e.g: - -* ``baz[^]``: the task ``baz`` from the initial cycle point. - - -.. _tutorial-cylc-datetime-utc: - -UTC Mode --------- - -.. ifnotslides:: - - Due to all of the difficulties caused by time zones, particularly with - respect to daylight savings, we typically use UTC (that's the ``+00`` time - zone) in Cylc suites. - - When a suite uses UTC all of the cycle points will be written in the - ``+00`` time zone. - - To make your suite use UTC set the ``[cylc]UTC mode`` setting to ``True``, - i.e: - -.. code-block:: cylc - - [cylc] - UTC mode = True - - -.. _tutorial-datetime-cycling-practical: - -Putting It All Together ------------------------ - -.. ifslides:: - - We will now develop a simple weather forecasting suite. - -.. ifnotslides:: - - Cylc was originally developed for running operational weather forecasting. In - this section we will outline a basic (dummy) weather-forecasting suite and - explain how to implement it in cylc. - - .. note:: - - Technically the suite outlined in this section is a `nowcasting`_ suite. - We will refer to it as forecasting for simplicity. - - A basic weather-forecasting workflow consists of three main steps: - -1. Gathering Observations -^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. ifnotslides:: - - We gather observations from different weather stations and use them to - build a picture of the current weather. Our dummy weather forecast - will get wind observations from four weather stations: - - * Belmullet - * Camborne - * Heathrow - * Shetland - - The tasks which retrieve observation data will be called - ``get_observations_`` where ``site`` is the name of the weather - station in question. - - Next we need to consolidate these observations so that our forecasting - system can work with them. To do this we have a - ``consolidate_observations`` task. - - We will fetch wind observations **every three hours starting from the initial - cycle point**. - - The ``consolidate_observations`` task must run after the - ``get_observations`` tasks. - -.. digraph:: example - :align: center - - size = "7,4" - - get_observations_belmullet -> consolidate_observations - get_observations_camborne -> consolidate_observations - get_observations_heathrow -> consolidate_observations - get_observations_shetland -> consolidate_observations - - hidden [style="invis"] - get_observations_belmullet -> hidden [style="invis"] - get_observations_camborne -> hidden [style="invis"] - hidden -> consolidate_observations [style="invis"] - -.. ifnotslides:: - - We will also use the UK radar network to get rainfall data with a task - called ``get_rainfall``. - - We will fetch rainfall data **every six hours starting six hours after the - initial cycle point**. - -2. Running computer models to generate forecast data -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. ifnotslides:: - - We will do this with a task called ``forecast`` which will run - **every six hours starting six hours after the initial cycle point**. - The ``forecast`` task will be dependent on: - - * The ``consolidate_observations`` task from the previous two cycles as well - as from the present cycle. - * The ``get_rainfall`` task from the present cycle. - -.. digraph:: example - :align: center - - size = "7,4" - - subgraph cluster_T00 { - label="+PT0H" - style="dashed" - "observations.t00" [label="consolidate observations\n+PT0H"] - } - - subgraph cluster_T03 { - label="+PT3H" - style="dashed" - "observations.t03" [label="consolidate observations\n+PT3H"] - } - - subgraph cluster_T06 { - label="+PT6H" - style="dashed" - "forecast.t06" [label="forecast\n+PT6H"] - "get_rainfall.t06" [label="get_rainfall\n+PT6H"] - "observations.t06" [label="consolidate observations\n+PT6H"] - } - - "observations.t00" -> "forecast.t06" - "observations.t03" -> "forecast.t06" - "observations.t06" -> "forecast.t06" - "get_rainfall.t06" -> "forecast.t06" - -3. Processing the data output to produce user-friendly forecasts -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. ifnotslides:: - - This will be done with a task called ``post_process_`` where - ``location`` is the place we want to generate the forecast for. For - the moment we will use Exeter. - - The ``post_process_exeter`` task will run **every six hours starting six - hours after the initial cycle point** and will be dependent on the - ``forecast`` task. - -.. digraph:: example - :align: center - - size = "2.5,2" - - "forecast" -> "post_process_exeter" - -.. nextslide:: - -.. ifslides:: - - .. rubric:: Next Steps - - 1. Read through the "Putting It All Together" section. - 2. Complete the practical. - - Next section: :ref:`tutorial-cylc-further-scheduling` - - -.. _datetime cycling practical: - -.. practical:: - - .. rubric:: In this practical we will create a dummy forecasting suite - using date-time cycling. - - #. **Create A New Suite.** - - Within your ``~/cylc-run`` directory create a new directory called - ``datetime-cycling`` and move into it: - - .. code-block:: bash - - mkdir ~/cylc-run/datetime-cycling - cd ~/cylc-run/datetime-cycling - - Create a ``flow.cylc`` file and paste the following code into it: - - .. code-block:: cylc - - [cylc] - UTC mode = True - [scheduling] - initial cycle point = 20000101T00Z - [[dependencies]] - - #. **Add The Recurrences.** - - The weather-forecasting suite will require two - recurrences. Add sections under the dependencies section for these, - based on the information given above. - - .. hint:: - - See :ref:`Date-Time Recurrences`. - - .. spoiler:: Solution warning - - The two recurrences you need are - - * ``PT3H``: repeat every three hours starting from the initial cycle - point. - * ``+PT6H/PT6H``: repeat every six hours starting six hours after the - initial cycle point. - - .. code-block:: diff - - [cylc] - UTC mode = True - [scheduling] - initial cycle point = 20000101T00Z - [[dependencies]] - + [[[PT3H]]] - + [[[+PT6H/PT6H]]] - - #. **Write The Graphing.** - - With the help of the graphs and the information above add dependencies to - your suite to implement the weather-forecasting workflow. - - You will need to consider the inter-cycle dependencies between tasks. - - Use ``cylc graph`` to inspect your work. - - .. spoiler:: Hint hint - - The dependencies you will need to formulate are as follows: - - * The ``consolidate_observations`` task is dependent on the - ``get_observations_`` tasks. - * The ``forecast`` task is dependent on: - - * the ``get_rainfall`` task; - * the ``consolidate_observations`` tasks from: - - * the same cycle; - * the cycle 3 hours before (``-PT3H``); - * the cycle 6 hours before (``-PT6H``). - - * The ``post_process_exeter`` task is dependent on the ``forecast`` - task. - - To launch ``cylc graph`` run the command: - - .. code-block:: sub - - cylc graph - - .. spoiler:: Solution warning - - .. code-block:: cylc - - [cylc] - UTC mode = True - [scheduling] - initial cycle point = 20000101T00Z - [[dependencies]] - [[[PT3H]]] - graph = """ - get_observations_belmullet => consolidate_observations - get_observations_camborne => consolidate_observations - get_observations_heathrow => consolidate_observations - get_observations_shetland => consolidate_observations - """ - [[[+PT6H/PT6H]]] - graph = """ - consolidate_observations => forecast - consolidate_observations[-PT3H] => forecast - consolidate_observations[-PT6H] => forecast - get_rainfall => forecast => post_process_exeter - """ - - #. **Inter-Cycle Offsets.** - - To ensure the ``forecast`` tasks for different cycles run in order the - ``forecast`` task will also need to be dependent on the previous run - of ``forecast``. - - .. digraph:: example - :align: center - - size = "4,1.5" - rankdir=LR - - subgraph cluster_T06 { - label="T06" - style="dashed" - "forecast.t06" [label="forecast\nT06"] - } - - subgraph cluster_T12 { - label="T12" - style="dashed" - "forecast.t12" [label="forecast\nT12"] - } - - subgraph cluster_T18 { - label="T18" - style="dashed" - "forecast.t18" [label="forecast\nT18"] - } - - "forecast.t06" -> "forecast.t12" -> "forecast.t18" - - We can express this dependency as ``forecast[-PT6H] => forecast``. - - Try adding this line to your suite then visualising it with ``cylc - graph``. - - .. hint:: - - Try adjusting the number of cycles displayed by ``cylc graph``: - - .. code-block:: console - - $ cylc graph . 2000 20000101T12Z & - - You will notice that there is a dependency which looks like this: - - .. digraph:: example - :align: center - - size = "4,1" - rankdir=LR - - "forecast.t00" [label="forecast\n20000101T0000Z" - color="#888888" - fontcolor="#888888"] - "forecast.t06" [label="forecast\n20000101T0600Z"] - - - "forecast.t00" -> "forecast.t06" - - Note in particular that the ``forecast`` task in the 00:00 cycle is - grey. The reason for this is that this task does not exist. Remember - the forecast task runs every six hours - **starting 6 hours after the initial cycle point**, so the - dependency is only valid from 12:00 onwards. To fix the problem we - must add a new dependency section which repeats every six hours - **starting 12 hours after the initial cycle point**. - - Make the following changes to your suite and the grey task should - disappear: - - .. code-block:: diff - - [[[+PT6H/PT6H]]] - graph = """ - ... - - forecast[-PT6H] => forecast - """ - + [[[+PT12H/PT6H]]] - + graph = """ - + forecast[-PT6H] => forecast - + """ diff --git a/sphinx/tutorial/cylc/scheduling/further-scheduling.rst b/sphinx/tutorial/cylc/scheduling/further-scheduling.rst deleted file mode 100644 index b728cd3cda..0000000000 --- a/sphinx/tutorial/cylc/scheduling/further-scheduling.rst +++ /dev/null @@ -1,107 +0,0 @@ -.. include:: ../../../hyperlinks.rst - :start-line: 1 - -.. _tutorial-cylc-further-scheduling: - -Further Scheduling -================== - -In this section we will quickly run through some of the more advanced features -of Cylc's scheduling logic. - - -.. _tutorial-qualifiers: - -Qualifiers ----------- - -.. ifnotslides:: - - So far we have written dependencies like ``foo => bar``. This is, in fact, - shorthand for ``foo:succeed => bar``. It means that the task ``bar`` will run - once ``foo`` has finished successfully. If ``foo`` were to fail then ``bar`` - would not run. We will talk more about these :term:`task states ` - in the :ref:`Runtime Section `: - - We refer to the ``:succeed`` descriptor as a :term:`qualifier`. - There are qualifiers for different :term:`task states ` e.g: - -.. ifslides:: - - .. code-block:: cylc-graph - - foo => bar - foo:succeed => bar - foo:fail => bar - -``:start`` - When the task has started running. -``:fail`` - When the task finishes if it fails (produces non-zero return code). -``:finish`` - When the task has completed (either succeeded or failed). - -.. nextslide:: - -It is also possible to create your own custom :term:`qualifiers ` -to handle events within your code (custom outputs). - -.. ifnotslides:: - - *For more information see the* `Cylc User Guide`_. - - -Clock Triggers --------------- - -.. ifnotslides:: - - In Cylc, :term:`cycle points ` are just labels. Tasks are - triggered when their dependencies are met irrespective of the cycle they are - in, but we can force cycles to wait for a particular time before running - using clock triggers. This is necessary for certain operational and - monitoring systems. - - For example in the following suite the cycle ``2000-01-01T12Z`` will wait - until 11:00 on the 1st of January 2000 before running: - -.. code-block:: cylc - - [scheduling] - initial cycle point = 2000-01-01T00Z - [[special tasks]] - clock-trigger = daily(-PT1H) - [[dependencies]] - [[[T12]]] - graph = daily # "daily" will run, at the earliest, one hour - # before midday. - -.. tip:: - - See the :ref:`tutorial-cylc-clock-trigger` tutorial for more information. - - -Alternative Calendars ---------------------- - -.. ifnotslides:: - - By default Cylc uses the Gregorian calendar for :term:`datetime cycling`, - but Cylc also supports the 360-day calendar (12 months of 30 days each in - a year). - -.. code-block:: cylc - - [scheduling] - cycling mode = 360day - -.. ifnotslides:: - - *For more information see the* `Cylc User Guide`_. - -.. nextslide:: - -.. ifslides:: - - Next section: :ref:`Runtime Introduction - ` diff --git a/sphinx/tutorial/cylc/scheduling/graphing.rst b/sphinx/tutorial/cylc/scheduling/graphing.rst deleted file mode 100644 index 6f634c2ef8..0000000000 --- a/sphinx/tutorial/cylc/scheduling/graphing.rst +++ /dev/null @@ -1,408 +0,0 @@ -.. include:: ../../../hyperlinks.rst - - -.. _tutorial-cylc-graphing: - -Graphing -======== - -In this section we will cover writing basic workflows in cylc. - - -.. _Cylc file format: - -The ``flow.cylc`` File Format ------------------------------ - -.. ifnotslides:: - - We refer to a Cylc workflow as a :term:`Cylc suite`. A Cylc suite is a - directory containing a ``flow.cylc`` file. This configuration file is where - we define our workflow. The ``flow.cylc`` file uses a nested `INI`_-based - format: - -.. ifslides:: - - * Cylc workflow == Cylc suite - * Cylc suite is a directory containing a ``flow.cylc`` file - * The ``flow.cylc`` file is written in a nested `INI`_-based format - -.. ifnotslides:: - - * Comments start with a ``#`` character. - * Settings are written as ``key = value`` pairs. - * Settings can be contained within sections. - * Sections are written inside square brackets i.e. ``[section-name]``. - * Sections can be nested, by adding an extra square bracket with each level, - so a sub-section would be written ``[[sub-section]]``, a sub-sub-section - ``[[[sub-sub-section]]]``, and so on. - -Example -^^^^^^^ - -.. code-block:: cylc - - # Comment - [section] - key = value - [[sub-section]] - another-key = another-value # Inline comment - yet-another-key = """ - A - Multi-line - String - """ - -Shorthand -^^^^^^^^^ - -Throughout this tutorial we will refer to settings in the following format: - -``[section]`` - Refers to the entire section. -``[section]key`` - Refers to a setting within the section. -``[section]key=value`` - Expresses the value of the setting. -``[section][sub-section]another-key`` - Note we only use one set of square brackets with nested sections. - -Duplicate Items -^^^^^^^^^^^^^^^ - -Duplicate sections get merged: - -.. list-table:: - :class: grid-table - - * - - .. code-block:: cylc - :caption: input - - [a] - c = C - [b] - d = D - [a] # duplicate - e = E - - - - .. code-block:: cylc - :caption: result - - [a] - c = C - e = E - [b] - d = D - -.. nextslide:: - -Duplicate settings get overwritten: - -.. list-table:: - :class: grid-table - - * - - .. code-block:: cylc - :caption: input - - a = foo - a = bar # duplicate - - - - .. code-block:: cylc - :caption: result - - a = bar - -Indentation -^^^^^^^^^^^ - -It is advisable to indent ``flow.cylc`` files. - -However, Cylc ignores this indentation meaning the following two examples -are equivalent: - -.. list-table:: - :class: grid-table - - * - - .. code-block:: cylc - :caption: input - - [section] - a = A - [[sub-section]] - b = B - b = C - # this setting is still - # in [[sub-section]] - - - - - .. code-block:: cylc - :caption: result - - [section] - a = A - [[sub-section]] - b = C - - -Graph Strings -------------- - -In Cylc we consider workflows in terms of :term:`tasks ` and -:term:`dependencies `. - -.. ifnotslides:: - - Task are represented as words and dependencies as arrows (``=>``), so the - following text defines two tasks where ``make_dough`` is dependent on - ``purchase_ingredients``: - -.. minicylc:: - :align: center - :snippet: - :theme: demo - - purchase_ingredients => make_dough - -.. nextslide:: - -.. ifnotslides:: - - In a Cylc workflow this would mean that ``make_dough`` would only run when - ``purchase_ingredients`` has succeeded. These :term:`dependencies - ` can be chained together: - -.. minicylc:: - :align: center - :snippet: - :theme: demo - - purchase_ingredients => make_dough => bake_bread => sell_bread - -.. nextslide:: - -.. ifnotslides:: - - This line of text is referred to as a :term:`graph string`. These graph - strings can be combined to form more complex workflows: - -.. minicylc:: - :align: center - :snippet: - :theme: demo - - purchase_ingredients => make_dough => bake_bread => sell_bread - pre_heat_oven => bake_bread - bake_bread => clean_oven - -.. nextslide:: - -.. ifnotslides:: - - Graph strings can also contain "and" (``&``) and "or" (``|``) operators, for - instance the following lines are equivalent to the ones just above: - -.. code-block:: cylc-graph - - purchase_ingredients => make_dough - pre_heat_oven & make_dough => bake_bread => sell_bread & clean_oven - -.. nextslide:: - -Collectively these :term:`graph strings` are referred to as a -:term:`graph`. - -.. admonition:: Note - :class: tip - - .. ifnotslides:: - - The order in which lines appear in the graph section doesn't matter, for - instance the following examples are the same as each other: - - .. code-block:: cylc-graph - - foo => bar - bar => baz - - .. code-block:: cylc-graph - - bar => baz - foo => bar - - -Cylc Graphs ------------ - -.. ifnotslides:: - - In a :term:`Cylc suite` the :term:`graph` is stored under the - ``[scheduling][dependencies]graph`` setting, i.e: - -.. code-block:: cylc - - [scheduling] - [[dependencies]] - graph = """ - purchase_ingredients => make_dough - pre_heat_oven & make_dough => bake_bread => sell_bread & clean_oven - """ - -.. nextslide:: - -.. ifnotslides:: - - This is a minimal :term:`Cylc suite`, in which we have defined a - :term:`graph` representing a workflow for Cylc to run. - We have not yet provided Cylc with the scripts or binaries to run for - each task. This will be covered later in the - :ref:`runtime tutorial `. - - Cylc provides a GUI for visualising :term:`graphs `. It is run on the - command line using the ``cylc graph `` command where the path ``path`` - is to the ``flow.cylc`` file you wish to visualise. - - When run, ``cylc graph`` will display a diagram similar to the ones you have - seen so far. The number ``1`` which appears below each task is the - :term:`cycle point`. We will explain what this means in the next section. - -.. image:: ../img/cylc-graph.png - :align: center - -.. nextslide:: - -.. hint:: - - .. ifnotslides:: - - A graph can be drawn in multiple ways, for instance the following two - examples are equivalent: - - .. ifslides:: - - A graph can be drawn in multiple ways: - - .. image:: ../img/cylc-graph-reversible.svg - :align: center - - .. ifnotslides:: - - The graph drawn by ``cylc graph`` may vary slightly from one run to - another but the tasks and dependencies will always be the same. - -.. nextslide:: - -.. ifslides:: - - .. rubric:: In this practical we will create a new Cylc suite and write a - graph for it to use. - - Next session: :ref:`tutorial-integer-cycling` - -.. practical:: - - .. rubric:: In this practical we will create a new Cylc suite and write a - graph for it to use. - - #. **Create a Cylc suite.** - - A Cylc suite is just a directory containing a ``flow.cylc`` file. - - If you don't have one already, create a ``cylc-run`` directory in your - user space i.e:: - - ~/cylc-run - - Within this directory create a new folder called ``graph-introduction``, - which is to be our :term:`suite directory`. Move into it: - - .. code-block:: bash - - mkdir ~/cylc-run/graph-introduction - cd ~/cylc-run/graph-introduction - - Inside this directory create a ``flow.cylc`` file and paste in the - following text: - - .. code-block:: cylc - - [scheduling] - [[dependencies]] - graph = """ - # Write graph strings here! - """ - - #. **Write a graph.** - - We now have a blank Cylc suite, next we need to define a workflow. - - Edit your ``flow.cylc`` file to add graph strings representing the - following graph: - - .. digraph:: graph_tutorial - :align: center - - foo -> bar -> baz -> qux - pub -> bar -> wop - - #. **Use** ``cylc graph`` **to visualise the workflow.** - - Once you have written some graph strings try using ``cylc graph`` to - display the workflow. Run the following command: - - .. code-block:: bash - - cylc graph . - - .. admonition:: Note - :class: hint - - ``cylc graph`` takes the path to the suite as an argument. As we are - inside the :term:`suite directory` we can run ``cylc graph .``. - - If the results don't match the diagram above try going back to the - flow.cylc file and making changes. - - .. tip:: - - In the top right-hand corner of the ``cylc graph`` window there is a - refresh button which will reload the GUI with any changes you have - made. - - .. image:: ../img/cylc-graph-refresh.png - :align: center - - - .. spoiler:: Solution warning - - There are multiple correct ways to write this graph. So long as what - you see in ``cylc graph`` matches the above diagram then you have a - correct solution. - - Two valid examples: - - .. code-block:: cylc-graph - - foo & pub => bar => baz & wop - baz => qux - - .. code-block:: cylc-graph - - foo => bar => baz => qux - pub => bar => wop - - The whole suite should look something like this: - - .. code-block:: cylc - - [scheduling] - [[dependencies]] - graph = """ - foo & pub => bar => baz & wop - baz => qux - """ diff --git a/sphinx/tutorial/cylc/scheduling/index.rst b/sphinx/tutorial/cylc/scheduling/index.rst deleted file mode 100644 index f52df39fa2..0000000000 --- a/sphinx/tutorial/cylc/scheduling/index.rst +++ /dev/null @@ -1,15 +0,0 @@ -.. _tutorial-scheduling: - -Scheduling -========== - -This section looks at how to write workflows in cylc. - -.. toctree:: - :name: rug-scheduling-toc - :maxdepth: 2 - - graphing - integer-cycling - datetime-cycling - further-scheduling diff --git a/sphinx/tutorial/cylc/scheduling/integer-cycling.rst b/sphinx/tutorial/cylc/scheduling/integer-cycling.rst deleted file mode 100644 index 42cf105816..0000000000 --- a/sphinx/tutorial/cylc/scheduling/integer-cycling.rst +++ /dev/null @@ -1,612 +0,0 @@ -.. _tutorial-integer-cycling: - -Basic Cycling -============= - - -In this section we will look at how to write :term:`cycling` (repeating) -workflows. - - -Repeating Workflows -------------------- - -.. ifnotslides:: - - Often, we will want to repeat the same workflow multiple times. In Cylc this - "repetition" is called :term:`cycling` and each repetition of the workflow is - referred to as a :term:`cycle`. - - Each :term:`cycle` is given a unique label. This is called a - :term:`cycle point`. For now these :term:`cycle points` will be - integers *(they can also be dates as we will see in the next section)*. - -To make a workflow repeat we must tell Cylc three things: - -.. ifslides:: - - * :term:`recurrence` - * :term:`initial cycle point` - * :term:`final cycle point` - -.. ifnotslides:: - - The :term:`recurrence` - How often we want the workflow to repeat. - The :term:`initial cycle point` - At what cycle point we want to start the workflow. - The :term:`final cycle point` - *Optionally* we can also tell Cylc what cycle point we want to stop the - workflow. - -.. nextslide:: - -.. ifnotslides:: - - Let's take the bakery example from the previous section. Bread is - produced in batches so the bakery will repeat this workflow for each - batch of bread they bake. We can make this workflow repeat with the addition - of three lines: - -.. code-block:: diff - - [scheduling] - + cycling mode = integer - + initial cycle point = 1 - [[dependencies]] - + [[[P1]]] - graph = """ - purchase_ingredients => make_dough - pre_heat_oven & make_dough => bake_bread => sell_bread & clean_oven - """ - -.. nextslide:: - -.. ifnotslides:: - - * The ``cycling mode = integer`` setting tells Cylc that we want our - :term:`cycle points ` to be numbered. - * The ``initial cycle point = 1`` setting tells Cylc to start counting - from 1. - * ``P1`` is the :term:`recurrence`. The :term:`graph` within the ``[[[P1]]]`` - section will be repeated at each :term:`cycle point`. - - The first three :term:`cycles` would look like this, with the entire - workflow repeated at each cycle point: - -.. digraph:: example - :align: center - - size = "7,15" - - subgraph cluster_1 { - label = 1 - style = dashed - "pur.1" [label="purchase_ingredients\n1"] - "mak.1" [label="make_dough\n1"] - "bak.1" [label="bake_bread\n1"] - "sel.1" [label="sell_bread\n1"] - "cle.1" [label="clean_oven\n1"] - "pre.1" [label="pre_heat_oven\n1"] - } - - subgraph cluster_2 { - label = 2 - style = dashed - "pur.2" [label="purchase_ingredients\n2"] - "mak.2" [label="make_dough\n2"] - "bak.2" [label="bake_bread\n2"] - "sel.2" [label="sell_bread\n2"] - "cle.2" [label="clean_oven\n2"] - "pre.2" [label="pre_heat_oven\n2"] - } - - subgraph cluster_3 { - label = 3 - style = dashed - "pur.3" [label="purchase_ingredients\n3"] - "mak.3" [label="make_dough\n3"] - "bak.3" [label="bake_bread\n3"] - "sel.3" [label="sell_bread\n3"] - "cle.3" [label="clean_oven\n3"] - "pre.3" [label="pre_heat_oven\n3"] - } - - "pur.1" -> "mak.1" -> "bak.1" -> "sel.1" - "pre.1" -> "bak.1" -> "cle.1" - "pur.2" -> "mak.2" -> "bak.2" -> "sel.2" - "pre.2" -> "bak.2" -> "cle.2" - "pur.3" -> "mak.3" -> "bak.3" -> "sel.3" - "pre.3" -> "bak.3" -> "cle.3" - -.. ifnotslides:: - - Note the numbers under each task which represent the :term:`cycle point` each - task is in. - - -Inter-Cycle Dependencies ------------------------- - -.. ifnotslides:: - - We've just seen how to write a workflow that repeats every :term:`cycle`. - - Cylc runs tasks as soon as their dependencies are met so cycles are not - necessarily run in order. This could cause problems, for instance we could - find ourselves pre-heating the oven in one cycle whist we are still - cleaning it in another. - - To resolve this we must add :term:`dependencies` *between* the - cycles. We do this by adding lines to the :term:`graph`. Tasks in the - previous cycle can be referred to by suffixing their name with ``[-P1]``, - for example. So to ensure the ``clean_oven`` task has been completed before - the start of the ``pre_heat_oven`` task in the next cycle, we would write - the following dependency: - - .. code-block:: cylc-graph - - clean_oven[-P1] => pre_heat_oven - - This dependency can be added to the suite by adding it to the other graph - lines: - -.. code-block:: diff - - [scheduling] - cycling mode = integer - initial cycle point = 1 - [[dependencies]] - [[[P1]]] - graph = """ - purchase_ingredients => make_dough - pre_heat_oven & make_dough => bake_bread => sell_bread & clean_oven - + clean_oven[-P1] => pre_heat_oven - """ - -.. nextslide:: - -.. ifnotslides:: - - The resulting suite would look like this: - -.. digraph:: example - :align: center - - size = "7,15" - - subgraph cluster_1 { - label = 1 - style = dashed - "pur.1" [label="purchase_ingredients\n1"] - "mak.1" [label="make_dough\n1"] - "bak.1" [label="bake_bread\n1"] - "sel.1" [label="sell_bread\n1"] - "cle.1" [label="clean_oven\n1"] - "pre.1" [label="pre_heat_oven\n1"] - } - - subgraph cluster_2 { - label = 2 - style = dashed - "pur.2" [label="purchase_ingredients\n2"] - "mak.2" [label="make_dough\n2"] - "bak.2" [label="bake_bread\n2"] - "sel.2" [label="sell_bread\n2"] - "cle.2" [label="clean_oven\n2"] - "pre.2" [label="pre_heat_oven\n2"] - } - - subgraph cluster_3 { - label = 3 - style = dashed - "pur.3" [label="purchase_ingredients\n3"] - "mak.3" [label="make_dough\n3"] - "bak.3" [label="bake_bread\n3"] - "sel.3" [label="sell_bread\n3"] - "cle.3" [label="clean_oven\n3"] - "pre.3" [label="pre_heat_oven\n3"] - } - - "pur.1" -> "mak.1" -> "bak.1" -> "sel.1" - "pre.1" -> "bak.1" -> "cle.1" - "cle.1" -> "pre.2" - "pur.2" -> "mak.2" -> "bak.2" -> "sel.2" - "pre.2" -> "bak.2" -> "cle.2" - "cle.2" -> "pre.3" - "pur.3" -> "mak.3" -> "bak.3" -> "sel.3" - "pre.3" -> "bak.3" -> "cle.3" - -.. nextslide:: - -.. ifnotslides:: - - Adding this dependency "strings together" the cycles, forcing them to run in - order. We refer to dependencies between cycles as - :term:`inter-cycle dependencies`. - - In the dependency the ``[-P1]`` suffix tells Cylc that we are referring to a - task in the previous cycle. Equally ``[-P2]`` would refer to a task two - cycles ago. - - Note that the ``purchase_ingredients`` task has no arrows pointing at it - meaning that it has no dependencies. Consequently the ``purchase_ingredients`` - tasks will all run straight away. This could cause our bakery to run into - cash-flow problems as they would be purchasing ingredients well in advance - of using them. - - To solve this, but still make sure that they never run out of - ingredients, the bakery wants to purchase ingredients two batches ahead. - This can be achieved by adding the following dependency: - -.. ifslides:: - - We need ``purchase_ingredients`` to be dependent on ``sell_bread`` from - two cycles before. - -.. nextslide:: - -.. code-block:: diff - - [scheduling] - cycling mode = integer - initial cycle point = 1 - [[dependencies]] - [[[P1]]] - graph = """ - purchase_ingredients => make_dough - pre_heat_oven & make_dough => bake_bread => sell_bread & clean_oven - clean_oven[-P1] => pre_heat_oven - + sell_bread[-P2] => purchase_ingredients - """ - -.. nextslide:: - -.. ifnotslides:: - - This dependency means that the ``purchase_ingredients`` task will run after - the ``sell_bread`` task two cycles before. - -.. note:: - - The ``[-P2]`` suffix is used to reference a task two cycles before. For the - first two cycles this doesn't make sense as there was no cycle two cycles - before, so this dependency will be ignored. - - Any inter-cycle dependencies stretching back to before the - :term:`initial cycle point` will be ignored. - -.. digraph:: example - :align: center - - size = "4.5,15" - - subgraph cluster_1 { - label = 1 - style = dashed - "pur.1" [label="purchase_ingredients\n1"] - "mak.1" [label="make_dough\n1"] - "bak.1" [label="bake_bread\n1"] - "sel.1" [label="sell_bread\n1"] - "cle.1" [label="clean_oven\n1"] - "pre.1" [label="pre_heat_oven\n1"] - } - - subgraph cluster_2 { - label = 2 - style = dashed - "pur.2" [label="purchase_ingredients\n2"] - "mak.2" [label="make_dough\n2"] - "bak.2" [label="bake_bread\n2"] - "sel.2" [label="sell_bread\n2"] - "cle.2" [label="clean_oven\n2"] - "pre.2" [label="pre_heat_oven\n2"] - } - - subgraph cluster_3 { - label = 3 - style = dashed - "pur.3" [label="purchase_ingredients\n3"] - "mak.3" [label="make_dough\n3"] - "bak.3" [label="bake_bread\n3"] - "sel.3" [label="sell_bread\n3"] - "cle.3" [label="clean_oven\n3"] - "pre.3" [label="pre_heat_oven\n3"] - } - - subgraph cluster_4 { - label = 4 - style = dashed - "pur.4" [label="purchase_ingredients\n4"] - "mak.4" [label="make_dough\n4"] - "bak.4" [label="bake_bread\n4"] - "sel.4" [label="sell_bread\n4"] - "cle.4" [label="clean_oven\n4"] - "pre.4" [label="pre_heat_oven\n4"] - } - - "pur.1" -> "mak.1" -> "bak.1" -> "sel.1" - "pre.1" -> "bak.1" -> "cle.1" - "cle.1" -> "pre.2" - "sel.1" -> "pur.3" - "pur.2" -> "mak.2" -> "bak.2" -> "sel.2" - "pre.2" -> "bak.2" -> "cle.2" - "cle.2" -> "pre.3" - "sel.2" -> "pur.4" - "pur.3" -> "mak.3" -> "bak.3" -> "sel.3" - "pre.3" -> "bak.3" -> "cle.3" - "cle.3" -> "pre.4" - "pur.4" -> "mak.4" -> "bak.4" -> "sel.4" - "pre.4" -> "bak.4" -> "cle.4" - - -Recurrence Sections -------------------- - -.. ifnotslides:: - - In the previous examples we made the workflow repeat by placing the graph - within the ``[[[P1]]]`` section. Here ``P1`` is a :term:`recurrence` meaning - repeat every cycle, where ``P1`` means every cycle, ``P2`` means every - *other* cycle, and so on. To build more complex workflows we can use multiple - recurrences: - -.. code-block:: cylc - - [scheduling] - cycling mode = integer - initial cycle point = 1 - [[dependencies]] - [[[P1]]] # Repeat every cycle. - graph = foo - [[[P2]]] # Repeat every second cycle. - graph = bar - [[[P3]]] # Repeat every third cycle. - graph = baz - -.. digraph:: example - :align: center - - subgraph cluster_1 { - label = 1 - style = dashed - "foo.1" [label="foo\n1"] - "bar.1" [label="bar\n1"] - "baz.1" [label="baz\n1"] - } - - subgraph cluster_2 { - label = 2 - style = dashed - "foo.2" [label="foo\n2"] - } - - subgraph cluster_3 { - label = 3 - style = dashed - "foo.3" [label="foo\n3"] - "bar.3" [label="bar\n3"] - } - -.. nextslide:: - -.. ifnotslides:: - - By default recurrences start at the :term:`initial cycle point`, however it - is possible to make them start at an arbitrary cycle point. This is done by - writing the cycle point and the recurrence separated by a forward slash - (``/``), e.g. ``5/P3`` means repeat every third cycle starting *from* cycle - number 5. - - The start point of a recurrence can also be defined as an offset from the - :term:`initial cycle point`, e.g. ``+P5/P3`` means repeat every third cycle - starting 5 cycles *after* the initial cycle point. - -.. ifslides:: - - ``5/P3`` - Repeat every third cycle starting *from* cycle number 5. - ``+P5/P3`` - Repeat every third cycle starting 5 cycles *after* the initial cycle - point. - - .. nextslide:: - - .. rubric:: In this practical we will take the :term:`suite ` - we wrote in the previous section and turn it into a - :term:`cycling suite `. - - Next section: :ref:`tutorial-datetime-cycling` - -.. _basic cycling practical: - -.. practical:: - - .. rubric:: In this practical we will take the :term:`suite ` - we wrote in the previous section and turn it into a - :term:`cycling suite `. - - If you have not completed the previous practical use the following code for - your ``flow.cylc`` file. - - .. code-block:: cylc - - [scheduling] - [[dependencies]] - graph = """ - foo & pub => bar => baz & wop - baz => qux - """ - - #. **Create a new suite.** - - Within your ``~/cylc-run/`` directory create a new (sub-)directory called - ``integer-cycling`` and move into it: - - .. code-block:: bash - - mkdir -p ~/cylc-run/integer-cycling - cd ~/cylc-run/integer-cycling - - Copy the above code into a ``flow.cylc`` file in that directory. - - #. **Make the suite cycle.** - - Add in the following lines. - - .. code-block:: diff - - [scheduling] - + cycling mode = integer - + initial cycle point = 1 - [[dependencies]] - + [[[P1]]] - graph = """ - foo & pub => bar => baz & wop - baz => qux - """ - - #. **Visualise the suite.** - - Try visualising the suite using ``cylc graph``. - - .. code-block:: none - - cylc graph . - - .. tip:: - - You can get Cylc graph to draw dotted boxes around the cycles by - clicking the "Organise by cycle point" button on the toolbar: - - .. image:: ../img/cylc-graph-cluster.png - :align: center - - .. tip:: - - By default ``cylc graph`` displays the first three cycles of a suite. - You can tell ``cylc graph`` to visualise the cycles between two points - by providing them as arguments, for instance the following example - would show all cycles between ``1`` and ``5`` (inclusive):: - - cylc graph . 1 5 & - - #. **Add another recurrence.** - - Suppose we wanted the ``qux`` task to run every *other* cycle as opposed - to every cycle. We can do this by adding another recurrence. - - Make the following changes to your ``flow.cylc`` file. - - .. code-block:: diff - - [scheduling] - cycling mode = integer - initial cycle point = 1 - [[dependencies]] - [[[P1]]] - graph = """ - foo & pub => bar => baz & wop - - baz => qux - """ - + [[[P2]]] - + graph = """ - + baz => qux - + """ - - Use ``cylc graph`` to see the effect this has on the workflow. - - #. **Inter-cycle dependencies.** - - Next we need to add some inter-cycle dependencies. We are going to add - three inter-cycle dependencies: - - #. Between ``wop`` from the previous cycle and ``pub``. - #. Between ``baz`` from the previous cycle and ``foo`` - *every odd cycle* (e.g. baz.2 => foo.3). - #. Between ``qux`` from the previous cycle and ``foo`` - *every even cycle* (e.g. qux.1 => foo.2). - - Have a go at adding inter-cycle dependencies to your ``flow.cylc`` file to - make your workflow match the diagram below. - - .. hint:: - - * ``P2`` means every odd cycle. - * ``2/P2`` means every even cycle. - - .. digraph:: example - :align: center - - size = "4.5,7" - - subgraph cluster_1 { - label = 1 - style = dashed - "foo.1" [label="foo\n1"] - "bar.1" [label="bar\n1"] - "baz.1" [label="baz\n1"] - "wop.1" [label="wop\n1"] - "pub.1" [label="pub\n1"] - "qux.1" [label="qux\n1"] - } - - subgraph cluster_2 { - label = 2 - style = dashed - "foo.2" [label="foo\n2"] - "bar.2" [label="bar\n2"] - "baz.2" [label="baz\n2"] - "wop.2" [label="wop\n2"] - "pub.2" [label="pub\n2"] - } - - subgraph cluster_3 { - label = 3 - style = dashed - "foo.3" [label="foo\n3"] - "bar.3" [label="bar\n3"] - "baz.3" [label="baz\n3"] - "wop.3" [label="wop\n3"] - "pub.3" [label="pub\n3"] - "qux.3" [label="qux\n3"] - } - - "foo.1" -> "bar.1" -> "wop.1" - "bar.1" -> "baz.1" - "pub.1" -> "bar.1" - "foo.2" -> "bar.2" -> "wop.2" - "bar.2" -> "baz.2" - "pub.2" -> "bar.2" - "foo.3" -> "bar.3" -> "wop.3" - "bar.3" -> "baz.3" - "pub.3" -> "bar.3" - "baz.1" -> "qux.1" -> "foo.2" - "baz.3" -> "qux.3" - "baz.2" -> "foo.3" - "wop.1" -> "pub.2" - "wop.2" -> "pub.3" - - .. spoiler:: Solution warning - - .. code-block:: cylc - - - [scheduling] - cycling mode = integer - initial cycle point = 1 - [[dependencies]] - [[[P1]]] - graph = """ - foo & pub => bar => baz & wop - wop[-P1] => pub # (1) - """ - [[[P2]]] - graph = """ - baz => qux - baz[-P1] => foo # (2) - """ - [[[2/P2]]] - graph = """ - qux[-P1] => foo # (3) - """ From 61d48fa29c32f215ba273d44dca15a9c69816c57 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Thu, 21 Jan 2021 17:09:35 +0000 Subject: [PATCH 39/78] docs: allow docs build to pass --- sphinx/api/command-reference.rst | 23 +++++++++++++++++++ sphinx/index.rst | 1 - .../rose/furthertopics/command-keys.rst | 2 +- .../tutorial/rose/furthertopics/rose-arch.rst | 2 +- .../rose/furthertopics/rose-bunch.rst | 2 +- 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/sphinx/api/command-reference.rst b/sphinx/api/command-reference.rst index 7ba8f85c0d..70ba7a5ab9 100644 --- a/sphinx/api/command-reference.rst +++ b/sphinx/api/command-reference.rst @@ -11,6 +11,29 @@ Rose Commands ---- +.. _command-rose-config-edit: + +rose config-edit +^^^^^^^^^^^^^^^^ + +TODO: This is here to allow the documentation tests to pass + +.. _command-rose-suite-run: + +rose suite-run +^^^^^^^^^^^^^^ + +TODO: This is here to allow the documentation tests to pass + +.. _command-rose-suite-restart: + +rose suite-restart +^^^^^^^^^^^^^^^^^^ + +TODO: This is here to allow the documentation tests to pass + +---- + .. _command-rose-test-battery: etc/bin/rose-test-battery diff --git a/sphinx/index.rst b/sphinx/index.rst index faf3e58c91..d18adf9d67 100644 --- a/sphinx/index.rst +++ b/sphinx/index.rst @@ -44,7 +44,6 @@ applications. :ref:`What Is Cylc? ` installation getting-started - tutorial/cylc/index tutorial/rose/index cheat-sheet glossary diff --git a/sphinx/tutorial/rose/furthertopics/command-keys.rst b/sphinx/tutorial/rose/furthertopics/command-keys.rst index daca00d2bd..3991e48b8c 100644 --- a/sphinx/tutorial/rose/furthertopics/command-keys.rst +++ b/sphinx/tutorial/rose/furthertopics/command-keys.rst @@ -57,7 +57,7 @@ This sets up a simple suite that contains the following: Save your changes then run the suite using :ref:`command-rose-suite-run`. -Once it has finished use :ref:`command-rose-suite-log` to view the suite log. +Once it has finished use ``cylc cat-log`` to view the suite log. In the page that appears, click the "out" link for the breadmaker task. In the page you are taken to you should see a line saying "fresh bread". diff --git a/sphinx/tutorial/rose/furthertopics/rose-arch.rst b/sphinx/tutorial/rose/furthertopics/rose-arch.rst index bfc3ed4e24..1bc60d625e 100644 --- a/sphinx/tutorial/rose/furthertopics/rose-arch.rst +++ b/sphinx/tutorial/rose/furthertopics/rose-arch.rst @@ -155,7 +155,7 @@ Save your changes and run the suite:: rose suite-run -View the suite output using :ref:`command-rose-suite-log` and inspect the +View the suite output using ``cylc cat-log`` and inspect the output of the ``make_files``, ``archive_files_rsync`` and ``archive_files_scp`` tasks. diff --git a/sphinx/tutorial/rose/furthertopics/rose-bunch.rst b/sphinx/tutorial/rose/furthertopics/rose-bunch.rst index 17fb0caa9d..69a024ca20 100644 --- a/sphinx/tutorial/rose/furthertopics/rose-bunch.rst +++ b/sphinx/tutorial/rose/furthertopics/rose-bunch.rst @@ -149,7 +149,7 @@ its output (note that you can close the Cylc GUI at this point):: .. note:: You can quickly get to the relevant page by running - :ref:`command-rose-suite-log` from within the :term:`suite directory`. + ``cylc cat-log`` from within the :term:`suite directory`. In the Rose Bush jobs page for your suite you should be presented with a page containing a single row for the ``lander`` task, from which you can From 497cf2d53a392d8f0bf43171665e3049ca7c877b Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Thu, 21 Jan 2021 17:40:05 +0000 Subject: [PATCH 40/78] docs: upgrade makefile to sphinx3 --- .github/workflows/test.yml | 4 - sphinx/Makefile | 188 +++----------------------------- sphinx/extract-pdf-documents.py | 82 -------------- 3 files changed, 13 insertions(+), 261 deletions(-) mode change 100755 => 100644 sphinx/Makefile delete mode 100644 sphinx/extract-pdf-documents.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fe73b89048..aa5e653689 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -162,10 +162,6 @@ jobs: run: | make -C sphinx/ slides SPHINXOPTS='-Wn' - - name: build (latexpdf) - run: | - make -C sphinx/ latexpdf SPHINXOPTS='-Wn' - - name: build (linkcheck) run: | make -C sphinx/ linkcheck SPHINXOPTS='-Wn' diff --git a/sphinx/Makefile b/sphinx/Makefile old mode 100755 new mode 100644 index 614210ecb5..f922fc7cb3 --- a/sphinx/Makefile +++ b/sphinx/Makefile @@ -1,186 +1,24 @@ -# Makefile for Sphinx documentation -# +# Minimal makefile for Sphinx documentation -# You can set these variables from the command line. +# You can set these variables from the command line: SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = ../doc +SOURCEDIR = . -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext pdf slides - +# Put it first so that "make" without argument is like "make help". help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -slides: - $(SPHINXBUILD) -b slides $(ALLSPHINXOPTS) $(BUILDDIR)/slides - @echo - @echo "Build finished. The slide-html files are in $(BUILDDIR)/slides." - -pdf: latexpdf - python2 extract-pdf-documents.py "$(BUILDDIR)" - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/pdf." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/rose-api-doc.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/rose-api-doc.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/rose-api-doc" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/rose-api-doc" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." + @$(SPHINXBUILD) -M clean "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." +.PHONY: help clean Makefile .EXPORT_ALL_VARIABLES M -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +# NOTE: EXPORT_ALL_VARIABLES exports make vars as env vars +%: Makefile .EXPORT_ALL_VARIABLES + # build documentation + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/sphinx/extract-pdf-documents.py b/sphinx/extract-pdf-documents.py deleted file mode 100644 index 476731ddb4..0000000000 --- a/sphinx/extract-pdf-documents.py +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright (C) British Crown (Met Office) & Contributors. -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -# ----------------------------------------------------------------------------- -"""Script for extracting PDF documents from a Sphinx LaTeX build. - -* Copies PDF documents from the ``latex`` build directory to a ``pdf`` folder. -* Creates an HTML index of these documents. - -""" - -import os -import shutil -import sys - -import conf - - -def main(): - try: - build_dir = sys.argv[1] - except IndexError: - sys.exit('usage: extract-pdf-documents build_dir') - latex_dir = os.path.join(build_dir, 'latex') - pdf_dir = os.path.join(build_dir, 'pdf') - os.makedirs(pdf_dir, exist_ok=True) - - # the index html file - html = ( - '' - '' - '' - 'Rose Documentation - PDF Documents' - '' - '' - '

Rose Documentation - PDF Documents

' - '
' - '' - '' - ) - - # write index file - with open(os.path.join(pdf_dir, 'index.html'), 'w+') as index: - index.write(html) - - # remove now un-necessary latex directory - shutil.rmtree(latex_dir) - - -if __name__ == '__main__': - main() From a277a0645a509333d74c1a07b34ae609aaa80a72 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Fri, 22 Jan 2021 10:35:07 +0000 Subject: [PATCH 41/78] abort if any task fails --- t/rose-task-env/00-non-cycle.t | 1 + t/rose-task-env/00-non-cycle/flow.cylc | 1 - t/rose-task-env/01-integer-cycling.t | 1 + t/rose-task-env/01-integer-cycling/flow.cylc | 1 - t/rose-task-run/00-run-basic.t | 1 + t/rose-task-run/00-run-basic/flow.cylc | 1 - t/rose-task-run/01-run-basic-iso.t | 1 + t/rose-task-run/01-run-basic-iso/flow.cylc | 1 - t/rose-task-run/02-env-basic/flow.cylc | 1 - t/rose-task-run/03-env-basic-iso/flow.cylc | 1 - t/rose-task-run/04-run-path-empty.t | 1 + t/rose-task-run/04-run-path-empty/flow.cylc | 1 - t/rose-task-run/06-app-prune-iso.t | 1 + t/rose-task-run/06-app-prune-iso/flow.cylc | 1 - t/rose-task-run/07-app-arch.t | 1 + t/rose-task-run/07-app-arch/flow.cylc | 1 - t/rose-task-run/08-app-fcm-make.t | 1 + t/rose-task-run/08-app-fcm-make/flow.cylc | 1 - t/rose-task-run/09-app-prune-work.t | 1 + t/rose-task-run/09-app-prune-work/flow.cylc | 1 - t/rose-task-run/10-specify-cycle.t | 1 + t/rose-task-run/10-specify-cycle/flow.cylc | 1 - t/rose-task-run/11-specify-cycle-iso.t | 1 + .../11-specify-cycle-iso/flow.cylc | 1 - t/rose-task-run/12-app-prune-integer.t | 1 + .../12-app-prune-integer/flow.cylc | 1 - t/rose-task-run/13-app-arch-cmd-out.t | 1 + t/rose-task-run/13-app-arch-cmd-out/flow.cylc | 1 - t/rose-task-run/14-app-prune-remove.t | 1 + t/rose-task-run/14-app-prune-remove/flow.cylc | 1 - t/rose-task-run/15-app-prune-extglob.t | 1 + .../15-app-prune-extglob/flow.cylc | 1 - t/rose-task-run/16-app-prune-point.t | 1 + t/rose-task-run/16-app-prune-point/flow.cylc | 1 - .../17-app-prune-glob-as-cycle-fmt.t | 1 + .../17-app-prune-glob-as-cycle-fmt/flow.cylc | 1 - t/rose-task-run/18-app-fcm-make-ctx-name.t | 1 + .../18-app-fcm-make-ctx-name/flow.cylc | 1 - t/rose-task-run/20-app-fcm-make-dest.t | 1 + .../20-app-fcm-make-dest/flow.cylc | 1 - t/rose-task-run/24-app-fcm-make-fast.t | 1 + .../24-app-fcm-make-fast/flow.cylc | 1 - t/rose-task-run/25-app-fcm-make-new-mode.t | 1 + .../25-app-fcm-make-new-mode/flow.cylc | 1 - .../26-app-fcm-make-new-mode-with-cont.t | 1 + .../flow.cylc | 1 - t/rose-task-run/28-env-path-run-path.t | 1 + .../28-env-path-run-path/flow.cylc | 1 - t/rose-task-run/30-app-arch-opt-source.t | 1 + .../30-app-arch-opt-source/flow.cylc | 19 +++++++++---------- t/rose-task-run/32-app-arch-compressed.t | 1 + .../32-app-arch-compressed/flow.cylc | 1 - t/rose-task-run/33-app-prune-cycle-format.t | 1 + .../33-app-prune-cycle-format/flow.cylc | 1 - .../34-app-prune-hosts-sharing-fs.t | 1 + .../34-app-prune-hosts-sharing-fs/flow.cylc | 1 - .../35-app-prune-log-on-hosts-sharing-fs.t | 1 + .../flow.cylc | 1 - t/rose-task-run/37-app-bunch-rm-old.t | 2 ++ t/rose-task-run/37-app-bunch-rm-old/flow.cylc | 1 - t/rose-task-run/38-app-bunch-counts.t | 1 + t/rose-task-run/38-app-bunch-counts/flow.cylc | 1 - t/rose-task-run/39-app-prune-cycle-host.t | 1 + .../39-app-prune-cycle-host/flow.cylc | 1 - .../41-app-bunch-default-command.t | 8 +++++--- .../41-app-bunch-default-command/flow.cylc | 1 - 66 files changed, 46 insertions(+), 46 deletions(-) diff --git a/t/rose-task-env/00-non-cycle.t b/t/rose-task-env/00-non-cycle.t index e6d1ba30e0..dcc76e9f40 100755 --- a/t/rose-task-env/00-non-cycle.t +++ b/t/rose-task-env/00-non-cycle.t @@ -37,6 +37,7 @@ run_pass "${TEST_KEY_BASE}-install" \ run_pass "${TEST_KEY_BASE}-run" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug diff --git a/t/rose-task-env/00-non-cycle/flow.cylc b/t/rose-task-env/00-non-cycle/flow.cylc index 19faad0a85..3baa8beb32 100644 --- a/t/rose-task-env/00-non-cycle/flow.cylc +++ b/t/rose-task-env/00-non-cycle/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode = True - abort if any task fails = True [[events]] abort on timeout = True timeout = PT1M diff --git a/t/rose-task-env/01-integer-cycling.t b/t/rose-task-env/01-integer-cycling.t index 0643fac13b..206365097c 100755 --- a/t/rose-task-env/01-integer-cycling.t +++ b/t/rose-task-env/01-integer-cycling.t @@ -37,6 +37,7 @@ run_pass "${TEST_KEY_BASE}-install" \ run_pass "${TEST_KEY_BASE}-run" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug diff --git a/t/rose-task-env/01-integer-cycling/flow.cylc b/t/rose-task-env/01-integer-cycling/flow.cylc index af29f0982e..f43a252a40 100644 --- a/t/rose-task-env/01-integer-cycling/flow.cylc +++ b/t/rose-task-env/01-integer-cycling/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode = True - abort if any task fails = True [[events]] abort on timeout = True timeout = PT1M diff --git a/t/rose-task-run/00-run-basic.t b/t/rose-task-run/00-run-basic.t index 83d8781aab..d2d854fdcf 100755 --- a/t/rose-task-run/00-run-basic.t +++ b/t/rose-task-run/00-run-basic.t @@ -37,6 +37,7 @@ run_pass "${TEST_KEY_BASE}-install" \ run_pass "${TEST_KEY_BASE}-run" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug diff --git a/t/rose-task-run/00-run-basic/flow.cylc b/t/rose-task-run/00-run-basic/flow.cylc index 01dedfe8e1..92b9a26a7c 100644 --- a/t/rose-task-run/00-run-basic/flow.cylc +++ b/t/rose-task-run/00-run-basic/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode = True - abort if any task fails = True [[events]] abort on timeout = True timeout = PT2M diff --git a/t/rose-task-run/01-run-basic-iso.t b/t/rose-task-run/01-run-basic-iso.t index c7c51d1864..a764e5dc80 100755 --- a/t/rose-task-run/01-run-basic-iso.t +++ b/t/rose-task-run/01-run-basic-iso.t @@ -37,6 +37,7 @@ run_pass "${TEST_KEY_BASE}-install" \ run_pass "${TEST_KEY_BASE}-run" \ cylc run \ "$NAME" \ + --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug diff --git a/t/rose-task-run/01-run-basic-iso/flow.cylc b/t/rose-task-run/01-run-basic-iso/flow.cylc index 0026b5a4ca..67f86a67fd 100644 --- a/t/rose-task-run/01-run-basic-iso/flow.cylc +++ b/t/rose-task-run/01-run-basic-iso/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode = True - abort if any task fails = True [[events]] abort on timeout = True timeout = PT2M diff --git a/t/rose-task-run/02-env-basic/flow.cylc b/t/rose-task-run/02-env-basic/flow.cylc index db616fbd1e..7bf0cebae0 100644 --- a/t/rose-task-run/02-env-basic/flow.cylc +++ b/t/rose-task-run/02-env-basic/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode = True - abort if any task fails = True [[events]] abort on timeout = True timeout = PT2M diff --git a/t/rose-task-run/03-env-basic-iso/flow.cylc b/t/rose-task-run/03-env-basic-iso/flow.cylc index 10518d4a83..535a0d18b0 100644 --- a/t/rose-task-run/03-env-basic-iso/flow.cylc +++ b/t/rose-task-run/03-env-basic-iso/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode = True - abort if any task fails = True [[events]] abort on timeout = True timeout = PT2M diff --git a/t/rose-task-run/04-run-path-empty.t b/t/rose-task-run/04-run-path-empty.t index 5e261dceb5..25e85d631c 100755 --- a/t/rose-task-run/04-run-path-empty.t +++ b/t/rose-task-run/04-run-path-empty.t @@ -37,6 +37,7 @@ run_pass "${TEST_KEY_BASE}-install" \ run_pass "${TEST_KEY_BASE}-run" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug diff --git a/t/rose-task-run/04-run-path-empty/flow.cylc b/t/rose-task-run/04-run-path-empty/flow.cylc index 717e782ce4..6a6f2e8504 100644 --- a/t/rose-task-run/04-run-path-empty/flow.cylc +++ b/t/rose-task-run/04-run-path-empty/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode = True - abort if any task fails = True [[events]] abort on timeout = True timeout = PT2M diff --git a/t/rose-task-run/06-app-prune-iso.t b/t/rose-task-run/06-app-prune-iso.t index c0e048b840..004e5b8844 100755 --- a/t/rose-task-run/06-app-prune-iso.t +++ b/t/rose-task-run/06-app-prune-iso.t @@ -49,6 +49,7 @@ run_pass "${TEST_KEY_BASE}-install" \ run_pass "${TEST_KEY_BASE}-run" \ cylc run \ "$NAME" \ + --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug diff --git a/t/rose-task-run/06-app-prune-iso/flow.cylc b/t/rose-task-run/06-app-prune-iso/flow.cylc index 5a866cadb0..d9d2ba59fa 100644 --- a/t/rose-task-run/06-app-prune-iso/flow.cylc +++ b/t/rose-task-run/06-app-prune-iso/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode = True - abort if any task fails = True [[events]] abort on timeout = True timeout = PT1M diff --git a/t/rose-task-run/07-app-arch.t b/t/rose-task-run/07-app-arch.t index 668aaa1650..a9b4469657 100755 --- a/t/rose-task-run/07-app-arch.t +++ b/t/rose-task-run/07-app-arch.t @@ -40,6 +40,7 @@ TEST_KEY="${TEST_KEY_BASE}-run" run_pass "$TEST_KEY" \ cylc run \ "$NAME" \ + --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug diff --git a/t/rose-task-run/07-app-arch/flow.cylc b/t/rose-task-run/07-app-arch/flow.cylc index 55c6a6cfc2..0787739379 100644 --- a/t/rose-task-run/07-app-arch/flow.cylc +++ b/t/rose-task-run/07-app-arch/flow.cylc @@ -2,7 +2,6 @@ {% set bad_nums=["1", "2", "3", "4", "5", "6", "7", "8", "9"] %} [cylc] UTC mode=True - abort if any task fails=True [[events]] abort on timeout=True timeout=PT1M diff --git a/t/rose-task-run/08-app-fcm-make.t b/t/rose-task-run/08-app-fcm-make.t index 756c410be6..f8a6671b29 100755 --- a/t/rose-task-run/08-app-fcm-make.t +++ b/t/rose-task-run/08-app-fcm-make.t @@ -54,6 +54,7 @@ run_pass "${TEST_KEY_BASE}-install" \ run_pass "${TEST_KEY_BASE}-run" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host='localhost' \ --no-detach \ --debug diff --git a/t/rose-task-run/08-app-fcm-make/flow.cylc b/t/rose-task-run/08-app-fcm-make/flow.cylc index d555cd102b..d7e98a3eed 100644 --- a/t/rose-task-run/08-app-fcm-make/flow.cylc +++ b/t/rose-task-run/08-app-fcm-make/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode=True - abort if any task fails=True [[events]] abort on timeout=True timeout=PT5M diff --git a/t/rose-task-run/09-app-prune-work.t b/t/rose-task-run/09-app-prune-work.t index 710a0e93a9..c8d7c073b9 100755 --- a/t/rose-task-run/09-app-prune-work.t +++ b/t/rose-task-run/09-app-prune-work.t @@ -39,6 +39,7 @@ TEST_KEY="${TEST_KEY_BASE}-run" run_pass "$TEST_KEY" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug diff --git a/t/rose-task-run/09-app-prune-work/flow.cylc b/t/rose-task-run/09-app-prune-work/flow.cylc index e7813e2be8..917c352398 100644 --- a/t/rose-task-run/09-app-prune-work/flow.cylc +++ b/t/rose-task-run/09-app-prune-work/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode=True - abort if any task fails=True [[events]] abort on timeout=True timeout=PT1M diff --git a/t/rose-task-run/10-specify-cycle.t b/t/rose-task-run/10-specify-cycle.t index 682b2bc9da..76d20292a4 100755 --- a/t/rose-task-run/10-specify-cycle.t +++ b/t/rose-task-run/10-specify-cycle.t @@ -38,6 +38,7 @@ run_pass "${TEST_KEY_BASE}-install" \ run_pass "${TEST_KEY_BASE}-run" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug diff --git a/t/rose-task-run/10-specify-cycle/flow.cylc b/t/rose-task-run/10-specify-cycle/flow.cylc index dbc8660c52..c1cbbcffd0 100644 --- a/t/rose-task-run/10-specify-cycle/flow.cylc +++ b/t/rose-task-run/10-specify-cycle/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode=True - abort if any task fails = True [[events]] abort on timeout = True timeout=PT1M diff --git a/t/rose-task-run/11-specify-cycle-iso.t b/t/rose-task-run/11-specify-cycle-iso.t index db1d0ee77d..8580ddef82 100755 --- a/t/rose-task-run/11-specify-cycle-iso.t +++ b/t/rose-task-run/11-specify-cycle-iso.t @@ -38,6 +38,7 @@ run_pass "${TEST_KEY_BASE}-install" \ run_pass "${TEST_KEY_BASE}-run" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug diff --git a/t/rose-task-run/11-specify-cycle-iso/flow.cylc b/t/rose-task-run/11-specify-cycle-iso/flow.cylc index 6a7b1305aa..cef73734ac 100644 --- a/t/rose-task-run/11-specify-cycle-iso/flow.cylc +++ b/t/rose-task-run/11-specify-cycle-iso/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode=True - abort if any task fails=True [[events]] abort on timeout=True timeout=PT1M diff --git a/t/rose-task-run/12-app-prune-integer.t b/t/rose-task-run/12-app-prune-integer.t index c879489e79..1b9c8415cf 100755 --- a/t/rose-task-run/12-app-prune-integer.t +++ b/t/rose-task-run/12-app-prune-integer.t @@ -54,6 +54,7 @@ TEST_KEY="${TEST_KEY_BASE}-run" run_pass "${TEST_KEY}" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug diff --git a/t/rose-task-run/12-app-prune-integer/flow.cylc b/t/rose-task-run/12-app-prune-integer/flow.cylc index 803c65b538..ff953af4fd 100644 --- a/t/rose-task-run/12-app-prune-integer/flow.cylc +++ b/t/rose-task-run/12-app-prune-integer/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode = True - abort if any task fails = True [[events]] abort on timeout = True timeout = PT1M diff --git a/t/rose-task-run/13-app-arch-cmd-out.t b/t/rose-task-run/13-app-arch-cmd-out.t index 9921f0026f..13bdbae419 100755 --- a/t/rose-task-run/13-app-arch-cmd-out.t +++ b/t/rose-task-run/13-app-arch-cmd-out.t @@ -39,6 +39,7 @@ run_pass "${TEST_KEY_BASE}-install" \ run_pass "${TEST_KEY_BASE}-run" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug diff --git a/t/rose-task-run/13-app-arch-cmd-out/flow.cylc b/t/rose-task-run/13-app-arch-cmd-out/flow.cylc index 6218a9ec50..36e28dcd07 100644 --- a/t/rose-task-run/13-app-arch-cmd-out/flow.cylc +++ b/t/rose-task-run/13-app-arch-cmd-out/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode=True - abort if any task fails=True [[events]] abort on timeout=True timeout=PT1M diff --git a/t/rose-task-run/14-app-prune-remove.t b/t/rose-task-run/14-app-prune-remove.t index 23386c067b..aba9184040 100755 --- a/t/rose-task-run/14-app-prune-remove.t +++ b/t/rose-task-run/14-app-prune-remove.t @@ -41,6 +41,7 @@ TEST_KEY="${TEST_KEY_BASE}-run" run_pass "$TEST_KEY" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug diff --git a/t/rose-task-run/14-app-prune-remove/flow.cylc b/t/rose-task-run/14-app-prune-remove/flow.cylc index 0eb0a0213d..733f1b5b7c 100644 --- a/t/rose-task-run/14-app-prune-remove/flow.cylc +++ b/t/rose-task-run/14-app-prune-remove/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode = True - abort if any task fails = True [[events]] abort on timeout = True timeout = PT1M diff --git a/t/rose-task-run/15-app-prune-extglob.t b/t/rose-task-run/15-app-prune-extglob.t index 079b631710..d4748f35c9 100755 --- a/t/rose-task-run/15-app-prune-extglob.t +++ b/t/rose-task-run/15-app-prune-extglob.t @@ -38,6 +38,7 @@ TEST_KEY="${TEST_KEY_BASE}-run" run_pass "${TEST_KEY}" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host='localhost' \ --no-detach \ --debug diff --git a/t/rose-task-run/15-app-prune-extglob/flow.cylc b/t/rose-task-run/15-app-prune-extglob/flow.cylc index 248885b806..0c0607c9eb 100644 --- a/t/rose-task-run/15-app-prune-extglob/flow.cylc +++ b/t/rose-task-run/15-app-prune-extglob/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode=True - abort if any task fails = True [[events]] abort on timeout = True timeout=PT1M diff --git a/t/rose-task-run/16-app-prune-point.t b/t/rose-task-run/16-app-prune-point.t index c9f3f6854f..a4ded109f6 100755 --- a/t/rose-task-run/16-app-prune-point.t +++ b/t/rose-task-run/16-app-prune-point.t @@ -41,6 +41,7 @@ TEST_KEY="${TEST_KEY_BASE}-run" run_pass "${TEST_KEY}" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host='localhost' \ --debug \ --no-detach diff --git a/t/rose-task-run/16-app-prune-point/flow.cylc b/t/rose-task-run/16-app-prune-point/flow.cylc index 76e9de7701..3b332ad652 100644 --- a/t/rose-task-run/16-app-prune-point/flow.cylc +++ b/t/rose-task-run/16-app-prune-point/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode=True - abort if any task fails=True [[events]] abort on timeout=True timeout=PT1M diff --git a/t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t b/t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t index fe45841eaf..e98b057052 100755 --- a/t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t +++ b/t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t @@ -39,6 +39,7 @@ TEST_KEY="${TEST_KEY_BASE}-run" run_pass "${TEST_KEY}" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host='localhost' \ --debug \ --no-detach diff --git a/t/rose-task-run/17-app-prune-glob-as-cycle-fmt/flow.cylc b/t/rose-task-run/17-app-prune-glob-as-cycle-fmt/flow.cylc index f72a4962db..2cc6bf9b9c 100644 --- a/t/rose-task-run/17-app-prune-glob-as-cycle-fmt/flow.cylc +++ b/t/rose-task-run/17-app-prune-glob-as-cycle-fmt/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode=True - abort if any task fails=True [[events]] abort on timeout=True timeout=PT1M diff --git a/t/rose-task-run/18-app-fcm-make-ctx-name.t b/t/rose-task-run/18-app-fcm-make-ctx-name.t index 880899db86..da7560b738 100755 --- a/t/rose-task-run/18-app-fcm-make-ctx-name.t +++ b/t/rose-task-run/18-app-fcm-make-ctx-name.t @@ -61,6 +61,7 @@ run_pass "${TEST_KEY_BASE}-run" \ timeout 120 \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host='localhost' \ --no-detach \ --debug diff --git a/t/rose-task-run/18-app-fcm-make-ctx-name/flow.cylc b/t/rose-task-run/18-app-fcm-make-ctx-name/flow.cylc index 33119f068c..8f02021fca 100644 --- a/t/rose-task-run/18-app-fcm-make-ctx-name/flow.cylc +++ b/t/rose-task-run/18-app-fcm-make-ctx-name/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode = True - abort if any task fails = True [scheduling] [[dependencies]] graph = """hello-make => hello-make-bin => hello-run""" diff --git a/t/rose-task-run/20-app-fcm-make-dest.t b/t/rose-task-run/20-app-fcm-make-dest.t index 1470d56b1f..c7c82c690c 100755 --- a/t/rose-task-run/20-app-fcm-make-dest.t +++ b/t/rose-task-run/20-app-fcm-make-dest.t @@ -65,6 +65,7 @@ run_pass "${TEST_KEY_BASE}-run" \ timeout 120 \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --no-detach \ --debug \ --host='localhost' diff --git a/t/rose-task-run/20-app-fcm-make-dest/flow.cylc b/t/rose-task-run/20-app-fcm-make-dest/flow.cylc index 1ea6092e94..5128a9b97b 100644 --- a/t/rose-task-run/20-app-fcm-make-dest/flow.cylc +++ b/t/rose-task-run/20-app-fcm-make-dest/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode = True - abort if any task fails = True [scheduling] [[dependencies]] graph = """hello-make => hello-make-bin => hello-run""" diff --git a/t/rose-task-run/24-app-fcm-make-fast.t b/t/rose-task-run/24-app-fcm-make-fast.t index d049bc9ddc..23b7420351 100755 --- a/t/rose-task-run/24-app-fcm-make-fast.t +++ b/t/rose-task-run/24-app-fcm-make-fast.t @@ -51,6 +51,7 @@ run_pass "${TEST_KEY_BASE}-run" \ timeout 120 \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host='localhost' \ --no-detach \ --debug diff --git a/t/rose-task-run/24-app-fcm-make-fast/flow.cylc b/t/rose-task-run/24-app-fcm-make-fast/flow.cylc index 42516edbe7..32ae0b3550 100644 --- a/t/rose-task-run/24-app-fcm-make-fast/flow.cylc +++ b/t/rose-task-run/24-app-fcm-make-fast/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode = True - abort if any task fails = True [scheduling] [[dependencies]] graph = """hello-make => hello-make-bin => hello-run""" diff --git a/t/rose-task-run/25-app-fcm-make-new-mode.t b/t/rose-task-run/25-app-fcm-make-new-mode.t index c719081995..b262392818 100755 --- a/t/rose-task-run/25-app-fcm-make-new-mode.t +++ b/t/rose-task-run/25-app-fcm-make-new-mode.t @@ -51,6 +51,7 @@ run_pass "${TEST_KEY_BASE}-run" \ timeout 120 \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host='localhost' \ --no-detach \ --debug diff --git a/t/rose-task-run/25-app-fcm-make-new-mode/flow.cylc b/t/rose-task-run/25-app-fcm-make-new-mode/flow.cylc index fb74b450d6..7cd1a3c5cd 100644 --- a/t/rose-task-run/25-app-fcm-make-new-mode/flow.cylc +++ b/t/rose-task-run/25-app-fcm-make-new-mode/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode = True - abort if any task fails = True [scheduling] [[dependencies]] graph = """hello-make => hello-run""" diff --git a/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t b/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t index 5414a4fc05..379ccc2e9a 100755 --- a/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t +++ b/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t @@ -65,6 +65,7 @@ run_pass "${TEST_KEY_BASE}-run" \ timeout 120 \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host='localhost' \ -S "HOST='${JOB_HOST}'" \ --no-detach \ diff --git a/t/rose-task-run/26-app-fcm-make-new-mode-with-cont/flow.cylc b/t/rose-task-run/26-app-fcm-make-new-mode-with-cont/flow.cylc index 9be2702803..90524113b4 100644 --- a/t/rose-task-run/26-app-fcm-make-new-mode-with-cont/flow.cylc +++ b/t/rose-task-run/26-app-fcm-make-new-mode-with-cont/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode = True - abort if any task fails = True [scheduling] [[dependencies]] graph = """hello-make => hello-make-bin => hello-run""" diff --git a/t/rose-task-run/28-env-path-run-path.t b/t/rose-task-run/28-env-path-run-path.t index 5594b75f70..3c2ccba95d 100755 --- a/t/rose-task-run/28-env-path-run-path.t +++ b/t/rose-task-run/28-env-path-run-path.t @@ -38,6 +38,7 @@ TEST_KEY="${TEST_KEY_BASE}-run" run_pass "${TEST_KEY}" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host='localhost' \ --no-detach \ --debug diff --git a/t/rose-task-run/28-env-path-run-path/flow.cylc b/t/rose-task-run/28-env-path-run-path/flow.cylc index dd4ccce23d..8529f9497a 100644 --- a/t/rose-task-run/28-env-path-run-path/flow.cylc +++ b/t/rose-task-run/28-env-path-run-path/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode = True - abort if any task fails = True [[events]] abort on timeout = True timeout = PT1M diff --git a/t/rose-task-run/30-app-arch-opt-source.t b/t/rose-task-run/30-app-arch-opt-source.t index 1ca960222a..b0363a10d2 100755 --- a/t/rose-task-run/30-app-arch-opt-source.t +++ b/t/rose-task-run/30-app-arch-opt-source.t @@ -39,6 +39,7 @@ run_pass "${TEST_KEY_BASE}-install" \ run_pass "${TEST_KEY_BASE}-run" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug diff --git a/t/rose-task-run/30-app-arch-opt-source/flow.cylc b/t/rose-task-run/30-app-arch-opt-source/flow.cylc index 26c39cdcbf..a17bcca0d1 100644 --- a/t/rose-task-run/30-app-arch-opt-source/flow.cylc +++ b/t/rose-task-run/30-app-arch-opt-source/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode=True - abort if any task fails = True [[events]] timeout=PT1M abort on timeout=True @@ -18,16 +17,16 @@ archive2 execution time limit=PT1M [[archive1]] script=""" -echo 'MMXIV' >'2014.txt' -echo 'MMXVI' >'2016.txt' -rose task-run --debug -""" + echo 'MMXIV' >'2014.txt' + echo 'MMXVI' >'2016.txt' + rose task-run --debug + """ [[archive2]] script=""" -if ((${CYLC_TASK_TRY_NUMBER} > 1)); then - echo 'MMXV' >'2015.txt' -fi -rose task-run --debug -""" + if ((${CYLC_TASK_TRY_NUMBER} > 1)); then + echo 'MMXV' >'2015.txt' + fi + rose task-run --debug + """ [[[job]]] execution retry delays = PT0S diff --git a/t/rose-task-run/32-app-arch-compressed.t b/t/rose-task-run/32-app-arch-compressed.t index cc1d7309bd..548a45a0c8 100755 --- a/t/rose-task-run/32-app-arch-compressed.t +++ b/t/rose-task-run/32-app-arch-compressed.t @@ -39,6 +39,7 @@ run_pass "${TEST_KEY_BASE}-install" \ run_pass "${TEST_KEY_BASE}-run" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug diff --git a/t/rose-task-run/32-app-arch-compressed/flow.cylc b/t/rose-task-run/32-app-arch-compressed/flow.cylc index 1b0891c649..8a8d771596 100644 --- a/t/rose-task-run/32-app-arch-compressed/flow.cylc +++ b/t/rose-task-run/32-app-arch-compressed/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode=True - abort if any task fails = True [[events]] timeout=PT1M abort on timeout=True diff --git a/t/rose-task-run/33-app-prune-cycle-format.t b/t/rose-task-run/33-app-prune-cycle-format.t index 0fae8e8b37..676c969a2d 100755 --- a/t/rose-task-run/33-app-prune-cycle-format.t +++ b/t/rose-task-run/33-app-prune-cycle-format.t @@ -39,6 +39,7 @@ TEST_KEY="${TEST_KEY_BASE}-run" run_pass "${TEST_KEY}" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host='localhost' \ --debug \ --no-detach diff --git a/t/rose-task-run/33-app-prune-cycle-format/flow.cylc b/t/rose-task-run/33-app-prune-cycle-format/flow.cylc index 858ff63dde..f76e960015 100644 --- a/t/rose-task-run/33-app-prune-cycle-format/flow.cylc +++ b/t/rose-task-run/33-app-prune-cycle-format/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode=True - abort if any task fails=True [[events]] abort on timeout=True timeout=PT1M diff --git a/t/rose-task-run/34-app-prune-hosts-sharing-fs.t b/t/rose-task-run/34-app-prune-hosts-sharing-fs.t index b555f5d136..25eaea23b8 100755 --- a/t/rose-task-run/34-app-prune-hosts-sharing-fs.t +++ b/t/rose-task-run/34-app-prune-hosts-sharing-fs.t @@ -50,6 +50,7 @@ TEST_KEY="${TEST_KEY_BASE}-run" run_pass "${TEST_KEY}" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host='localhost' \ --debug \ --no-detach diff --git a/t/rose-task-run/34-app-prune-hosts-sharing-fs/flow.cylc b/t/rose-task-run/34-app-prune-hosts-sharing-fs/flow.cylc index bb8a8a44f1..924a00a5bf 100644 --- a/t/rose-task-run/34-app-prune-hosts-sharing-fs/flow.cylc +++ b/t/rose-task-run/34-app-prune-hosts-sharing-fs/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode=True - abort if any task fails=True [[events]] abort on timeout=True timeout=PT1M diff --git a/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t b/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t index 4359ebc492..2d526fa850 100755 --- a/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t +++ b/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t @@ -50,6 +50,7 @@ TEST_KEY="${TEST_KEY_BASE}-run" run_pass "${TEST_KEY}" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host='localhost' \ --debug \ --no-detach diff --git a/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs/flow.cylc b/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs/flow.cylc index 857df13ff2..b673dbf974 100644 --- a/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs/flow.cylc +++ b/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode=True - abort if any task fails=True [[events]] abort on timeout=True timeout=PT1M diff --git a/t/rose-task-run/37-app-bunch-rm-old.t b/t/rose-task-run/37-app-bunch-rm-old.t index d4f06f8f41..299af3d1fc 100755 --- a/t/rose-task-run/37-app-bunch-rm-old.t +++ b/t/rose-task-run/37-app-bunch-rm-old.t @@ -39,6 +39,7 @@ TEST_KEY="${TEST_KEY_BASE}-run" run_pass "$TEST_KEY" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug @@ -76,6 +77,7 @@ rm -rf "${HOME}/cylc-run/${NAME}/.serivce/db" run_pass "$TEST_KEY" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug diff --git a/t/rose-task-run/37-app-bunch-rm-old/flow.cylc b/t/rose-task-run/37-app-bunch-rm-old/flow.cylc index 9c9468ed0b..58a33c3451 100644 --- a/t/rose-task-run/37-app-bunch-rm-old/flow.cylc +++ b/t/rose-task-run/37-app-bunch-rm-old/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode=True - abort if any task fails=True [[events]] abort on timeout=True timeout=PT1M diff --git a/t/rose-task-run/38-app-bunch-counts.t b/t/rose-task-run/38-app-bunch-counts.t index 62c91320bf..e7ef272709 100755 --- a/t/rose-task-run/38-app-bunch-counts.t +++ b/t/rose-task-run/38-app-bunch-counts.t @@ -39,6 +39,7 @@ TEST_KEY="${TEST_KEY_BASE}-run" run_pass "$TEST_KEY" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug diff --git a/t/rose-task-run/38-app-bunch-counts/flow.cylc b/t/rose-task-run/38-app-bunch-counts/flow.cylc index b415b77a82..7df605ae7f 100644 --- a/t/rose-task-run/38-app-bunch-counts/flow.cylc +++ b/t/rose-task-run/38-app-bunch-counts/flow.cylc @@ -1,6 +1,5 @@ [cylc] UTC mode = True - abort if any task fails = True [[events]] abort on timeout = True timeout = PT1M diff --git a/t/rose-task-run/39-app-prune-cycle-host.t b/t/rose-task-run/39-app-prune-cycle-host.t index 670fed19be..12d7ed1f45 100755 --- a/t/rose-task-run/39-app-prune-cycle-host.t +++ b/t/rose-task-run/39-app-prune-cycle-host.t @@ -49,6 +49,7 @@ TEST_KEY="${TEST_KEY_BASE}-run" run_pass "${TEST_KEY}" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host='localhost' \ --debug \ --no-detach diff --git a/t/rose-task-run/39-app-prune-cycle-host/flow.cylc b/t/rose-task-run/39-app-prune-cycle-host/flow.cylc index 738ca517a0..fdf378652d 100644 --- a/t/rose-task-run/39-app-prune-cycle-host/flow.cylc +++ b/t/rose-task-run/39-app-prune-cycle-host/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode=True - abort if any task fails=True [scheduling] initial cycle point=1970 final cycle point=1990 diff --git a/t/rose-task-run/41-app-bunch-default-command.t b/t/rose-task-run/41-app-bunch-default-command.t index 27fde1dfa9..060b92bfbc 100755 --- a/t/rose-task-run/41-app-bunch-default-command.t +++ b/t/rose-task-run/41-app-bunch-default-command.t @@ -39,6 +39,7 @@ TEST_KEY="${TEST_KEY_BASE}-run" run_pass "$TEST_KEY" \ cylc run \ "${NAME}" \ + --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug @@ -79,9 +80,10 @@ rm -rf "${HOME}/cylc-run/${NAME}/log" rm -rf "${HOME}/cylc-run/${NAME}/.service/db" run_pass "$TEST_KEY" \ cylc run "${NAME}" \ - --host=localhost \ - --no-detach \ - --debug + --abort-if-any-task-fails \ + --host=localhost \ + --no-detach \ + --debug #------------------------------------------------------------------------------- # Testing successful rerun #------------------------------------------------------------------------------- diff --git a/t/rose-task-run/41-app-bunch-default-command/flow.cylc b/t/rose-task-run/41-app-bunch-default-command/flow.cylc index 8cf242c3ba..3a6e355859 100644 --- a/t/rose-task-run/41-app-bunch-default-command/flow.cylc +++ b/t/rose-task-run/41-app-bunch-default-command/flow.cylc @@ -1,7 +1,6 @@ #!jinja2 [cylc] UTC mode=True - abort if any task fails=True [[events]] abort on timeout=True timeout=PT1M From 3ddd36d057bc17d924d53828b60728688ab5434a Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Fri, 22 Jan 2021 13:00:18 +0000 Subject: [PATCH 42/78] actions: install fcm --- .github/workflows/test.yml | 142 ++++++++++++++---- .../30-app-arch-opt-source/bin/foo | 1 + 2 files changed, 111 insertions(+), 32 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index aa5e653689..56ad746925 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,10 +4,30 @@ on: pull_request: workflow_dispatch: inputs: - branch: - description: The branch to open the PR against + rose-branch: + description: The Rose branch to test against required: false default: 'master' + fcm-branch: + description: The FCM branch to test against + required: false + default: 'master' + fcm-repo: + description: The FCM branch to test against + required: false + default: 'metomi/fcm' + cylc-branch: + description: The Cylc branch to test against + required: false + default: 'master' + cylc-repo: + description: The Cylc repo to test against + required: false + default: 'cylc/cylc-flow' + +defaults: + run: + shell: bash # macos default shell is zsh jobs: test: @@ -25,40 +45,33 @@ jobs: - name: Checkout uses: actions/checkout@v2 with: - ref: ${{ github.event.inputs.branch }} + ref: ${{ github.event.inputs.rose-branch }} + path: rose - name: Configure Python uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} - - name: Install + - name: Install Cylc + env: + cylc_repo: ${{ github.events.inputs.cylc-repo }} + cylc_branch: ${{ github.events.inputs.cylc-branch }} run: | # temp: use cylc-install branch - pip install git+https://github.com/datamel/cylc-flow@cylc-install - # TODO: remove editable mode - # there are other Rose files (e.g. meta-all/rose-meta.conf) - # that are not yet installed as part of the Python package - # which need to be moved into metomi/rose/etc/ - pip install ."[all]" - pip install --no-deps git+https://github.com/cylc/cylc-rose.git - yarn install + cylc_repo='datamel/cylc-flow' + cylc_branch='cylc-install' + pip install "git+https://github.com/$cylc_repo@$cylc_branch" - name: Brew Install if: startsWith(matrix.os, 'macos') run: | - # apply DNS patch - hostuserutil="$(python3 -c ' - import cylc.flow.hostuserutil - print(cylc.flow.hostuserutil.__file__) - ')" - patch "${hostuserutil}" < etc/conf/macos-patch - + which python # install system deps #brew update - brew install bash coreutils gnu-sed shellcheck sqlite3 + brew install bash coreutils gnu-sed shellcheck sqlite3 subversion - # add GNU coreutils and sed to the user PATH + # add GNU coreutils and sed to the user PATH (for actions steps) # (see instructions in brew install output) echo \ "$(brew --prefix)/opt/coreutils/libexec/gnubin" \ @@ -68,19 +81,73 @@ jobs: >> "${GITHUB_PATH}" # add GNU coreutils and sed to the user PATH (for Cylc jobs) - # (see instructions in brew install output) cat >> "$HOME/.bashrc" <<__HERE__ - PATH="$(brew --prefix)/opt/coreutils/libexec/gnubin:$PATH" - PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH" + PATH="/usr/local/opt/coreutils/libexec/gnubin:\$PATH" + PATH="/usr/local/opt/gnu-sed/libexec/gnubin:\$PATH" + PATH="$pythonLocation:\$PATH" + export PATH + # see NOTE in t/rosie-lookup/00-basic.t + export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES __HERE__ + cat "$HOME/.bashrc" + which python + + - name: Brew Install post + if: startsWith(matrix.os, 'macos') + run: | + set -x + which bash + which python + which python2 + which python3 + set +x + cat > mess <<__HERE__ + #!/usr/bin/env bash + set -x + which bash + which python + which python2 + which python3 + set +x + __HERE__ + chmod +x mess + ./mess - name: Apt-Get Install if: startsWith(matrix.os, 'ubuntu') run: | sudo apt-get update - sudo apt-get install -y shellcheck sqlite3 + sudo apt-get install -y shellcheck sqlite3 at + + - name: MacOS DNS Patch + if: startsWith(matrix.os, 'macos') + run: | + # apply DNS patch + hostuserutil="$(python3 -c ' + import cylc.flow.hostuserutil + print(cylc.flow.hostuserutil.__file__) + ')" + patch "${hostuserutil}" < rose/etc/conf/macos-patch + + - name: Install Rose + working-directory: rose + run: | + pip install ."[all]" + pip install --no-deps git+https://github.com/cylc/cylc-rose.git + yarn install + + - name: Checkout FCM + if: startsWith(matrix.os, 'ubuntu') + uses: actions/checkout@v2 + with: + repository: ${{ github.event.inputs.fcm-repo }} + ref: ${{ github.event.inputs.fcm-branch }} + path: 'fcm' - # old stuff, not sure if all needed + - name: Install FCM + if: startsWith(matrix.os, 'ubuntu') + run: | + # install FCM deps sudo apt-get install -y \ subversion \ build-essential \ @@ -90,32 +157,43 @@ jobs: libdbi-perl \ libdbd-sqlite3-perl - # yet more old stuff, not sure if needed - sudo apt-get install -y at + # install wandisco sudo sh -c 'echo "deb http://opensource.wandisco.com/ubuntu \ - `lsb_release -cs` svn19" >> /etc/apt/sources.list.d/subversion19.list' - sudo wget -q http://opensource.wandisco.com/wandisco-debian.gpg -O- | \ - sudo apt-key add - + `lsb_release -cs` svn19" \ + >> /etc/apt/sources.list.d/subversion19.list' + sudo wget -q http://opensource.wandisco.com/wandisco-debian.gpg -O- \ + | sudo apt-key add - + + # prepend FCM bin to $PATH + FCM_PATH="$GITHUB_WORKSPACE/fcm/bin" + # the github actions way (needed for cylc jobs) + echo "$FCM_PATH" >> "${GITHUB_PATH}" + # the bashrc wat (needed for subsequent gh action steps) + echo "export PATH=\"$FCM_PATH:\$PATH\"" >> "$HOME/.bashrc" - name: Style + working-directory: rose run: | flake8 etc/bin/shellchecker yarn run lint - - name: Tests + - name: Unit Tests + working-directory: rose run: | pytest - name: Functional Tests timeout-minutes: 30 id: functest + working-directory: rose run: | # rose tests should pass first time around etc/bin/rose-test-battery -j 4 --state=save - name: Re-Run Fails if: failure() && steps.functest.outcome == 'failure' + working-directory: rose run: | # so we only re-run for debug purposes cylc scan --state=all --color=never diff --git a/t/rose-task-run/30-app-arch-opt-source/bin/foo b/t/rose-task-run/30-app-arch-opt-source/bin/foo index 9afc5948f3..6c3a5e2b67 100755 --- a/t/rose-task-run/30-app-arch-opt-source/bin/foo +++ b/t/rose-task-run/30-app-arch-opt-source/bin/foo @@ -18,5 +18,6 @@ # along with Rose. If not, see . #------------------------------------------------------------------------------- set -eu +env >&2 mkdir -p "$1" exec install -t "$@" From 55b6cef865997780eb237e30687080def1f16ecc Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Fri, 22 Jan 2021 16:06:07 +0000 Subject: [PATCH 43/78] input defaults -> hardcoded defaults --- .github/workflows/test.yml | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 56ad746925..8d60a28865 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,26 +4,21 @@ on: pull_request: workflow_dispatch: inputs: - rose-branch: + rose-ref: description: The Rose branch to test against - required: false - default: 'master' - fcm-branch: + required: true + fcm-ref: description: The FCM branch to test against required: false - default: 'master' fcm-repo: description: The FCM branch to test against required: false - default: 'metomi/fcm' - cylc-branch: + cylc-ref: description: The Cylc branch to test against required: false - default: 'master' cylc-repo: description: The Cylc repo to test against required: false - default: 'cylc/cylc-flow' defaults: run: @@ -45,7 +40,7 @@ jobs: - name: Checkout uses: actions/checkout@v2 with: - ref: ${{ github.event.inputs.rose-branch }} + ref: ${{ github.event.inputs.rose-ref }} path: rose - name: Configure Python @@ -55,8 +50,8 @@ jobs: - name: Install Cylc env: - cylc_repo: ${{ github.events.inputs.cylc-repo }} - cylc_branch: ${{ github.events.inputs.cylc-branch }} + cylc_repo: ${{ github.events.inputs.cylc-repo || 'cylc/cylc-flow' }} + cylc_branch: ${{ github.events.inputs.cylc-ref || 'master' }} run: | # temp: use cylc-install branch cylc_repo='datamel/cylc-flow' @@ -140,8 +135,8 @@ jobs: if: startsWith(matrix.os, 'ubuntu') uses: actions/checkout@v2 with: - repository: ${{ github.event.inputs.fcm-repo }} - ref: ${{ github.event.inputs.fcm-branch }} + repository: ${{ github.event.inputs.fcm-repo || 'metomi/fcm' }} + ref: ${{ github.event.inputs.fcm-ref || 'master' }} path: 'fcm' - name: Install FCM @@ -213,7 +208,7 @@ jobs: - name: Checkout uses: actions/checkout@v2 with: - ref: ${{ github.event.inputs.branch }} + ref: ${{ github.event.inputs.rose-ref }} - name: Configure Python uses: actions/setup-python@v1 From b878e3ab017ccacbbc7df9ae1d7d90dcb923237d Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Fri, 22 Jan 2021 17:10:14 +0000 Subject: [PATCH 44/78] rose: add version --long option --- bin/rose | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/bin/rose b/bin/rose index 9b6ad55a7d..a1e609ea4b 100755 --- a/bin/rose +++ b/bin/rose @@ -28,6 +28,7 @@ # rose help # Print help and list available utilities # rose help UTIL ... # Print help for UTIL ... # rose version # Print version information +# rose version --long # Print more version information # # DESCRIPTION # Simple launcher for utilities in the "rose", "rosie" or "rosa" @@ -148,7 +149,13 @@ path_lead() { # Print Rose version function print_version() { - echo "Rose $ROSE_VERSION ($ROSE_HOME_BIN)" + for arg in "$@"; do + if [[ "$arg" == '--long' ]]; then + echo "Rose $ROSE_VERSION ($ROSE_HOME_BIN)" + return + fi + done + echo "$ROSE_VERSION" } #------------------------------------------------------------------------------- @@ -202,7 +209,7 @@ help|h|?|--help|-h) exit $RC :;; version|--version|-V) - print_version + print_version "$@" exit :;; doc) From 29dd2be859adce4992671cfe8f0428a0b6343215 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Wed, 27 Jan 2021 11:50:55 +0000 Subject: [PATCH 45/78] docs: replace rose make-docs with make configuration --- etc/bin/rose-make-docs | 290 ----------------------------------------- sphinx/Makefile | 3 +- 2 files changed, 2 insertions(+), 291 deletions(-) delete mode 100755 etc/bin/rose-make-docs diff --git a/etc/bin/rose-make-docs b/etc/bin/rose-make-docs deleted file mode 100755 index 1594ebbacd..0000000000 --- a/etc/bin/rose-make-docs +++ /dev/null @@ -1,290 +0,0 @@ -#!/usr/bin/env bash -#----------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#----------------------------------------------------------------------------- -# NAME -# rose make-docs -# -# SYNOPSIS -# rose make-docs [OPTIONS] [BUILD...] -# -# DESCRIPTION -# Build the rose documentation in the requested `BUILD` format(s). -# -# OPTIONS -# --venv -# Build virtualenv for temporarilly installing python dependencies -# if necessary. -# --dev -# Development mode, don't remove virtualenv after build. -# --strict -# Disable cache forcing a complete re-build and turn warnings into -# errors. -# --debug -# Run `make` with the --debug option. -# --default-version=VERSION -# By default the current version is symlinked as the default version, -# provide an alternative version to override this. -# -# BUILD -# The format(s) to build the documentation in - default html. -# Avaliable formats are listed in the sphinx documentation -# (http://www.sphinx-doc.org/en/stable/builders.html). -# The most commonly used formats are: -# -# `html` -# For building standalone HTML files. -# `singlehtml` -# For building a single page HTML document. -# `latexpdf` -# For building PDF documentation. -# `clean` -# Removes all built documentation for the current rose version. -# (use `rm -rf doc` to remove all documentation). -# -# DEVELOPMENT BUILDS -# For development purposes use the following BUILDs: -# -# `doctest` -# Runs any doctest examples present in documented python modules. -# `linkcheck` -# Checks external links. -# -# SOFTWARE ENVIRONMENT -# The software dependencies can be found by running the command:: -# -# $ rose check-software --docs -# -# Rose provides two ways of installing the Python dependencies for the Rose -# documentation builder: -# -# Virtual Environment: -# Rose will automatically build a Python virtual environment -# when the --venv flag is used with this command. The virtual -# environment will be created in rose/venv and will be destroyed -# after use. Use the --dev flag to prevent the environment being -# destroyed for future use. Example:: -# -# $ rose make-docs --venv --dev html # create and keep a virtualenv -# -# Conda -# An environment file for creating a conda env can be found in -# etc/rose-docs-env.yaml. Example usage:: -# -# $ conda env create -f etc/rose-docs-env.yaml # create conda env -# $ source activate rose-docs # activate conda env -# $ rose make-docs html # make docs as normal -#----------------------------------------------------------------------------- -set -e -set -o pipefail -shopt -s extglob - -# Move into rose directory -cd "$(dirname "$0")/../.." -ROSE_HOME=$PWD -# Path for virtualenv. -VENV_PATH='venv' # Note: update above docs when changing this value -# Set to `true` when the virtualenv is being used. -USING_VENV=false -# Path to the sphinx directory. -SPHINX_PATH=sphinx -# pick up official rose version -ROSE_VERSION="$(python3 -c "import metomi.rose; print(metomi.rose.__version__)")" -# documentation root output directory -DOCS_DIR="${ROSE_HOME}/doc" - -# is the virtualenv command available -if command -v virtualenv >/dev/null 2>&1; then - VENV_COMPLIANT=true -else - VENV_COMPLIANT=false -fi - -# Parse command line args. -VENV_MODE=false -FORCE=false -DEV_MODE=false -DEBUG='' -BUILDS=() -SPHINX_OPTS=() -DEFAULT_ALIAS='doc' -DEFAULT_VERSION= -while [[ $# -gt 0 ]]; do - case $1 in - --venv) - VENV_MODE=true - shift - ;; - --dev) - DEV_MODE=true - shift - ;; - --force) - FORCE=true - shift - ;; - --strict) - SPHINX_OPTS+=('SPHINXOPTS=-aEW') - shift - ;; - --debug) - DEBUG='--debug' - shift - ;; - --default-version) - DEFAULT_VERSION="$2" - shift - shift - ;; - *) - BUILDS+=("$1") - shift - ;; - esac -done -if [[ "${#BUILDS}" == 0 ]]; then - BUILDS=('html') -fi - - -venv-activate () { - USING_VENV=true - . "${VENV_PATH}/bin/activate" -} - -venv-install () { - venv-destroy - virtualenv --python=python3.7 "${VENV_PATH}" - venv-activate - pip install 'sphinx' - pip install 'sphinx_rtd_theme' - pip install 'sphinxcontrib-httpdomain' - pip install 'hieroglyph' -} - -venv-deactivate () { - deactivate >/dev/null 2>&1 || true -} - -venv-destroy () { - venv-deactivate - rm -rf "${VENV_PATH}" -} - -version_file () { - # output the dictionary {"version": ["build", ...]} in JSON format - DOCS_DIR="$1" - ret='{' - - # scrape filesystem for list of rose versions which have built docs - for version_dir in "${DOCS_DIR}/"*.*.*; do - version=$(basename "$version_dir") - # scrape filesystem to get list of formats this version is available in - ret+="\n \"$version\": [" - for format_dir in "${version_dir}/"!(doctrees|*.html); do - format=$(basename "$format_dir") - ret+="\"$format\", " - done - ret="${ret:0:-2}]," - done - - ret="${ret:0:-1}\n}" - echo -e "$ret" -} - -html_redirect () { - # write an html file to $2 which auto-redirects to the relative path $1 - SRC="$1" - DEST="$2" - - cat >"$DEST" << __HTML__ - - - - Rose Documentation - - - -

If not automatically redirected, please click - Rose Documentation.

- - -__HTML__ -} - - -# Use virtualenv if present and requested or if we are in development mode. -if "${DEV_MODE}" || "${VENV_MODE}"; then - if ! "${VENV_COMPLIANT}"; then - echo 'The virtualenv command is required for the --venv option.' - exit 1 - fi - if [[ -d "${VENV_PATH}" ]]; then - venv-activate - fi -fi - -# Check core (sphinx) builder. -if ! rose-check-software --doc >/dev/null; then - if "${VENV_MODE}"; then - venv-install - elif ! "${FORCE}"; then - echo 'Software required by the rose documentation builder is not -present (run "rose check-software --doc" for details). - -For information on building a Python environment for the Rose documentation -builder see the "Software Dependencies" section of the help page for this -command.' >&2 - exit 1 - fi -fi - -# makefile argument to set the output directory for this build -SPHINX_OPTS+=("BUILDDIR=${DOCS_DIR}/${ROSE_VERSION}") - -# run sphinx-build -if make ${DEBUG} -C "${SPHINX_PATH}" "${BUILDS[@]}" "${SPHINX_OPTS[@]}"; then - RET=0 - # output file containing details of all versions and formats the - # documentation has been built in (locally) for the version / format - # switching pane - version_file "${DOCS_DIR}" > "${DOCS_DIR}/versions.json" - # symlink this version as the default - ( - cd "${DOCS_DIR}" - rm "${DEFAULT_ALIAS}" 2>/dev/null || true - ln -s "${DEFAULT_VERSION:-$ROSE_VERSION}" "${DEFAULT_ALIAS}" - ) - # symlink landing pages - html_redirect "${DEFAULT_ALIAS}/html/index.html" 'doc/index.html' - html_redirect "html/index.html" "doc/${ROSE_VERSION}/index.html" - # support legacy doc/rose.html url - mkdir 'doc/doc' 2>/dev/null || true - html_redirect "html/index.html" "doc/${ROSE_VERSION}/rose.html" -else - RET=1 -fi - -# Remove virtualenv if used and if we are not in development mode. -if "${USING_VENV}"; then - if ! "${DEV_MODE}"; then - venv-destroy - fi -fi - -exit ${RET} diff --git a/sphinx/Makefile b/sphinx/Makefile index f922fc7cb3..198bbe5019 100644 --- a/sphinx/Makefile +++ b/sphinx/Makefile @@ -1,10 +1,11 @@ # Minimal makefile for Sphinx documentation # You can set these variables from the command line: +ROSE_VERSION = $(shell rose version) SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = -BUILDDIR = ../doc +BUILDDIR = ../doc/$(ROSE_VERSION) SOURCEDIR = . # Put it first so that "make" without argument is like "make help". From 2689ca14b1406568c0f09d65f0e94130ad78565a Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Wed, 27 Jan 2021 15:43:59 +0000 Subject: [PATCH 46/78] tests: centralised functionality for flow regs and purging * updates tests to work with latest cylc-install branch * centralises naming of flows * registers all test flows in a hierarchy (like cylc) * purges flows only if all subtests passed to leave files for debugging --- etc/bin/rose-test-battery | 3 ++ t/lib/bash/test_header | 30 ++++++++++++++ t/rose-ana/00-run-basic.t | 37 +++++++++-------- t/rose-ana/01-run-basic-v1.t | 26 +++++------- t/rose-task-env/00-non-cycle.t | 12 +++--- t/rose-task-env/01-integer-cycling.t | 23 +++++------ t/rose-task-env/02-360day-cycling.t | 11 +++-- t/rose-task-run/00-run-basic.t | 33 ++++++++------- t/rose-task-run/01-run-basic-iso.t | 37 +++++++++-------- t/rose-task-run/04-run-path-empty.t | 13 +++--- t/rose-task-run/06-app-prune-iso.t | 20 +++++----- t/rose-task-run/07-app-arch.t | 40 +++++++++---------- t/rose-task-run/08-app-fcm-make.t | 40 +++++++++---------- t/rose-task-run/09-app-prune-work.t | 12 +++--- t/rose-task-run/10-specify-cycle.t | 13 +++--- t/rose-task-run/11-specify-cycle-iso.t | 12 +++--- t/rose-task-run/12-app-prune-integer.t | 39 +++++++++--------- t/rose-task-run/13-app-arch-cmd-out.t | 18 ++++----- t/rose-task-run/14-app-prune-remove.t | 29 ++++++-------- t/rose-task-run/15-app-prune-extglob.t | 12 +++--- t/rose-task-run/16-app-prune-point.t | 12 +++--- .../17-app-prune-glob-as-cycle-fmt.t | 12 +++--- t/rose-task-run/18-app-fcm-make-ctx-name.t | 12 +++--- t/rose-task-run/20-app-fcm-make-dest.t | 16 ++++---- t/rose-task-run/24-app-fcm-make-fast.t | 33 ++++++++------- t/rose-task-run/25-app-fcm-make-new-mode.t | 29 +++++++------- .../26-app-fcm-make-new-mode-with-cont.t | 19 ++++----- t/rose-task-run/28-env-path-run-path.t | 12 +++--- t/rose-task-run/29-app-prune-extglob-remote.t | 12 +++--- t/rose-task-run/30-app-arch-opt-source.t | 33 ++++++++------- t/rose-task-run/31-app-bunch.t | 13 +++--- t/rose-task-run/32-app-arch-compressed.t | 15 ++++--- t/rose-task-run/33-app-prune-cycle-format.t | 12 +++--- .../34-app-prune-hosts-sharing-fs.t | 20 +++++----- .../35-app-prune-log-on-hosts-sharing-fs.t | 20 +++++----- t/rose-task-run/36-app-arch-interrupted.t | 17 ++++---- t/rose-task-run/37-app-bunch-rm-old.t | 20 +++++----- t/rose-task-run/38-app-bunch-counts.t | 13 +++--- t/rose-task-run/39-app-prune-cycle-host.t | 10 ++--- t/rose-task-run/40-app-arch-duplicate.t | 13 +++--- .../41-app-bunch-default-command.t | 23 +++++------ t/rosie-id/00-basic.t | 9 ++--- 42 files changed, 399 insertions(+), 436 deletions(-) diff --git a/etc/bin/rose-test-battery b/etc/bin/rose-test-battery index 9e01640e2b..a2aebb2b67 100755 --- a/etc/bin/rose-test-battery +++ b/etc/bin/rose-test-battery @@ -58,6 +58,9 @@ cd "$TESTDIR/../../" mkdir -p ~/.metomi mkdir -p "${HOME}/cylc-run" +ROSE_TEST_TIME_INIT="$(date -u +'%Y%m%dT%H%M%SZ')" +export ROSE_TEST_TIME_INIT + # Recompile *.pyc files to ensure we are running the current code. # @TODO Consider if this is appropriate in new version if [[ -w 'metomi/' ]]; then diff --git a/t/lib/bash/test_header b/t/lib/bash/test_header index 36bc04b15e..8f636d1299 100644 --- a/t/lib/bash/test_header +++ b/t/lib/bash/test_header @@ -96,6 +96,13 @@ # array index. It can also be a dict {attr_key: attr_value, ...}. In # which case, the expected data item is under a list of dicts, where a # unique dict in the list contains all elements attr_key: attr_value. +# get_reg +# Generates a unique Cylc registration name. +# exports FLOW, FLOW_RUN_DIR +# purge [FLOW] +# Runs `cylc clean` on the provided reg (else uses the $FLOW env var) +# only if none of the subtests failed. Otherwise it passes so that +# the test files are left behind for debugging. # # VARIABLES # TEST_DIR @@ -128,6 +135,7 @@ trap 'test_finally EXIT' 'EXIT' trap 'test_finally INT' 'INT' TEST_NUMBER=0 +FAILURES=0 tests() { echo "1..$1" @@ -153,6 +161,7 @@ pass() { fail() { echo "not ok $((++TEST_NUMBER)) - $*" + ((++FAILURES)) } run_pass() { @@ -442,6 +451,27 @@ __PYTHON__ fi } +get_reg () { + local FLOW_UID + FLOW_UID="$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c6)" + FLOW="rtb.$ROSE_TEST_TIME_INIT/${TEST_KEY_BASE}/${FLOW_UID}" + FLOW_RUN_DIR="$HOME/cylc-run/$FLOW" + echo "${FLOW}" + export FLOW FLOW_RUN_DIR +} + +purge () { + local FLOW="${1:-$FLOW}" + if [[ -z "$FLOW" ]]; then + echo 'no flow to purge' >&2 + return 1 + elif ((FAILURES == 0)); then + cylc clean "${FLOW}" + fi +} + +mkdir -p "$HOME/cylc-run" + ROSE_TEST_HOME=$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd) export ROSE_TEST_HOME DIFFTOOL="$(rose config '--default=diff -u' t difftool)" diff --git a/t/rose-ana/00-run-basic.t b/t/rose-ana/00-run-basic.t index ed5fcd910a..898b21fab8 100644 --- a/t/rose-ana/00-run-basic.t +++ b/t/rose-ana/00-run-basic.t @@ -33,31 +33,32 @@ kgo-database=.true. __CONF__ # Run the suite. +get_reg export CYLC_CONF_PATH= export ROSE_CONF_PATH=$PWD/conf -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) + TEST_KEY="${TEST_KEY_BASE}-install" run_pass "$TEST_KEY" \ cylc install \ - -C $TEST_SOURCE_DIR/$TEST_KEY_BASE \ - --flow-name=$NAME \ + -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ + --flow-name="${FLOW}" \ --no-run-name + TEST_KEY="${TEST_KEY_BASE}-run" run_fail "$TEST_KEY" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --host=localhost \ --no-detach \ --debug + #------------------------------------------------------------------------------- # Test the output # # Note that t1 and t5 are identical except t5 uses threading, so use a loop here for t in t1 t5 ; do - OUTPUT="${HOME}/cylc-run/${NAME}/log/job/1/rose_ana_$t/01/job.out" - file_pcregrep "${TEST_KEY_BASE}-$t-exact_list_fail}" \ + OUTPUT="${FLOW_RUN_DIR}/log/job/1/rose_ana_$t/01/job.out" + file_pcregrep "${TEST_KEY_BASE}-$t-exact_list_fail" \ 'Running task #([0-9]+).*\n.*Exact List Match Fail.*\n.*Task #\1 did not pass' \ "${OUTPUT}" file_pcregrep "${TEST_KEY_BASE}-$t-exact_list_success" \ @@ -141,12 +142,12 @@ for t in t1 t5 ; do done # Now check that the threading option is reflected in the output -OUTPUT="${HOME}/cylc-run/${NAME}/log/job/1/rose_ana_t1/01/job.out" +OUTPUT="${FLOW_RUN_DIR}/log/job/1/rose_ana_t1/01/job.out" TEST_KEY=$TEST_KEY_BASE-t1-serial_statement REGEXP="Running in SERIAL mode" file_grep $TEST_KEY "$REGEXP" $OUTPUT -OUTPUT="${HOME}/cylc-run/${NAME}/log/job/1/rose_ana_t5/01/job.out" +OUTPUT="${FLOW_RUN_DIR}/log/job/1/rose_ana_t5/01/job.out" TEST_KEY=$TEST_KEY_BASE-t5-threading_statement REGEXP="Running in THREADED mode, with 4 threads" file_grep $TEST_KEY "$REGEXP" $OUTPUT @@ -154,7 +155,7 @@ file_grep $TEST_KEY "$REGEXP" $OUTPUT #------------------------------------------------------------------------------- # Test of ignoring a task # First, test that the basic task ran ok -OUTPUT="${HOME}/cylc-run/${NAME}/log/job/1/rose_ana_t2_activated/01/job.out" +OUTPUT="${FLOW_RUN_DIR}/log/job/1/rose_ana_t2_activated/01/job.out" file_pcregrep "${TEST_KEY_BASE}-ignore-basic-1" \ 'Running task #([0-9]+).*\n.*First Test.*\n.*Task #\1 did not pass' \ "${OUTPUT}" @@ -163,16 +164,16 @@ file_pcregrep "${TEST_KEY_BASE}-ignore-basic-2" \ # Then test that ignoring a test means the output is not present file_pcregrep "${TEST_KEY_BASE}-ignore-notpresent" \ 'Running task #([0-9]+).*\n.*Second Test.*\n.*Task #\1 passed' \ - "${HOME}/cylc-run/${NAME}/log/job/1/rose_ana_t2_deactivated/01/job.out" + "${FLOW_RUN_DIR}/log/job/1/rose_ana_t2_deactivated/01/job.out" #------------------------------------------------------------------------------- # Test tolerance as an environment variable # First, test that the basic task ran ok file_pcregrep "${TEST_KEY_BASE}-tolerance-env-var-pass" \ 'Running task #([0-9]+).*\n.*First Test.*\n.*Task #\1 passed' \ - "${HOME}/cylc-run/${NAME}/log/job/1/rose_ana_t3_within_tolerance/01/job.out" + "${FLOW_RUN_DIR}/log/job/1/rose_ana_t3_within_tolerance/01/job.out" file_pcregrep "${TEST_KEY_BASE}-tolerance-env-var-fail" \ 'Running task #([0-9]+).*\n.*First Test.*\n.*Task #\1 did not pass' \ - "${HOME}/cylc-run/${NAME}/log/job/1/rose_ana_t3_outside_tolerance/01/job.out" + "${FLOW_RUN_DIR}/log/job/1/rose_ana_t3_outside_tolerance/01/job.out" #------------------------------------------------------------------------------- # Test of comparison database @@ -180,7 +181,7 @@ file_pcregrep "${TEST_KEY_BASE}-tolerance-env-var-fail" \ # Regexp for any number of digits (re-used a lot below) COMP_NUMBER="[0-9][0-9]*" -OUTPUT=$HOME/cylc-run/$NAME/log/job/1/db_check/01/job.out +OUTPUT="$FLOW_RUN_DIR/log/job/1/db_check/01/job.out" # For each of the 3 tasks check that a task entry exists with a status of 0 for TASK_NAME in "rose_ana_t1" "rose_ana_t2_activated" "rose_ana_t2_deactivated" ; do TEST_KEY=$TEST_KEY_BASE-db_check_${TASK_NAME}_success @@ -280,7 +281,7 @@ file_grep $TEST_KEY "$REGEXP" $OUTPUT #------------------------------------------------------------------------------- # Test of a few config options -OUTPUT="${HOME}/cylc-run/${NAME}/log/job/1/rose_ana_t4/01/job.out" +OUTPUT="${FLOW_RUN_DIR}/log/job/1/rose_ana_t4/01/job.out" file_pcregrep "${TEST_KEY_BASE}-report_limit_working" \ 'Running task #([0-9]+).*Check report limit is active.*Some output omitted due to limit.*Task #\1 passed' \ "${OUTPUT}" @@ -289,7 +290,5 @@ file_pcregrep "${TEST_KEY_BASE}-missing_skip_working" \ "${OUTPUT}" #------------------------------------------------------------------------------- -#Clean suite -cylc clean $NAME -#------------------------------------------------------------------------------- +purge exit 0 diff --git a/t/rose-ana/01-run-basic-v1.t b/t/rose-ana/01-run-basic-v1.t index 10e00e5339..9eb268c44e 100644 --- a/t/rose-ana/01-run-basic-v1.t +++ b/t/rose-ana/01-run-basic-v1.t @@ -26,25 +26,24 @@ tests $N_TESTS #------------------------------------------------------------------------------- # Run the suite. export ROSE_CONF_PATH= -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) + +get_reg TEST_KEY="${TEST_KEY_BASE}-install" run_pass "$TEST_KEY" \ cylc install \ -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ - --flow-name="$NAME" \ + --flow-name="$FLOW" \ --no-run-name TEST_KEY="${TEST_KEY_BASE}-run" run_pass "$TEST_KEY" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --host=localhost \ --no-detach \ --debug #------------------------------------------------------------------------------- # Test the output -OUTPUT=$HOME/cylc-run/$NAME/log/job/1/rose_ana_t1/01/job.out +OUTPUT="$FLOW_RUN_DIR/log/job/1/rose_ana_t1/01/job.out" TEST_KEY=$TEST_KEY_BASE-exact_numeric_success file_grep $TEST_KEY "[ OK ].*Semi-major Axis.*all: 0%:" $OUTPUT TEST_KEY=$TEST_KEY_BASE-exact_numeric_fail @@ -72,22 +71,22 @@ file_grep $TEST_KEY "[FAIL].*Inclination/Axial Tilt.*285.74423480[0-9]*% > 5%:.* #------------------------------------------------------------------------------- # Test of ignoring a task # First, test that the basic task ran ok -OUTPUT=$HOME/cylc-run/$NAME/log/job/1/rose_ana_t2_activated/01/job.out +OUTPUT="$FLOW_RUN_DIR/log/job/1/rose_ana_t2_activated/01/job.out" TEST_KEY=$TEST_KEY_BASE-ignore-basic-1 file_grep $TEST_KEY "[FAIL].*Species" $OUTPUT TEST_KEY=$TEST_KEY_BASE-ignore-basic-2 file_grep $TEST_KEY "[ OK ].*Class" $OUTPUT # Then test that ignoring a test means the output is not present -OUTPUT=$HOME/cylc-run/$NAME/log/job/1/rose_ana_t2_deactivated/01/job.out +OUTPUT="$FLOW_RUN_DIR/log/job/1/rose_ana_t2_deactivated/01/job.out" TEST_KEY=$TEST_KEY_BASE-ignore-notpresent file_grep_fail $TEST_KEY "[FAIL].*Species" $OUTPUT #------------------------------------------------------------------------------- # Test tolerance as an environment variable # First, test that the basic task ran ok -OUTPUT=$HOME/cylc-run/$NAME/log/job/1/rose_ana_t3_within_tolerance/01/job.out +OUTPUT="$FLOW_RUN_DIR/log/job/1/rose_ana_t3_within_tolerance/01/job.out" TEST_KEY=$TEST_KEY_BASE-tolerance-env-var-pass file_grep $TEST_KEY "[PASS].*data" $OUTPUT -OUTPUT=$HOME/cylc-run/$NAME/log/job/1/rose_ana_t3_outside_tolerance/01/job.out +OUTPUT="$FLOW_RUN_DIR/log/job/1/rose_ana_t3_outside_tolerance/01/job.out" TEST_KEY=$TEST_KEY_BASE-tolerance-env-var-fail file_grep $TEST_KEY "[FAIL].*data" $OUTPUT #------------------------------------------------------------------------------- @@ -97,7 +96,7 @@ file_grep $TEST_KEY "[FAIL].*data" $OUTPUT # Regexp for any number of digits (re-used a lot below) COMP_NUMBER="[0-9][0-9]*" -OUTPUT=$HOME/cylc-run/$NAME/log/job/1/db_check/01/job.out +OUTPUT="$FLOW_RUN_DIR/log/job/1/db_check/01/job.out" # For each of the 3 tasks check that a task entry exists with a status of 0 for TASK_NAME in "rose_ana_t1" "rose_ana_t2_activated" "rose_ana_t2_deactivated" ; do TEST_KEY=$TEST_KEY_BASE-db_check_${TASK_NAME}_success @@ -215,9 +214,6 @@ TASK_METHOD=".*Species.*" TEST_KEY=$TEST_KEY_BASE-db_check_t2_ignore_notpresent REGEXP="$COMP_NUMBER | $TASK_NAME | $COMP_FILES | $TASK_STATUS | $TASK_METHOD" file_grep_fail $TEST_KEY "$REGEXP" $OUTPUT - -#------------------------------------------------------------------------------- -#Clean suite -cylc clean $NAME #------------------------------------------------------------------------------- +purge exit 0 diff --git a/t/rose-task-env/00-non-cycle.t b/t/rose-task-env/00-non-cycle.t index dcc76e9f40..57c6847032 100755 --- a/t/rose-task-env/00-non-cycle.t +++ b/t/rose-task-env/00-non-cycle.t @@ -26,25 +26,23 @@ export ROSE_CONF_PATH= #------------------------------------------------------------------------------- tests 3 #------------------------------------------------------------------------------- -# Run the suite. -SUITE_RUN_DIR=$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX') -NAME=$(basename "${SUITE_RUN_DIR}") +get_reg run_pass "${TEST_KEY_BASE}-install" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name run_pass "${TEST_KEY_BASE}-run" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug -sqlite3 "${SUITE_RUN_DIR}/log/db" \ +sqlite3 "${FLOW_RUN_DIR}/log/db" \ 'select distinct status from task_states;' >"$TEST_KEY_BASE-db.out" file_cmp "$TEST_KEY_BASE-db.out" "$TEST_KEY_BASE-db.out" <<<'succeeded' #------------------------------------------------------------------------------- -cylc clean $NAME +purge exit 0 diff --git a/t/rose-task-env/01-integer-cycling.t b/t/rose-task-env/01-integer-cycling.t index 206365097c..f4efef3d1c 100755 --- a/t/rose-task-env/01-integer-cycling.t +++ b/t/rose-task-env/01-integer-cycling.t @@ -27,28 +27,27 @@ export ROSE_CONF_PATH= tests 9 #------------------------------------------------------------------------------- # Run the suite. -SUITE_RUN_DIR=$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX') -NAME=$(basename "${SUITE_RUN_DIR}") +get_reg run_pass "${TEST_KEY_BASE}-install" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name run_pass "${TEST_KEY_BASE}-run" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug -run_pass "$TEST_KEY_BASE-0" ls -d $HOME/cylc-run/${NAME}/share/cycle/0 -run_pass "$TEST_KEY_BASE-1" ls -d $HOME/cylc-run/${NAME}/share/cycle/1 -run_pass "$TEST_KEY_BASE-2" ls -d $HOME/cylc-run/${NAME}/share/cycle/2 -run_pass "$TEST_KEY_BASE-3" ls -d $HOME/cylc-run/${NAME}/share/cycle/3 -run_pass "$TEST_KEY_BASE-6" ls -d $HOME/cylc-run/${NAME}/share/cycle/6 -run_pass "$TEST_KEY_BASE-7" ls -d $HOME/cylc-run/${NAME}/share/cycle/7 -run_pass "$TEST_KEY_BASE-8" ls -d $HOME/cylc-run/${NAME}/share/cycle/8 +run_pass "$TEST_KEY_BASE-0" ls -d "$FLOW_RUN_DIR/share/cycle/0" +run_pass "$TEST_KEY_BASE-1" ls -d "$FLOW_RUN_DIR/share/cycle/1" +run_pass "$TEST_KEY_BASE-2" ls -d "$FLOW_RUN_DIR/share/cycle/2" +run_pass "$TEST_KEY_BASE-3" ls -d "$FLOW_RUN_DIR/share/cycle/3" +run_pass "$TEST_KEY_BASE-6" ls -d "$FLOW_RUN_DIR/share/cycle/6" +run_pass "$TEST_KEY_BASE-7" ls -d "$FLOW_RUN_DIR/share/cycle/7" +run_pass "$TEST_KEY_BASE-8" ls -d "$FLOW_RUN_DIR/share/cycle/8" #------------------------------------------------------------------------------- -cylc clean $NAME +purge exit 0 diff --git a/t/rose-task-env/02-360day-cycling.t b/t/rose-task-env/02-360day-cycling.t index 039d34c7ae..fe2945dbec 100755 --- a/t/rose-task-env/02-360day-cycling.t +++ b/t/rose-task-env/02-360day-cycling.t @@ -25,16 +25,15 @@ export ROSE_CONF_PATH= tests 3 -RUN_DIR="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rtb-rose-task-env-02.XXXXXX')" -NAME="$(basename "${RUN_DIR}")" +get_reg run_pass "${TEST_KEY_BASE}-install" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name run_pass "${TEST_KEY_BASE}-run" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --host='localhost' \ --no-detach \ --debug @@ -47,7 +46,7 @@ for CYCLE in \ '20200302T0000Z' do echo "${CYCLE}:" - sed "s?^${RUN_DIR}??" "${RUN_DIR}/work/${CYCLE}/foo/my-datac.txt" + sed "s?^${FLOW_RUN_DIR}??" "${FLOW_RUN_DIR}/work/${CYCLE}/foo/my-datac.txt" done >'expected-my-datac.txt' file_cmp "${TEST_KEY_BASE}-my-datac" 'expected-my-datac.txt' <<'__TXT__' @@ -77,5 +76,5 @@ file_cmp "${TEST_KEY_BASE}-my-datac" 'expected-my-datac.txt' <<'__TXT__' /share/cycle/20200303T0000Z __TXT__ -cylc clean "${NAME}" +purge exit 0 diff --git a/t/rose-task-run/00-run-basic.t b/t/rose-task-run/00-run-basic.t index d2d854fdcf..634389ff4d 100755 --- a/t/rose-task-run/00-run-basic.t +++ b/t/rose-task-run/00-run-basic.t @@ -27,46 +27,45 @@ export ROSE_CONF_PATH= tests 46 #------------------------------------------------------------------------------- # Run the suite. -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) +get_reg run_pass "${TEST_KEY_BASE}-install" \ cylc install \ -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name run_pass "${TEST_KEY_BASE}-run" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug #------------------------------------------------------------------------------- MY_PATH= -for P in $(ls -d $SUITE_RUN_DIR/etc/my-path/*); do +for P in $(ls -d $FLOW_RUN_DIR/etc/my-path/*); do if [[ -n $MY_PATH ]]; then MY_PATH="$P:$MY_PATH" else MY_PATH="$P" fi done -if [[ -d $SUITE_RUN_DIR/etc/your-path ]]; then +if [[ -d $FLOW_RUN_DIR/etc/your-path ]]; then if [[ -n $MY_PATH ]]; then - MY_PATH="$SUITE_RUN_DIR/etc/your-path:$MY_PATH" + MY_PATH="$FLOW_RUN_DIR/etc/your-path:$MY_PATH" else - MY_PATH="$SUITE_RUN_DIR/etc/your-path" + MY_PATH="$FLOW_RUN_DIR/etc/your-path" fi fi PREV_CYCLE= for CYCLE in 20130101T0000Z 20130101T1200Z 20130102T0000Z; do TEST_KEY=$TEST_KEY_BASE-file-$CYCLE TASK=my_task_1 - FILE="$HOME/cylc-run/$NAME/log/job/$CYCLE/$TASK/01/job.txt" + FILE="$FLOW_RUN_DIR/log/job/$CYCLE/$TASK/01/job.txt" file_test "$TEST_KEY" $FILE - file_grep "$TEST_KEY-ROSE_SUITE_DIR" "ROSE_SUITE_DIR=$SUITE_RUN_DIR" $FILE + file_grep "$TEST_KEY-ROSE_SUITE_DIR" "ROSE_SUITE_DIR=$FLOW_RUN_DIR" $FILE file_grep "$TEST_KEY-ROSE_SUITE_DIR_REL" \ - "ROSE_SUITE_DIR_REL=${SUITE_RUN_DIR#$HOME/}" $FILE - file_grep "$TEST_KEY-ROSE_SUITE_NAME" "ROSE_SUITE_NAME=$NAME" $FILE + "ROSE_SUITE_DIR_REL=${FLOW_RUN_DIR#$HOME/}" $FILE + file_grep "$TEST_KEY-ROSE_SUITE_NAME" "ROSE_SUITE_NAME=$FLOW" $FILE file_grep "$TEST_KEY-ROSE_TASK_NAME" "ROSE_TASK_NAME=$TASK" $FILE file_grep "$TEST_KEY-ROSE_TASK_CYCLE_TIME" \ "ROSE_TASK_CYCLE_TIME=$CYCLE" $FILE @@ -74,19 +73,19 @@ for CYCLE in 20130101T0000Z 20130101T1200Z 20130102T0000Z; do "ROSE_TASK_LOG_DIR=${FILE%/job.txt}" $FILE file_grep "$TEST_KEY-ROSE_TASK_LOG_ROOT" \ "ROSE_TASK_LOG_ROOT=${FILE%/job.txt}/job" $FILE - file_grep "$TEST_KEY-ROSE_DATA" "ROSE_DATA=$SUITE_RUN_DIR/share/data" $FILE + file_grep "$TEST_KEY-ROSE_DATA" "ROSE_DATA=$FLOW_RUN_DIR/share/data" $FILE file_grep "$TEST_KEY-ROSE_DATAC" \ - "ROSE_DATAC=$SUITE_RUN_DIR/share/cycle/$CYCLE" $FILE + "ROSE_DATAC=$FLOW_RUN_DIR/share/cycle/$CYCLE" $FILE if [[ -n $PREV_CYCLE ]]; then file_grep "$TEST_KEY-ROSE_DATACT12H" \ - "ROSE_DATACT12H=$SUITE_RUN_DIR/share/cycle/$PREV_CYCLE" $FILE + "ROSE_DATACT12H=$FLOW_RUN_DIR/share/cycle/$PREV_CYCLE" $FILE fi - file_grep "$TEST_KEY-ROSE_ETC" "ROSE_ETC=$SUITE_RUN_DIR/etc" $FILE + file_grep "$TEST_KEY-ROSE_ETC" "ROSE_ETC=$FLOW_RUN_DIR/etc" $FILE file_grep "$TEST_KEY-ROSE_TASK_PREFIX" "ROSE_TASK_PREFIX=my" $FILE file_grep "$TEST_KEY-ROSE_TASK_SUFFIX" "ROSE_TASK_SUFFIX=1" $FILE file_grep "$TEST_KEY-MY_PATH" "MY_PATH=$MY_PATH" $FILE PREV_CYCLE=$CYCLE done #------------------------------------------------------------------------------- -cylc clean "$NAME" +purge exit 0 diff --git a/t/rose-task-run/01-run-basic-iso.t b/t/rose-task-run/01-run-basic-iso.t index a764e5dc80..e2d756dd11 100755 --- a/t/rose-task-run/01-run-basic-iso.t +++ b/t/rose-task-run/01-run-basic-iso.t @@ -27,46 +27,45 @@ tests 48 #------------------------------------------------------------------------------- # Run the suite. -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) +get_reg run_pass "${TEST_KEY_BASE}-install" \ cylc install \ -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ - --flow-name="$NAME" \ + --flow-name="${FLOW}" \ --no-run-name run_pass "${TEST_KEY_BASE}-run" \ cylc run \ - "$NAME" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug #------------------------------------------------------------------------------- MY_PATH= -for P in $(ls -d $SUITE_RUN_DIR/etc/my-path/*); do +for P in $(ls -d $FLOW_RUN_DIR/etc/my-path/*); do if [[ -n $MY_PATH ]]; then MY_PATH="$P:$MY_PATH" else MY_PATH="$P" fi done -if [[ -d $SUITE_RUN_DIR/etc/your-path ]]; then +if [[ -d $FLOW_RUN_DIR/etc/your-path ]]; then if [[ -n $MY_PATH ]]; then - MY_PATH="$SUITE_RUN_DIR/etc/your-path:$MY_PATH" + MY_PATH="$FLOW_RUN_DIR/etc/your-path:$MY_PATH" else - MY_PATH="$SUITE_RUN_DIR/etc/your-path" + MY_PATH="$FLOW_RUN_DIR/etc/your-path" fi fi PREV_CYCLE= for CYCLE in 20130101T0000Z 20130101T1200Z 20130102T0000Z; do TEST_KEY=$TEST_KEY_BASE-file-$CYCLE TASK=my_task_1 - FILE=$HOME/cylc-run/$NAME/log/job/$CYCLE/$TASK/01/job.txt + FILE="$FLOW_RUN_DIR/log/job/$CYCLE/$TASK/01/job.txt" file_test "$TEST_KEY" $FILE - file_grep "$TEST_KEY-ROSE_SUITE_DIR" "ROSE_SUITE_DIR=$SUITE_RUN_DIR" $FILE + file_grep "$TEST_KEY-ROSE_SUITE_DIR" "ROSE_SUITE_DIR=$FLOW_RUN_DIR" $FILE file_grep "$TEST_KEY-ROSE_SUITE_DIR_REL" \ - "ROSE_SUITE_DIR_REL=${SUITE_RUN_DIR#$HOME/}" $FILE - file_grep "$TEST_KEY-ROSE_SUITE_NAME" "ROSE_SUITE_NAME=$NAME" $FILE + "ROSE_SUITE_DIR_REL=${FLOW_RUN_DIR#$HOME/}" $FILE + file_grep "$TEST_KEY-ROSE_SUITE_NAME" "ROSE_SUITE_NAME=${FLOW}" $FILE file_grep "$TEST_KEY-ROSE_TASK_NAME" "ROSE_TASK_NAME=$TASK" $FILE file_grep "$TEST_KEY-ROSE_TASK_CYCLE_TIME" \ "ROSE_TASK_CYCLE_TIME=$CYCLE" $FILE @@ -74,14 +73,14 @@ for CYCLE in 20130101T0000Z 20130101T1200Z 20130102T0000Z; do "ROSE_TASK_LOG_DIR=${FILE%/job.txt}" $FILE file_grep "$TEST_KEY-ROSE_TASK_LOG_ROOT" \ "ROSE_TASK_LOG_ROOT=${FILE%job.txt}job" $FILE - file_grep "$TEST_KEY-ROSE_DATA" "ROSE_DATA=$SUITE_RUN_DIR/share/data" $FILE + file_grep "$TEST_KEY-ROSE_DATA" "ROSE_DATA=$FLOW_RUN_DIR/share/data" $FILE file_grep "$TEST_KEY-ROSE_DATAC" \ - "ROSE_DATAC=$SUITE_RUN_DIR/share/cycle/$CYCLE" $FILE + "ROSE_DATAC=$FLOW_RUN_DIR/share/cycle/$CYCLE" $FILE if [[ -n $PREV_CYCLE ]]; then file_grep "$TEST_KEY-ROSE_DATACPT12H" \ - "ROSE_DATACPT12H=$SUITE_RUN_DIR/share/cycle/$PREV_CYCLE" $FILE + "ROSE_DATACPT12H=$FLOW_RUN_DIR/share/cycle/$PREV_CYCLE" $FILE fi - file_grep "$TEST_KEY-ROSE_ETC" "ROSE_ETC=$SUITE_RUN_DIR/etc" $FILE + file_grep "$TEST_KEY-ROSE_ETC" "ROSE_ETC=$FLOW_RUN_DIR/etc" $FILE file_grep "$TEST_KEY-ROSE_TASK_PREFIX" "ROSE_TASK_PREFIX=my" $FILE file_grep "$TEST_KEY-ROSE_TASK_SUFFIX" "ROSE_TASK_SUFFIX=1" $FILE file_grep "$TEST_KEY-MY_PATH" "MY_PATH=$MY_PATH" $FILE @@ -92,13 +91,13 @@ NEXT_CYCLE= for CYCLE in 20130102T0000Z 20130101T1200Z 20130101T0000Z; do if [[ -n "${NEXT_CYCLE}" ]]; then TEST_KEY="${TEST_KEY_BASE}-file-${CYCLE}" - FILE="${HOME}/cylc-run/${NAME}/log/job/${CYCLE}/my_task_1/01/job.txt" + FILE="${HOME}/cylc-run/${FLOW}/log/job/${CYCLE}/my_task_1/01/job.txt" file_grep "${TEST_KEY}-ROSE_DATAC__PT12H" \ - "ROSE_DATAC__PT12H=${SUITE_RUN_DIR}/share/cycle/${NEXT_CYCLE}" \ + "ROSE_DATAC__PT12H=${FLOW_RUN_DIR}/share/cycle/${NEXT_CYCLE}" \ "${FILE}" fi NEXT_CYCLE="${CYCLE}" done #------------------------------------------------------------------------------- -cylc clean $NAME +purge exit 0 diff --git a/t/rose-task-run/04-run-path-empty.t b/t/rose-task-run/04-run-path-empty.t index 25e85d631c..a098dd73c1 100755 --- a/t/rose-task-run/04-run-path-empty.t +++ b/t/rose-task-run/04-run-path-empty.t @@ -27,16 +27,15 @@ export ROSE_CONF_PATH= tests 5 #------------------------------------------------------------------------------- # Run the suite. -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) +get_reg run_pass "${TEST_KEY_BASE}-install" \ cylc install \ -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ - --flow-name="$NAME" \ + --flow-name="$FLOW" \ --no-run-name run_pass "${TEST_KEY_BASE}-run" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host=localhost \ --no-detach \ @@ -46,11 +45,11 @@ PREV_CYCLE= for CYCLE in 20130101T0000Z 20130101T1200Z 20130102T0000Z; do TEST_KEY=$TEST_KEY_BASE-file-$CYCLE TASK=my_task_1 - FILE=$HOME/cylc-run/$NAME/log/job/$CYCLE/$TASK/01/job.txt + FILE="$FLOW_RUN_DIR/log/job/$CYCLE/$TASK/01/job.txt" file_grep "$TEST_KEY-PATH" \ - "PATH=$SUITE_RUN_DIR/app/$TASK/bin:$SUITE_RUN_DIR/etc/your-path" $FILE + "PATH=$FLOW_RUN_DIR/app/$TASK/bin:$FLOW_RUN_DIR/etc/your-path" $FILE PREV_CYCLE=$CYCLE done #------------------------------------------------------------------------------- -cylc clean $NAME +purge exit 0 diff --git a/t/rose-task-run/06-app-prune-iso.t b/t/rose-task-run/06-app-prune-iso.t index 004e5b8844..6a7e8e3c1f 100755 --- a/t/rose-task-run/06-app-prune-iso.t +++ b/t/rose-task-run/06-app-prune-iso.t @@ -33,22 +33,20 @@ fi export CYLC_CONF_PATH= export ROSE_CONF_PATH= TEST_KEY=$TEST_KEY_BASE -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) #------------------------------------------------------------------------------- tests 9 #------------------------------------------------------------------------------- +get_reg run_pass "${TEST_KEY_BASE}-install" \ cylc install \ -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ - --flow-name="$NAME" \ + --flow-name="$FLOW" \ --no-run-name \ "$JOB_HOST" run_pass "${TEST_KEY_BASE}-run" \ cylc run \ - "$NAME" \ + "$FLOW" \ --abort-if-any-task-fails \ --host=localhost \ --no-detach \ @@ -60,7 +58,7 @@ sed '/^\[INFO\] \(create\|delete\|update\)/!d; /^\[INFO\] delete: \.rose-suite-log.lock/d; /\.json/d; /[0-9a-h]\{8\}\(-[0-9a-h]\{4\}\)\{3\}-[0-9a-h]\{12\}$/d' \ - $SUITE_RUN_DIR/prune.log >edited-prune.log + $FLOW_RUN_DIR/prune.log >edited-prune.log if [[ -n $JOB_HOST ]]; then sed "s/\\\$JOB_HOST/$JOB_HOST/g" \ $TEST_SOURCE_DIR/$TEST_KEY_BASE.log >expected-prune.log @@ -72,8 +70,8 @@ file_cmp "$TEST_KEY" expected-prune.log edited-prune.log #------------------------------------------------------------------------------- TEST_KEY=$TEST_KEY_BASE-ls run_pass "$TEST_KEY" \ - ls $SUITE_RUN_DIR/log/job-*.tar.gz $SUITE_RUN_DIR/{log/job,share/cycle,work} -sed "s?\\\$SUITE_RUN_DIR?$SUITE_RUN_DIR?g" \ + ls $FLOW_RUN_DIR/log/job-*.tar.gz $FLOW_RUN_DIR/{log/job,share/cycle,work} +sed "s?\\\$SUITE_RUN_DIR?$FLOW_RUN_DIR?g" \ $TEST_SOURCE_DIR/$TEST_KEY.out >expected-ls.out if [[ -z $JOB_HOST ]]; then sed -i "/my_task_2/d" expected-ls.out @@ -84,8 +82,8 @@ file_cmp "$TEST_KEY.err" "$TEST_KEY.err" expected-host-ls.out file_cmp "$TEST_KEY.out" "$TEST_KEY.out" expected-host-ls.out file_cmp "$TEST_KEY.err" "$TEST_KEY.err" "$TEST_KEY.out" +(cd $FLOW_RUN_DIR; find foo -type f | LANG=C sort) >"$TEST_KEY.out" file_cmp "$TEST_KEY.out" "$TEST_SOURCE_DIR/$TEST_KEY.out" "$TEST_KEY.out" for CYCLE in 20130101T0000Z 20130101T1200Z 20130102T0000Z; do TEST_KEY="$TEST_KEY_BASE-$CYCLE.out" sed '/^\[INFO\] [=!+]/!d; s/\(t(init)=\)[^Z]*Z/\1YYYY-mm-DDTHH:MM:SSZ/; s/\(dt(\(tran\|arch\))=\)[^s]*s/\1SSSSs/g' \ - $SUITE_RUN_DIR/log/job/$CYCLE/archive/0*/job.out >"$TEST_KEY" + $FLOW_RUN_DIR/log/job/$CYCLE/archive/0*/job.out >"$TEST_KEY" file_cmp "$TEST_KEY" "$TEST_KEY" $TEST_SOURCE_DIR/$TEST_KEY_BASE-$CYCLE.out TEST_KEY="$TEST_KEY_BASE-planet-n" - tar -tzf $SUITE_RUN_DIR/foo/$CYCLE/hello/worlds/planet-n.tar.gz | \ + tar -tzf $FLOW_RUN_DIR/foo/$CYCLE/hello/worlds/planet-n.tar.gz | \ LANG=C sort >"$TEST_KEY-$CYCLE.out" file_cmp "$TEST_KEY-$CYCLE.out" \ "$TEST_KEY-$CYCLE.out" "$TEST_SOURCE_DIR/$TEST_KEY.out" TEST_KEY="$TEST_KEY_BASE-unknown-stuff" - tar -tf $SUITE_RUN_DIR/foo/$CYCLE/hello/worlds/unknown/stuff.pax | \ + tar -tf $FLOW_RUN_DIR/foo/$CYCLE/hello/worlds/unknown/stuff.pax | \ LANG=C sort >"$TEST_KEY-$CYCLE.out" sed "s/\\\$CYCLE/$CYCLE/" "$TEST_SOURCE_DIR/$TEST_KEY.out" \ >"$TEST_KEY-$CYCLE.out.expected" @@ -72,27 +70,27 @@ for CYCLE in 20130101T0000Z 20130101T1200Z 20130102T0000Z; do "$TEST_KEY-$CYCLE.out" "$TEST_KEY-$CYCLE.out.expected" TEST_KEY="$TEST_KEY_BASE-db" for TRY in 1 2; do - FILE=$SUITE_RUN_DIR/work/$CYCLE/archive/rose-arch-db-$TRY.out - sed "s?\\\$ROSE_DATAC?$SUITE_RUN_DIR/share/cycle/$CYCLE?" \ + FILE=$FLOW_RUN_DIR/work/$CYCLE/archive/rose-arch-db-$TRY.out + sed "s?\\\$ROSE_DATAC?$FLOW_RUN_DIR/share/cycle/$CYCLE?" \ "$TEST_SOURCE_DIR/$TEST_KEY-$CYCLE-$TRY.out" >$FILE.expected file_cmp "$TEST_KEY-$CYCLE.out" $FILE.expected $FILE done for KEY in dark-matter.txt jupiter.txt try.nl uranus.txt; do TEST_KEY="$TEST_KEY_BASE-$CYCLE-grep-$KEY-foo-log-2" - file_grep "$TEST_KEY" $KEY $SUITE_RUN_DIR/foo.log.$CYCLE.2 + file_grep "$TEST_KEY" $KEY $FLOW_RUN_DIR/foo.log.$CYCLE.2 done - if test $(wc -l <$SUITE_RUN_DIR/foo.log.$CYCLE.2) -eq 4; then + if test $(wc -l <$FLOW_RUN_DIR/foo.log.$CYCLE.2) -eq 4; then pass "$TEST_KEY_BASE-$CYCLE-foo-log-2-wc-l" else fail "$TEST_KEY_BASE-$CYCLE-foo-log-2-wc-l" fi TEST_KEY="$TEST_KEY_BASE-$CYCLE-neptune-1.txt" file_cmp "$TEST_KEY" \ - $SUITE_RUN_DIR/foo/$CYCLE/hello/worlds/neptune-1.txt <<__TXT__ + $FLOW_RUN_DIR/foo/$CYCLE/hello/worlds/neptune-1.txt <<__TXT__ [$CYCLE] Greet Triton __TXT__ TEST_KEY="$TEST_KEY_BASE-$CYCLE-jupiter-moons.tar.gz" - tar -xzf $SUITE_RUN_DIR/foo/$CYCLE/hello/worlds/jupiter-moons.tar.gz -O \ + tar -xzf $FLOW_RUN_DIR/foo/$CYCLE/hello/worlds/jupiter-moons.tar.gz -O \ >"$TEST_KEY.out" file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__TXT__ [$CYCLE] Greet Io @@ -102,7 +100,7 @@ done # Results, bad ones CYCLE=20130101T1200Z TEST_KEY="$TEST_KEY_BASE-bad-archive-1" -FILE_PREFIX="$SUITE_RUN_DIR/log/job/$CYCLE/archive_bad_" +FILE_PREFIX="$FLOW_RUN_DIR/log/job/$CYCLE/archive_bad_" sed '/^\[FAIL\] /!d' "${FILE_PREFIX}1/01/job.err" >"${TEST_KEY}.err" file_cmp "${TEST_KEY}.err" "${TEST_KEY}.err" <<'__ERR__' [FAIL] foo://20130101T1200Z/hello/worlds/planet-n.tar.gz: bad command-format: foo put %(target)s %(source)s: KeyError: 'source' @@ -135,7 +133,7 @@ __ERR__ TEST_KEY="$TEST_KEY_BASE-bad-archive-5" sed '/^\[FAIL\] /!d' "${FILE_PREFIX}5/01/job.err" >"${TEST_KEY}.err" file_cmp "${TEST_KEY}.err" "${TEST_KEY}.err" <<__ERR__ -[FAIL] [arch:inner.tar.gz]source=hello/mercurry.txt: configuration value error: [Errno 2] No such file or directory: '${SUITE_RUN_DIR}/share/cycle/20130101T1200Z/hello/mercurry.txt' +[FAIL] [arch:inner.tar.gz]source=hello/mercurry.txt: configuration value error: [Errno 2] No such file or directory: '${FLOW_RUN_DIR}/share/cycle/20130101T1200Z/hello/mercurry.txt' [FAIL] ! foo://20130101T1200Z/hello/worlds/inner.tar.gz [compress=tar.gz] [FAIL] ! hello/venus.txt (hello/venus.txt) __ERR__ @@ -148,7 +146,7 @@ sed \ "$TEST_KEY.err" \ "${FILE_PREFIX}6/01/job.err" >"$TEST_KEY.err" file_cmp "$TEST_KEY.err" "$TEST_KEY.err" <<__ERR__ -[FAIL] foo push foo://20130101T1200Z/hello/worlds/mars.txt.gz $SUITE_RUN_DIR/share/cycle/20130101T1200Z/hello/mars.txt # return-code=1, stderr= +[FAIL] foo push foo://20130101T1200Z/hello/worlds/mars.txt.gz $FLOW_RUN_DIR/share/cycle/20130101T1200Z/hello/mars.txt # return-code=1, stderr= [FAIL] foo: push: unknown action [FAIL] ! foo://20130101T1200Z/hello/worlds/mars.txt.gz [FAIL] ! hello/mars.txt (hello/mars.txt) @@ -167,7 +165,7 @@ TEST_KEY="$TEST_KEY_BASE-bad-archive-9" sed '/^\[INFO\] [=!+]/!d; s/\(t(init)=\)[^Z]*Z/\1YYYY-mm-DDTHH:MM:SSZ/; s/\(dt(\(tran\|arch\))=\)[^s]*s/\1SSSSs/g' \ - "$SUITE_RUN_DIR/log/job/$CYCLE/archive_bad_9/01/job.out" >"$TEST_KEY.out" + "$FLOW_RUN_DIR/log/job/$CYCLE/archive_bad_9/01/job.out" >"$TEST_KEY.out" file_cmp "$TEST_KEY.out" \ "$TEST_SOURCE_DIR/$TEST_KEY_BASE-bad-9.out" "$TEST_KEY.out" sed -e '/^\[FAIL\] /!d' \ @@ -175,7 +173,7 @@ sed -e '/^\[FAIL\] /!d' \ -e '/^\[FAIL\] \[my-bad-command\]/d' \ -e 's/ \[compress.*]$//' \ -e '/BASH_XTRACEFD/d' \ - "$SUITE_RUN_DIR/log/job/$CYCLE/archive_bad_9/01/job.err" >"$TEST_KEY.err" + "$FLOW_RUN_DIR/log/job/$CYCLE/archive_bad_9/01/job.err" >"$TEST_KEY.err" file_cmp "${TEST_KEY}.err" "${TEST_KEY}.err" <<__ERR__ [FAIL] ! foo://20130101T1200Z/planet-n.tar.gz [FAIL] ! hello/dark-matter.txt (hello/dark-matter.txt) @@ -210,5 +208,5 @@ file_cmp "${TEST_KEY}.err" "${TEST_KEY}.err" <<__ERR__ __ERR__ #------------------------------------------------------------------------------- -cylc clean $NAME +purge exit 0 diff --git a/t/rose-task-run/08-app-fcm-make.t b/t/rose-task-run/08-app-fcm-make.t index f8a6671b29..712af0c346 100755 --- a/t/rose-task-run/08-app-fcm-make.t +++ b/t/rose-task-run/08-app-fcm-make.t @@ -37,11 +37,9 @@ fi #------------------------------------------------------------------------------- # Run the suite. export ROSE_CONF_PATH= -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) +get_reg OPTS=( - "--flow-name=${NAME}" + "--flow-name=${FLOW}" "--no-run-name" ) if [[ -n ${JOB_HOST:-} ]]; then @@ -53,14 +51,14 @@ run_pass "${TEST_KEY_BASE}-install" \ "${OPTS[@]}" run_pass "${TEST_KEY_BASE}-run" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host='localhost' \ --no-detach \ --debug #------------------------------------------------------------------------------- TEST_KEY="$TEST_KEY_BASE-status" -sqlite3 $SUITE_RUN_DIR/log/db \ +sqlite3 $FLOW_RUN_DIR/log/db \ 'SELECT name,status FROM task_states ORDER BY name;' \ >"$TEST_KEY" if [[ -n $JOB_HOST ]]; then @@ -83,40 +81,40 @@ fi #------------------------------------------------------------------------------- TEST_KEY="$TEST_KEY_BASE-t1" # normal file_grep "$TEST_KEY.out" \ - "\\[INFO\\] [0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0:9]* fcm make -f .*$SUITE_RUN_DIR/work/1/fcm_make_t1/fcm-make.cfg -C .*$SUITE_RUN_DIR/share/fcm_make_t1 -j 4" \ - $SUITE_RUN_DIR/log/job/1/fcm_make_t1/01/job.out + "\\[INFO\\] [0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0:9]* fcm make -f .*$FLOW_RUN_DIR/work/1/fcm_make_t1/fcm-make.cfg -C .*$FLOW_RUN_DIR/share/fcm_make_t1 -j 4" \ + $FLOW_RUN_DIR/log/job/1/fcm_make_t1/01/job.out #------------------------------------------------------------------------------- TEST_KEY="$TEST_KEY_BASE-t2" # use-pwd file_grep "$TEST_KEY.out" "\\[INFO\\] [0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0:9]* fcm make -j 4" \ - $SUITE_RUN_DIR/log/job/1/fcm_make_t2/01/job.out + $FLOW_RUN_DIR/log/job/1/fcm_make_t2/01/job.out #------------------------------------------------------------------------------- TEST_KEY="$TEST_KEY_BASE-t3" # opt.jobs file_grep "$TEST_KEY.out" \ - "\\[INFO\\] [0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0:9]* fcm make -f .*$SUITE_RUN_DIR/work/1/fcm_make_t3/fcm-make.cfg -C .*$SUITE_RUN_DIR/share/fcm_make_t3 -j 1" \ - $SUITE_RUN_DIR/log/job/1/fcm_make_t3/01/job.out + "\\[INFO\\] [0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0:9]* fcm make -f .*$FLOW_RUN_DIR/work/1/fcm_make_t3/fcm-make.cfg -C .*$FLOW_RUN_DIR/share/fcm_make_t3 -j 1" \ + $FLOW_RUN_DIR/log/job/1/fcm_make_t3/01/job.out #------------------------------------------------------------------------------- TEST_KEY="$TEST_KEY_BASE-t4" # args file_grep "$TEST_KEY.out" \ - "\\[INFO\\] [0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0:9]* fcm make -f .*$SUITE_RUN_DIR/work/1/fcm_make_t4/fcm-make.cfg -C .*$SUITE_RUN_DIR/share/fcm_make_t4 -j 4 -v -v" \ - $SUITE_RUN_DIR/log/job/1/fcm_make_t4/01/job.out + "\\[INFO\\] [0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0:9]* fcm make -f .*$FLOW_RUN_DIR/work/1/fcm_make_t4/fcm-make.cfg -C .*$FLOW_RUN_DIR/share/fcm_make_t4 -j 4 -v -v" \ + $FLOW_RUN_DIR/log/job/1/fcm_make_t4/01/job.out #------------------------------------------------------------------------------- if [[ -z $JOB_HOST ]]; then skip 3 '[t]job-host not defined' else TEST_KEY="$TEST_KEY_BASE-t5" # mirror file_grep "$TEST_KEY.out.env" \ - "\\[INFO\\] [0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0:9]* export ROSE_TASK_MIRROR_TARGET=$JOB_HOST:cylc-run/$NAME/share/fcm_make_t5" \ - $SUITE_RUN_DIR/log/job/1/fcm_make_t5/01/job.out + "\\[INFO\\] [0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0:9]* export ROSE_TASK_MIRROR_TARGET=$JOB_HOST:cylc-run/$FLOW/share/fcm_make_t5" \ + $FLOW_RUN_DIR/log/job/1/fcm_make_t5/01/job.out file_grep "$TEST_KEY.out.cmd" \ - "\\[INFO\\] [0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0:9]* fcm make -f .*$SUITE_RUN_DIR/work/1/fcm_make_t5/fcm-make.cfg -C .*$SUITE_RUN_DIR/share/fcm_make_t5 -j 4 mirror\\.target=${JOB_HOST}:cylc-run/${NAME}/share/fcm_make_t5" \ - $SUITE_RUN_DIR/log/job/1/fcm_make_t5/01/job.out + "\\[INFO\\] [0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0:9]* fcm make -f .*$FLOW_RUN_DIR/work/1/fcm_make_t5/fcm-make.cfg -C .*$FLOW_RUN_DIR/share/fcm_make_t5 -j 4 mirror\\.target=${JOB_HOST}:cylc-run/${FLOW}/share/fcm_make_t5" \ + $FLOW_RUN_DIR/log/job/1/fcm_make_t5/01/job.out TEST_KEY="$TEST_KEY_BASE-t5-part-2" - rose suite-log -q --name=$NAME --update fcm_make2_t5 + rose suite-log -q --name=$FLOW --update fcm_make2_t5 file_grep "$TEST_KEY.out" \ - "\\[INFO\\] [0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0:9]* fcm make -C .*/cylc-run/${NAME}/share/fcm_make_t5 -n 2 -j 4" \ - $SUITE_RUN_DIR/log/job/1/fcm_make2_t5/01/job.out + "\\[INFO\\] [0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0:9]* fcm make -C .*/cylc-run/${FLOW}/share/fcm_make_t5 -n 2 -j 4" \ + $FLOW_RUN_DIR/log/job/1/fcm_make2_t5/01/job.out fi #------------------------------------------------------------------------------- -cylc clean $NAME +purge exit 0 diff --git a/t/rose-task-run/09-app-prune-work.t b/t/rose-task-run/09-app-prune-work.t index c8d7c073b9..1d01708526 100755 --- a/t/rose-task-run/09-app-prune-work.t +++ b/t/rose-task-run/09-app-prune-work.t @@ -26,19 +26,17 @@ tests 3 #------------------------------------------------------------------------------- export ROSE_CONF_PATH= -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) +get_reg TEST_KEY="${TEST_KEY_BASE}-install" run_pass "$TEST_KEY" \ cylc install \ -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ - --flow-name="$NAME" \ + --flow-name="$FLOW" \ --no-run-name TEST_KEY="${TEST_KEY_BASE}-run" run_pass "$TEST_KEY" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host=localhost \ --no-detach \ @@ -46,11 +44,11 @@ run_pass "$TEST_KEY" \ #------------------------------------------------------------------------------- TEST_KEY="$TEST_KEY_BASE-prune.log" sed 's/[0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0-9]*/YYYY-MM-DDTHHMM/g'\ - "${SUITE_RUN_DIR}/prune.log" > stamp-removed.log + "${FLOW_RUN_DIR}/prune.log" > stamp-removed.log sed '/^\[INFO\] YYYY-MM-DDTHHMM export ROSE_TASK_CYCLE_TIME=/p; /^\[INFO\] YYYY-MM-DDTHHMM delete: /!d' \ stamp-removed.log >edited-prune.log file_cmp "$TEST_KEY" "$TEST_SOURCE_DIR/$TEST_KEY_BASE.log" edited-prune.log #------------------------------------------------------------------------------- -cylc clean $NAME +purge exit 0 diff --git a/t/rose-task-run/10-specify-cycle.t b/t/rose-task-run/10-specify-cycle.t index 76d20292a4..1962444032 100755 --- a/t/rose-task-run/10-specify-cycle.t +++ b/t/rose-task-run/10-specify-cycle.t @@ -27,22 +27,21 @@ export ROSE_CONF_PATH= tests 3 #------------------------------------------------------------------------------- # Run the suite. -TEST_KEY=$TEST_KEY_BASE -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) +TEST_KEY="$TEST_KEY_BASE" +get_reg run_pass "${TEST_KEY_BASE}-install" \ cylc install \ -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ - --flow-name=$NAME \ + --flow-name=$FLOW \ --no-run-name run_pass "${TEST_KEY_BASE}-run" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug +file_cmp "$TEST_KEY" "$FLOW_RUN_DIR/file" <<<'20121231T1200Z' #------------------------------------------------------------------------------- -file_cmp "$TEST_KEY" "$SUITE_RUN_DIR/file" <<<'20121231T1200Z' -cylc clean $NAME +purge exit 0 diff --git a/t/rose-task-run/11-specify-cycle-iso.t b/t/rose-task-run/11-specify-cycle-iso.t index 8580ddef82..9d383ca3df 100755 --- a/t/rose-task-run/11-specify-cycle-iso.t +++ b/t/rose-task-run/11-specify-cycle-iso.t @@ -27,22 +27,20 @@ export ROSE_CONF_PATH= # Run the suite. tests 3 TEST_KEY=$TEST_KEY_BASE -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) +get_reg run_pass "${TEST_KEY_BASE}-install" \ cylc install \ -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ - --flow-name=$NAME \ + --flow-name="$FLOW" \ --no-run-name run_pass "${TEST_KEY_BASE}-run" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug #------------------------------------------------------------------------------- -file_cmp "$TEST_KEY" "$SUITE_RUN_DIR/file" <<<'20121231T1200Z' -cylc clean $NAME +file_cmp "$TEST_KEY" "$FLOW_RUN_DIR/file" <<<'20121231T1200Z' +cylc clean "$FLOW" exit 0 diff --git a/t/rose-task-run/12-app-prune-integer.t b/t/rose-task-run/12-app-prune-integer.t index 1b9c8415cf..f41fb7a01c 100755 --- a/t/rose-task-run/12-app-prune-integer.t +++ b/t/rose-task-run/12-app-prune-integer.t @@ -33,11 +33,9 @@ fi # Run the suite. export CYLC_CONF_PATH= export ROSE_CONF_PATH= -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) +get_reg OPTS=( - "--flow-name=$NAME" + "--flow-name=$FLOW" --no-run-name ) if [[ -n ${JOB_HOST:-} ]]; then @@ -53,37 +51,36 @@ run_pass "${TEST_KEY}" \ TEST_KEY="${TEST_KEY_BASE}-run" run_pass "${TEST_KEY}" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug #------------------------------------------------------------------------------- TEST_KEY=$TEST_KEY_BASE-work -run_fail "$TEST_KEY.1" ls -d $HOME/cylc-run/$NAME/work/1 -run_fail "$TEST_KEY.2" ls -d $HOME/cylc-run/$NAME/work/2 -run_pass "$TEST_KEY.3" ls -d $HOME/cylc-run/$NAME/work/3 +run_fail "$TEST_KEY.1" ls -d "$FLOW_RUN_DIR/work/1" +run_fail "$TEST_KEY.2" ls -d "$FLOW_RUN_DIR/work/2" +run_pass "$TEST_KEY.3" ls -d "$FLOW_RUN_DIR/work/3" #------------------------------------------------------------------------------- TEST_KEY=$TEST_KEY_BASE-share -run_fail "$TEST_KEY.1" ls -d $HOME/cylc-run/$NAME/share/cycle/1 -run_fail "$TEST_KEY.2" ls -d $HOME/cylc-run/$NAME/share/cycle/2 -run_pass "$TEST_KEY.3" ls -d $HOME/cylc-run/$NAME/share/cycle/3 +run_fail "$TEST_KEY.1" ls -d "$FLOW_RUN_DIR/share/cycle/1" +run_fail "$TEST_KEY.2" ls -d "$FLOW_RUN_DIR/share/cycle/2" +run_pass "$TEST_KEY.3" ls -d "$FLOW_RUN_DIR/share/cycle/3" #------------------------------------------------------------------------------- TEST_KEY=$TEST_KEY_BASE-archive TEST_KEY=$TEST_KEY_BASE-share -run_fail "$TEST_KEY.1" ls -d $HOME/cylc-run/$NAME/log/job/1 -run_pass "$TEST_KEY.1-tar" ls -d $HOME/cylc-run/$NAME/log/job-1.tar.gz -run_fail "$TEST_KEY.2" ls -d $HOME/cylc-run/$NAME/log/job/2 -run_pass "$TEST_KEY.2-tar" ls -d $HOME/cylc-run/$NAME/log/job-2.tar.gz -run_pass "$TEST_KEY.3" ls -d $HOME/cylc-run/$NAME/log/job/3 +run_fail "$TEST_KEY.1" ls -d "$FLOW_RUN_DIR/log/job/1" +run_pass "$TEST_KEY.1-tar" ls -d "$FLOW_RUN_DIR/log/job-1.tar.gz" +run_fail "$TEST_KEY.2" ls -d "$FLOW_RUN_DIR/log/job/2" +run_pass "$TEST_KEY.2-tar" ls -d "$FLOW_RUN_DIR/log/job-2.tar.gz" +run_pass "$TEST_KEY.3" ls -d "$FLOW_RUN_DIR/log/job/3" #------------------------------------------------------------------------------- TEST_KEY=$TEST_KEY_BASE-remote if [[ -n "$JOB_HOST" ]]; then - run_fail "$TEST_KEY.1" ssh "$JOB_HOST" "ls -d ~/cylc-run/$NAME/log/job/1" - run_fail "$TEST_KEY.2" ssh "$JOB_HOST" "ls -d ~/cylc-run/$NAME/log/job/2" - run_pass "$TEST_KEY.3" ssh "$JOB_HOST" "ls -d ~/cylc-run/$NAME/log/job/3" + run_fail "$TEST_KEY.1" ssh "$JOB_HOST" "ls -d $FLOW_RUN_DIR/log/job/1" + run_fail "$TEST_KEY.2" ssh "$JOB_HOST" "ls -d $FLOW_RUN_DIW/log/job/2" + run_pass "$TEST_KEY.3" ssh "$JOB_HOST" "ls -d $FLOW_RUN_DIW/log/job/3" fi #------------------------------------------------------------------------------- -cylc clean $NAME -#------------------------------------------------------------------------------- +purge exit 0 diff --git a/t/rose-task-run/13-app-arch-cmd-out.t b/t/rose-task-run/13-app-arch-cmd-out.t index 13bdbae419..7d4a859499 100755 --- a/t/rose-task-run/13-app-arch-cmd-out.t +++ b/t/rose-task-run/13-app-arch-cmd-out.t @@ -28,36 +28,34 @@ tests 4 # Run the suite, and wait for it to complete export CYLC_CONF_PATH= export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR="$(mktemp -d "${HOME}/cylc-run/rose-test-battery.XXXXXX")" -NAME="$(basename "${SUITE_RUN_DIR}")" +get_reg run_pass "${TEST_KEY_BASE}-install" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name run_pass "${TEST_KEY_BASE}-run" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug #------------------------------------------------------------------------------- -FOO_UUID=$(<"${SUITE_RUN_DIR}/foo-uuid") -grep -F "${FOO_UUID}" "${SUITE_RUN_DIR}/log/job/1/archive/NN/job.out" \ +FOO_UUID=$(<"${FLOW_RUN_DIR}/foo-uuid") +grep -F "${FOO_UUID}" "${FLOW_RUN_DIR}/log/job/1/archive/NN/job.out" \ >"${TEST_KEY_BASE}.out" # This ensures that the STDOUT of the "foo" command is only printed out once. file_cmp "${TEST_KEY_BASE}.out" "${TEST_KEY_BASE}.out" <<__OUT__ -[INFO] ${FOO_UUID} share/namelist/x.nl ${SUITE_RUN_DIR}/share/backup/x.nl +[INFO] ${FOO_UUID} share/namelist/x.nl ${FLOW_RUN_DIR}/share/backup/x.nl __OUT__ # This tests that the "foo" command has done what it is asked to do. file_cmp "${TEST_KEY_BASE}.content" \ - "${SUITE_RUN_DIR}/share/backup/x.nl" <<'__NL__' + "${FLOW_RUN_DIR}/share/backup/x.nl" <<'__NL__' &x MMXIV=2014, / __NL__ #------------------------------------------------------------------------------- -cylc clean "${NAME}" +purge exit 0 diff --git a/t/rose-task-run/14-app-prune-remove.t b/t/rose-task-run/14-app-prune-remove.t index aba9184040..2d95955d7f 100755 --- a/t/rose-task-run/14-app-prune-remove.t +++ b/t/rose-task-run/14-app-prune-remove.t @@ -28,37 +28,34 @@ tests 11 # Run the suite. export CYLC_CONF_PATH= export ROSE_CONF_PATH= -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) +get_reg TEST_KEY="${TEST_KEY_BASE}-install" run_pass "$TEST_KEY" \ cylc install \ -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ - --flow-name="$NAME" \ + --flow-name="$FLOW" \ --no-run-name TEST_KEY="${TEST_KEY_BASE}-run" run_pass "$TEST_KEY" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug #------------------------------------------------------------------------------- TEST_KEY=$TEST_KEY_BASE-log -run_fail "$TEST_KEY.1" ls -d $HOME/cylc-run/$NAME/log/job/20100101T0000Z -run_fail "$TEST_KEY.2" ls -d $HOME/cylc-run/$NAME/log/job/20100102T0000Z -run_fail "$TEST_KEY.3" ls -d $HOME/cylc-run/$NAME/log/job/20100103T0000Z -run_fail "$TEST_KEY.4" ls -d $HOME/cylc-run/$NAME/log/job/20100104T0000Z -run_pass "$TEST_KEY.5" ls -d $HOME/cylc-run/$NAME/log/job/20100105T0000Z +run_fail "$TEST_KEY.1" ls -d "$FLOW_RUN_DIR/log/job/20100101T0000Z" +run_fail "$TEST_KEY.2" ls -d "$FLOW_RUN_DIR/log/job/20100102T0000Z" +run_fail "$TEST_KEY.3" ls -d "$FLOW_RUN_DIR/log/job/20100103T0000Z" +run_fail "$TEST_KEY.4" ls -d "$FLOW_RUN_DIR/log/job/20100104T0000Z" +run_pass "$TEST_KEY.5" ls -d "$FLOW_RUN_DIR/log/job/20100105T0000Z" #------------------------------------------------------------------------------- TEST_KEY=$TEST_KEY_BASE-archived -run_fail "$TEST_KEY.1" ls -d $HOME/cylc-run/$NAME/log/job-20100101T0000Z.tar.gz -run_fail "$TEST_KEY.2" ls -d $HOME/cylc-run/$NAME/log/job-20100102T0000Z.tar.gz -run_fail "$TEST_KEY.3" ls -d $HOME/cylc-run/$NAME/log/job-20100103T0000Z.tar.gz -run_pass "$TEST_KEY.4" ls -d $HOME/cylc-run/$NAME/log/job-20100104T0000Z.tar.gz -#------------------------------------------------------------------------------- -cylc clean $NAME +run_fail "$TEST_KEY.1" ls -d "$FLOW_RUN_DIR/log/job-20100101T0000Z.tar.gz" +run_fail "$TEST_KEY.2" ls -d "$FLOW_RUN_DIR/log/job-20100102T0000Z.tar.gz" +run_fail "$TEST_KEY.3" ls -d "$FLOW_RUN_DIR/log/job-20100103T0000Z.tar.gz" +run_pass "$TEST_KEY.4" ls -d "$FLOW_RUN_DIR/log/job-20100104T0000Z.tar.gz" #------------------------------------------------------------------------------- +purge exit 0 diff --git a/t/rose-task-run/15-app-prune-extglob.t b/t/rose-task-run/15-app-prune-extglob.t index d4748f35c9..5676d8b222 100755 --- a/t/rose-task-run/15-app-prune-extglob.t +++ b/t/rose-task-run/15-app-prune-extglob.t @@ -24,27 +24,25 @@ tests 3 export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR=$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX') -NAME="$(basename "${SUITE_RUN_DIR}")" #------------------------------------------------------------------------------- +get_reg TEST_KEY="${TEST_KEY_BASE}-install" run_pass "${TEST_KEY}" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name TEST_KEY="${TEST_KEY_BASE}-run" run_pass "${TEST_KEY}" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host='localhost' \ --no-detach \ --debug TEST_KEY="${TEST_KEY_BASE}-prune.log" sed 's/[0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0-9]*/YYYY-MM-DDTHHMM/g'\ - "${SUITE_RUN_DIR}/prune.log" > stamp-removed.log + "${FLOW_RUN_DIR}/prune.log" > stamp-removed.log sed '/^\[INFO\] YYYY-MM-DDTHHMM export ROSE_TASK_CYCLE_TIME=/p; /^\[INFO\] YYYY-MM-DDTHHMM delete: /!d' \ "stamp-removed.log" >'edited-prune.log' @@ -60,5 +58,5 @@ file_cmp "${TEST_KEY}" 'edited-prune.log' <<__LOG__ [INFO] YYYY-MM-DDTHHMM delete: work/20150102T0000Z/creator/rose-app-run.conf __LOG__ #------------------------------------------------------------------------------- -cylc clean "${NAME}" +purge exit 0 diff --git a/t/rose-task-run/16-app-prune-point.t b/t/rose-task-run/16-app-prune-point.t index a4ded109f6..f1da82f0c0 100755 --- a/t/rose-task-run/16-app-prune-point.t +++ b/t/rose-task-run/16-app-prune-point.t @@ -26,21 +26,19 @@ tests 3 export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR=$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX') -NAME="$(basename "${SUITE_RUN_DIR}")" #------------------------------------------------------------------------------- +get_reg TEST_KEY="${TEST_KEY_BASE}-install" run_pass "${TEST_KEY}" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name TEST_KEY="${TEST_KEY_BASE}-run" run_pass "${TEST_KEY}" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host='localhost' \ --debug \ @@ -48,7 +46,7 @@ run_pass "${TEST_KEY}" \ TEST_KEY="${TEST_KEY_BASE}-prune.log" sed 's/[0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0-9]*/YYYY-MM-DDTHHMM/g'\ - "${SUITE_RUN_DIR}/prune.log" > stamp-removed.log + "${FLOW_RUN_DIR}/prune.log" > stamp-removed.log sed '/^\[INFO\] YYYY-MM-DDTHHMM export ROSE_TASK_CYCLE_TIME=/p; /^\[INFO\] YYYY-MM-DDTHHMM delete: /!d' \ @@ -63,5 +61,5 @@ file_cmp "${TEST_KEY}" 'edited-prune.log' <<__LOG__ [INFO] YYYY-MM-DDTHHMM delete: work/19800101T0000Z __LOG__ #------------------------------------------------------------------------------- -cylc clean "${NAME}" +purge exit 0 diff --git a/t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t b/t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t index e98b057052..318ba08288 100755 --- a/t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t +++ b/t/rose-task-run/17-app-prune-glob-as-cycle-fmt.t @@ -25,20 +25,18 @@ tests 3 export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR=$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX') -NAME="$(basename "${SUITE_RUN_DIR}")" #------------------------------------------------------------------------------- +get_reg TEST_KEY="${TEST_KEY_BASE}-install" run_pass "${TEST_KEY}" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name TEST_KEY="${TEST_KEY_BASE}-run" run_pass "${TEST_KEY}" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host='localhost' \ --debug \ @@ -46,7 +44,7 @@ run_pass "${TEST_KEY}" \ TEST_KEY="${TEST_KEY_BASE}-prune.log" sed 's/[0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0:9]*/YYYY-MM-DDTHHMM/g'\ - "${SUITE_RUN_DIR}/prune.log" > stamp-removed.log + "${FLOW_RUN_DIR}/prune.log" > stamp-removed.log sed '/^\[INFO\] YYYY-MM-DDTHHMM export ROSE_TASK_CYCLE_TIME=/p; /^\[INFO\] YYYY-MM-DDTHHMM delete: /!d' \ "stamp-removed.log" >'edited-prune.log' @@ -57,5 +55,5 @@ file_cmp "${TEST_KEY}" 'edited-prune.log' <<__LOG__ [INFO] YYYY-MM-DDTHHMM delete: share/hello-venus-at-19700101T0000Z.txt __LOG__ #------------------------------------------------------------------------------- -cylc clean "${NAME}" +purge exit 0 diff --git a/t/rose-task-run/18-app-fcm-make-ctx-name.t b/t/rose-task-run/18-app-fcm-make-ctx-name.t index da7560b738..5a94839d0d 100755 --- a/t/rose-task-run/18-app-fcm-make-ctx-name.t +++ b/t/rose-task-run/18-app-fcm-make-ctx-name.t @@ -47,28 +47,26 @@ fi #------------------------------------------------------------------------------- tests 3 export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" #------------------------------------------------------------------------------- -SUITE_RUN_DIR="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" -NAME="$(basename "${SUITE_RUN_DIR}")" +get_reg run_pass "${TEST_KEY_BASE}-install" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name \ -S "HOST='${JOB_HOST}'" run_pass "${TEST_KEY_BASE}-run" \ timeout 120 \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host='localhost' \ --no-detach \ --debug #------------------------------------------------------------------------------- ssh -n -oBatchMode=yes "${JOB_HOST}" \ - cat "cylc-run/${NAME}/share/hello.txt" >'hello.txt' + cat "cylc-run/${FLOW}/share/hello.txt" >'hello.txt' file_cmp "${TEST_KEY_BASE}" 'hello.txt' <<<'Hello World!' #------------------------------------------------------------------------------- -cylc clean "${NAME}" +purge exit 0 diff --git a/t/rose-task-run/20-app-fcm-make-dest.t b/t/rose-task-run/20-app-fcm-make-dest.t index c7c82c690c..5cc16e1bd6 100755 --- a/t/rose-task-run/20-app-fcm-make-dest.t +++ b/t/rose-task-run/20-app-fcm-make-dest.t @@ -50,21 +50,19 @@ fi #------------------------------------------------------------------------------- tests 3 export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" #------------------------------------------------------------------------------- -SUITE_RUN_DIR="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" -NAME="$(basename "${SUITE_RUN_DIR}")" +get_reg run_pass "${TEST_KEY_BASE}-install" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name -S "HOST=\"${JOB_HOST}\"" \ -S "GREET=\"${GREET}\"" run_pass "${TEST_KEY_BASE}-run" \ timeout 120 \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --no-detach \ --debug \ @@ -72,18 +70,18 @@ run_pass "${TEST_KEY_BASE}-run" \ #------------------------------------------------------------------------------- JOB_HOST_HOME=$(ssh -n -oBatchMode=yes "${JOB_HOST}" 'echo "${HOME}"' | tail -1) ssh -n -oBatchMode=yes "${JOB_HOST}" \ - cat "cylc-run/${NAME}/share/hello.txt" >'hello.txt' + cat "cylc-run/${FLOW}/share/hello.txt" >'hello.txt' if [[ -n "${GREET}" ]]; then file_cmp "${TEST_KEY_BASE}" 'hello.txt' <<__TXT__ -${JOB_HOST_HOME}/cylc-run/${NAME}/opt/greet/build/bin/hello +${JOB_HOST_HOME}/cylc-run/${FLOW}/opt/greet/build/bin/hello Hello World! __TXT__ else file_cmp "${TEST_KEY_BASE}" 'hello.txt' <<__TXT__ -${JOB_HOST_HOME}/cylc-run/${NAME}/opt/hello/build/bin/hello +${JOB_HOST_HOME}/cylc-run/${FLOW}/opt/hello/build/bin/hello Hello World! __TXT__ fi #------------------------------------------------------------------------------- -cylc clean "${NAME}" +purge exit 0 diff --git a/t/rose-task-run/24-app-fcm-make-fast.t b/t/rose-task-run/24-app-fcm-make-fast.t index 23b7420351..4270ea50c7 100755 --- a/t/rose-task-run/24-app-fcm-make-fast.t +++ b/t/rose-task-run/24-app-fcm-make-fast.t @@ -35,50 +35,49 @@ fi tests 7 export CYLC_CONF_PATH= export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" mkdir 'fast' MTIME_OF_FAST_BEFORE=$(stat '-c%y' 'fast') #------------------------------------------------------------------------------- -SUITE_RUN_DIR="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" -NAME="$(basename "${SUITE_RUN_DIR}")" +get_reg run_pass "${TEST_KEY_BASE}-install" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ - -S "FAST_DEST_ROOT='${PWD}/fast'" \ + --flow-name="${FLOW}" \ + -S "FAST_DEST_ROOT='${PWD}/fast'" \ --no-run-name run_pass "${TEST_KEY_BASE}-run" \ timeout 120 \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host='localhost' \ --no-detach \ --debug + #------------------------------------------------------------------------------- # Permission modes of make directory should be the same as a normal directory -mkdir "${SUITE_RUN_DIR}/share/hello-make-perm-mode-test" +mkdir "${FLOW_RUN_DIR}/share/hello-make-perm-mode-test" run_pass "${TEST_KEY_BASE}-perm-mode" test \ - "$(stat -c'%a' "${SUITE_RUN_DIR}/share/hello-make-perm-mode-test")" = \ - "$(stat -c'%a' "${SUITE_RUN_DIR}/share/hello-make")" -rmdir "${SUITE_RUN_DIR}/share/hello-make-perm-mode-test" + "$(stat -c'%a' "${FLOW_RUN_DIR}/share/hello-make-perm-mode-test")" = \ + "$(stat -c'%a' "${FLOW_RUN_DIR}/share/hello-make")" +rmdir "${FLOW_RUN_DIR}/share/hello-make-perm-mode-test" # Executable runs OK -file_cmp "${TEST_KEY_BASE}" "${SUITE_RUN_DIR}/share/hello.txt" <<__TXT__ -${SUITE_RUN_DIR}/share/hello-make/build/bin/hello +file_cmp "${TEST_KEY_BASE}" "${FLOW_RUN_DIR}/share/hello.txt" <<__TXT__ +${FLOW_RUN_DIR}/share/hello-make/build/bin/hello Hello World! __TXT__ # Logs HOST=$(hostname) file_grep "${TEST_KEY_BASE}.log" \ - "\\[info\\] dest=${USER}@${HOST}:${PWD}/fast/hello-make.1.${NAME}" \ - "${SUITE_RUN_DIR}/share/hello-make/fcm-make.log" + "\\[info\\] dest=${USER}@${HOST}:${PWD}/fast/hello-make.1.${FLOW}" \ + "${FLOW_RUN_DIR}/share/hello-make/fcm-make.log" file_grep "${TEST_KEY_BASE}-bin.log" \ - "\\[info\\] dest=${USER}@${HOST}:${PWD}/fast/hello-make-bin.1.${NAME}" \ - "${SUITE_RUN_DIR}/share/hello-make/fcm-make-bin.log" + "\\[info\\] dest=${USER}@${HOST}:${PWD}/fast/hello-make-bin.1.${FLOW}" \ + "${FLOW_RUN_DIR}/share/hello-make/fcm-make-bin.log" # Prove that the fast directory has been modified MTIME_OF_FAST_AFTER=$(stat '-c%y' 'fast') run_pass "${TEST_KEY_BASE}-mtime-of-fast" \ test "${MTIME_OF_FAST_BEFORE}" '!=' "${MTIME_OF_FAST_AFTER}" #------------------------------------------------------------------------------- -cylc clean "${NAME}" +purge exit 0 diff --git a/t/rose-task-run/25-app-fcm-make-new-mode.t b/t/rose-task-run/25-app-fcm-make-new-mode.t index b262392818..d94cd6f1f1 100755 --- a/t/rose-task-run/25-app-fcm-make-new-mode.t +++ b/t/rose-task-run/25-app-fcm-make-new-mode.t @@ -34,37 +34,36 @@ fi #------------------------------------------------------------------------------- tests 5 export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" -NAME="$(basename "${SUITE_RUN_DIR}")" - -# Add some garbage before running the suite -mkdir -p "${SUITE_RUN_DIR}/share/hello-make/junk2" -touch "${SUITE_RUN_DIR}/share/hello-make/junk1" +get_reg run_pass "${TEST_KEY_BASE}-install" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name + +# Add some garbage before running the suite +mkdir -p "${FLOW_RUN_DIR}/share/hello-make/junk2" +touch "${FLOW_RUN_DIR}/share/hello-make/junk1" + run_pass "${TEST_KEY_BASE}-run" \ timeout 120 \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host='localhost' \ --no-detach \ --debug #------------------------------------------------------------------------------- -file_cmp "${TEST_KEY_BASE}" "${SUITE_RUN_DIR}/share/hello.txt" <<__TXT__ -${SUITE_RUN_DIR}/share/hello-make/build/bin/hello +file_cmp "${TEST_KEY_BASE}" "${FLOW_RUN_DIR}/share/hello.txt" <<__TXT__ +${FLOW_RUN_DIR}/share/hello-make/build/bin/hello Hello World! __TXT__ file_grep "${TEST_KEY_BASE}-fcm-make.log" \ - '\[info\] mode=new' "${SUITE_RUN_DIR}/share/hello-make/fcm-make.log" + '\[info\] mode=new' "${FLOW_RUN_DIR}/share/hello-make/fcm-make.log" run_fail "${TEST_KEY_BASE}" ls \ - "${SUITE_RUN_DIR}/share/hello-make/junk1" \ - "${SUITE_RUN_DIR}/share/hello-make/junk2" + "${FLOW_RUN_DIR}/share/hello-make/junk1" \ + "${FLOW_RUN_DIR}/share/hello-make/junk2" #------------------------------------------------------------------------------- -cylc clean "${NAME}" +purge exit 0 diff --git a/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t b/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t index 379ccc2e9a..4c9cb02953 100755 --- a/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t +++ b/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t @@ -45,26 +45,23 @@ fi #------------------------------------------------------------------------------- tests 4 export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" - -SUITE_RUN_DIR="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" -NAME="$(basename "${SUITE_RUN_DIR}")" +get_reg # Add some garbage before running the suite ssh -n -oBatchMode=yes "${JOB_HOST}" \ - "mkdir -p 'cylc-run/${NAME}/share/hello-make/junk2'" + "mkdir -p 'cylc-run/${FLOW}/share/hello-make/junk2'" ssh -n -oBatchMode=yes "${JOB_HOST}" \ - "touch 'cylc-run/${NAME}/share/hello-make/junk1'" + "touch 'cylc-run/${FLOW}/share/hello-make/junk1'" run_pass "${TEST_KEY_BASE}-install" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name run_pass "${TEST_KEY_BASE}-run" \ timeout 120 \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host='localhost' \ -S "HOST='${JOB_HOST}'" \ @@ -72,11 +69,11 @@ run_pass "${TEST_KEY_BASE}-run" \ --debug #------------------------------------------------------------------------------- ssh -n -oBatchMode=yes "${JOB_HOST}" \ - cat "cylc-run/${NAME}/share/hello.txt" >'hello.txt' + cat "cylc-run/${FLOW}/share/hello.txt" >'hello.txt' file_cmp "${TEST_KEY_BASE}" 'hello.txt' <<<'Hello World!' run_fail "${TEST_KEY_BASE}" \ ssh -n -oBatchMode=yes "${JOB_HOST}" \ - "ls 'cylc-run/${NAME}/share/hello-make/junk'*" + "ls 'cylc-run/${FLOW}/share/hello-make/junk'*" #------------------------------------------------------------------------------- -cylc clean "${NAME}" +purge exit 0 diff --git a/t/rose-task-run/28-env-path-run-path.t b/t/rose-task-run/28-env-path-run-path.t index 3c2ccba95d..67a1b83e7a 100755 --- a/t/rose-task-run/28-env-path-run-path.t +++ b/t/rose-task-run/28-env-path-run-path.t @@ -24,28 +24,26 @@ tests 3 export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR=$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX') -NAME="$(basename "${SUITE_RUN_DIR}")" #------------------------------------------------------------------------------- +get_reg TEST_KEY="${TEST_KEY_BASE}-install" run_pass "${TEST_KEY}" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name TEST_KEY="${TEST_KEY_BASE}-run" run_pass "${TEST_KEY}" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host='localhost' \ --no-detach \ --debug TEST_KEY="${TEST_KEY_BASE}-hello.txt" -file_cmp "${TEST_KEY}" "${SUITE_RUN_DIR}/hello.txt" <<__HELLO__ +file_cmp "${TEST_KEY}" "${FLOW_RUN_DIR}/hello.txt" <<__HELLO__ Hello Earth! __HELLO__ #------------------------------------------------------------------------------- -cylc clean "${NAME}" +purge exit 0 diff --git a/t/rose-task-run/29-app-prune-extglob-remote.t b/t/rose-task-run/29-app-prune-extglob-remote.t index b395506a5b..aa0b42a25c 100755 --- a/t/rose-task-run/29-app-prune-extglob-remote.t +++ b/t/rose-task-run/29-app-prune-extglob-remote.t @@ -37,27 +37,25 @@ fi tests 3 export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR=$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX') -NAME="$(basename "${SUITE_RUN_DIR}")" #------------------------------------------------------------------------------- +get_reg TEST_KEY="${TEST_KEY_BASE}-install" run_pass "${TEST_KEY}" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name TEST_KEY="${TEST_KEY_BASE}-run" run_pass "${TEST_KEY}" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --host='localhost' \ ${JOB_HOST_OPT} \ --no-detach \ --debug TEST_KEY="${TEST_KEY_BASE}-prune.log" sed 's/[0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0-9]*/YYYY-MM-DDTHHMM/g'\ - "${SUITE_RUN_DIR}/prune.log" > stamp-removed.log + "${FLOW_RUN_DIR}/prune.log" > stamp-removed.log sed '/^\[INFO\] YYYY-MM-DDTHHMM export ROSE_TASK_CYCLE_TIME=/p; /^\[INFO\] YYYY-MM-DDTHHMM delete: /!d' \ "stamp-removed.log" >'edited-prune.log' @@ -73,5 +71,5 @@ file_cmp "${TEST_KEY}" 'edited-prune.log' <<__LOG__ [INFO] YYYY-MM-DDTHHMM delete: work/20150101T0000Z __LOG__ #------------------------------------------------------------------------------- -cylc clean "${NAME}" +purge exit 0 diff --git a/t/rose-task-run/30-app-arch-opt-source.t b/t/rose-task-run/30-app-arch-opt-source.t index b0363a10d2..cbe50fc2f8 100755 --- a/t/rose-task-run/30-app-arch-opt-source.t +++ b/t/rose-task-run/30-app-arch-opt-source.t @@ -28,17 +28,16 @@ tests 8 # Run the suite, and wait for it to complete export CYLC_CONF_PATH= export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR="$(mktemp -d "${HOME}/cylc-run/rose-test-battery.XXXXXX")" -NAME="$(basename "${SUITE_RUN_DIR}")" + +get_reg run_pass "${TEST_KEY_BASE}-install" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name run_pass "${TEST_KEY_BASE}-run" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host=localhost \ --no-detach \ @@ -47,37 +46,37 @@ run_pass "${TEST_KEY_BASE}-run" \ TEST_KEY="${TEST_KEY_BASE}-job.status" file_grep "${TEST_KEY}-archive1-01" \ 'CYLC_JOB_EXIT=SUCCEEDED' \ - "${SUITE_RUN_DIR}/log/job/1/archive1/01/job.status" + "${FLOW_RUN_DIR}/log/job/1/archive1/01/job.status" file_grep "${TEST_KEY}-archive2-01" \ 'CYLC_JOB_EXIT=EXIT' \ - "${SUITE_RUN_DIR}/log/job/1/archive2/01/job.status" + "${FLOW_RUN_DIR}/log/job/1/archive2/01/job.status" file_grep "${TEST_KEY}-archive2-02" \ 'CYLC_JOB_EXIT=SUCCEEDED' \ - "${SUITE_RUN_DIR}/log/job/1/archive2/02/job.status" + "${FLOW_RUN_DIR}/log/job/1/archive2/02/job.status" TEST_KEY="${TEST_KEY_BASE}-find" -(cd "${SUITE_RUN_DIR}/share/backup" && find . -type f) | sort >"${TEST_KEY}.out" +(cd "${FLOW_RUN_DIR}/share/backup" && find . -type f) | sort >"${TEST_KEY}.out" file_cmp "${TEST_KEY}.out" "${TEST_KEY}.out" <<'__FIND__' ./archive1.d/2014.txt ./archive1.d/2016.txt ./archive2.d/2015.txt __FIND__ sed -n 's/^\[INFO\] \([+!=0] [^ ]*\) .*$/\1/p' \ - "${SUITE_RUN_DIR}/log/job/1/archive"*"/0"*"/job.out" \ + "${FLOW_RUN_DIR}/log/job/1/archive"*"/0"*"/job.out" \ | sort >'job.out.sorted' sort >'job.out.expected' <<__LOG__ -! ${SUITE_RUN_DIR}/share/backup/archive2.d/ -+ ${SUITE_RUN_DIR}/share/backup/archive1.d/ -+ ${SUITE_RUN_DIR}/share/backup/archive2.d/ -0 ${SUITE_RUN_DIR}/share/backup/nobody.d/ +! ${FLOW_RUN_DIR}/share/backup/archive2.d/ ++ ${FLOW_RUN_DIR}/share/backup/archive1.d/ ++ ${FLOW_RUN_DIR}/share/backup/archive2.d/ +0 ${FLOW_RUN_DIR}/share/backup/nobody.d/ __LOG__ file_cmp "${TEST_KEY}-job.out.sorted" 'job.out.sorted' 'job.out.expected' sed -n 's/^\[FAIL\] \(! [^ ]*\) .*$/\1/p' \ - "${SUITE_RUN_DIR}/log/job/1/archive"*"/0"*"/job.err" \ + "${FLOW_RUN_DIR}/log/job/1/archive"*"/0"*"/job.err" \ | sort >'job.err.sorted' sort >'job.err.expected' <<__LOG__ -! ${SUITE_RUN_DIR}/share/backup/archive2.d/ +! ${FLOW_RUN_DIR}/share/backup/archive2.d/ __LOG__ file_cmp "${TEST_KEY}-job.err.sorted" 'job.err.sorted' 'job.err.expected' #------------------------------------------------------------------------------- -cylc clean "${NAME}" +purge exit 0 diff --git a/t/rose-task-run/31-app-bunch.t b/t/rose-task-run/31-app-bunch.t index 94edb2f152..4bdba5ecdc 100755 --- a/t/rose-task-run/31-app-bunch.t +++ b/t/rose-task-run/31-app-bunch.t @@ -33,25 +33,24 @@ SKIP_PATTERN="\[SKIP\] [0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0:9]*" #------------------------------------------------------------------------------- # Run the suite, and wait for it to complete export ROSE_CONF_PATH= -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) + +get_reg TEST_KEY="${TEST_KEY_BASE}-install" run_pass "$TEST_KEY" \ cylc install \ -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name TEST_KEY="${TEST_KEY_BASE}-run" run_pass "$TEST_KEY" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --host=localhost \ --no-detach \ --debug #------------------------------------------------------------------------------- CYCLE=20100101T0000Z -LOG_DIR="$SUITE_RUN_DIR/log/job/$CYCLE" +LOG_DIR="$FLOW_RUN_DIR/log/job/$CYCLE" #------------------------------------------------------------------------------- # Testing successful runs #------------------------------------------------------------------------------- @@ -251,5 +250,5 @@ file_grep "$TEST_KEY_PREFIX-TOTAL-RAN" \ "$INFO_PATTERN TOTAL: 8" \ "$FILE" #------------------------------------------------------------------------------- -cylc clean $NAME +purge exit 0 diff --git a/t/rose-task-run/32-app-arch-compressed.t b/t/rose-task-run/32-app-arch-compressed.t index 548a45a0c8..1b4bf97c14 100755 --- a/t/rose-task-run/32-app-arch-compressed.t +++ b/t/rose-task-run/32-app-arch-compressed.t @@ -28,17 +28,16 @@ tests 4 # Run the suite, and wait for it to complete export CYLC_CONF_PATH= export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR="$(mktemp -d "${HOME}/cylc-run/rose-test-battery.XXXXXX")" -NAME="$(basename "${SUITE_RUN_DIR}")" + +get_reg run_pass "${TEST_KEY_BASE}-install" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name run_pass "${TEST_KEY_BASE}-run" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host=localhost \ --no-detach \ @@ -47,13 +46,13 @@ run_pass "${TEST_KEY_BASE}-run" \ TEST_KEY="${TEST_KEY_BASE}-job.status" file_grep "${TEST_KEY}-archive-01" \ 'CYLC_JOB_EXIT=SUCCEEDED' \ - "${SUITE_RUN_DIR}/log/job/1/archive/01/job.status" + "${FLOW_RUN_DIR}/log/job/1/archive/01/job.status" TEST_KEY="${TEST_KEY_BASE}-find" -(cd "${SUITE_RUN_DIR}/share/backup" && find . -type f) | sort >"${TEST_KEY}.out" +(cd "${FLOW_RUN_DIR}/share/backup" && find . -type f) | sort >"${TEST_KEY}.out" file_cmp "${TEST_KEY}.out" "${TEST_KEY}.out" <<'__FIND__' ./archive.d/2016.txt.gz ./archive.d/whatever.tar.gz __FIND__ #------------------------------------------------------------------------------- -cylc clean "${NAME}" +purge exit 0 diff --git a/t/rose-task-run/33-app-prune-cycle-format.t b/t/rose-task-run/33-app-prune-cycle-format.t index 676c969a2d..8b163b447e 100755 --- a/t/rose-task-run/33-app-prune-cycle-format.t +++ b/t/rose-task-run/33-app-prune-cycle-format.t @@ -25,20 +25,18 @@ tests 3 export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR=$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX') -NAME="$(basename "${SUITE_RUN_DIR}")" #------------------------------------------------------------------------------- +get_reg TEST_KEY="${TEST_KEY_BASE}-install" run_pass "${TEST_KEY}" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name TEST_KEY="${TEST_KEY_BASE}-run" run_pass "${TEST_KEY}" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host='localhost' \ --debug \ @@ -46,7 +44,7 @@ run_pass "${TEST_KEY}" \ TEST_KEY="${TEST_KEY_BASE}-prune.log" sed 's/[0-9]*-[0-9]*-[0-9]*T[0-9]*:[0-9]*:[0-9]*+[0-9]*/YYYY-MM-DDTHHMM/g'\ - "${SUITE_RUN_DIR}/prune.log" > stamp-removed.log + "${FLOW_RUN_DIR}/prune.log" > stamp-removed.log sed '/^\[INFO\] YYYY-MM-DDTHHMM export ROSE_TASK_CYCLE_TIME=/p; /^\[INFO\] YYYY-MM-DDTHHMM delete: /!d' \ "stamp-removed.log" >'edited-prune.log' @@ -58,5 +56,5 @@ file_cmp "${TEST_KEY}" 'edited-prune.log' <<__LOG__ [INFO] YYYY-MM-DDTHHMM delete: share/hello-venus-in-1970.txt __LOG__ #------------------------------------------------------------------------------- -cylc clean "${NAME}" +purge exit 0 diff --git a/t/rose-task-run/34-app-prune-hosts-sharing-fs.t b/t/rose-task-run/34-app-prune-hosts-sharing-fs.t index 25eaea23b8..436c8256ea 100755 --- a/t/rose-task-run/34-app-prune-hosts-sharing-fs.t +++ b/t/rose-task-run/34-app-prune-hosts-sharing-fs.t @@ -34,22 +34,20 @@ fi tests 5 export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR=$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX') -NAME="$(basename "${SUITE_RUN_DIR}")" #------------------------------------------------------------------------------- +get_reg TEST_KEY="${TEST_KEY_BASE}-install" run_pass "${TEST_KEY}" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name \ -S "JOB_HOST_1=\"${JOB_HOST_1}\"" \ -S "JOB_HOST_2=\"${JOB_HOST_2}\"" TEST_KEY="${TEST_KEY_BASE}-run" run_pass "${TEST_KEY}" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host='localhost' \ --debug \ @@ -58,24 +56,24 @@ run_pass "${TEST_KEY}" \ TEST_KEY="${TEST_KEY_BASE}-prune.log" grep \ "ssh .* \\(${JOB_HOST_1}\\|${JOB_HOST_2}\\) .* share/cycle/19700101T0000Z;" \ - "${SUITE_RUN_DIR}/prune.log" >'prune-ssh.log' + "${FLOW_RUN_DIR}/prune.log" >'prune-ssh.log' run_pass "${TEST_KEY}-prune-ssh-wc-l" test "$(wc -l <'prune-ssh.log')" -eq 2 if head -n 1 'prune-ssh.log' | grep ".* ${JOB_HOST_1} .*"; then run_pass "${TEST_KEY}-delete-1" \ grep -q "delete: ${JOB_HOST_1}:share/cycle/19700101T0000Z" \ - "${SUITE_RUN_DIR}/prune.log" + "${FLOW_RUN_DIR}/prune.log" run_fail "${TEST_KEY}-delete-2" \ grep -q "delete: ${JOB_HOST_2}:share/cycle/19700101T0000Z" \ - "${SUITE_RUN_DIR}/prune.log" + "${FLOW_RUN_DIR}/prune.log" else run_pass "${TEST_KEY}-delete-2" \ grep -q "delete: ${JOB_HOST_2}:share/cycle/19700101T0000Z" \ - "${SUITE_RUN_DIR}/prune.log" + "${FLOW_RUN_DIR}/prune.log" run_fail "${TEST_KEY}-delete-1" \ grep -q "delete: ${JOB_HOST_1}:share/cycle/19700101T0000Z" \ - "${SUITE_RUN_DIR}/prune.log" + "${FLOW_RUN_DIR}/prune.log" fi #------------------------------------------------------------------------------- -cylc clean "${NAME}" +purge exit 0 diff --git a/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t b/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t index 2d526fa850..7474641af5 100755 --- a/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t +++ b/t/rose-task-run/35-app-prune-log-on-hosts-sharing-fs.t @@ -34,22 +34,20 @@ fi tests 5 export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR=$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX') -NAME="$(basename "${SUITE_RUN_DIR}")" #------------------------------------------------------------------------------- +get_reg TEST_KEY="${TEST_KEY_BASE}-install" run_pass "${TEST_KEY}" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name \ -S "JOB_HOST_1=\"${JOB_HOST_1}\"" \ -S "JOB_HOST_2=\"${JOB_HOST_2}\"" TEST_KEY="${TEST_KEY_BASE}-run" run_pass "${TEST_KEY}" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host='localhost' \ --debug \ @@ -58,24 +56,24 @@ run_pass "${TEST_KEY}" \ TEST_KEY="${TEST_KEY_BASE}-prune.log" grep \ "ssh .* \\(${JOB_HOST_1}\\|${JOB_HOST_2}\\).* ls. -d..* 19700101T0000Z" \ - "${SUITE_RUN_DIR}/prune.log" >'prune-ssh.log' + "${FLOW_RUN_DIR}/prune.log" >'prune-ssh.log' run_pass "${TEST_KEY}-prune-ssh-wc-l" test "$(wc -l <'prune-ssh.log')" -eq 2 if head -n 1 'prune-ssh.log' | grep ".* ${JOB_HOST_1} .*"; then run_pass "${TEST_KEY}-delete-1" \ grep -q "delete: ${JOB_HOST_1}:log/job/19700101T0000Z" \ - "${SUITE_RUN_DIR}/prune.log" + "${FLOW_RUN_DIR}/prune.log" run_fail "${TEST_KEY}-delete-2" \ grep -q "delete: ${JOB_HOST_2}:log/job/19700101T0000Z" \ - "${SUITE_RUN_DIR}/prune.log" + "${FLOW_RUN_DIR}/prune.log" else run_pass "${TEST_KEY}-delete-2" \ grep -q "delete: ${JOB_HOST_2}:log/job/19700101T0000Z" \ - "${SUITE_RUN_DIR}/prune.log" + "${FLOW_RUN_DIR}/prune.log" run_fail "${TEST_KEY}-delete-1" \ grep -q "delete: ${JOB_HOST_1}:log/job/19700101T0000Z" \ - "${SUITE_RUN_DIR}/prune.log" + "${FLOW_RUN_DIR}/prune.log" fi #------------------------------------------------------------------------------- -cylc clean "${NAME}" +purge exit 0 diff --git a/t/rose-task-run/36-app-arch-interrupted.t b/t/rose-task-run/36-app-arch-interrupted.t index 02235ceeff..80ae109aff 100755 --- a/t/rose-task-run/36-app-arch-interrupted.t +++ b/t/rose-task-run/36-app-arch-interrupted.t @@ -28,17 +28,16 @@ tests 5 # Run the suite, and wait for it to complete export CYLC_CONF_PATH= export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR="$(mktemp -d "${HOME}/cylc-run/rose-test-battery.XXXXXX")" -NAME="$(basename "${SUITE_RUN_DIR}")" + +get_reg run_pass "${TEST_KEY_BASE}-install" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name run_pass "${TEST_KEY_BASE}-run" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --host=localhost \ --no-detach \ --debug @@ -46,15 +45,15 @@ run_pass "${TEST_KEY_BASE}-run" \ TEST_KEY="${TEST_KEY_BASE}-job.status" file_grep "${TEST_KEY}-archive-01" \ 'CYLC_JOB_EXIT=EXIT' \ - "${SUITE_RUN_DIR}/log/job/1/archive/01/job.status" + "${FLOW_RUN_DIR}/log/job/1/archive/01/job.status" file_grep "${TEST_KEY}-archive-02" \ 'CYLC_JOB_EXIT=SUCCEEDED' \ - "${SUITE_RUN_DIR}/log/job/1/archive/02/job.status" + "${FLOW_RUN_DIR}/log/job/1/archive/02/job.status" TEST_KEY="${TEST_KEY_BASE}-find" -(cd "${SUITE_RUN_DIR}/share" && find . -type f) | sort >"${TEST_KEY}.out" +(cd "${FLOW_RUN_DIR}/share" && find . -type f) | sort >"${TEST_KEY}.out" file_cmp "${TEST_KEY}.out" "${TEST_KEY}.out" <<'__FIND__' ./new_whatever __FIND__ #------------------------------------------------------------------------------- -cylc clean "${NAME}" +purge exit 0 diff --git a/t/rose-task-run/37-app-bunch-rm-old.t b/t/rose-task-run/37-app-bunch-rm-old.t index 299af3d1fc..89e2d00229 100755 --- a/t/rose-task-run/37-app-bunch-rm-old.t +++ b/t/rose-task-run/37-app-bunch-rm-old.t @@ -26,26 +26,25 @@ tests 15 #------------------------------------------------------------------------------- # Run the suite, and wait for it to complete export ROSE_CONF_PATH= -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) + +get_reg TEST_KEY="${TEST_KEY_BASE}-install" run_pass "$TEST_KEY" \ cylc install \ -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ - --flow-name=$NAME \ + --flow-name="$FLOW" \ --no-run-name TEST_KEY="${TEST_KEY_BASE}-run" run_pass "$TEST_KEY" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug #------------------------------------------------------------------------------- CYCLE=20100101T0000Z -LOG_DIR="$SUITE_RUN_DIR/log/job/$CYCLE" +LOG_DIR="$FLOW_RUN_DIR/log/job/$CYCLE" #------------------------------------------------------------------------------- # Testing successful runs #------------------------------------------------------------------------------- @@ -72,11 +71,11 @@ done #------------------------------------------------------------------------------- # Run suite a second time # TODO: replace with cylc run --re-run / cylc clean -rm -rf "${HOME}/cylc-run/${NAME}/log" -rm -rf "${HOME}/cylc-run/${NAME}/.serivce/db" +rm -rf "${FLOW_RUN_DIR}/log" +rm -rf "${FLOW_RUN_DIR}/.serivce/db" run_pass "$TEST_KEY" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host=localhost \ --no-detach \ @@ -92,6 +91,5 @@ for ARGVALUE in 0 1 2 3; do $FILE done #------------------------------------------------------------------------------- - -cylc clean $NAME +purge exit 0 diff --git a/t/rose-task-run/38-app-bunch-counts.t b/t/rose-task-run/38-app-bunch-counts.t index e7ef272709..de65fa65f2 100755 --- a/t/rose-task-run/38-app-bunch-counts.t +++ b/t/rose-task-run/38-app-bunch-counts.t @@ -26,26 +26,25 @@ tests 17 #------------------------------------------------------------------------------- # Run the suite, and wait for it to complete export ROSE_CONF_PATH= -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) + +get_reg TEST_KEY="${TEST_KEY_BASE}-install" run_pass "$TEST_KEY" \ cylc install \ -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ - --flow-name="$NAME" \ + --flow-name="$FLOW" \ --no-run-name \ TEST_KEY="${TEST_KEY_BASE}-run" run_pass "$TEST_KEY" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug #------------------------------------------------------------------------------- CYCLE=1 -LOG_DIR="$SUITE_RUN_DIR/log/job/$CYCLE" +LOG_DIR="$FLOW_RUN_DIR/log/job/$CYCLE" #------------------------------------------------------------------------------- # Testing successful runs #------------------------------------------------------------------------------- @@ -114,5 +113,5 @@ TEST_KEY=${TEST_KEY_PREFIX}-TOTAL file_grep $TEST_KEY "\[INFO\] TOTAL: 5" $FILE #------------------------------------------------------------------------------- -cylc clean $NAME +purge exit 0 diff --git a/t/rose-task-run/39-app-prune-cycle-host.t b/t/rose-task-run/39-app-prune-cycle-host.t index 12d7ed1f45..ef6a17ebec 100755 --- a/t/rose-task-run/39-app-prune-cycle-host.t +++ b/t/rose-task-run/39-app-prune-cycle-host.t @@ -33,22 +33,20 @@ fi tests 3 export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" -RUND="$(mktemp -d --tmpdir="${HOME}/cylc-run" 'rose-test-battery.XXXXXX')" -NAME="$(basename "${RUND}")" #------------------------------------------------------------------------------- +get_reg TEST_KEY="${TEST_KEY_BASE}-install" run_pass "${TEST_KEY}" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name \ -S "JOB_HOST_1=\"${JOB_HOST_1}\"" \ -S "JOB_HOST_2=\"${JOB_HOST_2}\"" TEST_KEY="${TEST_KEY_BASE}-run" run_pass "${TEST_KEY}" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host='localhost' \ --debug \ @@ -62,5 +60,5 @@ run_fail "${TEST_KEY}-ssh-2" \ grep -q "ssh .* ${JOB_HOST_2} .* share/cycle/19700101T0000Z;" \ "${RUND}/prune.log" #------------------------------------------------------------------------------- -cylc clean "${NAME}" +purge exit 0 diff --git a/t/rose-task-run/40-app-arch-duplicate.t b/t/rose-task-run/40-app-arch-duplicate.t index 3cb9edb78d..85926a7249 100644 --- a/t/rose-task-run/40-app-arch-duplicate.t +++ b/t/rose-task-run/40-app-arch-duplicate.t @@ -28,24 +28,23 @@ tests 3 # Run the suite, and wait for it to complete export CYLC_CONF_PATH= export ROSE_CONF_PATH= -mkdir -p "${HOME}/cylc-run" -SUITE_RUN_DIR="$(mktemp -d "${HOME}/cylc-run/rose-test-battery.XXXXXX")" -NAME="$(basename "${SUITE_RUN_DIR}")" + +get_reg run_pass "${TEST_KEY_BASE}-install" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ - --flow-name="${NAME}" \ + --flow-name="${FLOW}" \ --no-run-name run_pass "${TEST_KEY_BASE}-run" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --host=localhost \ --no-detach \ --debug #------------------------------------------------------------------------------- TEST_KEY="${TEST_KEY_BASE}" file_grep "${TEST_KEY}" 'duplicate archive target: "foo"' \ - "${SUITE_RUN_DIR}/log/job/1/archive_fail_duplicate/NN/job.err" + "${FLOW_RUN_DIR}/log/job/1/archive_fail_duplicate/NN/job.err" #------------------------------------------------------------------------------- -cylc clean "${NAME}" +purge exit 0 diff --git a/t/rose-task-run/41-app-bunch-default-command.t b/t/rose-task-run/41-app-bunch-default-command.t index 060b92bfbc..e595f3f373 100755 --- a/t/rose-task-run/41-app-bunch-default-command.t +++ b/t/rose-task-run/41-app-bunch-default-command.t @@ -26,26 +26,25 @@ tests 27 #------------------------------------------------------------------------------- # Run the suite, and wait for it to complete export ROSE_CONF_PATH= -mkdir -p $HOME/cylc-run -SUITE_RUN_DIR=$(mktemp -d --tmpdir=$HOME/cylc-run 'rose-test-battery.XXXXXX') -NAME=$(basename $SUITE_RUN_DIR) + +get_reg TEST_KEY="${TEST_KEY_BASE}-install" run_pass "$TEST_KEY" \ cylc install \ -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ - --flow-name="$NAME" \ + --flow-name="$FLOW" \ --no-run-name TEST_KEY="${TEST_KEY_BASE}-run" run_pass "$TEST_KEY" \ cylc run \ - "${NAME}" \ + "${FLOW}" \ --abort-if-any-task-fails \ --host=localhost \ --no-detach \ --debug #------------------------------------------------------------------------------- CYCLE=20100101T0000Z -LOG_DIR="$SUITE_RUN_DIR/log/job/$CYCLE" +LOG_DIR="$FLOW_RUN_DIR/log/job/$CYCLE" #------------------------------------------------------------------------------- # Testing successful runs #------------------------------------------------------------------------------- @@ -76,10 +75,10 @@ done #------------------------------------------------------------------------------- # Run suite a second time # TODO: replace with cylc run --rerun / cylc clean -rm -rf "${HOME}/cylc-run/${NAME}/log" -rm -rf "${HOME}/cylc-run/${NAME}/.service/db" +rm -rf "${FLOW_RUN_DIR}/log" +rm -rf "${FLOW_RUN_DIR}/.service/db" run_pass "$TEST_KEY" \ - cylc run "${NAME}" \ + cylc run "${FLOW}" \ --abort-if-any-task-fails \ --host=localhost \ --no-detach \ @@ -87,9 +86,8 @@ run_pass "$TEST_KEY" \ #------------------------------------------------------------------------------- # Testing successful rerun #------------------------------------------------------------------------------- -for TASK in buncher_default buncher_import; do -#------------------------------------------------------------------------------- # Confirm launching set of commands +for TASK in buncher_default buncher_import; do TEST_KEY_PREFIX=launch-ok-2nd-run FILE=$LOG_DIR/$TASK/NN/job.out for INSTANCE in 0 1 2 3; do @@ -98,8 +96,7 @@ for TASK in buncher_default buncher_import; do "\[INFO\] [-0-9]*T[+:0-9]* $INSTANCE: added to pool"\ $FILE done -#------------------------------------------------------------------------------- done #------------------------------------------------------------------------------- -cylc clean $NAME +purge exit 0 diff --git a/t/rosie-id/00-basic.t b/t/rosie-id/00-basic.t index 8fcdedb7e9..107cbfc53e 100755 --- a/t/rosie-id/00-basic.t +++ b/t/rosie-id/00-basic.t @@ -118,8 +118,7 @@ file_cmp "$TEST_KEY.err" "$TEST_KEY.err" 'foo-aa000/flow.cylc' <<'__SUITE_RC__' @@ -131,14 +130,14 @@ file_cmp "$TEST_KEY.err" "$TEST_KEY.err" Date: Wed, 27 Jan 2021 17:07:37 +0000 Subject: [PATCH 47/78] tests: remove doctest prove test (done by pytest) --- t/docs/01-doctest.t | 39 --------------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 t/docs/01-doctest.t diff --git a/t/docs/01-doctest.t b/t/docs/01-doctest.t deleted file mode 100644 index 56255ac1c3..0000000000 --- a/t/docs/01-doctest.t +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- -# Run doctests in sphinx extensions. -#------------------------------------------------------------------------------- -THIS_TEST_HOME=$(pwd) -. "$(dirname "$0")/test_header" -#------------------------------------------------------------------------------- -if ! rose check-software --docs 2>'/dev/null'; then - skip_all "Software dependencies for documentation not met." -fi -#------------------------------------------------------------------------------- -FILES=($(find "${THIS_TEST_HOME}/sphinx/ext" -name "*.py")) -tests $(( ${#FILES[@]} * 2 )) -#------------------------------------------------------------------------------- -TERM= # Nasty solution to prevent control chars being printed in python output. -for file in "${FILES[@]}"; do - TEST_KEY="${TEST_KEY_BASE}-$(basename ${file})" - run_pass "${TEST_KEY}" python3 -m doctest "${file}" - file_cmp "${TEST_KEY}-err" "${TEST_KEY}.out" '/dev/null' -done -#------------------------------------------------------------------------------- -exit From 0fd58b4f8def015be634de74911d5e00e102562f Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Wed, 27 Jan 2021 17:11:13 +0000 Subject: [PATCH 48/78] tests: rosie-lookup/00 - add timeouts --- t/rosie-lookup/00-basic.t | 59 ++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/t/rosie-lookup/00-basic.t b/t/rosie-lookup/00-basic.t index 6f69d28680..b76ff29eb0 100755 --- a/t/rosie-lookup/00-basic.t +++ b/t/rosie-lookup/00-basic.t @@ -157,14 +157,14 @@ fi #------------------------------------------------------------------------------- TEST_KEY=$TEST_KEY_BASE-bad-prefix -run_fail "$TEST_KEY" rosie lookup --prefix=bar poetry +run_fail "$TEST_KEY" timeout 10 rosie lookup --prefix=bar poetry file_cmp "$TEST_KEY.out" "$TEST_KEY.out" foo-aa001/trunk@2 roses poetry Roses are Red,... @@ -325,7 +330,8 @@ __OUT__ file_cmp "$TEST_KEY.err" "$TEST_KEY.err" foo-aa001/trunk@2 roses poetry Roses are Red,... @@ -336,7 +342,8 @@ __OUT__ file_cmp "$TEST_KEY.err" "$TEST_KEY.err" Date: Wed, 27 Jan 2021 18:23:44 +0000 Subject: [PATCH 49/78] tests: fix rosie lookup hanging on mac os --- .github/workflows/test.yml | 26 ++++---------------------- t/rosie-lookup/00-basic.t | 3 +++ 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8d60a28865..409a4fafe1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -85,28 +85,6 @@ jobs: export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES __HERE__ cat "$HOME/.bashrc" - which python - - - name: Brew Install post - if: startsWith(matrix.os, 'macos') - run: | - set -x - which bash - which python - which python2 - which python3 - set +x - cat > mess <<__HERE__ - #!/usr/bin/env bash - set -x - which bash - which python - which python2 - which python3 - set +x - __HERE__ - chmod +x mess - ./mess - name: Apt-Get Install if: startsWith(matrix.os, 'ubuntu') @@ -182,6 +160,8 @@ jobs: timeout-minutes: 30 id: functest working-directory: rose + env: + OBJC_DISABLE_INITIALIZE_FORK_SAFETY: YES run: | # rose tests should pass first time around etc/bin/rose-test-battery -j 4 --state=save @@ -189,6 +169,8 @@ jobs: - name: Re-Run Fails if: failure() && steps.functest.outcome == 'failure' working-directory: rose + env: + OBJC_DISABLE_INITIALIZE_FORK_SAFETY: YES run: | # so we only re-run for debug purposes cylc scan --state=all --color=never diff --git a/t/rosie-lookup/00-basic.t b/t/rosie-lookup/00-basic.t index b76ff29eb0..164ce208c4 100755 --- a/t/rosie-lookup/00-basic.t +++ b/t/rosie-lookup/00-basic.t @@ -31,6 +31,9 @@ tests 75 # Setup Rose site/user configuration for the tests. export TZ='UTC' +# NOTE: if failing on MacOS try the following in ~/.bash_profile +# export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES + # Ignore case in file_cmp methods # (this is to allow case-insensitive host names) DIFF_CASE_INSENSITIVE=true From 9324db7c2a3f49109e2a7a7233090581b8b8cef2 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Wed, 27 Jan 2021 17:27:45 +0000 Subject: [PATCH 50/78] fcm_make: fix bug with hierarchical registrations --- metomi/rose/apps/fcm_make.py | 8 +++++++- t/rose-task-run/24-app-fcm-make-fast.t | 7 +++---- t/rose-task-run/24-app-fcm-make-fast/flow.cylc | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/metomi/rose/apps/fcm_make.py b/metomi/rose/apps/fcm_make.py index 0f444e1b89..06502be45c 100644 --- a/metomi/rose/apps/fcm_make.py +++ b/metomi/rose/apps/fcm_make.py @@ -124,7 +124,13 @@ def _invoke_fcm_make(self, app_runner, conf_tree, opts, args, uuid, task, if fast_root: # N.B. Name in "little endian", like cycle task ID prefix = ".".join([ - task.task_name, task.task_cycle_time, task.suite_name]) + task.task_name, + task.task_cycle_time, + # suite_name may be a hierarchical registration which + # isn't a safe prefix + task.suite_name.replace(os.sep, '_') + ]) + os.makedirs(fast_root, exist_ok=True) dest = mkdtemp(prefix=prefix, dir=fast_root) # N.B. Don't use app_runner.popen.get_cmd("rsync") as we are using # "rsync" for a local copy. diff --git a/t/rose-task-run/24-app-fcm-make-fast.t b/t/rose-task-run/24-app-fcm-make-fast.t index 4270ea50c7..06f4adb953 100755 --- a/t/rose-task-run/24-app-fcm-make-fast.t +++ b/t/rose-task-run/24-app-fcm-make-fast.t @@ -43,17 +43,16 @@ run_pass "${TEST_KEY_BASE}-install" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ --flow-name="${FLOW}" \ - -S "FAST_DEST_ROOT='${PWD}/fast'" \ --no-run-name run_pass "${TEST_KEY_BASE}-run" \ timeout 120 \ cylc run \ "${FLOW}" \ + -s "FAST_DEST_ROOT='${PWD}/fast'" \ --abort-if-any-task-fails \ --host='localhost' \ --no-detach \ --debug - #------------------------------------------------------------------------------- # Permission modes of make directory should be the same as a normal directory mkdir "${FLOW_RUN_DIR}/share/hello-make-perm-mode-test" @@ -69,10 +68,10 @@ __TXT__ # Logs HOST=$(hostname) file_grep "${TEST_KEY_BASE}.log" \ - "\\[info\\] dest=${USER}@${HOST}:${PWD}/fast/hello-make.1.${FLOW}" \ + "\\[info\\] dest=${USER}@${HOST}:${PWD}/fast/hello-make.1.${FLOW//\//_}" \ "${FLOW_RUN_DIR}/share/hello-make/fcm-make.log" file_grep "${TEST_KEY_BASE}-bin.log" \ - "\\[info\\] dest=${USER}@${HOST}:${PWD}/fast/hello-make-bin.1.${FLOW}" \ + "\\[info\\] dest=${USER}@${HOST}:${PWD}/fast/hello-make-bin.1.${FLOW//\//_}" \ "${FLOW_RUN_DIR}/share/hello-make/fcm-make-bin.log" # Prove that the fast directory has been modified MTIME_OF_FAST_AFTER=$(stat '-c%y' 'fast') diff --git a/t/rose-task-run/24-app-fcm-make-fast/flow.cylc b/t/rose-task-run/24-app-fcm-make-fast/flow.cylc index 32ae0b3550..0a62be3a06 100644 --- a/t/rose-task-run/24-app-fcm-make-fast/flow.cylc +++ b/t/rose-task-run/24-app-fcm-make-fast/flow.cylc @@ -9,7 +9,7 @@ [[root]] [[MAKE]] script = """ -rose task-run --app-key=make \ +rose task-run --app-key=make -v -v --debug \ --define=fast-dest-root-orig={{FAST_DEST_ROOT}} \ --define=fast-dest-root-cont={{FAST_DEST_ROOT}} """ From 41859705ddc4dbf10362d7d5f7b8a47aa2556c74 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Thu, 28 Jan 2021 11:37:59 +0000 Subject: [PATCH 51/78] actions: fail-fast: false --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 409a4fafe1..7245215748 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,6 +29,7 @@ jobs: runs-on: ${{ matrix.os }} timeout-minutes: 45 strategy: + fail-fast: false matrix: os: ['ubuntu-latest'] python-version: ['3.7'] From 95cfdced2ed3a45ea6bfb19325a8a323613332d1 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Thu, 28 Jan 2021 15:13:43 +0000 Subject: [PATCH 52/78] portability: fix $PATH in test using bash -l --- t/rose-task-run/30-app-arch-opt-source/bin/foo | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/t/rose-task-run/30-app-arch-opt-source/bin/foo b/t/rose-task-run/30-app-arch-opt-source/bin/foo index 6c3a5e2b67..0f9481f6d9 100755 --- a/t/rose-task-run/30-app-arch-opt-source/bin/foo +++ b/t/rose-task-run/30-app-arch-opt-source/bin/foo @@ -1,4 +1,6 @@ -#!/usr/bin/env bash +#!/bin/bash -l +# NOTE: not using /usr/bin/env bash here to allow the -l flag to be provided +# (required for MacOS to pick up GNU install rather than the BSD variant) #------------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # From 896eed9727d088f28f4e03cc2a18937c3df04c6c Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Tue, 2 Feb 2021 11:42:31 +0000 Subject: [PATCH 53/78] actions: unpin from datamel/cylc-flow@cylc-install * run against master branches of cylc, fcm and cylc-rose by default * fix branch specification inputs * remove some debug statements --- .github/workflows/test.yml | 41 ++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7245215748..3e6667ecf8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,19 +4,19 @@ on: pull_request: workflow_dispatch: inputs: - rose-ref: + rose_ref: description: The Rose branch to test against required: true - fcm-ref: + fcm_ref: description: The FCM branch to test against required: false - fcm-repo: + fcm_repo: description: The FCM branch to test against required: false - cylc-ref: + cylc_ref: description: The Cylc branch to test against required: false - cylc-repo: + cylc_repo: description: The Cylc repo to test against required: false @@ -41,7 +41,7 @@ jobs: - name: Checkout uses: actions/checkout@v2 with: - ref: ${{ github.event.inputs.rose-ref }} + ref: ${{ github.event.inputs.rose_ref || github.sha }} path: rose - name: Configure Python @@ -51,20 +51,15 @@ jobs: - name: Install Cylc env: - cylc_repo: ${{ github.events.inputs.cylc-repo || 'cylc/cylc-flow' }} - cylc_branch: ${{ github.events.inputs.cylc-ref || 'master' }} + cylc_repo: ${{ github.event.inputs.cylc_repo || 'cylc/cylc-flow' }} + cylc_branch: ${{ github.event.inputs.cylc_ref || 'master' }} run: | - # temp: use cylc-install branch - cylc_repo='datamel/cylc-flow' - cylc_branch='cylc-install' pip install "git+https://github.com/$cylc_repo@$cylc_branch" - name: Brew Install if: startsWith(matrix.os, 'macos') run: | - which python # install system deps - #brew update brew install bash coreutils gnu-sed shellcheck sqlite3 subversion # add GNU coreutils and sed to the user PATH (for actions steps) @@ -114,8 +109,8 @@ jobs: if: startsWith(matrix.os, 'ubuntu') uses: actions/checkout@v2 with: - repository: ${{ github.event.inputs.fcm-repo || 'metomi/fcm' }} - ref: ${{ github.event.inputs.fcm-ref || 'master' }} + repository: ${{ github.event.inputs.fcm_repo || 'metomi/fcm' }} + ref: ${{ github.event.inputs.fcm_ref || 'master' }} path: 'fcm' - name: Install FCM @@ -191,23 +186,21 @@ jobs: - name: Checkout uses: actions/checkout@v2 with: - ref: ${{ github.event.inputs.rose-ref }} + ref: ${{ github.event.inputs.rose_ref || github.sha }} - name: Configure Python uses: actions/setup-python@v1 with: python-version: 3.7 - - name: install + - name: install graphviz run: | sudo apt-get update - sudo apt-get install -y \ - latexmk \ - texlive \ - texlive-generic-extra \ - texlive-latex-extra \ - texlive-fonts-recommended \ - graphviz + sudo apt-get install -y graphviz pkg-config libgraphviz-dev + pip install pygraphviz + + - name: install + run: | pip install -e .[docs] - name: build (html) From bc8b562972574b0710ed4bcd27510c2477ca4d9d Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Tue, 23 Feb 2021 12:28:43 +0000 Subject: [PATCH 54/78] tests: update to cylc play changes * fix test issues from rebase --- etc/tutorial/cylc-forecasting-suite/.validate | 2 +- .../test_loc_handlers_rsync_remote_check.py | 2 +- sphinx/cheat-sheet.rst | 2 +- sphinx/glossary.rst | 6 +- sphinx/tutorial/rose/suites.rst | 4 +- sphinx/tutorial/rose/summary.rst | 4 +- t/rosa-svn-pre-commit/04-unicode.t | 15 ++--- .../04-unicode/rose-suite.info | 3 + t/rose-ana/00-run-basic.t | 2 +- t/rose-ana/01-run-basic-v1.t | 4 +- t/rose-config/03-syntax-error.t | 60 +++++++++---------- t/rose-task-env/00-non-cycle.t | 4 +- t/rose-task-env/01-integer-cycling.t | 4 +- t/rose-task-env/02-360day-cycling.t | 4 +- t/rose-task-run/00-run-basic.t | 4 +- t/rose-task-run/01-run-basic-iso.t | 4 +- t/rose-task-run/04-run-path-empty.t | 4 +- t/rose-task-run/06-app-prune-iso.t | 4 +- t/rose-task-run/07-app-arch.t | 4 +- t/rose-task-run/08-app-fcm-make.t | 4 +- t/rose-task-run/09-app-prune-work.t | 4 +- t/rose-task-run/10-specify-cycle.t | 4 +- t/rose-task-run/11-specify-cycle-iso.t | 4 +- t/rose-task-run/12-app-prune-integer.t | 4 +- t/rose-task-run/13-app-arch-cmd-out.t | 4 +- t/rose-task-run/14-app-prune-remove.t | 4 +- t/rose-task-run/15-app-prune-extglob.t | 4 +- t/rose-task-run/16-app-prune-point.t | 4 +- .../17-app-prune-glob-as-cycle-fmt.t | 4 +- t/rose-task-run/18-app-fcm-make-ctx-name.t | 4 +- t/rose-task-run/20-app-fcm-make-dest.t | 4 +- t/rose-task-run/24-app-fcm-make-fast.t | 4 +- t/rose-task-run/25-app-fcm-make-new-mode.t | 4 +- .../26-app-fcm-make-new-mode-with-cont.t | 4 +- t/rose-task-run/28-env-path-run-path.t | 4 +- t/rose-task-run/29-app-prune-extglob-remote.t | 4 +- t/rose-task-run/30-app-arch-opt-source.t | 4 +- t/rose-task-run/31-app-bunch.t | 4 +- t/rose-task-run/32-app-arch-compressed.t | 4 +- t/rose-task-run/33-app-prune-cycle-format.t | 4 +- .../34-app-prune-hosts-sharing-fs.t | 4 +- .../35-app-prune-log-on-hosts-sharing-fs.t | 4 +- t/rose-task-run/36-app-arch-interrupted.t | 4 +- t/rose-task-run/37-app-bunch-rm-old.t | 12 ++-- t/rose-task-run/38-app-bunch-counts.t | 4 +- t/rose-task-run/39-app-prune-cycle-host.t | 4 +- t/rose-task-run/40-app-arch-duplicate.t | 4 +- .../41-app-bunch-default-command.t | 8 +-- 48 files changed, 132 insertions(+), 132 deletions(-) create mode 100644 t/rosa-svn-pre-commit/04-unicode/rose-suite.info diff --git a/etc/tutorial/cylc-forecasting-suite/.validate b/etc/tutorial/cylc-forecasting-suite/.validate index b0335dac54..3d0b183322 100644 --- a/etc/tutorial/cylc-forecasting-suite/.validate +++ b/etc/tutorial/cylc-forecasting-suite/.validate @@ -1,3 +1,3 @@ rose tutorial "$(basename $TUT_DIR)" "${CYLC_RUN_DIR}/${REG}" sed -i '1s;^;[cylc]\n abort if any task fails = True\n;' "${CYLC_RUN_DIR}/${REG}/flow.cylc" -cylc run --no-detach "${REG}" 2>&1 +cylc play --no-detach "${REG}" 2>&1 diff --git a/metomi/rose/tests/test_loc_handlers_rsync_remote_check.py b/metomi/rose/tests/test_loc_handlers_rsync_remote_check.py index 79cfc408cb..476eccbaa4 100644 --- a/metomi/rose/tests/test_loc_handlers_rsync_remote_check.py +++ b/metomi/rose/tests/test_loc_handlers_rsync_remote_check.py @@ -59,7 +59,7 @@ def test_check_folder( main() captured = capsys.readouterr() assert captured.out.splitlines()[0] == 'tree' - mode, _, size, path = literal_eval(captured.out.splitlines()[1]) + mode, _, size, path = literal_eval(captured.out.splitlines()[2]) assert path == str(dirpath / 'more.stuff') assert mode == '0o100633' assert size == 2 diff --git a/sphinx/cheat-sheet.rst b/sphinx/cheat-sheet.rst index d8fab0c72a..9b041d61b5 100644 --- a/sphinx/cheat-sheet.rst +++ b/sphinx/cheat-sheet.rst @@ -28,7 +28,7 @@ Starting Suites * - :: cylc validate - cylc run + cylc play - :: # run the suite in the current directory: diff --git a/sphinx/glossary.rst b/sphinx/glossary.rst index 8a0e2c17b9..87c4832a4a 100644 --- a/sphinx/glossary.rst +++ b/sphinx/glossary.rst @@ -566,7 +566,7 @@ Glossary run. This program controls the suite and is what we refer to as "running". - * A :term:`Cylc suite` is started using ``cylc run``. + * A :term:`Cylc suite` is started using ``cylc play``. * A :term:`Rose suite configuration` (or :term:`Rosie Suite`) is started using :ref:`command-rose-suite-run`. @@ -586,7 +586,7 @@ Glossary cold start A cold start is one in which the :term:`suite` :term:`starts ` from the :term:`initial cycle point`. This is the default behaviour of - ``cylc run``. + ``cylc play``. See also: @@ -825,7 +825,7 @@ Glossary * Jinja2 variables to be passed into the ``flow.cylc`` file ( :rose:conf:`rose-suite.conf[jinja2:suite.rc]`). - * Environment variables to be provided to ``cylc run`` ( + * Environment variables to be provided to ``cylc play`` ( :rose:conf:`rose-suite.conf[env]`). * Installation configuration (e.g. :rose:conf:`rose-suite.conf[file:NAME]`). diff --git a/sphinx/tutorial/rose/suites.rst b/sphinx/tutorial/rose/suites.rst index d9be7508e1..9adb0e1c1a 100644 --- a/sphinx/tutorial/rose/suites.rst +++ b/sphinx/tutorial/rose/suites.rst @@ -239,7 +239,7 @@ Start, Stop, Restart :ref:`Starting Suites` Suites must be run using the :ref:`command-rose-suite-run` command which - in turn calls the ``cylc run`` command. + in turn calls the ``cylc play`` command. :ref:`Stopping Suites` Suites can be stopped using the ``cylc stop `` command, as for regular Cylc suites. @@ -257,7 +257,7 @@ Start, Stop, Restart .. ifslides:: Starting Suites - ``rose suite-run`` which in turn calls ``cylc run`` + ``rose suite-run`` which in turn calls ``cylc play`` Stopping Suites ``cylc stop `` Restarting Suites diff --git a/sphinx/tutorial/rose/summary.rst b/sphinx/tutorial/rose/summary.rst index 508178089c..7632c863a8 100644 --- a/sphinx/tutorial/rose/summary.rst +++ b/sphinx/tutorial/rose/summary.rst @@ -91,7 +91,7 @@ Suite Commands Processes the ``flow.cylc`` file and prints it back out. ``cylc validate`` Validates the Cylc ``flow.cylc`` file to check for any obvious errors. - ``cylc run`` + ``cylc play`` Runs a suite. ``cylc stop`` Stops a suite, in a way that: @@ -109,7 +109,7 @@ Suite Commands * ``cylc graph`` * ``cylc get-config`` * ``cylc validate`` - * ``cylc run`` + * ``cylc play`` * ``cylc stop`` * ``--kill`` * ``--now --now`` diff --git a/t/rosa-svn-pre-commit/04-unicode.t b/t/rosa-svn-pre-commit/04-unicode.t index 30c7b31a67..bb9eb1a2fd 100644 --- a/t/rosa-svn-pre-commit/04-unicode.t +++ b/t/rosa-svn-pre-commit/04-unicode.t @@ -47,16 +47,13 @@ chmod +x repos/foo/hooks/pre-commit export LANG=C TEST_KEY="${TEST_KEY_BASE}-western" -cat > rose-suite.info << '__INFO__' -owner=ivy -project=euro -title=We should not éñçödê config files in latin-1/western -__INFO__ -iconv -f UTF-8 -t LATIN1 rose-suite.info -o rose-suite.info -# ----------------------------------------------------------------------------- run_fail "$TEST_KEY" \ - svn import rose-suite.info -q -m 't' --non-interactive \ - "${SVN_URL}/a/a/0/0/0/trunk/rose-suite.info" + svn import \ + "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}/rose-suite.info" \ + -q \ + -m 't' \ + --non-interactive \ + "${SVN_URL}/a/a/0/0/0/trunk/rose-suite.info" file_cmp "${TEST_KEY}.out" "${TEST_KEY}.out" < /dev/null sed -i '/^\[FAIL\]/!d' "$TEST_KEY.err" file_cmp "${TEST_KEY}.err" "${TEST_KEY}.err" << '__ERR__' diff --git a/t/rosa-svn-pre-commit/04-unicode/rose-suite.info b/t/rosa-svn-pre-commit/04-unicode/rose-suite.info new file mode 100644 index 0000000000..008d0f3859 --- /dev/null +++ b/t/rosa-svn-pre-commit/04-unicode/rose-suite.info @@ -0,0 +1,3 @@ +owner=ivy +project=euro +title=We should not éñçödê config files in latin-1/western diff --git a/t/rose-ana/00-run-basic.t b/t/rose-ana/00-run-basic.t index 898b21fab8..7c3148888d 100644 --- a/t/rose-ana/00-run-basic.t +++ b/t/rose-ana/00-run-basic.t @@ -46,7 +46,7 @@ run_pass "$TEST_KEY" \ TEST_KEY="${TEST_KEY_BASE}-run" run_fail "$TEST_KEY" \ - cylc run \ + cylc play \ "${FLOW}" \ --host=localhost \ --no-detach \ diff --git a/t/rose-ana/01-run-basic-v1.t b/t/rose-ana/01-run-basic-v1.t index 9eb268c44e..10c7e13e0e 100644 --- a/t/rose-ana/01-run-basic-v1.t +++ b/t/rose-ana/01-run-basic-v1.t @@ -34,9 +34,9 @@ run_pass "$TEST_KEY" \ -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ --flow-name="$FLOW" \ --no-run-name -TEST_KEY="${TEST_KEY_BASE}-run" +TEST_KEY="${TEST_KEY_BASE}-play" run_pass "$TEST_KEY" \ - cylc run \ + cylc play \ "${FLOW}" \ --host=localhost \ --no-detach \ diff --git a/t/rose-config/03-syntax-error.t b/t/rose-config/03-syntax-error.t index 9cfdf0557e..595f8742b4 100755 --- a/t/rose-config/03-syntax-error.t +++ b/t/rose-config/03-syntax-error.t @@ -27,7 +27,7 @@ echo ' foo=bar' >rose-bad.conf run_fail "$TEST_KEY" rose config -f rose-bad.conf file_cmp "$TEST_KEY.out" "$TEST_KEY.out" rose-bad.conf run_fail "$TEST_KEY" rose config -f rose-bad.conf file_cmp "$TEST_KEY.out" "$TEST_KEY.out" rose-bad.conf <<'__CONF__' -[flower:daisy((2)] +[flower:daisy((line 2)] ivy=poison __CONF__ run_fail "$TEST_KEY" rose config -f rose-bad.conf file_cmp "$TEST_KEY.out" "$TEST_KEY.out" rose-bad.conf <<'__CONF__' -[flower:daisy(2))] +[flower:daisy(line 2))] ivy=poison __CONF__ run_fail "$TEST_KEY" rose config -f rose-bad.conf file_cmp "$TEST_KEY.out" "$TEST_KEY.out" rose-bad.conf <<'__CONF__' -[flower:daisy(2){white}] +[flower:daisy(line 2){white}] ivy=poison __CONF__ run_fail "$TEST_KEY" rose config -f rose-bad.conf file_cmp "$TEST_KEY.out" "$TEST_KEY.out" rose-bad.conf <<'__CONF__' -[flower:daisy{white(2)}] +[flower:daisy{white(line 2)}] ivy=poison __CONF__ run_fail "$TEST_KEY" rose config -f rose-bad.conf file_cmp "$TEST_KEY.out" "$TEST_KEY.out" Date: Tue, 23 Feb 2021 15:49:31 +0000 Subject: [PATCH 55/78] actions: disable macos testing * python setup incompatible with rose in its current state * requires bash -c 'which python' === bash -l -c 'which python' * this requirement will be fixed when we convert the rose scripts to python * tests (excluding fcm tests) pass on macos with a patched version of cylc which submits jobs without the -l (login) flag. --- .github/workflows/test.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3e6667ecf8..fe9977ae61 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,9 +33,15 @@ jobs: matrix: os: ['ubuntu-latest'] python-version: ['3.7'] - include: - - os: 'macos-latest' - python-version: '3.7' + # TODO: re-enable macos testing + # currently (in the absence of a wrapper script) rose cannot be run + # from within cylc jobs in situations where the output of the following + # commands differ: + # bash -c 'which python' + # bash -l -c 'which python' + # include: + # - os: 'macos-latest' + # python-version: '3.7' steps: - name: Checkout From cf1a82e551bd74bc9b4fc77471c82251f80911a2 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Tue, 23 Feb 2021 16:01:11 +0000 Subject: [PATCH 56/78] travis: remove travis-ci test scripts --- .travis.yml | 41 ------ .travis/cover.py | 33 ----- .travis/now | 270 --------------------------------------- .travis/sitecustomize.py | 27 ---- 4 files changed, 371 deletions(-) delete mode 100644 .travis.yml delete mode 100644 .travis/cover.py delete mode 100755 .travis/now delete mode 100644 .travis/sitecustomize.py diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f7c9d572cd..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,41 +0,0 @@ -# Configuration for running Rose test battery on Travis CI -# See https://travis-ci.org/ for more info. - ---- -language: python -python: - - 3.7 -dist: xenial - -before_install: - - export PATH="$PWD/.travis:$PATH" - -after_success: - - now report coverage - -after_failure: - - now report error - -jobs: - include: - - name: "Unit Tests" - install: - - now install coverage linters pytest cylc rose - script: - - now test style - - now test units - - - name: "Test Battery" - before_install: - - export PATH="$PWD/.travis:$PATH" - - now install coverage fcm tut_suite - install: - - now install cylc rose - script: - - now test battery - - - name: "Documentation" - install: - - now install coverage rose sphinx tut_suite - script: - - now test docs diff --git a/.travis/cover.py b/.travis/cover.py deleted file mode 100644 index 64f55bf5c9..0000000000 --- a/.travis/cover.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (C) British Crown (Met Office) & Contributors. -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -# ----------------------------------------------------------------------------- - -import sys - -from subprocess import call - - -def main(): - command = ['etc/bin/rose-test-battery', '-j', '5'] - if call(command + ['--state=save']): - # Non-zero return code - sys.stderr.write('\n\nRerunning Failed Tests...\n\n') - # Exit with final return code - sys.exit(call(command + ['--state=save,failed', '-v'])) - - -if __name__ == '__main__': - main() diff --git a/.travis/now b/.travis/now deleted file mode 100755 index aeb708d532..0000000000 --- a/.travis/now +++ /dev/null @@ -1,270 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------- -# Copyright (C) British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -#------------------------------------------------------------------------------- - -# Bash script for use with Travis-CI builds. -# -# usage: now command [args...] -# -# commands: -# build -# install -# report_coverage -# test - -# source the .bashrc because for some reason this doesn't get done for us -# do this before the set -eu to avoid bailing on the build for hardcoded -# bashrc issues -if [[ -f "${HOME}/.bashrc" ]]; then - source "${HOME}/.bashrc" -fi - - -set -eu - -APT=() -PIP=() -GIT=() -BREW=() -YARN=false -PY_PATH=() -RC_PATH=("${HOME}") -RI_PATH=() -WANDISCO=false - - -_build_docs () { - etc/bin/rose-make-docs --strict clean html slides latexpdf -} - -_gh_extract () { # extract project from GitHub to $HOME - IFS='|' read -ra SPEC <<< "$1" - local USR="${SPEC[0]}" - local REPO="${SPEC[1]}" - local BRANCH="${SPEC[2]}" - local URL="https://github.com/${USR}/${REPO}/archive/${BRANCH}.tar.gz" - local DEST="${HOME}/${REPO}-${BRANCH}" - - if [[ -d "${DEST}" ]]; then - # already installed - return - fi - - # download + unpack - wget "${URL}" -O - | tar -xz -C "${HOME}" - - # in-place installation - if [[ -f "${DEST}/setup.py" ]]; then - pip install "${DEST}" - fi -} - -_install_coverage () { - PIP+=(coverage pytest-cov) - PY_PATH+=("./.travis") -} - -_install_rose () { - pip install . -} - -_install_cylc () { - APT+=(at) - BREW+=(bash coreutils gnu-sed) - if [[ "$OSTYPE" == darwin* ]]; then - # add GNU coreutils and sed to the user PATH - # (see instructions in brew install output) - # shellcheck disable=SC2016 - RC_PATH+=('$(brew --prefix)/opt/coreutils/libexec/gnubin:$PATH') - # shellcheck disable=SC2016 - RC_PATH+=('/usr/local/opt/gnu-sed/libexec/gnubin:$PATH') - # apply DNS patch - hostuserutil="$(python3 -c ' - import cylc.flow.hostuserutil - print(cylc.flow.hostuserutil.__file__) - ')" - patch "${hostuserutil}" < etc/conf/macos-patch - fi - #brew install bash coreutils gnu-sed shellcheck sqlite3 subversion -} - -_install_fcm () { - APT+=(subversion build-essential gfortran libxml-parser-perl \ - libconfig-inifiles-perl libdbi-perl libdbd-sqlite3-perl) - GIT+=('metomi|fcm|master') - RC_PATH+=("${HOME}/fcm-master/bin") - WANDISCO=true -} - -_install_rosie () { - PIP+=(requests tornado sqlalchemy) - RI_PATH+=("$(_path_for python)" "$(_path_for rose)") -} - -_install_sphinx () { - # sphinx documentation and its extensions - APT+=(latexmk texlive texlive-generic-extra texlive-latex-extra \ - texlive-fonts-recommended graphviz) - pip install -e .[docs] -} - -_install_tut_suite () { - # cylc tutorial suite - PIP+=(pillow) -} - -_join () { - local IFS="$1"; - shift; - echo "$*"; -} - -_install_linters () { - APT+=(shellcheck) - BREW+=(shellcheck) - YARN=true -} - -_path_for () { - COMMAND="$1" - dirname "$(command -v "${COMMAND}")" -} - -#_test_units () { -# pytest --cov-append metomi/rose/tests/* -#} - -#_test_style () { -# pycodestyle -# eslint . -# "$(dirname "$0")/shellchecker" -#} - -#_test_battery () { -# cp "./.travis/sitecustomize.py" ./lib/python -# coverage run .travis/cover.py -#} - -#_test_docs () { -# etc/bin/rose-make-docs --strict clean linkcheck doctest -#} - -_wandisco_configure () { # extract Wandisco stuff - # shellcheck disable=SC1004,SC2016 - sudo sh -c 'echo "deb http://opensource.wandisco.com/ubuntu \ - `lsb_release -cs` svn19" >> /etc/apt/sources.list.d/subversion19.list' - sudo wget -q http://opensource.wandisco.com/wandisco-debian.gpg -O- | \ - sudo apt-key add - -} - -build () { - for arg in "$@"; do - "_build_${arg}" - done -} - - # shellcheck disable=SC2032 -install () { - for arg in "$@"; do - "_install_${arg}" - done - - if ${WANDISCO}; then - _wandisco_configure - fi - - if [[ ${#PIP[@]} -gt 0 ]]; then - pip install "${PIP[@]}" & - fi - - if $YARN; then - yarn install - fi - - if [[ "$OSTYPE" == darwin* ]]; then - if [[ ${#BREW[@]} -gt 0 ]]; then - brew update - brew install "${BREW[@]}" - fi - else - if [[ ${#APT[@]} -gt 0 ]]; then - sudo apt-get update - # shellcheck disable=SC155,2033 - sudo apt-get install -y "${APT[@]}" - fi - fi - - if [[ ${#GIT[@]} -gt 0 ]]; then - # wrapping the for loop to avoid unbound variable "GIT[@]" error - for gh_project in "${GIT[@]}"; do - _gh_extract "${gh_project}" - done - fi - - wait - - # .bashrc - cat >"${HOME}/.bashrc" \ - <<<"export PATH=\"$(_join ':' "${RC_PATH[@]}"):\$PATH\";" - cat >>"${HOME}/.bashrc" \ - <<<"export PYTHONPATH=\"$(_join ':' "${PY_PATH[@]}"):\$PYTHONPATH\";" - - # rose_init_site - cat >"./lib/bash/rose_init_site" \ - <<<" - if [[ -z \${HOME+x} ]]; then - export PATH=\"$(_join ':' "${RI_PATH[@]}"):\${PATH:-}\"; - fi - " -} - -_report_coverage () { - coverage combine --append - coverage xml --ignore-errors - bash <(curl -s https://codecov.io/bash) -} - -#_report_error() { -# # don't bail out on error -# set +eu - -# printenv PATH PYTHONPATH -# rose check-software -# cat /tmp/sphinx-err* >&2 # sphinx traceback -#} - -report () { - for arg in "$@"; do - "_report_${arg}" - done -} - -test () { - PS1='$' . "${HOME}/.bashrc" - export COVERAGE_PROCESS_START="./.coveragerc" - for arg in "$@"; do - "_test_${arg}" - done -} - -# do this here so we only trace the commands we are interested in -set -o xtrace - -# run the specified function -"$@" diff --git a/.travis/sitecustomize.py b/.travis/sitecustomize.py deleted file mode 100644 index faed756e4c..0000000000 --- a/.travis/sitecustomize.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (C) British Crown (Met Office) & Contributors. -# This file is part of Rose, a framework for meteorological suites. -# -# Rose 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. -# -# Rose 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 Rose. If not, see . -# ----------------------------------------------------------------------------- - -"""This file is used by Travis-CI to start the coverage process. - -In order to make python aware of it, we export PYTHONPATH when running -the tests. - -""" - - -import coverage -coverage.process_startup() From 33b8d5eb65c20e8df390c6008d55efac41d3e788 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Fri, 5 Mar 2021 12:59:04 +0000 Subject: [PATCH 57/78] rebase host-select - test fix --- t/rose-host-select/02-localhost.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/rose-host-select/02-localhost.t b/t/rose-host-select/02-localhost.t index 71a0a66a43..0c5f95caee 100755 --- a/t/rose-host-select/02-localhost.t +++ b/t/rose-host-select/02-localhost.t @@ -67,7 +67,7 @@ if [[ -n ${MORE_HOST} ]]; then "stamp-removed.log" >"${TEST_KEY_BASE}.out.${MORE_HOST}" file_cmp "${TEST_KEY_BASE}.out.${MORE_HOST}" \ "${TEST_KEY_BASE}.out.${MORE_HOST}" <<__OUT__ -[INFO] YYYY-MM-DDTHHMM ssh -oBatchMode=yes -oConnectTimeout=10 ${MORE_HOST} bash <<'__STDIN__' +[INFO] YYYY-MM-DDTHHMM ssh -oBatchMode=yes -oConnectTimeout=10 ${MORE_HOST} rose host-select-client <<'__STDIN__' __OUT__ fi From dcbb73e8a3222335acac4a1e1f1b051b744c98ef Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Fri, 5 Mar 2021 12:58:28 +0000 Subject: [PATCH 58/78] loc_handlers: fix access mode bug * python2->3 conversion issue (not present in rose2019) * remove unnecessary rsync * simplify --- metomi/rose/config_processors/fileinstall.py | 46 +++++++++----- metomi/rose/loc_handlers/rsync.py | 10 +-- .../rose/loc_handlers/rsync_remote_check.py | 19 +++--- .../test_loc_handlers_rsync_remote_check.py | 62 +++++++++---------- 4 files changed, 76 insertions(+), 61 deletions(-) diff --git a/metomi/rose/config_processors/fileinstall.py b/metomi/rose/config_processors/fileinstall.py index f65e88cd48..190e4a16f7 100644 --- a/metomi/rose/config_processors/fileinstall.py +++ b/metomi/rose/config_processors/fileinstall.py @@ -17,27 +17,34 @@ """Process "file:*" sections in node of a metomi.rose.config_tree.ConfigTree. """ +import aiofiles from fnmatch import fnmatch from glob import glob +from io import BytesIO import os +import shlex +from shutil import rmtree +import sqlite3 +import sys +from tempfile import mkdtemp +from typing import Any, Optional +from urllib.parse import urlparse + from metomi.rose.checksum import ( - get_checksum, get_checksum_func, guess_checksum_algorithm) -from metomi.rose.config_processor import (ConfigProcessError, - ConfigProcessorBase) + get_checksum, + get_checksum_func, + guess_checksum_algorithm +) +from metomi.rose.config_processor import ( + ConfigProcessError, + ConfigProcessorBase, +) from metomi.rose.env import env_var_process, UnboundEnvironmentVariableError from metomi.rose.fs_util import FileSystemUtil from metomi.rose.job_runner import JobManager, JobProxy, JobRunner from metomi.rose.popen import RosePopener from metomi.rose.reporter import Event from metomi.rose.scheme_handler import SchemeHandlersManager -import shlex -from shutil import rmtree -import sqlite3 -from io import BytesIO -import sys -from tempfile import mkdtemp -from urllib.parse import urlparse -import aiofiles class ConfigProcessorForFile(ConfigProcessorBase): @@ -574,12 +581,21 @@ def __str__(self): class LocSubPath: - """Represent a sub-path in a location.""" + """Represent a sub-path in a location. + + Attrs: + name: + Path name. + checksum: + Computed checksum value. + access_mode: + File type and mode bits (see os.stat_result:st_mode). + """ def __init__(self, name, checksum=None, access_mode=None): - self.name = name - self.checksum = checksum - self.access_mode = access_mode + self.name: str = name + self.checksum: Any = checksum + self.access_mode: Optional[int] = access_mode def __lt__(self, other): return ( diff --git a/metomi/rose/loc_handlers/rsync.py b/metomi/rose/loc_handlers/rsync.py index a952d82326..a1ffd0e666 100644 --- a/metomi/rose/loc_handlers/rsync.py +++ b/metomi/rose/loc_handlers/rsync.py @@ -16,8 +16,6 @@ # ----------------------------------------------------------------------------- """A handler of locations on remote hosts.""" -from pathlib import Path -from tempfile import TemporaryFile from time import sleep, time from metomi.rose.popen import RosePopenError @@ -64,10 +62,8 @@ def parse(self, loc, _): host, path = loc.name.split(":", 1) cmd = self.manager.popen.get_cmd( "ssh", host, "python3", "-", path, loc.TYPE_BLOB, loc.TYPE_TREE) - temp_file = TemporaryFile() - temp_file.write(Path(rsync_remote_check_file).read_bytes()) - temp_file.seek(0) - out = self.manager.popen(*cmd, stdin=temp_file)[0].decode() + with open(rsync_remote_check_file, 'rb') as stdin: + out = self.manager.popen(*cmd, stdin=stdin)[0].decode() lines = out.splitlines() if not lines or lines[0] not in [loc.TYPE_BLOB, loc.TYPE_TREE]: raise ValueError(loc.name) @@ -77,7 +73,7 @@ def parse(self, loc, _): access_mode, mtime, size, name = line.split(None, 3) fake_sum = "source=%s:mtime=%s:size=%s" % ( name, mtime, size) - loc.add_path(loc.BLOB, fake_sum, int(access_mode, base=8)) + loc.add_path(loc.BLOB, fake_sum, int(access_mode)) else: # if loc.loc_type == loc.TYPE_TREE: for line in lines: access_mode, mtime, size, name = line.split(None, 3) diff --git a/metomi/rose/loc_handlers/rsync_remote_check.py b/metomi/rose/loc_handlers/rsync_remote_check.py index 8ab7e85a83..3f6de15365 100644 --- a/metomi/rose/loc_handlers/rsync_remote_check.py +++ b/metomi/rose/loc_handlers/rsync_remote_check.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- # Copyright (C) British Crown (Met Office) & Contributors. # # This file is part of Rose, a framework for meteorological suites. @@ -23,12 +21,18 @@ This is a Python file but we read it and pass it to stdin to avoid reliance on remote platforms having rose installed. + +Warning: + This script will not necessarily be run in the Rose Python environment. + It should not have any dependencies outside of the stdlib and should be + compatible with as wide a range of Python3 versions as possible. + """ import os import sys -def main(): +def main(path, str_blob, str_tree): """Check file exists and print some info: 1. Octal protection bits. @@ -36,7 +40,6 @@ def main(): 3. Filesize. 4. Path, which has been checked. """ - path, str_blob, str_tree = sys.argv[1:] if os.path.isdir(path): print(str_tree) os.chdir(path) @@ -46,19 +49,19 @@ def main(): if not dirname.startswith("."): good_dirnames.append(dirname) name = os.path.join(dirpath, dirname) - print(("-", "-", "-", name)) + print("-", "-", "-", name) dirnames[:] = good_dirnames for filename in filenames: if filename.startswith("."): continue name = os.path.join(dirpath, filename) stat = os.stat(name) - print((oct(stat.st_mode), stat.st_mtime, stat.st_size, name)) + print(stat.st_mode, stat.st_mtime, stat.st_size, name) elif os.path.isfile(path): print(str_blob) stat = os.stat(path) - print(oct(stat.st_mode), stat.st_mtime, stat.st_size, path) + print(stat.st_mode, stat.st_mtime, stat.st_size, path) if __name__ == '__main__': - main() + main(*sys.argv[1:]) diff --git a/metomi/rose/tests/test_loc_handlers_rsync_remote_check.py b/metomi/rose/tests/test_loc_handlers_rsync_remote_check.py index 476eccbaa4..a71efe8509 100644 --- a/metomi/rose/tests/test_loc_handlers_rsync_remote_check.py +++ b/metomi/rose/tests/test_loc_handlers_rsync_remote_check.py @@ -18,48 +18,48 @@ # along with Rose. If not, see . # ----------------------------------------------------------------------------- -import sys - -from ast import literal_eval +from pathlib import Path from metomi.rose.loc_handlers.rsync_remote_check import main -def test_check_file(monkeypatch, capsys, tmp_path): +def test_check_file(capsys, tmp_path): content = 'blah' - permission_level = '0o100644' + permission_level = 33188 filepath = tmp_path / 'stuff' filepath.write_text(content) - filepath.chmod(int(permission_level, base=8)) - monkeypatch.setattr( - sys, 'argv', ['ignored', str(filepath), 'blob', 'tree'] - ) - main() + filepath.chmod(permission_level) + main(str(filepath), 'blob', 'tree') captured = capsys.readouterr() assert captured.out.splitlines()[0] == 'blob' mode, mtime, size, path = captured.out.splitlines()[1].split() assert path == str(filepath) - assert mode == permission_level + assert int(mode) == permission_level assert size == str(filepath.stat().st_size) -def test_check_folder( - monkeypatch, capsys, tmp_path -): - folder_permission_level = '0o100700' - dirpath = tmp_path / 'stuff' - dirpath.mkdir() - (dirpath / 'more.stuff').write_text('Hi') - (dirpath / 'more.stuff').chmod(int('0o100633', base=8)) - (dirpath / 'even.more.stuff').write_text('Hi') - dirpath.chmod(int(folder_permission_level, base=8)) - monkeypatch.setattr( - sys, 'argv', ['ignored', str(dirpath), 'blob', 'tree'] - ) - main() - captured = capsys.readouterr() - assert captured.out.splitlines()[0] == 'tree' - mode, _, size, path = literal_eval(captured.out.splitlines()[2]) - assert path == str(dirpath / 'more.stuff') - assert mode == '0o100633' - assert size == 2 +def test_check_folder(capsys, tmp_path): + # create a file and chmod it + (tmp_path / 'more.stuff').write_text('Hi') + (tmp_path / 'more.stuff').chmod(int('0o100633', base=8)) + + # create another file + (tmp_path / 'even.more.stuff').write_text('Hi') + + # run the remote check on the dir + main(str(tmp_path), 'blob', 'tree') + lines = capsys.readouterr().out.splitlines() + + # the first line should be the dir + assert lines[0] == 'tree' + + # the following lines should be the files + files = { + # filename: [mode, mod_time, size] + str(Path(line.split()[-1]).relative_to(tmp_path)): line.split()[:-1] + for line in lines[1:] + } + assert list(sorted(files)) == ['even.more.stuff', 'more.stuff'] + mode, _, size = files['more.stuff'] + assert mode == '33179' + assert size == '2' From 132ec967b1e196648e178d65cbe9a111eaf2328d Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Mon, 4 Jan 2021 12:46:57 +0000 Subject: [PATCH 59/78] popen: fix __str__ method * fix issue where reading from stdin can hang * fix misc bytes/strings errors * handle a wider range of file-like objects --- metomi/rose/popen.py | 54 ++++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/metomi/rose/popen.py b/metomi/rose/popen.py index 4838c4da5a..c4f2cb2244 100644 --- a/metomi/rose/popen.py +++ b/metomi/rose/popen.py @@ -16,16 +16,17 @@ # ----------------------------------------------------------------------------- """Wraps Python's subprocess.Popen.""" -import os import asyncio +import os import re -import io -from metomi.rose.reporter import Event -from metomi.rose.resource import ResourceLocator +import select import shlex from subprocess import Popen, PIPE import sys +from metomi.rose.reporter import Event +from metomi.rose.resource import ResourceLocator + class RosePopenError(Exception): @@ -66,17 +67,40 @@ def __str__(self): ret = command else: ret = RosePopener.list_to_shell_str(self.command) - if isinstance(self.stdin, str): - ret += " <<'__STDIN__'\n" + self.stdin + "\n'__STDIN__'" - elif isinstance(self.stdin, io.IOBase): - try: - # FIXME: Is this safe? - pos = self.stdin.tell() - ret += " <<'__STDIN__'\n" +\ - self.stdin.read().decode() + "\n'__STDIN__'" - self.stdin.seek(pos) - except IOError: - pass + + try: + # real file or real stream + self.stdin.fileno() + # ask select if it is readable (real files can hang) + readable = bool(select.select([self.stdin], [], [], 0.0)[0]) + except (AttributeError, IOError): + # file like + readable = True + + if self.stdin: + if isinstance(self.stdin, str): + # string + stdin = self.stdin + elif isinstance(self.stdin, bytes): + # byte string + stdin = self.stdin.decode() + elif readable: + # file like + try: + pos = self.stdin.tell() + stdin = self.stdin.read() + self.stdin.seek(pos) + if not isinstance(stdin, str): + stdin = stdin.decode() + except Exception: + # purposefully vague for safety (catch any exception) + stdin = '' + else: + stdin = '' + + if stdin: + ret += f" <<'__STDIN__'\n{stdin}\n__STDIN__" + return ret From 5fe281399190bd40070075b8801a09d2c42be5d9 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Thu, 11 Mar 2021 16:03:45 +0000 Subject: [PATCH 60/78] tests: misc fixes + improvements * fix t/rose-task-run/06 * fix t/rose-task-run/20 * fix t/rose-task-run/15 * skip cylc8-broken tests (https://github.com/metomi/rose/issues/2445) * dump last 10 lines of stderr to console --- metomi/rose/suite_engine_procs/cylc.py | 30 ++++--------------- t/lib/bash/test_header | 4 +++ t/rose-task-run/06-app-prune-iso.t | 24 +++++---------- t/rose-task-run/06-app-prune-iso/flow.cylc | 4 +-- t/rose-task-run/08-app-fcm-make.t | 1 + t/rose-task-run/12-app-prune-integer.t | 2 ++ t/rose-task-run/15-app-prune-extglob.t | 3 +- .../15-app-prune-extglob/flow.cylc | 29 +++++++++--------- t/rose-task-run/18-app-fcm-make-ctx-name.t | 2 ++ t/rose-task-run/20-app-fcm-make-dest.t | 7 +++-- .../26-app-fcm-make-new-mode-with-cont.t | 1 + t/rose-task-run/29-app-prune-extglob-remote.t | 5 ++-- 12 files changed, 48 insertions(+), 64 deletions(-) diff --git a/metomi/rose/suite_engine_procs/cylc.py b/metomi/rose/suite_engine_procs/cylc.py index 1aefa6f394..3cfc621dd7 100644 --- a/metomi/rose/suite_engine_procs/cylc.py +++ b/metomi/rose/suite_engine_procs/cylc.py @@ -67,38 +67,18 @@ def get_suite_dir_rel(cls, suite_name, *paths): def get_suite_jobs_auths(self, suite_name, cycle_name_tuples=None): """Return remote ["[user@]host", ...] for submitted jobs.""" - auths = [] - stmt = "SELECT DISTINCT user_at_host FROM task_jobs" - stmt_where_list = [] - stmt_args = [] - if cycle_name_tuples: - for cycle, name in cycle_name_tuples: - stmt_fragments = [] - if cycle is not None: - stmt_fragments.append("cycle==?") - stmt_args.append(cycle) - if name is not None: - stmt_fragments.append("name==?") - stmt_args.append(name) - stmt_where_list.append(" AND ".join(stmt_fragments)) - if stmt_where_list: - stmt += " WHERE (" + ") OR (".join(stmt_where_list) + ")" - for row in self._db_exec(suite_name, stmt, stmt_args): - if row and row[0]: - auth = self._parse_user_host(auth=row[0]) - if auth: - auths.append(auth) - self._db_close(suite_name) - return auths + # TODO: reimplement for Cylc8? + # https://github.com/metomi/rose/issues/2445 + return [] def get_task_auth(self, suite_name, task_name): - """ - Return [user@]host for a remote task in a suite. + """Return [user@]host for a remote task in a suite. Or None if task does not run remotely. """ # TODO: reimplement for Cylc8? + # https://github.com/metomi/rose/issues/2445 return None def get_task_props_from_env(self): diff --git a/t/lib/bash/test_header b/t/lib/bash/test_header index 8f636d1299..4e256b03ed 100644 --- a/t/lib/bash/test_header +++ b/t/lib/bash/test_header @@ -162,6 +162,10 @@ pass() { fail() { echo "not ok $((++TEST_NUMBER)) - $*" ((++FAILURES)) + if [[ -f "${TEST_KEY}.err" ]]; then + # output the last 10 lines of stderr for debug + tail -n 10 "${TEST_KEY}.err" >&2 + fi } run_pass() { diff --git a/t/rose-task-run/06-app-prune-iso.t b/t/rose-task-run/06-app-prune-iso.t index 5035790434..3047bdb476 100755 --- a/t/rose-task-run/06-app-prune-iso.t +++ b/t/rose-task-run/06-app-prune-iso.t @@ -20,22 +20,14 @@ # Test rose_prune built-in application, basic cycle housekeep usage. #------------------------------------------------------------------------------- . $(dirname $0)/test_header - - -#------------------------------------------------------------------------------- -# Test the suite. +skip_all 'TODO: #2445' JOB_HOST=$(rose config --default= 't' 'job-host') -if [[ -n $JOB_HOST ]]; then - JOB_HOST="-S JOB_HOST='$(rose host-select -q "$JOB_HOST")'" -else - JOB_HOST= +tests 9 +if [[ -z $JOB_HOST ]]; then + JOB_HOST=localhost fi -export CYLC_CONF_PATH= export ROSE_CONF_PATH= TEST_KEY=$TEST_KEY_BASE - -#------------------------------------------------------------------------------- -tests 9 #------------------------------------------------------------------------------- get_reg run_pass "${TEST_KEY_BASE}-install" \ @@ -43,7 +35,7 @@ run_pass "${TEST_KEY_BASE}-install" \ -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ --flow-name="$FLOW" \ --no-run-name \ - "$JOB_HOST" + -S "HOST='${JOB_HOST}'" run_pass "${TEST_KEY_BASE}-play" \ cylc play \ "$FLOW" \ @@ -59,7 +51,7 @@ sed '/^\[INFO\] \(create\|delete\|update\)/!d; /\.json/d; /[0-9a-h]\{8\}\(-[0-9a-h]\{4\}\)\{3\}-[0-9a-h]\{12\}$/d' \ $FLOW_RUN_DIR/prune.log >edited-prune.log -if [[ -n $JOB_HOST ]]; then +if [[ $JOB_HOST != localhost ]]; then sed "s/\\\$JOB_HOST/$JOB_HOST/g" \ $TEST_SOURCE_DIR/$TEST_KEY_BASE.log >expected-prune.log else @@ -73,13 +65,13 @@ run_pass "$TEST_KEY" \ ls $FLOW_RUN_DIR/log/job-*.tar.gz $FLOW_RUN_DIR/{log/job,share/cycle,work} sed "s?\\\$SUITE_RUN_DIR?$FLOW_RUN_DIR?g" \ $TEST_SOURCE_DIR/$TEST_KEY.out >expected-ls.out -if [[ -z $JOB_HOST ]]; then +if [[ $JOB_HOST != localhost ]]; then sed -i "/my_task_2/d" expected-ls.out fi file_cmp "$TEST_KEY.out" "$TEST_KEY.out" expected-ls.out file_cmp "$TEST_KEY.err" "$TEST_KEY.err" rose_prune @@ -25,7 +25,7 @@ WARM[-PT12H]:finish-all => rose_prune [[WARM]] [[my_task_1]] inherit = WARM -{% if HOST is defined %} +{% if HOST != 'localhost' %} [[my_task_2]] inherit = WARM [[[remote]]] diff --git a/t/rose-task-run/08-app-fcm-make.t b/t/rose-task-run/08-app-fcm-make.t index 9fe0395e13..9675d6f019 100755 --- a/t/rose-task-run/08-app-fcm-make.t +++ b/t/rose-task-run/08-app-fcm-make.t @@ -20,6 +20,7 @@ # Test fcm_make built-in application, basic usages. #------------------------------------------------------------------------------- . $(dirname $0)/test_header +skip_all 'TODO: #2445' if ! fcm help make 1>/dev/null 2>&1; then skip_all '"fcm make" unavailable' diff --git a/t/rose-task-run/12-app-prune-integer.t b/t/rose-task-run/12-app-prune-integer.t index e4d4c285eb..16f7e3d47d 100755 --- a/t/rose-task-run/12-app-prune-integer.t +++ b/t/rose-task-run/12-app-prune-integer.t @@ -21,6 +21,8 @@ #------------------------------------------------------------------------------- . $(dirname $0)/test_header +skip_all 'TODO: #2445' + #------------------------------------------------------------------------------- JOB_HOST=$(rose config --default= 't' 'job-host') if [[ -n $JOB_HOST ]]; then diff --git a/t/rose-task-run/15-app-prune-extglob.t b/t/rose-task-run/15-app-prune-extglob.t index bdf4f8da02..6d52455b5b 100755 --- a/t/rose-task-run/15-app-prune-extglob.t +++ b/t/rose-task-run/15-app-prune-extglob.t @@ -31,7 +31,8 @@ run_pass "${TEST_KEY}" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ --flow-name="${FLOW}" \ - --no-run-name + --no-run-name \ + -S "JOB_HOST='localhost'" TEST_KEY="${TEST_KEY_BASE}-play" run_pass "${TEST_KEY}" \ cylc play \ diff --git a/t/rose-task-run/15-app-prune-extglob/flow.cylc b/t/rose-task-run/15-app-prune-extglob/flow.cylc index 0c0607c9eb..883d474049 100644 --- a/t/rose-task-run/15-app-prune-extglob/flow.cylc +++ b/t/rose-task-run/15-app-prune-extglob/flow.cylc @@ -1,29 +1,30 @@ #!jinja2 + [cylc] - UTC mode=True + UTC mode = True [[events]] abort on timeout = True - timeout=PT1M + timeout = PT1M + [scheduling] - initial cycle point=20150101 - final cycle point=20150103 + initial cycle point = 20150101 + final cycle point = 20150103 [[dependencies]] [[[P1D]]] - graph=""" -creator[-P1D] => pruner => creator -""" + graph = """ + creator[-P1D] => pruner => creator + """ [runtime] [[root]] - [[[job]]] - execution time limit=PT1M + execution time limit = PT1M [[creator]] - script=rose task-run -{% if JOB_HOST is defined %} + script = rose task-run +{% if JOB_HOST != 'localhost' %} [[[remote]]] host = {{JOB_HOST}} {% endif %} [[pruner]] - script=""" -rose task-run -v -v --debug | tee -a "${CYLC_SUITE_RUN_DIR}/prune.log" -""" + script = """ + rose task-run -v -v --debug | tee -a "${CYLC_SUITE_RUN_DIR}/prune.log" + """ diff --git a/t/rose-task-run/18-app-fcm-make-ctx-name.t b/t/rose-task-run/18-app-fcm-make-ctx-name.t index 9c5398f0d5..c858347848 100755 --- a/t/rose-task-run/18-app-fcm-make-ctx-name.t +++ b/t/rose-task-run/18-app-fcm-make-ctx-name.t @@ -27,6 +27,8 @@ #------------------------------------------------------------------------------- . $(dirname $0)/test_header +skip_all 'TODO: #2445' + if ! fcm help make 1>/dev/null 2>&1; then skip_all '"fcm make" unavailable' fi diff --git a/t/rose-task-run/20-app-fcm-make-dest.t b/t/rose-task-run/20-app-fcm-make-dest.t index 4dc2b41a57..37a06ffd0c 100755 --- a/t/rose-task-run/20-app-fcm-make-dest.t +++ b/t/rose-task-run/20-app-fcm-make-dest.t @@ -24,6 +24,7 @@ # host, as well as "gfortran" being installed and available there. #------------------------------------------------------------------------------- . $(dirname $0)/test_header +skip_all 'TODO: #2445' if ! fcm help make 1>/dev/null 2>&1; then skip_all '"fcm make" unavailable' @@ -56,9 +57,9 @@ run_pass "${TEST_KEY_BASE}-install" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ --flow-name="${FLOW}" \ - --no-run-name - -S "HOST=\"${JOB_HOST}\"" \ - -S "GREET=\"${GREET}\"" + --no-run-name \ + -S "HOST='${JOB_HOST}'" \ + -S "GREET='${GREET}'" run_pass "${TEST_KEY_BASE}-play" \ timeout 120 \ cylc play \ diff --git a/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t b/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t index 0f644551b3..59b194451c 100755 --- a/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t +++ b/t/rose-task-run/26-app-fcm-make-new-mode-with-cont.t @@ -24,6 +24,7 @@ # "gfortran" being installed and available. #------------------------------------------------------------------------------- . "$(dirname "$0")/test_header" +skip_all 'TODO: #2445' if ! fcm help make 1>/dev/null 2>&1; then skip_all '"fcm make" unavailable' diff --git a/t/rose-task-run/29-app-prune-extglob-remote.t b/t/rose-task-run/29-app-prune-extglob-remote.t index be44e47a7e..69cb5a2e9a 100755 --- a/t/rose-task-run/29-app-prune-extglob-remote.t +++ b/t/rose-task-run/29-app-prune-extglob-remote.t @@ -20,8 +20,8 @@ # Test "rose_prune" built-in application, with bash extglob, using not glob. #------------------------------------------------------------------------------- . $(dirname $0)/test_header +skip_all 'TODO: #2445' -JOB_HOST_OPT= if [[ "${TEST_KEY_BASE}" == *-remote ]]; then JOB_HOST=$(rose config --default= 't' 'job-host') if [[ -z "${JOB_HOST}" ]]; then @@ -31,7 +31,6 @@ if [[ "${TEST_KEY_BASE}" == *-remote ]]; then if [[ -z "${JOB_HOST}" ]]; then skip_all '"[t]job-host" not available' fi - JOB_HOST_OPT="-S JOB_HOST=\"${JOB_HOST}\"" fi tests 3 @@ -44,13 +43,13 @@ run_pass "${TEST_KEY}" \ cylc install \ -C "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}" \ --flow-name="${FLOW}" \ + -S "JOB_HOST='$JOB_HOST'" \ --no-run-name TEST_KEY="${TEST_KEY_BASE}-play" run_pass "${TEST_KEY}" \ cylc play \ "${FLOW}" \ --host='localhost' \ - ${JOB_HOST_OPT} \ --no-detach \ --debug TEST_KEY="${TEST_KEY_BASE}-prune.log" From d44e8e6839f5490a8031db3693a03805afc6f745 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Fri, 12 Mar 2021 16:08:12 +0000 Subject: [PATCH 61/78] flake8++ --- metomi/rose/config_processors/fileinstall.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/metomi/rose/config_processors/fileinstall.py b/metomi/rose/config_processors/fileinstall.py index 190e4a16f7..f7ed78c901 100644 --- a/metomi/rose/config_processors/fileinstall.py +++ b/metomi/rose/config_processors/fileinstall.py @@ -262,10 +262,14 @@ def _process(self, conf_tree, nodes, loc_dao, **kwargs): conn = loc_dao.get_conn() try: prev_dep_locs = conn.execute( - "SELECT * FROM dep_names WHERE name=?", [target.name] + "SELECT * FROM dep_names WHERE name=?", + [target.name] ).fetchall() prev_dep_locs = [i[1] for i in prev_dep_locs] - prev_dep_locs = [loc_dao.select(i) for i in prev_dep_locs] + prev_dep_locs = [ + loc_dao.select(i) + for i in prev_dep_locs + ] if ( [i.name for i in prev_dep_locs] != [i.name for i in target.dep_locs] From c78cdc1af66cea33d0bd66c5a89445ec449a5cbb Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Fri, 12 Mar 2021 16:59:54 +0000 Subject: [PATCH 62/78] fileinstall: don't close database * it needs to be open for subsequent calls --- metomi/rose/config_processors/fileinstall.py | 31 +++++++++----------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/metomi/rose/config_processors/fileinstall.py b/metomi/rose/config_processors/fileinstall.py index f7ed78c901..cb5c46e69a 100644 --- a/metomi/rose/config_processors/fileinstall.py +++ b/metomi/rose/config_processors/fileinstall.py @@ -260,23 +260,20 @@ def _process(self, conf_tree, nodes, loc_dao, **kwargs): # See if any sources have changed names. if not target.is_out_of_date: conn = loc_dao.get_conn() - try: - prev_dep_locs = conn.execute( - "SELECT * FROM dep_names WHERE name=?", - [target.name] - ).fetchall() - prev_dep_locs = [i[1] for i in prev_dep_locs] - prev_dep_locs = [ - loc_dao.select(i) - for i in prev_dep_locs - ] - if ( - [i.name for i in prev_dep_locs] != - [i.name for i in target.dep_locs] - ): - target.is_out_of_date = True - finally: - conn.close() + prev_dep_locs = conn.execute( + "SELECT * FROM dep_names WHERE name=?", + [target.name] + ).fetchall() + prev_dep_locs = [i[1] for i in prev_dep_locs] + prev_dep_locs = [ + loc_dao.select(i) + for i in prev_dep_locs + ] + if ( + [i.name for i in prev_dep_locs] != + [i.name for i in target.dep_locs] + ): + target.is_out_of_date = True # See if any sources out of date if not target.is_out_of_date: for dep_loc in target.dep_locs: From ced5ab953e8f8148597d061a25356c2bbea1548a Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Mon, 15 Mar 2021 16:54:13 +0000 Subject: [PATCH 63/78] fileinstall: fix source order check * small bug introduced in https://github.com/metomi/rose/pull/2441 * deps are in the correct order in the database * however insertion order is not preserved by SELECT --- metomi/rose/config_processors/fileinstall.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/metomi/rose/config_processors/fileinstall.py b/metomi/rose/config_processors/fileinstall.py index cb5c46e69a..2e2ef2202f 100644 --- a/metomi/rose/config_processors/fileinstall.py +++ b/metomi/rose/config_processors/fileinstall.py @@ -261,7 +261,12 @@ def _process(self, conf_tree, nodes, loc_dao, **kwargs): if not target.is_out_of_date: conn = loc_dao.get_conn() prev_dep_locs = conn.execute( - "SELECT * FROM dep_names WHERE name=?", + """ + SELECT * + FROM dep_names + WHERE name=? + ORDER BY ROWID + """, [target.name] ).fetchall() prev_dep_locs = [i[1] for i in prev_dep_locs] From 06b19aa8ab910862b953ee4ca56902548f0d4076 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Tue, 16 Mar 2021 13:16:18 +0000 Subject: [PATCH 64/78] Apply suggestions from code review Co-authored-by: Ronnie Dutta <61982285+MetRonnie@users.noreply.github.com> --- .github/workflows/test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fe9977ae61..24778397ff 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ on: description: The FCM branch to test against required: false fcm_repo: - description: The FCM branch to test against + description: The FCM repo to test against required: false cylc_ref: description: The Cylc branch to test against @@ -51,7 +51,7 @@ jobs: path: rose - name: Configure Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} @@ -60,7 +60,7 @@ jobs: cylc_repo: ${{ github.event.inputs.cylc_repo || 'cylc/cylc-flow' }} cylc_branch: ${{ github.event.inputs.cylc_ref || 'master' }} run: | - pip install "git+https://github.com/$cylc_repo@$cylc_branch" + pip install "git+https://github.com/${cylc_repo}@${cylc_branch}" - name: Brew Install if: startsWith(matrix.os, 'macos') @@ -195,7 +195,7 @@ jobs: ref: ${{ github.event.inputs.rose_ref || github.sha }} - name: Configure Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: 3.7 From 9fa289ea2701997136675c5e082331264cc33c68 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Wed, 17 Mar 2021 09:57:23 +0000 Subject: [PATCH 65/78] actions: test against py3.7,3.8,3.9 --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 24778397ff..7c415317c4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,7 +32,7 @@ jobs: fail-fast: false matrix: os: ['ubuntu-latest'] - python-version: ['3.7'] + python-version: ['3.7', '3.8', '3.9'] # TODO: re-enable macos testing # currently (in the absence of a wrapper script) rose cannot be run # from within cylc jobs in situations where the output of the following From f1bbd7e3426331cb94c7c51e2869cab9ba8c99e9 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Wed, 17 Mar 2021 12:04:44 +0000 Subject: [PATCH 66/78] Apply suggestions from code review Co-authored-by: Ronnie Dutta <61982285+MetRonnie@users.noreply.github.com> --- metomi/rosie/suite_id.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/metomi/rosie/suite_id.py b/metomi/rosie/suite_id.py index 7448e854a0..b98ed3ca57 100644 --- a/metomi/rosie/suite_id.py +++ b/metomi/rosie/suite_id.py @@ -373,19 +373,20 @@ def _from_location(self, location): loc = Path(location) sdrr = Path('~', suite_dir_rel_root).expanduser().resolve() try: - if loc.relative_to(sdrr): - if (loc / 'rose-suite.info').exists(): - # This is an installed workflow with a rose-suite.info file - # (most likely a Cylc8 run directory) - - # TODO: extract version control information written by - # Cylc install, see: - # https://github.com/metomi/rose/issues/2432 - # https://github.com/cylc/cylc-flow/issues/3849 - raise SuiteIdLocationError(location) + loc.relative_to(sdrr) except ValueError: # Not an installed Cylc8 workflow run directory pass + else: + if (loc / 'rose-suite.info').is_file(): + # This is an installed workflow with a rose-suite.info file + # (most likely a Cylc8 run directory) + + # TODO: extract version control information written by + # Cylc install, see: + # https://github.com/metomi/rose/issues/2432 + # https://github.com/cylc/cylc-flow/issues/3849 + raise SuiteIdLocationError(location) # Cylc7 run directory # Use a hacky way to read the "log/rose-suite-run.version" file From 2dd8ee3595f368c5c3286e136a09d77fb773a495 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Wed, 17 Mar 2021 12:36:38 +0000 Subject: [PATCH 67/78] bunch: simplify path parsing --- metomi/rose/apps/rose_bunch.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/metomi/rose/apps/rose_bunch.py b/metomi/rose/apps/rose_bunch.py index 3699ddbb6a..64091b6700 100644 --- a/metomi/rose/apps/rose_bunch.py +++ b/metomi/rose/apps/rose_bunch.py @@ -17,7 +17,6 @@ """Builtin application: rose_bunch: run multiple commands in parallel. """ -from collections import Counter import itertools import os import shlex @@ -582,13 +581,6 @@ def simplify_path(path): 'a:b:c:d:e' """ - path = path.split(':') - counter = Counter(path) - for item, count in counter.items(): - ptr = len(path) - 1 - while count > 1: - if path[ptr] == item: - path.pop(ptr) - count -= 1 - ptr -= 1 - return ':'.join(path) + return ':'.join( + dict.fromkeys(path.split(':')).keys() + ) From 35a66bbfa8ff0cfd7ea1a276d98eafb69dad6982 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Fri, 19 Mar 2021 09:03:48 +0000 Subject: [PATCH 68/78] host-select: stdin pollution filtering and testing * filter out stdin pollution before the start marker * test filtering --- metomi/rose/host_select_client.py | 6 +- metomi/rose/tests/test_host_select_client.py | 80 ++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 metomi/rose/tests/test_host_select_client.py diff --git a/metomi/rose/host_select_client.py b/metomi/rose/host_select_client.py index f2ed260433..fd74998141 100644 --- a/metomi/rose/host_select_client.py +++ b/metomi/rose/host_select_client.py @@ -23,13 +23,17 @@ def main(): # read metrics from stdin + started = False line = True metrics = '' while True: line = sys.stdin.readline().strip() if '**start**' in line: + started = True continue - if '**end**' in line: + elif not started: + continue + elif '**end**' in line: break metrics += f'\n{line}' metrics = json.loads(metrics) diff --git a/metomi/rose/tests/test_host_select_client.py b/metomi/rose/tests/test_host_select_client.py new file mode 100644 index 0000000000..b40c213c24 --- /dev/null +++ b/metomi/rose/tests/test_host_select_client.py @@ -0,0 +1,80 @@ +# Copyright (C) 2012-2019 British Crown (Met Office) & Contributors. +# +# This file is part of Rose, a framework for meteorological suites. +# +# Rose 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. +# +# Rose 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 Rose. If not, see . +import json +from io import StringIO +from textwrap import dedent + +from metomi.rose.host_select_client import main as host_select + + +def test_empty(monkeypatch, capsys): + """It should not return any results for an empty request.""" + monkeypatch.setattr( + 'sys.stdin', + StringIO(dedent(''' + **start** + [] + **end** + ''')) + ) + host_select() + captured = capsys.readouterr() + assert captured.out == '[]\n' + assert captured.err == '' + + +def test_stdin_pollution(monkeypatch, capsys): + """Any junk before or after the start/end markers should be ignored + + Note this can come from shell profile scripts. + """ + monkeypatch.setattr( + 'sys.stdin', + StringIO(dedent(''' + hello + *&^%$**start** + [] + **end***&^%$E + world + ''')) + ) + host_select() + captured = capsys.readouterr() + assert captured.out == '[]\n' + assert captured.err == '' + + +def test_request(monkeypatch, capsys): + """Test a simple request.""" + monkeypatch.setattr( + 'sys.stdin', + StringIO(dedent(''' + **start** + [["virtual_memory"]] + **end** + ''')) + ) + host_select() + captured = capsys.readouterr() + assert captured.out + assert captured.err == '' + + results = json.loads(captured.out) + assert len(results) == 1 + result = results[0] + for key in ('active', 'available', 'free'): + assert key in result From cc55bfb035bd459b639a925b8478df825a5ab455 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Fri, 19 Mar 2021 09:09:13 +0000 Subject: [PATCH 69/78] macro: correct symbol error --- metomi/rose/macro.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metomi/rose/macro.py b/metomi/rose/macro.py index 9010cc8864..69809bba30 100644 --- a/metomi/rose/macro.py +++ b/metomi/rose/macro.py @@ -765,7 +765,7 @@ def get_macro_class_methods(macro_modules): macro_name = macro_module.__name__ contents = inspect.getmembers(macro_module) for obj_name, obj in contents: - if not inspect.isclass(object): + if not inspect.isclass(obj): continue for att_name in ALLOWED_MACRO_CLASS_METHODS: if (hasattr(obj, att_name) and From fb9cfe6bce1280c32aa6ce3094007750abf599ac Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Fri, 19 Mar 2021 14:35:53 +0000 Subject: [PATCH 70/78] Apply suggestions from code review Co-authored-by: Melanie Hall <37735232+datamel@users.noreply.github.com> Co-authored-by: Ronnie Dutta <61982285+MetRonnie@users.noreply.github.com> --- bin/rose | 2 +- metomi/rose/host_select_client.py | 2 +- metomi/rose/tests/test_host_select_client.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/rose b/bin/rose index a1e609ea4b..6b766356eb 100755 --- a/bin/rose +++ b/bin/rose @@ -45,7 +45,7 @@ ROSE_HOME_BIN="$(dirname "$0")" ROSE_VERSION="$(python3 -c "import metomi.rose; print(metomi.rose.__version__)")" export ROSE_NS ROSE_HOME_BIN ROSE_VERSION -# NOTE: cannot use assiciative array due to bash version requirement +# NOTE: cannot use associative array due to bash version requirement DEAD_ENDS=( rose-config-edit rose-edit diff --git a/metomi/rose/host_select_client.py b/metomi/rose/host_select_client.py index fd74998141..affda21391 100644 --- a/metomi/rose/host_select_client.py +++ b/metomi/rose/host_select_client.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2019 British Crown (Met Office) & Contributors. +# Copyright (C) British Crown (Met Office) & Contributors. # # This file is part of Rose, a framework for meteorological suites. # diff --git a/metomi/rose/tests/test_host_select_client.py b/metomi/rose/tests/test_host_select_client.py index b40c213c24..4881875ff6 100644 --- a/metomi/rose/tests/test_host_select_client.py +++ b/metomi/rose/tests/test_host_select_client.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2019 British Crown (Met Office) & Contributors. +# Copyright (C) British Crown (Met Office) & Contributors. # # This file is part of Rose, a framework for meteorological suites. # From a106764b8430222b585881ba7bf5e30f9487c4d7 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Mon, 22 Mar 2021 12:13:01 +0000 Subject: [PATCH 71/78] Update etc/tutorial/rose-stem/.validate Co-authored-by: Melanie Hall <37735232+datamel@users.noreply.github.com> --- etc/tutorial/rose-stem/.validate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/tutorial/rose-stem/.validate b/etc/tutorial/rose-stem/.validate index 6c9aff2276..adf657b18f 100644 --- a/etc/tutorial/rose-stem/.validate +++ b/etc/tutorial/rose-stem/.validate @@ -1,4 +1,4 @@ mkdir "${CYLC_RUN_DIR}/${REG}" echo -e '#!Jinja2\n{% set RUN_NAMES=["command_spaceship"] %}' > "${CYLC_RUN_DIR}/${REG}/flow.cylc" -cat "$TUT_DIR/rose-stem/flow.cylc" >> "${CYLC_RUN_DIR}/${REG}/suite.rc" +cat "$TUT_DIR/rose-stem/flow.cylc" >> "${CYLC_RUN_DIR}/${REG}/flow.cylc" cylc validate "${CYLC_RUN_DIR}/${REG}" -s "SOURCE_SPACESHIP=foo" From 4184f26472b6ff1e0d68c66791dc1c9574f69da6 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Mon, 22 Mar 2021 12:35:59 +0000 Subject: [PATCH 72/78] cylc: add warning for unfinished interfaces --- metomi/rose/suite_engine_procs/cylc.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/metomi/rose/suite_engine_procs/cylc.py b/metomi/rose/suite_engine_procs/cylc.py index 3cfc621dd7..0475d9c363 100644 --- a/metomi/rose/suite_engine_procs/cylc.py +++ b/metomi/rose/suite_engine_procs/cylc.py @@ -69,6 +69,13 @@ def get_suite_jobs_auths(self, suite_name, cycle_name_tuples=None): """Return remote ["[user@]host", ...] for submitted jobs.""" # TODO: reimplement for Cylc8? # https://github.com/metomi/rose/issues/2445 + self.handle_event( + Exception( + 'WARNING: Rose cannot currently inspect the platform a Cylc' + ' task has or will run on.\n' + ' https://github.com/metomi/rose/issues/2445' + ) + ) return [] def get_task_auth(self, suite_name, task_name): @@ -79,6 +86,13 @@ def get_task_auth(self, suite_name, task_name): """ # TODO: reimplement for Cylc8? # https://github.com/metomi/rose/issues/2445 + self.handle_event( + Exception( + 'WARNING: Rose cannot currently inspect the platform a Cylc' + ' task has or will run on.\n' + ' https://github.com/metomi/rose/issues/2445' + ) + ) return None def get_task_props_from_env(self): From 0a79870c4fc2be13f9d31c6374f623d121488e5a Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Tue, 23 Mar 2021 13:11:58 +0000 Subject: [PATCH 73/78] host-select: separate command errors from ssh errors --- etc/rose.conf.example | 2 ++ metomi/rose/host_select.py | 27 ++++++++++++++++++++++----- t/rose-host-select/00-basic.t | 10 +++++++++- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/etc/rose.conf.example b/etc/rose.conf.example index ae743c6e52..983362ab7f 100644 --- a/etc/rose.conf.example +++ b/etc/rose.conf.example @@ -310,6 +310,8 @@ access-list-default=USER-ID ... # Tool to compare two files when there are differences. difftool=COMMAND ... # List of selectable host groups. +# +# These must be defined in :rose:conf:`[rose-host-select]group{NAME}`. host-groups=GROUP ... # A remote host that does not share its ``HOME`` directory with ``localhost``. job-host=HOST diff --git a/metomi/rose/host_select.py b/metomi/rose/host_select.py index 325657a9fb..5d8f5a6245 100644 --- a/metomi/rose/host_select.py +++ b/metomi/rose/host_select.py @@ -56,14 +56,23 @@ def __str__(self): return "No hosts selected." -class DeadHostEvent(Event): +class HostSelectCommandFailedEvent(Event): - """An error raised when a host is not contactable.""" + """A remote host select command failed.""" KIND = Event.KIND_ERR + def __init__(self, return_code: int, host: str): + self.return_code = return_code + self.host = host + Event.__init__(self) + def __str__(self): - return self.args[0] + ": (ssh failed)" + if self.return_code == 255: + msg = 'ssh failed' + else: + msg = f'failed {self.return_code}' + return f'{self.host}: ({msg})' class HostThresholdNotMetEvent(Event): @@ -348,7 +357,11 @@ def select(self, names=None, rank_method=None, thresholds=None, proc.wait() self.handle_event(TimedOutHostEvent(host_name)) elif proc.wait(): - self.handle_event(DeadHostEvent(host_name)) + self.handle_event( + HostSelectCommandFailedEvent( + proc.returncode, host_name + ) + ) else: return [(host_name, 1)] else: @@ -399,7 +412,11 @@ def select(self, names=None, rank_method=None, thresholds=None, score = None elif proc.wait(): stdout, stderr = (f.decode() for f in proc.communicate()) - self.handle_event(DeadHostEvent(host_name)) + self.handle_event( + HostSelectCommandFailedEvent( + proc.returncode, host_name + ) + ) host_proc_dict.pop(host_name) else: out = proc.communicate()[0].decode() diff --git a/t/rose-host-select/00-basic.t b/t/rose-host-select/00-basic.t index ccafa9b2b4..849bbe700c 100755 --- a/t/rose-host-select/00-basic.t +++ b/t/rose-host-select/00-basic.t @@ -30,7 +30,15 @@ if [[ -n $HOST_GROUPS ]]; then else N_HOST_GROUPS=1 fi -tests $((N_HOST_GROUPS * 2 + 4)) +tests $((N_HOST_GROUPS * 2 + 7)) +#------------------------------------------------------------------------------- +TEST_KEY="${TEST_KEY_BASE}-ssh-fail" +run_fail "${TEST_KEY}" rose 'host-select' 'electric-monkey-eggs' +file_cmp "${TEST_KEY}.out" "${TEST_KEY}.out" Date: Wed, 24 Mar 2021 17:20:40 +0000 Subject: [PATCH 74/78] changelog --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index f5a0e0fd2d..5b02a0532f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,8 @@ It is able to run most existing Rose Suites and has been ported to Python3. **This is the first Python3 version of Rose** +[#2446](https://github.com/metomi/rose/pull/2446): Host select: change implementation to psutil for portability. + [#2288](https://github.com/metomi/rose/pull/2288): Rosie & Rosa: migrate to Python 3(.6-.7) & Tornado From 1c5dd47f32b1f669acd94e693686a68ea0585ff0 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Wed, 24 Mar 2021 18:41:41 +0000 Subject: [PATCH 75/78] tests: fix rose-task-run/38 --- t/rose-task-run/38-app-bunch-counts.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/rose-task-run/38-app-bunch-counts.t b/t/rose-task-run/38-app-bunch-counts.t index 6745252265..4e7fd6b65e 100755 --- a/t/rose-task-run/38-app-bunch-counts.t +++ b/t/rose-task-run/38-app-bunch-counts.t @@ -33,7 +33,7 @@ run_pass "$TEST_KEY" \ cylc install \ -C "$TEST_SOURCE_DIR/$TEST_KEY_BASE" \ --flow-name="$FLOW" \ - --no-run-name \ + --no-run-name TEST_KEY="${TEST_KEY_BASE}-play" run_pass "$TEST_KEY" \ cylc play \ From b8e95f00412b02601aa3732dc7eb39ecc16aa2eb Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Thu, 25 Mar 2021 13:35:08 +0000 Subject: [PATCH 76/78] tests: change tests to match job script changes * https://github.com/cylc/cylc-flow/pull/3440 --- t/rose-task-run/30-app-arch-opt-source.t | 6 +----- t/rose-task-run/36-app-arch-interrupted.t | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/t/rose-task-run/30-app-arch-opt-source.t b/t/rose-task-run/30-app-arch-opt-source.t index 4b00e27d06..d673e15f0b 100755 --- a/t/rose-task-run/30-app-arch-opt-source.t +++ b/t/rose-task-run/30-app-arch-opt-source.t @@ -18,11 +18,7 @@ # along with Rose. If not, see . #------------------------------------------------------------------------------- # Test "rose_arch" built-in application, archive with optional sources. -#------------------------------------------------------------------------------- . "$(dirname "$0")/test_header" - - -#------------------------------------------------------------------------------- tests 8 #------------------------------------------------------------------------------- # Run the suite, and wait for it to complete @@ -48,7 +44,7 @@ file_grep "${TEST_KEY}-archive1-01" \ 'CYLC_JOB_EXIT=SUCCEEDED' \ "${FLOW_RUN_DIR}/log/job/1/archive1/01/job.status" file_grep "${TEST_KEY}-archive2-01" \ - 'CYLC_JOB_EXIT=EXIT' \ + 'CYLC_JOB_EXIT=ERR' \ "${FLOW_RUN_DIR}/log/job/1/archive2/01/job.status" file_grep "${TEST_KEY}-archive2-02" \ 'CYLC_JOB_EXIT=SUCCEEDED' \ diff --git a/t/rose-task-run/36-app-arch-interrupted.t b/t/rose-task-run/36-app-arch-interrupted.t index c9528671b9..dc6d526007 100755 --- a/t/rose-task-run/36-app-arch-interrupted.t +++ b/t/rose-task-run/36-app-arch-interrupted.t @@ -44,7 +44,7 @@ run_pass "${TEST_KEY_BASE}-play" \ #------------------------------------------------------------------------------- TEST_KEY="${TEST_KEY_BASE}-job.status" file_grep "${TEST_KEY}-archive-01" \ - 'CYLC_JOB_EXIT=EXIT' \ + 'CYLC_JOB_EXIT=ERR' \ "${FLOW_RUN_DIR}/log/job/1/archive/01/job.status" file_grep "${TEST_KEY}-archive-02" \ 'CYLC_JOB_EXIT=SUCCEEDED' \ From 241fb6c9da72692d968edbf3daae44fe3f03e52b Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Thu, 25 Mar 2021 13:37:25 +0000 Subject: [PATCH 77/78] docs: fix broken link * fix link broken by - https://github.com/cylc/cylc-doc/pull/225 --- sphinx/hyperlinks.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sphinx/hyperlinks.rst b/sphinx/hyperlinks.rst index 9129d67fc3..7c8c22b6b8 100644 --- a/sphinx/hyperlinks.rst +++ b/sphinx/hyperlinks.rst @@ -13,7 +13,8 @@ .. _Cylc Flow: https://github.com/cylc/cylc-flow .. _Cylc Rose: https://github.com/cylc/cylc-rose .. _Cylc User Guide: https://cylc.github.io/documentation/#the-cylc-user-guide -.. _Cylc Suite Design Guide: https://cylc.github.io/cylc-doc/current/html/suite-design-guide/suite-design-guide-master.html +.. _Cylc Suite Design Guide: https://cylc.github.io/cylc-doc/stable/html/suite-design-guide/suite-design-guide-master.html + .. _EmPy: http://www.alcyone.com/software/empy/ .. _extglob pattern matching: https://www.gnu.org/software/bash/manual/html_node/Pattern-Matching.html#Pattern-Matching .. _FCM: https://metomi.github.io/fcm/doc/ From 3b834a8596b3ff507fce9d54abbeb0d20ab38868 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Thu, 25 Mar 2021 13:57:58 +0000 Subject: [PATCH 78/78] host-select: expand paths in filesystem arguments * restores the default filesystem to ~ --- metomi/rose/host_select.py | 2 +- metomi/rose/host_select_client.py | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/metomi/rose/host_select.py b/metomi/rose/host_select.py index 5d8f5a6245..fc7c2a2f7e 100644 --- a/metomi/rose/host_select.py +++ b/metomi/rose/host_select.py @@ -589,7 +589,7 @@ class FileSystemScorer(RandomScorer): """Score host by average file system percentage usage.""" - ARG = "/" # TODO? + ARG = "~" KEY = "fs" def get_command(self, method_arg): diff --git a/metomi/rose/host_select_client.py b/metomi/rose/host_select_client.py index affda21391..c0a377499c 100644 --- a/metomi/rose/host_select_client.py +++ b/metomi/rose/host_select_client.py @@ -16,6 +16,7 @@ # along with Rose. If not, see . # ----------------------------------------------------------------------------- import json +from pathlib import Path import sys import psutil @@ -40,8 +41,16 @@ def main(): # extract metrics using psutil ret = [ - getattr(psutil, key[0])(*key[1:]) - for key in metrics + getattr(psutil, key)( + *args + if key != 'disk_usage' + else [ + # expand ~ in paths for disk usage queries + Path(arg).expanduser() + for arg in args + ] + ) + for key, *args in metrics ] # serialise results

yqu~C1db|T4E_*GG!W#5=+W5_z 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 I8KHaFBYht-bxl|317|J#Z92f^5M4sG#z4 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�HU9$WoQ*~i^-@Pg?#5AU&%s@vTcj3YS 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 aL}PSVZm#;Gp!hewBxoJe)hHs`kp2fSi0PsL diff --git a/sphinx/tutorial/cylc/img/cylc-gui-dot.png b/sphinx/tutorial/cylc/img/cylc-gui-dot.png deleted file mode 100644 index 0cfede9f3dd545b78c83ecebf79dbdf294c43b69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35155 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 z 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 NwA6Lga#byY{vSBYXu<#h diff --git a/sphinx/tutorial/cylc/img/cylc-gui-suite-start.png b/sphinx/tutorial/cylc/img/cylc-gui-suite-start.png deleted file mode 100644 index 6cbc05681da3bcc092a79c6afb977df6cd5b06d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18138 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 diff --git a/sphinx/tutorial/cylc/img/cylc-gui-view-log.png b/sphinx/tutorial/cylc/img/cylc-gui-view-log.png deleted file mode 100644 index 0b3338fd0b054b5de24eca2cc81b1b1ca518ca4d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19206 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 N002ovPDHLkV1h&^mSO+^ diff --git a/sphinx/tutorial/cylc/img/cylc-gui.png b/sphinx/tutorial/cylc/img/cylc-gui.png deleted file mode 100644 index f60097fa2b6d3c37807914e6208a1f579984a050..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 153532 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%0Do$Ec#7_8`OXw{w;JxFil?_8q?*f7 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