fix handling MarkDecorators called with a single positional plus keyword args
When a MarkDecorator instance is called it does the following: 1. If called with a single class as its only positional argument and no additional keyword arguments, it attaches itself to the class so it gets applied automatically to all test cases found in that class. 2. If called with a single function as its only positional argument and no additional keyword arguments, it attaches a MarkInfo object to the function, containing all the arguments already stored internally in the MarkDecorator. 3. When called in any other case, it performs a 'fake construction' call, i.e. it returns a new MarkDecorator instance with the original MarkDecorator's content updated with the arguments passed to this call. When Python applies a function decorator it always passes the target class/ function to the decorator as its positional argument with no additional positional or keyword arguments. However, when MarkDecorator was deciding whether it was being called to decorate a target function/class (cases 1. & 2. as documented above) or to return an updated MarkDecorator (case 3. as documented above), it only checked that it received a single callable positional argument and did not take into consideration whether additional keyword arguments were being passed in as well. With this change, it is now possible to create a pytest mark storing a function/ class parameter passed as its only positional argument and accompanied by one or more additional keyword arguments. Before, it was only possible to do so if the function/class parameter argument was accompanied by at least one other positional argument. Added a related unit test. Updated MarkDecorator doc-string.
This commit is contained in:
parent
492c60c202
commit
8e457338ee
|
@ -206,6 +206,24 @@ class MarkDecorator:
|
|||
@mark2
|
||||
def test_function():
|
||||
pass
|
||||
|
||||
When a MarkDecorator instance is called it does the following:
|
||||
1. If called with a single class as its only positional argument and no
|
||||
additional keyword arguments, it attaches itself to the class so it
|
||||
gets applied automatically to all test cases found in that class.
|
||||
2. If called with a single function as its only positional argument and
|
||||
no additional keyword arguments, it attaches a MarkInfo object to the
|
||||
function, containing all the arguments already stored internally in
|
||||
the MarkDecorator.
|
||||
3. When called in any other case, it performs a 'fake construction' call,
|
||||
i.e. it returns a new MarkDecorator instance with the original
|
||||
MarkDecorator's content updated with the arguments passed to this
|
||||
call.
|
||||
|
||||
Note: The rules above prevent MarkDecorator objects from storing only a
|
||||
single function or class reference as their positional argument with no
|
||||
additional keyword or positional arguments.
|
||||
|
||||
"""
|
||||
def __init__(self, name, args=None, kwargs=None):
|
||||
self.name = name
|
||||
|
@ -224,7 +242,7 @@ class MarkDecorator:
|
|||
def __call__(self, *args, **kwargs):
|
||||
""" if passed a single callable argument: decorate it with mark info.
|
||||
otherwise add *args/**kwargs in-place to mark information. """
|
||||
if args:
|
||||
if args and not kwargs:
|
||||
func = args[0]
|
||||
if len(args) == 1 and (istestfunc(func) or
|
||||
hasattr(func, '__bases__')):
|
||||
|
|
|
@ -57,6 +57,17 @@ class TestMark:
|
|||
assert f.world.args[0] == "hello"
|
||||
mark.world("world")(f)
|
||||
|
||||
def test_pytest_mark_positional_func_and_keyword(self):
|
||||
mark = Mark()
|
||||
def f():
|
||||
raise Exception
|
||||
m = mark.world(f, omega="hello")
|
||||
def g():
|
||||
pass
|
||||
assert m(g) == g
|
||||
assert g.world.args[0] is f
|
||||
assert g.world.kwargs["omega"] == "hello"
|
||||
|
||||
def test_pytest_mark_reuse(self):
|
||||
mark = Mark()
|
||||
def f():
|
||||
|
|
Loading…
Reference in New Issue