Merge pull request #3940 from nicoddemus/release-3.8.0

Release 3.8.0
This commit is contained in:
Bruno Oliveira 2018-09-06 09:15:00 -03:00 committed by GitHub
commit f2a427da25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 391 additions and 93 deletions

View File

@ -1,3 +1,4 @@
[run] [run]
source = _pytest,testing source = _pytest,testing
parallel = 1 parallel = 1
branch = 1

3
.gitignore vendored
View File

@ -38,3 +38,6 @@ env/
.ropeproject .ropeproject
.idea .idea
.hypothesis .hypothesis
.pydevproject
.project
.settings

View File

@ -73,6 +73,7 @@ Endre Galaczi
Eric Hunsberger Eric Hunsberger
Eric Siegerman Eric Siegerman
Erik M. Bray Erik M. Bray
Fabio Zadrozny
Feng Ma Feng Ma
Florian Bruhin Florian Bruhin
Floris Bruynooghe Floris Bruynooghe

View File

@ -18,6 +18,90 @@ with advance notice in the **Deprecations** section of releases.
.. towncrier release notes start .. towncrier release notes start
pytest 3.8.0 (2018-09-05)
=========================
Deprecations and Removals
-------------------------
- `#2452 <https://github.com/pytest-dev/pytest/issues/2452>`_: ``Config.warn`` has been deprecated, it should be replaced by calls to the standard ``warnings.warn``.
``Node.warn`` now supports two signatures:
* ``node.warn(PytestWarning("some message"))``: is now the recommended way to call this function. The warning
instance must be a ``PytestWarning`` or subclass instance.
* ``node.warn("CI", "some message")``: this code/message form is now deprecated and should be converted to
the warning instance form above.
``RemovedInPytest4Warning`` and ``PytestExperimentalApiWarning`` are now part of the public API and should be accessed
using ``pytest.RemovedInPytest4Warning`` and ``pytest.PytestExperimentalApiWarning``.
- `#3936 <https://github.com/pytest-dev/pytest/issues/3936>`_: ``@pytest.mark.filterwarnings`` second parameter is no longer regex-escaped,
making it possible to actually use regular expressions to check the warning message.
**Note**: regex-escaping the match string was an implementation oversight that might break test suites which depend
on the old behavior.
Features
--------
- `#2452 <https://github.com/pytest-dev/pytest/issues/2452>`_: Internal pytest warnings are now issued using the standard ``warnings`` module, making it possible to use
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.
- `#2908 <https://github.com/pytest-dev/pytest/issues/2908>`_: ``DeprecationWarning`` and ``PendingDeprecationWarning`` are now shown by default if no other warning filter is
configured. This makes pytest more compliant with
`PEP-0506 <https://www.python.org/dev/peps/pep-0565/#recommended-filter-settings-for-test-runners>`_. See
`the docs <https://docs.pytest.org/en/latest/warnings.html#deprecationwarning-and-pendingdeprecationwarning>`_ for
more info.
- `#3784 <https://github.com/pytest-dev/pytest/issues/3784>`_: Add option to disable plugin auto-loading.
- `#3829 <https://github.com/pytest-dev/pytest/issues/3829>`_: Added the ``count`` option to ``console_output_style`` to enable displaying the progress as a count instead of a percentage.
- `#3837 <https://github.com/pytest-dev/pytest/issues/3837>`_: Added support for 'xfailed' and 'xpassed' outcomes to the ``pytester.RunResult.assert_outcomes`` signature.
Bug Fixes
---------
- `#3911 <https://github.com/pytest-dev/pytest/issues/3911>`_: Terminal writer now takes into account unicode character width when writing out progress.
- `#3913 <https://github.com/pytest-dev/pytest/issues/3913>`_: Pytest now returns with correct exit code (EXIT_USAGEERROR, 4) when called with unknown arguments.
- `#3918 <https://github.com/pytest-dev/pytest/issues/3918>`_: Improve performance of assertion rewriting.
Improved Documentation
----------------------
- `#3566 <https://github.com/pytest-dev/pytest/issues/3566>`_: Added a blurb in usage.rst for the usage of -r flag which is used to show an extra test summary info.
- `#3907 <https://github.com/pytest-dev/pytest/issues/3907>`_: Corrected type of the exceptions collection passed to ``xfail``: ``raises`` argument accepts a ``tuple`` instead of ``list``.
Trivial/Internal Changes
------------------------
- `#3853 <https://github.com/pytest-dev/pytest/issues/3853>`_: Removed ``"run all (no recorded failures)"`` message printed with ``--failed-first`` and ``--last-failed`` when there are no failed tests.
pytest 3.7.4 (2018-08-29) pytest 3.7.4 (2018-08-29)
========================= =========================

