Skip to content

Commit

Permalink
Add integration test.
Browse files Browse the repository at this point in the history
  • Loading branch information
hjoliver committed Oct 12, 2022
1 parent 65c3cd1 commit 8456c2c
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 20 deletions.
75 changes: 63 additions & 12 deletions cylc/flow/scripts/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,16 @@
from cylc.flow.scripts.scan import (
get_pipe,
_format_plain,
FLOW_STATE_SYMBOLS,
FLOW_STATE_CMAP
)
from cylc.flow import iter_entry_points
from cylc.flow.exceptions import PluginError, InputError
from cylc.flow.loggingutil import CylcLogFormatter
from cylc.flow.option_parsers import CylcOptionParser as COP
from cylc.flow.option_parsers import (
CylcOptionParser as COP,
Options
)
from cylc.flow.pathutil import EXPLICIT_RELATIVE_PATH_REGEX, expand_path
from cylc.flow.workflow_files import (
install_workflow, search_install_source_dirs, parse_cli_sym_dirs
Expand Down Expand Up @@ -155,6 +160,16 @@ def get_option_parser() -> COP:
default=False,
dest="no_run_name")

parser.add_option(
"--no-ping",
help=(
"When scanning for active instances of the workflow, "
"do not attempt to contact the schedulers to get status."
),
action="store_true",
default=False,
dest="no_ping")

parser.add_cylc_rose_options()

