From f28922b35d7da7aa3ff471fc878bacf85a558c2c Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Wed, 6 Dec 2023 15:21:58 +0000 Subject: [PATCH 1/4] fileinstall: remove asyncio logic * Rose will now take over the role of managing its event loop. * Addresses https://github.com/cylc/cylc-rose/issues/274 --- cylc/rose/fileinstall.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/cylc/rose/fileinstall.py b/cylc/rose/fileinstall.py index d91d32a3..c69e7ac1 100644 --- a/cylc/rose/fileinstall.py +++ b/cylc/rose/fileinstall.py @@ -39,6 +39,7 @@ def rose_fileinstall( config_tree = rose_config_tree_loader(rundir, opts) if any(i.startswith('file') for i in config_tree.node.value): + startpoint = None try: startpoint = os.getcwd() os.chdir(rundir) @@ -46,8 +47,6 @@ def rose_fileinstall( raise exc else: # Carry out imports. - import asyncio - from metomi.rose.config_processor import ConfigProcessorsManager from metomi.rose.fs_util import FileSystemUtil from metomi.rose.popen import RosePopener @@ -64,19 +63,11 @@ def rose_fileinstall( fs_util = FileSystemUtil(event_handler) popen = RosePopener(event_handler) - # Get an Asyncio loop if one doesn't exist: - # Rose may need an event loop to invoke async interfaces, - # doing this here incase we want to go async in cylc-rose. - # See https://github.com/cylc/cylc-rose/pull/130/files - try: - asyncio.get_event_loop() - except RuntimeError: - asyncio.set_event_loop(asyncio.new_event_loop()) - # Process fileinstall. config_pm = ConfigProcessorsManager(event_handler, popen, fs_util) config_pm(config_tree, "file") finally: - os.chdir(startpoint) + if startpoint: + os.chdir(startpoint) return config_tree.node From 0479c9c11ac462367a723eea06163bfe4f0fa318 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Wed, 3 Jan 2024 11:07:54 +0000 Subject: [PATCH 2/4] install/reinstall: adapt to new async interfaces * The Cylc install and reinstall interfaces are now async. * This adapts rose-stem to handle the change and adjusts the tests. --- cylc/rose/entry_points.py | 7 +- cylc/rose/fileinstall.py | 6 +- cylc/rose/stem.py | 4 +- tests/conftest.py | 175 +++++++++++++++--- tests/functional/test_ROSE_ORIG_HOST.py | 20 +- tests/functional/test_pre_configure.py | 8 +- tests/functional/test_reinstall.py | 64 ++++--- tests/functional/test_reinstall_clean.py | 25 ++- .../functional/test_reinstall_fileinstall.py | 15 +- tests/functional/test_rose_fileinstall.py | 113 ++++++----- tests/functional/test_rose_stem.py | 53 +++--- tests/functional/test_utils.py | 38 ++-- tests/unit/test_fileinstall.py | 16 +- 13 files changed, 352 insertions(+), 192 deletions(-) diff --git a/cylc/rose/entry_points.py b/cylc/rose/entry_points.py index f749cd56..5c6f1e87 100644 --- a/cylc/rose/entry_points.py +++ b/cylc/rose/entry_points.py @@ -76,7 +76,10 @@ def post_install(srcdir: Path, rundir: str, opts: 'Values') -> bool: def rose_stem(): """Implements the "rose stem" command.""" - from cylc.rose.stem import get_rose_stem_opts + import asyncio + from cylc.rose.stem import get_rose_stem_opts, rose_stem parser, opts = get_rose_stem_opts() - rose_stem(parser, opts) + asyncio.run( + rose_stem(parser, opts) + ) diff --git a/cylc/rose/fileinstall.py b/cylc/rose/fileinstall.py index c69e7ac1..963ba6c9 100644 --- a/cylc/rose/fileinstall.py +++ b/cylc/rose/fileinstall.py @@ -39,9 +39,8 @@ def rose_fileinstall( config_tree = rose_config_tree_loader(rundir, opts) if any(i.startswith('file') for i in config_tree.node.value): - startpoint = None try: - startpoint = os.getcwd() + # NOTE: Cylc will chdir back for us afterwards os.chdir(rundir) except FileNotFoundError as exc: raise exc @@ -66,8 +65,5 @@ def rose_fileinstall( # Process fileinstall. config_pm = ConfigProcessorsManager(event_handler, popen, fs_util) config_pm(config_tree, "file") - finally: - if startpoint: - os.chdir(startpoint) return config_tree.node diff --git a/cylc/rose/stem.py b/cylc/rose/stem.py index 2a556cc0..5972f4cf 100644 --- a/cylc/rose/stem.py +++ b/cylc/rose/stem.py @@ -609,13 +609,13 @@ def get_rose_stem_opts(): return parser, opts -def rose_stem(parser, opts): +async def rose_stem(parser, opts): try: # modify the CLI options to add whatever rose stem would like to add opts = StemRunner(opts).process() # call cylc install - cylc_install(opts, opts.workflow_conf_dir) + await cylc_install(opts, opts.workflow_conf_dir) except CylcError as exc: if opts.verbosity > 1: diff --git a/tests/conftest.py b/tests/conftest.py index 5905240a..731e285d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,19 +14,52 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import asyncio +from pathlib import Path +from shutil import rmtree from types import SimpleNamespace from uuid import uuid4 +import pytest + from cylc.flow import __version__ as CYLC_VERSION from cylc.flow.option_parsers import Options -from cylc.flow.pathutil import get_workflow_run_dir +from cylc.flow.pathutil import get_cylc_run_dir from cylc.flow.scripts.install import get_option_parser as install_gop from cylc.flow.scripts.install import install_cli as cylc_install from cylc.flow.scripts.reinstall import get_option_parser as reinstall_gop from cylc.flow.scripts.reinstall import reinstall_cli as cylc_reinstall -from cylc.flow.scripts.validate import _main as cylc_validate +from cylc.flow.scripts.validate import run as cylc_validate from cylc.flow.scripts.validate import get_option_parser as validate_gop -import pytest +from cylc.flow.wallclock import get_current_time_string + + +CYLC_RUN_DIR = Path(get_cylc_run_dir()) + + +@pytest.fixture(scope='module') +def event_loop(): + """This fixture defines the event loop used for each test. + + The default scoping for this fixture is "function" which means that all + async fixtures must have "function" scoping. + + Defining `event_loop` as a module scoped fixture opens the door to + module scoped fixtures but means all tests in a module will run in the same + event loop. This is fine, it's actually an efficiency win but also + something to be aware of. + + See: https://github.com/pytest-dev/pytest-asyncio/issues/171 + + """ + loop = asyncio.get_event_loop_policy().new_event_loop() + yield loop + # gracefully exit async generators + loop.run_until_complete(loop.shutdown_asyncgens()) + # cancel any tasks still running in this event loop + for task in asyncio.all_tasks(loop): + task.cancel() + loop.close() @pytest.fixture() @@ -98,15 +131,34 @@ def pytest_runtest_makereport(item, call): item.module._module_outcomes = _module_outcomes +def _rm_if_empty(path): + """Convenience wrapper for removing empty directories.""" + try: + path.rmdir() + except OSError: + return False + return True + + +def _pytest_passed(request: pytest.FixtureRequest) -> bool: + """Returns True if the test(s) a fixture was used in passed.""" + if hasattr(request.node, '_function_outcome'): + return request.node._function_outcome.outcome in {'passed', 'skipped'} + return all(( + report.outcome in {'passed', 'skipped'} + for report in request.node.obj._module_outcomes.values() + )) + + def _cylc_validate_cli(capsys, caplog): """Access the validate CLI""" - def _inner(srcpath, args=None): + async def _inner(srcpath, args=None): parser = validate_gop() options = Options(parser, args)() output = SimpleNamespace() try: - cylc_validate(parser, options, str(srcpath)) + await cylc_validate(parser, options, str(srcpath)) output.ret = 0 output.exc = '' except Exception as exc: @@ -120,24 +172,39 @@ def _inner(srcpath, args=None): return _inner -def _cylc_install_cli(capsys, caplog, workflow_name): +def _cylc_install_cli(capsys, caplog, test_dir): """Access the install CLI""" - def _inner(srcpath, args=None): + async def _inner(srcpath, workflow_name=None, opts=None): """Install a workflow. Args: srcpath: - args: Dictionary of arguments. + 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. + """ - options = Options(install_gop(), args)() + nonlocal capsys, caplog, 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) output = SimpleNamespace() if not options.workflow_name: options.workflow_name = workflow_name - if not args or not args.get('no_run_name', ''): + if not opts or not opts.get('no_run_name', ''): options.no_run_name = True try: - output.name, output.id = cylc_install(options, str(srcpath)) + output.name, output.id = await cylc_install(options, str(srcpath)) output.ret = 0 output.exc = '' except Exception as exc: @@ -145,28 +212,37 @@ def _inner(srcpath, args=None): output.exc = exc output.logging = '\n'.join([i.message for i in caplog.records]) output.out, output.err = capsys.readouterr() - output.run_dir = get_workflow_run_dir(output.id) return output return _inner -def _cylc_reinstall_cli(capsys, caplog): +def _cylc_reinstall_cli(capsys, caplog, test_dir): """Access the reinstall CLI""" - def _inner(workflow_id, opts=None): + async def _inner(workflow_id=None, opts=None): """Install a workflow. Args: - srcpath: - args: Dictionary of arguments. + workflow_id: + The workflow ID to reinstall. + + If you leave this blank, it will use the module/function's + test directory as appropriate. + args: + Dictionary of arguments for cylc reinstall. + """ - options = Options(reinstall_gop(), opts)() + nonlocal capsys, caplog, test_dir + if not workflow_id: + workflow_id = str(test_dir.relative_to(CYLC_RUN_DIR)) + options = Options(reinstall_gop(), opts or {})() output = SimpleNamespace() try: - cylc_reinstall(options, workflow_id) + await cylc_reinstall(options, workflow_id) output.ret = 0 output.exc = '' except Exception as exc: + # raise output.ret = 1 output.exc = exc output.logging = '\n'.join([i.message for i in caplog.records]) @@ -176,24 +252,23 @@ def _inner(workflow_id, opts=None): @pytest.fixture -def cylc_install_cli(capsys, caplog, workflow_name): - return _cylc_install_cli(capsys, caplog, workflow_name) +def cylc_install_cli(capsys, caplog, test_dir): + return _cylc_install_cli(capsys, caplog, test_dir) @pytest.fixture(scope='module') -def mod_cylc_install_cli(mod_capsys, mod_caplog, mod_workflow_name): - return _cylc_install_cli( - mod_capsys, mod_caplog, mod_workflow_name) +def mod_cylc_install_cli(mod_capsys, mod_caplog): + return _cylc_install_cli(mod_capsys, mod_caplog, mod_test_dir) @pytest.fixture -def cylc_reinstall_cli(capsys, caplog): - return _cylc_reinstall_cli(capsys, caplog) +def cylc_reinstall_cli(capsys, caplog, test_dir): + return _cylc_reinstall_cli(capsys, caplog, test_dir) @pytest.fixture(scope='module') -def mod_cylc_reinstall_cli(mod_capsys, mod_caplog): - return _cylc_reinstall_cli(mod_capsys, mod_caplog) +def mod_cylc_reinstall_cli(mod_capsys, mod_caplog, mod_test_dir): + return _cylc_reinstall_cli(mod_capsys, mod_caplog, mod_test_dir) @pytest.fixture @@ -204,3 +279,49 @@ 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(scope='session') +def run_dir(): + """The cylc run directory for this host.""" + CYLC_RUN_DIR.mkdir(exist_ok=True) + yield CYLC_RUN_DIR + + +@pytest.fixture(scope='session') +def ses_test_dir(request, run_dir): + """The root run dir for test flows in this test session.""" + timestamp = get_current_time_string(use_basic_format=True) + uuid = f'cylc-rose-test-{timestamp}-{str(uuid4())[:4]}' + path = Path(run_dir, uuid) + path.mkdir(exist_ok=True) + yield path + _rm_if_empty(path) + + +@pytest.fixture(scope='module') +def mod_test_dir(request, ses_test_dir): + """The root run dir for test flows in this test module.""" + path = Path(ses_test_dir, request.module.__name__) + path.mkdir(exist_ok=True) + yield path + if _pytest_passed(request): + # test passed -> remove all files + rmtree(path, ignore_errors=False) + else: + # test failed -> remove the test dir if empty + _rm_if_empty(path) + + +@pytest.fixture +def test_dir(request, mod_test_dir): + """The root run dir for test flows in this test function.""" + path = Path(mod_test_dir, request.function.__name__) + path.mkdir(parents=True, exist_ok=True) + yield path + if _pytest_passed(request): + # test passed -> remove all files + rmtree(path, ignore_errors=False) + else: + # test failed -> remove the test dir if empty + _rm_if_empty(path) diff --git a/tests/functional/test_ROSE_ORIG_HOST.py b/tests/functional/test_ROSE_ORIG_HOST.py index d97e221b..7f9af1fb 100644 --- a/tests/functional/test_ROSE_ORIG_HOST.py +++ b/tests/functional/test_ROSE_ORIG_HOST.py @@ -102,7 +102,7 @@ def fixture_provide_flow(tmp_path_factory, request): @pytest.fixture(scope='module') -def fixture_install_flow( +async def fixture_install_flow( fixture_provide_flow, monkeymodule, mod_cylc_install_cli ): """Run ``cylc install``. @@ -113,9 +113,9 @@ def fixture_install_flow( If a test fails then using ``pytest --pdb`` and ``fixture_install_flow['result'].stderr`` may help with debugging. """ - result = mod_cylc_install_cli( + result = await mod_cylc_install_cli( fixture_provide_flow['srcpath'], - {'workflow_name': fixture_provide_flow['test_flow_name']} + fixture_provide_flow['test_flow_name'], ) install_conf_path = ( fixture_provide_flow['flowpath'] / @@ -130,20 +130,26 @@ def fixture_install_flow( } -def test_cylc_validate_srcdir(fixture_install_flow, mod_cylc_validate_cli): +async def test_cylc_validate_srcdir( + fixture_install_flow, + mod_cylc_validate_cli, +): """Sanity check that workflow validates: """ srcpath = fixture_install_flow['srcpath'] - result = mod_cylc_validate_cli(srcpath) + result = await mod_cylc_validate_cli(srcpath) search = re.findall(r'ROSE_ORIG_HOST \(.*\) is: (.*)', result.logging) assert search == [HOST, HOST] -def test_cylc_validate_rundir(fixture_install_flow, mod_cylc_validate_cli): +async def test_cylc_validate_rundir( + fixture_install_flow, + mod_cylc_validate_cli, +): """Sanity check that workflow validates: """ flowpath = fixture_install_flow['flowpath'] - result = mod_cylc_validate_cli(flowpath) + result = await mod_cylc_validate_cli(flowpath) assert 'ROSE_ORIG_HOST (env) is:' in result.logging diff --git a/tests/functional/test_pre_configure.py b/tests/functional/test_pre_configure.py index 00d423b7..5b597ba3 100644 --- a/tests/functional/test_pre_configure.py +++ b/tests/functional/test_pre_configure.py @@ -45,9 +45,9 @@ ) ] ) -def test_validate_fail(srcdir, expect, cylc_validate_cli): +async def test_validate_fail(srcdir, expect, cylc_validate_cli): srcdir = Path(__file__).parent / srcdir - validate = cylc_validate_cli(srcdir) + validate = await cylc_validate_cli(srcdir) assert validate.ret == 1 if expect: assert re.findall(expect, str(validate.exc)) @@ -77,11 +77,11 @@ def test_validate_fail(srcdir, expect, cylc_validate_cli): ('09_template_vars_vanilla', {'XYZ': 'xyz'}, None), ], ) -def test_validate(monkeypatch, srcdir, envvars, args, cylc_validate_cli): +async def test_validate(monkeypatch, srcdir, envvars, args, cylc_validate_cli): for key, value in (envvars or {}).items(): monkeypatch.setenv(key, value) srcdir = Path(__file__).parent / srcdir - validate = cylc_validate_cli(str(srcdir), args) + validate = await cylc_validate_cli(str(srcdir), args) assert validate.ret == 0 diff --git a/tests/functional/test_reinstall.py b/tests/functional/test_reinstall.py index 32abc432..4ee030a1 100644 --- a/tests/functional/test_reinstall.py +++ b/tests/functional/test_reinstall.py @@ -78,7 +78,7 @@ def fixture_provide_flow(tmp_path_factory, request): @pytest.fixture(scope='module') -def fixture_install_flow( +async def fixture_install_flow( fixture_provide_flow, monkeymodule, mod_cylc_install_cli ): """Run ``cylc install``. @@ -90,11 +90,12 @@ def fixture_install_flow( ``fixture_install_flow['result'].stderr`` may help with debugging. """ monkeymodule.setenv('ROSE_SUITE_OPT_CONF_KEYS', 'b') - result = mod_cylc_install_cli( - fixture_provide_flow['srcpath'], { + result = await mod_cylc_install_cli( + fixture_provide_flow['srcpath'], + fixture_provide_flow['test_flow_name'], + { 'opt_conf_keys': ['c'], - 'workflow_name': fixture_provide_flow['test_flow_name'] - } + }, ) yield { @@ -103,11 +104,11 @@ def fixture_install_flow( } -def test_cylc_validate(fixture_provide_flow, cylc_validate_cli): +async def test_cylc_validate(fixture_provide_flow, cylc_validate_cli): """Sanity check that workflow validates: """ srcpath = fixture_provide_flow['srcpath'] - assert cylc_validate_cli(str(srcpath)).ret == 0 + assert (await cylc_validate_cli(str(srcpath))).ret == 0 def test_cylc_install_run(fixture_install_flow): @@ -140,8 +141,8 @@ def test_cylc_install_files(fixture_install_flow, file_, expect): @pytest.fixture(scope='module') -def fixture_reinstall_flow( - fixture_provide_flow, monkeymodule, mod_cylc_reinstall_cli +async def fixture_reinstall_flow( + fixture_install_flow, monkeymodule, mod_cylc_reinstall_cli ): """Run ``cylc reinstall``. @@ -152,15 +153,13 @@ def fixture_reinstall_flow( ``fixture_install_flow['result'].stderr`` may help with debugging. """ monkeymodule.delenv('ROSE_SUITE_OPT_CONF_KEYS', raising=False) - result = mod_cylc_reinstall_cli( - f'{fixture_provide_flow["test_flow_name"]}/run1', - { - 'opt_conf_keys': ['d'] - } + result = await mod_cylc_reinstall_cli( + fixture_install_flow['result'].id, + {'opt_conf_keys': ['d']}, ) yield { - 'fixture_provide_flow': fixture_provide_flow, - 'result': result + 'fixture_install_flow': fixture_install_flow, + 'result': result, } @@ -189,13 +188,18 @@ def test_cylc_reinstall_run(fixture_reinstall_flow): ] ) def test_cylc_reinstall_files(fixture_reinstall_flow, file_, expect): - fpath = fixture_reinstall_flow['fixture_provide_flow']['flowpath'] + fpath = ( + fixture_reinstall_flow + ['fixture_install_flow'] + ['fixture_provide_flow'] + ['flowpath'] + ) assert (fpath / file_).read_text() == expect @pytest.fixture(scope='module') -def fixture_reinstall_flow2( - fixture_provide_flow, monkeymodule, mod_cylc_reinstall_cli +async def fixture_reinstall_flow2( + fixture_install_flow, monkeymodule, mod_cylc_reinstall_cli ): """Run ``cylc reinstall``. @@ -210,14 +214,17 @@ def fixture_reinstall_flow2( ``fixture_install_flow['result'].stderr`` may help with debugging. """ monkeymodule.delenv('ROSE_SUITE_OPT_CONF_KEYS', raising=False) - (fixture_provide_flow['srcpath'] / 'rose-suite.conf').write_text( - 'opts=z\n' - ) - result = mod_cylc_reinstall_cli( - f'{fixture_provide_flow["test_flow_name"]}/run1' + ( + fixture_install_flow + ['fixture_provide_flow'] + ['srcpath'] + / 'rose-suite.conf' + ).write_text('opts=z\n') + result = await mod_cylc_reinstall_cli( + fixture_install_flow['result'].id, ) yield { - 'fixture_provide_flow': fixture_provide_flow, + 'fixture_install_flow': fixture_install_flow, 'result': result } @@ -247,7 +254,12 @@ def test_cylc_reinstall_run2(fixture_reinstall_flow2): ] ) def test_cylc_reinstall_files2(fixture_reinstall_flow2, file_, expect): - fpath = fixture_reinstall_flow2['fixture_provide_flow']['flowpath'] + fpath = ( + fixture_reinstall_flow2 + ['fixture_install_flow'] + ['fixture_provide_flow'] + ['flowpath'] + ) assert (fpath / file_).read_text() == expect diff --git a/tests/functional/test_reinstall_clean.py b/tests/functional/test_reinstall_clean.py index 0a207430..e75cac0a 100644 --- a/tests/functional/test_reinstall_clean.py +++ b/tests/functional/test_reinstall_clean.py @@ -76,7 +76,7 @@ def fixture_provide_flow(tmp_path_factory, request): @pytest.fixture(scope='module') -def fixture_install_flow( +async def fixture_install_flow( fixture_provide_flow, monkeymodule, mod_cylc_install_cli ): """Run ``cylc install``. @@ -87,10 +87,10 @@ def fixture_install_flow( If a test fails using ``pytest --pdb then`` ``fixture_install_flow['result'].stderr`` may help with debugging. """ - result = mod_cylc_install_cli( + result = await mod_cylc_install_cli( fixture_provide_flow['srcpath'], + fixture_provide_flow['test_flow_name'], { - 'workflow_name': fixture_provide_flow['test_flow_name'], 'opt_conf_keys': ['bar'], 'defines': ['[env]FOO=1'] } @@ -126,8 +126,8 @@ def test_cylc_install_files(fixture_install_flow, file_, expect): @pytest.fixture(scope='module') -def fixture_reinstall_flow( - fixture_provide_flow, monkeymodule, mod_cylc_reinstall_cli +async def fixture_reinstall_flow( + fixture_install_flow, monkeymodule, mod_cylc_reinstall_cli ): """Run ``cylc reinstall --clear-rose-install-options``. @@ -141,8 +141,10 @@ def fixture_reinstall_flow( ``fixture_install_flow['result'].stderr`` may help with debugging. """ monkeymodule.delenv('ROSE_SUITE_OPT_CONF_KEYS', raising=False) - result = mod_cylc_reinstall_cli( - f'{fixture_provide_flow["test_flow_name"]}/run1', + result = await mod_cylc_reinstall_cli( + ( + fixture_install_flow['fixture_provide_flow']['test_flow_name'] + ), { 'opt_conf_keys': ['baz'], 'defines': ['[env]BAR=2'], @@ -150,7 +152,7 @@ def fixture_reinstall_flow( } ) yield { - 'fixture_provide_flow': fixture_provide_flow, + 'fixture_install_flow': fixture_install_flow, 'result': result } @@ -175,5 +177,10 @@ def test_cylc_reinstall_run(fixture_reinstall_flow): ] ) def test_cylc_reinstall_files(fixture_reinstall_flow, file_, expect): - fpath = fixture_reinstall_flow['fixture_provide_flow']['flowpath'] + fpath = ( + fixture_reinstall_flow + ['fixture_install_flow'] + ['fixture_provide_flow'] + ['flowpath'] + ) assert (fpath / file_).read_text() == expect diff --git a/tests/functional/test_reinstall_fileinstall.py b/tests/functional/test_reinstall_fileinstall.py index 817500c6..b1c523f6 100644 --- a/tests/functional/test_reinstall_fileinstall.py +++ b/tests/functional/test_reinstall_fileinstall.py @@ -23,9 +23,11 @@ import shutil from uuid import uuid4 -from cylc.flow.pathutil import get_workflow_run_dir import pytest +from cylc.flow.pathutil import get_workflow_run_dir + + WORKFLOW_SRC = Path(__file__).parent / '14_reinstall_fileinstall' @@ -49,18 +51,19 @@ def fixture_provide_flow(tmp_path_factory, request): shutil.rmtree(flowpath) -def test_install_flow(fixture_provide_flow, mod_cylc_install_cli): +async def test_install_flow(fixture_provide_flow, mod_cylc_install_cli): """Run ``cylc install``. """ - result = mod_cylc_install_cli( + result = await mod_cylc_install_cli( fixture_provide_flow['srcpath'], - {'workflow_name': fixture_provide_flow['test_flow_name']}) + fixture_provide_flow['test_flow_name'], + ) assert result.ret == 0 -def test_reinstall_flow(fixture_provide_flow, mod_cylc_reinstall_cli): +async def test_reinstall_flow(fixture_provide_flow, mod_cylc_reinstall_cli): """Run ``cylc reinstall``. """ - result = mod_cylc_reinstall_cli( + result = await mod_cylc_reinstall_cli( fixture_provide_flow['test_flow_name']) assert result.ret == 0 diff --git a/tests/functional/test_rose_fileinstall.py b/tests/functional/test_rose_fileinstall.py index ac99b3da..87834d7d 100644 --- a/tests/functional/test_rose_fileinstall.py +++ b/tests/functional/test_rose_fileinstall.py @@ -17,82 +17,79 @@ """Functional tests for Rose file installation.""" from pathlib import Path -import shutil -from uuid import uuid4 +from textwrap import dedent -from cylc.flow.pathutil import get_workflow_run_dir import pytest +from cylc.flow.pathutil import get_workflow_run_dir + +# @pytest.fixture(scope='module') @pytest.fixture -def fixture_provide_flow(tmp_path): +def workflow_source_dir(tmp_path): + """A source dir with a Rose config that configures file installation.""" # Set up paths for test: srcpath = tmp_path / 'src' + srcpath.mkdir() + + # the files to install are stored in a directory alongside this test file datapath = Path(__file__).parent / 'fileinstall_data' - for path in [srcpath]: - path.mkdir() # Create a unique flow name for this test: - flow_name = f'cylc-rose-test-{str(uuid4())[:8]}' - # Create source workflow: - (srcpath / 'flow.cylc').write_text( - '[scheduling]\n' - ' initial cycle point = 2020\n' - ' [[dependencies]]\n' - ' [[[R1]]]\n' - ' graph = pointless\n' - '[runtime]\n' - ' [[pointless]]\n' - ' script = true\n' - ) - (srcpath / 'rose-suite.conf').write_text( - '[file:lib/python/lion.py]\n' - f'source={str(datapath)}/lion.py\n' - '[file:data]\n' - f'source={str(datapath)}/*.data\n' - ) - yield srcpath, datapath, flow_name - + (srcpath / 'flow.cylc').touch() + (srcpath / 'rose-suite.conf').write_text(dedent(f''' + [file:lib/python/lion.py] + source={datapath}/lion.py -@pytest.fixture -def fixture_install_flow(fixture_provide_flow, request, cylc_install_cli): - srcpath, datapath, flow_name = fixture_provide_flow - result = cylc_install_cli(str(srcpath), {'workflow_name': flow_name}) - destpath = Path(get_workflow_run_dir(flow_name)) + [file:data] + source={datapath}/*.data + ''')) + yield srcpath, datapath - yield srcpath, datapath, flow_name, result, destpath - if not request.session.testsfailed: - shutil.rmtree(destpath, ignore_errors=True) +@pytest.fixture +async def installed_workflow( + workflow_source_dir, + cylc_install_cli, +): + srcpath, datapath = workflow_source_dir + result = await cylc_install_cli(srcpath) + assert result.ret == 0 # ensure the workflow installed successfully + workflow_id = result.id + run_dir = Path(get_workflow_run_dir(workflow_id)) + yield datapath, workflow_id, result, run_dir + + +async def test_rose_fileinstall_subfolders(installed_workflow): + """It should perform file installation creating directories as needed.""" + datapath, _, _, destpath = installed_workflow + assert (destpath / 'lib/python/lion.py').read_text() == ( + (datapath / 'lion.py').read_text() + ) -def test_rose_fileinstall_validate(fixture_provide_flow, cylc_validate_cli): - """Workflow validates: - """ - srcpath, _, _ = fixture_provide_flow - assert cylc_validate_cli(str(srcpath)).ret == 0 +def test_rose_fileinstall_concatenation(installed_workflow): + """It should install multiple sources into a single file. -def test_rose_fileinstall_run(fixture_install_flow): - """Workflow installs: + Note source contains wildcard. """ - _, _, _, result, _ = fixture_install_flow - assert result.ret == 0 - + datapath, _, _, destpath = installed_workflow + assert (destpath / 'data').read_text() == ( + (datapath / 'randoms1.data').read_text() + + (datapath / 'randoms3.data').read_text() + ) -def test_rose_fileinstall_subfolders(fixture_install_flow): - """File installed into a sub directory: - """ - _, datapath, _, _, destpath = fixture_install_flow - assert ((destpath / 'lib/python/lion.py').read_text() == - (datapath / 'lion.py').read_text()) +async def test_rose_fileinstall_error(tmp_path, cylc_install_cli): + """It should capture fileinstallation errors.""" + (tmp_path / 'flow.cylc').touch() + (tmp_path / 'rose-suite.conf').write_text(dedent(''' + [file:bad] + source=no-such-file + ''')) -def test_rose_fileinstall_concatenation(fixture_install_flow): - """Multiple files concatenated on install(source contained wildcard): - """ - _, datapath, _, _, destpath = fixture_install_flow - assert ((destpath / 'data').read_text() == - ((datapath / 'randoms1.data').read_text() + - (datapath / 'randoms3.data').read_text() - )) + result = await cylc_install_cli(tmp_path) + assert ( + 'file:bad=source=no-such-file: bad or missing value' + ) in str(result.exc) diff --git a/tests/functional/test_rose_stem.py b/tests/functional/test_rose_stem.py index 85a5a5a9..0e23a732 100644 --- a/tests/functional/test_rose_stem.py +++ b/tests/functional/test_rose_stem.py @@ -252,7 +252,7 @@ def rose_stem_run_template(setup_stem_repo, pytestconfig, monkeymodule): """ verbosity = pytestconfig.getoption('verbose') - def _inner_fn(rose_stem_opts, verbosity=verbosity): + async def _inner_fn(rose_stem_opts, verbosity=verbosity): monkeymodule.setattr('sys.argv', ['stem']) monkeymodule.chdir(setup_stem_repo['workingcopy']) parser, opts = get_rose_stem_opts() @@ -261,7 +261,7 @@ def _inner_fn(rose_stem_opts, verbosity=verbosity): run_stem = SimpleNamespace() run_stem.stdout = '' try: - rose_stem(parser, opts) + await rose_stem(parser, opts) run_stem.returncode = 0 run_stem.stderr = '' except Exception as exc: @@ -281,14 +281,14 @@ def _inner_fn(rose_stem_opts, verbosity=verbosity): @pytest.fixture(scope='class') -def rose_stem_run_really_basic(rose_stem_run_template, setup_stem_repo): +async def rose_stem_run_really_basic(rose_stem_run_template, setup_stem_repo): rose_stem_opts = { 'stem_groups': [], 'stem_sources': [ str(setup_stem_repo['workingcopy']), "fcm:foo.x_tr@head" ], } - yield rose_stem_run_template(rose_stem_opts) + yield await rose_stem_run_template(rose_stem_opts) class TestReallyBasic(): @@ -299,7 +299,7 @@ def test_really_basic(self, rose_stem_run_really_basic): @pytest.fixture(scope='class') -def rose_stem_run_basic(rose_stem_run_template, setup_stem_repo): +async def rose_stem_run_basic(rose_stem_run_template, setup_stem_repo): rose_stem_opts = { 'stem_groups': ['earl_grey', 'milk,sugar', 'spoon,cup,milk'], 'stem_sources': [ @@ -307,7 +307,7 @@ def rose_stem_run_basic(rose_stem_run_template, setup_stem_repo): ], 'workflow_name': setup_stem_repo['suitename'] } - yield rose_stem_run_template(rose_stem_opts) + yield await rose_stem_run_template(rose_stem_opts) class TestBasic(): @@ -338,7 +338,7 @@ def test_basic(self, rose_stem_run_basic, expected): @pytest.fixture(scope='class') -def project_override( +async def project_override( rose_stem_run_template, setup_stem_repo ): rose_stem_opts = { @@ -348,7 +348,7 @@ def project_override( ], 'workflow_name': setup_stem_repo['suitename'] } - yield rose_stem_run_template(rose_stem_opts) + yield await rose_stem_run_template(rose_stem_opts) class TestProjectOverride(): @@ -387,7 +387,7 @@ def test_project_override(self, project_override, expected): @pytest.fixture(scope='class') -def suite_redirection( +async def suite_redirection( rose_stem_run_template, setup_stem_repo ): rose_stem_opts = { @@ -396,7 +396,7 @@ def suite_redirection( 'stem_sources': ["fcm:foo.x_tr@head"], 'workflow_name': setup_stem_repo['suitename'] } - yield rose_stem_run_template(rose_stem_opts) + yield await rose_stem_run_template(rose_stem_opts) class TestSuiteRedirection: @@ -424,7 +424,7 @@ def test_suite_redirection(self, suite_redirection, expected): @pytest.fixture(scope='class') -def subdirectory( +async def subdirectory( rose_stem_run_template, setup_stem_repo ): rose_stem_opts = { @@ -432,7 +432,7 @@ def subdirectory( 'stem_sources': [f'{setup_stem_repo["workingcopy"]}/rose-stem'], 'workflow_name': setup_stem_repo['suitename'] } - yield rose_stem_run_template(rose_stem_opts) + yield await rose_stem_run_template(rose_stem_opts) class TestSubdirectory: @@ -463,7 +463,7 @@ def test_subdirectory(self, subdirectory, expected): @pytest.fixture(scope='class') -def relative_path( +async def relative_path( rose_stem_run_template, setup_stem_repo ): rose_stem_opts = { @@ -471,7 +471,7 @@ def relative_path( 'stem_groups': ['ceylon'], 'workflow_name': setup_stem_repo['suitename'] } - yield rose_stem_run_template(rose_stem_opts) + yield await rose_stem_run_template(rose_stem_opts) class TestRelativePath: @@ -503,7 +503,7 @@ def test_relative_path(self, relative_path, expected): @pytest.fixture(scope='class') -def with_config( +async def with_config( rose_stem_run_template, setup_stem_repo, mock_global_cfg ): """test for successful execution with site/user configuration @@ -519,7 +519,7 @@ def with_config( 'cylc.rose.stem.ResourceLocator.default', '[rose-stem]\nautomatic-options = MILK=true', ) - yield rose_stem_run_template(rose_stem_opts) + yield await rose_stem_run_template(rose_stem_opts) class TestWithConfig: @@ -544,7 +544,7 @@ def test_with_config(self, with_config): @pytest.fixture(scope='class') -def with_config2( +async def with_config2( rose_stem_run_template, setup_stem_repo, mock_global_cfg ): """test for successful execution with site/user configuration @@ -559,7 +559,7 @@ def with_config2( 'cylc.rose.stem.ResourceLocator.default', '[rose-stem]\nautomatic-options = MILK=true TEA=darjeeling', ) - yield rose_stem_run_template(rose_stem_opts) + yield await rose_stem_run_template(rose_stem_opts) class TestWithConfig2: @@ -578,7 +578,12 @@ def test_with_config2(self, with_config2): assert line in with_config2['jobout_content'] -def test_incompatible_versions(setup_stem_repo, monkeymodule, caplog, capsys): +async def test_incompatible_versions( + setup_stem_repo, + monkeymodule, + caplog, + capsys, +): """It fails if trying to install an incompatible version. """ # Copy suite into working copy. @@ -607,10 +612,10 @@ def test_incompatible_versions(setup_stem_repo, monkeymodule, caplog, capsys): with pytest.raises( RoseStemVersionException, match='1 but suite is at version 0' ): - rose_stem(parser, opts) + await rose_stem(parser, opts) -def test_project_not_in_keywords(setup_stem_repo, monkeymodule, capsys): +async def test_project_not_in_keywords(setup_stem_repo, monkeymodule, capsys): """It fails if it cannot extract project name from FCM keywords. """ # Copy suite into working copy. @@ -629,12 +634,12 @@ def test_project_not_in_keywords(setup_stem_repo, monkeymodule, capsys): parser, opts = get_rose_stem_opts() [setattr(opts, key, val) for key, val in rose_stem_opts.items()] - rose_stem(parser, opts) + await rose_stem(parser, opts) assert 'ProjectNotFoundException' in capsys.readouterr().err -def test_picks_template_section(setup_stem_repo, monkeymodule, capsys): +async def test_picks_template_section(setup_stem_repo, monkeymodule, capsys): """It can cope with template variables section being either ``template variables`` or ``jinja2:suite.rc``. """ @@ -645,6 +650,6 @@ def test_picks_template_section(setup_stem_repo, monkeymodule, capsys): '[template_variables]\n' ) parser, opts = get_rose_stem_opts() - rose_stem(parser, opts) + await rose_stem(parser, opts) _, err = capsys.readouterr() assert "[jinja2:suite.rc]' is deprecated" not in err diff --git a/tests/functional/test_utils.py b/tests/functional/test_utils.py index 3642fda9..a87ec494 100644 --- a/tests/functional/test_utils.py +++ b/tests/functional/test_utils.py @@ -17,9 +17,12 @@ """Unit tests for utilities.""" from pathlib import Path +from textwrap import dedent from cylc.rose.entry_points import copy_config_file +from cylc.flow.pathutil import get_workflow_run_dir + def test_basic(tmp_path): # Create files @@ -38,7 +41,7 @@ def test_basic(tmp_path): ) -def test_global_config_environment_validate( +async def test_global_config_environment_validate( monkeypatch, tmp_path, cylc_validate_cli ): """It should reload the global config after exporting env variables. @@ -69,7 +72,7 @@ def test_global_config_environment_validate( """) # Validate the config: - output = cylc_validate_cli(tmp_path) + output = await cylc_validate_cli(tmp_path) assert output.ret == 0 # CYLC_SYMLINKS == None the first time the global.cylc @@ -77,7 +80,7 @@ def test_global_config_environment_validate( assert output.logging.split('\n')[-1] == '"Foo"' -def test_global_config_environment_validate2( +async def test_global_config_environment_validate2( monkeypatch, tmp_path, cylc_install_cli ): """It should reload the global config after exporting env variables. @@ -85,18 +88,18 @@ def test_global_config_environment_validate2( See: https://github.com/cylc/cylc-rose/issues/237 """ # Setup global config: - global_conf = ( - '#!jinja2\n' - '[install]\n' - ' [[symlink dirs]]\n' - ' [[[localhost]]]\n' - '{% set cylc_symlinks = environ.get(\'CYLC_SYMLINKS\', None) %}\n' - '{% if cylc_symlinks == "foo" %}\n' - f'log = {str(tmp_path)}/foo\n' - '{% else %}\n' - f'log = {str(tmp_path)}/bar\n' - '{% endif %}\n' - ) + global_conf = dedent(f''' + #!jinja2 + [install] + [[symlink dirs]] + [[[localhost]]] + {{% set cylc_symlinks = environ.get(\'CYLC_SYMLINKS\', None) %}} + {{% if cylc_symlinks == "foo" %}} + log = {str(tmp_path)}/foo + {{% else %}} + log = {str(tmp_path)}/bar + {{% endif %}} + ''').strip() glbl_conf_path = tmp_path / 'conf' glbl_conf_path.mkdir() (glbl_conf_path / 'global.cylc').write_text(global_conf) @@ -115,15 +118,16 @@ def test_global_config_environment_validate2( """) # Install the config: - output = cylc_install_cli(tmp_path) + output = await cylc_install_cli(tmp_path) import sys for i in output.logging.split('\n'): print(i, file=sys.stderr) assert output.ret == 0 # Assert symlink created back to test_path/foo: + run_dir = get_workflow_run_dir(output.id) expected_msg = ( - f'Symlink created: {output.run_dir}/log -> ' + f'Symlink created: {run_dir}/log -> ' f'{tmp_path}/foo/cylc-run/{output.id}/log' ) assert expected_msg in output.logging.split('\n')[0] diff --git a/tests/unit/test_fileinstall.py b/tests/unit/test_fileinstall.py index a4f70abf..4a0cb673 100644 --- a/tests/unit/test_fileinstall.py +++ b/tests/unit/test_fileinstall.py @@ -56,12 +56,15 @@ def fixture_provide_flow(tmp_path_factory): @pytest.fixture(scope='module') -def fixture_install_flow(fixture_provide_flow, request, mod_cylc_install_cli): +async def fixture_install_flow( + fixture_provide_flow, request, + mod_cylc_install_cli, +): srcpath, datapath, flow_name = fixture_provide_flow - result = mod_cylc_install_cli( + result = await mod_cylc_install_cli( srcpath, + flow_name, { - 'workflow_name': flow_name, 'no_run_name': True, 'opt_conf_keys': ['A', 'B'], 'defines': ["[env]FOO=42", "[jinja2:suite.rc]BAR=84"], @@ -75,11 +78,14 @@ def fixture_install_flow(fixture_provide_flow, request, mod_cylc_install_cli): shutil.rmtree(destpath) -def test_rose_fileinstall_validate(fixture_provide_flow, cylc_validate_cli): +async def test_rose_fileinstall_validate( + fixture_provide_flow, + cylc_validate_cli, +): """Workflow validates: """ srcpath, _, _ = fixture_provide_flow - cylc_validate_cli(srcpath) + await cylc_validate_cli(srcpath) def test_rose_fileinstall_run(fixture_install_flow): From 89f2be6b3cf04ad1b2bd02729c7919b02e2ded44 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Thu, 22 Feb 2024 16:36:46 +0000 Subject: [PATCH 3/4] setup: add missing pytest-asyncio test dependency --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.cfg b/setup.cfg index 4041cd8c..71d44d1c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -68,6 +68,8 @@ include = cylc* tests = coverage>=5.0.0 pytest + # https://github.com/pytest-dev/pytest-asyncio/issues/705 + pytest-asyncio==0.21.* pytest-cov pytest-xdist>=2 lint = From 1111aae14def65c70fb8611dfb2b72e781403f11 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Thu, 22 Feb 2024 17:06:09 +0000 Subject: [PATCH 4/4] tests/functional: fix scoping issues --- tests/functional/test_rose_stem.py | 357 ++++++++++++++--------------- 1 file changed, 173 insertions(+), 184 deletions(-) diff --git a/tests/functional/test_rose_stem.py b/tests/functional/test_rose_stem.py index 0e23a732..ba662f9c 100644 --- a/tests/functional/test_rose_stem.py +++ b/tests/functional/test_rose_stem.py @@ -65,7 +65,6 @@ """ import os from pathlib import Path -import re from shlex import split import shutil import subprocess @@ -115,7 +114,7 @@ def monkeymodule(): mpatch.undo() -@pytest.fixture(scope='class') +@pytest.fixture() def mock_global_cfg(monkeymodule): """Mock the rose ResourceLocator.default @@ -136,7 +135,7 @@ def _inner(target, conf): yield _inner -@pytest.fixture(scope='class') +@pytest.fixture() def setup_stem_repo(tmp_path_factory, monkeymodule, request): """Setup a Rose Stem Repository for the tests. @@ -173,7 +172,7 @@ def setup_stem_repo(tmp_path_factory, monkeymodule, request): """ # Set up required folders: - testname = re.findall(r'Function\s(.*?)[\[>]', str(request))[0] + testname = request.function.__name__ basetemp = tmp_path_factory.getbasetemp() / testname baseinstall = basetemp / 'baseinstall' rose_stem_dir = baseinstall / 'trunk/rose-stem' @@ -181,7 +180,7 @@ def setup_stem_repo(tmp_path_factory, monkeymodule, request): confdir = basetemp / 'conf' workingcopy = basetemp / f'cylc-rose-stem-test-{str(uuid4())[:8]}' for dir_ in [baseinstall, repo, rose_stem_dir, confdir, workingcopy]: - dir_.mkdir(parents=True) + dir_.mkdir(parents=True, exist_ok=True) # Turn repo into an svn repo: subprocess.run(['svnadmin', 'create', f'{repo}/foo']) @@ -228,7 +227,7 @@ def setup_stem_repo(tmp_path_factory, monkeymodule, request): ResourceLocator.default(reset=True) -@pytest.fixture(scope='class') +@pytest.fixture() def rose_stem_run_template(setup_stem_repo, pytestconfig, monkeymodule): """Runs rose-stem @@ -280,7 +279,7 @@ async def _inner_fn(rose_stem_opts, verbosity=verbosity): yield _inner_fn -@pytest.fixture(scope='class') +@pytest.fixture() async def rose_stem_run_really_basic(rose_stem_run_template, setup_stem_repo): rose_stem_opts = { 'stem_groups': [], @@ -291,14 +290,13 @@ async def rose_stem_run_really_basic(rose_stem_run_template, setup_stem_repo): yield await rose_stem_run_template(rose_stem_opts) -class TestReallyBasic(): - def test_really_basic(self, rose_stem_run_really_basic): - """Check that assorted variables have been exported. - """ - assert rose_stem_run_really_basic['run_stem'].returncode == 0 +def test_really_basic(rose_stem_run_really_basic): + """Check that assorted variables have been exported. + """ + assert rose_stem_run_really_basic['run_stem'].returncode == 0 -@pytest.fixture(scope='class') +@pytest.fixture() async def rose_stem_run_basic(rose_stem_run_template, setup_stem_repo): rose_stem_opts = { 'stem_groups': ['earl_grey', 'milk,sugar', 'spoon,cup,milk'], @@ -310,34 +308,33 @@ async def rose_stem_run_basic(rose_stem_run_template, setup_stem_repo): yield await rose_stem_run_template(rose_stem_opts) -class TestBasic(): - @pytest.mark.parametrize( - 'expected', - [ - "run_ok", - "RUN_NAMES=['earl_grey', 'milk', 'sugar', 'spoon', 'cup', 'milk']", - "SOURCE_FOO=\"{workingcopy} fcm:foo.x_tr@head\"", - "HOST_SOURCE_FOO=\"{hostname}:{workingcopy} fcm:foo.x_tr@head\"", - "SOURCE_FOO_BASE=\"{workingcopy}\"\n", - "SOURCE_FOO_BASE=\"{hostname}:{workingcopy}\"\n", - "SOURCE_FOO_REV=\"\"\n", - "SOURCE_FOO_MIRROR=\"fcm:foo.xm/trunk@1\"\n", - ] - ) - def test_basic(self, rose_stem_run_basic, expected): - """Check that assorted variables have been exported. - """ - if expected == 'run_ok': - assert rose_stem_run_basic['run_stem'].returncode == 0 - else: - expected = expected.format( - workingcopy=rose_stem_run_basic['workingcopy'], - hostname=HOST - ) - assert expected in rose_stem_run_basic['jobout_content'] +@pytest.mark.parametrize( + 'expected', + [ + "run_ok", + "RUN_NAMES=['earl_grey', 'milk', 'sugar', 'spoon', 'cup', 'milk']", + "SOURCE_FOO=\"{workingcopy} fcm:foo.x_tr@head\"", + "HOST_SOURCE_FOO=\"{hostname}:{workingcopy} fcm:foo.x_tr@head\"", + "SOURCE_FOO_BASE=\"{workingcopy}\"\n", + "SOURCE_FOO_BASE=\"{hostname}:{workingcopy}\"\n", + "SOURCE_FOO_REV=\"\"\n", + "SOURCE_FOO_MIRROR=\"fcm:foo.xm/trunk@1\"\n", + ] +) +def test_basic(rose_stem_run_basic, expected): + """Check that assorted variables have been exported. + """ + if expected == 'run_ok': + assert rose_stem_run_basic['run_stem'].returncode == 0 + else: + expected = expected.format( + workingcopy=rose_stem_run_basic['workingcopy'], + hostname=HOST + ) + assert expected in rose_stem_run_basic['jobout_content'] -@pytest.fixture(scope='class') +@pytest.fixture() async def project_override( rose_stem_run_template, setup_stem_repo ): @@ -351,42 +348,41 @@ async def project_override( yield await rose_stem_run_template(rose_stem_opts) -class TestProjectOverride(): - @pytest.mark.parametrize( - 'expected', - [ - "run_ok", - ( - "RUN_NAMES=[\'earl_grey\', \'milk\', \'sugar\', " - "\'spoon\', \'cup\', \'milk\']" - ), - "SOURCE_FOO=\"fcm:foo.x_tr@head\"", - "HOST_SOURCE_FOO=\"fcm:foo.x_tr@head\"", - "SOURCE_BAR=\"{workingcopy}\"", - "HOST_SOURCE_BAR=\"{hostname}:{workingcopy}\"", - "SOURCE_FOO_BASE=\"fcm:foo.x_tr\"", - "HOST_SOURCE_FOO_BASE=\"fcm:foo.x_tr\"", - "SOURCE_BAR_BASE=\"{workingcopy}\"", - "HOST_SOURCE_BAR_BASE=\"{hostname}:{workingcopy}\"", - "SOURCE_FOO_REV=\"@1\"", - "SOURCE_BAR_REV=\"\"", - "SOURCE_FOO_MIRROR=\"fcm:foo.xm/trunk@1\"", - ] - ) - def test_project_override(self, project_override, expected): - """Check that assorted variables have been exported. - """ - if expected == 'run_ok': - assert project_override['run_stem'].returncode == 0 - else: - expected = expected.format( - workingcopy=project_override['workingcopy'], - hostname=HOST - ) - assert expected in project_override['jobout_content'] +@pytest.mark.parametrize( + 'expected', + [ + "run_ok", + ( + "RUN_NAMES=[\'earl_grey\', \'milk\', \'sugar\', " + "\'spoon\', \'cup\', \'milk\']" + ), + "SOURCE_FOO=\"fcm:foo.x_tr@head\"", + "HOST_SOURCE_FOO=\"fcm:foo.x_tr@head\"", + "SOURCE_BAR=\"{workingcopy}\"", + "HOST_SOURCE_BAR=\"{hostname}:{workingcopy}\"", + "SOURCE_FOO_BASE=\"fcm:foo.x_tr\"", + "HOST_SOURCE_FOO_BASE=\"fcm:foo.x_tr\"", + "SOURCE_BAR_BASE=\"{workingcopy}\"", + "HOST_SOURCE_BAR_BASE=\"{hostname}:{workingcopy}\"", + "SOURCE_FOO_REV=\"@1\"", + "SOURCE_BAR_REV=\"\"", + "SOURCE_FOO_MIRROR=\"fcm:foo.xm/trunk@1\"", + ] +) +def test_project_override(project_override, expected): + """Check that assorted variables have been exported. + """ + if expected == 'run_ok': + assert project_override['run_stem'].returncode == 0 + else: + expected = expected.format( + workingcopy=project_override['workingcopy'], + hostname=HOST + ) + assert expected in project_override['jobout_content'] -@pytest.fixture(scope='class') +@pytest.fixture() async def suite_redirection( rose_stem_run_template, setup_stem_repo ): @@ -399,31 +395,30 @@ async def suite_redirection( yield await rose_stem_run_template(rose_stem_opts) -class TestSuiteRedirection: - @pytest.mark.parametrize( - 'expected', - [ - "run_ok", - "RUN_NAMES=[\'lapsang\']", - "SOURCE_FOO=\"fcm:foo.x_tr@head\"", - "SOURCE_FOO_BASE=\"fcm:foo.x_tr\"", - "SOURCE_FOO_REV=\"@1\"", - ] - ) - def test_suite_redirection(self, suite_redirection, expected): - """Check that assorted variables have been exported. - """ - if expected == 'run_ok': - assert suite_redirection['run_stem'].returncode == 0 - else: - expected = expected.format( - workingcopy=suite_redirection['workingcopy'], - hostname=HOST - ) - assert expected in suite_redirection['jobout_content'] +@pytest.mark.parametrize( + 'expected', + [ + "run_ok", + "RUN_NAMES=[\'lapsang\']", + "SOURCE_FOO=\"fcm:foo.x_tr@head\"", + "SOURCE_FOO_BASE=\"fcm:foo.x_tr\"", + "SOURCE_FOO_REV=\"@1\"", + ] +) +def test_suite_redirection(suite_redirection, expected): + """Check that assorted variables have been exported. + """ + if expected == 'run_ok': + assert suite_redirection['run_stem'].returncode == 0 + else: + expected = expected.format( + workingcopy=suite_redirection['workingcopy'], + hostname=HOST + ) + assert expected in suite_redirection['jobout_content'] -@pytest.fixture(scope='class') +@pytest.fixture() async def subdirectory( rose_stem_run_template, setup_stem_repo ): @@ -435,34 +430,33 @@ async def subdirectory( yield await rose_stem_run_template(rose_stem_opts) -class TestSubdirectory: - @pytest.mark.parametrize( - 'expected', - [ - "run_ok", - "RUN_NAMES=[\'assam\']", - "SOURCE_FOO=\"{workingcopy}\"", - "HOST_SOURCE_FOO=\"{hostname}:{workingcopy}\"", - "SOURCE_FOO_BASE=\"{workingcopy}\"", - "HOST_SOURCE_FOO_BASE=\"{hostname}:{workingcopy}\"", - "SOURCE_FOO_REV=\"\"", - "SOURCE_FOO_MIRROR=\"fcm:foo.xm/trunk@1\"", - ] - ) - def test_subdirectory(self, subdirectory, expected): - """Check that assorted variables have been exported. - """ - if expected == 'run_ok': - assert subdirectory['run_stem'].returncode == 0 - else: - expected = expected.format( - workingcopy=subdirectory['workingcopy'], - hostname=HOST - ) - assert expected in subdirectory['jobout_content'] +@pytest.mark.parametrize( + 'expected', + [ + "run_ok", + "RUN_NAMES=[\'assam\']", + "SOURCE_FOO=\"{workingcopy}\"", + "HOST_SOURCE_FOO=\"{hostname}:{workingcopy}\"", + "SOURCE_FOO_BASE=\"{workingcopy}\"", + "HOST_SOURCE_FOO_BASE=\"{hostname}:{workingcopy}\"", + "SOURCE_FOO_REV=\"\"", + "SOURCE_FOO_MIRROR=\"fcm:foo.xm/trunk@1\"", + ] +) +def test_subdirectory(subdirectory, expected): + """Check that assorted variables have been exported. + """ + if expected == 'run_ok': + assert subdirectory['run_stem'].returncode == 0 + else: + expected = expected.format( + workingcopy=subdirectory['workingcopy'], + hostname=HOST + ) + assert expected in subdirectory['jobout_content'] -@pytest.fixture(scope='class') +@pytest.fixture() async def relative_path( rose_stem_run_template, setup_stem_repo ): @@ -474,35 +468,32 @@ async def relative_path( yield await rose_stem_run_template(rose_stem_opts) -class TestRelativePath: - """Check relative path with src is working. +@pytest.mark.parametrize( + 'expected', + [ + "run_ok", + "RUN_NAMES=[\'ceylon\']", + "SOURCE_FOO=\"{workingcopy}\"", + "HOST_SOURCE_FOO=\"{hostname}:{workingcopy}\"", + "SOURCE_FOO_BASE=\"{workingcopy}\"", + "HOST_SOURCE_FOO_BASE=\"{hostname}:{workingcopy}\"", + "SOURCE_FOO_REV=\"\"", + ] +) +def test_relative_path(relative_path, expected): + """Check that assorted variables have been exported. """ - @pytest.mark.parametrize( - 'expected', - [ - "run_ok", - "RUN_NAMES=[\'ceylon\']", - "SOURCE_FOO=\"{workingcopy}\"", - "HOST_SOURCE_FOO=\"{hostname}:{workingcopy}\"", - "SOURCE_FOO_BASE=\"{workingcopy}\"", - "HOST_SOURCE_FOO_BASE=\"{hostname}:{workingcopy}\"", - "SOURCE_FOO_REV=\"\"", - ] - ) - def test_relative_path(self, relative_path, expected): - """Check that assorted variables have been exported. - """ - if expected == 'run_ok': - assert relative_path['run_stem'].returncode == 0 - else: - expected = expected.format( - workingcopy=relative_path['workingcopy'], - hostname=HOST - ) - assert expected in relative_path['jobout_content'] + if expected == 'run_ok': + assert relative_path['run_stem'].returncode == 0 + else: + expected = expected.format( + workingcopy=relative_path['workingcopy'], + hostname=HOST + ) + assert expected in relative_path['jobout_content'] -@pytest.fixture(scope='class') +@pytest.fixture() async def with_config( rose_stem_run_template, setup_stem_repo, mock_global_cfg ): @@ -522,28 +513,27 @@ async def with_config( yield await rose_stem_run_template(rose_stem_opts) -class TestWithConfig: - def test_with_config(self, with_config): - """test for successful execution with site/user configuration - """ - assert with_config['run_stem'].returncode == 0 - for line in [ - "RUN_NAMES=['earl_grey', 'milk', 'sugar', 'spoon', 'cup', 'milk']", - 'SOURCE_FOO="{workingcopy} fcm:foo.x_tr@head"', - 'HOST_SOURCE_FOO="{hostname}:{workingcopy} fcm:foo.x_tr@head"', - 'SOURCE_FOO_BASE="{workingcopy}"', - 'HOST_SOURCE_FOO_BASE="{hostname}:{workingcopy}"', - 'SOURCE_FOO_REV=""', - 'MILK="true"', - ]: - line = line.format( - **with_config, - hostname=HOST - ) - assert line in with_config['jobout_content'] - - -@pytest.fixture(scope='class') +def test_with_config(with_config): + """test for successful execution with site/user configuration + """ + assert with_config['run_stem'].returncode == 0 + for line in [ + "RUN_NAMES=['earl_grey', 'milk', 'sugar', 'spoon', 'cup', 'milk']", + 'SOURCE_FOO="{workingcopy} fcm:foo.x_tr@head"', + 'HOST_SOURCE_FOO="{hostname}:{workingcopy} fcm:foo.x_tr@head"', + 'SOURCE_FOO_BASE="{workingcopy}"', + 'HOST_SOURCE_FOO_BASE="{hostname}:{workingcopy}"', + 'SOURCE_FOO_REV=""', + 'MILK="true"', + ]: + line = line.format( + **with_config, + hostname=HOST + ) + assert line in with_config['jobout_content'] + + +@pytest.fixture() async def with_config2( rose_stem_run_template, setup_stem_repo, mock_global_cfg ): @@ -562,20 +552,19 @@ async def with_config2( yield await rose_stem_run_template(rose_stem_opts) -class TestWithConfig2: - def test_with_config2(self, with_config2): - """test for successful execution with site/user configuration - """ - assert with_config2['run_stem'].returncode == 0 - for line in [ - 'MILK="true"', - 'TEA="darjeeling"', - ]: - line = line.format( - **with_config2, - hostname=HOST - ) - assert line in with_config2['jobout_content'] +def test_with_config2(with_config2): + """test for successful execution with site/user configuration + """ + assert with_config2['run_stem'].returncode == 0 + for line in [ + 'MILK="true"', + 'TEA="darjeeling"', + ]: + line = line.format( + **with_config2, + hostname=HOST + ) + assert line in with_config2['jobout_content'] async def test_incompatible_versions(