mirror of https://github.com/pytest-dev/pytest.git
Merge pull request #12496 from RonnyPfannschmidt/ronny/backport-annotations-8.2
backport annotations to 8.2
This commit is contained in:
commit
76065e5028
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
# 2.7.5 3.3.2
|
||||
# FilesCompleter 75.1109 69.2116
|
||||
# FastFilesCompleter 0.7383 1.0760
|
||||
from __future__ import annotations
|
||||
|
||||
import timeit
|
||||
|
||||
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
for i in range(1000):
|
||||
exec("def test_func_%d(): pass" % i)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from unittest import TestCase # noqa: F401
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
for i in range(5000):
|
||||
exec(
|
||||
f"""
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
Migrated all internal type-annotations to the python3.10+ style by using the `annotations` future import.
|
||||
|
||||
-- by :user:`RonnyPfannschmidt`
|
|
@ -15,15 +15,19 @@
|
|||
#
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
# The short X.Y version.
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
from textwrap import dedent
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from _pytest import __version__ as version
|
||||
from _pytest import __version__ as full_version
|
||||
|
||||
|
||||
version = full_version.split("+")[0]
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import sphinx.application
|
||||
|
||||
|
@ -189,6 +193,7 @@ nitpick_ignore = [
|
|||
("py:class", "SubRequest"),
|
||||
("py:class", "TerminalReporter"),
|
||||
("py:class", "_pytest._code.code.TerminalRepr"),
|
||||
("py:class", "TerminalRepr"),
|
||||
("py:class", "_pytest.fixtures.FixtureFunctionMarker"),
|
||||
("py:class", "_pytest.logging.LogCaptureHandler"),
|
||||
("py:class", "_pytest.mark.structures.ParameterSet"),
|
||||
|
@ -210,13 +215,16 @@ nitpick_ignore = [
|
|||
("py:class", "_PluggyPlugin"),
|
||||
# TypeVars
|
||||
("py:class", "_pytest._code.code.E"),
|
||||
("py:class", "E"), # due to delayed annotation
|
||||
("py:class", "_pytest.fixtures.FixtureFunction"),
|
||||
("py:class", "_pytest.nodes._NodeType"),
|
||||
("py:class", "_NodeType"), # due to delayed annotation
|
||||
("py:class", "_pytest.python_api.E"),
|
||||
("py:class", "_pytest.recwarn.T"),
|
||||
("py:class", "_pytest.runner.TResult"),
|
||||
("py:obj", "_pytest.fixtures.FixtureValue"),
|
||||
("py:obj", "_pytest.stash.T"),
|
||||
("py:class", "_ScopeName"),
|
||||
]
|
||||
|
||||
|
||||
|
@ -455,7 +463,7 @@ intersphinx_mapping = {
|
|||
}
|
||||
|
||||
|
||||
def setup(app: "sphinx.application.Sphinx") -> None:
|
||||
def setup(app: sphinx.application.Sphinx) -> None:
|
||||
app.add_crossref_type(
|
||||
"fixture",
|
||||
"fixture",
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
collect_ignore = ["conf.py"]
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from pytest import raises
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
hello = "world"
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
import shutil
|
||||
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
def setup_module(module):
|
||||
module.TestStateFullThing.classcount = 0
|
||||
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
collect_ignore = ["nonpython", "customdirectory"]
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# content of conftest.py
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# content of test_first.py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def test_1():
|
||||
pass
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# content of test_second.py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def test_2():
|
||||
pass
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# content of test_third.py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def test_3():
|
||||
pass
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
"""Module containing a parametrized tests testing cross-python serialization
|
||||
via the pickle module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
import textwrap
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# content of conftest.py
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# run this with $ pytest --collect-only test_collectonly.py
|
||||
#
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def test_function():
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
|
|
@ -6,15 +6,15 @@ keywords = [
|
|||
"test",
|
||||
"unittest",
|
||||
]
|
||||
license = {text = "MIT"}
|
||||
license = { text = "MIT" }
|
||||
authors = [
|
||||
{name = "Holger Krekel"},
|
||||
{name = "Bruno Oliveira"},
|
||||
{name = "Ronny Pfannschmidt"},
|
||||
{name = "Floris Bruynooghe"},
|
||||
{name = "Brianna Laugher"},
|
||||
{name = "Florian Bruhin"},
|
||||
{name = "Others (See AUTHORS)"},
|
||||
{ name = "Holger Krekel" },
|
||||
{ name = "Bruno Oliveira" },
|
||||
{ name = "Ronny Pfannschmidt" },
|
||||
{ name = "Floris Bruynooghe" },
|
||||
{ name = "Brianna Laugher" },
|
||||
{ name = "Florian Bruhin" },
|
||||
{ name = "Others (See AUTHORS)" },
|
||||
]
|
||||
requires-python = ">=3.8"
|
||||
classifiers = [
|
||||
|
@ -99,6 +99,7 @@ select = [
|
|||
"E", # pycodestyle
|
||||
"F", # pyflakes
|
||||
"I", # isort
|
||||
"FA100", # add future annotations
|
||||
"PYI", # flake8-pyi
|
||||
"UP", # pyupgrade
|
||||
"RUF", # ruff
|
||||
|
@ -169,7 +170,7 @@ lines-after-imports = 2
|
|||
[tool.pylint.main]
|
||||
# Maximum number of characters on a single line.
|
||||
max-line-length = 120
|
||||
disable= [
|
||||
disable = [
|
||||
"abstract-method",
|
||||
"arguments-differ",
|
||||
"arguments-renamed",
|
||||
|
|
|
@ -9,6 +9,8 @@ our CHANGELOG) into Markdown (which is required by GitHub Releases).
|
|||
Requires Python3.6+.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
|
|
|
@ -14,6 +14,8 @@ After that, it will create a release using the `release` tox environment, and pu
|
|||
`pytest bot <pytestbot@gmail.com>` commit author.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# mypy: disallow-untyped-defs
|
||||
"""Invoke development tasks."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# mypy: disallow-untyped-defs
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import pathlib
|
||||
import re
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
__all__ = ["__version__", "version_tuple"]
|
||||
|
||||
try:
|
||||
|
|
|
@ -62,13 +62,13 @@ If things do not work right away:
|
|||
global argcomplete script).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from glob import glob
|
||||
import os
|
||||
import sys
|
||||
from typing import Any
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class FastFilesCompleter:
|
||||
|
@ -77,7 +77,7 @@ class FastFilesCompleter:
|
|||
def __init__(self, directories: bool = True) -> None:
|
||||
self.directories = directories
|
||||
|
||||
def __call__(self, prefix: str, **kwargs: Any) -> List[str]:
|
||||
def __call__(self, prefix: str, **kwargs: Any) -> list[str]:
|
||||
# Only called on non option completions.
|
||||
if os.sep in prefix[1:]:
|
||||
prefix_dir = len(os.path.dirname(prefix) + os.sep)
|
||||
|
@ -104,7 +104,7 @@ if os.environ.get("_ARGCOMPLETE"):
|
|||
import argcomplete.completers
|
||||
except ImportError:
|
||||
sys.exit(-1)
|
||||
filescompleter: Optional[FastFilesCompleter] = FastFilesCompleter()
|
||||
filescompleter: FastFilesCompleter | None = FastFilesCompleter()
|
||||
|
||||
def try_argcomplete(parser: argparse.ArgumentParser) -> None:
|
||||
argcomplete.autocomplete(parser, always_complete_options=False)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
"""Python inspection/code generation API."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .code import Code
|
||||
from .code import ExceptionInfo
|
||||
from .code import filter_traceback
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# mypy: allow-untyped-defs
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import dataclasses
|
||||
import inspect
|
||||
|
@ -17,7 +19,6 @@ from types import TracebackType
|
|||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import ClassVar
|
||||
from typing import Dict
|
||||
from typing import Final
|
||||
from typing import final
|
||||
from typing import Generic
|
||||
|
@ -25,11 +26,9 @@ from typing import Iterable
|
|||
from typing import List
|
||||
from typing import Literal
|
||||
from typing import Mapping
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Pattern
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
from typing import SupportsIndex
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
|
@ -57,6 +56,8 @@ if sys.version_info < (3, 11):
|
|||
|
||||
_TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"]
|
||||
|
||||
EXCEPTION_OR_MORE = Union[Type[Exception], Tuple[Type[Exception], ...]]
|
||||
|
||||
|
||||
class Code:
|
||||
"""Wrapper around Python code objects."""
|
||||
|
@ -67,7 +68,7 @@ class Code:
|
|||
self.raw = obj
|
||||
|
||||
@classmethod
|
||||
def from_function(cls, obj: object) -> "Code":
|
||||
def from_function(cls, obj: object) -> Code:
|
||||
return cls(getrawcode(obj))
|
||||
|
||||
def __eq__(self, other):
|
||||
|
@ -85,7 +86,7 @@ class Code:
|
|||
return self.raw.co_name
|
||||
|
||||
@property
|
||||
def path(self) -> Union[Path, str]:
|
||||
def path(self) -> Path | str:
|
||||
"""Return a path object pointing to source code, or an ``str`` in
|
||||
case of ``OSError`` / non-existing file."""
|
||||
if not self.raw.co_filename:
|
||||
|
@ -102,17 +103,17 @@ class Code:
|
|||
return self.raw.co_filename
|
||||
|
||||
@property
|
||||
def fullsource(self) -> Optional["Source"]:
|
||||
def fullsource(self) -> Source | None:
|
||||
"""Return a _pytest._code.Source object for the full source file of the code."""
|
||||
full, _ = findsource(self.raw)
|
||||
return full
|
||||
|
||||
def source(self) -> "Source":
|
||||
def source(self) -> Source:
|
||||
"""Return a _pytest._code.Source object for the code object's source only."""
|
||||
# return source only for that part of code
|
||||
return Source(self.raw)
|
||||
|
||||
def getargs(self, var: bool = False) -> Tuple[str, ...]:
|
||||
def getargs(self, var: bool = False) -> tuple[str, ...]:
|
||||
"""Return a tuple with the argument names for the code object.
|
||||
|
||||
If 'var' is set True also return the names of the variable and
|
||||
|
@ -141,11 +142,11 @@ class Frame:
|
|||
return self.raw.f_lineno - 1
|
||||
|
||||
@property
|
||||
def f_globals(self) -> Dict[str, Any]:
|
||||
def f_globals(self) -> dict[str, Any]:
|
||||
return self.raw.f_globals
|
||||
|
||||
@property
|
||||
def f_locals(self) -> Dict[str, Any]:
|
||||
def f_locals(self) -> dict[str, Any]:
|
||||
return self.raw.f_locals
|
||||
|
||||
@property
|
||||
|
@ -153,7 +154,7 @@ class Frame:
|
|||
return Code(self.raw.f_code)
|
||||
|
||||
@property
|
||||
def statement(self) -> "Source":
|
||||
def statement(self) -> Source:
|
||||
"""Statement this frame is at."""
|
||||
if self.code.fullsource is None:
|
||||
return Source("")
|
||||
|
@ -197,14 +198,14 @@ class TracebackEntry:
|
|||
def __init__(
|
||||
self,
|
||||
rawentry: TracebackType,
|
||||
repr_style: Optional['Literal["short", "long"]'] = None,
|
||||
repr_style: Literal["short", "long"] | None = None,
|
||||
) -> None:
|
||||
self._rawentry: "Final" = rawentry
|
||||
self._repr_style: "Final" = repr_style
|
||||
self._rawentry: Final = rawentry
|
||||
self._repr_style: Final = repr_style
|
||||
|
||||
def with_repr_style(
|
||||
self, repr_style: Optional['Literal["short", "long"]']
|
||||
) -> "TracebackEntry":
|
||||
self, repr_style: Literal["short", "long"] | None
|
||||
) -> TracebackEntry:
|
||||
return TracebackEntry(self._rawentry, repr_style)
|
||||
|
||||
@property
|
||||
|
@ -223,19 +224,19 @@ class TracebackEntry:
|
|||
return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno + 1)
|
||||
|
||||
@property
|
||||
def statement(self) -> "Source":
|
||||
def statement(self) -> Source:
|
||||
"""_pytest._code.Source object for the current statement."""
|
||||
source = self.frame.code.fullsource
|
||||
assert source is not None
|
||||
return source.getstatement(self.lineno)
|
||||
|
||||
@property
|
||||
def path(self) -> Union[Path, str]:
|
||||
def path(self) -> Path | str:
|
||||
"""Path to the source code."""
|
||||
return self.frame.code.path
|
||||
|
||||
@property
|
||||
def locals(self) -> Dict[str, Any]:
|
||||
def locals(self) -> dict[str, Any]:
|
||||
"""Locals of underlying frame."""
|
||||
return self.frame.f_locals
|
||||
|
||||
|
@ -243,8 +244,8 @@ class TracebackEntry:
|
|||
return self.frame.code.firstlineno
|
||||
|
||||
def getsource(
|
||||
self, astcache: Optional[Dict[Union[str, Path], ast.AST]] = None
|
||||
) -> Optional["Source"]:
|
||||
self, astcache: dict[str | Path, ast.AST] | None = None
|
||||
) -> Source | None:
|
||||
"""Return failing source code."""
|
||||
# we use the passed in astcache to not reparse asttrees
|
||||
# within exception info printing
|
||||
|
@ -270,7 +271,7 @@ class TracebackEntry:
|
|||
|
||||
source = property(getsource)
|
||||
|
||||
def ishidden(self, excinfo: Optional["ExceptionInfo[BaseException]"]) -> bool:
|
||||
def ishidden(self, excinfo: ExceptionInfo[BaseException] | None) -> bool:
|
||||
"""Return True if the current frame has a var __tracebackhide__
|
||||
resolving to True.
|
||||
|
||||
|
@ -279,9 +280,7 @@ class TracebackEntry:
|
|||
|
||||
Mostly for internal use.
|
||||
"""
|
||||
tbh: Union[bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool]] = (
|
||||
False
|
||||
)
|
||||
tbh: bool | Callable[[ExceptionInfo[BaseException] | None], bool] = False
|
||||
for maybe_ns_dct in (self.frame.f_locals, self.frame.f_globals):
|
||||
# in normal cases, f_locals and f_globals are dictionaries
|
||||
# however via `exec(...)` / `eval(...)` they can be other types
|
||||
|
@ -326,13 +325,13 @@ class Traceback(List[TracebackEntry]):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
tb: Union[TracebackType, Iterable[TracebackEntry]],
|
||||
tb: TracebackType | Iterable[TracebackEntry],
|
||||
) -> None:
|
||||
"""Initialize from given python traceback object and ExceptionInfo."""
|
||||
if isinstance(tb, TracebackType):
|
||||
|
||||
def f(cur: TracebackType) -> Iterable[TracebackEntry]:
|
||||
cur_: Optional[TracebackType] = cur
|
||||
cur_: TracebackType | None = cur
|
||||
while cur_ is not None:
|
||||
yield TracebackEntry(cur_)
|
||||
cur_ = cur_.tb_next
|
||||
|
@ -343,11 +342,11 @@ class Traceback(List[TracebackEntry]):
|
|||
|
||||
def cut(
|
||||
self,
|
||||
path: Optional[Union["os.PathLike[str]", str]] = None,
|
||||
lineno: Optional[int] = None,
|
||||
firstlineno: Optional[int] = None,
|
||||
excludepath: Optional["os.PathLike[str]"] = None,
|
||||
) -> "Traceback":
|
||||
path: os.PathLike[str] | str | None = None,
|
||||
lineno: int | None = None,
|
||||
firstlineno: int | None = None,
|
||||
excludepath: os.PathLike[str] | None = None,
|
||||
) -> Traceback:
|
||||
"""Return a Traceback instance wrapping part of this Traceback.
|
||||
|
||||
By providing any combination of path, lineno and firstlineno, the
|
||||
|
@ -378,14 +377,12 @@ class Traceback(List[TracebackEntry]):
|
|||
return self
|
||||
|
||||
@overload
|
||||
def __getitem__(self, key: "SupportsIndex") -> TracebackEntry: ...
|
||||
def __getitem__(self, key: SupportsIndex) -> TracebackEntry: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(self, key: slice) -> "Traceback": ...
|
||||
def __getitem__(self, key: slice) -> Traceback: ...
|
||||
|
||||
def __getitem__(
|
||||
self, key: Union["SupportsIndex", slice]
|
||||
) -> Union[TracebackEntry, "Traceback"]:
|
||||
def __getitem__(self, key: SupportsIndex | slice) -> TracebackEntry | Traceback:
|
||||
if isinstance(key, slice):
|
||||
return self.__class__(super().__getitem__(key))
|
||||
else:
|
||||
|
@ -393,12 +390,9 @@ class Traceback(List[TracebackEntry]):
|
|||
|
||||
def filter(
|
||||
self,
|
||||
excinfo_or_fn: Union[
|
||||
"ExceptionInfo[BaseException]",
|
||||
Callable[[TracebackEntry], bool],
|
||||
],
|
||||
excinfo_or_fn: ExceptionInfo[BaseException] | Callable[[TracebackEntry], bool],
|
||||
/,
|
||||
) -> "Traceback":
|
||||
) -> Traceback:
|
||||
"""Return a Traceback instance with certain items removed.
|
||||
|
||||
If the filter is an `ExceptionInfo`, removes all the ``TracebackEntry``s
|
||||
|
@ -414,10 +408,10 @@ class Traceback(List[TracebackEntry]):
|
|||
fn = excinfo_or_fn
|
||||
return Traceback(filter(fn, self))
|
||||
|
||||
def recursionindex(self) -> Optional[int]:
|
||||
def recursionindex(self) -> int | None:
|
||||
"""Return the index of the frame/TracebackEntry where recursion originates if
|
||||
appropriate, None if no recursion occurred."""
|
||||
cache: Dict[Tuple[Any, int, int], List[Dict[str, Any]]] = {}
|
||||
cache: dict[tuple[Any, int, int], list[dict[str, Any]]] = {}
|
||||
for i, entry in enumerate(self):
|
||||
# id for the code.raw is needed to work around
|
||||
# the strange metaprogramming in the decorator lib from pypi
|
||||
|
@ -445,15 +439,15 @@ class ExceptionInfo(Generic[E]):
|
|||
|
||||
_assert_start_repr: ClassVar = "AssertionError('assert "
|
||||
|
||||
_excinfo: Optional[Tuple[Type["E"], "E", TracebackType]]
|
||||
_excinfo: tuple[type[E], E, TracebackType] | None
|
||||
_striptext: str
|
||||
_traceback: Optional[Traceback]
|
||||
_traceback: Traceback | None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
excinfo: Optional[Tuple[Type["E"], "E", TracebackType]],
|
||||
excinfo: tuple[type[E], E, TracebackType] | None,
|
||||
striptext: str = "",
|
||||
traceback: Optional[Traceback] = None,
|
||||
traceback: Traceback | None = None,
|
||||
*,
|
||||
_ispytest: bool = False,
|
||||
) -> None:
|
||||
|
@ -469,8 +463,8 @@ class ExceptionInfo(Generic[E]):
|
|||
# This is OK to ignore because this class is (conceptually) readonly.
|
||||
# See https://github.com/python/mypy/issues/7049.
|
||||
exception: E, # type: ignore[misc]
|
||||
exprinfo: Optional[str] = None,
|
||||
) -> "ExceptionInfo[E]":
|
||||
exprinfo: str | None = None,
|
||||
) -> ExceptionInfo[E]:
|
||||
"""Return an ExceptionInfo for an existing exception.
|
||||
|
||||
The exception must have a non-``None`` ``__traceback__`` attribute,
|
||||
|
@ -495,9 +489,9 @@ class ExceptionInfo(Generic[E]):
|
|||
@classmethod
|
||||
def from_exc_info(
|
||||
cls,
|
||||
exc_info: Tuple[Type[E], E, TracebackType],
|
||||
exprinfo: Optional[str] = None,
|
||||
) -> "ExceptionInfo[E]":
|
||||
exc_info: tuple[type[E], E, TracebackType],
|
||||
exprinfo: str | None = None,
|
||||
) -> ExceptionInfo[E]:
|
||||
"""Like :func:`from_exception`, but using old-style exc_info tuple."""
|
||||
_striptext = ""
|
||||
if exprinfo is None and isinstance(exc_info[1], AssertionError):
|
||||
|
@ -510,9 +504,7 @@ class ExceptionInfo(Generic[E]):
|
|||
return cls(exc_info, _striptext, _ispytest=True)
|
||||
|
||||
@classmethod
|
||||
def from_current(
|
||||
cls, exprinfo: Optional[str] = None
|
||||
) -> "ExceptionInfo[BaseException]":
|
||||
def from_current(cls, exprinfo: str | None = None) -> ExceptionInfo[BaseException]:
|
||||
"""Return an ExceptionInfo matching the current traceback.
|
||||
|
||||
.. warning::
|
||||
|
@ -532,17 +524,17 @@ class ExceptionInfo(Generic[E]):
|
|||
return ExceptionInfo.from_exc_info(exc_info, exprinfo)
|
||||
|
||||
@classmethod
|
||||
def for_later(cls) -> "ExceptionInfo[E]":
|
||||
def for_later(cls) -> ExceptionInfo[E]:
|
||||
"""Return an unfilled ExceptionInfo."""
|
||||
return cls(None, _ispytest=True)
|
||||
|
||||
def fill_unfilled(self, exc_info: Tuple[Type[E], E, TracebackType]) -> None:
|
||||
def fill_unfilled(self, exc_info: tuple[type[E], E, TracebackType]) -> None:
|
||||
"""Fill an unfilled ExceptionInfo created with ``for_later()``."""
|
||||
assert self._excinfo is None, "ExceptionInfo was already filled"
|
||||
self._excinfo = exc_info
|
||||
|
||||
@property
|
||||
def type(self) -> Type[E]:
|
||||
def type(self) -> type[E]:
|
||||
"""The exception class."""
|
||||
assert (
|
||||
self._excinfo is not None
|
||||
|
@ -605,16 +597,14 @@ class ExceptionInfo(Generic[E]):
|
|||
text = text[len(self._striptext) :]
|
||||
return text
|
||||
|
||||
def errisinstance(
|
||||
self, exc: Union[Type[BaseException], Tuple[Type[BaseException], ...]]
|
||||
) -> bool:
|
||||
def errisinstance(self, exc: EXCEPTION_OR_MORE) -> bool:
|
||||
"""Return True if the exception is an instance of exc.
|
||||
|
||||
Consider using ``isinstance(excinfo.value, exc)`` instead.
|
||||
"""
|
||||
return isinstance(self.value, exc)
|
||||
|
||||
def _getreprcrash(self) -> Optional["ReprFileLocation"]:
|
||||
def _getreprcrash(self) -> ReprFileLocation | None:
|
||||
# Find last non-hidden traceback entry that led to the exception of the
|
||||
# traceback, or None if all hidden.
|
||||
for i in range(-1, -len(self.traceback) - 1, -1):
|
||||
|
@ -630,13 +620,12 @@ class ExceptionInfo(Generic[E]):
|
|||
showlocals: bool = False,
|
||||
style: _TracebackStyle = "long",
|
||||
abspath: bool = False,
|
||||
tbfilter: Union[
|
||||
bool, Callable[["ExceptionInfo[BaseException]"], Traceback]
|
||||
] = True,
|
||||
tbfilter: bool
|
||||
| Callable[[ExceptionInfo[BaseException]], _pytest._code.code.Traceback] = True,
|
||||
funcargs: bool = False,
|
||||
truncate_locals: bool = True,
|
||||
chain: bool = True,
|
||||
) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]:
|
||||
) -> ReprExceptionInfo | ExceptionChainRepr:
|
||||
"""Return str()able representation of this exception info.
|
||||
|
||||
:param bool showlocals:
|
||||
|
@ -714,7 +703,7 @@ class ExceptionInfo(Generic[E]):
|
|||
]
|
||||
)
|
||||
|
||||
def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]":
|
||||
def match(self, regexp: str | Pattern[str]) -> Literal[True]:
|
||||
"""Check whether the regular expression `regexp` matches the string
|
||||
representation of the exception using :func:`python:re.search`.
|
||||
|
||||
|
@ -732,9 +721,9 @@ class ExceptionInfo(Generic[E]):
|
|||
def _group_contains(
|
||||
self,
|
||||
exc_group: BaseExceptionGroup[BaseException],
|
||||
expected_exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]],
|
||||
match: Union[str, Pattern[str], None],
|
||||
target_depth: Optional[int] = None,
|
||||
expected_exception: EXCEPTION_OR_MORE,
|
||||
match: str | Pattern[str] | None,
|
||||
target_depth: int | None = None,
|
||||
current_depth: int = 1,
|
||||
) -> bool:
|
||||
"""Return `True` if a `BaseExceptionGroup` contains a matching exception."""
|
||||
|
@ -761,10 +750,10 @@ class ExceptionInfo(Generic[E]):
|
|||
|
||||
def group_contains(
|
||||
self,
|
||||
expected_exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]],
|
||||
expected_exception: EXCEPTION_OR_MORE,
|
||||
*,
|
||||
match: Union[str, Pattern[str], None] = None,
|
||||
depth: Optional[int] = None,
|
||||
match: str | Pattern[str] | None = None,
|
||||
depth: int | None = None,
|
||||
) -> bool:
|
||||
"""Check whether a captured exception group contains a matching exception.
|
||||
|
||||
|
@ -806,15 +795,15 @@ class FormattedExcinfo:
|
|||
showlocals: bool = False
|
||||
style: _TracebackStyle = "long"
|
||||
abspath: bool = True
|
||||
tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] = True
|
||||
tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] = True
|
||||
funcargs: bool = False
|
||||
truncate_locals: bool = True
|
||||
chain: bool = True
|
||||
astcache: Dict[Union[str, Path], ast.AST] = dataclasses.field(
|
||||
astcache: dict[str | Path, ast.AST] = dataclasses.field(
|
||||
default_factory=dict, init=False, repr=False
|
||||
)
|
||||
|
||||
def _getindent(self, source: "Source") -> int:
|
||||
def _getindent(self, source: Source) -> int:
|
||||
# Figure out indent for the given source.
|
||||
try:
|
||||
s = str(source.getstatement(len(source) - 1))
|
||||
|
@ -829,13 +818,13 @@ class FormattedExcinfo:
|
|||
return 0
|
||||
return 4 + (len(s) - len(s.lstrip()))
|
||||
|
||||
def _getentrysource(self, entry: TracebackEntry) -> Optional["Source"]:
|
||||
def _getentrysource(self, entry: TracebackEntry) -> Source | None:
|
||||
source = entry.getsource(self.astcache)
|
||||
if source is not None:
|
||||
source = source.deindent()
|
||||
return source
|
||||
|
||||
def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]:
|
||||
def repr_args(self, entry: TracebackEntry) -> ReprFuncArgs | None:
|
||||
if self.funcargs:
|
||||
args = []
|
||||
for argname, argvalue in entry.frame.getargs(var=True):
|
||||
|
@ -845,11 +834,11 @@ class FormattedExcinfo:
|
|||
|
||||
def get_source(
|
||||
self,
|
||||
source: Optional["Source"],
|
||||
source: Source | None,
|
||||
line_index: int = -1,
|
||||
excinfo: Optional[ExceptionInfo[BaseException]] = None,
|
||||
excinfo: ExceptionInfo[BaseException] | None = None,
|
||||
short: bool = False,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
"""Return formatted and marked up source lines."""
|
||||
lines = []
|
||||
if source is not None and line_index < 0:
|
||||
|
@ -878,7 +867,7 @@ class FormattedExcinfo:
|
|||
excinfo: ExceptionInfo[BaseException],
|
||||
indent: int = 4,
|
||||
markall: bool = False,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
lines = []
|
||||
indentstr = " " * indent
|
||||
# Get the real exception information out.
|
||||
|
@ -890,7 +879,7 @@ class FormattedExcinfo:
|
|||
failindent = indentstr
|
||||
return lines
|
||||
|
||||
def repr_locals(self, locals: Mapping[str, object]) -> Optional["ReprLocals"]:
|
||||
def repr_locals(self, locals: Mapping[str, object]) -> ReprLocals | None:
|
||||
if self.showlocals:
|
||||
lines = []
|
||||
keys = [loc for loc in locals if loc[0] != "@"]
|
||||
|
@ -918,10 +907,10 @@ class FormattedExcinfo:
|
|||
|
||||
def repr_traceback_entry(
|
||||
self,
|
||||
entry: Optional[TracebackEntry],
|
||||
excinfo: Optional[ExceptionInfo[BaseException]] = None,
|
||||
) -> "ReprEntry":
|
||||
lines: List[str] = []
|
||||
entry: TracebackEntry | None,
|
||||
excinfo: ExceptionInfo[BaseException] | None = None,
|
||||
) -> ReprEntry:
|
||||
lines: list[str] = []
|
||||
style = (
|
||||
entry._repr_style
|
||||
if entry is not None and entry._repr_style is not None
|
||||
|
@ -956,7 +945,7 @@ class FormattedExcinfo:
|
|||
lines.extend(self.get_exconly(excinfo, indent=4))
|
||||
return ReprEntry(lines, None, None, None, style)
|
||||
|
||||
def _makepath(self, path: Union[Path, str]) -> str:
|
||||
def _makepath(self, path: Path | str) -> str:
|
||||
if not self.abspath and isinstance(path, Path):
|
||||
try:
|
||||
np = bestrelpath(Path.cwd(), path)
|
||||
|
@ -966,7 +955,7 @@ class FormattedExcinfo:
|
|||
return np
|
||||
return str(path)
|
||||
|
||||
def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTraceback":
|
||||
def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> ReprTraceback:
|
||||
traceback = excinfo.traceback
|
||||
if callable(self.tbfilter):
|
||||
traceback = self.tbfilter(excinfo)
|
||||
|
@ -997,7 +986,7 @@ class FormattedExcinfo:
|
|||
|
||||
def _truncate_recursive_traceback(
|
||||
self, traceback: Traceback
|
||||
) -> Tuple[Traceback, Optional[str]]:
|
||||
) -> tuple[Traceback, str | None]:
|
||||
"""Truncate the given recursive traceback trying to find the starting
|
||||
point of the recursion.
|
||||
|
||||
|
@ -1014,7 +1003,7 @@ class FormattedExcinfo:
|
|||
recursionindex = traceback.recursionindex()
|
||||
except Exception as e:
|
||||
max_frames = 10
|
||||
extraline: Optional[str] = (
|
||||
extraline: str | None = (
|
||||
"!!! Recursion error detected, but an error occurred locating the origin of recursion.\n"
|
||||
" The following exception happened when comparing locals in the stack frame:\n"
|
||||
f" {type(e).__name__}: {e!s}\n"
|
||||
|
@ -1032,16 +1021,12 @@ class FormattedExcinfo:
|
|||
|
||||
return traceback, extraline
|
||||
|
||||
def repr_excinfo(
|
||||
self, excinfo: ExceptionInfo[BaseException]
|
||||
) -> "ExceptionChainRepr":
|
||||
repr_chain: List[
|
||||
Tuple[ReprTraceback, Optional[ReprFileLocation], Optional[str]]
|
||||
] = []
|
||||
e: Optional[BaseException] = excinfo.value
|
||||
excinfo_: Optional[ExceptionInfo[BaseException]] = excinfo
|
||||
def repr_excinfo(self, excinfo: ExceptionInfo[BaseException]) -> ExceptionChainRepr:
|
||||
repr_chain: list[tuple[ReprTraceback, ReprFileLocation | None, str | None]] = []
|
||||
e: BaseException | None = excinfo.value
|
||||
excinfo_: ExceptionInfo[BaseException] | None = excinfo
|
||||
descr = None
|
||||
seen: Set[int] = set()
|
||||
seen: set[int] = set()
|
||||
while e is not None and id(e) not in seen:
|
||||
seen.add(id(e))
|
||||
|
||||
|
@ -1050,7 +1035,7 @@ class FormattedExcinfo:
|
|||
# full support for exception groups added to ExceptionInfo.
|
||||
# See https://github.com/pytest-dev/pytest/issues/9159
|
||||
if isinstance(e, BaseExceptionGroup):
|
||||
reprtraceback: Union[ReprTracebackNative, ReprTraceback] = (
|
||||
reprtraceback: ReprTracebackNative | ReprTraceback = (
|
||||
ReprTracebackNative(
|
||||
traceback.format_exception(
|
||||
type(excinfo_.value),
|
||||
|
@ -1108,9 +1093,9 @@ class TerminalRepr:
|
|||
@dataclasses.dataclass(eq=False)
|
||||
class ExceptionRepr(TerminalRepr):
|
||||
# Provided by subclasses.
|
||||
reprtraceback: "ReprTraceback"
|
||||
reprcrash: Optional["ReprFileLocation"]
|
||||
sections: List[Tuple[str, str, str]] = dataclasses.field(
|
||||
reprtraceback: ReprTraceback
|
||||
reprcrash: ReprFileLocation | None
|
||||
sections: list[tuple[str, str, str]] = dataclasses.field(
|
||||
init=False, default_factory=list
|
||||
)
|
||||
|
||||
|
@ -1125,13 +1110,11 @@ class ExceptionRepr(TerminalRepr):
|
|||
|
||||
@dataclasses.dataclass(eq=False)
|
||||
class ExceptionChainRepr(ExceptionRepr):
|
||||
chain: Sequence[Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]]
|
||||
chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
chain: Sequence[
|
||||
Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]
|
||||
],
|
||||
chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]],
|
||||
) -> None:
|
||||
# reprcrash and reprtraceback of the outermost (the newest) exception
|
||||
# in the chain.
|
||||
|
@ -1152,8 +1135,8 @@ class ExceptionChainRepr(ExceptionRepr):
|
|||
|
||||
@dataclasses.dataclass(eq=False)
|
||||
class ReprExceptionInfo(ExceptionRepr):
|
||||
reprtraceback: "ReprTraceback"
|
||||
reprcrash: Optional["ReprFileLocation"]
|
||||
reprtraceback: ReprTraceback
|
||||
reprcrash: ReprFileLocation | None
|
||||
|
||||
def toterminal(self, tw: TerminalWriter) -> None:
|
||||
self.reprtraceback.toterminal(tw)
|
||||
|
@ -1162,8 +1145,8 @@ class ReprExceptionInfo(ExceptionRepr):
|
|||
|
||||
@dataclasses.dataclass(eq=False)
|
||||
class ReprTraceback(TerminalRepr):
|
||||
reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]]
|
||||
extraline: Optional[str]
|
||||
reprentries: Sequence[ReprEntry | ReprEntryNative]
|
||||
extraline: str | None
|
||||
style: _TracebackStyle
|
||||
|
||||
entrysep: ClassVar = "_ "
|
||||
|
@ -1207,9 +1190,9 @@ class ReprEntryNative(TerminalRepr):
|
|||
@dataclasses.dataclass(eq=False)
|
||||
class ReprEntry(TerminalRepr):
|
||||
lines: Sequence[str]
|
||||
reprfuncargs: Optional["ReprFuncArgs"]
|
||||
reprlocals: Optional["ReprLocals"]
|
||||
reprfileloc: Optional["ReprFileLocation"]
|
||||
reprfuncargs: ReprFuncArgs | None
|
||||
reprlocals: ReprLocals | None
|
||||
reprfileloc: ReprFileLocation | None
|
||||
style: _TracebackStyle
|
||||
|
||||
def _write_entry_lines(self, tw: TerminalWriter) -> None:
|
||||
|
@ -1233,9 +1216,9 @@ class ReprEntry(TerminalRepr):
|
|||
# such as "> assert 0"
|
||||
fail_marker = f"{FormattedExcinfo.fail_marker} "
|
||||
indent_size = len(fail_marker)
|
||||
indents: List[str] = []
|
||||
source_lines: List[str] = []
|
||||
failure_lines: List[str] = []
|
||||
indents: list[str] = []
|
||||
source_lines: list[str] = []
|
||||
failure_lines: list[str] = []
|
||||
for index, line in enumerate(self.lines):
|
||||
is_failure_line = line.startswith(fail_marker)
|
||||
if is_failure_line:
|
||||
|
@ -1314,7 +1297,7 @@ class ReprLocals(TerminalRepr):
|
|||
|
||||
@dataclasses.dataclass(eq=False)
|
||||
class ReprFuncArgs(TerminalRepr):
|
||||
args: Sequence[Tuple[str, object]]
|
||||
args: Sequence[tuple[str, object]]
|
||||
|
||||
def toterminal(self, tw: TerminalWriter) -> None:
|
||||
if self.args:
|
||||
|
@ -1335,7 +1318,7 @@ class ReprFuncArgs(TerminalRepr):
|
|||
tw.line("")
|
||||
|
||||
|
||||
def getfslineno(obj: object) -> Tuple[Union[str, Path], int]:
|
||||
def getfslineno(obj: object) -> tuple[str | Path, int]:
|
||||
"""Return source location (path, lineno) for the given object.
|
||||
|
||||
If the source cannot be determined return ("", -1).
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# mypy: allow-untyped-defs
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
from bisect import bisect_right
|
||||
import inspect
|
||||
|
@ -7,11 +9,7 @@ import tokenize
|
|||
import types
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
|
||||
|
@ -23,7 +21,7 @@ class Source:
|
|||
|
||||
def __init__(self, obj: object = None) -> None:
|
||||
if not obj:
|
||||
self.lines: List[str] = []
|
||||
self.lines: list[str] = []
|
||||
elif isinstance(obj, Source):
|
||||
self.lines = obj.lines
|
||||
elif isinstance(obj, (tuple, list)):
|
||||
|
@ -50,9 +48,9 @@ class Source:
|
|||
def __getitem__(self, key: int) -> str: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(self, key: slice) -> "Source": ...
|
||||
def __getitem__(self, key: slice) -> Source: ...
|
||||
|
||||
def __getitem__(self, key: Union[int, slice]) -> Union[str, "Source"]:
|
||||
def __getitem__(self, key: int | slice) -> str | Source:
|
||||
if isinstance(key, int):
|
||||
return self.lines[key]
|
||||
else:
|
||||
|
@ -68,7 +66,7 @@ class Source:
|
|||
def __len__(self) -> int:
|
||||
return len(self.lines)
|
||||
|
||||
def strip(self) -> "Source":
|
||||
def strip(self) -> Source:
|
||||
"""Return new Source object with trailing and leading blank lines removed."""
|
||||
start, end = 0, len(self)
|
||||
while start < end and not self.lines[start].strip():
|
||||
|
@ -79,20 +77,20 @@ class Source:
|
|||
source.lines[:] = self.lines[start:end]
|
||||
return source
|
||||
|
||||
def indent(self, indent: str = " " * 4) -> "Source":
|
||||
def indent(self, indent: str = " " * 4) -> Source:
|
||||
"""Return a copy of the source object with all lines indented by the
|
||||
given indent-string."""
|
||||
newsource = Source()
|
||||
newsource.lines = [(indent + line) for line in self.lines]
|
||||
return newsource
|
||||
|
||||
def getstatement(self, lineno: int) -> "Source":
|
||||
def getstatement(self, lineno: int) -> Source:
|
||||
"""Return Source statement which contains the given linenumber
|
||||
(counted from 0)."""
|
||||
start, end = self.getstatementrange(lineno)
|
||||
return self[start:end]
|
||||
|
||||
def getstatementrange(self, lineno: int) -> Tuple[int, int]:
|
||||
def getstatementrange(self, lineno: int) -> tuple[int, int]:
|
||||
"""Return (start, end) tuple which spans the minimal statement region
|
||||
which containing the given lineno."""
|
||||
if not (0 <= lineno < len(self)):
|
||||
|
@ -100,7 +98,7 @@ class Source:
|
|||
ast, start, end = getstatementrange_ast(lineno, self)
|
||||
return start, end
|
||||
|
||||
def deindent(self) -> "Source":
|
||||
def deindent(self) -> Source:
|
||||
"""Return a new Source object deindented."""
|
||||
newsource = Source()
|
||||
newsource.lines[:] = deindent(self.lines)
|
||||
|
@ -115,7 +113,7 @@ class Source:
|
|||
#
|
||||
|
||||
|
||||
def findsource(obj) -> Tuple[Optional[Source], int]:
|
||||
def findsource(obj) -> tuple[Source | None, int]:
|
||||
try:
|
||||
sourcelines, lineno = inspect.findsource(obj)
|
||||
except Exception:
|
||||
|
@ -138,14 +136,14 @@ def getrawcode(obj: object, trycall: bool = True) -> types.CodeType:
|
|||
raise TypeError(f"could not get code object for {obj!r}")
|
||||
|
||||
|
||||
def deindent(lines: Iterable[str]) -> List[str]:
|
||||
def deindent(lines: Iterable[str]) -> list[str]:
|
||||
return textwrap.dedent("\n".join(lines)).splitlines()
|
||||
|
||||
|
||||
def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[int]]:
|
||||
def get_statement_startend2(lineno: int, node: ast.AST) -> tuple[int, int | None]:
|
||||
# Flatten all statements and except handlers into one lineno-list.
|
||||
# AST's line numbers start indexing at 1.
|
||||
values: List[int] = []
|
||||
values: list[int] = []
|
||||
for x in ast.walk(node):
|
||||
if isinstance(x, (ast.stmt, ast.ExceptHandler)):
|
||||
# The lineno points to the class/def, so need to include the decorators.
|
||||
|
@ -154,7 +152,7 @@ def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[i
|
|||
values.append(d.lineno - 1)
|
||||
values.append(x.lineno - 1)
|
||||
for name in ("finalbody", "orelse"):
|
||||
val: Optional[List[ast.stmt]] = getattr(x, name, None)
|
||||
val: list[ast.stmt] | None = getattr(x, name, None)
|
||||
if val:
|
||||
# Treat the finally/orelse part as its own statement.
|
||||
values.append(val[0].lineno - 1 - 1)
|
||||
|
@ -172,8 +170,8 @@ def getstatementrange_ast(
|
|||
lineno: int,
|
||||
source: Source,
|
||||
assertion: bool = False,
|
||||
astnode: Optional[ast.AST] = None,
|
||||
) -> Tuple[ast.AST, int, int]:
|
||||
astnode: ast.AST | None = None,
|
||||
) -> tuple[ast.AST, int, int]:
|
||||
if astnode is None:
|
||||
content = str(source)
|
||||
# See #4260:
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from .terminalwriter import get_terminal_width
|
||||
from .terminalwriter import TerminalWriter
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
# tuples with fairly non-descriptive content. This is modeled very much
|
||||
# after Lisp/Scheme - style pretty-printing of lists. If you find it
|
||||
# useful, thank small children who sleep at night.
|
||||
from __future__ import annotations
|
||||
|
||||
import collections as _collections
|
||||
import dataclasses as _dataclasses
|
||||
from io import StringIO as _StringIO
|
||||
|
@ -20,13 +22,8 @@ import re
|
|||
import types as _types
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import IO
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
class _safe_key:
|
||||
|
@ -64,7 +61,7 @@ class PrettyPrinter:
|
|||
self,
|
||||
indent: int = 4,
|
||||
width: int = 80,
|
||||
depth: Optional[int] = None,
|
||||
depth: int | None = None,
|
||||
) -> None:
|
||||
"""Handle pretty printing operations onto a stream using a set of
|
||||
configured parameters.
|
||||
|
@ -100,7 +97,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
objid = id(object)
|
||||
|
@ -136,7 +133,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
cls_name = object.__class__.__name__
|
||||
|
@ -149,9 +146,9 @@ class PrettyPrinter:
|
|||
self._format_namespace_items(items, stream, indent, allowance, context, level)
|
||||
stream.write(")")
|
||||
|
||||
_dispatch: Dict[
|
||||
_dispatch: dict[
|
||||
Callable[..., str],
|
||||
Callable[["PrettyPrinter", Any, IO[str], int, int, Set[int], int], None],
|
||||
Callable[[PrettyPrinter, Any, IO[str], int, int, set[int], int], None],
|
||||
] = {}
|
||||
|
||||
def _pprint_dict(
|
||||
|
@ -160,7 +157,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
write = stream.write
|
||||
|
@ -177,7 +174,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
if not len(object):
|
||||
|
@ -196,7 +193,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
stream.write("[")
|
||||
|
@ -211,7 +208,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
stream.write("(")
|
||||
|
@ -226,7 +223,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
if not len(object):
|
||||
|
@ -252,7 +249,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
write = stream.write
|
||||
|
@ -311,7 +308,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
write = stream.write
|
||||
|
@ -340,7 +337,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
write = stream.write
|
||||
|
@ -358,7 +355,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
stream.write("mappingproxy(")
|
||||
|
@ -373,7 +370,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
if type(object) is _types.SimpleNamespace:
|
||||
|
@ -391,11 +388,11 @@ class PrettyPrinter:
|
|||
|
||||
def _format_dict_items(
|
||||
self,
|
||||
items: List[Tuple[Any, Any]],
|
||||
items: list[tuple[Any, Any]],
|
||||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
if not items:
|
||||
|
@ -415,11 +412,11 @@ class PrettyPrinter:
|
|||
|
||||
def _format_namespace_items(
|
||||
self,
|
||||
items: List[Tuple[Any, Any]],
|
||||
items: list[tuple[Any, Any]],
|
||||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
if not items:
|
||||
|
@ -452,11 +449,11 @@ class PrettyPrinter:
|
|||
|
||||
def _format_items(
|
||||
self,
|
||||
items: List[Any],
|
||||
items: list[Any],
|
||||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
if not items:
|
||||
|
@ -473,7 +470,7 @@ class PrettyPrinter:
|
|||
|
||||
write("\n" + " " * indent)
|
||||
|
||||
def _repr(self, object: Any, context: Set[int], level: int) -> str:
|
||||
def _repr(self, object: Any, context: set[int], level: int) -> str:
|
||||
return self._safe_repr(object, context.copy(), self._depth, level)
|
||||
|
||||
def _pprint_default_dict(
|
||||
|
@ -482,7 +479,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
rdf = self._repr(object.default_factory, context, level)
|
||||
|
@ -498,7 +495,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
stream.write(object.__class__.__name__ + "(")
|
||||
|
@ -519,7 +516,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
if not len(object.maps) or (len(object.maps) == 1 and not len(object.maps[0])):
|
||||
|
@ -538,7 +535,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
stream.write(object.__class__.__name__ + "(")
|
||||
|
@ -557,7 +554,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
self._format(object.data, stream, indent, allowance, context, level - 1)
|
||||
|
@ -570,7 +567,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
self._format(object.data, stream, indent, allowance, context, level - 1)
|
||||
|
@ -583,7 +580,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
self._format(object.data, stream, indent, allowance, context, level - 1)
|
||||
|
@ -591,7 +588,7 @@ class PrettyPrinter:
|
|||
_dispatch[_collections.UserString.__repr__] = _pprint_user_string
|
||||
|
||||
def _safe_repr(
|
||||
self, object: Any, context: Set[int], maxlevels: Optional[int], level: int
|
||||
self, object: Any, context: set[int], maxlevels: int | None, level: int
|
||||
) -> str:
|
||||
typ = type(object)
|
||||
if typ in _builtin_scalars:
|
||||
|
@ -608,7 +605,7 @@ class PrettyPrinter:
|
|||
if objid in context:
|
||||
return _recursion(object)
|
||||
context.add(objid)
|
||||
components: List[str] = []
|
||||
components: list[str] = []
|
||||
append = components.append
|
||||
level += 1
|
||||
for k, v in sorted(object.items(), key=_safe_tuple):
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pprint
|
||||
import reprlib
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def _try_repr_or_str(obj: object) -> str:
|
||||
|
@ -38,7 +39,7 @@ class SafeRepr(reprlib.Repr):
|
|||
information on exceptions raised during the call.
|
||||
"""
|
||||
|
||||
def __init__(self, maxsize: Optional[int], use_ascii: bool = False) -> None:
|
||||
def __init__(self, maxsize: int | None, use_ascii: bool = False) -> None:
|
||||
"""
|
||||
:param maxsize:
|
||||
If not None, will truncate the resulting repr to that specific size, using ellipsis
|
||||
|
@ -97,7 +98,7 @@ DEFAULT_REPR_MAX_SIZE = 240
|
|||
|
||||
|
||||
def saferepr(
|
||||
obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False
|
||||
obj: object, maxsize: int | None = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False
|
||||
) -> str:
|
||||
"""Return a size-limited safe repr-string for the given object.
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
"""Helper functions for writing to terminals and files."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from typing import final
|
||||
from typing import Literal
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import TextIO
|
||||
|
||||
|
@ -65,7 +66,7 @@ class TerminalWriter:
|
|||
invert=7,
|
||||
)
|
||||
|
||||
def __init__(self, file: Optional[TextIO] = None) -> None:
|
||||
def __init__(self, file: TextIO | None = None) -> None:
|
||||
if file is None:
|
||||
file = sys.stdout
|
||||
if hasattr(file, "isatty") and file.isatty() and sys.platform == "win32":
|
||||
|
@ -79,7 +80,7 @@ class TerminalWriter:
|
|||
self._file = file
|
||||
self.hasmarkup = should_do_markup(file)
|
||||
self._current_line = ""
|
||||
self._terminal_width: Optional[int] = None
|
||||
self._terminal_width: int | None = None
|
||||
self.code_highlight = True
|
||||
|
||||
@property
|
||||
|
@ -110,8 +111,8 @@ class TerminalWriter:
|
|||
def sep(
|
||||
self,
|
||||
sepchar: str,
|
||||
title: Optional[str] = None,
|
||||
fullwidth: Optional[int] = None,
|
||||
title: str | None = None,
|
||||
fullwidth: int | None = None,
|
||||
**markup: bool,
|
||||
) -> None:
|
||||
if fullwidth is None:
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from functools import lru_cache
|
||||
import unicodedata
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Support for presenting detailed information in failing assertions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from typing import Any
|
||||
from typing import Generator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from _pytest.assertion import rewrite
|
||||
|
@ -94,7 +94,7 @@ class AssertionState:
|
|||
def __init__(self, config: Config, mode) -> None:
|
||||
self.mode = mode
|
||||
self.trace = config.trace.root.get("assertion")
|
||||
self.hook: Optional[rewrite.AssertionRewritingHook] = None
|
||||
self.hook: rewrite.AssertionRewritingHook | None = None
|
||||
|
||||
|
||||
def install_importhook(config: Config) -> rewrite.AssertionRewritingHook:
|
||||
|
@ -113,7 +113,7 @@ def install_importhook(config: Config) -> rewrite.AssertionRewritingHook:
|
|||
return hook
|
||||
|
||||
|
||||
def pytest_collection(session: "Session") -> None:
|
||||
def pytest_collection(session: Session) -> None:
|
||||
# This hook is only called when test modules are collected
|
||||
# so for example not in the managing process of pytest-xdist
|
||||
# (which does not collect test modules).
|
||||
|
@ -133,7 +133,7 @@ def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
|
|||
"""
|
||||
ihook = item.ihook
|
||||
|
||||
def callbinrepr(op, left: object, right: object) -> Optional[str]:
|
||||
def callbinrepr(op, left: object, right: object) -> str | None:
|
||||
"""Call the pytest_assertrepr_compare hook and prepare the result.
|
||||
|
||||
This uses the first result from the hook and then ensures the
|
||||
|
@ -179,7 +179,7 @@ def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
|
|||
util._config = None
|
||||
|
||||
|
||||
def pytest_sessionfinish(session: "Session") -> None:
|
||||
def pytest_sessionfinish(session: Session) -> None:
|
||||
assertstate = session.config.stash.get(assertstate_key, None)
|
||||
if assertstate:
|
||||
if assertstate.hook is not None:
|
||||
|
@ -188,5 +188,5 @@ def pytest_sessionfinish(session: "Session") -> None:
|
|||
|
||||
def pytest_assertrepr_compare(
|
||||
config: Config, op: str, left: Any, right: Any
|
||||
) -> Optional[List[str]]:
|
||||
) -> list[str] | None:
|
||||
return util.assertrepr_compare(config=config, op=op, left=left, right=right)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
"""Rewrite assertion AST to produce nice error messages."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
from collections import defaultdict
|
||||
import errno
|
||||
|
@ -18,17 +20,11 @@ import sys
|
|||
import tokenize
|
||||
import types
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import IO
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE
|
||||
from _pytest._io.saferepr import saferepr
|
||||
|
@ -73,17 +69,17 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
|||
self.fnpats = config.getini("python_files")
|
||||
except ValueError:
|
||||
self.fnpats = ["test_*.py", "*_test.py"]
|
||||
self.session: Optional[Session] = None
|
||||
self._rewritten_names: Dict[str, Path] = {}
|
||||
self._must_rewrite: Set[str] = set()
|
||||
self.session: Session | None = None
|
||||
self._rewritten_names: dict[str, Path] = {}
|
||||
self._must_rewrite: set[str] = set()
|
||||
# flag to guard against trying to rewrite a pyc file while we are already writing another pyc file,
|
||||
# which might result in infinite recursion (#3506)
|
||||
self._writing_pyc = False
|
||||
self._basenames_to_check_rewrite = {"conftest"}
|
||||
self._marked_for_rewrite_cache: Dict[str, bool] = {}
|
||||
self._marked_for_rewrite_cache: dict[str, bool] = {}
|
||||
self._session_paths_checked = False
|
||||
|
||||
def set_session(self, session: Optional[Session]) -> None:
|
||||
def set_session(self, session: Session | None) -> None:
|
||||
self.session = session
|
||||
self._session_paths_checked = False
|
||||
|
||||
|
@ -93,9 +89,9 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
|||
def find_spec(
|
||||
self,
|
||||
name: str,
|
||||
path: Optional[Sequence[Union[str, bytes]]] = None,
|
||||
target: Optional[types.ModuleType] = None,
|
||||
) -> Optional[importlib.machinery.ModuleSpec]:
|
||||
path: Sequence[str | bytes] | None = None,
|
||||
target: types.ModuleType | None = None,
|
||||
) -> importlib.machinery.ModuleSpec | None:
|
||||
if self._writing_pyc:
|
||||
return None
|
||||
state = self.config.stash[assertstate_key]
|
||||
|
@ -132,7 +128,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
|||
|
||||
def create_module(
|
||||
self, spec: importlib.machinery.ModuleSpec
|
||||
) -> Optional[types.ModuleType]:
|
||||
) -> types.ModuleType | None:
|
||||
return None # default behaviour is fine
|
||||
|
||||
def exec_module(self, module: types.ModuleType) -> None:
|
||||
|
@ -177,7 +173,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
|||
state.trace(f"found cached rewritten pyc for {fn}")
|
||||
exec(co, module.__dict__)
|
||||
|
||||
def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool:
|
||||
def _early_rewrite_bailout(self, name: str, state: AssertionState) -> bool:
|
||||
"""A fast way to get out of rewriting modules.
|
||||
|
||||
Profiling has shown that the call to PathFinder.find_spec (inside of
|
||||
|
@ -216,7 +212,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
|||
state.trace(f"early skip of rewriting module: {name}")
|
||||
return True
|
||||
|
||||
def _should_rewrite(self, name: str, fn: str, state: "AssertionState") -> bool:
|
||||
def _should_rewrite(self, name: str, fn: str, state: AssertionState) -> bool:
|
||||
# always rewrite conftest files
|
||||
if os.path.basename(fn) == "conftest.py":
|
||||
state.trace(f"rewriting conftest file: {fn!r}")
|
||||
|
@ -237,7 +233,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
|||
|
||||
return self._is_marked_for_rewrite(name, state)
|
||||
|
||||
def _is_marked_for_rewrite(self, name: str, state: "AssertionState") -> bool:
|
||||
def _is_marked_for_rewrite(self, name: str, state: AssertionState) -> bool:
|
||||
try:
|
||||
return self._marked_for_rewrite_cache[name]
|
||||
except KeyError:
|
||||
|
@ -278,7 +274,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
|||
stacklevel=5,
|
||||
)
|
||||
|
||||
def get_data(self, pathname: Union[str, bytes]) -> bytes:
|
||||
def get_data(self, pathname: str | bytes) -> bytes:
|
||||
"""Optional PEP302 get_data API."""
|
||||
with open(pathname, "rb") as f:
|
||||
return f.read()
|
||||
|
@ -317,7 +313,7 @@ def _write_pyc_fp(
|
|||
|
||||
|
||||
def _write_pyc(
|
||||
state: "AssertionState",
|
||||
state: AssertionState,
|
||||
co: types.CodeType,
|
||||
source_stat: os.stat_result,
|
||||
pyc: Path,
|
||||
|
@ -341,7 +337,7 @@ def _write_pyc(
|
|||
return True
|
||||
|
||||
|
||||
def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeType]:
|
||||
def _rewrite_test(fn: Path, config: Config) -> tuple[os.stat_result, types.CodeType]:
|
||||
"""Read and rewrite *fn* and return the code object."""
|
||||
stat = os.stat(fn)
|
||||
source = fn.read_bytes()
|
||||
|
@ -354,7 +350,7 @@ def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeT
|
|||
|
||||
def _read_pyc(
|
||||
source: Path, pyc: Path, trace: Callable[[str], None] = lambda x: None
|
||||
) -> Optional[types.CodeType]:
|
||||
) -> types.CodeType | None:
|
||||
"""Possibly read a pytest pyc containing rewritten code.
|
||||
|
||||
Return rewritten code if successful or None if not.
|
||||
|
@ -404,8 +400,8 @@ def _read_pyc(
|
|||
def rewrite_asserts(
|
||||
mod: ast.Module,
|
||||
source: bytes,
|
||||
module_path: Optional[str] = None,
|
||||
config: Optional[Config] = None,
|
||||
module_path: str | None = None,
|
||||
config: Config | None = None,
|
||||
) -> None:
|
||||
"""Rewrite the assert statements in mod."""
|
||||
AssertionRewriter(module_path, config, source).run(mod)
|
||||
|
@ -425,7 +421,7 @@ def _saferepr(obj: object) -> str:
|
|||
return saferepr(obj, maxsize=maxsize).replace("\n", "\\n")
|
||||
|
||||
|
||||
def _get_maxsize_for_saferepr(config: Optional[Config]) -> Optional[int]:
|
||||
def _get_maxsize_for_saferepr(config: Config | None) -> int | None:
|
||||
"""Get `maxsize` configuration for saferepr based on the given config object."""
|
||||
if config is None:
|
||||
verbosity = 0
|
||||
|
@ -543,14 +539,14 @@ def traverse_node(node: ast.AST) -> Iterator[ast.AST]:
|
|||
|
||||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
def _get_assertion_exprs(src: bytes) -> Dict[int, str]:
|
||||
def _get_assertion_exprs(src: bytes) -> dict[int, str]:
|
||||
"""Return a mapping from {lineno: "assertion test expression"}."""
|
||||
ret: Dict[int, str] = {}
|
||||
ret: dict[int, str] = {}
|
||||
|
||||
depth = 0
|
||||
lines: List[str] = []
|
||||
assert_lineno: Optional[int] = None
|
||||
seen_lines: Set[int] = set()
|
||||
lines: list[str] = []
|
||||
assert_lineno: int | None = None
|
||||
seen_lines: set[int] = set()
|
||||
|
||||
def _write_and_reset() -> None:
|
||||
nonlocal depth, lines, assert_lineno, seen_lines
|
||||
|
@ -657,7 +653,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
"""
|
||||
|
||||
def __init__(
|
||||
self, module_path: Optional[str], config: Optional[Config], source: bytes
|
||||
self, module_path: str | None, config: Config | None, source: bytes
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.module_path = module_path
|
||||
|
@ -670,7 +666,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
self.enable_assertion_pass_hook = False
|
||||
self.source = source
|
||||
self.scope: tuple[ast.AST, ...] = ()
|
||||
self.variables_overwrite: defaultdict[tuple[ast.AST, ...], Dict[str, str]] = (
|
||||
self.variables_overwrite: defaultdict[tuple[ast.AST, ...], dict[str, str]] = (
|
||||
defaultdict(dict)
|
||||
)
|
||||
|
||||
|
@ -737,7 +733,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
|
||||
# Collect asserts.
|
||||
self.scope = (mod,)
|
||||
nodes: List[Union[ast.AST, Sentinel]] = [mod]
|
||||
nodes: list[ast.AST | Sentinel] = [mod]
|
||||
while nodes:
|
||||
node = nodes.pop()
|
||||
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
||||
|
@ -749,7 +745,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
assert isinstance(node, ast.AST)
|
||||
for name, field in ast.iter_fields(node):
|
||||
if isinstance(field, list):
|
||||
new: List[ast.AST] = []
|
||||
new: list[ast.AST] = []
|
||||
for i, child in enumerate(field):
|
||||
if isinstance(child, ast.Assert):
|
||||
# Transform assert.
|
||||
|
@ -821,7 +817,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
to format a string of %-formatted values as added by
|
||||
.explanation_param().
|
||||
"""
|
||||
self.explanation_specifiers: Dict[str, ast.expr] = {}
|
||||
self.explanation_specifiers: dict[str, ast.expr] = {}
|
||||
self.stack.append(self.explanation_specifiers)
|
||||
|
||||
def pop_format_context(self, expl_expr: ast.expr) -> ast.Name:
|
||||
|
@ -835,7 +831,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
current = self.stack.pop()
|
||||
if self.stack:
|
||||
self.explanation_specifiers = self.stack[-1]
|
||||
keys = [ast.Constant(key) for key in current.keys()]
|
||||
keys: list[ast.expr | None] = [ast.Constant(key) for key in current.keys()]
|
||||
format_dict = ast.Dict(keys, list(current.values()))
|
||||
form = ast.BinOp(expl_expr, ast.Mod(), format_dict)
|
||||
name = "@py_format" + str(next(self.variable_counter))
|
||||
|
@ -844,13 +840,13 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
self.expl_stmts.append(ast.Assign([ast.Name(name, ast.Store())], form))
|
||||
return ast.Name(name, ast.Load())
|
||||
|
||||
def generic_visit(self, node: ast.AST) -> Tuple[ast.Name, str]:
|
||||
def generic_visit(self, node: ast.AST) -> tuple[ast.Name, str]:
|
||||
"""Handle expressions we don't have custom code for."""
|
||||
assert isinstance(node, ast.expr)
|
||||
res = self.assign(node)
|
||||
return res, self.explanation_param(self.display(res))
|
||||
|
||||
def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]:
|
||||
def visit_Assert(self, assert_: ast.Assert) -> list[ast.stmt]:
|
||||
"""Return the AST statements to replace the ast.Assert instance.
|
||||
|
||||
This rewrites the test of an assertion to provide
|
||||
|
@ -874,15 +870,15 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
lineno=assert_.lineno,
|
||||
)
|
||||
|
||||
self.statements: List[ast.stmt] = []
|
||||
self.variables: List[str] = []
|
||||
self.statements: list[ast.stmt] = []
|
||||
self.variables: list[str] = []
|
||||
self.variable_counter = itertools.count()
|
||||
|
||||
if self.enable_assertion_pass_hook:
|
||||
self.format_variables: List[str] = []
|
||||
self.format_variables: list[str] = []
|
||||
|
||||
self.stack: List[Dict[str, ast.expr]] = []
|
||||
self.expl_stmts: List[ast.stmt] = []
|
||||
self.stack: list[dict[str, ast.expr]] = []
|
||||
self.expl_stmts: list[ast.stmt] = []
|
||||
self.push_format_context()
|
||||
# Rewrite assert into a bunch of statements.
|
||||
top_condition, explanation = self.visit(assert_.test)
|
||||
|
@ -926,13 +922,13 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
[*self.expl_stmts, hook_call_pass],
|
||||
[],
|
||||
)
|
||||
statements_pass = [hook_impl_test]
|
||||
statements_pass: list[ast.stmt] = [hook_impl_test]
|
||||
|
||||
# Test for assertion condition
|
||||
main_test = ast.If(negation, statements_fail, statements_pass)
|
||||
self.statements.append(main_test)
|
||||
if self.format_variables:
|
||||
variables = [
|
||||
variables: list[ast.expr] = [
|
||||
ast.Name(name, ast.Store()) for name in self.format_variables
|
||||
]
|
||||
clear_format = ast.Assign(variables, ast.Constant(None))
|
||||
|
@ -968,7 +964,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
ast.copy_location(node, assert_)
|
||||
return self.statements
|
||||
|
||||
def visit_NamedExpr(self, name: ast.NamedExpr) -> Tuple[ast.NamedExpr, str]:
|
||||
def visit_NamedExpr(self, name: ast.NamedExpr) -> tuple[ast.NamedExpr, str]:
|
||||
# This method handles the 'walrus operator' repr of the target
|
||||
# name if it's a local variable or _should_repr_global_name()
|
||||
# thinks it's acceptable.
|
||||
|
@ -980,7 +976,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
expr = ast.IfExp(test, self.display(name), ast.Constant(target_id))
|
||||
return name, self.explanation_param(expr)
|
||||
|
||||
def visit_Name(self, name: ast.Name) -> Tuple[ast.Name, str]:
|
||||
def visit_Name(self, name: ast.Name) -> tuple[ast.Name, str]:
|
||||
# Display the repr of the name if it's a local variable or
|
||||
# _should_repr_global_name() thinks it's acceptable.
|
||||
locs = ast.Call(self.builtin("locals"), [], [])
|
||||
|
@ -990,7 +986,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
expr = ast.IfExp(test, self.display(name), ast.Constant(name.id))
|
||||
return name, self.explanation_param(expr)
|
||||
|
||||
def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]:
|
||||
def visit_BoolOp(self, boolop: ast.BoolOp) -> tuple[ast.Name, str]:
|
||||
res_var = self.variable()
|
||||
expl_list = self.assign(ast.List([], ast.Load()))
|
||||
app = ast.Attribute(expl_list, "append", ast.Load())
|
||||
|
@ -1002,7 +998,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
# Process each operand, short-circuiting if needed.
|
||||
for i, v in enumerate(boolop.values):
|
||||
if i:
|
||||
fail_inner: List[ast.stmt] = []
|
||||
fail_inner: list[ast.stmt] = []
|
||||
# cond is set in a prior loop iteration below
|
||||
self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa: F821
|
||||
self.expl_stmts = fail_inner
|
||||
|
@ -1030,7 +1026,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
cond: ast.expr = res
|
||||
if is_or:
|
||||
cond = ast.UnaryOp(ast.Not(), cond)
|
||||
inner: List[ast.stmt] = []
|
||||
inner: list[ast.stmt] = []
|
||||
self.statements.append(ast.If(cond, inner, []))
|
||||
self.statements = body = inner
|
||||
self.statements = save
|
||||
|
@ -1039,13 +1035,13 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
expl = self.pop_format_context(expl_template)
|
||||
return ast.Name(res_var, ast.Load()), self.explanation_param(expl)
|
||||
|
||||
def visit_UnaryOp(self, unary: ast.UnaryOp) -> Tuple[ast.Name, str]:
|
||||
def visit_UnaryOp(self, unary: ast.UnaryOp) -> tuple[ast.Name, str]:
|
||||
pattern = UNARY_MAP[unary.op.__class__]
|
||||
operand_res, operand_expl = self.visit(unary.operand)
|
||||
res = self.assign(ast.UnaryOp(unary.op, operand_res))
|
||||
return res, pattern % (operand_expl,)
|
||||
|
||||
def visit_BinOp(self, binop: ast.BinOp) -> Tuple[ast.Name, str]:
|
||||
def visit_BinOp(self, binop: ast.BinOp) -> tuple[ast.Name, str]:
|
||||
symbol = BINOP_MAP[binop.op.__class__]
|
||||
left_expr, left_expl = self.visit(binop.left)
|
||||
right_expr, right_expl = self.visit(binop.right)
|
||||
|
@ -1053,7 +1049,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
res = self.assign(ast.BinOp(left_expr, binop.op, right_expr))
|
||||
return res, explanation
|
||||
|
||||
def visit_Call(self, call: ast.Call) -> Tuple[ast.Name, str]:
|
||||
def visit_Call(self, call: ast.Call) -> tuple[ast.Name, str]:
|
||||
new_func, func_expl = self.visit(call.func)
|
||||
arg_expls = []
|
||||
new_args = []
|
||||
|
@ -1085,13 +1081,13 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
outer_expl = f"{res_expl}\n{{{res_expl} = {expl}\n}}"
|
||||
return res, outer_expl
|
||||
|
||||
def visit_Starred(self, starred: ast.Starred) -> Tuple[ast.Starred, str]:
|
||||
def visit_Starred(self, starred: ast.Starred) -> tuple[ast.Starred, str]:
|
||||
# A Starred node can appear in a function call.
|
||||
res, expl = self.visit(starred.value)
|
||||
new_starred = ast.Starred(res, starred.ctx)
|
||||
return new_starred, "*" + expl
|
||||
|
||||
def visit_Attribute(self, attr: ast.Attribute) -> Tuple[ast.Name, str]:
|
||||
def visit_Attribute(self, attr: ast.Attribute) -> tuple[ast.Name, str]:
|
||||
if not isinstance(attr.ctx, ast.Load):
|
||||
return self.generic_visit(attr)
|
||||
value, value_expl = self.visit(attr.value)
|
||||
|
@ -1101,7 +1097,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
expl = pat % (res_expl, res_expl, value_expl, attr.attr)
|
||||
return res, expl
|
||||
|
||||
def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]:
|
||||
def visit_Compare(self, comp: ast.Compare) -> tuple[ast.expr, str]:
|
||||
self.push_format_context()
|
||||
# We first check if we have overwritten a variable in the previous assert
|
||||
if isinstance(
|
||||
|
@ -1114,11 +1110,11 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
if isinstance(comp.left, (ast.Compare, ast.BoolOp)):
|
||||
left_expl = f"({left_expl})"
|
||||
res_variables = [self.variable() for i in range(len(comp.ops))]
|
||||
load_names = [ast.Name(v, ast.Load()) for v in res_variables]
|
||||
load_names: list[ast.expr] = [ast.Name(v, ast.Load()) for v in res_variables]
|
||||
store_names = [ast.Name(v, ast.Store()) for v in res_variables]
|
||||
it = zip(range(len(comp.ops)), comp.ops, comp.comparators)
|
||||
expls = []
|
||||
syms = []
|
||||
expls: list[ast.expr] = []
|
||||
syms: list[ast.expr] = []
|
||||
results = [left_res]
|
||||
for i, op, next_operand in it:
|
||||
if (
|
||||
|
|
|
@ -4,8 +4,7 @@ Current default behaviour is to truncate assertion explanations at
|
|||
terminal lines, unless running with an assertions verbosity level of at least 2 or running on CI.
|
||||
"""
|
||||
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from __future__ import annotations
|
||||
|
||||
from _pytest.assertion import util
|
||||
from _pytest.config import Config
|
||||
|
@ -18,8 +17,8 @@ USAGE_MSG = "use '-vv' to show"
|
|||
|
||||
|
||||
def truncate_if_required(
|
||||
explanation: List[str], item: Item, max_length: Optional[int] = None
|
||||
) -> List[str]:
|
||||
explanation: list[str], item: Item, max_length: int | None = None
|
||||
) -> list[str]:
|
||||
"""Truncate this assertion explanation if the given test item is eligible."""
|
||||
if _should_truncate_item(item):
|
||||
return _truncate_explanation(explanation)
|
||||
|
@ -33,10 +32,10 @@ def _should_truncate_item(item: Item) -> bool:
|
|||
|
||||
|
||||
def _truncate_explanation(
|
||||
input_lines: List[str],
|
||||
max_lines: Optional[int] = None,
|
||||
max_chars: Optional[int] = None,
|
||||
) -> List[str]:
|
||||
input_lines: list[str],
|
||||
max_lines: int | None = None,
|
||||
max_chars: int | None = None,
|
||||
) -> list[str]:
|
||||
"""Truncate given list of strings that makes up the assertion explanation.
|
||||
|
||||
Truncates to either 8 lines, or 640 characters - whichever the input reaches
|
||||
|
@ -100,7 +99,7 @@ def _truncate_explanation(
|
|||
]
|
||||
|
||||
|
||||
def _truncate_by_char_count(input_lines: List[str], max_chars: int) -> List[str]:
|
||||
def _truncate_by_char_count(input_lines: list[str], max_chars: int) -> list[str]:
|
||||
# Find point at which input length exceeds total allowed length
|
||||
iterated_char_count = 0
|
||||
for iterated_index, input_line in enumerate(input_lines):
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Utilities for assertion debugging."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import collections.abc
|
||||
import os
|
||||
import pprint
|
||||
|
@ -8,10 +10,8 @@ from typing import AbstractSet
|
|||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Literal
|
||||
from typing import Mapping
|
||||
from typing import Optional
|
||||
from typing import Protocol
|
||||
from typing import Sequence
|
||||
from unicodedata import normalize
|
||||
|
@ -28,14 +28,14 @@ from _pytest.config import Config
|
|||
# interpretation code and assertion rewriter to detect this plugin was
|
||||
# loaded and in turn call the hooks defined here as part of the
|
||||
# DebugInterpreter.
|
||||
_reprcompare: Optional[Callable[[str, object, object], Optional[str]]] = None
|
||||
_reprcompare: Callable[[str, object, object], str | None] | None = None
|
||||
|
||||
# Works similarly as _reprcompare attribute. Is populated with the hook call
|
||||
# when pytest_runtest_setup is called.
|
||||
_assertion_pass: Optional[Callable[[int, str, str], None]] = None
|
||||
_assertion_pass: Callable[[int, str, str], None] | None = None
|
||||
|
||||
# Config object which is assigned during pytest_runtest_protocol.
|
||||
_config: Optional[Config] = None
|
||||
_config: Config | None = None
|
||||
|
||||
|
||||
class _HighlightFunc(Protocol):
|
||||
|
@ -58,7 +58,7 @@ def format_explanation(explanation: str) -> str:
|
|||
return "\n".join(result)
|
||||
|
||||
|
||||
def _split_explanation(explanation: str) -> List[str]:
|
||||
def _split_explanation(explanation: str) -> list[str]:
|
||||
r"""Return a list of individual lines in the explanation.
|
||||
|
||||
This will return a list of lines split on '\n{', '\n}' and '\n~'.
|
||||
|
@ -75,7 +75,7 @@ def _split_explanation(explanation: str) -> List[str]:
|
|||
return lines
|
||||
|
||||
|
||||
def _format_lines(lines: Sequence[str]) -> List[str]:
|
||||
def _format_lines(lines: Sequence[str]) -> list[str]:
|
||||
"""Format the individual lines.
|
||||
|
||||
This will replace the '{', '}' and '~' characters of our mini formatting
|
||||
|
@ -169,7 +169,7 @@ def has_default_eq(
|
|||
|
||||
def assertrepr_compare(
|
||||
config, op: str, left: Any, right: Any, use_ascii: bool = False
|
||||
) -> Optional[List[str]]:
|
||||
) -> list[str] | None:
|
||||
"""Return specialised explanations for some operators/operands."""
|
||||
verbose = config.get_verbosity(Config.VERBOSITY_ASSERTIONS)
|
||||
|
||||
|
@ -239,7 +239,7 @@ def assertrepr_compare(
|
|||
|
||||
def _compare_eq_any(
|
||||
left: Any, right: Any, highlighter: _HighlightFunc, verbose: int = 0
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
explanation = []
|
||||
if istext(left) and istext(right):
|
||||
explanation = _diff_text(left, right, verbose)
|
||||
|
@ -274,7 +274,7 @@ def _compare_eq_any(
|
|||
return explanation
|
||||
|
||||
|
||||
def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
|
||||
def _diff_text(left: str, right: str, verbose: int = 0) -> list[str]:
|
||||
"""Return the explanation for the diff between text.
|
||||
|
||||
Unless --verbose is used this will skip leading and trailing
|
||||
|
@ -282,7 +282,7 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
|
|||
"""
|
||||
from difflib import ndiff
|
||||
|
||||
explanation: List[str] = []
|
||||
explanation: list[str] = []
|
||||
|
||||
if verbose < 1:
|
||||
i = 0 # just in case left or right has zero length
|
||||
|
@ -327,7 +327,7 @@ def _compare_eq_iterable(
|
|||
right: Iterable[Any],
|
||||
highlighter: _HighlightFunc,
|
||||
verbose: int = 0,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
if verbose <= 0 and not running_on_ci():
|
||||
return ["Use -v to get more diff"]
|
||||
# dynamic import to speedup pytest
|
||||
|
@ -356,9 +356,9 @@ def _compare_eq_sequence(
|
|||
right: Sequence[Any],
|
||||
highlighter: _HighlightFunc,
|
||||
verbose: int = 0,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes)
|
||||
explanation: List[str] = []
|
||||
explanation: list[str] = []
|
||||
len_left = len(left)
|
||||
len_right = len(right)
|
||||
for i in range(min(len_left, len_right)):
|
||||
|
@ -417,7 +417,7 @@ def _compare_eq_set(
|
|||
right: AbstractSet[Any],
|
||||
highlighter: _HighlightFunc,
|
||||
verbose: int = 0,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
explanation = []
|
||||
explanation.extend(_set_one_sided_diff("left", left, right, highlighter))
|
||||
explanation.extend(_set_one_sided_diff("right", right, left, highlighter))
|
||||
|
@ -429,7 +429,7 @@ def _compare_gt_set(
|
|||
right: AbstractSet[Any],
|
||||
highlighter: _HighlightFunc,
|
||||
verbose: int = 0,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
explanation = _compare_gte_set(left, right, highlighter)
|
||||
if not explanation:
|
||||
return ["Both sets are equal"]
|
||||
|
@ -441,7 +441,7 @@ def _compare_lt_set(
|
|||
right: AbstractSet[Any],
|
||||
highlighter: _HighlightFunc,
|
||||
verbose: int = 0,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
explanation = _compare_lte_set(left, right, highlighter)
|
||||
if not explanation:
|
||||
return ["Both sets are equal"]
|
||||
|
@ -453,7 +453,7 @@ def _compare_gte_set(
|
|||
right: AbstractSet[Any],
|
||||
highlighter: _HighlightFunc,
|
||||
verbose: int = 0,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
return _set_one_sided_diff("right", right, left, highlighter)
|
||||
|
||||
|
||||
|
@ -462,7 +462,7 @@ def _compare_lte_set(
|
|||
right: AbstractSet[Any],
|
||||
highlighter: _HighlightFunc,
|
||||
verbose: int = 0,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
return _set_one_sided_diff("left", left, right, highlighter)
|
||||
|
||||
|
||||
|
@ -471,7 +471,7 @@ def _set_one_sided_diff(
|
|||
set1: AbstractSet[Any],
|
||||
set2: AbstractSet[Any],
|
||||
highlighter: _HighlightFunc,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
explanation = []
|
||||
diff = set1 - set2
|
||||
if diff:
|
||||
|
@ -486,8 +486,8 @@ def _compare_eq_dict(
|
|||
right: Mapping[Any, Any],
|
||||
highlighter: _HighlightFunc,
|
||||
verbose: int = 0,
|
||||
) -> List[str]:
|
||||
explanation: List[str] = []
|
||||
) -> list[str]:
|
||||
explanation: list[str] = []
|
||||
set_left = set(left)
|
||||
set_right = set(right)
|
||||
common = set_left.intersection(set_right)
|
||||
|
@ -531,7 +531,7 @@ def _compare_eq_dict(
|
|||
|
||||
def _compare_eq_cls(
|
||||
left: Any, right: Any, highlighter: _HighlightFunc, verbose: int
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
if not has_default_eq(left):
|
||||
return []
|
||||
if isdatacls(left):
|
||||
|
@ -584,7 +584,7 @@ def _compare_eq_cls(
|
|||
return explanation
|
||||
|
||||
|
||||
def _notin_text(term: str, text: str, verbose: int = 0) -> List[str]:
|
||||
def _notin_text(term: str, text: str, verbose: int = 0) -> list[str]:
|
||||
index = text.find(term)
|
||||
head = text[:index]
|
||||
tail = text[index + len(term) :]
|
||||
|
|
|
@ -3,20 +3,17 @@
|
|||
|
||||
# This plugin was not named "cache" to avoid conflicts with the external
|
||||
# pytest-cache version.
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import errno
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
from typing import Dict
|
||||
from typing import final
|
||||
from typing import Generator
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Union
|
||||
|
||||
from .pathlib import resolve_from_str
|
||||
from .pathlib import rm_rf
|
||||
|
@ -77,7 +74,7 @@ class Cache:
|
|||
self._config = config
|
||||
|
||||
@classmethod
|
||||
def for_config(cls, config: Config, *, _ispytest: bool = False) -> "Cache":
|
||||
def for_config(cls, config: Config, *, _ispytest: bool = False) -> Cache:
|
||||
"""Create the Cache instance for a Config.
|
||||
|
||||
:meta private:
|
||||
|
@ -249,7 +246,7 @@ class Cache:
|
|||
|
||||
|
||||
class LFPluginCollWrapper:
|
||||
def __init__(self, lfplugin: "LFPlugin") -> None:
|
||||
def __init__(self, lfplugin: LFPlugin) -> None:
|
||||
self.lfplugin = lfplugin
|
||||
self._collected_at_least_one_failure = False
|
||||
|
||||
|
@ -263,7 +260,7 @@ class LFPluginCollWrapper:
|
|||
lf_paths = self.lfplugin._last_failed_paths
|
||||
|
||||
# Use stable sort to prioritize last failed.
|
||||
def sort_key(node: Union[nodes.Item, nodes.Collector]) -> bool:
|
||||
def sort_key(node: nodes.Item | nodes.Collector) -> bool:
|
||||
return node.path in lf_paths
|
||||
|
||||
res.result = sorted(
|
||||
|
@ -301,13 +298,13 @@ class LFPluginCollWrapper:
|
|||
|
||||
|
||||
class LFPluginCollSkipfiles:
|
||||
def __init__(self, lfplugin: "LFPlugin") -> None:
|
||||
def __init__(self, lfplugin: LFPlugin) -> None:
|
||||
self.lfplugin = lfplugin
|
||||
|
||||
@hookimpl
|
||||
def pytest_make_collect_report(
|
||||
self, collector: nodes.Collector
|
||||
) -> Optional[CollectReport]:
|
||||
) -> CollectReport | None:
|
||||
if isinstance(collector, File):
|
||||
if collector.path not in self.lfplugin._last_failed_paths:
|
||||
self.lfplugin._skipped_files += 1
|
||||
|
@ -326,9 +323,9 @@ class LFPlugin:
|
|||
active_keys = "lf", "failedfirst"
|
||||
self.active = any(config.getoption(key) for key in active_keys)
|
||||
assert config.cache
|
||||
self.lastfailed: Dict[str, bool] = config.cache.get("cache/lastfailed", {})
|
||||
self._previously_failed_count: Optional[int] = None
|
||||
self._report_status: Optional[str] = None
|
||||
self.lastfailed: dict[str, bool] = config.cache.get("cache/lastfailed", {})
|
||||
self._previously_failed_count: int | None = None
|
||||
self._report_status: str | None = None
|
||||
self._skipped_files = 0 # count skipped files during collection due to --lf
|
||||
|
||||
if config.getoption("lf"):
|
||||
|
@ -337,7 +334,7 @@ class LFPlugin:
|
|||
LFPluginCollWrapper(self), "lfplugin-collwrapper"
|
||||
)
|
||||
|
||||
def get_last_failed_paths(self) -> Set[Path]:
|
||||
def get_last_failed_paths(self) -> set[Path]:
|
||||
"""Return a set with all Paths of the previously failed nodeids and
|
||||
their parents."""
|
||||
rootpath = self.config.rootpath
|
||||
|
@ -348,7 +345,7 @@ class LFPlugin:
|
|||
result.update(path.parents)
|
||||
return {x for x in result if x.exists()}
|
||||
|
||||
def pytest_report_collectionfinish(self) -> Optional[str]:
|
||||
def pytest_report_collectionfinish(self) -> str | None:
|
||||
if self.active and self.config.getoption("verbose") >= 0:
|
||||
return "run-last-failure: %s" % self._report_status
|
||||
return None
|
||||
|
@ -370,7 +367,7 @@ class LFPlugin:
|
|||
|
||||
@hookimpl(wrapper=True, tryfirst=True)
|
||||
def pytest_collection_modifyitems(
|
||||
self, config: Config, items: List[nodes.Item]
|
||||
self, config: Config, items: list[nodes.Item]
|
||||
) -> Generator[None, None, None]:
|
||||
res = yield
|
||||
|
||||
|
@ -442,13 +439,13 @@ class NFPlugin:
|
|||
|
||||
@hookimpl(wrapper=True, tryfirst=True)
|
||||
def pytest_collection_modifyitems(
|
||||
self, items: List[nodes.Item]
|
||||
self, items: list[nodes.Item]
|
||||
) -> Generator[None, None, None]:
|
||||
res = yield
|
||||
|
||||
if self.active:
|
||||
new_items: Dict[str, nodes.Item] = {}
|
||||
other_items: Dict[str, nodes.Item] = {}
|
||||
new_items: dict[str, nodes.Item] = {}
|
||||
other_items: dict[str, nodes.Item] = {}
|
||||
for item in items:
|
||||
if item.nodeid not in self.cached_nodeids:
|
||||
new_items[item.nodeid] = item
|
||||
|
@ -464,7 +461,7 @@ class NFPlugin:
|
|||
|
||||
return res
|
||||
|
||||
def _get_increasing_order(self, items: Iterable[nodes.Item]) -> List[nodes.Item]:
|
||||
def _get_increasing_order(self, items: Iterable[nodes.Item]) -> list[nodes.Item]:
|
||||
return sorted(items, key=lambda item: item.path.stat().st_mtime, reverse=True)
|
||||
|
||||
def pytest_sessionfinish(self) -> None:
|
||||
|
@ -541,7 +538,7 @@ def pytest_addoption(parser: Parser) -> None:
|
|||
)
|
||||
|
||||
|
||||
def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
|
||||
def pytest_cmdline_main(config: Config) -> int | ExitCode | None:
|
||||
if config.option.cacheshow and not config.option.help:
|
||||
from _pytest.main import wrap_session
|
||||
|
||||
|
@ -572,7 +569,7 @@ def cache(request: FixtureRequest) -> Cache:
|
|||
return request.config.cache
|
||||
|
||||
|
||||
def pytest_report_header(config: Config) -> Optional[str]:
|
||||
def pytest_report_header(config: Config) -> str | None:
|
||||
"""Display cachedir with --cache-show and if non-default."""
|
||||
if config.option.verbose > 0 or config.getini("cache_dir") != ".pytest_cache":
|
||||
assert config.cache is not None
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Per-test stdout/stderr capturing mechanism."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
import collections
|
||||
import contextlib
|
||||
|
@ -19,15 +21,14 @@ from typing import Generator
|
|||
from typing import Generic
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Literal
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import TextIO
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from _pytest.config import Config
|
||||
from _pytest.config import hookimpl
|
||||
|
@ -213,7 +214,7 @@ class DontReadFromInput(TextIO):
|
|||
def __next__(self) -> str:
|
||||
return self.readline()
|
||||
|
||||
def readlines(self, hint: Optional[int] = -1) -> List[str]:
|
||||
def readlines(self, hint: int | None = -1) -> list[str]:
|
||||
raise OSError(
|
||||
"pytest: reading from stdin while output is captured! Consider using `-s`."
|
||||
)
|
||||
|
@ -245,7 +246,7 @@ class DontReadFromInput(TextIO):
|
|||
def tell(self) -> int:
|
||||
raise UnsupportedOperation("redirected stdin is pseudofile, has no tell()")
|
||||
|
||||
def truncate(self, size: Optional[int] = None) -> int:
|
||||
def truncate(self, size: int | None = None) -> int:
|
||||
raise UnsupportedOperation("cannot truncate stdin")
|
||||
|
||||
def write(self, data: str) -> int:
|
||||
|
@ -257,14 +258,14 @@ class DontReadFromInput(TextIO):
|
|||
def writable(self) -> bool:
|
||||
return False
|
||||
|
||||
def __enter__(self) -> "DontReadFromInput":
|
||||
def __enter__(self) -> Self:
|
||||
return self
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
type: Optional[Type[BaseException]],
|
||||
value: Optional[BaseException],
|
||||
traceback: Optional[TracebackType],
|
||||
type: type[BaseException] | None,
|
||||
value: BaseException | None,
|
||||
traceback: TracebackType | None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
|
@ -339,7 +340,7 @@ class NoCapture(CaptureBase[str]):
|
|||
|
||||
class SysCaptureBase(CaptureBase[AnyStr]):
|
||||
def __init__(
|
||||
self, fd: int, tmpfile: Optional[TextIO] = None, *, tee: bool = False
|
||||
self, fd: int, tmpfile: TextIO | None = None, *, tee: bool = False
|
||||
) -> None:
|
||||
name = patchsysdict[fd]
|
||||
self._old: TextIO = getattr(sys, name)
|
||||
|
@ -370,7 +371,7 @@ class SysCaptureBase(CaptureBase[AnyStr]):
|
|||
self.tmpfile,
|
||||
)
|
||||
|
||||
def _assert_state(self, op: str, states: Tuple[str, ...]) -> None:
|
||||
def _assert_state(self, op: str, states: tuple[str, ...]) -> None:
|
||||
assert (
|
||||
self._state in states
|
||||
), "cannot {} in state {!r}: expected one of {}".format(
|
||||
|
@ -457,7 +458,7 @@ class FDCaptureBase(CaptureBase[AnyStr]):
|
|||
# Further complications are the need to support suspend() and the
|
||||
# possibility of FD reuse (e.g. the tmpfile getting the very same
|
||||
# target FD). The following approach is robust, I believe.
|
||||
self.targetfd_invalid: Optional[int] = os.open(os.devnull, os.O_RDWR)
|
||||
self.targetfd_invalid: int | None = os.open(os.devnull, os.O_RDWR)
|
||||
os.dup2(self.targetfd_invalid, targetfd)
|
||||
else:
|
||||
self.targetfd_invalid = None
|
||||
|
@ -487,7 +488,7 @@ class FDCaptureBase(CaptureBase[AnyStr]):
|
|||
f"_state={self._state!r} tmpfile={self.tmpfile!r}>"
|
||||
)
|
||||
|
||||
def _assert_state(self, op: str, states: Tuple[str, ...]) -> None:
|
||||
def _assert_state(self, op: str, states: tuple[str, ...]) -> None:
|
||||
assert (
|
||||
self._state in states
|
||||
), "cannot {} in state {!r}: expected one of {}".format(
|
||||
|
@ -609,13 +610,13 @@ class MultiCapture(Generic[AnyStr]):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
in_: Optional[CaptureBase[AnyStr]],
|
||||
out: Optional[CaptureBase[AnyStr]],
|
||||
err: Optional[CaptureBase[AnyStr]],
|
||||
in_: CaptureBase[AnyStr] | None,
|
||||
out: CaptureBase[AnyStr] | None,
|
||||
err: CaptureBase[AnyStr] | None,
|
||||
) -> None:
|
||||
self.in_: Optional[CaptureBase[AnyStr]] = in_
|
||||
self.out: Optional[CaptureBase[AnyStr]] = out
|
||||
self.err: Optional[CaptureBase[AnyStr]] = err
|
||||
self.in_: CaptureBase[AnyStr] | None = in_
|
||||
self.out: CaptureBase[AnyStr] | None = out
|
||||
self.err: CaptureBase[AnyStr] | None = err
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
|
@ -632,7 +633,7 @@ class MultiCapture(Generic[AnyStr]):
|
|||
if self.err:
|
||||
self.err.start()
|
||||
|
||||
def pop_outerr_to_orig(self) -> Tuple[AnyStr, AnyStr]:
|
||||
def pop_outerr_to_orig(self) -> tuple[AnyStr, AnyStr]:
|
||||
"""Pop current snapshot out/err capture and flush to orig streams."""
|
||||
out, err = self.readouterr()
|
||||
if out:
|
||||
|
@ -725,8 +726,8 @@ class CaptureManager:
|
|||
|
||||
def __init__(self, method: _CaptureMethod) -> None:
|
||||
self._method: Final = method
|
||||
self._global_capturing: Optional[MultiCapture[str]] = None
|
||||
self._capture_fixture: Optional[CaptureFixture[Any]] = None
|
||||
self._global_capturing: MultiCapture[str] | None = None
|
||||
self._capture_fixture: CaptureFixture[Any] | None = None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
|
@ -734,7 +735,7 @@ class CaptureManager:
|
|||
f"_capture_fixture={self._capture_fixture!r}>"
|
||||
)
|
||||
|
||||
def is_capturing(self) -> Union[str, bool]:
|
||||
def is_capturing(self) -> str | bool:
|
||||
if self.is_globally_capturing():
|
||||
return "global"
|
||||
if self._capture_fixture:
|
||||
|
@ -782,7 +783,7 @@ class CaptureManager:
|
|||
|
||||
# Fixture Control
|
||||
|
||||
def set_fixture(self, capture_fixture: "CaptureFixture[Any]") -> None:
|
||||
def set_fixture(self, capture_fixture: CaptureFixture[Any]) -> None:
|
||||
if self._capture_fixture:
|
||||
current_fixture = self._capture_fixture.request.fixturename
|
||||
requested_fixture = capture_fixture.request.fixturename
|
||||
|
@ -897,15 +898,15 @@ class CaptureFixture(Generic[AnyStr]):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
captureclass: Type[CaptureBase[AnyStr]],
|
||||
captureclass: type[CaptureBase[AnyStr]],
|
||||
request: SubRequest,
|
||||
*,
|
||||
_ispytest: bool = False,
|
||||
) -> None:
|
||||
check_ispytest(_ispytest)
|
||||
self.captureclass: Type[CaptureBase[AnyStr]] = captureclass
|
||||
self.captureclass: type[CaptureBase[AnyStr]] = captureclass
|
||||
self.request = request
|
||||
self._capture: Optional[MultiCapture[AnyStr]] = None
|
||||
self._capture: MultiCapture[AnyStr] | None = None
|
||||
self._captured_out: AnyStr = self.captureclass.EMPTY_BUFFER
|
||||
self._captured_err: AnyStr = self.captureclass.EMPTY_BUFFER
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Command line options, ini-file and conftest.py processing."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import collections.abc
|
||||
import copy
|
||||
|
@ -11,7 +13,7 @@ import glob
|
|||
import importlib.metadata
|
||||
import inspect
|
||||
import os
|
||||
from pathlib import Path
|
||||
import pathlib
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
|
@ -21,22 +23,16 @@ from types import FunctionType
|
|||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
from typing import Dict
|
||||
from typing import Final
|
||||
from typing import final
|
||||
from typing import Generator
|
||||
from typing import IO
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
from typing import TextIO
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
import pluggy
|
||||
|
@ -74,6 +70,7 @@ if TYPE_CHECKING:
|
|||
from .argparsing import Argument
|
||||
from .argparsing import Parser
|
||||
from _pytest._code.code import _TracebackStyle
|
||||
from _pytest.cacheprovider import Cache
|
||||
from _pytest.terminal import TerminalReporter
|
||||
|
||||
|
||||
|
@ -117,7 +114,7 @@ class ExitCode(enum.IntEnum):
|
|||
class ConftestImportFailure(Exception):
|
||||
def __init__(
|
||||
self,
|
||||
path: Path,
|
||||
path: pathlib.Path,
|
||||
*,
|
||||
cause: Exception,
|
||||
) -> None:
|
||||
|
@ -140,9 +137,9 @@ def filter_traceback_for_conftest_import_failure(
|
|||
|
||||
|
||||
def main(
|
||||
args: Optional[Union[List[str], "os.PathLike[str]"]] = None,
|
||||
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
|
||||
) -> Union[int, ExitCode]:
|
||||
args: list[str] | os.PathLike[str] | None = None,
|
||||
plugins: Sequence[str | _PluggyPlugin] | None = None,
|
||||
) -> int | ExitCode:
|
||||
"""Perform an in-process test run.
|
||||
|
||||
:param args:
|
||||
|
@ -175,9 +172,7 @@ def main(
|
|||
return ExitCode.USAGE_ERROR
|
||||
else:
|
||||
try:
|
||||
ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main(
|
||||
config=config
|
||||
)
|
||||
ret: ExitCode | int = config.hook.pytest_cmdline_main(config=config)
|
||||
try:
|
||||
return ExitCode(ret)
|
||||
except ValueError:
|
||||
|
@ -285,9 +280,9 @@ builtin_plugins.add("pytester_assertions")
|
|||
|
||||
|
||||
def get_config(
|
||||
args: Optional[List[str]] = None,
|
||||
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
|
||||
) -> "Config":
|
||||
args: list[str] | None = None,
|
||||
plugins: Sequence[str | _PluggyPlugin] | None = None,
|
||||
) -> Config:
|
||||
# subsequent calls to main will create a fresh instance
|
||||
pluginmanager = PytestPluginManager()
|
||||
config = Config(
|
||||
|
@ -295,7 +290,7 @@ def get_config(
|
|||
invocation_params=Config.InvocationParams(
|
||||
args=args or (),
|
||||
plugins=plugins,
|
||||
dir=Path.cwd(),
|
||||
dir=pathlib.Path.cwd(),
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -309,7 +304,7 @@ def get_config(
|
|||
return config
|
||||
|
||||
|
||||
def get_plugin_manager() -> "PytestPluginManager":
|
||||
def get_plugin_manager() -> PytestPluginManager:
|
||||
"""Obtain a new instance of the
|
||||
:py:class:`pytest.PytestPluginManager`, with default plugins
|
||||
already loaded.
|
||||
|
@ -321,9 +316,9 @@ def get_plugin_manager() -> "PytestPluginManager":
|
|||
|
||||
|
||||
def _prepareconfig(
|
||||
args: Optional[Union[List[str], "os.PathLike[str]"]] = None,
|
||||
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
|
||||
) -> "Config":
|
||||
args: list[str] | os.PathLike[str] | None = None,
|
||||
plugins: Sequence[str | _PluggyPlugin] | None = None,
|
||||
) -> Config:
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
elif isinstance(args, os.PathLike):
|
||||
|
@ -352,7 +347,7 @@ def _prepareconfig(
|
|||
raise
|
||||
|
||||
|
||||
def _get_directory(path: Path) -> Path:
|
||||
def _get_directory(path: pathlib.Path) -> pathlib.Path:
|
||||
"""Get the directory of a path - itself if already a directory."""
|
||||
if path.is_file():
|
||||
return path.parent
|
||||
|
@ -363,14 +358,14 @@ def _get_directory(path: Path) -> Path:
|
|||
def _get_legacy_hook_marks(
|
||||
method: Any,
|
||||
hook_type: str,
|
||||
opt_names: Tuple[str, ...],
|
||||
) -> Dict[str, bool]:
|
||||
opt_names: tuple[str, ...],
|
||||
) -> dict[str, bool]:
|
||||
if TYPE_CHECKING:
|
||||
# abuse typeguard from importlib to avoid massive method type union thats lacking a alias
|
||||
assert inspect.isroutine(method)
|
||||
known_marks: Set[str] = {m.name for m in getattr(method, "pytestmark", [])}
|
||||
must_warn: List[str] = []
|
||||
opts: Dict[str, bool] = {}
|
||||
known_marks: set[str] = {m.name for m in getattr(method, "pytestmark", [])}
|
||||
must_warn: list[str] = []
|
||||
opts: dict[str, bool] = {}
|
||||
for opt_name in opt_names:
|
||||
opt_attr = getattr(method, opt_name, AttributeError)
|
||||
if opt_attr is not AttributeError:
|
||||
|
@ -409,13 +404,13 @@ class PytestPluginManager(PluginManager):
|
|||
|
||||
# -- State related to local conftest plugins.
|
||||
# All loaded conftest modules.
|
||||
self._conftest_plugins: Set[types.ModuleType] = set()
|
||||
self._conftest_plugins: set[types.ModuleType] = set()
|
||||
# All conftest modules applicable for a directory.
|
||||
# This includes the directory's own conftest modules as well
|
||||
# as those of its parent directories.
|
||||
self._dirpath2confmods: Dict[Path, List[types.ModuleType]] = {}
|
||||
self._dirpath2confmods: dict[pathlib.Path, list[types.ModuleType]] = {}
|
||||
# Cutoff directory above which conftests are no longer discovered.
|
||||
self._confcutdir: Optional[Path] = None
|
||||
self._confcutdir: pathlib.Path | None = None
|
||||
# If set, conftest loading is skipped.
|
||||
self._noconftest = False
|
||||
|
||||
|
@ -429,7 +424,7 @@ class PytestPluginManager(PluginManager):
|
|||
# previously we would issue a warning when a plugin was skipped, but
|
||||
# since we refactored warnings as first citizens of Config, they are
|
||||
# just stored here to be used later.
|
||||
self.skipped_plugins: List[Tuple[str, str]] = []
|
||||
self.skipped_plugins: list[tuple[str, str]] = []
|
||||
|
||||
self.add_hookspecs(_pytest.hookspec)
|
||||
self.register(self)
|
||||
|
@ -455,7 +450,7 @@ class PytestPluginManager(PluginManager):
|
|||
|
||||
def parse_hookimpl_opts(
|
||||
self, plugin: _PluggyPlugin, name: str
|
||||
) -> Optional[HookimplOpts]:
|
||||
) -> HookimplOpts | None:
|
||||
""":meta private:"""
|
||||
# pytest hooks are always prefixed with "pytest_",
|
||||
# so we avoid accessing possibly non-readable attributes
|
||||
|
@ -479,7 +474,7 @@ class PytestPluginManager(PluginManager):
|
|||
method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper")
|
||||
)
|
||||
|
||||
def parse_hookspec_opts(self, module_or_class, name: str) -> Optional[HookspecOpts]:
|
||||
def parse_hookspec_opts(self, module_or_class, name: str) -> HookspecOpts | None:
|
||||
""":meta private:"""
|
||||
opts = super().parse_hookspec_opts(module_or_class, name)
|
||||
if opts is None:
|
||||
|
@ -492,9 +487,7 @@ class PytestPluginManager(PluginManager):
|
|||
)
|
||||
return opts
|
||||
|
||||
def register(
|
||||
self, plugin: _PluggyPlugin, name: Optional[str] = None
|
||||
) -> Optional[str]:
|
||||
def register(self, plugin: _PluggyPlugin, name: str | None = None) -> str | None:
|
||||
if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS:
|
||||
warnings.warn(
|
||||
PytestConfigWarning(
|
||||
|
@ -521,14 +514,14 @@ class PytestPluginManager(PluginManager):
|
|||
|
||||
def getplugin(self, name: str):
|
||||
# Support deprecated naming because plugins (xdist e.g.) use it.
|
||||
plugin: Optional[_PluggyPlugin] = self.get_plugin(name)
|
||||
plugin: _PluggyPlugin | None = self.get_plugin(name)
|
||||
return plugin
|
||||
|
||||
def hasplugin(self, name: str) -> bool:
|
||||
"""Return whether a plugin with the given name is registered."""
|
||||
return bool(self.get_plugin(name))
|
||||
|
||||
def pytest_configure(self, config: "Config") -> None:
|
||||
def pytest_configure(self, config: Config) -> None:
|
||||
""":meta private:"""
|
||||
# XXX now that the pluginmanager exposes hookimpl(tryfirst...)
|
||||
# we should remove tryfirst/trylast as markers.
|
||||
|
@ -551,13 +544,13 @@ class PytestPluginManager(PluginManager):
|
|||
#
|
||||
def _set_initial_conftests(
|
||||
self,
|
||||
args: Sequence[Union[str, Path]],
|
||||
args: Sequence[str | pathlib.Path],
|
||||
pyargs: bool,
|
||||
noconftest: bool,
|
||||
rootpath: Path,
|
||||
confcutdir: Optional[Path],
|
||||
invocation_dir: Path,
|
||||
importmode: Union[ImportMode, str],
|
||||
rootpath: pathlib.Path,
|
||||
confcutdir: pathlib.Path | None,
|
||||
invocation_dir: pathlib.Path,
|
||||
importmode: ImportMode | str,
|
||||
*,
|
||||
consider_namespace_packages: bool,
|
||||
) -> None:
|
||||
|
@ -600,7 +593,7 @@ class PytestPluginManager(PluginManager):
|
|||
consider_namespace_packages=consider_namespace_packages,
|
||||
)
|
||||
|
||||
def _is_in_confcutdir(self, path: Path) -> bool:
|
||||
def _is_in_confcutdir(self, path: pathlib.Path) -> bool:
|
||||
"""Whether to consider the given path to load conftests from."""
|
||||
if self._confcutdir is None:
|
||||
return True
|
||||
|
@ -617,9 +610,9 @@ class PytestPluginManager(PluginManager):
|
|||
|
||||
def _try_load_conftest(
|
||||
self,
|
||||
anchor: Path,
|
||||
importmode: Union[str, ImportMode],
|
||||
rootpath: Path,
|
||||
anchor: pathlib.Path,
|
||||
importmode: str | ImportMode,
|
||||
rootpath: pathlib.Path,
|
||||
*,
|
||||
consider_namespace_packages: bool,
|
||||
) -> None:
|
||||
|
@ -642,9 +635,9 @@ class PytestPluginManager(PluginManager):
|
|||
|
||||
def _loadconftestmodules(
|
||||
self,
|
||||
path: Path,
|
||||
importmode: Union[str, ImportMode],
|
||||
rootpath: Path,
|
||||
path: pathlib.Path,
|
||||
importmode: str | ImportMode,
|
||||
rootpath: pathlib.Path,
|
||||
*,
|
||||
consider_namespace_packages: bool,
|
||||
) -> None:
|
||||
|
@ -672,15 +665,15 @@ class PytestPluginManager(PluginManager):
|
|||
clist.append(mod)
|
||||
self._dirpath2confmods[directory] = clist
|
||||
|
||||
def _getconftestmodules(self, path: Path) -> Sequence[types.ModuleType]:
|
||||
def _getconftestmodules(self, path: pathlib.Path) -> Sequence[types.ModuleType]:
|
||||
directory = self._get_directory(path)
|
||||
return self._dirpath2confmods.get(directory, ())
|
||||
|
||||
def _rget_with_confmod(
|
||||
self,
|
||||
name: str,
|
||||
path: Path,
|
||||
) -> Tuple[types.ModuleType, Any]:
|
||||
path: pathlib.Path,
|
||||
) -> tuple[types.ModuleType, Any]:
|
||||
modules = self._getconftestmodules(path)
|
||||
for mod in reversed(modules):
|
||||
try:
|
||||
|
@ -691,9 +684,9 @@ class PytestPluginManager(PluginManager):
|
|||
|
||||
def _importconftest(
|
||||
self,
|
||||
conftestpath: Path,
|
||||
importmode: Union[str, ImportMode],
|
||||
rootpath: Path,
|
||||
conftestpath: pathlib.Path,
|
||||
importmode: str | ImportMode,
|
||||
rootpath: pathlib.Path,
|
||||
*,
|
||||
consider_namespace_packages: bool,
|
||||
) -> types.ModuleType:
|
||||
|
@ -745,7 +738,7 @@ class PytestPluginManager(PluginManager):
|
|||
def _check_non_top_pytest_plugins(
|
||||
self,
|
||||
mod: types.ModuleType,
|
||||
conftestpath: Path,
|
||||
conftestpath: pathlib.Path,
|
||||
) -> None:
|
||||
if (
|
||||
hasattr(mod, "pytest_plugins")
|
||||
|
@ -831,7 +824,7 @@ class PytestPluginManager(PluginManager):
|
|||
self._import_plugin_specs(getattr(mod, "pytest_plugins", []))
|
||||
|
||||
def _import_plugin_specs(
|
||||
self, spec: Union[None, types.ModuleType, str, Sequence[str]]
|
||||
self, spec: None | types.ModuleType | str | Sequence[str]
|
||||
) -> None:
|
||||
plugins = _get_plugin_specs_as_list(spec)
|
||||
for import_spec in plugins:
|
||||
|
@ -876,8 +869,8 @@ class PytestPluginManager(PluginManager):
|
|||
|
||||
|
||||
def _get_plugin_specs_as_list(
|
||||
specs: Union[None, types.ModuleType, str, Sequence[str]],
|
||||
) -> List[str]:
|
||||
specs: None | types.ModuleType | str | Sequence[str],
|
||||
) -> list[str]:
|
||||
"""Parse a plugins specification into a list of plugin names."""
|
||||
# None means empty.
|
||||
if specs is None:
|
||||
|
@ -999,24 +992,27 @@ class Config:
|
|||
Plugins accessing ``InvocationParams`` must be aware of that.
|
||||
"""
|
||||
|
||||
args: Tuple[str, ...]
|
||||
args: tuple[str, ...]
|
||||
"""The command-line arguments as passed to :func:`pytest.main`."""
|
||||
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]]
|
||||
plugins: Sequence[str | _PluggyPlugin] | None
|
||||
"""Extra plugins, might be `None`."""
|
||||
dir: Path
|
||||
"""The directory from which :func:`pytest.main` was invoked."""
|
||||
dir: pathlib.Path
|
||||
"""The directory from which :func:`pytest.main` was invoked. :type: pathlib.Path"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
args: Iterable[str],
|
||||
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]],
|
||||
dir: Path,
|
||||
plugins: Sequence[str | _PluggyPlugin] | None,
|
||||
dir: pathlib.Path,
|
||||
) -> None:
|
||||
object.__setattr__(self, "args", tuple(args))
|
||||
object.__setattr__(self, "plugins", plugins)
|
||||
object.__setattr__(self, "dir", dir)
|
||||
|
||||
# Set by cacheprovider plugin.
|
||||
cache: Cache
|
||||
|
||||
class ArgsSource(enum.Enum):
|
||||
"""Indicates the source of the test arguments.
|
||||
|
||||
|
@ -1035,14 +1031,14 @@ class Config:
|
|||
self,
|
||||
pluginmanager: PytestPluginManager,
|
||||
*,
|
||||
invocation_params: Optional[InvocationParams] = None,
|
||||
invocation_params: InvocationParams | None = None,
|
||||
) -> None:
|
||||
from .argparsing import FILE_OR_DIR
|
||||
from .argparsing import Parser
|
||||
|
||||
if invocation_params is None:
|
||||
invocation_params = self.InvocationParams(
|
||||
args=(), plugins=None, dir=Path.cwd()
|
||||
args=(), plugins=None, dir=pathlib.Path.cwd()
|
||||
)
|
||||
|
||||
self.option = argparse.Namespace()
|
||||
|
@ -1080,25 +1076,20 @@ class Config:
|
|||
|
||||
self.trace = self.pluginmanager.trace.root.get("config")
|
||||
self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook) # type: ignore[assignment]
|
||||
self._inicache: Dict[str, Any] = {}
|
||||
self._inicache: dict[str, Any] = {}
|
||||
self._override_ini: Sequence[str] = ()
|
||||
self._opt2dest: Dict[str, str] = {}
|
||||
self._cleanup: List[Callable[[], None]] = []
|
||||
self._opt2dest: dict[str, str] = {}
|
||||
self._cleanup: list[Callable[[], None]] = []
|
||||
self.pluginmanager.register(self, "pytestconfig")
|
||||
self._configured = False
|
||||
self.hook.pytest_addoption.call_historic(
|
||||
kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager)
|
||||
)
|
||||
self.args_source = Config.ArgsSource.ARGS
|
||||
self.args: List[str] = []
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _pytest.cacheprovider import Cache
|
||||
|
||||
self.cache: Optional[Cache] = None
|
||||
self.args: list[str] = []
|
||||
|
||||
@property
|
||||
def rootpath(self) -> Path:
|
||||
def rootpath(self) -> pathlib.Path:
|
||||
"""The path to the :ref:`rootdir <rootdir>`.
|
||||
|
||||
:type: pathlib.Path
|
||||
|
@ -1108,11 +1099,9 @@ class Config:
|
|||
return self._rootpath
|
||||
|
||||
@property
|
||||
def inipath(self) -> Optional[Path]:
|
||||
def inipath(self) -> pathlib.Path | None:
|
||||
"""The path to the :ref:`configfile <configfiles>`.
|
||||
|
||||
:type: Optional[pathlib.Path]
|
||||
|
||||
.. versionadded:: 6.1
|
||||
"""
|
||||
return self._inipath
|
||||
|
@ -1139,15 +1128,15 @@ class Config:
|
|||
fin()
|
||||
|
||||
def get_terminal_writer(self) -> TerminalWriter:
|
||||
terminalreporter: Optional[TerminalReporter] = self.pluginmanager.get_plugin(
|
||||
terminalreporter: TerminalReporter | None = self.pluginmanager.get_plugin(
|
||||
"terminalreporter"
|
||||
)
|
||||
assert terminalreporter is not None
|
||||
return terminalreporter._tw
|
||||
|
||||
def pytest_cmdline_parse(
|
||||
self, pluginmanager: PytestPluginManager, args: List[str]
|
||||
) -> "Config":
|
||||
self, pluginmanager: PytestPluginManager, args: list[str]
|
||||
) -> Config:
|
||||
try:
|
||||
self.parse(args)
|
||||
except UsageError:
|
||||
|
@ -1173,7 +1162,7 @@ class Config:
|
|||
def notify_exception(
|
||||
self,
|
||||
excinfo: ExceptionInfo[BaseException],
|
||||
option: Optional[argparse.Namespace] = None,
|
||||
option: argparse.Namespace | None = None,
|
||||
) -> None:
|
||||
if option and getattr(option, "fulltrace", False):
|
||||
style: _TracebackStyle = "long"
|
||||
|
@ -1196,7 +1185,7 @@ class Config:
|
|||
return nodeid
|
||||
|
||||
@classmethod
|
||||
def fromdictargs(cls, option_dict, args) -> "Config":
|
||||
def fromdictargs(cls, option_dict, args) -> Config:
|
||||
"""Constructor usable for subprocesses."""
|
||||
config = get_config(args)
|
||||
config.option.__dict__.update(option_dict)
|
||||
|
@ -1205,7 +1194,7 @@ class Config:
|
|||
config.pluginmanager.consider_pluginarg(x)
|
||||
return config
|
||||
|
||||
def _processopt(self, opt: "Argument") -> None:
|
||||
def _processopt(self, opt: Argument) -> None:
|
||||
for name in opt._short_opts + opt._long_opts:
|
||||
self._opt2dest[name] = opt.dest
|
||||
|
||||
|
@ -1214,7 +1203,7 @@ class Config:
|
|||
setattr(self.option, opt.dest, opt.default)
|
||||
|
||||
@hookimpl(trylast=True)
|
||||
def pytest_load_initial_conftests(self, early_config: "Config") -> None:
|
||||
def pytest_load_initial_conftests(self, early_config: Config) -> None:
|
||||
# We haven't fully parsed the command line arguments yet, so
|
||||
# early_config.args it not set yet. But we need it for
|
||||
# discovering the initial conftests. So "pre-run" the logic here.
|
||||
|
@ -1305,7 +1294,7 @@ class Config:
|
|||
for name in _iter_rewritable_modules(package_files):
|
||||
hook.mark_rewrite(name)
|
||||
|
||||
def _validate_args(self, args: List[str], via: str) -> List[str]:
|
||||
def _validate_args(self, args: list[str], via: str) -> list[str]:
|
||||
"""Validate known args."""
|
||||
self._parser._config_source_hint = via # type: ignore
|
||||
try:
|
||||
|
@ -1320,13 +1309,13 @@ class Config:
|
|||
def _decide_args(
|
||||
self,
|
||||
*,
|
||||
args: List[str],
|
||||
args: list[str],
|
||||
pyargs: bool,
|
||||
testpaths: List[str],
|
||||
invocation_dir: Path,
|
||||
rootpath: Path,
|
||||
testpaths: list[str],
|
||||
invocation_dir: pathlib.Path,
|
||||
rootpath: pathlib.Path,
|
||||
warn: bool,
|
||||
) -> Tuple[List[str], ArgsSource]:
|
||||
) -> tuple[list[str], ArgsSource]:
|
||||
"""Decide the args (initial paths/nodeids) to use given the relevant inputs.
|
||||
|
||||
:param warn: Whether can issue warnings.
|
||||
|
@ -1362,7 +1351,7 @@ class Config:
|
|||
result = [str(invocation_dir)]
|
||||
return result, source
|
||||
|
||||
def _preparse(self, args: List[str], addopts: bool = True) -> None:
|
||||
def _preparse(self, args: list[str], addopts: bool = True) -> None:
|
||||
if addopts:
|
||||
env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
|
||||
if len(env_addopts):
|
||||
|
@ -1486,11 +1475,11 @@ class Config:
|
|||
|
||||
self.issue_config_time_warning(PytestConfigWarning(message), stacklevel=3)
|
||||
|
||||
def _get_unknown_ini_keys(self) -> List[str]:
|
||||
def _get_unknown_ini_keys(self) -> list[str]:
|
||||
parser_inicfg = self._parser._inidict
|
||||
return [name for name in self.inicfg if name not in parser_inicfg]
|
||||
|
||||
def parse(self, args: List[str], addopts: bool = True) -> None:
|
||||
def parse(self, args: list[str], addopts: bool = True) -> None:
|
||||
# Parse given cmdline arguments into this config object.
|
||||
assert (
|
||||
self.args == []
|
||||
|
@ -1594,7 +1583,7 @@ class Config:
|
|||
|
||||
# Meant for easy monkeypatching by legacypath plugin.
|
||||
# Can be inlined back (with no cover removed) once legacypath is gone.
|
||||
def _getini_unknown_type(self, name: str, type: str, value: Union[str, List[str]]):
|
||||
def _getini_unknown_type(self, name: str, type: str, value: str | list[str]):
|
||||
msg = f"unknown configuration type: {type}"
|
||||
raise ValueError(msg, value) # pragma: no cover
|
||||
|
||||
|
@ -1650,24 +1639,26 @@ class Config:
|
|||
else:
|
||||
return self._getini_unknown_type(name, type, value)
|
||||
|
||||
def _getconftest_pathlist(self, name: str, path: Path) -> Optional[List[Path]]:
|
||||
def _getconftest_pathlist(
|
||||
self, name: str, path: pathlib.Path
|
||||
) -> list[pathlib.Path] | None:
|
||||
try:
|
||||
mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
|
||||
except KeyError:
|
||||
return None
|
||||
assert mod.__file__ is not None
|
||||
modpath = Path(mod.__file__).parent
|
||||
values: List[Path] = []
|
||||
modpath = pathlib.Path(mod.__file__).parent
|
||||
values: list[pathlib.Path] = []
|
||||
for relroot in relroots:
|
||||
if isinstance(relroot, os.PathLike):
|
||||
relroot = Path(relroot)
|
||||
relroot = pathlib.Path(relroot)
|
||||
else:
|
||||
relroot = relroot.replace("/", os.sep)
|
||||
relroot = absolutepath(modpath / relroot)
|
||||
values.append(relroot)
|
||||
return values
|
||||
|
||||
def _get_override_ini_value(self, name: str) -> Optional[str]:
|
||||
def _get_override_ini_value(self, name: str) -> str | None:
|
||||
value = None
|
||||
# override_ini is a list of "ini=value" options.
|
||||
# Always use the last item if multiple values are set for same ini-name,
|
||||
|
@ -1722,7 +1713,7 @@ class Config:
|
|||
VERBOSITY_TEST_CASES: Final = "test_cases"
|
||||
_VERBOSITY_INI_DEFAULT: Final = "auto"
|
||||
|
||||
def get_verbosity(self, verbosity_type: Optional[str] = None) -> int:
|
||||
def get_verbosity(self, verbosity_type: str | None = None) -> int:
|
||||
r"""Retrieve the verbosity level for a fine-grained verbosity type.
|
||||
|
||||
:param verbosity_type: Verbosity type to get level for. If a level is
|
||||
|
@ -1772,7 +1763,7 @@ class Config:
|
|||
return f"verbosity_{verbosity_type}"
|
||||
|
||||
@staticmethod
|
||||
def _add_verbosity_ini(parser: "Parser", verbosity_type: str, help: str) -> None:
|
||||
def _add_verbosity_ini(parser: Parser, verbosity_type: str, help: str) -> None:
|
||||
"""Add a output verbosity configuration option for the given output type.
|
||||
|
||||
:param parser: Parser for command line arguments and ini-file values.
|
||||
|
@ -1828,7 +1819,7 @@ def _assertion_supported() -> bool:
|
|||
|
||||
|
||||
def create_terminal_writer(
|
||||
config: Config, file: Optional[TextIO] = None
|
||||
config: Config, file: TextIO | None = None
|
||||
) -> TerminalWriter:
|
||||
"""Create a TerminalWriter instance configured according to the options
|
||||
in the config object.
|
||||
|
@ -1872,7 +1863,7 @@ def _strtobool(val: str) -> bool:
|
|||
@lru_cache(maxsize=50)
|
||||
def parse_warning_filter(
|
||||
arg: str, *, escape: bool
|
||||
) -> Tuple["warnings._ActionKind", str, Type[Warning], str, int]:
|
||||
) -> tuple[warnings._ActionKind, str, type[Warning], str, int]:
|
||||
"""Parse a warnings filter string.
|
||||
|
||||
This is copied from warnings._setoption with the following changes:
|
||||
|
@ -1914,11 +1905,11 @@ def parse_warning_filter(
|
|||
parts.append("")
|
||||
action_, message, category_, module, lineno_ = (s.strip() for s in parts)
|
||||
try:
|
||||
action: "warnings._ActionKind" = warnings._getaction(action_) # type: ignore[attr-defined]
|
||||
action: warnings._ActionKind = warnings._getaction(action_) # type: ignore[attr-defined]
|
||||
except warnings._OptionError as e:
|
||||
raise UsageError(error_template.format(error=str(e))) from None
|
||||
try:
|
||||
category: Type[Warning] = _resolve_warning_category(category_)
|
||||
category: type[Warning] = _resolve_warning_category(category_)
|
||||
except Exception:
|
||||
exc_info = ExceptionInfo.from_current()
|
||||
exception_text = exc_info.getrepr(style="native")
|
||||
|
@ -1941,7 +1932,7 @@ def parse_warning_filter(
|
|||
return action, message, category, module, lineno
|
||||
|
||||
|
||||
def _resolve_warning_category(category: str) -> Type[Warning]:
|
||||
def _resolve_warning_category(category: str) -> type[Warning]:
|
||||
"""
|
||||
Copied from warnings._getcategory, but changed so it lets exceptions (specially ImportErrors)
|
||||
propagate so we can get access to their tracebacks (#9218).
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# mypy: allow-untyped-defs
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from gettext import gettext
|
||||
import os
|
||||
|
@ -6,16 +8,12 @@ import sys
|
|||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
from typing import Dict
|
||||
from typing import final
|
||||
from typing import List
|
||||
from typing import Literal
|
||||
from typing import Mapping
|
||||
from typing import NoReturn
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
import _pytest._io
|
||||
from _pytest.config.exceptions import UsageError
|
||||
|
@ -41,32 +39,32 @@ class Parser:
|
|||
there's an error processing the command line arguments.
|
||||
"""
|
||||
|
||||
prog: Optional[str] = None
|
||||
prog: str | None = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
usage: Optional[str] = None,
|
||||
processopt: Optional[Callable[["Argument"], None]] = None,
|
||||
usage: str | None = None,
|
||||
processopt: Callable[[Argument], None] | None = None,
|
||||
*,
|
||||
_ispytest: bool = False,
|
||||
) -> None:
|
||||
check_ispytest(_ispytest)
|
||||
self._anonymous = OptionGroup("Custom options", parser=self, _ispytest=True)
|
||||
self._groups: List[OptionGroup] = []
|
||||
self._groups: list[OptionGroup] = []
|
||||
self._processopt = processopt
|
||||
self._usage = usage
|
||||
self._inidict: Dict[str, Tuple[str, Optional[str], Any]] = {}
|
||||
self._ininames: List[str] = []
|
||||
self.extra_info: Dict[str, Any] = {}
|
||||
self._inidict: dict[str, tuple[str, str | None, Any]] = {}
|
||||
self._ininames: list[str] = []
|
||||
self.extra_info: dict[str, Any] = {}
|
||||
|
||||
def processoption(self, option: "Argument") -> None:
|
||||
def processoption(self, option: Argument) -> None:
|
||||
if self._processopt:
|
||||
if option.dest:
|
||||
self._processopt(option)
|
||||
|
||||
def getgroup(
|
||||
self, name: str, description: str = "", after: Optional[str] = None
|
||||
) -> "OptionGroup":
|
||||
self, name: str, description: str = "", after: str | None = None
|
||||
) -> OptionGroup:
|
||||
"""Get (or create) a named option Group.
|
||||
|
||||
:param name: Name of the option group.
|
||||
|
@ -108,8 +106,8 @@ class Parser:
|
|||
|
||||
def parse(
|
||||
self,
|
||||
args: Sequence[Union[str, "os.PathLike[str]"]],
|
||||
namespace: Optional[argparse.Namespace] = None,
|
||||
args: Sequence[str | os.PathLike[str]],
|
||||
namespace: argparse.Namespace | None = None,
|
||||
) -> argparse.Namespace:
|
||||
from _pytest._argcomplete import try_argcomplete
|
||||
|
||||
|
@ -118,7 +116,7 @@ class Parser:
|
|||
strargs = [os.fspath(x) for x in args]
|
||||
return self.optparser.parse_args(strargs, namespace=namespace)
|
||||
|
||||
def _getparser(self) -> "MyOptionParser":
|
||||
def _getparser(self) -> MyOptionParser:
|
||||
from _pytest._argcomplete import filescompleter
|
||||
|
||||
optparser = MyOptionParser(self, self.extra_info, prog=self.prog)
|
||||
|
@ -139,10 +137,10 @@ class Parser:
|
|||
|
||||
def parse_setoption(
|
||||
self,
|
||||
args: Sequence[Union[str, "os.PathLike[str]"]],
|
||||
args: Sequence[str | os.PathLike[str]],
|
||||
option: argparse.Namespace,
|
||||
namespace: Optional[argparse.Namespace] = None,
|
||||
) -> List[str]:
|
||||
namespace: argparse.Namespace | None = None,
|
||||
) -> list[str]:
|
||||
parsedoption = self.parse(args, namespace=namespace)
|
||||
for name, value in parsedoption.__dict__.items():
|
||||
setattr(option, name, value)
|
||||
|
@ -150,8 +148,8 @@ class Parser:
|
|||
|
||||
def parse_known_args(
|
||||
self,
|
||||
args: Sequence[Union[str, "os.PathLike[str]"]],
|
||||
namespace: Optional[argparse.Namespace] = None,
|
||||
args: Sequence[str | os.PathLike[str]],
|
||||
namespace: argparse.Namespace | None = None,
|
||||
) -> argparse.Namespace:
|
||||
"""Parse the known arguments at this point.
|
||||
|
||||
|
@ -161,9 +159,9 @@ class Parser:
|
|||
|
||||
def parse_known_and_unknown_args(
|
||||
self,
|
||||
args: Sequence[Union[str, "os.PathLike[str]"]],
|
||||
namespace: Optional[argparse.Namespace] = None,
|
||||
) -> Tuple[argparse.Namespace, List[str]]:
|
||||
args: Sequence[str | os.PathLike[str]],
|
||||
namespace: argparse.Namespace | None = None,
|
||||
) -> tuple[argparse.Namespace, list[str]]:
|
||||
"""Parse the known arguments at this point, and also return the
|
||||
remaining unknown arguments.
|
||||
|
||||
|
@ -179,9 +177,8 @@ class Parser:
|
|||
self,
|
||||
name: str,
|
||||
help: str,
|
||||
type: Optional[
|
||||
Literal["string", "paths", "pathlist", "args", "linelist", "bool"]
|
||||
] = None,
|
||||
type: Literal["string", "paths", "pathlist", "args", "linelist", "bool"]
|
||||
| None = None,
|
||||
default: Any = NOT_SET,
|
||||
) -> None:
|
||||
"""Register an ini-file option.
|
||||
|
@ -224,7 +221,7 @@ class Parser:
|
|||
|
||||
|
||||
def get_ini_default_for_type(
|
||||
type: Optional[Literal["string", "paths", "pathlist", "args", "linelist", "bool"]],
|
||||
type: Literal["string", "paths", "pathlist", "args", "linelist", "bool"] | None,
|
||||
) -> Any:
|
||||
"""
|
||||
Used by addini to get the default value for a given ini-option type, when
|
||||
|
@ -244,7 +241,7 @@ class ArgumentError(Exception):
|
|||
"""Raised if an Argument instance is created with invalid or
|
||||
inconsistent arguments."""
|
||||
|
||||
def __init__(self, msg: str, option: Union["Argument", str]) -> None:
|
||||
def __init__(self, msg: str, option: Argument | str) -> None:
|
||||
self.msg = msg
|
||||
self.option_id = str(option)
|
||||
|
||||
|
@ -267,8 +264,8 @@ class Argument:
|
|||
def __init__(self, *names: str, **attrs: Any) -> None:
|
||||
"""Store params in private vars for use in add_argument."""
|
||||
self._attrs = attrs
|
||||
self._short_opts: List[str] = []
|
||||
self._long_opts: List[str] = []
|
||||
self._short_opts: list[str] = []
|
||||
self._long_opts: list[str] = []
|
||||
try:
|
||||
self.type = attrs["type"]
|
||||
except KeyError:
|
||||
|
@ -279,7 +276,7 @@ class Argument:
|
|||
except KeyError:
|
||||
pass
|
||||
self._set_opt_strings(names)
|
||||
dest: Optional[str] = attrs.get("dest")
|
||||
dest: str | None = attrs.get("dest")
|
||||
if dest:
|
||||
self.dest = dest
|
||||
elif self._long_opts:
|
||||
|
@ -291,7 +288,7 @@ class Argument:
|
|||
self.dest = "???" # Needed for the error repr.
|
||||
raise ArgumentError("need a long or short option", self) from e
|
||||
|
||||
def names(self) -> List[str]:
|
||||
def names(self) -> list[str]:
|
||||
return self._short_opts + self._long_opts
|
||||
|
||||
def attrs(self) -> Mapping[str, Any]:
|
||||
|
@ -335,7 +332,7 @@ class Argument:
|
|||
self._long_opts.append(opt)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
args: List[str] = []
|
||||
args: list[str] = []
|
||||
if self._short_opts:
|
||||
args += ["_short_opts: " + repr(self._short_opts)]
|
||||
if self._long_opts:
|
||||
|
@ -355,14 +352,14 @@ class OptionGroup:
|
|||
self,
|
||||
name: str,
|
||||
description: str = "",
|
||||
parser: Optional[Parser] = None,
|
||||
parser: Parser | None = None,
|
||||
*,
|
||||
_ispytest: bool = False,
|
||||
) -> None:
|
||||
check_ispytest(_ispytest)
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.options: List[Argument] = []
|
||||
self.options: list[Argument] = []
|
||||
self.parser = parser
|
||||
|
||||
def addoption(self, *opts: str, **attrs: Any) -> None:
|
||||
|
@ -391,7 +388,7 @@ class OptionGroup:
|
|||
option = Argument(*opts, **attrs)
|
||||
self._addoption_instance(option, shortupper=True)
|
||||
|
||||
def _addoption_instance(self, option: "Argument", shortupper: bool = False) -> None:
|
||||
def _addoption_instance(self, option: Argument, shortupper: bool = False) -> None:
|
||||
if not shortupper:
|
||||
for opt in option._short_opts:
|
||||
if opt[0] == "-" and opt[1].islower():
|
||||
|
@ -405,8 +402,8 @@ class MyOptionParser(argparse.ArgumentParser):
|
|||
def __init__(
|
||||
self,
|
||||
parser: Parser,
|
||||
extra_info: Optional[Dict[str, Any]] = None,
|
||||
prog: Optional[str] = None,
|
||||
extra_info: dict[str, Any] | None = None,
|
||||
prog: str | None = None,
|
||||
) -> None:
|
||||
self._parser = parser
|
||||
super().__init__(
|
||||
|
@ -433,8 +430,8 @@ class MyOptionParser(argparse.ArgumentParser):
|
|||
# Type ignored because typeshed has a very complex type in the superclass.
|
||||
def parse_args( # type: ignore
|
||||
self,
|
||||
args: Optional[Sequence[str]] = None,
|
||||
namespace: Optional[argparse.Namespace] = None,
|
||||
args: Sequence[str] | None = None,
|
||||
namespace: argparse.Namespace | None = None,
|
||||
) -> argparse.Namespace:
|
||||
"""Allow splitting of positional arguments."""
|
||||
parsed, unrecognized = self.parse_known_args(args, namespace)
|
||||
|
@ -453,7 +450,7 @@ class MyOptionParser(argparse.ArgumentParser):
|
|||
# disable long --argument abbreviations without breaking short flags.
|
||||
def _parse_optional(
|
||||
self, arg_string: str
|
||||
) -> Optional[Tuple[Optional[argparse.Action], str, Optional[str]]]:
|
||||
) -> tuple[argparse.Action | None, str, str | None] | None:
|
||||
if not arg_string:
|
||||
return None
|
||||
if arg_string[0] not in self.prefix_chars:
|
||||
|
@ -505,7 +502,7 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
|
|||
orgstr = super()._format_action_invocation(action)
|
||||
if orgstr and orgstr[0] != "-": # only optional arguments
|
||||
return orgstr
|
||||
res: Optional[str] = getattr(action, "_formatted_action_invocation", None)
|
||||
res: str | None = getattr(action, "_formatted_action_invocation", None)
|
||||
if res:
|
||||
return res
|
||||
options = orgstr.split(", ")
|
||||
|
@ -514,7 +511,7 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
|
|||
action._formatted_action_invocation = orgstr # type: ignore
|
||||
return orgstr
|
||||
return_list = []
|
||||
short_long: Dict[str, str] = {}
|
||||
short_long: dict[str, str] = {}
|
||||
for option in options:
|
||||
if len(option) == 2 or option[2] == " ":
|
||||
continue
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import final
|
||||
|
||||
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
import iniconfig
|
||||
|
||||
|
@ -32,7 +29,7 @@ def _parse_ini_config(path: Path) -> iniconfig.IniConfig:
|
|||
|
||||
def load_config_dict_from_file(
|
||||
filepath: Path,
|
||||
) -> Optional[Dict[str, Union[str, List[str]]]]:
|
||||
) -> dict[str, str | list[str]] | None:
|
||||
"""Load pytest configuration from the given file path, if supported.
|
||||
|
||||
Return None if the file does not contain valid pytest configuration.
|
||||
|
@ -77,7 +74,7 @@ def load_config_dict_from_file(
|
|||
# TOML supports richer data types than ini files (strings, arrays, floats, ints, etc),
|
||||
# however we need to convert all scalar values to str for compatibility with the rest
|
||||
# of the configuration system, which expects strings only.
|
||||
def make_scalar(v: object) -> Union[str, List[str]]:
|
||||
def make_scalar(v: object) -> str | list[str]:
|
||||
return v if isinstance(v, list) else str(v)
|
||||
|
||||
return {k: make_scalar(v) for k, v in result.items()}
|
||||
|
@ -88,7 +85,7 @@ def load_config_dict_from_file(
|
|||
def locate_config(
|
||||
invocation_dir: Path,
|
||||
args: Iterable[Path],
|
||||
) -> Tuple[Optional[Path], Optional[Path], Dict[str, Union[str, List[str]]]]:
|
||||
) -> tuple[Path | None, Path | None, dict[str, str | list[str]]]:
|
||||
"""Search in the list of arguments for a valid ini-file for pytest,
|
||||
and return a tuple of (rootdir, inifile, cfg-dict)."""
|
||||
config_names = [
|
||||
|
@ -101,7 +98,7 @@ def locate_config(
|
|||
args = [x for x in args if not str(x).startswith("-")]
|
||||
if not args:
|
||||
args = [invocation_dir]
|
||||
found_pyproject_toml: Optional[Path] = None
|
||||
found_pyproject_toml: Path | None = None
|
||||
for arg in args:
|
||||
argpath = absolutepath(arg)
|
||||
for base in (argpath, *argpath.parents):
|
||||
|
@ -122,7 +119,7 @@ def get_common_ancestor(
|
|||
invocation_dir: Path,
|
||||
paths: Iterable[Path],
|
||||
) -> Path:
|
||||
common_ancestor: Optional[Path] = None
|
||||
common_ancestor: Path | None = None
|
||||
for path in paths:
|
||||
if not path.exists():
|
||||
continue
|
||||
|
@ -144,7 +141,7 @@ def get_common_ancestor(
|
|||
return common_ancestor
|
||||
|
||||
|
||||
def get_dirs_from_args(args: Iterable[str]) -> List[Path]:
|
||||
def get_dirs_from_args(args: Iterable[str]) -> list[Path]:
|
||||
def is_option(x: str) -> bool:
|
||||
return x.startswith("-")
|
||||
|
||||
|
@ -171,11 +168,11 @@ CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supporte
|
|||
|
||||
def determine_setup(
|
||||
*,
|
||||
inifile: Optional[str],
|
||||
inifile: str | None,
|
||||
args: Sequence[str],
|
||||
rootdir_cmd_arg: Optional[str],
|
||||
rootdir_cmd_arg: str | None,
|
||||
invocation_dir: Path,
|
||||
) -> Tuple[Path, Optional[Path], Dict[str, Union[str, List[str]]]]:
|
||||
) -> tuple[Path, Path | None, dict[str, str | list[str]]]:
|
||||
"""Determine the rootdir, inifile and ini configuration values from the
|
||||
command line arguments.
|
||||
|
||||
|
@ -192,7 +189,7 @@ def determine_setup(
|
|||
dirs = get_dirs_from_args(args)
|
||||
if inifile:
|
||||
inipath_ = absolutepath(inifile)
|
||||
inipath: Optional[Path] = inipath_
|
||||
inipath: Path | None = inipath_
|
||||
inicfg = load_config_dict_from_file(inipath_) or {}
|
||||
if rootdir_cmd_arg is None:
|
||||
rootdir = inipath_.parent
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Interactive debugging with PDB, the Python Debugger."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import functools
|
||||
import sys
|
||||
|
@ -8,12 +10,7 @@ import types
|
|||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Generator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
import unittest
|
||||
|
||||
from _pytest import outcomes
|
||||
|
@ -33,7 +30,7 @@ if TYPE_CHECKING:
|
|||
from _pytest.runner import CallInfo
|
||||
|
||||
|
||||
def _validate_usepdb_cls(value: str) -> Tuple[str, str]:
|
||||
def _validate_usepdb_cls(value: str) -> tuple[str, str]:
|
||||
"""Validate syntax of --pdbcls option."""
|
||||
try:
|
||||
modname, classname = value.split(":")
|
||||
|
@ -98,22 +95,22 @@ def pytest_configure(config: Config) -> None:
|
|||
class pytestPDB:
|
||||
"""Pseudo PDB that defers to the real pdb."""
|
||||
|
||||
_pluginmanager: Optional[PytestPluginManager] = None
|
||||
_config: Optional[Config] = None
|
||||
_saved: List[
|
||||
Tuple[Callable[..., None], Optional[PytestPluginManager], Optional[Config]]
|
||||
_pluginmanager: PytestPluginManager | None = None
|
||||
_config: Config | None = None
|
||||
_saved: list[
|
||||
tuple[Callable[..., None], PytestPluginManager | None, Config | None]
|
||||
] = []
|
||||
_recursive_debug = 0
|
||||
_wrapped_pdb_cls: Optional[Tuple[Type[Any], Type[Any]]] = None
|
||||
_wrapped_pdb_cls: tuple[type[Any], type[Any]] | None = None
|
||||
|
||||
@classmethod
|
||||
def _is_capturing(cls, capman: Optional["CaptureManager"]) -> Union[str, bool]:
|
||||
def _is_capturing(cls, capman: CaptureManager | None) -> str | bool:
|
||||
if capman:
|
||||
return capman.is_capturing()
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def _import_pdb_cls(cls, capman: Optional["CaptureManager"]):
|
||||
def _import_pdb_cls(cls, capman: CaptureManager | None):
|
||||
if not cls._config:
|
||||
import pdb
|
||||
|
||||
|
@ -152,7 +149,7 @@ class pytestPDB:
|
|||
return wrapped_cls
|
||||
|
||||
@classmethod
|
||||
def _get_pdb_wrapper_class(cls, pdb_cls, capman: Optional["CaptureManager"]):
|
||||
def _get_pdb_wrapper_class(cls, pdb_cls, capman: CaptureManager | None):
|
||||
import _pytest.config
|
||||
|
||||
class PytestPdbWrapper(pdb_cls):
|
||||
|
@ -242,7 +239,7 @@ class pytestPDB:
|
|||
import _pytest.config
|
||||
|
||||
if cls._pluginmanager is None:
|
||||
capman: Optional[CaptureManager] = None
|
||||
capman: CaptureManager | None = None
|
||||
else:
|
||||
capman = cls._pluginmanager.getplugin("capturemanager")
|
||||
if capman:
|
||||
|
@ -285,7 +282,7 @@ class pytestPDB:
|
|||
|
||||
class PdbInvoke:
|
||||
def pytest_exception_interact(
|
||||
self, node: Node, call: "CallInfo[Any]", report: BaseReport
|
||||
self, node: Node, call: CallInfo[Any], report: BaseReport
|
||||
) -> None:
|
||||
capman = node.config.pluginmanager.getplugin("capturemanager")
|
||||
if capman:
|
||||
|
|
|
@ -9,6 +9,8 @@ All constants defined in this module should be either instances of
|
|||
in case of warnings which need to format their messages.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from warnings import warn
|
||||
|
||||
from _pytest.warning_types import PytestDeprecationWarning
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Discover and run doctests in modules and test files."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import bdb
|
||||
from contextlib import contextmanager
|
||||
import functools
|
||||
|
@ -13,17 +15,11 @@ import traceback
|
|||
import types
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import Generator
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Pattern
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
from _pytest import outcomes
|
||||
|
@ -67,7 +63,7 @@ DOCTEST_REPORT_CHOICES = (
|
|||
# Lazy definition of runner class
|
||||
RUNNER_CLASS = None
|
||||
# Lazy definition of output checker class
|
||||
CHECKER_CLASS: Optional[Type["doctest.OutputChecker"]] = None
|
||||
CHECKER_CLASS: type[doctest.OutputChecker] | None = None
|
||||
|
||||
|
||||
def pytest_addoption(parser: Parser) -> None:
|
||||
|
@ -129,7 +125,7 @@ def pytest_unconfigure() -> None:
|
|||
def pytest_collect_file(
|
||||
file_path: Path,
|
||||
parent: Collector,
|
||||
) -> Optional[Union["DoctestModule", "DoctestTextfile"]]:
|
||||
) -> DoctestModule | DoctestTextfile | None:
|
||||
config = parent.config
|
||||
if file_path.suffix == ".py":
|
||||
if config.option.doctestmodules and not any(
|
||||
|
@ -161,7 +157,7 @@ def _is_main_py(path: Path) -> bool:
|
|||
|
||||
class ReprFailDoctest(TerminalRepr):
|
||||
def __init__(
|
||||
self, reprlocation_lines: Sequence[Tuple[ReprFileLocation, Sequence[str]]]
|
||||
self, reprlocation_lines: Sequence[tuple[ReprFileLocation, Sequence[str]]]
|
||||
) -> None:
|
||||
self.reprlocation_lines = reprlocation_lines
|
||||
|
||||
|
@ -173,12 +169,12 @@ class ReprFailDoctest(TerminalRepr):
|
|||
|
||||
|
||||
class MultipleDoctestFailures(Exception):
|
||||
def __init__(self, failures: Sequence["doctest.DocTestFailure"]) -> None:
|
||||
def __init__(self, failures: Sequence[doctest.DocTestFailure]) -> None:
|
||||
super().__init__()
|
||||
self.failures = failures
|
||||
|
||||
|
||||
def _init_runner_class() -> Type["doctest.DocTestRunner"]:
|
||||
def _init_runner_class() -> type[doctest.DocTestRunner]:
|
||||
import doctest
|
||||
|
||||
class PytestDoctestRunner(doctest.DebugRunner):
|
||||
|
@ -190,8 +186,8 @@ def _init_runner_class() -> Type["doctest.DocTestRunner"]:
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
checker: Optional["doctest.OutputChecker"] = None,
|
||||
verbose: Optional[bool] = None,
|
||||
checker: doctest.OutputChecker | None = None,
|
||||
verbose: bool | None = None,
|
||||
optionflags: int = 0,
|
||||
continue_on_failure: bool = True,
|
||||
) -> None:
|
||||
|
@ -201,8 +197,8 @@ def _init_runner_class() -> Type["doctest.DocTestRunner"]:
|
|||
def report_failure(
|
||||
self,
|
||||
out,
|
||||
test: "doctest.DocTest",
|
||||
example: "doctest.Example",
|
||||
test: doctest.DocTest,
|
||||
example: doctest.Example,
|
||||
got: str,
|
||||
) -> None:
|
||||
failure = doctest.DocTestFailure(test, example, got)
|
||||
|
@ -214,9 +210,9 @@ def _init_runner_class() -> Type["doctest.DocTestRunner"]:
|
|||
def report_unexpected_exception(
|
||||
self,
|
||||
out,
|
||||
test: "doctest.DocTest",
|
||||
example: "doctest.Example",
|
||||
exc_info: Tuple[Type[BaseException], BaseException, types.TracebackType],
|
||||
test: doctest.DocTest,
|
||||
example: doctest.Example,
|
||||
exc_info: tuple[type[BaseException], BaseException, types.TracebackType],
|
||||
) -> None:
|
||||
if isinstance(exc_info[1], OutcomeException):
|
||||
raise exc_info[1]
|
||||
|
@ -232,11 +228,11 @@ def _init_runner_class() -> Type["doctest.DocTestRunner"]:
|
|||
|
||||
|
||||
def _get_runner(
|
||||
checker: Optional["doctest.OutputChecker"] = None,
|
||||
verbose: Optional[bool] = None,
|
||||
checker: doctest.OutputChecker | None = None,
|
||||
verbose: bool | None = None,
|
||||
optionflags: int = 0,
|
||||
continue_on_failure: bool = True,
|
||||
) -> "doctest.DocTestRunner":
|
||||
) -> doctest.DocTestRunner:
|
||||
# We need this in order to do a lazy import on doctest
|
||||
global RUNNER_CLASS
|
||||
if RUNNER_CLASS is None:
|
||||
|
@ -255,9 +251,9 @@ class DoctestItem(Item):
|
|||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
parent: "Union[DoctestTextfile, DoctestModule]",
|
||||
runner: "doctest.DocTestRunner",
|
||||
dtest: "doctest.DocTest",
|
||||
parent: DoctestTextfile | DoctestModule,
|
||||
runner: doctest.DocTestRunner,
|
||||
dtest: doctest.DocTest,
|
||||
) -> None:
|
||||
super().__init__(name, parent)
|
||||
self.runner = runner
|
||||
|
@ -274,18 +270,18 @@ class DoctestItem(Item):
|
|||
@classmethod
|
||||
def from_parent( # type: ignore[override]
|
||||
cls,
|
||||
parent: "Union[DoctestTextfile, DoctestModule]",
|
||||
parent: DoctestTextfile | DoctestModule,
|
||||
*,
|
||||
name: str,
|
||||
runner: "doctest.DocTestRunner",
|
||||
dtest: "doctest.DocTest",
|
||||
) -> "Self":
|
||||
runner: doctest.DocTestRunner,
|
||||
dtest: doctest.DocTest,
|
||||
) -> Self:
|
||||
# incompatible signature due to imposed limits on subclass
|
||||
"""The public named constructor."""
|
||||
return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest)
|
||||
|
||||
def _initrequest(self) -> None:
|
||||
self.funcargs: Dict[str, object] = {}
|
||||
self.funcargs: dict[str, object] = {}
|
||||
self._request = TopRequest(self, _ispytest=True) # type: ignore[arg-type]
|
||||
|
||||
def setup(self) -> None:
|
||||
|
@ -298,7 +294,7 @@ class DoctestItem(Item):
|
|||
def runtest(self) -> None:
|
||||
_check_all_skipped(self.dtest)
|
||||
self._disable_output_capturing_for_darwin()
|
||||
failures: List["doctest.DocTestFailure"] = []
|
||||
failures: list[doctest.DocTestFailure] = []
|
||||
# Type ignored because we change the type of `out` from what
|
||||
# doctest expects.
|
||||
self.runner.run(self.dtest, out=failures) # type: ignore[arg-type]
|
||||
|
@ -320,12 +316,12 @@ class DoctestItem(Item):
|
|||
def repr_failure( # type: ignore[override]
|
||||
self,
|
||||
excinfo: ExceptionInfo[BaseException],
|
||||
) -> Union[str, TerminalRepr]:
|
||||
) -> str | TerminalRepr:
|
||||
import doctest
|
||||
|
||||
failures: Optional[
|
||||
Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]]
|
||||
] = None
|
||||
failures: (
|
||||
Sequence[doctest.DocTestFailure | doctest.UnexpectedException] | None
|
||||
) = None
|
||||
if isinstance(
|
||||
excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException)
|
||||
):
|
||||
|
@ -381,11 +377,11 @@ class DoctestItem(Item):
|
|||
reprlocation_lines.append((reprlocation, lines))
|
||||
return ReprFailDoctest(reprlocation_lines)
|
||||
|
||||
def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]:
|
||||
def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]:
|
||||
return self.path, self.dtest.lineno, "[doctest] %s" % self.name
|
||||
|
||||
|
||||
def _get_flag_lookup() -> Dict[str, int]:
|
||||
def _get_flag_lookup() -> dict[str, int]:
|
||||
import doctest
|
||||
|
||||
return dict(
|
||||
|
@ -451,7 +447,7 @@ class DoctestTextfile(Module):
|
|||
)
|
||||
|
||||
|
||||
def _check_all_skipped(test: "doctest.DocTest") -> None:
|
||||
def _check_all_skipped(test: doctest.DocTest) -> None:
|
||||
"""Raise pytest.skip() if all examples in the given DocTest have the SKIP
|
||||
option set."""
|
||||
import doctest
|
||||
|
@ -477,7 +473,7 @@ def _patch_unwrap_mock_aware() -> Generator[None, None, None]:
|
|||
real_unwrap = inspect.unwrap
|
||||
|
||||
def _mock_aware_unwrap(
|
||||
func: Callable[..., Any], *, stop: Optional[Callable[[Any], Any]] = None
|
||||
func: Callable[..., Any], *, stop: Callable[[Any], Any] | None = None
|
||||
) -> Any:
|
||||
try:
|
||||
if stop is None or stop is _is_mocked:
|
||||
|
@ -588,7 +584,7 @@ class DoctestModule(Module):
|
|||
)
|
||||
|
||||
|
||||
def _init_checker_class() -> Type["doctest.OutputChecker"]:
|
||||
def _init_checker_class() -> type[doctest.OutputChecker]:
|
||||
import doctest
|
||||
import re
|
||||
|
||||
|
@ -656,8 +652,8 @@ def _init_checker_class() -> Type["doctest.OutputChecker"]:
|
|||
return got
|
||||
offset = 0
|
||||
for w, g in zip(wants, gots):
|
||||
fraction: Optional[str] = w.group("fraction")
|
||||
exponent: Optional[str] = w.group("exponent1")
|
||||
fraction: str | None = w.group("fraction")
|
||||
exponent: str | None = w.group("exponent1")
|
||||
if exponent is None:
|
||||
exponent = w.group("exponent2")
|
||||
precision = 0 if fraction is None else len(fraction)
|
||||
|
@ -676,7 +672,7 @@ def _init_checker_class() -> Type["doctest.OutputChecker"]:
|
|||
return LiteralsOutputChecker
|
||||
|
||||
|
||||
def _get_checker() -> "doctest.OutputChecker":
|
||||
def _get_checker() -> doctest.OutputChecker:
|
||||
"""Return a doctest.OutputChecker subclass that supports some
|
||||
additional options:
|
||||
|
||||
|
@ -735,7 +731,7 @@ def _get_report_choice(key: str) -> int:
|
|||
|
||||
|
||||
@fixture(scope="session")
|
||||
def doctest_namespace() -> Dict[str, Any]:
|
||||
def doctest_namespace() -> dict[str, Any]:
|
||||
"""Fixture that returns a :py:class:`dict` that will be injected into the
|
||||
namespace of doctests.
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import Generator
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# mypy: allow-untyped-defs
|
||||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
from collections import defaultdict
|
||||
from collections import deque
|
||||
|
@ -12,21 +14,18 @@ from typing import AbstractSet
|
|||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
from typing import Dict
|
||||
from typing import Final
|
||||
from typing import final
|
||||
from typing import Generator
|
||||
from typing import Generic
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import MutableMapping
|
||||
from typing import NoReturn
|
||||
from typing import Optional
|
||||
from typing import OrderedDict
|
||||
from typing import overload
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
|
@ -111,18 +110,18 @@ _FixtureCachedResult = Union[
|
|||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class PseudoFixtureDef(Generic[FixtureValue]):
|
||||
cached_result: "_FixtureCachedResult[FixtureValue]"
|
||||
cached_result: _FixtureCachedResult[FixtureValue]
|
||||
_scope: Scope
|
||||
|
||||
|
||||
def pytest_sessionstart(session: "Session") -> None:
|
||||
def pytest_sessionstart(session: Session) -> None:
|
||||
session._fixturemanager = FixtureManager(session)
|
||||
|
||||
|
||||
def get_scope_package(
|
||||
node: nodes.Item,
|
||||
fixturedef: "FixtureDef[object]",
|
||||
) -> Optional[nodes.Node]:
|
||||
fixturedef: FixtureDef[object],
|
||||
) -> nodes.Node | None:
|
||||
from _pytest.python import Package
|
||||
|
||||
for parent in node.iter_parents():
|
||||
|
@ -131,7 +130,7 @@ def get_scope_package(
|
|||
return node.session
|
||||
|
||||
|
||||
def get_scope_node(node: nodes.Node, scope: Scope) -> Optional[nodes.Node]:
|
||||
def get_scope_node(node: nodes.Node, scope: Scope) -> nodes.Node | None:
|
||||
import _pytest.python
|
||||
|
||||
if scope is Scope.Function:
|
||||
|
@ -150,7 +149,7 @@ def get_scope_node(node: nodes.Node, scope: Scope) -> Optional[nodes.Node]:
|
|||
assert_never(scope)
|
||||
|
||||
|
||||
def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]:
|
||||
def getfixturemarker(obj: object) -> FixtureFunctionMarker | None:
|
||||
"""Return fixturemarker or None if it doesn't exist or raised
|
||||
exceptions."""
|
||||
return cast(
|
||||
|
@ -163,8 +162,8 @@ def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]:
|
|||
class FixtureArgKey:
|
||||
argname: str
|
||||
param_index: int
|
||||
scoped_item_path: Optional[Path]
|
||||
item_cls: Optional[type]
|
||||
scoped_item_path: Path | None
|
||||
item_cls: type | None
|
||||
|
||||
|
||||
def get_parametrized_fixture_keys(
|
||||
|
@ -204,10 +203,10 @@ def get_parametrized_fixture_keys(
|
|||
# setups and teardowns.
|
||||
|
||||
|
||||
def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]:
|
||||
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]] = {}
|
||||
items_by_argkey: Dict[
|
||||
Scope, Dict[FixtureArgKey, OrderedDict[nodes.Item, None]]
|
||||
def reorder_items(items: Sequence[nodes.Item]) -> list[nodes.Item]:
|
||||
argkeys_cache: dict[Scope, dict[nodes.Item, dict[FixtureArgKey, None]]] = {}
|
||||
items_by_argkey: dict[
|
||||
Scope, dict[FixtureArgKey, OrderedDict[nodes.Item, None]]
|
||||
] = {}
|
||||
for scope in HIGH_SCOPES:
|
||||
scoped_argkeys_cache = argkeys_cache[scope] = {}
|
||||
|
@ -226,8 +225,8 @@ def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]:
|
|||
|
||||
def fix_cache_order(
|
||||
item: nodes.Item,
|
||||
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]],
|
||||
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, OrderedDict[nodes.Item, None]]],
|
||||
argkeys_cache: dict[Scope, dict[nodes.Item, dict[FixtureArgKey, None]]],
|
||||
items_by_argkey: dict[Scope, dict[FixtureArgKey, OrderedDict[nodes.Item, None]]],
|
||||
) -> None:
|
||||
for scope in HIGH_SCOPES:
|
||||
scoped_items_by_argkey = items_by_argkey[scope]
|
||||
|
@ -237,20 +236,20 @@ def fix_cache_order(
|
|||
|
||||
|
||||
def reorder_items_atscope(
|
||||
items: Dict[nodes.Item, None],
|
||||
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]],
|
||||
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, OrderedDict[nodes.Item, None]]],
|
||||
items: dict[nodes.Item, None],
|
||||
argkeys_cache: dict[Scope, dict[nodes.Item, dict[FixtureArgKey, None]]],
|
||||
items_by_argkey: dict[Scope, dict[FixtureArgKey, OrderedDict[nodes.Item, None]]],
|
||||
scope: Scope,
|
||||
) -> Dict[nodes.Item, None]:
|
||||
) -> dict[nodes.Item, None]:
|
||||
if scope is Scope.Function or len(items) < 3:
|
||||
return items
|
||||
ignore: Set[Optional[FixtureArgKey]] = set()
|
||||
ignore: set[FixtureArgKey | None] = set()
|
||||
items_deque = deque(items)
|
||||
items_done: Dict[nodes.Item, None] = {}
|
||||
items_done: dict[nodes.Item, None] = {}
|
||||
scoped_items_by_argkey = items_by_argkey[scope]
|
||||
scoped_argkeys_cache = argkeys_cache[scope]
|
||||
while items_deque:
|
||||
no_argkey_group: Dict[nodes.Item, None] = {}
|
||||
no_argkey_group: dict[nodes.Item, None] = {}
|
||||
slicing_argkey = None
|
||||
while items_deque:
|
||||
item = items_deque.popleft()
|
||||
|
@ -299,19 +298,19 @@ class FuncFixtureInfo:
|
|||
__slots__ = ("argnames", "initialnames", "names_closure", "name2fixturedefs")
|
||||
|
||||
# Fixture names that the item requests directly by function parameters.
|
||||
argnames: Tuple[str, ...]
|
||||
argnames: tuple[str, ...]
|
||||
# Fixture names that the item immediately requires. These include
|
||||
# argnames + fixture names specified via usefixtures and via autouse=True in
|
||||
# fixture definitions.
|
||||
initialnames: Tuple[str, ...]
|
||||
initialnames: tuple[str, ...]
|
||||
# The transitive closure of the fixture names that the item requires.
|
||||
# Note: can't include dynamic dependencies (`request.getfixturevalue` calls).
|
||||
names_closure: List[str]
|
||||
names_closure: list[str]
|
||||
# A map from a fixture name in the transitive closure to the FixtureDefs
|
||||
# matching the name which are applicable to this function.
|
||||
# There may be multiple overriding fixtures with the same name. The
|
||||
# sequence is ordered from furthest to closes to the function.
|
||||
name2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]]
|
||||
name2fixturedefs: dict[str, Sequence[FixtureDef[Any]]]
|
||||
|
||||
def prune_dependency_tree(self) -> None:
|
||||
"""Recompute names_closure from initialnames and name2fixturedefs.
|
||||
|
@ -324,7 +323,7 @@ class FuncFixtureInfo:
|
|||
tree. In this way the dependency tree can get pruned, and the closure
|
||||
of argnames may get reduced.
|
||||
"""
|
||||
closure: Set[str] = set()
|
||||
closure: set[str] = set()
|
||||
working_set = set(self.initialnames)
|
||||
while working_set:
|
||||
argname = working_set.pop()
|
||||
|
@ -350,10 +349,10 @@ class FixtureRequest(abc.ABC):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
pyfuncitem: "Function",
|
||||
fixturename: Optional[str],
|
||||
arg2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]],
|
||||
fixture_defs: Dict[str, "FixtureDef[Any]"],
|
||||
pyfuncitem: Function,
|
||||
fixturename: str | None,
|
||||
arg2fixturedefs: dict[str, Sequence[FixtureDef[Any]]],
|
||||
fixture_defs: dict[str, FixtureDef[Any]],
|
||||
*,
|
||||
_ispytest: bool = False,
|
||||
) -> None:
|
||||
|
@ -380,7 +379,7 @@ class FixtureRequest(abc.ABC):
|
|||
self.param: Any
|
||||
|
||||
@property
|
||||
def _fixturemanager(self) -> "FixtureManager":
|
||||
def _fixturemanager(self) -> FixtureManager:
|
||||
return self._pyfuncitem.session._fixturemanager
|
||||
|
||||
@property
|
||||
|
@ -396,13 +395,13 @@ class FixtureRequest(abc.ABC):
|
|||
@abc.abstractmethod
|
||||
def _check_scope(
|
||||
self,
|
||||
requested_fixturedef: Union["FixtureDef[object]", PseudoFixtureDef[object]],
|
||||
requested_fixturedef: FixtureDef[object] | PseudoFixtureDef[object],
|
||||
requested_scope: Scope,
|
||||
) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def fixturenames(self) -> List[str]:
|
||||
def fixturenames(self) -> list[str]:
|
||||
"""Names of all active fixtures in this request."""
|
||||
result = list(self._pyfuncitem.fixturenames)
|
||||
result.extend(set(self._fixture_defs).difference(result))
|
||||
|
@ -467,7 +466,7 @@ class FixtureRequest(abc.ABC):
|
|||
return node.keywords
|
||||
|
||||
@property
|
||||
def session(self) -> "Session":
|
||||
def session(self) -> Session:
|
||||
"""Pytest session object."""
|
||||
return self._pyfuncitem.session
|
||||
|
||||
|
@ -477,7 +476,7 @@ class FixtureRequest(abc.ABC):
|
|||
the last test within the requesting test context finished execution."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def applymarker(self, marker: Union[str, MarkDecorator]) -> None:
|
||||
def applymarker(self, marker: str | MarkDecorator) -> None:
|
||||
"""Apply a marker to a single test function invocation.
|
||||
|
||||
This method is useful if you don't want to have a keyword/marker
|
||||
|
@ -488,7 +487,7 @@ class FixtureRequest(abc.ABC):
|
|||
"""
|
||||
self.node.add_marker(marker)
|
||||
|
||||
def raiseerror(self, msg: Optional[str]) -> NoReturn:
|
||||
def raiseerror(self, msg: str | None) -> NoReturn:
|
||||
"""Raise a FixtureLookupError exception.
|
||||
|
||||
:param msg:
|
||||
|
@ -525,7 +524,7 @@ class FixtureRequest(abc.ABC):
|
|||
)
|
||||
return fixturedef.cached_result[0]
|
||||
|
||||
def _iter_chain(self) -> Iterator["SubRequest"]:
|
||||
def _iter_chain(self) -> Iterator[SubRequest]:
|
||||
"""Yield all SubRequests in the chain, from self up.
|
||||
|
||||
Note: does *not* yield the TopRequest.
|
||||
|
@ -537,7 +536,7 @@ class FixtureRequest(abc.ABC):
|
|||
|
||||
def _get_active_fixturedef(
|
||||
self, argname: str
|
||||
) -> Union["FixtureDef[object]", PseudoFixtureDef[object]]:
|
||||
) -> FixtureDef[object] | PseudoFixtureDef[object]:
|
||||
if argname == "request":
|
||||
cached_result = (self, [0], None)
|
||||
return PseudoFixtureDef(cached_result, Scope.Function)
|
||||
|
@ -608,7 +607,7 @@ class FixtureRequest(abc.ABC):
|
|||
self._fixture_defs[argname] = fixturedef
|
||||
return fixturedef
|
||||
|
||||
def _check_fixturedef_without_param(self, fixturedef: "FixtureDef[object]") -> None:
|
||||
def _check_fixturedef_without_param(self, fixturedef: FixtureDef[object]) -> None:
|
||||
"""Check that this request is allowed to execute this fixturedef without
|
||||
a param."""
|
||||
funcitem = self._pyfuncitem
|
||||
|
@ -641,7 +640,7 @@ class FixtureRequest(abc.ABC):
|
|||
)
|
||||
fail(msg, pytrace=False)
|
||||
|
||||
def _get_fixturestack(self) -> List["FixtureDef[Any]"]:
|
||||
def _get_fixturestack(self) -> list[FixtureDef[Any]]:
|
||||
values = [request._fixturedef for request in self._iter_chain()]
|
||||
values.reverse()
|
||||
return values
|
||||
|
@ -651,7 +650,7 @@ class FixtureRequest(abc.ABC):
|
|||
class TopRequest(FixtureRequest):
|
||||
"""The type of the ``request`` fixture in a test function."""
|
||||
|
||||
def __init__(self, pyfuncitem: "Function", *, _ispytest: bool = False) -> None:
|
||||
def __init__(self, pyfuncitem: Function, *, _ispytest: bool = False) -> None:
|
||||
super().__init__(
|
||||
fixturename=None,
|
||||
pyfuncitem=pyfuncitem,
|
||||
|
@ -666,7 +665,7 @@ class TopRequest(FixtureRequest):
|
|||
|
||||
def _check_scope(
|
||||
self,
|
||||
requested_fixturedef: Union["FixtureDef[object]", PseudoFixtureDef[object]],
|
||||
requested_fixturedef: FixtureDef[object] | PseudoFixtureDef[object],
|
||||
requested_scope: Scope,
|
||||
) -> None:
|
||||
# TopRequest always has function scope so always valid.
|
||||
|
@ -700,7 +699,7 @@ class SubRequest(FixtureRequest):
|
|||
scope: Scope,
|
||||
param: Any,
|
||||
param_index: int,
|
||||
fixturedef: "FixtureDef[object]",
|
||||
fixturedef: FixtureDef[object],
|
||||
*,
|
||||
_ispytest: bool = False,
|
||||
) -> None:
|
||||
|
@ -730,7 +729,7 @@ class SubRequest(FixtureRequest):
|
|||
scope = self._scope
|
||||
if scope is Scope.Function:
|
||||
# This might also be a non-function Item despite its attribute name.
|
||||
node: Optional[nodes.Node] = self._pyfuncitem
|
||||
node: nodes.Node | None = self._pyfuncitem
|
||||
elif scope is Scope.Package:
|
||||
node = get_scope_package(self._pyfuncitem, self._fixturedef)
|
||||
else:
|
||||
|
@ -743,7 +742,7 @@ class SubRequest(FixtureRequest):
|
|||
|
||||
def _check_scope(
|
||||
self,
|
||||
requested_fixturedef: Union["FixtureDef[object]", PseudoFixtureDef[object]],
|
||||
requested_fixturedef: FixtureDef[object] | PseudoFixtureDef[object],
|
||||
requested_scope: Scope,
|
||||
) -> None:
|
||||
if isinstance(requested_fixturedef, PseudoFixtureDef):
|
||||
|
@ -764,7 +763,7 @@ class SubRequest(FixtureRequest):
|
|||
pytrace=False,
|
||||
)
|
||||
|
||||
def _format_fixturedef_line(self, fixturedef: "FixtureDef[object]") -> str:
|
||||
def _format_fixturedef_line(self, fixturedef: FixtureDef[object]) -> str:
|
||||
factory = fixturedef.func
|
||||
path, lineno = getfslineno(factory)
|
||||
if isinstance(path, Path):
|
||||
|
@ -781,15 +780,15 @@ class FixtureLookupError(LookupError):
|
|||
"""Could not return a requested fixture (missing or invalid)."""
|
||||
|
||||
def __init__(
|
||||
self, argname: Optional[str], request: FixtureRequest, msg: Optional[str] = None
|
||||
self, argname: str | None, request: FixtureRequest, msg: str | None = None
|
||||
) -> None:
|
||||
self.argname = argname
|
||||
self.request = request
|
||||
self.fixturestack = request._get_fixturestack()
|
||||
self.msg = msg
|
||||
|
||||
def formatrepr(self) -> "FixtureLookupErrorRepr":
|
||||
tblines: List[str] = []
|
||||
def formatrepr(self) -> FixtureLookupErrorRepr:
|
||||
tblines: list[str] = []
|
||||
addline = tblines.append
|
||||
stack = [self.request._pyfuncitem.obj]
|
||||
stack.extend(map(lambda x: x.func, self.fixturestack))
|
||||
|
@ -837,11 +836,11 @@ class FixtureLookupError(LookupError):
|
|||
class FixtureLookupErrorRepr(TerminalRepr):
|
||||
def __init__(
|
||||
self,
|
||||
filename: Union[str, "os.PathLike[str]"],
|
||||
filename: str | os.PathLike[str],
|
||||
firstlineno: int,
|
||||
tblines: Sequence[str],
|
||||
errorstring: str,
|
||||
argname: Optional[str],
|
||||
argname: str | None,
|
||||
) -> None:
|
||||
self.tblines = tblines
|
||||
self.errorstring = errorstring
|
||||
|
@ -869,7 +868,7 @@ class FixtureLookupErrorRepr(TerminalRepr):
|
|||
|
||||
|
||||
def call_fixture_func(
|
||||
fixturefunc: "_FixtureFunc[FixtureValue]", request: FixtureRequest, kwargs
|
||||
fixturefunc: _FixtureFunc[FixtureValue], request: FixtureRequest, kwargs
|
||||
) -> FixtureValue:
|
||||
if is_generator(fixturefunc):
|
||||
fixturefunc = cast(
|
||||
|
@ -940,14 +939,12 @@ class FixtureDef(Generic[FixtureValue]):
|
|||
def __init__(
|
||||
self,
|
||||
config: Config,
|
||||
baseid: Optional[str],
|
||||
baseid: str | None,
|
||||
argname: str,
|
||||
func: "_FixtureFunc[FixtureValue]",
|
||||
scope: Union[Scope, _ScopeName, Callable[[str, Config], _ScopeName], None],
|
||||
params: Optional[Sequence[object]],
|
||||
ids: Optional[
|
||||
Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]
|
||||
] = None,
|
||||
func: _FixtureFunc[FixtureValue],
|
||||
scope: Scope | _ScopeName | Callable[[str, Config], _ScopeName] | None,
|
||||
params: Sequence[object] | None,
|
||||
ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None,
|
||||
*,
|
||||
_ispytest: bool = False,
|
||||
) -> None:
|
||||
|
@ -993,8 +990,8 @@ class FixtureDef(Generic[FixtureValue]):
|
|||
self.argnames: Final = getfuncargnames(func, name=argname)
|
||||
# If the fixture was executed, the current value of the fixture.
|
||||
# Can change if the fixture is executed with different parameters.
|
||||
self.cached_result: Optional[_FixtureCachedResult[FixtureValue]] = None
|
||||
self._finalizers: Final[List[Callable[[], object]]] = []
|
||||
self.cached_result: _FixtureCachedResult[FixtureValue] | None = None
|
||||
self._finalizers: Final[list[Callable[[], object]]] = []
|
||||
|
||||
@property
|
||||
def scope(self) -> _ScopeName:
|
||||
|
@ -1005,7 +1002,7 @@ class FixtureDef(Generic[FixtureValue]):
|
|||
self._finalizers.append(finalizer)
|
||||
|
||||
def finish(self, request: SubRequest) -> None:
|
||||
exceptions: List[BaseException] = []
|
||||
exceptions: list[BaseException] = []
|
||||
while self._finalizers:
|
||||
fin = self._finalizers.pop()
|
||||
try:
|
||||
|
@ -1089,7 +1086,7 @@ class FixtureDef(Generic[FixtureValue]):
|
|||
|
||||
def resolve_fixture_function(
|
||||
fixturedef: FixtureDef[FixtureValue], request: FixtureRequest
|
||||
) -> "_FixtureFunc[FixtureValue]":
|
||||
) -> _FixtureFunc[FixtureValue]:
|
||||
"""Get the actual callable that can be called to obtain the fixture
|
||||
value."""
|
||||
fixturefunc = fixturedef.func
|
||||
|
@ -1137,7 +1134,7 @@ def pytest_fixture_setup(
|
|||
|
||||
def wrap_function_to_error_out_if_called_directly(
|
||||
function: FixtureFunction,
|
||||
fixture_marker: "FixtureFunctionMarker",
|
||||
fixture_marker: FixtureFunctionMarker,
|
||||
) -> FixtureFunction:
|
||||
"""Wrap the given fixture function so we can raise an error about it being called directly,
|
||||
instead of used as an argument in a test function."""
|
||||
|
@ -1163,13 +1160,11 @@ def wrap_function_to_error_out_if_called_directly(
|
|||
@final
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class FixtureFunctionMarker:
|
||||
scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]"
|
||||
params: Optional[Tuple[object, ...]]
|
||||
scope: _ScopeName | Callable[[str, Config], _ScopeName]
|
||||
params: tuple[object, ...] | None
|
||||
autouse: bool = False
|
||||
ids: Optional[
|
||||
Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]
|
||||
] = None
|
||||
name: Optional[str] = None
|
||||
ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None
|
||||
name: str | None = None
|
||||
|
||||
_ispytest: dataclasses.InitVar[bool] = False
|
||||
|
||||
|
@ -1207,13 +1202,11 @@ class FixtureFunctionMarker:
|
|||
def fixture(
|
||||
fixture_function: FixtureFunction,
|
||||
*,
|
||||
scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ...,
|
||||
params: Optional[Iterable[object]] = ...,
|
||||
scope: _ScopeName | Callable[[str, Config], _ScopeName] = ...,
|
||||
params: Iterable[object] | None = ...,
|
||||
autouse: bool = ...,
|
||||
ids: Optional[
|
||||
Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
|
||||
] = ...,
|
||||
name: Optional[str] = ...,
|
||||
ids: Sequence[object | None] | Callable[[Any], object | None] | None = ...,
|
||||
name: str | None = ...,
|
||||
) -> FixtureFunction: ...
|
||||
|
||||
|
||||
|
@ -1221,27 +1214,23 @@ def fixture(
|
|||
def fixture(
|
||||
fixture_function: None = ...,
|
||||
*,
|
||||
scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ...,
|
||||
params: Optional[Iterable[object]] = ...,
|
||||
scope: _ScopeName | Callable[[str, Config], _ScopeName] = ...,
|
||||
params: Iterable[object] | None = ...,
|
||||
autouse: bool = ...,
|
||||
ids: Optional[
|
||||
Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
|
||||
] = ...,
|
||||
name: Optional[str] = None,
|
||||
ids: Sequence[object | None] | Callable[[Any], object | None] | None = ...,
|
||||
name: str | None = None,
|
||||
) -> FixtureFunctionMarker: ...
|
||||
|
||||
|
||||
def fixture(
|
||||
fixture_function: Optional[FixtureFunction] = None,
|
||||
fixture_function: FixtureFunction | None = None,
|
||||
*,
|
||||
scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = "function",
|
||||
params: Optional[Iterable[object]] = None,
|
||||
scope: _ScopeName | Callable[[str, Config], _ScopeName] = "function",
|
||||
params: Iterable[object] | None = None,
|
||||
autouse: bool = False,
|
||||
ids: Optional[
|
||||
Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
|
||||
] = None,
|
||||
name: Optional[str] = None,
|
||||
) -> Union[FixtureFunctionMarker, FixtureFunction]:
|
||||
ids: Sequence[object | None] | Callable[[Any], object | None] | None = None,
|
||||
name: str | None = None,
|
||||
) -> FixtureFunctionMarker | FixtureFunction:
|
||||
"""Decorator to mark a fixture factory function.
|
||||
|
||||
This decorator can be used, with or without parameters, to define a
|
||||
|
@ -1375,7 +1364,7 @@ def pytest_addoption(parser: Parser) -> None:
|
|||
)
|
||||
|
||||
|
||||
def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
|
||||
def pytest_cmdline_main(config: Config) -> int | ExitCode | None:
|
||||
if config.option.showfixtures:
|
||||
showfixtures(config)
|
||||
return 0
|
||||
|
@ -1385,7 +1374,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
|
|||
return None
|
||||
|
||||
|
||||
def _get_direct_parametrize_args(node: nodes.Node) -> Set[str]:
|
||||
def _get_direct_parametrize_args(node: nodes.Node) -> set[str]:
|
||||
"""Return all direct parametrization arguments of a node, so we don't
|
||||
mistake them for fixtures.
|
||||
|
||||
|
@ -1394,7 +1383,7 @@ def _get_direct_parametrize_args(node: nodes.Node) -> Set[str]:
|
|||
These things are done later as well when dealing with parametrization
|
||||
so this could be improved.
|
||||
"""
|
||||
parametrize_argnames: Set[str] = set()
|
||||
parametrize_argnames: set[str] = set()
|
||||
for marker in node.iter_markers(name="parametrize"):
|
||||
if not marker.kwargs.get("indirect", False):
|
||||
p_argnames, _ = ParameterSet._parse_parametrize_args(
|
||||
|
@ -1404,7 +1393,7 @@ def _get_direct_parametrize_args(node: nodes.Node) -> Set[str]:
|
|||
return parametrize_argnames
|
||||
|
||||
|
||||
def deduplicate_names(*seqs: Iterable[str]) -> Tuple[str, ...]:
|
||||
def deduplicate_names(*seqs: Iterable[str]) -> tuple[str, ...]:
|
||||
"""De-duplicate the sequence of names while keeping the original order."""
|
||||
# Ideally we would use a set, but it does not preserve insertion order.
|
||||
return tuple(dict.fromkeys(name for seq in seqs for name in seq))
|
||||
|
@ -1441,17 +1430,17 @@ class FixtureManager:
|
|||
by a lookup of their FuncFixtureInfo.
|
||||
"""
|
||||
|
||||
def __init__(self, session: "Session") -> None:
|
||||
def __init__(self, session: Session) -> None:
|
||||
self.session = session
|
||||
self.config: Config = session.config
|
||||
# Maps a fixture name (argname) to all of the FixtureDefs in the test
|
||||
# suite/plugins defined with this name. Populated by parsefactories().
|
||||
# TODO: The order of the FixtureDefs list of each arg is significant,
|
||||
# explain.
|
||||
self._arg2fixturedefs: Final[Dict[str, List[FixtureDef[Any]]]] = {}
|
||||
self._holderobjseen: Final[Set[object]] = set()
|
||||
self._arg2fixturedefs: Final[dict[str, list[FixtureDef[Any]]]] = {}
|
||||
self._holderobjseen: Final[set[object]] = set()
|
||||
# A mapping from a nodeid to a list of autouse fixtures it defines.
|
||||
self._nodeid_autousenames: Final[Dict[str, List[str]]] = {
|
||||
self._nodeid_autousenames: Final[dict[str, list[str]]] = {
|
||||
"": self.config.getini("usefixtures"),
|
||||
}
|
||||
session.config.pluginmanager.register(self, "funcmanage")
|
||||
|
@ -1459,8 +1448,8 @@ class FixtureManager:
|
|||
def getfixtureinfo(
|
||||
self,
|
||||
node: nodes.Item,
|
||||
func: Optional[Callable[..., object]],
|
||||
cls: Optional[type],
|
||||
func: Callable[..., object] | None,
|
||||
cls: type | None,
|
||||
) -> FuncFixtureInfo:
|
||||
"""Calculate the :class:`FuncFixtureInfo` for an item.
|
||||
|
||||
|
@ -1531,9 +1520,9 @@ class FixtureManager:
|
|||
def getfixtureclosure(
|
||||
self,
|
||||
parentnode: nodes.Node,
|
||||
initialnames: Tuple[str, ...],
|
||||
initialnames: tuple[str, ...],
|
||||
ignore_args: AbstractSet[str],
|
||||
) -> Tuple[List[str], Dict[str, Sequence[FixtureDef[Any]]]]:
|
||||
) -> tuple[list[str], dict[str, Sequence[FixtureDef[Any]]]]:
|
||||
# Collect the closure of all fixtures, starting with the given
|
||||
# fixturenames as the initial set. As we have to visit all
|
||||
# factory definitions anyway, we also return an arg2fixturedefs
|
||||
|
@ -1543,7 +1532,7 @@ class FixtureManager:
|
|||
|
||||
fixturenames_closure = list(initialnames)
|
||||
|
||||
arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {}
|
||||
arg2fixturedefs: dict[str, Sequence[FixtureDef[Any]]] = {}
|
||||
lastlen = -1
|
||||
while lastlen != len(fixturenames_closure):
|
||||
lastlen = len(fixturenames_closure)
|
||||
|
@ -1570,7 +1559,7 @@ class FixtureManager:
|
|||
fixturenames_closure.sort(key=sort_by_scope, reverse=True)
|
||||
return fixturenames_closure, arg2fixturedefs
|
||||
|
||||
def pytest_generate_tests(self, metafunc: "Metafunc") -> None:
|
||||
def pytest_generate_tests(self, metafunc: Metafunc) -> None:
|
||||
"""Generate new tests based on parametrized fixtures used by the given metafunc"""
|
||||
|
||||
def get_parametrize_mark_argnames(mark: Mark) -> Sequence[str]:
|
||||
|
@ -1615,7 +1604,7 @@ class FixtureManager:
|
|||
|
||||
# Try next super fixture, if any.
|
||||
|
||||
def pytest_collection_modifyitems(self, items: List[nodes.Item]) -> None:
|
||||
def pytest_collection_modifyitems(self, items: list[nodes.Item]) -> None:
|
||||
# Separate parametrized setups.
|
||||
items[:] = reorder_items(items)
|
||||
|
||||
|
@ -1623,15 +1612,14 @@ class FixtureManager:
|
|||
self,
|
||||
*,
|
||||
name: str,
|
||||
func: "_FixtureFunc[object]",
|
||||
nodeid: Optional[str],
|
||||
scope: Union[
|
||||
Scope, _ScopeName, Callable[[str, Config], _ScopeName], None
|
||||
] = "function",
|
||||
params: Optional[Sequence[object]] = None,
|
||||
ids: Optional[
|
||||
Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]
|
||||
] = None,
|
||||
func: _FixtureFunc[object],
|
||||
nodeid: str | None,
|
||||
scope: Scope
|
||||
| _ScopeName
|
||||
| Callable[[str, Config], _ScopeName]
|
||||
| None = "function",
|
||||
params: Sequence[object] | None = None,
|
||||
ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None,
|
||||
autouse: bool = False,
|
||||
) -> None:
|
||||
"""Register a fixture
|
||||
|
@ -1689,14 +1677,14 @@ class FixtureManager:
|
|||
def parsefactories(
|
||||
self,
|
||||
node_or_obj: object,
|
||||
nodeid: Optional[str],
|
||||
nodeid: str | None,
|
||||
) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
def parsefactories(
|
||||
self,
|
||||
node_or_obj: Union[nodes.Node, object],
|
||||
nodeid: Union[str, NotSetType, None] = NOTSET,
|
||||
node_or_obj: nodes.Node | object,
|
||||
nodeid: str | NotSetType | None = NOTSET,
|
||||
) -> None:
|
||||
"""Collect fixtures from a collection node or object.
|
||||
|
||||
|
@ -1754,7 +1742,7 @@ class FixtureManager:
|
|||
|
||||
def getfixturedefs(
|
||||
self, argname: str, node: nodes.Node
|
||||
) -> Optional[Sequence[FixtureDef[Any]]]:
|
||||
) -> Sequence[FixtureDef[Any]] | None:
|
||||
"""Get FixtureDefs for a fixture name which are applicable
|
||||
to a given node.
|
||||
|
||||
|
@ -1781,7 +1769,7 @@ class FixtureManager:
|
|||
yield fixturedef
|
||||
|
||||
|
||||
def show_fixtures_per_test(config: Config) -> Union[int, ExitCode]:
|
||||
def show_fixtures_per_test(config: Config) -> int | ExitCode:
|
||||
from _pytest.main import wrap_session
|
||||
|
||||
return wrap_session(config, _show_fixtures_per_test)
|
||||
|
@ -1799,7 +1787,7 @@ def _pretty_fixture_path(invocation_dir: Path, func) -> str:
|
|||
return bestrelpath(invocation_dir, loc)
|
||||
|
||||
|
||||
def _show_fixtures_per_test(config: Config, session: "Session") -> None:
|
||||
def _show_fixtures_per_test(config: Config, session: Session) -> None:
|
||||
import _pytest.config
|
||||
|
||||
session.perform_collect()
|
||||
|
@ -1829,7 +1817,7 @@ def _show_fixtures_per_test(config: Config, session: "Session") -> None:
|
|||
|
||||
def write_item(item: nodes.Item) -> None:
|
||||
# Not all items have _fixtureinfo attribute.
|
||||
info: Optional[FuncFixtureInfo] = getattr(item, "_fixtureinfo", None)
|
||||
info: FuncFixtureInfo | None = getattr(item, "_fixtureinfo", None)
|
||||
if info is None or not info.name2fixturedefs:
|
||||
# This test item does not use any fixtures.
|
||||
return
|
||||
|
@ -1849,13 +1837,13 @@ def _show_fixtures_per_test(config: Config, session: "Session") -> None:
|
|||
write_item(session_item)
|
||||
|
||||
|
||||
def showfixtures(config: Config) -> Union[int, ExitCode]:
|
||||
def showfixtures(config: Config) -> int | ExitCode:
|
||||
from _pytest.main import wrap_session
|
||||
|
||||
return wrap_session(config, _showfixtures_main)
|
||||
|
||||
|
||||
def _showfixtures_main(config: Config, session: "Session") -> None:
|
||||
def _showfixtures_main(config: Config, session: Session) -> None:
|
||||
import _pytest.config
|
||||
|
||||
session.perform_collect()
|
||||
|
@ -1866,7 +1854,7 @@ def _showfixtures_main(config: Config, session: "Session") -> None:
|
|||
fm = session._fixturemanager
|
||||
|
||||
available = []
|
||||
seen: Set[Tuple[str, str]] = set()
|
||||
seen: set[tuple[str, str]] = set()
|
||||
|
||||
for argname, fixturedefs in fm._arg2fixturedefs.items():
|
||||
assert fixturedefs is not None
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
"""Provides a function to report all internal modules for using freezing
|
||||
tools."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import types
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Union
|
||||
|
||||
|
||||
def freeze_includes() -> List[str]:
|
||||
def freeze_includes() -> list[str]:
|
||||
"""Return a list of module names used by pytest that should be
|
||||
included by cx_freeze."""
|
||||
import _pytest
|
||||
|
@ -17,7 +17,7 @@ def freeze_includes() -> List[str]:
|
|||
|
||||
|
||||
def _iter_all_modules(
|
||||
package: Union[str, types.ModuleType],
|
||||
package: str | types.ModuleType,
|
||||
prefix: str = "",
|
||||
) -> Iterator[str]:
|
||||
"""Iterate over the names of all modules that can be found in the given
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Version info, help messages, tracing configuration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from argparse import Action
|
||||
import os
|
||||
import sys
|
||||
from typing import Generator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
|
||||
from _pytest.config import Config
|
||||
from _pytest.config import ExitCode
|
||||
|
@ -147,7 +146,7 @@ def showversion(config: Config) -> None:
|
|||
sys.stdout.write(f"pytest {pytest.__version__}\n")
|
||||
|
||||
|
||||
def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
|
||||
def pytest_cmdline_main(config: Config) -> int | ExitCode | None:
|
||||
if config.option.version > 0:
|
||||
showversion(config)
|
||||
return 0
|
||||
|
@ -162,7 +161,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
|
|||
def showhelp(config: Config) -> None:
|
||||
import textwrap
|
||||
|
||||
reporter: Optional[TerminalReporter] = config.pluginmanager.get_plugin(
|
||||
reporter: TerminalReporter | None = config.pluginmanager.get_plugin(
|
||||
"terminalreporter"
|
||||
)
|
||||
assert reporter is not None
|
||||
|
@ -239,7 +238,7 @@ def showhelp(config: Config) -> None:
|
|||
conftest_options = [("pytest_plugins", "list of plugin names to load")]
|
||||
|
||||
|
||||
def getpluginversioninfo(config: Config) -> List[str]:
|
||||
def getpluginversioninfo(config: Config) -> list[str]:
|
||||
lines = []
|
||||
plugininfo = config.pluginmanager.list_plugin_distinfo()
|
||||
if plugininfo:
|
||||
|
@ -251,7 +250,7 @@ def getpluginversioninfo(config: Config) -> List[str]:
|
|||
return lines
|
||||
|
||||
|
||||
def pytest_report_header(config: Config) -> List[str]:
|
||||
def pytest_report_header(config: Config) -> list[str]:
|
||||
lines = []
|
||||
if config.option.debug or config.option.traceconfig:
|
||||
lines.append(f"using: pytest-{pytest.__version__}")
|
||||
|
|
|
@ -2,16 +2,13 @@
|
|||
"""Hook specifications for pytest plugins which are invoked by pytest itself
|
||||
and by builtin plugins."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Mapping
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from pluggy import HookspecMarker
|
||||
|
||||
|
@ -56,7 +53,7 @@ hookspec = HookspecMarker("pytest")
|
|||
|
||||
|
||||
@hookspec(historic=True)
|
||||
def pytest_addhooks(pluginmanager: "PytestPluginManager") -> None:
|
||||
def pytest_addhooks(pluginmanager: PytestPluginManager) -> None:
|
||||
"""Called at plugin registration time to allow adding new hooks via a call to
|
||||
:func:`pluginmanager.add_hookspecs(module_or_class, prefix) <pytest.PytestPluginManager.add_hookspecs>`.
|
||||
|
||||
|
@ -75,9 +72,9 @@ def pytest_addhooks(pluginmanager: "PytestPluginManager") -> None:
|
|||
|
||||
@hookspec(historic=True)
|
||||
def pytest_plugin_registered(
|
||||
plugin: "_PluggyPlugin",
|
||||
plugin: _PluggyPlugin,
|
||||
plugin_name: str,
|
||||
manager: "PytestPluginManager",
|
||||
manager: PytestPluginManager,
|
||||
) -> None:
|
||||
"""A new pytest plugin got registered.
|
||||
|
||||
|
@ -99,7 +96,7 @@ def pytest_plugin_registered(
|
|||
|
||||
|
||||
@hookspec(historic=True)
|
||||
def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") -> None:
|
||||
def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager) -> None:
|
||||
"""Register argparse-style options and ini-style config values,
|
||||
called once at the beginning of a test run.
|
||||
|
||||
|
@ -140,7 +137,7 @@ def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") ->
|
|||
|
||||
|
||||
@hookspec(historic=True)
|
||||
def pytest_configure(config: "Config") -> None:
|
||||
def pytest_configure(config: Config) -> None:
|
||||
"""Allow plugins and conftest files to perform initial configuration.
|
||||
|
||||
.. note::
|
||||
|
@ -165,8 +162,8 @@ def pytest_configure(config: "Config") -> None:
|
|||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_cmdline_parse(
|
||||
pluginmanager: "PytestPluginManager", args: List[str]
|
||||
) -> Optional["Config"]:
|
||||
pluginmanager: PytestPluginManager, args: list[str]
|
||||
) -> Config | None:
|
||||
"""Return an initialized :class:`~pytest.Config`, parsing the specified args.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
|
@ -188,7 +185,7 @@ def pytest_cmdline_parse(
|
|||
|
||||
|
||||
def pytest_load_initial_conftests(
|
||||
early_config: "Config", parser: "Parser", args: List[str]
|
||||
early_config: Config, parser: Parser, args: list[str]
|
||||
) -> None:
|
||||
"""Called to implement the loading of :ref:`initial conftest files
|
||||
<pluginorder>` ahead of command line option parsing.
|
||||
|
@ -205,7 +202,7 @@ def pytest_load_initial_conftests(
|
|||
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_cmdline_main(config: "Config") -> Optional[Union["ExitCode", int]]:
|
||||
def pytest_cmdline_main(config: Config) -> ExitCode | int | None:
|
||||
"""Called for performing the main command line action.
|
||||
|
||||
The default implementation will invoke the configure hooks and
|
||||
|
@ -229,7 +226,7 @@ def pytest_cmdline_main(config: "Config") -> Optional[Union["ExitCode", int]]:
|
|||
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_collection(session: "Session") -> Optional[object]:
|
||||
def pytest_collection(session: Session) -> object | None:
|
||||
"""Perform the collection phase for the given session.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
|
@ -271,7 +268,7 @@ def pytest_collection(session: "Session") -> Optional[object]:
|
|||
|
||||
|
||||
def pytest_collection_modifyitems(
|
||||
session: "Session", config: "Config", items: List["Item"]
|
||||
session: Session, config: Config, items: list[Item]
|
||||
) -> None:
|
||||
"""Called after collection has been performed. May filter or re-order
|
||||
the items in-place.
|
||||
|
@ -287,7 +284,7 @@ def pytest_collection_modifyitems(
|
|||
"""
|
||||
|
||||
|
||||
def pytest_collection_finish(session: "Session") -> None:
|
||||
def pytest_collection_finish(session: Session) -> None:
|
||||
"""Called after collection has been performed and modified.
|
||||
|
||||
:param session: The pytest session object.
|
||||
|
@ -308,8 +305,8 @@ def pytest_collection_finish(session: "Session") -> None:
|
|||
},
|
||||
)
|
||||
def pytest_ignore_collect(
|
||||
collection_path: Path, path: "LEGACY_PATH", config: "Config"
|
||||
) -> Optional[bool]:
|
||||
collection_path: Path, path: LEGACY_PATH, config: Config
|
||||
) -> bool | None:
|
||||
"""Return ``True`` to ignore this path for collection.
|
||||
|
||||
Return ``None`` to let other plugins ignore the path for collection.
|
||||
|
@ -323,6 +320,7 @@ def pytest_ignore_collect(
|
|||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
|
||||
:param collection_path: The path to analyze.
|
||||
:type collection_path: pathlib.Path
|
||||
:param path: The path to analyze (deprecated).
|
||||
:param config: The pytest config object.
|
||||
|
||||
|
@ -342,7 +340,7 @@ def pytest_ignore_collect(
|
|||
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_collect_directory(path: Path, parent: "Collector") -> "Optional[Collector]":
|
||||
def pytest_collect_directory(path: Path, parent: Collector) -> Collector | None:
|
||||
"""Create a :class:`~pytest.Collector` for the given directory, or None if
|
||||
not relevant.
|
||||
|
||||
|
@ -356,6 +354,7 @@ def pytest_collect_directory(path: Path, parent: "Collector") -> "Optional[Colle
|
|||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
|
||||
:param path: The path to analyze.
|
||||
:type path: pathlib.Path
|
||||
|
||||
See :ref:`custom directory collectors` for a simple example of use of this
|
||||
hook.
|
||||
|
@ -378,8 +377,8 @@ def pytest_collect_directory(path: Path, parent: "Collector") -> "Optional[Colle
|
|||
},
|
||||
)
|
||||
def pytest_collect_file(
|
||||
file_path: Path, path: "LEGACY_PATH", parent: "Collector"
|
||||
) -> "Optional[Collector]":
|
||||
file_path: Path, path: LEGACY_PATH, parent: Collector
|
||||
) -> Collector | None:
|
||||
"""Create a :class:`~pytest.Collector` for the given path, or None if not relevant.
|
||||
|
||||
For best results, the returned collector should be a subclass of
|
||||
|
@ -388,6 +387,7 @@ def pytest_collect_file(
|
|||
The new node needs to have the specified ``parent`` as a parent.
|
||||
|
||||
:param file_path: The path to analyze.
|
||||
:type file_path: pathlib.Path
|
||||
:param path: The path to collect (deprecated).
|
||||
|
||||
.. versionchanged:: 7.0.0
|
||||
|
@ -406,7 +406,7 @@ def pytest_collect_file(
|
|||
# logging hooks for collection
|
||||
|
||||
|
||||
def pytest_collectstart(collector: "Collector") -> None:
|
||||
def pytest_collectstart(collector: Collector) -> None:
|
||||
"""Collector starts collecting.
|
||||
|
||||
:param collector:
|
||||
|
@ -421,7 +421,7 @@ def pytest_collectstart(collector: "Collector") -> None:
|
|||
"""
|
||||
|
||||
|
||||
def pytest_itemcollected(item: "Item") -> None:
|
||||
def pytest_itemcollected(item: Item) -> None:
|
||||
"""We just collected a test item.
|
||||
|
||||
:param item:
|
||||
|
@ -435,7 +435,7 @@ def pytest_itemcollected(item: "Item") -> None:
|
|||
"""
|
||||
|
||||
|
||||
def pytest_collectreport(report: "CollectReport") -> None:
|
||||
def pytest_collectreport(report: CollectReport) -> None:
|
||||
"""Collector finished collecting.
|
||||
|
||||
:param report:
|
||||
|
@ -450,7 +450,7 @@ def pytest_collectreport(report: "CollectReport") -> None:
|
|||
"""
|
||||
|
||||
|
||||
def pytest_deselected(items: Sequence["Item"]) -> None:
|
||||
def pytest_deselected(items: Sequence[Item]) -> None:
|
||||
"""Called for deselected test items, e.g. by keyword.
|
||||
|
||||
May be called multiple times.
|
||||
|
@ -466,7 +466,7 @@ def pytest_deselected(items: Sequence["Item"]) -> None:
|
|||
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectReport]":
|
||||
def pytest_make_collect_report(collector: Collector) -> CollectReport | None:
|
||||
"""Perform :func:`collector.collect() <pytest.Collector.collect>` and return
|
||||
a :class:`~pytest.CollectReport`.
|
||||
|
||||
|
@ -498,8 +498,8 @@ def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectRepor
|
|||
},
|
||||
)
|
||||
def pytest_pycollect_makemodule(
|
||||
module_path: Path, path: "LEGACY_PATH", parent
|
||||
) -> Optional["Module"]:
|
||||
module_path: Path, path: LEGACY_PATH, parent
|
||||
) -> Module | None:
|
||||
"""Return a :class:`pytest.Module` collector or None for the given path.
|
||||
|
||||
This hook will be called for each matching test module path.
|
||||
|
@ -509,6 +509,7 @@ def pytest_pycollect_makemodule(
|
|||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
|
||||
:param module_path: The path of the module to collect.
|
||||
:type module_path: pathlib.Path
|
||||
:param path: The path of the module to collect (deprecated).
|
||||
|
||||
.. versionchanged:: 7.0.0
|
||||
|
@ -528,8 +529,8 @@ def pytest_pycollect_makemodule(
|
|||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_pycollect_makeitem(
|
||||
collector: Union["Module", "Class"], name: str, obj: object
|
||||
) -> Union[None, "Item", "Collector", List[Union["Item", "Collector"]]]:
|
||||
collector: Module | Class, name: str, obj: object
|
||||
) -> None | Item | Collector | list[Item | Collector]:
|
||||
"""Return a custom item/collector for a Python object in a module, or None.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
|
@ -553,7 +554,7 @@ def pytest_pycollect_makeitem(
|
|||
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]:
|
||||
def pytest_pyfunc_call(pyfuncitem: Function) -> object | None:
|
||||
"""Call underlying test function.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
|
@ -570,7 +571,7 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]:
|
|||
"""
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc: "Metafunc") -> None:
|
||||
def pytest_generate_tests(metafunc: Metafunc) -> None:
|
||||
"""Generate (multiple) parametrized calls to a test function.
|
||||
|
||||
:param metafunc:
|
||||
|
@ -586,9 +587,7 @@ def pytest_generate_tests(metafunc: "Metafunc") -> None:
|
|||
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_make_parametrize_id(
|
||||
config: "Config", val: object, argname: str
|
||||
) -> Optional[str]:
|
||||
def pytest_make_parametrize_id(config: Config, val: object, argname: str) -> str | None:
|
||||
"""Return a user-friendly string representation of the given ``val``
|
||||
that will be used by @pytest.mark.parametrize calls, or None if the hook
|
||||
doesn't know about ``val``.
|
||||
|
@ -614,7 +613,7 @@ def pytest_make_parametrize_id(
|
|||
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_runtestloop(session: "Session") -> Optional[object]:
|
||||
def pytest_runtestloop(session: Session) -> object | None:
|
||||
"""Perform the main runtest loop (after collection finished).
|
||||
|
||||
The default hook implementation performs the runtest protocol for all items
|
||||
|
@ -640,9 +639,7 @@ def pytest_runtestloop(session: "Session") -> Optional[object]:
|
|||
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_runtest_protocol(
|
||||
item: "Item", nextitem: "Optional[Item]"
|
||||
) -> Optional[object]:
|
||||
def pytest_runtest_protocol(item: Item, nextitem: Item | None) -> object | None:
|
||||
"""Perform the runtest protocol for a single test item.
|
||||
|
||||
The default runtest protocol is this (see individual hooks for full details):
|
||||
|
@ -682,9 +679,7 @@ def pytest_runtest_protocol(
|
|||
"""
|
||||
|
||||
|
||||
def pytest_runtest_logstart(
|
||||
nodeid: str, location: Tuple[str, Optional[int], str]
|
||||
) -> None:
|
||||
def pytest_runtest_logstart(nodeid: str, location: tuple[str, int | None, str]) -> None:
|
||||
"""Called at the start of running the runtest protocol for a single item.
|
||||
|
||||
See :hook:`pytest_runtest_protocol` for a description of the runtest protocol.
|
||||
|
@ -703,7 +698,7 @@ def pytest_runtest_logstart(
|
|||
|
||||
|
||||
def pytest_runtest_logfinish(
|
||||
nodeid: str, location: Tuple[str, Optional[int], str]
|
||||
nodeid: str, location: tuple[str, int | None, str]
|
||||
) -> None:
|
||||
"""Called at the end of running the runtest protocol for a single item.
|
||||
|
||||
|
@ -722,7 +717,7 @@ def pytest_runtest_logfinish(
|
|||
"""
|
||||
|
||||
|
||||
def pytest_runtest_setup(item: "Item") -> None:
|
||||
def pytest_runtest_setup(item: Item) -> None:
|
||||
"""Called to perform the setup phase for a test item.
|
||||
|
||||
The default implementation runs ``setup()`` on ``item`` and all of its
|
||||
|
@ -741,7 +736,7 @@ def pytest_runtest_setup(item: "Item") -> None:
|
|||
"""
|
||||
|
||||
|
||||
def pytest_runtest_call(item: "Item") -> None:
|
||||
def pytest_runtest_call(item: Item) -> None:
|
||||
"""Called to run the test for test item (the call phase).
|
||||
|
||||
The default implementation calls ``item.runtest()``.
|
||||
|
@ -757,7 +752,7 @@ def pytest_runtest_call(item: "Item") -> None:
|
|||
"""
|
||||
|
||||
|
||||
def pytest_runtest_teardown(item: "Item", nextitem: Optional["Item"]) -> None:
|
||||
def pytest_runtest_teardown(item: Item, nextitem: Item | None) -> None:
|
||||
"""Called to perform the teardown phase for a test item.
|
||||
|
||||
The default implementation runs the finalizers and calls ``teardown()``
|
||||
|
@ -782,9 +777,7 @@ def pytest_runtest_teardown(item: "Item", nextitem: Optional["Item"]) -> None:
|
|||
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_runtest_makereport(
|
||||
item: "Item", call: "CallInfo[None]"
|
||||
) -> Optional["TestReport"]:
|
||||
def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport | None:
|
||||
"""Called to create a :class:`~pytest.TestReport` for each of
|
||||
the setup, call and teardown runtest phases of a test item.
|
||||
|
||||
|
@ -803,7 +796,7 @@ def pytest_runtest_makereport(
|
|||
"""
|
||||
|
||||
|
||||
def pytest_runtest_logreport(report: "TestReport") -> None:
|
||||
def pytest_runtest_logreport(report: TestReport) -> None:
|
||||
"""Process the :class:`~pytest.TestReport` produced for each
|
||||
of the setup, call and teardown runtest phases of an item.
|
||||
|
||||
|
@ -819,9 +812,9 @@ def pytest_runtest_logreport(report: "TestReport") -> None:
|
|||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_report_to_serializable(
|
||||
config: "Config",
|
||||
report: Union["CollectReport", "TestReport"],
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
config: Config,
|
||||
report: CollectReport | TestReport,
|
||||
) -> dict[str, Any] | None:
|
||||
"""Serialize the given report object into a data structure suitable for
|
||||
sending over the wire, e.g. converted to JSON.
|
||||
|
||||
|
@ -838,9 +831,9 @@ def pytest_report_to_serializable(
|
|||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_report_from_serializable(
|
||||
config: "Config",
|
||||
data: Dict[str, Any],
|
||||
) -> Optional[Union["CollectReport", "TestReport"]]:
|
||||
config: Config,
|
||||
data: dict[str, Any],
|
||||
) -> CollectReport | TestReport | None:
|
||||
"""Restore a report object previously serialized with
|
||||
:hook:`pytest_report_to_serializable`.
|
||||
|
||||
|
@ -861,8 +854,8 @@ def pytest_report_from_serializable(
|
|||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_fixture_setup(
|
||||
fixturedef: "FixtureDef[Any]", request: "SubRequest"
|
||||
) -> Optional[object]:
|
||||
fixturedef: FixtureDef[Any], request: SubRequest
|
||||
) -> object | None:
|
||||
"""Perform fixture setup execution.
|
||||
|
||||
:param fixturedef:
|
||||
|
@ -889,7 +882,7 @@ def pytest_fixture_setup(
|
|||
|
||||
|
||||
def pytest_fixture_post_finalizer(
|
||||
fixturedef: "FixtureDef[Any]", request: "SubRequest"
|
||||
fixturedef: FixtureDef[Any], request: SubRequest
|
||||
) -> None:
|
||||
"""Called after fixture teardown, but before the cache is cleared, so
|
||||
the fixture result ``fixturedef.cached_result`` is still available (not
|
||||
|
@ -914,7 +907,7 @@ def pytest_fixture_post_finalizer(
|
|||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
def pytest_sessionstart(session: "Session") -> None:
|
||||
def pytest_sessionstart(session: Session) -> None:
|
||||
"""Called after the ``Session`` object has been created and before performing collection
|
||||
and entering the run test loop.
|
||||
|
||||
|
@ -928,8 +921,8 @@ def pytest_sessionstart(session: "Session") -> None:
|
|||
|
||||
|
||||
def pytest_sessionfinish(
|
||||
session: "Session",
|
||||
exitstatus: Union[int, "ExitCode"],
|
||||
session: Session,
|
||||
exitstatus: int | ExitCode,
|
||||
) -> None:
|
||||
"""Called after whole test run finished, right before returning the exit status to the system.
|
||||
|
||||
|
@ -943,7 +936,7 @@ def pytest_sessionfinish(
|
|||
"""
|
||||
|
||||
|
||||
def pytest_unconfigure(config: "Config") -> None:
|
||||
def pytest_unconfigure(config: Config) -> None:
|
||||
"""Called before test process is exited.
|
||||
|
||||
:param config: The pytest config object.
|
||||
|
@ -961,8 +954,8 @@ def pytest_unconfigure(config: "Config") -> None:
|
|||
|
||||
|
||||
def pytest_assertrepr_compare(
|
||||
config: "Config", op: str, left: object, right: object
|
||||
) -> Optional[List[str]]:
|
||||
config: Config, op: str, left: object, right: object
|
||||
) -> list[str] | None:
|
||||
"""Return explanation for comparisons in failing assert expressions.
|
||||
|
||||
Return None for no custom explanation, otherwise return a list
|
||||
|
@ -983,7 +976,7 @@ def pytest_assertrepr_compare(
|
|||
"""
|
||||
|
||||
|
||||
def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> None:
|
||||
def pytest_assertion_pass(item: Item, lineno: int, orig: str, expl: str) -> None:
|
||||
"""Called whenever an assertion passes.
|
||||
|
||||
.. versionadded:: 5.0
|
||||
|
@ -1030,12 +1023,13 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No
|
|||
},
|
||||
)
|
||||
def pytest_report_header( # type:ignore[empty-body]
|
||||
config: "Config", start_path: Path, startdir: "LEGACY_PATH"
|
||||
) -> Union[str, List[str]]:
|
||||
config: Config, start_path: Path, startdir: LEGACY_PATH
|
||||
) -> str | list[str]:
|
||||
"""Return a string or list of strings to be displayed as header info for terminal reporting.
|
||||
|
||||
:param config: The pytest config object.
|
||||
:param start_path: The starting dir.
|
||||
:type start_path: pathlib.Path
|
||||
:param startdir: The starting dir (deprecated).
|
||||
|
||||
.. note::
|
||||
|
@ -1065,11 +1059,11 @@ def pytest_report_header( # type:ignore[empty-body]
|
|||
},
|
||||
)
|
||||
def pytest_report_collectionfinish( # type:ignore[empty-body]
|
||||
config: "Config",
|
||||
config: Config,
|
||||
start_path: Path,
|
||||
startdir: "LEGACY_PATH",
|
||||
items: Sequence["Item"],
|
||||
) -> Union[str, List[str]]:
|
||||
startdir: LEGACY_PATH,
|
||||
items: Sequence[Item],
|
||||
) -> str | list[str]:
|
||||
"""Return a string or list of strings to be displayed after collection
|
||||
has finished successfully.
|
||||
|
||||
|
@ -1079,6 +1073,7 @@ def pytest_report_collectionfinish( # type:ignore[empty-body]
|
|||
|
||||
:param config: The pytest config object.
|
||||
:param start_path: The starting dir.
|
||||
:type start_path: pathlib.Path
|
||||
:param startdir: The starting dir (deprecated).
|
||||
:param items: List of pytest items that are going to be executed; this list should not be modified.
|
||||
|
||||
|
@ -1103,8 +1098,8 @@ def pytest_report_collectionfinish( # type:ignore[empty-body]
|
|||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_report_teststatus( # type:ignore[empty-body]
|
||||
report: Union["CollectReport", "TestReport"], config: "Config"
|
||||
) -> "TestShortLogReport | Tuple[str, str, Union[str, Tuple[str, Mapping[str, bool]]]]":
|
||||
report: CollectReport | TestReport, config: Config
|
||||
) -> TestShortLogReport | tuple[str, str, str | tuple[str, Mapping[str, bool]]]:
|
||||
"""Return result-category, shortletter and verbose word for status
|
||||
reporting.
|
||||
|
||||
|
@ -1135,9 +1130,9 @@ def pytest_report_teststatus( # type:ignore[empty-body]
|
|||
|
||||
|
||||
def pytest_terminal_summary(
|
||||
terminalreporter: "TerminalReporter",
|
||||
exitstatus: "ExitCode",
|
||||
config: "Config",
|
||||
terminalreporter: TerminalReporter,
|
||||
exitstatus: ExitCode,
|
||||
config: Config,
|
||||
) -> None:
|
||||
"""Add a section to terminal summary reporting.
|
||||
|
||||
|
@ -1157,10 +1152,10 @@ def pytest_terminal_summary(
|
|||
|
||||
@hookspec(historic=True)
|
||||
def pytest_warning_recorded(
|
||||
warning_message: "warnings.WarningMessage",
|
||||
when: "Literal['config', 'collect', 'runtest']",
|
||||
warning_message: warnings.WarningMessage,
|
||||
when: Literal["config", "collect", "runtest"],
|
||||
nodeid: str,
|
||||
location: Optional[Tuple[str, int, str]],
|
||||
location: tuple[str, int, str] | None,
|
||||
) -> None:
|
||||
"""Process a warning captured by the internal pytest warnings plugin.
|
||||
|
||||
|
@ -1201,8 +1196,8 @@ def pytest_warning_recorded(
|
|||
|
||||
|
||||
def pytest_markeval_namespace( # type:ignore[empty-body]
|
||||
config: "Config",
|
||||
) -> Dict[str, Any]:
|
||||
config: Config,
|
||||
) -> dict[str, Any]:
|
||||
"""Called when constructing the globals dictionary used for
|
||||
evaluating string conditions in xfail/skipif markers.
|
||||
|
||||
|
@ -1230,9 +1225,9 @@ def pytest_markeval_namespace( # type:ignore[empty-body]
|
|||
|
||||
|
||||
def pytest_internalerror(
|
||||
excrepr: "ExceptionRepr",
|
||||
excinfo: "ExceptionInfo[BaseException]",
|
||||
) -> Optional[bool]:
|
||||
excrepr: ExceptionRepr,
|
||||
excinfo: ExceptionInfo[BaseException],
|
||||
) -> bool | None:
|
||||
"""Called for internal errors.
|
||||
|
||||
Return True to suppress the fallback handling of printing an
|
||||
|
@ -1249,7 +1244,7 @@ def pytest_internalerror(
|
|||
|
||||
|
||||
def pytest_keyboard_interrupt(
|
||||
excinfo: "ExceptionInfo[Union[KeyboardInterrupt, Exit]]",
|
||||
excinfo: ExceptionInfo[KeyboardInterrupt | Exit],
|
||||
) -> None:
|
||||
"""Called for keyboard interrupt.
|
||||
|
||||
|
@ -1263,9 +1258,9 @@ def pytest_keyboard_interrupt(
|
|||
|
||||
|
||||
def pytest_exception_interact(
|
||||
node: Union["Item", "Collector"],
|
||||
call: "CallInfo[Any]",
|
||||
report: Union["CollectReport", "TestReport"],
|
||||
node: Item | Collector,
|
||||
call: CallInfo[Any],
|
||||
report: CollectReport | TestReport,
|
||||
) -> None:
|
||||
"""Called when an exception was raised which can potentially be
|
||||
interactively handled.
|
||||
|
@ -1294,7 +1289,7 @@ def pytest_exception_interact(
|
|||
"""
|
||||
|
||||
|
||||
def pytest_enter_pdb(config: "Config", pdb: "pdb.Pdb") -> None:
|
||||
def pytest_enter_pdb(config: Config, pdb: pdb.Pdb) -> None:
|
||||
"""Called upon pdb.set_trace().
|
||||
|
||||
Can be used by plugins to take special action just before the python
|
||||
|
@ -1310,7 +1305,7 @@ def pytest_enter_pdb(config: "Config", pdb: "pdb.Pdb") -> None:
|
|||
"""
|
||||
|
||||
|
||||
def pytest_leave_pdb(config: "Config", pdb: "pdb.Pdb") -> None:
|
||||
def pytest_leave_pdb(config: Config, pdb: pdb.Pdb) -> None:
|
||||
"""Called when leaving pdb (e.g. with continue after pdb.set_trace()).
|
||||
|
||||
Can be used by plugins to take special action just after the python
|
||||
|
|
|
@ -8,18 +8,15 @@ Output conforms to
|
|||
https://github.com/jenkinsci/xunit-plugin/blob/master/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
import functools
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Match
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from _pytest import nodes
|
||||
|
@ -89,15 +86,15 @@ families["xunit2"] = families["_base"]
|
|||
|
||||
|
||||
class _NodeReporter:
|
||||
def __init__(self, nodeid: Union[str, TestReport], xml: "LogXML") -> None:
|
||||
def __init__(self, nodeid: str | TestReport, xml: LogXML) -> None:
|
||||
self.id = nodeid
|
||||
self.xml = xml
|
||||
self.add_stats = self.xml.add_stats
|
||||
self.family = self.xml.family
|
||||
self.duration = 0.0
|
||||
self.properties: List[Tuple[str, str]] = []
|
||||
self.nodes: List[ET.Element] = []
|
||||
self.attrs: Dict[str, str] = {}
|
||||
self.properties: list[tuple[str, str]] = []
|
||||
self.nodes: list[ET.Element] = []
|
||||
self.attrs: dict[str, str] = {}
|
||||
|
||||
def append(self, node: ET.Element) -> None:
|
||||
self.xml.add_stats(node.tag)
|
||||
|
@ -109,7 +106,7 @@ class _NodeReporter:
|
|||
def add_attribute(self, name: str, value: object) -> None:
|
||||
self.attrs[str(name)] = bin_xml_escape(value)
|
||||
|
||||
def make_properties_node(self) -> Optional[ET.Element]:
|
||||
def make_properties_node(self) -> ET.Element | None:
|
||||
"""Return a Junit node containing custom properties, if any."""
|
||||
if self.properties:
|
||||
properties = ET.Element("properties")
|
||||
|
@ -124,7 +121,7 @@ class _NodeReporter:
|
|||
classnames = names[:-1]
|
||||
if self.xml.prefix:
|
||||
classnames.insert(0, self.xml.prefix)
|
||||
attrs: Dict[str, str] = {
|
||||
attrs: dict[str, str] = {
|
||||
"classname": ".".join(classnames),
|
||||
"name": bin_xml_escape(names[-1]),
|
||||
"file": testreport.location[0],
|
||||
|
@ -156,7 +153,7 @@ class _NodeReporter:
|
|||
testcase.extend(self.nodes)
|
||||
return testcase
|
||||
|
||||
def _add_simple(self, tag: str, message: str, data: Optional[str] = None) -> None:
|
||||
def _add_simple(self, tag: str, message: str, data: str | None = None) -> None:
|
||||
node = ET.Element(tag, message=message)
|
||||
node.text = bin_xml_escape(data)
|
||||
self.append(node)
|
||||
|
@ -201,7 +198,7 @@ class _NodeReporter:
|
|||
self._add_simple("skipped", "xfail-marked test passes unexpectedly")
|
||||
else:
|
||||
assert report.longrepr is not None
|
||||
reprcrash: Optional[ReprFileLocation] = getattr(
|
||||
reprcrash: ReprFileLocation | None = getattr(
|
||||
report.longrepr, "reprcrash", None
|
||||
)
|
||||
if reprcrash is not None:
|
||||
|
@ -221,9 +218,7 @@ class _NodeReporter:
|
|||
|
||||
def append_error(self, report: TestReport) -> None:
|
||||
assert report.longrepr is not None
|
||||
reprcrash: Optional[ReprFileLocation] = getattr(
|
||||
report.longrepr, "reprcrash", None
|
||||
)
|
||||
reprcrash: ReprFileLocation | None = getattr(report.longrepr, "reprcrash", None)
|
||||
if reprcrash is not None:
|
||||
reason = reprcrash.message
|
||||
else:
|
||||
|
@ -451,7 +446,7 @@ def pytest_unconfigure(config: Config) -> None:
|
|||
config.pluginmanager.unregister(xml)
|
||||
|
||||
|
||||
def mangle_test_address(address: str) -> List[str]:
|
||||
def mangle_test_address(address: str) -> list[str]:
|
||||
path, possible_open_bracket, params = address.partition("[")
|
||||
names = path.split("::")
|
||||
# Convert file path to dotted path.
|
||||
|
@ -466,7 +461,7 @@ class LogXML:
|
|||
def __init__(
|
||||
self,
|
||||
logfile,
|
||||
prefix: Optional[str],
|
||||
prefix: str | None,
|
||||
suite_name: str = "pytest",
|
||||
logging: str = "no",
|
||||
report_duration: str = "total",
|
||||
|
@ -481,17 +476,15 @@ class LogXML:
|
|||
self.log_passing_tests = log_passing_tests
|
||||
self.report_duration = report_duration
|
||||
self.family = family
|
||||
self.stats: Dict[str, int] = dict.fromkeys(
|
||||
self.stats: dict[str, int] = dict.fromkeys(
|
||||
["error", "passed", "failure", "skipped"], 0
|
||||
)
|
||||
self.node_reporters: Dict[
|
||||
Tuple[Union[str, TestReport], object], _NodeReporter
|
||||
] = {}
|
||||
self.node_reporters_ordered: List[_NodeReporter] = []
|
||||
self.global_properties: List[Tuple[str, str]] = []
|
||||
self.node_reporters: dict[tuple[str | TestReport, object], _NodeReporter] = {}
|
||||
self.node_reporters_ordered: list[_NodeReporter] = []
|
||||
self.global_properties: list[tuple[str, str]] = []
|
||||
|
||||
# List of reports that failed on call but teardown is pending.
|
||||
self.open_reports: List[TestReport] = []
|
||||
self.open_reports: list[TestReport] = []
|
||||
self.cnt_double_fail_tests = 0
|
||||
|
||||
# Replaces convenience family with real family.
|
||||
|
@ -510,8 +503,8 @@ class LogXML:
|
|||
if reporter is not None:
|
||||
reporter.finalize()
|
||||
|
||||
def node_reporter(self, report: Union[TestReport, str]) -> _NodeReporter:
|
||||
nodeid: Union[str, TestReport] = getattr(report, "nodeid", report)
|
||||
def node_reporter(self, report: TestReport | str) -> _NodeReporter:
|
||||
nodeid: str | TestReport = getattr(report, "nodeid", report)
|
||||
# Local hack to handle xdist report order.
|
||||
workernode = getattr(report, "node", None)
|
||||
|
||||
|
@ -691,7 +684,7 @@ class LogXML:
|
|||
_check_record_param_type("name", name)
|
||||
self.global_properties.append((name, bin_xml_escape(value)))
|
||||
|
||||
def _get_global_properties_node(self) -> Optional[ET.Element]:
|
||||
def _get_global_properties_node(self) -> ET.Element | None:
|
||||
"""Return a Junit node containing custom properties, if any."""
|
||||
if self.global_properties:
|
||||
properties = ET.Element("properties")
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Add backward compatibility support for the legacy py path type."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
from pathlib import Path
|
||||
import shlex
|
||||
import subprocess
|
||||
from typing import Final
|
||||
from typing import final
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from iniconfig import SectionWrapper
|
||||
|
||||
|
@ -50,8 +49,8 @@ class Testdir:
|
|||
|
||||
__test__ = False
|
||||
|
||||
CLOSE_STDIN: "Final" = Pytester.CLOSE_STDIN
|
||||
TimeoutExpired: "Final" = Pytester.TimeoutExpired
|
||||
CLOSE_STDIN: Final = Pytester.CLOSE_STDIN
|
||||
TimeoutExpired: Final = Pytester.TimeoutExpired
|
||||
|
||||
def __init__(self, pytester: Pytester, *, _ispytest: bool = False) -> None:
|
||||
check_ispytest(_ispytest)
|
||||
|
@ -145,7 +144,7 @@ class Testdir:
|
|||
"""See :meth:`Pytester.copy_example`."""
|
||||
return legacy_path(self._pytester.copy_example(name))
|
||||
|
||||
def getnode(self, config: Config, arg) -> Optional[Union[Item, Collector]]:
|
||||
def getnode(self, config: Config, arg) -> Item | Collector | None:
|
||||
"""See :meth:`Pytester.getnode`."""
|
||||
return self._pytester.getnode(config, arg)
|
||||
|
||||
|
@ -153,7 +152,7 @@ class Testdir:
|
|||
"""See :meth:`Pytester.getpathnode`."""
|
||||
return self._pytester.getpathnode(path)
|
||||
|
||||
def genitems(self, colitems: List[Union[Item, Collector]]) -> List[Item]:
|
||||
def genitems(self, colitems: list[Item | Collector]) -> list[Item]:
|
||||
"""See :meth:`Pytester.genitems`."""
|
||||
return self._pytester.genitems(colitems)
|
||||
|
||||
|
@ -205,9 +204,7 @@ class Testdir:
|
|||
source, configargs=configargs, withinit=withinit
|
||||
)
|
||||
|
||||
def collect_by_name(
|
||||
self, modcol: Collector, name: str
|
||||
) -> Optional[Union[Item, Collector]]:
|
||||
def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None:
|
||||
"""See :meth:`Pytester.collect_by_name`."""
|
||||
return self._pytester.collect_by_name(modcol, name)
|
||||
|
||||
|
@ -238,13 +235,11 @@ class Testdir:
|
|||
"""See :meth:`Pytester.runpytest_subprocess`."""
|
||||
return self._pytester.runpytest_subprocess(*args, timeout=timeout)
|
||||
|
||||
def spawn_pytest(
|
||||
self, string: str, expect_timeout: float = 10.0
|
||||
) -> "pexpect.spawn":
|
||||
def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn:
|
||||
"""See :meth:`Pytester.spawn_pytest`."""
|
||||
return self._pytester.spawn_pytest(string, expect_timeout=expect_timeout)
|
||||
|
||||
def spawn(self, cmd: str, expect_timeout: float = 10.0) -> "pexpect.spawn":
|
||||
def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn:
|
||||
"""See :meth:`Pytester.spawn`."""
|
||||
return self._pytester.spawn(cmd, expect_timeout=expect_timeout)
|
||||
|
||||
|
@ -374,7 +369,7 @@ def Config_rootdir(self: Config) -> LEGACY_PATH:
|
|||
return legacy_path(str(self.rootpath))
|
||||
|
||||
|
||||
def Config_inifile(self: Config) -> Optional[LEGACY_PATH]:
|
||||
def Config_inifile(self: Config) -> LEGACY_PATH | None:
|
||||
"""The path to the :ref:`configfile <configfiles>`.
|
||||
|
||||
Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`.
|
||||
|
@ -394,9 +389,7 @@ def Session_startdir(self: Session) -> LEGACY_PATH:
|
|||
return legacy_path(self.startpath)
|
||||
|
||||
|
||||
def Config__getini_unknown_type(
|
||||
self, name: str, type: str, value: Union[str, List[str]]
|
||||
):
|
||||
def Config__getini_unknown_type(self, name: str, type: str, value: str | list[str]):
|
||||
if type == "pathlist":
|
||||
# TODO: This assert is probably not valid in all cases.
|
||||
assert self.inipath is not None
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Access and control log capturing."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager
|
||||
from contextlib import nullcontext
|
||||
from datetime import datetime
|
||||
|
@ -22,12 +24,8 @@ from typing import Generic
|
|||
from typing import List
|
||||
from typing import Literal
|
||||
from typing import Mapping
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
from _pytest import nodes
|
||||
from _pytest._io import TerminalWriter
|
||||
|
@ -68,7 +66,7 @@ class DatetimeFormatter(logging.Formatter):
|
|||
:func:`time.strftime` in case of microseconds in format string.
|
||||
"""
|
||||
|
||||
def formatTime(self, record: LogRecord, datefmt: Optional[str] = None) -> str:
|
||||
def formatTime(self, record: LogRecord, datefmt: str | None = None) -> str:
|
||||
if datefmt and "%f" in datefmt:
|
||||
ct = self.converter(record.created)
|
||||
tz = timezone(timedelta(seconds=ct.tm_gmtoff), ct.tm_zone)
|
||||
|
@ -100,7 +98,7 @@ class ColoredLevelFormatter(DatetimeFormatter):
|
|||
super().__init__(*args, **kwargs)
|
||||
self._terminalwriter = terminalwriter
|
||||
self._original_fmt = self._style._fmt
|
||||
self._level_to_fmt_mapping: Dict[int, str] = {}
|
||||
self._level_to_fmt_mapping: dict[int, str] = {}
|
||||
|
||||
for level, color_opts in self.LOGLEVEL_COLOROPTS.items():
|
||||
self.add_color_level(level, *color_opts)
|
||||
|
@ -148,12 +146,12 @@ class PercentStyleMultiline(logging.PercentStyle):
|
|||
formats the message as if each line were logged separately.
|
||||
"""
|
||||
|
||||
def __init__(self, fmt: str, auto_indent: Union[int, str, bool, None]) -> None:
|
||||
def __init__(self, fmt: str, auto_indent: int | str | bool | None) -> None:
|
||||
super().__init__(fmt)
|
||||
self._auto_indent = self._get_auto_indent(auto_indent)
|
||||
|
||||
@staticmethod
|
||||
def _get_auto_indent(auto_indent_option: Union[int, str, bool, None]) -> int:
|
||||
def _get_auto_indent(auto_indent_option: int | str | bool | None) -> int:
|
||||
"""Determine the current auto indentation setting.
|
||||
|
||||
Specify auto indent behavior (on/off/fixed) by passing in
|
||||
|
@ -348,7 +346,7 @@ class catching_logs(Generic[_HandlerType]):
|
|||
|
||||
__slots__ = ("handler", "level", "orig_level")
|
||||
|
||||
def __init__(self, handler: _HandlerType, level: Optional[int] = None) -> None:
|
||||
def __init__(self, handler: _HandlerType, level: int | None = None) -> None:
|
||||
self.handler = handler
|
||||
self.level = level
|
||||
|
||||
|
@ -364,9 +362,9 @@ class catching_logs(Generic[_HandlerType]):
|
|||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc_val: Optional[BaseException],
|
||||
exc_tb: Optional[TracebackType],
|
||||
exc_type: type[BaseException] | None,
|
||||
exc_val: BaseException | None,
|
||||
exc_tb: TracebackType | None,
|
||||
) -> None:
|
||||
root_logger = logging.getLogger()
|
||||
if self.level is not None:
|
||||
|
@ -380,7 +378,7 @@ class LogCaptureHandler(logging_StreamHandler):
|
|||
def __init__(self) -> None:
|
||||
"""Create a new log handler."""
|
||||
super().__init__(StringIO())
|
||||
self.records: List[logging.LogRecord] = []
|
||||
self.records: list[logging.LogRecord] = []
|
||||
|
||||
def emit(self, record: logging.LogRecord) -> None:
|
||||
"""Keep the log records in a list in addition to the log text."""
|
||||
|
@ -411,10 +409,10 @@ class LogCaptureFixture:
|
|||
def __init__(self, item: nodes.Node, *, _ispytest: bool = False) -> None:
|
||||
check_ispytest(_ispytest)
|
||||
self._item = item
|
||||
self._initial_handler_level: Optional[int] = None
|
||||
self._initial_handler_level: int | None = None
|
||||
# Dict of log name -> log level.
|
||||
self._initial_logger_levels: Dict[Optional[str], int] = {}
|
||||
self._initial_disabled_logging_level: Optional[int] = None
|
||||
self._initial_logger_levels: dict[str | None, int] = {}
|
||||
self._initial_disabled_logging_level: int | None = None
|
||||
|
||||
def _finalize(self) -> None:
|
||||
"""Finalize the fixture.
|
||||
|
@ -439,7 +437,7 @@ class LogCaptureFixture:
|
|||
|
||||
def get_records(
|
||||
self, when: Literal["setup", "call", "teardown"]
|
||||
) -> List[logging.LogRecord]:
|
||||
) -> list[logging.LogRecord]:
|
||||
"""Get the logging records for one of the possible test phases.
|
||||
|
||||
:param when:
|
||||
|
@ -458,12 +456,12 @@ class LogCaptureFixture:
|
|||
return _remove_ansi_escape_sequences(self.handler.stream.getvalue())
|
||||
|
||||
@property
|
||||
def records(self) -> List[logging.LogRecord]:
|
||||
def records(self) -> list[logging.LogRecord]:
|
||||
"""The list of log records."""
|
||||
return self.handler.records
|
||||
|
||||
@property
|
||||
def record_tuples(self) -> List[Tuple[str, int, str]]:
|
||||
def record_tuples(self) -> list[tuple[str, int, str]]:
|
||||
"""A list of a stripped down version of log records intended
|
||||
for use in assertion comparison.
|
||||
|
||||
|
@ -474,7 +472,7 @@ class LogCaptureFixture:
|
|||
return [(r.name, r.levelno, r.getMessage()) for r in self.records]
|
||||
|
||||
@property
|
||||
def messages(self) -> List[str]:
|
||||
def messages(self) -> list[str]:
|
||||
"""A list of format-interpolated log messages.
|
||||
|
||||
Unlike 'records', which contains the format string and parameters for
|
||||
|
@ -497,7 +495,7 @@ class LogCaptureFixture:
|
|||
self.handler.clear()
|
||||
|
||||
def _force_enable_logging(
|
||||
self, level: Union[int, str], logger_obj: logging.Logger
|
||||
self, level: int | str, logger_obj: logging.Logger
|
||||
) -> int:
|
||||
"""Enable the desired logging level if the global level was disabled via ``logging.disabled``.
|
||||
|
||||
|
@ -530,7 +528,7 @@ class LogCaptureFixture:
|
|||
|
||||
return original_disable_level
|
||||
|
||||
def set_level(self, level: Union[int, str], logger: Optional[str] = None) -> None:
|
||||
def set_level(self, level: int | str, logger: str | None = None) -> None:
|
||||
"""Set the threshold level of a logger for the duration of a test.
|
||||
|
||||
Logging messages which are less severe than this level will not be captured.
|
||||
|
@ -557,7 +555,7 @@ class LogCaptureFixture:
|
|||
|
||||
@contextmanager
|
||||
def at_level(
|
||||
self, level: Union[int, str], logger: Optional[str] = None
|
||||
self, level: int | str, logger: str | None = None
|
||||
) -> Generator[None, None, None]:
|
||||
"""Context manager that sets the level for capturing of logs. After
|
||||
the end of the 'with' statement the level is restored to its original
|
||||
|
@ -615,7 +613,7 @@ def caplog(request: FixtureRequest) -> Generator[LogCaptureFixture, None, None]:
|
|||
result._finalize()
|
||||
|
||||
|
||||
def get_log_level_for_setting(config: Config, *setting_names: str) -> Optional[int]:
|
||||
def get_log_level_for_setting(config: Config, *setting_names: str) -> int | None:
|
||||
for setting_name in setting_names:
|
||||
log_level = config.getoption(setting_name)
|
||||
if log_level is None:
|
||||
|
@ -701,9 +699,9 @@ class LoggingPlugin:
|
|||
assert terminal_reporter is not None
|
||||
capture_manager = config.pluginmanager.get_plugin("capturemanager")
|
||||
# if capturemanager plugin is disabled, live logging still works.
|
||||
self.log_cli_handler: Union[
|
||||
_LiveLoggingStreamHandler, _LiveLoggingNullHandler
|
||||
] = _LiveLoggingStreamHandler(terminal_reporter, capture_manager)
|
||||
self.log_cli_handler: (
|
||||
_LiveLoggingStreamHandler | _LiveLoggingNullHandler
|
||||
) = _LiveLoggingStreamHandler(terminal_reporter, capture_manager)
|
||||
else:
|
||||
self.log_cli_handler = _LiveLoggingNullHandler()
|
||||
log_cli_formatter = self._create_formatter(
|
||||
|
@ -714,7 +712,7 @@ class LoggingPlugin:
|
|||
self.log_cli_handler.setFormatter(log_cli_formatter)
|
||||
self._disable_loggers(loggers_to_disable=config.option.logger_disable)
|
||||
|
||||
def _disable_loggers(self, loggers_to_disable: List[str]) -> None:
|
||||
def _disable_loggers(self, loggers_to_disable: list[str]) -> None:
|
||||
if not loggers_to_disable:
|
||||
return
|
||||
|
||||
|
@ -839,7 +837,7 @@ class LoggingPlugin:
|
|||
def pytest_runtest_setup(self, item: nodes.Item) -> Generator[None, None, None]:
|
||||
self.log_cli_handler.set_when("setup")
|
||||
|
||||
empty: Dict[str, List[logging.LogRecord]] = {}
|
||||
empty: dict[str, list[logging.LogRecord]] = {}
|
||||
item.stash[caplog_records_key] = empty
|
||||
yield from self._runtest_for(item, "setup")
|
||||
|
||||
|
@ -902,7 +900,7 @@ class _LiveLoggingStreamHandler(logging_StreamHandler):
|
|||
def __init__(
|
||||
self,
|
||||
terminal_reporter: TerminalReporter,
|
||||
capture_manager: Optional[CaptureManager],
|
||||
capture_manager: CaptureManager | None,
|
||||
) -> None:
|
||||
super().__init__(stream=terminal_reporter) # type: ignore[arg-type]
|
||||
self.capture_manager = capture_manager
|
||||
|
@ -914,7 +912,7 @@ class _LiveLoggingStreamHandler(logging_StreamHandler):
|
|||
"""Reset the handler; should be called before the start of each test."""
|
||||
self._first_record_emitted = False
|
||||
|
||||
def set_when(self, when: Optional[str]) -> None:
|
||||
def set_when(self, when: str | None) -> None:
|
||||
"""Prepare for the given test phase (setup/call/teardown)."""
|
||||
self._when = when
|
||||
self._section_name_shown = False
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
"""Core implementation of the testing process: init, session, runtest loop."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import dataclasses
|
||||
import fnmatch
|
||||
|
@ -13,17 +15,12 @@ from typing import AbstractSet
|
|||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import final
|
||||
from typing import FrozenSet
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Literal
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
import pluggy
|
||||
|
@ -270,8 +267,8 @@ def validate_basetemp(path: str) -> str:
|
|||
|
||||
|
||||
def wrap_session(
|
||||
config: Config, doit: Callable[[Config, "Session"], Optional[Union[int, ExitCode]]]
|
||||
) -> Union[int, ExitCode]:
|
||||
config: Config, doit: Callable[[Config, Session], int | ExitCode | None]
|
||||
) -> int | ExitCode:
|
||||
"""Skeleton command line program."""
|
||||
session = Session.from_config(config)
|
||||
session.exitstatus = ExitCode.OK
|
||||
|
@ -290,7 +287,7 @@ def wrap_session(
|
|||
session.exitstatus = ExitCode.TESTS_FAILED
|
||||
except (KeyboardInterrupt, exit.Exception):
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
exitstatus: Union[int, ExitCode] = ExitCode.INTERRUPTED
|
||||
exitstatus: int | ExitCode = ExitCode.INTERRUPTED
|
||||
if isinstance(excinfo.value, exit.Exception):
|
||||
if excinfo.value.returncode is not None:
|
||||
exitstatus = excinfo.value.returncode
|
||||
|
@ -328,11 +325,11 @@ def wrap_session(
|
|||
return session.exitstatus
|
||||
|
||||
|
||||
def pytest_cmdline_main(config: Config) -> Union[int, ExitCode]:
|
||||
def pytest_cmdline_main(config: Config) -> int | ExitCode:
|
||||
return wrap_session(config, _main)
|
||||
|
||||
|
||||
def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]:
|
||||
def _main(config: Config, session: Session) -> int | ExitCode | None:
|
||||
"""Default command line protocol for initialization, session,
|
||||
running tests and reporting."""
|
||||
config.hook.pytest_collection(session=session)
|
||||
|
@ -345,11 +342,11 @@ def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]:
|
|||
return None
|
||||
|
||||
|
||||
def pytest_collection(session: "Session") -> None:
|
||||
def pytest_collection(session: Session) -> None:
|
||||
session.perform_collect()
|
||||
|
||||
|
||||
def pytest_runtestloop(session: "Session") -> bool:
|
||||
def pytest_runtestloop(session: Session) -> bool:
|
||||
if session.testsfailed and not session.config.option.continue_on_collection_errors:
|
||||
raise session.Interrupted(
|
||||
"%d error%s during collection"
|
||||
|
@ -389,7 +386,7 @@ def _in_venv(path: Path) -> bool:
|
|||
return any(fname.name in activates for fname in bindir.iterdir())
|
||||
|
||||
|
||||
def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[bool]:
|
||||
def pytest_ignore_collect(collection_path: Path, config: Config) -> bool | None:
|
||||
if collection_path.name == "__pycache__":
|
||||
return True
|
||||
|
||||
|
@ -429,11 +426,11 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[boo
|
|||
|
||||
def pytest_collect_directory(
|
||||
path: Path, parent: nodes.Collector
|
||||
) -> Optional[nodes.Collector]:
|
||||
) -> nodes.Collector | None:
|
||||
return Dir.from_parent(parent, path=path)
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(items: List[nodes.Item], config: Config) -> None:
|
||||
def pytest_collection_modifyitems(items: list[nodes.Item], config: Config) -> None:
|
||||
deselect_prefixes = tuple(config.getoption("deselect") or [])
|
||||
if not deselect_prefixes:
|
||||
return
|
||||
|
@ -507,17 +504,18 @@ class Dir(nodes.Directory):
|
|||
parent: nodes.Collector,
|
||||
*,
|
||||
path: Path,
|
||||
) -> "Self":
|
||||
) -> Self:
|
||||
"""The public constructor.
|
||||
|
||||
:param parent: The parent collector of this Dir.
|
||||
:param path: The directory's path.
|
||||
:type path: pathlib.Path
|
||||
"""
|
||||
return super().from_parent(parent=parent, path=path)
|
||||
|
||||
def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
|
||||
def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
|
||||
config = self.config
|
||||
col: Optional[nodes.Collector]
|
||||
col: nodes.Collector | None
|
||||
cols: Sequence[nodes.Collector]
|
||||
ihook = self.ihook
|
||||
for direntry in scandir(self.path):
|
||||
|
@ -552,7 +550,7 @@ class Session(nodes.Collector):
|
|||
_setupstate: SetupState
|
||||
# Set on the session by fixtures.pytest_sessionstart.
|
||||
_fixturemanager: FixtureManager
|
||||
exitstatus: Union[int, ExitCode]
|
||||
exitstatus: int | ExitCode
|
||||
|
||||
def __init__(self, config: Config) -> None:
|
||||
super().__init__(
|
||||
|
@ -566,22 +564,22 @@ class Session(nodes.Collector):
|
|||
)
|
||||
self.testsfailed = 0
|
||||
self.testscollected = 0
|
||||
self._shouldstop: Union[bool, str] = False
|
||||
self._shouldfail: Union[bool, str] = False
|
||||
self._shouldstop: bool | str = False
|
||||
self._shouldfail: bool | str = False
|
||||
self.trace = config.trace.root.get("collection")
|
||||
self._initialpaths: FrozenSet[Path] = frozenset()
|
||||
self._initialpaths_with_parents: FrozenSet[Path] = frozenset()
|
||||
self._notfound: List[Tuple[str, Sequence[nodes.Collector]]] = []
|
||||
self._initial_parts: List[CollectionArgument] = []
|
||||
self._collection_cache: Dict[nodes.Collector, CollectReport] = {}
|
||||
self.items: List[nodes.Item] = []
|
||||
self._initialpaths: frozenset[Path] = frozenset()
|
||||
self._initialpaths_with_parents: frozenset[Path] = frozenset()
|
||||
self._notfound: list[tuple[str, Sequence[nodes.Collector]]] = []
|
||||
self._initial_parts: list[CollectionArgument] = []
|
||||
self._collection_cache: dict[nodes.Collector, CollectReport] = {}
|
||||
self.items: list[nodes.Item] = []
|
||||
|
||||
self._bestrelpathcache: Dict[Path, str] = _bestrelpath_cache(config.rootpath)
|
||||
self._bestrelpathcache: dict[Path, str] = _bestrelpath_cache(config.rootpath)
|
||||
|
||||
self.config.pluginmanager.register(self, name="session")
|
||||
|
||||
@classmethod
|
||||
def from_config(cls, config: Config) -> "Session":
|
||||
def from_config(cls, config: Config) -> Session:
|
||||
session: Session = cls._create(config=config)
|
||||
return session
|
||||
|
||||
|
@ -595,11 +593,11 @@ class Session(nodes.Collector):
|
|||
)
|
||||
|
||||
@property
|
||||
def shouldstop(self) -> Union[bool, str]:
|
||||
def shouldstop(self) -> bool | str:
|
||||
return self._shouldstop
|
||||
|
||||
@shouldstop.setter
|
||||
def shouldstop(self, value: Union[bool, str]) -> None:
|
||||
def shouldstop(self, value: bool | str) -> None:
|
||||
# The runner checks shouldfail and assumes that if it is set we are
|
||||
# definitely stopping, so prevent unsetting it.
|
||||
if value is False and self._shouldstop:
|
||||
|
@ -613,11 +611,11 @@ class Session(nodes.Collector):
|
|||
self._shouldstop = value
|
||||
|
||||
@property
|
||||
def shouldfail(self) -> Union[bool, str]:
|
||||
def shouldfail(self) -> bool | str:
|
||||
return self._shouldfail
|
||||
|
||||
@shouldfail.setter
|
||||
def shouldfail(self, value: Union[bool, str]) -> None:
|
||||
def shouldfail(self, value: bool | str) -> None:
|
||||
# The runner checks shouldfail and assumes that if it is set we are
|
||||
# definitely stopping, so prevent unsetting it.
|
||||
if value is False and self._shouldfail:
|
||||
|
@ -650,9 +648,7 @@ class Session(nodes.Collector):
|
|||
raise self.Interrupted(self.shouldstop)
|
||||
|
||||
@hookimpl(tryfirst=True)
|
||||
def pytest_runtest_logreport(
|
||||
self, report: Union[TestReport, CollectReport]
|
||||
) -> None:
|
||||
def pytest_runtest_logreport(self, report: TestReport | CollectReport) -> None:
|
||||
if report.failed and not hasattr(report, "wasxfail"):
|
||||
self.testsfailed += 1
|
||||
maxfail = self.config.getvalue("maxfail")
|
||||
|
@ -663,7 +659,7 @@ class Session(nodes.Collector):
|
|||
|
||||
def isinitpath(
|
||||
self,
|
||||
path: Union[str, "os.PathLike[str]"],
|
||||
path: str | os.PathLike[str],
|
||||
*,
|
||||
with_parents: bool = False,
|
||||
) -> bool:
|
||||
|
@ -685,7 +681,7 @@ class Session(nodes.Collector):
|
|||
else:
|
||||
return path_ in self._initialpaths
|
||||
|
||||
def gethookproxy(self, fspath: "os.PathLike[str]") -> pluggy.HookRelay:
|
||||
def gethookproxy(self, fspath: os.PathLike[str]) -> pluggy.HookRelay:
|
||||
# Optimization: Path(Path(...)) is much slower than isinstance.
|
||||
path = fspath if isinstance(fspath, Path) else Path(fspath)
|
||||
pm = self.config.pluginmanager
|
||||
|
@ -705,7 +701,7 @@ class Session(nodes.Collector):
|
|||
def _collect_path(
|
||||
self,
|
||||
path: Path,
|
||||
path_cache: Dict[Path, Sequence[nodes.Collector]],
|
||||
path_cache: dict[Path, Sequence[nodes.Collector]],
|
||||
) -> Sequence[nodes.Collector]:
|
||||
"""Create a Collector for the given path.
|
||||
|
||||
|
@ -717,7 +713,7 @@ class Session(nodes.Collector):
|
|||
|
||||
if path.is_dir():
|
||||
ihook = self.gethookproxy(path.parent)
|
||||
col: Optional[nodes.Collector] = ihook.pytest_collect_directory(
|
||||
col: nodes.Collector | None = ihook.pytest_collect_directory(
|
||||
path=path, parent=self
|
||||
)
|
||||
cols: Sequence[nodes.Collector] = (col,) if col is not None else ()
|
||||
|
@ -735,17 +731,17 @@ class Session(nodes.Collector):
|
|||
|
||||
@overload
|
||||
def perform_collect(
|
||||
self, args: Optional[Sequence[str]] = ..., genitems: "Literal[True]" = ...
|
||||
self, args: Sequence[str] | None = ..., genitems: Literal[True] = ...
|
||||
) -> Sequence[nodes.Item]: ...
|
||||
|
||||
@overload
|
||||
def perform_collect(
|
||||
self, args: Optional[Sequence[str]] = ..., genitems: bool = ...
|
||||
) -> Sequence[Union[nodes.Item, nodes.Collector]]: ...
|
||||
self, args: Sequence[str] | None = ..., genitems: bool = ...
|
||||
) -> Sequence[nodes.Item | nodes.Collector]: ...
|
||||
|
||||
def perform_collect(
|
||||
self, args: Optional[Sequence[str]] = None, genitems: bool = True
|
||||
) -> Sequence[Union[nodes.Item, nodes.Collector]]:
|
||||
self, args: Sequence[str] | None = None, genitems: bool = True
|
||||
) -> Sequence[nodes.Item | nodes.Collector]:
|
||||
"""Perform the collection phase for this session.
|
||||
|
||||
This is called by the default :hook:`pytest_collection` hook
|
||||
|
@ -771,10 +767,10 @@ class Session(nodes.Collector):
|
|||
self._initial_parts = []
|
||||
self._collection_cache = {}
|
||||
self.items = []
|
||||
items: Sequence[Union[nodes.Item, nodes.Collector]] = self.items
|
||||
items: Sequence[nodes.Item | nodes.Collector] = self.items
|
||||
try:
|
||||
initialpaths: List[Path] = []
|
||||
initialpaths_with_parents: List[Path] = []
|
||||
initialpaths: list[Path] = []
|
||||
initialpaths_with_parents: list[Path] = []
|
||||
for arg in args:
|
||||
collection_argument = resolve_collection_argument(
|
||||
self.config.invocation_params.dir,
|
||||
|
@ -829,7 +825,7 @@ class Session(nodes.Collector):
|
|||
self,
|
||||
node: nodes.Collector,
|
||||
handle_dupes: bool = True,
|
||||
) -> Tuple[CollectReport, bool]:
|
||||
) -> tuple[CollectReport, bool]:
|
||||
if node in self._collection_cache and handle_dupes:
|
||||
rep = self._collection_cache[node]
|
||||
return rep, True
|
||||
|
@ -838,11 +834,11 @@ class Session(nodes.Collector):
|
|||
self._collection_cache[node] = rep
|
||||
return rep, False
|
||||
|
||||
def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]:
|
||||
def collect(self) -> Iterator[nodes.Item | nodes.Collector]:
|
||||
# This is a cache for the root directories of the initial paths.
|
||||
# We can't use collection_cache for Session because of its special
|
||||
# role as the bootstrapping collector.
|
||||
path_cache: Dict[Path, Sequence[nodes.Collector]] = {}
|
||||
path_cache: dict[Path, Sequence[nodes.Collector]] = {}
|
||||
|
||||
pm = self.config.pluginmanager
|
||||
|
||||
|
@ -880,9 +876,9 @@ class Session(nodes.Collector):
|
|||
# and discarding all nodes which don't match the level's part.
|
||||
any_matched_in_initial_part = False
|
||||
notfound_collectors = []
|
||||
work: List[
|
||||
Tuple[Union[nodes.Collector, nodes.Item], List[Union[Path, str]]]
|
||||
] = [(self, [*paths, *names])]
|
||||
work: list[tuple[nodes.Collector | nodes.Item, list[Path | str]]] = [
|
||||
(self, [*paths, *names])
|
||||
]
|
||||
while work:
|
||||
matchnode, matchparts = work.pop()
|
||||
|
||||
|
@ -899,7 +895,7 @@ class Session(nodes.Collector):
|
|||
# Collect this level of matching.
|
||||
# Collecting Session (self) is done directly to avoid endless
|
||||
# recursion to this function.
|
||||
subnodes: Sequence[Union[nodes.Collector, nodes.Item]]
|
||||
subnodes: Sequence[nodes.Collector | nodes.Item]
|
||||
if isinstance(matchnode, Session):
|
||||
assert isinstance(matchparts[0], Path)
|
||||
subnodes = matchnode._collect_path(matchparts[0], path_cache)
|
||||
|
@ -959,9 +955,7 @@ class Session(nodes.Collector):
|
|||
|
||||
self.trace.root.indent -= 1
|
||||
|
||||
def genitems(
|
||||
self, node: Union[nodes.Item, nodes.Collector]
|
||||
) -> Iterator[nodes.Item]:
|
||||
def genitems(self, node: nodes.Item | nodes.Collector) -> Iterator[nodes.Item]:
|
||||
self.trace("genitems", node)
|
||||
if isinstance(node, nodes.Item):
|
||||
node.ihook.pytest_itemcollected(item=node)
|
||||
|
@ -981,7 +975,7 @@ class Session(nodes.Collector):
|
|||
node.ihook.pytest_collectreport(report=rep)
|
||||
|
||||
|
||||
def search_pypath(module_name: str) -> Optional[str]:
|
||||
def search_pypath(module_name: str) -> str | None:
|
||||
"""Search sys.path for the given a dotted module name, and return its file
|
||||
system path if found."""
|
||||
try:
|
||||
|
@ -1005,7 +999,7 @@ class CollectionArgument:
|
|||
|
||||
path: Path
|
||||
parts: Sequence[str]
|
||||
module_name: Optional[str]
|
||||
module_name: str | None
|
||||
|
||||
|
||||
def resolve_collection_argument(
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
"""Generic mechanism for marking and selecting python functions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
from typing import AbstractSet
|
||||
from typing import Collection
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from .expression import Expression
|
||||
from .expression import ParseError
|
||||
|
@ -44,8 +44,8 @@ old_mark_config_key = StashKey[Optional[Config]]()
|
|||
|
||||
def param(
|
||||
*values: object,
|
||||
marks: Union[MarkDecorator, Collection[Union[MarkDecorator, Mark]]] = (),
|
||||
id: Optional[str] = None,
|
||||
marks: MarkDecorator | Collection[MarkDecorator | Mark] = (),
|
||||
id: str | None = None,
|
||||
) -> ParameterSet:
|
||||
"""Specify a parameter in `pytest.mark.parametrize`_ calls or
|
||||
:ref:`parametrized fixtures <fixture-parametrize-marks>`.
|
||||
|
@ -112,7 +112,7 @@ def pytest_addoption(parser: Parser) -> None:
|
|||
|
||||
|
||||
@hookimpl(tryfirst=True)
|
||||
def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
|
||||
def pytest_cmdline_main(config: Config) -> int | ExitCode | None:
|
||||
import _pytest.config
|
||||
|
||||
if config.option.markers:
|
||||
|
@ -151,7 +151,7 @@ class KeywordMatcher:
|
|||
_names: AbstractSet[str]
|
||||
|
||||
@classmethod
|
||||
def from_item(cls, item: "Item") -> "KeywordMatcher":
|
||||
def from_item(cls, item: Item) -> KeywordMatcher:
|
||||
mapped_names = set()
|
||||
|
||||
# Add the names of the current item and any parent items,
|
||||
|
@ -191,7 +191,7 @@ class KeywordMatcher:
|
|||
return False
|
||||
|
||||
|
||||
def deselect_by_keyword(items: "List[Item]", config: Config) -> None:
|
||||
def deselect_by_keyword(items: list[Item], config: Config) -> None:
|
||||
keywordexpr = config.option.keyword.lstrip()
|
||||
if not keywordexpr:
|
||||
return
|
||||
|
@ -223,7 +223,7 @@ class MarkMatcher:
|
|||
own_mark_names: AbstractSet[str]
|
||||
|
||||
@classmethod
|
||||
def from_item(cls, item: "Item") -> "MarkMatcher":
|
||||
def from_item(cls, item: Item) -> MarkMatcher:
|
||||
mark_names = {mark.name for mark in item.iter_markers()}
|
||||
return cls(mark_names)
|
||||
|
||||
|
@ -231,14 +231,14 @@ class MarkMatcher:
|
|||
return name in self.own_mark_names
|
||||
|
||||
|
||||
def deselect_by_mark(items: "List[Item]", config: Config) -> None:
|
||||
def deselect_by_mark(items: list[Item], config: Config) -> None:
|
||||
matchexpr = config.option.markexpr
|
||||
if not matchexpr:
|
||||
return
|
||||
|
||||
expr = _parse_expression(matchexpr, "Wrong expression passed to '-m'")
|
||||
remaining: List[Item] = []
|
||||
deselected: List[Item] = []
|
||||
remaining: list[Item] = []
|
||||
deselected: list[Item] = []
|
||||
for item in items:
|
||||
if expr.evaluate(MarkMatcher.from_item(item)):
|
||||
remaining.append(item)
|
||||
|
@ -256,7 +256,7 @@ def _parse_expression(expr: str, exc_message: str) -> Expression:
|
|||
raise UsageError(f"{exc_message}: {expr}: {e}") from None
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(items: "List[Item]", config: Config) -> None:
|
||||
def pytest_collection_modifyitems(items: list[Item], config: Config) -> None:
|
||||
deselect_by_keyword(items, config)
|
||||
deselect_by_mark(items, config)
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ The semantics are:
|
|||
- or/and/not evaluate according to the usual boolean semantics.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import dataclasses
|
||||
import enum
|
||||
|
@ -24,7 +26,6 @@ from typing import Callable
|
|||
from typing import Iterator
|
||||
from typing import Mapping
|
||||
from typing import NoReturn
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
|
@ -105,7 +106,7 @@ class Scanner:
|
|||
)
|
||||
yield Token(TokenType.EOF, "", pos)
|
||||
|
||||
def accept(self, type: TokenType, *, reject: bool = False) -> Optional[Token]:
|
||||
def accept(self, type: TokenType, *, reject: bool = False) -> Token | None:
|
||||
if self.current.type is type:
|
||||
token = self.current
|
||||
if token.type is not TokenType.EOF:
|
||||
|
@ -197,7 +198,7 @@ class Expression:
|
|||
self.code = code
|
||||
|
||||
@classmethod
|
||||
def compile(self, input: str) -> "Expression":
|
||||
def compile(self, input: str) -> Expression:
|
||||
"""Compile a match expression.
|
||||
|
||||
:param input: The input expression - one line.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# mypy: allow-untyped-defs
|
||||
from __future__ import annotations
|
||||
|
||||
import collections.abc
|
||||
import dataclasses
|
||||
import inspect
|
||||
|
@ -8,16 +10,11 @@ from typing import Collection
|
|||
from typing import final
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Mapping
|
||||
from typing import MutableMapping
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
@ -47,7 +44,7 @@ def istestfunc(func) -> bool:
|
|||
|
||||
def get_empty_parameterset_mark(
|
||||
config: Config, argnames: Sequence[str], func
|
||||
) -> "MarkDecorator":
|
||||
) -> MarkDecorator:
|
||||
from ..nodes import Collector
|
||||
|
||||
fs, lineno = getfslineno(func)
|
||||
|
@ -75,17 +72,17 @@ def get_empty_parameterset_mark(
|
|||
|
||||
|
||||
class ParameterSet(NamedTuple):
|
||||
values: Sequence[Union[object, NotSetType]]
|
||||
marks: Collection[Union["MarkDecorator", "Mark"]]
|
||||
id: Optional[str]
|
||||
values: Sequence[object | NotSetType]
|
||||
marks: Collection[MarkDecorator | Mark]
|
||||
id: str | None
|
||||
|
||||
@classmethod
|
||||
def param(
|
||||
cls,
|
||||
*values: object,
|
||||
marks: Union["MarkDecorator", Collection[Union["MarkDecorator", "Mark"]]] = (),
|
||||
id: Optional[str] = None,
|
||||
) -> "ParameterSet":
|
||||
marks: MarkDecorator | Collection[MarkDecorator | Mark] = (),
|
||||
id: str | None = None,
|
||||
) -> ParameterSet:
|
||||
if isinstance(marks, MarkDecorator):
|
||||
marks = (marks,)
|
||||
else:
|
||||
|
@ -100,9 +97,9 @@ class ParameterSet(NamedTuple):
|
|||
@classmethod
|
||||
def extract_from(
|
||||
cls,
|
||||
parameterset: Union["ParameterSet", Sequence[object], object],
|
||||
parameterset: ParameterSet | Sequence[object] | object,
|
||||
force_tuple: bool = False,
|
||||
) -> "ParameterSet":
|
||||
) -> ParameterSet:
|
||||
"""Extract from an object or objects.
|
||||
|
||||
:param parameterset:
|
||||
|
@ -127,11 +124,11 @@ class ParameterSet(NamedTuple):
|
|||
|
||||
@staticmethod
|
||||
def _parse_parametrize_args(
|
||||
argnames: Union[str, Sequence[str]],
|
||||
argvalues: Iterable[Union["ParameterSet", Sequence[object], object]],
|
||||
argnames: str | Sequence[str],
|
||||
argvalues: Iterable[ParameterSet | Sequence[object] | object],
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> Tuple[Sequence[str], bool]:
|
||||
) -> tuple[Sequence[str], bool]:
|
||||
if isinstance(argnames, str):
|
||||
argnames = [x.strip() for x in argnames.split(",") if x.strip()]
|
||||
force_tuple = len(argnames) == 1
|
||||
|
@ -141,9 +138,9 @@ class ParameterSet(NamedTuple):
|
|||
|
||||
@staticmethod
|
||||
def _parse_parametrize_parameters(
|
||||
argvalues: Iterable[Union["ParameterSet", Sequence[object], object]],
|
||||
argvalues: Iterable[ParameterSet | Sequence[object] | object],
|
||||
force_tuple: bool,
|
||||
) -> List["ParameterSet"]:
|
||||
) -> list[ParameterSet]:
|
||||
return [
|
||||
ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues
|
||||
]
|
||||
|
@ -151,12 +148,12 @@ class ParameterSet(NamedTuple):
|
|||
@classmethod
|
||||
def _for_parametrize(
|
||||
cls,
|
||||
argnames: Union[str, Sequence[str]],
|
||||
argvalues: Iterable[Union["ParameterSet", Sequence[object], object]],
|
||||
argnames: str | Sequence[str],
|
||||
argvalues: Iterable[ParameterSet | Sequence[object] | object],
|
||||
func,
|
||||
config: Config,
|
||||
nodeid: str,
|
||||
) -> Tuple[Sequence[str], List["ParameterSet"]]:
|
||||
) -> tuple[Sequence[str], list[ParameterSet]]:
|
||||
argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues)
|
||||
parameters = cls._parse_parametrize_parameters(argvalues, force_tuple)
|
||||
del argvalues
|
||||
|
@ -199,24 +196,24 @@ class Mark:
|
|||
#: Name of the mark.
|
||||
name: str
|
||||
#: Positional arguments of the mark decorator.
|
||||
args: Tuple[Any, ...]
|
||||
args: tuple[Any, ...]
|
||||
#: Keyword arguments of the mark decorator.
|
||||
kwargs: Mapping[str, Any]
|
||||
|
||||
#: Source Mark for ids with parametrize Marks.
|
||||
_param_ids_from: Optional["Mark"] = dataclasses.field(default=None, repr=False)
|
||||
_param_ids_from: Mark | None = dataclasses.field(default=None, repr=False)
|
||||
#: Resolved/generated ids with parametrize Marks.
|
||||
_param_ids_generated: Optional[Sequence[str]] = dataclasses.field(
|
||||
_param_ids_generated: Sequence[str] | None = dataclasses.field(
|
||||
default=None, repr=False
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
args: Tuple[Any, ...],
|
||||
args: tuple[Any, ...],
|
||||
kwargs: Mapping[str, Any],
|
||||
param_ids_from: Optional["Mark"] = None,
|
||||
param_ids_generated: Optional[Sequence[str]] = None,
|
||||
param_ids_from: Mark | None = None,
|
||||
param_ids_generated: Sequence[str] | None = None,
|
||||
*,
|
||||
_ispytest: bool = False,
|
||||
) -> None:
|
||||
|
@ -232,7 +229,7 @@ class Mark:
|
|||
def _has_param_ids(self) -> bool:
|
||||
return "ids" in self.kwargs or len(self.args) >= 4
|
||||
|
||||
def combined_with(self, other: "Mark") -> "Mark":
|
||||
def combined_with(self, other: Mark) -> Mark:
|
||||
"""Return a new Mark which is a combination of this
|
||||
Mark and another Mark.
|
||||
|
||||
|
@ -244,7 +241,7 @@ class Mark:
|
|||
assert self.name == other.name
|
||||
|
||||
# Remember source of ids with parametrize Marks.
|
||||
param_ids_from: Optional[Mark] = None
|
||||
param_ids_from: Mark | None = None
|
||||
if self.name == "parametrize":
|
||||
if other._has_param_ids():
|
||||
param_ids_from = other
|
||||
|
@ -315,7 +312,7 @@ class MarkDecorator:
|
|||
return self.mark.name
|
||||
|
||||
@property
|
||||
def args(self) -> Tuple[Any, ...]:
|
||||
def args(self) -> tuple[Any, ...]:
|
||||
"""Alias for mark.args."""
|
||||
return self.mark.args
|
||||
|
||||
|
@ -329,7 +326,7 @@ class MarkDecorator:
|
|||
""":meta private:"""
|
||||
return self.name # for backward-compat (2.4.1 had this attr)
|
||||
|
||||
def with_args(self, *args: object, **kwargs: object) -> "MarkDecorator":
|
||||
def with_args(self, *args: object, **kwargs: object) -> MarkDecorator:
|
||||
"""Return a MarkDecorator with extra arguments added.
|
||||
|
||||
Unlike calling the MarkDecorator, with_args() can be used even
|
||||
|
@ -346,7 +343,7 @@ class MarkDecorator:
|
|||
pass
|
||||
|
||||
@overload
|
||||
def __call__(self, *args: object, **kwargs: object) -> "MarkDecorator":
|
||||
def __call__(self, *args: object, **kwargs: object) -> MarkDecorator:
|
||||
pass
|
||||
|
||||
def __call__(self, *args: object, **kwargs: object):
|
||||
|
@ -361,10 +358,10 @@ class MarkDecorator:
|
|||
|
||||
|
||||
def get_unpacked_marks(
|
||||
obj: Union[object, type],
|
||||
obj: object | type,
|
||||
*,
|
||||
consider_mro: bool = True,
|
||||
) -> List[Mark]:
|
||||
) -> list[Mark]:
|
||||
"""Obtain the unpacked marks that are stored on an object.
|
||||
|
||||
If obj is a class and consider_mro is true, return marks applied to
|
||||
|
@ -394,7 +391,7 @@ def get_unpacked_marks(
|
|||
|
||||
|
||||
def normalize_mark_list(
|
||||
mark_list: Iterable[Union[Mark, MarkDecorator]],
|
||||
mark_list: Iterable[Mark | MarkDecorator],
|
||||
) -> Iterable[Mark]:
|
||||
"""
|
||||
Normalize an iterable of Mark or MarkDecorator objects into a list of marks
|
||||
|
@ -437,13 +434,13 @@ if TYPE_CHECKING:
|
|||
def __call__(self, arg: Markable) -> Markable: ...
|
||||
|
||||
@overload
|
||||
def __call__(self, reason: str = ...) -> "MarkDecorator": ...
|
||||
def __call__(self, reason: str = ...) -> MarkDecorator: ...
|
||||
|
||||
class _SkipifMarkDecorator(MarkDecorator):
|
||||
def __call__( # type: ignore[override]
|
||||
self,
|
||||
condition: Union[str, bool] = ...,
|
||||
*conditions: Union[str, bool],
|
||||
condition: str | bool = ...,
|
||||
*conditions: str | bool,
|
||||
reason: str = ...,
|
||||
) -> MarkDecorator: ...
|
||||
|
||||
|
@ -454,30 +451,25 @@ if TYPE_CHECKING:
|
|||
@overload
|
||||
def __call__(
|
||||
self,
|
||||
condition: Union[str, bool] = False,
|
||||
*conditions: Union[str, bool],
|
||||
condition: str | bool = False,
|
||||
*conditions: str | bool,
|
||||
reason: str = ...,
|
||||
run: bool = ...,
|
||||
raises: Union[
|
||||
None, Type[BaseException], Tuple[Type[BaseException], ...]
|
||||
] = ...,
|
||||
raises: None | type[BaseException] | tuple[type[BaseException], ...] = ...,
|
||||
strict: bool = ...,
|
||||
) -> MarkDecorator: ...
|
||||
|
||||
class _ParametrizeMarkDecorator(MarkDecorator):
|
||||
def __call__( # type: ignore[override]
|
||||
self,
|
||||
argnames: Union[str, Sequence[str]],
|
||||
argvalues: Iterable[Union[ParameterSet, Sequence[object], object]],
|
||||
argnames: str | Sequence[str],
|
||||
argvalues: Iterable[ParameterSet | Sequence[object] | object],
|
||||
*,
|
||||
indirect: Union[bool, Sequence[str]] = ...,
|
||||
ids: Optional[
|
||||
Union[
|
||||
Iterable[Union[None, str, float, int, bool]],
|
||||
Callable[[Any], Optional[object]],
|
||||
]
|
||||
] = ...,
|
||||
scope: Optional[_ScopeName] = ...,
|
||||
indirect: bool | Sequence[str] = ...,
|
||||
ids: Iterable[None | str | float | int | bool]
|
||||
| Callable[[Any], object | None]
|
||||
| None = ...,
|
||||
scope: _ScopeName | None = ...,
|
||||
) -> MarkDecorator: ...
|
||||
|
||||
class _UsefixturesMarkDecorator(MarkDecorator):
|
||||
|
@ -517,8 +509,8 @@ class MarkGenerator:
|
|||
|
||||
def __init__(self, *, _ispytest: bool = False) -> None:
|
||||
check_ispytest(_ispytest)
|
||||
self._config: Optional[Config] = None
|
||||
self._markers: Set[str] = set()
|
||||
self._config: Config | None = None
|
||||
self._markers: set[str] = set()
|
||||
|
||||
def __getattr__(self, name: str) -> MarkDecorator:
|
||||
"""Generate a new :class:`MarkDecorator` with the given name."""
|
||||
|
@ -569,7 +561,7 @@ MARK_GEN = MarkGenerator(_ispytest=True)
|
|||
class NodeKeywords(MutableMapping[str, Any]):
|
||||
__slots__ = ("node", "parent", "_markers")
|
||||
|
||||
def __init__(self, node: "Node") -> None:
|
||||
def __init__(self, node: Node) -> None:
|
||||
self.node = node
|
||||
self.parent = node.parent
|
||||
self._markers = {node.name: True}
|
||||
|
@ -597,7 +589,7 @@ class NodeKeywords(MutableMapping[str, Any]):
|
|||
|
||||
def update( # type: ignore[override]
|
||||
self,
|
||||
other: Union[Mapping[str, Any], Iterable[Tuple[str, Any]]] = (),
|
||||
other: Mapping[str, Any] | Iterable[tuple[str, Any]] = (),
|
||||
**kwds: Any,
|
||||
) -> None:
|
||||
self._markers.update(other)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Monkeypatching and mocking functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager
|
||||
import os
|
||||
import re
|
||||
|
@ -8,14 +10,10 @@ import sys
|
|||
from typing import Any
|
||||
from typing import final
|
||||
from typing import Generator
|
||||
from typing import List
|
||||
from typing import Mapping
|
||||
from typing import MutableMapping
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Tuple
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
from _pytest.fixtures import fixture
|
||||
|
@ -30,7 +28,7 @@ V = TypeVar("V")
|
|||
|
||||
|
||||
@fixture
|
||||
def monkeypatch() -> Generator["MonkeyPatch", None, None]:
|
||||
def monkeypatch() -> Generator[MonkeyPatch, None, None]:
|
||||
"""A convenient fixture for monkey-patching.
|
||||
|
||||
The fixture provides these methods to modify objects, dictionaries, or
|
||||
|
@ -97,7 +95,7 @@ def annotated_getattr(obj: object, name: str, ann: str) -> object:
|
|||
return obj
|
||||
|
||||
|
||||
def derive_importpath(import_path: str, raising: bool) -> Tuple[str, object]:
|
||||
def derive_importpath(import_path: str, raising: bool) -> tuple[str, object]:
|
||||
if not isinstance(import_path, str) or "." not in import_path:
|
||||
raise TypeError(f"must be absolute import path string, not {import_path!r}")
|
||||
module, attr = import_path.rsplit(".", 1)
|
||||
|
@ -130,14 +128,14 @@ class MonkeyPatch:
|
|||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._setattr: List[Tuple[object, str, object]] = []
|
||||
self._setitem: List[Tuple[Mapping[Any, Any], object, object]] = []
|
||||
self._cwd: Optional[str] = None
|
||||
self._savesyspath: Optional[List[str]] = None
|
||||
self._setattr: list[tuple[object, str, object]] = []
|
||||
self._setitem: list[tuple[Mapping[Any, Any], object, object]] = []
|
||||
self._cwd: str | None = None
|
||||
self._savesyspath: list[str] | None = None
|
||||
|
||||
@classmethod
|
||||
@contextmanager
|
||||
def context(cls) -> Generator["MonkeyPatch", None, None]:
|
||||
def context(cls) -> Generator[MonkeyPatch, None, None]:
|
||||
"""Context manager that returns a new :class:`MonkeyPatch` object
|
||||
which undoes any patching done inside the ``with`` block upon exit.
|
||||
|
||||
|
@ -181,8 +179,8 @@ class MonkeyPatch:
|
|||
|
||||
def setattr(
|
||||
self,
|
||||
target: Union[str, object],
|
||||
name: Union[object, str],
|
||||
target: str | object,
|
||||
name: object | str,
|
||||
value: object = notset,
|
||||
raising: bool = True,
|
||||
) -> None:
|
||||
|
@ -253,8 +251,8 @@ class MonkeyPatch:
|
|||
|
||||
def delattr(
|
||||
self,
|
||||
target: Union[object, str],
|
||||
name: Union[str, Notset] = notset,
|
||||
target: object | str,
|
||||
name: str | Notset = notset,
|
||||
raising: bool = True,
|
||||
) -> None:
|
||||
"""Delete attribute ``name`` from ``target``.
|
||||
|
@ -309,7 +307,7 @@ class MonkeyPatch:
|
|||
# Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
|
||||
del dic[name] # type: ignore[attr-defined]
|
||||
|
||||
def setenv(self, name: str, value: str, prepend: Optional[str] = None) -> None:
|
||||
def setenv(self, name: str, value: str, prepend: str | None = None) -> None:
|
||||
"""Set environment variable ``name`` to ``value``.
|
||||
|
||||
If ``prepend`` is a character, read the current environment variable
|
||||
|
@ -362,7 +360,7 @@ class MonkeyPatch:
|
|||
|
||||
invalidate_caches()
|
||||
|
||||
def chdir(self, path: Union[str, "os.PathLike[str]"]) -> None:
|
||||
def chdir(self, path: str | os.PathLike[str]) -> None:
|
||||
"""Change the current working directory to the specified path.
|
||||
|
||||
:param path:
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# mypy: allow-untyped-defs
|
||||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
from functools import cached_property
|
||||
from inspect import signature
|
||||
|
@ -10,17 +12,11 @@ from typing import Callable
|
|||
from typing import cast
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import MutableMapping
|
||||
from typing import NoReturn
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
import pluggy
|
||||
|
@ -62,9 +58,9 @@ _T = TypeVar("_T")
|
|||
|
||||
|
||||
def _imply_path(
|
||||
node_type: Type["Node"],
|
||||
path: Optional[Path],
|
||||
fspath: Optional[LEGACY_PATH],
|
||||
node_type: type[Node],
|
||||
path: Path | None,
|
||||
fspath: LEGACY_PATH | None,
|
||||
) -> Path:
|
||||
if fspath is not None:
|
||||
warnings.warn(
|
||||
|
@ -109,7 +105,7 @@ class NodeMeta(abc.ABCMeta):
|
|||
).format(name=f"{cls.__module__}.{cls.__name__}")
|
||||
fail(msg, pytrace=False)
|
||||
|
||||
def _create(cls: Type[_T], *k, **kw) -> _T:
|
||||
def _create(cls: type[_T], *k, **kw) -> _T:
|
||||
try:
|
||||
return super().__call__(*k, **kw) # type: ignore[no-any-return,misc]
|
||||
except TypeError:
|
||||
|
@ -160,12 +156,12 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
parent: "Optional[Node]" = None,
|
||||
config: Optional[Config] = None,
|
||||
session: "Optional[Session]" = None,
|
||||
fspath: Optional[LEGACY_PATH] = None,
|
||||
path: Optional[Path] = None,
|
||||
nodeid: Optional[str] = None,
|
||||
parent: Node | None = None,
|
||||
config: Config | None = None,
|
||||
session: Session | None = None,
|
||||
fspath: LEGACY_PATH | None = None,
|
||||
path: Path | None = None,
|
||||
nodeid: str | None = None,
|
||||
) -> None:
|
||||
#: A unique name within the scope of the parent node.
|
||||
self.name: str = name
|
||||
|
@ -199,10 +195,10 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
self.keywords: MutableMapping[str, Any] = NodeKeywords(self)
|
||||
|
||||
#: The marker objects belonging to this node.
|
||||
self.own_markers: List[Mark] = []
|
||||
self.own_markers: list[Mark] = []
|
||||
|
||||
#: Allow adding of extra keywords to use for matching.
|
||||
self.extra_keyword_matches: Set[str] = set()
|
||||
self.extra_keyword_matches: set[str] = set()
|
||||
|
||||
if nodeid is not None:
|
||||
assert "::()" not in nodeid
|
||||
|
@ -219,7 +215,7 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
self._store = self.stash
|
||||
|
||||
@classmethod
|
||||
def from_parent(cls, parent: "Node", **kw) -> "Self":
|
||||
def from_parent(cls, parent: Node, **kw) -> Self:
|
||||
"""Public constructor for Nodes.
|
||||
|
||||
This indirection got introduced in order to enable removing
|
||||
|
@ -295,31 +291,29 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
def teardown(self) -> None:
|
||||
pass
|
||||
|
||||
def iter_parents(self) -> Iterator["Node"]:
|
||||
def iter_parents(self) -> Iterator[Node]:
|
||||
"""Iterate over all parent collectors starting from and including self
|
||||
up to the root of the collection tree.
|
||||
|
||||
.. versionadded:: 8.1
|
||||
"""
|
||||
parent: Optional[Node] = self
|
||||
parent: Node | None = self
|
||||
while parent is not None:
|
||||
yield parent
|
||||
parent = parent.parent
|
||||
|
||||
def listchain(self) -> List["Node"]:
|
||||
def listchain(self) -> list[Node]:
|
||||
"""Return a list of all parent collectors starting from the root of the
|
||||
collection tree down to and including self."""
|
||||
chain = []
|
||||
item: Optional[Node] = self
|
||||
item: Node | None = self
|
||||
while item is not None:
|
||||
chain.append(item)
|
||||
item = item.parent
|
||||
chain.reverse()
|
||||
return chain
|
||||
|
||||
def add_marker(
|
||||
self, marker: Union[str, MarkDecorator], append: bool = True
|
||||
) -> None:
|
||||
def add_marker(self, marker: str | MarkDecorator, append: bool = True) -> None:
|
||||
"""Dynamically add a marker object to the node.
|
||||
|
||||
:param marker:
|
||||
|
@ -341,7 +335,7 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
else:
|
||||
self.own_markers.insert(0, marker_.mark)
|
||||
|
||||
def iter_markers(self, name: Optional[str] = None) -> Iterator[Mark]:
|
||||
def iter_markers(self, name: str | None = None) -> Iterator[Mark]:
|
||||
"""Iterate over all markers of the node.
|
||||
|
||||
:param name: If given, filter the results by the name attribute.
|
||||
|
@ -350,8 +344,8 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
return (x[1] for x in self.iter_markers_with_node(name=name))
|
||||
|
||||
def iter_markers_with_node(
|
||||
self, name: Optional[str] = None
|
||||
) -> Iterator[Tuple["Node", Mark]]:
|
||||
self, name: str | None = None
|
||||
) -> Iterator[tuple[Node, Mark]]:
|
||||
"""Iterate over all markers of the node.
|
||||
|
||||
:param name: If given, filter the results by the name attribute.
|
||||
|
@ -363,14 +357,12 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
yield node, mark
|
||||
|
||||
@overload
|
||||
def get_closest_marker(self, name: str) -> Optional[Mark]: ...
|
||||
def get_closest_marker(self, name: str) -> Mark | None: ...
|
||||
|
||||
@overload
|
||||
def get_closest_marker(self, name: str, default: Mark) -> Mark: ...
|
||||
|
||||
def get_closest_marker(
|
||||
self, name: str, default: Optional[Mark] = None
|
||||
) -> Optional[Mark]:
|
||||
def get_closest_marker(self, name: str, default: Mark | None = None) -> Mark | None:
|
||||
"""Return the first marker matching the name, from closest (for
|
||||
example function) to farther level (for example module level).
|
||||
|
||||
|
@ -379,14 +371,14 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
"""
|
||||
return next(self.iter_markers(name=name), default)
|
||||
|
||||
def listextrakeywords(self) -> Set[str]:
|
||||
def listextrakeywords(self) -> set[str]:
|
||||
"""Return a set of all extra keywords in self and any parents."""
|
||||
extra_keywords: Set[str] = set()
|
||||
extra_keywords: set[str] = set()
|
||||
for item in self.listchain():
|
||||
extra_keywords.update(item.extra_keyword_matches)
|
||||
return extra_keywords
|
||||
|
||||
def listnames(self) -> List[str]:
|
||||
def listnames(self) -> list[str]:
|
||||
return [x.name for x in self.listchain()]
|
||||
|
||||
def addfinalizer(self, fin: Callable[[], object]) -> None:
|
||||
|
@ -398,7 +390,7 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
"""
|
||||
self.session._setupstate.addfinalizer(fin, self)
|
||||
|
||||
def getparent(self, cls: Type[_NodeType]) -> Optional[_NodeType]:
|
||||
def getparent(self, cls: type[_NodeType]) -> _NodeType | None:
|
||||
"""Get the closest parent node (including self) which is an instance of
|
||||
the given class.
|
||||
|
||||
|
@ -416,7 +408,7 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
def _repr_failure_py(
|
||||
self,
|
||||
excinfo: ExceptionInfo[BaseException],
|
||||
style: "Optional[_TracebackStyle]" = None,
|
||||
style: _TracebackStyle | None = None,
|
||||
) -> TerminalRepr:
|
||||
from _pytest.fixtures import FixtureLookupError
|
||||
|
||||
|
@ -428,7 +420,7 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
if isinstance(excinfo.value, FixtureLookupError):
|
||||
return excinfo.value.formatrepr()
|
||||
|
||||
tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]]
|
||||
tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback]
|
||||
if self.config.getoption("fulltrace", False):
|
||||
style = "long"
|
||||
tbfilter = False
|
||||
|
@ -471,8 +463,8 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
def repr_failure(
|
||||
self,
|
||||
excinfo: ExceptionInfo[BaseException],
|
||||
style: "Optional[_TracebackStyle]" = None,
|
||||
) -> Union[str, TerminalRepr]:
|
||||
style: _TracebackStyle | None = None,
|
||||
) -> str | TerminalRepr:
|
||||
"""Return a representation of a collection or test failure.
|
||||
|
||||
.. seealso:: :ref:`non-python tests`
|
||||
|
@ -482,7 +474,7 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
return self._repr_failure_py(excinfo, style)
|
||||
|
||||
|
||||
def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[int]]:
|
||||
def get_fslocation_from_item(node: Node) -> tuple[str | Path, int | None]:
|
||||
"""Try to extract the actual location from a node, depending on available attributes:
|
||||
|
||||
* "location": a pair (path, lineno)
|
||||
|
@ -492,7 +484,7 @@ def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[i
|
|||
:rtype: A tuple of (str|Path, int) with filename and 0-based line number.
|
||||
"""
|
||||
# See Item.location.
|
||||
location: Optional[Tuple[str, Optional[int], str]] = getattr(node, "location", None)
|
||||
location: tuple[str, int | None, str] | None = getattr(node, "location", None)
|
||||
if location is not None:
|
||||
return location[:2]
|
||||
obj = getattr(node, "obj", None)
|
||||
|
@ -512,14 +504,14 @@ class Collector(Node, abc.ABC):
|
|||
"""An error during collection, contains a custom message."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def collect(self) -> Iterable[Union["Item", "Collector"]]:
|
||||
def collect(self) -> Iterable[Item | Collector]:
|
||||
"""Collect children (items and collectors) for this collector."""
|
||||
raise NotImplementedError("abstract")
|
||||
|
||||
# TODO: This omits the style= parameter which breaks Liskov Substitution.
|
||||
def repr_failure( # type: ignore[override]
|
||||
self, excinfo: ExceptionInfo[BaseException]
|
||||
) -> Union[str, TerminalRepr]:
|
||||
) -> str | TerminalRepr:
|
||||
"""Return a representation of a collection failure.
|
||||
|
||||
:param excinfo: Exception information for the failure.
|
||||
|
@ -548,7 +540,7 @@ class Collector(Node, abc.ABC):
|
|||
return excinfo.traceback
|
||||
|
||||
|
||||
def _check_initialpaths_for_relpath(session: "Session", path: Path) -> Optional[str]:
|
||||
def _check_initialpaths_for_relpath(session: Session, path: Path) -> str | None:
|
||||
for initial_path in session._initialpaths:
|
||||
if commonpath(path, initial_path) == initial_path:
|
||||
rel = str(path.relative_to(initial_path))
|
||||
|
@ -561,14 +553,14 @@ class FSCollector(Collector, abc.ABC):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
fspath: Optional[LEGACY_PATH] = None,
|
||||
path_or_parent: Optional[Union[Path, Node]] = None,
|
||||
path: Optional[Path] = None,
|
||||
name: Optional[str] = None,
|
||||
parent: Optional[Node] = None,
|
||||
config: Optional[Config] = None,
|
||||
session: Optional["Session"] = None,
|
||||
nodeid: Optional[str] = None,
|
||||
fspath: LEGACY_PATH | None = None,
|
||||
path_or_parent: Path | Node | None = None,
|
||||
path: Path | None = None,
|
||||
name: str | None = None,
|
||||
parent: Node | None = None,
|
||||
config: Config | None = None,
|
||||
session: Session | None = None,
|
||||
nodeid: str | None = None,
|
||||
) -> None:
|
||||
if path_or_parent:
|
||||
if isinstance(path_or_parent, Node):
|
||||
|
@ -618,10 +610,10 @@ class FSCollector(Collector, abc.ABC):
|
|||
cls,
|
||||
parent,
|
||||
*,
|
||||
fspath: Optional[LEGACY_PATH] = None,
|
||||
path: Optional[Path] = None,
|
||||
fspath: LEGACY_PATH | None = None,
|
||||
path: Path | None = None,
|
||||
**kw,
|
||||
) -> "Self":
|
||||
) -> Self:
|
||||
"""The public constructor."""
|
||||
return super().from_parent(parent=parent, fspath=fspath, path=path, **kw)
|
||||
|
||||
|
@ -663,9 +655,9 @@ class Item(Node, abc.ABC):
|
|||
self,
|
||||
name,
|
||||
parent=None,
|
||||
config: Optional[Config] = None,
|
||||
session: Optional["Session"] = None,
|
||||
nodeid: Optional[str] = None,
|
||||
config: Config | None = None,
|
||||
session: Session | None = None,
|
||||
nodeid: str | None = None,
|
||||
**kw,
|
||||
) -> None:
|
||||
# The first two arguments are intentionally passed positionally,
|
||||
|
@ -680,11 +672,11 @@ class Item(Node, abc.ABC):
|
|||
nodeid=nodeid,
|
||||
**kw,
|
||||
)
|
||||
self._report_sections: List[Tuple[str, str, str]] = []
|
||||
self._report_sections: list[tuple[str, str, str]] = []
|
||||
|
||||
#: A list of tuples (name, value) that holds user defined properties
|
||||
#: for this test.
|
||||
self.user_properties: List[Tuple[str, object]] = []
|
||||
self.user_properties: list[tuple[str, object]] = []
|
||||
|
||||
self._check_item_and_collector_diamond_inheritance()
|
||||
|
||||
|
@ -744,7 +736,7 @@ class Item(Node, abc.ABC):
|
|||
if content:
|
||||
self._report_sections.append((when, key, content))
|
||||
|
||||
def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]:
|
||||
def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]:
|
||||
"""Get location information for this item for test reports.
|
||||
|
||||
Returns a tuple with three elements:
|
||||
|
@ -758,7 +750,7 @@ class Item(Node, abc.ABC):
|
|||
return self.path, None, ""
|
||||
|
||||
@cached_property
|
||||
def location(self) -> Tuple[str, Optional[int], str]:
|
||||
def location(self) -> tuple[str, int | None, str]:
|
||||
"""
|
||||
Returns a tuple of ``(relfspath, lineno, testname)`` for this item
|
||||
where ``relfspath`` is file path relative to ``config.rootpath``
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
"""Exception classes and constants handling test outcomes as well as
|
||||
functions creating them."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
from typing import NoReturn
|
||||
from typing import Optional
|
||||
from typing import Protocol
|
||||
from typing import Type
|
||||
from typing import TypeVar
|
||||
|
@ -18,7 +19,7 @@ class OutcomeException(BaseException):
|
|||
"""OutcomeException and its subclass instances indicate and contain info
|
||||
about test and collection outcomes."""
|
||||
|
||||
def __init__(self, msg: Optional[str] = None, pytrace: bool = True) -> None:
|
||||
def __init__(self, msg: str | None = None, pytrace: bool = True) -> None:
|
||||
if msg is not None and not isinstance(msg, str):
|
||||
error_msg = ( # type: ignore[unreachable]
|
||||
"{} expected string as 'msg' parameter, got '{}' instead.\n"
|
||||
|
@ -47,7 +48,7 @@ class Skipped(OutcomeException):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
msg: Optional[str] = None,
|
||||
msg: str | None = None,
|
||||
pytrace: bool = True,
|
||||
allow_module_level: bool = False,
|
||||
*,
|
||||
|
@ -70,7 +71,7 @@ class Exit(Exception):
|
|||
"""Raised for immediate program exits (no tracebacks/summaries)."""
|
||||
|
||||
def __init__(
|
||||
self, msg: str = "unknown reason", returncode: Optional[int] = None
|
||||
self, msg: str = "unknown reason", returncode: int | None = None
|
||||
) -> None:
|
||||
self.msg = msg
|
||||
self.returncode = returncode
|
||||
|
@ -104,7 +105,7 @@ def _with_exception(exception_type: _ET) -> Callable[[_F], _WithException[_F, _E
|
|||
@_with_exception(Exit)
|
||||
def exit(
|
||||
reason: str = "",
|
||||
returncode: Optional[int] = None,
|
||||
returncode: int | None = None,
|
||||
) -> NoReturn:
|
||||
"""Exit testing process.
|
||||
|
||||
|
@ -207,10 +208,10 @@ def xfail(reason: str = "") -> NoReturn:
|
|||
|
||||
def importorskip(
|
||||
modname: str,
|
||||
minversion: Optional[str] = None,
|
||||
reason: Optional[str] = None,
|
||||
minversion: str | None = None,
|
||||
reason: str | None = None,
|
||||
*,
|
||||
exc_type: Optional[Type[ImportError]] = None,
|
||||
exc_type: type[ImportError] | None = None,
|
||||
) -> Any:
|
||||
"""Import and return the requested module ``modname``, or skip the
|
||||
current test if the module cannot be imported.
|
||||
|
@ -267,8 +268,8 @@ def importorskip(
|
|||
else:
|
||||
warn_on_import_error = False
|
||||
|
||||
skipped: Optional[Skipped] = None
|
||||
warning: Optional[Warning] = None
|
||||
skipped: Skipped | None = None
|
||||
warning: Warning | None = None
|
||||
|
||||
with warnings.catch_warnings():
|
||||
# Make sure to ignore ImportWarnings that might happen because
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Submit failure or test session information to a pastebin service."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from io import StringIO
|
||||
import tempfile
|
||||
from typing import IO
|
||||
from typing import Union
|
||||
|
||||
from _pytest.config import Config
|
||||
from _pytest.config import create_terminal_writer
|
||||
|
@ -68,7 +69,7 @@ def pytest_unconfigure(config: Config) -> None:
|
|||
tr.write_line("pastebin session-log: %s\n" % pastebinurl)
|
||||
|
||||
|
||||
def create_new_paste(contents: Union[str, bytes]) -> str:
|
||||
def create_new_paste(contents: str | bytes) -> str:
|
||||
"""Create a new paste using the bpaste.net service.
|
||||
|
||||
:contents: Paste contents string.
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import atexit
|
||||
import contextlib
|
||||
from enum import Enum
|
||||
|
@ -24,16 +26,9 @@ import types
|
|||
from types import ModuleType
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
import uuid
|
||||
import warnings
|
||||
|
||||
|
@ -71,12 +66,10 @@ def get_lock_path(path: _AnyPurePath) -> _AnyPurePath:
|
|||
|
||||
|
||||
def on_rm_rf_error(
|
||||
func: Optional[Callable[..., Any]],
|
||||
func: Callable[..., Any] | None,
|
||||
path: str,
|
||||
excinfo: Union[
|
||||
BaseException,
|
||||
Tuple[Type[BaseException], BaseException, Optional[types.TracebackType]],
|
||||
],
|
||||
excinfo: BaseException
|
||||
| tuple[type[BaseException], BaseException, types.TracebackType | None],
|
||||
*,
|
||||
start_path: Path,
|
||||
) -> bool:
|
||||
|
@ -172,7 +165,7 @@ def rm_rf(path: Path) -> None:
|
|||
shutil.rmtree(str(path), onerror=onerror)
|
||||
|
||||
|
||||
def find_prefixed(root: Path, prefix: str) -> Iterator["os.DirEntry[str]"]:
|
||||
def find_prefixed(root: Path, prefix: str) -> Iterator[os.DirEntry[str]]:
|
||||
"""Find all elements in root that begin with the prefix, case-insensitive."""
|
||||
l_prefix = prefix.lower()
|
||||
for x in os.scandir(root):
|
||||
|
@ -180,7 +173,7 @@ def find_prefixed(root: Path, prefix: str) -> Iterator["os.DirEntry[str]"]:
|
|||
yield x
|
||||
|
||||
|
||||
def extract_suffixes(iter: Iterable["os.DirEntry[str]"], prefix: str) -> Iterator[str]:
|
||||
def extract_suffixes(iter: Iterable[os.DirEntry[str]], prefix: str) -> Iterator[str]:
|
||||
"""Return the parts of the paths following the prefix.
|
||||
|
||||
:param iter: Iterator over path names.
|
||||
|
@ -204,9 +197,7 @@ def parse_num(maybe_num: str) -> int:
|
|||
return -1
|
||||
|
||||
|
||||
def _force_symlink(
|
||||
root: Path, target: Union[str, PurePath], link_to: Union[str, Path]
|
||||
) -> None:
|
||||
def _force_symlink(root: Path, target: str | PurePath, link_to: str | Path) -> None:
|
||||
"""Helper to create the current symlink.
|
||||
|
||||
It's full of race conditions that are reasonably OK to ignore
|
||||
|
@ -420,7 +411,7 @@ def resolve_from_str(input: str, rootpath: Path) -> Path:
|
|||
return rootpath.joinpath(input)
|
||||
|
||||
|
||||
def fnmatch_ex(pattern: str, path: Union[str, "os.PathLike[str]"]) -> bool:
|
||||
def fnmatch_ex(pattern: str, path: str | os.PathLike[str]) -> bool:
|
||||
"""A port of FNMatcher from py.path.common which works with PurePath() instances.
|
||||
|
||||
The difference between this algorithm and PurePath.match() is that the
|
||||
|
@ -456,14 +447,14 @@ def fnmatch_ex(pattern: str, path: Union[str, "os.PathLike[str]"]) -> bool:
|
|||
return fnmatch.fnmatch(name, pattern)
|
||||
|
||||
|
||||
def parts(s: str) -> Set[str]:
|
||||
def parts(s: str) -> set[str]:
|
||||
parts = s.split(sep)
|
||||
return {sep.join(parts[: i + 1]) or sep for i in range(len(parts))}
|
||||
|
||||
|
||||
def symlink_or_skip(
|
||||
src: Union["os.PathLike[str]", str],
|
||||
dst: Union["os.PathLike[str]", str],
|
||||
src: os.PathLike[str] | str,
|
||||
dst: os.PathLike[str] | str,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Make a symlink, or skip the test in case symlinks are not supported."""
|
||||
|
@ -491,9 +482,9 @@ class ImportPathMismatchError(ImportError):
|
|||
|
||||
|
||||
def import_path(
|
||||
path: Union[str, "os.PathLike[str]"],
|
||||
path: str | os.PathLike[str],
|
||||
*,
|
||||
mode: Union[str, ImportMode] = ImportMode.prepend,
|
||||
mode: str | ImportMode = ImportMode.prepend,
|
||||
root: Path,
|
||||
consider_namespace_packages: bool,
|
||||
) -> ModuleType:
|
||||
|
@ -618,7 +609,7 @@ def import_path(
|
|||
|
||||
def _import_module_using_spec(
|
||||
module_name: str, module_path: Path, module_location: Path, *, insert_modules: bool
|
||||
) -> Optional[ModuleType]:
|
||||
) -> ModuleType | None:
|
||||
"""
|
||||
Tries to import a module by its canonical name, path to the .py file, and its
|
||||
parent location.
|
||||
|
@ -641,7 +632,7 @@ def _import_module_using_spec(
|
|||
# Attempt to import the parent module, seems is our responsibility:
|
||||
# https://github.com/python/cpython/blob/73906d5c908c1e0b73c5436faeff7d93698fc074/Lib/importlib/_bootstrap.py#L1308-L1311
|
||||
parent_module_name, _, name = module_name.rpartition(".")
|
||||
parent_module: Optional[ModuleType] = None
|
||||
parent_module: ModuleType | None = None
|
||||
if parent_module_name:
|
||||
parent_module = sys.modules.get(parent_module_name)
|
||||
if parent_module is None:
|
||||
|
@ -680,9 +671,7 @@ def _import_module_using_spec(
|
|||
return None
|
||||
|
||||
|
||||
def spec_matches_module_path(
|
||||
module_spec: Optional[ModuleSpec], module_path: Path
|
||||
) -> bool:
|
||||
def spec_matches_module_path(module_spec: ModuleSpec | None, module_path: Path) -> bool:
|
||||
"""Return true if the given ModuleSpec can be used to import the given module path."""
|
||||
if module_spec is None or module_spec.origin is None:
|
||||
return False
|
||||
|
@ -734,7 +723,7 @@ def module_name_from_path(path: Path, root: Path) -> str:
|
|||
return ".".join(path_parts)
|
||||
|
||||
|
||||
def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) -> None:
|
||||
def insert_missing_modules(modules: dict[str, ModuleType], module_name: str) -> None:
|
||||
"""
|
||||
Used by ``import_path`` to create intermediate modules when using mode=importlib.
|
||||
|
||||
|
@ -772,7 +761,7 @@ def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) ->
|
|||
module_name = ".".join(module_parts)
|
||||
|
||||
|
||||
def resolve_package_path(path: Path) -> Optional[Path]:
|
||||
def resolve_package_path(path: Path) -> Path | None:
|
||||
"""Return the Python package path by looking for the last
|
||||
directory upwards which still contains an __init__.py.
|
||||
|
||||
|
@ -791,7 +780,7 @@ def resolve_package_path(path: Path) -> Optional[Path]:
|
|||
|
||||
def resolve_pkg_root_and_module_name(
|
||||
path: Path, *, consider_namespace_packages: bool = False
|
||||
) -> Tuple[Path, str]:
|
||||
) -> tuple[Path, str]:
|
||||
"""
|
||||
Return the path to the directory of the root package that contains the
|
||||
given Python file, and its module name:
|
||||
|
@ -812,7 +801,7 @@ def resolve_pkg_root_and_module_name(
|
|||
|
||||
Raises CouldNotResolvePathError if the given path does not belong to a package (missing any __init__.py files).
|
||||
"""
|
||||
pkg_root: Optional[Path] = None
|
||||
pkg_root: Path | None = None
|
||||
pkg_path = resolve_package_path(path)
|
||||
if pkg_path is not None:
|
||||
pkg_root = pkg_path.parent
|
||||
|
@ -859,7 +848,7 @@ def is_importable(module_name: str, module_path: Path) -> bool:
|
|||
return spec_matches_module_path(spec, module_path)
|
||||
|
||||
|
||||
def compute_module_name(root: Path, module_path: Path) -> Optional[str]:
|
||||
def compute_module_name(root: Path, module_path: Path) -> str | None:
|
||||
"""Compute a module name based on a path and a root anchor."""
|
||||
try:
|
||||
path_without_suffix = module_path.with_suffix("")
|
||||
|
@ -884,9 +873,9 @@ class CouldNotResolvePathError(Exception):
|
|||
|
||||
|
||||
def scandir(
|
||||
path: Union[str, "os.PathLike[str]"],
|
||||
sort_key: Callable[["os.DirEntry[str]"], object] = lambda entry: entry.name,
|
||||
) -> List["os.DirEntry[str]"]:
|
||||
path: str | os.PathLike[str],
|
||||
sort_key: Callable[[os.DirEntry[str]], object] = lambda entry: entry.name,
|
||||
) -> list[os.DirEntry[str]]:
|
||||
"""Scan a directory recursively, in breadth-first order.
|
||||
|
||||
The returned entries are sorted according to the given key.
|
||||
|
@ -909,8 +898,8 @@ def scandir(
|
|||
|
||||
|
||||
def visit(
|
||||
path: Union[str, "os.PathLike[str]"], recurse: Callable[["os.DirEntry[str]"], bool]
|
||||
) -> Iterator["os.DirEntry[str]"]:
|
||||
path: str | os.PathLike[str], recurse: Callable[[os.DirEntry[str]], bool]
|
||||
) -> Iterator[os.DirEntry[str]]:
|
||||
"""Walk a directory recursively, in breadth-first order.
|
||||
|
||||
The `recurse` predicate determines whether a directory is recursed.
|
||||
|
@ -924,7 +913,7 @@ def visit(
|
|||
yield from visit(entry.path, recurse)
|
||||
|
||||
|
||||
def absolutepath(path: "Union[str, os.PathLike[str]]") -> Path:
|
||||
def absolutepath(path: str | os.PathLike[str]) -> Path:
|
||||
"""Convert a path to an absolute path using os.path.abspath.
|
||||
|
||||
Prefer this over Path.resolve() (see #6523).
|
||||
|
@ -933,7 +922,7 @@ def absolutepath(path: "Union[str, os.PathLike[str]]") -> Path:
|
|||
return Path(os.path.abspath(path))
|
||||
|
||||
|
||||
def commonpath(path1: Path, path2: Path) -> Optional[Path]:
|
||||
def commonpath(path1: Path, path2: Path) -> Path | None:
|
||||
"""Return the common part shared with the other path, or None if there is
|
||||
no common part.
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
PYTEST_DONT_REWRITE
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import collections.abc
|
||||
import contextlib
|
||||
from fnmatch import fnmatch
|
||||
|
@ -21,22 +23,16 @@ import sys
|
|||
import traceback
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import Final
|
||||
from typing import final
|
||||
from typing import Generator
|
||||
from typing import IO
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Literal
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Sequence
|
||||
from typing import TextIO
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
from iniconfig import IniConfig
|
||||
|
@ -123,7 +119,7 @@ def pytest_configure(config: Config) -> None:
|
|||
|
||||
|
||||
class LsofFdLeakChecker:
|
||||
def get_open_files(self) -> List[Tuple[str, str]]:
|
||||
def get_open_files(self) -> list[tuple[str, str]]:
|
||||
if sys.version_info >= (3, 11):
|
||||
# New in Python 3.11, ignores utf-8 mode
|
||||
encoding = locale.getencoding()
|
||||
|
@ -199,7 +195,7 @@ class LsofFdLeakChecker:
|
|||
|
||||
|
||||
@fixture
|
||||
def _pytest(request: FixtureRequest) -> "PytestArg":
|
||||
def _pytest(request: FixtureRequest) -> PytestArg:
|
||||
"""Return a helper which offers a gethookrecorder(hook) method which
|
||||
returns a HookRecorder instance which helps to make assertions about called
|
||||
hooks."""
|
||||
|
@ -210,13 +206,13 @@ class PytestArg:
|
|||
def __init__(self, request: FixtureRequest) -> None:
|
||||
self._request = request
|
||||
|
||||
def gethookrecorder(self, hook) -> "HookRecorder":
|
||||
def gethookrecorder(self, hook) -> HookRecorder:
|
||||
hookrecorder = HookRecorder(hook._pm)
|
||||
self._request.addfinalizer(hookrecorder.finish_recording)
|
||||
return hookrecorder
|
||||
|
||||
|
||||
def get_public_names(values: Iterable[str]) -> List[str]:
|
||||
def get_public_names(values: Iterable[str]) -> list[str]:
|
||||
"""Only return names from iterator values without a leading underscore."""
|
||||
return [x for x in values if x[0] != "_"]
|
||||
|
||||
|
@ -265,8 +261,8 @@ class HookRecorder:
|
|||
check_ispytest(_ispytest)
|
||||
|
||||
self._pluginmanager = pluginmanager
|
||||
self.calls: List[RecordedHookCall] = []
|
||||
self.ret: Optional[Union[int, ExitCode]] = None
|
||||
self.calls: list[RecordedHookCall] = []
|
||||
self.ret: int | ExitCode | None = None
|
||||
|
||||
def before(hook_name: str, hook_impls, kwargs) -> None:
|
||||
self.calls.append(RecordedHookCall(hook_name, kwargs))
|
||||
|
@ -279,13 +275,13 @@ class HookRecorder:
|
|||
def finish_recording(self) -> None:
|
||||
self._undo_wrapping()
|
||||
|
||||
def getcalls(self, names: Union[str, Iterable[str]]) -> List[RecordedHookCall]:
|
||||
def getcalls(self, names: str | Iterable[str]) -> list[RecordedHookCall]:
|
||||
"""Get all recorded calls to hooks with the given names (or name)."""
|
||||
if isinstance(names, str):
|
||||
names = names.split()
|
||||
return [call for call in self.calls if call._name in names]
|
||||
|
||||
def assert_contains(self, entries: Sequence[Tuple[str, str]]) -> None:
|
||||
def assert_contains(self, entries: Sequence[tuple[str, str]]) -> None:
|
||||
__tracebackhide__ = True
|
||||
i = 0
|
||||
entries = list(entries)
|
||||
|
@ -327,42 +323,42 @@ class HookRecorder:
|
|||
@overload
|
||||
def getreports(
|
||||
self,
|
||||
names: "Literal['pytest_collectreport']",
|
||||
names: Literal["pytest_collectreport"],
|
||||
) -> Sequence[CollectReport]: ...
|
||||
|
||||
@overload
|
||||
def getreports(
|
||||
self,
|
||||
names: "Literal['pytest_runtest_logreport']",
|
||||
names: Literal["pytest_runtest_logreport"],
|
||||
) -> Sequence[TestReport]: ...
|
||||
|
||||
@overload
|
||||
def getreports(
|
||||
self,
|
||||
names: Union[str, Iterable[str]] = (
|
||||
names: str | Iterable[str] = (
|
||||
"pytest_collectreport",
|
||||
"pytest_runtest_logreport",
|
||||
),
|
||||
) -> Sequence[Union[CollectReport, TestReport]]: ...
|
||||
) -> Sequence[CollectReport | TestReport]: ...
|
||||
|
||||
def getreports(
|
||||
self,
|
||||
names: Union[str, Iterable[str]] = (
|
||||
names: str | Iterable[str] = (
|
||||
"pytest_collectreport",
|
||||
"pytest_runtest_logreport",
|
||||
),
|
||||
) -> Sequence[Union[CollectReport, TestReport]]:
|
||||
) -> Sequence[CollectReport | TestReport]:
|
||||
return [x.report for x in self.getcalls(names)]
|
||||
|
||||
def matchreport(
|
||||
self,
|
||||
inamepart: str = "",
|
||||
names: Union[str, Iterable[str]] = (
|
||||
names: str | Iterable[str] = (
|
||||
"pytest_runtest_logreport",
|
||||
"pytest_collectreport",
|
||||
),
|
||||
when: Optional[str] = None,
|
||||
) -> Union[CollectReport, TestReport]:
|
||||
when: str | None = None,
|
||||
) -> CollectReport | TestReport:
|
||||
"""Return a testreport whose dotted import path matches."""
|
||||
values = []
|
||||
for rep in self.getreports(names=names):
|
||||
|
@ -387,31 +383,31 @@ class HookRecorder:
|
|||
@overload
|
||||
def getfailures(
|
||||
self,
|
||||
names: "Literal['pytest_collectreport']",
|
||||
names: Literal["pytest_collectreport"],
|
||||
) -> Sequence[CollectReport]: ...
|
||||
|
||||
@overload
|
||||
def getfailures(
|
||||
self,
|
||||
names: "Literal['pytest_runtest_logreport']",
|
||||
names: Literal["pytest_runtest_logreport"],
|
||||
) -> Sequence[TestReport]: ...
|
||||
|
||||
@overload
|
||||
def getfailures(
|
||||
self,
|
||||
names: Union[str, Iterable[str]] = (
|
||||
names: str | Iterable[str] = (
|
||||
"pytest_collectreport",
|
||||
"pytest_runtest_logreport",
|
||||
),
|
||||
) -> Sequence[Union[CollectReport, TestReport]]: ...
|
||||
) -> Sequence[CollectReport | TestReport]: ...
|
||||
|
||||
def getfailures(
|
||||
self,
|
||||
names: Union[str, Iterable[str]] = (
|
||||
names: str | Iterable[str] = (
|
||||
"pytest_collectreport",
|
||||
"pytest_runtest_logreport",
|
||||
),
|
||||
) -> Sequence[Union[CollectReport, TestReport]]:
|
||||
) -> Sequence[CollectReport | TestReport]:
|
||||
return [rep for rep in self.getreports(names) if rep.failed]
|
||||
|
||||
def getfailedcollections(self) -> Sequence[CollectReport]:
|
||||
|
@ -419,10 +415,10 @@ class HookRecorder:
|
|||
|
||||
def listoutcomes(
|
||||
self,
|
||||
) -> Tuple[
|
||||
) -> tuple[
|
||||
Sequence[TestReport],
|
||||
Sequence[Union[CollectReport, TestReport]],
|
||||
Sequence[Union[CollectReport, TestReport]],
|
||||
Sequence[CollectReport | TestReport],
|
||||
Sequence[CollectReport | TestReport],
|
||||
]:
|
||||
passed = []
|
||||
skipped = []
|
||||
|
@ -441,7 +437,7 @@ class HookRecorder:
|
|||
failed.append(rep)
|
||||
return passed, skipped, failed
|
||||
|
||||
def countoutcomes(self) -> List[int]:
|
||||
def countoutcomes(self) -> list[int]:
|
||||
return [len(x) for x in self.listoutcomes()]
|
||||
|
||||
def assertoutcome(self, passed: int = 0, skipped: int = 0, failed: int = 0) -> None:
|
||||
|
@ -461,14 +457,14 @@ class HookRecorder:
|
|||
|
||||
|
||||
@fixture
|
||||
def linecomp() -> "LineComp":
|
||||
def linecomp() -> LineComp:
|
||||
"""A :class: `LineComp` instance for checking that an input linearly
|
||||
contains a sequence of strings."""
|
||||
return LineComp()
|
||||
|
||||
|
||||
@fixture(name="LineMatcher")
|
||||
def LineMatcher_fixture(request: FixtureRequest) -> Type["LineMatcher"]:
|
||||
def LineMatcher_fixture(request: FixtureRequest) -> type[LineMatcher]:
|
||||
"""A reference to the :class: `LineMatcher`.
|
||||
|
||||
This is instantiable with a list of lines (without their trailing newlines).
|
||||
|
@ -480,7 +476,7 @@ def LineMatcher_fixture(request: FixtureRequest) -> Type["LineMatcher"]:
|
|||
@fixture
|
||||
def pytester(
|
||||
request: FixtureRequest, tmp_path_factory: TempPathFactory, monkeypatch: MonkeyPatch
|
||||
) -> "Pytester":
|
||||
) -> Pytester:
|
||||
"""
|
||||
Facilities to write tests/configuration files, execute pytest in isolation, and match
|
||||
against expected output, perfect for black-box testing of pytest plugins.
|
||||
|
@ -524,13 +520,13 @@ class RunResult:
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
ret: Union[int, ExitCode],
|
||||
outlines: List[str],
|
||||
errlines: List[str],
|
||||
ret: int | ExitCode,
|
||||
outlines: list[str],
|
||||
errlines: list[str],
|
||||
duration: float,
|
||||
) -> None:
|
||||
try:
|
||||
self.ret: Union[int, ExitCode] = ExitCode(ret)
|
||||
self.ret: int | ExitCode = ExitCode(ret)
|
||||
"""The return value."""
|
||||
except ValueError:
|
||||
self.ret = ret
|
||||
|
@ -555,7 +551,7 @@ class RunResult:
|
|||
% (self.ret, len(self.stdout.lines), len(self.stderr.lines), self.duration)
|
||||
)
|
||||
|
||||
def parseoutcomes(self) -> Dict[str, int]:
|
||||
def parseoutcomes(self) -> dict[str, int]:
|
||||
"""Return a dictionary of outcome noun -> count from parsing the terminal
|
||||
output that the test process produced.
|
||||
|
||||
|
@ -568,7 +564,7 @@ class RunResult:
|
|||
return self.parse_summary_nouns(self.outlines)
|
||||
|
||||
@classmethod
|
||||
def parse_summary_nouns(cls, lines) -> Dict[str, int]:
|
||||
def parse_summary_nouns(cls, lines) -> dict[str, int]:
|
||||
"""Extract the nouns from a pytest terminal summary line.
|
||||
|
||||
It always returns the plural noun for consistency::
|
||||
|
@ -599,8 +595,8 @@ class RunResult:
|
|||
errors: int = 0,
|
||||
xpassed: int = 0,
|
||||
xfailed: int = 0,
|
||||
warnings: Optional[int] = None,
|
||||
deselected: Optional[int] = None,
|
||||
warnings: int | None = None,
|
||||
deselected: int | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
Assert that the specified outcomes appear with the respective
|
||||
|
@ -626,7 +622,7 @@ class RunResult:
|
|||
|
||||
|
||||
class SysModulesSnapshot:
|
||||
def __init__(self, preserve: Optional[Callable[[str], bool]] = None) -> None:
|
||||
def __init__(self, preserve: Callable[[str], bool] | None = None) -> None:
|
||||
self.__preserve = preserve
|
||||
self.__saved = dict(sys.modules)
|
||||
|
||||
|
@ -659,7 +655,7 @@ class Pytester:
|
|||
|
||||
__test__ = False
|
||||
|
||||
CLOSE_STDIN: "Final" = NOTSET
|
||||
CLOSE_STDIN: Final = NOTSET
|
||||
|
||||
class TimeoutExpired(Exception):
|
||||
pass
|
||||
|
@ -674,9 +670,9 @@ class Pytester:
|
|||
) -> None:
|
||||
check_ispytest(_ispytest)
|
||||
self._request = request
|
||||
self._mod_collections: WeakKeyDictionary[
|
||||
Collector, List[Union[Item, Collector]]
|
||||
] = WeakKeyDictionary()
|
||||
self._mod_collections: WeakKeyDictionary[Collector, list[Item | Collector]] = (
|
||||
WeakKeyDictionary()
|
||||
)
|
||||
if request.function:
|
||||
name: str = request.function.__name__
|
||||
else:
|
||||
|
@ -687,7 +683,7 @@ class Pytester:
|
|||
#: :py:meth:`runpytest`. Initially this is an empty list but plugins can
|
||||
#: be added to the list. The type of items to add to the list depends on
|
||||
#: the method using them so refer to them for details.
|
||||
self.plugins: List[Union[str, _PluggyPlugin]] = []
|
||||
self.plugins: list[str | _PluggyPlugin] = []
|
||||
self._sys_path_snapshot = SysPathsSnapshot()
|
||||
self._sys_modules_snapshot = self.__take_sys_modules_snapshot()
|
||||
self._request.addfinalizer(self._finalize)
|
||||
|
@ -755,8 +751,8 @@ class Pytester:
|
|||
def _makefile(
|
||||
self,
|
||||
ext: str,
|
||||
lines: Sequence[Union[Any, bytes]],
|
||||
files: Dict[str, str],
|
||||
lines: Sequence[Any | bytes],
|
||||
files: dict[str, str],
|
||||
encoding: str = "utf-8",
|
||||
) -> Path:
|
||||
items = list(files.items())
|
||||
|
@ -769,7 +765,7 @@ class Pytester:
|
|||
f"pytester.makefile expects a file extension, try .{ext} instead of {ext}"
|
||||
)
|
||||
|
||||
def to_text(s: Union[Any, bytes]) -> str:
|
||||
def to_text(s: Any | bytes) -> str:
|
||||
return s.decode(encoding) if isinstance(s, bytes) else str(s)
|
||||
|
||||
if lines:
|
||||
|
@ -889,9 +885,7 @@ class Pytester:
|
|||
"""
|
||||
return self._makefile(".txt", args, kwargs)
|
||||
|
||||
def syspathinsert(
|
||||
self, path: Optional[Union[str, "os.PathLike[str]"]] = None
|
||||
) -> None:
|
||||
def syspathinsert(self, path: str | os.PathLike[str] | None = None) -> None:
|
||||
"""Prepend a directory to sys.path, defaults to :attr:`path`.
|
||||
|
||||
This is undone automatically when this object dies at the end of each
|
||||
|
@ -905,19 +899,20 @@ class Pytester:
|
|||
|
||||
self._monkeypatch.syspath_prepend(str(path))
|
||||
|
||||
def mkdir(self, name: Union[str, "os.PathLike[str]"]) -> Path:
|
||||
def mkdir(self, name: str | os.PathLike[str]) -> Path:
|
||||
"""Create a new (sub)directory.
|
||||
|
||||
:param name:
|
||||
The name of the directory, relative to the pytester path.
|
||||
:returns:
|
||||
The created directory.
|
||||
:rtype: pathlib.Path
|
||||
"""
|
||||
p = self.path / name
|
||||
p.mkdir()
|
||||
return p
|
||||
|
||||
def mkpydir(self, name: Union[str, "os.PathLike[str]"]) -> Path:
|
||||
def mkpydir(self, name: str | os.PathLike[str]) -> Path:
|
||||
"""Create a new python package.
|
||||
|
||||
This creates a (sub)directory with an empty ``__init__.py`` file so it
|
||||
|
@ -928,13 +923,14 @@ class Pytester:
|
|||
p.joinpath("__init__.py").touch()
|
||||
return p
|
||||
|
||||
def copy_example(self, name: Optional[str] = None) -> Path:
|
||||
def copy_example(self, name: str | None = None) -> Path:
|
||||
"""Copy file from project's directory into the testdir.
|
||||
|
||||
:param name:
|
||||
The name of the file to copy.
|
||||
:return:
|
||||
Path to the copied directory (inside ``self.path``).
|
||||
:rtype: pathlib.Path
|
||||
"""
|
||||
example_dir_ = self._request.config.getini("pytester_example_dir")
|
||||
if example_dir_ is None:
|
||||
|
@ -973,9 +969,7 @@ class Pytester:
|
|||
f'example "{example_path}" is not found as a file or directory'
|
||||
)
|
||||
|
||||
def getnode(
|
||||
self, config: Config, arg: Union[str, "os.PathLike[str]"]
|
||||
) -> Union[Collector, Item]:
|
||||
def getnode(self, config: Config, arg: str | os.PathLike[str]) -> Collector | Item:
|
||||
"""Get the collection node of a file.
|
||||
|
||||
:param config:
|
||||
|
@ -994,9 +988,7 @@ class Pytester:
|
|||
config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK)
|
||||
return res
|
||||
|
||||
def getpathnode(
|
||||
self, path: Union[str, "os.PathLike[str]"]
|
||||
) -> Union[Collector, Item]:
|
||||
def getpathnode(self, path: str | os.PathLike[str]) -> Collector | Item:
|
||||
"""Return the collection node of a file.
|
||||
|
||||
This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to
|
||||
|
@ -1016,7 +1008,7 @@ class Pytester:
|
|||
config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK)
|
||||
return res
|
||||
|
||||
def genitems(self, colitems: Sequence[Union[Item, Collector]]) -> List[Item]:
|
||||
def genitems(self, colitems: Sequence[Item | Collector]) -> list[Item]:
|
||||
"""Generate all test items from a collection node.
|
||||
|
||||
This recurses into the collection node and returns a list of all the
|
||||
|
@ -1028,7 +1020,7 @@ class Pytester:
|
|||
The collected items.
|
||||
"""
|
||||
session = colitems[0].session
|
||||
result: List[Item] = []
|
||||
result: list[Item] = []
|
||||
for colitem in colitems:
|
||||
result.extend(session.genitems(colitem))
|
||||
return result
|
||||
|
@ -1062,7 +1054,7 @@ class Pytester:
|
|||
values = [*list(cmdlineargs), p]
|
||||
return self.inline_run(*values)
|
||||
|
||||
def inline_genitems(self, *args) -> Tuple[List[Item], HookRecorder]:
|
||||
def inline_genitems(self, *args) -> tuple[list[Item], HookRecorder]:
|
||||
"""Run ``pytest.main(['--collect-only'])`` in-process.
|
||||
|
||||
Runs the :py:func:`pytest.main` function to run all of pytest inside
|
||||
|
@ -1075,7 +1067,7 @@ class Pytester:
|
|||
|
||||
def inline_run(
|
||||
self,
|
||||
*args: Union[str, "os.PathLike[str]"],
|
||||
*args: str | os.PathLike[str],
|
||||
plugins=(),
|
||||
no_reraise_ctrlc: bool = False,
|
||||
) -> HookRecorder:
|
||||
|
@ -1145,7 +1137,7 @@ class Pytester:
|
|||
finalizer()
|
||||
|
||||
def runpytest_inprocess(
|
||||
self, *args: Union[str, "os.PathLike[str]"], **kwargs: Any
|
||||
self, *args: str | os.PathLike[str], **kwargs: Any
|
||||
) -> RunResult:
|
||||
"""Return result of running pytest in-process, providing a similar
|
||||
interface to what self.runpytest() provides."""
|
||||
|
@ -1188,9 +1180,7 @@ class Pytester:
|
|||
res.reprec = reprec # type: ignore
|
||||
return res
|
||||
|
||||
def runpytest(
|
||||
self, *args: Union[str, "os.PathLike[str]"], **kwargs: Any
|
||||
) -> RunResult:
|
||||
def runpytest(self, *args: str | os.PathLike[str], **kwargs: Any) -> RunResult:
|
||||
"""Run pytest inline or in a subprocess, depending on the command line
|
||||
option "--runpytest" and return a :py:class:`~pytest.RunResult`."""
|
||||
new_args = self._ensure_basetemp(args)
|
||||
|
@ -1201,8 +1191,8 @@ class Pytester:
|
|||
raise RuntimeError(f"Unrecognized runpytest option: {self._method}")
|
||||
|
||||
def _ensure_basetemp(
|
||||
self, args: Sequence[Union[str, "os.PathLike[str]"]]
|
||||
) -> List[Union[str, "os.PathLike[str]"]]:
|
||||
self, args: Sequence[str | os.PathLike[str]]
|
||||
) -> list[str | os.PathLike[str]]:
|
||||
new_args = list(args)
|
||||
for x in new_args:
|
||||
if str(x).startswith("--basetemp"):
|
||||
|
@ -1211,7 +1201,7 @@ class Pytester:
|
|||
new_args.append("--basetemp=%s" % self.path.parent.joinpath("basetemp"))
|
||||
return new_args
|
||||
|
||||
def parseconfig(self, *args: Union[str, "os.PathLike[str]"]) -> Config:
|
||||
def parseconfig(self, *args: str | os.PathLike[str]) -> Config:
|
||||
"""Return a new pytest :class:`pytest.Config` instance from given
|
||||
commandline args.
|
||||
|
||||
|
@ -1235,7 +1225,7 @@ class Pytester:
|
|||
self._request.addfinalizer(config._ensure_unconfigure)
|
||||
return config
|
||||
|
||||
def parseconfigure(self, *args: Union[str, "os.PathLike[str]"]) -> Config:
|
||||
def parseconfigure(self, *args: str | os.PathLike[str]) -> Config:
|
||||
"""Return a new pytest configured Config instance.
|
||||
|
||||
Returns a new :py:class:`pytest.Config` instance like
|
||||
|
@ -1247,7 +1237,7 @@ class Pytester:
|
|||
return config
|
||||
|
||||
def getitem(
|
||||
self, source: Union[str, "os.PathLike[str]"], funcname: str = "test_func"
|
||||
self, source: str | os.PathLike[str], funcname: str = "test_func"
|
||||
) -> Item:
|
||||
"""Return the test item for a test function.
|
||||
|
||||
|
@ -1268,7 +1258,7 @@ class Pytester:
|
|||
return item
|
||||
assert 0, f"{funcname!r} item not found in module:\n{source}\nitems: {items}"
|
||||
|
||||
def getitems(self, source: Union[str, "os.PathLike[str]"]) -> List[Item]:
|
||||
def getitems(self, source: str | os.PathLike[str]) -> list[Item]:
|
||||
"""Return all test items collected from the module.
|
||||
|
||||
Writes the source to a Python file and runs pytest's collection on
|
||||
|
@ -1279,7 +1269,7 @@ class Pytester:
|
|||
|
||||
def getmodulecol(
|
||||
self,
|
||||
source: Union[str, "os.PathLike[str]"],
|
||||
source: str | os.PathLike[str],
|
||||
configargs=(),
|
||||
*,
|
||||
withinit: bool = False,
|
||||
|
@ -1311,9 +1301,7 @@ class Pytester:
|
|||
self.config = config = self.parseconfigure(path, *configargs)
|
||||
return self.getnode(config, path)
|
||||
|
||||
def collect_by_name(
|
||||
self, modcol: Collector, name: str
|
||||
) -> Optional[Union[Item, Collector]]:
|
||||
def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None:
|
||||
"""Return the collection node for name from the module collection.
|
||||
|
||||
Searches a module collection node for a collection node matching the
|
||||
|
@ -1331,10 +1319,10 @@ class Pytester:
|
|||
|
||||
def popen(
|
||||
self,
|
||||
cmdargs: Sequence[Union[str, "os.PathLike[str]"]],
|
||||
stdout: Union[int, TextIO] = subprocess.PIPE,
|
||||
stderr: Union[int, TextIO] = subprocess.PIPE,
|
||||
stdin: Union[NotSetType, bytes, IO[Any], int] = CLOSE_STDIN,
|
||||
cmdargs: Sequence[str | os.PathLike[str]],
|
||||
stdout: int | TextIO = subprocess.PIPE,
|
||||
stderr: int | TextIO = subprocess.PIPE,
|
||||
stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN,
|
||||
**kw,
|
||||
):
|
||||
"""Invoke :py:class:`subprocess.Popen`.
|
||||
|
@ -1369,9 +1357,9 @@ class Pytester:
|
|||
|
||||
def run(
|
||||
self,
|
||||
*cmdargs: Union[str, "os.PathLike[str]"],
|
||||
timeout: Optional[float] = None,
|
||||
stdin: Union[NotSetType, bytes, IO[Any], int] = CLOSE_STDIN,
|
||||
*cmdargs: str | os.PathLike[str],
|
||||
timeout: float | None = None,
|
||||
stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN,
|
||||
) -> RunResult:
|
||||
"""Run a command with arguments.
|
||||
|
||||
|
@ -1399,8 +1387,10 @@ class Pytester:
|
|||
- Otherwise, it is passed through to :py:class:`subprocess.Popen`.
|
||||
For further information in this case, consult the document of the
|
||||
``stdin`` parameter in :py:class:`subprocess.Popen`.
|
||||
:type stdin: _pytest.compat.NotSetType | bytes | IO[Any] | int
|
||||
:returns:
|
||||
The result.
|
||||
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
|
||||
|
@ -1457,10 +1447,10 @@ class Pytester:
|
|||
except UnicodeEncodeError:
|
||||
print(f"couldn't print to {fp} because of encoding")
|
||||
|
||||
def _getpytestargs(self) -> Tuple[str, ...]:
|
||||
def _getpytestargs(self) -> tuple[str, ...]:
|
||||
return sys.executable, "-mpytest"
|
||||
|
||||
def runpython(self, script: "os.PathLike[str]") -> RunResult:
|
||||
def runpython(self, script: os.PathLike[str]) -> RunResult:
|
||||
"""Run a python script using sys.executable as interpreter."""
|
||||
return self.run(sys.executable, script)
|
||||
|
||||
|
@ -1469,7 +1459,7 @@ class Pytester:
|
|||
return self.run(sys.executable, "-c", command)
|
||||
|
||||
def runpytest_subprocess(
|
||||
self, *args: Union[str, "os.PathLike[str]"], timeout: Optional[float] = None
|
||||
self, *args: str | os.PathLike[str], timeout: float | None = None
|
||||
) -> RunResult:
|
||||
"""Run pytest as a subprocess with given arguments.
|
||||
|
||||
|
@ -1496,9 +1486,7 @@ class Pytester:
|
|||
args = self._getpytestargs() + args
|
||||
return self.run(*args, timeout=timeout)
|
||||
|
||||
def spawn_pytest(
|
||||
self, string: str, expect_timeout: float = 10.0
|
||||
) -> "pexpect.spawn":
|
||||
def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn:
|
||||
"""Run pytest using pexpect.
|
||||
|
||||
This makes sure to use the right pytest and sets up the temporary
|
||||
|
@ -1512,7 +1500,7 @@ class Pytester:
|
|||
cmd = f"{invoke} --basetemp={basetemp} {string}"
|
||||
return self.spawn(cmd, expect_timeout=expect_timeout)
|
||||
|
||||
def spawn(self, cmd: str, expect_timeout: float = 10.0) -> "pexpect.spawn":
|
||||
def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn:
|
||||
"""Run a command using pexpect.
|
||||
|
||||
The pexpect child is returned.
|
||||
|
@ -1557,9 +1545,9 @@ class LineMatcher:
|
|||
``text.splitlines()``.
|
||||
"""
|
||||
|
||||
def __init__(self, lines: List[str]) -> None:
|
||||
def __init__(self, lines: list[str]) -> None:
|
||||
self.lines = lines
|
||||
self._log_output: List[str] = []
|
||||
self._log_output: list[str] = []
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Return the entire original text.
|
||||
|
@ -1569,7 +1557,7 @@ class LineMatcher:
|
|||
"""
|
||||
return "\n".join(self.lines)
|
||||
|
||||
def _getlines(self, lines2: Union[str, Sequence[str], Source]) -> Sequence[str]:
|
||||
def _getlines(self, lines2: str | Sequence[str] | Source) -> Sequence[str]:
|
||||
if isinstance(lines2, str):
|
||||
lines2 = Source(lines2)
|
||||
if isinstance(lines2, Source):
|
||||
|
|
|
@ -4,21 +4,19 @@
|
|||
# contain them itself, since it is imported by the `pytest` module,
|
||||
# hence cannot be subject to assertion rewriting, which requires a
|
||||
# module to not be already imported.
|
||||
from typing import Dict
|
||||
from typing import Optional
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
from _pytest.reports import CollectReport
|
||||
from _pytest.reports import TestReport
|
||||
|
||||
|
||||
def assertoutcome(
|
||||
outcomes: Tuple[
|
||||
outcomes: tuple[
|
||||
Sequence[TestReport],
|
||||
Sequence[Union[CollectReport, TestReport]],
|
||||
Sequence[Union[CollectReport, TestReport]],
|
||||
Sequence[CollectReport | TestReport],
|
||||
Sequence[CollectReport | TestReport],
|
||||
],
|
||||
passed: int = 0,
|
||||
skipped: int = 0,
|
||||
|
@ -37,15 +35,15 @@ def assertoutcome(
|
|||
|
||||
|
||||
def assert_outcomes(
|
||||
outcomes: Dict[str, int],
|
||||
outcomes: dict[str, int],
|
||||
passed: int = 0,
|
||||
skipped: int = 0,
|
||||
failed: int = 0,
|
||||
errors: int = 0,
|
||||
xpassed: int = 0,
|
||||
xfailed: int = 0,
|
||||
warnings: Optional[int] = None,
|
||||
deselected: Optional[int] = None,
|
||||
warnings: int | None = None,
|
||||
deselected: int | None = None,
|
||||
) -> None:
|
||||
"""Assert that the specified outcomes appear with the respective
|
||||
numbers (0 means it didn't occur) in the text output from a test run."""
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Python test discovery, setup and run of test functions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
from collections import Counter
|
||||
from collections import defaultdict
|
||||
|
@ -20,16 +22,11 @@ from typing import final
|
|||
from typing import Generator
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Literal
|
||||
from typing import Mapping
|
||||
from typing import Optional
|
||||
from typing import Pattern
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
import _pytest
|
||||
|
@ -113,7 +110,7 @@ def pytest_addoption(parser: Parser) -> None:
|
|||
)
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc: "Metafunc") -> None:
|
||||
def pytest_generate_tests(metafunc: Metafunc) -> None:
|
||||
for marker in metafunc.definition.iter_markers(name="parametrize"):
|
||||
metafunc.parametrize(*marker.args, **marker.kwargs, _param_mark=marker)
|
||||
|
||||
|
@ -153,7 +150,7 @@ def async_warn_and_skip(nodeid: str) -> None:
|
|||
|
||||
|
||||
@hookimpl(trylast=True)
|
||||
def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]:
|
||||
def pytest_pyfunc_call(pyfuncitem: Function) -> object | None:
|
||||
testfunction = pyfuncitem.obj
|
||||
if is_async_function(testfunction):
|
||||
async_warn_and_skip(pyfuncitem.nodeid)
|
||||
|
@ -174,7 +171,7 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]:
|
|||
|
||||
def pytest_collect_directory(
|
||||
path: Path, parent: nodes.Collector
|
||||
) -> Optional[nodes.Collector]:
|
||||
) -> nodes.Collector | None:
|
||||
pkginit = path / "__init__.py"
|
||||
try:
|
||||
has_pkginit = pkginit.is_file()
|
||||
|
@ -186,7 +183,7 @@ def pytest_collect_directory(
|
|||
return None
|
||||
|
||||
|
||||
def pytest_collect_file(file_path: Path, parent: nodes.Collector) -> Optional["Module"]:
|
||||
def pytest_collect_file(file_path: Path, parent: nodes.Collector) -> Module | None:
|
||||
if file_path.suffix == ".py":
|
||||
if not parent.session.isinitpath(file_path):
|
||||
if not path_matches_patterns(
|
||||
|
@ -206,14 +203,14 @@ def path_matches_patterns(path: Path, patterns: Iterable[str]) -> bool:
|
|||
return any(fnmatch_ex(pattern, path) for pattern in patterns)
|
||||
|
||||
|
||||
def pytest_pycollect_makemodule(module_path: Path, parent) -> "Module":
|
||||
def pytest_pycollect_makemodule(module_path: Path, parent) -> Module:
|
||||
return Module.from_parent(parent, path=module_path)
|
||||
|
||||
|
||||
@hookimpl(trylast=True)
|
||||
def pytest_pycollect_makeitem(
|
||||
collector: Union["Module", "Class"], name: str, obj: object
|
||||
) -> Union[None, nodes.Item, nodes.Collector, List[Union[nodes.Item, nodes.Collector]]]:
|
||||
collector: Module | Class, name: str, obj: object
|
||||
) -> None | nodes.Item | nodes.Collector | list[nodes.Item | nodes.Collector]:
|
||||
assert isinstance(collector, (Class, Module)), type(collector)
|
||||
# Nothing was collected elsewhere, let's do it here.
|
||||
if safe_isclass(obj):
|
||||
|
@ -320,7 +317,7 @@ class PyobjMixin(nodes.Node):
|
|||
parts.reverse()
|
||||
return ".".join(parts)
|
||||
|
||||
def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]:
|
||||
def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]:
|
||||
# XXX caching?
|
||||
path, lineno = getfslineno(self.obj)
|
||||
modpath = self.getmodpath()
|
||||
|
@ -390,7 +387,7 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC):
|
|||
return True
|
||||
return False
|
||||
|
||||
def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
|
||||
def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
|
||||
if not getattr(self.obj, "__test__", True):
|
||||
return []
|
||||
|
||||
|
@ -402,11 +399,11 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC):
|
|||
|
||||
# In each class, nodes should be definition ordered.
|
||||
# __dict__ is definition ordered.
|
||||
seen: Set[str] = set()
|
||||
dict_values: List[List[Union[nodes.Item, nodes.Collector]]] = []
|
||||
seen: set[str] = set()
|
||||
dict_values: list[list[nodes.Item | nodes.Collector]] = []
|
||||
ihook = self.ihook
|
||||
for dic in dicts:
|
||||
values: List[Union[nodes.Item, nodes.Collector]] = []
|
||||
values: list[nodes.Item | nodes.Collector] = []
|
||||
# Note: seems like the dict can change during iteration -
|
||||
# be careful not to remove the list() without consideration.
|
||||
for name, obj in list(dic.items()):
|
||||
|
@ -433,7 +430,7 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC):
|
|||
result.extend(values)
|
||||
return result
|
||||
|
||||
def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]:
|
||||
def _genfunctions(self, name: str, funcobj) -> Iterator[Function]:
|
||||
modulecol = self.getparent(Module)
|
||||
assert modulecol is not None
|
||||
module = modulecol.obj
|
||||
|
@ -544,7 +541,7 @@ class Module(nodes.File, PyCollector):
|
|||
def _getobj(self):
|
||||
return importtestmodule(self.path, self.config)
|
||||
|
||||
def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
|
||||
def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
|
||||
self._register_setup_module_fixture()
|
||||
self._register_setup_function_fixture()
|
||||
self.session._fixturemanager.parsefactories(self)
|
||||
|
@ -638,13 +635,13 @@ class Package(nodes.Directory):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
fspath: Optional[LEGACY_PATH],
|
||||
fspath: LEGACY_PATH | None,
|
||||
parent: nodes.Collector,
|
||||
# NOTE: following args are unused:
|
||||
config=None,
|
||||
session=None,
|
||||
nodeid=None,
|
||||
path: Optional[Path] = None,
|
||||
path: Path | None = None,
|
||||
) -> None:
|
||||
# NOTE: Could be just the following, but kept as-is for compat.
|
||||
# super().__init__(self, fspath, parent=parent)
|
||||
|
@ -676,13 +673,13 @@ class Package(nodes.Directory):
|
|||
func = partial(_call_with_optional_argument, teardown_module, init_mod)
|
||||
self.addfinalizer(func)
|
||||
|
||||
def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
|
||||
def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
|
||||
# Always collect __init__.py first.
|
||||
def sort_key(entry: "os.DirEntry[str]") -> object:
|
||||
def sort_key(entry: os.DirEntry[str]) -> object:
|
||||
return (entry.name != "__init__.py", entry.name)
|
||||
|
||||
config = self.config
|
||||
col: Optional[nodes.Collector]
|
||||
col: nodes.Collector | None
|
||||
cols: Sequence[nodes.Collector]
|
||||
ihook = self.ihook
|
||||
for direntry in scandir(self.path, sort_key):
|
||||
|
@ -716,12 +713,12 @@ def _call_with_optional_argument(func, arg) -> None:
|
|||
func()
|
||||
|
||||
|
||||
def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> Optional[object]:
|
||||
def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> object | None:
|
||||
"""Return the attribute from the given object to be used as a setup/teardown
|
||||
xunit-style function, but only if not marked as a fixture to avoid calling it twice.
|
||||
"""
|
||||
for name in names:
|
||||
meth: Optional[object] = getattr(obj, name, None)
|
||||
meth: object | None = getattr(obj, name, None)
|
||||
if meth is not None and fixtures.getfixturemarker(meth) is None:
|
||||
return meth
|
||||
return None
|
||||
|
@ -731,14 +728,14 @@ class Class(PyCollector):
|
|||
"""Collector for test methods (and nested classes) in a Python class."""
|
||||
|
||||
@classmethod
|
||||
def from_parent(cls, parent, *, name, obj=None, **kw) -> "Self": # type: ignore[override]
|
||||
def from_parent(cls, parent, *, name, obj=None, **kw) -> Self: # type: ignore[override]
|
||||
"""The public constructor."""
|
||||
return super().from_parent(name=name, parent=parent, **kw)
|
||||
|
||||
def newinstance(self):
|
||||
return self.obj()
|
||||
|
||||
def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
|
||||
def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
|
||||
if not safe_getattr(self.obj, "__test__", True):
|
||||
return []
|
||||
if hasinit(self.obj):
|
||||
|
@ -868,21 +865,21 @@ class IdMaker:
|
|||
parametersets: Sequence[ParameterSet]
|
||||
# Optionally, a user-provided callable to make IDs for parameters in a
|
||||
# ParameterSet.
|
||||
idfn: Optional[Callable[[Any], Optional[object]]]
|
||||
idfn: Callable[[Any], object | None] | None
|
||||
# Optionally, explicit IDs for ParameterSets by index.
|
||||
ids: Optional[Sequence[Optional[object]]]
|
||||
ids: Sequence[object | None] | None
|
||||
# Optionally, the pytest config.
|
||||
# Used for controlling ASCII escaping, and for calling the
|
||||
# :hook:`pytest_make_parametrize_id` hook.
|
||||
config: Optional[Config]
|
||||
config: Config | None
|
||||
# Optionally, the ID of the node being parametrized.
|
||||
# Used only for clearer error messages.
|
||||
nodeid: Optional[str]
|
||||
nodeid: str | None
|
||||
# Optionally, the ID of the function being parametrized.
|
||||
# Used only for clearer error messages.
|
||||
func_name: Optional[str]
|
||||
func_name: str | None
|
||||
|
||||
def make_unique_parameterset_ids(self) -> List[str]:
|
||||
def make_unique_parameterset_ids(self) -> list[str]:
|
||||
"""Make a unique identifier for each ParameterSet, that may be used to
|
||||
identify the parametrization in a node ID.
|
||||
|
||||
|
@ -899,7 +896,7 @@ class IdMaker:
|
|||
# Record the number of occurrences of each ID.
|
||||
id_counts = Counter(resolved_ids)
|
||||
# Map the ID to its next suffix.
|
||||
id_suffixes: Dict[str, int] = defaultdict(int)
|
||||
id_suffixes: dict[str, int] = defaultdict(int)
|
||||
# Suffix non-unique IDs to make them unique.
|
||||
for index, id in enumerate(resolved_ids):
|
||||
if id_counts[id] > 1:
|
||||
|
@ -946,9 +943,7 @@ class IdMaker:
|
|||
return idval
|
||||
return self._idval_from_argname(argname, idx)
|
||||
|
||||
def _idval_from_function(
|
||||
self, val: object, argname: str, idx: int
|
||||
) -> Optional[str]:
|
||||
def _idval_from_function(self, val: object, argname: str, idx: int) -> str | None:
|
||||
"""Try to make an ID for a parameter in a ParameterSet using the
|
||||
user-provided id callable, if given."""
|
||||
if self.idfn is None:
|
||||
|
@ -964,17 +959,17 @@ class IdMaker:
|
|||
return None
|
||||
return self._idval_from_value(id)
|
||||
|
||||
def _idval_from_hook(self, val: object, argname: str) -> Optional[str]:
|
||||
def _idval_from_hook(self, val: object, argname: str) -> str | None:
|
||||
"""Try to make an ID for a parameter in a ParameterSet by calling the
|
||||
:hook:`pytest_make_parametrize_id` hook."""
|
||||
if self.config:
|
||||
id: Optional[str] = self.config.hook.pytest_make_parametrize_id(
|
||||
id: str | None = self.config.hook.pytest_make_parametrize_id(
|
||||
config=self.config, val=val, argname=argname
|
||||
)
|
||||
return id
|
||||
return None
|
||||
|
||||
def _idval_from_value(self, val: object) -> Optional[str]:
|
||||
def _idval_from_value(self, val: object) -> str | None:
|
||||
"""Try to make an ID for a parameter in a ParameterSet from its value,
|
||||
if the value type is supported."""
|
||||
if isinstance(val, (str, bytes)):
|
||||
|
@ -1032,15 +1027,15 @@ class CallSpec2:
|
|||
|
||||
# arg name -> arg value which will be passed to a fixture or pseudo-fixture
|
||||
# of the same name. (indirect or direct parametrization respectively)
|
||||
params: Dict[str, object] = dataclasses.field(default_factory=dict)
|
||||
params: dict[str, object] = dataclasses.field(default_factory=dict)
|
||||
# arg name -> arg index.
|
||||
indices: Dict[str, int] = dataclasses.field(default_factory=dict)
|
||||
indices: dict[str, int] = dataclasses.field(default_factory=dict)
|
||||
# Used for sorting parametrized resources.
|
||||
_arg2scope: Mapping[str, Scope] = dataclasses.field(default_factory=dict)
|
||||
# Parts which will be added to the item's name in `[..]` separated by "-".
|
||||
_idlist: Sequence[str] = dataclasses.field(default_factory=tuple)
|
||||
# Marks which will be applied to the item.
|
||||
marks: List[Mark] = dataclasses.field(default_factory=list)
|
||||
marks: list[Mark] = dataclasses.field(default_factory=list)
|
||||
|
||||
def setmulti(
|
||||
self,
|
||||
|
@ -1048,10 +1043,10 @@ class CallSpec2:
|
|||
argnames: Iterable[str],
|
||||
valset: Iterable[object],
|
||||
id: str,
|
||||
marks: Iterable[Union[Mark, MarkDecorator]],
|
||||
marks: Iterable[Mark | MarkDecorator],
|
||||
scope: Scope,
|
||||
param_index: int,
|
||||
) -> "CallSpec2":
|
||||
) -> CallSpec2:
|
||||
params = self.params.copy()
|
||||
indices = self.indices.copy()
|
||||
arg2scope = dict(self._arg2scope)
|
||||
|
@ -1099,7 +1094,7 @@ class Metafunc:
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
definition: "FunctionDefinition",
|
||||
definition: FunctionDefinition,
|
||||
fixtureinfo: fixtures.FuncFixtureInfo,
|
||||
config: Config,
|
||||
cls=None,
|
||||
|
@ -1130,19 +1125,17 @@ class Metafunc:
|
|||
self._arg2fixturedefs = fixtureinfo.name2fixturedefs
|
||||
|
||||
# Result of parametrize().
|
||||
self._calls: List[CallSpec2] = []
|
||||
self._calls: list[CallSpec2] = []
|
||||
|
||||
def parametrize(
|
||||
self,
|
||||
argnames: Union[str, Sequence[str]],
|
||||
argvalues: Iterable[Union[ParameterSet, Sequence[object], object]],
|
||||
indirect: Union[bool, Sequence[str]] = False,
|
||||
ids: Optional[
|
||||
Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]]
|
||||
] = None,
|
||||
scope: Optional[_ScopeName] = None,
|
||||
argnames: str | Sequence[str],
|
||||
argvalues: Iterable[ParameterSet | Sequence[object] | object],
|
||||
indirect: bool | Sequence[str] = False,
|
||||
ids: Iterable[object | None] | Callable[[Any], object | None] | None = None,
|
||||
scope: _ScopeName | None = None,
|
||||
*,
|
||||
_param_mark: Optional[Mark] = None,
|
||||
_param_mark: Mark | None = None,
|
||||
) -> None:
|
||||
"""Add new invocations to the underlying test function using the list
|
||||
of argvalues for the given argnames. Parametrization is performed
|
||||
|
@ -1171,7 +1164,7 @@ class Metafunc:
|
|||
If N argnames were specified, argvalues must be a list of
|
||||
N-tuples, where each tuple-element specifies a value for its
|
||||
respective argname.
|
||||
|
||||
:type argvalues: Iterable[_pytest.mark.structures.ParameterSet | Sequence[object] | object]
|
||||
:param indirect:
|
||||
A list of arguments' names (subset of argnames) or a boolean.
|
||||
If True the list contains all names from the argnames. Each
|
||||
|
@ -1271,7 +1264,7 @@ class Metafunc:
|
|||
if node is None:
|
||||
name2pseudofixturedef = None
|
||||
else:
|
||||
default: Dict[str, FixtureDef[Any]] = {}
|
||||
default: dict[str, FixtureDef[Any]] = {}
|
||||
name2pseudofixturedef = node.stash.setdefault(
|
||||
name2pseudofixturedef_key, default
|
||||
)
|
||||
|
@ -1318,12 +1311,10 @@ class Metafunc:
|
|||
def _resolve_parameter_set_ids(
|
||||
self,
|
||||
argnames: Sequence[str],
|
||||
ids: Optional[
|
||||
Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]]
|
||||
],
|
||||
ids: Iterable[object | None] | Callable[[Any], object | None] | None,
|
||||
parametersets: Sequence[ParameterSet],
|
||||
nodeid: str,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
"""Resolve the actual ids for the given parameter sets.
|
||||
|
||||
:param argnames:
|
||||
|
@ -1361,10 +1352,10 @@ class Metafunc:
|
|||
|
||||
def _validate_ids(
|
||||
self,
|
||||
ids: Iterable[Optional[object]],
|
||||
ids: Iterable[object | None],
|
||||
parametersets: Sequence[ParameterSet],
|
||||
func_name: str,
|
||||
) -> List[Optional[object]]:
|
||||
) -> list[object | None]:
|
||||
try:
|
||||
num_ids = len(ids) # type: ignore[arg-type]
|
||||
except TypeError:
|
||||
|
@ -1384,8 +1375,8 @@ class Metafunc:
|
|||
def _resolve_args_directness(
|
||||
self,
|
||||
argnames: Sequence[str],
|
||||
indirect: Union[bool, Sequence[str]],
|
||||
) -> Dict[str, Literal["indirect", "direct"]]:
|
||||
indirect: bool | Sequence[str],
|
||||
) -> dict[str, Literal["indirect", "direct"]]:
|
||||
"""Resolve if each parametrized argument must be considered an indirect
|
||||
parameter to a fixture of the same name, or a direct parameter to the
|
||||
parametrized function, based on the ``indirect`` parameter of the
|
||||
|
@ -1398,7 +1389,7 @@ class Metafunc:
|
|||
:returns
|
||||
A dict mapping each arg name to either "indirect" or "direct".
|
||||
"""
|
||||
arg_directness: Dict[str, Literal["indirect", "direct"]]
|
||||
arg_directness: dict[str, Literal["indirect", "direct"]]
|
||||
if isinstance(indirect, bool):
|
||||
arg_directness = dict.fromkeys(
|
||||
argnames, "indirect" if indirect else "direct"
|
||||
|
@ -1423,7 +1414,7 @@ class Metafunc:
|
|||
def _validate_if_using_arg_names(
|
||||
self,
|
||||
argnames: Sequence[str],
|
||||
indirect: Union[bool, Sequence[str]],
|
||||
indirect: bool | Sequence[str],
|
||||
) -> None:
|
||||
"""Check if all argnames are being used, by default values, or directly/indirectly.
|
||||
|
||||
|
@ -1454,7 +1445,7 @@ class Metafunc:
|
|||
def _find_parametrized_scope(
|
||||
argnames: Sequence[str],
|
||||
arg2fixturedefs: Mapping[str, Sequence[fixtures.FixtureDef[object]]],
|
||||
indirect: Union[bool, Sequence[str]],
|
||||
indirect: bool | Sequence[str],
|
||||
) -> Scope:
|
||||
"""Find the most appropriate scope for a parametrized call based on its arguments.
|
||||
|
||||
|
@ -1483,7 +1474,7 @@ def _find_parametrized_scope(
|
|||
return Scope.Function
|
||||
|
||||
|
||||
def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) -> str:
|
||||
def _ascii_escaped_by_config(val: str | bytes, config: Config | None) -> str:
|
||||
if config is None:
|
||||
escape_option = False
|
||||
else:
|
||||
|
@ -1532,13 +1523,13 @@ class Function(PyobjMixin, nodes.Item):
|
|||
self,
|
||||
name: str,
|
||||
parent,
|
||||
config: Optional[Config] = None,
|
||||
callspec: Optional[CallSpec2] = None,
|
||||
config: Config | None = None,
|
||||
callspec: CallSpec2 | None = None,
|
||||
callobj=NOTSET,
|
||||
keywords: Optional[Mapping[str, Any]] = None,
|
||||
session: Optional[Session] = None,
|
||||
fixtureinfo: Optional[FuncFixtureInfo] = None,
|
||||
originalname: Optional[str] = None,
|
||||
keywords: Mapping[str, Any] | None = None,
|
||||
session: Session | None = None,
|
||||
fixtureinfo: FuncFixtureInfo | None = None,
|
||||
originalname: str | None = None,
|
||||
) -> None:
|
||||
super().__init__(name, parent, config=config, session=session)
|
||||
|
||||
|
@ -1581,12 +1572,12 @@ class Function(PyobjMixin, nodes.Item):
|
|||
|
||||
# todo: determine sound type limitations
|
||||
@classmethod
|
||||
def from_parent(cls, parent, **kw) -> "Self":
|
||||
def from_parent(cls, parent, **kw) -> Self:
|
||||
"""The public constructor."""
|
||||
return super().from_parent(parent=parent, **kw)
|
||||
|
||||
def _initrequest(self) -> None:
|
||||
self.funcargs: Dict[str, object] = {}
|
||||
self.funcargs: dict[str, object] = {}
|
||||
self._request = fixtures.TopRequest(self, _ispytest=True)
|
||||
|
||||
@property
|
||||
|
@ -1667,7 +1658,7 @@ class Function(PyobjMixin, nodes.Item):
|
|||
def repr_failure( # type: ignore[override]
|
||||
self,
|
||||
excinfo: ExceptionInfo[BaseException],
|
||||
) -> Union[str, TerminalRepr]:
|
||||
) -> str | TerminalRepr:
|
||||
style = self.config.getoption("tbstyle", "auto")
|
||||
if style == "auto":
|
||||
style = "long"
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# mypy: allow-untyped-defs
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Collection
|
||||
from collections.abc import Sized
|
||||
from decimal import Decimal
|
||||
|
@ -11,9 +13,7 @@ from typing import Callable
|
|||
from typing import cast
|
||||
from typing import ContextManager
|
||||
from typing import final
|
||||
from typing import List
|
||||
from typing import Mapping
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Pattern
|
||||
from typing import Sequence
|
||||
|
@ -21,7 +21,6 @@ from typing import Tuple
|
|||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
import _pytest._code
|
||||
from _pytest.outcomes import fail
|
||||
|
@ -33,12 +32,12 @@ if TYPE_CHECKING:
|
|||
|
||||
def _compare_approx(
|
||||
full_object: object,
|
||||
message_data: Sequence[Tuple[str, str, str]],
|
||||
message_data: Sequence[tuple[str, str, str]],
|
||||
number_of_elements: int,
|
||||
different_ids: Sequence[object],
|
||||
max_abs_diff: float,
|
||||
max_rel_diff: float,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
message_list = list(message_data)
|
||||
message_list.insert(0, ("Index", "Obtained", "Expected"))
|
||||
max_sizes = [0, 0, 0]
|
||||
|
@ -79,7 +78,7 @@ class ApproxBase:
|
|||
def __repr__(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def _repr_compare(self, other_side: Any) -> List[str]:
|
||||
def _repr_compare(self, other_side: Any) -> list[str]:
|
||||
return [
|
||||
"comparison failed",
|
||||
f"Obtained: {other_side}",
|
||||
|
@ -103,7 +102,7 @@ class ApproxBase:
|
|||
def __ne__(self, actual) -> bool:
|
||||
return not (actual == self)
|
||||
|
||||
def _approx_scalar(self, x) -> "ApproxScalar":
|
||||
def _approx_scalar(self, x) -> ApproxScalar:
|
||||
if isinstance(x, Decimal):
|
||||
return ApproxDecimal(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok)
|
||||
return ApproxScalar(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok)
|
||||
|
@ -142,12 +141,12 @@ class ApproxNumpy(ApproxBase):
|
|||
)
|
||||
return f"approx({list_scalars!r})"
|
||||
|
||||
def _repr_compare(self, other_side: Union["ndarray", List[Any]]) -> List[str]:
|
||||
def _repr_compare(self, other_side: ndarray | list[Any]) -> list[str]:
|
||||
import itertools
|
||||
import math
|
||||
|
||||
def get_value_from_nested_list(
|
||||
nested_list: List[Any], nd_index: Tuple[Any, ...]
|
||||
nested_list: list[Any], nd_index: tuple[Any, ...]
|
||||
) -> Any:
|
||||
"""
|
||||
Helper function to get the value out of a nested list, given an n-dimensional index.
|
||||
|
@ -244,7 +243,7 @@ class ApproxMapping(ApproxBase):
|
|||
def __repr__(self) -> str:
|
||||
return f"approx({({k: self._approx_scalar(v) for k, v in self.expected.items()})!r})"
|
||||
|
||||
def _repr_compare(self, other_side: Mapping[object, float]) -> List[str]:
|
||||
def _repr_compare(self, other_side: Mapping[object, float]) -> list[str]:
|
||||
import math
|
||||
|
||||
approx_side_as_map = {
|
||||
|
@ -319,7 +318,7 @@ class ApproxSequenceLike(ApproxBase):
|
|||
seq_type = list
|
||||
return f"approx({seq_type(self._approx_scalar(x) for x in self.expected)!r})"
|
||||
|
||||
def _repr_compare(self, other_side: Sequence[float]) -> List[str]:
|
||||
def _repr_compare(self, other_side: Sequence[float]) -> list[str]:
|
||||
import math
|
||||
|
||||
if len(self.expected) != len(other_side):
|
||||
|
@ -384,8 +383,8 @@ class ApproxScalar(ApproxBase):
|
|||
|
||||
# Using Real should be better than this Union, but not possible yet:
|
||||
# https://github.com/python/typeshed/pull/3108
|
||||
DEFAULT_ABSOLUTE_TOLERANCE: Union[float, Decimal] = 1e-12
|
||||
DEFAULT_RELATIVE_TOLERANCE: Union[float, Decimal] = 1e-6
|
||||
DEFAULT_ABSOLUTE_TOLERANCE: float | Decimal = 1e-12
|
||||
DEFAULT_RELATIVE_TOLERANCE: float | Decimal = 1e-6
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Return a string communicating both the expected value and the
|
||||
|
@ -715,7 +714,7 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
|
|||
__tracebackhide__ = True
|
||||
|
||||
if isinstance(expected, Decimal):
|
||||
cls: Type[ApproxBase] = ApproxDecimal
|
||||
cls: type[ApproxBase] = ApproxDecimal
|
||||
elif isinstance(expected, Mapping):
|
||||
cls = ApproxMapping
|
||||
elif _is_numpy_array(expected):
|
||||
|
@ -744,7 +743,7 @@ def _is_numpy_array(obj: object) -> bool:
|
|||
return _as_numpy_array(obj) is not None
|
||||
|
||||
|
||||
def _as_numpy_array(obj: object) -> Optional["ndarray"]:
|
||||
def _as_numpy_array(obj: object) -> ndarray | None:
|
||||
"""
|
||||
Return an ndarray if the given object is implicitly convertible to ndarray,
|
||||
and numpy is already imported, otherwise None.
|
||||
|
@ -770,15 +769,15 @@ E = TypeVar("E", bound=BaseException)
|
|||
|
||||
@overload
|
||||
def raises(
|
||||
expected_exception: Union[Type[E], Tuple[Type[E], ...]],
|
||||
expected_exception: type[E] | tuple[type[E], ...],
|
||||
*,
|
||||
match: Optional[Union[str, Pattern[str]]] = ...,
|
||||
) -> "RaisesContext[E]": ...
|
||||
match: str | Pattern[str] | None = ...,
|
||||
) -> RaisesContext[E]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def raises(
|
||||
expected_exception: Union[Type[E], Tuple[Type[E], ...]],
|
||||
expected_exception: type[E] | tuple[type[E], ...],
|
||||
func: Callable[..., Any],
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
|
@ -786,8 +785,8 @@ def raises(
|
|||
|
||||
|
||||
def raises(
|
||||
expected_exception: Union[Type[E], Tuple[Type[E], ...]], *args: Any, **kwargs: Any
|
||||
) -> Union["RaisesContext[E]", _pytest._code.ExceptionInfo[E]]:
|
||||
expected_exception: type[E] | tuple[type[E], ...], *args: Any, **kwargs: Any
|
||||
) -> RaisesContext[E] | _pytest._code.ExceptionInfo[E]:
|
||||
r"""Assert that a code block/function call raises an exception type, or one of its subclasses.
|
||||
|
||||
:param expected_exception:
|
||||
|
@ -935,7 +934,7 @@ def raises(
|
|||
f"any special code to say 'this should never raise an exception'."
|
||||
)
|
||||
if isinstance(expected_exception, type):
|
||||
expected_exceptions: Tuple[Type[E], ...] = (expected_exception,)
|
||||
expected_exceptions: tuple[type[E], ...] = (expected_exception,)
|
||||
else:
|
||||
expected_exceptions = expected_exception
|
||||
for exc in expected_exceptions:
|
||||
|
@ -947,7 +946,7 @@ def raises(
|
|||
message = f"DID NOT RAISE {expected_exception}"
|
||||
|
||||
if not args:
|
||||
match: Optional[Union[str, Pattern[str]]] = kwargs.pop("match", None)
|
||||
match: str | Pattern[str] | None = kwargs.pop("match", None)
|
||||
if kwargs:
|
||||
msg = "Unexpected keyword arguments passed to pytest.raises: "
|
||||
msg += ", ".join(sorted(kwargs))
|
||||
|
@ -973,14 +972,14 @@ raises.Exception = fail.Exception # type: ignore
|
|||
class RaisesContext(ContextManager[_pytest._code.ExceptionInfo[E]]):
|
||||
def __init__(
|
||||
self,
|
||||
expected_exception: Union[Type[E], Tuple[Type[E], ...]],
|
||||
expected_exception: type[E] | tuple[type[E], ...],
|
||||
message: str,
|
||||
match_expr: Optional[Union[str, Pattern[str]]] = None,
|
||||
match_expr: str | Pattern[str] | None = None,
|
||||
) -> None:
|
||||
self.expected_exception = expected_exception
|
||||
self.message = message
|
||||
self.match_expr = match_expr
|
||||
self.excinfo: Optional[_pytest._code.ExceptionInfo[E]] = None
|
||||
self.excinfo: _pytest._code.ExceptionInfo[E] | None = None
|
||||
|
||||
def __enter__(self) -> _pytest._code.ExceptionInfo[E]:
|
||||
self.excinfo = _pytest._code.ExceptionInfo.for_later()
|
||||
|
@ -988,9 +987,9 @@ class RaisesContext(ContextManager[_pytest._code.ExceptionInfo[E]]):
|
|||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc_val: Optional[BaseException],
|
||||
exc_tb: Optional[TracebackType],
|
||||
exc_type: type[BaseException] | None,
|
||||
exc_val: BaseException | None,
|
||||
exc_tb: TracebackType | None,
|
||||
) -> bool:
|
||||
__tracebackhide__ = True
|
||||
if exc_type is None:
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Record warnings during test function execution."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pprint import pformat
|
||||
import re
|
||||
from types import TracebackType
|
||||
|
@ -9,14 +11,15 @@ from typing import Callable
|
|||
from typing import final
|
||||
from typing import Generator
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Pattern
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
import warnings
|
||||
|
||||
from _pytest.deprecated import check_ispytest
|
||||
|
@ -29,7 +32,7 @@ T = TypeVar("T")
|
|||
|
||||
|
||||
@fixture
|
||||
def recwarn() -> Generator["WarningsRecorder", None, None]:
|
||||
def recwarn() -> Generator[WarningsRecorder, None, None]:
|
||||
"""Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions.
|
||||
|
||||
See https://docs.pytest.org/en/latest/how-to/capture-warnings.html for information
|
||||
|
@ -42,9 +45,7 @@ def recwarn() -> Generator["WarningsRecorder", None, None]:
|
|||
|
||||
|
||||
@overload
|
||||
def deprecated_call(
|
||||
*, match: Optional[Union[str, Pattern[str]]] = ...
|
||||
) -> "WarningsRecorder": ...
|
||||
def deprecated_call(*, match: str | Pattern[str] | None = ...) -> WarningsRecorder: ...
|
||||
|
||||
|
||||
@overload
|
||||
|
@ -52,8 +53,8 @@ def deprecated_call(func: Callable[..., T], *args: Any, **kwargs: Any) -> T: ...
|
|||
|
||||
|
||||
def deprecated_call(
|
||||
func: Optional[Callable[..., Any]] = None, *args: Any, **kwargs: Any
|
||||
) -> Union["WarningsRecorder", Any]:
|
||||
func: Callable[..., Any] | None = None, *args: Any, **kwargs: Any
|
||||
) -> WarningsRecorder | Any:
|
||||
"""Assert that code produces a ``DeprecationWarning`` or ``PendingDeprecationWarning`` or ``FutureWarning``.
|
||||
|
||||
This function can be used as a context manager::
|
||||
|
@ -87,15 +88,15 @@ def deprecated_call(
|
|||
|
||||
@overload
|
||||
def warns(
|
||||
expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = ...,
|
||||
expected_warning: type[Warning] | tuple[type[Warning], ...] = ...,
|
||||
*,
|
||||
match: Optional[Union[str, Pattern[str]]] = ...,
|
||||
) -> "WarningsChecker": ...
|
||||
match: str | Pattern[str] | None = ...,
|
||||
) -> WarningsChecker: ...
|
||||
|
||||
|
||||
@overload
|
||||
def warns(
|
||||
expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]],
|
||||
expected_warning: type[Warning] | tuple[type[Warning], ...],
|
||||
func: Callable[..., T],
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
|
@ -103,11 +104,11 @@ def warns(
|
|||
|
||||
|
||||
def warns(
|
||||
expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = Warning,
|
||||
expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning,
|
||||
*args: Any,
|
||||
match: Optional[Union[str, Pattern[str]]] = None,
|
||||
match: str | Pattern[str] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> Union["WarningsChecker", Any]:
|
||||
) -> WarningsChecker | Any:
|
||||
r"""Assert that code raises a particular class of warning.
|
||||
|
||||
Specifically, the parameter ``expected_warning`` can be a warning class or tuple
|
||||
|
@ -183,18 +184,18 @@ class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg]
|
|||
check_ispytest(_ispytest)
|
||||
super().__init__(record=True)
|
||||
self._entered = False
|
||||
self._list: List[warnings.WarningMessage] = []
|
||||
self._list: list[warnings.WarningMessage] = []
|
||||
|
||||
@property
|
||||
def list(self) -> List["warnings.WarningMessage"]:
|
||||
def list(self) -> list[warnings.WarningMessage]:
|
||||
"""The list of recorded warnings."""
|
||||
return self._list
|
||||
|
||||
def __getitem__(self, i: int) -> "warnings.WarningMessage":
|
||||
def __getitem__(self, i: int) -> warnings.WarningMessage:
|
||||
"""Get a recorded warning by index."""
|
||||
return self._list[i]
|
||||
|
||||
def __iter__(self) -> Iterator["warnings.WarningMessage"]:
|
||||
def __iter__(self) -> Iterator[warnings.WarningMessage]:
|
||||
"""Iterate through the recorded warnings."""
|
||||
return iter(self._list)
|
||||
|
||||
|
@ -202,12 +203,12 @@ class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg]
|
|||
"""The number of recorded warnings."""
|
||||
return len(self._list)
|
||||
|
||||
def pop(self, cls: Type[Warning] = Warning) -> "warnings.WarningMessage":
|
||||
def pop(self, cls: type[Warning] = Warning) -> warnings.WarningMessage:
|
||||
"""Pop the first recorded warning which is an instance of ``cls``,
|
||||
but not an instance of a child class of any other match.
|
||||
Raises ``AssertionError`` if there is no match.
|
||||
"""
|
||||
best_idx: Optional[int] = None
|
||||
best_idx: int | None = None
|
||||
for i, w in enumerate(self._list):
|
||||
if w.category == cls:
|
||||
return self._list.pop(i) # exact match, stop looking
|
||||
|
@ -225,9 +226,7 @@ class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg]
|
|||
"""Clear the list of recorded warnings."""
|
||||
self._list[:] = []
|
||||
|
||||
# Type ignored because it doesn't exactly warnings.catch_warnings.__enter__
|
||||
# -- it returns a List but we only emulate one.
|
||||
def __enter__(self) -> "WarningsRecorder": # type: ignore
|
||||
def __enter__(self) -> Self:
|
||||
if self._entered:
|
||||
__tracebackhide__ = True
|
||||
raise RuntimeError(f"Cannot enter {self!r} twice")
|
||||
|
@ -240,9 +239,9 @@ class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg]
|
|||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc_val: Optional[BaseException],
|
||||
exc_tb: Optional[TracebackType],
|
||||
exc_type: type[BaseException] | None,
|
||||
exc_val: BaseException | None,
|
||||
exc_tb: TracebackType | None,
|
||||
) -> None:
|
||||
if not self._entered:
|
||||
__tracebackhide__ = True
|
||||
|
@ -259,8 +258,8 @@ class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg]
|
|||
class WarningsChecker(WarningsRecorder):
|
||||
def __init__(
|
||||
self,
|
||||
expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = Warning,
|
||||
match_expr: Optional[Union[str, Pattern[str]]] = None,
|
||||
expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning,
|
||||
match_expr: str | Pattern[str] | None = None,
|
||||
*,
|
||||
_ispytest: bool = False,
|
||||
) -> None:
|
||||
|
@ -291,9 +290,9 @@ class WarningsChecker(WarningsRecorder):
|
|||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc_val: Optional[BaseException],
|
||||
exc_tb: Optional[TracebackType],
|
||||
exc_type: type[BaseException] | None,
|
||||
exc_val: BaseException | None,
|
||||
exc_tb: TracebackType | None,
|
||||
) -> None:
|
||||
super().__exit__(exc_type, exc_val, exc_tb)
|
||||
|
||||
|
|
|
@ -1,24 +1,19 @@
|
|||
# mypy: allow-untyped-defs
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
from io import StringIO
|
||||
import os
|
||||
from pprint import pprint
|
||||
from typing import Any
|
||||
from typing import cast
|
||||
from typing import Dict
|
||||
from typing import final
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Literal
|
||||
from typing import Mapping
|
||||
from typing import NoReturn
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
from _pytest._code.code import ExceptionChainRepr
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
|
@ -39,6 +34,8 @@ from _pytest.outcomes import skip
|
|||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from _pytest.runner import CallInfo
|
||||
|
||||
|
||||
|
@ -54,16 +51,13 @@ def getworkerinfoline(node):
|
|||
return s
|
||||
|
||||
|
||||
_R = TypeVar("_R", bound="BaseReport")
|
||||
|
||||
|
||||
class BaseReport:
|
||||
when: Optional[str]
|
||||
location: Optional[Tuple[str, Optional[int], str]]
|
||||
longrepr: Union[
|
||||
None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr
|
||||
]
|
||||
sections: List[Tuple[str, str]]
|
||||
when: str | None
|
||||
location: tuple[str, int | None, str] | None
|
||||
longrepr: (
|
||||
None | ExceptionInfo[BaseException] | tuple[str, int, str] | str | TerminalRepr
|
||||
)
|
||||
sections: list[tuple[str, str]]
|
||||
nodeid: str
|
||||
outcome: Literal["passed", "failed", "skipped"]
|
||||
|
||||
|
@ -94,7 +88,7 @@ class BaseReport:
|
|||
s = "<unprintable longrepr>"
|
||||
out.line(s)
|
||||
|
||||
def get_sections(self, prefix: str) -> Iterator[Tuple[str, str]]:
|
||||
def get_sections(self, prefix: str) -> Iterator[tuple[str, str]]:
|
||||
for name, content in self.sections:
|
||||
if name.startswith(prefix):
|
||||
yield prefix, content
|
||||
|
@ -176,7 +170,7 @@ class BaseReport:
|
|||
return True
|
||||
|
||||
@property
|
||||
def head_line(self) -> Optional[str]:
|
||||
def head_line(self) -> str | None:
|
||||
"""**Experimental** The head line shown with longrepr output for this
|
||||
report, more commonly during traceback representation during
|
||||
failures::
|
||||
|
@ -202,7 +196,7 @@ class BaseReport:
|
|||
)
|
||||
return verbose
|
||||
|
||||
def _to_json(self) -> Dict[str, Any]:
|
||||
def _to_json(self) -> dict[str, Any]:
|
||||
"""Return the contents of this report as a dict of builtin entries,
|
||||
suitable for serialization.
|
||||
|
||||
|
@ -213,7 +207,7 @@ class BaseReport:
|
|||
return _report_to_json(self)
|
||||
|
||||
@classmethod
|
||||
def _from_json(cls: Type[_R], reportdict: Dict[str, object]) -> _R:
|
||||
def _from_json(cls, reportdict: dict[str, object]) -> Self:
|
||||
"""Create either a TestReport or CollectReport, depending on the calling class.
|
||||
|
||||
It is the callers responsibility to know which class to pass here.
|
||||
|
@ -227,7 +221,7 @@ class BaseReport:
|
|||
|
||||
|
||||
def _report_unserialization_failure(
|
||||
type_name: str, report_class: Type[BaseReport], reportdict
|
||||
type_name: str, report_class: type[BaseReport], reportdict
|
||||
) -> NoReturn:
|
||||
url = "https://github.com/pytest-dev/pytest/issues"
|
||||
stream = StringIO()
|
||||
|
@ -256,18 +250,20 @@ class TestReport(BaseReport):
|
|||
def __init__(
|
||||
self,
|
||||
nodeid: str,
|
||||
location: Tuple[str, Optional[int], str],
|
||||
location: tuple[str, int | None, str],
|
||||
keywords: Mapping[str, Any],
|
||||
outcome: Literal["passed", "failed", "skipped"],
|
||||
longrepr: Union[
|
||||
None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr
|
||||
],
|
||||
longrepr: None
|
||||
| ExceptionInfo[BaseException]
|
||||
| tuple[str, int, str]
|
||||
| str
|
||||
| TerminalRepr,
|
||||
when: Literal["setup", "call", "teardown"],
|
||||
sections: Iterable[Tuple[str, str]] = (),
|
||||
sections: Iterable[tuple[str, str]] = (),
|
||||
duration: float = 0,
|
||||
start: float = 0,
|
||||
stop: float = 0,
|
||||
user_properties: Optional[Iterable[Tuple[str, object]]] = None,
|
||||
user_properties: Iterable[tuple[str, object]] | None = None,
|
||||
**extra,
|
||||
) -> None:
|
||||
#: Normalized collection nodeid.
|
||||
|
@ -278,7 +274,7 @@ class TestReport(BaseReport):
|
|||
#: collected one e.g. if a method is inherited from a different module.
|
||||
#: The filesystempath may be relative to ``config.rootdir``.
|
||||
#: The line number is 0-based.
|
||||
self.location: Tuple[str, Optional[int], str] = location
|
||||
self.location: tuple[str, int | None, str] = location
|
||||
|
||||
#: A name -> value dictionary containing all keywords and
|
||||
#: markers associated with a test invocation.
|
||||
|
@ -317,7 +313,7 @@ class TestReport(BaseReport):
|
|||
return f"<{self.__class__.__name__} {self.nodeid!r} when={self.when!r} outcome={self.outcome!r}>"
|
||||
|
||||
@classmethod
|
||||
def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport":
|
||||
def from_item_and_call(cls, item: Item, call: CallInfo[None]) -> TestReport:
|
||||
"""Create and fill a TestReport with standard item and call info.
|
||||
|
||||
:param item: The item.
|
||||
|
@ -334,13 +330,13 @@ class TestReport(BaseReport):
|
|||
sections = []
|
||||
if not call.excinfo:
|
||||
outcome: Literal["passed", "failed", "skipped"] = "passed"
|
||||
longrepr: Union[
|
||||
None,
|
||||
ExceptionInfo[BaseException],
|
||||
Tuple[str, int, str],
|
||||
str,
|
||||
TerminalRepr,
|
||||
] = None
|
||||
longrepr: (
|
||||
None
|
||||
| ExceptionInfo[BaseException]
|
||||
| tuple[str, int, str]
|
||||
| str
|
||||
| TerminalRepr
|
||||
) = None
|
||||
else:
|
||||
if not isinstance(excinfo, ExceptionInfo):
|
||||
outcome = "failed"
|
||||
|
@ -394,12 +390,14 @@ class CollectReport(BaseReport):
|
|||
def __init__(
|
||||
self,
|
||||
nodeid: str,
|
||||
outcome: "Literal['passed', 'failed', 'skipped']",
|
||||
longrepr: Union[
|
||||
None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr
|
||||
],
|
||||
result: Optional[List[Union[Item, Collector]]],
|
||||
sections: Iterable[Tuple[str, str]] = (),
|
||||
outcome: Literal["passed", "failed", "skipped"],
|
||||
longrepr: None
|
||||
| ExceptionInfo[BaseException]
|
||||
| tuple[str, int, str]
|
||||
| str
|
||||
| TerminalRepr,
|
||||
result: list[Item | Collector] | None,
|
||||
sections: Iterable[tuple[str, str]] = (),
|
||||
**extra,
|
||||
) -> None:
|
||||
#: Normalized collection nodeid.
|
||||
|
@ -425,7 +423,7 @@ class CollectReport(BaseReport):
|
|||
@property
|
||||
def location( # type:ignore[override]
|
||||
self,
|
||||
) -> Optional[Tuple[str, Optional[int], str]]:
|
||||
) -> tuple[str, int | None, str] | None:
|
||||
return (self.fspath, None, self.fspath)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
|
@ -441,8 +439,8 @@ class CollectErrorRepr(TerminalRepr):
|
|||
|
||||
|
||||
def pytest_report_to_serializable(
|
||||
report: Union[CollectReport, TestReport],
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
report: CollectReport | TestReport,
|
||||
) -> dict[str, Any] | None:
|
||||
if isinstance(report, (TestReport, CollectReport)):
|
||||
data = report._to_json()
|
||||
data["$report_type"] = report.__class__.__name__
|
||||
|
@ -452,8 +450,8 @@ def pytest_report_to_serializable(
|
|||
|
||||
|
||||
def pytest_report_from_serializable(
|
||||
data: Dict[str, Any],
|
||||
) -> Optional[Union[CollectReport, TestReport]]:
|
||||
data: dict[str, Any],
|
||||
) -> CollectReport | TestReport | None:
|
||||
if "$report_type" in data:
|
||||
if data["$report_type"] == "TestReport":
|
||||
return TestReport._from_json(data)
|
||||
|
@ -465,7 +463,7 @@ def pytest_report_from_serializable(
|
|||
return None
|
||||
|
||||
|
||||
def _report_to_json(report: BaseReport) -> Dict[str, Any]:
|
||||
def _report_to_json(report: BaseReport) -> dict[str, Any]:
|
||||
"""Return the contents of this report as a dict of builtin entries,
|
||||
suitable for serialization.
|
||||
|
||||
|
@ -473,8 +471,8 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]:
|
|||
"""
|
||||
|
||||
def serialize_repr_entry(
|
||||
entry: Union[ReprEntry, ReprEntryNative],
|
||||
) -> Dict[str, Any]:
|
||||
entry: ReprEntry | ReprEntryNative,
|
||||
) -> dict[str, Any]:
|
||||
data = dataclasses.asdict(entry)
|
||||
for key, value in data.items():
|
||||
if hasattr(value, "__dict__"):
|
||||
|
@ -482,7 +480,7 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]:
|
|||
entry_data = {"type": type(entry).__name__, "data": data}
|
||||
return entry_data
|
||||
|
||||
def serialize_repr_traceback(reprtraceback: ReprTraceback) -> Dict[str, Any]:
|
||||
def serialize_repr_traceback(reprtraceback: ReprTraceback) -> dict[str, Any]:
|
||||
result = dataclasses.asdict(reprtraceback)
|
||||
result["reprentries"] = [
|
||||
serialize_repr_entry(x) for x in reprtraceback.reprentries
|
||||
|
@ -490,18 +488,18 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]:
|
|||
return result
|
||||
|
||||
def serialize_repr_crash(
|
||||
reprcrash: Optional[ReprFileLocation],
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
reprcrash: ReprFileLocation | None,
|
||||
) -> dict[str, Any] | None:
|
||||
if reprcrash is not None:
|
||||
return dataclasses.asdict(reprcrash)
|
||||
else:
|
||||
return None
|
||||
|
||||
def serialize_exception_longrepr(rep: BaseReport) -> Dict[str, Any]:
|
||||
def serialize_exception_longrepr(rep: BaseReport) -> dict[str, Any]:
|
||||
assert rep.longrepr is not None
|
||||
# TODO: Investigate whether the duck typing is really necessary here.
|
||||
longrepr = cast(ExceptionRepr, rep.longrepr)
|
||||
result: Dict[str, Any] = {
|
||||
result: dict[str, Any] = {
|
||||
"reprcrash": serialize_repr_crash(longrepr.reprcrash),
|
||||
"reprtraceback": serialize_repr_traceback(longrepr.reprtraceback),
|
||||
"sections": longrepr.sections,
|
||||
|
@ -538,7 +536,7 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]:
|
|||
return d
|
||||
|
||||
|
||||
def _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]:
|
||||
def _report_kwargs_from_json(reportdict: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Return **kwargs that can be used to construct a TestReport or
|
||||
CollectReport instance.
|
||||
|
||||
|
@ -559,7 +557,7 @@ def _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]:
|
|||
if data["reprlocals"]:
|
||||
reprlocals = ReprLocals(data["reprlocals"]["lines"])
|
||||
|
||||
reprentry: Union[ReprEntry, ReprEntryNative] = ReprEntry(
|
||||
reprentry: ReprEntry | ReprEntryNative = ReprEntry(
|
||||
lines=data["lines"],
|
||||
reprfuncargs=reprfuncargs,
|
||||
reprlocals=reprlocals,
|
||||
|
@ -578,7 +576,7 @@ def _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]:
|
|||
]
|
||||
return ReprTraceback(**repr_traceback_dict)
|
||||
|
||||
def deserialize_repr_crash(repr_crash_dict: Optional[Dict[str, Any]]):
|
||||
def deserialize_repr_crash(repr_crash_dict: dict[str, Any] | None):
|
||||
if repr_crash_dict is not None:
|
||||
return ReprFileLocation(**repr_crash_dict)
|
||||
else:
|
||||
|
@ -605,8 +603,8 @@ def _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]:
|
|||
description,
|
||||
)
|
||||
)
|
||||
exception_info: Union[ExceptionChainRepr, ReprExceptionInfo] = (
|
||||
ExceptionChainRepr(chain)
|
||||
exception_info: ExceptionChainRepr | ReprExceptionInfo = ExceptionChainRepr(
|
||||
chain
|
||||
)
|
||||
else:
|
||||
exception_info = ReprExceptionInfo(
|
||||
|
|
|
@ -1,23 +1,19 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Basic collect and runtest protocol implementations."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import bdb
|
||||
import dataclasses
|
||||
import os
|
||||
import sys
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
from typing import Dict
|
||||
from typing import final
|
||||
from typing import Generic
|
||||
from typing import List
|
||||
from typing import Literal
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
from .reports import BaseReport
|
||||
from .reports import CollectErrorRepr
|
||||
|
@ -71,7 +67,7 @@ def pytest_addoption(parser: Parser) -> None:
|
|||
)
|
||||
|
||||
|
||||
def pytest_terminal_summary(terminalreporter: "TerminalReporter") -> None:
|
||||
def pytest_terminal_summary(terminalreporter: TerminalReporter) -> None:
|
||||
durations = terminalreporter.config.option.durations
|
||||
durations_min = terminalreporter.config.option.durations_min
|
||||
verbose = terminalreporter.config.getvalue("verbose")
|
||||
|
@ -102,15 +98,15 @@ def pytest_terminal_summary(terminalreporter: "TerminalReporter") -> None:
|
|||
tr.write_line(f"{rep.duration:02.2f}s {rep.when:<8} {rep.nodeid}")
|
||||
|
||||
|
||||
def pytest_sessionstart(session: "Session") -> None:
|
||||
def pytest_sessionstart(session: Session) -> None:
|
||||
session._setupstate = SetupState()
|
||||
|
||||
|
||||
def pytest_sessionfinish(session: "Session") -> None:
|
||||
def pytest_sessionfinish(session: Session) -> None:
|
||||
session._setupstate.teardown_exact(None)
|
||||
|
||||
|
||||
def pytest_runtest_protocol(item: Item, nextitem: Optional[Item]) -> bool:
|
||||
def pytest_runtest_protocol(item: Item, nextitem: Item | None) -> bool:
|
||||
ihook = item.ihook
|
||||
ihook.pytest_runtest_logstart(nodeid=item.nodeid, location=item.location)
|
||||
runtestprotocol(item, nextitem=nextitem)
|
||||
|
@ -119,8 +115,8 @@ def pytest_runtest_protocol(item: Item, nextitem: Optional[Item]) -> bool:
|
|||
|
||||
|
||||
def runtestprotocol(
|
||||
item: Item, log: bool = True, nextitem: Optional[Item] = None
|
||||
) -> List[TestReport]:
|
||||
item: Item, log: bool = True, nextitem: Item | None = None
|
||||
) -> list[TestReport]:
|
||||
hasrequest = hasattr(item, "_request")
|
||||
if hasrequest and not item._request: # type: ignore[attr-defined]
|
||||
# This only happens if the item is re-run, as is done by
|
||||
|
@ -183,14 +179,14 @@ def pytest_runtest_call(item: Item) -> None:
|
|||
raise e
|
||||
|
||||
|
||||
def pytest_runtest_teardown(item: Item, nextitem: Optional[Item]) -> None:
|
||||
def pytest_runtest_teardown(item: Item, nextitem: Item | None) -> None:
|
||||
_update_current_test_var(item, "teardown")
|
||||
item.session._setupstate.teardown_exact(nextitem)
|
||||
_update_current_test_var(item, None)
|
||||
|
||||
|
||||
def _update_current_test_var(
|
||||
item: Item, when: Optional[Literal["setup", "call", "teardown"]]
|
||||
item: Item, when: Literal["setup", "call", "teardown"] | None
|
||||
) -> None:
|
||||
"""Update :envvar:`PYTEST_CURRENT_TEST` to reflect the current item and stage.
|
||||
|
||||
|
@ -206,7 +202,7 @@ def _update_current_test_var(
|
|||
os.environ.pop(var_name)
|
||||
|
||||
|
||||
def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]:
|
||||
def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str] | None:
|
||||
if report.when in ("setup", "teardown"):
|
||||
if report.failed:
|
||||
# category, shortletter, verbose-word
|
||||
|
@ -234,7 +230,7 @@ def call_and_report(
|
|||
runtest_hook = ihook.pytest_runtest_teardown
|
||||
else:
|
||||
assert False, f"Unhandled runtest hook case: {when}"
|
||||
reraise: Tuple[Type[BaseException], ...] = (Exit,)
|
||||
reraise: tuple[type[BaseException], ...] = (Exit,)
|
||||
if not item.config.getoption("usepdb", False):
|
||||
reraise += (KeyboardInterrupt,)
|
||||
call = CallInfo.from_call(
|
||||
|
@ -248,7 +244,7 @@ def call_and_report(
|
|||
return report
|
||||
|
||||
|
||||
def check_interactive_exception(call: "CallInfo[object]", report: BaseReport) -> bool:
|
||||
def check_interactive_exception(call: CallInfo[object], report: BaseReport) -> bool:
|
||||
"""Check whether the call raised an exception that should be reported as
|
||||
interactive."""
|
||||
if call.excinfo is None:
|
||||
|
@ -271,9 +267,9 @@ TResult = TypeVar("TResult", covariant=True)
|
|||
class CallInfo(Generic[TResult]):
|
||||
"""Result/Exception info of a function invocation."""
|
||||
|
||||
_result: Optional[TResult]
|
||||
_result: TResult | None
|
||||
#: The captured exception of the call, if it raised.
|
||||
excinfo: Optional[ExceptionInfo[BaseException]]
|
||||
excinfo: ExceptionInfo[BaseException] | None
|
||||
#: The system time when the call started, in seconds since the epoch.
|
||||
start: float
|
||||
#: The system time when the call ended, in seconds since the epoch.
|
||||
|
@ -285,8 +281,8 @@ class CallInfo(Generic[TResult]):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
result: Optional[TResult],
|
||||
excinfo: Optional[ExceptionInfo[BaseException]],
|
||||
result: TResult | None,
|
||||
excinfo: ExceptionInfo[BaseException] | None,
|
||||
start: float,
|
||||
stop: float,
|
||||
duration: float,
|
||||
|
@ -320,14 +316,13 @@ class CallInfo(Generic[TResult]):
|
|||
cls,
|
||||
func: Callable[[], TResult],
|
||||
when: Literal["collect", "setup", "call", "teardown"],
|
||||
reraise: Optional[
|
||||
Union[Type[BaseException], Tuple[Type[BaseException], ...]]
|
||||
] = None,
|
||||
) -> "CallInfo[TResult]":
|
||||
reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None,
|
||||
) -> CallInfo[TResult]:
|
||||
"""Call func, wrapping the result in a CallInfo.
|
||||
|
||||
:param func:
|
||||
The function to call. Called without arguments.
|
||||
:type func: Callable[[], _pytest.runner.TResult]
|
||||
:param when:
|
||||
The phase in which the function is called.
|
||||
:param reraise:
|
||||
|
@ -338,7 +333,7 @@ class CallInfo(Generic[TResult]):
|
|||
start = timing.time()
|
||||
precise_start = timing.perf_counter()
|
||||
try:
|
||||
result: Optional[TResult] = func()
|
||||
result: TResult | None = func()
|
||||
except BaseException:
|
||||
excinfo = ExceptionInfo.from_current()
|
||||
if reraise is not None and isinstance(excinfo.value, reraise):
|
||||
|
@ -369,7 +364,7 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport:
|
|||
|
||||
|
||||
def pytest_make_collect_report(collector: Collector) -> CollectReport:
|
||||
def collect() -> List[Union[Item, Collector]]:
|
||||
def collect() -> list[Item | Collector]:
|
||||
# Before collecting, if this is a Directory, load the conftests.
|
||||
# If a conftest import fails to load, it is considered a collection
|
||||
# error of the Directory collector. This is why it's done inside of the
|
||||
|
@ -391,7 +386,7 @@ def pytest_make_collect_report(collector: Collector) -> CollectReport:
|
|||
call = CallInfo.from_call(
|
||||
collect, "collect", reraise=(KeyboardInterrupt, SystemExit)
|
||||
)
|
||||
longrepr: Union[None, Tuple[str, int, str], str, TerminalRepr] = None
|
||||
longrepr: None | tuple[str, int, str] | str | TerminalRepr = None
|
||||
if not call.excinfo:
|
||||
outcome: Literal["passed", "skipped", "failed"] = "passed"
|
||||
else:
|
||||
|
@ -485,13 +480,13 @@ class SetupState:
|
|||
|
||||
def __init__(self) -> None:
|
||||
# The stack is in the dict insertion order.
|
||||
self.stack: Dict[
|
||||
self.stack: dict[
|
||||
Node,
|
||||
Tuple[
|
||||
tuple[
|
||||
# Node's finalizers.
|
||||
List[Callable[[], object]],
|
||||
# Node's exception, if its setup raised.
|
||||
Optional[Union[OutcomeException, Exception]],
|
||||
list[Callable[[], object]],
|
||||
# Node's exception and original traceback, if its setup raised.
|
||||
OutcomeException | Exception | None,
|
||||
],
|
||||
] = {}
|
||||
|
||||
|
@ -526,7 +521,7 @@ class SetupState:
|
|||
assert node in self.stack, (node, self.stack)
|
||||
self.stack[node][0].append(finalizer)
|
||||
|
||||
def teardown_exact(self, nextitem: Optional[Item]) -> None:
|
||||
def teardown_exact(self, nextitem: Item | None) -> None:
|
||||
"""Teardown the current stack up until reaching nodes that nextitem
|
||||
also descends from.
|
||||
|
||||
|
@ -534,7 +529,7 @@ class SetupState:
|
|||
stack is torn down.
|
||||
"""
|
||||
needed_collectors = nextitem and nextitem.listchain() or []
|
||||
exceptions: List[BaseException] = []
|
||||
exceptions: list[BaseException] = []
|
||||
while self.stack:
|
||||
if list(self.stack.keys()) == needed_collectors[: len(self.stack)]:
|
||||
break
|
||||
|
|
|
@ -8,10 +8,11 @@ would cause circular references.
|
|||
Also this makes the module light to import, as it should.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from functools import total_ordering
|
||||
from typing import Literal
|
||||
from typing import Optional
|
||||
|
||||
|
||||
_ScopeName = Literal["session", "package", "module", "class", "function"]
|
||||
|
@ -38,29 +39,29 @@ class Scope(Enum):
|
|||
Package: _ScopeName = "package"
|
||||
Session: _ScopeName = "session"
|
||||
|
||||
def next_lower(self) -> "Scope":
|
||||
def next_lower(self) -> Scope:
|
||||
"""Return the next lower scope."""
|
||||
index = _SCOPE_INDICES[self]
|
||||
if index == 0:
|
||||
raise ValueError(f"{self} is the lower-most scope")
|
||||
return _ALL_SCOPES[index - 1]
|
||||
|
||||
def next_higher(self) -> "Scope":
|
||||
def next_higher(self) -> Scope:
|
||||
"""Return the next higher scope."""
|
||||
index = _SCOPE_INDICES[self]
|
||||
if index == len(_SCOPE_INDICES) - 1:
|
||||
raise ValueError(f"{self} is the upper-most scope")
|
||||
return _ALL_SCOPES[index + 1]
|
||||
|
||||
def __lt__(self, other: "Scope") -> bool:
|
||||
def __lt__(self, other: Scope) -> bool:
|
||||
self_index = _SCOPE_INDICES[self]
|
||||
other_index = _SCOPE_INDICES[other]
|
||||
return self_index < other_index
|
||||
|
||||
@classmethod
|
||||
def from_user(
|
||||
cls, scope_name: _ScopeName, descr: str, where: Optional[str] = None
|
||||
) -> "Scope":
|
||||
cls, scope_name: _ScopeName, descr: str, where: str | None = None
|
||||
) -> Scope:
|
||||
"""
|
||||
Given a scope name from the user, return the equivalent Scope enum. Should be used
|
||||
whenever we want to convert a user provided scope name to its enum object.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Generator
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.config import Config
|
||||
|
@ -96,7 +96,7 @@ def _show_fixture_action(
|
|||
|
||||
|
||||
@pytest.hookimpl(tryfirst=True)
|
||||
def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
|
||||
def pytest_cmdline_main(config: Config) -> int | ExitCode | None:
|
||||
if config.option.setuponly:
|
||||
config.option.setupshow = True
|
||||
return None
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from typing import Optional
|
||||
from typing import Union
|
||||
from __future__ import annotations
|
||||
|
||||
from _pytest.config import Config
|
||||
from _pytest.config import ExitCode
|
||||
|
@ -23,7 +22,7 @@ def pytest_addoption(parser: Parser) -> None:
|
|||
@pytest.hookimpl(tryfirst=True)
|
||||
def pytest_fixture_setup(
|
||||
fixturedef: FixtureDef[object], request: SubRequest
|
||||
) -> Optional[object]:
|
||||
) -> object | None:
|
||||
# Will return a dummy fixture if the setuponly option is provided.
|
||||
if request.config.option.setupplan:
|
||||
my_cache_key = fixturedef.cache_key(request)
|
||||
|
@ -33,7 +32,7 @@ def pytest_fixture_setup(
|
|||
|
||||
|
||||
@pytest.hookimpl(tryfirst=True)
|
||||
def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
|
||||
def pytest_cmdline_main(config: Config) -> int | ExitCode | None:
|
||||
if config.option.setupplan:
|
||||
config.option.setuponly = True
|
||||
config.option.setupshow = True
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Support for skip/xfail functions and markers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
import dataclasses
|
||||
import os
|
||||
|
@ -9,8 +11,6 @@ import sys
|
|||
import traceback
|
||||
from typing import Generator
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
|
||||
from _pytest.config import Config
|
||||
from _pytest.config import hookimpl
|
||||
|
@ -84,7 +84,7 @@ def pytest_configure(config: Config) -> None:
|
|||
)
|
||||
|
||||
|
||||
def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, str]:
|
||||
def evaluate_condition(item: Item, mark: Mark, condition: object) -> tuple[bool, str]:
|
||||
"""Evaluate a single skipif/xfail condition.
|
||||
|
||||
If an old-style string condition is given, it is eval()'d, otherwise the
|
||||
|
@ -164,7 +164,7 @@ class Skip:
|
|||
reason: str = "unconditional skip"
|
||||
|
||||
|
||||
def evaluate_skip_marks(item: Item) -> Optional[Skip]:
|
||||
def evaluate_skip_marks(item: Item) -> Skip | None:
|
||||
"""Evaluate skip and skipif marks on item, returning Skip if triggered."""
|
||||
for mark in item.iter_markers(name="skipif"):
|
||||
if "condition" not in mark.kwargs:
|
||||
|
@ -201,10 +201,10 @@ class Xfail:
|
|||
reason: str
|
||||
run: bool
|
||||
strict: bool
|
||||
raises: Optional[Tuple[Type[BaseException], ...]]
|
||||
raises: tuple[type[BaseException], ...] | None
|
||||
|
||||
|
||||
def evaluate_xfail_marks(item: Item) -> Optional[Xfail]:
|
||||
def evaluate_xfail_marks(item: Item) -> Xfail | None:
|
||||
"""Evaluate xfail marks on item, returning Xfail if triggered."""
|
||||
for mark in item.iter_markers(name="xfail"):
|
||||
run = mark.kwargs.get("run", True)
|
||||
|
@ -292,7 +292,7 @@ def pytest_runtest_makereport(
|
|||
return rep
|
||||
|
||||
|
||||
def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]:
|
||||
def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str] | None:
|
||||
if hasattr(report, "wasxfail"):
|
||||
if report.skipped:
|
||||
return "xfailed", "x", "XFAIL"
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import cast
|
||||
from typing import Dict
|
||||
from typing import Generic
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
|
||||
__all__ = ["Stash", "StashKey"]
|
||||
|
@ -70,7 +70,7 @@ class Stash:
|
|||
__slots__ = ("_storage",)
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._storage: Dict[StashKey[Any], object] = {}
|
||||
self._storage: dict[StashKey[Any], object] = {}
|
||||
|
||||
def __setitem__(self, key: StashKey[T], value: T) -> None:
|
||||
"""Set a value for key."""
|
||||
|
@ -83,7 +83,7 @@ class Stash:
|
|||
"""
|
||||
return cast(T, self._storage[key])
|
||||
|
||||
def get(self, key: StashKey[T], default: D) -> Union[T, D]:
|
||||
def get(self, key: StashKey[T], default: D) -> T | D:
|
||||
"""Get the value for key, or return default if the key wasn't set
|
||||
before."""
|
||||
try:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from typing import List
|
||||
from typing import Optional
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from _pytest import nodes
|
||||
|
@ -13,6 +13,7 @@ import pytest
|
|||
if TYPE_CHECKING:
|
||||
from _pytest.cacheprovider import Cache
|
||||
|
||||
|
||||
STEPWISE_CACHE_DIR = "cache/stepwise"
|
||||
|
||||
|
||||
|
@ -60,18 +61,18 @@ def pytest_sessionfinish(session: Session) -> None:
|
|||
class StepwisePlugin:
|
||||
def __init__(self, config: Config) -> None:
|
||||
self.config = config
|
||||
self.session: Optional[Session] = None
|
||||
self.session: Session | None = None
|
||||
self.report_status = ""
|
||||
assert config.cache is not None
|
||||
self.cache: Cache = config.cache
|
||||
self.lastfailed: Optional[str] = self.cache.get(STEPWISE_CACHE_DIR, None)
|
||||
self.lastfailed: str | None = self.cache.get(STEPWISE_CACHE_DIR, None)
|
||||
self.skip: bool = config.getoption("stepwise_skip")
|
||||
|
||||
def pytest_sessionstart(self, session: Session) -> None:
|
||||
self.session = session
|
||||
|
||||
def pytest_collection_modifyitems(
|
||||
self, config: Config, items: List[nodes.Item]
|
||||
self, config: Config, items: list[nodes.Item]
|
||||
) -> None:
|
||||
if not self.lastfailed:
|
||||
self.report_status = "no previously failed tests, not skipping."
|
||||
|
@ -118,7 +119,7 @@ class StepwisePlugin:
|
|||
if report.nodeid == self.lastfailed:
|
||||
self.lastfailed = None
|
||||
|
||||
def pytest_report_collectionfinish(self) -> Optional[str]:
|
||||
def pytest_report_collectionfinish(self) -> str | None:
|
||||
if self.config.getoption("verbose") >= 0 and self.report_status:
|
||||
return f"stepwise: {self.report_status}"
|
||||
return None
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
This is a good source for looking at the various reporting hooks.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from collections import Counter
|
||||
import dataclasses
|
||||
|
@ -17,20 +19,14 @@ import textwrap
|
|||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import ClassVar
|
||||
from typing import Dict
|
||||
from typing import final
|
||||
from typing import Generator
|
||||
from typing import List
|
||||
from typing import Literal
|
||||
from typing import Mapping
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
from typing import TextIO
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
import pluggy
|
||||
|
@ -90,7 +86,7 @@ class MoreQuietAction(argparse.Action):
|
|||
dest: str,
|
||||
default: object = None,
|
||||
required: bool = False,
|
||||
help: Optional[str] = None,
|
||||
help: str | None = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
option_strings=option_strings,
|
||||
|
@ -105,8 +101,8 @@ class MoreQuietAction(argparse.Action):
|
|||
self,
|
||||
parser: argparse.ArgumentParser,
|
||||
namespace: argparse.Namespace,
|
||||
values: Union[str, Sequence[object], None],
|
||||
option_string: Optional[str] = None,
|
||||
values: str | Sequence[object] | None,
|
||||
option_string: str | None = None,
|
||||
) -> None:
|
||||
new_count = getattr(namespace, self.dest, 0) - 1
|
||||
setattr(namespace, self.dest, new_count)
|
||||
|
@ -131,7 +127,7 @@ class TestShortLogReport(NamedTuple):
|
|||
|
||||
category: str
|
||||
letter: str
|
||||
word: Union[str, Tuple[str, Mapping[str, bool]]]
|
||||
word: str | tuple[str, Mapping[str, bool]]
|
||||
|
||||
|
||||
def pytest_addoption(parser: Parser) -> None:
|
||||
|
@ -304,7 +300,7 @@ def getreportopt(config: Config) -> str:
|
|||
|
||||
|
||||
@hookimpl(trylast=True) # after _pytest.runner
|
||||
def pytest_report_teststatus(report: BaseReport) -> Tuple[str, str, str]:
|
||||
def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str]:
|
||||
letter = "F"
|
||||
if report.passed:
|
||||
letter = "."
|
||||
|
@ -332,12 +328,12 @@ class WarningReport:
|
|||
"""
|
||||
|
||||
message: str
|
||||
nodeid: Optional[str] = None
|
||||
fslocation: Optional[Tuple[str, int]] = None
|
||||
nodeid: str | None = None
|
||||
fslocation: tuple[str, int] | None = None
|
||||
|
||||
count_towards_summary: ClassVar = True
|
||||
|
||||
def get_location(self, config: Config) -> Optional[str]:
|
||||
def get_location(self, config: Config) -> str | None:
|
||||
"""Return the more user-friendly information about the location of a warning, or None."""
|
||||
if self.nodeid:
|
||||
return self.nodeid
|
||||
|
@ -350,31 +346,31 @@ class WarningReport:
|
|||
|
||||
@final
|
||||
class TerminalReporter:
|
||||
def __init__(self, config: Config, file: Optional[TextIO] = None) -> None:
|
||||
def __init__(self, config: Config, file: TextIO | None = None) -> None:
|
||||
import _pytest.config
|
||||
|
||||
self.config = config
|
||||
self._numcollected = 0
|
||||
self._session: Optional[Session] = None
|
||||
self._showfspath: Optional[bool] = None
|
||||
self._session: Session | None = None
|
||||
self._showfspath: bool | None = None
|
||||
|
||||
self.stats: Dict[str, List[Any]] = {}
|
||||
self._main_color: Optional[str] = None
|
||||
self._known_types: Optional[List[str]] = None
|
||||
self.stats: dict[str, list[Any]] = {}
|
||||
self._main_color: str | None = None
|
||||
self._known_types: list[str] | None = None
|
||||
self.startpath = config.invocation_params.dir
|
||||
if file is None:
|
||||
file = sys.stdout
|
||||
self._tw = _pytest.config.create_terminal_writer(config, file)
|
||||
self._screen_width = self._tw.fullwidth
|
||||
self.currentfspath: Union[None, Path, str, int] = None
|
||||
self.currentfspath: None | Path | str | int = None
|
||||
self.reportchars = getreportopt(config)
|
||||
self.hasmarkup = self._tw.hasmarkup
|
||||
self.isatty = file.isatty()
|
||||
self._progress_nodeids_reported: Set[str] = set()
|
||||
self._progress_nodeids_reported: set[str] = set()
|
||||
self._show_progress_info = self._determine_show_progress_info()
|
||||
self._collect_report_last_write: Optional[float] = None
|
||||
self._already_displayed_warnings: Optional[int] = None
|
||||
self._keyboardinterrupt_memo: Optional[ExceptionRepr] = None
|
||||
self._collect_report_last_write: float | None = None
|
||||
self._already_displayed_warnings: int | None = None
|
||||
self._keyboardinterrupt_memo: ExceptionRepr | None = None
|
||||
|
||||
def _determine_show_progress_info(self) -> Literal["progress", "count", False]:
|
||||
"""Return whether we should display progress information based on the current config."""
|
||||
|
@ -421,7 +417,7 @@ class TerminalReporter:
|
|||
return self._showfspath
|
||||
|
||||
@showfspath.setter
|
||||
def showfspath(self, value: Optional[bool]) -> None:
|
||||
def showfspath(self, value: bool | None) -> None:
|
||||
self._showfspath = value
|
||||
|
||||
@property
|
||||
|
@ -485,7 +481,7 @@ class TerminalReporter:
|
|||
def flush(self) -> None:
|
||||
self._tw.flush()
|
||||
|
||||
def write_line(self, line: Union[str, bytes], **markup: bool) -> None:
|
||||
def write_line(self, line: str | bytes, **markup: bool) -> None:
|
||||
if not isinstance(line, str):
|
||||
line = str(line, errors="replace")
|
||||
self.ensure_newline()
|
||||
|
@ -512,8 +508,8 @@ class TerminalReporter:
|
|||
def write_sep(
|
||||
self,
|
||||
sep: str,
|
||||
title: Optional[str] = None,
|
||||
fullwidth: Optional[int] = None,
|
||||
title: str | None = None,
|
||||
fullwidth: int | None = None,
|
||||
**markup: bool,
|
||||
) -> None:
|
||||
self.ensure_newline()
|
||||
|
@ -563,7 +559,7 @@ class TerminalReporter:
|
|||
self._add_stats("deselected", items)
|
||||
|
||||
def pytest_runtest_logstart(
|
||||
self, nodeid: str, location: Tuple[str, Optional[int], str]
|
||||
self, nodeid: str, location: tuple[str, int | None, str]
|
||||
) -> None:
|
||||
# Ensure that the path is printed before the
|
||||
# 1st test of a module starts running.
|
||||
|
@ -757,7 +753,7 @@ class TerminalReporter:
|
|||
self.write_line(line)
|
||||
|
||||
@hookimpl(trylast=True)
|
||||
def pytest_sessionstart(self, session: "Session") -> None:
|
||||
def pytest_sessionstart(self, session: Session) -> None:
|
||||
self._session = session
|
||||
self._sessionstarttime = timing.time()
|
||||
if not self.showheader:
|
||||
|
@ -784,7 +780,7 @@ class TerminalReporter:
|
|||
self._write_report_lines_from_hooks(lines)
|
||||
|
||||
def _write_report_lines_from_hooks(
|
||||
self, lines: Sequence[Union[str, Sequence[str]]]
|
||||
self, lines: Sequence[str | Sequence[str]]
|
||||
) -> None:
|
||||
for line_or_lines in reversed(lines):
|
||||
if isinstance(line_or_lines, str):
|
||||
|
@ -793,14 +789,14 @@ class TerminalReporter:
|
|||
for line in line_or_lines:
|
||||
self.write_line(line)
|
||||
|
||||
def pytest_report_header(self, config: Config) -> List[str]:
|
||||
def pytest_report_header(self, config: Config) -> list[str]:
|
||||
result = [f"rootdir: {config.rootpath}"]
|
||||
|
||||
if config.inipath:
|
||||
result.append("configfile: " + bestrelpath(config.rootpath, config.inipath))
|
||||
|
||||
if config.args_source == Config.ArgsSource.TESTPATHS:
|
||||
testpaths: List[str] = config.getini("testpaths")
|
||||
testpaths: list[str] = config.getini("testpaths")
|
||||
result.append("testpaths: {}".format(", ".join(testpaths)))
|
||||
|
||||
plugininfo = config.pluginmanager.list_plugin_distinfo()
|
||||
|
@ -808,7 +804,7 @@ class TerminalReporter:
|
|||
result.append("plugins: %s" % ", ".join(_plugin_nameversions(plugininfo)))
|
||||
return result
|
||||
|
||||
def pytest_collection_finish(self, session: "Session") -> None:
|
||||
def pytest_collection_finish(self, session: Session) -> None:
|
||||
self.report_collect(True)
|
||||
|
||||
lines = self.config.hook.pytest_report_collectionfinish(
|
||||
|
@ -841,7 +837,7 @@ class TerminalReporter:
|
|||
for item in items:
|
||||
self._tw.line(item.nodeid)
|
||||
return
|
||||
stack: List[Node] = []
|
||||
stack: list[Node] = []
|
||||
indent = ""
|
||||
for item in items:
|
||||
needed_collectors = item.listchain()[1:] # strip root node
|
||||
|
@ -862,7 +858,7 @@ class TerminalReporter:
|
|||
|
||||
@hookimpl(wrapper=True)
|
||||
def pytest_sessionfinish(
|
||||
self, session: "Session", exitstatus: Union[int, ExitCode]
|
||||
self, session: Session, exitstatus: int | ExitCode
|
||||
) -> Generator[None, None, None]:
|
||||
result = yield
|
||||
self._tw.line("")
|
||||
|
@ -926,7 +922,7 @@ class TerminalReporter:
|
|||
)
|
||||
|
||||
def _locationline(
|
||||
self, nodeid: str, fspath: str, lineno: Optional[int], domain: str
|
||||
self, nodeid: str, fspath: str, lineno: int | None, domain: str
|
||||
) -> str:
|
||||
def mkrel(nodeid: str) -> str:
|
||||
line = self.config.cwd_relative_nodeid(nodeid)
|
||||
|
@ -971,7 +967,7 @@ class TerminalReporter:
|
|||
|
||||
def summary_warnings(self) -> None:
|
||||
if self.hasopt("w"):
|
||||
all_warnings: Optional[List[WarningReport]] = self.stats.get("warnings")
|
||||
all_warnings: list[WarningReport] | None = self.stats.get("warnings")
|
||||
if not all_warnings:
|
||||
return
|
||||
|
||||
|
@ -984,11 +980,11 @@ class TerminalReporter:
|
|||
if not warning_reports:
|
||||
return
|
||||
|
||||
reports_grouped_by_message: Dict[str, List[WarningReport]] = {}
|
||||
reports_grouped_by_message: dict[str, list[WarningReport]] = {}
|
||||
for wr in warning_reports:
|
||||
reports_grouped_by_message.setdefault(wr.message, []).append(wr)
|
||||
|
||||
def collapsed_location_report(reports: List[WarningReport]) -> str:
|
||||
def collapsed_location_report(reports: list[WarningReport]) -> str:
|
||||
locations = []
|
||||
for w in reports:
|
||||
location = w.get_location(self.config)
|
||||
|
@ -1034,7 +1030,7 @@ class TerminalReporter:
|
|||
) -> None:
|
||||
if self.config.option.tbstyle != "no":
|
||||
if self.hasopt(needed_opt):
|
||||
reports: List[TestReport] = self.getreports(which_reports)
|
||||
reports: list[TestReport] = self.getreports(which_reports)
|
||||
if not reports:
|
||||
return
|
||||
self.write_sep("=", sep_title)
|
||||
|
@ -1045,7 +1041,7 @@ class TerminalReporter:
|
|||
self._outrep_summary(rep)
|
||||
self._handle_teardown_sections(rep.nodeid)
|
||||
|
||||
def _get_teardown_reports(self, nodeid: str) -> List[TestReport]:
|
||||
def _get_teardown_reports(self, nodeid: str) -> list[TestReport]:
|
||||
reports = self.getreports("")
|
||||
return [
|
||||
report
|
||||
|
@ -1074,14 +1070,17 @@ class TerminalReporter:
|
|||
self.summary_failures_combined("failed", "FAILURES")
|
||||
|
||||
def summary_xfailures(self) -> None:
|
||||
self.summary_failures_combined("xfailed", "XFAILURES", "x")
|
||||
self.summary_failures_combined("xfailed", "XFAILURES", needed_opt="x")
|
||||
|
||||
def summary_failures_combined(
|
||||
self, which_reports: str, sep_title: str, needed_opt: Optional[str] = None
|
||||
self,
|
||||
which_reports: str,
|
||||
sep_title: str,
|
||||
needed_opt: str | None = None,
|
||||
) -> None:
|
||||
if self.config.option.tbstyle != "no":
|
||||
if not needed_opt or self.hasopt(needed_opt):
|
||||
reports: List[BaseReport] = self.getreports(which_reports)
|
||||
reports: list[BaseReport] = self.getreports(which_reports)
|
||||
if not reports:
|
||||
return
|
||||
self.write_sep("=", sep_title)
|
||||
|
@ -1098,7 +1097,7 @@ class TerminalReporter:
|
|||
|
||||
def summary_errors(self) -> None:
|
||||
if self.config.option.tbstyle != "no":
|
||||
reports: List[BaseReport] = self.getreports("error")
|
||||
reports: list[BaseReport] = self.getreports("error")
|
||||
if not reports:
|
||||
return
|
||||
self.write_sep("=", "ERRORS")
|
||||
|
@ -1165,7 +1164,7 @@ class TerminalReporter:
|
|||
if not self.reportchars:
|
||||
return
|
||||
|
||||
def show_simple(lines: List[str], *, stat: str) -> None:
|
||||
def show_simple(lines: list[str], *, stat: str) -> None:
|
||||
failed = self.stats.get(stat, [])
|
||||
if not failed:
|
||||
return
|
||||
|
@ -1177,7 +1176,7 @@ class TerminalReporter:
|
|||
)
|
||||
lines.append(line)
|
||||
|
||||
def show_xfailed(lines: List[str]) -> None:
|
||||
def show_xfailed(lines: list[str]) -> None:
|
||||
xfailed = self.stats.get("xfailed", [])
|
||||
for rep in xfailed:
|
||||
verbose_word = rep._get_verbose_word(self.config)
|
||||
|
@ -1192,7 +1191,7 @@ class TerminalReporter:
|
|||
|
||||
lines.append(line)
|
||||
|
||||
def show_xpassed(lines: List[str]) -> None:
|
||||
def show_xpassed(lines: list[str]) -> None:
|
||||
xpassed = self.stats.get("xpassed", [])
|
||||
for rep in xpassed:
|
||||
verbose_word = rep._get_verbose_word(self.config)
|
||||
|
@ -1206,8 +1205,8 @@ class TerminalReporter:
|
|||
line += " - " + str(reason)
|
||||
lines.append(line)
|
||||
|
||||
def show_skipped(lines: List[str]) -> None:
|
||||
skipped: List[CollectReport] = self.stats.get("skipped", [])
|
||||
def show_skipped(lines: list[str]) -> None:
|
||||
skipped: list[CollectReport] = self.stats.get("skipped", [])
|
||||
fskips = _folded_skips(self.startpath, skipped) if skipped else []
|
||||
if not fskips:
|
||||
return
|
||||
|
@ -1226,7 +1225,7 @@ class TerminalReporter:
|
|||
else:
|
||||
lines.append("%s [%d] %s: %s" % (markup_word, num, fspath, reason))
|
||||
|
||||
REPORTCHAR_ACTIONS: Mapping[str, Callable[[List[str]], None]] = {
|
||||
REPORTCHAR_ACTIONS: Mapping[str, Callable[[list[str]], None]] = {
|
||||
"x": show_xfailed,
|
||||
"X": show_xpassed,
|
||||
"f": partial(show_simple, stat="failed"),
|
||||
|
@ -1235,7 +1234,7 @@ class TerminalReporter:
|
|||
"E": partial(show_simple, stat="error"),
|
||||
}
|
||||
|
||||
lines: List[str] = []
|
||||
lines: list[str] = []
|
||||
for char in self.reportchars:
|
||||
action = REPORTCHAR_ACTIONS.get(char)
|
||||
if action: # skipping e.g. "P" (passed with output) here.
|
||||
|
@ -1246,7 +1245,7 @@ class TerminalReporter:
|
|||
for line in lines:
|
||||
self.write_line(line)
|
||||
|
||||
def _get_main_color(self) -> Tuple[str, List[str]]:
|
||||
def _get_main_color(self) -> tuple[str, list[str]]:
|
||||
if self._main_color is None or self._known_types is None or self._is_last_item:
|
||||
self._set_main_color()
|
||||
assert self._main_color
|
||||
|
@ -1266,7 +1265,7 @@ class TerminalReporter:
|
|||
return main_color
|
||||
|
||||
def _set_main_color(self) -> None:
|
||||
unknown_types: List[str] = []
|
||||
unknown_types: list[str] = []
|
||||
for found_type in self.stats:
|
||||
if found_type: # setup/teardown reports have an empty key, ignore them
|
||||
if found_type not in KNOWN_TYPES and found_type not in unknown_types:
|
||||
|
@ -1274,7 +1273,7 @@ class TerminalReporter:
|
|||
self._known_types = list(KNOWN_TYPES) + unknown_types
|
||||
self._main_color = self._determine_main_color(bool(unknown_types))
|
||||
|
||||
def build_summary_stats_line(self) -> Tuple[List[Tuple[str, Dict[str, bool]]], str]:
|
||||
def build_summary_stats_line(self) -> tuple[list[tuple[str, dict[str, bool]]], str]:
|
||||
"""
|
||||
Build the parts used in the last summary stats line.
|
||||
|
||||
|
@ -1299,14 +1298,14 @@ class TerminalReporter:
|
|||
else:
|
||||
return self._build_normal_summary_stats_line()
|
||||
|
||||
def _get_reports_to_display(self, key: str) -> List[Any]:
|
||||
def _get_reports_to_display(self, key: str) -> list[Any]:
|
||||
"""Get test/collection reports for the given status key, such as `passed` or `error`."""
|
||||
reports = self.stats.get(key, [])
|
||||
return [x for x in reports if getattr(x, "count_towards_summary", True)]
|
||||
|
||||
def _build_normal_summary_stats_line(
|
||||
self,
|
||||
) -> Tuple[List[Tuple[str, Dict[str, bool]]], str]:
|
||||
) -> tuple[list[tuple[str, dict[str, bool]]], str]:
|
||||
main_color, known_types = self._get_main_color()
|
||||
parts = []
|
||||
|
||||
|
@ -1325,7 +1324,7 @@ class TerminalReporter:
|
|||
|
||||
def _build_collect_only_summary_stats_line(
|
||||
self,
|
||||
) -> Tuple[List[Tuple[str, Dict[str, bool]]], str]:
|
||||
) -> tuple[list[tuple[str, dict[str, bool]]], str]:
|
||||
deselected = len(self._get_reports_to_display("deselected"))
|
||||
errors = len(self._get_reports_to_display("error"))
|
||||
|
||||
|
@ -1366,7 +1365,7 @@ def _get_node_id_with_markup(tw: TerminalWriter, config: Config, rep: BaseReport
|
|||
return path
|
||||
|
||||
|
||||
def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str]:
|
||||
def _format_trimmed(format: str, msg: str, available_width: int) -> str | None:
|
||||
"""Format msg into format, ellipsizing it if doesn't fit in available_width.
|
||||
|
||||
Returns None if even the ellipsis can't fit.
|
||||
|
@ -1392,7 +1391,7 @@ def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str
|
|||
|
||||
|
||||
def _get_line_with_reprcrash_message(
|
||||
config: Config, rep: BaseReport, tw: TerminalWriter, word_markup: Dict[str, bool]
|
||||
config: Config, rep: BaseReport, tw: TerminalWriter, word_markup: dict[str, bool]
|
||||
) -> str:
|
||||
"""Get summary line for a report, trying to add reprcrash message."""
|
||||
verbose_word = rep._get_verbose_word(config)
|
||||
|
@ -1422,8 +1421,8 @@ def _get_line_with_reprcrash_message(
|
|||
def _folded_skips(
|
||||
startpath: Path,
|
||||
skipped: Sequence[CollectReport],
|
||||
) -> List[Tuple[int, str, Optional[int], str]]:
|
||||
d: Dict[Tuple[str, Optional[int], str], List[CollectReport]] = {}
|
||||
) -> list[tuple[int, str, int | None, str]]:
|
||||
d: dict[tuple[str, int | None, str], list[CollectReport]] = {}
|
||||
for event in skipped:
|
||||
assert event.longrepr is not None
|
||||
assert isinstance(event.longrepr, tuple), (event, event.longrepr)
|
||||
|
@ -1440,11 +1439,11 @@ def _folded_skips(
|
|||
and "skip" in keywords
|
||||
and "pytestmark" not in keywords
|
||||
):
|
||||
key: Tuple[str, Optional[int], str] = (fspath, None, reason)
|
||||
key: tuple[str, int | None, str] = (fspath, None, reason)
|
||||
else:
|
||||
key = (fspath, lineno, reason)
|
||||
d.setdefault(key, []).append(event)
|
||||
values: List[Tuple[int, str, Optional[int], str]] = []
|
||||
values: list[tuple[int, str, int | None, str]] = []
|
||||
for key, events in d.items():
|
||||
values.append((len(events), *key))
|
||||
return values
|
||||
|
@ -1459,7 +1458,7 @@ _color_for_type = {
|
|||
_color_for_type_default = "yellow"
|
||||
|
||||
|
||||
def pluralize(count: int, noun: str) -> Tuple[int, str]:
|
||||
def pluralize(count: int, noun: str) -> tuple[int, str]:
|
||||
# No need to pluralize words such as `failed` or `passed`.
|
||||
if noun not in ["error", "warnings", "test"]:
|
||||
return count, noun
|
||||
|
@ -1472,8 +1471,8 @@ def pluralize(count: int, noun: str) -> Tuple[int, str]:
|
|||
return count, noun + "s" if count != 1 else noun
|
||||
|
||||
|
||||
def _plugin_nameversions(plugininfo) -> List[str]:
|
||||
values: List[str] = []
|
||||
def _plugin_nameversions(plugininfo) -> list[str]:
|
||||
values: list[str] = []
|
||||
for plugin, dist in plugininfo:
|
||||
# Gets us name and version!
|
||||
name = f"{dist.project_name}-{dist.version}"
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
import traceback
|
||||
from types import TracebackType
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Generator
|
||||
from typing import Optional
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
|
||||
# Copied from cpython/Lib/test/support/threading_helper.py, with modifications.
|
||||
class catch_threading_exception:
|
||||
"""Context manager catching threading.Thread exception using
|
||||
|
@ -34,22 +39,22 @@ class catch_threading_exception:
|
|||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.args: Optional["threading.ExceptHookArgs"] = None
|
||||
self._old_hook: Optional[Callable[["threading.ExceptHookArgs"], Any]] = None
|
||||
self.args: threading.ExceptHookArgs | None = None
|
||||
self._old_hook: Callable[[threading.ExceptHookArgs], Any] | None = None
|
||||
|
||||
def _hook(self, args: "threading.ExceptHookArgs") -> None:
|
||||
def _hook(self, args: threading.ExceptHookArgs) -> None:
|
||||
self.args = args
|
||||
|
||||
def __enter__(self) -> "catch_threading_exception":
|
||||
def __enter__(self) -> Self:
|
||||
self._old_hook = threading.excepthook
|
||||
threading.excepthook = self._hook
|
||||
return self
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc_val: Optional[BaseException],
|
||||
exc_tb: Optional[TracebackType],
|
||||
exc_type: type[BaseException] | None,
|
||||
exc_val: BaseException | None,
|
||||
exc_tb: TracebackType | None,
|
||||
) -> None:
|
||||
assert self._old_hook is not None
|
||||
threading.excepthook = self._old_hook
|
||||
|
|
|
@ -6,6 +6,8 @@ pytest runtime information (issue #185).
|
|||
Fixture "mock_timing" also interacts with this module for pytest's own tests.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from time import perf_counter
|
||||
from time import sleep
|
||||
from time import time
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Support for providing temporary directories to test functions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
@ -12,8 +14,6 @@ from typing import Dict
|
|||
from typing import final
|
||||
from typing import Generator
|
||||
from typing import Literal
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
|
||||
from .pathlib import cleanup_dead_symlinks
|
||||
from .pathlib import LOCK_TIMEOUT
|
||||
|
@ -46,20 +46,20 @@ class TempPathFactory:
|
|||
The base directory can be configured using the ``--basetemp`` option.
|
||||
"""
|
||||
|
||||
_given_basetemp: Optional[Path]
|
||||
_given_basetemp: Path | None
|
||||
# pluggy TagTracerSub, not currently exposed, so Any.
|
||||
_trace: Any
|
||||
_basetemp: Optional[Path]
|
||||
_basetemp: Path | None
|
||||
_retention_count: int
|
||||
_retention_policy: RetentionType
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
given_basetemp: Optional[Path],
|
||||
given_basetemp: Path | None,
|
||||
retention_count: int,
|
||||
retention_policy: RetentionType,
|
||||
trace,
|
||||
basetemp: Optional[Path] = None,
|
||||
basetemp: Path | None = None,
|
||||
*,
|
||||
_ispytest: bool = False,
|
||||
) -> None:
|
||||
|
@ -82,7 +82,7 @@ class TempPathFactory:
|
|||
config: Config,
|
||||
*,
|
||||
_ispytest: bool = False,
|
||||
) -> "TempPathFactory":
|
||||
) -> TempPathFactory:
|
||||
"""Create a factory according to pytest configuration.
|
||||
|
||||
:meta private:
|
||||
|
@ -198,7 +198,7 @@ class TempPathFactory:
|
|||
return basetemp
|
||||
|
||||
|
||||
def get_user() -> Optional[str]:
|
||||
def get_user() -> str | None:
|
||||
"""Return the current user name, or None if getuser() does not work
|
||||
in the current environment (see #1010)."""
|
||||
try:
|
||||
|
@ -286,7 +286,7 @@ def tmp_path(
|
|||
del request.node.stash[tmppath_result_key]
|
||||
|
||||
|
||||
def pytest_sessionfinish(session, exitstatus: Union[int, ExitCode]):
|
||||
def pytest_sessionfinish(session, exitstatus: int | ExitCode):
|
||||
"""After each session, remove base directory if all the tests passed,
|
||||
the policy is "failed", and the basetemp is not specified by a user.
|
||||
"""
|
||||
|
@ -317,6 +317,6 @@ def pytest_runtest_makereport(
|
|||
) -> Generator[None, TestReport, TestReport]:
|
||||
rep = yield
|
||||
assert rep.when is not None
|
||||
empty: Dict[str, bool] = {}
|
||||
empty: dict[str, bool] = {}
|
||||
item.stash.setdefault(tmppath_result_key, empty)[rep.when] = rep.passed
|
||||
return rep
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Discover and run std-library "unittest" style tests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
import types
|
||||
|
@ -8,8 +10,6 @@ from typing import Any
|
|||
from typing import Callable
|
||||
from typing import Generator
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
|
@ -47,8 +47,8 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
def pytest_pycollect_makeitem(
|
||||
collector: Union[Module, Class], name: str, obj: object
|
||||
) -> Optional["UnitTestCase"]:
|
||||
collector: Module | Class, name: str, obj: object
|
||||
) -> UnitTestCase | None:
|
||||
# Has unittest been imported and is obj a subclass of its TestCase?
|
||||
try:
|
||||
ut = sys.modules["unittest"]
|
||||
|
@ -74,7 +74,7 @@ class UnitTestCase(Class):
|
|||
# it.
|
||||
return self.obj("runTest")
|
||||
|
||||
def collect(self) -> Iterable[Union[Item, Collector]]:
|
||||
def collect(self) -> Iterable[Item | Collector]:
|
||||
from unittest import TestLoader
|
||||
|
||||
cls = self.obj
|
||||
|
@ -194,7 +194,7 @@ class UnitTestCase(Class):
|
|||
|
||||
class TestCaseFunction(Function):
|
||||
nofuncargs = True
|
||||
_excinfo: Optional[List[_pytest._code.ExceptionInfo[BaseException]]] = None
|
||||
_excinfo: list[_pytest._code.ExceptionInfo[BaseException]] | None = None
|
||||
|
||||
def _getinstance(self):
|
||||
assert isinstance(self.parent, UnitTestCase)
|
||||
|
@ -208,7 +208,7 @@ class TestCaseFunction(Function):
|
|||
|
||||
def setup(self) -> None:
|
||||
# A bound method to be called during teardown() if set (see 'runtest()').
|
||||
self._explicit_tearDown: Optional[Callable[[], None]] = None
|
||||
self._explicit_tearDown: Callable[[], None] | None = None
|
||||
super().setup()
|
||||
|
||||
def teardown(self) -> None:
|
||||
|
@ -219,10 +219,10 @@ class TestCaseFunction(Function):
|
|||
del self._instance
|
||||
super().teardown()
|
||||
|
||||
def startTest(self, testcase: "unittest.TestCase") -> None:
|
||||
def startTest(self, testcase: unittest.TestCase) -> None:
|
||||
pass
|
||||
|
||||
def _addexcinfo(self, rawexcinfo: "_SysExcInfoType") -> None:
|
||||
def _addexcinfo(self, rawexcinfo: _SysExcInfoType) -> None:
|
||||
# Unwrap potential exception info (see twisted trial support below).
|
||||
rawexcinfo = getattr(rawexcinfo, "_rawexcinfo", rawexcinfo)
|
||||
try:
|
||||
|
@ -258,7 +258,7 @@ class TestCaseFunction(Function):
|
|||
self.__dict__.setdefault("_excinfo", []).append(excinfo)
|
||||
|
||||
def addError(
|
||||
self, testcase: "unittest.TestCase", rawexcinfo: "_SysExcInfoType"
|
||||
self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType
|
||||
) -> None:
|
||||
try:
|
||||
if isinstance(rawexcinfo[1], exit.Exception):
|
||||
|
@ -268,11 +268,11 @@ class TestCaseFunction(Function):
|
|||
self._addexcinfo(rawexcinfo)
|
||||
|
||||
def addFailure(
|
||||
self, testcase: "unittest.TestCase", rawexcinfo: "_SysExcInfoType"
|
||||
self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType
|
||||
) -> None:
|
||||
self._addexcinfo(rawexcinfo)
|
||||
|
||||
def addSkip(self, testcase: "unittest.TestCase", reason: str) -> None:
|
||||
def addSkip(self, testcase: unittest.TestCase, reason: str) -> None:
|
||||
try:
|
||||
raise pytest.skip.Exception(reason, _use_item_location=True)
|
||||
except skip.Exception:
|
||||
|
@ -280,8 +280,8 @@ class TestCaseFunction(Function):
|
|||
|
||||
def addExpectedFailure(
|
||||
self,
|
||||
testcase: "unittest.TestCase",
|
||||
rawexcinfo: "_SysExcInfoType",
|
||||
testcase: unittest.TestCase,
|
||||
rawexcinfo: _SysExcInfoType,
|
||||
reason: str = "",
|
||||
) -> None:
|
||||
try:
|
||||
|
@ -291,8 +291,8 @@ class TestCaseFunction(Function):
|
|||
|
||||
def addUnexpectedSuccess(
|
||||
self,
|
||||
testcase: "unittest.TestCase",
|
||||
reason: Optional["twisted.trial.unittest.Todo"] = None,
|
||||
testcase: unittest.TestCase,
|
||||
reason: twisted.trial.unittest.Todo | None = None,
|
||||
) -> None:
|
||||
msg = "Unexpected success"
|
||||
if reason:
|
||||
|
@ -303,13 +303,13 @@ class TestCaseFunction(Function):
|
|||
except fail.Exception:
|
||||
self._addexcinfo(sys.exc_info())
|
||||
|
||||
def addSuccess(self, testcase: "unittest.TestCase") -> None:
|
||||
def addSuccess(self, testcase: unittest.TestCase) -> None:
|
||||
pass
|
||||
|
||||
def stopTest(self, testcase: "unittest.TestCase") -> None:
|
||||
def stopTest(self, testcase: unittest.TestCase) -> None:
|
||||
pass
|
||||
|
||||
def addDuration(self, testcase: "unittest.TestCase", elapsed: float) -> None:
|
||||
def addDuration(self, testcase: unittest.TestCase, elapsed: float) -> None:
|
||||
pass
|
||||
|
||||
def runtest(self) -> None:
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
from types import TracebackType
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Generator
|
||||
from typing import Optional
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
|
||||
# Copied from cpython/Lib/test/support/__init__.py, with modifications.
|
||||
class catch_unraisable_exception:
|
||||
"""Context manager catching unraisable exception using sys.unraisablehook.
|
||||
|
@ -34,24 +39,24 @@ class catch_unraisable_exception:
|
|||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.unraisable: Optional["sys.UnraisableHookArgs"] = None
|
||||
self._old_hook: Optional[Callable[["sys.UnraisableHookArgs"], Any]] = None
|
||||
self.unraisable: sys.UnraisableHookArgs | None = None
|
||||
self._old_hook: Callable[[sys.UnraisableHookArgs], Any] | None = None
|
||||
|
||||
def _hook(self, unraisable: "sys.UnraisableHookArgs") -> None:
|
||||
def _hook(self, unraisable: sys.UnraisableHookArgs) -> None:
|
||||
# Storing unraisable.object can resurrect an object which is being
|
||||
# finalized. Storing unraisable.exc_value creates a reference cycle.
|
||||
self.unraisable = unraisable
|
||||
|
||||
def __enter__(self) -> "catch_unraisable_exception":
|
||||
def __enter__(self) -> Self:
|
||||
self._old_hook = sys.unraisablehook
|
||||
sys.unraisablehook = self._hook
|
||||
return self
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc_val: Optional[BaseException],
|
||||
exc_tb: Optional[TracebackType],
|
||||
exc_type: type[BaseException] | None,
|
||||
exc_val: BaseException | None,
|
||||
exc_tb: TracebackType | None,
|
||||
) -> None:
|
||||
assert self._old_hook is not None
|
||||
sys.unraisablehook = self._old_hook
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import inspect
|
||||
from types import FunctionType
|
||||
from typing import Any
|
||||
from typing import final
|
||||
from typing import Generic
|
||||
from typing import Type
|
||||
from typing import TypeVar
|
||||
import warnings
|
||||
|
||||
|
@ -72,7 +73,7 @@ class PytestExperimentalApiWarning(PytestWarning, FutureWarning):
|
|||
__module__ = "pytest"
|
||||
|
||||
@classmethod
|
||||
def simple(cls, apiname: str) -> "PytestExperimentalApiWarning":
|
||||
def simple(cls, apiname: str) -> PytestExperimentalApiWarning:
|
||||
return cls(f"{apiname} is an experimental api that may change over time")
|
||||
|
||||
|
||||
|
@ -132,7 +133,7 @@ class UnformattedWarning(Generic[_W]):
|
|||
as opposed to a direct message.
|
||||
"""
|
||||
|
||||
category: Type["_W"]
|
||||
category: type[_W]
|
||||
template: str
|
||||
|
||||
def format(self, **kwargs: Any) -> _W:
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
# mypy: allow-untyped-defs
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager
|
||||
import sys
|
||||
from typing import Generator
|
||||
from typing import Literal
|
||||
from typing import Optional
|
||||
import warnings
|
||||
|
||||
from _pytest.config import apply_warning_filters
|
||||
|
@ -28,7 +29,7 @@ def catch_warnings_for_item(
|
|||
config: Config,
|
||||
ihook,
|
||||
when: Literal["config", "collect", "runtest"],
|
||||
item: Optional[Item],
|
||||
item: Item | None,
|
||||
) -> Generator[None, None, None]:
|
||||
"""Context manager that catches warnings generated in the contained execution block.
|
||||
|
||||
|
@ -142,7 +143,7 @@ def pytest_sessionfinish(session: Session) -> Generator[None, None, None]:
|
|||
|
||||
@pytest.hookimpl(wrapper=True)
|
||||
def pytest_load_initial_conftests(
|
||||
early_config: "Config",
|
||||
early_config: Config,
|
||||
) -> Generator[None, None, None]:
|
||||
with catch_warnings_for_item(
|
||||
config=early_config, ihook=early_config.hook, when="config", item=None
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# shim for pylib going away
|
||||
# if pylib is installed this file will get skipped
|
||||
# (`py/__init__.py` has higher precedence)
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
import _pytest._py.error as error
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# PYTHON_ARGCOMPLETE_OK
|
||||
"""pytest: unit and functional testing with Python."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from _pytest import __version__
|
||||
from _pytest import version_tuple
|
||||
from _pytest._code import ExceptionInfo
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue