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

# Conflicts:
#	AUTHORS
#	CHANGELOG.rst
#	_pytest/pytester.py
This commit is contained in:
Bruno Oliveira 2017-03-10 15:54:05 -03:00
commit 1e0cf5ce4d
33 changed files with 310 additions and 97 deletions

View File

@ -8,34 +8,37 @@ install: "pip install -U tox"
env: env:
matrix: matrix:
# coveralls is not listed in tox's envlist, but should run in travis # coveralls is not listed in tox's envlist, but should run in travis
- TESTENV=coveralls - TOXENV=coveralls
# note: please use "tox --listenvs" to populate the build matrix below # note: please use "tox --listenvs" to populate the build matrix below
- TESTENV=linting - TOXENV=linting
- TESTENV=py26 - TOXENV=py26
- TESTENV=py27 - TOXENV=py27
- TESTENV=py33 - TOXENV=py33
- TESTENV=py34 - TOXENV=py34
- TESTENV=py35 - TOXENV=py35
- TESTENV=pypy - TOXENV=pypy
- TESTENV=py27-pexpect - TOXENV=py27-pexpect
- TESTENV=py27-xdist - TOXENV=py27-xdist
- TESTENV=py27-trial - TOXENV=py27-trial
- TESTENV=py35-pexpect - TOXENV=py35-pexpect
- TESTENV=py35-xdist - TOXENV=py35-xdist
- TESTENV=py35-trial - TOXENV=py35-trial
- TESTENV=py27-nobyte - TOXENV=py27-nobyte
- TESTENV=doctesting - TOXENV=doctesting
- TESTENV=freeze - TOXENV=freeze
- TESTENV=docs - TOXENV=docs
matrix: matrix:
include: include:
- env: TESTENV=py36 - env: TOXENV=py36
python: '3.6-dev' python: '3.6-dev'
- env: TESTENV=py37 - env: TOXENV=py37
python: 'nightly'
allow_failures:
- env: TOXENV=py37
python: 'nightly' python: 'nightly'
script: tox --recreate -e $TESTENV script: tox --recreate
notifications: notifications:
irc: irc:

View File

@ -17,6 +17,7 @@ Anthony Sottile
Armin Rigo Armin Rigo
Aron Curzon Aron Curzon
Aviv Palivoda Aviv Palivoda
Barney Gale
Ben Webb Ben Webb
Benjamin Peterson Benjamin Peterson
Bernard Pratz Bernard Pratz
@ -43,6 +44,7 @@ Dave Hunt
David Díaz-Barquero David Díaz-Barquero
David Mohr David Mohr
David Vierra David Vierra
Denis Kirisov
Diego Russo Diego Russo
Dmitry Dygalo Dmitry Dygalo
Duncan Betts Duncan Betts
@ -117,11 +119,14 @@ Nicolas Delaby
Oleg Pidsadnyi Oleg Pidsadnyi
Oliver Bestwalter Oliver Bestwalter
Omar Kohl Omar Kohl
Omer Hadari
Patrick Hayes
Pieter Mulder Pieter Mulder
Piotr Banaszkiewicz Piotr Banaszkiewicz
Punyashloka Biswal Punyashloka Biswal
Quentin Pradet Quentin Pradet
Ralf Schmitt Ralf Schmitt
Ran Benita
Raphael Pierzina Raphael Pierzina
Raquel Alegre Raquel Alegre
Ravi Chandra Ravi Chandra
@ -148,5 +153,6 @@ Tyler Goodlet
Vasily Kuznetsov Vasily Kuznetsov
Victor Uriarte Victor Uriarte
Vlad Dragos Vlad Dragos
Vidar T. Fauske
Wouter van Ackooy Wouter van Ackooy
Xuecong Liao Xuecong Liao

View File

