Skip to content

Commit b1304e0

Browse files
authored
Merge branch 'master' into master
2 parents 89d1afe + 20faa0c commit b1304e0

38 files changed

+2435
-799
lines changed

.github/workflows/main.yml

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,27 @@ jobs:
1313
include:
1414
- python-version: "3.8"
1515
toxfactor: py3.8
16-
ignore-typecheck-outcome: true
16+
ignore-typecheck-outcome: false
1717
ignore-test-outcome: false
1818
- python-version: "3.9"
1919
toxfactor: py3.9
20-
ignore-typecheck-outcome: true
20+
ignore-typecheck-outcome: false
2121
ignore-test-outcome: false
2222
- python-version: "3.10"
2323
toxfactor: py3.10
24-
ignore-typecheck-outcome: true
24+
ignore-typecheck-outcome: false
2525
ignore-test-outcome: false
2626
- python-version: "3.11"
2727
toxfactor: py3.11
28-
ignore-typecheck-outcome: true
28+
ignore-typecheck-outcome: false
2929
ignore-test-outcome: false
3030
- python-version: "3.12"
3131
toxfactor: py3.12
32-
ignore-typecheck-outcome: true
32+
ignore-typecheck-outcome: false
3333
ignore-test-outcome: false
3434
- python-version: "3.13-dev"
3535
toxfactor: py3.13
36-
ignore-typecheck-outcome: true
36+
ignore-typecheck-outcome: false
3737
ignore-test-outcome: false
3838

3939
steps:
@@ -47,7 +47,7 @@ jobs:
4747

4848
- name: Install poetry
4949
run: |
50-
python -m pip install poetry==1.8.2
50+
python -m pip install poetry==1.8.3
5151
5252
- name: Configure poetry
5353
run: |
@@ -81,10 +81,35 @@ jobs:
8181
coverage combine
8282
coverage xml
8383
84-
- uses: codecov/codecov-action@v3
84+
- uses: codecov/codecov-action@v4
8585
with:
8686
# Explicitly using the token to avoid Codecov rate limit errors
8787
# See https://community.codecov.com/t/upload-issues-unable-to-locate-build-via-github-actions-api/3954
8888
token: ${{ secrets.CODECOV_TOKEN }}
89-
fail_ci_if_error: true
89+
fail_ci_if_error: false
9090
verbose: true # optional (default = false)
91+
92+
pypi-publish:
93+
name: Upload release to PyPI
94+
runs-on: ubuntu-latest
95+
environment:
96+
name: pypi
97+
url: https://pypi.org/p/pytest-bdd
98+
permissions:
99+
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
100+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
101+
needs: test-run
102+
steps:
103+
- uses: actions/checkout@v4
104+
- name: Set up Python
105+
uses: actions/setup-python@v5
106+
- name: Install pypa/build
107+
run: >-
108+
python3 -m
109+
pip install
110+
build
111+
--user
112+
- name: Build a binary wheel and a source tarball
113+
run: python3 -m build
114+
- name: Publish package distributions to PyPI
115+
uses: pypa/gh-action-pypi-publish@release/v1

CHANGES.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@ Changelog
44
Unreleased
55
----------
66
- Update documentation to clarify that `--gherkin-terminal-reporter` needs to be used with `-v` or `-vv`.
7+
- Drop compatibility with pytest < 7.0.0.
8+
9+
8.0.0b1
10+
----------
11+
- Use `gherkin-official` parser to replace custom parsing logic. This will make pytest-bdd more compatible with the Gherkin specification.
12+
- Multiline steps must now always use triple-quotes for the additional lines.
13+
- All feature files must now use the keyword `Feature:` to be considered valid.
14+
- Tags can no longer have spaces (e.g. "@tag one" "@tag two" are no longer valid).
15+
16+
7.3.0
17+
----------
18+
- Fix an issue when only the first Step would inject a fixture, while later steps would not be able to.
19+
- Test against the latest versions of pytest (8.2, 8.3).
720

821
7.2.0
922
----------

poetry.lock

