Skip to content

Commit bf2c0db

Browse files
author
Sylvain MARIE
committed
Fixed bug with concatenation of marks on cases. Fixes #191
1 parent 7276eeb commit bf2c0db

File tree

6 files changed

+96
-25
lines changed

6 files changed

+96
-25
lines changed

pytest_cases/case_funcs.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def __init__(self,
4949
tags=() # type: Tuple[Any]
5050
):
5151
self.id = id
52-
self.marks = marks
52+
self.marks = marks # type: Tuple[MarkDecorator, ...]
5353
self.tags = ()
5454
self.add_tags(tags)
5555

@@ -180,6 +180,7 @@ def get_case_marks(case_func, # type: Callable
180180
concatenate_with_fun_marks=False, # type: bool
181181
as_decorators=False # type: bool
182182
):
183+
# type: (...) -> Union[Tuple[Mark, ...], Tuple[MarkDecorator, ...]]
183184
"""Return the marks that are on the case function.
184185
185186
There are currently two ways to place a mark on a case function: either with `@pytest.mark.<name>` or in
@@ -198,12 +199,14 @@ def get_case_marks(case_func, # type: Callable
198199
if _ci is None:
199200
_ci_marks = None
200201
else:
202+
# convert the MarkDecorators to Marks if needed
201203
_ci_marks = _ci.marks if as_decorators else markdecorators_to_markinfos(_ci.marks)
202204

203205
if not concatenate_with_fun_marks:
204206
return _ci_marks
205207
else:
206-
fun_marks = get_pytest_marks_on_function(case_func, as_decorators=as_decorators)
208+
# concatenate the marks on the `_CaseInfo` with the ones on `case_func`
209+
fun_marks = tuple(get_pytest_marks_on_function(case_func, as_decorators=as_decorators))
207210
return (_ci_marks + fun_marks) if _ci_marks else fun_marks
208211

209212

pytest_cases/case_parametrizer_new.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ def case_to_argvalues(host_class_or_module, # type: Union[Type, ModuleType]
337337
if not meta.requires_fixtures and not meta.is_parametrized:
338338
# only retrieve the extra marks added with @case, since the others will be automatically retrieved by the
339339
# lazy_value.
340-
case_marks = get_case_marks(case_fun)
340+
case_marks = get_case_marks(case_fun, as_decorators=True)
341341

342342
# if not meta.is_parametrized:
343343
# single unparametrized case function

pytest_cases/common_pytest_lazy_values.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from funcsigs import signature # noqa
1212

1313
try:
14-
from typing import Union, Callable, List, Set, Tuple, Any, Sequence, Optional # noqa
14+
from typing import Union, Callable, List, Set, Tuple, Any, Sequence, Optional, Iterable # noqa
1515
except ImportError:
1616
pass
1717

@@ -20,8 +20,8 @@
2020
except ImportError:
2121
pass
2222

23-
from .common_pytest_marks import get_pytest_marks_on_function, markinfos_to_markdecorators, markdecorators_as_tuple, \
24-
PYTEST53_OR_GREATER
23+
from .common_pytest_marks import get_pytest_marks_on_function, markdecorators_as_tuple, PYTEST53_OR_GREATER, \
24+
markdecorators_to_markinfos
2525

2626

2727
class Lazy(object):
@@ -156,26 +156,34 @@ def copy_from(cls,
156156
def __init__(self,
157157
valuegetter, # type: Callable[[], Any]
158158
id=None, # type: str # noqa
159-
marks=None, # type: Union[MarkDecorator, Tuple[MarkDecorator, ...], List[MarkDecorator], Set[MarkDecorator]]
159+
marks=None, # type: Union[MarkDecorator, Iterable[MarkDecorator]]
160160
):
161161
self.valuegetter = valuegetter
162162
self._id = id
163163
self._marks = markdecorators_as_tuple(marks)
164164
self.cached_value_context = None
165165
self.cached_value = None
166166

167-
def get_marks(self, as_decorators=False):
167+
def get_marks(self,
168+
as_decorators=False # type: bool
169+
):
170+
# type: (...) -> Union[Tuple[Mark, ...], Tuple[MarkDecorator, ...]]
168171
"""
169172
Overrides default implementation to return the marks that are on the case function
170173
171174
:param as_decorators: when True, the marks (MarkInfo) will be transformed into MarkDecorators before being
172175
returned
173176
:return:
174177
"""
175-
valuegetter_marks = get_pytest_marks_on_function(self.valuegetter, as_decorators=as_decorators)
178+
valuegetter_marks = tuple(get_pytest_marks_on_function(self.valuegetter, as_decorators=as_decorators))
176179

177180
if self._marks:
178-
self_marks = markinfos_to_markdecorators(self._marks, function_marks=True) if as_decorators else self._marks
181+
if as_decorators:
182+
# self_marks = markinfos_to_markdecorators(self._marks, function_marks=True)
183+
self_marks = self._marks
184+
else:
185+
self_marks = markdecorators_to_markinfos(self._marks)
186+
179187
return self_marks + valuegetter_marks
180188
else:
181189
return valuegetter_marks
@@ -423,7 +431,7 @@ def clone(self, remove_int_base=False):
423431

424432
def lazy_value(valuegetter, # type: Callable[[], Any]
425433
id=None, # type: str # noqa
426-
marks=() # type: Union[Any, Sequence[Any]]
434+
marks=() # type: Union[MarkDecorator, Iterable[MarkDecorator]]
427435
):
428436
"""
429437
Creates a reference to a value getter, to be used in `parametrize_plus`.

