Compare commits
49 Commits
Author | SHA1 | Date |
---|---|---|
|
c934de6e1b | |
|
5ff9bf4ea0 | |
|
6702741978 | |
|
03bf27f4df | |
|
48f787ecff | |
|
b322004047 | |
|
2d795dc07b | |
|
2d6b846978 | |
|
703d0f50d8 | |
|
56e6482405 | |
|
589176e9fe | |
|
3734a27733 | |
|
e1a21e46b0 | |
|
551400e8d6 | |
|
b7b729298c | |
|
21ca38b932 | |
|
565f4cb4ad | |
|
af6548a4e7 | |
|
2fb2962df4 | |
|
f838c7b7eb | |
|
25b53c4196 | |
|
fc27171d57 | |
|
d18e426b24 | |
|
e83fa48dd1 | |
|
c53d52c3f2 | |
|
3886c6d735 | |
|
80936b6762 | |
|
5ca08e9b8a | |
|
ba2c49e71e | |
|
da615a4fbe | |
|
32562881d4 | |
|
2a0bbfe63f | |
|
9aed656ec7 | |
|
a600e7a2a4 | |
|
57a95b3a2c | |
|
f5e430fd8f | |
|
38a4c7e56c | |
|
40f02d72b0 | |
|
554f600fb4 | |
|
e3a3c90d94 | |
|
f7327759e8 | |
|
ee1950af77 | |
|
3d0f3baa2b | |
|
8da758b93a | |
|
59e5d1bfbf | |
|
b9e2cd0a81 | |
|
a84fcbf5b2 | |
|
59c1bfada7 | |
|
3267f64724 |
|
@ -39,6 +39,7 @@ jobs:
|
||||||
"ubuntu-py37-pluggy",
|
"ubuntu-py37-pluggy",
|
||||||
"ubuntu-py37-freeze",
|
"ubuntu-py37-freeze",
|
||||||
"ubuntu-py38",
|
"ubuntu-py38",
|
||||||
|
"ubuntu-py39",
|
||||||
"ubuntu-pypy3",
|
"ubuntu-pypy3",
|
||||||
|
|
||||||
"macos-py37",
|
"macos-py37",
|
||||||
|
@ -62,7 +63,7 @@ jobs:
|
||||||
- name: "windows-py37"
|
- name: "windows-py37"
|
||||||
python: "3.7"
|
python: "3.7"
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
tox_env: "py37-twisted-numpy"
|
tox_env: "py37-numpy"
|
||||||
- name: "windows-py37-pluggy"
|
- name: "windows-py37-pluggy"
|
||||||
python: "3.7"
|
python: "3.7"
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
|
@ -70,7 +71,7 @@ jobs:
|
||||||
- name: "windows-py38"
|
- name: "windows-py38"
|
||||||
python: "3.8"
|
python: "3.8"
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
tox_env: "py38"
|
tox_env: "py38-unittestextras"
|
||||||
use_coverage: true
|
use_coverage: true
|
||||||
|
|
||||||
- name: "ubuntu-py35"
|
- name: "ubuntu-py35"
|
||||||
|
@ -84,7 +85,7 @@ jobs:
|
||||||
- name: "ubuntu-py37"
|
- name: "ubuntu-py37"
|
||||||
python: "3.7"
|
python: "3.7"
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
tox_env: "py37-lsof-numpy-oldattrs-pexpect-twisted"
|
tox_env: "py37-lsof-numpy-oldattrs-pexpect"
|
||||||
use_coverage: true
|
use_coverage: true
|
||||||
- name: "ubuntu-py37-pluggy"
|
- name: "ubuntu-py37-pluggy"
|
||||||
python: "3.7"
|
python: "3.7"
|
||||||
|
@ -98,6 +99,10 @@ jobs:
|
||||||
python: "3.8"
|
python: "3.8"
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
tox_env: "py38-xdist"
|
tox_env: "py38-xdist"
|
||||||
|
- name: "ubuntu-py39"
|
||||||
|
python: "3.8"
|
||||||
|
os: ubuntu-latest
|
||||||
|
tox_env: "py39-xdist"
|
||||||
- name: "ubuntu-pypy3"
|
- name: "ubuntu-pypy3"
|
||||||
python: "pypy3"
|
python: "pypy3"
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
|
@ -133,6 +138,12 @@ jobs:
|
||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v1
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python }}
|
python-version: ${{ matrix.python }}
|
||||||
|
- name: install python3.9
|
||||||
|
if: matrix.tox_env == 'py39-xdist'
|
||||||
|
run: |
|
||||||
|
sudo add-apt-repository ppa:deadsnakes/nightly
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y --no-install-recommends python3.9-dev python3.9-distutils
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
|
|
1
AUTHORS
1
AUTHORS
|
@ -267,6 +267,7 @@ Tom Dalton
|
||||||
Tom Viner
|
Tom Viner
|
||||||
Tomáš Gavenčiak
|
Tomáš Gavenčiak
|
||||||
Tomer Keren
|
Tomer Keren
|
||||||
|
Tor Colvin
|
||||||
Trevor Bekolay
|
Trevor Bekolay
|
||||||
Tyler Goodlet
|
Tyler Goodlet
|
||||||
Tzu-ping Chung
|
Tzu-ping Chung
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Fix a possible race condition when trying to remove lock files used to control access to folders
|
||||||
|
created by ``tmp_path`` and ``tmpdir``.
|
|
@ -1 +1,6 @@
|
||||||
comment: off
|
# reference: https://docs.codecov.io/docs/codecovyml-reference
|
||||||
|
coverage:
|
||||||
|
status:
|
||||||
|
patch: true
|
||||||
|
project: false
|
||||||
|
comment: false
|
||||||
|
|
|
@ -6,6 +6,9 @@ Release announcements
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
|
||||||
|
release-5.4.3
|
||||||
|
release-5.4.2
|
||||||
|
release-5.4.1
|
||||||
release-5.4.0
|
release-5.4.0
|
||||||
release-5.3.5
|
release-5.3.5
|
||||||
release-5.3.4
|
release-5.3.4
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
pytest-5.4.1
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
pytest 5.4.1 has just been released to PyPI.
|
||||||
|
|
||||||
|
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||||
|
|
||||||
|
pip install --upgrade pytest
|
||||||
|
|
||||||
|
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
|
||||||
|
|
||||||
|
Thanks to all who contributed to this release, among them:
|
||||||
|
|
||||||
|
* Bruno Oliveira
|
||||||
|
|
||||||
|
|
||||||
|
Happy testing,
|
||||||
|
The pytest Development Team
|
|
@ -0,0 +1,22 @@
|
||||||
|
pytest-5.4.2
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
pytest 5.4.2 has just been released to PyPI.
|
||||||
|
|
||||||
|
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||||
|
|
||||||
|
pip install --upgrade pytest
|
||||||
|
|
||||||
|
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
|
||||||
|
|
||||||
|
Thanks to all who contributed to this release, among them:
|
||||||
|
|
||||||
|
* Anthony Sottile
|
||||||
|
* Bruno Oliveira
|
||||||
|
* Daniel Hahler
|
||||||
|
* Ran Benita
|
||||||
|
* Ronny Pfannschmidt
|
||||||
|
|
||||||
|
|
||||||
|
Happy testing,
|
||||||
|
The pytest Development Team
|
|
@ -0,0 +1,21 @@
|
||||||
|
pytest-5.4.3
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
pytest 5.4.3 has just been released to PyPI.
|
||||||
|
|
||||||
|
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||||
|
|
||||||
|
pip install --upgrade pytest
|
||||||
|
|
||||||
|
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
|
||||||
|
|
||||||
|
Thanks to all who contributed to this release, among them:
|
||||||
|
|
||||||
|
* Anthony Sottile
|
||||||
|
* Bruno Oliveira
|
||||||
|
* Ran Benita
|
||||||
|
* Tor Colvin
|
||||||
|
|
||||||
|
|
||||||
|
Happy testing,
|
||||||
|
The pytest Development Team
|
|
@ -28,6 +28,79 @@ with advance notice in the **Deprecations** section of releases.
|
||||||
|
|
||||||
.. towncrier release notes start
|
.. towncrier release notes start
|
||||||
|
|
||||||
|
pytest 5.4.3 (2020-06-02)
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
- `#6428 <https://github.com/pytest-dev/pytest/issues/6428>`_: Paths appearing in error messages are now correct in case the current working directory has
|
||||||
|
changed since the start of the session.
|
||||||
|
|
||||||
|
|
||||||
|
- `#6755 <https://github.com/pytest-dev/pytest/issues/6755>`_: Support deleting paths longer than 260 characters on windows created inside tmpdir.
|
||||||
|
|
||||||
|
|
||||||
|
- `#6956 <https://github.com/pytest-dev/pytest/issues/6956>`_: Prevent pytest from printing ConftestImportFailure traceback to stdout.
|
||||||
|
|
||||||
|
|
||||||
|
- `#7150 <https://github.com/pytest-dev/pytest/issues/7150>`_: Prevent hiding the underlying exception when ``ConfTestImportFailure`` is raised.
|
||||||
|
|
||||||
|
|
||||||
|
- `#7215 <https://github.com/pytest-dev/pytest/issues/7215>`_: Fix regression where running with ``--pdb`` would call the ``tearDown`` methods of ``unittest.TestCase``
|
||||||
|
subclasses for skipped tests.
|
||||||
|
|
||||||
|
|
||||||
|
pytest 5.4.2 (2020-05-08)
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
- `#6871 <https://github.com/pytest-dev/pytest/issues/6871>`_: Fix crash with captured output when using the :fixture:`capsysbinary fixture <capsysbinary>`.
|
||||||
|
|
||||||
|
|
||||||
|
- `#6924 <https://github.com/pytest-dev/pytest/issues/6924>`_: Ensure a ``unittest.IsolatedAsyncioTestCase`` is actually awaited.
|
||||||
|
|
||||||
|
|
||||||
|
- `#6925 <https://github.com/pytest-dev/pytest/issues/6925>`_: Fix TerminalRepr instances to be hashable again.
|
||||||
|
|
||||||
|
|
||||||
|
- `#6947 <https://github.com/pytest-dev/pytest/issues/6947>`_: Fix regression where functions registered with ``TestCase.addCleanup`` were not being called on test failures.
|
||||||
|
|
||||||
|
|
||||||
|
- `#6951 <https://github.com/pytest-dev/pytest/issues/6951>`_: Allow users to still set the deprecated ``TerminalReporter.writer`` attribute.
|
||||||
|
|
||||||
|
|
||||||
|
- `#6992 <https://github.com/pytest-dev/pytest/issues/6992>`_: Revert "tmpdir: clean up indirection via config for factories" #6767 as it breaks pytest-xdist.
|
||||||
|
|
||||||
|
|
||||||
|
- `#7110 <https://github.com/pytest-dev/pytest/issues/7110>`_: Fixed regression: ``asyncbase.TestCase`` tests are executed correctly again.
|
||||||
|
|
||||||
|
|
||||||
|
- `#7143 <https://github.com/pytest-dev/pytest/issues/7143>`_: Fix ``File.from_constructor`` so it forwards extra keyword arguments to the constructor.
|
||||||
|
|
||||||
|
|
||||||
|
- `#7145 <https://github.com/pytest-dev/pytest/issues/7145>`_: Classes with broken ``__getattribute__`` methods are displayed correctly during failures.
|
||||||
|
|
||||||
|
|
||||||
|
- `#7180 <https://github.com/pytest-dev/pytest/issues/7180>`_: Fix ``_is_setup_py`` for files encoded differently than locale.
|
||||||
|
|
||||||
|
|
||||||
|
pytest 5.4.1 (2020-03-13)
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
- `#6909 <https://github.com/pytest-dev/pytest/issues/6909>`_: Revert the change introduced by `#6330 <https://github.com/pytest-dev/pytest/pull/6330>`_, which required all arguments to ``@pytest.mark.parametrize`` to be explicitly defined in the function signature.
|
||||||
|
|
||||||
|
The intention of the original change was to remove what was expected to be an unintended/surprising behavior, but it turns out many people relied on it, so the restriction has been reverted.
|
||||||
|
|
||||||
|
|
||||||
|
- `#6910 <https://github.com/pytest-dev/pytest/issues/6910>`_: Fix crash when plugins return an unknown stats while using the ``--reportlog`` option.
|
||||||
|
|
||||||
|
|
||||||
pytest 5.4.0 (2020-03-12)
|
pytest 5.4.0 (2020-03-12)
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
|
@ -63,10 +136,10 @@ Breaking Changes
|
||||||
Deprecations
|
Deprecations
|
||||||
------------
|
------------
|
||||||
|
|
||||||
- `#3238 <https://github.com/pytest-dev/pytest/issues/3238>`_: Option ``--no-print-logs`` is deprecated and meant to be removed in a future release. If you use ``--no-print-logs``, please try out ``--show-capture`` and
|
- `#3238 <https://github.com/pytest-dev/pytest/issues/3238>`_: Option ``--no-print-logs`` is deprecated and meant to be removed in a future release. If you use ``--no-print-logs``, please try out ``--show-capture`` and
|
||||||
provide feedback.
|
provide feedback.
|
||||||
|
|
||||||
``--show-capture`` command-line option was added in ``pytest 3.5.0`` and allows to specify how to
|
``--show-capture`` command-line option was added in ``pytest 3.5.0`` and allows to specify how to
|
||||||
display captured output when tests fail: ``no``, ``stdout``, ``stderr``, ``log`` or ``all`` (the default).
|
display captured output when tests fail: ``no``, ``stdout``, ``stderr``, ``log`` or ``all`` (the default).
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -402,9 +402,6 @@ The result of this test will be successful:
|
||||||
|
|
||||||
.. regendoc:wipe
|
.. regendoc:wipe
|
||||||
|
|
||||||
Note, that each argument in `parametrize` list should be explicitly declared in corresponding
|
|
||||||
python test function or via `indirect`.
|
|
||||||
|
|
||||||
Parametrizing test methods through per-class configuration
|
Parametrizing test methods through per-class configuration
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -484,11 +481,10 @@ Running it results in some skips if we don't have all the python interpreters in
|
||||||
.. code-block:: pytest
|
.. code-block:: pytest
|
||||||
|
|
||||||
. $ pytest -rs -q multipython.py
|
. $ pytest -rs -q multipython.py
|
||||||
ssssssssssss...ssssssssssss [100%]
|
ssssssssssss......sss...... [100%]
|
||||||
========================= short test summary info ==========================
|
========================= short test summary info ==========================
|
||||||
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.5' not found
|
SKIPPED [15] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.5' not found
|
||||||
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.7' not found
|
12 passed, 15 skipped in 0.12s
|
||||||
3 passed, 24 skipped in 0.12s
|
|
||||||
|
|
||||||
Indirect parametrization of optional implementations/imports
|
Indirect parametrization of optional implementations/imports
|
||||||
--------------------------------------------------------------------
|
--------------------------------------------------------------------
|
||||||
|
|
|
@ -443,9 +443,9 @@ additionally it is possible to copy examples for an example folder before runnin
|
||||||
testdir.copy_example("test_example.py")
|
testdir.copy_example("test_example.py")
|
||||||
|
|
||||||
test_example.py::test_plugin
|
test_example.py::test_plugin
|
||||||
$PYTHON_PREFIX/lib/python3.8/site-packages/_pytest/terminal.py:287: PytestDeprecationWarning: TerminalReporter.writer attribute is deprecated, use TerminalReporter._tw instead at your own risk.
|
$PYTHON_PREFIX/lib/python3.8/site-packages/_pytest/compat.py:333: PytestDeprecationWarning: The TerminalReporter.writer attribute is deprecated, use TerminalReporter._tw instead at your own risk.
|
||||||
See https://docs.pytest.org/en/latest/deprecations.html#terminalreporter-writer for more information.
|
See https://docs.pytest.org/en/latest/deprecations.html#terminalreporter-writer for more information.
|
||||||
warnings.warn(
|
return getattr(object, name, default)
|
||||||
|
|
||||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
||||||
====================== 2 passed, 2 warnings in 0.12s =======================
|
====================== 2 passed, 2 warnings in 0.12s =======================
|
||||||
|
|
|
@ -126,7 +126,9 @@ def trigger_release(payload_path: Path, token: str) -> None:
|
||||||
|
|
||||||
print(f"Branch {Fore.CYAN}{release_branch}{Fore.RESET} created.")
|
print(f"Branch {Fore.CYAN}{release_branch}{Fore.RESET} created.")
|
||||||
|
|
||||||
check_call([sys.executable, "scripts/release.py", version])
|
check_call(
|
||||||
|
[sys.executable, "scripts/release.py", version, "--skip-check-links"]
|
||||||
|
)
|
||||||
|
|
||||||
oauth_url = f"https://{token}:x-oauth-basic@github.com/{SLUG}.git"
|
oauth_url = f"https://{token}:x-oauth-basic@github.com/{SLUG}.git"
|
||||||
check_call(["git", "push", oauth_url, f"HEAD:{release_branch}", "--force"])
|
check_call(["git", "push", oauth_url, f"HEAD:{release_branch}", "--force"])
|
||||||
|
|
|
@ -26,6 +26,7 @@ classifiers =
|
||||||
Programming Language :: Python :: 3.6
|
Programming Language :: Python :: 3.6
|
||||||
Programming Language :: Python :: 3.7
|
Programming Language :: Python :: 3.7
|
||||||
Programming Language :: Python :: 3.8
|
Programming Language :: Python :: 3.8
|
||||||
|
Programming Language :: Python :: 3.9
|
||||||
platforms = unix, linux, osx, cygwin, win32
|
platforms = unix, linux, osx, cygwin, win32
|
||||||
|
|
||||||
[options]
|
[options]
|
||||||
|
|
|
@ -32,6 +32,7 @@ import _pytest
|
||||||
from _pytest._io import TerminalWriter
|
from _pytest._io import TerminalWriter
|
||||||
from _pytest._io.saferepr import safeformat
|
from _pytest._io.saferepr import safeformat
|
||||||
from _pytest._io.saferepr import saferepr
|
from _pytest._io.saferepr import saferepr
|
||||||
|
from _pytest.compat import ATTRS_EQ_FIELD
|
||||||
from _pytest.compat import overload
|
from _pytest.compat import overload
|
||||||
from _pytest.compat import TYPE_CHECKING
|
from _pytest.compat import TYPE_CHECKING
|
||||||
|
|
||||||
|
@ -911,7 +912,7 @@ class FormattedExcinfo:
|
||||||
return ExceptionChainRepr(repr_chain)
|
return ExceptionChainRepr(repr_chain)
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
|
||||||
class TerminalRepr:
|
class TerminalRepr:
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
# FYI this is called from pytest-xdist's serialization of exception
|
# FYI this is called from pytest-xdist's serialization of exception
|
||||||
|
@ -928,7 +929,7 @@ class TerminalRepr:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
|
||||||
class ExceptionRepr(TerminalRepr):
|
class ExceptionRepr(TerminalRepr):
|
||||||
def __attrs_post_init__(self):
|
def __attrs_post_init__(self):
|
||||||
self.sections = [] # type: List[Tuple[str, str, str]]
|
self.sections = [] # type: List[Tuple[str, str, str]]
|
||||||
|
@ -942,7 +943,7 @@ class ExceptionRepr(TerminalRepr):
|
||||||
tw.line(content)
|
tw.line(content)
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
|
||||||
class ExceptionChainRepr(ExceptionRepr):
|
class ExceptionChainRepr(ExceptionRepr):
|
||||||
chain = attr.ib(
|
chain = attr.ib(
|
||||||
type=Sequence[
|
type=Sequence[
|
||||||
|
@ -966,7 +967,7 @@ class ExceptionChainRepr(ExceptionRepr):
|
||||||
super().toterminal(tw)
|
super().toterminal(tw)
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
|
||||||
class ReprExceptionInfo(ExceptionRepr):
|
class ReprExceptionInfo(ExceptionRepr):
|
||||||
reprtraceback = attr.ib(type="ReprTraceback")
|
reprtraceback = attr.ib(type="ReprTraceback")
|
||||||
reprcrash = attr.ib(type="ReprFileLocation")
|
reprcrash = attr.ib(type="ReprFileLocation")
|
||||||
|
@ -976,7 +977,7 @@ class ReprExceptionInfo(ExceptionRepr):
|
||||||
super().toterminal(tw)
|
super().toterminal(tw)
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
|
||||||
class ReprTraceback(TerminalRepr):
|
class ReprTraceback(TerminalRepr):
|
||||||
reprentries = attr.ib(type=Sequence[Union["ReprEntry", "ReprEntryNative"]])
|
reprentries = attr.ib(type=Sequence[Union["ReprEntry", "ReprEntryNative"]])
|
||||||
extraline = attr.ib(type=Optional[str])
|
extraline = attr.ib(type=Optional[str])
|
||||||
|
@ -1010,7 +1011,7 @@ class ReprTracebackNative(ReprTraceback):
|
||||||
self.extraline = None
|
self.extraline = None
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
|
||||||
class ReprEntryNative(TerminalRepr):
|
class ReprEntryNative(TerminalRepr):
|
||||||
lines = attr.ib(type=Sequence[str])
|
lines = attr.ib(type=Sequence[str])
|
||||||
style = "native" # type: _TracebackStyle
|
style = "native" # type: _TracebackStyle
|
||||||
|
@ -1019,7 +1020,7 @@ class ReprEntryNative(TerminalRepr):
|
||||||
tw.write("".join(self.lines))
|
tw.write("".join(self.lines))
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
|
||||||
class ReprEntry(TerminalRepr):
|
class ReprEntry(TerminalRepr):
|
||||||
lines = attr.ib(type=Sequence[str])
|
lines = attr.ib(type=Sequence[str])
|
||||||
reprfuncargs = attr.ib(type=Optional["ReprFuncArgs"])
|
reprfuncargs = attr.ib(type=Optional["ReprFuncArgs"])
|
||||||
|
@ -1093,7 +1094,7 @@ class ReprEntry(TerminalRepr):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
|
||||||
class ReprFileLocation(TerminalRepr):
|
class ReprFileLocation(TerminalRepr):
|
||||||
path = attr.ib(type=str, converter=str)
|
path = attr.ib(type=str, converter=str)
|
||||||
lineno = attr.ib(type=int)
|
lineno = attr.ib(type=int)
|
||||||
|
@ -1110,7 +1111,7 @@ class ReprFileLocation(TerminalRepr):
|
||||||
tw.line(":{}: {}".format(self.lineno, msg))
|
tw.line(":{}: {}".format(self.lineno, msg))
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
|
||||||
class ReprLocals(TerminalRepr):
|
class ReprLocals(TerminalRepr):
|
||||||
lines = attr.ib(type=Sequence[str])
|
lines = attr.ib(type=Sequence[str])
|
||||||
|
|
||||||
|
@ -1119,7 +1120,7 @@ class ReprLocals(TerminalRepr):
|
||||||
tw.line(indent + line)
|
tw.line(indent + line)
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
|
||||||
class ReprFuncArgs(TerminalRepr):
|
class ReprFuncArgs(TerminalRepr):
|
||||||
args = attr.ib(type=Sequence[Tuple[str, object]])
|
args = attr.ib(type=Sequence[Tuple[str, object]])
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ def _format_repr_exception(exc: BaseException, obj: Any) -> str:
|
||||||
except BaseException as exc:
|
except BaseException as exc:
|
||||||
exc_info = "unpresentable exception ({})".format(_try_repr_or_str(exc))
|
exc_info = "unpresentable exception ({})".format(_try_repr_or_str(exc))
|
||||||
return "<[{} raised in repr()] {} object at 0x{:x}>".format(
|
return "<[{} raised in repr()] {} object at 0x{:x}>".format(
|
||||||
exc_info, obj.__class__.__name__, id(obj)
|
exc_info, type(obj).__name__, id(obj)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -610,8 +610,6 @@ class FDCaptureBinary:
|
||||||
|
|
||||||
def writeorg(self, data):
|
def writeorg(self, data):
|
||||||
""" write to original file descriptor. """
|
""" write to original file descriptor. """
|
||||||
if isinstance(data, str):
|
|
||||||
data = data.encode("utf8") # XXX use encoding of original stream
|
|
||||||
os.write(self.targetfd_save, data)
|
os.write(self.targetfd_save, data)
|
||||||
|
|
||||||
|
|
||||||
|
@ -631,6 +629,11 @@ class FDCapture(FDCaptureBinary):
|
||||||
res = str(res, enc, "replace")
|
res = str(res, enc, "replace")
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def writeorg(self, data):
|
||||||
|
""" write to original file descriptor. """
|
||||||
|
data = data.encode("utf-8") # XXX use encoding of original stream
|
||||||
|
os.write(self.targetfd_save, data)
|
||||||
|
|
||||||
|
|
||||||
class SysCaptureBinary:
|
class SysCaptureBinary:
|
||||||
|
|
||||||
|
@ -682,8 +685,9 @@ class SysCaptureBinary:
|
||||||
self._state = "resumed"
|
self._state = "resumed"
|
||||||
|
|
||||||
def writeorg(self, data):
|
def writeorg(self, data):
|
||||||
self._old.write(data)
|
|
||||||
self._old.flush()
|
self._old.flush()
|
||||||
|
self._old.buffer.write(data)
|
||||||
|
self._old.buffer.flush()
|
||||||
|
|
||||||
|
|
||||||
class SysCapture(SysCaptureBinary):
|
class SysCapture(SysCaptureBinary):
|
||||||
|
@ -695,6 +699,10 @@ class SysCapture(SysCaptureBinary):
|
||||||
self.tmpfile.truncate()
|
self.tmpfile.truncate()
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def writeorg(self, data):
|
||||||
|
self._old.write(data)
|
||||||
|
self._old.flush()
|
||||||
|
|
||||||
|
|
||||||
class TeeSysCapture(SysCapture):
|
class TeeSysCapture(SysCapture):
|
||||||
def __init__(self, fd, tmpfile=None):
|
def __init__(self, fd, tmpfile=None):
|
||||||
|
|
|
@ -93,6 +93,13 @@ def iscoroutinefunction(func: object) -> bool:
|
||||||
return inspect.iscoroutinefunction(func) or getattr(func, "_is_coroutine", False)
|
return inspect.iscoroutinefunction(func) or getattr(func, "_is_coroutine", False)
|
||||||
|
|
||||||
|
|
||||||
|
def is_async_function(func: object) -> bool:
|
||||||
|
"""Return True if the given function seems to be an async function or async generator"""
|
||||||
|
return iscoroutinefunction(func) or (
|
||||||
|
sys.version_info >= (3, 6) and inspect.isasyncgenfunction(func)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def getlocation(function, curdir=None) -> str:
|
def getlocation(function, curdir=None) -> str:
|
||||||
function = get_real_func(function)
|
function = get_real_func(function)
|
||||||
fn = py.path.local(inspect.getfile(function))
|
fn = py.path.local(inspect.getfile(function))
|
||||||
|
|
|
@ -4,6 +4,7 @@ import functools
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from _pytest import outcomes
|
from _pytest import outcomes
|
||||||
|
from _pytest.config import ConftestImportFailure
|
||||||
from _pytest.config import hookimpl
|
from _pytest.config import hookimpl
|
||||||
from _pytest.config.exceptions import UsageError
|
from _pytest.config.exceptions import UsageError
|
||||||
|
|
||||||
|
@ -272,11 +273,15 @@ class PdbInvoke:
|
||||||
class PdbTrace:
|
class PdbTrace:
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(hookwrapper=True)
|
||||||
def pytest_pyfunc_call(self, pyfuncitem):
|
def pytest_pyfunc_call(self, pyfuncitem):
|
||||||
_test_pytest_function(pyfuncitem)
|
wrap_pytest_function_for_tracing(pyfuncitem)
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
||||||
def _test_pytest_function(pyfuncitem):
|
def wrap_pytest_function_for_tracing(pyfuncitem):
|
||||||
|
"""Changes the python function object of the given Function item by a wrapper which actually
|
||||||
|
enters pdb before calling the python function itself, effectively leaving the user
|
||||||
|
in the pdb prompt in the first statement of the function.
|
||||||
|
"""
|
||||||
_pdb = pytestPDB._init_pdb("runcall")
|
_pdb = pytestPDB._init_pdb("runcall")
|
||||||
testfunction = pyfuncitem.obj
|
testfunction = pyfuncitem.obj
|
||||||
|
|
||||||
|
@ -291,6 +296,13 @@ def _test_pytest_function(pyfuncitem):
|
||||||
pyfuncitem.obj = wrapper
|
pyfuncitem.obj = wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def maybe_wrap_pytest_function_for_tracing(pyfuncitem):
|
||||||
|
"""Wrap the given pytestfunct item for tracing support if --trace was given in
|
||||||
|
the command line"""
|
||||||
|
if pyfuncitem.config.getvalue("trace"):
|
||||||
|
wrap_pytest_function_for_tracing(pyfuncitem)
|
||||||
|
|
||||||
|
|
||||||
def _enter_pdb(node, excinfo, rep):
|
def _enter_pdb(node, excinfo, rep):
|
||||||
# XXX we re-use the TerminalReporter's terminalwriter
|
# XXX we re-use the TerminalReporter's terminalwriter
|
||||||
# because this seems to avoid some encoding related troubles
|
# because this seems to avoid some encoding related troubles
|
||||||
|
@ -327,6 +339,10 @@ def _postmortem_traceback(excinfo):
|
||||||
# A doctest.UnexpectedException is not useful for post_mortem.
|
# A doctest.UnexpectedException is not useful for post_mortem.
|
||||||
# Use the underlying exception instead:
|
# Use the underlying exception instead:
|
||||||
return excinfo.value.exc_info[2]
|
return excinfo.value.exc_info[2]
|
||||||
|
elif isinstance(excinfo.value, ConftestImportFailure):
|
||||||
|
# A config.ConftestImportFailure is not useful for post_mortem.
|
||||||
|
# Use the underlying exception instead:
|
||||||
|
return excinfo.value.excinfo[2]
|
||||||
else:
|
else:
|
||||||
return excinfo._excinfo[2]
|
return excinfo._excinfo[2]
|
||||||
|
|
||||||
|
|
|
@ -54,3 +54,9 @@ COLLECT_DIRECTORY_HOOK = PytestDeprecationWarning(
|
||||||
"The pytest_collect_directory hook is not working.\n"
|
"The pytest_collect_directory hook is not working.\n"
|
||||||
"Please use collect_ignore in conftests or pytest_collection_modifyitems."
|
"Please use collect_ignore in conftests or pytest_collection_modifyitems."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
TERMINALWRITER_WRITER = PytestDeprecationWarning(
|
||||||
|
"The TerminalReporter.writer attribute is deprecated, use TerminalReporter._tw instead at your own risk.\n"
|
||||||
|
"See https://docs.pytest.org/en/latest/deprecations.html#terminalreporter-writer for more information."
|
||||||
|
)
|
||||||
|
|
|
@ -108,20 +108,20 @@ def pytest_unconfigure():
|
||||||
RUNNER_CLASS = None
|
RUNNER_CLASS = None
|
||||||
|
|
||||||
|
|
||||||
def pytest_collect_file(path, parent):
|
def pytest_collect_file(path: py.path.local, parent):
|
||||||
config = parent.config
|
config = parent.config
|
||||||
if path.ext == ".py":
|
if path.ext == ".py":
|
||||||
if config.option.doctestmodules and not _is_setup_py(config, path, parent):
|
if config.option.doctestmodules and not _is_setup_py(path):
|
||||||
return DoctestModule.from_parent(parent, fspath=path)
|
return DoctestModule.from_parent(parent, fspath=path)
|
||||||
elif _is_doctest(config, path, parent):
|
elif _is_doctest(config, path, parent):
|
||||||
return DoctestTextfile.from_parent(parent, fspath=path)
|
return DoctestTextfile.from_parent(parent, fspath=path)
|
||||||
|
|
||||||
|
|
||||||
def _is_setup_py(config, path, parent):
|
def _is_setup_py(path: py.path.local) -> bool:
|
||||||
if path.basename != "setup.py":
|
if path.basename != "setup.py":
|
||||||
return False
|
return False
|
||||||
contents = path.read()
|
contents = path.read_binary()
|
||||||
return "setuptools" in contents or "distutils" in contents
|
return b"setuptools" in contents or b"distutils" in contents
|
||||||
|
|
||||||
|
|
||||||
def _is_doctest(config, path, parent):
|
def _is_doctest(config, path, parent):
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
|
import itertools
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
@ -1277,8 +1278,10 @@ class FixtureManager:
|
||||||
else:
|
else:
|
||||||
argnames = ()
|
argnames = ()
|
||||||
|
|
||||||
usefixtures = get_use_fixtures_for_node(node)
|
usefixtures = itertools.chain.from_iterable(
|
||||||
initialnames = usefixtures + argnames
|
mark.args for mark in node.iter_markers(name="usefixtures")
|
||||||
|
)
|
||||||
|
initialnames = tuple(usefixtures) + argnames
|
||||||
fm = node.session._fixturemanager
|
fm = node.session._fixturemanager
|
||||||
initialnames, names_closure, arg2fixturedefs = fm.getfixtureclosure(
|
initialnames, names_closure, arg2fixturedefs = fm.getfixtureclosure(
|
||||||
initialnames, node, ignore_args=self._get_direct_parametrize_args(node)
|
initialnames, node, ignore_args=self._get_direct_parametrize_args(node)
|
||||||
|
@ -1475,12 +1478,3 @@ class FixtureManager:
|
||||||
for fixturedef in fixturedefs:
|
for fixturedef in fixturedefs:
|
||||||
if nodes.ischildnode(fixturedef.baseid, nodeid):
|
if nodes.ischildnode(fixturedef.baseid, nodeid):
|
||||||
yield fixturedef
|
yield fixturedef
|
||||||
|
|
||||||
|
|
||||||
def get_use_fixtures_for_node(node) -> Tuple[str, ...]:
|
|
||||||
"""Returns the names of all the usefixtures() marks on the given node"""
|
|
||||||
return tuple(
|
|
||||||
str(name)
|
|
||||||
for mark in node.iter_markers(name="usefixtures")
|
|
||||||
for name in mark.args
|
|
||||||
)
|
|
||||||
|
|
|
@ -13,7 +13,6 @@ import os
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import time
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import py
|
import py
|
||||||
|
@ -21,6 +20,7 @@ import py
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest import deprecated
|
from _pytest import deprecated
|
||||||
from _pytest import nodes
|
from _pytest import nodes
|
||||||
|
from _pytest import timing
|
||||||
from _pytest.config import filename_arg
|
from _pytest.config import filename_arg
|
||||||
from _pytest.store import StoreKey
|
from _pytest.store import StoreKey
|
||||||
from _pytest.warnings import _issue_warning_captured
|
from _pytest.warnings import _issue_warning_captured
|
||||||
|
@ -627,14 +627,14 @@ class LogXML:
|
||||||
reporter._add_simple(Junit.error, "internal error", excrepr)
|
reporter._add_simple(Junit.error, "internal error", excrepr)
|
||||||
|
|
||||||
def pytest_sessionstart(self):
|
def pytest_sessionstart(self):
|
||||||
self.suite_start_time = time.time()
|
self.suite_start_time = timing.time()
|
||||||
|
|
||||||
def pytest_sessionfinish(self):
|
def pytest_sessionfinish(self):
|
||||||
dirname = os.path.dirname(os.path.abspath(self.logfile))
|
dirname = os.path.dirname(os.path.abspath(self.logfile))
|
||||||
if not os.path.isdir(dirname):
|
if not os.path.isdir(dirname):
|
||||||
os.makedirs(dirname)
|
os.makedirs(dirname)
|
||||||
logfile = open(self.logfile, "w", encoding="utf-8")
|
logfile = open(self.logfile, "w", encoding="utf-8")
|
||||||
suite_stop_time = time.time()
|
suite_stop_time = timing.time()
|
||||||
suite_time_delta = suite_stop_time - self.suite_start_time
|
suite_time_delta = suite_stop_time - self.suite_start_time
|
||||||
|
|
||||||
numtests = (
|
numtests = (
|
||||||
|
@ -662,7 +662,7 @@ class LogXML:
|
||||||
logfile.close()
|
logfile.close()
|
||||||
|
|
||||||
def pytest_terminal_summary(self, terminalreporter):
|
def pytest_terminal_summary(self, terminalreporter):
|
||||||
terminalreporter.write_sep("-", "generated xml file: %s" % (self.logfile))
|
terminalreporter.write_sep("-", "generated xml file: {}".format(self.logfile))
|
||||||
|
|
||||||
def add_global_property(self, name, value):
|
def add_global_property(self, name, value):
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
|
|
|
@ -19,6 +19,7 @@ from _pytest._code.source import getfslineno
|
||||||
from _pytest.compat import cached_property
|
from _pytest.compat import cached_property
|
||||||
from _pytest.compat import TYPE_CHECKING
|
from _pytest.compat import TYPE_CHECKING
|
||||||
from _pytest.config import Config
|
from _pytest.config import Config
|
||||||
|
from _pytest.config import ConftestImportFailure
|
||||||
from _pytest.config import PytestPluginManager
|
from _pytest.config import PytestPluginManager
|
||||||
from _pytest.deprecated import NODE_USE_FROM_PARENT
|
from _pytest.deprecated import NODE_USE_FROM_PARENT
|
||||||
from _pytest.fixtures import FixtureDef
|
from _pytest.fixtures import FixtureDef
|
||||||
|
@ -28,7 +29,7 @@ from _pytest.mark.structures import Mark
|
||||||
from _pytest.mark.structures import MarkDecorator
|
from _pytest.mark.structures import MarkDecorator
|
||||||
from _pytest.mark.structures import NodeKeywords
|
from _pytest.mark.structures import NodeKeywords
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
from _pytest.outcomes import Failed
|
from _pytest.pathlib import Path
|
||||||
from _pytest.store import Store
|
from _pytest.store import Store
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -318,8 +319,10 @@ class Node(metaclass=NodeMeta):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _repr_failure_py(
|
def _repr_failure_py(
|
||||||
self, excinfo: ExceptionInfo[Union[Failed, FixtureLookupError]], style=None
|
self, excinfo: ExceptionInfo[BaseException], style=None,
|
||||||
) -> Union[str, ReprExceptionInfo, ExceptionChainRepr, FixtureLookupErrorRepr]:
|
) -> Union[str, ReprExceptionInfo, ExceptionChainRepr, FixtureLookupErrorRepr]:
|
||||||
|
if isinstance(excinfo.value, ConftestImportFailure):
|
||||||
|
excinfo = ExceptionInfo(excinfo.value.excinfo)
|
||||||
if isinstance(excinfo.value, fail.Exception):
|
if isinstance(excinfo.value, fail.Exception):
|
||||||
if not excinfo.value.pytrace:
|
if not excinfo.value.pytrace:
|
||||||
return str(excinfo.value)
|
return str(excinfo.value)
|
||||||
|
@ -346,9 +349,14 @@ class Node(metaclass=NodeMeta):
|
||||||
else:
|
else:
|
||||||
truncate_locals = True
|
truncate_locals = True
|
||||||
|
|
||||||
|
# excinfo.getrepr() formats paths relative to the CWD if `abspath` is False.
|
||||||
|
# It is possible for a fixture/test to change the CWD while this code runs, which
|
||||||
|
# would then result in the user seeing confusing paths in the failure message.
|
||||||
|
# To fix this, if the CWD changed, always display the full absolute path.
|
||||||
|
# It will be better to just always display paths relative to invocation_dir, but
|
||||||
|
# this requires a lot of plumbing (#6428).
|
||||||
try:
|
try:
|
||||||
os.getcwd()
|
abspath = Path(os.getcwd()) != Path(self.config.invocation_dir)
|
||||||
abspath = False
|
|
||||||
except OSError:
|
except OSError:
|
||||||
abspath = True
|
abspath = True
|
||||||
|
|
||||||
|
@ -474,11 +482,11 @@ class FSCollector(Collector):
|
||||||
self._norecursepatterns = self.config.getini("norecursedirs")
|
self._norecursepatterns = self.config.getini("norecursedirs")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_parent(cls, parent, *, fspath):
|
def from_parent(cls, parent, *, fspath, **kw):
|
||||||
"""
|
"""
|
||||||
The public constructor
|
The public constructor
|
||||||
"""
|
"""
|
||||||
return super().from_parent(parent=parent, fspath=fspath)
|
return super().from_parent(parent=parent, fspath=fspath, **kw)
|
||||||
|
|
||||||
def _gethookproxy(self, fspath: py.path.local):
|
def _gethookproxy(self, fspath: py.path.local):
|
||||||
# check if we have the common case of running
|
# check if we have the common case of running
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import atexit
|
import atexit
|
||||||
|
import contextlib
|
||||||
import fnmatch
|
import fnmatch
|
||||||
import itertools
|
import itertools
|
||||||
import os
|
import os
|
||||||
|
@ -100,10 +101,41 @@ def on_rm_rf_error(func, path: str, exc, *, start_path: Path) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_extended_length_path(path: Path) -> Path:
|
||||||
|
"""Get the extended-length version of a path (Windows).
|
||||||
|
|
||||||
|
On Windows, by default, the maximum length of a path (MAX_PATH) is 260
|
||||||
|
characters, and operations on paths longer than that fail. But it is possible
|
||||||
|
to overcome this by converting the path to "extended-length" form before
|
||||||
|
performing the operation:
|
||||||
|
https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation
|
||||||
|
|
||||||
|
On Windows, this function returns the extended-length absolute version of path.
|
||||||
|
On other platforms it returns path unchanged.
|
||||||
|
"""
|
||||||
|
if sys.platform.startswith("win32"):
|
||||||
|
path = path.resolve()
|
||||||
|
path = Path(get_extended_length_path_str(str(path)))
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def get_extended_length_path_str(path: str) -> str:
|
||||||
|
"""Converts to extended length path as a str"""
|
||||||
|
long_path_prefix = "\\\\?\\"
|
||||||
|
unc_long_path_prefix = "\\\\?\\UNC\\"
|
||||||
|
if path.startswith((long_path_prefix, unc_long_path_prefix)):
|
||||||
|
return path
|
||||||
|
# UNC
|
||||||
|
if path.startswith("\\\\"):
|
||||||
|
return unc_long_path_prefix + path[2:]
|
||||||
|
return long_path_prefix + path
|
||||||
|
|
||||||
|
|
||||||
def rm_rf(path: Path) -> None:
|
def rm_rf(path: Path) -> None:
|
||||||
"""Remove the path contents recursively, even if some elements
|
"""Remove the path contents recursively, even if some elements
|
||||||
are read-only.
|
are read-only.
|
||||||
"""
|
"""
|
||||||
|
path = ensure_extended_length_path(path)
|
||||||
onerror = partial(on_rm_rf_error, start_path=path)
|
onerror = partial(on_rm_rf_error, start_path=path)
|
||||||
shutil.rmtree(str(path), onerror=onerror)
|
shutil.rmtree(str(path), onerror=onerror)
|
||||||
|
|
||||||
|
@ -220,6 +252,7 @@ def register_cleanup_lock_removal(lock_path: Path, register=atexit.register):
|
||||||
|
|
||||||
def maybe_delete_a_numbered_dir(path: Path) -> None:
|
def maybe_delete_a_numbered_dir(path: Path) -> None:
|
||||||
"""removes a numbered directory if its lock can be obtained and it does not seem to be in use"""
|
"""removes a numbered directory if its lock can be obtained and it does not seem to be in use"""
|
||||||
|
path = ensure_extended_length_path(path)
|
||||||
lock_path = None
|
lock_path = None
|
||||||
try:
|
try:
|
||||||
lock_path = create_cleanup_lock(path)
|
lock_path = create_cleanup_lock(path)
|
||||||
|
@ -257,10 +290,14 @@ def ensure_deletable(path: Path, consider_lock_dead_if_created_before: float) ->
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
if lock_time < consider_lock_dead_if_created_before:
|
if lock_time < consider_lock_dead_if_created_before:
|
||||||
lock.unlink()
|
# wa want to ignore any errors while trying to remove the lock such as:
|
||||||
return True
|
# - PermissionDenied, like the file permissions have changed since the lock creation
|
||||||
else:
|
# - FileNotFoundError, in case another pytest process got here first.
|
||||||
return False
|
# and any other cause of failure.
|
||||||
|
with contextlib.suppress(OSError):
|
||||||
|
lock.unlink()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def try_cleanup(path: Path, consider_lock_dead_if_created_before: float) -> None:
|
def try_cleanup(path: Path, consider_lock_dead_if_created_before: float) -> None:
|
||||||
|
|
|
@ -7,7 +7,6 @@ import platform
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
|
||||||
import traceback
|
import traceback
|
||||||
from fnmatch import fnmatch
|
from fnmatch import fnmatch
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
@ -24,6 +23,7 @@ from weakref import WeakKeyDictionary
|
||||||
import py
|
import py
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from _pytest import timing
|
||||||
from _pytest._code import Source
|
from _pytest._code import Source
|
||||||
from _pytest.capture import MultiCapture
|
from _pytest.capture import MultiCapture
|
||||||
from _pytest.capture import SysCapture
|
from _pytest.capture import SysCapture
|
||||||
|
@ -924,7 +924,7 @@ class Testdir:
|
||||||
|
|
||||||
if syspathinsert:
|
if syspathinsert:
|
||||||
self.syspathinsert()
|
self.syspathinsert()
|
||||||
now = time.time()
|
now = timing.time()
|
||||||
capture = MultiCapture(Capture=SysCapture)
|
capture = MultiCapture(Capture=SysCapture)
|
||||||
capture.start_capturing()
|
capture.start_capturing()
|
||||||
try:
|
try:
|
||||||
|
@ -953,7 +953,7 @@ class Testdir:
|
||||||
sys.stderr.write(err)
|
sys.stderr.write(err)
|
||||||
|
|
||||||
res = RunResult(
|
res = RunResult(
|
||||||
reprec.ret, out.splitlines(), err.splitlines(), time.time() - now
|
reprec.ret, out.splitlines(), err.splitlines(), timing.time() - now
|
||||||
)
|
)
|
||||||
res.reprec = reprec # type: ignore
|
res.reprec = reprec # type: ignore
|
||||||
return res
|
return res
|
||||||
|
@ -1154,7 +1154,7 @@ class Testdir:
|
||||||
f1 = open(str(p1), "w", encoding="utf8")
|
f1 = open(str(p1), "w", encoding="utf8")
|
||||||
f2 = open(str(p2), "w", encoding="utf8")
|
f2 = open(str(p2), "w", encoding="utf8")
|
||||||
try:
|
try:
|
||||||
now = time.time()
|
now = timing.time()
|
||||||
popen = self.popen(
|
popen = self.popen(
|
||||||
cmdargs,
|
cmdargs,
|
||||||
stdin=stdin,
|
stdin=stdin,
|
||||||
|
@ -1201,7 +1201,7 @@ class Testdir:
|
||||||
ret = ExitCode(ret)
|
ret = ExitCode(ret)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
return RunResult(ret, out, err, time.time() - now)
|
return RunResult(ret, out, err, timing.time() - now)
|
||||||
|
|
||||||
def _dump_lines(self, lines, fp):
|
def _dump_lines(self, lines, fp):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -34,8 +34,8 @@ from _pytest.compat import get_default_arg_names
|
||||||
from _pytest.compat import get_real_func
|
from _pytest.compat import get_real_func
|
||||||
from _pytest.compat import getimfunc
|
from _pytest.compat import getimfunc
|
||||||
from _pytest.compat import getlocation
|
from _pytest.compat import getlocation
|
||||||
|
from _pytest.compat import is_async_function
|
||||||
from _pytest.compat import is_generator
|
from _pytest.compat import is_generator
|
||||||
from _pytest.compat import iscoroutinefunction
|
|
||||||
from _pytest.compat import NOTSET
|
from _pytest.compat import NOTSET
|
||||||
from _pytest.compat import REGEX_TYPE
|
from _pytest.compat import REGEX_TYPE
|
||||||
from _pytest.compat import safe_getattr
|
from _pytest.compat import safe_getattr
|
||||||
|
@ -159,7 +159,7 @@ def pytest_configure(config):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def async_warn(nodeid: str) -> None:
|
def async_warn_and_skip(nodeid: str) -> None:
|
||||||
msg = "async def functions are not natively supported and have been skipped.\n"
|
msg = "async def functions are not natively supported and have been skipped.\n"
|
||||||
msg += (
|
msg += (
|
||||||
"You need to install a suitable plugin for your async framework, for example:\n"
|
"You need to install a suitable plugin for your async framework, for example:\n"
|
||||||
|
@ -175,15 +175,13 @@ def async_warn(nodeid: str) -> None:
|
||||||
@hookimpl(trylast=True)
|
@hookimpl(trylast=True)
|
||||||
def pytest_pyfunc_call(pyfuncitem: "Function"):
|
def pytest_pyfunc_call(pyfuncitem: "Function"):
|
||||||
testfunction = pyfuncitem.obj
|
testfunction = pyfuncitem.obj
|
||||||
if iscoroutinefunction(testfunction) or (
|
if is_async_function(testfunction):
|
||||||
sys.version_info >= (3, 6) and inspect.isasyncgenfunction(testfunction)
|
async_warn_and_skip(pyfuncitem.nodeid)
|
||||||
):
|
|
||||||
async_warn(pyfuncitem.nodeid)
|
|
||||||
funcargs = pyfuncitem.funcargs
|
funcargs = pyfuncitem.funcargs
|
||||||
testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames}
|
testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames}
|
||||||
result = testfunction(**testargs)
|
result = testfunction(**testargs)
|
||||||
if hasattr(result, "__await__") or hasattr(result, "__aiter__"):
|
if hasattr(result, "__await__") or hasattr(result, "__aiter__"):
|
||||||
async_warn(pyfuncitem.nodeid)
|
async_warn_and_skip(pyfuncitem.nodeid)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -936,8 +934,6 @@ class Metafunc:
|
||||||
|
|
||||||
arg_values_types = self._resolve_arg_value_types(argnames, indirect)
|
arg_values_types = self._resolve_arg_value_types(argnames, indirect)
|
||||||
|
|
||||||
self._validate_explicit_parameters(argnames, indirect)
|
|
||||||
|
|
||||||
# Use any already (possibly) generated ids with parametrize Marks.
|
# Use any already (possibly) generated ids with parametrize Marks.
|
||||||
if _param_mark and _param_mark._param_ids_from:
|
if _param_mark and _param_mark._param_ids_from:
|
||||||
generated_ids = _param_mark._param_ids_from._param_ids_generated
|
generated_ids = _param_mark._param_ids_from._param_ids_generated
|
||||||
|
@ -1110,39 +1106,6 @@ class Metafunc:
|
||||||
pytrace=False,
|
pytrace=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _validate_explicit_parameters(
|
|
||||||
self,
|
|
||||||
argnames: typing.Sequence[str],
|
|
||||||
indirect: Union[bool, typing.Sequence[str]],
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
The argnames in *parametrize* should either be declared explicitly via
|
|
||||||
indirect list or in the function signature
|
|
||||||
|
|
||||||
:param List[str] argnames: list of argument names passed to ``parametrize()``.
|
|
||||||
:param indirect: same ``indirect`` parameter of ``parametrize()``.
|
|
||||||
:raise ValueError: if validation fails
|
|
||||||
"""
|
|
||||||
if isinstance(indirect, bool):
|
|
||||||
parametrized_argnames = [] if indirect else argnames
|
|
||||||
else:
|
|
||||||
parametrized_argnames = [arg for arg in argnames if arg not in indirect]
|
|
||||||
|
|
||||||
if not parametrized_argnames:
|
|
||||||
return
|
|
||||||
|
|
||||||
funcargnames = _pytest.compat.getfuncargnames(self.function)
|
|
||||||
usefixtures = fixtures.get_use_fixtures_for_node(self.definition)
|
|
||||||
|
|
||||||
for arg in parametrized_argnames:
|
|
||||||
if arg not in funcargnames and arg not in usefixtures:
|
|
||||||
func_name = self.function.__name__
|
|
||||||
msg = (
|
|
||||||
'In function "{func_name}":\n'
|
|
||||||
'Parameter "{arg}" should be declared explicitly via indirect or in function itself'
|
|
||||||
).format(func_name=func_name, arg=arg)
|
|
||||||
fail(msg, pytrace=False)
|
|
||||||
|
|
||||||
|
|
||||||
def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
|
def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
|
||||||
"""Find the most appropriate scope for a parametrized call based on its arguments.
|
"""Find the most appropriate scope for a parametrized call based on its arguments.
|
||||||
|
|
|
@ -77,10 +77,10 @@ class ResultLog:
|
||||||
longrepr = ""
|
longrepr = ""
|
||||||
elif report.passed:
|
elif report.passed:
|
||||||
longrepr = ""
|
longrepr = ""
|
||||||
elif report.failed:
|
|
||||||
longrepr = str(report.longrepr)
|
|
||||||
elif report.skipped:
|
elif report.skipped:
|
||||||
longrepr = str(report.longrepr[2])
|
longrepr = str(report.longrepr[2])
|
||||||
|
else:
|
||||||
|
longrepr = str(report.longrepr)
|
||||||
self.log_outcome(report, code, longrepr)
|
self.log_outcome(report, code, longrepr)
|
||||||
|
|
||||||
def pytest_collectreport(self, report):
|
def pytest_collectreport(self, report):
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import bdb
|
import bdb
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from time import time
|
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import List
|
from typing import List
|
||||||
|
@ -14,6 +13,7 @@ import attr
|
||||||
from .reports import CollectErrorRepr
|
from .reports import CollectErrorRepr
|
||||||
from .reports import CollectReport
|
from .reports import CollectReport
|
||||||
from .reports import TestReport
|
from .reports import TestReport
|
||||||
|
from _pytest import timing
|
||||||
from _pytest._code.code import ExceptionChainRepr
|
from _pytest._code.code import ExceptionChainRepr
|
||||||
from _pytest._code.code import ExceptionInfo
|
from _pytest._code.code import ExceptionInfo
|
||||||
from _pytest.compat import TYPE_CHECKING
|
from _pytest.compat import TYPE_CHECKING
|
||||||
|
@ -238,8 +238,8 @@ class CallInfo:
|
||||||
def from_call(cls, func, when, reraise=None) -> "CallInfo":
|
def from_call(cls, func, when, reraise=None) -> "CallInfo":
|
||||||
#: context of invocation: one of "setup", "call",
|
#: context of invocation: one of "setup", "call",
|
||||||
#: "teardown", "memocollect"
|
#: "teardown", "memocollect"
|
||||||
start = time()
|
|
||||||
excinfo = None
|
excinfo = None
|
||||||
|
start = timing.time()
|
||||||
try:
|
try:
|
||||||
result = func()
|
result = func()
|
||||||
except: # noqa
|
except: # noqa
|
||||||
|
@ -247,7 +247,7 @@ class CallInfo:
|
||||||
if reraise is not None and excinfo.errisinstance(reraise):
|
if reraise is not None and excinfo.errisinstance(reraise):
|
||||||
raise
|
raise
|
||||||
result = None
|
result = None
|
||||||
stop = time()
|
stop = timing.time()
|
||||||
return cls(start=start, stop=stop, when=when, result=result, excinfo=excinfo)
|
return cls(start=start, stop=stop, when=when, result=result, excinfo=excinfo)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
|
|
@ -7,7 +7,6 @@ import collections
|
||||||
import datetime
|
import datetime
|
||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
import time
|
|
||||||
import warnings
|
import warnings
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
@ -26,9 +25,11 @@ from more_itertools import collapse
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest import nodes
|
from _pytest import nodes
|
||||||
|
from _pytest import timing
|
||||||
from _pytest._io import TerminalWriter
|
from _pytest._io import TerminalWriter
|
||||||
from _pytest.config import Config
|
from _pytest.config import Config
|
||||||
from _pytest.config import ExitCode
|
from _pytest.config import ExitCode
|
||||||
|
from _pytest.deprecated import TERMINALWRITER_WRITER
|
||||||
from _pytest.main import Session
|
from _pytest.main import Session
|
||||||
from _pytest.reports import CollectReport
|
from _pytest.reports import CollectReport
|
||||||
from _pytest.reports import TestReport
|
from _pytest.reports import TestReport
|
||||||
|
@ -284,14 +285,14 @@ class TerminalReporter:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def writer(self) -> TerminalWriter:
|
def writer(self) -> TerminalWriter:
|
||||||
warnings.warn(
|
warnings.warn(TERMINALWRITER_WRITER, stacklevel=2)
|
||||||
pytest.PytestDeprecationWarning(
|
|
||||||
"TerminalReporter.writer attribute is deprecated, use TerminalReporter._tw instead at your own risk.\n"
|
|
||||||
"See https://docs.pytest.org/en/latest/deprecations.html#terminalreporter-writer for more information."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return self._tw
|
return self._tw
|
||||||
|
|
||||||
|
@writer.setter
|
||||||
|
def writer(self, value: TerminalWriter):
|
||||||
|
warnings.warn(TERMINALWRITER_WRITER, stacklevel=2)
|
||||||
|
self._tw = value
|
||||||
|
|
||||||
def _determine_show_progress_info(self):
|
def _determine_show_progress_info(self):
|
||||||
"""Return True if we should display progress information based on the current config"""
|
"""Return True if we should display progress information based on the current config"""
|
||||||
# do not show progress if we are not capturing output (#3038)
|
# do not show progress if we are not capturing output (#3038)
|
||||||
|
@ -553,7 +554,7 @@ class TerminalReporter:
|
||||||
if self.isatty:
|
if self.isatty:
|
||||||
if self.config.option.verbose >= 0:
|
if self.config.option.verbose >= 0:
|
||||||
self.write("collecting ... ", bold=True)
|
self.write("collecting ... ", bold=True)
|
||||||
self._collect_report_last_write = time.time()
|
self._collect_report_last_write = timing.time()
|
||||||
elif self.config.option.verbose >= 1:
|
elif self.config.option.verbose >= 1:
|
||||||
self.write("collecting ... ", bold=True)
|
self.write("collecting ... ", bold=True)
|
||||||
|
|
||||||
|
@ -573,7 +574,7 @@ class TerminalReporter:
|
||||||
|
|
||||||
if not final:
|
if not final:
|
||||||
# Only write "collecting" report every 0.5s.
|
# Only write "collecting" report every 0.5s.
|
||||||
t = time.time()
|
t = timing.time()
|
||||||
if (
|
if (
|
||||||
self._collect_report_last_write is not None
|
self._collect_report_last_write is not None
|
||||||
and self._collect_report_last_write > t - REPORT_COLLECTING_RESOLUTION
|
and self._collect_report_last_write > t - REPORT_COLLECTING_RESOLUTION
|
||||||
|
@ -610,7 +611,7 @@ class TerminalReporter:
|
||||||
@pytest.hookimpl(trylast=True)
|
@pytest.hookimpl(trylast=True)
|
||||||
def pytest_sessionstart(self, session: Session) -> None:
|
def pytest_sessionstart(self, session: Session) -> None:
|
||||||
self._session = session
|
self._session = session
|
||||||
self._sessionstarttime = time.time()
|
self._sessionstarttime = timing.time()
|
||||||
if not self.showheader:
|
if not self.showheader:
|
||||||
return
|
return
|
||||||
self.write_sep("=", "test session starts", bold=True)
|
self.write_sep("=", "test session starts", bold=True)
|
||||||
|
@ -959,7 +960,7 @@ class TerminalReporter:
|
||||||
if self.verbosity < -1:
|
if self.verbosity < -1:
|
||||||
return
|
return
|
||||||
|
|
||||||
session_duration = time.time() - self._sessionstarttime
|
session_duration = timing.time() - self._sessionstarttime
|
||||||
(parts, main_color) = self.build_summary_stats_line()
|
(parts, main_color) = self.build_summary_stats_line()
|
||||||
line_parts = []
|
line_parts = []
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
"""
|
||||||
|
Indirection for time functions.
|
||||||
|
|
||||||
|
We intentionally grab some "time" functions internally to avoid tests mocking "time" to affect
|
||||||
|
pytest runtime information (issue #185).
|
||||||
|
|
||||||
|
Fixture "mock_timinig" also interacts with this module for pytest's own tests.
|
||||||
|
"""
|
||||||
|
from time import perf_counter
|
||||||
|
from time import sleep
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
__all__ = ["perf_counter", "sleep", "time"]
|
|
@ -14,6 +14,7 @@ from .pathlib import make_numbered_dir
|
||||||
from .pathlib import make_numbered_dir_with_cleanup
|
from .pathlib import make_numbered_dir_with_cleanup
|
||||||
from .pathlib import Path
|
from .pathlib import Path
|
||||||
from _pytest.fixtures import FixtureRequest
|
from _pytest.fixtures import FixtureRequest
|
||||||
|
from _pytest.monkeypatch import MonkeyPatch
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s
|
||||||
|
@ -134,18 +135,35 @@ def get_user() -> Optional[str]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_configure(config) -> None:
|
||||||
|
"""Create a TempdirFactory and attach it to the config object.
|
||||||
|
|
||||||
|
This is to comply with existing plugins which expect the handler to be
|
||||||
|
available at pytest_configure time, but ideally should be moved entirely
|
||||||
|
to the tmpdir_factory session fixture.
|
||||||
|
"""
|
||||||
|
mp = MonkeyPatch()
|
||||||
|
tmppath_handler = TempPathFactory.from_config(config)
|
||||||
|
t = TempdirFactory(tmppath_handler)
|
||||||
|
config._cleanup.append(mp.undo)
|
||||||
|
mp.setattr(config, "_tmp_path_factory", tmppath_handler, raising=False)
|
||||||
|
mp.setattr(config, "_tmpdirhandler", t, raising=False)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def tmpdir_factory(tmp_path_factory) -> TempdirFactory:
|
def tmpdir_factory(request: FixtureRequest) -> TempdirFactory:
|
||||||
"""Return a :class:`_pytest.tmpdir.TempdirFactory` instance for the test session.
|
"""Return a :class:`_pytest.tmpdir.TempdirFactory` instance for the test session.
|
||||||
"""
|
"""
|
||||||
return TempdirFactory(tmp_path_factory)
|
# Set dynamically by pytest_configure() above.
|
||||||
|
return request.config._tmpdirhandler # type: ignore
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def tmp_path_factory(request: FixtureRequest) -> TempPathFactory:
|
def tmp_path_factory(request: FixtureRequest) -> TempPathFactory:
|
||||||
"""Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session.
|
"""Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session.
|
||||||
"""
|
"""
|
||||||
return TempPathFactory.from_config(request.config)
|
# Set dynamically by pytest_configure() above.
|
||||||
|
return request.config._tmp_path_factory # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def _mk_tmp(request: FixtureRequest, factory: TempPathFactory) -> Path:
|
def _mk_tmp(request: FixtureRequest, factory: TempPathFactory) -> Path:
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
""" discovery and running of std-library "unittest" style tests. """
|
""" discovery and running of std-library "unittest" style tests. """
|
||||||
import functools
|
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.compat import getimfunc
|
from _pytest.compat import getimfunc
|
||||||
|
from _pytest.compat import is_async_function
|
||||||
from _pytest.config import hookimpl
|
from _pytest.config import hookimpl
|
||||||
from _pytest.outcomes import exit
|
from _pytest.outcomes import exit
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
|
@ -41,7 +41,7 @@ class UnitTestCase(Class):
|
||||||
if not getattr(cls, "__test__", True):
|
if not getattr(cls, "__test__", True):
|
||||||
return
|
return
|
||||||
|
|
||||||
skipped = getattr(cls, "__unittest_skip__", False)
|
skipped = _is_skipped(cls)
|
||||||
if not skipped:
|
if not skipped:
|
||||||
self._inject_setup_teardown_fixtures(cls)
|
self._inject_setup_teardown_fixtures(cls)
|
||||||
self._inject_setup_class_fixture()
|
self._inject_setup_class_fixture()
|
||||||
|
@ -89,7 +89,7 @@ def _make_xunit_fixture(obj, setup_name, teardown_name, scope, pass_self):
|
||||||
|
|
||||||
@pytest.fixture(scope=scope, autouse=True)
|
@pytest.fixture(scope=scope, autouse=True)
|
||||||
def fixture(self, request):
|
def fixture(self, request):
|
||||||
if getattr(self, "__unittest_skip__", None):
|
if _is_skipped(self):
|
||||||
reason = self.__unittest_skip_why__
|
reason = self.__unittest_skip_why__
|
||||||
pytest.skip(reason)
|
pytest.skip(reason)
|
||||||
if setup is not None:
|
if setup is not None:
|
||||||
|
@ -113,15 +113,17 @@ class TestCaseFunction(Function):
|
||||||
_testcase = None
|
_testcase = None
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
self._needs_explicit_tearDown = False
|
# a bound method to be called during teardown() if set (see 'runtest()')
|
||||||
|
self._explicit_tearDown = None
|
||||||
self._testcase = self.parent.obj(self.name)
|
self._testcase = self.parent.obj(self.name)
|
||||||
self._obj = getattr(self._testcase, self.name)
|
self._obj = getattr(self._testcase, self.name)
|
||||||
if hasattr(self, "_request"):
|
if hasattr(self, "_request"):
|
||||||
self._request._fillfixtures()
|
self._request._fillfixtures()
|
||||||
|
|
||||||
def teardown(self):
|
def teardown(self):
|
||||||
if self._needs_explicit_tearDown:
|
if self._explicit_tearDown is not None:
|
||||||
self._testcase.tearDown()
|
self._explicit_tearDown()
|
||||||
|
self._explicit_tearDown = None
|
||||||
self._testcase = None
|
self._testcase = None
|
||||||
self._obj = None
|
self._obj = None
|
||||||
|
|
||||||
|
@ -204,36 +206,31 @@ class TestCaseFunction(Function):
|
||||||
return bool(expecting_failure_class or expecting_failure_method)
|
return bool(expecting_failure_class or expecting_failure_method)
|
||||||
|
|
||||||
def runtest(self):
|
def runtest(self):
|
||||||
# TODO: move testcase reporter into separate class, this shouldnt be on item
|
from _pytest.debugging import maybe_wrap_pytest_function_for_tracing
|
||||||
import unittest
|
|
||||||
|
|
||||||
testMethod = getattr(self._testcase, self._testcase._testMethodName)
|
maybe_wrap_pytest_function_for_tracing(self)
|
||||||
|
|
||||||
class _GetOutOf_testPartExecutor(KeyboardInterrupt):
|
# let the unittest framework handle async functions
|
||||||
"""Helper exception to get out of unittests's testPartExecutor (see TestCase.run)."""
|
if is_async_function(self.obj):
|
||||||
|
self._testcase(self)
|
||||||
|
else:
|
||||||
|
# when --pdb is given, we want to postpone calling tearDown() otherwise
|
||||||
|
# when entering the pdb prompt, tearDown() would have probably cleaned up
|
||||||
|
# instance variables, which makes it difficult to debug
|
||||||
|
# arguably we could always postpone tearDown(), but this changes the moment where the
|
||||||
|
# TestCase instance interacts with the results object, so better to only do it
|
||||||
|
# when absolutely needed
|
||||||
|
if self.config.getoption("usepdb") and not _is_skipped(self.obj):
|
||||||
|
self._explicit_tearDown = self._testcase.tearDown
|
||||||
|
setattr(self._testcase, "tearDown", lambda *args: None)
|
||||||
|
|
||||||
@functools.wraps(testMethod)
|
# we need to update the actual bound method with self.obj, because
|
||||||
def wrapped_testMethod(*args, **kwargs):
|
# wrap_pytest_function_for_tracing replaces self.obj by a wrapper
|
||||||
"""Wrap the original method to call into pytest's machinery, so other pytest
|
setattr(self._testcase, self.name, self.obj)
|
||||||
features can have a chance to kick in (notably --pdb)"""
|
|
||||||
try:
|
try:
|
||||||
self.ihook.pytest_pyfunc_call(pyfuncitem=self)
|
self._testcase(result=self)
|
||||||
except unittest.SkipTest:
|
finally:
|
||||||
raise
|
delattr(self._testcase, self.name)
|
||||||
except Exception as exc:
|
|
||||||
expecting_failure = self._expecting_failure(testMethod)
|
|
||||||
if expecting_failure:
|
|
||||||
raise
|
|
||||||
self._needs_explicit_tearDown = True
|
|
||||||
raise _GetOutOf_testPartExecutor(exc)
|
|
||||||
|
|
||||||
setattr(self._testcase, self._testcase._testMethodName, wrapped_testMethod)
|
|
||||||
try:
|
|
||||||
self._testcase(result=self)
|
|
||||||
except _GetOutOf_testPartExecutor as exc:
|
|
||||||
raise exc.args[0] from exc.args[0]
|
|
||||||
finally:
|
|
||||||
delattr(self._testcase, self._testcase._testMethodName)
|
|
||||||
|
|
||||||
def _prunetraceback(self, excinfo):
|
def _prunetraceback(self, excinfo):
|
||||||
Function._prunetraceback(self, excinfo)
|
Function._prunetraceback(self, excinfo)
|
||||||
|
@ -304,3 +301,8 @@ def check_testcase_implements_trial_reporter(done=[]):
|
||||||
|
|
||||||
classImplements(TestCaseFunction, IReporter)
|
classImplements(TestCaseFunction, IReporter)
|
||||||
done.append(1)
|
done.append(1)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_skipped(obj) -> bool:
|
||||||
|
"""Return True if the given object has been marked with @unittest.skip"""
|
||||||
|
return bool(getattr(obj, "__unittest_skip__", False))
|
||||||
|
|
|
@ -895,21 +895,20 @@ class TestInvocationVariants:
|
||||||
|
|
||||||
class TestDurations:
|
class TestDurations:
|
||||||
source = """
|
source = """
|
||||||
import time
|
from _pytest import timing
|
||||||
frag = 0.002
|
|
||||||
def test_something():
|
def test_something():
|
||||||
pass
|
pass
|
||||||
def test_2():
|
def test_2():
|
||||||
time.sleep(frag*5)
|
timing.sleep(0.010)
|
||||||
def test_1():
|
def test_1():
|
||||||
time.sleep(frag)
|
timing.sleep(0.002)
|
||||||
def test_3():
|
def test_3():
|
||||||
time.sleep(frag*10)
|
timing.sleep(0.020)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def test_calls(self, testdir):
|
def test_calls(self, testdir, mock_timing):
|
||||||
testdir.makepyfile(self.source)
|
testdir.makepyfile(self.source)
|
||||||
result = testdir.runpytest("--durations=10")
|
result = testdir.runpytest_inprocess("--durations=10")
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
result.stdout.fnmatch_lines_random(
|
result.stdout.fnmatch_lines_random(
|
||||||
["*durations*", "*call*test_3*", "*call*test_2*"]
|
["*durations*", "*call*test_3*", "*call*test_2*"]
|
||||||
|
@ -918,16 +917,17 @@ class TestDurations:
|
||||||
["(0.00 durations hidden. Use -vv to show these durations.)"]
|
["(0.00 durations hidden. Use -vv to show these durations.)"]
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_calls_show_2(self, testdir):
|
def test_calls_show_2(self, testdir, mock_timing):
|
||||||
|
|
||||||
testdir.makepyfile(self.source)
|
testdir.makepyfile(self.source)
|
||||||
result = testdir.runpytest("--durations=2")
|
result = testdir.runpytest_inprocess("--durations=2")
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
lines = result.stdout.get_lines_after("*slowest*durations*")
|
lines = result.stdout.get_lines_after("*slowest*durations*")
|
||||||
assert "4 passed" in lines[2]
|
assert "4 passed" in lines[2]
|
||||||
|
|
||||||
def test_calls_showall(self, testdir):
|
def test_calls_showall(self, testdir, mock_timing):
|
||||||
testdir.makepyfile(self.source)
|
testdir.makepyfile(self.source)
|
||||||
result = testdir.runpytest("--durations=0")
|
result = testdir.runpytest_inprocess("--durations=0")
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
for x in "23":
|
for x in "23":
|
||||||
for y in ("call",): # 'setup', 'call', 'teardown':
|
for y in ("call",): # 'setup', 'call', 'teardown':
|
||||||
|
@ -937,9 +937,9 @@ class TestDurations:
|
||||||
else:
|
else:
|
||||||
raise AssertionError("not found {} {}".format(x, y))
|
raise AssertionError("not found {} {}".format(x, y))
|
||||||
|
|
||||||
def test_calls_showall_verbose(self, testdir):
|
def test_calls_showall_verbose(self, testdir, mock_timing):
|
||||||
testdir.makepyfile(self.source)
|
testdir.makepyfile(self.source)
|
||||||
result = testdir.runpytest("--durations=0", "-vv")
|
result = testdir.runpytest_inprocess("--durations=0", "-vv")
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
for x in "123":
|
for x in "123":
|
||||||
for y in ("call",): # 'setup', 'call', 'teardown':
|
for y in ("call",): # 'setup', 'call', 'teardown':
|
||||||
|
@ -949,13 +949,13 @@ class TestDurations:
|
||||||
else:
|
else:
|
||||||
raise AssertionError("not found {} {}".format(x, y))
|
raise AssertionError("not found {} {}".format(x, y))
|
||||||
|
|
||||||
def test_with_deselected(self, testdir):
|
def test_with_deselected(self, testdir, mock_timing):
|
||||||
testdir.makepyfile(self.source)
|
testdir.makepyfile(self.source)
|
||||||
result = testdir.runpytest("--durations=2", "-k test_2")
|
result = testdir.runpytest_inprocess("--durations=2", "-k test_2")
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
result.stdout.fnmatch_lines(["*durations*", "*call*test_2*"])
|
result.stdout.fnmatch_lines(["*durations*", "*call*test_2*"])
|
||||||
|
|
||||||
def test_with_failing_collection(self, testdir):
|
def test_with_failing_collection(self, testdir, mock_timing):
|
||||||
testdir.makepyfile(self.source)
|
testdir.makepyfile(self.source)
|
||||||
testdir.makepyfile(test_collecterror="""xyz""")
|
testdir.makepyfile(test_collecterror="""xyz""")
|
||||||
result = testdir.runpytest("--durations=2", "-k test_1")
|
result = testdir.runpytest("--durations=2", "-k test_1")
|
||||||
|
@ -965,36 +965,35 @@ class TestDurations:
|
||||||
# output
|
# output
|
||||||
result.stdout.no_fnmatch_line("*duration*")
|
result.stdout.no_fnmatch_line("*duration*")
|
||||||
|
|
||||||
def test_with_not(self, testdir):
|
def test_with_not(self, testdir, mock_timing):
|
||||||
testdir.makepyfile(self.source)
|
testdir.makepyfile(self.source)
|
||||||
result = testdir.runpytest("-k not 1")
|
result = testdir.runpytest_inprocess("-k not 1")
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
|
|
||||||
|
|
||||||
class TestDurationWithFixture:
|
class TestDurationWithFixture:
|
||||||
source = """
|
source = """
|
||||||
import pytest
|
import pytest
|
||||||
import time
|
from _pytest import timing
|
||||||
frag = 0.01
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def setup_fixt():
|
def setup_fixt():
|
||||||
time.sleep(frag)
|
timing.sleep(2)
|
||||||
|
|
||||||
def test_1(setup_fixt):
|
def test_1(setup_fixt):
|
||||||
time.sleep(frag)
|
timing.sleep(5)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def test_setup_function(self, testdir):
|
def test_setup_function(self, testdir, mock_timing):
|
||||||
testdir.makepyfile(self.source)
|
testdir.makepyfile(self.source)
|
||||||
result = testdir.runpytest("--durations=10")
|
result = testdir.runpytest_inprocess("--durations=10")
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
|
|
||||||
result.stdout.fnmatch_lines_random(
|
result.stdout.fnmatch_lines_random(
|
||||||
"""
|
"""
|
||||||
*durations*
|
*durations*
|
||||||
* setup *test_1*
|
5.00s call *test_1*
|
||||||
* call *test_1*
|
2.00s setup *test_1*
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import pytest
|
||||||
from _pytest._code import Code
|
from _pytest._code import Code
|
||||||
from _pytest._code import ExceptionInfo
|
from _pytest._code import ExceptionInfo
|
||||||
from _pytest._code import Frame
|
from _pytest._code import Frame
|
||||||
|
from _pytest._code.code import ExceptionChainRepr
|
||||||
from _pytest._code.code import ReprFuncArgs
|
from _pytest._code.code import ReprFuncArgs
|
||||||
|
|
||||||
|
|
||||||
|
@ -180,3 +181,20 @@ class TestReprFuncArgs:
|
||||||
tw_mock.lines[0]
|
tw_mock.lines[0]
|
||||||
== r"unicode_string = São Paulo, utf8_string = b'S\xc3\xa3o Paulo'"
|
== r"unicode_string = São Paulo, utf8_string = b'S\xc3\xa3o Paulo'"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ExceptionChainRepr():
|
||||||
|
"""Test ExceptionChainRepr, especially with regard to being hashable."""
|
||||||
|
try:
|
||||||
|
raise ValueError()
|
||||||
|
except ValueError:
|
||||||
|
excinfo1 = ExceptionInfo.from_current()
|
||||||
|
excinfo2 = ExceptionInfo.from_current()
|
||||||
|
|
||||||
|
repr1 = excinfo1.getrepr()
|
||||||
|
repr2 = excinfo2.getrepr()
|
||||||
|
assert repr1 != repr2
|
||||||
|
|
||||||
|
assert isinstance(repr1, ExceptionChainRepr)
|
||||||
|
assert hash(repr1) != hash(repr2)
|
||||||
|
assert repr1 is not excinfo1.getrepr()
|
||||||
|
|
|
@ -122,7 +122,8 @@ def test_syntaxerror_rerepresentation() -> None:
|
||||||
assert ex is not None
|
assert ex is not None
|
||||||
assert ex.value.lineno == 1
|
assert ex.value.lineno == 1
|
||||||
assert ex.value.offset in {5, 7} # cpython: 7, pypy3.6 7.1.1: 5
|
assert ex.value.offset in {5, 7} # cpython: 7, pypy3.6 7.1.1: 5
|
||||||
assert ex.value.text == "xyz xyz\n"
|
assert ex.value.text
|
||||||
|
assert ex.value.text.rstrip("\n") == "xyz xyz"
|
||||||
|
|
||||||
|
|
||||||
def test_isparseable() -> None:
|
def test_isparseable() -> None:
|
||||||
|
@ -521,7 +522,7 @@ def test_getfslineno() -> None:
|
||||||
class B:
|
class B:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
B.__name__ = "B2"
|
B.__name__ = B.__qualname__ = "B2"
|
||||||
assert getfslineno(B)[1] == -1
|
assert getfslineno(B)[1] == -1
|
||||||
|
|
||||||
co = compile("...", "", "eval")
|
co = compile("...", "", "eval")
|
||||||
|
|
|
@ -195,3 +195,41 @@ def color_mapping():
|
||||||
pytest.skip("doing limited testing because lacking ordered markup")
|
pytest.skip("doing limited testing because lacking ordered markup")
|
||||||
|
|
||||||
return ColorMapping
|
return ColorMapping
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_timing(monkeypatch):
|
||||||
|
"""Mocks _pytest.timing with a known object that can be used to control timing in tests
|
||||||
|
deterministically.
|
||||||
|
|
||||||
|
pytest itself should always use functions from `_pytest.timing` instead of `time` directly.
|
||||||
|
|
||||||
|
This then allows us more control over time during testing, if testing code also
|
||||||
|
uses `_pytest.timing` functions.
|
||||||
|
|
||||||
|
Time is static, and only advances through `sleep` calls, thus tests might sleep over large
|
||||||
|
numbers and obtain accurate time() calls at the end, making tests reliable and instant.
|
||||||
|
"""
|
||||||
|
import attr
|
||||||
|
|
||||||
|
@attr.s
|
||||||
|
class MockTiming:
|
||||||
|
|
||||||
|
_current_time = attr.ib(default=1590150050.0)
|
||||||
|
|
||||||
|
def sleep(self, seconds):
|
||||||
|
self._current_time += seconds
|
||||||
|
|
||||||
|
def time(self):
|
||||||
|
return self._current_time
|
||||||
|
|
||||||
|
def patch(self):
|
||||||
|
from _pytest import timing
|
||||||
|
|
||||||
|
monkeypatch.setattr(timing, "sleep", self.sleep)
|
||||||
|
monkeypatch.setattr(timing, "time", self.time)
|
||||||
|
monkeypatch.setattr(timing, "perf_counter", self.time)
|
||||||
|
|
||||||
|
result = MockTiming()
|
||||||
|
result.patch()
|
||||||
|
return result
|
||||||
|
|
|
@ -36,8 +36,15 @@ def test_terminal_reporter_writer_attr(pytestconfig):
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
terminal_reporter = pytestconfig.pluginmanager.get_plugin("terminalreporter")
|
terminal_reporter = pytestconfig.pluginmanager.get_plugin("terminalreporter")
|
||||||
|
expected_tw = terminal_reporter._tw
|
||||||
|
|
||||||
with pytest.warns(pytest.PytestDeprecationWarning):
|
with pytest.warns(pytest.PytestDeprecationWarning):
|
||||||
assert terminal_reporter.writer is terminal_reporter._tw
|
assert terminal_reporter.writer is expected_tw
|
||||||
|
|
||||||
|
with pytest.warns(pytest.PytestDeprecationWarning):
|
||||||
|
terminal_reporter.writer = expected_tw
|
||||||
|
|
||||||
|
assert terminal_reporter._tw is expected_tw
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("plugin", sorted(deprecated.DEPRECATED_EXTERNAL_PLUGINS))
|
@pytest.mark.parametrize("plugin", sorted(deprecated.DEPRECATED_EXTERNAL_PLUGINS))
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
[pytest]
|
||||||
|
# dummy pytest.ini to ease direct running of example scripts
|
|
@ -0,0 +1,24 @@
|
||||||
|
from unittest import IsolatedAsyncioTestCase # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
teardowns = []
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncArguments(IsolatedAsyncioTestCase):
|
||||||
|
async def asyncTearDown(self):
|
||||||
|
teardowns.append(None)
|
||||||
|
|
||||||
|
async def test_something_async(self):
|
||||||
|
async def addition(x, y):
|
||||||
|
return x + y
|
||||||
|
|
||||||
|
self.assertEqual(await addition(2, 2), 4)
|
||||||
|
|
||||||
|
async def test_something_async_fails(self):
|
||||||
|
async def addition(x, y):
|
||||||
|
return x + y
|
||||||
|
|
||||||
|
self.assertEqual(await addition(2, 2), 3)
|
||||||
|
|
||||||
|
def test_teardowns(self):
|
||||||
|
assert len(teardowns) == 2
|
|
@ -0,0 +1,22 @@
|
||||||
|
"""Issue #7110"""
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
import asynctest
|
||||||
|
|
||||||
|
|
||||||
|
teardowns = []
|
||||||
|
|
||||||
|
|
||||||
|
class Test(asynctest.TestCase):
|
||||||
|
async def tearDown(self):
|
||||||
|
teardowns.append(None)
|
||||||
|
|
||||||
|
async def test_error(self):
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
self.fail("failing on purpose")
|
||||||
|
|
||||||
|
async def test_ok(self):
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
|
def test_teardowns(self):
|
||||||
|
assert len(teardowns) == 2
|
|
@ -154,3 +154,20 @@ def test_pformat_dispatch():
|
||||||
assert _pformat_dispatch("a") == "'a'"
|
assert _pformat_dispatch("a") == "'a'"
|
||||||
assert _pformat_dispatch("a" * 10, width=5) == "'aaaaaaaaaa'"
|
assert _pformat_dispatch("a" * 10, width=5) == "'aaaaaaaaaa'"
|
||||||
assert _pformat_dispatch("foo bar", width=5) == "('foo '\n 'bar')"
|
assert _pformat_dispatch("foo bar", width=5) == "('foo '\n 'bar')"
|
||||||
|
|
||||||
|
|
||||||
|
def test_broken_getattribute():
|
||||||
|
"""saferepr() can create proper representations of classes with
|
||||||
|
broken __getattribute__ (#7145)
|
||||||
|
"""
|
||||||
|
|
||||||
|
class SomeClass:
|
||||||
|
def __getattribute__(self, attr):
|
||||||
|
raise RuntimeError
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
raise RuntimeError
|
||||||
|
|
||||||
|
assert saferepr(SomeClass()).startswith(
|
||||||
|
"<[RuntimeError() raised in repr()] SomeClass object at 0x"
|
||||||
|
)
|
||||||
|
|
|
@ -463,7 +463,7 @@ class TestFunction:
|
||||||
return '3'
|
return '3'
|
||||||
|
|
||||||
@pytest.mark.parametrize('fix2', ['2'])
|
@pytest.mark.parametrize('fix2', ['2'])
|
||||||
def test_it(fix1, fix2):
|
def test_it(fix1):
|
||||||
assert fix1 == '21'
|
assert fix1 == '21'
|
||||||
assert not fix3_instantiated
|
assert not fix3_instantiated
|
||||||
"""
|
"""
|
||||||
|
@ -1225,7 +1225,7 @@ def test_syntax_error_with_non_ascii_chars(testdir):
|
||||||
result.stdout.fnmatch_lines(["*ERROR collecting*", "*SyntaxError*", "*1 error in*"])
|
result.stdout.fnmatch_lines(["*ERROR collecting*", "*SyntaxError*", "*1 error in*"])
|
||||||
|
|
||||||
|
|
||||||
def test_collecterror_with_fulltrace(testdir):
|
def test_collect_error_with_fulltrace(testdir):
|
||||||
testdir.makepyfile("assert 0")
|
testdir.makepyfile("assert 0")
|
||||||
result = testdir.runpytest("--fulltrace")
|
result = testdir.runpytest("--fulltrace")
|
||||||
result.stdout.fnmatch_lines(
|
result.stdout.fnmatch_lines(
|
||||||
|
@ -1233,15 +1233,12 @@ def test_collecterror_with_fulltrace(testdir):
|
||||||
"collected 0 items / 1 error",
|
"collected 0 items / 1 error",
|
||||||
"",
|
"",
|
||||||
"*= ERRORS =*",
|
"*= ERRORS =*",
|
||||||
"*_ ERROR collecting test_collecterror_with_fulltrace.py _*",
|
"*_ ERROR collecting test_collect_error_with_fulltrace.py _*",
|
||||||
"",
|
|
||||||
"*/_pytest/python.py:*: ",
|
|
||||||
"_ _ _ _ _ _ _ _ *",
|
|
||||||
"",
|
"",
|
||||||
"> assert 0",
|
"> assert 0",
|
||||||
"E assert 0",
|
"E assert 0",
|
||||||
"",
|
"",
|
||||||
"test_collecterror_with_fulltrace.py:1: AssertionError",
|
"test_collect_error_with_fulltrace.py:1: AssertionError",
|
||||||
"*! Interrupted: 1 error during collection !*",
|
"*! Interrupted: 1 error during collection !*",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -36,9 +36,6 @@ class TestMetafunc:
|
||||||
class DefinitionMock(python.FunctionDefinition):
|
class DefinitionMock(python.FunctionDefinition):
|
||||||
obj = attr.ib()
|
obj = attr.ib()
|
||||||
|
|
||||||
def listchain(self):
|
|
||||||
return []
|
|
||||||
|
|
||||||
names = fixtures.getfuncargnames(func)
|
names = fixtures.getfuncargnames(func)
|
||||||
fixtureinfo = FuncFixtureInfoMock(names) # type: Any
|
fixtureinfo = FuncFixtureInfoMock(names) # type: Any
|
||||||
definition = DefinitionMock._create(func) # type: Any
|
definition = DefinitionMock._create(func) # type: Any
|
||||||
|
@ -1902,51 +1899,3 @@ class TestMarkersWithParametrization:
|
||||||
"*= 6 passed in *",
|
"*= 6 passed in *",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_parametrize_explicit_parameters_func(self, testdir: Testdir) -> None:
|
|
||||||
testdir.makepyfile(
|
|
||||||
"""
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def fixture(arg):
|
|
||||||
return arg
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("arg", ["baz"])
|
|
||||||
def test_without_arg(fixture):
|
|
||||||
assert "baz" == fixture
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
result = testdir.runpytest()
|
|
||||||
result.assert_outcomes(error=1)
|
|
||||||
result.stdout.fnmatch_lines(
|
|
||||||
[
|
|
||||||
'*In function "test_without_arg"*',
|
|
||||||
'*Parameter "arg" should be declared explicitly via indirect or in function itself*',
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_parametrize_explicit_parameters_method(self, testdir: Testdir) -> None:
|
|
||||||
testdir.makepyfile(
|
|
||||||
"""
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
class Test:
|
|
||||||
@pytest.fixture
|
|
||||||
def test_fixture(self, argument):
|
|
||||||
return argument
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("argument", ["foobar"])
|
|
||||||
def test_without_argument(self, test_fixture):
|
|
||||||
assert "foobar" == test_fixture
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
result = testdir.runpytest()
|
|
||||||
result.assert_outcomes(error=1)
|
|
||||||
result.stdout.fnmatch_lines(
|
|
||||||
[
|
|
||||||
'*In function "test_without_argument"*',
|
|
||||||
'*Parameter "argument" should be declared explicitly via indirect or in function itself*',
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
|
@ -542,18 +542,40 @@ class TestCaptureFixture:
|
||||||
reprec.assertoutcome(passed=1)
|
reprec.assertoutcome(passed=1)
|
||||||
|
|
||||||
def test_capsysbinary(self, testdir):
|
def test_capsysbinary(self, testdir):
|
||||||
reprec = testdir.inline_runsource(
|
p1 = testdir.makepyfile(
|
||||||
"""\
|
r"""
|
||||||
def test_hello(capsysbinary):
|
def test_hello(capsysbinary):
|
||||||
import sys
|
import sys
|
||||||
# some likely un-decodable bytes
|
|
||||||
sys.stdout.buffer.write(b'\\xfe\\x98\\x20')
|
sys.stdout.buffer.write(b'hello')
|
||||||
|
|
||||||
|
# Some likely un-decodable bytes.
|
||||||
|
sys.stdout.buffer.write(b'\xfe\x98\x20')
|
||||||
|
|
||||||
|
sys.stdout.buffer.flush()
|
||||||
|
|
||||||
|
# Ensure writing in text mode still works and is captured.
|
||||||
|
# https://github.com/pytest-dev/pytest/issues/6871
|
||||||
|
print("world", flush=True)
|
||||||
|
|
||||||
out, err = capsysbinary.readouterr()
|
out, err = capsysbinary.readouterr()
|
||||||
assert out == b'\\xfe\\x98\\x20'
|
assert out == b'hello\xfe\x98\x20world\n'
|
||||||
assert err == b''
|
assert err == b''
|
||||||
|
|
||||||
|
print("stdout after")
|
||||||
|
print("stderr after", file=sys.stderr)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
reprec.assertoutcome(passed=1)
|
result = testdir.runpytest(str(p1), "-rA")
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
[
|
||||||
|
"*- Captured stdout call -*",
|
||||||
|
"stdout after",
|
||||||
|
"*- Captured stderr call -*",
|
||||||
|
"stderr after",
|
||||||
|
"*= 1 passed in *",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
def test_partial_setup_failure(self, testdir):
|
def test_partial_setup_failure(self, testdir):
|
||||||
p = testdir.makepyfile(
|
p = testdir.makepyfile(
|
||||||
|
@ -977,7 +999,7 @@ class TestFDCapture:
|
||||||
cap.start()
|
cap.start()
|
||||||
tmpfile.write(data1)
|
tmpfile.write(data1)
|
||||||
tmpfile.flush()
|
tmpfile.flush()
|
||||||
cap.writeorg(data2)
|
cap.writeorg(data2.decode("ascii"))
|
||||||
scap = cap.snap()
|
scap = cap.snap()
|
||||||
cap.done()
|
cap.done()
|
||||||
assert scap == data1.decode("ascii")
|
assert scap == data1.decode("ascii")
|
||||||
|
|
|
@ -1332,3 +1332,24 @@ def test_does_not_put_src_on_path(testdir):
|
||||||
)
|
)
|
||||||
result = testdir.runpytest()
|
result = testdir.runpytest()
|
||||||
assert result.ret == ExitCode.OK
|
assert result.ret == ExitCode.OK
|
||||||
|
|
||||||
|
|
||||||
|
def test_fscollector_from_parent(tmpdir, request):
|
||||||
|
"""Ensure File.from_parent can forward custom arguments to the constructor.
|
||||||
|
|
||||||
|
Context: https://github.com/pytest-dev/pytest-cpp/pull/47
|
||||||
|
"""
|
||||||
|
|
||||||
|
class MyCollector(pytest.File):
|
||||||
|
def __init__(self, fspath, parent, x):
|
||||||
|
super().__init__(fspath, parent)
|
||||||
|
self.x = x
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_parent(cls, parent, *, fspath, x):
|
||||||
|
return super().from_parent(parent=parent, fspath=fspath, x=x)
|
||||||
|
|
||||||
|
collector = MyCollector.from_parent(
|
||||||
|
parent=request.session, fspath=tmpdir / "foo", x=10
|
||||||
|
)
|
||||||
|
assert collector.x == 10
|
||||||
|
|
|
@ -342,6 +342,15 @@ class TestPDB:
|
||||||
child.sendeof()
|
child.sendeof()
|
||||||
self.flush(child)
|
self.flush(child)
|
||||||
|
|
||||||
|
def test_pdb_prevent_ConftestImportFailure_hiding_exception(self, testdir):
|
||||||
|
testdir.makepyfile("def test_func(): pass")
|
||||||
|
sub_dir = testdir.tmpdir.join("ns").ensure_dir()
|
||||||
|
sub_dir.join("conftest").new(ext=".py").write("import unknown")
|
||||||
|
sub_dir.join("test_file").new(ext=".py").write("def test_func(): pass")
|
||||||
|
|
||||||
|
result = testdir.runpytest_subprocess("--pdb", ".")
|
||||||
|
result.stdout.fnmatch_lines(["-> import unknown"])
|
||||||
|
|
||||||
def test_pdb_interaction_capturing_simple(self, testdir):
|
def test_pdb_interaction_capturing_simple(self, testdir):
|
||||||
p1 = testdir.makepyfile(
|
p1 = testdir.makepyfile(
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -5,6 +5,7 @@ import pytest
|
||||||
from _pytest.compat import MODULE_NOT_FOUND_ERROR
|
from _pytest.compat import MODULE_NOT_FOUND_ERROR
|
||||||
from _pytest.doctest import _get_checker
|
from _pytest.doctest import _get_checker
|
||||||
from _pytest.doctest import _is_mocked
|
from _pytest.doctest import _is_mocked
|
||||||
|
from _pytest.doctest import _is_setup_py
|
||||||
from _pytest.doctest import _patch_unwrap_mock_aware
|
from _pytest.doctest import _patch_unwrap_mock_aware
|
||||||
from _pytest.doctest import DoctestItem
|
from _pytest.doctest import DoctestItem
|
||||||
from _pytest.doctest import DoctestModule
|
from _pytest.doctest import DoctestModule
|
||||||
|
@ -1487,3 +1488,27 @@ def test_warning_on_unwrap_of_broken_object(stop):
|
||||||
with pytest.raises(KeyError):
|
with pytest.raises(KeyError):
|
||||||
inspect.unwrap(bad_instance, stop=stop)
|
inspect.unwrap(bad_instance, stop=stop)
|
||||||
assert inspect.unwrap.__module__ == "inspect"
|
assert inspect.unwrap.__module__ == "inspect"
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_setup_py_not_named_setup_py(tmpdir):
|
||||||
|
not_setup_py = tmpdir.join("not_setup.py")
|
||||||
|
not_setup_py.write('from setuptools import setup; setup(name="foo")')
|
||||||
|
assert not _is_setup_py(not_setup_py)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mod", ("setuptools", "distutils.core"))
|
||||||
|
def test_is_setup_py_is_a_setup_py(tmpdir, mod):
|
||||||
|
setup_py = tmpdir.join("setup.py")
|
||||||
|
setup_py.write('from {} import setup; setup(name="foo")'.format(mod))
|
||||||
|
assert _is_setup_py(setup_py)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mod", ("setuptools", "distutils.core"))
|
||||||
|
def test_is_setup_py_different_encoding(tmpdir, mod):
|
||||||
|
setup_py = tmpdir.join("setup.py")
|
||||||
|
contents = (
|
||||||
|
"# -*- coding: cp1252 -*-\n"
|
||||||
|
'from {} import setup; setup(name="foo", description="€")\n'.format(mod)
|
||||||
|
)
|
||||||
|
setup_py.write_binary(contents.encode("cp1252"))
|
||||||
|
assert _is_setup_py(setup_py)
|
||||||
|
|
|
@ -200,23 +200,23 @@ class TestPython:
|
||||||
timestamp = datetime.strptime(node["timestamp"], "%Y-%m-%dT%H:%M:%S.%f")
|
timestamp = datetime.strptime(node["timestamp"], "%Y-%m-%dT%H:%M:%S.%f")
|
||||||
assert start_time <= timestamp < datetime.now()
|
assert start_time <= timestamp < datetime.now()
|
||||||
|
|
||||||
def test_timing_function(self, testdir, run_and_parse):
|
def test_timing_function(self, testdir, run_and_parse, mock_timing):
|
||||||
testdir.makepyfile(
|
testdir.makepyfile(
|
||||||
"""
|
"""
|
||||||
import time, pytest
|
from _pytest import timing
|
||||||
def setup_module():
|
def setup_module():
|
||||||
time.sleep(0.01)
|
timing.sleep(1)
|
||||||
def teardown_module():
|
def teardown_module():
|
||||||
time.sleep(0.01)
|
timing.sleep(2)
|
||||||
def test_sleep():
|
def test_sleep():
|
||||||
time.sleep(0.01)
|
timing.sleep(4)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
result, dom = run_and_parse()
|
result, dom = run_and_parse()
|
||||||
node = dom.find_first_by_tag("testsuite")
|
node = dom.find_first_by_tag("testsuite")
|
||||||
tnode = node.find_first_by_tag("testcase")
|
tnode = node.find_first_by_tag("testcase")
|
||||||
val = tnode["time"]
|
val = tnode["time"]
|
||||||
assert round(float(val), 2) >= 0.03
|
assert float(val) == 7.0
|
||||||
|
|
||||||
@pytest.mark.parametrize("duration_report", ["call", "total"])
|
@pytest.mark.parametrize("duration_report", ["call", "total"])
|
||||||
def test_junit_duration_report(
|
def test_junit_duration_report(
|
||||||
|
|
|
@ -58,3 +58,30 @@ def test__check_initialpaths_for_relpath():
|
||||||
|
|
||||||
outside = py.path.local("/outside")
|
outside = py.path.local("/outside")
|
||||||
assert nodes._check_initialpaths_for_relpath(FakeSession, outside) is None
|
assert nodes._check_initialpaths_for_relpath(FakeSession, outside) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_failure_with_changed_cwd(testdir):
|
||||||
|
"""
|
||||||
|
Test failure lines should use absolute paths if cwd has changed since
|
||||||
|
invocation, so the path is correct (#6428).
|
||||||
|
"""
|
||||||
|
p = testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def private_dir():
|
||||||
|
out_dir = 'ddd'
|
||||||
|
os.mkdir(out_dir)
|
||||||
|
old_dir = os.getcwd()
|
||||||
|
os.chdir(out_dir)
|
||||||
|
yield out_dir
|
||||||
|
os.chdir(old_dir)
|
||||||
|
|
||||||
|
def test_show_wrong_path(private_dir):
|
||||||
|
assert False
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest()
|
||||||
|
result.stdout.fnmatch_lines([str(p) + ":*: AssertionError", "*1 failed in *"])
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import os.path
|
import os.path
|
||||||
import sys
|
import sys
|
||||||
|
import unittest.mock
|
||||||
|
|
||||||
import py
|
import py
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from _pytest.pathlib import ensure_deletable
|
||||||
from _pytest.pathlib import fnmatch_ex
|
from _pytest.pathlib import fnmatch_ex
|
||||||
|
from _pytest.pathlib import get_extended_length_path_str
|
||||||
from _pytest.pathlib import get_lock_path
|
from _pytest.pathlib import get_lock_path
|
||||||
from _pytest.pathlib import maybe_delete_a_numbered_dir
|
from _pytest.pathlib import maybe_delete_a_numbered_dir
|
||||||
from _pytest.pathlib import Path
|
from _pytest.pathlib import Path
|
||||||
|
@ -89,3 +92,45 @@ def test_access_denied_during_cleanup(tmp_path, monkeypatch):
|
||||||
lock_path = get_lock_path(path)
|
lock_path = get_lock_path(path)
|
||||||
maybe_delete_a_numbered_dir(path)
|
maybe_delete_a_numbered_dir(path)
|
||||||
assert not lock_path.is_file()
|
assert not lock_path.is_file()
|
||||||
|
|
||||||
|
|
||||||
|
def test_long_path_during_cleanup(tmp_path):
|
||||||
|
"""Ensure that deleting long path works (particularly on Windows (#6775))."""
|
||||||
|
path = (tmp_path / ("a" * 250)).resolve()
|
||||||
|
if sys.platform == "win32":
|
||||||
|
# make sure that the full path is > 260 characters without any
|
||||||
|
# component being over 260 characters
|
||||||
|
assert len(str(path)) > 260
|
||||||
|
extended_path = "\\\\?\\" + str(path)
|
||||||
|
else:
|
||||||
|
extended_path = str(path)
|
||||||
|
os.mkdir(extended_path)
|
||||||
|
assert os.path.isdir(extended_path)
|
||||||
|
maybe_delete_a_numbered_dir(path)
|
||||||
|
assert not os.path.isdir(extended_path)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_extended_length_path_str():
|
||||||
|
assert get_extended_length_path_str(r"c:\foo") == r"\\?\c:\foo"
|
||||||
|
assert get_extended_length_path_str(r"\\share\foo") == r"\\?\UNC\share\foo"
|
||||||
|
assert get_extended_length_path_str(r"\\?\UNC\share\foo") == r"\\?\UNC\share\foo"
|
||||||
|
assert get_extended_length_path_str(r"\\?\c:\foo") == r"\\?\c:\foo"
|
||||||
|
|
||||||
|
|
||||||
|
def test_suppress_error_removing_lock(tmp_path):
|
||||||
|
"""ensure_deletable should not raise an exception if the lock file cannot be removed (#5456)"""
|
||||||
|
path = tmp_path / "dir"
|
||||||
|
path.mkdir()
|
||||||
|
lock = get_lock_path(path)
|
||||||
|
lock.touch()
|
||||||
|
mtime = lock.stat().st_mtime
|
||||||
|
|
||||||
|
with unittest.mock.patch.object(Path, "unlink", side_effect=OSError):
|
||||||
|
assert not ensure_deletable(
|
||||||
|
path, consider_lock_dead_if_created_before=mtime + 30
|
||||||
|
)
|
||||||
|
assert lock.is_file()
|
||||||
|
|
||||||
|
# check now that we can remove the lock file in normal circumstances
|
||||||
|
assert ensure_deletable(path, consider_lock_dead_if_created_before=mtime + 30)
|
||||||
|
assert not lock.is_file()
|
||||||
|
|
|
@ -395,6 +395,14 @@ class TestReportSerialization:
|
||||||
# for same reasons as previous test, ensure we don't blow up here
|
# for same reasons as previous test, ensure we don't blow up here
|
||||||
loaded_report.longrepr.toterminal(tw_mock)
|
loaded_report.longrepr.toterminal(tw_mock)
|
||||||
|
|
||||||
|
def test_report_prevent_ConftestImportFailure_hiding_exception(self, testdir):
|
||||||
|
sub_dir = testdir.tmpdir.join("ns").ensure_dir()
|
||||||
|
sub_dir.join("conftest").new(ext=".py").write("import unknown")
|
||||||
|
|
||||||
|
result = testdir.runpytest_subprocess(".")
|
||||||
|
result.stdout.fnmatch_lines(["E *Error: No module named 'unknown'"])
|
||||||
|
result.stdout.no_fnmatch_line("ERROR - *ConftestImportFailure*")
|
||||||
|
|
||||||
|
|
||||||
class TestHooks:
|
class TestHooks:
|
||||||
"""Test that the hooks are working correctly for plugins"""
|
"""Test that the hooks are working correctly for plugins"""
|
||||||
|
|
|
@ -193,6 +193,42 @@ def test_no_resultlog_on_slaves(testdir):
|
||||||
assert resultlog_key not in config._store
|
assert resultlog_key not in config._store
|
||||||
|
|
||||||
|
|
||||||
|
def test_unknown_teststatus(testdir):
|
||||||
|
"""Ensure resultlog correctly handles unknown status from pytest_report_teststatus
|
||||||
|
|
||||||
|
Inspired on pytest-rerunfailures.
|
||||||
|
"""
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
def test():
|
||||||
|
assert 0
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
testdir.makeconftest(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
def pytest_report_teststatus(report):
|
||||||
|
if report.outcome == 'rerun':
|
||||||
|
return "rerun", "r", "RERUN"
|
||||||
|
|
||||||
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
|
def pytest_runtest_makereport():
|
||||||
|
res = yield
|
||||||
|
report = res.get_result()
|
||||||
|
if report.when == "call":
|
||||||
|
report.outcome = 'rerun'
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest("--resultlog=result.log")
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
["test_unknown_teststatus.py r *[[]100%[]]", "* 1 rerun *"]
|
||||||
|
)
|
||||||
|
|
||||||
|
lines = testdir.tmpdir.join("result.log").readlines(cr=0)
|
||||||
|
assert lines[0] == "r test_unknown_teststatus.py::test"
|
||||||
|
|
||||||
|
|
||||||
def test_failure_issue380(testdir):
|
def test_failure_issue380(testdir):
|
||||||
testdir.makeconftest(
|
testdir.makeconftest(
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -537,28 +537,24 @@ class TestTrialUnittest:
|
||||||
)
|
)
|
||||||
result.stdout.fnmatch_lines(
|
result.stdout.fnmatch_lines(
|
||||||
[
|
[
|
||||||
"test_trial_error.py::TC::test_four SKIPPED",
|
"test_trial_error.py::TC::test_four FAILED",
|
||||||
"test_trial_error.py::TC::test_four ERROR",
|
"test_trial_error.py::TC::test_four ERROR",
|
||||||
"test_trial_error.py::TC::test_one FAILED",
|
"test_trial_error.py::TC::test_one FAILED",
|
||||||
"test_trial_error.py::TC::test_three FAILED",
|
"test_trial_error.py::TC::test_three FAILED",
|
||||||
"test_trial_error.py::TC::test_two SKIPPED",
|
"test_trial_error.py::TC::test_two FAILED",
|
||||||
"test_trial_error.py::TC::test_two ERROR",
|
|
||||||
"*ERRORS*",
|
"*ERRORS*",
|
||||||
"*_ ERROR at teardown of TC.test_four _*",
|
"*_ ERROR at teardown of TC.test_four _*",
|
||||||
"NOTE: Incompatible Exception Representation, displaying natively:",
|
|
||||||
"*DelayedCalls*",
|
|
||||||
"*_ ERROR at teardown of TC.test_two _*",
|
|
||||||
"NOTE: Incompatible Exception Representation, displaying natively:",
|
|
||||||
"*DelayedCalls*",
|
"*DelayedCalls*",
|
||||||
"*= FAILURES =*",
|
"*= FAILURES =*",
|
||||||
# "*_ TC.test_four _*",
|
"*_ TC.test_four _*",
|
||||||
# "*NameError*crash*",
|
"*NameError*crash*",
|
||||||
"*_ TC.test_one _*",
|
"*_ TC.test_one _*",
|
||||||
"*NameError*crash*",
|
"*NameError*crash*",
|
||||||
"*_ TC.test_three _*",
|
"*_ TC.test_three _*",
|
||||||
"NOTE: Incompatible Exception Representation, displaying natively:",
|
|
||||||
"*DelayedCalls*",
|
"*DelayedCalls*",
|
||||||
"*= 2 failed, 2 skipped, 2 errors in *",
|
"*_ TC.test_two _*",
|
||||||
|
"*NameError*crash*",
|
||||||
|
"*= 4 failed, 1 error in *",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -876,6 +872,37 @@ def test_no_teardown_if_setupclass_failed(testdir):
|
||||||
reprec.assertoutcome(passed=1, failed=1)
|
reprec.assertoutcome(passed=1, failed=1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_cleanup_functions(testdir):
|
||||||
|
"""Ensure functions added with addCleanup are always called after each test ends (#6947)"""
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
cleanups = []
|
||||||
|
|
||||||
|
class Test(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_func_1(self):
|
||||||
|
self.addCleanup(cleanups.append, "test_func_1")
|
||||||
|
|
||||||
|
def test_func_2(self):
|
||||||
|
self.addCleanup(cleanups.append, "test_func_2")
|
||||||
|
assert 0
|
||||||
|
|
||||||
|
def test_func_3_check_cleanups(self):
|
||||||
|
assert cleanups == ["test_func_1", "test_func_2"]
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest("-v")
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
[
|
||||||
|
"*::test_func_1 PASSED *",
|
||||||
|
"*::test_func_2 FAILED *",
|
||||||
|
"*::test_func_3_check_cleanups PASSED *",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_issue333_result_clearing(testdir):
|
def test_issue333_result_clearing(testdir):
|
||||||
testdir.makeconftest(
|
testdir.makeconftest(
|
||||||
"""
|
"""
|
||||||
|
@ -1129,3 +1156,89 @@ def test_trace(testdir, monkeypatch):
|
||||||
result = testdir.runpytest("--trace", str(p1))
|
result = testdir.runpytest("--trace", str(p1))
|
||||||
assert len(calls) == 2
|
assert len(calls) == 2
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_pdb_teardown_called(testdir, monkeypatch):
|
||||||
|
"""Ensure tearDown() is always called when --pdb is given in the command-line.
|
||||||
|
|
||||||
|
We delay the normal tearDown() calls when --pdb is given, so this ensures we are calling
|
||||||
|
tearDown() eventually to avoid memory leaks when using --pdb.
|
||||||
|
"""
|
||||||
|
teardowns = []
|
||||||
|
monkeypatch.setattr(
|
||||||
|
pytest, "test_pdb_teardown_called_teardowns", teardowns, raising=False
|
||||||
|
)
|
||||||
|
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
class MyTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pytest.test_pdb_teardown_called_teardowns.append(self.id())
|
||||||
|
|
||||||
|
def test_1(self):
|
||||||
|
pass
|
||||||
|
def test_2(self):
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest_inprocess("--pdb")
|
||||||
|
result.stdout.fnmatch_lines("* 2 passed in *")
|
||||||
|
assert teardowns == [
|
||||||
|
"test_pdb_teardown_called.MyTestCase.test_1",
|
||||||
|
"test_pdb_teardown_called.MyTestCase.test_2",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mark", ["@unittest.skip", "@pytest.mark.skip"])
|
||||||
|
def test_pdb_teardown_skipped(testdir, monkeypatch, mark):
|
||||||
|
"""
|
||||||
|
With --pdb, setUp and tearDown should not be called for skipped tests.
|
||||||
|
"""
|
||||||
|
tracked = []
|
||||||
|
monkeypatch.setattr(pytest, "test_pdb_teardown_skipped", tracked, raising=False)
|
||||||
|
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import unittest
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
class MyTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
pytest.test_pdb_teardown_skipped.append("setUp:" + self.id())
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pytest.test_pdb_teardown_skipped.append("tearDown:" + self.id())
|
||||||
|
|
||||||
|
{mark}("skipped for reasons")
|
||||||
|
def test_1(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
""".format(
|
||||||
|
mark=mark
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result = testdir.runpytest_inprocess("--pdb")
|
||||||
|
result.stdout.fnmatch_lines("* 1 skipped in *")
|
||||||
|
assert tracked == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_async_support(testdir):
|
||||||
|
pytest.importorskip("unittest.async_case")
|
||||||
|
|
||||||
|
testdir.copy_example("unittest/test_unittest_asyncio.py")
|
||||||
|
reprec = testdir.inline_run()
|
||||||
|
reprec.assertoutcome(failed=1, passed=2)
|
||||||
|
|
||||||
|
|
||||||
|
def test_asynctest_support(testdir):
|
||||||
|
"""Check asynctest support (#7110)"""
|
||||||
|
pytest.importorskip("asynctest")
|
||||||
|
|
||||||
|
testdir.copy_example("unittest/test_unittest_asynctest.py")
|
||||||
|
reprec = testdir.inline_run()
|
||||||
|
reprec.assertoutcome(failed=1, passed=2)
|
||||||
|
|
7
tox.ini
7
tox.ini
|
@ -9,9 +9,9 @@ envlist =
|
||||||
py36
|
py36
|
||||||
py37
|
py37
|
||||||
py38
|
py38
|
||||||
pypy
|
py39
|
||||||
pypy3
|
pypy3
|
||||||
py37-{pexpect,xdist,twisted,numpy,pluggymaster}
|
py37-{pexpect,xdist,unittestextras,numpy,pluggymaster}
|
||||||
doctesting
|
doctesting
|
||||||
py37-freeze
|
py37-freeze
|
||||||
docs
|
docs
|
||||||
|
@ -50,7 +50,8 @@ deps =
|
||||||
pexpect: pexpect
|
pexpect: pexpect
|
||||||
pluggymaster: git+https://github.com/pytest-dev/pluggy.git@master
|
pluggymaster: git+https://github.com/pytest-dev/pluggy.git@master
|
||||||
pygments
|
pygments
|
||||||
twisted: twisted
|
unittestextras: twisted
|
||||||
|
unittestextras: asynctest
|
||||||
xdist: pytest-xdist>=1.13
|
xdist: pytest-xdist>=1.13
|
||||||
{env:_PYTEST_TOX_EXTRA_DEP:}
|
{env:_PYTEST_TOX_EXTRA_DEP:}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue