fix issue89 - allow py.test.mark decorators to be used with classes
(if you are using >=python2.6) also allow to have multiple markers applied at class level and test and fix a bug with chained skip/xfail decorators: if any of the conditions is true a test will be skipped/xfailed with a explanation which condition evaluated to true. --HG-- branch : trunk
This commit is contained in:
parent
67ec87e7f9
commit
4f7ef0b63f
|
@ -6,6 +6,14 @@ Changes between 1.3.0 and 1.3.1
|
|||
to the underlying capturing functionality to avoid race
|
||||
conditions).
|
||||
|
||||
- fix issue89 - allow py.test.mark decorators to be used on classes
|
||||
(class decorators were introduced with python2.6)
|
||||
also allow to have multiple markers applied at class/module level
|
||||
|
||||
- fix chaining of conditional skipif/xfail decorators - so it works now
|
||||
as expected to use multiple @py.test.mark.skipif(condition) decorators,
|
||||
including specific reporting which of the conditions lead to skipping.
|
||||
|
||||
- fix issue95: late-import zlib so that it's not required
|
||||
for general py.test startup.
|
||||
|
||||
|
|
|
@ -39,38 +39,43 @@ and later access it with ``test_receive.webtest.args[0] == 'triangular``.
|
|||
|
||||
.. _`scoped-marking`:
|
||||
|
||||
Marking classes or modules
|
||||
Marking whole classes or modules
|
||||
----------------------------------------------------
|
||||
|
||||
To mark all methods of a class set a ``pytestmark`` attribute like this::
|
||||
If you are programming with Python2.6 you may use ``py.test.mark`` decorators
|
||||
with classes to apply markers to all its test methods::
|
||||
|
||||
@py.test.mark.webtest
|
||||
class TestClass:
|
||||
def test_startup(self):
|
||||
...
|
||||
|
||||
This is equivalent to directly applying the decorator to the
|
||||
``test_startup`` function.
|
||||
|
||||
To remain compatible with Python2.5 you can instead set a
|
||||
``pytestmark`` attribute on a TestClass like this::
|
||||
|
||||
import py
|
||||
|
||||
class TestClass:
|
||||
pytestmark = py.test.mark.webtest
|
||||
|
||||
You can re-use the same markers that you would use for decorating
|
||||
a function - in fact this marker decorator will be applied
|
||||
to all test methods of the class.
|
||||
or if you need to use multiple markers::
|
||||
|
||||
import py
|
||||
|
||||
class TestClass:
|
||||
pytestmark = [py.test.mark.webtest, pytest.mark.slowtest]
|
||||
|
||||
You can also set a module level marker::
|
||||
|
||||
import py
|
||||
pytestmark = py.test.mark.webtest
|
||||
|
||||
in which case then the marker decorator will be applied to all functions and
|
||||
in which case then it will be applied to all functions and
|
||||
methods defined in the module.
|
||||
|
||||
The order in which marker functions are called is this::
|
||||
|
||||
per-function (upon import of module already)
|
||||
per-class
|
||||
per-module
|
||||
|
||||
Later called markers may overwrite previous key-value settings.
|
||||
Positional arguments are all appended to the same 'args' list
|
||||
of the Marker object.
|
||||
|
||||
Using "-k MARKNAME" to select tests
|
||||
----------------------------------------------------
|
||||
|
||||
|
|
|
@ -65,6 +65,18 @@ for skipping all methods of a test class based on platform::
|
|||
#
|
||||
|
||||
The ``pytestmark`` decorator will be applied to each test function.
|
||||
If your code targets python2.6 or above you can also use the
|
||||
skipif decorator with classes::
|
||||
|
||||
@py.test.mark.skipif("sys.platform == 'win32'")
|
||||
class TestPosixCalls:
|
||||
|
||||
def test_function(self):
|
||||
# will not be setup or run under 'win32' platform
|
||||
#
|
||||
|
||||
It is fine in both situations to use multiple "skipif" decorators
|
||||
on a single function.
|
||||
|
||||
.. _`whole class- or module level`: mark.html#scoped-marking
|
||||
|
||||
|
|
|
@ -34,38 +34,43 @@ and later access it with ``test_receive.webtest.args[0] == 'triangular``.
|
|||
|
||||
.. _`scoped-marking`:
|
||||
|
||||
Marking classes or modules
|
||||
Marking whole classes or modules
|
||||
----------------------------------------------------
|
||||
|
||||
To mark all methods of a class set a ``pytestmark`` attribute like this::
|
||||
If you are programming with Python2.6 you may use ``py.test.mark`` decorators
|
||||
with classes to apply markers to all its test methods::
|
||||
|
||||
@py.test.mark.webtest
|
||||
class TestClass:
|
||||
def test_startup(self):
|
||||
...
|
||||
|
||||
This is equivalent to directly applying the decorator to the
|
||||
``test_startup`` function.
|
||||
|
||||
To remain compatible with Python2.5 you can instead set a
|
||||
``pytestmark`` attribute on a TestClass like this::
|
||||
|
||||
import py
|
||||
|
||||
class TestClass:
|
||||
pytestmark = py.test.mark.webtest
|
||||
|
||||
You can re-use the same markers that you would use for decorating
|
||||
a function - in fact this marker decorator will be applied
|
||||
to all test methods of the class.
|
||||
or if you need to use multiple markers::
|
||||
|
||||
import py
|
||||
|
||||
class TestClass:
|
||||
pytestmark = [py.test.mark.webtest, pytest.mark.slowtest]
|
||||
|
||||
You can also set a module level marker::
|
||||
|
||||
import py
|
||||
pytestmark = py.test.mark.webtest
|
||||
|
||||
in which case then the marker decorator will be applied to all functions and
|
||||
in which case then it will be applied to all functions and
|
||||
methods defined in the module.
|
||||
|
||||
The order in which marker functions are called is this::
|
||||
|
||||
per-function (upon import of module already)
|
||||
per-class
|
||||
per-module
|
||||
|
||||
Later called markers may overwrite previous key-value settings.
|
||||
Positional arguments are all appended to the same 'args' list
|
||||
of the Marker object.
|
||||
|
||||
Using "-k MARKNAME" to select tests
|
||||
----------------------------------------------------
|
||||
|
||||
|
@ -105,15 +110,23 @@ class MarkDecorator:
|
|||
""" if passed a single callable argument: decorate it with mark info.
|
||||
otherwise add *args/**kwargs in-place to mark information. """
|
||||
if args:
|
||||
if len(args) == 1 and hasattr(args[0], '__call__'):
|
||||
func = args[0]
|
||||
holder = getattr(func, self.markname, None)
|
||||
if holder is None:
|
||||
holder = MarkInfo(self.markname, self.args, self.kwargs)
|
||||
setattr(func, self.markname, holder)
|
||||
func = args[0]
|
||||
if len(args) == 1 and hasattr(func, '__call__') or \
|
||||
hasattr(func, '__bases__'):
|
||||
if hasattr(func, '__bases__'):
|
||||
l = func.__dict__.setdefault("pytestmark", [])
|
||||
if not isinstance(l, list):
|
||||
func.pytestmark = [l, self]
|
||||
else:
|
||||
l.append(self)
|
||||
else:
|
||||
holder.kwargs.update(self.kwargs)
|
||||
holder.args.extend(self.args)
|
||||
holder = getattr(func, self.markname, None)
|
||||
if holder is None:
|
||||
holder = MarkInfo(self.markname, self.args, self.kwargs)
|
||||
setattr(func, self.markname, holder)
|
||||
else:
|
||||
holder.kwargs.update(self.kwargs)
|
||||
holder.args.extend(self.args)
|
||||
return func
|
||||
else:
|
||||
self.args.extend(args)
|
||||
|
@ -147,6 +160,10 @@ def pytest_pycollect_makeitem(__multicall__, collector, name, obj):
|
|||
func = getattr(func, 'im_func', func) # py2
|
||||
for parent in [x for x in (mod, cls) if x]:
|
||||
marker = getattr(parent.obj, 'pytestmark', None)
|
||||
if isinstance(marker, MarkDecorator):
|
||||
marker(func)
|
||||
if marker is not None:
|
||||
if not isinstance(marker, list):
|
||||
marker = [marker]
|
||||
for mark in marker:
|
||||
if isinstance(mark, MarkDecorator):
|
||||
mark(func)
|
||||
return item
|
||||
|
|
|
@ -60,6 +60,19 @@ for skipping all methods of a test class based on platform::
|
|||
#
|
||||
|
||||
The ``pytestmark`` decorator will be applied to each test function.
|
||||
If your code targets python2.6 or above you can equivalently use
|
||||
the skipif decorator on classes::
|
||||
|
||||
@py.test.mark.skipif("sys.platform == 'win32'")
|
||||
class TestPosixCalls:
|
||||
|
||||
def test_function(self):
|
||||
# will not be setup or run under 'win32' platform
|
||||
#
|
||||
|
||||
It is fine in general to apply multiple "skipif" decorators
|
||||
on a single function - this means that if any of the conditions
|
||||
apply the function will be skipped.
|
||||
|
||||
.. _`whole class- or module level`: mark.html#scoped-marking
|
||||
|
||||
|
@ -144,17 +157,20 @@ class MarkEvaluator:
|
|||
def istrue(self):
|
||||
if self.holder:
|
||||
d = {'os': py.std.os, 'sys': py.std.sys, 'config': self.item.config}
|
||||
self.result = True
|
||||
for expr in self.holder.args:
|
||||
self.expr = expr
|
||||
if isinstance(expr, str):
|
||||
result = cached_eval(self.item.config, expr, d)
|
||||
else:
|
||||
result = expr
|
||||
if not result:
|
||||
self.result = False
|
||||
if self.holder.args:
|
||||
self.result = False
|
||||
for expr in self.holder.args:
|
||||
self.expr = expr
|
||||
break
|
||||
if isinstance(expr, str):
|
||||
result = cached_eval(self.item.config, expr, d)
|
||||
else:
|
||||
result = expr
|
||||
if result:
|
||||
self.result = True
|
||||
self.expr = expr
|
||||
break
|
||||
else:
|
||||
self.result = True
|
||||
return getattr(self, 'result', False)
|
||||
|
||||
def get(self, attr, default=None):
|
||||
|
|
|
@ -68,11 +68,41 @@ class TestFunctional:
|
|||
keywords = item.readkeywords()
|
||||
assert 'hello' in keywords
|
||||
|
||||
def test_mark_per_class(self, testdir):
|
||||
def test_marklist_per_class(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
import py
|
||||
class TestClass:
|
||||
pytestmark = py.test.mark.hello
|
||||
pytestmark = [py.test.mark.hello, py.test.mark.world]
|
||||
def test_func(self):
|
||||
assert TestClass.test_func.hello
|
||||
assert TestClass.test_func.world
|
||||
""")
|
||||
clscol = modcol.collect()[0]
|
||||
item = clscol.collect()[0].collect()[0]
|
||||
keywords = item.readkeywords()
|
||||
assert 'hello' in keywords
|
||||
|
||||
def test_marklist_per_module(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
import py
|
||||
pytestmark = [py.test.mark.hello, py.test.mark.world]
|
||||
class TestClass:
|
||||
def test_func(self):
|
||||
assert TestClass.test_func.hello
|
||||
assert TestClass.test_func.world
|
||||
""")
|
||||
clscol = modcol.collect()[0]
|
||||
item = clscol.collect()[0].collect()[0]
|
||||
keywords = item.readkeywords()
|
||||
assert 'hello' in keywords
|
||||
assert 'world' in keywords
|
||||
|
||||
@py.test.mark.skipif("sys.version_info < (2,6)")
|
||||
def test_mark_per_class_decorator(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
import py
|
||||
@py.test.mark.hello
|
||||
class TestClass:
|
||||
def test_func(self):
|
||||
assert TestClass.test_func.hello
|
||||
""")
|
||||
|
@ -81,6 +111,23 @@ class TestFunctional:
|
|||
keywords = item.readkeywords()
|
||||
assert 'hello' in keywords
|
||||
|
||||
@py.test.mark.skipif("sys.version_info < (2,6)")
|
||||
def test_mark_per_class_decorator_plus_existing_dec(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
import py
|
||||
@py.test.mark.hello
|
||||
class TestClass:
|
||||
pytestmark = py.test.mark.world
|
||||
def test_func(self):
|
||||
assert TestClass.test_func.hello
|
||||
assert TestClass.test_func.world
|
||||
""")
|
||||
clscol = modcol.collect()[0]
|
||||
item = clscol.collect()[0].collect()[0]
|
||||
keywords = item.readkeywords()
|
||||
assert 'hello' in keywords
|
||||
assert 'world' in keywords
|
||||
|
||||
def test_merging_markers(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
import py
|
||||
|
|
|
@ -52,6 +52,39 @@ class TestEvaluator:
|
|||
assert expl == "hello world"
|
||||
assert ev.get("attr") == 2
|
||||
|
||||
def test_marked_one_arg_twice(self, testdir):
|
||||
lines = [
|
||||
'''@py.test.mark.skipif("not hasattr(os, 'murks')")''',
|
||||
'''@py.test.mark.skipif("hasattr(os, 'murks')")'''
|
||||
]
|
||||
for i in range(0, 2):
|
||||
item = testdir.getitem("""
|
||||
import py
|
||||
%s
|
||||
%s
|
||||
def test_func():
|
||||
pass
|
||||
""" % (lines[i], lines[(i+1) %2]))
|
||||
ev = MarkEvaluator(item, 'skipif')
|
||||
assert ev
|
||||
assert ev.istrue()
|
||||
expl = ev.getexplanation()
|
||||
assert expl == "condition: not hasattr(os, 'murks')"
|
||||
|
||||
def test_marked_one_arg_twice2(self, testdir):
|
||||
item = testdir.getitem("""
|
||||
import py
|
||||
@py.test.mark.skipif("hasattr(os, 'murks')")
|
||||
@py.test.mark.skipif("not hasattr(os, 'murks')")
|
||||
def test_func():
|
||||
pass
|
||||
""")
|
||||
ev = MarkEvaluator(item, 'skipif')
|
||||
assert ev
|
||||
assert ev.istrue()
|
||||
expl = ev.getexplanation()
|
||||
assert expl == "condition: not hasattr(os, 'murks')"
|
||||
|
||||
def test_skipif_class(self, testdir):
|
||||
item, = testdir.getitems("""
|
||||
import py
|
||||
|
|
Loading…
Reference in New Issue