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 Mark 37from _pytest.mark import MARK_GEN as mark 38from _pytest.mark import MarkDecorator 39from _pytest.mark import MarkGenerator 40from _pytest.mark import param 41from _pytest.monkeypatch import MonkeyPatch 42from _pytest.nodes import Collector 43from _pytest.nodes import Directory 44from _pytest.nodes import File 45from _pytest.nodes import Item 46from _pytest.outcomes import exit 47from _pytest.outcomes import fail 48from _pytest.outcomes import importorskip 49from _pytest.outcomes import skip 50from _pytest.outcomes import xfail 51from _pytest.pytester import HookRecorder 52from _pytest.pytester import LineMatcher 53from _pytest.pytester import Pytester 54from _pytest.pytester import RecordedHookCall 55from _pytest.pytester import RunResult 56from _pytest.python import Class 57from _pytest.python import Function 58from _pytest.python import Metafunc 59from _pytest.python import Module 60from _pytest.python import Package 61from _pytest.python_api import approx 62from _pytest.python_api import raises 63from _pytest.recwarn import deprecated_call 64from _pytest.recwarn import WarningsRecorder 65from _pytest.recwarn import warns 66from _pytest.reports import CollectReport 67from _pytest.reports import TestReport 68from _pytest.runner import CallInfo 69from _pytest.stash import Stash 70from _pytest.stash import StashKey 71from _pytest.terminal import TestShortLogReport 72from _pytest.tmpdir import TempPathFactory 73from _pytest.warning_types import PytestAssertRewriteWarning 74from _pytest.warning_types import PytestCacheWarning 75from _pytest.warning_types import PytestCollectionWarning 76from _pytest.warning_types import PytestConfigWarning 77from _pytest.warning_types import PytestDeprecationWarning 78from _pytest.warning_types import PytestExperimentalApiWarning 79from _pytest.warning_types import PytestRemovedIn9Warning 80from _pytest.warning_types import PytestReturnNotNoneWarning 81from _pytest.warning_types import PytestUnhandledCoroutineWarning 82from _pytest.warning_types import PytestUnhandledThreadExceptionWarning 83from _pytest.warning_types import PytestUnknownMarkWarning 84from _pytest.warning_types import PytestUnraisableExceptionWarning 85from _pytest.warning_types import PytestWarning 86 87 88set_trace = __pytestPDB.set_trace 89 90 91__all__ = [ 92 "__version__", 93 "approx", 94 "Cache", 95 "CallInfo", 96 "CaptureFixture", 97 "Class", 98 "cmdline", 99 "Collector", 100 "CollectReport", 101 "Config", 102 "console_main", 103 "deprecated_call", 104 "Dir", 105 "Directory", 106 "DoctestItem", 107 "exit", 108 "ExceptionInfo", 109 "ExitCode", 110 "fail", 111 "File", 112 "fixture", 113 "FixtureDef", 114 "FixtureLookupError", 115 "FixtureRequest", 116 "freeze_includes", 117 "Function", 118 "hookimpl", 119 "HookRecorder", 120 "hookspec", 121 "importorskip", 122 "Item", 123 "LineMatcher", 124 "LogCaptureFixture", 125 "main", 126 "mark", 127 "Mark", 128 "MarkDecorator", 129 "MarkGenerator", 130 "Metafunc", 131 "Module", 132 "MonkeyPatch", 133 "OptionGroup", 134 "Package", 135 "param", 136 "Parser", 137 "PytestAssertRewriteWarning", 138 "PytestCacheWarning", 139 "PytestCollectionWarning", 140 "PytestConfigWarning", 141 "PytestDeprecationWarning", 142 "PytestExperimentalApiWarning", 143 "PytestRemovedIn9Warning", 144 "PytestReturnNotNoneWarning", 145 "Pytester", 146 "PytestPluginManager", 147 "PytestUnhandledCoroutineWarning", 148 "PytestUnhandledThreadExceptionWarning", 149 "PytestUnknownMarkWarning", 150 "PytestUnraisableExceptionWarning", 151 "PytestWarning", 152 "raises", 153 "RecordedHookCall", 154 "register_assert_rewrite", 155 "RunResult", 156 "Session", 157 "set_trace", 158 "skip", 159 "Stash", 160 "StashKey", 161 "version_tuple", 162 "TempdirFactory", 163 "TempPathFactory", 164 "Testdir", 165 "TestReport", 166 "TestShortLogReport", 167 "UsageError", 168 "WarningsRecorder", 169 "warns", 170 "xfail", 171 "yield_fixture", 172]
529def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: 530 """Assert that two numbers (or two ordered sequences of numbers) are equal to each other 531 within some tolerance. 532 533 Due to the :doc:`python:tutorial/floatingpoint`, numbers that we 534 would intuitively expect to be equal are not always so:: 535 536 >>> 0.1 + 0.2 == 0.3 537 False 538 539 This problem is commonly encountered when writing tests, e.g. when making 540 sure that floating-point values are what you expect them to be. One way to 541 deal with this problem is to assert that two floating-point numbers are 542 equal to within some appropriate tolerance:: 543 544 >>> abs((0.1 + 0.2) - 0.3) < 1e-6 545 True 546 547 However, comparisons like this are tedious to write and difficult to 548 understand. Furthermore, absolute comparisons like the one above are 549 usually discouraged because there's no tolerance that works well for all 550 situations. ``1e-6`` is good for numbers around ``1``, but too small for 551 very big numbers and too big for very small ones. It's better to express 552 the tolerance as a fraction of the expected value, but relative comparisons 553 like that are even more difficult to write correctly and concisely. 554 555 The ``approx`` class performs floating-point comparisons using a syntax 556 that's as intuitive as possible:: 557 558 >>> from pytest import approx 559 >>> 0.1 + 0.2 == approx(0.3) 560 True 561 562 The same syntax also works for ordered sequences of numbers:: 563 564 >>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6)) 565 True 566 567 ``numpy`` arrays:: 568 569 >>> import numpy as np # doctest: +SKIP 570 >>> np.array([0.1, 0.2]) + np.array([0.2, 0.4]) == approx(np.array([0.3, 0.6])) # doctest: +SKIP 571 True 572 573 And for a ``numpy`` array against a scalar:: 574 575 >>> import numpy as np # doctest: +SKIP 576 >>> np.array([0.1, 0.2]) + np.array([0.2, 0.1]) == approx(0.3) # doctest: +SKIP 577 True 578 579 Only ordered sequences are supported, because ``approx`` needs 580 to infer the relative position of the sequences without ambiguity. This means 581 ``sets`` and other unordered sequences are not supported. 582 583 Finally, dictionary *values* can also be compared:: 584 585 >>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6}) 586 True 587 588 The comparison will be true if both mappings have the same keys and their 589 respective values match the expected tolerances. 590 591 **Tolerances** 592 593 By default, ``approx`` considers numbers within a relative tolerance of 594 ``1e-6`` (i.e. one part in a million) of its expected value to be equal. 595 This treatment would lead to surprising results if the expected value was 596 ``0.0``, because nothing but ``0.0`` itself is relatively close to ``0.0``. 597 To handle this case less surprisingly, ``approx`` also considers numbers 598 within an absolute tolerance of ``1e-12`` of its expected value to be 599 equal. Infinity and NaN are special cases. Infinity is only considered 600 equal to itself, regardless of the relative tolerance. NaN is not 601 considered equal to anything by default, but you can make it be equal to 602 itself by setting the ``nan_ok`` argument to True. (This is meant to 603 facilitate comparing arrays that use NaN to mean "no data".) 604 605 Both the relative and absolute tolerances can be changed by passing 606 arguments to the ``approx`` constructor:: 607 608 >>> 1.0001 == approx(1) 609 False 610 >>> 1.0001 == approx(1, rel=1e-3) 611 True 612 >>> 1.0001 == approx(1, abs=1e-3) 613 True 614 615 If you specify ``abs`` but not ``rel``, the comparison will not consider 616 the relative tolerance at all. In other words, two numbers that are within 617 the default relative tolerance of ``1e-6`` will still be considered unequal 618 if they exceed the specified absolute tolerance. If you specify both 619 ``abs`` and ``rel``, the numbers will be considered equal if either 620 tolerance is met:: 621 622 >>> 1 + 1e-8 == approx(1) 623 True 624 >>> 1 + 1e-8 == approx(1, abs=1e-12) 625 False 626 >>> 1 + 1e-8 == approx(1, rel=1e-6, abs=1e-12) 627 True 628 629 You can also use ``approx`` to compare nonnumeric types, or dicts and 630 sequences containing nonnumeric types, in which case it falls back to 631 strict equality. This can be useful for comparing dicts and sequences that 632 can contain optional values:: 633 634 >>> {"required": 1.0000005, "optional": None} == approx({"required": 1, "optional": None}) 635 True 636 >>> [None, 1.0000005] == approx([None,1]) 637 True 638 >>> ["foo", 1.0000005] == approx([None,1]) 639 False 640 641 If you're thinking about using ``approx``, then you might want to know how 642 it compares to other good ways of comparing floating-point numbers. All of 643 these algorithms are based on relative and absolute tolerances and should 644 agree for the most part, but they do have meaningful differences: 645 646 - ``math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)``: True if the relative 647 tolerance is met w.r.t. either ``a`` or ``b`` or if the absolute 648 tolerance is met. Because the relative tolerance is calculated w.r.t. 649 both ``a`` and ``b``, this test is symmetric (i.e. neither ``a`` nor 650 ``b`` is a "reference value"). You have to specify an absolute tolerance 651 if you want to compare to ``0.0`` because there is no tolerance by 652 default. More information: :py:func:`math.isclose`. 653 654 - ``numpy.isclose(a, b, rtol=1e-5, atol=1e-8)``: True if the difference 655 between ``a`` and ``b`` is less that the sum of the relative tolerance 656 w.r.t. ``b`` and the absolute tolerance. Because the relative tolerance 657 is only calculated w.r.t. ``b``, this test is asymmetric and you can 658 think of ``b`` as the reference value. Support for comparing sequences 659 is provided by :py:func:`numpy.allclose`. More information: 660 :std:doc:`numpy:reference/generated/numpy.isclose`. 661 662 - ``unittest.TestCase.assertAlmostEqual(a, b)``: True if ``a`` and ``b`` 663 are within an absolute tolerance of ``1e-7``. No relative tolerance is 664 considered , so this function is not appropriate for very large or very 665 small numbers. Also, it's only available in subclasses of ``unittest.TestCase`` 666 and it's ugly because it doesn't follow PEP8. More information: 667 :py:meth:`unittest.TestCase.assertAlmostEqual`. 668 669 - ``a == pytest.approx(b, rel=1e-6, abs=1e-12)``: True if the relative 670 tolerance is met w.r.t. ``b`` or if the absolute tolerance is met. 671 Because the relative tolerance is only calculated w.r.t. ``b``, this test 672 is asymmetric and you can think of ``b`` as the reference value. In the 673 special case that you explicitly specify an absolute tolerance but not a 674 relative tolerance, only the absolute tolerance is considered. 675 676 .. note:: 677 678 ``approx`` can handle numpy arrays, but we recommend the 679 specialised test helpers in :std:doc:`numpy:reference/routines.testing` 680 if you need support for comparisons, NaNs, or ULP-based tolerances. 681 682 To match strings using regex, you can use 683 `Matches <https://github.com/asottile/re-assert#re_assertmatchespattern-str-args-kwargs>`_ 684 from the 685 `re_assert package <https://github.com/asottile/re-assert>`_. 686 687 .. warning:: 688 689 .. versionchanged:: 3.2 690 691 In order to avoid inconsistent behavior, :py:exc:`TypeError` is 692 raised for ``>``, ``>=``, ``<`` and ``<=`` comparisons. 693 The example below illustrates the problem:: 694 695 assert approx(0.1) > 0.1 + 1e-10 # calls approx(0.1).__gt__(0.1 + 1e-10) 696 assert 0.1 + 1e-10 > approx(0.1) # calls approx(0.1).__lt__(0.1 + 1e-10) 697 698 In the second example one expects ``approx(0.1).__le__(0.1 + 1e-10)`` 699 to be called. But instead, ``approx(0.1).__lt__(0.1 + 1e-10)`` is used to 700 comparison. This is because the call hierarchy of rich comparisons 701 follows a fixed behavior. More information: :py:meth:`object.__ge__` 702 703 .. versionchanged:: 3.7.1 704 ``approx`` raises ``TypeError`` when it encounters a dict value or 705 sequence element of nonnumeric type. 706 707 .. versionchanged:: 6.1.0 708 ``approx`` falls back to strict equality for nonnumeric types instead 709 of raising ``TypeError``. 710 """ 711 # Delegate the comparison to a class that knows how to deal with the type 712 # of the expected value (e.g. int, float, list, dict, numpy.array, etc). 713 # 714 # The primary responsibility of these classes is to implement ``__eq__()`` 715 # and ``__repr__()``. The former is used to actually check if some 716 # "actual" value is equivalent to the given expected value within the 717 # allowed tolerance. The latter is used to show the user the expected 718 # value and tolerance, in the case that a test failed. 719 # 720 # The actual logic for making approximate comparisons can be found in 721 # ApproxScalar, which is used to compare individual numbers. All of the 722 # other Approx classes eventually delegate to this class. The ApproxBase 723 # class provides some convenient methods and overloads, but isn't really 724 # essential. 725 726 __tracebackhide__ = True 727 728 if isinstance(expected, Decimal): 729 cls: type[ApproxBase] = ApproxDecimal 730 elif isinstance(expected, Mapping): 731 cls = ApproxMapping 732 elif _is_numpy_array(expected): 733 expected = _as_numpy_array(expected) 734 cls = ApproxNumpy 735 elif _is_sequence_like(expected): 736 cls = ApproxSequenceLike 737 elif isinstance(expected, Collection) and not isinstance(expected, (str, bytes)): 738 msg = f"pytest.approx() only supports ordered sequences, but got: {expected!r}" 739 raise TypeError(msg) 740 else: 741 cls = ApproxScalar 742 743 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
You can also use approx
to compare nonnumeric types, or dicts and
sequences containing nonnumeric 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 .
.. 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 nonnumeric type.
Changed in version 6.1.0:
approx
falls back to strict equality for nonnumeric types instead
of raising TypeError
.
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.
271@final 272@dataclasses.dataclass 273class CallInfo(Generic[TResult]): 274 """Result/Exception info of a function invocation.""" 275 276 _result: TResult | None 277 #: The captured exception of the call, if it raised. 278 excinfo: ExceptionInfo[BaseException] | None 279 #: The system time when the call started, in seconds since the epoch. 280 start: float 281 #: The system time when the call ended, in seconds since the epoch. 282 stop: float 283 #: The call duration, in seconds. 284 duration: float 285 #: The context of invocation: "collect", "setup", "call" or "teardown". 286 when: Literal["collect", "setup", "call", "teardown"] 287 288 def __init__( 289 self, 290 result: TResult | None, 291 excinfo: ExceptionInfo[BaseException] | None, 292 start: float, 293 stop: float, 294 duration: float, 295 when: Literal["collect", "setup", "call", "teardown"], 296 *, 297 _ispytest: bool = False, 298 ) -> None: 299 check_ispytest(_ispytest) 300 self._result = result 301 self.excinfo = excinfo 302 self.start = start 303 self.stop = stop 304 self.duration = duration 305 self.when = when 306 307 @property 308 def result(self) -> TResult: 309 """The return value of the call, if it didn't raise. 310 311 Can only be accessed if excinfo is None. 312 """ 313 if self.excinfo is not None: 314 raise AttributeError(f"{self!r} has no valid result") 315 # The cast is safe because an exception wasn't raised, hence 316 # _result has the expected function return type (which may be 317 # None, that's why a cast and not an assert). 318 return cast(TResult, self._result) 319 320 @classmethod 321 def from_call( 322 cls, 323 func: Callable[[], TResult], 324 when: Literal["collect", "setup", "call", "teardown"], 325 reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None, 326 ) -> CallInfo[TResult]: 327 """Call func, wrapping the result in a CallInfo. 328 329 :param func: 330 The function to call. Called without arguments. 331 :type func: Callable[[], _pytest.runner.TResult] 332 :param when: 333 The phase in which the function is called. 334 :param reraise: 335 Exception or exceptions that shall propagate if raised by the 336 function, instead of being wrapped in the CallInfo. 337 """ 338 excinfo = None 339 start = timing.time() 340 precise_start = timing.perf_counter() 341 try: 342 result: TResult | None = func() 343 except BaseException: 344 excinfo = ExceptionInfo.from_current() 345 if reraise is not None and isinstance(excinfo.value, reraise): 346 raise 347 result = None 348 # use the perf counter 349 precise_stop = timing.perf_counter() 350 duration = precise_stop - precise_start 351 stop = timing.time() 352 return cls( 353 start=start, 354 stop=stop, 355 duration=duration, 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.
288 def __init__( 289 self, 290 result: TResult | None, 291 excinfo: ExceptionInfo[BaseException] | None, 292 start: float, 293 stop: float, 294 duration: float, 295 when: Literal["collect", "setup", "call", "teardown"], 296 *, 297 _ispytest: bool = False, 298 ) -> None: 299 check_ispytest(_ispytest) 300 self._result = result 301 self.excinfo = excinfo 302 self.start = start 303 self.stop = stop 304 self.duration = duration 305 self.when = when
307 @property 308 def result(self) -> TResult: 309 """The return value of the call, if it didn't raise. 310 311 Can only be accessed if excinfo is None. 312 """ 313 if self.excinfo is not None: 314 raise AttributeError(f"{self!r} has no valid result") 315 # The cast is safe because an exception wasn't raised, hence 316 # _result has the expected function return type (which may be 317 # None, that's why a cast and not an assert). 318 return cast(TResult, self._result)
The return value of the call, if it didn't raise.
Can only be accessed if excinfo is None.
320 @classmethod 321 def from_call( 322 cls, 323 func: Callable[[], TResult], 324 when: Literal["collect", "setup", "call", "teardown"], 325 reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None, 326 ) -> CallInfo[TResult]: 327 """Call func, wrapping the result in a CallInfo. 328 329 :param func: 330 The function to call. Called without arguments. 331 :type func: Callable[[], _pytest.runner.TResult] 332 :param when: 333 The phase in which the function is called. 334 :param reraise: 335 Exception or exceptions that shall propagate if raised by the 336 function, instead of being wrapped in the CallInfo. 337 """ 338 excinfo = None 339 start = timing.time() 340 precise_start = timing.perf_counter() 341 try: 342 result: TResult | None = func() 343 except BaseException: 344 excinfo = ExceptionInfo.from_current() 345 if reraise is not None and isinstance(excinfo.value, reraise): 346 raise 347 result = None 348 # use the perf counter 349 precise_stop = timing.perf_counter() 350 duration = precise_stop - precise_start 351 stop = timing.time() 352 return cls( 353 start=start, 354 stop=stop, 355 duration=duration, 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.
897class CaptureFixture(Generic[AnyStr]): 898 """Object returned by the :fixture:`capsys`, :fixture:`capsysbinary`, 899 :fixture:`capfd` and :fixture:`capfdbinary` fixtures.""" 900 901 def __init__( 902 self, 903 captureclass: type[CaptureBase[AnyStr]], 904 request: SubRequest, 905 *, 906 _ispytest: bool = False, 907 ) -> None: 908 check_ispytest(_ispytest) 909 self.captureclass: type[CaptureBase[AnyStr]] = captureclass 910 self.request = request 911 self._capture: MultiCapture[AnyStr] | None = None 912 self._captured_out: AnyStr = self.captureclass.EMPTY_BUFFER 913 self._captured_err: AnyStr = self.captureclass.EMPTY_BUFFER 914 915 def _start(self) -> None: 916 if self._capture is None: 917 self._capture = MultiCapture( 918 in_=None, 919 out=self.captureclass(1), 920 err=self.captureclass(2), 921 ) 922 self._capture.start_capturing() 923 924 def close(self) -> None: 925 if self._capture is not None: 926 out, err = self._capture.pop_outerr_to_orig() 927 self._captured_out += out 928 self._captured_err += err 929 self._capture.stop_capturing() 930 self._capture = None 931 932 def readouterr(self) -> CaptureResult[AnyStr]: 933 """Read and return the captured output so far, resetting the internal 934 buffer. 935 936 :returns: 937 The captured content as a namedtuple with ``out`` and ``err`` 938 string attributes. 939 """ 940 captured_out, captured_err = self._captured_out, self._captured_err 941 if self._capture is not None: 942 out, err = self._capture.readouterr() 943 captured_out += out 944 captured_err += err 945 self._captured_out = self.captureclass.EMPTY_BUFFER 946 self._captured_err = self.captureclass.EMPTY_BUFFER 947 return CaptureResult(captured_out, captured_err) 948 949 def _suspend(self) -> None: 950 """Suspend this fixture's own capturing temporarily.""" 951 if self._capture is not None: 952 self._capture.suspend_capturing() 953 954 def _resume(self) -> None: 955 """Resume this fixture's own capturing temporarily.""" 956 if self._capture is not None: 957 self._capture.resume_capturing() 958 959 def _is_started(self) -> bool: 960 """Whether actively capturing -- not disabled or closed.""" 961 if self._capture is not None: 962 return self._capture.is_started() 963 return False 964 965 @contextlib.contextmanager 966 def disabled(self) -> Generator[None]: 967 """Temporarily disable capturing while inside the ``with`` block.""" 968 capmanager: CaptureManager = self.request.config.pluginmanager.getplugin( 969 "capturemanager" 970 ) 971 with capmanager.global_and_fixture_disabled(): 972 yield
Object returned by the :fixture:capsys
, :fixture:capsysbinary
,
:fixture:capfd
and :fixture:capfdbinary
fixtures.
901 def __init__( 902 self, 903 captureclass: type[CaptureBase[AnyStr]], 904 request: SubRequest, 905 *, 906 _ispytest: bool = False, 907 ) -> None: 908 check_ispytest(_ispytest) 909 self.captureclass: type[CaptureBase[AnyStr]] = captureclass 910 self.request = request 911 self._capture: MultiCapture[AnyStr] | None = None 912 self._captured_out: AnyStr = self.captureclass.EMPTY_BUFFER 913 self._captured_err: AnyStr = self.captureclass.EMPTY_BUFFER
932 def readouterr(self) -> CaptureResult[AnyStr]: 933 """Read and return the captured output so far, resetting the internal 934 buffer. 935 936 :returns: 937 The captured content as a namedtuple with ``out`` and ``err`` 938 string attributes. 939 """ 940 captured_out, captured_err = self._captured_out, self._captured_err 941 if self._capture is not None: 942 out, err = self._capture.readouterr() 943 captured_out += out 944 captured_err += err 945 self._captured_out = self.captureclass.EMPTY_BUFFER 946 self._captured_err = self.captureclass.EMPTY_BUFFER 947 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.
965 @contextlib.contextmanager 966 def disabled(self) -> Generator[None]: 967 """Temporarily disable capturing while inside the ``with`` block.""" 968 capmanager: CaptureManager = self.request.config.pluginmanager.getplugin( 969 "capturemanager" 970 ) 971 with capmanager.global_and_fixture_disabled(): 972 yield
Temporarily disable capturing while inside the with
block.
732class Class(PyCollector): 733 """Collector for test methods (and nested classes) in a Python class.""" 734 735 @classmethod 736 def from_parent(cls, parent, *, name, obj=None, **kw) -> Self: # type: ignore[override] 737 """The public constructor.""" 738 return super().from_parent(name=name, parent=parent, **kw) 739 740 def newinstance(self): 741 return self.obj() 742 743 def collect(self) -> Iterable[nodes.Item | nodes.Collector]: 744 if not safe_getattr(self.obj, "__test__", True): 745 return [] 746 if hasinit(self.obj): 747 assert self.parent is not None 748 self.warn( 749 PytestCollectionWarning( 750 f"cannot collect test class {self.obj.__name__!r} because it has a " 751 f"__init__ constructor (from: {self.parent.nodeid})" 752 ) 753 ) 754 return [] 755 elif hasnew(self.obj): 756 assert self.parent is not None 757 self.warn( 758 PytestCollectionWarning( 759 f"cannot collect test class {self.obj.__name__!r} because it has a " 760 f"__new__ constructor (from: {self.parent.nodeid})" 761 ) 762 ) 763 return [] 764 765 self._register_setup_class_fixture() 766 self._register_setup_method_fixture() 767 768 self.session._fixturemanager.parsefactories(self.newinstance(), self.nodeid) 769 770 return super().collect() 771 772 def _register_setup_class_fixture(self) -> None: 773 """Register an autouse, class scoped fixture into the collected class object 774 that invokes setup_class/teardown_class if either or both are available. 775 776 Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with 777 other fixtures (#517). 778 """ 779 setup_class = _get_first_non_fixture_func(self.obj, ("setup_class",)) 780 teardown_class = _get_first_non_fixture_func(self.obj, ("teardown_class",)) 781 if setup_class is None and teardown_class is None: 782 return 783 784 def xunit_setup_class_fixture(request) -> Generator[None]: 785 cls = request.cls 786 if setup_class is not None: 787 func = getimfunc(setup_class) 788 _call_with_optional_argument(func, cls) 789 yield 790 if teardown_class is not None: 791 func = getimfunc(teardown_class) 792 _call_with_optional_argument(func, cls) 793 794 self.session._fixturemanager._register_fixture( 795 # Use a unique name to speed up lookup. 796 name=f"_xunit_setup_class_fixture_{self.obj.__qualname__}", 797 func=xunit_setup_class_fixture, 798 nodeid=self.nodeid, 799 scope="class", 800 autouse=True, 801 ) 802 803 def _register_setup_method_fixture(self) -> None: 804 """Register an autouse, function scoped fixture into the collected class object 805 that invokes setup_method/teardown_method if either or both are available. 806 807 Using a fixture to invoke these methods ensures we play nicely and unsurprisingly with 808 other fixtures (#517). 809 """ 810 setup_name = "setup_method" 811 setup_method = _get_first_non_fixture_func(self.obj, (setup_name,)) 812 teardown_name = "teardown_method" 813 teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,)) 814 if setup_method is None and teardown_method is None: 815 return 816 817 def xunit_setup_method_fixture(request) -> Generator[None]: 818 instance = request.instance 819 method = request.function 820 if setup_method is not None: 821 func = getattr(instance, setup_name) 822 _call_with_optional_argument(func, method) 823 yield 824 if teardown_method is not None: 825 func = getattr(instance, teardown_name) 826 _call_with_optional_argument(func, method) 827 828 self.session._fixturemanager._register_fixture( 829 # Use a unique name to speed up lookup. 830 name=f"_xunit_setup_method_fixture_{self.obj.__qualname__}", 831 func=xunit_setup_method_fixture, 832 nodeid=self.nodeid, 833 scope="function", 834 autouse=True, 835 )
Collector for test methods (and nested classes) in a Python class.
735 @classmethod 736 def from_parent(cls, parent, *, name, obj=None, **kw) -> Self: # type: ignore[override] 737 """The public constructor.""" 738 return super().from_parent(name=name, parent=parent, **kw)
The public constructor.
743 def collect(self) -> Iterable[nodes.Item | nodes.Collector]: 744 if not safe_getattr(self.obj, "__test__", True): 745 return [] 746 if hasinit(self.obj): 747 assert self.parent is not None 748 self.warn( 749 PytestCollectionWarning( 750 f"cannot collect test class {self.obj.__name__!r} because it has a " 751 f"__init__ constructor (from: {self.parent.nodeid})" 752 ) 753 ) 754 return [] 755 elif hasnew(self.obj): 756 assert self.parent is not None 757 self.warn( 758 PytestCollectionWarning( 759 f"cannot collect test class {self.obj.__name__!r} because it has a " 760 f"__new__ constructor (from: {self.parent.nodeid})" 761 ) 762 ) 763 return [] 764 765 self._register_setup_class_fixture() 766 self._register_setup_method_fixture() 767 768 self.session._fixturemanager.parsefactories(self.newinstance(), self.nodeid) 769 770 return super().collect()
Collect children (items and collectors) for this collector.
Inherited Members
- _pytest.nodes.Node
- Node
- fspath
- name
- parent
- path
- keywords
- own_markers
- extra_keyword_matches
- stash
- ihook
- warn
- nodeid
- setup
- teardown
- iter_parents
- listchain
- add_marker
- iter_markers
- iter_markers_with_node
- get_closest_marker
- listextrakeywords
- listnames
- addfinalizer
- getparent
- config
- session
- _pytest.python.PyCollector
- funcnamefilter
- isnosetest
- classnamefilter
- istestfunction
- istestclass
- _pytest.python.PyobjMixin
- module
- cls
- instance
- obj
- getmodpath
- reportinfo
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.
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.
Inherited Members
- _pytest.nodes.Node
- fspath
- name
- parent
- path
- keywords
- own_markers
- extra_keyword_matches
- stash
- from_parent
- ihook
- warn
- nodeid
- setup
- teardown
- iter_parents
- listchain
- add_marker
- iter_markers
- iter_markers_with_node
- get_closest_marker
- listextrakeywords
- listnames
- addfinalizer
- getparent
- config
- session
An error during collection, contains a custom message.
Inherited Members
- builtins.Exception
- Exception
- builtins.BaseException
- with_traceback
- add_note
- args
399@final 400class CollectReport(BaseReport): 401 """Collection report object. 402 403 Reports can contain arbitrary extra attributes. 404 """ 405 406 when = "collect" 407 408 def __init__( 409 self, 410 nodeid: str, 411 outcome: Literal["passed", "failed", "skipped"], 412 longrepr: None 413 | ExceptionInfo[BaseException] 414 | tuple[str, int, str] 415 | str 416 | TerminalRepr, 417 result: list[Item | Collector] | None, 418 sections: Iterable[tuple[str, str]] = (), 419 **extra, 420 ) -> None: 421 #: Normalized collection nodeid. 422 self.nodeid = nodeid 423 424 #: Test outcome, always one of "passed", "failed", "skipped". 425 self.outcome = outcome 426 427 #: None or a failure representation. 428 self.longrepr = longrepr 429 430 #: The collected items and collection nodes. 431 self.result = result or [] 432 433 #: Tuples of str ``(heading, content)`` with extra information 434 #: for the test report. Used by pytest to add text captured 435 #: from ``stdout``, ``stderr``, and intercepted logging events. May 436 #: be used by other plugins to add arbitrary information to reports. 437 self.sections = list(sections) 438 439 self.__dict__.update(extra) 440 441 @property 442 def location( # type:ignore[override] 443 self, 444 ) -> tuple[str, int | None, str] | None: 445 return (self.fspath, None, self.fspath) 446 447 def __repr__(self) -> str: 448 return f"<CollectReport {self.nodeid!r} lenresult={len(self.result)} outcome={self.outcome!r}>"
Collection report object.
Reports can contain arbitrary extra attributes.
408 def __init__( 409 self, 410 nodeid: str, 411 outcome: Literal["passed", "failed", "skipped"], 412 longrepr: None 413 | ExceptionInfo[BaseException] 414 | tuple[str, int, str] 415 | str 416 | TerminalRepr, 417 result: list[Item | Collector] | None, 418 sections: Iterable[tuple[str, str]] = (), 419 **extra, 420 ) -> None: 421 #: Normalized collection nodeid. 422 self.nodeid = nodeid 423 424 #: Test outcome, always one of "passed", "failed", "skipped". 425 self.outcome = outcome 426 427 #: None or a failure representation. 428 self.longrepr = longrepr 429 430 #: The collected items and collection nodes. 431 self.result = result or [] 432 433 #: Tuples of str ``(heading, content)`` with extra information 434 #: for the test report. Used by pytest to add text captured 435 #: from ``stdout``, ``stderr``, and intercepted logging events. May 436 #: be used by other plugins to add arbitrary information to reports. 437 self.sections = list(sections) 438 439 self.__dict__.update(extra)
Inherited Members
- _pytest.reports.BaseReport
- toterminal
- get_sections
- longreprtext
- caplog
- capstdout
- capstderr
- passed
- failed
- skipped
- fspath
- count_towards_summary
- head_line
966@final 967class Config: 968 """Access to configuration values, pluginmanager and plugin hooks. 969 970 :param PytestPluginManager pluginmanager: 971 A pytest PluginManager. 972 973 :param InvocationParams invocation_params: 974 Object containing parameters regarding the :func:`pytest.main` 975 invocation. 976 """ 977 978 @final 979 @dataclasses.dataclass(frozen=True) 980 class InvocationParams: 981 """Holds parameters passed during :func:`pytest.main`. 982 983 The object attributes are read-only. 984 985 .. versionadded:: 5.1 986 987 .. note:: 988 989 Note that the environment variable ``PYTEST_ADDOPTS`` and the ``addopts`` 990 ini option are handled by pytest, not being included in the ``args`` attribute. 991 992 Plugins accessing ``InvocationParams`` must be aware of that. 993 """ 994 995 args: tuple[str, ...] 996 """The command-line arguments as passed to :func:`pytest.main`.""" 997 plugins: Sequence[str | _PluggyPlugin] | None 998 """Extra plugins, might be `None`.""" 999 dir: pathlib.Path 1000 """The directory from which :func:`pytest.main` was invoked. :type: pathlib.Path""" 1001 1002 def __init__( 1003 self, 1004 *, 1005 args: Iterable[str], 1006 plugins: Sequence[str | _PluggyPlugin] | None, 1007 dir: pathlib.Path, 1008 ) -> None: 1009 object.__setattr__(self, "args", tuple(args)) 1010 object.__setattr__(self, "plugins", plugins) 1011 object.__setattr__(self, "dir", dir) 1012 1013 class ArgsSource(enum.Enum): 1014 """Indicates the source of the test arguments. 1015 1016 .. versionadded:: 7.2 1017 """ 1018 1019 #: Command line arguments. 1020 ARGS = enum.auto() 1021 #: Invocation directory. 1022 INVOCATION_DIR = enum.auto() 1023 INCOVATION_DIR = INVOCATION_DIR # backwards compatibility alias 1024 #: 'testpaths' configuration value. 1025 TESTPATHS = enum.auto() 1026 1027 # Set by cacheprovider plugin. 1028 cache: Cache 1029 1030 def __init__( 1031 self, 1032 pluginmanager: PytestPluginManager, 1033 *, 1034 invocation_params: InvocationParams | None = None, 1035 ) -> None: 1036 from .argparsing import FILE_OR_DIR 1037 from .argparsing import Parser 1038 1039 if invocation_params is None: 1040 invocation_params = self.InvocationParams( 1041 args=(), plugins=None, dir=pathlib.Path.cwd() 1042 ) 1043 1044 self.option = argparse.Namespace() 1045 """Access to command line option as attributes. 1046 1047 :type: argparse.Namespace 1048 """ 1049 1050 self.invocation_params = invocation_params 1051 """The parameters with which pytest was invoked. 1052 1053 :type: InvocationParams 1054 """ 1055 1056 _a = FILE_OR_DIR 1057 self._parser = Parser( 1058 usage=f"%(prog)s [options] [{_a}] [{_a}] [...]", 1059 processopt=self._processopt, 1060 _ispytest=True, 1061 ) 1062 self.pluginmanager = pluginmanager 1063 """The plugin manager handles plugin registration and hook invocation. 1064 1065 :type: PytestPluginManager 1066 """ 1067 1068 self.stash = Stash() 1069 """A place where plugins can store information on the config for their 1070 own use. 1071 1072 :type: Stash 1073 """ 1074 # Deprecated alias. Was never public. Can be removed in a few releases. 1075 self._store = self.stash 1076 1077 self.trace = self.pluginmanager.trace.root.get("config") 1078 self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook) # type: ignore[assignment] 1079 self._inicache: dict[str, Any] = {} 1080 self._override_ini: Sequence[str] = () 1081 self._opt2dest: dict[str, str] = {} 1082 self._cleanup: list[Callable[[], None]] = [] 1083 self.pluginmanager.register(self, "pytestconfig") 1084 self._configured = False 1085 self.hook.pytest_addoption.call_historic( 1086 kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager) 1087 ) 1088 self.args_source = Config.ArgsSource.ARGS 1089 self.args: list[str] = [] 1090 1091 @property 1092 def rootpath(self) -> pathlib.Path: 1093 """The path to the :ref:`rootdir <rootdir>`. 1094 1095 :type: pathlib.Path 1096 1097 .. versionadded:: 6.1 1098 """ 1099 return self._rootpath 1100 1101 @property 1102 def inipath(self) -> pathlib.Path | None: 1103 """The path to the :ref:`configfile <configfiles>`. 1104 1105 .. versionadded:: 6.1 1106 """ 1107 return self._inipath 1108 1109 def add_cleanup(self, func: Callable[[], None]) -> None: 1110 """Add a function to be called when the config object gets out of 1111 use (usually coinciding with pytest_unconfigure).""" 1112 self._cleanup.append(func) 1113 1114 def _do_configure(self) -> None: 1115 assert not self._configured 1116 self._configured = True 1117 with warnings.catch_warnings(): 1118 warnings.simplefilter("default") 1119 self.hook.pytest_configure.call_historic(kwargs=dict(config=self)) 1120 1121 def _ensure_unconfigure(self) -> None: 1122 if self._configured: 1123 self._configured = False 1124 self.hook.pytest_unconfigure(config=self) 1125 self.hook.pytest_configure._call_history = [] 1126 while self._cleanup: 1127 fin = self._cleanup.pop() 1128 fin() 1129 1130 def get_terminal_writer(self) -> TerminalWriter: 1131 terminalreporter: TerminalReporter | None = self.pluginmanager.get_plugin( 1132 "terminalreporter" 1133 ) 1134 assert terminalreporter is not None 1135 return terminalreporter._tw 1136 1137 def pytest_cmdline_parse( 1138 self, pluginmanager: PytestPluginManager, args: list[str] 1139 ) -> Config: 1140 try: 1141 self.parse(args) 1142 except UsageError: 1143 # Handle --version and --help here in a minimal fashion. 1144 # This gets done via helpconfig normally, but its 1145 # pytest_cmdline_main is not called in case of errors. 1146 if getattr(self.option, "version", False) or "--version" in args: 1147 from _pytest.helpconfig import showversion 1148 1149 showversion(self) 1150 elif ( 1151 getattr(self.option, "help", False) or "--help" in args or "-h" in args 1152 ): 1153 self._parser._getparser().print_help() 1154 sys.stdout.write( 1155 "\nNOTE: displaying only minimal help due to UsageError.\n\n" 1156 ) 1157 1158 raise 1159 1160 return self 1161 1162 def notify_exception( 1163 self, 1164 excinfo: ExceptionInfo[BaseException], 1165 option: argparse.Namespace | None = None, 1166 ) -> None: 1167 if option and getattr(option, "fulltrace", False): 1168 style: TracebackStyle = "long" 1169 else: 1170 style = "native" 1171 excrepr = excinfo.getrepr( 1172 funcargs=True, showlocals=getattr(option, "showlocals", False), style=style 1173 ) 1174 res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo) 1175 if not any(res): 1176 for line in str(excrepr).split("\n"): 1177 sys.stderr.write(f"INTERNALERROR> {line}\n") 1178 sys.stderr.flush() 1179 1180 def cwd_relative_nodeid(self, nodeid: str) -> str: 1181 # nodeid's are relative to the rootpath, compute relative to cwd. 1182 if self.invocation_params.dir != self.rootpath: 1183 base_path_part, *nodeid_part = nodeid.split("::") 1184 # Only process path part 1185 fullpath = self.rootpath / base_path_part 1186 relative_path = bestrelpath(self.invocation_params.dir, fullpath) 1187 1188 nodeid = "::".join([relative_path, *nodeid_part]) 1189 return nodeid 1190 1191 @classmethod 1192 def fromdictargs(cls, option_dict, args) -> Config: 1193 """Constructor usable for subprocesses.""" 1194 config = get_config(args) 1195 config.option.__dict__.update(option_dict) 1196 config.parse(args, addopts=False) 1197 for x in config.option.plugins: 1198 config.pluginmanager.consider_pluginarg(x) 1199 return config 1200 1201 def _processopt(self, opt: Argument) -> None: 1202 for name in opt._short_opts + opt._long_opts: 1203 self._opt2dest[name] = opt.dest 1204 1205 if hasattr(opt, "default"): 1206 if not hasattr(self.option, opt.dest): 1207 setattr(self.option, opt.dest, opt.default) 1208 1209 @hookimpl(trylast=True) 1210 def pytest_load_initial_conftests(self, early_config: Config) -> None: 1211 # We haven't fully parsed the command line arguments yet, so 1212 # early_config.args it not set yet. But we need it for 1213 # discovering the initial conftests. So "pre-run" the logic here. 1214 # It will be done for real in `parse()`. 1215 args, args_source = early_config._decide_args( 1216 args=early_config.known_args_namespace.file_or_dir, 1217 pyargs=early_config.known_args_namespace.pyargs, 1218 testpaths=early_config.getini("testpaths"), 1219 invocation_dir=early_config.invocation_params.dir, 1220 rootpath=early_config.rootpath, 1221 warn=False, 1222 ) 1223 self.pluginmanager._set_initial_conftests( 1224 args=args, 1225 pyargs=early_config.known_args_namespace.pyargs, 1226 noconftest=early_config.known_args_namespace.noconftest, 1227 rootpath=early_config.rootpath, 1228 confcutdir=early_config.known_args_namespace.confcutdir, 1229 invocation_dir=early_config.invocation_params.dir, 1230 importmode=early_config.known_args_namespace.importmode, 1231 consider_namespace_packages=early_config.getini( 1232 "consider_namespace_packages" 1233 ), 1234 ) 1235 1236 def _initini(self, args: Sequence[str]) -> None: 1237 ns, unknown_args = self._parser.parse_known_and_unknown_args( 1238 args, namespace=copy.copy(self.option) 1239 ) 1240 rootpath, inipath, inicfg = determine_setup( 1241 inifile=ns.inifilename, 1242 args=ns.file_or_dir + unknown_args, 1243 rootdir_cmd_arg=ns.rootdir or None, 1244 invocation_dir=self.invocation_params.dir, 1245 ) 1246 self._rootpath = rootpath 1247 self._inipath = inipath 1248 self.inicfg = inicfg 1249 self._parser.extra_info["rootdir"] = str(self.rootpath) 1250 self._parser.extra_info["inifile"] = str(self.inipath) 1251 self._parser.addini("addopts", "Extra command line options", "args") 1252 self._parser.addini("minversion", "Minimally required pytest version") 1253 self._parser.addini( 1254 "required_plugins", 1255 "Plugins that must be present for pytest to run", 1256 type="args", 1257 default=[], 1258 ) 1259 self._override_ini = ns.override_ini or () 1260 1261 def _consider_importhook(self, args: Sequence[str]) -> None: 1262 """Install the PEP 302 import hook if using assertion rewriting. 1263 1264 Needs to parse the --assert=<mode> option from the commandline 1265 and find all the installed plugins to mark them for rewriting 1266 by the importhook. 1267 """ 1268 ns, unknown_args = self._parser.parse_known_and_unknown_args(args) 1269 mode = getattr(ns, "assertmode", "plain") 1270 if mode == "rewrite": 1271 import _pytest.assertion 1272 1273 try: 1274 hook = _pytest.assertion.install_importhook(self) 1275 except SystemError: 1276 mode = "plain" 1277 else: 1278 self._mark_plugins_for_rewrite(hook) 1279 self._warn_about_missing_assertion(mode) 1280 1281 def _mark_plugins_for_rewrite(self, hook) -> None: 1282 """Given an importhook, mark for rewrite any top-level 1283 modules or packages in the distribution package for 1284 all pytest plugins.""" 1285 self.pluginmanager.rewrite_hook = hook 1286 1287 if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"): 1288 # We don't autoload from distribution package entry points, 1289 # no need to continue. 1290 return 1291 1292 package_files = ( 1293 str(file) 1294 for dist in importlib.metadata.distributions() 1295 if any(ep.group == "pytest11" for ep in dist.entry_points) 1296 for file in dist.files or [] 1297 ) 1298 1299 for name in _iter_rewritable_modules(package_files): 1300 hook.mark_rewrite(name) 1301 1302 def _validate_args(self, args: list[str], via: str) -> list[str]: 1303 """Validate known args.""" 1304 self._parser._config_source_hint = via # type: ignore 1305 try: 1306 self._parser.parse_known_and_unknown_args( 1307 args, namespace=copy.copy(self.option) 1308 ) 1309 finally: 1310 del self._parser._config_source_hint # type: ignore 1311 1312 return args 1313 1314 def _decide_args( 1315 self, 1316 *, 1317 args: list[str], 1318 pyargs: bool, 1319 testpaths: list[str], 1320 invocation_dir: pathlib.Path, 1321 rootpath: pathlib.Path, 1322 warn: bool, 1323 ) -> tuple[list[str], ArgsSource]: 1324 """Decide the args (initial paths/nodeids) to use given the relevant inputs. 1325 1326 :param warn: Whether can issue warnings. 1327 1328 :returns: The args and the args source. Guaranteed to be non-empty. 1329 """ 1330 if args: 1331 source = Config.ArgsSource.ARGS 1332 result = args 1333 else: 1334 if invocation_dir == rootpath: 1335 source = Config.ArgsSource.TESTPATHS 1336 if pyargs: 1337 result = testpaths 1338 else: 1339 result = [] 1340 for path in testpaths: 1341 result.extend(sorted(glob.iglob(path, recursive=True))) 1342 if testpaths and not result: 1343 if warn: 1344 warning_text = ( 1345 "No files were found in testpaths; " 1346 "consider removing or adjusting your testpaths configuration. " 1347 "Searching recursively from the current directory instead." 1348 ) 1349 self.issue_config_time_warning( 1350 PytestConfigWarning(warning_text), stacklevel=3 1351 ) 1352 else: 1353 result = [] 1354 if not result: 1355 source = Config.ArgsSource.INVOCATION_DIR 1356 result = [str(invocation_dir)] 1357 return result, source 1358 1359 def _preparse(self, args: list[str], addopts: bool = True) -> None: 1360 if addopts: 1361 env_addopts = os.environ.get("PYTEST_ADDOPTS", "") 1362 if len(env_addopts): 1363 args[:] = ( 1364 self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS") 1365 + args 1366 ) 1367 self._initini(args) 1368 if addopts: 1369 args[:] = ( 1370 self._validate_args(self.getini("addopts"), "via addopts config") + args 1371 ) 1372 1373 self.known_args_namespace = self._parser.parse_known_args( 1374 args, namespace=copy.copy(self.option) 1375 ) 1376 self._checkversion() 1377 self._consider_importhook(args) 1378 self.pluginmanager.consider_preparse(args, exclude_only=False) 1379 if not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"): 1380 # Don't autoload from distribution package entry point. Only 1381 # explicitly specified plugins are going to be loaded. 1382 self.pluginmanager.load_setuptools_entrypoints("pytest11") 1383 self.pluginmanager.consider_env() 1384 1385 self.known_args_namespace = self._parser.parse_known_args( 1386 args, namespace=copy.copy(self.known_args_namespace) 1387 ) 1388 1389 self._validate_plugins() 1390 self._warn_about_skipped_plugins() 1391 1392 if self.known_args_namespace.confcutdir is None: 1393 if self.inipath is not None: 1394 confcutdir = str(self.inipath.parent) 1395 else: 1396 confcutdir = str(self.rootpath) 1397 self.known_args_namespace.confcutdir = confcutdir 1398 try: 1399 self.hook.pytest_load_initial_conftests( 1400 early_config=self, args=args, parser=self._parser 1401 ) 1402 except ConftestImportFailure as e: 1403 if self.known_args_namespace.help or self.known_args_namespace.version: 1404 # we don't want to prevent --help/--version to work 1405 # so just let is pass and print a warning at the end 1406 self.issue_config_time_warning( 1407 PytestConfigWarning(f"could not load initial conftests: {e.path}"), 1408 stacklevel=2, 1409 ) 1410 else: 1411 raise 1412 1413 @hookimpl(wrapper=True) 1414 def pytest_collection(self) -> Generator[None, object, object]: 1415 # Validate invalid ini keys after collection is done so we take in account 1416 # options added by late-loading conftest files. 1417 try: 1418 return (yield) 1419 finally: 1420 self._validate_config_options() 1421 1422 def _checkversion(self) -> None: 1423 import pytest 1424 1425 minver = self.inicfg.get("minversion", None) 1426 if minver: 1427 # Imported lazily to improve start-up time. 1428 from packaging.version import Version 1429 1430 if not isinstance(minver, str): 1431 raise pytest.UsageError( 1432 f"{self.inipath}: 'minversion' must be a single value" 1433 ) 1434 1435 if Version(minver) > Version(pytest.__version__): 1436 raise pytest.UsageError( 1437 f"{self.inipath}: 'minversion' requires pytest-{minver}, actual pytest-{pytest.__version__}'" 1438 ) 1439 1440 def _validate_config_options(self) -> None: 1441 for key in sorted(self._get_unknown_ini_keys()): 1442 self._warn_or_fail_if_strict(f"Unknown config option: {key}\n") 1443 1444 def _validate_plugins(self) -> None: 1445 required_plugins = sorted(self.getini("required_plugins")) 1446 if not required_plugins: 1447 return 1448 1449 # Imported lazily to improve start-up time. 1450 from packaging.requirements import InvalidRequirement 1451 from packaging.requirements import Requirement 1452 from packaging.version import Version 1453 1454 plugin_info = self.pluginmanager.list_plugin_distinfo() 1455 plugin_dist_info = {dist.project_name: dist.version for _, dist in plugin_info} 1456 1457 missing_plugins = [] 1458 for required_plugin in required_plugins: 1459 try: 1460 req = Requirement(required_plugin) 1461 except InvalidRequirement: 1462 missing_plugins.append(required_plugin) 1463 continue 1464 1465 if req.name not in plugin_dist_info: 1466 missing_plugins.append(required_plugin) 1467 elif not req.specifier.contains( 1468 Version(plugin_dist_info[req.name]), prereleases=True 1469 ): 1470 missing_plugins.append(required_plugin) 1471 1472 if missing_plugins: 1473 raise UsageError( 1474 "Missing required plugins: {}".format(", ".join(missing_plugins)), 1475 ) 1476 1477 def _warn_or_fail_if_strict(self, message: str) -> None: 1478 if self.known_args_namespace.strict_config: 1479 raise UsageError(message) 1480 1481 self.issue_config_time_warning(PytestConfigWarning(message), stacklevel=3) 1482 1483 def _get_unknown_ini_keys(self) -> list[str]: 1484 parser_inicfg = self._parser._inidict 1485 return [name for name in self.inicfg if name not in parser_inicfg] 1486 1487 def parse(self, args: list[str], addopts: bool = True) -> None: 1488 # Parse given cmdline arguments into this config object. 1489 assert ( 1490 self.args == [] 1491 ), "can only parse cmdline args at most once per Config object" 1492 self.hook.pytest_addhooks.call_historic( 1493 kwargs=dict(pluginmanager=self.pluginmanager) 1494 ) 1495 self._preparse(args, addopts=addopts) 1496 self._parser.after_preparse = True # type: ignore 1497 try: 1498 args = self._parser.parse_setoption( 1499 args, self.option, namespace=self.option 1500 ) 1501 self.args, self.args_source = self._decide_args( 1502 args=args, 1503 pyargs=self.known_args_namespace.pyargs, 1504 testpaths=self.getini("testpaths"), 1505 invocation_dir=self.invocation_params.dir, 1506 rootpath=self.rootpath, 1507 warn=True, 1508 ) 1509 except PrintHelp: 1510 pass 1511 1512 def issue_config_time_warning(self, warning: Warning, stacklevel: int) -> None: 1513 """Issue and handle a warning during the "configure" stage. 1514 1515 During ``pytest_configure`` we can't capture warnings using the ``catch_warnings_for_item`` 1516 function because it is not possible to have hook wrappers around ``pytest_configure``. 1517 1518 This function is mainly intended for plugins that need to issue warnings during 1519 ``pytest_configure`` (or similar stages). 1520 1521 :param warning: The warning instance. 1522 :param stacklevel: stacklevel forwarded to warnings.warn. 1523 """ 1524 if self.pluginmanager.is_blocked("warnings"): 1525 return 1526 1527 cmdline_filters = self.known_args_namespace.pythonwarnings or [] 1528 config_filters = self.getini("filterwarnings") 1529 1530 with warnings.catch_warnings(record=True) as records: 1531 warnings.simplefilter("always", type(warning)) 1532 apply_warning_filters(config_filters, cmdline_filters) 1533 warnings.warn(warning, stacklevel=stacklevel) 1534 1535 if records: 1536 frame = sys._getframe(stacklevel - 1) 1537 location = frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name 1538 self.hook.pytest_warning_recorded.call_historic( 1539 kwargs=dict( 1540 warning_message=records[0], 1541 when="config", 1542 nodeid="", 1543 location=location, 1544 ) 1545 ) 1546 1547 def addinivalue_line(self, name: str, line: str) -> None: 1548 """Add a line to an ini-file option. The option must have been 1549 declared but might not yet be set in which case the line becomes 1550 the first line in its value.""" 1551 x = self.getini(name) 1552 assert isinstance(x, list) 1553 x.append(line) # modifies the cached list inline 1554 1555 def getini(self, name: str): 1556 """Return configuration value from an :ref:`ini file <configfiles>`. 1557 1558 If a configuration value is not defined in an 1559 :ref:`ini file <configfiles>`, then the ``default`` value provided while 1560 registering the configuration through 1561 :func:`parser.addini <pytest.Parser.addini>` will be returned. 1562 Please note that you can even provide ``None`` as a valid 1563 default value. 1564 1565 If ``default`` is not provided while registering using 1566 :func:`parser.addini <pytest.Parser.addini>`, then a default value 1567 based on the ``type`` parameter passed to 1568 :func:`parser.addini <pytest.Parser.addini>` will be returned. 1569 The default values based on ``type`` are: 1570 ``paths``, ``pathlist``, ``args`` and ``linelist`` : empty list ``[]`` 1571 ``bool`` : ``False`` 1572 ``string`` : empty string ``""`` 1573 1574 If neither the ``default`` nor the ``type`` parameter is passed 1575 while registering the configuration through 1576 :func:`parser.addini <pytest.Parser.addini>`, then the configuration 1577 is treated as a string and a default empty string '' is returned. 1578 1579 If the specified name hasn't been registered through a prior 1580 :func:`parser.addini <pytest.Parser.addini>` call (usually from a 1581 plugin), a ValueError is raised. 1582 """ 1583 try: 1584 return self._inicache[name] 1585 except KeyError: 1586 self._inicache[name] = val = self._getini(name) 1587 return val 1588 1589 # Meant for easy monkeypatching by legacypath plugin. 1590 # Can be inlined back (with no cover removed) once legacypath is gone. 1591 def _getini_unknown_type(self, name: str, type: str, value: str | list[str]): 1592 msg = f"unknown configuration type: {type}" 1593 raise ValueError(msg, value) # pragma: no cover 1594 1595 def _getini(self, name: str): 1596 try: 1597 description, type, default = self._parser._inidict[name] 1598 except KeyError as e: 1599 raise ValueError(f"unknown configuration value: {name!r}") from e 1600 override_value = self._get_override_ini_value(name) 1601 if override_value is None: 1602 try: 1603 value = self.inicfg[name] 1604 except KeyError: 1605 return default 1606 else: 1607 value = override_value 1608 # Coerce the values based on types. 1609 # 1610 # Note: some coercions are only required if we are reading from .ini files, because 1611 # the file format doesn't contain type information, but when reading from toml we will 1612 # get either str or list of str values (see _parse_ini_config_from_pyproject_toml). 1613 # For example: 1614 # 1615 # ini: 1616 # a_line_list = "tests acceptance" 1617 # in this case, we need to split the string to obtain a list of strings. 1618 # 1619 # toml: 1620 # a_line_list = ["tests", "acceptance"] 1621 # in this case, we already have a list ready to use. 1622 # 1623 if type == "paths": 1624 dp = ( 1625 self.inipath.parent 1626 if self.inipath is not None 1627 else self.invocation_params.dir 1628 ) 1629 input_values = shlex.split(value) if isinstance(value, str) else value 1630 return [dp / x for x in input_values] 1631 elif type == "args": 1632 return shlex.split(value) if isinstance(value, str) else value 1633 elif type == "linelist": 1634 if isinstance(value, str): 1635 return [t for t in map(lambda x: x.strip(), value.split("\n")) if t] 1636 else: 1637 return value 1638 elif type == "bool": 1639 return _strtobool(str(value).strip()) 1640 elif type == "string": 1641 return value 1642 elif type is None: 1643 return value 1644 else: 1645 return self._getini_unknown_type(name, type, value) 1646 1647 def _getconftest_pathlist( 1648 self, name: str, path: pathlib.Path 1649 ) -> list[pathlib.Path] | None: 1650 try: 1651 mod, relroots = self.pluginmanager._rget_with_confmod(name, path) 1652 except KeyError: 1653 return None 1654 assert mod.__file__ is not None 1655 modpath = pathlib.Path(mod.__file__).parent 1656 values: list[pathlib.Path] = [] 1657 for relroot in relroots: 1658 if isinstance(relroot, os.PathLike): 1659 relroot = pathlib.Path(relroot) 1660 else: 1661 relroot = relroot.replace("/", os.sep) 1662 relroot = absolutepath(modpath / relroot) 1663 values.append(relroot) 1664 return values 1665 1666 def _get_override_ini_value(self, name: str) -> str | None: 1667 value = None 1668 # override_ini is a list of "ini=value" options. 1669 # Always use the last item if multiple values are set for same ini-name, 1670 # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2. 1671 for ini_config in self._override_ini: 1672 try: 1673 key, user_ini_value = ini_config.split("=", 1) 1674 except ValueError as e: 1675 raise UsageError( 1676 f"-o/--override-ini expects option=value style (got: {ini_config!r})." 1677 ) from e 1678 else: 1679 if key == name: 1680 value = user_ini_value 1681 return value 1682 1683 def getoption(self, name: str, default=notset, skip: bool = False): 1684 """Return command line option value. 1685 1686 :param name: Name of the option. You may also specify 1687 the literal ``--OPT`` option instead of the "dest" option name. 1688 :param default: Fallback value if no option of that name is **declared** via :hook:`pytest_addoption`. 1689 Note this parameter will be ignored when the option is **declared** even if the option's value is ``None``. 1690 :param skip: If ``True``, raise :func:`pytest.skip` if option is undeclared or has a ``None`` value. 1691 Note that even if ``True``, if a default was specified it will be returned instead of a skip. 1692 """ 1693 name = self._opt2dest.get(name, name) 1694 try: 1695 val = getattr(self.option, name) 1696 if val is None and skip: 1697 raise AttributeError(name) 1698 return val 1699 except AttributeError as e: 1700 if default is not notset: 1701 return default 1702 if skip: 1703 import pytest 1704 1705 pytest.skip(f"no {name!r} option found") 1706 raise ValueError(f"no option named {name!r}") from e 1707 1708 def getvalue(self, name: str, path=None): 1709 """Deprecated, use getoption() instead.""" 1710 return self.getoption(name) 1711 1712 def getvalueorskip(self, name: str, path=None): 1713 """Deprecated, use getoption(skip=True) instead.""" 1714 return self.getoption(name, skip=True) 1715 1716 #: Verbosity type for failed assertions (see :confval:`verbosity_assertions`). 1717 VERBOSITY_ASSERTIONS: Final = "assertions" 1718 #: Verbosity type for test case execution (see :confval:`verbosity_test_cases`). 1719 VERBOSITY_TEST_CASES: Final = "test_cases" 1720 _VERBOSITY_INI_DEFAULT: Final = "auto" 1721 1722 def get_verbosity(self, verbosity_type: str | None = None) -> int: 1723 r"""Retrieve the verbosity level for a fine-grained verbosity type. 1724 1725 :param verbosity_type: Verbosity type to get level for. If a level is 1726 configured for the given type, that value will be returned. If the 1727 given type is not a known verbosity type, the global verbosity 1728 level will be returned. If the given type is None (default), the 1729 global verbosity level will be returned. 1730 1731 To configure a level for a fine-grained verbosity type, the 1732 configuration file should have a setting for the configuration name 1733 and a numeric value for the verbosity level. A special value of "auto" 1734 can be used to explicitly use the global verbosity level. 1735 1736 Example: 1737 1738 .. code-block:: ini 1739 1740 # content of pytest.ini 1741 [pytest] 1742 verbosity_assertions = 2 1743 1744 .. code-block:: console 1745 1746 pytest -v 1747 1748 .. code-block:: python 1749 1750 print(config.get_verbosity()) # 1 1751 print(config.get_verbosity(Config.VERBOSITY_ASSERTIONS)) # 2 1752 """ 1753 global_level = self.getoption("verbose", default=0) 1754 assert isinstance(global_level, int) 1755 if verbosity_type is None: 1756 return global_level 1757 1758 ini_name = Config._verbosity_ini_name(verbosity_type) 1759 if ini_name not in self._parser._inidict: 1760 return global_level 1761 1762 level = self.getini(ini_name) 1763 if level == Config._VERBOSITY_INI_DEFAULT: 1764 return global_level 1765 1766 return int(level) 1767 1768 @staticmethod 1769 def _verbosity_ini_name(verbosity_type: str) -> str: 1770 return f"verbosity_{verbosity_type}" 1771 1772 @staticmethod 1773 def _add_verbosity_ini(parser: Parser, verbosity_type: str, help: str) -> None: 1774 """Add a output verbosity configuration option for the given output type. 1775 1776 :param parser: Parser for command line arguments and ini-file values. 1777 :param verbosity_type: Fine-grained verbosity category. 1778 :param help: Description of the output this type controls. 1779 1780 The value should be retrieved via a call to 1781 :py:func:`config.get_verbosity(type) <pytest.Config.get_verbosity>`. 1782 """ 1783 parser.addini( 1784 Config._verbosity_ini_name(verbosity_type), 1785 help=help, 1786 type="string", 1787 default=Config._VERBOSITY_INI_DEFAULT, 1788 ) 1789 1790 def _warn_about_missing_assertion(self, mode: str) -> None: 1791 if not _assertion_supported(): 1792 if mode == "plain": 1793 warning_text = ( 1794 "ASSERTIONS ARE NOT EXECUTED" 1795 " and FAILING TESTS WILL PASS. Are you" 1796 " using python -O?" 1797 ) 1798 else: 1799 warning_text = ( 1800 "assertions not in test modules or" 1801 " plugins will be ignored" 1802 " because assert statements are not executed " 1803 "by the underlying Python interpreter " 1804 "(are you using python -O?)\n" 1805 ) 1806 self.issue_config_time_warning( 1807 PytestConfigWarning(warning_text), 1808 stacklevel=3, 1809 ) 1810 1811 def _warn_about_skipped_plugins(self) -> None: 1812 for module_name, msg in self.pluginmanager.skipped_plugins: 1813 self.issue_config_time_warning( 1814 PytestConfigWarning(f"skipped plugin {module_name!r}: {msg}"), 1815 stacklevel=2, 1816 )
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.
1030 def __init__( 1031 self, 1032 pluginmanager: PytestPluginManager, 1033 *, 1034 invocation_params: InvocationParams | None = None, 1035 ) -> None: 1036 from .argparsing import FILE_OR_DIR 1037 from .argparsing import Parser 1038 1039 if invocation_params is None: 1040 invocation_params = self.InvocationParams( 1041 args=(), plugins=None, dir=pathlib.Path.cwd() 1042 ) 1043 1044 self.option = argparse.Namespace() 1045 """Access to command line option as attributes. 1046 1047 :type: argparse.Namespace 1048 """ 1049 1050 self.invocation_params = invocation_params 1051 """The parameters with which pytest was invoked. 1052 1053 :type: InvocationParams 1054 """ 1055 1056 _a = FILE_OR_DIR 1057 self._parser = Parser( 1058 usage=f"%(prog)s [options] [{_a}] [{_a}] [...]", 1059 processopt=self._processopt, 1060 _ispytest=True, 1061 ) 1062 self.pluginmanager = pluginmanager 1063 """The plugin manager handles plugin registration and hook invocation. 1064 1065 :type: PytestPluginManager 1066 """ 1067 1068 self.stash = Stash() 1069 """A place where plugins can store information on the config for their 1070 own use. 1071 1072 :type: Stash 1073 """ 1074 # Deprecated alias. Was never public. Can be removed in a few releases. 1075 self._store = self.stash 1076 1077 self.trace = self.pluginmanager.trace.root.get("config") 1078 self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook) # type: ignore[assignment] 1079 self._inicache: dict[str, Any] = {} 1080 self._override_ini: Sequence[str] = () 1081 self._opt2dest: dict[str, str] = {} 1082 self._cleanup: list[Callable[[], None]] = [] 1083 self.pluginmanager.register(self, "pytestconfig") 1084 self._configured = False 1085 self.hook.pytest_addoption.call_historic( 1086 kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager) 1087 ) 1088 self.args_source = Config.ArgsSource.ARGS 1089 self.args: list[str] = []
1091 @property 1092 def rootpath(self) -> pathlib.Path: 1093 """The path to the :ref:`rootdir <rootdir>`. 1094 1095 :type: pathlib.Path 1096 1097 .. versionadded:: 6.1 1098 """ 1099 return self._rootpath
The path to the :ref:rootdir <rootdir>
.
New in version 6.1.
1101 @property 1102 def inipath(self) -> pathlib.Path | None: 1103 """The path to the :ref:`configfile <configfiles>`. 1104 1105 .. versionadded:: 6.1 1106 """ 1107 return self._inipath
The path to the :ref:configfile <configfiles>
.
New in version 6.1.
1109 def add_cleanup(self, func: Callable[[], None]) -> None: 1110 """Add a function to be called when the config object gets out of 1111 use (usually coinciding with pytest_unconfigure).""" 1112 self._cleanup.append(func)
Add a function to be called when the config object gets out of use (usually coinciding with pytest_unconfigure).
1137 def pytest_cmdline_parse( 1138 self, pluginmanager: PytestPluginManager, args: list[str] 1139 ) -> Config: 1140 try: 1141 self.parse(args) 1142 except UsageError: 1143 # Handle --version and --help here in a minimal fashion. 1144 # This gets done via helpconfig normally, but its 1145 # pytest_cmdline_main is not called in case of errors. 1146 if getattr(self.option, "version", False) or "--version" in args: 1147 from _pytest.helpconfig import showversion 1148 1149 showversion(self) 1150 elif ( 1151 getattr(self.option, "help", False) or "--help" in args or "-h" in args 1152 ): 1153 self._parser._getparser().print_help() 1154 sys.stdout.write( 1155 "\nNOTE: displaying only minimal help due to UsageError.\n\n" 1156 ) 1157 1158 raise 1159 1160 return self
1162 def notify_exception( 1163 self, 1164 excinfo: ExceptionInfo[BaseException], 1165 option: argparse.Namespace | None = None, 1166 ) -> None: 1167 if option and getattr(option, "fulltrace", False): 1168 style: TracebackStyle = "long" 1169 else: 1170 style = "native" 1171 excrepr = excinfo.getrepr( 1172 funcargs=True, showlocals=getattr(option, "showlocals", False), style=style 1173 ) 1174 res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo) 1175 if not any(res): 1176 for line in str(excrepr).split("\n"): 1177 sys.stderr.write(f"INTERNALERROR> {line}\n") 1178 sys.stderr.flush()
1180 def cwd_relative_nodeid(self, nodeid: str) -> str: 1181 # nodeid's are relative to the rootpath, compute relative to cwd. 1182 if self.invocation_params.dir != self.rootpath: 1183 base_path_part, *nodeid_part = nodeid.split("::") 1184 # Only process path part 1185 fullpath = self.rootpath / base_path_part 1186 relative_path = bestrelpath(self.invocation_params.dir, fullpath) 1187 1188 nodeid = "::".join([relative_path, *nodeid_part]) 1189 return nodeid
1191 @classmethod 1192 def fromdictargs(cls, option_dict, args) -> Config: 1193 """Constructor usable for subprocesses.""" 1194 config = get_config(args) 1195 config.option.__dict__.update(option_dict) 1196 config.parse(args, addopts=False) 1197 for x in config.option.plugins: 1198 config.pluginmanager.consider_pluginarg(x) 1199 return config
Constructor usable for subprocesses.
1209 @hookimpl(trylast=True) 1210 def pytest_load_initial_conftests(self, early_config: Config) -> None: 1211 # We haven't fully parsed the command line arguments yet, so 1212 # early_config.args it not set yet. But we need it for 1213 # discovering the initial conftests. So "pre-run" the logic here. 1214 # It will be done for real in `parse()`. 1215 args, args_source = early_config._decide_args( 1216 args=early_config.known_args_namespace.file_or_dir, 1217 pyargs=early_config.known_args_namespace.pyargs, 1218 testpaths=early_config.getini("testpaths"), 1219 invocation_dir=early_config.invocation_params.dir, 1220 rootpath=early_config.rootpath, 1221 warn=False, 1222 ) 1223 self.pluginmanager._set_initial_conftests( 1224 args=args, 1225 pyargs=early_config.known_args_namespace.pyargs, 1226 noconftest=early_config.known_args_namespace.noconftest, 1227 rootpath=early_config.rootpath, 1228 confcutdir=early_config.known_args_namespace.confcutdir, 1229 invocation_dir=early_config.invocation_params.dir, 1230 importmode=early_config.known_args_namespace.importmode, 1231 consider_namespace_packages=early_config.getini( 1232 "consider_namespace_packages" 1233 ), 1234 )
1413 @hookimpl(wrapper=True) 1414 def pytest_collection(self) -> Generator[None, object, object]: 1415 # Validate invalid ini keys after collection is done so we take in account 1416 # options added by late-loading conftest files. 1417 try: 1418 return (yield) 1419 finally: 1420 self._validate_config_options()
1487 def parse(self, args: list[str], addopts: bool = True) -> None: 1488 # Parse given cmdline arguments into this config object. 1489 assert ( 1490 self.args == [] 1491 ), "can only parse cmdline args at most once per Config object" 1492 self.hook.pytest_addhooks.call_historic( 1493 kwargs=dict(pluginmanager=self.pluginmanager) 1494 ) 1495 self._preparse(args, addopts=addopts) 1496 self._parser.after_preparse = True # type: ignore 1497 try: 1498 args = self._parser.parse_setoption( 1499 args, self.option, namespace=self.option 1500 ) 1501 self.args, self.args_source = self._decide_args( 1502 args=args, 1503 pyargs=self.known_args_namespace.pyargs, 1504 testpaths=self.getini("testpaths"), 1505 invocation_dir=self.invocation_params.dir, 1506 rootpath=self.rootpath, 1507 warn=True, 1508 ) 1509 except PrintHelp: 1510 pass
1512 def issue_config_time_warning(self, warning: Warning, stacklevel: int) -> None: 1513 """Issue and handle a warning during the "configure" stage. 1514 1515 During ``pytest_configure`` we can't capture warnings using the ``catch_warnings_for_item`` 1516 function because it is not possible to have hook wrappers around ``pytest_configure``. 1517 1518 This function is mainly intended for plugins that need to issue warnings during 1519 ``pytest_configure`` (or similar stages). 1520 1521 :param warning: The warning instance. 1522 :param stacklevel: stacklevel forwarded to warnings.warn. 1523 """ 1524 if self.pluginmanager.is_blocked("warnings"): 1525 return 1526 1527 cmdline_filters = self.known_args_namespace.pythonwarnings or [] 1528 config_filters = self.getini("filterwarnings") 1529 1530 with warnings.catch_warnings(record=True) as records: 1531 warnings.simplefilter("always", type(warning)) 1532 apply_warning_filters(config_filters, cmdline_filters) 1533 warnings.warn(warning, stacklevel=stacklevel) 1534 1535 if records: 1536 frame = sys._getframe(stacklevel - 1) 1537 location = frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name 1538 self.hook.pytest_warning_recorded.call_historic( 1539 kwargs=dict( 1540 warning_message=records[0], 1541 when="config", 1542 nodeid="", 1543 location=location, 1544 ) 1545 )
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.
1547 def addinivalue_line(self, name: str, line: str) -> None: 1548 """Add a line to an ini-file option. The option must have been 1549 declared but might not yet be set in which case the line becomes 1550 the first line in its value.""" 1551 x = self.getini(name) 1552 assert isinstance(x, list) 1553 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.
1555 def getini(self, name: str): 1556 """Return configuration value from an :ref:`ini file <configfiles>`. 1557 1558 If a configuration value is not defined in an 1559 :ref:`ini file <configfiles>`, then the ``default`` value provided while 1560 registering the configuration through 1561 :func:`parser.addini <pytest.Parser.addini>` will be returned. 1562 Please note that you can even provide ``None`` as a valid 1563 default value. 1564 1565 If ``default`` is not provided while registering using 1566 :func:`parser.addini <pytest.Parser.addini>`, then a default value 1567 based on the ``type`` parameter passed to 1568 :func:`parser.addini <pytest.Parser.addini>` will be returned. 1569 The default values based on ``type`` are: 1570 ``paths``, ``pathlist``, ``args`` and ``linelist`` : empty list ``[]`` 1571 ``bool`` : ``False`` 1572 ``string`` : empty string ``""`` 1573 1574 If neither the ``default`` nor the ``type`` parameter is passed 1575 while registering the configuration through 1576 :func:`parser.addini <pytest.Parser.addini>`, then the configuration 1577 is treated as a string and a default empty string '' is returned. 1578 1579 If the specified name hasn't been registered through a prior 1580 :func:`parser.addini <pytest.Parser.addini>` call (usually from a 1581 plugin), a ValueError is raised. 1582 """ 1583 try: 1584 return self._inicache[name] 1585 except KeyError: 1586 self._inicache[name] = val = self._getini(name) 1587 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 ""
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.
1683 def getoption(self, name: str, default=notset, skip: bool = False): 1684 """Return command line option value. 1685 1686 :param name: Name of the option. You may also specify 1687 the literal ``--OPT`` option instead of the "dest" option name. 1688 :param default: Fallback value if no option of that name is **declared** via :hook:`pytest_addoption`. 1689 Note this parameter will be ignored when the option is **declared** even if the option's value is ``None``. 1690 :param skip: If ``True``, raise :func:`pytest.skip` if option is undeclared or has a ``None`` value. 1691 Note that even if ``True``, if a default was specified it will be returned instead of a skip. 1692 """ 1693 name = self._opt2dest.get(name, name) 1694 try: 1695 val = getattr(self.option, name) 1696 if val is None and skip: 1697 raise AttributeError(name) 1698 return val 1699 except AttributeError as e: 1700 if default is not notset: 1701 return default 1702 if skip: 1703 import pytest 1704 1705 pytest.skip(f"no {name!r} option found") 1706 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.
1708 def getvalue(self, name: str, path=None): 1709 """Deprecated, use getoption() instead.""" 1710 return self.getoption(name)
Deprecated, use getoption() instead.
1712 def getvalueorskip(self, name: str, path=None): 1713 """Deprecated, use getoption(skip=True) instead.""" 1714 return self.getoption(name, skip=True)
Deprecated, use getoption(skip=True) instead.
1722 def get_verbosity(self, verbosity_type: str | None = None) -> int: 1723 r"""Retrieve the verbosity level for a fine-grained verbosity type. 1724 1725 :param verbosity_type: Verbosity type to get level for. If a level is 1726 configured for the given type, that value will be returned. If the 1727 given type is not a known verbosity type, the global verbosity 1728 level will be returned. If the given type is None (default), the 1729 global verbosity level will be returned. 1730 1731 To configure a level for a fine-grained verbosity type, the 1732 configuration file should have a setting for the configuration name 1733 and a numeric value for the verbosity level. A special value of "auto" 1734 can be used to explicitly use the global verbosity level. 1735 1736 Example: 1737 1738 .. code-block:: ini 1739 1740 # content of pytest.ini 1741 [pytest] 1742 verbosity_assertions = 2 1743 1744 .. code-block:: console 1745 1746 pytest -v 1747 1748 .. code-block:: python 1749 1750 print(config.get_verbosity()) # 1 1751 print(config.get_verbosity(Config.VERBOSITY_ASSERTIONS)) # 2 1752 """ 1753 global_level = self.getoption("verbose", default=0) 1754 assert isinstance(global_level, int) 1755 if verbosity_type is None: 1756 return global_level 1757 1758 ini_name = Config._verbosity_ini_name(verbosity_type) 1759 if ini_name not in self._parser._inidict: 1760 return global_level 1761 1762 level = self.getini(ini_name) 1763 if level == Config._VERBOSITY_INI_DEFAULT: 1764 return global_level 1765 1766 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
978 @final 979 @dataclasses.dataclass(frozen=True) 980 class InvocationParams: 981 """Holds parameters passed during :func:`pytest.main`. 982 983 The object attributes are read-only. 984 985 .. versionadded:: 5.1 986 987 .. note:: 988 989 Note that the environment variable ``PYTEST_ADDOPTS`` and the ``addopts`` 990 ini option are handled by pytest, not being included in the ``args`` attribute. 991 992 Plugins accessing ``InvocationParams`` must be aware of that. 993 """ 994 995 args: tuple[str, ...] 996 """The command-line arguments as passed to :func:`pytest.main`.""" 997 plugins: Sequence[str | _PluggyPlugin] | None 998 """Extra plugins, might be `None`.""" 999 dir: pathlib.Path 1000 """The directory from which :func:`pytest.main` was invoked. :type: pathlib.Path""" 1001 1002 def __init__( 1003 self, 1004 *, 1005 args: Iterable[str], 1006 plugins: Sequence[str | _PluggyPlugin] | None, 1007 dir: pathlib.Path, 1008 ) -> None: 1009 object.__setattr__(self, "args", tuple(args)) 1010 object.__setattr__(self, "plugins", plugins) 1011 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.
1013 class ArgsSource(enum.Enum): 1014 """Indicates the source of the test arguments. 1015 1016 .. versionadded:: 7.2 1017 """ 1018 1019 #: Command line arguments. 1020 ARGS = enum.auto() 1021 #: Invocation directory. 1022 INVOCATION_DIR = enum.auto() 1023 INCOVATION_DIR = INVOCATION_DIR # backwards compatibility alias 1024 #: 'testpaths' configuration value. 1025 TESTPATHS = enum.auto()
Indicates the source of the test arguments.
New in version 7.2.
Inherited Members
- enum.Enum
- name
- value
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.
55def deprecated_call( 56 func: Callable[..., Any] | None = None, *args: Any, **kwargs: Any 57) -> WarningsRecorder | Any: 58 """Assert that code produces a ``DeprecationWarning`` or ``PendingDeprecationWarning`` or ``FutureWarning``. 59 60 This function can be used as a context manager:: 61 62 >>> import warnings 63 >>> def api_call_v2(): 64 ... warnings.warn('use v3 of this api', DeprecationWarning) 65 ... return 200 66 67 >>> import pytest 68 >>> with pytest.deprecated_call(): 69 ... assert api_call_v2() == 200 70 71 It can also be used by passing a function and ``*args`` and ``**kwargs``, 72 in which case it will ensure calling ``func(*args, **kwargs)`` produces one of 73 the warnings types above. The return value is the return value of the function. 74 75 In the context manager form you may use the keyword argument ``match`` to assert 76 that the warning matches a text or regex. 77 78 The context manager produces a list of :class:`warnings.WarningMessage` objects, 79 one for each warning raised. 80 """ 81 __tracebackhide__ = True 82 if func is not None: 83 args = (func, *args) 84 return warns( 85 (DeprecationWarning, PendingDeprecationWarning, FutureWarning), *args, **kwargs 86 )
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.
491@final 492class Dir(nodes.Directory): 493 """Collector of files in a file system directory. 494 495 .. versionadded:: 8.0 496 497 .. note:: 498 499 Python directories with an `__init__.py` file are instead collected by 500 :class:`~pytest.Package` by default. Both are :class:`~pytest.Directory` 501 collectors. 502 """ 503 504 @classmethod 505 def from_parent( # type: ignore[override] 506 cls, 507 parent: nodes.Collector, 508 *, 509 path: Path, 510 ) -> Self: 511 """The public constructor. 512 513 :param parent: The parent collector of this Dir. 514 :param path: The directory's path. 515 :type path: pathlib.Path 516 """ 517 return super().from_parent(parent=parent, path=path) 518 519 def collect(self) -> Iterable[nodes.Item | nodes.Collector]: 520 config = self.config 521 col: nodes.Collector | None 522 cols: Sequence[nodes.Collector] 523 ihook = self.ihook 524 for direntry in scandir(self.path): 525 if direntry.is_dir(): 526 path = Path(direntry.path) 527 if not self.session.isinitpath(path, with_parents=True): 528 if ihook.pytest_ignore_collect(collection_path=path, config=config): 529 continue 530 col = ihook.pytest_collect_directory(path=path, parent=self) 531 if col is not None: 532 yield col 533 534 elif direntry.is_file(): 535 path = Path(direntry.path) 536 if not self.session.isinitpath(path): 537 if ihook.pytest_ignore_collect(collection_path=path, config=config): 538 continue 539 cols = ihook.pytest_collect_file(file_path=path, parent=self) 540 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.
504 @classmethod 505 def from_parent( # type: ignore[override] 506 cls, 507 parent: nodes.Collector, 508 *, 509 path: Path, 510 ) -> Self: 511 """The public constructor. 512 513 :param parent: The parent collector of this Dir. 514 :param path: The directory's path. 515 :type path: pathlib.Path 516 """ 517 return super().from_parent(parent=parent, path=path)
The public constructor.
Parameters
- parent: The parent collector of this Dir.
- path: The directory's path.
519 def collect(self) -> Iterable[nodes.Item | nodes.Collector]: 520 config = self.config 521 col: nodes.Collector | None 522 cols: Sequence[nodes.Collector] 523 ihook = self.ihook 524 for direntry in scandir(self.path): 525 if direntry.is_dir(): 526 path = Path(direntry.path) 527 if not self.session.isinitpath(path, with_parents=True): 528 if ihook.pytest_ignore_collect(collection_path=path, config=config): 529 continue 530 col = ihook.pytest_collect_directory(path=path, parent=self) 531 if col is not None: 532 yield col 533 534 elif direntry.is_file(): 535 path = Path(direntry.path) 536 if not self.session.isinitpath(path): 537 if ihook.pytest_ignore_collect(collection_path=path, config=config): 538 continue 539 cols = ihook.pytest_collect_file(file_path=path, parent=self) 540 yield from cols
Collect children (items and collectors) for this collector.
Inherited Members
- _pytest.nodes.FSCollector
- FSCollector
- path
- _pytest.nodes.Node
- fspath
- name
- parent
- keywords
- own_markers
- extra_keyword_matches
- stash
- ihook
- warn
- nodeid
- setup
- teardown
- iter_parents
- listchain
- add_marker
- iter_markers
- iter_markers_with_node
- get_closest_marker
- listextrakeywords
- listnames
- addfinalizer
- getparent
- config
- session
632class Directory(FSCollector, abc.ABC): 633 """Base class for collecting files from a directory. 634 635 A basic directory collector does the following: goes over the files and 636 sub-directories in the directory and creates collectors for them by calling 637 the hooks :hook:`pytest_collect_directory` and :hook:`pytest_collect_file`, 638 after checking that they are not ignored using 639 :hook:`pytest_ignore_collect`. 640 641 The default directory collectors are :class:`~pytest.Dir` and 642 :class:`~pytest.Package`. 643 644 .. versionadded:: 8.0 645 646 :ref:`custom directory collectors`. 647 """
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
.
Inherited Members
- _pytest.nodes.FSCollector
- path
- from_parent
- _pytest.nodes.Node
- fspath
- name
- parent
- keywords
- own_markers
- extra_keyword_matches
- stash
- ihook
- warn
- nodeid
- setup
- teardown
- iter_parents
- listchain
- add_marker
- iter_markers
- iter_markers_with_node
- get_closest_marker
- listextrakeywords
- listnames
- addfinalizer
- getparent
- config
- session
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 "%03d %s" % (i + test.lineno + 1, 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 "%03d %s" % (i + test.lineno + 1, 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
.
Inherited Members
- _pytest.nodes.Node
- fspath
- name
- parent
- path
- keywords
- own_markers
- extra_keyword_matches
- stash
- ihook
- warn
- nodeid
- teardown
- iter_parents
- listchain
- add_marker
- iter_markers
- iter_markers_with_node
- get_closest_marker
- listextrakeywords
- listnames
- addfinalizer
- getparent
- config
- session
106@_with_exception(Exit) 107def exit( 108 reason: str = "", 109 returncode: int | None = None, 110) -> NoReturn: 111 """Exit testing process. 112 113 :param reason: 114 The message to show as the reason for exiting pytest. reason has a default value 115 only because `msg` is deprecated. 116 117 :param returncode: 118 Return code to be used when exiting pytest. None means the same as ``0`` (no error), same as :func:`sys.exit`. 119 120 :raises pytest.exit.Exception: 121 The exception that is raised. 122 """ 123 __tracebackhide__ = True 124 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.
436@final 437@dataclasses.dataclass 438class ExceptionInfo(Generic[E]): 439 """Wraps sys.exc_info() objects and offers help for navigating the traceback.""" 440 441 _assert_start_repr: ClassVar = "AssertionError('assert " 442 443 _excinfo: tuple[type[E], E, TracebackType] | None 444 _striptext: str 445 _traceback: Traceback | None 446 447 def __init__( 448 self, 449 excinfo: tuple[type[E], E, TracebackType] | None, 450 striptext: str = "", 451 traceback: Traceback | None = None, 452 *, 453 _ispytest: bool = False, 454 ) -> None: 455 check_ispytest(_ispytest) 456 self._excinfo = excinfo 457 self._striptext = striptext 458 self._traceback = traceback 459 460 @classmethod 461 def from_exception( 462 cls, 463 # Ignoring error: "Cannot use a covariant type variable as a parameter". 464 # This is OK to ignore because this class is (conceptually) readonly. 465 # See https://github.com/python/mypy/issues/7049. 466 exception: E, # type: ignore[misc] 467 exprinfo: str | None = None, 468 ) -> ExceptionInfo[E]: 469 """Return an ExceptionInfo for an existing exception. 470 471 The exception must have a non-``None`` ``__traceback__`` attribute, 472 otherwise this function fails with an assertion error. This means that 473 the exception must have been raised, or added a traceback with the 474 :py:meth:`~BaseException.with_traceback()` method. 475 476 :param exprinfo: 477 A text string helping to determine if we should strip 478 ``AssertionError`` from the output. Defaults to the exception 479 message/``__str__()``. 480 481 .. versionadded:: 7.4 482 """ 483 assert exception.__traceback__, ( 484 "Exceptions passed to ExcInfo.from_exception(...)" 485 " must have a non-None __traceback__." 486 ) 487 exc_info = (type(exception), exception, exception.__traceback__) 488 return cls.from_exc_info(exc_info, exprinfo) 489 490 @classmethod 491 def from_exc_info( 492 cls, 493 exc_info: tuple[type[E], E, TracebackType], 494 exprinfo: str | None = None, 495 ) -> ExceptionInfo[E]: 496 """Like :func:`from_exception`, but using old-style exc_info tuple.""" 497 _striptext = "" 498 if exprinfo is None and isinstance(exc_info[1], AssertionError): 499 exprinfo = getattr(exc_info[1], "msg", None) 500 if exprinfo is None: 501 exprinfo = saferepr(exc_info[1]) 502 if exprinfo and exprinfo.startswith(cls._assert_start_repr): 503 _striptext = "AssertionError: " 504 505 return cls(exc_info, _striptext, _ispytest=True) 506 507 @classmethod 508 def from_current(cls, exprinfo: str | None = None) -> ExceptionInfo[BaseException]: 509 """Return an ExceptionInfo matching the current traceback. 510 511 .. warning:: 512 513 Experimental API 514 515 :param exprinfo: 516 A text string helping to determine if we should strip 517 ``AssertionError`` from the output. Defaults to the exception 518 message/``__str__()``. 519 """ 520 tup = sys.exc_info() 521 assert tup[0] is not None, "no current exception" 522 assert tup[1] is not None, "no current exception" 523 assert tup[2] is not None, "no current exception" 524 exc_info = (tup[0], tup[1], tup[2]) 525 return ExceptionInfo.from_exc_info(exc_info, exprinfo) 526 527 @classmethod 528 def for_later(cls) -> ExceptionInfo[E]: 529 """Return an unfilled ExceptionInfo.""" 530 return cls(None, _ispytest=True) 531 532 def fill_unfilled(self, exc_info: tuple[type[E], E, TracebackType]) -> None: 533 """Fill an unfilled ExceptionInfo created with ``for_later()``.""" 534 assert self._excinfo is None, "ExceptionInfo was already filled" 535 self._excinfo = exc_info 536 537 @property 538 def type(self) -> type[E]: 539 """The exception class.""" 540 assert ( 541 self._excinfo is not None 542 ), ".type can only be used after the context manager exits" 543 return self._excinfo[0] 544 545 @property 546 def value(self) -> E: 547 """The exception value.""" 548 assert ( 549 self._excinfo is not None 550 ), ".value can only be used after the context manager exits" 551 return self._excinfo[1] 552 553 @property 554 def tb(self) -> TracebackType: 555 """The exception raw traceback.""" 556 assert ( 557 self._excinfo is not None 558 ), ".tb can only be used after the context manager exits" 559 return self._excinfo[2] 560 561 @property 562 def typename(self) -> str: 563 """The type name of the exception.""" 564 assert ( 565 self._excinfo is not None 566 ), ".typename can only be used after the context manager exits" 567 return self.type.__name__ 568 569 @property 570 def traceback(self) -> Traceback: 571 """The traceback.""" 572 if self._traceback is None: 573 self._traceback = Traceback(self.tb) 574 return self._traceback 575 576 @traceback.setter 577 def traceback(self, value: Traceback) -> None: 578 self._traceback = value 579 580 def __repr__(self) -> str: 581 if self._excinfo is None: 582 return "<ExceptionInfo for raises contextmanager>" 583 return f"<{self.__class__.__name__} {saferepr(self._excinfo[1])} tblen={len(self.traceback)}>" 584 585 def exconly(self, tryshort: bool = False) -> str: 586 """Return the exception as a string. 587 588 When 'tryshort' resolves to True, and the exception is an 589 AssertionError, only the actual exception part of the exception 590 representation is returned (so 'AssertionError: ' is removed from 591 the beginning). 592 """ 593 lines = format_exception_only(self.type, self.value) 594 text = "".join(lines) 595 text = text.rstrip() 596 if tryshort: 597 if text.startswith(self._striptext): 598 text = text[len(self._striptext) :] 599 return text 600 601 def errisinstance(self, exc: EXCEPTION_OR_MORE) -> bool: 602 """Return True if the exception is an instance of exc. 603 604 Consider using ``isinstance(excinfo.value, exc)`` instead. 605 """ 606 return isinstance(self.value, exc) 607 608 def _getreprcrash(self) -> ReprFileLocation | None: 609 # Find last non-hidden traceback entry that led to the exception of the 610 # traceback, or None if all hidden. 611 for i in range(-1, -len(self.traceback) - 1, -1): 612 entry = self.traceback[i] 613 if not entry.ishidden(self): 614 path, lineno = entry.frame.code.raw.co_filename, entry.lineno 615 exconly = self.exconly(tryshort=True) 616 return ReprFileLocation(path, lineno + 1, exconly) 617 return None 618 619 def getrepr( 620 self, 621 showlocals: bool = False, 622 style: TracebackStyle = "long", 623 abspath: bool = False, 624 tbfilter: bool 625 | Callable[[ExceptionInfo[BaseException]], _pytest._code.code.Traceback] = True, 626 funcargs: bool = False, 627 truncate_locals: bool = True, 628 truncate_args: bool = True, 629 chain: bool = True, 630 ) -> ReprExceptionInfo | ExceptionChainRepr: 631 """Return str()able representation of this exception info. 632 633 :param bool showlocals: 634 Show locals per traceback entry. 635 Ignored if ``style=="native"``. 636 637 :param str style: 638 long|short|line|no|native|value traceback style. 639 640 :param bool abspath: 641 If paths should be changed to absolute or left unchanged. 642 643 :param tbfilter: 644 A filter for traceback entries. 645 646 * If false, don't hide any entries. 647 * If true, hide internal entries and entries that contain a local 648 variable ``__tracebackhide__ = True``. 649 * If a callable, delegates the filtering to the callable. 650 651 Ignored if ``style`` is ``"native"``. 652 653 :param bool funcargs: 654 Show fixtures ("funcargs" for legacy purposes) per traceback entry. 655 656 :param bool truncate_locals: 657 With ``showlocals==True``, make sure locals can be safely represented as strings. 658 659 :param bool truncate_args: 660 With ``showargs==True``, make sure args can be safely represented as strings. 661 662 :param bool chain: 663 If chained exceptions in Python 3 should be shown. 664 665 .. versionchanged:: 3.9 666 667 Added the ``chain`` parameter. 668 """ 669 if style == "native": 670 return ReprExceptionInfo( 671 reprtraceback=ReprTracebackNative( 672 traceback.format_exception( 673 self.type, 674 self.value, 675 self.traceback[0]._rawentry if self.traceback else None, 676 ) 677 ), 678 reprcrash=self._getreprcrash(), 679 ) 680 681 fmt = FormattedExcinfo( 682 showlocals=showlocals, 683 style=style, 684 abspath=abspath, 685 tbfilter=tbfilter, 686 funcargs=funcargs, 687 truncate_locals=truncate_locals, 688 truncate_args=truncate_args, 689 chain=chain, 690 ) 691 return fmt.repr_excinfo(self) 692 693 def _stringify_exception(self, exc: BaseException) -> str: 694 try: 695 notes = getattr(exc, "__notes__", []) 696 except KeyError: 697 # Workaround for https://github.com/python/cpython/issues/98778 on 698 # Python <= 3.9, and some 3.10 and 3.11 patch versions. 699 HTTPError = getattr(sys.modules.get("urllib.error", None), "HTTPError", ()) 700 if sys.version_info < (3, 12) and isinstance(exc, HTTPError): 701 notes = [] 702 else: 703 raise 704 705 return "\n".join( 706 [ 707 str(exc), 708 *notes, 709 ] 710 ) 711 712 def match(self, regexp: str | Pattern[str]) -> Literal[True]: 713 """Check whether the regular expression `regexp` matches the string 714 representation of the exception using :func:`python:re.search`. 715 716 If it matches `True` is returned, otherwise an `AssertionError` is raised. 717 """ 718 __tracebackhide__ = True 719 value = self._stringify_exception(self.value) 720 msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}" 721 if regexp == value: 722 msg += "\n Did you mean to `re.escape()` the regex?" 723 assert re.search(regexp, value), msg 724 # Return True to allow for "assert excinfo.match()". 725 return True 726 727 def _group_contains( 728 self, 729 exc_group: BaseExceptionGroup[BaseException], 730 expected_exception: EXCEPTION_OR_MORE, 731 match: str | Pattern[str] | None, 732 target_depth: int | None = None, 733 current_depth: int = 1, 734 ) -> bool: 735 """Return `True` if a `BaseExceptionGroup` contains a matching exception.""" 736 if (target_depth is not None) and (current_depth > target_depth): 737 # already descended past the target depth 738 return False 739 for exc in exc_group.exceptions: 740 if isinstance(exc, BaseExceptionGroup): 741 if self._group_contains( 742 exc, expected_exception, match, target_depth, current_depth + 1 743 ): 744 return True 745 if (target_depth is not None) and (current_depth != target_depth): 746 # not at the target depth, no match 747 continue 748 if not isinstance(exc, expected_exception): 749 continue 750 if match is not None: 751 value = self._stringify_exception(exc) 752 if not re.search(match, value): 753 continue 754 return True 755 return False 756 757 def group_contains( 758 self, 759 expected_exception: EXCEPTION_OR_MORE, 760 *, 761 match: str | Pattern[str] | None = None, 762 depth: int | None = None, 763 ) -> bool: 764 """Check whether a captured exception group contains a matching exception. 765 766 :param Type[BaseException] | Tuple[Type[BaseException]] expected_exception: 767 The expected exception type, or a tuple if one of multiple possible 768 exception types are expected. 769 770 :param str | Pattern[str] | None match: 771 If specified, a string containing a regular expression, 772 or a regular expression object, that is tested against the string 773 representation of the exception and its `PEP-678 <https://peps.python.org/pep-0678/>` `__notes__` 774 using :func:`re.search`. 775 776 To match a literal string that may contain :ref:`special characters 777 <re-syntax>`, the pattern can first be escaped with :func:`re.escape`. 778 779 :param Optional[int] depth: 780 If `None`, will search for a matching exception at any nesting depth. 781 If >= 1, will only match an exception if it's at the specified depth (depth = 1 being 782 the exceptions contained within the topmost exception group). 783 784 .. versionadded:: 8.0 785 """ 786 msg = "Captured exception is not an instance of `BaseExceptionGroup`" 787 assert isinstance(self.value, BaseExceptionGroup), msg 788 msg = "`depth` must be >= 1 if specified" 789 assert (depth is None) or (depth >= 1), msg 790 return self._group_contains(self.value, expected_exception, match, depth)
Wraps sys.exc_info() objects and offers help for navigating the traceback.
447 def __init__( 448 self, 449 excinfo: tuple[type[E], E, TracebackType] | None, 450 striptext: str = "", 451 traceback: Traceback | None = None, 452 *, 453 _ispytest: bool = False, 454 ) -> None: 455 check_ispytest(_ispytest) 456 self._excinfo = excinfo 457 self._striptext = striptext 458 self._traceback = traceback
460 @classmethod 461 def from_exception( 462 cls, 463 # Ignoring error: "Cannot use a covariant type variable as a parameter". 464 # This is OK to ignore because this class is (conceptually) readonly. 465 # See https://github.com/python/mypy/issues/7049. 466 exception: E, # type: ignore[misc] 467 exprinfo: str | None = None, 468 ) -> ExceptionInfo[E]: 469 """Return an ExceptionInfo for an existing exception. 470 471 The exception must have a non-``None`` ``__traceback__`` attribute, 472 otherwise this function fails with an assertion error. This means that 473 the exception must have been raised, or added a traceback with the 474 :py:meth:`~BaseException.with_traceback()` method. 475 476 :param exprinfo: 477 A text string helping to determine if we should strip 478 ``AssertionError`` from the output. Defaults to the exception 479 message/``__str__()``. 480 481 .. versionadded:: 7.4 482 """ 483 assert exception.__traceback__, ( 484 "Exceptions passed to ExcInfo.from_exception(...)" 485 " must have a non-None __traceback__." 486 ) 487 exc_info = (type(exception), exception, exception.__traceback__) 488 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.
490 @classmethod 491 def from_exc_info( 492 cls, 493 exc_info: tuple[type[E], E, TracebackType], 494 exprinfo: str | None = None, 495 ) -> ExceptionInfo[E]: 496 """Like :func:`from_exception`, but using old-style exc_info tuple.""" 497 _striptext = "" 498 if exprinfo is None and isinstance(exc_info[1], AssertionError): 499 exprinfo = getattr(exc_info[1], "msg", None) 500 if exprinfo is None: 501 exprinfo = saferepr(exc_info[1]) 502 if exprinfo and exprinfo.startswith(cls._assert_start_repr): 503 _striptext = "AssertionError: " 504 505 return cls(exc_info, _striptext, _ispytest=True)
Like from_exception()
, but using old-style exc_info tuple.
507 @classmethod 508 def from_current(cls, exprinfo: str | None = None) -> ExceptionInfo[BaseException]: 509 """Return an ExceptionInfo matching the current traceback. 510 511 .. warning:: 512 513 Experimental API 514 515 :param exprinfo: 516 A text string helping to determine if we should strip 517 ``AssertionError`` from the output. Defaults to the exception 518 message/``__str__()``. 519 """ 520 tup = sys.exc_info() 521 assert tup[0] is not None, "no current exception" 522 assert tup[1] is not None, "no current exception" 523 assert tup[2] is not None, "no current exception" 524 exc_info = (tup[0], tup[1], tup[2]) 525 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__()
.
527 @classmethod 528 def for_later(cls) -> ExceptionInfo[E]: 529 """Return an unfilled ExceptionInfo.""" 530 return cls(None, _ispytest=True)
Return an unfilled ExceptionInfo.
532 def fill_unfilled(self, exc_info: tuple[type[E], E, TracebackType]) -> None: 533 """Fill an unfilled ExceptionInfo created with ``for_later()``.""" 534 assert self._excinfo is None, "ExceptionInfo was already filled" 535 self._excinfo = exc_info
Fill an unfilled ExceptionInfo created with for_later()
.
537 @property 538 def type(self) -> type[E]: 539 """The exception class.""" 540 assert ( 541 self._excinfo is not None 542 ), ".type can only be used after the context manager exits" 543 return self._excinfo[0]
The exception class.
545 @property 546 def value(self) -> E: 547 """The exception value.""" 548 assert ( 549 self._excinfo is not None 550 ), ".value can only be used after the context manager exits" 551 return self._excinfo[1]
The exception value.
553 @property 554 def tb(self) -> TracebackType: 555 """The exception raw traceback.""" 556 assert ( 557 self._excinfo is not None 558 ), ".tb can only be used after the context manager exits" 559 return self._excinfo[2]
The exception raw traceback.
561 @property 562 def typename(self) -> str: 563 """The type name of the exception.""" 564 assert ( 565 self._excinfo is not None 566 ), ".typename can only be used after the context manager exits" 567 return self.type.__name__
The type name of the exception.
569 @property 570 def traceback(self) -> Traceback: 571 """The traceback.""" 572 if self._traceback is None: 573 self._traceback = Traceback(self.tb) 574 return self._traceback
The traceback.
585 def exconly(self, tryshort: bool = False) -> str: 586 """Return the exception as a string. 587 588 When 'tryshort' resolves to True, and the exception is an 589 AssertionError, only the actual exception part of the exception 590 representation is returned (so 'AssertionError: ' is removed from 591 the beginning). 592 """ 593 lines = format_exception_only(self.type, self.value) 594 text = "".join(lines) 595 text = text.rstrip() 596 if tryshort: 597 if text.startswith(self._striptext): 598 text = text[len(self._striptext) :] 599 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).
601 def errisinstance(self, exc: EXCEPTION_OR_MORE) -> bool: 602 """Return True if the exception is an instance of exc. 603 604 Consider using ``isinstance(excinfo.value, exc)`` instead. 605 """ 606 return isinstance(self.value, exc)
Return True if the exception is an instance of exc.
Consider using isinstance(excinfo.value, exc)
instead.
619 def getrepr( 620 self, 621 showlocals: bool = False, 622 style: TracebackStyle = "long", 623 abspath: bool = False, 624 tbfilter: bool 625 | Callable[[ExceptionInfo[BaseException]], _pytest._code.code.Traceback] = True, 626 funcargs: bool = False, 627 truncate_locals: bool = True, 628 truncate_args: bool = True, 629 chain: bool = True, 630 ) -> ReprExceptionInfo | ExceptionChainRepr: 631 """Return str()able representation of this exception info. 632 633 :param bool showlocals: 634 Show locals per traceback entry. 635 Ignored if ``style=="native"``. 636 637 :param str style: 638 long|short|line|no|native|value traceback style. 639 640 :param bool abspath: 641 If paths should be changed to absolute or left unchanged. 642 643 :param tbfilter: 644 A filter for traceback entries. 645 646 * If false, don't hide any entries. 647 * If true, hide internal entries and entries that contain a local 648 variable ``__tracebackhide__ = True``. 649 * If a callable, delegates the filtering to the callable. 650 651 Ignored if ``style`` is ``"native"``. 652 653 :param bool funcargs: 654 Show fixtures ("funcargs" for legacy purposes) per traceback entry. 655 656 :param bool truncate_locals: 657 With ``showlocals==True``, make sure locals can be safely represented as strings. 658 659 :param bool truncate_args: 660 With ``showargs==True``, make sure args can be safely represented as strings. 661 662 :param bool chain: 663 If chained exceptions in Python 3 should be shown. 664 665 .. versionchanged:: 3.9 666 667 Added the ``chain`` parameter. 668 """ 669 if style == "native": 670 return ReprExceptionInfo( 671 reprtraceback=ReprTracebackNative( 672 traceback.format_exception( 673 self.type, 674 self.value, 675 self.traceback[0]._rawentry if self.traceback else None, 676 ) 677 ), 678 reprcrash=self._getreprcrash(), 679 ) 680 681 fmt = FormattedExcinfo( 682 showlocals=showlocals, 683 style=style, 684 abspath=abspath, 685 tbfilter=tbfilter, 686 funcargs=funcargs, 687 truncate_locals=truncate_locals, 688 truncate_args=truncate_args, 689 chain=chain, 690 ) 691 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.
712 def match(self, regexp: str | Pattern[str]) -> Literal[True]: 713 """Check whether the regular expression `regexp` matches the string 714 representation of the exception using :func:`python:re.search`. 715 716 If it matches `True` is returned, otherwise an `AssertionError` is raised. 717 """ 718 __tracebackhide__ = True 719 value = self._stringify_exception(self.value) 720 msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}" 721 if regexp == value: 722 msg += "\n Did you mean to `re.escape()` the regex?" 723 assert re.search(regexp, value), msg 724 # Return True to allow for "assert excinfo.match()". 725 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.
757 def group_contains( 758 self, 759 expected_exception: EXCEPTION_OR_MORE, 760 *, 761 match: str | Pattern[str] | None = None, 762 depth: int | None = None, 763 ) -> bool: 764 """Check whether a captured exception group contains a matching exception. 765 766 :param Type[BaseException] | Tuple[Type[BaseException]] expected_exception: 767 The expected exception type, or a tuple if one of multiple possible 768 exception types are expected. 769 770 :param str | Pattern[str] | None match: 771 If specified, a string containing a regular expression, 772 or a regular expression object, that is tested against the string 773 representation of the exception and its `PEP-678 <https://peps.python.org/pep-0678/>` `__notes__` 774 using :func:`re.search`. 775 776 To match a literal string that may contain :ref:`special characters 777 <re-syntax>`, the pattern can first be escaped with :func:`re.escape`. 778 779 :param Optional[int] depth: 780 If `None`, will search for a matching exception at any nesting depth. 781 If >= 1, will only match an exception if it's at the specified depth (depth = 1 being 782 the exceptions contained within the topmost exception group). 783 784 .. versionadded:: 8.0 785 """ 786 msg = "Captured exception is not an instance of `BaseExceptionGroup`" 787 assert isinstance(self.value, BaseExceptionGroup), msg 788 msg = "`depth` must be >= 1 if specified" 789 assert (depth is None) or (depth >= 1), msg 790 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 | 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.
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.
Inherited Members
- enum.Enum
- name
- value
- builtins.int
- conjugate
- bit_length
- bit_count
- to_bytes
- from_bytes
- as_integer_ratio
- is_integer
- real
- imag
- numerator
- denominator
164@_with_exception(Failed) 165def fail(reason: str = "", pytrace: bool = True) -> NoReturn: 166 """Explicitly fail an executing test with the given message. 167 168 :param reason: 169 The message to show the user as reason for the failure. 170 171 :param pytrace: 172 If False, msg represents the full failure information and no 173 python traceback will be reported. 174 175 :raises pytest.fail.Exception: 176 The exception that is raised. 177 """ 178 __tracebackhide__ = True 179 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.
625class File(FSCollector, abc.ABC): 626 """Base class for collecting tests from a file. 627 628 :ref:`non-python tests`. 629 """
Base class for collecting tests from a file.
:ref:non-python tests
.
Inherited Members
- _pytest.nodes.FSCollector
- path
- from_parent
- _pytest.nodes.Node
- fspath
- name
- parent
- keywords
- own_markers
- extra_keyword_matches
- stash
- ihook
- warn
- nodeid
- setup
- teardown
- iter_parents
- listchain
- add_marker
- iter_markers
- iter_markers_with_node
- get_closest_marker
- listextrakeywords
- listnames
- addfinalizer
- getparent
- config
- session
1244def fixture( 1245 fixture_function: FixtureFunction | None = None, 1246 *, 1247 scope: _ScopeName | Callable[[str, Config], _ScopeName] = "function", 1248 params: Iterable[object] | None = None, 1249 autouse: bool = False, 1250 ids: Sequence[object | None] | Callable[[Any], object | None] | None = None, 1251 name: str | None = None, 1252) -> FixtureFunctionMarker | FixtureFunction: 1253 """Decorator to mark a fixture factory function. 1254 1255 This decorator can be used, with or without parameters, to define a 1256 fixture function. 1257 1258 The name of the fixture function can later be referenced to cause its 1259 invocation ahead of running tests: test modules or classes can use the 1260 ``pytest.mark.usefixtures(fixturename)`` marker. 1261 1262 Test functions can directly use fixture names as input arguments in which 1263 case the fixture instance returned from the fixture function will be 1264 injected. 1265 1266 Fixtures can provide their values to test functions using ``return`` or 1267 ``yield`` statements. When using ``yield`` the code block after the 1268 ``yield`` statement is executed as teardown code regardless of the test 1269 outcome, and must yield exactly once. 1270 1271 :param scope: 1272 The scope for which this fixture is shared; one of ``"function"`` 1273 (default), ``"class"``, ``"module"``, ``"package"`` or ``"session"``. 1274 1275 This parameter may also be a callable which receives ``(fixture_name, config)`` 1276 as parameters, and must return a ``str`` with one of the values mentioned above. 1277 1278 See :ref:`dynamic scope` in the docs for more information. 1279 1280 :param params: 1281 An optional list of parameters which will cause multiple invocations 1282 of the fixture function and all of the tests using it. The current 1283 parameter is available in ``request.param``. 1284 1285 :param autouse: 1286 If True, the fixture func is activated for all tests that can see it. 1287 If False (the default), an explicit reference is needed to activate 1288 the fixture. 1289 1290 :param ids: 1291 Sequence of ids each corresponding to the params so that they are 1292 part of the test id. If no ids are provided they will be generated 1293 automatically from the params. 1294 1295 :param name: 1296 The name of the fixture. This defaults to the name of the decorated 1297 function. If a fixture is used in the same module in which it is 1298 defined, the function name of the fixture will be shadowed by the 1299 function arg that requests the fixture; one way to resolve this is to 1300 name the decorated function ``fixture_<fixturename>`` and then use 1301 ``@pytest.fixture(name='<fixturename>')``. 1302 """ 1303 fixture_marker = FixtureFunctionMarker( 1304 scope=scope, 1305 params=tuple(params) if params is not None else None, 1306 autouse=autouse, 1307 ids=None if ids is None else ids if callable(ids) else tuple(ids), 1308 name=name, 1309 _ispytest=True, 1310 ) 1311 1312 # Direct decoration. 1313 if fixture_function: 1314 return fixture_marker(fixture_function) 1315 1316 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>')
.
944@final 945class FixtureDef(Generic[FixtureValue]): 946 """A container for a fixture definition. 947 948 Note: At this time, only explicitly documented fields and methods are 949 considered public stable API. 950 """ 951 952 def __init__( 953 self, 954 config: Config, 955 baseid: str | None, 956 argname: str, 957 func: _FixtureFunc[FixtureValue], 958 scope: Scope | _ScopeName | Callable[[str, Config], _ScopeName] | None, 959 params: Sequence[object] | None, 960 ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None, 961 *, 962 _ispytest: bool = False, 963 ) -> None: 964 check_ispytest(_ispytest) 965 # The "base" node ID for the fixture. 966 # 967 # This is a node ID prefix. A fixture is only available to a node (e.g. 968 # a `Function` item) if the fixture's baseid is a nodeid of a parent of 969 # node. 970 # 971 # For a fixture found in a Collector's object (e.g. a `Module`s module, 972 # a `Class`'s class), the baseid is the Collector's nodeid. 973 # 974 # For a fixture found in a conftest plugin, the baseid is the conftest's 975 # directory path relative to the rootdir. 976 # 977 # For other plugins, the baseid is the empty string (always matches). 978 self.baseid: Final = baseid or "" 979 # Whether the fixture was found from a node or a conftest in the 980 # collection tree. Will be false for fixtures defined in non-conftest 981 # plugins. 982 self.has_location: Final = baseid is not None 983 # The fixture factory function. 984 self.func: Final = func 985 # The name by which the fixture may be requested. 986 self.argname: Final = argname 987 if scope is None: 988 scope = Scope.Function 989 elif callable(scope): 990 scope = _eval_scope_callable(scope, argname, config) 991 if isinstance(scope, str): 992 scope = Scope.from_user( 993 scope, descr=f"Fixture '{func.__name__}'", where=baseid 994 ) 995 self._scope: Final = scope 996 # If the fixture is directly parametrized, the parameter values. 997 self.params: Final = params 998 # If the fixture is directly parametrized, a tuple of explicit IDs to 999 # assign to the parameter values, or a callable to generate an ID given 1000 # a parameter value. 1001 self.ids: Final = ids 1002 # The names requested by the fixtures. 1003 self.argnames: Final = getfuncargnames(func, name=argname) 1004 # If the fixture was executed, the current value of the fixture. 1005 # Can change if the fixture is executed with different parameters. 1006 self.cached_result: _FixtureCachedResult[FixtureValue] | None = None 1007 self._finalizers: Final[list[Callable[[], object]]] = [] 1008 1009 @property 1010 def scope(self) -> _ScopeName: 1011 """Scope string, one of "function", "class", "module", "package", "session".""" 1012 return self._scope.value 1013 1014 def addfinalizer(self, finalizer: Callable[[], object]) -> None: 1015 self._finalizers.append(finalizer) 1016 1017 def finish(self, request: SubRequest) -> None: 1018 exceptions: list[BaseException] = [] 1019 while self._finalizers: 1020 fin = self._finalizers.pop() 1021 try: 1022 fin() 1023 except BaseException as e: 1024 exceptions.append(e) 1025 node = request.node 1026 node.ihook.pytest_fixture_post_finalizer(fixturedef=self, request=request) 1027 # Even if finalization fails, we invalidate the cached fixture 1028 # value and remove all finalizers because they may be bound methods 1029 # which will keep instances alive. 1030 self.cached_result = None 1031 self._finalizers.clear() 1032 if len(exceptions) == 1: 1033 raise exceptions[0] 1034 elif len(exceptions) > 1: 1035 msg = f'errors while tearing down fixture "{self.argname}" of {node}' 1036 raise BaseExceptionGroup(msg, exceptions[::-1]) 1037 1038 def execute(self, request: SubRequest) -> FixtureValue: 1039 """Return the value of this fixture, executing it if not cached.""" 1040 # Ensure that the dependent fixtures requested by this fixture are loaded. 1041 # This needs to be done before checking if we have a cached value, since 1042 # if a dependent fixture has their cache invalidated, e.g. due to 1043 # parametrization, they finalize themselves and fixtures depending on it 1044 # (which will likely include this fixture) setting `self.cached_result = None`. 1045 # See #4871 1046 requested_fixtures_that_should_finalize_us = [] 1047 for argname in self.argnames: 1048 fixturedef = request._get_active_fixturedef(argname) 1049 # Saves requested fixtures in a list so we later can add our finalizer 1050 # to them, ensuring that if a requested fixture gets torn down we get torn 1051 # down first. This is generally handled by SetupState, but still currently 1052 # needed when this fixture is not parametrized but depends on a parametrized 1053 # fixture. 1054 if not isinstance(fixturedef, PseudoFixtureDef): 1055 requested_fixtures_that_should_finalize_us.append(fixturedef) 1056 1057 # Check for (and return) cached value/exception. 1058 if self.cached_result is not None: 1059 request_cache_key = self.cache_key(request) 1060 cache_key = self.cached_result[1] 1061 try: 1062 # Attempt to make a normal == check: this might fail for objects 1063 # which do not implement the standard comparison (like numpy arrays -- #6497). 1064 cache_hit = bool(request_cache_key == cache_key) 1065 except (ValueError, RuntimeError): 1066 # If the comparison raises, use 'is' as fallback. 1067 cache_hit = request_cache_key is cache_key 1068 1069 if cache_hit: 1070 if self.cached_result[2] is not None: 1071 exc, exc_tb = self.cached_result[2] 1072 raise exc.with_traceback(exc_tb) 1073 else: 1074 result = self.cached_result[0] 1075 return result 1076 # We have a previous but differently parametrized fixture instance 1077 # so we need to tear it down before creating a new one. 1078 self.finish(request) 1079 assert self.cached_result is None 1080 1081 # Add finalizer to requested fixtures we saved previously. 1082 # We make sure to do this after checking for cached value to avoid 1083 # adding our finalizer multiple times. (#12135) 1084 finalizer = functools.partial(self.finish, request=request) 1085 for parent_fixture in requested_fixtures_that_should_finalize_us: 1086 parent_fixture.addfinalizer(finalizer) 1087 1088 ihook = request.node.ihook 1089 try: 1090 # Setup the fixture, run the code in it, and cache the value 1091 # in self.cached_result 1092 result = ihook.pytest_fixture_setup(fixturedef=self, request=request) 1093 finally: 1094 # schedule our finalizer, even if the setup failed 1095 request.node.addfinalizer(finalizer) 1096 1097 return result 1098 1099 def cache_key(self, request: SubRequest) -> object: 1100 return getattr(request, "param", None) 1101 1102 def __repr__(self) -> str: 1103 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.
952 def __init__( 953 self, 954 config: Config, 955 baseid: str | None, 956 argname: str, 957 func: _FixtureFunc[FixtureValue], 958 scope: Scope | _ScopeName | Callable[[str, Config], _ScopeName] | None, 959 params: Sequence[object] | None, 960 ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None, 961 *, 962 _ispytest: bool = False, 963 ) -> None: 964 check_ispytest(_ispytest) 965 # The "base" node ID for the fixture. 966 # 967 # This is a node ID prefix. A fixture is only available to a node (e.g. 968 # a `Function` item) if the fixture's baseid is a nodeid of a parent of 969 # node. 970 # 971 # For a fixture found in a Collector's object (e.g. a `Module`s module, 972 # a `Class`'s class), the baseid is the Collector's nodeid. 973 # 974 # For a fixture found in a conftest plugin, the baseid is the conftest's 975 # directory path relative to the rootdir. 976 # 977 # For other plugins, the baseid is the empty string (always matches). 978 self.baseid: Final = baseid or "" 979 # Whether the fixture was found from a node or a conftest in the 980 # collection tree. Will be false for fixtures defined in non-conftest 981 # plugins. 982 self.has_location: Final = baseid is not None 983 # The fixture factory function. 984 self.func: Final = func 985 # The name by which the fixture may be requested. 986 self.argname: Final = argname 987 if scope is None: 988 scope = Scope.Function 989 elif callable(scope): 990 scope = _eval_scope_callable(scope, argname, config) 991 if isinstance(scope, str): 992 scope = Scope.from_user( 993 scope, descr=f"Fixture '{func.__name__}'", where=baseid 994 ) 995 self._scope: Final = scope 996 # If the fixture is directly parametrized, the parameter values. 997 self.params: Final = params 998 # If the fixture is directly parametrized, a tuple of explicit IDs to 999 # assign to the parameter values, or a callable to generate an ID given 1000 # a parameter value. 1001 self.ids: Final = ids 1002 # The names requested by the fixtures. 1003 self.argnames: Final = getfuncargnames(func, name=argname) 1004 # If the fixture was executed, the current value of the fixture. 1005 # Can change if the fixture is executed with different parameters. 1006 self.cached_result: _FixtureCachedResult[FixtureValue] | None = None 1007 self._finalizers: Final[list[Callable[[], object]]] = []
1009 @property 1010 def scope(self) -> _ScopeName: 1011 """Scope string, one of "function", "class", "module", "package", "session".""" 1012 return self._scope.value
Scope string, one of "function", "class", "module", "package", "session".
1017 def finish(self, request: SubRequest) -> None: 1018 exceptions: list[BaseException] = [] 1019 while self._finalizers: 1020 fin = self._finalizers.pop() 1021 try: 1022 fin() 1023 except BaseException as e: 1024 exceptions.append(e) 1025 node = request.node 1026 node.ihook.pytest_fixture_post_finalizer(fixturedef=self, request=request) 1027 # Even if finalization fails, we invalidate the cached fixture 1028 # value and remove all finalizers because they may be bound methods 1029 # which will keep instances alive. 1030 self.cached_result = None 1031 self._finalizers.clear() 1032 if len(exceptions) == 1: 1033 raise exceptions[0] 1034 elif len(exceptions) > 1: 1035 msg = f'errors while tearing down fixture "{self.argname}" of {node}' 1036 raise BaseExceptionGroup(msg, exceptions[::-1])
1038 def execute(self, request: SubRequest) -> FixtureValue: 1039 """Return the value of this fixture, executing it if not cached.""" 1040 # Ensure that the dependent fixtures requested by this fixture are loaded. 1041 # This needs to be done before checking if we have a cached value, since 1042 # if a dependent fixture has their cache invalidated, e.g. due to 1043 # parametrization, they finalize themselves and fixtures depending on it 1044 # (which will likely include this fixture) setting `self.cached_result = None`. 1045 # See #4871 1046 requested_fixtures_that_should_finalize_us = [] 1047 for argname in self.argnames: 1048 fixturedef = request._get_active_fixturedef(argname) 1049 # Saves requested fixtures in a list so we later can add our finalizer 1050 # to them, ensuring that if a requested fixture gets torn down we get torn 1051 # down first. This is generally handled by SetupState, but still currently 1052 # needed when this fixture is not parametrized but depends on a parametrized 1053 # fixture. 1054 if not isinstance(fixturedef, PseudoFixtureDef): 1055 requested_fixtures_that_should_finalize_us.append(fixturedef) 1056 1057 # Check for (and return) cached value/exception. 1058 if self.cached_result is not None: 1059 request_cache_key = self.cache_key(request) 1060 cache_key = self.cached_result[1] 1061 try: 1062 # Attempt to make a normal == check: this might fail for objects 1063 # which do not implement the standard comparison (like numpy arrays -- #6497). 1064 cache_hit = bool(request_cache_key == cache_key) 1065 except (ValueError, RuntimeError): 1066 # If the comparison raises, use 'is' as fallback. 1067 cache_hit = request_cache_key is cache_key 1068 1069 if cache_hit: 1070 if self.cached_result[2] is not None: 1071 exc, exc_tb = self.cached_result[2] 1072 raise exc.with_traceback(exc_tb) 1073 else: 1074 result = self.cached_result[0] 1075 return result 1076 # We have a previous but differently parametrized fixture instance 1077 # so we need to tear it down before creating a new one. 1078 self.finish(request) 1079 assert self.cached_result is None 1080 1081 # Add finalizer to requested fixtures we saved previously. 1082 # We make sure to do this after checking for cached value to avoid 1083 # adding our finalizer multiple times. (#12135) 1084 finalizer = functools.partial(self.finish, request=request) 1085 for parent_fixture in requested_fixtures_that_should_finalize_us: 1086 parent_fixture.addfinalizer(finalizer) 1087 1088 ihook = request.node.ihook 1089 try: 1090 # Setup the fixture, run the code in it, and cache the value 1091 # in self.cached_result 1092 result = ihook.pytest_fixture_setup(fixturedef=self, request=request) 1093 finally: 1094 # schedule our finalizer, even if the setup failed 1095 request.node.addfinalizer(finalizer) 1096 1097 return result
Return the value of this fixture, executing it if not cached.
791@final 792class FixtureLookupError(LookupError): 793 """Could not return a requested fixture (missing or invalid).""" 794 795 def __init__( 796 self, argname: str | None, request: FixtureRequest, msg: str | None = None 797 ) -> None: 798 self.argname = argname 799 self.request = request 800 self.fixturestack = request._get_fixturestack() 801 self.msg = msg 802 803 def formatrepr(self) -> FixtureLookupErrorRepr: 804 tblines: list[str] = [] 805 addline = tblines.append 806 stack = [self.request._pyfuncitem.obj] 807 stack.extend(map(lambda x: x.func, self.fixturestack)) 808 msg = self.msg 809 if msg is not None: 810 # The last fixture raise an error, let's present 811 # it at the requesting side. 812 stack = stack[:-1] 813 for function in stack: 814 fspath, lineno = getfslineno(function) 815 try: 816 lines, _ = inspect.getsourcelines(get_real_func(function)) 817 except (OSError, IndexError, TypeError): 818 error_msg = "file %s, line %s: source code not available" 819 addline(error_msg % (fspath, lineno + 1)) 820 else: 821 addline(f"file {fspath}, line {lineno + 1}") 822 for i, line in enumerate(lines): 823 line = line.rstrip() 824 addline(" " + line) 825 if line.lstrip().startswith("def"): 826 break 827 828 if msg is None: 829 fm = self.request._fixturemanager 830 available = set() 831 parent = self.request._pyfuncitem.parent 832 assert parent is not None 833 for name, fixturedefs in fm._arg2fixturedefs.items(): 834 faclist = list(fm._matchfactories(fixturedefs, parent)) 835 if faclist: 836 available.add(name) 837 if self.argname in available: 838 msg = ( 839 f" recursive dependency involving fixture '{self.argname}' detected" 840 ) 841 else: 842 msg = f"fixture '{self.argname}' not found" 843 msg += "\n available fixtures: {}".format(", ".join(sorted(available))) 844 msg += "\n use 'pytest --fixtures [testpath]' for help on them." 845 846 return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname)
Could not return a requested fixture (missing or invalid).
803 def formatrepr(self) -> FixtureLookupErrorRepr: 804 tblines: list[str] = [] 805 addline = tblines.append 806 stack = [self.request._pyfuncitem.obj] 807 stack.extend(map(lambda x: x.func, self.fixturestack)) 808 msg = self.msg 809 if msg is not None: 810 # The last fixture raise an error, let's present 811 # it at the requesting side. 812 stack = stack[:-1] 813 for function in stack: 814 fspath, lineno = getfslineno(function) 815 try: 816 lines, _ = inspect.getsourcelines(get_real_func(function)) 817 except (OSError, IndexError, TypeError): 818 error_msg = "file %s, line %s: source code not available" 819 addline(error_msg % (fspath, lineno + 1)) 820 else: 821 addline(f"file {fspath}, line {lineno + 1}") 822 for i, line in enumerate(lines): 823 line = line.rstrip() 824 addline(" " + line) 825 if line.lstrip().startswith("def"): 826 break 827 828 if msg is None: 829 fm = self.request._fixturemanager 830 available = set() 831 parent = self.request._pyfuncitem.parent 832 assert parent is not None 833 for name, fixturedefs in fm._arg2fixturedefs.items(): 834 faclist = list(fm._matchfactories(fixturedefs, parent)) 835 if faclist: 836 available.add(name) 837 if self.argname in available: 838 msg = ( 839 f" recursive dependency involving fixture '{self.argname}' detected" 840 ) 841 else: 842 msg = f"fixture '{self.argname}' not found" 843 msg += "\n available fixtures: {}".format(", ".join(sorted(available))) 844 msg += "\n use 'pytest --fixtures [testpath]' for help on them." 845 846 return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname)
Inherited Members
- builtins.BaseException
- with_traceback
- add_note
- args
356class FixtureRequest(abc.ABC): 357 """The type of the ``request`` fixture. 358 359 A request object gives access to the requesting test context and has a 360 ``param`` attribute in case the fixture is parametrized. 361 """ 362 363 def __init__( 364 self, 365 pyfuncitem: Function, 366 fixturename: str | None, 367 arg2fixturedefs: dict[str, Sequence[FixtureDef[Any]]], 368 fixture_defs: dict[str, FixtureDef[Any]], 369 *, 370 _ispytest: bool = False, 371 ) -> None: 372 check_ispytest(_ispytest) 373 #: Fixture for which this request is being performed. 374 self.fixturename: Final = fixturename 375 self._pyfuncitem: Final = pyfuncitem 376 # The FixtureDefs for each fixture name requested by this item. 377 # Starts from the statically-known fixturedefs resolved during 378 # collection. Dynamically requested fixtures (using 379 # `request.getfixturevalue("foo")`) are added dynamically. 380 self._arg2fixturedefs: Final = arg2fixturedefs 381 # The evaluated argnames so far, mapping to the FixtureDef they resolved 382 # to. 383 self._fixture_defs: Final = fixture_defs 384 # Notes on the type of `param`: 385 # -`request.param` is only defined in parametrized fixtures, and will raise 386 # AttributeError otherwise. Python typing has no notion of "undefined", so 387 # this cannot be reflected in the type. 388 # - Technically `param` is only (possibly) defined on SubRequest, not 389 # FixtureRequest, but the typing of that is still in flux so this cheats. 390 # - In the future we might consider using a generic for the param type, but 391 # for now just using Any. 392 self.param: Any 393 394 @property 395 def _fixturemanager(self) -> FixtureManager: 396 return self._pyfuncitem.session._fixturemanager 397 398 @property 399 @abc.abstractmethod 400 def _scope(self) -> Scope: 401 raise NotImplementedError() 402 403 @property 404 def scope(self) -> _ScopeName: 405 """Scope string, one of "function", "class", "module", "package", "session".""" 406 return self._scope.value 407 408 @abc.abstractmethod 409 def _check_scope( 410 self, 411 requested_fixturedef: FixtureDef[object] | PseudoFixtureDef[object], 412 requested_scope: Scope, 413 ) -> None: 414 raise NotImplementedError() 415 416 @property 417 def fixturenames(self) -> list[str]: 418 """Names of all active fixtures in this request.""" 419 result = list(self._pyfuncitem.fixturenames) 420 result.extend(set(self._fixture_defs).difference(result)) 421 return result 422 423 @property 424 @abc.abstractmethod 425 def node(self): 426 """Underlying collection node (depends on current request scope).""" 427 raise NotImplementedError() 428 429 @property 430 def config(self) -> Config: 431 """The pytest config object associated with this request.""" 432 return self._pyfuncitem.config 433 434 @property 435 def function(self): 436 """Test function object if the request has a per-function scope.""" 437 if self.scope != "function": 438 raise AttributeError( 439 f"function not available in {self.scope}-scoped context" 440 ) 441 return self._pyfuncitem.obj 442 443 @property 444 def cls(self): 445 """Class (can be None) where the test function was collected.""" 446 if self.scope not in ("class", "function"): 447 raise AttributeError(f"cls not available in {self.scope}-scoped context") 448 clscol = self._pyfuncitem.getparent(_pytest.python.Class) 449 if clscol: 450 return clscol.obj 451 452 @property 453 def instance(self): 454 """Instance (can be None) on which test function was collected.""" 455 if self.scope != "function": 456 return None 457 return getattr(self._pyfuncitem, "instance", None) 458 459 @property 460 def module(self): 461 """Python module object where the test function was collected.""" 462 if self.scope not in ("function", "class", "module"): 463 raise AttributeError(f"module not available in {self.scope}-scoped context") 464 mod = self._pyfuncitem.getparent(_pytest.python.Module) 465 assert mod is not None 466 return mod.obj 467 468 @property 469 def path(self) -> Path: 470 """Path where the test function was collected.""" 471 if self.scope not in ("function", "class", "module", "package"): 472 raise AttributeError(f"path not available in {self.scope}-scoped context") 473 return self._pyfuncitem.path 474 475 @property 476 def keywords(self) -> MutableMapping[str, Any]: 477 """Keywords/markers dictionary for the underlying node.""" 478 node: nodes.Node = self.node 479 return node.keywords 480 481 @property 482 def session(self) -> Session: 483 """Pytest session object.""" 484 return self._pyfuncitem.session 485 486 @abc.abstractmethod 487 def addfinalizer(self, finalizer: Callable[[], object]) -> None: 488 """Add finalizer/teardown function to be called without arguments after 489 the last test within the requesting test context finished execution.""" 490 raise NotImplementedError() 491 492 def applymarker(self, marker: str | MarkDecorator) -> None: 493 """Apply a marker to a single test function invocation. 494 495 This method is useful if you don't want to have a keyword/marker 496 on all function invocations. 497 498 :param marker: 499 An object created by a call to ``pytest.mark.NAME(...)``. 500 """ 501 self.node.add_marker(marker) 502 503 def raiseerror(self, msg: str | None) -> NoReturn: 504 """Raise a FixtureLookupError exception. 505 506 :param msg: 507 An optional custom error message. 508 """ 509 raise FixtureLookupError(None, self, msg) 510 511 def getfixturevalue(self, argname: str) -> Any: 512 """Dynamically run a named fixture function. 513 514 Declaring fixtures via function argument is recommended where possible. 515 But if you can only decide whether to use another fixture at test 516 setup time, you may use this function to retrieve it inside a fixture 517 or test function body. 518 519 This method can be used during the test setup phase or the test run 520 phase, but during the test teardown phase a fixture's value may not 521 be available. 522 523 :param argname: 524 The fixture name. 525 :raises pytest.FixtureLookupError: 526 If the given fixture could not be found. 527 """ 528 # Note that in addition to the use case described in the docstring, 529 # getfixturevalue() is also called by pytest itself during item and fixture 530 # setup to evaluate the fixtures that are requested statically 531 # (using function parameters, autouse, etc). 532 533 fixturedef = self._get_active_fixturedef(argname) 534 assert fixturedef.cached_result is not None, ( 535 f'The fixture value for "{argname}" is not available. ' 536 "This can happen when the fixture has already been torn down." 537 ) 538 return fixturedef.cached_result[0] 539 540 def _iter_chain(self) -> Iterator[SubRequest]: 541 """Yield all SubRequests in the chain, from self up. 542 543 Note: does *not* yield the TopRequest. 544 """ 545 current = self 546 while isinstance(current, SubRequest): 547 yield current 548 current = current._parent_request 549 550 def _get_active_fixturedef( 551 self, argname: str 552 ) -> FixtureDef[object] | PseudoFixtureDef[object]: 553 if argname == "request": 554 cached_result = (self, [0], None) 555 return PseudoFixtureDef(cached_result, Scope.Function) 556 557 # If we already finished computing a fixture by this name in this item, 558 # return it. 559 fixturedef = self._fixture_defs.get(argname) 560 if fixturedef is not None: 561 self._check_scope(fixturedef, fixturedef._scope) 562 return fixturedef 563 564 # Find the appropriate fixturedef. 565 fixturedefs = self._arg2fixturedefs.get(argname, None) 566 if fixturedefs is None: 567 # We arrive here because of a dynamic call to 568 # getfixturevalue(argname) which was naturally 569 # not known at parsing/collection time. 570 fixturedefs = self._fixturemanager.getfixturedefs(argname, self._pyfuncitem) 571 if fixturedefs is not None: 572 self._arg2fixturedefs[argname] = fixturedefs 573 # No fixtures defined with this name. 574 if fixturedefs is None: 575 raise FixtureLookupError(argname, self) 576 # The are no fixtures with this name applicable for the function. 577 if not fixturedefs: 578 raise FixtureLookupError(argname, self) 579 # A fixture may override another fixture with the same name, e.g. a 580 # fixture in a module can override a fixture in a conftest, a fixture in 581 # a class can override a fixture in the module, and so on. 582 # An overriding fixture can request its own name (possibly indirectly); 583 # in this case it gets the value of the fixture it overrides, one level 584 # up. 585 # Check how many `argname`s deep we are, and take the next one. 586 # `fixturedefs` is sorted from furthest to closest, so use negative 587 # indexing to go in reverse. 588 index = -1 589 for request in self._iter_chain(): 590 if request.fixturename == argname: 591 index -= 1 592 # If already consumed all of the available levels, fail. 593 if -index > len(fixturedefs): 594 raise FixtureLookupError(argname, self) 595 fixturedef = fixturedefs[index] 596 597 # Prepare a SubRequest object for calling the fixture. 598 try: 599 callspec = self._pyfuncitem.callspec 600 except AttributeError: 601 callspec = None 602 if callspec is not None and argname in callspec.params: 603 param = callspec.params[argname] 604 param_index = callspec.indices[argname] 605 # The parametrize invocation scope overrides the fixture's scope. 606 scope = callspec._arg2scope[argname] 607 else: 608 param = NOTSET 609 param_index = 0 610 scope = fixturedef._scope 611 self._check_fixturedef_without_param(fixturedef) 612 self._check_scope(fixturedef, scope) 613 subrequest = SubRequest( 614 self, scope, param, param_index, fixturedef, _ispytest=True 615 ) 616 617 # Make sure the fixture value is cached, running it if it isn't 618 fixturedef.execute(request=subrequest) 619 620 self._fixture_defs[argname] = fixturedef 621 return fixturedef 622 623 def _check_fixturedef_without_param(self, fixturedef: FixtureDef[object]) -> None: 624 """Check that this request is allowed to execute this fixturedef without 625 a param.""" 626 funcitem = self._pyfuncitem 627 has_params = fixturedef.params is not None 628 fixtures_not_supported = getattr(funcitem, "nofuncargs", False) 629 if has_params and fixtures_not_supported: 630 msg = ( 631 f"{funcitem.name} does not support fixtures, maybe unittest.TestCase subclass?\n" 632 f"Node id: {funcitem.nodeid}\n" 633 f"Function type: {type(funcitem).__name__}" 634 ) 635 fail(msg, pytrace=False) 636 if has_params: 637 frame = inspect.stack()[3] 638 frameinfo = inspect.getframeinfo(frame[0]) 639 source_path = absolutepath(frameinfo.filename) 640 source_lineno = frameinfo.lineno 641 try: 642 source_path_str = str(source_path.relative_to(funcitem.config.rootpath)) 643 except ValueError: 644 source_path_str = str(source_path) 645 location = getlocation(fixturedef.func, funcitem.config.rootpath) 646 msg = ( 647 "The requested fixture has no parameter defined for test:\n" 648 f" {funcitem.nodeid}\n\n" 649 f"Requested fixture '{fixturedef.argname}' defined in:\n" 650 f"{location}\n\n" 651 f"Requested here:\n" 652 f"{source_path_str}:{source_lineno}" 653 ) 654 fail(msg, pytrace=False) 655 656 def _get_fixturestack(self) -> list[FixtureDef[Any]]: 657 values = [request._fixturedef for request in self._iter_chain()] 658 values.reverse() 659 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.
403 @property 404 def scope(self) -> _ScopeName: 405 """Scope string, one of "function", "class", "module", "package", "session".""" 406 return self._scope.value
Scope string, one of "function", "class", "module", "package", "session".
416 @property 417 def fixturenames(self) -> list[str]: 418 """Names of all active fixtures in this request.""" 419 result = list(self._pyfuncitem.fixturenames) 420 result.extend(set(self._fixture_defs).difference(result)) 421 return result
Names of all active fixtures in this request.
423 @property 424 @abc.abstractmethod 425 def node(self): 426 """Underlying collection node (depends on current request scope).""" 427 raise NotImplementedError()
Underlying collection node (depends on current request scope).
429 @property 430 def config(self) -> Config: 431 """The pytest config object associated with this request.""" 432 return self._pyfuncitem.config
The pytest config object associated with this request.
434 @property 435 def function(self): 436 """Test function object if the request has a per-function scope.""" 437 if self.scope != "function": 438 raise AttributeError( 439 f"function not available in {self.scope}-scoped context" 440 ) 441 return self._pyfuncitem.obj
Test function object if the request has a per-function scope.
443 @property 444 def cls(self): 445 """Class (can be None) where the test function was collected.""" 446 if self.scope not in ("class", "function"): 447 raise AttributeError(f"cls not available in {self.scope}-scoped context") 448 clscol = self._pyfuncitem.getparent(_pytest.python.Class) 449 if clscol: 450 return clscol.obj
Class (can be None) where the test function was collected.
452 @property 453 def instance(self): 454 """Instance (can be None) on which test function was collected.""" 455 if self.scope != "function": 456 return None 457 return getattr(self._pyfuncitem, "instance", None)
Instance (can be None) on which test function was collected.
459 @property 460 def module(self): 461 """Python module object where the test function was collected.""" 462 if self.scope not in ("function", "class", "module"): 463 raise AttributeError(f"module not available in {self.scope}-scoped context") 464 mod = self._pyfuncitem.getparent(_pytest.python.Module) 465 assert mod is not None 466 return mod.obj
Python module object where the test function was collected.
468 @property 469 def path(self) -> Path: 470 """Path where the test function was collected.""" 471 if self.scope not in ("function", "class", "module", "package"): 472 raise AttributeError(f"path not available in {self.scope}-scoped context") 473 return self._pyfuncitem.path
Path where the test function was collected.
475 @property 476 def keywords(self) -> MutableMapping[str, Any]: 477 """Keywords/markers dictionary for the underlying node.""" 478 node: nodes.Node = self.node 479 return node.keywords
Keywords/markers dictionary for the underlying node.
481 @property 482 def session(self) -> Session: 483 """Pytest session object.""" 484 return self._pyfuncitem.session
Pytest session object.
486 @abc.abstractmethod 487 def addfinalizer(self, finalizer: Callable[[], object]) -> None: 488 """Add finalizer/teardown function to be called without arguments after 489 the last test within the requesting test context finished execution.""" 490 raise NotImplementedError()
Add finalizer/teardown function to be called without arguments after the last test within the requesting test context finished execution.
492 def applymarker(self, marker: str | MarkDecorator) -> None: 493 """Apply a marker to a single test function invocation. 494 495 This method is useful if you don't want to have a keyword/marker 496 on all function invocations. 497 498 :param marker: 499 An object created by a call to ``pytest.mark.NAME(...)``. 500 """ 501 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(...)
.
503 def raiseerror(self, msg: str | None) -> NoReturn: 504 """Raise a FixtureLookupError exception. 505 506 :param msg: 507 An optional custom error message. 508 """ 509 raise FixtureLookupError(None, self, msg)
Raise a FixtureLookupError exception.
Parameters
- msg: An optional custom error message.
511 def getfixturevalue(self, argname: str) -> Any: 512 """Dynamically run a named fixture function. 513 514 Declaring fixtures via function argument is recommended where possible. 515 But if you can only decide whether to use another fixture at test 516 setup time, you may use this function to retrieve it inside a fixture 517 or test function body. 518 519 This method can be used during the test setup phase or the test run 520 phase, but during the test teardown phase a fixture's value may not 521 be available. 522 523 :param argname: 524 The fixture name. 525 :raises pytest.FixtureLookupError: 526 If the given fixture could not be found. 527 """ 528 # Note that in addition to the use case described in the docstring, 529 # getfixturevalue() is also called by pytest itself during item and fixture 530 # setup to evaluate the fixtures that are requested statically 531 # (using function parameters, autouse, etc). 532 533 fixturedef = self._get_active_fixturedef(argname) 534 assert fixturedef.cached_result is not None, ( 535 f'The fixture value for "{argname}" is not available. ' 536 "This can happen when the fixture has already been torn down." 537 ) 538 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.
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.
1495class Function(PyobjMixin, nodes.Item): 1496 """Item responsible for setting up and executing a Python test function. 1497 1498 :param name: 1499 The full function name, including any decorations like those 1500 added by parametrization (``my_func[my_param]``). 1501 :param parent: 1502 The parent Node. 1503 :param config: 1504 The pytest Config object. 1505 :param callspec: 1506 If given, this function has been parametrized and the callspec contains 1507 meta information about the parametrization. 1508 :param callobj: 1509 If given, the object which will be called when the Function is invoked, 1510 otherwise the callobj will be obtained from ``parent`` using ``originalname``. 1511 :param keywords: 1512 Keywords bound to the function object for "-k" matching. 1513 :param session: 1514 The pytest Session object. 1515 :param fixtureinfo: 1516 Fixture information already resolved at this fixture node.. 1517 :param originalname: 1518 The attribute name to use for accessing the underlying function object. 1519 Defaults to ``name``. Set this if name is different from the original name, 1520 for example when it contains decorations like those added by parametrization 1521 (``my_func[my_param]``). 1522 """ 1523 1524 # Disable since functions handle it themselves. 1525 _ALLOW_MARKERS = False 1526 1527 def __init__( 1528 self, 1529 name: str, 1530 parent, 1531 config: Config | None = None, 1532 callspec: CallSpec2 | None = None, 1533 callobj=NOTSET, 1534 keywords: Mapping[str, Any] | None = None, 1535 session: Session | None = None, 1536 fixtureinfo: FuncFixtureInfo | None = None, 1537 originalname: str | None = None, 1538 ) -> None: 1539 super().__init__(name, parent, config=config, session=session) 1540 1541 if callobj is not NOTSET: 1542 self._obj = callobj 1543 self._instance = getattr(callobj, "__self__", None) 1544 1545 #: Original function name, without any decorations (for example 1546 #: parametrization adds a ``"[...]"`` suffix to function names), used to access 1547 #: the underlying function object from ``parent`` (in case ``callobj`` is not given 1548 #: explicitly). 1549 #: 1550 #: .. versionadded:: 3.0 1551 self.originalname = originalname or name 1552 1553 # Note: when FunctionDefinition is introduced, we should change ``originalname`` 1554 # to a readonly property that returns FunctionDefinition.name. 1555 1556 self.own_markers.extend(get_unpacked_marks(self.obj)) 1557 if callspec: 1558 self.callspec = callspec 1559 self.own_markers.extend(callspec.marks) 1560 1561 # todo: this is a hell of a hack 1562 # https://github.com/pytest-dev/pytest/issues/4569 1563 # Note: the order of the updates is important here; indicates what 1564 # takes priority (ctor argument over function attributes over markers). 1565 # Take own_markers only; NodeKeywords handles parent traversal on its own. 1566 self.keywords.update((mark.name, mark) for mark in self.own_markers) 1567 self.keywords.update(self.obj.__dict__) 1568 if keywords: 1569 self.keywords.update(keywords) 1570 1571 if fixtureinfo is None: 1572 fm = self.session._fixturemanager 1573 fixtureinfo = fm.getfixtureinfo(self, self.obj, self.cls) 1574 self._fixtureinfo: FuncFixtureInfo = fixtureinfo 1575 self.fixturenames = fixtureinfo.names_closure 1576 self._initrequest() 1577 1578 # todo: determine sound type limitations 1579 @classmethod 1580 def from_parent(cls, parent, **kw) -> Self: 1581 """The public constructor.""" 1582 return super().from_parent(parent=parent, **kw) 1583 1584 def _initrequest(self) -> None: 1585 self.funcargs: dict[str, object] = {} 1586 self._request = fixtures.TopRequest(self, _ispytest=True) 1587 1588 @property 1589 def function(self): 1590 """Underlying python 'function' object.""" 1591 return getimfunc(self.obj) 1592 1593 @property 1594 def instance(self): 1595 try: 1596 return self._instance 1597 except AttributeError: 1598 if isinstance(self.parent, Class): 1599 # Each Function gets a fresh class instance. 1600 self._instance = self._getinstance() 1601 else: 1602 self._instance = None 1603 return self._instance 1604 1605 def _getinstance(self): 1606 if isinstance(self.parent, Class): 1607 # Each Function gets a fresh class instance. 1608 return self.parent.newinstance() 1609 else: 1610 return None 1611 1612 def _getobj(self): 1613 instance = self.instance 1614 if instance is not None: 1615 parent_obj = instance 1616 else: 1617 assert self.parent is not None 1618 parent_obj = self.parent.obj # type: ignore[attr-defined] 1619 return getattr(parent_obj, self.originalname) 1620 1621 @property 1622 def _pyfuncitem(self): 1623 """(compatonly) for code expecting pytest-2.2 style request objects.""" 1624 return self 1625 1626 def runtest(self) -> None: 1627 """Execute the underlying test function.""" 1628 self.ihook.pytest_pyfunc_call(pyfuncitem=self) 1629 1630 def setup(self) -> None: 1631 self._request._fillfixtures() 1632 1633 def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: 1634 if hasattr(self, "_obj") and not self.config.getoption("fulltrace", False): 1635 code = _pytest._code.Code.from_function(get_real_func(self.obj)) 1636 path, firstlineno = code.path, code.firstlineno 1637 traceback = excinfo.traceback 1638 ntraceback = traceback.cut(path=path, firstlineno=firstlineno) 1639 if ntraceback == traceback: 1640 ntraceback = ntraceback.cut(path=path) 1641 if ntraceback == traceback: 1642 ntraceback = ntraceback.filter(filter_traceback) 1643 if not ntraceback: 1644 ntraceback = traceback 1645 ntraceback = ntraceback.filter(excinfo) 1646 1647 # issue364: mark all but first and last frames to 1648 # only show a single-line message for each frame. 1649 if self.config.getoption("tbstyle", "auto") == "auto": 1650 if len(ntraceback) > 2: 1651 ntraceback = Traceback( 1652 ( 1653 ntraceback[0], 1654 *(t.with_repr_style("short") for t in ntraceback[1:-1]), 1655 ntraceback[-1], 1656 ) 1657 ) 1658 1659 return ntraceback 1660 return excinfo.traceback 1661 1662 # TODO: Type ignored -- breaks Liskov Substitution. 1663 def repr_failure( # type: ignore[override] 1664 self, 1665 excinfo: ExceptionInfo[BaseException], 1666 ) -> str | TerminalRepr: 1667 style = self.config.getoption("tbstyle", "auto") 1668 if style == "auto": 1669 style = "long" 1670 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]
).
1527 def __init__( 1528 self, 1529 name: str, 1530 parent, 1531 config: Config | None = None, 1532 callspec: CallSpec2 | None = None, 1533 callobj=NOTSET, 1534 keywords: Mapping[str, Any] | None = None, 1535 session: Session | None = None, 1536 fixtureinfo: FuncFixtureInfo | None = None, 1537 originalname: str | None = None, 1538 ) -> None: 1539 super().__init__(name, parent, config=config, session=session) 1540 1541 if callobj is not NOTSET: 1542 self._obj = callobj 1543 self._instance = getattr(callobj, "__self__", None) 1544 1545 #: Original function name, without any decorations (for example 1546 #: parametrization adds a ``"[...]"`` suffix to function names), used to access 1547 #: the underlying function object from ``parent`` (in case ``callobj`` is not given 1548 #: explicitly). 1549 #: 1550 #: .. versionadded:: 3.0 1551 self.originalname = originalname or name 1552 1553 # Note: when FunctionDefinition is introduced, we should change ``originalname`` 1554 # to a readonly property that returns FunctionDefinition.name. 1555 1556 self.own_markers.extend(get_unpacked_marks(self.obj)) 1557 if callspec: 1558 self.callspec = callspec 1559 self.own_markers.extend(callspec.marks) 1560 1561 # todo: this is a hell of a hack 1562 # https://github.com/pytest-dev/pytest/issues/4569 1563 # Note: the order of the updates is important here; indicates what 1564 # takes priority (ctor argument over function attributes over markers). 1565 # Take own_markers only; NodeKeywords handles parent traversal on its own. 1566 self.keywords.update((mark.name, mark) for mark in self.own_markers) 1567 self.keywords.update(self.obj.__dict__) 1568 if keywords: 1569 self.keywords.update(keywords) 1570 1571 if fixtureinfo is None: 1572 fm = self.session._fixturemanager 1573 fixtureinfo = fm.getfixtureinfo(self, self.obj, self.cls) 1574 self._fixtureinfo: FuncFixtureInfo = fixtureinfo 1575 self.fixturenames = fixtureinfo.names_closure 1576 self._initrequest()
1579 @classmethod 1580 def from_parent(cls, parent, **kw) -> Self: 1581 """The public constructor.""" 1582 return super().from_parent(parent=parent, **kw)
The public constructor.
1588 @property 1589 def function(self): 1590 """Underlying python 'function' object.""" 1591 return getimfunc(self.obj)
Underlying python 'function' object.
1593 @property 1594 def instance(self): 1595 try: 1596 return self._instance 1597 except AttributeError: 1598 if isinstance(self.parent, Class): 1599 # Each Function gets a fresh class instance. 1600 self._instance = self._getinstance() 1601 else: 1602 self._instance = None 1603 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.
1626 def runtest(self) -> None: 1627 """Execute the underlying test function.""" 1628 self.ihook.pytest_pyfunc_call(pyfuncitem=self)
Execute the underlying test function.
1663 def repr_failure( # type: ignore[override] 1664 self, 1665 excinfo: ExceptionInfo[BaseException], 1666 ) -> str | TerminalRepr: 1667 style = self.config.getoption("tbstyle", "auto") 1668 if style == "auto": 1669 style = "long" 1670 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.
Inherited Members
- _pytest.python.PyobjMixin
- module
- cls
- obj
- getmodpath
- reportinfo
- _pytest.nodes.Node
- fspath
- name
- parent
- path
- keywords
- own_markers
- extra_keyword_matches
- stash
- ihook
- warn
- nodeid
- teardown
- iter_parents
- listchain
- add_marker
- iter_markers
- iter_markers_with_node
- get_closest_marker
- listextrakeywords
- listnames
- addfinalizer
- getparent
- config
- session
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 )
210def importorskip( 211 modname: str, 212 minversion: str | None = None, 213 reason: str | None = None, 214 *, 215 exc_type: type[ImportError] | None = None, 216) -> Any: 217 """Import and return the requested module ``modname``, or skip the 218 current test if the module cannot be imported. 219 220 :param modname: 221 The name of the module to import. 222 :param minversion: 223 If given, the imported module's ``__version__`` attribute must be at 224 least this minimal version, otherwise the test is still skipped. 225 :param reason: 226 If given, this reason is shown as the message when the module cannot 227 be imported. 228 :param exc_type: 229 The exception that should be captured in order to skip modules. 230 Must be :py:class:`ImportError` or a subclass. 231 232 If the module can be imported but raises :class:`ImportError`, pytest will 233 issue a warning to the user, as often users expect the module not to be 234 found (which would raise :class:`ModuleNotFoundError` instead). 235 236 This warning can be suppressed by passing ``exc_type=ImportError`` explicitly. 237 238 See :ref:`import-or-skip-import-error` for details. 239 240 241 :returns: 242 The imported module. This should be assigned to its canonical name. 243 244 :raises pytest.skip.Exception: 245 If the module cannot be imported. 246 247 Example:: 248 249 docutils = pytest.importorskip("docutils") 250 251 .. versionadded:: 8.2 252 253 The ``exc_type`` parameter. 254 """ 255 import warnings 256 257 __tracebackhide__ = True 258 compile(modname, "", "eval") # to catch syntaxerrors 259 260 # Until pytest 9.1, we will warn the user if we catch ImportError (instead of ModuleNotFoundError), 261 # as this might be hiding an installation/environment problem, which is not usually what is intended 262 # when using importorskip() (#11523). 263 # In 9.1, to keep the function signature compatible, we just change the code below to: 264 # 1. Use `exc_type = ModuleNotFoundError` if `exc_type` is not given. 265 # 2. Remove `warn_on_import` and the warning handling. 266 if exc_type is None: 267 exc_type = ImportError 268 warn_on_import_error = True 269 else: 270 warn_on_import_error = False 271 272 skipped: Skipped | None = None 273 warning: Warning | None = None 274 275 with warnings.catch_warnings(): 276 # Make sure to ignore ImportWarnings that might happen because 277 # of existing directories with the same name we're trying to 278 # import but without a __init__.py file. 279 warnings.simplefilter("ignore") 280 281 try: 282 __import__(modname) 283 except exc_type as exc: 284 # Do not raise or issue warnings inside the catch_warnings() block. 285 if reason is None: 286 reason = f"could not import {modname!r}: {exc}" 287 skipped = Skipped(reason, allow_module_level=True) 288 289 if warn_on_import_error and not isinstance(exc, ModuleNotFoundError): 290 lines = [ 291 "", 292 f"Module '{modname}' was found, but when imported by pytest it raised:", 293 f" {exc!r}", 294 "In pytest 9.1 this warning will become an error by default.", 295 "You can fix the underlying problem, or alternatively overwrite this behavior and silence this " 296 "warning by passing exc_type=ImportError explicitly.", 297 "See https://docs.pytest.org/en/stable/deprecations.html#pytest-importorskip-default-behavior-regarding-importerror", 298 ] 299 warning = PytestDeprecationWarning("\n".join(lines)) 300 301 if warning: 302 warnings.warn(warning, stacklevel=2) 303 if skipped: 304 raise skipped 305 306 mod = sys.modules[modname] 307 if minversion is None: 308 return mod 309 verattr = getattr(mod, "__version__", None) 310 if minversion is not None: 311 # Imported lazily to improve start-up time. 312 from packaging.version import Version 313 314 if verattr is None or Version(verattr) < Version(minversion): 315 raise Skipped( 316 f"module {modname!r} has __version__ {verattr!r}, required is: {minversion!r}", 317 allow_module_level=True, 318 ) 319 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.
650class Item(Node, abc.ABC): 651 """Base class of all test invocation items. 652 653 Note that for a single function there might be multiple test invocation items. 654 """ 655 656 nextitem = None 657 658 def __init__( 659 self, 660 name, 661 parent=None, 662 config: Config | None = None, 663 session: Session | None = None, 664 nodeid: str | None = None, 665 **kw, 666 ) -> None: 667 # The first two arguments are intentionally passed positionally, 668 # to keep plugins who define a node type which inherits from 669 # (pytest.Item, pytest.File) working (see issue #8435). 670 # They can be made kwargs when the deprecation above is done. 671 super().__init__( 672 name, 673 parent, 674 config=config, 675 session=session, 676 nodeid=nodeid, 677 **kw, 678 ) 679 self._report_sections: list[tuple[str, str, str]] = [] 680 681 #: A list of tuples (name, value) that holds user defined properties 682 #: for this test. 683 self.user_properties: list[tuple[str, object]] = [] 684 685 self._check_item_and_collector_diamond_inheritance() 686 687 def _check_item_and_collector_diamond_inheritance(self) -> None: 688 """ 689 Check if the current type inherits from both File and Collector 690 at the same time, emitting a warning accordingly (#8447). 691 """ 692 cls = type(self) 693 694 # We inject an attribute in the type to avoid issuing this warning 695 # for the same class more than once, which is not helpful. 696 # It is a hack, but was deemed acceptable in order to avoid 697 # flooding the user in the common case. 698 attr_name = "_pytest_diamond_inheritance_warning_shown" 699 if getattr(cls, attr_name, False): 700 return 701 setattr(cls, attr_name, True) 702 703 problems = ", ".join( 704 base.__name__ for base in cls.__bases__ if issubclass(base, Collector) 705 ) 706 if problems: 707 warnings.warn( 708 f"{cls.__name__} is an Item subclass and should not be a collector, " 709 f"however its bases {problems} are collectors.\n" 710 "Please split the Collectors and the Item into separate node types.\n" 711 "Pytest Doc example: https://docs.pytest.org/en/latest/example/nonpython.html\n" 712 "example pull request on a plugin: https://github.com/asmeurer/pytest-flakes/pull/40/", 713 PytestWarning, 714 ) 715 716 @abc.abstractmethod 717 def runtest(self) -> None: 718 """Run the test case for this item. 719 720 Must be implemented by subclasses. 721 722 .. seealso:: :ref:`non-python tests` 723 """ 724 raise NotImplementedError("runtest must be implemented by Item subclass") 725 726 def add_report_section(self, when: str, key: str, content: str) -> None: 727 """Add a new report section, similar to what's done internally to add 728 stdout and stderr captured output:: 729 730 item.add_report_section("call", "stdout", "report section contents") 731 732 :param str when: 733 One of the possible capture states, ``"setup"``, ``"call"``, ``"teardown"``. 734 :param str key: 735 Name of the section, can be customized at will. Pytest uses ``"stdout"`` and 736 ``"stderr"`` internally. 737 :param str content: 738 The full contents as a string. 739 """ 740 if content: 741 self._report_sections.append((when, key, content)) 742 743 def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]: 744 """Get location information for this item for test reports. 745 746 Returns a tuple with three elements: 747 748 - The path of the test (default ``self.path``) 749 - The 0-based line number of the test (default ``None``) 750 - A name of the test to be shown (default ``""``) 751 752 .. seealso:: :ref:`non-python tests` 753 """ 754 return self.path, None, "" 755 756 @cached_property 757 def location(self) -> tuple[str, int | None, str]: 758 """ 759 Returns a tuple of ``(relfspath, lineno, testname)`` for this item 760 where ``relfspath`` is file path relative to ``config.rootpath`` 761 and lineno is a 0-based line number. 762 """ 763 location = self.reportinfo() 764 path = absolutepath(location[0]) 765 relfspath = self.session._node_location_to_relpath(path) 766 assert type(location[2]) is str 767 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.
716 @abc.abstractmethod 717 def runtest(self) -> None: 718 """Run the test case for this item. 719 720 Must be implemented by subclasses. 721 722 .. seealso:: :ref:`non-python tests` 723 """ 724 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
.
726 def add_report_section(self, when: str, key: str, content: str) -> None: 727 """Add a new report section, similar to what's done internally to add 728 stdout and stderr captured output:: 729 730 item.add_report_section("call", "stdout", "report section contents") 731 732 :param str when: 733 One of the possible capture states, ``"setup"``, ``"call"``, ``"teardown"``. 734 :param str key: 735 Name of the section, can be customized at will. Pytest uses ``"stdout"`` and 736 ``"stderr"`` internally. 737 :param str content: 738 The full contents as a string. 739 """ 740 if content: 741 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.
743 def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]: 744 """Get location information for this item for test reports. 745 746 Returns a tuple with three elements: 747 748 - The path of the test (default ``self.path``) 749 - The 0-based line number of the test (default ``None``) 750 - A name of the test to be shown (default ``""``) 751 752 .. seealso:: :ref:`non-python tests` 753 """ 754 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
.
756 @cached_property 757 def location(self) -> tuple[str, int | None, str]: 758 """ 759 Returns a tuple of ``(relfspath, lineno, testname)`` for this item 760 where ``relfspath`` is file path relative to ``config.rootpath`` 761 and lineno is a 0-based line number. 762 """ 763 location = self.reportinfo() 764 path = absolutepath(location[0]) 765 relfspath = self.session._node_location_to_relpath(path) 766 assert type(location[2]) is str 767 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.
Inherited Members
- _pytest.nodes.Node
- fspath
- name
- parent
- path
- keywords
- own_markers
- extra_keyword_matches
- stash
- from_parent
- ihook
- warn
- nodeid
- setup
- teardown
- iter_parents
- listchain
- add_marker
- iter_markers
- iter_markers_with_node
- get_closest_marker
- listextrakeywords
- listnames
- addfinalizer
- getparent
- repr_failure
- config
- session
1544class LineMatcher: 1545 """Flexible matching of text. 1546 1547 This is a convenience class to test large texts like the output of 1548 commands. 1549 1550 The constructor takes a list of lines without their trailing newlines, i.e. 1551 ``text.splitlines()``. 1552 """ 1553 1554 def __init__(self, lines: list[str]) -> None: 1555 self.lines = lines 1556 self._log_output: list[str] = [] 1557 1558 def __str__(self) -> str: 1559 """Return the entire original text. 1560 1561 .. versionadded:: 6.2 1562 You can use :meth:`str` in older versions. 1563 """ 1564 return "\n".join(self.lines) 1565 1566 def _getlines(self, lines2: str | Sequence[str] | Source) -> Sequence[str]: 1567 if isinstance(lines2, str): 1568 lines2 = Source(lines2) 1569 if isinstance(lines2, Source): 1570 lines2 = lines2.strip().lines 1571 return lines2 1572 1573 def fnmatch_lines_random(self, lines2: Sequence[str]) -> None: 1574 """Check lines exist in the output in any order (using :func:`python:fnmatch.fnmatch`).""" 1575 __tracebackhide__ = True 1576 self._match_lines_random(lines2, fnmatch) 1577 1578 def re_match_lines_random(self, lines2: Sequence[str]) -> None: 1579 """Check lines exist in the output in any order (using :func:`python:re.match`).""" 1580 __tracebackhide__ = True 1581 self._match_lines_random(lines2, lambda name, pat: bool(re.match(pat, name))) 1582 1583 def _match_lines_random( 1584 self, lines2: Sequence[str], match_func: Callable[[str, str], bool] 1585 ) -> None: 1586 __tracebackhide__ = True 1587 lines2 = self._getlines(lines2) 1588 for line in lines2: 1589 for x in self.lines: 1590 if line == x or match_func(x, line): 1591 self._log("matched: ", repr(line)) 1592 break 1593 else: 1594 msg = f"line {line!r} not found in output" 1595 self._log(msg) 1596 self._fail(msg) 1597 1598 def get_lines_after(self, fnline: str) -> Sequence[str]: 1599 """Return all lines following the given line in the text. 1600 1601 The given line can contain glob wildcards. 1602 """ 1603 for i, line in enumerate(self.lines): 1604 if fnline == line or fnmatch(line, fnline): 1605 return self.lines[i + 1 :] 1606 raise ValueError(f"line {fnline!r} not found in output") 1607 1608 def _log(self, *args) -> None: 1609 self._log_output.append(" ".join(str(x) for x in args)) 1610 1611 @property 1612 def _log_text(self) -> str: 1613 return "\n".join(self._log_output) 1614 1615 def fnmatch_lines( 1616 self, lines2: Sequence[str], *, consecutive: bool = False 1617 ) -> None: 1618 """Check lines exist in the output (using :func:`python:fnmatch.fnmatch`). 1619 1620 The argument is a list of lines which have to match and can use glob 1621 wildcards. If they do not match a pytest.fail() is called. The 1622 matches and non-matches are also shown as part of the error message. 1623 1624 :param lines2: String patterns to match. 1625 :param consecutive: Match lines consecutively? 1626 """ 1627 __tracebackhide__ = True 1628 self._match_lines(lines2, fnmatch, "fnmatch", consecutive=consecutive) 1629 1630 def re_match_lines( 1631 self, lines2: Sequence[str], *, consecutive: bool = False 1632 ) -> None: 1633 """Check lines exist in the output (using :func:`python:re.match`). 1634 1635 The argument is a list of lines which have to match using ``re.match``. 1636 If they do not match a pytest.fail() is called. 1637 1638 The matches and non-matches are also shown as part of the error message. 1639 1640 :param lines2: string patterns to match. 1641 :param consecutive: match lines consecutively? 1642 """ 1643 __tracebackhide__ = True 1644 self._match_lines( 1645 lines2, 1646 lambda name, pat: bool(re.match(pat, name)), 1647 "re.match", 1648 consecutive=consecutive, 1649 ) 1650 1651 def _match_lines( 1652 self, 1653 lines2: Sequence[str], 1654 match_func: Callable[[str, str], bool], 1655 match_nickname: str, 1656 *, 1657 consecutive: bool = False, 1658 ) -> None: 1659 """Underlying implementation of ``fnmatch_lines`` and ``re_match_lines``. 1660 1661 :param Sequence[str] lines2: 1662 List of string patterns to match. The actual format depends on 1663 ``match_func``. 1664 :param match_func: 1665 A callable ``match_func(line, pattern)`` where line is the 1666 captured line from stdout/stderr and pattern is the matching 1667 pattern. 1668 :param str match_nickname: 1669 The nickname for the match function that will be logged to stdout 1670 when a match occurs. 1671 :param consecutive: 1672 Match lines consecutively? 1673 """ 1674 if not isinstance(lines2, collections.abc.Sequence): 1675 raise TypeError(f"invalid type for lines2: {type(lines2).__name__}") 1676 lines2 = self._getlines(lines2) 1677 lines1 = self.lines[:] 1678 extralines = [] 1679 __tracebackhide__ = True 1680 wnick = len(match_nickname) + 1 1681 started = False 1682 for line in lines2: 1683 nomatchprinted = False 1684 while lines1: 1685 nextline = lines1.pop(0) 1686 if line == nextline: 1687 self._log("exact match:", repr(line)) 1688 started = True 1689 break 1690 elif match_func(nextline, line): 1691 self._log(f"{match_nickname}:", repr(line)) 1692 self._log( 1693 "{:>{width}}".format("with:", width=wnick), repr(nextline) 1694 ) 1695 started = True 1696 break 1697 else: 1698 if consecutive and started: 1699 msg = f"no consecutive match: {line!r}" 1700 self._log(msg) 1701 self._log( 1702 "{:>{width}}".format("with:", width=wnick), repr(nextline) 1703 ) 1704 self._fail(msg) 1705 if not nomatchprinted: 1706 self._log( 1707 "{:>{width}}".format("nomatch:", width=wnick), repr(line) 1708 ) 1709 nomatchprinted = True 1710 self._log("{:>{width}}".format("and:", width=wnick), repr(nextline)) 1711 extralines.append(nextline) 1712 else: 1713 msg = f"remains unmatched: {line!r}" 1714 self._log(msg) 1715 self._fail(msg) 1716 self._log_output = [] 1717 1718 def no_fnmatch_line(self, pat: str) -> None: 1719 """Ensure captured lines do not match the given pattern, using ``fnmatch.fnmatch``. 1720 1721 :param str pat: The pattern to match lines. 1722 """ 1723 __tracebackhide__ = True 1724 self._no_match_line(pat, fnmatch, "fnmatch") 1725 1726 def no_re_match_line(self, pat: str) -> None: 1727 """Ensure captured lines do not match the given pattern, using ``re.match``. 1728 1729 :param str pat: The regular expression to match lines. 1730 """ 1731 __tracebackhide__ = True 1732 self._no_match_line( 1733 pat, lambda name, pat: bool(re.match(pat, name)), "re.match" 1734 ) 1735 1736 def _no_match_line( 1737 self, pat: str, match_func: Callable[[str, str], bool], match_nickname: str 1738 ) -> None: 1739 """Ensure captured lines does not have a the given pattern, using ``fnmatch.fnmatch``. 1740 1741 :param str pat: The pattern to match lines. 1742 """ 1743 __tracebackhide__ = True 1744 nomatch_printed = False 1745 wnick = len(match_nickname) + 1 1746 for line in self.lines: 1747 if match_func(line, pat): 1748 msg = f"{match_nickname}: {pat!r}" 1749 self._log(msg) 1750 self._log("{:>{width}}".format("with:", width=wnick), repr(line)) 1751 self._fail(msg) 1752 else: 1753 if not nomatch_printed: 1754 self._log("{:>{width}}".format("nomatch:", width=wnick), repr(pat)) 1755 nomatch_printed = True 1756 self._log("{:>{width}}".format("and:", width=wnick), repr(line)) 1757 self._log_output = [] 1758 1759 def _fail(self, msg: str) -> None: 1760 __tracebackhide__ = True 1761 log_text = self._log_text 1762 self._log_output = [] 1763 fail(log_text) 1764 1765 def str(self) -> str: 1766 """Return the entire original text.""" 1767 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()
.
1573 def fnmatch_lines_random(self, lines2: Sequence[str]) -> None: 1574 """Check lines exist in the output in any order (using :func:`python:fnmatch.fnmatch`).""" 1575 __tracebackhide__ = True 1576 self._match_lines_random(lines2, fnmatch)
Check lines exist in the output in any order (using python:fnmatch.fnmatch()
).
1578 def re_match_lines_random(self, lines2: Sequence[str]) -> None: 1579 """Check lines exist in the output in any order (using :func:`python:re.match`).""" 1580 __tracebackhide__ = True 1581 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()
).
1598 def get_lines_after(self, fnline: str) -> Sequence[str]: 1599 """Return all lines following the given line in the text. 1600 1601 The given line can contain glob wildcards. 1602 """ 1603 for i, line in enumerate(self.lines): 1604 if fnline == line or fnmatch(line, fnline): 1605 return self.lines[i + 1 :] 1606 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.
1615 def fnmatch_lines( 1616 self, lines2: Sequence[str], *, consecutive: bool = False 1617 ) -> None: 1618 """Check lines exist in the output (using :func:`python:fnmatch.fnmatch`). 1619 1620 The argument is a list of lines which have to match and can use glob 1621 wildcards. If they do not match a pytest.fail() is called. The 1622 matches and non-matches are also shown as part of the error message. 1623 1624 :param lines2: String patterns to match. 1625 :param consecutive: Match lines consecutively? 1626 """ 1627 __tracebackhide__ = True 1628 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?
1630 def re_match_lines( 1631 self, lines2: Sequence[str], *, consecutive: bool = False 1632 ) -> None: 1633 """Check lines exist in the output (using :func:`python:re.match`). 1634 1635 The argument is a list of lines which have to match using ``re.match``. 1636 If they do not match a pytest.fail() is called. 1637 1638 The matches and non-matches are also shown as part of the error message. 1639 1640 :param lines2: string patterns to match. 1641 :param consecutive: match lines consecutively? 1642 """ 1643 __tracebackhide__ = True 1644 self._match_lines( 1645 lines2, 1646 lambda name, pat: bool(re.match(pat, name)), 1647 "re.match", 1648 consecutive=consecutive, 1649 )
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?
1718 def no_fnmatch_line(self, pat: str) -> None: 1719 """Ensure captured lines do not match the given pattern, using ``fnmatch.fnmatch``. 1720 1721 :param str pat: The pattern to match lines. 1722 """ 1723 __tracebackhide__ = True 1724 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.
1726 def no_re_match_line(self, pat: str) -> None: 1727 """Ensure captured lines do not match the given pattern, using ``re.match``. 1728 1729 :param str pat: The regular expression to match lines. 1730 """ 1731 __tracebackhide__ = True 1732 self._no_match_line( 1733 pat, lambda name, pat: bool(re.match(pat, name)), "re.match" 1734 )
Ensure captured lines do not match the given pattern, using re.match
.
Parameters
- str pat: The regular expression to match lines.
406@final 407class LogCaptureFixture: 408 """Provides access and control of log capturing.""" 409 410 def __init__(self, item: nodes.Node, *, _ispytest: bool = False) -> None: 411 check_ispytest(_ispytest) 412 self._item = item 413 self._initial_handler_level: int | None = None 414 # Dict of log name -> log level. 415 self._initial_logger_levels: dict[str | None, int] = {} 416 self._initial_disabled_logging_level: int | None = None 417 418 def _finalize(self) -> None: 419 """Finalize the fixture. 420 421 This restores the log levels and the disabled logging levels changed by :meth:`set_level`. 422 """ 423 # Restore log levels. 424 if self._initial_handler_level is not None: 425 self.handler.setLevel(self._initial_handler_level) 426 for logger_name, level in self._initial_logger_levels.items(): 427 logger = logging.getLogger(logger_name) 428 logger.setLevel(level) 429 # Disable logging at the original disabled logging level. 430 if self._initial_disabled_logging_level is not None: 431 logging.disable(self._initial_disabled_logging_level) 432 self._initial_disabled_logging_level = None 433 434 @property 435 def handler(self) -> LogCaptureHandler: 436 """Get the logging handler used by the fixture.""" 437 return self._item.stash[caplog_handler_key] 438 439 def get_records( 440 self, when: Literal["setup", "call", "teardown"] 441 ) -> list[logging.LogRecord]: 442 """Get the logging records for one of the possible test phases. 443 444 :param when: 445 Which test phase to obtain the records from. 446 Valid values are: "setup", "call" and "teardown". 447 448 :returns: The list of captured records at the given stage. 449 450 .. versionadded:: 3.4 451 """ 452 return self._item.stash[caplog_records_key].get(when, []) 453 454 @property 455 def text(self) -> str: 456 """The formatted log text.""" 457 return _remove_ansi_escape_sequences(self.handler.stream.getvalue()) 458 459 @property 460 def records(self) -> list[logging.LogRecord]: 461 """The list of log records.""" 462 return self.handler.records 463 464 @property 465 def record_tuples(self) -> list[tuple[str, int, str]]: 466 """A list of a stripped down version of log records intended 467 for use in assertion comparison. 468 469 The format of the tuple is: 470 471 (logger_name, log_level, message) 472 """ 473 return [(r.name, r.levelno, r.getMessage()) for r in self.records] 474 475 @property 476 def messages(self) -> list[str]: 477 """A list of format-interpolated log messages. 478 479 Unlike 'records', which contains the format string and parameters for 480 interpolation, log messages in this list are all interpolated. 481 482 Unlike 'text', which contains the output from the handler, log 483 messages in this list are unadorned with levels, timestamps, etc, 484 making exact comparisons more reliable. 485 486 Note that traceback or stack info (from :func:`logging.exception` or 487 the `exc_info` or `stack_info` arguments to the logging functions) is 488 not included, as this is added by the formatter in the handler. 489 490 .. versionadded:: 3.7 491 """ 492 return [r.getMessage() for r in self.records] 493 494 def clear(self) -> None: 495 """Reset the list of log records and the captured log text.""" 496 self.handler.clear() 497 498 def _force_enable_logging( 499 self, level: int | str, logger_obj: logging.Logger 500 ) -> int: 501 """Enable the desired logging level if the global level was disabled via ``logging.disabled``. 502 503 Only enables logging levels greater than or equal to the requested ``level``. 504 505 Does nothing if the desired ``level`` wasn't disabled. 506 507 :param level: 508 The logger level caplog should capture. 509 All logging is enabled if a non-standard logging level string is supplied. 510 Valid level strings are in :data:`logging._nameToLevel`. 511 :param logger_obj: The logger object to check. 512 513 :return: The original disabled logging level. 514 """ 515 original_disable_level: int = logger_obj.manager.disable 516 517 if isinstance(level, str): 518 # Try to translate the level string to an int for `logging.disable()` 519 level = logging.getLevelName(level) 520 521 if not isinstance(level, int): 522 # The level provided was not valid, so just un-disable all logging. 523 logging.disable(logging.NOTSET) 524 elif not logger_obj.isEnabledFor(level): 525 # Each level is `10` away from other levels. 526 # https://docs.python.org/3/library/logging.html#logging-levels 527 disable_level = max(level - 10, logging.NOTSET) 528 logging.disable(disable_level) 529 530 return original_disable_level 531 532 def set_level(self, level: int | str, logger: str | None = None) -> None: 533 """Set the threshold level of a logger for the duration of a test. 534 535 Logging messages which are less severe than this level will not be captured. 536 537 .. versionchanged:: 3.4 538 The levels of the loggers changed by this function will be 539 restored to their initial values at the end of the test. 540 541 Will enable the requested logging level if it was disabled via :func:`logging.disable`. 542 543 :param level: The level. 544 :param logger: The logger to update. If not given, the root logger. 545 """ 546 logger_obj = logging.getLogger(logger) 547 # Save the original log-level to restore it during teardown. 548 self._initial_logger_levels.setdefault(logger, logger_obj.level) 549 logger_obj.setLevel(level) 550 if self._initial_handler_level is None: 551 self._initial_handler_level = self.handler.level 552 self.handler.setLevel(level) 553 initial_disabled_logging_level = self._force_enable_logging(level, logger_obj) 554 if self._initial_disabled_logging_level is None: 555 self._initial_disabled_logging_level = initial_disabled_logging_level 556 557 @contextmanager 558 def at_level(self, level: int | str, logger: str | None = None) -> Generator[None]: 559 """Context manager that sets the level for capturing of logs. After 560 the end of the 'with' statement the level is restored to its original 561 value. 562 563 Will enable the requested logging level if it was disabled via :func:`logging.disable`. 564 565 :param level: The level. 566 :param logger: The logger to update. If not given, the root logger. 567 """ 568 logger_obj = logging.getLogger(logger) 569 orig_level = logger_obj.level 570 logger_obj.setLevel(level) 571 handler_orig_level = self.handler.level 572 self.handler.setLevel(level) 573 original_disable_level = self._force_enable_logging(level, logger_obj) 574 try: 575 yield 576 finally: 577 logger_obj.setLevel(orig_level) 578 self.handler.setLevel(handler_orig_level) 579 logging.disable(original_disable_level) 580 581 @contextmanager 582 def filtering(self, filter_: logging.Filter) -> Generator[None]: 583 """Context manager that temporarily adds the given filter to the caplog's 584 :meth:`handler` for the 'with' statement block, and removes that filter at the 585 end of the block. 586 587 :param filter_: A custom :class:`logging.Filter` object. 588 589 .. versionadded:: 7.5 590 """ 591 self.handler.addFilter(filter_) 592 try: 593 yield 594 finally: 595 self.handler.removeFilter(filter_)
Provides access and control of log capturing.
410 def __init__(self, item: nodes.Node, *, _ispytest: bool = False) -> None: 411 check_ispytest(_ispytest) 412 self._item = item 413 self._initial_handler_level: int | None = None 414 # Dict of log name -> log level. 415 self._initial_logger_levels: dict[str | None, int] = {} 416 self._initial_disabled_logging_level: int | None = None
434 @property 435 def handler(self) -> LogCaptureHandler: 436 """Get the logging handler used by the fixture.""" 437 return self._item.stash[caplog_handler_key]
Get the logging handler used by the fixture.
439 def get_records( 440 self, when: Literal["setup", "call", "teardown"] 441 ) -> list[logging.LogRecord]: 442 """Get the logging records for one of the possible test phases. 443 444 :param when: 445 Which test phase to obtain the records from. 446 Valid values are: "setup", "call" and "teardown". 447 448 :returns: The list of captured records at the given stage. 449 450 .. versionadded:: 3.4 451 """ 452 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.
454 @property 455 def text(self) -> str: 456 """The formatted log text.""" 457 return _remove_ansi_escape_sequences(self.handler.stream.getvalue())
The formatted log text.
459 @property 460 def records(self) -> list[logging.LogRecord]: 461 """The list of log records.""" 462 return self.handler.records
The list of log records.
464 @property 465 def record_tuples(self) -> list[tuple[str, int, str]]: 466 """A list of a stripped down version of log records intended 467 for use in assertion comparison. 468 469 The format of the tuple is: 470 471 (logger_name, log_level, message) 472 """ 473 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)
475 @property 476 def messages(self) -> list[str]: 477 """A list of format-interpolated log messages. 478 479 Unlike 'records', which contains the format string and parameters for 480 interpolation, log messages in this list are all interpolated. 481 482 Unlike 'text', which contains the output from the handler, log 483 messages in this list are unadorned with levels, timestamps, etc, 484 making exact comparisons more reliable. 485 486 Note that traceback or stack info (from :func:`logging.exception` or 487 the `exc_info` or `stack_info` arguments to the logging functions) is 488 not included, as this is added by the formatter in the handler. 489 490 .. versionadded:: 3.7 491 """ 492 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.
494 def clear(self) -> None: 495 """Reset the list of log records and the captured log text.""" 496 self.handler.clear()
Reset the list of log records and the captured log text.
532 def set_level(self, level: int | str, logger: str | None = None) -> None: 533 """Set the threshold level of a logger for the duration of a test. 534 535 Logging messages which are less severe than this level will not be captured. 536 537 .. versionchanged:: 3.4 538 The levels of the loggers changed by this function will be 539 restored to their initial values at the end of the test. 540 541 Will enable the requested logging level if it was disabled via :func:`logging.disable`. 542 543 :param level: The level. 544 :param logger: The logger to update. If not given, the root logger. 545 """ 546 logger_obj = logging.getLogger(logger) 547 # Save the original log-level to restore it during teardown. 548 self._initial_logger_levels.setdefault(logger, logger_obj.level) 549 logger_obj.setLevel(level) 550 if self._initial_handler_level is None: 551 self._initial_handler_level = self.handler.level 552 self.handler.setLevel(level) 553 initial_disabled_logging_level = self._force_enable_logging(level, logger_obj) 554 if self._initial_disabled_logging_level is None: 555 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.
557 @contextmanager 558 def at_level(self, level: int | str, logger: str | None = None) -> Generator[None]: 559 """Context manager that sets the level for capturing of logs. After 560 the end of the 'with' statement the level is restored to its original 561 value. 562 563 Will enable the requested logging level if it was disabled via :func:`logging.disable`. 564 565 :param level: The level. 566 :param logger: The logger to update. If not given, the root logger. 567 """ 568 logger_obj = logging.getLogger(logger) 569 orig_level = logger_obj.level 570 logger_obj.setLevel(level) 571 handler_orig_level = self.handler.level 572 self.handler.setLevel(level) 573 original_disable_level = self._force_enable_logging(level, logger_obj) 574 try: 575 yield 576 finally: 577 logger_obj.setLevel(orig_level) 578 self.handler.setLevel(handler_orig_level) 579 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.
581 @contextmanager 582 def filtering(self, filter_: logging.Filter) -> Generator[None]: 583 """Context manager that temporarily adds the given filter to the caplog's 584 :meth:`handler` for the 'with' statement block, and removes that filter at the 585 end of the block. 586 587 :param filter_: A custom :class:`logging.Filter` object. 588 589 .. versionadded:: 7.5 590 """ 591 self.handler.addFilter(filter_) 592 try: 593 yield 594 finally: 595 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.
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.
193@final 194@dataclasses.dataclass(frozen=True) 195class Mark: 196 """A pytest mark.""" 197 198 #: Name of the mark. 199 name: str 200 #: Positional arguments of the mark decorator. 201 args: tuple[Any, ...] 202 #: Keyword arguments of the mark decorator. 203 kwargs: Mapping[str, Any] 204 205 #: Source Mark for ids with parametrize Marks. 206 _param_ids_from: Mark | None = dataclasses.field(default=None, repr=False) 207 #: Resolved/generated ids with parametrize Marks. 208 _param_ids_generated: Sequence[str] | None = dataclasses.field( 209 default=None, repr=False 210 ) 211 212 def __init__( 213 self, 214 name: str, 215 args: tuple[Any, ...], 216 kwargs: Mapping[str, Any], 217 param_ids_from: Mark | None = None, 218 param_ids_generated: Sequence[str] | None = None, 219 *, 220 _ispytest: bool = False, 221 ) -> None: 222 """:meta private:""" 223 check_ispytest(_ispytest) 224 # Weirdness to bypass frozen=True. 225 object.__setattr__(self, "name", name) 226 object.__setattr__(self, "args", args) 227 object.__setattr__(self, "kwargs", kwargs) 228 object.__setattr__(self, "_param_ids_from", param_ids_from) 229 object.__setattr__(self, "_param_ids_generated", param_ids_generated) 230 231 def _has_param_ids(self) -> bool: 232 return "ids" in self.kwargs or len(self.args) >= 4 233 234 def combined_with(self, other: Mark) -> Mark: 235 """Return a new Mark which is a combination of this 236 Mark and another Mark. 237 238 Combines by appending args and merging kwargs. 239 240 :param Mark other: The mark to combine with. 241 :rtype: Mark 242 """ 243 assert self.name == other.name 244 245 # Remember source of ids with parametrize Marks. 246 param_ids_from: Mark | None = None 247 if self.name == "parametrize": 248 if other._has_param_ids(): 249 param_ids_from = other 250 elif self._has_param_ids(): 251 param_ids_from = self 252 253 return Mark( 254 self.name, 255 self.args + other.args, 256 dict(self.kwargs, **other.kwargs), 257 param_ids_from=param_ids_from, 258 _ispytest=True, 259 )
A pytest mark.
212 def __init__( 213 self, 214 name: str, 215 args: tuple[Any, ...], 216 kwargs: Mapping[str, Any], 217 param_ids_from: Mark | None = None, 218 param_ids_generated: Sequence[str] | None = None, 219 *, 220 _ispytest: bool = False, 221 ) -> None: 222 """:meta private:""" 223 check_ispytest(_ispytest) 224 # Weirdness to bypass frozen=True. 225 object.__setattr__(self, "name", name) 226 object.__setattr__(self, "args", args) 227 object.__setattr__(self, "kwargs", kwargs) 228 object.__setattr__(self, "_param_ids_from", param_ids_from) 229 object.__setattr__(self, "_param_ids_generated", param_ids_generated)
:meta private:
234 def combined_with(self, other: Mark) -> Mark: 235 """Return a new Mark which is a combination of this 236 Mark and another Mark. 237 238 Combines by appending args and merging kwargs. 239 240 :param Mark other: The mark to combine with. 241 :rtype: Mark 242 """ 243 assert self.name == other.name 244 245 # Remember source of ids with parametrize Marks. 246 param_ids_from: Mark | None = None 247 if self.name == "parametrize": 248 if other._has_param_ids(): 249 param_ids_from = other 250 elif self._has_param_ids(): 251 param_ids_from = self 252 253 return Mark( 254 self.name, 255 self.args + other.args, 256 dict(self.kwargs, **other.kwargs), 257 param_ids_from=param_ids_from, 258 _ispytest=True, 259 )
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.
268@dataclasses.dataclass 269class MarkDecorator: 270 """A decorator for applying a mark on test functions and classes. 271 272 ``MarkDecorators`` are created with ``pytest.mark``:: 273 274 mark1 = pytest.mark.NAME # Simple MarkDecorator 275 mark2 = pytest.mark.NAME(name1=value) # Parametrized MarkDecorator 276 277 and can then be applied as decorators to test functions:: 278 279 @mark2 280 def test_function(): 281 pass 282 283 When a ``MarkDecorator`` is called, it does the following: 284 285 1. If called with a single class as its only positional argument and no 286 additional keyword arguments, it attaches the mark to the class so it 287 gets applied automatically to all test cases found in that class. 288 289 2. If called with a single function as its only positional argument and 290 no additional keyword arguments, it attaches the mark to the function, 291 containing all the arguments already stored internally in the 292 ``MarkDecorator``. 293 294 3. When called in any other case, it returns a new ``MarkDecorator`` 295 instance with the original ``MarkDecorator``'s content updated with 296 the arguments passed to this call. 297 298 Note: The rules above prevent a ``MarkDecorator`` from storing only a 299 single function or class reference as its positional argument with no 300 additional keyword or positional arguments. You can work around this by 301 using `with_args()`. 302 """ 303 304 mark: Mark 305 306 def __init__(self, mark: Mark, *, _ispytest: bool = False) -> None: 307 """:meta private:""" 308 check_ispytest(_ispytest) 309 self.mark = mark 310 311 @property 312 def name(self) -> str: 313 """Alias for mark.name.""" 314 return self.mark.name 315 316 @property 317 def args(self) -> tuple[Any, ...]: 318 """Alias for mark.args.""" 319 return self.mark.args 320 321 @property 322 def kwargs(self) -> Mapping[str, Any]: 323 """Alias for mark.kwargs.""" 324 return self.mark.kwargs 325 326 @property 327 def markname(self) -> str: 328 """:meta private:""" 329 return self.name # for backward-compat (2.4.1 had this attr) 330 331 def with_args(self, *args: object, **kwargs: object) -> MarkDecorator: 332 """Return a MarkDecorator with extra arguments added. 333 334 Unlike calling the MarkDecorator, with_args() can be used even 335 if the sole argument is a callable/class. 336 """ 337 mark = Mark(self.name, args, kwargs, _ispytest=True) 338 return MarkDecorator(self.mark.combined_with(mark), _ispytest=True) 339 340 # Type ignored because the overloads overlap with an incompatible 341 # return type. Not much we can do about that. Thankfully mypy picks 342 # the first match so it works out even if we break the rules. 343 @overload 344 def __call__(self, arg: Markable) -> Markable: # type: ignore[overload-overlap] 345 pass 346 347 @overload 348 def __call__(self, *args: object, **kwargs: object) -> MarkDecorator: 349 pass 350 351 def __call__(self, *args: object, **kwargs: object): 352 """Call the MarkDecorator.""" 353 if args and not kwargs: 354 func = args[0] 355 is_class = inspect.isclass(func) 356 if len(args) == 1 and (istestfunc(func) or is_class): 357 store_mark(func, self.mark, stacklevel=3) 358 return func 359 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()
.
306 def __init__(self, mark: Mark, *, _ispytest: bool = False) -> None: 307 """:meta private:""" 308 check_ispytest(_ispytest) 309 self.mark = mark
:meta private:
316 @property 317 def args(self) -> tuple[Any, ...]: 318 """Alias for mark.args.""" 319 return self.mark.args
Alias for mark.args.
321 @property 322 def kwargs(self) -> Mapping[str, Any]: 323 """Alias for mark.kwargs.""" 324 return self.mark.kwargs
Alias for mark.kwargs.
326 @property 327 def markname(self) -> str: 328 """:meta private:""" 329 return self.name # for backward-compat (2.4.1 had this attr)
:meta private:
331 def with_args(self, *args: object, **kwargs: object) -> MarkDecorator: 332 """Return a MarkDecorator with extra arguments added. 333 334 Unlike calling the MarkDecorator, with_args() can be used even 335 if the sole argument is a callable/class. 336 """ 337 mark = Mark(self.name, args, kwargs, _ispytest=True) 338 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.
485@final 486class MarkGenerator: 487 """Factory for :class:`MarkDecorator` objects - exposed as 488 a ``pytest.mark`` singleton instance. 489 490 Example:: 491 492 import pytest 493 494 495 @pytest.mark.slowtest 496 def test_function(): 497 pass 498 499 applies a 'slowtest' :class:`Mark` on ``test_function``. 500 """ 501 502 # See TYPE_CHECKING above. 503 if TYPE_CHECKING: 504 skip: _SkipMarkDecorator 505 skipif: _SkipifMarkDecorator 506 xfail: _XfailMarkDecorator 507 parametrize: _ParametrizeMarkDecorator 508 usefixtures: _UsefixturesMarkDecorator 509 filterwarnings: _FilterwarningsMarkDecorator 510 511 def __init__(self, *, _ispytest: bool = False) -> None: 512 check_ispytest(_ispytest) 513 self._config: Config | None = None 514 self._markers: set[str] = set() 515 516 def __getattr__(self, name: str) -> MarkDecorator: 517 """Generate a new :class:`MarkDecorator` with the given name.""" 518 if name[0] == "_": 519 raise AttributeError("Marker name must NOT start with underscore") 520 521 if self._config is not None: 522 # We store a set of markers as a performance optimisation - if a mark 523 # name is in the set we definitely know it, but a mark may be known and 524 # not in the set. We therefore start by updating the set! 525 if name not in self._markers: 526 for line in self._config.getini("markers"): 527 # example lines: "skipif(condition): skip the given test if..." 528 # or "hypothesis: tests which use Hypothesis", so to get the 529 # marker name we split on both `:` and `(`. 530 marker = line.split(":")[0].split("(")[0].strip() 531 self._markers.add(marker) 532 533 # If the name is not in the set of known marks after updating, 534 # then it really is time to issue a warning or an error. 535 if name not in self._markers: 536 if self._config.option.strict_markers or self._config.option.strict: 537 fail( 538 f"{name!r} not found in `markers` configuration option", 539 pytrace=False, 540 ) 541 542 # Raise a specific error for common misspellings of "parametrize". 543 if name in ["parameterize", "parametrise", "parameterise"]: 544 __tracebackhide__ = True 545 fail(f"Unknown '{name}' mark, did you mean 'parametrize'?") 546 547 warnings.warn( 548 f"Unknown pytest.mark.{name} - is this a typo? You can register " 549 "custom marks to avoid this warning - for details, see " 550 "https://docs.pytest.org/en/stable/how-to/mark.html", 551 PytestUnknownMarkWarning, 552 2, 553 ) 554 555 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
.
1091@final 1092class Metafunc: 1093 """Objects passed to the :hook:`pytest_generate_tests` hook. 1094 1095 They help to inspect a test function and to generate tests according to 1096 test configuration or values specified in the class or module where a 1097 test function is defined. 1098 """ 1099 1100 def __init__( 1101 self, 1102 definition: FunctionDefinition, 1103 fixtureinfo: fixtures.FuncFixtureInfo, 1104 config: Config, 1105 cls=None, 1106 module=None, 1107 *, 1108 _ispytest: bool = False, 1109 ) -> None: 1110 check_ispytest(_ispytest) 1111 1112 #: Access to the underlying :class:`_pytest.python.FunctionDefinition`. 1113 self.definition = definition 1114 1115 #: Access to the :class:`pytest.Config` object for the test session. 1116 self.config = config 1117 1118 #: The module object where the test function is defined in. 1119 self.module = module 1120 1121 #: Underlying Python test function. 1122 self.function = definition.obj 1123 1124 #: Set of fixture names required by the test function. 1125 self.fixturenames = fixtureinfo.names_closure 1126 1127 #: Class object where the test function is defined in or ``None``. 1128 self.cls = cls 1129 1130 self._arg2fixturedefs = fixtureinfo.name2fixturedefs 1131 1132 # Result of parametrize(). 1133 self._calls: list[CallSpec2] = [] 1134 1135 def parametrize( 1136 self, 1137 argnames: str | Sequence[str], 1138 argvalues: Iterable[ParameterSet | Sequence[object] | object], 1139 indirect: bool | Sequence[str] = False, 1140 ids: Iterable[object | None] | Callable[[Any], object | None] | None = None, 1141 scope: _ScopeName | None = None, 1142 *, 1143 _param_mark: Mark | None = None, 1144 ) -> None: 1145 """Add new invocations to the underlying test function using the list 1146 of argvalues for the given argnames. Parametrization is performed 1147 during the collection phase. If you need to setup expensive resources 1148 see about setting indirect to do it rather than at test setup time. 1149 1150 Can be called multiple times per test function (but only on different 1151 argument names), in which case each call parametrizes all previous 1152 parametrizations, e.g. 1153 1154 :: 1155 1156 unparametrized: t 1157 parametrize ["x", "y"]: t[x], t[y] 1158 parametrize [1, 2]: t[x-1], t[x-2], t[y-1], t[y-2] 1159 1160 :param argnames: 1161 A comma-separated string denoting one or more argument names, or 1162 a list/tuple of argument strings. 1163 1164 :param argvalues: 1165 The list of argvalues determines how often a test is invoked with 1166 different argument values. 1167 1168 If only one argname was specified argvalues is a list of values. 1169 If N argnames were specified, argvalues must be a list of 1170 N-tuples, where each tuple-element specifies a value for its 1171 respective argname. 1172 :type argvalues: Iterable[_pytest.mark.structures.ParameterSet | Sequence[object] | object] 1173 :param indirect: 1174 A list of arguments' names (subset of argnames) or a boolean. 1175 If True the list contains all names from the argnames. Each 1176 argvalue corresponding to an argname in this list will 1177 be passed as request.param to its respective argname fixture 1178 function so that it can perform more expensive setups during the 1179 setup phase of a test rather than at collection time. 1180 1181 :param ids: 1182 Sequence of (or generator for) ids for ``argvalues``, 1183 or a callable to return part of the id for each argvalue. 1184 1185 With sequences (and generators like ``itertools.count()``) the 1186 returned ids should be of type ``string``, ``int``, ``float``, 1187 ``bool``, or ``None``. 1188 They are mapped to the corresponding index in ``argvalues``. 1189 ``None`` means to use the auto-generated id. 1190 1191 If it is a callable it will be called for each entry in 1192 ``argvalues``, and the return value is used as part of the 1193 auto-generated id for the whole set (where parts are joined with 1194 dashes ("-")). 1195 This is useful to provide more specific ids for certain items, e.g. 1196 dates. Returning ``None`` will use an auto-generated id. 1197 1198 If no ids are provided they will be generated automatically from 1199 the argvalues. 1200 1201 :param scope: 1202 If specified it denotes the scope of the parameters. 1203 The scope is used for grouping tests by parameter instances. 1204 It will also override any fixture-function defined scope, allowing 1205 to set a dynamic scope using test context or configuration. 1206 """ 1207 argnames, parametersets = ParameterSet._for_parametrize( 1208 argnames, 1209 argvalues, 1210 self.function, 1211 self.config, 1212 nodeid=self.definition.nodeid, 1213 ) 1214 del argvalues 1215 1216 if "request" in argnames: 1217 fail( 1218 "'request' is a reserved name and cannot be used in @pytest.mark.parametrize", 1219 pytrace=False, 1220 ) 1221 1222 if scope is not None: 1223 scope_ = Scope.from_user( 1224 scope, descr=f"parametrize() call in {self.function.__name__}" 1225 ) 1226 else: 1227 scope_ = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect) 1228 1229 self._validate_if_using_arg_names(argnames, indirect) 1230 1231 # Use any already (possibly) generated ids with parametrize Marks. 1232 if _param_mark and _param_mark._param_ids_from: 1233 generated_ids = _param_mark._param_ids_from._param_ids_generated 1234 if generated_ids is not None: 1235 ids = generated_ids 1236 1237 ids = self._resolve_parameter_set_ids( 1238 argnames, ids, parametersets, nodeid=self.definition.nodeid 1239 ) 1240 1241 # Store used (possibly generated) ids with parametrize Marks. 1242 if _param_mark and _param_mark._param_ids_from and generated_ids is None: 1243 object.__setattr__(_param_mark._param_ids_from, "_param_ids_generated", ids) 1244 1245 # Add funcargs as fixturedefs to fixtureinfo.arg2fixturedefs by registering 1246 # artificial "pseudo" FixtureDef's so that later at test execution time we can 1247 # rely on a proper FixtureDef to exist for fixture setup. 1248 node = None 1249 # If we have a scope that is higher than function, we need 1250 # to make sure we only ever create an according fixturedef on 1251 # a per-scope basis. We thus store and cache the fixturedef on the 1252 # node related to the scope. 1253 if scope_ is not Scope.Function: 1254 collector = self.definition.parent 1255 assert collector is not None 1256 node = get_scope_node(collector, scope_) 1257 if node is None: 1258 # If used class scope and there is no class, use module-level 1259 # collector (for now). 1260 if scope_ is Scope.Class: 1261 assert isinstance(collector, Module) 1262 node = collector 1263 # If used package scope and there is no package, use session 1264 # (for now). 1265 elif scope_ is Scope.Package: 1266 node = collector.session 1267 else: 1268 assert False, f"Unhandled missing scope: {scope}" 1269 if node is None: 1270 name2pseudofixturedef = None 1271 else: 1272 default: dict[str, FixtureDef[Any]] = {} 1273 name2pseudofixturedef = node.stash.setdefault( 1274 name2pseudofixturedef_key, default 1275 ) 1276 arg_directness = self._resolve_args_directness(argnames, indirect) 1277 for argname in argnames: 1278 if arg_directness[argname] == "indirect": 1279 continue 1280 if name2pseudofixturedef is not None and argname in name2pseudofixturedef: 1281 fixturedef = name2pseudofixturedef[argname] 1282 else: 1283 fixturedef = FixtureDef( 1284 config=self.config, 1285 baseid="", 1286 argname=argname, 1287 func=get_direct_param_fixture_func, 1288 scope=scope_, 1289 params=None, 1290 ids=None, 1291 _ispytest=True, 1292 ) 1293 if name2pseudofixturedef is not None: 1294 name2pseudofixturedef[argname] = fixturedef 1295 self._arg2fixturedefs[argname] = [fixturedef] 1296 1297 # Create the new calls: if we are parametrize() multiple times (by applying the decorator 1298 # more than once) then we accumulate those calls generating the cartesian product 1299 # of all calls. 1300 newcalls = [] 1301 for callspec in self._calls or [CallSpec2()]: 1302 for param_index, (param_id, param_set) in enumerate( 1303 zip(ids, parametersets) 1304 ): 1305 newcallspec = callspec.setmulti( 1306 argnames=argnames, 1307 valset=param_set.values, 1308 id=param_id, 1309 marks=param_set.marks, 1310 scope=scope_, 1311 param_index=param_index, 1312 ) 1313 newcalls.append(newcallspec) 1314 self._calls = newcalls 1315 1316 def _resolve_parameter_set_ids( 1317 self, 1318 argnames: Sequence[str], 1319 ids: Iterable[object | None] | Callable[[Any], object | None] | None, 1320 parametersets: Sequence[ParameterSet], 1321 nodeid: str, 1322 ) -> list[str]: 1323 """Resolve the actual ids for the given parameter sets. 1324 1325 :param argnames: 1326 Argument names passed to ``parametrize()``. 1327 :param ids: 1328 The `ids` parameter of the ``parametrize()`` call (see docs). 1329 :param parametersets: 1330 The parameter sets, each containing a set of values corresponding 1331 to ``argnames``. 1332 :param nodeid str: 1333 The nodeid of the definition item that generated this 1334 parametrization. 1335 :returns: 1336 List with ids for each parameter set given. 1337 """ 1338 if ids is None: 1339 idfn = None 1340 ids_ = None 1341 elif callable(ids): 1342 idfn = ids 1343 ids_ = None 1344 else: 1345 idfn = None 1346 ids_ = self._validate_ids(ids, parametersets, self.function.__name__) 1347 id_maker = IdMaker( 1348 argnames, 1349 parametersets, 1350 idfn, 1351 ids_, 1352 self.config, 1353 nodeid=nodeid, 1354 func_name=self.function.__name__, 1355 ) 1356 return id_maker.make_unique_parameterset_ids() 1357 1358 def _validate_ids( 1359 self, 1360 ids: Iterable[object | None], 1361 parametersets: Sequence[ParameterSet], 1362 func_name: str, 1363 ) -> list[object | None]: 1364 try: 1365 num_ids = len(ids) # type: ignore[arg-type] 1366 except TypeError: 1367 try: 1368 iter(ids) 1369 except TypeError as e: 1370 raise TypeError("ids must be a callable or an iterable") from e 1371 num_ids = len(parametersets) 1372 1373 # num_ids == 0 is a special case: https://github.com/pytest-dev/pytest/issues/1849 1374 if num_ids != len(parametersets) and num_ids != 0: 1375 msg = "In {}: {} parameter sets specified, with different number of ids: {}" 1376 fail(msg.format(func_name, len(parametersets), num_ids), pytrace=False) 1377 1378 return list(itertools.islice(ids, num_ids)) 1379 1380 def _resolve_args_directness( 1381 self, 1382 argnames: Sequence[str], 1383 indirect: bool | Sequence[str], 1384 ) -> dict[str, Literal["indirect", "direct"]]: 1385 """Resolve if each parametrized argument must be considered an indirect 1386 parameter to a fixture of the same name, or a direct parameter to the 1387 parametrized function, based on the ``indirect`` parameter of the 1388 parametrized() call. 1389 1390 :param argnames: 1391 List of argument names passed to ``parametrize()``. 1392 :param indirect: 1393 Same as the ``indirect`` parameter of ``parametrize()``. 1394 :returns 1395 A dict mapping each arg name to either "indirect" or "direct". 1396 """ 1397 arg_directness: dict[str, Literal["indirect", "direct"]] 1398 if isinstance(indirect, bool): 1399 arg_directness = dict.fromkeys( 1400 argnames, "indirect" if indirect else "direct" 1401 ) 1402 elif isinstance(indirect, Sequence): 1403 arg_directness = dict.fromkeys(argnames, "direct") 1404 for arg in indirect: 1405 if arg not in argnames: 1406 fail( 1407 f"In {self.function.__name__}: indirect fixture '{arg}' doesn't exist", 1408 pytrace=False, 1409 ) 1410 arg_directness[arg] = "indirect" 1411 else: 1412 fail( 1413 f"In {self.function.__name__}: expected Sequence or boolean" 1414 f" for indirect, got {type(indirect).__name__}", 1415 pytrace=False, 1416 ) 1417 return arg_directness 1418 1419 def _validate_if_using_arg_names( 1420 self, 1421 argnames: Sequence[str], 1422 indirect: bool | Sequence[str], 1423 ) -> None: 1424 """Check if all argnames are being used, by default values, or directly/indirectly. 1425 1426 :param List[str] argnames: List of argument names passed to ``parametrize()``. 1427 :param indirect: Same as the ``indirect`` parameter of ``parametrize()``. 1428 :raises ValueError: If validation fails. 1429 """ 1430 default_arg_names = set(get_default_arg_names(self.function)) 1431 func_name = self.function.__name__ 1432 for arg in argnames: 1433 if arg not in self.fixturenames: 1434 if arg in default_arg_names: 1435 fail( 1436 f"In {func_name}: function already takes an argument '{arg}' with a default value", 1437 pytrace=False, 1438 ) 1439 else: 1440 if isinstance(indirect, Sequence): 1441 name = "fixture" if arg in indirect else "argument" 1442 else: 1443 name = "fixture" if indirect else "argument" 1444 fail( 1445 f"In {func_name}: function uses no {name} '{arg}'", 1446 pytrace=False, 1447 )
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.
1100 def __init__( 1101 self, 1102 definition: FunctionDefinition, 1103 fixtureinfo: fixtures.FuncFixtureInfo, 1104 config: Config, 1105 cls=None, 1106 module=None, 1107 *, 1108 _ispytest: bool = False, 1109 ) -> None: 1110 check_ispytest(_ispytest) 1111 1112 #: Access to the underlying :class:`_pytest.python.FunctionDefinition`. 1113 self.definition = definition 1114 1115 #: Access to the :class:`pytest.Config` object for the test session. 1116 self.config = config 1117 1118 #: The module object where the test function is defined in. 1119 self.module = module 1120 1121 #: Underlying Python test function. 1122 self.function = definition.obj 1123 1124 #: Set of fixture names required by the test function. 1125 self.fixturenames = fixtureinfo.names_closure 1126 1127 #: Class object where the test function is defined in or ``None``. 1128 self.cls = cls 1129 1130 self._arg2fixturedefs = fixtureinfo.name2fixturedefs 1131 1132 # Result of parametrize(). 1133 self._calls: list[CallSpec2] = []
1135 def parametrize( 1136 self, 1137 argnames: str | Sequence[str], 1138 argvalues: Iterable[ParameterSet | Sequence[object] | object], 1139 indirect: bool | Sequence[str] = False, 1140 ids: Iterable[object | None] | Callable[[Any], object | None] | None = None, 1141 scope: _ScopeName | None = None, 1142 *, 1143 _param_mark: Mark | None = None, 1144 ) -> None: 1145 """Add new invocations to the underlying test function using the list 1146 of argvalues for the given argnames. Parametrization is performed 1147 during the collection phase. If you need to setup expensive resources 1148 see about setting indirect to do it rather than at test setup time. 1149 1150 Can be called multiple times per test function (but only on different 1151 argument names), in which case each call parametrizes all previous 1152 parametrizations, e.g. 1153 1154 :: 1155 1156 unparametrized: t 1157 parametrize ["x", "y"]: t[x], t[y] 1158 parametrize [1, 2]: t[x-1], t[x-2], t[y-1], t[y-2] 1159 1160 :param argnames: 1161 A comma-separated string denoting one or more argument names, or 1162 a list/tuple of argument strings. 1163 1164 :param argvalues: 1165 The list of argvalues determines how often a test is invoked with 1166 different argument values. 1167 1168 If only one argname was specified argvalues is a list of values. 1169 If N argnames were specified, argvalues must be a list of 1170 N-tuples, where each tuple-element specifies a value for its 1171 respective argname. 1172 :type argvalues: Iterable[_pytest.mark.structures.ParameterSet | Sequence[object] | object] 1173 :param indirect: 1174 A list of arguments' names (subset of argnames) or a boolean. 1175 If True the list contains all names from the argnames. Each 1176 argvalue corresponding to an argname in this list will 1177 be passed as request.param to its respective argname fixture 1178 function so that it can perform more expensive setups during the 1179 setup phase of a test rather than at collection time. 1180 1181 :param ids: 1182 Sequence of (or generator for) ids for ``argvalues``, 1183 or a callable to return part of the id for each argvalue. 1184 1185 With sequences (and generators like ``itertools.count()``) the 1186 returned ids should be of type ``string``, ``int``, ``float``, 1187 ``bool``, or ``None``. 1188 They are mapped to the corresponding index in ``argvalues``. 1189 ``None`` means to use the auto-generated id. 1190 1191 If it is a callable it will be called for each entry in 1192 ``argvalues``, and the return value is used as part of the 1193 auto-generated id for the whole set (where parts are joined with 1194 dashes ("-")). 1195 This is useful to provide more specific ids for certain items, e.g. 1196 dates. Returning ``None`` will use an auto-generated id. 1197 1198 If no ids are provided they will be generated automatically from 1199 the argvalues. 1200 1201 :param scope: 1202 If specified it denotes the scope of the parameters. 1203 The scope is used for grouping tests by parameter instances. 1204 It will also override any fixture-function defined scope, allowing 1205 to set a dynamic scope using test context or configuration. 1206 """ 1207 argnames, parametersets = ParameterSet._for_parametrize( 1208 argnames, 1209 argvalues, 1210 self.function, 1211 self.config, 1212 nodeid=self.definition.nodeid, 1213 ) 1214 del argvalues 1215 1216 if "request" in argnames: 1217 fail( 1218 "'request' is a reserved name and cannot be used in @pytest.mark.parametrize", 1219 pytrace=False, 1220 ) 1221 1222 if scope is not None: 1223 scope_ = Scope.from_user( 1224 scope, descr=f"parametrize() call in {self.function.__name__}" 1225 ) 1226 else: 1227 scope_ = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect) 1228 1229 self._validate_if_using_arg_names(argnames, indirect) 1230 1231 # Use any already (possibly) generated ids with parametrize Marks. 1232 if _param_mark and _param_mark._param_ids_from: 1233 generated_ids = _param_mark._param_ids_from._param_ids_generated 1234 if generated_ids is not None: 1235 ids = generated_ids 1236 1237 ids = self._resolve_parameter_set_ids( 1238 argnames, ids, parametersets, nodeid=self.definition.nodeid 1239 ) 1240 1241 # Store used (possibly generated) ids with parametrize Marks. 1242 if _param_mark and _param_mark._param_ids_from and generated_ids is None: 1243 object.__setattr__(_param_mark._param_ids_from, "_param_ids_generated", ids) 1244 1245 # Add funcargs as fixturedefs to fixtureinfo.arg2fixturedefs by registering 1246 # artificial "pseudo" FixtureDef's so that later at test execution time we can 1247 # rely on a proper FixtureDef to exist for fixture setup. 1248 node = None 1249 # If we have a scope that is higher than function, we need 1250 # to make sure we only ever create an according fixturedef on 1251 # a per-scope basis. We thus store and cache the fixturedef on the 1252 # node related to the scope. 1253 if scope_ is not Scope.Function: 1254 collector = self.definition.parent 1255 assert collector is not None 1256 node = get_scope_node(collector, scope_) 1257 if node is None: 1258 # If used class scope and there is no class, use module-level 1259 # collector (for now). 1260 if scope_ is Scope.Class: 1261 assert isinstance(collector, Module) 1262 node = collector 1263 # If used package scope and there is no package, use session 1264 # (for now). 1265 elif scope_ is Scope.Package: 1266 node = collector.session 1267 else: 1268 assert False, f"Unhandled missing scope: {scope}" 1269 if node is None: 1270 name2pseudofixturedef = None 1271 else: 1272 default: dict[str, FixtureDef[Any]] = {} 1273 name2pseudofixturedef = node.stash.setdefault( 1274 name2pseudofixturedef_key, default 1275 ) 1276 arg_directness = self._resolve_args_directness(argnames, indirect) 1277 for argname in argnames: 1278 if arg_directness[argname] == "indirect": 1279 continue 1280 if name2pseudofixturedef is not None and argname in name2pseudofixturedef: 1281 fixturedef = name2pseudofixturedef[argname] 1282 else: 1283 fixturedef = FixtureDef( 1284 config=self.config, 1285 baseid="", 1286 argname=argname, 1287 func=get_direct_param_fixture_func, 1288 scope=scope_, 1289 params=None, 1290 ids=None, 1291 _ispytest=True, 1292 ) 1293 if name2pseudofixturedef is not None: 1294 name2pseudofixturedef[argname] = fixturedef 1295 self._arg2fixturedefs[argname] = [fixturedef] 1296 1297 # Create the new calls: if we are parametrize() multiple times (by applying the decorator 1298 # more than once) then we accumulate those calls generating the cartesian product 1299 # of all calls. 1300 newcalls = [] 1301 for callspec in self._calls or [CallSpec2()]: 1302 for param_index, (param_id, param_set) in enumerate( 1303 zip(ids, parametersets) 1304 ): 1305 newcallspec = callspec.setmulti( 1306 argnames=argnames, 1307 valset=param_set.values, 1308 id=param_id, 1309 marks=param_set.marks, 1310 scope=scope_, 1311 param_index=param_index, 1312 ) 1313 newcalls.append(newcallspec) 1314 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 rather than at test setup time.
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.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.
543class Module(nodes.File, PyCollector): 544 """Collector for test classes and functions in a Python module.""" 545 546 def _getobj(self): 547 return importtestmodule(self.path, self.config) 548 549 def collect(self) -> Iterable[nodes.Item | nodes.Collector]: 550 self._register_setup_module_fixture() 551 self._register_setup_function_fixture() 552 self.session._fixturemanager.parsefactories(self) 553 return super().collect() 554 555 def _register_setup_module_fixture(self) -> None: 556 """Register an autouse, module-scoped fixture for the collected module object 557 that invokes setUpModule/tearDownModule if either or both are available. 558 559 Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with 560 other fixtures (#517). 561 """ 562 setup_module = _get_first_non_fixture_func( 563 self.obj, ("setUpModule", "setup_module") 564 ) 565 teardown_module = _get_first_non_fixture_func( 566 self.obj, ("tearDownModule", "teardown_module") 567 ) 568 569 if setup_module is None and teardown_module is None: 570 return 571 572 def xunit_setup_module_fixture(request) -> Generator[None]: 573 module = request.module 574 if setup_module is not None: 575 _call_with_optional_argument(setup_module, module) 576 yield 577 if teardown_module is not None: 578 _call_with_optional_argument(teardown_module, module) 579 580 self.session._fixturemanager._register_fixture( 581 # Use a unique name to speed up lookup. 582 name=f"_xunit_setup_module_fixture_{self.obj.__name__}", 583 func=xunit_setup_module_fixture, 584 nodeid=self.nodeid, 585 scope="module", 586 autouse=True, 587 ) 588 589 def _register_setup_function_fixture(self) -> None: 590 """Register an autouse, function-scoped fixture for the collected module object 591 that invokes setup_function/teardown_function if either or both are available. 592 593 Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with 594 other fixtures (#517). 595 """ 596 setup_function = _get_first_non_fixture_func(self.obj, ("setup_function",)) 597 teardown_function = _get_first_non_fixture_func( 598 self.obj, ("teardown_function",) 599 ) 600 if setup_function is None and teardown_function is None: 601 return 602 603 def xunit_setup_function_fixture(request) -> Generator[None]: 604 if request.instance is not None: 605 # in this case we are bound to an instance, so we need to let 606 # setup_method handle this 607 yield 608 return 609 function = request.function 610 if setup_function is not None: 611 _call_with_optional_argument(setup_function, function) 612 yield 613 if teardown_function is not None: 614 _call_with_optional_argument(teardown_function, function) 615 616 self.session._fixturemanager._register_fixture( 617 # Use a unique name to speed up lookup. 618 name=f"_xunit_setup_function_fixture_{self.obj.__name__}", 619 func=xunit_setup_function_fixture, 620 nodeid=self.nodeid, 621 scope="function", 622 autouse=True, 623 )
Collector for test classes and functions in a Python module.
549 def collect(self) -> Iterable[nodes.Item | nodes.Collector]: 550 self._register_setup_module_fixture() 551 self._register_setup_function_fixture() 552 self.session._fixturemanager.parsefactories(self) 553 return super().collect()
Collect children (items and collectors) for this collector.
Inherited Members
- _pytest.nodes.FSCollector
- FSCollector
- path
- from_parent
- _pytest.python.PyCollector
- funcnamefilter
- isnosetest
- classnamefilter
- istestfunction
- istestclass
- _pytest.python.PyobjMixin
- module
- cls
- instance
- obj
- getmodpath
- reportinfo
- _pytest.nodes.Node
- fspath
- name
- parent
- keywords
- own_markers
- extra_keyword_matches
- stash
- ihook
- warn
- nodeid
- setup
- teardown
- iter_parents
- listchain
- add_marker
- iter_markers
- iter_markers_with_node
- get_closest_marker
- listextrakeywords
- listnames
- addfinalizer
- getparent
- config
- session
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.
349class OptionGroup: 350 """A group of options shown in its own section.""" 351 352 def __init__( 353 self, 354 name: str, 355 description: str = "", 356 parser: Parser | None = None, 357 *, 358 _ispytest: bool = False, 359 ) -> None: 360 check_ispytest(_ispytest) 361 self.name = name 362 self.description = description 363 self.options: list[Argument] = [] 364 self.parser = parser 365 366 def addoption(self, *opts: str, **attrs: Any) -> None: 367 """Add an option to this group. 368 369 If a shortened version of a long option is specified, it will 370 be suppressed in the help. ``addoption('--twowords', '--two-words')`` 371 results in help showing ``--two-words`` only, but ``--twowords`` gets 372 accepted **and** the automatic destination is in ``args.twowords``. 373 374 :param opts: 375 Option names, can be short or long options. 376 :param attrs: 377 Same attributes as the argparse library's :meth:`add_argument() 378 <argparse.ArgumentParser.add_argument>` function accepts. 379 """ 380 conflict = set(opts).intersection( 381 name for opt in self.options for name in opt.names() 382 ) 383 if conflict: 384 raise ValueError(f"option names {conflict} already added") 385 option = Argument(*opts, **attrs) 386 self._addoption_instance(option, shortupper=False) 387 388 def _addoption(self, *opts: str, **attrs: Any) -> None: 389 option = Argument(*opts, **attrs) 390 self._addoption_instance(option, shortupper=True) 391 392 def _addoption_instance(self, option: Argument, shortupper: bool = False) -> None: 393 if not shortupper: 394 for opt in option._short_opts: 395 if opt[0] == "-" and opt[1].islower(): 396 raise ValueError("lowercase shortoptions reserved") 397 if self.parser: 398 self.parser.processoption(option) 399 self.options.append(option)
A group of options shown in its own section.
352 def __init__( 353 self, 354 name: str, 355 description: str = "", 356 parser: Parser | None = None, 357 *, 358 _ispytest: bool = False, 359 ) -> None: 360 check_ispytest(_ispytest) 361 self.name = name 362 self.description = description 363 self.options: list[Argument] = [] 364 self.parser = parser
366 def addoption(self, *opts: str, **attrs: Any) -> None: 367 """Add an option to this group. 368 369 If a shortened version of a long option is specified, it will 370 be suppressed in the help. ``addoption('--twowords', '--two-words')`` 371 results in help showing ``--two-words`` only, but ``--twowords`` gets 372 accepted **and** the automatic destination is in ``args.twowords``. 373 374 :param opts: 375 Option names, can be short or long options. 376 :param attrs: 377 Same attributes as the argparse library's :meth:`add_argument() 378 <argparse.ArgumentParser.add_argument>` function accepts. 379 """ 380 conflict = set(opts).intersection( 381 name for opt in self.options for name in opt.names() 382 ) 383 if conflict: 384 raise ValueError(f"option names {conflict} already added") 385 option = Argument(*opts, **attrs) 386 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.
626class Package(nodes.Directory): 627 """Collector for files and directories in a Python packages -- directories 628 with an `__init__.py` file. 629 630 .. note:: 631 632 Directories without an `__init__.py` file are instead collected by 633 :class:`~pytest.Dir` by default. Both are :class:`~pytest.Directory` 634 collectors. 635 636 .. versionchanged:: 8.0 637 638 Now inherits from :class:`~pytest.Directory`. 639 """ 640 641 def __init__( 642 self, 643 fspath: LEGACY_PATH | None, 644 parent: nodes.Collector, 645 # NOTE: following args are unused: 646 config=None, 647 session=None, 648 nodeid=None, 649 path: Path | None = None, 650 ) -> None: 651 # NOTE: Could be just the following, but kept as-is for compat. 652 # super().__init__(self, fspath, parent=parent) 653 session = parent.session 654 super().__init__( 655 fspath=fspath, 656 path=path, 657 parent=parent, 658 config=config, 659 session=session, 660 nodeid=nodeid, 661 ) 662 663 def setup(self) -> None: 664 init_mod = importtestmodule(self.path / "__init__.py", self.config) 665 666 # Not using fixtures to call setup_module here because autouse fixtures 667 # from packages are not called automatically (#4085). 668 setup_module = _get_first_non_fixture_func( 669 init_mod, ("setUpModule", "setup_module") 670 ) 671 if setup_module is not None: 672 _call_with_optional_argument(setup_module, init_mod) 673 674 teardown_module = _get_first_non_fixture_func( 675 init_mod, ("tearDownModule", "teardown_module") 676 ) 677 if teardown_module is not None: 678 func = partial(_call_with_optional_argument, teardown_module, init_mod) 679 self.addfinalizer(func) 680 681 def collect(self) -> Iterable[nodes.Item | nodes.Collector]: 682 # Always collect __init__.py first. 683 def sort_key(entry: os.DirEntry[str]) -> object: 684 return (entry.name != "__init__.py", entry.name) 685 686 config = self.config 687 col: nodes.Collector | None 688 cols: Sequence[nodes.Collector] 689 ihook = self.ihook 690 for direntry in scandir(self.path, sort_key): 691 if direntry.is_dir(): 692 path = Path(direntry.path) 693 if not self.session.isinitpath(path, with_parents=True): 694 if ihook.pytest_ignore_collect(collection_path=path, config=config): 695 continue 696 col = ihook.pytest_collect_directory(path=path, parent=self) 697 if col is not None: 698 yield col 699 700 elif direntry.is_file(): 701 path = Path(direntry.path) 702 if not self.session.isinitpath(path): 703 if ihook.pytest_ignore_collect(collection_path=path, config=config): 704 continue 705 cols = ihook.pytest_collect_file(file_path=path, parent=self) 706 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
.
641 def __init__( 642 self, 643 fspath: LEGACY_PATH | None, 644 parent: nodes.Collector, 645 # NOTE: following args are unused: 646 config=None, 647 session=None, 648 nodeid=None, 649 path: Path | None = None, 650 ) -> None: 651 # NOTE: Could be just the following, but kept as-is for compat. 652 # super().__init__(self, fspath, parent=parent) 653 session = parent.session 654 super().__init__( 655 fspath=fspath, 656 path=path, 657 parent=parent, 658 config=config, 659 session=session, 660 nodeid=nodeid, 661 )
663 def setup(self) -> None: 664 init_mod = importtestmodule(self.path / "__init__.py", self.config) 665 666 # Not using fixtures to call setup_module here because autouse fixtures 667 # from packages are not called automatically (#4085). 668 setup_module = _get_first_non_fixture_func( 669 init_mod, ("setUpModule", "setup_module") 670 ) 671 if setup_module is not None: 672 _call_with_optional_argument(setup_module, init_mod) 673 674 teardown_module = _get_first_non_fixture_func( 675 init_mod, ("tearDownModule", "teardown_module") 676 ) 677 if teardown_module is not None: 678 func = partial(_call_with_optional_argument, teardown_module, init_mod) 679 self.addfinalizer(func)
681 def collect(self) -> Iterable[nodes.Item | nodes.Collector]: 682 # Always collect __init__.py first. 683 def sort_key(entry: os.DirEntry[str]) -> object: 684 return (entry.name != "__init__.py", entry.name) 685 686 config = self.config 687 col: nodes.Collector | None 688 cols: Sequence[nodes.Collector] 689 ihook = self.ihook 690 for direntry in scandir(self.path, sort_key): 691 if direntry.is_dir(): 692 path = Path(direntry.path) 693 if not self.session.isinitpath(path, with_parents=True): 694 if ihook.pytest_ignore_collect(collection_path=path, config=config): 695 continue 696 col = ihook.pytest_collect_directory(path=path, parent=self) 697 if col is not None: 698 yield col 699 700 elif direntry.is_file(): 701 path = Path(direntry.path) 702 if not self.session.isinitpath(path): 703 if ihook.pytest_ignore_collect(collection_path=path, config=config): 704 continue 705 cols = ihook.pytest_collect_file(file_path=path, parent=self) 706 yield from cols
Collect children (items and collectors) for this collector.
Inherited Members
- _pytest.nodes.FSCollector
- path
- from_parent
- _pytest.nodes.Node
- fspath
- name
- parent
- keywords
- own_markers
- extra_keyword_matches
- stash
- ihook
- warn
- nodeid
- teardown
- iter_parents
- listchain
- add_marker
- iter_markers
- iter_markers_with_node
- get_closest_marker
- listextrakeywords
- listnames
- addfinalizer
- getparent
- config
- session
49def param( 50 *values: object, 51 marks: MarkDecorator | Collection[MarkDecorator | Mark] = (), 52 id: str | None = None, 53) -> ParameterSet: 54 """Specify a parameter in `pytest.mark.parametrize`_ calls or 55 :ref:`parametrized fixtures <fixture-parametrize-marks>`. 56 57 .. code-block:: python 58 59 @pytest.mark.parametrize( 60 "test_input,expected", 61 [ 62 ("3+5", 8), 63 pytest.param("6*9", 42, marks=pytest.mark.xfail), 64 ], 65 ) 66 def test_eval(test_input, expected): 67 assert eval(test_input) == expected 68 69 :param values: Variable args of the values of the parameter set, in order. 70 :param marks: A single mark or a list of marks to be applied to this parameter set. 71 :param id: The id to attribute to this parameter set. 72 """ 73 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.
- id: The id to attribute to this parameter set.
35@final 36class Parser: 37 """Parser for command line arguments and ini-file values. 38 39 :ivar extra_info: Dict of generic param -> value to display in case 40 there's an error processing the command line arguments. 41 """ 42 43 prog: str | None = None 44 45 def __init__( 46 self, 47 usage: str | None = None, 48 processopt: Callable[[Argument], None] | None = None, 49 *, 50 _ispytest: bool = False, 51 ) -> None: 52 check_ispytest(_ispytest) 53 self._anonymous = OptionGroup("Custom options", parser=self, _ispytest=True) 54 self._groups: list[OptionGroup] = [] 55 self._processopt = processopt 56 self._usage = usage 57 self._inidict: dict[str, tuple[str, str | None, Any]] = {} 58 self._ininames: list[str] = [] 59 self.extra_info: dict[str, Any] = {} 60 61 def processoption(self, option: Argument) -> None: 62 if self._processopt: 63 if option.dest: 64 self._processopt(option) 65 66 def getgroup( 67 self, name: str, description: str = "", after: str | None = None 68 ) -> OptionGroup: 69 """Get (or create) a named option Group. 70 71 :param name: Name of the option group. 72 :param description: Long description for --help output. 73 :param after: Name of another group, used for ordering --help output. 74 :returns: The option group. 75 76 The returned group object has an ``addoption`` method with the same 77 signature as :func:`parser.addoption <pytest.Parser.addoption>` but 78 will be shown in the respective group in the output of 79 ``pytest --help``. 80 """ 81 for group in self._groups: 82 if group.name == name: 83 return group 84 group = OptionGroup(name, description, parser=self, _ispytest=True) 85 i = 0 86 for i, grp in enumerate(self._groups): 87 if grp.name == after: 88 break 89 self._groups.insert(i + 1, group) 90 return group 91 92 def addoption(self, *opts: str, **attrs: Any) -> None: 93 """Register a command line option. 94 95 :param opts: 96 Option names, can be short or long options. 97 :param attrs: 98 Same attributes as the argparse library's :meth:`add_argument() 99 <argparse.ArgumentParser.add_argument>` function accepts. 100 101 After command line parsing, options are available on the pytest config 102 object via ``config.option.NAME`` where ``NAME`` is usually set 103 by passing a ``dest`` attribute, for example 104 ``addoption("--long", dest="NAME", ...)``. 105 """ 106 self._anonymous.addoption(*opts, **attrs) 107 108 def parse( 109 self, 110 args: Sequence[str | os.PathLike[str]], 111 namespace: argparse.Namespace | None = None, 112 ) -> argparse.Namespace: 113 from _pytest._argcomplete import try_argcomplete 114 115 self.optparser = self._getparser() 116 try_argcomplete(self.optparser) 117 strargs = [os.fspath(x) for x in args] 118 return self.optparser.parse_args(strargs, namespace=namespace) 119 120 def _getparser(self) -> MyOptionParser: 121 from _pytest._argcomplete import filescompleter 122 123 optparser = MyOptionParser(self, self.extra_info, prog=self.prog) 124 groups = [*self._groups, self._anonymous] 125 for group in groups: 126 if group.options: 127 desc = group.description or group.name 128 arggroup = optparser.add_argument_group(desc) 129 for option in group.options: 130 n = option.names() 131 a = option.attrs() 132 arggroup.add_argument(*n, **a) 133 file_or_dir_arg = optparser.add_argument(FILE_OR_DIR, nargs="*") 134 # bash like autocompletion for dirs (appending '/') 135 # Type ignored because typeshed doesn't know about argcomplete. 136 file_or_dir_arg.completer = filescompleter # type: ignore 137 return optparser 138 139 def parse_setoption( 140 self, 141 args: Sequence[str | os.PathLike[str]], 142 option: argparse.Namespace, 143 namespace: argparse.Namespace | None = None, 144 ) -> list[str]: 145 parsedoption = self.parse(args, namespace=namespace) 146 for name, value in parsedoption.__dict__.items(): 147 setattr(option, name, value) 148 return cast(List[str], getattr(parsedoption, FILE_OR_DIR)) 149 150 def parse_known_args( 151 self, 152 args: Sequence[str | os.PathLike[str]], 153 namespace: argparse.Namespace | None = None, 154 ) -> argparse.Namespace: 155 """Parse the known arguments at this point. 156 157 :returns: An argparse namespace object. 158 """ 159 return self.parse_known_and_unknown_args(args, namespace=namespace)[0] 160 161 def parse_known_and_unknown_args( 162 self, 163 args: Sequence[str | os.PathLike[str]], 164 namespace: argparse.Namespace | None = None, 165 ) -> tuple[argparse.Namespace, list[str]]: 166 """Parse the known arguments at this point, and also return the 167 remaining unknown arguments. 168 169 :returns: 170 A tuple containing an argparse namespace object for the known 171 arguments, and a list of the unknown arguments. 172 """ 173 optparser = self._getparser() 174 strargs = [os.fspath(x) for x in args] 175 return optparser.parse_known_args(strargs, namespace=namespace) 176 177 def addini( 178 self, 179 name: str, 180 help: str, 181 type: Literal["string", "paths", "pathlist", "args", "linelist", "bool"] 182 | None = None, 183 default: Any = NOT_SET, 184 ) -> None: 185 """Register an ini-file option. 186 187 :param name: 188 Name of the ini-variable. 189 :param type: 190 Type of the variable. Can be: 191 192 * ``string``: a string 193 * ``bool``: a boolean 194 * ``args``: a list of strings, separated as in a shell 195 * ``linelist``: a list of strings, separated by line breaks 196 * ``paths``: a list of :class:`pathlib.Path`, separated as in a shell 197 * ``pathlist``: a list of ``py.path``, separated as in a shell 198 199 For ``paths`` and ``pathlist`` types, they are considered relative to the ini-file. 200 In case the execution is happening without an ini-file defined, 201 they will be considered relative to the current working directory (for example with ``--override-ini``). 202 203 .. versionadded:: 7.0 204 The ``paths`` variable type. 205 206 .. versionadded:: 8.1 207 Use the current working directory to resolve ``paths`` and ``pathlist`` in the absence of an ini-file. 208 209 Defaults to ``string`` if ``None`` or not passed. 210 :param default: 211 Default value if no ini-file option exists but is queried. 212 213 The value of ini-variables can be retrieved via a call to 214 :py:func:`config.getini(name) <pytest.Config.getini>`. 215 """ 216 assert type in (None, "string", "paths", "pathlist", "args", "linelist", "bool") 217 if default is NOT_SET: 218 default = get_ini_default_for_type(type) 219 220 self._inidict[name] = (help, type, default) 221 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.
45 def __init__( 46 self, 47 usage: str | None = None, 48 processopt: Callable[[Argument], None] | None = None, 49 *, 50 _ispytest: bool = False, 51 ) -> None: 52 check_ispytest(_ispytest) 53 self._anonymous = OptionGroup("Custom options", parser=self, _ispytest=True) 54 self._groups: list[OptionGroup] = [] 55 self._processopt = processopt 56 self._usage = usage 57 self._inidict: dict[str, tuple[str, str | None, Any]] = {} 58 self._ininames: list[str] = [] 59 self.extra_info: dict[str, Any] = {}
66 def getgroup( 67 self, name: str, description: str = "", after: str | None = None 68 ) -> OptionGroup: 69 """Get (or create) a named option Group. 70 71 :param name: Name of the option group. 72 :param description: Long description for --help output. 73 :param after: Name of another group, used for ordering --help output. 74 :returns: The option group. 75 76 The returned group object has an ``addoption`` method with the same 77 signature as :func:`parser.addoption <pytest.Parser.addoption>` but 78 will be shown in the respective group in the output of 79 ``pytest --help``. 80 """ 81 for group in self._groups: 82 if group.name == name: 83 return group 84 group = OptionGroup(name, description, parser=self, _ispytest=True) 85 i = 0 86 for i, grp in enumerate(self._groups): 87 if grp.name == after: 88 break 89 self._groups.insert(i + 1, group) 90 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
.
92 def addoption(self, *opts: str, **attrs: Any) -> None: 93 """Register a command line option. 94 95 :param opts: 96 Option names, can be short or long options. 97 :param attrs: 98 Same attributes as the argparse library's :meth:`add_argument() 99 <argparse.ArgumentParser.add_argument>` function accepts. 100 101 After command line parsing, options are available on the pytest config 102 object via ``config.option.NAME`` where ``NAME`` is usually set 103 by passing a ``dest`` attribute, for example 104 ``addoption("--long", dest="NAME", ...)``. 105 """ 106 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", ...)
.
108 def parse( 109 self, 110 args: Sequence[str | os.PathLike[str]], 111 namespace: argparse.Namespace | None = None, 112 ) -> argparse.Namespace: 113 from _pytest._argcomplete import try_argcomplete 114 115 self.optparser = self._getparser() 116 try_argcomplete(self.optparser) 117 strargs = [os.fspath(x) for x in args] 118 return self.optparser.parse_args(strargs, namespace=namespace)
139 def parse_setoption( 140 self, 141 args: Sequence[str | os.PathLike[str]], 142 option: argparse.Namespace, 143 namespace: argparse.Namespace | None = None, 144 ) -> list[str]: 145 parsedoption = self.parse(args, namespace=namespace) 146 for name, value in parsedoption.__dict__.items(): 147 setattr(option, name, value) 148 return cast(List[str], getattr(parsedoption, FILE_OR_DIR))
150 def parse_known_args( 151 self, 152 args: Sequence[str | os.PathLike[str]], 153 namespace: argparse.Namespace | None = None, 154 ) -> argparse.Namespace: 155 """Parse the known arguments at this point. 156 157 :returns: An argparse namespace object. 158 """ 159 return self.parse_known_and_unknown_args(args, namespace=namespace)[0]
Parse the known arguments at this point.
:returns: An argparse namespace object.
161 def parse_known_and_unknown_args( 162 self, 163 args: Sequence[str | os.PathLike[str]], 164 namespace: argparse.Namespace | None = None, 165 ) -> tuple[argparse.Namespace, list[str]]: 166 """Parse the known arguments at this point, and also return the 167 remaining unknown arguments. 168 169 :returns: 170 A tuple containing an argparse namespace object for the known 171 arguments, and a list of the unknown arguments. 172 """ 173 optparser = self._getparser() 174 strargs = [os.fspath(x) for x in args] 175 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.
177 def addini( 178 self, 179 name: str, 180 help: str, 181 type: Literal["string", "paths", "pathlist", "args", "linelist", "bool"] 182 | None = None, 183 default: Any = NOT_SET, 184 ) -> None: 185 """Register an ini-file option. 186 187 :param name: 188 Name of the ini-variable. 189 :param type: 190 Type of the variable. Can be: 191 192 * ``string``: a string 193 * ``bool``: a boolean 194 * ``args``: a list of strings, separated as in a shell 195 * ``linelist``: a list of strings, separated by line breaks 196 * ``paths``: a list of :class:`pathlib.Path`, separated as in a shell 197 * ``pathlist``: a list of ``py.path``, separated as in a shell 198 199 For ``paths`` and ``pathlist`` types, they are considered relative to the ini-file. 200 In case the execution is happening without an ini-file defined, 201 they will be considered relative to the current working directory (for example with ``--override-ini``). 202 203 .. versionadded:: 7.0 204 The ``paths`` variable type. 205 206 .. versionadded:: 8.1 207 Use the current working directory to resolve ``paths`` and ``pathlist`` in the absence of an ini-file. 208 209 Defaults to ``string`` if ``None`` or not passed. 210 :param default: 211 Default value if no ini-file option exists but is queried. 212 213 The value of ini-variables can be retrieved via a call to 214 :py:func:`config.getini(name) <pytest.Config.getini>`. 215 """ 216 assert type in (None, "string", "paths", "pathlist", "args", "linelist", "bool") 217 if default is NOT_SET: 218 default = get_ini_default_for_type(type) 219 220 self._inidict[name] = (help, type, default) 221 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
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.
Inherited Members
- builtins.UserWarning
- UserWarning
- builtins.BaseException
- with_traceback
- add_note
- args
27from _pytest.fixtures import FixtureDef
Warning emitted by the cache plugin in various situations.
Inherited Members
- builtins.UserWarning
- UserWarning
- builtins.BaseException
- with_traceback
- add_note
- args
41from _pytest.mark import param
Warning emitted when pytest is not able to collect a file or symbol in a module.
Inherited Members
- builtins.UserWarning
- UserWarning
- builtins.BaseException
- with_traceback
- add_note
- args
34from _pytest.logging import LogCaptureFixture
Warning emitted for configuration issues.
Inherited Members
- builtins.UserWarning
- UserWarning
- builtins.BaseException
- with_traceback
- add_note
- args
48from _pytest.outcomes import fail
Warning class for features that will be removed in a future version.
Inherited Members
- builtins.UserWarning
- UserWarning
- builtins.BaseException
- with_traceback
- add_note
- args
66from _pytest.recwarn import warns
Warning category used to denote experiments in pytest.
Use sparingly as the API might change or even be removed completely in a future version.
Inherited Members
- builtins.UserWarning
- UserWarning
- builtins.BaseException
- with_traceback
- add_note
- args
54from _pytest.pytester import Pytester
Warning class for features that will be removed in pytest 9.
Inherited Members
- builtins.UserWarning
- UserWarning
- builtins.BaseException
- with_traceback
- add_note
- args
60from _pytest.python import Module
Warning emitted when a test function is returning value other than None.
Inherited Members
- builtins.UserWarning
- UserWarning
- builtins.BaseException
- with_traceback
- add_note
- args
647@final 648class Pytester: 649 """ 650 Facilities to write tests/configuration files, execute pytest in isolation, and match 651 against expected output, perfect for black-box testing of pytest plugins. 652 653 It attempts to isolate the test run from external factors as much as possible, modifying 654 the current working directory to :attr:`path` and environment variables during initialization. 655 """ 656 657 __test__ = False 658 659 CLOSE_STDIN: Final = NOTSET 660 661 class TimeoutExpired(Exception): 662 pass 663 664 def __init__( 665 self, 666 request: FixtureRequest, 667 tmp_path_factory: TempPathFactory, 668 monkeypatch: MonkeyPatch, 669 *, 670 _ispytest: bool = False, 671 ) -> None: 672 check_ispytest(_ispytest) 673 self._request = request 674 self._mod_collections: WeakKeyDictionary[Collector, list[Item | Collector]] = ( 675 WeakKeyDictionary() 676 ) 677 if request.function: 678 name: str = request.function.__name__ 679 else: 680 name = request.node.name 681 self._name = name 682 self._path: Path = tmp_path_factory.mktemp(name, numbered=True) 683 #: A list of plugins to use with :py:meth:`parseconfig` and 684 #: :py:meth:`runpytest`. Initially this is an empty list but plugins can 685 #: be added to the list. The type of items to add to the list depends on 686 #: the method using them so refer to them for details. 687 self.plugins: list[str | _PluggyPlugin] = [] 688 self._sys_path_snapshot = SysPathsSnapshot() 689 self._sys_modules_snapshot = self.__take_sys_modules_snapshot() 690 self._request.addfinalizer(self._finalize) 691 self._method = self._request.config.getoption("--runpytest") 692 self._test_tmproot = tmp_path_factory.mktemp(f"tmp-{name}", numbered=True) 693 694 self._monkeypatch = mp = monkeypatch 695 self.chdir() 696 mp.setenv("PYTEST_DEBUG_TEMPROOT", str(self._test_tmproot)) 697 # Ensure no unexpected caching via tox. 698 mp.delenv("TOX_ENV_DIR", raising=False) 699 # Discard outer pytest options. 700 mp.delenv("PYTEST_ADDOPTS", raising=False) 701 # Ensure no user config is used. 702 tmphome = str(self.path) 703 mp.setenv("HOME", tmphome) 704 mp.setenv("USERPROFILE", tmphome) 705 # Do not use colors for inner runs by default. 706 mp.setenv("PY_COLORS", "0") 707 708 @property 709 def path(self) -> Path: 710 """Temporary directory path used to create files/run tests from, etc.""" 711 return self._path 712 713 def __repr__(self) -> str: 714 return f"<Pytester {self.path!r}>" 715 716 def _finalize(self) -> None: 717 """ 718 Clean up global state artifacts. 719 720 Some methods modify the global interpreter state and this tries to 721 clean this up. It does not remove the temporary directory however so 722 it can be looked at after the test run has finished. 723 """ 724 self._sys_modules_snapshot.restore() 725 self._sys_path_snapshot.restore() 726 727 def __take_sys_modules_snapshot(self) -> SysModulesSnapshot: 728 # Some zope modules used by twisted-related tests keep internal state 729 # and can't be deleted; we had some trouble in the past with 730 # `zope.interface` for example. 731 # 732 # Preserve readline due to https://bugs.python.org/issue41033. 733 # pexpect issues a SIGWINCH. 734 def preserve_module(name): 735 return name.startswith(("zope", "readline")) 736 737 return SysModulesSnapshot(preserve=preserve_module) 738 739 def make_hook_recorder(self, pluginmanager: PytestPluginManager) -> HookRecorder: 740 """Create a new :class:`HookRecorder` for a :class:`PytestPluginManager`.""" 741 pluginmanager.reprec = reprec = HookRecorder(pluginmanager, _ispytest=True) # type: ignore[attr-defined] 742 self._request.addfinalizer(reprec.finish_recording) 743 return reprec 744 745 def chdir(self) -> None: 746 """Cd into the temporary directory. 747 748 This is done automatically upon instantiation. 749 """ 750 self._monkeypatch.chdir(self.path) 751 752 def _makefile( 753 self, 754 ext: str, 755 lines: Sequence[Any | bytes], 756 files: dict[str, str], 757 encoding: str = "utf-8", 758 ) -> Path: 759 items = list(files.items()) 760 761 if ext is None: 762 raise TypeError("ext must not be None") 763 764 if ext and not ext.startswith("."): 765 raise ValueError( 766 f"pytester.makefile expects a file extension, try .{ext} instead of {ext}" 767 ) 768 769 def to_text(s: Any | bytes) -> str: 770 return s.decode(encoding) if isinstance(s, bytes) else str(s) 771 772 if lines: 773 source = "\n".join(to_text(x) for x in lines) 774 basename = self._name 775 items.insert(0, (basename, source)) 776 777 ret = None 778 for basename, value in items: 779 p = self.path.joinpath(basename).with_suffix(ext) 780 p.parent.mkdir(parents=True, exist_ok=True) 781 source_ = Source(value) 782 source = "\n".join(to_text(line) for line in source_.lines) 783 p.write_text(source.strip(), encoding=encoding) 784 if ret is None: 785 ret = p 786 assert ret is not None 787 return ret 788 789 def makefile(self, ext: str, *args: str, **kwargs: str) -> Path: 790 r"""Create new text file(s) in the test directory. 791 792 :param ext: 793 The extension the file(s) should use, including the dot, e.g. `.py`. 794 :param args: 795 All args are treated as strings and joined using newlines. 796 The result is written as contents to the file. The name of the 797 file is based on the test function requesting this fixture. 798 :param kwargs: 799 Each keyword is the name of a file, while the value of it will 800 be written as contents of the file. 801 :returns: 802 The first created file. 803 804 Examples: 805 806 .. code-block:: python 807 808 pytester.makefile(".txt", "line1", "line2") 809 810 pytester.makefile(".ini", pytest="[pytest]\naddopts=-rs\n") 811 812 To create binary files, use :meth:`pathlib.Path.write_bytes` directly: 813 814 .. code-block:: python 815 816 filename = pytester.path.joinpath("foo.bin") 817 filename.write_bytes(b"...") 818 """ 819 return self._makefile(ext, args, kwargs) 820 821 def makeconftest(self, source: str) -> Path: 822 """Write a conftest.py file. 823 824 :param source: The contents. 825 :returns: The conftest.py file. 826 """ 827 return self.makepyfile(conftest=source) 828 829 def makeini(self, source: str) -> Path: 830 """Write a tox.ini file. 831 832 :param source: The contents. 833 :returns: The tox.ini file. 834 """ 835 return self.makefile(".ini", tox=source) 836 837 def getinicfg(self, source: str) -> SectionWrapper: 838 """Return the pytest section from the tox.ini config file.""" 839 p = self.makeini(source) 840 return IniConfig(str(p))["pytest"] 841 842 def makepyprojecttoml(self, source: str) -> Path: 843 """Write a pyproject.toml file. 844 845 :param source: The contents. 846 :returns: The pyproject.ini file. 847 848 .. versionadded:: 6.0 849 """ 850 return self.makefile(".toml", pyproject=source) 851 852 def makepyfile(self, *args, **kwargs) -> Path: 853 r"""Shortcut for .makefile() with a .py extension. 854 855 Defaults to the test name with a '.py' extension, e.g test_foobar.py, overwriting 856 existing files. 857 858 Examples: 859 860 .. code-block:: python 861 862 def test_something(pytester): 863 # Initial file is created test_something.py. 864 pytester.makepyfile("foobar") 865 # To create multiple files, pass kwargs accordingly. 866 pytester.makepyfile(custom="foobar") 867 # At this point, both 'test_something.py' & 'custom.py' exist in the test directory. 868 869 """ 870 return self._makefile(".py", args, kwargs) 871 872 def maketxtfile(self, *args, **kwargs) -> Path: 873 r"""Shortcut for .makefile() with a .txt extension. 874 875 Defaults to the test name with a '.txt' extension, e.g test_foobar.txt, overwriting 876 existing files. 877 878 Examples: 879 880 .. code-block:: python 881 882 def test_something(pytester): 883 # Initial file is created test_something.txt. 884 pytester.maketxtfile("foobar") 885 # To create multiple files, pass kwargs accordingly. 886 pytester.maketxtfile(custom="foobar") 887 # At this point, both 'test_something.txt' & 'custom.txt' exist in the test directory. 888 889 """ 890 return self._makefile(".txt", args, kwargs) 891 892 def syspathinsert(self, path: str | os.PathLike[str] | None = None) -> None: 893 """Prepend a directory to sys.path, defaults to :attr:`path`. 894 895 This is undone automatically when this object dies at the end of each 896 test. 897 898 :param path: 899 The path. 900 """ 901 if path is None: 902 path = self.path 903 904 self._monkeypatch.syspath_prepend(str(path)) 905 906 def mkdir(self, name: str | os.PathLike[str]) -> Path: 907 """Create a new (sub)directory. 908 909 :param name: 910 The name of the directory, relative to the pytester path. 911 :returns: 912 The created directory. 913 :rtype: pathlib.Path 914 """ 915 p = self.path / name 916 p.mkdir() 917 return p 918 919 def mkpydir(self, name: str | os.PathLike[str]) -> Path: 920 """Create a new python package. 921 922 This creates a (sub)directory with an empty ``__init__.py`` file so it 923 gets recognised as a Python package. 924 """ 925 p = self.path / name 926 p.mkdir() 927 p.joinpath("__init__.py").touch() 928 return p 929 930 def copy_example(self, name: str | None = None) -> Path: 931 """Copy file from project's directory into the testdir. 932 933 :param name: 934 The name of the file to copy. 935 :return: 936 Path to the copied directory (inside ``self.path``). 937 :rtype: pathlib.Path 938 """ 939 example_dir_ = self._request.config.getini("pytester_example_dir") 940 if example_dir_ is None: 941 raise ValueError("pytester_example_dir is unset, can't copy examples") 942 example_dir: Path = self._request.config.rootpath / example_dir_ 943 944 for extra_element in self._request.node.iter_markers("pytester_example_path"): 945 assert extra_element.args 946 example_dir = example_dir.joinpath(*extra_element.args) 947 948 if name is None: 949 func_name = self._name 950 maybe_dir = example_dir / func_name 951 maybe_file = example_dir / (func_name + ".py") 952 953 if maybe_dir.is_dir(): 954 example_path = maybe_dir 955 elif maybe_file.is_file(): 956 example_path = maybe_file 957 else: 958 raise LookupError( 959 f"{func_name} can't be found as module or package in {example_dir}" 960 ) 961 else: 962 example_path = example_dir.joinpath(name) 963 964 if example_path.is_dir() and not example_path.joinpath("__init__.py").is_file(): 965 shutil.copytree(example_path, self.path, symlinks=True, dirs_exist_ok=True) 966 return self.path 967 elif example_path.is_file(): 968 result = self.path.joinpath(example_path.name) 969 shutil.copy(example_path, result) 970 return result 971 else: 972 raise LookupError( 973 f'example "{example_path}" is not found as a file or directory' 974 ) 975 976 def getnode(self, config: Config, arg: str | os.PathLike[str]) -> Collector | Item: 977 """Get the collection node of a file. 978 979 :param config: 980 A pytest config. 981 See :py:meth:`parseconfig` and :py:meth:`parseconfigure` for creating it. 982 :param arg: 983 Path to the file. 984 :returns: 985 The node. 986 """ 987 session = Session.from_config(config) 988 assert "::" not in str(arg) 989 p = Path(os.path.abspath(arg)) 990 config.hook.pytest_sessionstart(session=session) 991 res = session.perform_collect([str(p)], genitems=False)[0] 992 config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) 993 return res 994 995 def getpathnode(self, path: str | os.PathLike[str]) -> Collector | Item: 996 """Return the collection node of a file. 997 998 This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to 999 create the (configured) pytest Config instance. 1000 1001 :param path: 1002 Path to the file. 1003 :returns: 1004 The node. 1005 """ 1006 path = Path(path) 1007 config = self.parseconfigure(path) 1008 session = Session.from_config(config) 1009 x = bestrelpath(session.path, path) 1010 config.hook.pytest_sessionstart(session=session) 1011 res = session.perform_collect([x], genitems=False)[0] 1012 config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) 1013 return res 1014 1015 def genitems(self, colitems: Sequence[Item | Collector]) -> list[Item]: 1016 """Generate all test items from a collection node. 1017 1018 This recurses into the collection node and returns a list of all the 1019 test items contained within. 1020 1021 :param colitems: 1022 The collection nodes. 1023 :returns: 1024 The collected items. 1025 """ 1026 session = colitems[0].session 1027 result: list[Item] = [] 1028 for colitem in colitems: 1029 result.extend(session.genitems(colitem)) 1030 return result 1031 1032 def runitem(self, source: str) -> Any: 1033 """Run the "test_func" Item. 1034 1035 The calling test instance (class containing the test method) must 1036 provide a ``.getrunner()`` method which should return a runner which 1037 can run the test protocol for a single item, e.g. 1038 ``_pytest.runner.runtestprotocol``. 1039 """ 1040 # used from runner functional tests 1041 item = self.getitem(source) 1042 # the test class where we are called from wants to provide the runner 1043 testclassinstance = self._request.instance 1044 runner = testclassinstance.getrunner() 1045 return runner(item) 1046 1047 def inline_runsource(self, source: str, *cmdlineargs) -> HookRecorder: 1048 """Run a test module in process using ``pytest.main()``. 1049 1050 This run writes "source" into a temporary file and runs 1051 ``pytest.main()`` on it, returning a :py:class:`HookRecorder` instance 1052 for the result. 1053 1054 :param source: The source code of the test module. 1055 :param cmdlineargs: Any extra command line arguments to use. 1056 """ 1057 p = self.makepyfile(source) 1058 values = [*list(cmdlineargs), p] 1059 return self.inline_run(*values) 1060 1061 def inline_genitems(self, *args) -> tuple[list[Item], HookRecorder]: 1062 """Run ``pytest.main(['--collect-only'])`` in-process. 1063 1064 Runs the :py:func:`pytest.main` function to run all of pytest inside 1065 the test process itself like :py:meth:`inline_run`, but returns a 1066 tuple of the collected items and a :py:class:`HookRecorder` instance. 1067 """ 1068 rec = self.inline_run("--collect-only", *args) 1069 items = [x.item for x in rec.getcalls("pytest_itemcollected")] 1070 return items, rec 1071 1072 def inline_run( 1073 self, 1074 *args: str | os.PathLike[str], 1075 plugins=(), 1076 no_reraise_ctrlc: bool = False, 1077 ) -> HookRecorder: 1078 """Run ``pytest.main()`` in-process, returning a HookRecorder. 1079 1080 Runs the :py:func:`pytest.main` function to run all of pytest inside 1081 the test process itself. This means it can return a 1082 :py:class:`HookRecorder` instance which gives more detailed results 1083 from that run than can be done by matching stdout/stderr from 1084 :py:meth:`runpytest`. 1085 1086 :param args: 1087 Command line arguments to pass to :py:func:`pytest.main`. 1088 :param plugins: 1089 Extra plugin instances the ``pytest.main()`` instance should use. 1090 :param no_reraise_ctrlc: 1091 Typically we reraise keyboard interrupts from the child run. If 1092 True, the KeyboardInterrupt exception is captured. 1093 """ 1094 # (maybe a cpython bug?) the importlib cache sometimes isn't updated 1095 # properly between file creation and inline_run (especially if imports 1096 # are interspersed with file creation) 1097 importlib.invalidate_caches() 1098 1099 plugins = list(plugins) 1100 finalizers = [] 1101 try: 1102 # Any sys.module or sys.path changes done while running pytest 1103 # inline should be reverted after the test run completes to avoid 1104 # clashing with later inline tests run within the same pytest test, 1105 # e.g. just because they use matching test module names. 1106 finalizers.append(self.__take_sys_modules_snapshot().restore) 1107 finalizers.append(SysPathsSnapshot().restore) 1108 1109 # Important note: 1110 # - our tests should not leave any other references/registrations 1111 # laying around other than possibly loaded test modules 1112 # referenced from sys.modules, as nothing will clean those up 1113 # automatically 1114 1115 rec = [] 1116 1117 class Collect: 1118 def pytest_configure(x, config: Config) -> None: 1119 rec.append(self.make_hook_recorder(config.pluginmanager)) 1120 1121 plugins.append(Collect()) 1122 ret = main([str(x) for x in args], plugins=plugins) 1123 if len(rec) == 1: 1124 reprec = rec.pop() 1125 else: 1126 1127 class reprec: # type: ignore 1128 pass 1129 1130 reprec.ret = ret 1131 1132 # Typically we reraise keyboard interrupts from the child run 1133 # because it's our user requesting interruption of the testing. 1134 if ret == ExitCode.INTERRUPTED and not no_reraise_ctrlc: 1135 calls = reprec.getcalls("pytest_keyboard_interrupt") 1136 if calls and calls[-1].excinfo.type == KeyboardInterrupt: 1137 raise KeyboardInterrupt() 1138 return reprec 1139 finally: 1140 for finalizer in finalizers: 1141 finalizer() 1142 1143 def runpytest_inprocess( 1144 self, *args: str | os.PathLike[str], **kwargs: Any 1145 ) -> RunResult: 1146 """Return result of running pytest in-process, providing a similar 1147 interface to what self.runpytest() provides.""" 1148 syspathinsert = kwargs.pop("syspathinsert", False) 1149 1150 if syspathinsert: 1151 self.syspathinsert() 1152 now = timing.time() 1153 capture = _get_multicapture("sys") 1154 capture.start_capturing() 1155 try: 1156 try: 1157 reprec = self.inline_run(*args, **kwargs) 1158 except SystemExit as e: 1159 ret = e.args[0] 1160 try: 1161 ret = ExitCode(e.args[0]) 1162 except ValueError: 1163 pass 1164 1165 class reprec: # type: ignore 1166 ret = ret 1167 1168 except Exception: 1169 traceback.print_exc() 1170 1171 class reprec: # type: ignore 1172 ret = ExitCode(3) 1173 1174 finally: 1175 out, err = capture.readouterr() 1176 capture.stop_capturing() 1177 sys.stdout.write(out) 1178 sys.stderr.write(err) 1179 1180 assert reprec.ret is not None 1181 res = RunResult( 1182 reprec.ret, out.splitlines(), err.splitlines(), timing.time() - now 1183 ) 1184 res.reprec = reprec # type: ignore 1185 return res 1186 1187 def runpytest(self, *args: str | os.PathLike[str], **kwargs: Any) -> RunResult: 1188 """Run pytest inline or in a subprocess, depending on the command line 1189 option "--runpytest" and return a :py:class:`~pytest.RunResult`.""" 1190 new_args = self._ensure_basetemp(args) 1191 if self._method == "inprocess": 1192 return self.runpytest_inprocess(*new_args, **kwargs) 1193 elif self._method == "subprocess": 1194 return self.runpytest_subprocess(*new_args, **kwargs) 1195 raise RuntimeError(f"Unrecognized runpytest option: {self._method}") 1196 1197 def _ensure_basetemp( 1198 self, args: Sequence[str | os.PathLike[str]] 1199 ) -> list[str | os.PathLike[str]]: 1200 new_args = list(args) 1201 for x in new_args: 1202 if str(x).startswith("--basetemp"): 1203 break 1204 else: 1205 new_args.append( 1206 "--basetemp={}".format(self.path.parent.joinpath("basetemp")) 1207 ) 1208 return new_args 1209 1210 def parseconfig(self, *args: str | os.PathLike[str]) -> Config: 1211 """Return a new pytest :class:`pytest.Config` instance from given 1212 commandline args. 1213 1214 This invokes the pytest bootstrapping code in _pytest.config to create a 1215 new :py:class:`pytest.PytestPluginManager` and call the 1216 :hook:`pytest_cmdline_parse` hook to create a new :class:`pytest.Config` 1217 instance. 1218 1219 If :attr:`plugins` has been populated they should be plugin modules 1220 to be registered with the plugin manager. 1221 """ 1222 import _pytest.config 1223 1224 new_args = self._ensure_basetemp(args) 1225 new_args = [str(x) for x in new_args] 1226 1227 config = _pytest.config._prepareconfig(new_args, self.plugins) # type: ignore[arg-type] 1228 # we don't know what the test will do with this half-setup config 1229 # object and thus we make sure it gets unconfigured properly in any 1230 # case (otherwise capturing could still be active, for example) 1231 self._request.addfinalizer(config._ensure_unconfigure) 1232 return config 1233 1234 def parseconfigure(self, *args: str | os.PathLike[str]) -> Config: 1235 """Return a new pytest configured Config instance. 1236 1237 Returns a new :py:class:`pytest.Config` instance like 1238 :py:meth:`parseconfig`, but also calls the :hook:`pytest_configure` 1239 hook. 1240 """ 1241 config = self.parseconfig(*args) 1242 config._do_configure() 1243 return config 1244 1245 def getitem( 1246 self, source: str | os.PathLike[str], funcname: str = "test_func" 1247 ) -> Item: 1248 """Return the test item for a test function. 1249 1250 Writes the source to a python file and runs pytest's collection on 1251 the resulting module, returning the test item for the requested 1252 function name. 1253 1254 :param source: 1255 The module source. 1256 :param funcname: 1257 The name of the test function for which to return a test item. 1258 :returns: 1259 The test item. 1260 """ 1261 items = self.getitems(source) 1262 for item in items: 1263 if item.name == funcname: 1264 return item 1265 assert 0, f"{funcname!r} item not found in module:\n{source}\nitems: {items}" 1266 1267 def getitems(self, source: str | os.PathLike[str]) -> list[Item]: 1268 """Return all test items collected from the module. 1269 1270 Writes the source to a Python file and runs pytest's collection on 1271 the resulting module, returning all test items contained within. 1272 """ 1273 modcol = self.getmodulecol(source) 1274 return self.genitems([modcol]) 1275 1276 def getmodulecol( 1277 self, 1278 source: str | os.PathLike[str], 1279 configargs=(), 1280 *, 1281 withinit: bool = False, 1282 ): 1283 """Return the module collection node for ``source``. 1284 1285 Writes ``source`` to a file using :py:meth:`makepyfile` and then 1286 runs the pytest collection on it, returning the collection node for the 1287 test module. 1288 1289 :param source: 1290 The source code of the module to collect. 1291 1292 :param configargs: 1293 Any extra arguments to pass to :py:meth:`parseconfigure`. 1294 1295 :param withinit: 1296 Whether to also write an ``__init__.py`` file to the same 1297 directory to ensure it is a package. 1298 """ 1299 if isinstance(source, os.PathLike): 1300 path = self.path.joinpath(source) 1301 assert not withinit, "not supported for paths" 1302 else: 1303 kw = {self._name: str(source)} 1304 path = self.makepyfile(**kw) 1305 if withinit: 1306 self.makepyfile(__init__="#") 1307 self.config = config = self.parseconfigure(path, *configargs) 1308 return self.getnode(config, path) 1309 1310 def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None: 1311 """Return the collection node for name from the module collection. 1312 1313 Searches a module collection node for a collection node matching the 1314 given name. 1315 1316 :param modcol: A module collection node; see :py:meth:`getmodulecol`. 1317 :param name: The name of the node to return. 1318 """ 1319 if modcol not in self._mod_collections: 1320 self._mod_collections[modcol] = list(modcol.collect()) 1321 for colitem in self._mod_collections[modcol]: 1322 if colitem.name == name: 1323 return colitem 1324 return None 1325 1326 def popen( 1327 self, 1328 cmdargs: Sequence[str | os.PathLike[str]], 1329 stdout: int | TextIO = subprocess.PIPE, 1330 stderr: int | TextIO = subprocess.PIPE, 1331 stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, 1332 **kw, 1333 ): 1334 """Invoke :py:class:`subprocess.Popen`. 1335 1336 Calls :py:class:`subprocess.Popen` making sure the current working 1337 directory is in ``PYTHONPATH``. 1338 1339 You probably want to use :py:meth:`run` instead. 1340 """ 1341 env = os.environ.copy() 1342 env["PYTHONPATH"] = os.pathsep.join( 1343 filter(None, [os.getcwd(), env.get("PYTHONPATH", "")]) 1344 ) 1345 kw["env"] = env 1346 1347 if stdin is self.CLOSE_STDIN: 1348 kw["stdin"] = subprocess.PIPE 1349 elif isinstance(stdin, bytes): 1350 kw["stdin"] = subprocess.PIPE 1351 else: 1352 kw["stdin"] = stdin 1353 1354 popen = subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw) 1355 if stdin is self.CLOSE_STDIN: 1356 assert popen.stdin is not None 1357 popen.stdin.close() 1358 elif isinstance(stdin, bytes): 1359 assert popen.stdin is not None 1360 popen.stdin.write(stdin) 1361 1362 return popen 1363 1364 def run( 1365 self, 1366 *cmdargs: str | os.PathLike[str], 1367 timeout: float | None = None, 1368 stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, 1369 ) -> RunResult: 1370 """Run a command with arguments. 1371 1372 Run a process using :py:class:`subprocess.Popen` saving the stdout and 1373 stderr. 1374 1375 :param cmdargs: 1376 The sequence of arguments to pass to :py:class:`subprocess.Popen`, 1377 with path-like objects being converted to :py:class:`str` 1378 automatically. 1379 :param timeout: 1380 The period in seconds after which to timeout and raise 1381 :py:class:`Pytester.TimeoutExpired`. 1382 :param stdin: 1383 Optional standard input. 1384 1385 - If it is ``CLOSE_STDIN`` (Default), then this method calls 1386 :py:class:`subprocess.Popen` with ``stdin=subprocess.PIPE``, and 1387 the standard input is closed immediately after the new command is 1388 started. 1389 1390 - If it is of type :py:class:`bytes`, these bytes are sent to the 1391 standard input of the command. 1392 1393 - Otherwise, it is passed through to :py:class:`subprocess.Popen`. 1394 For further information in this case, consult the document of the 1395 ``stdin`` parameter in :py:class:`subprocess.Popen`. 1396 :type stdin: _pytest.compat.NotSetType | bytes | IO[Any] | int 1397 :returns: 1398 The result. 1399 1400 """ 1401 __tracebackhide__ = True 1402 1403 cmdargs = tuple(os.fspath(arg) for arg in cmdargs) 1404 p1 = self.path.joinpath("stdout") 1405 p2 = self.path.joinpath("stderr") 1406 print("running:", *cmdargs) 1407 print(" in:", Path.cwd()) 1408 1409 with p1.open("w", encoding="utf8") as f1, p2.open("w", encoding="utf8") as f2: 1410 now = timing.time() 1411 popen = self.popen( 1412 cmdargs, 1413 stdin=stdin, 1414 stdout=f1, 1415 stderr=f2, 1416 close_fds=(sys.platform != "win32"), 1417 ) 1418 if popen.stdin is not None: 1419 popen.stdin.close() 1420 1421 def handle_timeout() -> None: 1422 __tracebackhide__ = True 1423 1424 timeout_message = f"{timeout} second timeout expired running: {cmdargs}" 1425 1426 popen.kill() 1427 popen.wait() 1428 raise self.TimeoutExpired(timeout_message) 1429 1430 if timeout is None: 1431 ret = popen.wait() 1432 else: 1433 try: 1434 ret = popen.wait(timeout) 1435 except subprocess.TimeoutExpired: 1436 handle_timeout() 1437 1438 with p1.open(encoding="utf8") as f1, p2.open(encoding="utf8") as f2: 1439 out = f1.read().splitlines() 1440 err = f2.read().splitlines() 1441 1442 self._dump_lines(out, sys.stdout) 1443 self._dump_lines(err, sys.stderr) 1444 1445 with contextlib.suppress(ValueError): 1446 ret = ExitCode(ret) 1447 return RunResult(ret, out, err, timing.time() - now) 1448 1449 def _dump_lines(self, lines, fp): 1450 try: 1451 for line in lines: 1452 print(line, file=fp) 1453 except UnicodeEncodeError: 1454 print(f"couldn't print to {fp} because of encoding") 1455 1456 def _getpytestargs(self) -> tuple[str, ...]: 1457 return sys.executable, "-mpytest" 1458 1459 def runpython(self, script: os.PathLike[str]) -> RunResult: 1460 """Run a python script using sys.executable as interpreter.""" 1461 return self.run(sys.executable, script) 1462 1463 def runpython_c(self, command: str) -> RunResult: 1464 """Run ``python -c "command"``.""" 1465 return self.run(sys.executable, "-c", command) 1466 1467 def runpytest_subprocess( 1468 self, *args: str | os.PathLike[str], timeout: float | None = None 1469 ) -> RunResult: 1470 """Run pytest as a subprocess with given arguments. 1471 1472 Any plugins added to the :py:attr:`plugins` list will be added using the 1473 ``-p`` command line option. Additionally ``--basetemp`` is used to put 1474 any temporary files and directories in a numbered directory prefixed 1475 with "runpytest-" to not conflict with the normal numbered pytest 1476 location for temporary files and directories. 1477 1478 :param args: 1479 The sequence of arguments to pass to the pytest subprocess. 1480 :param timeout: 1481 The period in seconds after which to timeout and raise 1482 :py:class:`Pytester.TimeoutExpired`. 1483 :returns: 1484 The result. 1485 """ 1486 __tracebackhide__ = True 1487 p = make_numbered_dir(root=self.path, prefix="runpytest-", mode=0o700) 1488 args = (f"--basetemp={p}", *args) 1489 plugins = [x for x in self.plugins if isinstance(x, str)] 1490 if plugins: 1491 args = ("-p", plugins[0], *args) 1492 args = self._getpytestargs() + args 1493 return self.run(*args, timeout=timeout) 1494 1495 def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn: 1496 """Run pytest using pexpect. 1497 1498 This makes sure to use the right pytest and sets up the temporary 1499 directory locations. 1500 1501 The pexpect child is returned. 1502 """ 1503 basetemp = self.path / "temp-pexpect" 1504 basetemp.mkdir(mode=0o700) 1505 invoke = " ".join(map(str, self._getpytestargs())) 1506 cmd = f"{invoke} --basetemp={basetemp} {string}" 1507 return self.spawn(cmd, expect_timeout=expect_timeout) 1508 1509 def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn: 1510 """Run a command using pexpect. 1511 1512 The pexpect child is returned. 1513 """ 1514 pexpect = importorskip("pexpect", "3.0") 1515 if hasattr(sys, "pypy_version_info") and "64" in platform.machine(): 1516 skip("pypy-64 bit not supported") 1517 if not hasattr(pexpect, "spawn"): 1518 skip("pexpect.spawn not available") 1519 logfile = self.path.joinpath("spawn.out").open("wb") 1520 1521 child = pexpect.spawn(cmd, logfile=logfile, timeout=expect_timeout) 1522 self._request.addfinalizer(logfile.close) 1523 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.
664 def __init__( 665 self, 666 request: FixtureRequest, 667 tmp_path_factory: TempPathFactory, 668 monkeypatch: MonkeyPatch, 669 *, 670 _ispytest: bool = False, 671 ) -> None: 672 check_ispytest(_ispytest) 673 self._request = request 674 self._mod_collections: WeakKeyDictionary[Collector, list[Item | Collector]] = ( 675 WeakKeyDictionary() 676 ) 677 if request.function: 678 name: str = request.function.__name__ 679 else: 680 name = request.node.name 681 self._name = name 682 self._path: Path = tmp_path_factory.mktemp(name, numbered=True) 683 #: A list of plugins to use with :py:meth:`parseconfig` and 684 #: :py:meth:`runpytest`. Initially this is an empty list but plugins can 685 #: be added to the list. The type of items to add to the list depends on 686 #: the method using them so refer to them for details. 687 self.plugins: list[str | _PluggyPlugin] = [] 688 self._sys_path_snapshot = SysPathsSnapshot() 689 self._sys_modules_snapshot = self.__take_sys_modules_snapshot() 690 self._request.addfinalizer(self._finalize) 691 self._method = self._request.config.getoption("--runpytest") 692 self._test_tmproot = tmp_path_factory.mktemp(f"tmp-{name}", numbered=True) 693 694 self._monkeypatch = mp = monkeypatch 695 self.chdir() 696 mp.setenv("PYTEST_DEBUG_TEMPROOT", str(self._test_tmproot)) 697 # Ensure no unexpected caching via tox. 698 mp.delenv("TOX_ENV_DIR", raising=False) 699 # Discard outer pytest options. 700 mp.delenv("PYTEST_ADDOPTS", raising=False) 701 # Ensure no user config is used. 702 tmphome = str(self.path) 703 mp.setenv("HOME", tmphome) 704 mp.setenv("USERPROFILE", tmphome) 705 # Do not use colors for inner runs by default. 706 mp.setenv("PY_COLORS", "0")
708 @property 709 def path(self) -> Path: 710 """Temporary directory path used to create files/run tests from, etc.""" 711 return self._path
Temporary directory path used to create files/run tests from, etc.
739 def make_hook_recorder(self, pluginmanager: PytestPluginManager) -> HookRecorder: 740 """Create a new :class:`HookRecorder` for a :class:`PytestPluginManager`.""" 741 pluginmanager.reprec = reprec = HookRecorder(pluginmanager, _ispytest=True) # type: ignore[attr-defined] 742 self._request.addfinalizer(reprec.finish_recording) 743 return reprec
Create a new HookRecorder
for a PytestPluginManager
.
745 def chdir(self) -> None: 746 """Cd into the temporary directory. 747 748 This is done automatically upon instantiation. 749 """ 750 self._monkeypatch.chdir(self.path)
Cd into the temporary directory.
This is done automatically upon instantiation.
789 def makefile(self, ext: str, *args: str, **kwargs: str) -> Path: 790 r"""Create new text file(s) in the test directory. 791 792 :param ext: 793 The extension the file(s) should use, including the dot, e.g. `.py`. 794 :param args: 795 All args are treated as strings and joined using newlines. 796 The result is written as contents to the file. The name of the 797 file is based on the test function requesting this fixture. 798 :param kwargs: 799 Each keyword is the name of a file, while the value of it will 800 be written as contents of the file. 801 :returns: 802 The first created file. 803 804 Examples: 805 806 .. code-block:: python 807 808 pytester.makefile(".txt", "line1", "line2") 809 810 pytester.makefile(".ini", pytest="[pytest]\naddopts=-rs\n") 811 812 To create binary files, use :meth:`pathlib.Path.write_bytes` directly: 813 814 .. code-block:: python 815 816 filename = pytester.path.joinpath("foo.bin") 817 filename.write_bytes(b"...") 818 """ 819 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"...")
821 def makeconftest(self, source: str) -> Path: 822 """Write a conftest.py file. 823 824 :param source: The contents. 825 :returns: The conftest.py file. 826 """ 827 return self.makepyfile(conftest=source)
Write a conftest.py file.
Parameters
- source: The contents. :returns: The conftest.py file.
829 def makeini(self, source: str) -> Path: 830 """Write a tox.ini file. 831 832 :param source: The contents. 833 :returns: The tox.ini file. 834 """ 835 return self.makefile(".ini", tox=source)
Write a tox.ini file.
Parameters
- source: The contents. :returns: The tox.ini file.
837 def getinicfg(self, source: str) -> SectionWrapper: 838 """Return the pytest section from the tox.ini config file.""" 839 p = self.makeini(source) 840 return IniConfig(str(p))["pytest"]
Return the pytest section from the tox.ini config file.
842 def makepyprojecttoml(self, source: str) -> Path: 843 """Write a pyproject.toml file. 844 845 :param source: The contents. 846 :returns: The pyproject.ini file. 847 848 .. versionadded:: 6.0 849 """ 850 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.
852 def makepyfile(self, *args, **kwargs) -> Path: 853 r"""Shortcut for .makefile() with a .py extension. 854 855 Defaults to the test name with a '.py' extension, e.g test_foobar.py, overwriting 856 existing files. 857 858 Examples: 859 860 .. code-block:: python 861 862 def test_something(pytester): 863 # Initial file is created test_something.py. 864 pytester.makepyfile("foobar") 865 # To create multiple files, pass kwargs accordingly. 866 pytester.makepyfile(custom="foobar") 867 # At this point, both 'test_something.py' & 'custom.py' exist in the test directory. 868 869 """ 870 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.
872 def maketxtfile(self, *args, **kwargs) -> Path: 873 r"""Shortcut for .makefile() with a .txt extension. 874 875 Defaults to the test name with a '.txt' extension, e.g test_foobar.txt, overwriting 876 existing files. 877 878 Examples: 879 880 .. code-block:: python 881 882 def test_something(pytester): 883 # Initial file is created test_something.txt. 884 pytester.maketxtfile("foobar") 885 # To create multiple files, pass kwargs accordingly. 886 pytester.maketxtfile(custom="foobar") 887 # At this point, both 'test_something.txt' & 'custom.txt' exist in the test directory. 888 889 """ 890 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.
892 def syspathinsert(self, path: str | os.PathLike[str] | None = None) -> None: 893 """Prepend a directory to sys.path, defaults to :attr:`path`. 894 895 This is undone automatically when this object dies at the end of each 896 test. 897 898 :param path: 899 The path. 900 """ 901 if path is None: 902 path = self.path 903 904 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.
906 def mkdir(self, name: str | os.PathLike[str]) -> Path: 907 """Create a new (sub)directory. 908 909 :param name: 910 The name of the directory, relative to the pytester path. 911 :returns: 912 The created directory. 913 :rtype: pathlib.Path 914 """ 915 p = self.path / name 916 p.mkdir() 917 return p
Create a new (sub)directory.
Parameters
- name: The name of the directory, relative to the pytester path. :returns: The created directory.
919 def mkpydir(self, name: str | os.PathLike[str]) -> Path: 920 """Create a new python package. 921 922 This creates a (sub)directory with an empty ``__init__.py`` file so it 923 gets recognised as a Python package. 924 """ 925 p = self.path / name 926 p.mkdir() 927 p.joinpath("__init__.py").touch() 928 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.
930 def copy_example(self, name: str | None = None) -> Path: 931 """Copy file from project's directory into the testdir. 932 933 :param name: 934 The name of the file to copy. 935 :return: 936 Path to the copied directory (inside ``self.path``). 937 :rtype: pathlib.Path 938 """ 939 example_dir_ = self._request.config.getini("pytester_example_dir") 940 if example_dir_ is None: 941 raise ValueError("pytester_example_dir is unset, can't copy examples") 942 example_dir: Path = self._request.config.rootpath / example_dir_ 943 944 for extra_element in self._request.node.iter_markers("pytester_example_path"): 945 assert extra_element.args 946 example_dir = example_dir.joinpath(*extra_element.args) 947 948 if name is None: 949 func_name = self._name 950 maybe_dir = example_dir / func_name 951 maybe_file = example_dir / (func_name + ".py") 952 953 if maybe_dir.is_dir(): 954 example_path = maybe_dir 955 elif maybe_file.is_file(): 956 example_path = maybe_file 957 else: 958 raise LookupError( 959 f"{func_name} can't be found as module or package in {example_dir}" 960 ) 961 else: 962 example_path = example_dir.joinpath(name) 963 964 if example_path.is_dir() and not example_path.joinpath("__init__.py").is_file(): 965 shutil.copytree(example_path, self.path, symlinks=True, dirs_exist_ok=True) 966 return self.path 967 elif example_path.is_file(): 968 result = self.path.joinpath(example_path.name) 969 shutil.copy(example_path, result) 970 return result 971 else: 972 raise LookupError( 973 f'example "{example_path}" is not found as a file or directory' 974 )
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``).
976 def getnode(self, config: Config, arg: str | os.PathLike[str]) -> Collector | Item: 977 """Get the collection node of a file. 978 979 :param config: 980 A pytest config. 981 See :py:meth:`parseconfig` and :py:meth:`parseconfigure` for creating it. 982 :param arg: 983 Path to the file. 984 :returns: 985 The node. 986 """ 987 session = Session.from_config(config) 988 assert "::" not in str(arg) 989 p = Path(os.path.abspath(arg)) 990 config.hook.pytest_sessionstart(session=session) 991 res = session.perform_collect([str(p)], genitems=False)[0] 992 config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) 993 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.
995 def getpathnode(self, path: str | os.PathLike[str]) -> Collector | Item: 996 """Return the collection node of a file. 997 998 This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to 999 create the (configured) pytest Config instance. 1000 1001 :param path: 1002 Path to the file. 1003 :returns: 1004 The node. 1005 """ 1006 path = Path(path) 1007 config = self.parseconfigure(path) 1008 session = Session.from_config(config) 1009 x = bestrelpath(session.path, path) 1010 config.hook.pytest_sessionstart(session=session) 1011 res = session.perform_collect([x], genitems=False)[0] 1012 config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) 1013 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.
1015 def genitems(self, colitems: Sequence[Item | Collector]) -> list[Item]: 1016 """Generate all test items from a collection node. 1017 1018 This recurses into the collection node and returns a list of all the 1019 test items contained within. 1020 1021 :param colitems: 1022 The collection nodes. 1023 :returns: 1024 The collected items. 1025 """ 1026 session = colitems[0].session 1027 result: list[Item] = [] 1028 for colitem in colitems: 1029 result.extend(session.genitems(colitem)) 1030 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.
1032 def runitem(self, source: str) -> Any: 1033 """Run the "test_func" Item. 1034 1035 The calling test instance (class containing the test method) must 1036 provide a ``.getrunner()`` method which should return a runner which 1037 can run the test protocol for a single item, e.g. 1038 ``_pytest.runner.runtestprotocol``. 1039 """ 1040 # used from runner functional tests 1041 item = self.getitem(source) 1042 # the test class where we are called from wants to provide the runner 1043 testclassinstance = self._request.instance 1044 runner = testclassinstance.getrunner() 1045 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
.
1047 def inline_runsource(self, source: str, *cmdlineargs) -> HookRecorder: 1048 """Run a test module in process using ``pytest.main()``. 1049 1050 This run writes "source" into a temporary file and runs 1051 ``pytest.main()`` on it, returning a :py:class:`HookRecorder` instance 1052 for the result. 1053 1054 :param source: The source code of the test module. 1055 :param cmdlineargs: Any extra command line arguments to use. 1056 """ 1057 p = self.makepyfile(source) 1058 values = [*list(cmdlineargs), p] 1059 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.
1061 def inline_genitems(self, *args) -> tuple[list[Item], HookRecorder]: 1062 """Run ``pytest.main(['--collect-only'])`` in-process. 1063 1064 Runs the :py:func:`pytest.main` function to run all of pytest inside 1065 the test process itself like :py:meth:`inline_run`, but returns a 1066 tuple of the collected items and a :py:class:`HookRecorder` instance. 1067 """ 1068 rec = self.inline_run("--collect-only", *args) 1069 items = [x.item for x in rec.getcalls("pytest_itemcollected")] 1070 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.
1072 def inline_run( 1073 self, 1074 *args: str | os.PathLike[str], 1075 plugins=(), 1076 no_reraise_ctrlc: bool = False, 1077 ) -> HookRecorder: 1078 """Run ``pytest.main()`` in-process, returning a HookRecorder. 1079 1080 Runs the :py:func:`pytest.main` function to run all of pytest inside 1081 the test process itself. This means it can return a 1082 :py:class:`HookRecorder` instance which gives more detailed results 1083 from that run than can be done by matching stdout/stderr from 1084 :py:meth:`runpytest`. 1085 1086 :param args: 1087 Command line arguments to pass to :py:func:`pytest.main`. 1088 :param plugins: 1089 Extra plugin instances the ``pytest.main()`` instance should use. 1090 :param no_reraise_ctrlc: 1091 Typically we reraise keyboard interrupts from the child run. If 1092 True, the KeyboardInterrupt exception is captured. 1093 """ 1094 # (maybe a cpython bug?) the importlib cache sometimes isn't updated 1095 # properly between file creation and inline_run (especially if imports 1096 # are interspersed with file creation) 1097 importlib.invalidate_caches() 1098 1099 plugins = list(plugins) 1100 finalizers = [] 1101 try: 1102 # Any sys.module or sys.path changes done while running pytest 1103 # inline should be reverted after the test run completes to avoid 1104 # clashing with later inline tests run within the same pytest test, 1105 # e.g. just because they use matching test module names. 1106 finalizers.append(self.__take_sys_modules_snapshot().restore) 1107 finalizers.append(SysPathsSnapshot().restore) 1108 1109 # Important note: 1110 # - our tests should not leave any other references/registrations 1111 # laying around other than possibly loaded test modules 1112 # referenced from sys.modules, as nothing will clean those up 1113 # automatically 1114 1115 rec = [] 1116 1117 class Collect: 1118 def pytest_configure(x, config: Config) -> None: 1119 rec.append(self.make_hook_recorder(config.pluginmanager)) 1120 1121 plugins.append(Collect()) 1122 ret = main([str(x) for x in args], plugins=plugins) 1123 if len(rec) == 1: 1124 reprec = rec.pop() 1125 else: 1126 1127 class reprec: # type: ignore 1128 pass 1129 1130 reprec.ret = ret 1131 1132 # Typically we reraise keyboard interrupts from the child run 1133 # because it's our user requesting interruption of the testing. 1134 if ret == ExitCode.INTERRUPTED and not no_reraise_ctrlc: 1135 calls = reprec.getcalls("pytest_keyboard_interrupt") 1136 if calls and calls[-1].excinfo.type == KeyboardInterrupt: 1137 raise KeyboardInterrupt() 1138 return reprec 1139 finally: 1140 for finalizer in finalizers: 1141 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.
1143 def runpytest_inprocess( 1144 self, *args: str | os.PathLike[str], **kwargs: Any 1145 ) -> RunResult: 1146 """Return result of running pytest in-process, providing a similar 1147 interface to what self.runpytest() provides.""" 1148 syspathinsert = kwargs.pop("syspathinsert", False) 1149 1150 if syspathinsert: 1151 self.syspathinsert() 1152 now = timing.time() 1153 capture = _get_multicapture("sys") 1154 capture.start_capturing() 1155 try: 1156 try: 1157 reprec = self.inline_run(*args, **kwargs) 1158 except SystemExit as e: 1159 ret = e.args[0] 1160 try: 1161 ret = ExitCode(e.args[0]) 1162 except ValueError: 1163 pass 1164 1165 class reprec: # type: ignore 1166 ret = ret 1167 1168 except Exception: 1169 traceback.print_exc() 1170 1171 class reprec: # type: ignore 1172 ret = ExitCode(3) 1173 1174 finally: 1175 out, err = capture.readouterr() 1176 capture.stop_capturing() 1177 sys.stdout.write(out) 1178 sys.stderr.write(err) 1179 1180 assert reprec.ret is not None 1181 res = RunResult( 1182 reprec.ret, out.splitlines(), err.splitlines(), timing.time() - now 1183 ) 1184 res.reprec = reprec # type: ignore 1185 return res
Return result of running pytest in-process, providing a similar interface to what self.runpytest() provides.
1187 def runpytest(self, *args: str | os.PathLike[str], **kwargs: Any) -> RunResult: 1188 """Run pytest inline or in a subprocess, depending on the command line 1189 option "--runpytest" and return a :py:class:`~pytest.RunResult`.""" 1190 new_args = self._ensure_basetemp(args) 1191 if self._method == "inprocess": 1192 return self.runpytest_inprocess(*new_args, **kwargs) 1193 elif self._method == "subprocess": 1194 return self.runpytest_subprocess(*new_args, **kwargs) 1195 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
.
1210 def parseconfig(self, *args: str | os.PathLike[str]) -> Config: 1211 """Return a new pytest :class:`pytest.Config` instance from given 1212 commandline args. 1213 1214 This invokes the pytest bootstrapping code in _pytest.config to create a 1215 new :py:class:`pytest.PytestPluginManager` and call the 1216 :hook:`pytest_cmdline_parse` hook to create a new :class:`pytest.Config` 1217 instance. 1218 1219 If :attr:`plugins` has been populated they should be plugin modules 1220 to be registered with the plugin manager. 1221 """ 1222 import _pytest.config 1223 1224 new_args = self._ensure_basetemp(args) 1225 new_args = [str(x) for x in new_args] 1226 1227 config = _pytest.config._prepareconfig(new_args, self.plugins) # type: ignore[arg-type] 1228 # we don't know what the test will do with this half-setup config 1229 # object and thus we make sure it gets unconfigured properly in any 1230 # case (otherwise capturing could still be active, for example) 1231 self._request.addfinalizer(config._ensure_unconfigure) 1232 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.
1234 def parseconfigure(self, *args: str | os.PathLike[str]) -> Config: 1235 """Return a new pytest configured Config instance. 1236 1237 Returns a new :py:class:`pytest.Config` instance like 1238 :py:meth:`parseconfig`, but also calls the :hook:`pytest_configure` 1239 hook. 1240 """ 1241 config = self.parseconfig(*args) 1242 config._do_configure() 1243 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.
1245 def getitem( 1246 self, source: str | os.PathLike[str], funcname: str = "test_func" 1247 ) -> Item: 1248 """Return the test item for a test function. 1249 1250 Writes the source to a python file and runs pytest's collection on 1251 the resulting module, returning the test item for the requested 1252 function name. 1253 1254 :param source: 1255 The module source. 1256 :param funcname: 1257 The name of the test function for which to return a test item. 1258 :returns: 1259 The test item. 1260 """ 1261 items = self.getitems(source) 1262 for item in items: 1263 if item.name == funcname: 1264 return item 1265 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.
1267 def getitems(self, source: str | os.PathLike[str]) -> list[Item]: 1268 """Return all test items collected from the module. 1269 1270 Writes the source to a Python file and runs pytest's collection on 1271 the resulting module, returning all test items contained within. 1272 """ 1273 modcol = self.getmodulecol(source) 1274 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.
1276 def getmodulecol( 1277 self, 1278 source: str | os.PathLike[str], 1279 configargs=(), 1280 *, 1281 withinit: bool = False, 1282 ): 1283 """Return the module collection node for ``source``. 1284 1285 Writes ``source`` to a file using :py:meth:`makepyfile` and then 1286 runs the pytest collection on it, returning the collection node for the 1287 test module. 1288 1289 :param source: 1290 The source code of the module to collect. 1291 1292 :param configargs: 1293 Any extra arguments to pass to :py:meth:`parseconfigure`. 1294 1295 :param withinit: 1296 Whether to also write an ``__init__.py`` file to the same 1297 directory to ensure it is a package. 1298 """ 1299 if isinstance(source, os.PathLike): 1300 path = self.path.joinpath(source) 1301 assert not withinit, "not supported for paths" 1302 else: 1303 kw = {self._name: str(source)} 1304 path = self.makepyfile(**kw) 1305 if withinit: 1306 self.makepyfile(__init__="#") 1307 self.config = config = self.parseconfigure(path, *configargs) 1308 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.
1310 def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None: 1311 """Return the collection node for name from the module collection. 1312 1313 Searches a module collection node for a collection node matching the 1314 given name. 1315 1316 :param modcol: A module collection node; see :py:meth:`getmodulecol`. 1317 :param name: The name of the node to return. 1318 """ 1319 if modcol not in self._mod_collections: 1320 self._mod_collections[modcol] = list(modcol.collect()) 1321 for colitem in self._mod_collections[modcol]: 1322 if colitem.name == name: 1323 return colitem 1324 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.
1326 def popen( 1327 self, 1328 cmdargs: Sequence[str | os.PathLike[str]], 1329 stdout: int | TextIO = subprocess.PIPE, 1330 stderr: int | TextIO = subprocess.PIPE, 1331 stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, 1332 **kw, 1333 ): 1334 """Invoke :py:class:`subprocess.Popen`. 1335 1336 Calls :py:class:`subprocess.Popen` making sure the current working 1337 directory is in ``PYTHONPATH``. 1338 1339 You probably want to use :py:meth:`run` instead. 1340 """ 1341 env = os.environ.copy() 1342 env["PYTHONPATH"] = os.pathsep.join( 1343 filter(None, [os.getcwd(), env.get("PYTHONPATH", "")]) 1344 ) 1345 kw["env"] = env 1346 1347 if stdin is self.CLOSE_STDIN: 1348 kw["stdin"] = subprocess.PIPE 1349 elif isinstance(stdin, bytes): 1350 kw["stdin"] = subprocess.PIPE 1351 else: 1352 kw["stdin"] = stdin 1353 1354 popen = subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw) 1355 if stdin is self.CLOSE_STDIN: 1356 assert popen.stdin is not None 1357 popen.stdin.close() 1358 elif isinstance(stdin, bytes): 1359 assert popen.stdin is not None 1360 popen.stdin.write(stdin) 1361 1362 return popen
Invoke subprocess.Popen
.
Calls subprocess.Popen
making sure the current working
directory is in PYTHONPATH
.
You probably want to use run()
instead.
1364 def run( 1365 self, 1366 *cmdargs: str | os.PathLike[str], 1367 timeout: float | None = None, 1368 stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, 1369 ) -> RunResult: 1370 """Run a command with arguments. 1371 1372 Run a process using :py:class:`subprocess.Popen` saving the stdout and 1373 stderr. 1374 1375 :param cmdargs: 1376 The sequence of arguments to pass to :py:class:`subprocess.Popen`, 1377 with path-like objects being converted to :py:class:`str` 1378 automatically. 1379 :param timeout: 1380 The period in seconds after which to timeout and raise 1381 :py:class:`Pytester.TimeoutExpired`. 1382 :param stdin: 1383 Optional standard input. 1384 1385 - If it is ``CLOSE_STDIN`` (Default), then this method calls 1386 :py:class:`subprocess.Popen` with ``stdin=subprocess.PIPE``, and 1387 the standard input is closed immediately after the new command is 1388 started. 1389 1390 - If it is of type :py:class:`bytes`, these bytes are sent to the 1391 standard input of the command. 1392 1393 - Otherwise, it is passed through to :py:class:`subprocess.Popen`. 1394 For further information in this case, consult the document of the 1395 ``stdin`` parameter in :py:class:`subprocess.Popen`. 1396 :type stdin: _pytest.compat.NotSetType | bytes | IO[Any] | int 1397 :returns: 1398 The result. 1399 1400 """ 1401 __tracebackhide__ = True 1402 1403 cmdargs = tuple(os.fspath(arg) for arg in cmdargs) 1404 p1 = self.path.joinpath("stdout") 1405 p2 = self.path.joinpath("stderr") 1406 print("running:", *cmdargs) 1407 print(" in:", Path.cwd()) 1408 1409 with p1.open("w", encoding="utf8") as f1, p2.open("w", encoding="utf8") as f2: 1410 now = timing.time() 1411 popen = self.popen( 1412 cmdargs, 1413 stdin=stdin, 1414 stdout=f1, 1415 stderr=f2, 1416 close_fds=(sys.platform != "win32"), 1417 ) 1418 if popen.stdin is not None: 1419 popen.stdin.close() 1420 1421 def handle_timeout() -> None: 1422 __tracebackhide__ = True 1423 1424 timeout_message = f"{timeout} second timeout expired running: {cmdargs}" 1425 1426 popen.kill() 1427 popen.wait() 1428 raise self.TimeoutExpired(timeout_message) 1429 1430 if timeout is None: 1431 ret = popen.wait() 1432 else: 1433 try: 1434 ret = popen.wait(timeout) 1435 except subprocess.TimeoutExpired: 1436 handle_timeout() 1437 1438 with p1.open(encoding="utf8") as f1, p2.open(encoding="utf8") as f2: 1439 out = f1.read().splitlines() 1440 err = f2.read().splitlines() 1441 1442 self._dump_lines(out, sys.stdout) 1443 self._dump_lines(err, sys.stderr) 1444 1445 with contextlib.suppress(ValueError): 1446 ret = ExitCode(ret) 1447 return RunResult(ret, out, err, timing.time() - now)
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.
1459 def runpython(self, script: os.PathLike[str]) -> RunResult: 1460 """Run a python script using sys.executable as interpreter.""" 1461 return self.run(sys.executable, script)
Run a python script using sys.executable as interpreter.
1463 def runpython_c(self, command: str) -> RunResult: 1464 """Run ``python -c "command"``.""" 1465 return self.run(sys.executable, "-c", command)
Run python -c "command"
.
1467 def runpytest_subprocess( 1468 self, *args: str | os.PathLike[str], timeout: float | None = None 1469 ) -> RunResult: 1470 """Run pytest as a subprocess with given arguments. 1471 1472 Any plugins added to the :py:attr:`plugins` list will be added using the 1473 ``-p`` command line option. Additionally ``--basetemp`` is used to put 1474 any temporary files and directories in a numbered directory prefixed 1475 with "runpytest-" to not conflict with the normal numbered pytest 1476 location for temporary files and directories. 1477 1478 :param args: 1479 The sequence of arguments to pass to the pytest subprocess. 1480 :param timeout: 1481 The period in seconds after which to timeout and raise 1482 :py:class:`Pytester.TimeoutExpired`. 1483 :returns: 1484 The result. 1485 """ 1486 __tracebackhide__ = True 1487 p = make_numbered_dir(root=self.path, prefix="runpytest-", mode=0o700) 1488 args = (f"--basetemp={p}", *args) 1489 plugins = [x for x in self.plugins if isinstance(x, str)] 1490 if plugins: 1491 args = ("-p", plugins[0], *args) 1492 args = self._getpytestargs() + args 1493 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.
1495 def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn: 1496 """Run pytest using pexpect. 1497 1498 This makes sure to use the right pytest and sets up the temporary 1499 directory locations. 1500 1501 The pexpect child is returned. 1502 """ 1503 basetemp = self.path / "temp-pexpect" 1504 basetemp.mkdir(mode=0o700) 1505 invoke = " ".join(map(str, self._getpytestargs())) 1506 cmd = f"{invoke} --basetemp={basetemp} {string}" 1507 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.
1509 def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn: 1510 """Run a command using pexpect. 1511 1512 The pexpect child is returned. 1513 """ 1514 pexpect = importorskip("pexpect", "3.0") 1515 if hasattr(sys, "pypy_version_info") and "64" in platform.machine(): 1516 skip("pypy-64 bit not supported") 1517 if not hasattr(pexpect, "spawn"): 1518 skip("pexpect.spawn not available") 1519 logfile = self.path.joinpath("spawn.out").open("wb") 1520 1521 child = pexpect.spawn(cmd, logfile=logfile, timeout=expect_timeout) 1522 self._request.addfinalizer(logfile.close) 1523 return child
Run a command using pexpect.
The pexpect child is returned.
Common base class for all non-exit exceptions.
Inherited Members
- builtins.Exception
- Exception
- builtins.BaseException
- with_traceback
- add_note
- args
391@final 392class PytestPluginManager(PluginManager): 393 """A :py:class:`pluggy.PluginManager <pluggy.PluginManager>` with 394 additional pytest-specific functionality: 395 396 * Loading plugins from the command line, ``PYTEST_PLUGINS`` env variable and 397 ``pytest_plugins`` global variables found in plugins being loaded. 398 * ``conftest.py`` loading during start-up. 399 """ 400 401 def __init__(self) -> None: 402 import _pytest.assertion 403 404 super().__init__("pytest") 405 406 # -- State related to local conftest plugins. 407 # All loaded conftest modules. 408 self._conftest_plugins: set[types.ModuleType] = set() 409 # All conftest modules applicable for a directory. 410 # This includes the directory's own conftest modules as well 411 # as those of its parent directories. 412 self._dirpath2confmods: dict[pathlib.Path, list[types.ModuleType]] = {} 413 # Cutoff directory above which conftests are no longer discovered. 414 self._confcutdir: pathlib.Path | None = None 415 # If set, conftest loading is skipped. 416 self._noconftest = False 417 418 # _getconftestmodules()'s call to _get_directory() causes a stat 419 # storm when it's called potentially thousands of times in a test 420 # session (#9478), often with the same path, so cache it. 421 self._get_directory = lru_cache(256)(_get_directory) 422 423 # plugins that were explicitly skipped with pytest.skip 424 # list of (module name, skip reason) 425 # previously we would issue a warning when a plugin was skipped, but 426 # since we refactored warnings as first citizens of Config, they are 427 # just stored here to be used later. 428 self.skipped_plugins: list[tuple[str, str]] = [] 429 430 self.add_hookspecs(_pytest.hookspec) 431 self.register(self) 432 if os.environ.get("PYTEST_DEBUG"): 433 err: IO[str] = sys.stderr 434 encoding: str = getattr(err, "encoding", "utf8") 435 try: 436 err = open( 437 os.dup(err.fileno()), 438 mode=err.mode, 439 buffering=1, 440 encoding=encoding, 441 ) 442 except Exception: 443 pass 444 self.trace.root.setwriter(err.write) 445 self.enable_tracing() 446 447 # Config._consider_importhook will set a real object if required. 448 self.rewrite_hook = _pytest.assertion.DummyRewriteHook() 449 # Used to know when we are importing conftests after the pytest_configure stage. 450 self._configured = False 451 452 def parse_hookimpl_opts( 453 self, plugin: _PluggyPlugin, name: str 454 ) -> HookimplOpts | None: 455 """:meta private:""" 456 # pytest hooks are always prefixed with "pytest_", 457 # so we avoid accessing possibly non-readable attributes 458 # (see issue #1073). 459 if not name.startswith("pytest_"): 460 return None 461 # Ignore names which cannot be hooks. 462 if name == "pytest_plugins": 463 return None 464 465 opts = super().parse_hookimpl_opts(plugin, name) 466 if opts is not None: 467 return opts 468 469 method = getattr(plugin, name) 470 # Consider only actual functions for hooks (#3775). 471 if not inspect.isroutine(method): 472 return None 473 # Collect unmarked hooks as long as they have the `pytest_' prefix. 474 return _get_legacy_hook_marks( # type: ignore[return-value] 475 method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper") 476 ) 477 478 def parse_hookspec_opts(self, module_or_class, name: str) -> HookspecOpts | None: 479 """:meta private:""" 480 opts = super().parse_hookspec_opts(module_or_class, name) 481 if opts is None: 482 method = getattr(module_or_class, name) 483 if name.startswith("pytest_"): 484 opts = _get_legacy_hook_marks( # type: ignore[assignment] 485 method, 486 "spec", 487 ("firstresult", "historic"), 488 ) 489 return opts 490 491 def register(self, plugin: _PluggyPlugin, name: str | None = None) -> str | None: 492 if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS: 493 warnings.warn( 494 PytestConfigWarning( 495 "{} plugin has been merged into the core, " 496 "please remove it from your requirements.".format( 497 name.replace("_", "-") 498 ) 499 ) 500 ) 501 return None 502 plugin_name = super().register(plugin, name) 503 if plugin_name is not None: 504 self.hook.pytest_plugin_registered.call_historic( 505 kwargs=dict( 506 plugin=plugin, 507 plugin_name=plugin_name, 508 manager=self, 509 ) 510 ) 511 512 if isinstance(plugin, types.ModuleType): 513 self.consider_module(plugin) 514 return plugin_name 515 516 def getplugin(self, name: str): 517 # Support deprecated naming because plugins (xdist e.g.) use it. 518 plugin: _PluggyPlugin | None = self.get_plugin(name) 519 return plugin 520 521 def hasplugin(self, name: str) -> bool: 522 """Return whether a plugin with the given name is registered.""" 523 return bool(self.get_plugin(name)) 524 525 def pytest_configure(self, config: Config) -> None: 526 """:meta private:""" 527 # XXX now that the pluginmanager exposes hookimpl(tryfirst...) 528 # we should remove tryfirst/trylast as markers. 529 config.addinivalue_line( 530 "markers", 531 "tryfirst: mark a hook implementation function such that the " 532 "plugin machinery will try to call it first/as early as possible. " 533 "DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.", 534 ) 535 config.addinivalue_line( 536 "markers", 537 "trylast: mark a hook implementation function such that the " 538 "plugin machinery will try to call it last/as late as possible. " 539 "DEPRECATED, use @pytest.hookimpl(trylast=True) instead.", 540 ) 541 self._configured = True 542 543 # 544 # Internal API for local conftest plugin handling. 545 # 546 def _set_initial_conftests( 547 self, 548 args: Sequence[str | pathlib.Path], 549 pyargs: bool, 550 noconftest: bool, 551 rootpath: pathlib.Path, 552 confcutdir: pathlib.Path | None, 553 invocation_dir: pathlib.Path, 554 importmode: ImportMode | str, 555 *, 556 consider_namespace_packages: bool, 557 ) -> None: 558 """Load initial conftest files given a preparsed "namespace". 559 560 As conftest files may add their own command line options which have 561 arguments ('--my-opt somepath') we might get some false positives. 562 All builtin and 3rd party plugins will have been loaded, however, so 563 common options will not confuse our logic here. 564 """ 565 self._confcutdir = ( 566 absolutepath(invocation_dir / confcutdir) if confcutdir else None 567 ) 568 self._noconftest = noconftest 569 self._using_pyargs = pyargs 570 foundanchor = False 571 for initial_path in args: 572 path = str(initial_path) 573 # remove node-id syntax 574 i = path.find("::") 575 if i != -1: 576 path = path[:i] 577 anchor = absolutepath(invocation_dir / path) 578 579 # Ensure we do not break if what appears to be an anchor 580 # is in fact a very long option (#10169, #11394). 581 if safe_exists(anchor): 582 self._try_load_conftest( 583 anchor, 584 importmode, 585 rootpath, 586 consider_namespace_packages=consider_namespace_packages, 587 ) 588 foundanchor = True 589 if not foundanchor: 590 self._try_load_conftest( 591 invocation_dir, 592 importmode, 593 rootpath, 594 consider_namespace_packages=consider_namespace_packages, 595 ) 596 597 def _is_in_confcutdir(self, path: pathlib.Path) -> bool: 598 """Whether to consider the given path to load conftests from.""" 599 if self._confcutdir is None: 600 return True 601 # The semantics here are literally: 602 # Do not load a conftest if it is found upwards from confcut dir. 603 # But this is *not* the same as: 604 # Load only conftests from confcutdir or below. 605 # At first glance they might seem the same thing, however we do support use cases where 606 # we want to load conftests that are not found in confcutdir or below, but are found 607 # in completely different directory hierarchies like packages installed 608 # in out-of-source trees. 609 # (see #9767 for a regression where the logic was inverted). 610 return path not in self._confcutdir.parents 611 612 def _try_load_conftest( 613 self, 614 anchor: pathlib.Path, 615 importmode: str | ImportMode, 616 rootpath: pathlib.Path, 617 *, 618 consider_namespace_packages: bool, 619 ) -> None: 620 self._loadconftestmodules( 621 anchor, 622 importmode, 623 rootpath, 624 consider_namespace_packages=consider_namespace_packages, 625 ) 626 # let's also consider test* subdirs 627 if anchor.is_dir(): 628 for x in anchor.glob("test*"): 629 if x.is_dir(): 630 self._loadconftestmodules( 631 x, 632 importmode, 633 rootpath, 634 consider_namespace_packages=consider_namespace_packages, 635 ) 636 637 def _loadconftestmodules( 638 self, 639 path: pathlib.Path, 640 importmode: str | ImportMode, 641 rootpath: pathlib.Path, 642 *, 643 consider_namespace_packages: bool, 644 ) -> None: 645 if self._noconftest: 646 return 647 648 directory = self._get_directory(path) 649 650 # Optimization: avoid repeated searches in the same directory. 651 # Assumes always called with same importmode and rootpath. 652 if directory in self._dirpath2confmods: 653 return 654 655 clist = [] 656 for parent in reversed((directory, *directory.parents)): 657 if self._is_in_confcutdir(parent): 658 conftestpath = parent / "conftest.py" 659 if conftestpath.is_file(): 660 mod = self._importconftest( 661 conftestpath, 662 importmode, 663 rootpath, 664 consider_namespace_packages=consider_namespace_packages, 665 ) 666 clist.append(mod) 667 self._dirpath2confmods[directory] = clist 668 669 def _getconftestmodules(self, path: pathlib.Path) -> Sequence[types.ModuleType]: 670 directory = self._get_directory(path) 671 return self._dirpath2confmods.get(directory, ()) 672 673 def _rget_with_confmod( 674 self, 675 name: str, 676 path: pathlib.Path, 677 ) -> tuple[types.ModuleType, Any]: 678 modules = self._getconftestmodules(path) 679 for mod in reversed(modules): 680 try: 681 return mod, getattr(mod, name) 682 except AttributeError: 683 continue 684 raise KeyError(name) 685 686 def _importconftest( 687 self, 688 conftestpath: pathlib.Path, 689 importmode: str | ImportMode, 690 rootpath: pathlib.Path, 691 *, 692 consider_namespace_packages: bool, 693 ) -> types.ModuleType: 694 conftestpath_plugin_name = str(conftestpath) 695 existing = self.get_plugin(conftestpath_plugin_name) 696 if existing is not None: 697 return cast(types.ModuleType, existing) 698 699 # conftest.py files there are not in a Python package all have module 700 # name "conftest", and thus conflict with each other. Clear the existing 701 # before loading the new one, otherwise the existing one will be 702 # returned from the module cache. 703 pkgpath = resolve_package_path(conftestpath) 704 if pkgpath is None: 705 try: 706 del sys.modules[conftestpath.stem] 707 except KeyError: 708 pass 709 710 try: 711 mod = import_path( 712 conftestpath, 713 mode=importmode, 714 root=rootpath, 715 consider_namespace_packages=consider_namespace_packages, 716 ) 717 except Exception as e: 718 assert e.__traceback__ is not None 719 raise ConftestImportFailure(conftestpath, cause=e) from e 720 721 self._check_non_top_pytest_plugins(mod, conftestpath) 722 723 self._conftest_plugins.add(mod) 724 dirpath = conftestpath.parent 725 if dirpath in self._dirpath2confmods: 726 for path, mods in self._dirpath2confmods.items(): 727 if dirpath in path.parents or path == dirpath: 728 if mod in mods: 729 raise AssertionError( 730 f"While trying to load conftest path {conftestpath!s}, " 731 f"found that the module {mod} is already loaded with path {mod.__file__}. " 732 "This is not supposed to happen. Please report this issue to pytest." 733 ) 734 mods.append(mod) 735 self.trace(f"loading conftestmodule {mod!r}") 736 self.consider_conftest(mod, registration_name=conftestpath_plugin_name) 737 return mod 738 739 def _check_non_top_pytest_plugins( 740 self, 741 mod: types.ModuleType, 742 conftestpath: pathlib.Path, 743 ) -> None: 744 if ( 745 hasattr(mod, "pytest_plugins") 746 and self._configured 747 and not self._using_pyargs 748 ): 749 msg = ( 750 "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported:\n" 751 "It affects the entire test suite instead of just below the conftest as expected.\n" 752 " {}\n" 753 "Please move it to a top level conftest file at the rootdir:\n" 754 " {}\n" 755 "For more information, visit:\n" 756 " https://docs.pytest.org/en/stable/deprecations.html#pytest-plugins-in-non-top-level-conftest-files" 757 ) 758 fail(msg.format(conftestpath, self._confcutdir), pytrace=False) 759 760 # 761 # API for bootstrapping plugin loading 762 # 763 # 764 765 def consider_preparse( 766 self, args: Sequence[str], *, exclude_only: bool = False 767 ) -> None: 768 """:meta private:""" 769 i = 0 770 n = len(args) 771 while i < n: 772 opt = args[i] 773 i += 1 774 if isinstance(opt, str): 775 if opt == "-p": 776 try: 777 parg = args[i] 778 except IndexError: 779 return 780 i += 1 781 elif opt.startswith("-p"): 782 parg = opt[2:] 783 else: 784 continue 785 parg = parg.strip() 786 if exclude_only and not parg.startswith("no:"): 787 continue 788 self.consider_pluginarg(parg) 789 790 def consider_pluginarg(self, arg: str) -> None: 791 """:meta private:""" 792 if arg.startswith("no:"): 793 name = arg[3:] 794 if name in essential_plugins: 795 raise UsageError(f"plugin {name} cannot be disabled") 796 797 # PR #4304: remove stepwise if cacheprovider is blocked. 798 if name == "cacheprovider": 799 self.set_blocked("stepwise") 800 self.set_blocked("pytest_stepwise") 801 802 self.set_blocked(name) 803 if not name.startswith("pytest_"): 804 self.set_blocked("pytest_" + name) 805 else: 806 name = arg 807 # Unblock the plugin. 808 self.unblock(name) 809 if not name.startswith("pytest_"): 810 self.unblock("pytest_" + name) 811 self.import_plugin(arg, consider_entry_points=True) 812 813 def consider_conftest( 814 self, conftestmodule: types.ModuleType, registration_name: str 815 ) -> None: 816 """:meta private:""" 817 self.register(conftestmodule, name=registration_name) 818 819 def consider_env(self) -> None: 820 """:meta private:""" 821 self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS")) 822 823 def consider_module(self, mod: types.ModuleType) -> None: 824 """:meta private:""" 825 self._import_plugin_specs(getattr(mod, "pytest_plugins", [])) 826 827 def _import_plugin_specs( 828 self, spec: None | types.ModuleType | str | Sequence[str] 829 ) -> None: 830 plugins = _get_plugin_specs_as_list(spec) 831 for import_spec in plugins: 832 self.import_plugin(import_spec) 833 834 def import_plugin(self, modname: str, consider_entry_points: bool = False) -> None: 835 """Import a plugin with ``modname``. 836 837 If ``consider_entry_points`` is True, entry point names are also 838 considered to find a plugin. 839 """ 840 # Most often modname refers to builtin modules, e.g. "pytester", 841 # "terminal" or "capture". Those plugins are registered under their 842 # basename for historic purposes but must be imported with the 843 # _pytest prefix. 844 assert isinstance( 845 modname, str 846 ), f"module name as text required, got {modname!r}" 847 if self.is_blocked(modname) or self.get_plugin(modname) is not None: 848 return 849 850 importspec = "_pytest." + modname if modname in builtin_plugins else modname 851 self.rewrite_hook.mark_rewrite(importspec) 852 853 if consider_entry_points: 854 loaded = self.load_setuptools_entrypoints("pytest11", name=modname) 855 if loaded: 856 return 857 858 try: 859 __import__(importspec) 860 except ImportError as e: 861 raise ImportError( 862 f'Error importing plugin "{modname}": {e.args[0]}' 863 ).with_traceback(e.__traceback__) from e 864 865 except Skipped as e: 866 self.skipped_plugins.append((modname, e.msg or "")) 867 else: 868 mod = sys.modules[importspec] 869 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.
452 def parse_hookimpl_opts( 453 self, plugin: _PluggyPlugin, name: str 454 ) -> HookimplOpts | None: 455 """:meta private:""" 456 # pytest hooks are always prefixed with "pytest_", 457 # so we avoid accessing possibly non-readable attributes 458 # (see issue #1073). 459 if not name.startswith("pytest_"): 460 return None 461 # Ignore names which cannot be hooks. 462 if name == "pytest_plugins": 463 return None 464 465 opts = super().parse_hookimpl_opts(plugin, name) 466 if opts is not None: 467 return opts 468 469 method = getattr(plugin, name) 470 # Consider only actual functions for hooks (#3775). 471 if not inspect.isroutine(method): 472 return None 473 # Collect unmarked hooks as long as they have the `pytest_' prefix. 474 return _get_legacy_hook_marks( # type: ignore[return-value] 475 method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper") 476 )
:meta private:
478 def parse_hookspec_opts(self, module_or_class, name: str) -> HookspecOpts | None: 479 """:meta private:""" 480 opts = super().parse_hookspec_opts(module_or_class, name) 481 if opts is None: 482 method = getattr(module_or_class, name) 483 if name.startswith("pytest_"): 484 opts = _get_legacy_hook_marks( # type: ignore[assignment] 485 method, 486 "spec", 487 ("firstresult", "historic"), 488 ) 489 return opts
:meta private:
491 def register(self, plugin: _PluggyPlugin, name: str | None = None) -> str | None: 492 if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS: 493 warnings.warn( 494 PytestConfigWarning( 495 "{} plugin has been merged into the core, " 496 "please remove it from your requirements.".format( 497 name.replace("_", "-") 498 ) 499 ) 500 ) 501 return None 502 plugin_name = super().register(plugin, name) 503 if plugin_name is not None: 504 self.hook.pytest_plugin_registered.call_historic( 505 kwargs=dict( 506 plugin=plugin, 507 plugin_name=plugin_name, 508 manager=self, 509 ) 510 ) 511 512 if isinstance(plugin, types.ModuleType): 513 self.consider_module(plugin) 514 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
.
521 def hasplugin(self, name: str) -> bool: 522 """Return whether a plugin with the given name is registered.""" 523 return bool(self.get_plugin(name))
Return whether a plugin with the given name is registered.
525 def pytest_configure(self, config: Config) -> None: 526 """:meta private:""" 527 # XXX now that the pluginmanager exposes hookimpl(tryfirst...) 528 # we should remove tryfirst/trylast as markers. 529 config.addinivalue_line( 530 "markers", 531 "tryfirst: mark a hook implementation function such that the " 532 "plugin machinery will try to call it first/as early as possible. " 533 "DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.", 534 ) 535 config.addinivalue_line( 536 "markers", 537 "trylast: mark a hook implementation function such that the " 538 "plugin machinery will try to call it last/as late as possible. " 539 "DEPRECATED, use @pytest.hookimpl(trylast=True) instead.", 540 ) 541 self._configured = True
:meta private:
765 def consider_preparse( 766 self, args: Sequence[str], *, exclude_only: bool = False 767 ) -> None: 768 """:meta private:""" 769 i = 0 770 n = len(args) 771 while i < n: 772 opt = args[i] 773 i += 1 774 if isinstance(opt, str): 775 if opt == "-p": 776 try: 777 parg = args[i] 778 except IndexError: 779 return 780 i += 1 781 elif opt.startswith("-p"): 782 parg = opt[2:] 783 else: 784 continue 785 parg = parg.strip() 786 if exclude_only and not parg.startswith("no:"): 787 continue 788 self.consider_pluginarg(parg)
:meta private:
790 def consider_pluginarg(self, arg: str) -> None: 791 """:meta private:""" 792 if arg.startswith("no:"): 793 name = arg[3:] 794 if name in essential_plugins: 795 raise UsageError(f"plugin {name} cannot be disabled") 796 797 # PR #4304: remove stepwise if cacheprovider is blocked. 798 if name == "cacheprovider": 799 self.set_blocked("stepwise") 800 self.set_blocked("pytest_stepwise") 801 802 self.set_blocked(name) 803 if not name.startswith("pytest_"): 804 self.set_blocked("pytest_" + name) 805 else: 806 name = arg 807 # Unblock the plugin. 808 self.unblock(name) 809 if not name.startswith("pytest_"): 810 self.unblock("pytest_" + name) 811 self.import_plugin(arg, consider_entry_points=True)
:meta private:
813 def consider_conftest( 814 self, conftestmodule: types.ModuleType, registration_name: str 815 ) -> None: 816 """:meta private:""" 817 self.register(conftestmodule, name=registration_name)
:meta private:
819 def consider_env(self) -> None: 820 """:meta private:""" 821 self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
:meta private:
823 def consider_module(self, mod: types.ModuleType) -> None: 824 """:meta private:""" 825 self._import_plugin_specs(getattr(mod, "pytest_plugins", []))
:meta private:
834 def import_plugin(self, modname: str, consider_entry_points: bool = False) -> None: 835 """Import a plugin with ``modname``. 836 837 If ``consider_entry_points`` is True, entry point names are also 838 considered to find a plugin. 839 """ 840 # Most often modname refers to builtin modules, e.g. "pytester", 841 # "terminal" or "capture". Those plugins are registered under their 842 # basename for historic purposes but must be imported with the 843 # _pytest prefix. 844 assert isinstance( 845 modname, str 846 ), f"module name as text required, got {modname!r}" 847 if self.is_blocked(modname) or self.get_plugin(modname) is not None: 848 return 849 850 importspec = "_pytest." + modname if modname in builtin_plugins else modname 851 self.rewrite_hook.mark_rewrite(importspec) 852 853 if consider_entry_points: 854 loaded = self.load_setuptools_entrypoints("pytest11", name=modname) 855 if loaded: 856 return 857 858 try: 859 __import__(importspec) 860 except ImportError as e: 861 raise ImportError( 862 f'Error importing plugin "{modname}": {e.args[0]}' 863 ).with_traceback(e.__traceback__) from e 864 865 except Skipped as e: 866 self.skipped_plugins.append((modname, e.msg or "")) 867 else: 868 mod = sys.modules[importspec] 869 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.
Inherited Members
- pluggy._manager.PluginManager
- project_name
- hook
- trace
- unregister
- set_blocked
- is_blocked
- unblock
- add_hookspecs
- get_plugins
- is_registered
- get_canonical_name
- get_plugin
- has_plugin
- get_name
- check_pending
- load_setuptools_entrypoints
- list_plugin_distinfo
- list_name_plugin
- get_hookcallers
- add_hookcall_monitoring
- enable_tracing
- subset_hook_caller
81from _pytest.warning_types import PytestReturnNotNoneWarning
Warning emitted for an unhandled coroutine.
A coroutine was encountered when collecting test functions, but was not handled by any async-aware plugin. Coroutine test functions are not natively supported.
Inherited Members
- builtins.UserWarning
- UserWarning
- builtins.BaseException
- with_traceback
- add_note
- args
115 "FixtureLookupError",
An unhandled exception occurred in a ~threading.Thread
.
Such exceptions don't propagate normally.
Inherited Members
- builtins.UserWarning
- UserWarning
- builtins.BaseException
- with_traceback
- add_note
- args
93 "__version__",
Warning emitted on use of unknown markers.
See :ref:mark
for details.
Inherited Members
- builtins.UserWarning
- UserWarning
- builtins.BaseException
- with_traceback
- add_note
- args
103 "console_main",
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.
Inherited Members
- builtins.UserWarning
- UserWarning
- builtins.BaseException
- with_traceback
- add_note
- args
14from _pytest.config import Config
Base class for all warnings emitted by pytest.
Inherited Members
- builtins.UserWarning
- UserWarning
- builtins.BaseException
- with_traceback
- add_note
- args
803def raises( 804 expected_exception: type[E] | tuple[type[E], ...], *args: Any, **kwargs: Any 805) -> RaisesContext[E] | _pytest._code.ExceptionInfo[E]: 806 r"""Assert that a code block/function call raises an exception type, or one of its subclasses. 807 808 :param expected_exception: 809 The expected exception type, or a tuple if one of multiple possible 810 exception types are expected. Note that subclasses of the passed exceptions 811 will also match. 812 813 :kwparam str | re.Pattern[str] | None match: 814 If specified, a string containing a regular expression, 815 or a regular expression object, that is tested against the string 816 representation of the exception and its :pep:`678` `__notes__` 817 using :func:`re.search`. 818 819 To match a literal string that may contain :ref:`special characters 820 <re-syntax>`, the pattern can first be escaped with :func:`re.escape`. 821 822 (This is only used when ``pytest.raises`` is used as a context manager, 823 and passed through to the function otherwise. 824 When using ``pytest.raises`` as a function, you can use: 825 ``pytest.raises(Exc, func, match="passed on").match("my pattern")``.) 826 827 Use ``pytest.raises`` as a context manager, which will capture the exception of the given 828 type, or any of its subclasses:: 829 830 >>> import pytest 831 >>> with pytest.raises(ZeroDivisionError): 832 ... 1/0 833 834 If the code block does not raise the expected exception (:class:`ZeroDivisionError` in the example 835 above), or no exception at all, the check will fail instead. 836 837 You can also use the keyword argument ``match`` to assert that the 838 exception matches a text or regex:: 839 840 >>> with pytest.raises(ValueError, match='must be 0 or None'): 841 ... raise ValueError("value must be 0 or None") 842 843 >>> with pytest.raises(ValueError, match=r'must be \d+$'): 844 ... raise ValueError("value must be 42") 845 846 The ``match`` argument searches the formatted exception string, which includes any 847 `PEP-678 <https://peps.python.org/pep-0678/>`__ ``__notes__``: 848 849 >>> with pytest.raises(ValueError, match=r"had a note added"): # doctest: +SKIP 850 ... e = ValueError("value must be 42") 851 ... e.add_note("had a note added") 852 ... raise e 853 854 The context manager produces an :class:`ExceptionInfo` object which can be used to inspect the 855 details of the captured exception:: 856 857 >>> with pytest.raises(ValueError) as exc_info: 858 ... raise ValueError("value must be 42") 859 >>> assert exc_info.type is ValueError 860 >>> assert exc_info.value.args[0] == "value must be 42" 861 862 .. warning:: 863 864 Given that ``pytest.raises`` matches subclasses, be wary of using it to match :class:`Exception` like this:: 865 866 with pytest.raises(Exception): # Careful, this will catch ANY exception raised. 867 some_function() 868 869 Because :class:`Exception` is the base class of almost all exceptions, it is easy for this to hide 870 real bugs, where the user wrote this expecting a specific exception, but some other exception is being 871 raised due to a bug introduced during a refactoring. 872 873 Avoid using ``pytest.raises`` to catch :class:`Exception` unless certain that you really want to catch 874 **any** exception raised. 875 876 .. note:: 877 878 When using ``pytest.raises`` as a context manager, it's worthwhile to 879 note that normal context manager rules apply and that the exception 880 raised *must* be the final line in the scope of the context manager. 881 Lines of code after that, within the scope of the context manager will 882 not be executed. For example:: 883 884 >>> value = 15 885 >>> with pytest.raises(ValueError) as exc_info: 886 ... if value > 10: 887 ... raise ValueError("value must be <= 10") 888 ... assert exc_info.type is ValueError # This will not execute. 889 890 Instead, the following approach must be taken (note the difference in 891 scope):: 892 893 >>> with pytest.raises(ValueError) as exc_info: 894 ... if value > 10: 895 ... raise ValueError("value must be <= 10") 896 ... 897 >>> assert exc_info.type is ValueError 898 899 **Using with** ``pytest.mark.parametrize`` 900 901 When using :ref:`pytest.mark.parametrize ref` 902 it is possible to parametrize tests such that 903 some runs raise an exception and others do not. 904 905 See :ref:`parametrizing_conditional_raising` for an example. 906 907 .. seealso:: 908 909 :ref:`assertraises` for more examples and detailed discussion. 910 911 **Legacy form** 912 913 It is possible to specify a callable by passing a to-be-called lambda:: 914 915 >>> raises(ZeroDivisionError, lambda: 1/0) 916 <ExceptionInfo ...> 917 918 or you can specify an arbitrary callable with arguments:: 919 920 >>> def f(x): return 1/x 921 ... 922 >>> raises(ZeroDivisionError, f, 0) 923 <ExceptionInfo ...> 924 >>> raises(ZeroDivisionError, f, x=0) 925 <ExceptionInfo ...> 926 927 The form above is fully supported but discouraged for new code because the 928 context manager form is regarded as more readable and less error-prone. 929 930 .. note:: 931 Similar to caught exception objects in Python, explicitly clearing 932 local references to returned ``ExceptionInfo`` objects can 933 help the Python interpreter speed up its garbage collection. 934 935 Clearing those references breaks a reference cycle 936 (``ExceptionInfo`` --> caught exception --> frame stack raising 937 the exception --> current frame stack --> local variables --> 938 ``ExceptionInfo``) which makes Python keep all objects referenced 939 from that cycle (including all local variables in the current 940 frame) alive until the next cyclic garbage collection run. 941 More detailed information can be found in the official Python 942 documentation for :ref:`the try statement <python:try>`. 943 """ 944 __tracebackhide__ = True 945 946 if not expected_exception: 947 raise ValueError( 948 f"Expected an exception type or a tuple of exception types, but got `{expected_exception!r}`. " 949 f"Raising exceptions is already understood as failing the test, so you don't need " 950 f"any special code to say 'this should never raise an exception'." 951 ) 952 if isinstance(expected_exception, type): 953 expected_exceptions: tuple[type[E], ...] = (expected_exception,) 954 else: 955 expected_exceptions = expected_exception 956 for exc in expected_exceptions: 957 if not isinstance(exc, type) or not issubclass(exc, BaseException): 958 msg = "expected exception must be a BaseException type, not {}" # type: ignore[unreachable] 959 not_a = exc.__name__ if isinstance(exc, type) else type(exc).__name__ 960 raise TypeError(msg.format(not_a)) 961 962 message = f"DID NOT RAISE {expected_exception}" 963 964 if not args: 965 match: str | Pattern[str] | None = kwargs.pop("match", None) 966 if kwargs: 967 msg = "Unexpected keyword arguments passed to pytest.raises: " 968 msg += ", ".join(sorted(kwargs)) 969 msg += "\nUse context-manager form instead?" 970 raise TypeError(msg) 971 return RaisesContext(expected_exception, message, match) 972 else: 973 func = args[0] 974 if not callable(func): 975 raise TypeError(f"{func!r} object (type: {type(func)}) must be callable") 976 try: 977 func(*args[1:], **kwargs) 978 except expected_exception as e: 979 return _pytest._code.ExceptionInfo.from_exception(e) 980 fail(message)
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.
: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")``.)
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 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::
with pytest.raises(Exception): # Careful, this will catch ANY exception raised.
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
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>
.
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
59def register_assert_rewrite(*names: str) -> None: 60 """Register one or more module names to be rewritten on import. 61 62 This function will make sure that this module or all modules inside 63 the package will get their assert statements rewritten. 64 Thus you should make sure to call this before the module is 65 actually imported, usually in your __init__.py if you are a plugin 66 using a package. 67 68 :param names: The module names to register. 69 """ 70 for name in names: 71 if not isinstance(name, str): 72 msg = "expected module names as *args, got {0} instead" # type: ignore[unreachable] 73 raise TypeError(msg.format(repr(names))) 74 for hook in sys.meta_path: 75 if isinstance(hook, rewrite.AssertionRewritingHook): 76 importhook = hook 77 break 78 else: 79 # TODO(typing): Add a protocol for mark_rewrite() and use it 80 # for importhook and for PytestPluginManager.rewrite_hook. 81 importhook = DummyRewriteHook() # type: ignore 82 importhook.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.
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 "<RunResult ret=%s len(stdout.lines)=%d len(stderr.lines)=%d duration=%.2fs>" 552 % (self.ret, len(self.stdout.lines), len(self.stderr.lines), self.duration) 553 ) 554 555 def parseoutcomes(self) -> dict[str, int]: 556 """Return a dictionary of outcome noun -> count from parsing the terminal 557 output that the test process produced. 558 559 The returned nouns will always be in plural form:: 560 561 ======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ==== 562 563 Will return ``{"failed": 1, "passed": 1, "warnings": 1, "errors": 1}``. 564 """ 565 return self.parse_summary_nouns(self.outlines) 566 567 @classmethod 568 def parse_summary_nouns(cls, lines) -> dict[str, int]: 569 """Extract the nouns from a pytest terminal summary line. 570 571 It always returns the plural noun for consistency:: 572 573 ======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ==== 574 575 Will return ``{"failed": 1, "passed": 1, "warnings": 1, "errors": 1}``. 576 """ 577 for line in reversed(lines): 578 if rex_session_duration.search(line): 579 outcomes = rex_outcome.findall(line) 580 ret = {noun: int(count) for (count, noun) in outcomes} 581 break 582 else: 583 raise ValueError("Pytest terminal summary report not found") 584 585 to_plural = { 586 "warning": "warnings", 587 "error": "errors", 588 } 589 return {to_plural.get(k, k): v for k, v in ret.items()} 590 591 def assert_outcomes( 592 self, 593 passed: int = 0, 594 skipped: int = 0, 595 failed: int = 0, 596 errors: int = 0, 597 xpassed: int = 0, 598 xfailed: int = 0, 599 warnings: int | None = None, 600 deselected: int | None = None, 601 ) -> None: 602 """ 603 Assert that the specified outcomes appear with the respective 604 numbers (0 means it didn't occur) in the text output from a test run. 605 606 ``warnings`` and ``deselected`` are only checked if not None. 607 """ 608 __tracebackhide__ = True 609 from _pytest.pytester_assertions import assert_outcomes 610 611 outcomes = self.parseoutcomes() 612 assert_outcomes( 613 outcomes, 614 passed=passed, 615 skipped=skipped, 616 failed=failed, 617 errors=errors, 618 xpassed=xpassed, 619 xfailed=xfailed, 620 warnings=warnings, 621 deselected=deselected, 622 )
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.
555 def parseoutcomes(self) -> dict[str, int]: 556 """Return a dictionary of outcome noun -> count from parsing the terminal 557 output that the test process produced. 558 559 The returned nouns will always be in plural form:: 560 561 ======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ==== 562 563 Will return ``{"failed": 1, "passed": 1, "warnings": 1, "errors": 1}``. 564 """ 565 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}
.
567 @classmethod 568 def parse_summary_nouns(cls, lines) -> dict[str, int]: 569 """Extract the nouns from a pytest terminal summary line. 570 571 It always returns the plural noun for consistency:: 572 573 ======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ==== 574 575 Will return ``{"failed": 1, "passed": 1, "warnings": 1, "errors": 1}``. 576 """ 577 for line in reversed(lines): 578 if rex_session_duration.search(line): 579 outcomes = rex_outcome.findall(line) 580 ret = {noun: int(count) for (count, noun) in outcomes} 581 break 582 else: 583 raise ValueError("Pytest terminal summary report not found") 584 585 to_plural = { 586 "warning": "warnings", 587 "error": "errors", 588 } 589 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}
.
591 def assert_outcomes( 592 self, 593 passed: int = 0, 594 skipped: int = 0, 595 failed: int = 0, 596 errors: int = 0, 597 xpassed: int = 0, 598 xfailed: int = 0, 599 warnings: int | None = None, 600 deselected: int | None = None, 601 ) -> None: 602 """ 603 Assert that the specified outcomes appear with the respective 604 numbers (0 means it didn't occur) in the text output from a test run. 605 606 ``warnings`` and ``deselected`` are only checked if not None. 607 """ 608 __tracebackhide__ = True 609 from _pytest.pytester_assertions import assert_outcomes 610 611 outcomes = self.parseoutcomes() 612 assert_outcomes( 613 outcomes, 614 passed=passed, 615 skipped=skipped, 616 failed=failed, 617 errors=errors, 618 xpassed=xpassed, 619 xfailed=xfailed, 620 warnings=warnings, 621 deselected=deselected, 622 )
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.
543@final 544class Session(nodes.Collector): 545 """The root of the collection tree. 546 547 ``Session`` collects the initial paths given as arguments to pytest. 548 """ 549 550 Interrupted = Interrupted 551 Failed = Failed 552 # Set on the session by runner.pytest_sessionstart. 553 _setupstate: SetupState 554 # Set on the session by fixtures.pytest_sessionstart. 555 _fixturemanager: FixtureManager 556 exitstatus: int | ExitCode 557 558 def __init__(self, config: Config) -> None: 559 super().__init__( 560 name="", 561 path=config.rootpath, 562 fspath=None, 563 parent=None, 564 config=config, 565 session=self, 566 nodeid="", 567 ) 568 self.testsfailed = 0 569 self.testscollected = 0 570 self._shouldstop: bool | str = False 571 self._shouldfail: bool | str = False 572 self.trace = config.trace.root.get("collection") 573 self._initialpaths: frozenset[Path] = frozenset() 574 self._initialpaths_with_parents: frozenset[Path] = frozenset() 575 self._notfound: list[tuple[str, Sequence[nodes.Collector]]] = [] 576 self._initial_parts: list[CollectionArgument] = [] 577 self._collection_cache: dict[nodes.Collector, CollectReport] = {} 578 self.items: list[nodes.Item] = [] 579 580 self._bestrelpathcache: dict[Path, str] = _bestrelpath_cache(config.rootpath) 581 582 self.config.pluginmanager.register(self, name="session") 583 584 @classmethod 585 def from_config(cls, config: Config) -> Session: 586 session: Session = cls._create(config=config) 587 return session 588 589 def __repr__(self) -> str: 590 return "<%s %s exitstatus=%r testsfailed=%d testscollected=%d>" % ( 591 self.__class__.__name__, 592 self.name, 593 getattr(self, "exitstatus", "<UNSET>"), 594 self.testsfailed, 595 self.testscollected, 596 ) 597 598 @property 599 def shouldstop(self) -> bool | str: 600 return self._shouldstop 601 602 @shouldstop.setter 603 def shouldstop(self, value: bool | str) -> None: 604 # The runner checks shouldfail and assumes that if it is set we are 605 # definitely stopping, so prevent unsetting it. 606 if value is False and self._shouldstop: 607 warnings.warn( 608 PytestWarning( 609 "session.shouldstop cannot be unset after it has been set; ignoring." 610 ), 611 stacklevel=2, 612 ) 613 return 614 self._shouldstop = value 615 616 @property 617 def shouldfail(self) -> bool | str: 618 return self._shouldfail 619 620 @shouldfail.setter 621 def shouldfail(self, value: bool | str) -> None: 622 # The runner checks shouldfail and assumes that if it is set we are 623 # definitely stopping, so prevent unsetting it. 624 if value is False and self._shouldfail: 625 warnings.warn( 626 PytestWarning( 627 "session.shouldfail cannot be unset after it has been set; ignoring." 628 ), 629 stacklevel=2, 630 ) 631 return 632 self._shouldfail = value 633 634 @property 635 def startpath(self) -> Path: 636 """The path from which pytest was invoked. 637 638 .. versionadded:: 7.0.0 639 """ 640 return self.config.invocation_params.dir 641 642 def _node_location_to_relpath(self, node_path: Path) -> str: 643 # bestrelpath is a quite slow function. 644 return self._bestrelpathcache[node_path] 645 646 @hookimpl(tryfirst=True) 647 def pytest_collectstart(self) -> None: 648 if self.shouldfail: 649 raise self.Failed(self.shouldfail) 650 if self.shouldstop: 651 raise self.Interrupted(self.shouldstop) 652 653 @hookimpl(tryfirst=True) 654 def pytest_runtest_logreport(self, report: TestReport | CollectReport) -> None: 655 if report.failed and not hasattr(report, "wasxfail"): 656 self.testsfailed += 1 657 maxfail = self.config.getvalue("maxfail") 658 if maxfail and self.testsfailed >= maxfail: 659 self.shouldfail = "stopping after %d failures" % (self.testsfailed) 660 661 pytest_collectreport = pytest_runtest_logreport 662 663 def isinitpath( 664 self, 665 path: str | os.PathLike[str], 666 *, 667 with_parents: bool = False, 668 ) -> bool: 669 """Is path an initial path? 670 671 An initial path is a path explicitly given to pytest on the command 672 line. 673 674 :param with_parents: 675 If set, also return True if the path is a parent of an initial path. 676 677 .. versionchanged:: 8.0 678 Added the ``with_parents`` parameter. 679 """ 680 # Optimization: Path(Path(...)) is much slower than isinstance. 681 path_ = path if isinstance(path, Path) else Path(path) 682 if with_parents: 683 return path_ in self._initialpaths_with_parents 684 else: 685 return path_ in self._initialpaths 686 687 def gethookproxy(self, fspath: os.PathLike[str]) -> pluggy.HookRelay: 688 # Optimization: Path(Path(...)) is much slower than isinstance. 689 path = fspath if isinstance(fspath, Path) else Path(fspath) 690 pm = self.config.pluginmanager 691 # Check if we have the common case of running 692 # hooks with all conftest.py files. 693 my_conftestmodules = pm._getconftestmodules(path) 694 remove_mods = pm._conftest_plugins.difference(my_conftestmodules) 695 proxy: pluggy.HookRelay 696 if remove_mods: 697 # One or more conftests are not in use at this path. 698 proxy = PathAwareHookProxy(FSHookProxy(pm, remove_mods)) # type: ignore[arg-type,assignment] 699 else: 700 # All plugins are active for this fspath. 701 proxy = self.config.hook 702 return proxy 703 704 def _collect_path( 705 self, 706 path: Path, 707 path_cache: dict[Path, Sequence[nodes.Collector]], 708 ) -> Sequence[nodes.Collector]: 709 """Create a Collector for the given path. 710 711 `path_cache` makes it so the same Collectors are returned for the same 712 path. 713 """ 714 if path in path_cache: 715 return path_cache[path] 716 717 if path.is_dir(): 718 ihook = self.gethookproxy(path.parent) 719 col: nodes.Collector | None = ihook.pytest_collect_directory( 720 path=path, parent=self 721 ) 722 cols: Sequence[nodes.Collector] = (col,) if col is not None else () 723 724 elif path.is_file(): 725 ihook = self.gethookproxy(path) 726 cols = ihook.pytest_collect_file(file_path=path, parent=self) 727 728 else: 729 # Broken symlink or invalid/missing file. 730 cols = () 731 732 path_cache[path] = cols 733 return cols 734 735 @overload 736 def perform_collect( 737 self, args: Sequence[str] | None = ..., genitems: Literal[True] = ... 738 ) -> Sequence[nodes.Item]: ... 739 740 @overload 741 def perform_collect( 742 self, args: Sequence[str] | None = ..., genitems: bool = ... 743 ) -> Sequence[nodes.Item | nodes.Collector]: ... 744 745 def perform_collect( 746 self, args: Sequence[str] | None = None, genitems: bool = True 747 ) -> Sequence[nodes.Item | nodes.Collector]: 748 """Perform the collection phase for this session. 749 750 This is called by the default :hook:`pytest_collection` hook 751 implementation; see the documentation of this hook for more details. 752 For testing purposes, it may also be called directly on a fresh 753 ``Session``. 754 755 This function normally recursively expands any collectors collected 756 from the session to their items, and only items are returned. For 757 testing purposes, this may be suppressed by passing ``genitems=False``, 758 in which case the return value contains these collectors unexpanded, 759 and ``session.items`` is empty. 760 """ 761 if args is None: 762 args = self.config.args 763 764 self.trace("perform_collect", self, args) 765 self.trace.root.indent += 1 766 767 hook = self.config.hook 768 769 self._notfound = [] 770 self._initial_parts = [] 771 self._collection_cache = {} 772 self.items = [] 773 items: Sequence[nodes.Item | nodes.Collector] = self.items 774 try: 775 initialpaths: list[Path] = [] 776 initialpaths_with_parents: list[Path] = [] 777 for arg in args: 778 collection_argument = resolve_collection_argument( 779 self.config.invocation_params.dir, 780 arg, 781 as_pypath=self.config.option.pyargs, 782 ) 783 self._initial_parts.append(collection_argument) 784 initialpaths.append(collection_argument.path) 785 initialpaths_with_parents.append(collection_argument.path) 786 initialpaths_with_parents.extend(collection_argument.path.parents) 787 self._initialpaths = frozenset(initialpaths) 788 self._initialpaths_with_parents = frozenset(initialpaths_with_parents) 789 790 rep = collect_one_node(self) 791 self.ihook.pytest_collectreport(report=rep) 792 self.trace.root.indent -= 1 793 if self._notfound: 794 errors = [] 795 for arg, collectors in self._notfound: 796 if collectors: 797 errors.append( 798 f"not found: {arg}\n(no match in any of {collectors!r})" 799 ) 800 else: 801 errors.append(f"found no collectors for {arg}") 802 803 raise UsageError(*errors) 804 805 if not genitems: 806 items = rep.result 807 else: 808 if rep.passed: 809 for node in rep.result: 810 self.items.extend(self.genitems(node)) 811 812 self.config.pluginmanager.check_pending() 813 hook.pytest_collection_modifyitems( 814 session=self, config=self.config, items=items 815 ) 816 finally: 817 self._notfound = [] 818 self._initial_parts = [] 819 self._collection_cache = {} 820 hook.pytest_collection_finish(session=self) 821 822 if genitems: 823 self.testscollected = len(items) 824 825 return items 826 827 def _collect_one_node( 828 self, 829 node: nodes.Collector, 830 handle_dupes: bool = True, 831 ) -> tuple[CollectReport, bool]: 832 if node in self._collection_cache and handle_dupes: 833 rep = self._collection_cache[node] 834 return rep, True 835 else: 836 rep = collect_one_node(node) 837 self._collection_cache[node] = rep 838 return rep, False 839 840 def collect(self) -> Iterator[nodes.Item | nodes.Collector]: 841 # This is a cache for the root directories of the initial paths. 842 # We can't use collection_cache for Session because of its special 843 # role as the bootstrapping collector. 844 path_cache: dict[Path, Sequence[nodes.Collector]] = {} 845 846 pm = self.config.pluginmanager 847 848 for collection_argument in self._initial_parts: 849 self.trace("processing argument", collection_argument) 850 self.trace.root.indent += 1 851 852 argpath = collection_argument.path 853 names = collection_argument.parts 854 module_name = collection_argument.module_name 855 856 # resolve_collection_argument() ensures this. 857 if argpath.is_dir(): 858 assert not names, f"invalid arg {(argpath, names)!r}" 859 860 paths = [argpath] 861 # Add relevant parents of the path, from the root, e.g. 862 # /a/b/c.py -> [/, /a, /a/b, /a/b/c.py] 863 if module_name is None: 864 # Paths outside of the confcutdir should not be considered. 865 for path in argpath.parents: 866 if not pm._is_in_confcutdir(path): 867 break 868 paths.insert(0, path) 869 else: 870 # For --pyargs arguments, only consider paths matching the module 871 # name. Paths beyond the package hierarchy are not included. 872 module_name_parts = module_name.split(".") 873 for i, path in enumerate(argpath.parents, 2): 874 if i > len(module_name_parts) or path.stem != module_name_parts[-i]: 875 break 876 paths.insert(0, path) 877 878 # Start going over the parts from the root, collecting each level 879 # and discarding all nodes which don't match the level's part. 880 any_matched_in_initial_part = False 881 notfound_collectors = [] 882 work: list[tuple[nodes.Collector | nodes.Item, list[Path | str]]] = [ 883 (self, [*paths, *names]) 884 ] 885 while work: 886 matchnode, matchparts = work.pop() 887 888 # Pop'd all of the parts, this is a match. 889 if not matchparts: 890 yield matchnode 891 any_matched_in_initial_part = True 892 continue 893 894 # Should have been matched by now, discard. 895 if not isinstance(matchnode, nodes.Collector): 896 continue 897 898 # Collect this level of matching. 899 # Collecting Session (self) is done directly to avoid endless 900 # recursion to this function. 901 subnodes: Sequence[nodes.Collector | nodes.Item] 902 if isinstance(matchnode, Session): 903 assert isinstance(matchparts[0], Path) 904 subnodes = matchnode._collect_path(matchparts[0], path_cache) 905 else: 906 # For backward compat, files given directly multiple 907 # times on the command line should not be deduplicated. 908 handle_dupes = not ( 909 len(matchparts) == 1 910 and isinstance(matchparts[0], Path) 911 and matchparts[0].is_file() 912 ) 913 rep, duplicate = self._collect_one_node(matchnode, handle_dupes) 914 if not duplicate and not rep.passed: 915 # Report collection failures here to avoid failing to 916 # run some test specified in the command line because 917 # the module could not be imported (#134). 918 matchnode.ihook.pytest_collectreport(report=rep) 919 if not rep.passed: 920 continue 921 subnodes = rep.result 922 923 # Prune this level. 924 any_matched_in_collector = False 925 for node in reversed(subnodes): 926 # Path part e.g. `/a/b/` in `/a/b/test_file.py::TestIt::test_it`. 927 if isinstance(matchparts[0], Path): 928 is_match = node.path == matchparts[0] 929 if sys.platform == "win32" and not is_match: 930 # In case the file paths do not match, fallback to samefile() to 931 # account for short-paths on Windows (#11895). 932 same_file = os.path.samefile(node.path, matchparts[0]) 933 # We don't want to match links to the current node, 934 # otherwise we would match the same file more than once (#12039). 935 is_match = same_file and ( 936 os.path.islink(node.path) 937 == os.path.islink(matchparts[0]) 938 ) 939 940 # Name part e.g. `TestIt` in `/a/b/test_file.py::TestIt::test_it`. 941 else: 942 # TODO: Remove parametrized workaround once collection structure contains 943 # parametrization. 944 is_match = ( 945 node.name == matchparts[0] 946 or node.name.split("[")[0] == matchparts[0] 947 ) 948 if is_match: 949 work.append((node, matchparts[1:])) 950 any_matched_in_collector = True 951 952 if not any_matched_in_collector: 953 notfound_collectors.append(matchnode) 954 955 if not any_matched_in_initial_part: 956 report_arg = "::".join((str(argpath), *names)) 957 self._notfound.append((report_arg, notfound_collectors)) 958 959 self.trace.root.indent -= 1 960 961 def genitems(self, node: nodes.Item | nodes.Collector) -> Iterator[nodes.Item]: 962 self.trace("genitems", node) 963 if isinstance(node, nodes.Item): 964 node.ihook.pytest_itemcollected(item=node) 965 yield node 966 else: 967 assert isinstance(node, nodes.Collector) 968 keepduplicates = self.config.getoption("keepduplicates") 969 # For backward compat, dedup only applies to files. 970 handle_dupes = not (keepduplicates and isinstance(node, nodes.File)) 971 rep, duplicate = self._collect_one_node(node, handle_dupes) 972 if duplicate and not keepduplicates: 973 return 974 if rep.passed: 975 for subnode in rep.result: 976 yield from self.genitems(subnode) 977 if not duplicate: 978 node.ihook.pytest_collectreport(report=rep)
The root of the collection tree.
Session
collects the initial paths given as arguments to pytest.
558 def __init__(self, config: Config) -> None: 559 super().__init__( 560 name="", 561 path=config.rootpath, 562 fspath=None, 563 parent=None, 564 config=config, 565 session=self, 566 nodeid="", 567 ) 568 self.testsfailed = 0 569 self.testscollected = 0 570 self._shouldstop: bool | str = False 571 self._shouldfail: bool | str = False 572 self.trace = config.trace.root.get("collection") 573 self._initialpaths: frozenset[Path] = frozenset() 574 self._initialpaths_with_parents: frozenset[Path] = frozenset() 575 self._notfound: list[tuple[str, Sequence[nodes.Collector]]] = [] 576 self._initial_parts: list[CollectionArgument] = [] 577 self._collection_cache: dict[nodes.Collector, CollectReport] = {} 578 self.items: list[nodes.Item] = [] 579 580 self._bestrelpathcache: dict[Path, str] = _bestrelpath_cache(config.rootpath) 581 582 self.config.pluginmanager.register(self, name="session")
634 @property 635 def startpath(self) -> Path: 636 """The path from which pytest was invoked. 637 638 .. versionadded:: 7.0.0 639 """ 640 return self.config.invocation_params.dir
The path from which pytest was invoked.
New in version 7.0.0.
653 @hookimpl(tryfirst=True) 654 def pytest_runtest_logreport(self, report: TestReport | CollectReport) -> None: 655 if report.failed and not hasattr(report, "wasxfail"): 656 self.testsfailed += 1 657 maxfail = self.config.getvalue("maxfail") 658 if maxfail and self.testsfailed >= maxfail: 659 self.shouldfail = "stopping after %d failures" % (self.testsfailed)
653 @hookimpl(tryfirst=True) 654 def pytest_runtest_logreport(self, report: TestReport | CollectReport) -> None: 655 if report.failed and not hasattr(report, "wasxfail"): 656 self.testsfailed += 1 657 maxfail = self.config.getvalue("maxfail") 658 if maxfail and self.testsfailed >= maxfail: 659 self.shouldfail = "stopping after %d failures" % (self.testsfailed)
663 def isinitpath( 664 self, 665 path: str | os.PathLike[str], 666 *, 667 with_parents: bool = False, 668 ) -> bool: 669 """Is path an initial path? 670 671 An initial path is a path explicitly given to pytest on the command 672 line. 673 674 :param with_parents: 675 If set, also return True if the path is a parent of an initial path. 676 677 .. versionchanged:: 8.0 678 Added the ``with_parents`` parameter. 679 """ 680 # Optimization: Path(Path(...)) is much slower than isinstance. 681 path_ = path if isinstance(path, Path) else Path(path) 682 if with_parents: 683 return path_ in self._initialpaths_with_parents 684 else: 685 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.
687 def gethookproxy(self, fspath: os.PathLike[str]) -> pluggy.HookRelay: 688 # Optimization: Path(Path(...)) is much slower than isinstance. 689 path = fspath if isinstance(fspath, Path) else Path(fspath) 690 pm = self.config.pluginmanager 691 # Check if we have the common case of running 692 # hooks with all conftest.py files. 693 my_conftestmodules = pm._getconftestmodules(path) 694 remove_mods = pm._conftest_plugins.difference(my_conftestmodules) 695 proxy: pluggy.HookRelay 696 if remove_mods: 697 # One or more conftests are not in use at this path. 698 proxy = PathAwareHookProxy(FSHookProxy(pm, remove_mods)) # type: ignore[arg-type,assignment] 699 else: 700 # All plugins are active for this fspath. 701 proxy = self.config.hook 702 return proxy
745 def perform_collect( 746 self, args: Sequence[str] | None = None, genitems: bool = True 747 ) -> Sequence[nodes.Item | nodes.Collector]: 748 """Perform the collection phase for this session. 749 750 This is called by the default :hook:`pytest_collection` hook 751 implementation; see the documentation of this hook for more details. 752 For testing purposes, it may also be called directly on a fresh 753 ``Session``. 754 755 This function normally recursively expands any collectors collected 756 from the session to their items, and only items are returned. For 757 testing purposes, this may be suppressed by passing ``genitems=False``, 758 in which case the return value contains these collectors unexpanded, 759 and ``session.items`` is empty. 760 """ 761 if args is None: 762 args = self.config.args 763 764 self.trace("perform_collect", self, args) 765 self.trace.root.indent += 1 766 767 hook = self.config.hook 768 769 self._notfound = [] 770 self._initial_parts = [] 771 self._collection_cache = {} 772 self.items = [] 773 items: Sequence[nodes.Item | nodes.Collector] = self.items 774 try: 775 initialpaths: list[Path] = [] 776 initialpaths_with_parents: list[Path] = [] 777 for arg in args: 778 collection_argument = resolve_collection_argument( 779 self.config.invocation_params.dir, 780 arg, 781 as_pypath=self.config.option.pyargs, 782 ) 783 self._initial_parts.append(collection_argument) 784 initialpaths.append(collection_argument.path) 785 initialpaths_with_parents.append(collection_argument.path) 786 initialpaths_with_parents.extend(collection_argument.path.parents) 787 self._initialpaths = frozenset(initialpaths) 788 self._initialpaths_with_parents = frozenset(initialpaths_with_parents) 789 790 rep = collect_one_node(self) 791 self.ihook.pytest_collectreport(report=rep) 792 self.trace.root.indent -= 1 793 if self._notfound: 794 errors = [] 795 for arg, collectors in self._notfound: 796 if collectors: 797 errors.append( 798 f"not found: {arg}\n(no match in any of {collectors!r})" 799 ) 800 else: 801 errors.append(f"found no collectors for {arg}") 802 803 raise UsageError(*errors) 804 805 if not genitems: 806 items = rep.result 807 else: 808 if rep.passed: 809 for node in rep.result: 810 self.items.extend(self.genitems(node)) 811 812 self.config.pluginmanager.check_pending() 813 hook.pytest_collection_modifyitems( 814 session=self, config=self.config, items=items 815 ) 816 finally: 817 self._notfound = [] 818 self._initial_parts = [] 819 self._collection_cache = {} 820 hook.pytest_collection_finish(session=self) 821 822 if genitems: 823 self.testscollected = len(items) 824 825 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.
840 def collect(self) -> Iterator[nodes.Item | nodes.Collector]: 841 # This is a cache for the root directories of the initial paths. 842 # We can't use collection_cache for Session because of its special 843 # role as the bootstrapping collector. 844 path_cache: dict[Path, Sequence[nodes.Collector]] = {} 845 846 pm = self.config.pluginmanager 847 848 for collection_argument in self._initial_parts: 849 self.trace("processing argument", collection_argument) 850 self.trace.root.indent += 1 851 852 argpath = collection_argument.path 853 names = collection_argument.parts 854 module_name = collection_argument.module_name 855 856 # resolve_collection_argument() ensures this. 857 if argpath.is_dir(): 858 assert not names, f"invalid arg {(argpath, names)!r}" 859 860 paths = [argpath] 861 # Add relevant parents of the path, from the root, e.g. 862 # /a/b/c.py -> [/, /a, /a/b, /a/b/c.py] 863 if module_name is None: 864 # Paths outside of the confcutdir should not be considered. 865 for path in argpath.parents: 866 if not pm._is_in_confcutdir(path): 867 break 868 paths.insert(0, path) 869 else: 870 # For --pyargs arguments, only consider paths matching the module 871 # name. Paths beyond the package hierarchy are not included. 872 module_name_parts = module_name.split(".") 873 for i, path in enumerate(argpath.parents, 2): 874 if i > len(module_name_parts) or path.stem != module_name_parts[-i]: 875 break 876 paths.insert(0, path) 877 878 # Start going over the parts from the root, collecting each level 879 # and discarding all nodes which don't match the level's part. 880 any_matched_in_initial_part = False 881 notfound_collectors = [] 882 work: list[tuple[nodes.Collector | nodes.Item, list[Path | str]]] = [ 883 (self, [*paths, *names]) 884 ] 885 while work: 886 matchnode, matchparts = work.pop() 887 888 # Pop'd all of the parts, this is a match. 889 if not matchparts: 890 yield matchnode 891 any_matched_in_initial_part = True 892 continue 893 894 # Should have been matched by now, discard. 895 if not isinstance(matchnode, nodes.Collector): 896 continue 897 898 # Collect this level of matching. 899 # Collecting Session (self) is done directly to avoid endless 900 # recursion to this function. 901 subnodes: Sequence[nodes.Collector | nodes.Item] 902 if isinstance(matchnode, Session): 903 assert isinstance(matchparts[0], Path) 904 subnodes = matchnode._collect_path(matchparts[0], path_cache) 905 else: 906 # For backward compat, files given directly multiple 907 # times on the command line should not be deduplicated. 908 handle_dupes = not ( 909 len(matchparts) == 1 910 and isinstance(matchparts[0], Path) 911 and matchparts[0].is_file() 912 ) 913 rep, duplicate = self._collect_one_node(matchnode, handle_dupes) 914 if not duplicate and not rep.passed: 915 # Report collection failures here to avoid failing to 916 # run some test specified in the command line because 917 # the module could not be imported (#134). 918 matchnode.ihook.pytest_collectreport(report=rep) 919 if not rep.passed: 920 continue 921 subnodes = rep.result 922 923 # Prune this level. 924 any_matched_in_collector = False 925 for node in reversed(subnodes): 926 # Path part e.g. `/a/b/` in `/a/b/test_file.py::TestIt::test_it`. 927 if isinstance(matchparts[0], Path): 928 is_match = node.path == matchparts[0] 929 if sys.platform == "win32" and not is_match: 930 # In case the file paths do not match, fallback to samefile() to 931 # account for short-paths on Windows (#11895). 932 same_file = os.path.samefile(node.path, matchparts[0]) 933 # We don't want to match links to the current node, 934 # otherwise we would match the same file more than once (#12039). 935 is_match = same_file and ( 936 os.path.islink(node.path) 937 == os.path.islink(matchparts[0]) 938 ) 939 940 # Name part e.g. `TestIt` in `/a/b/test_file.py::TestIt::test_it`. 941 else: 942 # TODO: Remove parametrized workaround once collection structure contains 943 # parametrization. 944 is_match = ( 945 node.name == matchparts[0] 946 or node.name.split("[")[0] == matchparts[0] 947 ) 948 if is_match: 949 work.append((node, matchparts[1:])) 950 any_matched_in_collector = True 951 952 if not any_matched_in_collector: 953 notfound_collectors.append(matchnode) 954 955 if not any_matched_in_initial_part: 956 report_arg = "::".join((str(argpath), *names)) 957 self._notfound.append((report_arg, notfound_collectors)) 958 959 self.trace.root.indent -= 1
Collect children (items and collectors) for this collector.
961 def genitems(self, node: nodes.Item | nodes.Collector) -> Iterator[nodes.Item]: 962 self.trace("genitems", node) 963 if isinstance(node, nodes.Item): 964 node.ihook.pytest_itemcollected(item=node) 965 yield node 966 else: 967 assert isinstance(node, nodes.Collector) 968 keepduplicates = self.config.getoption("keepduplicates") 969 # For backward compat, dedup only applies to files. 970 handle_dupes = not (keepduplicates and isinstance(node, nodes.File)) 971 rep, duplicate = self._collect_one_node(node, handle_dupes) 972 if duplicate and not keepduplicates: 973 return 974 if rep.passed: 975 for subnode in rep.result: 976 yield from self.genitems(subnode) 977 if not duplicate: 978 node.ihook.pytest_collectreport(report=rep)
Inherited Members
- _pytest.nodes.Node
- fspath
- name
- parent
- path
- keywords
- own_markers
- extra_keyword_matches
- stash
- from_parent
- ihook
- warn
- nodeid
- setup
- teardown
- iter_parents
- listchain
- add_marker
- iter_markers
- iter_markers_with_node
- get_closest_marker
- listextrakeywords
- listnames
- addfinalizer
- getparent
- config
- session
Signals that the test run was interrupted.
Inherited Members
- builtins.KeyboardInterrupt
- KeyboardInterrupt
- builtins.BaseException
- with_traceback
- add_note
- args
Signals a stop as failed test run.
Inherited Members
- builtins.Exception
- Exception
- builtins.BaseException
- with_traceback
- add_note
- args
272 @classmethod 273 def set_trace(cls, *args, **kwargs) -> None: 274 """Invoke debugging via ``Pdb.set_trace``, dropping any IO capturing.""" 275 frame = sys._getframe().f_back 276 _pdb = cls._init_pdb("set_trace", *args, **kwargs) 277 _pdb.set_trace(frame)
Invoke debugging via Pdb.set_trace
, dropping any IO capturing.
127@_with_exception(Skipped) 128def skip( 129 reason: str = "", 130 *, 131 allow_module_level: bool = False, 132) -> NoReturn: 133 """Skip an executing test with the given message. 134 135 This function should be called only during testing (setup, call or teardown) or 136 during collection by using the ``allow_module_level`` flag. This function can 137 be called in doctests as well. 138 139 :param reason: 140 The message to show the user as reason for the skip. 141 142 :param allow_module_level: 143 Allows this function to be called at module level. 144 Raising the skip exception at module level will stop 145 the execution of the module and prevent the collection of all tests in the module, 146 even those defined before the `skip` call. 147 148 Defaults to False. 149 150 :raises pytest.skip.Exception: 151 The exception that is raised. 152 153 .. note:: 154 It is better to use the :ref:`pytest.mark.skipif ref` marker when 155 possible to declare a test to be skipped under certain conditions 156 like mismatching platforms or dependencies. 157 Similarly, use the ``# doctest: +SKIP`` directive (see :py:data:`doctest.SKIP`) 158 to skip a doctest statically. 159 """ 160 __tracebackhide__ = True 161 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.
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__ = ()
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.
42@final 43@dataclasses.dataclass 44class TempPathFactory: 45 """Factory for temporary directories under the common base temp directory, 46 as discussed at :ref:`temporary directory location and retention`. 47 """ 48 49 _given_basetemp: Path | None 50 # pluggy TagTracerSub, not currently exposed, so Any. 51 _trace: Any 52 _basetemp: Path | None 53 _retention_count: int 54 _retention_policy: RetentionType 55 56 def __init__( 57 self, 58 given_basetemp: Path | None, 59 retention_count: int, 60 retention_policy: RetentionType, 61 trace, 62 basetemp: Path | None = None, 63 *, 64 _ispytest: bool = False, 65 ) -> None: 66 check_ispytest(_ispytest) 67 if given_basetemp is None: 68 self._given_basetemp = None 69 else: 70 # Use os.path.abspath() to get absolute path instead of resolve() as it 71 # does not work the same in all platforms (see #4427). 72 # Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012). 73 self._given_basetemp = Path(os.path.abspath(str(given_basetemp))) 74 self._trace = trace 75 self._retention_count = retention_count 76 self._retention_policy = retention_policy 77 self._basetemp = basetemp 78 79 @classmethod 80 def from_config( 81 cls, 82 config: Config, 83 *, 84 _ispytest: bool = False, 85 ) -> TempPathFactory: 86 """Create a factory according to pytest configuration. 87 88 :meta private: 89 """ 90 check_ispytest(_ispytest) 91 count = int(config.getini("tmp_path_retention_count")) 92 if count < 0: 93 raise ValueError( 94 f"tmp_path_retention_count must be >= 0. Current input: {count}." 95 ) 96 97 policy = config.getini("tmp_path_retention_policy") 98 if policy not in ("all", "failed", "none"): 99 raise ValueError( 100 f"tmp_path_retention_policy must be either all, failed, none. Current input: {policy}." 101 ) 102 103 return cls( 104 given_basetemp=config.option.basetemp, 105 trace=config.trace.get("tmpdir"), 106 retention_count=count, 107 retention_policy=policy, 108 _ispytest=True, 109 ) 110 111 def _ensure_relative_to_basetemp(self, basename: str) -> str: 112 basename = os.path.normpath(basename) 113 if (self.getbasetemp() / basename).resolve().parent != self.getbasetemp(): 114 raise ValueError(f"{basename} is not a normalized and relative path") 115 return basename 116 117 def mktemp(self, basename: str, numbered: bool = True) -> Path: 118 """Create a new temporary directory managed by the factory. 119 120 :param basename: 121 Directory base name, must be a relative path. 122 123 :param numbered: 124 If ``True``, ensure the directory is unique by adding a numbered 125 suffix greater than any existing one: ``basename="foo-"`` and ``numbered=True`` 126 means that this function will create directories named ``"foo-0"``, 127 ``"foo-1"``, ``"foo-2"`` and so on. 128 129 :returns: 130 The path to the new directory. 131 """ 132 basename = self._ensure_relative_to_basetemp(basename) 133 if not numbered: 134 p = self.getbasetemp().joinpath(basename) 135 p.mkdir(mode=0o700) 136 else: 137 p = make_numbered_dir(root=self.getbasetemp(), prefix=basename, mode=0o700) 138 self._trace("mktemp", p) 139 return p 140 141 def getbasetemp(self) -> Path: 142 """Return the base temporary directory, creating it if needed. 143 144 :returns: 145 The base temporary directory. 146 """ 147 if self._basetemp is not None: 148 return self._basetemp 149 150 if self._given_basetemp is not None: 151 basetemp = self._given_basetemp 152 if basetemp.exists(): 153 rm_rf(basetemp) 154 basetemp.mkdir(mode=0o700) 155 basetemp = basetemp.resolve() 156 else: 157 from_env = os.environ.get("PYTEST_DEBUG_TEMPROOT") 158 temproot = Path(from_env or tempfile.gettempdir()).resolve() 159 user = get_user() or "unknown" 160 # use a sub-directory in the temproot to speed-up 161 # make_numbered_dir() call 162 rootdir = temproot.joinpath(f"pytest-of-{user}") 163 try: 164 rootdir.mkdir(mode=0o700, exist_ok=True) 165 except OSError: 166 # getuser() likely returned illegal characters for the platform, use unknown back off mechanism 167 rootdir = temproot.joinpath("pytest-of-unknown") 168 rootdir.mkdir(mode=0o700, exist_ok=True) 169 # Because we use exist_ok=True with a predictable name, make sure 170 # we are the owners, to prevent any funny business (on unix, where 171 # temproot is usually shared). 172 # Also, to keep things private, fixup any world-readable temp 173 # rootdir's permissions. Historically 0o755 was used, so we can't 174 # just error out on this, at least for a while. 175 uid = get_user_id() 176 if uid is not None: 177 rootdir_stat = rootdir.stat() 178 if rootdir_stat.st_uid != uid: 179 raise OSError( 180 f"The temporary directory {rootdir} is not owned by the current user. " 181 "Fix this and try again." 182 ) 183 if (rootdir_stat.st_mode & 0o077) != 0: 184 os.chmod(rootdir, rootdir_stat.st_mode & ~0o077) 185 keep = self._retention_count 186 if self._retention_policy == "none": 187 keep = 0 188 basetemp = make_numbered_dir_with_cleanup( 189 prefix="pytest-", 190 root=rootdir, 191 keep=keep, 192 lock_timeout=LOCK_TIMEOUT, 193 mode=0o700, 194 ) 195 assert basetemp is not None, basetemp 196 self._basetemp = basetemp 197 self._trace("new basetemp", basetemp) 198 return basetemp
Factory for temporary directories under the common base temp directory,
as discussed at :ref:temporary directory location and retention
.
56 def __init__( 57 self, 58 given_basetemp: Path | None, 59 retention_count: int, 60 retention_policy: RetentionType, 61 trace, 62 basetemp: Path | None = None, 63 *, 64 _ispytest: bool = False, 65 ) -> None: 66 check_ispytest(_ispytest) 67 if given_basetemp is None: 68 self._given_basetemp = None 69 else: 70 # Use os.path.abspath() to get absolute path instead of resolve() as it 71 # does not work the same in all platforms (see #4427). 72 # Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012). 73 self._given_basetemp = Path(os.path.abspath(str(given_basetemp))) 74 self._trace = trace 75 self._retention_count = retention_count 76 self._retention_policy = retention_policy 77 self._basetemp = basetemp
79 @classmethod 80 def from_config( 81 cls, 82 config: Config, 83 *, 84 _ispytest: bool = False, 85 ) -> TempPathFactory: 86 """Create a factory according to pytest configuration. 87 88 :meta private: 89 """ 90 check_ispytest(_ispytest) 91 count = int(config.getini("tmp_path_retention_count")) 92 if count < 0: 93 raise ValueError( 94 f"tmp_path_retention_count must be >= 0. Current input: {count}." 95 ) 96 97 policy = config.getini("tmp_path_retention_policy") 98 if policy not in ("all", "failed", "none"): 99 raise ValueError( 100 f"tmp_path_retention_policy must be either all, failed, none. Current input: {policy}." 101 ) 102 103 return cls( 104 given_basetemp=config.option.basetemp, 105 trace=config.trace.get("tmpdir"), 106 retention_count=count, 107 retention_policy=policy, 108 _ispytest=True, 109 )
Create a factory according to pytest configuration.
:meta private:
117 def mktemp(self, basename: str, numbered: bool = True) -> Path: 118 """Create a new temporary directory managed by the factory. 119 120 :param basename: 121 Directory base name, must be a relative path. 122 123 :param numbered: 124 If ``True``, ensure the directory is unique by adding a numbered 125 suffix greater than any existing one: ``basename="foo-"`` and ``numbered=True`` 126 means that this function will create directories named ``"foo-0"``, 127 ``"foo-1"``, ``"foo-2"`` and so on. 128 129 :returns: 130 The path to the new directory. 131 """ 132 basename = self._ensure_relative_to_basetemp(basename) 133 if not numbered: 134 p = self.getbasetemp().joinpath(basename) 135 p.mkdir(mode=0o700) 136 else: 137 p = make_numbered_dir(root=self.getbasetemp(), prefix=basename, mode=0o700) 138 self._trace("mktemp", p) 139 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.
141 def getbasetemp(self) -> Path: 142 """Return the base temporary directory, creating it if needed. 143 144 :returns: 145 The base temporary directory. 146 """ 147 if self._basetemp is not None: 148 return self._basetemp 149 150 if self._given_basetemp is not None: 151 basetemp = self._given_basetemp 152 if basetemp.exists(): 153 rm_rf(basetemp) 154 basetemp.mkdir(mode=0o700) 155 basetemp = basetemp.resolve() 156 else: 157 from_env = os.environ.get("PYTEST_DEBUG_TEMPROOT") 158 temproot = Path(from_env or tempfile.gettempdir()).resolve() 159 user = get_user() or "unknown" 160 # use a sub-directory in the temproot to speed-up 161 # make_numbered_dir() call 162 rootdir = temproot.joinpath(f"pytest-of-{user}") 163 try: 164 rootdir.mkdir(mode=0o700, exist_ok=True) 165 except OSError: 166 # getuser() likely returned illegal characters for the platform, use unknown back off mechanism 167 rootdir = temproot.joinpath("pytest-of-unknown") 168 rootdir.mkdir(mode=0o700, exist_ok=True) 169 # Because we use exist_ok=True with a predictable name, make sure 170 # we are the owners, to prevent any funny business (on unix, where 171 # temproot is usually shared). 172 # Also, to keep things private, fixup any world-readable temp 173 # rootdir's permissions. Historically 0o755 was used, so we can't 174 # just error out on this, at least for a while. 175 uid = get_user_id() 176 if uid is not None: 177 rootdir_stat = rootdir.stat() 178 if rootdir_stat.st_uid != uid: 179 raise OSError( 180 f"The temporary directory {rootdir} is not owned by the current user. " 181 "Fix this and try again." 182 ) 183 if (rootdir_stat.st_mode & 0o077) != 0: 184 os.chmod(rootdir, rootdir_stat.st_mode & ~0o077) 185 keep = self._retention_count 186 if self._retention_policy == "none": 187 keep = 0 188 basetemp = make_numbered_dir_with_cleanup( 189 prefix="pytest-", 190 root=rootdir, 191 keep=keep, 192 lock_timeout=LOCK_TIMEOUT, 193 mode=0o700, 194 ) 195 assert basetemp is not None, basetemp 196 self._basetemp = basetemp 197 self._trace("new basetemp", basetemp) 198 return basetemp
Return the base temporary directory, creating it if needed.
:returns: The base temporary directory.
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.
Inherited Members
- builtins.Exception
- Exception
- builtins.BaseException
- with_traceback
- add_note
- args
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 # Defined by skipping plugin. 265 # xfail reason if xfailed, otherwise not defined. Use hasattr to distinguish. 266 wasxfail: str 267 268 def __init__( 269 self, 270 nodeid: str, 271 location: tuple[str, int | None, str], 272 keywords: Mapping[str, Any], 273 outcome: Literal["passed", "failed", "skipped"], 274 longrepr: None 275 | ExceptionInfo[BaseException] 276 | tuple[str, int, str] 277 | str 278 | TerminalRepr, 279 when: Literal["setup", "call", "teardown"], 280 sections: Iterable[tuple[str, str]] = (), 281 duration: float = 0, 282 start: float = 0, 283 stop: float = 0, 284 user_properties: Iterable[tuple[str, object]] | None = None, 285 **extra, 286 ) -> None: 287 #: Normalized collection nodeid. 288 self.nodeid = nodeid 289 290 #: A (filesystempath, lineno, domaininfo) tuple indicating the 291 #: actual location of a test item - it might be different from the 292 #: collected one e.g. if a method is inherited from a different module. 293 #: The filesystempath may be relative to ``config.rootdir``. 294 #: The line number is 0-based. 295 self.location: tuple[str, int | None, str] = location 296 297 #: A name -> value dictionary containing all keywords and 298 #: markers associated with a test invocation. 299 self.keywords: Mapping[str, Any] = keywords 300 301 #: Test outcome, always one of "passed", "failed", "skipped". 302 self.outcome = outcome 303 304 #: None or a failure representation. 305 self.longrepr = longrepr 306 307 #: One of 'setup', 'call', 'teardown' to indicate runtest phase. 308 self.when = when 309 310 #: User properties is a list of tuples (name, value) that holds user 311 #: defined properties of the test. 312 self.user_properties = list(user_properties or []) 313 314 #: Tuples of str ``(heading, content)`` with extra information 315 #: for the test report. Used by pytest to add text captured 316 #: from ``stdout``, ``stderr``, and intercepted logging events. May 317 #: be used by other plugins to add arbitrary information to reports. 318 self.sections = list(sections) 319 320 #: Time it took to run just the test. 321 self.duration: float = duration 322 323 #: The system time when the call started, in seconds since the epoch. 324 self.start: float = start 325 #: The system time when the call ended, in seconds since the epoch. 326 self.stop: float = stop 327 328 self.__dict__.update(extra) 329 330 def __repr__(self) -> str: 331 return f"<{self.__class__.__name__} {self.nodeid!r} when={self.when!r} outcome={self.outcome!r}>" 332 333 @classmethod 334 def from_item_and_call(cls, item: Item, call: CallInfo[None]) -> TestReport: 335 """Create and fill a TestReport with standard item and call info. 336 337 :param item: The item. 338 :param call: The call info. 339 """ 340 when = call.when 341 # Remove "collect" from the Literal type -- only for collection calls. 342 assert when != "collect" 343 duration = call.duration 344 start = call.start 345 stop = call.stop 346 keywords = {x: 1 for x in item.keywords} 347 excinfo = call.excinfo 348 sections = [] 349 if not call.excinfo: 350 outcome: Literal["passed", "failed", "skipped"] = "passed" 351 longrepr: ( 352 None 353 | ExceptionInfo[BaseException] 354 | tuple[str, int, str] 355 | str 356 | TerminalRepr 357 ) = None 358 else: 359 if not isinstance(excinfo, ExceptionInfo): 360 outcome = "failed" 361 longrepr = excinfo 362 elif isinstance(excinfo.value, skip.Exception): 363 outcome = "skipped" 364 r = excinfo._getreprcrash() 365 assert ( 366 r is not None 367 ), "There should always be a traceback entry for skipping a test." 368 if excinfo.value._use_item_location: 369 path, line = item.reportinfo()[:2] 370 assert line is not None 371 longrepr = os.fspath(path), line + 1, r.message 372 else: 373 longrepr = (str(r.path), r.lineno, r.message) 374 else: 375 outcome = "failed" 376 if call.when == "call": 377 longrepr = item.repr_failure(excinfo) 378 else: # exception in setup or teardown 379 longrepr = item._repr_failure_py( 380 excinfo, style=item.config.getoption("tbstyle", "auto") 381 ) 382 for rwhen, key, content in item._report_sections: 383 sections.append((f"Captured {key} {rwhen}", content)) 384 return cls( 385 item.nodeid, 386 item.location, 387 keywords, 388 outcome, 389 longrepr, 390 when, 391 sections, 392 duration, 393 start, 394 stop, 395 user_properties=item.user_properties, 396 )
Basic test report object (also used for setup and teardown calls if they fail).
Reports can contain arbitrary extra attributes.
268 def __init__( 269 self, 270 nodeid: str, 271 location: tuple[str, int | None, str], 272 keywords: Mapping[str, Any], 273 outcome: Literal["passed", "failed", "skipped"], 274 longrepr: None 275 | ExceptionInfo[BaseException] 276 | tuple[str, int, str] 277 | str 278 | TerminalRepr, 279 when: Literal["setup", "call", "teardown"], 280 sections: Iterable[tuple[str, str]] = (), 281 duration: float = 0, 282 start: float = 0, 283 stop: float = 0, 284 user_properties: Iterable[tuple[str, object]] | None = None, 285 **extra, 286 ) -> None: 287 #: Normalized collection nodeid. 288 self.nodeid = nodeid 289 290 #: A (filesystempath, lineno, domaininfo) tuple indicating the 291 #: actual location of a test item - it might be different from the 292 #: collected one e.g. if a method is inherited from a different module. 293 #: The filesystempath may be relative to ``config.rootdir``. 294 #: The line number is 0-based. 295 self.location: tuple[str, int | None, str] = location 296 297 #: A name -> value dictionary containing all keywords and 298 #: markers associated with a test invocation. 299 self.keywords: Mapping[str, Any] = keywords 300 301 #: Test outcome, always one of "passed", "failed", "skipped". 302 self.outcome = outcome 303 304 #: None or a failure representation. 305 self.longrepr = longrepr 306 307 #: One of 'setup', 'call', 'teardown' to indicate runtest phase. 308 self.when = when 309 310 #: User properties is a list of tuples (name, value) that holds user 311 #: defined properties of the test. 312 self.user_properties = list(user_properties or []) 313 314 #: Tuples of str ``(heading, content)`` with extra information 315 #: for the test report. Used by pytest to add text captured 316 #: from ``stdout``, ``stderr``, and intercepted logging events. May 317 #: be used by other plugins to add arbitrary information to reports. 318 self.sections = list(sections) 319 320 #: Time it took to run just the test. 321 self.duration: float = duration 322 323 #: The system time when the call started, in seconds since the epoch. 324 self.start: float = start 325 #: The system time when the call ended, in seconds since the epoch. 326 self.stop: float = stop 327 328 self.__dict__.update(extra)
333 @classmethod 334 def from_item_and_call(cls, item: Item, call: CallInfo[None]) -> TestReport: 335 """Create and fill a TestReport with standard item and call info. 336 337 :param item: The item. 338 :param call: The call info. 339 """ 340 when = call.when 341 # Remove "collect" from the Literal type -- only for collection calls. 342 assert when != "collect" 343 duration = call.duration 344 start = call.start 345 stop = call.stop 346 keywords = {x: 1 for x in item.keywords} 347 excinfo = call.excinfo 348 sections = [] 349 if not call.excinfo: 350 outcome: Literal["passed", "failed", "skipped"] = "passed" 351 longrepr: ( 352 None 353 | ExceptionInfo[BaseException] 354 | tuple[str, int, str] 355 | str 356 | TerminalRepr 357 ) = None 358 else: 359 if not isinstance(excinfo, ExceptionInfo): 360 outcome = "failed" 361 longrepr = excinfo 362 elif isinstance(excinfo.value, skip.Exception): 363 outcome = "skipped" 364 r = excinfo._getreprcrash() 365 assert ( 366 r is not None 367 ), "There should always be a traceback entry for skipping a test." 368 if excinfo.value._use_item_location: 369 path, line = item.reportinfo()[:2] 370 assert line is not None 371 longrepr = os.fspath(path), line + 1, r.message 372 else: 373 longrepr = (str(r.path), r.lineno, r.message) 374 else: 375 outcome = "failed" 376 if call.when == "call": 377 longrepr = item.repr_failure(excinfo) 378 else: # exception in setup or teardown 379 longrepr = item._repr_failure_py( 380 excinfo, style=item.config.getoption("tbstyle", "auto") 381 ) 382 for rwhen, key, content in item._report_sections: 383 sections.append((f"Captured {key} {rwhen}", content)) 384 return cls( 385 item.nodeid, 386 item.location, 387 keywords, 388 outcome, 389 longrepr, 390 when, 391 sections, 392 duration, 393 start, 394 stop, 395 user_properties=item.user_properties, 396 )
Create and fill a TestReport with standard item and call info.
Parameters
- item: The item.
- call: The call info.
Inherited Members
- _pytest.reports.BaseReport
- toterminal
- get_sections
- longreprtext
- caplog
- capstdout
- capstderr
- passed
- failed
- skipped
- fspath
- count_towards_summary
- head_line
114class TestShortLogReport(NamedTuple): 115 """Used to store the test status result category, shortletter and verbose word. 116 For example ``"rerun", "R", ("RERUN", {"yellow": True})``. 117 118 :ivar category: 119 The class of result, for example ``“passed”``, ``“skipped”``, ``“error”``, or the empty string. 120 121 :ivar letter: 122 The short letter shown as testing progresses, for example ``"."``, ``"s"``, ``"E"``, or the empty string. 123 124 :ivar word: 125 Verbose word is shown as testing progresses in verbose mode, for example ``"PASSED"``, ``"SKIPPED"``, 126 ``"ERROR"``, or the empty string. 127 """ 128 129 category: str 130 letter: str 131 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.
Create new instance of TestShortLogReport(category, letter, word)
Inherited Members
- builtins.tuple
- index
- count
Error in pytest usage or invocation.
Inherited Members
- builtins.Exception
- Exception
- builtins.BaseException
- with_traceback
- add_note
- args
170class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg] 171 """A context manager to record raised warnings. 172 173 Each recorded warning is an instance of :class:`warnings.WarningMessage`. 174 175 Adapted from `warnings.catch_warnings`. 176 177 .. note:: 178 ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated 179 differently; see :ref:`ensuring_function_triggers`. 180 181 """ 182 183 def __init__(self, *, _ispytest: bool = False) -> None: 184 check_ispytest(_ispytest) 185 super().__init__(record=True) 186 self._entered = False 187 self._list: list[warnings.WarningMessage] = [] 188 189 @property 190 def list(self) -> list[warnings.WarningMessage]: 191 """The list of recorded warnings.""" 192 return self._list 193 194 def __getitem__(self, i: int) -> warnings.WarningMessage: 195 """Get a recorded warning by index.""" 196 return self._list[i] 197 198 def __iter__(self) -> Iterator[warnings.WarningMessage]: 199 """Iterate through the recorded warnings.""" 200 return iter(self._list) 201 202 def __len__(self) -> int: 203 """The number of recorded warnings.""" 204 return len(self._list) 205 206 def pop(self, cls: type[Warning] = Warning) -> warnings.WarningMessage: 207 """Pop the first recorded warning which is an instance of ``cls``, 208 but not an instance of a child class of any other match. 209 Raises ``AssertionError`` if there is no match. 210 """ 211 best_idx: int | None = None 212 for i, w in enumerate(self._list): 213 if w.category == cls: 214 return self._list.pop(i) # exact match, stop looking 215 if issubclass(w.category, cls) and ( 216 best_idx is None 217 or not issubclass(w.category, self._list[best_idx].category) 218 ): 219 best_idx = i 220 if best_idx is not None: 221 return self._list.pop(best_idx) 222 __tracebackhide__ = True 223 raise AssertionError(f"{cls!r} not found in warning list") 224 225 def clear(self) -> None: 226 """Clear the list of recorded warnings.""" 227 self._list[:] = [] 228 229 def __enter__(self) -> Self: 230 if self._entered: 231 __tracebackhide__ = True 232 raise RuntimeError(f"Cannot enter {self!r} twice") 233 _list = super().__enter__() 234 # record=True means it's None. 235 assert _list is not None 236 self._list = _list 237 warnings.simplefilter("always") 238 return self 239 240 def __exit__( 241 self, 242 exc_type: type[BaseException] | None, 243 exc_val: BaseException | None, 244 exc_tb: TracebackType | None, 245 ) -> None: 246 if not self._entered: 247 __tracebackhide__ = True 248 raise RuntimeError(f"Cannot exit {self!r} without entering first") 249 250 super().__exit__(exc_type, exc_val, exc_tb) 251 252 # Built-in catch_warnings does not reset entered state so we do it 253 # manually here for this context manager to become reusable. 254 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
.
183 def __init__(self, *, _ispytest: bool = False) -> None: 184 check_ispytest(_ispytest) 185 super().__init__(record=True) 186 self._entered = False 187 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.
189 @property 190 def list(self) -> list[warnings.WarningMessage]: 191 """The list of recorded warnings.""" 192 return self._list
The list of recorded warnings.
206 def pop(self, cls: type[Warning] = Warning) -> warnings.WarningMessage: 207 """Pop the first recorded warning which is an instance of ``cls``, 208 but not an instance of a child class of any other match. 209 Raises ``AssertionError`` if there is no match. 210 """ 211 best_idx: int | None = None 212 for i, w in enumerate(self._list): 213 if w.category == cls: 214 return self._list.pop(i) # exact match, stop looking 215 if issubclass(w.category, cls) and ( 216 best_idx is None 217 or not issubclass(w.category, self._list[best_idx].category) 218 ): 219 best_idx = i 220 if best_idx is not None: 221 return self._list.pop(best_idx) 222 __tracebackhide__ = True 223 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.
106def warns( 107 expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning, 108 *args: Any, 109 match: str | Pattern[str] | None = None, 110 **kwargs: Any, 111) -> WarningsChecker | Any: 112 r"""Assert that code raises a particular class of warning. 113 114 Specifically, the parameter ``expected_warning`` can be a warning class or tuple 115 of warning classes, and the code inside the ``with`` block must issue at least one 116 warning of that class or classes. 117 118 This helper produces a list of :class:`warnings.WarningMessage` objects, one for 119 each warning emitted (regardless of whether it is an ``expected_warning`` or not). 120 Since pytest 8.0, unmatched warnings are also re-emitted when the context closes. 121 122 This function can be used as a context manager:: 123 124 >>> import pytest 125 >>> with pytest.warns(RuntimeWarning): 126 ... warnings.warn("my warning", RuntimeWarning) 127 128 In the context manager form you may use the keyword argument ``match`` to assert 129 that the warning matches a text or regex:: 130 131 >>> with pytest.warns(UserWarning, match='must be 0 or None'): 132 ... warnings.warn("value must be 0 or None", UserWarning) 133 134 >>> with pytest.warns(UserWarning, match=r'must be \d+$'): 135 ... warnings.warn("value must be 42", UserWarning) 136 137 >>> with pytest.warns(UserWarning): # catch re-emitted warning 138 ... with pytest.warns(UserWarning, match=r'must be \d+$'): 139 ... warnings.warn("this is not here", UserWarning) 140 Traceback (most recent call last): 141 ... 142 Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted... 143 144 **Using with** ``pytest.mark.parametrize`` 145 146 When using :ref:`pytest.mark.parametrize ref` it is possible to parametrize tests 147 such that some runs raise a warning and others do not. 148 149 This could be achieved in the same way as with exceptions, see 150 :ref:`parametrizing_conditional_raising` for an example. 151 152 """ 153 __tracebackhide__ = True 154 if not args: 155 if kwargs: 156 argnames = ", ".join(sorted(kwargs)) 157 raise TypeError( 158 f"Unexpected keyword arguments passed to pytest.warns: {argnames}" 159 "\nUse context-manager form instead?" 160 ) 161 return WarningsChecker(expected_warning, match_expr=match, _ispytest=True) 162 else: 163 func = args[0] 164 if not callable(func): 165 raise TypeError(f"{func!r} object (type: {type(func)}) must be callable") 166 with WarningsChecker(expected_warning, _ispytest=True): 167 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.
186@_with_exception(XFailed) 187def xfail(reason: str = "") -> NoReturn: 188 """Imperatively xfail an executing test or setup function with the given reason. 189 190 This function should be called only during testing (setup, call or teardown). 191 192 No other code is executed after using ``xfail()`` (it is implemented 193 internally by raising an exception). 194 195 :param reason: 196 The message to show the user as reason for the xfail. 197 198 .. note:: 199 It is better to use the :ref:`pytest.mark.xfail ref` marker when 200 possible to declare a test to be xfailed under certain conditions 201 like known bugs or missing features. 202 203 :raises pytest.xfail.Exception: 204 The exception that is raised. 205 """ 206 __tracebackhide__ = True 207 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.
1319def yield_fixture( 1320 fixture_function=None, 1321 *args, 1322 scope="function", 1323 params=None, 1324 autouse=False, 1325 ids=None, 1326 name=None, 1327): 1328 """(Return a) decorator to mark a yield-fixture factory function. 1329 1330 .. deprecated:: 3.0 1331 Use :py:func:`pytest.fixture` directly instead. 1332 """ 1333 warnings.warn(YIELD_FIXTURE, stacklevel=2) 1334 return fixture( 1335 fixture_function, 1336 *args, 1337 scope=scope, 1338 params=params, 1339 autouse=autouse, 1340 ids=ids, 1341 name=name, 1342 )
(Return a) decorator to mark a yield-fixture factory function.
Deprecated since version 3.0:
Use pytest.fixture()
directly instead.