Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

improve test coverage #227

Merged
merged 7 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,20 @@ source=./cylc

[report]
precision=2

exclude_lines =
pragma: no cover

# Don't complain if tests don't hit defensive assertion code:
raise NotImplementedError
return NotImplemented

# Ignore type checking code:
if (typing\.)?TYPE_CHECKING:
@overload( |$)

# Don't complain about ellipsis (exception classes, typing overloads etc):
\.\.\.

# Ignore abstract methods
@(abc\.)?abstractmethod
7 changes: 2 additions & 5 deletions cylc/rose/entry_points.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,7 @@ def record_cylc_install_options(
"""
# Create a config based on command line options:
cli_config = get_cli_opts_node(opts, srcdir)
# Leave now if there is nothing to do:
if not cli_config:
return False
MetRonnie marked this conversation as resolved.
Show resolved Hide resolved

# raise error if CLI config has multiple templating sections
identify_templating_section(cli_config)

Expand Down Expand Up @@ -218,8 +216,7 @@ def record_cylc_install_options(
dumper.dump(cli_config, str(conf_filepath))

# Merge the opts section of the rose-suite.conf with those set by CLI:
if not rose_conf_filepath.is_file():
rose_conf_filepath.touch()
rose_conf_filepath.touch()
MetRonnie marked this conversation as resolved.
Show resolved Hide resolved
rose_suite_conf = loader.load(str(rose_conf_filepath))
rose_suite_conf = add_cylc_install_to_rose_conf_node_opts(
rose_suite_conf, cli_config
Expand Down
37 changes: 16 additions & 21 deletions cylc/rose/stem.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,21 +100,13 @@ def __repr__(self):
__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."""
"""Event to report a name for the suite being set.

Simple parser of output expected to be in the format:
Key: Value.
"""

LEVEL = Event.V

Expand Down Expand Up @@ -433,6 +425,16 @@ def _prepend_localhost(self, url):
url = self.host_selector.get_local_host() + ':' + url
return url

def _parse_auto_opts(self):
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] + '"')

def process(self):
"""Process STEM options into 'rose suite-run' options."""
# Generate options for source trees
Expand Down Expand Up @@ -487,14 +489,7 @@ def process(self):
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] + '"')
self._parse_auto_opts()

# Change into the suite directory
if getattr(self.opts, 'workflow_conf_dir', None):
Expand Down
87 changes: 36 additions & 51 deletions cylc/rose/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ def get_rose_vars_from_config_node(config, config_node, environ):
Dictionary of environment variables

"""
templating = None

# Don't allow multiple templating sections.
templating = identify_templating_section(config_node)

Expand Down Expand Up @@ -128,54 +126,43 @@ def get_rose_vars_from_config_node(config, config_node, environ):

# For each of the template language sections extract items to a simple
# dict to be returned.
if 'env' in config_node.value:

config['env'] = {
item[0][1]: item[1].value for item in
config_node.value['env'].walk()
if item[1].state == ConfigNode.STATE_NORMAL
}
if templating in config_node.value:
config['template_variables'] = {
item[0][1]: item[1].value for item in
config_node.value[templating].walk()
if item[1].state == ConfigNode.STATE_NORMAL
}
elif 'template variables' in config_node.value:
config['template_variables'] = {
item[0][1]: item[1].value for item in
config_node.value['template variables'].walk()
if item[1].state == ConfigNode.STATE_NORMAL
}
config['env'] = {
item[0][1]: item[1].value for item in
config_node.value['env'].walk()
if item[1].state == ConfigNode.STATE_NORMAL
}
config['template_variables'] = {
item[0][1]: item[1].value for item in
config_node.value[templating].walk()
if item[1].state == ConfigNode.STATE_NORMAL
}

# Add the entire config to ROSE_SUITE_VARIABLES to allow for programatic
# access.
if templating is not None:
with patch_jinja2_leading_zeros():
# BACK COMPAT: patch_jinja2_leading_zeros
# back support zero-padded integers for a limited time to help
# users migrate before upgrading cylc-flow to Jinja2>=3.1
parser = Parser()
for key, value in config['template_variables'].items():
# The special variables are already Python variables.
if key not in ['ROSE_ORIG_HOST', 'ROSE_VERSION', 'ROSE_SITE']:
try:
config['template_variables'][key] = (
parser.literal_eval(value)
)
except Exception:
raise ConfigProcessError(
[templating, key],
value,
f'Invalid template variable: {value}'
'\nMust be a valid Python or Jinja2 literal'
' (note strings "must be quoted").'
) from None
with patch_jinja2_leading_zeros():
# BACK COMPAT: patch_jinja2_leading_zeros
# back support zero-padded integers for a limited time to help
# users migrate before upgrading cylc-flow to Jinja2>=3.1
parser = Parser()
for key, value in config['template_variables'].items():
# The special variables are already Python variables.
if key not in ['ROSE_ORIG_HOST', 'ROSE_VERSION', 'ROSE_SITE']:
try:
config['template_variables'][key] = (
parser.literal_eval(value)
)
except Exception:
raise ConfigProcessError(
[templating, key],
value,
f'Invalid template variable: {value}'
'\nMust be a valid Python or Jinja2 literal'
' (note strings "must be quoted").'
) from None

# Add ROSE_SUITE_VARIABLES to config of templating engines in use.
if templating is not None:
config['template_variables'][
'ROSE_SUITE_VARIABLES'] = config['template_variables']
config['template_variables'][
'ROSE_SUITE_VARIABLES'] = config['template_variables']


def identify_templating_section(config_node):
Expand Down Expand Up @@ -244,7 +231,7 @@ def rose_config_tree_loader(srcdir=None, opts=None):
if opts and 'opt_conf_keys' in dir(opts) and opts.opt_conf_keys:
if isinstance(opts.opt_conf_keys, str):
opt_conf_keys += opts.opt_conf_keys.split()
elif isinstance(opts.opt_conf_keys, list):
else:
opt_conf_keys += opts.opt_conf_keys

# Optional definitions
Expand Down Expand Up @@ -367,6 +354,7 @@ def get_cli_opts_node(opts=None, srcdir=None):

# Construct new ouput based on optional Configs:
newconfig = ConfigNode()
newconfig.set(['opts'], ConfigNode())

# For each __define__ determine whether it is an env or template define.
for define in defines:
Expand Down Expand Up @@ -407,9 +395,7 @@ def get_cli_opts_node(opts=None, srcdir=None):
)

# Specialised treatement of optional configs.
if 'opts' not in newconfig:
newconfig['opts'] = ConfigNode()
newconfig['opts'].value = ''
newconfig['opts'].value = ''
MetRonnie marked this conversation as resolved.
Show resolved Hide resolved
newconfig['opts'].value = merge_opts(newconfig, opt_conf_keys)
newconfig['opts'].state = '!'

Expand Down Expand Up @@ -492,8 +478,7 @@ def merge_opts(config, opt_conf_keys):
'aleph bet gimmel'
"""
all_opt_conf_keys = []
if 'opts' in config:
all_opt_conf_keys.append(config['opts'].value)
all_opt_conf_keys.append(config['opts'].value)
if "ROSE_SUITE_OPT_CONF_KEYS" in os.environ:
all_opt_conf_keys.append(os.environ["ROSE_SUITE_OPT_CONF_KEYS"])
if opt_conf_keys and isinstance(opt_conf_keys, str):
Expand Down
39 changes: 32 additions & 7 deletions tests/unit/test_functional_post_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,18 @@

