Skip to content

Commit c64a8c9

Browse files
committed
Merge remote-tracking branch 'upstream/master' into merge-master-into-features
2 parents 5f97711 + 28aff05 commit c64a8c9

38 files changed

+457
-77
lines changed

AUTHORS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ Russel Winder
182182
Ryan Wooden
183183
Samuel Dion-Girardeau
184184
Samuele Pedroni
185+
Sankt Petersbug
185186
Segev Finer
186187
Serhii Mozghovyi
187188
Simon Gomizelj
@@ -205,6 +206,7 @@ Trevor Bekolay
205206
Tyler Goodlet
206207
Tzu-ping Chung
207208
Vasily Kuznetsov
209+
Victor Maryama
208210
Victor Uriarte
209211
Vidar T. Fauske
210212
Virgil Dupras

CHANGELOG.rst

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
=================
2+
Changelog history
3+
=================
4+
5+
Versions follow `Semantic Versioning <https://semver.org/>`_ (``<major>.<minor>.<patch>``).
6+
7+
Backward incompatible (breaking) changes will only be introduced in major versions
8+
with advance notice in the **Deprecations** section of releases.
9+
10+
111
..
212
You should *NOT* be adding new change log entries to this file, this
313
file is managed by towncrier. You *may* edit previous change logs to
@@ -8,6 +18,40 @@
818
919
.. towncrier release notes start
1020
21+
pytest 3.7.2 (2018-08-16)
22+
=========================
23+
24+
Bug Fixes
25+
---------
26+
27+
- `#3671 <https://github.com/pytest-dev/pytest/issues/3671>`_: Fix ``filterwarnings`` not being registered as a builtin mark.
28+
29+
30+
- `#3768 <https://github.com/pytest-dev/pytest/issues/3768>`_, `#3789 <https://github.com/pytest-dev/pytest/issues/3789>`_: Fix test collection from packages mixed with normal directories.
31+
32+
33+
- `#3771 <https://github.com/pytest-dev/pytest/issues/3771>`_: Fix infinite recursion during collection if a ``pytest_ignore_collect`` hook returns ``False`` instead of ``None``.
34+
35+
36+
- `#3774 <https://github.com/pytest-dev/pytest/issues/3774>`_: Fix bug where decorated fixtures would lose functionality (for example ``@mock.patch``).
37+
38+
39+
- `#3775 <https://github.com/pytest-dev/pytest/issues/3775>`_: Fix bug where importing modules or other objects with prefix ``pytest_`` prefix would raise a ``PluginValidationError``.
40+
41+
42+
- `#3788 <https://github.com/pytest-dev/pytest/issues/3788>`_: Fix ``AttributeError`` during teardown of ``TestCase`` subclasses which raise an exception during ``__init__``.
43+
44+
45+
- `#3804 <https://github.com/pytest-dev/pytest/issues/3804>`_: Fix traceback reporting for exceptions with ``__cause__`` cycles.
46+
47+
48+
49+
Improved Documentation
50+
----------------------
51+
52+
- `#3746 <https://github.com/pytest-dev/pytest/issues/3746>`_: Add documentation for ``metafunc.config`` that had been mistakenly hidden.
53+
54+
1155
pytest 3.7.1 (2018-08-02)
1256
=========================
1357

changelog/3819.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix ``stdout/stderr`` not getting captured when real-time cli logging is active.

changelog/3826.trivial.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Replace broken type annotations with type comments.

doc/en/announce/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Release announcements
66
:maxdepth: 2
77

88

9+
release-3.7.2
910
release-3.7.1
1011
release-3.7.0
1112
release-3.6.4

doc/en/announce/release-3.7.2.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
pytest-3.7.2
2+
=======================================
3+
4+
pytest 3.7.2 has just been released to PyPI.
5+
6+
This is a bug-fix release, being a drop-in replacement. To upgrade::
7+
8+
pip install --upgrade pytest
9+
10+
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
11+
12+
Thanks to all who contributed to this release, among them:
13+
14+
* Anthony Sottile
15+
* Bruno Oliveira
16+
* Daniel Hahler
17+
* Josh Holland
18+
* Ronny Pfannschmidt
19+
* Sankt Petersbug
20+
* Wes Thomas
21+
* turturica
22+
23+
24+
Happy testing,
25+
The pytest Development Team

doc/en/changelog.rst

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11

22
.. _changelog:
33

4-
Changelog history
5-
=================================
6-
74
.. include:: ../../CHANGELOG.rst

doc/en/example/markers.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,8 @@ You can ask which markers exist for your test suite - the list includes our just
200200
$ pytest --markers
201201
@pytest.mark.webtest: mark a test as a webtest.
202202

203+
@pytest.mark.filterwarnings(warning): add a warning filter to the given test. see http://pytest.org/latest/warnings.html#pytest-mark-filterwarnings
204+
203205
@pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test.
204206

205207
@pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html
@@ -374,6 +376,8 @@ The ``--markers`` option always gives you a list of available markers::
374376
$ pytest --markers
375377
@pytest.mark.env(name): mark test to run only on named environment
376378

