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

This commit is contained in:
Bruno Oliveira 2017-06-24 00:47:48 -03:00
commit 3de93657bd
38 changed files with 405 additions and 230 deletions

View File

@ -25,9 +25,9 @@ Bug Fixes
- ``UnicodeWarning`` is issued from the internal pytest warnings plugin only
when the message contains non-ascii unicode (Python 2 only). (#2463)
- Added a workaround for Python 3.6 WindowsConsoleIO breaking due to Pytests's
FDCapture. Other code using console handles might still be affected by the
very same issue and might require further workarounds/fixes, i.e. colorama.
- Added a workaround for Python 3.6 ``WindowsConsoleIO`` breaking due to Pytests's
``FDCapture``. Other code using console handles might still be affected by the
very same issue and might require further workarounds/fixes, i.e. ``colorama``.
(#2467)

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2004-2016 Holger Krekel and others
Copyright (c) 2004-2017 Holger Krekel and others
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

View File

@ -1,39 +0,0 @@
include CHANGELOG.rst
include LICENSE
include AUTHORS
include pyproject.toml
include README.rst
include CONTRIBUTING.rst
include HOWTORELEASE.rst
include tox.ini
include setup.py
recursive-include changelog *
recursive-include scripts *.py
recursive-include scripts *.bat
include .coveragerc
recursive-include bench *.py
recursive-include extra *.py
graft testing
graft doc
prune doc/en/_build
graft tasks
exclude _pytest/impl
graft _pytest/vendored_packages
recursive-exclude * *.pyc *.pyo
recursive-exclude testing/.hypothesis *
recursive-exclude testing/freeze/~ *
recursive-exclude testing/freeze/build *
recursive-exclude testing/freeze/dist *
exclude appveyor.yml
exclude .travis.yml
prune .github

View File

@ -102,7 +102,7 @@ Consult the `Changelog <http://docs.pytest.org/en/latest/changelog.html>`__ page
License
-------
Copyright Holger Krekel and others, 2004-2016.
Copyright Holger Krekel and others, 2004-2017.
Distributed under the terms of the `MIT`_ license, pytest is free and open source software.

View File

@ -640,8 +640,11 @@ class FormattedExcinfo(object):
).format(exc_type=type(e).__name__, exc_msg=safe_str(e), max_frames=max_frames, total=len(traceback))
traceback = traceback[:max_frames] + traceback[-max_frames:]
else:
extraline = "!!! Recursion detected (same locals & position)"
traceback = traceback[:recursionindex + 1]
if recursionindex is not None:
extraline = "!!! Recursion detected (same locals & position)"
traceback = traceback[:recursionindex + 1]
else:
extraline = None
return traceback, extraline

View File

@ -162,10 +162,6 @@ class AssertionRewritingHook(object):
# modules not passed explicitly on the command line are only
# rewritten if they match the naming convention for test files
for pat in self.fnpats:
# use fnmatch instead of fn_pypath.fnmatch because the
# latter might trigger an import to fnmatch.fnmatch
# internally, which would cause this method to be
# called recursively
if fn_pypath.fnmatch(pat):
state.trace("matched test file %r" % (fn,))
return True

View File

@ -177,7 +177,6 @@ class DoctestTextfile(pytest.Module):
name = self.fspath.basename
globs = {'__name__': '__main__'}
optionflags = get_optionflags(self)
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
checker=_get_checker())
@ -218,9 +217,6 @@ class DoctestModule(pytest.Module):
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
checker=_get_checker())
encoding = self.config.getini("doctest_encoding")
_fix_spoof_python2(runner, encoding)
for test in finder.find(module, module.__name__):
if test.examples: # skip empty doctests
yield DoctestItem(test.name, self, runner, test)
@ -332,7 +328,10 @@ def _get_report_choice(key):
def _fix_spoof_python2(runner, encoding):
"""
Installs a "SpoofOut" into the given DebugRunner so it properly deals with unicode output.
Installs a "SpoofOut" into the given DebugRunner so it properly deals with unicode output. This
should patch only doctests for text files because they don't have a way to declare their
encoding. Doctests in docstrings from Python modules don't have the same problem given that
Python already decoded the strings.
This fixes the problem related in issue #2434.
"""

View File

@ -733,10 +733,19 @@ class FixtureDef:
self._finalizer.append(finalizer)
def finish(self):
exceptions = []
try:
while self._finalizer:
func = self._finalizer.pop()
func()
try:
func = self._finalizer.pop()
func()
except:
exceptions.append(sys.exc_info())
if exceptions:
e = exceptions[0]
del exceptions # ensure we don't keep all frames alive because of the traceback
py.builtin._reraise(*e)
finally:
ihook = self._fixturemanager.session.ihook
ihook.pytest_fixture_post_finalizer(fixturedef=self)

View File

