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 # # command to run tests
env: env:
matrix: matrix:
- TESTENV=coveralls
- TESTENV=doctesting
- TESTENV=flakes - TESTENV=flakes
- TESTENV=py26 - TESTENV=py26
- TESTENV=py27 - 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=py33
- TESTENV=py34 - TESTENV=py34
- TESTENV=py34-pexpect
- TESTENV=py34-trial
- TESTENV=py34-trial
- TESTENV=py34-xdist
- TESTENV=py35 - TESTENV=py35
- TESTENV=pypy - 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: matrix:
allow_failures: allow_failures:
# py35 is currently broken on travis, see #744 # py35 is currently broken on travis, see #744

View File

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

View File

@ -1,10 +1,6 @@
2.8.0.dev (compared to 2.7.X) 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 - 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. fixtures. Thanks Jason R. Coombs for reporting and Bruno Oliveira for the PR.
@ -105,6 +101,34 @@
- add ``file`` and ``line`` attributes to JUnit-XML output. - 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) 2.7.2 (compared to 2.7.1)
----------------------------- -----------------------------
@ -128,7 +152,7 @@
Thanks Thomas De Schampheleire for reporting and Bruno Oliveira for the PR. Thanks Thomas De Schampheleire for reporting and Bruno Oliveira for the PR.
- fix issue718: failed to create representation of sets containing unsortable - 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 - fix issue756, fix issue752 (and similar issues): depend on py-1.4.29
which has a refined algorithm for traceback generation. which has a refined algorithm for traceback generation.

View File

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

View File

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

View File

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

View File

@ -98,24 +98,36 @@ class MarkEvaluator:
return d return d
def _istrue(self): def _istrue(self):
if hasattr(self, 'result'):
return self.result
if self.holder: if self.holder:
d = self._getglobals() d = self._getglobals()
if self.holder.args: if self.holder.args:
self.result = False self.result = False
for expr in self.holder.args: # "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 self.expr = expr
if isinstance(expr, py.builtin._basestring): if isinstance(expr, py.builtin._basestring):
result = cached_eval(self.item.config, expr, d) result = cached_eval(self.item.config, expr, d)
else: else:
if self.get("reason") is None: if "reason" not in kwargs:
# XXX better be checked at collection time # XXX better be checked at collection time
pytest.fail("you need to specify reason=STRING " msg = "you need to specify reason=STRING " \
"when using booleans as conditions.") "when using booleans as conditions."
pytest.fail(msg)
result = bool(expr) result = bool(expr)
if result: if result:
self.result = True self.result = True
self.reason = kwargs.get('reason', None)
self.expr = expr self.expr = expr
break return self.result
else: else:
self.result = True self.result = True
return getattr(self, 'result', False) return getattr(self, 'result', False)
@ -124,7 +136,7 @@ class MarkEvaluator:
return self.holder.kwargs.get(attr, default) return self.holder.kwargs.get(attr, default)
def getexplanation(self): def getexplanation(self):
expl = self.get('reason', None) expl = getattr(self, 'reason', None) or self.get('reason', None)
if not expl: if not expl:
if not hasattr(self, 'expr'): if not hasattr(self, 'expr'):
return "" return ""

View File

@ -52,7 +52,14 @@ class TempdirFactory:
basetemp.remove() basetemp.remove()
basetemp.mkdir() basetemp.mkdir()
else: 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._basetemp = t = basetemp.realpath()
self.trace("new basetemp", t) self.trace("new basetemp", t)
return 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 For simple applications and for people experienced with nose_ or
unittest-style test setup using `xUnit style setup`_ probably unittest-style test setup using `xUnit style setup`_ probably
feels natural. For larger test suites, parametrized testing feels natural. For larger test suites, parametrized testing
or setup of complex test resources using funcargs_ may feel more natural. or setup of complex test resources using fixtures_ may feel more natural.
Moreover, funcargs are ideal for writing advanced test support Moreover, fixtures are ideal for writing advanced test support
code (like e.g. the monkeypatch_, the tmpdir_ or capture_ funcargs) code (like e.g. the monkeypatch_, the tmpdir_ or capture_ fixtures)
because the support code can register setup/teardown functions because the support code can register setup/teardown functions
in a managed class/module/function scope. in a managed class/module/function scope.
.. _monkeypatch: monkeypatch.html .. _monkeypatch: monkeypatch.html
.. _tmpdir: tmpdir.html .. _tmpdir: tmpdir.html
.. _capture: capture.html .. _capture: capture.html
.. _fixtures: fixture.html
.. _`why pytest_pyfuncarg__ methods?`: .. _`why pytest_pyfuncarg__ methods?`:

View File

@ -6,7 +6,7 @@
.. _`pytest_nose`: plugin/nose.html .. _`pytest_nose`: plugin/nose.html
.. _`reStructured Text`: http://docutils.sourceforge.net .. _`reStructured Text`: http://docutils.sourceforge.net
.. _`Python debugger`: http://docs.python.org/lib/module-pdb.html .. _`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 .. _pytest: http://pypi.python.org/pypi/pytest
.. _mercurial: http://mercurial.selenic.com/wiki/ .. _mercurial: http://mercurial.selenic.com/wiki/
.. _`setuptools`: http://pypi.python.org/pypi/setuptools .. _`setuptools`: http://pypi.python.org/pypi/setuptools

View File

@ -865,3 +865,47 @@ def test_unorderable_types(testdir):
result = testdir.runpytest() result = testdir.runpytest()
assert "TypeError" not in result.stdout.str() assert "TypeError" not in result.stdout.str()
assert result.ret == 0 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: class TestContextManagerFixtureFuncs:
def test_simple(self, testdir): def test_simple(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""

View File

@ -369,6 +369,45 @@ class TestFunctional:
print (item, item.keywords) print (item, item.keywords)
assert 'a' in 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): def test_mark_with_wrong_marker(self, testdir):
reprec = testdir.inline_runsource(""" reprec = testdir.inline_runsource("""
import pytest import pytest
@ -477,6 +516,22 @@ class TestFunctional:
reprec = testdir.inline_run("-m", "mark1") reprec = testdir.inline_run("-m", "mark1")
reprec.assertoutcome(passed=1) 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: class TestKeywordSelection:
def test_select_simple(self, testdir): def test_select_simple(self, testdir):
file_test = testdir.makepyfile(""" file_test = testdir.makepyfile("""

View File

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

View File

@ -409,6 +409,26 @@ class TestSkipif:
]) ])
assert result.ret == 0 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): def test_skip_not_report_default(testdir):
p = testdir.makepyfile(test_one=""" p = testdir.makepyfile(test_one="""
import pytest import pytest

View File

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