Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pytest_otel/src/pytest_otel/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def end_session(self, exitstatus: int) -> None:
if exitstatus == 0:
span.set_status(Status(StatusCode.OK))
else:
span.set_status(Status(StatusCode.ERROR, f"Exit status: {exitstatus}"))
span.set_status(Status(StatusCode.ERROR, "test session failed"))

span.end()

Expand Down Expand Up @@ -178,7 +178,7 @@ def end_test(self, item: "pytest.Item", outcome: str) -> None:
if outcome == "passed":
span.set_status(Status(StatusCode.OK))
elif outcome in ("failed", "error"):
span.set_status(Status(StatusCode.ERROR, f"Test {outcome}"))
span.set_status(Status(StatusCode.ERROR, "test failed"))
# "skipped" keeps UNSET status (neither OK nor ERROR)

span.end()
Expand Down
27 changes: 27 additions & 0 deletions pytest_otel/tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,30 @@ def test_capture_stderr(self, monkeypatch):
plugin._capture_test_output(mock_item, report)

assert emitted == [("test stderr", STDIO_STREAM_STDERR, mock_span)]

def test_capture_failure_details(self, monkeypatch):
"""Test that assertion details are captured as stderr logs."""
from pytest_otel import plugin
from pytest_otel.logging_handler import STDIO_STREAM_STDERR

mock_item = Mock()
mock_item.nodeid = "tests/test_example.py::test_function"
mock_span = Mock()
emitted = []

monkeypatch.setattr(plugin.tracer, "get_test_span", lambda item: mock_span)
monkeypatch.setattr(
"pytest_otel.logging_handler.emit_stdio_log",
lambda body, stream, span=None: emitted.append((body, stream, span)),
)

report = Mock()
report.when = "call"
report.failed = True
report.capstdout = None
report.capstderr = None
report.longrepr = "assert 1 == 2"

plugin._capture_test_output(mock_item, report)

assert emitted == [("assert 1 == 2", STDIO_STREAM_STDERR, mock_span)]
46 changes: 45 additions & 1 deletion pytest_otel/tests/test_tracer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
"""Tests for the tracer module."""

from pytest_otel.tracer import SpanContextManager
from unittest.mock import Mock

from opentelemetry.trace import StatusCode

from pytest_otel.tracer import SpanContextManager, TestNode as OtelTestNode


class TestParseNodeid:
Expand Down Expand Up @@ -84,6 +88,46 @@ def test_suite_run_status_mapping(self):
assert ctx._suite_run_status(5) == TestSuiteRunStatusValues.SKIPPED.value


class TestStatusDescriptions:
"""Tests for low-cardinality span status descriptions."""

def test_session_failure_status_is_static(self):
"""Verify session failures do not include dynamic exit status text."""
ctx = SpanContextManager()
span = Mock()
ctx._session_node = OtelTestNode(
nodeid="session",
name="pytest session",
kind="session",
span=span,
)

ctx.end_session(1)

status = span.set_status.call_args.args[0]
assert status.status_code == StatusCode.ERROR
assert status.description == "test session failed"

def test_test_failure_status_is_static(self):
"""Verify test failures do not include dynamic assertion text."""
ctx = SpanContextManager()
span = Mock()
item = Mock()
item.nodeid = "tests/test_foo.py::test_bar"
ctx._tests[item.nodeid] = OtelTestNode(
nodeid=item.nodeid,
name="test_bar",
kind="function",
span=span,
)

ctx.end_test(item, "failed")

status = span.set_status.call_args.args[0]
assert status.status_code == StatusCode.ERROR
assert status.description == "test failed"


class TestAttributeNames:
"""Tests for attribute name constants."""

Expand Down