Merge remote-tracking branch 'origin/master' into merge-master

This commit is contained in:
Daniel Hahler 2018-10-24 22:36:34 +02:00
commit eee8201e4f
36 changed files with 284 additions and 53 deletions

View File

@ -13,7 +13,7 @@ repos:
additional_dependencies: [black==18.9b0] additional_dependencies: [black==18.9b0]
language_version: python3 language_version: python3
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v1.4.0-1 rev: v2.0.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: end-of-file-fixer - id: end-of-file-fixer

View File

@ -47,6 +47,11 @@ jobs:
env: TOXENV=py37 env: TOXENV=py37
before_install: before_install:
- brew update - brew update
# remove c++ include files because upgrading python as of 2018-10-23, also
# attempts to upgrade gcc, and it fails because the include files already
# exist. removing the include files is one of the solutions recommended by brew
# this workaround might not be necessary in the future
- rm '/usr/local/include/c++'
- brew upgrade python - brew upgrade python
- brew unlink python - brew unlink python
- brew link python - brew link python

View File

@ -18,6 +18,37 @@ with advance notice in the **Deprecations** section of releases.
.. towncrier release notes start .. towncrier release notes start
pytest 3.9.2 (2018-10-22)
=========================
Bug Fixes
---------
- `#2909 <https://github.com/pytest-dev/pytest/issues/2909>`_: Improve error message when a recursive dependency between fixtures is detected.
- `#3340 <https://github.com/pytest-dev/pytest/issues/3340>`_: Fix logging messages not shown in hooks ``pytest_sessionstart()`` and ``pytest_sessionfinish()``.
- `#3533 <https://github.com/pytest-dev/pytest/issues/3533>`_: Fix unescaped XML raw objects in JUnit report for skipped tests
- `#3691 <https://github.com/pytest-dev/pytest/issues/3691>`_: Python 2: safely format warning message about passing unicode strings to ``warnings.warn``, which may cause
surprising ``MemoryError`` exception when monkey patching ``warnings.warn`` itself.
- `#4026 <https://github.com/pytest-dev/pytest/issues/4026>`_: Improve error message when it is not possible to determine a function's signature.
- `#4177 <https://github.com/pytest-dev/pytest/issues/4177>`_: Pin ``setuptools>=40.0`` to support ``py_modules`` in ``setup.cfg``
- `#4179 <https://github.com/pytest-dev/pytest/issues/4179>`_: Restore the tmpdir behaviour of symlinking the current test run.
- `#4192 <https://github.com/pytest-dev/pytest/issues/4192>`_: Fix filename reported by ``warnings.warn`` when using ``recwarn`` under python2.
pytest 3.9.1 (2018-10-16) pytest 3.9.1 (2018-10-16)
========================= =========================

View File

@ -1 +0,0 @@
Fix unescaped XML raw objects in JUnit report for skipped tests

1
changelog/3851.doc.rst Normal file
View File

@ -0,0 +1 @@
Add reference to ``empty_parameter_set_mark`` ini option in documentation of ``@pytest.mark.parametrize``

View File

@ -0,0 +1 @@
Fix "ValueError: Plugin already registered" with conftest plugins via symlink.

View File

@ -1 +0,0 @@
Pin ``setuptools>=40.0`` to support ``py_modules`` in ``setup.cfg``

View File

@ -1 +0,0 @@
Restore the tmpdir behaviour of symlinking the current test run.

View File

@ -6,6 +6,7 @@ Release announcements
:maxdepth: 2 :maxdepth: 2
release-3.9.2
release-3.9.1 release-3.9.1
release-3.9.0 release-3.9.0
release-3.8.2 release-3.8.2

View File

@ -0,0 +1,23 @@
pytest-3.9.2
=======================================
pytest 3.9.2 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Ankit Goel
* Anthony Sottile
* Bruno Oliveira
* Ronny Pfannschmidt
* Vincent Barbaresi
* ykantor
Happy testing,
The pytest Development Team

View File

