Skip to content

Commit bca6112

Browse files
authored
Add more CLI tests (#152)
1 parent 410b987 commit bca6112

File tree

4 files changed

+138
-38
lines changed

4 files changed

+138
-38
lines changed

junitparser/cli.py

+23-32
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
from . import JUnitXml, version
77

88

9-
def merge(paths, output, suite_name):
10-
"""Merge XML report."""
9+
def merge(paths, output, suite_name=""):
10+
"""Merge XML reports."""
1111
result = JUnitXml()
1212
for path in paths:
1313
result += JUnitXml.fromfile(path)
@@ -44,18 +44,23 @@ def _parser(prog_name=None): # pragma: no cover
4444
command_parser = parser.add_subparsers(dest="command", help="command")
4545
command_parser.required = True
4646

47-
# command: merge
48-
merge_parser = command_parser.add_parser(
49-
"merge", help="Merge JUnit XML format reports with junitparser."
50-
)
51-
merge_parser.add_argument(
47+
# an abstract object that defines common arguments used by multiple commands
48+
abstract_parser = ArgumentParser(add_help=False)
49+
abstract_parser.add_argument(
5250
"--glob",
5351
help="Treat original XML path(s) as glob(s).",
5452
dest="paths_are_globs",
5553
action="store_true",
5654
default=False,
5755
)
58-
merge_parser.add_argument("paths", nargs="+", help="Original XML path(s).")
56+
abstract_parser.add_argument("paths", help="Original XML path(s).", nargs="+")
57+
58+
# command: merge
59+
merge_parser = command_parser.add_parser(
60+
"merge",
61+
help="Merge JUnit XML format reports with junitparser.",
62+
parents=[abstract_parser],
63+
)
5964
merge_parser.add_argument(
6065
"output", help='Merged XML Path, setting to "-" will output to the console'
6166
)
@@ -65,39 +70,25 @@ def _parser(prog_name=None): # pragma: no cover
6570
)
6671

6772
# command: verify
68-
merge_parser = command_parser.add_parser(
73+
verify_parser = command_parser.add_parser( # noqa: F841
6974
"verify",
7075
help="Return a non-zero exit code if one of the testcases failed or errored.",
71-
)
72-
merge_parser.add_argument(
73-
"--glob",
74-
help="Treat original XML path(s) as glob(s).",
75-
dest="paths_are_globs",
76-
action="store_true",
77-
default=False,
78-
)
79-
merge_parser.add_argument(
80-
"paths", nargs="+", help="XML path(s) of reports to verify."
76+
parents=[abstract_parser],
8177
)
8278

8379
return parser
8480

8581

8682
def main(args=None, prog_name=None):
8783
"""CLI's main runner."""
88-
args = args or _parser(prog_name=prog_name).parse_args()
84+
args = _parser(prog_name=prog_name).parse_args(args)
85+
paths = (
86+
chain.from_iterable(iglob(path) for path in args.paths)
87+
if args.paths_are_globs
88+
else args.paths
89+
)
8990
if args.command == "merge":
90-
return merge(
91-
chain.from_iterable(iglob(path) for path in args.paths)
92-
if args.paths_are_globs
93-
else args.paths,
94-
args.output,
95-
args.suite_name,
96-
)
91+
return merge(paths, args.output, args.suite_name)
9792
if args.command == "verify":
98-
return verify(
99-
chain.from_iterable(iglob(path) for path in args.paths)
100-
if args.paths_are_globs
101-
else args.paths
102-
)
93+
return verify(paths)
10394
return 255

tests/data/pytest_error.xml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<testsuites>
3+
<testsuite name="pytest" errors="0" failures="1" skipped="0" tests="1" time="0.039" timestamp="2025-01-14T20:33:43.564504+01:00">
4+
<testcase classname="tests.test_cli" name="test_merge" time="0.001">
5+
<failure message="NameError: name 'file' is not defined" />
6+
</testcase>
7+
</testsuite>
8+
</testsuites>

tests/data/pytest_success.xml

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<testsuites>
3+
<testsuite name="pytest" errors="0" failures="0" skipped="0" tests="1" time="0.026" timestamp="2025-01-14T20:33:46.249864+01:00" >
4+
<testcase classname="tests.test_fromfile" name="test_fromfile" time="0.001" />
5+
</testsuite>
6+
</testsuites>

tests/test_cli.py

+101-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,107 @@
1-
import os
1+
from pathlib import Path
22
import pytest
3-
from junitparser.cli import verify
3+
from junitparser import cli
4+
from junitparser import version
5+
6+
DATA_DIR = Path(__file__).parent / "data"
47

58

69
@pytest.mark.parametrize(
710
"file, expected_exitcode",
8-
[("data/jenkins.xml", 1), ("data/no_fails.xml", 0), ("data/normal.xml", 1)],
11+
[("jenkins.xml", 1), ("no_fails.xml", 0), ("normal.xml", 1)],
912
)
10-
def test_verify(file, expected_exitcode):
11-
path = os.path.join(os.path.dirname(__file__), file)
12-
assert verify([path]) == expected_exitcode
13+
def test_verify(file: str, expected_exitcode: int):
14+
path = DATA_DIR / file
15+
assert cli.verify([path]) == expected_exitcode
16+
17+
18+
def test_merge(tmp_path: Path):
19+
files = [DATA_DIR / "jenkins.xml", DATA_DIR / "pytest_success.xml"]
20+
suites = ["JUnitXmlReporter", "JUnitXmlReporter.constructor", "pytest"]
21+
outfile = tmp_path / "merged.xml"
22+
cli.merge(files, str(outfile))
23+
xml = outfile.read_text()
24+
for s in suites:
25+
assert f'name="{s}"' in xml
26+
27+
28+
def test_merge_output_to_terminal(capsys: pytest.CaptureFixture):
29+
ret = cli.main(["merge", str(DATA_DIR / "normal.xml"), "-"])
30+
assert ret == 0
31+
captured = capsys.readouterr()
32+
assert captured.out.startswith("<?xml version='1.0'")
33+
34+
35+
def test_verify_with_glob():
36+
ret = cli.main(["verify", "--glob", str(DATA_DIR / "pytest_*.xml")])
37+
# we expect failure, as one of the files has errors
38+
assert ret == 1
39+
40+
41+
class Test_CommandlineOptions:
42+
43+
@classmethod
44+
def setup_class(cls):
45+
cls.parser = cli._parser("junitparser")
46+
47+
@pytest.mark.parametrize("arg", ["-v", "--version"])
48+
def test_version(self, arg, capsys):
49+
with pytest.raises(SystemExit) as e:
50+
self.parser.parse_args([arg])
51+
captured = capsys.readouterr()
52+
assert e.value.code == 0
53+
assert captured.out == f"junitparser {version}\n"
54+
55+
def test_help_shows_subcommands(self, capsys):
56+
with pytest.raises(SystemExit) as e:
57+
self.parser.parse_args(["--help"])
58+
captured = capsys.readouterr()
59+
assert "{merge,verify} ...\n" in captured.out
60+
assert e.value.code == 0
61+
62+
@pytest.mark.parametrize("command", ["merge", "verify"])
63+
def test_subcommand_help(self, command):
64+
with pytest.raises(SystemExit) as e:
65+
self.parser.parse_args([command, "--help"])
66+
assert e.value.code == 0
67+
68+
@pytest.mark.parametrize("command", ["merge", "verify"])
69+
def test_subcommands_help_general_options(self, command, capsys):
70+
with pytest.raises(SystemExit):
71+
self.parser.parse_args([command, "--help"])
72+
captured = capsys.readouterr()
73+
assert "[--glob]" in captured.out
74+
assert "paths [paths ...]" in captured.out
75+
76+
def test_merge_help_options(self, capsys):
77+
with pytest.raises(SystemExit):
78+
self.parser.parse_args(["merge", "--help"])
79+
captured = capsys.readouterr()
80+
assert "[--suite-name SUITE_NAME]" in captured.out
81+
assert "output\n" in captured.out
82+
83+
@pytest.mark.parametrize("command", ["merge", "verify"])
84+
def test_option_glob(
85+
self,
86+
command,
87+
):
88+
args = self.parser.parse_args([command, "--glob", "pytest_*.xml", "-"])
89+
assert args.paths_are_globs
90+
91+
def test_verify_argument_path(self):
92+
files = ["foo", "bar"]
93+
args = self.parser.parse_args(["verify", *files])
94+
assert args.paths == files
95+
96+
def test_merge_argument_path(self):
97+
files = ["foo", "bar"]
98+
args = self.parser.parse_args(["merge", *files, "-"])
99+
assert args.paths == files
100+
101+
def test_merge_option_suite_name(self):
102+
args = self.parser.parse_args(["merge", "--suite-name", "foo", "_", "-"])
103+
assert args.suite_name == "foo"
104+
105+
def test_merge_argument_output(self):
106+
args = self.parser.parse_args(["merge", "foo", "bar"])
107+
assert args.output == "bar"

0 commit comments

Comments
 (0)