@ -27,6 +27,11 @@ Changes
``__test__`` attribute to ``False`` in the class body (`#2007`_). Thanks ``__test__`` attribute to ``False`` in the class body (`#2007`_). Thanks
to `@syre`_ for the report and `@lwm`_ for the PR. to `@syre`_ for the report and `@lwm`_ for the PR.
* Change junitxml.py to produce reports that comply with Junitxml schema.
If the same test fails with failure in call and then errors in teardown
we split testcase element into two, one containing the error and the other
the failure. (`#2228`_) Thanks to `@kkoukiou`_ for the PR.
* Testcase reports with a ``url`` attribute will now properly write this to junitxml. * Testcase reports with a ``url`` attribute will now properly write this to junitxml.
Thanks `@fushi`_ for the PR (`#1874`_). Thanks `@fushi`_ for the PR (`#1874`_).
@ -73,6 +78,7 @@ Bug Fixes
.. _@unsignedint: https://github.com/unsignedint .. _@unsignedint: https://github.com/unsignedint
.. _@Kriechi: https://github.com/Kriechi .. _@Kriechi: https://github.com/Kriechi
.. _#1407: https://github.com/pytest-dev/pytest/issues/1407 .. _#1407: https://github.com/pytest-dev/pytest/issues/1407
.. _#1512: https://github.com/pytest-dev/pytest/issues/1512 .. _#1512: https://github.com/pytest-dev/pytest/issues/1512
.. _#1874: https://github.com/pytest-dev/pytest/pull/1874 .. _#1874: https://github.com/pytest-dev/pytest/pull/1874
@ -83,24 +89,65 @@ Bug Fixes
.. _#2166: https://github.com/pytest-dev/pytest/pull/2166 .. _#2166: https://github.com/pytest-dev/pytest/pull/2166
.. _#2147: https://github.com/pytest-dev/pytest/issues/2147 .. _#2147: https://github.com/pytest-dev/pytest/issues/2147
.. _#2208: https://github.com/pytest-dev/pytest/issues/2208 .. _#2208: https://github.com/pytest-dev/pytest/issues/2208
.. _#2228: https://github.com/pytest-dev/pytest/issues/2228
3.0.7 (unreleased) 3.0.7 (unreleased)
======================= ==================
* Change junitxml.py to produce reports that comply with Junitxml schema.
If the same test fails with failure in call and then errors in teardown * Fix issue in assertion rewriting breaking due to modules silently discarding
we split testcase element into two, one containing the error and the other other modules when importing fails
the failure. (`#2228`_) Thanks to `@kkoukiou`_ for the PR. Notably, importing the `anydbm` module is fixed. (`#2248`_).
Thanks `@pfhayes`_ for the PR.
*
* junitxml: Fix problematic case where system-out tag occured twice per testcase
* element in the XML report. Thanks `@kkoukiou`_ for the PR.
* Fix regression, pytest now skips unittest correctly if run with ``--pdb``
(`#2137`_). Thanks to `@gst`_ for the report and `@mbyt`_ for the PR.
* Ignore exceptions raised from descriptors (e.g. properties) during Python test collection (`#2234`_).
Thanks to `@bluetech`_.
* ``--override-ini`` now correctly overrides some fundamental options like ``python_files`` (`#2238`_).
Thanks `@sirex`_ for the report and `@nicoddemus`_ for the PR.
* Replace ``raise StopIteration`` usages in the code by simple ``returns`` to finish generators, in accordance to `PEP-479`_ (`#2160`_).
Thanks `@tgoodlet`_ for the report and `@nicoddemus`_ for the PR.
* Fix internal errors when an unprintable ``AssertionError`` is raised inside a test.
Thanks `@omerhadari`_ for the PR.
* Skipping plugin now also works with test items generated by custom collectors (`#2231`_).
Thanks to `@vidartf`_.
* Fix trailing whitespace in console output if no .ini file presented (`#2281`_). Thanks `@fbjorn`_ for the PR.
* Conditionless ``xfail`` markers no longer rely on the underlying test item
being an instance of ``PyobjMixin``, and can therefore apply to tests not
collected by the built-in python test collector. Thanks `@barneygale`_ for the
PR.
* *
.. _@pfhayes: https://github.com/pfhayes
.. _@bluetech: https://github.com/bluetech
.. _@gst: https://github.com/gst
.. _@sirex: https://github.com/sirex
.. _@vidartf: https://github.com/vidartf
.. _@kkoukiou: https://github.com/KKoukiou .. _@kkoukiou: https://github.com/KKoukiou
.. _@omerhadari: https://github.com/omerhadari
.. _@fbjorn: https://github.com/fbjorn
.. _#2228: https://github.com/pytest-dev/pytest/issues/2228 .. _#2248: https://github.com/pytest-dev/pytest/issues/2248
.. _#2137: https://github.com/pytest-dev/pytest/issues/2137
.. _#2160: https://github.com/pytest-dev/pytest/issues/2160
.. _#2231: https://github.com/pytest-dev/pytest/issues/2231
.. _#2234: https://github.com/pytest-dev/pytest/issues/2234
.. _#2238: https://github.com/pytest-dev/pytest/issues/2238
.. _#2281: https://github.com/pytest-dev/pytest/issues/2281
.. _PEP-479: https://www.python.org/dev/peps/pep-0479/
3.0.6 (2017-01-22) 3.0.6 (2017-01-22)
@ -135,6 +182,7 @@ Bug Fixes
terminal output it relies on is missing. Thanks to `@eli-b`_ for the PR. terminal output it relies on is missing. Thanks to `@eli-b`_ for the PR.
.. _@barneygale: https://github.com/barneygale
.. _@lesteve: https://github.com/lesteve .. _@lesteve: https://github.com/lesteve
.. _@malinoff: https://github.com/malinoff .. _@malinoff: https://github.com/malinoff
.. _@pelme: https://github.com/pelme .. _@pelme: https://github.com/pelme
@ -2467,7 +2515,7 @@ Bug fixes:
teardown function are called earlier. teardown function are called earlier.
- add an all-powerful metafunc.parametrize function which allows to - add an all-powerful metafunc.parametrize function which allows to
parametrize test function arguments in multiple steps and therefore parametrize test function arguments in multiple steps and therefore
from indepdenent plugins and palces. from independent plugins and places.
- add a @pytest.mark.parametrize helper which allows to easily - add a @pytest.mark.parametrize helper which allows to easily
call a test function with different argument values call a test function with different argument values
- Add examples to the "parametrize" example page, including a quick port - Add examples to the "parametrize" example page, including a quick port

View File

@ -352,6 +352,8 @@ class ExceptionInfo(object):
help for navigating the traceback. help for navigating the traceback.
""" """
_striptext = '' _striptext = ''
_assert_start_repr = "AssertionError(u\'assert " if sys.version_info[0] < 3 else "AssertionError(\'assert "
def __init__(self, tup=None, exprinfo=None): def __init__(self, tup=None, exprinfo=None):
import _pytest._code import _pytest._code
if tup is None: if tup is None:
@ -359,8 +361,8 @@ class ExceptionInfo(object):
if exprinfo is None and isinstance(tup[1], AssertionError): if exprinfo is None and isinstance(tup[1], AssertionError):
exprinfo = getattr(tup[1], 'msg', None) exprinfo = getattr(tup[1], 'msg', None)
if exprinfo is None: if exprinfo is None:
exprinfo = py._builtin._totext(tup[1]) exprinfo = py.io.saferepr(tup[1])
if exprinfo and exprinfo.startswith('assert '): if exprinfo and exprinfo.startswith(self._assert_start_repr):
self._striptext = 'AssertionError: ' self._striptext = 'AssertionError: '
self._excinfo = tup self._excinfo = tup
#: the exception class #: the exception class

View File

@ -215,6 +215,7 @@ class AssertionRewritingHook(object):
mod.__loader__ = self mod.__loader__ = self
py.builtin.exec_(co, mod.__dict__) py.builtin.exec_(co, mod.__dict__)
except: except:
if name in sys.modules:
del sys.modules[name] del sys.modules[name]
raise raise
return sys.modules[name] return sys.modules[name]

View File

@ -1,7 +1,7 @@
""" """
merged implementation of the cache provider merged implementation of the cache provider
the name cache was not choosen to ensure pluggy automatically the name cache was not chosen to ensure pluggy automatically
ignores the external pytest-cache ignores the external pytest-cache
""" """

View File

@ -877,6 +877,7 @@ class Config(object):
self.trace = self.pluginmanager.trace.root.get("config") self.trace = self.pluginmanager.trace.root.get("config")
self.hook = self.pluginmanager.hook self.hook = self.pluginmanager.hook
self._inicache = {} self._inicache = {}
self._override_ini = ()
self._opt2dest = {} self._opt2dest = {}
self._cleanup = [] self._cleanup = []
self._warn = self.pluginmanager._warn self._warn = self.pluginmanager._warn
@ -977,6 +978,7 @@ class Config(object):
self.invocation_dir = py.path.local() self.invocation_dir = py.path.local()
self._parser.addini('addopts', 'extra command line options', 'args') self._parser.addini('addopts', 'extra command line options', 'args')
self._parser.addini('minversion', 'minimally required pytest version') self._parser.addini('minversion', 'minimally required pytest version')
self._override_ini = ns.override_ini or ()
def _consider_importhook(self, args, entrypoint_name): def _consider_importhook(self, args, entrypoint_name):
"""Install the PEP 302 import hook if using assertion re-writing. """Install the PEP 302 import hook if using assertion re-writing.
@ -1159,8 +1161,7 @@ class Config(object):
# and -o foo1=bar1 -o foo2=bar2 options # and -o foo1=bar1 -o foo2=bar2 options
# always use the last item if multiple value set for same ini-name, # always use the last item if multiple value set for same ini-name,
# e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2 # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2
if self.getoption("override_ini", None): for ini_config_list in self._override_ini:
for ini_config_list in self.option.override_ini:
for ini_config in ini_config_list: for ini_config in ini_config_list:
try: try:
(key, user_ini_value) = ini_config.split("=", 1) (key, user_ini_value) = ini_config.split("=", 1)

View File

@ -14,6 +14,7 @@ from _pytest.compat import (
getfslineno, get_real_func, getfslineno, get_real_func,
is_generator, isclass, getimfunc, is_generator, isclass, getimfunc,
getlocation, getfuncargnames, getlocation, getfuncargnames,
safe_getattr,
) )
def pytest_sessionstart(session): def pytest_sessionstart(session):
@ -124,8 +125,6 @@ def getfixturemarker(obj):
exceptions.""" exceptions."""
try: try:
return getattr(obj, "_pytestfixturefunction", None) return getattr(obj, "_pytestfixturefunction", None)
except KeyboardInterrupt:
raise
except Exception: except Exception:
# some objects raise errors like request (from flask import request) # some objects raise errors like request (from flask import request)
# we don't expect them to be fixture functions # we don't expect them to be fixture functions
@ -1068,7 +1067,9 @@ class FixtureManager(object):
self._holderobjseen.add(holderobj) self._holderobjseen.add(holderobj)
autousenames = [] autousenames = []
for name in dir(holderobj): for name in dir(holderobj):
obj = getattr(holderobj, name, None) # The attribute can be an arbitrary descriptor, so the attribute
# access below can raise. safe_getatt() ignores such exceptions.
obj = safe_getattr(holderobj, name, None)
# fixture functions have a pytest_funcarg__ prefix (pre-2.3 style) # fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
# or are "@pytest.fixture" marked # or are "@pytest.fixture" marked
marker = getfixturemarker(obj) marker = getfixturemarker(obj)

View File

@ -247,7 +247,7 @@ def pytest_unconfigure(config):
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# hooks for customising the assert methods # hooks for customizing the assert methods
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
def pytest_assertrepr_compare(config, op, left, right): def pytest_assertrepr_compare(config, op, left, right):
@ -256,7 +256,7 @@ def pytest_assertrepr_compare(config, op, left, right):
Return None for no custom explanation, otherwise return a list Return None for no custom explanation, otherwise return a list
of strings. The strings will be joined by newlines but any newlines of strings. The strings will be joined by newlines but any newlines
*in* a string will be escaped. Note that all but the first line will *in* a string will be escaped. Note that all but the first line will
be indented sligthly, the intention is for the first line to be a summary. be indented slightly, the intention is for the first line to be a summary.
""" """
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@ -264,7 +264,14 @@ def pytest_assertrepr_compare(config, op, left, right):
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
def pytest_report_header(config, startdir): def pytest_report_header(config, startdir):
""" return a string to be displayed as header info for terminal reporting.""" """ return a string to be displayed as header info for terminal reporting.
.. note::
This function should be implemented only in plugins or ``conftest.py``
files situated at the tests root directory due to how pytest
:ref:`discovers plugins during startup <pluginorder>`.
"""
@hookspec(firstresult=True) @hookspec(firstresult=True)
def pytest_report_teststatus(report): def pytest_report_teststatus(report):

View File

@ -121,7 +121,7 @@ class _NodeReporter(object):
node = kind(data, message=message) node = kind(data, message=message)
self.append(node) self.append(node)
def _write_captured_output(self, report): def write_captured_output(self, report):
for capname in ('out', 'err'): for capname in ('out', 'err'):
content = getattr(report, 'capstd' + capname) content = getattr(report, 'capstd' + capname)
if content: if content:
@ -130,7 +130,6 @@ class _NodeReporter(object):
def append_pass(self, report): def append_pass(self, report):
self.add_stats('passed') self.add_stats('passed')
self._write_captured_output(report)
def append_failure(self, report): def append_failure(self, report):
# msg = str(report.longrepr.reprtraceback.extraline) # msg = str(report.longrepr.reprtraceback.extraline)
@ -149,7 +148,6 @@ class _NodeReporter(object):
fail = Junit.failure(message=message) fail = Junit.failure(message=message)
fail.append(bin_xml_escape(report.longrepr)) fail.append(bin_xml_escape(report.longrepr))
self.append(fail) self.append(fail)
self._write_captured_output(report)
def append_collect_error(self, report): def append_collect_error(self, report):
# msg = str(report.longrepr.reprtraceback.extraline) # msg = str(report.longrepr.reprtraceback.extraline)
@ -167,7 +165,6 @@ class _NodeReporter(object):
msg = "test setup failure" msg = "test setup failure"
self._add_simple( self._add_simple(
Junit.error, msg, report.longrepr) Junit.error, msg, report.longrepr)
self._write_captured_output(report)
def append_skipped(self, report): def append_skipped(self, report):
if hasattr(report, "wasxfail"): if hasattr(report, "wasxfail"):
@ -182,7 +179,7 @@ class _NodeReporter(object):
Junit.skipped("%s:%s: %s" % (filename, lineno, skipreason), Junit.skipped("%s:%s: %s" % (filename, lineno, skipreason),
type="pytest.skip", type="pytest.skip",
message=skipreason)) message=skipreason))
self._write_captured_output(report) self.write_captured_output(report)
def finalize(self): def finalize(self):
data = self.to_xml().unicode(indent=0) data = self.to_xml().unicode(indent=0)
@ -369,6 +366,8 @@ class LogXML(object):
reporter.append_skipped(report) reporter.append_skipped(report)
self.update_testcase_duration(report) self.update_testcase_duration(report)
if report.when == "teardown": if report.when == "teardown":
reporter = self._opentestcase(report)
reporter.write_captured_output(report)
self.finalize(report) self.finalize(report)
report_wid = getattr(report, "worker_id", None) report_wid = getattr(report, "worker_id", None)
report_ii = getattr(report, "item_index", None) report_ii = getattr(report, "item_index", None)

View File

@ -81,7 +81,7 @@ def pytest_namespace():
def pytest_configure(config): def pytest_configure(config):
pytest.config = config # compatibiltiy pytest.config = config # compatibility
def wrap_session(config, doit): def wrap_session(config, doit):

View File

