Skip to content

Commit d01b886

Browse files
Enforce Child dispatch exhaustiveness with assert_never, drop the mock test
assert_never on the Child dataclass itself does not typecheck: mypy cannot prove exhaustiveness from attribute patterns on a class with three optional fields, so the fall-through arm keeps the type "Child" instead of "Never". Matching on `child.background or child.rule or child.scenario` gives mypy a proper union (Background | Rule | Scenario | None) to exhaust; removing any arm is now a mypy error. Both defensive arms (empty Child, assert_never) are unreachable with the current gherkin version, so they are excluded from coverage instead of being exercised through a monkeypatched document, and the mock-based test is removed. "pragma: no cover" is re-added to coverage's exclude_lines: setting exclude_lines in the config replaces coverage.py's defaults, so the pragma had silently stopped working.
1 parent 60c2862 commit d01b886

3 files changed

Lines changed: 14 additions & 39 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ lint.isort.required-imports = [
9696

9797
[tool.coverage.report]
9898
exclude_lines = [
99+
"pragma: no cover",
99100
"if TYPE_CHECKING:",
100101
"if typing\\.TYPE_CHECKING:",
101102
]

src/pytest_bdd/parser.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
from collections.abc import Generator, Iterable, Mapping, Sequence
99
from dataclasses import dataclass, field
1010

11+
from typing_extensions import assert_never
12+
1113
from .exceptions import StepError
1214
from .gherkin_parser import Background as GherkinBackground
13-
from .gherkin_parser import Child, DataTable, GherkinDocument, get_gherkin_document
15+
from .gherkin_parser import DataTable, GherkinDocument, get_gherkin_document
1416
from .gherkin_parser import Feature as GherkinFeature
1517
from .gherkin_parser import Rule as GherkinRule
1618
from .gherkin_parser import Scenario as GherkinScenario
@@ -492,13 +494,19 @@ def parse(self) -> Feature:
492494
)
493495

494496
for child in feature_data.children:
495-
match child:
496-
case Child(background=GherkinBackground() as background):
497+
match child.background or child.rule or child.scenario:
498+
case GherkinBackground() as background:
497499
feature.background = self.parse_background(background)
498-
case Child(rule=GherkinRule() as rule):
500+
case GherkinRule() as rule:
499501
self._parse_and_add_rule(rule, feature)
500-
case Child(scenario=GherkinScenario() as scenario):
502+
case GherkinScenario() as scenario:
501503
self._parse_and_add_scenario(scenario, feature)
504+
case None: # pragma: no cover
505+
# An empty Child (a child kind newer than this gherkin version);
506+
# skip it, like unknown children were skipped before the match.
507+
pass
508+
case unreachable: # pragma: no cover
509+
assert_never(unreachable)
502510

503511
return feature
504512

tests/parser/test_parser.py

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
from __future__ import annotations
22

3-
import textwrap
43
from pathlib import Path
54

6-
from src.pytest_bdd import parser as parser_module
75
from src.pytest_bdd.gherkin_parser import (
86
Background,
97
Cell,
@@ -853,35 +851,3 @@ def test_parser():
853851
)
854852

855853
assert gherkin_doc == expected_document
856-
857-
858-
def test_feature_parser_skips_unknown_child_kinds(tmp_path, monkeypatch):
859-
"""A Child with none of background/rule/scenario set is skipped.
860-
861-
The gherkin AST reserves the right to grow new child kinds; ``Child.from_dict``
862-
would map such a child to an all-``None`` dataclass, and ``FeatureParser.parse``
863-
must ignore it instead of crashing.
864-
"""
865-
(tmp_path / "minimal.feature").write_text(
866-
textwrap.dedent(
867-
"""\
868-
Feature: Minimal
869-
Scenario: A scenario
870-
Given a step
871-
"""
872-
)
873-
)
874-
875-
real_get_gherkin_document = parser_module.get_gherkin_document
876-
877-
def get_gherkin_document_with_unknown_child(abs_filename: str, encoding: str = "utf-8") -> GherkinDocument:
878-
document = real_get_gherkin_document(abs_filename, encoding)
879-
document.feature.children.append(Child())
880-
return document
881-
882-
monkeypatch.setattr(parser_module, "get_gherkin_document", get_gherkin_document_with_unknown_child)
883-
884-
feature = parser_module.FeatureParser(str(tmp_path), "minimal.feature").parse()
885-
886-
assert list(feature.scenarios) == ["A scenario"]
887-
assert feature.background is None

0 commit comments

Comments
 (0)