diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e5cc56230..c3003ad43 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: additional_dependencies: [black==18.9b0] language_version: python3 - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v1.4.0-1 + rev: v2.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/.travis.yml b/.travis.yml index 00abca0b2..49c94b47e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,6 +47,11 @@ jobs: env: TOXENV=py37 before_install: - 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 unlink python - brew link python diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 14881e373..9b4c6355d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,6 +18,37 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 3.9.2 (2018-10-22) +========================= + +Bug Fixes +--------- + +- `#2909 `_: Improve error message when a recursive dependency between fixtures is detected. + + +- `#3340 `_: Fix logging messages not shown in hooks ``pytest_sessionstart()`` and ``pytest_sessionfinish()``. + + +- `#3533 `_: Fix unescaped XML raw objects in JUnit report for skipped tests + + +- `#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 `_: Improve error message when it is not possible to determine a function's signature. + + +- `#4177 `_: Pin ``setuptools>=40.0`` to support ``py_modules`` in ``setup.cfg`` + + +- `#4179 `_: Restore the tmpdir behaviour of symlinking the current test run. + + +- `#4192 `_: Fix filename reported by ``warnings.warn`` when using ``recwarn`` under python2. + + pytest 3.9.1 (2018-10-16) ========================= diff --git a/changelog/3533.bugfix.rst b/changelog/3533.bugfix.rst deleted file mode 100644 index 89f136458..000000000 --- a/changelog/3533.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix unescaped XML raw objects in JUnit report for skipped tests diff --git a/changelog/3851.doc.rst b/changelog/3851.doc.rst new file mode 100644 index 000000000..ec46126f1 --- /dev/null +++ b/changelog/3851.doc.rst @@ -0,0 +1 @@ +Add reference to ``empty_parameter_set_mark`` ini option in documentation of ``@pytest.mark.parametrize`` diff --git a/changelog/4174.bugfix.rst b/changelog/4174.bugfix.rst new file mode 100644 index 000000000..5e263c23a --- /dev/null +++ b/changelog/4174.bugfix.rst @@ -0,0 +1 @@ +Fix "ValueError: Plugin already registered" with conftest plugins via symlink. diff --git a/changelog/4177.bugfix.rst b/changelog/4177.bugfix.rst deleted file mode 100644 index b26ad4bad..000000000 --- a/changelog/4177.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Pin ``setuptools>=40.0`` to support ``py_modules`` in ``setup.cfg`` diff --git a/changelog/4179.bugfix.rst b/changelog/4179.bugfix.rst deleted file mode 100644 index 6f7467f50..000000000 --- a/changelog/4179.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Restore the tmpdir behaviour of symlinking the current test run. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index a692eee15..a39be6b17 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-3.9.2 release-3.9.1 release-3.9.0 release-3.8.2 diff --git a/doc/en/announce/release-3.9.2.rst b/doc/en/announce/release-3.9.2.rst new file mode 100644 index 000000000..1440831cb --- /dev/null +++ b/doc/en/announce/release-3.9.2.rst @@ -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 diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index a2cb8a676..cb6368a64 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -31,7 +31,7 @@ You can then restrict a test run to only run tests marked with ``webtest``:: $ pytest -v -m webtest =========================== 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 rootdir: $REGENDOC_TMPDIR, inifile: 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" =========================== 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 rootdir: $REGENDOC_TMPDIR, inifile: 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 =========================== 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 rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 1 item @@ -77,7 +77,7 @@ You can also select on the class:: $ pytest -v test_server.py::TestClass =========================== 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 rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 1 item @@ -90,7 +90,7 @@ Or select multiple nodes:: $ pytest -v test_server.py::TestClass test_server.py::test_send_http =========================== 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 rootdir: $REGENDOC_TMPDIR, inifile: 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 =========================== 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 rootdir: $REGENDOC_TMPDIR, inifile: 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 =========================== 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 rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items / 1 deselected @@ -156,7 +156,7 @@ Or to select "http" and "quick" tests:: $ pytest -k "http or quick" -v =========================== 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 rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items / 2 deselected diff --git a/doc/en/example/nonpython.rst b/doc/en/example/nonpython.rst index a266b5bfe..bda15065a 100644 --- a/doc/en/example/nonpython.rst +++ b/doc/en/example/nonpython.rst @@ -59,7 +59,7 @@ consulted when reporting in ``verbose`` mode:: nonpython $ pytest -v =========================== 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 rootdir: $REGENDOC_TMPDIR/nonpython, inifile: collecting ... collected 2 items diff --git a/doc/en/example/py2py3/test_py2.py b/doc/en/example/py2py3/test_py2.py index 664acf178..1f665086e 100644 --- a/doc/en/example/py2py3/test_py2.py +++ b/doc/en/example/py2py3/test_py2.py @@ -1,6 +1,5 @@ - def test_exception_syntax(): try: - 0/0 + 0 / 0 except ZeroDivisionError, e: - pass + assert e diff --git a/doc/en/example/py2py3/test_py3.py b/doc/en/example/py2py3/test_py3.py index baf0ffbd8..d95702a53 100644 --- a/doc/en/example/py2py3/test_py3.py +++ b/doc/en/example/py2py3/test_py3.py @@ -2,4 +2,4 @@ def test_exception_syntax(): try: 0 / 0 except ZeroDivisionError as e: - pass + assert e diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index df83ec97e..b16070287 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -357,7 +357,7 @@ which will add info only when run with "--v":: $ pytest -v =========================== 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 info1: did you know that ... did you? diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index fa111afa4..65664c0b2 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -732,7 +732,7 @@ Running this test will *skip* the invocation of ``data_set`` with value ``2``:: $ pytest test_fixture_marks.py -v =========================== 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 rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 3 items @@ -775,7 +775,7 @@ Here we declare an ``app`` fixture which receives the previously defined $ pytest -v test_appsetup.py =========================== 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 rootdir: $REGENDOC_TMPDIR, inifile: 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 =========================== 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 rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 8 items diff --git a/doc/en/parametrize.rst b/doc/en/parametrize.rst index 693cf1913..90ce4ffc6 100644 --- a/doc/en/parametrize.rst +++ b/doc/en/parametrize.rst @@ -114,6 +114,10 @@ Let's run this:: The one parameter set which caused a failure previously now 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 ``parametrize`` decorators:: diff --git a/doc/en/skipping.rst b/doc/en/skipping.rst index dabc8a90f..f705422d8 100644 --- a/doc/en/skipping.rst +++ b/doc/en/skipping.rst @@ -58,18 +58,20 @@ by calling the ``pytest.skip(reason)`` function: if not valid_config(): 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 ``pytest.skip(reason, allow_module_level=True)`` at the module level: .. code-block:: python + import sys import pytest - if not pytest.config.getoption("--custom-flag"): - pytest.skip("--custom-flag is missing, skipping tests", allow_module_level=True) + if not sys.platform.startswith("win"): + 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` diff --git a/doc/en/tmpdir.rst b/doc/en/tmpdir.rst index d8cd8b705..21bdcdd6a 100644 --- a/doc/en/tmpdir.rst +++ b/doc/en/tmpdir.rst @@ -11,11 +11,11 @@ The ``tmp_path`` fixture .. 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, 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 @@ -69,10 +69,10 @@ The ``tmp_path_factory`` fixture .. 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. -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 diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index 2cb1caefb..8ac316578 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -420,17 +420,17 @@ additionally it is possible to copy examples for a example folder before running ============================= warnings summary ============================= $REGENDOC_TMPDIR/test_example.py:4: PytestExperimentalApiWarning: testdir.copy_example is an experimental api that may change over time testdir.copy_example("test_example.py") - $PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py: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) - $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) - $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) - $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) - $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) - $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) -- Docs: https://docs.pytest.org/en/latest/warnings.html diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 5e76563d9..9b3daf9b4 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -706,10 +706,9 @@ class AssertionRewriter(ast.NodeVisitor): setattr(node, name, new) elif ( isinstance(field, ast.AST) - and # Don't recurse into expressions as they can't contain # asserts. - not isinstance(field, ast.expr) + and not isinstance(field, ast.expr) ): nodes.append(field) diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 0cf0e41c2..78bf1bc04 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -13,7 +13,7 @@ from contextlib import contextmanager import py import _pytest -from _pytest.outcomes import TEST_OUTCOME +from _pytest.outcomes import TEST_OUTCOME, fail from six import text_type import six @@ -131,9 +131,17 @@ def getfuncargnames(function, is_method=False, cls=None): # ordered mapping of parameter names to Parameter instances. This # creates a tuple of the names of the parameters that don't have # 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( p.name - for p in signature(function).parameters.values() + for p in parameters.values() if ( p.kind is Parameter.POSITIONAL_OR_KEYWORD or p.kind is Parameter.KEYWORD_ONLY diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 88cbf14ba..47e5c2945 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -391,7 +391,7 @@ class PytestPluginManager(PluginManager): # and allow users to opt into looking into the rootdir parent # directories instead of requiring to specify confcutdir clist = [] - for parent in directory.parts(): + for parent in directory.realpath().parts(): if self._confcutdir and self._confcutdir.relto(parent): continue conftestpath = parent.join("conftest.py") diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 565f8d061..582b4141a 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -762,14 +762,19 @@ class FixtureLookupError(LookupError): if msg is None: fm = self.request._fixturemanager - available = [] + available = set() parentid = self.request._pyfuncitem.parent.nodeid for name, fixturedefs in fm._arg2fixturedefs.items(): faclist = list(fm._matchfactories(fixturedefs, parentid)) - if faclist and name not in available: - available.append(name) - msg = "fixture %r not found" % (self.argname,) - msg += "\n available fixtures: %s" % (", ".join(sorted(available)),) + if faclist: + available.add(name) + if self.argname in 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." return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname) diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index ee6795250..8244c8030 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -497,6 +497,29 @@ class LoggingPlugin(object): with self._runtest_for(None, "finish"): 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) def pytest_runtestloop(self, session): """Runs all collected test items.""" diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index 8356a6f5c..ccf9b8dc7 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -237,8 +237,8 @@ def make_numbered_dir_with_cleanup(root, prefix, keep, lock_timeout): p = make_numbered_dir(root, prefix) lock_path = create_cleanup_lock(p) register_cleanup_lock_removal(lock_path) - except Exception as e: - pass + except Exception as exc: + e = exc else: consider_lock_dead_if_created_before = p.stat().st_mtime - lock_timeout cleanup_numbered_dir( diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index 62c9158fb..8738fe0b8 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -156,7 +156,20 @@ class WarningsRecorder(warnings.catch_warnings): if six.PY2: 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 return self diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py index 5574eee8e..e900ff067 100644 --- a/src/_pytest/warnings.py +++ b/src/_pytest/warnings.py @@ -123,7 +123,7 @@ def warning_record_to_str(warning_message): if unicode_warning: warnings.warn( "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, ) return msg diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py b/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py new file mode 100644 index 000000000..d1efcbb33 --- /dev/null +++ b/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py @@ -0,0 +1,15 @@ +import pytest + + +@pytest.fixture +def fix1(fix2): + return 1 + + +@pytest.fixture +def fix2(fix1): + return 1 + + +def test(fix1): + pass diff --git a/testing/logging/test_reporting.py b/testing/logging/test_reporting.py index 498b4c5bd..5863e0115 100644 --- a/testing/logging/test_reporting.py +++ b/testing/logging/test_reporting.py @@ -966,3 +966,39 @@ def test_collection_logging_to_file(testdir): assert "Normal message" in contents assert "debug message in test_simple" not 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 diff --git a/testing/python/fixture.py b/testing/python/fixture.py index f21f7a861..02304ac32 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -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): testdir.copy_example() item = testdir.getitem(Path("test_funcarg_basic.py")) diff --git a/testing/test_collection.py b/testing/test_collection.py index fb3860f99..9cd085778 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -952,3 +952,23 @@ def test_collect_init_tests(testdir): "*", ] ) + + +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"] + ) diff --git a/testing/test_conftest.py b/testing/test_conftest.py index a2df0ae37..44126613e 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -192,8 +192,10 @@ def test_conftest_confcutdir(testdir): ) def test_conftest_symlink(testdir): """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("symlink").mksymlinkto(real) testdir.makepyfile( **{ "real/app/tests/test_foo.py": "def test1(fixture): pass", @@ -220,6 +222,10 @@ def test_conftest_symlink(testdir): ) 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") result = testdir.runpytest("-vs", "symlinktests/test_foo.py::test1") result.stdout.fnmatch_lines( diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 3ae543248..e1d44f174 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -6,6 +6,12 @@ import pytest 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): testdir.makepyfile( """ diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 7825f2167..10eb5ea33 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -3,6 +3,8 @@ from __future__ import unicode_literals import sys +import six + import pytest @@ -562,3 +564,30 @@ class TestDeprecationWarningsByDefault: monkeypatch.setenv(str("PYTHONWARNINGS"), str("once::UserWarning")) result = testdir.runpytest_subprocess() 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*"]) diff --git a/tox.ini b/tox.ini index 86b3b9458..dbfd4eef5 100644 --- a/tox.ini +++ b/tox.ini @@ -18,7 +18,7 @@ envlist = [testenv] commands = - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --lsof + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --lsof {posargs} coverage: coverage combine coverage: coverage report passenv = USER USERNAME COVERAGE_* TRAVIS @@ -41,7 +41,7 @@ deps = py27: mock nose commands = - pytest -n auto --runpytest=subprocess + pytest -n auto --runpytest=subprocess {posargs} [testenv:linting] @@ -58,7 +58,7 @@ deps = hypothesis>=3.56 {env:_PYTEST_TOX_EXTRA_DEP:} commands = - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto {posargs} [testenv:py36-xdist] # NOTE: copied from above due to https://github.com/tox-dev/tox/issues/706.