@ -72,7 +72,7 @@ def pytest_collection_modifyitems(items, config):
return return
# pytest used to allow "-" for negating # pytest used to allow "-" for negating
# but today we just allow "-" at the beginning, use "not" instead # but today we just allow "-" at the beginning, use "not" instead
# we probably remove "-" alltogether soon # we probably remove "-" altogether soon
if keywordexpr.startswith("-"): if keywordexpr.startswith("-"):
keywordexpr = "not " + keywordexpr[1:] keywordexpr = "not " + keywordexpr[1:]
selectuntil = False selectuntil = False

View File

@ -335,7 +335,7 @@ def testdir(request, tmpdir_factory):
return Testdir(request, tmpdir_factory) return Testdir(request, tmpdir_factory)
rex_outcome = re.compile("(\d+) ([\w-]+)") rex_outcome = re.compile(r"(\d+) ([\w-]+)")
class RunResult(object): class RunResult(object):
"""The result of running a command. """The result of running a command.
@ -570,7 +570,7 @@ class Testdir(object):
def mkpydir(self, name): def mkpydir(self, name):
"""Create a new python package. """Create a new python package.
This creates a (sub)direcotry with an empty ``__init__.py`` This creates a (sub)directory with an empty ``__init__.py``
file so that is recognised as a python package. file so that is recognised as a python package.
""" """
@ -665,7 +665,7 @@ class Testdir(object):
def inline_genitems(self, *args): def inline_genitems(self, *args):
"""Run ``pytest.main(['--collectonly'])`` in-process. """Run ``pytest.main(['--collectonly'])`` in-process.
Retuns a tuple of the collected items and a Returns a tuple of the collected items and a
:py:class:`HookRecorder` instance. :py:class:`HookRecorder` instance.
This runs the :py:func:`pytest.main` function to run all of This runs the :py:func:`pytest.main` function to run all of
@ -863,7 +863,7 @@ class Testdir(object):
:py:meth:`parseconfigure`. :py:meth:`parseconfigure`.
:param withinit: Whether to also write a ``__init__.py`` file :param withinit: Whether to also write a ``__init__.py`` file
to the temporarly directory to ensure it is a package. to the temporary directory to ensure it is a package.
""" """
kw = {self.request.function.__name__: Source(source).strip()} kw = {self.request.function.__name__: Source(source).strip()}

View File

@ -174,7 +174,7 @@ def pytest_pycollect_makeitem(collector, name, obj):
outcome = yield outcome = yield
res = outcome.get_result() res = outcome.get_result()
if res is not None: if res is not None:
raise StopIteration return
# nothing was collected elsewhere, let's do it here # nothing was collected elsewhere, let's do it here
if isclass(obj): if isclass(obj):
if collector.istestclass(obj, name): if collector.istestclass(obj, name):
@ -632,7 +632,7 @@ class Generator(FunctionMixin, PyCollector):
def getcallargs(self, obj): def getcallargs(self, obj):
if not isinstance(obj, (tuple, list)): if not isinstance(obj, (tuple, list)):
obj = (obj,) obj = (obj,)
# explict naming # explicit naming
if isinstance(obj[0], py.builtin._basestring): if isinstance(obj[0], py.builtin._basestring):
name = obj[0] name = obj[0]
obj = obj[1:] obj = obj[1:]

View File

@ -112,6 +112,7 @@ class MarkEvaluator(object):
def _getglobals(self): def _getglobals(self):
d = {'os': os, 'sys': sys, 'config': self.item.config} d = {'os': os, 'sys': sys, 'config': self.item.config}
if hasattr(self.item, 'obj'):
d.update(self.item.obj.__globals__) d.update(self.item.obj.__globals__)
return d return d
@ -119,7 +120,6 @@ class MarkEvaluator(object):
if hasattr(self, 'result'): if hasattr(self, 'result'):
return self.result return self.result
if self.holder: if self.holder:
d = self._getglobals()
if self.holder.args or 'condition' in self.holder.kwargs: if self.holder.args or 'condition' in self.holder.kwargs:
self.result = False self.result = False
# "holder" might be a MarkInfo or a MarkDecorator; only # "holder" might be a MarkInfo or a MarkDecorator; only
@ -133,6 +133,7 @@ class MarkEvaluator(object):
for expr in args: for expr in args:
self.expr = expr self.expr = expr
if isinstance(expr, py.builtin._basestring): if isinstance(expr, py.builtin._basestring):
d = self._getglobals()
result = cached_eval(self.item.config, expr, d) result = cached_eval(self.item.config, expr, d)
else: else:
if "reason" not in kwargs: if "reason" not in kwargs:

View File

@ -295,7 +295,7 @@ class TerminalReporter(object):
def pytest_report_header(self, config): def pytest_report_header(self, config):
inifile = "" inifile = ""
if config.inifile: if config.inifile:
inifile = config.rootdir.bestrelpath(config.inifile) inifile = " " + config.rootdir.bestrelpath(config.inifile)
lines = ["rootdir: %s, inifile:%s" % (config.rootdir, inifile)] lines = ["rootdir: %s, inifile:%s" % (config.rootdir, inifile)]
plugininfo = config.pluginmanager.list_plugin_distinfo() plugininfo = config.pluginmanager.list_plugin_distinfo()

