From 4a76c096da0b75abe0d0aca9b0de0a914ab6228d Mon Sep 17 00:00:00 2001 From: holger krekel Date: Thu, 22 Oct 2009 15:21:58 +0200 Subject: [PATCH] extend and refine test marking - allow to mark tests via a "pytestmark" name at class/module level. - make combined positional args of marker calls available via an _args argument --HG-- branch : trunk --- _py/test/plugin/pytest_keyword.py | 69 +++++++++-- testing/pytest/plugin/test_pytest_keyword.py | 119 +++++++++++++++---- 2 files changed, 154 insertions(+), 34 deletions(-) diff --git a/_py/test/plugin/pytest_keyword.py b/_py/test/plugin/pytest_keyword.py index 66697819f..626b6fea5 100644 --- a/_py/test/plugin/pytest_keyword.py +++ b/_py/test/plugin/pytest_keyword.py @@ -1,7 +1,7 @@ """ mark test functions with keywords that may hold values. -Marking functions and setting rich attributes +Marking functions by a decorator ---------------------------------------------------- By default, all filename parts and class/function names of a test @@ -30,8 +30,29 @@ In addition to keyword arguments you can also use positional arguments:: def test_receive(): ... -after which ``test_receive.webtest._1 == 'triangular`` hold true. +after which ``test_receive.webtest._args[0] == 'triangular`` holds true. + +Marking classes or modules +---------------------------------------------------- + +To mark all methods of a class you can set a class-level attribute:: + + class TestClass: + pytestmark = py.test.mark.webtest + +the marker function will be applied to all test methods. + +If you set a marker it inside a test module like this:: + + pytestmark = py.test.mark.webtest + +the marker will be applied to all functions and methods of +that module. The module marker is applied last. + +Outer ``pytestmark`` keywords will overwrite inner keyword +values. Positional arguments are all appeneded to the +same '_args' list. """ import py @@ -49,6 +70,8 @@ class MarkerDecorator: """ decorator for setting function attributes. """ def __init__(self, name): self.markname = name + self.kwargs = {} + self.args = [] def __repr__(self): d = self.__dict__.copy() @@ -57,19 +80,41 @@ class MarkerDecorator: def __call__(self, *args, **kwargs): if args: - if hasattr(args[0], '__call__'): + if len(args) == 1 and hasattr(args[0], '__call__'): func = args[0] - mh = MarkHolder(getattr(self, 'kwargs', {})) - setattr(func, self.markname, mh) + holder = getattr(func, self.markname, None) + if holder is None: + holder = MarkHolder(self.markname, self.args, self.kwargs) + setattr(func, self.markname, holder) + else: + holder.__dict__.update(self.kwargs) + holder._args.extend(self.args) return func - # not a function so we memorize all args/kwargs settings - for i, arg in enumerate(args): - kwargs["_" + str(i)] = arg - if hasattr(self, 'kwargs'): - raise TypeError("double mark-keywords?") - self.kwargs = kwargs.copy() + else: + self.args.extend(args) + self.kwargs.update(kwargs) return self class MarkHolder: - def __init__(self, kwargs): + def __init__(self, name, args, kwargs): + self._name = name + self._args = args + self._kwargs = kwargs self.__dict__.update(kwargs) + + def __repr__(self): + return "" % ( + self._name, self._args, self._kwargs) + + +def pytest_pycollect_makeitem(__multicall__, collector, name, obj): + item = __multicall__.execute() + if isinstance(item, py.test.collect.Function): + cls = collector.getparent(py.test.collect.Class) + mod = collector.getparent(py.test.collect.Module) + func = getattr(item.obj, 'im_func', item.obj) + for parent in [x for x in (mod, cls) if x]: + marker = getattr(parent.obj, 'pytestmark', None) + if isinstance(marker, MarkerDecorator): + marker(func) + return item diff --git a/testing/pytest/plugin/test_pytest_keyword.py b/testing/pytest/plugin/test_pytest_keyword.py index 2156e2ca8..af4c87e8f 100644 --- a/testing/pytest/plugin/test_pytest_keyword.py +++ b/testing/pytest/plugin/test_pytest_keyword.py @@ -1,30 +1,105 @@ import py from _py.test.plugin.pytest_keyword import Mark -def test_pytest_mark_api(): - mark = Mark() - py.test.raises(TypeError, "mark(x=3)") +class TestMark: + def test_pytest_mark_notcallable(self): + mark = Mark() + py.test.raises(TypeError, "mark()") - def f(): pass - mark.hello(f) - assert f.hello + def test_pytest_mark_bare(self): + mark = Mark() + def f(): pass + mark.hello(f) + assert f.hello - mark.world(x=3, y=4)(f) - assert f.world - assert f.world.x == 3 - assert f.world.y == 4 + def test_pytest_mark_keywords(self): + mark = Mark() + def f(): pass + mark.world(x=3, y=4)(f) + assert f.world + assert f.world.x == 3 + assert f.world.y == 4 - mark.world("hello")(f) - assert f.world._0 == "hello" + def test_apply_multiple_and_merge(self): + mark = Mark() + def f(): pass + marker = mark.world + mark.world(x=3)(f) + assert f.world.x == 3 + mark.world(y=4)(f) + assert f.world.x == 3 + assert f.world.y == 4 + mark.world(y=1)(f) + assert f.world.y == 1 + assert len(f.world._args) == 0 - py.test.raises(TypeError, "mark.some(x=3)(f=5)") + def test_pytest_mark_positional(self): + mark = Mark() + def f(): pass + mark.world("hello")(f) + assert f.world._args[0] == "hello" + mark.world("world")(f) -def test_mark_plugin(testdir): - p = testdir.makepyfile(""" - import py - @py.test.mark.hello - def test_hello(): - assert hasattr(test_hello, 'hello') - """) - result = testdir.runpytest(p) - assert result.stdout.fnmatch_lines(["*passed*"]) +class TestFunctional: + def test_mark_per_function(self, testdir): + p = testdir.makepyfile(""" + import py + @py.test.mark.hello + def test_hello(): + assert hasattr(test_hello, 'hello') + """) + result = testdir.runpytest(p) + assert result.stdout.fnmatch_lines(["*passed*"]) + + def test_mark_per_module(self, testdir): + item = testdir.getitem(""" + import py + pytestmark = py.test.mark.hello + def test_func(): + pass + """) + keywords = item.readkeywords() + assert 'hello' in keywords + + def test_mark_per_class(self, testdir): + modcol = testdir.getmodulecol(""" + import py + class TestClass: + pytestmark = py.test.mark.hello + def test_func(self): + assert TestClass.test_func.hello + """) + clscol = modcol.collect()[0] + item = clscol.collect()[0].collect()[0] + keywords = item.readkeywords() + assert 'hello' in keywords + + def test_merging_markers(self, testdir): + p = testdir.makepyfile(""" + import py + pytestmark = py.test.mark.hello("pos1", x=1, y=2) + class TestClass: + # classlevel overrides module level + pytestmark = py.test.mark.hello(x=3) + @py.test.mark.hello("pos0", z=4) + def test_func(self): + pass + """) + items, rec = testdir.inline_genitems(p) + item, = items + keywords = item.readkeywords() + marker = keywords['hello'] + assert marker._args == ["pos0", "pos1"] + assert marker.x == 3 + assert marker.y == 2 + assert marker.z == 4 + + def test_mark_other(self, testdir): + item = testdir.getitem(""" + import py + class pytestmark: + pass + def test_func(): + pass + """) + keywords = item.readkeywords()