Revert "Allows use <params> in parsers defined steps"
This reverts commit 7850f218b7
.
This commit is contained in:
parent
4f1a0f8707
commit
f75a57b22b
|
@ -1,3 +1,4 @@
|
||||||
|
import io
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
import textwrap
|
import textwrap
|
||||||
|
@ -456,5 +457,4 @@ def get_tags(line):
|
||||||
return {tag.lstrip("@") for tag in line.strip().split(" @") if len(tag) > 1}
|
return {tag.lstrip("@") for tag in line.strip().split(" @") if len(tag) > 1}
|
||||||
|
|
||||||
|
|
||||||
STEP_PARAM_TEMPLATE = "<{param}>"
|
STEP_PARAM_RE = re.compile(r"\<(.+?)\>")
|
||||||
STEP_PARAM_RE = re.compile(STEP_PARAM_TEMPLATE.format(param="((?<=<)[^<>]+(?=>))"))
|
|
||||||
|
|
|
@ -10,16 +10,15 @@ test_publish_article = scenario(
|
||||||
scenario_name="Publishing the article",
|
scenario_name="Publishing the article",
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
|
import collections
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from itertools import chain, product
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.fixtures import FixtureLookupError
|
from _pytest.fixtures import FixtureLookupError
|
||||||
|
|
||||||
from . import exceptions
|
from . import exceptions
|
||||||
from .feature import get_feature, get_features
|
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 .steps import get_step_fixture_name, inject_fixture
|
||||||
from .utils import CONFIG_STACK, get_args, get_caller_module_locals, get_caller_module_path
|
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+_*")
|
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):
|
def find_argumented_step_fixture_name(name, type_, fixturemanager, request=None):
|
||||||
"""Find argumented step fixture name."""
|
"""Find argumented step fixture name."""
|
||||||
# happens to be that _arg2fixturedefs is changed during the iteration so we use a copy
|
# 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()))
|
converters = getattr(fixturedef.func, "converters", {})
|
||||||
fixturedef_funcs = (fixturedef.func for fixturedef in fixturedefs)
|
for arg, value in parser.parse_arguments(name).items():
|
||||||
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():
|
|
||||||
if arg in converters:
|
if arg in converters:
|
||||||
value = converters[arg](value)
|
value = converters[arg](value)
|
||||||
try:
|
if request:
|
||||||
overridable_fixture_value = request.getfixturevalue(arg)
|
|
||||||
except FixtureLookupError:
|
|
||||||
inject_fixture(request, arg, value)
|
inject_fixture(request, arg, value)
|
||||||
else:
|
parser_name = get_step_fixture_name(parser.name, type_)
|
||||||
if overridable_fixture_value != value:
|
if request:
|
||||||
inject_fixture(request, arg, value)
|
try:
|
||||||
return get_step_fixture_name(parser.name, type_)
|
request.getfixturevalue(parser_name)
|
||||||
|
except FixtureLookupError:
|
||||||
|
continue
|
||||||
|
return parser_name
|
||||||
|
|
||||||
|
|
||||||
def _find_step_function(request, step, scenario):
|
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)
|
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):
|
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
|
# HACK: Ideally we would use `def decorator(fn)`, but we want to return a custom exception
|
||||||
# when the decorator is misused.
|
# when the decorator is misused.
|
||||||
|
|
|
@ -1,28 +1,25 @@
|
||||||
"""Scenario Outline tests."""
|
"""Scenario Outline tests."""
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
from pytest import mark
|
|
||||||
|
|
||||||
from tests.utils import assert_outcomes
|
from tests.utils import assert_outcomes
|
||||||
|
|
||||||
FLOAT_NUMBER_PATTERN = r"[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?"
|
STEPS = """\
|
||||||
STEPS_TEMPLATE = """\
|
|
||||||
from pytest_bdd import given, when, then
|
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):
|
def start_cucumbers(start):
|
||||||
assert isinstance(start, int)
|
assert isinstance(start, int)
|
||||||
return dict(start=start)
|
return dict(start=start)
|
||||||
|
|
||||||
|
|
||||||
{when_decorator_definition}
|
@when("I eat <eat> cucumbers")
|
||||||
def eat_cucumbers(start_cucumbers, eat):
|
def eat_cucumbers(start_cucumbers, eat):
|
||||||
assert isinstance(eat, float)
|
assert isinstance(eat, float)
|
||||||
start_cucumbers["eat"] = eat
|
start_cucumbers["eat"] = eat
|
||||||
|
|
||||||
|
|
||||||
{then_decorator_definition}
|
@then("I should have <left> cucumbers")
|
||||||
def should_have_left_cucumbers(start_cucumbers, start, eat, left):
|
def should_have_left_cucumbers(start_cucumbers, start, eat, left):
|
||||||
assert isinstance(left, str)
|
assert isinstance(left, str)
|
||||||
assert start - eat == int(left)
|
assert start - eat == int(left)
|
||||||
|
@ -32,31 +29,7 @@ def should_have_left_cucumbers(start_cucumbers, start, eat, left):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
STRING_STEPS = STEPS_TEMPLATE.format(
|
def test_outlined(testdir):
|
||||||
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):
|
|
||||||
testdir.makefile(
|
testdir.makefile(
|
||||||
".feature",
|
".feature",
|
||||||
outline=textwrap.dedent(
|
outline=textwrap.dedent(
|
||||||
|
@ -76,7 +49,7 @@ def test_outlined(testdir, steps):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
testdir.makeconftest(textwrap.dedent(steps))
|
testdir.makeconftest(textwrap.dedent(STEPS))
|
||||||
|
|
||||||
testdir.makepyfile(
|
testdir.makepyfile(
|
||||||
textwrap.dedent(
|
textwrap.dedent(
|
||||||
|
@ -105,9 +78,8 @@ def test_outlined(testdir, steps):
|
||||||
result.assert_outcomes(passed=2)
|
result.assert_outcomes(passed=2)
|
||||||
|
|
||||||
|
|
||||||
@mark.parametrize("steps", [STRING_STEPS, PARSER_STEPS_CONVERTED])
|
def test_wrongly_outlined(testdir):
|
||||||
def test_outline_has_subset_of_parameters(testdir, steps):
|
"""Test parametrized scenario when the test function lacks parameters."""
|
||||||
"""Test parametrized scenario when the test function has a subset of the parameters of the examples."""
|
|
||||||
|
|
||||||
testdir.makefile(
|
testdir.makefile(
|
||||||
".feature",
|
".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(
|
testdir.makepyfile(
|
||||||
textwrap.dedent(
|
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'[]].*")
|
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):
|
||||||
def test_wrong_vertical_examples_scenario(testdir, steps):
|
|
||||||
"""Test parametrized scenario vertical example table has wrong format."""
|
"""Test parametrized scenario vertical example table has wrong format."""
|
||||||
testdir.makefile(
|
testdir.makefile(
|
||||||
".feature",
|
".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(
|
testdir.makepyfile(
|
||||||
textwrap.dedent(
|
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):
|
||||||
def test_wrong_vertical_examples_feature(testdir, steps):
|
|
||||||
"""Test parametrized feature vertical example table has wrong format."""
|
"""Test parametrized feature vertical example table has wrong format."""
|
||||||
testdir.makefile(
|
testdir.makefile(
|
||||||
".feature",
|
".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(
|
testdir.makepyfile(
|
||||||
textwrap.dedent(
|
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):
|
||||||
def test_outlined_with_other_fixtures(testdir, steps):
|
|
||||||
"""Test outlined scenario also using other parametrized fixture."""
|
"""Test outlined scenario also using other parametrized fixture."""
|
||||||
testdir.makefile(
|
testdir.makefile(
|
||||||
".feature",
|
".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(
|
testdir.makepyfile(
|
||||||
textwrap.dedent(
|
textwrap.dedent(
|
||||||
|
@ -282,8 +251,7 @@ def test_outlined_with_other_fixtures(testdir, steps):
|
||||||
result.assert_outcomes(passed=6)
|
result.assert_outcomes(passed=6)
|
||||||
|
|
||||||
|
|
||||||
@mark.parametrize("steps", [STRING_STEPS, PARSER_STEPS_CONVERTED])
|
def test_vertical_example(testdir):
|
||||||
def test_vertical_example(testdir, steps):
|
|
||||||
"""Test outlined scenario with vertical examples table."""
|
"""Test outlined scenario with vertical examples table."""
|
||||||
testdir.makefile(
|
testdir.makefile(
|
||||||
".feature",
|
".feature",
|
||||||
|
@ -304,7 +272,7 @@ def test_vertical_example(testdir, steps):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
testdir.makeconftest(textwrap.dedent(steps))
|
testdir.makeconftest(textwrap.dedent(STEPS))
|
||||||
|
|
||||||
testdir.makepyfile(
|
testdir.makepyfile(
|
||||||
textwrap.dedent(
|
textwrap.dedent(
|
||||||
|
@ -333,62 +301,6 @@ def test_vertical_example(testdir, steps):
|
||||||
result.assert_outcomes(passed=2)
|
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):
|
def test_outlined_feature(testdir):
|
||||||
testdir.makefile(
|
testdir.makefile(
|
||||||
".feature",
|
".feature",
|
||||||
|
|
|
@ -59,54 +59,3 @@ def test_parametrized(testdir):
|
||||||
)
|
)
|
||||||
result = testdir.runpytest()
|
result = testdir.runpytest()
|
||||||
result.assert_outcomes(passed=3)
|
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)
|
|
||||||
|
|
Loading…
Reference in New Issue