⬅ pytest_cases/common_pytest.py source

1 # Authors: Sylvain MARIE <sylvain.marie@se.com>
2 # + All contributors to <https://github.com/smarie/python-pytest-cases>
3 #
4 # License: 3-clause BSD, <https://github.com/smarie/python-pytest-cases/blob/master/LICENSE>
5 from __future__ import division
6  
7 import inspect
8 import sys
9 import os
10 from importlib import import_module
11  
12 from makefun import add_signature_parameters, wraps
13  
14 try: # python 3.3+
15 from inspect import signature, Parameter
16 except ImportError:
17 from funcsigs import signature, Parameter # noqa
18  
19 from inspect import isgeneratorfunction, isclass
20  
21 try:
22 from typing import Union, Callable, Any, Optional, Tuple, Type, Iterable, Sized, List # noqa
23 except ImportError:
24 pass
25  
26 import pytest
27 from _pytest.python import Metafunc
28  
29 from .common_mini_six import string_types
30 from .common_others import get_function_host
31 from .common_pytest_marks import make_marked_parameter_value, get_param_argnames_as_list, \
32 get_pytest_parametrize_marks, get_pytest_usefixture_marks, PYTEST3_OR_GREATER, PYTEST6_OR_GREATER, \
33 PYTEST38_OR_GREATER, PYTEST34_OR_GREATER, PYTEST33_OR_GREATER, PYTEST32_OR_GREATER, PYTEST71_OR_GREATER, \
34 PYTEST8_OR_GREATER
35 from .common_pytest_lazy_values import is_lazy_value, is_lazy
36  
37  
38 # A decorator that will work to create a fixture containing 'yield', whatever the pytest version, and supports hooks
39 if PYTEST3_OR_GREATER:
40 def pytest_fixture(hook=None, **kwargs):
41 def _decorate(f):
42 # call hook if needed
43 if hook is not None:
44 f = hook(f)
45  
46 # create the fixture
47 return pytest.fixture(**kwargs)(f)
48 return _decorate
49 else:
50 def pytest_fixture(hook=None, name=None, **kwargs):
51 """Generator-aware pytest.fixture decorator for legacy pytest versions"""
52 def _decorate(f):
53 if name is not None:
54 # 'name' argument is not supported in this old version, use the __name__ trick.
55 f.__name__ = name
56  
57 # call hook if needed
58 if hook is not None:
59 f = hook(f)
60  
61 # create the fixture
62 if isgeneratorfunction(f):
63 return pytest.yield_fixture(**kwargs)(f)
64 else:
65 return pytest.fixture(**kwargs)(f)
66 return _decorate
67  
68  
69 def pytest_is_running():
70 """Return True if the current process is a pytest run
71  
72 See https://stackoverflow.com/questions/25188119/test-if-code-is-executed-from-within-a-py-test-session
73 """
74 if PYTEST32_OR_GREATER:
75 return "PYTEST_CURRENT_TEST" in os.environ
76 else:
77 import re
78 return any(re.findall(r'pytest|py.test', sys.argv[0]))
79  
80  
81 def remove_duplicates(lst):
82 dset = set()
83 # relies on the fact that dset.add() always returns None.
84 return [item for item in lst
85 if item not in dset and not dset.add(item)]
86  
87  
88 def is_fixture(fixture_fun # type: Any
89 ):
90 """
91 Returns True if the provided function is a fixture
92  
93 :param fixture_fun:
94 :return:
95 """
96 try:
97 fixture_fun._pytestfixturefunction # noqa
98 return True
99 except AttributeError:
100 # not a fixture ?
101 return False
102  
103  
104 def list_all_fixtures_in(cls_or_module, return_names=True, recurse_to_module=False):
105 """
106 Returns a list containing all fixture names (or symbols if `return_names=False`)
107 in the given class or module.
108  
109 Note that `recurse_to_module` can be used so that the fixtures in the parent
110 module of a class are listed too.
111  
112 :param cls_or_module:
113 :param return_names:
114 :param recurse_to_module:
115 :return:
116 """
117 res = [get_fixture_name(symb) if return_names else symb
118 for n, symb in inspect.getmembers(cls_or_module, lambda f: inspect.isfunction(f) or inspect.ismethod(f))
119 if is_fixture(symb)]
120  
121 if recurse_to_module and not inspect.ismodule(cls_or_module):
122 # TODO currently this only works for a single level of nesting, we should use __qualname__ (py3) or .im_class
123 host = import_module(cls_or_module.__module__)
124 res += list_all_fixtures_in(host, recurse_to_module=True, return_names=return_names)
125  
126 return res
127  
128  
129 def safe_isclass(obj # type: object
130 ):
131 # type: (...) -> bool
132 """Ignore any exception via isinstance on Python 3."""
133 try:
134 return isclass(obj)
135 except Exception: # noqa
136 return False
137  
138  
139 def safe_isinstance(obj, # type: object
140 cls):
141 # type: (...) -> bool
142 """Ignore any exception via isinstance"""
143 try:
144 return isinstance(obj, cls)
145 except Exception: # noqa
146 return False
147  
148  
149 def assert_is_fixture(fixture_fun # type: Any
150 ):
151 """
152 Raises a ValueError if the provided fixture function is not a fixture.
153  
154 :param fixture_fun:
155 :return:
156 """
157 if not is_fixture(fixture_fun):
158 raise ValueError("The provided fixture function does not seem to be a fixture: %s. Did you properly decorate "
159 "it ?" % fixture_fun)
160  
161  
162 def get_fixture_name(fixture_fun # type: Union[str, Callable]
163 ):
164 """
165 Internal utility to retrieve the fixture name corresponding to the given fixture function.
166 Indeed there is currently no pytest API to do this.
167  
168 Note: this function can receive a string, in which case it is directly returned.
169  
170 :param fixture_fun:
171 :return:
172 """
173 if isinstance(fixture_fun, string_types):
174 return fixture_fun
175 assert_is_fixture(fixture_fun)
176 try: # pytest 3
177 custom_fixture_name = fixture_fun._pytestfixturefunction.name # noqa
178 except AttributeError:
179 try: # pytest 2
180 custom_fixture_name = fixture_fun.func_name # noqa
181 except AttributeError:
182 custom_fixture_name = None
183  
184 if custom_fixture_name is not None:
185 # there is a custom fixture name
186 return custom_fixture_name
187 else:
188 obj__name = getattr(fixture_fun, '__name__', None)
189 if obj__name is not None:
190 # a function, probably
191 return obj__name
192 else:
193 # a callable object probably
194 return str(fixture_fun)
195  
196  
197 def get_fixture_scope(fixture_fun):
198 """
199 Internal utility to retrieve the fixture scope corresponding to the given fixture function .
200 Indeed there is currently no pytest API to do this.
201  
202 :param fixture_fun:
203 :return:
204 """
205 assert_is_fixture(fixture_fun)
206 return fixture_fun._pytestfixturefunction.scope # noqa
207 # except AttributeError:
208 # # pytest 2
209 # return fixture_fun.func_scope
210  
211  
212 # ---------------- working on pytest nodes (e.g. Function)
213  
214 def is_function_node(node):
215 try:
216 node.function # noqa
217 return True
218 except AttributeError:
219 return False
220  
221  
222 def get_parametrization_markers(fnode):
223 """
224 Returns the parametrization marks on a pytest Function node.
225 :param fnode:
226 :return:
227 """
228 if PYTEST34_OR_GREATER:
229 return list(fnode.iter_markers(name="parametrize"))
230 else:
231 return list(fnode.parametrize)
232  
233  
234 def get_param_names(fnode):
235 """
236 Returns a list of parameter names for the given pytest Function node.
237 parameterization marks containing several names are split
238  
239 :param fnode:
240 :return:
241 """
242 p_markers = get_parametrization_markers(fnode)
243 param_names = []
244 for paramz_mark in p_markers:
245 argnames = paramz_mark.args[0] if len(paramz_mark.args) > 0 else paramz_mark.kwargs['argnames']
246 param_names += get_param_argnames_as_list(argnames)
247 return param_names
248  
249  
250 # ---------- test ids utils ---------
251 def combine_ids(paramid_tuples):
252 """
253 Receives a list of tuples containing ids for each parameterset.
254 Returns the final ids, that are obtained by joining the various param ids by '-' for each test node
255  
256 :param paramid_tuples:
257 :return:
258 """
259 #
260 return ['-'.join(pid for pid in testid) for testid in paramid_tuples]
261  
262  
263 def make_test_ids(global_ids, id_marks, argnames=None, argvalues=None, precomputed_ids=None):
264 """
265 Creates the proper id for each test based on (higher precedence first)
266  
267 - any specific id mark from a `pytest.param` (`id_marks`)
268 - the global `ids` argument of pytest parametrize (`global_ids`)
269 - the name and value of parameters (`argnames`, `argvalues`) or the precomputed ids(`precomputed_ids`)
270  
271 See also _pytest.python._idvalset method
272  
273 :param global_ids:
274 :param id_marks:
275 :param argnames:
276 :param argvalues:
277 :param precomputed_ids:
278 :return:
279 """
280 if global_ids is not None:
281 # overridden at global pytest.mark.parametrize level - this takes precedence.
282 # resolve possibly infinite generators of ids here
283 p_ids = resolve_ids(global_ids, argvalues, full_resolve=True)
284 else:
285 # default: values-based
286 if precomputed_ids is not None:
287 if argnames is not None or argvalues is not None:
288 raise ValueError("Only one of `precomputed_ids` or argnames/argvalues should be provided.")
289 p_ids = precomputed_ids
290 else:
291 p_ids = make_test_ids_from_param_values(argnames, argvalues)
292  
293 # Finally, local pytest.param takes precedence over everything else
294 for i, _id in enumerate(id_marks):
295 if _id is not None:
296 p_ids[i] = _id
297 return p_ids
298  
299  
300 def resolve_ids(ids, # type: Optional[Union[Callable, Iterable[str]]]
301 argvalues, # type: Sized(Any)
302 full_resolve=False # type: bool
303 ):
304 # type: (...) -> Union[List[str], Callable]
305 """
306 Resolves the `ids` argument of a parametrized fixture.
307  
308 If `full_resolve` is False (default), iterable ids will be resolved, but not callable ids. This is useful if the
309 `argvalues` have not yet been cleaned of possible `pytest.param` wrappers.
310  
311 If `full_resolve` is True, callable ids will be called using the argvalues, so the result is guaranteed to be a
312 list.
313 """
314 try:
315 # an explicit list or generator of ids ?
316 iter(ids)
317 except TypeError:
318 # a callable to apply on the values
319 if full_resolve:
320 return [ids(v) for v in argvalues]
321 else:
322 # return the callable without resolving
323 return ids
324 else:
325 # iterable.
326 try:
327 # a sized container ? (list, set, tuple)
328 nb_ids = len(ids)
329 # convert to list
330 ids = list(ids)
331 except TypeError:
332 # a generator. Consume it
333 ids = [id for id, v in zip(ids, argvalues)]
334 nb_ids = len(ids)
335  
336 if nb_ids != len(argvalues):
337 raise ValueError("Explicit list or generator of `ids` provided has a different length (%s) than the number "
338 "of argvalues (%s). Ids provided: %r" % (len(ids), len(argvalues), ids))
339 return ids
340  
341  
342 def make_test_ids_from_param_values(param_names,
343 param_values,
344 ):
345 """
346 Replicates pytest behaviour to generate the ids when there are several parameters in a single `parametrize.
347 Note that param_values should not contain marks.
348  
349 :param param_names:
350 :param param_values:
351 :return: a list of param ids
352 """
353 if isinstance(param_names, string_types):
354 raise TypeError("param_names must be an iterable. Found %r" % param_names)
355  
356 nb_params = len(param_names)
357 if nb_params == 0:
358 raise ValueError("empty list provided")
359 elif nb_params == 1:
360 paramids = []
361 for _idx, v in enumerate(param_values):
362 _id = mini_idvalset(param_names, (v,), _idx)
363 paramids.append(_id)
364 else:
365 paramids = []
366 for _idx, vv in enumerate(param_values):
367 if len(vv) != nb_params:
368 raise ValueError("Inconsistent lengths for parameter names and values: '%s' and '%s'"
369 "" % (param_names, vv))
370 _id = mini_idvalset(param_names, vv, _idx)
371 paramids.append(_id)
372 return paramids
373  
374  
375 # ---- ParameterSet api ---
376 # def analyze_parameter_set(pmark=None, argnames=None, argvalues=None, ids=None, check_nb=True):
377 # """
378 # analyzes a parameter set passed either as a pmark or as distinct
379 # (argnames, argvalues, ids) to extract/construct the various ids, marks, and
380 # values
381 #
382 # See also pytest.Metafunc.parametrize method, that calls in particular
383 # pytest.ParameterSet._for_parametrize and _pytest.python._idvalset
384 #
385 # :param pmark:
386 # :param argnames:
387 # :param argvalues:
388 # :param ids:
389 # :param check_nb: a bool indicating if we should raise an error if len(argnames) > 1 and any argvalue has
390 # a different length than len(argnames)
391 # :return: ids, marks, values
392 # """
393 # if pmark is not None:
394 # if any(a is not None for a in (argnames, argvalues, ids)):
395 # raise ValueError("Either provide a pmark OR the details")
396 # argnames = pmark.param_names
397 # argvalues = pmark.param_values
398 # ids = pmark.param_ids
399 #
400 # # extract all parameters that have a specific configuration (pytest.param())
401 # custom_pids, p_marks, p_values = extract_parameterset_info(argnames, argvalues, check_nb=check_nb)
402 #
403 # # get the ids by merging/creating the various possibilities
404 # p_ids = make_test_ids(argnames=argnames, argvalues=p_values, global_ids=ids, id_marks=custom_pids)
405 #
406 # return p_ids, p_marks, p_values
407  
408  
409 def extract_parameterset_info(argnames, argvalues, check_nb=True):
410 """
411  
412 :param argnames: the names in this parameterset
413 :param argvalues: the values in this parameterset
414 :param check_nb: a bool indicating if we should raise an error if len(argnames) > 1 and any argvalue has
415 a different length than len(argnames)
416 :return:
417 """
418 pids = []
419 pmarks = []
420 pvalues = []
421 if isinstance(argnames, string_types):
422 raise TypeError("argnames must be an iterable. Found %r" % argnames)
423 nbnames = len(argnames)
424 for v in argvalues:
425 _pid, _pmark, _pvalue = extract_pset_info_single(nbnames, v)
426  
427 pids.append(_pid)
428 pmarks.append(_pmark)
429 pvalues.append(_pvalue)
430  
431 if check_nb and nbnames > 1 and (len(_pvalue) != nbnames):
432 raise ValueError("Inconsistent number of values in pytest parametrize: %s items found while the "
433 "number of parameters is %s: %s." % (len(_pvalue), nbnames, _pvalue))
434  
435 return pids, pmarks, pvalues
436  
437  
438 def extract_pset_info_single(nbnames, argvalue):
439 """Return id, marks, value"""
440 if is_marked_parameter_value(argvalue):
441 # --id
442 _id = get_marked_parameter_id(argvalue)
443 # --marks
444 marks = get_marked_parameter_marks(argvalue)
445 # --value(a tuple if this is a tuple parameter)
446 argvalue = get_marked_parameter_values(argvalue, nbargs=nbnames)
447 return _id, marks, argvalue[0] if nbnames == 1 else argvalue
448 else:
449 # normal argvalue
450 return None, None, argvalue
451  
452  
453 try: # pytest 3.x+
454 from _pytest.mark import ParameterSet # noqa
455  
456 def is_marked_parameter_value(v):
457 return isinstance(v, ParameterSet)
458  
459 def get_marked_parameter_marks(v):
460 return v.marks
461  
462 def get_marked_parameter_values(v, nbargs):
463 """This always returns a tuple. nbargs is useful for pytest2 compatibility """
464 return v.values
465  
466 def get_marked_parameter_id(v):
467 return v.id
468  
469 except ImportError: # pytest 2.x
470 from _pytest.mark import MarkDecorator
471  
472 # noinspection PyPep8Naming
473 def ParameterSet(values,
474 id, # noqa
475 marks):
476 """ Dummy function (not a class) used only by `parametrize` """
477 if id is not None:
478 raise ValueError("This should not happen as `pytest.param` does not exist in pytest 2")
479  
480 # smart unpack is required for compatibility
481 val = values[0] if len(values) == 1 else values
482 nbmarks = len(marks)
483  
484 if nbmarks == 0:
485 return val
486 elif nbmarks > 1:
487 raise ValueError("Multiple marks on parameters not supported for old versions of pytest")
488 else:
489 # decorate with the MarkDecorator
490 return marks[0](val)
491  
492 def is_marked_parameter_value(v):
493 return isinstance(v, MarkDecorator)
494  
495 def get_marked_parameter_marks(v):
496 return [v]
497  
498 def get_marked_parameter_values(v, nbargs):
499 """Returns a tuple containing the values"""
500  
501 # v.args[-1] contains the values.
502 # see MetaFunc.parametrize in pytest 2 to be convinced :)
503  
504 # if v.name in ('skip', 'skipif'):
505 if nbargs == 1:
506 # the last element of args is not a tuple when there is a single arg.
507 return (v.args[-1],)
508 else:
509 return v.args[-1]
510 # else:
511 # raise ValueError("Unsupported mark")
512  
513 def get_marked_parameter_id(v):
514 return v.kwargs.get('id', None)
515  
516  
517 def get_pytest_nodeid(metafunc):
518 try:
519 return metafunc.definition.nodeid
520 except AttributeError:
521 return "unknown"
522  
523  
524 try:
525 # pytest 7+ : scopes is an enum
526 from _pytest.scope import Scope
527  
528 def get_pytest_function_scopeval():
529 return Scope.Function
530  
531 def has_function_scope(fixdef):
532 return fixdef._scope is Scope.Function
533  
534 def set_callspec_arg_scope_to_function(callspec, arg_name):
535 callspec._arg2scope[arg_name] = Scope.Function
536  
537 except ImportError:
538 try:
539 # pytest 3+
540 from _pytest.fixtures import scopes as pt_scopes
541 except ImportError:
542 # pytest 2
543 from _pytest.python import scopes as pt_scopes
544  
545 # def get_pytest_scopenum(scope_str):
546 # return pt_scopes.index(scope_str)
547  
548 def get_pytest_function_scopeval():
549 return pt_scopes.index("function")
550  
551 def has_function_scope(fixdef):
552 return fixdef.scopenum == get_pytest_function_scopeval()
553  
554 def set_callspec_arg_scope_to_function(callspec, arg_name):
555 callspec._arg2scopenum[arg_name] = get_pytest_function_scopeval() # noqa
556  
557  
558 def in_callspec_explicit_args(
  • F821 Undefined name 'CallSpec2'
559 callspec, # type: CallSpec2
560 name # type: str
561 ): # type: (...) -> bool
562 """Return True if name is explicitly used in callspec args"""
563 return (name in callspec.params) or (not PYTEST8_OR_GREATER and name in callspec.funcargs)
564  
565  
566 if PYTEST71_OR_GREATER:
567 from _pytest.python import IdMaker # noqa
568  
569 _idval = IdMaker([], [], None, None, None, None, None)._idval
570 _idval_kwargs = dict()
571 else:
572 from _pytest.python import _idval # noqa
573  
574 if PYTEST6_OR_GREATER:
575 _idval_kwargs = dict(idfn=None,
576 nodeid=None, # item is not used in pytest(>=6.0.0) nodeid is only used by idfn
577 config=None # if a config hook was available it would be used before this is called)
578 )
579 elif PYTEST38_OR_GREATER:
580 _idval_kwargs = dict(idfn=None,
581 item=None, # item is only used by idfn
582 config=None # if a config hook was available it would be used before this is called)
583 )
584 else:
585 _idval_kwargs = dict(idfn=None,
586 # config=None # if a config hook was available it would be used before this is called)
587 )
588  
589  
590 def mini_idval(
591 val, # type: object
592 argname, # type: str
593 idx, # type: int
594 ):
595 """
596 A simplified version of idval where idfn, item and config do not need to be passed.
597  
598 :param val:
599 :param argname:
600 :param idx:
601 :return:
602 """
603 return _idval(val=val, argname=argname, idx=idx, **_idval_kwargs)
604  
605  
606 def mini_idvalset(argnames, argvalues, idx):
607 """ mimic _pytest.python._idvalset but can handle lazyvalues used for tuples or args
608  
609 argvalues should not be a pytest.param (ParameterSet)
610 This function returns a SINGLE id for a single test node
611 """
612 if len(argnames) > 1 and is_lazy(argvalues):
613 # handle the case of LazyTuple used for several args
614 return argvalues.get_id()
615  
616 this_id = [
617 _idval(val, argname, idx=idx, **_idval_kwargs)
618 for val, argname in zip(argvalues, argnames)
619 ]
620 return "-".join(this_id)
621  
622  
623 try:
624 from _pytest.compat import getfuncargnames # noqa
625 except ImportError:
626 def num_mock_patch_args(function):
627 """ return number of arguments used up by mock arguments (if any) """
628 patchings = getattr(function, "patchings", None)
629 if not patchings:
630 return 0
631  
632 mock_sentinel = getattr(sys.modules.get("mock"), "DEFAULT", object())
633 ut_mock_sentinel = getattr(sys.modules.get("unittest.mock"), "DEFAULT", object())
634  
635 return len(
636 [p for p in patchings if not p.attribute_name and (p.new is mock_sentinel or p.new is ut_mock_sentinel)]
637 )
638  
639 # noinspection SpellCheckingInspection
640 def getfuncargnames(function, cls=None):
641 """Returns the names of a function's mandatory arguments."""
642 parameters = signature(function).parameters
643  
644 arg_names = tuple(
645 p.name
646 for p in parameters.values()
647 if (
648 p.kind is Parameter.POSITIONAL_OR_KEYWORD
649 or p.kind is Parameter.KEYWORD_ONLY
650 )
651 and p.default is Parameter.empty
652 )
653  
654 # If this function should be treated as a bound method even though
655 # it's passed as an unbound method or function, remove the first
656 # parameter name.
657 if cls and not isinstance(cls.__dict__.get(function.__name__, None), staticmethod):
658 arg_names = arg_names[1:]
659 # Remove any names that will be replaced with mocks.
660 if hasattr(function, "__wrapped__"):
661 arg_names = arg_names[num_mock_patch_args(function):]
662 return arg_names
663  
664  
665 class FakeSession(object):
666 __slots__ = ('_fixturemanager',)
667  
668 def __init__(self):
669 self._fixturemanager = None
670  
671  
672 class MiniFuncDef(object):
673 __slots__ = ('nodeid', 'session')
674  
675 def __init__(self, nodeid):
676 self.nodeid = nodeid
677 if PYTEST8_OR_GREATER:
678 self.session = FakeSession()
679  
680  
681 class MiniMetafunc(Metafunc):
682 """
683 A class to know what pytest *would* do for a given function in terms of callspec.
684 It is used in function `case_to_argvalues`
685 """
686 # noinspection PyMissingConstructor
687 def __init__(self, func):
688 from .plugin import PYTEST_CONFIG # late import to ensure config has been loaded by now
689  
690 self.config = PYTEST_CONFIG
691  
692 # self.config can be `None` if the same module is reloaded by another thread/process inside a test (parallelism)
693 # In that case, a priori we are outside the pytest main runner so we can silently ignore, this
694 # MetaFunc will not be used/read by anyone.
695 # See https://github.com/smarie/python-pytest-cases/issues/242
696 #
697 # if self.config is None:
698 # if pytest_is_running():
699 # raise ValueError("Internal error - config has not been correctly loaded. Please report")
700  
701 self.function = func
702 self.definition = MiniFuncDef(func.__name__)
703 self._calls = []
704 # non-default parameters
705 self.fixturenames = getfuncargnames(func)
706 # add declared used fixtures with @pytest.mark.usefixtures
707 self.fixturenames_not_in_sig = [f for f in get_pytest_usefixture_marks(func) if f not in self.fixturenames]
708 if self.fixturenames_not_in_sig:
709 self.fixturenames = tuple(self.fixturenames_not_in_sig + list(self.fixturenames))
710  
711 if PYTEST8_OR_GREATER:
712 # dummy
  • F821 Undefined name 'Sequence'
  • F821 Undefined name 'FixtureDef'
