Compare commits

...

49 Commits
main ... 5.4.x

Author SHA1 Message Date
Bruno Oliveira c934de6e1b
Merge pull request #7354 from nicoddemus/backport-7352
[5.4.x] Merge pull request #7352 from nicoddemus/lock-unlink-5456
2020-06-12 10:13:46 -03:00
Bruno Oliveira 5ff9bf4ea0 Merge pull request #7352 from nicoddemus/lock-unlink-5456
Suppress errors while removing tmpdir's lock files
2020-06-12 08:54:09 -03:00
Bruno Oliveira 6702741978
Merge pull request #7306 from nicoddemus/backport-7242
[5.4.x] Merge pull request #7242 from nicoddemus/fix-flaky-test
2020-06-02 21:44:06 -03:00
Bruno Oliveira 03bf27f4df Merge pull request #7242 from nicoddemus/fix-flaky-test
Introduce _pytest.timing as a way to control timing during internal tests

Conflicts:
  	src/_pytest/pytester.py
	  src/_pytest/runner.py
	  src/_pytest/terminal.py
  	testing/acceptance_test.py
2020-06-02 20:39:00 -03:00
Bruno Oliveira 48f787ecff
Merge pull request #7301 from pytest-dev/release-5.4.3
Prepare release 5.4.3
2020-06-02 14:17:25 -03:00
pytest bot b322004047 Preparing release version 5.4.3 2020-06-02 15:22:35 +00:00
Bruno Oliveira 2d795dc07b
Merge pull request #7298 from nicoddemus/backport-6755
[5.4.x] Fix removal of very long paths on Windows (#6755)
2020-06-02 09:48:49 -03:00
Ran Benita 2d6b846978
Merge pull request #7299 from nicoddemus/backport-7294
[5.4.x] Merge pull request #7294 from nicoddemus/codecov-adjustments
2020-06-02 15:29:26 +03:00
Bruno Oliveira 703d0f50d8 Merge pull request #7294 from nicoddemus/codecov-adjustments 2020-06-02 09:02:48 -03:00
Tor Colvin 56e6482405 Fix removal of very long paths on Windows (#6755)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2020-06-02 08:58:01 -03:00
Bruno Oliveira 589176e9fe
Merge pull request #7285 from nicoddemus/backport-7220
[5.4] Merge pull request #7220 from nicoddemus/issue-6428
2020-05-30 21:00:53 -03:00
Bruno Oliveira 3734a27733
[5.4] Do not call TestCase.tearDown for skipped tests (#7236) (#7283)
[5.4] Do not call TestCase.tearDown for skipped tests (#7236)
2020-05-30 21:00:08 -03:00
Bruno Oliveira e1a21e46b0 Merge pull request #7220 from nicoddemus/issue-6428 2020-05-30 20:14:57 -03:00
Bruno Oliveira 551400e8d6 Do not call TestCase.tearDown for skipped tests (#7236)
Fix #7215
2020-05-30 14:34:45 -03:00
Anthony Sottile b7b729298c
Merge pull request #7271 from asottile/backport-7257
[5.4.x] Merge pull request #7257 from DahlitzFlorian/fix-issue-6956
2020-05-27 15:02:10 -07:00
Anthony Sottile 21ca38b932 Merge pull request #7257 from DahlitzFlorian/fix-issue-6956
Prevent pytest from printing ConftestImportFailure traceback

(cherry picked from commit b3db440d4c)
2020-05-27 13:52:10 -07:00
Anthony Sottile 565f4cb4ad
Merge pull request #7248 from asottile/backport-7244
[5.4.x] Merge pull request #7244 from DahlitzFlorian/fix-issue-7150
2020-05-23 12:45:35 -07:00
Anthony Sottile af6548a4e7 Merge pull request #7244 from DahlitzFlorian/fix-issue-7150
Prevent hiding underlying exception when ConfTestImportFailure is raised

(cherry picked from commit 45f53266e6)
2020-05-23 12:13:34 -07:00
Bruno Oliveira 2fb2962df4
Merge pull request #7193 from pytest-dev/release-5.4.2 2020-05-08 09:01:11 -03:00
pytest bot f838c7b7eb Preparing release version 5.4.2 2020-05-08 11:14:08 +00:00
Bruno Oliveira 25b53c4196
Merge pull request #7188 from asottile/backport_7179 2020-05-08 06:58:08 -03:00
Bruno Oliveira fc27171d57
Merge pull request #7189 from asottile/backport-7186 2020-05-08 06:57:25 -03:00
Anthony Sottile d18e426b24 Merge pull request #7186 from asottile/is_setup_py_encoding_agnostic
Fix _is_setup_py for files encoded differently than locale
2020-05-07 14:02:00 -07:00
Bruno Oliveira e83fa48dd1 Merge pull request #7179 from asottile/py39 2020-05-07 13:57:10 -07:00
Bruno Oliveira c53d52c3f2
Merge pull request #7174 from nicoddemus/backport-7168 2020-05-06 18:24:43 -03:00
Bruno Oliveira 3886c6d735 Merge pull request #7168 from nicoddemus/saferepr-getattr-fail-7145 2020-05-06 17:59:42 -03:00
Bruno Oliveira 80936b6762
Merge pull request #7156 from nicoddemus/backport-7151 2020-05-02 16:01:15 -03:00
Bruno Oliveira 5ca08e9b8a Merge pull request #7151 from nicoddemus/unittest-cleanup-6947 2020-05-02 15:44:39 -03:00
Bruno Oliveira ba2c49e71e
Merge pull request #7149 from nicoddemus/backport-7144 2020-05-01 17:08:55 -03:00
Bruno Oliveira da615a4fbe Merge pull request #7144 from nicoddemus/async-testcase-7110 2020-05-01 16:45:01 -03:00
Bruno Oliveira 32562881d4
Merge pull request #7146 from nicoddemus/backport-7143 2020-05-01 13:31:24 -03:00
Bruno Oliveira 2a0bbfe63f Merge pull request #7143 from nicoddemus/file-from-parent 2020-05-01 13:08:42 -03:00
Ronny Pfannschmidt 9aed656ec7
Merge pull request #7066 from RonnyPfannschmidt/backport-6992-restore-tmpdir-indirection
backport #6992 to 5.4.x - restores tmpdir indirection
2020-04-10 13:11:17 +02:00
Ronny Pfannschmidt a600e7a2a4
Merge pull request #7068 from RonnyPfannschmidt/backport-6927-run-async-unittest
backport #6927: run async testcase methods
2020-04-10 13:11:05 +02:00
Ronny Pfannschmidt 57a95b3a2c
Merge pull request #7067 from RonnyPfannschmidt/backport-6986-terminalreporter.writer
backport #6986: writable TerminalWriter.writer for deprecation
2020-04-10 11:25:42 +02:00
Ronny Pfannschmidt f5e430fd8f Merge pull request #6927 from RonnyPfannschmidt/fix-6924-run-async-stdlib-unittests
running stdlib asyncio unittests again
2020-04-10 08:16:58 +02:00
Ronny Pfannschmidt 38a4c7e56c Merge pull request #6986 from RonnyPfannschmidt/fix-6951-tw.writer-writable
fix #6951: allow to write TerminalReporter.writer
2020-04-10 08:12:34 +02:00
Ronny Pfannschmidt 40f02d72b0 Merge pull request #6992 from pytest-dev/revert-6767-tmpdir-cleanup-upstream
Revert "tmpdir: clean up indirection via config for factories"
2020-04-10 08:07:53 +02:00
Daniel Hahler 554f600fb4
Fix TerminalRepr instances to be hashable (#6988) (#7006)
pytest-xdist assumes `ExceptionChainRepr` is hashable.

Fixes https://github.com/pytest-dev/pytest/issues/6925.
Fixes https://github.com/pytest-dev/pytest-xdist/issues/515.

(cherry picked from commit 20f6331afd)
2020-04-03 10:59:23 +02:00
Ran Benita e3a3c90d94
[5.4.x] Fix crash when printing while capsysbinary is active (#7002)
Backport of 1fda861190 from master.
2020-04-02 14:31:32 +03:00
Ran Benita f7327759e8 ci: twisted and oldattrs tox envs are now incompatible, don't run them together
twisted started to use `attr.s(eq)` argument which was added recently,
so it fails with oldattrs. One of the CI jobs ran twisted and oldattrs
together, so it started to fail.

Move the twisted code to be covered by another job, and remove it from
the job with the oldattrs.

(cherry picked from commit 2cc3227f6a)
2020-03-31 09:48:11 +02:00
Bruno Oliveira ee1950af77
Merge pull request #6918 from pytest-dev/release-5.4.1
Prepare release 5.4.1
2020-03-13 11:11:05 -03:00
pytest bot 3d0f3baa2b Preparing release version 5.4.1 2020-03-13 13:34:24 +00:00
Bruno Oliveira 8da758b93a
Merge pull request #6917 from nicoddemus/backport-6916
[Backport] Skip link checks when doing releases through the bot (#6916)
2020-03-13 10:34:10 -03:00
Bruno Oliveira 59e5d1bfbf Merge pull request #6916 from nicoddemus/no-link-checks
Skip link checks when doing releases through the bot
2020-03-13 10:32:25 -03:00
Bruno Oliveira b9e2cd0a81
Merge pull request #6914 from nicoddemus/revert-6330
Revert "[parametrize] enforce explicit argnames declaration (#6330)"
2020-03-13 10:18:06 -03:00
Bruno Oliveira a84fcbf5b2 Revert "[parametrize] enforce explicit argnames declaration (#6330)"
This reverts commit 9e262038c8.

Fix #6909
2020-03-13 09:59:53 -03:00
Bruno Oliveira 59c1bfada7
Merge pull request #6913 from nicoddemus/backport-6910
[Backport #6910] Handle unknown stats in pytest_report_teststatus hook
2020-03-13 09:48:11 -03:00
Bruno Oliveira 3267f64724 Merge pull request #6910 from nicoddemus/resultlog-logreport
Handle unknown stats in pytest_report_teststatus hook
2020-03-13 09:30:14 -03:00
54 changed files with 886 additions and 276 deletions

View File

@ -39,6 +39,7 @@ jobs:
"ubuntu-py37-pluggy",
"ubuntu-py37-freeze",
"ubuntu-py38",
"ubuntu-py39",
"ubuntu-pypy3",
"macos-py37",
@ -62,7 +63,7 @@ jobs:
- name: "windows-py37"
python: "3.7"
os: windows-latest
tox_env: "py37-twisted-numpy"
tox_env: "py37-numpy"
- name: "windows-py37-pluggy"
python: "3.7"
os: windows-latest
@ -70,7 +71,7 @@ jobs:
- name: "windows-py38"
python: "3.8"
os: windows-latest
tox_env: "py38"
tox_env: "py38-unittestextras"
use_coverage: true
- name: "ubuntu-py35"
@ -84,7 +85,7 @@ jobs:
- name: "ubuntu-py37"
python: "3.7"
os: ubuntu-latest
tox_env: "py37-lsof-numpy-oldattrs-pexpect-twisted"
tox_env: "py37-lsof-numpy-oldattrs-pexpect"
use_coverage: true
- name: "ubuntu-py37-pluggy"
python: "3.7"
@ -98,6 +99,10 @@ jobs:
python: "3.8"
os: ubuntu-latest
tox_env: "py38-xdist"
- name: "ubuntu-py39"
python: "3.8"
os: ubuntu-latest
tox_env: "py39-xdist"
- name: "ubuntu-pypy3"
python: "pypy3"
os: ubuntu-latest
@ -133,6 +138,12 @@ jobs:
uses: actions/setup-python@v1
with:
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
run: |
python -m pip install --upgrade pip

View File

@ -267,6 +267,7 @@ Tom Dalton
Tom Viner
Tomáš Gavenčiak
Tomer Keren
Tor Colvin
Trevor Bekolay
Tyler Goodlet
Tzu-ping Chung

View File

@ -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``.

View File

@ -1 +1,6 @@
comment: off
# reference: https://docs.codecov.io/docs/codecovyml-reference
coverage:
status:
patch: true
project: false
comment: false

View File

@ -6,6 +6,9 @@ Release announcements
:maxdepth: 2
release-5.4.3
release-5.4.2
release-5.4.1
release-5.4.0
release-5.3.5
release-5.3.4

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -28,6 +28,79 @@ with advance notice in the **Deprecations** section of releases.
.. 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)
=========================
@ -63,10 +136,10 @@ Breaking Changes
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
provide feedback.
``--show-capture`` command-line option was added in ``pytest 3.5.0`` and allows to specify how to
- `#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.
``--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).

View File

@ -402,9 +402,6 @@ The result of this test will be successful:
.. 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
--------------------------------------------------------------
@ -484,11 +481,10 @@ Running it results in some skips if we don't have all the python interpreters in
.. code-block:: pytest
. $ pytest -rs -q multipython.py
ssssssssssss...ssssssssssss [100%]
ssssssssssss......sss...... [100%]
========================= short test summary info ==========================
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.5' not found
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.7' not found
3 passed, 24 skipped in 0.12s
SKIPPED [15] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.5' not found
12 passed, 15 skipped in 0.12s
Indirect parametrization of optional implementations/imports
--------------------------------------------------------------------

View File

@ -443,9 +443,9 @@ additionally it is possible to copy examples for an example folder before runnin
testdir.copy_example("test_example.py")
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.
warnings.warn(
return getattr(object, name, default)
-- Docs: https://docs.pytest.org/en/latest/warnings.html
====================== 2 passed, 2 warnings in 0.12s =======================

View File

@ -126,7 +126,9 @@ def trigger_release(payload_path: Path, token: str) -> None:
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"
check_call(["git", "push", oauth_url, f"HEAD:{release_branch}", "--force"])

View File

@ -26,6 +26,7 @@ classifiers =
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
platforms = unix, linux, osx, cygwin, win32
[options]

View File

@ -32,6 +32,7 @@ import _pytest
from _pytest._io import TerminalWriter
from _pytest._io.saferepr import safeformat
from _pytest._io.saferepr import saferepr
from _pytest.compat import ATTRS_EQ_FIELD
from _pytest.compat import overload
from _pytest.compat import TYPE_CHECKING
@ -911,7 +912,7 @@ class FormattedExcinfo:
return ExceptionChainRepr(repr_chain)
@attr.s
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class TerminalRepr:
def __str__(self) -> str:
# FYI this is called from pytest-xdist's serialization of exception
@ -928,7 +929,7 @@ class TerminalRepr:
raise NotImplementedError()
@attr.s
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ExceptionRepr(TerminalRepr):
def __attrs_post_init__(self):
self.sections = [] # type: List[Tuple[str, str, str]]
@ -942,7 +943,7 @@ class ExceptionRepr(TerminalRepr):
tw.line(content)
@attr.s
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ExceptionChainRepr(ExceptionRepr):
chain = attr.ib(
type=Sequence[
@ -966,7 +967,7 @@ class ExceptionChainRepr(ExceptionRepr):
super().toterminal(tw)
@attr.s
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ReprExceptionInfo(ExceptionRepr):
reprtraceback = attr.ib(type="ReprTraceback")
reprcrash = attr.ib(type="ReprFileLocation")
@ -976,7 +977,7 @@ class ReprExceptionInfo(ExceptionRepr):
super().toterminal(tw)
@attr.s
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ReprTraceback(TerminalRepr):
reprentries = attr.ib(type=Sequence[Union["ReprEntry", "ReprEntryNative"]])
extraline = attr.ib(type=Optional[str])
@ -1010,7 +1011,7 @@ class ReprTracebackNative(ReprTraceback):
self.extraline = None
@attr.s
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ReprEntryNative(TerminalRepr):
lines = attr.ib(type=Sequence[str])
style = "native" # type: _TracebackStyle
@ -1019,7 +1020,7 @@ class ReprEntryNative(TerminalRepr):
tw.write("".join(self.lines))
@attr.s
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ReprEntry(TerminalRepr):
lines = attr.ib(type=Sequence[str])
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):
path = attr.ib(type=str, converter=str)
lineno = attr.ib(type=int)
@ -1110,7 +1111,7 @@ class ReprFileLocation(TerminalRepr):
tw.line(":{}: {}".format(self.lineno, msg))
@attr.s
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ReprLocals(TerminalRepr):
lines = attr.ib(type=Sequence[str])
@ -1119,7 +1120,7 @@ class ReprLocals(TerminalRepr):
tw.line(indent + line)
@attr.s
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ReprFuncArgs(TerminalRepr):
args = attr.ib(type=Sequence[Tuple[str, object]])

View File

@ -20,7 +20,7 @@ def _format_repr_exception(exc: BaseException, obj: Any) -> str:
except BaseException as exc:
exc_info = "unpresentable exception ({})".format(_try_repr_or_str(exc))
return "<[{} raised in repr()] {} object at 0x{:x}>".format(
exc_info, obj.__class__.__name__, id(obj)
exc_info, type(obj).__name__, id(obj)
)

View File

@ -610,8 +610,6 @@ class FDCaptureBinary:
def writeorg(self, data):
""" 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)
@ -631,6 +629,11 @@ class FDCapture(FDCaptureBinary):
res = str(res, enc, "replace")
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:
@ -682,8 +685,9 @@ class SysCaptureBinary:
self._state = "resumed"
def writeorg(self, data):
self._old.write(data)
self._old.flush()
self._old.buffer.write(data)
self._old.buffer.flush()
class SysCapture(SysCaptureBinary):
@ -695,6 +699,10 @@ class SysCapture(SysCaptureBinary):
self.tmpfile.truncate()
return res
def writeorg(self, data):
self._old.write(data)
self._old.flush()
class TeeSysCapture(SysCapture):
def __init__(self, fd, tmpfile=None):

View File

@ -93,6 +93,13 @@ def iscoroutinefunction(func: object) -> bool:
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:
function = get_real_func(function)
fn = py.path.local(inspect.getfile(function))

View File

@ -4,6 +4,7 @@ import functools
import sys
from _pytest import outcomes
from _pytest.config import ConftestImportFailure
from _pytest.config import hookimpl
from _pytest.config.exceptions import UsageError
@ -272,11 +273,15 @@ class PdbInvoke:
class PdbTrace:
@hookimpl(hookwrapper=True)
def pytest_pyfunc_call(self, pyfuncitem):
_test_pytest_function(pyfuncitem)
wrap_pytest_function_for_tracing(pyfuncitem)
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")
testfunction = pyfuncitem.obj
@ -291,6 +296,13 @@ def _test_pytest_function(pyfuncitem):
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):
# XXX we re-use the TerminalReporter's terminalwriter
# 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.
# Use the underlying exception instead:
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:
return excinfo._excinfo[2]

View File

@ -54,3 +54,9 @@ COLLECT_DIRECTORY_HOOK = PytestDeprecationWarning(
"The pytest_collect_directory hook is not working.\n"
"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."
)

View File

@ -108,20 +108,20 @@ def pytest_unconfigure():
RUNNER_CLASS = None
def pytest_collect_file(path, parent):
def pytest_collect_file(path: py.path.local, parent):
config = parent.config
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)
elif _is_doctest(config, path, parent):
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":
return False
contents = path.read()
return "setuptools" in contents or "distutils" in contents
contents = path.read_binary()
return b"setuptools" in contents or b"distutils" in contents
def _is_doctest(config, path, parent):

View File

@ -1,5 +1,6 @@
import functools
import inspect
import itertools
import sys
import warnings
from collections import defaultdict
@ -1277,8 +1278,10 @@ class FixtureManager:
else:
argnames = ()
usefixtures = get_use_fixtures_for_node(node)
initialnames = usefixtures + argnames
usefixtures = itertools.chain.from_iterable(
mark.args for mark in node.iter_markers(name="usefixtures")
)
initialnames = tuple(usefixtures) + argnames
fm = node.session._fixturemanager
initialnames, names_closure, arg2fixturedefs = fm.getfixtureclosure(
initialnames, node, ignore_args=self._get_direct_parametrize_args(node)
@ -1475,12 +1478,3 @@ class FixtureManager:
for fixturedef in fixturedefs:
if nodes.ischildnode(fixturedef.baseid, nodeid):
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
)

View File

@ -13,7 +13,6 @@ import os
import platform
import re
import sys
import time
from datetime import datetime
import py
@ -21,6 +20,7 @@ import py
import pytest
from _pytest import deprecated
from _pytest import nodes
from _pytest import timing
from _pytest.config import filename_arg
from _pytest.store import StoreKey
from _pytest.warnings import _issue_warning_captured
@ -627,14 +627,14 @@ class LogXML:
reporter._add_simple(Junit.error, "internal error", excrepr)
def pytest_sessionstart(self):
self.suite_start_time = time.time()
self.suite_start_time = timing.time()
def pytest_sessionfinish(self):
dirname = os.path.dirname(os.path.abspath(self.logfile))
if not os.path.isdir(dirname):
os.makedirs(dirname)
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
numtests = (
@ -662,7 +662,7 @@ class LogXML:
logfile.close()
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):
__tracebackhide__ = True

View File

@ -19,6 +19,7 @@ from _pytest._code.source import getfslineno
from _pytest.compat import cached_property
from _pytest.compat import TYPE_CHECKING
from _pytest.config import Config
from _pytest.config import ConftestImportFailure
from _pytest.config import PytestPluginManager
from _pytest.deprecated import NODE_USE_FROM_PARENT
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 NodeKeywords
from _pytest.outcomes import fail
from _pytest.outcomes import Failed
from _pytest.pathlib import Path
from _pytest.store import Store
if TYPE_CHECKING:
@ -318,8 +319,10 @@ class Node(metaclass=NodeMeta):
pass
def _repr_failure_py(
self, excinfo: ExceptionInfo[Union[Failed, FixtureLookupError]], style=None
self, excinfo: ExceptionInfo[BaseException], style=None,
) -> Union[str, ReprExceptionInfo, ExceptionChainRepr, FixtureLookupErrorRepr]:
if isinstance(excinfo.value, ConftestImportFailure):
excinfo = ExceptionInfo(excinfo.value.excinfo)
if isinstance(excinfo.value, fail.Exception):
if not excinfo.value.pytrace:
return str(excinfo.value)
@ -346,9 +349,14 @@ class Node(metaclass=NodeMeta):
else:
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:
os.getcwd()
abspath = False
abspath = Path(os.getcwd()) != Path(self.config.invocation_dir)
except OSError:
abspath = True
@ -474,11 +482,11 @@ class FSCollector(Collector):
self._norecursepatterns = self.config.getini("norecursedirs")
@classmethod
def from_parent(cls, parent, *, fspath):
def from_parent(cls, parent, *, fspath, **kw):
"""
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):
# check if we have the common case of running

View File

@ -1,4 +1,5 @@
import atexit
import contextlib
import fnmatch
import itertools
import os
@ -100,10 +101,41 @@ def on_rm_rf_error(func, path: str, exc, *, start_path: Path) -> bool:
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:
"""Remove the path contents recursively, even if some elements
are read-only.
"""
path = ensure_extended_length_path(path)
onerror = partial(on_rm_rf_error, start_path=path)
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:
"""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
try:
lock_path = create_cleanup_lock(path)
@ -257,10 +290,14 @@ def ensure_deletable(path: Path, consider_lock_dead_if_created_before: float) ->
return False
else:
if lock_time < consider_lock_dead_if_created_before:
lock.unlink()
return True
else:
return False
# wa want to ignore any errors while trying to remove the lock such as:
# - PermissionDenied, like the file permissions have changed since the lock creation
# - FileNotFoundError, in case another pytest process got here first.
# 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:

View File

@ -7,7 +7,6 @@ import platform
import re
import subprocess
import sys
import time
import traceback
from fnmatch import fnmatch
from io import StringIO
@ -24,6 +23,7 @@ from weakref import WeakKeyDictionary
import py
import pytest
from _pytest import timing
from _pytest._code import Source
from _pytest.capture import MultiCapture
from _pytest.capture import SysCapture
@ -924,7 +924,7 @@ class Testdir:
if syspathinsert:
self.syspathinsert()
now = time.time()
now = timing.time()
capture = MultiCapture(Capture=SysCapture)
capture.start_capturing()
try:
@ -953,7 +953,7 @@ class Testdir:
sys.stderr.write(err)
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
return res
@ -1154,7 +1154,7 @@ class Testdir:
f1 = open(str(p1), "w", encoding="utf8")
f2 = open(str(p2), "w", encoding="utf8")
try:
now = time.time()
now = timing.time()
popen = self.popen(
cmdargs,
stdin=stdin,
@ -1201,7 +1201,7 @@ class Testdir:
ret = ExitCode(ret)
except ValueError:
pass
return RunResult(ret, out, err, time.time() - now)
return RunResult(ret, out, err, timing.time() - now)
def _dump_lines(self, lines, fp):
try:

View File

@ -34,8 +34,8 @@ from _pytest.compat import get_default_arg_names
from _pytest.compat import get_real_func
from _pytest.compat import getimfunc
from _pytest.compat import getlocation
from _pytest.compat import is_async_function
from _pytest.compat import is_generator
from _pytest.compat import iscoroutinefunction
from _pytest.compat import NOTSET
from _pytest.compat import REGEX_TYPE
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 += (
"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)
def pytest_pyfunc_call(pyfuncitem: "Function"):
testfunction = pyfuncitem.obj
if iscoroutinefunction(testfunction) or (
sys.version_info >= (3, 6) and inspect.isasyncgenfunction(testfunction)
):
async_warn(pyfuncitem.nodeid)
if is_async_function(testfunction):
async_warn_and_skip(pyfuncitem.nodeid)
funcargs = pyfuncitem.funcargs
testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames}
result = testfunction(**testargs)
if hasattr(result, "__await__") or hasattr(result, "__aiter__"):
async_warn(pyfuncitem.nodeid)
async_warn_and_skip(pyfuncitem.nodeid)
return True
@ -936,8 +934,6 @@ class Metafunc:
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.
if _param_mark and _param_mark._param_ids_from:
generated_ids = _param_mark._param_ids_from._param_ids_generated
@ -1110,39 +1106,6 @@ class Metafunc:
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):
"""Find the most appropriate scope for a parametrized call based on its arguments.

View File

@ -77,10 +77,10 @@ class ResultLog:
longrepr = ""
elif report.passed:
longrepr = ""
elif report.failed:
longrepr = str(report.longrepr)
elif report.skipped:
longrepr = str(report.longrepr[2])
else:
longrepr = str(report.longrepr)
self.log_outcome(report, code, longrepr)
def pytest_collectreport(self, report):

View File

@ -2,7 +2,6 @@
import bdb
import os
import sys
from time import time
from typing import Callable
from typing import Dict
from typing import List
@ -14,6 +13,7 @@ import attr
from .reports import CollectErrorRepr
from .reports import CollectReport
from .reports import TestReport
from _pytest import timing
from _pytest._code.code import ExceptionChainRepr
from _pytest._code.code import ExceptionInfo
from _pytest.compat import TYPE_CHECKING
@ -238,8 +238,8 @@ class CallInfo:
def from_call(cls, func, when, reraise=None) -> "CallInfo":
#: context of invocation: one of "setup", "call",
#: "teardown", "memocollect"
start = time()
excinfo = None
start = timing.time()
try:
result = func()
except: # noqa
@ -247,7 +247,7 @@ class CallInfo:
if reraise is not None and excinfo.errisinstance(reraise):
raise
result = None
stop = time()
stop = timing.time()
return cls(start=start, stop=stop, when=when, result=result, excinfo=excinfo)
def __repr__(self):

View File

@ -7,7 +7,6 @@ import collections
import datetime
import platform
import sys
import time
import warnings
from functools import partial
from typing import Any
@ -26,9 +25,11 @@ from more_itertools import collapse
import pytest
from _pytest import nodes
from _pytest import timing
from _pytest._io import TerminalWriter
from _pytest.config import Config
from _pytest.config import ExitCode
from _pytest.deprecated import TERMINALWRITER_WRITER
from _pytest.main import Session
from _pytest.reports import CollectReport
from _pytest.reports import TestReport
@ -284,14 +285,14 @@ class TerminalReporter:
@property
def writer(self) -> TerminalWriter:
warnings.warn(
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."
)
)
warnings.warn(TERMINALWRITER_WRITER, stacklevel=2)
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):
"""Return True if we should display progress information based on the current config"""
# do not show progress if we are not capturing output (#3038)
@ -553,7 +554,7 @@ class TerminalReporter:
if self.isatty:
if self.config.option.verbose >= 0:
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:
self.write("collecting ... ", bold=True)
@ -573,7 +574,7 @@ class TerminalReporter:
if not final:
# Only write "collecting" report every 0.5s.
t = time.time()
t = timing.time()
if (
self._collect_report_last_write is not None
and self._collect_report_last_write > t - REPORT_COLLECTING_RESOLUTION
@ -610,7 +611,7 @@ class TerminalReporter:
@pytest.hookimpl(trylast=True)
def pytest_sessionstart(self, session: Session) -> None:
self._session = session
self._sessionstarttime = time.time()
self._sessionstarttime = timing.time()
if not self.showheader:
return
self.write_sep("=", "test session starts", bold=True)
@ -959,7 +960,7 @@ class TerminalReporter:
if self.verbosity < -1:
return
session_duration = time.time() - self._sessionstarttime
session_duration = timing.time() - self._sessionstarttime
(parts, main_color) = self.build_summary_stats_line()
line_parts = []

13
src/_pytest/timing.py Normal file
View File

@ -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"]

View File

@ -14,6 +14,7 @@ from .pathlib import make_numbered_dir
from .pathlib import make_numbered_dir_with_cleanup
from .pathlib import Path
from _pytest.fixtures import FixtureRequest
from _pytest.monkeypatch import MonkeyPatch
@attr.s
@ -134,18 +135,35 @@ def get_user() -> Optional[str]:
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")
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 TempdirFactory(tmp_path_factory)
# Set dynamically by pytest_configure() above.
return request.config._tmpdirhandler # type: ignore
@pytest.fixture(scope="session")
def tmp_path_factory(request: FixtureRequest) -> TempPathFactory:
"""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:

View File

@ -1,11 +1,11 @@
""" discovery and running of std-library "unittest" style tests. """
import functools
import sys
import traceback
import _pytest._code
import pytest
from _pytest.compat import getimfunc
from _pytest.compat import is_async_function
from _pytest.config import hookimpl
from _pytest.outcomes import exit
from _pytest.outcomes import fail
@ -41,7 +41,7 @@ class UnitTestCase(Class):
if not getattr(cls, "__test__", True):
return
skipped = getattr(cls, "__unittest_skip__", False)
skipped = _is_skipped(cls)
if not skipped:
self._inject_setup_teardown_fixtures(cls)
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)
def fixture(self, request):
if getattr(self, "__unittest_skip__", None):
if _is_skipped(self):
reason = self.__unittest_skip_why__
pytest.skip(reason)
if setup is not None:
@ -113,15 +113,17 @@ class TestCaseFunction(Function):
_testcase = None
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._obj = getattr(self._testcase, self.name)
if hasattr(self, "_request"):
self._request._fillfixtures()
def teardown(self):
if self._needs_explicit_tearDown:
self._testcase.tearDown()
if self._explicit_tearDown is not None:
self._explicit_tearDown()
self._explicit_tearDown = None
self._testcase = None
self._obj = None
@ -204,36 +206,31 @@ class TestCaseFunction(Function):
return bool(expecting_failure_class or expecting_failure_method)
def runtest(self):
# TODO: move testcase reporter into separate class, this shouldnt be on item
import unittest
from _pytest.debugging import maybe_wrap_pytest_function_for_tracing
testMethod = getattr(self._testcase, self._testcase._testMethodName)
maybe_wrap_pytest_function_for_tracing(self)
class _GetOutOf_testPartExecutor(KeyboardInterrupt):
"""Helper exception to get out of unittests's testPartExecutor (see TestCase.run)."""
# let the unittest framework handle async functions
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)
def wrapped_testMethod(*args, **kwargs):
"""Wrap the original method to call into pytest's machinery, so other pytest
features can have a chance to kick in (notably --pdb)"""
# we need to update the actual bound method with self.obj, because
# wrap_pytest_function_for_tracing replaces self.obj by a wrapper
setattr(self._testcase, self.name, self.obj)
try:
self.ihook.pytest_pyfunc_call(pyfuncitem=self)
except unittest.SkipTest:
raise
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)
self._testcase(result=self)
finally:
delattr(self._testcase, self.name)
def _prunetraceback(self, excinfo):
Function._prunetraceback(self, excinfo)
@ -304,3 +301,8 @@ def check_testcase_implements_trial_reporter(done=[]):
classImplements(TestCaseFunction, IReporter)
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))

View File

@ -895,21 +895,20 @@ class TestInvocationVariants:
class TestDurations:
source = """
import time
frag = 0.002
from _pytest import timing
def test_something():
pass
def test_2():
time.sleep(frag*5)
timing.sleep(0.010)
def test_1():
time.sleep(frag)
timing.sleep(0.002)
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)
result = testdir.runpytest("--durations=10")
result = testdir.runpytest_inprocess("--durations=10")
assert result.ret == 0
result.stdout.fnmatch_lines_random(
["*durations*", "*call*test_3*", "*call*test_2*"]
@ -918,16 +917,17 @@ class TestDurations:
["(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)
result = testdir.runpytest("--durations=2")
result = testdir.runpytest_inprocess("--durations=2")
assert result.ret == 0
lines = result.stdout.get_lines_after("*slowest*durations*")
assert "4 passed" in lines[2]
def test_calls_showall(self, testdir):
def test_calls_showall(self, testdir, mock_timing):
testdir.makepyfile(self.source)
result = testdir.runpytest("--durations=0")
result = testdir.runpytest_inprocess("--durations=0")
assert result.ret == 0
for x in "23":
for y in ("call",): # 'setup', 'call', 'teardown':
@ -937,9 +937,9 @@ class TestDurations:
else:
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)
result = testdir.runpytest("--durations=0", "-vv")
result = testdir.runpytest_inprocess("--durations=0", "-vv")
assert result.ret == 0
for x in "123":
for y in ("call",): # 'setup', 'call', 'teardown':
@ -949,13 +949,13 @@ class TestDurations:
else:
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)
result = testdir.runpytest("--durations=2", "-k test_2")
result = testdir.runpytest_inprocess("--durations=2", "-k test_2")
assert result.ret == 0
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(test_collecterror="""xyz""")
result = testdir.runpytest("--durations=2", "-k test_1")
@ -965,36 +965,35 @@ class TestDurations:
# output
result.stdout.no_fnmatch_line("*duration*")
def test_with_not(self, testdir):
def test_with_not(self, testdir, mock_timing):
testdir.makepyfile(self.source)
result = testdir.runpytest("-k not 1")
result = testdir.runpytest_inprocess("-k not 1")
assert result.ret == 0
class TestDurationWithFixture:
source = """
import pytest
import time
frag = 0.01
from _pytest import timing
@pytest.fixture
def setup_fixt():
time.sleep(frag)
timing.sleep(2)
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)
result = testdir.runpytest("--durations=10")
result = testdir.runpytest_inprocess("--durations=10")
assert result.ret == 0
result.stdout.fnmatch_lines_random(
"""
*durations*
* setup *test_1*
* call *test_1*
5.00s call *test_1*
2.00s setup *test_1*
"""
)

View File

@ -6,6 +6,7 @@ import pytest
from _pytest._code import Code
from _pytest._code import ExceptionInfo
from _pytest._code import Frame
from _pytest._code.code import ExceptionChainRepr
from _pytest._code.code import ReprFuncArgs
@ -180,3 +181,20 @@ class TestReprFuncArgs:
tw_mock.lines[0]
== 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()

View File

@ -122,7 +122,8 @@ def test_syntaxerror_rerepresentation() -> None:
assert ex is not None
assert ex.value.lineno == 1
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:
@ -521,7 +522,7 @@ def test_getfslineno() -> None:
class B:
pass
B.__name__ = "B2"
B.__name__ = B.__qualname__ = "B2"
assert getfslineno(B)[1] == -1
co = compile("...", "", "eval")

View File

@ -195,3 +195,41 @@ def color_mapping():
pytest.skip("doing limited testing because lacking ordered markup")
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

View File

@ -36,8 +36,15 @@ def test_terminal_reporter_writer_attr(pytestconfig):
except ImportError:
pass
terminal_reporter = pytestconfig.pluginmanager.get_plugin("terminalreporter")
expected_tw = terminal_reporter._tw
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))

View File

@ -0,0 +1,2 @@
[pytest]
# dummy pytest.ini to ease direct running of example scripts

View File

@ -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

View File

@ -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

View File

@ -154,3 +154,20 @@ def test_pformat_dispatch():
assert _pformat_dispatch("a") == "'a'"
assert _pformat_dispatch("a" * 10, width=5) == "'aaaaaaaaaa'"
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"
)

View File

@ -463,7 +463,7 @@ class TestFunction:
return '3'
@pytest.mark.parametrize('fix2', ['2'])
def test_it(fix1, fix2):
def test_it(fix1):
assert fix1 == '21'
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*"])
def test_collecterror_with_fulltrace(testdir):
def test_collect_error_with_fulltrace(testdir):
testdir.makepyfile("assert 0")
result = testdir.runpytest("--fulltrace")
result.stdout.fnmatch_lines(
@ -1233,15 +1233,12 @@ def test_collecterror_with_fulltrace(testdir):
"collected 0 items / 1 error",
"",
"*= ERRORS =*",
"*_ ERROR collecting test_collecterror_with_fulltrace.py _*",
"",
"*/_pytest/python.py:*: ",
"_ _ _ _ _ _ _ _ *",
"*_ ERROR collecting test_collect_error_with_fulltrace.py _*",
"",
"> 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 !*",
]
)

View File

@ -36,9 +36,6 @@ class TestMetafunc:
class DefinitionMock(python.FunctionDefinition):
obj = attr.ib()
def listchain(self):
return []
names = fixtures.getfuncargnames(func)
fixtureinfo = FuncFixtureInfoMock(names) # type: Any
definition = DefinitionMock._create(func) # type: Any
@ -1902,51 +1899,3 @@ class TestMarkersWithParametrization:
"*= 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*',
]
)

View File

@ -542,18 +542,40 @@ class TestCaptureFixture:
reprec.assertoutcome(passed=1)
def test_capsysbinary(self, testdir):
reprec = testdir.inline_runsource(
"""\
p1 = testdir.makepyfile(
r"""
def test_hello(capsysbinary):
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()
assert out == b'\\xfe\\x98\\x20'
assert out == b'hello\xfe\x98\x20world\n'
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):
p = testdir.makepyfile(
@ -977,7 +999,7 @@ class TestFDCapture:
cap.start()
tmpfile.write(data1)
tmpfile.flush()
cap.writeorg(data2)
cap.writeorg(data2.decode("ascii"))
scap = cap.snap()
cap.done()
assert scap == data1.decode("ascii")

View File

@ -1332,3 +1332,24 @@ def test_does_not_put_src_on_path(testdir):
)
result = testdir.runpytest()
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

View File

@ -342,6 +342,15 @@ class TestPDB:
child.sendeof()
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):
p1 = testdir.makepyfile(
"""

View File

@ -5,6 +5,7 @@ import pytest
from _pytest.compat import MODULE_NOT_FOUND_ERROR
from _pytest.doctest import _get_checker
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 DoctestItem
from _pytest.doctest import DoctestModule
@ -1487,3 +1488,27 @@ def test_warning_on_unwrap_of_broken_object(stop):
with pytest.raises(KeyError):
inspect.unwrap(bad_instance, stop=stop)
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)

View File

@ -200,23 +200,23 @@ class TestPython:
timestamp = datetime.strptime(node["timestamp"], "%Y-%m-%dT%H:%M:%S.%f")
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(
"""
import time, pytest
from _pytest import timing
def setup_module():
time.sleep(0.01)
timing.sleep(1)
def teardown_module():
time.sleep(0.01)
timing.sleep(2)
def test_sleep():
time.sleep(0.01)
timing.sleep(4)
"""
)
result, dom = run_and_parse()
node = dom.find_first_by_tag("testsuite")
tnode = node.find_first_by_tag("testcase")
val = tnode["time"]
assert round(float(val), 2) >= 0.03
assert float(val) == 7.0
@pytest.mark.parametrize("duration_report", ["call", "total"])
def test_junit_duration_report(

View File

@ -58,3 +58,30 @@ def test__check_initialpaths_for_relpath():
outside = py.path.local("/outside")
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 *"])

View File

@ -1,10 +1,13 @@
import os.path
import sys
import unittest.mock
import py
import pytest
from _pytest.pathlib import ensure_deletable
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 maybe_delete_a_numbered_dir
from _pytest.pathlib import Path
@ -89,3 +92,45 @@ def test_access_denied_during_cleanup(tmp_path, monkeypatch):
lock_path = get_lock_path(path)
maybe_delete_a_numbered_dir(path)
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()

View File

@ -395,6 +395,14 @@ class TestReportSerialization:
# for same reasons as previous test, ensure we don't blow up here
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:
"""Test that the hooks are working correctly for plugins"""

View File

@ -193,6 +193,42 @@ def test_no_resultlog_on_slaves(testdir):
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):
testdir.makeconftest(
"""

View File

@ -537,28 +537,24 @@ class TestTrialUnittest:
)
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_one FAILED",
"test_trial_error.py::TC::test_three FAILED",
"test_trial_error.py::TC::test_two SKIPPED",
"test_trial_error.py::TC::test_two ERROR",
"test_trial_error.py::TC::test_two FAILED",
"*ERRORS*",
"*_ 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*",
"*= FAILURES =*",
# "*_ TC.test_four _*",
# "*NameError*crash*",
"*_ TC.test_four _*",
"*NameError*crash*",
"*_ TC.test_one _*",
"*NameError*crash*",
"*_ TC.test_three _*",
"NOTE: Incompatible Exception Representation, displaying natively:",
"*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)
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):
testdir.makeconftest(
"""
@ -1129,3 +1156,89 @@ def test_trace(testdir, monkeypatch):
result = testdir.runpytest("--trace", str(p1))
assert len(calls) == 2
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)

View File

@ -9,9 +9,9 @@ envlist =
py36
py37
py38
pypy
py39
pypy3
py37-{pexpect,xdist,twisted,numpy,pluggymaster}
py37-{pexpect,xdist,unittestextras,numpy,pluggymaster}
doctesting
py37-freeze
docs
@ -50,7 +50,8 @@ deps =
pexpect: pexpect
pluggymaster: git+https://github.com/pytest-dev/pluggy.git@master
pygments
twisted: twisted
unittestextras: twisted
unittestextras: asynctest
xdist: pytest-xdist>=1.13
{env:_PYTEST_TOX_EXTRA_DEP:}