Skip to content

Commit 67a6197

Browse files
graph: don't open a blank image for an empty graph
* Write a message to stderr saying there are no tasks to display rather than just opening a blank image. * Closes #6841
1 parent bcb190a commit 67a6197

File tree

4 files changed

+65
-8
lines changed

4 files changed

+65
-8
lines changed

.github/workflows/test_fast.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ jobs:
5454
bash
5555
subversion
5656
sqlite
57+
graphviz
5758
5859
- name: Install Python Dependencies
5960
run: |

cylc/flow/scripts/graph.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
from tempfile import NamedTemporaryFile
4545
from typing import Dict, List, Optional, TYPE_CHECKING, Tuple, Callable
4646

47+
from ansimarkup import ansiprint as cprint
48+
4749
from cylc.flow.config import WorkflowConfig
4850
from cylc.flow.exceptions import InputError, CylcError
4951
from cylc.flow.id import Tokens
@@ -323,7 +325,6 @@ def render_dot(dot_lines, filename, fmt):
323325

324326
def open_image(filename):
325327
"""Open an image file."""
326-
print(f'Graph rendered to {filename}')
327328
try:
328329
from PIL import Image
329330
except ModuleNotFoundError:
@@ -352,6 +353,13 @@ def graph_render(opts, workflow_id, start, stop, flow_file) -> int:
352353
flow_file
353354
)
354355

356+
if not nodes:
357+
msg = 'No tasks to display.'
358+
if start or stop:
359+
msg += ' Try changing the start and stop values.'
360+
cprint(f'<red>{msg}</red>', file=sys.stderr)
361+
return 1
362+
355363
# format the graph in graphviz-dot format
356364
dot_lines = format_graphviz(opts, nodes, edges)
357365

@@ -361,7 +369,11 @@ def graph_render(opts, workflow_id, start, stop, flow_file) -> int:
361369
try:
362370
fmt = filename.rsplit('.', 1)[1]
363371
except IndexError:
364-
sys.exit('Output filename requires a format.')
372+
cprint(
373+
'<red>Output filename requires a format.</red>',
374+
file=sys.stderr,
375+
)
376+
return 1
365377
else:
366378
filename = NamedTemporaryFile().name
367379
fmt = 'png'
@@ -376,9 +388,8 @@ def graph_render(opts, workflow_id, start, stop, flow_file) -> int:
376388
render_dot(dot_lines, filename, fmt)
377389

378390
# notify the user / open the graph
379-
if opts.output:
380-
print(f'Graph rendered to {opts.output}')
381-
else:
391+
cprint(f'<green>Graph rendered to {opts.output or filename}</green>')
392+
if not opts.output:
382393
open_image(filename)
383394
return 0
384395

@@ -537,12 +548,11 @@ def main(
537548
start: Optional[str] = None,
538549
stop: Optional[str] = None
539550
) -> None:
540-
result = asyncio.run(_main(parser, opts, workflow_id, start, stop))
551+
result = asyncio.run(_main(opts, workflow_id, start, stop))
541552
sys.exit(result)
542553

543554

544555
async def _main(
545-
parser: COP,
546556
opts: 'Values',
547557
workflow_id: str,
548558
start: Optional[str] = None,
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# THIS FILE IS PART OF THE CYLC WORKFLOW ENGINE.
2+
# Copyright (C) NIWA & British Crown (Met Office) & Contributors.
3+
#
4+
# This program is free software: you can redistribute it and/or modify
5+
# it under the terms of the GNU General Public License as published by
6+
# the Free Software Foundation, either version 3 of the License, or
7+
# (at your option) any later version.
8+
#
9+
# This program is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
# GNU General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU General Public License
15+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
import pytest
18+
19+
from cylc.flow.option_parsers import Options
20+
from cylc.flow.scripts.graph import _main, get_option_parser
21+
22+
23+
Opts = Options(get_option_parser())
24+
25+
26+
@pytest.fixture
27+
def disable_graph_open(monkeypatch):
28+
"""Prevent "cylc graph" from trying to pop open the image."""
29+
monkeypatch.setattr(
30+
'cylc.flow.scripts.graph.open_image',
31+
lambda *_a, **_k: None,
32+
)
33+
34+
35+
async def test_blank_graph(one, disable_graph_open, capsys):
36+
"""It should inform the user if there are no tasks to display."""
37+
# graph with one task
38+
await _main(Opts(color='never'), one.tokens.id)
39+
out, err = capsys.readouterr()
40+
assert 'Graph rendered to' in out
41+
42+
# graph with no tasks (the only task is in cycle "1")
43+
await _main(Opts(color='never'), one.tokens.id, '5')
44+
out, err = capsys.readouterr()
45+
assert 'No tasks to display' in err
46+
assert 'Try changing the start and stop values' in err

tests/integration/test_get_old_tvars.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ async def test_validate_with_old_tvars(
8585
opts.reference = True
8686

8787
async with mod_start(_setup):
88-
if function in {view, cylclist}:
88+
if function in {view, cylclist, graph}:
8989
await function(opts, _setup.workflow_name)
9090
else:
9191
await function(parser, opts, _setup.workflow_name)

0 commit comments

Comments
 (0)