@ -73,7 +73,9 @@ def pytest_configure(config):
@hookspec(firstresult=True)
def pytest_cmdline_parse(pluginmanager, args):
"""return initialized config object, parsing the specified args. """
"""return initialized config object, parsing the specified args.
Stops at first non-None result, see :ref:`firstresult` """
def pytest_cmdline_preparse(config, args):
"""(deprecated) modify command line arguments before option parsing. """
@ -81,7 +83,9 @@ def pytest_cmdline_preparse(config, args):
@hookspec(firstresult=True)
def pytest_cmdline_main(config):
""" called for performing the main command line action. The default
implementation will invoke the configure hooks and runtest_mainloop. """
implementation will invoke the configure hooks and runtest_mainloop.
Stops at first non-None result, see :ref:`firstresult` """
def pytest_load_initial_conftests(early_config, parser, args):
""" implements the loading of initial conftest files ahead
@ -94,7 +98,9 @@ def pytest_load_initial_conftests(early_config, parser, args):
@hookspec(firstresult=True)
def pytest_collection(session):
""" perform the collection protocol for the given session. """
""" perform the collection protocol for the given session.
Stops at first non-None result, see :ref:`firstresult` """
def pytest_collection_modifyitems(session, config, items):
""" called after collection has been performed, may filter or re-order
@ -108,11 +114,15 @@ def pytest_ignore_collect(path, config):
""" return True to prevent considering this path for collection.
This hook is consulted for all files and directories prior to calling
more specific hooks.
Stops at first non-None result, see :ref:`firstresult`
"""
@hookspec(firstresult=True)
def pytest_collect_directory(path, parent):
""" called before traversing a directory for collection files. """
""" called before traversing a directory for collection files.
Stops at first non-None result, see :ref:`firstresult` """
def pytest_collect_file(path, parent):
""" return collection Node or None for the given path. Any new node
@ -133,7 +143,9 @@ def pytest_deselected(items):
@hookspec(firstresult=True)
def pytest_make_collect_report(collector):
""" perform ``collector.collect()`` and return a CollectReport. """
""" perform ``collector.collect()`` and return a CollectReport.
Stops at first non-None result, see :ref:`firstresult` """
# -------------------------------------------------------------------------
# Python test function related hooks
@ -145,15 +157,20 @@ def pytest_pycollect_makemodule(path, parent):
This hook will be called for each matching test module path.
The pytest_collect_file hook needs to be used if you want to
create test modules for files that do not match as a test module.
"""
Stops at first non-None result, see :ref:`firstresult` """
@hookspec(firstresult=True)
def pytest_pycollect_makeitem(collector, name, obj):
""" return custom item/collector for a python object in a module, or None. """
""" return custom item/collector for a python object in a module, or None.
Stops at first non-None result, see :ref:`firstresult` """
@hookspec(firstresult=True)
def pytest_pyfunc_call(pyfuncitem):
""" call underlying test function. """
""" call underlying test function.
Stops at first non-None result, see :ref:`firstresult` """
def pytest_generate_tests(metafunc):
""" generate (multiple) parametrized calls to a test function."""
@ -163,7 +180,8 @@ def pytest_make_parametrize_id(config, val, argname):
"""Return a user-friendly string representation of the given ``val`` that will be used
by @pytest.mark.parametrize calls. Return None if the hook doesn't know about ``val``.
The parameter name is available as ``argname``, if required.
"""
Stops at first non-None result, see :ref:`firstresult` """
# -------------------------------------------------------------------------
# generic runtest related hooks
@ -172,7 +190,9 @@ def pytest_make_parametrize_id(config, val, argname):
@hookspec(firstresult=True)
def pytest_runtestloop(session):
""" called for performing the main runtest loop
(after collection finished). """
(after collection finished).
Stops at first non-None result, see :ref:`firstresult` """
def pytest_itemstart(item, node):
""" (deprecated, use pytest_runtest_logstart). """
@ -190,7 +210,9 @@ def pytest_runtest_protocol(item, nextitem):
:py:func:`pytest_runtest_teardown`.
:return boolean: True if no further hook implementations should be invoked.
"""
Stops at first non-None result, see :ref:`firstresult` """
def pytest_runtest_logstart(nodeid, location):
""" signal the start of running a single test item. """
@ -215,7 +237,8 @@ def pytest_runtest_makereport(item, call):
""" return a :py:class:`_pytest.runner.TestReport` object
for the given :py:class:`pytest.Item <_pytest.main.Item>` and
:py:class:`_pytest.runner.CallInfo`.
"""
Stops at first non-None result, see :ref:`firstresult` """
def pytest_runtest_logreport(report):
""" process a test setup/call/teardown report relating to
@ -227,7 +250,9 @@ def pytest_runtest_logreport(report):
@hookspec(firstresult=True)
def pytest_fixture_setup(fixturedef, request):
""" performs fixture setup execution. """
""" performs fixture setup execution.
Stops at first non-None result, see :ref:`firstresult` """
def pytest_fixture_post_finalizer(fixturedef):
""" called after fixture teardown, but before the cache is cleared so
@ -277,7 +302,9 @@ def pytest_report_header(config, startdir):
@hookspec(firstresult=True)
def pytest_report_teststatus(report):
""" return result-category, shortletter and verbose word for reporting."""
""" return result-category, shortletter and verbose word for reporting.
Stops at first non-None result, see :ref:`firstresult` """
def pytest_terminal_summary(terminalreporter, exitstatus):
""" add additional section in terminal summary reporting. """
@ -295,7 +322,9 @@ def pytest_logwarning(message, code, nodeid, fslocation):
@hookspec(firstresult=True)
def pytest_doctest_prepare_content(content):
""" return processed content for a given doctest"""
""" return processed content for a given doctest
Stops at first non-None result, see :ref:`firstresult` """
# -------------------------------------------------------------------------
# error handling and internal debugging hooks

View File

@ -763,7 +763,11 @@ class Session(FSCollector):
if not has_matched and len(rep.result) == 1 and x.name == "()":
nextnames.insert(0, name)
resultnodes.extend(self.matchnodes([x], nextnames))
node.ihook.pytest_collectreport(report=rep)
else:
# report collection failures here to avoid failing to run some test
# specified in the command line because the module could not be
# imported (#134)
node.ihook.pytest_collectreport(report=rep)
return resultnodes
def genitems(self, node):

View File

@ -27,10 +27,8 @@ def recwarn():
def deprecated_call(func=None, *args, **kwargs):
""" assert that calling ``func(*args, **kwargs)`` triggers a
``DeprecationWarning`` or ``PendingDeprecationWarning``.
This function can be used as a context manager::
"""context manager that can be used to ensure a block of code triggers a
``DeprecationWarning`` or ``PendingDeprecationWarning``::
>>> import warnings
>>> def api_call_v2():
@ -40,38 +38,47 @@ def deprecated_call(func=None, *args, **kwargs):
>>> with deprecated_call():
... assert api_call_v2() == 200
Note: we cannot use WarningsRecorder here because it is still subject
to the mechanism that prevents warnings of the same type from being
triggered twice for the same module. See #1190.
``deprecated_call`` can also be used by passing a function and ``*args`` and ``*kwargs``,
in which case it will ensure calling ``func(*args, **kwargs)`` produces one of the warnings
types above.
"""
if not func:
return WarningsChecker(expected_warning=(DeprecationWarning, PendingDeprecationWarning))
categories = []
def warn_explicit(message, category, *args, **kwargs):
categories.append(category)
def warn(message, category=None, *args, **kwargs):
if isinstance(message, Warning):
categories.append(message.__class__)
else:
categories.append(category)
old_warn = warnings.warn
old_warn_explicit = warnings.warn_explicit
warnings.warn_explicit = warn_explicit
warnings.warn = warn
try:
ret = func(*args, **kwargs)
finally:
warnings.warn_explicit = old_warn_explicit
warnings.warn = old_warn
deprecation_categories = (DeprecationWarning, PendingDeprecationWarning)
if not any(issubclass(c, deprecation_categories) for c in categories):
return _DeprecatedCallContext()
else:
__tracebackhide__ = True
raise AssertionError("%r did not produce DeprecationWarning" % (func,))
return ret
with _DeprecatedCallContext():
return func(*args, **kwargs)
class _DeprecatedCallContext(object):
"""Implements the logic to capture deprecation warnings as a context manager."""
def __enter__(self):
self._captured_categories = []
self._old_warn = warnings.warn
self._old_warn_explicit = warnings.warn_explicit
warnings.warn_explicit = self._warn_explicit
warnings.warn = self._warn
def _warn_explicit(self, message, category, *args, **kwargs):
self._captured_categories.append(category)
def _warn(self, message, category=None, *args, **kwargs):
if isinstance(message, Warning):
self._captured_categories.append(message.__class__)
else:
self._captured_categories.append(category)
def __exit__(self, exc_type, exc_val, exc_tb):
warnings.warn_explicit = self._old_warn_explicit
warnings.warn = self._old_warn
if exc_type is None:
deprecation_categories = (DeprecationWarning, PendingDeprecationWarning)
if not any(issubclass(c, deprecation_categories) for c in self._captured_categories):
__tracebackhide__ = True
msg = "Did not produce DeprecationWarning or PendingDeprecationWarning"
raise AssertionError(msg)
def warns(expected_warning, *args, **kwargs):

