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]
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.
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:
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:
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:
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:
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.
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.
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.
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.
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
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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)
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.
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.
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.
An error during collection, contains a custom message.
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.
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] = []
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.
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.
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).
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
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()
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
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.
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 )
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()
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
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.
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.
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.
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 isNone
. - skip: If
True
, raisepytest.skip()
if option is undeclared or has aNone
value. Note that even ifTrue
, if a default was specified it will be returned instead of a skip.
1757 def getvalue(self, name: str, path=None): 1758 """Deprecated, use getoption() instead.""" 1759 return self.getoption(name)
Deprecated, use getoption() instead.
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.
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
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.
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.
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.
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.
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.
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
.
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.
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()
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.
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
.
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.
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
.
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.
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
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.
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.
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__()
.
587 @classmethod 588 def for_later(cls) -> ExceptionInfo[E]: 589 """Return an unfilled ExceptionInfo.""" 590 return cls(None, _ispytest=True)
Return an unfilled ExceptionInfo.
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()
.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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__
usingre.search()
.To match a literal string that may contain :ref:
special characters <re-syntax>
, the pattern can first be escaped withre.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
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.
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
.
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.
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
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".
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])
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.
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).
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)
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.
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".
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.
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).
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.
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.
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.
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.
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.
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.
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.
497 @property 498 def session(self) -> Session: 499 """Pytest session object.""" 500 return self._pyfuncitem.session
Pytest session object.
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.
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(...)
.
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.
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
- pytest.FixtureLookupError: If the given fixture could not be found.
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
usingoriginalname
. - 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]
).
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()
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.
1632 @property 1633 def function(self): 1634 """Underlying python 'function' object.""" 1635 return getimfunc(self.obj)
Underlying python 'function' object.
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.
1670 def runtest(self) -> None: 1671 """Execute the underlying test function.""" 1672 self.ihook.pytest_pyfunc_call(pyfuncitem=self)
Execute the underlying test function.
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.
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.
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)
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).
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}")
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))
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.
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
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 )
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.
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
.
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.
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
.
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.
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()
.
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()
).
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()
).
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.
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?
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?
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.
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.
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.
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
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.
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.
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.
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.
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)
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.
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.
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.
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.
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
- filter_: A custom
logging.Filter
object.
New in version 7.5.
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.
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:
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.
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:
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.
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
.When called in any other case, it returns a new
MarkDecorator
instance with the originalMarkDecorator
'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()
.
347 def __init__(self, mark: Mark, *, _ispytest: bool = False) -> None: 348 """:meta private:""" 349 check_ispytest(_ispytest) 350 self.mark = mark
:meta private:
357 @property 358 def args(self) -> tuple[Any, ...]: 359 """Alias for mark.args.""" 360 return self.mark.args
Alias for mark.args.
362 @property 363 def kwargs(self) -> Mapping[str, Any]: 364 """Alias for mark.kwargs.""" 365 return self.mark.kwargs
Alias for mark.kwargs.
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:
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.
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
.
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.
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"]] = {}
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 typestring
,int
,float
,bool
, orNone
. They are mapped to the corresponding index inargvalues
.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. ReturningNone
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.
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.
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.
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.
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
).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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.
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
.
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 )
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)
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.
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.
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] = {}
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
.
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", ...)
.
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)
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))
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.
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.
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
andpathlist
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
andpathlist
in the absence of an ini-file.Defaults to
string
ifNone
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>()
.
20from _pytest.config import PytestPluginManager
Warning emitted by the pytest assert rewrite module.
27from _pytest.fixtures import FixtureDef
Warning emitted by the cache plugin in various situations.
41from _pytest.mark import MarkGenerator
Warning emitted when pytest is not able to collect a file or symbol in a module.
34from _pytest.logging import LogCaptureFixture
Warning emitted for configuration issues.
48from _pytest.outcomes import exit
Warning class for features that will be removed in a future version.
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.
138 "PytestUnhandledThreadExceptionWarning",
When the lsof plugin finds leaked fds.
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 andpytest_plugins
global variables found in plugins being loaded. conftest.py
loading during start-up.
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:
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:
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
.
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.
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:
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:
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:
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:
821 def consider_env(self) -> None: 822 """:meta private:""" 823 self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
:meta private:
825 def consider_module(self, mod: types.ModuleType) -> None: 826 """:meta private:""" 827 self._import_plugin_specs(getattr(mod, "pytest_plugins", []))
:meta private:
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.
54from _pytest.pytester import LineMatcher
Warning class for features that will be removed in pytest 9.
75from _pytest.terminal import TerminalReporter
Warning emitted when a test function returns a value other than None
.
See :ref:return-not-none
for details.
108 "ExceptionInfo",
An unhandled exception occurred in a ~threading.Thread
.
Such exceptions don't propagate normally.
86from _pytest.warning_types import PytestReturnNotNoneWarning
Warning emitted on use of unknown markers.
See :ref:mark
for details.
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.
14from _pytest.config import Config
Base class for all warnings emitted by pytest.
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.
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")
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.
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
.
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.
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"...")
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.
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.
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.
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.
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.
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.
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.
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.
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.
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``).
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
- config:
A pytest config.
See
parseconfig()
andparseconfigure()
for creating it. - arg: Path to the file. :returns: The node.
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.
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.
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
.
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.
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.
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.
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.
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
.
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.
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.
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.
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.
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.
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.
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.
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 tostr
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 callssubprocess.Popen
withstdin=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 thestdin
parameter insubprocess.Popen
. :returns: The result.
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.
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"
.
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.
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.
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.
Common base class for all non-exit exceptions.
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 ofmatch
and/orcheck
The type is checked with
isinstance()
, and does not need to be an exact match. If that is wanted you can use thecheck
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)):
...
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
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__)
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
orRaisesExc
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 ofRaisesExc.matches()
.It does not care about the order of the exceptions, so
RaisesGroup(ValueError, TypeError)
is equivalent toRaisesGroup(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__
usingre.search()
.To match a literal string that may contain :ref:
special characters <re-syntax>
, the pattern can first be escaped withre.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 returnsTrue
it will be considered a match, if not it will be considered a failed match. :kwparam bool allow_unwrapped: If expecting a single exception orRaisesExc
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 passingRaisesGroup
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.
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 )
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)
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)})"
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
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
.
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."""
~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.
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}
.
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}
.
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.
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.
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")
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.
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"
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"
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.
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
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.
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.
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)
Signals that the test run was interrupted.
Signals a stop as failed test run.
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.
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.
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.
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__ = ()
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
.
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
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:
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-"
andnumbered=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.
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.
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>
.
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.
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.
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
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
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)
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.
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.
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])
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)
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()
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()
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
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()
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)
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)
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
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)
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
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()
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 )
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)
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)
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)
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)
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)
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)
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.
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.
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)
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.
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.
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.
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.
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))
See Pytester.makefile()
.
112 def makeini(self, source) -> LEGACY_PATH: 113 """See :meth:`Pytester.makeini`.""" 114 return legacy_path(self._pytester.makeini(source))
See Pytester.makeini()
.
116 def getinicfg(self, source: str) -> SectionWrapper: 117 """See :meth:`Pytester.getinicfg`.""" 118 return self._pytester.getinicfg(source)
See Pytester.getinicfg()
.
136 def mkdir(self, name) -> LEGACY_PATH: 137 """See :meth:`Pytester.mkdir`.""" 138 return legacy_path(self._pytester.mkdir(name))
See Pytester.mkdir()
.
140 def mkpydir(self, name) -> LEGACY_PATH: 141 """See :meth:`Pytester.mkpydir`.""" 142 return legacy_path(self._pytester.mkpydir(name))
See Pytester.mkpydir()
.
148 def getnode(self, config: Config, arg) -> Item | Collector | None: 149 """See :meth:`Pytester.getnode`.""" 150 return self._pytester.getnode(config, arg)
See Pytester.getnode()
.
156 def genitems(self, colitems: list[Item | Collector]) -> list[Item]: 157 """See :meth:`Pytester.genitems`.""" 158 return self._pytester.genitems(colitems)
See Pytester.genitems()
.
160 def runitem(self, source): 161 """See :meth:`Pytester.runitem`.""" 162 return self._pytester.runitem(source)
See Pytester.runitem()
.
182 def runpytest(self, *args, **kwargs) -> RunResult: 183 """See :meth:`Pytester.runpytest`.""" 184 return self._pytester.runpytest(*args, **kwargs)
See Pytester.runpytest()
.
194 def getitem(self, source, funcname="test_func"): 195 """See :meth:`Pytester.getitem`.""" 196 return self._pytester.getitem(source, funcname)
See Pytester.getitem()
.
198 def getitems(self, source): 199 """See :meth:`Pytester.getitems`.""" 200 return self._pytester.getitems(source)
See Pytester.getitems()
.
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)
See Pytester.popen()
.
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)
See Pytester.run()
.
227 def runpython(self, script) -> RunResult: 228 """See :meth:`Pytester.runpython`.""" 229 return self._pytester.runpython(script)
See Pytester.runpython()
.
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)
See Pytester.spawn()
.
Common base class for all non-exit exceptions.
Error in pytest usage or invocation.
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
.
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.
190 @property 191 def list(self) -> list[warnings.WarningMessage]: 192 """The list of recorded warnings.""" 193 return self._list
The list of recorded warnings.
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.
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. eithera
orb
or if the absolute tolerance is met. Because the relative tolerance is calculated w.r.t. botha
andb
, this test is symmetric (i.e. neithera
norb
is a "reference value"). You have to specify an absolute tolerance if you want to compare to0.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 betweena
andb
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 ofb
as the reference value. Support for comparing sequences is provided bynumpy.allclose()
. More information: :std:doc:numpy:reference/generated/numpy.isclose
.unittest.TestCase.assertAlmostEqual(a, b)
: True ifa
andb
are within an absolute tolerance of1e-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 ofunittest.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 ofb
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
.
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.
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.
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.
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 assys.exit()
.
Raises
- pytest.exit.Exception: The exception that is raised.
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.
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 astr
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>')
.
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.
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 raiseModuleNotFoundError
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.
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.
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.
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/orcheck
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>
.
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.
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.
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.
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.
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.
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.