Merge remote-tracking branch 'upstream/master' into merge-master-into-features

This commit is contained in:
Bruno Oliveira 2018-10-09 19:34:04 -03:00
commit 9a3836a0cf
38 changed files with 176 additions and 162 deletions

View File

@ -7,13 +7,13 @@ repos:
args: [--safe, --quiet]
language_version: python3
- repo: https://github.com/asottile/blacken-docs
rev: v0.2.0
rev: v0.3.0
hooks:
- id: blacken-docs
additional_dependencies: [black==18.6b4]
additional_dependencies: [black==18.9b0]
language_version: python3
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v1.3.0
rev: v1.4.0-1
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
@ -22,11 +22,12 @@ repos:
exclude: _pytest/debugging.py
- id: flake8
- repo: https://github.com/asottile/pyupgrade
rev: v1.2.0
rev: v1.8.0
hooks:
- id: pyupgrade
- id: pyupgrade
args: [--keep-percent-format]
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.0.0
rev: v1.1.0
hooks:
- id: rst-backticks
- repo: local

View File

@ -1,5 +1,3 @@
# 10000 iterations, just for relative comparison
# 2.7.5 3.3.2
# FilesCompleter 75.1109 69.2116

View File

@ -1,4 +1,3 @@
import pytest

View File

@ -0,0 +1 @@
Improve error message when test functions of ``unittest.TestCase`` subclasses use a parametrized fixture.

View File

@ -0,0 +1 @@
``request.fixturenames`` now correctly returns the name of fixtures created by ``request.getfixturevalue()``.

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

@ -0,0 +1 @@
Update fixture documentation to specify that a fixture can be invoked twice in the scope it's defined for.

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

@ -0,0 +1 @@
According to unittest.rst, setUpModule and tearDownModule were not implemented, but it turns out they are. So updated the documentation for unittest.

View File

@ -0,0 +1 @@
Fix source reindenting by using ``textwrap.dedent`` directly.

View File

@ -20,8 +20,7 @@ Thanks to all who contributed to this release, among them:
* Ondřej Súkup
* Ronny Pfannschmidt
* T.E.A de Souza
* Victor
* victor
* Victor Maryama
Happy testing,

View File

@ -22,10 +22,9 @@ Thanks to all who contributed to this release, among them:
* Ronny Pfannschmidt
* Sankt Petersbug
* Tyler Richard
* Victor
* Victor Maryama
* Vlad Shcherbina
* turturica
* victor
* wim glenn

View File

@ -245,9 +245,9 @@ class TestCustomAssertMsg(object):
a = 1
b = 2
assert (
A.a == b
), "A.a appears not to be b\n" "or does not appear to be b\none of those"
assert A.a == b, (
"A.a appears not to be b\n" "or does not appear to be b\none of those"
)
def test_custom_repr(self):
class JSON(object):

View File

@ -1,4 +1,3 @@
hello = "world"

View File

@ -1,4 +1,3 @@
import py
failure_demo = py.path.local(__file__).dirpath("failure_demo.py")

View File

@ -1,4 +1,3 @@
import pytest

View File

@ -852,6 +852,8 @@ In that order.
can be changed between releases (even bug fixes) so it shouldn't be relied on for scripting
or automation.
.. _freezing-pytest:
Freezing pytest
---------------

View File

@ -259,6 +259,11 @@ instance, you can simply declare it:
Finally, the ``class`` scope will invoke the fixture once per test *class*.
.. note::
Pytest will only cache one instance of a fixture at a time.
This means that when using a parametrized fixture, pytest may invoke a fixture more than once in the given scope.
``package`` scope (experimental)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -69,17 +69,15 @@ You may also discover more plugins through a `pytest- pypi.python.org search`_.
Requiring/Loading plugins in a test module or conftest file
-----------------------------------------------------------
You can require plugins in a test module or a conftest file like this::
You can require plugins in a test module or a conftest file like this:
pytest_plugins = "myapp.testsupport.myplugin",
.. code-block:: python
pytest_plugins = ("myapp.testsupport.myplugin",)
When the test module or conftest plugin is loaded the specified plugins
will be loaded as well.
pytest_plugins = "myapp.testsupport.myplugin"
which will import the specified module as a ``pytest`` plugin.
.. note::
Requiring plugins using a ``pytest_plugins`` variable in non-root
``conftest.py`` files is deprecated. See