View File

@ -282,7 +282,7 @@ class TerminalReporter:
line = "collected "
else:
line = "collecting "
line += str(self._numcollected) + " items"
line += str(self._numcollected) + " item" + ('' if self._numcollected == 1 else 's')
if errors:
line += " / %d errors" % errors
if skipped:

1
changelog/2434.bugfix Normal file
View File

@ -0,0 +1 @@
Fix decode error in Python 2 for doctests in docstrings.

1
changelog/2440.bugfix Normal file
View File

@ -0,0 +1 @@
Exceptions raised during teardown by finalizers are now suppressed until all finalizers are called, with the initial exception reraised.

1
changelog/2464.bugfix Normal file
View File

@ -0,0 +1 @@
Fix incorrect "collected items" report when specifying tests on the command-line.

4
changelog/2469.bugfix Normal file
View File

@ -0,0 +1,4 @@
``deprecated_call`` in context-manager form now captures deprecation warnings even if
the same warning has already been raised. Also, ``deprecated_call`` will always produce
the same error message (previously it would produce different messages in context-manager vs.
function-call mode).

1
changelog/2474.trivial Normal file
View File

@ -0,0 +1 @@
Create invoke tasks for updating the vendored packages.

1
changelog/2486.bugfix Normal file
View File

@ -0,0 +1 @@
Fix internal error when trying to detect the start of a recursive traceback.

