ariadne/tests/test_hermes_incident_evidence_section.py
codex 75c7ac10c7
All checks were successful
Tests / Declarative: Post Actions passed: 1374
feat(hermes): show the failure in the issue instead of citing where it is
Issue #2 on bstein/ariadne cites its evidence as "console_failures marker
'=== FAILURES ===' at line 1949". That is precise and unusable: the
maintainer has to open Jenkins, find build 408, scroll a 2000-line console and
reconstruct what the diagnosis had already read. The whole point of filing an
issue is that someone can act on it without doing that.

The traceback was not merely unrendered - it was never collected. The junit
query asked for errorDetails and not errorStackTrace, so the bundle carried
the assertion that failed but not the line it failed on, and no amount of
rendering would have found it. Both halves are fixed: the trace is fetched and
bounded, and the issue now opens an Evidence section with it.

Console regions are the fallback for a build that published no test results,
clipped to their last lines because the tail of a failure region holds the
failure while the head only approaches it. A fence inside an excerpt is
escaped so raw log text cannot break out of the code block and spill into the
rendered page.

Bounded deliberately. The body has a hard character budget, and an issue that
spends it on console noise buries the one sentence saying why a person is
needed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 22:24:15 -03:00

156 lines
4.8 KiB
Python

"""Tests for showing the failure itself in a triage issue."""
from __future__ import annotations
import pytest
from ariadne.services import hermes_incident_evidence_section as module
from ariadne.services import hermes_incident_body as body
TRACE = (
"tests/test_utils.py:212: in test_safe_error_detail\n"
" assert 'bad things' in safe_error_detail(exc)\n"
"E AssertionError: assert 'bad things' in 'http 400'"
)
def _bundle(**jenkins):
return {"jenkins": jenkins}
def test_the_traceback_is_shown_not_merely_cited() -> None:
"""Citing a console line number makes a maintainer reconstruct the failure."""
section = module.evidence_section(
_bundle(failed_tests=[{"name": "t", "errorStackTrace": TRACE}])
)
assert section.startswith("## Evidence")
assert "The failing test's traceback" in section
assert "AssertionError: assert 'bad things' in 'http 400'" in section
assert section.count("```") == 2
def test_the_stack_trace_is_preferred_over_the_assertion_line() -> None:
"""The assertion says what was wrong; the trace says where."""
section = module.evidence_section(
_bundle(failed_tests=[{"errorDetails": "AssertionError: nope", "errorStackTrace": TRACE}])
)
assert "tests/test_utils.py:212" in section
assert "AssertionError: nope" not in section
def test_the_assertion_is_used_when_no_trace_was_published() -> None:
section = module.evidence_section(
_bundle(failed_tests=[{"errorDetails": "AssertionError: nope"}])
)
assert "AssertionError: nope" in section
def test_a_test_without_either_field_is_skipped() -> None:
section = module.evidence_section(
_bundle(
failed_tests=[
{"name": "a"},
{"name": "b", "errorStackTrace": " "},
"not-a-dict",
{"name": "c", "errorStackTrace": TRACE},
]
)
)
assert "tests/test_utils.py:212" in section
def test_the_console_region_is_used_when_no_tests_were_published() -> None:
"""A build whose runner published nothing still has a console to quote."""
section = module.evidence_section(
_bundle(console_failures=[{"text": "Traceback\n File x\nValueError: boom"}])
)
assert "The earliest failure region" in section
assert "ValueError: boom" in section
def test_the_region_keeps_its_last_lines_when_it_is_long() -> None:
"""The tail of a failure region holds the failure; the head approaches it."""
text = "\n".join([f"line {i}" for i in range(100)] + ["ValueError: boom"])
section = module.evidence_section(_bundle(console_failures=[{"text": text}]))
assert "ValueError: boom" in section
assert "line 0" not in section
def test_a_long_trace_is_truncated_with_a_pointer_to_the_build() -> None:
section = module.evidence_section(
_bundle(failed_tests=[{"errorStackTrace": "x" * 9000}])
)
assert len(section) < 3000
assert "truncated" in section
assert section.rstrip().endswith("```")
def test_a_fence_inside_the_excerpt_cannot_end_the_block_early() -> None:
"""Raw log text must not be able to break out into the rendered issue."""
section = module.evidence_section(
_bundle(failed_tests=[{"errorStackTrace": "before\n```\nafter"}])
)
assert section.count("```") == 2
assert "after" in section
@pytest.mark.parametrize(
"bundle",
[
None,
{},
"not-a-dict",
{"jenkins": "not-a-dict"},
{"jenkins": {}},
{"jenkins": {"failed_tests": [], "console_failures": []}},
{"jenkins": {"failed_tests": None, "console_failures": None}},
{"jenkins": {"console_failures": [{"text": " "}, "not-a-dict"]}},
],
)
def test_a_bundle_with_nothing_to_quote_renders_no_heading(bundle) -> None:
"""An empty Evidence heading is worse than no heading."""
assert module.evidence_section(bundle) == ""
def test_the_issue_body_carries_the_excerpt_and_keeps_its_marker() -> None:
rendered = body.issue_body(
{
"incident_id": "ariadne/408",
"job": "ariadne",
"build_number": 408,
"classification": "pytest_test_failure",
"reason": "a repository test failure",
"run_id": "run-1",
"bundle": _bundle(failed_tests=[{"errorStackTrace": TRACE}]),
}
)
assert "## Evidence" in rendered
assert "tests/test_utils.py:212" in rendered
assert rendered.index("## Evidence") < rendered.index("## Links")
assert rendered.rstrip().endswith("-->")
def test_an_issue_without_a_bundle_still_renders() -> None:
rendered = body.issue_body(
{"incident_id": "a/1", "job": "a", "build_number": 1, "reason": "r", "run_id": "x"}
)
assert "## Evidence" not in rendered
assert rendered.rstrip().endswith("-->")