@ -31,7 +31,7 @@ You can then restrict a test run to only run tests marked with ``webtest``::
$ pytest -v -m webtest $ pytest -v -m webtest
=========================== test session starts ============================ =========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items / 3 deselected collecting ... collected 4 items / 3 deselected
@ -44,7 +44,7 @@ Or the inverse, running all tests except the webtest ones::
$ pytest -v -m "not webtest" $ pytest -v -m "not webtest"
=========================== test session starts ============================ =========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items / 1 deselected collecting ... collected 4 items / 1 deselected
@ -64,7 +64,7 @@ tests based on their module, class, method, or function name::
$ pytest -v test_server.py::TestClass::test_method $ pytest -v test_server.py::TestClass::test_method
=========================== test session starts ============================ =========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 1 item collecting ... collected 1 item
@ -77,7 +77,7 @@ You can also select on the class::
$ pytest -v test_server.py::TestClass $ pytest -v test_server.py::TestClass
=========================== test session starts ============================ =========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 1 item collecting ... collected 1 item
@ -90,7 +90,7 @@ Or select multiple nodes::
$ pytest -v test_server.py::TestClass test_server.py::test_send_http $ pytest -v test_server.py::TestClass test_server.py::test_send_http
=========================== test session starts ============================ =========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 2 items collecting ... collected 2 items
@ -128,7 +128,7 @@ select tests based on their names::
$ pytest -v -k http # running with the above defined example module $ pytest -v -k http # running with the above defined example module
=========================== test session starts ============================ =========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items / 3 deselected collecting ... collected 4 items / 3 deselected
@ -141,7 +141,7 @@ And you can also run all tests except the ones that match the keyword::
$ pytest -k "not send_http" -v $ pytest -k "not send_http" -v
=========================== test session starts ============================ =========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items / 1 deselected collecting ... collected 4 items / 1 deselected
@ -156,7 +156,7 @@ Or to select "http" and "quick" tests::
$ pytest -k "http or quick" -v $ pytest -k "http or quick" -v
=========================== test session starts ============================ =========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items / 2 deselected collecting ... collected 4 items / 2 deselected

View File

@ -59,7 +59,7 @@ consulted when reporting in ``verbose`` mode::
nonpython $ pytest -v nonpython $ pytest -v
=========================== test session starts ============================ =========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR/nonpython, inifile: rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
collecting ... collected 2 items collecting ... collected 2 items

View File

@ -1,6 +1,5 @@
def test_exception_syntax(): def test_exception_syntax():
try: try:
0/0 0 / 0
except ZeroDivisionError, e: except ZeroDivisionError, e:
pass assert e

View File

@ -2,4 +2,4 @@ def test_exception_syntax():
try: try:
0 / 0 0 / 0
except ZeroDivisionError as e: except ZeroDivisionError as e:
pass assert e

View File

@ -357,7 +357,7 @@ which will add info only when run with "--v"::
$ pytest -v $ pytest -v
=========================== test session starts ============================ =========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache cachedir: .pytest_cache
info1: did you know that ... info1: did you know that ...
did you? did you?

View File

@ -732,7 +732,7 @@ Running this test will *skip* the invocation of ``data_set`` with value ``2``::
$ pytest test_fixture_marks.py -v $ pytest test_fixture_marks.py -v
=========================== test session starts ============================ =========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 3 items collecting ... collected 3 items
@ -775,7 +775,7 @@ Here we declare an ``app`` fixture which receives the previously defined
$ pytest -v test_appsetup.py $ pytest -v test_appsetup.py
=========================== test session starts ============================ =========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 2 items collecting ... collected 2 items
@ -844,7 +844,7 @@ Let's run the tests in verbose mode and with looking at the print-output::
$ pytest -v -s test_module.py $ pytest -v -s test_module.py
=========================== test session starts ============================ =========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 8 items collecting ... collected 8 items

View File

@ -114,6 +114,10 @@ Let's run this::
The one parameter set which caused a failure previously now The one parameter set which caused a failure previously now
shows up as an "xfailed (expected to fail)" test. shows up as an "xfailed (expected to fail)" test.
In case the values provided to ``parametrize`` result in an empty list - for
example, if they're dynamically generated by some function - the behaviour of
pytest is defined by the :confval:`empty_parameter_set_mark` option.
To get all combinations of multiple parametrized arguments you can stack To get all combinations of multiple parametrized arguments you can stack
``parametrize`` decorators:: ``parametrize`` decorators::

View File

