Merge branch 'pytest-2.7'

This commit is contained in:
Anatoly Bubenkov 2015-07-19 15:25:04 +02:00
commit 6af7172204
16 changed files with 289 additions and 65 deletions

View File

@ -7,24 +7,26 @@ install: "pip install -U tox"
# # command to run tests
env:
matrix:
- TESTENV=coveralls
- TESTENV=doctesting
- TESTENV=flakes
- TESTENV=py26
- TESTENV=py27
- TESTENV=py27-cxfreeze
- TESTENV=py27-nobyte
- TESTENV=py27-pexpect
- TESTENV=py27-subprocess
- TESTENV=py27-trial
- TESTENV=py27-xdist
- TESTENV=py33
- TESTENV=py33
- TESTENV=py34
- TESTENV=py34-pexpect
- TESTENV=py34-trial
- TESTENV=py34-trial
- TESTENV=py34-xdist
- TESTENV=py35
- TESTENV=pypy
- TESTENV=py27-pexpect
- TESTENV=py34-pexpect
- TESTENV=py27-nobyte
- TESTENV=py27-xdist
- TESTENV=py34-xdist
- TESTENV=py27-trial
- TESTENV=py34-trial
- TESTENV=py27-subprocess
- TESTENV=doctesting
- TESTENV=py27-cxfreeze
- TESTENV=coveralls
matrix:
allow_failures:
# py35 is currently broken on travis, see #744

View File

@ -47,6 +47,7 @@ Marc Schlaich
Mark Abramowitz
Martijn Faassen
Nicolas Delaby
Pieter Mulder
Piotr Banaszkiewicz
Punyashloka Biswal
Ralf Schmitt

View File

@ -1,10 +1,6 @@
2.8.0.dev (compared to 2.7.X)
-----------------------------
- fix issue744: fix for ast.Call changes in Python 3.5+. Thanks
Guido van Rossum, Matthias Bussonnier, Stefan Zimmermann and
Thomas Kluyver.
- fix issue768: docstrings found in python modules were not setting up session
fixtures. Thanks Jason R. Coombs for reporting and Bruno Oliveira for the PR.
@ -27,7 +23,7 @@
- Summary bar now is colored yellow for warning
situations such as: all tests either were skipped or xpass/xfailed,
or no tests were run at all (this is a partial fix for issue500).
or no tests were run at all (this is a partial fix for issue500).
Thanks Eric Siegerman.
- New `testdirs` ini option: list of directories to search for tests
@ -105,6 +101,34 @@
- add ``file`` and ``line`` attributes to JUnit-XML output.
2.7.3 (compared to 2.7.2)
-----------------------------
- fix issue744: fix for ast.Call changes in Python 3.5+. Thanks
Guido van Rossum, Matthias Bussonnier, Stefan Zimmermann and
Thomas Kluyver.
- fix issue842: applying markers in classes no longer propagate this markers
to superclasses which also have markers.
Thanks xmo-odoo for the report and Bruno Oliveira for the PR.
- preserve warning functions after call to pytest.deprecated_call. Thanks
Pieter Mulder for PR.
- fix issue833: --fixtures now shows all fixtures of collected test files, instead of just the
fixtures declared on the first one.
Thanks Florian Bruhin for reporting and Bruno Oliveira for the PR.
- fix issue863: skipped tests now report the correct reason when a skip/xfail
condition is met when using multiple markers.
Thanks Raphael Pierzina for reporting and Bruno Oliveira for the PR.
- optimized tmpdir fixture initialization, which should make test sessions
faster (specially when using pytest-xdist). The only visible effect
is that now pytest uses a subdirectory in the $TEMP directory for all
directories created by this fixture (defaults to $TEMP/pytest-$USER).
Thanks Bruno Oliveira for the PR.
2.7.2 (compared to 2.7.1)
-----------------------------
@ -128,7 +152,7 @@
Thanks Thomas De Schampheleire for reporting and Bruno Oliveira for the PR.
- fix issue718: failed to create representation of sets containing unsortable
elements in python 2. Thanks Edison Gustavo Muenz
elements in python 2. Thanks Edison Gustavo Muenz.
- fix issue756, fix issue752 (and similar issues): depend on py-1.4.29
which has a refined algorithm for traceback generation.

