From 37c41b2b75052708097966e390a44e7e75a31f2d Mon Sep 17 00:00:00 2001 From: Tim Pillinger <26465611+wxtim@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:23:53 +0100 Subject: [PATCH 1/3] Refactor testing framework: Remove references to runN and run1 --- tests/conftest.py | 12 ++++++++++++ tests/functional/test_ROSE_ORIG_HOST.py | 4 ++-- tests/functional/test_reinstall.py | 16 ++++++++-------- tests/functional/test_reinstall_clean.py | 6 +++--- tests/functional/test_rose_fileinstall.py | 4 ++-- 5 files changed, 27 insertions(+), 15 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 4031397e..de037e88 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,6 +18,7 @@ import pytest from types import SimpleNamespace +from uuid import uuid4 from cylc.flow import __version__ as CYLC_VERSION from cylc.flow.option_parsers import Options @@ -38,6 +39,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 @@ -128,8 +134,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'] try: cylc_install(options, str(srcpath)) diff --git a/tests/functional/test_ROSE_ORIG_HOST.py b/tests/functional/test_ROSE_ORIG_HOST.py index b5037da8..743bb518 100644 --- a/tests/functional/test_ROSE_ORIG_HOST.py +++ b/tests/functional/test_ROSE_ORIG_HOST.py @@ -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) @@ -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 diff --git a/tests/functional/test_reinstall.py b/tests/functional/test_reinstall.py index db4c5e46..d7507824 100644 --- a/tests/functional/test_reinstall.py +++ b/tests/functional/test_reinstall.py @@ -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' @@ -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'] } @@ -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' @@ -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, @@ -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' diff --git a/tests/functional/test_reinstall_clean.py b/tests/functional/test_reinstall_clean.py index 2883afb5..f443d0ef 100644 --- a/tests/functional/test_reinstall_clean.py +++ b/tests/functional/test_reinstall_clean.py @@ -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' @@ -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'], @@ -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' diff --git a/tests/functional/test_rose_fileinstall.py b/tests/functional/test_rose_fileinstall.py index e11a496f..0b134122 100644 --- a/tests/functional/test_rose_fileinstall.py +++ b/tests/functional/test_rose_fileinstall.py @@ -85,7 +85,7 @@ 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()) @@ -93,7 +93,7 @@ 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() )) From c0e9ddaff9b794e66195261b4455bbe7d9b47349 Mon Sep 17 00:00:00 2001 From: Tim Pillinger <26465611+wxtim@users.noreply.github.com> Date: Wed, 10 Apr 2024 13:17:47 +0100 Subject: [PATCH 2/3] working but imperfect test full test replicating original case --- tests/conftest.py | 45 +++++++++ tests/functional/test_reinstall.py | 1 + tests/functional/test_reload.py | 148 +++++++++++++++++++++++++++++ 3 files changed, 194 insertions(+) create mode 100644 tests/functional/test_reload.py diff --git a/tests/conftest.py b/tests/conftest.py index de037e88..e4a32719 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,8 +16,13 @@ """Functional tests for top-level function record_cylc_install_options and """ +from pathlib import Path import pytest +from subprocess import run +from shlex import split +from time import sleep from types import SimpleNamespace +from typing import TYPE_CHECKING from uuid import uuid4 from cylc.flow import __version__ as CYLC_VERSION @@ -39,6 +44,10 @@ ) +if TYPE_CHECKING: + from pathlib import Path + + def test_workflow_name(): """Return a UUID to use as a test name""" return f'cylc-rose-test-{str(uuid4())[:8]}' @@ -209,3 +218,39 @@ 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, file_exists=True): + for _ in range(timeout): + if file_exists and fpath.exists(): + break + elif not file_exists and not fpath.exists(): + break + sleep(1) + else: + raise TimeoutError( + f'file {fpath} not found after {timeout} seconds') + return _inner + + +@pytest.fixture +def cylc_stop(file_poll): + """Cylc stop & check the contact file""" + def _inner(id_): + run(split(f'cylc stop --now --now {id_}')) + contactfile = Path.home() / f'cylc-run{id_}.service/contact' + file_poll(contactfile, file_exists=False) + sleep(3) + return _inner + + +@pytest.fixture +def purge_workflow(cylc_stop): + def _inner(id_): + cylc_stop(id_) + run(split(f'cylc clean {id_}')) + return _inner diff --git a/tests/functional/test_reinstall.py b/tests/functional/test_reinstall.py index d7507824..274bc337 100644 --- a/tests/functional/test_reinstall.py +++ b/tests/functional/test_reinstall.py @@ -29,6 +29,7 @@ import pytest import shutil +from subprocess import run from itertools import product from pathlib import Path diff --git a/tests/functional/test_reload.py b/tests/functional/test_reload.py new file mode 100644 index 00000000..abb9e5ce --- /dev/null +++ b/tests/functional/test_reload.py @@ -0,0 +1,148 @@ +# 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 . +"""Functional tests for reinstalling of config files. +This test does the following: + +1. Validates a workflow. +2. Installs a workflow with some opts set using -O and + ROSE_SUITE_OPT_CONF_KEYS. +3. Re-install workflow. +4. After modifying the source ``rose-suite.conf``, re-install the flow again. + +At each step it checks the contents of +- ~/cylc-run/temporary-id/rose-suite.conf +- ~/cylc-run/temporary-id/opt/rose-suite-cylc-install.conf +""" + +import pytest +from subprocess import run + +from pathlib import Path + +@pytest.fixture +def provide_template_vars_workflow(tmp_path): + (tmp_path / 'flow.cylc').write_text( + '#!jinja2\n' + '[scheduling]\n' + ' [[graph]]\n' + ' R1 = foo\n' + '[runtime]\n' + ' [[foo]]\n' + ' script = cylc message -- {{var}}') + (tmp_path / 'rose-suite.conf').write_text( + '[template variables]\nvar="rose-suite.conf"') + + +def test_reinstall_overrides( + cylc_install_cli, cylc_reinstall_cli, tmp_path, file_poll, + provide_template_vars_workflow, purge_workflow +): + """When reinstalling and reloading the new installation are picked up. + """ + # Install workflow. + install_results = cylc_install_cli( + tmp_path, {'rose_template_vars': ['var="CLIinstall"']}) + assert install_results.ret == 0 + + # Play workflow + # TODO: non-subprocess play command: + play = run( + [ + 'cylc', 'play', '--pause', + install_results.id, + '-S', 'var="CLIplay"' + ], + capture_output=True + ) + assert play.returncode == 0 + + # Reinstall the workflow: + reinstall_results = cylc_reinstall_cli( + install_results.id, + {'rose_template_vars': ['var="CLIreinstall"']}) + assert reinstall_results.ret == 0 + + # Reload the workflow: + reload_ = run( + ['cylc', 'reload', install_results.id], + capture_output=True, + ) + assert reload_.returncode == 0 + + # 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) + + +def test_reload_overrides( + cylc_install_cli, cylc_reinstall_cli, tmp_path, file_poll, + provide_template_vars_workflow, purge_workflow, cylc_stop +): + """When we restart a workflow, the play CLI options are honoured. + + Example: + $ cylc install foo -S 'X=0' + $ cylc play foo -S 'X=1' + $ cylc stop foo + $ cylc play foo -S 'X=2' + + Inside the workflow the restart command arg should override the + content of the files and be X=2. + """ + # Install workflow. + install_results = cylc_install_cli( + tmp_path, {'rose_template_vars': ['var="CLIinstall"']}) + assert install_results.ret == 0 + + # Play workflow + # TODO: non-subprocess play command: + play = run( + [ + 'cylc', 'play', '--pause', + install_results.id, + '-S', 'var="CLIplay"' + ], + capture_output=True + ) + assert play.returncode == 0 + + cylc_stop(install_results.id) + + # Play (restart) workflow + # TODO: non-subprocess play command: + play = run( + [ + 'cylc', 'play', '--pause', + install_results.id, + '-S', 'var="CLIrestart"' + ], + capture_output=True + ) + assert play.returncode == 0 + + # The config being run has been modified: + run_dir = Path.home() / 'cylc-run' / install_results.id + config_log = (run_dir / 'log/config/02-restart-02.cylc') + file_poll(config_log) + + assert 'cylc message -- CLIrestart' in config_log.read_text() + + purge_workflow(install_results.id) From 1d9e5238ac62891c86144874eef59ad9866ee9a8 Mon Sep 17 00:00:00 2001 From: Tim Pillinger <26465611+wxtim@users.noreply.github.com> Date: Fri, 12 Apr 2024 11:37:31 +0100 Subject: [PATCH 3/3] Update tests/functional/test_reload.py --- tests/functional/test_reload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/test_reload.py b/tests/functional/test_reload.py index abb9e5ce..2ede58bc 100644 --- a/tests/functional/test_reload.py +++ b/tests/functional/test_reload.py @@ -92,7 +92,7 @@ def test_reinstall_overrides( purge_workflow(install_results.id) -def test_reload_overrides( +def test_restart_overrides( cylc_install_cli, cylc_reinstall_cli, tmp_path, file_poll, provide_template_vars_workflow, purge_workflow, cylc_stop ):