1
changelog/2493.doc Normal file
View File

@ -0,0 +1 @@
Explicitly state for which hooks the calls stop after the first non-None result.

1
changelog/2499.trivial Normal file
View File

@ -0,0 +1 @@
Update copyright dates in LICENSE, README.rst and in the documentation.

View File

@ -6,14 +6,14 @@
{% endif %}
{% if sections[section] %}
{% for category, val in definitions.items() if category in sections[section] and category != 'trivial' %}
{% for category, val in definitions.items() if category in sections[section] %}
{{ definitions[category]['name'] }}
{{ underline * definitions[category]['name']|length }}
{% if definitions[category]['showcontent'] %}
{% for text, values in sections[section][category]|dictsort(by='value') %}
- {{ text }}{% if category != 'vendor' %} ({{ values|sort|join(', ') }}){% endif %}
- {{ text }}{% if category != 'vendor' %} (`{{ values[0] }} <https://github.com/pytest-dev/pytest/issues/{{ values[0][1:] }}>`_){% endif %}
{% endfor %}

View File

@ -73,20 +73,20 @@ marked ``smtp`` fixture function. Running the test looks like this::
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 items
test_smtpsimple.py F
======= FAILURES ========
_______ test_ehlo ________
smtp = <smtplib.SMTP object at 0xdeadbeef>
def test_ehlo(smtp):
response, msg = smtp.ehlo()
assert response == 250
> assert 0 # for demo purposes
E assert 0
test_smtpsimple.py:11: AssertionError
======= 1 failed in 0.12 seconds ========
@ -123,20 +123,13 @@ with a list of available function arguments.
but is not anymore advertised as the primary means of declaring fixture
functions.
"Funcargs" a prime example of dependency injection
Fixtures: a prime example of dependency injection
---------------------------------------------------
When injecting fixtures to test functions, pytest-2.0 introduced the
term "funcargs" or "funcarg mechanism" which continues to be present
also in docs today. It now refers to the specific case of injecting
fixture values as arguments to test functions. With pytest-2.3 there are
more possibilities to use fixtures but "funcargs" remain as the main way
as they allow to directly state the dependencies of a test function.
As the following examples show in more detail, funcargs allow test
functions to easily receive and work against specific pre-initialized
application objects without having to care about import/setup/cleanup
details. It's a prime example of `dependency injection`_ where fixture
Fixtures allow test functions to easily receive and work
against specific pre-initialized application objects without having
to care about import/setup/cleanup details.
It's a prime example of `dependency injection`_ where fixture
functions take the role of the *injector* and test functions are the
*consumers* of fixture objects.
@ -176,7 +169,7 @@ function (in or below the directory where ``conftest.py`` is located)::
response, msg = smtp.ehlo()
assert response == 250
assert b"smtp.gmail.com" in msg
assert 0 # for demo purposes
assert 0 # for demo purposes
def test_noop(smtp):
response, msg = smtp.noop()
@ -191,32 +184,32 @@ inspect what is going on and can now run the tests::
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items
test_module.py FF
======= FAILURES ========
_______ test_ehlo ________
smtp = <smtplib.SMTP object at 0xdeadbeef>
def test_ehlo(smtp):
response, msg = smtp.ehlo()
assert response == 250
assert b"smtp.gmail.com" in msg
> assert 0 # for demo purposes
E assert 0
test_module.py:6: AssertionError
_______ test_noop ________
smtp = <smtplib.SMTP object at 0xdeadbeef>
def test_noop(smtp):
response, msg = smtp.noop()
assert response == 250
> assert 0 # for demo purposes
E assert 0
test_module.py:11: AssertionError
======= 2 failed in 0.12 seconds ========
@ -267,7 +260,7 @@ Let's execute it::
$ pytest -s -q --tb=no
FFteardown smtp
2 failed in 0.12 seconds
We see that the ``smtp`` instance is finalized after the two
@ -296,6 +289,9 @@ The ``smtp`` connection will be closed after the test finished execution
because the ``smtp`` object automatically closes when
the ``with`` statement ends.
Note that if an exception happens during the *setup* code (before the ``yield`` keyword), the
*teardown* code (after the ``yield``) will not be called.
.. note::
Prior to version 2.10, in order to use a ``yield`` statement to execute teardown code one
@ -303,29 +299,51 @@ the ``with`` statement ends.
fixtures can use ``yield`` directly so the ``yield_fixture`` decorator is no longer needed
and considered deprecated.
.. note::
As historical note, another way to write teardown code is
by accepting a ``request`` object into your fixture function and can call its
``request.addfinalizer`` one or multiple times::
# content of conftest.py
An alternative option for executing *teardown* code is to
make use of the ``addfinalizer`` method of the `request-context`_ object to register
finalization functions.
import smtplib
import pytest
Here's the ``smtp`` fixture changed to use ``addfinalizer`` for cleanup:
@pytest.fixture(scope="module")
def smtp(request):
smtp = smtplib.SMTP("smtp.gmail.com")
def fin():
print ("teardown smtp")
smtp.close()
request.addfinalizer(fin)
return smtp # provide the fixture value
.. code-block:: python
The ``fin`` function will execute when the last test in the module has finished execution.
# content of conftest.py
import smtplib
import pytest
@pytest.fixture(scope="module")
def smtp(request):
smtp = smtplib.SMTP("smtp.gmail.com")
def fin():
print ("teardown smtp")
smtp.close()
request.addfinalizer(fin)
return smtp # provide the fixture value
Both ``yield`` and ``addfinalizer`` methods work similarly by calling their code after the test
ends, but ``addfinalizer`` has two key differences over ``yield``:
1. It is possible to register multiple finalizer functions.
2. Finalizers will always be called regardless if the fixture *setup* code raises an exception.
This is handy to properly close all resources created by a fixture even if one of them
fails to be created/acquired::
@pytest.fixture
def equipments(request):
r = []
for port in ('C1', 'C3', 'C28'):
equip = connect(port)
request.addfinalizer(equip.disconnect)
r.append(equip)
return r
In the example above, if ``"C28"`` fails with an exception, ``"C1"`` and ``"C3"`` will still
be properly closed. Of course, if an exception happens before the finalize function is
registered then it will not be executed.
This method is still fully supported, but ``yield`` is recommended from 2.10 onward because
it is considered simpler and better describes the natural code flow.
.. _`request-context`:
@ -355,7 +373,7 @@ again, nothing much has changed::
$ pytest -s -q --tb=no
FFfinalizing <smtplib.SMTP object at 0xdeadbeef> (smtp.gmail.com)
2 failed in 0.12 seconds
Let's quickly create another test module that actually sets the
@ -423,51 +441,51 @@ So let's just do another run::
FFFF
======= FAILURES ========
_______ test_ehlo[smtp.gmail.com] ________
smtp = <smtplib.SMTP object at 0xdeadbeef>
def test_ehlo(smtp):
response, msg = smtp.ehlo()
assert response == 250
assert b"smtp.gmail.com" in msg
> assert 0 # for demo purposes
E assert 0
test_module.py:6: AssertionError
_______ test_noop[smtp.gmail.com] ________
smtp = <smtplib.SMTP object at 0xdeadbeef>
def test_noop(smtp):
response, msg = smtp.noop()
assert response == 250
> assert 0 # for demo purposes
E assert 0
test_module.py:11: AssertionError
_______ test_ehlo[mail.python.org] ________
smtp = <smtplib.SMTP object at 0xdeadbeef>
def test_ehlo(smtp):
response, msg = smtp.ehlo()
assert response == 250
> assert b"smtp.gmail.com" in msg
E AssertionError: assert b'smtp.gmail.com' in b'mail.python.org\nSIZE 51200000\nETRN\nSTARTTLS\nENHANCEDSTATUSCODES\n8BITMIME\nDSN\nSMTPUTF8'
test_module.py:5: AssertionError
-------------------------- Captured stdout setup ---------------------------
finalizing <smtplib.SMTP object at 0xdeadbeef>
_______ test_noop[mail.python.org] ________
smtp = <smtplib.SMTP object at 0xdeadbeef>
def test_noop(smtp):
response, msg = smtp.noop()
assert response == 250
> assert 0 # for demo purposes
E assert 0
test_module.py:11: AssertionError
------------------------- Captured stdout teardown -------------------------
finalizing <smtplib.SMTP object at 0xdeadbeef>
@ -539,7 +557,7 @@ Running the above tests results in the following test IDs being used::
<Function 'test_noop[smtp.gmail.com]'>
<Function 'test_ehlo[mail.python.org]'>
<Function 'test_noop[mail.python.org]'>
======= no tests ran in 0.12 seconds ========
.. _`interdependent fixtures`:
@ -578,10 +596,10 @@ Here we declare an ``app`` fixture which receives the previously defined
cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 2 items
test_appsetup.py::test_smtp_exists[smtp.gmail.com] PASSED
test_appsetup.py::test_smtp_exists[mail.python.org] PASSED
======= 2 passed in 0.12 seconds ========
Due to the parametrization of ``smtp`` the test will run twice with two
@ -647,26 +665,26 @@ Let's run the tests in verbose mode and with looking at the print-output::
cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 8 items
test_module.py::test_0[1] SETUP otherarg 1
RUN test0 with otherarg 1
PASSED TEARDOWN otherarg 1
test_module.py::test_0[2] SETUP otherarg 2
RUN test0 with otherarg 2
PASSED TEARDOWN otherarg 2
test_module.py::test_1[mod1] SETUP modarg mod1
RUN test1 with modarg mod1
PASSED
test_module.py::test_2[1-mod1] SETUP otherarg 1
RUN test2 with otherarg 1 and modarg mod1
PASSED TEARDOWN otherarg 1
test_module.py::test_2[2-mod1] SETUP otherarg 2
RUN test2 with otherarg 2 and modarg mod1
PASSED TEARDOWN otherarg 2
test_module.py::test_1[mod2] TEARDOWN modarg mod1
SETUP modarg mod2
RUN test1 with modarg mod2
@ -674,13 +692,13 @@ Let's run the tests in verbose mode and with looking at the print-output::
test_module.py::test_2[1-mod2] SETUP otherarg 1
RUN test2 with otherarg 1 and modarg mod2
PASSED TEARDOWN otherarg 1
test_module.py::test_2[2-mod2] SETUP otherarg 2
RUN test2 with otherarg 2 and modarg mod2
PASSED TEARDOWN otherarg 2
TEARDOWN modarg mod2
======= 8 passed in 0.12 seconds ========
You can see that the parametrized module-scoped ``modarg`` resource caused an
@ -782,8 +800,8 @@ Autouse fixtures (xUnit setup on steroids)
.. regendoc:wipe
Occasionally, you may want to have fixtures get invoked automatically
without a `usefixtures`_ or `funcargs`_ reference. As a practical
example, suppose we have a database fixture which has a
without declaring a function argument explicitly or a `usefixtures`_ decorator.
As a practical example, suppose we have a database fixture which has a
begin/rollback/commit architecture and we want to automatically surround
each test method by a transaction and a rollback. Here is a dummy
self-contained implementation of this idea::