View File

@ -15,7 +15,7 @@ environment:
- TOXENV: "py35" - TOXENV: "py35"
- TOXENV: "py36" - TOXENV: "py36"
- TOXENV: "py37" - TOXENV: "py37"
# - TOXENV: "pypy" reenable when we are able to provide a scandir wheel or build scandir - TOXENV: "pypy"
- TOXENV: "py27-pexpect" - TOXENV: "py27-pexpect"
- TOXENV: "py27-xdist" - TOXENV: "py27-xdist"
- TOXENV: "py27-trial" - TOXENV: "py27-trial"

View File

@ -1,5 +0,0 @@
Internal pytest warnings are now issued using the standard ``warnings`` module, making it possible to use
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.

View File

@ -1,12 +0,0 @@
``Config.warn`` has been deprecated, it should be replaced by calls to the standard ``warnings.warn``.
``Node.warn`` now supports two signatures:
* ``node.warn(PytestWarning("some message"))``: is now the recommended way to call this function. The warning
instance must be a ``PytestWarning`` or subclass instance.
* ``node.warn("CI", "some message")``: this code/message form is now deprecated and should be converted to
the warning instance form above.
``RemovedInPytest4Warning`` and ``PytestExperimentalApiWarning`` are now part of the public API and should be accessed
using ``pytest.RemovedInPytest4Warning`` and ``pytest.PytestExperimentalApiWarning``.

View File

@ -1,5 +0,0 @@
``DeprecationWarning`` and ``PendingDeprecationWarning`` are now shown by default if no other warning filter is
configured. This makes pytest more compliant with
`PEP-0506 <https://www.python.org/dev/peps/pep-0565/#recommended-filter-settings-for-test-runners>`_. See
`the docs <https://docs.pytest.org/en/latest/warnings.html#deprecationwarning-and-pendingdeprecationwarning>`_ for
more info.

View File

@ -1 +0,0 @@
Add option to disable plugin auto-loading.

View File

@ -1 +0,0 @@
Added the ``count`` option to ``console_output_style`` to enable displaying the progress as a count instead of a percentage.

View File

@ -1 +0,0 @@
Added support for 'xfailed' and 'xpassed' outcomes to the ``pytester.RunResult.assert_outcomes`` signature.

View File

@ -1 +0,0 @@
Removed ``"run all (no recorded failures)"`` message printed with ``--failed-first`` and ``--last-failed`` when there are no failed tests.

View File

@ -1 +0,0 @@
Corrected type of the exceptions collection passed to ``xfail``: ``raises`` argument accepts a ``tuple`` instead of ``list``.

View File

@ -1 +0,0 @@
Pytest now returns with correct exit code (EXIT_USAGEERROR, 4) when called with unknown arguments.

View File

@ -1,5 +0,0 @@
``@pytest.mark.filterwarnings`` second parameter is no longer regex-escaped,
making it possible to actually use regular expressions to check the warning message.
**Note**: regex-escaping the match string was an implementation oversight that might break test suites which depend
on the old behavior.

View File

@ -6,6 +6,7 @@ Release announcements
:maxdepth: 2 :maxdepth: 2
release-3.8.0
release-3.7.4 release-3.7.4
release-3.7.3 release-3.7.3
release-3.7.2 release-3.7.2

View File

@ -0,0 +1,38 @@
pytest-3.8.0
=======================================
The pytest team is proud to announce the 3.8.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:
* Anthony Sottile
* Bruno Oliveira
* CrazyMerlyn
* Daniel Hahler
* Fabio Zadrozny
* Jeffrey Rackauckas
* Ronny Pfannschmidt
* Virgil Dupras
* dhirensr
* hoefling
* wim glenn
Happy testing,
The Pytest Development Team

View File

