Merge branch 'master' into 'features'

This commit is contained in:
Bruno Oliveira 2016-01-22 18:32:45 -02:00
commit 199fcf93d4
24 changed files with 244 additions and 48 deletions

View File

@ -37,6 +37,7 @@ Erik M. Bray
Florian Bruhin Florian Bruhin
Floris Bruynooghe Floris Bruynooghe
Gabriel Reis Gabriel Reis
Georgy Dyuldin
Graham Horler Graham Horler
Grig Gheorghiu Grig Gheorghiu
Guido Wesdorp Guido Wesdorp
@ -77,3 +78,4 @@ Simon Gomizelj
Russel Winder Russel Winder
Ben Webb Ben Webb
Alexei Kozlenok Alexei Kozlenok
Cal Leeming

View File

@ -51,8 +51,12 @@
.. _@The-Compiler: https://github.com/The-Compiler .. _@The-Compiler: https://github.com/The-Compiler
2.8.6.dev1 2.8.7.dev1
========== ----------
2.8.6
-----
- fix #1259: allow for double nodeids in junitxml, - fix #1259: allow for double nodeids in junitxml,
this was a regression failing plugins combinations this was a regression failing plugins combinations
@ -68,10 +72,20 @@
- fix #1292: monkeypatch calls (setattr, setenv, etc.) are now O(1). - fix #1292: monkeypatch calls (setattr, setenv, etc.) are now O(1).
Thanks David R. MacIver for the report and Bruno Oliveira for the PR. Thanks David R. MacIver for the report and Bruno Oliveira for the PR.
- fix #1223: captured stdout and stderr are now properly displayed before
entering pdb when ``--pdb`` is used instead of being thrown away.
Thanks Cal Leeming for the PR.
- fix #1305: pytest warnings emitted during ``pytest_terminal_summary`` are now - fix #1305: pytest warnings emitted during ``pytest_terminal_summary`` are now
properly displayed. properly displayed.
Thanks Ionel Maries Cristian for the report and Bruno Oliveira for the PR. Thanks Ionel Maries Cristian for the report and Bruno Oliveira for the PR.
- fix #628: fixed internal UnicodeDecodeError when doctests contain unicode.
Thanks Jason R. Coombs for the report and Bruno Oliveira for the PR.
- fix #1334: Add captured stdout to jUnit XML report on setup error.
Thanks Georgy Dyuldin for the PR.
2.8.5 2.8.5
===== =====

View File

@ -27,10 +27,6 @@ Note: this assumes you have already registered on pypi.
devpi list pytest devpi list pytest
or look at failures with "devpi list -f pytest". or look at failures with "devpi list -f pytest".
There will be some failed environments like e.g. the py33-trial
or py27-pexpect tox environments on Win32 platforms
which is ok (tox does not support skipping on
per-platform basis yet).
7. Regenerate the docs examples using tox, and check for regressions:: 7. Regenerate the docs examples using tox, and check for regressions::

View File

@ -1,4 +1,4 @@
.. image:: doc/en/img/pytest1.png .. image:: http://pytest.org/latest/_static/pytest1.png
:target: http://pytest.org :target: http://pytest.org
:align: center :align: center
:alt: pytest :alt: pytest
@ -60,7 +60,7 @@ Features
- Detailed info on failing `assert statements <http://pytest.org/latest/assert.html>`_ (no need to remember ``self.assert*`` names); - Detailed info on failing `assert statements <http://pytest.org/latest/assert.html>`_ (no need to remember ``self.assert*`` names);
- `Auto-discovery - `Auto-discovery
<http://pytest.org/latest/goodpractises.html#python-test-discovery>`_ <http://pytest.org/latest/goodpractices.html#python-test-discovery>`_
of test modules and functions; of test modules and functions;
- `Modular fixtures <http://pytest.org/latest/fixture.html>`_ for - `Modular fixtures <http://pytest.org/latest/fixture.html>`_ for

View File