View File

@ -83,8 +83,8 @@ Consult the :ref:`Changelog <changelog>` page for fixes and enhancements of each
License
-------
Copyright Holger Krekel and others, 2004-2016.
Copyright Holger Krekel and others, 2004-2017.
Distributed under the terms of the `MIT`_ license, pytest is free and open source software.
.. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE
.. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE

View File

@ -9,7 +9,7 @@ Distributed under the terms of the `MIT`_ license, pytest is free and open sourc
The MIT License (MIT)
Copyright (c) 2004-2016 Holger Krekel and others
Copyright (c) 2004-2017 Holger Krekel and others
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

View File

@ -255,11 +255,11 @@ if ``myapp.testsupport.myplugin`` also declares ``pytest_plugins``, the contents
of the variable will also be loaded as plugins, and so on.
This mechanism makes it easy to share fixtures within applications or even
external applications without the need to create external plugins using
external applications without the need to create external plugins using
the ``setuptools``'s entry point technique.
Plugins imported by ``pytest_plugins`` will also automatically be marked
for assertion rewriting (see :func:`pytest.register_assert_rewrite`).
for assertion rewriting (see :func:`pytest.register_assert_rewrite`).
However for this to have any effect the module must not be
imported already; if it was already imported at the time the
``pytest_plugins`` statement is processed, a warning will result and
@ -357,6 +357,8 @@ allowed to raise exceptions. Doing so will break the pytest run.
.. _firstresult:
firstresult: stop at first non-None result
-------------------------------------------