Lines changed: 199 additions & 168 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "pytest-bdd"
3-
version = "7.2.0"
3+
version = "8.0.0b1"
44
description = "BDD for pytest"
55
authors = ["Oleg Pidsadnyi <[email protected]>", "Anatoly Bubenkov <[email protected]>"]
66
maintainers = ["Alessio Bogon <[email protected]>"]
@@ -39,9 +39,10 @@ python = ">=3.8"
3939
Mako = "*"
4040
parse = "*"
4141
parse-type = "*"
42-
pytest = ">=6.2.0"
42+
pytest = ">=7.0.0"
4343
typing-extensions = "*"
4444
packaging = "*"
45+
gherkin-official = "^29.0.0"
4546

4647
[tool.poetry.group.dev.dependencies]
4748
tox = ">=4.11.3"
@@ -93,6 +94,7 @@ python_version = "3.8"
9394
warn_return_any = true
9495
warn_unused_configs = true
9596
files = "src/pytest_bdd/**/*.py"
97+
disallow_untyped_defs = true
9698

9799
[[tool.mypy.overrides]]
98100
module = ["parse", "parse_type"]

pytest.ini

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[pytest]
22
testpaths = tests
33
filterwarnings =
4-
error
4+
# only ignore errors from the pytest_bdd package
5+
error:::(src)?\.pytest_bdd.*

