Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f838c7b7eb | ||
|
|
25b53c4196 | ||
|
|
fc27171d57 | ||
|
|
d18e426b24 | ||
|
|
e83fa48dd1 | ||
|
|
c53d52c3f2 | ||
|
|
3886c6d735 | ||
|
|
80936b6762 | ||
|
|
5ca08e9b8a | ||
|
|
ba2c49e71e | ||
|
|
da615a4fbe | ||
|
|
32562881d4 | ||
|
|
2a0bbfe63f | ||
|
|
9aed656ec7 | ||
|
|
a600e7a2a4 | ||
|
|
57a95b3a2c | ||
|
|
f5e430fd8f | ||
|
|
38a4c7e56c | ||
|
|
40f02d72b0 | ||
|
|
554f600fb4 | ||
|
|
e3a3c90d94 | ||
|
|
f7327759e8 | ||
|
|
ee1950af77 | ||
|
|
3d0f3baa2b | ||
|
|
8da758b93a | ||
|
|
59e5d1bfbf | ||
|
|
b9e2cd0a81 | ||
|
|
a84fcbf5b2 | ||
|
|
59c1bfada7 | ||
|
|
3267f64724 |
17
.github/workflows/main.yml
vendored
17
.github/workflows/main.yml
vendored
@@ -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
|
||||
|
||||
@@ -6,6 +6,8 @@ Release announcements
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
release-5.4.2
|
||||
release-5.4.1
|
||||
release-5.4.0
|
||||
release-5.3.5
|
||||
release-5.3.4
|
||||
|
||||
18
doc/en/announce/release-5.4.1.rst
Normal file
18
doc/en/announce/release-5.4.1.rst
Normal 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
|
||||
22
doc/en/announce/release-5.4.2.rst
Normal file
22
doc/en/announce/release-5.4.2.rst
Normal 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
|
||||
@@ -28,6 +28,56 @@ with advance notice in the **Deprecations** section of releases.
|
||||
|
||||
.. towncrier release notes start
|
||||
|
||||
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 +113,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).
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
--------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -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 =======================
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]])
|
||||
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -272,11 +272,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 +295,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
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -474,11 +474,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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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"):
|
||||
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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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))
|
||||
|
||||
2
testing/example_scripts/pytest.ini
Normal file
2
testing/example_scripts/pytest.ini
Normal file
@@ -0,0 +1,2 @@
|
||||
[pytest]
|
||||
# dummy pytest.ini to ease direct running of example scripts
|
||||
24
testing/example_scripts/unittest/test_unittest_asyncio.py
Normal file
24
testing/example_scripts/unittest/test_unittest_asyncio.py
Normal 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
|
||||
22
testing/example_scripts/unittest/test_unittest_asynctest.py
Normal file
22
testing/example_scripts/unittest/test_unittest_asynctest.py
Normal 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
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
@@ -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*',
|
||||
]
|
||||
)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
"""
|
||||
|
||||
@@ -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,55 @@ 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",
|
||||
]
|
||||
|
||||
|
||||
def test_async_support(testdir):
|
||||
pytest.importorskip("unittest.async_case")
|
||||
|
||||
testdir.copy_example("unittest/test_unittest_asyncio.py")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(failed=1, passed=2)
|
||||
|
||||
|
||||
def test_asynctest_support(testdir):
|
||||
"""Check asynctest support (#7110)"""
|
||||
pytest.importorskip("asynctest")
|
||||
|
||||
testdir.copy_example("unittest/test_unittest_asynctest.py")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(failed=1, passed=2)
|
||||
|
||||
7
tox.ini
7
tox.ini
@@ -9,9 +9,9 @@ envlist =
|
||||
py36
|
||||
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:}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user