View File

@ -31,5 +31,5 @@ template = "changelog/_template.rst"
[[tool.towncrier.type]]
directory = "trivial"
name = "Trivial Changes"
showcontent = false
name = "Trivial/Internal Changes"
showcontent = true

View File

@ -1,20 +0,0 @@
"""
Script used by tox.ini to check the manifest file if we are under version control, or skip the
check altogether if not.
"check-manifest" will needs a vcs to work, which is not available when testing the package
instead of the source code (with ``devpi test`` for example).
"""
from __future__ import print_function
import os
import subprocess
import sys
from check_manifest import main
if os.path.isdir('.git'):
sys.exit(main())
else:
print('No .git directory found, skipping checking the manifest file')
sys.exit(0)

View File

@ -4,6 +4,10 @@ Invoke tasks to help with pytest development and release process.
import invoke
from . import generate
from . import generate, vendoring
ns = invoke.Collection(generate)
ns = invoke.Collection(
generate,
vendoring
)

23
tasks/vendoring.py Normal file
View File

@ -0,0 +1,23 @@
from __future__ import absolute_import, print_function
import py
import invoke
VENDOR_TARGET = py.path.local("_pytest/vendored_packages")
GOOD_FILES = 'README.md', '__init__.py'
@invoke.task()
def remove_libs(ctx):
print("removing vendored libs")
for path in VENDOR_TARGET.listdir():
if path.basename not in GOOD_FILES:
print(" ", path)
path.remove()
@invoke.task(pre=[remove_libs])
def update_libs(ctx):
print("installing libs")
ctx.run("pip install -t {target} pluggy".format(target=VENDOR_TARGET))
ctx.run("git add {target}".format(target=VENDOR_TARGET))
print("Please commit to finish the update after running the tests:")
print()
print(' git commit -am "Updated vendored libs"')

View File

