Revert "Allows use <params> in parsers defined steps"

This reverts commit 7850f218b7.
This commit is contained in:
Oleg Pidsadnyi 2021-08-18 09:20:58 +02:00
parent 628f2e1f44
commit c6b7134e59
4 changed files with 42 additions and 209 deletions

View File

@ -1,3 +1,4 @@
import io
import os.path
import re
import textwrap
@ -456,5 +457,4 @@ def get_tags(line):
return {tag.lstrip("@") for tag in line.strip().split(" @") if len(tag) > 1}
STEP_PARAM_TEMPLATE = "<{param}>"
STEP_PARAM_RE = re.compile(STEP_PARAM_TEMPLATE.format(param="((?<=<)[^<>]+(?=>))"))
STEP_PARAM_RE = re.compile(r"\<(.+?)\>")

View File

@ -10,16 +10,15 @@ test_publish_article = scenario(
scenario_name="Publishing the article",
)
"""
import collections
import os
import re
from itertools import chain, product
import pytest
from _pytest.fixtures import FixtureLookupError
from . import exceptions
from .feature import get_feature, get_features
from .parser import STEP_PARAM_RE, STEP_PARAM_TEMPLATE
from .steps import get_step_fixture_name, inject_fixture
from .utils import CONFIG_STACK, get_args, get_caller_module_locals, get_caller_module_path
@ -27,61 +26,31 @@ PYTHON_REPLACE_REGEX = re.compile(r"\W")
ALPHA_REGEX = re.compile(r"^\d+_*")
def generate_partial_substituted_step_parameters(name: str, request):
"""\
Returns step name with substituted parameters from fixtures, example tables, param marks starting
from most substituted to less, so giving chance to most specific parser
"""
matches = re.finditer(STEP_PARAM_RE, name)
for match in matches:
param_name = match.group(1)
try:
sub_name = re.sub(
STEP_PARAM_TEMPLATE.format(param=re.escape(param_name)),
str(request.getfixturevalue(param_name)),
name,
count=1,
)
except FixtureLookupError:
continue
else:
yield from generate_partial_substituted_step_parameters(sub_name, request)
yield name
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
for fixturename, fixturedefs in list(fixturemanager._arg2fixturedefs.items()):
for fixturedef in fixturedefs:
parser = getattr(fixturedef.func, "parser", None)
if parser is None:
continue
match = parser.is_matching(name)
if not match:
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")
)
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)
)
for step_name, parser, converters in matched_steps_with_parsers:
if request:
for arg, value in parser.parse_arguments(step_name).items():
converters = getattr(fixturedef.func, "converters", {})
for arg, value in parser.parse_arguments(name).items():
if arg in converters:
value = converters[arg](value)
try:
overridable_fixture_value = request.getfixturevalue(arg)
except FixtureLookupError:
if request:
inject_fixture(request, arg, value)
else:
if overridable_fixture_value != value:
inject_fixture(request, arg, value)
return get_step_fixture_name(parser.name, type_)
parser_name = get_step_fixture_name(parser.name, type_)
if request:
try:
request.getfixturevalue(parser_name)
except FixtureLookupError:
continue
return parser_name
def _find_step_function(request, step, scenario):
@ -169,6 +138,9 @@ 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.

View File

@ -1,28 +1,25 @@
"""Scenario Outline tests."""
import textwrap
from pytest import mark
from tests.utils import assert_outcomes
FLOAT_NUMBER_PATTERN = r"[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?"
STEPS_TEMPLATE = """\
STEPS = """\
from pytest_bdd import given, when, then
from pytest_bdd.parsers import re
{given_decorator_definition}
@given("there are <start> cucumbers", target_fixture="start_cucumbers")
def start_cucumbers(start):
assert isinstance(start, int)
return dict(start=start)
{when_decorator_definition}
@when("I eat <eat> cucumbers")
def eat_cucumbers(start_cucumbers, eat):
assert isinstance(eat, float)
start_cucumbers["eat"] = eat
{then_decorator_definition}
@then("I should have <left> cucumbers")
def should_have_left_cucumbers(start_cucumbers, start, eat, left):
assert isinstance(left, str)
assert start - eat == int(left)
@ -32,31 +29,7 @@ def should_have_left_cucumbers(start_cucumbers, start, eat, left):
"""
STRING_STEPS = STEPS_TEMPLATE.format(
given_decorator_definition='@given("there are <start> cucumbers", target_fixture="start_cucumbers")',
when_decorator_definition='@when("I eat <eat> cucumbers")',
then_decorator_definition='@then("I should have <left> cucumbers")',
)
PARSER_STEPS = STEPS_TEMPLATE.format(
given_decorator_definition=f'@given(re("there are (?P<start>{FLOAT_NUMBER_PATTERN}) cucumbers"), '
f'target_fixture="start_cucumbers")',
when_decorator_definition=f'@when(re("I eat (?P<eat>{FLOAT_NUMBER_PATTERN}) cucumbers"))',
then_decorator_definition=f'@then(re("I should have (?P<left>{FLOAT_NUMBER_PATTERN}) cucumbers"))',
)
PARSER_STEPS_CONVERTED = STEPS_TEMPLATE.format(
given_decorator_definition=f'@given(re("there are (?P<start>{FLOAT_NUMBER_PATTERN}) cucumbers"), '
f'target_fixture="start_cucumbers", converters=dict(start=int))',
when_decorator_definition=f'@when(re("I eat (?P<eat>{FLOAT_NUMBER_PATTERN}) cucumbers"), '
f"converters=dict(eat=float))",
then_decorator_definition=f'@then(re("I should have (?P<left>{FLOAT_NUMBER_PATTERN}) cucumbers"), '
f"converters=dict(left=str))",
)
@mark.parametrize("steps", [STRING_STEPS, PARSER_STEPS_CONVERTED])
def test_outlined(testdir, steps):
def test_outlined(testdir):
testdir.makefile(
".feature",
outline=textwrap.dedent(
@ -76,7 +49,7 @@ def test_outlined(testdir, steps):
),
)
testdir.makeconftest(textwrap.dedent(steps))
testdir.makeconftest(textwrap.dedent(STEPS))
testdir.makepyfile(
textwrap.dedent(
@ -105,9 +78,8 @@ def test_outlined(testdir, steps):
result.assert_outcomes(passed=2)
@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."""
def test_wrongly_outlined(testdir):
"""Test parametrized scenario when the test function lacks parameters."""
testdir.makefile(
".feature",
@ -126,7 +98,7 @@ def test_outline_has_subset_of_parameters(testdir, steps):
"""
),
)
testdir.makeconftest(textwrap.dedent(steps))
testdir.makeconftest(textwrap.dedent(STEPS))
testdir.makepyfile(
textwrap.dedent(
@ -147,8 +119,7 @@ def test_outline_has_subset_of_parameters(testdir, steps):
result.stdout.fnmatch_lines("*should match set of example values [[]'eat', 'left', 'start', 'unknown_param'[]].*")
@mark.parametrize("steps", [STRING_STEPS, PARSER_STEPS])
def test_wrong_vertical_examples_scenario(testdir, steps):
def test_wrong_vertical_examples_scenario(testdir):
"""Test parametrized scenario vertical example table has wrong format."""
testdir.makefile(
".feature",
@ -167,7 +138,7 @@ def test_wrong_vertical_examples_scenario(testdir, steps):
"""
),
)
testdir.makeconftest(textwrap.dedent(steps))
testdir.makeconftest(textwrap.dedent(STEPS))
testdir.makepyfile(
textwrap.dedent(
@ -188,8 +159,7 @@ def test_wrong_vertical_examples_scenario(testdir, steps):
)
@mark.parametrize("steps", [STRING_STEPS, PARSER_STEPS])
def test_wrong_vertical_examples_feature(testdir, steps):
def test_wrong_vertical_examples_feature(testdir):
"""Test parametrized feature vertical example table has wrong format."""
testdir.makefile(
".feature",
@ -209,7 +179,7 @@ def test_wrong_vertical_examples_feature(testdir, steps):
"""
),
)
testdir.makeconftest(textwrap.dedent(steps))
testdir.makeconftest(textwrap.dedent(STEPS))
testdir.makepyfile(
textwrap.dedent(
@ -230,8 +200,7 @@ def test_wrong_vertical_examples_feature(testdir, steps):
)
@mark.parametrize("steps", [STRING_STEPS, PARSER_STEPS_CONVERTED])
def test_outlined_with_other_fixtures(testdir, steps):
def test_outlined_with_other_fixtures(testdir):
"""Test outlined scenario also using other parametrized fixture."""
testdir.makefile(
".feature",
@ -252,7 +221,7 @@ def test_outlined_with_other_fixtures(testdir, steps):
),
)
testdir.makeconftest(textwrap.dedent(steps))
testdir.makeconftest(textwrap.dedent(STEPS))
testdir.makepyfile(
textwrap.dedent(
@ -282,8 +251,7 @@ def test_outlined_with_other_fixtures(testdir, steps):
result.assert_outcomes(passed=6)
@mark.parametrize("steps", [STRING_STEPS, PARSER_STEPS_CONVERTED])
def test_vertical_example(testdir, steps):
def test_vertical_example(testdir):
"""Test outlined scenario with vertical examples table."""
testdir.makefile(
".feature",
@ -304,7 +272,7 @@ def test_vertical_example(testdir, steps):
),
)
testdir.makeconftest(textwrap.dedent(steps))
testdir.makeconftest(textwrap.dedent(STEPS))
testdir.makepyfile(
textwrap.dedent(
@ -333,62 +301,6 @@ def test_vertical_example(testdir, steps):
result.assert_outcomes(passed=2)
def test_outlined_paramaters_parsed_indirectly(testdir):
testdir.makefile(
".feature",
outline=textwrap.dedent(
"""\
Feature: Outline
Examples:
| first | consume | remaining |
| 12 | 5 | 7 |
| 5 | 4 | 1 |
Scenario Outline: Outlined modern given, when, thens
Given there were <first> <foods>
When I ate <consume> <foods>
Then I should have had <remaining> <foods>
Examples:
| foods |
| ice-creams |
| almonds |
"""
),
)
testdir.makepyfile(
textwrap.dedent(
"""\
from pytest_bdd import scenarios, given, when, then
from pytest_bdd.parsers import parse
@given(parse('there were {start:d} {fruits}'), target_fixture='context')
def started_fruits(start, fruits):
assert isinstance(start, int)
return {fruits: dict(start=start)}
@when(parse('I ate {eat:g} {fruits}'))
def ate_fruits(start, eat, fruits, context):
assert isinstance(eat, float)
context[fruits]['eat'] = eat
@then(parse('I should have had {left} {fruits}'))
def should_have_had_left_fruits(start, eat, left, fruits, context):
assert isinstance(left, str)
assert start - eat == int(left)
assert context[fruits]['start'] == start
assert context[fruits]['eat'] == eat
scenarios('outline.feature')
"""
)
)
result = testdir.runpytest()
result.assert_outcomes(passed=4)
def test_outlined_feature(testdir):
testdir.makefile(
".feature",

View File

@ -59,54 +59,3 @@ def test_parametrized(testdir):
)
result = testdir.runpytest()
result.assert_outcomes(passed=3)
def test_parametrized_with_parsers(testdir):
"""Test parametrized scenario."""
testdir.makefile(
".feature",
parametrized=textwrap.dedent(
"""\
Feature: Parametrized scenario
Scenario: Parametrized given, when, thens with parsers invocation
Given there are <start> gherkins
When I eat <eat> gherkins
Then I should have <left> gherkins
"""
),
)
testdir.makepyfile(
textwrap.dedent(
"""\
import pytest
from pytest_bdd import given, when, then, scenario
from pytest_bdd.parsers import re, parse
@pytest.mark.parametrize(["start", "eat", "left"], [(12, 5, 7)])
@scenario("parametrized.feature", "Parametrized given, when, thens with parsers invocation")
def test_parametrized(request, start, eat, left):
pass
@given(re("there are <start> (?P<vegetables>\\\\w+)"), target_fixture="start_vegetables")
def start_vegetables(start, vegetables):
return dict(start=start)
@when("I eat <eat> gherkins")
def eat_cucumbers(start_vegetables, start, eat):
start_vegetables["eat"] = eat
@then(re("I should have (?P<left>\\\\d+) (?P<vegetables>\\\\w+)"), converters=dict(left=int))
def should_have_left_vegetables(start_vegetables, start, eat, left, vegetables):
assert start - eat == left
assert start_vegetables["start"] == start
assert start_vegetables["eat"] == eat
"""
)
)
result = testdir.runpytest()
result.assert_outcomes(passed=1)