Merge remote-tracking branch 'upstream/master' into merge-master-into-features
This commit is contained in:
commit
3de93657bd
|
@ -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)
|
||||
|
||||
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -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
|
||||
|
|
39
MANIFEST.in
39
MANIFEST.in
|
@ -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
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Fix decode error in Python 2 for doctests in docstrings.
|
|
@ -0,0 +1 @@
|
|||
Exceptions raised during teardown by finalizers are now suppressed until all finalizers are called, with the initial exception reraised.
|
|
@ -0,0 +1 @@
|
|||
Fix incorrect "collected items" report when specifying tests on the command-line.
|
|
@ -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).
|
|
@ -0,0 +1 @@
|
|||
Create invoke tasks for updating the vendored packages.
|
|
@ -0,0 +1 @@
|
|||
Fix internal error when trying to detect the start of a recursive traceback.
|
|
@ -0,0 +1 @@
|
|||
Explicitly state for which hooks the calls stop after the first non-None result.
|
|
@ -0,0 +1 @@
|
|||
Update copyright dates in LICENSE, README.rst and in the documentation.
|
|
@ -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 %}
|
||||
|
|
|
@ -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::
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
-------------------------------------------
|
||||
|
||||
|
|
|
@ -31,5 +31,5 @@ template = "changelog/_template.rst"
|
|||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "trivial"
|
||||
name = "Trivial Changes"
|
||||
showcontent = false
|
||||
name = "Trivial/Internal Changes"
|
||||
showcontent = true
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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"')
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue