litellm/tests/e2e/test_junit_properties.py
ryan-crabbe-berri 0c2d4c5773 Refuse a source path carrying a colon
`path:line` cannot represent a path that itself contains a colon, and the
one way pytest produces one is a Windows absolute location: separator
normalization turns `C:\app\e2e\a2a\test_x.py` into `C:/app/...`, which
slipped past the leading-slash check and composed the nonsense repo path
`tests/e2e/C:/app/e2e/a2a/test_x.py`.

Reject the colon itself rather than special-casing a drive letter: it is
the character the format reserves, so no path containing one was ever
linkable.

Claude-Session: https://claude.ai/code/session_017dTKXwJkzhtVLzDhePHsKG
2026-09-01 16:02:54 -07:00

146 lines
5.9 KiB
Python

"""Harness coverage for the custom JUnit properties.
No proxy and no ``e2e`` marker. Pins the two normalizations that have to agree
about where a suite file lives -- ``package_from_nodeid`` (strip the suite root)
and ``source_from_location`` (re-root at it) -- across both ways the suite is
launched, plus the one-based line offset and the refusal to emit a path that
escapes the suite. The consumers of these properties are the Loki/Grafana
rollups and, for ``source``, the status page's per-test links to GitHub.
"""
from __future__ import annotations
from pathlib import Path
import pytest
from junit_properties import (
SUITE_ROOT,
attach_result_properties,
dedupe_covers,
package_from_nodeid,
result_properties,
source_from_location,
suite_parts,
)
class FakeMarker:
def __init__(self, name: str, *args: object) -> None:
self.name = name
self.args = args
class FakeItem:
"""The three attributes junit_properties reads off a pytest Item."""
def __init__(
self, nodeid: str, location: tuple[str, int | None, str], markers: tuple[FakeMarker, ...] = ()
) -> None:
self.nodeid = nodeid
self.location = location
self.user_properties: list[tuple[str, str]] = []
self._markers = markers
def iter_markers(self, name: str):
return (marker for marker in self._markers if marker.name == name)
def repo_root() -> Path | None:
"""The litellm checkout above this file, or None when there isn't one."""
return next((p for p in Path(__file__).resolve().parents if (p / ".git").exists()), None)
class TestSuiteParts:
@pytest.mark.parametrize(
"path",
["logging/test_x.py", "tests/e2e/logging/test_x.py", "./logging/test_x.py", "tests\\e2e\\logging\\test_x.py"],
)
def test_both_invocation_shapes_collapse_to_the_same_components(self, path: str) -> None:
"""A repo-root run and a suite-cwd run report the same file differently;
every downstream signal has to see one spelling."""
assert suite_parts(path) == ("logging", "test_x.py")
def test_top_level_suite_file_keeps_its_single_component(self) -> None:
assert suite_parts("tests/e2e/test_fixture_mode.py") == ("test_fixture_mode.py",)
class TestPackageFromNodeid:
@pytest.mark.parametrize(
("nodeid", "expected"),
[
("logging/test_x.py::TestFoo::test_bar", "logging"),
("tests/e2e/logging/test_x.py::TestFoo::test_bar", "logging"),
("quota_management/spend_tracking/test_x.py::test_bar", "quota_management"),
("test_fixture_mode.py::TestParseFixtureMode::test_known_values_normalize", "root"),
("tests/e2e/test_fixture_mode.py::test_bar", "root"),
],
)
def test_package_is_the_first_dir_under_the_suite_root(self, nodeid: str, expected: str) -> None:
assert package_from_nodeid(nodeid) == expected
class TestSourceFromLocation:
@pytest.mark.parametrize("path", ["a2a/test_a2a_agent_e2e.py", "tests/e2e/a2a/test_a2a_agent_e2e.py"])
def test_path_is_repo_relative_however_pytest_was_started(self, path: str) -> None:
assert source_from_location(path, 40) == "tests/e2e/a2a/test_a2a_agent_e2e.py:41"
def test_line_is_emitted_one_based(self) -> None:
"""pytest.Item.location counts from 0; editors, tracebacks and GitHub's
#L anchor all count from 1, and an off-by-one lands on the decorator."""
assert source_from_location("a2a/test_x.py", 0) == "tests/e2e/a2a/test_x.py:1"
def test_top_level_suite_file_sits_directly_under_the_suite_root(self) -> None:
assert source_from_location("test_fixture_mode.py", 39) == "tests/e2e/test_fixture_mode.py:40"
@pytest.mark.parametrize(
("path", "lineno"),
[
("a2a/test_x.py", None),
("/app/e2e/a2a/test_x.py", 40),
("C:\\app\\e2e\\a2a\\test_x.py", 40),
("../conftest.py", 40),
("", 40),
],
)
def test_nothing_linkable_yields_empty_rather_than_a_guess(self, path: str, lineno: int | None) -> None:
"""A colon is rejected on two counts: it is how a Windows absolute path
arrives, and `path:line` cannot represent one in the path half."""
assert source_from_location(path, lineno) == ""
class TestResultProperties:
def test_every_test_carries_package_covers_and_source(self) -> None:
item = FakeItem(
"logging/test_x.py::TestFoo::test_bar",
("logging/test_x.py", 40, "TestFoo.test_bar"),
(FakeMarker("covers", "LOG-1", "LOG-2"),),
)
assert result_properties(item) == (
("package", "logging"),
("covers", "LOG-1,LOG-2"),
("source", "tests/e2e/logging/test_x.py:41"),
)
def test_attach_is_idempotent(self) -> None:
"""Collection can run the hook more than once; a second pass must not
double the <property> entries in the report."""
item = FakeItem("logging/test_x.py::test_bar", ("logging/test_x.py", 40, "test_bar"))
attach_result_properties(item)
attach_result_properties(item)
assert [name for name, _ in item.user_properties] == ["package", "covers", "source"]
class TestSuiteRoot:
def test_suite_root_names_this_file_s_real_home(self) -> None:
"""SUITE_ROOT is hardcoded because the runner image has no repo to read it
from. Where there IS a checkout, prove the constant still points at us --
otherwise a moved tests/e2e/ ships links that 404."""
root = repo_root()
if root is None:
pytest.skip("no checkout above this file (the runner image copies tests/e2e/ to /app/e2e)")
assert (root / SUITE_ROOT / Path(__file__).name).resolve() == Path(__file__).resolve()
class TestDedupeCovers:
def test_ids_are_unique_order_preserving_and_non_empty_strings(self) -> None:
assert dedupe_covers([("A", "B"), ("B", ""), ("C", 7)]) == ("A", "B", "C")