View File

@ -116,7 +116,7 @@ def tmpdir(request, tmpdir_factory):
path object. path object.
""" """
name = request.node.name name = request.node.name
name = re.sub("[\W]", "_", name) name = re.sub(r"[\W]", "_", name)
MAXVAL = 30 MAXVAL = 30
if len(name) > MAXVAL: if len(name) > MAXVAL:
name = name[:MAXVAL] name = name[:MAXVAL]

View File

@ -5,7 +5,7 @@ import sys
import traceback import traceback
import pytest import pytest
# for transfering markers # for transferring markers
import _pytest._code import _pytest._code
from _pytest.python import transfer_markers from _pytest.python import transfer_markers
from _pytest.skipping import MarkEvaluator from _pytest.skipping import MarkEvaluator
@ -65,7 +65,6 @@ class UnitTestCase(pytest.Class):
yield TestCaseFunction('runTest', parent=self) yield TestCaseFunction('runTest', parent=self)
class TestCaseFunction(pytest.Function): class TestCaseFunction(pytest.Function):
_excinfo = None _excinfo = None
@ -152,14 +151,33 @@ class TestCaseFunction(pytest.Function):
def stopTest(self, testcase): def stopTest(self, testcase):
pass pass
def _handle_skip(self):
# implements the skipping machinery (see #2137)
# analog to pythons Lib/unittest/case.py:run
testMethod = getattr(self._testcase, self._testcase._testMethodName)
if (getattr(self._testcase.__class__, "__unittest_skip__", False) or
getattr(testMethod, "__unittest_skip__", False)):
# If the class or method was skipped.
skip_why = (getattr(self._testcase.__class__, '__unittest_skip_why__', '') or
getattr(testMethod, '__unittest_skip_why__', ''))
try: # PY3, unittest2 on PY2
self._testcase._addSkip(self, self._testcase, skip_why)
except TypeError: # PY2
if sys.version_info[0] != 2:
raise
self._testcase._addSkip(self, skip_why)
return True
return False
def runtest(self): def runtest(self):
if self.config.pluginmanager.get_plugin("pdbinvoke") is None: if self.config.pluginmanager.get_plugin("pdbinvoke") is None:
self._testcase(result=self) self._testcase(result=self)
else: else:
# disables tearDown and cleanups for post mortem debugging (see #1890) # disables tearDown and cleanups for post mortem debugging (see #1890)
if self._handle_skip():
return
self._testcase.debug() self._testcase.debug()
def _prunetraceback(self, excinfo): def _prunetraceback(self, excinfo):
pytest.Function._prunetraceback(self, excinfo) pytest.Function._prunetraceback(self, excinfo)
traceback = excinfo.traceback.filter( traceback = excinfo.traceback.filter(

View File

@ -243,7 +243,9 @@ Fixture finalization / executing teardown code
pytest supports execution of fixture specific finalization code pytest supports execution of fixture specific finalization code
when the fixture goes out of scope. By using a ``yield`` statement instead of ``return``, all when the fixture goes out of scope. By using a ``yield`` statement instead of ``return``, all
the code after the *yield* statement serves as the teardown code.:: the code after the *yield* statement serves as the teardown code:
.. code-block:: python
# content of conftest.py # content of conftest.py
@ -275,22 +277,23 @@ occur around each single test. In either case the test
module itself does not need to change or know about these details module itself does not need to change or know about these details
of fixture setup. of fixture setup.
Note that we can also seamlessly use the ``yield`` syntax with ``with`` statements:: Note that we can also seamlessly use the ``yield`` syntax with ``with`` statements:
.. code-block:: python
# content of test_yield2.py # content of test_yield2.py
import smtplib
import pytest import pytest
@pytest.fixture @pytest.fixture(scope="module")
def passwd(): def smtp(request):
with open("/etc/passwd") as f: with smtplib.SMTP("smtp.gmail.com") as smtp:
yield f.readlines() yield smtp # provide the fixture value
def test_has_lines(passwd):
assert len(passwd) >= 1
The file ``f`` will be closed after the test finished execution The ``smtp`` connection will be closed after the test finished execution
because the Python ``file`` object supports finalization when because the ``smtp`` object automatically closes when
the ``with`` statement ends. the ``with`` statement ends.

View File

@ -4,7 +4,9 @@ Talks and Tutorials
.. sidebar:: Next Open Trainings .. sidebar:: Next Open Trainings
`pytest workshop <http://www.meetup.com/Python-Django-User-Group-Bern/events/235151115/>`_, 8th December 2016, Bern, Switzerland `Professional Testing with Python
<http://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_,
26-28 April 2017, Leipzig, Germany.
.. _`funcargs`: funcargs.html .. _`funcargs`: funcargs.html

View File

@ -236,20 +236,31 @@ import ``helper.py`` normally. The contents of
Requiring/Loading plugins in a test module or conftest file Requiring/Loading plugins in a test module or conftest file
----------------------------------------------------------- -----------------------------------------------------------
You can require plugins in a test module or a conftest file like this:: You can require plugins in a test module or a ``conftest.py`` file like this:
pytest_plugins = "name1", "name2", .. code-block:: python
pytest_plugins = ["name1", "name2"]
When the test module or conftest plugin is loaded the specified plugins When the test module or conftest plugin is loaded the specified plugins
will be loaded as well. You can also use dotted path like this:: will be loaded as well. Any module can be blessed as a plugin, including internal
application modules:
.. code-block:: python
pytest_plugins = "myapp.testsupport.myplugin" pytest_plugins = "myapp.testsupport.myplugin"
which will import the specified module as a ``pytest`` plugin. ``pytest_plugins`` variables are processed recursively, so note that in the example above
if ``myapp.testsupport.myplugin`` also declares ``pytest_plugins``, the contents
of the variable will also be loaded as plugins, and so on.
Plugins imported like this will automatically be marked to require This mechanism makes it easy to share fixtures within applications or even
assertion rewriting using the :func:`pytest.register_assert_rewrite` external applications without the need to create external plugins using
mechanism. However for this to have any effect the module must not be 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`).
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
assertions inside the plugin will not be re-written. To fix this you assertions inside the plugin will not be re-written. To fix this you

View File

@ -166,6 +166,16 @@ class TestClass(object):
"because it has a __new__ constructor*" "because it has a __new__ constructor*"
) )
def test_issue2234_property(self, testdir):
testdir.makepyfile("""
class TestCase(object):
@property
def prop(self):
raise NotImplementedError()
""")
result = testdir.runpytest()
assert result.ret == EXIT_NOTESTSCOLLECTED
class TestGenerator(object): class TestGenerator(object):
def test_generative_functions(self, testdir): def test_generative_functions(self, testdir):

