diff --git a/pyproject.toml b/pyproject.toml index dcd1a009..c52cec0f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,6 +96,7 @@ lint.isort.required-imports = [ [tool.coverage.report] exclude_lines = [ + "pragma: no cover", "if TYPE_CHECKING:", "if typing\\.TYPE_CHECKING:", ] diff --git a/src/pytest_bdd/parser.py b/src/pytest_bdd/parser.py index fdc86c9a..9f269e51 100644 --- a/src/pytest_bdd/parser.py +++ b/src/pytest_bdd/parser.py @@ -4,6 +4,7 @@ import os.path import re import textwrap +import warnings from collections import OrderedDict from collections.abc import Generator, Iterable, Mapping, Sequence from dataclasses import dataclass, field @@ -492,12 +493,21 @@ def parse(self) -> Feature: ) for child in feature_data.children: - if child.background: - feature.background = self.parse_background(child.background) - elif child.rule: - self._parse_and_add_rule(child.rule, feature) - elif child.scenario: - self._parse_and_add_scenario(child.scenario, feature) + match child.background or child.rule or child.scenario: + case GherkinBackground() as background: + feature.background = self.parse_background(background) + case GherkinRule() as rule: + self._parse_and_add_rule(rule, feature) + case GherkinScenario() as scenario: + self._parse_and_add_scenario(scenario, feature) + case _: + # An empty Child, or a child kind newer than this gherkin version; + # skip it and warn instead of crashing, to keep working against + # future gherkin releases that add new child types. + warnings.warn( + f"Unknown gherkin child skipped in {self.rel_filename!r}; consider upgrading pytest-bdd.", + stacklevel=2, + ) return feature diff --git a/tests/parser/test_parser.py b/tests/parser/test_parser.py index 09be7c85..0de24430 100644 --- a/tests/parser/test_parser.py +++ b/tests/parser/test_parser.py @@ -1,7 +1,12 @@ from __future__ import annotations +import textwrap from pathlib import Path +from unittest.mock import patch +import pytest + +from src.pytest_bdd import parser as parser_module from src.pytest_bdd.gherkin_parser import ( Background, Cell, @@ -851,3 +856,36 @@ def test_parser(): ) assert gherkin_doc == expected_document + + +def test_feature_parser_warns_on_unknown_child_kinds(tmp_path): + """A Child with none of background/rule/scenario set is skipped with a warning. + + The gherkin AST reserves the right to grow new child kinds; ``Child.from_dict`` + would map such a child to an all-``None`` dataclass, and ``FeatureParser.parse`` + must warn and skip it instead of crashing, so pytest-bdd keeps working against + future gherkin releases. + """ + (tmp_path / "minimal.feature").write_text( + textwrap.dedent( + """\ + Feature: Minimal + Scenario: A scenario + Given a step + """ + ) + ) + + real_get_gherkin_document = parser_module.get_gherkin_document + + def get_gherkin_document_with_unknown_child(abs_filename: str, encoding: str = "utf-8") -> GherkinDocument: + document = real_get_gherkin_document(abs_filename, encoding) + document.feature.children.append(Child()) + return document + + with patch.object(parser_module, "get_gherkin_document", get_gherkin_document_with_unknown_child): + with pytest.warns(UserWarning, match="Unknown gherkin child"): + feature = parser_module.FeatureParser(str(tmp_path), "minimal.feature").parse() + + assert list(feature.scenarios) == ["A scenario"] + assert feature.background is None