379+
@pytest.mark.filterwarnings(warning): add a warning filter to the given test. see http://pytest.org/latest/warnings.html#pytest-mark-filterwarnings
380+
377381
@pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test.
378382

379383
@pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html

doc/en/example/nonpython.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,9 @@ interesting to just look at the collection tree::
8484
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
8585
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
8686
collected 2 items
87-
<YamlFile 'test_simple.yml'>
88-
<YamlItem 'hello'>
89-
<YamlItem 'ok'>
87+
<Package '$REGENDOC_TMPDIR/nonpython'>
88+
<YamlFile 'test_simple.yml'>
89+
<YamlItem 'hello'>
90+
<YamlItem 'ok'>
9091

9192
======================= no tests ran in 0.12 seconds =======================

doc/en/example/parametrize.rst

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -411,11 +411,10 @@ is to be run with different sets of arguments for its three arguments:
411411
Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (5 interpreters times 5 interpreters times 3 objects to serialize/deserialize)::
412412

413413
. $ pytest -rs -q multipython.py
414-
...ssssssssssssssssssssssss [100%]
414+
...sss...sssssssss...sss... [100%]
415415
========================= short test summary info ==========================
416-
SKIP [12] $REGENDOC_TMPDIR/CWD/multipython.py:28: 'python3.4' not found
417-
SKIP [12] $REGENDOC_TMPDIR/CWD/multipython.py:28: 'python3.5' not found
418-
3 passed, 24 skipped in 0.12 seconds
416+
SKIP [15] $REGENDOC_TMPDIR/CWD/multipython.py:28: 'python3.4' not found
417+
12 passed, 15 skipped in 0.12 seconds
419418

420419
Indirect parametrization of optional implementations/imports
421420
--------------------------------------------------------------------

src/_pytest/_code/code.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -719,7 +719,9 @@ def repr_excinfo(self, excinfo):
719719
repr_chain = []
720720
e = excinfo.value
721721
descr = None
722-
while e is not None:
722+
seen = set()
723+
while e is not None and id(e) not in seen:
724+
seen.add(id(e))
723725
if excinfo:
724726
reprtraceback = self.repr_traceback(excinfo)
725727
reprcrash = excinfo._getreprcrash()

src/_pytest/capture.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@
1414

1515
import six
1616
import pytest
17-
from _pytest.compat import CaptureIO
18-
17+
from _pytest.compat import CaptureIO, dummy_context_manager
1918

2019
patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"}
2120

@@ -85,6 +84,7 @@ class CaptureManager(object):
8584
def __init__(self, method):
8685
self._method = method
8786
self._global_capturing = None
87+
self._current_item = None
8888

8989
def _getcapture(self, method):
9090
if method == "fd":
@@ -121,6 +121,19 @@ def suspend_global_capture(self, item=None, in_=False):
121121
cap.suspend_capturing(in_=in_)
122122
return outerr
123123

124+
@contextlib.contextmanager
125+
def global_and_fixture_disabled(self):
126+
"""Context manager to temporarily disables global and current fixture capturing."""
127+
# Need to undo local capsys-et-al if exists before disabling global capture
128+
fixture = getattr(self._current_item, "_capture_fixture", None)
129+
ctx_manager = fixture._suspend() if fixture else dummy_context_manager()
130+
with ctx_manager:
131+
self.suspend_global_capture(item=None, in_=False)
132+
try:
133+
yield
134+
finally:
135+
self.resume_global_capture()
136+
124137
def activate_fixture(self, item):
125138
"""If the current item is using ``capsys`` or ``capfd``, activate them so they take precedence over
126139
the global capture.
@@ -151,28 +164,34 @@ def pytest_make_collect_report(self, collector):
151164

152165
@pytest.hookimpl(hookwrapper=True)
153166
def pytest_runtest_setup(self, item):
167+
self._current_item = item
154168
self.resume_global_capture()
155169
# no need to activate a capture fixture because they activate themselves during creation; this
156170
# only makes sense when a fixture uses a capture fixture, otherwise the capture fixture will
157171
# be activated during pytest_runtest_call
158172
yield
159173
self.suspend_capture_item(item, "setup")
174+
self._current_item = None
160175

161176
@pytest.hookimpl(hookwrapper=True)
162177
def pytest_runtest_call(self, item):
178+
self._current_item = item
163179
self.resume_global_capture()
164180
# it is important to activate this fixture during the call phase so it overwrites the "global"
165181
# capture
166182
self.activate_fixture(item)
167183
yield
168184
self.suspend_capture_item(item, "call")
185+
self._current_item = None
169186

170187
@pytest.hookimpl(hookwrapper=True)
171188
def pytest_runtest_teardown(self, item):
189+
self._current_item = item
172190
self.resume_global_capture()
173191
self.activate_fixture(item)
174192
yield
175193
self.suspend_capture_item(item, "teardown")
194+
self._current_item = None
176195

177196
@pytest.hookimpl(tryfirst=True)
178197
def pytest_keyboard_interrupt(self, excinfo):
@@ -314,17 +333,21 @@ def readouterr(self):
314333
return self._outerr
315334

316335
@contextlib.contextmanager
317-
def disabled(self):
318-
"""Temporarily disables capture while inside the 'with' block."""
336+
def _suspend(self):
337+
"""Suspends this fixture's own capturing temporarily."""
319338
self._capture.suspend_capturing()
320-
capmanager = self.request.config.pluginmanager.getplugin("capturemanager")
321-
capmanager.suspend_global_capture(item=None, in_=False)
322339
try:
323340
yield
324341
finally:
325-
capmanager.resume_global_capture()
326342
self._capture.resume_capturing()
327343