@ -317,8 +317,8 @@ class TestGeneralUsage(object):
])
assert 'sessionstarttime' not in result.stderr.str()
@pytest.mark.parametrize('lookfor', ['test_fun.py', 'test_fun.py::test_a'])
def test_issue134_report_syntaxerror_when_collecting_member(self, testdir, lookfor):
@pytest.mark.parametrize('lookfor', ['test_fun.py::test_a'])
def test_issue134_report_error_when_collecting_member(self, testdir, lookfor):
testdir.makepyfile(test_fun="""
def test_a():
pass

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function
import sys
import operator
import _pytest
import py
@ -1173,3 +1174,25 @@ def test_exception_repr_extraction_error_on_recursion():
'*The following exception happened*',
'*ValueError: The truth value of an array*',
])
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)
RecursionDepthError().trigger
except:
from _pytest._code.code import ExceptionInfo
exc_info = ExceptionInfo()
if sys.version_info[:2] == (2, 6):
assert "'RecursionDepthError' object has no attribute '___" in str(exc_info.getrepr())
else:
assert 'maximum recursion' in str(exc_info.getrepr())
else:
assert 0

View File

@ -657,6 +657,39 @@ class TestRequestBasic(object):
"*1 error*" # XXX the whole module collection fails
])
def test_request_subrequest_addfinalizer_exceptions(self, testdir):
"""
Ensure exceptions raised during teardown by a finalizer are suppressed
until all finalizers are called, re-raising the first exception (#2440)
"""
testdir.makepyfile("""
import pytest
l = []
def _excepts(where):
raise Exception('Error in %s fixture' % where)
@pytest.fixture
def subrequest(request):
return request
@pytest.fixture
def something(subrequest):
subrequest.addfinalizer(lambda: l.append(1))
subrequest.addfinalizer(lambda: l.append(2))
subrequest.addfinalizer(lambda: _excepts('something'))
@pytest.fixture
def excepts(subrequest):
subrequest.addfinalizer(lambda: _excepts('excepts'))
subrequest.addfinalizer(lambda: l.append(3))
def test_first(something, excepts):
pass
def test_second():
assert l == [3, 2, 1]
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines([
'*Exception: Error in excepts fixture',
'* 2 passed, 1 error in *',
])
def test_request_getmodulepath(self, testdir):
modcol = testdir.getmodulecol("def test_somefunc(): pass")
item, = testdir.genitems([modcol])

View File

@ -369,6 +369,11 @@ class TestSession(object):
assert len(colitems) == 1
assert colitems[0].fspath == p
def get_reported_items(self, hookrec):
"""Return pytest.Item instances reported by the pytest_collectreport hook"""
calls = hookrec.getcalls('pytest_collectreport')
return [x for call in calls for x in call.report.result
if isinstance(x, pytest.Item)]
def test_collect_protocol_single_function(self, testdir):
p = testdir.makepyfile("def test_func(): pass")
@ -386,9 +391,10 @@ class TestSession(object):
("pytest_collectstart", "collector.fspath == p"),
("pytest_make_collect_report", "collector.fspath == p"),
("pytest_pycollect_makeitem", "name == 'test_func'"),
("pytest_collectreport", "report.nodeid.startswith(p.basename)"),
("pytest_collectreport", "report.nodeid == ''")
("pytest_collectreport", "report.result[0].name == 'test_func'"),
])
# ensure we are reporting the collection of the single test item (#2464)
assert [x.name for x in self.get_reported_items(hookrec)] == ['test_func']
def test_collect_protocol_method(self, testdir):
p = testdir.makepyfile("""
@ -407,6 +413,8 @@ class TestSession(object):
assert items[0].name == "test_method"
newid = items[0].nodeid
assert newid == normid
# ensure we are reporting the collection of the single test item (#2464)
assert [x.name for x in self.get_reported_items(hookrec)] == ['test_method']
def test_collect_custom_nodes_multi_id(self, testdir):
p = testdir.makepyfile("def test_func(): pass")
@ -436,9 +444,8 @@ class TestSession(object):
"collector.__class__.__name__ == 'Module'"),
("pytest_pycollect_makeitem", "name == 'test_func'"),
("pytest_collectreport", "report.nodeid.startswith(p.basename)"),
#("pytest_collectreport",
# "report.fspath == %r" % str(rcol.fspath)),
])
assert len(self.get_reported_items(hookrec)) == 2
def test_collect_subdir_event_ordering(self, testdir):
p = testdir.makepyfile("def test_func(): pass")
@ -495,11 +502,13 @@ class TestSession(object):
def test_method(self):
pass
""")
arg = p.basename + ("::TestClass::test_method")
arg = p.basename + "::TestClass::test_method"
items, hookrec = testdir.inline_genitems(arg)
assert len(items) == 1
item, = items
assert item.nodeid.endswith("TestClass::()::test_method")
# ensure we are reporting the collection of the single test item (#2464)
assert [x.name for x in self.get_reported_items(hookrec)] == ['test_method']
class Test_getinitialnodes(object):
def test_global_file(self, testdir, tmpdir):

View File

