From 1cb22de0928dd82486a5abd57bedf25f8cf01283 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Fri, 3 Jul 2026 07:53:46 +0200 Subject: [PATCH 1/2] Use a match statement to dispatch on gherkin Child fields A gherkin Child has exactly one of background/rule/scenario set; class patterns make that dispatch explicit and replace the truthiness checks with type matching. --- src/pytest_bdd/parser.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/pytest_bdd/parser.py b/src/pytest_bdd/parser.py index fdc86c9a..78af534e 100644 --- a/src/pytest_bdd/parser.py +++ b/src/pytest_bdd/parser.py @@ -10,7 +10,7 @@ from .exceptions import StepError from .gherkin_parser import Background as GherkinBackground -from .gherkin_parser import DataTable, GherkinDocument, get_gherkin_document +from .gherkin_parser import Child, DataTable, GherkinDocument, get_gherkin_document from .gherkin_parser import Feature as GherkinFeature from .gherkin_parser import Rule as GherkinRule from .gherkin_parser import Scenario as GherkinScenario @@ -492,12 +492,13 @@ 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: + case Child(background=GherkinBackground() as background): + feature.background = self.parse_background(background) + case Child(rule=GherkinRule() as rule): + self._parse_and_add_rule(rule, feature) + case Child(scenario=GherkinScenario() as scenario): + self._parse_and_add_scenario(scenario, feature) return feature From d87588c66e51d519ec9e1163f0dc9e8a006c2912 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Fri, 3 Jul 2026 09:05:36 +0200 Subject: [PATCH 2/2] Cover the no-match arm of the gherkin Child dispatch A Child with none of background/rule/scenario set (a child kind a future gherkin version could introduce) must be skipped by FeatureParser.parse. No real feature file can produce one today, so the test appends a synthetic empty Child to the parsed document. --- tests/parser/test_parser.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/parser/test_parser.py b/tests/parser/test_parser.py index 09be7c85..5d51a25b 100644 --- a/tests/parser/test_parser.py +++ b/tests/parser/test_parser.py @@ -1,7 +1,9 @@ from __future__ import annotations +import textwrap from pathlib import Path +from src.pytest_bdd import parser as parser_module from src.pytest_bdd.gherkin_parser import ( Background, Cell, @@ -851,3 +853,35 @@ def test_parser(): ) assert gherkin_doc == expected_document + + +def test_feature_parser_skips_unknown_child_kinds(tmp_path, monkeypatch): + """A Child with none of background/rule/scenario set is skipped. + + 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 ignore it instead of crashing. + """ + (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 + + monkeypatch.setattr(parser_module, "get_gherkin_document", get_gherkin_document_with_unknown_child) + + feature = parser_module.FeatureParser(str(tmp_path), "minimal.feature").parse() + + assert list(feature.scenarios) == ["A scenario"] + assert feature.background is None