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("-->")
|