View File

@ -84,6 +84,12 @@ pytest.warns
.. autofunction:: pytest.warns(expected_warning: Exception, [match])
:with:
pytest.freeze_includes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Tutorial**: :ref:`freezing-pytest`.
.. autofunction:: pytest.freeze_includes
.. _`marks ref`:

View File

@ -22,16 +22,15 @@ Almost all ``unittest`` features are supported:
* ``@unittest.skip`` style decorators;
* ``setUp/tearDown``;
* ``setUpClass/tearDownClass()``;
* ``setUpClass/tearDownClass``;
* ``setUpModule/tearDownModule``;
.. _`load_tests protocol`: https://docs.python.org/3/library/unittest.html#load-tests-protocol
.. _`setUpModule/tearDownModule`: https://docs.python.org/3/library/unittest.html#setupmodule-and-teardownmodule
.. _`subtests`: https://docs.python.org/3/library/unittest.html#distinguishing-test-iterations-using-subtests
Up to this point pytest does not have support for the following features:
* `load_tests protocol`_;
* `setUpModule/tearDownModule`_;
* `subtests`_;
Benefits out of the box

View File

@ -1,4 +1,3 @@
"""allow bash-completion for argparse with argcomplete if installed
needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail
to find the magic string, so _ARGCOMPLETE env. var is never set, and

View File

@ -7,6 +7,7 @@ import linecache
import sys
import six
import inspect
import textwrap
import tokenize
import py
@ -23,7 +24,6 @@ class Source(object):
def __init__(self, *parts, **kwargs):
self.lines = lines = []
de = kwargs.get("deindent", True)
rstrip = kwargs.get("rstrip", True)
for part in parts:
if not part:
partlines = []
@ -33,11 +33,6 @@ class Source(object):
partlines = [x.rstrip("\n") for x in part]
elif isinstance(part, six.string_types):
partlines = part.split("\n")
if rstrip:
while partlines:
if partlines[-1].strip():
break
partlines.pop()
else:
partlines = getsource(part, deindent=de).lines
if de:
@ -115,17 +110,10 @@ class Source(object):
ast, start, end = getstatementrange_ast(lineno, self)
return start, end
def deindent(self, offset=None):
""" return a new source object deindented by offset.
If offset is None then guess an indentation offset from
the first non-blank line. Subsequent lines which have a
lower indentation offset will be copied verbatim as
they are assumed to be part of multilines.
"""
# XXX maybe use the tokenizer to properly handle multiline
# strings etc.pp?
def deindent(self):
"""return a new source object deindented."""
newsource = Source()
newsource.lines[:] = deindent(self.lines, offset)
newsource.lines[:] = deindent(self.lines)
return newsource
def isparseable(self, deindent=True):
@ -268,47 +256,8 @@ def getsource(obj, **kwargs):
return Source(strsrc, **kwargs)
def deindent(lines, offset=None):
if offset is None:
for line in lines:
line = line.expandtabs()
s = line.lstrip()
if s:
offset = len(line) - len(s)
break
else:
offset = 0
if offset == 0:
return list(lines)
newlines = []
def readline_generator(lines):
for line in lines:
yield line + "\n"
it = readline_generator(lines)
try:
for _, _, (sline, _), (eline, _), _ in tokenize.generate_tokens(
lambda: next(it)
):
if sline > len(lines):
break # End of input reached
if sline > len(newlines):
line = lines[sline - 1].expandtabs()
if line.lstrip() and line[:offset].isspace():
line = line[offset:] # Deindent
newlines.append(line)
for i in range(sline, eline):
# Don't deindent continuing lines of
# multiline tokens (i.e. multiline strings)
newlines.append(lines[i])
except (IndentationError, tokenize.TokenError):
pass
# Add any lines we didn't see. E.g. if an exception was raised.
newlines.extend(lines[len(newlines) :])
return newlines
def deindent(lines):
return textwrap.dedent("\n".join(lines)).splitlines()
def get_statement_startend2(lineno, node):

View File

@ -359,8 +359,10 @@ class FixtureRequest(FuncargnamesCompatAttr):
@property
def fixturenames(self):
# backward incompatible note: now a readonly property
return list(self._pyfuncitem._fixtureinfo.names_closure)
"""names of all active fixtures in this request"""
result = list(self._pyfuncitem._fixtureinfo.names_closure)
result.extend(set(self._fixture_defs).difference(result))
return result
@property
def node(self):
@ -565,7 +567,20 @@ class FixtureRequest(FuncargnamesCompatAttr):
except (AttributeError, ValueError):
param = NOTSET
param_index = 0
if fixturedef.params is not None:
has_params = fixturedef.params is not None
fixtures_not_supported = getattr(funcitem, "nofuncargs", False)
if has_params and fixtures_not_supported:
msg = (
"{name} does not support fixtures, maybe unittest.TestCase subclass?\n"
"Node id: {nodeid}\n"
"Function type: {typename}"
).format(
name=funcitem.name,
nodeid=funcitem.nodeid,
typename=type(funcitem).__name__,
)
fail(msg)
if has_params:
frame = inspect.stack()[3]
frameinfo = inspect.getframeinfo(frame[0])
source_path = frameinfo.filename
@ -574,9 +589,11 @@ class FixtureRequest(FuncargnamesCompatAttr):
if source_path.relto(funcitem.config.rootdir):
source_path = source_path.relto(funcitem.config.rootdir)
msg = (
"The requested fixture has no parameter defined for the "
"current test.\n\nRequested fixture '{}' defined in:\n{}"
"The requested fixture has no parameter defined for test:\n"
" {}\n\n"
"Requested fixture '{}' defined in:\n{}"
"\n\nRequested here:\n{}:{}".format(
funcitem.nodeid,
fixturedef.argname,
getlocation(fixturedef.func, funcitem.config.rootdir),
source_path,

View File

@ -38,7 +38,7 @@ class Junit(py.xml.Namespace):
# this dynamically instead of hardcoding it. The spec range of valid
# chars is: Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
# | [#x10000-#x10FFFF]
_legal_chars = (0x09, 0x0A, 0x0d)
_legal_chars = (0x09, 0x0A, 0x0D)
_legal_ranges = ((0x20, 0x7E), (0x80, 0xD7FF), (0xE000, 0xFFFD), (0x10000, 0x10FFFF))
_legal_xml_re = [
unicode("%s-%s") % (unichr(low), unichr(high))

View File

@ -213,7 +213,8 @@ class LogCaptureFixture(object):
def __init__(self, item):
"""Creates a new funcarg."""
self._item = item
self._initial_log_levels = {} # type: Dict[str, int] # dict of log name -> log level
# dict of log name -> log level
self._initial_log_levels = {} # type: Dict[str, int]
def _finalize(self):
"""Finalizes the fixture.