344+
@contextlib.contextmanager
345+
def disabled(self):
346+
"""Temporarily disables capture while inside the 'with' block."""
347+
capmanager = self.request.config.pluginmanager.getplugin("capturemanager")
348+
with capmanager.global_and_fixture_disabled():
349+
yield
350+
328351

329352
def safe_text_dupfile(f, mode, default_encoding="UTF8"):
330353
""" return an open text file object that's a duplicate of f on the

src/_pytest/compat.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import inspect
99
import re
1010
import sys
11+
from contextlib import contextmanager
1112

1213
import py
1314

@@ -151,6 +152,13 @@ def getfuncargnames(function, is_method=False, cls=None):
151152
return arg_names
152153

153154

155+
@contextmanager
156+
def dummy_context_manager():
157+
"""Context manager that does nothing, useful in situations where you might need an actual context manager or not
158+
depending on some condition. Using this allow to keep the same code"""
159+
yield
160+
161+
154162
def get_default_arg_names(function):
155163
# Note: this code intentionally mirrors the code at the beginning of getfuncargnames,
156164
# to get the arguments which were excluded from its result because they had default values
@@ -228,12 +236,31 @@ def ascii_escaped(val):
228236
return val.encode("unicode-escape")
229237

230238

239+
class _PytestWrapper(object):
240+
"""Dummy wrapper around a function object for internal use only.
241+
242+
Used to correctly unwrap the underlying function object
243+
when we are creating fixtures, because we wrap the function object ourselves with a decorator
244+
to issue warnings when the fixture function is called directly.
245+
"""
246+
247+
def __init__(self, obj):
248+
self.obj = obj
249+
250+
231251
def get_real_func(obj):
232252
""" gets the real function object of the (possibly) wrapped object by
233253
functools.wraps or functools.partial.
234254
"""
235255
start_obj = obj
236256
for i in range(100):
257+
# __pytest_wrapped__ is set by @pytest.fixture when wrapping the fixture function
258+
# to trigger a warning if it gets called directly instead of by pytest: we don't
259+
# want to unwrap further than this otherwise we lose useful wrappings like @mock.patch (#3774)
260+
new_obj = getattr(obj, "__pytest_wrapped__", None)
261+
if isinstance(new_obj, _PytestWrapper):
262+
obj = new_obj.obj
263+
break
237264
new_obj = getattr(obj, "__wrapped__", None)
238265
if new_obj is None:
239266
break

src/_pytest/config/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
""" command line options, ini-file and conftest.py processing. """
22
from __future__ import absolute_import, division, print_function
33
import argparse
4+
import inspect
45
import shlex
56
import traceback
67
import types
@@ -252,6 +253,10 @@ def parse_hookimpl_opts(self, plugin, name):
252253
method = getattr(plugin, name)
253254
opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name)
254255

256+
# consider only actual functions for hooks (#3775)
257+
if not inspect.isroutine(method):
258+
return
259+
255260
# collect unmarked hooks as long as they have the `pytest_' prefix
256261
if opts is None and name.startswith("pytest_"):
257262
opts = {}

src/_pytest/config/argparsing.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -174,23 +174,23 @@ def __init__(self, *names, **attrs):
174174
if isinstance(typ, six.string_types):
175175
if typ == "choice":
176176
warnings.warn(
177-
"type argument to addoption() is a string %r."
178-
" For parsearg this is optional and when supplied"
179-
" should be a type."
177+
"`type` argument to addoption() is the string %r."
178+
" For choices this is optional and can be omitted, "
179+
" but when supplied should be a type (for example `str` or `int`)."
180180
" (options: %s)" % (typ, names),
181181
DeprecationWarning,
182-
stacklevel=3,
182+
stacklevel=4,
183183
)
184184
# argparse expects a type here take it from
185185
# the type of the first element
186186
attrs["type"] = type(attrs["choices"][0])
187187
else:
188188
warnings.warn(
189-
"type argument to addoption() is a string %r."
190-
" For parsearg this should be a type."
189+
"`type` argument to addoption() is the string %r, "
190+
" but when supplied should be a type (for example `str` or `int`)."
191191
" (options: %s)" % (typ, names),
192192
DeprecationWarning,
193-
stacklevel=3,
193+
stacklevel=4,
194194
)
195195
attrs["type"] = Argument._typ_map[typ]
196196
# used in test_parseopt -> test_parse_defaultgetter

0 commit comments

Comments
 (0)