@ -329,7 +329,7 @@ texinfo_documents = [
# Example configuration for intersphinx: refer to the Python standard library. # Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {"python": ("http://docs.python.org/3", None)} intersphinx_mapping = {"python": ("https://docs.python.org/3", None)}
def setup(app): def setup(app):

View File

@ -613,9 +613,9 @@ get on the terminal - we are working on that)::
failure_demo.py:261: AssertionError failure_demo.py:261: AssertionError
============================= warnings summary ============================= ============================= warnings summary =============================
<undetermined location> $REGENDOC_TMPDIR/assertion/failure_demo.py:24: RemovedInPytest4Warning: Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0.
Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0. Please use Metafunc.parametrize instead.
Please use Metafunc.parametrize instead. metafunc.addcall(funcargs=dict(param1=3, param2=6))
-- Docs: https://docs.pytest.org/en/latest/warnings.html -- Docs: https://docs.pytest.org/en/latest/warnings.html
================== 42 failed, 1 warnings in 0.12 seconds =================== ================== 42 failed, 1 warnings in 0.12 seconds ===================

View File

@ -14,6 +14,9 @@ Talks and Tutorials
Books Books
--------------------------------------------- ---------------------------------------------
- `pytest Quick Start Guide, by Bruno Oliveira (2018)
<https://www.packtpub.com/web-development/pytest-quick-start-guide>`_.
- `Python Testing with pytest, by Brian Okken (2017) - `Python Testing with pytest, by Brian Okken (2017)
<https://pragprog.com/book/bopytest/python-testing-with-pytest>`_. <https://pragprog.com/book/bopytest/python-testing-with-pytest>`_.

View File

@ -140,6 +140,49 @@ will be shown (because KeyboardInterrupt is caught by pytest). By using this
option you make sure a trace is shown. option you make sure a trace is shown.
.. _`pytest.detailed_failed_tests_usage`:
Detailed summary report
-----------------------
.. versionadded:: 2.9
The ``-r`` flag can be used to display test results summary at the end of the test session,
making it easy in large test suites to get a clear picture of all failures, skips, xfails, etc.
Example::
$ pytest -ra
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 0 items
======================= no tests ran in 0.12 seconds =======================
The ``-r`` options accepts a number of characters after it, with ``a`` used above meaning "all except passes".
Here is the full list of available characters that can be used:
- ``f`` - failed
- ``E`` - error
- ``s`` - skipped
- ``x`` - xfailed
- ``X`` - xpassed
- ``p`` - passed
- ``P`` - passed with output
- ``a`` - all except ``pP``
More than one character can be used, so for example to only see failed and skipped tests, you can execute::
$ pytest -rfs
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 0 items
======================= no tests ran in 0.12 seconds =======================
.. _pdb-option: .. _pdb-option:
Dropping to PDB_ (Python Debugger) on failures Dropping to PDB_ (Python Debugger) on failures

View File

@ -29,9 +29,8 @@ Running pytest now produces this output::
test_show_warnings.py . [100%] test_show_warnings.py . [100%]
============================= warnings summary ============================= ============================= warnings summary =============================
test_show_warnings.py::test_one $REGENDOC_TMPDIR/test_show_warnings.py:4: UserWarning: api v1, should use functions from v2
$REGENDOC_TMPDIR/test_show_warnings.py:4: UserWarning: api v1, should use functions from v2 warnings.warn(UserWarning("api v1, should use functions from v2"))
warnings.warn(UserWarning("api v1, should use functions from v2"))
-- Docs: https://docs.pytest.org/en/latest/warnings.html -- Docs: https://docs.pytest.org/en/latest/warnings.html
=================== 1 passed, 1 warnings in 0.12 seconds =================== =================== 1 passed, 1 warnings in 0.12 seconds ===================
@ -354,15 +353,13 @@ defines an ``__init__`` constructor, as this prevents the class from being insta
:: ::
$ pytest test_pytest_warnings.py -q $ pytest test_pytest_warnings.py -q
======================================== warnings summary =========================================
test_pytest_warnings.py:1
$REGENDOC_TMPDIR/test_pytest_warnings.py:1: PytestWarning: cannot collect test class 'Test' because it has a __init__ constructor
class Test:
-- Docs: http://doc.pytest.org/en/latest/warnings.html
1 warnings in 0.01 seconds
============================= warnings summary =============================
$REGENDOC_TMPDIR/test_pytest_warnings.py:1: PytestWarning: cannot collect test class 'Test' because it has a __init__ constructor
class Test:
-- Docs: https://docs.pytest.org/en/latest/warnings.html
1 warnings in 0.12 seconds
These warnings might be filtered using the same builtin mechanisms used to filter other types of warnings. These warnings might be filtered using the same builtin mechanisms used to filter other types of warnings.