@ -527,6 +527,25 @@ class TestDoctests(object):
'*1 failed*',
])
def test_unicode_doctest_module(self, testdir):
"""
Test case for issue 2434: DecodeError on Python 2 when doctest docstring
contains non-ascii characters.
"""
p = testdir.makepyfile(test_unicode_doctest_module="""
# -*- encoding: utf-8 -*-
from __future__ import unicode_literals
def fix_bad_unicode(text):
'''
>>> print(fix_bad_unicode('único'))
único
'''
return "único"
""")
result = testdir.runpytest(p, '--doctest-modules')
result.stdout.fnmatch_lines(['* 1 passed *'])
class TestLiterals(object):

View File

@ -77,7 +77,7 @@ class TestDeprecatedCall(object):
def test_deprecated_call_raises(self):
with pytest.raises(AssertionError) as excinfo:
pytest.deprecated_call(self.dep, 3, 5)
assert str(excinfo).find("did not produce") != -1
assert 'Did not produce' in str(excinfo)
def test_deprecated_call(self):
pytest.deprecated_call(self.dep, 0, 5)
@ -106,31 +106,69 @@ class TestDeprecatedCall(object):
pytest.deprecated_call(self.dep_explicit, 0)
pytest.deprecated_call(self.dep_explicit, 0)
def test_deprecated_call_as_context_manager_no_warning(self):
with pytest.raises(pytest.fail.Exception, matches='^DID NOT WARN'):
with pytest.deprecated_call():
self.dep(1)
@pytest.mark.parametrize('mode', ['context_manager', 'call'])
def test_deprecated_call_no_warning(self, mode):
"""Ensure deprecated_call() raises the expected failure when its block/function does
not raise a deprecation warning.
"""
def f():
pass
msg = 'Did not produce DeprecationWarning or PendingDeprecationWarning'
with pytest.raises(AssertionError, matches=msg):
if mode == 'call':
pytest.deprecated_call(f)
else:
with pytest.deprecated_call():
f()
@pytest.mark.parametrize('warning_type', [PendingDeprecationWarning, DeprecationWarning])
@pytest.mark.parametrize('mode', ['context_manager', 'call'])
def test_deprecated_call_modes(self, warning_type, mode):
@pytest.mark.parametrize('call_f_first', [True, False])
def test_deprecated_call_modes(self, warning_type, mode, call_f_first):
"""Ensure deprecated_call() captures a deprecation warning as expected inside its
block/function.
"""
def f():
warnings.warn(warning_type("hi"))
return 10
# ensure deprecated_call() can capture the warning even if it has already been triggered
if call_f_first:
assert f() == 10
if mode == 'call':
pytest.deprecated_call(f)
assert pytest.deprecated_call(f) == 10
else:
with pytest.deprecated_call():
f()
assert f() == 10
@pytest.mark.parametrize('mode', ['context_manager', 'call'])
def test_deprecated_call_exception_is_raised(self, mode):
"""If the block of the code being tested by deprecated_call() raises an exception,
it must raise the exception undisturbed.
"""
def f():
raise ValueError('some exception')
with pytest.raises(ValueError, match='some exception'):
if mode == 'call':
pytest.deprecated_call(f)
else:
with pytest.deprecated_call():
f()
def test_deprecated_call_specificity(self):
other_warnings = [Warning, UserWarning, SyntaxWarning, RuntimeWarning,
FutureWarning, ImportWarning, UnicodeWarning]
for warning in other_warnings:
def f():
py.std.warnings.warn(warning("hi"))
warnings.warn(warning("hi"))
with pytest.raises(AssertionError):
pytest.deprecated_call(f)
with pytest.raises(AssertionError):
with pytest.deprecated_call():
f()
def test_deprecated_function_already_called(self, testdir):
"""deprecated_call should be able to catch a call to a deprecated

View File

@ -513,12 +513,12 @@ def test_pytest_no_tests_collected_exit_status(testdir):
assert 1
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines('*collected 1 items*')
result.stdout.fnmatch_lines('*collected 1 item*')
result.stdout.fnmatch_lines('*1 passed*')
assert result.ret == main.EXIT_OK
result = testdir.runpytest('-k nonmatch')
result.stdout.fnmatch_lines('*collected 1 items*')
result.stdout.fnmatch_lines('*collected 1 item*')
result.stdout.fnmatch_lines('*1 deselected*')
assert result.ret == main.EXIT_NOTESTSCOLLECTED

View File

@ -204,6 +204,15 @@ class TestTerminal(object):
assert result.ret == 2
result.stdout.fnmatch_lines(['*KeyboardInterrupt*'])
def test_collect_single_item(self, testdir):
"""Use singular 'item' when reporting a single test item"""
testdir.makepyfile("""
def test_foobar():
pass
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines(['collected 1 item'])
class TestCollectonly(object):
def test_collectonly_basic(self, testdir):

View File

@ -57,9 +57,7 @@ deps =
# pygments required by rst-lint
pygments
restructuredtext_lint
check-manifest
commands =
{envpython} scripts/check-manifest.py
flake8 pytest.py _pytest testing
{envpython} scripts/check-rst.py