Skip to content

Commit

Permalink
Merge pull request #181 from wxtim/20220913T1539--improve_rose_stem_c…
Browse files Browse the repository at this point in the history
…overage

Improve rose stem coverage
  • Loading branch information
oliver-sanders authored Oct 20, 2022
2 parents 7464430 + d10a387 commit 1012cf0
Show file tree
Hide file tree
Showing 3 changed files with 277 additions and 16 deletions.
36 changes: 21 additions & 15 deletions cylc/rose/stem.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,9 @@ class RoseStemVersionException(Exception):
def __init__(self, version):
Exception.__init__(self, version)
if version is None:
self.suite_version = "not rose-stem compatible"
self.suite_version = (
"does not have ROSE_VERSION set in the rose-suite.conf"
)
else:
self.suite_version = "at version %s" % (version)

Expand Down Expand Up @@ -225,32 +227,40 @@ class StemRunner:

def __init__(self, opts, reporter=None, popen=None, fs_util=None):
self.opts = opts

if reporter is None:
self.reporter = Reporter(opts.verbosity - opts.quietness)
else:
self.reporter = reporter

if popen is None:
self.popen = RosePopener(event_handler=self.reporter)
else:
self.popen = popen

if fs_util is None:
self.fs_util = FileSystemUtil(event_handler=self.reporter)
else:
self.fs_util = fs_util

self.host_selector = HostSelector(event_handler=self.reporter,
popen=self.popen)

def _add_define_option(self, var, val):
"""Add a define option passed to the SuiteRunner."""
"""Add a define option passed to the SuiteRunner.
Args:
var: Name of variable to set
val: Value of variable to set
"""
if self.opts.defines:
self.opts.defines.append(SUITE_RC_PREFIX + var + '=' + val)
else:
self.opts.defines = [SUITE_RC_PREFIX + var + '=' + val]
self.reporter(ConfigVariableSetEvent(var, val))
return

def _get_base_dir(self, item):
def _get_fcm_loc_layout_info(self, src_tree):
"""Given a source tree return the following from 'fcm loc-layout':
* url
* sub_tree
Expand All @@ -259,15 +269,17 @@ def _get_base_dir(self, item):
* project
"""

ret_code, output, stderr = self.popen.run('fcm', 'loc-layout', item)
ret_code, output, stderr = self.popen.run(
'fcm', 'loc-layout', src_tree)
if ret_code != 0:
raise ProjectNotFoundException(item, stderr)
raise ProjectNotFoundException(src_tree, stderr)

ret = {}
for line in output.splitlines():
if ":" not in line:
continue
key, value = line.split(":", 1)

if key and value:
ret[key] = value.strip()

Expand All @@ -289,7 +301,8 @@ def _get_project_from_url(self, source_dict):
break
return project

def _deduce_mirror(self, source_dict, project):
@staticmethod
def _deduce_mirror(source_dict, project):
"""Deduce the mirror location of this source tree."""

# Root location for project
Expand Down Expand Up @@ -331,7 +344,7 @@ def _ascertain_project(self, item):
print(f"[WARN] Forcing project for '{item}' to be '{project}'")
return project, item, item, '', ''

source_dict = self._get_base_dir(item)
source_dict = self._get_fcm_loc_layout_info(item)
project = self._get_project_from_url(source_dict)
if not project:
raise ProjectNotFoundException(item)
Expand Down Expand Up @@ -587,10 +600,7 @@ def rose_stem(parser, opts):
opts = StemRunner(opts).process()

# call cylc install
if hasattr(opts, 'source'):
cylc_install(parser, opts, opts.source)
else:
cylc_install(parser, opts)
cylc_install(parser, opts, opts.source)

except CylcError as exc:
if opts.verbosity > 1:
Expand All @@ -602,7 +612,3 @@ def rose_stem(parser, opts):
),
file=sys.stderr
)


if __name__ == "__main__":
main()
18 changes: 18 additions & 0 deletions tests/functional/test_rose_stem.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,24 @@ def _inner_fn(rose_stem_opts, verbosity=verbosity):
yield _inner_fn


