pytest

pytest: unit and functional testing with Python.

  1# PYTHON_ARGCOMPLETE_OK
  2"""pytest: unit and functional testing with Python."""
  3
  4from __future__ import annotations
  5
  6from _pytest import __version__
  7from _pytest import version_tuple
  8from _pytest._code import ExceptionInfo
  9from _pytest.assertion import register_assert_rewrite
 10from _pytest.cacheprovider import Cache
 11from _pytest.capture import CaptureFixture
 12from _pytest.config import cmdline
 13from _pytest.config import Config
 14from _pytest.config import console_main
 15from _pytest.config import ExitCode
 16from _pytest.config import hookimpl
 17from _pytest.config import hookspec
 18from _pytest.config import main
 19from _pytest.config import PytestPluginManager
 20from _pytest.config import UsageError
 21from _pytest.config.argparsing import OptionGroup
 22from _pytest.config.argparsing import Parser
 23from _pytest.debugging import pytestPDB as __pytestPDB
 24from _pytest.doctest import DoctestItem
 25from _pytest.fixtures import fixture
 26from _pytest.fixtures import FixtureDef
 27from _pytest.fixtures import FixtureLookupError
 28from _pytest.fixtures import FixtureRequest
 29from _pytest.fixtures import yield_fixture
 30from _pytest.freeze_support import freeze_includes
 31from _pytest.legacypath import TempdirFactory
 32from _pytest.legacypath import Testdir
 33from _pytest.logging import LogCaptureFixture
 34from _pytest.main import Dir
 35from _pytest.main import Session
 36from _pytest.mark import HIDDEN_PARAM
 37from _pytest.mark import Mark
 38from _pytest.mark import MARK_GEN as mark
 39from _pytest.mark import MarkDecorator
 40from _pytest.mark import MarkGenerator
 41from _pytest.mark import param
 42from _pytest.monkeypatch import MonkeyPatch
 43from _pytest.nodes import Collector
 44from _pytest.nodes import Directory
 45from _pytest.nodes import File
 46from _pytest.nodes import Item
 47from _pytest.outcomes import exit
 48from _pytest.outcomes import fail
 49from _pytest.outcomes import importorskip
 50from _pytest.outcomes import skip
 51from _pytest.outcomes import xfail
 52from _pytest.pytester import HookRecorder
 53from _pytest.pytester import LineMatcher
 54from _pytest.pytester import Pytester
 55from _pytest.pytester import RecordedHookCall
 56from _pytest.pytester import RunResult
 57from _pytest.python import Class
 58from _pytest.python import Function
 59from _pytest.python import Metafunc
 60from _pytest.python import Module
 61from _pytest.python import Package
 62from _pytest.python_api import approx
 63from _pytest.raises import raises
 64from _pytest.raises import RaisesExc
 65from _pytest.raises import RaisesGroup
 66from _pytest.recwarn import deprecated_call
 67from _pytest.recwarn import WarningsRecorder
 68from _pytest.recwarn import warns
 69from _pytest.reports import CollectReport
 70from _pytest.reports import TestReport
 71from _pytest.runner import CallInfo
 72from _pytest.stash import Stash
 73from _pytest.stash import StashKey
 74from _pytest.terminal import TerminalReporter
 75from _pytest.terminal import TestShortLogReport
 76from _pytest.tmpdir import TempPathFactory
 77from _pytest.warning_types import PytestAssertRewriteWarning
 78from _pytest.warning_types import PytestCacheWarning
 79from _pytest.warning_types import PytestCollectionWarning
 80from _pytest.warning_types import PytestConfigWarning
 81from _pytest.warning_types import PytestDeprecationWarning
 82from _pytest.warning_types import PytestExperimentalApiWarning
 83from _pytest.warning_types import PytestFDWarning
 84from _pytest.warning_types import PytestRemovedIn9Warning
 85from _pytest.warning_types import PytestReturnNotNoneWarning
 86from _pytest.warning_types import PytestUnhandledThreadExceptionWarning
 87from _pytest.warning_types import PytestUnknownMarkWarning
 88from _pytest.warning_types import PytestUnraisableExceptionWarning
 89from _pytest.warning_types import PytestWarning
 90
 91
 92set_trace = __pytestPDB.set_trace
 93
 94
 95__all__ = [
 96    "HIDDEN_PARAM",
 97    "Cache",
 98    "CallInfo",
 99    "CaptureFixture",
100    "Class",
101    "CollectReport",
102    "Collector",
103    "Config",
104    "Dir",
105    "Directory",
106    "DoctestItem",
107    "ExceptionInfo",
108    "ExitCode",
109    "File",
110    "FixtureDef",
111    "FixtureLookupError",
112    "FixtureRequest",
113    "Function",
114    "HookRecorder",
115    "Item",
116    "LineMatcher",
117    "LogCaptureFixture",
118    "Mark",
119    "MarkDecorator",
120    "MarkGenerator",
121    "Metafunc",
122    "Module",
123    "MonkeyPatch",
124    "OptionGroup",
125    "Package",
126    "Parser",
127    "PytestAssertRewriteWarning",
128    "PytestCacheWarning",
129    "PytestCollectionWarning",
130    "PytestConfigWarning",
131    "PytestDeprecationWarning",
132    "PytestExperimentalApiWarning",
133    "PytestFDWarning",
134    "PytestPluginManager",
135    "PytestRemovedIn9Warning",
136    "PytestReturnNotNoneWarning",
137    "PytestUnhandledThreadExceptionWarning",
138    "PytestUnknownMarkWarning",
139    "PytestUnraisableExceptionWarning",
140    "PytestWarning",
141    "Pytester",
142    "RaisesExc",
143    "RaisesGroup",
144    "RecordedHookCall",
145    "RunResult",
146    "Session",
147    "Stash",
148    "StashKey",
149    "TempPathFactory",
150    "TempdirFactory",
151    "TerminalReporter",
152    "TestReport",
153    "TestShortLogReport",
154    "Testdir",
155    "UsageError",
156    "WarningsRecorder",
157    "__version__",
158    "approx",
159    "cmdline",
160    "console_main",
161    "deprecated_call",
162    "exit",
163    "fail",
164    "fixture",
165    "freeze_includes",
166    "hookimpl",
167    "hookspec",
168    "importorskip",
169    "main",
170    "mark",
171    "param",
172    "raises",
173    "register_assert_rewrite",
174    "set_trace",
175    "skip",
176    "version_tuple",
177    "warns",
178    "xfail",
179    "yield_fixture",
180]
HIDDEN_PARAM = <_HiddenParam.token: 0>
@final
@dataclasses.dataclass
class Cache:
 56@final
 57@dataclasses.dataclass
 58class Cache:
 59    """Instance of the `cache` fixture."""
 60
 61    _cachedir: Path = dataclasses.field(repr=False)
 62    _config: Config = dataclasses.field(repr=False)
 63
 64    # Sub-directory under cache-dir for directories created by `mkdir()`.
 65    _CACHE_PREFIX_DIRS = "d"
 66
 67    # Sub-directory under cache-dir for values created by `set()`.
 68    _CACHE_PREFIX_VALUES = "v"
 69
 70    def __init__(
 71        self, cachedir: Path, config: Config, *, _ispytest: bool = False
 72    ) -> None:
 73        check_ispytest(_ispytest)
 74        self._cachedir = cachedir
 75        self._config = config
 76
 77    @classmethod
 78    def for_config(cls, config: Config, *, _ispytest: bool = False) -> Cache:
 79        """Create the Cache instance for a Config.
 80
 81        :meta private:
 82        """
 83        check_ispytest(_ispytest)
 84        cachedir = cls.cache_dir_from_config(config, _ispytest=True)
 85        if config.getoption("cacheclear") and cachedir.is_dir():
 86            cls.clear_cache(cachedir, _ispytest=True)
 87        return cls(cachedir, config, _ispytest=True)
 88
 89    @classmethod
 90    def clear_cache(cls, cachedir: Path, _ispytest: bool = False) -> None:
 91        """Clear the sub-directories used to hold cached directories and values.
 92
 93        :meta private:
 94        """
 95        check_ispytest(_ispytest)
 96        for prefix in (cls._CACHE_PREFIX_DIRS, cls._CACHE_PREFIX_VALUES):
 97            d = cachedir / prefix
 98            if d.is_dir():
 99                rm_rf(d)
100
101    @staticmethod
102    def cache_dir_from_config(config: Config, *, _ispytest: bool = False) -> Path:
103        """Get the path to the cache directory for a Config.
104
105        :meta private:
106        """
107        check_ispytest(_ispytest)
108        return resolve_from_str(config.getini("cache_dir"), config.rootpath)
109
110    def warn(self, fmt: str, *, _ispytest: bool = False, **args: object) -> None:
111        """Issue a cache warning.
112
113        :meta private:
114        """
115        check_ispytest(_ispytest)
116        import warnings
117
118        from _pytest.warning_types import PytestCacheWarning
119
120        warnings.warn(
121            PytestCacheWarning(fmt.format(**args) if args else fmt),
122            self._config.hook,
123            stacklevel=3,
124        )
125
126    def _mkdir(self, path: Path) -> None:
127        self._ensure_cache_dir_and_supporting_files()
128        path.mkdir(exist_ok=True, parents=True)
129
130    def mkdir(self, name: str) -> Path:
131        """Return a directory path object with the given name.
132
133        If the directory does not yet exist, it will be created. You can use
134        it to manage files to e.g. store/retrieve database dumps across test
135        sessions.
136
137        .. versionadded:: 7.0
138
139        :param name:
140            Must be a string not containing a ``/`` separator.
141            Make sure the name contains your plugin or application
142            identifiers to prevent clashes with other cache users.
143        """
144        path = Path(name)
145        if len(path.parts) > 1:
146            raise ValueError("name is not allowed to contain path separators")
147        res = self._cachedir.joinpath(self._CACHE_PREFIX_DIRS, path)
148        self._mkdir(res)
149        return res
150
151    def _getvaluepath(self, key: str) -> Path:
152        return self._cachedir.joinpath(self._CACHE_PREFIX_VALUES, Path(key))
153
154    def get(self, key: str, default):
155        """Return the cached value for the given key.
156
157        If no value was yet cached or the value cannot be read, the specified
158        default is returned.
159
160        :param key:
161            Must be a ``/`` separated value. Usually the first
162            name is the name of your plugin or your application.
163        :param default:
164            The value to return in case of a cache-miss or invalid cache value.
165        """
166        path = self._getvaluepath(key)
167        try:
168            with path.open("r", encoding="UTF-8") as f:
169                return json.load(f)
170        except (ValueError, OSError):
171            return default
172
173    def set(self, key: str, value: object) -> None:
174        """Save value for the given key.
175
176        :param key:
177            Must be a ``/`` separated value. Usually the first
178            name is the name of your plugin or your application.
179        :param value:
180            Must be of any combination of basic python types,
181            including nested types like lists of dictionaries.
182        """
183        path = self._getvaluepath(key)
184        try:
185            self._mkdir(path.parent)
186        except OSError as exc:
187            self.warn(
188                f"could not create cache path {path}: {exc}",
189                _ispytest=True,
190            )
191            return
192        data = json.dumps(value, ensure_ascii=False, indent=2)
193        try:
194            f = path.open("w", encoding="UTF-8")
195        except OSError as exc:
196            self.warn(
197                f"cache could not write path {path}: {exc}",
198                _ispytest=True,
199            )
200        else:
201            with f:
202                f.write(data)
203
204    def _ensure_cache_dir_and_supporting_files(self) -> None:
205        """Create the cache dir and its supporting files."""
206        if self._cachedir.is_dir():
207            return
208
209        self._cachedir.parent.mkdir(parents=True, exist_ok=True)
210        with tempfile.TemporaryDirectory(
211            prefix="pytest-cache-files-",
212            dir=self._cachedir.parent,
213        ) as newpath:
214            path = Path(newpath)
215
216            # Reset permissions to the default, see #12308.
217            # Note: there's no way to get the current umask atomically, eek.
218            umask = os.umask(0o022)
219            os.umask(umask)
220            path.chmod(0o777 - umask)
221
222            with open(path.joinpath("README.md"), "x", encoding="UTF-8") as f:
223                f.write(README_CONTENT)
224            with open(path.joinpath(".gitignore"), "x", encoding="UTF-8") as f:
225                f.write("# Created by pytest automatically.\n*\n")
226            with open(path.joinpath("CACHEDIR.TAG"), "xb") as f:
227                f.write(CACHEDIR_TAG_CONTENT)
228
229            try:
230                path.rename(self._cachedir)
231            except OSError as e:
232                # If 2 concurrent pytests both race to the rename, the loser
233                # gets "Directory not empty" from the rename. In this case,
234                # everything is handled so just continue (while letting the
235                # temporary directory be cleaned up).
236                # On Windows, the error is a FileExistsError which translates to EEXIST.
237                if e.errno not in (errno.ENOTEMPTY, errno.EEXIST):
238                    raise
239            else:
240                # Create a directory in place of the one we just moved so that
241                # `TemporaryDirectory`'s cleanup doesn't complain.
242                #
243                # TODO: pass ignore_cleanup_errors=True when we no longer support python < 3.10.
244                # See https://github.com/python/cpython/issues/74168. Note that passing
245                # delete=False would do the wrong thing in case of errors and isn't supported
246                # until python 3.12.
247                path.mkdir()

Instance of the cache fixture.

Cache( cachedir: pathlib.Path, config: _pytest.config.Config, *, _ispytest: bool = False)
70    def __init__(
71        self, cachedir: Path, config: Config, *, _ispytest: bool = False
72    ) -> None:
73        check_ispytest(_ispytest)
74        self._cachedir = cachedir
75        self._config = config
@classmethod
def for_config( cls, config: _pytest.config.Config, *, _ispytest: bool = False) -> _pytest.cacheprovider.Cache:
77    @classmethod
78    def for_config(cls, config: Config, *, _ispytest: bool = False) -> Cache:
79        """Create the Cache instance for a Config.
80
81        :meta private:
82        """
83        check_ispytest(_ispytest)
84        cachedir = cls.cache_dir_from_config(config, _ispytest=True)
85        if config.getoption("cacheclear") and cachedir.is_dir():
86            cls.clear_cache(cachedir, _ispytest=True)
87        return cls(cachedir, config, _ispytest=True)

Create the Cache instance for a Config.

:meta private:

@classmethod
def clear_cache(cls, cachedir: pathlib.Path, _ispytest: bool = False) -> None:
89    @classmethod
90    def clear_cache(cls, cachedir: Path, _ispytest: bool = False) -> None:
91        """Clear the sub-directories used to hold cached directories and values.
92
93        :meta private:
94        """
95        check_ispytest(_ispytest)
96        for prefix in (cls._CACHE_PREFIX_DIRS, cls._CACHE_PREFIX_VALUES):
97            d = cachedir / prefix
98            if d.is_dir():
99                rm_rf(d)

Clear the sub-directories used to hold cached directories and values.

:meta private:

@staticmethod
def cache_dir_from_config( config: _pytest.config.Config, *, _ispytest: bool = False) -> pathlib.Path:
101    @staticmethod
102    def cache_dir_from_config(config: Config, *, _ispytest: bool = False) -> Path:
103        """Get the path to the cache directory for a Config.
104
105        :meta private:
106        """
107        check_ispytest(_ispytest)
108        return resolve_from_str(config.getini("cache_dir"), config.rootpath)

Get the path to the cache directory for a Config.

:meta private:

def warn(self, fmt: str, *, _ispytest: bool = False, **args: object) -> None:
110    def warn(self, fmt: str, *, _ispytest: bool = False, **args: object) -> None:
111        """Issue a cache warning.
112
113        :meta private:
114        """
115        check_ispytest(_ispytest)
116        import warnings
117
118        from _pytest.warning_types import PytestCacheWarning
119
120        warnings.warn(
121            PytestCacheWarning(fmt.format(**args) if args else fmt),
122            self._config.hook,
123            stacklevel=3,
124        )

Issue a cache warning.

:meta private:

def mkdir(self, name: str) -> pathlib.Path:
130    def mkdir(self, name: str) -> Path:
131        """Return a directory path object with the given name.
132
133        If the directory does not yet exist, it will be created. You can use
134        it to manage files to e.g. store/retrieve database dumps across test
135        sessions.
136
137        .. versionadded:: 7.0
138
139        :param name:
140            Must be a string not containing a ``/`` separator.
141            Make sure the name contains your plugin or application
142            identifiers to prevent clashes with other cache users.
143        """
144        path = Path(name)
145        if len(path.parts) > 1:
146            raise ValueError("name is not allowed to contain path separators")
147        res = self._cachedir.joinpath(self._CACHE_PREFIX_DIRS, path)
148        self._mkdir(res)
149        return res

Return a directory path object with the given name.

If the directory does not yet exist, it will be created. You can use it to manage files to e.g. store/retrieve database dumps across test sessions.

New in version 7.0.

Parameters
  • name: Must be a string not containing a / separator. Make sure the name contains your plugin or application identifiers to prevent clashes with other cache users.
def get(self, key: str, default):
154    def get(self, key: str, default):
155        """Return the cached value for the given key.
156
157        If no value was yet cached or the value cannot be read, the specified
158        default is returned.
159
160        :param key:
161            Must be a ``/`` separated value. Usually the first
162            name is the name of your plugin or your application.
163        :param default:
164            The value to return in case of a cache-miss or invalid cache value.
165        """
166        path = self._getvaluepath(key)
167        try:
168            with path.open("r", encoding="UTF-8") as f:
169                return json.load(f)
170        except (ValueError, OSError):
171            return default

Return the cached value for the given key.

If no value was yet cached or the value cannot be read, the specified default is returned.

Parameters
  • key: Must be a / separated value. Usually the first name is the name of your plugin or your application.
  • default: The value to return in case of a cache-miss or invalid cache value.
def set(self, key: str, value: object) -> None:
173    def set(self, key: str, value: object) -> None:
174        """Save value for the given key.
175
176        :param key:
177            Must be a ``/`` separated value. Usually the first
178            name is the name of your plugin or your application.
179        :param value:
180            Must be of any combination of basic python types,
181            including nested types like lists of dictionaries.
182        """
183        path = self._getvaluepath(key)
184        try:
185            self._mkdir(path.parent)
186        except OSError as exc:
187            self.warn(
188                f"could not create cache path {path}: {exc}",
189                _ispytest=True,
190            )
191            return
192        data = json.dumps(value, ensure_ascii=False, indent=2)
193        try:
194            f = path.open("w", encoding="UTF-8")
195        except OSError as exc:
196            self.warn(
197                f"cache could not write path {path}: {exc}",
198                _ispytest=True,
199            )
200        else:
201            with f:
202                f.write(data)

Save value for the given key.

Parameters
  • key: Must be a / separated value. Usually the first name is the name of your plugin or your application.
  • value: Must be of any combination of basic python types, including nested types like lists of dictionaries.
@final
@dataclasses.dataclass
class CallInfo(typing.Generic[+TResult]):
275@final
276@dataclasses.dataclass
277class CallInfo(Generic[TResult]):
278    """Result/Exception info of a function invocation."""
279
280    _result: TResult | None
281    #: The captured exception of the call, if it raised.
282    excinfo: ExceptionInfo[BaseException] | None
283    #: The system time when the call started, in seconds since the epoch.
284    start: float
285    #: The system time when the call ended, in seconds since the epoch.
286    stop: float
287    #: The call duration, in seconds.
288    duration: float
289    #: The context of invocation: "collect", "setup", "call" or "teardown".
290    when: Literal["collect", "setup", "call", "teardown"]
291
292    def __init__(
293        self,
294        result: TResult | None,
295        excinfo: ExceptionInfo[BaseException] | None,
296        start: float,
297        stop: float,
298        duration: float,
299        when: Literal["collect", "setup", "call", "teardown"],
300        *,
301        _ispytest: bool = False,
302    ) -> None:
303        check_ispytest(_ispytest)
304        self._result = result
305        self.excinfo = excinfo
306        self.start = start
307        self.stop = stop
308        self.duration = duration
309        self.when = when
310
311    @property
312    def result(self) -> TResult:
313        """The return value of the call, if it didn't raise.
314
315        Can only be accessed if excinfo is None.
316        """
317        if self.excinfo is not None:
318            raise AttributeError(f"{self!r} has no valid result")
319        # The cast is safe because an exception wasn't raised, hence
320        # _result has the expected function return type (which may be
321        #  None, that's why a cast and not an assert).
322        return cast(TResult, self._result)
323
324    @classmethod
325    def from_call(
326        cls,
327        func: Callable[[], TResult],
328        when: Literal["collect", "setup", "call", "teardown"],
329        reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None,
330    ) -> CallInfo[TResult]:
331        """Call func, wrapping the result in a CallInfo.
332
333        :param func:
334            The function to call. Called without arguments.
335        :type func: Callable[[], _pytest.runner.TResult]
336        :param when:
337            The phase in which the function is called.
338        :param reraise:
339            Exception or exceptions that shall propagate if raised by the
340            function, instead of being wrapped in the CallInfo.
341        """
342        excinfo = None
343        instant = timing.Instant()
344        try:
345            result: TResult | None = func()
346        except BaseException:
347            excinfo = ExceptionInfo.from_current()
348            if reraise is not None and isinstance(excinfo.value, reraise):
349                raise
350            result = None
351        duration = instant.elapsed()
352        return cls(
353            start=duration.start.time,
354            stop=duration.stop.time,
355            duration=duration.seconds,
356            when=when,
357            result=result,
358            excinfo=excinfo,
359            _ispytest=True,
360        )
361
362    def __repr__(self) -> str:
363        if self.excinfo is None:
364            return f"<CallInfo when={self.when!r} result: {self._result!r}>"
365        return f"<CallInfo when={self.when!r} excinfo={self.excinfo!r}>"

Result/Exception info of a function invocation.

CallInfo( result: Optional[+TResult], excinfo: Optional[_pytest._code.code.ExceptionInfo[BaseException]], start: float, stop: float, duration: float, when: Literal['collect', 'setup', 'call', 'teardown'], *, _ispytest: bool = False)
292    def __init__(
293        self,
294        result: TResult | None,
295        excinfo: ExceptionInfo[BaseException] | None,
296        start: float,
297        stop: float,
298        duration: float,
299        when: Literal["collect", "setup", "call", "teardown"],
300        *,
301        _ispytest: bool = False,
302    ) -> None:
303        check_ispytest(_ispytest)
304        self._result = result
305        self.excinfo = excinfo
306        self.start = start
307        self.stop = stop
308        self.duration = duration
309        self.when = when
excinfo: Optional[_pytest._code.code.ExceptionInfo[BaseException]]
start: float
stop: float
duration: float
when: Literal['collect', 'setup', 'call', 'teardown']
result: +TResult
311    @property
312    def result(self) -> TResult:
313        """The return value of the call, if it didn't raise.
314
315        Can only be accessed if excinfo is None.
316        """
317        if self.excinfo is not None:
318            raise AttributeError(f"{self!r} has no valid result")
319        # The cast is safe because an exception wasn't raised, hence
320        # _result has the expected function return type (which may be
321        #  None, that's why a cast and not an assert).
322        return cast(TResult, self._result)

The return value of the call, if it didn't raise.

Can only be accessed if excinfo is None.

@classmethod
def from_call( cls, func: Callable[[], +TResult], when: Literal['collect', 'setup', 'call', 'teardown'], reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None) -> _pytest.runner.CallInfo[+TResult]:
324    @classmethod
325    def from_call(
326        cls,
327        func: Callable[[], TResult],
328        when: Literal["collect", "setup", "call", "teardown"],
329        reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None,
330    ) -> CallInfo[TResult]:
331        """Call func, wrapping the result in a CallInfo.
332
333        :param func:
334            The function to call. Called without arguments.
335        :type func: Callable[[], _pytest.runner.TResult]
336        :param when:
337            The phase in which the function is called.
338        :param reraise:
339            Exception or exceptions that shall propagate if raised by the
340            function, instead of being wrapped in the CallInfo.
341        """
342        excinfo = None
343        instant = timing.Instant()
344        try:
345            result: TResult | None = func()
346        except BaseException:
347            excinfo = ExceptionInfo.from_current()
348            if reraise is not None and isinstance(excinfo.value, reraise):
349                raise
350            result = None
351        duration = instant.elapsed()
352        return cls(
353            start=duration.start.time,
354            stop=duration.stop.time,
355            duration=duration.seconds,
356            when=when,
357            result=result,
358            excinfo=excinfo,
359            _ispytest=True,
360        )

Call func, wrapping the result in a CallInfo.

Parameters
  • func: The function to call. Called without arguments.
  • when: The phase in which the function is called.
  • reraise: Exception or exceptions that shall propagate if raised by the function, instead of being wrapped in the CallInfo.
class CaptureFixture(typing.Generic[~AnyStr]):
917class CaptureFixture(Generic[AnyStr]):
918    """Object returned by the :fixture:`capsys`, :fixture:`capsysbinary`,
919    :fixture:`capfd` and :fixture:`capfdbinary` fixtures."""
920
921    def __init__(
922        self,
923        captureclass: type[CaptureBase[AnyStr]],
924        request: SubRequest,
925        *,
926        config: dict[str, Any] | None = None,
927        _ispytest: bool = False,
928    ) -> None:
929        check_ispytest(_ispytest)
930        self.captureclass: type[CaptureBase[AnyStr]] = captureclass
931        self.request = request
932        self._config = config if config else {}
933        self._capture: MultiCapture[AnyStr] | None = None
934        self._captured_out: AnyStr = self.captureclass.EMPTY_BUFFER
935        self._captured_err: AnyStr = self.captureclass.EMPTY_BUFFER
936
937    def _start(self) -> None:
938        if self._capture is None:
939            self._capture = MultiCapture(
940                in_=None,
941                out=self.captureclass(1, **self._config),
942                err=self.captureclass(2, **self._config),
943            )
944            self._capture.start_capturing()
945
946    def close(self) -> None:
947        if self._capture is not None:
948            out, err = self._capture.pop_outerr_to_orig()
949            self._captured_out += out
950            self._captured_err += err
951            self._capture.stop_capturing()
952            self._capture = None
953
954    def readouterr(self) -> CaptureResult[AnyStr]:
955        """Read and return the captured output so far, resetting the internal
956        buffer.
957
958        :returns:
959            The captured content as a namedtuple with ``out`` and ``err``
960            string attributes.
961        """
962        captured_out, captured_err = self._captured_out, self._captured_err
963        if self._capture is not None:
964            out, err = self._capture.readouterr()
965            captured_out += out
966            captured_err += err
967        self._captured_out = self.captureclass.EMPTY_BUFFER
968        self._captured_err = self.captureclass.EMPTY_BUFFER
969        return CaptureResult(captured_out, captured_err)
970
971    def _suspend(self) -> None:
972        """Suspend this fixture's own capturing temporarily."""
973        if self._capture is not None:
974            self._capture.suspend_capturing()
975
976    def _resume(self) -> None:
977        """Resume this fixture's own capturing temporarily."""
978        if self._capture is not None:
979            self._capture.resume_capturing()
980
981    def _is_started(self) -> bool:
982        """Whether actively capturing -- not disabled or closed."""
983        if self._capture is not None:
984            return self._capture.is_started()
985        return False
986
987    @contextlib.contextmanager
988    def disabled(self) -> Generator[None]:
989        """Temporarily disable capturing while inside the ``with`` block."""
990        capmanager: CaptureManager = self.request.config.pluginmanager.getplugin(
991            "capturemanager"
992        )
993        with capmanager.global_and_fixture_disabled():
994            yield

Object returned by the :fixture:capsys, :fixture:capsysbinary, :fixture:capfd and :fixture:capfdbinary fixtures.

CaptureFixture( captureclass: type[_pytest.capture.CaptureBase[~AnyStr]], request: _pytest.fixtures.SubRequest, *, config: dict[str, typing.Any] | None = None, _ispytest: bool = False)
921    def __init__(
922        self,
923        captureclass: type[CaptureBase[AnyStr]],
924        request: SubRequest,
925        *,
926        config: dict[str, Any] | None = None,
927        _ispytest: bool = False,
928    ) -> None:
929        check_ispytest(_ispytest)
930        self.captureclass: type[CaptureBase[AnyStr]] = captureclass
931        self.request = request
932        self._config = config if config else {}
933        self._capture: MultiCapture[AnyStr] | None = None
934        self._captured_out: AnyStr = self.captureclass.EMPTY_BUFFER
935        self._captured_err: AnyStr = self.captureclass.EMPTY_BUFFER
captureclass: type[_pytest.capture.CaptureBase[~AnyStr]]
request
def close(self) -> None:
946    def close(self) -> None:
947        if self._capture is not None:
948            out, err = self._capture.pop_outerr_to_orig()
949            self._captured_out += out
950            self._captured_err += err
951            self._capture.stop_capturing()
952            self._capture = None
def readouterr(self) -> _pytest.capture.CaptureResult[~AnyStr]:
954    def readouterr(self) -> CaptureResult[AnyStr]:
955        """Read and return the captured output so far, resetting the internal
956        buffer.
957
958        :returns:
959            The captured content as a namedtuple with ``out`` and ``err``
960            string attributes.
961        """
962        captured_out, captured_err = self._captured_out, self._captured_err
963        if self._capture is not None:
964            out, err = self._capture.readouterr()
965            captured_out += out
966            captured_err += err
967        self._captured_out = self.captureclass.EMPTY_BUFFER
968        self._captured_err = self.captureclass.EMPTY_BUFFER
969        return CaptureResult(captured_out, captured_err)

Read and return the captured output so far, resetting the internal buffer.

:returns: The captured content as a namedtuple with out and err string attributes.

@contextlib.contextmanager
def disabled(self) -> Generator[None]:
987    @contextlib.contextmanager
988    def disabled(self) -> Generator[None]:
989        """Temporarily disable capturing while inside the ``with`` block."""
990        capmanager: CaptureManager = self.request.config.pluginmanager.getplugin(
991            "capturemanager"
992        )
993        with capmanager.global_and_fixture_disabled():
994            yield

Temporarily disable capturing while inside the with block.

class Class(_pytest.python.PyCollector):
737class Class(PyCollector):
738    """Collector for test methods (and nested classes) in a Python class."""
739
740    @classmethod
741    def from_parent(cls, parent, *, name, obj=None, **kw) -> Self:  # type: ignore[override]
742        """The public constructor."""
743        return super().from_parent(name=name, parent=parent, **kw)
744
745    def newinstance(self):
746        return self.obj()
747
748    def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
749        if not safe_getattr(self.obj, "__test__", True):
750            return []
751        if hasinit(self.obj):
752            assert self.parent is not None
753            self.warn(
754                PytestCollectionWarning(
755                    f"cannot collect test class {self.obj.__name__!r} because it has a "
756                    f"__init__ constructor (from: {self.parent.nodeid})"
757                )
758            )
759            return []
760        elif hasnew(self.obj):
761            assert self.parent is not None
762            self.warn(
763                PytestCollectionWarning(
764                    f"cannot collect test class {self.obj.__name__!r} because it has a "
765                    f"__new__ constructor (from: {self.parent.nodeid})"
766                )
767            )
768            return []
769
770        self._register_setup_class_fixture()
771        self._register_setup_method_fixture()
772
773        self.session._fixturemanager.parsefactories(self.newinstance(), self.nodeid)
774
775        return super().collect()
776
777    def _register_setup_class_fixture(self) -> None:
778        """Register an autouse, class scoped fixture into the collected class object
779        that invokes setup_class/teardown_class if either or both are available.
780
781        Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
782        other fixtures (#517).
783        """
784        setup_class = _get_first_non_fixture_func(self.obj, ("setup_class",))
785        teardown_class = _get_first_non_fixture_func(self.obj, ("teardown_class",))
786        if setup_class is None and teardown_class is None:
787            return
788
789        def xunit_setup_class_fixture(request) -> Generator[None]:
790            cls = request.cls
791            if setup_class is not None:
792                func = getimfunc(setup_class)
793                _call_with_optional_argument(func, cls)
794            yield
795            if teardown_class is not None:
796                func = getimfunc(teardown_class)
797                _call_with_optional_argument(func, cls)
798
799        self.session._fixturemanager._register_fixture(
800            # Use a unique name to speed up lookup.
801            name=f"_xunit_setup_class_fixture_{self.obj.__qualname__}",
802            func=xunit_setup_class_fixture,
803            nodeid=self.nodeid,
804            scope="class",
805            autouse=True,
806        )
807
808    def _register_setup_method_fixture(self) -> None:
809        """Register an autouse, function scoped fixture into the collected class object
810        that invokes setup_method/teardown_method if either or both are available.
811
812        Using a fixture to invoke these methods ensures we play nicely and unsurprisingly with
813        other fixtures (#517).
814        """
815        setup_name = "setup_method"
816        setup_method = _get_first_non_fixture_func(self.obj, (setup_name,))
817        teardown_name = "teardown_method"
818        teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,))
819        if setup_method is None and teardown_method is None:
820            return
821
822        def xunit_setup_method_fixture(request) -> Generator[None]:
823            instance = request.instance
824            method = request.function
825            if setup_method is not None:
826                func = getattr(instance, setup_name)
827                _call_with_optional_argument(func, method)
828            yield
829            if teardown_method is not None:
830                func = getattr(instance, teardown_name)
831                _call_with_optional_argument(func, method)
832
833        self.session._fixturemanager._register_fixture(
834            # Use a unique name to speed up lookup.
835            name=f"_xunit_setup_method_fixture_{self.obj.__qualname__}",
836            func=xunit_setup_method_fixture,
837            nodeid=self.nodeid,
838            scope="function",
839            autouse=True,
840        )

Collector for test methods (and nested classes) in a Python class.

@classmethod
def from_parent(cls, parent, *, name, obj=None, **kw) -> Self:
740    @classmethod
741    def from_parent(cls, parent, *, name, obj=None, **kw) -> Self:  # type: ignore[override]
742        """The public constructor."""
743        return super().from_parent(name=name, parent=parent, **kw)

The public constructor.

def newinstance(self):
745    def newinstance(self):
746        return self.obj()
def collect(self) -> Iterable[_pytest.nodes.Item | _pytest.nodes.Collector]:
748    def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
749        if not safe_getattr(self.obj, "__test__", True):
750            return []
751        if hasinit(self.obj):
752            assert self.parent is not None
753            self.warn(
754                PytestCollectionWarning(
755                    f"cannot collect test class {self.obj.__name__!r} because it has a "
756                    f"__init__ constructor (from: {self.parent.nodeid})"
757                )
758            )
759            return []
760        elif hasnew(self.obj):
761            assert self.parent is not None
762            self.warn(
763                PytestCollectionWarning(
764                    f"cannot collect test class {self.obj.__name__!r} because it has a "
765                    f"__new__ constructor (from: {self.parent.nodeid})"
766                )
767            )
768            return []
769
770        self._register_setup_class_fixture()
771        self._register_setup_method_fixture()
772
773        self.session._fixturemanager.parsefactories(self.newinstance(), self.nodeid)
774
775        return super().collect()

Collect children (items and collectors) for this collector.

@final
class CollectReport(_pytest.reports.BaseReport):
400@final
401class CollectReport(BaseReport):
402    """Collection report object.
403
404    Reports can contain arbitrary extra attributes.
405    """
406
407    when = "collect"
408
409    def __init__(
410        self,
411        nodeid: str,
412        outcome: Literal["passed", "failed", "skipped"],
413        longrepr: None
414        | ExceptionInfo[BaseException]
415        | tuple[str, int, str]
416        | str
417        | TerminalRepr,
418        result: list[Item | Collector] | None,
419        sections: Iterable[tuple[str, str]] = (),
420        **extra,
421    ) -> None:
422        #: Normalized collection nodeid.
423        self.nodeid = nodeid
424
425        #: Test outcome, always one of "passed", "failed", "skipped".
426        self.outcome = outcome
427
428        #: None or a failure representation.
429        self.longrepr = longrepr
430
431        #: The collected items and collection nodes.
432        self.result = result or []
433
434        #: Tuples of str ``(heading, content)`` with extra information
435        #: for the test report. Used by pytest to add text captured
436        #: from ``stdout``, ``stderr``, and intercepted logging events. May
437        #: be used by other plugins to add arbitrary information to reports.
438        self.sections = list(sections)
439
440        self.__dict__.update(extra)
441
442    @property
443    def location(  # type:ignore[override]
444        self,
445    ) -> tuple[str, int | None, str] | None:
446        return (self.fspath, None, self.fspath)
447
448    def __repr__(self) -> str:
449        return f"<CollectReport {self.nodeid!r} lenresult={len(self.result)} outcome={self.outcome!r}>"

Collection report object.

Reports can contain arbitrary extra attributes.

CollectReport( nodeid: str, outcome: Literal['passed', 'failed', 'skipped'], longrepr: Union[NoneType, _pytest._code.code.ExceptionInfo[BaseException], tuple[str, int, str], str, _pytest._code.code.TerminalRepr], result: list[_pytest.nodes.Item | _pytest.nodes.Collector] | None, sections: Iterable[tuple[str, str]] = (), **extra)
409    def __init__(
410        self,
411        nodeid: str,
412        outcome: Literal["passed", "failed", "skipped"],
413        longrepr: None
414        | ExceptionInfo[BaseException]
415        | tuple[str, int, str]
416        | str
417        | TerminalRepr,
418        result: list[Item | Collector] | None,
419        sections: Iterable[tuple[str, str]] = (),
420        **extra,
421    ) -> None:
422        #: Normalized collection nodeid.
423        self.nodeid = nodeid
424
425        #: Test outcome, always one of "passed", "failed", "skipped".
426        self.outcome = outcome
427
428        #: None or a failure representation.
429        self.longrepr = longrepr
430
431        #: The collected items and collection nodes.
432        self.result = result or []
433
434        #: Tuples of str ``(heading, content)`` with extra information
435        #: for the test report. Used by pytest to add text captured
436        #: from ``stdout``, ``stderr``, and intercepted logging events. May
437        #: be used by other plugins to add arbitrary information to reports.
438        self.sections = list(sections)
439
440        self.__dict__.update(extra)
when = 'collect'
nodeid
outcome
longrepr
result
sections
location: tuple[str, int | None, str] | None
442    @property
443    def location(  # type:ignore[override]
444        self,
445    ) -> tuple[str, int | None, str] | None:
446        return (self.fspath, None, self.fspath)
class Collector(_pytest.nodes.Node, abc.ABC):
500class Collector(Node, abc.ABC):
501    """Base class of all collectors.
502
503    Collector create children through `collect()` and thus iteratively build
504    the collection tree.
505    """
506
507    class CollectError(Exception):
508        """An error during collection, contains a custom message."""
509
510    @abc.abstractmethod
511    def collect(self) -> Iterable[Item | Collector]:
512        """Collect children (items and collectors) for this collector."""
513        raise NotImplementedError("abstract")
514
515    # TODO: This omits the style= parameter which breaks Liskov Substitution.
516    def repr_failure(  # type: ignore[override]
517        self, excinfo: ExceptionInfo[BaseException]
518    ) -> str | TerminalRepr:
519        """Return a representation of a collection failure.
520
521        :param excinfo: Exception information for the failure.
522        """
523        if isinstance(excinfo.value, self.CollectError) and not self.config.getoption(
524            "fulltrace", False
525        ):
526            exc = excinfo.value
527            return str(exc.args[0])
528
529        # Respect explicit tbstyle option, but default to "short"
530        # (_repr_failure_py uses "long" with "fulltrace" option always).
531        tbstyle = self.config.getoption("tbstyle", "auto")
532        if tbstyle == "auto":
533            tbstyle = "short"
534
535        return self._repr_failure_py(excinfo, style=tbstyle)
536
537    def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback:
538        if hasattr(self, "path"):
539            traceback = excinfo.traceback
540            ntraceback = traceback.cut(path=self.path)
541            if ntraceback == traceback:
542                ntraceback = ntraceback.cut(excludepath=tracebackcutdir)
543            return ntraceback.filter(excinfo)
544        return excinfo.traceback

Base class of all collectors.

Collector create children through collect() and thus iteratively build the collection tree.

@abc.abstractmethod
def collect(self) -> Iterable[_pytest.nodes.Item | _pytest.nodes.Collector]:
510    @abc.abstractmethod
511    def collect(self) -> Iterable[Item | Collector]:
512        """Collect children (items and collectors) for this collector."""
513        raise NotImplementedError("abstract")

Collect children (items and collectors) for this collector.

def repr_failure( self, excinfo: _pytest._code.code.ExceptionInfo[BaseException]) -> str | _pytest._code.code.TerminalRepr:
516    def repr_failure(  # type: ignore[override]
517        self, excinfo: ExceptionInfo[BaseException]
518    ) -> str | TerminalRepr:
519        """Return a representation of a collection failure.
520
521        :param excinfo: Exception information for the failure.
522        """
523        if isinstance(excinfo.value, self.CollectError) and not self.config.getoption(
524            "fulltrace", False
525        ):
526            exc = excinfo.value
527            return str(exc.args[0])
528
529        # Respect explicit tbstyle option, but default to "short"
530        # (_repr_failure_py uses "long" with "fulltrace" option always).
531        tbstyle = self.config.getoption("tbstyle", "auto")
532        if tbstyle == "auto":
533            tbstyle = "short"
534
535        return self._repr_failure_py(excinfo, style=tbstyle)

Return a representation of a collection failure.

Parameters
  • excinfo: Exception information for the failure.
class Collector.CollectError(builtins.Exception):
507    class CollectError(Exception):
508        """An error during collection, contains a custom message."""

An error during collection, contains a custom message.

@final
class Config:
 968@final
 969class Config:
 970    """Access to configuration values, pluginmanager and plugin hooks.
 971
 972    :param PytestPluginManager pluginmanager:
 973        A pytest PluginManager.
 974
 975    :param InvocationParams invocation_params:
 976        Object containing parameters regarding the :func:`pytest.main`
 977        invocation.
 978    """
 979
 980    @final
 981    @dataclasses.dataclass(frozen=True)
 982    class InvocationParams:
 983        """Holds parameters passed during :func:`pytest.main`.
 984
 985        The object attributes are read-only.
 986
 987        .. versionadded:: 5.1
 988
 989        .. note::
 990
 991            Note that the environment variable ``PYTEST_ADDOPTS`` and the ``addopts``
 992            ini option are handled by pytest, not being included in the ``args`` attribute.
 993
 994            Plugins accessing ``InvocationParams`` must be aware of that.
 995        """
 996
 997        args: tuple[str, ...]
 998        """The command-line arguments as passed to :func:`pytest.main`."""
 999        plugins: Sequence[str | _PluggyPlugin] | None
1000        """Extra plugins, might be `None`."""
1001        dir: pathlib.Path
1002        """The directory from which :func:`pytest.main` was invoked. :type: pathlib.Path"""
1003
1004        def __init__(
1005            self,
1006            *,
1007            args: Iterable[str],
1008            plugins: Sequence[str | _PluggyPlugin] | None,
1009            dir: pathlib.Path,
1010        ) -> None:
1011            object.__setattr__(self, "args", tuple(args))
1012            object.__setattr__(self, "plugins", plugins)
1013            object.__setattr__(self, "dir", dir)
1014
1015    class ArgsSource(enum.Enum):
1016        """Indicates the source of the test arguments.
1017
1018        .. versionadded:: 7.2
1019        """
1020
1021        #: Command line arguments.
1022        ARGS = enum.auto()
1023        #: Invocation directory.
1024        INVOCATION_DIR = enum.auto()
1025        INCOVATION_DIR = INVOCATION_DIR  # backwards compatibility alias
1026        #: 'testpaths' configuration value.
1027        TESTPATHS = enum.auto()
1028
1029    # Set by cacheprovider plugin.
1030    cache: Cache
1031
1032    def __init__(
1033        self,
1034        pluginmanager: PytestPluginManager,
1035        *,
1036        invocation_params: InvocationParams | None = None,
1037    ) -> None:
1038        from .argparsing import FILE_OR_DIR
1039        from .argparsing import Parser
1040
1041        if invocation_params is None:
1042            invocation_params = self.InvocationParams(
1043                args=(), plugins=None, dir=pathlib.Path.cwd()
1044            )
1045
1046        self.option = argparse.Namespace()
1047        """Access to command line option as attributes.
1048
1049        :type: argparse.Namespace
1050        """
1051
1052        self.invocation_params = invocation_params
1053        """The parameters with which pytest was invoked.
1054
1055        :type: InvocationParams
1056        """
1057
1058        _a = FILE_OR_DIR
1059        self._parser = Parser(
1060            usage=f"%(prog)s [options] [{_a}] [{_a}] [...]",
1061            processopt=self._processopt,
1062            _ispytest=True,
1063        )
1064        self.pluginmanager = pluginmanager
1065        """The plugin manager handles plugin registration and hook invocation.
1066
1067        :type: PytestPluginManager
1068        """
1069
1070        self.stash = Stash()
1071        """A place where plugins can store information on the config for their
1072        own use.
1073
1074        :type: Stash
1075        """
1076        # Deprecated alias. Was never public. Can be removed in a few releases.
1077        self._store = self.stash
1078
1079        self.trace = self.pluginmanager.trace.root.get("config")
1080        self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook)  # type: ignore[assignment]
1081        self._inicache: dict[str, Any] = {}
1082        self._override_ini: Sequence[str] = ()
1083        self._opt2dest: dict[str, str] = {}
1084        self._cleanup_stack = contextlib.ExitStack()
1085        self.pluginmanager.register(self, "pytestconfig")
1086        self._configured = False
1087        self.hook.pytest_addoption.call_historic(
1088            kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager)
1089        )
1090        self.args_source = Config.ArgsSource.ARGS
1091        self.args: list[str] = []
1092
1093    @property
1094    def rootpath(self) -> pathlib.Path:
1095        """The path to the :ref:`rootdir <rootdir>`.
1096
1097        :type: pathlib.Path
1098
1099        .. versionadded:: 6.1
1100        """
1101        return self._rootpath
1102
1103    @property
1104    def inipath(self) -> pathlib.Path | None:
1105        """The path to the :ref:`configfile <configfiles>`.
1106
1107        .. versionadded:: 6.1
1108        """
1109        return self._inipath
1110
1111    def add_cleanup(self, func: Callable[[], None]) -> None:
1112        """Add a function to be called when the config object gets out of
1113        use (usually coinciding with pytest_unconfigure).
1114        """
1115        self._cleanup_stack.callback(func)
1116
1117    def _do_configure(self) -> None:
1118        assert not self._configured
1119        self._configured = True
1120        self.hook.pytest_configure.call_historic(kwargs=dict(config=self))
1121
1122    def _ensure_unconfigure(self) -> None:
1123        try:
1124            if self._configured:
1125                self._configured = False
1126                try:
1127                    self.hook.pytest_unconfigure(config=self)
1128                finally:
1129                    self.hook.pytest_configure._call_history = []
1130        finally:
1131            try:
1132                self._cleanup_stack.close()
1133            finally:
1134                self._cleanup_stack = contextlib.ExitStack()
1135
1136    def get_terminal_writer(self) -> TerminalWriter:
1137        terminalreporter: TerminalReporter | None = self.pluginmanager.get_plugin(
1138            "terminalreporter"
1139        )
1140        assert terminalreporter is not None
1141        return terminalreporter._tw
1142
1143    def pytest_cmdline_parse(
1144        self, pluginmanager: PytestPluginManager, args: list[str]
1145    ) -> Config:
1146        try:
1147            self.parse(args)
1148        except UsageError:
1149            # Handle --version and --help here in a minimal fashion.
1150            # This gets done via helpconfig normally, but its
1151            # pytest_cmdline_main is not called in case of errors.
1152            if getattr(self.option, "version", False) or "--version" in args:
1153                from _pytest.helpconfig import showversion
1154
1155                showversion(self)
1156            elif (
1157                getattr(self.option, "help", False) or "--help" in args or "-h" in args
1158            ):
1159                self._parser._getparser().print_help()
1160                sys.stdout.write(
1161                    "\nNOTE: displaying only minimal help due to UsageError.\n\n"
1162                )
1163
1164            raise
1165
1166        return self
1167
1168    def notify_exception(
1169        self,
1170        excinfo: ExceptionInfo[BaseException],
1171        option: argparse.Namespace | None = None,
1172    ) -> None:
1173        if option and getattr(option, "fulltrace", False):
1174            style: TracebackStyle = "long"
1175        else:
1176            style = "native"
1177        excrepr = excinfo.getrepr(
1178            funcargs=True, showlocals=getattr(option, "showlocals", False), style=style
1179        )
1180        res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo)
1181        if not any(res):
1182            for line in str(excrepr).split("\n"):
1183                sys.stderr.write(f"INTERNALERROR> {line}\n")
1184                sys.stderr.flush()
1185
1186    def cwd_relative_nodeid(self, nodeid: str) -> str:
1187        # nodeid's are relative to the rootpath, compute relative to cwd.
1188        if self.invocation_params.dir != self.rootpath:
1189            base_path_part, *nodeid_part = nodeid.split("::")
1190            # Only process path part
1191            fullpath = self.rootpath / base_path_part
1192            relative_path = bestrelpath(self.invocation_params.dir, fullpath)
1193
1194            nodeid = "::".join([relative_path, *nodeid_part])
1195        return nodeid
1196
1197    @classmethod
1198    def fromdictargs(cls, option_dict, args) -> Config:
1199        """Constructor usable for subprocesses."""
1200        config = get_config(args)
1201        config.option.__dict__.update(option_dict)
1202        config.parse(args, addopts=False)
1203        for x in config.option.plugins:
1204            config.pluginmanager.consider_pluginarg(x)
1205        return config
1206
1207    def _processopt(self, opt: Argument) -> None:
1208        for name in opt._short_opts + opt._long_opts:
1209            self._opt2dest[name] = opt.dest
1210
1211        if hasattr(opt, "default"):
1212            if not hasattr(self.option, opt.dest):
1213                setattr(self.option, opt.dest, opt.default)
1214
1215    @hookimpl(trylast=True)
1216    def pytest_load_initial_conftests(self, early_config: Config) -> None:
1217        # We haven't fully parsed the command line arguments yet, so
1218        # early_config.args it not set yet. But we need it for
1219        # discovering the initial conftests. So "pre-run" the logic here.
1220        # It will be done for real in `parse()`.
1221        args, args_source = early_config._decide_args(
1222            args=early_config.known_args_namespace.file_or_dir,
1223            pyargs=early_config.known_args_namespace.pyargs,
1224            testpaths=early_config.getini("testpaths"),
1225            invocation_dir=early_config.invocation_params.dir,
1226            rootpath=early_config.rootpath,
1227            warn=False,
1228        )
1229        self.pluginmanager._set_initial_conftests(
1230            args=args,
1231            pyargs=early_config.known_args_namespace.pyargs,
1232            noconftest=early_config.known_args_namespace.noconftest,
1233            rootpath=early_config.rootpath,
1234            confcutdir=early_config.known_args_namespace.confcutdir,
1235            invocation_dir=early_config.invocation_params.dir,
1236            importmode=early_config.known_args_namespace.importmode,
1237            consider_namespace_packages=early_config.getini(
1238                "consider_namespace_packages"
1239            ),
1240        )
1241
1242    def _initini(self, args: Sequence[str]) -> None:
1243        ns, unknown_args = self._parser.parse_known_and_unknown_args(
1244            args, namespace=copy.copy(self.option)
1245        )
1246        rootpath, inipath, inicfg = determine_setup(
1247            inifile=ns.inifilename,
1248            args=ns.file_or_dir + unknown_args,
1249            rootdir_cmd_arg=ns.rootdir or None,
1250            invocation_dir=self.invocation_params.dir,
1251        )
1252        self._rootpath = rootpath
1253        self._inipath = inipath
1254        self.inicfg = inicfg
1255        self._parser.extra_info["rootdir"] = str(self.rootpath)
1256        self._parser.extra_info["inifile"] = str(self.inipath)
1257        self._parser.addini("addopts", "Extra command line options", "args")
1258        self._parser.addini("minversion", "Minimally required pytest version")
1259        self._parser.addini(
1260            "pythonpath", type="paths", help="Add paths to sys.path", default=[]
1261        )
1262        self._parser.addini(
1263            "required_plugins",
1264            "Plugins that must be present for pytest to run",
1265            type="args",
1266            default=[],
1267        )
1268        self._override_ini = ns.override_ini or ()
1269
1270    def _consider_importhook(self, args: Sequence[str]) -> None:
1271        """Install the PEP 302 import hook if using assertion rewriting.
1272
1273        Needs to parse the --assert=<mode> option from the commandline
1274        and find all the installed plugins to mark them for rewriting
1275        by the importhook.
1276        """
1277        ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
1278        mode = getattr(ns, "assertmode", "plain")
1279
1280        disable_autoload = getattr(ns, "disable_plugin_autoload", False) or bool(
1281            os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
1282        )
1283        if mode == "rewrite":
1284            import _pytest.assertion
1285
1286            try:
1287                hook = _pytest.assertion.install_importhook(self)
1288            except SystemError:
1289                mode = "plain"
1290            else:
1291                self._mark_plugins_for_rewrite(hook, disable_autoload)
1292        self._warn_about_missing_assertion(mode)
1293
1294    def _mark_plugins_for_rewrite(
1295        self, hook: AssertionRewritingHook, disable_autoload: bool
1296    ) -> None:
1297        """Given an importhook, mark for rewrite any top-level
1298        modules or packages in the distribution package for
1299        all pytest plugins."""
1300        self.pluginmanager.rewrite_hook = hook
1301
1302        if disable_autoload:
1303            # We don't autoload from distribution package entry points,
1304            # no need to continue.
1305            return
1306
1307        package_files = (
1308            str(file)
1309            for dist in importlib.metadata.distributions()
1310            if any(ep.group == "pytest11" for ep in dist.entry_points)
1311            for file in dist.files or []
1312        )
1313
1314        for name in _iter_rewritable_modules(package_files):
1315            hook.mark_rewrite(name)
1316
1317    def _configure_python_path(self) -> None:
1318        # `pythonpath = a b` will set `sys.path` to `[a, b, x, y, z, ...]`
1319        for path in reversed(self.getini("pythonpath")):
1320            sys.path.insert(0, str(path))
1321        self.add_cleanup(self._unconfigure_python_path)
1322
1323    def _unconfigure_python_path(self) -> None:
1324        for path in self.getini("pythonpath"):
1325            path_str = str(path)
1326            if path_str in sys.path:
1327                sys.path.remove(path_str)
1328
1329    def _validate_args(self, args: list[str], via: str) -> list[str]:
1330        """Validate known args."""
1331        self._parser._config_source_hint = via  # type: ignore
1332        try:
1333            self._parser.parse_known_and_unknown_args(
1334                args, namespace=copy.copy(self.option)
1335            )
1336        finally:
1337            del self._parser._config_source_hint  # type: ignore
1338
1339        return args
1340
1341    def _decide_args(
1342        self,
1343        *,
1344        args: list[str],
1345        pyargs: bool,
1346        testpaths: list[str],
1347        invocation_dir: pathlib.Path,
1348        rootpath: pathlib.Path,
1349        warn: bool,
1350    ) -> tuple[list[str], ArgsSource]:
1351        """Decide the args (initial paths/nodeids) to use given the relevant inputs.
1352
1353        :param warn: Whether can issue warnings.
1354
1355        :returns: The args and the args source. Guaranteed to be non-empty.
1356        """
1357        if args:
1358            source = Config.ArgsSource.ARGS
1359            result = args
1360        else:
1361            if invocation_dir == rootpath:
1362                source = Config.ArgsSource.TESTPATHS
1363                if pyargs:
1364                    result = testpaths
1365                else:
1366                    result = []
1367                    for path in testpaths:
1368                        result.extend(sorted(glob.iglob(path, recursive=True)))
1369                    if testpaths and not result:
1370                        if warn:
1371                            warning_text = (
1372                                "No files were found in testpaths; "
1373                                "consider removing or adjusting your testpaths configuration. "
1374                                "Searching recursively from the current directory instead."
1375                            )
1376                            self.issue_config_time_warning(
1377                                PytestConfigWarning(warning_text), stacklevel=3
1378                            )
1379            else:
1380                result = []
1381            if not result:
1382                source = Config.ArgsSource.INVOCATION_DIR
1383                result = [str(invocation_dir)]
1384        return result, source
1385
1386    def _preparse(self, args: list[str], addopts: bool = True) -> None:
1387        if addopts:
1388            env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
1389            if len(env_addopts):
1390                args[:] = (
1391                    self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS")
1392                    + args
1393                )
1394        self._initini(args)
1395        if addopts:
1396            args[:] = (
1397                self._validate_args(self.getini("addopts"), "via addopts config") + args
1398            )
1399
1400        self.known_args_namespace = self._parser.parse_known_args(
1401            args, namespace=copy.copy(self.option)
1402        )
1403        self._checkversion()
1404        self._consider_importhook(args)
1405        self._configure_python_path()
1406        self.pluginmanager.consider_preparse(args, exclude_only=False)
1407        if (
1408            not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
1409            and not self.known_args_namespace.disable_plugin_autoload
1410        ):
1411            # Autoloading from distribution package entry point has
1412            # not been disabled.
1413            self.pluginmanager.load_setuptools_entrypoints("pytest11")
1414        # Otherwise only plugins explicitly specified in PYTEST_PLUGINS
1415        # are going to be loaded.
1416        self.pluginmanager.consider_env()
1417
1418        self.known_args_namespace = self._parser.parse_known_args(
1419            args, namespace=copy.copy(self.known_args_namespace)
1420        )
1421
1422        self._validate_plugins()
1423        self._warn_about_skipped_plugins()
1424
1425        if self.known_args_namespace.confcutdir is None:
1426            if self.inipath is not None:
1427                confcutdir = str(self.inipath.parent)
1428            else:
1429                confcutdir = str(self.rootpath)
1430            self.known_args_namespace.confcutdir = confcutdir
1431        try:
1432            self.hook.pytest_load_initial_conftests(
1433                early_config=self, args=args, parser=self._parser
1434            )
1435        except ConftestImportFailure as e:
1436            if self.known_args_namespace.help or self.known_args_namespace.version:
1437                # we don't want to prevent --help/--version to work
1438                # so just let it pass and print a warning at the end
1439                self.issue_config_time_warning(
1440                    PytestConfigWarning(f"could not load initial conftests: {e.path}"),
1441                    stacklevel=2,
1442                )
1443            else:
1444                raise
1445
1446    @hookimpl(wrapper=True)
1447    def pytest_collection(self) -> Generator[None, object, object]:
1448        # Validate invalid ini keys after collection is done so we take in account
1449        # options added by late-loading conftest files.
1450        try:
1451            return (yield)
1452        finally:
1453            self._validate_config_options()
1454
1455    def _checkversion(self) -> None:
1456        import pytest
1457
1458        minver = self.inicfg.get("minversion", None)
1459        if minver:
1460            # Imported lazily to improve start-up time.
1461            from packaging.version import Version
1462
1463            if not isinstance(minver, str):
1464                raise pytest.UsageError(
1465                    f"{self.inipath}: 'minversion' must be a single value"
1466                )
1467
1468            if Version(minver) > Version(pytest.__version__):
1469                raise pytest.UsageError(
1470                    f"{self.inipath}: 'minversion' requires pytest-{minver}, actual pytest-{pytest.__version__}'"
1471                )
1472
1473    def _validate_config_options(self) -> None:
1474        for key in sorted(self._get_unknown_ini_keys()):
1475            self._warn_or_fail_if_strict(f"Unknown config option: {key}\n")
1476
1477    def _validate_plugins(self) -> None:
1478        required_plugins = sorted(self.getini("required_plugins"))
1479        if not required_plugins:
1480            return
1481
1482        # Imported lazily to improve start-up time.
1483        from packaging.requirements import InvalidRequirement
1484        from packaging.requirements import Requirement
1485        from packaging.version import Version
1486
1487        plugin_info = self.pluginmanager.list_plugin_distinfo()
1488        plugin_dist_info = {dist.project_name: dist.version for _, dist in plugin_info}
1489
1490        missing_plugins = []
1491        for required_plugin in required_plugins:
1492            try:
1493                req = Requirement(required_plugin)
1494            except InvalidRequirement:
1495                missing_plugins.append(required_plugin)
1496                continue
1497
1498            if req.name not in plugin_dist_info:
1499                missing_plugins.append(required_plugin)
1500            elif not req.specifier.contains(
1501                Version(plugin_dist_info[req.name]), prereleases=True
1502            ):
1503                missing_plugins.append(required_plugin)
1504
1505        if missing_plugins:
1506            raise UsageError(
1507                "Missing required plugins: {}".format(", ".join(missing_plugins)),
1508            )
1509
1510    def _warn_or_fail_if_strict(self, message: str) -> None:
1511        if self.known_args_namespace.strict_config:
1512            raise UsageError(message)
1513
1514        self.issue_config_time_warning(PytestConfigWarning(message), stacklevel=3)
1515
1516    def _get_unknown_ini_keys(self) -> list[str]:
1517        parser_inicfg = self._parser._inidict
1518        return [name for name in self.inicfg if name not in parser_inicfg]
1519
1520    def parse(self, args: list[str], addopts: bool = True) -> None:
1521        # Parse given cmdline arguments into this config object.
1522        assert self.args == [], (
1523            "can only parse cmdline args at most once per Config object"
1524        )
1525        self.hook.pytest_addhooks.call_historic(
1526            kwargs=dict(pluginmanager=self.pluginmanager)
1527        )
1528        self._preparse(args, addopts=addopts)
1529        self._parser.after_preparse = True  # type: ignore
1530        try:
1531            args = self._parser.parse_setoption(
1532                args, self.option, namespace=self.option
1533            )
1534            self.args, self.args_source = self._decide_args(
1535                args=args,
1536                pyargs=self.known_args_namespace.pyargs,
1537                testpaths=self.getini("testpaths"),
1538                invocation_dir=self.invocation_params.dir,
1539                rootpath=self.rootpath,
1540                warn=True,
1541            )
1542        except PrintHelp:
1543            pass
1544
1545    def issue_config_time_warning(self, warning: Warning, stacklevel: int) -> None:
1546        """Issue and handle a warning during the "configure" stage.
1547
1548        During ``pytest_configure`` we can't capture warnings using the ``catch_warnings_for_item``
1549        function because it is not possible to have hook wrappers around ``pytest_configure``.
1550
1551        This function is mainly intended for plugins that need to issue warnings during
1552        ``pytest_configure`` (or similar stages).
1553
1554        :param warning: The warning instance.
1555        :param stacklevel: stacklevel forwarded to warnings.warn.
1556        """
1557        if self.pluginmanager.is_blocked("warnings"):
1558            return
1559
1560        cmdline_filters = self.known_args_namespace.pythonwarnings or []
1561        config_filters = self.getini("filterwarnings")
1562
1563        with warnings.catch_warnings(record=True) as records:
1564            warnings.simplefilter("always", type(warning))
1565            apply_warning_filters(config_filters, cmdline_filters)
1566            warnings.warn(warning, stacklevel=stacklevel)
1567
1568        if records:
1569            frame = sys._getframe(stacklevel - 1)
1570            location = frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name
1571            self.hook.pytest_warning_recorded.call_historic(
1572                kwargs=dict(
1573                    warning_message=records[0],
1574                    when="config",
1575                    nodeid="",
1576                    location=location,
1577                )
1578            )
1579
1580    def addinivalue_line(self, name: str, line: str) -> None:
1581        """Add a line to an ini-file option. The option must have been
1582        declared but might not yet be set in which case the line becomes
1583        the first line in its value."""
1584        x = self.getini(name)
1585        assert isinstance(x, list)
1586        x.append(line)  # modifies the cached list inline
1587
1588    def getini(self, name: str) -> Any:
1589        """Return configuration value from an :ref:`ini file <configfiles>`.
1590
1591        If a configuration value is not defined in an
1592        :ref:`ini file <configfiles>`, then the ``default`` value provided while
1593        registering the configuration through
1594        :func:`parser.addini <pytest.Parser.addini>` will be returned.
1595        Please note that you can even provide ``None`` as a valid
1596        default value.
1597
1598        If ``default`` is not provided while registering using
1599        :func:`parser.addini <pytest.Parser.addini>`, then a default value
1600        based on the ``type`` parameter passed to
1601        :func:`parser.addini <pytest.Parser.addini>` will be returned.
1602        The default values based on ``type`` are:
1603        ``paths``, ``pathlist``, ``args`` and ``linelist`` : empty list ``[]``
1604        ``bool`` : ``False``
1605        ``string`` : empty string ``""``
1606        ``int`` : ``0``
1607        ``float`` : ``0.0``
1608
1609        If neither the ``default`` nor the ``type`` parameter is passed
1610        while registering the configuration through
1611        :func:`parser.addini <pytest.Parser.addini>`, then the configuration
1612        is treated as a string and a default empty string '' is returned.
1613
1614        If the specified name hasn't been registered through a prior
1615        :func:`parser.addini <pytest.Parser.addini>` call (usually from a
1616        plugin), a ValueError is raised.
1617        """
1618        try:
1619            return self._inicache[name]
1620        except KeyError:
1621            self._inicache[name] = val = self._getini(name)
1622            return val
1623
1624    # Meant for easy monkeypatching by legacypath plugin.
1625    # Can be inlined back (with no cover removed) once legacypath is gone.
1626    def _getini_unknown_type(self, name: str, type: str, value: object):
1627        msg = (
1628            f"Option {name} has unknown configuration type {type} with value {value!r}"
1629        )
1630        raise ValueError(msg)  # pragma: no cover
1631
1632    def _getini(self, name: str):
1633        try:
1634            description, type, default = self._parser._inidict[name]
1635        except KeyError as e:
1636            raise ValueError(f"unknown configuration value: {name!r}") from e
1637        override_value = self._get_override_ini_value(name)
1638        if override_value is None:
1639            try:
1640                value = self.inicfg[name]
1641            except KeyError:
1642                return default
1643        else:
1644            value = override_value
1645        # Coerce the values based on types.
1646        #
1647        # Note: some coercions are only required if we are reading from .ini files, because
1648        # the file format doesn't contain type information, but when reading from toml we will
1649        # get either str or list of str values (see _parse_ini_config_from_pyproject_toml).
1650        # For example:
1651        #
1652        #   ini:
1653        #     a_line_list = "tests acceptance"
1654        #   in this case, we need to split the string to obtain a list of strings.
1655        #
1656        #   toml:
1657        #     a_line_list = ["tests", "acceptance"]
1658        #   in this case, we already have a list ready to use.
1659        #
1660        if type == "paths":
1661            dp = (
1662                self.inipath.parent
1663                if self.inipath is not None
1664                else self.invocation_params.dir
1665            )
1666            input_values = shlex.split(value) if isinstance(value, str) else value
1667            return [dp / x for x in input_values]
1668        elif type == "args":
1669            return shlex.split(value) if isinstance(value, str) else value
1670        elif type == "linelist":
1671            if isinstance(value, str):
1672                return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
1673            else:
1674                return value
1675        elif type == "bool":
1676            return _strtobool(str(value).strip())
1677        elif type == "string":
1678            return value
1679        elif type == "int":
1680            if not isinstance(value, str):
1681                raise TypeError(
1682                    f"Expected an int string for option {name} of type integer, but got: {value!r}"
1683                ) from None
1684            return int(value)
1685        elif type == "float":
1686            if not isinstance(value, str):
1687                raise TypeError(
1688                    f"Expected a float string for option {name} of type float, but got: {value!r}"
1689                ) from None
1690            return float(value)
1691        elif type is None:
1692            return value
1693        else:
1694            return self._getini_unknown_type(name, type, value)
1695
1696    def _getconftest_pathlist(
1697        self, name: str, path: pathlib.Path
1698    ) -> list[pathlib.Path] | None:
1699        try:
1700            mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
1701        except KeyError:
1702            return None
1703        assert mod.__file__ is not None
1704        modpath = pathlib.Path(mod.__file__).parent
1705        values: list[pathlib.Path] = []
1706        for relroot in relroots:
1707            if isinstance(relroot, os.PathLike):
1708                relroot = pathlib.Path(relroot)
1709            else:
1710                relroot = relroot.replace("/", os.sep)
1711                relroot = absolutepath(modpath / relroot)
1712            values.append(relroot)
1713        return values
1714
1715    def _get_override_ini_value(self, name: str) -> str | None:
1716        value = None
1717        # override_ini is a list of "ini=value" options.
1718        # Always use the last item if multiple values are set for same ini-name,
1719        # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2.
1720        for ini_config in self._override_ini:
1721            try:
1722                key, user_ini_value = ini_config.split("=", 1)
1723            except ValueError as e:
1724                raise UsageError(
1725                    f"-o/--override-ini expects option=value style (got: {ini_config!r})."
1726                ) from e
1727            else:
1728                if key == name:
1729                    value = user_ini_value
1730        return value
1731
1732    def getoption(self, name: str, default: Any = notset, skip: bool = False):
1733        """Return command line option value.
1734
1735        :param name: Name of the option. You may also specify
1736            the literal ``--OPT`` option instead of the "dest" option name.
1737        :param default: Fallback value if no option of that name is **declared** via :hook:`pytest_addoption`.
1738            Note this parameter will be ignored when the option is **declared** even if the option's value is ``None``.
1739        :param skip: If ``True``, raise :func:`pytest.skip` if option is undeclared or has a ``None`` value.
1740            Note that even if ``True``, if a default was specified it will be returned instead of a skip.
1741        """
1742        name = self._opt2dest.get(name, name)
1743        try:
1744            val = getattr(self.option, name)
1745            if val is None and skip:
1746                raise AttributeError(name)
1747            return val
1748        except AttributeError as e:
1749            if default is not notset:
1750                return default
1751            if skip:
1752                import pytest
1753
1754                pytest.skip(f"no {name!r} option found")
1755            raise ValueError(f"no option named {name!r}") from e
1756
1757    def getvalue(self, name: str, path=None):
1758        """Deprecated, use getoption() instead."""
1759        return self.getoption(name)
1760
1761    def getvalueorskip(self, name: str, path=None):
1762        """Deprecated, use getoption(skip=True) instead."""
1763        return self.getoption(name, skip=True)
1764
1765    #: Verbosity type for failed assertions (see :confval:`verbosity_assertions`).
1766    VERBOSITY_ASSERTIONS: Final = "assertions"
1767    #: Verbosity type for test case execution (see :confval:`verbosity_test_cases`).
1768    VERBOSITY_TEST_CASES: Final = "test_cases"
1769    _VERBOSITY_INI_DEFAULT: Final = "auto"
1770
1771    def get_verbosity(self, verbosity_type: str | None = None) -> int:
1772        r"""Retrieve the verbosity level for a fine-grained verbosity type.
1773
1774        :param verbosity_type: Verbosity type to get level for. If a level is
1775            configured for the given type, that value will be returned. If the
1776            given type is not a known verbosity type, the global verbosity
1777            level will be returned. If the given type is None (default), the
1778            global verbosity level will be returned.
1779
1780        To configure a level for a fine-grained verbosity type, the
1781        configuration file should have a setting for the configuration name
1782        and a numeric value for the verbosity level. A special value of "auto"
1783        can be used to explicitly use the global verbosity level.
1784
1785        Example:
1786
1787        .. code-block:: ini
1788
1789            # content of pytest.ini
1790            [pytest]
1791            verbosity_assertions = 2
1792
1793        .. code-block:: console
1794
1795            pytest -v
1796
1797        .. code-block:: python
1798
1799            print(config.get_verbosity())  # 1
1800            print(config.get_verbosity(Config.VERBOSITY_ASSERTIONS))  # 2
1801        """
1802        global_level = self.getoption("verbose", default=0)
1803        assert isinstance(global_level, int)
1804        if verbosity_type is None:
1805            return global_level
1806
1807        ini_name = Config._verbosity_ini_name(verbosity_type)
1808        if ini_name not in self._parser._inidict:
1809            return global_level
1810
1811        level = self.getini(ini_name)
1812        if level == Config._VERBOSITY_INI_DEFAULT:
1813            return global_level
1814
1815        return int(level)
1816
1817    @staticmethod
1818    def _verbosity_ini_name(verbosity_type: str) -> str:
1819        return f"verbosity_{verbosity_type}"
1820
1821    @staticmethod
1822    def _add_verbosity_ini(parser: Parser, verbosity_type: str, help: str) -> None:
1823        """Add a output verbosity configuration option for the given output type.
1824
1825        :param parser: Parser for command line arguments and ini-file values.
1826        :param verbosity_type: Fine-grained verbosity category.
1827        :param help: Description of the output this type controls.
1828
1829        The value should be retrieved via a call to
1830        :py:func:`config.get_verbosity(type) <pytest.Config.get_verbosity>`.
1831        """
1832        parser.addini(
1833            Config._verbosity_ini_name(verbosity_type),
1834            help=help,
1835            type="string",
1836            default=Config._VERBOSITY_INI_DEFAULT,
1837        )
1838
1839    def _warn_about_missing_assertion(self, mode: str) -> None:
1840        if not _assertion_supported():
1841            if mode == "plain":
1842                warning_text = (
1843                    "ASSERTIONS ARE NOT EXECUTED"
1844                    " and FAILING TESTS WILL PASS.  Are you"
1845                    " using python -O?"
1846                )
1847            else:
1848                warning_text = (
1849                    "assertions not in test modules or"
1850                    " plugins will be ignored"
1851                    " because assert statements are not executed "
1852                    "by the underlying Python interpreter "
1853                    "(are you using python -O?)\n"
1854                )
1855            self.issue_config_time_warning(
1856                PytestConfigWarning(warning_text),
1857                stacklevel=3,
1858            )
1859
1860    def _warn_about_skipped_plugins(self) -> None:
1861        for module_name, msg in self.pluginmanager.skipped_plugins:
1862            self.issue_config_time_warning(
1863                PytestConfigWarning(f"skipped plugin {module_name!r}: {msg}"),
1864                stacklevel=2,
1865            )

Access to configuration values, pluginmanager and plugin hooks.

Parameters
  • PytestPluginManager pluginmanager: A pytest PluginManager.

  • InvocationParams invocation_params: Object containing parameters regarding the pytest.main() invocation.

Config( pluginmanager: _pytest.config.PytestPluginManager, *, invocation_params: _pytest.config.Config.InvocationParams | None = None)
1032    def __init__(
1033        self,
1034        pluginmanager: PytestPluginManager,
1035        *,
1036        invocation_params: InvocationParams | None = None,
1037    ) -> None:
1038        from .argparsing import FILE_OR_DIR
1039        from .argparsing import Parser
1040
1041        if invocation_params is None:
1042            invocation_params = self.InvocationParams(
1043                args=(), plugins=None, dir=pathlib.Path.cwd()
1044            )
1045
1046        self.option = argparse.Namespace()
1047        """Access to command line option as attributes.
1048
1049        :type: argparse.Namespace
1050        """
1051
1052        self.invocation_params = invocation_params
1053        """The parameters with which pytest was invoked.
1054
1055        :type: InvocationParams
1056        """
1057
1058        _a = FILE_OR_DIR
1059        self._parser = Parser(
1060            usage=f"%(prog)s [options] [{_a}] [{_a}] [...]",
1061            processopt=self._processopt,
1062            _ispytest=True,
1063        )
1064        self.pluginmanager = pluginmanager
1065        """The plugin manager handles plugin registration and hook invocation.
1066
1067        :type: PytestPluginManager
1068        """
1069
1070        self.stash = Stash()
1071        """A place where plugins can store information on the config for their
1072        own use.
1073
1074        :type: Stash
1075        """
1076        # Deprecated alias. Was never public. Can be removed in a few releases.
1077        self._store = self.stash
1078
1079        self.trace = self.pluginmanager.trace.root.get("config")
1080        self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook)  # type: ignore[assignment]
1081        self._inicache: dict[str, Any] = {}
1082        self._override_ini: Sequence[str] = ()
1083        self._opt2dest: dict[str, str] = {}
1084        self._cleanup_stack = contextlib.ExitStack()
1085        self.pluginmanager.register(self, "pytestconfig")
1086        self._configured = False
1087        self.hook.pytest_addoption.call_historic(
1088            kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager)
1089        )
1090        self.args_source = Config.ArgsSource.ARGS
1091        self.args: list[str] = []
cache: _pytest.cacheprovider.Cache
option

Access to command line option as attributes.

invocation_params

The parameters with which pytest was invoked.

pluginmanager

The plugin manager handles plugin registration and hook invocation.

stash

A place where plugins can store information on the config for their own use.

trace
hook: pluggy._hooks.HookRelay
args_source
args: list[str]
rootpath: pathlib.Path
1093    @property
1094    def rootpath(self) -> pathlib.Path:
1095        """The path to the :ref:`rootdir <rootdir>`.
1096
1097        :type: pathlib.Path
1098
1099        .. versionadded:: 6.1
1100        """
1101        return self._rootpath

The path to the :ref:rootdir <rootdir>.

New in version 6.1.

inipath: pathlib.Path | None
1103    @property
1104    def inipath(self) -> pathlib.Path | None:
1105        """The path to the :ref:`configfile <configfiles>`.
1106
1107        .. versionadded:: 6.1
1108        """
1109        return self._inipath

The path to the :ref:configfile <configfiles>.

New in version 6.1.

def add_cleanup(self, func: Callable[[], None]) -> None:
1111    def add_cleanup(self, func: Callable[[], None]) -> None:
1112        """Add a function to be called when the config object gets out of
1113        use (usually coinciding with pytest_unconfigure).
1114        """
1115        self._cleanup_stack.callback(func)

Add a function to be called when the config object gets out of use (usually coinciding with pytest_unconfigure).

def get_terminal_writer(self) -> _pytest._io.terminalwriter.TerminalWriter:
1136    def get_terminal_writer(self) -> TerminalWriter:
1137        terminalreporter: TerminalReporter | None = self.pluginmanager.get_plugin(
1138            "terminalreporter"
1139        )
1140        assert terminalreporter is not None
1141        return terminalreporter._tw
def pytest_cmdline_parse( self, pluginmanager: _pytest.config.PytestPluginManager, args: list[str]) -> _pytest.config.Config:
1143    def pytest_cmdline_parse(
1144        self, pluginmanager: PytestPluginManager, args: list[str]
1145    ) -> Config:
1146        try:
1147            self.parse(args)
1148        except UsageError:
1149            # Handle --version and --help here in a minimal fashion.
1150            # This gets done via helpconfig normally, but its
1151            # pytest_cmdline_main is not called in case of errors.
1152            if getattr(self.option, "version", False) or "--version" in args:
1153                from _pytest.helpconfig import showversion
1154
1155                showversion(self)
1156            elif (
1157                getattr(self.option, "help", False) or "--help" in args or "-h" in args
1158            ):
1159                self._parser._getparser().print_help()
1160                sys.stdout.write(
1161                    "\nNOTE: displaying only minimal help due to UsageError.\n\n"
1162                )
1163
1164            raise
1165
1166        return self
def notify_exception( self, excinfo: _pytest._code.code.ExceptionInfo[BaseException], option: argparse.Namespace | None = None) -> None:
1168    def notify_exception(
1169        self,
1170        excinfo: ExceptionInfo[BaseException],
1171        option: argparse.Namespace | None = None,
1172    ) -> None:
1173        if option and getattr(option, "fulltrace", False):
1174            style: TracebackStyle = "long"
1175        else:
1176            style = "native"
1177        excrepr = excinfo.getrepr(
1178            funcargs=True, showlocals=getattr(option, "showlocals", False), style=style
1179        )
1180        res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo)
1181        if not any(res):
1182            for line in str(excrepr).split("\n"):
1183                sys.stderr.write(f"INTERNALERROR> {line}\n")
1184                sys.stderr.flush()
def cwd_relative_nodeid(self, nodeid: str) -> str:
1186    def cwd_relative_nodeid(self, nodeid: str) -> str:
1187        # nodeid's are relative to the rootpath, compute relative to cwd.
1188        if self.invocation_params.dir != self.rootpath:
1189            base_path_part, *nodeid_part = nodeid.split("::")
1190            # Only process path part
1191            fullpath = self.rootpath / base_path_part
1192            relative_path = bestrelpath(self.invocation_params.dir, fullpath)
1193
1194            nodeid = "::".join([relative_path, *nodeid_part])
1195        return nodeid
@classmethod
def fromdictargs(cls, option_dict, args) -> _pytest.config.Config:
1197    @classmethod
1198    def fromdictargs(cls, option_dict, args) -> Config:
1199        """Constructor usable for subprocesses."""
1200        config = get_config(args)
1201        config.option.__dict__.update(option_dict)
1202        config.parse(args, addopts=False)
1203        for x in config.option.plugins:
1204            config.pluginmanager.consider_pluginarg(x)
1205        return config

Constructor usable for subprocesses.

@hookimpl(trylast=True)
def pytest_load_initial_conftests(self, early_config: _pytest.config.Config) -> None:
1215    @hookimpl(trylast=True)
1216    def pytest_load_initial_conftests(self, early_config: Config) -> None:
1217        # We haven't fully parsed the command line arguments yet, so
1218        # early_config.args it not set yet. But we need it for
1219        # discovering the initial conftests. So "pre-run" the logic here.
1220        # It will be done for real in `parse()`.
1221        args, args_source = early_config._decide_args(
1222            args=early_config.known_args_namespace.file_or_dir,
1223            pyargs=early_config.known_args_namespace.pyargs,
1224            testpaths=early_config.getini("testpaths"),
1225            invocation_dir=early_config.invocation_params.dir,
1226            rootpath=early_config.rootpath,
1227            warn=False,
1228        )
1229        self.pluginmanager._set_initial_conftests(
1230            args=args,
1231            pyargs=early_config.known_args_namespace.pyargs,
1232            noconftest=early_config.known_args_namespace.noconftest,
1233            rootpath=early_config.rootpath,
1234            confcutdir=early_config.known_args_namespace.confcutdir,
1235            invocation_dir=early_config.invocation_params.dir,
1236            importmode=early_config.known_args_namespace.importmode,
1237            consider_namespace_packages=early_config.getini(
1238                "consider_namespace_packages"
1239            ),
1240        )
@hookimpl(wrapper=True)
def pytest_collection(self) -> Generator[None, object, object]:
1446    @hookimpl(wrapper=True)
1447    def pytest_collection(self) -> Generator[None, object, object]:
1448        # Validate invalid ini keys after collection is done so we take in account
1449        # options added by late-loading conftest files.
1450        try:
1451            return (yield)
1452        finally:
1453            self._validate_config_options()
def parse(self, args: list[str], addopts: bool = True) -> None:
1520    def parse(self, args: list[str], addopts: bool = True) -> None:
1521        # Parse given cmdline arguments into this config object.
1522        assert self.args == [], (
1523            "can only parse cmdline args at most once per Config object"
1524        )
1525        self.hook.pytest_addhooks.call_historic(
1526            kwargs=dict(pluginmanager=self.pluginmanager)
1527        )
1528        self._preparse(args, addopts=addopts)
1529        self._parser.after_preparse = True  # type: ignore
1530        try:
1531            args = self._parser.parse_setoption(
1532                args, self.option, namespace=self.option
1533            )
1534            self.args, self.args_source = self._decide_args(
1535                args=args,
1536                pyargs=self.known_args_namespace.pyargs,
1537                testpaths=self.getini("testpaths"),
1538                invocation_dir=self.invocation_params.dir,
1539                rootpath=self.rootpath,
1540                warn=True,
1541            )
1542        except PrintHelp:
1543            pass
def issue_config_time_warning(self, warning: Warning, stacklevel: int) -> None:
1545    def issue_config_time_warning(self, warning: Warning, stacklevel: int) -> None:
1546        """Issue and handle a warning during the "configure" stage.
1547
1548        During ``pytest_configure`` we can't capture warnings using the ``catch_warnings_for_item``
1549        function because it is not possible to have hook wrappers around ``pytest_configure``.
1550
1551        This function is mainly intended for plugins that need to issue warnings during
1552        ``pytest_configure`` (or similar stages).
1553
1554        :param warning: The warning instance.
1555        :param stacklevel: stacklevel forwarded to warnings.warn.
1556        """
1557        if self.pluginmanager.is_blocked("warnings"):
1558            return
1559
1560        cmdline_filters = self.known_args_namespace.pythonwarnings or []
1561        config_filters = self.getini("filterwarnings")
1562
1563        with warnings.catch_warnings(record=True) as records:
1564            warnings.simplefilter("always", type(warning))
1565            apply_warning_filters(config_filters, cmdline_filters)
1566            warnings.warn(warning, stacklevel=stacklevel)
1567
1568        if records:
1569            frame = sys._getframe(stacklevel - 1)
1570            location = frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name
1571            self.hook.pytest_warning_recorded.call_historic(
1572                kwargs=dict(
1573                    warning_message=records[0],
1574                    when="config",
1575                    nodeid="",
1576                    location=location,
1577                )
1578            )

Issue and handle a warning during the "configure" stage.

During pytest_configure we can't capture warnings using the catch_warnings_for_item function because it is not possible to have hook wrappers around pytest_configure.

This function is mainly intended for plugins that need to issue warnings during pytest_configure (or similar stages).

Parameters
  • warning: The warning instance.
  • stacklevel: stacklevel forwarded to warnings.warn.
def addinivalue_line(self, name: str, line: str) -> None:
1580    def addinivalue_line(self, name: str, line: str) -> None:
1581        """Add a line to an ini-file option. The option must have been
1582        declared but might not yet be set in which case the line becomes
1583        the first line in its value."""
1584        x = self.getini(name)
1585        assert isinstance(x, list)
1586        x.append(line)  # modifies the cached list inline

Add a line to an ini-file option. The option must have been declared but might not yet be set in which case the line becomes the first line in its value.

def getini(self, name: str) -> Any:
1588    def getini(self, name: str) -> Any:
1589        """Return configuration value from an :ref:`ini file <configfiles>`.
1590
1591        If a configuration value is not defined in an
1592        :ref:`ini file <configfiles>`, then the ``default`` value provided while
1593        registering the configuration through
1594        :func:`parser.addini <pytest.Parser.addini>` will be returned.
1595        Please note that you can even provide ``None`` as a valid
1596        default value.
1597
1598        If ``default`` is not provided while registering using
1599        :func:`parser.addini <pytest.Parser.addini>`, then a default value
1600        based on the ``type`` parameter passed to
1601        :func:`parser.addini <pytest.Parser.addini>` will be returned.
1602        The default values based on ``type`` are:
1603        ``paths``, ``pathlist``, ``args`` and ``linelist`` : empty list ``[]``
1604        ``bool`` : ``False``
1605        ``string`` : empty string ``""``
1606        ``int`` : ``0``
1607        ``float`` : ``0.0``
1608
1609        If neither the ``default`` nor the ``type`` parameter is passed
1610        while registering the configuration through
1611        :func:`parser.addini <pytest.Parser.addini>`, then the configuration
1612        is treated as a string and a default empty string '' is returned.
1613
1614        If the specified name hasn't been registered through a prior
1615        :func:`parser.addini <pytest.Parser.addini>` call (usually from a
1616        plugin), a ValueError is raised.
1617        """
1618        try:
1619            return self._inicache[name]
1620        except KeyError:
1621            self._inicache[name] = val = self._getini(name)
1622            return val

Return configuration value from an :ref:ini file <configfiles>.

If a configuration value is not defined in an :ref:ini file <configfiles>, then the default value provided while registering the configuration through parser.addini <pytest.Parser.addini>() will be returned. Please note that you can even provide None as a valid default value.

If default is not provided while registering using parser.addini <pytest.Parser.addini>(), then a default value based on the type parameter passed to parser.addini <pytest.Parser.addini>() will be returned. The default values based on type are: paths, pathlist, args and linelist : empty list [] bool : False string : empty string "" int : 0 float : 0.0

If neither the default nor the type parameter is passed while registering the configuration through parser.addini <pytest.Parser.addini>(), then the configuration is treated as a string and a default empty string '' is returned.

If the specified name hasn't been registered through a prior parser.addini <pytest.Parser.addini>() call (usually from a plugin), a ValueError is raised.

def getoption(self, name: str, default: Any = <NOTSET>, skip: bool = False):
1732    def getoption(self, name: str, default: Any = notset, skip: bool = False):
1733        """Return command line option value.
1734
1735        :param name: Name of the option. You may also specify
1736            the literal ``--OPT`` option instead of the "dest" option name.
1737        :param default: Fallback value if no option of that name is **declared** via :hook:`pytest_addoption`.
1738            Note this parameter will be ignored when the option is **declared** even if the option's value is ``None``.
1739        :param skip: If ``True``, raise :func:`pytest.skip` if option is undeclared or has a ``None`` value.
1740            Note that even if ``True``, if a default was specified it will be returned instead of a skip.
1741        """
1742        name = self._opt2dest.get(name, name)
1743        try:
1744            val = getattr(self.option, name)
1745            if val is None and skip:
1746                raise AttributeError(name)
1747            return val
1748        except AttributeError as e:
1749            if default is not notset:
1750                return default
1751            if skip:
1752                import pytest
1753
1754                pytest.skip(f"no {name!r} option found")
1755            raise ValueError(f"no option named {name!r}") from e

Return command line option value.

Parameters
  • name: Name of the option. You may also specify the literal --OPT option instead of the "dest" option name.
  • default: Fallback value if no option of that name is *declared via :hook: pytest_addoption. Note this parameter will be ignored when the option is *declared even if the option's value is None.
  • skip: If True, raise pytest.skip() if option is undeclared or has a None value. Note that even if True, if a default was specified it will be returned instead of a skip.
def getvalue(self, name: str, path=None):
1757    def getvalue(self, name: str, path=None):
1758        """Deprecated, use getoption() instead."""
1759        return self.getoption(name)

Deprecated, use getoption() instead.

def getvalueorskip(self, name: str, path=None):
1761    def getvalueorskip(self, name: str, path=None):
1762        """Deprecated, use getoption(skip=True) instead."""
1763        return self.getoption(name, skip=True)

Deprecated, use getoption(skip=True) instead.

VERBOSITY_ASSERTIONS: Final = 'assertions'
VERBOSITY_TEST_CASES: Final = 'test_cases'
def get_verbosity(self, verbosity_type: str | None = None) -> int:
1771    def get_verbosity(self, verbosity_type: str | None = None) -> int:
1772        r"""Retrieve the verbosity level for a fine-grained verbosity type.
1773
1774        :param verbosity_type: Verbosity type to get level for. If a level is
1775            configured for the given type, that value will be returned. If the
1776            given type is not a known verbosity type, the global verbosity
1777            level will be returned. If the given type is None (default), the
1778            global verbosity level will be returned.
1779
1780        To configure a level for a fine-grained verbosity type, the
1781        configuration file should have a setting for the configuration name
1782        and a numeric value for the verbosity level. A special value of "auto"
1783        can be used to explicitly use the global verbosity level.
1784
1785        Example:
1786
1787        .. code-block:: ini
1788
1789            # content of pytest.ini
1790            [pytest]
1791            verbosity_assertions = 2
1792
1793        .. code-block:: console
1794
1795            pytest -v
1796
1797        .. code-block:: python
1798
1799            print(config.get_verbosity())  # 1
1800            print(config.get_verbosity(Config.VERBOSITY_ASSERTIONS))  # 2
1801        """
1802        global_level = self.getoption("verbose", default=0)
1803        assert isinstance(global_level, int)
1804        if verbosity_type is None:
1805            return global_level
1806
1807        ini_name = Config._verbosity_ini_name(verbosity_type)
1808        if ini_name not in self._parser._inidict:
1809            return global_level
1810
1811        level = self.getini(ini_name)
1812        if level == Config._VERBOSITY_INI_DEFAULT:
1813            return global_level
1814
1815        return int(level)

Retrieve the verbosity level for a fine-grained verbosity type.

Parameters
  • verbosity_type: Verbosity type to get level for. If a level is configured for the given type, that value will be returned. If the given type is not a known verbosity type, the global verbosity level will be returned. If the given type is None (default), the global verbosity level will be returned.

To configure a level for a fine-grained verbosity type, the configuration file should have a setting for the configuration name and a numeric value for the verbosity level. A special value of "auto" can be used to explicitly use the global verbosity level.

Example:

# content of pytest.ini
[pytest]
verbosity_assertions = 2
pytest -v
print(config.get_verbosity())  # 1
print(config.get_verbosity(Config.VERBOSITY_ASSERTIONS))  # 2
@final
@dataclasses.dataclass(frozen=True)
class Config.InvocationParams:
 980    @final
 981    @dataclasses.dataclass(frozen=True)
 982    class InvocationParams:
 983        """Holds parameters passed during :func:`pytest.main`.
 984
 985        The object attributes are read-only.
 986
 987        .. versionadded:: 5.1
 988
 989        .. note::
 990
 991            Note that the environment variable ``PYTEST_ADDOPTS`` and the ``addopts``
 992            ini option are handled by pytest, not being included in the ``args`` attribute.
 993
 994            Plugins accessing ``InvocationParams`` must be aware of that.
 995        """
 996
 997        args: tuple[str, ...]
 998        """The command-line arguments as passed to :func:`pytest.main`."""
 999        plugins: Sequence[str | _PluggyPlugin] | None
1000        """Extra plugins, might be `None`."""
1001        dir: pathlib.Path
1002        """The directory from which :func:`pytest.main` was invoked. :type: pathlib.Path"""
1003
1004        def __init__(
1005            self,
1006            *,
1007            args: Iterable[str],
1008            plugins: Sequence[str | _PluggyPlugin] | None,
1009            dir: pathlib.Path,
1010        ) -> None:
1011            object.__setattr__(self, "args", tuple(args))
1012            object.__setattr__(self, "plugins", plugins)
1013            object.__setattr__(self, "dir", dir)

Holds parameters passed during pytest.main().

The object attributes are read-only.

New in version 5.1.

Note that the environment variable PYTEST_ADDOPTS and the addopts ini option are handled by pytest, not being included in the args attribute.

Plugins accessing InvocationParams must be aware of that.

Config.InvocationParams( *, args: Iterable[str], plugins: Sequence[str | object] | None, dir: pathlib.Path)
1004        def __init__(
1005            self,
1006            *,
1007            args: Iterable[str],
1008            plugins: Sequence[str | _PluggyPlugin] | None,
1009            dir: pathlib.Path,
1010        ) -> None:
1011            object.__setattr__(self, "args", tuple(args))
1012            object.__setattr__(self, "plugins", plugins)
1013            object.__setattr__(self, "dir", dir)
args: tuple[str, ...]

The command-line arguments as passed to pytest.main().

plugins: Sequence[str | object] | None

Extra plugins, might be None.

The directory from which pytest.main() was invoked. :type: pathlib.Path

class Config.ArgsSource(enum.Enum):
1015    class ArgsSource(enum.Enum):
1016        """Indicates the source of the test arguments.
1017
1018        .. versionadded:: 7.2
1019        """
1020
1021        #: Command line arguments.
1022        ARGS = enum.auto()
1023        #: Invocation directory.
1024        INVOCATION_DIR = enum.auto()
1025        INCOVATION_DIR = INVOCATION_DIR  # backwards compatibility alias
1026        #: 'testpaths' configuration value.
1027        TESTPATHS = enum.auto()

Indicates the source of the test arguments.

New in version 7.2.

ARGS = <ArgsSource.ARGS: 1>
INVOCATION_DIR = <ArgsSource.INVOCATION_DIR: 2>
INCOVATION_DIR = <ArgsSource.INVOCATION_DIR: 2>
TESTPATHS = <ArgsSource.TESTPATHS: 3>
@final
class Dir(pytest.Directory):
496@final
497class Dir(nodes.Directory):
498    """Collector of files in a file system directory.
499
500    .. versionadded:: 8.0
501
502    .. note::
503
504        Python directories with an `__init__.py` file are instead collected by
505        :class:`~pytest.Package` by default. Both are :class:`~pytest.Directory`
506        collectors.
507    """
508
509    @classmethod
510    def from_parent(  # type: ignore[override]
511        cls,
512        parent: nodes.Collector,
513        *,
514        path: Path,
515    ) -> Self:
516        """The public constructor.
517
518        :param parent: The parent collector of this Dir.
519        :param path: The directory's path.
520        :type path: pathlib.Path
521        """
522        return super().from_parent(parent=parent, path=path)
523
524    def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
525        config = self.config
526        col: nodes.Collector | None
527        cols: Sequence[nodes.Collector]
528        ihook = self.ihook
529        for direntry in scandir(self.path):
530            if direntry.is_dir():
531                path = Path(direntry.path)
532                if not self.session.isinitpath(path, with_parents=True):
533                    if ihook.pytest_ignore_collect(collection_path=path, config=config):
534                        continue
535                col = ihook.pytest_collect_directory(path=path, parent=self)
536                if col is not None:
537                    yield col
538
539            elif direntry.is_file():
540                path = Path(direntry.path)
541                if not self.session.isinitpath(path):
542                    if ihook.pytest_ignore_collect(collection_path=path, config=config):
543                        continue
544                cols = ihook.pytest_collect_file(file_path=path, parent=self)
545                yield from cols

Collector of files in a file system directory.

New in version 8.0.

Python directories with an __init__.py file are instead collected by ~pytest.Package by default. Both are ~pytest.Directory collectors.

@classmethod
def from_parent( cls, parent: _pytest.nodes.Collector, *, path: pathlib.Path) -> Self:
509    @classmethod
510    def from_parent(  # type: ignore[override]
511        cls,
512        parent: nodes.Collector,
513        *,
514        path: Path,
515    ) -> Self:
516        """The public constructor.
517
518        :param parent: The parent collector of this Dir.
519        :param path: The directory's path.
520        :type path: pathlib.Path
521        """
522        return super().from_parent(parent=parent, path=path)

The public constructor.

Parameters
  • parent: The parent collector of this Dir.
  • path: The directory's path.
def collect(self) -> Iterable[_pytest.nodes.Item | _pytest.nodes.Collector]:
524    def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
525        config = self.config
526        col: nodes.Collector | None
527        cols: Sequence[nodes.Collector]
528        ihook = self.ihook
529        for direntry in scandir(self.path):
530            if direntry.is_dir():
531                path = Path(direntry.path)
532                if not self.session.isinitpath(path, with_parents=True):
533                    if ihook.pytest_ignore_collect(collection_path=path, config=config):
534                        continue
535                col = ihook.pytest_collect_directory(path=path, parent=self)
536                if col is not None:
537                    yield col
538
539            elif direntry.is_file():
540                path = Path(direntry.path)
541                if not self.session.isinitpath(path):
542                    if ihook.pytest_ignore_collect(collection_path=path, config=config):
543                        continue
544                cols = ihook.pytest_collect_file(file_path=path, parent=self)
545                yield from cols

Collect children (items and collectors) for this collector.

class Directory(_pytest.nodes.FSCollector, abc.ABC):
638class Directory(FSCollector, abc.ABC):
639    """Base class for collecting files from a directory.
640
641    A basic directory collector does the following: goes over the files and
642    sub-directories in the directory and creates collectors for them by calling
643    the hooks :hook:`pytest_collect_directory` and :hook:`pytest_collect_file`,
644    after checking that they are not ignored using
645    :hook:`pytest_ignore_collect`.
646
647    The default directory collectors are :class:`~pytest.Dir` and
648    :class:`~pytest.Package`.
649
650    .. versionadded:: 8.0
651
652    :ref:`custom directory collectors`.
653    """

Base class for collecting files from a directory.

A basic directory collector does the following: goes over the files and sub-directories in the directory and creates collectors for them by calling the hooks :hook:pytest_collect_directory and :hook:pytest_collect_file, after checking that they are not ignored using :hook:pytest_ignore_collect.

The default directory collectors are ~pytest.Dir and ~pytest.Package.

New in version 8.0.

:ref:custom directory collectors.

class DoctestItem(pytest.Item):
252class DoctestItem(Item):
253    def __init__(
254        self,
255        name: str,
256        parent: DoctestTextfile | DoctestModule,
257        runner: doctest.DocTestRunner,
258        dtest: doctest.DocTest,
259    ) -> None:
260        super().__init__(name, parent)
261        self.runner = runner
262        self.dtest = dtest
263
264        # Stuff needed for fixture support.
265        self.obj = None
266        fm = self.session._fixturemanager
267        fixtureinfo = fm.getfixtureinfo(node=self, func=None, cls=None)
268        self._fixtureinfo = fixtureinfo
269        self.fixturenames = fixtureinfo.names_closure
270        self._initrequest()
271
272    @classmethod
273    def from_parent(  # type: ignore[override]
274        cls,
275        parent: DoctestTextfile | DoctestModule,
276        *,
277        name: str,
278        runner: doctest.DocTestRunner,
279        dtest: doctest.DocTest,
280    ) -> Self:
281        # incompatible signature due to imposed limits on subclass
282        """The public named constructor."""
283        return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest)
284
285    def _initrequest(self) -> None:
286        self.funcargs: dict[str, object] = {}
287        self._request = TopRequest(self, _ispytest=True)  # type: ignore[arg-type]
288
289    def setup(self) -> None:
290        self._request._fillfixtures()
291        globs = dict(getfixture=self._request.getfixturevalue)
292        for name, value in self._request.getfixturevalue("doctest_namespace").items():
293            globs[name] = value
294        self.dtest.globs.update(globs)
295
296    def runtest(self) -> None:
297        _check_all_skipped(self.dtest)
298        self._disable_output_capturing_for_darwin()
299        failures: list[doctest.DocTestFailure] = []
300        # Type ignored because we change the type of `out` from what
301        # doctest expects.
302        self.runner.run(self.dtest, out=failures)  # type: ignore[arg-type]
303        if failures:
304            raise MultipleDoctestFailures(failures)
305
306    def _disable_output_capturing_for_darwin(self) -> None:
307        """Disable output capturing. Otherwise, stdout is lost to doctest (#985)."""
308        if platform.system() != "Darwin":
309            return
310        capman = self.config.pluginmanager.getplugin("capturemanager")
311        if capman:
312            capman.suspend_global_capture(in_=True)
313            out, err = capman.read_global_capture()
314            sys.stdout.write(out)
315            sys.stderr.write(err)
316
317    # TODO: Type ignored -- breaks Liskov Substitution.
318    def repr_failure(  # type: ignore[override]
319        self,
320        excinfo: ExceptionInfo[BaseException],
321    ) -> str | TerminalRepr:
322        import doctest
323
324        failures: (
325            Sequence[doctest.DocTestFailure | doctest.UnexpectedException] | None
326        ) = None
327        if isinstance(
328            excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException)
329        ):
330            failures = [excinfo.value]
331        elif isinstance(excinfo.value, MultipleDoctestFailures):
332            failures = excinfo.value.failures
333
334        if failures is None:
335            return super().repr_failure(excinfo)
336
337        reprlocation_lines = []
338        for failure in failures:
339            example = failure.example
340            test = failure.test
341            filename = test.filename
342            if test.lineno is None:
343                lineno = None
344            else:
345                lineno = test.lineno + example.lineno + 1
346            message = type(failure).__name__
347            # TODO: ReprFileLocation doesn't expect a None lineno.
348            reprlocation = ReprFileLocation(filename, lineno, message)  # type: ignore[arg-type]
349            checker = _get_checker()
350            report_choice = _get_report_choice(self.config.getoption("doctestreport"))
351            if lineno is not None:
352                assert failure.test.docstring is not None
353                lines = failure.test.docstring.splitlines(False)
354                # add line numbers to the left of the error message
355                assert test.lineno is not None
356                lines = [
357                    f"{i + test.lineno + 1:03d} {x}" for (i, x) in enumerate(lines)
358                ]
359                # trim docstring error lines to 10
360                lines = lines[max(example.lineno - 9, 0) : example.lineno + 1]
361            else:
362                lines = [
363                    "EXAMPLE LOCATION UNKNOWN, not showing all tests of that example"
364                ]
365                indent = ">>>"
366                for line in example.source.splitlines():
367                    lines.append(f"??? {indent} {line}")
368                    indent = "..."
369            if isinstance(failure, doctest.DocTestFailure):
370                lines += checker.output_difference(
371                    example, failure.got, report_choice
372                ).split("\n")
373            else:
374                inner_excinfo = ExceptionInfo.from_exc_info(failure.exc_info)
375                lines += [f"UNEXPECTED EXCEPTION: {inner_excinfo.value!r}"]
376                lines += [
377                    x.strip("\n") for x in traceback.format_exception(*failure.exc_info)
378                ]
379            reprlocation_lines.append((reprlocation, lines))
380        return ReprFailDoctest(reprlocation_lines)
381
382    def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]:
383        return self.path, self.dtest.lineno, f"[doctest] {self.name}"

Base class of all test invocation items.

Note that for a single function there might be multiple test invocation items.

DoctestItem( name: str, parent: _pytest.doctest.DoctestTextfile | _pytest.doctest.DoctestModule, runner: doctest.DocTestRunner, dtest: doctest.DocTest)
253    def __init__(
254        self,
255        name: str,
256        parent: DoctestTextfile | DoctestModule,
257        runner: doctest.DocTestRunner,
258        dtest: doctest.DocTest,
259    ) -> None:
260        super().__init__(name, parent)
261        self.runner = runner
262        self.dtest = dtest
263
264        # Stuff needed for fixture support.
265        self.obj = None
266        fm = self.session._fixturemanager
267        fixtureinfo = fm.getfixtureinfo(node=self, func=None, cls=None)
268        self._fixtureinfo = fixtureinfo
269        self.fixturenames = fixtureinfo.names_closure
270        self._initrequest()
runner
dtest
obj
fixturenames
@classmethod
def from_parent( cls, parent: _pytest.doctest.DoctestTextfile | _pytest.doctest.DoctestModule, *, name: str, runner: doctest.DocTestRunner, dtest: doctest.DocTest) -> Self:
272    @classmethod
273    def from_parent(  # type: ignore[override]
274        cls,
275        parent: DoctestTextfile | DoctestModule,
276        *,
277        name: str,
278        runner: doctest.DocTestRunner,
279        dtest: doctest.DocTest,
280    ) -> Self:
281        # incompatible signature due to imposed limits on subclass
282        """The public named constructor."""
283        return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest)

The public named constructor.

def setup(self) -> None:
289    def setup(self) -> None:
290        self._request._fillfixtures()
291        globs = dict(getfixture=self._request.getfixturevalue)
292        for name, value in self._request.getfixturevalue("doctest_namespace").items():
293            globs[name] = value
294        self.dtest.globs.update(globs)
def runtest(self) -> None:
296    def runtest(self) -> None:
297        _check_all_skipped(self.dtest)
298        self._disable_output_capturing_for_darwin()
299        failures: list[doctest.DocTestFailure] = []
300        # Type ignored because we change the type of `out` from what
301        # doctest expects.
302        self.runner.run(self.dtest, out=failures)  # type: ignore[arg-type]
303        if failures:
304            raise MultipleDoctestFailures(failures)

Run the test case for this item.

Must be implemented by subclasses.

seealso :ref:non-python tests.

def repr_failure( self, excinfo: _pytest._code.code.ExceptionInfo[BaseException]) -> str | _pytest._code.code.TerminalRepr:
318    def repr_failure(  # type: ignore[override]
319        self,
320        excinfo: ExceptionInfo[BaseException],
321    ) -> str | TerminalRepr:
322        import doctest
323
324        failures: (
325            Sequence[doctest.DocTestFailure | doctest.UnexpectedException] | None
326        ) = None
327        if isinstance(
328            excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException)
329        ):
330            failures = [excinfo.value]
331        elif isinstance(excinfo.value, MultipleDoctestFailures):
332            failures = excinfo.value.failures
333
334        if failures is None:
335            return super().repr_failure(excinfo)
336
337        reprlocation_lines = []
338        for failure in failures:
339            example = failure.example
340            test = failure.test
341            filename = test.filename
342            if test.lineno is None:
343                lineno = None
344            else:
345                lineno = test.lineno + example.lineno + 1
346            message = type(failure).__name__
347            # TODO: ReprFileLocation doesn't expect a None lineno.
348            reprlocation = ReprFileLocation(filename, lineno, message)  # type: ignore[arg-type]
349            checker = _get_checker()
350            report_choice = _get_report_choice(self.config.getoption("doctestreport"))
351            if lineno is not None:
352                assert failure.test.docstring is not None
353                lines = failure.test.docstring.splitlines(False)
354                # add line numbers to the left of the error message
355                assert test.lineno is not None
356                lines = [
357                    f"{i + test.lineno + 1:03d} {x}" for (i, x) in enumerate(lines)
358                ]
359                # trim docstring error lines to 10
360                lines = lines[max(example.lineno - 9, 0) : example.lineno + 1]
361            else:
362                lines = [
363                    "EXAMPLE LOCATION UNKNOWN, not showing all tests of that example"
364                ]
365                indent = ">>>"
366                for line in example.source.splitlines():
367                    lines.append(f"??? {indent} {line}")
368                    indent = "..."
369            if isinstance(failure, doctest.DocTestFailure):
370                lines += checker.output_difference(
371                    example, failure.got, report_choice
372                ).split("\n")
373            else:
374                inner_excinfo = ExceptionInfo.from_exc_info(failure.exc_info)
375                lines += [f"UNEXPECTED EXCEPTION: {inner_excinfo.value!r}"]
376                lines += [
377                    x.strip("\n") for x in traceback.format_exception(*failure.exc_info)
378                ]
379            reprlocation_lines.append((reprlocation, lines))
380        return ReprFailDoctest(reprlocation_lines)

Return a representation of a collection or test failure.

seealso :ref:non-python tests.

Parameters
  • excinfo: Exception information for the failure.
def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]:
382    def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]:
383        return self.path, self.dtest.lineno, f"[doctest] {self.name}"

Get location information for this item for test reports.

Returns a tuple with three elements:

  • The path of the test (default self.path)
  • The 0-based line number of the test (default None)
  • A name of the test to be shown (default "")

seealso :ref:non-python tests.

@final
@dataclasses.dataclass
class ExceptionInfo(typing.Generic[+E]):
496@final
497@dataclasses.dataclass
498class ExceptionInfo(Generic[E]):
499    """Wraps sys.exc_info() objects and offers help for navigating the traceback."""
500
501    _assert_start_repr: ClassVar = "AssertionError('assert "
502
503    _excinfo: tuple[type[E], E, TracebackType] | None
504    _striptext: str
505    _traceback: Traceback | None
506
507    def __init__(
508        self,
509        excinfo: tuple[type[E], E, TracebackType] | None,
510        striptext: str = "",
511        traceback: Traceback | None = None,
512        *,
513        _ispytest: bool = False,
514    ) -> None:
515        check_ispytest(_ispytest)
516        self._excinfo = excinfo
517        self._striptext = striptext
518        self._traceback = traceback
519
520    @classmethod
521    def from_exception(
522        cls,
523        # Ignoring error: "Cannot use a covariant type variable as a parameter".
524        # This is OK to ignore because this class is (conceptually) readonly.
525        # See https://github.com/python/mypy/issues/7049.
526        exception: E,  # type: ignore[misc]
527        exprinfo: str | None = None,
528    ) -> ExceptionInfo[E]:
529        """Return an ExceptionInfo for an existing exception.
530
531        The exception must have a non-``None`` ``__traceback__`` attribute,
532        otherwise this function fails with an assertion error. This means that
533        the exception must have been raised, or added a traceback with the
534        :py:meth:`~BaseException.with_traceback()` method.
535
536        :param exprinfo:
537            A text string helping to determine if we should strip
538            ``AssertionError`` from the output. Defaults to the exception
539            message/``__str__()``.
540
541        .. versionadded:: 7.4
542        """
543        assert exception.__traceback__, (
544            "Exceptions passed to ExcInfo.from_exception(...)"
545            " must have a non-None __traceback__."
546        )
547        exc_info = (type(exception), exception, exception.__traceback__)
548        return cls.from_exc_info(exc_info, exprinfo)
549
550    @classmethod
551    def from_exc_info(
552        cls,
553        exc_info: tuple[type[E], E, TracebackType],
554        exprinfo: str | None = None,
555    ) -> ExceptionInfo[E]:
556        """Like :func:`from_exception`, but using old-style exc_info tuple."""
557        _striptext = ""
558        if exprinfo is None and isinstance(exc_info[1], AssertionError):
559            exprinfo = getattr(exc_info[1], "msg", None)
560            if exprinfo is None:
561                exprinfo = saferepr(exc_info[1])
562            if exprinfo and exprinfo.startswith(cls._assert_start_repr):
563                _striptext = "AssertionError: "
564
565        return cls(exc_info, _striptext, _ispytest=True)
566
567    @classmethod
568    def from_current(cls, exprinfo: str | None = None) -> ExceptionInfo[BaseException]:
569        """Return an ExceptionInfo matching the current traceback.
570
571        .. warning::
572
573            Experimental API
574
575        :param exprinfo:
576            A text string helping to determine if we should strip
577            ``AssertionError`` from the output. Defaults to the exception
578            message/``__str__()``.
579        """
580        tup = sys.exc_info()
581        assert tup[0] is not None, "no current exception"
582        assert tup[1] is not None, "no current exception"
583        assert tup[2] is not None, "no current exception"
584        exc_info = (tup[0], tup[1], tup[2])
585        return ExceptionInfo.from_exc_info(exc_info, exprinfo)
586
587    @classmethod
588    def for_later(cls) -> ExceptionInfo[E]:
589        """Return an unfilled ExceptionInfo."""
590        return cls(None, _ispytest=True)
591
592    def fill_unfilled(self, exc_info: tuple[type[E], E, TracebackType]) -> None:
593        """Fill an unfilled ExceptionInfo created with ``for_later()``."""
594        assert self._excinfo is None, "ExceptionInfo was already filled"
595        self._excinfo = exc_info
596
597    @property
598    def type(self) -> type[E]:
599        """The exception class."""
600        assert self._excinfo is not None, (
601            ".type can only be used after the context manager exits"
602        )
603        return self._excinfo[0]
604
605    @property
606    def value(self) -> E:
607        """The exception value."""
608        assert self._excinfo is not None, (
609            ".value can only be used after the context manager exits"
610        )
611        return self._excinfo[1]
612
613    @property
614    def tb(self) -> TracebackType:
615        """The exception raw traceback."""
616        assert self._excinfo is not None, (
617            ".tb can only be used after the context manager exits"
618        )
619        return self._excinfo[2]
620
621    @property
622    def typename(self) -> str:
623        """The type name of the exception."""
624        assert self._excinfo is not None, (
625            ".typename can only be used after the context manager exits"
626        )
627        return self.type.__name__
628
629    @property
630    def traceback(self) -> Traceback:
631        """The traceback."""
632        if self._traceback is None:
633            self._traceback = Traceback(self.tb)
634        return self._traceback
635
636    @traceback.setter
637    def traceback(self, value: Traceback) -> None:
638        self._traceback = value
639
640    def __repr__(self) -> str:
641        if self._excinfo is None:
642            return "<ExceptionInfo for raises contextmanager>"
643        return f"<{self.__class__.__name__} {saferepr(self._excinfo[1])} tblen={len(self.traceback)}>"
644
645    def exconly(self, tryshort: bool = False) -> str:
646        """Return the exception as a string.
647
648        When 'tryshort' resolves to True, and the exception is an
649        AssertionError, only the actual exception part of the exception
650        representation is returned (so 'AssertionError: ' is removed from
651        the beginning).
652        """
653
654        def _get_single_subexc(
655            eg: BaseExceptionGroup[BaseException],
656        ) -> BaseException | None:
657            if len(eg.exceptions) != 1:
658                return None
659            if isinstance(e := eg.exceptions[0], BaseExceptionGroup):
660                return _get_single_subexc(e)
661            return e
662
663        if (
664            tryshort
665            and isinstance(self.value, BaseExceptionGroup)
666            and (subexc := _get_single_subexc(self.value)) is not None
667        ):
668            return f"{subexc!r} [single exception in {type(self.value).__name__}]"
669
670        lines = format_exception_only(self.type, self.value)
671        text = "".join(lines)
672        text = text.rstrip()
673        if tryshort:
674            if text.startswith(self._striptext):
675                text = text[len(self._striptext) :]
676        return text
677
678    def errisinstance(self, exc: EXCEPTION_OR_MORE) -> bool:
679        """Return True if the exception is an instance of exc.
680
681        Consider using ``isinstance(excinfo.value, exc)`` instead.
682        """
683        return isinstance(self.value, exc)
684
685    def _getreprcrash(self) -> ReprFileLocation | None:
686        # Find last non-hidden traceback entry that led to the exception of the
687        # traceback, or None if all hidden.
688        for i in range(-1, -len(self.traceback) - 1, -1):
689            entry = self.traceback[i]
690            if not entry.ishidden(self):
691                path, lineno = entry.frame.code.raw.co_filename, entry.lineno
692                exconly = self.exconly(tryshort=True)
693                return ReprFileLocation(path, lineno + 1, exconly)
694        return None
695
696    def getrepr(
697        self,
698        showlocals: bool = False,
699        style: TracebackStyle = "long",
700        abspath: bool = False,
701        tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] = True,
702        funcargs: bool = False,
703        truncate_locals: bool = True,
704        truncate_args: bool = True,
705        chain: bool = True,
706    ) -> ReprExceptionInfo | ExceptionChainRepr:
707        """Return str()able representation of this exception info.
708
709        :param bool showlocals:
710            Show locals per traceback entry.
711            Ignored if ``style=="native"``.
712
713        :param str style:
714            long|short|line|no|native|value traceback style.
715
716        :param bool abspath:
717            If paths should be changed to absolute or left unchanged.
718
719        :param tbfilter:
720            A filter for traceback entries.
721
722            * If false, don't hide any entries.
723            * If true, hide internal entries and entries that contain a local
724              variable ``__tracebackhide__ = True``.
725            * If a callable, delegates the filtering to the callable.
726
727            Ignored if ``style`` is ``"native"``.
728
729        :param bool funcargs:
730            Show fixtures ("funcargs" for legacy purposes) per traceback entry.
731
732        :param bool truncate_locals:
733            With ``showlocals==True``, make sure locals can be safely represented as strings.
734
735        :param bool truncate_args:
736            With ``showargs==True``, make sure args can be safely represented as strings.
737
738        :param bool chain:
739            If chained exceptions in Python 3 should be shown.
740
741        .. versionchanged:: 3.9
742
743            Added the ``chain`` parameter.
744        """
745        if style == "native":
746            return ReprExceptionInfo(
747                reprtraceback=ReprTracebackNative(
748                    format_exception(
749                        self.type,
750                        self.value,
751                        self.traceback[0]._rawentry if self.traceback else None,
752                    )
753                ),
754                reprcrash=self._getreprcrash(),
755            )
756
757        fmt = FormattedExcinfo(
758            showlocals=showlocals,
759            style=style,
760            abspath=abspath,
761            tbfilter=tbfilter,
762            funcargs=funcargs,
763            truncate_locals=truncate_locals,
764            truncate_args=truncate_args,
765            chain=chain,
766        )
767        return fmt.repr_excinfo(self)
768
769    def match(self, regexp: str | re.Pattern[str]) -> Literal[True]:
770        """Check whether the regular expression `regexp` matches the string
771        representation of the exception using :func:`python:re.search`.
772
773        If it matches `True` is returned, otherwise an `AssertionError` is raised.
774        """
775        __tracebackhide__ = True
776        value = stringify_exception(self.value)
777        msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}"
778        if regexp == value:
779            msg += "\n Did you mean to `re.escape()` the regex?"
780        assert re.search(regexp, value), msg
781        # Return True to allow for "assert excinfo.match()".
782        return True
783
784    def _group_contains(
785        self,
786        exc_group: BaseExceptionGroup[BaseException],
787        expected_exception: EXCEPTION_OR_MORE,
788        match: str | re.Pattern[str] | None,
789        target_depth: int | None = None,
790        current_depth: int = 1,
791    ) -> bool:
792        """Return `True` if a `BaseExceptionGroup` contains a matching exception."""
793        if (target_depth is not None) and (current_depth > target_depth):
794            # already descended past the target depth
795            return False
796        for exc in exc_group.exceptions:
797            if isinstance(exc, BaseExceptionGroup):
798                if self._group_contains(
799                    exc, expected_exception, match, target_depth, current_depth + 1
800                ):
801                    return True
802            if (target_depth is not None) and (current_depth != target_depth):
803                # not at the target depth, no match
804                continue
805            if not isinstance(exc, expected_exception):
806                continue
807            if match is not None:
808                value = stringify_exception(exc)
809                if not re.search(match, value):
810                    continue
811            return True
812        return False
813
814    def group_contains(
815        self,
816        expected_exception: EXCEPTION_OR_MORE,
817        *,
818        match: str | re.Pattern[str] | None = None,
819        depth: int | None = None,
820    ) -> bool:
821        """Check whether a captured exception group contains a matching exception.
822
823        :param Type[BaseException] | Tuple[Type[BaseException]] expected_exception:
824            The expected exception type, or a tuple if one of multiple possible
825            exception types are expected.
826
827        :param str | re.Pattern[str] | None match:
828            If specified, a string containing a regular expression,
829            or a regular expression object, that is tested against the string
830            representation of the exception and its `PEP-678 <https://peps.python.org/pep-0678/>` `__notes__`
831            using :func:`re.search`.
832
833            To match a literal string that may contain :ref:`special characters
834            <re-syntax>`, the pattern can first be escaped with :func:`re.escape`.
835
836        :param Optional[int] depth:
837            If `None`, will search for a matching exception at any nesting depth.
838            If >= 1, will only match an exception if it's at the specified depth (depth = 1 being
839            the exceptions contained within the topmost exception group).
840
841        .. versionadded:: 8.0
842
843        .. warning::
844           This helper makes it easy to check for the presence of specific exceptions,
845           but it is very bad for checking that the group does *not* contain
846           *any other exceptions*.
847           You should instead consider using :class:`pytest.RaisesGroup`
848
849        """
850        msg = "Captured exception is not an instance of `BaseExceptionGroup`"
851        assert isinstance(self.value, BaseExceptionGroup), msg
852        msg = "`depth` must be >= 1 if specified"
853        assert (depth is None) or (depth >= 1), msg
854        return self._group_contains(self.value, expected_exception, match, depth)

Wraps sys.exc_info() objects and offers help for navigating the traceback.

ExceptionInfo( excinfo: 'tuple[type[E], E, TracebackType] | None', striptext: str = '', traceback: _pytest._code.code.Traceback | None = None, *, _ispytest: bool = False)
507    def __init__(
508        self,
509        excinfo: tuple[type[E], E, TracebackType] | None,
510        striptext: str = "",
511        traceback: Traceback | None = None,
512        *,
513        _ispytest: bool = False,
514    ) -> None:
515        check_ispytest(_ispytest)
516        self._excinfo = excinfo
517        self._striptext = striptext
518        self._traceback = traceback
@classmethod
def from_exception( cls, exception: +E, exprinfo: str | None = None) -> _pytest._code.code.ExceptionInfo[+E]:
520    @classmethod
521    def from_exception(
522        cls,
523        # Ignoring error: "Cannot use a covariant type variable as a parameter".
524        # This is OK to ignore because this class is (conceptually) readonly.
525        # See https://github.com/python/mypy/issues/7049.
526        exception: E,  # type: ignore[misc]
527        exprinfo: str | None = None,
528    ) -> ExceptionInfo[E]:
529        """Return an ExceptionInfo for an existing exception.
530
531        The exception must have a non-``None`` ``__traceback__`` attribute,
532        otherwise this function fails with an assertion error. This means that
533        the exception must have been raised, or added a traceback with the
534        :py:meth:`~BaseException.with_traceback()` method.
535
536        :param exprinfo:
537            A text string helping to determine if we should strip
538            ``AssertionError`` from the output. Defaults to the exception
539            message/``__str__()``.
540
541        .. versionadded:: 7.4
542        """
543        assert exception.__traceback__, (
544            "Exceptions passed to ExcInfo.from_exception(...)"
545            " must have a non-None __traceback__."
546        )
547        exc_info = (type(exception), exception, exception.__traceback__)
548        return cls.from_exc_info(exc_info, exprinfo)

Return an ExceptionInfo for an existing exception.

The exception must have a non-None __traceback__ attribute, otherwise this function fails with an assertion error. This means that the exception must have been raised, or added a traceback with the ~BaseException.with_traceback()() method.

Parameters
  • exprinfo: A text string helping to determine if we should strip AssertionError from the output. Defaults to the exception message/__str__().

New in version 7.4.

@classmethod
def from_exc_info( cls, exc_info: 'tuple[type[E], E, TracebackType]', exprinfo: str | None = None) -> _pytest._code.code.ExceptionInfo[+E]:
550    @classmethod
551    def from_exc_info(
552        cls,
553        exc_info: tuple[type[E], E, TracebackType],
554        exprinfo: str | None = None,
555    ) -> ExceptionInfo[E]:
556        """Like :func:`from_exception`, but using old-style exc_info tuple."""
557        _striptext = ""
558        if exprinfo is None and isinstance(exc_info[1], AssertionError):
559            exprinfo = getattr(exc_info[1], "msg", None)
560            if exprinfo is None:
561                exprinfo = saferepr(exc_info[1])
562            if exprinfo and exprinfo.startswith(cls._assert_start_repr):
563                _striptext = "AssertionError: "
564
565        return cls(exc_info, _striptext, _ispytest=True)

Like from_exception(), but using old-style exc_info tuple.

@classmethod
def from_current( cls, exprinfo: str | None = None) -> _pytest._code.code.ExceptionInfo[BaseException]:
567    @classmethod
568    def from_current(cls, exprinfo: str | None = None) -> ExceptionInfo[BaseException]:
569        """Return an ExceptionInfo matching the current traceback.
570
571        .. warning::
572
573            Experimental API
574
575        :param exprinfo:
576            A text string helping to determine if we should strip
577            ``AssertionError`` from the output. Defaults to the exception
578            message/``__str__()``.
579        """
580        tup = sys.exc_info()
581        assert tup[0] is not None, "no current exception"
582        assert tup[1] is not None, "no current exception"
583        assert tup[2] is not None, "no current exception"
584        exc_info = (tup[0], tup[1], tup[2])
585        return ExceptionInfo.from_exc_info(exc_info, exprinfo)

Return an ExceptionInfo matching the current traceback.

Experimental API

Parameters
  • exprinfo: A text string helping to determine if we should strip AssertionError from the output. Defaults to the exception message/__str__().
@classmethod
def for_later(cls) -> _pytest._code.code.ExceptionInfo[+E]:
587    @classmethod
588    def for_later(cls) -> ExceptionInfo[E]:
589        """Return an unfilled ExceptionInfo."""
590        return cls(None, _ispytest=True)

Return an unfilled ExceptionInfo.

def fill_unfilled(self, exc_info: 'tuple[type[E], E, TracebackType]') -> None:
592    def fill_unfilled(self, exc_info: tuple[type[E], E, TracebackType]) -> None:
593        """Fill an unfilled ExceptionInfo created with ``for_later()``."""
594        assert self._excinfo is None, "ExceptionInfo was already filled"
595        self._excinfo = exc_info

Fill an unfilled ExceptionInfo created with for_later().

type: 'type[E]'
597    @property
598    def type(self) -> type[E]:
599        """The exception class."""
600        assert self._excinfo is not None, (
601            ".type can only be used after the context manager exits"
602        )
603        return self._excinfo[0]

The exception class.

value: +E
605    @property
606    def value(self) -> E:
607        """The exception value."""
608        assert self._excinfo is not None, (
609            ".value can only be used after the context manager exits"
610        )
611        return self._excinfo[1]

The exception value.

tb: traceback
613    @property
614    def tb(self) -> TracebackType:
615        """The exception raw traceback."""
616        assert self._excinfo is not None, (
617            ".tb can only be used after the context manager exits"
618        )
619        return self._excinfo[2]

The exception raw traceback.

typename: str
621    @property
622    def typename(self) -> str:
623        """The type name of the exception."""
624        assert self._excinfo is not None, (
625            ".typename can only be used after the context manager exits"
626        )
627        return self.type.__name__

The type name of the exception.

traceback: _pytest._code.code.Traceback
629    @property
630    def traceback(self) -> Traceback:
631        """The traceback."""
632        if self._traceback is None:
633            self._traceback = Traceback(self.tb)
634        return self._traceback

The traceback.

def exconly(self, tryshort: bool = False) -> str:
645    def exconly(self, tryshort: bool = False) -> str:
646        """Return the exception as a string.
647
648        When 'tryshort' resolves to True, and the exception is an
649        AssertionError, only the actual exception part of the exception
650        representation is returned (so 'AssertionError: ' is removed from
651        the beginning).
652        """
653
654        def _get_single_subexc(
655            eg: BaseExceptionGroup[BaseException],
656        ) -> BaseException | None:
657            if len(eg.exceptions) != 1:
658                return None
659            if isinstance(e := eg.exceptions[0], BaseExceptionGroup):
660                return _get_single_subexc(e)
661            return e
662
663        if (
664            tryshort
665            and isinstance(self.value, BaseExceptionGroup)
666            and (subexc := _get_single_subexc(self.value)) is not None
667        ):
668            return f"{subexc!r} [single exception in {type(self.value).__name__}]"
669
670        lines = format_exception_only(self.type, self.value)
671        text = "".join(lines)
672        text = text.rstrip()
673        if tryshort:
674            if text.startswith(self._striptext):
675                text = text[len(self._striptext) :]
676        return text

Return the exception as a string.

When 'tryshort' resolves to True, and the exception is an AssertionError, only the actual exception part of the exception representation is returned (so 'AssertionError: ' is removed from the beginning).

def errisinstance( self, exc: Union[type[BaseException], tuple[type[BaseException], ...]]) -> bool:
678    def errisinstance(self, exc: EXCEPTION_OR_MORE) -> bool:
679        """Return True if the exception is an instance of exc.
680
681        Consider using ``isinstance(excinfo.value, exc)`` instead.
682        """
683        return isinstance(self.value, exc)

Return True if the exception is an instance of exc.

Consider using isinstance(excinfo.value, exc) instead.

def getrepr( self, showlocals: bool = False, style: Literal['long', 'short', 'line', 'no', 'native', 'value', 'auto'] = 'long', abspath: bool = False, tbfilter: bool | Callable[[_pytest._code.code.ExceptionInfo[BaseException]], _pytest._code.code.Traceback] = True, funcargs: bool = False, truncate_locals: bool = True, truncate_args: bool = True, chain: bool = True) -> _pytest._code.code.ReprExceptionInfo | _pytest._code.code.ExceptionChainRepr:
696    def getrepr(
697        self,
698        showlocals: bool = False,
699        style: TracebackStyle = "long",
700        abspath: bool = False,
701        tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] = True,
702        funcargs: bool = False,
703        truncate_locals: bool = True,
704        truncate_args: bool = True,
705        chain: bool = True,
706    ) -> ReprExceptionInfo | ExceptionChainRepr:
707        """Return str()able representation of this exception info.
708
709        :param bool showlocals:
710            Show locals per traceback entry.
711            Ignored if ``style=="native"``.
712
713        :param str style:
714            long|short|line|no|native|value traceback style.
715
716        :param bool abspath:
717            If paths should be changed to absolute or left unchanged.
718
719        :param tbfilter:
720            A filter for traceback entries.
721
722            * If false, don't hide any entries.
723            * If true, hide internal entries and entries that contain a local
724              variable ``__tracebackhide__ = True``.
725            * If a callable, delegates the filtering to the callable.
726
727            Ignored if ``style`` is ``"native"``.
728
729        :param bool funcargs:
730            Show fixtures ("funcargs" for legacy purposes) per traceback entry.
731
732        :param bool truncate_locals:
733            With ``showlocals==True``, make sure locals can be safely represented as strings.
734
735        :param bool truncate_args:
736            With ``showargs==True``, make sure args can be safely represented as strings.
737
738        :param bool chain:
739            If chained exceptions in Python 3 should be shown.
740
741        .. versionchanged:: 3.9
742
743            Added the ``chain`` parameter.
744        """
745        if style == "native":
746            return ReprExceptionInfo(
747                reprtraceback=ReprTracebackNative(
748                    format_exception(
749                        self.type,
750                        self.value,
751                        self.traceback[0]._rawentry if self.traceback else None,
752                    )
753                ),
754                reprcrash=self._getreprcrash(),
755            )
756
757        fmt = FormattedExcinfo(
758            showlocals=showlocals,
759            style=style,
760            abspath=abspath,
761            tbfilter=tbfilter,
762            funcargs=funcargs,
763            truncate_locals=truncate_locals,
764            truncate_args=truncate_args,
765            chain=chain,
766        )
767        return fmt.repr_excinfo(self)

Return str()able representation of this exception info.

Parameters
  • bool showlocals: Show locals per traceback entry. Ignored if style=="native".

  • str style: long|short|line|no|native|value traceback style.

  • bool abspath: If paths should be changed to absolute or left unchanged.

  • tbfilter: A filter for traceback entries.

    • If false, don't hide any entries.
    • If true, hide internal entries and entries that contain a local variable __tracebackhide__ = True.
    • If a callable, delegates the filtering to the callable.

    Ignored if style is "native".

  • bool funcargs: Show fixtures ("funcargs" for legacy purposes) per traceback entry.

  • bool truncate_locals: With showlocals==True, make sure locals can be safely represented as strings.

  • bool truncate_args: With showargs==True, make sure args can be safely represented as strings.

  • bool chain: If chained exceptions in Python 3 should be shown.

Changed in version 3.9: Added the chain parameter.

def match(self, regexp: str | re.Pattern[str]) -> Literal[True]:
769    def match(self, regexp: str | re.Pattern[str]) -> Literal[True]:
770        """Check whether the regular expression `regexp` matches the string
771        representation of the exception using :func:`python:re.search`.
772
773        If it matches `True` is returned, otherwise an `AssertionError` is raised.
774        """
775        __tracebackhide__ = True
776        value = stringify_exception(self.value)
777        msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}"
778        if regexp == value:
779            msg += "\n Did you mean to `re.escape()` the regex?"
780        assert re.search(regexp, value), msg
781        # Return True to allow for "assert excinfo.match()".
782        return True

Check whether the regular expression regexp matches the string representation of the exception using python:re.search().

If it matches True is returned, otherwise an AssertionError is raised.

def group_contains( self, expected_exception: Union[type[BaseException], tuple[type[BaseException], ...]], *, match: str | re.Pattern[str] | None = None, depth: int | None = None) -> bool:
814    def group_contains(
815        self,
816        expected_exception: EXCEPTION_OR_MORE,
817        *,
818        match: str | re.Pattern[str] | None = None,
819        depth: int | None = None,
820    ) -> bool:
821        """Check whether a captured exception group contains a matching exception.
822
823        :param Type[BaseException] | Tuple[Type[BaseException]] expected_exception:
824            The expected exception type, or a tuple if one of multiple possible
825            exception types are expected.
826
827        :param str | re.Pattern[str] | None match:
828            If specified, a string containing a regular expression,
829            or a regular expression object, that is tested against the string
830            representation of the exception and its `PEP-678 <https://peps.python.org/pep-0678/>` `__notes__`
831            using :func:`re.search`.
832
833            To match a literal string that may contain :ref:`special characters
834            <re-syntax>`, the pattern can first be escaped with :func:`re.escape`.
835
836        :param Optional[int] depth:
837            If `None`, will search for a matching exception at any nesting depth.
838            If >= 1, will only match an exception if it's at the specified depth (depth = 1 being
839            the exceptions contained within the topmost exception group).
840
841        .. versionadded:: 8.0
842
843        .. warning::
844           This helper makes it easy to check for the presence of specific exceptions,
845           but it is very bad for checking that the group does *not* contain
846           *any other exceptions*.
847           You should instead consider using :class:`pytest.RaisesGroup`
848
849        """
850        msg = "Captured exception is not an instance of `BaseExceptionGroup`"
851        assert isinstance(self.value, BaseExceptionGroup), msg
852        msg = "`depth` must be >= 1 if specified"
853        assert (depth is None) or (depth >= 1), msg
854        return self._group_contains(self.value, expected_exception, match, depth)

Check whether a captured exception group contains a matching exception.

Parameters
  • Type[BaseException] | Tuple[Type[BaseException]] expected_exception: The expected exception type, or a tuple if one of multiple possible exception types are expected.

  • str | re.Pattern[str] | None match: If specified, a string containing a regular expression, or a regular expression object, that is tested against the string representation of the exception and its PEP-678 <https://peps.python.org/pep-0678/> __notes__ using re.search().

    To match a literal string that may contain :ref:special characters <re-syntax>, the pattern can first be escaped with re.escape().

  • Optional[int] depth: If None, will search for a matching exception at any nesting depth. If >= 1, will only match an exception if it's at the specified depth (depth = 1 being the exceptions contained within the topmost exception group).

New in version 8.0.

This helper makes it easy to check for the presence of specific exceptions, but it is very bad for checking that the group does not contain any other exceptions. You should instead consider using pytest.RaisesGroup

@final
class ExitCode(enum.IntEnum):
 92@final
 93class ExitCode(enum.IntEnum):
 94    """Encodes the valid exit codes by pytest.
 95
 96    Currently users and plugins may supply other exit codes as well.
 97
 98    .. versionadded:: 5.0
 99    """
100
101    #: Tests passed.
102    OK = 0
103    #: Tests failed.
104    TESTS_FAILED = 1
105    #: pytest was interrupted.
106    INTERRUPTED = 2
107    #: An internal error got in the way.
108    INTERNAL_ERROR = 3
109    #: pytest was misused.
110    USAGE_ERROR = 4
111    #: pytest couldn't find tests.
112    NO_TESTS_COLLECTED = 5

Encodes the valid exit codes by pytest.

Currently users and plugins may supply other exit codes as well.

New in version 5.0.

OK = <ExitCode.OK: 0>
TESTS_FAILED = <ExitCode.TESTS_FAILED: 1>
INTERRUPTED = <ExitCode.INTERRUPTED: 2>
INTERNAL_ERROR = <ExitCode.INTERNAL_ERROR: 3>
USAGE_ERROR = <ExitCode.USAGE_ERROR: 4>
NO_TESTS_COLLECTED = <ExitCode.NO_TESTS_COLLECTED: 5>
class File(_pytest.nodes.FSCollector, abc.ABC):
631class File(FSCollector, abc.ABC):
632    """Base class for collecting tests from a file.
633
634    :ref:`non-python tests`.
635    """

Base class for collecting tests from a file.

:ref:non-python tests.

@final
class FixtureDef(typing.Generic[~FixtureValue]):
 975@final
 976class FixtureDef(Generic[FixtureValue]):
 977    """A container for a fixture definition.
 978
 979    Note: At this time, only explicitly documented fields and methods are
 980    considered public stable API.
 981    """
 982
 983    def __init__(
 984        self,
 985        config: Config,
 986        baseid: str | None,
 987        argname: str,
 988        func: _FixtureFunc[FixtureValue],
 989        scope: Scope | _ScopeName | Callable[[str, Config], _ScopeName] | None,
 990        params: Sequence[object] | None,
 991        ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None,
 992        *,
 993        _ispytest: bool = False,
 994        # only used in a deprecationwarning msg, can be removed in pytest9
 995        _autouse: bool = False,
 996    ) -> None:
 997        check_ispytest(_ispytest)
 998        # The "base" node ID for the fixture.
 999        #
1000        # This is a node ID prefix. A fixture is only available to a node (e.g.
1001        # a `Function` item) if the fixture's baseid is a nodeid of a parent of
1002        # node.
1003        #
1004        # For a fixture found in a Collector's object (e.g. a `Module`s module,
1005        # a `Class`'s class), the baseid is the Collector's nodeid.
1006        #
1007        # For a fixture found in a conftest plugin, the baseid is the conftest's
1008        # directory path relative to the rootdir.
1009        #
1010        # For other plugins, the baseid is the empty string (always matches).
1011        self.baseid: Final = baseid or ""
1012        # Whether the fixture was found from a node or a conftest in the
1013        # collection tree. Will be false for fixtures defined in non-conftest
1014        # plugins.
1015        self.has_location: Final = baseid is not None
1016        # The fixture factory function.
1017        self.func: Final = func
1018        # The name by which the fixture may be requested.
1019        self.argname: Final = argname
1020        if scope is None:
1021            scope = Scope.Function
1022        elif callable(scope):
1023            scope = _eval_scope_callable(scope, argname, config)
1024        if isinstance(scope, str):
1025            scope = Scope.from_user(
1026                scope, descr=f"Fixture '{func.__name__}'", where=baseid
1027            )
1028        self._scope: Final = scope
1029        # If the fixture is directly parametrized, the parameter values.
1030        self.params: Final = params
1031        # If the fixture is directly parametrized, a tuple of explicit IDs to
1032        # assign to the parameter values, or a callable to generate an ID given
1033        # a parameter value.
1034        self.ids: Final = ids
1035        # The names requested by the fixtures.
1036        self.argnames: Final = getfuncargnames(func, name=argname)
1037        # If the fixture was executed, the current value of the fixture.
1038        # Can change if the fixture is executed with different parameters.
1039        self.cached_result: _FixtureCachedResult[FixtureValue] | None = None
1040        self._finalizers: Final[list[Callable[[], object]]] = []
1041
1042        # only used to emit a deprecationwarning, can be removed in pytest9
1043        self._autouse = _autouse
1044
1045    @property
1046    def scope(self) -> _ScopeName:
1047        """Scope string, one of "function", "class", "module", "package", "session"."""
1048        return self._scope.value
1049
1050    def addfinalizer(self, finalizer: Callable[[], object]) -> None:
1051        self._finalizers.append(finalizer)
1052
1053    def finish(self, request: SubRequest) -> None:
1054        exceptions: list[BaseException] = []
1055        while self._finalizers:
1056            fin = self._finalizers.pop()
1057            try:
1058                fin()
1059            except BaseException as e:
1060                exceptions.append(e)
1061        node = request.node
1062        node.ihook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
1063        # Even if finalization fails, we invalidate the cached fixture
1064        # value and remove all finalizers because they may be bound methods
1065        # which will keep instances alive.
1066        self.cached_result = None
1067        self._finalizers.clear()
1068        if len(exceptions) == 1:
1069            raise exceptions[0]
1070        elif len(exceptions) > 1:
1071            msg = f'errors while tearing down fixture "{self.argname}" of {node}'
1072            raise BaseExceptionGroup(msg, exceptions[::-1])
1073
1074    def execute(self, request: SubRequest) -> FixtureValue:
1075        """Return the value of this fixture, executing it if not cached."""
1076        # Ensure that the dependent fixtures requested by this fixture are loaded.
1077        # This needs to be done before checking if we have a cached value, since
1078        # if a dependent fixture has their cache invalidated, e.g. due to
1079        # parametrization, they finalize themselves and fixtures depending on it
1080        # (which will likely include this fixture) setting `self.cached_result = None`.
1081        # See #4871
1082        requested_fixtures_that_should_finalize_us = []
1083        for argname in self.argnames:
1084            fixturedef = request._get_active_fixturedef(argname)
1085            # Saves requested fixtures in a list so we later can add our finalizer
1086            # to them, ensuring that if a requested fixture gets torn down we get torn
1087            # down first. This is generally handled by SetupState, but still currently
1088            # needed when this fixture is not parametrized but depends on a parametrized
1089            # fixture.
1090            if not isinstance(fixturedef, PseudoFixtureDef):
1091                requested_fixtures_that_should_finalize_us.append(fixturedef)
1092
1093        # Check for (and return) cached value/exception.
1094        if self.cached_result is not None:
1095            request_cache_key = self.cache_key(request)
1096            cache_key = self.cached_result[1]
1097            try:
1098                # Attempt to make a normal == check: this might fail for objects
1099                # which do not implement the standard comparison (like numpy arrays -- #6497).
1100                cache_hit = bool(request_cache_key == cache_key)
1101            except (ValueError, RuntimeError):
1102                # If the comparison raises, use 'is' as fallback.
1103                cache_hit = request_cache_key is cache_key
1104
1105            if cache_hit:
1106                if self.cached_result[2] is not None:
1107                    exc, exc_tb = self.cached_result[2]
1108                    raise exc.with_traceback(exc_tb)
1109                else:
1110                    result = self.cached_result[0]
1111                    return result
1112            # We have a previous but differently parametrized fixture instance
1113            # so we need to tear it down before creating a new one.
1114            self.finish(request)
1115            assert self.cached_result is None
1116
1117        # Add finalizer to requested fixtures we saved previously.
1118        # We make sure to do this after checking for cached value to avoid
1119        # adding our finalizer multiple times. (#12135)
1120        finalizer = functools.partial(self.finish, request=request)
1121        for parent_fixture in requested_fixtures_that_should_finalize_us:
1122            parent_fixture.addfinalizer(finalizer)
1123
1124        ihook = request.node.ihook
1125        try:
1126            # Setup the fixture, run the code in it, and cache the value
1127            # in self.cached_result
1128            result = ihook.pytest_fixture_setup(fixturedef=self, request=request)
1129        finally:
1130            # schedule our finalizer, even if the setup failed
1131            request.node.addfinalizer(finalizer)
1132
1133        return result
1134
1135    def cache_key(self, request: SubRequest) -> object:
1136        return getattr(request, "param", None)
1137
1138    def __repr__(self) -> str:
1139        return f"<FixtureDef argname={self.argname!r} scope={self.scope!r} baseid={self.baseid!r}>"

A container for a fixture definition.

Note: At this time, only explicitly documented fields and methods are considered public stable API.

FixtureDef( config: _pytest.config.Config, baseid: str | None, argname: str, func: Union[Callable[..., ~FixtureValue], Callable[..., Generator[~FixtureValue]]], scope: Union[_pytest.scope.Scope, Literal['session', 'package', 'module', 'class', 'function'], Callable[[str, _pytest.config.Config], Literal['session', 'package', 'module', 'class', 'function']], NoneType], params: Sequence[object] | None, ids: tuple[object | None, ...] | Callable[[typing.Any], object | None] | None = None, *, _ispytest: bool = False, _autouse: bool = False)
 983    def __init__(
 984        self,
 985        config: Config,
 986        baseid: str | None,
 987        argname: str,
 988        func: _FixtureFunc[FixtureValue],
 989        scope: Scope | _ScopeName | Callable[[str, Config], _ScopeName] | None,
 990        params: Sequence[object] | None,
 991        ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None,
 992        *,
 993        _ispytest: bool = False,
 994        # only used in a deprecationwarning msg, can be removed in pytest9
 995        _autouse: bool = False,
 996    ) -> None:
 997        check_ispytest(_ispytest)
 998        # The "base" node ID for the fixture.
 999        #
1000        # This is a node ID prefix. A fixture is only available to a node (e.g.
1001        # a `Function` item) if the fixture's baseid is a nodeid of a parent of
1002        # node.
1003        #
1004        # For a fixture found in a Collector's object (e.g. a `Module`s module,
1005        # a `Class`'s class), the baseid is the Collector's nodeid.
1006        #
1007        # For a fixture found in a conftest plugin, the baseid is the conftest's
1008        # directory path relative to the rootdir.
1009        #
1010        # For other plugins, the baseid is the empty string (always matches).
1011        self.baseid: Final = baseid or ""
1012        # Whether the fixture was found from a node or a conftest in the
1013        # collection tree. Will be false for fixtures defined in non-conftest
1014        # plugins.
1015        self.has_location: Final = baseid is not None
1016        # The fixture factory function.
1017        self.func: Final = func
1018        # The name by which the fixture may be requested.
1019        self.argname: Final = argname
1020        if scope is None:
1021            scope = Scope.Function
1022        elif callable(scope):
1023            scope = _eval_scope_callable(scope, argname, config)
1024        if isinstance(scope, str):
1025            scope = Scope.from_user(
1026                scope, descr=f"Fixture '{func.__name__}'", where=baseid
1027            )
1028        self._scope: Final = scope
1029        # If the fixture is directly parametrized, the parameter values.
1030        self.params: Final = params
1031        # If the fixture is directly parametrized, a tuple of explicit IDs to
1032        # assign to the parameter values, or a callable to generate an ID given
1033        # a parameter value.
1034        self.ids: Final = ids
1035        # The names requested by the fixtures.
1036        self.argnames: Final = getfuncargnames(func, name=argname)
1037        # If the fixture was executed, the current value of the fixture.
1038        # Can change if the fixture is executed with different parameters.
1039        self.cached_result: _FixtureCachedResult[FixtureValue] | None = None
1040        self._finalizers: Final[list[Callable[[], object]]] = []
1041
1042        # only used to emit a deprecationwarning, can be removed in pytest9
1043        self._autouse = _autouse
baseid: Final
has_location: Final
func: Final
argname: Final
params: Final
ids: Final
argnames: Final
cached_result: Union[tuple[~FixtureValue, object, None], tuple[None, object, tuple[BaseException, Optional[traceback]]], NoneType]
scope: Literal['session', 'package', 'module', 'class', 'function']
1045    @property
1046    def scope(self) -> _ScopeName:
1047        """Scope string, one of "function", "class", "module", "package", "session"."""
1048        return self._scope.value

Scope string, one of "function", "class", "module", "package", "session".

def addfinalizer(self, finalizer: Callable[[], object]) -> None:
1050    def addfinalizer(self, finalizer: Callable[[], object]) -> None:
1051        self._finalizers.append(finalizer)
def finish(self, request: _pytest.fixtures.SubRequest) -> None:
1053    def finish(self, request: SubRequest) -> None:
1054        exceptions: list[BaseException] = []
1055        while self._finalizers:
1056            fin = self._finalizers.pop()
1057            try:
1058                fin()
1059            except BaseException as e:
1060                exceptions.append(e)
1061        node = request.node
1062        node.ihook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
1063        # Even if finalization fails, we invalidate the cached fixture
1064        # value and remove all finalizers because they may be bound methods
1065        # which will keep instances alive.
1066        self.cached_result = None
1067        self._finalizers.clear()
1068        if len(exceptions) == 1:
1069            raise exceptions[0]
1070        elif len(exceptions) > 1:
1071            msg = f'errors while tearing down fixture "{self.argname}" of {node}'
1072            raise BaseExceptionGroup(msg, exceptions[::-1])
def execute(self, request: _pytest.fixtures.SubRequest) -> ~FixtureValue:
1074    def execute(self, request: SubRequest) -> FixtureValue:
1075        """Return the value of this fixture, executing it if not cached."""
1076        # Ensure that the dependent fixtures requested by this fixture are loaded.
1077        # This needs to be done before checking if we have a cached value, since
1078        # if a dependent fixture has their cache invalidated, e.g. due to
1079        # parametrization, they finalize themselves and fixtures depending on it
1080        # (which will likely include this fixture) setting `self.cached_result = None`.
1081        # See #4871
1082        requested_fixtures_that_should_finalize_us = []
1083        for argname in self.argnames:
1084            fixturedef = request._get_active_fixturedef(argname)
1085            # Saves requested fixtures in a list so we later can add our finalizer
1086            # to them, ensuring that if a requested fixture gets torn down we get torn
1087            # down first. This is generally handled by SetupState, but still currently
1088            # needed when this fixture is not parametrized but depends on a parametrized
1089            # fixture.
1090            if not isinstance(fixturedef, PseudoFixtureDef):
1091                requested_fixtures_that_should_finalize_us.append(fixturedef)
1092
1093        # Check for (and return) cached value/exception.
1094        if self.cached_result is not None:
1095            request_cache_key = self.cache_key(request)
1096            cache_key = self.cached_result[1]
1097            try:
1098                # Attempt to make a normal == check: this might fail for objects
1099                # which do not implement the standard comparison (like numpy arrays -- #6497).
1100                cache_hit = bool(request_cache_key == cache_key)
1101            except (ValueError, RuntimeError):
1102                # If the comparison raises, use 'is' as fallback.
1103                cache_hit = request_cache_key is cache_key
1104
1105            if cache_hit:
1106                if self.cached_result[2] is not None:
1107                    exc, exc_tb = self.cached_result[2]
1108                    raise exc.with_traceback(exc_tb)
1109                else:
1110                    result = self.cached_result[0]
1111                    return result
1112            # We have a previous but differently parametrized fixture instance
1113            # so we need to tear it down before creating a new one.
1114            self.finish(request)
1115            assert self.cached_result is None
1116
1117        # Add finalizer to requested fixtures we saved previously.
1118        # We make sure to do this after checking for cached value to avoid
1119        # adding our finalizer multiple times. (#12135)
1120        finalizer = functools.partial(self.finish, request=request)
1121        for parent_fixture in requested_fixtures_that_should_finalize_us:
1122            parent_fixture.addfinalizer(finalizer)
1123
1124        ihook = request.node.ihook
1125        try:
1126            # Setup the fixture, run the code in it, and cache the value
1127            # in self.cached_result
1128            result = ihook.pytest_fixture_setup(fixturedef=self, request=request)
1129        finally:
1130            # schedule our finalizer, even if the setup failed
1131            request.node.addfinalizer(finalizer)
1132
1133        return result

Return the value of this fixture, executing it if not cached.

def cache_key(self, request: _pytest.fixtures.SubRequest) -> object:
1135    def cache_key(self, request: SubRequest) -> object:
1136        return getattr(request, "param", None)
@final
class FixtureLookupError(builtins.LookupError):
815@final
816class FixtureLookupError(LookupError):
817    """Could not return a requested fixture (missing or invalid)."""
818
819    def __init__(
820        self, argname: str | None, request: FixtureRequest, msg: str | None = None
821    ) -> None:
822        self.argname = argname
823        self.request = request
824        self.fixturestack = request._get_fixturestack()
825        self.msg = msg
826
827    def formatrepr(self) -> FixtureLookupErrorRepr:
828        tblines: list[str] = []
829        addline = tblines.append
830        stack = [self.request._pyfuncitem.obj]
831        stack.extend(map(lambda x: x.func, self.fixturestack))
832        msg = self.msg
833        # This function currently makes an assumption that a non-None msg means we
834        # have a non-empty `self.fixturestack`. This is currently true, but if
835        # somebody at some point want to extend the use of FixtureLookupError to
836        # new cases it might break.
837        # Add the assert to make it clearer to developer that this will fail, otherwise
838        # it crashes because `fspath` does not get set due to `stack` being empty.
839        assert self.msg is None or self.fixturestack, (
840            "formatrepr assumptions broken, rewrite it to handle it"
841        )
842        if msg is not None:
843            # The last fixture raise an error, let's present
844            # it at the requesting side.
845            stack = stack[:-1]
846        for function in stack:
847            fspath, lineno = getfslineno(function)
848            try:
849                lines, _ = inspect.getsourcelines(get_real_func(function))
850            except (OSError, IndexError, TypeError):
851                error_msg = "file %s, line %s: source code not available"
852                addline(error_msg % (fspath, lineno + 1))
853            else:
854                addline(f"file {fspath}, line {lineno + 1}")
855                for i, line in enumerate(lines):
856                    line = line.rstrip()
857                    addline("  " + line)
858                    if line.lstrip().startswith("def"):
859                        break
860
861        if msg is None:
862            fm = self.request._fixturemanager
863            available = set()
864            parent = self.request._pyfuncitem.parent
865            assert parent is not None
866            for name, fixturedefs in fm._arg2fixturedefs.items():
867                faclist = list(fm._matchfactories(fixturedefs, parent))
868                if faclist:
869                    available.add(name)
870            if self.argname in available:
871                msg = (
872                    f" recursive dependency involving fixture '{self.argname}' detected"
873                )
874            else:
875                msg = f"fixture '{self.argname}' not found"
876            msg += "\n available fixtures: {}".format(", ".join(sorted(available)))
877            msg += "\n use 'pytest --fixtures [testpath]' for help on them."
878
879        return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname)

Could not return a requested fixture (missing or invalid).

FixtureLookupError( argname: str | None, request: _pytest.fixtures.FixtureRequest, msg: str | None = None)
819    def __init__(
820        self, argname: str | None, request: FixtureRequest, msg: str | None = None
821    ) -> None:
822        self.argname = argname
823        self.request = request
824        self.fixturestack = request._get_fixturestack()
825        self.msg = msg
argname
request
fixturestack
msg
def formatrepr(self) -> _pytest.fixtures.FixtureLookupErrorRepr:
827    def formatrepr(self) -> FixtureLookupErrorRepr:
828        tblines: list[str] = []
829        addline = tblines.append
830        stack = [self.request._pyfuncitem.obj]
831        stack.extend(map(lambda x: x.func, self.fixturestack))
832        msg = self.msg
833        # This function currently makes an assumption that a non-None msg means we
834        # have a non-empty `self.fixturestack`. This is currently true, but if
835        # somebody at some point want to extend the use of FixtureLookupError to
836        # new cases it might break.
837        # Add the assert to make it clearer to developer that this will fail, otherwise
838        # it crashes because `fspath` does not get set due to `stack` being empty.
839        assert self.msg is None or self.fixturestack, (
840            "formatrepr assumptions broken, rewrite it to handle it"
841        )
842        if msg is not None:
843            # The last fixture raise an error, let's present
844            # it at the requesting side.
845            stack = stack[:-1]
846        for function in stack:
847            fspath, lineno = getfslineno(function)
848            try:
849                lines, _ = inspect.getsourcelines(get_real_func(function))
850            except (OSError, IndexError, TypeError):
851                error_msg = "file %s, line %s: source code not available"
852                addline(error_msg % (fspath, lineno + 1))
853            else:
854                addline(f"file {fspath}, line {lineno + 1}")
855                for i, line in enumerate(lines):
856                    line = line.rstrip()
857                    addline("  " + line)
858                    if line.lstrip().startswith("def"):
859                        break
860
861        if msg is None:
862            fm = self.request._fixturemanager
863            available = set()
864            parent = self.request._pyfuncitem.parent
865            assert parent is not None
866            for name, fixturedefs in fm._arg2fixturedefs.items():
867                faclist = list(fm._matchfactories(fixturedefs, parent))
868                if faclist:
869                    available.add(name)
870            if self.argname in available:
871                msg = (
872                    f" recursive dependency involving fixture '{self.argname}' detected"
873                )
874            else:
875                msg = f"fixture '{self.argname}' not found"
876            msg += "\n available fixtures: {}".format(", ".join(sorted(available)))
877            msg += "\n use 'pytest --fixtures [testpath]' for help on them."
878
879        return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname)
class FixtureRequest(abc.ABC):
372class FixtureRequest(abc.ABC):
373    """The type of the ``request`` fixture.
374
375    A request object gives access to the requesting test context and has a
376    ``param`` attribute in case the fixture is parametrized.
377    """
378
379    def __init__(
380        self,
381        pyfuncitem: Function,
382        fixturename: str | None,
383        arg2fixturedefs: dict[str, Sequence[FixtureDef[Any]]],
384        fixture_defs: dict[str, FixtureDef[Any]],
385        *,
386        _ispytest: bool = False,
387    ) -> None:
388        check_ispytest(_ispytest)
389        #: Fixture for which this request is being performed.
390        self.fixturename: Final = fixturename
391        self._pyfuncitem: Final = pyfuncitem
392        # The FixtureDefs for each fixture name requested by this item.
393        # Starts from the statically-known fixturedefs resolved during
394        # collection. Dynamically requested fixtures (using
395        # `request.getfixturevalue("foo")`) are added dynamically.
396        self._arg2fixturedefs: Final = arg2fixturedefs
397        # The evaluated argnames so far, mapping to the FixtureDef they resolved
398        # to.
399        self._fixture_defs: Final = fixture_defs
400        # Notes on the type of `param`:
401        # -`request.param` is only defined in parametrized fixtures, and will raise
402        #   AttributeError otherwise. Python typing has no notion of "undefined", so
403        #   this cannot be reflected in the type.
404        # - Technically `param` is only (possibly) defined on SubRequest, not
405        #   FixtureRequest, but the typing of that is still in flux so this cheats.
406        # - In the future we might consider using a generic for the param type, but
407        #   for now just using Any.
408        self.param: Any
409
410    @property
411    def _fixturemanager(self) -> FixtureManager:
412        return self._pyfuncitem.session._fixturemanager
413
414    @property
415    @abc.abstractmethod
416    def _scope(self) -> Scope:
417        raise NotImplementedError()
418
419    @property
420    def scope(self) -> _ScopeName:
421        """Scope string, one of "function", "class", "module", "package", "session"."""
422        return self._scope.value
423
424    @abc.abstractmethod
425    def _check_scope(
426        self,
427        requested_fixturedef: FixtureDef[object] | PseudoFixtureDef[object],
428        requested_scope: Scope,
429    ) -> None:
430        raise NotImplementedError()
431
432    @property
433    def fixturenames(self) -> list[str]:
434        """Names of all active fixtures in this request."""
435        result = list(self._pyfuncitem.fixturenames)
436        result.extend(set(self._fixture_defs).difference(result))
437        return result
438
439    @property
440    @abc.abstractmethod
441    def node(self):
442        """Underlying collection node (depends on current request scope)."""
443        raise NotImplementedError()
444
445    @property
446    def config(self) -> Config:
447        """The pytest config object associated with this request."""
448        return self._pyfuncitem.config
449
450    @property
451    def function(self):
452        """Test function object if the request has a per-function scope."""
453        if self.scope != "function":
454            raise AttributeError(
455                f"function not available in {self.scope}-scoped context"
456            )
457        return self._pyfuncitem.obj
458
459    @property
460    def cls(self):
461        """Class (can be None) where the test function was collected."""
462        if self.scope not in ("class", "function"):
463            raise AttributeError(f"cls not available in {self.scope}-scoped context")
464        clscol = self._pyfuncitem.getparent(_pytest.python.Class)
465        if clscol:
466            return clscol.obj
467
468    @property
469    def instance(self):
470        """Instance (can be None) on which test function was collected."""
471        if self.scope != "function":
472            return None
473        return getattr(self._pyfuncitem, "instance", None)
474
475    @property
476    def module(self):
477        """Python module object where the test function was collected."""
478        if self.scope not in ("function", "class", "module"):
479            raise AttributeError(f"module not available in {self.scope}-scoped context")
480        mod = self._pyfuncitem.getparent(_pytest.python.Module)
481        assert mod is not None
482        return mod.obj
483
484    @property
485    def path(self) -> Path:
486        """Path where the test function was collected."""
487        if self.scope not in ("function", "class", "module", "package"):
488            raise AttributeError(f"path not available in {self.scope}-scoped context")
489        return self._pyfuncitem.path
490
491    @property
492    def keywords(self) -> MutableMapping[str, Any]:
493        """Keywords/markers dictionary for the underlying node."""
494        node: nodes.Node = self.node
495        return node.keywords
496
497    @property
498    def session(self) -> Session:
499        """Pytest session object."""
500        return self._pyfuncitem.session
501
502    @abc.abstractmethod
503    def addfinalizer(self, finalizer: Callable[[], object]) -> None:
504        """Add finalizer/teardown function to be called without arguments after
505        the last test within the requesting test context finished execution."""
506        raise NotImplementedError()
507
508    def applymarker(self, marker: str | MarkDecorator) -> None:
509        """Apply a marker to a single test function invocation.
510
511        This method is useful if you don't want to have a keyword/marker
512        on all function invocations.
513
514        :param marker:
515            An object created by a call to ``pytest.mark.NAME(...)``.
516        """
517        self.node.add_marker(marker)
518
519    def raiseerror(self, msg: str | None) -> NoReturn:
520        """Raise a FixtureLookupError exception.
521
522        :param msg:
523            An optional custom error message.
524        """
525        raise FixtureLookupError(None, self, msg)
526
527    def getfixturevalue(self, argname: str) -> Any:
528        """Dynamically run a named fixture function.
529
530        Declaring fixtures via function argument is recommended where possible.
531        But if you can only decide whether to use another fixture at test
532        setup time, you may use this function to retrieve it inside a fixture
533        or test function body.
534
535        This method can be used during the test setup phase or the test run
536        phase, but during the test teardown phase a fixture's value may not
537        be available.
538
539        :param argname:
540            The fixture name.
541        :raises pytest.FixtureLookupError:
542            If the given fixture could not be found.
543        """
544        # Note that in addition to the use case described in the docstring,
545        # getfixturevalue() is also called by pytest itself during item and fixture
546        # setup to evaluate the fixtures that are requested statically
547        # (using function parameters, autouse, etc).
548
549        fixturedef = self._get_active_fixturedef(argname)
550        assert fixturedef.cached_result is not None, (
551            f'The fixture value for "{argname}" is not available.  '
552            "This can happen when the fixture has already been torn down."
553        )
554        return fixturedef.cached_result[0]
555
556    def _iter_chain(self) -> Iterator[SubRequest]:
557        """Yield all SubRequests in the chain, from self up.
558
559        Note: does *not* yield the TopRequest.
560        """
561        current = self
562        while isinstance(current, SubRequest):
563            yield current
564            current = current._parent_request
565
566    def _get_active_fixturedef(
567        self, argname: str
568    ) -> FixtureDef[object] | PseudoFixtureDef[object]:
569        if argname == "request":
570            cached_result = (self, [0], None)
571            return PseudoFixtureDef(cached_result, Scope.Function)
572
573        # If we already finished computing a fixture by this name in this item,
574        # return it.
575        fixturedef = self._fixture_defs.get(argname)
576        if fixturedef is not None:
577            self._check_scope(fixturedef, fixturedef._scope)
578            return fixturedef
579
580        # Find the appropriate fixturedef.
581        fixturedefs = self._arg2fixturedefs.get(argname, None)
582        if fixturedefs is None:
583            # We arrive here because of a dynamic call to
584            # getfixturevalue(argname) which was naturally
585            # not known at parsing/collection time.
586            fixturedefs = self._fixturemanager.getfixturedefs(argname, self._pyfuncitem)
587            if fixturedefs is not None:
588                self._arg2fixturedefs[argname] = fixturedefs
589        # No fixtures defined with this name.
590        if fixturedefs is None:
591            raise FixtureLookupError(argname, self)
592        # The are no fixtures with this name applicable for the function.
593        if not fixturedefs:
594            raise FixtureLookupError(argname, self)
595
596        # A fixture may override another fixture with the same name, e.g. a
597        # fixture in a module can override a fixture in a conftest, a fixture in
598        # a class can override a fixture in the module, and so on.
599        # An overriding fixture can request its own name (possibly indirectly);
600        # in this case it gets the value of the fixture it overrides, one level
601        # up.
602        # Check how many `argname`s deep we are, and take the next one.
603        # `fixturedefs` is sorted from furthest to closest, so use negative
604        # indexing to go in reverse.
605        index = -1
606        for request in self._iter_chain():
607            if request.fixturename == argname:
608                index -= 1
609        # If already consumed all of the available levels, fail.
610        if -index > len(fixturedefs):
611            raise FixtureLookupError(argname, self)
612        fixturedef = fixturedefs[index]
613
614        # Prepare a SubRequest object for calling the fixture.
615        try:
616            callspec = self._pyfuncitem.callspec
617        except AttributeError:
618            callspec = None
619        if callspec is not None and argname in callspec.params:
620            param = callspec.params[argname]
621            param_index = callspec.indices[argname]
622            # The parametrize invocation scope overrides the fixture's scope.
623            scope = callspec._arg2scope[argname]
624        else:
625            param = NOTSET
626            param_index = 0
627            scope = fixturedef._scope
628            self._check_fixturedef_without_param(fixturedef)
629        # The parametrize invocation scope only controls caching behavior while
630        # allowing wider-scoped fixtures to keep depending on the parametrized
631        # fixture. Scope control is enforced for parametrized fixtures
632        # by recreating the whole fixture tree on parameter change.
633        # Hence `fixturedef._scope`, not `scope`.
634        self._check_scope(fixturedef, fixturedef._scope)
635        subrequest = SubRequest(
636            self, scope, param, param_index, fixturedef, _ispytest=True
637        )
638
639        # Make sure the fixture value is cached, running it if it isn't
640        fixturedef.execute(request=subrequest)
641
642        self._fixture_defs[argname] = fixturedef
643        return fixturedef
644
645    def _check_fixturedef_without_param(self, fixturedef: FixtureDef[object]) -> None:
646        """Check that this request is allowed to execute this fixturedef without
647        a param."""
648        funcitem = self._pyfuncitem
649        has_params = fixturedef.params is not None
650        fixtures_not_supported = getattr(funcitem, "nofuncargs", False)
651        if has_params and fixtures_not_supported:
652            msg = (
653                f"{funcitem.name} does not support fixtures, maybe unittest.TestCase subclass?\n"
654                f"Node id: {funcitem.nodeid}\n"
655                f"Function type: {type(funcitem).__name__}"
656            )
657            fail(msg, pytrace=False)
658        if has_params:
659            frame = inspect.stack()[3]
660            frameinfo = inspect.getframeinfo(frame[0])
661            source_path = absolutepath(frameinfo.filename)
662            source_lineno = frameinfo.lineno
663            try:
664                source_path_str = str(source_path.relative_to(funcitem.config.rootpath))
665            except ValueError:
666                source_path_str = str(source_path)
667            location = getlocation(fixturedef.func, funcitem.config.rootpath)
668            msg = (
669                "The requested fixture has no parameter defined for test:\n"
670                f"    {funcitem.nodeid}\n\n"
671                f"Requested fixture '{fixturedef.argname}' defined in:\n"
672                f"{location}\n\n"
673                f"Requested here:\n"
674                f"{source_path_str}:{source_lineno}"
675            )
676            fail(msg, pytrace=False)
677
678    def _get_fixturestack(self) -> list[FixtureDef[Any]]:
679        values = [request._fixturedef for request in self._iter_chain()]
680        values.reverse()
681        return values

The type of the request fixture.

A request object gives access to the requesting test context and has a param attribute in case the fixture is parametrized.

fixturename: Final
param: Any
scope: Literal['session', 'package', 'module', 'class', 'function']
419    @property
420    def scope(self) -> _ScopeName:
421        """Scope string, one of "function", "class", "module", "package", "session"."""
422        return self._scope.value

Scope string, one of "function", "class", "module", "package", "session".

fixturenames: list[str]
432    @property
433    def fixturenames(self) -> list[str]:
434        """Names of all active fixtures in this request."""
435        result = list(self._pyfuncitem.fixturenames)
436        result.extend(set(self._fixture_defs).difference(result))
437        return result

Names of all active fixtures in this request.

node
439    @property
440    @abc.abstractmethod
441    def node(self):
442        """Underlying collection node (depends on current request scope)."""
443        raise NotImplementedError()

Underlying collection node (depends on current request scope).

config: _pytest.config.Config
445    @property
446    def config(self) -> Config:
447        """The pytest config object associated with this request."""
448        return self._pyfuncitem.config

The pytest config object associated with this request.

function
450    @property
451    def function(self):
452        """Test function object if the request has a per-function scope."""
453        if self.scope != "function":
454            raise AttributeError(
455                f"function not available in {self.scope}-scoped context"
456            )
457        return self._pyfuncitem.obj

Test function object if the request has a per-function scope.

cls
459    @property
460    def cls(self):
461        """Class (can be None) where the test function was collected."""
462        if self.scope not in ("class", "function"):
463            raise AttributeError(f"cls not available in {self.scope}-scoped context")
464        clscol = self._pyfuncitem.getparent(_pytest.python.Class)
465        if clscol:
466            return clscol.obj

Class (can be None) where the test function was collected.

instance
468    @property
469    def instance(self):
470        """Instance (can be None) on which test function was collected."""
471        if self.scope != "function":
472            return None
473        return getattr(self._pyfuncitem, "instance", None)

Instance (can be None) on which test function was collected.

module
475    @property
476    def module(self):
477        """Python module object where the test function was collected."""
478        if self.scope not in ("function", "class", "module"):
479            raise AttributeError(f"module not available in {self.scope}-scoped context")
480        mod = self._pyfuncitem.getparent(_pytest.python.Module)
481        assert mod is not None
482        return mod.obj

Python module object where the test function was collected.

path: pathlib.Path
484    @property
485    def path(self) -> Path:
486        """Path where the test function was collected."""
487        if self.scope not in ("function", "class", "module", "package"):
488            raise AttributeError(f"path not available in {self.scope}-scoped context")
489        return self._pyfuncitem.path

Path where the test function was collected.

keywords: MutableMapping[str, typing.Any]
491    @property
492    def keywords(self) -> MutableMapping[str, Any]:
493        """Keywords/markers dictionary for the underlying node."""
494        node: nodes.Node = self.node
495        return node.keywords

Keywords/markers dictionary for the underlying node.

session: _pytest.main.Session
497    @property
498    def session(self) -> Session:
499        """Pytest session object."""
500        return self._pyfuncitem.session

Pytest session object.

@abc.abstractmethod
def addfinalizer(self, finalizer: Callable[[], object]) -> None:
502    @abc.abstractmethod
503    def addfinalizer(self, finalizer: Callable[[], object]) -> None:
504        """Add finalizer/teardown function to be called without arguments after
505        the last test within the requesting test context finished execution."""
506        raise NotImplementedError()

Add finalizer/teardown function to be called without arguments after the last test within the requesting test context finished execution.

def applymarker(self, marker: str | _pytest.mark.structures.MarkDecorator) -> None:
508    def applymarker(self, marker: str | MarkDecorator) -> None:
509        """Apply a marker to a single test function invocation.
510
511        This method is useful if you don't want to have a keyword/marker
512        on all function invocations.
513
514        :param marker:
515            An object created by a call to ``pytest.mark.NAME(...)``.
516        """
517        self.node.add_marker(marker)

Apply a marker to a single test function invocation.

This method is useful if you don't want to have a keyword/marker on all function invocations.

Parameters
  • marker: An object created by a call to pytest.mark.NAME(...).
def raiseerror(self, msg: str | None) -> NoReturn:
519    def raiseerror(self, msg: str | None) -> NoReturn:
520        """Raise a FixtureLookupError exception.
521
522        :param msg:
523            An optional custom error message.
524        """
525        raise FixtureLookupError(None, self, msg)

Raise a FixtureLookupError exception.

Parameters
  • msg: An optional custom error message.
def getfixturevalue(self, argname: str) -> Any:
527    def getfixturevalue(self, argname: str) -> Any:
528        """Dynamically run a named fixture function.
529
530        Declaring fixtures via function argument is recommended where possible.
531        But if you can only decide whether to use another fixture at test
532        setup time, you may use this function to retrieve it inside a fixture
533        or test function body.
534
535        This method can be used during the test setup phase or the test run
536        phase, but during the test teardown phase a fixture's value may not
537        be available.
538
539        :param argname:
540            The fixture name.
541        :raises pytest.FixtureLookupError:
542            If the given fixture could not be found.
543        """
544        # Note that in addition to the use case described in the docstring,
545        # getfixturevalue() is also called by pytest itself during item and fixture
546        # setup to evaluate the fixtures that are requested statically
547        # (using function parameters, autouse, etc).
548
549        fixturedef = self._get_active_fixturedef(argname)
550        assert fixturedef.cached_result is not None, (
551            f'The fixture value for "{argname}" is not available.  '
552            "This can happen when the fixture has already been torn down."
553        )
554        return fixturedef.cached_result[0]

Dynamically run a named fixture function.

Declaring fixtures via function argument is recommended where possible. But if you can only decide whether to use another fixture at test setup time, you may use this function to retrieve it inside a fixture or test function body.

This method can be used during the test setup phase or the test run phase, but during the test teardown phase a fixture's value may not be available.

Parameters
  • argname: The fixture name.
Raises
class Function(_pytest.python.PyobjMixin, pytest.Item):
1539class Function(PyobjMixin, nodes.Item):
1540    """Item responsible for setting up and executing a Python test function.
1541
1542    :param name:
1543        The full function name, including any decorations like those
1544        added by parametrization (``my_func[my_param]``).
1545    :param parent:
1546        The parent Node.
1547    :param config:
1548        The pytest Config object.
1549    :param callspec:
1550        If given, this function has been parametrized and the callspec contains
1551        meta information about the parametrization.
1552    :param callobj:
1553        If given, the object which will be called when the Function is invoked,
1554        otherwise the callobj will be obtained from ``parent`` using ``originalname``.
1555    :param keywords:
1556        Keywords bound to the function object for "-k" matching.
1557    :param session:
1558        The pytest Session object.
1559    :param fixtureinfo:
1560        Fixture information already resolved at this fixture node..
1561    :param originalname:
1562        The attribute name to use for accessing the underlying function object.
1563        Defaults to ``name``. Set this if name is different from the original name,
1564        for example when it contains decorations like those added by parametrization
1565        (``my_func[my_param]``).
1566    """
1567
1568    # Disable since functions handle it themselves.
1569    _ALLOW_MARKERS = False
1570
1571    def __init__(
1572        self,
1573        name: str,
1574        parent,
1575        config: Config | None = None,
1576        callspec: CallSpec2 | None = None,
1577        callobj=NOTSET,
1578        keywords: Mapping[str, Any] | None = None,
1579        session: Session | None = None,
1580        fixtureinfo: FuncFixtureInfo | None = None,
1581        originalname: str | None = None,
1582    ) -> None:
1583        super().__init__(name, parent, config=config, session=session)
1584
1585        if callobj is not NOTSET:
1586            self._obj = callobj
1587            self._instance = getattr(callobj, "__self__", None)
1588
1589        #: Original function name, without any decorations (for example
1590        #: parametrization adds a ``"[...]"`` suffix to function names), used to access
1591        #: the underlying function object from ``parent`` (in case ``callobj`` is not given
1592        #: explicitly).
1593        #:
1594        #: .. versionadded:: 3.0
1595        self.originalname = originalname or name
1596
1597        # Note: when FunctionDefinition is introduced, we should change ``originalname``
1598        # to a readonly property that returns FunctionDefinition.name.
1599
1600        self.own_markers.extend(get_unpacked_marks(self.obj))
1601        if callspec:
1602            self.callspec = callspec
1603            self.own_markers.extend(callspec.marks)
1604
1605        # todo: this is a hell of a hack
1606        # https://github.com/pytest-dev/pytest/issues/4569
1607        # Note: the order of the updates is important here; indicates what
1608        # takes priority (ctor argument over function attributes over markers).
1609        # Take own_markers only; NodeKeywords handles parent traversal on its own.
1610        self.keywords.update((mark.name, mark) for mark in self.own_markers)
1611        self.keywords.update(self.obj.__dict__)
1612        if keywords:
1613            self.keywords.update(keywords)
1614
1615        if fixtureinfo is None:
1616            fm = self.session._fixturemanager
1617            fixtureinfo = fm.getfixtureinfo(self, self.obj, self.cls)
1618        self._fixtureinfo: FuncFixtureInfo = fixtureinfo
1619        self.fixturenames = fixtureinfo.names_closure
1620        self._initrequest()
1621
1622    # todo: determine sound type limitations
1623    @classmethod
1624    def from_parent(cls, parent, **kw) -> Self:
1625        """The public constructor."""
1626        return super().from_parent(parent=parent, **kw)
1627
1628    def _initrequest(self) -> None:
1629        self.funcargs: dict[str, object] = {}
1630        self._request = fixtures.TopRequest(self, _ispytest=True)
1631
1632    @property
1633    def function(self):
1634        """Underlying python 'function' object."""
1635        return getimfunc(self.obj)
1636
1637    @property
1638    def instance(self):
1639        try:
1640            return self._instance
1641        except AttributeError:
1642            if isinstance(self.parent, Class):
1643                # Each Function gets a fresh class instance.
1644                self._instance = self._getinstance()
1645            else:
1646                self._instance = None
1647        return self._instance
1648
1649    def _getinstance(self):
1650        if isinstance(self.parent, Class):
1651            # Each Function gets a fresh class instance.
1652            return self.parent.newinstance()
1653        else:
1654            return None
1655
1656    def _getobj(self):
1657        instance = self.instance
1658        if instance is not None:
1659            parent_obj = instance
1660        else:
1661            assert self.parent is not None
1662            parent_obj = self.parent.obj  # type: ignore[attr-defined]
1663        return getattr(parent_obj, self.originalname)
1664
1665    @property
1666    def _pyfuncitem(self):
1667        """(compatonly) for code expecting pytest-2.2 style request objects."""
1668        return self
1669
1670    def runtest(self) -> None:
1671        """Execute the underlying test function."""
1672        self.ihook.pytest_pyfunc_call(pyfuncitem=self)
1673
1674    def setup(self) -> None:
1675        self._request._fillfixtures()
1676
1677    def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback:
1678        if hasattr(self, "_obj") and not self.config.getoption("fulltrace", False):
1679            code = _pytest._code.Code.from_function(get_real_func(self.obj))
1680            path, firstlineno = code.path, code.firstlineno
1681            traceback = excinfo.traceback
1682            ntraceback = traceback.cut(path=path, firstlineno=firstlineno)
1683            if ntraceback == traceback:
1684                ntraceback = ntraceback.cut(path=path)
1685                if ntraceback == traceback:
1686                    ntraceback = ntraceback.filter(filter_traceback)
1687                    if not ntraceback:
1688                        ntraceback = traceback
1689            ntraceback = ntraceback.filter(excinfo)
1690
1691            # issue364: mark all but first and last frames to
1692            # only show a single-line message for each frame.
1693            if self.config.getoption("tbstyle", "auto") == "auto":
1694                if len(ntraceback) > 2:
1695                    ntraceback = Traceback(
1696                        (
1697                            ntraceback[0],
1698                            *(t.with_repr_style("short") for t in ntraceback[1:-1]),
1699                            ntraceback[-1],
1700                        )
1701                    )
1702
1703            return ntraceback
1704        return excinfo.traceback
1705
1706    # TODO: Type ignored -- breaks Liskov Substitution.
1707    def repr_failure(  # type: ignore[override]
1708        self,
1709        excinfo: ExceptionInfo[BaseException],
1710    ) -> str | TerminalRepr:
1711        style = self.config.getoption("tbstyle", "auto")
1712        if style == "auto":
1713            style = "long"
1714        return self._repr_failure_py(excinfo, style=style)

Item responsible for setting up and executing a Python test function.

Parameters
  • name: The full function name, including any decorations like those added by parametrization (my_func[my_param]).
  • parent: The parent Node.
  • config: The pytest Config object.
  • callspec: If given, this function has been parametrized and the callspec contains meta information about the parametrization.
  • callobj: If given, the object which will be called when the Function is invoked, otherwise the callobj will be obtained from parent using originalname.
  • keywords: Keywords bound to the function object for "-k" matching.
  • session: The pytest Session object.
  • fixtureinfo: Fixture information already resolved at this fixture node..
  • originalname: The attribute name to use for accessing the underlying function object. Defaults to name. Set this if name is different from the original name, for example when it contains decorations like those added by parametrization (my_func[my_param]).
Function( name: str, parent, config: _pytest.config.Config | None = None, callspec: _pytest.python.CallSpec2 | None = None, callobj=<NotSetType.token: 0>, keywords: Mapping[str, typing.Any] | None = None, session: _pytest.main.Session | None = None, fixtureinfo: _pytest.fixtures.FuncFixtureInfo | None = None, originalname: str | None = None)
1571    def __init__(
1572        self,
1573        name: str,
1574        parent,
1575        config: Config | None = None,
1576        callspec: CallSpec2 | None = None,
1577        callobj=NOTSET,
1578        keywords: Mapping[str, Any] | None = None,
1579        session: Session | None = None,
1580        fixtureinfo: FuncFixtureInfo | None = None,
1581        originalname: str | None = None,
1582    ) -> None:
1583        super().__init__(name, parent, config=config, session=session)
1584
1585        if callobj is not NOTSET:
1586            self._obj = callobj
1587            self._instance = getattr(callobj, "__self__", None)
1588
1589        #: Original function name, without any decorations (for example
1590        #: parametrization adds a ``"[...]"`` suffix to function names), used to access
1591        #: the underlying function object from ``parent`` (in case ``callobj`` is not given
1592        #: explicitly).
1593        #:
1594        #: .. versionadded:: 3.0
1595        self.originalname = originalname or name
1596
1597        # Note: when FunctionDefinition is introduced, we should change ``originalname``
1598        # to a readonly property that returns FunctionDefinition.name.
1599
1600        self.own_markers.extend(get_unpacked_marks(self.obj))
1601        if callspec:
1602            self.callspec = callspec
1603            self.own_markers.extend(callspec.marks)
1604
1605        # todo: this is a hell of a hack
1606        # https://github.com/pytest-dev/pytest/issues/4569
1607        # Note: the order of the updates is important here; indicates what
1608        # takes priority (ctor argument over function attributes over markers).
1609        # Take own_markers only; NodeKeywords handles parent traversal on its own.
1610        self.keywords.update((mark.name, mark) for mark in self.own_markers)
1611        self.keywords.update(self.obj.__dict__)
1612        if keywords:
1613            self.keywords.update(keywords)
1614
1615        if fixtureinfo is None:
1616            fm = self.session._fixturemanager
1617            fixtureinfo = fm.getfixtureinfo(self, self.obj, self.cls)
1618        self._fixtureinfo: FuncFixtureInfo = fixtureinfo
1619        self.fixturenames = fixtureinfo.names_closure
1620        self._initrequest()
originalname
fixturenames
@classmethod
def from_parent(cls, parent, **kw) -> Self:
1623    @classmethod
1624    def from_parent(cls, parent, **kw) -> Self:
1625        """The public constructor."""
1626        return super().from_parent(parent=parent, **kw)

The public constructor.

function
1632    @property
1633    def function(self):
1634        """Underlying python 'function' object."""
1635        return getimfunc(self.obj)

Underlying python 'function' object.

instance
1637    @property
1638    def instance(self):
1639        try:
1640            return self._instance
1641        except AttributeError:
1642            if isinstance(self.parent, Class):
1643                # Each Function gets a fresh class instance.
1644                self._instance = self._getinstance()
1645            else:
1646                self._instance = None
1647        return self._instance

Python instance object the function is bound to.

Returns None if not a test method, e.g. for a standalone test function, a class or a module.

def runtest(self) -> None:
1670    def runtest(self) -> None:
1671        """Execute the underlying test function."""
1672        self.ihook.pytest_pyfunc_call(pyfuncitem=self)

Execute the underlying test function.

def setup(self) -> None:
1674    def setup(self) -> None:
1675        self._request._fillfixtures()
def repr_failure( self, excinfo: _pytest._code.code.ExceptionInfo[BaseException]) -> str | _pytest._code.code.TerminalRepr:
1707    def repr_failure(  # type: ignore[override]
1708        self,
1709        excinfo: ExceptionInfo[BaseException],
1710    ) -> str | TerminalRepr:
1711        style = self.config.getoption("tbstyle", "auto")
1712        if style == "auto":
1713            style = "long"
1714        return self._repr_failure_py(excinfo, style=style)

Return a representation of a collection or test failure.

seealso :ref:non-python tests.

Parameters
  • excinfo: Exception information for the failure.
@final
class HookRecorder:
249@final
250class HookRecorder:
251    """Record all hooks called in a plugin manager.
252
253    Hook recorders are created by :class:`Pytester`.
254
255    This wraps all the hook calls in the plugin manager, recording each call
256    before propagating the normal calls.
257    """
258
259    def __init__(
260        self, pluginmanager: PytestPluginManager, *, _ispytest: bool = False
261    ) -> None:
262        check_ispytest(_ispytest)
263
264        self._pluginmanager = pluginmanager
265        self.calls: list[RecordedHookCall] = []
266        self.ret: int | ExitCode | None = None
267
268        def before(hook_name: str, hook_impls, kwargs) -> None:
269            self.calls.append(RecordedHookCall(hook_name, kwargs))
270
271        def after(outcome, hook_name: str, hook_impls, kwargs) -> None:
272            pass
273
274        self._undo_wrapping = pluginmanager.add_hookcall_monitoring(before, after)
275
276    def finish_recording(self) -> None:
277        self._undo_wrapping()
278
279    def getcalls(self, names: str | Iterable[str]) -> list[RecordedHookCall]:
280        """Get all recorded calls to hooks with the given names (or name)."""
281        if isinstance(names, str):
282            names = names.split()
283        return [call for call in self.calls if call._name in names]
284
285    def assert_contains(self, entries: Sequence[tuple[str, str]]) -> None:
286        __tracebackhide__ = True
287        i = 0
288        entries = list(entries)
289        # Since Python 3.13, f_locals is not a dict, but eval requires a dict.
290        backlocals = dict(sys._getframe(1).f_locals)
291        while entries:
292            name, check = entries.pop(0)
293            for ind, call in enumerate(self.calls[i:]):
294                if call._name == name:
295                    print("NAMEMATCH", name, call)
296                    if eval(check, backlocals, call.__dict__):
297                        print("CHECKERMATCH", repr(check), "->", call)
298                    else:
299                        print("NOCHECKERMATCH", repr(check), "-", call)
300                        continue
301                    i += ind + 1
302                    break
303                print("NONAMEMATCH", name, "with", call)
304            else:
305                fail(f"could not find {name!r} check {check!r}")
306
307    def popcall(self, name: str) -> RecordedHookCall:
308        __tracebackhide__ = True
309        for i, call in enumerate(self.calls):
310            if call._name == name:
311                del self.calls[i]
312                return call
313        lines = [f"could not find call {name!r}, in:"]
314        lines.extend([f"  {x}" for x in self.calls])
315        fail("\n".join(lines))
316
317    def getcall(self, name: str) -> RecordedHookCall:
318        values = self.getcalls(name)
319        assert len(values) == 1, (name, values)
320        return values[0]
321
322    # functionality for test reports
323
324    @overload
325    def getreports(
326        self,
327        names: Literal["pytest_collectreport"],
328    ) -> Sequence[CollectReport]: ...
329
330    @overload
331    def getreports(
332        self,
333        names: Literal["pytest_runtest_logreport"],
334    ) -> Sequence[TestReport]: ...
335
336    @overload
337    def getreports(
338        self,
339        names: str | Iterable[str] = (
340            "pytest_collectreport",
341            "pytest_runtest_logreport",
342        ),
343    ) -> Sequence[CollectReport | TestReport]: ...
344
345    def getreports(
346        self,
347        names: str | Iterable[str] = (
348            "pytest_collectreport",
349            "pytest_runtest_logreport",
350        ),
351    ) -> Sequence[CollectReport | TestReport]:
352        return [x.report for x in self.getcalls(names)]
353
354    def matchreport(
355        self,
356        inamepart: str = "",
357        names: str | Iterable[str] = (
358            "pytest_runtest_logreport",
359            "pytest_collectreport",
360        ),
361        when: str | None = None,
362    ) -> CollectReport | TestReport:
363        """Return a testreport whose dotted import path matches."""
364        values = []
365        for rep in self.getreports(names=names):
366            if not when and rep.when != "call" and rep.passed:
367                # setup/teardown passing reports - let's ignore those
368                continue
369            if when and rep.when != when:
370                continue
371            if not inamepart or inamepart in rep.nodeid.split("::"):
372                values.append(rep)
373        if not values:
374            raise ValueError(
375                f"could not find test report matching {inamepart!r}: "
376                "no test reports at all!"
377            )
378        if len(values) > 1:
379            raise ValueError(
380                f"found 2 or more testreports matching {inamepart!r}: {values}"
381            )
382        return values[0]
383
384    @overload
385    def getfailures(
386        self,
387        names: Literal["pytest_collectreport"],
388    ) -> Sequence[CollectReport]: ...
389
390    @overload
391    def getfailures(
392        self,
393        names: Literal["pytest_runtest_logreport"],
394    ) -> Sequence[TestReport]: ...
395
396    @overload
397    def getfailures(
398        self,
399        names: str | Iterable[str] = (
400            "pytest_collectreport",
401            "pytest_runtest_logreport",
402        ),
403    ) -> Sequence[CollectReport | TestReport]: ...
404
405    def getfailures(
406        self,
407        names: str | Iterable[str] = (
408            "pytest_collectreport",
409            "pytest_runtest_logreport",
410        ),
411    ) -> Sequence[CollectReport | TestReport]:
412        return [rep for rep in self.getreports(names) if rep.failed]
413
414    def getfailedcollections(self) -> Sequence[CollectReport]:
415        return self.getfailures("pytest_collectreport")
416
417    def listoutcomes(
418        self,
419    ) -> tuple[
420        Sequence[TestReport],
421        Sequence[CollectReport | TestReport],
422        Sequence[CollectReport | TestReport],
423    ]:
424        passed = []
425        skipped = []
426        failed = []
427        for rep in self.getreports(
428            ("pytest_collectreport", "pytest_runtest_logreport")
429        ):
430            if rep.passed:
431                if rep.when == "call":
432                    assert isinstance(rep, TestReport)
433                    passed.append(rep)
434            elif rep.skipped:
435                skipped.append(rep)
436            else:
437                assert rep.failed, f"Unexpected outcome: {rep!r}"
438                failed.append(rep)
439        return passed, skipped, failed
440
441    def countoutcomes(self) -> list[int]:
442        return [len(x) for x in self.listoutcomes()]
443
444    def assertoutcome(self, passed: int = 0, skipped: int = 0, failed: int = 0) -> None:
445        __tracebackhide__ = True
446        from _pytest.pytester_assertions import assertoutcome
447
448        outcomes = self.listoutcomes()
449        assertoutcome(
450            outcomes,
451            passed=passed,
452            skipped=skipped,
453            failed=failed,
454        )
455
456    def clear(self) -> None:
457        self.calls[:] = []

Record all hooks called in a plugin manager.

Hook recorders are created by Pytester.

This wraps all the hook calls in the plugin manager, recording each call before propagating the normal calls.

HookRecorder( pluginmanager: _pytest.config.PytestPluginManager, *, _ispytest: bool = False)
259    def __init__(
260        self, pluginmanager: PytestPluginManager, *, _ispytest: bool = False
261    ) -> None:
262        check_ispytest(_ispytest)
263
264        self._pluginmanager = pluginmanager
265        self.calls: list[RecordedHookCall] = []
266        self.ret: int | ExitCode | None = None
267
268        def before(hook_name: str, hook_impls, kwargs) -> None:
269            self.calls.append(RecordedHookCall(hook_name, kwargs))
270
271        def after(outcome, hook_name: str, hook_impls, kwargs) -> None:
272            pass
273
274        self._undo_wrapping = pluginmanager.add_hookcall_monitoring(before, after)
calls: list[_pytest.pytester.RecordedHookCall]
ret: int | _pytest.config.ExitCode | None
def finish_recording(self) -> None:
276    def finish_recording(self) -> None:
277        self._undo_wrapping()
def getcalls( self, names: str | Iterable[str]) -> list[_pytest.pytester.RecordedHookCall]:
279    def getcalls(self, names: str | Iterable[str]) -> list[RecordedHookCall]:
280        """Get all recorded calls to hooks with the given names (or name)."""
281        if isinstance(names, str):
282            names = names.split()
283        return [call for call in self.calls if call._name in names]

Get all recorded calls to hooks with the given names (or name).

def assert_contains(self, entries: Sequence[tuple[str, str]]) -> None:
285    def assert_contains(self, entries: Sequence[tuple[str, str]]) -> None:
286        __tracebackhide__ = True
287        i = 0
288        entries = list(entries)
289        # Since Python 3.13, f_locals is not a dict, but eval requires a dict.
290        backlocals = dict(sys._getframe(1).f_locals)
291        while entries:
292            name, check = entries.pop(0)
293            for ind, call in enumerate(self.calls[i:]):
294                if call._name == name:
295                    print("NAMEMATCH", name, call)
296                    if eval(check, backlocals, call.__dict__):
297                        print("CHECKERMATCH", repr(check), "->", call)
298                    else:
299                        print("NOCHECKERMATCH", repr(check), "-", call)
300                        continue
301                    i += ind + 1
302                    break
303                print("NONAMEMATCH", name, "with", call)
304            else:
305                fail(f"could not find {name!r} check {check!r}")
def popcall(self, name: str) -> _pytest.pytester.RecordedHookCall:
307    def popcall(self, name: str) -> RecordedHookCall:
308        __tracebackhide__ = True
309        for i, call in enumerate(self.calls):
310            if call._name == name:
311                del self.calls[i]
312                return call
313        lines = [f"could not find call {name!r}, in:"]
314        lines.extend([f"  {x}" for x in self.calls])
315        fail("\n".join(lines))
def getcall(self, name: str) -> _pytest.pytester.RecordedHookCall:
317    def getcall(self, name: str) -> RecordedHookCall:
318        values = self.getcalls(name)
319        assert len(values) == 1, (name, values)
320        return values[0]
def getreports( self, names: str | Iterable[str] = ('pytest_collectreport', 'pytest_runtest_logreport')) -> Sequence[_pytest.reports.CollectReport | _pytest.reports.TestReport]:
345    def getreports(
346        self,
347        names: str | Iterable[str] = (
348            "pytest_collectreport",
349            "pytest_runtest_logreport",
350        ),
351    ) -> Sequence[CollectReport | TestReport]:
352        return [x.report for x in self.getcalls(names)]
def matchreport( self, inamepart: str = '', names: str | Iterable[str] = ('pytest_runtest_logreport', 'pytest_collectreport'), when: str | None = None) -> _pytest.reports.CollectReport | _pytest.reports.TestReport:
354    def matchreport(
355        self,
356        inamepart: str = "",
357        names: str | Iterable[str] = (
358            "pytest_runtest_logreport",
359            "pytest_collectreport",
360        ),
361        when: str | None = None,
362    ) -> CollectReport | TestReport:
363        """Return a testreport whose dotted import path matches."""
364        values = []
365        for rep in self.getreports(names=names):
366            if not when and rep.when != "call" and rep.passed:
367                # setup/teardown passing reports - let's ignore those
368                continue
369            if when and rep.when != when:
370                continue
371            if not inamepart or inamepart in rep.nodeid.split("::"):
372                values.append(rep)
373        if not values:
374            raise ValueError(
375                f"could not find test report matching {inamepart!r}: "
376                "no test reports at all!"
377            )
378        if len(values) > 1:
379            raise ValueError(
380                f"found 2 or more testreports matching {inamepart!r}: {values}"
381            )
382        return values[0]

Return a testreport whose dotted import path matches.

def getfailures( self, names: str | Iterable[str] = ('pytest_collectreport', 'pytest_runtest_logreport')) -> Sequence[_pytest.reports.CollectReport | _pytest.reports.TestReport]:
405    def getfailures(
406        self,
407        names: str | Iterable[str] = (
408            "pytest_collectreport",
409            "pytest_runtest_logreport",
410        ),
411    ) -> Sequence[CollectReport | TestReport]:
412        return [rep for rep in self.getreports(names) if rep.failed]
def getfailedcollections(self) -> Sequence[_pytest.reports.CollectReport]:
414    def getfailedcollections(self) -> Sequence[CollectReport]:
415        return self.getfailures("pytest_collectreport")
def listoutcomes( self) -> tuple[Sequence[_pytest.reports.TestReport], Sequence[_pytest.reports.CollectReport | _pytest.reports.TestReport], Sequence[_pytest.reports.CollectReport | _pytest.reports.TestReport]]:
417    def listoutcomes(
418        self,
419    ) -> tuple[
420        Sequence[TestReport],
421        Sequence[CollectReport | TestReport],
422        Sequence[CollectReport | TestReport],
423    ]:
424        passed = []
425        skipped = []
426        failed = []
427        for rep in self.getreports(
428            ("pytest_collectreport", "pytest_runtest_logreport")
429        ):
430            if rep.passed:
431                if rep.when == "call":
432                    assert isinstance(rep, TestReport)
433                    passed.append(rep)
434            elif rep.skipped:
435                skipped.append(rep)
436            else:
437                assert rep.failed, f"Unexpected outcome: {rep!r}"
438                failed.append(rep)
439        return passed, skipped, failed
def countoutcomes(self) -> list[int]:
441    def countoutcomes(self) -> list[int]:
442        return [len(x) for x in self.listoutcomes()]
def assertoutcome(self, passed: int = 0, skipped: int = 0, failed: int = 0) -> None:
444    def assertoutcome(self, passed: int = 0, skipped: int = 0, failed: int = 0) -> None:
445        __tracebackhide__ = True
446        from _pytest.pytester_assertions import assertoutcome
447
448        outcomes = self.listoutcomes()
449        assertoutcome(
450            outcomes,
451            passed=passed,
452            skipped=skipped,
453            failed=failed,
454        )
def clear(self) -> None:
456    def clear(self) -> None:
457        self.calls[:] = []
class Item(_pytest.nodes.Node, abc.ABC):
656class Item(Node, abc.ABC):
657    """Base class of all test invocation items.
658
659    Note that for a single function there might be multiple test invocation items.
660    """
661
662    nextitem = None
663
664    def __init__(
665        self,
666        name,
667        parent=None,
668        config: Config | None = None,
669        session: Session | None = None,
670        nodeid: str | None = None,
671        **kw,
672    ) -> None:
673        # The first two arguments are intentionally passed positionally,
674        # to keep plugins who define a node type which inherits from
675        # (pytest.Item, pytest.File) working (see issue #8435).
676        # They can be made kwargs when the deprecation above is done.
677        super().__init__(
678            name,
679            parent,
680            config=config,
681            session=session,
682            nodeid=nodeid,
683            **kw,
684        )
685        self._report_sections: list[tuple[str, str, str]] = []
686
687        #: A list of tuples (name, value) that holds user defined properties
688        #: for this test.
689        self.user_properties: list[tuple[str, object]] = []
690
691        self._check_item_and_collector_diamond_inheritance()
692
693    def _check_item_and_collector_diamond_inheritance(self) -> None:
694        """
695        Check if the current type inherits from both File and Collector
696        at the same time, emitting a warning accordingly (#8447).
697        """
698        cls = type(self)
699
700        # We inject an attribute in the type to avoid issuing this warning
701        # for the same class more than once, which is not helpful.
702        # It is a hack, but was deemed acceptable in order to avoid
703        # flooding the user in the common case.
704        attr_name = "_pytest_diamond_inheritance_warning_shown"
705        if getattr(cls, attr_name, False):
706            return
707        setattr(cls, attr_name, True)
708
709        problems = ", ".join(
710            base.__name__ for base in cls.__bases__ if issubclass(base, Collector)
711        )
712        if problems:
713            warnings.warn(
714                f"{cls.__name__} is an Item subclass and should not be a collector, "
715                f"however its bases {problems} are collectors.\n"
716                "Please split the Collectors and the Item into separate node types.\n"
717                "Pytest Doc example: https://docs.pytest.org/en/latest/example/nonpython.html\n"
718                "example pull request on a plugin: https://github.com/asmeurer/pytest-flakes/pull/40/",
719                PytestWarning,
720            )
721
722    @abc.abstractmethod
723    def runtest(self) -> None:
724        """Run the test case for this item.
725
726        Must be implemented by subclasses.
727
728        .. seealso:: :ref:`non-python tests`
729        """
730        raise NotImplementedError("runtest must be implemented by Item subclass")
731
732    def add_report_section(self, when: str, key: str, content: str) -> None:
733        """Add a new report section, similar to what's done internally to add
734        stdout and stderr captured output::
735
736            item.add_report_section("call", "stdout", "report section contents")
737
738        :param str when:
739            One of the possible capture states, ``"setup"``, ``"call"``, ``"teardown"``.
740        :param str key:
741            Name of the section, can be customized at will. Pytest uses ``"stdout"`` and
742            ``"stderr"`` internally.
743        :param str content:
744            The full contents as a string.
745        """
746        if content:
747            self._report_sections.append((when, key, content))
748
749    def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]:
750        """Get location information for this item for test reports.
751
752        Returns a tuple with three elements:
753
754        - The path of the test (default ``self.path``)
755        - The 0-based line number of the test (default ``None``)
756        - A name of the test to be shown (default ``""``)
757
758        .. seealso:: :ref:`non-python tests`
759        """
760        return self.path, None, ""
761
762    @cached_property
763    def location(self) -> tuple[str, int | None, str]:
764        """
765        Returns a tuple of ``(relfspath, lineno, testname)`` for this item
766        where ``relfspath`` is file path relative to ``config.rootpath``
767        and lineno is a 0-based line number.
768        """
769        location = self.reportinfo()
770        path = absolutepath(location[0])
771        relfspath = self.session._node_location_to_relpath(path)
772        assert type(location[2]) is str
773        return (relfspath, location[1], location[2])

Base class of all test invocation items.

Note that for a single function there might be multiple test invocation items.

nextitem = None
user_properties: list[tuple[str, object]]
@abc.abstractmethod
def runtest(self) -> None:
722    @abc.abstractmethod
723    def runtest(self) -> None:
724        """Run the test case for this item.
725
726        Must be implemented by subclasses.
727
728        .. seealso:: :ref:`non-python tests`
729        """
730        raise NotImplementedError("runtest must be implemented by Item subclass")

Run the test case for this item.

Must be implemented by subclasses.

seealso :ref:non-python tests.

def add_report_section(self, when: str, key: str, content: str) -> None:
732    def add_report_section(self, when: str, key: str, content: str) -> None:
733        """Add a new report section, similar to what's done internally to add
734        stdout and stderr captured output::
735
736            item.add_report_section("call", "stdout", "report section contents")
737
738        :param str when:
739            One of the possible capture states, ``"setup"``, ``"call"``, ``"teardown"``.
740        :param str key:
741            Name of the section, can be customized at will. Pytest uses ``"stdout"`` and
742            ``"stderr"`` internally.
743        :param str content:
744            The full contents as a string.
745        """
746        if content:
747            self._report_sections.append((when, key, content))

Add a new report section, similar to what's done internally to add stdout and stderr captured output::

item.add_report_section("call", "stdout", "report section contents")
Parameters
  • str when: One of the possible capture states, "setup", "call", "teardown".
  • str key: Name of the section, can be customized at will. Pytest uses "stdout" and "stderr" internally.
  • str content: The full contents as a string.
def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]:
749    def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]:
750        """Get location information for this item for test reports.
751
752        Returns a tuple with three elements:
753
754        - The path of the test (default ``self.path``)
755        - The 0-based line number of the test (default ``None``)
756        - A name of the test to be shown (default ``""``)
757
758        .. seealso:: :ref:`non-python tests`
759        """
760        return self.path, None, ""

Get location information for this item for test reports.

Returns a tuple with three elements:

  • The path of the test (default self.path)
  • The 0-based line number of the test (default None)
  • A name of the test to be shown (default "")

seealso :ref:non-python tests.

location: tuple[str, int | None, str]
762    @cached_property
763    def location(self) -> tuple[str, int | None, str]:
764        """
765        Returns a tuple of ``(relfspath, lineno, testname)`` for this item
766        where ``relfspath`` is file path relative to ``config.rootpath``
767        and lineno is a 0-based line number.
768        """
769        location = self.reportinfo()
770        path = absolutepath(location[0])
771        relfspath = self.session._node_location_to_relpath(path)
772        assert type(location[2]) is str
773        return (relfspath, location[1], location[2])

Returns a tuple of (relfspath, lineno, testname) for this item where relfspath is file path relative to config.rootpath and lineno is a 0-based line number.

class LineMatcher:
1553class LineMatcher:
1554    """Flexible matching of text.
1555
1556    This is a convenience class to test large texts like the output of
1557    commands.
1558
1559    The constructor takes a list of lines without their trailing newlines, i.e.
1560    ``text.splitlines()``.
1561    """
1562
1563    def __init__(self, lines: list[str]) -> None:
1564        self.lines = lines
1565        self._log_output: list[str] = []
1566
1567    def __str__(self) -> str:
1568        """Return the entire original text.
1569
1570        .. versionadded:: 6.2
1571            You can use :meth:`str` in older versions.
1572        """
1573        return "\n".join(self.lines)
1574
1575    def _getlines(self, lines2: str | Sequence[str] | Source) -> Sequence[str]:
1576        if isinstance(lines2, str):
1577            lines2 = Source(lines2)
1578        if isinstance(lines2, Source):
1579            lines2 = lines2.strip().lines
1580        return lines2
1581
1582    def fnmatch_lines_random(self, lines2: Sequence[str]) -> None:
1583        """Check lines exist in the output in any order (using :func:`python:fnmatch.fnmatch`)."""
1584        __tracebackhide__ = True
1585        self._match_lines_random(lines2, fnmatch)
1586
1587    def re_match_lines_random(self, lines2: Sequence[str]) -> None:
1588        """Check lines exist in the output in any order (using :func:`python:re.match`)."""
1589        __tracebackhide__ = True
1590        self._match_lines_random(lines2, lambda name, pat: bool(re.match(pat, name)))
1591
1592    def _match_lines_random(
1593        self, lines2: Sequence[str], match_func: Callable[[str, str], bool]
1594    ) -> None:
1595        __tracebackhide__ = True
1596        lines2 = self._getlines(lines2)
1597        for line in lines2:
1598            for x in self.lines:
1599                if line == x or match_func(x, line):
1600                    self._log("matched: ", repr(line))
1601                    break
1602            else:
1603                msg = f"line {line!r} not found in output"
1604                self._log(msg)
1605                self._fail(msg)
1606
1607    def get_lines_after(self, fnline: str) -> Sequence[str]:
1608        """Return all lines following the given line in the text.
1609
1610        The given line can contain glob wildcards.
1611        """
1612        for i, line in enumerate(self.lines):
1613            if fnline == line or fnmatch(line, fnline):
1614                return self.lines[i + 1 :]
1615        raise ValueError(f"line {fnline!r} not found in output")
1616
1617    def _log(self, *args) -> None:
1618        self._log_output.append(" ".join(str(x) for x in args))
1619
1620    @property
1621    def _log_text(self) -> str:
1622        return "\n".join(self._log_output)
1623
1624    def fnmatch_lines(
1625        self, lines2: Sequence[str], *, consecutive: bool = False
1626    ) -> None:
1627        """Check lines exist in the output (using :func:`python:fnmatch.fnmatch`).
1628
1629        The argument is a list of lines which have to match and can use glob
1630        wildcards.  If they do not match a pytest.fail() is called.  The
1631        matches and non-matches are also shown as part of the error message.
1632
1633        :param lines2: String patterns to match.
1634        :param consecutive: Match lines consecutively?
1635        """
1636        __tracebackhide__ = True
1637        self._match_lines(lines2, fnmatch, "fnmatch", consecutive=consecutive)
1638
1639    def re_match_lines(
1640        self, lines2: Sequence[str], *, consecutive: bool = False
1641    ) -> None:
1642        """Check lines exist in the output (using :func:`python:re.match`).
1643
1644        The argument is a list of lines which have to match using ``re.match``.
1645        If they do not match a pytest.fail() is called.
1646
1647        The matches and non-matches are also shown as part of the error message.
1648
1649        :param lines2: string patterns to match.
1650        :param consecutive: match lines consecutively?
1651        """
1652        __tracebackhide__ = True
1653        self._match_lines(
1654            lines2,
1655            lambda name, pat: bool(re.match(pat, name)),
1656            "re.match",
1657            consecutive=consecutive,
1658        )
1659
1660    def _match_lines(
1661        self,
1662        lines2: Sequence[str],
1663        match_func: Callable[[str, str], bool],
1664        match_nickname: str,
1665        *,
1666        consecutive: bool = False,
1667    ) -> None:
1668        """Underlying implementation of ``fnmatch_lines`` and ``re_match_lines``.
1669
1670        :param Sequence[str] lines2:
1671            List of string patterns to match. The actual format depends on
1672            ``match_func``.
1673        :param match_func:
1674            A callable ``match_func(line, pattern)`` where line is the
1675            captured line from stdout/stderr and pattern is the matching
1676            pattern.
1677        :param str match_nickname:
1678            The nickname for the match function that will be logged to stdout
1679            when a match occurs.
1680        :param consecutive:
1681            Match lines consecutively?
1682        """
1683        if not isinstance(lines2, collections.abc.Sequence):
1684            raise TypeError(f"invalid type for lines2: {type(lines2).__name__}")
1685        lines2 = self._getlines(lines2)
1686        lines1 = self.lines[:]
1687        extralines = []
1688        __tracebackhide__ = True
1689        wnick = len(match_nickname) + 1
1690        started = False
1691        for line in lines2:
1692            nomatchprinted = False
1693            while lines1:
1694                nextline = lines1.pop(0)
1695                if line == nextline:
1696                    self._log("exact match:", repr(line))
1697                    started = True
1698                    break
1699                elif match_func(nextline, line):
1700                    self._log(f"{match_nickname}:", repr(line))
1701                    self._log(
1702                        "{:>{width}}".format("with:", width=wnick), repr(nextline)
1703                    )
1704                    started = True
1705                    break
1706                else:
1707                    if consecutive and started:
1708                        msg = f"no consecutive match: {line!r}"
1709                        self._log(msg)
1710                        self._log(
1711                            "{:>{width}}".format("with:", width=wnick), repr(nextline)
1712                        )
1713                        self._fail(msg)
1714                    if not nomatchprinted:
1715                        self._log(
1716                            "{:>{width}}".format("nomatch:", width=wnick), repr(line)
1717                        )
1718                        nomatchprinted = True
1719                    self._log("{:>{width}}".format("and:", width=wnick), repr(nextline))
1720                extralines.append(nextline)
1721            else:
1722                msg = f"remains unmatched: {line!r}"
1723                self._log(msg)
1724                self._fail(msg)
1725        self._log_output = []
1726
1727    def no_fnmatch_line(self, pat: str) -> None:
1728        """Ensure captured lines do not match the given pattern, using ``fnmatch.fnmatch``.
1729
1730        :param str pat: The pattern to match lines.
1731        """
1732        __tracebackhide__ = True
1733        self._no_match_line(pat, fnmatch, "fnmatch")
1734
1735    def no_re_match_line(self, pat: str) -> None:
1736        """Ensure captured lines do not match the given pattern, using ``re.match``.
1737
1738        :param str pat: The regular expression to match lines.
1739        """
1740        __tracebackhide__ = True
1741        self._no_match_line(
1742            pat, lambda name, pat: bool(re.match(pat, name)), "re.match"
1743        )
1744
1745    def _no_match_line(
1746        self, pat: str, match_func: Callable[[str, str], bool], match_nickname: str
1747    ) -> None:
1748        """Ensure captured lines does not have a the given pattern, using ``fnmatch.fnmatch``.
1749
1750        :param str pat: The pattern to match lines.
1751        """
1752        __tracebackhide__ = True
1753        nomatch_printed = False
1754        wnick = len(match_nickname) + 1
1755        for line in self.lines:
1756            if match_func(line, pat):
1757                msg = f"{match_nickname}: {pat!r}"
1758                self._log(msg)
1759                self._log("{:>{width}}".format("with:", width=wnick), repr(line))
1760                self._fail(msg)
1761            else:
1762                if not nomatch_printed:
1763                    self._log("{:>{width}}".format("nomatch:", width=wnick), repr(pat))
1764                    nomatch_printed = True
1765                self._log("{:>{width}}".format("and:", width=wnick), repr(line))
1766        self._log_output = []
1767
1768    def _fail(self, msg: str) -> None:
1769        __tracebackhide__ = True
1770        log_text = self._log_text
1771        self._log_output = []
1772        fail(log_text)
1773
1774    def str(self) -> str:
1775        """Return the entire original text."""
1776        return str(self)

Flexible matching of text.

This is a convenience class to test large texts like the output of commands.

The constructor takes a list of lines without their trailing newlines, i.e. text.splitlines().

LineMatcher(lines: list[_pytest.pytester.LineMatcher.str])
1563    def __init__(self, lines: list[str]) -> None:
1564        self.lines = lines
1565        self._log_output: list[str] = []
lines
def fnmatch_lines_random(self, lines2: Sequence[_pytest.pytester.LineMatcher.str]) -> None:
1582    def fnmatch_lines_random(self, lines2: Sequence[str]) -> None:
1583        """Check lines exist in the output in any order (using :func:`python:fnmatch.fnmatch`)."""
1584        __tracebackhide__ = True
1585        self._match_lines_random(lines2, fnmatch)

Check lines exist in the output in any order (using python:fnmatch.fnmatch()).

def re_match_lines_random(self, lines2: Sequence[_pytest.pytester.LineMatcher.str]) -> None:
1587    def re_match_lines_random(self, lines2: Sequence[str]) -> None:
1588        """Check lines exist in the output in any order (using :func:`python:re.match`)."""
1589        __tracebackhide__ = True
1590        self._match_lines_random(lines2, lambda name, pat: bool(re.match(pat, name)))

Check lines exist in the output in any order (using python:re.match()).

def get_lines_after( self, fnline: <function LineMatcher.str>) -> Sequence[_pytest.pytester.LineMatcher.str]:
1607    def get_lines_after(self, fnline: str) -> Sequence[str]:
1608        """Return all lines following the given line in the text.
1609
1610        The given line can contain glob wildcards.
1611        """
1612        for i, line in enumerate(self.lines):
1613            if fnline == line or fnmatch(line, fnline):
1614                return self.lines[i + 1 :]
1615        raise ValueError(f"line {fnline!r} not found in output")

Return all lines following the given line in the text.

The given line can contain glob wildcards.

def fnmatch_lines( self, lines2: Sequence[_pytest.pytester.LineMatcher.str], *, consecutive: bool = False) -> None:
1624    def fnmatch_lines(
1625        self, lines2: Sequence[str], *, consecutive: bool = False
1626    ) -> None:
1627        """Check lines exist in the output (using :func:`python:fnmatch.fnmatch`).
1628
1629        The argument is a list of lines which have to match and can use glob
1630        wildcards.  If they do not match a pytest.fail() is called.  The
1631        matches and non-matches are also shown as part of the error message.
1632
1633        :param lines2: String patterns to match.
1634        :param consecutive: Match lines consecutively?
1635        """
1636        __tracebackhide__ = True
1637        self._match_lines(lines2, fnmatch, "fnmatch", consecutive=consecutive)

Check lines exist in the output (using python:fnmatch.fnmatch()).

The argument is a list of lines which have to match and can use glob wildcards. If they do not match a pytest.fail() is called. The matches and non-matches are also shown as part of the error message.

Parameters
  • lines2: String patterns to match.
  • consecutive: Match lines consecutively?
def re_match_lines( self, lines2: Sequence[_pytest.pytester.LineMatcher.str], *, consecutive: bool = False) -> None:
1639    def re_match_lines(
1640        self, lines2: Sequence[str], *, consecutive: bool = False
1641    ) -> None:
1642        """Check lines exist in the output (using :func:`python:re.match`).
1643
1644        The argument is a list of lines which have to match using ``re.match``.
1645        If they do not match a pytest.fail() is called.
1646
1647        The matches and non-matches are also shown as part of the error message.
1648
1649        :param lines2: string patterns to match.
1650        :param consecutive: match lines consecutively?
1651        """
1652        __tracebackhide__ = True
1653        self._match_lines(
1654            lines2,
1655            lambda name, pat: bool(re.match(pat, name)),
1656            "re.match",
1657            consecutive=consecutive,
1658        )

Check lines exist in the output (using python:re.match()).

The argument is a list of lines which have to match using re.match. If they do not match a pytest.fail() is called.

The matches and non-matches are also shown as part of the error message.

Parameters
  • lines2: string patterns to match.
  • consecutive: match lines consecutively?
def no_fnmatch_line(self, pat: <function LineMatcher.str>) -> None:
1727    def no_fnmatch_line(self, pat: str) -> None:
1728        """Ensure captured lines do not match the given pattern, using ``fnmatch.fnmatch``.
1729
1730        :param str pat: The pattern to match lines.
1731        """
1732        __tracebackhide__ = True
1733        self._no_match_line(pat, fnmatch, "fnmatch")

Ensure captured lines do not match the given pattern, using fnmatch.fnmatch.

Parameters
  • str pat: The pattern to match lines.
def no_re_match_line(self, pat: <function LineMatcher.str>) -> None:
1735    def no_re_match_line(self, pat: str) -> None:
1736        """Ensure captured lines do not match the given pattern, using ``re.match``.
1737
1738        :param str pat: The regular expression to match lines.
1739        """
1740        __tracebackhide__ = True
1741        self._no_match_line(
1742            pat, lambda name, pat: bool(re.match(pat, name)), "re.match"
1743        )

Ensure captured lines do not match the given pattern, using re.match.

Parameters
  • str pat: The regular expression to match lines.
def str(self) -> <function LineMatcher.str at 0x7f8fb0ba0d60>:
1774    def str(self) -> str:
1775        """Return the entire original text."""
1776        return str(self)

Return the entire original text.

@final
class LogCaptureFixture:
404@final
405class LogCaptureFixture:
406    """Provides access and control of log capturing."""
407
408    def __init__(self, item: nodes.Node, *, _ispytest: bool = False) -> None:
409        check_ispytest(_ispytest)
410        self._item = item
411        self._initial_handler_level: int | None = None
412        # Dict of log name -> log level.
413        self._initial_logger_levels: dict[str | None, int] = {}
414        self._initial_disabled_logging_level: int | None = None
415
416    def _finalize(self) -> None:
417        """Finalize the fixture.
418
419        This restores the log levels and the disabled logging levels changed by :meth:`set_level`.
420        """
421        # Restore log levels.
422        if self._initial_handler_level is not None:
423            self.handler.setLevel(self._initial_handler_level)
424        for logger_name, level in self._initial_logger_levels.items():
425            logger = logging.getLogger(logger_name)
426            logger.setLevel(level)
427        # Disable logging at the original disabled logging level.
428        if self._initial_disabled_logging_level is not None:
429            logging.disable(self._initial_disabled_logging_level)
430            self._initial_disabled_logging_level = None
431
432    @property
433    def handler(self) -> LogCaptureHandler:
434        """Get the logging handler used by the fixture."""
435        return self._item.stash[caplog_handler_key]
436
437    def get_records(
438        self, when: Literal["setup", "call", "teardown"]
439    ) -> list[logging.LogRecord]:
440        """Get the logging records for one of the possible test phases.
441
442        :param when:
443            Which test phase to obtain the records from.
444            Valid values are: "setup", "call" and "teardown".
445
446        :returns: The list of captured records at the given stage.
447
448        .. versionadded:: 3.4
449        """
450        return self._item.stash[caplog_records_key].get(when, [])
451
452    @property
453    def text(self) -> str:
454        """The formatted log text."""
455        return _remove_ansi_escape_sequences(self.handler.stream.getvalue())
456
457    @property
458    def records(self) -> list[logging.LogRecord]:
459        """The list of log records."""
460        return self.handler.records
461
462    @property
463    def record_tuples(self) -> list[tuple[str, int, str]]:
464        """A list of a stripped down version of log records intended
465        for use in assertion comparison.
466
467        The format of the tuple is:
468
469            (logger_name, log_level, message)
470        """
471        return [(r.name, r.levelno, r.getMessage()) for r in self.records]
472
473    @property
474    def messages(self) -> list[str]:
475        """A list of format-interpolated log messages.
476
477        Unlike 'records', which contains the format string and parameters for
478        interpolation, log messages in this list are all interpolated.
479
480        Unlike 'text', which contains the output from the handler, log
481        messages in this list are unadorned with levels, timestamps, etc,
482        making exact comparisons more reliable.
483
484        Note that traceback or stack info (from :func:`logging.exception` or
485        the `exc_info` or `stack_info` arguments to the logging functions) is
486        not included, as this is added by the formatter in the handler.
487
488        .. versionadded:: 3.7
489        """
490        return [r.getMessage() for r in self.records]
491
492    def clear(self) -> None:
493        """Reset the list of log records and the captured log text."""
494        self.handler.clear()
495
496    def _force_enable_logging(
497        self, level: int | str, logger_obj: logging.Logger
498    ) -> int:
499        """Enable the desired logging level if the global level was disabled via ``logging.disabled``.
500
501        Only enables logging levels greater than or equal to the requested ``level``.
502
503        Does nothing if the desired ``level`` wasn't disabled.
504
505        :param level:
506            The logger level caplog should capture.
507            All logging is enabled if a non-standard logging level string is supplied.
508            Valid level strings are in :data:`logging._nameToLevel`.
509        :param logger_obj: The logger object to check.
510
511        :return: The original disabled logging level.
512        """
513        original_disable_level: int = logger_obj.manager.disable
514
515        if isinstance(level, str):
516            # Try to translate the level string to an int for `logging.disable()`
517            level = logging.getLevelName(level)
518
519        if not isinstance(level, int):
520            # The level provided was not valid, so just un-disable all logging.
521            logging.disable(logging.NOTSET)
522        elif not logger_obj.isEnabledFor(level):
523            # Each level is `10` away from other levels.
524            # https://docs.python.org/3/library/logging.html#logging-levels
525            disable_level = max(level - 10, logging.NOTSET)
526            logging.disable(disable_level)
527
528        return original_disable_level
529
530    def set_level(self, level: int | str, logger: str | None = None) -> None:
531        """Set the threshold level of a logger for the duration of a test.
532
533        Logging messages which are less severe than this level will not be captured.
534
535        .. versionchanged:: 3.4
536            The levels of the loggers changed by this function will be
537            restored to their initial values at the end of the test.
538
539        Will enable the requested logging level if it was disabled via :func:`logging.disable`.
540
541        :param level: The level.
542        :param logger: The logger to update. If not given, the root logger.
543        """
544        logger_obj = logging.getLogger(logger)
545        # Save the original log-level to restore it during teardown.
546        self._initial_logger_levels.setdefault(logger, logger_obj.level)
547        logger_obj.setLevel(level)
548        if self._initial_handler_level is None:
549            self._initial_handler_level = self.handler.level
550        self.handler.setLevel(level)
551        initial_disabled_logging_level = self._force_enable_logging(level, logger_obj)
552        if self._initial_disabled_logging_level is None:
553            self._initial_disabled_logging_level = initial_disabled_logging_level
554
555    @contextmanager
556    def at_level(self, level: int | str, logger: str | None = None) -> Generator[None]:
557        """Context manager that sets the level for capturing of logs. After
558        the end of the 'with' statement the level is restored to its original
559        value.
560
561        Will enable the requested logging level if it was disabled via :func:`logging.disable`.
562
563        :param level: The level.
564        :param logger: The logger to update. If not given, the root logger.
565        """
566        logger_obj = logging.getLogger(logger)
567        orig_level = logger_obj.level
568        logger_obj.setLevel(level)
569        handler_orig_level = self.handler.level
570        self.handler.setLevel(level)
571        original_disable_level = self._force_enable_logging(level, logger_obj)
572        try:
573            yield
574        finally:
575            logger_obj.setLevel(orig_level)
576            self.handler.setLevel(handler_orig_level)
577            logging.disable(original_disable_level)
578
579    @contextmanager
580    def filtering(self, filter_: logging.Filter) -> Generator[None]:
581        """Context manager that temporarily adds the given filter to the caplog's
582        :meth:`handler` for the 'with' statement block, and removes that filter at the
583        end of the block.
584
585        :param filter_: A custom :class:`logging.Filter` object.
586
587        .. versionadded:: 7.5
588        """
589        self.handler.addFilter(filter_)
590        try:
591            yield
592        finally:
593            self.handler.removeFilter(filter_)

Provides access and control of log capturing.

LogCaptureFixture(item: _pytest.nodes.Node, *, _ispytest: bool = False)
408    def __init__(self, item: nodes.Node, *, _ispytest: bool = False) -> None:
409        check_ispytest(_ispytest)
410        self._item = item
411        self._initial_handler_level: int | None = None
412        # Dict of log name -> log level.
413        self._initial_logger_levels: dict[str | None, int] = {}
414        self._initial_disabled_logging_level: int | None = None
handler: _pytest.logging.LogCaptureHandler
432    @property
433    def handler(self) -> LogCaptureHandler:
434        """Get the logging handler used by the fixture."""
435        return self._item.stash[caplog_handler_key]

Get the logging handler used by the fixture.

def get_records( self, when: Literal['setup', 'call', 'teardown']) -> list[logging.LogRecord]:
437    def get_records(
438        self, when: Literal["setup", "call", "teardown"]
439    ) -> list[logging.LogRecord]:
440        """Get the logging records for one of the possible test phases.
441
442        :param when:
443            Which test phase to obtain the records from.
444            Valid values are: "setup", "call" and "teardown".
445
446        :returns: The list of captured records at the given stage.
447
448        .. versionadded:: 3.4
449        """
450        return self._item.stash[caplog_records_key].get(when, [])

Get the logging records for one of the possible test phases.

Parameters
  • when: Which test phase to obtain the records from. Valid values are: "setup", "call" and "teardown".

:returns: The list of captured records at the given stage.

New in version 3.4.

text: str
452    @property
453    def text(self) -> str:
454        """The formatted log text."""
455        return _remove_ansi_escape_sequences(self.handler.stream.getvalue())

The formatted log text.

records: list[logging.LogRecord]
457    @property
458    def records(self) -> list[logging.LogRecord]:
459        """The list of log records."""
460        return self.handler.records

The list of log records.

record_tuples: list[tuple[str, int, str]]
462    @property
463    def record_tuples(self) -> list[tuple[str, int, str]]:
464        """A list of a stripped down version of log records intended
465        for use in assertion comparison.
466
467        The format of the tuple is:
468
469            (logger_name, log_level, message)
470        """
471        return [(r.name, r.levelno, r.getMessage()) for r in self.records]

A list of a stripped down version of log records intended for use in assertion comparison.

The format of the tuple is:

(logger_name, log_level, message)

messages: list[str]
473    @property
474    def messages(self) -> list[str]:
475        """A list of format-interpolated log messages.
476
477        Unlike 'records', which contains the format string and parameters for
478        interpolation, log messages in this list are all interpolated.
479
480        Unlike 'text', which contains the output from the handler, log
481        messages in this list are unadorned with levels, timestamps, etc,
482        making exact comparisons more reliable.
483
484        Note that traceback or stack info (from :func:`logging.exception` or
485        the `exc_info` or `stack_info` arguments to the logging functions) is
486        not included, as this is added by the formatter in the handler.
487
488        .. versionadded:: 3.7
489        """
490        return [r.getMessage() for r in self.records]

A list of format-interpolated log messages.

Unlike 'records', which contains the format string and parameters for interpolation, log messages in this list are all interpolated.

Unlike 'text', which contains the output from the handler, log messages in this list are unadorned with levels, timestamps, etc, making exact comparisons more reliable.

Note that traceback or stack info (from logging.exception() or the exc_info or stack_info arguments to the logging functions) is not included, as this is added by the formatter in the handler.

New in version 3.7.

def clear(self) -> None:
492    def clear(self) -> None:
493        """Reset the list of log records and the captured log text."""
494        self.handler.clear()

Reset the list of log records and the captured log text.

def set_level(self, level: int | str, logger: str | None = None) -> None:
530    def set_level(self, level: int | str, logger: str | None = None) -> None:
531        """Set the threshold level of a logger for the duration of a test.
532
533        Logging messages which are less severe than this level will not be captured.
534
535        .. versionchanged:: 3.4
536            The levels of the loggers changed by this function will be
537            restored to their initial values at the end of the test.
538
539        Will enable the requested logging level if it was disabled via :func:`logging.disable`.
540
541        :param level: The level.
542        :param logger: The logger to update. If not given, the root logger.
543        """
544        logger_obj = logging.getLogger(logger)
545        # Save the original log-level to restore it during teardown.
546        self._initial_logger_levels.setdefault(logger, logger_obj.level)
547        logger_obj.setLevel(level)
548        if self._initial_handler_level is None:
549            self._initial_handler_level = self.handler.level
550        self.handler.setLevel(level)
551        initial_disabled_logging_level = self._force_enable_logging(level, logger_obj)
552        if self._initial_disabled_logging_level is None:
553            self._initial_disabled_logging_level = initial_disabled_logging_level

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.

Changed in version 3.4: The levels of the loggers changed by this function will be restored to their initial values at the end of the test.

Will enable the requested logging level if it was disabled via logging.disable().

Parameters
  • level: The level.
  • logger: The logger to update. If not given, the root logger.
@contextmanager
def at_level(self, level: int | str, logger: str | None = None) -> Generator[None]:
555    @contextmanager
556    def at_level(self, level: int | str, logger: str | None = None) -> Generator[None]:
557        """Context manager that sets the level for capturing of logs. After
558        the end of the 'with' statement the level is restored to its original
559        value.
560
561        Will enable the requested logging level if it was disabled via :func:`logging.disable`.
562
563        :param level: The level.
564        :param logger: The logger to update. If not given, the root logger.
565        """
566        logger_obj = logging.getLogger(logger)
567        orig_level = logger_obj.level
568        logger_obj.setLevel(level)
569        handler_orig_level = self.handler.level
570        self.handler.setLevel(level)
571        original_disable_level = self._force_enable_logging(level, logger_obj)
572        try:
573            yield
574        finally:
575            logger_obj.setLevel(orig_level)
576            self.handler.setLevel(handler_orig_level)
577            logging.disable(original_disable_level)

Context manager that sets the level for capturing of logs. After the end of the 'with' statement the level is restored to its original value.

Will enable the requested logging level if it was disabled via logging.disable().

Parameters
  • level: The level.
  • logger: The logger to update. If not given, the root logger.
@contextmanager
def filtering(self, filter_: logging.Filter) -> Generator[None]:
579    @contextmanager
580    def filtering(self, filter_: logging.Filter) -> Generator[None]:
581        """Context manager that temporarily adds the given filter to the caplog's
582        :meth:`handler` for the 'with' statement block, and removes that filter at the
583        end of the block.
584
585        :param filter_: A custom :class:`logging.Filter` object.
586
587        .. versionadded:: 7.5
588        """
589        self.handler.addFilter(filter_)
590        try:
591            yield
592        finally:
593            self.handler.removeFilter(filter_)

Context manager that temporarily adds the given filter to the caplog's handler() for the 'with' statement block, and removes that filter at the end of the block.

Parameters

New in version 7.5.

@final
@dataclasses.dataclass(frozen=True)
class Mark:
234@final
235@dataclasses.dataclass(frozen=True)
236class Mark:
237    """A pytest mark."""
238
239    #: Name of the mark.
240    name: str
241    #: Positional arguments of the mark decorator.
242    args: tuple[Any, ...]
243    #: Keyword arguments of the mark decorator.
244    kwargs: Mapping[str, Any]
245
246    #: Source Mark for ids with parametrize Marks.
247    _param_ids_from: Mark | None = dataclasses.field(default=None, repr=False)
248    #: Resolved/generated ids with parametrize Marks.
249    _param_ids_generated: Sequence[str] | None = dataclasses.field(
250        default=None, repr=False
251    )
252
253    def __init__(
254        self,
255        name: str,
256        args: tuple[Any, ...],
257        kwargs: Mapping[str, Any],
258        param_ids_from: Mark | None = None,
259        param_ids_generated: Sequence[str] | None = None,
260        *,
261        _ispytest: bool = False,
262    ) -> None:
263        """:meta private:"""
264        check_ispytest(_ispytest)
265        # Weirdness to bypass frozen=True.
266        object.__setattr__(self, "name", name)
267        object.__setattr__(self, "args", args)
268        object.__setattr__(self, "kwargs", kwargs)
269        object.__setattr__(self, "_param_ids_from", param_ids_from)
270        object.__setattr__(self, "_param_ids_generated", param_ids_generated)
271
272    def _has_param_ids(self) -> bool:
273        return "ids" in self.kwargs or len(self.args) >= 4
274
275    def combined_with(self, other: Mark) -> Mark:
276        """Return a new Mark which is a combination of this
277        Mark and another Mark.
278
279        Combines by appending args and merging kwargs.
280
281        :param Mark other: The mark to combine with.
282        :rtype: Mark
283        """
284        assert self.name == other.name
285
286        # Remember source of ids with parametrize Marks.
287        param_ids_from: Mark | None = None
288        if self.name == "parametrize":
289            if other._has_param_ids():
290                param_ids_from = other
291            elif self._has_param_ids():
292                param_ids_from = self
293
294        return Mark(
295            self.name,
296            self.args + other.args,
297            dict(self.kwargs, **other.kwargs),
298            param_ids_from=param_ids_from,
299            _ispytest=True,
300        )

A pytest mark.

Mark( name: str, args: tuple[typing.Any, ...], kwargs: Mapping[str, typing.Any], param_ids_from: _pytest.mark.structures.Mark | None = None, param_ids_generated: Sequence[str] | None = None, *, _ispytest: bool = False)
253    def __init__(
254        self,
255        name: str,
256        args: tuple[Any, ...],
257        kwargs: Mapping[str, Any],
258        param_ids_from: Mark | None = None,
259        param_ids_generated: Sequence[str] | None = None,
260        *,
261        _ispytest: bool = False,
262    ) -> None:
263        """:meta private:"""
264        check_ispytest(_ispytest)
265        # Weirdness to bypass frozen=True.
266        object.__setattr__(self, "name", name)
267        object.__setattr__(self, "args", args)
268        object.__setattr__(self, "kwargs", kwargs)
269        object.__setattr__(self, "_param_ids_from", param_ids_from)
270        object.__setattr__(self, "_param_ids_generated", param_ids_generated)

:meta private:

name: str
args: tuple[typing.Any, ...]
kwargs: Mapping[str, typing.Any]
def combined_with( self, other: _pytest.mark.structures.Mark) -> _pytest.mark.structures.Mark:
275    def combined_with(self, other: Mark) -> Mark:
276        """Return a new Mark which is a combination of this
277        Mark and another Mark.
278
279        Combines by appending args and merging kwargs.
280
281        :param Mark other: The mark to combine with.
282        :rtype: Mark
283        """
284        assert self.name == other.name
285
286        # Remember source of ids with parametrize Marks.
287        param_ids_from: Mark | None = None
288        if self.name == "parametrize":
289            if other._has_param_ids():
290                param_ids_from = other
291            elif self._has_param_ids():
292                param_ids_from = self
293
294        return Mark(
295            self.name,
296            self.args + other.args,
297            dict(self.kwargs, **other.kwargs),
298            param_ids_from=param_ids_from,
299            _ispytest=True,
300        )

Return a new Mark which is a combination of this Mark and another Mark.

Combines by appending args and merging kwargs.

Parameters
  • Mark other: The mark to combine with.
@dataclasses.dataclass
class MarkDecorator:
309@dataclasses.dataclass
310class MarkDecorator:
311    """A decorator for applying a mark on test functions and classes.
312
313    ``MarkDecorators`` are created with ``pytest.mark``::
314
315        mark1 = pytest.mark.NAME  # Simple MarkDecorator
316        mark2 = pytest.mark.NAME(name1=value)  # Parametrized MarkDecorator
317
318    and can then be applied as decorators to test functions::
319
320        @mark2
321        def test_function():
322            pass
323
324    When a ``MarkDecorator`` is called, it does the following:
325
326    1. If called with a single class as its only positional argument and no
327       additional keyword arguments, it attaches the mark to the class so it
328       gets applied automatically to all test cases found in that class.
329
330    2. If called with a single function as its only positional argument and
331       no additional keyword arguments, it attaches the mark to the function,
332       containing all the arguments already stored internally in the
333       ``MarkDecorator``.
334
335    3. When called in any other case, it returns a new ``MarkDecorator``
336       instance with the original ``MarkDecorator``'s content updated with
337       the arguments passed to this call.
338
339    Note: The rules above prevent a ``MarkDecorator`` from storing only a
340    single function or class reference as its positional argument with no
341    additional keyword or positional arguments. You can work around this by
342    using `with_args()`.
343    """
344
345    mark: Mark
346
347    def __init__(self, mark: Mark, *, _ispytest: bool = False) -> None:
348        """:meta private:"""
349        check_ispytest(_ispytest)
350        self.mark = mark
351
352    @property
353    def name(self) -> str:
354        """Alias for mark.name."""
355        return self.mark.name
356
357    @property
358    def args(self) -> tuple[Any, ...]:
359        """Alias for mark.args."""
360        return self.mark.args
361
362    @property
363    def kwargs(self) -> Mapping[str, Any]:
364        """Alias for mark.kwargs."""
365        return self.mark.kwargs
366
367    @property
368    def markname(self) -> str:
369        """:meta private:"""
370        return self.name  # for backward-compat (2.4.1 had this attr)
371
372    def with_args(self, *args: object, **kwargs: object) -> MarkDecorator:
373        """Return a MarkDecorator with extra arguments added.
374
375        Unlike calling the MarkDecorator, with_args() can be used even
376        if the sole argument is a callable/class.
377        """
378        mark = Mark(self.name, args, kwargs, _ispytest=True)
379        return MarkDecorator(self.mark.combined_with(mark), _ispytest=True)
380
381    # Type ignored because the overloads overlap with an incompatible
382    # return type. Not much we can do about that. Thankfully mypy picks
383    # the first match so it works out even if we break the rules.
384    @overload
385    def __call__(self, arg: Markable) -> Markable:  # type: ignore[overload-overlap]
386        pass
387
388    @overload
389    def __call__(self, *args: object, **kwargs: object) -> MarkDecorator:
390        pass
391
392    def __call__(self, *args: object, **kwargs: object):
393        """Call the MarkDecorator."""
394        if args and not kwargs:
395            func = args[0]
396            is_class = inspect.isclass(func)
397            # For staticmethods/classmethods, the marks are eventually fetched from the
398            # function object, not the descriptor, so unwrap.
399            unwrapped_func = func
400            if isinstance(func, (staticmethod, classmethod)):
401                unwrapped_func = func.__func__
402            if len(args) == 1 and (istestfunc(unwrapped_func) or is_class):
403                store_mark(unwrapped_func, self.mark, stacklevel=3)
404                return func
405        return self.with_args(*args, **kwargs)

A decorator for applying a mark on test functions and classes.

MarkDecorators are created with pytest.mark::

mark1 = pytest.mark.NAME  # Simple MarkDecorator
mark2 = pytest.mark.NAME(name1=value)  # Parametrized MarkDecorator

and can then be applied as decorators to test functions::

@mark2
def test_function():
    pass

When a MarkDecorator is called, it does the following:

  1. If called with a single class as its only positional argument and no additional keyword arguments, it attaches the mark to the class so it gets applied automatically to all test cases found in that class.

  2. If called with a single function as its only positional argument and no additional keyword arguments, it attaches the mark to the function, containing all the arguments already stored internally in the MarkDecorator.

  3. When called in any other case, it returns a new MarkDecorator instance with the original MarkDecorator's content updated with the arguments passed to this call.

Note: The rules above prevent a MarkDecorator from storing only a single function or class reference as its positional argument with no additional keyword or positional arguments. You can work around this by using with_args().

MarkDecorator(mark: _pytest.mark.structures.Mark, *, _ispytest: bool = False)
347    def __init__(self, mark: Mark, *, _ispytest: bool = False) -> None:
348        """:meta private:"""
349        check_ispytest(_ispytest)
350        self.mark = mark

:meta private:

mark: _pytest.mark.structures.Mark
name: str
352    @property
353    def name(self) -> str:
354        """Alias for mark.name."""
355        return self.mark.name

Alias for mark.name.

args: tuple[typing.Any, ...]
357    @property
358    def args(self) -> tuple[Any, ...]:
359        """Alias for mark.args."""
360        return self.mark.args

Alias for mark.args.

kwargs: Mapping[str, typing.Any]
362    @property
363    def kwargs(self) -> Mapping[str, Any]:
364        """Alias for mark.kwargs."""
365        return self.mark.kwargs

Alias for mark.kwargs.

markname: str
367    @property
368    def markname(self) -> str:
369        """:meta private:"""
370        return self.name  # for backward-compat (2.4.1 had this attr)

:meta private:

def with_args( self, *args: object, **kwargs: object) -> _pytest.mark.structures.MarkDecorator:
372    def with_args(self, *args: object, **kwargs: object) -> MarkDecorator:
373        """Return a MarkDecorator with extra arguments added.
374
375        Unlike calling the MarkDecorator, with_args() can be used even
376        if the sole argument is a callable/class.
377        """
378        mark = Mark(self.name, args, kwargs, _ispytest=True)
379        return MarkDecorator(self.mark.combined_with(mark), _ispytest=True)

Return a MarkDecorator with extra arguments added.

Unlike calling the MarkDecorator, with_args() can be used even if the sole argument is a callable/class.

@final
class MarkGenerator:
534@final
535class MarkGenerator:
536    """Factory for :class:`MarkDecorator` objects - exposed as
537    a ``pytest.mark`` singleton instance.
538
539    Example::
540
541         import pytest
542
543
544         @pytest.mark.slowtest
545         def test_function():
546             pass
547
548    applies a 'slowtest' :class:`Mark` on ``test_function``.
549    """
550
551    # See TYPE_CHECKING above.
552    if TYPE_CHECKING:
553        skip: _SkipMarkDecorator
554        skipif: _SkipifMarkDecorator
555        xfail: _XfailMarkDecorator
556        parametrize: _ParametrizeMarkDecorator
557        usefixtures: _UsefixturesMarkDecorator
558        filterwarnings: _FilterwarningsMarkDecorator
559
560    def __init__(self, *, _ispytest: bool = False) -> None:
561        check_ispytest(_ispytest)
562        self._config: Config | None = None
563        self._markers: set[str] = set()
564
565    def __getattr__(self, name: str) -> MarkDecorator:
566        """Generate a new :class:`MarkDecorator` with the given name."""
567        if name[0] == "_":
568            raise AttributeError("Marker name must NOT start with underscore")
569
570        if self._config is not None:
571            # We store a set of markers as a performance optimisation - if a mark
572            # name is in the set we definitely know it, but a mark may be known and
573            # not in the set.  We therefore start by updating the set!
574            if name not in self._markers:
575                for line in self._config.getini("markers"):
576                    # example lines: "skipif(condition): skip the given test if..."
577                    # or "hypothesis: tests which use Hypothesis", so to get the
578                    # marker name we split on both `:` and `(`.
579                    marker = line.split(":")[0].split("(")[0].strip()
580                    self._markers.add(marker)
581
582            # If the name is not in the set of known marks after updating,
583            # then it really is time to issue a warning or an error.
584            if name not in self._markers:
585                if self._config.option.strict_markers or self._config.option.strict:
586                    fail(
587                        f"{name!r} not found in `markers` configuration option",
588                        pytrace=False,
589                    )
590
591                # Raise a specific error for common misspellings of "parametrize".
592                if name in ["parameterize", "parametrise", "parameterise"]:
593                    __tracebackhide__ = True
594                    fail(f"Unknown '{name}' mark, did you mean 'parametrize'?")
595
596                warnings.warn(
597                    f"Unknown pytest.mark.{name} - is this a typo?  You can register "
598                    "custom marks to avoid this warning - for details, see "
599                    "https://docs.pytest.org/en/stable/how-to/mark.html",
600                    PytestUnknownMarkWarning,
601                    2,
602                )
603
604        return MarkDecorator(Mark(name, (), {}, _ispytest=True), _ispytest=True)

Factory for MarkDecorator objects - exposed as a pytest.mark singleton instance.

Example::

 import pytest


 @pytest.mark.slowtest
 def test_function():
     pass

applies a 'slowtest' Mark on test_function.

MarkGenerator(*, _ispytest: bool = False)
560    def __init__(self, *, _ispytest: bool = False) -> None:
561        check_ispytest(_ispytest)
562        self._config: Config | None = None
563        self._markers: set[str] = set()
@final
class Metafunc:
1118@final
1119class Metafunc:
1120    """Objects passed to the :hook:`pytest_generate_tests` hook.
1121
1122    They help to inspect a test function and to generate tests according to
1123    test configuration or values specified in the class or module where a
1124    test function is defined.
1125    """
1126
1127    def __init__(
1128        self,
1129        definition: FunctionDefinition,
1130        fixtureinfo: fixtures.FuncFixtureInfo,
1131        config: Config,
1132        cls=None,
1133        module=None,
1134        *,
1135        _ispytest: bool = False,
1136    ) -> None:
1137        check_ispytest(_ispytest)
1138
1139        #: Access to the underlying :class:`_pytest.python.FunctionDefinition`.
1140        self.definition = definition
1141
1142        #: Access to the :class:`pytest.Config` object for the test session.
1143        self.config = config
1144
1145        #: The module object where the test function is defined in.
1146        self.module = module
1147
1148        #: Underlying Python test function.
1149        self.function = definition.obj
1150
1151        #: Set of fixture names required by the test function.
1152        self.fixturenames = fixtureinfo.names_closure
1153
1154        #: Class object where the test function is defined in or ``None``.
1155        self.cls = cls
1156
1157        self._arg2fixturedefs = fixtureinfo.name2fixturedefs
1158
1159        # Result of parametrize().
1160        self._calls: list[CallSpec2] = []
1161
1162        self._params_directness: dict[str, Literal["indirect", "direct"]] = {}
1163
1164    def parametrize(
1165        self,
1166        argnames: str | Sequence[str],
1167        argvalues: Iterable[ParameterSet | Sequence[object] | object],
1168        indirect: bool | Sequence[str] = False,
1169        ids: Iterable[object | None] | Callable[[Any], object | None] | None = None,
1170        scope: _ScopeName | None = None,
1171        *,
1172        _param_mark: Mark | None = None,
1173    ) -> None:
1174        """Add new invocations to the underlying test function using the list
1175        of argvalues for the given argnames. Parametrization is performed
1176        during the collection phase. If you need to setup expensive resources
1177        see about setting ``indirect`` to do it at test setup time instead.
1178
1179        Can be called multiple times per test function (but only on different
1180        argument names), in which case each call parametrizes all previous
1181        parametrizations, e.g.
1182
1183        ::
1184
1185            unparametrized:         t
1186            parametrize ["x", "y"]: t[x], t[y]
1187            parametrize [1, 2]:     t[x-1], t[x-2], t[y-1], t[y-2]
1188
1189        :param argnames:
1190            A comma-separated string denoting one or more argument names, or
1191            a list/tuple of argument strings.
1192
1193        :param argvalues:
1194            The list of argvalues determines how often a test is invoked with
1195            different argument values.
1196
1197            If only one argname was specified argvalues is a list of values.
1198            If N argnames were specified, argvalues must be a list of
1199            N-tuples, where each tuple-element specifies a value for its
1200            respective argname.
1201
1202        :param indirect:
1203            A list of arguments' names (subset of argnames) or a boolean.
1204            If True the list contains all names from the argnames. Each
1205            argvalue corresponding to an argname in this list will
1206            be passed as request.param to its respective argname fixture
1207            function so that it can perform more expensive setups during the
1208            setup phase of a test rather than at collection time.
1209
1210        :param ids:
1211            Sequence of (or generator for) ids for ``argvalues``,
1212            or a callable to return part of the id for each argvalue.
1213
1214            With sequences (and generators like ``itertools.count()``) the
1215            returned ids should be of type ``string``, ``int``, ``float``,
1216            ``bool``, or ``None``.
1217            They are mapped to the corresponding index in ``argvalues``.
1218            ``None`` means to use the auto-generated id.
1219
1220            .. versionadded:: 8.4
1221                :ref:`hidden-param` means to hide the parameter set
1222                from the test name. Can only be used at most 1 time, as
1223                test names need to be unique.
1224
1225            If it is a callable it will be called for each entry in
1226            ``argvalues``, and the return value is used as part of the
1227            auto-generated id for the whole set (where parts are joined with
1228            dashes ("-")).
1229            This is useful to provide more specific ids for certain items, e.g.
1230            dates.  Returning ``None`` will use an auto-generated id.
1231
1232            If no ids are provided they will be generated automatically from
1233            the argvalues.
1234
1235        :param scope:
1236            If specified it denotes the scope of the parameters.
1237            The scope is used for grouping tests by parameter instances.
1238            It will also override any fixture-function defined scope, allowing
1239            to set a dynamic scope using test context or configuration.
1240        """
1241        nodeid = self.definition.nodeid
1242
1243        argnames, parametersets = ParameterSet._for_parametrize(
1244            argnames,
1245            argvalues,
1246            self.function,
1247            self.config,
1248            nodeid=self.definition.nodeid,
1249        )
1250        del argvalues
1251
1252        if "request" in argnames:
1253            fail(
1254                f"{nodeid}: 'request' is a reserved name and cannot be used in @pytest.mark.parametrize",
1255                pytrace=False,
1256            )
1257
1258        if scope is not None:
1259            scope_ = Scope.from_user(
1260                scope, descr=f"parametrize() call in {self.function.__name__}"
1261            )
1262        else:
1263            scope_ = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
1264
1265        self._validate_if_using_arg_names(argnames, indirect)
1266
1267        # Use any already (possibly) generated ids with parametrize Marks.
1268        if _param_mark and _param_mark._param_ids_from:
1269            generated_ids = _param_mark._param_ids_from._param_ids_generated
1270            if generated_ids is not None:
1271                ids = generated_ids
1272
1273        ids = self._resolve_parameter_set_ids(
1274            argnames, ids, parametersets, nodeid=self.definition.nodeid
1275        )
1276
1277        # Store used (possibly generated) ids with parametrize Marks.
1278        if _param_mark and _param_mark._param_ids_from and generated_ids is None:
1279            object.__setattr__(_param_mark._param_ids_from, "_param_ids_generated", ids)
1280
1281        # Calculate directness.
1282        arg_directness = self._resolve_args_directness(argnames, indirect)
1283        self._params_directness.update(arg_directness)
1284
1285        # Add direct parametrizations as fixturedefs to arg2fixturedefs by
1286        # registering artificial "pseudo" FixtureDef's such that later at test
1287        # setup time we can rely on FixtureDefs to exist for all argnames.
1288        node = None
1289        # For scopes higher than function, a "pseudo" FixtureDef might have
1290        # already been created for the scope. We thus store and cache the
1291        # FixtureDef on the node related to the scope.
1292        if scope_ is Scope.Function:
1293            name2pseudofixturedef = None
1294        else:
1295            collector = self.definition.parent
1296            assert collector is not None
1297            node = get_scope_node(collector, scope_)
1298            if node is None:
1299                # If used class scope and there is no class, use module-level
1300                # collector (for now).
1301                if scope_ is Scope.Class:
1302                    assert isinstance(collector, Module)
1303                    node = collector
1304                # If used package scope and there is no package, use session
1305                # (for now).
1306                elif scope_ is Scope.Package:
1307                    node = collector.session
1308                else:
1309                    assert False, f"Unhandled missing scope: {scope}"
1310            default: dict[str, FixtureDef[Any]] = {}
1311            name2pseudofixturedef = node.stash.setdefault(
1312                name2pseudofixturedef_key, default
1313            )
1314        for argname in argnames:
1315            if arg_directness[argname] == "indirect":
1316                continue
1317            if name2pseudofixturedef is not None and argname in name2pseudofixturedef:
1318                fixturedef = name2pseudofixturedef[argname]
1319            else:
1320                fixturedef = FixtureDef(
1321                    config=self.config,
1322                    baseid="",
1323                    argname=argname,
1324                    func=get_direct_param_fixture_func,
1325                    scope=scope_,
1326                    params=None,
1327                    ids=None,
1328                    _ispytest=True,
1329                )
1330                if name2pseudofixturedef is not None:
1331                    name2pseudofixturedef[argname] = fixturedef
1332            self._arg2fixturedefs[argname] = [fixturedef]
1333
1334        # Create the new calls: if we are parametrize() multiple times (by applying the decorator
1335        # more than once) then we accumulate those calls generating the cartesian product
1336        # of all calls.
1337        newcalls = []
1338        for callspec in self._calls or [CallSpec2()]:
1339            for param_index, (param_id, param_set) in enumerate(
1340                zip(ids, parametersets)
1341            ):
1342                newcallspec = callspec.setmulti(
1343                    argnames=argnames,
1344                    valset=param_set.values,
1345                    id=param_id,
1346                    marks=param_set.marks,
1347                    scope=scope_,
1348                    param_index=param_index,
1349                    nodeid=nodeid,
1350                )
1351                newcalls.append(newcallspec)
1352        self._calls = newcalls
1353
1354    def _resolve_parameter_set_ids(
1355        self,
1356        argnames: Sequence[str],
1357        ids: Iterable[object | None] | Callable[[Any], object | None] | None,
1358        parametersets: Sequence[ParameterSet],
1359        nodeid: str,
1360    ) -> list[str | _HiddenParam]:
1361        """Resolve the actual ids for the given parameter sets.
1362
1363        :param argnames:
1364            Argument names passed to ``parametrize()``.
1365        :param ids:
1366            The `ids` parameter of the ``parametrize()`` call (see docs).
1367        :param parametersets:
1368            The parameter sets, each containing a set of values corresponding
1369            to ``argnames``.
1370        :param nodeid str:
1371            The nodeid of the definition item that generated this
1372            parametrization.
1373        :returns:
1374            List with ids for each parameter set given.
1375        """
1376        if ids is None:
1377            idfn = None
1378            ids_ = None
1379        elif callable(ids):
1380            idfn = ids
1381            ids_ = None
1382        else:
1383            idfn = None
1384            ids_ = self._validate_ids(ids, parametersets, self.function.__name__)
1385        id_maker = IdMaker(
1386            argnames,
1387            parametersets,
1388            idfn,
1389            ids_,
1390            self.config,
1391            nodeid=nodeid,
1392            func_name=self.function.__name__,
1393        )
1394        return id_maker.make_unique_parameterset_ids()
1395
1396    def _validate_ids(
1397        self,
1398        ids: Iterable[object | None],
1399        parametersets: Sequence[ParameterSet],
1400        func_name: str,
1401    ) -> list[object | None]:
1402        try:
1403            num_ids = len(ids)  # type: ignore[arg-type]
1404        except TypeError:
1405            try:
1406                iter(ids)
1407            except TypeError as e:
1408                raise TypeError("ids must be a callable or an iterable") from e
1409            num_ids = len(parametersets)
1410
1411        # num_ids == 0 is a special case: https://github.com/pytest-dev/pytest/issues/1849
1412        if num_ids != len(parametersets) and num_ids != 0:
1413            msg = "In {}: {} parameter sets specified, with different number of ids: {}"
1414            fail(msg.format(func_name, len(parametersets), num_ids), pytrace=False)
1415
1416        return list(itertools.islice(ids, num_ids))
1417
1418    def _resolve_args_directness(
1419        self,
1420        argnames: Sequence[str],
1421        indirect: bool | Sequence[str],
1422    ) -> dict[str, Literal["indirect", "direct"]]:
1423        """Resolve if each parametrized argument must be considered an indirect
1424        parameter to a fixture of the same name, or a direct parameter to the
1425        parametrized function, based on the ``indirect`` parameter of the
1426        parametrized() call.
1427
1428        :param argnames:
1429            List of argument names passed to ``parametrize()``.
1430        :param indirect:
1431            Same as the ``indirect`` parameter of ``parametrize()``.
1432        :returns
1433            A dict mapping each arg name to either "indirect" or "direct".
1434        """
1435        arg_directness: dict[str, Literal["indirect", "direct"]]
1436        if isinstance(indirect, bool):
1437            arg_directness = dict.fromkeys(
1438                argnames, "indirect" if indirect else "direct"
1439            )
1440        elif isinstance(indirect, Sequence):
1441            arg_directness = dict.fromkeys(argnames, "direct")
1442            for arg in indirect:
1443                if arg not in argnames:
1444                    fail(
1445                        f"In {self.function.__name__}: indirect fixture '{arg}' doesn't exist",
1446                        pytrace=False,
1447                    )
1448                arg_directness[arg] = "indirect"
1449        else:
1450            fail(
1451                f"In {self.function.__name__}: expected Sequence or boolean"
1452                f" for indirect, got {type(indirect).__name__}",
1453                pytrace=False,
1454            )
1455        return arg_directness
1456
1457    def _validate_if_using_arg_names(
1458        self,
1459        argnames: Sequence[str],
1460        indirect: bool | Sequence[str],
1461    ) -> None:
1462        """Check if all argnames are being used, by default values, or directly/indirectly.
1463
1464        :param List[str] argnames: List of argument names passed to ``parametrize()``.
1465        :param indirect: Same as the ``indirect`` parameter of ``parametrize()``.
1466        :raises ValueError: If validation fails.
1467        """
1468        default_arg_names = set(get_default_arg_names(self.function))
1469        func_name = self.function.__name__
1470        for arg in argnames:
1471            if arg not in self.fixturenames:
1472                if arg in default_arg_names:
1473                    fail(
1474                        f"In {func_name}: function already takes an argument '{arg}' with a default value",
1475                        pytrace=False,
1476                    )
1477                else:
1478                    if isinstance(indirect, Sequence):
1479                        name = "fixture" if arg in indirect else "argument"
1480                    else:
1481                        name = "fixture" if indirect else "argument"
1482                    fail(
1483                        f"In {func_name}: function uses no {name} '{arg}'",
1484                        pytrace=False,
1485                    )
1486
1487    def _recompute_direct_params_indices(self) -> None:
1488        for argname, param_type in self._params_directness.items():
1489            if param_type == "direct":
1490                for i, callspec in enumerate(self._calls):
1491                    callspec.indices[argname] = i

Objects passed to the :hook:pytest_generate_tests hook.

They help to inspect a test function and to generate tests according to test configuration or values specified in the class or module where a test function is defined.

Metafunc( definition: _pytest.python.FunctionDefinition, fixtureinfo: _pytest.fixtures.FuncFixtureInfo, config: _pytest.config.Config, cls=None, module=None, *, _ispytest: bool = False)
1127    def __init__(
1128        self,
1129        definition: FunctionDefinition,
1130        fixtureinfo: fixtures.FuncFixtureInfo,
1131        config: Config,
1132        cls=None,
1133        module=None,
1134        *,
1135        _ispytest: bool = False,
1136    ) -> None:
1137        check_ispytest(_ispytest)
1138
1139        #: Access to the underlying :class:`_pytest.python.FunctionDefinition`.
1140        self.definition = definition
1141
1142        #: Access to the :class:`pytest.Config` object for the test session.
1143        self.config = config
1144
1145        #: The module object where the test function is defined in.
1146        self.module = module
1147
1148        #: Underlying Python test function.
1149        self.function = definition.obj
1150
1151        #: Set of fixture names required by the test function.
1152        self.fixturenames = fixtureinfo.names_closure
1153
1154        #: Class object where the test function is defined in or ``None``.
1155        self.cls = cls
1156
1157        self._arg2fixturedefs = fixtureinfo.name2fixturedefs
1158
1159        # Result of parametrize().
1160        self._calls: list[CallSpec2] = []
1161
1162        self._params_directness: dict[str, Literal["indirect", "direct"]] = {}
definition
config
module
function
fixturenames
cls
def parametrize( self, argnames: str | Sequence[str], argvalues: Iterable[_pytest.mark.structures.ParameterSet | Sequence[object] | object], indirect: bool | Sequence[str] = False, ids: Iterable[object | None] | Callable[[typing.Any], object | None] | None = None, scope: Optional[Literal['session', 'package', 'module', 'class', 'function']] = None, *, _param_mark: _pytest.mark.structures.Mark | None = None) -> None:
1164    def parametrize(
1165        self,
1166        argnames: str | Sequence[str],
1167        argvalues: Iterable[ParameterSet | Sequence[object] | object],
1168        indirect: bool | Sequence[str] = False,
1169        ids: Iterable[object | None] | Callable[[Any], object | None] | None = None,
1170        scope: _ScopeName | None = None,
1171        *,
1172        _param_mark: Mark | None = None,
1173    ) -> None:
1174        """Add new invocations to the underlying test function using the list
1175        of argvalues for the given argnames. Parametrization is performed
1176        during the collection phase. If you need to setup expensive resources
1177        see about setting ``indirect`` to do it at test setup time instead.
1178
1179        Can be called multiple times per test function (but only on different
1180        argument names), in which case each call parametrizes all previous
1181        parametrizations, e.g.
1182
1183        ::
1184
1185            unparametrized:         t
1186            parametrize ["x", "y"]: t[x], t[y]
1187            parametrize [1, 2]:     t[x-1], t[x-2], t[y-1], t[y-2]
1188
1189        :param argnames:
1190            A comma-separated string denoting one or more argument names, or
1191            a list/tuple of argument strings.
1192
1193        :param argvalues:
1194            The list of argvalues determines how often a test is invoked with
1195            different argument values.
1196
1197            If only one argname was specified argvalues is a list of values.
1198            If N argnames were specified, argvalues must be a list of
1199            N-tuples, where each tuple-element specifies a value for its
1200            respective argname.
1201
1202        :param indirect:
1203            A list of arguments' names (subset of argnames) or a boolean.
1204            If True the list contains all names from the argnames. Each
1205            argvalue corresponding to an argname in this list will
1206            be passed as request.param to its respective argname fixture
1207            function so that it can perform more expensive setups during the
1208            setup phase of a test rather than at collection time.
1209
1210        :param ids:
1211            Sequence of (or generator for) ids for ``argvalues``,
1212            or a callable to return part of the id for each argvalue.
1213
1214            With sequences (and generators like ``itertools.count()``) the
1215            returned ids should be of type ``string``, ``int``, ``float``,
1216            ``bool``, or ``None``.
1217            They are mapped to the corresponding index in ``argvalues``.
1218            ``None`` means to use the auto-generated id.
1219
1220            .. versionadded:: 8.4
1221                :ref:`hidden-param` means to hide the parameter set
1222                from the test name. Can only be used at most 1 time, as
1223                test names need to be unique.
1224
1225            If it is a callable it will be called for each entry in
1226            ``argvalues``, and the return value is used as part of the
1227            auto-generated id for the whole set (where parts are joined with
1228            dashes ("-")).
1229            This is useful to provide more specific ids for certain items, e.g.
1230            dates.  Returning ``None`` will use an auto-generated id.
1231
1232            If no ids are provided they will be generated automatically from
1233            the argvalues.
1234
1235        :param scope:
1236            If specified it denotes the scope of the parameters.
1237            The scope is used for grouping tests by parameter instances.
1238            It will also override any fixture-function defined scope, allowing
1239            to set a dynamic scope using test context or configuration.
1240        """
1241        nodeid = self.definition.nodeid
1242
1243        argnames, parametersets = ParameterSet._for_parametrize(
1244            argnames,
1245            argvalues,
1246            self.function,
1247            self.config,
1248            nodeid=self.definition.nodeid,
1249        )
1250        del argvalues
1251
1252        if "request" in argnames:
1253            fail(
1254                f"{nodeid}: 'request' is a reserved name and cannot be used in @pytest.mark.parametrize",
1255                pytrace=False,
1256            )
1257
1258        if scope is not None:
1259            scope_ = Scope.from_user(
1260                scope, descr=f"parametrize() call in {self.function.__name__}"
1261            )
1262        else:
1263            scope_ = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
1264
1265        self._validate_if_using_arg_names(argnames, indirect)
1266
1267        # Use any already (possibly) generated ids with parametrize Marks.
1268        if _param_mark and _param_mark._param_ids_from:
1269            generated_ids = _param_mark._param_ids_from._param_ids_generated
1270            if generated_ids is not None:
1271                ids = generated_ids
1272
1273        ids = self._resolve_parameter_set_ids(
1274            argnames, ids, parametersets, nodeid=self.definition.nodeid
1275        )
1276
1277        # Store used (possibly generated) ids with parametrize Marks.
1278        if _param_mark and _param_mark._param_ids_from and generated_ids is None:
1279            object.__setattr__(_param_mark._param_ids_from, "_param_ids_generated", ids)
1280
1281        # Calculate directness.
1282        arg_directness = self._resolve_args_directness(argnames, indirect)
1283        self._params_directness.update(arg_directness)
1284
1285        # Add direct parametrizations as fixturedefs to arg2fixturedefs by
1286        # registering artificial "pseudo" FixtureDef's such that later at test
1287        # setup time we can rely on FixtureDefs to exist for all argnames.
1288        node = None
1289        # For scopes higher than function, a "pseudo" FixtureDef might have
1290        # already been created for the scope. We thus store and cache the
1291        # FixtureDef on the node related to the scope.
1292        if scope_ is Scope.Function:
1293            name2pseudofixturedef = None
1294        else:
1295            collector = self.definition.parent
1296            assert collector is not None
1297            node = get_scope_node(collector, scope_)
1298            if node is None:
1299                # If used class scope and there is no class, use module-level
1300                # collector (for now).
1301                if scope_ is Scope.Class:
1302                    assert isinstance(collector, Module)
1303                    node = collector
1304                # If used package scope and there is no package, use session
1305                # (for now).
1306                elif scope_ is Scope.Package:
1307                    node = collector.session
1308                else:
1309                    assert False, f"Unhandled missing scope: {scope}"
1310            default: dict[str, FixtureDef[Any]] = {}
1311            name2pseudofixturedef = node.stash.setdefault(
1312                name2pseudofixturedef_key, default
1313            )
1314        for argname in argnames:
1315            if arg_directness[argname] == "indirect":
1316                continue
1317            if name2pseudofixturedef is not None and argname in name2pseudofixturedef:
1318                fixturedef = name2pseudofixturedef[argname]
1319            else:
1320                fixturedef = FixtureDef(
1321                    config=self.config,
1322                    baseid="",
1323                    argname=argname,
1324                    func=get_direct_param_fixture_func,
1325                    scope=scope_,
1326                    params=None,
1327                    ids=None,
1328                    _ispytest=True,
1329                )
1330                if name2pseudofixturedef is not None:
1331                    name2pseudofixturedef[argname] = fixturedef
1332            self._arg2fixturedefs[argname] = [fixturedef]
1333
1334        # Create the new calls: if we are parametrize() multiple times (by applying the decorator
1335        # more than once) then we accumulate those calls generating the cartesian product
1336        # of all calls.
1337        newcalls = []
1338        for callspec in self._calls or [CallSpec2()]:
1339            for param_index, (param_id, param_set) in enumerate(
1340                zip(ids, parametersets)
1341            ):
1342                newcallspec = callspec.setmulti(
1343                    argnames=argnames,
1344                    valset=param_set.values,
1345                    id=param_id,
1346                    marks=param_set.marks,
1347                    scope=scope_,
1348                    param_index=param_index,
1349                    nodeid=nodeid,
1350                )
1351                newcalls.append(newcallspec)
1352        self._calls = newcalls

Add new invocations to the underlying test function using the list of argvalues for the given argnames. Parametrization is performed during the collection phase. If you need to setup expensive resources see about setting indirect to do it at test setup time instead.

Can be called multiple times per test function (but only on different argument names), in which case each call parametrizes all previous parametrizations, e.g.

::

unparametrized:         t
parametrize ["x", "y"]: t[x], t[y]
parametrize [1, 2]:     t[x-1], t[x-2], t[y-1], t[y-2]
Parameters
  • argnames: A comma-separated string denoting one or more argument names, or a list/tuple of argument strings.

  • argvalues: The list of argvalues determines how often a test is invoked with different argument values.

    If only one argname was specified argvalues is a list of values. If N argnames were specified, argvalues must be a list of N-tuples, where each tuple-element specifies a value for its respective argname.

  • indirect: A list of arguments' names (subset of argnames) or a boolean. If True the list contains all names from the argnames. Each argvalue corresponding to an argname in this list will be passed as request.param to its respective argname fixture function so that it can perform more expensive setups during the setup phase of a test rather than at collection time.

  • ids: Sequence of (or generator for) ids for argvalues, or a callable to return part of the id for each argvalue.

    With sequences (and generators like itertools.count()) the returned ids should be of type string, int, float, bool, or None. They are mapped to the corresponding index in argvalues. None means to use the auto-generated id.

    New in version 8.4:

    from the test name. Can only be used at most 1 time, as test names need to be unique.

    If it is a callable it will be called for each entry in argvalues, and the return value is used as part of the auto-generated id for the whole set (where parts are joined with dashes ("-")). This is useful to provide more specific ids for certain items, e.g. dates. Returning None will use an auto-generated id.

    If no ids are provided they will be generated automatically from the argvalues.

  • scope: If specified it denotes the scope of the parameters. The scope is used for grouping tests by parameter instances. It will also override any fixture-function defined scope, allowing to set a dynamic scope using test context or configuration.

class Module(pytest.File, _pytest.python.PyCollector):
548class Module(nodes.File, PyCollector):
549    """Collector for test classes and functions in a Python module."""
550
551    def _getobj(self):
552        return importtestmodule(self.path, self.config)
553
554    def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
555        self._register_setup_module_fixture()
556        self._register_setup_function_fixture()
557        self.session._fixturemanager.parsefactories(self)
558        return super().collect()
559
560    def _register_setup_module_fixture(self) -> None:
561        """Register an autouse, module-scoped fixture for the collected module object
562        that invokes setUpModule/tearDownModule if either or both are available.
563
564        Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
565        other fixtures (#517).
566        """
567        setup_module = _get_first_non_fixture_func(
568            self.obj, ("setUpModule", "setup_module")
569        )
570        teardown_module = _get_first_non_fixture_func(
571            self.obj, ("tearDownModule", "teardown_module")
572        )
573
574        if setup_module is None and teardown_module is None:
575            return
576
577        def xunit_setup_module_fixture(request) -> Generator[None]:
578            module = request.module
579            if setup_module is not None:
580                _call_with_optional_argument(setup_module, module)
581            yield
582            if teardown_module is not None:
583                _call_with_optional_argument(teardown_module, module)
584
585        self.session._fixturemanager._register_fixture(
586            # Use a unique name to speed up lookup.
587            name=f"_xunit_setup_module_fixture_{self.obj.__name__}",
588            func=xunit_setup_module_fixture,
589            nodeid=self.nodeid,
590            scope="module",
591            autouse=True,
592        )
593
594    def _register_setup_function_fixture(self) -> None:
595        """Register an autouse, function-scoped fixture for the collected module object
596        that invokes setup_function/teardown_function if either or both are available.
597
598        Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
599        other fixtures (#517).
600        """
601        setup_function = _get_first_non_fixture_func(self.obj, ("setup_function",))
602        teardown_function = _get_first_non_fixture_func(
603            self.obj, ("teardown_function",)
604        )
605        if setup_function is None and teardown_function is None:
606            return
607
608        def xunit_setup_function_fixture(request) -> Generator[None]:
609            if request.instance is not None:
610                # in this case we are bound to an instance, so we need to let
611                # setup_method handle this
612                yield
613                return
614            function = request.function
615            if setup_function is not None:
616                _call_with_optional_argument(setup_function, function)
617            yield
618            if teardown_function is not None:
619                _call_with_optional_argument(teardown_function, function)
620
621        self.session._fixturemanager._register_fixture(
622            # Use a unique name to speed up lookup.
623            name=f"_xunit_setup_function_fixture_{self.obj.__name__}",
624            func=xunit_setup_function_fixture,
625            nodeid=self.nodeid,
626            scope="function",
627            autouse=True,
628        )

Collector for test classes and functions in a Python module.

def collect(self) -> Iterable[_pytest.nodes.Item | _pytest.nodes.Collector]:
554    def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
555        self._register_setup_module_fixture()
556        self._register_setup_function_fixture()
557        self.session._fixturemanager.parsefactories(self)
558        return super().collect()

Collect children (items and collectors) for this collector.

@final
class MonkeyPatch:
117@final
118class MonkeyPatch:
119    """Helper to conveniently monkeypatch attributes/items/environment
120    variables/syspath.
121
122    Returned by the :fixture:`monkeypatch` fixture.
123
124    .. versionchanged:: 6.2
125        Can now also be used directly as `pytest.MonkeyPatch()`, for when
126        the fixture is not available. In this case, use
127        :meth:`with MonkeyPatch.context() as mp: <context>` or remember to call
128        :meth:`undo` explicitly.
129    """
130
131    def __init__(self) -> None:
132        self._setattr: list[tuple[object, str, object]] = []
133        self._setitem: list[tuple[Mapping[Any, Any], object, object]] = []
134        self._cwd: str | None = None
135        self._savesyspath: list[str] | None = None
136
137    @classmethod
138    @contextmanager
139    def context(cls) -> Generator[MonkeyPatch]:
140        """Context manager that returns a new :class:`MonkeyPatch` object
141        which undoes any patching done inside the ``with`` block upon exit.
142
143        Example:
144
145        .. code-block:: python
146
147            import functools
148
149
150            def test_partial(monkeypatch):
151                with monkeypatch.context() as m:
152                    m.setattr(functools, "partial", 3)
153
154        Useful in situations where it is desired to undo some patches before the test ends,
155        such as mocking ``stdlib`` functions that might break pytest itself if mocked (for examples
156        of this see :issue:`3290`).
157        """
158        m = cls()
159        try:
160            yield m
161        finally:
162            m.undo()
163
164    @overload
165    def setattr(
166        self,
167        target: str,
168        name: object,
169        value: Notset = ...,
170        raising: bool = ...,
171    ) -> None: ...
172
173    @overload
174    def setattr(
175        self,
176        target: object,
177        name: str,
178        value: object,
179        raising: bool = ...,
180    ) -> None: ...
181
182    def setattr(
183        self,
184        target: str | object,
185        name: object | str,
186        value: object = notset,
187        raising: bool = True,
188    ) -> None:
189        """
190        Set attribute value on target, memorizing the old value.
191
192        For example:
193
194        .. code-block:: python
195
196            import os
197
198            monkeypatch.setattr(os, "getcwd", lambda: "/")
199
200        The code above replaces the :func:`os.getcwd` function by a ``lambda`` which
201        always returns ``"/"``.
202
203        For convenience, you can specify a string as ``target`` which
204        will be interpreted as a dotted import path, with the last part
205        being the attribute name:
206
207        .. code-block:: python
208
209            monkeypatch.setattr("os.getcwd", lambda: "/")
210
211        Raises :class:`AttributeError` if the attribute does not exist, unless
212        ``raising`` is set to False.
213
214        **Where to patch**
215
216        ``monkeypatch.setattr`` works by (temporarily) changing the object that a name points to with another one.
217        There can be many names pointing to any individual object, so for patching to work you must ensure
218        that you patch the name used by the system under test.
219
220        See the section :ref:`Where to patch <python:where-to-patch>` in the :mod:`unittest.mock`
221        docs for a complete explanation, which is meant for :func:`unittest.mock.patch` but
222        applies to ``monkeypatch.setattr`` as well.
223        """
224        __tracebackhide__ = True
225        import inspect
226
227        if isinstance(value, Notset):
228            if not isinstance(target, str):
229                raise TypeError(
230                    "use setattr(target, name, value) or "
231                    "setattr(target, value) with target being a dotted "
232                    "import string"
233                )
234            value = name
235            name, target = derive_importpath(target, raising)
236        else:
237            if not isinstance(name, str):
238                raise TypeError(
239                    "use setattr(target, name, value) with name being a string or "
240                    "setattr(target, value) with target being a dotted "
241                    "import string"
242                )
243
244        oldval = getattr(target, name, notset)
245        if raising and oldval is notset:
246            raise AttributeError(f"{target!r} has no attribute {name!r}")
247
248        # avoid class descriptors like staticmethod/classmethod
249        if inspect.isclass(target):
250            oldval = target.__dict__.get(name, notset)
251        self._setattr.append((target, name, oldval))
252        setattr(target, name, value)
253
254    def delattr(
255        self,
256        target: object | str,
257        name: str | Notset = notset,
258        raising: bool = True,
259    ) -> None:
260        """Delete attribute ``name`` from ``target``.
261
262        If no ``name`` is specified and ``target`` is a string
263        it will be interpreted as a dotted import path with the
264        last part being the attribute name.
265
266        Raises AttributeError it the attribute does not exist, unless
267        ``raising`` is set to False.
268        """
269        __tracebackhide__ = True
270        import inspect
271
272        if isinstance(name, Notset):
273            if not isinstance(target, str):
274                raise TypeError(
275                    "use delattr(target, name) or "
276                    "delattr(target) with target being a dotted "
277                    "import string"
278                )
279            name, target = derive_importpath(target, raising)
280
281        if not hasattr(target, name):
282            if raising:
283                raise AttributeError(name)
284        else:
285            oldval = getattr(target, name, notset)
286            # Avoid class descriptors like staticmethod/classmethod.
287            if inspect.isclass(target):
288                oldval = target.__dict__.get(name, notset)
289            self._setattr.append((target, name, oldval))
290            delattr(target, name)
291
292    def setitem(self, dic: Mapping[K, V], name: K, value: V) -> None:
293        """Set dictionary entry ``name`` to value."""
294        self._setitem.append((dic, name, dic.get(name, notset)))
295        # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
296        dic[name] = value  # type: ignore[index]
297
298    def delitem(self, dic: Mapping[K, V], name: K, raising: bool = True) -> None:
299        """Delete ``name`` from dict.
300
301        Raises ``KeyError`` if it doesn't exist, unless ``raising`` is set to
302        False.
303        """
304        if name not in dic:
305            if raising:
306                raise KeyError(name)
307        else:
308            self._setitem.append((dic, name, dic.get(name, notset)))
309            # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
310            del dic[name]  # type: ignore[attr-defined]
311
312    def setenv(self, name: str, value: str, prepend: str | None = None) -> None:
313        """Set environment variable ``name`` to ``value``.
314
315        If ``prepend`` is a character, read the current environment variable
316        value and prepend the ``value`` adjoined with the ``prepend``
317        character.
318        """
319        if not isinstance(value, str):
320            warnings.warn(  # type: ignore[unreachable]
321                PytestWarning(
322                    f"Value of environment variable {name} type should be str, but got "
323                    f"{value!r} (type: {type(value).__name__}); converted to str implicitly"
324                ),
325                stacklevel=2,
326            )
327            value = str(value)
328        if prepend and name in os.environ:
329            value = value + prepend + os.environ[name]
330        self.setitem(os.environ, name, value)
331
332    def delenv(self, name: str, raising: bool = True) -> None:
333        """Delete ``name`` from the environment.
334
335        Raises ``KeyError`` if it does not exist, unless ``raising`` is set to
336        False.
337        """
338        environ: MutableMapping[str, str] = os.environ
339        self.delitem(environ, name, raising=raising)
340
341    def syspath_prepend(self, path) -> None:
342        """Prepend ``path`` to ``sys.path`` list of import locations."""
343        if self._savesyspath is None:
344            self._savesyspath = sys.path[:]
345        sys.path.insert(0, str(path))
346
347        # https://github.com/pypa/setuptools/blob/d8b901bc/docs/pkg_resources.txt#L162-L171
348        # this is only needed when pkg_resources was already loaded by the namespace package
349        if "pkg_resources" in sys.modules:
350            from pkg_resources import fixup_namespace_packages
351
352            fixup_namespace_packages(str(path))
353
354        # A call to syspathinsert() usually means that the caller wants to
355        # import some dynamically created files, thus with python3 we
356        # invalidate its import caches.
357        # This is especially important when any namespace package is in use,
358        # since then the mtime based FileFinder cache (that gets created in
359        # this case already) gets not invalidated when writing the new files
360        # quickly afterwards.
361        from importlib import invalidate_caches
362
363        invalidate_caches()
364
365    def chdir(self, path: str | os.PathLike[str]) -> None:
366        """Change the current working directory to the specified path.
367
368        :param path:
369            The path to change into.
370        """
371        if self._cwd is None:
372            self._cwd = os.getcwd()
373        os.chdir(path)
374
375    def undo(self) -> None:
376        """Undo previous changes.
377
378        This call consumes the undo stack. Calling it a second time has no
379        effect unless you do more monkeypatching after the undo call.
380
381        There is generally no need to call `undo()`, since it is
382        called automatically during tear-down.
383
384        .. note::
385            The same `monkeypatch` fixture is used across a
386            single test function invocation. If `monkeypatch` is used both by
387            the test function itself and one of the test fixtures,
388            calling `undo()` will undo all of the changes made in
389            both functions.
390
391            Prefer to use :meth:`context() <pytest.MonkeyPatch.context>` instead.
392        """
393        for obj, name, value in reversed(self._setattr):
394            if value is not notset:
395                setattr(obj, name, value)
396            else:
397                delattr(obj, name)
398        self._setattr[:] = []
399        for dictionary, key, value in reversed(self._setitem):
400            if value is notset:
401                try:
402                    # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
403                    del dictionary[key]  # type: ignore[attr-defined]
404                except KeyError:
405                    pass  # Was already deleted, so we have the desired state.
406            else:
407                # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
408                dictionary[key] = value  # type: ignore[index]
409        self._setitem[:] = []
410        if self._savesyspath is not None:
411            sys.path[:] = self._savesyspath
412            self._savesyspath = None
413
414        if self._cwd is not None:
415            os.chdir(self._cwd)
416            self._cwd = None

Helper to conveniently monkeypatch attributes/items/environment variables/syspath.

Returned by the :fixture:monkeypatch fixture.

Changed in version 6.2: Can now also be used directly as pytest.MonkeyPatch(), for when the fixture is not available. In this case, use with MonkeyPatch.context() as mp: <context>() or remember to call undo() explicitly.

@classmethod
@contextmanager
def context(cls) -> Generator[_pytest.monkeypatch.MonkeyPatch]:
137    @classmethod
138    @contextmanager
139    def context(cls) -> Generator[MonkeyPatch]:
140        """Context manager that returns a new :class:`MonkeyPatch` object
141        which undoes any patching done inside the ``with`` block upon exit.
142
143        Example:
144
145        .. code-block:: python
146
147            import functools
148
149
150            def test_partial(monkeypatch):
151                with monkeypatch.context() as m:
152                    m.setattr(functools, "partial", 3)
153
154        Useful in situations where it is desired to undo some patches before the test ends,
155        such as mocking ``stdlib`` functions that might break pytest itself if mocked (for examples
156        of this see :issue:`3290`).
157        """
158        m = cls()
159        try:
160            yield m
161        finally:
162            m.undo()

Context manager that returns a new MonkeyPatch object which undoes any patching done inside the with block upon exit.

Example:

import functools


def test_partial(monkeypatch):
    with monkeypatch.context() as m:
        m.setattr(functools, "partial", 3)

Useful in situations where it is desired to undo some patches before the test ends, such as mocking stdlib functions that might break pytest itself if mocked (for examples of this see :issue:3290).

def setattr( self, target: str | object, name: object | str, value: object = <notset>, raising: bool = True) -> None:
182    def setattr(
183        self,
184        target: str | object,
185        name: object | str,
186        value: object = notset,
187        raising: bool = True,
188    ) -> None:
189        """
190        Set attribute value on target, memorizing the old value.
191
192        For example:
193
194        .. code-block:: python
195
196            import os
197
198            monkeypatch.setattr(os, "getcwd", lambda: "/")
199
200        The code above replaces the :func:`os.getcwd` function by a ``lambda`` which
201        always returns ``"/"``.
202
203        For convenience, you can specify a string as ``target`` which
204        will be interpreted as a dotted import path, with the last part
205        being the attribute name:
206
207        .. code-block:: python
208
209            monkeypatch.setattr("os.getcwd", lambda: "/")
210
211        Raises :class:`AttributeError` if the attribute does not exist, unless
212        ``raising`` is set to False.
213
214        **Where to patch**
215
216        ``monkeypatch.setattr`` works by (temporarily) changing the object that a name points to with another one.
217        There can be many names pointing to any individual object, so for patching to work you must ensure
218        that you patch the name used by the system under test.
219
220        See the section :ref:`Where to patch <python:where-to-patch>` in the :mod:`unittest.mock`
221        docs for a complete explanation, which is meant for :func:`unittest.mock.patch` but
222        applies to ``monkeypatch.setattr`` as well.
223        """
224        __tracebackhide__ = True
225        import inspect
226
227        if isinstance(value, Notset):
228            if not isinstance(target, str):
229                raise TypeError(
230                    "use setattr(target, name, value) or "
231                    "setattr(target, value) with target being a dotted "
232                    "import string"
233                )
234            value = name
235            name, target = derive_importpath(target, raising)
236        else:
237            if not isinstance(name, str):
238                raise TypeError(
239                    "use setattr(target, name, value) with name being a string or "
240                    "setattr(target, value) with target being a dotted "
241                    "import string"
242                )
243
244        oldval = getattr(target, name, notset)
245        if raising and oldval is notset:
246            raise AttributeError(f"{target!r} has no attribute {name!r}")
247
248        # avoid class descriptors like staticmethod/classmethod
249        if inspect.isclass(target):
250            oldval = target.__dict__.get(name, notset)
251        self._setattr.append((target, name, oldval))
252        setattr(target, name, value)

Set attribute value on target, memorizing the old value.

For example:

import os

monkeypatch.setattr(os, "getcwd", lambda: "/")

The code above replaces the os.getcwd() function by a lambda which always returns "/".

For convenience, you can specify a string as target which will be interpreted as a dotted import path, with the last part being the attribute name:

monkeypatch.setattr("os.getcwd", lambda: "/")

Raises AttributeError if the attribute does not exist, unless raising is set to False.

Where to patch

monkeypatch.setattr works by (temporarily) changing the object that a name points to with another one. There can be many names pointing to any individual object, so for patching to work you must ensure that you patch the name used by the system under test.

See the section :ref:Where to patch <python:where-to-patch> in the unittest.mock docs for a complete explanation, which is meant for unittest.mock.patch() but applies to monkeypatch.setattr as well.

def delattr( self, target: object | str, name: str | _pytest.monkeypatch.Notset = <notset>, raising: bool = True) -> None:
254    def delattr(
255        self,
256        target: object | str,
257        name: str | Notset = notset,
258        raising: bool = True,
259    ) -> None:
260        """Delete attribute ``name`` from ``target``.
261
262        If no ``name`` is specified and ``target`` is a string
263        it will be interpreted as a dotted import path with the
264        last part being the attribute name.
265
266        Raises AttributeError it the attribute does not exist, unless
267        ``raising`` is set to False.
268        """
269        __tracebackhide__ = True
270        import inspect
271
272        if isinstance(name, Notset):
273            if not isinstance(target, str):
274                raise TypeError(
275                    "use delattr(target, name) or "
276                    "delattr(target) with target being a dotted "
277                    "import string"
278                )
279            name, target = derive_importpath(target, raising)
280
281        if not hasattr(target, name):
282            if raising:
283                raise AttributeError(name)
284        else:
285            oldval = getattr(target, name, notset)
286            # Avoid class descriptors like staticmethod/classmethod.
287            if inspect.isclass(target):
288                oldval = target.__dict__.get(name, notset)
289            self._setattr.append((target, name, oldval))
290            delattr(target, name)

Delete attribute name from target.

If no name is specified and target is a string it will be interpreted as a dotted import path with the last part being the attribute name.

Raises AttributeError it the attribute does not exist, unless raising is set to False.

def setitem(self, dic: Mapping[~K, ~V], name: ~K, value: ~V) -> None:
292    def setitem(self, dic: Mapping[K, V], name: K, value: V) -> None:
293        """Set dictionary entry ``name`` to value."""
294        self._setitem.append((dic, name, dic.get(name, notset)))
295        # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
296        dic[name] = value  # type: ignore[index]

Set dictionary entry name to value.

def delitem(self, dic: Mapping[~K, ~V], name: ~K, raising: bool = True) -> None:
298    def delitem(self, dic: Mapping[K, V], name: K, raising: bool = True) -> None:
299        """Delete ``name`` from dict.
300
301        Raises ``KeyError`` if it doesn't exist, unless ``raising`` is set to
302        False.
303        """
304        if name not in dic:
305            if raising:
306                raise KeyError(name)
307        else:
308            self._setitem.append((dic, name, dic.get(name, notset)))
309            # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
310            del dic[name]  # type: ignore[attr-defined]

Delete name from dict.

Raises KeyError if it doesn't exist, unless raising is set to False.

def setenv(self, name: str, value: str, prepend: str | None = None) -> None:
312    def setenv(self, name: str, value: str, prepend: str | None = None) -> None:
313        """Set environment variable ``name`` to ``value``.
314
315        If ``prepend`` is a character, read the current environment variable
316        value and prepend the ``value`` adjoined with the ``prepend``
317        character.
318        """
319        if not isinstance(value, str):
320            warnings.warn(  # type: ignore[unreachable]
321                PytestWarning(
322                    f"Value of environment variable {name} type should be str, but got "
323                    f"{value!r} (type: {type(value).__name__}); converted to str implicitly"
324                ),
325                stacklevel=2,
326            )
327            value = str(value)
328        if prepend and name in os.environ:
329            value = value + prepend + os.environ[name]
330        self.setitem(os.environ, name, value)

Set environment variable name to value.

If prepend is a character, read the current environment variable value and prepend the value adjoined with the prepend character.

def delenv(self, name: str, raising: bool = True) -> None:
332    def delenv(self, name: str, raising: bool = True) -> None:
333        """Delete ``name`` from the environment.
334
335        Raises ``KeyError`` if it does not exist, unless ``raising`` is set to
336        False.
337        """
338        environ: MutableMapping[str, str] = os.environ
339        self.delitem(environ, name, raising=raising)

Delete name from the environment.

Raises KeyError if it does not exist, unless raising is set to False.

def syspath_prepend(self, path) -> None:
341    def syspath_prepend(self, path) -> None:
342        """Prepend ``path`` to ``sys.path`` list of import locations."""
343        if self._savesyspath is None:
344            self._savesyspath = sys.path[:]
345        sys.path.insert(0, str(path))
346
347        # https://github.com/pypa/setuptools/blob/d8b901bc/docs/pkg_resources.txt#L162-L171
348        # this is only needed when pkg_resources was already loaded by the namespace package
349        if "pkg_resources" in sys.modules:
350            from pkg_resources import fixup_namespace_packages
351
352            fixup_namespace_packages(str(path))
353
354        # A call to syspathinsert() usually means that the caller wants to
355        # import some dynamically created files, thus with python3 we
356        # invalidate its import caches.
357        # This is especially important when any namespace package is in use,
358        # since then the mtime based FileFinder cache (that gets created in
359        # this case already) gets not invalidated when writing the new files
360        # quickly afterwards.
361        from importlib import invalidate_caches
362
363        invalidate_caches()

Prepend path to sys.path list of import locations.

def chdir(self, path: str | os.PathLike[str]) -> None:
365    def chdir(self, path: str | os.PathLike[str]) -> None:
366        """Change the current working directory to the specified path.
367
368        :param path:
369            The path to change into.
370        """
371        if self._cwd is None:
372            self._cwd = os.getcwd()
373        os.chdir(path)

Change the current working directory to the specified path.

Parameters
  • path: The path to change into.
def undo(self) -> None:
375    def undo(self) -> None:
376        """Undo previous changes.
377
378        This call consumes the undo stack. Calling it a second time has no
379        effect unless you do more monkeypatching after the undo call.
380
381        There is generally no need to call `undo()`, since it is
382        called automatically during tear-down.
383
384        .. note::
385            The same `monkeypatch` fixture is used across a
386            single test function invocation. If `monkeypatch` is used both by
387            the test function itself and one of the test fixtures,
388            calling `undo()` will undo all of the changes made in
389            both functions.
390
391            Prefer to use :meth:`context() <pytest.MonkeyPatch.context>` instead.
392        """
393        for obj, name, value in reversed(self._setattr):
394            if value is not notset:
395                setattr(obj, name, value)
396            else:
397                delattr(obj, name)
398        self._setattr[:] = []
399        for dictionary, key, value in reversed(self._setitem):
400            if value is notset:
401                try:
402                    # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
403                    del dictionary[key]  # type: ignore[attr-defined]
404                except KeyError:
405                    pass  # Was already deleted, so we have the desired state.
406            else:
407                # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
408                dictionary[key] = value  # type: ignore[index]
409        self._setitem[:] = []
410        if self._savesyspath is not None:
411            sys.path[:] = self._savesyspath
412            self._savesyspath = None
413
414        if self._cwd is not None:
415            os.chdir(self._cwd)
416            self._cwd = None

Undo previous changes.

This call consumes the undo stack. Calling it a second time has no effect unless you do more monkeypatching after the undo call.

There is generally no need to call undo(), since it is called automatically during tear-down.

The same monkeypatch fixture is used across a single test function invocation. If monkeypatch is used both by the test function itself and one of the test fixtures, calling undo() will undo all of the changes made in both functions.

Prefer to use context() <pytest.MonkeyPatch.context>() instead.

class OptionGroup:
369class OptionGroup:
370    """A group of options shown in its own section."""
371
372    def __init__(
373        self,
374        name: str,
375        description: str = "",
376        parser: Parser | None = None,
377        *,
378        _ispytest: bool = False,
379    ) -> None:
380        check_ispytest(_ispytest)
381        self.name = name
382        self.description = description
383        self.options: list[Argument] = []
384        self.parser = parser
385
386    def addoption(self, *opts: str, **attrs: Any) -> None:
387        """Add an option to this group.
388
389        If a shortened version of a long option is specified, it will
390        be suppressed in the help. ``addoption('--twowords', '--two-words')``
391        results in help showing ``--two-words`` only, but ``--twowords`` gets
392        accepted **and** the automatic destination is in ``args.twowords``.
393
394        :param opts:
395            Option names, can be short or long options.
396        :param attrs:
397            Same attributes as the argparse library's :meth:`add_argument()
398            <argparse.ArgumentParser.add_argument>` function accepts.
399        """
400        conflict = set(opts).intersection(
401            name for opt in self.options for name in opt.names()
402        )
403        if conflict:
404            raise ValueError(f"option names {conflict} already added")
405        option = Argument(*opts, **attrs)
406        self._addoption_instance(option, shortupper=False)
407
408    def _addoption(self, *opts: str, **attrs: Any) -> None:
409        option = Argument(*opts, **attrs)
410        self._addoption_instance(option, shortupper=True)
411
412    def _addoption_instance(self, option: Argument, shortupper: bool = False) -> None:
413        if not shortupper:
414            for opt in option._short_opts:
415                if opt[0] == "-" and opt[1].islower():
416                    raise ValueError("lowercase shortoptions reserved")
417        if self.parser:
418            self.parser.processoption(option)
419        self.options.append(option)

A group of options shown in its own section.

OptionGroup( name: str, description: str = '', parser: _pytest.config.argparsing.Parser | None = None, *, _ispytest: bool = False)
372    def __init__(
373        self,
374        name: str,
375        description: str = "",
376        parser: Parser | None = None,
377        *,
378        _ispytest: bool = False,
379    ) -> None:
380        check_ispytest(_ispytest)
381        self.name = name
382        self.description = description
383        self.options: list[Argument] = []
384        self.parser = parser
name
description
options: list[_pytest.config.argparsing.Argument]
parser
def addoption(self, *opts: str, **attrs: Any) -> None:
386    def addoption(self, *opts: str, **attrs: Any) -> None:
387        """Add an option to this group.
388
389        If a shortened version of a long option is specified, it will
390        be suppressed in the help. ``addoption('--twowords', '--two-words')``
391        results in help showing ``--two-words`` only, but ``--twowords`` gets
392        accepted **and** the automatic destination is in ``args.twowords``.
393
394        :param opts:
395            Option names, can be short or long options.
396        :param attrs:
397            Same attributes as the argparse library's :meth:`add_argument()
398            <argparse.ArgumentParser.add_argument>` function accepts.
399        """
400        conflict = set(opts).intersection(
401            name for opt in self.options for name in opt.names()
402        )
403        if conflict:
404            raise ValueError(f"option names {conflict} already added")
405        option = Argument(*opts, **attrs)
406        self._addoption_instance(option, shortupper=False)

Add an option to this group.

If a shortened version of a long option is specified, it will be suppressed in the help. addoption('--twowords', '--two-words') results in help showing --two-words only, but --twowords gets accepted and the automatic destination is in args.twowords.

Parameters
  • opts: Option names, can be short or long options.
  • attrs: Same attributes as the argparse library's add_argument() <argparse.ArgumentParser.add_argument>() function accepts.
class Package(pytest.Directory):
631class Package(nodes.Directory):
632    """Collector for files and directories in a Python packages -- directories
633    with an `__init__.py` file.
634
635    .. note::
636
637        Directories without an `__init__.py` file are instead collected by
638        :class:`~pytest.Dir` by default. Both are :class:`~pytest.Directory`
639        collectors.
640
641    .. versionchanged:: 8.0
642
643        Now inherits from :class:`~pytest.Directory`.
644    """
645
646    def __init__(
647        self,
648        fspath: LEGACY_PATH | None,
649        parent: nodes.Collector,
650        # NOTE: following args are unused:
651        config=None,
652        session=None,
653        nodeid=None,
654        path: Path | None = None,
655    ) -> None:
656        # NOTE: Could be just the following, but kept as-is for compat.
657        # super().__init__(self, fspath, parent=parent)
658        session = parent.session
659        super().__init__(
660            fspath=fspath,
661            path=path,
662            parent=parent,
663            config=config,
664            session=session,
665            nodeid=nodeid,
666        )
667
668    def setup(self) -> None:
669        init_mod = importtestmodule(self.path / "__init__.py", self.config)
670
671        # Not using fixtures to call setup_module here because autouse fixtures
672        # from packages are not called automatically (#4085).
673        setup_module = _get_first_non_fixture_func(
674            init_mod, ("setUpModule", "setup_module")
675        )
676        if setup_module is not None:
677            _call_with_optional_argument(setup_module, init_mod)
678
679        teardown_module = _get_first_non_fixture_func(
680            init_mod, ("tearDownModule", "teardown_module")
681        )
682        if teardown_module is not None:
683            func = partial(_call_with_optional_argument, teardown_module, init_mod)
684            self.addfinalizer(func)
685
686    def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
687        # Always collect __init__.py first.
688        def sort_key(entry: os.DirEntry[str]) -> object:
689            return (entry.name != "__init__.py", entry.name)
690
691        config = self.config
692        col: nodes.Collector | None
693        cols: Sequence[nodes.Collector]
694        ihook = self.ihook
695        for direntry in scandir(self.path, sort_key):
696            if direntry.is_dir():
697                path = Path(direntry.path)
698                if not self.session.isinitpath(path, with_parents=True):
699                    if ihook.pytest_ignore_collect(collection_path=path, config=config):
700                        continue
701                col = ihook.pytest_collect_directory(path=path, parent=self)
702                if col is not None:
703                    yield col
704
705            elif direntry.is_file():
706                path = Path(direntry.path)
707                if not self.session.isinitpath(path):
708                    if ihook.pytest_ignore_collect(collection_path=path, config=config):
709                        continue
710                cols = ihook.pytest_collect_file(file_path=path, parent=self)
711                yield from cols

Collector for files and directories in a Python packages -- directories with an __init__.py file.

Directories without an __init__.py file are instead collected by ~pytest.Dir by default. Both are ~pytest.Directory collectors.

Changed in version 8.0: Now inherits from ~pytest.Directory.

Package( fspath: _pytest._py.path.LocalPath | None, parent: _pytest.nodes.Collector, config=None, session=None, nodeid=None, path: pathlib.Path | None = None)
646    def __init__(
647        self,
648        fspath: LEGACY_PATH | None,
649        parent: nodes.Collector,
650        # NOTE: following args are unused:
651        config=None,
652        session=None,
653        nodeid=None,
654        path: Path | None = None,
655    ) -> None:
656        # NOTE: Could be just the following, but kept as-is for compat.
657        # super().__init__(self, fspath, parent=parent)
658        session = parent.session
659        super().__init__(
660            fspath=fspath,
661            path=path,
662            parent=parent,
663            config=config,
664            session=session,
665            nodeid=nodeid,
666        )
def setup(self) -> None:
668    def setup(self) -> None:
669        init_mod = importtestmodule(self.path / "__init__.py", self.config)
670
671        # Not using fixtures to call setup_module here because autouse fixtures
672        # from packages are not called automatically (#4085).
673        setup_module = _get_first_non_fixture_func(
674            init_mod, ("setUpModule", "setup_module")
675        )
676        if setup_module is not None:
677            _call_with_optional_argument(setup_module, init_mod)
678
679        teardown_module = _get_first_non_fixture_func(
680            init_mod, ("tearDownModule", "teardown_module")
681        )
682        if teardown_module is not None:
683            func = partial(_call_with_optional_argument, teardown_module, init_mod)
684            self.addfinalizer(func)
def collect(self) -> Iterable[_pytest.nodes.Item | _pytest.nodes.Collector]:
686    def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
687        # Always collect __init__.py first.
688        def sort_key(entry: os.DirEntry[str]) -> object:
689            return (entry.name != "__init__.py", entry.name)
690
691        config = self.config
692        col: nodes.Collector | None
693        cols: Sequence[nodes.Collector]
694        ihook = self.ihook
695        for direntry in scandir(self.path, sort_key):
696            if direntry.is_dir():
697                path = Path(direntry.path)
698                if not self.session.isinitpath(path, with_parents=True):
699                    if ihook.pytest_ignore_collect(collection_path=path, config=config):
700                        continue
701                col = ihook.pytest_collect_directory(path=path, parent=self)
702                if col is not None:
703                    yield col
704
705            elif direntry.is_file():
706                path = Path(direntry.path)
707                if not self.session.isinitpath(path):
708                    if ihook.pytest_ignore_collect(collection_path=path, config=config):
709                        continue
710                cols = ihook.pytest_collect_file(file_path=path, parent=self)
711                yield from cols

Collect children (items and collectors) for this collector.

@final
class Parser:
 32@final
 33class Parser:
 34    """Parser for command line arguments and ini-file values.
 35
 36    :ivar extra_info: Dict of generic param -> value to display in case
 37        there's an error processing the command line arguments.
 38    """
 39
 40    prog: str | None = None
 41
 42    def __init__(
 43        self,
 44        usage: str | None = None,
 45        processopt: Callable[[Argument], None] | None = None,
 46        *,
 47        _ispytest: bool = False,
 48    ) -> None:
 49        check_ispytest(_ispytest)
 50        self._anonymous = OptionGroup("Custom options", parser=self, _ispytest=True)
 51        self._groups: list[OptionGroup] = []
 52        self._processopt = processopt
 53        self._usage = usage
 54        self._inidict: dict[str, tuple[str, str | None, Any]] = {}
 55        self._ininames: list[str] = []
 56        self.extra_info: dict[str, Any] = {}
 57
 58    def processoption(self, option: Argument) -> None:
 59        if self._processopt:
 60            if option.dest:
 61                self._processopt(option)
 62
 63    def getgroup(
 64        self, name: str, description: str = "", after: str | None = None
 65    ) -> OptionGroup:
 66        """Get (or create) a named option Group.
 67
 68        :param name: Name of the option group.
 69        :param description: Long description for --help output.
 70        :param after: Name of another group, used for ordering --help output.
 71        :returns: The option group.
 72
 73        The returned group object has an ``addoption`` method with the same
 74        signature as :func:`parser.addoption <pytest.Parser.addoption>` but
 75        will be shown in the respective group in the output of
 76        ``pytest --help``.
 77        """
 78        for group in self._groups:
 79            if group.name == name:
 80                return group
 81        group = OptionGroup(name, description, parser=self, _ispytest=True)
 82        i = 0
 83        for i, grp in enumerate(self._groups):
 84            if grp.name == after:
 85                break
 86        self._groups.insert(i + 1, group)
 87        return group
 88
 89    def addoption(self, *opts: str, **attrs: Any) -> None:
 90        """Register a command line option.
 91
 92        :param opts:
 93            Option names, can be short or long options.
 94        :param attrs:
 95            Same attributes as the argparse library's :meth:`add_argument()
 96            <argparse.ArgumentParser.add_argument>` function accepts.
 97
 98        After command line parsing, options are available on the pytest config
 99        object via ``config.option.NAME`` where ``NAME`` is usually set
100        by passing a ``dest`` attribute, for example
101        ``addoption("--long", dest="NAME", ...)``.
102        """
103        self._anonymous.addoption(*opts, **attrs)
104
105    def parse(
106        self,
107        args: Sequence[str | os.PathLike[str]],
108        namespace: argparse.Namespace | None = None,
109    ) -> argparse.Namespace:
110        from _pytest._argcomplete import try_argcomplete
111
112        self.optparser = self._getparser()
113        try_argcomplete(self.optparser)
114        strargs = [os.fspath(x) for x in args]
115        return self.optparser.parse_args(strargs, namespace=namespace)
116
117    def _getparser(self) -> MyOptionParser:
118        from _pytest._argcomplete import filescompleter
119
120        optparser = MyOptionParser(self, self.extra_info, prog=self.prog)
121        groups = [*self._groups, self._anonymous]
122        for group in groups:
123            if group.options:
124                desc = group.description or group.name
125                arggroup = optparser.add_argument_group(desc)
126                for option in group.options:
127                    n = option.names()
128                    a = option.attrs()
129                    arggroup.add_argument(*n, **a)
130        file_or_dir_arg = optparser.add_argument(FILE_OR_DIR, nargs="*")
131        # bash like autocompletion for dirs (appending '/')
132        # Type ignored because typeshed doesn't know about argcomplete.
133        file_or_dir_arg.completer = filescompleter  # type: ignore
134        return optparser
135
136    def parse_setoption(
137        self,
138        args: Sequence[str | os.PathLike[str]],
139        option: argparse.Namespace,
140        namespace: argparse.Namespace | None = None,
141    ) -> list[str]:
142        parsedoption = self.parse(args, namespace=namespace)
143        for name, value in parsedoption.__dict__.items():
144            setattr(option, name, value)
145        return cast(list[str], getattr(parsedoption, FILE_OR_DIR))
146
147    def parse_known_args(
148        self,
149        args: Sequence[str | os.PathLike[str]],
150        namespace: argparse.Namespace | None = None,
151    ) -> argparse.Namespace:
152        """Parse the known arguments at this point.
153
154        :returns: An argparse namespace object.
155        """
156        return self.parse_known_and_unknown_args(args, namespace=namespace)[0]
157
158    def parse_known_and_unknown_args(
159        self,
160        args: Sequence[str | os.PathLike[str]],
161        namespace: argparse.Namespace | None = None,
162    ) -> tuple[argparse.Namespace, list[str]]:
163        """Parse the known arguments at this point, and also return the
164        remaining unknown arguments.
165
166        :returns:
167            A tuple containing an argparse namespace object for the known
168            arguments, and a list of the unknown arguments.
169        """
170        optparser = self._getparser()
171        strargs = [os.fspath(x) for x in args]
172        return optparser.parse_known_args(strargs, namespace=namespace)
173
174    def addini(
175        self,
176        name: str,
177        help: str,
178        type: Literal["string", "paths", "pathlist", "args", "linelist", "bool"]
179        | None = None,
180        default: Any = NOT_SET,
181    ) -> None:
182        """Register an ini-file option.
183
184        :param name:
185            Name of the ini-variable.
186        :param type:
187            Type of the variable. Can be:
188
189                * ``string``: a string
190                * ``bool``: a boolean
191                * ``args``: a list of strings, separated as in a shell
192                * ``linelist``: a list of strings, separated by line breaks
193                * ``paths``: a list of :class:`pathlib.Path`, separated as in a shell
194                * ``pathlist``: a list of ``py.path``, separated as in a shell
195                * ``int``: an integer
196                * ``float``: a floating-point number
197
198                .. versionadded:: 8.4
199
200                    The ``float`` and ``int`` types.
201
202            For ``paths`` and ``pathlist`` types, they are considered relative to the ini-file.
203            In case the execution is happening without an ini-file defined,
204            they will be considered relative to the current working directory (for example with ``--override-ini``).
205
206            .. versionadded:: 7.0
207                The ``paths`` variable type.
208
209            .. versionadded:: 8.1
210                Use the current working directory to resolve ``paths`` and ``pathlist`` in the absence of an ini-file.
211
212            Defaults to ``string`` if ``None`` or not passed.
213        :param default:
214            Default value if no ini-file option exists but is queried.
215
216        The value of ini-variables can be retrieved via a call to
217        :py:func:`config.getini(name) <pytest.Config.getini>`.
218        """
219        assert type in (
220            None,
221            "string",
222            "paths",
223            "pathlist",
224            "args",
225            "linelist",
226            "bool",
227            "int",
228            "float",
229        )
230        if default is NOT_SET:
231            default = get_ini_default_for_type(type)
232
233        self._inidict[name] = (help, type, default)
234        self._ininames.append(name)

Parser for command line arguments and ini-file values.

:ivar extra_info: Dict of generic param -> value to display in case there's an error processing the command line arguments.

Parser( usage: str | None = None, processopt: Callable[[_pytest.config.argparsing.Argument], None] | None = None, *, _ispytest: bool = False)
42    def __init__(
43        self,
44        usage: str | None = None,
45        processopt: Callable[[Argument], None] | None = None,
46        *,
47        _ispytest: bool = False,
48    ) -> None:
49        check_ispytest(_ispytest)
50        self._anonymous = OptionGroup("Custom options", parser=self, _ispytest=True)
51        self._groups: list[OptionGroup] = []
52        self._processopt = processopt
53        self._usage = usage
54        self._inidict: dict[str, tuple[str, str | None, Any]] = {}
55        self._ininames: list[str] = []
56        self.extra_info: dict[str, Any] = {}
prog: str | None = None
extra_info: dict[str, typing.Any]
def processoption(self, option: _pytest.config.argparsing.Argument) -> None:
58    def processoption(self, option: Argument) -> None:
59        if self._processopt:
60            if option.dest:
61                self._processopt(option)
def getgroup( self, name: str, description: str = '', after: str | None = None) -> _pytest.config.argparsing.OptionGroup:
63    def getgroup(
64        self, name: str, description: str = "", after: str | None = None
65    ) -> OptionGroup:
66        """Get (or create) a named option Group.
67
68        :param name: Name of the option group.
69        :param description: Long description for --help output.
70        :param after: Name of another group, used for ordering --help output.
71        :returns: The option group.
72
73        The returned group object has an ``addoption`` method with the same
74        signature as :func:`parser.addoption <pytest.Parser.addoption>` but
75        will be shown in the respective group in the output of
76        ``pytest --help``.
77        """
78        for group in self._groups:
79            if group.name == name:
80                return group
81        group = OptionGroup(name, description, parser=self, _ispytest=True)
82        i = 0
83        for i, grp in enumerate(self._groups):
84            if grp.name == after:
85                break
86        self._groups.insert(i + 1, group)
87        return group

Get (or create) a named option Group.

Parameters
  • name: Name of the option group.
  • description: Long description for --help output.
  • after: Name of another group, used for ordering --help output. :returns: The option group.

The returned group object has an addoption method with the same signature as parser.addoption <pytest.Parser.addoption>() but will be shown in the respective group in the output of pytest --help.

def addoption(self, *opts: str, **attrs: Any) -> None:
 89    def addoption(self, *opts: str, **attrs: Any) -> None:
 90        """Register a command line option.
 91
 92        :param opts:
 93            Option names, can be short or long options.
 94        :param attrs:
 95            Same attributes as the argparse library's :meth:`add_argument()
 96            <argparse.ArgumentParser.add_argument>` function accepts.
 97
 98        After command line parsing, options are available on the pytest config
 99        object via ``config.option.NAME`` where ``NAME`` is usually set
100        by passing a ``dest`` attribute, for example
101        ``addoption("--long", dest="NAME", ...)``.
102        """
103        self._anonymous.addoption(*opts, **attrs)

Register a command line option.

Parameters
  • opts: Option names, can be short or long options.
  • attrs: Same attributes as the argparse library's add_argument() <argparse.ArgumentParser.add_argument>() function accepts.

After command line parsing, options are available on the pytest config object via config.option.NAME where NAME is usually set by passing a dest attribute, for example addoption("--long", dest="NAME", ...).

def parse( self, args: Sequence[str | os.PathLike[str]], namespace: argparse.Namespace | None = None) -> argparse.Namespace:
105    def parse(
106        self,
107        args: Sequence[str | os.PathLike[str]],
108        namespace: argparse.Namespace | None = None,
109    ) -> argparse.Namespace:
110        from _pytest._argcomplete import try_argcomplete
111
112        self.optparser = self._getparser()
113        try_argcomplete(self.optparser)
114        strargs = [os.fspath(x) for x in args]
115        return self.optparser.parse_args(strargs, namespace=namespace)
def parse_setoption( self, args: Sequence[str | os.PathLike[str]], option: argparse.Namespace, namespace: argparse.Namespace | None = None) -> list[str]:
136    def parse_setoption(
137        self,
138        args: Sequence[str | os.PathLike[str]],
139        option: argparse.Namespace,
140        namespace: argparse.Namespace | None = None,
141    ) -> list[str]:
142        parsedoption = self.parse(args, namespace=namespace)
143        for name, value in parsedoption.__dict__.items():
144            setattr(option, name, value)
145        return cast(list[str], getattr(parsedoption, FILE_OR_DIR))
def parse_known_args( self, args: Sequence[str | os.PathLike[str]], namespace: argparse.Namespace | None = None) -> argparse.Namespace:
147    def parse_known_args(
148        self,
149        args: Sequence[str | os.PathLike[str]],
150        namespace: argparse.Namespace | None = None,
151    ) -> argparse.Namespace:
152        """Parse the known arguments at this point.
153
154        :returns: An argparse namespace object.
155        """
156        return self.parse_known_and_unknown_args(args, namespace=namespace)[0]

Parse the known arguments at this point.

:returns: An argparse namespace object.

def parse_known_and_unknown_args( self, args: Sequence[str | os.PathLike[str]], namespace: argparse.Namespace | None = None) -> tuple[argparse.Namespace, list[str]]:
158    def parse_known_and_unknown_args(
159        self,
160        args: Sequence[str | os.PathLike[str]],
161        namespace: argparse.Namespace | None = None,
162    ) -> tuple[argparse.Namespace, list[str]]:
163        """Parse the known arguments at this point, and also return the
164        remaining unknown arguments.
165
166        :returns:
167            A tuple containing an argparse namespace object for the known
168            arguments, and a list of the unknown arguments.
169        """
170        optparser = self._getparser()
171        strargs = [os.fspath(x) for x in args]
172        return optparser.parse_known_args(strargs, namespace=namespace)

Parse the known arguments at this point, and also return the remaining unknown arguments.

:returns: A tuple containing an argparse namespace object for the known arguments, and a list of the unknown arguments.

def addini( self, name: str, help: str, type: Optional[Literal['string', 'paths', 'pathlist', 'args', 'linelist', 'bool']] = None, default: Any = <notset>) -> None:
174    def addini(
175        self,
176        name: str,
177        help: str,
178        type: Literal["string", "paths", "pathlist", "args", "linelist", "bool"]
179        | None = None,
180        default: Any = NOT_SET,
181    ) -> None:
182        """Register an ini-file option.
183
184        :param name:
185            Name of the ini-variable.
186        :param type:
187            Type of the variable. Can be:
188
189                * ``string``: a string
190                * ``bool``: a boolean
191                * ``args``: a list of strings, separated as in a shell
192                * ``linelist``: a list of strings, separated by line breaks
193                * ``paths``: a list of :class:`pathlib.Path`, separated as in a shell
194                * ``pathlist``: a list of ``py.path``, separated as in a shell
195                * ``int``: an integer
196                * ``float``: a floating-point number
197
198                .. versionadded:: 8.4
199
200                    The ``float`` and ``int`` types.
201
202            For ``paths`` and ``pathlist`` types, they are considered relative to the ini-file.
203            In case the execution is happening without an ini-file defined,
204            they will be considered relative to the current working directory (for example with ``--override-ini``).
205
206            .. versionadded:: 7.0
207                The ``paths`` variable type.
208
209            .. versionadded:: 8.1
210                Use the current working directory to resolve ``paths`` and ``pathlist`` in the absence of an ini-file.
211
212            Defaults to ``string`` if ``None`` or not passed.
213        :param default:
214            Default value if no ini-file option exists but is queried.
215
216        The value of ini-variables can be retrieved via a call to
217        :py:func:`config.getini(name) <pytest.Config.getini>`.
218        """
219        assert type in (
220            None,
221            "string",
222            "paths",
223            "pathlist",
224            "args",
225            "linelist",
226            "bool",
227            "int",
228            "float",
229        )
230        if default is NOT_SET:
231            default = get_ini_default_for_type(type)
232
233        self._inidict[name] = (help, type, default)
234        self._ininames.append(name)

Register an ini-file option.

Parameters
  • name: Name of the ini-variable.
  • type: Type of the variable. Can be:

    * ``string``: a string
    * ``bool``: a boolean
    * ``args``: a list of strings, separated as in a shell
    * ``linelist``: a list of strings, separated by line breaks
    * ``paths``: a list of `pathlib.Path`, separated as in a shell
    * ``pathlist``: a list of ``py.path``, separated as in a shell
    * ``int``: an integer
    * ``float``: a floating-point number
    
    *New in version 8.4:*
    The ``float`` and ``int`` types.
    

    For paths and pathlist types, they are considered relative to the ini-file. In case the execution is happening without an ini-file defined, they will be considered relative to the current working directory (for example with --override-ini).

    New in version 7.0: The paths variable type.

    New in version 8.1: Use the current working directory to resolve paths and pathlist in the absence of an ini-file.

    Defaults to string if None or not passed.

  • default: Default value if no ini-file option exists but is queried.

The value of ini-variables can be retrieved via a call to config.getini(name) <pytest.Config.getini>().

class PytestAssertRewriteWarning(PytestWarning):
20from _pytest.config import PytestPluginManager

Warning emitted by the pytest assert rewrite module.

class PytestCacheWarning(PytestWarning):
27from _pytest.fixtures import FixtureDef

Warning emitted by the cache plugin in various situations.

class PytestCollectionWarning(PytestWarning):
41from _pytest.mark import MarkGenerator

Warning emitted when pytest is not able to collect a file or symbol in a module.

class PytestConfigWarning(PytestWarning):
34from _pytest.logging import LogCaptureFixture

Warning emitted for configuration issues.

class PytestDeprecationWarning(PytestWarning, builtins.DeprecationWarning):
48from _pytest.outcomes import exit

Warning class for features that will be removed in a future version.

class PytestExperimentalApiWarning(PytestWarning, builtins.FutureWarning):
60from _pytest.python import Metafunc

Warning category used to denote experiments in pytest.

Use sparingly as the API might change or even be removed completely in a future version.

@classmethod
def simple(cls, apiname: str) -> PytestExperimentalApiWarning:
70    @classmethod
71    def simple(cls, apiname: str) -> PytestExperimentalApiWarning:
72        return cls(f"{apiname} is an experimental api that may change over time")
class PytestFDWarning(PytestWarning):
138    "PytestUnhandledThreadExceptionWarning",

When the lsof plugin finds leaked fds.

@final
class PytestPluginManager(pluggy._manager.PluginManager):
392@final
393class PytestPluginManager(PluginManager):
394    """A :py:class:`pluggy.PluginManager <pluggy.PluginManager>` with
395    additional pytest-specific functionality:
396
397    * Loading plugins from the command line, ``PYTEST_PLUGINS`` env variable and
398      ``pytest_plugins`` global variables found in plugins being loaded.
399    * ``conftest.py`` loading during start-up.
400    """
401
402    def __init__(self) -> None:
403        from _pytest.assertion import DummyRewriteHook
404        from _pytest.assertion import RewriteHook
405
406        super().__init__("pytest")
407
408        # -- State related to local conftest plugins.
409        # All loaded conftest modules.
410        self._conftest_plugins: set[types.ModuleType] = set()
411        # All conftest modules applicable for a directory.
412        # This includes the directory's own conftest modules as well
413        # as those of its parent directories.
414        self._dirpath2confmods: dict[pathlib.Path, list[types.ModuleType]] = {}
415        # Cutoff directory above which conftests are no longer discovered.
416        self._confcutdir: pathlib.Path | None = None
417        # If set, conftest loading is skipped.
418        self._noconftest = False
419
420        # _getconftestmodules()'s call to _get_directory() causes a stat
421        # storm when it's called potentially thousands of times in a test
422        # session (#9478), often with the same path, so cache it.
423        self._get_directory = lru_cache(256)(_get_directory)
424
425        # plugins that were explicitly skipped with pytest.skip
426        # list of (module name, skip reason)
427        # previously we would issue a warning when a plugin was skipped, but
428        # since we refactored warnings as first citizens of Config, they are
429        # just stored here to be used later.
430        self.skipped_plugins: list[tuple[str, str]] = []
431
432        self.add_hookspecs(_pytest.hookspec)
433        self.register(self)
434        if os.environ.get("PYTEST_DEBUG"):
435            err: IO[str] = sys.stderr
436            encoding: str = getattr(err, "encoding", "utf8")
437            try:
438                err = open(
439                    os.dup(err.fileno()),
440                    mode=err.mode,
441                    buffering=1,
442                    encoding=encoding,
443                )
444            except Exception:
445                pass
446            self.trace.root.setwriter(err.write)
447            self.enable_tracing()
448
449        # Config._consider_importhook will set a real object if required.
450        self.rewrite_hook: RewriteHook = DummyRewriteHook()
451        # Used to know when we are importing conftests after the pytest_configure stage.
452        self._configured = False
453
454    def parse_hookimpl_opts(
455        self, plugin: _PluggyPlugin, name: str
456    ) -> HookimplOpts | None:
457        """:meta private:"""
458        # pytest hooks are always prefixed with "pytest_",
459        # so we avoid accessing possibly non-readable attributes
460        # (see issue #1073).
461        if not name.startswith("pytest_"):
462            return None
463        # Ignore names which cannot be hooks.
464        if name == "pytest_plugins":
465            return None
466
467        opts = super().parse_hookimpl_opts(plugin, name)
468        if opts is not None:
469            return opts
470
471        method = getattr(plugin, name)
472        # Consider only actual functions for hooks (#3775).
473        if not inspect.isroutine(method):
474            return None
475        # Collect unmarked hooks as long as they have the `pytest_' prefix.
476        legacy = _get_legacy_hook_marks(
477            method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper")
478        )
479        return cast(HookimplOpts, legacy)
480
481    def parse_hookspec_opts(self, module_or_class, name: str) -> HookspecOpts | None:
482        """:meta private:"""
483        opts = super().parse_hookspec_opts(module_or_class, name)
484        if opts is None:
485            method = getattr(module_or_class, name)
486            if name.startswith("pytest_"):
487                legacy = _get_legacy_hook_marks(
488                    method, "spec", ("firstresult", "historic")
489                )
490                opts = cast(HookspecOpts, legacy)
491        return opts
492
493    def register(self, plugin: _PluggyPlugin, name: str | None = None) -> str | None:
494        if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS:
495            warnings.warn(
496                PytestConfigWarning(
497                    "{} plugin has been merged into the core, "
498                    "please remove it from your requirements.".format(
499                        name.replace("_", "-")
500                    )
501                )
502            )
503            return None
504        plugin_name = super().register(plugin, name)
505        if plugin_name is not None:
506            self.hook.pytest_plugin_registered.call_historic(
507                kwargs=dict(
508                    plugin=plugin,
509                    plugin_name=plugin_name,
510                    manager=self,
511                )
512            )
513
514            if isinstance(plugin, types.ModuleType):
515                self.consider_module(plugin)
516        return plugin_name
517
518    def getplugin(self, name: str):
519        # Support deprecated naming because plugins (xdist e.g.) use it.
520        plugin: _PluggyPlugin | None = self.get_plugin(name)
521        return plugin
522
523    def hasplugin(self, name: str) -> bool:
524        """Return whether a plugin with the given name is registered."""
525        return bool(self.get_plugin(name))
526
527    def pytest_configure(self, config: Config) -> None:
528        """:meta private:"""
529        # XXX now that the pluginmanager exposes hookimpl(tryfirst...)
530        # we should remove tryfirst/trylast as markers.
531        config.addinivalue_line(
532            "markers",
533            "tryfirst: mark a hook implementation function such that the "
534            "plugin machinery will try to call it first/as early as possible. "
535            "DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.",
536        )
537        config.addinivalue_line(
538            "markers",
539            "trylast: mark a hook implementation function such that the "
540            "plugin machinery will try to call it last/as late as possible. "
541            "DEPRECATED, use @pytest.hookimpl(trylast=True) instead.",
542        )
543        self._configured = True
544
545    #
546    # Internal API for local conftest plugin handling.
547    #
548    def _set_initial_conftests(
549        self,
550        args: Sequence[str | pathlib.Path],
551        pyargs: bool,
552        noconftest: bool,
553        rootpath: pathlib.Path,
554        confcutdir: pathlib.Path | None,
555        invocation_dir: pathlib.Path,
556        importmode: ImportMode | str,
557        *,
558        consider_namespace_packages: bool,
559    ) -> None:
560        """Load initial conftest files given a preparsed "namespace".
561
562        As conftest files may add their own command line options which have
563        arguments ('--my-opt somepath') we might get some false positives.
564        All builtin and 3rd party plugins will have been loaded, however, so
565        common options will not confuse our logic here.
566        """
567        self._confcutdir = (
568            absolutepath(invocation_dir / confcutdir) if confcutdir else None
569        )
570        self._noconftest = noconftest
571        self._using_pyargs = pyargs
572        foundanchor = False
573        for initial_path in args:
574            path = str(initial_path)
575            # remove node-id syntax
576            i = path.find("::")
577            if i != -1:
578                path = path[:i]
579            anchor = absolutepath(invocation_dir / path)
580
581            # Ensure we do not break if what appears to be an anchor
582            # is in fact a very long option (#10169, #11394).
583            if safe_exists(anchor):
584                self._try_load_conftest(
585                    anchor,
586                    importmode,
587                    rootpath,
588                    consider_namespace_packages=consider_namespace_packages,
589                )
590                foundanchor = True
591        if not foundanchor:
592            self._try_load_conftest(
593                invocation_dir,
594                importmode,
595                rootpath,
596                consider_namespace_packages=consider_namespace_packages,
597            )
598
599    def _is_in_confcutdir(self, path: pathlib.Path) -> bool:
600        """Whether to consider the given path to load conftests from."""
601        if self._confcutdir is None:
602            return True
603        # The semantics here are literally:
604        #   Do not load a conftest if it is found upwards from confcut dir.
605        # But this is *not* the same as:
606        #   Load only conftests from confcutdir or below.
607        # At first glance they might seem the same thing, however we do support use cases where
608        # we want to load conftests that are not found in confcutdir or below, but are found
609        # in completely different directory hierarchies like packages installed
610        # in out-of-source trees.
611        # (see #9767 for a regression where the logic was inverted).
612        return path not in self._confcutdir.parents
613
614    def _try_load_conftest(
615        self,
616        anchor: pathlib.Path,
617        importmode: str | ImportMode,
618        rootpath: pathlib.Path,
619        *,
620        consider_namespace_packages: bool,
621    ) -> None:
622        self._loadconftestmodules(
623            anchor,
624            importmode,
625            rootpath,
626            consider_namespace_packages=consider_namespace_packages,
627        )
628        # let's also consider test* subdirs
629        if anchor.is_dir():
630            for x in anchor.glob("test*"):
631                if x.is_dir():
632                    self._loadconftestmodules(
633                        x,
634                        importmode,
635                        rootpath,
636                        consider_namespace_packages=consider_namespace_packages,
637                    )
638
639    def _loadconftestmodules(
640        self,
641        path: pathlib.Path,
642        importmode: str | ImportMode,
643        rootpath: pathlib.Path,
644        *,
645        consider_namespace_packages: bool,
646    ) -> None:
647        if self._noconftest:
648            return
649
650        directory = self._get_directory(path)
651
652        # Optimization: avoid repeated searches in the same directory.
653        # Assumes always called with same importmode and rootpath.
654        if directory in self._dirpath2confmods:
655            return
656
657        clist = []
658        for parent in reversed((directory, *directory.parents)):
659            if self._is_in_confcutdir(parent):
660                conftestpath = parent / "conftest.py"
661                if conftestpath.is_file():
662                    mod = self._importconftest(
663                        conftestpath,
664                        importmode,
665                        rootpath,
666                        consider_namespace_packages=consider_namespace_packages,
667                    )
668                    clist.append(mod)
669        self._dirpath2confmods[directory] = clist
670
671    def _getconftestmodules(self, path: pathlib.Path) -> Sequence[types.ModuleType]:
672        directory = self._get_directory(path)
673        return self._dirpath2confmods.get(directory, ())
674
675    def _rget_with_confmod(
676        self,
677        name: str,
678        path: pathlib.Path,
679    ) -> tuple[types.ModuleType, Any]:
680        modules = self._getconftestmodules(path)
681        for mod in reversed(modules):
682            try:
683                return mod, getattr(mod, name)
684            except AttributeError:
685                continue
686        raise KeyError(name)
687
688    def _importconftest(
689        self,
690        conftestpath: pathlib.Path,
691        importmode: str | ImportMode,
692        rootpath: pathlib.Path,
693        *,
694        consider_namespace_packages: bool,
695    ) -> types.ModuleType:
696        conftestpath_plugin_name = str(conftestpath)
697        existing = self.get_plugin(conftestpath_plugin_name)
698        if existing is not None:
699            return cast(types.ModuleType, existing)
700
701        # conftest.py files there are not in a Python package all have module
702        # name "conftest", and thus conflict with each other. Clear the existing
703        # before loading the new one, otherwise the existing one will be
704        # returned from the module cache.
705        pkgpath = resolve_package_path(conftestpath)
706        if pkgpath is None:
707            try:
708                del sys.modules[conftestpath.stem]
709            except KeyError:
710                pass
711
712        try:
713            mod = import_path(
714                conftestpath,
715                mode=importmode,
716                root=rootpath,
717                consider_namespace_packages=consider_namespace_packages,
718            )
719        except Exception as e:
720            assert e.__traceback__ is not None
721            raise ConftestImportFailure(conftestpath, cause=e) from e
722
723        self._check_non_top_pytest_plugins(mod, conftestpath)
724
725        self._conftest_plugins.add(mod)
726        dirpath = conftestpath.parent
727        if dirpath in self._dirpath2confmods:
728            for path, mods in self._dirpath2confmods.items():
729                if dirpath in path.parents or path == dirpath:
730                    if mod in mods:
731                        raise AssertionError(
732                            f"While trying to load conftest path {conftestpath!s}, "
733                            f"found that the module {mod} is already loaded with path {mod.__file__}. "
734                            "This is not supposed to happen. Please report this issue to pytest."
735                        )
736                    mods.append(mod)
737        self.trace(f"loading conftestmodule {mod!r}")
738        self.consider_conftest(mod, registration_name=conftestpath_plugin_name)
739        return mod
740
741    def _check_non_top_pytest_plugins(
742        self,
743        mod: types.ModuleType,
744        conftestpath: pathlib.Path,
745    ) -> None:
746        if (
747            hasattr(mod, "pytest_plugins")
748            and self._configured
749            and not self._using_pyargs
750        ):
751            msg = (
752                "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported:\n"
753                "It affects the entire test suite instead of just below the conftest as expected.\n"
754                "  {}\n"
755                "Please move it to a top level conftest file at the rootdir:\n"
756                "  {}\n"
757                "For more information, visit:\n"
758                "  https://docs.pytest.org/en/stable/deprecations.html#pytest-plugins-in-non-top-level-conftest-files"
759            )
760            fail(msg.format(conftestpath, self._confcutdir), pytrace=False)
761
762    #
763    # API for bootstrapping plugin loading
764    #
765    #
766
767    def consider_preparse(
768        self, args: Sequence[str], *, exclude_only: bool = False
769    ) -> None:
770        """:meta private:"""
771        i = 0
772        n = len(args)
773        while i < n:
774            opt = args[i]
775            i += 1
776            if isinstance(opt, str):
777                if opt == "-p":
778                    try:
779                        parg = args[i]
780                    except IndexError:
781                        return
782                    i += 1
783                elif opt.startswith("-p"):
784                    parg = opt[2:]
785                else:
786                    continue
787                parg = parg.strip()
788                if exclude_only and not parg.startswith("no:"):
789                    continue
790                self.consider_pluginarg(parg)
791
792    def consider_pluginarg(self, arg: str) -> None:
793        """:meta private:"""
794        if arg.startswith("no:"):
795            name = arg[3:]
796            if name in essential_plugins:
797                raise UsageError(f"plugin {name} cannot be disabled")
798
799            # PR #4304: remove stepwise if cacheprovider is blocked.
800            if name == "cacheprovider":
801                self.set_blocked("stepwise")
802                self.set_blocked("pytest_stepwise")
803
804            self.set_blocked(name)
805            if not name.startswith("pytest_"):
806                self.set_blocked("pytest_" + name)
807        else:
808            name = arg
809            # Unblock the plugin.
810            self.unblock(name)
811            if not name.startswith("pytest_"):
812                self.unblock("pytest_" + name)
813            self.import_plugin(arg, consider_entry_points=True)
814
815    def consider_conftest(
816        self, conftestmodule: types.ModuleType, registration_name: str
817    ) -> None:
818        """:meta private:"""
819        self.register(conftestmodule, name=registration_name)
820
821    def consider_env(self) -> None:
822        """:meta private:"""
823        self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
824
825    def consider_module(self, mod: types.ModuleType) -> None:
826        """:meta private:"""
827        self._import_plugin_specs(getattr(mod, "pytest_plugins", []))
828
829    def _import_plugin_specs(
830        self, spec: None | types.ModuleType | str | Sequence[str]
831    ) -> None:
832        plugins = _get_plugin_specs_as_list(spec)
833        for import_spec in plugins:
834            self.import_plugin(import_spec)
835
836    def import_plugin(self, modname: str, consider_entry_points: bool = False) -> None:
837        """Import a plugin with ``modname``.
838
839        If ``consider_entry_points`` is True, entry point names are also
840        considered to find a plugin.
841        """
842        # Most often modname refers to builtin modules, e.g. "pytester",
843        # "terminal" or "capture".  Those plugins are registered under their
844        # basename for historic purposes but must be imported with the
845        # _pytest prefix.
846        assert isinstance(modname, str), (
847            f"module name as text required, got {modname!r}"
848        )
849        if self.is_blocked(modname) or self.get_plugin(modname) is not None:
850            return
851
852        importspec = "_pytest." + modname if modname in builtin_plugins else modname
853        self.rewrite_hook.mark_rewrite(importspec)
854
855        if consider_entry_points:
856            loaded = self.load_setuptools_entrypoints("pytest11", name=modname)
857            if loaded:
858                return
859
860        try:
861            __import__(importspec)
862        except ImportError as e:
863            raise ImportError(
864                f'Error importing plugin "{modname}": {e.args[0]}'
865            ).with_traceback(e.__traceback__) from e
866
867        except Skipped as e:
868            self.skipped_plugins.append((modname, e.msg or ""))
869        else:
870            mod = sys.modules[importspec]
871            self.register(mod, modname)

A pluggy.PluginManager <pluggy.PluginManager> with additional pytest-specific functionality:

  • Loading plugins from the command line, PYTEST_PLUGINS env variable and pytest_plugins global variables found in plugins being loaded.
  • conftest.py loading during start-up.
skipped_plugins: list[tuple[str, str]]
rewrite_hook: 'RewriteHook'
def parse_hookimpl_opts(self, plugin: object, name: str) -> pluggy._hooks.HookimplOpts | None:
454    def parse_hookimpl_opts(
455        self, plugin: _PluggyPlugin, name: str
456    ) -> HookimplOpts | None:
457        """:meta private:"""
458        # pytest hooks are always prefixed with "pytest_",
459        # so we avoid accessing possibly non-readable attributes
460        # (see issue #1073).
461        if not name.startswith("pytest_"):
462            return None
463        # Ignore names which cannot be hooks.
464        if name == "pytest_plugins":
465            return None
466
467        opts = super().parse_hookimpl_opts(plugin, name)
468        if opts is not None:
469            return opts
470
471        method = getattr(plugin, name)
472        # Consider only actual functions for hooks (#3775).
473        if not inspect.isroutine(method):
474            return None
475        # Collect unmarked hooks as long as they have the `pytest_' prefix.
476        legacy = _get_legacy_hook_marks(
477            method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper")
478        )
479        return cast(HookimplOpts, legacy)

:meta private:

def parse_hookspec_opts(self, module_or_class, name: str) -> pluggy._hooks.HookspecOpts | None:
481    def parse_hookspec_opts(self, module_or_class, name: str) -> HookspecOpts | None:
482        """:meta private:"""
483        opts = super().parse_hookspec_opts(module_or_class, name)
484        if opts is None:
485            method = getattr(module_or_class, name)
486            if name.startswith("pytest_"):
487                legacy = _get_legacy_hook_marks(
488                    method, "spec", ("firstresult", "historic")
489                )
490                opts = cast(HookspecOpts, legacy)
491        return opts

:meta private:

def register(self, plugin: object, name: str | None = None) -> str | None:
493    def register(self, plugin: _PluggyPlugin, name: str | None = None) -> str | None:
494        if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS:
495            warnings.warn(
496                PytestConfigWarning(
497                    "{} plugin has been merged into the core, "
498                    "please remove it from your requirements.".format(
499                        name.replace("_", "-")
500                    )
501                )
502            )
503            return None
504        plugin_name = super().register(plugin, name)
505        if plugin_name is not None:
506            self.hook.pytest_plugin_registered.call_historic(
507                kwargs=dict(
508                    plugin=plugin,
509                    plugin_name=plugin_name,
510                    manager=self,
511                )
512            )
513
514            if isinstance(plugin, types.ModuleType):
515                self.consider_module(plugin)
516        return plugin_name

Register a plugin and return its name.

Parameters
  • name: The name under which to register the plugin. If not specified, a name is generated using get_canonical_name().

:returns: The plugin name. If the name is blocked from registering, returns None.

If the plugin is already registered, raises a ValueError.

def getplugin(self, name: str):
518    def getplugin(self, name: str):
519        # Support deprecated naming because plugins (xdist e.g.) use it.
520        plugin: _PluggyPlugin | None = self.get_plugin(name)
521        return plugin
def hasplugin(self, name: str) -> bool:
523    def hasplugin(self, name: str) -> bool:
524        """Return whether a plugin with the given name is registered."""
525        return bool(self.get_plugin(name))

Return whether a plugin with the given name is registered.

def pytest_configure(self, config: _pytest.config.Config) -> None:
527    def pytest_configure(self, config: Config) -> None:
528        """:meta private:"""
529        # XXX now that the pluginmanager exposes hookimpl(tryfirst...)
530        # we should remove tryfirst/trylast as markers.
531        config.addinivalue_line(
532            "markers",
533            "tryfirst: mark a hook implementation function such that the "
534            "plugin machinery will try to call it first/as early as possible. "
535            "DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.",
536        )
537        config.addinivalue_line(
538            "markers",
539            "trylast: mark a hook implementation function such that the "
540            "plugin machinery will try to call it last/as late as possible. "
541            "DEPRECATED, use @pytest.hookimpl(trylast=True) instead.",
542        )
543        self._configured = True

:meta private:

def consider_preparse(self, args: Sequence[str], *, exclude_only: bool = False) -> None:
767    def consider_preparse(
768        self, args: Sequence[str], *, exclude_only: bool = False
769    ) -> None:
770        """:meta private:"""
771        i = 0
772        n = len(args)
773        while i < n:
774            opt = args[i]
775            i += 1
776            if isinstance(opt, str):
777                if opt == "-p":
778                    try:
779                        parg = args[i]
780                    except IndexError:
781                        return
782                    i += 1
783                elif opt.startswith("-p"):
784                    parg = opt[2:]
785                else:
786                    continue
787                parg = parg.strip()
788                if exclude_only and not parg.startswith("no:"):
789                    continue
790                self.consider_pluginarg(parg)

:meta private:

def consider_pluginarg(self, arg: str) -> None:
792    def consider_pluginarg(self, arg: str) -> None:
793        """:meta private:"""
794        if arg.startswith("no:"):
795            name = arg[3:]
796            if name in essential_plugins:
797                raise UsageError(f"plugin {name} cannot be disabled")
798
799            # PR #4304: remove stepwise if cacheprovider is blocked.
800            if name == "cacheprovider":
801                self.set_blocked("stepwise")
802                self.set_blocked("pytest_stepwise")
803
804            self.set_blocked(name)
805            if not name.startswith("pytest_"):
806                self.set_blocked("pytest_" + name)
807        else:
808            name = arg
809            # Unblock the plugin.
810            self.unblock(name)
811            if not name.startswith("pytest_"):
812                self.unblock("pytest_" + name)
813            self.import_plugin(arg, consider_entry_points=True)

:meta private:

def consider_conftest(self, conftestmodule: module, registration_name: str) -> None:
815    def consider_conftest(
816        self, conftestmodule: types.ModuleType, registration_name: str
817    ) -> None:
818        """:meta private:"""
819        self.register(conftestmodule, name=registration_name)

:meta private:

def consider_env(self) -> None:
821    def consider_env(self) -> None:
822        """:meta private:"""
823        self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))

:meta private:

def consider_module(self, mod: module) -> None:
825    def consider_module(self, mod: types.ModuleType) -> None:
826        """:meta private:"""
827        self._import_plugin_specs(getattr(mod, "pytest_plugins", []))

:meta private:

def import_plugin(self, modname: str, consider_entry_points: bool = False) -> None:
836    def import_plugin(self, modname: str, consider_entry_points: bool = False) -> None:
837        """Import a plugin with ``modname``.
838
839        If ``consider_entry_points`` is True, entry point names are also
840        considered to find a plugin.
841        """
842        # Most often modname refers to builtin modules, e.g. "pytester",
843        # "terminal" or "capture".  Those plugins are registered under their
844        # basename for historic purposes but must be imported with the
845        # _pytest prefix.
846        assert isinstance(modname, str), (
847            f"module name as text required, got {modname!r}"
848        )
849        if self.is_blocked(modname) or self.get_plugin(modname) is not None:
850            return
851
852        importspec = "_pytest." + modname if modname in builtin_plugins else modname
853        self.rewrite_hook.mark_rewrite(importspec)
854
855        if consider_entry_points:
856            loaded = self.load_setuptools_entrypoints("pytest11", name=modname)
857            if loaded:
858                return
859
860        try:
861            __import__(importspec)
862        except ImportError as e:
863            raise ImportError(
864                f'Error importing plugin "{modname}": {e.args[0]}'
865            ).with_traceback(e.__traceback__) from e
866
867        except Skipped as e:
868            self.skipped_plugins.append((modname, e.msg or ""))
869        else:
870            mod = sys.modules[importspec]
871            self.register(mod, modname)

Import a plugin with modname.

If consider_entry_points is True, entry point names are also considered to find a plugin.

class PytestRemovedIn9Warning(PytestDeprecationWarning):
54from _pytest.pytester import LineMatcher

Warning class for features that will be removed in pytest 9.

class PytestReturnNotNoneWarning(PytestWarning):
75from _pytest.terminal import TerminalReporter

Warning emitted when a test function returns a value other than None.

See :ref:return-not-none for details.

class PytestUnhandledThreadExceptionWarning(PytestWarning):
108    "ExceptionInfo",

An unhandled exception occurred in a ~threading.Thread.

Such exceptions don't propagate normally.

class PytestUnknownMarkWarning(PytestWarning):
86from _pytest.warning_types import PytestReturnNotNoneWarning

Warning emitted on use of unknown markers.

See :ref:mark for details.

class PytestUnraisableExceptionWarning(PytestWarning):
96__all__ = [

An unraisable exception was reported.

Unraisable exceptions are exceptions raised in __del__ <object.__del__>() implementations and similar situations when the exception cannot be raised as normal.

class PytestWarning(builtins.UserWarning):
14from _pytest.config import Config

Base class for all warnings emitted by pytest.

@final
class Pytester:
 649@final
 650class Pytester:
 651    """
 652    Facilities to write tests/configuration files, execute pytest in isolation, and match
 653    against expected output, perfect for black-box testing of pytest plugins.
 654
 655    It attempts to isolate the test run from external factors as much as possible, modifying
 656    the current working directory to :attr:`path` and environment variables during initialization.
 657    """
 658
 659    __test__ = False
 660
 661    CLOSE_STDIN: Final = NOTSET
 662
 663    class TimeoutExpired(Exception):
 664        pass
 665
 666    def __init__(
 667        self,
 668        request: FixtureRequest,
 669        tmp_path_factory: TempPathFactory,
 670        monkeypatch: MonkeyPatch,
 671        *,
 672        _ispytest: bool = False,
 673    ) -> None:
 674        check_ispytest(_ispytest)
 675        self._request = request
 676        self._mod_collections: WeakKeyDictionary[Collector, list[Item | Collector]] = (
 677            WeakKeyDictionary()
 678        )
 679        if request.function:
 680            name: str = request.function.__name__
 681        else:
 682            name = request.node.name
 683        self._name = name
 684        self._path: Path = tmp_path_factory.mktemp(name, numbered=True)
 685        #: A list of plugins to use with :py:meth:`parseconfig` and
 686        #: :py:meth:`runpytest`.  Initially this is an empty list but plugins can
 687        #: be added to the list.  The type of items to add to the list depends on
 688        #: the method using them so refer to them for details.
 689        self.plugins: list[str | _PluggyPlugin] = []
 690        self._sys_path_snapshot = SysPathsSnapshot()
 691        self._sys_modules_snapshot = self.__take_sys_modules_snapshot()
 692        self._request.addfinalizer(self._finalize)
 693        self._method = self._request.config.getoption("--runpytest")
 694        self._test_tmproot = tmp_path_factory.mktemp(f"tmp-{name}", numbered=True)
 695
 696        self._monkeypatch = mp = monkeypatch
 697        self.chdir()
 698        mp.setenv("PYTEST_DEBUG_TEMPROOT", str(self._test_tmproot))
 699        # Ensure no unexpected caching via tox.
 700        mp.delenv("TOX_ENV_DIR", raising=False)
 701        # Discard outer pytest options.
 702        mp.delenv("PYTEST_ADDOPTS", raising=False)
 703        # Ensure no user config is used.
 704        tmphome = str(self.path)
 705        mp.setenv("HOME", tmphome)
 706        mp.setenv("USERPROFILE", tmphome)
 707        # Do not use colors for inner runs by default.
 708        mp.setenv("PY_COLORS", "0")
 709
 710    @property
 711    def path(self) -> Path:
 712        """Temporary directory path used to create files/run tests from, etc."""
 713        return self._path
 714
 715    def __repr__(self) -> str:
 716        return f"<Pytester {self.path!r}>"
 717
 718    def _finalize(self) -> None:
 719        """
 720        Clean up global state artifacts.
 721
 722        Some methods modify the global interpreter state and this tries to
 723        clean this up. It does not remove the temporary directory however so
 724        it can be looked at after the test run has finished.
 725        """
 726        self._sys_modules_snapshot.restore()
 727        self._sys_path_snapshot.restore()
 728
 729    def __take_sys_modules_snapshot(self) -> SysModulesSnapshot:
 730        # Some zope modules used by twisted-related tests keep internal state
 731        # and can't be deleted; we had some trouble in the past with
 732        # `zope.interface` for example.
 733        #
 734        # Preserve readline due to https://bugs.python.org/issue41033.
 735        # pexpect issues a SIGWINCH.
 736        def preserve_module(name):
 737            return name.startswith(("zope", "readline"))
 738
 739        return SysModulesSnapshot(preserve=preserve_module)
 740
 741    def make_hook_recorder(self, pluginmanager: PytestPluginManager) -> HookRecorder:
 742        """Create a new :class:`HookRecorder` for a :class:`PytestPluginManager`."""
 743        pluginmanager.reprec = reprec = HookRecorder(pluginmanager, _ispytest=True)  # type: ignore[attr-defined]
 744        self._request.addfinalizer(reprec.finish_recording)
 745        return reprec
 746
 747    def chdir(self) -> None:
 748        """Cd into the temporary directory.
 749
 750        This is done automatically upon instantiation.
 751        """
 752        self._monkeypatch.chdir(self.path)
 753
 754    def _makefile(
 755        self,
 756        ext: str,
 757        lines: Sequence[Any | bytes],
 758        files: dict[str, str],
 759        encoding: str = "utf-8",
 760    ) -> Path:
 761        items = list(files.items())
 762
 763        if ext is None:
 764            raise TypeError("ext must not be None")
 765
 766        if ext and not ext.startswith("."):
 767            raise ValueError(
 768                f"pytester.makefile expects a file extension, try .{ext} instead of {ext}"
 769            )
 770
 771        def to_text(s: Any | bytes) -> str:
 772            return s.decode(encoding) if isinstance(s, bytes) else str(s)
 773
 774        if lines:
 775            source = "\n".join(to_text(x) for x in lines)
 776            basename = self._name
 777            items.insert(0, (basename, source))
 778
 779        ret = None
 780        for basename, value in items:
 781            p = self.path.joinpath(basename).with_suffix(ext)
 782            p.parent.mkdir(parents=True, exist_ok=True)
 783            source_ = Source(value)
 784            source = "\n".join(to_text(line) for line in source_.lines)
 785            p.write_text(source.strip(), encoding=encoding)
 786            if ret is None:
 787                ret = p
 788        assert ret is not None
 789        return ret
 790
 791    def makefile(self, ext: str, *args: str, **kwargs: str) -> Path:
 792        r"""Create new text file(s) in the test directory.
 793
 794        :param ext:
 795            The extension the file(s) should use, including the dot, e.g. `.py`.
 796        :param args:
 797            All args are treated as strings and joined using newlines.
 798            The result is written as contents to the file.  The name of the
 799            file is based on the test function requesting this fixture.
 800        :param kwargs:
 801            Each keyword is the name of a file, while the value of it will
 802            be written as contents of the file.
 803        :returns:
 804            The first created file.
 805
 806        Examples:
 807
 808        .. code-block:: python
 809
 810            pytester.makefile(".txt", "line1", "line2")
 811
 812            pytester.makefile(".ini", pytest="[pytest]\naddopts=-rs\n")
 813
 814        To create binary files, use :meth:`pathlib.Path.write_bytes` directly:
 815
 816        .. code-block:: python
 817
 818            filename = pytester.path.joinpath("foo.bin")
 819            filename.write_bytes(b"...")
 820        """
 821        return self._makefile(ext, args, kwargs)
 822
 823    def makeconftest(self, source: str) -> Path:
 824        """Write a conftest.py file.
 825
 826        :param source: The contents.
 827        :returns: The conftest.py file.
 828        """
 829        return self.makepyfile(conftest=source)
 830
 831    def makeini(self, source: str) -> Path:
 832        """Write a tox.ini file.
 833
 834        :param source: The contents.
 835        :returns: The tox.ini file.
 836        """
 837        return self.makefile(".ini", tox=source)
 838
 839    def getinicfg(self, source: str) -> SectionWrapper:
 840        """Return the pytest section from the tox.ini config file."""
 841        p = self.makeini(source)
 842        return IniConfig(str(p))["pytest"]
 843
 844    def makepyprojecttoml(self, source: str) -> Path:
 845        """Write a pyproject.toml file.
 846
 847        :param source: The contents.
 848        :returns: The pyproject.ini file.
 849
 850        .. versionadded:: 6.0
 851        """
 852        return self.makefile(".toml", pyproject=source)
 853
 854    def makepyfile(self, *args, **kwargs) -> Path:
 855        r"""Shortcut for .makefile() with a .py extension.
 856
 857        Defaults to the test name with a '.py' extension, e.g test_foobar.py, overwriting
 858        existing files.
 859
 860        Examples:
 861
 862        .. code-block:: python
 863
 864            def test_something(pytester):
 865                # Initial file is created test_something.py.
 866                pytester.makepyfile("foobar")
 867                # To create multiple files, pass kwargs accordingly.
 868                pytester.makepyfile(custom="foobar")
 869                # At this point, both 'test_something.py' & 'custom.py' exist in the test directory.
 870
 871        """
 872        return self._makefile(".py", args, kwargs)
 873
 874    def maketxtfile(self, *args, **kwargs) -> Path:
 875        r"""Shortcut for .makefile() with a .txt extension.
 876
 877        Defaults to the test name with a '.txt' extension, e.g test_foobar.txt, overwriting
 878        existing files.
 879
 880        Examples:
 881
 882        .. code-block:: python
 883
 884            def test_something(pytester):
 885                # Initial file is created test_something.txt.
 886                pytester.maketxtfile("foobar")
 887                # To create multiple files, pass kwargs accordingly.
 888                pytester.maketxtfile(custom="foobar")
 889                # At this point, both 'test_something.txt' & 'custom.txt' exist in the test directory.
 890
 891        """
 892        return self._makefile(".txt", args, kwargs)
 893
 894    def syspathinsert(self, path: str | os.PathLike[str] | None = None) -> None:
 895        """Prepend a directory to sys.path, defaults to :attr:`path`.
 896
 897        This is undone automatically when this object dies at the end of each
 898        test.
 899
 900        :param path:
 901            The path.
 902        """
 903        if path is None:
 904            path = self.path
 905
 906        self._monkeypatch.syspath_prepend(str(path))
 907
 908    def mkdir(self, name: str | os.PathLike[str]) -> Path:
 909        """Create a new (sub)directory.
 910
 911        :param name:
 912            The name of the directory, relative to the pytester path.
 913        :returns:
 914            The created directory.
 915        :rtype: pathlib.Path
 916        """
 917        p = self.path / name
 918        p.mkdir()
 919        return p
 920
 921    def mkpydir(self, name: str | os.PathLike[str]) -> Path:
 922        """Create a new python package.
 923
 924        This creates a (sub)directory with an empty ``__init__.py`` file so it
 925        gets recognised as a Python package.
 926        """
 927        p = self.path / name
 928        p.mkdir()
 929        p.joinpath("__init__.py").touch()
 930        return p
 931
 932    def copy_example(self, name: str | None = None) -> Path:
 933        """Copy file from project's directory into the testdir.
 934
 935        :param name:
 936            The name of the file to copy.
 937        :return:
 938            Path to the copied directory (inside ``self.path``).
 939        :rtype: pathlib.Path
 940        """
 941        example_dir_ = self._request.config.getini("pytester_example_dir")
 942        if example_dir_ is None:
 943            raise ValueError("pytester_example_dir is unset, can't copy examples")
 944        example_dir: Path = self._request.config.rootpath / example_dir_
 945
 946        for extra_element in self._request.node.iter_markers("pytester_example_path"):
 947            assert extra_element.args
 948            example_dir = example_dir.joinpath(*extra_element.args)
 949
 950        if name is None:
 951            func_name = self._name
 952            maybe_dir = example_dir / func_name
 953            maybe_file = example_dir / (func_name + ".py")
 954
 955            if maybe_dir.is_dir():
 956                example_path = maybe_dir
 957            elif maybe_file.is_file():
 958                example_path = maybe_file
 959            else:
 960                raise LookupError(
 961                    f"{func_name} can't be found as module or package in {example_dir}"
 962                )
 963        else:
 964            example_path = example_dir.joinpath(name)
 965
 966        if example_path.is_dir() and not example_path.joinpath("__init__.py").is_file():
 967            shutil.copytree(example_path, self.path, symlinks=True, dirs_exist_ok=True)
 968            return self.path
 969        elif example_path.is_file():
 970            result = self.path.joinpath(example_path.name)
 971            shutil.copy(example_path, result)
 972            return result
 973        else:
 974            raise LookupError(
 975                f'example "{example_path}" is not found as a file or directory'
 976            )
 977
 978    def getnode(self, config: Config, arg: str | os.PathLike[str]) -> Collector | Item:
 979        """Get the collection node of a file.
 980
 981        :param config:
 982           A pytest config.
 983           See :py:meth:`parseconfig` and :py:meth:`parseconfigure` for creating it.
 984        :param arg:
 985            Path to the file.
 986        :returns:
 987            The node.
 988        """
 989        session = Session.from_config(config)
 990        assert "::" not in str(arg)
 991        p = Path(os.path.abspath(arg))
 992        config.hook.pytest_sessionstart(session=session)
 993        res = session.perform_collect([str(p)], genitems=False)[0]
 994        config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK)
 995        return res
 996
 997    def getpathnode(self, path: str | os.PathLike[str]) -> Collector | Item:
 998        """Return the collection node of a file.
 999
1000        This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to
1001        create the (configured) pytest Config instance.
1002
1003        :param path:
1004            Path to the file.
1005        :returns:
1006            The node.
1007        """
1008        path = Path(path)
1009        config = self.parseconfigure(path)
1010        session = Session.from_config(config)
1011        x = bestrelpath(session.path, path)
1012        config.hook.pytest_sessionstart(session=session)
1013        res = session.perform_collect([x], genitems=False)[0]
1014        config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK)
1015        return res
1016
1017    def genitems(self, colitems: Sequence[Item | Collector]) -> list[Item]:
1018        """Generate all test items from a collection node.
1019
1020        This recurses into the collection node and returns a list of all the
1021        test items contained within.
1022
1023        :param colitems:
1024            The collection nodes.
1025        :returns:
1026            The collected items.
1027        """
1028        session = colitems[0].session
1029        result: list[Item] = []
1030        for colitem in colitems:
1031            result.extend(session.genitems(colitem))
1032        return result
1033
1034    def runitem(self, source: str) -> Any:
1035        """Run the "test_func" Item.
1036
1037        The calling test instance (class containing the test method) must
1038        provide a ``.getrunner()`` method which should return a runner which
1039        can run the test protocol for a single item, e.g.
1040        ``_pytest.runner.runtestprotocol``.
1041        """
1042        # used from runner functional tests
1043        item = self.getitem(source)
1044        # the test class where we are called from wants to provide the runner
1045        testclassinstance = self._request.instance
1046        runner = testclassinstance.getrunner()
1047        return runner(item)
1048
1049    def inline_runsource(self, source: str, *cmdlineargs) -> HookRecorder:
1050        """Run a test module in process using ``pytest.main()``.
1051
1052        This run writes "source" into a temporary file and runs
1053        ``pytest.main()`` on it, returning a :py:class:`HookRecorder` instance
1054        for the result.
1055
1056        :param source: The source code of the test module.
1057        :param cmdlineargs: Any extra command line arguments to use.
1058        """
1059        p = self.makepyfile(source)
1060        values = [*list(cmdlineargs), p]
1061        return self.inline_run(*values)
1062
1063    def inline_genitems(self, *args) -> tuple[list[Item], HookRecorder]:
1064        """Run ``pytest.main(['--collect-only'])`` in-process.
1065
1066        Runs the :py:func:`pytest.main` function to run all of pytest inside
1067        the test process itself like :py:meth:`inline_run`, but returns a
1068        tuple of the collected items and a :py:class:`HookRecorder` instance.
1069        """
1070        rec = self.inline_run("--collect-only", *args)
1071        items = [x.item for x in rec.getcalls("pytest_itemcollected")]
1072        return items, rec
1073
1074    def inline_run(
1075        self,
1076        *args: str | os.PathLike[str],
1077        plugins=(),
1078        no_reraise_ctrlc: bool = False,
1079    ) -> HookRecorder:
1080        """Run ``pytest.main()`` in-process, returning a HookRecorder.
1081
1082        Runs the :py:func:`pytest.main` function to run all of pytest inside
1083        the test process itself.  This means it can return a
1084        :py:class:`HookRecorder` instance which gives more detailed results
1085        from that run than can be done by matching stdout/stderr from
1086        :py:meth:`runpytest`.
1087
1088        :param args:
1089            Command line arguments to pass to :py:func:`pytest.main`.
1090        :param plugins:
1091            Extra plugin instances the ``pytest.main()`` instance should use.
1092        :param no_reraise_ctrlc:
1093            Typically we reraise keyboard interrupts from the child run. If
1094            True, the KeyboardInterrupt exception is captured.
1095        """
1096        from _pytest.unraisableexception import gc_collect_iterations_key
1097
1098        # (maybe a cpython bug?) the importlib cache sometimes isn't updated
1099        # properly between file creation and inline_run (especially if imports
1100        # are interspersed with file creation)
1101        importlib.invalidate_caches()
1102
1103        plugins = list(plugins)
1104        finalizers = []
1105        try:
1106            # Any sys.module or sys.path changes done while running pytest
1107            # inline should be reverted after the test run completes to avoid
1108            # clashing with later inline tests run within the same pytest test,
1109            # e.g. just because they use matching test module names.
1110            finalizers.append(self.__take_sys_modules_snapshot().restore)
1111            finalizers.append(SysPathsSnapshot().restore)
1112
1113            # Important note:
1114            # - our tests should not leave any other references/registrations
1115            #   laying around other than possibly loaded test modules
1116            #   referenced from sys.modules, as nothing will clean those up
1117            #   automatically
1118
1119            rec = []
1120
1121            class PytesterHelperPlugin:
1122                @staticmethod
1123                def pytest_configure(config: Config) -> None:
1124                    rec.append(self.make_hook_recorder(config.pluginmanager))
1125
1126                    # The unraisable plugin GC collect slows down inline
1127                    # pytester runs too much.
1128                    config.stash[gc_collect_iterations_key] = 0
1129
1130            plugins.append(PytesterHelperPlugin())
1131            ret = main([str(x) for x in args], plugins=plugins)
1132            if len(rec) == 1:
1133                reprec = rec.pop()
1134            else:
1135
1136                class reprec:  # type: ignore
1137                    pass
1138
1139            reprec.ret = ret
1140
1141            # Typically we reraise keyboard interrupts from the child run
1142            # because it's our user requesting interruption of the testing.
1143            if ret == ExitCode.INTERRUPTED and not no_reraise_ctrlc:
1144                calls = reprec.getcalls("pytest_keyboard_interrupt")
1145                if calls and calls[-1].excinfo.type == KeyboardInterrupt:
1146                    raise KeyboardInterrupt()
1147            return reprec
1148        finally:
1149            for finalizer in finalizers:
1150                finalizer()
1151
1152    def runpytest_inprocess(
1153        self, *args: str | os.PathLike[str], **kwargs: Any
1154    ) -> RunResult:
1155        """Return result of running pytest in-process, providing a similar
1156        interface to what self.runpytest() provides."""
1157        syspathinsert = kwargs.pop("syspathinsert", False)
1158
1159        if syspathinsert:
1160            self.syspathinsert()
1161        instant = timing.Instant()
1162        capture = _get_multicapture("sys")
1163        capture.start_capturing()
1164        try:
1165            try:
1166                reprec = self.inline_run(*args, **kwargs)
1167            except SystemExit as e:
1168                ret = e.args[0]
1169                try:
1170                    ret = ExitCode(e.args[0])
1171                except ValueError:
1172                    pass
1173
1174                class reprec:  # type: ignore
1175                    ret = ret
1176
1177            except Exception:
1178                traceback.print_exc()
1179
1180                class reprec:  # type: ignore
1181                    ret = ExitCode(3)
1182
1183        finally:
1184            out, err = capture.readouterr()
1185            capture.stop_capturing()
1186            sys.stdout.write(out)
1187            sys.stderr.write(err)
1188
1189        assert reprec.ret is not None
1190        res = RunResult(
1191            reprec.ret, out.splitlines(), err.splitlines(), instant.elapsed().seconds
1192        )
1193        res.reprec = reprec  # type: ignore
1194        return res
1195
1196    def runpytest(self, *args: str | os.PathLike[str], **kwargs: Any) -> RunResult:
1197        """Run pytest inline or in a subprocess, depending on the command line
1198        option "--runpytest" and return a :py:class:`~pytest.RunResult`."""
1199        new_args = self._ensure_basetemp(args)
1200        if self._method == "inprocess":
1201            return self.runpytest_inprocess(*new_args, **kwargs)
1202        elif self._method == "subprocess":
1203            return self.runpytest_subprocess(*new_args, **kwargs)
1204        raise RuntimeError(f"Unrecognized runpytest option: {self._method}")
1205
1206    def _ensure_basetemp(
1207        self, args: Sequence[str | os.PathLike[str]]
1208    ) -> list[str | os.PathLike[str]]:
1209        new_args = list(args)
1210        for x in new_args:
1211            if str(x).startswith("--basetemp"):
1212                break
1213        else:
1214            new_args.append(
1215                "--basetemp={}".format(self.path.parent.joinpath("basetemp"))
1216            )
1217        return new_args
1218
1219    def parseconfig(self, *args: str | os.PathLike[str]) -> Config:
1220        """Return a new pytest :class:`pytest.Config` instance from given
1221        commandline args.
1222
1223        This invokes the pytest bootstrapping code in _pytest.config to create a
1224        new :py:class:`pytest.PytestPluginManager` and call the
1225        :hook:`pytest_cmdline_parse` hook to create a new :class:`pytest.Config`
1226        instance.
1227
1228        If :attr:`plugins` has been populated they should be plugin modules
1229        to be registered with the plugin manager.
1230        """
1231        import _pytest.config
1232
1233        new_args = self._ensure_basetemp(args)
1234        new_args = [str(x) for x in new_args]
1235
1236        config = _pytest.config._prepareconfig(new_args, self.plugins)  # type: ignore[arg-type]
1237        # we don't know what the test will do with this half-setup config
1238        # object and thus we make sure it gets unconfigured properly in any
1239        # case (otherwise capturing could still be active, for example)
1240        self._request.addfinalizer(config._ensure_unconfigure)
1241        return config
1242
1243    def parseconfigure(self, *args: str | os.PathLike[str]) -> Config:
1244        """Return a new pytest configured Config instance.
1245
1246        Returns a new :py:class:`pytest.Config` instance like
1247        :py:meth:`parseconfig`, but also calls the :hook:`pytest_configure`
1248        hook.
1249        """
1250        config = self.parseconfig(*args)
1251        config._do_configure()
1252        return config
1253
1254    def getitem(
1255        self, source: str | os.PathLike[str], funcname: str = "test_func"
1256    ) -> Item:
1257        """Return the test item for a test function.
1258
1259        Writes the source to a python file and runs pytest's collection on
1260        the resulting module, returning the test item for the requested
1261        function name.
1262
1263        :param source:
1264            The module source.
1265        :param funcname:
1266            The name of the test function for which to return a test item.
1267        :returns:
1268            The test item.
1269        """
1270        items = self.getitems(source)
1271        for item in items:
1272            if item.name == funcname:
1273                return item
1274        assert 0, f"{funcname!r} item not found in module:\n{source}\nitems: {items}"
1275
1276    def getitems(self, source: str | os.PathLike[str]) -> list[Item]:
1277        """Return all test items collected from the module.
1278
1279        Writes the source to a Python file and runs pytest's collection on
1280        the resulting module, returning all test items contained within.
1281        """
1282        modcol = self.getmodulecol(source)
1283        return self.genitems([modcol])
1284
1285    def getmodulecol(
1286        self,
1287        source: str | os.PathLike[str],
1288        configargs=(),
1289        *,
1290        withinit: bool = False,
1291    ):
1292        """Return the module collection node for ``source``.
1293
1294        Writes ``source`` to a file using :py:meth:`makepyfile` and then
1295        runs the pytest collection on it, returning the collection node for the
1296        test module.
1297
1298        :param source:
1299            The source code of the module to collect.
1300
1301        :param configargs:
1302            Any extra arguments to pass to :py:meth:`parseconfigure`.
1303
1304        :param withinit:
1305            Whether to also write an ``__init__.py`` file to the same
1306            directory to ensure it is a package.
1307        """
1308        if isinstance(source, os.PathLike):
1309            path = self.path.joinpath(source)
1310            assert not withinit, "not supported for paths"
1311        else:
1312            kw = {self._name: str(source)}
1313            path = self.makepyfile(**kw)
1314        if withinit:
1315            self.makepyfile(__init__="#")
1316        self.config = config = self.parseconfigure(path, *configargs)
1317        return self.getnode(config, path)
1318
1319    def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None:
1320        """Return the collection node for name from the module collection.
1321
1322        Searches a module collection node for a collection node matching the
1323        given name.
1324
1325        :param modcol: A module collection node; see :py:meth:`getmodulecol`.
1326        :param name: The name of the node to return.
1327        """
1328        if modcol not in self._mod_collections:
1329            self._mod_collections[modcol] = list(modcol.collect())
1330        for colitem in self._mod_collections[modcol]:
1331            if colitem.name == name:
1332                return colitem
1333        return None
1334
1335    def popen(
1336        self,
1337        cmdargs: Sequence[str | os.PathLike[str]],
1338        stdout: int | TextIO = subprocess.PIPE,
1339        stderr: int | TextIO = subprocess.PIPE,
1340        stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN,
1341        **kw,
1342    ):
1343        """Invoke :py:class:`subprocess.Popen`.
1344
1345        Calls :py:class:`subprocess.Popen` making sure the current working
1346        directory is in ``PYTHONPATH``.
1347
1348        You probably want to use :py:meth:`run` instead.
1349        """
1350        env = os.environ.copy()
1351        env["PYTHONPATH"] = os.pathsep.join(
1352            filter(None, [os.getcwd(), env.get("PYTHONPATH", "")])
1353        )
1354        kw["env"] = env
1355
1356        if stdin is self.CLOSE_STDIN:
1357            kw["stdin"] = subprocess.PIPE
1358        elif isinstance(stdin, bytes):
1359            kw["stdin"] = subprocess.PIPE
1360        else:
1361            kw["stdin"] = stdin
1362
1363        popen = subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw)
1364        if stdin is self.CLOSE_STDIN:
1365            assert popen.stdin is not None
1366            popen.stdin.close()
1367        elif isinstance(stdin, bytes):
1368            assert popen.stdin is not None
1369            popen.stdin.write(stdin)
1370
1371        return popen
1372
1373    def run(
1374        self,
1375        *cmdargs: str | os.PathLike[str],
1376        timeout: float | None = None,
1377        stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN,
1378    ) -> RunResult:
1379        """Run a command with arguments.
1380
1381        Run a process using :py:class:`subprocess.Popen` saving the stdout and
1382        stderr.
1383
1384        :param cmdargs:
1385            The sequence of arguments to pass to :py:class:`subprocess.Popen`,
1386            with path-like objects being converted to :py:class:`str`
1387            automatically.
1388        :param timeout:
1389            The period in seconds after which to timeout and raise
1390            :py:class:`Pytester.TimeoutExpired`.
1391        :param stdin:
1392            Optional standard input.
1393
1394            - If it is ``CLOSE_STDIN`` (Default), then this method calls
1395              :py:class:`subprocess.Popen` with ``stdin=subprocess.PIPE``, and
1396              the standard input is closed immediately after the new command is
1397              started.
1398
1399            - If it is of type :py:class:`bytes`, these bytes are sent to the
1400              standard input of the command.
1401
1402            - Otherwise, it is passed through to :py:class:`subprocess.Popen`.
1403              For further information in this case, consult the document of the
1404              ``stdin`` parameter in :py:class:`subprocess.Popen`.
1405        :type stdin: _pytest.compat.NotSetType | bytes | IO[Any] | int
1406        :returns:
1407            The result.
1408
1409        """
1410        __tracebackhide__ = True
1411
1412        cmdargs = tuple(os.fspath(arg) for arg in cmdargs)
1413        p1 = self.path.joinpath("stdout")
1414        p2 = self.path.joinpath("stderr")
1415        print("running:", *cmdargs)
1416        print("     in:", Path.cwd())
1417
1418        with p1.open("w", encoding="utf8") as f1, p2.open("w", encoding="utf8") as f2:
1419            instant = timing.Instant()
1420            popen = self.popen(
1421                cmdargs,
1422                stdin=stdin,
1423                stdout=f1,
1424                stderr=f2,
1425                close_fds=(sys.platform != "win32"),
1426            )
1427            if popen.stdin is not None:
1428                popen.stdin.close()
1429
1430            def handle_timeout() -> None:
1431                __tracebackhide__ = True
1432
1433                timeout_message = f"{timeout} second timeout expired running: {cmdargs}"
1434
1435                popen.kill()
1436                popen.wait()
1437                raise self.TimeoutExpired(timeout_message)
1438
1439            if timeout is None:
1440                ret = popen.wait()
1441            else:
1442                try:
1443                    ret = popen.wait(timeout)
1444                except subprocess.TimeoutExpired:
1445                    handle_timeout()
1446
1447        with p1.open(encoding="utf8") as f1, p2.open(encoding="utf8") as f2:
1448            out = f1.read().splitlines()
1449            err = f2.read().splitlines()
1450
1451        self._dump_lines(out, sys.stdout)
1452        self._dump_lines(err, sys.stderr)
1453
1454        with contextlib.suppress(ValueError):
1455            ret = ExitCode(ret)
1456        return RunResult(ret, out, err, instant.elapsed().seconds)
1457
1458    def _dump_lines(self, lines, fp):
1459        try:
1460            for line in lines:
1461                print(line, file=fp)
1462        except UnicodeEncodeError:
1463            print(f"couldn't print to {fp} because of encoding")
1464
1465    def _getpytestargs(self) -> tuple[str, ...]:
1466        return sys.executable, "-mpytest"
1467
1468    def runpython(self, script: os.PathLike[str]) -> RunResult:
1469        """Run a python script using sys.executable as interpreter."""
1470        return self.run(sys.executable, script)
1471
1472    def runpython_c(self, command: str) -> RunResult:
1473        """Run ``python -c "command"``."""
1474        return self.run(sys.executable, "-c", command)
1475
1476    def runpytest_subprocess(
1477        self, *args: str | os.PathLike[str], timeout: float | None = None
1478    ) -> RunResult:
1479        """Run pytest as a subprocess with given arguments.
1480
1481        Any plugins added to the :py:attr:`plugins` list will be added using the
1482        ``-p`` command line option.  Additionally ``--basetemp`` is used to put
1483        any temporary files and directories in a numbered directory prefixed
1484        with "runpytest-" to not conflict with the normal numbered pytest
1485        location for temporary files and directories.
1486
1487        :param args:
1488            The sequence of arguments to pass to the pytest subprocess.
1489        :param timeout:
1490            The period in seconds after which to timeout and raise
1491            :py:class:`Pytester.TimeoutExpired`.
1492        :returns:
1493            The result.
1494        """
1495        __tracebackhide__ = True
1496        p = make_numbered_dir(root=self.path, prefix="runpytest-", mode=0o700)
1497        args = (f"--basetemp={p}", *args)
1498        plugins = [x for x in self.plugins if isinstance(x, str)]
1499        if plugins:
1500            args = ("-p", plugins[0], *args)
1501        args = self._getpytestargs() + args
1502        return self.run(*args, timeout=timeout)
1503
1504    def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn:
1505        """Run pytest using pexpect.
1506
1507        This makes sure to use the right pytest and sets up the temporary
1508        directory locations.
1509
1510        The pexpect child is returned.
1511        """
1512        basetemp = self.path / "temp-pexpect"
1513        basetemp.mkdir(mode=0o700)
1514        invoke = " ".join(map(str, self._getpytestargs()))
1515        cmd = f"{invoke} --basetemp={basetemp} {string}"
1516        return self.spawn(cmd, expect_timeout=expect_timeout)
1517
1518    def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn:
1519        """Run a command using pexpect.
1520
1521        The pexpect child is returned.
1522        """
1523        pexpect = importorskip("pexpect", "3.0")
1524        if hasattr(sys, "pypy_version_info") and "64" in platform.machine():
1525            skip("pypy-64 bit not supported")
1526        if not hasattr(pexpect, "spawn"):
1527            skip("pexpect.spawn not available")
1528        logfile = self.path.joinpath("spawn.out").open("wb")
1529
1530        child = pexpect.spawn(cmd, logfile=logfile, timeout=expect_timeout)
1531        self._request.addfinalizer(logfile.close)
1532        return child

Facilities to write tests/configuration files, execute pytest in isolation, and match against expected output, perfect for black-box testing of pytest plugins.

It attempts to isolate the test run from external factors as much as possible, modifying the current working directory to path and environment variables during initialization.

Pytester( request: _pytest.fixtures.FixtureRequest, tmp_path_factory: _pytest.tmpdir.TempPathFactory, monkeypatch: _pytest.monkeypatch.MonkeyPatch, *, _ispytest: bool = False)
666    def __init__(
667        self,
668        request: FixtureRequest,
669        tmp_path_factory: TempPathFactory,
670        monkeypatch: MonkeyPatch,
671        *,
672        _ispytest: bool = False,
673    ) -> None:
674        check_ispytest(_ispytest)
675        self._request = request
676        self._mod_collections: WeakKeyDictionary[Collector, list[Item | Collector]] = (
677            WeakKeyDictionary()
678        )
679        if request.function:
680            name: str = request.function.__name__
681        else:
682            name = request.node.name
683        self._name = name
684        self._path: Path = tmp_path_factory.mktemp(name, numbered=True)
685        #: A list of plugins to use with :py:meth:`parseconfig` and
686        #: :py:meth:`runpytest`.  Initially this is an empty list but plugins can
687        #: be added to the list.  The type of items to add to the list depends on
688        #: the method using them so refer to them for details.
689        self.plugins: list[str | _PluggyPlugin] = []
690        self._sys_path_snapshot = SysPathsSnapshot()
691        self._sys_modules_snapshot = self.__take_sys_modules_snapshot()
692        self._request.addfinalizer(self._finalize)
693        self._method = self._request.config.getoption("--runpytest")
694        self._test_tmproot = tmp_path_factory.mktemp(f"tmp-{name}", numbered=True)
695
696        self._monkeypatch = mp = monkeypatch
697        self.chdir()
698        mp.setenv("PYTEST_DEBUG_TEMPROOT", str(self._test_tmproot))
699        # Ensure no unexpected caching via tox.
700        mp.delenv("TOX_ENV_DIR", raising=False)
701        # Discard outer pytest options.
702        mp.delenv("PYTEST_ADDOPTS", raising=False)
703        # Ensure no user config is used.
704        tmphome = str(self.path)
705        mp.setenv("HOME", tmphome)
706        mp.setenv("USERPROFILE", tmphome)
707        # Do not use colors for inner runs by default.
708        mp.setenv("PY_COLORS", "0")
CLOSE_STDIN: Final = <NotSetType.token: 0>
plugins: list[str | object]
path: pathlib.Path
710    @property
711    def path(self) -> Path:
712        """Temporary directory path used to create files/run tests from, etc."""
713        return self._path

Temporary directory path used to create files/run tests from, etc.

def make_hook_recorder( self, pluginmanager: _pytest.config.PytestPluginManager) -> _pytest.pytester.HookRecorder:
741    def make_hook_recorder(self, pluginmanager: PytestPluginManager) -> HookRecorder:
742        """Create a new :class:`HookRecorder` for a :class:`PytestPluginManager`."""
743        pluginmanager.reprec = reprec = HookRecorder(pluginmanager, _ispytest=True)  # type: ignore[attr-defined]
744        self._request.addfinalizer(reprec.finish_recording)
745        return reprec

Create a new HookRecorder for a PytestPluginManager.

def chdir(self) -> None:
747    def chdir(self) -> None:
748        """Cd into the temporary directory.
749
750        This is done automatically upon instantiation.
751        """
752        self._monkeypatch.chdir(self.path)

Cd into the temporary directory.

This is done automatically upon instantiation.

def makefile(self, ext: str, *args: str, **kwargs: str) -> pathlib.Path:
791    def makefile(self, ext: str, *args: str, **kwargs: str) -> Path:
792        r"""Create new text file(s) in the test directory.
793
794        :param ext:
795            The extension the file(s) should use, including the dot, e.g. `.py`.
796        :param args:
797            All args are treated as strings and joined using newlines.
798            The result is written as contents to the file.  The name of the
799            file is based on the test function requesting this fixture.
800        :param kwargs:
801            Each keyword is the name of a file, while the value of it will
802            be written as contents of the file.
803        :returns:
804            The first created file.
805
806        Examples:
807
808        .. code-block:: python
809
810            pytester.makefile(".txt", "line1", "line2")
811
812            pytester.makefile(".ini", pytest="[pytest]\naddopts=-rs\n")
813
814        To create binary files, use :meth:`pathlib.Path.write_bytes` directly:
815
816        .. code-block:: python
817
818            filename = pytester.path.joinpath("foo.bin")
819            filename.write_bytes(b"...")
820        """
821        return self._makefile(ext, args, kwargs)

Create new text file(s) in the test directory.

Parameters
  • ext: The extension the file(s) should use, including the dot, e.g. .py.
  • args: All args are treated as strings and joined using newlines. The result is written as contents to the file. The name of the file is based on the test function requesting this fixture.
  • kwargs: Each keyword is the name of a file, while the value of it will be written as contents of the file. :returns: The first created file.

Examples:

pytester.makefile(".txt", "line1", "line2")

pytester.makefile(".ini", pytest="[pytest]\naddopts=-rs\n")

To create binary files, use pathlib.Path.write_bytes() directly:

filename = pytester.path.joinpath("foo.bin")
filename.write_bytes(b"...")
def makeconftest(self, source: str) -> pathlib.Path:
823    def makeconftest(self, source: str) -> Path:
824        """Write a conftest.py file.
825
826        :param source: The contents.
827        :returns: The conftest.py file.
828        """
829        return self.makepyfile(conftest=source)

Write a conftest.py file.

Parameters
  • source: The contents. :returns: The conftest.py file.
def makeini(self, source: str) -> pathlib.Path:
831    def makeini(self, source: str) -> Path:
832        """Write a tox.ini file.
833
834        :param source: The contents.
835        :returns: The tox.ini file.
836        """
837        return self.makefile(".ini", tox=source)

Write a tox.ini file.

Parameters
  • source: The contents. :returns: The tox.ini file.
def getinicfg(self, source: str) -> iniconfig.SectionWrapper:
839    def getinicfg(self, source: str) -> SectionWrapper:
840        """Return the pytest section from the tox.ini config file."""
841        p = self.makeini(source)
842        return IniConfig(str(p))["pytest"]

Return the pytest section from the tox.ini config file.

def makepyprojecttoml(self, source: str) -> pathlib.Path:
844    def makepyprojecttoml(self, source: str) -> Path:
845        """Write a pyproject.toml file.
846
847        :param source: The contents.
848        :returns: The pyproject.ini file.
849
850        .. versionadded:: 6.0
851        """
852        return self.makefile(".toml", pyproject=source)

Write a pyproject.toml file.

Parameters
  • source: The contents. :returns: The pyproject.ini file.

New in version 6.0.

def makepyfile(self, *args, **kwargs) -> pathlib.Path:
854    def makepyfile(self, *args, **kwargs) -> Path:
855        r"""Shortcut for .makefile() with a .py extension.
856
857        Defaults to the test name with a '.py' extension, e.g test_foobar.py, overwriting
858        existing files.
859
860        Examples:
861
862        .. code-block:: python
863
864            def test_something(pytester):
865                # Initial file is created test_something.py.
866                pytester.makepyfile("foobar")
867                # To create multiple files, pass kwargs accordingly.
868                pytester.makepyfile(custom="foobar")
869                # At this point, both 'test_something.py' & 'custom.py' exist in the test directory.
870
871        """
872        return self._makefile(".py", args, kwargs)

Shortcut for .makefile() with a .py extension.

Defaults to the test name with a '.py' extension, e.g test_foobar.py, overwriting existing files.

Examples:

def test_something(pytester):
    # Initial file is created test_something.py.
    pytester.makepyfile("foobar")
    # To create multiple files, pass kwargs accordingly.
    pytester.makepyfile(custom="foobar")
    # At this point, both 'test_something.py' & 'custom.py' exist in the test directory.
def maketxtfile(self, *args, **kwargs) -> pathlib.Path:
874    def maketxtfile(self, *args, **kwargs) -> Path:
875        r"""Shortcut for .makefile() with a .txt extension.
876
877        Defaults to the test name with a '.txt' extension, e.g test_foobar.txt, overwriting
878        existing files.
879
880        Examples:
881
882        .. code-block:: python
883
884            def test_something(pytester):
885                # Initial file is created test_something.txt.
886                pytester.maketxtfile("foobar")
887                # To create multiple files, pass kwargs accordingly.
888                pytester.maketxtfile(custom="foobar")
889                # At this point, both 'test_something.txt' & 'custom.txt' exist in the test directory.
890
891        """
892        return self._makefile(".txt", args, kwargs)

Shortcut for .makefile() with a .txt extension.

Defaults to the test name with a '.txt' extension, e.g test_foobar.txt, overwriting existing files.

Examples:

def test_something(pytester):
    # Initial file is created test_something.txt.
    pytester.maketxtfile("foobar")
    # To create multiple files, pass kwargs accordingly.
    pytester.maketxtfile(custom="foobar")
    # At this point, both 'test_something.txt' & 'custom.txt' exist in the test directory.
def syspathinsert(self, path: str | os.PathLike[str] | None = None) -> None:
894    def syspathinsert(self, path: str | os.PathLike[str] | None = None) -> None:
895        """Prepend a directory to sys.path, defaults to :attr:`path`.
896
897        This is undone automatically when this object dies at the end of each
898        test.
899
900        :param path:
901            The path.
902        """
903        if path is None:
904            path = self.path
905
906        self._monkeypatch.syspath_prepend(str(path))

Prepend a directory to sys.path, defaults to path.

This is undone automatically when this object dies at the end of each test.

Parameters
  • path: The path.
def mkdir(self, name: str | os.PathLike[str]) -> pathlib.Path:
908    def mkdir(self, name: str | os.PathLike[str]) -> Path:
909        """Create a new (sub)directory.
910
911        :param name:
912            The name of the directory, relative to the pytester path.
913        :returns:
914            The created directory.
915        :rtype: pathlib.Path
916        """
917        p = self.path / name
918        p.mkdir()
919        return p

Create a new (sub)directory.

Parameters
  • name: The name of the directory, relative to the pytester path. :returns: The created directory.
def mkpydir(self, name: str | os.PathLike[str]) -> pathlib.Path:
921    def mkpydir(self, name: str | os.PathLike[str]) -> Path:
922        """Create a new python package.
923
924        This creates a (sub)directory with an empty ``__init__.py`` file so it
925        gets recognised as a Python package.
926        """
927        p = self.path / name
928        p.mkdir()
929        p.joinpath("__init__.py").touch()
930        return p

Create a new python package.

This creates a (sub)directory with an empty __init__.py file so it gets recognised as a Python package.

def copy_example(self, name: str | None = None) -> pathlib.Path:
932    def copy_example(self, name: str | None = None) -> Path:
933        """Copy file from project's directory into the testdir.
934
935        :param name:
936            The name of the file to copy.
937        :return:
938            Path to the copied directory (inside ``self.path``).
939        :rtype: pathlib.Path
940        """
941        example_dir_ = self._request.config.getini("pytester_example_dir")
942        if example_dir_ is None:
943            raise ValueError("pytester_example_dir is unset, can't copy examples")
944        example_dir: Path = self._request.config.rootpath / example_dir_
945
946        for extra_element in self._request.node.iter_markers("pytester_example_path"):
947            assert extra_element.args
948            example_dir = example_dir.joinpath(*extra_element.args)
949
950        if name is None:
951            func_name = self._name
952            maybe_dir = example_dir / func_name
953            maybe_file = example_dir / (func_name + ".py")
954
955            if maybe_dir.is_dir():
956                example_path = maybe_dir
957            elif maybe_file.is_file():
958                example_path = maybe_file
959            else:
960                raise LookupError(
961                    f"{func_name} can't be found as module or package in {example_dir}"
962                )
963        else:
964            example_path = example_dir.joinpath(name)
965
966        if example_path.is_dir() and not example_path.joinpath("__init__.py").is_file():
967            shutil.copytree(example_path, self.path, symlinks=True, dirs_exist_ok=True)
968            return self.path
969        elif example_path.is_file():
970            result = self.path.joinpath(example_path.name)
971            shutil.copy(example_path, result)
972            return result
973        else:
974            raise LookupError(
975                f'example "{example_path}" is not found as a file or directory'
976            )

Copy file from project's directory into the testdir.

Parameters
  • name: The name of the file to copy.
Returns
Path to the copied directory (inside ``self.path``).
def getnode( self, config: _pytest.config.Config, arg: str | os.PathLike[str]) -> _pytest.nodes.Collector | _pytest.nodes.Item:
978    def getnode(self, config: Config, arg: str | os.PathLike[str]) -> Collector | Item:
979        """Get the collection node of a file.
980
981        :param config:
982           A pytest config.
983           See :py:meth:`parseconfig` and :py:meth:`parseconfigure` for creating it.
984        :param arg:
985            Path to the file.
986        :returns:
987            The node.
988        """
989        session = Session.from_config(config)
990        assert "::" not in str(arg)
991        p = Path(os.path.abspath(arg))
992        config.hook.pytest_sessionstart(session=session)
993        res = session.perform_collect([str(p)], genitems=False)[0]
994        config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK)
995        return res

Get the collection node of a file.

Parameters
def getpathnode( self, path: str | os.PathLike[str]) -> _pytest.nodes.Collector | _pytest.nodes.Item:
 997    def getpathnode(self, path: str | os.PathLike[str]) -> Collector | Item:
 998        """Return the collection node of a file.
 999
1000        This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to
1001        create the (configured) pytest Config instance.
1002
1003        :param path:
1004            Path to the file.
1005        :returns:
1006            The node.
1007        """
1008        path = Path(path)
1009        config = self.parseconfigure(path)
1010        session = Session.from_config(config)
1011        x = bestrelpath(session.path, path)
1012        config.hook.pytest_sessionstart(session=session)
1013        res = session.perform_collect([x], genitems=False)[0]
1014        config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK)
1015        return res

Return the collection node of a file.

This is like getnode() but uses parseconfigure() to create the (configured) pytest Config instance.

Parameters
  • path: Path to the file. :returns: The node.
def genitems( self, colitems: Sequence[_pytest.nodes.Item | _pytest.nodes.Collector]) -> list[_pytest.nodes.Item]:
1017    def genitems(self, colitems: Sequence[Item | Collector]) -> list[Item]:
1018        """Generate all test items from a collection node.
1019
1020        This recurses into the collection node and returns a list of all the
1021        test items contained within.
1022
1023        :param colitems:
1024            The collection nodes.
1025        :returns:
1026            The collected items.
1027        """
1028        session = colitems[0].session
1029        result: list[Item] = []
1030        for colitem in colitems:
1031            result.extend(session.genitems(colitem))
1032        return result

Generate all test items from a collection node.

This recurses into the collection node and returns a list of all the test items contained within.

Parameters
  • colitems: The collection nodes. :returns: The collected items.
def runitem(self, source: str) -> Any:
1034    def runitem(self, source: str) -> Any:
1035        """Run the "test_func" Item.
1036
1037        The calling test instance (class containing the test method) must
1038        provide a ``.getrunner()`` method which should return a runner which
1039        can run the test protocol for a single item, e.g.
1040        ``_pytest.runner.runtestprotocol``.
1041        """
1042        # used from runner functional tests
1043        item = self.getitem(source)
1044        # the test class where we are called from wants to provide the runner
1045        testclassinstance = self._request.instance
1046        runner = testclassinstance.getrunner()
1047        return runner(item)

Run the "test_func" Item.

The calling test instance (class containing the test method) must provide a .getrunner() method which should return a runner which can run the test protocol for a single item, e.g. _pytest.runner.runtestprotocol.

def inline_runsource(self, source: str, *cmdlineargs) -> _pytest.pytester.HookRecorder:
1049    def inline_runsource(self, source: str, *cmdlineargs) -> HookRecorder:
1050        """Run a test module in process using ``pytest.main()``.
1051
1052        This run writes "source" into a temporary file and runs
1053        ``pytest.main()`` on it, returning a :py:class:`HookRecorder` instance
1054        for the result.
1055
1056        :param source: The source code of the test module.
1057        :param cmdlineargs: Any extra command line arguments to use.
1058        """
1059        p = self.makepyfile(source)
1060        values = [*list(cmdlineargs), p]
1061        return self.inline_run(*values)

Run a test module in process using pytest.main().

This run writes "source" into a temporary file and runs pytest.main() on it, returning a HookRecorder instance for the result.

Parameters
  • source: The source code of the test module.
  • cmdlineargs: Any extra command line arguments to use.
def inline_genitems( self, *args) -> tuple[list[_pytest.nodes.Item], _pytest.pytester.HookRecorder]:
1063    def inline_genitems(self, *args) -> tuple[list[Item], HookRecorder]:
1064        """Run ``pytest.main(['--collect-only'])`` in-process.
1065
1066        Runs the :py:func:`pytest.main` function to run all of pytest inside
1067        the test process itself like :py:meth:`inline_run`, but returns a
1068        tuple of the collected items and a :py:class:`HookRecorder` instance.
1069        """
1070        rec = self.inline_run("--collect-only", *args)
1071        items = [x.item for x in rec.getcalls("pytest_itemcollected")]
1072        return items, rec

Run pytest.main(['--collect-only']) in-process.

Runs the pytest.main() function to run all of pytest inside the test process itself like inline_run(), but returns a tuple of the collected items and a HookRecorder instance.

def inline_run( self, *args: str | os.PathLike[str], plugins=(), no_reraise_ctrlc: bool = False) -> _pytest.pytester.HookRecorder:
1074    def inline_run(
1075        self,
1076        *args: str | os.PathLike[str],
1077        plugins=(),
1078        no_reraise_ctrlc: bool = False,
1079    ) -> HookRecorder:
1080        """Run ``pytest.main()`` in-process, returning a HookRecorder.
1081
1082        Runs the :py:func:`pytest.main` function to run all of pytest inside
1083        the test process itself.  This means it can return a
1084        :py:class:`HookRecorder` instance which gives more detailed results
1085        from that run than can be done by matching stdout/stderr from
1086        :py:meth:`runpytest`.
1087
1088        :param args:
1089            Command line arguments to pass to :py:func:`pytest.main`.
1090        :param plugins:
1091            Extra plugin instances the ``pytest.main()`` instance should use.
1092        :param no_reraise_ctrlc:
1093            Typically we reraise keyboard interrupts from the child run. If
1094            True, the KeyboardInterrupt exception is captured.
1095        """
1096        from _pytest.unraisableexception import gc_collect_iterations_key
1097
1098        # (maybe a cpython bug?) the importlib cache sometimes isn't updated
1099        # properly between file creation and inline_run (especially if imports
1100        # are interspersed with file creation)
1101        importlib.invalidate_caches()
1102
1103        plugins = list(plugins)
1104        finalizers = []
1105        try:
1106            # Any sys.module or sys.path changes done while running pytest
1107            # inline should be reverted after the test run completes to avoid
1108            # clashing with later inline tests run within the same pytest test,
1109            # e.g. just because they use matching test module names.
1110            finalizers.append(self.__take_sys_modules_snapshot().restore)
1111            finalizers.append(SysPathsSnapshot().restore)
1112
1113            # Important note:
1114            # - our tests should not leave any other references/registrations
1115            #   laying around other than possibly loaded test modules
1116            #   referenced from sys.modules, as nothing will clean those up
1117            #   automatically
1118
1119            rec = []
1120
1121            class PytesterHelperPlugin:
1122                @staticmethod
1123                def pytest_configure(config: Config) -> None:
1124                    rec.append(self.make_hook_recorder(config.pluginmanager))
1125
1126                    # The unraisable plugin GC collect slows down inline
1127                    # pytester runs too much.
1128                    config.stash[gc_collect_iterations_key] = 0
1129
1130            plugins.append(PytesterHelperPlugin())
1131            ret = main([str(x) for x in args], plugins=plugins)
1132            if len(rec) == 1:
1133                reprec = rec.pop()
1134            else:
1135
1136                class reprec:  # type: ignore
1137                    pass
1138
1139            reprec.ret = ret
1140
1141            # Typically we reraise keyboard interrupts from the child run
1142            # because it's our user requesting interruption of the testing.
1143            if ret == ExitCode.INTERRUPTED and not no_reraise_ctrlc:
1144                calls = reprec.getcalls("pytest_keyboard_interrupt")
1145                if calls and calls[-1].excinfo.type == KeyboardInterrupt:
1146                    raise KeyboardInterrupt()
1147            return reprec
1148        finally:
1149            for finalizer in finalizers:
1150                finalizer()

Run pytest.main() in-process, returning a HookRecorder.

Runs the pytest.main() function to run all of pytest inside the test process itself. This means it can return a HookRecorder instance which gives more detailed results from that run than can be done by matching stdout/stderr from runpytest().

Parameters
  • args: Command line arguments to pass to pytest.main().
  • plugins: Extra plugin instances the pytest.main() instance should use.
  • no_reraise_ctrlc: Typically we reraise keyboard interrupts from the child run. If True, the KeyboardInterrupt exception is captured.
def runpytest_inprocess( self, *args: str | os.PathLike[str], **kwargs: Any) -> _pytest.pytester.RunResult:
1152    def runpytest_inprocess(
1153        self, *args: str | os.PathLike[str], **kwargs: Any
1154    ) -> RunResult:
1155        """Return result of running pytest in-process, providing a similar
1156        interface to what self.runpytest() provides."""
1157        syspathinsert = kwargs.pop("syspathinsert", False)
1158
1159        if syspathinsert:
1160            self.syspathinsert()
1161        instant = timing.Instant()
1162        capture = _get_multicapture("sys")
1163        capture.start_capturing()
1164        try:
1165            try:
1166                reprec = self.inline_run(*args, **kwargs)
1167            except SystemExit as e:
1168                ret = e.args[0]
1169                try:
1170                    ret = ExitCode(e.args[0])
1171                except ValueError:
1172                    pass
1173
1174                class reprec:  # type: ignore
1175                    ret = ret
1176
1177            except Exception:
1178                traceback.print_exc()
1179
1180                class reprec:  # type: ignore
1181                    ret = ExitCode(3)
1182
1183        finally:
1184            out, err = capture.readouterr()
1185            capture.stop_capturing()
1186            sys.stdout.write(out)
1187            sys.stderr.write(err)
1188
1189        assert reprec.ret is not None
1190        res = RunResult(
1191            reprec.ret, out.splitlines(), err.splitlines(), instant.elapsed().seconds
1192        )
1193        res.reprec = reprec  # type: ignore
1194        return res

Return result of running pytest in-process, providing a similar interface to what self.runpytest() provides.

def runpytest( self, *args: str | os.PathLike[str], **kwargs: Any) -> _pytest.pytester.RunResult:
1196    def runpytest(self, *args: str | os.PathLike[str], **kwargs: Any) -> RunResult:
1197        """Run pytest inline or in a subprocess, depending on the command line
1198        option "--runpytest" and return a :py:class:`~pytest.RunResult`."""
1199        new_args = self._ensure_basetemp(args)
1200        if self._method == "inprocess":
1201            return self.runpytest_inprocess(*new_args, **kwargs)
1202        elif self._method == "subprocess":
1203            return self.runpytest_subprocess(*new_args, **kwargs)
1204        raise RuntimeError(f"Unrecognized runpytest option: {self._method}")

Run pytest inline or in a subprocess, depending on the command line option "--runpytest" and return a ~pytest.RunResult.

def parseconfig(self, *args: str | os.PathLike[str]) -> _pytest.config.Config:
1219    def parseconfig(self, *args: str | os.PathLike[str]) -> Config:
1220        """Return a new pytest :class:`pytest.Config` instance from given
1221        commandline args.
1222
1223        This invokes the pytest bootstrapping code in _pytest.config to create a
1224        new :py:class:`pytest.PytestPluginManager` and call the
1225        :hook:`pytest_cmdline_parse` hook to create a new :class:`pytest.Config`
1226        instance.
1227
1228        If :attr:`plugins` has been populated they should be plugin modules
1229        to be registered with the plugin manager.
1230        """
1231        import _pytest.config
1232
1233        new_args = self._ensure_basetemp(args)
1234        new_args = [str(x) for x in new_args]
1235
1236        config = _pytest.config._prepareconfig(new_args, self.plugins)  # type: ignore[arg-type]
1237        # we don't know what the test will do with this half-setup config
1238        # object and thus we make sure it gets unconfigured properly in any
1239        # case (otherwise capturing could still be active, for example)
1240        self._request.addfinalizer(config._ensure_unconfigure)
1241        return config

Return a new pytest pytest.Config instance from given commandline args.

This invokes the pytest bootstrapping code in _pytest.config to create a new pytest.PytestPluginManager and call the :hook:pytest_cmdline_parse hook to create a new pytest.Config instance.

If plugins has been populated they should be plugin modules to be registered with the plugin manager.

def parseconfigure(self, *args: str | os.PathLike[str]) -> _pytest.config.Config:
1243    def parseconfigure(self, *args: str | os.PathLike[str]) -> Config:
1244        """Return a new pytest configured Config instance.
1245
1246        Returns a new :py:class:`pytest.Config` instance like
1247        :py:meth:`parseconfig`, but also calls the :hook:`pytest_configure`
1248        hook.
1249        """
1250        config = self.parseconfig(*args)
1251        config._do_configure()
1252        return config

Return a new pytest configured Config instance.

Returns a new pytest.Config instance like parseconfig(), but also calls the :hook:pytest_configure hook.

def getitem( self, source: str | os.PathLike[str], funcname: str = 'test_func') -> _pytest.nodes.Item:
1254    def getitem(
1255        self, source: str | os.PathLike[str], funcname: str = "test_func"
1256    ) -> Item:
1257        """Return the test item for a test function.
1258
1259        Writes the source to a python file and runs pytest's collection on
1260        the resulting module, returning the test item for the requested
1261        function name.
1262
1263        :param source:
1264            The module source.
1265        :param funcname:
1266            The name of the test function for which to return a test item.
1267        :returns:
1268            The test item.
1269        """
1270        items = self.getitems(source)
1271        for item in items:
1272            if item.name == funcname:
1273                return item
1274        assert 0, f"{funcname!r} item not found in module:\n{source}\nitems: {items}"

Return the test item for a test function.

Writes the source to a python file and runs pytest's collection on the resulting module, returning the test item for the requested function name.

Parameters
  • source: The module source.
  • funcname: The name of the test function for which to return a test item. :returns: The test item.
def getitems(self, source: str | os.PathLike[str]) -> list[_pytest.nodes.Item]:
1276    def getitems(self, source: str | os.PathLike[str]) -> list[Item]:
1277        """Return all test items collected from the module.
1278
1279        Writes the source to a Python file and runs pytest's collection on
1280        the resulting module, returning all test items contained within.
1281        """
1282        modcol = self.getmodulecol(source)
1283        return self.genitems([modcol])

Return all test items collected from the module.

Writes the source to a Python file and runs pytest's collection on the resulting module, returning all test items contained within.

def getmodulecol( self, source: str | os.PathLike[str], configargs=(), *, withinit: bool = False):
1285    def getmodulecol(
1286        self,
1287        source: str | os.PathLike[str],
1288        configargs=(),
1289        *,
1290        withinit: bool = False,
1291    ):
1292        """Return the module collection node for ``source``.
1293
1294        Writes ``source`` to a file using :py:meth:`makepyfile` and then
1295        runs the pytest collection on it, returning the collection node for the
1296        test module.
1297
1298        :param source:
1299            The source code of the module to collect.
1300
1301        :param configargs:
1302            Any extra arguments to pass to :py:meth:`parseconfigure`.
1303
1304        :param withinit:
1305            Whether to also write an ``__init__.py`` file to the same
1306            directory to ensure it is a package.
1307        """
1308        if isinstance(source, os.PathLike):
1309            path = self.path.joinpath(source)
1310            assert not withinit, "not supported for paths"
1311        else:
1312            kw = {self._name: str(source)}
1313            path = self.makepyfile(**kw)
1314        if withinit:
1315            self.makepyfile(__init__="#")
1316        self.config = config = self.parseconfigure(path, *configargs)
1317        return self.getnode(config, path)

Return the module collection node for source.

Writes source to a file using makepyfile() and then runs the pytest collection on it, returning the collection node for the test module.

Parameters
  • source: The source code of the module to collect.

  • configargs: Any extra arguments to pass to parseconfigure().

  • withinit: Whether to also write an __init__.py file to the same directory to ensure it is a package.

def collect_by_name( self, modcol: _pytest.nodes.Collector, name: str) -> _pytest.nodes.Item | _pytest.nodes.Collector | None:
1319    def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None:
1320        """Return the collection node for name from the module collection.
1321
1322        Searches a module collection node for a collection node matching the
1323        given name.
1324
1325        :param modcol: A module collection node; see :py:meth:`getmodulecol`.
1326        :param name: The name of the node to return.
1327        """
1328        if modcol not in self._mod_collections:
1329            self._mod_collections[modcol] = list(modcol.collect())
1330        for colitem in self._mod_collections[modcol]:
1331            if colitem.name == name:
1332                return colitem
1333        return None

Return the collection node for name from the module collection.

Searches a module collection node for a collection node matching the given name.

Parameters
  • modcol: A module collection node; see getmodulecol().
  • name: The name of the node to return.
def popen( self, cmdargs: Sequence[str | os.PathLike[str]], stdout: int | typing.TextIO = -1, stderr: int | typing.TextIO = -1, stdin: Union[_pytest.compat.NotSetType, bytes, IO[Any], int] = <NotSetType.token: 0>, **kw):
1335    def popen(
1336        self,
1337        cmdargs: Sequence[str | os.PathLike[str]],
1338        stdout: int | TextIO = subprocess.PIPE,
1339        stderr: int | TextIO = subprocess.PIPE,
1340        stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN,
1341        **kw,
1342    ):
1343        """Invoke :py:class:`subprocess.Popen`.
1344
1345        Calls :py:class:`subprocess.Popen` making sure the current working
1346        directory is in ``PYTHONPATH``.
1347
1348        You probably want to use :py:meth:`run` instead.
1349        """
1350        env = os.environ.copy()
1351        env["PYTHONPATH"] = os.pathsep.join(
1352            filter(None, [os.getcwd(), env.get("PYTHONPATH", "")])
1353        )
1354        kw["env"] = env
1355
1356        if stdin is self.CLOSE_STDIN:
1357            kw["stdin"] = subprocess.PIPE
1358        elif isinstance(stdin, bytes):
1359            kw["stdin"] = subprocess.PIPE
1360        else:
1361            kw["stdin"] = stdin
1362
1363        popen = subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw)
1364        if stdin is self.CLOSE_STDIN:
1365            assert popen.stdin is not None
1366            popen.stdin.close()
1367        elif isinstance(stdin, bytes):
1368            assert popen.stdin is not None
1369            popen.stdin.write(stdin)
1370
1371        return popen

Invoke subprocess.Popen.

Calls subprocess.Popen making sure the current working directory is in PYTHONPATH.

You probably want to use run() instead.

def run( self, *cmdargs: str | os.PathLike[str], timeout: float | None = None, stdin: Union[_pytest.compat.NotSetType, bytes, IO[Any], int] = <NotSetType.token: 0>) -> _pytest.pytester.RunResult:
1373    def run(
1374        self,
1375        *cmdargs: str | os.PathLike[str],
1376        timeout: float | None = None,
1377        stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN,
1378    ) -> RunResult:
1379        """Run a command with arguments.
1380
1381        Run a process using :py:class:`subprocess.Popen` saving the stdout and
1382        stderr.
1383
1384        :param cmdargs:
1385            The sequence of arguments to pass to :py:class:`subprocess.Popen`,
1386            with path-like objects being converted to :py:class:`str`
1387            automatically.
1388        :param timeout:
1389            The period in seconds after which to timeout and raise
1390            :py:class:`Pytester.TimeoutExpired`.
1391        :param stdin:
1392            Optional standard input.
1393
1394            - If it is ``CLOSE_STDIN`` (Default), then this method calls
1395              :py:class:`subprocess.Popen` with ``stdin=subprocess.PIPE``, and
1396              the standard input is closed immediately after the new command is
1397              started.
1398
1399            - If it is of type :py:class:`bytes`, these bytes are sent to the
1400              standard input of the command.
1401
1402            - Otherwise, it is passed through to :py:class:`subprocess.Popen`.
1403              For further information in this case, consult the document of the
1404              ``stdin`` parameter in :py:class:`subprocess.Popen`.
1405        :type stdin: _pytest.compat.NotSetType | bytes | IO[Any] | int
1406        :returns:
1407            The result.
1408
1409        """
1410        __tracebackhide__ = True
1411
1412        cmdargs = tuple(os.fspath(arg) for arg in cmdargs)
1413        p1 = self.path.joinpath("stdout")
1414        p2 = self.path.joinpath("stderr")
1415        print("running:", *cmdargs)
1416        print("     in:", Path.cwd())
1417
1418        with p1.open("w", encoding="utf8") as f1, p2.open("w", encoding="utf8") as f2:
1419            instant = timing.Instant()
1420            popen = self.popen(
1421                cmdargs,
1422                stdin=stdin,
1423                stdout=f1,
1424                stderr=f2,
1425                close_fds=(sys.platform != "win32"),
1426            )
1427            if popen.stdin is not None:
1428                popen.stdin.close()
1429
1430            def handle_timeout() -> None:
1431                __tracebackhide__ = True
1432
1433                timeout_message = f"{timeout} second timeout expired running: {cmdargs}"
1434
1435                popen.kill()
1436                popen.wait()
1437                raise self.TimeoutExpired(timeout_message)
1438
1439            if timeout is None:
1440                ret = popen.wait()
1441            else:
1442                try:
1443                    ret = popen.wait(timeout)
1444                except subprocess.TimeoutExpired:
1445                    handle_timeout()
1446
1447        with p1.open(encoding="utf8") as f1, p2.open(encoding="utf8") as f2:
1448            out = f1.read().splitlines()
1449            err = f2.read().splitlines()
1450
1451        self._dump_lines(out, sys.stdout)
1452        self._dump_lines(err, sys.stderr)
1453
1454        with contextlib.suppress(ValueError):
1455            ret = ExitCode(ret)
1456        return RunResult(ret, out, err, instant.elapsed().seconds)

Run a command with arguments.

Run a process using subprocess.Popen saving the stdout and stderr.

Parameters
  • cmdargs: The sequence of arguments to pass to subprocess.Popen, with path-like objects being converted to str automatically.
  • timeout: The period in seconds after which to timeout and raise Pytester.TimeoutExpired.
  • stdin: Optional standard input.

    • If it is CLOSE_STDIN (Default), then this method calls subprocess.Popen with stdin=subprocess.PIPE, and the standard input is closed immediately after the new command is started.

    • If it is of type bytes, these bytes are sent to the standard input of the command.

    • Otherwise, it is passed through to subprocess.Popen. For further information in this case, consult the document of the stdin parameter in subprocess.Popen. :returns: The result.

def runpython(self, script: os.PathLike[str]) -> _pytest.pytester.RunResult:
1468    def runpython(self, script: os.PathLike[str]) -> RunResult:
1469        """Run a python script using sys.executable as interpreter."""
1470        return self.run(sys.executable, script)

Run a python script using sys.executable as interpreter.

def runpython_c(self, command: str) -> _pytest.pytester.RunResult:
1472    def runpython_c(self, command: str) -> RunResult:
1473        """Run ``python -c "command"``."""
1474        return self.run(sys.executable, "-c", command)

Run python -c "command".

def runpytest_subprocess( self, *args: str | os.PathLike[str], timeout: float | None = None) -> _pytest.pytester.RunResult:
1476    def runpytest_subprocess(
1477        self, *args: str | os.PathLike[str], timeout: float | None = None
1478    ) -> RunResult:
1479        """Run pytest as a subprocess with given arguments.
1480
1481        Any plugins added to the :py:attr:`plugins` list will be added using the
1482        ``-p`` command line option.  Additionally ``--basetemp`` is used to put
1483        any temporary files and directories in a numbered directory prefixed
1484        with "runpytest-" to not conflict with the normal numbered pytest
1485        location for temporary files and directories.
1486
1487        :param args:
1488            The sequence of arguments to pass to the pytest subprocess.
1489        :param timeout:
1490            The period in seconds after which to timeout and raise
1491            :py:class:`Pytester.TimeoutExpired`.
1492        :returns:
1493            The result.
1494        """
1495        __tracebackhide__ = True
1496        p = make_numbered_dir(root=self.path, prefix="runpytest-", mode=0o700)
1497        args = (f"--basetemp={p}", *args)
1498        plugins = [x for x in self.plugins if isinstance(x, str)]
1499        if plugins:
1500            args = ("-p", plugins[0], *args)
1501        args = self._getpytestargs() + args
1502        return self.run(*args, timeout=timeout)

Run pytest as a subprocess with given arguments.

Any plugins added to the plugins list will be added using the -p command line option. Additionally --basetemp is used to put any temporary files and directories in a numbered directory prefixed with "runpytest-" to not conflict with the normal numbered pytest location for temporary files and directories.

Parameters
  • args: The sequence of arguments to pass to the pytest subprocess.
  • timeout: The period in seconds after which to timeout and raise Pytester.TimeoutExpired. :returns: The result.
def spawn_pytest( self, string: str, expect_timeout: float = 10.0) -> pexpect.pty_spawn.spawn:
1504    def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn:
1505        """Run pytest using pexpect.
1506
1507        This makes sure to use the right pytest and sets up the temporary
1508        directory locations.
1509
1510        The pexpect child is returned.
1511        """
1512        basetemp = self.path / "temp-pexpect"
1513        basetemp.mkdir(mode=0o700)
1514        invoke = " ".join(map(str, self._getpytestargs()))
1515        cmd = f"{invoke} --basetemp={basetemp} {string}"
1516        return self.spawn(cmd, expect_timeout=expect_timeout)

Run pytest using pexpect.

This makes sure to use the right pytest and sets up the temporary directory locations.

The pexpect child is returned.

def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.pty_spawn.spawn:
1518    def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn:
1519        """Run a command using pexpect.
1520
1521        The pexpect child is returned.
1522        """
1523        pexpect = importorskip("pexpect", "3.0")
1524        if hasattr(sys, "pypy_version_info") and "64" in platform.machine():
1525            skip("pypy-64 bit not supported")
1526        if not hasattr(pexpect, "spawn"):
1527            skip("pexpect.spawn not available")
1528        logfile = self.path.joinpath("spawn.out").open("wb")
1529
1530        child = pexpect.spawn(cmd, logfile=logfile, timeout=expect_timeout)
1531        self._request.addfinalizer(logfile.close)
1532        return child

Run a command using pexpect.

The pexpect child is returned.

class Pytester.TimeoutExpired(builtins.Exception):
663    class TimeoutExpired(Exception):
664        pass

Common base class for all non-exit exceptions.

@final
class RaisesExc(_pytest.raises.AbstractRaises[+BaseExcT_co_default]):
543@final
544class RaisesExc(AbstractRaises[BaseExcT_co_default]):
545    """
546    .. versionadded:: 8.4
547
548
549    This is the class constructed when calling :func:`pytest.raises`, but may be used
550    directly as a helper class with :class:`RaisesGroup` when you want to specify
551    requirements on sub-exceptions.
552
553    You don't need this if you only want to specify the type, since :class:`RaisesGroup`
554    accepts ``type[BaseException]``.
555
556    :param type[BaseException] | tuple[type[BaseException]] | None expected_exception:
557        The expected type, or one of several possible types.
558        May be ``None`` in order to only make use of ``match`` and/or ``check``
559
560        The type is checked with :func:`isinstance`, and does not need to be an exact match.
561        If that is wanted you can use the ``check`` parameter.
562
563    :kwparam str | Pattern[str] match
564        A regex to match.
565
566    :kwparam Callable[[BaseException], bool] check:
567        If specified, a callable that will be called with the exception as a parameter
568        after checking the type and the match regex if specified.
569        If it returns ``True`` it will be considered a match, if not it will
570        be considered a failed match.
571
572    :meth:`RaisesExc.matches` can also be used standalone to check individual exceptions.
573
574    Examples::
575
576        with RaisesGroup(RaisesExc(ValueError, match="string"))
577            ...
578        with RaisesGroup(RaisesExc(check=lambda x: x.args == (3, "hello"))):
579            ...
580        with RaisesGroup(RaisesExc(check=lambda x: type(x) is ValueError)):
581            ...
582    """
583
584    # Trio bundled hypothesis monkeypatching, we will probably instead assume that
585    # hypothesis will handle that in their pytest plugin by the time this is released.
586    # Alternatively we could add a version of get_pretty_function_description ourselves
587    # https://github.com/HypothesisWorks/hypothesis/blob/8ced2f59f5c7bea3344e35d2d53e1f8f8eb9fcd8/hypothesis-python/src/hypothesis/internal/reflection.py#L439
588
589    # At least one of the three parameters must be passed.
590    @overload
591    def __init__(
592        self,
593        expected_exception: (
594            type[BaseExcT_co_default] | tuple[type[BaseExcT_co_default], ...]
595        ),
596        /,
597        *,
598        match: str | Pattern[str] | None = ...,
599        check: Callable[[BaseExcT_co_default], bool] | None = ...,
600    ) -> None: ...
601
602    @overload
603    def __init__(
604        self: RaisesExc[BaseException],  # Give E a value.
605        /,
606        *,
607        match: str | Pattern[str] | None,
608        # If exception_type is not provided, check() must do any typechecks itself.
609        check: Callable[[BaseException], bool] | None = ...,
610    ) -> None: ...
611
612    @overload
613    def __init__(self, /, *, check: Callable[[BaseException], bool]) -> None: ...
614
615    def __init__(
616        self,
617        expected_exception: (
618            type[BaseExcT_co_default] | tuple[type[BaseExcT_co_default], ...] | None
619        ) = None,
620        /,
621        *,
622        match: str | Pattern[str] | None = None,
623        check: Callable[[BaseExcT_co_default], bool] | None = None,
624    ):
625        super().__init__(match=match, check=check)
626        if isinstance(expected_exception, tuple):
627            expected_exceptions = expected_exception
628        elif expected_exception is None:
629            expected_exceptions = ()
630        else:
631            expected_exceptions = (expected_exception,)
632
633        if (expected_exceptions == ()) and match is None and check is None:
634            raise ValueError("You must specify at least one parameter to match on.")
635
636        self.expected_exceptions = tuple(
637            self._parse_exc(e, expected="a BaseException type")
638            for e in expected_exceptions
639        )
640
641        self._just_propagate = False
642
643    def matches(
644        self,
645        exception: BaseException | None,
646    ) -> TypeGuard[BaseExcT_co_default]:
647        """Check if an exception matches the requirements of this :class:`RaisesExc`.
648        If it fails, :attr:`RaisesExc.fail_reason` will be set.
649
650        Examples::
651
652            assert RaisesExc(ValueError).matches(my_exception):
653            # is equivalent to
654            assert isinstance(my_exception, ValueError)
655
656            # this can be useful when checking e.g. the ``__cause__`` of an exception.
657            with pytest.raises(ValueError) as excinfo:
658                ...
659            assert RaisesExc(SyntaxError, match="foo").matches(excinfo.value.__cause__)
660            # above line is equivalent to
661            assert isinstance(excinfo.value.__cause__, SyntaxError)
662            assert re.search("foo", str(excinfo.value.__cause__)
663
664        """
665        self._just_propagate = False
666        if exception is None:
667            self._fail_reason = "exception is None"
668            return False
669        if not self._check_type(exception):
670            self._just_propagate = True
671            return False
672
673        if not self._check_match(exception):
674            return False
675
676        return self._check_check(exception)
677
678    def __repr__(self) -> str:
679        parameters = []
680        if self.expected_exceptions:
681            parameters.append(_exception_type_name(self.expected_exceptions))
682        if self.match is not None:
683            # If no flags were specified, discard the redundant re.compile() here.
684            parameters.append(
685                f"match={_match_pattern(self.match)!r}",
686            )
687        if self.check is not None:
688            parameters.append(f"check={repr_callable(self.check)}")
689        return f"RaisesExc({', '.join(parameters)})"
690
691    def _check_type(self, exception: BaseException) -> TypeGuard[BaseExcT_co_default]:
692        self._fail_reason = _check_raw_type(self.expected_exceptions, exception)
693        return self._fail_reason is None
694
695    def __enter__(self) -> ExceptionInfo[BaseExcT_co_default]:
696        self.excinfo: ExceptionInfo[BaseExcT_co_default] = ExceptionInfo.for_later()
697        return self.excinfo
698
699    # TODO: move common code into superclass
700    def __exit__(
701        self,
702        exc_type: type[BaseException] | None,
703        exc_val: BaseException | None,
704        exc_tb: types.TracebackType | None,
705    ) -> bool:
706        __tracebackhide__ = True
707        if exc_type is None:
708            if not self.expected_exceptions:
709                fail("DID NOT RAISE any exception")
710            if len(self.expected_exceptions) > 1:
711                fail(f"DID NOT RAISE any of {self.expected_exceptions!r}")
712
713            fail(f"DID NOT RAISE {self.expected_exceptions[0]!r}")
714
715        assert self.excinfo is not None, (
716            "Internal error - should have been constructed in __enter__"
717        )
718
719        if not self.matches(exc_val):
720            if self._just_propagate:
721                return False
722            raise AssertionError(self._fail_reason)
723
724        # Cast to narrow the exception type now that it's verified....
725        # even though the TypeGuard in self.matches should be narrowing
726        exc_info = cast(
727            "tuple[type[BaseExcT_co_default], BaseExcT_co_default, types.TracebackType]",
728            (exc_type, exc_val, exc_tb),
729        )
730        self.excinfo.fill_unfilled(exc_info)
731        return True

New in version 8.4.

This is the class constructed when calling pytest.raises(), but may be used directly as a helper class with RaisesGroup when you want to specify requirements on sub-exceptions.

You don't need this if you only want to specify the type, since RaisesGroup accepts type[BaseException].

Parameters
  • type[BaseException] | tuple[type[BaseException]] | None expected_exception: The expected type, or one of several possible types. May be None in order to only make use of match and/or check

    The type is checked with isinstance(), and does not need to be an exact match. If that is wanted you can use the check parameter.

:kwparam str | Pattern[str] match A regex to match.

:kwparam Callable[[BaseException], bool] check: If specified, a callable that will be called with the exception as a parameter after checking the type and the match regex if specified. If it returns True it will be considered a match, if not it will be considered a failed match.

RaisesExc.matches() can also be used standalone to check individual exceptions.

Examples::

with RaisesGroup(RaisesExc(ValueError, match="string"))
    ...
with RaisesGroup(RaisesExc(check=lambda x: x.args == (3, "hello"))):
    ...
with RaisesGroup(RaisesExc(check=lambda x: type(x) is ValueError)):
    ...
RaisesExc( expected_exception: type[+BaseExcT_co_default] | tuple[type[+BaseExcT_co_default], ...] | None = None, /, *, match: str | re.Pattern[str] | None = None, check: Callable[[+BaseExcT_co_default], bool] | None = None)
615    def __init__(
616        self,
617        expected_exception: (
618            type[BaseExcT_co_default] | tuple[type[BaseExcT_co_default], ...] | None
619        ) = None,
620        /,
621        *,
622        match: str | Pattern[str] | None = None,
623        check: Callable[[BaseExcT_co_default], bool] | None = None,
624    ):
625        super().__init__(match=match, check=check)
626        if isinstance(expected_exception, tuple):
627            expected_exceptions = expected_exception
628        elif expected_exception is None:
629            expected_exceptions = ()
630        else:
631            expected_exceptions = (expected_exception,)
632
633        if (expected_exceptions == ()) and match is None and check is None:
634            raise ValueError("You must specify at least one parameter to match on.")
635
636        self.expected_exceptions = tuple(
637            self._parse_exc(e, expected="a BaseException type")
638            for e in expected_exceptions
639        )
640
641        self._just_propagate = False
expected_exceptions
def matches(self, exception: BaseException | None) -> TypeGuard[+BaseExcT_co_default]:
643    def matches(
644        self,
645        exception: BaseException | None,
646    ) -> TypeGuard[BaseExcT_co_default]:
647        """Check if an exception matches the requirements of this :class:`RaisesExc`.
648        If it fails, :attr:`RaisesExc.fail_reason` will be set.
649
650        Examples::
651
652            assert RaisesExc(ValueError).matches(my_exception):
653            # is equivalent to
654            assert isinstance(my_exception, ValueError)
655
656            # this can be useful when checking e.g. the ``__cause__`` of an exception.
657            with pytest.raises(ValueError) as excinfo:
658                ...
659            assert RaisesExc(SyntaxError, match="foo").matches(excinfo.value.__cause__)
660            # above line is equivalent to
661            assert isinstance(excinfo.value.__cause__, SyntaxError)
662            assert re.search("foo", str(excinfo.value.__cause__)
663
664        """
665        self._just_propagate = False
666        if exception is None:
667            self._fail_reason = "exception is None"
668            return False
669        if not self._check_type(exception):
670            self._just_propagate = True
671            return False
672
673        if not self._check_match(exception):
674            return False
675
676        return self._check_check(exception)

Check if an exception matches the requirements of this RaisesExc. If it fails, RaisesExc.fail_reason will be set.

Examples::

assert RaisesExc(ValueError).matches(my_exception):
# is equivalent to
assert isinstance(my_exception, ValueError)

# this can be useful when checking e.g. the ``__cause__`` of an exception.
with pytest.raises(ValueError) as excinfo:
    ...
assert RaisesExc(SyntaxError, match="foo").matches(excinfo.value.__cause__)
# above line is equivalent to
assert isinstance(excinfo.value.__cause__, SyntaxError)
assert re.search("foo", str(excinfo.value.__cause__)
@final
class RaisesGroup(_pytest.raises.AbstractRaises[BaseExceptionGroup[+BaseExcT_co]]):
 734@final
 735class RaisesGroup(AbstractRaises[BaseExceptionGroup[BaseExcT_co]]):
 736    """
 737    .. versionadded:: 8.4
 738
 739    Contextmanager for checking for an expected :exc:`ExceptionGroup`.
 740    This works similar to :func:`pytest.raises`, but allows for specifying the structure of an :exc:`ExceptionGroup`.
 741    :meth:`ExceptionInfo.group_contains` also tries to handle exception groups,
 742    but it is very bad at checking that you *didn't* get unexpected exceptions.
 743
 744    The catching behaviour differs from :ref:`except* <except_star>`, being much
 745    stricter about the structure by default.
 746    By using ``allow_unwrapped=True`` and ``flatten_subgroups=True`` you can match
 747    :ref:`except* <except_star>` fully when expecting a single exception.
 748
 749    :param args:
 750        Any number of exception types, :class:`RaisesGroup` or :class:`RaisesExc`
 751        to specify the exceptions contained in this exception.
 752        All specified exceptions must be present in the raised group, *and no others*.
 753
 754        If you expect a variable number of exceptions you need to use
 755        :func:`pytest.raises(ExceptionGroup) <pytest.raises>` and manually check
 756        the contained exceptions. Consider making use of :meth:`RaisesExc.matches`.
 757
 758        It does not care about the order of the exceptions, so
 759        ``RaisesGroup(ValueError, TypeError)``
 760        is equivalent to
 761        ``RaisesGroup(TypeError, ValueError)``.
 762    :kwparam str | re.Pattern[str] | None match:
 763        If specified, a string containing a regular expression,
 764        or a regular expression object, that is tested against the string
 765        representation of the exception group and its :pep:`678` `__notes__`
 766        using :func:`re.search`.
 767
 768        To match a literal string that may contain :ref:`special characters
 769        <re-syntax>`, the pattern can first be escaped with :func:`re.escape`.
 770
 771        Note that " (5 subgroups)" will be stripped from the ``repr`` before matching.
 772    :kwparam Callable[[E], bool] check:
 773        If specified, a callable that will be called with the group as a parameter
 774        after successfully matching the expected exceptions. If it returns ``True``
 775        it will be considered a match, if not it will be considered a failed match.
 776    :kwparam bool allow_unwrapped:
 777        If expecting a single exception or :class:`RaisesExc` it will match even
 778        if the exception is not inside an exceptiongroup.
 779
 780        Using this together with ``match``, ``check`` or expecting multiple exceptions
 781        will raise an error.
 782    :kwparam bool flatten_subgroups:
 783        "flatten" any groups inside the raised exception group, extracting all exceptions
 784        inside any nested groups, before matching. Without this it expects you to
 785        fully specify the nesting structure by passing :class:`RaisesGroup` as expected
 786        parameter.
 787
 788    Examples::
 789
 790        with RaisesGroup(ValueError):
 791            raise ExceptionGroup("", (ValueError(),))
 792        # match
 793        with RaisesGroup(
 794            ValueError,
 795            ValueError,
 796            RaisesExc(TypeError, match="^expected int$"),
 797            match="^my group$",
 798        ):
 799            raise ExceptionGroup(
 800                "my group",
 801                [
 802                    ValueError(),
 803                    TypeError("expected int"),
 804                    ValueError(),
 805                ],
 806            )
 807        # check
 808        with RaisesGroup(
 809            KeyboardInterrupt,
 810            match="^hello$",
 811            check=lambda x: isinstance(x.__cause__, ValueError),
 812        ):
 813            raise BaseExceptionGroup("hello", [KeyboardInterrupt()]) from ValueError
 814        # nested groups
 815        with RaisesGroup(RaisesGroup(ValueError)):
 816            raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),))
 817
 818        # flatten_subgroups
 819        with RaisesGroup(ValueError, flatten_subgroups=True):
 820            raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),))
 821
 822        # allow_unwrapped
 823        with RaisesGroup(ValueError, allow_unwrapped=True):
 824            raise ValueError
 825
 826
 827    :meth:`RaisesGroup.matches` can also be used directly to check a standalone exception group.
 828
 829
 830    The matching algorithm is greedy, which means cases such as this may fail::
 831
 832        with RaisesGroup(ValueError, RaisesExc(ValueError, match="hello")):
 833            raise ExceptionGroup("", (ValueError("hello"), ValueError("goodbye")))
 834
 835    even though it generally does not care about the order of the exceptions in the group.
 836    To avoid the above you should specify the first :exc:`ValueError` with a :class:`RaisesExc` as well.
 837
 838    .. note::
 839        When raised exceptions don't match the expected ones, you'll get a detailed error
 840        message explaining why. This includes ``repr(check)`` if set, which in Python can be
 841        overly verbose, showing memory locations etc etc.
 842
 843        If installed and imported (in e.g. ``conftest.py``), the ``hypothesis`` library will
 844        monkeypatch this output to provide shorter & more readable repr's.
 845    """
 846
 847    # allow_unwrapped=True requires: singular exception, exception not being
 848    # RaisesGroup instance, match is None, check is None
 849    @overload
 850    def __init__(
 851        self,
 852        expected_exception: type[BaseExcT_co] | RaisesExc[BaseExcT_co],
 853        /,
 854        *,
 855        allow_unwrapped: Literal[True],
 856        flatten_subgroups: bool = False,
 857    ) -> None: ...
 858
 859    # flatten_subgroups = True also requires no nested RaisesGroup
 860    @overload
 861    def __init__(
 862        self,
 863        expected_exception: type[BaseExcT_co] | RaisesExc[BaseExcT_co],
 864        /,
 865        *other_exceptions: type[BaseExcT_co] | RaisesExc[BaseExcT_co],
 866        flatten_subgroups: Literal[True],
 867        match: str | Pattern[str] | None = None,
 868        check: Callable[[BaseExceptionGroup[BaseExcT_co]], bool] | None = None,
 869    ) -> None: ...
 870
 871    # simplify the typevars if possible (the following 3 are equivalent but go simpler->complicated)
 872    # ... the first handles RaisesGroup[ValueError], the second RaisesGroup[ExceptionGroup[ValueError]],
 873    #     the third RaisesGroup[ValueError | ExceptionGroup[ValueError]].
 874    # ... otherwise, we will get results like RaisesGroup[ValueError | ExceptionGroup[Never]] (I think)
 875    #     (technically correct but misleading)
 876    @overload
 877    def __init__(
 878        self: RaisesGroup[ExcT_1],
 879        expected_exception: type[ExcT_1] | RaisesExc[ExcT_1],
 880        /,
 881        *other_exceptions: type[ExcT_1] | RaisesExc[ExcT_1],
 882        match: str | Pattern[str] | None = None,
 883        check: Callable[[ExceptionGroup[ExcT_1]], bool] | None = None,
 884    ) -> None: ...
 885
 886    @overload
 887    def __init__(
 888        self: RaisesGroup[ExceptionGroup[ExcT_2]],
 889        expected_exception: RaisesGroup[ExcT_2],
 890        /,
 891        *other_exceptions: RaisesGroup[ExcT_2],
 892        match: str | Pattern[str] | None = None,
 893        check: Callable[[ExceptionGroup[ExceptionGroup[ExcT_2]]], bool] | None = None,
 894    ) -> None: ...
 895
 896    @overload
 897    def __init__(
 898        self: RaisesGroup[ExcT_1 | ExceptionGroup[ExcT_2]],
 899        expected_exception: type[ExcT_1] | RaisesExc[ExcT_1] | RaisesGroup[ExcT_2],
 900        /,
 901        *other_exceptions: type[ExcT_1] | RaisesExc[ExcT_1] | RaisesGroup[ExcT_2],
 902        match: str | Pattern[str] | None = None,
 903        check: (
 904            Callable[[ExceptionGroup[ExcT_1 | ExceptionGroup[ExcT_2]]], bool] | None
 905        ) = None,
 906    ) -> None: ...
 907
 908    # same as the above 3 but handling BaseException
 909    @overload
 910    def __init__(
 911        self: RaisesGroup[BaseExcT_1],
 912        expected_exception: type[BaseExcT_1] | RaisesExc[BaseExcT_1],
 913        /,
 914        *other_exceptions: type[BaseExcT_1] | RaisesExc[BaseExcT_1],
 915        match: str | Pattern[str] | None = None,
 916        check: Callable[[BaseExceptionGroup[BaseExcT_1]], bool] | None = None,
 917    ) -> None: ...
 918
 919    @overload
 920    def __init__(
 921        self: RaisesGroup[BaseExceptionGroup[BaseExcT_2]],
 922        expected_exception: RaisesGroup[BaseExcT_2],
 923        /,
 924        *other_exceptions: RaisesGroup[BaseExcT_2],
 925        match: str | Pattern[str] | None = None,
 926        check: (
 927            Callable[[BaseExceptionGroup[BaseExceptionGroup[BaseExcT_2]]], bool] | None
 928        ) = None,
 929    ) -> None: ...
 930
 931    @overload
 932    def __init__(
 933        self: RaisesGroup[BaseExcT_1 | BaseExceptionGroup[BaseExcT_2]],
 934        expected_exception: type[BaseExcT_1]
 935        | RaisesExc[BaseExcT_1]
 936        | RaisesGroup[BaseExcT_2],
 937        /,
 938        *other_exceptions: type[BaseExcT_1]
 939        | RaisesExc[BaseExcT_1]
 940        | RaisesGroup[BaseExcT_2],
 941        match: str | Pattern[str] | None = None,
 942        check: (
 943            Callable[
 944                [BaseExceptionGroup[BaseExcT_1 | BaseExceptionGroup[BaseExcT_2]]],
 945                bool,
 946            ]
 947            | None
 948        ) = None,
 949    ) -> None: ...
 950
 951    def __init__(
 952        self: RaisesGroup[ExcT_1 | BaseExcT_1 | BaseExceptionGroup[BaseExcT_2]],
 953        expected_exception: type[BaseExcT_1]
 954        | RaisesExc[BaseExcT_1]
 955        | RaisesGroup[BaseExcT_2],
 956        /,
 957        *other_exceptions: type[BaseExcT_1]
 958        | RaisesExc[BaseExcT_1]
 959        | RaisesGroup[BaseExcT_2],
 960        allow_unwrapped: bool = False,
 961        flatten_subgroups: bool = False,
 962        match: str | Pattern[str] | None = None,
 963        check: (
 964            Callable[[BaseExceptionGroup[BaseExcT_1]], bool]
 965            | Callable[[ExceptionGroup[ExcT_1]], bool]
 966            | None
 967        ) = None,
 968    ):
 969        # The type hint on the `self` and `check` parameters uses different formats
 970        # that are *very* hard to reconcile while adhering to the overloads, so we cast
 971        # it to avoid an error when passing it to super().__init__
 972        check = cast(
 973            "Callable[[BaseExceptionGroup[ExcT_1|BaseExcT_1|BaseExceptionGroup[BaseExcT_2]]], bool]",
 974            check,
 975        )
 976        super().__init__(match=match, check=check)
 977        self.allow_unwrapped = allow_unwrapped
 978        self.flatten_subgroups: bool = flatten_subgroups
 979        self.is_baseexception = False
 980
 981        if allow_unwrapped and other_exceptions:
 982            raise ValueError(
 983                "You cannot specify multiple exceptions with `allow_unwrapped=True.`"
 984                " If you want to match one of multiple possible exceptions you should"
 985                " use a `RaisesExc`."
 986                " E.g. `RaisesExc(check=lambda e: isinstance(e, (...)))`",
 987            )
 988        if allow_unwrapped and isinstance(expected_exception, RaisesGroup):
 989            raise ValueError(
 990                "`allow_unwrapped=True` has no effect when expecting a `RaisesGroup`."
 991                " You might want it in the expected `RaisesGroup`, or"
 992                " `flatten_subgroups=True` if you don't care about the structure.",
 993            )
 994        if allow_unwrapped and (match is not None or check is not None):
 995            raise ValueError(
 996                "`allow_unwrapped=True` bypasses the `match` and `check` parameters"
 997                " if the exception is unwrapped. If you intended to match/check the"
 998                " exception you should use a `RaisesExc` object. If you want to match/check"
 999                " the exceptiongroup when the exception *is* wrapped you need to"
1000                " do e.g. `if isinstance(exc.value, ExceptionGroup):"
1001                " assert RaisesGroup(...).matches(exc.value)` afterwards.",
1002            )
1003
1004        self.expected_exceptions: tuple[
1005            type[BaseExcT_co] | RaisesExc[BaseExcT_co] | RaisesGroup[BaseException], ...
1006        ] = tuple(
1007            self._parse_excgroup(e, "a BaseException type, RaisesExc, or RaisesGroup")
1008            for e in (
1009                expected_exception,
1010                *other_exceptions,
1011            )
1012        )
1013
1014    def _parse_excgroup(
1015        self,
1016        exc: (
1017            type[BaseExcT_co]
1018            | types.GenericAlias
1019            | RaisesExc[BaseExcT_1]
1020            | RaisesGroup[BaseExcT_2]
1021        ),
1022        expected: str,
1023    ) -> type[BaseExcT_co] | RaisesExc[BaseExcT_1] | RaisesGroup[BaseExcT_2]:
1024        # verify exception type and set `self.is_baseexception`
1025        if isinstance(exc, RaisesGroup):
1026            if self.flatten_subgroups:
1027                raise ValueError(
1028                    "You cannot specify a nested structure inside a RaisesGroup with"
1029                    " `flatten_subgroups=True`. The parameter will flatten subgroups"
1030                    " in the raised exceptiongroup before matching, which would never"
1031                    " match a nested structure.",
1032                )
1033            self.is_baseexception |= exc.is_baseexception
1034            exc._nested = True
1035            return exc
1036        elif isinstance(exc, RaisesExc):
1037            self.is_baseexception |= exc.is_baseexception
1038            exc._nested = True
1039            return exc
1040        elif isinstance(exc, tuple):
1041            raise TypeError(
1042                f"expected exception must be {expected}, not {type(exc).__name__!r}.\n"
1043                "RaisesGroup does not support tuples of exception types when expecting one of "
1044                "several possible exception types like RaisesExc.\n"
1045                "If you meant to expect a group with multiple exceptions, list them as separate arguments."
1046            )
1047        else:
1048            return super()._parse_exc(exc, expected)
1049
1050    @overload
1051    def __enter__(
1052        self: RaisesGroup[ExcT_1],
1053    ) -> ExceptionInfo[ExceptionGroup[ExcT_1]]: ...
1054    @overload
1055    def __enter__(
1056        self: RaisesGroup[BaseExcT_1],
1057    ) -> ExceptionInfo[BaseExceptionGroup[BaseExcT_1]]: ...
1058
1059    def __enter__(self) -> ExceptionInfo[BaseExceptionGroup[BaseException]]:
1060        self.excinfo: ExceptionInfo[BaseExceptionGroup[BaseExcT_co]] = (
1061            ExceptionInfo.for_later()
1062        )
1063        return self.excinfo
1064
1065    def __repr__(self) -> str:
1066        reqs = [
1067            e.__name__ if isinstance(e, type) else repr(e)
1068            for e in self.expected_exceptions
1069        ]
1070        if self.allow_unwrapped:
1071            reqs.append(f"allow_unwrapped={self.allow_unwrapped}")
1072        if self.flatten_subgroups:
1073            reqs.append(f"flatten_subgroups={self.flatten_subgroups}")
1074        if self.match is not None:
1075            # If no flags were specified, discard the redundant re.compile() here.
1076            reqs.append(f"match={_match_pattern(self.match)!r}")
1077        if self.check is not None:
1078            reqs.append(f"check={repr_callable(self.check)}")
1079        return f"RaisesGroup({', '.join(reqs)})"
1080
1081    def _unroll_exceptions(
1082        self,
1083        exceptions: Sequence[BaseException],
1084    ) -> Sequence[BaseException]:
1085        """Used if `flatten_subgroups=True`."""
1086        res: list[BaseException] = []
1087        for exc in exceptions:
1088            if isinstance(exc, BaseExceptionGroup):
1089                res.extend(self._unroll_exceptions(exc.exceptions))
1090
1091            else:
1092                res.append(exc)
1093        return res
1094
1095    @overload
1096    def matches(
1097        self: RaisesGroup[ExcT_1],
1098        exception: BaseException | None,
1099    ) -> TypeGuard[ExceptionGroup[ExcT_1]]: ...
1100    @overload
1101    def matches(
1102        self: RaisesGroup[BaseExcT_1],
1103        exception: BaseException | None,
1104    ) -> TypeGuard[BaseExceptionGroup[BaseExcT_1]]: ...
1105
1106    def matches(
1107        self,
1108        exception: BaseException | None,
1109    ) -> bool:
1110        """Check if an exception matches the requirements of this RaisesGroup.
1111        If it fails, `RaisesGroup.fail_reason` will be set.
1112
1113        Example::
1114
1115            with pytest.raises(TypeError) as excinfo:
1116                ...
1117            assert RaisesGroup(ValueError).matches(excinfo.value.__cause__)
1118            # the above line is equivalent to
1119            myexc = excinfo.value.__cause
1120            assert isinstance(myexc, BaseExceptionGroup)
1121            assert len(myexc.exceptions) == 1
1122            assert isinstance(myexc.exceptions[0], ValueError)
1123        """
1124        self._fail_reason = None
1125        if exception is None:
1126            self._fail_reason = "exception is None"
1127            return False
1128        if not isinstance(exception, BaseExceptionGroup):
1129            # we opt to only print type of the exception here, as the repr would
1130            # likely be quite long
1131            not_group_msg = f"`{type(exception).__name__}()` is not an exception group"
1132            if len(self.expected_exceptions) > 1:
1133                self._fail_reason = not_group_msg
1134                return False
1135            # if we have 1 expected exception, check if it would work even if
1136            # allow_unwrapped is not set
1137            res = self._check_expected(self.expected_exceptions[0], exception)
1138            if res is None and self.allow_unwrapped:
1139                return True
1140
1141            if res is None:
1142                self._fail_reason = (
1143                    f"{not_group_msg}, but would match with `allow_unwrapped=True`"
1144                )
1145            elif self.allow_unwrapped:
1146                self._fail_reason = res
1147            else:
1148                self._fail_reason = not_group_msg
1149            return False
1150
1151        actual_exceptions: Sequence[BaseException] = exception.exceptions
1152        if self.flatten_subgroups:
1153            actual_exceptions = self._unroll_exceptions(actual_exceptions)
1154
1155        if not self._check_match(exception):
1156            self._fail_reason = cast(str, self._fail_reason)
1157            old_reason = self._fail_reason
1158            if (
1159                len(actual_exceptions) == len(self.expected_exceptions) == 1
1160                and isinstance(expected := self.expected_exceptions[0], type)
1161                and isinstance(actual := actual_exceptions[0], expected)
1162                and self._check_match(actual)
1163            ):
1164                assert self.match is not None, "can't be None if _check_match failed"
1165                assert self._fail_reason is old_reason is not None
1166                self._fail_reason += (
1167                    f"\n"
1168                    f" but matched the expected `{self._repr_expected(expected)}`.\n"
1169                    f" You might want "
1170                    f"`RaisesGroup(RaisesExc({expected.__name__}, match={_match_pattern(self.match)!r}))`"
1171                )
1172            else:
1173                self._fail_reason = old_reason
1174            return False
1175
1176        # do the full check on expected exceptions
1177        if not self._check_exceptions(
1178            exception,
1179            actual_exceptions,
1180        ):
1181            self._fail_reason = cast(str, self._fail_reason)
1182            assert self._fail_reason is not None
1183            old_reason = self._fail_reason
1184            # if we're not expecting a nested structure, and there is one, do a second
1185            # pass where we try flattening it
1186            if (
1187                not self.flatten_subgroups
1188                and not any(
1189                    isinstance(e, RaisesGroup) for e in self.expected_exceptions
1190                )
1191                and any(isinstance(e, BaseExceptionGroup) for e in actual_exceptions)
1192                and self._check_exceptions(
1193                    exception,
1194                    self._unroll_exceptions(exception.exceptions),
1195                )
1196            ):
1197                # only indent if it's a single-line reason. In a multi-line there's already
1198                # indented lines that this does not belong to.
1199                indent = "  " if "\n" not in self._fail_reason else ""
1200                self._fail_reason = (
1201                    old_reason
1202                    + f"\n{indent}Did you mean to use `flatten_subgroups=True`?"
1203                )
1204            else:
1205                self._fail_reason = old_reason
1206            return False
1207
1208        # Only run `self.check` once we know `exception` is of the correct type.
1209        if not self._check_check(exception):
1210            reason = (
1211                cast(str, self._fail_reason) + f" on the {type(exception).__name__}"
1212            )
1213            if (
1214                len(actual_exceptions) == len(self.expected_exceptions) == 1
1215                and isinstance(expected := self.expected_exceptions[0], type)
1216                # we explicitly break typing here :)
1217                and self._check_check(actual_exceptions[0])  # type: ignore[arg-type]
1218            ):
1219                self._fail_reason = reason + (
1220                    f", but did return True for the expected {self._repr_expected(expected)}."
1221                    f" You might want RaisesGroup(RaisesExc({expected.__name__}, check=<...>))"
1222                )
1223            else:
1224                self._fail_reason = reason
1225            return False
1226
1227        return True
1228
1229    @staticmethod
1230    def _check_expected(
1231        expected_type: (
1232            type[BaseException] | RaisesExc[BaseException] | RaisesGroup[BaseException]
1233        ),
1234        exception: BaseException,
1235    ) -> str | None:
1236        """Helper method for `RaisesGroup.matches` and `RaisesGroup._check_exceptions`
1237        to check one of potentially several expected exceptions."""
1238        if isinstance(expected_type, type):
1239            return _check_raw_type(expected_type, exception)
1240        res = expected_type.matches(exception)
1241        if res:
1242            return None
1243        assert expected_type.fail_reason is not None
1244        if expected_type.fail_reason.startswith("\n"):
1245            return f"\n{expected_type!r}: {indent(expected_type.fail_reason, '  ')}"
1246        return f"{expected_type!r}: {expected_type.fail_reason}"
1247
1248    @staticmethod
1249    def _repr_expected(e: type[BaseException] | AbstractRaises[BaseException]) -> str:
1250        """Get the repr of an expected type/RaisesExc/RaisesGroup, but we only want
1251        the name if it's a type"""
1252        if isinstance(e, type):
1253            return _exception_type_name(e)
1254        return repr(e)
1255
1256    @overload
1257    def _check_exceptions(
1258        self: RaisesGroup[ExcT_1],
1259        _exception: Exception,
1260        actual_exceptions: Sequence[Exception],
1261    ) -> TypeGuard[ExceptionGroup[ExcT_1]]: ...
1262    @overload
1263    def _check_exceptions(
1264        self: RaisesGroup[BaseExcT_1],
1265        _exception: BaseException,
1266        actual_exceptions: Sequence[BaseException],
1267    ) -> TypeGuard[BaseExceptionGroup[BaseExcT_1]]: ...
1268
1269    def _check_exceptions(
1270        self,
1271        _exception: BaseException,
1272        actual_exceptions: Sequence[BaseException],
1273    ) -> bool:
1274        """Helper method for RaisesGroup.matches that attempts to pair up expected and actual exceptions"""
1275        # The _exception parameter is not used, but necessary for the TypeGuard
1276
1277        # full table with all results
1278        results = ResultHolder(self.expected_exceptions, actual_exceptions)
1279
1280        # (indexes of) raised exceptions that haven't (yet) found an expected
1281        remaining_actual = list(range(len(actual_exceptions)))
1282        # (indexes of) expected exceptions that haven't found a matching raised
1283        failed_expected: list[int] = []
1284        # successful greedy matches
1285        matches: dict[int, int] = {}
1286
1287        # loop over expected exceptions first to get a more predictable result
1288        for i_exp, expected in enumerate(self.expected_exceptions):
1289            for i_rem in remaining_actual:
1290                res = self._check_expected(expected, actual_exceptions[i_rem])
1291                results.set_result(i_exp, i_rem, res)
1292                if res is None:
1293                    remaining_actual.remove(i_rem)
1294                    matches[i_exp] = i_rem
1295                    break
1296            else:
1297                failed_expected.append(i_exp)
1298
1299        # All exceptions matched up successfully
1300        if not remaining_actual and not failed_expected:
1301            return True
1302
1303        # in case of a single expected and single raised we simplify the output
1304        if 1 == len(actual_exceptions) == len(self.expected_exceptions):
1305            assert not matches
1306            self._fail_reason = res
1307            return False
1308
1309        # The test case is failing, so we can do a slow and exhaustive check to find
1310        # duplicate matches etc that will be helpful in debugging
1311        for i_exp, expected in enumerate(self.expected_exceptions):
1312            for i_actual, actual in enumerate(actual_exceptions):
1313                if results.has_result(i_exp, i_actual):
1314                    continue
1315                results.set_result(
1316                    i_exp, i_actual, self._check_expected(expected, actual)
1317                )
1318
1319        successful_str = (
1320            f"{len(matches)} matched exception{'s' if len(matches) > 1 else ''}. "
1321            if matches
1322            else ""
1323        )
1324
1325        # all expected were found
1326        if not failed_expected and results.no_match_for_actual(remaining_actual):
1327            self._fail_reason = (
1328                f"{successful_str}Unexpected exception(s):"
1329                f" {[actual_exceptions[i] for i in remaining_actual]!r}"
1330            )
1331            return False
1332        # all raised exceptions were expected
1333        if not remaining_actual and results.no_match_for_expected(failed_expected):
1334            no_match_for_str = ", ".join(
1335                self._repr_expected(self.expected_exceptions[i])
1336                for i in failed_expected
1337            )
1338            self._fail_reason = f"{successful_str}Too few exceptions raised, found no match for: [{no_match_for_str}]"
1339            return False
1340
1341        # if there's only one remaining and one failed, and the unmatched didn't match anything else,
1342        # we elect to only print why the remaining and the failed didn't match.
1343        if (
1344            1 == len(remaining_actual) == len(failed_expected)
1345            and results.no_match_for_actual(remaining_actual)
1346            and results.no_match_for_expected(failed_expected)
1347        ):
1348            self._fail_reason = f"{successful_str}{results.get_result(failed_expected[0], remaining_actual[0])}"
1349            return False
1350
1351        # there's both expected and raised exceptions without matches
1352        s = ""
1353        if matches:
1354            s += f"\n{successful_str}"
1355        indent_1 = " " * 2
1356        indent_2 = " " * 4
1357
1358        if not remaining_actual:
1359            s += "\nToo few exceptions raised!"
1360        elif not failed_expected:
1361            s += "\nUnexpected exception(s)!"
1362
1363        if failed_expected:
1364            s += "\nThe following expected exceptions did not find a match:"
1365            rev_matches = {v: k for k, v in matches.items()}
1366        for i_failed in failed_expected:
1367            s += (
1368                f"\n{indent_1}{self._repr_expected(self.expected_exceptions[i_failed])}"
1369            )
1370            for i_actual, actual in enumerate(actual_exceptions):
1371                if results.get_result(i_exp, i_actual) is None:
1372                    # we print full repr of match target
1373                    s += (
1374                        f"\n{indent_2}It matches {backquote(repr(actual))} which was paired with "
1375                        + backquote(
1376                            self._repr_expected(
1377                                self.expected_exceptions[rev_matches[i_actual]]
1378                            )
1379                        )
1380                    )
1381
1382        if remaining_actual:
1383            s += "\nThe following raised exceptions did not find a match"
1384        for i_actual in remaining_actual:
1385            s += f"\n{indent_1}{actual_exceptions[i_actual]!r}:"
1386            for i_exp, expected in enumerate(self.expected_exceptions):
1387                res = results.get_result(i_exp, i_actual)
1388                if i_exp in failed_expected:
1389                    assert res is not None
1390                    if res[0] != "\n":
1391                        s += "\n"
1392                    s += indent(res, indent_2)
1393                if res is None:
1394                    # we print full repr of match target
1395                    s += (
1396                        f"\n{indent_2}It matches {backquote(self._repr_expected(expected))} "
1397                        f"which was paired with {backquote(repr(actual_exceptions[matches[i_exp]]))}"
1398                    )
1399
1400        if len(self.expected_exceptions) == len(actual_exceptions) and possible_match(
1401            results
1402        ):
1403            s += (
1404                "\nThere exist a possible match when attempting an exhaustive check,"
1405                " but RaisesGroup uses a greedy algorithm. "
1406                "Please make your expected exceptions more stringent with `RaisesExc` etc"
1407                " so the greedy algorithm can function."
1408            )
1409        self._fail_reason = s
1410        return False
1411
1412    def __exit__(
1413        self,
1414        exc_type: type[BaseException] | None,
1415        exc_val: BaseException | None,
1416        exc_tb: types.TracebackType | None,
1417    ) -> bool:
1418        __tracebackhide__ = True
1419        if exc_type is None:
1420            fail(f"DID NOT RAISE any exception, expected `{self.expected_type()}`")
1421
1422        assert self.excinfo is not None, (
1423            "Internal error - should have been constructed in __enter__"
1424        )
1425
1426        # group_str is the only thing that differs between RaisesExc and RaisesGroup...
1427        # I might just scrap it? Or make it part of fail_reason
1428        group_str = (
1429            "(group)"
1430            if self.allow_unwrapped and not issubclass(exc_type, BaseExceptionGroup)
1431            else "group"
1432        )
1433
1434        if not self.matches(exc_val):
1435            fail(f"Raised exception {group_str} did not match: {self._fail_reason}")
1436
1437        # Cast to narrow the exception type now that it's verified....
1438        # even though the TypeGuard in self.matches should be narrowing
1439        exc_info = cast(
1440            "tuple[type[BaseExceptionGroup[BaseExcT_co]], BaseExceptionGroup[BaseExcT_co], types.TracebackType]",
1441            (exc_type, exc_val, exc_tb),
1442        )
1443        self.excinfo.fill_unfilled(exc_info)
1444        return True
1445
1446    def expected_type(self) -> str:
1447        subexcs = []
1448        for e in self.expected_exceptions:
1449            if isinstance(e, RaisesExc):
1450                subexcs.append(repr(e))
1451            elif isinstance(e, RaisesGroup):
1452                subexcs.append(e.expected_type())
1453            elif isinstance(e, type):
1454                subexcs.append(e.__name__)
1455            else:  # pragma: no cover
1456                raise AssertionError("unknown type")
1457        group_type = "Base" if self.is_baseexception else ""
1458        return f"{group_type}ExceptionGroup({', '.join(subexcs)})"

New in version 8.4.

Contextmanager for checking for an expected ExceptionGroup. This works similar to pytest.raises(), but allows for specifying the structure of an ExceptionGroup. ExceptionInfo.group_contains() also tries to handle exception groups, but it is very bad at checking that you didn't get unexpected exceptions.

The catching behaviour differs from :ref:except* <except_star>, being much stricter about the structure by default. By using allow_unwrapped=True and flatten_subgroups=True you can match :ref:except* <except_star> fully when expecting a single exception.

Parameters
  • args: Any number of exception types, RaisesGroup or RaisesExc to specify the exceptions contained in this exception. All specified exceptions must be present in the raised group, and no others.

    If you expect a variable number of exceptions you need to use pytest.raises(ExceptionGroup) <pytest.raises>() and manually check the contained exceptions. Consider making use of RaisesExc.matches().

    It does not care about the order of the exceptions, so RaisesGroup(ValueError, TypeError) is equivalent to RaisesGroup(TypeError, ValueError). :kwparam str | re.Pattern[str] | None match: If specified, a string containing a regular expression, or a regular expression object, that is tested against the string representation of the exception group and its :pep:678 __notes__ using re.search().

    To match a literal string that may contain :ref:special characters <re-syntax>, the pattern can first be escaped with re.escape().

    Note that " (5 subgroups)" will be stripped from the repr before matching. :kwparam Callable[[E], bool] check: If specified, a callable that will be called with the group as a parameter after successfully matching the expected exceptions. If it returns True it will be considered a match, if not it will be considered a failed match. :kwparam bool allow_unwrapped: If expecting a single exception or RaisesExc it will match even if the exception is not inside an exceptiongroup.

    Using this together with match, check or expecting multiple exceptions will raise an error. :kwparam bool flatten_subgroups: "flatten" any groups inside the raised exception group, extracting all exceptions inside any nested groups, before matching. Without this it expects you to fully specify the nesting structure by passing RaisesGroup as expected parameter.

Examples::

with RaisesGroup(ValueError):
    raise ExceptionGroup("", (ValueError(),))
# match
with RaisesGroup(
    ValueError,
    ValueError,
    RaisesExc(TypeError, match="^expected int$"),
    match="^my group$",
):
    raise ExceptionGroup(
        "my group",
        [
            ValueError(),
            TypeError("expected int"),
            ValueError(),
        ],
    )
# check
with RaisesGroup(
    KeyboardInterrupt,
    match="^hello$",
    check=lambda x: isinstance(x.__cause__, ValueError),
):
    raise BaseExceptionGroup("hello", [KeyboardInterrupt()]) from ValueError
# nested groups
with RaisesGroup(RaisesGroup(ValueError)):
    raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),))

# flatten_subgroups
with RaisesGroup(ValueError, flatten_subgroups=True):
    raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),))

# allow_unwrapped
with RaisesGroup(ValueError, allow_unwrapped=True):
    raise ValueError

RaisesGroup.matches() can also be used directly to check a standalone exception group.

The matching algorithm is greedy, which means cases such as this may fail::

with RaisesGroup(ValueError, RaisesExc(ValueError, match="hello")):
    raise ExceptionGroup("", (ValueError("hello"), ValueError("goodbye")))

even though it generally does not care about the order of the exceptions in the group. To avoid the above you should specify the first ValueError with a RaisesExc as well.

When raised exceptions don't match the expected ones, you'll get a detailed error message explaining why. This includes repr(check) if set, which in Python can be overly verbose, showing memory locations etc etc.

If installed and imported (in e.g. conftest.py), the hypothesis library will monkeypatch this output to provide shorter & more readable repr's.

RaisesGroup( expected_exception: Union[type[~BaseExcT_1], _pytest.raises.RaisesExc[~BaseExcT_1], _pytest.raises.RaisesGroup[~BaseExcT_2]], /, *other_exceptions: Union[type[~BaseExcT_1], _pytest.raises.RaisesExc[~BaseExcT_1], _pytest.raises.RaisesGroup[~BaseExcT_2]], allow_unwrapped: bool = False, flatten_subgroups: bool = False, match: str | re.Pattern[str] | None = None, check: Callable[[BaseExceptionGroup[~BaseExcT_1]], bool] | Callable[[ExceptionGroup[~ExcT_1]], bool] | None = None)
 951    def __init__(
 952        self: RaisesGroup[ExcT_1 | BaseExcT_1 | BaseExceptionGroup[BaseExcT_2]],
 953        expected_exception: type[BaseExcT_1]
 954        | RaisesExc[BaseExcT_1]
 955        | RaisesGroup[BaseExcT_2],
 956        /,
 957        *other_exceptions: type[BaseExcT_1]
 958        | RaisesExc[BaseExcT_1]
 959        | RaisesGroup[BaseExcT_2],
 960        allow_unwrapped: bool = False,
 961        flatten_subgroups: bool = False,
 962        match: str | Pattern[str] | None = None,
 963        check: (
 964            Callable[[BaseExceptionGroup[BaseExcT_1]], bool]
 965            | Callable[[ExceptionGroup[ExcT_1]], bool]
 966            | None
 967        ) = None,
 968    ):
 969        # The type hint on the `self` and `check` parameters uses different formats
 970        # that are *very* hard to reconcile while adhering to the overloads, so we cast
 971        # it to avoid an error when passing it to super().__init__
 972        check = cast(
 973            "Callable[[BaseExceptionGroup[ExcT_1|BaseExcT_1|BaseExceptionGroup[BaseExcT_2]]], bool]",
 974            check,
 975        )
 976        super().__init__(match=match, check=check)
 977        self.allow_unwrapped = allow_unwrapped
 978        self.flatten_subgroups: bool = flatten_subgroups
 979        self.is_baseexception = False
 980
 981        if allow_unwrapped and other_exceptions:
 982            raise ValueError(
 983                "You cannot specify multiple exceptions with `allow_unwrapped=True.`"
 984                " If you want to match one of multiple possible exceptions you should"
 985                " use a `RaisesExc`."
 986                " E.g. `RaisesExc(check=lambda e: isinstance(e, (...)))`",
 987            )
 988        if allow_unwrapped and isinstance(expected_exception, RaisesGroup):
 989            raise ValueError(
 990                "`allow_unwrapped=True` has no effect when expecting a `RaisesGroup`."
 991                " You might want it in the expected `RaisesGroup`, or"
 992                " `flatten_subgroups=True` if you don't care about the structure.",
 993            )
 994        if allow_unwrapped and (match is not None or check is not None):
 995            raise ValueError(
 996                "`allow_unwrapped=True` bypasses the `match` and `check` parameters"
 997                " if the exception is unwrapped. If you intended to match/check the"
 998                " exception you should use a `RaisesExc` object. If you want to match/check"
 999                " the exceptiongroup when the exception *is* wrapped you need to"
1000                " do e.g. `if isinstance(exc.value, ExceptionGroup):"
1001                " assert RaisesGroup(...).matches(exc.value)` afterwards.",
1002            )
1003
1004        self.expected_exceptions: tuple[
1005            type[BaseExcT_co] | RaisesExc[BaseExcT_co] | RaisesGroup[BaseException], ...
1006        ] = tuple(
1007            self._parse_excgroup(e, "a BaseException type, RaisesExc, or RaisesGroup")
1008            for e in (
1009                expected_exception,
1010                *other_exceptions,
1011            )
1012        )
allow_unwrapped
flatten_subgroups: bool
is_baseexception
expected_exceptions: tuple[typing.Union[type[+BaseExcT_co], _pytest.raises.RaisesExc[+BaseExcT_co], _pytest.raises.RaisesGroup[BaseException]], ...]
def matches(self, exception: BaseException | None) -> bool:
1106    def matches(
1107        self,
1108        exception: BaseException | None,
1109    ) -> bool:
1110        """Check if an exception matches the requirements of this RaisesGroup.
1111        If it fails, `RaisesGroup.fail_reason` will be set.
1112
1113        Example::
1114
1115            with pytest.raises(TypeError) as excinfo:
1116                ...
1117            assert RaisesGroup(ValueError).matches(excinfo.value.__cause__)
1118            # the above line is equivalent to
1119            myexc = excinfo.value.__cause
1120            assert isinstance(myexc, BaseExceptionGroup)
1121            assert len(myexc.exceptions) == 1
1122            assert isinstance(myexc.exceptions[0], ValueError)
1123        """
1124        self._fail_reason = None
1125        if exception is None:
1126            self._fail_reason = "exception is None"
1127            return False
1128        if not isinstance(exception, BaseExceptionGroup):
1129            # we opt to only print type of the exception here, as the repr would
1130            # likely be quite long
1131            not_group_msg = f"`{type(exception).__name__}()` is not an exception group"
1132            if len(self.expected_exceptions) > 1:
1133                self._fail_reason = not_group_msg
1134                return False
1135            # if we have 1 expected exception, check if it would work even if
1136            # allow_unwrapped is not set
1137            res = self._check_expected(self.expected_exceptions[0], exception)
1138            if res is None and self.allow_unwrapped:
1139                return True
1140
1141            if res is None:
1142                self._fail_reason = (
1143                    f"{not_group_msg}, but would match with `allow_unwrapped=True`"
1144                )
1145            elif self.allow_unwrapped:
1146                self._fail_reason = res
1147            else:
1148                self._fail_reason = not_group_msg
1149            return False
1150
1151        actual_exceptions: Sequence[BaseException] = exception.exceptions
1152        if self.flatten_subgroups:
1153            actual_exceptions = self._unroll_exceptions(actual_exceptions)
1154
1155        if not self._check_match(exception):
1156            self._fail_reason = cast(str, self._fail_reason)
1157            old_reason = self._fail_reason
1158            if (
1159                len(actual_exceptions) == len(self.expected_exceptions) == 1
1160                and isinstance(expected := self.expected_exceptions[0], type)
1161                and isinstance(actual := actual_exceptions[0], expected)
1162                and self._check_match(actual)
1163            ):
1164                assert self.match is not None, "can't be None if _check_match failed"
1165                assert self._fail_reason is old_reason is not None
1166                self._fail_reason += (
1167                    f"\n"
1168                    f" but matched the expected `{self._repr_expected(expected)}`.\n"
1169                    f" You might want "
1170                    f"`RaisesGroup(RaisesExc({expected.__name__}, match={_match_pattern(self.match)!r}))`"
1171                )
1172            else:
1173                self._fail_reason = old_reason
1174            return False
1175
1176        # do the full check on expected exceptions
1177        if not self._check_exceptions(
1178            exception,
1179            actual_exceptions,
1180        ):
1181            self._fail_reason = cast(str, self._fail_reason)
1182            assert self._fail_reason is not None
1183            old_reason = self._fail_reason
1184            # if we're not expecting a nested structure, and there is one, do a second
1185            # pass where we try flattening it
1186            if (
1187                not self.flatten_subgroups
1188                and not any(
1189                    isinstance(e, RaisesGroup) for e in self.expected_exceptions
1190                )
1191                and any(isinstance(e, BaseExceptionGroup) for e in actual_exceptions)
1192                and self._check_exceptions(
1193                    exception,
1194                    self._unroll_exceptions(exception.exceptions),
1195                )
1196            ):
1197                # only indent if it's a single-line reason. In a multi-line there's already
1198                # indented lines that this does not belong to.
1199                indent = "  " if "\n" not in self._fail_reason else ""
1200                self._fail_reason = (
1201                    old_reason
1202                    + f"\n{indent}Did you mean to use `flatten_subgroups=True`?"
1203                )
1204            else:
1205                self._fail_reason = old_reason
1206            return False
1207
1208        # Only run `self.check` once we know `exception` is of the correct type.
1209        if not self._check_check(exception):
1210            reason = (
1211                cast(str, self._fail_reason) + f" on the {type(exception).__name__}"
1212            )
1213            if (
1214                len(actual_exceptions) == len(self.expected_exceptions) == 1
1215                and isinstance(expected := self.expected_exceptions[0], type)
1216                # we explicitly break typing here :)
1217                and self._check_check(actual_exceptions[0])  # type: ignore[arg-type]
1218            ):
1219                self._fail_reason = reason + (
1220                    f", but did return True for the expected {self._repr_expected(expected)}."
1221                    f" You might want RaisesGroup(RaisesExc({expected.__name__}, check=<...>))"
1222                )
1223            else:
1224                self._fail_reason = reason
1225            return False
1226
1227        return True

Check if an exception matches the requirements of this RaisesGroup. If it fails, RaisesGroup.fail_reason will be set.

Example::

with pytest.raises(TypeError) as excinfo:
    ...
assert RaisesGroup(ValueError).matches(excinfo.value.__cause__)
# the above line is equivalent to
myexc = excinfo.value.__cause
assert isinstance(myexc, BaseExceptionGroup)
assert len(myexc.exceptions) == 1
assert isinstance(myexc.exceptions[0], ValueError)
def expected_type(self) -> str:
1446    def expected_type(self) -> str:
1447        subexcs = []
1448        for e in self.expected_exceptions:
1449            if isinstance(e, RaisesExc):
1450                subexcs.append(repr(e))
1451            elif isinstance(e, RaisesGroup):
1452                subexcs.append(e.expected_type())
1453            elif isinstance(e, type):
1454                subexcs.append(e.__name__)
1455            else:  # pragma: no cover
1456                raise AssertionError("unknown type")
1457        group_type = "Base" if self.is_baseexception else ""
1458        return f"{group_type}ExceptionGroup({', '.join(subexcs)})"
@final
class RecordedHookCall:
221@final
222class RecordedHookCall:
223    """A recorded call to a hook.
224
225    The arguments to the hook call are set as attributes.
226    For example:
227
228    .. code-block:: python
229
230        calls = hook_recorder.getcalls("pytest_runtest_setup")
231        # Suppose pytest_runtest_setup was called once with `item=an_item`.
232        assert calls[0].item is an_item
233    """
234
235    def __init__(self, name: str, kwargs) -> None:
236        self.__dict__.update(kwargs)
237        self._name = name
238
239    def __repr__(self) -> str:
240        d = self.__dict__.copy()
241        del d["_name"]
242        return f"<RecordedHookCall {self._name!r}(**{d!r})>"
243
244    if TYPE_CHECKING:
245        # The class has undetermined attributes, this tells mypy about it.
246        def __getattr__(self, key: str): ...

A recorded call to a hook.

The arguments to the hook call are set as attributes. For example:

calls = hook_recorder.getcalls("pytest_runtest_setup")
# Suppose pytest_runtest_setup was called once with `item=an_item`.
assert calls[0].item is an_item
RecordedHookCall(name: str, kwargs)
235    def __init__(self, name: str, kwargs) -> None:
236        self.__dict__.update(kwargs)
237        self._name = name
@final
class RunResult:
518@final
519class RunResult:
520    """The result of running a command from :class:`~pytest.Pytester`."""
521
522    def __init__(
523        self,
524        ret: int | ExitCode,
525        outlines: list[str],
526        errlines: list[str],
527        duration: float,
528    ) -> None:
529        try:
530            self.ret: int | ExitCode = ExitCode(ret)
531            """The return value."""
532        except ValueError:
533            self.ret = ret
534        self.outlines = outlines
535        """List of lines captured from stdout."""
536        self.errlines = errlines
537        """List of lines captured from stderr."""
538        self.stdout = LineMatcher(outlines)
539        """:class:`~pytest.LineMatcher` of stdout.
540
541        Use e.g. :func:`str(stdout) <pytest.LineMatcher.__str__()>` to reconstruct stdout, or the commonly used
542        :func:`stdout.fnmatch_lines() <pytest.LineMatcher.fnmatch_lines()>` method.
543        """
544        self.stderr = LineMatcher(errlines)
545        """:class:`~pytest.LineMatcher` of stderr."""
546        self.duration = duration
547        """Duration in seconds."""
548
549    def __repr__(self) -> str:
550        return (
551            f"<RunResult ret={self.ret!s} "
552            f"len(stdout.lines)={len(self.stdout.lines)} "
553            f"len(stderr.lines)={len(self.stderr.lines)} "
554            f"duration={self.duration:.2f}s>"
555        )
556
557    def parseoutcomes(self) -> dict[str, int]:
558        """Return a dictionary of outcome noun -> count from parsing the terminal
559        output that the test process produced.
560
561        The returned nouns will always be in plural form::
562
563            ======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ====
564
565        Will return ``{"failed": 1, "passed": 1, "warnings": 1, "errors": 1}``.
566        """
567        return self.parse_summary_nouns(self.outlines)
568
569    @classmethod
570    def parse_summary_nouns(cls, lines) -> dict[str, int]:
571        """Extract the nouns from a pytest terminal summary line.
572
573        It always returns the plural noun for consistency::
574
575            ======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ====
576
577        Will return ``{"failed": 1, "passed": 1, "warnings": 1, "errors": 1}``.
578        """
579        for line in reversed(lines):
580            if rex_session_duration.search(line):
581                outcomes = rex_outcome.findall(line)
582                ret = {noun: int(count) for (count, noun) in outcomes}
583                break
584        else:
585            raise ValueError("Pytest terminal summary report not found")
586
587        to_plural = {
588            "warning": "warnings",
589            "error": "errors",
590        }
591        return {to_plural.get(k, k): v for k, v in ret.items()}
592
593    def assert_outcomes(
594        self,
595        passed: int = 0,
596        skipped: int = 0,
597        failed: int = 0,
598        errors: int = 0,
599        xpassed: int = 0,
600        xfailed: int = 0,
601        warnings: int | None = None,
602        deselected: int | None = None,
603    ) -> None:
604        """
605        Assert that the specified outcomes appear with the respective
606        numbers (0 means it didn't occur) in the text output from a test run.
607
608        ``warnings`` and ``deselected`` are only checked if not None.
609        """
610        __tracebackhide__ = True
611        from _pytest.pytester_assertions import assert_outcomes
612
613        outcomes = self.parseoutcomes()
614        assert_outcomes(
615            outcomes,
616            passed=passed,
617            skipped=skipped,
618            failed=failed,
619            errors=errors,
620            xpassed=xpassed,
621            xfailed=xfailed,
622            warnings=warnings,
623            deselected=deselected,
624        )

The result of running a command from ~pytest.Pytester.

RunResult( ret: int | _pytest.config.ExitCode, outlines: list[str], errlines: list[str], duration: float)
522    def __init__(
523        self,
524        ret: int | ExitCode,
525        outlines: list[str],
526        errlines: list[str],
527        duration: float,
528    ) -> None:
529        try:
530            self.ret: int | ExitCode = ExitCode(ret)
531            """The return value."""
532        except ValueError:
533            self.ret = ret
534        self.outlines = outlines
535        """List of lines captured from stdout."""
536        self.errlines = errlines
537        """List of lines captured from stderr."""
538        self.stdout = LineMatcher(outlines)
539        """:class:`~pytest.LineMatcher` of stdout.
540
541        Use e.g. :func:`str(stdout) <pytest.LineMatcher.__str__()>` to reconstruct stdout, or the commonly used
542        :func:`stdout.fnmatch_lines() <pytest.LineMatcher.fnmatch_lines()>` method.
543        """
544        self.stderr = LineMatcher(errlines)
545        """:class:`~pytest.LineMatcher` of stderr."""
546        self.duration = duration
547        """Duration in seconds."""
outlines

List of lines captured from stdout.

errlines

List of lines captured from stderr.

stdout

~pytest.LineMatcher of stdout.

Use e.g. str(stdout) <pytest.LineMatcher.__str__()>() to reconstruct stdout, or the commonly used stdout.fnmatch_lines() <pytest.LineMatcher.fnmatch_lines()>() method.

stderr

~pytest.LineMatcher of stderr.

duration

Duration in seconds.

def parseoutcomes(self) -> dict[str, int]:
557    def parseoutcomes(self) -> dict[str, int]:
558        """Return a dictionary of outcome noun -> count from parsing the terminal
559        output that the test process produced.
560
561        The returned nouns will always be in plural form::
562
563            ======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ====
564
565        Will return ``{"failed": 1, "passed": 1, "warnings": 1, "errors": 1}``.
566        """
567        return self.parse_summary_nouns(self.outlines)

Return a dictionary of outcome noun -> count from parsing the terminal output that the test process produced.

The returned nouns will always be in plural form::

======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ====

Will return {"failed": 1, "passed": 1, "warnings": 1, "errors": 1}.

@classmethod
def parse_summary_nouns(cls, lines) -> dict[str, int]:
569    @classmethod
570    def parse_summary_nouns(cls, lines) -> dict[str, int]:
571        """Extract the nouns from a pytest terminal summary line.
572
573        It always returns the plural noun for consistency::
574
575            ======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ====
576
577        Will return ``{"failed": 1, "passed": 1, "warnings": 1, "errors": 1}``.
578        """
579        for line in reversed(lines):
580            if rex_session_duration.search(line):
581                outcomes = rex_outcome.findall(line)
582                ret = {noun: int(count) for (count, noun) in outcomes}
583                break
584        else:
585            raise ValueError("Pytest terminal summary report not found")
586
587        to_plural = {
588            "warning": "warnings",
589            "error": "errors",
590        }
591        return {to_plural.get(k, k): v for k, v in ret.items()}

Extract the nouns from a pytest terminal summary line.

It always returns the plural noun for consistency::

======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ====

Will return {"failed": 1, "passed": 1, "warnings": 1, "errors": 1}.

def assert_outcomes( self, passed: int = 0, skipped: int = 0, failed: int = 0, errors: int = 0, xpassed: int = 0, xfailed: int = 0, warnings: int | None = None, deselected: int | None = None) -> None:
593    def assert_outcomes(
594        self,
595        passed: int = 0,
596        skipped: int = 0,
597        failed: int = 0,
598        errors: int = 0,
599        xpassed: int = 0,
600        xfailed: int = 0,
601        warnings: int | None = None,
602        deselected: int | None = None,
603    ) -> None:
604        """
605        Assert that the specified outcomes appear with the respective
606        numbers (0 means it didn't occur) in the text output from a test run.
607
608        ``warnings`` and ``deselected`` are only checked if not None.
609        """
610        __tracebackhide__ = True
611        from _pytest.pytester_assertions import assert_outcomes
612
613        outcomes = self.parseoutcomes()
614        assert_outcomes(
615            outcomes,
616            passed=passed,
617            skipped=skipped,
618            failed=failed,
619            errors=errors,
620            xpassed=xpassed,
621            xfailed=xfailed,
622            warnings=warnings,
623            deselected=deselected,
624        )

Assert that the specified outcomes appear with the respective numbers (0 means it didn't occur) in the text output from a test run.

warnings and deselected are only checked if not None.

@final
class Session(pytest.Collector):
548@final
549class Session(nodes.Collector):
550    """The root of the collection tree.
551
552    ``Session`` collects the initial paths given as arguments to pytest.
553    """
554
555    Interrupted = Interrupted
556    Failed = Failed
557    # Set on the session by runner.pytest_sessionstart.
558    _setupstate: SetupState
559    # Set on the session by fixtures.pytest_sessionstart.
560    _fixturemanager: FixtureManager
561    exitstatus: int | ExitCode
562
563    def __init__(self, config: Config) -> None:
564        super().__init__(
565            name="",
566            path=config.rootpath,
567            fspath=None,
568            parent=None,
569            config=config,
570            session=self,
571            nodeid="",
572        )
573        self.testsfailed = 0
574        self.testscollected = 0
575        self._shouldstop: bool | str = False
576        self._shouldfail: bool | str = False
577        self.trace = config.trace.root.get("collection")
578        self._initialpaths: frozenset[Path] = frozenset()
579        self._initialpaths_with_parents: frozenset[Path] = frozenset()
580        self._notfound: list[tuple[str, Sequence[nodes.Collector]]] = []
581        self._initial_parts: list[CollectionArgument] = []
582        self._collection_cache: dict[nodes.Collector, CollectReport] = {}
583        self.items: list[nodes.Item] = []
584
585        self._bestrelpathcache: dict[Path, str] = _bestrelpath_cache(config.rootpath)
586
587        self.config.pluginmanager.register(self, name="session")
588
589    @classmethod
590    def from_config(cls, config: Config) -> Session:
591        session: Session = cls._create(config=config)
592        return session
593
594    def __repr__(self) -> str:
595        return (
596            f"<{self.__class__.__name__} {self.name} "
597            f"exitstatus=%r "
598            f"testsfailed={self.testsfailed} "
599            f"testscollected={self.testscollected}>"
600        ) % getattr(self, "exitstatus", "<UNSET>")
601
602    @property
603    def shouldstop(self) -> bool | str:
604        return self._shouldstop
605
606    @shouldstop.setter
607    def shouldstop(self, value: bool | str) -> None:
608        # The runner checks shouldfail and assumes that if it is set we are
609        # definitely stopping, so prevent unsetting it.
610        if value is False and self._shouldstop:
611            warnings.warn(
612                PytestWarning(
613                    "session.shouldstop cannot be unset after it has been set; ignoring."
614                ),
615                stacklevel=2,
616            )
617            return
618        self._shouldstop = value
619
620    @property
621    def shouldfail(self) -> bool | str:
622        return self._shouldfail
623
624    @shouldfail.setter
625    def shouldfail(self, value: bool | str) -> None:
626        # The runner checks shouldfail and assumes that if it is set we are
627        # definitely stopping, so prevent unsetting it.
628        if value is False and self._shouldfail:
629            warnings.warn(
630                PytestWarning(
631                    "session.shouldfail cannot be unset after it has been set; ignoring."
632                ),
633                stacklevel=2,
634            )
635            return
636        self._shouldfail = value
637
638    @property
639    def startpath(self) -> Path:
640        """The path from which pytest was invoked.
641
642        .. versionadded:: 7.0.0
643        """
644        return self.config.invocation_params.dir
645
646    def _node_location_to_relpath(self, node_path: Path) -> str:
647        # bestrelpath is a quite slow function.
648        return self._bestrelpathcache[node_path]
649
650    @hookimpl(tryfirst=True)
651    def pytest_collectstart(self) -> None:
652        if self.shouldfail:
653            raise self.Failed(self.shouldfail)
654        if self.shouldstop:
655            raise self.Interrupted(self.shouldstop)
656
657    @hookimpl(tryfirst=True)
658    def pytest_runtest_logreport(self, report: TestReport | CollectReport) -> None:
659        if report.failed and not hasattr(report, "wasxfail"):
660            self.testsfailed += 1
661            maxfail = self.config.getvalue("maxfail")
662            if maxfail and self.testsfailed >= maxfail:
663                self.shouldfail = f"stopping after {self.testsfailed} failures"
664
665    pytest_collectreport = pytest_runtest_logreport
666
667    def isinitpath(
668        self,
669        path: str | os.PathLike[str],
670        *,
671        with_parents: bool = False,
672    ) -> bool:
673        """Is path an initial path?
674
675        An initial path is a path explicitly given to pytest on the command
676        line.
677
678        :param with_parents:
679            If set, also return True if the path is a parent of an initial path.
680
681        .. versionchanged:: 8.0
682            Added the ``with_parents`` parameter.
683        """
684        # Optimization: Path(Path(...)) is much slower than isinstance.
685        path_ = path if isinstance(path, Path) else Path(path)
686        if with_parents:
687            return path_ in self._initialpaths_with_parents
688        else:
689            return path_ in self._initialpaths
690
691    def gethookproxy(self, fspath: os.PathLike[str]) -> pluggy.HookRelay:
692        # Optimization: Path(Path(...)) is much slower than isinstance.
693        path = fspath if isinstance(fspath, Path) else Path(fspath)
694        pm = self.config.pluginmanager
695        # Check if we have the common case of running
696        # hooks with all conftest.py files.
697        my_conftestmodules = pm._getconftestmodules(path)
698        remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
699        proxy: pluggy.HookRelay
700        if remove_mods:
701            # One or more conftests are not in use at this path.
702            proxy = PathAwareHookProxy(FSHookProxy(pm, remove_mods))  # type: ignore[arg-type,assignment]
703        else:
704            # All plugins are active for this fspath.
705            proxy = self.config.hook
706        return proxy
707
708    def _collect_path(
709        self,
710        path: Path,
711        path_cache: dict[Path, Sequence[nodes.Collector]],
712    ) -> Sequence[nodes.Collector]:
713        """Create a Collector for the given path.
714
715        `path_cache` makes it so the same Collectors are returned for the same
716        path.
717        """
718        if path in path_cache:
719            return path_cache[path]
720
721        if path.is_dir():
722            ihook = self.gethookproxy(path.parent)
723            col: nodes.Collector | None = ihook.pytest_collect_directory(
724                path=path, parent=self
725            )
726            cols: Sequence[nodes.Collector] = (col,) if col is not None else ()
727
728        elif path.is_file():
729            ihook = self.gethookproxy(path)
730            cols = ihook.pytest_collect_file(file_path=path, parent=self)
731
732        else:
733            # Broken symlink or invalid/missing file.
734            cols = ()
735
736        path_cache[path] = cols
737        return cols
738
739    @overload
740    def perform_collect(
741        self, args: Sequence[str] | None = ..., genitems: Literal[True] = ...
742    ) -> Sequence[nodes.Item]: ...
743
744    @overload
745    def perform_collect(
746        self, args: Sequence[str] | None = ..., genitems: bool = ...
747    ) -> Sequence[nodes.Item | nodes.Collector]: ...
748
749    def perform_collect(
750        self, args: Sequence[str] | None = None, genitems: bool = True
751    ) -> Sequence[nodes.Item | nodes.Collector]:
752        """Perform the collection phase for this session.
753
754        This is called by the default :hook:`pytest_collection` hook
755        implementation; see the documentation of this hook for more details.
756        For testing purposes, it may also be called directly on a fresh
757        ``Session``.
758
759        This function normally recursively expands any collectors collected
760        from the session to their items, and only items are returned. For
761        testing purposes, this may be suppressed by passing ``genitems=False``,
762        in which case the return value contains these collectors unexpanded,
763        and ``session.items`` is empty.
764        """
765        if args is None:
766            args = self.config.args
767
768        self.trace("perform_collect", self, args)
769        self.trace.root.indent += 1
770
771        hook = self.config.hook
772
773        self._notfound = []
774        self._initial_parts = []
775        self._collection_cache = {}
776        self.items = []
777        items: Sequence[nodes.Item | nodes.Collector] = self.items
778        try:
779            initialpaths: list[Path] = []
780            initialpaths_with_parents: list[Path] = []
781            for arg in args:
782                collection_argument = resolve_collection_argument(
783                    self.config.invocation_params.dir,
784                    arg,
785                    as_pypath=self.config.option.pyargs,
786                )
787                self._initial_parts.append(collection_argument)
788                initialpaths.append(collection_argument.path)
789                initialpaths_with_parents.append(collection_argument.path)
790                initialpaths_with_parents.extend(collection_argument.path.parents)
791            self._initialpaths = frozenset(initialpaths)
792            self._initialpaths_with_parents = frozenset(initialpaths_with_parents)
793
794            rep = collect_one_node(self)
795            self.ihook.pytest_collectreport(report=rep)
796            self.trace.root.indent -= 1
797            if self._notfound:
798                errors = []
799                for arg, collectors in self._notfound:
800                    if collectors:
801                        errors.append(
802                            f"not found: {arg}\n(no match in any of {collectors!r})"
803                        )
804                    else:
805                        errors.append(f"found no collectors for {arg}")
806
807                raise UsageError(*errors)
808
809            if not genitems:
810                items = rep.result
811            else:
812                if rep.passed:
813                    for node in rep.result:
814                        self.items.extend(self.genitems(node))
815
816            self.config.pluginmanager.check_pending()
817            hook.pytest_collection_modifyitems(
818                session=self, config=self.config, items=items
819            )
820        finally:
821            self._notfound = []
822            self._initial_parts = []
823            self._collection_cache = {}
824            hook.pytest_collection_finish(session=self)
825
826        if genitems:
827            self.testscollected = len(items)
828
829        return items
830
831    def _collect_one_node(
832        self,
833        node: nodes.Collector,
834        handle_dupes: bool = True,
835    ) -> tuple[CollectReport, bool]:
836        if node in self._collection_cache and handle_dupes:
837            rep = self._collection_cache[node]
838            return rep, True
839        else:
840            rep = collect_one_node(node)
841            self._collection_cache[node] = rep
842            return rep, False
843
844    def collect(self) -> Iterator[nodes.Item | nodes.Collector]:
845        # This is a cache for the root directories of the initial paths.
846        # We can't use collection_cache for Session because of its special
847        # role as the bootstrapping collector.
848        path_cache: dict[Path, Sequence[nodes.Collector]] = {}
849
850        pm = self.config.pluginmanager
851
852        for collection_argument in self._initial_parts:
853            self.trace("processing argument", collection_argument)
854            self.trace.root.indent += 1
855
856            argpath = collection_argument.path
857            names = collection_argument.parts
858            module_name = collection_argument.module_name
859
860            # resolve_collection_argument() ensures this.
861            if argpath.is_dir():
862                assert not names, f"invalid arg {(argpath, names)!r}"
863
864            paths = [argpath]
865            # Add relevant parents of the path, from the root, e.g.
866            #   /a/b/c.py -> [/, /a, /a/b, /a/b/c.py]
867            if module_name is None:
868                # Paths outside of the confcutdir should not be considered.
869                for path in argpath.parents:
870                    if not pm._is_in_confcutdir(path):
871                        break
872                    paths.insert(0, path)
873            else:
874                # For --pyargs arguments, only consider paths matching the module
875                # name. Paths beyond the package hierarchy are not included.
876                module_name_parts = module_name.split(".")
877                for i, path in enumerate(argpath.parents, 2):
878                    if i > len(module_name_parts) or path.stem != module_name_parts[-i]:
879                        break
880                    paths.insert(0, path)
881
882            # Start going over the parts from the root, collecting each level
883            # and discarding all nodes which don't match the level's part.
884            any_matched_in_initial_part = False
885            notfound_collectors = []
886            work: list[tuple[nodes.Collector | nodes.Item, list[Path | str]]] = [
887                (self, [*paths, *names])
888            ]
889            while work:
890                matchnode, matchparts = work.pop()
891
892                # Pop'd all of the parts, this is a match.
893                if not matchparts:
894                    yield matchnode
895                    any_matched_in_initial_part = True
896                    continue
897
898                # Should have been matched by now, discard.
899                if not isinstance(matchnode, nodes.Collector):
900                    continue
901
902                # Collect this level of matching.
903                # Collecting Session (self) is done directly to avoid endless
904                # recursion to this function.
905                subnodes: Sequence[nodes.Collector | nodes.Item]
906                if isinstance(matchnode, Session):
907                    assert isinstance(matchparts[0], Path)
908                    subnodes = matchnode._collect_path(matchparts[0], path_cache)
909                else:
910                    # For backward compat, files given directly multiple
911                    # times on the command line should not be deduplicated.
912                    handle_dupes = not (
913                        len(matchparts) == 1
914                        and isinstance(matchparts[0], Path)
915                        and matchparts[0].is_file()
916                    )
917                    rep, duplicate = self._collect_one_node(matchnode, handle_dupes)
918                    if not duplicate and not rep.passed:
919                        # Report collection failures here to avoid failing to
920                        # run some test specified in the command line because
921                        # the module could not be imported (#134).
922                        matchnode.ihook.pytest_collectreport(report=rep)
923                    if not rep.passed:
924                        continue
925                    subnodes = rep.result
926
927                # Prune this level.
928                any_matched_in_collector = False
929                for node in reversed(subnodes):
930                    # Path part e.g. `/a/b/` in `/a/b/test_file.py::TestIt::test_it`.
931                    if isinstance(matchparts[0], Path):
932                        is_match = node.path == matchparts[0]
933                        if sys.platform == "win32" and not is_match:
934                            # In case the file paths do not match, fallback to samefile() to
935                            # account for short-paths on Windows (#11895).
936                            same_file = os.path.samefile(node.path, matchparts[0])
937                            # We don't want to match links to the current node,
938                            # otherwise we would match the same file more than once (#12039).
939                            is_match = same_file and (
940                                os.path.islink(node.path)
941                                == os.path.islink(matchparts[0])
942                            )
943
944                    # Name part e.g. `TestIt` in `/a/b/test_file.py::TestIt::test_it`.
945                    else:
946                        # TODO: Remove parametrized workaround once collection structure contains
947                        # parametrization.
948                        is_match = (
949                            node.name == matchparts[0]
950                            or node.name.split("[")[0] == matchparts[0]
951                        )
952                    if is_match:
953                        work.append((node, matchparts[1:]))
954                        any_matched_in_collector = True
955
956                if not any_matched_in_collector:
957                    notfound_collectors.append(matchnode)
958
959            if not any_matched_in_initial_part:
960                report_arg = "::".join((str(argpath), *names))
961                self._notfound.append((report_arg, notfound_collectors))
962
963            self.trace.root.indent -= 1
964
965    def genitems(self, node: nodes.Item | nodes.Collector) -> Iterator[nodes.Item]:
966        self.trace("genitems", node)
967        if isinstance(node, nodes.Item):
968            node.ihook.pytest_itemcollected(item=node)
969            yield node
970        else:
971            assert isinstance(node, nodes.Collector)
972            keepduplicates = self.config.getoption("keepduplicates")
973            # For backward compat, dedup only applies to files.
974            handle_dupes = not (keepduplicates and isinstance(node, nodes.File))
975            rep, duplicate = self._collect_one_node(node, handle_dupes)
976            if duplicate and not keepduplicates:
977                return
978            if rep.passed:
979                for subnode in rep.result:
980                    yield from self.genitems(subnode)
981            if not duplicate:
982                node.ihook.pytest_collectreport(report=rep)

The root of the collection tree.

Session collects the initial paths given as arguments to pytest.

Session(config: _pytest.config.Config)
563    def __init__(self, config: Config) -> None:
564        super().__init__(
565            name="",
566            path=config.rootpath,
567            fspath=None,
568            parent=None,
569            config=config,
570            session=self,
571            nodeid="",
572        )
573        self.testsfailed = 0
574        self.testscollected = 0
575        self._shouldstop: bool | str = False
576        self._shouldfail: bool | str = False
577        self.trace = config.trace.root.get("collection")
578        self._initialpaths: frozenset[Path] = frozenset()
579        self._initialpaths_with_parents: frozenset[Path] = frozenset()
580        self._notfound: list[tuple[str, Sequence[nodes.Collector]]] = []
581        self._initial_parts: list[CollectionArgument] = []
582        self._collection_cache: dict[nodes.Collector, CollectReport] = {}
583        self.items: list[nodes.Item] = []
584
585        self._bestrelpathcache: dict[Path, str] = _bestrelpath_cache(config.rootpath)
586
587        self.config.pluginmanager.register(self, name="session")
exitstatus: int | _pytest.config.ExitCode
testsfailed
testscollected
trace
items: list[_pytest.nodes.Item]
@classmethod
def from_config(cls, config: _pytest.config.Config) -> _pytest.main.Session:
589    @classmethod
590    def from_config(cls, config: Config) -> Session:
591        session: Session = cls._create(config=config)
592        return session
shouldstop: bool | str
602    @property
603    def shouldstop(self) -> bool | str:
604        return self._shouldstop
shouldfail: bool | str
620    @property
621    def shouldfail(self) -> bool | str:
622        return self._shouldfail
startpath: pathlib.Path
638    @property
639    def startpath(self) -> Path:
640        """The path from which pytest was invoked.
641
642        .. versionadded:: 7.0.0
643        """
644        return self.config.invocation_params.dir

The path from which pytest was invoked.

New in version 7.0.0.

@hookimpl(tryfirst=True)
def pytest_collectstart(self) -> None:
650    @hookimpl(tryfirst=True)
651    def pytest_collectstart(self) -> None:
652        if self.shouldfail:
653            raise self.Failed(self.shouldfail)
654        if self.shouldstop:
655            raise self.Interrupted(self.shouldstop)
@hookimpl(tryfirst=True)
def pytest_runtest_logreport( self, report: _pytest.reports.TestReport | _pytest.reports.CollectReport) -> None:
657    @hookimpl(tryfirst=True)
658    def pytest_runtest_logreport(self, report: TestReport | CollectReport) -> None:
659        if report.failed and not hasattr(report, "wasxfail"):
660            self.testsfailed += 1
661            maxfail = self.config.getvalue("maxfail")
662            if maxfail and self.testsfailed >= maxfail:
663                self.shouldfail = f"stopping after {self.testsfailed} failures"
@hookimpl(tryfirst=True)
def pytest_collectreport( self, report: _pytest.reports.TestReport | _pytest.reports.CollectReport) -> None:
657    @hookimpl(tryfirst=True)
658    def pytest_runtest_logreport(self, report: TestReport | CollectReport) -> None:
659        if report.failed and not hasattr(report, "wasxfail"):
660            self.testsfailed += 1
661            maxfail = self.config.getvalue("maxfail")
662            if maxfail and self.testsfailed >= maxfail:
663                self.shouldfail = f"stopping after {self.testsfailed} failures"
def isinitpath( self, path: str | os.PathLike[str], *, with_parents: bool = False) -> bool:
667    def isinitpath(
668        self,
669        path: str | os.PathLike[str],
670        *,
671        with_parents: bool = False,
672    ) -> bool:
673        """Is path an initial path?
674
675        An initial path is a path explicitly given to pytest on the command
676        line.
677
678        :param with_parents:
679            If set, also return True if the path is a parent of an initial path.
680
681        .. versionchanged:: 8.0
682            Added the ``with_parents`` parameter.
683        """
684        # Optimization: Path(Path(...)) is much slower than isinstance.
685        path_ = path if isinstance(path, Path) else Path(path)
686        if with_parents:
687            return path_ in self._initialpaths_with_parents
688        else:
689            return path_ in self._initialpaths

Is path an initial path?

An initial path is a path explicitly given to pytest on the command line.

Parameters
  • with_parents: If set, also return True if the path is a parent of an initial path.

Changed in version 8.0: Added the with_parents parameter.

def gethookproxy(self, fspath: os.PathLike[str]) -> pluggy._hooks.HookRelay:
691    def gethookproxy(self, fspath: os.PathLike[str]) -> pluggy.HookRelay:
692        # Optimization: Path(Path(...)) is much slower than isinstance.
693        path = fspath if isinstance(fspath, Path) else Path(fspath)
694        pm = self.config.pluginmanager
695        # Check if we have the common case of running
696        # hooks with all conftest.py files.
697        my_conftestmodules = pm._getconftestmodules(path)
698        remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
699        proxy: pluggy.HookRelay
700        if remove_mods:
701            # One or more conftests are not in use at this path.
702            proxy = PathAwareHookProxy(FSHookProxy(pm, remove_mods))  # type: ignore[arg-type,assignment]
703        else:
704            # All plugins are active for this fspath.
705            proxy = self.config.hook
706        return proxy
def perform_collect( self, args: Sequence[str] | None = None, genitems: bool = True) -> Sequence[_pytest.nodes.Item | _pytest.nodes.Collector]:
749    def perform_collect(
750        self, args: Sequence[str] | None = None, genitems: bool = True
751    ) -> Sequence[nodes.Item | nodes.Collector]:
752        """Perform the collection phase for this session.
753
754        This is called by the default :hook:`pytest_collection` hook
755        implementation; see the documentation of this hook for more details.
756        For testing purposes, it may also be called directly on a fresh
757        ``Session``.
758
759        This function normally recursively expands any collectors collected
760        from the session to their items, and only items are returned. For
761        testing purposes, this may be suppressed by passing ``genitems=False``,
762        in which case the return value contains these collectors unexpanded,
763        and ``session.items`` is empty.
764        """
765        if args is None:
766            args = self.config.args
767
768        self.trace("perform_collect", self, args)
769        self.trace.root.indent += 1
770
771        hook = self.config.hook
772
773        self._notfound = []
774        self._initial_parts = []
775        self._collection_cache = {}
776        self.items = []
777        items: Sequence[nodes.Item | nodes.Collector] = self.items
778        try:
779            initialpaths: list[Path] = []
780            initialpaths_with_parents: list[Path] = []
781            for arg in args:
782                collection_argument = resolve_collection_argument(
783                    self.config.invocation_params.dir,
784                    arg,
785                    as_pypath=self.config.option.pyargs,
786                )
787                self._initial_parts.append(collection_argument)
788                initialpaths.append(collection_argument.path)
789                initialpaths_with_parents.append(collection_argument.path)
790                initialpaths_with_parents.extend(collection_argument.path.parents)
791            self._initialpaths = frozenset(initialpaths)
792            self._initialpaths_with_parents = frozenset(initialpaths_with_parents)
793
794            rep = collect_one_node(self)
795            self.ihook.pytest_collectreport(report=rep)
796            self.trace.root.indent -= 1
797            if self._notfound:
798                errors = []
799                for arg, collectors in self._notfound:
800                    if collectors:
801                        errors.append(
802                            f"not found: {arg}\n(no match in any of {collectors!r})"
803                        )
804                    else:
805                        errors.append(f"found no collectors for {arg}")
806
807                raise UsageError(*errors)
808
809            if not genitems:
810                items = rep.result
811            else:
812                if rep.passed:
813                    for node in rep.result:
814                        self.items.extend(self.genitems(node))
815
816            self.config.pluginmanager.check_pending()
817            hook.pytest_collection_modifyitems(
818                session=self, config=self.config, items=items
819            )
820        finally:
821            self._notfound = []
822            self._initial_parts = []
823            self._collection_cache = {}
824            hook.pytest_collection_finish(session=self)
825
826        if genitems:
827            self.testscollected = len(items)
828
829        return items

Perform the collection phase for this session.

This is called by the default :hook:pytest_collection hook implementation; see the documentation of this hook for more details. For testing purposes, it may also be called directly on a fresh Session.

This function normally recursively expands any collectors collected from the session to their items, and only items are returned. For testing purposes, this may be suppressed by passing genitems=False, in which case the return value contains these collectors unexpanded, and session.items is empty.

def collect(self) -> Iterator[_pytest.nodes.Item | _pytest.nodes.Collector]:
844    def collect(self) -> Iterator[nodes.Item | nodes.Collector]:
845        # This is a cache for the root directories of the initial paths.
846        # We can't use collection_cache for Session because of its special
847        # role as the bootstrapping collector.
848        path_cache: dict[Path, Sequence[nodes.Collector]] = {}
849
850        pm = self.config.pluginmanager
851
852        for collection_argument in self._initial_parts:
853            self.trace("processing argument", collection_argument)
854            self.trace.root.indent += 1
855
856            argpath = collection_argument.path
857            names = collection_argument.parts
858            module_name = collection_argument.module_name
859
860            # resolve_collection_argument() ensures this.
861            if argpath.is_dir():
862                assert not names, f"invalid arg {(argpath, names)!r}"
863
864            paths = [argpath]
865            # Add relevant parents of the path, from the root, e.g.
866            #   /a/b/c.py -> [/, /a, /a/b, /a/b/c.py]
867            if module_name is None:
868                # Paths outside of the confcutdir should not be considered.
869                for path in argpath.parents:
870                    if not pm._is_in_confcutdir(path):
871                        break
872                    paths.insert(0, path)
873            else:
874                # For --pyargs arguments, only consider paths matching the module
875                # name. Paths beyond the package hierarchy are not included.
876                module_name_parts = module_name.split(".")
877                for i, path in enumerate(argpath.parents, 2):
878                    if i > len(module_name_parts) or path.stem != module_name_parts[-i]:
879                        break
880                    paths.insert(0, path)
881
882            # Start going over the parts from the root, collecting each level
883            # and discarding all nodes which don't match the level's part.
884            any_matched_in_initial_part = False
885            notfound_collectors = []
886            work: list[tuple[nodes.Collector | nodes.Item, list[Path | str]]] = [
887                (self, [*paths, *names])
888            ]
889            while work:
890                matchnode, matchparts = work.pop()
891
892                # Pop'd all of the parts, this is a match.
893                if not matchparts:
894                    yield matchnode
895                    any_matched_in_initial_part = True
896                    continue
897
898                # Should have been matched by now, discard.
899                if not isinstance(matchnode, nodes.Collector):
900                    continue
901
902                # Collect this level of matching.
903                # Collecting Session (self) is done directly to avoid endless
904                # recursion to this function.
905                subnodes: Sequence[nodes.Collector | nodes.Item]
906                if isinstance(matchnode, Session):
907                    assert isinstance(matchparts[0], Path)
908                    subnodes = matchnode._collect_path(matchparts[0], path_cache)
909                else:
910                    # For backward compat, files given directly multiple
911                    # times on the command line should not be deduplicated.
912                    handle_dupes = not (
913                        len(matchparts) == 1
914                        and isinstance(matchparts[0], Path)
915                        and matchparts[0].is_file()
916                    )
917                    rep, duplicate = self._collect_one_node(matchnode, handle_dupes)
918                    if not duplicate and not rep.passed:
919                        # Report collection failures here to avoid failing to
920                        # run some test specified in the command line because
921                        # the module could not be imported (#134).
922                        matchnode.ihook.pytest_collectreport(report=rep)
923                    if not rep.passed:
924                        continue
925                    subnodes = rep.result
926
927                # Prune this level.
928                any_matched_in_collector = False
929                for node in reversed(subnodes):
930                    # Path part e.g. `/a/b/` in `/a/b/test_file.py::TestIt::test_it`.
931                    if isinstance(matchparts[0], Path):
932                        is_match = node.path == matchparts[0]
933                        if sys.platform == "win32" and not is_match:
934                            # In case the file paths do not match, fallback to samefile() to
935                            # account for short-paths on Windows (#11895).
936                            same_file = os.path.samefile(node.path, matchparts[0])
937                            # We don't want to match links to the current node,
938                            # otherwise we would match the same file more than once (#12039).
939                            is_match = same_file and (
940                                os.path.islink(node.path)
941                                == os.path.islink(matchparts[0])
942                            )
943
944                    # Name part e.g. `TestIt` in `/a/b/test_file.py::TestIt::test_it`.
945                    else:
946                        # TODO: Remove parametrized workaround once collection structure contains
947                        # parametrization.
948                        is_match = (
949                            node.name == matchparts[0]
950                            or node.name.split("[")[0] == matchparts[0]
951                        )
952                    if is_match:
953                        work.append((node, matchparts[1:]))
954                        any_matched_in_collector = True
955
956                if not any_matched_in_collector:
957                    notfound_collectors.append(matchnode)
958
959            if not any_matched_in_initial_part:
960                report_arg = "::".join((str(argpath), *names))
961                self._notfound.append((report_arg, notfound_collectors))
962
963            self.trace.root.indent -= 1

Collect children (items and collectors) for this collector.

def genitems( self, node: _pytest.nodes.Item | _pytest.nodes.Collector) -> Iterator[_pytest.nodes.Item]:
965    def genitems(self, node: nodes.Item | nodes.Collector) -> Iterator[nodes.Item]:
966        self.trace("genitems", node)
967        if isinstance(node, nodes.Item):
968            node.ihook.pytest_itemcollected(item=node)
969            yield node
970        else:
971            assert isinstance(node, nodes.Collector)
972            keepduplicates = self.config.getoption("keepduplicates")
973            # For backward compat, dedup only applies to files.
974            handle_dupes = not (keepduplicates and isinstance(node, nodes.File))
975            rep, duplicate = self._collect_one_node(node, handle_dupes)
976            if duplicate and not keepduplicates:
977                return
978            if rep.passed:
979                for subnode in rep.result:
980                    yield from self.genitems(subnode)
981            if not duplicate:
982                node.ihook.pytest_collectreport(report=rep)
class Session.Interrupted(builtins.KeyboardInterrupt):

Signals that the test run was interrupted.

class Session.Failed(builtins.Exception):
480class Failed(Exception):
481    """Signals a stop as failed test run."""

Signals a stop as failed test run.

class Stash:
 30class Stash:
 31    r"""``Stash`` is a type-safe heterogeneous mutable mapping that
 32    allows keys and value types to be defined separately from
 33    where it (the ``Stash``) is created.
 34
 35    Usually you will be given an object which has a ``Stash``, for example
 36    :class:`~pytest.Config` or a :class:`~_pytest.nodes.Node`:
 37
 38    .. code-block:: python
 39
 40        stash: Stash = some_object.stash
 41
 42    If a module or plugin wants to store data in this ``Stash``, it creates
 43    :class:`StashKey`\s for its keys (at the module level):
 44
 45    .. code-block:: python
 46
 47        # At the top-level of the module
 48        some_str_key = StashKey[str]()
 49        some_bool_key = StashKey[bool]()
 50
 51    To store information:
 52
 53    .. code-block:: python
 54
 55        # Value type must match the key.
 56        stash[some_str_key] = "value"
 57        stash[some_bool_key] = True
 58
 59    To retrieve the information:
 60
 61    .. code-block:: python
 62
 63        # The static type of some_str is str.
 64        some_str = stash[some_str_key]
 65        # The static type of some_bool is bool.
 66        some_bool = stash[some_bool_key]
 67
 68    .. versionadded:: 7.0
 69    """
 70
 71    __slots__ = ("_storage",)
 72
 73    def __init__(self) -> None:
 74        self._storage: dict[StashKey[Any], object] = {}
 75
 76    def __setitem__(self, key: StashKey[T], value: T) -> None:
 77        """Set a value for key."""
 78        self._storage[key] = value
 79
 80    def __getitem__(self, key: StashKey[T]) -> T:
 81        """Get the value for key.
 82
 83        Raises ``KeyError`` if the key wasn't set before.
 84        """
 85        return cast(T, self._storage[key])
 86
 87    def get(self, key: StashKey[T], default: D) -> T | D:
 88        """Get the value for key, or return default if the key wasn't set
 89        before."""
 90        try:
 91            return self[key]
 92        except KeyError:
 93            return default
 94
 95    def setdefault(self, key: StashKey[T], default: T) -> T:
 96        """Return the value of key if already set, otherwise set the value
 97        of key to default and return default."""
 98        try:
 99            return self[key]
100        except KeyError:
101            self[key] = default
102            return default
103
104    def __delitem__(self, key: StashKey[T]) -> None:
105        """Delete the value for key.
106
107        Raises ``KeyError`` if the key wasn't set before.
108        """
109        del self._storage[key]
110
111    def __contains__(self, key: StashKey[T]) -> bool:
112        """Return whether key was set."""
113        return key in self._storage
114
115    def __len__(self) -> int:
116        """Return how many items exist in the stash."""
117        return len(self._storage)

Stash is a type-safe heterogeneous mutable mapping that allows keys and value types to be defined separately from where it (the Stash) is created.

Usually you will be given an object which has a Stash, for example ~pytest.Config or a ~_pytest.nodes.Node:

stash: Stash = some_object.stash

If a module or plugin wants to store data in this Stash, it creates StashKey\s for its keys (at the module level):

# At the top-level of the module
some_str_key = StashKey[str]()
some_bool_key = StashKey[bool]()

To store information:

# Value type must match the key.
stash[some_str_key] = "value"
stash[some_bool_key] = True

To retrieve the information:

# The static type of some_str is str.
some_str = stash[some_str_key]
# The static type of some_bool is bool.
some_bool = stash[some_bool_key]

New in version 7.0.

def get(self, key: _pytest.stash.StashKey[~T], default: ~D) -> Union[~T, ~D]:
87    def get(self, key: StashKey[T], default: D) -> T | D:
88        """Get the value for key, or return default if the key wasn't set
89        before."""
90        try:
91            return self[key]
92        except KeyError:
93            return default

Get the value for key, or return default if the key wasn't set before.

def setdefault(self, key: _pytest.stash.StashKey[~T], default: ~T) -> ~T:
 95    def setdefault(self, key: StashKey[T], default: T) -> T:
 96        """Return the value of key if already set, otherwise set the value
 97        of key to default and return default."""
 98        try:
 99            return self[key]
100        except KeyError:
101            self[key] = default
102            return default

Return the value of key if already set, otherwise set the value of key to default and return default.

class StashKey(typing.Generic[~T]):
17class StashKey(Generic[T]):
18    """``StashKey`` is an object used as a key to a :class:`Stash`.
19
20    A ``StashKey`` is associated with the type ``T`` of the value of the key.
21
22    A ``StashKey`` is unique and cannot conflict with another key.
23
24    .. versionadded:: 7.0
25    """
26
27    __slots__ = ()

StashKey is an object used as a key to a Stash.

A StashKey is associated with the type T of the value of the key.

A StashKey is unique and cannot conflict with another key.

New in version 7.0.

@final
@dataclasses.dataclass
class TempPathFactory:
 41@final
 42@dataclasses.dataclass
 43class TempPathFactory:
 44    """Factory for temporary directories under the common base temp directory,
 45    as discussed at :ref:`temporary directory location and retention`.
 46    """
 47
 48    _given_basetemp: Path | None
 49    # pluggy TagTracerSub, not currently exposed, so Any.
 50    _trace: Any
 51    _basetemp: Path | None
 52    _retention_count: int
 53    _retention_policy: RetentionType
 54
 55    def __init__(
 56        self,
 57        given_basetemp: Path | None,
 58        retention_count: int,
 59        retention_policy: RetentionType,
 60        trace,
 61        basetemp: Path | None = None,
 62        *,
 63        _ispytest: bool = False,
 64    ) -> None:
 65        check_ispytest(_ispytest)
 66        if given_basetemp is None:
 67            self._given_basetemp = None
 68        else:
 69            # Use os.path.abspath() to get absolute path instead of resolve() as it
 70            # does not work the same in all platforms (see #4427).
 71            # Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012).
 72            self._given_basetemp = Path(os.path.abspath(str(given_basetemp)))
 73        self._trace = trace
 74        self._retention_count = retention_count
 75        self._retention_policy = retention_policy
 76        self._basetemp = basetemp
 77
 78    @classmethod
 79    def from_config(
 80        cls,
 81        config: Config,
 82        *,
 83        _ispytest: bool = False,
 84    ) -> TempPathFactory:
 85        """Create a factory according to pytest configuration.
 86
 87        :meta private:
 88        """
 89        check_ispytest(_ispytest)
 90        count = int(config.getini("tmp_path_retention_count"))
 91        if count < 0:
 92            raise ValueError(
 93                f"tmp_path_retention_count must be >= 0. Current input: {count}."
 94            )
 95
 96        policy = config.getini("tmp_path_retention_policy")
 97        if policy not in ("all", "failed", "none"):
 98            raise ValueError(
 99                f"tmp_path_retention_policy must be either all, failed, none. Current input: {policy}."
100            )
101
102        return cls(
103            given_basetemp=config.option.basetemp,
104            trace=config.trace.get("tmpdir"),
105            retention_count=count,
106            retention_policy=policy,
107            _ispytest=True,
108        )
109
110    def _ensure_relative_to_basetemp(self, basename: str) -> str:
111        basename = os.path.normpath(basename)
112        if (self.getbasetemp() / basename).resolve().parent != self.getbasetemp():
113            raise ValueError(f"{basename} is not a normalized and relative path")
114        return basename
115
116    def mktemp(self, basename: str, numbered: bool = True) -> Path:
117        """Create a new temporary directory managed by the factory.
118
119        :param basename:
120            Directory base name, must be a relative path.
121
122        :param numbered:
123            If ``True``, ensure the directory is unique by adding a numbered
124            suffix greater than any existing one: ``basename="foo-"`` and ``numbered=True``
125            means that this function will create directories named ``"foo-0"``,
126            ``"foo-1"``, ``"foo-2"`` and so on.
127
128        :returns:
129            The path to the new directory.
130        """
131        basename = self._ensure_relative_to_basetemp(basename)
132        if not numbered:
133            p = self.getbasetemp().joinpath(basename)
134            p.mkdir(mode=0o700)
135        else:
136            p = make_numbered_dir(root=self.getbasetemp(), prefix=basename, mode=0o700)
137            self._trace("mktemp", p)
138        return p
139
140    def getbasetemp(self) -> Path:
141        """Return the base temporary directory, creating it if needed.
142
143        :returns:
144            The base temporary directory.
145        """
146        if self._basetemp is not None:
147            return self._basetemp
148
149        if self._given_basetemp is not None:
150            basetemp = self._given_basetemp
151            if basetemp.exists():
152                rm_rf(basetemp)
153            basetemp.mkdir(mode=0o700)
154            basetemp = basetemp.resolve()
155        else:
156            from_env = os.environ.get("PYTEST_DEBUG_TEMPROOT")
157            temproot = Path(from_env or tempfile.gettempdir()).resolve()
158            user = get_user() or "unknown"
159            # use a sub-directory in the temproot to speed-up
160            # make_numbered_dir() call
161            rootdir = temproot.joinpath(f"pytest-of-{user}")
162            try:
163                rootdir.mkdir(mode=0o700, exist_ok=True)
164            except OSError:
165                # getuser() likely returned illegal characters for the platform, use unknown back off mechanism
166                rootdir = temproot.joinpath("pytest-of-unknown")
167                rootdir.mkdir(mode=0o700, exist_ok=True)
168            # Because we use exist_ok=True with a predictable name, make sure
169            # we are the owners, to prevent any funny business (on unix, where
170            # temproot is usually shared).
171            # Also, to keep things private, fixup any world-readable temp
172            # rootdir's permissions. Historically 0o755 was used, so we can't
173            # just error out on this, at least for a while.
174            uid = get_user_id()
175            if uid is not None:
176                rootdir_stat = rootdir.stat()
177                if rootdir_stat.st_uid != uid:
178                    raise OSError(
179                        f"The temporary directory {rootdir} is not owned by the current user. "
180                        "Fix this and try again."
181                    )
182                if (rootdir_stat.st_mode & 0o077) != 0:
183                    os.chmod(rootdir, rootdir_stat.st_mode & ~0o077)
184            keep = self._retention_count
185            if self._retention_policy == "none":
186                keep = 0
187            basetemp = make_numbered_dir_with_cleanup(
188                prefix="pytest-",
189                root=rootdir,
190                keep=keep,
191                lock_timeout=LOCK_TIMEOUT,
192                mode=0o700,
193            )
194        assert basetemp is not None, basetemp
195        self._basetemp = basetemp
196        self._trace("new basetemp", basetemp)
197        return basetemp

Factory for temporary directories under the common base temp directory, as discussed at :ref:temporary directory location and retention.

TempPathFactory( given_basetemp: pathlib.Path | None, retention_count: int, retention_policy: Literal['all', 'failed', 'none'], trace, basetemp: pathlib.Path | None = None, *, _ispytest: bool = False)
55    def __init__(
56        self,
57        given_basetemp: Path | None,
58        retention_count: int,
59        retention_policy: RetentionType,
60        trace,
61        basetemp: Path | None = None,
62        *,
63        _ispytest: bool = False,
64    ) -> None:
65        check_ispytest(_ispytest)
66        if given_basetemp is None:
67            self._given_basetemp = None
68        else:
69            # Use os.path.abspath() to get absolute path instead of resolve() as it
70            # does not work the same in all platforms (see #4427).
71            # Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012).
72            self._given_basetemp = Path(os.path.abspath(str(given_basetemp)))
73        self._trace = trace
74        self._retention_count = retention_count
75        self._retention_policy = retention_policy
76        self._basetemp = basetemp
@classmethod
def from_config( cls, config: _pytest.config.Config, *, _ispytest: bool = False) -> _pytest.tmpdir.TempPathFactory:
 78    @classmethod
 79    def from_config(
 80        cls,
 81        config: Config,
 82        *,
 83        _ispytest: bool = False,
 84    ) -> TempPathFactory:
 85        """Create a factory according to pytest configuration.
 86
 87        :meta private:
 88        """
 89        check_ispytest(_ispytest)
 90        count = int(config.getini("tmp_path_retention_count"))
 91        if count < 0:
 92            raise ValueError(
 93                f"tmp_path_retention_count must be >= 0. Current input: {count}."
 94            )
 95
 96        policy = config.getini("tmp_path_retention_policy")
 97        if policy not in ("all", "failed", "none"):
 98            raise ValueError(
 99                f"tmp_path_retention_policy must be either all, failed, none. Current input: {policy}."
100            )
101
102        return cls(
103            given_basetemp=config.option.basetemp,
104            trace=config.trace.get("tmpdir"),
105            retention_count=count,
106            retention_policy=policy,
107            _ispytest=True,
108        )

Create a factory according to pytest configuration.

:meta private:

def mktemp(self, basename: str, numbered: bool = True) -> pathlib.Path:
116    def mktemp(self, basename: str, numbered: bool = True) -> Path:
117        """Create a new temporary directory managed by the factory.
118
119        :param basename:
120            Directory base name, must be a relative path.
121
122        :param numbered:
123            If ``True``, ensure the directory is unique by adding a numbered
124            suffix greater than any existing one: ``basename="foo-"`` and ``numbered=True``
125            means that this function will create directories named ``"foo-0"``,
126            ``"foo-1"``, ``"foo-2"`` and so on.
127
128        :returns:
129            The path to the new directory.
130        """
131        basename = self._ensure_relative_to_basetemp(basename)
132        if not numbered:
133            p = self.getbasetemp().joinpath(basename)
134            p.mkdir(mode=0o700)
135        else:
136            p = make_numbered_dir(root=self.getbasetemp(), prefix=basename, mode=0o700)
137            self._trace("mktemp", p)
138        return p

Create a new temporary directory managed by the factory.

Parameters
  • basename: Directory base name, must be a relative path.

  • numbered: If True, ensure the directory is unique by adding a numbered suffix greater than any existing one: basename="foo-" and numbered=True means that this function will create directories named "foo-0", "foo-1", "foo-2" and so on.

:returns: The path to the new directory.

def getbasetemp(self) -> pathlib.Path:
140    def getbasetemp(self) -> Path:
141        """Return the base temporary directory, creating it if needed.
142
143        :returns:
144            The base temporary directory.
145        """
146        if self._basetemp is not None:
147            return self._basetemp
148
149        if self._given_basetemp is not None:
150            basetemp = self._given_basetemp
151            if basetemp.exists():
152                rm_rf(basetemp)
153            basetemp.mkdir(mode=0o700)
154            basetemp = basetemp.resolve()
155        else:
156            from_env = os.environ.get("PYTEST_DEBUG_TEMPROOT")
157            temproot = Path(from_env or tempfile.gettempdir()).resolve()
158            user = get_user() or "unknown"
159            # use a sub-directory in the temproot to speed-up
160            # make_numbered_dir() call
161            rootdir = temproot.joinpath(f"pytest-of-{user}")
162            try:
163                rootdir.mkdir(mode=0o700, exist_ok=True)
164            except OSError:
165                # getuser() likely returned illegal characters for the platform, use unknown back off mechanism
166                rootdir = temproot.joinpath("pytest-of-unknown")
167                rootdir.mkdir(mode=0o700, exist_ok=True)
168            # Because we use exist_ok=True with a predictable name, make sure
169            # we are the owners, to prevent any funny business (on unix, where
170            # temproot is usually shared).
171            # Also, to keep things private, fixup any world-readable temp
172            # rootdir's permissions. Historically 0o755 was used, so we can't
173            # just error out on this, at least for a while.
174            uid = get_user_id()
175            if uid is not None:
176                rootdir_stat = rootdir.stat()
177                if rootdir_stat.st_uid != uid:
178                    raise OSError(
179                        f"The temporary directory {rootdir} is not owned by the current user. "
180                        "Fix this and try again."
181                    )
182                if (rootdir_stat.st_mode & 0o077) != 0:
183                    os.chmod(rootdir, rootdir_stat.st_mode & ~0o077)
184            keep = self._retention_count
185            if self._retention_policy == "none":
186                keep = 0
187            basetemp = make_numbered_dir_with_cleanup(
188                prefix="pytest-",
189                root=rootdir,
190                keep=keep,
191                lock_timeout=LOCK_TIMEOUT,
192                mode=0o700,
193            )
194        assert basetemp is not None, basetemp
195        self._basetemp = basetemp
196        self._trace("new basetemp", basetemp)
197        return basetemp

Return the base temporary directory, creating it if needed.

:returns: The base temporary directory.

@final
@dataclasses.dataclass
class TempdirFactory:
267@final
268@dataclasses.dataclass
269class TempdirFactory:
270    """Backward compatibility wrapper that implements ``py.path.local``
271    for :class:`TempPathFactory`.
272
273    .. note::
274        These days, it is preferred to use ``tmp_path_factory``.
275
276        :ref:`About the tmpdir and tmpdir_factory fixtures<tmpdir and tmpdir_factory>`.
277
278    """
279
280    _tmppath_factory: TempPathFactory
281
282    def __init__(
283        self, tmppath_factory: TempPathFactory, *, _ispytest: bool = False
284    ) -> None:
285        check_ispytest(_ispytest)
286        self._tmppath_factory = tmppath_factory
287
288    def mktemp(self, basename: str, numbered: bool = True) -> LEGACY_PATH:
289        """Same as :meth:`TempPathFactory.mktemp`, but returns a ``py.path.local`` object."""
290        return legacy_path(self._tmppath_factory.mktemp(basename, numbered).resolve())
291
292    def getbasetemp(self) -> LEGACY_PATH:
293        """Same as :meth:`TempPathFactory.getbasetemp`, but returns a ``py.path.local`` object."""
294        return legacy_path(self._tmppath_factory.getbasetemp().resolve())

Backward compatibility wrapper that implements py.path.local for TempPathFactory.

These days, it is preferred to use tmp_path_factory.

:ref:About the tmpdir and tmpdir_factory fixtures<tmpdir and tmpdir_factory>.

TempdirFactory( tmppath_factory: _pytest.tmpdir.TempPathFactory, *, _ispytest: bool = False)
282    def __init__(
283        self, tmppath_factory: TempPathFactory, *, _ispytest: bool = False
284    ) -> None:
285        check_ispytest(_ispytest)
286        self._tmppath_factory = tmppath_factory
def mktemp(self, basename: str, numbered: bool = True) -> _pytest._py.path.LocalPath:
288    def mktemp(self, basename: str, numbered: bool = True) -> LEGACY_PATH:
289        """Same as :meth:`TempPathFactory.mktemp`, but returns a ``py.path.local`` object."""
290        return legacy_path(self._tmppath_factory.mktemp(basename, numbered).resolve())

Same as TempPathFactory.mktemp(), but returns a py.path.local object.

def getbasetemp(self) -> _pytest._py.path.LocalPath:
292    def getbasetemp(self) -> LEGACY_PATH:
293        """Same as :meth:`TempPathFactory.getbasetemp`, but returns a ``py.path.local`` object."""
294        return legacy_path(self._tmppath_factory.getbasetemp().resolve())

Same as TempPathFactory.getbasetemp(), but returns a py.path.local object.

@final
class TerminalReporter:
 370@final
 371class TerminalReporter:
 372    def __init__(self, config: Config, file: TextIO | None = None) -> None:
 373        import _pytest.config
 374
 375        self.config = config
 376        self._numcollected = 0
 377        self._session: Session | None = None
 378        self._showfspath: bool | None = None
 379
 380        self.stats: dict[str, list[Any]] = {}
 381        self._main_color: str | None = None
 382        self._known_types: list[str] | None = None
 383        self.startpath = config.invocation_params.dir
 384        if file is None:
 385            file = sys.stdout
 386        self._tw = _pytest.config.create_terminal_writer(config, file)
 387        self._screen_width = self._tw.fullwidth
 388        self.currentfspath: None | Path | str | int = None
 389        self.reportchars = getreportopt(config)
 390        self.foldskipped = config.option.fold_skipped
 391        self.hasmarkup = self._tw.hasmarkup
 392        # isatty should be a method but was wrongly implemented as a boolean.
 393        # We use CallableBool here to support both.
 394        self.isatty = compat.CallableBool(file.isatty())
 395        self._progress_nodeids_reported: set[str] = set()
 396        self._timing_nodeids_reported: set[str] = set()
 397        self._show_progress_info = self._determine_show_progress_info()
 398        self._collect_report_last_write = timing.Instant()
 399        self._already_displayed_warnings: int | None = None
 400        self._keyboardinterrupt_memo: ExceptionRepr | None = None
 401
 402    def _determine_show_progress_info(
 403        self,
 404    ) -> Literal["progress", "count", "times", False]:
 405        """Return whether we should display progress information based on the current config."""
 406        # do not show progress if we are not capturing output (#3038) unless explicitly
 407        # overridden by progress-even-when-capture-no
 408        if (
 409            self.config.getoption("capture", "no") == "no"
 410            and self.config.getini("console_output_style")
 411            != "progress-even-when-capture-no"
 412        ):
 413            return False
 414        # do not show progress if we are showing fixture setup/teardown
 415        if self.config.getoption("setupshow", False):
 416            return False
 417        cfg: str = self.config.getini("console_output_style")
 418        if cfg in {"progress", "progress-even-when-capture-no"}:
 419            return "progress"
 420        elif cfg == "count":
 421            return "count"
 422        elif cfg == "times":
 423            return "times"
 424        else:
 425            return False
 426
 427    @property
 428    def verbosity(self) -> int:
 429        verbosity: int = self.config.option.verbose
 430        return verbosity
 431
 432    @property
 433    def showheader(self) -> bool:
 434        return self.verbosity >= 0
 435
 436    @property
 437    def no_header(self) -> bool:
 438        return bool(self.config.option.no_header)
 439
 440    @property
 441    def no_summary(self) -> bool:
 442        return bool(self.config.option.no_summary)
 443
 444    @property
 445    def showfspath(self) -> bool:
 446        if self._showfspath is None:
 447            return self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) >= 0
 448        return self._showfspath
 449
 450    @showfspath.setter
 451    def showfspath(self, value: bool | None) -> None:
 452        self._showfspath = value
 453
 454    @property
 455    def showlongtestinfo(self) -> bool:
 456        return self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) > 0
 457
 458    def hasopt(self, char: str) -> bool:
 459        char = {"xfailed": "x", "skipped": "s"}.get(char, char)
 460        return char in self.reportchars
 461
 462    def write_fspath_result(self, nodeid: str, res: str, **markup: bool) -> None:
 463        fspath = self.config.rootpath / nodeid.split("::")[0]
 464        if self.currentfspath is None or fspath != self.currentfspath:
 465            if self.currentfspath is not None and self._show_progress_info:
 466                self._write_progress_information_filling_space()
 467            self.currentfspath = fspath
 468            relfspath = bestrelpath(self.startpath, fspath)
 469            self._tw.line()
 470            self._tw.write(relfspath + " ")
 471        self._tw.write(res, flush=True, **markup)
 472
 473    def write_ensure_prefix(self, prefix: str, extra: str = "", **kwargs) -> None:
 474        if self.currentfspath != prefix:
 475            self._tw.line()
 476            self.currentfspath = prefix
 477            self._tw.write(prefix)
 478        if extra:
 479            self._tw.write(extra, **kwargs)
 480            self.currentfspath = -2
 481
 482    def ensure_newline(self) -> None:
 483        if self.currentfspath:
 484            self._tw.line()
 485            self.currentfspath = None
 486
 487    def wrap_write(
 488        self,
 489        content: str,
 490        *,
 491        flush: bool = False,
 492        margin: int = 8,
 493        line_sep: str = "\n",
 494        **markup: bool,
 495    ) -> None:
 496        """Wrap message with margin for progress info."""
 497        width_of_current_line = self._tw.width_of_current_line
 498        wrapped = line_sep.join(
 499            textwrap.wrap(
 500                " " * width_of_current_line + content,
 501                width=self._screen_width - margin,
 502                drop_whitespace=True,
 503                replace_whitespace=False,
 504            ),
 505        )
 506        wrapped = wrapped[width_of_current_line:]
 507        self._tw.write(wrapped, flush=flush, **markup)
 508
 509    def write(self, content: str, *, flush: bool = False, **markup: bool) -> None:
 510        self._tw.write(content, flush=flush, **markup)
 511
 512    def flush(self) -> None:
 513        self._tw.flush()
 514
 515    def write_line(self, line: str | bytes, **markup: bool) -> None:
 516        if not isinstance(line, str):
 517            line = str(line, errors="replace")
 518        self.ensure_newline()
 519        self._tw.line(line, **markup)
 520
 521    def rewrite(self, line: str, **markup: bool) -> None:
 522        """Rewinds the terminal cursor to the beginning and writes the given line.
 523
 524        :param erase:
 525            If True, will also add spaces until the full terminal width to ensure
 526            previous lines are properly erased.
 527
 528        The rest of the keyword arguments are markup instructions.
 529        """
 530        erase = markup.pop("erase", False)
 531        if erase:
 532            fill_count = self._tw.fullwidth - len(line) - 1
 533            fill = " " * fill_count
 534        else:
 535            fill = ""
 536        line = str(line)
 537        self._tw.write("\r" + line + fill, **markup)
 538
 539    def write_sep(
 540        self,
 541        sep: str,
 542        title: str | None = None,
 543        fullwidth: int | None = None,
 544        **markup: bool,
 545    ) -> None:
 546        self.ensure_newline()
 547        self._tw.sep(sep, title, fullwidth, **markup)
 548
 549    def section(self, title: str, sep: str = "=", **kw: bool) -> None:
 550        self._tw.sep(sep, title, **kw)
 551
 552    def line(self, msg: str, **kw: bool) -> None:
 553        self._tw.line(msg, **kw)
 554
 555    def _add_stats(self, category: str, items: Sequence[Any]) -> None:
 556        set_main_color = category not in self.stats
 557        self.stats.setdefault(category, []).extend(items)
 558        if set_main_color:
 559            self._set_main_color()
 560
 561    def pytest_internalerror(self, excrepr: ExceptionRepr) -> bool:
 562        for line in str(excrepr).split("\n"):
 563            self.write_line("INTERNALERROR> " + line)
 564        return True
 565
 566    def pytest_warning_recorded(
 567        self,
 568        warning_message: warnings.WarningMessage,
 569        nodeid: str,
 570    ) -> None:
 571        from _pytest.warnings import warning_record_to_str
 572
 573        fslocation = warning_message.filename, warning_message.lineno
 574        message = warning_record_to_str(warning_message)
 575
 576        warning_report = WarningReport(
 577            fslocation=fslocation, message=message, nodeid=nodeid
 578        )
 579        self._add_stats("warnings", [warning_report])
 580
 581    def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None:
 582        if self.config.option.traceconfig:
 583            msg = f"PLUGIN registered: {plugin}"
 584            # XXX This event may happen during setup/teardown time
 585            #     which unfortunately captures our output here
 586            #     which garbles our output if we use self.write_line.
 587            self.write_line(msg)
 588
 589    def pytest_deselected(self, items: Sequence[Item]) -> None:
 590        self._add_stats("deselected", items)
 591
 592    def pytest_runtest_logstart(
 593        self, nodeid: str, location: tuple[str, int | None, str]
 594    ) -> None:
 595        fspath, lineno, domain = location
 596        # Ensure that the path is printed before the
 597        # 1st test of a module starts running.
 598        if self.showlongtestinfo:
 599            line = self._locationline(nodeid, fspath, lineno, domain)
 600            self.write_ensure_prefix(line, "")
 601            self.flush()
 602        elif self.showfspath:
 603            self.write_fspath_result(nodeid, "")
 604            self.flush()
 605
 606    def pytest_runtest_logreport(self, report: TestReport) -> None:
 607        self._tests_ran = True
 608        rep = report
 609
 610        res = TestShortLogReport(
 611            *self.config.hook.pytest_report_teststatus(report=rep, config=self.config)
 612        )
 613        category, letter, word = res.category, res.letter, res.word
 614        if not isinstance(word, tuple):
 615            markup = None
 616        else:
 617            word, markup = word
 618        self._add_stats(category, [rep])
 619        if not letter and not word:
 620            # Probably passed setup/teardown.
 621            return
 622        if markup is None:
 623            was_xfail = hasattr(report, "wasxfail")
 624            if rep.passed and not was_xfail:
 625                markup = {"green": True}
 626            elif rep.passed and was_xfail:
 627                markup = {"yellow": True}
 628            elif rep.failed:
 629                markup = {"red": True}
 630            elif rep.skipped:
 631                markup = {"yellow": True}
 632            else:
 633                markup = {}
 634        self._progress_nodeids_reported.add(rep.nodeid)
 635        if self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) <= 0:
 636            self._tw.write(letter, **markup)
 637            # When running in xdist, the logreport and logfinish of multiple
 638            # items are interspersed, e.g. `logreport`, `logreport`,
 639            # `logfinish`, `logfinish`. To avoid the "past edge" calculation
 640            # from getting confused and overflowing (#7166), do the past edge
 641            # printing here and not in logfinish, except for the 100% which
 642            # should only be printed after all teardowns are finished.
 643            if self._show_progress_info and not self._is_last_item:
 644                self._write_progress_information_if_past_edge()
 645        else:
 646            line = self._locationline(rep.nodeid, *rep.location)
 647            running_xdist = hasattr(rep, "node")
 648            if not running_xdist:
 649                self.write_ensure_prefix(line, word, **markup)
 650                if rep.skipped or hasattr(report, "wasxfail"):
 651                    reason = _get_raw_skip_reason(rep)
 652                    if self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) < 2:
 653                        available_width = (
 654                            (self._tw.fullwidth - self._tw.width_of_current_line)
 655                            - len(" [100%]")
 656                            - 1
 657                        )
 658                        formatted_reason = _format_trimmed(
 659                            " ({})", reason, available_width
 660                        )
 661                    else:
 662                        formatted_reason = f" ({reason})"
 663
 664                    if reason and formatted_reason is not None:
 665                        self.wrap_write(formatted_reason)
 666                if self._show_progress_info:
 667                    self._write_progress_information_filling_space()
 668            else:
 669                self.ensure_newline()
 670                self._tw.write(f"[{rep.node.gateway.id}]")
 671                if self._show_progress_info:
 672                    self._tw.write(
 673                        self._get_progress_information_message() + " ", cyan=True
 674                    )
 675                else:
 676                    self._tw.write(" ")
 677                self._tw.write(word, **markup)
 678                self._tw.write(" " + line)
 679                self.currentfspath = -2
 680        self.flush()
 681
 682    @property
 683    def _is_last_item(self) -> bool:
 684        assert self._session is not None
 685        return len(self._progress_nodeids_reported) == self._session.testscollected
 686
 687    @hookimpl(wrapper=True)
 688    def pytest_runtestloop(self) -> Generator[None, object, object]:
 689        result = yield
 690
 691        # Write the final/100% progress -- deferred until the loop is complete.
 692        if (
 693            self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) <= 0
 694            and self._show_progress_info
 695            and self._progress_nodeids_reported
 696        ):
 697            self._write_progress_information_filling_space()
 698
 699        return result
 700
 701    def _get_progress_information_message(self) -> str:
 702        assert self._session
 703        collected = self._session.testscollected
 704        if self._show_progress_info == "count":
 705            if collected:
 706                progress = len(self._progress_nodeids_reported)
 707                counter_format = f"{{:{len(str(collected))}d}}"
 708                format_string = f" [{counter_format}/{{}}]"
 709                return format_string.format(progress, collected)
 710            return f" [ {collected} / {collected} ]"
 711        if self._show_progress_info == "times":
 712            if not collected:
 713                return ""
 714            all_reports = (
 715                self._get_reports_to_display("passed")
 716                + self._get_reports_to_display("xpassed")
 717                + self._get_reports_to_display("failed")
 718                + self._get_reports_to_display("xfailed")
 719                + self._get_reports_to_display("skipped")
 720                + self._get_reports_to_display("error")
 721                + self._get_reports_to_display("")
 722            )
 723            current_location = all_reports[-1].location[0]
 724            not_reported = [
 725                r for r in all_reports if r.nodeid not in self._timing_nodeids_reported
 726            ]
 727            tests_in_module = sum(
 728                i.location[0] == current_location for i in self._session.items
 729            )
 730            tests_completed = sum(
 731                r.when == "setup"
 732                for r in not_reported
 733                if r.location[0] == current_location
 734            )
 735            last_in_module = tests_completed == tests_in_module
 736            if self.showlongtestinfo or last_in_module:
 737                self._timing_nodeids_reported.update(r.nodeid for r in not_reported)
 738                return format_node_duration(sum(r.duration for r in not_reported))
 739            return ""
 740        if collected:
 741            return f" [{len(self._progress_nodeids_reported) * 100 // collected:3d}%]"
 742        return " [100%]"
 743
 744    def _write_progress_information_if_past_edge(self) -> None:
 745        w = self._width_of_current_line
 746        if self._show_progress_info == "count":
 747            assert self._session
 748            num_tests = self._session.testscollected
 749            progress_length = len(f" [{num_tests}/{num_tests}]")
 750        elif self._show_progress_info == "times":
 751            progress_length = len(" 99h 59m")
 752        else:
 753            progress_length = len(" [100%]")
 754        past_edge = w + progress_length + 1 >= self._screen_width
 755        if past_edge:
 756            main_color, _ = self._get_main_color()
 757            msg = self._get_progress_information_message()
 758            self._tw.write(msg + "\n", **{main_color: True})
 759
 760    def _write_progress_information_filling_space(self) -> None:
 761        color, _ = self._get_main_color()
 762        msg = self._get_progress_information_message()
 763        w = self._width_of_current_line
 764        fill = self._tw.fullwidth - w - 1
 765        self.write(msg.rjust(fill), flush=True, **{color: True})
 766
 767    @property
 768    def _width_of_current_line(self) -> int:
 769        """Return the width of the current line."""
 770        return self._tw.width_of_current_line
 771
 772    def pytest_collection(self) -> None:
 773        if self.isatty():
 774            if self.config.option.verbose >= 0:
 775                self.write("collecting ... ", flush=True, bold=True)
 776        elif self.config.option.verbose >= 1:
 777            self.write("collecting ... ", flush=True, bold=True)
 778
 779    def pytest_collectreport(self, report: CollectReport) -> None:
 780        if report.failed:
 781            self._add_stats("error", [report])
 782        elif report.skipped:
 783            self._add_stats("skipped", [report])
 784        items = [x for x in report.result if isinstance(x, Item)]
 785        self._numcollected += len(items)
 786        if self.isatty():
 787            self.report_collect()
 788
 789    def report_collect(self, final: bool = False) -> None:
 790        if self.config.option.verbose < 0:
 791            return
 792
 793        if not final:
 794            # Only write the "collecting" report every `REPORT_COLLECTING_RESOLUTION`.
 795            if (
 796                self._collect_report_last_write.elapsed().seconds
 797                < REPORT_COLLECTING_RESOLUTION
 798            ):
 799                return
 800            self._collect_report_last_write = timing.Instant()
 801
 802        errors = len(self.stats.get("error", []))
 803        skipped = len(self.stats.get("skipped", []))
 804        deselected = len(self.stats.get("deselected", []))
 805        selected = self._numcollected - deselected
 806        line = "collected " if final else "collecting "
 807        line += (
 808            str(self._numcollected) + " item" + ("" if self._numcollected == 1 else "s")
 809        )
 810        if errors:
 811            line += f" / {errors} error{'s' if errors != 1 else ''}"
 812        if deselected:
 813            line += f" / {deselected} deselected"
 814        if skipped:
 815            line += f" / {skipped} skipped"
 816        if self._numcollected > selected:
 817            line += f" / {selected} selected"
 818        if self.isatty():
 819            self.rewrite(line, bold=True, erase=True)
 820            if final:
 821                self.write("\n")
 822        else:
 823            self.write_line(line)
 824
 825    @hookimpl(trylast=True)
 826    def pytest_sessionstart(self, session: Session) -> None:
 827        self._session = session
 828        self._session_start = timing.Instant()
 829        if not self.showheader:
 830            return
 831        self.write_sep("=", "test session starts", bold=True)
 832        verinfo = platform.python_version()
 833        if not self.no_header:
 834            msg = f"platform {sys.platform} -- Python {verinfo}"
 835            pypy_version_info = getattr(sys, "pypy_version_info", None)
 836            if pypy_version_info:
 837                verinfo = ".".join(map(str, pypy_version_info[:3]))
 838                msg += f"[pypy-{verinfo}-{pypy_version_info[3]}]"
 839            msg += f", pytest-{_pytest._version.version}, pluggy-{pluggy.__version__}"
 840            if (
 841                self.verbosity > 0
 842                or self.config.option.debug
 843                or getattr(self.config.option, "pastebin", None)
 844            ):
 845                msg += " -- " + str(sys.executable)
 846            self.write_line(msg)
 847            lines = self.config.hook.pytest_report_header(
 848                config=self.config, start_path=self.startpath
 849            )
 850            self._write_report_lines_from_hooks(lines)
 851
 852    def _write_report_lines_from_hooks(
 853        self, lines: Sequence[str | Sequence[str]]
 854    ) -> None:
 855        for line_or_lines in reversed(lines):
 856            if isinstance(line_or_lines, str):
 857                self.write_line(line_or_lines)
 858            else:
 859                for line in line_or_lines:
 860                    self.write_line(line)
 861
 862    def pytest_report_header(self, config: Config) -> list[str]:
 863        result = [f"rootdir: {config.rootpath}"]
 864
 865        if config.inipath:
 866            result.append("configfile: " + bestrelpath(config.rootpath, config.inipath))
 867
 868        if config.args_source == Config.ArgsSource.TESTPATHS:
 869            testpaths: list[str] = config.getini("testpaths")
 870            result.append("testpaths: {}".format(", ".join(testpaths)))
 871
 872        plugininfo = config.pluginmanager.list_plugin_distinfo()
 873        if plugininfo:
 874            result.append(
 875                "plugins: {}".format(", ".join(_plugin_nameversions(plugininfo)))
 876            )
 877        return result
 878
 879    def pytest_collection_finish(self, session: Session) -> None:
 880        self.report_collect(True)
 881
 882        lines = self.config.hook.pytest_report_collectionfinish(
 883            config=self.config,
 884            start_path=self.startpath,
 885            items=session.items,
 886        )
 887        self._write_report_lines_from_hooks(lines)
 888
 889        if self.config.getoption("collectonly"):
 890            if session.items:
 891                if self.config.option.verbose > -1:
 892                    self._tw.line("")
 893                self._printcollecteditems(session.items)
 894
 895            failed = self.stats.get("failed")
 896            if failed:
 897                self._tw.sep("!", "collection failures")
 898                for rep in failed:
 899                    rep.toterminal(self._tw)
 900
 901    def _printcollecteditems(self, items: Sequence[Item]) -> None:
 902        test_cases_verbosity = self.config.get_verbosity(Config.VERBOSITY_TEST_CASES)
 903        if test_cases_verbosity < 0:
 904            if test_cases_verbosity < -1:
 905                counts = Counter(item.nodeid.split("::", 1)[0] for item in items)
 906                for name, count in sorted(counts.items()):
 907                    self._tw.line(f"{name}: {count}")
 908            else:
 909                for item in items:
 910                    self._tw.line(item.nodeid)
 911            return
 912        stack: list[Node] = []
 913        indent = ""
 914        for item in items:
 915            needed_collectors = item.listchain()[1:]  # strip root node
 916            while stack:
 917                if stack == needed_collectors[: len(stack)]:
 918                    break
 919                stack.pop()
 920            for col in needed_collectors[len(stack) :]:
 921                stack.append(col)
 922                indent = (len(stack) - 1) * "  "
 923                self._tw.line(f"{indent}{col}")
 924                if test_cases_verbosity >= 1:
 925                    obj = getattr(col, "obj", None)
 926                    doc = inspect.getdoc(obj) if obj else None
 927                    if doc:
 928                        for line in doc.splitlines():
 929                            self._tw.line("{}{}".format(indent + "  ", line))
 930
 931    @hookimpl(wrapper=True)
 932    def pytest_sessionfinish(
 933        self, session: Session, exitstatus: int | ExitCode
 934    ) -> Generator[None]:
 935        result = yield
 936        self._tw.line("")
 937        summary_exit_codes = (
 938            ExitCode.OK,
 939            ExitCode.TESTS_FAILED,
 940            ExitCode.INTERRUPTED,
 941            ExitCode.USAGE_ERROR,
 942            ExitCode.NO_TESTS_COLLECTED,
 943        )
 944        if exitstatus in summary_exit_codes and not self.no_summary:
 945            self.config.hook.pytest_terminal_summary(
 946                terminalreporter=self, exitstatus=exitstatus, config=self.config
 947            )
 948        if session.shouldfail:
 949            self.write_sep("!", str(session.shouldfail), red=True)
 950        if exitstatus == ExitCode.INTERRUPTED:
 951            self._report_keyboardinterrupt()
 952            self._keyboardinterrupt_memo = None
 953        elif session.shouldstop:
 954            self.write_sep("!", str(session.shouldstop), red=True)
 955        self.summary_stats()
 956        return result
 957
 958    @hookimpl(wrapper=True)
 959    def pytest_terminal_summary(self) -> Generator[None]:
 960        self.summary_errors()
 961        self.summary_failures()
 962        self.summary_xfailures()
 963        self.summary_warnings()
 964        self.summary_passes()
 965        self.summary_xpasses()
 966        try:
 967            return (yield)
 968        finally:
 969            self.short_test_summary()
 970            # Display any extra warnings from teardown here (if any).
 971            self.summary_warnings()
 972
 973    def pytest_keyboard_interrupt(self, excinfo: ExceptionInfo[BaseException]) -> None:
 974        self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
 975
 976    def pytest_unconfigure(self) -> None:
 977        if self._keyboardinterrupt_memo is not None:
 978            self._report_keyboardinterrupt()
 979
 980    def _report_keyboardinterrupt(self) -> None:
 981        excrepr = self._keyboardinterrupt_memo
 982        assert excrepr is not None
 983        assert excrepr.reprcrash is not None
 984        msg = excrepr.reprcrash.message
 985        self.write_sep("!", msg)
 986        if "KeyboardInterrupt" in msg:
 987            if self.config.option.fulltrace:
 988                excrepr.toterminal(self._tw)
 989            else:
 990                excrepr.reprcrash.toterminal(self._tw)
 991                self._tw.line(
 992                    "(to show a full traceback on KeyboardInterrupt use --full-trace)",
 993                    yellow=True,
 994                )
 995
 996    def _locationline(
 997        self, nodeid: str, fspath: str, lineno: int | None, domain: str
 998    ) -> str:
 999        def mkrel(nodeid: str) -> str:
1000            line = self.config.cwd_relative_nodeid(nodeid)
1001            if domain and line.endswith(domain):
1002                line = line[: -len(domain)]
1003                values = domain.split("[")
1004                values[0] = values[0].replace(".", "::")  # don't replace '.' in params
1005                line += "[".join(values)
1006            return line
1007
1008        # fspath comes from testid which has a "/"-normalized path.
1009        if fspath:
1010            res = mkrel(nodeid)
1011            if self.verbosity >= 2 and nodeid.split("::")[0] != fspath.replace(
1012                "\\", nodes.SEP
1013            ):
1014                res += " <- " + bestrelpath(self.startpath, Path(fspath))
1015        else:
1016            res = "[location]"
1017        return res + " "
1018
1019    def _getfailureheadline(self, rep):
1020        head_line = rep.head_line
1021        if head_line:
1022            return head_line
1023        return "test session"  # XXX?
1024
1025    def _getcrashline(self, rep):
1026        try:
1027            return str(rep.longrepr.reprcrash)
1028        except AttributeError:
1029            try:
1030                return str(rep.longrepr)[:50]
1031            except AttributeError:
1032                return ""
1033
1034    #
1035    # Summaries for sessionfinish.
1036    #
1037    def getreports(self, name: str):
1038        return [x for x in self.stats.get(name, ()) if not hasattr(x, "_pdbshown")]
1039
1040    def summary_warnings(self) -> None:
1041        if self.hasopt("w"):
1042            all_warnings: list[WarningReport] | None = self.stats.get("warnings")
1043            if not all_warnings:
1044                return
1045
1046            final = self._already_displayed_warnings is not None
1047            if final:
1048                warning_reports = all_warnings[self._already_displayed_warnings :]
1049            else:
1050                warning_reports = all_warnings
1051            self._already_displayed_warnings = len(warning_reports)
1052            if not warning_reports:
1053                return
1054
1055            reports_grouped_by_message: dict[str, list[WarningReport]] = {}
1056            for wr in warning_reports:
1057                reports_grouped_by_message.setdefault(wr.message, []).append(wr)
1058
1059            def collapsed_location_report(reports: list[WarningReport]) -> str:
1060                locations = []
1061                for w in reports:
1062                    location = w.get_location(self.config)
1063                    if location:
1064                        locations.append(location)
1065
1066                if len(locations) < 10:
1067                    return "\n".join(map(str, locations))
1068
1069                counts_by_filename = Counter(
1070                    str(loc).split("::", 1)[0] for loc in locations
1071                )
1072                return "\n".join(
1073                    "{}: {} warning{}".format(k, v, "s" if v > 1 else "")
1074                    for k, v in counts_by_filename.items()
1075                )
1076
1077            title = "warnings summary (final)" if final else "warnings summary"
1078            self.write_sep("=", title, yellow=True, bold=False)
1079            for message, message_reports in reports_grouped_by_message.items():
1080                maybe_location = collapsed_location_report(message_reports)
1081                if maybe_location:
1082                    self._tw.line(maybe_location)
1083                    lines = message.splitlines()
1084                    indented = "\n".join("  " + x for x in lines)
1085                    message = indented.rstrip()
1086                else:
1087                    message = message.rstrip()
1088                self._tw.line(message)
1089                self._tw.line()
1090            self._tw.line(
1091                "-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html"
1092            )
1093
1094    def summary_passes(self) -> None:
1095        self.summary_passes_combined("passed", "PASSES", "P")
1096
1097    def summary_xpasses(self) -> None:
1098        self.summary_passes_combined("xpassed", "XPASSES", "X")
1099
1100    def summary_passes_combined(
1101        self, which_reports: str, sep_title: str, needed_opt: str
1102    ) -> None:
1103        if self.config.option.tbstyle != "no":
1104            if self.hasopt(needed_opt):
1105                reports: list[TestReport] = self.getreports(which_reports)
1106                if not reports:
1107                    return
1108                self.write_sep("=", sep_title)
1109                for rep in reports:
1110                    if rep.sections:
1111                        msg = self._getfailureheadline(rep)
1112                        self.write_sep("_", msg, green=True, bold=True)
1113                        self._outrep_summary(rep)
1114                    self._handle_teardown_sections(rep.nodeid)
1115
1116    def _get_teardown_reports(self, nodeid: str) -> list[TestReport]:
1117        reports = self.getreports("")
1118        return [
1119            report
1120            for report in reports
1121            if report.when == "teardown" and report.nodeid == nodeid
1122        ]
1123
1124    def _handle_teardown_sections(self, nodeid: str) -> None:
1125        for report in self._get_teardown_reports(nodeid):
1126            self.print_teardown_sections(report)
1127
1128    def print_teardown_sections(self, rep: TestReport) -> None:
1129        showcapture = self.config.option.showcapture
1130        if showcapture == "no":
1131            return
1132        for secname, content in rep.sections:
1133            if showcapture != "all" and showcapture not in secname:
1134                continue
1135            if "teardown" in secname:
1136                self._tw.sep("-", secname)
1137                if content[-1:] == "\n":
1138                    content = content[:-1]
1139                self._tw.line(content)
1140
1141    def summary_failures(self) -> None:
1142        style = self.config.option.tbstyle
1143        self.summary_failures_combined("failed", "FAILURES", style=style)
1144
1145    def summary_xfailures(self) -> None:
1146        show_tb = self.config.option.xfail_tb
1147        style = self.config.option.tbstyle if show_tb else "no"
1148        self.summary_failures_combined("xfailed", "XFAILURES", style=style)
1149
1150    def summary_failures_combined(
1151        self,
1152        which_reports: str,
1153        sep_title: str,
1154        *,
1155        style: str,
1156        needed_opt: str | None = None,
1157    ) -> None:
1158        if style != "no":
1159            if not needed_opt or self.hasopt(needed_opt):
1160                reports: list[BaseReport] = self.getreports(which_reports)
1161                if not reports:
1162                    return
1163                self.write_sep("=", sep_title)
1164                if style == "line":
1165                    for rep in reports:
1166                        line = self._getcrashline(rep)
1167                        self.write_line(line)
1168                else:
1169                    for rep in reports:
1170                        msg = self._getfailureheadline(rep)
1171                        self.write_sep("_", msg, red=True, bold=True)
1172                        self._outrep_summary(rep)
1173                        self._handle_teardown_sections(rep.nodeid)
1174
1175    def summary_errors(self) -> None:
1176        if self.config.option.tbstyle != "no":
1177            reports: list[BaseReport] = self.getreports("error")
1178            if not reports:
1179                return
1180            self.write_sep("=", "ERRORS")
1181            for rep in self.stats["error"]:
1182                msg = self._getfailureheadline(rep)
1183                if rep.when == "collect":
1184                    msg = "ERROR collecting " + msg
1185                else:
1186                    msg = f"ERROR at {rep.when} of {msg}"
1187                self.write_sep("_", msg, red=True, bold=True)
1188                self._outrep_summary(rep)
1189
1190    def _outrep_summary(self, rep: BaseReport) -> None:
1191        rep.toterminal(self._tw)
1192        showcapture = self.config.option.showcapture
1193        if showcapture == "no":
1194            return
1195        for secname, content in rep.sections:
1196            if showcapture != "all" and showcapture not in secname:
1197                continue
1198            self._tw.sep("-", secname)
1199            if content[-1:] == "\n":
1200                content = content[:-1]
1201            self._tw.line(content)
1202
1203    def summary_stats(self) -> None:
1204        if self.verbosity < -1:
1205            return
1206
1207        session_duration = self._session_start.elapsed()
1208        (parts, main_color) = self.build_summary_stats_line()
1209        line_parts = []
1210
1211        display_sep = self.verbosity >= 0
1212        if display_sep:
1213            fullwidth = self._tw.fullwidth
1214        for text, markup in parts:
1215            with_markup = self._tw.markup(text, **markup)
1216            if display_sep:
1217                fullwidth += len(with_markup) - len(text)
1218            line_parts.append(with_markup)
1219        msg = ", ".join(line_parts)
1220
1221        main_markup = {main_color: True}
1222        duration = f" in {format_session_duration(session_duration.seconds)}"
1223        duration_with_markup = self._tw.markup(duration, **main_markup)
1224        if display_sep:
1225            fullwidth += len(duration_with_markup) - len(duration)
1226        msg += duration_with_markup
1227
1228        if display_sep:
1229            markup_for_end_sep = self._tw.markup("", **main_markup)
1230            if markup_for_end_sep.endswith("\x1b[0m"):
1231                markup_for_end_sep = markup_for_end_sep[:-4]
1232            fullwidth += len(markup_for_end_sep)
1233            msg += markup_for_end_sep
1234
1235        if display_sep:
1236            self.write_sep("=", msg, fullwidth=fullwidth, **main_markup)
1237        else:
1238            self.write_line(msg, **main_markup)
1239
1240    def short_test_summary(self) -> None:
1241        if not self.reportchars:
1242            return
1243
1244        def show_simple(lines: list[str], *, stat: str) -> None:
1245            failed = self.stats.get(stat, [])
1246            if not failed:
1247                return
1248            config = self.config
1249            for rep in failed:
1250                color = _color_for_type.get(stat, _color_for_type_default)
1251                line = _get_line_with_reprcrash_message(
1252                    config, rep, self._tw, {color: True}
1253                )
1254                lines.append(line)
1255
1256        def show_xfailed(lines: list[str]) -> None:
1257            xfailed = self.stats.get("xfailed", [])
1258            for rep in xfailed:
1259                verbose_word, verbose_markup = rep._get_verbose_word_with_markup(
1260                    self.config, {_color_for_type["warnings"]: True}
1261                )
1262                markup_word = self._tw.markup(verbose_word, **verbose_markup)
1263                nodeid = _get_node_id_with_markup(self._tw, self.config, rep)
1264                line = f"{markup_word} {nodeid}"
1265                reason = rep.wasxfail
1266                if reason:
1267                    line += " - " + str(reason)
1268
1269                lines.append(line)
1270
1271        def show_xpassed(lines: list[str]) -> None:
1272            xpassed = self.stats.get("xpassed", [])
1273            for rep in xpassed:
1274                verbose_word, verbose_markup = rep._get_verbose_word_with_markup(
1275                    self.config, {_color_for_type["warnings"]: True}
1276                )
1277                markup_word = self._tw.markup(verbose_word, **verbose_markup)
1278                nodeid = _get_node_id_with_markup(self._tw, self.config, rep)
1279                line = f"{markup_word} {nodeid}"
1280                reason = rep.wasxfail
1281                if reason:
1282                    line += " - " + str(reason)
1283                lines.append(line)
1284
1285        def show_skipped_folded(lines: list[str]) -> None:
1286            skipped: list[CollectReport] = self.stats.get("skipped", [])
1287            fskips = _folded_skips(self.startpath, skipped) if skipped else []
1288            if not fskips:
1289                return
1290            verbose_word, verbose_markup = skipped[0]._get_verbose_word_with_markup(
1291                self.config, {_color_for_type["warnings"]: True}
1292            )
1293            markup_word = self._tw.markup(verbose_word, **verbose_markup)
1294            prefix = "Skipped: "
1295            for num, fspath, lineno, reason in fskips:
1296                if reason.startswith(prefix):
1297                    reason = reason[len(prefix) :]
1298                if lineno is not None:
1299                    lines.append(f"{markup_word} [{num}] {fspath}:{lineno}: {reason}")
1300                else:
1301                    lines.append(f"{markup_word} [{num}] {fspath}: {reason}")
1302
1303        def show_skipped_unfolded(lines: list[str]) -> None:
1304            skipped: list[CollectReport] = self.stats.get("skipped", [])
1305
1306            for rep in skipped:
1307                assert rep.longrepr is not None
1308                assert isinstance(rep.longrepr, tuple), (rep, rep.longrepr)
1309                assert len(rep.longrepr) == 3, (rep, rep.longrepr)
1310
1311                verbose_word, verbose_markup = rep._get_verbose_word_with_markup(
1312                    self.config, {_color_for_type["warnings"]: True}
1313                )
1314                markup_word = self._tw.markup(verbose_word, **verbose_markup)
1315                nodeid = _get_node_id_with_markup(self._tw, self.config, rep)
1316                line = f"{markup_word} {nodeid}"
1317                reason = rep.longrepr[2]
1318                if reason:
1319                    line += " - " + str(reason)
1320                lines.append(line)
1321
1322        def show_skipped(lines: list[str]) -> None:
1323            if self.foldskipped:
1324                show_skipped_folded(lines)
1325            else:
1326                show_skipped_unfolded(lines)
1327
1328        REPORTCHAR_ACTIONS: Mapping[str, Callable[[list[str]], None]] = {
1329            "x": show_xfailed,
1330            "X": show_xpassed,
1331            "f": partial(show_simple, stat="failed"),
1332            "s": show_skipped,
1333            "p": partial(show_simple, stat="passed"),
1334            "E": partial(show_simple, stat="error"),
1335        }
1336
1337        lines: list[str] = []
1338        for char in self.reportchars:
1339            action = REPORTCHAR_ACTIONS.get(char)
1340            if action:  # skipping e.g. "P" (passed with output) here.
1341                action(lines)
1342
1343        if lines:
1344            self.write_sep("=", "short test summary info", cyan=True, bold=True)
1345            for line in lines:
1346                self.write_line(line)
1347
1348    def _get_main_color(self) -> tuple[str, list[str]]:
1349        if self._main_color is None or self._known_types is None or self._is_last_item:
1350            self._set_main_color()
1351            assert self._main_color
1352            assert self._known_types
1353        return self._main_color, self._known_types
1354
1355    def _determine_main_color(self, unknown_type_seen: bool) -> str:
1356        stats = self.stats
1357        if "failed" in stats or "error" in stats:
1358            main_color = "red"
1359        elif "warnings" in stats or "xpassed" in stats or unknown_type_seen:
1360            main_color = "yellow"
1361        elif "passed" in stats or not self._is_last_item:
1362            main_color = "green"
1363        else:
1364            main_color = "yellow"
1365        return main_color
1366
1367    def _set_main_color(self) -> None:
1368        unknown_types: list[str] = []
1369        for found_type in self.stats:
1370            if found_type:  # setup/teardown reports have an empty key, ignore them
1371                if found_type not in KNOWN_TYPES and found_type not in unknown_types:
1372                    unknown_types.append(found_type)
1373        self._known_types = list(KNOWN_TYPES) + unknown_types
1374        self._main_color = self._determine_main_color(bool(unknown_types))
1375
1376    def build_summary_stats_line(self) -> tuple[list[tuple[str, dict[str, bool]]], str]:
1377        """
1378        Build the parts used in the last summary stats line.
1379
1380        The summary stats line is the line shown at the end, "=== 12 passed, 2 errors in Xs===".
1381
1382        This function builds a list of the "parts" that make up for the text in that line, in
1383        the example above it would be::
1384
1385            [
1386                ("12 passed", {"green": True}),
1387                ("2 errors", {"red": True}
1388            ]
1389
1390        That last dict for each line is a "markup dictionary", used by TerminalWriter to
1391        color output.
1392
1393        The final color of the line is also determined by this function, and is the second
1394        element of the returned tuple.
1395        """
1396        if self.config.getoption("collectonly"):
1397            return self._build_collect_only_summary_stats_line()
1398        else:
1399            return self._build_normal_summary_stats_line()
1400
1401    def _get_reports_to_display(self, key: str) -> list[Any]:
1402        """Get test/collection reports for the given status key, such as `passed` or `error`."""
1403        reports = self.stats.get(key, [])
1404        return [x for x in reports if getattr(x, "count_towards_summary", True)]
1405
1406    def _build_normal_summary_stats_line(
1407        self,
1408    ) -> tuple[list[tuple[str, dict[str, bool]]], str]:
1409        main_color, known_types = self._get_main_color()
1410        parts = []
1411
1412        for key in known_types:
1413            reports = self._get_reports_to_display(key)
1414            if reports:
1415                count = len(reports)
1416                color = _color_for_type.get(key, _color_for_type_default)
1417                markup = {color: True, "bold": color == main_color}
1418                parts.append(("%d %s" % pluralize(count, key), markup))  # noqa: UP031
1419
1420        if not parts:
1421            parts = [("no tests ran", {_color_for_type_default: True})]
1422
1423        return parts, main_color
1424
1425    def _build_collect_only_summary_stats_line(
1426        self,
1427    ) -> tuple[list[tuple[str, dict[str, bool]]], str]:
1428        deselected = len(self._get_reports_to_display("deselected"))
1429        errors = len(self._get_reports_to_display("error"))
1430
1431        if self._numcollected == 0:
1432            parts = [("no tests collected", {"yellow": True})]
1433            main_color = "yellow"
1434
1435        elif deselected == 0:
1436            main_color = "green"
1437            collected_output = "%d %s collected" % pluralize(self._numcollected, "test")  # noqa: UP031
1438            parts = [(collected_output, {main_color: True})]
1439        else:
1440            all_tests_were_deselected = self._numcollected == deselected
1441            if all_tests_were_deselected:
1442                main_color = "yellow"
1443                collected_output = f"no tests collected ({deselected} deselected)"
1444            else:
1445                main_color = "green"
1446                selected = self._numcollected - deselected
1447                collected_output = f"{selected}/{self._numcollected} tests collected ({deselected} deselected)"
1448
1449            parts = [(collected_output, {main_color: True})]
1450
1451        if errors:
1452            main_color = _color_for_type["error"]
1453            parts += [("%d %s" % pluralize(errors, "error"), {main_color: True})]  # noqa: UP031
1454
1455        return parts, main_color
TerminalReporter(config: _pytest.config.Config, file: typing.TextIO | None = None)
372    def __init__(self, config: Config, file: TextIO | None = None) -> None:
373        import _pytest.config
374
375        self.config = config
376        self._numcollected = 0
377        self._session: Session | None = None
378        self._showfspath: bool | None = None
379
380        self.stats: dict[str, list[Any]] = {}
381        self._main_color: str | None = None
382        self._known_types: list[str] | None = None
383        self.startpath = config.invocation_params.dir
384        if file is None:
385            file = sys.stdout
386        self._tw = _pytest.config.create_terminal_writer(config, file)
387        self._screen_width = self._tw.fullwidth
388        self.currentfspath: None | Path | str | int = None
389        self.reportchars = getreportopt(config)
390        self.foldskipped = config.option.fold_skipped
391        self.hasmarkup = self._tw.hasmarkup
392        # isatty should be a method but was wrongly implemented as a boolean.
393        # We use CallableBool here to support both.
394        self.isatty = compat.CallableBool(file.isatty())
395        self._progress_nodeids_reported: set[str] = set()
396        self._timing_nodeids_reported: set[str] = set()
397        self._show_progress_info = self._determine_show_progress_info()
398        self._collect_report_last_write = timing.Instant()
399        self._already_displayed_warnings: int | None = None
400        self._keyboardinterrupt_memo: ExceptionRepr | None = None
config
stats: dict[str, list[typing.Any]]
startpath
currentfspath: None | pathlib.Path | str | int
reportchars
foldskipped
hasmarkup
isatty
verbosity: int
427    @property
428    def verbosity(self) -> int:
429        verbosity: int = self.config.option.verbose
430        return verbosity
showheader: bool
432    @property
433    def showheader(self) -> bool:
434        return self.verbosity >= 0
no_header: bool
436    @property
437    def no_header(self) -> bool:
438        return bool(self.config.option.no_header)
no_summary: bool
440    @property
441    def no_summary(self) -> bool:
442        return bool(self.config.option.no_summary)
showfspath: bool
444    @property
445    def showfspath(self) -> bool:
446        if self._showfspath is None:
447            return self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) >= 0
448        return self._showfspath
showlongtestinfo: bool
454    @property
455    def showlongtestinfo(self) -> bool:
456        return self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) > 0
def hasopt(self, char: str) -> bool:
458    def hasopt(self, char: str) -> bool:
459        char = {"xfailed": "x", "skipped": "s"}.get(char, char)
460        return char in self.reportchars
def write_fspath_result(self, nodeid: str, res: str, **markup: bool) -> None:
462    def write_fspath_result(self, nodeid: str, res: str, **markup: bool) -> None:
463        fspath = self.config.rootpath / nodeid.split("::")[0]
464        if self.currentfspath is None or fspath != self.currentfspath:
465            if self.currentfspath is not None and self._show_progress_info:
466                self._write_progress_information_filling_space()
467            self.currentfspath = fspath
468            relfspath = bestrelpath(self.startpath, fspath)
469            self._tw.line()
470            self._tw.write(relfspath + " ")
471        self._tw.write(res, flush=True, **markup)
def write_ensure_prefix(self, prefix: str, extra: str = '', **kwargs) -> None:
473    def write_ensure_prefix(self, prefix: str, extra: str = "", **kwargs) -> None:
474        if self.currentfspath != prefix:
475            self._tw.line()
476            self.currentfspath = prefix
477            self._tw.write(prefix)
478        if extra:
479            self._tw.write(extra, **kwargs)
480            self.currentfspath = -2
def ensure_newline(self) -> None:
482    def ensure_newline(self) -> None:
483        if self.currentfspath:
484            self._tw.line()
485            self.currentfspath = None
def wrap_write( self, content: str, *, flush: bool = False, margin: int = 8, line_sep: str = '\n', **markup: bool) -> None:
487    def wrap_write(
488        self,
489        content: str,
490        *,
491        flush: bool = False,
492        margin: int = 8,
493        line_sep: str = "\n",
494        **markup: bool,
495    ) -> None:
496        """Wrap message with margin for progress info."""
497        width_of_current_line = self._tw.width_of_current_line
498        wrapped = line_sep.join(
499            textwrap.wrap(
500                " " * width_of_current_line + content,
501                width=self._screen_width - margin,
502                drop_whitespace=True,
503                replace_whitespace=False,
504            ),
505        )
506        wrapped = wrapped[width_of_current_line:]
507        self._tw.write(wrapped, flush=flush, **markup)

Wrap message with margin for progress info.

def write(self, content: str, *, flush: bool = False, **markup: bool) -> None:
509    def write(self, content: str, *, flush: bool = False, **markup: bool) -> None:
510        self._tw.write(content, flush=flush, **markup)
def flush(self) -> None:
512    def flush(self) -> None:
513        self._tw.flush()
def write_line(self, line: str | bytes, **markup: bool) -> None:
515    def write_line(self, line: str | bytes, **markup: bool) -> None:
516        if not isinstance(line, str):
517            line = str(line, errors="replace")
518        self.ensure_newline()
519        self._tw.line(line, **markup)
def rewrite(self, line: str, **markup: bool) -> None:
521    def rewrite(self, line: str, **markup: bool) -> None:
522        """Rewinds the terminal cursor to the beginning and writes the given line.
523
524        :param erase:
525            If True, will also add spaces until the full terminal width to ensure
526            previous lines are properly erased.
527
528        The rest of the keyword arguments are markup instructions.
529        """
530        erase = markup.pop("erase", False)
531        if erase:
532            fill_count = self._tw.fullwidth - len(line) - 1
533            fill = " " * fill_count
534        else:
535            fill = ""
536        line = str(line)
537        self._tw.write("\r" + line + fill, **markup)

Rewinds the terminal cursor to the beginning and writes the given line.

Parameters
  • erase: If True, will also add spaces until the full terminal width to ensure previous lines are properly erased.

The rest of the keyword arguments are markup instructions.

def write_sep( self, sep: str, title: str | None = None, fullwidth: int | None = None, **markup: bool) -> None:
539    def write_sep(
540        self,
541        sep: str,
542        title: str | None = None,
543        fullwidth: int | None = None,
544        **markup: bool,
545    ) -> None:
546        self.ensure_newline()
547        self._tw.sep(sep, title, fullwidth, **markup)
def section(self, title: str, sep: str = '=', **kw: bool) -> None:
549    def section(self, title: str, sep: str = "=", **kw: bool) -> None:
550        self._tw.sep(sep, title, **kw)
def line(self, msg: str, **kw: bool) -> None:
552    def line(self, msg: str, **kw: bool) -> None:
553        self._tw.line(msg, **kw)
def pytest_internalerror(self, excrepr: _pytest._code.code.ExceptionRepr) -> bool:
561    def pytest_internalerror(self, excrepr: ExceptionRepr) -> bool:
562        for line in str(excrepr).split("\n"):
563            self.write_line("INTERNALERROR> " + line)
564        return True
def pytest_warning_recorded(self, warning_message: warnings.WarningMessage, nodeid: str) -> None:
566    def pytest_warning_recorded(
567        self,
568        warning_message: warnings.WarningMessage,
569        nodeid: str,
570    ) -> None:
571        from _pytest.warnings import warning_record_to_str
572
573        fslocation = warning_message.filename, warning_message.lineno
574        message = warning_record_to_str(warning_message)
575
576        warning_report = WarningReport(
577            fslocation=fslocation, message=message, nodeid=nodeid
578        )
579        self._add_stats("warnings", [warning_report])
def pytest_plugin_registered(self, plugin: object) -> None:
581    def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None:
582        if self.config.option.traceconfig:
583            msg = f"PLUGIN registered: {plugin}"
584            # XXX This event may happen during setup/teardown time
585            #     which unfortunately captures our output here
586            #     which garbles our output if we use self.write_line.
587            self.write_line(msg)
def pytest_deselected(self, items: Sequence[_pytest.nodes.Item]) -> None:
589    def pytest_deselected(self, items: Sequence[Item]) -> None:
590        self._add_stats("deselected", items)
def pytest_runtest_logstart(self, nodeid: str, location: tuple[str, int | None, str]) -> None:
592    def pytest_runtest_logstart(
593        self, nodeid: str, location: tuple[str, int | None, str]
594    ) -> None:
595        fspath, lineno, domain = location
596        # Ensure that the path is printed before the
597        # 1st test of a module starts running.
598        if self.showlongtestinfo:
599            line = self._locationline(nodeid, fspath, lineno, domain)
600            self.write_ensure_prefix(line, "")
601            self.flush()
602        elif self.showfspath:
603            self.write_fspath_result(nodeid, "")
604            self.flush()
def pytest_runtest_logreport(self, report: _pytest.reports.TestReport) -> None:
606    def pytest_runtest_logreport(self, report: TestReport) -> None:
607        self._tests_ran = True
608        rep = report
609
610        res = TestShortLogReport(
611            *self.config.hook.pytest_report_teststatus(report=rep, config=self.config)
612        )
613        category, letter, word = res.category, res.letter, res.word
614        if not isinstance(word, tuple):
615            markup = None
616        else:
617            word, markup = word
618        self._add_stats(category, [rep])
619        if not letter and not word:
620            # Probably passed setup/teardown.
621            return
622        if markup is None:
623            was_xfail = hasattr(report, "wasxfail")
624            if rep.passed and not was_xfail:
625                markup = {"green": True}
626            elif rep.passed and was_xfail:
627                markup = {"yellow": True}
628            elif rep.failed:
629                markup = {"red": True}
630            elif rep.skipped:
631                markup = {"yellow": True}
632            else:
633                markup = {}
634        self._progress_nodeids_reported.add(rep.nodeid)
635        if self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) <= 0:
636            self._tw.write(letter, **markup)
637            # When running in xdist, the logreport and logfinish of multiple
638            # items are interspersed, e.g. `logreport`, `logreport`,
639            # `logfinish`, `logfinish`. To avoid the "past edge" calculation
640            # from getting confused and overflowing (#7166), do the past edge
641            # printing here and not in logfinish, except for the 100% which
642            # should only be printed after all teardowns are finished.
643            if self._show_progress_info and not self._is_last_item:
644                self._write_progress_information_if_past_edge()
645        else:
646            line = self._locationline(rep.nodeid, *rep.location)
647            running_xdist = hasattr(rep, "node")
648            if not running_xdist:
649                self.write_ensure_prefix(line, word, **markup)
650                if rep.skipped or hasattr(report, "wasxfail"):
651                    reason = _get_raw_skip_reason(rep)
652                    if self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) < 2:
653                        available_width = (
654                            (self._tw.fullwidth - self._tw.width_of_current_line)
655                            - len(" [100%]")
656                            - 1
657                        )
658                        formatted_reason = _format_trimmed(
659                            " ({})", reason, available_width
660                        )
661                    else:
662                        formatted_reason = f" ({reason})"
663
664                    if reason and formatted_reason is not None:
665                        self.wrap_write(formatted_reason)
666                if self._show_progress_info:
667                    self._write_progress_information_filling_space()
668            else:
669                self.ensure_newline()
670                self._tw.write(f"[{rep.node.gateway.id}]")
671                if self._show_progress_info:
672                    self._tw.write(
673                        self._get_progress_information_message() + " ", cyan=True
674                    )
675                else:
676                    self._tw.write(" ")
677                self._tw.write(word, **markup)
678                self._tw.write(" " + line)
679                self.currentfspath = -2
680        self.flush()
@hookimpl(wrapper=True)
def pytest_runtestloop(self) -> Generator[None, object, object]:
687    @hookimpl(wrapper=True)
688    def pytest_runtestloop(self) -> Generator[None, object, object]:
689        result = yield
690
691        # Write the final/100% progress -- deferred until the loop is complete.
692        if (
693            self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) <= 0
694            and self._show_progress_info
695            and self._progress_nodeids_reported
696        ):
697            self._write_progress_information_filling_space()
698
699        return result
def pytest_collection(self) -> None:
772    def pytest_collection(self) -> None:
773        if self.isatty():
774            if self.config.option.verbose >= 0:
775                self.write("collecting ... ", flush=True, bold=True)
776        elif self.config.option.verbose >= 1:
777            self.write("collecting ... ", flush=True, bold=True)
def pytest_collectreport(self, report: _pytest.reports.CollectReport) -> None:
779    def pytest_collectreport(self, report: CollectReport) -> None:
780        if report.failed:
781            self._add_stats("error", [report])
782        elif report.skipped:
783            self._add_stats("skipped", [report])
784        items = [x for x in report.result if isinstance(x, Item)]
785        self._numcollected += len(items)
786        if self.isatty():
787            self.report_collect()
def report_collect(self, final: bool = False) -> None:
789    def report_collect(self, final: bool = False) -> None:
790        if self.config.option.verbose < 0:
791            return
792
793        if not final:
794            # Only write the "collecting" report every `REPORT_COLLECTING_RESOLUTION`.
795            if (
796                self._collect_report_last_write.elapsed().seconds
797                < REPORT_COLLECTING_RESOLUTION
798            ):
799                return
800            self._collect_report_last_write = timing.Instant()
801
802        errors = len(self.stats.get("error", []))
803        skipped = len(self.stats.get("skipped", []))
804        deselected = len(self.stats.get("deselected", []))
805        selected = self._numcollected - deselected
806        line = "collected " if final else "collecting "
807        line += (
808            str(self._numcollected) + " item" + ("" if self._numcollected == 1 else "s")
809        )
810        if errors:
811            line += f" / {errors} error{'s' if errors != 1 else ''}"
812        if deselected:
813            line += f" / {deselected} deselected"
814        if skipped:
815            line += f" / {skipped} skipped"
816        if self._numcollected > selected:
817            line += f" / {selected} selected"
818        if self.isatty():
819            self.rewrite(line, bold=True, erase=True)
820            if final:
821                self.write("\n")
822        else:
823            self.write_line(line)
@hookimpl(trylast=True)
def pytest_sessionstart(self, session: _pytest.main.Session) -> None:
825    @hookimpl(trylast=True)
826    def pytest_sessionstart(self, session: Session) -> None:
827        self._session = session
828        self._session_start = timing.Instant()
829        if not self.showheader:
830            return
831        self.write_sep("=", "test session starts", bold=True)
832        verinfo = platform.python_version()
833        if not self.no_header:
834            msg = f"platform {sys.platform} -- Python {verinfo}"
835            pypy_version_info = getattr(sys, "pypy_version_info", None)
836            if pypy_version_info:
837                verinfo = ".".join(map(str, pypy_version_info[:3]))
838                msg += f"[pypy-{verinfo}-{pypy_version_info[3]}]"
839            msg += f", pytest-{_pytest._version.version}, pluggy-{pluggy.__version__}"
840            if (
841                self.verbosity > 0
842                or self.config.option.debug
843                or getattr(self.config.option, "pastebin", None)
844            ):
845                msg += " -- " + str(sys.executable)
846            self.write_line(msg)
847            lines = self.config.hook.pytest_report_header(
848                config=self.config, start_path=self.startpath
849            )
850            self._write_report_lines_from_hooks(lines)
def pytest_report_header(self, config: _pytest.config.Config) -> list[str]:
862    def pytest_report_header(self, config: Config) -> list[str]:
863        result = [f"rootdir: {config.rootpath}"]
864
865        if config.inipath:
866            result.append("configfile: " + bestrelpath(config.rootpath, config.inipath))
867
868        if config.args_source == Config.ArgsSource.TESTPATHS:
869            testpaths: list[str] = config.getini("testpaths")
870            result.append("testpaths: {}".format(", ".join(testpaths)))
871
872        plugininfo = config.pluginmanager.list_plugin_distinfo()
873        if plugininfo:
874            result.append(
875                "plugins: {}".format(", ".join(_plugin_nameversions(plugininfo)))
876            )
877        return result
def pytest_collection_finish(self, session: _pytest.main.Session) -> None:
879    def pytest_collection_finish(self, session: Session) -> None:
880        self.report_collect(True)
881
882        lines = self.config.hook.pytest_report_collectionfinish(
883            config=self.config,
884            start_path=self.startpath,
885            items=session.items,
886        )
887        self._write_report_lines_from_hooks(lines)
888
889        if self.config.getoption("collectonly"):
890            if session.items:
891                if self.config.option.verbose > -1:
892                    self._tw.line("")
893                self._printcollecteditems(session.items)
894
895            failed = self.stats.get("failed")
896            if failed:
897                self._tw.sep("!", "collection failures")
898                for rep in failed:
899                    rep.toterminal(self._tw)
@hookimpl(wrapper=True)
def pytest_sessionfinish( self, session: _pytest.main.Session, exitstatus: int | _pytest.config.ExitCode) -> Generator[None]:
931    @hookimpl(wrapper=True)
932    def pytest_sessionfinish(
933        self, session: Session, exitstatus: int | ExitCode
934    ) -> Generator[None]:
935        result = yield
936        self._tw.line("")
937        summary_exit_codes = (
938            ExitCode.OK,
939            ExitCode.TESTS_FAILED,
940            ExitCode.INTERRUPTED,
941            ExitCode.USAGE_ERROR,
942            ExitCode.NO_TESTS_COLLECTED,
943        )
944        if exitstatus in summary_exit_codes and not self.no_summary:
945            self.config.hook.pytest_terminal_summary(
946                terminalreporter=self, exitstatus=exitstatus, config=self.config
947            )
948        if session.shouldfail:
949            self.write_sep("!", str(session.shouldfail), red=True)
950        if exitstatus == ExitCode.INTERRUPTED:
951            self._report_keyboardinterrupt()
952            self._keyboardinterrupt_memo = None
953        elif session.shouldstop:
954            self.write_sep("!", str(session.shouldstop), red=True)
955        self.summary_stats()
956        return result
@hookimpl(wrapper=True)
def pytest_terminal_summary(self) -> Generator[None]:
958    @hookimpl(wrapper=True)
959    def pytest_terminal_summary(self) -> Generator[None]:
960        self.summary_errors()
961        self.summary_failures()
962        self.summary_xfailures()
963        self.summary_warnings()
964        self.summary_passes()
965        self.summary_xpasses()
966        try:
967            return (yield)
968        finally:
969            self.short_test_summary()
970            # Display any extra warnings from teardown here (if any).
971            self.summary_warnings()
def pytest_keyboard_interrupt(self, excinfo: _pytest._code.code.ExceptionInfo[BaseException]) -> None:
973    def pytest_keyboard_interrupt(self, excinfo: ExceptionInfo[BaseException]) -> None:
974        self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
def pytest_unconfigure(self) -> None:
976    def pytest_unconfigure(self) -> None:
977        if self._keyboardinterrupt_memo is not None:
978            self._report_keyboardinterrupt()
def getreports(self, name: str):
1037    def getreports(self, name: str):
1038        return [x for x in self.stats.get(name, ()) if not hasattr(x, "_pdbshown")]
def summary_warnings(self) -> None:
1040    def summary_warnings(self) -> None:
1041        if self.hasopt("w"):
1042            all_warnings: list[WarningReport] | None = self.stats.get("warnings")
1043            if not all_warnings:
1044                return
1045
1046            final = self._already_displayed_warnings is not None
1047            if final:
1048                warning_reports = all_warnings[self._already_displayed_warnings :]
1049            else:
1050                warning_reports = all_warnings
1051            self._already_displayed_warnings = len(warning_reports)
1052            if not warning_reports:
1053                return
1054
1055            reports_grouped_by_message: dict[str, list[WarningReport]] = {}
1056            for wr in warning_reports:
1057                reports_grouped_by_message.setdefault(wr.message, []).append(wr)
1058
1059            def collapsed_location_report(reports: list[WarningReport]) -> str:
1060                locations = []
1061                for w in reports:
1062                    location = w.get_location(self.config)
1063                    if location:
1064                        locations.append(location)
1065
1066                if len(locations) < 10:
1067                    return "\n".join(map(str, locations))
1068
1069                counts_by_filename = Counter(
1070                    str(loc).split("::", 1)[0] for loc in locations
1071                )
1072                return "\n".join(
1073                    "{}: {} warning{}".format(k, v, "s" if v > 1 else "")
1074                    for k, v in counts_by_filename.items()
1075                )
1076
1077            title = "warnings summary (final)" if final else "warnings summary"
1078            self.write_sep("=", title, yellow=True, bold=False)
1079            for message, message_reports in reports_grouped_by_message.items():
1080                maybe_location = collapsed_location_report(message_reports)
1081                if maybe_location:
1082                    self._tw.line(maybe_location)
1083                    lines = message.splitlines()
1084                    indented = "\n".join("  " + x for x in lines)
1085                    message = indented.rstrip()
1086                else:
1087                    message = message.rstrip()
1088                self._tw.line(message)
1089                self._tw.line()
1090            self._tw.line(
1091                "-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html"
1092            )
def summary_passes(self) -> None:
1094    def summary_passes(self) -> None:
1095        self.summary_passes_combined("passed", "PASSES", "P")
def summary_xpasses(self) -> None:
1097    def summary_xpasses(self) -> None:
1098        self.summary_passes_combined("xpassed", "XPASSES", "X")
def summary_passes_combined(self, which_reports: str, sep_title: str, needed_opt: str) -> None:
1100    def summary_passes_combined(
1101        self, which_reports: str, sep_title: str, needed_opt: str
1102    ) -> None:
1103        if self.config.option.tbstyle != "no":
1104            if self.hasopt(needed_opt):
1105                reports: list[TestReport] = self.getreports(which_reports)
1106                if not reports:
1107                    return
1108                self.write_sep("=", sep_title)
1109                for rep in reports:
1110                    if rep.sections:
1111                        msg = self._getfailureheadline(rep)
1112                        self.write_sep("_", msg, green=True, bold=True)
1113                        self._outrep_summary(rep)
1114                    self._handle_teardown_sections(rep.nodeid)
def print_teardown_sections(self, rep: _pytest.reports.TestReport) -> None:
1128    def print_teardown_sections(self, rep: TestReport) -> None:
1129        showcapture = self.config.option.showcapture
1130        if showcapture == "no":
1131            return
1132        for secname, content in rep.sections:
1133            if showcapture != "all" and showcapture not in secname:
1134                continue
1135            if "teardown" in secname:
1136                self._tw.sep("-", secname)
1137                if content[-1:] == "\n":
1138                    content = content[:-1]
1139                self._tw.line(content)
def summary_failures(self) -> None:
1141    def summary_failures(self) -> None:
1142        style = self.config.option.tbstyle
1143        self.summary_failures_combined("failed", "FAILURES", style=style)
def summary_xfailures(self) -> None:
1145    def summary_xfailures(self) -> None:
1146        show_tb = self.config.option.xfail_tb
1147        style = self.config.option.tbstyle if show_tb else "no"
1148        self.summary_failures_combined("xfailed", "XFAILURES", style=style)
def summary_failures_combined( self, which_reports: str, sep_title: str, *, style: str, needed_opt: str | None = None) -> None:
1150    def summary_failures_combined(
1151        self,
1152        which_reports: str,
1153        sep_title: str,
1154        *,
1155        style: str,
1156        needed_opt: str | None = None,
1157    ) -> None:
1158        if style != "no":
1159            if not needed_opt or self.hasopt(needed_opt):
1160                reports: list[BaseReport] = self.getreports(which_reports)
1161                if not reports:
1162                    return
1163                self.write_sep("=", sep_title)
1164                if style == "line":
1165                    for rep in reports:
1166                        line = self._getcrashline(rep)
1167                        self.write_line(line)
1168                else:
1169                    for rep in reports:
1170                        msg = self._getfailureheadline(rep)
1171                        self.write_sep("_", msg, red=True, bold=True)
1172                        self._outrep_summary(rep)
1173                        self._handle_teardown_sections(rep.nodeid)
def summary_errors(self) -> None:
1175    def summary_errors(self) -> None:
1176        if self.config.option.tbstyle != "no":
1177            reports: list[BaseReport] = self.getreports("error")
1178            if not reports:
1179                return
1180            self.write_sep("=", "ERRORS")
1181            for rep in self.stats["error"]:
1182                msg = self._getfailureheadline(rep)
1183                if rep.when == "collect":
1184                    msg = "ERROR collecting " + msg
1185                else:
1186                    msg = f"ERROR at {rep.when} of {msg}"
1187                self.write_sep("_", msg, red=True, bold=True)
1188                self._outrep_summary(rep)
def summary_stats(self) -> None:
1203    def summary_stats(self) -> None:
1204        if self.verbosity < -1:
1205            return
1206
1207        session_duration = self._session_start.elapsed()
1208        (parts, main_color) = self.build_summary_stats_line()
1209        line_parts = []
1210
1211        display_sep = self.verbosity >= 0
1212        if display_sep:
1213            fullwidth = self._tw.fullwidth
1214        for text, markup in parts:
1215            with_markup = self._tw.markup(text, **markup)
1216            if display_sep:
1217                fullwidth += len(with_markup) - len(text)
1218            line_parts.append(with_markup)
1219        msg = ", ".join(line_parts)
1220
1221        main_markup = {main_color: True}
1222        duration = f" in {format_session_duration(session_duration.seconds)}"
1223        duration_with_markup = self._tw.markup(duration, **main_markup)
1224        if display_sep:
1225            fullwidth += len(duration_with_markup) - len(duration)
1226        msg += duration_with_markup
1227
1228        if display_sep:
1229            markup_for_end_sep = self._tw.markup("", **main_markup)
1230            if markup_for_end_sep.endswith("\x1b[0m"):
1231                markup_for_end_sep = markup_for_end_sep[:-4]
1232            fullwidth += len(markup_for_end_sep)
1233            msg += markup_for_end_sep
1234
1235        if display_sep:
1236            self.write_sep("=", msg, fullwidth=fullwidth, **main_markup)
1237        else:
1238            self.write_line(msg, **main_markup)
def short_test_summary(self) -> None:
1240    def short_test_summary(self) -> None:
1241        if not self.reportchars:
1242            return
1243
1244        def show_simple(lines: list[str], *, stat: str) -> None:
1245            failed = self.stats.get(stat, [])
1246            if not failed:
1247                return
1248            config = self.config
1249            for rep in failed:
1250                color = _color_for_type.get(stat, _color_for_type_default)
1251                line = _get_line_with_reprcrash_message(
1252                    config, rep, self._tw, {color: True}
1253                )
1254                lines.append(line)
1255
1256        def show_xfailed(lines: list[str]) -> None:
1257            xfailed = self.stats.get("xfailed", [])
1258            for rep in xfailed:
1259                verbose_word, verbose_markup = rep._get_verbose_word_with_markup(
1260                    self.config, {_color_for_type["warnings"]: True}
1261                )
1262                markup_word = self._tw.markup(verbose_word, **verbose_markup)
1263                nodeid = _get_node_id_with_markup(self._tw, self.config, rep)
1264                line = f"{markup_word} {nodeid}"
1265                reason = rep.wasxfail
1266                if reason:
1267                    line += " - " + str(reason)
1268
1269                lines.append(line)
1270
1271        def show_xpassed(lines: list[str]) -> None:
1272            xpassed = self.stats.get("xpassed", [])
1273            for rep in xpassed:
1274                verbose_word, verbose_markup = rep._get_verbose_word_with_markup(
1275                    self.config, {_color_for_type["warnings"]: True}
1276                )
1277                markup_word = self._tw.markup(verbose_word, **verbose_markup)
1278                nodeid = _get_node_id_with_markup(self._tw, self.config, rep)
1279                line = f"{markup_word} {nodeid}"
1280                reason = rep.wasxfail
1281                if reason:
1282                    line += " - " + str(reason)
1283                lines.append(line)
1284
1285        def show_skipped_folded(lines: list[str]) -> None:
1286            skipped: list[CollectReport] = self.stats.get("skipped", [])
1287            fskips = _folded_skips(self.startpath, skipped) if skipped else []
1288            if not fskips:
1289                return
1290            verbose_word, verbose_markup = skipped[0]._get_verbose_word_with_markup(
1291                self.config, {_color_for_type["warnings"]: True}
1292            )
1293            markup_word = self._tw.markup(verbose_word, **verbose_markup)
1294            prefix = "Skipped: "
1295            for num, fspath, lineno, reason in fskips:
1296                if reason.startswith(prefix):
1297                    reason = reason[len(prefix) :]
1298                if lineno is not None:
1299                    lines.append(f"{markup_word} [{num}] {fspath}:{lineno}: {reason}")
1300                else:
1301                    lines.append(f"{markup_word} [{num}] {fspath}: {reason}")
1302
1303        def show_skipped_unfolded(lines: list[str]) -> None:
1304            skipped: list[CollectReport] = self.stats.get("skipped", [])
1305
1306            for rep in skipped:
1307                assert rep.longrepr is not None
1308                assert isinstance(rep.longrepr, tuple), (rep, rep.longrepr)
1309                assert len(rep.longrepr) == 3, (rep, rep.longrepr)
1310
1311                verbose_word, verbose_markup = rep._get_verbose_word_with_markup(
1312                    self.config, {_color_for_type["warnings"]: True}
1313                )
1314                markup_word = self._tw.markup(verbose_word, **verbose_markup)
1315                nodeid = _get_node_id_with_markup(self._tw, self.config, rep)
1316                line = f"{markup_word} {nodeid}"
1317                reason = rep.longrepr[2]
1318                if reason:
1319                    line += " - " + str(reason)
1320                lines.append(line)
1321
1322        def show_skipped(lines: list[str]) -> None:
1323            if self.foldskipped:
1324                show_skipped_folded(lines)
1325            else:
1326                show_skipped_unfolded(lines)
1327
1328        REPORTCHAR_ACTIONS: Mapping[str, Callable[[list[str]], None]] = {
1329            "x": show_xfailed,
1330            "X": show_xpassed,
1331            "f": partial(show_simple, stat="failed"),
1332            "s": show_skipped,
1333            "p": partial(show_simple, stat="passed"),
1334            "E": partial(show_simple, stat="error"),
1335        }
1336
1337        lines: list[str] = []
1338        for char in self.reportchars:
1339            action = REPORTCHAR_ACTIONS.get(char)
1340            if action:  # skipping e.g. "P" (passed with output) here.
1341                action(lines)
1342
1343        if lines:
1344            self.write_sep("=", "short test summary info", cyan=True, bold=True)
1345            for line in lines:
1346                self.write_line(line)
def build_summary_stats_line(self) -> tuple[list[tuple[str, dict[str, bool]]], str]:
1376    def build_summary_stats_line(self) -> tuple[list[tuple[str, dict[str, bool]]], str]:
1377        """
1378        Build the parts used in the last summary stats line.
1379
1380        The summary stats line is the line shown at the end, "=== 12 passed, 2 errors in Xs===".
1381
1382        This function builds a list of the "parts" that make up for the text in that line, in
1383        the example above it would be::
1384
1385            [
1386                ("12 passed", {"green": True}),
1387                ("2 errors", {"red": True}
1388            ]
1389
1390        That last dict for each line is a "markup dictionary", used by TerminalWriter to
1391        color output.
1392
1393        The final color of the line is also determined by this function, and is the second
1394        element of the returned tuple.
1395        """
1396        if self.config.getoption("collectonly"):
1397            return self._build_collect_only_summary_stats_line()
1398        else:
1399            return self._build_normal_summary_stats_line()

Build the parts used in the last summary stats line.

The summary stats line is the line shown at the end, "=== 12 passed, 2 errors in Xs===".

This function builds a list of the "parts" that make up for the text in that line, in the example above it would be::

[
    ("12 passed", {"green": True}),
    ("2 errors", {"red": True}
]

That last dict for each line is a "markup dictionary", used by TerminalWriter to color output.

The final color of the line is also determined by this function, and is the second element of the returned tuple.

@final
class TestReport(_pytest.reports.BaseReport):
255@final
256class TestReport(BaseReport):
257    """Basic test report object (also used for setup and teardown calls if
258    they fail).
259
260    Reports can contain arbitrary extra attributes.
261    """
262
263    __test__ = False
264
265    # Defined by skipping plugin.
266    # xfail reason if xfailed, otherwise not defined. Use hasattr to distinguish.
267    wasxfail: str
268
269    def __init__(
270        self,
271        nodeid: str,
272        location: tuple[str, int | None, str],
273        keywords: Mapping[str, Any],
274        outcome: Literal["passed", "failed", "skipped"],
275        longrepr: None
276        | ExceptionInfo[BaseException]
277        | tuple[str, int, str]
278        | str
279        | TerminalRepr,
280        when: Literal["setup", "call", "teardown"],
281        sections: Iterable[tuple[str, str]] = (),
282        duration: float = 0,
283        start: float = 0,
284        stop: float = 0,
285        user_properties: Iterable[tuple[str, object]] | None = None,
286        **extra,
287    ) -> None:
288        #: Normalized collection nodeid.
289        self.nodeid = nodeid
290
291        #: A (filesystempath, lineno, domaininfo) tuple indicating the
292        #: actual location of a test item - it might be different from the
293        #: collected one e.g. if a method is inherited from a different module.
294        #: The filesystempath may be relative to ``config.rootdir``.
295        #: The line number is 0-based.
296        self.location: tuple[str, int | None, str] = location
297
298        #: A name -> value dictionary containing all keywords and
299        #: markers associated with a test invocation.
300        self.keywords: Mapping[str, Any] = keywords
301
302        #: Test outcome, always one of "passed", "failed", "skipped".
303        self.outcome = outcome
304
305        #: None or a failure representation.
306        self.longrepr = longrepr
307
308        #: One of 'setup', 'call', 'teardown' to indicate runtest phase.
309        self.when: Literal["setup", "call", "teardown"] = when
310
311        #: User properties is a list of tuples (name, value) that holds user
312        #: defined properties of the test.
313        self.user_properties = list(user_properties or [])
314
315        #: Tuples of str ``(heading, content)`` with extra information
316        #: for the test report. Used by pytest to add text captured
317        #: from ``stdout``, ``stderr``, and intercepted logging events. May
318        #: be used by other plugins to add arbitrary information to reports.
319        self.sections = list(sections)
320
321        #: Time it took to run just the test.
322        self.duration: float = duration
323
324        #: The system time when the call started, in seconds since the epoch.
325        self.start: float = start
326        #: The system time when the call ended, in seconds since the epoch.
327        self.stop: float = stop
328
329        self.__dict__.update(extra)
330
331    def __repr__(self) -> str:
332        return f"<{self.__class__.__name__} {self.nodeid!r} when={self.when!r} outcome={self.outcome!r}>"
333
334    @classmethod
335    def from_item_and_call(cls, item: Item, call: CallInfo[None]) -> TestReport:
336        """Create and fill a TestReport with standard item and call info.
337
338        :param item: The item.
339        :param call: The call info.
340        """
341        when = call.when
342        # Remove "collect" from the Literal type -- only for collection calls.
343        assert when != "collect"
344        duration = call.duration
345        start = call.start
346        stop = call.stop
347        keywords = {x: 1 for x in item.keywords}
348        excinfo = call.excinfo
349        sections = []
350        if not call.excinfo:
351            outcome: Literal["passed", "failed", "skipped"] = "passed"
352            longrepr: (
353                None
354                | ExceptionInfo[BaseException]
355                | tuple[str, int, str]
356                | str
357                | TerminalRepr
358            ) = None
359        else:
360            if not isinstance(excinfo, ExceptionInfo):
361                outcome = "failed"
362                longrepr = excinfo
363            elif isinstance(excinfo.value, skip.Exception):
364                outcome = "skipped"
365                r = excinfo._getreprcrash()
366                assert r is not None, (
367                    "There should always be a traceback entry for skipping a test."
368                )
369                if excinfo.value._use_item_location:
370                    path, line = item.reportinfo()[:2]
371                    assert line is not None
372                    longrepr = os.fspath(path), line + 1, r.message
373                else:
374                    longrepr = (str(r.path), r.lineno, r.message)
375            else:
376                outcome = "failed"
377                if call.when == "call":
378                    longrepr = item.repr_failure(excinfo)
379                else:  # exception in setup or teardown
380                    longrepr = item._repr_failure_py(
381                        excinfo, style=item.config.getoption("tbstyle", "auto")
382                    )
383        for rwhen, key, content in item._report_sections:
384            sections.append((f"Captured {key} {rwhen}", content))
385        return cls(
386            item.nodeid,
387            item.location,
388            keywords,
389            outcome,
390            longrepr,
391            when,
392            sections,
393            duration,
394            start,
395            stop,
396            user_properties=item.user_properties,
397        )

Basic test report object (also used for setup and teardown calls if they fail).

Reports can contain arbitrary extra attributes.

TestReport( nodeid: str, location: tuple[str, int | None, str], keywords: Mapping[str, typing.Any], outcome: Literal['passed', 'failed', 'skipped'], longrepr: Union[NoneType, _pytest._code.code.ExceptionInfo[BaseException], tuple[str, int, str], str, _pytest._code.code.TerminalRepr], when: Literal['setup', 'call', 'teardown'], sections: Iterable[tuple[str, str]] = (), duration: float = 0, start: float = 0, stop: float = 0, user_properties: Iterable[tuple[str, object]] | None = None, **extra)
269    def __init__(
270        self,
271        nodeid: str,
272        location: tuple[str, int | None, str],
273        keywords: Mapping[str, Any],
274        outcome: Literal["passed", "failed", "skipped"],
275        longrepr: None
276        | ExceptionInfo[BaseException]
277        | tuple[str, int, str]
278        | str
279        | TerminalRepr,
280        when: Literal["setup", "call", "teardown"],
281        sections: Iterable[tuple[str, str]] = (),
282        duration: float = 0,
283        start: float = 0,
284        stop: float = 0,
285        user_properties: Iterable[tuple[str, object]] | None = None,
286        **extra,
287    ) -> None:
288        #: Normalized collection nodeid.
289        self.nodeid = nodeid
290
291        #: A (filesystempath, lineno, domaininfo) tuple indicating the
292        #: actual location of a test item - it might be different from the
293        #: collected one e.g. if a method is inherited from a different module.
294        #: The filesystempath may be relative to ``config.rootdir``.
295        #: The line number is 0-based.
296        self.location: tuple[str, int | None, str] = location
297
298        #: A name -> value dictionary containing all keywords and
299        #: markers associated with a test invocation.
300        self.keywords: Mapping[str, Any] = keywords
301
302        #: Test outcome, always one of "passed", "failed", "skipped".
303        self.outcome = outcome
304
305        #: None or a failure representation.
306        self.longrepr = longrepr
307
308        #: One of 'setup', 'call', 'teardown' to indicate runtest phase.
309        self.when: Literal["setup", "call", "teardown"] = when
310
311        #: User properties is a list of tuples (name, value) that holds user
312        #: defined properties of the test.
313        self.user_properties = list(user_properties or [])
314
315        #: Tuples of str ``(heading, content)`` with extra information
316        #: for the test report. Used by pytest to add text captured
317        #: from ``stdout``, ``stderr``, and intercepted logging events. May
318        #: be used by other plugins to add arbitrary information to reports.
319        self.sections = list(sections)
320
321        #: Time it took to run just the test.
322        self.duration: float = duration
323
324        #: The system time when the call started, in seconds since the epoch.
325        self.start: float = start
326        #: The system time when the call ended, in seconds since the epoch.
327        self.stop: float = stop
328
329        self.__dict__.update(extra)
wasxfail: str
nodeid
location: tuple[str, int | None, str]
keywords: Mapping[str, typing.Any]
outcome
longrepr
when: Literal['setup', 'call', 'teardown']
user_properties
sections
duration: float
start: float
stop: float
@classmethod
def from_item_and_call( cls, item: _pytest.nodes.Item, call: _pytest.runner.CallInfo[NoneType]) -> _pytest.reports.TestReport:
334    @classmethod
335    def from_item_and_call(cls, item: Item, call: CallInfo[None]) -> TestReport:
336        """Create and fill a TestReport with standard item and call info.
337
338        :param item: The item.
339        :param call: The call info.
340        """
341        when = call.when
342        # Remove "collect" from the Literal type -- only for collection calls.
343        assert when != "collect"
344        duration = call.duration
345        start = call.start
346        stop = call.stop
347        keywords = {x: 1 for x in item.keywords}
348        excinfo = call.excinfo
349        sections = []
350        if not call.excinfo:
351            outcome: Literal["passed", "failed", "skipped"] = "passed"
352            longrepr: (
353                None
354                | ExceptionInfo[BaseException]
355                | tuple[str, int, str]
356                | str
357                | TerminalRepr
358            ) = None
359        else:
360            if not isinstance(excinfo, ExceptionInfo):
361                outcome = "failed"
362                longrepr = excinfo
363            elif isinstance(excinfo.value, skip.Exception):
364                outcome = "skipped"
365                r = excinfo._getreprcrash()
366                assert r is not None, (
367                    "There should always be a traceback entry for skipping a test."
368                )
369                if excinfo.value._use_item_location:
370                    path, line = item.reportinfo()[:2]
371                    assert line is not None
372                    longrepr = os.fspath(path), line + 1, r.message
373                else:
374                    longrepr = (str(r.path), r.lineno, r.message)
375            else:
376                outcome = "failed"
377                if call.when == "call":
378                    longrepr = item.repr_failure(excinfo)
379                else:  # exception in setup or teardown
380                    longrepr = item._repr_failure_py(
381                        excinfo, style=item.config.getoption("tbstyle", "auto")
382                    )
383        for rwhen, key, content in item._report_sections:
384            sections.append((f"Captured {key} {rwhen}", content))
385        return cls(
386            item.nodeid,
387            item.location,
388            keywords,
389            outcome,
390            longrepr,
391            when,
392            sections,
393            duration,
394            start,
395            stop,
396            user_properties=item.user_properties,
397        )

Create and fill a TestReport with standard item and call info.

Parameters
  • item: The item.
  • call: The call info.
class TestShortLogReport(typing.NamedTuple):
115class TestShortLogReport(NamedTuple):
116    """Used to store the test status result category, shortletter and verbose word.
117    For example ``"rerun", "R", ("RERUN", {"yellow": True})``.
118
119    :ivar category:
120        The class of result, for example ``“passed”``, ``“skipped”``, ``“error”``, or the empty string.
121
122    :ivar letter:
123        The short letter shown as testing progresses, for example ``"."``, ``"s"``, ``"E"``, or the empty string.
124
125    :ivar word:
126        Verbose word is shown as testing progresses in verbose mode, for example ``"PASSED"``, ``"SKIPPED"``,
127        ``"ERROR"``, or the empty string.
128    """
129
130    category: str
131    letter: str
132    word: str | tuple[str, Mapping[str, bool]]

Used to store the test status result category, shortletter and verbose word. For example "rerun", "R", ("RERUN", {"yellow": True}).

:ivar category: The class of result, for example “passed”, “skipped”, “error”, or the empty string.

:ivar letter: The short letter shown as testing progresses, for example ".", "s", "E", or the empty string.

:ivar word: Verbose word is shown as testing progresses in verbose mode, for example "PASSED", "SKIPPED", "ERROR", or the empty string.

TestShortLogReport( category: str, letter: str, word: ForwardRef('str | tuple[str, Mapping[str, bool]]'))

Create new instance of TestShortLogReport(category, letter, word)

category: str

Alias for field number 0

letter: str

Alias for field number 1

word: str | tuple[str, Mapping[str, bool]]

Alias for field number 2

@final
class Testdir:
 42@final
 43class Testdir:
 44    """
 45    Similar to :class:`Pytester`, but this class works with legacy legacy_path objects instead.
 46
 47    All methods just forward to an internal :class:`Pytester` instance, converting results
 48    to `legacy_path` objects as necessary.
 49    """
 50
 51    __test__ = False
 52
 53    CLOSE_STDIN: Final = Pytester.CLOSE_STDIN
 54    TimeoutExpired: Final = Pytester.TimeoutExpired
 55
 56    def __init__(self, pytester: Pytester, *, _ispytest: bool = False) -> None:
 57        check_ispytest(_ispytest)
 58        self._pytester = pytester
 59
 60    @property
 61    def tmpdir(self) -> LEGACY_PATH:
 62        """Temporary directory where tests are executed."""
 63        return legacy_path(self._pytester.path)
 64
 65    @property
 66    def test_tmproot(self) -> LEGACY_PATH:
 67        return legacy_path(self._pytester._test_tmproot)
 68
 69    @property
 70    def request(self):
 71        return self._pytester._request
 72
 73    @property
 74    def plugins(self):
 75        return self._pytester.plugins
 76
 77    @plugins.setter
 78    def plugins(self, plugins):
 79        self._pytester.plugins = plugins
 80
 81    @property
 82    def monkeypatch(self) -> MonkeyPatch:
 83        return self._pytester._monkeypatch
 84
 85    def make_hook_recorder(self, pluginmanager) -> HookRecorder:
 86        """See :meth:`Pytester.make_hook_recorder`."""
 87        return self._pytester.make_hook_recorder(pluginmanager)
 88
 89    def chdir(self) -> None:
 90        """See :meth:`Pytester.chdir`."""
 91        return self._pytester.chdir()
 92
 93    def finalize(self) -> None:
 94        return self._pytester._finalize()
 95
 96    def makefile(self, ext, *args, **kwargs) -> LEGACY_PATH:
 97        """See :meth:`Pytester.makefile`."""
 98        if ext and not ext.startswith("."):
 99            # pytester.makefile is going to throw a ValueError in a way that
100            # testdir.makefile did not, because
101            # pathlib.Path is stricter suffixes than py.path
102            # This ext arguments is likely user error, but since testdir has
103            # allowed this, we will prepend "." as a workaround to avoid breaking
104            # testdir usage that worked before
105            ext = "." + ext
106        return legacy_path(self._pytester.makefile(ext, *args, **kwargs))
107
108    def makeconftest(self, source) -> LEGACY_PATH:
109        """See :meth:`Pytester.makeconftest`."""
110        return legacy_path(self._pytester.makeconftest(source))
111
112    def makeini(self, source) -> LEGACY_PATH:
113        """See :meth:`Pytester.makeini`."""
114        return legacy_path(self._pytester.makeini(source))
115
116    def getinicfg(self, source: str) -> SectionWrapper:
117        """See :meth:`Pytester.getinicfg`."""
118        return self._pytester.getinicfg(source)
119
120    def makepyprojecttoml(self, source) -> LEGACY_PATH:
121        """See :meth:`Pytester.makepyprojecttoml`."""
122        return legacy_path(self._pytester.makepyprojecttoml(source))
123
124    def makepyfile(self, *args, **kwargs) -> LEGACY_PATH:
125        """See :meth:`Pytester.makepyfile`."""
126        return legacy_path(self._pytester.makepyfile(*args, **kwargs))
127
128    def maketxtfile(self, *args, **kwargs) -> LEGACY_PATH:
129        """See :meth:`Pytester.maketxtfile`."""
130        return legacy_path(self._pytester.maketxtfile(*args, **kwargs))
131
132    def syspathinsert(self, path=None) -> None:
133        """See :meth:`Pytester.syspathinsert`."""
134        return self._pytester.syspathinsert(path)
135
136    def mkdir(self, name) -> LEGACY_PATH:
137        """See :meth:`Pytester.mkdir`."""
138        return legacy_path(self._pytester.mkdir(name))
139
140    def mkpydir(self, name) -> LEGACY_PATH:
141        """See :meth:`Pytester.mkpydir`."""
142        return legacy_path(self._pytester.mkpydir(name))
143
144    def copy_example(self, name=None) -> LEGACY_PATH:
145        """See :meth:`Pytester.copy_example`."""
146        return legacy_path(self._pytester.copy_example(name))
147
148    def getnode(self, config: Config, arg) -> Item | Collector | None:
149        """See :meth:`Pytester.getnode`."""
150        return self._pytester.getnode(config, arg)
151
152    def getpathnode(self, path):
153        """See :meth:`Pytester.getpathnode`."""
154        return self._pytester.getpathnode(path)
155
156    def genitems(self, colitems: list[Item | Collector]) -> list[Item]:
157        """See :meth:`Pytester.genitems`."""
158        return self._pytester.genitems(colitems)
159
160    def runitem(self, source):
161        """See :meth:`Pytester.runitem`."""
162        return self._pytester.runitem(source)
163
164    def inline_runsource(self, source, *cmdlineargs):
165        """See :meth:`Pytester.inline_runsource`."""
166        return self._pytester.inline_runsource(source, *cmdlineargs)
167
168    def inline_genitems(self, *args):
169        """See :meth:`Pytester.inline_genitems`."""
170        return self._pytester.inline_genitems(*args)
171
172    def inline_run(self, *args, plugins=(), no_reraise_ctrlc: bool = False):
173        """See :meth:`Pytester.inline_run`."""
174        return self._pytester.inline_run(
175            *args, plugins=plugins, no_reraise_ctrlc=no_reraise_ctrlc
176        )
177
178    def runpytest_inprocess(self, *args, **kwargs) -> RunResult:
179        """See :meth:`Pytester.runpytest_inprocess`."""
180        return self._pytester.runpytest_inprocess(*args, **kwargs)
181
182    def runpytest(self, *args, **kwargs) -> RunResult:
183        """See :meth:`Pytester.runpytest`."""
184        return self._pytester.runpytest(*args, **kwargs)
185
186    def parseconfig(self, *args) -> Config:
187        """See :meth:`Pytester.parseconfig`."""
188        return self._pytester.parseconfig(*args)
189
190    def parseconfigure(self, *args) -> Config:
191        """See :meth:`Pytester.parseconfigure`."""
192        return self._pytester.parseconfigure(*args)
193
194    def getitem(self, source, funcname="test_func"):
195        """See :meth:`Pytester.getitem`."""
196        return self._pytester.getitem(source, funcname)
197
198    def getitems(self, source):
199        """See :meth:`Pytester.getitems`."""
200        return self._pytester.getitems(source)
201
202    def getmodulecol(self, source, configargs=(), withinit=False):
203        """See :meth:`Pytester.getmodulecol`."""
204        return self._pytester.getmodulecol(
205            source, configargs=configargs, withinit=withinit
206        )
207
208    def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None:
209        """See :meth:`Pytester.collect_by_name`."""
210        return self._pytester.collect_by_name(modcol, name)
211
212    def popen(
213        self,
214        cmdargs,
215        stdout=subprocess.PIPE,
216        stderr=subprocess.PIPE,
217        stdin=CLOSE_STDIN,
218        **kw,
219    ):
220        """See :meth:`Pytester.popen`."""
221        return self._pytester.popen(cmdargs, stdout, stderr, stdin, **kw)
222
223    def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> RunResult:
224        """See :meth:`Pytester.run`."""
225        return self._pytester.run(*cmdargs, timeout=timeout, stdin=stdin)
226
227    def runpython(self, script) -> RunResult:
228        """See :meth:`Pytester.runpython`."""
229        return self._pytester.runpython(script)
230
231    def runpython_c(self, command):
232        """See :meth:`Pytester.runpython_c`."""
233        return self._pytester.runpython_c(command)
234
235    def runpytest_subprocess(self, *args, timeout=None) -> RunResult:
236        """See :meth:`Pytester.runpytest_subprocess`."""
237        return self._pytester.runpytest_subprocess(*args, timeout=timeout)
238
239    def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn:
240        """See :meth:`Pytester.spawn_pytest`."""
241        return self._pytester.spawn_pytest(string, expect_timeout=expect_timeout)
242
243    def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn:
244        """See :meth:`Pytester.spawn`."""
245        return self._pytester.spawn(cmd, expect_timeout=expect_timeout)
246
247    def __repr__(self) -> str:
248        return f"<Testdir {self.tmpdir!r}>"
249
250    def __str__(self) -> str:
251        return str(self.tmpdir)

Similar to Pytester, but this class works with legacy legacy_path objects instead.

All methods just forward to an internal Pytester instance, converting results to legacy_path objects as necessary.

Testdir(pytester: _pytest.pytester.Pytester, *, _ispytest: bool = False)
56    def __init__(self, pytester: Pytester, *, _ispytest: bool = False) -> None:
57        check_ispytest(_ispytest)
58        self._pytester = pytester
CLOSE_STDIN: Final = <NotSetType.token: 0>
tmpdir: _pytest._py.path.LocalPath
60    @property
61    def tmpdir(self) -> LEGACY_PATH:
62        """Temporary directory where tests are executed."""
63        return legacy_path(self._pytester.path)

Temporary directory where tests are executed.

test_tmproot: _pytest._py.path.LocalPath
65    @property
66    def test_tmproot(self) -> LEGACY_PATH:
67        return legacy_path(self._pytester._test_tmproot)
request
69    @property
70    def request(self):
71        return self._pytester._request
plugins
73    @property
74    def plugins(self):
75        return self._pytester.plugins
monkeypatch: _pytest.monkeypatch.MonkeyPatch
81    @property
82    def monkeypatch(self) -> MonkeyPatch:
83        return self._pytester._monkeypatch
def make_hook_recorder(self, pluginmanager) -> _pytest.pytester.HookRecorder:
85    def make_hook_recorder(self, pluginmanager) -> HookRecorder:
86        """See :meth:`Pytester.make_hook_recorder`."""
87        return self._pytester.make_hook_recorder(pluginmanager)
def chdir(self) -> None:
89    def chdir(self) -> None:
90        """See :meth:`Pytester.chdir`."""
91        return self._pytester.chdir()
def finalize(self) -> None:
93    def finalize(self) -> None:
94        return self._pytester._finalize()
def makefile(self, ext, *args, **kwargs) -> _pytest._py.path.LocalPath:
 96    def makefile(self, ext, *args, **kwargs) -> LEGACY_PATH:
 97        """See :meth:`Pytester.makefile`."""
 98        if ext and not ext.startswith("."):
 99            # pytester.makefile is going to throw a ValueError in a way that
100            # testdir.makefile did not, because
101            # pathlib.Path is stricter suffixes than py.path
102            # This ext arguments is likely user error, but since testdir has
103            # allowed this, we will prepend "." as a workaround to avoid breaking
104            # testdir usage that worked before
105            ext = "." + ext
106        return legacy_path(self._pytester.makefile(ext, *args, **kwargs))
def makeconftest(self, source) -> _pytest._py.path.LocalPath:
108    def makeconftest(self, source) -> LEGACY_PATH:
109        """See :meth:`Pytester.makeconftest`."""
110        return legacy_path(self._pytester.makeconftest(source))
def makeini(self, source) -> _pytest._py.path.LocalPath:
112    def makeini(self, source) -> LEGACY_PATH:
113        """See :meth:`Pytester.makeini`."""
114        return legacy_path(self._pytester.makeini(source))
def getinicfg(self, source: str) -> iniconfig.SectionWrapper:
116    def getinicfg(self, source: str) -> SectionWrapper:
117        """See :meth:`Pytester.getinicfg`."""
118        return self._pytester.getinicfg(source)
def makepyprojecttoml(self, source) -> _pytest._py.path.LocalPath:
120    def makepyprojecttoml(self, source) -> LEGACY_PATH:
121        """See :meth:`Pytester.makepyprojecttoml`."""
122        return legacy_path(self._pytester.makepyprojecttoml(source))
def makepyfile(self, *args, **kwargs) -> _pytest._py.path.LocalPath:
124    def makepyfile(self, *args, **kwargs) -> LEGACY_PATH:
125        """See :meth:`Pytester.makepyfile`."""
126        return legacy_path(self._pytester.makepyfile(*args, **kwargs))
def maketxtfile(self, *args, **kwargs) -> _pytest._py.path.LocalPath:
128    def maketxtfile(self, *args, **kwargs) -> LEGACY_PATH:
129        """See :meth:`Pytester.maketxtfile`."""
130        return legacy_path(self._pytester.maketxtfile(*args, **kwargs))
def syspathinsert(self, path=None) -> None:
132    def syspathinsert(self, path=None) -> None:
133        """See :meth:`Pytester.syspathinsert`."""
134        return self._pytester.syspathinsert(path)
def mkdir(self, name) -> _pytest._py.path.LocalPath:
136    def mkdir(self, name) -> LEGACY_PATH:
137        """See :meth:`Pytester.mkdir`."""
138        return legacy_path(self._pytester.mkdir(name))
def mkpydir(self, name) -> _pytest._py.path.LocalPath:
140    def mkpydir(self, name) -> LEGACY_PATH:
141        """See :meth:`Pytester.mkpydir`."""
142        return legacy_path(self._pytester.mkpydir(name))
def copy_example(self, name=None) -> _pytest._py.path.LocalPath:
144    def copy_example(self, name=None) -> LEGACY_PATH:
145        """See :meth:`Pytester.copy_example`."""
146        return legacy_path(self._pytester.copy_example(name))
def getnode( self, config: _pytest.config.Config, arg) -> _pytest.nodes.Item | _pytest.nodes.Collector | None:
148    def getnode(self, config: Config, arg) -> Item | Collector | None:
149        """See :meth:`Pytester.getnode`."""
150        return self._pytester.getnode(config, arg)
def getpathnode(self, path):
152    def getpathnode(self, path):
153        """See :meth:`Pytester.getpathnode`."""
154        return self._pytester.getpathnode(path)
def genitems( self, colitems: list[_pytest.nodes.Item | _pytest.nodes.Collector]) -> list[_pytest.nodes.Item]:
156    def genitems(self, colitems: list[Item | Collector]) -> list[Item]:
157        """See :meth:`Pytester.genitems`."""
158        return self._pytester.genitems(colitems)
def runitem(self, source):
160    def runitem(self, source):
161        """See :meth:`Pytester.runitem`."""
162        return self._pytester.runitem(source)
def inline_runsource(self, source, *cmdlineargs):
164    def inline_runsource(self, source, *cmdlineargs):
165        """See :meth:`Pytester.inline_runsource`."""
166        return self._pytester.inline_runsource(source, *cmdlineargs)
def inline_genitems(self, *args):
168    def inline_genitems(self, *args):
169        """See :meth:`Pytester.inline_genitems`."""
170        return self._pytester.inline_genitems(*args)
def inline_run(self, *args, plugins=(), no_reraise_ctrlc: bool = False):
172    def inline_run(self, *args, plugins=(), no_reraise_ctrlc: bool = False):
173        """See :meth:`Pytester.inline_run`."""
174        return self._pytester.inline_run(
175            *args, plugins=plugins, no_reraise_ctrlc=no_reraise_ctrlc
176        )
def runpytest_inprocess(self, *args, **kwargs) -> _pytest.pytester.RunResult:
178    def runpytest_inprocess(self, *args, **kwargs) -> RunResult:
179        """See :meth:`Pytester.runpytest_inprocess`."""
180        return self._pytester.runpytest_inprocess(*args, **kwargs)
def runpytest(self, *args, **kwargs) -> _pytest.pytester.RunResult:
182    def runpytest(self, *args, **kwargs) -> RunResult:
183        """See :meth:`Pytester.runpytest`."""
184        return self._pytester.runpytest(*args, **kwargs)
def parseconfig(self, *args) -> _pytest.config.Config:
186    def parseconfig(self, *args) -> Config:
187        """See :meth:`Pytester.parseconfig`."""
188        return self._pytester.parseconfig(*args)
def parseconfigure(self, *args) -> _pytest.config.Config:
190    def parseconfigure(self, *args) -> Config:
191        """See :meth:`Pytester.parseconfigure`."""
192        return self._pytester.parseconfigure(*args)
def getitem(self, source, funcname='test_func'):
194    def getitem(self, source, funcname="test_func"):
195        """See :meth:`Pytester.getitem`."""
196        return self._pytester.getitem(source, funcname)
def getitems(self, source):
198    def getitems(self, source):
199        """See :meth:`Pytester.getitems`."""
200        return self._pytester.getitems(source)
def getmodulecol(self, source, configargs=(), withinit=False):
202    def getmodulecol(self, source, configargs=(), withinit=False):
203        """See :meth:`Pytester.getmodulecol`."""
204        return self._pytester.getmodulecol(
205            source, configargs=configargs, withinit=withinit
206        )
def collect_by_name( self, modcol: _pytest.nodes.Collector, name: str) -> _pytest.nodes.Item | _pytest.nodes.Collector | None:
208    def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None:
209        """See :meth:`Pytester.collect_by_name`."""
210        return self._pytester.collect_by_name(modcol, name)
def popen( self, cmdargs, stdout=-1, stderr=-1, stdin=<NotSetType.token: 0>, **kw):
212    def popen(
213        self,
214        cmdargs,
215        stdout=subprocess.PIPE,
216        stderr=subprocess.PIPE,
217        stdin=CLOSE_STDIN,
218        **kw,
219    ):
220        """See :meth:`Pytester.popen`."""
221        return self._pytester.popen(cmdargs, stdout, stderr, stdin, **kw)
def run( self, *cmdargs, timeout=None, stdin=<NotSetType.token: 0>) -> _pytest.pytester.RunResult:
223    def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> RunResult:
224        """See :meth:`Pytester.run`."""
225        return self._pytester.run(*cmdargs, timeout=timeout, stdin=stdin)
def runpython(self, script) -> _pytest.pytester.RunResult:
227    def runpython(self, script) -> RunResult:
228        """See :meth:`Pytester.runpython`."""
229        return self._pytester.runpython(script)
def runpython_c(self, command):
231    def runpython_c(self, command):
232        """See :meth:`Pytester.runpython_c`."""
233        return self._pytester.runpython_c(command)
def runpytest_subprocess(self, *args, timeout=None) -> _pytest.pytester.RunResult:
235    def runpytest_subprocess(self, *args, timeout=None) -> RunResult:
236        """See :meth:`Pytester.runpytest_subprocess`."""
237        return self._pytester.runpytest_subprocess(*args, timeout=timeout)
def spawn_pytest( self, string: str, expect_timeout: float = 10.0) -> pexpect.pty_spawn.spawn:
239    def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn:
240        """See :meth:`Pytester.spawn_pytest`."""
241        return self._pytester.spawn_pytest(string, expect_timeout=expect_timeout)
def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.pty_spawn.spawn:
243    def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn:
244        """See :meth:`Pytester.spawn`."""
245        return self._pytester.spawn(cmd, expect_timeout=expect_timeout)
class Testdir.TimeoutExpired(builtins.Exception):
663    class TimeoutExpired(Exception):
664        pass

Common base class for all non-exit exceptions.

@final
class UsageError(builtins.Exception):
7@final
8class UsageError(Exception):
9    """Error in pytest usage or invocation."""

Error in pytest usage or invocation.

class WarningsRecorder(warnings.catch_warnings):
171class WarningsRecorder(warnings.catch_warnings):  # type:ignore[type-arg]
172    """A context manager to record raised warnings.
173
174    Each recorded warning is an instance of :class:`warnings.WarningMessage`.
175
176    Adapted from `warnings.catch_warnings`.
177
178    .. note::
179        ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
180        differently; see :ref:`ensuring_function_triggers`.
181
182    """
183
184    def __init__(self, *, _ispytest: bool = False) -> None:
185        check_ispytest(_ispytest)
186        super().__init__(record=True)
187        self._entered = False
188        self._list: list[warnings.WarningMessage] = []
189
190    @property
191    def list(self) -> list[warnings.WarningMessage]:
192        """The list of recorded warnings."""
193        return self._list
194
195    def __getitem__(self, i: int) -> warnings.WarningMessage:
196        """Get a recorded warning by index."""
197        return self._list[i]
198
199    def __iter__(self) -> Iterator[warnings.WarningMessage]:
200        """Iterate through the recorded warnings."""
201        return iter(self._list)
202
203    def __len__(self) -> int:
204        """The number of recorded warnings."""
205        return len(self._list)
206
207    def pop(self, cls: type[Warning] = Warning) -> warnings.WarningMessage:
208        """Pop the first recorded warning which is an instance of ``cls``,
209        but not an instance of a child class of any other match.
210        Raises ``AssertionError`` if there is no match.
211        """
212        best_idx: int | None = None
213        for i, w in enumerate(self._list):
214            if w.category == cls:
215                return self._list.pop(i)  # exact match, stop looking
216            if issubclass(w.category, cls) and (
217                best_idx is None
218                or not issubclass(w.category, self._list[best_idx].category)
219            ):
220                best_idx = i
221        if best_idx is not None:
222            return self._list.pop(best_idx)
223        __tracebackhide__ = True
224        raise AssertionError(f"{cls!r} not found in warning list")
225
226    def clear(self) -> None:
227        """Clear the list of recorded warnings."""
228        self._list[:] = []
229
230    def __enter__(self) -> Self:
231        if self._entered:
232            __tracebackhide__ = True
233            raise RuntimeError(f"Cannot enter {self!r} twice")
234        _list = super().__enter__()
235        # record=True means it's None.
236        assert _list is not None
237        self._list = _list
238        warnings.simplefilter("always")
239        return self
240
241    def __exit__(
242        self,
243        exc_type: type[BaseException] | None,
244        exc_val: BaseException | None,
245        exc_tb: TracebackType | None,
246    ) -> None:
247        if not self._entered:
248            __tracebackhide__ = True
249            raise RuntimeError(f"Cannot exit {self!r} without entering first")
250
251        super().__exit__(exc_type, exc_val, exc_tb)
252
253        # Built-in catch_warnings does not reset entered state so we do it
254        # manually here for this context manager to become reusable.
255        self._entered = False

A context manager to record raised warnings.

Each recorded warning is an instance of warnings.WarningMessage.

Adapted from warnings.catch_warnings.

DeprecationWarning and PendingDeprecationWarning are treated differently; see :ref:ensuring_function_triggers.

WarningsRecorder(*, _ispytest: bool = False)
184    def __init__(self, *, _ispytest: bool = False) -> None:
185        check_ispytest(_ispytest)
186        super().__init__(record=True)
187        self._entered = False
188        self._list: list[warnings.WarningMessage] = []

Specify whether to record warnings and if an alternative module should be used other than sys.modules['warnings'].

For compatibility with Python 3.0, please consider all arguments to be keyword-only.

list: 'list[warnings.WarningMessage]'
190    @property
191    def list(self) -> list[warnings.WarningMessage]:
192        """The list of recorded warnings."""
193        return self._list

The list of recorded warnings.

def pop(self, cls: type[Warning] = <class 'Warning'>) -> warnings.WarningMessage:
207    def pop(self, cls: type[Warning] = Warning) -> warnings.WarningMessage:
208        """Pop the first recorded warning which is an instance of ``cls``,
209        but not an instance of a child class of any other match.
210        Raises ``AssertionError`` if there is no match.
211        """
212        best_idx: int | None = None
213        for i, w in enumerate(self._list):
214            if w.category == cls:
215                return self._list.pop(i)  # exact match, stop looking
216            if issubclass(w.category, cls) and (
217                best_idx is None
218                or not issubclass(w.category, self._list[best_idx].category)
219            ):
220                best_idx = i
221        if best_idx is not None:
222            return self._list.pop(best_idx)
223        __tracebackhide__ = True
224        raise AssertionError(f"{cls!r} not found in warning list")

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.

def clear(self) -> None:
226    def clear(self) -> None:
227        """Clear the list of recorded warnings."""
228        self._list[:] = []

Clear the list of recorded warnings.

__version__ = '8.4.1'
def approx( expected, rel=None, abs=None, nan_ok: bool = False) -> _pytest.python_api.ApproxBase:
536def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
537    """Assert that two numbers (or two ordered sequences of numbers) are equal to each other
538    within some tolerance.
539
540    Due to the :doc:`python:tutorial/floatingpoint`, numbers that we
541    would intuitively expect to be equal are not always so::
542
543        >>> 0.1 + 0.2 == 0.3
544        False
545
546    This problem is commonly encountered when writing tests, e.g. when making
547    sure that floating-point values are what you expect them to be.  One way to
548    deal with this problem is to assert that two floating-point numbers are
549    equal to within some appropriate tolerance::
550
551        >>> abs((0.1 + 0.2) - 0.3) < 1e-6
552        True
553
554    However, comparisons like this are tedious to write and difficult to
555    understand.  Furthermore, absolute comparisons like the one above are
556    usually discouraged because there's no tolerance that works well for all
557    situations.  ``1e-6`` is good for numbers around ``1``, but too small for
558    very big numbers and too big for very small ones.  It's better to express
559    the tolerance as a fraction of the expected value, but relative comparisons
560    like that are even more difficult to write correctly and concisely.
561
562    The ``approx`` class performs floating-point comparisons using a syntax
563    that's as intuitive as possible::
564
565        >>> from pytest import approx
566        >>> 0.1 + 0.2 == approx(0.3)
567        True
568
569    The same syntax also works for ordered sequences of numbers::
570
571        >>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6))
572        True
573
574    ``numpy`` arrays::
575
576        >>> import numpy as np                                                          # doctest: +SKIP
577        >>> np.array([0.1, 0.2]) + np.array([0.2, 0.4]) == approx(np.array([0.3, 0.6])) # doctest: +SKIP
578        True
579
580    And for a ``numpy`` array against a scalar::
581
582        >>> import numpy as np                                         # doctest: +SKIP
583        >>> np.array([0.1, 0.2]) + np.array([0.2, 0.1]) == approx(0.3) # doctest: +SKIP
584        True
585
586    Only ordered sequences are supported, because ``approx`` needs
587    to infer the relative position of the sequences without ambiguity. This means
588    ``sets`` and other unordered sequences are not supported.
589
590    Finally, dictionary *values* can also be compared::
591
592        >>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
593        True
594
595    The comparison will be true if both mappings have the same keys and their
596    respective values match the expected tolerances.
597
598    **Tolerances**
599
600    By default, ``approx`` considers numbers within a relative tolerance of
601    ``1e-6`` (i.e. one part in a million) of its expected value to be equal.
602    This treatment would lead to surprising results if the expected value was
603    ``0.0``, because nothing but ``0.0`` itself is relatively close to ``0.0``.
604    To handle this case less surprisingly, ``approx`` also considers numbers
605    within an absolute tolerance of ``1e-12`` of its expected value to be
606    equal.  Infinity and NaN are special cases.  Infinity is only considered
607    equal to itself, regardless of the relative tolerance.  NaN is not
608    considered equal to anything by default, but you can make it be equal to
609    itself by setting the ``nan_ok`` argument to True.  (This is meant to
610    facilitate comparing arrays that use NaN to mean "no data".)
611
612    Both the relative and absolute tolerances can be changed by passing
613    arguments to the ``approx`` constructor::
614
615        >>> 1.0001 == approx(1)
616        False
617        >>> 1.0001 == approx(1, rel=1e-3)
618        True
619        >>> 1.0001 == approx(1, abs=1e-3)
620        True
621
622    If you specify ``abs`` but not ``rel``, the comparison will not consider
623    the relative tolerance at all.  In other words, two numbers that are within
624    the default relative tolerance of ``1e-6`` will still be considered unequal
625    if they exceed the specified absolute tolerance.  If you specify both
626    ``abs`` and ``rel``, the numbers will be considered equal if either
627    tolerance is met::
628
629        >>> 1 + 1e-8 == approx(1)
630        True
631        >>> 1 + 1e-8 == approx(1, abs=1e-12)
632        False
633        >>> 1 + 1e-8 == approx(1, rel=1e-6, abs=1e-12)
634        True
635
636    **Non-numeric types**
637
638    You can also use ``approx`` to compare non-numeric types, or dicts and
639    sequences containing non-numeric types, in which case it falls back to
640    strict equality. This can be useful for comparing dicts and sequences that
641    can contain optional values::
642
643        >>> {"required": 1.0000005, "optional": None} == approx({"required": 1, "optional": None})
644        True
645        >>> [None, 1.0000005] == approx([None,1])
646        True
647        >>> ["foo", 1.0000005] == approx([None,1])
648        False
649
650    If you're thinking about using ``approx``, then you might want to know how
651    it compares to other good ways of comparing floating-point numbers.  All of
652    these algorithms are based on relative and absolute tolerances and should
653    agree for the most part, but they do have meaningful differences:
654
655    - ``math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)``:  True if the relative
656      tolerance is met w.r.t. either ``a`` or ``b`` or if the absolute
657      tolerance is met.  Because the relative tolerance is calculated w.r.t.
658      both ``a`` and ``b``, this test is symmetric (i.e.  neither ``a`` nor
659      ``b`` is a "reference value").  You have to specify an absolute tolerance
660      if you want to compare to ``0.0`` because there is no tolerance by
661      default.  More information: :py:func:`math.isclose`.
662
663    - ``numpy.isclose(a, b, rtol=1e-5, atol=1e-8)``: True if the difference
664      between ``a`` and ``b`` is less that the sum of the relative tolerance
665      w.r.t. ``b`` and the absolute tolerance.  Because the relative tolerance
666      is only calculated w.r.t. ``b``, this test is asymmetric and you can
667      think of ``b`` as the reference value.  Support for comparing sequences
668      is provided by :py:func:`numpy.allclose`.  More information:
669      :std:doc:`numpy:reference/generated/numpy.isclose`.
670
671    - ``unittest.TestCase.assertAlmostEqual(a, b)``: True if ``a`` and ``b``
672      are within an absolute tolerance of ``1e-7``.  No relative tolerance is
673      considered , so this function is not appropriate for very large or very
674      small numbers.  Also, it's only available in subclasses of ``unittest.TestCase``
675      and it's ugly because it doesn't follow PEP8.  More information:
676      :py:meth:`unittest.TestCase.assertAlmostEqual`.
677
678    - ``a == pytest.approx(b, rel=1e-6, abs=1e-12)``: True if the relative
679      tolerance is met w.r.t. ``b`` or if the absolute tolerance is met.
680      Because the relative tolerance is only calculated w.r.t. ``b``, this test
681      is asymmetric and you can think of ``b`` as the reference value.  In the
682      special case that you explicitly specify an absolute tolerance but not a
683      relative tolerance, only the absolute tolerance is considered.
684
685    .. note::
686
687        ``approx`` can handle numpy arrays, but we recommend the
688        specialised test helpers in :std:doc:`numpy:reference/routines.testing`
689        if you need support for comparisons, NaNs, or ULP-based tolerances.
690
691        To match strings using regex, you can use
692        `Matches <https://github.com/asottile/re-assert#re_assertmatchespattern-str-args-kwargs>`_
693        from the
694        `re_assert package <https://github.com/asottile/re-assert>`_.
695
696
697    .. note::
698
699        Unlike built-in equality, this function considers
700        booleans unequal to numeric zero or one. For example::
701
702           >>> 1 == approx(True)
703           False
704
705    .. warning::
706
707       .. versionchanged:: 3.2
708
709       In order to avoid inconsistent behavior, :py:exc:`TypeError` is
710       raised for ``>``, ``>=``, ``<`` and ``<=`` comparisons.
711       The example below illustrates the problem::
712
713           assert approx(0.1) > 0.1 + 1e-10  # calls approx(0.1).__gt__(0.1 + 1e-10)
714           assert 0.1 + 1e-10 > approx(0.1)  # calls approx(0.1).__lt__(0.1 + 1e-10)
715
716       In the second example one expects ``approx(0.1).__le__(0.1 + 1e-10)``
717       to be called. But instead, ``approx(0.1).__lt__(0.1 + 1e-10)`` is used to
718       comparison. This is because the call hierarchy of rich comparisons
719       follows a fixed behavior. More information: :py:meth:`object.__ge__`
720
721    .. versionchanged:: 3.7.1
722       ``approx`` raises ``TypeError`` when it encounters a dict value or
723       sequence element of non-numeric type.
724
725    .. versionchanged:: 6.1.0
726       ``approx`` falls back to strict equality for non-numeric types instead
727       of raising ``TypeError``.
728    """
729    # Delegate the comparison to a class that knows how to deal with the type
730    # of the expected value (e.g. int, float, list, dict, numpy.array, etc).
731    #
732    # The primary responsibility of these classes is to implement ``__eq__()``
733    # and ``__repr__()``.  The former is used to actually check if some
734    # "actual" value is equivalent to the given expected value within the
735    # allowed tolerance.  The latter is used to show the user the expected
736    # value and tolerance, in the case that a test failed.
737    #
738    # The actual logic for making approximate comparisons can be found in
739    # ApproxScalar, which is used to compare individual numbers.  All of the
740    # other Approx classes eventually delegate to this class.  The ApproxBase
741    # class provides some convenient methods and overloads, but isn't really
742    # essential.
743
744    __tracebackhide__ = True
745
746    if isinstance(expected, Decimal):
747        cls: type[ApproxBase] = ApproxDecimal
748    elif isinstance(expected, Mapping):
749        cls = ApproxMapping
750    elif _is_numpy_array(expected):
751        expected = _as_numpy_array(expected)
752        cls = ApproxNumpy
753    elif _is_sequence_like(expected):
754        cls = ApproxSequenceLike
755    elif isinstance(expected, Collection) and not isinstance(expected, (str, bytes)):
756        msg = f"pytest.approx() only supports ordered sequences, but got: {expected!r}"
757        raise TypeError(msg)
758    else:
759        cls = ApproxScalar
760
761    return cls(expected, rel, abs, nan_ok)

Assert that two numbers (or two ordered sequences of numbers) are equal to each other within some tolerance.

Due to the :doc:python:tutorial/floatingpoint, numbers that we would intuitively expect to be equal are not always so::

>>> 0.1 + 0.2 == 0.3
False

This problem is commonly encountered when writing tests, e.g. when making sure that floating-point values are what you expect them to be. One way to deal with this problem is to assert that two floating-point numbers are equal to within some appropriate tolerance::

>>> abs((0.1 + 0.2) - 0.3) < 1e-6
True

However, comparisons like this are tedious to write and difficult to understand. Furthermore, absolute comparisons like the one above are usually discouraged because there's no tolerance that works well for all situations. 1e-6 is good for numbers around 1, but too small for very big numbers and too big for very small ones. It's better to express the tolerance as a fraction of the expected value, but relative comparisons like that are even more difficult to write correctly and concisely.

The approx class performs floating-point comparisons using a syntax that's as intuitive as possible::

>>> from pytest import approx
>>> 0.1 + 0.2 == approx(0.3)
True

The same syntax also works for ordered sequences of numbers::

>>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6))
True

numpy arrays::

>>> import numpy as np                                                          # doctest: +SKIP
>>> np.array([0.1, 0.2]) + np.array([0.2, 0.4]) == approx(np.array([0.3, 0.6])) # doctest: +SKIP
True

And for a numpy array against a scalar::

>>> import numpy as np                                         # doctest: +SKIP
>>> np.array([0.1, 0.2]) + np.array([0.2, 0.1]) == approx(0.3) # doctest: +SKIP
True

Only ordered sequences are supported, because approx needs to infer the relative position of the sequences without ambiguity. This means sets and other unordered sequences are not supported.

Finally, dictionary values can also be compared::

>>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
True

The comparison will be true if both mappings have the same keys and their respective values match the expected tolerances.

Tolerances

By default, approx considers numbers within a relative tolerance of 1e-6 (i.e. one part in a million) of its expected value to be equal. This treatment would lead to surprising results if the expected value was 0.0, because nothing but 0.0 itself is relatively close to 0.0. To handle this case less surprisingly, approx also considers numbers within an absolute tolerance of 1e-12 of its expected value to be equal. Infinity and NaN are special cases. Infinity is only considered equal to itself, regardless of the relative tolerance. NaN is not considered equal to anything by default, but you can make it be equal to itself by setting the nan_ok argument to True. (This is meant to facilitate comparing arrays that use NaN to mean "no data".)

Both the relative and absolute tolerances can be changed by passing arguments to the approx constructor::

>>> 1.0001 == approx(1)
False
>>> 1.0001 == approx(1, rel=1e-3)
True
>>> 1.0001 == approx(1, abs=1e-3)
True

If you specify abs but not rel, the comparison will not consider the relative tolerance at all. In other words, two numbers that are within the default relative tolerance of 1e-6 will still be considered unequal if they exceed the specified absolute tolerance. If you specify both abs and rel, the numbers will be considered equal if either tolerance is met::

>>> 1 + 1e-8 == approx(1)
True
>>> 1 + 1e-8 == approx(1, abs=1e-12)
False
>>> 1 + 1e-8 == approx(1, rel=1e-6, abs=1e-12)
True

Non-numeric types

You can also use approx to compare non-numeric types, or dicts and sequences containing non-numeric types, in which case it falls back to strict equality. This can be useful for comparing dicts and sequences that can contain optional values::

>>> {"required": 1.0000005, "optional": None} == approx({"required": 1, "optional": None})
True
>>> [None, 1.0000005] == approx([None,1])
True
>>> ["foo", 1.0000005] == approx([None,1])
False

If you're thinking about using approx, then you might want to know how it compares to other good ways of comparing floating-point numbers. All of these algorithms are based on relative and absolute tolerances and should agree for the most part, but they do have meaningful differences:

  • math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0): True if the relative tolerance is met w.r.t. either a or b or if the absolute tolerance is met. Because the relative tolerance is calculated w.r.t. both a and b, this test is symmetric (i.e. neither a nor b is a "reference value"). You have to specify an absolute tolerance if you want to compare to 0.0 because there is no tolerance by default. More information: math.isclose().

  • numpy.isclose(a, b, rtol=1e-5, atol=1e-8): True if the difference between a and b is less that the sum of the relative tolerance w.r.t. b and the absolute tolerance. Because the relative tolerance is only calculated w.r.t. b, this test is asymmetric and you can think of b as the reference value. Support for comparing sequences is provided by numpy.allclose(). More information: :std:doc:numpy:reference/generated/numpy.isclose.

  • unittest.TestCase.assertAlmostEqual(a, b): True if a and b are within an absolute tolerance of 1e-7. No relative tolerance is considered , so this function is not appropriate for very large or very small numbers. Also, it's only available in subclasses of unittest.TestCase and it's ugly because it doesn't follow PEP8. More information: unittest.TestCase.assertAlmostEqual().

  • a == pytest.approx(b, rel=1e-6, abs=1e-12): True if the relative tolerance is met w.r.t. b or if the absolute tolerance is met. Because the relative tolerance is only calculated w.r.t. b, this test is asymmetric and you can think of b as the reference value. In the special case that you explicitly specify an absolute tolerance but not a relative tolerance, only the absolute tolerance is considered.

approx can handle numpy arrays, but we recommend the specialised test helpers in :std:doc:numpy:reference/routines.testing if you need support for comparisons, NaNs, or ULP-based tolerances.

To match strings using regex, you can use Matches from the re_assert package .

Unlike built-in equality, this function considers booleans unequal to numeric zero or one. For example::

>>> 1 == approx(True)
False

.. versionchanged:: 3.2

In order to avoid inconsistent behavior, TypeError is raised for >, >=, < and <= comparisons. The example below illustrates the problem::

assert approx(0.1) > 0.1 + 1e-10  # calls approx(0.1).__gt__(0.1 + 1e-10)
assert 0.1 + 1e-10 > approx(0.1)  # calls approx(0.1).__lt__(0.1 + 1e-10)

In the second example one expects approx(0.1).__le__(0.1 + 1e-10) to be called. But instead, approx(0.1).__lt__(0.1 + 1e-10) is used to comparison. This is because the call hierarchy of rich comparisons follows a fixed behavior. More information: object.__ge__()

Changed in version 3.7.1: approx raises TypeError when it encounters a dict value or sequence element of non-numeric type.

Changed in version 6.1.0: approx falls back to strict equality for non-numeric types instead of raising TypeError.

class cmdline:
213class cmdline:  # compatibility namespace
214    main = staticmethod(main)
def main( args: list[str] | os.PathLike[str] | None = None, plugins: Sequence[str | object] | None = None) -> int | _pytest.config.ExitCode:
140def main(
141    args: list[str] | os.PathLike[str] | None = None,
142    plugins: Sequence[str | _PluggyPlugin] | None = None,
143) -> int | ExitCode:
144    """Perform an in-process test run.
145
146    :param args:
147        List of command line arguments. If `None` or not given, defaults to reading
148        arguments directly from the process command line (:data:`sys.argv`).
149    :param plugins: List of plugin objects to be auto-registered during initialization.
150
151    :returns: An exit code.
152    """
153    old_pytest_version = os.environ.get("PYTEST_VERSION")
154    try:
155        os.environ["PYTEST_VERSION"] = __version__
156        try:
157            config = _prepareconfig(args, plugins)
158        except ConftestImportFailure as e:
159            exc_info = ExceptionInfo.from_exception(e.cause)
160            tw = TerminalWriter(sys.stderr)
161            tw.line(f"ImportError while loading conftest '{e.path}'.", red=True)
162            exc_info.traceback = exc_info.traceback.filter(
163                filter_traceback_for_conftest_import_failure
164            )
165            exc_repr = (
166                exc_info.getrepr(style="short", chain=False)
167                if exc_info.traceback
168                else exc_info.exconly()
169            )
170            formatted_tb = str(exc_repr)
171            for line in formatted_tb.splitlines():
172                tw.line(line.rstrip(), red=True)
173            return ExitCode.USAGE_ERROR
174        else:
175            try:
176                ret: ExitCode | int = config.hook.pytest_cmdline_main(config=config)
177                try:
178                    return ExitCode(ret)
179                except ValueError:
180                    return ret
181            finally:
182                config._ensure_unconfigure()
183    except UsageError as e:
184        tw = TerminalWriter(sys.stderr)
185        for msg in e.args:
186            tw.line(f"ERROR: {msg}\n", red=True)
187        return ExitCode.USAGE_ERROR
188    finally:
189        if old_pytest_version is None:
190            os.environ.pop("PYTEST_VERSION", None)
191        else:
192            os.environ["PYTEST_VERSION"] = old_pytest_version

Perform an in-process test run.

Parameters
  • args: List of command line arguments. If None or not given, defaults to reading arguments directly from the process command line (sys.argv).
  • plugins: List of plugin objects to be auto-registered during initialization.

:returns: An exit code.

def console_main() -> int:
195def console_main() -> int:
196    """The CLI entry point of pytest.
197
198    This function is not meant for programmable use; use `main()` instead.
199    """
200    # https://docs.python.org/3/library/signal.html#note-on-sigpipe
201    try:
202        code = main()
203        sys.stdout.flush()
204        return code
205    except BrokenPipeError:
206        # Python flushes standard streams on exit; redirect remaining output
207        # to devnull to avoid another BrokenPipeError at shutdown
208        devnull = os.open(os.devnull, os.O_WRONLY)
209        os.dup2(devnull, sys.stdout.fileno())
210        return 1  # Python exits with error code 1 on EPIPE

The CLI entry point of pytest.

This function is not meant for programmable use; use main() instead.

def deprecated_call( func: Callable[..., typing.Any] | None = None, *args: Any, **kwargs: Any) -> _pytest.recwarn.WarningsRecorder | typing.Any:
56def deprecated_call(
57    func: Callable[..., Any] | None = None, *args: Any, **kwargs: Any
58) -> WarningsRecorder | Any:
59    """Assert that code produces a ``DeprecationWarning`` or ``PendingDeprecationWarning`` or ``FutureWarning``.
60
61    This function can be used as a context manager::
62
63        >>> import warnings
64        >>> def api_call_v2():
65        ...     warnings.warn('use v3 of this api', DeprecationWarning)
66        ...     return 200
67
68        >>> import pytest
69        >>> with pytest.deprecated_call():
70        ...    assert api_call_v2() == 200
71
72    It can also be used by passing a function and ``*args`` and ``**kwargs``,
73    in which case it will ensure calling ``func(*args, **kwargs)`` produces one of
74    the warnings types above. The return value is the return value of the function.
75
76    In the context manager form you may use the keyword argument ``match`` to assert
77    that the warning matches a text or regex.
78
79    The context manager produces a list of :class:`warnings.WarningMessage` objects,
80    one for each warning raised.
81    """
82    __tracebackhide__ = True
83    if func is not None:
84        args = (func, *args)
85    return warns(
86        (DeprecationWarning, PendingDeprecationWarning, FutureWarning), *args, **kwargs
87    )

Assert that code produces a DeprecationWarning or PendingDeprecationWarning or FutureWarning.

This function can be used as a context manager::

>>> import warnings
>>> def api_call_v2():
...     warnings.warn('use v3 of this api', DeprecationWarning)
...     return 200

>>> import pytest
>>> with pytest.deprecated_call():
...    assert api_call_v2() == 200

It can also be used by passing a function and *args and **kwargs, in which case it will ensure calling func(*args, **kwargs) produces one of the warnings types above. The return value is the return value of the function.

In the context manager form you may use the keyword argument match to assert that the warning matches a text or regex.

The context manager produces a list of warnings.WarningMessage objects, one for each warning raised.

def exit(reason: str = '', returncode: int | None = None) -> NoReturn:
105@_with_exception(Exit)
106def exit(
107    reason: str = "",
108    returncode: int | None = None,
109) -> NoReturn:
110    """Exit testing process.
111
112    :param reason:
113        The message to show as the reason for exiting pytest.  reason has a default value
114        only because `msg` is deprecated.
115
116    :param returncode:
117        Return code to be used when exiting pytest. None means the same as ``0`` (no error), same as :func:`sys.exit`.
118
119    :raises pytest.exit.Exception:
120        The exception that is raised.
121    """
122    __tracebackhide__ = True
123    raise Exit(reason, returncode)

Exit testing process.

Parameters
  • reason: The message to show as the reason for exiting pytest. reason has a default value only because msg is deprecated.

  • returncode: Return code to be used when exiting pytest. None means the same as 0 (no error), same as sys.exit().

Raises
  • pytest.exit.Exception: The exception that is raised.
def fail(reason: str = '', pytrace: bool = True) -> NoReturn:
163@_with_exception(Failed)
164def fail(reason: str = "", pytrace: bool = True) -> NoReturn:
165    """Explicitly fail an executing test with the given message.
166
167    :param reason:
168        The message to show the user as reason for the failure.
169
170    :param pytrace:
171        If False, msg represents the full failure information and no
172        python traceback will be reported.
173
174    :raises pytest.fail.Exception:
175        The exception that is raised.
176    """
177    __tracebackhide__ = True
178    raise Failed(msg=reason, pytrace=pytrace)

Explicitly fail an executing test with the given message.

Parameters
  • reason: The message to show the user as reason for the failure.

  • pytrace: If False, msg represents the full failure information and no python traceback will be reported.

Raises
  • pytest.fail.Exception: The exception that is raised.
def fixture( fixture_function: Optional[~FixtureFunction] = None, *, scope: Union[Literal['session', 'package', 'module', 'class', 'function'], Callable[[str, _pytest.config.Config], Literal['session', 'package', 'module', 'class', 'function']]] = 'function', params: Iterable[object] | None = None, autouse: bool = False, ids: Sequence[object | None] | Callable[[typing.Any], object | None] | None = None, name: str | None = None) -> _pytest.fixtures.FixtureFunctionMarker | _pytest.fixtures.FixtureFunctionDefinition:
1323def fixture(
1324    fixture_function: FixtureFunction | None = None,
1325    *,
1326    scope: _ScopeName | Callable[[str, Config], _ScopeName] = "function",
1327    params: Iterable[object] | None = None,
1328    autouse: bool = False,
1329    ids: Sequence[object | None] | Callable[[Any], object | None] | None = None,
1330    name: str | None = None,
1331) -> FixtureFunctionMarker | FixtureFunctionDefinition:
1332    """Decorator to mark a fixture factory function.
1333
1334    This decorator can be used, with or without parameters, to define a
1335    fixture function.
1336
1337    The name of the fixture function can later be referenced to cause its
1338    invocation ahead of running tests: test modules or classes can use the
1339    ``pytest.mark.usefixtures(fixturename)`` marker.
1340
1341    Test functions can directly use fixture names as input arguments in which
1342    case the fixture instance returned from the fixture function will be
1343    injected.
1344
1345    Fixtures can provide their values to test functions using ``return`` or
1346    ``yield`` statements. When using ``yield`` the code block after the
1347    ``yield`` statement is executed as teardown code regardless of the test
1348    outcome, and must yield exactly once.
1349
1350    :param scope:
1351        The scope for which this fixture is shared; one of ``"function"``
1352        (default), ``"class"``, ``"module"``, ``"package"`` or ``"session"``.
1353
1354        This parameter may also be a callable which receives ``(fixture_name, config)``
1355        as parameters, and must return a ``str`` with one of the values mentioned above.
1356
1357        See :ref:`dynamic scope` in the docs for more information.
1358
1359    :param params:
1360        An optional list of parameters which will cause multiple invocations
1361        of the fixture function and all of the tests using it. The current
1362        parameter is available in ``request.param``.
1363
1364    :param autouse:
1365        If True, the fixture func is activated for all tests that can see it.
1366        If False (the default), an explicit reference is needed to activate
1367        the fixture.
1368
1369    :param ids:
1370        Sequence of ids each corresponding to the params so that they are
1371        part of the test id. If no ids are provided they will be generated
1372        automatically from the params.
1373
1374    :param name:
1375        The name of the fixture. This defaults to the name of the decorated
1376        function. If a fixture is used in the same module in which it is
1377        defined, the function name of the fixture will be shadowed by the
1378        function arg that requests the fixture; one way to resolve this is to
1379        name the decorated function ``fixture_<fixturename>`` and then use
1380        ``@pytest.fixture(name='<fixturename>')``.
1381    """
1382    fixture_marker = FixtureFunctionMarker(
1383        scope=scope,
1384        params=tuple(params) if params is not None else None,
1385        autouse=autouse,
1386        ids=None if ids is None else ids if callable(ids) else tuple(ids),
1387        name=name,
1388        _ispytest=True,
1389    )
1390
1391    # Direct decoration.
1392    if fixture_function:
1393        return fixture_marker(fixture_function)
1394
1395    return fixture_marker

Decorator to mark a fixture factory function.

This decorator can be used, with or without parameters, to define a fixture function.

The name of the fixture function can later be referenced to cause its invocation ahead of running tests: test modules or classes can use the pytest.mark.usefixtures(fixturename) marker.

Test functions can directly use fixture names as input arguments in which case the fixture instance returned from the fixture function will be injected.

Fixtures can provide their values to test functions using return or yield statements. When using yield the code block after the yield statement is executed as teardown code regardless of the test outcome, and must yield exactly once.

Parameters
  • scope: The scope for which this fixture is shared; one of "function" (default), "class", "module", "package" or "session".

    This parameter may also be a callable which receives (fixture_name, config) as parameters, and must return a str with one of the values mentioned above.

    See :ref:dynamic scope in the docs for more information.

  • params: An optional list of parameters which will cause multiple invocations of the fixture function and all of the tests using it. The current parameter is available in request.param.

  • autouse: If True, the fixture func is activated for all tests that can see it. If False (the default), an explicit reference is needed to activate the fixture.

  • ids: Sequence of ids each corresponding to the params so that they are part of the test id. If no ids are provided they will be generated automatically from the params.

  • name: The name of the fixture. This defaults to the name of the decorated function. If a fixture is used in the same module in which it is defined, the function name of the fixture will be shadowed by the function arg that requests the fixture; one way to resolve this is to name the decorated function fixture_<fixturename> and then use @pytest.fixture(name='<fixturename>').

def freeze_includes() -> list[str]:
11def freeze_includes() -> list[str]:
12    """Return a list of module names used by pytest that should be
13    included by cx_freeze."""
14    import _pytest
15
16    result = list(_iter_all_modules(_pytest))
17    return result

Return a list of module names used by pytest that should be included by cx_freeze.

hookimpl = <pluggy._hooks.HookimplMarker object>
hookspec = <pluggy._hooks.HookspecMarker object>
def importorskip( modname: str, minversion: str | None = None, reason: str | None = None, *, exc_type: type[ImportError] | None = None) -> Any:
209def importorskip(
210    modname: str,
211    minversion: str | None = None,
212    reason: str | None = None,
213    *,
214    exc_type: type[ImportError] | None = None,
215) -> Any:
216    """Import and return the requested module ``modname``, or skip the
217    current test if the module cannot be imported.
218
219    :param modname:
220        The name of the module to import.
221    :param minversion:
222        If given, the imported module's ``__version__`` attribute must be at
223        least this minimal version, otherwise the test is still skipped.
224    :param reason:
225        If given, this reason is shown as the message when the module cannot
226        be imported.
227    :param exc_type:
228        The exception that should be captured in order to skip modules.
229        Must be :py:class:`ImportError` or a subclass.
230
231        If the module can be imported but raises :class:`ImportError`, pytest will
232        issue a warning to the user, as often users expect the module not to be
233        found (which would raise :class:`ModuleNotFoundError` instead).
234
235        This warning can be suppressed by passing ``exc_type=ImportError`` explicitly.
236
237        See :ref:`import-or-skip-import-error` for details.
238
239
240    :returns:
241        The imported module. This should be assigned to its canonical name.
242
243    :raises pytest.skip.Exception:
244        If the module cannot be imported.
245
246    Example::
247
248        docutils = pytest.importorskip("docutils")
249
250    .. versionadded:: 8.2
251
252        The ``exc_type`` parameter.
253    """
254    import warnings
255
256    __tracebackhide__ = True
257    compile(modname, "", "eval")  # to catch syntaxerrors
258
259    # Until pytest 9.1, we will warn the user if we catch ImportError (instead of ModuleNotFoundError),
260    # as this might be hiding an installation/environment problem, which is not usually what is intended
261    # when using importorskip() (#11523).
262    # In 9.1, to keep the function signature compatible, we just change the code below to:
263    # 1. Use `exc_type = ModuleNotFoundError` if `exc_type` is not given.
264    # 2. Remove `warn_on_import` and the warning handling.
265    if exc_type is None:
266        exc_type = ImportError
267        warn_on_import_error = True
268    else:
269        warn_on_import_error = False
270
271    skipped: Skipped | None = None
272    warning: Warning | None = None
273
274    with warnings.catch_warnings():
275        # Make sure to ignore ImportWarnings that might happen because
276        # of existing directories with the same name we're trying to
277        # import but without a __init__.py file.
278        warnings.simplefilter("ignore")
279
280        try:
281            __import__(modname)
282        except exc_type as exc:
283            # Do not raise or issue warnings inside the catch_warnings() block.
284            if reason is None:
285                reason = f"could not import {modname!r}: {exc}"
286            skipped = Skipped(reason, allow_module_level=True)
287
288            if warn_on_import_error and not isinstance(exc, ModuleNotFoundError):
289                lines = [
290                    "",
291                    f"Module '{modname}' was found, but when imported by pytest it raised:",
292                    f"    {exc!r}",
293                    "In pytest 9.1 this warning will become an error by default.",
294                    "You can fix the underlying problem, or alternatively overwrite this behavior and silence this "
295                    "warning by passing exc_type=ImportError explicitly.",
296                    "See https://docs.pytest.org/en/stable/deprecations.html#pytest-importorskip-default-behavior-regarding-importerror",
297                ]
298                warning = PytestDeprecationWarning("\n".join(lines))
299
300    if warning:
301        warnings.warn(warning, stacklevel=2)
302    if skipped:
303        raise skipped
304
305    mod = sys.modules[modname]
306    if minversion is None:
307        return mod
308    verattr = getattr(mod, "__version__", None)
309    if minversion is not None:
310        # Imported lazily to improve start-up time.
311        from packaging.version import Version
312
313        if verattr is None or Version(verattr) < Version(minversion):
314            raise Skipped(
315                f"module {modname!r} has __version__ {verattr!r}, required is: {minversion!r}",
316                allow_module_level=True,
317            )
318    return mod

Import and return the requested module modname, or skip the current test if the module cannot be imported.

Parameters
  • modname: The name of the module to import.
  • minversion: If given, the imported module's __version__ attribute must be at least this minimal version, otherwise the test is still skipped.
  • reason: If given, this reason is shown as the message when the module cannot be imported.
  • exc_type: The exception that should be captured in order to skip modules. Must be ImportError or a subclass.

    If the module can be imported but raises ImportError, pytest will issue a warning to the user, as often users expect the module not to be found (which would raise ModuleNotFoundError instead).

    This warning can be suppressed by passing exc_type=ImportError explicitly.

    See :ref:import-or-skip-import-error for details.

:returns: The imported module. This should be assigned to its canonical name.

Raises
  • pytest.skip.Exception: If the module cannot be imported.

Example::

docutils = pytest.importorskip("docutils")

New in version 8.2: The exc_type parameter.

def main( args: list[str] | os.PathLike[str] | None = None, plugins: Sequence[str | object] | None = None) -> int | _pytest.config.ExitCode:
140def main(
141    args: list[str] | os.PathLike[str] | None = None,
142    plugins: Sequence[str | _PluggyPlugin] | None = None,
143) -> int | ExitCode:
144    """Perform an in-process test run.
145
146    :param args:
147        List of command line arguments. If `None` or not given, defaults to reading
148        arguments directly from the process command line (:data:`sys.argv`).
149    :param plugins: List of plugin objects to be auto-registered during initialization.
150
151    :returns: An exit code.
152    """
153    old_pytest_version = os.environ.get("PYTEST_VERSION")
154    try:
155        os.environ["PYTEST_VERSION"] = __version__
156        try:
157            config = _prepareconfig(args, plugins)
158        except ConftestImportFailure as e:
159            exc_info = ExceptionInfo.from_exception(e.cause)
160            tw = TerminalWriter(sys.stderr)
161            tw.line(f"ImportError while loading conftest '{e.path}'.", red=True)
162            exc_info.traceback = exc_info.traceback.filter(
163                filter_traceback_for_conftest_import_failure
164            )
165            exc_repr = (
166                exc_info.getrepr(style="short", chain=False)
167                if exc_info.traceback
168                else exc_info.exconly()
169            )
170            formatted_tb = str(exc_repr)
171            for line in formatted_tb.splitlines():
172                tw.line(line.rstrip(), red=True)
173            return ExitCode.USAGE_ERROR
174        else:
175            try:
176                ret: ExitCode | int = config.hook.pytest_cmdline_main(config=config)
177                try:
178                    return ExitCode(ret)
179                except ValueError:
180                    return ret
181            finally:
182                config._ensure_unconfigure()
183    except UsageError as e:
184        tw = TerminalWriter(sys.stderr)
185        for msg in e.args:
186            tw.line(f"ERROR: {msg}\n", red=True)
187        return ExitCode.USAGE_ERROR
188    finally:
189        if old_pytest_version is None:
190            os.environ.pop("PYTEST_VERSION", None)
191        else:
192            os.environ["PYTEST_VERSION"] = old_pytest_version

Perform an in-process test run.

Parameters
  • args: List of command line arguments. If None or not given, defaults to reading arguments directly from the process command line (sys.argv).
  • plugins: List of plugin objects to be auto-registered during initialization.

:returns: An exit code.

mark = <_pytest.mark.structures.MarkGenerator object>
def param( *values: object, marks: _pytest.mark.structures.MarkDecorator | Collection[_pytest.mark.structures.MarkDecorator | _pytest.mark.structures.Mark] = (), id: str | _pytest.mark.structures._HiddenParam | None = None) -> _pytest.mark.structures.ParameterSet:
52def param(
53    *values: object,
54    marks: MarkDecorator | Collection[MarkDecorator | Mark] = (),
55    id: str | _HiddenParam | None = None,
56) -> ParameterSet:
57    """Specify a parameter in `pytest.mark.parametrize`_ calls or
58    :ref:`parametrized fixtures <fixture-parametrize-marks>`.
59
60    .. code-block:: python
61
62        @pytest.mark.parametrize(
63            "test_input,expected",
64            [
65                ("3+5", 8),
66                pytest.param("6*9", 42, marks=pytest.mark.xfail),
67            ],
68        )
69        def test_eval(test_input, expected):
70            assert eval(test_input) == expected
71
72    :param values: Variable args of the values of the parameter set, in order.
73
74    :param marks:
75        A single mark or a list of marks to be applied to this parameter set.
76
77        :ref:`pytest.mark.usefixtures <pytest.mark.usefixtures ref>` cannot be added via this parameter.
78
79    :type id: str | Literal[pytest.HIDDEN_PARAM] | None
80    :param id:
81        The id to attribute to this parameter set.
82
83        .. versionadded:: 8.4
84            :ref:`hidden-param` means to hide the parameter set
85            from the test name. Can only be used at most 1 time, as
86            test names need to be unique.
87    """
88    return ParameterSet.param(*values, marks=marks, id=id)

Specify a parameter in pytest.mark.parametrize_ calls or :ref:parametrized fixtures <fixture-parametrize-marks>.

@pytest.mark.parametrize(
    "test_input,expected",
    [
        ("3+5", 8),
        pytest.param("6*9", 42, marks=pytest.mark.xfail),
    ],
)
def test_eval(test_input, expected):
    assert eval(test_input) == expected
Parameters
  • values: Variable args of the values of the parameter set, in order.

  • marks: A single mark or a list of marks to be applied to this parameter set.

    :ref:pytest.mark.usefixtures <pytest.mark.usefixtures ref> cannot be added via this parameter.

  • id: The id to attribute to this parameter set.

    New in version 8.4:

    from the test name. Can only be used at most 1 time, as test names need to be unique.

def raises( expected_exception: type[~E] | tuple[type[~E], ...] | None = None, *args: Any, **kwargs: Any) -> Union[_pytest.raises.RaisesExc[BaseException], _pytest._code.code.ExceptionInfo[~E]]:
105def raises(
106    expected_exception: type[E] | tuple[type[E], ...] | None = None,
107    *args: Any,
108    **kwargs: Any,
109) -> RaisesExc[BaseException] | ExceptionInfo[E]:
110    r"""Assert that a code block/function call raises an exception type, or one of its subclasses.
111
112    :param expected_exception:
113        The expected exception type, or a tuple if one of multiple possible
114        exception types are expected. Note that subclasses of the passed exceptions
115        will also match.
116
117        This is not a required parameter, you may opt to only use ``match`` and/or
118        ``check`` for verifying the raised exception.
119
120    :kwparam str | re.Pattern[str] | None match:
121        If specified, a string containing a regular expression,
122        or a regular expression object, that is tested against the string
123        representation of the exception and its :pep:`678` `__notes__`
124        using :func:`re.search`.
125
126        To match a literal string that may contain :ref:`special characters
127        <re-syntax>`, the pattern can first be escaped with :func:`re.escape`.
128
129        (This is only used when ``pytest.raises`` is used as a context manager,
130        and passed through to the function otherwise.
131        When using ``pytest.raises`` as a function, you can use:
132        ``pytest.raises(Exc, func, match="passed on").match("my pattern")``.)
133
134    :kwparam Callable[[BaseException], bool] check:
135
136        .. versionadded:: 8.4
137
138        If specified, a callable that will be called with the exception as a parameter
139        after checking the type and the match regex if specified.
140        If it returns ``True`` it will be considered a match, if not it will
141        be considered a failed match.
142
143
144    Use ``pytest.raises`` as a context manager, which will capture the exception of the given
145    type, or any of its subclasses::
146
147        >>> import pytest
148        >>> with pytest.raises(ZeroDivisionError):
149        ...    1/0
150
151    If the code block does not raise the expected exception (:class:`ZeroDivisionError` in the example
152    above), or no exception at all, the check will fail instead.
153
154    You can also use the keyword argument ``match`` to assert that the
155    exception matches a text or regex::
156
157        >>> with pytest.raises(ValueError, match='must be 0 or None'):
158        ...     raise ValueError("value must be 0 or None")
159
160        >>> with pytest.raises(ValueError, match=r'must be \d+$'):
161        ...     raise ValueError("value must be 42")
162
163    The ``match`` argument searches the formatted exception string, which includes any
164    `PEP-678 <https://peps.python.org/pep-0678/>`__ ``__notes__``:
165
166        >>> with pytest.raises(ValueError, match=r"had a note added"):  # doctest: +SKIP
167        ...     e = ValueError("value must be 42")
168        ...     e.add_note("had a note added")
169        ...     raise e
170
171    The ``check`` argument, if provided, must return True when passed the raised exception
172    for the match to be successful, otherwise an :exc:`AssertionError` is raised.
173
174        >>> import errno
175        >>> with pytest.raises(OSError, check=lambda e: e.errno == errno.EACCES):
176        ...     raise OSError(errno.EACCES, "no permission to view")
177
178    The context manager produces an :class:`ExceptionInfo` object which can be used to inspect the
179    details of the captured exception::
180
181        >>> with pytest.raises(ValueError) as exc_info:
182        ...     raise ValueError("value must be 42")
183        >>> assert exc_info.type is ValueError
184        >>> assert exc_info.value.args[0] == "value must be 42"
185
186    .. warning::
187
188       Given that ``pytest.raises`` matches subclasses, be wary of using it to match :class:`Exception` like this::
189
190           # Careful, this will catch ANY exception raised.
191           with pytest.raises(Exception):
192               some_function()
193
194       Because :class:`Exception` is the base class of almost all exceptions, it is easy for this to hide
195       real bugs, where the user wrote this expecting a specific exception, but some other exception is being
196       raised due to a bug introduced during a refactoring.
197
198       Avoid using ``pytest.raises`` to catch :class:`Exception` unless certain that you really want to catch
199       **any** exception raised.
200
201    .. note::
202
203       When using ``pytest.raises`` as a context manager, it's worthwhile to
204       note that normal context manager rules apply and that the exception
205       raised *must* be the final line in the scope of the context manager.
206       Lines of code after that, within the scope of the context manager will
207       not be executed. For example::
208
209           >>> value = 15
210           >>> with pytest.raises(ValueError) as exc_info:
211           ...     if value > 10:
212           ...         raise ValueError("value must be <= 10")
213           ...     assert exc_info.type is ValueError  # This will not execute.
214
215       Instead, the following approach must be taken (note the difference in
216       scope)::
217
218           >>> with pytest.raises(ValueError) as exc_info:
219           ...     if value > 10:
220           ...         raise ValueError("value must be <= 10")
221           ...
222           >>> assert exc_info.type is ValueError
223
224    **Expecting exception groups**
225
226    When expecting exceptions wrapped in :exc:`BaseExceptionGroup` or
227    :exc:`ExceptionGroup`, you should instead use :class:`pytest.RaisesGroup`.
228
229    **Using with** ``pytest.mark.parametrize``
230
231    When using :ref:`pytest.mark.parametrize ref`
232    it is possible to parametrize tests such that
233    some runs raise an exception and others do not.
234
235    See :ref:`parametrizing_conditional_raising` for an example.
236
237    .. seealso::
238
239        :ref:`assertraises` for more examples and detailed discussion.
240
241    **Legacy form**
242
243    It is possible to specify a callable by passing a to-be-called lambda::
244
245        >>> raises(ZeroDivisionError, lambda: 1/0)
246        <ExceptionInfo ...>
247
248    or you can specify an arbitrary callable with arguments::
249
250        >>> def f(x): return 1/x
251        ...
252        >>> raises(ZeroDivisionError, f, 0)
253        <ExceptionInfo ...>
254        >>> raises(ZeroDivisionError, f, x=0)
255        <ExceptionInfo ...>
256
257    The form above is fully supported but discouraged for new code because the
258    context manager form is regarded as more readable and less error-prone.
259
260    .. note::
261        Similar to caught exception objects in Python, explicitly clearing
262        local references to returned ``ExceptionInfo`` objects can
263        help the Python interpreter speed up its garbage collection.
264
265        Clearing those references breaks a reference cycle
266        (``ExceptionInfo`` --> caught exception --> frame stack raising
267        the exception --> current frame stack --> local variables -->
268        ``ExceptionInfo``) which makes Python keep all objects referenced
269        from that cycle (including all local variables in the current
270        frame) alive until the next cyclic garbage collection run.
271        More detailed information can be found in the official Python
272        documentation for :ref:`the try statement <python:try>`.
273    """
274    __tracebackhide__ = True
275
276    if not args:
277        if set(kwargs) - {"match", "check", "expected_exception"}:
278            msg = "Unexpected keyword arguments passed to pytest.raises: "
279            msg += ", ".join(sorted(kwargs))
280            msg += "\nUse context-manager form instead?"
281            raise TypeError(msg)
282
283        if expected_exception is None:
284            return RaisesExc(**kwargs)
285        return RaisesExc(expected_exception, **kwargs)
286
287    if not expected_exception:
288        raise ValueError(
289            f"Expected an exception type or a tuple of exception types, but got `{expected_exception!r}`. "
290            f"Raising exceptions is already understood as failing the test, so you don't need "
291            f"any special code to say 'this should never raise an exception'."
292        )
293    func = args[0]
294    if not callable(func):
295        raise TypeError(f"{func!r} object (type: {type(func)}) must be callable")
296    with RaisesExc(expected_exception) as excinfo:
297        func(*args[1:], **kwargs)
298    try:
299        return excinfo
300    finally:
301        del excinfo

Assert that a code block/function call raises an exception type, or one of its subclasses.

Parameters
  • expected_exception: The expected exception type, or a tuple if one of multiple possible exception types are expected. Note that subclasses of the passed exceptions will also match.

    This is not a required parameter, you may opt to only use match and/or check for verifying the raised exception.

:kwparam str | re.Pattern[str] | None match: If specified, a string containing a regular expression, or a regular expression object, that is tested against the string representation of the exception and its :pep:678 __notes__ using re.search().

To match a literal string that may contain :ref:`special characters
<re-syntax>`, the pattern can first be escaped with `re.escape()`.

(This is only used when ``pytest.raises`` is used as a context manager,
and passed through to the function otherwise.
When using ``pytest.raises`` as a function, you can use:
``pytest.raises(Exc, func, match="passed on").match("my pattern")``.)

:kwparam Callable[[BaseException], bool] check:

*New in version 8.4.*

If specified, a callable that will be called with the exception as a parameter
after checking the type and the match regex if specified.
If it returns ``True`` it will be considered a match, if not it will
be considered a failed match.

Use pytest.raises as a context manager, which will capture the exception of the given type, or any of its subclasses::

>>> import pytest
>>> with pytest.raises(ZeroDivisionError):
...    1/0

If the code block does not raise the expected exception (ZeroDivisionError in the example above), or no exception at all, the check will fail instead.

You can also use the keyword argument match to assert that the exception matches a text or regex::

>>> with pytest.raises(ValueError, match='must be 0 or None'):
...     raise ValueError("value must be 0 or None")

>>> with pytest.raises(ValueError, match=r'must be \d+$'):
...     raise ValueError("value must be 42")

The match argument searches the formatted exception string, which includes any PEP-678 _ __notes__:

>>> with pytest.raises(ValueError, match=r"had a note added"):  # doctest: +SKIP
...     e = ValueError("value must be 42")
...     e.add_note("had a note added")
...     raise e

The check argument, if provided, must return True when passed the raised exception for the match to be successful, otherwise an AssertionError is raised.

>>> import errno
>>> with pytest.raises(OSError, check=lambda e: e.errno == errno.EACCES):
...     raise OSError(errno.EACCES, "no permission to view")

The context manager produces an ExceptionInfo object which can be used to inspect the details of the captured exception::

>>> with pytest.raises(ValueError) as exc_info:
...     raise ValueError("value must be 42")
>>> assert exc_info.type is ValueError
>>> assert exc_info.value.args[0] == "value must be 42"

Given that pytest.raises matches subclasses, be wary of using it to match Exception like this::

# Careful, this will catch ANY exception raised.
with pytest.raises(Exception):
    some_function()

Because Exception is the base class of almost all exceptions, it is easy for this to hide real bugs, where the user wrote this expecting a specific exception, but some other exception is being raised due to a bug introduced during a refactoring.

Avoid using pytest.raises to catch Exception unless certain that you really want to catch any exception raised.

When using pytest.raises as a context manager, it's worthwhile to note that normal context manager rules apply and that the exception raised must be the final line in the scope of the context manager. Lines of code after that, within the scope of the context manager will not be executed. For example::

>>> value = 15
>>> with pytest.raises(ValueError) as exc_info:
...     if value > 10:
...         raise ValueError("value must be <= 10")
...     assert exc_info.type is ValueError  # This will not execute.

Instead, the following approach must be taken (note the difference in scope)::

>>> with pytest.raises(ValueError) as exc_info:
...     if value > 10:
...         raise ValueError("value must be <= 10")
...
>>> assert exc_info.type is ValueError

Expecting exception groups

When expecting exceptions wrapped in BaseExceptionGroup or ExceptionGroup, you should instead use pytest.RaisesGroup.

Using with pytest.mark.parametrize

When using :ref:pytest.mark.parametrize ref it is possible to parametrize tests such that some runs raise an exception and others do not.

See :ref:parametrizing_conditional_raising for an example.

seealso.

Legacy form

It is possible to specify a callable by passing a to-be-called lambda::

>>> raises(ZeroDivisionError, lambda: 1/0)
<ExceptionInfo ...>

or you can specify an arbitrary callable with arguments::

>>> def f(x): return 1/x
...
>>> raises(ZeroDivisionError, f, 0)
<ExceptionInfo ...>
>>> raises(ZeroDivisionError, f, x=0)
<ExceptionInfo ...>

The form above is fully supported but discouraged for new code because the context manager form is regarded as more readable and less error-prone.

Similar to caught exception objects in Python, explicitly clearing local references to returned ExceptionInfo objects can help the Python interpreter speed up its garbage collection.

Clearing those references breaks a reference cycle (ExceptionInfo --> caught exception --> frame stack raising the exception --> current frame stack --> local variables --> ExceptionInfo) which makes Python keep all objects referenced from that cycle (including all local variables in the current frame) alive until the next cyclic garbage collection run. More detailed information can be found in the official Python documentation for :ref:the try statement <python:try>.

def register_assert_rewrite(*names: str) -> None:
72def register_assert_rewrite(*names: str) -> None:
73    """Register one or more module names to be rewritten on import.
74
75    This function will make sure that this module or all modules inside
76    the package will get their assert statements rewritten.
77    Thus you should make sure to call this before the module is
78    actually imported, usually in your __init__.py if you are a plugin
79    using a package.
80
81    :param names: The module names to register.
82    """
83    for name in names:
84        if not isinstance(name, str):
85            msg = "expected module names as *args, got {0} instead"  # type: ignore[unreachable]
86            raise TypeError(msg.format(repr(names)))
87    rewrite_hook: RewriteHook
88    for hook in sys.meta_path:
89        if isinstance(hook, rewrite.AssertionRewritingHook):
90            rewrite_hook = hook
91            break
92    else:
93        rewrite_hook = DummyRewriteHook()
94    rewrite_hook.mark_rewrite(*names)

Register one or more module names to be rewritten on import.

This function will make sure that this module or all modules inside the package will get their assert statements rewritten. Thus you should make sure to call this before the module is actually imported, usually in your __init__.py if you are a plugin using a package.

Parameters
  • names: The module names to register.
@classmethod
def set_trace(*args, **kwargs) -> None:
280    @classmethod
281    def set_trace(cls, *args, **kwargs) -> None:
282        """Invoke debugging via ``Pdb.set_trace``, dropping any IO capturing."""
283        frame = sys._getframe().f_back
284        _pdb = cls._init_pdb("set_trace", *args, **kwargs)
285        _pdb.set_trace(frame)

Invoke debugging via Pdb.set_trace, dropping any IO capturing.

def skip(reason: str = '', *, allow_module_level: bool = False) -> NoReturn:
126@_with_exception(Skipped)
127def skip(
128    reason: str = "",
129    *,
130    allow_module_level: bool = False,
131) -> NoReturn:
132    """Skip an executing test with the given message.
133
134    This function should be called only during testing (setup, call or teardown) or
135    during collection by using the ``allow_module_level`` flag.  This function can
136    be called in doctests as well.
137
138    :param reason:
139        The message to show the user as reason for the skip.
140
141    :param allow_module_level:
142        Allows this function to be called at module level.
143        Raising the skip exception at module level will stop
144        the execution of the module and prevent the collection of all tests in the module,
145        even those defined before the `skip` call.
146
147        Defaults to False.
148
149    :raises pytest.skip.Exception:
150        The exception that is raised.
151
152    .. note::
153        It is better to use the :ref:`pytest.mark.skipif ref` marker when
154        possible to declare a test to be skipped under certain conditions
155        like mismatching platforms or dependencies.
156        Similarly, use the ``# doctest: +SKIP`` directive (see :py:data:`doctest.SKIP`)
157        to skip a doctest statically.
158    """
159    __tracebackhide__ = True
160    raise Skipped(msg=reason, allow_module_level=allow_module_level)

Skip an executing test with the given message.

This function should be called only during testing (setup, call or teardown) or during collection by using the allow_module_level flag. This function can be called in doctests as well.

Parameters
  • reason: The message to show the user as reason for the skip.

  • allow_module_level: Allows this function to be called at module level. Raising the skip exception at module level will stop the execution of the module and prevent the collection of all tests in the module, even those defined before the skip call.

    Defaults to False.

Raises
  • pytest.skip.Exception: The exception that is raised.

It is better to use the :ref:pytest.mark.skipif ref marker when possible to declare a test to be skipped under certain conditions like mismatching platforms or dependencies. Similarly, use the # doctest: +SKIP directive (see doctest.SKIP) to skip a doctest statically.

version_tuple = (8, 4, 1)
def warns( expected_warning: type[Warning] | tuple[type[Warning], ...] = <class 'Warning'>, *args: Any, match: str | re.Pattern[str] | None = None, **kwargs: Any) -> _pytest.recwarn.WarningsChecker | typing.Any:
107def warns(
108    expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning,
109    *args: Any,
110    match: str | re.Pattern[str] | None = None,
111    **kwargs: Any,
112) -> WarningsChecker | Any:
113    r"""Assert that code raises a particular class of warning.
114
115    Specifically, the parameter ``expected_warning`` can be a warning class or tuple
116    of warning classes, and the code inside the ``with`` block must issue at least one
117    warning of that class or classes.
118
119    This helper produces a list of :class:`warnings.WarningMessage` objects, one for
120    each warning emitted (regardless of whether it is an ``expected_warning`` or not).
121    Since pytest 8.0, unmatched warnings are also re-emitted when the context closes.
122
123    This function can be used as a context manager::
124
125        >>> import pytest
126        >>> with pytest.warns(RuntimeWarning):
127        ...    warnings.warn("my warning", RuntimeWarning)
128
129    In the context manager form you may use the keyword argument ``match`` to assert
130    that the warning matches a text or regex::
131
132        >>> with pytest.warns(UserWarning, match='must be 0 or None'):
133        ...     warnings.warn("value must be 0 or None", UserWarning)
134
135        >>> with pytest.warns(UserWarning, match=r'must be \d+$'):
136        ...     warnings.warn("value must be 42", UserWarning)
137
138        >>> with pytest.warns(UserWarning):  # catch re-emitted warning
139        ...     with pytest.warns(UserWarning, match=r'must be \d+$'):
140        ...         warnings.warn("this is not here", UserWarning)
141        Traceback (most recent call last):
142          ...
143        Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted...
144
145    **Using with** ``pytest.mark.parametrize``
146
147    When using :ref:`pytest.mark.parametrize ref` it is possible to parametrize tests
148    such that some runs raise a warning and others do not.
149
150    This could be achieved in the same way as with exceptions, see
151    :ref:`parametrizing_conditional_raising` for an example.
152
153    """
154    __tracebackhide__ = True
155    if not args:
156        if kwargs:
157            argnames = ", ".join(sorted(kwargs))
158            raise TypeError(
159                f"Unexpected keyword arguments passed to pytest.warns: {argnames}"
160                "\nUse context-manager form instead?"
161            )
162        return WarningsChecker(expected_warning, match_expr=match, _ispytest=True)
163    else:
164        func = args[0]
165        if not callable(func):
166            raise TypeError(f"{func!r} object (type: {type(func)}) must be callable")
167        with WarningsChecker(expected_warning, _ispytest=True):
168            return func(*args[1:], **kwargs)

Assert that code raises a particular class of warning.

Specifically, the parameter expected_warning can be a warning class or tuple of warning classes, and the code inside the with block must issue at least one warning of that class or classes.

This helper produces a list of warnings.WarningMessage objects, one for each warning emitted (regardless of whether it is an expected_warning or not). Since pytest 8.0, unmatched warnings are also re-emitted when the context closes.

This function can be used as a context manager::

>>> import pytest
>>> with pytest.warns(RuntimeWarning):
...    warnings.warn("my warning", RuntimeWarning)

In the context manager form you may use the keyword argument match to assert that the warning matches a text or regex::

>>> with pytest.warns(UserWarning, match='must be 0 or None'):
...     warnings.warn("value must be 0 or None", UserWarning)

>>> with pytest.warns(UserWarning, match=r'must be \d+$'):
...     warnings.warn("value must be 42", UserWarning)

>>> with pytest.warns(UserWarning):  # catch re-emitted warning
...     with pytest.warns(UserWarning, match=r'must be \d+$'):
...         warnings.warn("this is not here", UserWarning)
Traceback (most recent call last):
  ...
Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted...

Using with pytest.mark.parametrize

When using :ref:pytest.mark.parametrize ref it is possible to parametrize tests such that some runs raise a warning and others do not.

This could be achieved in the same way as with exceptions, see :ref:parametrizing_conditional_raising for an example.

def xfail(reason: str = '') -> NoReturn:
185@_with_exception(XFailed)
186def xfail(reason: str = "") -> NoReturn:
187    """Imperatively xfail an executing test or setup function with the given reason.
188
189    This function should be called only during testing (setup, call or teardown).
190
191    No other code is executed after using ``xfail()`` (it is implemented
192    internally by raising an exception).
193
194    :param reason:
195        The message to show the user as reason for the xfail.
196
197    .. note::
198        It is better to use the :ref:`pytest.mark.xfail ref` marker when
199        possible to declare a test to be xfailed under certain conditions
200        like known bugs or missing features.
201
202    :raises pytest.xfail.Exception:
203        The exception that is raised.
204    """
205    __tracebackhide__ = True
206    raise XFailed(reason)

Imperatively xfail an executing test or setup function with the given reason.

This function should be called only during testing (setup, call or teardown).

No other code is executed after using xfail() (it is implemented internally by raising an exception).

Parameters
  • reason: The message to show the user as reason for the xfail.

It is better to use the :ref:pytest.mark.xfail ref marker when possible to declare a test to be xfailed under certain conditions like known bugs or missing features.

Raises
  • pytest.xfail.Exception: The exception that is raised.
def yield_fixture( fixture_function=None, *args, scope='function', params=None, autouse=False, ids=None, name=None):
1398def yield_fixture(
1399    fixture_function=None,
1400    *args,
1401    scope="function",
1402    params=None,
1403    autouse=False,
1404    ids=None,
1405    name=None,
1406):
1407    """(Return a) decorator to mark a yield-fixture factory function.
1408
1409    .. deprecated:: 3.0
1410        Use :py:func:`pytest.fixture` directly instead.
1411    """
1412    warnings.warn(YIELD_FIXTURE, stacklevel=2)
1413    return fixture(
1414        fixture_function,
1415        *args,
1416        scope=scope,
1417        params=params,
1418        autouse=autouse,
1419        ids=ids,
1420        name=name,
1421    )

(Return a) decorator to mark a yield-fixture factory function.

Deprecated since version 3.0: Use pytest.fixture() directly instead.