From 47ba4cea286c05823ebb81d9798ed2abf7c3fa79 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sat, 25 Jun 2016 11:22:51 +0200 Subject: [PATCH 1/3] mark plugin: introduce a dedicated mark class and use it for the decorator --- _pytest/mark.py | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/_pytest/mark.py b/_pytest/mark.py index d8b60def366..1603270efc3 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -202,7 +202,7 @@ def istestfunc(func): return hasattr(func, "__call__") and \ getattr(func, "__name__", "") != "" -class MarkDecorator: +class MarkDecorator(object): """ A decorator for test functions and test classes. When applied it will create :class:`MarkInfo` objects which may be :ref:`retrieved by hooks as item keywords `. @@ -236,18 +236,28 @@ def test_function(): """ def __init__(self, name, args=None, kwargs=None): - self.name = name - self.args = args or () - self.kwargs = kwargs or {} + + self._mark = Mark(name, args or (), kwargs or {}) + + @property + def name(self): + return self._mark.name + + @property + def args(self): + return self._mark.args + + @property + def kwargs(self): + return self._mark.kwargs @property def markname(self): return self.name # for backward-compat (2.4.1 had this attr) def __repr__(self): - d = self.__dict__.copy() - name = d.pop('name') - return "" % (name, d) + return "" % ( + self.name, { 'args': self.args, 'kwargs': self.kwargs}) def __call__(self, *args, **kwargs): """ if passed a single callable argument: decorate it with mark info. @@ -283,6 +293,18 @@ def __call__(self, *args, **kwargs): return self.__class__(self.name, args=args, kwargs=kw) + +class Mark(object): + """ Marking object created by :class:`MarkDecorator` instances. """ + def __init__(self, name, args, kwargs): + #: name of attribute + self.name = name + #: positional argument list, empty if none specified + self.args = args + #: keyword argument dictionary, empty if nothing specified + self.kwargs = kwargs.copy() + + class MarkInfo: """ Marking object created by :class:`MarkDecorator` instances. """ def __init__(self, name, args, kwargs): From c7861627fa41443e66c6ffd20fd09bbd1d9ffe15 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sat, 25 Jun 2016 11:37:48 +0200 Subject: [PATCH 2/3] mark plugin: use mark object in the MarkInfo class --- _pytest/mark.py | 44 ++++++++++++++++++++++++++------------------ testing/test_mark.py | 4 ++-- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/_pytest/mark.py b/_pytest/mark.py index 1603270efc3..5b81941456d 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -280,12 +280,10 @@ def __call__(self, *args, **kwargs): else: holder = getattr(func, self.name, None) if holder is None: - holder = MarkInfo( - self.name, self.args, self.kwargs - ) + holder = MarkInfo(self._mark) setattr(func, self.name, holder) else: - holder.add(self.args, self.kwargs) + holder.add(self._mark) return func kw = self.kwargs.copy() kw.update(kwargs) @@ -307,27 +305,37 @@ def __init__(self, name, args, kwargs): class MarkInfo: """ Marking object created by :class:`MarkDecorator` instances. """ - def __init__(self, name, args, kwargs): - #: name of attribute - self.name = name - #: positional argument list, empty if none specified - self.args = args - #: keyword argument dictionary, empty if nothing specified - self.kwargs = kwargs.copy() - self._arglist = [(args, kwargs.copy())] + def __init__(self, mark): + self._marks = [mark] + + @property + def name(self): + return self._marks[0].name + + @property + def args(self): + ret = () + for mark in self._marks: + ret += mark.args + return ret + + @property + def kwargs(self): + ret = {} + for mark in self._marks: + ret.update(mark.kwargs) + return ret def __repr__(self): return "" % ( self.name, self.args, self.kwargs ) - def add(self, args, kwargs): + def add(self, mark): """ add a MarkInfo with the given args and kwargs. """ - self._arglist.append((args, kwargs)) - self.args += args - self.kwargs.update(kwargs) + self._marks.append(mark) def __iter__(self): """ yield MarkInfo objects each relating to a marking-call. """ - for args, kwargs in self._arglist: - yield MarkInfo(self.name, args, kwargs) + for mark in self._marks: + yield MarkInfo(mark) diff --git a/testing/test_mark.py b/testing/test_mark.py index 9b3e15b37e3..00a797fac81 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -5,8 +5,8 @@ class TestMark: def test_markinfo_repr(self): - from _pytest.mark import MarkInfo - m = MarkInfo("hello", (1,2), {}) + from _pytest.mark import MarkInfo, Mark + m = MarkInfo(Mark("hello", (1,2), {})) repr(m) def test_pytest_exists_in_namespace_all(self): From 3a1eb24d1e004a0a92c37b8e169724473f7419e1 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sat, 25 Jun 2016 12:00:56 +0200 Subject: [PATCH 3/3] mark plugin: move mark copying into the mark class --- _pytest/main.py | 2 +- _pytest/mark.py | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/_pytest/main.py b/_pytest/main.py index 845d5dd00f3..cbab306d017 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -335,7 +335,7 @@ def add_marker(self, marker): """ from _pytest.mark import MarkDecorator if isinstance(marker, py.builtin._basestring): - marker = MarkDecorator(marker) + marker = getattr(pytest.mark, marker) elif not isinstance(marker, MarkDecorator): raise ValueError("is not a string or pytest.mark.* Marker") self.keywords[marker.name] = marker diff --git a/_pytest/mark.py b/_pytest/mark.py index 5b81941456d..af72d86057d 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -182,7 +182,7 @@ def __getattr__(self, name): raise AttributeError("Marker name must NOT start with underscore") if hasattr(self, '_config'): self._check(name) - return MarkDecorator(name) + return MarkDecorator(Mark(name, (), {})) def _check(self, name): try: @@ -235,9 +235,9 @@ def test_function(): additional keyword or positional arguments. """ - def __init__(self, name, args=None, kwargs=None): + def __init__(self, mark): - self._mark = Mark(name, args or (), kwargs or {}) + self._mark = mark @property def name(self): @@ -285,10 +285,7 @@ def __call__(self, *args, **kwargs): else: holder.add(self._mark) return func - kw = self.kwargs.copy() - kw.update(kwargs) - args = self.args + args - return self.__class__(self.name, args=args, kwargs=kw) + return self.__class__(self._mark.new_with(args, kwargs)) @@ -302,6 +299,11 @@ def __init__(self, name, args, kwargs): #: keyword argument dictionary, empty if nothing specified self.kwargs = kwargs.copy() + def new_with(self, args, kwargs): + kw = self.kwargs.copy() + kw.update(kwargs) + return Mark(self.name, self.args + args, kw) + class MarkInfo: """ Marking object created by :class:`MarkDecorator` instances. """