Skip to content

Commit

Permalink
refactor: remove fixture support (#76)
Browse files Browse the repository at this point in the history
  • Loading branch information
jd authored Feb 19, 2025
1 parent ef03494 commit 0752112
Showing 2 changed files with 0 additions and 221 deletions.
106 changes: 0 additions & 106 deletions pytest_mergify/__init__.py
Original file line number Diff line number Diff line change
@@ -114,112 +114,6 @@ def pytest_runtest_protocol(
else:
yield

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_teardown(
self, item: _pytest.nodes.Item
) -> typing.Generator[None, None, None]:
if self.tracer:
# Since there is no pytest_fixture_teardown hook, we have to be a
# little clever to capture the spans for each fixture's teardown.
# The pytest_fixture_post_finalizer hook is called at the end of a
# fixture's teardown, but we don't know when the fixture actually
# began tearing down.
#
# Instead start a span here for the first fixture to be torn down,
# but give it a temporary name, since we don't know which fixture it
# will be. Then, in pytest_fixture_post_finalizer, when we do know
# which fixture is being torn down, update the name and attributes
# to the actual fixture, end the span, and create the span for the
# next fixture in line to be torn down.
self._fixture_teardown_span = self.tracer.start_span("fixture teardown")
yield
# The last call to pytest_fixture_post_finalizer will create
# a span that is unneeded, so delete it.
del self._fixture_teardown_span
else:
yield

def _attributes_from_fixturedef(
self, fixturedef: _pytest.fixtures.FixtureDef[typing.Any]
) -> dict[str, str | int]:
return {
SpanAttributes.CODE_FILEPATH: fixturedef.func.__code__.co_filename,
SpanAttributes.CODE_FUNCTION: fixturedef.argname,
SpanAttributes.CODE_LINENO: fixturedef.func.__code__.co_firstlineno,
"test.fixture.scope": fixturedef.scope,
"test.scope": "fixture",
}

def _name_from_fixturedef(
self,
fixturedef: _pytest.fixtures.FixtureDef[typing.Any],
request: _pytest.fixtures.FixtureRequest,
) -> str:
if fixturedef.params and "request" in fixturedef.argnames:
try:
parameter = str(request.param)
except Exception:
parameter = str(
request.param_index
if isinstance(request, _pytest.fixtures.SubRequest)
else "?"
)
return f"{fixturedef.argname}[{parameter}]"
return fixturedef.argname

@pytest.hookimpl(hookwrapper=True)
def pytest_fixture_setup(
self,
fixturedef: _pytest.fixtures.FixtureDef[typing.Any],
request: _pytest.fixtures.FixtureRequest,
) -> typing.Generator[None, None, None]:
if self.tracer:
with self.tracer.start_as_current_span(
name=f"{self._name_from_fixturedef(fixturedef, request)} setup",
attributes=self._attributes_from_fixturedef(fixturedef),
):
yield
else:
yield

@pytest.hookimpl(hookwrapper=True)
def pytest_fixture_post_finalizer(
self,
fixturedef: _pytest.fixtures.FixtureDef[typing.Any],
request: _pytest.fixtures.SubRequest,
) -> typing.Generator[None, None, None]:
"""When the span for a fixture teardown is created by
pytest_runtest_teardown or a previous pytest_fixture_post_finalizer, we
need to update the name and attributes now that we know which fixture it
was for."""

if self.tracer:
# If the fixture has already been torn down, then it will have no cached
# result, so we can skip this one.
if fixturedef.cached_result is None:
yield
# Passing `-x` option to pytest can cause it to exit early so it may not
# have this span attribute.
elif not hasattr(self, "_fixture_teardown_span"): # pragma: no cover
yield
else:
# If we've gotten here, we have a real fixture about to be torn down.
name = f"{self._name_from_fixturedef(fixturedef, request)} teardown"
self._fixture_teardown_span.update_name(name)
attributes = self._attributes_from_fixturedef(fixturedef)
self._fixture_teardown_span.set_attributes(
attributes # type: ignore[arg-type]
)
yield
self._fixture_teardown_span.end()

# Create the span for the next fixture to be torn down. When there are
# no more fixtures remaining, this will be an empty, useless span, so it
# needs to be deleted by pytest_runtest_teardown.
self._fixture_teardown_span = self.tracer.start_span("fixture teardown")
else:
yield

def pytest_exception_interact(
self,
node: _pytest.nodes.Node,
115 changes: 0 additions & 115 deletions tests/test_spans.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import typing

import opentelemetry.trace
from opentelemetry.semconv.trace import SpanAttributes

import anys

from tests import conftest

@@ -110,118 +107,6 @@ def test_skipped():
assert spans["test_skipped"].parent.span_id == session_span.context.span_id


def test_fixture(
pytester_with_spans: conftest.PytesterWithSpanT,
) -> None:
result, spans = pytester_with_spans("""
import pytest
@pytest.fixture
def myfix(): pass
def test_pass(myfix): pass
""")

expected_spans = (
"myfix setup",
"myfix teardown",
)
for name in expected_spans:
assert name in spans

assert spans["myfix setup"].attributes == {
"test.scope": "fixture",
"code.function": "myfix",
"code.lineno": 2,
"code.filepath": anys.ANY_STR,
"test.fixture.scope": "function",
}
assert typing.cast(str, spans["myfix setup"].attributes["code.filepath"]).endswith(
"test_fixture.py"
)
assert (
spans["myfix setup"].status.status_code == opentelemetry.trace.StatusCode.UNSET
)

assert spans["myfix teardown"].attributes == {
"test.scope": "fixture",
"code.function": "myfix",
"code.lineno": 2,
"code.filepath": anys.ANY_STR,
"test.fixture.scope": "function",
}
assert typing.cast(
str, spans["myfix teardown"].attributes["code.filepath"]
).endswith("test_fixture.py")
assert (
spans["myfix teardown"].status.status_code
== opentelemetry.trace.StatusCode.UNSET
)


def test_fixture_failure(
pytester_with_spans: conftest.PytesterWithSpanT,
) -> None:
result, spans = pytester_with_spans("""
import pytest
@pytest.fixture
def myfix(): raise Exception("HELLO")
def test_pass(myfix): pass
""")

expected_spans = (
"myfix setup",
"myfix teardown",
)
for name in expected_spans:
assert name in spans

assert spans["myfix setup"].attributes == {
"test.scope": "fixture",
"code.function": "myfix",
"code.lineno": 2,
"code.filepath": anys.ANY_STR,
"test.fixture.scope": "function",
}
assert typing.cast(str, spans["myfix setup"].attributes["code.filepath"]).endswith(
"test_fixture_failure.py"
)
# NOTE: this should probably be error, but it seems there no way to catch fixture issue
# Anyhow, the test is marked as fail and the problem is pointed to the fixture
assert (
spans["myfix setup"].status.status_code == opentelemetry.trace.StatusCode.UNSET
)

assert spans["myfix teardown"].attributes == {
"test.scope": "fixture",
"code.function": "myfix",
"code.lineno": 2,
"code.filepath": anys.ANY_STR,
"test.fixture.scope": "function",
}
assert typing.cast(
str, spans["myfix teardown"].attributes["code.filepath"]
).endswith("test_fixture_failure.py")
assert (
spans["myfix teardown"].status.status_code
== opentelemetry.trace.StatusCode.UNSET
)

assert spans["test_pass"].attributes == {
"test.scope": "case",
"code.function": "test_pass",
"code.lineno": 3,
"code.filepath": "test_fixture_failure.py",
SpanAttributes.EXCEPTION_TYPE: "<class 'Exception'>",
SpanAttributes.EXCEPTION_MESSAGE: "HELLO",
SpanAttributes.EXCEPTION_STACKTRACE: """@pytest.fixture
> def myfix(): raise Exception("HELLO")
E Exception: HELLO
test_fixture_failure.py:3: Exception""",
}
assert spans["test_pass"].status.status_code == opentelemetry.trace.StatusCode.ERROR
assert spans["test_pass"].status.description == "<class 'Exception'>: HELLO"


def test_span_resources_test_run_id(
pytester_with_spans: conftest.PytesterWithSpanT,
) -> None:

0 comments on commit 0752112

Please sign in to comment.