View File

@ -365,7 +365,7 @@ class TestAssert_reprcompare(object):
expl = '\n'.join(callequal(left, right, verbose=True)) expl = '\n'.join(callequal(left, right, verbose=True))
assert expl.endswith(textwrap.dedent(expected).strip()) assert expl.endswith(textwrap.dedent(expected).strip())
def test_list_different_lenghts(self): def test_list_different_lengths(self):
expl = callequal([0, 1], [0, 1, 2]) expl = callequal([0, 1], [0, 1, 2])
assert len(expl) > 1 assert len(expl) > 1
expl = callequal([0, 1, 2], [0, 1]) expl = callequal([0, 1, 2], [0, 1])
@ -996,6 +996,25 @@ def test_assert_with_unicode(monkeypatch, testdir):
result = testdir.runpytest() result = testdir.runpytest()
result.stdout.fnmatch_lines(['*AssertionError*']) result.stdout.fnmatch_lines(['*AssertionError*'])
def test_raise_unprintable_assertion_error(testdir):
testdir.makepyfile(r"""
def test_raise_assertion_error():
raise AssertionError('\xff')
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines([r"> raise AssertionError('\xff')", 'E AssertionError: *'])
def test_raise_assertion_error_raisin_repr(testdir):
testdir.makepyfile(u"""
class RaisingRepr(object):
def __repr__(self):
raise Exception()
def test_raising_repr():
raise AssertionError(RaisingRepr())
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines(['E AssertionError: <unprintable AssertionError object>'])
def test_issue_1944(testdir): def test_issue_1944(testdir):
testdir.makepyfile(""" testdir.makepyfile("""
def f(): def f():

View File

@ -271,7 +271,7 @@ class TestAssertionRewrite(object):
getmsg(f, must_pass=True) getmsg(f, must_pass=True)
def test_short_circut_evaluation(self): def test_short_circuit_evaluation(self):
def f(): def f():
assert True or explode # noqa assert True or explode # noqa

View File

@ -604,7 +604,7 @@ def test_capture_binary_output(testdir):
def test_error_during_readouterr(testdir): def test_error_during_readouterr(testdir):
"""Make sure we suspend capturing if errors occurr during readouterr""" """Make sure we suspend capturing if errors occur during readouterr"""
testdir.makepyfile(pytest_xyz=""" testdir.makepyfile(pytest_xyz="""
from _pytest.capture import FDCapture from _pytest.capture import FDCapture
def bad_snap(self): def bad_snap(self):

View File

@ -778,6 +778,21 @@ class TestOverrideIniArgs(object):
result = testdir.runpytest("--override-ini", 'xdist_strict True', "-s") result = testdir.runpytest("--override-ini", 'xdist_strict True', "-s")
result.stderr.fnmatch_lines(["*ERROR* *expects option=value*"]) result.stderr.fnmatch_lines(["*ERROR* *expects option=value*"])
@pytest.mark.parametrize('with_ini', [True, False])
def test_override_ini_handled_asap(self, testdir, with_ini):
"""-o should be handled as soon as possible and always override what's in ini files (#2238)"""
if with_ini:
testdir.makeini("""
[pytest]
python_files=test_*.py
""")
testdir.makepyfile(unittest_ini_handle="""
def test():
pass
""")
result = testdir.runpytest("--override-ini", 'python_files=unittest_*.py')
result.stdout.fnmatch_lines(["*1 passed in*"])
def test_with_arg_outside_cwd_without_inifile(self, tmpdir, monkeypatch): def test_with_arg_outside_cwd_without_inifile(self, tmpdir, monkeypatch):
monkeypatch.chdir(str(tmpdir)) monkeypatch.chdir(str(tmpdir))
a = tmpdir.mkdir("a") a = tmpdir.mkdir("a")

View File

@ -580,6 +580,25 @@ class TestPython(object):
systemout = pnode.find_first_by_tag("system-err") systemout = pnode.find_first_by_tag("system-err")
assert "hello-stderr" in systemout.toxml() assert "hello-stderr" in systemout.toxml()
def test_avoid_double_stdout(self, testdir):
testdir.makepyfile("""
import sys
import pytest
@pytest.fixture
def arg(request):
yield
sys.stdout.write('hello-stdout teardown')
raise ValueError()
def test_function(arg):
sys.stdout.write('hello-stdout call')
""")
result, dom = runandparse(testdir)
node = dom.find_first_by_tag("testsuite")
pnode = node.find_first_by_tag("testcase")
systemout = pnode.find_first_by_tag("system-out")
assert "hello-stdout call" in systemout.toxml()
assert "hello-stdout teardown" in systemout.toxml()
def test_mangle_test_address(): def test_mangle_test_address():
from _pytest.junitxml import mangle_test_address from _pytest.junitxml import mangle_test_address

View File

@ -126,6 +126,21 @@ class TestPDB(object):
assert 'debug.me' in rest assert 'debug.me' in rest
self.flush(child) self.flush(child)
def test_pdb_unittest_skip(self, testdir):
"""Test for issue #2137"""
p1 = testdir.makepyfile("""
import unittest
@unittest.skipIf(True, 'Skipping also with pdb active')
class MyTestCase(unittest.TestCase):
def test_one(self):
assert 0
""")
child = testdir.spawn_pytest("-rs --pdb %s" % p1)
child.expect('Skipping also with pdb active')
child.expect('1 skipped in')
child.sendeof()
self.flush(child)
def test_pdb_interaction_capture(self, testdir): def test_pdb_interaction_capture(self, testdir):
p1 = testdir.makepyfile(""" p1 = testdir.makepyfile("""
def test_1(): def test_1():

View File

@ -197,7 +197,7 @@ class TestNewSession(SessionTests):
colfail = [x for x in finished if x.failed] colfail = [x for x in finished if x.failed]
assert len(colfail) == 1 assert len(colfail) == 1
def test_minus_x_overriden_by_maxfail(self, testdir): def test_minus_x_overridden_by_maxfail(self, testdir):
testdir.makepyfile(__init__="") testdir.makepyfile(__init__="")
testdir.makepyfile(test_one="xxxx", test_two="yyyy", test_third="zzz") testdir.makepyfile(test_one="xxxx", test_two="yyyy", test_third="zzz")
reprec = testdir.inline_run("-x", "--maxfail=2", testdir.tmpdir) reprec = testdir.inline_run("-x", "--maxfail=2", testdir.tmpdir)

View File

@ -969,3 +969,26 @@ def test_module_level_skip_error(testdir):
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
"*Using pytest.skip outside of a test is not allowed*" "*Using pytest.skip outside of a test is not allowed*"
) )
def test_mark_xfail_item(testdir):
# Ensure pytest.mark.xfail works with non-Python Item
testdir.makeconftest("""
import pytest
class MyItem(pytest.Item):
nodeid = 'foo'
def setup(self):
marker = pytest.mark.xfail(True, reason="Expected failure")
self.add_marker(marker)
def runtest(self):
assert False
def pytest_collect_file(path, parent):
return MyItem("foo", parent)
""")
result = testdir.inline_run()
passed, skipped, failed = result.listoutcomes()
assert not failed
xfailed = [r for r in skipped if hasattr(r, 'wasxfail')]
assert xfailed

View File

@ -906,3 +906,12 @@ def test_summary_stats(exp_line, exp_color, stats_arg):
print("Actually got: \"%s\"; with color \"%s\"" % (line, color)) print("Actually got: \"%s\"; with color \"%s\"" % (line, color))
assert line == exp_line assert line == exp_line
assert color == exp_color assert color == exp_color
def test_no_trailing_whitespace_after_inifile_word(testdir):
result = testdir.runpytest('')
assert 'inifile:\n' in result.stdout.str()
testdir.makeini('[pytest]')
result = testdir.runpytest('')
assert 'inifile: tox.ini\n' in result.stdout.str()

View File

@ -1,7 +1,7 @@
[tox] [tox]
minversion=2.0 minversion=2.0
distshare={homedir}/.tox/distshare distshare={homedir}/.tox/distshare
# make sure to update enviroment list on appveyor.yml # make sure to update environment list on appveyor.yml
envlist= envlist=
linting linting
py26 py26