View File

@ -90,7 +90,10 @@ class MarkEvaluator(object):
else:
if "reason" not in mark.kwargs:
# XXX better be checked at collection time
msg = "you need to specify reason=STRING " "when using booleans as conditions."
msg = (
"you need to specify reason=STRING "
"when using booleans as conditions."
)
fail(msg)
result = bool(expr)
if result:

View File

@ -31,6 +31,14 @@ failsonjython = pytest.mark.xfail("sys.platform.startswith('java')")
pytest_version_info = tuple(map(int, pytest.__version__.split(".")[:3]))
@pytest.fixture
def limited_recursion_depth():
before = sys.getrecursionlimit()
sys.setrecursionlimit(150)
yield
sys.setrecursionlimit(before)
class TWMock(object):
WRITE = object()
@ -239,7 +247,7 @@ class TestTraceback_f_g_h(object):
raise RuntimeError("hello")
f(n - 1)
excinfo = pytest.raises(RuntimeError, f, 100)
excinfo = pytest.raises(RuntimeError, f, 25)
monkeypatch.delattr(excinfo.traceback.__class__, "recursionindex")
repr = excinfo.getrepr()
assert "RuntimeError: hello" in str(repr.reprcrash)
@ -1341,11 +1349,13 @@ def test_cwd_deleted(testdir):
assert "INTERNALERROR" not in result.stdout.str() + result.stderr.str()
@pytest.mark.usefixtures("limited_recursion_depth")
def test_exception_repr_extraction_error_on_recursion():
"""
Ensure we can properly detect a recursion error even
if some locals raise error on comparison (#2459).
"""
from _pytest.pytester import LineMatcher
class numpy_like(object):
def __eq__(self, other):
@ -1361,40 +1371,30 @@ def test_exception_repr_extraction_error_on_recursion():
def b(x):
return a(numpy_like())
try:
with pytest.raises(RuntimeError) as excinfo:
a(numpy_like())
except: # noqa
from _pytest._code.code import ExceptionInfo
from _pytest.pytester import LineMatcher
exc_info = ExceptionInfo()
matcher = LineMatcher(str(exc_info.getrepr()).splitlines())
matcher.fnmatch_lines(
[
"!!! Recursion error detected, but an error occurred locating the origin of recursion.",
"*The following exception happened*",
"*ValueError: The truth value of an array*",
]
)
matcher = LineMatcher(str(excinfo.getrepr()).splitlines())
matcher.fnmatch_lines(
[
"!!! Recursion error detected, but an error occurred locating the origin of recursion.",
"*The following exception happened*",
"*ValueError: The truth value of an array*",
]
)
@pytest.mark.usefixtures("limited_recursion_depth")
def test_no_recursion_index_on_recursion_error():
"""
Ensure that we don't break in case we can't find the recursion index
during a recursion error (#2486).
"""
try:
class RecursionDepthError(object):
def __getattr__(self, attr):
return getattr(self, "_" + attr)
class RecursionDepthError(object):
def __getattr__(self, attr):
return getattr(self, "_" + attr)
with pytest.raises(RuntimeError) as excinfo:
RecursionDepthError().trigger
except: # noqa
from _pytest._code.code import ExceptionInfo
exc_info = ExceptionInfo()
assert "maximum recursion" in str(exc_info.getrepr())
else:
assert 0
assert "maximum recursion" in str(excinfo.getrepr())

