"""Unit tests for script/update_integration_test_durations.py."""
import json
from pathlib import Path
import sys
from unittest.mock import patch
import pytest
# Add the script directory to Python path so we can import the module
script_dir = str((Path(__file__).parent / ".." / ".." / "script").resolve())
sys.path.insert(0, script_dir)
import helpers # noqa: E402
import update_integration_test_durations as uitd # noqa: E402
JUNIT_TEMPLATE = """
{testcases}
"""
KNOWN = {
"tests/integration/test_a.py",
"tests/integration/test_b.py",
}
def _write_junit(path: Path, testcases: str) -> None:
path.write_text(JUNIT_TEMPLATE.format(testcases=testcases), encoding="utf-8")
def test_collect_durations_sums_per_file(tmp_path: Path) -> None:
"""Testcases from the same module sum."""
_write_junit(
tmp_path / "a.xml",
''
''
'',
)
assert uitd.collect_durations(tmp_path, KNOWN) == {
"tests/integration/test_a.py": 3.5,
"tests/integration/test_b.py": 4.0,
}
def test_collect_durations_class_based_testcase(tmp_path: Path) -> None:
"""A class-based classname still maps to its module file."""
_write_junit(
tmp_path / "a.xml",
'',
)
assert uitd.collect_durations(tmp_path, KNOWN) == {
"tests/integration/test_a.py": 2.5
}
def test_collect_durations_unknown_module_skipped(
tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
"""A classname that maps to no known file is skipped with a warning."""
_write_junit(
tmp_path / "a.xml",
'',
)
assert uitd.collect_durations(tmp_path, KNOWN) == {}
assert "test_gone" in capsys.readouterr().err
def test_collect_durations_skips_skipped_testcases(tmp_path: Path) -> None:
"""Skipped testcases do not record a bogus zero duration."""
_write_junit(
tmp_path / "a.xml",
''
"",
)
assert uitd.collect_durations(tmp_path, KNOWN) == {}
def test_collect_durations_unexpected_classname_aborts(tmp_path: Path) -> None:
"""A classname outside tests.integration means the junit layout changed."""
_write_junit(
tmp_path / "a.xml",
'',
)
with pytest.raises(SystemExit):
uitd.collect_durations(tmp_path, KNOWN)
def test_collect_durations_empty_dir_aborts(tmp_path: Path) -> None:
"""No junit XML at all is a hard error, not an empty recording."""
with pytest.raises(SystemExit):
uitd.collect_durations(tmp_path, KNOWN)
def test_main_merges_partial_run(tmp_path: Path) -> None:
"""A partial run merges over the previous data instead of truncating it."""
tests_dir = tmp_path / "tests" / "integration"
tests_dir.mkdir(parents=True)
for name in ("test_a", "test_b", "test_c"):
(tests_dir / f"{name}.py").write_text("", encoding="utf-8")
durations_file = tmp_path / helpers.INTEGRATION_TEST_DURATIONS_FILE
durations_file.write_text(
json.dumps(
{
"tests/integration/test_a.py": 5.0,
"tests/integration/test_b.py": 7.0,
"tests/integration/test_gone.py": 9.0,
}
),
encoding="utf-8",
)
junit_dir = tmp_path / "junit"
junit_dir.mkdir()
_write_junit(
junit_dir / "a.xml",
'',
)
with (
patch.object(helpers, "root_path", str(tmp_path)),
patch.object(uitd, "DURATIONS_FILE", durations_file),
):
# 1 of 3 files covered: refused without --allow-partial
with patch.object(sys, "argv", ["uitd", str(junit_dir)]):
assert uitd.main() == uitd.EXIT_LOW_COVERAGE
with patch.object(sys, "argv", ["uitd", str(junit_dir), "--allow-partial"]):
assert uitd.main() == 0
# test_a updated, test_b kept, deleted test_gone dropped
assert json.loads(durations_file.read_text()) == {
"tests/integration/test_a.py": 6.0,
"tests/integration/test_b.py": 7.0,
}