From b001d8b03293b26eb133df81502bedae6c779098 Mon Sep 17 00:00:00 2001 From: Kostiantyn Goloveshko Date: Tue, 29 Oct 2024 19:50:56 +0200 Subject: [PATCH] Update versions - Drop python 3.8 - Add python 3.13 - Drop pytest<5.2 - Update pre-commit deps --- .github/workflows/main.yml | 10 +- .github/workflows/release.yaml | 2 +- .pre-commit-config.yaml | 18 +- CHANGES.rst | 5 + docs/features.rst | 166 +++++++++--------- docs/tutorial/src/catalog.py | 6 +- pyproject.toml | 8 +- src/pytest_bdd/__init__.py | 1 + .../compatibility/importlib/resources.py | 2 + src/pytest_bdd/compatibility/pytest.py | 8 +- src/pytest_bdd/compatibility/typing.py | 6 - src/pytest_bdd/cucumber_json.py | 35 ++-- src/pytest_bdd/generation.py | 20 ++- src/pytest_bdd/message_plugin.py | 12 +- src/pytest_bdd/model/gherkin_document.py | 4 +- src/pytest_bdd/parsers.py | 42 +++-- src/pytest_bdd/reporting.py | 7 +- src/pytest_bdd/scenario.py | 10 +- src/pytest_bdd/script/bdd_tree_to_rst.py | 1 + src/pytest_bdd/steps.py | 45 ++--- src/pytest_bdd/struct_bdd/model.py | 16 +- src/pytest_bdd/struct_bdd/model_builder.py | 40 +++-- src/pytest_bdd/utils.py | 33 +--- tests/args/cucumber_expression/test_args.py | 1 + tests/feature/test_background.py | 1 + tests/feature/test_feature_base_dir.py | 1 + tests/feature/test_outline.py | 9 +- tests/feature/test_report.py | 1 + tests/feature/test_scenario.py | 1 + tests/feature/test_scenarios.py | 1 + tests/feature/test_tags.py | 1 + tests/generation/test_generate_missing.py | 1 + tests/library/test_parent.py | 1 + tests/messages/test_messages.py | 8 +- tests/test_utils.py | 33 ++-- tox.ini | 39 ++-- 36 files changed, 293 insertions(+), 302 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 870a4682..e36863a6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,12 +14,11 @@ jobs: strategy: matrix: python-version: - - "3.8" - "3.9" - "3.10" - "3.11" - "3.12" - - "pypy3.8" + - "3.13" - "pypy3.9" - "pypy3.10" os: @@ -47,13 +46,18 @@ jobs: - name: Install npm dependencies run: npm install --global - name: Test with tox + if: > + !((matrix.python-version == '3.12' || + matrix.python-version == '3.11' || + matrix.python-version == '3.10') && + (matrix.os =='macos-latest' || matrix.os =='windows-latest')) run: | tox - name: Gather codecov run: | codecov - name: Build checking - if: "matrix.python-version == '3.12'" + if: "matrix.python-version == '3.13'" env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6217935d..0c82d7f1 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -15,7 +15,7 @@ jobs: submodules: 'recursive' - uses: actions/setup-python@v4 with: - python-version: "3.12" + python-version: "3.13" - name: Install dependencies run: | python -m pip install --upgrade pip build twine diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5e2195f9..597c2fb5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ --- repos: - repo: https://github.com/psf/black - rev: 23.3.0 + rev: 24.10.0 hooks: - id: black args: @@ -11,12 +11,12 @@ repos: - "src/pytest_bdd" - tests - repo: https://github.com/pycqa/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort name: isort (python) - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v5.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -24,12 +24,12 @@ repos: - id: check-added-large-files - id: check-toml - repo: https://github.com/asottile/pyupgrade - rev: v3.7.0 + rev: v3.19.0 hooks: - id: pyupgrade - args: ["--py38-plus"] + args: ["--py39-plus"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.3.0 + rev: v1.13.0 hooks: - id: mypy additional_dependencies: @@ -38,16 +38,16 @@ repos: - types-certifi - types-setuptools - repo: https://github.com/tox-dev/tox-ini-fmt - rev: "1.1.0" + rev: "1.4.1" hooks: - id: tox-ini-fmt - repo: https://github.com/adrienverge/yamllint.git - rev: v1.21.0 + rev: v1.35.1 hooks: - id: yamllint args: [--format, parsable, --strict] - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks - rev: v2.9.0 + rev: v2.14.0 hooks: - id: pretty-format-toml args: [--autofix] diff --git a/CHANGES.rst b/CHANGES.rst index 24d577f3..71ac2922 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -64,6 +64,11 @@ Planned Unreleased ---------- +- Update versions: + - Drop python 3.8 + - Add python 3.13 + - Drop pytest<5.2 +- Added dummy html reporter - Fixed pytest.ini option "disable_feature_autoload" - Improved fixture injection by adding seamless fixtures on plugin/module collection diff --git a/docs/features.rst b/docs/features.rst index 7fe413c5..ed5a81d8 100644 --- a/docs/features.rst +++ b/docs/features.rst @@ -7,75 +7,99 @@ Features -Feature -------- +Tutorial +-------- -Tag conversion -############## +Launch +###### -.. include:: ../features/Feature/Tag conversion.feature +.. include:: ../features/Tutorial/Launch.feature :code: gherkin -Tag -### +Step definition +--------------- -.. include:: ../features/Feature/Tag.feature +Pytest fixtures substitution +############################ + +.. include:: ../features/Step definition/Pytest fixtures substitution.feature :code: gherkin -Description -########### +Target fixtures specification +############################# -.. include:: ../features/Feature/Description.feature +.. include:: ../features/Step definition/Target fixtures specification.feature :code: gherkin -Localization -############ +Parameters +########## -.. include:: ../features/Feature/Localization.feature - :code: gherkin +Conversion +!!!!!!!!!! -Load -#### +.. include:: ../features/Step definition/Parameters/Conversion.feature + :code: gherkin -Autoload +Defaults !!!!!!!! -.. include:: ../features/Feature/Load/Autoload.feature +.. include:: ../features/Step definition/Parameters/Defaults.feature :code: gherkin -Scenario function loader +Injection as fixtures +!!!!!!!!!!!!!!!!!!!!! + +.. include:: ../features/Step definition/Parameters/Injection as fixtures.feature + :code: gherkin + +Parsing by custom parser !!!!!!!!!!!!!!!!!!!!!!!! -.. include:: ../features/Feature/Load/Scenario function loader.feature +.. include:: ../features/Step definition/Parameters/Parsing by custom parser.feature :code: gherkin -Scenario search from base directory -!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +Parsing +!!!!!!! -.. include:: ../features/Feature/Load/Scenario search from base directory.feature +.. include:: ../features/Step definition/Parameters/Parsing.feature :code: gherkin -Scenario search from base url -!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +Step +---- -.. include:: ../features/Feature/Load/Scenario search from base url.feature +Data table +########## + +.. include:: ../features/Step/Data table.feature :code: gherkin -Scenario --------- +Doc string +########## -Tag -### +.. include:: ../features/Step/Doc string.feature + :code: gherkin -.. include:: ../features/Scenario/Tag.feature +Step definition bounding +######################## + +.. include:: ../features/Step/Step definition bounding.feature :code: gherkin +Scenario +-------- + Description ########### .. include:: ../features/Scenario/Description.feature :code: gherkin +Tag +### + +.. include:: ../features/Scenario/Tag.feature + :code: gherkin + Outline ####### @@ -94,80 +118,56 @@ Gathering .. include:: ../features/Report/Gathering.feature :code: gherkin -Tutorial --------- - -Launch -###### - -.. include:: ../features/Tutorial/Launch.feature - :code: gherkin - -Step ----- - -Data table -########## - -.. include:: ../features/Step/Data table.feature - :code: gherkin +Feature +------- -Step definition bounding -######################## +Description +########### -.. include:: ../features/Step/Step definition bounding.feature +.. include:: ../features/Feature/Description.feature :code: gherkin -Doc string -########## +Localization +############ -.. include:: ../features/Step/Doc string.feature +.. include:: ../features/Feature/Localization.feature :code: gherkin -Step definition ---------------- - -Target fixtures specification -############################# +Tag conversion +############## -.. include:: ../features/Step definition/Target fixtures specification.feature +.. include:: ../features/Feature/Tag conversion.feature :code: gherkin -Pytest fixtures substitution -############################ +Tag +### -.. include:: ../features/Step definition/Pytest fixtures substitution.feature +.. include:: ../features/Feature/Tag.feature :code: gherkin -Parameters -########## - -Injection as fixtures -!!!!!!!!!!!!!!!!!!!!! - -.. include:: ../features/Step definition/Parameters/Injection as fixtures.feature - :code: gherkin +Load +#### -Parsing -!!!!!!! +Autoload +!!!!!!!! -.. include:: ../features/Step definition/Parameters/Parsing.feature +.. include:: ../features/Feature/Load/Autoload.feature :code: gherkin -Parsing by custom parser +Scenario function loader !!!!!!!!!!!!!!!!!!!!!!!! -.. include:: ../features/Step definition/Parameters/Parsing by custom parser.feature +.. include:: ../features/Feature/Load/Scenario function loader.feature :code: gherkin -Conversion -!!!!!!!!!! +Scenario search from base directory +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -.. include:: ../features/Step definition/Parameters/Conversion.feature +.. include:: ../features/Feature/Load/Scenario search from base directory.feature :code: gherkin -Defaults -!!!!!!!! +Scenario search from base url +!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -.. include:: ../features/Step definition/Parameters/Defaults.feature +.. include:: ../features/Feature/Load/Scenario search from base url.feature :code: gherkin diff --git a/docs/tutorial/src/catalog.py b/docs/tutorial/src/catalog.py index fab76deb..6cd6ea68 100644 --- a/docs/tutorial/src/catalog.py +++ b/docs/tutorial/src/catalog.py @@ -1,6 +1,8 @@ """This files represents simple `Application under test`""" + +from collections.abc import Iterable from dataclasses import dataclass, field -from typing import Iterable, List +from typing import List @dataclass # Easy way to not write redundant __init__ https://docs.python.org/3/library/dataclasses.html @@ -11,7 +13,7 @@ class Book: @dataclass class Catalog: - storage: List[Book] = field(default_factory=list) + storage: list[Book] = field(default_factory=list) def add_books_to_catalog(self, books: Iterable[Book]): self.storage.extend(books) diff --git a/pyproject.toml b/pyproject.toml index e4f7cecd..72959382 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,11 +37,11 @@ classifiers = [ "Topic :: Software Development :: Libraries", "Topic :: Utilities", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12" + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13" ] dependencies = [ "aiohttp", @@ -80,7 +80,7 @@ maintainers = [ ] name = "pytest-bdd-ng" readme = {file = "README.rst", content-type = "text/x-rst"} -requires-python = ">=3.8" +requires-python = ">=3.9" urls = {Repository = "https://github.com/elchupanebrej/pytest-bdd-ng"} version = "2.1.4" @@ -130,7 +130,7 @@ bdd_tree_to_rst = "pytest_bdd.script.bdd_tree_to_rst:main" [tool.black] line-length = 120 -target-version = ["py38", "py39", "py310", "py311"] +target-version = ["py39", "py310", "py311", "py312", "py313"] verbose = true [tool.isort] diff --git a/src/pytest_bdd/__init__.py b/src/pytest_bdd/__init__.py index da241789..e39b8f87 100644 --- a/src/pytest_bdd/__init__.py +++ b/src/pytest_bdd/__init__.py @@ -1,4 +1,5 @@ """pytest-bdd public API.""" + from pytest_bdd.packaging import get_distribution_version from pytest_bdd.scenario import FeaturePathType, scenario, scenarios from pytest_bdd.steps import given, step, then, when diff --git a/src/pytest_bdd/compatibility/importlib/resources.py b/src/pytest_bdd/compatibility/importlib/resources.py index f91799ee..e40df6a7 100644 --- a/src/pytest_bdd/compatibility/importlib/resources.py +++ b/src/pytest_bdd/compatibility/importlib/resources.py @@ -1,3 +1,5 @@ +from importlib.resources import Resource + from importlib_resources import * from importlib_resources import as_file, files diff --git a/src/pytest_bdd/compatibility/pytest.py b/src/pytest_bdd/compatibility/pytest.py index fadebfc7..78900ffd 100644 --- a/src/pytest_bdd/compatibility/pytest.py +++ b/src/pytest_bdd/compatibility/pytest.py @@ -1,6 +1,7 @@ """ Compatibility module for pytest """ + from __future__ import annotations import sys @@ -20,13 +21,9 @@ from pytest import Module as PytestModule from pytest import fail as _pytest_fail +from pytest_bdd.compatibility.typing import TypeAlias from pytest_bdd.packaging import compare_distribution_version -if sys.version_info >= (3, 10): - from typing import TypeAlias -else: - from typing_extensions import TypeAlias - __all__ = [ "assert_outcomes", "Item", @@ -51,7 +48,6 @@ "TerminalReporter", "Testdir", "TestReport", - "TypeAlias", "wrap_session", ] diff --git a/src/pytest_bdd/compatibility/typing.py b/src/pytest_bdd/compatibility/typing.py index 11d22689..944494f3 100644 --- a/src/pytest_bdd/compatibility/typing.py +++ b/src/pytest_bdd/compatibility/typing.py @@ -1,10 +1,5 @@ import sys -if sys.version_info >= (3, 9): - from typing import Annotated -else: - from typing_extensions import Annotated - if sys.version_info >= (3, 10): from typing import TypeAlias else: @@ -16,7 +11,6 @@ from typing_extensions import Self __all__ = [ - "Annotated", "Self", "TypeAlias", ] diff --git a/src/pytest_bdd/cucumber_json.py b/src/pytest_bdd/cucumber_json.py index 53e18f01..ac87d1c3 100644 --- a/src/pytest_bdd/cucumber_json.py +++ b/src/pytest_bdd/cucumber_json.py @@ -1,10 +1,12 @@ """Cucumber json output formatter.""" + import json import math import os import time +from collections.abc import Sequence from enum import Enum -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Protocol, Sequence, Union, cast, runtime_checkable +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Protocol, Union, cast, runtime_checkable from pydantic import BaseModel, ConfigDict @@ -92,7 +94,7 @@ class DataTableRow(BaseModel): extra="forbid", populate_by_name=True, ) - cells: List[str] + cells: list[str] class Tag(BaseModel): @@ -110,7 +112,7 @@ class Match(BaseModel): populate_by_name=True, ) location: Optional[str] = None - arguments: Optional[List["Argument"]] = None + arguments: Optional[list["Argument"]] = None class Result(BaseModel): @@ -134,7 +136,7 @@ class Step(BaseModel): name: Optional[str] = None result: Optional["Result"] = None doc_string: Optional["DocString"] = None - rows: Optional[List["DataTableRow"]] = None + rows: Optional[list["DataTableRow"]] = None class Hook(BaseModel): @@ -158,10 +160,10 @@ class Element(BaseModel): keyword: Optional[str] = None name: Optional[str] = None description: Optional[str] = None - before: Optional[List["Hook"]] = None - steps: Optional[List["Step"]] = None - after: Optional[List["Hook"]] = None - tags: Optional[List["Tag"]] = None + before: Optional[list["Hook"]] = None + steps: Optional[list["Step"]] = None + after: Optional[list["Hook"]] = None + tags: Optional[list["Tag"]] = None class Feature(BaseModel): @@ -175,8 +177,8 @@ class Feature(BaseModel): keyword: Optional[str] = None name: Optional[str] = None description: Optional[str] = None - elements: Optional[List["Element"]] = None - tags: Optional[List["Tag"]] = None + elements: Optional[list["Element"]] = None + tags: Optional[list["Tag"]] = None class CucumberJson(BaseModel): @@ -185,26 +187,25 @@ class CucumberJson(BaseModel): populate_by_name=True, ) implementation: Optional[str] = None - features: Optional[List["Feature"]] = None + features: Optional[list["Feature"]] = None class LogBDDCucumberJSON: - """Logging plugin for cucumber like json output.""" def __init__(self, logfile: str) -> None: logfile = os.path.expanduser(os.path.expandvars(logfile)) self.logfile = os.path.normpath(os.path.abspath(logfile)) - self.features: Dict[str, dict] = {} + self.features: dict[str, dict] = {} - def _get_result(self, step: Dict[str, Any], report: TestReport, error_message: bool = False) -> Dict[str, Any]: + def _get_result(self, step: dict[str, Any], report: TestReport, error_message: bool = False) -> dict[str, Any]: """Get scenario test run result. :param step: `StepHandler` step we get result for :param report: pytest `Report` object :return: `dict` in form {"status": "", ["error_message": ""]} """ - result: Dict[str, Any] = {} + result: dict[str, Any] = {} if report.passed or not step["failed"]: # ignore setup/teardown result = {"status": "passed"} elif report.failed and step["failed"]: @@ -214,7 +215,7 @@ def _get_result(self, step: Dict[str, Any], report: TestReport, error_message: b result["duration"] = int(math.floor((10**9) * step["duration"])) # nanosec return result - def _serialize_tags(self, item: Dict[str, Any]) -> Sequence[Dict[str, Any]]: + def _serialize_tags(self, item: dict[str, Any]) -> Sequence[dict[str, Any]]: """Serialize item's tags. :param item: json-serialized `Scenario` or `Feature`. @@ -239,7 +240,7 @@ def pytest_runtest_logreport(self, report: TestReport) -> None: # skip if there isn't a result or scenario has no steps return - def stepmap(step: Dict[str, Any]) -> Dict[str, Any]: + def stepmap(step: dict[str, Any]) -> dict[str, Any]: error_message = False if step["failed"] and not scenario.setdefault("failed", False): scenario["failed"] = True diff --git a/src/pytest_bdd/generation.py b/src/pytest_bdd/generation.py index a2727059..9d37c35a 100644 --- a/src/pytest_bdd/generation.py +++ b/src/pytest_bdd/generation.py @@ -1,10 +1,12 @@ """pytest-bdd missing test code generation.""" + import argparse import os.path +from collections.abc import Iterable, Sequence from itertools import chain, filterfalse, zip_longest from operator import lt, methodcaller from pathlib import Path -from typing import Iterable, Optional, Sequence, Tuple, Union, cast +from typing import Optional, Tuple, Union, cast import py from mako.template import Template @@ -83,8 +85,8 @@ def cmdline_main(config: Config) -> Optional[int]: def generate_code( features: Sequence[Feature], - feature_pickles: Sequence[Tuple[Feature, Pickle]], - feature_pickle_steps: Sequence[Tuple[Tuple[Feature, Pickle], PickleStep]], + feature_pickles: Sequence[tuple[Feature, Pickle]], + feature_pickle_steps: Sequence[tuple[tuple[Feature, Pickle], PickleStep]], ) -> str: """Generate test code for the given filenames.""" with as_file(files("pytest_bdd.template").joinpath("test.py.mak")) as path: @@ -204,22 +206,22 @@ def _(config: Config, session: Session) -> None: features = GherkinParser().get_from_paths(config, list(map(Path, config.option.features))) - feature_pickles: Sequence[Tuple[Feature, Pickle]] = list( + feature_pickles: Sequence[tuple[Feature, Pickle]] = list( chain.from_iterable( map( lambda feature: cast( - Iterable[Tuple[Feature, Pickle]], zip_longest((), feature.pickles, fillvalue=feature) + Iterable[tuple[Feature, Pickle]], zip_longest((), feature.pickles, fillvalue=feature) ), features, ) ) ) - feature_pickles_steps: Sequence[Tuple[Tuple[Feature, Pickle], PickleStep]] = list( + feature_pickles_steps: Sequence[tuple[tuple[Feature, Pickle], PickleStep]] = list( chain.from_iterable( map( lambda feature_pickle: cast( - Iterable[Tuple[Tuple[Feature, Pickle], PickleStep]], + Iterable[tuple[tuple[Feature, Pickle], PickleStep]], zip_longest((), feature_pickle[1].steps, fillvalue=feature_pickle), ), feature_pickles, @@ -258,8 +260,8 @@ def _(config: Config, session: Session) -> None: def print_missing_code( features, - feature_pickles: Sequence[Tuple[Feature, Pickle]], - feature_pickle_steps: Sequence[Tuple[Tuple[Feature, Pickle], PickleStep]], + feature_pickles: Sequence[tuple[Feature, Pickle]], + feature_pickle_steps: Sequence[tuple[tuple[Feature, Pickle], PickleStep]], unique_steps, ) -> None: """Print missing code with TerminalWriter.""" diff --git a/src/pytest_bdd/message_plugin.py b/src/pytest_bdd/message_plugin.py index e6c33f7b..ccd482ce 100644 --- a/src/pytest_bdd/message_plugin.py +++ b/src/pytest_bdd/message_plugin.py @@ -86,8 +86,8 @@ class MessagePlugin: config: Config = attrib() current_test_case = attrib(default=None) current_test_case_step_to_definition_mapping = attrib(default=None) - parameter_type_registry: Set[int] = set() - hook_registry: Set[int] = set() + parameter_type_registry: set[int] = set() + hook_registry: set[int] = set() npm_formatter_package = "@cucumber/html-formatter" def __attrs_post_init__(self): @@ -355,9 +355,9 @@ def pytest_runtest_setup(self, item): yield session = item.session - config: Union[ - Config, PytestBDDIdGeneratorHandler - ] = session.config # https://github.com/python/typing/issues/213 + config: Union[Config, PytestBDDIdGeneratorHandler] = ( + session.config + ) # https://github.com/python/typing/issues/213 hook_handler = cast(Config, config).hook @@ -439,7 +439,7 @@ def pytest_runtest_setup(self, item): test_step = TestStep( id=cast(PytestBDDIdGeneratorHandler, config).pytest_bdd_id_generator.get_next_id(), pickle_step_id=step.id, - step_definition_ids=[step_definition.as_message(config).id] + step_definition_ids=[step_definition.as_message(config).id], # TODO Check step_match_arguments_lists ) test_steps.append(test_step) diff --git a/src/pytest_bdd/model/gherkin_document.py b/src/pytest_bdd/model/gherkin_document.py index d380af4c..364b0d8c 100644 --- a/src/pytest_bdd/model/gherkin_document.py +++ b/src/pytest_bdd/model/gherkin_document.py @@ -19,9 +19,11 @@ :note: There are no multiline steps, the description of the step must fit in one line. """ + +from collections.abc import Sequence from itertools import chain from textwrap import dedent -from typing import Sequence, Union, cast +from typing import Union, cast from attr import Factory, attrib, attrs from gherkin.errors import CompositeParserException # type: ignore[import] diff --git a/src/pytest_bdd/parsers.py b/src/pytest_bdd/parsers.py index 8646a951..94ab30ba 100644 --- a/src/pytest_bdd/parsers.py +++ b/src/pytest_bdd/parsers.py @@ -1,5 +1,7 @@ """StepHandler parsers.""" + from abc import ABCMeta, abstractmethod +from collections.abc import Collection, Iterable, Sequence from enum import Enum from functools import partial, singledispatchmethod from itertools import chain, filterfalse @@ -7,7 +9,7 @@ from re import Match from re import Pattern as _RePattern from re import compile as re_compile -from typing import Any, Collection, Dict, Iterable, Optional, Protocol, Sequence, Type, Union, cast, runtime_checkable +from typing import Any, Dict, Optional, Protocol, Type, Union, cast, runtime_checkable import parse as base_parse import parse_type.cfparse as base_cfparse @@ -23,8 +25,7 @@ from pytest_bdd.utils import StringableProtocol, stringify -class ParserBuildValueError(ValueError): - ... +class ParserBuildValueError(ValueError): ... @runtime_checkable @@ -33,18 +34,14 @@ class StepParserProtocol(Protocol): def parse_arguments( self, request: FixtureRequest, name: str, anonymous_group_names: Optional[Iterable[str]] = None - ) -> Optional[Dict[str, Any]]: - ... # pragma: no cover + ) -> Optional[dict[str, Any]]: ... # pragma: no cover @property - def arguments(self) -> Collection[str]: - ... # pragma: no cover + def arguments(self) -> Collection[str]: ... # pragma: no cover - def is_matching(self, request: FixtureRequest, name: str) -> bool: - ... # pragma: no cover + def is_matching(self, request: FixtureRequest, name: str) -> bool: ... # pragma: no cover - def __str__(self) -> str: - ... # pragma: no cover + def __str__(self) -> str: ... # pragma: no cover class RegistryMode(Enum): @@ -60,7 +57,7 @@ class StepParser(StepParserProtocol, metaclass=ABCMeta): @abstractmethod def parse_arguments( self, request: FixtureRequest, name: str, anonymous_group_names: Optional[Iterable[str]] = None - ) -> Optional[Dict[str, Any]]: + ) -> Optional[dict[str, Any]]: """Get step arguments from the given step name. :return: `dict` of step arguments @@ -197,7 +194,7 @@ def cfparse(cls, *args, **kwargs): def parse_arguments( self, request: FixtureRequest, name: str, anonymous_group_names: Optional[Iterable[str]] = None - ) -> Union[Dict[str, Any]]: + ) -> Union[dict[str, Any]]: match = self.parser.parse(name) group_dict = cast(dict, match.named) if anonymous_group_names is not None: @@ -238,7 +235,7 @@ def __init__(self, name: Union[StringableProtocol, str, bytes]) -> None: def parse_arguments( self, request: FixtureRequest, name: str, anonymous_group_names: Optional[Iterable[str]] = None - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """No parameters are available for simple string step. :return: `dict` of step arguments @@ -259,14 +256,13 @@ def __str__(self): @runtime_checkable class _CucumberExpressionProtocol(Protocol): - def match(self, text: str) -> Optional[Sequence[CucumberExpressionArgument]]: - ... # pragma: no cover + def match(self, text: str) -> Optional[Sequence[CucumberExpressionArgument]]: ... # pragma: no cover class _CucumberExpression(StepParser): pattern: str - expression_type: Type[Union[CucumberExpression, CucumberRegularExpression]] + expression_type: type[Union[CucumberExpression, CucumberRegularExpression]] parameter_type_registry_like: Union[ParameterTypeRegistry, Any] parameter_type_registry = ParameterTypeRegistry() # default registry @@ -278,7 +274,7 @@ def is_matching(self, request: FixtureRequest, name: str) -> bool: def parse_arguments( self, request: FixtureRequest, name: str, anonymous_group_names: Optional[Iterable[str]] = None - ) -> Optional[Dict[str, Any]]: + ) -> Optional[dict[str, Any]]: return dict( zip( anonymous_group_names or [], @@ -434,7 +430,7 @@ def is_matching(self, request: FixtureRequest, name: str) -> bool: def parse_arguments( self, request: FixtureRequest, name: str, anonymous_group_names: Optional[Iterable[str]] = None - ) -> Optional[Dict[str, Any]]: + ) -> Optional[dict[str, Any]]: for parser in self.parser_by_priorities: if parser is not None and parser.is_matching(request, name): arguments = parser.parse_arguments(request, name, anonymous_group_names=anonymous_group_names) @@ -448,9 +444,11 @@ def arguments(self) -> Collection[str]: return [ *chain.from_iterable( map( - lambda parser: [] # type:ignore[no-any-return] - if (args := getattr(parser, "arguments")) is None - else args, + lambda parser: ( + [] # type:ignore[no-any-return] + if (args := getattr(parser, "arguments")) is None + else args + ), self.parser_by_priorities, ) ) diff --git a/src/pytest_bdd/reporting.py b/src/pytest_bdd/reporting.py index 29e65116..5cff6993 100644 --- a/src/pytest_bdd/reporting.py +++ b/src/pytest_bdd/reporting.py @@ -3,6 +3,7 @@ Collection of the scenario execution statuses, timing and other information that enriches the pytest test reporting. """ + import time from typing import Any, Callable, Dict, List @@ -28,7 +29,7 @@ def __init__(self, step: PickleStep) -> None: self.step = step self.started = time.perf_counter() - def serialize(self, feature: Feature) -> Dict[str, Any]: + def serialize(self, feature: Feature) -> dict[str, Any]: """Serialize the step execution report. :return: Serialized step execution report. @@ -70,7 +71,7 @@ class ScenarioReport: feature: Feature = attrib() scenario: Pickle = attrib() - step_reports: List[StepReport] = attrib(default=Factory(list)) + step_reports: list[StepReport] = attrib(default=Factory(list)) @property def current_step_report(self) -> StepReport: @@ -89,7 +90,7 @@ def add_step_report(self, step_report: StepReport) -> None: """ self.step_reports.append(step_report) - def serialize(self) -> Dict[str, Any]: + def serialize(self) -> dict[str, Any]: """Serialize scenario execution report in order to transfer reporting from nodes in the distributed mode. :return: Serialized report. diff --git a/src/pytest_bdd/scenario.py b/src/pytest_bdd/scenario.py index ea5f2c0d..9040ae3f 100644 --- a/src/pytest_bdd/scenario.py +++ b/src/pytest_bdd/scenario.py @@ -1,7 +1,8 @@ import collections +from collections.abc import Iterable from enum import Enum from pathlib import Path -from typing import Callable, Iterable, Optional, Type, Union +from typing import Callable, Optional, Type, Union from pytest import mark @@ -64,7 +65,7 @@ def scenario( features_path_type: Optional[Union[FeaturePathType, str]] = FeaturePathType.PATH, features_mimetype: Optional[Mimetype] = None, return_test_decorator=True, - parser_type: Optional[Type[ParserProtocol]] = None, + parser_type: Optional[type[ParserProtocol]] = None, parse_args=Args((), {}), locators=(), ): @@ -107,7 +108,7 @@ def scenarios( features_base_url: Optional[str] = None, features_path_type: Optional[Union[FeaturePathType, str]] = FeaturePathType.PATH, features_mimetype: Optional[Mimetype] = None, - parser_type: Optional[Type[ParserProtocol]] = None, + parser_type: Optional[type[ParserProtocol]] = None, parse_args=Args((), {}), locators=(), ): @@ -156,8 +157,7 @@ def scenarios( else: @decorator - def test(): - ... + def test(): ... test.__name__ = next(iter(test_names)) diff --git a/src/pytest_bdd/script/bdd_tree_to_rst.py b/src/pytest_bdd/script/bdd_tree_to_rst.py index 8a79e16a..1f79f777 100644 --- a/src/pytest_bdd/script/bdd_tree_to_rst.py +++ b/src/pytest_bdd/script/bdd_tree_to_rst.py @@ -4,6 +4,7 @@ bdd_tree_to_rst.py """ + import sys from collections import deque from functools import reduce diff --git a/src/pytest_bdd/steps.py b/src/pytest_bdd/steps.py index 5ec9faef..53891083 100644 --- a/src/pytest_bdd/steps.py +++ b/src/pytest_bdd/steps.py @@ -34,24 +34,12 @@ def given_beautiful_article(article): pass """ + import warnings +from collections.abc import Collection, Iterable, Iterator, Mapping, Sequence from contextlib import suppress from inspect import getfile, getsourcelines -from typing import ( - Any, - Callable, - Collection, - Dict, - Iterable, - Iterator, - Mapping, - Optional, - Sequence, - Set, - Type, - Union, - cast, -) +from typing import Any, Callable, Dict, Optional, Set, Type, Union, cast from uuid import uuid4 from warnings import warn @@ -65,7 +53,8 @@ def given_beautiful_article(article): from messages import PickleStep as Step # type:ignore[attr-defined] from messages import SourceReference, StepDefinition, StepDefinitionPattern # type:ignore[attr-defined] from pytest_bdd.compatibility.path import relpath -from pytest_bdd.compatibility.pytest import Config, FixtureLookupError, Parser, TypeAlias, get_config_root_path +from pytest_bdd.compatibility.pytest import Config, FixtureLookupError, Parser, get_config_root_path +from pytest_bdd.compatibility.typing import TypeAlias from pytest_bdd.model import Feature, StepType from pytest_bdd.model.messages_extension import ExpressionType as ExpressionTypeExtension from pytest_bdd.parsers import StepParser @@ -100,10 +89,10 @@ def add_options(parser: Parser): def given( parserlike: Any, anonymous_group_names: Optional[Iterable[str]] = None, - converters: Optional[Dict[str, Callable]] = None, + converters: Optional[dict[str, Callable]] = None, target_fixture: Optional[str] = None, target_fixtures: Optional[Sequence[str]] = None, - params_fixtures_mapping: Union[Set[str], Dict[str, str], Any] = True, + params_fixtures_mapping: Union[set[str], dict[str, str], Any] = True, param_defaults: Optional[dict] = None, liberal: Optional[bool] = None, stacklevel=1, @@ -141,10 +130,10 @@ def given( def when( parserlike: Any, anonymous_group_names: Optional[Iterable[str]] = None, - converters: Optional[Dict[str, Callable]] = None, + converters: Optional[dict[str, Callable]] = None, target_fixture: Optional[str] = None, target_fixtures: Optional[Sequence[str]] = None, - params_fixtures_mapping: Union[Set[str], Dict[str, str], Any] = True, + params_fixtures_mapping: Union[set[str], dict[str, str], Any] = True, param_defaults: Optional[dict] = None, liberal: Optional[bool] = None, stacklevel=1, @@ -181,10 +170,10 @@ def when( def then( parserlike: Any, anonymous_group_names: Optional[Iterable[str]] = None, - converters: Optional[Dict[str, Callable]] = None, + converters: Optional[dict[str, Callable]] = None, target_fixture: Optional[str] = None, target_fixtures: Optional[Sequence[str]] = None, - params_fixtures_mapping: Union[Set[str], Dict[str, str], Any] = True, + params_fixtures_mapping: Union[set[str], dict[str, str], Any] = True, param_defaults: Optional[dict] = None, liberal: Optional[bool] = None, stacklevel=1, @@ -221,10 +210,10 @@ def then( def step( parserlike: Any, anonymous_group_names: Optional[Iterable[str]] = None, - converters: Optional[Dict[str, Callable]] = None, + converters: Optional[dict[str, Callable]] = None, target_fixture: Optional[str] = None, target_fixtures: Optional[Sequence[str]] = None, - params_fixtures_mapping: Union[Set[str], Dict[str, str], Any] = True, + params_fixtures_mapping: Union[set[str], dict[str, str], Any] = True, param_defaults: Optional[dict] = None, liberal: Optional[bool] = None, stacklevel=1, @@ -363,7 +352,7 @@ class Definition: type_: Optional[Union[str, StepType]] = attrib() parser: StepParser = attrib() anonymous_group_names: Optional[Collection[str]] = attrib() - converters: Dict[str, Callable] = attrib() + converters: dict[str, Callable] = attrib() params_fixtures_mapping: Union[ # type: ignore[valid-type] Collection[str], Mapping[Union[str, Any], Union[str, Any, None]], Any ] = attrib() @@ -454,7 +443,7 @@ def get_parameters(self, request: FixtureRequest, step: Step): @attrs class Registry: - registry: Set["StepHandler.Definition"] = attrib(default=Factory(set)) + registry: set["StepHandler.Definition"] = attrib(default=Factory(set)) parent: "StepHandler.Registry" = attrib(default=None, init=False) @classmethod @@ -509,10 +498,10 @@ def decorator_builder( step_type: Optional[Union[str, StepType]], step_parserlike: Any, anonymous_group_names: Optional[Iterable[str]] = None, - converters: Optional[Dict[str, Callable]] = None, + converters: Optional[dict[str, Callable]] = None, target_fixture: Optional[str] = None, target_fixtures: Optional[Sequence[str]] = None, - params_fixtures_mapping: Union[Set[str], Dict[str, str], Any] = True, + params_fixtures_mapping: Union[set[str], dict[str, str], Any] = True, param_defaults: Optional[dict] = None, liberal: Optional[Any] = None, stacklevel=2, diff --git a/src/pytest_bdd/struct_bdd/model.py b/src/pytest_bdd/struct_bdd/model.py index bd7d5229..16962de1 100644 --- a/src/pytest_bdd/struct_bdd/model.py +++ b/src/pytest_bdd/struct_bdd/model.py @@ -1,11 +1,12 @@ from collections import defaultdict, namedtuple +from collections.abc import Mapping, Sequence from enum import Enum from functools import partial from inspect import getfile from itertools import chain, product, starmap from operator import attrgetter, eq, is_not from pathlib import Path -from typing import Any, ClassVar, List, Literal, Mapping, Optional, Sequence, Type, Union +from typing import Annotated, Any, ClassVar, List, Literal, NamedTuple, Optional, Type, Union from attr import attrib, attrs from pydantic import ( # type:ignore[attr-defined] # migration to pydantic 2 @@ -19,7 +20,7 @@ ) from messages import KeywordType, MediaType, Source # type:ignore[attr-defined] -from pytest_bdd.compatibility.typing import Annotated, Self +from pytest_bdd.compatibility.typing import Self from pytest_bdd.mimetypes import Mimetype from pytest_bdd.scenario_locator import ScenarioLocatorFilterMixin from pytest_bdd.utils import deepattrgetter @@ -96,7 +97,7 @@ class Join(BaseModel): populate_by_name=True, ) - tables: List[Annotated[Union[Table, "Join", SubTable], convert_sub_tables_to_tables]] = Field( + tables: list[Annotated[Union[Table, "Join", SubTable], convert_sub_tables_to_tables]] = Field( default_factory=list, alias="Join" ) @@ -222,15 +223,18 @@ class StepPrototype(Node): ] = Field(default_factory=list, alias="Steps") type: Optional[StepKeywordType] = Field(default=Keyword.Star, alias="Type") - data: List[Annotated[Union[Table, Join, SubTable], convert_sub_tables_to_tables]] = Field( + data: list[Annotated[Union[Table, Join, SubTable], convert_sub_tables_to_tables]] = Field( default_factory=list, alias="Data" ) - examples: List[Annotated[Union[Table, Join, SubTable], convert_sub_tables_to_tables]] = Field( + examples: list[Annotated[Union[Table, Join, SubTable], convert_sub_tables_to_tables]] = Field( default_factory=list, alias="Examples" ) keyword_type: Optional[KeywordType] = Field(KeywordType.unknown) - Route: ClassVar[Type] = namedtuple("Route", ["tags", "steps", "example_table"]) + class Route(NamedTuple): + tags: Optional[Sequence[str]] + steps: list["StepPrototype"] + example_table: Union[Table, "Join", SubTable] @model_validator(mode="after") # type: ignore[misc] # migration to pydantic 2 def set_keyword_type(self) -> Self: diff --git a/src/pytest_bdd/struct_bdd/model_builder.py b/src/pytest_bdd/struct_bdd/model_builder.py index 701048e0..12f1fe7a 100644 --- a/src/pytest_bdd/struct_bdd/model_builder.py +++ b/src/pytest_bdd/struct_bdd/model_builder.py @@ -102,14 +102,16 @@ def steps_gen(steps): keyword_type=step_keyword_type.value, **( ( - lambda rows: dict( - data_table=DataTable( - rows=rows, - location=Location(column=0, line=0), # type: ignore[call-arg] - ) # type: ignore[call-arg] + lambda rows: ( + dict( + data_table=DataTable( + rows=rows, + location=Location(column=0, line=0), # type: ignore[call-arg] + ) # type: ignore[call-arg] + ) + if rows + else {} ) - if rows - else {} )( [ *filterfalse( @@ -117,13 +119,15 @@ def steps_gen(steps): map( lambda row_values: ( ( - lambda cells: TableRow( - id=next(id_generator), - location=Location(column=0, line=0), # type: ignore[call-arg] - cells=cells, - ) # type: ignore[call-arg] - if cells - else None + lambda cells: ( + TableRow( + id=next(id_generator), + location=Location(column=0, line=0), # type: ignore[call-arg] + cells=cells, + ) # type: ignore[call-arg] + if cells + else None + ) )( [ *map( @@ -161,9 +165,11 @@ def steps_gen(steps): yield FeatureChild( scenario=Scenario( description=route.steps[0].description or "", - examples=[ExampleASTBuilder(route.example_table).build(id_generator=id_generator)] - if route.example_table.values - else [], + examples=( + [ExampleASTBuilder(route.example_table).build(id_generator=id_generator)] + if route.example_table.values + else [] + ), id=next(id_generator), keyword="Scenario", location=Location(column=0, line=0), diff --git a/src/pytest_bdd/utils.py b/src/pytest_bdd/utils.py index 51eb7922..b2d48d7f 100644 --- a/src/pytest_bdd/utils.py +++ b/src/pytest_bdd/utils.py @@ -1,33 +1,20 @@ """Various utility functions.""" + import base64 import pickle import re import sys from collections import defaultdict +from collections.abc import Collection, Mapping, Sequence from contextlib import contextmanager, nullcontext, suppress from enum import Enum from functools import reduce from inspect import getframeinfo, signature from itertools import tee from operator import attrgetter, getitem, itemgetter +from re import Pattern from sys import _getframe -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Collection, - Dict, - Literal, - Mapping, - Optional, - Pattern, - Protocol, - Sequence, - Type, - Union, - cast, - runtime_checkable, -) +from typing import TYPE_CHECKING, Any, Callable, Dict, Literal, Optional, Protocol, Type, Union, cast, runtime_checkable from urllib.parse import urlparse from _pytest.fixtures import FixtureDef, FixtureRequest @@ -56,7 +43,7 @@ def get_args(func: Callable) -> Sequence[str]: return [param.name for param in params if param.kind == param.POSITIONAL_OR_KEYWORD] -def get_caller_module_locals(stacklevel: int = 1) -> Dict[str, Any]: +def get_caller_module_locals(stacklevel: int = 1) -> dict[str, Any]: """Get the caller module locals dictionary. We use sys._getframe instead of inspect.stack(0) because the latter is way slower, since it iterates over @@ -140,7 +127,7 @@ def warm_up(self, *items): @classmethod def instantiate_from_collection_or_bool( - cls, bool_or_items: Union[Collection[str], Dict[str, Any], Any] = True, *, warm_up_keys=() + cls, bool_or_items: Union[Collection[str], dict[str, Any], Any] = True, *, warm_up_keys=() ): if isinstance(bool_or_items, Collection): if not isinstance(bool_or_items, Mapping): @@ -200,8 +187,7 @@ def func(obj): return func -class _NoneException(Exception): - ... +class _NoneException(Exception): ... class Empty(Enum): @@ -288,8 +274,7 @@ def make_python_name(string: str) -> str: @runtime_checkable class StringableProtocol(Protocol): - def __str__(self) -> str: - ... # pragma: no cover + def __str__(self) -> str: ... # pragma: no cover def stringify(value: Union[StringableProtocol, str, bytes]) -> str: @@ -311,7 +296,7 @@ def __next__(self): @contextmanager def doesnt_raise( - expected_exception: Union[Type[Exception], Sequence[Type[Exception]]], + expected_exception: Union[type[Exception], Sequence[type[Exception]]], *, match: Optional[Union[str, Pattern[str]]] = None, suppress_not_matched=True, diff --git a/tests/args/cucumber_expression/test_args.py b/tests/args/cucumber_expression/test_args.py index 9fc54064..0729b57e 100644 --- a/tests/args/cucumber_expression/test_args.py +++ b/tests/args/cucumber_expression/test_args.py @@ -1,4 +1,5 @@ """StepHandler arguments tests.""" + from typing import TYPE_CHECKING from pytest import mark diff --git a/tests/feature/test_background.py b/tests/feature/test_background.py index 2fbc8d86..8b3c03e8 100644 --- a/tests/feature/test_background.py +++ b/tests/feature/test_background.py @@ -1,4 +1,5 @@ """Test feature background.""" + from textwrap import dedent # language=gherkin diff --git a/tests/feature/test_feature_base_dir.py b/tests/feature/test_feature_base_dir.py index 2e939404..f9581a3a 100644 --- a/tests/feature/test_feature_base_dir.py +++ b/tests/feature/test_feature_base_dir.py @@ -1,4 +1,5 @@ """Test feature base dir.""" + import pytest NOT_EXISTING_FEATURE_PATHS = [".", "/does/not/exist/"] diff --git a/tests/feature/test_outline.py b/tests/feature/test_outline.py index 5e8f1c94..99573074 100644 --- a/tests/feature/test_outline.py +++ b/tests/feature/test_outline.py @@ -1,4 +1,5 @@ """Scenario Outline tests.""" + from operator import lt from textwrap import dedent @@ -224,9 +225,11 @@ def test_outline(other_fixture): [ param( "GherkinParser", - marks=[mark.xfail(reason="https://github.com/cucumber/common/issues/1954")] - if compare_distribution_version("gherkin-official", "24.1", lt) - else [], + marks=( + [mark.xfail(reason="https://github.com/cucumber/common/issues/1954")] + if compare_distribution_version("gherkin-official", "24.1", lt) + else [] + ), ), ], ) diff --git a/tests/feature/test_report.py b/tests/feature/test_report.py index 8108a02e..e695a396 100644 --- a/tests/feature/test_report.py +++ b/tests/feature/test_report.py @@ -1,4 +1,5 @@ """Test scenario reporting.""" + import re from pathlib import Path from typing import Optional, Union diff --git a/tests/feature/test_scenario.py b/tests/feature/test_scenario.py index a9339ea8..39af2be4 100644 --- a/tests/feature/test_scenario.py +++ b/tests/feature/test_scenario.py @@ -1,4 +1,5 @@ """Test scenario decorator.""" + from textwrap import dedent from pytest_bdd.compatibility.pytest import assert_outcomes diff --git a/tests/feature/test_scenarios.py b/tests/feature/test_scenarios.py index 4593d71e..9ab8475f 100644 --- a/tests/feature/test_scenarios.py +++ b/tests/feature/test_scenarios.py @@ -1,4 +1,5 @@ """Test scenarios shortcut.""" + from textwrap import dedent from pytest_bdd.compatibility.pytest import assert_outcomes diff --git a/tests/feature/test_tags.py b/tests/feature/test_tags.py index 6f805bed..af23dea3 100644 --- a/tests/feature/test_tags.py +++ b/tests/feature/test_tags.py @@ -1,4 +1,5 @@ """Test tags.""" + from operator import ge from pytest_bdd.packaging import compare_distribution_version diff --git a/tests/generation/test_generate_missing.py b/tests/generation/test_generate_missing.py index a2246dc8..a790ce87 100644 --- a/tests/generation/test_generate_missing.py +++ b/tests/generation/test_generate_missing.py @@ -1,4 +1,5 @@ """Code generation and assertion tests.""" + import itertools import textwrap diff --git a/tests/library/test_parent.py b/tests/library/test_parent.py index 3af28d5c..0c6d508e 100644 --- a/tests/library/test_parent.py +++ b/tests/library/test_parent.py @@ -2,6 +2,7 @@ Check the parent givens are collected and overridden in the local conftest. """ + import textwrap from textwrap import dedent diff --git a/tests/messages/test_messages.py b/tests/messages/test_messages.py index df212801..c17855c5 100644 --- a/tests/messages/test_messages.py +++ b/tests/messages/test_messages.py @@ -1,8 +1,9 @@ import json +from collections.abc import Iterable from functools import partial from pathlib import Path from pprint import pformat -from typing import TYPE_CHECKING, Iterable, Type, Union, cast +from typing import TYPE_CHECKING, Type, Union, cast from pydantic import ValidationError @@ -60,12 +61,11 @@ def unfold_message(message: Message): raise ValueError("Empty message was given") -def list_filter_by_type(t: Union[Type, Iterable[Type]], items): +def list_filter_by_type(t: Union[type, Iterable[type]], items): return list(filter(partial(flip(isinstance), tuple(t) if isinstance(t, Iterable) else t), items)) -class ParseError(RuntimeError): - ... +class ParseError(RuntimeError): ... def parse_and_unflold_messages(lines): diff --git a/tests/test_utils.py b/tests/test_utils.py index 026c379c..372aa24e 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -30,8 +30,7 @@ class Foo: def test_get_default_for_attribute(): - class Foo: - ... + class Foo: ... item = Foo() @@ -39,8 +38,7 @@ class Foo: def test_get_default_for_multi_attribute(): - class Foo: - ... + class Foo: ... item = Foo() @@ -48,8 +46,7 @@ class Foo: def test_raise_on_missing_attribute(): - class Foo: - ... + class Foo: ... item = Foo() @@ -58,8 +55,7 @@ class Foo: def test_raise_on_missing_multi_attribute(): - class Foo: - ... + class Foo: ... item = Foo() @@ -68,8 +64,7 @@ class Foo: def test_raise_on_missing_nested_attribute(): - class Foo: - ... + class Foo: ... item = Foo() @@ -78,8 +73,7 @@ class Foo: def test_raise_on_missing_multi_nested_attribute(): - class Foo: - ... + class Foo: ... item = Foo() @@ -163,8 +157,7 @@ class Foo: def test_setdefaultattr_set_nonexisting_attr_value(): - class Dumb: - ... + class Dumb: ... dumb = Dumb() setdefaultattr(dumb, "foo", value=10) @@ -173,8 +166,7 @@ class Dumb: def test_setdefaultattr_set_nonexisting_attr_value_factory(): - class Dumb: - ... + class Dumb: ... dumb = Dumb() setdefaultattr(dumb, "foo", value_factory=lambda: 10) @@ -183,8 +175,7 @@ class Dumb: def test_setdefaultattr_not_set_existing_attr_value(): - class Dumb: - ... + class Dumb: ... dumb = Dumb() dumb.foo = 20 @@ -194,8 +185,7 @@ class Dumb: def test_setdefaultattr_not_set_existing_attr_value_factory(): - class Dumb: - ... + class Dumb: ... dumb = Dumb() dumb.foo = 20 @@ -205,8 +195,7 @@ class Dumb: def test_setdefaultattr_for_both_factory_and_value(): - class Dumb: - ... + class Dumb: ... with pytest.raises(ValueError): setdefaultattr(Dumb(), "a", value=10, value_factory=lambda: 20) diff --git a/tox.ini b/tox.ini index 181fb939..e3a1562e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,17 +1,17 @@ [tox] -envlist = - py312-pre-commit-lin - py312-pytest{625, 83, 82, 81, 80, 74, 73, 72, 71, 70, latest}-mypy-lin - py312-pytest{625, 83, 82, 81, 80, 74, 73, 72, 71, 70, latest}-coverage-lin - py312-pytestlatest-gherkin{24, latest}-xdist-coverage-{lin, win, mac} - py312-pytest80-allure-coverage-{lin, win, mac} - py39-pytest{62, 61, 60, 54, 53, 52, 51, 50}-coverage-lin - py38-pytest{62, 54}-coverage-{win, mac} - py{py39, py38, 38}-pytest{62, 54}-coverage-lin - py{311, 310, 39, 38}-pytestlatest-mypy-lin - py{py310, py39, py38, 311, 310, 39, 38}-pytestlatest-coverage-{lin, win, mac} - py{py310, py39, py38, 311, 310, 39, 38}-pytestlatest-xdist-coverage-{lin, win, mac} -isolated_build = true +requires = + tox>=4.2 +env_list = + py313-pre-commit-lin + py313-pytest{625, 83, 82, 81, 80, 74, 73, 72, 71, 70, latest}-mypy-lin + py313-pytest{625, 83, 82, 81, 80, 74, 73, 72, 71, 70, latest}-coverage-{lin, win, mac} + py313-pytestlatest-gherkin{24, latest}-xdist-coverage-{lin, win, mac} + py313-pytest80-allure-coverage-{lin, win, mac} + py39-pytest{62, 61, 60, 54, 53, 52}-coverage-{lin, win, mac} + pypy39-pytest{62, 54}-coverage-lin + py{312, 311, 310, 39}-pytestlatest-mypy-lin + py{py310, py39, 312, 311, 310, 39}-pytestlatest-coverage-lin + py{py310, py39, 312, 311, 310, 39}-pytestlatest-xdist-coverage-lin distshare = {homedir}/.tox/distshare [testenv] @@ -22,14 +22,12 @@ deps = coverage: coverage gherkin24: gherkin-official~=24.0.0 gherkinlatest: gherkin-official - pytest50: pytest~=5.0.0 - pytest51: pytest~=5.1.0 pytest52: pytest~=5.2.0 pytest53: pytest~=5.3.0 pytest54: pytest~=5.4.0 pytest60: pytest~=6.0.0 pytest61: pytest~=6.1.0 - pytest62: pytest>=6.2,<6.2.5 + pytest62: pytest<6.2.5,>=6.2 pytest625: pytest==6.2.5 pytest70: pytest~=7.0.0 pytest71: pytest~=7.1.0 @@ -42,7 +40,7 @@ deps = pytest83: pytest~=8.3.0 pytestlatest: pytest xdist: pytest-xdist -setenv = +set_env = COLUMNS = 80 coverage: _PYTEST_CMD = coverage run --append -m pytest xdist: _PYTEST_MORE_ARGS = -n3 -rfsxX @@ -53,14 +51,14 @@ platform = mac: darwin win: win32 -[testenv:py312-pre-commit] +[testenv:py313-pre-commit] skip_install = true deps = pre-commit commands = pre-commit run --all-files -[testenv:py{38,39,310,311,312}-pytest{latest,625,70,71,72,73,74}-mypy] +[testenv:py{39,310,311,312,313}-pytest{latest,625,70,71,72,73,74}-mypy] deps = .[testtypes] commands = @@ -68,11 +66,10 @@ commands = [gh-actions] python = - 3.8: py38 3.9: py39 3.10: py310 3.11: py311 3.12: py312 - pypy-3.8: pypy38 + 3.13: py313 pypy-3.9: pypy39 pypy-3.10: pypy310