src/pytest_bdd/compat.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,22 @@ def inject_fixture(request: FixtureRequest, arg: str, value: Any) -> None:
2424
:param arg: argument name
2525
:param value: argument value
2626
"""
27-
27+
# Ensure there's a fixture definition for the argument
2828
request._fixturemanager._register_fixture(
2929
name=arg,
3030
func=lambda: value,
3131
nodeid=request.node.nodeid,
3232
)
33+
# Note the fixture we just registered will have a lower priority
34+
# if there was already one registered, so we need to force its value
35+
# to the one we want to inject.
36+
fixture_def = request._get_active_fixturedef(arg)
37+
fixture_def.cached_result = (value, None, None) # type: ignore
3338

3439
else:
3540

3641
def getfixturedefs(fixturemanager: FixtureManager, fixturename: str, node: Node) -> Sequence[FixtureDef] | None:
37-
return fixturemanager.getfixturedefs(fixturename, node.nodeid)
42+
return fixturemanager.getfixturedefs(fixturename, node.nodeid) # type: ignore
3843

3944
def inject_fixture(request: FixtureRequest, arg: str, value: Any) -> None:
4045
"""Inject fixture into pytest fixture request.
@@ -44,7 +49,7 @@ def inject_fixture(request: FixtureRequest, arg: str, value: Any) -> None:
4449
:param value: argument value
4550
"""
4651
fd = FixtureDef(
47-
fixturemanager=request._fixturemanager,
52+
fixturemanager=request._fixturemanager, # type: ignore
4853
baseid=None,
4954
argname=arg,
5055
func=lambda: value,

src/pytest_bdd/cucumber_json.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,14 @@ def configure(config: Config) -> None:
3535
cucumber_json_path = config.option.cucumber_json_path
3636
# prevent opening json log on worker nodes (xdist)
3737
if cucumber_json_path and not hasattr(config, "workerinput"):
38-
config._bddcucumberjson = LogBDDCucumberJSON(cucumber_json_path)
39-
config.pluginmanager.register(config._bddcucumberjson)
38+
config._bddcucumberjson = LogBDDCucumberJSON(cucumber_json_path) # type: ignore[attr-defined]
39+
config.pluginmanager.register(config._bddcucumberjson) # type: ignore[attr-defined]
4040

4141

4242
def unconfigure(config: Config) -> None:
43-
xml = getattr(config, "_bddcucumberjson", None)
43+
xml = getattr(config, "_bddcucumberjson", None) # type: ignore[attr-defined]
4444
if xml is not None:
45-
del config._bddcucumberjson
45+
del config._bddcucumberjson # type: ignore[attr-defined]
4646
config.pluginmanager.unregister(xml)
4747

4848

src/pytest_bdd/exceptions.py

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@ class ScenarioNotFound(ScenarioValidationError):
1515
"""Scenario Not Found."""
1616

1717

18-
class ExamplesNotValidError(ScenarioValidationError):
19-
"""Example table is not valid."""
20-
21-
2218
class StepDefinitionNotFoundError(Exception):
2319
"""Step definition not found."""
2420

@@ -27,11 +23,39 @@ class NoScenariosFound(Exception):
2723
"""No scenarios found."""
2824

2925

30-
class FeatureError(Exception):
31-
"""Feature parse error."""
26+
class GherkinParseError(Exception):
27+
"""Base class for all Gherkin parsing errors."""
3228

33-
message = "{0}.\nLine number: {1}.\nLine: {2}.\nFile: {3}"
29+
def __init__(self, message: str, line: int, line_content: str, filename: str) -> None:
30+
super().__init__(message)
31+
self.message = message
32+
self.line = line
33+
self.line_content = line_content
34+
self.filename = filename
3435

3536
def __str__(self) -> str:
36-
"""String representation."""
37-
return self.message.format(*self.args)
37+
return f"{self.message}\nLine number: {self.line}\nLine: {self.line_content}\nFile: {self.filename}"
38+
39+
40+
class FeatureError(GherkinParseError):
41+
pass
42+
43+
44+
class BackgroundError(GherkinParseError):
45+
pass
46+
47+
48+
class ScenarioError(GherkinParseError):
49+
pass
50+
51+
52+
class StepError(GherkinParseError):
53+
pass
54+
55+
56+
class RuleError(GherkinParseError):
57+
pass
58+
59+
60+
class TokenError(GherkinParseError):
61+
pass

src/pytest_bdd/feature.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import glob
3030
import os.path
3131

32-
from .parser import Feature, parse_feature
32+
from .parser import Feature, FeatureParser
3333

3434
# Global features dictionary
3535
features: dict[str, Feature] = {}
@@ -52,30 +52,29 @@ def get_feature(base_path: str, filename: str, encoding: str = "utf-8") -> Featu
5252
full_name = os.path.abspath(os.path.join(base_path, filename))
5353
feature = features.get(full_name)
5454
if not feature:
55-
feature = parse_feature(base_path, filename, encoding=encoding)
55+
feature = FeatureParser(base_path, filename, encoding).parse()
5656
features[full_name] = feature
5757
return feature
5858

5959

60-
def get_features(paths: list[str], **kwargs) -> list[Feature]:
60+
def get_features(paths: list[str], encoding: str = "utf-8") -> list[Feature]:
6161
"""Get features for given paths.
6262
6363
:param list paths: `list` of paths (file or dirs)
6464
6565
:return: `list` of `Feature` objects.
6666
"""
6767
seen_names = set()
68-
features = []
68+
_features = []
6969
for path in paths:
7070
if path not in seen_names:
7171
seen_names.add(path)
7272
if os.path.isdir(path):
73-
features.extend(
74-
get_features(glob.iglob(os.path.join(path, "**", "*.feature"), recursive=True), **kwargs)
75-
)
73+
file_paths = list(glob.iglob(os.path.join(path, "**", "*.feature"), recursive=True))
74+
_features.extend(get_features(file_paths, encoding=encoding))
7675
else:
7776
base, name = os.path.split(path)
78-
feature = get_feature(base, name, **kwargs)
79-
features.append(feature)
80-
features.sort(key=lambda feature: feature.name or feature.filename)
81-
return features
77+
feature = get_feature(base, name, encoding=encoding)
78+
_features.append(feature)
79+
_features.sort(key=lambda _feature: _feature.name or _feature.filename)
80+
return _features

src/pytest_bdd/generation.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from typing import TYPE_CHECKING, cast
88

99
from _pytest._io import TerminalWriter
10-
from mako.lookup import TemplateLookup
10+
from mako.lookup import TemplateLookup # type: ignore
1111

1212
from .compat import getfixturedefs
1313
from .feature import get_features
@@ -181,11 +181,11 @@ def _show_missing_code_main(config: Config, session: Session) -> None:
181181
features, scenarios, steps = parse_feature_files(config.option.features)
182182

183183
for item in session.items:
184-
if scenario := getattr(item.obj, "__scenario__", None):
184+
if scenario := getattr(item.obj, "__scenario__", None): # type: ignore
185185
if scenario in scenarios:
186186
scenarios.remove(scenario)
187187
for step in scenario.steps:
188-
if _find_step_fixturedef(fm, item, step=step):
188+
if _find_step_fixturedef(fm, item, step=step): # type: ignore
189189
try:
190190
steps.remove(step)
191191
except ValueError:

0 commit comments

Comments
 (0)