713 self._arg2fixturedefs = dict() # type: dict[str, Sequence["FixtureDef[Any]"]]
714  
715 # get parametrization marks
716 self.pmarks = get_pytest_parametrize_marks(self.function)
717 if self.is_parametrized:
718 self.update_callspecs()
719 # preserve order
720 ref_names = self._calls[0].params if PYTEST8_OR_GREATER else self._calls[0].funcargs
721 self.required_fixtures = tuple(f for f in self.fixturenames if f not in ref_names)
722 else:
723 self.required_fixtures = self.fixturenames
724  
725 @property
726 def is_parametrized(self):
727 return len(self.pmarks) > 0
728  
729 @property
730 def requires_fixtures(self):
731 return len(self.required_fixtures) > 0
732  
733 def update_callspecs(self):
734 """
735  
736 :return:
737 """
738 for pmark in self.pmarks:
739 if len(pmark.param_names) == 1:
740 if PYTEST3_OR_GREATER:
741 argvals = tuple(v if is_marked_parameter_value(v) else (v,) for v in pmark.param_values)
742 else:
743 argvals = []
744 for v in pmark.param_values:
745 if is_marked_parameter_value(v):
746 newmark = MarkDecorator(v.markname, v.args[:-1] + ((v.args[-1],),), v.kwargs)
747 argvals.append(newmark)
748 else:
749 argvals.append((v,))
750 argvals = tuple(argvals)
751 else:
752 argvals = pmark.param_values
753 self.parametrize(argnames=pmark.param_names, argvalues=argvals, ids=pmark.param_ids,
754 # use indirect = False and scope = 'function' to avoid having to implement complex patches
755 indirect=False, scope='function')
756  
757 if not PYTEST33_OR_GREATER:
758 # fix the CallSpec2 instances so that the marks appear in an attribute "mark"
759 # noinspection PyProtectedMember
760 for c in self._calls:
761 c.marks = list(c.keywords.values())
762  
763  
764 def add_fixture_params(func, new_names):
765 """Creates a wrapper of the given function with additional arguments"""
766  
767 old_sig = signature(func)
768  
769 # prepend all new parameters if needed
770 for n in new_names:
771 if n in old_sig.parameters:
772 raise ValueError("argument named %s already present in signature" % n)
773 new_sig = add_signature_parameters(old_sig,
774 first=[Parameter(n, kind=Parameter.POSITIONAL_OR_KEYWORD) for n in new_names])
775  
  • S101 Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