View File

@ -27,16 +27,7 @@ def test_source_str_function():
x = Source(
"""
3
""",
rstrip=False,
)
assert str(x) == "\n3\n "
x = Source(
"""
3
""",
rstrip=True,
)
assert str(x) == "\n3"
@ -400,10 +391,13 @@ def test_getfuncsource_with_multine_string():
pass
"""
assert (
str(_pytest._code.Source(f)).strip()
== 'def f():\n c = """while True:\n pass\n"""'
)
expected = '''\
def f():
c = """while True:
pass
"""
'''
assert str(_pytest._code.Source(f)) == expected.rstrip()
def test_deindent():
@ -411,21 +405,13 @@ def test_deindent():
assert deindent(["\tfoo", "\tbar"]) == ["foo", "bar"]
def f():
c = """while True:
pass
"""
lines = deindent(inspect.getsource(f).splitlines())
assert lines == ["def f():", ' c = """while True:', " pass", '"""']
source = """
source = """\
def f():
def g():
pass
"""
lines = deindent(source.splitlines())
assert lines == ["", "def f():", " def g():", " pass", " "]
assert lines == ["def f():", " def g():", " pass"]
def test_source_of_class_at_eof_without_newline(tmpdir):

View File

@ -0,0 +1,20 @@
import pytest
@pytest.fixture
def dynamic():
pass
@pytest.fixture
def a(request):
request.getfixturevalue("dynamic")
@pytest.fixture
def b(a):
pass
def test(b, request):
assert request.fixturenames == ["b", "request", "a", "dynamic"]

View File

@ -1,4 +1,3 @@
import pytest
import pprint

View File

@ -0,0 +1,13 @@
import pytest
import unittest
@pytest.fixture(params=[1, 2])
def two(request):
return request.param
@pytest.mark.usefixtures("two")
class TestSomethingElse(unittest.TestCase):
def test_two(self):
pass

View File