return parser
Expand All @@ -167,7 +182,7 @@ def get_source_location(path: Optional[str]) -> Path:
"""
if path is None:
return Path.cwd()
path = path.strip()
path = str(path).strip()
expanded_path = Path(expand_path(path))
if expanded_path.is_absolute():
return expanded_path
Expand All @@ -176,39 +191,75 @@ def get_source_location(path: Optional[str]) -> Path:
return search_install_source_dirs(expanded_path)


async def scan(wf_name: str) -> None:
async def scan(wf_name: str, ping: bool = True) -> None:
"""Print any instances of wf_name that are already active."""
opts = Values({
'name': [f'{wf_name}/*'],
'states': {'running', 'paused', 'stopping'},
'source': False,
'ping': False,
'ping': ping, # get status of scanned workflows
})
active = [
item async for item in get_pipe(opts, None, scan_dir=None)
]
if active:
n = len(active)
grammar = (
["s", "are", "them"]
if n > 1 else
["", "is", "it"]
)
print(
CylcLogFormatter.COLORS['WARNING'].format(
f'Instance(s) of "{wf_name}" are already active:'
f'WARNING: {n} run%s of "{wf_name}"'
' %s already active:' % tuple(grammar[:2])
)
)
for item in active:
cprint(
_format_plain(item, opts)
)
if opts.ping:
status = item['status']
tag = FLOW_STATE_CMAP[status]
symbol = f" <{tag}>{FLOW_STATE_SYMBOLS[status]}</{tag}>"
else:
symbol = " "
cprint(symbol, _format_plain(item, opts))
pattern = (
f"'{wf_name}/*'"
if n > 1 else
f"{item['name']}"
)
print(
f'You can stop %s with "cylc stop [options] {pattern}".\n'
'See "cylc stop --help" for options.' % grammar[-1]
)


InstallOptions = Options(get_option_parser())


@cli_function(get_option_parser)
def main(parser, opts, reg=None):
wf_name = install(parser, opts, reg)
def main(
_parser: COP,
opts: 'Values',
reg: Optional[str] = None
) -> None:
"""CLI wrapper."""
install_cli(opts, reg)


def install_cli(
opts: 'Values',
reg: Optional[str] = None
) -> None:
"""Install workflow and scan for already-running instances."""
wf_name = install(opts, reg)
asyncio.run(
scan(wf_name)
scan(wf_name, not opts.no_ping)
)


def install(
parser: COP, opts: 'Values', reg: Optional[str] = None
opts: 'Values', reg: Optional[str] = None
) -> str:
if opts.no_run_name and opts.run_name:
raise InputError(
Expand Down
88 changes: 88 additions & 0 deletions tests/integration/test_install.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# THIS FILE IS PART OF 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/>.

"""Test cylc install."""

from pathlib import Path
from shutil import rmtree
from tempfile import TemporaryDirectory

import pytest

from .test_scan import init_flows

from cylc.flow.workflow_files import WorkflowFiles
from cylc.flow.scripts.install import (
InstallOptions,
install_cli
)


SRV_DIR = Path(WorkflowFiles.Service.DIRNAME)
CONTACT = Path(WorkflowFiles.Service.CONTACT)
RUN_N = Path(WorkflowFiles.RUN_N)
INSTALL = Path(WorkflowFiles.Install.DIRNAME)


@pytest.fixture()
def src_run_dirs(mock_glbl_cfg, monkeypatch):
"""Create some workflow source and run dirs for testing.
Source dirs:
<tmp-src>/w1
<tmp-src>/w2
Run dir:
<tmp-run>/w1/run1
"""
tmp_src_path = Path(TemporaryDirectory().name)
tmp_run_path = Path(TemporaryDirectory().name)
tmp_src_path.mkdir()
tmp_run_path.mkdir()

init_flows(
tmp_run_path=tmp_run_path,
running=('w1/run1',),
tmp_src_path=tmp_src_path,
src=('w1', 'w2')
)
mock_glbl_cfg(
'cylc.flow.workflow_files.glbl_cfg',
f'''
[install]
source dirs = {tmp_src_path}
'''
)
monkeypatch.setattr('cylc.flow.pathutil._CYLC_RUN_DIR', tmp_run_path)

yield tmp_src_path, tmp_run_path
rmtree(tmp_src_path)
rmtree(tmp_run_path)


def test_install_scan(src_run_dirs, capsys):
"""At install, any running intances should be reported."""

opts = InstallOptions()
# Don't ping the scheduler: it's not really running here.
opts.no_ping = True

install_cli(opts, reg='w1')
assert '1 run of "w1" is already active:' in capsys.readouterr().out

install_cli(opts, reg='w2')
assert '1 run of "w2" is already active:' not in capsys.readouterr().out
26 changes: 18 additions & 8 deletions tests/integration/test_scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,20 @@
INSTALL = Path(WorkflowFiles.Install.DIRNAME)


def init_flows(tmp_path, running=None, registered=None, un_registered=None):
def init_flows(tmp_run_path=None, running=None, registered=None,
un_registered=None, tmp_src_path=None, src=None):
"""Create some dummy workflows for scan to discover.
Assume "run1, run2, ..., runN" structure if flow name constains "run".
Optionally create workflow source dirs in a give location too.
"""
def make_registered(name, running=False):
run_d = Path(tmp_path, name)
run_d = Path(tmp_run_path, name)
run_d.mkdir(parents=True, exist_ok=True)
(run_d / "flow.cylc").touch()
if "run" in name:
root = Path(tmp_path, name).parent
root = Path(tmp_run_path, name).parent
with suppress(FileExistsError):
(root / "runN").symlink_to(run_d, target_is_directory=True)
else:
Expand All @@ -63,12 +66,19 @@ def make_registered(name, running=False):
if running:
(srv_d / CONTACT).touch()

def make_src(name):
src_d = Path(tmp_src_path, name)
src_d.mkdir(parents=True, exist_ok=True)
(src_d / "flow.cylc").touch()

for name in (running or []):
make_registered(name, running=True)
for name in (registered or []):
make_registered(name)
for name in (un_registered or []):
Path(tmp_path, name).mkdir(parents=True, exist_ok=True)
Path(tmp_run_path, name).mkdir(parents=True, exist_ok=True)
for name in (src or []):
make_src(name)


@pytest.fixture(scope='session')
Expand Down Expand Up @@ -157,14 +167,14 @@ def source_dirs(mock_glbl_cfg):
src1 = src / '1'
src1.mkdir()
init_flows(
src1,
registered=('a', 'b/c')
tmp_src_path=src1,
src=('a', 'b/c')
)
src2 = src / '2'
src2.mkdir()
init_flows(
src2,
registered=('d', 'e/f')
tmp_src_path=src2,
src=('d', 'e/f')
)
mock_glbl_cfg(
'cylc.flow.scripts.scan.glbl_cfg',
Expand Down

0 comments on commit 8456c2c

Please sign in to comment.