"""Unit tests for devx.ci.merge_junit.""" from pathlib import Path from xml.etree import ElementTree as ET import pytest from click.testing import CliRunner from devx.ci.merge_junit import main, merge_files def _write_suite(path: Path, name: str, tests: int, failures: int) -> None: suite = ET.Element("testsuite", name=name, tests=str(tests), failures=str(failures)) for i in range(tests): tc = ET.SubElement(suite, "testcase", classname="cls", name=f"test{i}", time="0.1") if i < failures: ET.SubElement(tc, "failure", message="fail") tree = ET.ElementTree(suite) tree.write(path, encoding="UTF-8", xml_declaration=True) class TestMergeFiles: def test_merges_multiple_suites(self, tmp_path: Path) -> None: _write_suite(tmp_path / "runner-1.xml", "r1", tests=3, failures=1) _write_suite(tmp_path / "runner-2.xml", "r2", tests=2, failures=0) merged, total_tests, total_failures = merge_files(str(tmp_path / "runner-*.xml")) assert total_tests == 5 assert total_failures == 1 assert merged.tag == "testsuites" assert len(merged) == 2 def test_no_files_returns_empty(self, tmp_path: Path) -> None: merged, total_tests, total_failures = merge_files(str(tmp_path / "nonexistent-*.xml")) assert total_tests == 0 assert total_failures == 0 assert merged.tag == "testsuites" assert len(merged) == 0 def test_handles_testsuites_wrapper_root(self, tmp_path: Path) -> None: wrapper = ET.Element("testsuites") suite = ET.SubElement(wrapper, "testsuite", name="r1", tests="4", failures="2") ET.SubElement(suite, "testcase", classname="c", name="t", time="0.1") tree = ET.ElementTree(wrapper) tree.write(tmp_path / "runner-1.xml", encoding="UTF-8", xml_declaration=True) merged, total_tests, total_failures = merge_files(str(tmp_path / "runner-*.xml")) assert total_tests == 4 assert total_failures == 2 class TestCli: def test_writes_merged_file(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: _write_suite(tmp_path / "runner-1.xml", "r1", tests=2, failures=0) _write_suite(tmp_path / "runner-2.xml", "r2", tests=3, failures=0) out = tmp_path / "merged.xml" runner = CliRunner() result = runner.invoke( main, ["--pattern", str(tmp_path / "runner-*.xml"), "--output", str(out)], ) assert result.exit_code == 0 assert out.exists() tree = ET.parse(out) root = tree.getroot() assert root.get("tests") == "5" assert root.get("failures") == "0" def test_exits_nonzero_on_failures(self, tmp_path: Path) -> None: _write_suite(tmp_path / "runner-1.xml", "r1", tests=2, failures=1) out = tmp_path / "merged.xml" runner = CliRunner() result = runner.invoke( main, ["--pattern", str(tmp_path / "runner-*.xml"), "--output", str(out)], ) assert result.exit_code != 0 assert "failures" in result.output def test_no_files_exits_zero(self, tmp_path: Path) -> None: runner = CliRunner() result = runner.invoke( main, ["--pattern", str(tmp_path / "nonexistent-*.xml"), "--output", str(tmp_path / "out.xml")], ) assert result.exit_code == 0 assert "No JUnit" in result.output or "skipping" in result.output def test_main_module_block() -> None: import devx.ci.merge_junit as mod assert hasattr(mod, "main")