Merge pull request #2526 from nicoddemus/merge-master-into-features
Merge master into features
This commit is contained in:
commit
9b51fc646c
|
@ -25,9 +25,9 @@ Bug Fixes
|
||||||
- ``UnicodeWarning`` is issued from the internal pytest warnings plugin only
|
- ``UnicodeWarning`` is issued from the internal pytest warnings plugin only
|
||||||
when the message contains non-ascii unicode (Python 2 only). (#2463)
|
when the message contains non-ascii unicode (Python 2 only). (#2463)
|
||||||
|
|
||||||
- Added a workaround for Python 3.6 WindowsConsoleIO breaking due to Pytests's
|
- 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
|
``FDCapture``. Other code using console handles might still be affected by the
|
||||||
very same issue and might require further workarounds/fixes, i.e. colorama.
|
very same issue and might require further workarounds/fixes, i.e. ``colorama``.
|
||||||
(#2467)
|
(#2467)
|
||||||
|
|
||||||
|
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
The MIT License (MIT)
|
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
|
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
|
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
|
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.
|
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))
|
).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:]
|
traceback = traceback[:max_frames] + traceback[-max_frames:]
|
||||||
else:
|
else:
|
||||||
extraline = "!!! Recursion detected (same locals & position)"
|
if recursionindex is not None:
|
||||||
traceback = traceback[:recursionindex + 1]
|
extraline = "!!! Recursion detected (same locals & position)"
|
||||||
|
traceback = traceback[:recursionindex + 1]
|
||||||
|
else:
|
||||||
|
extraline = None
|
||||||
|
|
||||||
return traceback, extraline
|
return traceback, extraline
|
||||||
|
|
||||||
|
|
|
@ -162,10 +162,6 @@ class AssertionRewritingHook(object):
|
||||||
# modules not passed explicitly on the command line are only
|
# modules not passed explicitly on the command line are only
|
||||||
# rewritten if they match the naming convention for test files
|
# rewritten if they match the naming convention for test files
|
||||||
for pat in self.fnpats:
|
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):
|
if fn_pypath.fnmatch(pat):
|
||||||
state.trace("matched test file %r" % (fn,))
|
state.trace("matched test file %r" % (fn,))
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -177,7 +177,6 @@ class DoctestTextfile(pytest.Module):
|
||||||
name = self.fspath.basename
|
name = self.fspath.basename
|
||||||
globs = {'__name__': '__main__'}
|
globs = {'__name__': '__main__'}
|
||||||
|
|
||||||
|
|
||||||
optionflags = get_optionflags(self)
|
optionflags = get_optionflags(self)
|
||||||
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
|
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
|
||||||
checker=_get_checker())
|
checker=_get_checker())
|
||||||
|
@ -218,9 +217,6 @@ class DoctestModule(pytest.Module):
|
||||||
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
|
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
|
||||||
checker=_get_checker())
|
checker=_get_checker())
|
||||||
|
|
||||||
encoding = self.config.getini("doctest_encoding")
|
|
||||||
_fix_spoof_python2(runner, encoding)
|
|
||||||
|
|
||||||
for test in finder.find(module, module.__name__):
|
for test in finder.find(module, module.__name__):
|
||||||
if test.examples: # skip empty doctests
|
if test.examples: # skip empty doctests
|
||||||
yield DoctestItem(test.name, self, runner, test)
|
yield DoctestItem(test.name, self, runner, test)
|
||||||
|
@ -332,7 +328,10 @@ def _get_report_choice(key):
|
||||||
|
|
||||||
def _fix_spoof_python2(runner, encoding):
|
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.
|
This fixes the problem related in issue #2434.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -733,10 +733,19 @@ class FixtureDef:
|
||||||
self._finalizer.append(finalizer)
|
self._finalizer.append(finalizer)
|
||||||
|
|
||||||
def finish(self):
|
def finish(self):
|
||||||
|
exceptions = []
|
||||||
try:
|
try:
|
||||||
while self._finalizer:
|
while self._finalizer:
|
||||||
func = self._finalizer.pop()
|
try:
|
||||||
func()
|
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:
|
finally:
|
||||||
ihook = self._fixturemanager.session.ihook
|
ihook = self._fixturemanager.session.ihook
|
||||||
ihook.pytest_fixture_post_finalizer(fixturedef=self)
|
ihook.pytest_fixture_post_finalizer(fixturedef=self)
|
||||||
|
|
|
@ -73,7 +73,9 @@ def pytest_configure(config):
|
||||||
|
|
||||||
@hookspec(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_cmdline_parse(pluginmanager, args):
|
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):
|
def pytest_cmdline_preparse(config, args):
|
||||||
"""(deprecated) modify command line arguments before option parsing. """
|
"""(deprecated) modify command line arguments before option parsing. """
|
||||||
|
@ -81,7 +83,9 @@ def pytest_cmdline_preparse(config, args):
|
||||||
@hookspec(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_cmdline_main(config):
|
def pytest_cmdline_main(config):
|
||||||
""" called for performing the main command line action. The default
|
""" 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):
|
def pytest_load_initial_conftests(early_config, parser, args):
|
||||||
""" implements the loading of initial conftest files ahead
|
""" implements the loading of initial conftest files ahead
|
||||||
|
@ -94,7 +98,9 @@ def pytest_load_initial_conftests(early_config, parser, args):
|
||||||
|
|
||||||
@hookspec(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_collection(session):
|
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):
|
def pytest_collection_modifyitems(session, config, items):
|
||||||
""" called after collection has been performed, may filter or re-order
|
""" 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.
|
""" return True to prevent considering this path for collection.
|
||||||
This hook is consulted for all files and directories prior to calling
|
This hook is consulted for all files and directories prior to calling
|
||||||
more specific hooks.
|
more specific hooks.
|
||||||
|
|
||||||
|
Stops at first non-None result, see :ref:`firstresult`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@hookspec(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_collect_directory(path, parent):
|
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):
|
def pytest_collect_file(path, parent):
|
||||||
""" return collection Node or None for the given path. Any new node
|
""" return collection Node or None for the given path. Any new node
|
||||||
|
@ -133,7 +143,9 @@ def pytest_deselected(items):
|
||||||
|
|
||||||
@hookspec(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_make_collect_report(collector):
|
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
|
# 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.
|
This hook will be called for each matching test module path.
|
||||||
The pytest_collect_file hook needs to be used if you want to
|
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.
|
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)
|
@hookspec(firstresult=True)
|
||||||
def pytest_pycollect_makeitem(collector, name, obj):
|
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)
|
@hookspec(firstresult=True)
|
||||||
def pytest_pyfunc_call(pyfuncitem):
|
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):
|
def pytest_generate_tests(metafunc):
|
||||||
""" generate (multiple) parametrized calls to a test function."""
|
""" 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
|
"""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``.
|
by @pytest.mark.parametrize calls. Return None if the hook doesn't know about ``val``.
|
||||||
The parameter name is available as ``argname``, if required.
|
The parameter name is available as ``argname``, if required.
|
||||||
"""
|
|
||||||
|
Stops at first non-None result, see :ref:`firstresult` """
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# generic runtest related hooks
|
# generic runtest related hooks
|
||||||
|
@ -172,7 +190,9 @@ def pytest_make_parametrize_id(config, val, argname):
|
||||||
@hookspec(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_runtestloop(session):
|
def pytest_runtestloop(session):
|
||||||
""" called for performing the main runtest loop
|
""" 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):
|
def pytest_itemstart(item, node):
|
||||||
""" (deprecated, use pytest_runtest_logstart). """
|
""" (deprecated, use pytest_runtest_logstart). """
|
||||||
|
@ -190,7 +210,9 @@ def pytest_runtest_protocol(item, nextitem):
|
||||||
:py:func:`pytest_runtest_teardown`.
|
:py:func:`pytest_runtest_teardown`.
|
||||||
|
|
||||||
:return boolean: True if no further hook implementations should be invoked.
|
: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):
|
def pytest_runtest_logstart(nodeid, location):
|
||||||
""" signal the start of running a single test item. """
|
""" 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
|
""" return a :py:class:`_pytest.runner.TestReport` object
|
||||||
for the given :py:class:`pytest.Item <_pytest.main.Item>` and
|
for the given :py:class:`pytest.Item <_pytest.main.Item>` and
|
||||||
:py:class:`_pytest.runner.CallInfo`.
|
:py:class:`_pytest.runner.CallInfo`.
|
||||||
"""
|
|
||||||
|
Stops at first non-None result, see :ref:`firstresult` """
|
||||||
|
|
||||||
def pytest_runtest_logreport(report):
|
def pytest_runtest_logreport(report):
|
||||||
""" process a test setup/call/teardown report relating to
|
""" process a test setup/call/teardown report relating to
|
||||||
|
@ -227,7 +250,9 @@ def pytest_runtest_logreport(report):
|
||||||
|
|
||||||
@hookspec(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_fixture_setup(fixturedef, request):
|
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):
|
def pytest_fixture_post_finalizer(fixturedef):
|
||||||
""" called after fixture teardown, but before the cache is cleared so
|
""" called after fixture teardown, but before the cache is cleared so
|
||||||
|
@ -277,7 +302,9 @@ def pytest_report_header(config, startdir):
|
||||||
|
|
||||||
@hookspec(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_report_teststatus(report):
|
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):
|
def pytest_terminal_summary(terminalreporter, exitstatus):
|
||||||
""" add additional section in terminal summary reporting. """
|
""" add additional section in terminal summary reporting. """
|
||||||
|
@ -295,7 +322,9 @@ def pytest_logwarning(message, code, nodeid, fslocation):
|
||||||
|
|
||||||
@hookspec(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_doctest_prepare_content(content):
|
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
|
# 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 == "()":
|
if not has_matched and len(rep.result) == 1 and x.name == "()":
|
||||||
nextnames.insert(0, name)
|
nextnames.insert(0, name)
|
||||||
resultnodes.extend(self.matchnodes([x], nextnames))
|
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
|
return resultnodes
|
||||||
|
|
||||||
def genitems(self, node):
|
def genitems(self, node):
|
||||||
|
|
|
@ -27,10 +27,8 @@ def recwarn():
|
||||||
|
|
||||||
|
|
||||||
def deprecated_call(func=None, *args, **kwargs):
|
def deprecated_call(func=None, *args, **kwargs):
|
||||||
""" assert that calling ``func(*args, **kwargs)`` triggers a
|
"""context manager that can be used to ensure a block of code triggers a
|
||||||
``DeprecationWarning`` or ``PendingDeprecationWarning``.
|
``DeprecationWarning`` or ``PendingDeprecationWarning``::
|
||||||
|
|
||||||
This function can be used as a context manager::
|
|
||||||
|
|
||||||
>>> import warnings
|
>>> import warnings
|
||||||
>>> def api_call_v2():
|
>>> def api_call_v2():
|
||||||
|
@ -40,38 +38,47 @@ def deprecated_call(func=None, *args, **kwargs):
|
||||||
>>> with deprecated_call():
|
>>> with deprecated_call():
|
||||||
... assert api_call_v2() == 200
|
... assert api_call_v2() == 200
|
||||||
|
|
||||||
Note: we cannot use WarningsRecorder here because it is still subject
|
``deprecated_call`` can also be used by passing a function and ``*args`` and ``*kwargs``,
|
||||||
to the mechanism that prevents warnings of the same type from being
|
in which case it will ensure calling ``func(*args, **kwargs)`` produces one of the warnings
|
||||||
triggered twice for the same module. See #1190.
|
types above.
|
||||||
"""
|
"""
|
||||||
if not func:
|
if not func:
|
||||||
return WarningsChecker(expected_warning=(DeprecationWarning, PendingDeprecationWarning))
|
return _DeprecatedCallContext()
|
||||||
|
else:
|
||||||
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):
|
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
raise AssertionError("%r did not produce DeprecationWarning" % (func,))
|
with _DeprecatedCallContext():
|
||||||
return ret
|
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):
|
def warns(expected_warning, *args, **kwargs):
|
||||||
|
|
|
@ -282,7 +282,7 @@ class TerminalReporter:
|
||||||
line = "collected "
|
line = "collected "
|
||||||
else:
|
else:
|
||||||
line = "collecting "
|
line = "collecting "
|
||||||
line += str(self._numcollected) + " items"
|
line += str(self._numcollected) + " item" + ('' if self._numcollected == 1 else 's')
|
||||||
if errors:
|
if errors:
|
||||||
line += " / %d errors" % errors
|
line += " / %d errors" % errors
|
||||||
if skipped:
|
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 %}
|
{% endif %}
|
||||||
{% if sections[section] %}
|
{% 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'] }}
|
{{ definitions[category]['name'] }}
|
||||||
{{ underline * definitions[category]['name']|length }}
|
{{ underline * definitions[category]['name']|length }}
|
||||||
|
|
||||||
{% if definitions[category]['showcontent'] %}
|
{% if definitions[category]['showcontent'] %}
|
||||||
{% for text, values in sections[section][category]|dictsort(by='value') %}
|
{% 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 %}
|
{% 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
|
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||||
collected 1 items
|
collected 1 items
|
||||||
|
|
||||||
test_smtpsimple.py F
|
test_smtpsimple.py F
|
||||||
|
|
||||||
======= FAILURES ========
|
======= FAILURES ========
|
||||||
_______ test_ehlo ________
|
_______ test_ehlo ________
|
||||||
|
|
||||||
smtp = <smtplib.SMTP object at 0xdeadbeef>
|
smtp = <smtplib.SMTP object at 0xdeadbeef>
|
||||||
|
|
||||||
def test_ehlo(smtp):
|
def test_ehlo(smtp):
|
||||||
response, msg = smtp.ehlo()
|
response, msg = smtp.ehlo()
|
||||||
assert response == 250
|
assert response == 250
|
||||||
> assert 0 # for demo purposes
|
> assert 0 # for demo purposes
|
||||||
E assert 0
|
E assert 0
|
||||||
|
|
||||||
test_smtpsimple.py:11: AssertionError
|
test_smtpsimple.py:11: AssertionError
|
||||||
======= 1 failed in 0.12 seconds ========
|
======= 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
|
but is not anymore advertised as the primary means of declaring fixture
|
||||||
functions.
|
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
|
Fixtures allow test functions to easily receive and work
|
||||||
term "funcargs" or "funcarg mechanism" which continues to be present
|
against specific pre-initialized application objects without having
|
||||||
also in docs today. It now refers to the specific case of injecting
|
to care about import/setup/cleanup details.
|
||||||
fixture values as arguments to test functions. With pytest-2.3 there are
|
It's a prime example of `dependency injection`_ where fixture
|
||||||
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
|
|
||||||
functions take the role of the *injector* and test functions are the
|
functions take the role of the *injector* and test functions are the
|
||||||
*consumers* of fixture objects.
|
*consumers* of fixture objects.
|
||||||
|
|
||||||
|
@ -176,7 +169,7 @@ function (in or below the directory where ``conftest.py`` is located)::
|
||||||
response, msg = smtp.ehlo()
|
response, msg = smtp.ehlo()
|
||||||
assert response == 250
|
assert response == 250
|
||||||
assert b"smtp.gmail.com" in msg
|
assert b"smtp.gmail.com" in msg
|
||||||
assert 0 # for demo purposes
|
assert 0 # for demo purposes
|
||||||
|
|
||||||
def test_noop(smtp):
|
def test_noop(smtp):
|
||||||
response, msg = smtp.noop()
|
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
|
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||||
collected 2 items
|
collected 2 items
|
||||||
|
|
||||||
test_module.py FF
|
test_module.py FF
|
||||||
|
|
||||||
======= FAILURES ========
|
======= FAILURES ========
|
||||||
_______ test_ehlo ________
|
_______ test_ehlo ________
|
||||||
|
|
||||||
smtp = <smtplib.SMTP object at 0xdeadbeef>
|
smtp = <smtplib.SMTP object at 0xdeadbeef>
|
||||||
|
|
||||||
def test_ehlo(smtp):
|
def test_ehlo(smtp):
|
||||||
response, msg = smtp.ehlo()
|
response, msg = smtp.ehlo()
|
||||||
assert response == 250
|
assert response == 250
|
||||||
assert b"smtp.gmail.com" in msg
|
assert b"smtp.gmail.com" in msg
|
||||||
> assert 0 # for demo purposes
|
> assert 0 # for demo purposes
|
||||||
E assert 0
|
E assert 0
|
||||||
|
|
||||||
test_module.py:6: AssertionError
|
test_module.py:6: AssertionError
|
||||||
_______ test_noop ________
|
_______ test_noop ________
|
||||||
|
|
||||||
smtp = <smtplib.SMTP object at 0xdeadbeef>
|
smtp = <smtplib.SMTP object at 0xdeadbeef>
|
||||||
|
|
||||||
def test_noop(smtp):
|
def test_noop(smtp):
|
||||||
response, msg = smtp.noop()
|
response, msg = smtp.noop()
|
||||||
assert response == 250
|
assert response == 250
|
||||||
> assert 0 # for demo purposes
|
> assert 0 # for demo purposes
|
||||||
E assert 0
|
E assert 0
|
||||||
|
|
||||||
test_module.py:11: AssertionError
|
test_module.py:11: AssertionError
|
||||||
======= 2 failed in 0.12 seconds ========
|
======= 2 failed in 0.12 seconds ========
|
||||||
|
|
||||||
|
@ -267,7 +260,7 @@ Let's execute it::
|
||||||
|
|
||||||
$ pytest -s -q --tb=no
|
$ pytest -s -q --tb=no
|
||||||
FFteardown smtp
|
FFteardown smtp
|
||||||
|
|
||||||
2 failed in 0.12 seconds
|
2 failed in 0.12 seconds
|
||||||
|
|
||||||
We see that the ``smtp`` instance is finalized after the two
|
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
|
because the ``smtp`` object automatically closes when
|
||||||
the ``with`` statement ends.
|
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::
|
.. note::
|
||||||
Prior to version 2.10, in order to use a ``yield`` statement to execute teardown code one
|
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
|
fixtures can use ``yield`` directly so the ``yield_fixture`` decorator is no longer needed
|
||||||
and considered deprecated.
|
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
|
Here's the ``smtp`` fixture changed to use ``addfinalizer`` for cleanup:
|
||||||
import pytest
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
.. code-block:: python
|
||||||
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
|
|
||||||
|
|
||||||
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`:
|
.. _`request-context`:
|
||||||
|
|
||||||
|
@ -355,7 +373,7 @@ again, nothing much has changed::
|
||||||
|
|
||||||
$ pytest -s -q --tb=no
|
$ pytest -s -q --tb=no
|
||||||
FFfinalizing <smtplib.SMTP object at 0xdeadbeef> (smtp.gmail.com)
|
FFfinalizing <smtplib.SMTP object at 0xdeadbeef> (smtp.gmail.com)
|
||||||
|
|
||||||
2 failed in 0.12 seconds
|
2 failed in 0.12 seconds
|
||||||
|
|
||||||
Let's quickly create another test module that actually sets the
|
Let's quickly create another test module that actually sets the
|
||||||
|
@ -423,51 +441,51 @@ So let's just do another run::
|
||||||
FFFF
|
FFFF
|
||||||
======= FAILURES ========
|
======= FAILURES ========
|
||||||
_______ test_ehlo[smtp.gmail.com] ________
|
_______ test_ehlo[smtp.gmail.com] ________
|
||||||
|
|
||||||
smtp = <smtplib.SMTP object at 0xdeadbeef>
|
smtp = <smtplib.SMTP object at 0xdeadbeef>
|
||||||
|
|
||||||
def test_ehlo(smtp):
|
def test_ehlo(smtp):
|
||||||
response, msg = smtp.ehlo()
|
response, msg = smtp.ehlo()
|
||||||
assert response == 250
|
assert response == 250
|
||||||
assert b"smtp.gmail.com" in msg
|
assert b"smtp.gmail.com" in msg
|
||||||
> assert 0 # for demo purposes
|
> assert 0 # for demo purposes
|
||||||
E assert 0
|
E assert 0
|
||||||
|
|
||||||
test_module.py:6: AssertionError
|
test_module.py:6: AssertionError
|
||||||
_______ test_noop[smtp.gmail.com] ________
|
_______ test_noop[smtp.gmail.com] ________
|
||||||
|
|
||||||
smtp = <smtplib.SMTP object at 0xdeadbeef>
|
smtp = <smtplib.SMTP object at 0xdeadbeef>
|
||||||
|
|
||||||
def test_noop(smtp):
|
def test_noop(smtp):
|
||||||
response, msg = smtp.noop()
|
response, msg = smtp.noop()
|
||||||
assert response == 250
|
assert response == 250
|
||||||
> assert 0 # for demo purposes
|
> assert 0 # for demo purposes
|
||||||
E assert 0
|
E assert 0
|
||||||
|
|
||||||
test_module.py:11: AssertionError
|
test_module.py:11: AssertionError
|
||||||
_______ test_ehlo[mail.python.org] ________
|
_______ test_ehlo[mail.python.org] ________
|
||||||
|
|
||||||
smtp = <smtplib.SMTP object at 0xdeadbeef>
|
smtp = <smtplib.SMTP object at 0xdeadbeef>
|
||||||
|
|
||||||
def test_ehlo(smtp):
|
def test_ehlo(smtp):
|
||||||
response, msg = smtp.ehlo()
|
response, msg = smtp.ehlo()
|
||||||
assert response == 250
|
assert response == 250
|
||||||
> assert b"smtp.gmail.com" in msg
|
> 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'
|
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
|
test_module.py:5: AssertionError
|
||||||
-------------------------- Captured stdout setup ---------------------------
|
-------------------------- Captured stdout setup ---------------------------
|
||||||
finalizing <smtplib.SMTP object at 0xdeadbeef>
|
finalizing <smtplib.SMTP object at 0xdeadbeef>
|
||||||
_______ test_noop[mail.python.org] ________
|
_______ test_noop[mail.python.org] ________
|
||||||
|
|
||||||
smtp = <smtplib.SMTP object at 0xdeadbeef>
|
smtp = <smtplib.SMTP object at 0xdeadbeef>
|
||||||
|
|
||||||
def test_noop(smtp):
|
def test_noop(smtp):
|
||||||
response, msg = smtp.noop()
|
response, msg = smtp.noop()
|
||||||
assert response == 250
|
assert response == 250
|
||||||
> assert 0 # for demo purposes
|
> assert 0 # for demo purposes
|
||||||
E assert 0
|
E assert 0
|
||||||
|
|
||||||
test_module.py:11: AssertionError
|
test_module.py:11: AssertionError
|
||||||
------------------------- Captured stdout teardown -------------------------
|
------------------------- Captured stdout teardown -------------------------
|
||||||
finalizing <smtplib.SMTP object at 0xdeadbeef>
|
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_noop[smtp.gmail.com]'>
|
||||||
<Function 'test_ehlo[mail.python.org]'>
|
<Function 'test_ehlo[mail.python.org]'>
|
||||||
<Function 'test_noop[mail.python.org]'>
|
<Function 'test_noop[mail.python.org]'>
|
||||||
|
|
||||||
======= no tests ran in 0.12 seconds ========
|
======= no tests ran in 0.12 seconds ========
|
||||||
|
|
||||||
.. _`interdependent fixtures`:
|
.. _`interdependent fixtures`:
|
||||||
|
@ -578,10 +596,10 @@ Here we declare an ``app`` fixture which receives the previously defined
|
||||||
cachedir: .cache
|
cachedir: .cache
|
||||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||||
collecting ... collected 2 items
|
collecting ... collected 2 items
|
||||||
|
|
||||||
test_appsetup.py::test_smtp_exists[smtp.gmail.com] PASSED
|
test_appsetup.py::test_smtp_exists[smtp.gmail.com] PASSED
|
||||||
test_appsetup.py::test_smtp_exists[mail.python.org] PASSED
|
test_appsetup.py::test_smtp_exists[mail.python.org] PASSED
|
||||||
|
|
||||||
======= 2 passed in 0.12 seconds ========
|
======= 2 passed in 0.12 seconds ========
|
||||||
|
|
||||||
Due to the parametrization of ``smtp`` the test will run twice with two
|
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
|
cachedir: .cache
|
||||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||||
collecting ... collected 8 items
|
collecting ... collected 8 items
|
||||||
|
|
||||||
test_module.py::test_0[1] SETUP otherarg 1
|
test_module.py::test_0[1] SETUP otherarg 1
|
||||||
RUN test0 with otherarg 1
|
RUN test0 with otherarg 1
|
||||||
PASSED TEARDOWN otherarg 1
|
PASSED TEARDOWN otherarg 1
|
||||||
|
|
||||||
test_module.py::test_0[2] SETUP otherarg 2
|
test_module.py::test_0[2] SETUP otherarg 2
|
||||||
RUN test0 with otherarg 2
|
RUN test0 with otherarg 2
|
||||||
PASSED TEARDOWN otherarg 2
|
PASSED TEARDOWN otherarg 2
|
||||||
|
|
||||||
test_module.py::test_1[mod1] SETUP modarg mod1
|
test_module.py::test_1[mod1] SETUP modarg mod1
|
||||||
RUN test1 with modarg mod1
|
RUN test1 with modarg mod1
|
||||||
PASSED
|
PASSED
|
||||||
test_module.py::test_2[1-mod1] SETUP otherarg 1
|
test_module.py::test_2[1-mod1] SETUP otherarg 1
|
||||||
RUN test2 with otherarg 1 and modarg mod1
|
RUN test2 with otherarg 1 and modarg mod1
|
||||||
PASSED TEARDOWN otherarg 1
|
PASSED TEARDOWN otherarg 1
|
||||||
|
|
||||||
test_module.py::test_2[2-mod1] SETUP otherarg 2
|
test_module.py::test_2[2-mod1] SETUP otherarg 2
|
||||||
RUN test2 with otherarg 2 and modarg mod1
|
RUN test2 with otherarg 2 and modarg mod1
|
||||||
PASSED TEARDOWN otherarg 2
|
PASSED TEARDOWN otherarg 2
|
||||||
|
|
||||||
test_module.py::test_1[mod2] TEARDOWN modarg mod1
|
test_module.py::test_1[mod2] TEARDOWN modarg mod1
|
||||||
SETUP modarg mod2
|
SETUP modarg mod2
|
||||||
RUN test1 with 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
|
test_module.py::test_2[1-mod2] SETUP otherarg 1
|
||||||
RUN test2 with otherarg 1 and modarg mod2
|
RUN test2 with otherarg 1 and modarg mod2
|
||||||
PASSED TEARDOWN otherarg 1
|
PASSED TEARDOWN otherarg 1
|
||||||
|
|
||||||
test_module.py::test_2[2-mod2] SETUP otherarg 2
|
test_module.py::test_2[2-mod2] SETUP otherarg 2
|
||||||
RUN test2 with otherarg 2 and modarg mod2
|
RUN test2 with otherarg 2 and modarg mod2
|
||||||
PASSED TEARDOWN otherarg 2
|
PASSED TEARDOWN otherarg 2
|
||||||
TEARDOWN modarg mod2
|
TEARDOWN modarg mod2
|
||||||
|
|
||||||
|
|
||||||
======= 8 passed in 0.12 seconds ========
|
======= 8 passed in 0.12 seconds ========
|
||||||
|
|
||||||
You can see that the parametrized module-scoped ``modarg`` resource caused an
|
You can see that the parametrized module-scoped ``modarg`` resource caused an
|
||||||
|
@ -782,8 +800,8 @@ Autouse fixtures (xUnit setup on steroids)
|
||||||
.. regendoc:wipe
|
.. regendoc:wipe
|
||||||
|
|
||||||
Occasionally, you may want to have fixtures get invoked automatically
|
Occasionally, you may want to have fixtures get invoked automatically
|
||||||
without a `usefixtures`_ or `funcargs`_ reference. As a practical
|
without declaring a function argument explicitly or a `usefixtures`_ decorator.
|
||||||
example, suppose we have a database fixture which has a
|
As a practical example, suppose we have a database fixture which has a
|
||||||
begin/rollback/commit architecture and we want to automatically surround
|
begin/rollback/commit architecture and we want to automatically surround
|
||||||
each test method by a transaction and a rollback. Here is a dummy
|
each test method by a transaction and a rollback. Here is a dummy
|
||||||
self-contained implementation of this idea::
|
self-contained implementation of this idea::
|
||||||
|
|
|
@ -83,8 +83,8 @@ Consult the :ref:`Changelog <changelog>` page for fixes and enhancements of each
|
||||||
License
|
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.
|
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)
|
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
|
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
|
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.
|
of the variable will also be loaded as plugins, and so on.
|
||||||
|
|
||||||
This mechanism makes it easy to share fixtures within applications or even
|
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.
|
the ``setuptools``'s entry point technique.
|
||||||
|
|
||||||
Plugins imported by ``pytest_plugins`` will also automatically be marked
|
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
|
However for this to have any effect the module must not be
|
||||||
imported already; if it was already imported at the time the
|
imported already; if it was already imported at the time the
|
||||||
``pytest_plugins`` statement is processed, a warning will result and
|
``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
|
firstresult: stop at first non-None result
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -31,5 +31,5 @@ template = "changelog/_template.rst"
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
[[tool.towncrier.type]]
|
||||||
directory = "trivial"
|
directory = "trivial"
|
||||||
name = "Trivial Changes"
|
name = "Trivial/Internal Changes"
|
||||||
showcontent = false
|
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
|
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()
|
assert 'sessionstarttime' not in result.stderr.str()
|
||||||
|
|
||||||
@pytest.mark.parametrize('lookfor', ['test_fun.py', 'test_fun.py::test_a'])
|
@pytest.mark.parametrize('lookfor', ['test_fun.py::test_a'])
|
||||||
def test_issue134_report_syntaxerror_when_collecting_member(self, testdir, lookfor):
|
def test_issue134_report_error_when_collecting_member(self, testdir, lookfor):
|
||||||
testdir.makepyfile(test_fun="""
|
testdir.makepyfile(test_fun="""
|
||||||
def test_a():
|
def test_a():
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
import sys
|
||||||
import operator
|
import operator
|
||||||
import _pytest
|
import _pytest
|
||||||
import py
|
import py
|
||||||
|
@ -1173,3 +1174,25 @@ def test_exception_repr_extraction_error_on_recursion():
|
||||||
'*The following exception happened*',
|
'*The following exception happened*',
|
||||||
'*ValueError: The truth value of an array*',
|
'*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
|
"*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):
|
def test_request_getmodulepath(self, testdir):
|
||||||
modcol = testdir.getmodulecol("def test_somefunc(): pass")
|
modcol = testdir.getmodulecol("def test_somefunc(): pass")
|
||||||
item, = testdir.genitems([modcol])
|
item, = testdir.genitems([modcol])
|
||||||
|
|
|
@ -369,6 +369,11 @@ class TestSession(object):
|
||||||
assert len(colitems) == 1
|
assert len(colitems) == 1
|
||||||
assert colitems[0].fspath == p
|
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):
|
def test_collect_protocol_single_function(self, testdir):
|
||||||
p = testdir.makepyfile("def test_func(): pass")
|
p = testdir.makepyfile("def test_func(): pass")
|
||||||
|
@ -386,9 +391,10 @@ class TestSession(object):
|
||||||
("pytest_collectstart", "collector.fspath == p"),
|
("pytest_collectstart", "collector.fspath == p"),
|
||||||
("pytest_make_collect_report", "collector.fspath == p"),
|
("pytest_make_collect_report", "collector.fspath == p"),
|
||||||
("pytest_pycollect_makeitem", "name == 'test_func'"),
|
("pytest_pycollect_makeitem", "name == 'test_func'"),
|
||||||
("pytest_collectreport", "report.nodeid.startswith(p.basename)"),
|
("pytest_collectreport", "report.result[0].name == 'test_func'"),
|
||||||
("pytest_collectreport", "report.nodeid == ''")
|
|
||||||
])
|
])
|
||||||
|
# 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):
|
def test_collect_protocol_method(self, testdir):
|
||||||
p = testdir.makepyfile("""
|
p = testdir.makepyfile("""
|
||||||
|
@ -407,6 +413,8 @@ class TestSession(object):
|
||||||
assert items[0].name == "test_method"
|
assert items[0].name == "test_method"
|
||||||
newid = items[0].nodeid
|
newid = items[0].nodeid
|
||||||
assert newid == normid
|
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):
|
def test_collect_custom_nodes_multi_id(self, testdir):
|
||||||
p = testdir.makepyfile("def test_func(): pass")
|
p = testdir.makepyfile("def test_func(): pass")
|
||||||
|
@ -436,9 +444,8 @@ class TestSession(object):
|
||||||
"collector.__class__.__name__ == 'Module'"),
|
"collector.__class__.__name__ == 'Module'"),
|
||||||
("pytest_pycollect_makeitem", "name == 'test_func'"),
|
("pytest_pycollect_makeitem", "name == 'test_func'"),
|
||||||
("pytest_collectreport", "report.nodeid.startswith(p.basename)"),
|
("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):
|
def test_collect_subdir_event_ordering(self, testdir):
|
||||||
p = testdir.makepyfile("def test_func(): pass")
|
p = testdir.makepyfile("def test_func(): pass")
|
||||||
|
@ -495,11 +502,13 @@ class TestSession(object):
|
||||||
def test_method(self):
|
def test_method(self):
|
||||||
pass
|
pass
|
||||||
""")
|
""")
|
||||||
arg = p.basename + ("::TestClass::test_method")
|
arg = p.basename + "::TestClass::test_method"
|
||||||
items, hookrec = testdir.inline_genitems(arg)
|
items, hookrec = testdir.inline_genitems(arg)
|
||||||
assert len(items) == 1
|
assert len(items) == 1
|
||||||
item, = items
|
item, = items
|
||||||
assert item.nodeid.endswith("TestClass::()::test_method")
|
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):
|
class Test_getinitialnodes(object):
|
||||||
def test_global_file(self, testdir, tmpdir):
|
def test_global_file(self, testdir, tmpdir):
|
||||||
|
|
|
@ -527,6 +527,25 @@ class TestDoctests(object):
|
||||||
'*1 failed*',
|
'*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):
|
class TestLiterals(object):
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,7 @@ class TestDeprecatedCall(object):
|
||||||
def test_deprecated_call_raises(self):
|
def test_deprecated_call_raises(self):
|
||||||
with pytest.raises(AssertionError) as excinfo:
|
with pytest.raises(AssertionError) as excinfo:
|
||||||
pytest.deprecated_call(self.dep, 3, 5)
|
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):
|
def test_deprecated_call(self):
|
||||||
pytest.deprecated_call(self.dep, 0, 5)
|
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)
|
||||||
pytest.deprecated_call(self.dep_explicit, 0)
|
pytest.deprecated_call(self.dep_explicit, 0)
|
||||||
|
|
||||||
def test_deprecated_call_as_context_manager_no_warning(self):
|
@pytest.mark.parametrize('mode', ['context_manager', 'call'])
|
||||||
with pytest.raises(pytest.fail.Exception, matches='^DID NOT WARN'):
|
def test_deprecated_call_no_warning(self, mode):
|
||||||
with pytest.deprecated_call():
|
"""Ensure deprecated_call() raises the expected failure when its block/function does
|
||||||
self.dep(1)
|
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('warning_type', [PendingDeprecationWarning, DeprecationWarning])
|
||||||
@pytest.mark.parametrize('mode', ['context_manager', 'call'])
|
@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():
|
def f():
|
||||||
warnings.warn(warning_type("hi"))
|
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':
|
if mode == 'call':
|
||||||
pytest.deprecated_call(f)
|
assert pytest.deprecated_call(f) == 10
|
||||||
else:
|
else:
|
||||||
with pytest.deprecated_call():
|
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):
|
def test_deprecated_call_specificity(self):
|
||||||
other_warnings = [Warning, UserWarning, SyntaxWarning, RuntimeWarning,
|
other_warnings = [Warning, UserWarning, SyntaxWarning, RuntimeWarning,
|
||||||
FutureWarning, ImportWarning, UnicodeWarning]
|
FutureWarning, ImportWarning, UnicodeWarning]
|
||||||
for warning in other_warnings:
|
for warning in other_warnings:
|
||||||
def f():
|
def f():
|
||||||
py.std.warnings.warn(warning("hi"))
|
warnings.warn(warning("hi"))
|
||||||
|
|
||||||
with pytest.raises(AssertionError):
|
with pytest.raises(AssertionError):
|
||||||
pytest.deprecated_call(f)
|
pytest.deprecated_call(f)
|
||||||
|
with pytest.raises(AssertionError):
|
||||||
|
with pytest.deprecated_call():
|
||||||
|
f()
|
||||||
|
|
||||||
def test_deprecated_function_already_called(self, testdir):
|
def test_deprecated_function_already_called(self, testdir):
|
||||||
"""deprecated_call should be able to catch a call to a deprecated
|
"""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
|
assert 1
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest()
|
result = testdir.runpytest()
|
||||||
result.stdout.fnmatch_lines('*collected 1 items*')
|
result.stdout.fnmatch_lines('*collected 1 item*')
|
||||||
result.stdout.fnmatch_lines('*1 passed*')
|
result.stdout.fnmatch_lines('*1 passed*')
|
||||||
assert result.ret == main.EXIT_OK
|
assert result.ret == main.EXIT_OK
|
||||||
|
|
||||||
result = testdir.runpytest('-k nonmatch')
|
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*')
|
result.stdout.fnmatch_lines('*1 deselected*')
|
||||||
assert result.ret == main.EXIT_NOTESTSCOLLECTED
|
assert result.ret == main.EXIT_NOTESTSCOLLECTED
|
||||||
|
|
||||||
|
|
|
@ -204,6 +204,15 @@ class TestTerminal(object):
|
||||||
assert result.ret == 2
|
assert result.ret == 2
|
||||||
result.stdout.fnmatch_lines(['*KeyboardInterrupt*'])
|
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):
|
class TestCollectonly(object):
|
||||||
def test_collectonly_basic(self, testdir):
|
def test_collectonly_basic(self, testdir):
|
||||||
|
|
2
tox.ini
2
tox.ini
|
@ -57,9 +57,7 @@ deps =
|
||||||
# pygments required by rst-lint
|
# pygments required by rst-lint
|
||||||
pygments
|
pygments
|
||||||
restructuredtext_lint
|
restructuredtext_lint
|
||||||
check-manifest
|
|
||||||
commands =
|
commands =
|
||||||
{envpython} scripts/check-manifest.py
|
|
||||||
flake8 pytest.py _pytest testing
|
flake8 pytest.py _pytest testing
|
||||||
{envpython} scripts/check-rst.py
|
{envpython} scripts/check-rst.py
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue