commit
d1c9c54571
3
AUTHORS
3
AUTHORS
|
@ -59,6 +59,7 @@ Danielle Jenkins
|
|||
Dave Hunt
|
||||
David Díaz-Barquero
|
||||
David Mohr
|
||||
David Szotten
|
||||
David Vierra
|
||||
Daw-Ran Liou
|
||||
Denis Kirisov
|
||||
|
@ -161,6 +162,7 @@ Miro Hrončok
|
|||
Nathaniel Waisbrot
|
||||
Ned Batchelder
|
||||
Neven Mundar
|
||||
Niclas Olofsson
|
||||
Nicolas Delaby
|
||||
Oleg Pidsadnyi
|
||||
Oleg Sushchenko
|
||||
|
@ -202,6 +204,7 @@ Stefan Zimmermann
|
|||
Stefano Taschini
|
||||
Steffen Allner
|
||||
Stephan Obermann
|
||||
Sven-Hendrik Haase
|
||||
Tadek Teleżyński
|
||||
Tarcisio Fischer
|
||||
Tareq Alayan
|
||||
|
|
|
@ -18,6 +18,72 @@ with advance notice in the **Deprecations** section of releases.
|
|||
|
||||
.. towncrier release notes start
|
||||
|
||||
pytest 3.10.0 (2018-11-03)
|
||||
==========================
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- `#2619 <https://github.com/pytest-dev/pytest/issues/2619>`_: Resume capturing output after ``continue`` with ``__import__("pdb").set_trace()``.
|
||||
|
||||
This also adds a new ``pytest_leave_pdb`` hook, and passes in ``pdb`` to the
|
||||
existing ``pytest_enter_pdb`` hook.
|
||||
|
||||
|
||||
- `#4147 <https://github.com/pytest-dev/pytest/issues/4147>`_: Add ``-sw``, ``--stepwise`` as an alternative to ``--lf -x`` for stopping at the first failure, but starting the next test invocation from that test. See `the documentation <https://docs.pytest.org/en/latest/cache.html#stepwise>`__ for more info.
|
||||
|
||||
|
||||
- `#4188 <https://github.com/pytest-dev/pytest/issues/4188>`_: Make ``--color`` emit colorful dots when not running in verbose mode. Earlier, it would only colorize the test-by-test output if ``--verbose`` was also passed.
|
||||
|
||||
|
||||
- `#4225 <https://github.com/pytest-dev/pytest/issues/4225>`_: Improve performance with collection reporting in non-quiet mode with terminals.
|
||||
|
||||
The "collecting …" message is only printed/updated every 0.5s.
|
||||
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#2701 <https://github.com/pytest-dev/pytest/issues/2701>`_: Fix false ``RemovedInPytest4Warning: usage of Session... is deprecated, please use pytest`` warnings.
|
||||
|
||||
|
||||
- `#4046 <https://github.com/pytest-dev/pytest/issues/4046>`_: Fix problems with running tests in package ``__init__.py`` files.
|
||||
|
||||
|
||||
- `#4260 <https://github.com/pytest-dev/pytest/issues/4260>`_: Swallow warnings during anonymous compilation of source.
|
||||
|
||||
|
||||
- `#4262 <https://github.com/pytest-dev/pytest/issues/4262>`_: Fix access denied error when deleting stale directories created by ``tmpdir`` / ``tmp_path``.
|
||||
|
||||
|
||||
- `#611 <https://github.com/pytest-dev/pytest/issues/611>`_: Naming a fixture ``request`` will now raise a warning: the ``request`` fixture is internal and
|
||||
should not be overwritten as it will lead to internal errors.
|
||||
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- `#4255 <https://github.com/pytest-dev/pytest/issues/4255>`_: Added missing documentation about the fact that module names passed to filter warnings are not regex-escaped.
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#4272 <https://github.com/pytest-dev/pytest/issues/4272>`_: Display cachedir also in non-verbose mode if non-default.
|
||||
|
||||
|
||||
- `#4277 <https://github.com/pytest-dev/pytest/issues/4277>`_: pdb: improve message about output capturing with ``set_trace``.
|
||||
|
||||
Do not display "IO-capturing turned off/on" when ``-s`` is used to avoid
|
||||
confusion.
|
||||
|
||||
|
||||
- `#4279 <https://github.com/pytest-dev/pytest/issues/4279>`_: Improve message and stack level of warnings issued by ``monkeypatch.setenv`` when the value of the environment variable is not a ``str``.
|
||||
|
||||
|
||||
pytest 3.9.3 (2018-10-27)
|
||||
=========================
|
||||
|
||||
|
@ -366,7 +432,7 @@ Features
|
|||
the standard warnings filters to manage those warnings. This introduces ``PytestWarning``,
|
||||
``PytestDeprecationWarning`` and ``RemovedInPytest4Warning`` warning types as part of the public API.
|
||||
|
||||
Consult `the documentation <https://docs.pytest.org/en/latest/warnings.html#internal-pytest-warnings>`_ for more info.
|
||||
Consult `the documentation <https://docs.pytest.org/en/latest/warnings.html#internal-pytest-warnings>`__ for more info.
|
||||
|
||||
|
||||
- `#2908 <https://github.com/pytest-dev/pytest/issues/2908>`_: ``DeprecationWarning`` and ``PendingDeprecationWarning`` are now shown by default if no other warning filter is
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Fix false ``RemovedInPytest4Warning: usage of Session... is deprecated, please use pytest`` warnings.
|
|
@ -1 +0,0 @@
|
|||
Fix problems with running tests in package ``__init__.py`` files.
|
|
@ -1 +0,0 @@
|
|||
Added missing documentation about the fact that module names passed to filter warnings are not regex-escaped.
|
|
@ -1 +0,0 @@
|
|||
Swallow warnings during anonymous compilation of source.
|
|
@ -1 +0,0 @@
|
|||
Fix access denied error when deleting stale directories created by ``tmpdir`` / ``tmp_path``.
|
|
@ -1 +0,0 @@
|
|||
Improve message and stack level of warnings issued by ``monkeypatch.setenv`` when the value of the environment variable is not a ``str``.
|
|
@ -6,6 +6,7 @@ Release announcements
|
|||
:maxdepth: 2
|
||||
|
||||
|
||||
release-3.10.0
|
||||
release-3.9.3
|
||||
release-3.9.2
|
||||
release-3.9.1
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
pytest-3.10.0
|
||||
=======================================
|
||||
|
||||
The pytest team is proud to announce the 3.10.0 release!
|
||||
|
||||
pytest is a mature Python testing tool with more than a 2000 tests
|
||||
against itself, passing on many different interpreters and platforms.
|
||||
|
||||
This release contains a number of bugs fixes and improvements, so users are encouraged
|
||||
to take a look at the CHANGELOG:
|
||||
|
||||
https://docs.pytest.org/en/latest/changelog.html
|
||||
|
||||
For complete documentation, please visit:
|
||||
|
||||
https://docs.pytest.org/en/latest/
|
||||
|
||||
As usual, you can upgrade from pypi via:
|
||||
|
||||
pip install -U pytest
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
* Anders Hovmöller
|
||||
* Andreu Vallbona Plazas
|
||||
* Ankit Goel
|
||||
* Anthony Sottile
|
||||
* Bernardo Gomes
|
||||
* Brianna Laugher
|
||||
* Bruno Oliveira
|
||||
* Daniel Hahler
|
||||
* David Szotten
|
||||
* Mick Koch
|
||||
* Niclas Olofsson
|
||||
* Palash Chatterjee
|
||||
* Ronny Pfannschmidt
|
||||
* Sven-Hendrik Haase
|
||||
* Ville Skyttä
|
||||
* William Jamir Silva
|
||||
|
||||
|
||||
Happy testing,
|
||||
The Pytest Development Team
|
|
@ -244,6 +244,8 @@ You can always peek at the content of the cache using the
|
|||
{'test_caching.py::test_function': True}
|
||||
cache/nodeids contains:
|
||||
['test_caching.py::test_function']
|
||||
cache/stepwise contains:
|
||||
[]
|
||||
example/value contains:
|
||||
42
|
||||
|
||||
|
@ -260,3 +262,9 @@ by adding the ``--cache-clear`` option like this::
|
|||
This is recommended for invocations from Continuous Integration
|
||||
servers where isolation and correctness is more important
|
||||
than speed.
|
||||
|
||||
|
||||
Stepwise
|
||||
--------
|
||||
|
||||
As an alternative to ``--lf -x``, especially for cases where you expect a large part of the test suite will fail, ``--sw``, ``--stepwise`` allows you to fix them one at a time. The test suite will run until the first failure and then stop. At the next invocation, tests will continue from the last failing test and then run until the next failing test. You may use the ``--stepwise-skip`` option to ignore one failing test and stop the test execution on the second failing test instead. This is useful if you get stuck on a failing test and just want to ignore it until later.
|
||||
|
|
|
@ -33,7 +33,7 @@ class Python(object):
|
|||
dumpfile = self.picklefile.dirpath("dump.py")
|
||||
dumpfile.write(
|
||||
textwrap.dedent(
|
||||
r"""\
|
||||
r"""
|
||||
import pickle
|
||||
f = open({!r}, 'wb')
|
||||
s = pickle.dump({!r}, f, protocol=2)
|
||||
|
@ -49,7 +49,7 @@ class Python(object):
|
|||
loadfile = self.picklefile.dirpath("load.py")
|
||||
loadfile.write(
|
||||
textwrap.dedent(
|
||||
r"""\
|
||||
r"""
|
||||
import pickle
|
||||
f = open({!r}, 'rb')
|
||||
obj = pickle.load(f)
|
||||
|
|
|
@ -85,8 +85,9 @@ interesting to just look at the collection tree::
|
|||
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
|
||||
collected 2 items
|
||||
<Package '$REGENDOC_TMPDIR/nonpython'>
|
||||
<YamlFile 'test_simple.yml'>
|
||||
<YamlItem 'hello'>
|
||||
<YamlItem 'ok'>
|
||||
<Package '$REGENDOC_TMPDIR/nonpython'>
|
||||
<YamlFile 'test_simple.yml'>
|
||||
<YamlItem 'hello'>
|
||||
<YamlItem 'ok'>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
|
|
@ -421,21 +421,9 @@ additionally it is possible to copy examples for an example folder before runnin
|
|||
test_example.py::test_plugin
|
||||
$REGENDOC_TMPDIR/test_example.py:4: PytestExperimentalApiWarning: testdir.copy_example is an experimental api that may change over time
|
||||
testdir.copy_example("test_example.py")
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:332: RemovedInPytest4Warning: usage of Session.Class is deprecated, please use pytest.Class instead
|
||||
return getattr(object, name, default)
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:332: RemovedInPytest4Warning: usage of Session.File is deprecated, please use pytest.File instead
|
||||
return getattr(object, name, default)
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:332: RemovedInPytest4Warning: usage of Session.Function is deprecated, please use pytest.Function instead
|
||||
return getattr(object, name, default)
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:332: RemovedInPytest4Warning: usage of Session.Instance is deprecated, please use pytest.Instance instead
|
||||
return getattr(object, name, default)
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:332: RemovedInPytest4Warning: usage of Session.Item is deprecated, please use pytest.Item instead
|
||||
return getattr(object, name, default)
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:332: RemovedInPytest4Warning: usage of Session.Module is deprecated, please use pytest.Module instead
|
||||
return getattr(object, name, default)
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
||||
=================== 2 passed, 7 warnings in 0.12 seconds ===================
|
||||
=================== 2 passed, 1 warnings in 0.12 seconds ===================
|
||||
|
||||
For more information about the result object that ``runpytest()`` returns, and
|
||||
the methods that it provides please check out the :py:class:`RunResult
|
||||
|
|
|
@ -319,7 +319,8 @@ def cache(request):
|
|||
|
||||
|
||||
def pytest_report_header(config):
|
||||
if config.option.verbose:
|
||||
"""Display cachedir with --cache-show and if non-default."""
|
||||
if config.option.verbose or config.getini("cache_dir") != ".pytest_cache":
|
||||
cachedir = config.cache._cachedir
|
||||
# TODO: evaluate generating upward relative paths
|
||||
# starting with .., ../.. if sensible
|
||||
|
|
|
@ -102,6 +102,9 @@ class CaptureManager(object):
|
|||
|
||||
# Global capturing control
|
||||
|
||||
def is_globally_capturing(self):
|
||||
return self._method != "no"
|
||||
|
||||
def start_global_capturing(self):
|
||||
assert self._global_capturing is None
|
||||
self._global_capturing = self._getcapture(self._method)
|
||||
|
|
|
@ -428,3 +428,16 @@ class FuncargnamesCompatAttr(object):
|
|||
def funcargnames(self):
|
||||
""" alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
|
||||
return self.fixturenames
|
||||
|
||||
|
||||
if six.PY2:
|
||||
|
||||
def lru_cache(*_, **__):
|
||||
def dec(fn):
|
||||
return fn
|
||||
|
||||
return dec
|
||||
|
||||
|
||||
else:
|
||||
from functools import lru_cache # noqa: F401
|
||||
|
|
|
@ -27,6 +27,7 @@ from .findpaths import determine_setup
|
|||
from .findpaths import exists
|
||||
from _pytest._code import ExceptionInfo
|
||||
from _pytest._code import filter_traceback
|
||||
from _pytest.compat import lru_cache
|
||||
from _pytest.compat import safe_str
|
||||
from _pytest.outcomes import Skipped
|
||||
|
||||
|
@ -133,6 +134,7 @@ default_plugins = (
|
|||
"freeze_support",
|
||||
"setuponly",
|
||||
"setupplan",
|
||||
"stepwise",
|
||||
"warnings",
|
||||
"logging",
|
||||
)
|
||||
|
@ -212,7 +214,7 @@ class PytestPluginManager(PluginManager):
|
|||
self._conftest_plugins = set()
|
||||
|
||||
# state related to local conftest plugins
|
||||
self._path2confmods = {}
|
||||
self._dirpath2confmods = {}
|
||||
self._conftestpath2mod = {}
|
||||
self._confcutdir = None
|
||||
self._noconftest = False
|
||||
|
@ -383,31 +385,35 @@ class PytestPluginManager(PluginManager):
|
|||
if x.check(dir=1):
|
||||
self._getconftestmodules(x)
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
def _getconftestmodules(self, path):
|
||||
if self._noconftest:
|
||||
return []
|
||||
|
||||
try:
|
||||
return self._path2confmods[path]
|
||||
except KeyError:
|
||||
if path.isfile():
|
||||
directory = path.dirpath()
|
||||
else:
|
||||
directory = path
|
||||
# XXX these days we may rather want to use config.rootdir
|
||||
# and allow users to opt into looking into the rootdir parent
|
||||
# directories instead of requiring to specify confcutdir
|
||||
clist = []
|
||||
for parent in directory.realpath().parts():
|
||||
if self._confcutdir and self._confcutdir.relto(parent):
|
||||
continue
|
||||
conftestpath = parent.join("conftest.py")
|
||||
if conftestpath.isfile():
|
||||
mod = self._importconftest(conftestpath)
|
||||
clist.append(mod)
|
||||
if path.isfile():
|
||||
directory = path.dirpath()
|
||||
else:
|
||||
directory = path
|
||||
|
||||
self._path2confmods[path] = clist
|
||||
return clist
|
||||
if six.PY2: # py2 is not using lru_cache.
|
||||
try:
|
||||
return self._dirpath2confmods[directory]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# XXX these days we may rather want to use config.rootdir
|
||||
# and allow users to opt into looking into the rootdir parent
|
||||
# directories instead of requiring to specify confcutdir
|
||||
clist = []
|
||||
for parent in directory.realpath().parts():
|
||||
if self._confcutdir and self._confcutdir.relto(parent):
|
||||
continue
|
||||
conftestpath = parent.join("conftest.py")
|
||||
if conftestpath.isfile():
|
||||
mod = self._importconftest(conftestpath)
|
||||
clist.append(mod)
|
||||
self._dirpath2confmods[directory] = clist
|
||||
return clist
|
||||
|
||||
def _rget_with_confmod(self, name, path):
|
||||
modules = self._getconftestmodules(path)
|
||||
|
@ -448,8 +454,8 @@ class PytestPluginManager(PluginManager):
|
|||
self._conftest_plugins.add(mod)
|
||||
self._conftestpath2mod[conftestpath] = mod
|
||||
dirpath = conftestpath.dirpath()
|
||||
if dirpath in self._path2confmods:
|
||||
for path, mods in self._path2confmods.items():
|
||||
if dirpath in self._dirpath2confmods:
|
||||
for path, mods in self._dirpath2confmods.items():
|
||||
if path and path.relto(dirpath) or path == dirpath:
|
||||
assert mod not in mods
|
||||
mods.append(mod)
|
||||
|
|
|
@ -88,10 +88,54 @@ class pytestPDB(object):
|
|||
capman.suspend_global_capture(in_=True)
|
||||
tw = _pytest.config.create_terminal_writer(cls._config)
|
||||
tw.line()
|
||||
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
|
||||
cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config)
|
||||
if capman and capman.is_globally_capturing():
|
||||
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
|
||||
else:
|
||||
tw.sep(">", "PDB set_trace")
|
||||
|
||||
class _PdbWrapper(cls._pdb_cls, object):
|
||||
_pytest_capman = capman
|
||||
_continued = False
|
||||
|
||||
def do_continue(self, arg):
|
||||
ret = super(_PdbWrapper, self).do_continue(arg)
|
||||
if self._pytest_capman:
|
||||
tw = _pytest.config.create_terminal_writer(cls._config)
|
||||
tw.line()
|
||||
if self._pytest_capman.is_globally_capturing():
|
||||
tw.sep(">", "PDB continue (IO-capturing resumed)")
|
||||
else:
|
||||
tw.sep(">", "PDB continue")
|
||||
self._pytest_capman.resume_global_capture()
|
||||
cls._pluginmanager.hook.pytest_leave_pdb(
|
||||
config=cls._config, pdb=self
|
||||
)
|
||||
self._continued = True
|
||||
return ret
|
||||
|
||||
do_c = do_cont = do_continue
|
||||
|
||||
def setup(self, f, tb):
|
||||
"""Suspend on setup().
|
||||
|
||||
Needed after do_continue resumed, and entering another
|
||||
breakpoint again.
|
||||
"""
|
||||
ret = super(_PdbWrapper, self).setup(f, tb)
|
||||
if not ret and self._continued:
|
||||
# pdb.setup() returns True if the command wants to exit
|
||||
# from the interaction: do not suspend capturing then.
|
||||
if self._pytest_capman:
|
||||
self._pytest_capman.suspend_global_capture(in_=True)
|
||||
return ret
|
||||
|
||||
_pdb = _PdbWrapper()
|
||||
cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config, pdb=_pdb)
|
||||
else:
|
||||
_pdb = cls._pdb_cls()
|
||||
|
||||
if set_break:
|
||||
cls._pdb_cls().set_trace(frame)
|
||||
_pdb.set_trace(frame)
|
||||
|
||||
|
||||
class PdbInvoke(object):
|
||||
|
|
|
@ -12,6 +12,7 @@ from __future__ import absolute_import
|
|||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
from _pytest.warning_types import PytestDeprecationWarning
|
||||
from _pytest.warning_types import RemovedInPytest4Warning
|
||||
from _pytest.warning_types import UnformattedWarning
|
||||
|
||||
|
@ -57,6 +58,10 @@ FIXTURE_FUNCTION_CALL = UnformattedWarning(
|
|||
"See https://docs.pytest.org/en/latest/fixture.html for more information.",
|
||||
)
|
||||
|
||||
FIXTURE_NAMED_REQUEST = PytestDeprecationWarning(
|
||||
"'request' is a reserved name for fixtures and will raise an error in future versions"
|
||||
)
|
||||
|
||||
CFG_PYTEST_SECTION = UnformattedWarning(
|
||||
RemovedInPytest4Warning,
|
||||
"[pytest] section in {filename} files is deprecated, use [tool:pytest] instead.",
|
||||
|
|
|
@ -34,6 +34,7 @@ from _pytest.compat import isclass
|
|||
from _pytest.compat import NOTSET
|
||||
from _pytest.compat import safe_getattr
|
||||
from _pytest.deprecated import FIXTURE_FUNCTION_CALL
|
||||
from _pytest.deprecated import FIXTURE_NAMED_REQUEST
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.outcomes import TEST_OUTCOME
|
||||
|
||||
|
@ -1036,6 +1037,9 @@ class FixtureFunctionMarker(object):
|
|||
|
||||
function = wrap_function_to_warning_if_called_directly(function, self)
|
||||
|
||||
name = self.name or function.__name__
|
||||
if name == "request":
|
||||
warnings.warn(FIXTURE_NAMED_REQUEST)
|
||||
function._pytestfixturefunction = self
|
||||
return function
|
||||
|
||||
|
|
|
@ -603,9 +603,21 @@ def pytest_exception_interact(node, call, report):
|
|||
"""
|
||||
|
||||
|
||||
def pytest_enter_pdb(config):
|
||||
def pytest_enter_pdb(config, pdb):
|
||||
""" called upon pdb.set_trace(), can be used by plugins to take special
|
||||
action just before the python debugger enters in interactive mode.
|
||||
|
||||
:param _pytest.config.Config config: pytest config object
|
||||
:param pdb.Pdb pdb: Pdb instance
|
||||
"""
|
||||
|
||||
|
||||
def pytest_leave_pdb(config, pdb):
|
||||
""" called when leaving pdb (e.g. with continue after pdb.set_trace()).
|
||||
|
||||
Can be used by plugins to take special action just after the python
|
||||
debugger leaves interactive mode.
|
||||
|
||||
:param _pytest.config.Config config: pytest config object
|
||||
:param pdb.Pdb pdb: Pdb instance
|
||||
"""
|
||||
|
|
|
@ -18,6 +18,7 @@ from _pytest.config import directory_arg
|
|||
from _pytest.config import hookimpl
|
||||
from _pytest.config import UsageError
|
||||
from _pytest.outcomes import exit
|
||||
from _pytest.pathlib import parts
|
||||
from _pytest.runner import collect_one_node
|
||||
|
||||
|
||||
|
@ -469,8 +470,8 @@ class Session(nodes.FSCollector):
|
|||
return items
|
||||
|
||||
def collect(self):
|
||||
for parts in self._initialparts:
|
||||
arg = "::".join(map(str, parts))
|
||||
for initialpart in self._initialparts:
|
||||
arg = "::".join(map(str, initialpart))
|
||||
self.trace("processing argument", arg)
|
||||
self.trace.root.indent += 1
|
||||
try:
|
||||
|
@ -488,7 +489,7 @@ class Session(nodes.FSCollector):
|
|||
|
||||
names = self._parsearg(arg)
|
||||
argpath = names.pop(0).realpath()
|
||||
paths = []
|
||||
paths = set()
|
||||
|
||||
root = self
|
||||
# Start with a Session root, and delve to argpath item (dir or file)
|
||||
|
@ -517,21 +518,37 @@ class Session(nodes.FSCollector):
|
|||
# Let the Package collector deal with subnodes, don't collect here.
|
||||
if argpath.check(dir=1):
|
||||
assert not names, "invalid arg %r" % (arg,)
|
||||
for path in argpath.visit(
|
||||
fil=lambda x: x.check(file=1), rec=self._recurse, bf=True, sort=True
|
||||
):
|
||||
pkginit = path.dirpath().join("__init__.py")
|
||||
if pkginit.exists() and not any(x in pkginit.parts() for x in paths):
|
||||
for x in root._collectfile(pkginit):
|
||||
yield x
|
||||
paths.append(x.fspath.dirpath())
|
||||
|
||||
if not any(x in path.parts() for x in paths):
|
||||
if six.PY2:
|
||||
|
||||
def filter_(f):
|
||||
return f.check(file=1) and not f.strpath.endswith("*.pyc")
|
||||
|
||||
else:
|
||||
|
||||
def filter_(f):
|
||||
return f.check(file=1)
|
||||
|
||||
seen_dirs = set()
|
||||
for path in argpath.visit(
|
||||
fil=filter_, rec=self._recurse, bf=True, sort=True
|
||||
):
|
||||
dirpath = path.dirpath()
|
||||
if dirpath not in seen_dirs:
|
||||
seen_dirs.add(dirpath)
|
||||
pkginit = dirpath.join("__init__.py")
|
||||
if pkginit.exists() and parts(pkginit.strpath).isdisjoint(paths):
|
||||
for x in root._collectfile(pkginit):
|
||||
yield x
|
||||
paths.add(x.fspath.dirpath())
|
||||
|
||||
if parts(path.strpath).isdisjoint(paths):
|
||||
for x in root._collectfile(path):
|
||||
if (type(x), x.fspath) in self._node_cache:
|
||||
yield self._node_cache[(type(x), x.fspath)]
|
||||
key = (type(x), x.fspath)
|
||||
if key in self._node_cache:
|
||||
yield self._node_cache[key]
|
||||
else:
|
||||
self._node_cache[(type(x), x.fspath)] = x
|
||||
self._node_cache[key] = x
|
||||
yield x
|
||||
else:
|
||||
assert argpath.check(file=1)
|
||||
|
@ -570,15 +587,17 @@ class Session(nodes.FSCollector):
|
|||
|
||||
return ihook.pytest_collect_file(path=path, parent=self)
|
||||
|
||||
def _recurse(self, path):
|
||||
ihook = self.gethookproxy(path.dirpath())
|
||||
if ihook.pytest_ignore_collect(path=path, config=self.config):
|
||||
return
|
||||
def _recurse(self, dirpath):
|
||||
if dirpath.basename == "__pycache__":
|
||||
return False
|
||||
ihook = self.gethookproxy(dirpath.dirpath())
|
||||
if ihook.pytest_ignore_collect(path=dirpath, config=self.config):
|
||||
return False
|
||||
for pat in self._norecursepatterns:
|
||||
if path.check(fnmatch=pat):
|
||||
if dirpath.check(fnmatch=pat):
|
||||
return False
|
||||
ihook = self.gethookproxy(path)
|
||||
ihook.pytest_collect_directory(path=path, parent=self)
|
||||
ihook = self.gethookproxy(dirpath)
|
||||
ihook.pytest_collect_directory(path=dirpath, parent=self)
|
||||
return True
|
||||
|
||||
def _tryconvertpyarg(self, x):
|
||||
|
|
|
@ -5,7 +5,6 @@ import itertools
|
|||
import operator
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import sys
|
||||
import uuid
|
||||
from functools import reduce
|
||||
|
@ -43,17 +42,10 @@ def ensure_reset_dir(path):
|
|||
path.mkdir()
|
||||
|
||||
|
||||
def _shutil_rmtree_remove_writable(func, fspath, _):
|
||||
"Clear the readonly bit and reattempt the removal"
|
||||
os.chmod(fspath, stat.S_IWRITE)
|
||||
func(fspath)
|
||||
|
||||
|
||||
def rmtree(path, force=False):
|
||||
if force:
|
||||
# ignore_errors leaves dead folders around
|
||||
# python needs a rm -rf as a followup
|
||||
# the trick with _shutil_rmtree_remove_writable is unreliable
|
||||
# NOTE: ignore_errors might leave dead folders around.
|
||||
# Python needs a rm -rf as a followup.
|
||||
shutil.rmtree(str(path), ignore_errors=True)
|
||||
else:
|
||||
shutil.rmtree(str(path))
|
||||
|
@ -321,3 +313,8 @@ def fnmatch_ex(pattern, path):
|
|||
else:
|
||||
name = six.text_type(path)
|
||||
return fnmatch.fnmatch(name, pattern)
|
||||
|
||||
|
||||
def parts(s):
|
||||
parts = s.split(sep)
|
||||
return {sep.join(parts[: i + 1]) or sep for i in range(len(parts))}
|
||||
|
|
|
@ -42,6 +42,7 @@ from _pytest.mark.structures import get_unpacked_marks
|
|||
from _pytest.mark.structures import normalize_mark_list
|
||||
from _pytest.mark.structures import transfer_markers
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.pathlib import parts
|
||||
from _pytest.warning_types import PytestWarning
|
||||
from _pytest.warning_types import RemovedInPytest4Warning
|
||||
|
||||
|
@ -517,15 +518,17 @@ class Package(Module):
|
|||
self._norecursepatterns = session._norecursepatterns
|
||||
self.fspath = fspath
|
||||
|
||||
def _recurse(self, path):
|
||||
ihook = self.gethookproxy(path.dirpath())
|
||||
if ihook.pytest_ignore_collect(path=path, config=self.config):
|
||||
def _recurse(self, dirpath):
|
||||
if dirpath.basename == "__pycache__":
|
||||
return False
|
||||
ihook = self.gethookproxy(dirpath.dirpath())
|
||||
if ihook.pytest_ignore_collect(path=dirpath, config=self.config):
|
||||
return
|
||||
for pat in self._norecursepatterns:
|
||||
if path.check(fnmatch=pat):
|
||||
if dirpath.check(fnmatch=pat):
|
||||
return False
|
||||
ihook = self.gethookproxy(path)
|
||||
ihook.pytest_collect_directory(path=path, parent=self)
|
||||
ihook = self.gethookproxy(dirpath)
|
||||
ihook.pytest_collect_directory(path=dirpath, parent=self)
|
||||
return True
|
||||
|
||||
def gethookproxy(self, fspath):
|
||||
|
@ -561,19 +564,16 @@ class Package(Module):
|
|||
yield Module(init_module, self)
|
||||
pkg_prefixes = set()
|
||||
for path in this_path.visit(rec=self._recurse, bf=True, sort=True):
|
||||
# we will visit our own __init__.py file, in which case we skip it
|
||||
skip = False
|
||||
if path.basename == "__init__.py" and path.dirpath() == this_path:
|
||||
continue
|
||||
# We will visit our own __init__.py file, in which case we skip it.
|
||||
if path.isfile():
|
||||
if path.basename == "__init__.py" and path.dirpath() == this_path:
|
||||
continue
|
||||
|
||||
for pkg_prefix in pkg_prefixes:
|
||||
if (
|
||||
pkg_prefix in path.parts()
|
||||
and pkg_prefix.join("__init__.py") != path
|
||||
):
|
||||
skip = True
|
||||
|
||||
if skip:
|
||||
parts_ = parts(path.strpath)
|
||||
if any(
|
||||
pkg_prefix in parts_ and pkg_prefix.join("__init__.py") != path
|
||||
for pkg_prefix in pkg_prefixes
|
||||
):
|
||||
continue
|
||||
|
||||
if path.isdir() and path.join("__init__.py").check(file=1):
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
import pytest
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("general")
|
||||
group.addoption(
|
||||
"--sw",
|
||||
"--stepwise",
|
||||
action="store_true",
|
||||
dest="stepwise",
|
||||
help="exit on test fail and continue from last failing test next time",
|
||||
)
|
||||
group.addoption(
|
||||
"--stepwise-skip",
|
||||
action="store_true",
|
||||
dest="stepwise_skip",
|
||||
help="ignore the first failing test but stop on the next failing test",
|
||||
)
|
||||
|
||||
|
||||
@pytest.hookimpl
|
||||
def pytest_configure(config):
|
||||
config.pluginmanager.register(StepwisePlugin(config), "stepwiseplugin")
|
||||
|
||||
|
||||
class StepwisePlugin:
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.active = config.getvalue("stepwise")
|
||||
self.session = None
|
||||
|
||||
if self.active:
|
||||
self.lastfailed = config.cache.get("cache/stepwise", None)
|
||||
self.skip = config.getvalue("stepwise_skip")
|
||||
|
||||
def pytest_sessionstart(self, session):
|
||||
self.session = session
|
||||
|
||||
def pytest_collection_modifyitems(self, session, config, items):
|
||||
if not self.active or not self.lastfailed:
|
||||
return
|
||||
|
||||
already_passed = []
|
||||
found = False
|
||||
|
||||
# Make a list of all tests that have been run before the last failing one.
|
||||
for item in items:
|
||||
if item.nodeid == self.lastfailed:
|
||||
found = True
|
||||
break
|
||||
else:
|
||||
already_passed.append(item)
|
||||
|
||||
# If the previously failed test was not found among the test items,
|
||||
# do not skip any tests.
|
||||
if not found:
|
||||
already_passed = []
|
||||
|
||||
for item in already_passed:
|
||||
items.remove(item)
|
||||
|
||||
config.hook.pytest_deselected(items=already_passed)
|
||||
|
||||
def pytest_collectreport(self, report):
|
||||
if self.active and report.failed:
|
||||
self.session.shouldstop = (
|
||||
"Error when collecting test, stopping test execution."
|
||||
)
|
||||
|
||||
def pytest_runtest_logreport(self, report):
|
||||
# Skip this hook if plugin is not active or the test is xfailed.
|
||||
if not self.active or "xfail" in report.keywords:
|
||||
return
|
||||
|
||||
if report.failed:
|
||||
if self.skip:
|
||||
# Remove test from the failed ones (if it exists) and unset the skip option
|
||||
# to make sure the following tests will not be skipped.
|
||||
if report.nodeid == self.lastfailed:
|
||||
self.lastfailed = None
|
||||
|
||||
self.skip = False
|
||||
else:
|
||||
# Mark test as the last failing and interrupt the test session.
|
||||
self.lastfailed = report.nodeid
|
||||
self.session.shouldstop = (
|
||||
"Test failed, continuing from this test next run."
|
||||
)
|
||||
|
||||
else:
|
||||
# If the test was actually run and did pass.
|
||||
if report.when == "call":
|
||||
# Remove test from the failed ones, if exists.
|
||||
if report.nodeid == self.lastfailed:
|
||||
self.lastfailed = None
|
||||
|
||||
def pytest_sessionfinish(self, session):
|
||||
if self.active:
|
||||
self.config.cache.set("cache/stepwise", self.lastfailed)
|
||||
else:
|
||||
# Clear the list of failing tests if the plugin is not active.
|
||||
self.config.cache.set("cache/stepwise", [])
|
|
@ -246,6 +246,7 @@ class TerminalReporter(object):
|
|||
self.isatty = file.isatty()
|
||||
self._progress_nodeids_reported = set()
|
||||
self._show_progress_info = self._determine_show_progress_info()
|
||||
self._collect_report_last_write = None
|
||||
|
||||
def _determine_show_progress_info(self):
|
||||
"""Return True if we should display progress information based on the current config"""
|
||||
|
@ -261,7 +262,7 @@ class TerminalReporter(object):
|
|||
char = {"xfailed": "x", "skipped": "s"}.get(char, char)
|
||||
return char in self.reportchars
|
||||
|
||||
def write_fspath_result(self, nodeid, res):
|
||||
def write_fspath_result(self, nodeid, res, **markup):
|
||||
fspath = self.config.rootdir.join(nodeid.split("::")[0])
|
||||
if fspath != self.currentfspath:
|
||||
if self.currentfspath is not None and self._show_progress_info:
|
||||
|
@ -270,7 +271,7 @@ class TerminalReporter(object):
|
|||
fspath = self.startdir.bestrelpath(fspath)
|
||||
self._tw.line()
|
||||
self._tw.write(fspath + " ")
|
||||
self._tw.write(res)
|
||||
self._tw.write(res, **markup)
|
||||
|
||||
def write_ensure_prefix(self, prefix, extra="", **kwargs):
|
||||
if self.currentfspath != prefix:
|
||||
|
@ -384,22 +385,22 @@ class TerminalReporter(object):
|
|||
# probably passed setup/teardown
|
||||
return
|
||||
running_xdist = hasattr(rep, "node")
|
||||
if markup is None:
|
||||
if rep.passed:
|
||||
markup = {"green": True}
|
||||
elif rep.failed:
|
||||
markup = {"red": True}
|
||||
elif rep.skipped:
|
||||
markup = {"yellow": True}
|
||||
else:
|
||||
markup = {}
|
||||
if self.verbosity <= 0:
|
||||
if not running_xdist and self.showfspath:
|
||||
self.write_fspath_result(rep.nodeid, letter)
|
||||
self.write_fspath_result(rep.nodeid, letter, **markup)
|
||||
else:
|
||||
self._tw.write(letter)
|
||||
self._tw.write(letter, **markup)
|
||||
else:
|
||||
self._progress_nodeids_reported.add(rep.nodeid)
|
||||
if markup is None:
|
||||
if rep.passed:
|
||||
markup = {"green": True}
|
||||
elif rep.failed:
|
||||
markup = {"red": True}
|
||||
elif rep.skipped:
|
||||
markup = {"yellow": True}
|
||||
else:
|
||||
markup = {}
|
||||
line = self._locationline(rep.nodeid, *rep.location)
|
||||
if not running_xdist:
|
||||
self.write_ensure_prefix(line, word, **markup)
|
||||
|
@ -472,7 +473,11 @@ class TerminalReporter(object):
|
|||
return self._tw.chars_on_current_line
|
||||
|
||||
def pytest_collection(self):
|
||||
if not self.isatty and self.config.option.verbose >= 1:
|
||||
if self.isatty:
|
||||
if self.config.option.verbose >= 0:
|
||||
self.write("collecting ... ", bold=True)
|
||||
self._collect_report_last_write = time.time()
|
||||
elif self.config.option.verbose >= 1:
|
||||
self.write("collecting ... ", bold=True)
|
||||
|
||||
def pytest_collectreport(self, report):
|
||||
|
@ -483,13 +488,19 @@ class TerminalReporter(object):
|
|||
items = [x for x in report.result if isinstance(x, pytest.Item)]
|
||||
self._numcollected += len(items)
|
||||
if self.isatty:
|
||||
# self.write_fspath_result(report.nodeid, 'E')
|
||||
self.report_collect()
|
||||
|
||||
def report_collect(self, final=False):
|
||||
if self.config.option.verbose < 0:
|
||||
return
|
||||
|
||||
if not final:
|
||||
# Only write "collecting" report every 0.5s.
|
||||
t = time.time()
|
||||
if self._collect_report_last_write > t - 0.5:
|
||||
return
|
||||
self._collect_report_last_write = t
|
||||
|
||||
errors = len(self.stats.get("error", []))
|
||||
skipped = len(self.stats.get("skipped", []))
|
||||
deselected = len(self.stats.get("deselected", []))
|
||||
|
|
|
@ -6,6 +6,8 @@ import os
|
|||
|
||||
import pytest
|
||||
|
||||
pytestmark = pytest.mark.pytester_example_path("deprecated")
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("default")
|
||||
def test_yield_tests_deprecation(testdir):
|
||||
|
@ -394,3 +396,13 @@ def test_pycollector_makeitem_is_deprecated():
|
|||
with pytest.warns(RemovedInPytest4Warning):
|
||||
collector.makeitem("foo", "bar")
|
||||
assert collector.called
|
||||
|
||||
|
||||
def test_fixture_named_request(testdir):
|
||||
testdir.copy_example()
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*'request' is a reserved name for fixtures and will raise an error in future versions"
|
||||
]
|
||||
)
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def request():
|
||||
pass
|
||||
|
||||
|
||||
def test():
|
||||
pass
|
|
@ -66,7 +66,8 @@ class TestNewAPI(object):
|
|||
)
|
||||
result = testdir.runpytest("-rw")
|
||||
assert result.ret == 1
|
||||
result.stdout.fnmatch_lines(["*could not create cache path*", "*2 warnings*"])
|
||||
# warnings from nodeids, lastfailed, and stepwise
|
||||
result.stdout.fnmatch_lines(["*could not create cache path*", "*3 warnings*"])
|
||||
|
||||
def test_config_cache(self, testdir):
|
||||
testdir.makeconftest(
|
||||
|
|
|
@ -49,14 +49,14 @@ class TestConftestValueAccessGlobal(object):
|
|||
|
||||
def test_immediate_initialiation_and_incremental_are_the_same(self, basedir):
|
||||
conftest = PytestPluginManager()
|
||||
len(conftest._path2confmods)
|
||||
len(conftest._dirpath2confmods)
|
||||
conftest._getconftestmodules(basedir)
|
||||
snap1 = len(conftest._path2confmods)
|
||||
# assert len(conftest._path2confmods) == snap1 + 1
|
||||
snap1 = len(conftest._dirpath2confmods)
|
||||
# assert len(conftest._dirpath2confmods) == snap1 + 1
|
||||
conftest._getconftestmodules(basedir.join("adir"))
|
||||
assert len(conftest._path2confmods) == snap1 + 1
|
||||
assert len(conftest._dirpath2confmods) == snap1 + 1
|
||||
conftest._getconftestmodules(basedir.join("b"))
|
||||
assert len(conftest._path2confmods) == snap1 + 2
|
||||
assert len(conftest._dirpath2confmods) == snap1 + 2
|
||||
|
||||
def test_value_access_not_existing(self, basedir):
|
||||
conftest = ConftestWithSetinitial(basedir)
|
||||
|
|
|
@ -167,6 +167,7 @@ class TestPDB(object):
|
|||
assert "= 1 failed in" in rest
|
||||
assert "def test_1" not in rest
|
||||
assert "Exit: Quitting debugger" in rest
|
||||
assert "PDB continue (IO-capturing resumed)" not in rest
|
||||
self.flush(child)
|
||||
|
||||
@staticmethod
|
||||
|
@ -498,18 +499,39 @@ class TestPDB(object):
|
|||
"""
|
||||
)
|
||||
child = testdir.spawn_pytest(str(p1))
|
||||
child.expect(r"PDB set_trace \(IO-capturing turned off\)")
|
||||
child.expect("test_1")
|
||||
child.expect("x = 3")
|
||||
child.expect("Pdb")
|
||||
child.sendline("c")
|
||||
child.expect(r"PDB continue \(IO-capturing resumed\)")
|
||||
child.expect(r"PDB set_trace \(IO-capturing turned off\)")
|
||||
child.expect("x = 4")
|
||||
child.expect("Pdb")
|
||||
child.sendeof()
|
||||
child.expect("_ test_1 _")
|
||||
child.expect("def test_1")
|
||||
child.expect("Captured stdout call")
|
||||
rest = child.read().decode("utf8")
|
||||
assert "1 failed" in rest
|
||||
assert "def test_1" in rest
|
||||
assert "hello17" in rest # out is captured
|
||||
assert "hello18" in rest # out is captured
|
||||
assert "1 failed" in rest
|
||||
self.flush(child)
|
||||
|
||||
def test_pdb_without_capture(self, testdir):
|
||||
p1 = testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
def test_1():
|
||||
pytest.set_trace()
|
||||
"""
|
||||
)
|
||||
child = testdir.spawn_pytest("-s %s" % p1)
|
||||
child.expect(r">>> PDB set_trace >>>")
|
||||
child.expect("Pdb")
|
||||
child.sendline("c")
|
||||
child.expect(r">>> PDB continue >>>")
|
||||
child.expect("1 passed")
|
||||
self.flush(child)
|
||||
|
||||
def test_pdb_used_outside_test(self, testdir):
|
||||
|
@ -550,15 +572,29 @@ class TestPDB(object):
|
|||
["E NameError: *xxx*", "*! *Exit: Quitting debugger !*"] # due to EOF
|
||||
)
|
||||
|
||||
def test_enter_pdb_hook_is_called(self, testdir):
|
||||
def test_enter_leave_pdb_hooks_are_called(self, testdir):
|
||||
testdir.makeconftest(
|
||||
"""
|
||||
def pytest_enter_pdb(config):
|
||||
assert config.testing_verification == 'configured'
|
||||
print('enter_pdb_hook')
|
||||
mypdb = None
|
||||
|
||||
def pytest_configure(config):
|
||||
config.testing_verification = 'configured'
|
||||
|
||||
def pytest_enter_pdb(config, pdb):
|
||||
assert config.testing_verification == 'configured'
|
||||
print('enter_pdb_hook')
|
||||
|
||||
global mypdb
|
||||
mypdb = pdb
|
||||
mypdb.set_attribute = "bar"
|
||||
|
||||
def pytest_leave_pdb(config, pdb):
|
||||
assert config.testing_verification == 'configured'
|
||||
print('leave_pdb_hook')
|
||||
|
||||
global mypdb
|
||||
assert mypdb is pdb
|
||||
assert mypdb.set_attribute == "bar"
|
||||
"""
|
||||
)
|
||||
p1 = testdir.makepyfile(
|
||||
|
@ -567,11 +603,17 @@ class TestPDB(object):
|
|||
|
||||
def test_foo():
|
||||
pytest.set_trace()
|
||||
assert 0
|
||||
"""
|
||||
)
|
||||
child = testdir.spawn_pytest(str(p1))
|
||||
child.expect("enter_pdb_hook")
|
||||
child.send("c\n")
|
||||
child.sendline("c")
|
||||
child.expect(r"PDB continue \(IO-capturing resumed\)")
|
||||
child.expect("Captured stdout call")
|
||||
rest = child.read().decode("utf8")
|
||||
assert "leave_pdb_hook" in rest
|
||||
assert "1 failed" in rest
|
||||
child.sendeof()
|
||||
self.flush(child)
|
||||
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def stepwise_testdir(testdir):
|
||||
# Rather than having to modify our testfile between tests, we introduce
|
||||
# a flag for wether or not the second test should fail.
|
||||
testdir.makeconftest(
|
||||
"""
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup('general')
|
||||
group.addoption('--fail', action='store_true', dest='fail')
|
||||
group.addoption('--fail-last', action='store_true', dest='fail_last')
|
||||
"""
|
||||
)
|
||||
|
||||
# Create a simple test suite.
|
||||
testdir.makepyfile(
|
||||
test_a="""
|
||||
def test_success_before_fail():
|
||||
assert 1
|
||||
|
||||
def test_fail_on_flag(request):
|
||||
assert not request.config.getvalue('fail')
|
||||
|
||||
def test_success_after_fail():
|
||||
assert 1
|
||||
|
||||
def test_fail_last_on_flag(request):
|
||||
assert not request.config.getvalue('fail_last')
|
||||
|
||||
def test_success_after_last_fail():
|
||||
assert 1
|
||||
"""
|
||||
)
|
||||
|
||||
testdir.makepyfile(
|
||||
test_b="""
|
||||
def test_success():
|
||||
assert 1
|
||||
"""
|
||||
)
|
||||
|
||||
return testdir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def error_testdir(testdir):
|
||||
testdir.makepyfile(
|
||||
test_a="""
|
||||
def test_error(nonexisting_fixture):
|
||||
assert 1
|
||||
|
||||
def test_success_after_fail():
|
||||
assert 1
|
||||
"""
|
||||
)
|
||||
|
||||
return testdir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def broken_testdir(testdir):
|
||||
testdir.makepyfile(
|
||||
working_testfile="def test_proper(): assert 1", broken_testfile="foobar"
|
||||
)
|
||||
return testdir
|
||||
|
||||
|
||||
def test_run_without_stepwise(stepwise_testdir):
|
||||
result = stepwise_testdir.runpytest("-v", "--strict", "--fail")
|
||||
|
||||
result.stdout.fnmatch_lines(["*test_success_before_fail PASSED*"])
|
||||
result.stdout.fnmatch_lines(["*test_fail_on_flag FAILED*"])
|
||||
result.stdout.fnmatch_lines(["*test_success_after_fail PASSED*"])
|
||||
|
||||
|
||||
def test_fail_and_continue_with_stepwise(stepwise_testdir):
|
||||
# Run the tests with a failing second test.
|
||||
result = stepwise_testdir.runpytest("-v", "--strict", "--stepwise", "--fail")
|
||||
assert not result.stderr.str()
|
||||
|
||||
stdout = result.stdout.str()
|
||||
# Make sure we stop after first failing test.
|
||||
assert "test_success_before_fail PASSED" in stdout
|
||||
assert "test_fail_on_flag FAILED" in stdout
|
||||
assert "test_success_after_fail" not in stdout
|
||||
|
||||
# "Fix" the test that failed in the last run and run it again.
|
||||
result = stepwise_testdir.runpytest("-v", "--strict", "--stepwise")
|
||||
assert not result.stderr.str()
|
||||
|
||||
stdout = result.stdout.str()
|
||||
# Make sure the latest failing test runs and then continues.
|
||||
assert "test_success_before_fail" not in stdout
|
||||
assert "test_fail_on_flag PASSED" in stdout
|
||||
assert "test_success_after_fail PASSED" in stdout
|
||||
|
||||
|
||||
def test_run_with_skip_option(stepwise_testdir):
|
||||
result = stepwise_testdir.runpytest(
|
||||
"-v", "--strict", "--stepwise", "--stepwise-skip", "--fail", "--fail-last"
|
||||
)
|
||||
assert not result.stderr.str()
|
||||
|
||||
stdout = result.stdout.str()
|
||||
# Make sure first fail is ignore and second fail stops the test run.
|
||||
assert "test_fail_on_flag FAILED" in stdout
|
||||
assert "test_success_after_fail PASSED" in stdout
|
||||
assert "test_fail_last_on_flag FAILED" in stdout
|
||||
assert "test_success_after_last_fail" not in stdout
|
||||
|
||||
|
||||
def test_fail_on_errors(error_testdir):
|
||||
result = error_testdir.runpytest("-v", "--strict", "--stepwise")
|
||||
|
||||
assert not result.stderr.str()
|
||||
stdout = result.stdout.str()
|
||||
|
||||
assert "test_error ERROR" in stdout
|
||||
assert "test_success_after_fail" not in stdout
|
||||
|
||||
|
||||
def test_change_testfile(stepwise_testdir):
|
||||
result = stepwise_testdir.runpytest(
|
||||
"-v", "--strict", "--stepwise", "--fail", "test_a.py"
|
||||
)
|
||||
assert not result.stderr.str()
|
||||
|
||||
stdout = result.stdout.str()
|
||||
assert "test_fail_on_flag FAILED" in stdout
|
||||
|
||||
# Make sure the second test run starts from the beginning, since the
|
||||
# test to continue from does not exist in testfile_b.
|
||||
result = stepwise_testdir.runpytest("-v", "--strict", "--stepwise", "test_b.py")
|
||||
assert not result.stderr.str()
|
||||
|
||||
stdout = result.stdout.str()
|
||||
assert "test_success PASSED" in stdout
|
||||
|
||||
|
||||
def test_stop_on_collection_errors(broken_testdir):
|
||||
result = broken_testdir.runpytest(
|
||||
"-v", "--strict", "--stepwise", "working_testfile.py", "broken_testfile.py"
|
||||
)
|
||||
|
||||
stdout = result.stdout.str()
|
||||
assert "errors during collection" in stdout
|
Loading…
Reference in New Issue