Merge pull request #2442 from nicoddemus/merge-master-into-features
Merge master into features
This commit is contained in:
commit
4e6e29dbee
5
AUTHORS
5
AUTHORS
|
@ -13,8 +13,8 @@ Andreas Zeidler
|
||||||
Andrzej Ostrowski
|
Andrzej Ostrowski
|
||||||
Andy Freeland
|
Andy Freeland
|
||||||
Anthon van der Neut
|
Anthon van der Neut
|
||||||
Antony Lee
|
|
||||||
Anthony Sottile
|
Anthony Sottile
|
||||||
|
Antony Lee
|
||||||
Armin Rigo
|
Armin Rigo
|
||||||
Aron Curzon
|
Aron Curzon
|
||||||
Aviv Palivoda
|
Aviv Palivoda
|
||||||
|
@ -70,6 +70,7 @@ Grig Gheorghiu
|
||||||
Grigorii Eremeev (budulianin)
|
Grigorii Eremeev (budulianin)
|
||||||
Guido Wesdorp
|
Guido Wesdorp
|
||||||
Harald Armin Massa
|
Harald Armin Massa
|
||||||
|
Hui Wang (coldnight)
|
||||||
Ian Bicking
|
Ian Bicking
|
||||||
Jaap Broekhuizen
|
Jaap Broekhuizen
|
||||||
Jan Balster
|
Jan Balster
|
||||||
|
@ -158,8 +159,8 @@ Trevor Bekolay
|
||||||
Tyler Goodlet
|
Tyler Goodlet
|
||||||
Vasily Kuznetsov
|
Vasily Kuznetsov
|
||||||
Victor Uriarte
|
Victor Uriarte
|
||||||
Vlad Dragos
|
|
||||||
Vidar T. Fauske
|
Vidar T. Fauske
|
||||||
Vitaly Lashmanov
|
Vitaly Lashmanov
|
||||||
|
Vlad Dragos
|
||||||
Wouter van Ackooy
|
Wouter van Ackooy
|
||||||
Xuecong Liao
|
Xuecong Liao
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
3.1.1 (unreleased)
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fix encoding errors for unicode warnings in Python 2. (towncrier: 2436.bugfix)
|
||||||
|
|
||||||
|
* Fix issue with non-ascii contents in doctest text files. (towncrier: 2434.bugfix)
|
||||||
|
|
||||||
|
|
||||||
3.1.0 (2017-05-22)
|
3.1.0 (2017-05-22)
|
||||||
==================
|
==================
|
||||||
|
|
||||||
|
@ -5,11 +13,25 @@
|
||||||
New Features
|
New Features
|
||||||
------------
|
------------
|
||||||
|
|
||||||
* The ``pytest-warnings`` plugin has been integrated into the core, so now ``pytest`` automatically
|
* The ``pytest-warnings`` plugin has been integrated into the core and now ``pytest`` automatically
|
||||||
captures and displays warnings at the end of the test session.
|
captures and displays warnings at the end of the test session.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
This feature may disrupt test suites which apply and treat warnings themselves, and can be
|
||||||
|
disabled in your ``pytest.ini``:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[pytest]
|
||||||
|
addopts = -p no:warnings
|
||||||
|
|
||||||
|
See the `warnings documentation page <https://docs.pytest.org/en/latest/warnings.html>`_ for more
|
||||||
|
information.
|
||||||
|
|
||||||
Thanks `@nicoddemus`_ for the PR.
|
Thanks `@nicoddemus`_ for the PR.
|
||||||
|
|
||||||
* Added ``junit_suite_name`` ini option to specify root `<testsuite>` name for JUnit XML reports (`#533`_).
|
* Added ``junit_suite_name`` ini option to specify root ``<testsuite>`` name for JUnit XML reports (`#533`_).
|
||||||
|
|
||||||
* Added an ini option ``doctest_encoding`` to specify which encoding to use for doctest files.
|
* Added an ini option ``doctest_encoding`` to specify which encoding to use for doctest files.
|
||||||
Thanks `@wheerd`_ for the PR (`#2101`_).
|
Thanks `@wheerd`_ for the PR (`#2101`_).
|
||||||
|
@ -53,8 +75,6 @@ Changes
|
||||||
* ``--pdbcls`` no longer implies ``--pdb``. This makes it possible to use
|
* ``--pdbcls`` no longer implies ``--pdb``. This makes it possible to use
|
||||||
``addopts=--pdbcls=module.SomeClass`` on ``pytest.ini``. Thanks `@davidszotten`_ for
|
``addopts=--pdbcls=module.SomeClass`` on ``pytest.ini``. Thanks `@davidszotten`_ for
|
||||||
the PR (`#1952`_).
|
the PR (`#1952`_).
|
||||||
* Change exception raised by ``capture.DontReadFromInput.fileno()`` from ``ValueError``
|
|
||||||
to ``io.UnsupportedOperation``. Thanks `@vlad-dragos`_ for the PR.
|
|
||||||
|
|
||||||
* fix `#2013`_: turn RecordedWarning into ``namedtuple``,
|
* fix `#2013`_: turn RecordedWarning into ``namedtuple``,
|
||||||
to give it a comprehensible repr while preventing unwarranted modification.
|
to give it a comprehensible repr while preventing unwarranted modification.
|
||||||
|
@ -86,7 +106,7 @@ Changes
|
||||||
Thanks `@RonnyPfannschmidt`_ for the PR.
|
Thanks `@RonnyPfannschmidt`_ for the PR.
|
||||||
|
|
||||||
* fix `#2391`_: consider pytest_plugins on all plugin modules
|
* fix `#2391`_: consider pytest_plugins on all plugin modules
|
||||||
Thansks `@RonnyPfannschmidt`_ for the PR.
|
Thanks `@RonnyPfannschmidt`_ for the PR.
|
||||||
|
|
||||||
|
|
||||||
Bug Fixes
|
Bug Fixes
|
||||||
|
@ -98,7 +118,7 @@ Bug Fixes
|
||||||
|
|
||||||
* Change capture.py's ``DontReadFromInput`` class to throw ``io.UnsupportedOperation`` errors rather
|
* Change capture.py's ``DontReadFromInput`` class to throw ``io.UnsupportedOperation`` errors rather
|
||||||
than ValueErrors in the ``fileno`` method (`#2276`_).
|
than ValueErrors in the ``fileno`` method (`#2276`_).
|
||||||
Thanks `@metasyn`_ for the PR.
|
Thanks `@metasyn`_ and `@vlad-dragos`_ for the PR.
|
||||||
|
|
||||||
* Fix exception formatting while importing modules when the exception message
|
* Fix exception formatting while importing modules when the exception message
|
||||||
contains non-ascii characters (`#2336`_).
|
contains non-ascii characters (`#2336`_).
|
||||||
|
|
|
@ -219,7 +219,7 @@ def cacheshow(config, session):
|
||||||
basedir = config.cache._cachedir
|
basedir = config.cache._cachedir
|
||||||
vdir = basedir.join("v")
|
vdir = basedir.join("v")
|
||||||
tw.sep("-", "cache values")
|
tw.sep("-", "cache values")
|
||||||
for valpath in vdir.visit(lambda x: x.isfile()):
|
for valpath in sorted(vdir.visit(lambda x: x.isfile())):
|
||||||
key = valpath.relto(vdir).replace(valpath.sep, "/")
|
key = valpath.relto(vdir).replace(valpath.sep, "/")
|
||||||
val = config.cache.get(key, dummy)
|
val = config.cache.get(key, dummy)
|
||||||
if val is dummy:
|
if val is dummy:
|
||||||
|
@ -235,7 +235,7 @@ def cacheshow(config, session):
|
||||||
ddir = basedir.join("d")
|
ddir = basedir.join("d")
|
||||||
if ddir.isdir() and ddir.listdir():
|
if ddir.isdir() and ddir.listdir():
|
||||||
tw.sep("-", "cache directories")
|
tw.sep("-", "cache directories")
|
||||||
for p in basedir.join("d").visit():
|
for p in sorted(basedir.join("d").visit()):
|
||||||
#if p.check(dir=1):
|
#if p.check(dir=1):
|
||||||
# print("%s/" % p.relto(basedir))
|
# print("%s/" % p.relto(basedir))
|
||||||
if p.isfile():
|
if p.isfile():
|
||||||
|
|
|
@ -126,6 +126,7 @@ if _PY3:
|
||||||
import codecs
|
import codecs
|
||||||
imap = map
|
imap = map
|
||||||
STRING_TYPES = bytes, str
|
STRING_TYPES = bytes, str
|
||||||
|
UNICODE_TYPES = str,
|
||||||
|
|
||||||
def _escape_strings(val):
|
def _escape_strings(val):
|
||||||
"""If val is pure ascii, returns it as a str(). Otherwise, escapes
|
"""If val is pure ascii, returns it as a str(). Otherwise, escapes
|
||||||
|
@ -157,6 +158,7 @@ if _PY3:
|
||||||
return val.encode('unicode_escape').decode('ascii')
|
return val.encode('unicode_escape').decode('ascii')
|
||||||
else:
|
else:
|
||||||
STRING_TYPES = bytes, str, unicode
|
STRING_TYPES = bytes, str, unicode
|
||||||
|
UNICODE_TYPES = unicode,
|
||||||
|
|
||||||
from itertools import imap # NOQA
|
from itertools import imap # NOQA
|
||||||
|
|
||||||
|
|
|
@ -181,6 +181,7 @@ class DoctestTextfile(pytest.Module):
|
||||||
optionflags = get_optionflags(self)
|
optionflags = get_optionflags(self)
|
||||||
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
|
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
|
||||||
checker=_get_checker())
|
checker=_get_checker())
|
||||||
|
_fix_spoof_python2(runner, encoding)
|
||||||
|
|
||||||
parser = doctest.DocTestParser()
|
parser = doctest.DocTestParser()
|
||||||
test = parser.get_doctest(text, globs, name, filename, 0)
|
test = parser.get_doctest(text, globs, name, filename, 0)
|
||||||
|
@ -216,6 +217,10 @@ class DoctestModule(pytest.Module):
|
||||||
optionflags = get_optionflags(self)
|
optionflags = get_optionflags(self)
|
||||||
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
|
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
|
||||||
checker=_get_checker())
|
checker=_get_checker())
|
||||||
|
|
||||||
|
encoding = self.config.getini("doctest_encoding")
|
||||||
|
_fix_spoof_python2(runner, encoding)
|
||||||
|
|
||||||
for test in finder.find(module, module.__name__):
|
for test in finder.find(module, module.__name__):
|
||||||
if test.examples: # skip empty doctests
|
if test.examples: # skip empty doctests
|
||||||
yield DoctestItem(test.name, self, runner, test)
|
yield DoctestItem(test.name, self, runner, test)
|
||||||
|
@ -324,6 +329,30 @@ def _get_report_choice(key):
|
||||||
DOCTEST_REPORT_CHOICE_NONE: 0,
|
DOCTEST_REPORT_CHOICE_NONE: 0,
|
||||||
}[key]
|
}[key]
|
||||||
|
|
||||||
|
|
||||||
|
def _fix_spoof_python2(runner, encoding):
|
||||||
|
"""
|
||||||
|
Installs a "SpoofOut" into the given DebugRunner so it properly deals with unicode output.
|
||||||
|
|
||||||
|
This fixes the problem related in issue #2434.
|
||||||
|
"""
|
||||||
|
from _pytest.compat import _PY2
|
||||||
|
if not _PY2:
|
||||||
|
return
|
||||||
|
|
||||||
|
from doctest import _SpoofOut
|
||||||
|
|
||||||
|
class UnicodeSpoof(_SpoofOut):
|
||||||
|
|
||||||
|
def getvalue(self):
|
||||||
|
result = _SpoofOut.getvalue(self)
|
||||||
|
if encoding:
|
||||||
|
result = result.decode(encoding)
|
||||||
|
return result
|
||||||
|
|
||||||
|
runner._fakeout = UnicodeSpoof()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope='session')
|
||||||
def doctest_namespace():
|
def doctest_namespace():
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -5,6 +5,8 @@ from contextlib import contextmanager
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from _pytest import compat
|
||||||
|
|
||||||
|
|
||||||
def _setoption(wmod, arg):
|
def _setoption(wmod, arg):
|
||||||
"""
|
"""
|
||||||
|
@ -61,11 +63,24 @@ def catch_warnings_for_item(item):
|
||||||
yield
|
yield
|
||||||
|
|
||||||
for warning in log:
|
for warning in log:
|
||||||
|
warn_msg = warning.message
|
||||||
|
unicode_warning = False
|
||||||
|
|
||||||
|
if compat._PY2 and any(isinstance(m, compat.UNICODE_TYPES) for m in warn_msg.args):
|
||||||
|
warn_msg.args = [compat.safe_str(m) for m in warn_msg.args]
|
||||||
|
unicode_warning = True
|
||||||
|
|
||||||
msg = warnings.formatwarning(
|
msg = warnings.formatwarning(
|
||||||
warning.message, warning.category,
|
warn_msg, warning.category,
|
||||||
warning.filename, warning.lineno, warning.line)
|
warning.filename, warning.lineno, warning.line)
|
||||||
item.warn("unused", msg)
|
item.warn("unused", msg)
|
||||||
|
|
||||||
|
if unicode_warning:
|
||||||
|
warnings.warn(
|
||||||
|
"This warning %s is broken as it's message is not a str instance"
|
||||||
|
"(after all this is a stdlib problem workaround)" % msg,
|
||||||
|
UnicodeWarning)
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_runtest_protocol(item):
|
def pytest_runtest_protocol(item):
|
||||||
|
|
|
@ -231,10 +231,10 @@ You can always peek at the content of the cache using the
|
||||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||||
cachedir: $REGENDOC_TMPDIR/.cache
|
cachedir: $REGENDOC_TMPDIR/.cache
|
||||||
------------------------------- cache values -------------------------------
|
------------------------------- cache values -------------------------------
|
||||||
example/value contains:
|
|
||||||
42
|
|
||||||
cache/lastfailed contains:
|
cache/lastfailed contains:
|
||||||
{'test_caching.py::test_function': True}
|
{'test_caching.py::test_function': True}
|
||||||
|
example/value contains:
|
||||||
|
42
|
||||||
|
|
||||||
======= no tests ran in 0.12 seconds ========
|
======= no tests ran in 0.12 seconds ========
|
||||||
|
|
||||||
|
|
|
@ -116,6 +116,15 @@ the argument name::
|
||||||
diff = a - b
|
diff = a - b
|
||||||
assert diff == expected
|
assert diff == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("a,b,expected", [
|
||||||
|
pytest.param(datetime(2001, 12, 12), datetime(2001, 12, 11),
|
||||||
|
timedelta(1), id='forward'),
|
||||||
|
pytest.param(datetime(2001, 12, 11), datetime(2001, 12, 12),
|
||||||
|
timedelta(-1), id='backward'),
|
||||||
|
])
|
||||||
|
def test_timedistance_v3(a, b, expected):
|
||||||
|
diff = a - b
|
||||||
|
assert diff == expected
|
||||||
|
|
||||||
In ``test_timedistance_v0``, we let pytest generate the test IDs.
|
In ``test_timedistance_v0``, we let pytest generate the test IDs.
|
||||||
|
|
||||||
|
@ -132,7 +141,7 @@ objects, they are still using the default pytest representation::
|
||||||
======= test session starts ========
|
======= test session starts ========
|
||||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||||
collected 6 items
|
collected 8 items
|
||||||
<Module 'test_time.py'>
|
<Module 'test_time.py'>
|
||||||
<Function 'test_timedistance_v0[a0-b0-expected0]'>
|
<Function 'test_timedistance_v0[a0-b0-expected0]'>
|
||||||
<Function 'test_timedistance_v0[a1-b1-expected1]'>
|
<Function 'test_timedistance_v0[a1-b1-expected1]'>
|
||||||
|
@ -140,9 +149,14 @@ objects, they are still using the default pytest representation::
|
||||||
<Function 'test_timedistance_v1[backward]'>
|
<Function 'test_timedistance_v1[backward]'>
|
||||||
<Function 'test_timedistance_v2[20011212-20011211-expected0]'>
|
<Function 'test_timedistance_v2[20011212-20011211-expected0]'>
|
||||||
<Function 'test_timedistance_v2[20011211-20011212-expected1]'>
|
<Function 'test_timedistance_v2[20011211-20011212-expected1]'>
|
||||||
|
<Function 'test_timedistance_v3[forward]'>
|
||||||
|
<Function 'test_timedistance_v3[backward]'>
|
||||||
|
|
||||||
======= no tests ran in 0.12 seconds ========
|
======= no tests ran in 0.12 seconds ========
|
||||||
|
|
||||||
|
In ``test_timedistance_v3``, we used ``pytest.param`` to specify the test IDs
|
||||||
|
together with the actual data, instead of listing them separately.
|
||||||
|
|
||||||
A quick port of "testscenarios"
|
A quick port of "testscenarios"
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,20 @@ Warnings Capture
|
||||||
|
|
||||||
.. versionadded:: 3.1
|
.. versionadded:: 3.1
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
pytest captures all warnings between tests, which prevents custom warning
|
||||||
|
filters in existing test suites from working. If this causes problems to your test suite,
|
||||||
|
this plugin can be disabled in your ``pytest.ini`` file with:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[pytest]
|
||||||
|
addopts = -p no:warnings
|
||||||
|
|
||||||
|
There's an ongoing discussion about this on `#2430
|
||||||
|
<https://github.com/pytest-dev/pytest/issues/2430>`_.
|
||||||
|
|
||||||
|
|
||||||
Starting from version ``3.1``, pytest now automatically catches all warnings during test execution
|
Starting from version ``3.1``, pytest now automatically catches all warnings during test execution
|
||||||
and displays them at the end of the session::
|
and displays them at the end of the session::
|
||||||
|
|
||||||
|
|
|
@ -505,6 +505,28 @@ class TestDoctests(object):
|
||||||
"--junit-xml=junit.xml")
|
"--junit-xml=junit.xml")
|
||||||
reprec.assertoutcome(failed=1)
|
reprec.assertoutcome(failed=1)
|
||||||
|
|
||||||
|
def test_unicode_doctest(self, testdir):
|
||||||
|
"""
|
||||||
|
Test case for issue 2434: DecodeError on Python 2 when doctest contains non-ascii
|
||||||
|
characters.
|
||||||
|
"""
|
||||||
|
p = testdir.maketxtfile(test_unicode_doctest="""
|
||||||
|
.. doctest::
|
||||||
|
|
||||||
|
>>> print(
|
||||||
|
... "Hi\\n\\nByé")
|
||||||
|
Hi
|
||||||
|
...
|
||||||
|
Byé
|
||||||
|
>>> 1/0 # Byé
|
||||||
|
1
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest(p)
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
'*UNEXPECTED EXCEPTION: ZeroDivisionError*',
|
||||||
|
'*1 failed*',
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
class TestLiterals(object):
|
class TestLiterals(object):
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
# -*- coding: utf8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@ -106,3 +111,58 @@ def test_ignore(testdir, pyfile_with_warnings, method):
|
||||||
])
|
])
|
||||||
assert WARNINGS_SUMMARY_HEADER not in result.stdout.str()
|
assert WARNINGS_SUMMARY_HEADER not in result.stdout.str()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(sys.version_info < (3, 0),
|
||||||
|
reason='warnings message is unicode is ok in python3')
|
||||||
|
def test_unicode(testdir, pyfile_with_warnings):
|
||||||
|
testdir.makepyfile('''
|
||||||
|
# -*- coding: utf8 -*-
|
||||||
|
import warnings
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def fix():
|
||||||
|
warnings.warn(u"测试")
|
||||||
|
yield
|
||||||
|
|
||||||
|
def test_func(fix):
|
||||||
|
pass
|
||||||
|
''')
|
||||||
|
result = testdir.runpytest()
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
'*== %s ==*' % WARNINGS_SUMMARY_HEADER,
|
||||||
|
|
||||||
|
'*test_unicode.py:8: UserWarning: \u6d4b\u8bd5',
|
||||||
|
'*warnings.warn(u"\u6d4b\u8bd5")',
|
||||||
|
'* 1 passed, 1 warnings*',
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(sys.version_info >= (3, 0),
|
||||||
|
reason='warnings message is broken as it is not str instance')
|
||||||
|
def test_py2_unicode(testdir, pyfile_with_warnings):
|
||||||
|
testdir.makepyfile('''
|
||||||
|
# -*- coding: utf8 -*-
|
||||||
|
import warnings
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def fix():
|
||||||
|
warnings.warn(u"测试")
|
||||||
|
yield
|
||||||
|
|
||||||
|
def test_func(fix):
|
||||||
|
pass
|
||||||
|
''')
|
||||||
|
result = testdir.runpytest()
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
'*== %s ==*' % WARNINGS_SUMMARY_HEADER,
|
||||||
|
|
||||||
|
'*test_py2_unicode.py:8: UserWarning: \u6d4b\u8bd5',
|
||||||
|
'*warnings.warn(u"\u6d4b\u8bd5")',
|
||||||
|
'*warnings.py:82: UnicodeWarning: This warning*\u6d4b\u8bd5',
|
||||||
|
'* 1 passed, 2 warnings*',
|
||||||
|
])
|
||||||
|
|
Loading…
Reference in New Issue