Skip to content

Commit 0f171cc

Browse files
committed
Tests: Add test to guarantee CLI is reachable
There are tests for the CLI but these use click's `CliRunner` which calls the commands directly from the Python API instead of calling the actual CLI script. This would not catch if the script is not properly installed or reachable. Here a test is added that tries to call the CLI through a subprocess, which simulates the real situation of a user invoking it. This test would have caught the missing imports that were accidentally removed by the `ruff` linter.
1 parent 7bf1ce5 commit 0f171cc

File tree

2 files changed

+48
-0
lines changed

2 files changed

+48
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
11
"""Module for the command line interface."""
2+
from .launch import cmd_launch # noqa: F401
3+
from .plot import cmd_plot # noqa: F401
4+
from .root import cmd_root # noqa: F401

tests/cli/test_root.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""Tests for CLI commands."""
2+
from __future__ import annotations
3+
4+
import subprocess
5+
6+
import click
7+
import pytest
8+
from aiida_common_workflows.cli import cmd_root
9+
10+
11+
def recurse_commands(command: click.Command, parents: list[str] | None = None):
12+
"""Recursively return all subcommands that are part of ``command``.
13+
14+
:param command: The click command to start with.
15+
:param parents: A list of strings that represent the parent commands leading up to the current command.
16+
:returns: A list of strings denoting the full path to the current command.
17+
"""
18+
if isinstance(command, click.Group):
19+
for command_name in command.commands:
20+
subcommand = command.get_command(None, command_name)
21+
if parents is not None:
22+
subparents = [*parents, command.name]
23+
else:
24+
subparents = [command.name]
25+
yield from recurse_commands(subcommand, subparents)
26+
27+
if parents is not None:
28+
yield [*parents, command.name]
29+
else:
30+
yield [command.name]
31+
32+
33+
@pytest.mark.parametrize('command', recurse_commands(cmd_root))
34+
@pytest.mark.parametrize('help_option', ('--help', '-h'))
35+
def test_commands_help_option(command, help_option):
36+
"""Test the help options for all subcommands of the CLI.
37+
38+
The usage of ``subprocess.run`` is on purpose because using :meth:`click.Context.invoke`, which is used by the
39+
``run_cli_command`` fixture that should usually be used in testing CLI commands, does not behave exactly the same
40+
compared to a direct invocation on the command line. The invocation through ``invoke`` does not go through all the
41+
parent commands and so might not get all the necessary initializations.
42+
"""
43+
result = subprocess.run([*command, help_option], check=False, capture_output=True, text=True)
44+
assert result.returncode == 0, result.stderr
45+
assert 'Usage:' in result.stdout

0 commit comments

Comments
 (0)