Compare commits

...

44 Commits
7.2.2 ... 5.4.3

Author SHA1 Message Date
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
46 changed files with 755 additions and 223 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

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

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

@@ -100,10 +100,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 +251,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)

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

@@ -29,6 +29,7 @@ from _pytest import nodes
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)

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

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

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

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

@@ -5,6 +5,7 @@ import py
import pytest
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 +90,26 @@ 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"

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:}