@ -58,18 +58,20 @@ by calling the ``pytest.skip(reason)`` function:
if not valid_config(): if not valid_config():
pytest.skip("unsupported configuration") pytest.skip("unsupported configuration")
The imperative method is useful when it is not possible to evaluate the skip condition
during import time.
It is also possible to skip the whole module using It is also possible to skip the whole module using
``pytest.skip(reason, allow_module_level=True)`` at the module level: ``pytest.skip(reason, allow_module_level=True)`` at the module level:
.. code-block:: python .. code-block:: python
import sys
import pytest import pytest
if not pytest.config.getoption("--custom-flag"): if not sys.platform.startswith("win"):
pytest.skip("--custom-flag is missing, skipping tests", allow_module_level=True) pytest.skip("skipping windows-only tests", allow_module_level=True)
The imperative method is useful when it is not possible to evaluate the skip condition
during import time.
**Reference**: :ref:`pytest.mark.skip ref` **Reference**: :ref:`pytest.mark.skip ref`

View File

@ -11,11 +11,11 @@ The ``tmp_path`` fixture
.. versionadded:: 3.9 .. versionadded:: 3.9
You can use the ``tmpdir`` fixture which will You can use the ``tmp_path`` fixture which will
provide a temporary directory unique to the test invocation, provide a temporary directory unique to the test invocation,
created in the `base temporary directory`_. created in the `base temporary directory`_.
``tmpdir`` is a ``pathlib/pathlib2.Path`` object. Here is an example test usage: ``tmp_path`` is a ``pathlib/pathlib2.Path`` object. Here is an example test usage:
.. code-block:: python .. code-block:: python
@ -69,10 +69,10 @@ The ``tmp_path_factory`` fixture
.. versionadded:: 3.9 .. versionadded:: 3.9
The ``tmp_path_facotry`` is a session-scoped fixture which can be used The ``tmp_path_factory`` is a session-scoped fixture which can be used
to create arbitrary temporary directories from any other fixture or test. to create arbitrary temporary directories from any other fixture or test.
its intended to replace ``tmpdir_factory`` and returns :class:`pathlib.Path` instances. It is intended to replace ``tmpdir_factory``, and returns :class:`pathlib.Path` instances.
The 'tmpdir' fixture The 'tmpdir' fixture

View File

@ -420,17 +420,17 @@ additionally it is possible to copy examples for a example folder before running
============================= warnings summary ============================= ============================= warnings summary =============================
$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")
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.Class is deprecated, please use pytest.Class instead $PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:329: RemovedInPytest4Warning: usage of Session.Class is deprecated, please use pytest.Class instead
return getattr(object, name, default) return getattr(object, name, default)
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.File is deprecated, please use pytest.File instead $PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:329: RemovedInPytest4Warning: usage of Session.File is deprecated, please use pytest.File instead
return getattr(object, name, default) return getattr(object, name, default)
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.Function is deprecated, please use pytest.Function instead $PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:329: RemovedInPytest4Warning: usage of Session.Function is deprecated, please use pytest.Function instead
return getattr(object, name, default) return getattr(object, name, default)
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.Instance is deprecated, please use pytest.Instance instead $PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:329: RemovedInPytest4Warning: usage of Session.Instance is deprecated, please use pytest.Instance instead
return getattr(object, name, default) return getattr(object, name, default)
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.Item is deprecated, please use pytest.Item instead $PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:329: RemovedInPytest4Warning: usage of Session.Item is deprecated, please use pytest.Item instead
return getattr(object, name, default) return getattr(object, name, default)
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.Module is deprecated, please use pytest.Module instead $PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:329: RemovedInPytest4Warning: usage of Session.Module is deprecated, please use pytest.Module instead
return getattr(object, name, default) return getattr(object, name, default)
-- Docs: https://docs.pytest.org/en/latest/warnings.html -- Docs: https://docs.pytest.org/en/latest/warnings.html

View File

@ -706,10 +706,9 @@ class AssertionRewriter(ast.NodeVisitor):
setattr(node, name, new) setattr(node, name, new)
elif ( elif (
isinstance(field, ast.AST) isinstance(field, ast.AST)
and
# Don't recurse into expressions as they can't contain # Don't recurse into expressions as they can't contain
# asserts. # asserts.
not isinstance(field, ast.expr) and not isinstance(field, ast.expr)
): ):
nodes.append(field) nodes.append(field)