776 assert not isgeneratorfunction(func)
777  
778 # normal function with return statement
779 @wraps(func, new_sig=new_sig)
780 def wrapped_func(**kwargs):
781 for n in new_names:
782 kwargs.pop(n)
783 return func(**kwargs)
784  
785 # else:
786 # # generator function (with a yield statement)
787 # @wraps(fixture_func, new_sig=new_sig)
788 # def wrapped_fixture_func(*args, **kwargs):
789 # request = kwargs['request'] if func_needs_request else kwargs.pop('request')
790 # if is_used_request(request):
791 # for res in fixture_func(*args, **kwargs):
792 # yield res
793 # else:
794 # yield NOT_USED
795  
796 return wrapped_func
797  
798  
799 def get_callspecs(func):
800 """
801 Returns a list of pytest CallSpec objects corresponding to calls that should be made for this parametrized function.
802 This mini-helper assumes no complex things (scope='function', indirect=False, no fixtures, no custom configuration)
803  
804 Note that this function is currently only used in tests.
805 """
806 meta = MiniMetafunc(func)
807 # meta.update_callspecs()
808 # noinspection PyProtectedMember
809 return meta._calls
810  
811  
812 def cart_product_pytest(argnames, argvalues):
813 """
814 - do NOT use `itertools.product` as it fails to handle MarkDecorators
815 - we also unpack tuples associated with several argnames ("a,b") if needed
816 - we also propagate marks
817  
818 :param argnames:
819 :param argvalues:
820 :return:
821 """
822 # transform argnames into a list of lists
823 argnames_lists = [get_param_argnames_as_list(_argnames) if len(_argnames) > 0 else [] for _argnames in argnames]
824  
825 # make the cartesian product per se
826 argvalues_prod = _cart_product_pytest(argnames_lists, argvalues)
827  
828 # flatten the list of argnames
829 argnames_list = [n for nlist in argnames_lists for n in nlist]
830  
831 # apply all marks to the arvalues
832 argvalues_prod = [make_marked_parameter_value(tuple(argvalues), marks=marks) if len(marks) > 0 else tuple(argvalues)
833 for marks, argvalues in argvalues_prod]
834  
835 return argnames_list, argvalues_prod
836  
837  
838 def _cart_product_pytest(argnames_lists, argvalues):
839 result = []
840  
841 # first perform the sub cartesian product with entries [1:]
842 sub_product = _cart_product_pytest(argnames_lists[1:], argvalues[1:]) if len(argvalues) > 1 else None
843  
844 # then do the final product with entry [0]
845 for x in argvalues[0]:
846 # handle x
847 nb_names = len(argnames_lists[0])
848  
849 # (1) extract meta-info
850 x_id, x_marks, x_value = extract_pset_info_single(nb_names, x)
851 x_marks_lst = list(x_marks) if x_marks is not None else []
852 if x_id is not None:
853 raise ValueError("It is not possible to specify a sub-param id when using the new parametrization style. "
854 "Either use the traditional style or customize all ids at once in `idgen`")
855  
856 # (2) possibly unpack
857 if nb_names > 1:
858 # if lazy value, we have to do something
859 if is_lazy_value(x_value):
860 x_value_lst = x_value.as_lazy_items_list(nb_names)
861 else:
862 x_value_lst = list(x_value)
863 else:
864 x_value_lst = [x_value]
865  
866 # product
867 if len(argvalues) > 1:
868 for m, p in sub_product:
869 # combine marks and values
870 result.append((x_marks_lst + m, x_value_lst + p))
871 else:
872 result.append((x_marks_lst, x_value_lst))
873  
874 return result
875  
876  
877 def inject_host(apply_decorator):
878 """
879 A decorator for function with signature `apply_decorator(f, host)`, in order to inject 'host', the host of f.
880  
881 Since it is not entirely feasible to detect the host in python, my first implementation was a bit complex: it was
882 returning an object with custom implementation of __call__ and __get__ methods, both reacting when pytest collection
883 happens.
884  
885 That was very complex. Now we rely on an approximate but good enough alternative with `get_function_host`
886  
887 :param apply_decorator:
888 :return:
889 """
890 # class _apply_decorator_with_host_tracking(object):
891 # def __init__(self, _target):
892 # # This is called when the decorator is applied on the target. Remember the target and result of paramz
893 # self._target = _target
894 # self.__wrapped__ = None
895 #
896 # def __get__(self, obj, type_=None):
897 # """
898 # When the decorated test function or fixture sits in a cl
899 # :param obj:
900 # :param type_:
901 # :return:
902 # """
903 # # We now know that the parametrized function/fixture self._target sits in obj (a class or a module)
904 # # We can therefore apply our parametrization accordingly (we need a reference to this host container in
905 # # order to store fixtures there)
906 # if self.__wrapped__ is None:
907 # self.__wrapped__ = 1 # means 'pending', to protect against infinite recursion
908 # try:
909 # self.__wrapped__ = apply_decorator(self._target, obj)
910 # except Exception as e:
911 # traceback = sys.exc_info()[2]
912 # reraise(BaseException, e.args, traceback)
913 #
914 # # path, lineno = get_fslocation_from_item(self)
915 # # warn_explicit(
916 # # "Error parametrizing function %s : [%s] %s" % (self._target, e.__class__, e),
917 # # category=None,
918 # # filename=str(path),
919 # # lineno=lineno + 1 if lineno is not None else None,
920 # # )
921 # #
922 # # @wraps(self._target)
923 # # def _exc_raiser(*args, **kwargs):
924 # # raise e
925 # # # remove this metadata otherwise pytest will unpack it
926 # # del _exc_raiser.__wrapped__
927 # # self.__wrapped__ = _exc_raiser
928 #
929 # return self.__wrapped__
930 #
931 # def __getattribute__(self, item):
932 # if item == '__call__':
933 # # direct call means that the parametrized function sits in a module. import it
934 # host_module = import_module(self._target.__module__)
935 #
936 # # next time the __call__ attribute will be set so callable() will work
937 # self.__call__ = self.__get__(host_module)
938 # return self.__call__
939 # else:
940 # return object.__getattribute__(self, item)
941 #
942 # return _apply_decorator_with_host_tracking
943  
944 def apply(test_or_fixture_func):
945 # approximate: always returns the module and not the class :(
946 #
947 # indeed when this is called, the function exists (and its qualname mentions the host class) but the
948 # host class is not yet created in the module, so it is not found by our `get_class_that_defined_method`
949 #
950 # but still ... this is far less complex to debug than the above attempt and it does not yet have side effects..
951 container = get_function_host(test_or_fixture_func)
952 return apply_decorator(test_or_fixture_func, container)
953  
954 return apply
955  
956  
957 def get_pytest_request_and_item(request_or_item):
958 """Return the `request` and `item` (node) from whatever is provided"""
959 try:
960 item = request_or_item.node
961 except AttributeError:
962 item = request_or_item
963 request = item._request
964 else:
965 request = request_or_item
966  
967 return item, request