Skip to content

Commit

Permalink
Possible redefine steps during step variable substitution
Browse files Browse the repository at this point in the history
  • Loading branch information
Kostiantyn Goloveshko authored and Kostiantyn Goloveshko committed Aug 9, 2021
1 parent e5d22c4 commit 8cfd432
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 54 deletions.
2 changes: 1 addition & 1 deletion pytest_bdd/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,4 +457,4 @@ def get_tags(line):


STEP_PARAM_TEMPLATE = "<{param}>"
STEP_PARAM_RE = re.compile(STEP_PARAM_TEMPLATE.format(param="([^<>]+?)"))
STEP_PARAM_RE = re.compile(STEP_PARAM_TEMPLATE.format(param="((?<=<)[^<>]+(?=>))"))
77 changes: 30 additions & 47 deletions pytest_bdd/scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
scenario_name="Publishing the article",
)
"""
import collections
import os
import re
from itertools import chain, product

import pytest
from _pytest.fixtures import FixtureLookupError
Expand All @@ -27,68 +27,54 @@
ALPHA_REGEX = re.compile(r"^\d+_*")


def substitute_step_parameters(name: str, request):
def generate_partial_substituted_step_parameters(name: str, request):
"""Returns step name with substituted parameters from fixtures, example tables, param marks"""
while True:
match = re.search(STEP_PARAM_RE, name)
if not match:
break
yield name
matches = re.finditer(STEP_PARAM_RE, name)
for match in matches:
param_name = match.group(1)
name = re.sub(
sub_name = re.sub(
STEP_PARAM_TEMPLATE.format(param=re.escape(param_name)),
str(request.getfixturevalue(param_name)),
name,
count=1
count=1,
)
return name
yield from generate_partial_substituted_step_parameters(sub_name, request)


def find_argumented_step_fixture_name(name, type_, fixturemanager, request=None):
"""Find argumented step fixture name."""
# happens to be that _arg2fixturedefs is changed during the iteration so we use a copy

step_name_parameters_substituted = substitute_step_parameters(name, request) if request else None

for fixturename, fixturedefs in list(fixturemanager._arg2fixturedefs.items()):
for fixturedef in fixturedefs:
parser = getattr(fixturedef.func, "parser", None)
if parser is None:
continue

is_step_name_directly_processable = parser.is_matching(name)
is_step_name_param_substitution_needed = not is_step_name_directly_processable
is_step_name_param_substituted_processable = step_name_parameters_substituted and parser.is_matching(
step_name_parameters_substituted
)
is_step_name_processable = is_step_name_directly_processable or is_step_name_param_substituted_processable

if not is_step_name_processable:
continue
fixturedefs = chain.from_iterable(fixturedefs for _, fixturedefs in list(fixturemanager._arg2fixturedefs.items()))
fixturedef_funcs = (fixturedef.func for fixturedef in fixturedefs)
parsers_fixturedef_function_mappings = (
(fixturedef_func.parser, fixturedef_func)
for fixturedef_func in fixturedef_funcs
if hasattr(fixturedef_func, "parser")
)

if is_step_name_param_substitution_needed:
step_name = step_name_parameters_substituted
else:
step_name = name
matched_steps_with_parsers = (
(step_name, parser, getattr(fixturedef_function, "converters", {}))
for step_name, (parser, fixturedef_function) in product(
generate_partial_substituted_step_parameters(name, request), parsers_fixturedef_function_mappings
)
if parser.is_matching(step_name)
)

converters = getattr(fixturedef.func, "converters", {})
for step_name, parser, converters in matched_steps_with_parsers:
if request:
for arg, value in parser.parse_arguments(step_name).items():
if arg in converters:
value = converters[arg](value)
if request:
try:
overridable_fixture_value = request.getfixturevalue(arg)
except FixtureLookupError:
inject_fixture(request, arg, value)
else:
if overridable_fixture_value != value:
inject_fixture(request, arg, value)
parser_name = get_step_fixture_name(parser.name, type_)
if request:
try:
request.getfixturevalue(parser_name)
overridable_fixture_value = request.getfixturevalue(arg)
except FixtureLookupError:
continue
return parser_name
inject_fixture(request, arg, value)
else:
if overridable_fixture_value != value:
inject_fixture(request, arg, value)
return get_step_fixture_name(parser.name, type_)


def _find_step_function(request, step, scenario):
Expand Down Expand Up @@ -176,9 +162,6 @@ def _execute_scenario(feature, scenario, request):
request.config.hook.pytest_bdd_after_scenario(request=request, feature=feature, scenario=scenario)


FakeRequest = collections.namedtuple("FakeRequest", ["module"])


def _get_scenario_decorator(feature, feature_name, scenario, scenario_name):
# HACK: Ideally we would use `def decorator(fn)`, but we want to return a custom exception
# when the decorator is misused.
Expand Down
15 changes: 9 additions & 6 deletions tests/feature/test_outline.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,9 @@ def test_nested_outlining(testdir):
),
)

testdir.makeconftest(textwrap.dedent(
"""\
testdir.makeconftest(
textwrap.dedent(
"""\
from pytest_bdd import given, when, then
from pytest_bdd.parsers import re, parse
Expand All @@ -152,7 +153,8 @@ def should_have_left_cucumbers(cucumbers_count, left):
assert isinstance(left, str)
assert cucumbers_count["cucumbers_count"] == float(left)
"""
))
)
)

testdir.makepyfile(
textwrap.dedent(
Expand Down Expand Up @@ -181,7 +183,7 @@ def test_outline(request):
result.assert_outcomes(passed=2)


@mark.parametrize("steps", [STRING_STEPS, PARSER_STEPS])
@mark.parametrize("steps", [STRING_STEPS, PARSER_STEPS_CONVERTED])
def test_outline_has_subset_of_parameters(testdir, steps):
"""Test parametrized scenario when the test function has a subset of the parameters of the examples."""

Expand Down Expand Up @@ -220,7 +222,8 @@ def test_outline(request):
assert_outcomes(result, passed=1)


def test_wrongly_outlined_parameters_not_a_subset_of_examples(testdir):
@mark.parametrize("steps", [STRING_STEPS, PARSER_STEPS])
def test_wrongly_outlined_parameters_not_a_subset_of_examples(testdir, steps):
"""Test parametrized scenario when the test function has a parameter set which is not a subset of those in the examples table."""

testdir.makefile(
Expand All @@ -240,7 +243,7 @@ def test_wrongly_outlined_parameters_not_a_subset_of_examples(testdir):
"""
),
)
testdir.makeconftest(textwrap.dedent(STEPS))
testdir.makeconftest(textwrap.dedent(steps))

testdir.makepyfile(
textwrap.dedent(
Expand Down

0 comments on commit 8cfd432

Please sign in to comment.