View File

@ -1,4 +1,5 @@
""" generic mechanism for marking and selecting python functions. """
import inspect
import py
@ -253,15 +254,17 @@ class MarkDecorator:
otherwise add *args/**kwargs in-place to mark information. """
if args and not kwargs:
func = args[0]
if len(args) == 1 and (istestfunc(func) or
hasattr(func, '__bases__')):
if hasattr(func, '__bases__'):
is_class = inspect.isclass(func)
if len(args) == 1 and (istestfunc(func) or is_class):
if is_class:
if hasattr(func, 'pytestmark'):
l = func.pytestmark
if not isinstance(l, list):
func.pytestmark = [l, self]
else:
l.append(self)
mark_list = func.pytestmark
if not isinstance(mark_list, list):
mark_list = [mark_list]
# always work on a copy to avoid updating pytestmark
# from a superclass by accident
mark_list = mark_list + [self]
func.pytestmark = mark_list
else:
func.pytestmark = [self]
else:

View File

@ -1,5 +1,6 @@
""" Python test discovery, setup and run of test functions. """
import fnmatch
import functools
import py
import inspect
import sys
@ -22,15 +23,23 @@ callable = py.builtin.callable
# used to work around a python2 exception info leak
exc_clear = getattr(sys, 'exc_clear', lambda: None)
def filter_traceback(entry):
return entry.path != cutdir1 and not entry.path.relto(cutdir2)
def getfslineno(obj):
# xxx let decorators etc specify a sane ordering
def get_real_func(obj):
"""gets the real function object of the (possibly) wrapped object by
functools.wraps or functools.partial.
"""
while hasattr(obj, "__wrapped__"):
obj = obj.__wrapped__
if isinstance(obj, functools.partial):
obj = obj.func
return obj
def getfslineno(obj):
# xxx let decorators etc specify a sane ordering
obj = get_real_func(obj)
if hasattr(obj, 'place_as'):
obj = obj.place_as
fslineno = py.code.getfslineno(obj)
@ -606,7 +615,7 @@ class FunctionMixin(PyobjMixin):
def _prunetraceback(self, excinfo):
if hasattr(self, '_obj') and not self.config.option.fulltrace:
code = py.code.Code(self.obj)
code = py.code.Code(get_real_func(self.obj))
path, firstlineno = code.path, code.firstlineno
traceback = excinfo.traceback
ntraceback = traceback.cut(path=path, firstlineno=firstlineno)
@ -976,21 +985,13 @@ def showfixtures(config):
def _showfixtures_main(config, session):
session.perform_collect()
curdir = py.path.local()
if session.items:
nodeid = session.items[0].nodeid
else:
part = session._initialparts[0]
nodeid = "::".join(map(str, [curdir.bestrelpath(part[0])] + part[1:]))
nodeid.replace(session.fspath.sep, "/")
tw = py.io.TerminalWriter()
verbose = config.getvalue("verbose")
fm = session._fixturemanager
available = []
for argname in fm._arg2fixturedefs:
fixturedefs = fm.getfixturedefs(argname, nodeid)
for argname, fixturedefs in fm._arg2fixturedefs.items():
assert fixturedefs is not None
if not fixturedefs:
continue
@ -1582,7 +1583,7 @@ class FixtureLookupError(LookupError):
for function in stack:
fspath, lineno = getfslineno(function)
try:
lines, _ = inspect.getsourcelines(function)
lines, _ = inspect.getsourcelines(get_real_func(function))
except IOError:
error_msg = "file %s, line %s: source code not available"
addline(error_msg % (fspath, lineno+1))
@ -1970,7 +1971,15 @@ def getfuncargnames(function, startindex=None):
if realfunction != function:
startindex += num_mock_patch_args(function)
function = realfunction
argnames = inspect.getargs(py.code.getrawcode(function))[0]
if isinstance(function, functools.partial):
argnames = inspect.getargs(py.code.getrawcode(function.func))[0]
partial = function
argnames = argnames[len(partial.args):]
if partial.keywords:
for kw in partial.keywords:
argnames.remove(kw)
else:
argnames = inspect.getargs(py.code.getrawcode(function))[0]
defaults = getattr(function, 'func_defaults',
getattr(function, '__defaults__', None)) or ()
numdefaults = len(defaults)

View File

@ -45,8 +45,8 @@ def deprecated_call(func, *args, **kwargs):
try:
ret = func(*args, **kwargs)
finally:
warnings.warn_explicit = warn_explicit
warnings.warn = warn
warnings.warn_explicit = oldwarn_explicit
warnings.warn = oldwarn
if not l:
__tracebackhide__ = True
raise AssertionError("%r did not produce DeprecationWarning" %(func,))

View File

@ -98,24 +98,36 @@ class MarkEvaluator:
return d
def _istrue(self):
if hasattr(self, 'result'):
return self.result
if self.holder:
d = self._getglobals()
if self.holder.args:
self.result = False
for expr in self.holder.args:
self.expr = expr
if isinstance(expr, py.builtin._basestring):
result = cached_eval(self.item.config, expr, d)
else:
if self.get("reason") is None:
# XXX better be checked at collection time
pytest.fail("you need to specify reason=STRING "
"when using booleans as conditions.")
result = bool(expr)
if result:
self.result = True
# "holder" might be a MarkInfo or a MarkDecorator; only
# MarkInfo keeps track of all parameters it received in an
# _arglist attribute
if hasattr(self.holder, '_arglist'):
arglist = self.holder._arglist
else:
arglist = [(self.holder.args, self.holder.kwargs)]
for args, kwargs in arglist:
for expr in args:
self.expr = expr
break
if isinstance(expr, py.builtin._basestring):
result = cached_eval(self.item.config, expr, d)
else:
if "reason" not in kwargs:
# XXX better be checked at collection time
msg = "you need to specify reason=STRING " \
"when using booleans as conditions."
pytest.fail(msg)
result = bool(expr)
if result:
self.result = True
self.reason = kwargs.get('reason', None)
self.expr = expr
return self.result
else:
self.result = True
return getattr(self, 'result', False)
@ -124,7 +136,7 @@ class MarkEvaluator:
return self.holder.kwargs.get(attr, default)
def getexplanation(self):
expl = self.get('reason', None)
expl = getattr(self, 'reason', None) or self.get('reason', None)
if not expl:
if not hasattr(self, 'expr'):
return ""

View File

@ -52,7 +52,14 @@ class TempdirFactory:
basetemp.remove()
basetemp.mkdir()
else:
basetemp = py.path.local.make_numbered_dir(prefix='pytest-')
# use a sub-directory in the temproot to speed-up
# make_numbered_dir() call
import getpass
temproot = py.path.local.get_temproot()
rootdir = temproot.join('pytest-%s' % getpass.getuser())
rootdir.ensure(dir=1)
basetemp = py.path.local.make_numbered_dir(prefix='pytest-',
rootdir=rootdir)
self._basetemp = t = basetemp.realpath()
self.trace("new basetemp", t)
return t

View File

@ -106,15 +106,16 @@ Is using pytest fixtures versus xUnit setup a style question?
For simple applications and for people experienced with nose_ or
unittest-style test setup using `xUnit style setup`_ probably
feels natural. For larger test suites, parametrized testing
or setup of complex test resources using funcargs_ may feel more natural.
Moreover, funcargs are ideal for writing advanced test support
code (like e.g. the monkeypatch_, the tmpdir_ or capture_ funcargs)
or setup of complex test resources using fixtures_ may feel more natural.
Moreover, fixtures are ideal for writing advanced test support
code (like e.g. the monkeypatch_, the tmpdir_ or capture_ fixtures)
because the support code can register setup/teardown functions
in a managed class/module/function scope.
.. _monkeypatch: monkeypatch.html
.. _tmpdir: tmpdir.html
.. _capture: capture.html
.. _fixtures: fixture.html
.. _`why pytest_pyfuncarg__ methods?`:

View File

@ -6,7 +6,7 @@
.. _`pytest_nose`: plugin/nose.html
.. _`reStructured Text`: http://docutils.sourceforge.net
.. _`Python debugger`: http://docs.python.org/lib/module-pdb.html
.. _nose: http://somethingaboutorange.com/mrl/projects/nose/
.. _nose: https://nose.readthedocs.org/en/latest/
.. _pytest: http://pypi.python.org/pypi/pytest
.. _mercurial: http://mercurial.selenic.com/wiki/
.. _`setuptools`: http://pypi.python.org/pypi/setuptools

View File

@ -865,3 +865,47 @@ def test_unorderable_types(testdir):
result = testdir.runpytest()
assert "TypeError" not in result.stdout.str()
assert result.ret == 0
def test_collect_functools_partial(testdir):
"""
Test that collection of functools.partial object works, and arguments
to the wrapped functions are dealt correctly (see #811).
"""
testdir.makepyfile("""
import functools
import pytest
@pytest.fixture
def fix1():
return 'fix1'
@pytest.fixture
def fix2():
return 'fix2'
def check1(i, fix1):
assert i == 2
assert fix1 == 'fix1'
def check2(fix1, i):
assert i == 2
assert fix1 == 'fix1'
def check3(fix1, i, fix2):
assert i == 2
assert fix1 == 'fix1'
assert fix2 == 'fix2'
test_ok_1 = functools.partial(check1, i=2)
test_ok_2 = functools.partial(check1, i=2, fix1='fix1')
test_ok_3 = functools.partial(check1, 2)
test_ok_4 = functools.partial(check2, i=2)
test_ok_5 = functools.partial(check3, i=2)
test_ok_6 = functools.partial(check3, i=2, fix1='fix1')
test_fail_1 = functools.partial(check2, 2)
test_fail_2 = functools.partial(check3, 2)
""")
result = testdir.inline_run()
result.assertoutcome(passed=6, failed=2)

View File

@ -2482,6 +2482,44 @@ class TestShowFixtures:
""")
def test_show_fixtures_different_files(self, testdir):
"""
#833: --fixtures only shows fixtures from first file
"""
testdir.makepyfile(test_a='''
import pytest
@pytest.fixture
def fix_a():
"""Fixture A"""
pass
def test_a(fix_a):
pass
''')
testdir.makepyfile(test_b='''
import pytest
@pytest.fixture
def fix_b():
"""Fixture B"""
pass
def test_b(fix_b):
pass
''')
result = testdir.runpytest("--fixtures")
result.stdout.fnmatch_lines("""
* fixtures defined from test_a *
fix_a
Fixture A
* fixtures defined from test_b *
fix_b
Fixture B
""")
class TestContextManagerFixtureFuncs:
def test_simple(self, testdir):
testdir.makepyfile("""

View File

@ -369,6 +369,45 @@ class TestFunctional:
print (item, item.keywords)
assert 'a' in item.keywords
def test_mark_decorator_subclass_does_not_propagate_to_base(self, testdir):
p = testdir.makepyfile("""
import pytest
@pytest.mark.a
class Base: pass
@pytest.mark.b
class Test1(Base):
def test_foo(self): pass
class Test2(Base):
def test_bar(self): pass
""")
items, rec = testdir.inline_genitems(p)
self.assert_markers(items, test_foo=('a', 'b'), test_bar=('a',))
def test_mark_decorator_baseclasses_merged(self, testdir):
p = testdir.makepyfile("""
import pytest
@pytest.mark.a
class Base: pass
@pytest.mark.b
class Base2(Base): pass
@pytest.mark.c
class Test1(Base2):
def test_foo(self): pass
class Test2(Base2):
@pytest.mark.d
def test_bar(self): pass
""")
items, rec = testdir.inline_genitems(p)
self.assert_markers(items, test_foo=('a', 'b', 'c'),
test_bar=('a', 'b', 'd'))
def test_mark_with_wrong_marker(self, testdir):
reprec = testdir.inline_runsource("""
import pytest
@ -477,6 +516,22 @@ class TestFunctional:
reprec = testdir.inline_run("-m", "mark1")
reprec.assertoutcome(passed=1)
def assert_markers(self, items, **expected):
"""assert that given items have expected marker names applied to them.
expected should be a dict of (item name -> seq of expected marker names)
.. note:: this could be moved to ``testdir`` if proven to be useful
to other modules.
"""
from _pytest.mark import MarkInfo
items = dict((x.name, x) for x in items)
for name, expected_markers in expected.items():
markers = items[name].keywords._markers
marker_names = set([name for (name, v) in markers.items()
if isinstance(v, MarkInfo)])
assert marker_names == set(expected_markers)
class TestKeywordSelection:
def test_select_simple(self, testdir):
file_test = testdir.makepyfile("""

View File

@ -63,12 +63,16 @@ def test_deprecated_call_ret():
assert ret == 42
def test_deprecated_call_preserves():
r = py.std.warnings.onceregistry.copy()
f = py.std.warnings.filters[:]
onceregistry = py.std.warnings.onceregistry.copy()
filters = py.std.warnings.filters[:]
warn = py.std.warnings.warn
warn_explicit = py.std.warnings.warn_explicit
test_deprecated_call_raises()
test_deprecated_call()
assert r == py.std.warnings.onceregistry
assert f == py.std.warnings.filters
assert onceregistry == py.std.warnings.onceregistry
assert filters == py.std.warnings.filters
assert warn is py.std.warnings.warn
assert warn_explicit is py.std.warnings.warn_explicit
def test_deprecated_explicit_call_raises():
pytest.raises(AssertionError,

View File

@ -409,6 +409,26 @@ class TestSkipif:
])
assert result.ret == 0
@pytest.mark.parametrize('marker, msg1, msg2', [
('skipif', 'SKIP', 'skipped'),
('xfail', 'XPASS', 'xpassed'),
])
def test_skipif_reporting_multiple(self, testdir, marker, msg1, msg2):
testdir.makepyfile(test_foo="""
import pytest
@pytest.mark.{marker}(False, reason='first_condition')
@pytest.mark.{marker}(True, reason='second_condition')
def test_foobar():
assert 1
""".format(marker=marker))
result = testdir.runpytest('-s', '-rsxX')
result.stdout.fnmatch_lines([
"*{msg1}*test_foo.py*second_condition*".format(msg1=msg1),
"*1 {msg2}*".format(msg2=msg2),
])
assert result.ret == 0
def test_skip_not_report_default(testdir):
p = testdir.makepyfile(test_one="""
import pytest

View File

@ -8,6 +8,7 @@ envlist=
[testenv]
commands= py.test --lsof -rfsxX {posargs:testing}
passenv = USER USERNAME
deps=
nose
mock
@ -18,6 +19,7 @@ deps=
nose
mock<1.1 # last supported version for py26
<<<<<<< HEAD
[testenv:py27-subprocess]
changedir=.
basepython=python2.7
@ -27,6 +29,8 @@ deps=pytest-xdist
commands=
py.test -n3 -rfsxX --runpytest=subprocess {posargs:testing}
=======
>>>>>>> pytest-2.7
[testenv:genscript]
commands= py.test --genscript=pytest1