Skip to content

Commit

Permalink
Merge pull request #666 from bluetech/pytest81-fix
Browse files Browse the repository at this point in the history
Adapt to `getfixturedefs` change in pytest 8.1
  • Loading branch information
youtux committed Jan 21, 2024
2 parents 8a694ff + 32a19ce commit 5581e77
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Expand Up @@ -3,6 +3,7 @@ Changelog

Unreleased
----------
- Address compatibility issue with pytest 8.1. `#666 <https://github.com/pytest-dev/pytest-bdd/pull/666>`_

7.0.1
-----
Expand Down
2 changes: 1 addition & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Expand Up @@ -40,6 +40,7 @@ parse = "*"
parse-type = "*"
pytest = ">=6.2.0"
typing-extensions = "*"
packaging = "*"

[tool.poetry.group.dev.dependencies]
tox = ">=4.11.3"
Expand Down
22 changes: 22 additions & 0 deletions src/pytest_bdd/compat.py
@@ -0,0 +1,22 @@
from __future__ import annotations

from collections.abc import Sequence
from importlib.metadata import version

from _pytest.fixtures import FixtureDef, FixtureManager
from _pytest.nodes import Node
from packaging.version import Version
from packaging.version import parse as parse_version

pytest_version = parse_version(version("pytest"))


if pytest_version >= Version("8.1"):

def getfixturedefs(fixturemanager: FixtureManager, fixturename: str, node: Node) -> Sequence[FixtureDef] | None:
return fixturemanager.getfixturedefs(fixturename, node)

else:

def getfixturedefs(fixturemanager: FixtureManager, fixturename: str, node: Node) -> Sequence[FixtureDef] | None:
return fixturemanager.getfixturedefs(fixturename, node.nodeid)
5 changes: 3 additions & 2 deletions src/pytest_bdd/generation.py
Expand Up @@ -8,6 +8,7 @@
from _pytest._io import TerminalWriter
from mako.lookup import TemplateLookup

from .compat import getfixturedefs
from .feature import get_features
from .scenario import inject_fixturedefs_for_step, make_python_docstring, make_python_name, make_string_literal
from .steps import get_step_fixture_name
Expand Down Expand Up @@ -127,9 +128,9 @@ def _find_step_fixturedef(
fixturemanager: FixtureManager, item: Function, step: Step
) -> Sequence[FixtureDef[Any]] | None:
"""Find step fixturedef."""
with inject_fixturedefs_for_step(step=step, fixturemanager=fixturemanager, nodeid=item.nodeid):
with inject_fixturedefs_for_step(step=step, fixturemanager=fixturemanager, node=item):
bdd_name = get_step_fixture_name(step=step)
return fixturemanager.getfixturedefs(bdd_name, item.nodeid)
return getfixturedefs(fixturemanager, bdd_name, item)


def parse_feature_files(paths: list[str], **kwargs: Any) -> tuple[list[Feature], list[ScenarioTemplate], list[Step]]:
Expand Down
61 changes: 55 additions & 6 deletions src/pytest_bdd/scenario.py
Expand Up @@ -20,16 +20,17 @@

import pytest
from _pytest.fixtures import FixtureDef, FixtureManager, FixtureRequest, call_fixture_func
from _pytest.nodes import iterparentnodeids
from typing_extensions import ParamSpec

from . import exceptions
from .compat import getfixturedefs
from .feature import get_feature, get_features
from .steps import StepFunctionContext, get_step_fixture_name, inject_fixture
from .utils import CONFIG_STACK, get_args, get_caller_module_locals, get_caller_module_path

if TYPE_CHECKING:
from _pytest.mark.structures import ParameterSet
from _pytest.nodes import Node

from .parser import Feature, Scenario, ScenarioTemplate, Step

Expand All @@ -43,7 +44,7 @@
ALPHA_REGEX = re.compile(r"^\d+_*")


def find_fixturedefs_for_step(step: Step, fixturemanager: FixtureManager, nodeid: str) -> Iterable[FixtureDef[Any]]:
def find_fixturedefs_for_step(step: Step, fixturemanager: FixtureManager, node: Node) -> Iterable[FixtureDef[Any]]:
"""Find the fixture defs that can parse a step."""
# happens to be that _arg2fixturedefs is changed during the iteration so we use a copy
fixture_def_by_name = list(fixturemanager._arg2fixturedefs.items())
Expand All @@ -60,14 +61,62 @@ def find_fixturedefs_for_step(step: Step, fixturemanager: FixtureManager, nodeid
if not match:
continue

if fixturedef not in (fixturemanager.getfixturedefs(fixturename, nodeid) or []):
fixturedefs = getfixturedefs(fixturemanager, fixturename, node)
if fixturedef not in (fixturedefs or []):
continue

yield fixturedef


# Function copied from pytest 8.0 (removed in later versions).
def iterparentnodeids(nodeid: str) -> Iterator[str]:
"""Return the parent node IDs of a given node ID, inclusive.
For the node ID
"testing/code/test_excinfo.py::TestFormattedExcinfo::test_repr_source"
the result would be
""
"testing"
"testing/code"
"testing/code/test_excinfo.py"
"testing/code/test_excinfo.py::TestFormattedExcinfo"
"testing/code/test_excinfo.py::TestFormattedExcinfo::test_repr_source"
Note that / components are only considered until the first ::.
"""
SEP = "/"
pos = 0
first_colons: Optional[int] = nodeid.find("::")
if first_colons == -1:
first_colons = None
# The root Session node - always present.
yield ""
# Eagerly consume SEP parts until first colons.
while True:
at = nodeid.find(SEP, pos, first_colons)
if at == -1:
break
if at > 0:
yield nodeid[:at]
pos = at + len(SEP)
# Eagerly consume :: parts.
while True:
at = nodeid.find("::", pos)
if at == -1:
break
if at > 0:
yield nodeid[:at]
pos = at + len("::")
# The node ID itself.
if nodeid:
yield nodeid


@contextlib.contextmanager
def inject_fixturedefs_for_step(step: Step, fixturemanager: FixtureManager, nodeid: str) -> Iterator[None]:
def inject_fixturedefs_for_step(step: Step, fixturemanager: FixtureManager, node: Node) -> Iterator[None]:
"""Inject fixture definitions that can parse a step.
We fist iterate over all the fixturedefs that can parse the step.
Expand All @@ -78,7 +127,7 @@ def inject_fixturedefs_for_step(step: Step, fixturemanager: FixtureManager, node
"""
bdd_name = get_step_fixture_name(step=step)

fixturedefs = list(find_fixturedefs_for_step(step=step, fixturemanager=fixturemanager, nodeid=nodeid))
fixturedefs = list(find_fixturedefs_for_step(step=step, fixturemanager=fixturemanager, node=node))

# Sort the fixture definitions by their "path", so that the `bdd_name` fixture will
# respect the fixture scope
Expand Down Expand Up @@ -114,7 +163,7 @@ def get_step_function(request, step: Step) -> StepFunctionContext | None:
__tracebackhide__ = True
bdd_name = get_step_fixture_name(step=step)

with inject_fixturedefs_for_step(step=step, fixturemanager=request._fixturemanager, nodeid=request.node.nodeid):
with inject_fixturedefs_for_step(step=step, fixturemanager=request._fixturemanager, node=request.node):
try:
return cast(StepFunctionContext, request.getfixturevalue(bdd_name))
except pytest.FixtureLookupError:
Expand Down

0 comments on commit 5581e77

Please sign in to comment.