@ -45,7 +45,7 @@ def test_change_level_undo(testdir):
assert 0
"""
)
result = testdir.runpytest_subprocess()
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*log from test1*", "*2 failed in *"])
assert "log from test2" not in result.stdout.str()

View File

@ -62,11 +62,11 @@ class TestApprox(object):
@pytest.mark.parametrize(
"value, repr_string",
[
(5., "approx(5.0 {pm} 5.0e-06)"),
([5.], "approx([5.0 {pm} 5.0e-06])"),
([[5.]], "approx([[5.0 {pm} 5.0e-06]])"),
([[5., 6.]], "approx([[5.0 {pm} 5.0e-06, 6.0 {pm} 6.0e-06]])"),
([[5.], [6.]], "approx([[5.0 {pm} 5.0e-06], [6.0 {pm} 6.0e-06]])"),
(5.0, "approx(5.0 {pm} 5.0e-06)"),
([5.0], "approx([5.0 {pm} 5.0e-06])"),
([[5.0]], "approx([[5.0 {pm} 5.0e-06]])"),
([[5.0, 6.0]], "approx([[5.0 {pm} 5.0e-06, 6.0 {pm} 6.0e-06]])"),
([[5.0], [6.0]], "approx([[5.0 {pm} 5.0e-06], [6.0 {pm} 6.0e-06]])"),
],
)
def test_repr_nd_array(self, plus_minus, value, repr_string):
@ -354,16 +354,16 @@ class TestApprox(object):
Test all permutations of where the approx and np.array() can show up
"""
np = pytest.importorskip("numpy")
expected = 100.
actual = 99.
expected = 100.0
actual = 99.0
abs_diff = expected - actual
rel_diff = (expected - actual) / expected
tests = [
(eq, abs_diff, 0),
(eq, 0, rel_diff),
(ne, 0, rel_diff / 2.), # rel diff fail
(ne, abs_diff / 2., 0), # abs diff fail
(ne, 0, rel_diff / 2.0), # rel diff fail
(ne, abs_diff / 2.0, 0), # abs diff fail
]
for op, _abs, _rel in tests:

View File

@ -756,6 +756,12 @@ class TestRequestBasic(object):
reprec = testdir.inline_run()
reprec.assertoutcome(passed=1)
def test_request_fixturenames_dynamic_fixture(self, testdir):
"""Regression test for #3057"""
testdir.copy_example("fixtures/test_getfixturevalue_dynamic.py")
result = testdir.runpytest()
result.stdout.fnmatch_lines("*1 passed*")
def test_funcargnames_compatattr(self, testdir):
testdir.makepyfile(
"""
@ -3602,7 +3608,8 @@ class TestParameterizedSubRequest(object):
result = testdir.runpytest()
result.stdout.fnmatch_lines(
"""
E*Failed: The requested fixture has no parameter defined for the current test.
E*Failed: The requested fixture has no parameter defined for test:
E* test_call_from_fixture.py::test_foo
E*
E*Requested fixture 'fix_with_param' defined in:
E*test_call_from_fixture.py:4
@ -3628,7 +3635,8 @@ class TestParameterizedSubRequest(object):
result = testdir.runpytest()
result.stdout.fnmatch_lines(
"""
E*Failed: The requested fixture has no parameter defined for the current test.
E*Failed: The requested fixture has no parameter defined for test:
E* test_call_from_test.py::test_foo
E*
E*Requested fixture 'fix_with_param' defined in:
E*test_call_from_test.py:4
@ -3658,7 +3666,8 @@ class TestParameterizedSubRequest(object):
result = testdir.runpytest()
result.stdout.fnmatch_lines(
"""
E*Failed: The requested fixture has no parameter defined for the current test.
E*Failed: The requested fixture has no parameter defined for test:
E* test_external_fixture.py::test_foo
E*
E*Requested fixture 'fix_with_param' defined in:
E*conftest.py:4
@ -3701,7 +3710,8 @@ class TestParameterizedSubRequest(object):
result = testdir.runpytest()
result.stdout.fnmatch_lines(
"""
E*Failed: The requested fixture has no parameter defined for the current test.
E*Failed: The requested fixture has no parameter defined for test:
E* test_foos.py::test_foo
E*
E*Requested fixture 'fix_with_param' defined in:
E*fix.py:4

View File

@ -177,7 +177,7 @@ def test_makepyfile_unicode(testdir):
unichr(65)
except NameError:
unichr = chr
testdir.makepyfile(unichr(0xfffd))
testdir.makepyfile(unichr(0xFFFD))
def test_makepyfile_utf8(testdir):

View File

@ -1010,3 +1010,15 @@ def test_testcase_handles_init_exceptions(testdir):
result = testdir.runpytest()
assert "should raise this exception" in result.stdout.str()
assert "ERROR at teardown of MyTestCase.test_hello" not in result.stdout.str()
def test_error_message_with_parametrized_fixtures(testdir):
testdir.copy_example("unittest/test_parametrized_fixture_error_message.py")
result = testdir.runpytest()
result.stdout.fnmatch_lines(
[
"*test_two does not support fixtures*",
"*TestSomethingElse::test_two",
"*Function type: TestCaseFunction",
]
)