commit
f2a427da25
|
@ -1,3 +1,4 @@
|
||||||
[run]
|
[run]
|
||||||
source = _pytest,testing
|
source = _pytest,testing
|
||||||
parallel = 1
|
parallel = 1
|
||||||
|
branch = 1
|
||||||
|
|
|
@ -38,3 +38,6 @@ env/
|
||||||
.ropeproject
|
.ropeproject
|
||||||
.idea
|
.idea
|
||||||
.hypothesis
|
.hypothesis
|
||||||
|
.pydevproject
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
|
1
AUTHORS
1
AUTHORS
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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.
|
|
|
@ -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``.
|
|
|
@ -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.
|
|
|
@ -1 +0,0 @@
|
||||||
Add option to disable plugin auto-loading.
|
|
|
@ -1 +0,0 @@
|
||||||
Added the ``count`` option to ``console_output_style`` to enable displaying the progress as a count instead of a percentage.
|
|
|
@ -1 +0,0 @@
|
||||||
Added support for 'xfailed' and 'xpassed' outcomes to the ``pytester.RunResult.assert_outcomes`` signature.
|
|
|
@ -1 +0,0 @@
|
||||||
Removed ``"run all (no recorded failures)"`` message printed with ``--failed-first`` and ``--last-failed`` when there are no failed tests.
|
|
|
@ -1 +0,0 @@
|
||||||
Corrected type of the exceptions collection passed to ``xfail``: ``raises`` argument accepts a ``tuple`` instead of ``list``.
|
|
|
@ -1 +0,0 @@
|
||||||
Pytest now returns with correct exit code (EXIT_USAGEERROR, 4) when called with unknown arguments.
|
|
|
@ -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.
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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):
|
||||||
|
|
|
@ -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 ===================
|
||||||
|
|
|
@ -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>`_.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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 ===================
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -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",
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
Loading…
Reference in New Issue