summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruno Oliveira <nicoddemus@gmail.com>2017-03-10 15:54:05 -0300
committerBruno Oliveira <nicoddemus@gmail.com>2017-03-10 15:54:05 -0300
commit1e0cf5ce4dcd8b110a45261595d69a502dd0226a (patch)
treee5b7993a41db97332a15920f1fa7642ac31750e3
parent0c94f517a18bc135fe80a07a64c8c74c27b41dcb (diff)
parent906b40fbb209916ea13199d5391c2e64b3587103 (diff)
downloadpytest-1e0cf5ce4dcd8b110a45261595d69a502dd0226a.tar.gz
Merge remote-tracking branch 'upstream/master' into merge-master-into-features
# Conflicts: # AUTHORS # CHANGELOG.rst # _pytest/pytester.py
-rw-r--r--.travis.yml45
-rw-r--r--AUTHORS6
-rw-r--r--CHANGELOG.rst66
-rw-r--r--_pytest/_code/code.py6
-rw-r--r--_pytest/assertion/rewrite.py3
-rwxr-xr-x_pytest/cacheprovider.py2
-rw-r--r--_pytest/config.py19
-rw-r--r--_pytest/fixtures.py7
-rw-r--r--_pytest/hookspec.py13
-rw-r--r--_pytest/junitxml.py9
-rw-r--r--_pytest/main.py2
-rw-r--r--_pytest/mark.py2
-rw-r--r--_pytest/pytester.py8
-rw-r--r--_pytest/python.py4
-rw-r--r--_pytest/skipping.py5
-rw-r--r--_pytest/terminal.py4
-rw-r--r--_pytest/tmpdir.py2
-rw-r--r--_pytest/unittest.py24
-rw-r--r--doc/en/assert.rst2
-rw-r--r--doc/en/fixture.rst23
-rw-r--r--doc/en/talks.rst4
-rw-r--r--doc/en/writing_plugins.rst25
-rw-r--r--testing/python/collect.py10
-rw-r--r--testing/test_assertion.py21
-rw-r--r--testing/test_assertrewrite.py2
-rw-r--r--testing/test_capture.py2
-rw-r--r--testing/test_config.py17
-rw-r--r--testing/test_junitxml.py19
-rw-r--r--testing/test_pdb.py15
-rw-r--r--testing/test_session.py2
-rw-r--r--testing/test_skipping.py23
-rw-r--r--testing/test_terminal.py9
-rw-r--r--tox.ini2
33 files changed, 308 insertions, 95 deletions
diff --git a/.travis.yml b/.travis.yml
index f701302d9..41537a466 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,34 +8,37 @@ install: "pip install -U tox"
env:
matrix:
# 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
- - TESTENV=linting
- - TESTENV=py26
- - TESTENV=py27
- - TESTENV=py33
- - TESTENV=py34
- - TESTENV=py35
- - TESTENV=pypy
- - TESTENV=py27-pexpect
- - TESTENV=py27-xdist
- - TESTENV=py27-trial
- - TESTENV=py35-pexpect
- - TESTENV=py35-xdist
- - TESTENV=py35-trial
- - TESTENV=py27-nobyte
- - TESTENV=doctesting
- - TESTENV=freeze
- - TESTENV=docs
+ - TOXENV=linting
+ - TOXENV=py26
+ - TOXENV=py27
+ - TOXENV=py33
+ - TOXENV=py34
+ - TOXENV=py35
+ - TOXENV=pypy
+ - TOXENV=py27-pexpect
+ - TOXENV=py27-xdist
+ - TOXENV=py27-trial
+ - TOXENV=py35-pexpect
+ - TOXENV=py35-xdist
+ - TOXENV=py35-trial
+ - TOXENV=py27-nobyte
+ - TOXENV=doctesting
+ - TOXENV=freeze
+ - TOXENV=docs
matrix:
include:
- - env: TESTENV=py36
+ - env: TOXENV=py36
python: '3.6-dev'
- - env: TESTENV=py37
+ - env: TOXENV=py37
+ python: 'nightly'
+ allow_failures:
+ - env: TOXENV=py37
python: 'nightly'
-script: tox --recreate -e $TESTENV
+script: tox --recreate
notifications:
irc:
diff --git a/AUTHORS b/AUTHORS
index d17963e73..3d59906b2 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -17,6 +17,7 @@ Anthony Sottile
Armin Rigo
Aron Curzon
Aviv Palivoda
+Barney Gale
Ben Webb
Benjamin Peterson
Bernard Pratz
@@ -43,6 +44,7 @@ Dave Hunt
David Díaz-Barquero
David Mohr
David Vierra
+Denis Kirisov
Diego Russo
Dmitry Dygalo
Duncan Betts
@@ -117,11 +119,14 @@ Nicolas Delaby
Oleg Pidsadnyi
Oliver Bestwalter
Omar Kohl
+Omer Hadari
+Patrick Hayes
Pieter Mulder
Piotr Banaszkiewicz
Punyashloka Biswal
Quentin Pradet
Ralf Schmitt
+Ran Benita
Raphael Pierzina
Raquel Alegre
Ravi Chandra
@@ -148,5 +153,6 @@ Tyler Goodlet
Vasily Kuznetsov
Victor Uriarte
Vlad Dragos
+Vidar T. Fauske
Wouter van Ackooy
Xuecong Liao
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 44ac7baf3..472c79696 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -27,6 +27,11 @@ Changes
``__test__`` attribute to ``False`` in the class body (`#2007`_). Thanks
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.
Thanks `@fushi`_ for the PR (`#1874`_).
@@ -73,6 +78,7 @@ Bug Fixes
.. _@unsignedint: https://github.com/unsignedint
.. _@Kriechi: https://github.com/Kriechi
+
.. _#1407: https://github.com/pytest-dev/pytest/issues/1407
.. _#1512: https://github.com/pytest-dev/pytest/issues/1512
.. _#1874: https://github.com/pytest-dev/pytest/pull/1874
@@ -83,24 +89,65 @@ Bug Fixes
.. _#2166: https://github.com/pytest-dev/pytest/pull/2166
.. _#2147: https://github.com/pytest-dev/pytest/issues/2147
.. _#2208: https://github.com/pytest-dev/pytest/issues/2208
+.. _#2228: https://github.com/pytest-dev/pytest/issues/2228
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
- we split testcase element into two, one containing the error and the other
- the failure. (`#2228`_) Thanks to `@kkoukiou`_ for the PR.
-*
+* Fix issue in assertion rewriting breaking due to modules silently discarding
+ other modules when importing fails
+ 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
+.. _@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)
@@ -135,6 +182,7 @@ Bug Fixes
terminal output it relies on is missing. Thanks to `@eli-b`_ for the PR.
+.. _@barneygale: https://github.com/barneygale
.. _@lesteve: https://github.com/lesteve
.. _@malinoff: https://github.com/malinoff
.. _@pelme: https://github.com/pelme
@@ -2467,7 +2515,7 @@ Bug fixes:
teardown function are called earlier.
- add an all-powerful metafunc.parametrize function which allows to
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
call a test function with different argument values
- Add examples to the "parametrize" example page, including a quick port
diff --git a/_pytest/_code/code.py b/_pytest/_code/code.py
index 616d5c431..6eceb0c7f 100644
--- a/_pytest/_code/code.py
+++ b/_pytest/_code/code.py
@@ -352,6 +352,8 @@ class ExceptionInfo(object):
help for navigating the traceback.
"""
_striptext = ''
+ _assert_start_repr = "AssertionError(u\'assert " if sys.version_info[0] < 3 else "AssertionError(\'assert "
+
def __init__(self, tup=None, exprinfo=None):
import _pytest._code
if tup is None:
@@ -359,8 +361,8 @@ class ExceptionInfo(object):
if exprinfo is None and isinstance(tup[1], AssertionError):
exprinfo = getattr(tup[1], 'msg', None)
if exprinfo is None:
- exprinfo = py._builtin._totext(tup[1])
- if exprinfo and exprinfo.startswith('assert '):
+ exprinfo = py.io.saferepr(tup[1])
+ if exprinfo and exprinfo.startswith(self._assert_start_repr):
self._striptext = 'AssertionError: '
self._excinfo = tup
#: the exception class
diff --git a/_pytest/assertion/rewrite.py b/_pytest/assertion/rewrite.py
index abf5b491f..7408c4746 100644
--- a/_pytest/assertion/rewrite.py
+++ b/_pytest/assertion/rewrite.py
@@ -215,7 +215,8 @@ class AssertionRewritingHook(object):
mod.__loader__ = self
py.builtin.exec_(co, mod.__dict__)
except:
- del sys.modules[name]
+ if name in sys.modules:
+ del sys.modules[name]
raise
return sys.modules[name]
diff --git a/_pytest/cacheprovider.py b/_pytest/cacheprovider.py
index cd09f4a02..782fdadd9 100755
--- a/_pytest/cacheprovider.py
+++ b/_pytest/cacheprovider.py
@@ -1,7 +1,7 @@
"""
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
"""
diff --git a/_pytest/config.py b/_pytest/config.py
index 81f39cda4..6a607c7e3 100644
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -877,6 +877,7 @@ class Config(object):
self.trace = self.pluginmanager.trace.root.get("config")
self.hook = self.pluginmanager.hook
self._inicache = {}
+ self._override_ini = ()
self._opt2dest = {}
self._cleanup = []
self._warn = self.pluginmanager._warn
@@ -977,6 +978,7 @@ class Config(object):
self.invocation_dir = py.path.local()
self._parser.addini('addopts', 'extra command line options', 'args')
self._parser.addini('minversion', 'minimally required pytest version')
+ self._override_ini = ns.override_ini or ()
def _consider_importhook(self, args, entrypoint_name):
"""Install the PEP 302 import hook if using assertion re-writing.
@@ -1159,15 +1161,14 @@ class Config(object):
# and -o foo1=bar1 -o foo2=bar2 options
# 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
- if self.getoption("override_ini", None):
- for ini_config_list in self.option.override_ini:
- for ini_config in ini_config_list:
- try:
- (key, user_ini_value) = ini_config.split("=", 1)
- except ValueError:
- raise UsageError("-o/--override-ini expects option=value style.")
- if key == name:
- value = user_ini_value
+ for ini_config_list in self._override_ini:
+ for ini_config in ini_config_list:
+ try:
+ (key, user_ini_value) = ini_config.split("=", 1)
+ except ValueError:
+ raise UsageError("-o/--override-ini expects option=value style.")
+ if key == name:
+ value = user_ini_value
return value
def getoption(self, name, default=notset, skip=False):
diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py
index f675217f8..c4d21635f 100644
--- a/_pytest/fixtures.py
+++ b/_pytest/fixtures.py
@@ -14,6 +14,7 @@ from _pytest.compat import (
getfslineno, get_real_func,
is_generator, isclass, getimfunc,
getlocation, getfuncargnames,
+ safe_getattr,
)
def pytest_sessionstart(session):
@@ -124,8 +125,6 @@ def getfixturemarker(obj):
exceptions."""
try:
return getattr(obj, "_pytestfixturefunction", None)
- except KeyboardInterrupt:
- raise
except Exception:
# some objects raise errors like request (from flask import request)
# we don't expect them to be fixture functions
@@ -1068,7 +1067,9 @@ class FixtureManager(object):
self._holderobjseen.add(holderobj)
autousenames = []
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)
# or are "@pytest.fixture" marked
marker = getfixturemarker(obj)
diff --git a/_pytest/hookspec.py b/_pytest/hookspec.py
index bb382a597..403f823a0 100644
--- a/_pytest/hookspec.py
+++ b/_pytest/hookspec.py
@@ -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):
@@ -256,7 +256,7 @@ def pytest_assertrepr_compare(config, op, left, right):
Return None for no custom explanation, otherwise return a list
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
- 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):
- """ 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)
def pytest_report_teststatus(report):
diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py
index 4f7792aec..e5f20b6f6 100644
--- a/_pytest/junitxml.py
+++ b/_pytest/junitxml.py
@@ -121,7 +121,7 @@ class _NodeReporter(object):
node = kind(data, message=message)
self.append(node)
- def _write_captured_output(self, report):
+ def write_captured_output(self, report):
for capname in ('out', 'err'):
content = getattr(report, 'capstd' + capname)
if content:
@@ -130,7 +130,6 @@ class _NodeReporter(object):
def append_pass(self, report):
self.add_stats('passed')
- self._write_captured_output(report)
def append_failure(self, report):
# msg = str(report.longrepr.reprtraceback.extraline)
@@ -149,7 +148,6 @@ class _NodeReporter(object):
fail = Junit.failure(message=message)
fail.append(bin_xml_escape(report.longrepr))
self.append(fail)
- self._write_captured_output(report)
def append_collect_error(self, report):
# msg = str(report.longrepr.reprtraceback.extraline)
@@ -167,7 +165,6 @@ class _NodeReporter(object):
msg = "test setup failure"
self._add_simple(
Junit.error, msg, report.longrepr)
- self._write_captured_output(report)
def append_skipped(self, report):
if hasattr(report, "wasxfail"):
@@ -182,7 +179,7 @@ class _NodeReporter(object):
Junit.skipped("%s:%s: %s" % (filename, lineno, skipreason),
type="pytest.skip",
message=skipreason))
- self._write_captured_output(report)
+ self.write_captured_output(report)
def finalize(self):
data = self.to_xml().unicode(indent=0)
@@ -369,6 +366,8 @@ class LogXML(object):
reporter.append_skipped(report)
self.update_testcase_duration(report)
if report.when == "teardown":
+ reporter = self._opentestcase(report)
+ reporter.write_captured_output(report)
self.finalize(report)
report_wid = getattr(report, "worker_id", None)
report_ii = getattr(report, "item_index", None)
diff --git a/_pytest/main.py b/_pytest/main.py
index 73858e099..f100b7974 100644
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -81,7 +81,7 @@ def pytest_namespace():
def pytest_configure(config):
- pytest.config = config # compatibiltiy
+ pytest.config = config # compatibility
def wrap_session(config, doit):
diff --git a/_pytest/mark.py b/_pytest/mark.py
index ef9a94ad9..582eb1277 100644
--- a/_pytest/mark.py
+++ b/_pytest/mark.py
@@ -72,7 +72,7 @@ def pytest_collection_modifyitems(items, config):
return
# pytest used to allow "-" for negating
# but today we just allow "-" at the beginning, use "not" instead
- # we probably remove "-" alltogether soon
+ # we probably remove "-" altogether soon
if keywordexpr.startswith("-"):
keywordexpr = "not " + keywordexpr[1:]
selectuntil = False
diff --git a/_pytest/pytester.py b/_pytest/pytester.py
index 17cba5700..97fc62312 100644
--- a/_pytest/pytester.py
+++ b/_pytest/pytester.py
@@ -335,7 +335,7 @@ def 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):
"""The result of running a command.
@@ -570,7 +570,7 @@ class Testdir(object):
def mkpydir(self, name):
"""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.
"""
@@ -665,7 +665,7 @@ class Testdir(object):
def inline_genitems(self, *args):
"""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.
This runs the :py:func:`pytest.main` function to run all of
@@ -863,7 +863,7 @@ class Testdir(object):
:py:meth:`parseconfigure`.
: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()}
diff --git a/_pytest/python.py b/_pytest/python.py
index ff4edff5b..471a9563f 100644
--- a/_pytest/python.py
+++ b/_pytest/python.py
@@ -174,7 +174,7 @@ def pytest_pycollect_makeitem(collector, name, obj):
outcome = yield
res = outcome.get_result()
if res is not None:
- raise StopIteration
+ return
# nothing was collected elsewhere, let's do it here
if isclass(obj):
if collector.istestclass(obj, name):
@@ -632,7 +632,7 @@ class Generator(FunctionMixin, PyCollector):
def getcallargs(self, obj):
if not isinstance(obj, (tuple, list)):
obj = (obj,)
- # explict naming
+ # explicit naming
if isinstance(obj[0], py.builtin._basestring):
name = obj[0]
obj = obj[1:]
diff --git a/_pytest/skipping.py b/_pytest/skipping.py
index a005bc7c2..86176acaf 100644
--- a/_pytest/skipping.py
+++ b/_pytest/skipping.py
@@ -112,14 +112,14 @@ class MarkEvaluator(object):
def _getglobals(self):
d = {'os': os, 'sys': sys, 'config': self.item.config}
- d.update(self.item.obj.__globals__)
+ if hasattr(self.item, 'obj'):
+ d.update(self.item.obj.__globals__)
return d
def _istrue(self):
if hasattr(self, 'result'):
return self.result
if self.holder:
- d = self._getglobals()
if self.holder.args or 'condition' in self.holder.kwargs:
self.result = False
# "holder" might be a MarkInfo or a MarkDecorator; only
@@ -133,6 +133,7 @@ class MarkEvaluator(object):
for expr in args:
self.expr = expr
if isinstance(expr, py.builtin._basestring):
+ d = self._getglobals()
result = cached_eval(self.item.config, expr, d)
else:
if "reason" not in kwargs:
diff --git a/_pytest/terminal.py b/_pytest/terminal.py
index f509283cb..b07945426 100644
--- a/_pytest/terminal.py
+++ b/_pytest/terminal.py
@@ -295,8 +295,8 @@ class TerminalReporter(object):
def pytest_report_header(self, config):
inifile = ""
if config.inifile:
- inifile = config.rootdir.bestrelpath(config.inifile)
- lines = ["rootdir: %s, inifile: %s" %(config.rootdir, inifile)]
+ inifile = " " + config.rootdir.bestrelpath(config.inifile)
+ lines = ["rootdir: %s, inifile:%s" % (config.rootdir, inifile)]
plugininfo = config.pluginmanager.list_plugin_distinfo()
if plugininfo:
diff --git a/_pytest/tmpdir.py b/_pytest/tmpdir.py
index 565c35cf9..67f999e5a 100644
--- a/_pytest/tmpdir.py
+++ b/_pytest/tmpdir.py
@@ -116,7 +116,7 @@ def tmpdir(request, tmpdir_factory):
path object.
"""
name = request.node.name
- name = re.sub("[\W]", "_", name)
+ name = re.sub(r"[\W]", "_", name)
MAXVAL = 30
if len(name) > MAXVAL:
name = name[:MAXVAL]
diff --git a/_pytest/unittest.py b/_pytest/unittest.py
index 73224010b..276b9ba16 100644
--- a/_pytest/unittest.py
+++ b/_pytest/unittest.py
@@ -5,7 +5,7 @@ import sys
import traceback
import pytest
-# for transfering markers
+# for transferring markers
import _pytest._code
from _pytest.python import transfer_markers
from _pytest.skipping import MarkEvaluator
@@ -65,7 +65,6 @@ class UnitTestCase(pytest.Class):
yield TestCaseFunction('runTest', parent=self)
-
class TestCaseFunction(pytest.Function):
_excinfo = None
@@ -152,14 +151,33 @@ class TestCaseFunction(pytest.Function):
def stopTest(self, testcase):
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):
if self.config.pluginmanager.get_plugin("pdbinvoke") is None:
self._testcase(result=self)
else:
# disables tearDown and cleanups for post mortem debugging (see #1890)
+ if self._handle_skip():
+ return
self._testcase.debug()
-
def _prunetraceback(self, excinfo):
pytest.Function._prunetraceback(self, excinfo)
traceback = excinfo.traceback.filter(
diff --git a/doc/en/assert.rst b/doc/en/assert.rst
index 5ec07a78e..ef8636142 100644
--- a/doc/en/assert.rst
+++ b/doc/en/assert.rst
@@ -287,5 +287,5 @@ For further information, Benjamin Peterson wrote up `Behind the scenes of pytest
``--nomagic``.
.. versionchanged:: 3.0
- Removes the ``--no-assert`` and``--nomagic`` options.
+ Removes the ``--no-assert`` and ``--nomagic`` options.
Removes the ``--assert=reinterp`` option.
diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst
index 6f88aad73..bd2c35c67 100644
--- a/doc/en/fixture.rst
+++ b/doc/en/fixture.rst
@@ -243,7 +243,9 @@ Fixture finalization / executing teardown 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
-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
@@ -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
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
+ import smtplib
import pytest
- @pytest.fixture
- def passwd():
- with open("/etc/passwd") as f:
- yield f.readlines()
+ @pytest.fixture(scope="module")
+ def smtp(request):
+ with smtplib.SMTP("smtp.gmail.com") as smtp:
+ 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
-because the Python ``file`` object supports finalization when
+The ``smtp`` connection will be closed after the test finished execution
+because the ``smtp`` object automatically closes when
the ``with`` statement ends.
diff --git a/doc/en/talks.rst b/doc/en/talks.rst
index c35fba0b0..85faf6455 100644
--- a/doc/en/talks.rst
+++ b/doc/en/talks.rst
@@ -4,7 +4,9 @@ Talks and Tutorials
.. 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
diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst
index 770f81e46..f6ed6e4e3 100644
--- a/doc/en/writing_plugins.rst
+++ b/doc/en/writing_plugins.rst
@@ -236,20 +236,31 @@ import ``helper.py`` normally. The contents of
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
-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"
-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.
+
+This mechanism makes it easy to share fixtures within applications or even
+external applications without the need to create external plugins using
+the ``setuptools``'s entry point technique.
-Plugins imported like this will automatically be marked to require
-assertion rewriting using the :func:`pytest.register_assert_rewrite`
-mechanism. However for this to have any effect the module must not be
+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
``pytest_plugins`` statement is processed, a warning will result and
assertions inside the plugin will not be re-written. To fix this you
diff --git a/testing/python/collect.py b/testing/python/collect.py
index 5511b3d75..e4069983a 100644
--- a/testing/python/collect.py
+++ b/testing/python/collect.py
@@ -166,6 +166,16 @@ class TestClass(object):
"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):
def test_generative_functions(self, testdir):
diff --git a/testing/test_assertion.py b/testing/test_assertion.py
index fc2819f69..e5c18b43a 100644
--- a/testing/test_assertion.py
+++ b/testing/test_assertion.py
@@ -365,7 +365,7 @@ class TestAssert_reprcompare(object):
expl = '\n'.join(callequal(left, right, verbose=True))
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])
assert len(expl) > 1
expl = callequal([0, 1, 2], [0, 1])
@@ -996,6 +996,25 @@ def test_assert_with_unicode(monkeypatch, testdir):
result = testdir.runpytest()
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):
testdir.makepyfile("""
def f():
diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py
index 0ec528e82..047a2ac6e 100644
--- a/testing/test_assertrewrite.py
+++ b/testing/test_assertrewrite.py
@@ -271,7 +271,7 @@ class TestAssertionRewrite(object):
getmsg(f, must_pass=True)
- def test_short_circut_evaluation(self):
+ def test_short_circuit_evaluation(self):
def f():
assert True or explode # noqa
diff --git a/testing/test_capture.py b/testing/test_capture.py
index 364281fe6..28326fa73 100644
--- a/testing/test_capture.py
+++ b/testing/test_capture.py
@@ -604,7 +604,7 @@ def test_capture_binary_output(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="""
from _pytest.capture import FDCapture
def bad_snap(self):
diff --git a/testing/test_config.py b/testing/test_config.py
index 7876063bd..171e9486e 100644
--- a/testing/test_config.py
+++ b/testing/test_config.py
@@ -519,7 +519,7 @@ def test_consider_args_after_options_for_rootdir_and_inifile(testdir, args):
args[i] = d2
with root.as_cwd():
result = testdir.runpytest(*args)
- result.stdout.fnmatch_lines(['*rootdir: *myroot, inifile: '])
+ result.stdout.fnmatch_lines(['*rootdir: *myroot, inifile:'])
@pytest.mark.skipif("sys.platform == 'win32'")
@@ -778,6 +778,21 @@ class TestOverrideIniArgs(object):
result = testdir.runpytest("--override-ini", 'xdist_strict True', "-s")
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):
monkeypatch.chdir(str(tmpdir))
a = tmpdir.mkdir("a")
diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py
index b4e4c5b14..a0f51fc71 100644
--- a/testing/test_junitxml.py
+++ b/testing/test_junitxml.py
@@ -580,6 +580,25 @@ class TestPython(object):
systemout = pnode.find_first_by_tag("system-err")
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():
from _pytest.junitxml import mangle_test_address
diff --git a/testing/test_pdb.py b/testing/test_pdb.py
index cf6d6f7fb..161b4f5f7 100644
--- a/testing/test_pdb.py
+++ b/testing/test_pdb.py
@@ -126,6 +126,21 @@ class TestPDB(object):
assert 'debug.me' in rest
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):
p1 = testdir.makepyfile("""
def test_1():
diff --git a/testing/test_session.py b/testing/test_session.py
index b6e7d2b6c..66a0f5978 100644
--- a/testing/test_session.py
+++ b/testing/test_session.py
@@ -197,7 +197,7 @@ class TestNewSession(SessionTests):
colfail = [x for x in finished if x.failed]
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(test_one="xxxx", test_two="yyyy", test_third="zzz")
reprec = testdir.inline_run("-x", "--maxfail=2", testdir.tmpdir)
diff --git a/testing/test_skipping.py b/testing/test_skipping.py
index 6a8d147ad..f621a010f 100644
--- a/testing/test_skipping.py
+++ b/testing/test_skipping.py
@@ -969,3 +969,26 @@ def test_module_level_skip_error(testdir):
result.stdout.fnmatch_lines(
"*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
diff --git a/testing/test_terminal.py b/testing/test_terminal.py
index 77fb3b154..d72f6a12b 100644
--- a/testing/test_terminal.py
+++ b/testing/test_terminal.py
@@ -906,3 +906,12 @@ def test_summary_stats(exp_line, exp_color, stats_arg):
print("Actually got: \"%s\"; with color \"%s\"" % (line, color))
assert line == exp_line
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()
diff --git a/tox.ini b/tox.ini
index e57fabd4b..1b9fb9f5a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,7 +1,7 @@
[tox]
minversion=2.0
distshare={homedir}/.tox/distshare
-# make sure to update enviroment list on appveyor.yml
+# make sure to update environment list on appveyor.yml
envlist=
linting
py26