View File

@ -418,9 +418,8 @@ additionally it is possible to copy examples for a example folder before running
test_example.py .. [100%] test_example.py .. [100%]
============================= warnings summary ============================= ============================= warnings summary =============================
test_example.py::test_plugin $REGENDOC_TMPDIR/test_example.py:4: PytestExperimentalApiWarning: testdir.copy_example is an experimental api that may change over time
$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")
testdir.copy_example("test_example.py")
-- Docs: https://docs.pytest.org/en/latest/warnings.html -- Docs: https://docs.pytest.org/en/latest/warnings.html
=================== 2 passed, 1 warnings in 0.12 seconds =================== =================== 2 passed, 1 warnings in 0.12 seconds ===================

View File

@ -59,7 +59,7 @@ def get_environment_marker_support_level():
def main(): def main():
extras_require = {} extras_require = {}
install_requires = [ install_requires = [
"py>=1.5.0", "py>=1.5.0", # if py gets upgrade to >=1.6, remove _width_of_current_line in terminal.py
"six>=1.10.0", "six>=1.10.0",
"setuptools", "setuptools",
"attrs>=17.4.0", "attrs>=17.4.0",

View File

@ -67,14 +67,24 @@ class AssertionRewritingHook(object):
# flag to guard against trying to rewrite a pyc file while we are already writing another pyc file, # flag to guard against trying to rewrite a pyc file while we are already writing another pyc file,
# which might result in infinite recursion (#3506) # which might result in infinite recursion (#3506)
self._writing_pyc = False self._writing_pyc = False
self._basenames_to_check_rewrite = {"conftest"}
self._marked_for_rewrite_cache = {}
self._session_paths_checked = False
def set_session(self, session): def set_session(self, session):
self.session = session self.session = session
self._session_paths_checked = False
def _imp_find_module(self, name, path=None):
"""Indirection so we can mock calls to find_module originated from the hook during testing"""
return imp.find_module(name, path)
def find_module(self, name, path=None): def find_module(self, name, path=None):
if self._writing_pyc: if self._writing_pyc:
return None return None
state = self.config._assertstate state = self.config._assertstate
if self._early_rewrite_bailout(name, state):
return None
state.trace("find_module called for: %s" % name) state.trace("find_module called for: %s" % name)
names = name.rsplit(".", 1) names = name.rsplit(".", 1)
lastname = names[-1] lastname = names[-1]
@ -87,7 +97,7 @@ class AssertionRewritingHook(object):
pth = path[0] pth = path[0]
if pth is None: if pth is None:
try: try:
fd, fn, desc = imp.find_module(lastname, path) fd, fn, desc = self._imp_find_module(lastname, path)
except ImportError: except ImportError:
return None return None
if fd is not None: if fd is not None:
@ -166,6 +176,44 @@ class AssertionRewritingHook(object):
self.modules[name] = co, pyc self.modules[name] = co, pyc
return self return self
def _early_rewrite_bailout(self, name, state):
"""
This is a fast way to get out of rewriting modules. Profiling has
shown that the call to imp.find_module (inside of the find_module
from this class) is a major slowdown, so, this method tries to
filter what we're sure won't be rewritten before getting to it.
"""
if self.session is not None and not self._session_paths_checked:
self._session_paths_checked = True
for path in self.session._initialpaths:
# Make something as c:/projects/my_project/path.py ->
# ['c:', 'projects', 'my_project', 'path.py']
parts = str(path).split(os.path.sep)
# add 'path' to basenames to be checked.
self._basenames_to_check_rewrite.add(os.path.splitext(parts[-1])[0])
# Note: conftest already by default in _basenames_to_check_rewrite.
parts = name.split(".")
if parts[-1] in self._basenames_to_check_rewrite:
return False
# For matching the name it must be as if it was a filename.
parts[-1] = parts[-1] + ".py"
fn_pypath = py.path.local(os.path.sep.join(parts))
for pat in self.fnpats:
# if the pattern contains subdirectories ("tests/**.py" for example) we can't bail out based
# on the name alone because we need to match against the full path
if os.path.dirname(pat):
return False
if fn_pypath.fnmatch(pat):
return False
if self._is_marked_for_rewrite(name, state):
return False
state.trace("early skip of rewriting module: %s" % (name,))
return True
def _should_rewrite(self, name, fn_pypath, state): def _should_rewrite(self, name, fn_pypath, state):
# always rewrite conftest files # always rewrite conftest files
fn = str(fn_pypath) fn = str(fn_pypath)
@ -185,12 +233,20 @@ class AssertionRewritingHook(object):
state.trace("matched test file %r" % (fn,)) state.trace("matched test file %r" % (fn,))
return True return True
for marked in self._must_rewrite: return self._is_marked_for_rewrite(name, state)
if name == marked or name.startswith(marked + "."):
state.trace("matched marked file %r (from %r)" % (name, marked))
return True
return False def _is_marked_for_rewrite(self, name, state):
try:
return self._marked_for_rewrite_cache[name]
except KeyError:
for marked in self._must_rewrite:
if name == marked or name.startswith(marked + "."):
state.trace("matched marked file %r (from %r)" % (name, marked))
self._marked_for_rewrite_cache[name] = True
return True
self._marked_for_rewrite_cache[name] = False
return False
def mark_rewrite(self, *names): def mark_rewrite(self, *names):
"""Mark import names as needing to be rewritten. """Mark import names as needing to be rewritten.
@ -207,6 +263,7 @@ class AssertionRewritingHook(object):
): ):
self._warn_already_imported(name) self._warn_already_imported(name)
self._must_rewrite.update(names) self._must_rewrite.update(names)
self._marked_for_rewrite_cache.clear()
def _warn_already_imported(self, name): def _warn_already_imported(self, name):
from _pytest.warning_types import PytestWarning from _pytest.warning_types import PytestWarning
@ -245,7 +302,7 @@ class AssertionRewritingHook(object):
def is_package(self, name): def is_package(self, name):
try: try:
fd, fn, desc = imp.find_module(name) fd, fn, desc = self._imp_find_module(name)
except ImportError: except ImportError:
return False return False
if fd is not None: if fd is not None:

