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

Delete opts after post install #312

Merged
merged 20 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
15 changes: 12 additions & 3 deletions cylc/rose/entry_points.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from metomi.rose.config import ConfigLoader, ConfigDumper
from cylc.rose.utilities import (
ROSE_ORIG_HOST_INSTALLED_OVERRIDE_STRING,
ROSE_SUITE_OPT_CONF_KEYS,
deprecation_warnings,
dump_rose_log,
get_rose_vars_from_config_node,
Expand Down Expand Up @@ -54,12 +55,12 @@ def __str__(self):
return msg


def pre_configure(srcdir=None, opts=None, rundir=None):
srcdir, rundir = paths_to_pathlib([srcdir, rundir])
def pre_configure(srcdir, opts):
srcdir = Path(srcdir)
return get_rose_vars(srcdir=srcdir, opts=opts)


def post_install(srcdir=None, opts=None, rundir=None):
def post_install(srcdir, opts, rundir):
if not rose_config_exists(srcdir, opts):
return False
srcdir, rundir = paths_to_pathlib([srcdir, rundir])
Expand All @@ -75,6 +76,14 @@ def post_install(srcdir=None, opts=None, rundir=None):
if results['fileinstall']:
dump_rose_log(rundir=rundir, node=results['fileinstall'])

# Having dumped the config we clear rose options
# as they do not apply after this.
# see https://github.com/cylc/cylc-rose/pull/312
opts.rose_template_vars = []
opts.opt_conf_keys = []
opts.defines = []
os.unsetenv(ROSE_SUITE_OPT_CONF_KEYS)

return results


Expand Down
7 changes: 4 additions & 3 deletions cylc/rose/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from optparse import Values


ROSE_SUITE_OPT_CONF_KEYS = "ROSE_SUITE_OPT_CONF_KEYS"
SECTIONS = {'jinja2:suite.rc', 'empy:suite.rc', 'template variables'}
SET_BY_CYLC = 'set by Cylc'
ROSE_ORIG_HOST_INSTALLED_OVERRIDE_STRING = (
Expand Down Expand Up @@ -257,7 +258,7 @@ def rose_config_tree_loader(srcdir=None, opts=None):
opt_conf_keys = []

# get optional config key set as environment variable:
opt_conf_keys_env = os.getenv("ROSE_SUITE_OPT_CONF_KEYS")
opt_conf_keys_env = os.getenv(ROSE_SUITE_OPT_CONF_KEYS)
if opt_conf_keys_env:
opt_conf_keys += shlex.split(opt_conf_keys_env)

Expand Down Expand Up @@ -591,8 +592,8 @@ def merge_opts(config, opt_conf_keys):
all_opt_conf_keys = []
if 'opts' in config:
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 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):
all_opt_conf_keys.append(opt_conf_keys)
if opt_conf_keys and isinstance(opt_conf_keys, list):
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ python_requires = >=3.7
include_package_data = True
install_requires =
metomi-rose>=2.1.0, <2.3.0
cylc-flow==8.2.*
cylc-flow>8.2.5, <8.3
metomi-isodatetime
ansimarkup
jinja2
Expand Down
79 changes: 78 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Functional tests for top-level function record_cylc_install_options and
"""

from functools import partial
from subprocess import run
from shlex import split
from pathlib import Path
import pytest
from time import sleep
from types import SimpleNamespace
from uuid import uuid4

from cylc.flow import __version__ as CYLC_VERSION
from cylc.flow.option_parsers import Options
Expand All @@ -38,6 +43,11 @@
)


def test_workflow_name():
"""Return a UUID to use as a test name"""
return f'cylc-rose-test-{str(uuid4())[:8]}'


@pytest.fixture(scope='module')
def mod_capsys(request):
from _pytest.capture import SysCapture
Expand Down Expand Up @@ -128,8 +138,14 @@ def _inner(srcpath, args=None):
srcpath:
args: Dictionary of arguments.
"""
if not args or not args.get('workflow_name', ''):
id_ = test_workflow_name()
args = {'workflow_name': id_}

args.update({'no_run_name': True})
options = Options(install_gop(), args)()
output = SimpleNamespace()
output.id = args['workflow_name']
Comment on lines +141 to +148
Copy link
Member

Choose a reason for hiding this comment

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

FYI, this is going to conflict when merged into master because all the output.ret, output.exc, output.out, etc stuff was stripped in favour of native pytest functionalities.

I think what you've added here mirrors the behaviour on master so should be easy to reconcile.

cylc-rose/tests/conftest.py

Lines 175 to 205 in ed485a8

def _cylc_install_cli(test_dir):
"""Access the install CLI"""
async def _inner(srcpath, workflow_name=None, opts=None):
"""Install a workflow.
Args:
srcpath:
The workflow to install
workflow_name:
The workflow ID prefix to install this workflow as.
If you leave this blank, it will use the module/function's
test directory as appropriate.
opts:
Dictionary of arguments for cylc install.
"""
nonlocal test_dir
if not workflow_name:
workflow_name = str(
(test_dir / str(uuid4())[:4]).relative_to(CYLC_RUN_DIR)
)
options = Options(
install_gop(), opts or {}
)(workflow_name=workflow_name)
if not options.workflow_name:
options.workflow_name = workflow_name
if not opts or not opts.get('no_run_name', ''):
options.no_run_name = True
return await cylc_install(options, str(srcpath))
return _inner


try:
cylc_install(options, str(srcpath))
Expand Down Expand Up @@ -197,3 +213,64 @@ def cylc_validate_cli(capsys, caplog):
@pytest.fixture(scope='module')
def mod_cylc_validate_cli(mod_capsys, mod_caplog):
return _cylc_validate_cli(mod_capsys, mod_caplog)


@pytest.fixture
def file_poll():
"""Poll for the existance of a file.
"""
def _inner(
fpath: "Path", timeout: int = 5, inverse: bool = False
):
timeout_func(
lambda: fpath.exists() != inverse,
f"file {fpath} {'still' if inverse else 'not'} found after "
f"{timeout} seconds",
timeout
)
return _inner


@pytest.fixture
def purge_workflow(run_ok, file_poll):
"""Ensure workflow is stopped and cleaned"""
def _inner(id_, timeout=5):
stop = f'cylc stop {id_} --now --now'
clean = f'cylc clean {id_}'
timeout_func(
partial(run_ok, stop),
message=f'Not run after {timeout} seconds: {stop}',
timeout=timeout
)
file_poll(
Path.home() / 'cylc-run' / id_ / '.service/contact',
inverse=True,
)
timeout_func(
partial(run_ok, clean),
message=f'Not run after {timeout} seconds: {clean}',
timeout=timeout
)
return _inner


@pytest.fixture
def run_ok():
"""Run a bash script.
Fail if it fails and return its output.
"""
def _inner(script: str):
result = run(split(script), capture_output=True)
assert result.returncode == 0, f'{script} failed: {result.stderr}'
return result
return _inner


def timeout_func(func, message, timeout=5):
"""Wrap a function in a timeout"""
for _ in range(timeout):
if func():
break
sleep(1)
else:
raise TimeoutError(message)
4 changes: 2 additions & 2 deletions tests/functional/test_ROSE_ORIG_HOST.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def fixture_install_flow(
)
install_conf_path = (
fixture_provide_flow['flowpath'] /
'runN/opt/rose-suite-cylc-install.conf'
'opt/rose-suite-cylc-install.conf'
)
text = install_conf_path.read_text()
text = re.sub('ROSE_ORIG_HOST=.*', 'ROSE_ORIG_HOST=foo', text)
Expand All @@ -143,7 +143,7 @@ def test_cylc_validate_srcdir(fixture_install_flow, mod_cylc_validate_cli):
def test_cylc_validate_rundir(fixture_install_flow, mod_cylc_validate_cli):
"""Sanity check that workflow validates:
"""
flowpath = fixture_install_flow['flowpath'] / 'runN'
flowpath = fixture_install_flow['flowpath']
result = mod_cylc_validate_cli(flowpath)
assert 'ROSE_ORIG_HOST (env) is:' in result.logging

Expand Down
16 changes: 8 additions & 8 deletions tests/functional/test_reinstall.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,14 @@ def test_cylc_install_run(fixture_install_flow):
'file_, expect',
[
(
'run1/rose-suite.conf', (
'rose-suite.conf', (
'# Config Options \'b c (cylc-install)\' from CLI appended to'
' options already in `rose-suite.conf`.\n'
'opts=a b c (cylc-install)\n'
)
),
(
'run1/opt/rose-suite-cylc-install.conf', (
'opt/rose-suite-cylc-install.conf', (
'# This file records CLI Options.\n\n'
'!opts=b c\n'
f'\n[env]\n#{ROHIOS}\nROSE_ORIG_HOST={HOST}\n'
Expand Down Expand Up @@ -152,7 +152,7 @@ def fixture_reinstall_flow(
"""
monkeymodule.delenv('ROSE_SUITE_OPT_CONF_KEYS', raising=False)
result = mod_cylc_reinstall_cli(
f'{fixture_provide_flow["test_flow_name"]}/run1',
f'{fixture_provide_flow["test_flow_name"]}',
{
'opt_conf_keys': ['d']
}
Expand All @@ -171,14 +171,14 @@ def test_cylc_reinstall_run(fixture_reinstall_flow):
'file_, expect',
[
(
'run1/rose-suite.conf', (
'rose-suite.conf', (
'# Config Options \'b c d (cylc-install)\' from CLI appended '
'to options already in `rose-suite.conf`.\n'
'opts=a b c d (cylc-install)\n'
)
),
(
'run1/opt/rose-suite-cylc-install.conf', (
'opt/rose-suite-cylc-install.conf', (
'# This file records CLI Options.\n\n'
'!opts=b c d\n'
f'\n[env]\n#{ROHIOS}\nROSE_ORIG_HOST={HOST}\n'
Expand Down Expand Up @@ -213,7 +213,7 @@ def fixture_reinstall_flow2(
'opts=z\n'
)
result = mod_cylc_reinstall_cli(
f'{fixture_provide_flow["test_flow_name"]}/run1'
f'{fixture_provide_flow["test_flow_name"]}'
)
yield {
'fixture_provide_flow': fixture_provide_flow,
Expand All @@ -229,14 +229,14 @@ def test_cylc_reinstall_run2(fixture_reinstall_flow2):
'file_, expect',
[
(
'run1/rose-suite.conf', (
'rose-suite.conf', (
'# Config Options \'b c d (cylc-install)\' from CLI appended '
'to options already in `rose-suite.conf`.\n'
'opts=z b c d (cylc-install)\n'
)
),
(
'run1/opt/rose-suite-cylc-install.conf', (
'opt/rose-suite-cylc-install.conf', (
'# This file records CLI Options.\n\n'
'!opts=b c d\n'
f'\n[env]\n#{ROHIOS}\nROSE_ORIG_HOST={HOST}\n'
Expand Down
6 changes: 3 additions & 3 deletions tests/functional/test_reinstall_clean.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def test_cylc_install_run(fixture_install_flow):
'file_, expect',
[
(
'run1/opt/rose-suite-cylc-install.conf', (
'opt/rose-suite-cylc-install.conf', (
'# This file records CLI Options.\n\n'
'!opts=bar\n\n'
'[env]\n'
Expand Down Expand Up @@ -141,7 +141,7 @@ def fixture_reinstall_flow(
"""
monkeymodule.delenv('ROSE_SUITE_OPT_CONF_KEYS', raising=False)
result = mod_cylc_reinstall_cli(
f'{fixture_provide_flow["test_flow_name"]}/run1',
f'{fixture_provide_flow["test_flow_name"]}',
{
'opt_conf_keys': ['baz'],
'defines': ['[env]BAR=2'],
Expand All @@ -162,7 +162,7 @@ def test_cylc_reinstall_run(fixture_reinstall_flow):
'file_, expect',
[
(
'run1/opt/rose-suite-cylc-install.conf', (
'opt/rose-suite-cylc-install.conf', (
'# This file records CLI Options.\n\n'
'!opts=baz\n\n'
'[env]\n'
Expand Down
76 changes: 76 additions & 0 deletions tests/functional/test_reinstall_overrides.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# THIS FILE IS PART OF THE ROSE-CYLC PLUGIN FOR THE CYLC WORKFLOW ENGINE.
# 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 <http://www.gnu.org/licenses/>.
"""Functional tests checking Cylc reinstall and reload behaviour is correct
WRT to deletion of Rose Options at the end of the post install plugin.

https://github.com/cylc/cylc-rose/pull/312
"""


from pathlib import Path
from textwrap import dedent


def test_reinstall_overrides(
cylc_install_cli,
cylc_reinstall_cli,
file_poll,
tmp_path,
purge_workflow,
run_ok
):
"""When reinstalling and reloading the new installation are picked up.
> cylc install this -S 'var=CLIinstall'
> cylc play this --pause
> cylc reinstall this -S 'var=CLIreinstall'
> cylc play this --pause
See https://github.com/cylc/cylc-flow/issues/5968
"""
(tmp_path / 'flow.cylc').write_text(dedent(""" #!jinja2
[scheduling]
[[graph]]
R1 = foo
[runtime]
[[foo]]
script = cylc message -- {{var}}
"""))
(tmp_path / 'rose-suite.conf').write_text(
'[template variables]\nvar="rose-suite.conf"')

# Install workflow.
install_results = cylc_install_cli(
tmp_path, {'rose_template_vars': ['var="CLIinstall"']})
assert install_results.ret == 0

# Play workflow
run_ok(f'cylc play --pause {install_results.id}')

# Reinstall the workflow:
reinstall_results = cylc_reinstall_cli(
install_results.id,
{'rose_template_vars': ['var="CLIreinstall"']})
assert reinstall_results.ret == 0

# Reload the workflow:
run_ok(f'cylc reload {install_results.id}')

# The config being run has been modified:
run_dir = Path.home() / 'cylc-run' / install_results.id
config_log = (run_dir / 'log/config/02-reload-01.cylc')
file_poll(config_log)
assert 'cylc message -- CLIreinstall' in config_log.read_text()

purge_workflow(install_results.id)
4 changes: 2 additions & 2 deletions tests/functional/test_rose_fileinstall.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,15 @@ def test_rose_fileinstall_subfolders(fixture_install_flow):
"""File installed into a sub directory:
"""
_, datapath, _, _, destpath = fixture_install_flow
assert ((destpath / 'runN/lib/python/lion.py').read_text() ==
assert ((destpath / 'lib/python/lion.py').read_text() ==
(datapath / 'lion.py').read_text())


def test_rose_fileinstall_concatenation(fixture_install_flow):
"""Multiple files concatenated on install(source contained wildcard):
"""
_, datapath, _, _, destpath = fixture_install_flow
assert ((destpath / 'runN/data').read_text() ==
assert ((destpath / 'data').read_text() ==
((datapath / 'randoms1.data').read_text() +
(datapath / 'randoms3.data').read_text()
))
Loading
Loading