From 8b5a87aeadef7173b172af133b8b7064a23f61c2 Mon Sep 17 00:00:00 2001 From: Kostiantyn Goloveshko Date: Sat, 8 Jan 2022 15:28:06 +0200 Subject: [PATCH 01/11] Document examples union --- README.rst | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.rst b/README.rst index 2a35b437f..442efd3a0 100644 --- a/README.rst +++ b/README.rst @@ -637,6 +637,33 @@ This is allowed as long as parameter names do not clash: | carrots | | tomatoes | +To not repeat steps as in example above you could want store your data in sequent Examples sections: + + +.. code-block:: gherkin + + Feature: Outline + + Examples: + | start | eat | left | + | 12 | 5 | 7 | + | 5 | 4 | 1 | + + Scenario Outline: Eat food + Given there are + When I eat + Then I should have + + Examples: Fruits + | food | + | oranges | + | apples | + + Examples: Vegetables + | food | + | carrots | + | tomatoes | + Organizing your scenarios ------------------------- From 8471d182cfd41f4e2a0e0df86dc18ace44781ae8 Mon Sep 17 00:00:00 2001 From: Kostiantyn Goloveshko Date: Sat, 8 Jan 2022 12:47:37 +0200 Subject: [PATCH 02/11] Reorder test outline vertical --- tests/feature/test_outline.py | 160 +++++++++++++++++----------------- 1 file changed, 80 insertions(+), 80 deletions(-) diff --git a/tests/feature/test_outline.py b/tests/feature/test_outline.py index 3628db8d7..168a6a3c6 100644 --- a/tests/feature/test_outline.py +++ b/tests/feature/test_outline.py @@ -122,66 +122,76 @@ def test_outline(request): result.stdout.fnmatch_lines("*should match set of example values [[]'eat', 'left', 'start', 'unknown_param'[]].*") -def test_wrong_vertical_examples_scenario(testdir): - """Test parametrized scenario vertical example table has wrong format.""" +def test_outlined_with_other_fixtures(testdir): + """Test outlined scenario also using other parametrized fixture.""" testdir.makefile( ".feature", outline=textwrap.dedent( """\ Feature: Outline - Scenario Outline: Outlined with wrong vertical example table + Scenario Outline: Outlined given, when, thens Given there are cucumbers When I eat cucumbers Then I should have cucumbers - Examples: Vertical - | start | 12 | 2 | - | start | 10 | 1 | - | left | 7 | 1 | + Examples: + | start | eat | left | + | 12 | 5 | 7 | + | 5 | 4 | 1 | + """ ), ) + testdir.makeconftest(textwrap.dedent(STEPS)) testdir.makepyfile( textwrap.dedent( """\ + import pytest from pytest_bdd import scenario - @scenario("outline.feature", "Outlined with wrong vertical example table") - def test_outline(request): + + @pytest.fixture(params=[1, 2, 3]) + def other_fixture(request): + return request.param + + + @scenario( + "outline.feature", + "Outlined given, when, thens", + ) + def test_outline(other_fixture): pass + """ ) ) result = testdir.runpytest() - assert_outcomes(result, errors=1) - result.stdout.fnmatch_lines( - "*Scenario has not valid examples. Example rows should contain unique parameters. " - '"start" appeared more than once.*' - ) + result.assert_outcomes(passed=6) -def test_wrong_vertical_examples_feature(testdir): - """Test parametrized feature vertical example table has wrong format.""" +def test_vertical_example(testdir): + """Test outlined scenario with vertical examples table.""" testdir.makefile( ".feature", outline=textwrap.dedent( """\ - Feature: Outlines - - Examples: Vertical - | start | 12 | 2 | - | start | 10 | 1 | - | left | 7 | 1 | - - Scenario Outline: Outlined with wrong vertical example table + Feature: Outline + Scenario Outline: Outlined with vertical example table Given there are cucumbers When I eat cucumbers Then I should have cucumbers + + Examples: Vertical + | start | 12 | 2 | + | eat | 5 | 1 | + | left | 7 | 1 | + """ ), ) + testdir.makeconftest(textwrap.dedent(STEPS)) testdir.makepyfile( @@ -189,90 +199,86 @@ def test_wrong_vertical_examples_feature(testdir): """\ from pytest_bdd import scenario - @scenario("outline.feature", "Outlined with wrong vertical example table") - def test_outline(request): + @scenario( + "outline.feature", + "Outlined with vertical example table", + ) + def test_outline(): pass """ ) ) - result = testdir.runpytest() - assert_outcomes(result, errors=1) - result.stdout.fnmatch_lines( - "*Feature has not valid examples. Example rows should contain unique parameters. " - '"start" appeared more than once.*' - ) + result = testdir.runpytest("-s") + result.assert_outcomes(passed=2) + parametrizations = collect_dumped_objects(result) + # fmt: off + assert parametrizations == [ + 12, 5.0, "7", + 2, 1.0, "1", + ] + # fmt: on -def test_outlined_with_other_fixtures(testdir): - """Test outlined scenario also using other parametrized fixture.""" +def test_wrong_vertical_examples_scenario(testdir): + """Test parametrized scenario vertical example table has wrong format.""" testdir.makefile( ".feature", outline=textwrap.dedent( """\ Feature: Outline - Scenario Outline: Outlined given, when, thens + Scenario Outline: Outlined with wrong vertical example table Given there are cucumbers When I eat cucumbers Then I should have cucumbers - Examples: - | start | eat | left | - | 12 | 5 | 7 | - | 5 | 4 | 1 | - + Examples: Vertical + | start | 12 | 2 | + | start | 10 | 1 | + | left | 7 | 1 | """ ), ) - testdir.makeconftest(textwrap.dedent(STEPS)) testdir.makepyfile( textwrap.dedent( """\ - import pytest from pytest_bdd import scenario - - @pytest.fixture(params=[1, 2, 3]) - def other_fixture(request): - return request.param - - - @scenario( - "outline.feature", - "Outlined given, when, thens", - ) - def test_outline(other_fixture): + @scenario("outline.feature", "Outlined with wrong vertical example table") + def test_outline(request): pass - """ ) ) result = testdir.runpytest() - result.assert_outcomes(passed=6) + assert_outcomes(result, errors=1) + result.stdout.fnmatch_lines( + "*Scenario has not valid examples. Example rows should contain unique parameters. " + '"start" appeared more than once.*' + ) -def test_vertical_example(testdir): - """Test outlined scenario with vertical examples table.""" +def test_wrong_vertical_examples_feature(testdir): + """Test parametrized feature vertical example table has wrong format.""" testdir.makefile( ".feature", outline=textwrap.dedent( """\ - Feature: Outline - Scenario Outline: Outlined with vertical example table + Feature: Outlines + + Examples: Vertical + | start | 12 | 2 | + | start | 10 | 1 | + | left | 7 | 1 | + + Scenario Outline: Outlined with wrong vertical example table Given there are cucumbers When I eat cucumbers Then I should have cucumbers - - Examples: Vertical - | start | 12 | 2 | - | eat | 5 | 1 | - | left | 7 | 1 | - """ ), ) - testdir.makeconftest(textwrap.dedent(STEPS)) testdir.makepyfile( @@ -280,24 +286,18 @@ def test_vertical_example(testdir): """\ from pytest_bdd import scenario - @scenario( - "outline.feature", - "Outlined with vertical example table", - ) - def test_outline(): + @scenario("outline.feature", "Outlined with wrong vertical example table") + def test_outline(request): pass """ ) ) - result = testdir.runpytest("-s") - result.assert_outcomes(passed=2) - parametrizations = collect_dumped_objects(result) - # fmt: off - assert parametrizations == [ - 12, 5.0, "7", - 2, 1.0, "1", - ] - # fmt: on + result = testdir.runpytest() + assert_outcomes(result, errors=1) + result.stdout.fnmatch_lines( + "*Feature has not valid examples. Example rows should contain unique parameters. " + '"start" appeared more than once.*' + ) def test_outlined_feature(testdir): From 6dafe2f573676e6d856d0b29c28ccb78dd218bb5 Mon Sep 17 00:00:00 2001 From: Kostiantyn Goloveshko Date: Sat, 8 Jan 2022 14:42:55 +0200 Subject: [PATCH 03/11] Outlined scenarios tests #479 --- tests/feature/test_outline.py | 297 ++++++++++++++++++++++++++++------ 1 file changed, 246 insertions(+), 51 deletions(-) diff --git a/tests/feature/test_outline.py b/tests/feature/test_outline.py index 168a6a3c6..8b3eb7e4c 100644 --- a/tests/feature/test_outline.py +++ b/tests/feature/test_outline.py @@ -1,6 +1,8 @@ """Scenario Outline tests.""" import textwrap +import pytest + from pytest_bdd.utils import collect_dumped_objects from tests.utils import assert_outcomes @@ -32,20 +34,63 @@ def should_have_left_cucumbers(start_cucumbers, start, eat, left): assert start_cucumbers["eat"] == eat """ +STEPS_OUTLINED = """\ +from pytest_bdd import given, when, then, scenario, parsers +from pytest_bdd.utils import dump_obj + +@scenario( + "outline.feature", + "Outlined given, when, thens", +) +def test_outline(): + pass + +@given(parsers.parse("there are {start:d} {fruits}"), target_fixture="start_fruits") +def start_fruits(start, fruits): + dump_obj(start, fruits) + + assert isinstance(start, int) + return {fruits: dict(start=start)} + + +@when(parsers.parse("I eat {eat:g} {fruits}")) +def eat_fruits(start_fruits, eat, fruits): + dump_obj(eat, fruits) + + assert isinstance(eat, float) + start_fruits[fruits]["eat"] = eat + + +@then(parsers.parse("I should have {left} {fruits}")) +def should_have_left_fruits(start_fruits, start, eat, left, fruits): + dump_obj(left, fruits) + + assert isinstance(left, str) + assert start - eat == int(left) + assert start_fruits[fruits]["start"] == start + assert start_fruits[fruits]["eat"] == eat +""" -def test_outlined(testdir): +@pytest.mark.parametrize( + "examples_header", + ( + pytest.param("Examples:", id="non_named"), + pytest.param("Examples: Named", id="named"), + ), +) +def test_outlined(testdir, examples_header): testdir.makefile( ".feature", outline=textwrap.dedent( - """\ + f"""\ Feature: Outline Scenario Outline: Outlined given, when, thens Given there are cucumbers When I eat cucumbers Then I should have cucumbers - Examples: + {examples_header} | start | eat | left | | 12 | 5 | 7 | # a comment | 5 | 4 | 1 | @@ -81,7 +126,7 @@ def test_outline(request): # fmt: on -def test_wrongly_outlined(testdir): +def test_wrongly_outlined_extra_parameter(testdir): """Test parametrized scenario when the test function lacks parameters.""" testdir.makefile( @@ -171,20 +216,27 @@ def test_outline(other_fixture): result.assert_outcomes(passed=6) -def test_vertical_example(testdir): +@pytest.mark.parametrize( + "examples_header", + ( + pytest.param("Examples: Vertical", id="non_named"), + pytest.param("Examples: Vertical Named", id="named"), + ), +) +def test_vertical_example(testdir, examples_header): """Test outlined scenario with vertical examples table.""" testdir.makefile( ".feature", outline=textwrap.dedent( - """\ + f"""\ Feature: Outline Scenario Outline: Outlined with vertical example table Given there are cucumbers When I eat cucumbers Then I should have cucumbers - Examples: Vertical - | start | 12 | 2 | + {examples_header} + | start | 12 | 2 | # a comment | eat | 5 | 1 | | left | 7 | 1 | @@ -219,7 +271,49 @@ def test_outline(): # fmt: on -def test_wrong_vertical_examples_scenario(testdir): +def test_wrongly_outlined_extra_parameter_vertical(testdir): + """Test parametrized scenario when the test function lacks parameters.""" + + testdir.makefile( + ".feature", + outline=textwrap.dedent( + """\ + Feature: Outline + Scenario Outline: Outlined with wrong examples + Given there are cucumbers + When I eat cucumbers + Then I should have cucumbers + + Examples: Vertical + | start | 12 | + | eat | 5 | + | left | 7 | + | unknown_param | value | + """ + ), + ) + testdir.makeconftest(textwrap.dedent(STEPS)) + + testdir.makepyfile( + textwrap.dedent( + """\ + from pytest_bdd import scenario + + @scenario("outline.feature", "Outlined with wrong examples") + def test_outline(request): + pass + """ + ) + ) + result = testdir.runpytest() + assert_outcomes(result, errors=1) + result.stdout.fnmatch_lines( + '*ScenarioExamplesNotValidError: Scenario "Outlined with wrong examples"*has not valid examples*', + ) + result.stdout.fnmatch_lines("*should match set of example values [[]'eat', 'left', 'start', 'unknown_param'[]].*") + + +def test_wrongly_outlined_duplicated_parameter_vertical_scenario(testdir): """Test parametrized scenario vertical example table has wrong format.""" testdir.makefile( ".feature", @@ -259,7 +353,7 @@ def test_outline(request): ) -def test_wrong_vertical_examples_feature(testdir): +def test_wrongly_outlined_duplicated_parameter_vertical_feature(testdir): """Test parametrized feature vertical example table has wrong format.""" testdir.makefile( ".feature", @@ -325,47 +419,7 @@ def test_outlined_feature(testdir): ), ) - testdir.makepyfile( - textwrap.dedent( - """\ - from pytest_bdd import given, when, then, scenario, parsers - from pytest_bdd.utils import dump_obj - - @scenario( - "outline.feature", - "Outlined given, when, thens", - ) - def test_outline(): - pass - - @given(parsers.parse("there are {start:d} {fruits}"), target_fixture="start_fruits") - def start_fruits(start, fruits): - dump_obj(start, fruits) - - assert isinstance(start, int) - return {fruits: dict(start=start)} - - - @when(parsers.parse("I eat {eat:g} {fruits}")) - def eat_fruits(start_fruits, eat, fruits): - dump_obj(eat, fruits) - - assert isinstance(eat, float) - start_fruits[fruits]["eat"] = eat - - - @then(parsers.parse("I should have {left} {fruits}")) - def should_have_left_fruits(start_fruits, start, eat, left, fruits): - dump_obj(left, fruits) - - assert isinstance(left, str) - assert start - eat == int(left) - assert start_fruits[fruits]["start"] == start - assert start_fruits[fruits]["eat"] == eat - - """ - ) - ) + testdir.makepyfile(STEPS_OUTLINED) result = testdir.runpytest("-s") result.assert_outcomes(passed=4) parametrizations = collect_dumped_objects(result) @@ -433,3 +487,144 @@ def i_print_the_string(string): r"bork \\", r"bork \\|", ] + + +@pytest.mark.xfail(reason="https://github.com/pytest-dev/pytest-bdd/issues/479") +def test_multi_outlined(testdir): + testdir.makefile( + ".feature", + outline=textwrap.dedent( + """\ + Feature: Outline + Scenario Outline: Outlined given, when, thens + Given there are cucumbers + When I eat cucumbers + Then I should have cucumbers + + Examples: + | start | eat | left | + | 12 | 5 | 7 | # a comment + + Examples: Vertical + | start | 5 | + | eat | 4 | + | left | 1 | + """ + ), + ) + + testdir.makeconftest(textwrap.dedent(STEPS)) + + testdir.makepyfile( + textwrap.dedent( + """\ + from pytest_bdd import scenario + + @scenario( + "outline.feature", + "Outlined given, when, thens", + ) + def test_outline(request): + pass + + """ + ) + ) + result = testdir.runpytest("-s") + result.assert_outcomes(passed=2) + # fmt: off + assert collect_dumped_objects(result) == [ + 12, 5.0, "7", + 5, 4.0, "1", + ] + # fmt: on + + +@pytest.mark.xfail(reason="https://github.com/pytest-dev/pytest-bdd/issues/479") +def test_multi_outlined_feature_with_parameter_union(testdir): + testdir.makefile( + ".feature", + outline=textwrap.dedent( + """\ + Feature: Outline + + Examples: + | start | eat | left | + | 12 | 5 | 7 | + + Examples: Vertical + | start | 5 | + | eat | 4 | + | left | 1 | + + Scenario Outline: Outlined given, when, thens + Given there are + When I eat + Then I should have + + Examples: + | fruits | + | oranges | + | apples | + """ + ), + ) + + testdir.makepyfile(STEPS_OUTLINED) + result = testdir.runpytest("-s") + result.assert_outcomes(passed=4) + parametrizations = collect_dumped_objects(result) + # fmt: off + assert parametrizations == [ + 12, "oranges", 5.0, "oranges", "7", "oranges", + 12, "apples", 5.0, "apples", "7", "apples", + 5, "oranges", 4.0, "oranges", "1", "oranges", + 5, "apples", 4.0, "apples", "1", "apples", + ] + # fmt: on + + +@pytest.mark.xfail(reason="https://github.com/pytest-dev/pytest-bdd/issues/479") +def test_multi_outlined_scenario_and_feature_with_parameter_union(testdir): + testdir.makefile( + ".feature", + outline=textwrap.dedent( + """\ + Feature: Outline + + Examples: + | start | eat | left | + | 12 | 5 | 7 | + + Examples: Vertical + | start | 5 | + | eat | 4 | + | left | 1 | + + Scenario Outline: Outlined given, when, thens + Given there are + When I eat + Then I should have + + Examples: Vertical + | fruits | oranges | + + Examples: + | fruits | + | apples | + """ + ), + ) + + testdir.makepyfile(STEPS_OUTLINED) + result = testdir.runpytest("-s") + result.assert_outcomes(passed=4) + parametrizations = collect_dumped_objects(result) + # fmt: off + assert parametrizations == [ + 12, "oranges", 5.0, "oranges", "7", "oranges", + 12, "apples", 5.0, "apples", "7", "apples", + 5, "oranges", 4.0, "oranges", "1", "oranges", + 5, "apples", 4.0, "apples", "1", "apples", + ] + # fmt: on From 2193e041ae8689209d9aecf62fd84fe5a05e3149 Mon Sep 17 00:00:00 2001 From: Kostiantyn Goloveshko Date: Sat, 8 Jan 2022 17:25:15 +0200 Subject: [PATCH 04/11] Fix Examples union #479 --- pytest_bdd/parser.py | 178 ++++++++++++++++------------ pytest_bdd/scenario.py | 27 +---- requirements-testing.txt | 2 + setup.cfg | 1 + tests/feature/test_cucumber_json.py | 4 +- tests/feature/test_outline.py | 3 - tests/feature/test_report.py | 6 +- 7 files changed, 114 insertions(+), 107 deletions(-) diff --git a/pytest_bdd/parser.py b/pytest_bdd/parser.py index 8cbfbb145..8fa4768ef 100644 --- a/pytest_bdd/parser.py +++ b/pytest_bdd/parser.py @@ -3,9 +3,16 @@ import textwrap import typing from collections import OrderedDict +from itertools import chain, product, zip_longest +from typing import List + +import pytest +from attr import attrs, attrib, Factory, validate from . import exceptions, types +if typing.TYPE_CHECKING: + from _pytest.mark import ParameterSet SPLIT_LINE_RE = re.compile(r"(?") COMMENT_RE = re.compile(r"(^|(?<=\s))#") @@ -83,7 +90,9 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> "Feat """ abs_filename = os.path.abspath(os.path.join(basedir, filename)) rel_filename = os.path.join(os.path.basename(basedir), filename) - feature = Feature( + build_node: typing.Union[Feature, ScenarioTemplate] + build_example_table: typing.Optional[ExampleTable] = None + feature = build_node = Feature( scenarios=OrderedDict(), filename=abs_filename, rel_filename=rel_filename, @@ -150,41 +159,41 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> "Feat keyword, parsed_line = parse_line(clean_line) if mode in [types.SCENARIO, types.SCENARIO_OUTLINE]: tags = get_tags(prev_line) - feature.scenarios[parsed_line] = scenario = ScenarioTemplate( + feature.scenarios[parsed_line] = scenario = build_node = ScenarioTemplate( feature=feature, name=parsed_line, line_number=line_number, tags=tags ) elif mode == types.BACKGROUND: feature.background = Background(feature=feature, line_number=line_number) - elif mode == types.EXAMPLES: - mode = types.EXAMPLES_HEADERS - (scenario or feature).examples.line_number = line_number - elif mode == types.EXAMPLES_VERTICAL: - mode = types.EXAMPLE_LINE_VERTICAL - (scenario or feature).examples.line_number = line_number + elif mode in [types.EXAMPLES, types.EXAMPLES_VERTICAL]: + if mode == types.EXAMPLES: + mode, ExampleTableBuilder = types.EXAMPLES_HEADERS, ExampleTableColumns + else: + mode, ExampleTableBuilder = types.EXAMPLE_LINE_VERTICAL, ExampleTableRows + _, table_name = parse_line(clean_line) + build_example_table = ExampleTableBuilder(name=table_name or None, line_number=line_number) + build_node.examples.example_tables.append(build_example_table) elif mode == types.EXAMPLES_HEADERS: - (scenario or feature).examples.set_param_names([l for l in split_line(parsed_line) if l]) mode = types.EXAMPLE_LINE + build_example_table.example_params = [l for l in split_line(parsed_line) if l] elif mode == types.EXAMPLE_LINE: - (scenario or feature).examples.add_example([l for l in split_line(stripped_line)]) + try: + build_example_table.examples += [[*split_line(stripped_line)]] + except exceptions.ExamplesNotValidError as exc: + node_message_prefix = "Scenario" if scenario else "Feature" + message = f"{node_message_prefix} has not valid examples. {exc.args[0]}" + raise exceptions.FeatureError(message, line_number, clean_line, filename) from exc elif mode == types.EXAMPLE_LINE_VERTICAL: - param_line_parts = [l for l in split_line(stripped_line)] + param, *examples = split_line(stripped_line) try: - (scenario or feature).examples.add_example_row(param_line_parts[0], param_line_parts[1:]) + build_example_table: ExampleTableRows + build_example_table.example_params += [param] + build_example_table.examples_transposed += [examples] + validate(build_example_table) except exceptions.ExamplesNotValidError as exc: - if scenario: - raise exceptions.FeatureError( - f"Scenario has not valid examples. {exc.args[0]}", - line_number, - clean_line, - filename, - ) - else: - raise exceptions.FeatureError( - f"Feature has not valid examples. {exc.args[0]}", - line_number, - clean_line, - filename, - ) + message = "{node} has not valid examples. {original_message}".format( + node="Scenario" if scenario else "Feature", original_message=exc.args[0] + ) + raise exceptions.FeatureError(message, line_number, clean_line, filename) from exc elif mode and mode not in (types.FEATURE, types.TAG): step = Step(name=parsed_line, type=mode, indent=line_indent, line_number=line_number, keyword=keyword) if feature.background and not scenario: @@ -258,13 +267,25 @@ def render(self, context: typing.Mapping[str, typing.Any]) -> "Scenario": ] return Scenario(feature=self.feature, name=self.name, line_number=self.line_number, steps=steps, tags=self.tags) + @property + def example_parametrizations(self) -> "typing.Optional[typing.List[ParameterSet]]": + feature_scenario_examples_combinations = product(self.feature.examples, self.examples) + + def examples(): + for feature_examples, scenario_examples in feature_scenario_examples_combinations: + result = {**feature_examples, **scenario_examples} + if result != {}: + yield result + + return [pytest.param(example, id="-".join(example.values())) for example in examples()] + def validate(self): """Validate the scenario. :raises ScenarioValidationError: when scenario is not valid """ params = frozenset(sum((list(step.params) for step in self.steps), [])) - example_params = set(self.examples.example_params + self.feature.examples.example_params) + example_params = set(self.examples.example_params).union(self.feature.examples.example_params) if params and example_params and params != example_params: raise exceptions.ScenarioExamplesNotValidError( """Scenario "{}" in the feature "{}" has not valid examples. """ @@ -384,67 +405,78 @@ def add_step(self, step): self.steps.append(step) +@attrs class Examples: + example_tables: List["ExampleTable"] = attrib(default=Factory(list)) - """Example table.""" + def __iter__(self) -> typing.Iterable[typing.Dict[str, typing.Any]]: + if any(self.example_tables): + yield from chain.from_iterable(self.example_tables) + else: + # We must make sure that we always have at least one element, otherwise + # examples cartesian product will be empty + yield {} - def __init__(self): - """Initialize examples instance.""" - self.example_params = [] - self.examples = [] - self.vertical_examples = [] - self.line_number = None - self.name = None + @property + def example_params(self): + return set(chain.from_iterable(set(table.example_params) for table in self.example_tables)) - def set_param_names(self, keys): - """Set parameter names. - :param names: `list` of `string` parameter names. - """ - self.example_params = [str(key) for key in keys] +@attrs +class ExampleTable: + """Example table.""" - def add_example(self, values): - """Add example. + examples: list + examples_transposed: list + example_params = attrib(default=Factory(list)) + + @example_params.validator + def unique(self, attribute, value): + unique_items = set() + for item in value: + if item in unique_items: + raise exceptions.ExamplesNotValidError( + f"""Example rows should contain unique parameters. "{item}" appeared more than once""" + ) + unique_items.add(item) + return True - :param values: `list` of `string` parameter values. - """ - self.examples.append(values) + line_number = attrib(default=None) + name = attrib(default=None) + tags = attrib(default=Factory(list)) - def add_example_row(self, param, values): - """Add example row. + def __iter__(self) -> typing.Iterable[typing.Dict[str, typing.Any]]: + examples = self.examples + for example in examples: + assert len(self.example_params) == len(example) + yield dict(zip(self.example_params, example)) - :param param: `str` parameter name - :param values: `list` of `string` parameter values - """ - if param in self.example_params: - raise exceptions.ExamplesNotValidError( - f"""Example rows should contain unique parameters. "{param}" appeared more than once""" - ) - self.example_params.append(param) - self.vertical_examples.append(values) + def __bool__(self): + """Bool comparison.""" + return bool(self.examples) - def as_contexts(self) -> typing.Iterable[typing.Dict[str, typing.Any]]: - param_count = len(self.example_params) - if self.vertical_examples and not self.examples: - for value_index in range(len(self.vertical_examples[0])): - example = [] - for param_index in range(param_count): - example.append(self.vertical_examples[param_index][value_index]) - self.examples.append(example) - if not self.examples: - return +@attrs +class ExampleTableRows(ExampleTable): + examples_transposed = attrib(default=Factory(list)) - header, rows = self.example_params, self.examples + @examples_transposed.validator + def each_row_contains_same_count_of_values(self, attribute, value): + if value: + if not all(len(value[0]) == len(item) for item in value): + raise exceptions.ExamplesNotValidError( + f"""All example columns in Examples: Vertical must have same count of values""" + ) + return True - for row in rows: - assert len(header) == len(row) + @property + def examples(self): + return list(zip_longest(*self.examples_transposed)) - yield dict(zip(header, row)) - def __bool__(self): - """Bool comparison.""" - return bool(self.vertical_examples or self.examples) +@attrs +class ExampleTableColumns(ExampleTable): + examples = attrib(default=Factory(list)) def get_tags(line): diff --git a/pytest_bdd/scenario.py b/pytest_bdd/scenario.py index c9446e38e..3174c8b02 100644 --- a/pytest_bdd/scenario.py +++ b/pytest_bdd/scenario.py @@ -24,8 +24,6 @@ from .utils import CONFIG_STACK, get_args, get_caller_module_locals, get_caller_module_path if typing.TYPE_CHECKING: - from _pytest.mark.structures import ParameterSet - from .parser import Feature, Scenario, ScenarioTemplate PYTHON_REPLACE_REGEX = re.compile(r"\W") @@ -175,8 +173,8 @@ def scenario_wrapper(request, _pytest_bdd_example): fixture_values = [request.getfixturevalue(arg) for arg in args] return fn(*fixture_values) - example_parametrizations = collect_example_parametrizations(templated_scenario) - if example_parametrizations is not None: + example_parametrizations = templated_scenario.example_parametrizations + if example_parametrizations: # Parametrize the scenario outlines scenario_wrapper = pytest.mark.parametrize( "_pytest_bdd_example", @@ -194,27 +192,6 @@ def scenario_wrapper(request, _pytest_bdd_example): return decorator -def collect_example_parametrizations( - templated_scenario: "ScenarioTemplate", -) -> "typing.Optional[typing.List[ParameterSet]]": - # We need to evaluate these iterators and store them as lists, otherwise - # we won't be able to do the cartesian product later (the second iterator will be consumed) - feature_contexts = list(templated_scenario.feature.examples.as_contexts()) - scenario_contexts = list(templated_scenario.examples.as_contexts()) - - contexts = [ - {**feature_context, **scenario_context} - # We must make sure that we always have at least one element in each list, otherwise - # the cartesian product will result in an empty list too, even if one of the 2 sets - # is non empty. - for feature_context in feature_contexts or [{}] - for scenario_context in scenario_contexts or [{}] - ] - if contexts == [{}]: - return None - return [pytest.param(context, id="-".join(context.values())) for context in contexts] - - def scenario(feature_name: str, scenario_name: str, encoding: str = "utf-8", features_base_dir=None): """Scenario decorator. diff --git a/requirements-testing.txt b/requirements-testing.txt index 748809f75..6b3d7cfbd 100644 --- a/requirements-testing.txt +++ b/requirements-testing.txt @@ -1 +1,3 @@ +execnet +deepdiff packaging diff --git a/setup.cfg b/setup.cfg index f65f9aa6f..b109151c0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,6 +27,7 @@ classifiers = [options] python_requires = >=3.6 install_requires = + attrs glob2 Mako parse diff --git a/tests/feature/test_cucumber_json.py b/tests/feature/test_cucumber_json.py index e34208183..f75cae111 100644 --- a/tests/feature/test_cucumber_json.py +++ b/tests/feature/test_cucumber_json.py @@ -3,6 +3,8 @@ import os.path import textwrap +from deepdiff import DeepDiff + def runandparse(testdir, *args): """Run tests in testdir and parse json output.""" @@ -225,4 +227,4 @@ def test_passing_outline(): } ] - assert jsonobject == expected + assert DeepDiff(jsonobject, expected, ignore_order=True, report_repetition=True) diff --git a/tests/feature/test_outline.py b/tests/feature/test_outline.py index 8b3eb7e4c..6c52163e2 100644 --- a/tests/feature/test_outline.py +++ b/tests/feature/test_outline.py @@ -489,7 +489,6 @@ def i_print_the_string(string): ] -@pytest.mark.xfail(reason="https://github.com/pytest-dev/pytest-bdd/issues/479") def test_multi_outlined(testdir): testdir.makefile( ".feature", @@ -540,7 +539,6 @@ def test_outline(request): # fmt: on -@pytest.mark.xfail(reason="https://github.com/pytest-dev/pytest-bdd/issues/479") def test_multi_outlined_feature_with_parameter_union(testdir): testdir.makefile( ".feature", @@ -584,7 +582,6 @@ def test_multi_outlined_feature_with_parameter_union(testdir): # fmt: on -@pytest.mark.xfail(reason="https://github.com/pytest-dev/pytest-bdd/issues/479") def test_multi_outlined_scenario_and_feature_with_parameter_union(testdir): testdir.makefile( ".feature", diff --git a/tests/feature/test_report.py b/tests/feature/test_report.py index f11f8d0f5..1fe364401 100644 --- a/tests/feature/test_report.py +++ b/tests/feature/test_report.py @@ -1,8 +1,6 @@ """Test scenario reporting.""" import textwrap -import pytest - class OfType: """Helper object comparison to which is always 'equal'.""" @@ -256,10 +254,8 @@ def should_have_left_cucumbers(start_cucumbers, start, eat, left): assert report == expected -def test_complex_types(testdir, pytestconfig): +def test_complex_types(testdir): """Test serialization of the complex types.""" - if not pytestconfig.pluginmanager.has_plugin("xdist"): - pytest.skip("Execnet not installed") import execnet.gateway_base From 1c1190df0ab76dec829e8b6fc7fa47354b487880 Mon Sep 17 00:00:00 2001 From: Kostiantyn Goloveshko Date: Sat, 8 Jan 2022 14:42:55 +0200 Subject: [PATCH 05/11] Outlined scenarios tests #480 --- tests/feature/test_outline.py | 47 +++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/feature/test_outline.py b/tests/feature/test_outline.py index 6c52163e2..952ce79b6 100644 --- a/tests/feature/test_outline.py +++ b/tests/feature/test_outline.py @@ -539,6 +539,53 @@ def test_outline(request): # fmt: on +@pytest.mark.xfail(reason="https://github.com/pytest-dev/pytest-bdd/issues/480") +def test_multi_outlined_empty_examples(testdir): + testdir.makefile( + ".feature", + outline=textwrap.dedent( + """\ + Feature: Outline + + Examples: + + Examples: Vertical + + Examples: + | + + Examples: Vertical + | + + Scenario Outline: Outlined given, when, thens + Given there are 12 apples + When I eat 5 apples + Then I should have 7 apples + + Examples: + + Examples: Vertical + + Examples: + | + + Examples: Vertical + | + """ + ), + ) + + testdir.makepyfile(STEPS_OUTLINED) + result = testdir.runpytest("-s") + result.assert_outcomes(passed=1) + parametrizations = collect_dumped_objects(result) + # fmt: off + assert parametrizations == [ + 12, "apples", 5.0, "apples", "7", "apples", + ] + # fmt: on + + def test_multi_outlined_feature_with_parameter_union(testdir): testdir.makefile( ".feature", From dc982b8c27402ae1949b53e65fc373c796f139cb Mon Sep 17 00:00:00 2001 From: Kostiantyn Goloveshko Date: Sat, 8 Jan 2022 17:53:45 +0200 Subject: [PATCH 06/11] Fix empty Examples tables parsing and union #480 --- pytest_bdd/parser.py | 24 ++++++++++++++---------- tests/feature/test_outline.py | 1 - 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/pytest_bdd/parser.py b/pytest_bdd/parser.py index 8fa4768ef..2a62d5d13 100644 --- a/pytest_bdd/parser.py +++ b/pytest_bdd/parser.py @@ -183,17 +183,21 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> "Feat message = f"{node_message_prefix} has not valid examples. {exc.args[0]}" raise exceptions.FeatureError(message, line_number, clean_line, filename) from exc elif mode == types.EXAMPLE_LINE_VERTICAL: - param, *examples = split_line(stripped_line) try: - build_example_table: ExampleTableRows - build_example_table.example_params += [param] - build_example_table.examples_transposed += [examples] - validate(build_example_table) - except exceptions.ExamplesNotValidError as exc: - message = "{node} has not valid examples. {original_message}".format( - node="Scenario" if scenario else "Feature", original_message=exc.args[0] - ) - raise exceptions.FeatureError(message, line_number, clean_line, filename) from exc + param, *examples = split_line(stripped_line) + except ValueError: + pass + else: + try: + build_example_table: ExampleTableRows + build_example_table.example_params += [param] + build_example_table.examples_transposed += [examples] + validate(build_example_table) + except exceptions.ExamplesNotValidError as exc: + message = "{node} has not valid examples. {original_message}".format( + node="Scenario" if scenario else "Feature", original_message=exc.args[0] + ) + raise exceptions.FeatureError(message, line_number, clean_line, filename) from exc elif mode and mode not in (types.FEATURE, types.TAG): step = Step(name=parsed_line, type=mode, indent=line_indent, line_number=line_number, keyword=keyword) if feature.background and not scenario: diff --git a/tests/feature/test_outline.py b/tests/feature/test_outline.py index 952ce79b6..ad0228895 100644 --- a/tests/feature/test_outline.py +++ b/tests/feature/test_outline.py @@ -539,7 +539,6 @@ def test_outline(request): # fmt: on -@pytest.mark.xfail(reason="https://github.com/pytest-dev/pytest-bdd/issues/480") def test_multi_outlined_empty_examples(testdir): testdir.makefile( ".feature", From 85401bfee3e7d661d1956bb3aa8d07f726b0bda8 Mon Sep 17 00:00:00 2001 From: Kostiantyn Goloveshko Date: Sat, 8 Jan 2022 18:20:24 +0200 Subject: [PATCH 07/11] Fix examples table validation #482 --- pytest_bdd/parser.py | 13 ++++++ tests/feature/test_outline.py | 80 +++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/pytest_bdd/parser.py b/pytest_bdd/parser.py index 2a62d5d13..4c10f35a3 100644 --- a/pytest_bdd/parser.py +++ b/pytest_bdd/parser.py @@ -175,6 +175,12 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> "Feat elif mode == types.EXAMPLES_HEADERS: mode = types.EXAMPLE_LINE build_example_table.example_params = [l for l in split_line(parsed_line) if l] + try: + validate(build_example_table) + except exceptions.ExamplesNotValidError as exc: + node_message_prefix = "Scenario" if scenario else "Feature" + message = f"{node_message_prefix} has not valid examples. {exc.args[0]}" + raise exceptions.FeatureError(message, line_number, clean_line, filename) from exc elif mode == types.EXAMPLE_LINE: try: build_example_table.examples += [[*split_line(stripped_line)]] @@ -482,6 +488,13 @@ def examples(self): class ExampleTableColumns(ExampleTable): examples = attrib(default=Factory(list)) + @examples.validator + def each_row_contains_same_count_of_values(self, attribute, value): + if value: + if not (all(len(value[0]) == len(item) for item in value) and (len(value[0]) != len(self.example_params))): + raise exceptions.ExamplesNotValidError(f"""All example rows must have same count of values""") + return True + def get_tags(line): """Get tags out of the given line. diff --git a/tests/feature/test_outline.py b/tests/feature/test_outline.py index ad0228895..c1a5ae18a 100644 --- a/tests/feature/test_outline.py +++ b/tests/feature/test_outline.py @@ -167,6 +167,86 @@ def test_outline(request): result.stdout.fnmatch_lines("*should match set of example values [[]'eat', 'left', 'start', 'unknown_param'[]].*") +def test_wrongly_outlined_duplicated_parameter_scenario(testdir): + """Test parametrized scenario vertical example table has wrong format.""" + testdir.makefile( + ".feature", + outline=textwrap.dedent( + """\ + Feature: Outline + Scenario Outline: Outlined with wrong vertical example table + Given there are cucumbers + When I eat cucumbers + Then I should have cucumbers + + Examples: + | start | start | left | + | 12 | 10 | 7 | + | 2 | 1 | 1 | + """ + ), + ) + testdir.makeconftest(textwrap.dedent(STEPS)) + + testdir.makepyfile( + textwrap.dedent( + """\ + from pytest_bdd import scenario + + @scenario("outline.feature", "Outlined with wrong vertical example table") + def test_outline(request): + pass + """ + ) + ) + result = testdir.runpytest() + assert_outcomes(result, errors=1) + result.stdout.fnmatch_lines( + "*Scenario has not valid examples. Example rows should contain unique parameters. " + '"start" appeared more than once.*' + ) + + +def test_wrongly_outlined_duplicated_parameter_feature(testdir): + """Test parametrized scenario vertical example table has wrong format.""" + testdir.makefile( + ".feature", + outline=textwrap.dedent( + """\ + Feature: Outline + Examples: + | start | start | left | + | 12 | 10 | 7 | + | 2 | 1 | 1 | + + Scenario Outline: Outlined with wrong vertical example table + Given there are cucumbers + When I eat cucumbers + Then I should have cucumbers + """ + ), + ) + testdir.makeconftest(textwrap.dedent(STEPS)) + + testdir.makepyfile( + textwrap.dedent( + """\ + from pytest_bdd import scenario + + @scenario("outline.feature", "Outlined with wrong vertical example table") + def test_outline(request): + pass + """ + ) + ) + result = testdir.runpytest() + assert_outcomes(result, errors=1) + result.stdout.fnmatch_lines( + "*Feature has not valid examples. Example rows should contain unique parameters. " + '"start" appeared more than once.*' + ) + + def test_outlined_with_other_fixtures(testdir): """Test outlined scenario also using other parametrized fixture.""" testdir.makefile( From 646cb526804a493e1e051bbcbd60b565e6af0ab7 Mon Sep 17 00:00:00 2001 From: Kostiantyn Goloveshko Date: Sat, 8 Jan 2022 14:42:55 +0200 Subject: [PATCH 08/11] Outlined scenarios tests for #481 --- tests/feature/test_outline.py | 137 ++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/tests/feature/test_outline.py b/tests/feature/test_outline.py index c1a5ae18a..c2f1c58ea 100644 --- a/tests/feature/test_outline.py +++ b/tests/feature/test_outline.py @@ -751,3 +751,140 @@ def test_multi_outlined_scenario_and_feature_with_parameter_union(testdir): 5, "apples", 4.0, "apples", "1", "apples", ] # fmt: on + + +@pytest.mark.xfail +def test_outlined_scenario_and_feature_with_parameter_join_by_one_parameter(testdir): + testdir.makefile( + ".feature", + outline=textwrap.dedent( + """\ + Feature: Outline + + Examples: + | start | eat | left | + | 12 | 5 | 7 | + | 5 | 4 | 1 | + + + Scenario Outline: Outlined given, when, thens + Given there are + When I eat + Then I should have + + Examples: + | fruits | left | + | apples | 7 | + | oranges | 1 | + """ + ), + ) + + testdir.makepyfile(STEPS_OUTLINED) + result = testdir.runpytest("-s") + result.assert_outcomes(passed=2) + parametrizations = collect_dumped_objects(result) + # fmt: off + assert parametrizations == [ + 12, "apples", 5.0, "apples", "7", "apples", + 5, "oranges", 4.0, "oranges", "1", "oranges", + ] + # fmt: on + + +@pytest.mark.xfail +def test_outlined_scenario_and_feature_with_parameter_join_by_multi_parameter(testdir): + testdir.makefile( + ".feature", + outline=textwrap.dedent( + """\ + Feature: Outline + + Examples: + | fruits | start | eat | left | + | apples | 12 | 5 | 7 | + | apples | 12 | 9 | 3 | # not joined by + | oranges | 5 | 4 | 1 | + | cucumbers | 8 | 3 | 5 | # not joined by + + + Scenario Outline: Outlined given, when, thens + Given there are + When I eat + Then I should have + + Examples: + | fruits | eat | left | + | apples | 5 | 7 | + | oranges | 4 | 1 | + | cucumbers | 5 | 7 | # not joined by + """ + ), + ) + + testdir.makepyfile(STEPS_OUTLINED) + result = testdir.runpytest("-s") + result.assert_outcomes(passed=2) + parametrizations = collect_dumped_objects(result) + # fmt: off + assert parametrizations == [ + 12, "apples", 5.0, "apples", "7", "apples", + 5, "oranges", 4.0, "oranges", "1", "oranges", + ] + # fmt: on + + +@pytest.mark.xfail +def test_outlined_scenario_and_feature_with_parameter_join_by_multi_parameter_unbalanced(testdir): + testdir.makefile( + ".feature", + outline=textwrap.dedent( + """\ + Feature: Outline + Examples: + | start | eat | left | + | 14 | 6 | 8 | + | 15 | 5 | 10 | + + Examples: + | fruits | start | eat | left | + | apples | 12 | 5 | 7 | + | apples | 12 | 9 | 3 | + | oranges | 5 | 4 | 1 | + | cucumbers | 8 | 3 | 5 | + + + Scenario Outline: Outlined given, when, thens + Given there are + When I eat + Then I should have + + Examples: + | fruits | eat | left | + | apples | 5 | 7 | + | oranges | 4 | 1 | + | cucumbers | 5 | 7 | + + Examples: + | fruits | + | pineapples | + | peaches | + + """ + ), + ) + + testdir.makepyfile(STEPS_OUTLINED) + result = testdir.runpytest("-s") + result.assert_outcomes(passed=6) + parametrizations = collect_dumped_objects(result) + # fmt: off + assert parametrizations == [ + 14, 'pineapples', 6.0, 'pineapples', '8', 'pineapples', + 14, 'peaches', 6.0, 'peaches', '8', 'peaches', + 15, 'pineapples', 5.0, 'pineapples', '10', 'pineapples', + 15, 'peaches', 5.0, 'peaches', '10', 'peaches', + 12, 'apples', 5.0, 'apples', '7', 'apples', + 5, 'oranges', 4.0, 'oranges', '1', 'oranges' + ] + # fmt: on From 51a0dc5abe86fd159f66a608dc76d8b4d17e3a7d Mon Sep 17 00:00:00 2001 From: Kostiantyn Goloveshko Date: Sat, 8 Jan 2022 13:24:21 +0200 Subject: [PATCH 09/11] Add joining example tables by common keys --- pytest_bdd/parser.py | 10 +++++++--- tests/feature/test_outline.py | 4 ---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pytest_bdd/parser.py b/pytest_bdd/parser.py index 4c10f35a3..de8532172 100644 --- a/pytest_bdd/parser.py +++ b/pytest_bdd/parser.py @@ -283,9 +283,13 @@ def example_parametrizations(self) -> "typing.Optional[typing.List[ParameterSet] def examples(): for feature_examples, scenario_examples in feature_scenario_examples_combinations: - result = {**feature_examples, **scenario_examples} - if result != {}: - yield result + common_param_names = set(feature_examples.keys()).intersection(scenario_examples.keys()) + if all( + feature_examples[param_name] == scenario_examples[param_name] for param_name in common_param_names + ): + result = {**feature_examples, **scenario_examples} + if result != {}: + yield result return [pytest.param(example, id="-".join(example.values())) for example in examples()] diff --git a/tests/feature/test_outline.py b/tests/feature/test_outline.py index c2f1c58ea..b79a3db2c 100644 --- a/tests/feature/test_outline.py +++ b/tests/feature/test_outline.py @@ -753,7 +753,6 @@ def test_multi_outlined_scenario_and_feature_with_parameter_union(testdir): # fmt: on -@pytest.mark.xfail def test_outlined_scenario_and_feature_with_parameter_join_by_one_parameter(testdir): testdir.makefile( ".feature", @@ -792,7 +791,6 @@ def test_outlined_scenario_and_feature_with_parameter_join_by_one_parameter(test # fmt: on -@pytest.mark.xfail def test_outlined_scenario_and_feature_with_parameter_join_by_multi_parameter(testdir): testdir.makefile( ".feature", @@ -834,7 +832,6 @@ def test_outlined_scenario_and_feature_with_parameter_join_by_multi_parameter(te # fmt: on -@pytest.mark.xfail def test_outlined_scenario_and_feature_with_parameter_join_by_multi_parameter_unbalanced(testdir): testdir.makefile( ".feature", @@ -869,7 +866,6 @@ def test_outlined_scenario_and_feature_with_parameter_join_by_multi_parameter_un | fruits | | pineapples | | peaches | - """ ), ) From 2fc9e7f5f6cbf65c4b38ea4df88bb9d76bef4512 Mon Sep 17 00:00:00 2001 From: Kostiantyn Goloveshko Date: Sat, 8 Jan 2022 14:42:55 +0200 Subject: [PATCH 10/11] Outlined scenarios tests --- tests/feature/test_outline.py | 115 ++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/tests/feature/test_outline.py b/tests/feature/test_outline.py index b79a3db2c..bdedadcce 100644 --- a/tests/feature/test_outline.py +++ b/tests/feature/test_outline.py @@ -884,3 +884,118 @@ def test_outlined_scenario_and_feature_with_parameter_join_by_multi_parameter_un 5, 'oranges', 4.0, 'oranges', '1', 'oranges' ] # fmt: on + + +@pytest.mark.xfail(reason="https://github.com/pytest-dev/pytest-bdd/pull/439") +def test_outlined_scenario_and_feature_with_insufficient_parameter_join(testdir): + testdir.makefile( + ".feature", + outline=textwrap.dedent( + """\ + Feature: Outline + Examples: + | fruits | + | apples | + | oranges | + + Scenario Outline: Outlined given, when, thens + Given there are + When I eat + Then I should have + + Examples: + | eat | left | + | 5 | 7 | + | 4 | 1 | + + Examples: + | fruits | start | + | pineapples | 12 | + | peaches | 10 | + + """ + ), + ) + + testdir.makepyfile(STEPS_OUTLINED) + result = testdir.runpytest("-s") + result.assert_outcomes(passed=0) + + +@pytest.mark.xfail(reason="https://github.com/pytest-dev/pytest-bdd/pull/439") +def test_outlined_scenario_and_feature_with_extra_parameter_join(testdir): + testdir.makefile( + ".feature", + outline=textwrap.dedent( + """\ + Feature: Outline + Examples: + | fruits | extra | + | apples | not used | + | oranges | not needed | + + Scenario Outline: Outlined given, when, thens + Given there are + When I eat + Then I should have + + Examples: + | start | eat | left | + | 12 | 5 | 7 | + | 5 | 4 | 1 | + """ + ), + ) + + testdir.makepyfile(STEPS_OUTLINED) + result = testdir.runpytest("-s") + result.assert_outcomes(passed=0) + + +@pytest.mark.xfail(reason="https://github.com/pytest-dev/pytest-bdd/pull/439") +def test_outlined_scenario_and_feature_with_combine_extra_and_insufficient_parameter_join(testdir): + testdir.makefile( + ".feature", + outline=textwrap.dedent( + """\ + Feature: Outline + Examples: + | fruits | extra | + | apples | not used | + | oranges | not needed | + + Examples: + | fruits | + | cucumbers | + | peaches | + + Scenario Outline: Outlined given, when, thens + Given there are + When I eat + Then I should have + + Examples: + | start | eat | left | + | 12 | 5 | 7 | + | 5 | 4 | 1 | + + Examples: + | eat | left | + | 8 | 6 | + | 9 | 5 | + """ + ), + ) + + testdir.makepyfile(STEPS_OUTLINED) + result = testdir.runpytest("-s") + result.assert_outcomes(passed=4) + parametrizations = collect_dumped_objects(result) + # fmt: off + assert parametrizations == [ + 12, "cucumbers", 5.0, "cucumbers", "7", "cucumbers", + 12, "peaches", 5.0, "peaches", "7", "peaches", + 5, "cucumbers", 4.0, "cucumbers", "1", "cucumbers", + 5, "peaches", 4.0, "peaches", "1", "peaches", + ] + # fmt: on From b0690cdd83124c51f0e8c2aed79a5fa016bafa1e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 8 Jan 2022 16:47:25 +0000 Subject: [PATCH 11/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pytest_bdd/parser.py | 2 +- tests/feature/test_outline.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pytest_bdd/parser.py b/pytest_bdd/parser.py index de8532172..0faf28c9e 100644 --- a/pytest_bdd/parser.py +++ b/pytest_bdd/parser.py @@ -7,7 +7,7 @@ from typing import List import pytest -from attr import attrs, attrib, Factory, validate +from attr import Factory, attrib, attrs, validate from . import exceptions, types diff --git a/tests/feature/test_outline.py b/tests/feature/test_outline.py index bdedadcce..f45b5b845 100644 --- a/tests/feature/test_outline.py +++ b/tests/feature/test_outline.py @@ -860,7 +860,7 @@ def test_outlined_scenario_and_feature_with_parameter_join_by_multi_parameter_un | fruits | eat | left | | apples | 5 | 7 | | oranges | 4 | 1 | - | cucumbers | 5 | 7 | + | cucumbers | 5 | 7 | Examples: | fruits |