View File

@ -51,6 +51,8 @@ def main(args=None, plugins=None):
:arg plugins: list of plugin objects to be auto-registered during :arg plugins: list of plugin objects to be auto-registered during
initialization. initialization.
""" """
from _pytest.main import EXIT_USAGEERROR
try: try:
try: try:
config = _prepareconfig(args, plugins) config = _prepareconfig(args, plugins)
@ -69,7 +71,7 @@ def main(args=None, plugins=None):
tw = py.io.TerminalWriter(sys.stderr) tw = py.io.TerminalWriter(sys.stderr)
for msg in e.args: for msg in e.args:
tw.line("ERROR: {}\n".format(msg), red=True) tw.line("ERROR: {}\n".format(msg), red=True)
return 4 return EXIT_USAGEERROR
class cmdline(object): # compatibility namespace class cmdline(object): # compatibility namespace

View File

@ -383,6 +383,7 @@ class Session(nodes.FSCollector):
self.trace = config.trace.root.get("collection") self.trace = config.trace.root.get("collection")
self._norecursepatterns = config.getini("norecursedirs") self._norecursepatterns = config.getini("norecursedirs")
self.startdir = py.path.local() self.startdir = py.path.local()
self._initialpaths = frozenset()
# Keep track of any collected nodes in here, so we don't duplicate fixtures # Keep track of any collected nodes in here, so we don't duplicate fixtures
self._node_cache = {} self._node_cache = {}
@ -441,13 +442,14 @@ class Session(nodes.FSCollector):
self.trace("perform_collect", self, args) self.trace("perform_collect", self, args)
self.trace.root.indent += 1 self.trace.root.indent += 1
self._notfound = [] self._notfound = []
self._initialpaths = set() initialpaths = []
self._initialparts = [] self._initialparts = []
self.items = items = [] self.items = items = []
for arg in args: for arg in args:
parts = self._parsearg(arg) parts = self._parsearg(arg)
self._initialparts.append(parts) self._initialparts.append(parts)
self._initialpaths.add(parts[0]) initialpaths.append(parts[0])
self._initialpaths = frozenset(initialpaths)
rep = collect_one_node(self) rep = collect_one_node(self)
self.ihook.pytest_collectreport(report=rep) self.ihook.pytest_collectreport(report=rep)
self.trace.root.indent -= 1 self.trace.root.indent -= 1
@ -564,7 +566,6 @@ class Session(nodes.FSCollector):
"""Convert a dotted module name to path. """Convert a dotted module name to path.
""" """
try: try:
with _patched_find_module(): with _patched_find_module():
loader = pkgutil.find_loader(x) loader = pkgutil.find_loader(x)