@ -120,9 +120,9 @@ def pytest_runtest_setup(item):
config=item.config, op=op, left=left, right=right) config=item.config, op=op, left=left, right=right)
for new_expl in hook_result: for new_expl in hook_result:
if new_expl: if new_expl:
if (sum(len(p) for p in new_expl[1:]) > 80*8 if (sum(len(p) for p in new_expl[1:]) > 80*8 and
and item.config.option.verbose < 2 item.config.option.verbose < 2 and
and not _running_on_ci()): not _running_on_ci()):
show_max = 10 show_max = 10
truncated_lines = len(new_expl) - show_max truncated_lines = len(new_expl) - show_max
new_expl[show_max:] = [py.builtin._totext( new_expl[show_max:] = [py.builtin._totext(

View File

@ -140,8 +140,8 @@ def assertrepr_compare(config, op, left, right):
summary = u('%s %s %s') % (ecu(left_repr), op, ecu(right_repr)) summary = u('%s %s %s') % (ecu(left_repr), op, ecu(right_repr))
issequence = lambda x: (isinstance(x, (list, tuple, Sequence)) issequence = lambda x: (isinstance(x, (list, tuple, Sequence)) and
and not isinstance(x, basestring)) not isinstance(x, basestring))
istext = lambda x: isinstance(x, basestring) istext = lambda x: isinstance(x, basestring)
isdict = lambda x: isinstance(x, dict) isdict = lambda x: isinstance(x, dict)
isset = lambda x: isinstance(x, (set, frozenset)) isset = lambda x: isinstance(x, (set, frozenset))
@ -263,8 +263,7 @@ def _compare_eq_sequence(left, right, verbose=False):
explanation += [ explanation += [
u('Right contains more items, first extra item: %s') % u('Right contains more items, first extra item: %s') %
py.io.saferepr(right[len(left)],)] py.io.saferepr(right[len(left)],)]
return explanation # + _diff_text(pprint.pformat(left), return explanation
# pprint.pformat(right))
def _compare_eq_set(left, right, verbose=False): def _compare_eq_set(left, right, verbose=False):

View File

@ -90,15 +90,15 @@ class DoctestItem(pytest.Item):
reprlocation = ReprFileLocation(filename, lineno, message) reprlocation = ReprFileLocation(filename, lineno, message)
checker = _get_checker() checker = _get_checker()
REPORT_UDIFF = doctest.REPORT_UDIFF REPORT_UDIFF = doctest.REPORT_UDIFF
filelines = py.path.local(filename).readlines(cr=0)
lines = []
if lineno is not None: if lineno is not None:
i = max(test.lineno, max(0, lineno - 10)) # XXX? lines = doctestfailure.test.docstring.splitlines(False)
for line in filelines[i:lineno]: # add line numbers to the left of the error message
lines.append("%03d %s" % (i+1, line)) lines = ["%03d %s" % (i + test.lineno + 1, x)
i += 1 for (i, x) in enumerate(lines)]
# trim docstring error lines to 10
lines = lines[example.lineno - 9:example.lineno + 1]
else: else:
lines.append('EXAMPLE LOCATION UNKNOWN, not showing all tests of that example') lines = ['EXAMPLE LOCATION UNKNOWN, not showing all tests of that example']
indent = '>>>' indent = '>>>'
for line in example.source.splitlines(): for line in example.source.splitlines():
lines.append('??? %s %s' % (indent, line)) lines.append('??? %s %s' % (indent, line))

View File

@ -163,6 +163,7 @@ class _NodeReporter(object):
def append_error(self, report): def append_error(self, report):
self._add_simple( self._add_simple(
Junit.error, "test setup failure", report.longrepr) Junit.error, "test setup failure", report.longrepr)
self._write_captured_output(report)
def append_skipped(self, report): def append_skipped(self, report):
if hasattr(report, "wasxfail"): if hasattr(report, "wasxfail"):

View File

@ -52,7 +52,9 @@ class PdbInvoke:
def pytest_exception_interact(self, node, call, report): def pytest_exception_interact(self, node, call, report):
capman = node.config.pluginmanager.getplugin("capturemanager") capman = node.config.pluginmanager.getplugin("capturemanager")
if capman: if capman:
capman.suspendcapture(in_=True) out, err = capman.suspendcapture(in_=True)
sys.stdout.write(out)
sys.stdout.write(err)
_enter_pdb(node, call.excinfo, report) _enter_pdb(node, call.excinfo, report)
def pytest_internalerror(self, excrepr, excinfo): def pytest_internalerror(self, excrepr, excinfo):

View File

@ -416,8 +416,8 @@ class PyCollector(PyobjMixin, pytest.Collector):
def istestfunction(self, obj, name): def istestfunction(self, obj, name):
return ( return (
(self.funcnamefilter(name) or self.isnosetest(obj)) (self.funcnamefilter(name) or self.isnosetest(obj)) and
and safe_getattr(obj, "__call__", False) and getfixturemarker(obj) is None safe_getattr(obj, "__call__", False) and getfixturemarker(obj) is None
) )
def istestclass(self, obj, name): def istestclass(self, obj, name):
@ -1765,8 +1765,10 @@ class FixtureLookupError(LookupError):
stack.extend(map(lambda x: x.func, self.fixturestack)) stack.extend(map(lambda x: x.func, self.fixturestack))
msg = self.msg msg = self.msg
if msg is not None: if msg is not None:
stack = stack[:-1] # the last fixture raise an error, let's present # the last fixture raise an error, let's present
# it at the requesting side # it at the requesting side
stack = stack[:-1]
for function in stack: for function in stack:
fspath, lineno = getfslineno(function) fspath, lineno = getfslineno(function)
try: try:

View File

@ -24,9 +24,10 @@ def pytest_pycollect_makeitem(collector, name, obj):
class UnitTestCase(pytest.Class): class UnitTestCase(pytest.Class):
nofuncargs = True # marker for fixturemanger.getfixtureinfo() # marker for fixturemanger.getfixtureinfo()
# to declare that our children do not support funcargs # to declare that our children do not support funcargs
# nofuncargs = True
def setup(self): def setup(self):
cls = self.obj cls = self.obj
if getattr(cls, '__unittest_skip__', False): if getattr(cls, '__unittest_skip__', False):

View File

@ -6,6 +6,7 @@ Release announcements
:maxdepth: 2 :maxdepth: 2
release-2.8.6
release-2.8.5 release-2.8.5
release-2.8.4 release-2.8.4
release-2.8.3 release-2.8.3

View File

@ -0,0 +1,67 @@
pytest-2.8.6
============
pytest is a mature Python testing tool with more than a 1100 tests
against itself, passing on many different interpreters and platforms.
This release is supposed to be drop-in compatible to 2.8.5.
See below for the changes and see docs at:
http://pytest.org
As usual, you can upgrade from pypi via::
pip install -U pytest
Thanks to all who contributed to this release, among them:
AMiT Kumar
Bruno Oliveira
Erik M. Bray
Florian Bruhin
Georgy Dyuldin
Jeff Widman
Kartik Singhal
Loïc Estève
Manu Phatak
Peter Demin
Rick van Hattem
Ronny Pfannschmidt
Ulrich Petri
foxx
Happy testing,
The py.test Development Team
2.8.6 (compared to 2.8.5)
-------------------------
- fix #1259: allow for double nodeids in junitxml,
this was a regression failing plugins combinations
like pytest-pep8 + pytest-flakes
- Workaround for exception that occurs in pyreadline when using
``--pdb`` with standard I/O capture enabled.
Thanks Erik M. Bray for the PR.
- fix #900: Better error message in case the target of a ``monkeypatch`` call
raises an ``ImportError``.
- fix #1292: monkeypatch calls (setattr, setenv, etc.) are now O(1).
Thanks David R. MacIver for the report and Bruno Oliveira for the PR.
- fix #1223: captured stdout and stderr are now properly displayed before
entering pdb when ``--pdb`` is used instead of being thrown away.
Thanks Cal Leeming for the PR.
- fix #1305: pytest warnings emitted during ``pytest_terminal_summary`` are now
properly displayed.
Thanks Ionel Maries Cristian for the report and Bruno Oliveira for the PR.
- fix #628: fixed internal UnicodeDecodeError when doctests contain unicode.
Thanks Jason R. Coombs for the report and Bruno Oliveira for the PR.
- fix #1334: Add captured stdout to jUnit XML report on setup error.
Thanks Georgy Dyuldin for the PR.

View File

@ -193,7 +193,7 @@ Where to go next
Here are a few suggestions where to go next: Here are a few suggestions where to go next:
* :ref:`cmdline` for command line invocation examples * :ref:`cmdline` for command line invocation examples
* :ref:`good practises <goodpractises>` for virtualenv, test layout, genscript support * :ref:`good practices <goodpractices>` for virtualenv, test layout, genscript support
* :ref:`fixtures` for providing a functional baseline to your tests * :ref:`fixtures` for providing a functional baseline to your tests
* :ref:`apiref` for documentation and examples on using ``pytest`` * :ref:`apiref` for documentation and examples on using ``pytest``
* :ref:`plugins` managing and writing plugins * :ref:`plugins` managing and writing plugins

View File

@ -1,5 +1,5 @@
.. highlightlang:: python .. highlightlang:: python
.. _`goodpractises`: .. _`goodpractices`:
Good Integration Practices Good Integration Practices
================================================= =================================================

View File

@ -40,7 +40,7 @@ pytest: helps you write better programs
- multi-paradigm: pytest can run ``nose``, ``unittest`` and - multi-paradigm: pytest can run ``nose``, ``unittest`` and
``doctest`` style test suites, including running testcases made for ``doctest`` style test suites, including running testcases made for
Django and trial Django and trial
- supports :ref:`good integration practises <goodpractises>` - supports :ref:`good integration practices <goodpractices>`
- supports extended :ref:`xUnit style setup <xunitsetup>` - supports extended :ref:`xUnit style setup <xunitsetup>`
- supports domain-specific :ref:`non-python tests` - supports domain-specific :ref:`non-python tests`
- supports generating `test coverage reports - supports generating `test coverage reports

View File

@ -7,7 +7,7 @@ Getting started basics
getting-started getting-started
usage usage
goodpractises goodpractices
projects projects
faq faq

View File

@ -41,15 +41,15 @@ to an expected output::
# content of test_expectation.py # content of test_expectation.py
import pytest import pytest
@pytest.mark.parametrize("input,expected", [ @pytest.mark.parametrize("test_input,expected", [
("3+5", 8), ("3+5", 8),
("2+4", 6), ("2+4", 6),
("6*9", 42), ("6*9", 42),
]) ])
def test_eval(input, expected): def test_eval(test_input, expected):
assert eval(input) == expected assert eval(test_input) == expected
Here, the ``@parametrize`` decorator defines three different ``(input,expected)`` Here, the ``@parametrize`` decorator defines three different ``(test_input,expected)``
tuples so that the ``test_eval`` function will run three times using tuples so that the ``test_eval`` function will run three times using
them in turn:: them in turn::
@ -64,15 +64,15 @@ them in turn::
======= FAILURES ======== ======= FAILURES ========
_______ test_eval[6*9-42] ________ _______ test_eval[6*9-42] ________
input = '6*9', expected = 42 test_input = '6*9', expected = 42
@pytest.mark.parametrize("input,expected", [ @pytest.mark.parametrize("test_input,expected", [
("3+5", 8), ("3+5", 8),
("2+4", 6), ("2+4", 6),
("6*9", 42), ("6*9", 42),
]) ])
def test_eval(input, expected): def test_eval(test_input, expected):
> assert eval(input) == expected > assert eval(test_input) == expected
E assert 54 == 42 E assert 54 == 42
E + where 54 = eval('6*9') E + where 54 = eval('6*9')
@ -91,13 +91,13 @@ for example with the builtin ``mark.xfail``::
# content of test_expectation.py # content of test_expectation.py
import pytest import pytest
@pytest.mark.parametrize("input,expected", [ @pytest.mark.parametrize("test_input,expected", [
("3+5", 8), ("3+5", 8),
("2+4", 6), ("2+4", 6),
pytest.mark.xfail(("6*9", 42)), pytest.mark.xfail(("6*9", 42)),
]) ])
def test_eval(input, expected): def test_eval(test_input, expected):
assert eval(input) == expected assert eval(test_input) == expected
Let's run this:: Let's run this::

View File

@ -56,7 +56,7 @@ Here is a little annotated list for some popular plugins:
check source code with pyflakes. check source code with pyflakes.
* `oejskit <http://pypi.python.org/pypi/oejskit>`_: * `oejskit <http://pypi.python.org/pypi/oejskit>`_:
a plugin to run javascript unittests in life browsers. a plugin to run javascript unittests in live browsers.
To see a complete list of all plugins with their latest testing To see a complete list of all plugins with their latest testing
status against different py.test and Python versions, please visit status against different py.test and Python versions, please visit

View File

@ -95,7 +95,7 @@ Here is how you might run it::
python package directory (i.e. one containing an ``__init__.py``) then python package directory (i.e. one containing an ``__init__.py``) then
"import conftest" can be ambiguous because there might be other "import conftest" can be ambiguous because there might be other
``conftest.py`` files as well on your PYTHONPATH or ``sys.path``. ``conftest.py`` files as well on your PYTHONPATH or ``sys.path``.
It is thus good practise for projects to either put ``conftest.py`` It is thus good practice for projects to either put ``conftest.py``
under a package scope or to never import anything from a under a package scope or to never import anything from a
conftest.py file. conftest.py file.
@ -485,12 +485,20 @@ Session related reporting hooks:
.. autofunction:: pytest_itemcollected .. autofunction:: pytest_itemcollected
.. autofunction:: pytest_collectreport .. autofunction:: pytest_collectreport
.. autofunction:: pytest_deselected .. autofunction:: pytest_deselected
.. autofunction:: pytest_report_header
.. autofunction:: pytest_report_teststatus
.. autofunction:: pytest_terminal_summary
And here is the central hook for reporting about And here is the central hook for reporting about
test execution: test execution:
.. autofunction:: pytest_runtest_logreport .. autofunction:: pytest_runtest_logreport
You can also use this hook to customize assertion representation for some
types:
.. autofunction:: pytest_assertrepr_compare
Debugging/Interaction hooks Debugging/Interaction hooks
--------------------------- ---------------------------

View File

@ -1,3 +1,4 @@
# encoding: utf-8
import sys import sys
from _pytest.doctest import DoctestItem, DoctestModule, DoctestTextfile from _pytest.doctest import DoctestItem, DoctestModule, DoctestTextfile
import py import py
@ -139,6 +140,46 @@ class TestDoctests:
"*UNEXPECTED*ZeroDivision*", "*UNEXPECTED*ZeroDivision*",
]) ])
def test_docstring_context_around_error(self, testdir):
"""Test that we show some context before the actual line of a failing
doctest.
"""
testdir.makepyfile('''
def foo():
"""
text-line-1
text-line-2
text-line-3
text-line-4
text-line-5
text-line-6
text-line-7
text-line-8
text-line-9
text-line-10
text-line-11
>>> 1 + 1
3
text-line-after
"""
''')
result = testdir.runpytest('--doctest-modules')
result.stdout.fnmatch_lines([
'*docstring_context_around_error*',
'005*text-line-3',
'006*text-line-4',
'013*text-line-11',
'014*>>> 1 + 1',
'Expected:',
' 3',
'Got:',
' 2',
])
# lines below should be trimmed out
assert 'text-line-2' not in result.stdout.str()
assert 'text-line-after' not in result.stdout.str()
def test_doctest_linedata_missing(self, testdir): def test_doctest_linedata_missing(self, testdir):
testdir.tmpdir.join('hello.py').write(py.code.Source(""" testdir.tmpdir.join('hello.py').write(py.code.Source("""
class Fun(object): class Fun(object):
@ -369,6 +410,23 @@ class TestDoctests:
reprec = testdir.inline_run(p, "--doctest-glob=x*.txt") reprec = testdir.inline_run(p, "--doctest-glob=x*.txt")
reprec.assertoutcome(failed=1, passed=0) reprec.assertoutcome(failed=1, passed=0)
def test_contains_unicode(self, testdir):
"""Fix internal error with docstrings containing non-ascii characters.
"""
testdir.makepyfile(u'''
# encoding: utf-8
def foo():
"""
>>> name = 'с' # not letter 'c' but instead Cyrillic 's'.
'anything'
"""
''')
result = testdir.runpytest('--doctest-modules')
result.stdout.fnmatch_lines([
'Got nothing',
'* 1 failed in*',
])
def test_ignore_import_errors_on_doctest(self, testdir): def test_ignore_import_errors_on_doctest(self, testdir):
p = testdir.makepyfile(""" p = testdir.makepyfile("""
import asdf import asdf

View File

@ -419,6 +419,35 @@ class TestPython:
systemout = pnode.find_first_by_tag("system-err") systemout = pnode.find_first_by_tag("system-err")
assert "hello-stderr" in systemout.toxml() assert "hello-stderr" in systemout.toxml()
def test_setup_error_captures_stdout(self, testdir):
testdir.makepyfile("""
def pytest_funcarg__arg(request):
print('hello-stdout')
raise ValueError()
def test_function(arg):
pass
""")
result, dom = runandparse(testdir)
node = dom.find_first_by_tag("testsuite")
pnode = node.find_first_by_tag("testcase")
systemout = pnode.find_first_by_tag("system-out")
assert "hello-stdout" in systemout.toxml()
def test_setup_error_captures_stderr(self, testdir):
testdir.makepyfile("""
import sys
def pytest_funcarg__arg(request):
sys.stderr.write('hello-stderr')
raise ValueError()
def test_function(arg):
pass
""")
result, dom = runandparse(testdir)
node = dom.find_first_by_tag("testsuite")
pnode = node.find_first_by_tag("testcase")
systemout = pnode.find_first_by_tag("system-err")
assert "hello-stderr" in systemout.toxml()
def test_mangle_testnames(): def test_mangle_testnames():
from _pytest.junitxml import mangle_testnames from _pytest.junitxml import mangle_testnames

View File

@ -75,6 +75,22 @@ class TestPDB:
if child.isalive(): if child.isalive():
child.wait() child.wait()
def test_pdb_interaction_capture(self, testdir):
p1 = testdir.makepyfile("""
def test_1():
print("getrekt")
assert False
""")
child = testdir.spawn_pytest("--pdb %s" % p1)
child.expect("getrekt")
child.expect("(Pdb)")
child.sendeof()
rest = child.read().decode("utf8")
assert "1 failed" in rest
assert "getrekt" not in rest
if child.isalive():
child.wait()
def test_pdb_interaction_exception(self, testdir): def test_pdb_interaction_exception(self, testdir):
p1 = testdir.makepyfile(""" p1 = testdir.makepyfile("""
import pytest import pytest

View File

@ -157,4 +157,4 @@ norecursedirs = .tox ja .hg cx_freeze_source
[flake8] [flake8]
ignore =E401,E225,E261,E128,E124,E301,E302,E121,E303,W391,E501,E231,E126,E701,E265,E241,E251,E226,E101,W191,E131,E203,E122,E123,E271,E712,E222,E127,E125,E221,W292,E111,E113,E293,E262,W293,E129,E702,E201,E272,E202 ignore =E401,E225,E261,E128,E124,E301,E302,E121,E303,W391,E501,E231,E126,E701,E265,E241,E251,E226,E101,W191,E131,E203,E122,E123,E271,E712,E222,E127,E125,E221,W292,E111,E113,E293,E262,W293,E129,E702,E201,E272,E202,E704,E731,E402