pytest_cases/common_pytest_marks.py

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
try:
2323
from _pytest.mark.structures import MarkDecorator, Mark # noqa
2424
except ImportError:
25-
pass
25+
from _pytest.mark import MarkDecorator, MarkInfo as Mark # noqa
2626

2727
from .common_mini_six import string_types
2828

@@ -114,15 +114,26 @@ def copy_pytest_marks(from_f, to_f, override=False):
114114
to_f.pytestmark = to_marks + from_marks
115115

116116

117-
def filter_marks(marks,
117+
def filter_marks(marks, # type: Iterable[Mark]
118118
remove # type: str
119119
):
120-
return [m for m in marks if m.name != remove]
120+
# type: (...) -> Tuple[Mark]
121+
"""
122+
Returns a tuple of all marks in `marks` that do not have a 'parametrize' name.
123+
124+
:param marks:
125+
:param remove:
126+
:return:
127+
"""
128+
return tuple(m for m in marks if m.name != remove)
121129

122130

123-
def get_pytest_marks_on_function(f, as_decorators=False):
131+
def get_pytest_marks_on_function(f,
132+
as_decorators=False # type: bool
133+
):
134+
# type: (...) -> Union[List[Mark], List[MarkDecorator]]
124135
"""
125-
Utility to return *ALL* pytest marks (not only parametrization) applied on a function
136+
Utility to return a list of *ALL* pytest marks (not only parametrization) applied on a function
126137
Note that this also works on classes
127138
128139
:param f:
@@ -146,6 +157,14 @@ def get_pytest_marks_on_function(f, as_decorators=False):
146157
return mks
147158

148159

160+
def get_pytest_marks_on_item(item):
161+
"""lists all marks on an item such as `request._pyfuncitem`"""
162+
if PYTEST3_OR_GREATER:
163+
return item.callspec.marks
164+
else:
165+
return [val for val in item.keywords.values() if isinstance(val, (MarkDecorator, Mark))]
166+
167+
149168
def get_pytest_usefixture_marks(f):
150169
# pytest > 3.2.0
151170
marks = getattr(f, 'pytestmark', None)
@@ -253,10 +272,13 @@ def make_marked_parameter_value(argvalues_tuple, marks):
253272
def markinfos_to_markdecorators(marks, # type: Iterable[Mark]
254273
function_marks=False # type: bool
255274
):
275+
# type: (...) -> List[MarkDecorator]
256276
"""
257277
Transforms the provided marks (MarkInfo or Mark in recent pytest) obtained from marked cases, into MarkDecorator so
258278
that they can be re-applied to generated pytest parameters in the global @pytest.mark.parametrize.
259279
280+
Returns a list.
281+
260282
:param marks:
261283
:param function_marks:
262284
:return:
@@ -297,7 +319,7 @@ def markinfos_to_markdecorators(marks, # type: Iterable[Mark]
297319
return marks_mod
298320

299321

300-
def markdecorators_as_tuple(marks # type: Optional[Union[MarkDecorator, Tuple[MarkDecorator, ...], List[MarkDecorator], Set[MarkDecorator]]]
322+
def markdecorators_as_tuple(marks # type: Optional[Union[MarkDecorator, Iterable[MarkDecorator]]]
301323
):
302324
# type: (...) -> Tuple[MarkDecorator, ...]
303325
"""
@@ -306,18 +328,23 @@ def markdecorators_as_tuple(marks # type: Optional[Union[MarkDecorator, Tuple[M
306328
:param marks:
307329
:return:
308330
"""
309-
if isinstance(marks, (tuple, list, set)):
331+
if marks is None:
332+
return ()
333+
334+
try:
335+
# iterable ?
310336
return tuple(marks)
311-
elif marks is not None:
337+
except TypeError:
338+
# single
312339
return (marks,)
313340

314341

315342
def markdecorators_to_markinfos(marks # type: Sequence[MarkDecorator]
316343
):
344+
# type: (...) -> Tuple[Mark, ...]
317345
if PYTEST3_OR_GREATER:
318346
return tuple(m.mark for m in marks)
347+
elif len(marks) == 0:
348+
return ()
319349
else:
320-
if len(marks) == 0:
321-
return ()
322-
else:
323-
raise NotImplementedError("TODO")
350+
return tuple(Mark(m.name, m.args, m.kwargs) for m in marks)

pytest_cases/fixture_parametrize_plus.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,7 +1185,7 @@ def _process_argvalues(argnames, marked_argvalues, nb_params, has_custom_ids, au
11851185
_mks = v.get_marks(as_decorators=True)
11861186
if len(_mks) > 0:
11871187
# update/create the pytest.param marks on this value
1188-
p_marks[i] = (list(p_marks[i]) + _mks) if p_marks[i] is not None else list(_mks)
1188+
p_marks[i] = (p_marks[i] + _mks) if p_marks[i] is not None else _mks
11891189

11901190
# update the original marked_argvalues. Note that argvalues[i] = v
11911191
marked_argvalues[i] = ParameterSet(values=(argvalues[i],), id=p_ids[i], marks=p_marks[i])
@@ -1282,7 +1282,7 @@ def _process_argvalues(argnames, marked_argvalues, nb_params, has_custom_ids, au
12821282
_mks = v.get_marks(as_decorators=True)
12831283
if len(_mks) > 0:
12841284
# update/create the pytest.param marks on this value
1285-
p_marks[i] = (list(p_marks[i]) + _mks) if p_marks[i] is not None else list(_mks)
1285+
p_marks[i] = (p_marks[i] + _mks) if p_marks[i] is not None else _mks
12861286

12871287
# update the marked_argvalues
12881288
# - at least with the unpacked lazytuple if no pytest.param is there or needs to be created
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import pytest
2+
3+
from pytest_cases.common_pytest_marks import get_pytest_marks_on_item
4+
from pytest_cases import case, parametrize_with_cases, fixture
5+
6+
7+
@fixture
8+
def my_fix():
9+
return 2
10+
11+
12+
class CasesFeature:
13+
@case(tags=["categorical"], marks=pytest.mark.fast)
14+
def case_no_fixture(self):
15+
return 1
16+
17+
@case(tags=["med", "categorical"], marks=pytest.mark.slow)
18+
def case_fixture(self, my_fix):
19+
return my_fix
20+
21+
22+
@parametrize_with_cases("data", cases=CasesFeature, has_tag="categorical")
23+
def test_marks(data, request):
24+
"""Make sure that the marks are correctly set"""
25+
26+
current_marks = get_pytest_marks_on_item(request._pyfuncitem)
27+
assert len(current_marks) == 1
28+
if data == 1:
29+
assert current_marks[0].name == "fast"
30+
elif data == 2:
31+
assert current_marks[0].name == "slow"
32+
else:
33+
raise AssertionError()

0 commit comments

Comments
 (0)