View File

@ -67,13 +67,19 @@ exit.Exception = Exit
def skip(msg="", **kwargs): def skip(msg="", **kwargs):
""" skip an executing test with the given message. Note: it's usually """
better to use the pytest.mark.skipif marker to declare a test to be Skip an executing test with the given message.
skipped under certain conditions like mismatching platforms or
dependencies. See the pytest_skipping plugin for details. This function should be called only during testing (setup, call or teardown) or
during collection by using the ``allow_module_level`` flag.
:kwarg bool allow_module_level: allows this function to be called at :kwarg bool allow_module_level: allows this function to be called at
module level, skipping the rest of the module. Default to False. module level, skipping the rest of the module. Default to False.
.. note::
It is better to use the :ref:`pytest.mark.skipif ref` marker when possible to declare a test to be
skipped under certain conditions like mismatching platforms or
dependencies.
""" """
__tracebackhide__ = True __tracebackhide__ = True
allow_module_level = kwargs.pop("allow_module_level", False) allow_module_level = kwargs.pop("allow_module_level", False)
@ -87,10 +93,12 @@ skip.Exception = Skipped
def fail(msg="", pytrace=True): def fail(msg="", pytrace=True):
""" explicitly fail a currently-executing test with the given Message. """
Explicitly fail an executing test with the given message.
:arg pytrace: if false the msg represents the full failure information :param str msg: the message to show the user as reason for the failure.
and no python traceback will be reported. :param bool pytrace: if false the msg represents the full failure information and no
python traceback will be reported.
""" """
__tracebackhide__ = True __tracebackhide__ = True
raise Failed(msg=msg, pytrace=pytrace) raise Failed(msg=msg, pytrace=pytrace)
@ -104,7 +112,15 @@ class XFailed(fail.Exception):
def xfail(reason=""): def xfail(reason=""):
""" xfail an executing test or setup functions with the given reason.""" """
Imperatively xfail an executing test or setup functions with the given reason.
This function should be called only during testing (setup, call or teardown).
.. note::
It is better to use the :ref:`pytest.mark.xfail ref` marker when possible to declare a test to be
xfailed under certain conditions like known bugs or missing features.
"""
__tracebackhide__ = True __tracebackhide__ = True
raise XFailed(reason) raise XFailed(reason)

View File

@ -435,10 +435,8 @@ class TerminalReporter(object):
if last_item: if last_item:
self._write_progress_information_filling_space() self._write_progress_information_filling_space()
else: else:
past_edge = ( w = self._width_of_current_line
self._tw.chars_on_current_line + progress_length + 1 past_edge = w + progress_length + 1 >= self._screen_width
>= self._screen_width
)
if past_edge: if past_edge:
msg = self._get_progress_information_message() msg = self._get_progress_information_message()
self._tw.write(msg + "\n", cyan=True) self._tw.write(msg + "\n", cyan=True)
@ -462,10 +460,18 @@ class TerminalReporter(object):
def _write_progress_information_filling_space(self): def _write_progress_information_filling_space(self):
msg = self._get_progress_information_message() msg = self._get_progress_information_message()
fill = " " * ( w = self._width_of_current_line
self._tw.fullwidth - self._tw.chars_on_current_line - len(msg) - 1 fill = self._tw.fullwidth - w - 1
) self.write(msg.rjust(fill), cyan=True)
self.write(fill + msg, cyan=True)
@property
def _width_of_current_line(self):
"""Return the width of current line, using the superior implementation of py-1.6 when available"""
try:
return self._tw.width_of_current_line
except AttributeError:
# py < 1.6.0
return self._tw.chars_on_current_line
def pytest_collection(self): def pytest_collection(self):
if not self.isatty and self.config.option.verbose >= 1: if not self.isatty and self.config.option.verbose >= 1:

View File

@ -1106,22 +1106,21 @@ class TestIssue925(object):
class TestIssue2121: class TestIssue2121:
def test_simple(self, testdir): def test_rewrite_python_files_contain_subdirs(self, testdir):
testdir.tmpdir.join("tests/file.py").ensure().write( testdir.makepyfile(
""" **{
def test_simple_failure(): "tests/file.py": """
assert 1 + 1 == 3 def test_simple_failure():
""" assert 1 + 1 == 3
)
testdir.tmpdir.join("pytest.ini").write(
textwrap.dedent(
""" """
[pytest] }
python_files = tests/**.py )
""" testdir.makeini(
) """
[pytest]
python_files = tests/**.py
"""
) )
result = testdir.runpytest() result = testdir.runpytest()
result.stdout.fnmatch_lines("*E*assert (1 + 1) == 3") result.stdout.fnmatch_lines("*E*assert (1 + 1) == 3")
@ -1153,3 +1152,83 @@ def test_rewrite_infinite_recursion(testdir, pytestconfig, monkeypatch):
hook = AssertionRewritingHook(pytestconfig) hook = AssertionRewritingHook(pytestconfig)
assert hook.find_module("test_foo") is not None assert hook.find_module("test_foo") is not None
assert len(write_pyc_called) == 1 assert len(write_pyc_called) == 1
class TestEarlyRewriteBailout(object):
@pytest.fixture
def hook(self, pytestconfig, monkeypatch, testdir):
"""Returns a patched AssertionRewritingHook instance so we can configure its initial paths and track
if imp.find_module has been called.
"""
import imp
self.find_module_calls = []
self.initial_paths = set()
class StubSession(object):
_initialpaths = self.initial_paths
def isinitpath(self, p):
return p in self._initialpaths
def spy_imp_find_module(name, path):
self.find_module_calls.append(name)
return imp.find_module(name, path)
hook = AssertionRewritingHook(pytestconfig)
# use default patterns, otherwise we inherit pytest's testing config
hook.fnpats[:] = ["test_*.py", "*_test.py"]
monkeypatch.setattr(hook, "_imp_find_module", spy_imp_find_module)
hook.set_session(StubSession())
testdir.syspathinsert()
return hook
def test_basic(self, testdir, hook):
"""
Ensure we avoid calling imp.find_module when we know for sure a certain module will not be rewritten
to optimize assertion rewriting (#3918).
"""
testdir.makeconftest(
"""
import pytest
@pytest.fixture
def fix(): return 1
"""
)
testdir.makepyfile(test_foo="def test_foo(): pass")
testdir.makepyfile(bar="def bar(): pass")
foobar_path = testdir.makepyfile(foobar="def foobar(): pass")
self.initial_paths.add(foobar_path)
# conftest files should always be rewritten
assert hook.find_module("conftest") is not None
assert self.find_module_calls == ["conftest"]
# files matching "python_files" mask should always be rewritten
assert hook.find_module("test_foo") is not None
assert self.find_module_calls == ["conftest", "test_foo"]
# file does not match "python_files": early bailout
assert hook.find_module("bar") is None
assert self.find_module_calls == ["conftest", "test_foo"]
# file is an initial path (passed on the command-line): should be rewritten
assert hook.find_module("foobar") is not None
assert self.find_module_calls == ["conftest", "test_foo", "foobar"]
def test_pattern_contains_subdirectories(self, testdir, hook):
"""If one of the python_files patterns contain subdirectories ("tests/**.py") we can't bailout early
because we need to match with the full path, which can only be found by calling imp.find_module.
"""
p = testdir.makepyfile(
**{
"tests/file.py": """
def test_simple_failure():
assert 1 + 1 == 3
"""
}
)
testdir.syspathinsert(p.dirpath())
hook.fnpats[:] = ["tests/**.py"]
assert hook.find_module("file") is not None
assert self.find_module_calls == ["file"]