View File

@ -13,7 +13,7 @@ from contextlib import contextmanager
import py import py
import _pytest import _pytest
from _pytest.outcomes import TEST_OUTCOME from _pytest.outcomes import TEST_OUTCOME, fail
from six import text_type from six import text_type
import six import six
@ -131,9 +131,17 @@ def getfuncargnames(function, is_method=False, cls=None):
# ordered mapping of parameter names to Parameter instances. This # ordered mapping of parameter names to Parameter instances. This
# creates a tuple of the names of the parameters that don't have # creates a tuple of the names of the parameters that don't have
# defaults. # defaults.
try:
parameters = signature(function).parameters
except (ValueError, TypeError) as e:
fail(
"Could not determine arguments of {!r}: {}".format(function, e),
pytrace=False,
)
arg_names = tuple( arg_names = tuple(
p.name p.name
for p in signature(function).parameters.values() for p in parameters.values()
if ( if (
p.kind is Parameter.POSITIONAL_OR_KEYWORD p.kind is Parameter.POSITIONAL_OR_KEYWORD
or p.kind is Parameter.KEYWORD_ONLY or p.kind is Parameter.KEYWORD_ONLY

View File

@ -391,7 +391,7 @@ class PytestPluginManager(PluginManager):
# and allow users to opt into looking into the rootdir parent # and allow users to opt into looking into the rootdir parent
# directories instead of requiring to specify confcutdir # directories instead of requiring to specify confcutdir
clist = [] clist = []
for parent in directory.parts(): for parent in directory.realpath().parts():
if self._confcutdir and self._confcutdir.relto(parent): if self._confcutdir and self._confcutdir.relto(parent):
continue continue
conftestpath = parent.join("conftest.py") conftestpath = parent.join("conftest.py")

View File

@ -762,14 +762,19 @@ class FixtureLookupError(LookupError):
if msg is None: if msg is None:
fm = self.request._fixturemanager fm = self.request._fixturemanager
available = [] available = set()
parentid = self.request._pyfuncitem.parent.nodeid parentid = self.request._pyfuncitem.parent.nodeid
for name, fixturedefs in fm._arg2fixturedefs.items(): for name, fixturedefs in fm._arg2fixturedefs.items():
faclist = list(fm._matchfactories(fixturedefs, parentid)) faclist = list(fm._matchfactories(fixturedefs, parentid))
if faclist and name not in available: if faclist:
available.append(name) available.add(name)
msg = "fixture %r not found" % (self.argname,) if self.argname in available:
msg += "\n available fixtures: %s" % (", ".join(sorted(available)),) msg = " recursive dependency involving fixture '{}' detected".format(
self.argname
)
else:
msg = "fixture '{}' not found".format(self.argname)
msg += "\n available fixtures: {}".format(", ".join(sorted(available)))
msg += "\n use 'pytest --fixtures [testpath]' for help on them." msg += "\n use 'pytest --fixtures [testpath]' for help on them."
return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname) return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname)

View File

@ -497,6 +497,29 @@ class LoggingPlugin(object):
with self._runtest_for(None, "finish"): with self._runtest_for(None, "finish"):
yield yield
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_sessionfinish(self):
with self.live_logs_context():
if self.log_cli_handler:
self.log_cli_handler.set_when("sessionfinish")
if self.log_file_handler is not None:
with catching_logs(self.log_file_handler, level=self.log_file_level):
yield
else:
yield
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_sessionstart(self):
self._setup_cli_logging()
with self.live_logs_context():
if self.log_cli_handler:
self.log_cli_handler.set_when("sessionstart")
if self.log_file_handler is not None:
with catching_logs(self.log_file_handler, level=self.log_file_level):
yield
else:
yield
@pytest.hookimpl(hookwrapper=True) @pytest.hookimpl(hookwrapper=True)
def pytest_runtestloop(self, session): def pytest_runtestloop(self, session):
"""Runs all collected test items.""" """Runs all collected test items."""

View File

@ -237,8 +237,8 @@ def make_numbered_dir_with_cleanup(root, prefix, keep, lock_timeout):
p = make_numbered_dir(root, prefix) p = make_numbered_dir(root, prefix)
lock_path = create_cleanup_lock(p) lock_path = create_cleanup_lock(p)
register_cleanup_lock_removal(lock_path) register_cleanup_lock_removal(lock_path)
except Exception as e: except Exception as exc:
pass e = exc
else: else:
consider_lock_dead_if_created_before = p.stat().st_mtime - lock_timeout consider_lock_dead_if_created_before = p.stat().st_mtime - lock_timeout
cleanup_numbered_dir( cleanup_numbered_dir(

View File

@ -156,7 +156,20 @@ class WarningsRecorder(warnings.catch_warnings):
if six.PY2: if six.PY2:
def warn(*args, **kwargs): def warn(*args, **kwargs):
return self._saved_warn(*args, **kwargs) kwargs.setdefault("stacklevel", 1)
kwargs["stacklevel"] += 1
# emulate resetting the warn registry
f_globals = sys._getframe(kwargs["stacklevel"] - 1).f_globals
if "__warningregistry__" in f_globals:
orig = f_globals["__warningregistry__"]
f_globals["__warningregistry__"] = None
try:
return self._saved_warn(*args, **kwargs)
finally:
f_globals["__warningregistry__"] = orig
else:
return self._saved_warn(*args, **kwargs)
warnings.warn, self._saved_warn = warn, warnings.warn warnings.warn, self._saved_warn = warn, warnings.warn
return self return self

View File

@ -123,7 +123,7 @@ def warning_record_to_str(warning_message):
if unicode_warning: if unicode_warning:
warnings.warn( warnings.warn(
"Warning is using unicode non convertible to ascii, " "Warning is using unicode non convertible to ascii, "
"converting to a safe representation:\n %s" % msg, "converting to a safe representation:\n {!r}".format(compat.safe_str(msg)),
UnicodeWarning, UnicodeWarning,
) )
return msg return msg

View File

@ -0,0 +1,15 @@
import pytest
@pytest.fixture
def fix1(fix2):
return 1
@pytest.fixture
def fix2(fix1):
return 1
def test(fix1):
pass

View File

@ -966,3 +966,39 @@ def test_collection_logging_to_file(testdir):
assert "Normal message" in contents assert "Normal message" in contents
assert "debug message in test_simple" not in contents assert "debug message in test_simple" not in contents
assert "info message in test_simple" in contents assert "info message in test_simple" in contents
def test_log_in_hooks(testdir):
log_file = testdir.tmpdir.join("pytest.log").strpath
testdir.makeini(
"""
[pytest]
log_file={}
log_file_level = INFO
log_cli=true
""".format(
log_file
)
)
testdir.makeconftest(
"""
import logging
def pytest_runtestloop(session):
logging.info('runtestloop')
def pytest_sessionstart(session):
logging.info('sessionstart')
def pytest_sessionfinish(session, exitstatus):
logging.info('sessionfinish')
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*sessionstart*", "*runtestloop*", "*sessionfinish*"])
with open(log_file) as rfh:
contents = rfh.read()
assert "sessionstart" in contents
assert "runtestloop" in contents
assert "sessionfinish" in contents

View File

@ -60,6 +60,13 @@ class TestFillFixtures(object):
""" """
) )
def test_detect_recursive_dependency_error(self, testdir):
testdir.copy_example()
result = testdir.runpytest()
result.stdout.fnmatch_lines(
["*recursive dependency involving fixture 'fix1' detected*"]
)
def test_funcarg_basic(self, testdir): def test_funcarg_basic(self, testdir):
testdir.copy_example() testdir.copy_example()
item = testdir.getitem(Path("test_funcarg_basic.py")) item = testdir.getitem(Path("test_funcarg_basic.py"))

View File

@ -952,3 +952,23 @@ def test_collect_init_tests(testdir):
"*<Function 'test_foo'>", "*<Function 'test_foo'>",
] ]
) )
def test_collect_invalid_signature_message(testdir):
"""Check that we issue a proper message when we can't determine the signature of a test
function (#4026).
"""
testdir.makepyfile(
"""
import pytest
class TestCase:
@pytest.fixture
def fix():
pass
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(
["Could not determine arguments of *.fix *: invalid method signature"]
)

View File

@ -192,8 +192,10 @@ def test_conftest_confcutdir(testdir):
) )
def test_conftest_symlink(testdir): def test_conftest_symlink(testdir):
"""Ensure that conftest.py is used for resolved symlinks.""" """Ensure that conftest.py is used for resolved symlinks."""
realtests = testdir.tmpdir.mkdir("real").mkdir("app").mkdir("tests") real = testdir.tmpdir.mkdir("real")
realtests = real.mkdir("app").mkdir("tests")
testdir.tmpdir.join("symlinktests").mksymlinkto(realtests) testdir.tmpdir.join("symlinktests").mksymlinkto(realtests)
testdir.tmpdir.join("symlink").mksymlinkto(real)
testdir.makepyfile( testdir.makepyfile(
**{ **{
"real/app/tests/test_foo.py": "def test1(fixture): pass", "real/app/tests/test_foo.py": "def test1(fixture): pass",
@ -220,6 +222,10 @@ def test_conftest_symlink(testdir):
) )
assert result.ret == EXIT_OK assert result.ret == EXIT_OK
# Should not cause "ValueError: Plugin already registered" (#4174).
result = testdir.runpytest("-vs", "symlink")
assert result.ret == EXIT_OK
realtests.ensure("__init__.py") realtests.ensure("__init__.py")
result = testdir.runpytest("-vs", "symlinktests/test_foo.py::test1") result = testdir.runpytest("-vs", "symlinktests/test_foo.py::test1")
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(

View File

@ -6,6 +6,12 @@ import pytest
from _pytest.recwarn import WarningsRecorder from _pytest.recwarn import WarningsRecorder
def test_recwarn_stacklevel(recwarn):
warnings.warn("hello")
warn = recwarn.pop()
assert warn.filename == __file__
def test_recwarn_functional(testdir): def test_recwarn_functional(testdir):
testdir.makepyfile( testdir.makepyfile(
""" """

View File

@ -3,6 +3,8 @@ from __future__ import unicode_literals
import sys import sys
import six
import pytest import pytest
@ -562,3 +564,30 @@ class TestDeprecationWarningsByDefault:
monkeypatch.setenv(str("PYTHONWARNINGS"), str("once::UserWarning")) monkeypatch.setenv(str("PYTHONWARNINGS"), str("once::UserWarning"))
result = testdir.runpytest_subprocess() result = testdir.runpytest_subprocess()
assert WARNINGS_SUMMARY_HEADER not in result.stdout.str() assert WARNINGS_SUMMARY_HEADER not in result.stdout.str()
@pytest.mark.skipif(six.PY3, reason="Python 2 only issue")
def test_infinite_loop_warning_against_unicode_usage_py2(testdir):
"""
We need to be careful when raising the warning about unicode usage with "warnings.warn"
because it might be overwritten by users and this itself causes another warning (#3691).
"""
testdir.makepyfile(
"""
# -*- coding: utf8 -*-
from __future__ import unicode_literals
import warnings
import pytest
def _custom_showwarning(message, *a, **b):
return "WARNING: {}".format(message)
warnings.formatwarning = _custom_showwarning
@pytest.mark.filterwarnings("default")
def test_custom_warning_formatter():
warnings.warn("¥")
"""
)
result = testdir.runpytest_subprocess()
result.stdout.fnmatch_lines(["*1 passed, * warnings in*"])

View File

@ -18,7 +18,7 @@ envlist =
[testenv] [testenv]
commands = commands =
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest --lsof {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --lsof {posargs}
coverage: coverage combine coverage: coverage combine
coverage: coverage report coverage: coverage report
passenv = USER USERNAME COVERAGE_* TRAVIS passenv = USER USERNAME COVERAGE_* TRAVIS
@ -41,7 +41,7 @@ deps =
py27: mock py27: mock
nose nose
commands = commands =
pytest -n auto --runpytest=subprocess pytest -n auto --runpytest=subprocess {posargs}
[testenv:linting] [testenv:linting]
@ -58,7 +58,7 @@ deps =
hypothesis>=3.56 hypothesis>=3.56
{env:_PYTEST_TOX_EXTRA_DEP:} {env:_PYTEST_TOX_EXTRA_DEP:}
commands = commands =
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto {posargs}
[testenv:py36-xdist] [testenv:py36-xdist]
# NOTE: copied from above due to https://github.com/tox-dev/tox/issues/706. # NOTE: copied from above due to https://github.com/tox-dev/tox/issues/706.