from metomi.isodatetime.datetimeoper import DateTimeOperator

import cylc
from cylc.flow.hostuserutil import get_host
from cylc.rose.entry_points import (
record_cylc_install_options, rose_fileinstall, post_install
record_cylc_install_options, rose_fileinstall, post_install,
copy_config_file
)
from cylc.rose.utilities import (
ROSE_ORIG_HOST_INSTALLED_OVERRIDE_STRING,
MultipleTemplatingEnginesError
)
from metomi.rose.config import ConfigLoader
from metomi.rose.config_tree import ConfigTree


HOST = get_host()
Expand Down Expand Up @@ -346,17 +349,39 @@ def test_template_section_conflict(


def test_rose_fileinstall_exception(tmp_path, monkeypatch):
def broken():
raise FileNotFoundError('Any Old Error')
import os
monkeypatch.setattr(os, 'getcwd', broken)
(tmp_path / 'rose-suite.conf').touch()
"""It throws an exception if you try to install files to a non existent
destination.

(And returns to the directory you started at)
"""
def fakenode(_, __):
tree = ConfigTree()
tree.node.value = {'file': ''}
return tree

monkeypatch.setattr(
cylc.rose.entry_points, 'rose_config_tree_loader',
fakenode
)
monkeypatch.setattr(
cylc.rose.entry_points, "rose_config_exists", lambda x, y: True)
with pytest.raises(FileNotFoundError):
rose_fileinstall(srcdir=tmp_path, rundir=tmp_path)
rose_fileinstall(srcdir=tmp_path, rundir='/oiruhgaqhnaigujhj')


def test_cylc_no_rose(tmp_path):
"""A Cylc workflow that contains no ``rose-suite.conf`` installs OK.
"""
from cylc.rose.entry_points import post_install
assert post_install(srcdir=tmp_path, rundir=tmp_path) is False


def test_copy_config_file_fails():
"""It fails when source or rundir not specified."""
with pytest.raises(FileNotFoundError, match='both source and rundir'):
copy_config_file()


def test_copy_config_file_fails2():
"""It fails if source not a rose suite."""
copy_config_file(srcdir='/foo', rundir='/bar')
3 changes: 1 addition & 2 deletions tests/unit/test_rose_opts.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,7 @@ def test_rose_fileinstall_validate(fixture_provide_flow, cylc_validate_cli):
def test_rose_fileinstall_run(fixture_install_flow):
"""Workflow installs:
"""
_, _, _, result, _ = fixture_install_flow
assert result.ret == 0
fixture_install_flow
wxtim marked this conversation as resolved.
Show resolved Hide resolved


def test_rose_fileinstall_rose_conf(fixture_install_flow):
Expand Down
Loading