Skip to content

Commit

Permalink
Add more CLI tests (#152)
Browse files Browse the repository at this point in the history
  • Loading branch information
Cube707 authored Feb 16, 2025
1 parent 410b987 commit bca6112
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 38 deletions.
55 changes: 23 additions & 32 deletions junitparser/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from . import JUnitXml, version


def merge(paths, output, suite_name):
"""Merge XML report."""
def merge(paths, output, suite_name=""):
"""Merge XML reports."""
result = JUnitXml()
for path in paths:
result += JUnitXml.fromfile(path)
Expand Down Expand Up @@ -44,18 +44,23 @@ def _parser(prog_name=None): # pragma: no cover
command_parser = parser.add_subparsers(dest="command", help="command")
command_parser.required = True

# command: merge
merge_parser = command_parser.add_parser(
"merge", help="Merge JUnit XML format reports with junitparser."
)
merge_parser.add_argument(
# an abstract object that defines common arguments used by multiple commands
abstract_parser = ArgumentParser(add_help=False)
abstract_parser.add_argument(
"--glob",
help="Treat original XML path(s) as glob(s).",
dest="paths_are_globs",
action="store_true",
default=False,
)
merge_parser.add_argument("paths", nargs="+", help="Original XML path(s).")
abstract_parser.add_argument("paths", help="Original XML path(s).", nargs="+")

# command: merge
merge_parser = command_parser.add_parser(
"merge",
help="Merge JUnit XML format reports with junitparser.",
parents=[abstract_parser],
)
merge_parser.add_argument(
"output", help='Merged XML Path, setting to "-" will output to the console'
)
Expand All @@ -65,39 +70,25 @@ def _parser(prog_name=None): # pragma: no cover
)

# command: verify
merge_parser = command_parser.add_parser(
verify_parser = command_parser.add_parser( # noqa: F841
"verify",
help="Return a non-zero exit code if one of the testcases failed or errored.",
)
merge_parser.add_argument(
"--glob",
help="Treat original XML path(s) as glob(s).",
dest="paths_are_globs",
action="store_true",
default=False,
)
merge_parser.add_argument(
"paths", nargs="+", help="XML path(s) of reports to verify."
parents=[abstract_parser],
)

return parser


def main(args=None, prog_name=None):
"""CLI's main runner."""
args = args or _parser(prog_name=prog_name).parse_args()
args = _parser(prog_name=prog_name).parse_args(args)
paths = (
chain.from_iterable(iglob(path) for path in args.paths)
if args.paths_are_globs
else args.paths
)
if args.command == "merge":
return merge(
chain.from_iterable(iglob(path) for path in args.paths)
if args.paths_are_globs
else args.paths,
args.output,
args.suite_name,
)
return merge(paths, args.output, args.suite_name)
if args.command == "verify":
return verify(
chain.from_iterable(iglob(path) for path in args.paths)
if args.paths_are_globs
else args.paths
)
return verify(paths)
return 255
8 changes: 8 additions & 0 deletions tests/data/pytest_error.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<testsuites>
<testsuite name="pytest" errors="0" failures="1" skipped="0" tests="1" time="0.039" timestamp="2025-01-14T20:33:43.564504+01:00">
<testcase classname="tests.test_cli" name="test_merge" time="0.001">
<failure message="NameError: name 'file' is not defined" />
</testcase>
</testsuite>
</testsuites>
6 changes: 6 additions & 0 deletions tests/data/pytest_success.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<testsuites>
<testsuite name="pytest" errors="0" failures="0" skipped="0" tests="1" time="0.026" timestamp="2025-01-14T20:33:46.249864+01:00" >
<testcase classname="tests.test_fromfile" name="test_fromfile" time="0.001" />
</testsuite>
</testsuites>
107 changes: 101 additions & 6 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,107 @@
import os
from pathlib import Path
import pytest
from junitparser.cli import verify
from junitparser import cli
from junitparser import version

DATA_DIR = Path(__file__).parent / "data"


@pytest.mark.parametrize(
"file, expected_exitcode",
[("data/jenkins.xml", 1), ("data/no_fails.xml", 0), ("data/normal.xml", 1)],
[("jenkins.xml", 1), ("no_fails.xml", 0), ("normal.xml", 1)],
)
def test_verify(file, expected_exitcode):
path = os.path.join(os.path.dirname(__file__), file)
assert verify([path]) == expected_exitcode
def test_verify(file: str, expected_exitcode: int):
path = DATA_DIR / file
assert cli.verify([path]) == expected_exitcode


def test_merge(tmp_path: Path):
files = [DATA_DIR / "jenkins.xml", DATA_DIR / "pytest_success.xml"]
suites = ["JUnitXmlReporter", "JUnitXmlReporter.constructor", "pytest"]
outfile = tmp_path / "merged.xml"
cli.merge(files, str(outfile))
xml = outfile.read_text()
for s in suites:
assert f'name="{s}"' in xml


def test_merge_output_to_terminal(capsys: pytest.CaptureFixture):
ret = cli.main(["merge", str(DATA_DIR / "normal.xml"), "-"])
assert ret == 0
captured = capsys.readouterr()
assert captured.out.startswith("<?xml version='1.0'")


def test_verify_with_glob():
ret = cli.main(["verify", "--glob", str(DATA_DIR / "pytest_*.xml")])
# we expect failure, as one of the files has errors
assert ret == 1


class Test_CommandlineOptions:

@classmethod
def setup_class(cls):
cls.parser = cli._parser("junitparser")

@pytest.mark.parametrize("arg", ["-v", "--version"])
def test_version(self, arg, capsys):
with pytest.raises(SystemExit) as e:
self.parser.parse_args([arg])
captured = capsys.readouterr()
assert e.value.code == 0
assert captured.out == f"junitparser {version}\n"

def test_help_shows_subcommands(self, capsys):
with pytest.raises(SystemExit) as e:
self.parser.parse_args(["--help"])
captured = capsys.readouterr()
assert "{merge,verify} ...\n" in captured.out
assert e.value.code == 0

@pytest.mark.parametrize("command", ["merge", "verify"])
def test_subcommand_help(self, command):
with pytest.raises(SystemExit) as e:
self.parser.parse_args([command, "--help"])
assert e.value.code == 0

@pytest.mark.parametrize("command", ["merge", "verify"])
def test_subcommands_help_general_options(self, command, capsys):
with pytest.raises(SystemExit):
self.parser.parse_args([command, "--help"])
captured = capsys.readouterr()
assert "[--glob]" in captured.out
assert "paths [paths ...]" in captured.out

def test_merge_help_options(self, capsys):
with pytest.raises(SystemExit):
self.parser.parse_args(["merge", "--help"])
captured = capsys.readouterr()
assert "[--suite-name SUITE_NAME]" in captured.out
assert "output\n" in captured.out

@pytest.mark.parametrize("command", ["merge", "verify"])
def test_option_glob(
self,
command,
):
args = self.parser.parse_args([command, "--glob", "pytest_*.xml", "-"])
assert args.paths_are_globs

def test_verify_argument_path(self):
files = ["foo", "bar"]
args = self.parser.parse_args(["verify", *files])
assert args.paths == files

def test_merge_argument_path(self):
files = ["foo", "bar"]
args = self.parser.parse_args(["merge", *files, "-"])
assert args.paths == files

def test_merge_option_suite_name(self):
args = self.parser.parse_args(["merge", "--suite-name", "foo", "_", "-"])
assert args.suite_name == "foo"

def test_merge_argument_output(self):
args = self.parser.parse_args(["merge", "foo", "bar"])
assert args.output == "bar"

0 comments on commit bca6112

Please sign in to comment.