All checks were successful
Tests / Declarative: Post Actions passed: 1374
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>
156 lines
4.8 KiB
Python
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("-->")
|