Skip to content
Open
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
3 changes: 3 additions & 0 deletions allure-pytest/src/listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from allure_pytest.utils import get_pytest_report_status
from allure_pytest.utils import format_allure_link
from allure_pytest.utils import get_history_id
from allure_pytest.utils import is_internal_finalizer
from allure_pytest.compat import getfixturedefs


Expand Down Expand Up @@ -180,6 +181,8 @@ def pytest_fixture_setup(self, fixturedef, request):

finalizers = getattr(fixturedef, "_finalizers", [])
for index, finalizer in enumerate(finalizers):
if is_internal_finalizer(finalizer):
continue
finalizer_name = getattr(finalizer, "__name__", index)
name = f"{fixture_name}::{finalizer_name}"
finalizers[index] = allure_commons.fixture(finalizer, parent_uuid=container_uuid, name=name)
Expand Down
17 changes: 17 additions & 0 deletions allure-pytest/src/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,20 @@ def get_history_id(full_name, parameters, original_values):
key=lambda p: p.name
))
)


def is_internal_finalizer(finalizer):
"""True for finalizers that belong to pytest itself rather than to user code.

pytest 9.1 attaches its own finalizer inside ``FixtureDef.execute``
(``FixtureDef.execute.<locals>.<lambda>``). Wrapping it produces an
``<fixture>::<lambda>`` afterStage that pytest never invokes through the
wrapper, so it is reported with no status at all - which Allure renders as
"unknown", once per fixture, in every report.

The check is on which module owns the callable, not on its name: a lambda
passed to ``request.addfinalizer`` by a test author is a real teardown and
must still be reported.
"""
module = getattr(finalizer, "__module__", None) or ""
return module == "_pytest" or module.startswith("_pytest.")
69 changes: 69 additions & 0 deletions tests/allure_pytest/acceptance/fixture/internal_finalizer_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import allure
from hamcrest import assert_that
from tests.allure_pytest.pytest_runner import AllurePytestRunner

from allure_commons_test.report import has_test_case
from allure_commons_test.container import has_container
from allure_commons_test.container import has_after


def afters_of(allure_results):
return [
after
for container in allure_results.test_containers
for after in container.get("afters", [])
]


@allure.feature("Fixture")
@allure.story("Fixture finalizer")
def test_pytest_internal_finalizer_is_not_reported(allure_pytest_runner: AllurePytestRunner):
"""
>>> import pytest

pytest attaches its own finalizer to every fixture from 9.1 onwards, inside
``FixtureDef.execute``. It is machinery, not a teardown anyone wrote:
>>> @pytest.fixture
... def plain_fixture():
... yield

>>> def test_plain_fixture_example(plain_fixture):
... pass
"""

allure_results = allure_pytest_runner.run_docstring()

unreported = [after for after in afters_of(allure_results) if after.get("status") is None]

assert_that(unreported == [], f"expected no statusless afterStage, got {unreported}")


@allure.feature("Fixture")
@allure.story("Fixture finalizer")
def test_user_lambda_finalizer_is_still_reported(allure_pytest_runner: AllurePytestRunner):
"""
A finalizer is skipped based on which module owns it, never on its name, so a
lambda registered by the test author is still a real teardown and is reported.

>>> import pytest

>>> @pytest.fixture
... def fixture_with_lambda_finalizer(request):
... request.addfinalizer(lambda: None)

>>> def test_fixture_with_lambda_finalizer_example(fixture_with_lambda_finalizer):
... pass
"""

allure_results = allure_pytest_runner.run_docstring()

assert_that(
allure_results,
has_test_case(
"test_fixture_with_lambda_finalizer_example",
has_container(
allure_results,
has_after("fixture_with_lambda_finalizer::<lambda>")
)
)
)
Loading