@pytest.fixture(scope='class')
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)


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


@pytest.fixture(scope='class')
def rose_stem_run_basic(rose_stem_run_template, setup_stem_repo):
rose_stem_opts = {
Expand Down
239 changes: 238 additions & 1 deletion tests/unit/test_rose_stem_units.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,30 @@
"""Functional tests for top-level function record_cylc_install_options and
"""

import cylc
import pytest
from pytest import param
from types import SimpleNamespace

from cylc.rose.stem import get_source_opt_from_args
from cylc.rose.stem import (
ProjectNotFoundException,
RoseStemVersionException,
RoseSuiteConfNotFoundException,
StemRunner, SUITE_RC_PREFIX,
get_source_opt_from_args
)

from metomi.rose.reporter import Reporter
from metomi.rose.popen import RosePopener
from metomi.rose.fs_util import FileSystemUtil


class MockPopen:
def __init__(self, mocked_return):
self.mocked_return = mocked_return

def run(self, *args):
return self.mocked_return


@pytest.mark.parametrize(
Expand Down Expand Up @@ -54,3 +74,220 @@ def test_get_source_opt_from_args(tmp_path, monkeypatch, args, expect):
assert result == expect
else:
assert result == expect.format(tmp_path=str(tmp_path))


@pytest.fixture
def get_StemRunner():
def _inner(kwargs, options=None):
if options is None:
options = {}
"""Create a StemRunner objects with some options set."""
opts = SimpleNamespace(verbosity=1, quietness=1, **options)
stemrunner = StemRunner(opts, **kwargs)
return stemrunner
return _inner


def test_StemRunner_init_kwargs_set(get_StemRunner):
"""It handles __init__ with different kwargs."""
stemrunner = get_StemRunner({
'reporter': 'foo', 'popen': 'foo', 'fs_util': 'foo'
})
assert isinstance(stemrunner.reporter, str)
assert isinstance(stemrunner.popen, str)
assert isinstance(stemrunner.popen, str)


def test_StemRunner_init_defaults(get_StemRunner):
"""It handles __init__ with different kwargs."""
stemrunner = get_StemRunner({})
assert isinstance(stemrunner.reporter, Reporter)
assert isinstance(stemrunner.popen, RosePopener)
assert isinstance(stemrunner.fs_util, FileSystemUtil)


@pytest.mark.parametrize(
'exisiting_defines',
[
param([], id='no existing defines'),
param(['opts=(cylc-install)'], id='existing defines')
]
)
def test__add_define_option(get_StemRunner, capsys, exisiting_defines):
"""It adds to defines, rather than replacing any."""
stemrunner = get_StemRunner(
{'reporter': print}, {'defines': exisiting_defines})
assert stemrunner._add_define_option('FOO', '"bar"') is None
assert f'{SUITE_RC_PREFIX}FOO="bar"' in stemrunner.opts.defines
assert 'Variable FOO set to "bar"' in capsys.readouterr().out


@pytest.mark.parametrize(
'mocked_return',
[
param((1, 'foo', 'SomeError'), id='it fails if fcm-loc-layout fails'),
param(
(
0,
'url: file:///worthwhile/foo/bar/baz/trunk@1\n'
'project: \n'
'some waffle which ought to be ignored',
''
),
id='Good fcm output'
)
]
)
def test__get_fcm_loc_layout_info(get_StemRunner, capsys, mocked_return):
"""It parses information from fcm loc layout"""

stemrunner = get_StemRunner({'popen': MockPopen(mocked_return)})

if mocked_return[0] == 0:
expect = {
'url': 'file:///worthwhile/foo/bar/baz/trunk@1',
'project': ''
}
assert expect == stemrunner._get_fcm_loc_layout_info('foo')
else:
with pytest.raises(ProjectNotFoundException) as exc:
stemrunner._get_fcm_loc_layout_info('foo')
assert mocked_return[2] in str(exc.value)


@pytest.mark.parametrize(
'source_dict, mockreturn, expect',
[
param(
{
'root': 'svn://subversive',
'project': 'waltheof',
'url': 'Irrelevent, it\'s mocked away, but required.'
},
(
0,
(
"location{primary}[mortimer] = "
"svn://subversive/rogermortimer\n"
"location{primary}[fenwick] = "
"svn://subversive/johnfenwick\n"
"location{primary}[waltheof] = "
"svn://subversive/waltheof\n"
),
),
'waltheof',
id='all paths true'
),
param(
{
'root': 'svn://subversive',
'project': 'waltheof',
'url': 'Irrelevent, it\'s mocked away, but required.'
},
(0, "location{primary} = svn://subversive/waltheof\n"),
None,
id='no kp result'
)
]
)
def test__get_project_from_url(
get_StemRunner, source_dict, mockreturn, expect
):
stemrunner = get_StemRunner({'popen': MockPopen(mockreturn)})
project = stemrunner._get_project_from_url(source_dict)
assert project == expect


@pytest.mark.parametrize(
'source, expect',
(
(None, 'cwd'),
('foo/bar', 'some_dir'),
)
)
def test__generate_name(get_StemRunner, monkeypatch, tmp_path, source, expect):
"""It generates a name if StemRunner._ascertain_project fails.
(This happens if the workflow source is not controlled with FCM)
"""
monkeypatch.chdir(tmp_path)

# Case: we've set source:
source = (tmp_path / source / expect) if expect == 'some_dir' else None
# Case: we've not set source:
expect = tmp_path.name if expect == 'cwd' else expect

stemrunner = get_StemRunner({}, {'source': source})
assert stemrunner._generate_name() == expect


@pytest.mark.parametrize(
'stem_sources, expect',
(
('given', True),
('given', False),
('infer', True),
('infer', False),
)
)
def test__this_suite(
get_StemRunner, monkeypatch, tmp_path, stem_sources, expect
):
"""It returns a sensible suite-dir."""
stem_suite_subdir = tmp_path / 'rose-stem'
stem_suite_subdir.mkdir()

if stem_sources == 'infer':
stem_sources = []
monkeypatch.setattr(
cylc.rose.stem.StemRunner,
'_ascertain_project',
lambda x, y: [0, str(tmp_path)]
)
else:
stem_sources = [tmp_path]

if expect:
(stem_suite_subdir / 'rose-suite.conf').write_text(
'ROSE_STEM_VERSION=1')
stemrunner = get_StemRunner({}, {'stem_sources': stem_sources})
assert stemrunner._this_suite() == str(stem_suite_subdir)
else:
stemrunner = get_StemRunner({}, {'stem_sources': stem_sources})
with pytest.raises(RoseSuiteConfNotFoundException):
stemrunner._this_suite()


def test__check_suite_version_fails_if_no_stem_source(
get_StemRunner, tmp_path
):
"""It fails if path of first stem source is not a file"""
stemrunner = get_StemRunner(
{}, {'stem_sources': str(tmp_path), 'source': None})
stem_suite_subdir = tmp_path / 'rose-stem'
stem_suite_subdir.mkdir()
with pytest.raises(RoseSuiteConfNotFoundException, match='^\nCannot'):
stemrunner._check_suite_version(str(tmp_path))


def test__check_suite_version_incompatible(get_StemRunner, tmp_path):
"""It fails if path of first stem source is not a file"""
(tmp_path / 'rose-suite.conf').write_text('')
stemrunner = get_StemRunner(
{}, {'stem_sources': [], 'source': str(tmp_path)})
with pytest.raises(
RoseStemVersionException, match='ROSE_VERSION'
):
stemrunner._check_suite_version(str(tmp_path / 'rose-suite.conf'))


def test__deduce_mirror():
source_dict = {
'root': 'svn://lab/spaniel.xm',
'project': 'myproject.xm',
'url': 'svn://lab/spaniel.xm/myproject/trunk@123',
'sub_tree': 'foo'
}
project = 'someproject'
StemRunner._deduce_mirror(source_dict, project)

0 comments on commit 1012cf0

Please sign in to comment.