Coverage for src/pytest_cases/case_parametrizer_new.py: 89%

455 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-04-04 21:17 +0000

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# Use true division operator always even in old python 2.x (used in `_extract_cases_from_module`) 

6from __future__ import division 

7 

8from collections import namedtuple 

9 

10import functools 

11from importlib import import_module 

12from inspect import getmembers, ismodule 

13import re 

14from warnings import warn 

15 

16try: # python 3.3+ 

17 from inspect import signature 

18except ImportError: 

19 from funcsigs import signature # noqa 

20 

21try: 

22 from typing import Union, Callable, Iterable, Any, Type, List, Tuple # noqa 

23except ImportError: 

24 pass 

25 

26from .common_mini_six import string_types 

27from .common_others import get_code_first_line, AUTO, qname, funcopy, needs_binding, get_function_host, \ 

28 in_same_module, get_host_module, get_class_that_defined_method 

29from .common_pytest_marks import copy_pytest_marks, make_marked_parameter_value, remove_pytest_mark, filter_marks, \ 

30 get_param_argnames_as_list, Mark 

31from .common_pytest_lazy_values import LazyValue, LazyTuple, LazyTupleItem 

32from .common_pytest import safe_isclass, MiniMetafunc, is_fixture, get_fixture_name, inject_host, add_fixture_params, \ 

33 list_all_fixtures_in, get_pytest_request_and_item, safe_isinstance 

34 

35from .case_funcs import matches_tag_query, is_case_function, is_case_class, CASE_PREFIX_FUN, copy_case_info, \ 

36 get_case_id, get_case_marks, GEN_BY_US 

37 

38from .fixture_core1_unions import USED, NOT_USED 

39from .fixture_core2 import CombinedFixtureParamValue, fixture 

40from .fixture__creation import check_name_available, get_caller_module, CHANGE 

41from .fixture_parametrize_plus import fixture_ref, _parametrize_plus, FixtureParamAlternative, ParamAlternative, \ 

42 SingleParamAlternative, MultiParamAlternative, FixtureRefItem 

43 

44try: 

45 ModuleNotFoundError 

46except NameError: 

47 # python < 3.6 

48 ModuleNotFoundError = ImportError 

49 

50 

51THIS_MODULE = object() 

52"""Singleton that can be used instead of a module name to indicate that the module is the current one""" 

53 

54try: 

55 from typing import Literal, Optional # noqa 

56 from types import ModuleType # noqa 

57 

58 ModuleRef = Union[str, ModuleType, Literal[AUTO], Literal[THIS_MODULE]] # noqa 

59 CaseType = Union[Callable, Type, ModuleRef] 

60 

61except: # noqa 

62 pass 

63 

64 

65_HOST_CLS_ATTR = '_pytestcases_host_cls' 

66 

67 

68def parametrize_with_cases(argnames, # type: Union[str, List[str], Tuple[str, ...]] 

69 cases=AUTO, # type: Union[CaseType, List[CaseType]] 

70 prefix=CASE_PREFIX_FUN, # type: str 

71 glob=None, # type: str 

72 has_tag=None, # type: Any 

73 filter=None, # type: Callable[..., bool] # noqa 

74 ids=None, # type: Union[Callable, Iterable[str]] 

75 idstyle=None, # type: Union[str, Callable] 

76 # idgen=_IDGEN, # type: Union[str, Callable] 

77 debug=False, # type: bool 

78 scope="function", # type: str 

79 import_fixtures=False # type: bool 

80 ): 

81 # type: (...) -> Callable[[Callable], Callable] 

82 """ 

83 A decorator for test functions or fixtures, to parametrize them based on test cases. It works similarly to 

84 `@pytest.mark.parametrize`: argnames represent a coma-separated string of arguments to inject in the decorated 

85 test function or fixture. The argument values (argvalues in `pytest.mark.parametrize`) are collected from the 

86 various case functions found according to `cases`, and injected as lazy values so that the case functions are called 

87 just before the test or fixture is executed. 

88 

89 By default (`cases=AUTO`) the list of test cases is automatically drawn from the python module file named 

90 `test_<name>_cases.py` or if not found, `cases_<name>.py`, where `test_<name>` is the current module name. 

91 

92 Finally, the `cases` argument also accepts an explicit case function, cases-containing class, module or module name; 

93 or a list of such elements. Note that both absolute and relative module names are supported. 

94 

95 Note that `@parametrize_with_cases` collection and parameter creation steps are strictly equivalent to 

96 `get_all_cases` + `get_parametrize_args`. This can be handy for debugging purposes. 

97 

98 ```python 

99 # Collect all cases 

100 cases_funs = get_all_cases(f, cases=cases, prefix=prefix, glob=glob, has_tag=has_tag, filter=filter) 

101 

102 # Transform the various functions found 

103 argvalues = get_parametrize_args(host_class_or_module, cases_funs, debug=False) 

104 ``` 

105 

106 :param argnames: same than in @pytest.mark.parametrize 

107 :param cases: a case function, a class containing cases, a module object or a module name string (relative module 

108 names accepted). Or a list of such items. You may use `THIS_MODULE` or `'.'` to include current module. 

109 `AUTO` (default) means that the module named `test_<name>_cases.py` or if not found, `cases_<name>.py`, will be 

110 loaded, where `test_<name>.py` is the module file of the decorated function. When a module is listed, all of 

111 its functions matching the `prefix`, `filter` and `has_tag` are selected, including those functions nested in 

112 classes following naming pattern `*Case*`. Nested subclasses are taken into account, as long as they follow the 

113 `*Case*` naming pattern. When classes are explicitly provided in the list, they can have any name and do not 

114 need to follow this `*Case*` pattern. 

115 :param prefix: the prefix for case functions. Default is 'case_' but you might wish to use different prefixes to 

116 denote different kind of cases, for example 'data_', 'algo_', 'user_', etc. 

117 :param glob: an optional glob-like pattern for case ids, for example "*_success" or "*_failure". Note that this 

118 is applied on the case id, and therefore if it is customized through `@case(id=...)` it should be taken into 

119 account. 

120 :param has_tag: a single tag or a tuple, set, list of tags that should be matched by the ones set with the `@case` 

121 decorator on the case function(s) to be selected. 

122 :param filter: a callable receiving the case function and returning `True` or a truth value in case the function 

123 needs to be selected. 

124 :param ids: optional custom ids, similar to the one in `pytest.mark.parametrize`. Users may either provide an 

125 iterable of string ids, or a callable. If a callable is provided it will receive the case functions. Users 

126 may wish to use `get_case_id` or other functions in the API to inspect the case functions. 

127 :param idstyle: This is mostly for debug. Style of ids to be used in the "union" fixtures generated by 

128 `@parametrize` if some cases are transformed into fixtures behind the scenes. `idstyle` possible values are 

129 'compact', 'explicit' or None/'nostyle' (default), or a callable. `idstyle` has no effect if no cases are 

130 transformed into fixtures. As opposed to `ids`, a callable provided here will receive a `ParamAlternative` 

131 object indicating which generated fixture should be used. See `@parametrize` for details. 

132 :param scope: the scope of the union fixture to create if `fixture_ref`s are found in the argvalues 

133 :param import_fixtures: experimental feature. Turn this to True in order to automatically import all fixtures 

134 defined in the cases module into the current module. 

135 :param debug: a boolean flag to debug what happens behind the scenes 

136 :return: 

137 """ 

138 @inject_host 

139 def _apply_parametrization(f, host_class_or_module): 

140 """ execute parametrization of test function or fixture `f` """ 

141 

142 # Collect all cases 

143 cases_funs = get_all_cases(f, cases=cases, prefix=prefix, glob=glob, has_tag=has_tag, filter=filter) 

144 

145 # Build ids from callable if provided. 

146 _ids = ids 

147 if ids is not None: 

148 try: 

149 # if this is an iterable, don't do anything 

150 iter(ids) 

151 except TypeError: 

152 # id this is a callable however, use the callable on the case function (not fixture_ref and lazy_values) 

153 _ids = tuple(ids(_get_original_case_func(c)[0]) for c in cases_funs) 

154 

155 # Transform the various case functions found into `lazy_value` (for case functions not requiring fixtures) 

156 # or `fixture_ref` (for case functions requiring fixtures - for them we create associated case fixtures in 

157 # `host_class_or_module`) 

158 argvalues = get_parametrize_args(host_class_or_module, cases_funs, prefix=prefix, 

159 import_fixtures=import_fixtures, debug=debug, scope=scope) 

160 

161 # Finally apply parametrization - note that we need to call the private method so that fixture are created in 

162 # the right module (not here) 

163 _parametrize_with_cases, needs_inject = _parametrize_plus(argnames, argvalues, ids=_ids, idstyle=idstyle, 

164 debug=debug, scope=scope) 

165 

166 if needs_inject: 

167 return _parametrize_with_cases(f, host_class_or_module) 

168 else: 

169 return _parametrize_with_cases(f) 

170 

171 return _apply_parametrization 

172 

173 

174def _get_original_case_func(case_fun # type: Callable 

175 ): 

176 """ 

177 

178 :param case_fun: 

179 :return: the original case function, and a boolean indicating if it is different from the input 

180 """ 

181 case_in_class = hasattr(case_fun, _HOST_CLS_ATTR) 

182 true_case_func = case_fun.func if case_in_class else case_fun 

183 return true_case_func, case_in_class 

184 

185 

186def create_glob_name_filter(glob_str # type: str 

187 ): 

188 """ 

189 Creates a glob-like matcher for the name of case functions 

190 The only special character that is supported is `*` and it can not be 

191 escaped. However it can be used multiple times in an expression. 

192 

193 :param glob_str: for example `*_success` or `*_*` 

194 :return: 

195 """ 

196 # escape all special regex characters, then find the (escaped) stars and turn them into the regex star .* 

197 re_str = re.escape(glob_str).replace("\\*", ".*") 

198 # add "end" special regex char 

199 name_matcher = re.compile(re_str + "$") 

200 

201 def _glob_name_filter(case_fun): 

202 case_fun_id = get_case_id(case_fun) 

203 assert case_fun_id is not None 

204 return name_matcher.match(case_fun_id) 

205 

206 return _glob_name_filter 

207 

208 

209def get_all_cases(parametrization_target=None, # type: Callable 

210 cases=AUTO, # type: Union[CaseType, List[CaseType]] 

211 prefix=CASE_PREFIX_FUN, # type: str 

212 glob=None, # type: str 

213 has_tag=None, # type: Union[str, Iterable[str]] 

214 filter=None # type: Callable[[Callable], bool] # noqa 

215 ): 

216 # type: (...) -> List[Callable] 

217 """ 

218 Lists all desired cases for a given `parametrization_target` (a test function or a fixture). This function may be 

219 convenient for debugging purposes. See `@parametrize_with_cases` for details on the parameters. 

220 

221 :param parametrization_target: either an explicit module object or a function or None. If it's a function, it will 

222 use the module it is defined in. If None is given, it will just get the module it was called from. 

223 :param cases: a case function, a class containing cases, a module or a module name string (relative module 

224 names accepted). Or a list of such items. You may use `THIS_MODULE` or `'.'` to include current module. 

225 `AUTO` (default) means that the module named `test_<name>_cases.py` will be loaded, where `test_<name>.py` is 

226 the module file of the decorated function. `AUTO2` allows you to use the alternative naming scheme 

227 `cases_<name>.py`. When a module is listed, all of its functions matching the `prefix`, `filter` and `has_tag` 

228 are selected, including those functions nested in classes following naming pattern `*Case*`. When classes are 

229 explicitly provided in the list, they can have any name and do not need to follow this `*Case*` pattern. 

230 :param prefix: the prefix for case functions. Default is 'case_' but you might wish to use different prefixes to 

231 denote different kind of cases, for example 'data_', 'algo_', 'user_', etc. 

232 :param glob: a matching pattern for case ids, for example `*_success` or `*_failure`. The only special character 

233 that can be used for now in this pattern is `*`, it can not be escaped, and it can be used several times in the 

234 same expression. The pattern should match the entire case id for the case to be selected. Note that this is 

235 applied on the case id, and therefore if it is customized through `@case(id=...)` it will be taken into 

236 account. 

237 :param has_tag: a single tag or a tuple, set, list of tags that should be matched by the ones set with the `@case` 

238 decorator on the case function(s) to be selected. 

239 :param filter: a callable receiving the case function and returning True or a truth value in case the function 

240 needs to be selected. 

241 """ 

242 # Handle single elements 

243 if isinstance(cases, string_types): 

244 cases = (cases,) 

245 else: 

246 try: 

247 cases = tuple(cases) 

248 except TypeError: 

249 cases = (cases,) 

250 

251 # validate prefix 

252 if not isinstance(prefix, str): 252 ↛ 253line 252 didn't jump to line 253, because the condition on line 252 was never true

253 raise TypeError("`prefix` should be a string, found: %r" % prefix) 

254 

255 # validate glob and filter and merge them in a single tuple of callables 

256 filters = () 

257 if glob is not None: 

258 if not isinstance(glob, string_types): 258 ↛ 259line 258 didn't jump to line 259, because the condition on line 258 was never true

259 raise TypeError("`glob` should be a string containing a glob-like pattern (not a regex).") 

260 

261 filters += (create_glob_name_filter(glob),) 

262 if filter is not None: 

263 if not callable(filter): 263 ↛ 264line 263 didn't jump to line 264, because the condition on line 263 was never true

264 raise TypeError( 

265 "`filter` should be a callable starting in pytest-cases 0.8.0. If you wish to provide a single" 

266 " tag to match, use `has_tag` instead.") 

267 

268 filters += (filter,) 

269 

270 # parent package 

271 if parametrization_target is None: 

272 parametrization_target = get_caller_module() 

273 

274 if ismodule(parametrization_target): 

275 caller_module_name = parametrization_target.__name__ 

276 elif callable(parametrization_target): 276 ↛ 279line 276 didn't jump to line 279, because the condition on line 276 was never false

277 caller_module_name = getattr(parametrization_target, '__module__', None) 

278 else: 

279 raise ValueError("Can't handle parametrization_target=%s" % parametrization_target) 

280 

281 parent_pkg_name = '.'.join(caller_module_name.split('.')[:-1]) if caller_module_name is not None else None 

282 

283 # start collecting all cases 

284 cases_funs = [] 

285 for c in cases: 

286 # load case or cases depending on type 

287 if safe_isclass(c): 

288 # class - do not check name, it was explicitly passed 

289 new_cases = extract_cases_from_class(c, case_fun_prefix=prefix, check_name=False) 

290 cases_funs += new_cases 

291 elif callable(c): 

292 # function 

293 if is_case_function(c, check_prefix=False): # do not check prefix, it was explicitly passed 293 ↛ 298line 293 didn't jump to line 298, because the condition on line 293 was never false

294 # bind it automatically if needed (if unbound class method) 

295 shall_bind, bound_c = needs_binding(c, return_bound=True) 

296 cases_funs.append(bound_c) 

297 else: 

298 raise ValueError("Unsupported case function: %r" % c) 

299 else: 

300 # module 

301 if c is AUTO: 

302 # Make sure we're in a test_<xxx>.py-like module. 

303 # We cannot accept AUTO cases in, e.g., conftest.py 

304 # as we don't know what to look for. We complain here 

305 # rather than raising AssertionError in the call to 

306 # import_default_cases_module. See #309. 

307 if not caller_module_name.split('.')[-1].startswith('test_'): 

308 raise ValueError( 

309 'Cannot use `cases=AUTO` in file "%s". `cases=AUTO` is ' 

310 'only allowed in files whose name starts with "test_" ' 

311 % caller_module_name 

312 ) 

313 # First try `test_<name>_cases.py` Then `cases_<name>.py` 

314 c = import_default_cases_module(caller_module_name) 

315 

316 elif c is THIS_MODULE or c == '.': 

317 c = caller_module_name 

318 

319 new_cases = extract_cases_from_module(c, package_name=parent_pkg_name, case_fun_prefix=prefix) 

320 cases_funs += new_cases 

321 

322 # filter last, for easier debugging (collection will be slightly less performant when a large volume of cases exist) 

323 return [c for c in cases_funs 

324 if matches_tag_query(c, has_tag=has_tag, filter=filters)] 

325 

326 

327def get_parametrize_args(host_class_or_module, # type: Union[Type, ModuleType] 

328 cases_funs, # type: List[Callable] 

329 prefix, # type: str 

330 scope="function", # type: str 

331 import_fixtures=False, # type: bool 

332 debug=False # type: bool 

333 ): 

334 # type: (...) -> List[CaseParamValue] 

335 """ 

336 Transforms a list of cases (obtained from `get_all_cases`) into a list of argvalues for `@parametrize`. 

337 Each case function `case_fun` is transformed into one or several `lazy_value`(s) or a `fixture_ref`: 

338 

339 - If `case_fun` requires at least on fixture, a fixture will be created if not yet present, and a `fixture_ref` 

340 will be returned. The fixture will be created in `host_class_or_module` 

341 - If `case_fun` is a parametrized case, one `lazy_value` with a partialized version will be created for each 

342 parameter combination. 

343 - Otherwise, `case_fun` represents a single case: in that case a single `lazy_value` is returned. 

344 

345 :param host_class_or_module: host of the parametrization target. A class or a module. 

346 :param cases_funs: a list of case functions, returned typically by `get_all_cases` 

347 :param prefix: 

348 :param scope: 

349 :param import_fixtures: experimental feature. Turn this to True in order to automatically import all fixtures 

350 defined in the cases module into the current module. 

351 :param debug: a boolean flag, turn it to True to print debug messages. 

352 :return: 

353 """ 

354 return [c for _f in cases_funs for c in case_to_argvalues(host_class_or_module, _f, prefix, scope, import_fixtures, 

355 debug)] 

356 

357 

358class CaseParamValue(object): 

359 """Common class for lazy values and fixture refs created from cases""" 

360 __slots__ = () 

361 

362 def get_case_id(self): 

363 raise NotImplementedError() 

364 

365 def get_case_function(self, request): 

366 raise NotImplementedError() 

367 

368 

369class _LazyValueCaseParamValue(LazyValue, CaseParamValue): 

370 """A case that does not require any fixture is transformed into a `lazy_value` parameter 

371 when passed to @parametrize. 

372 

373 We subclass it so that we can easily find back all parameter values that are cases 

374 """ 

375 

376 def get_case_id(self): 

377 return super(_LazyValueCaseParamValue, self).get_id() 

378 

379 def get_case_function(self, request): 

380 return _get_original_case_func(self.valuegetter)[0] 

381 

382 def as_lazy_tuple(self, nb_params): 

383 return _LazyTupleCaseParamValue(self, nb_params) 

384 

385 

386class _LazyTupleCaseParamValue(LazyTuple, CaseParamValue): 

387 """A case representing a tuple""" 

388 

389 def get_case_id(self): 

390 return super(_LazyTupleCaseParamValue, self).get_id() 

391 

392 def get_case_function(self, request): 

393 return _get_original_case_func(self._lazyvalue.valuegetter)[0] 

394 

395 

396class _FixtureRefCaseParamValue(fixture_ref, CaseParamValue): 

397 """A case that requires at least a fixture is transformed into a `fixture_ref` parameter 

398 when passed to @parametrize""" 

399 

400 def get_case_id(self): 

401 return self.get_name_for_id() 

402 

403 def get_case_function(self, request): 

404 # get the case function copy, or copy of the partial 

405 f = request._arg2fixturedefs[self.fixture][0].func 

406 

407 # extract the actual original case 

408 return f.__origcasefun__ 

409 

410 

411def case_to_argvalues(host_class_or_module, # type: Union[Type, ModuleType] 

412 case_fun, # type: Callable 

413 prefix, # type: str 

414 scope, # type: str 

415 import_fixtures=False, # type: bool 

416 debug=False # type: bool 

417 ): 

418 # type: (...) -> Tuple[CaseParamValue, ...] 

419 """Transform a single case into one or several `lazy_value`(s) or a `fixture_ref` to be used in `@parametrize` 

420 

421 If `case_fun` requires at least on fixture, a fixture will be created if not yet present, and a `fixture_ref` will 

422 be returned. 

423 

424 If `case_fun` is a parametrized case, one `lazy_value` with a partialized version will be created for each parameter 

425 combination. 

426 

427 Otherwise, `case_fun` represents a single case: in that case a single `lazy_value` is returned. 

428 

429 :param case_fun: 

430 :param import_fixtures: experimental feature. Turn this to True in order to automatically import all fixtures 

431 defined in the cases module into the current module. 

432 :return: 

433 """ 

434 # get the id from the case function either added by the @case decorator, or default one. 

435 case_id = get_case_id(case_fun, prefix_for_default_ids=prefix) 

436 

437 # get the list of all calls that pytest *would* have made for such a (possibly parametrized) function 

438 meta = MiniMetafunc(case_fun) 

439 

440 if not meta.requires_fixtures and not meta.is_parametrized: 

441 # only retrieve the extra marks added with @case, since the others will be automatically retrieved by the 

442 # lazy_value. 

443 case_marks = get_case_marks(case_fun, as_decorators=True) 

444 

445 # if not meta.is_parametrized: 

446 # single unparametrized case function 

447 if debug: 

448 case_fun_str = qname(case_fun.func if isinstance(case_fun, functools.partial) else case_fun) 

449 print("Case function %s > 1 lazy_value() with id %s and additional marks %s" 

450 % (case_fun_str, case_id, case_marks)) 

451 return (_LazyValueCaseParamValue(case_fun, id=case_id, marks=case_marks),) 

452 # else: 

453 # THIS WAS A PREMATURE OPTIMIZATION WITH MANY SHORTCOMINGS. For example what if the case function is 

454 # itself parametrized with lazy values ? Let's consider that a parametrized case should be a fixture, 

455 # for now 

456 # 

457 # # parametrized. create one version of the callable for each parametrized call 

458 # # do not forget to merge the marks ! 

459 # if debug: 

460 # case_fun_str = qname(case_fun.func if isinstance(case_fun, functools.partial) else case_fun) 

461 # print("Case function %s > tuple of lazy_value() with ids %s and additional marks %s" 

462 # % (case_fun_str, ["%s-%s" % (case_id, c.id) for c in meta._calls], 

463 # [case_marks + tuple(c.marks) for c in meta._calls])) 

464 # return tuple(lazy_value(functools.partial(case_fun, **c.funcargs), 

465 # id="%s-%s" % (case_id, c.id), marks=case_marks + tuple(c.marks)) 

466 # for c in meta._calls) 

467 else: 

468 # at least 1 required fixture (direct req or through @pytest.mark.usefixtures ), OR parametrized. 

469 

470 # if meta.is_parametrized: 

471 # # nothing to do, the parametrization marks are on the fixture to create so they will be taken into account 

472 

473 # create or reuse a fixture in the host (pytest collector: module or class) of the parametrization target 

474 fix_name, remaining_marks = get_or_create_case_fixture(case_id, case_fun, host_class_or_module, 

475 meta.fixturenames_not_in_sig, scope, 

476 import_fixtures=import_fixtures, debug=debug) 

477 

478 # reference that case fixture, and preserve the case id in the associated id whatever the generated fixture name 

479 argvalues = _FixtureRefCaseParamValue(fix_name, id=case_id) 

480 if debug: 

481 case_fun_str = qname(case_fun.func if isinstance(case_fun, functools.partial) else case_fun) 

482 print("Case function %s > fixture_ref(%r) with marks %s" % (case_fun_str, fix_name, remaining_marks)) 

483 # return a length-1 tuple because there is a single case created 

484 return (make_marked_parameter_value((argvalues,), marks=remaining_marks) if remaining_marks else argvalues,) 

485 

486 

487def get_or_create_case_fixture(case_id, # type: str 

488 case_fun, # type: Callable 

489 target_host, # type: Union[Type, ModuleType] 

490 add_required_fixtures, # type: Iterable[str] 

491 scope, # type: str 

492 import_fixtures=False, # type: bool 

493 debug=False # type: bool 

494 ): 

495 # type: (...) -> Tuple[str, Tuple[Mark]] 

496 """ 

497 When case functions require fixtures, we want to rely on pytest to inject everything. Therefore 

498 we create a "case fixture" wrapping the case function. Since a case function may not be located in the same place 

499 than the symbol decorated with @parametrize_with_cases, we create that "case fixture" in the 

500 appropriate module/class (the host of the test/fixture function, `target_host`). 

501 

502 If the case is parametrized, the parametrization marks are put on the created fixture. 

503 

504 If the case has other marks, they are returned as the 

505 

506 Note that we create a small cache in the module/class in order to reuse the created fixture corresponding 

507 to a case function if it was already required by a test/fixture in this host. 

508 

509 :param case_id: 

510 :param case_fun: 

511 :param target_host: 

512 :param add_required_fixtures: 

513 :param import_fixtures: experimental feature. Turn this to True in order to automatically import all fixtures 

514 defined in the cases module into the current module. 

515 :param debug: 

516 :return: the newly created fixture name, and the remaining marks not applied 

517 """ 

518 if is_fixture(case_fun): 518 ↛ 519line 518 didn't jump to line 519, because the condition on line 518 was never true

519 raise ValueError("A case function can not be decorated as a `@fixture`. This seems to be the case for" 

520 " %s. If you did not decorate it but still see this error, please report this issue" 

521 % case_fun) 

522 

523 # source: detect a functools.partial wrapper created by us because of a host class 

524 true_case_func, case_in_class = _get_original_case_func(case_fun) 

525 true_case_func_host = get_function_host(true_case_func) 

526 

527 # for checks 

528 orig_name = true_case_func.__name__ 

529 orig_case = true_case_func 

530 

531 # destination 

532 target_in_class = safe_isclass(target_host) 

533 fix_cases_dct, imported_fixtures_list = _get_fixture_cases(target_host) # get our "storage unit" in this module 

534 

535 # shortcut if the case fixture is already known/registered in target host 

536 try: 

537 fix_name, marks = fix_cases_dct[(true_case_func, scope)] 

538 if debug: 

539 print("Case function %s > Reusing fixture %r and marks %s" % (qname(true_case_func), fix_name, marks)) 

540 return fix_name, marks 

541 except KeyError: 

542 pass 

543 

544 # not yet known there. Create a new symbol in the target host : 

545 # we need a "free" fixture name, and a "free" symbol name 

546 existing_fixture_names = [] 

547 # -- fixtures in target module or class should not be overridden 

548 existing_fixture_names += list_all_fixtures_in(target_host, recurse_to_module=False) 

549 # -- are there fixtures in source module or class ? should not be overridden too 

550 if not in_same_module(target_host, true_case_func_host): 

551 fixtures_in_cases_module = list_all_fixtures_in(true_case_func_host, recurse_to_module=False) 

552 if len(fixtures_in_cases_module) > 0: 

553 # EXPERIMENTAL we can try to import the fixtures into current module 

554 if import_fixtures: 

555 from_module = get_host_module(true_case_func_host) 

556 if from_module not in imported_fixtures_list: 

557 for f in list_all_fixtures_in(true_case_func_host, recurse_to_module=False, return_names=False): 

558 f_name = get_fixture_name(f) 

559 if (f_name in existing_fixture_names) or (f.__name__ in existing_fixture_names): 559 ↛ 560line 559 didn't jump to line 560, because the condition on line 559 was never true

560 raise ValueError("Cannot import fixture %r from %r as it would override an existing symbol " 

561 "in %r. Please set `@parametrize_with_cases(import_fixtures=False)`" 

562 "" % (f, from_module, target_host)) 

563 target_host_module = target_host if not target_in_class else get_host_module(target_host) 

564 setattr(target_host_module, f.__name__, f) 

565 

566 imported_fixtures_list.append(from_module) 

567 

568 # Fix the problem with "case_foo(foo)" leading to the generated fixture having the same name 

569 existing_fixture_names += fixtures_in_cases_module 

570 

571 # If the fixture will be injected in a conftest, make sure its name 

572 # is unique. Include also its scope to avoid conflicts. See #311. 

573 # Notice target_host.__name__ may just be 'conftest' when tests 

574 # are simple modules or a more complicated fully qualified name 

575 # when the test suite is a package (i.e., with __init__.py). For 

576 # example, target_host.__name__ would be 'tests.conftest' when 

577 # executing tests from within 'base' in the following tree: 

578 # base/ 

579 # tests/ 

580 # __init__.py 

581 # conftest.py 

582 if 'conftest' in target_host.__name__: 

583 extra = target_host.__name__.replace('.', '_') 

584 case_id = extra + '_' + case_id + '_with_scope_' + scope 

585 

586 def name_changer(name, i): 

587 return name + '_' * i 

588 

589 # start with name = case_id and find a name that does not exist 

590 fix_name = check_name_available(target_host, extra_forbidden_names=existing_fixture_names, name=case_id, 

591 if_name_exists=CHANGE, name_changer=name_changer) 

592 

593 if debug: 

594 print("Case function %s > Creating fixture %r in %s" % (qname(true_case_func), fix_name, target_host)) 

595 

596 if case_in_class: 

597 if target_in_class: 597 ↛ 599line 597 didn't jump to line 599, because the condition on line 597 was never true

598 # both in class: direct copy of the non-partialized version 

599 case_fun = funcopy(true_case_func) 

600 else: 

601 # case in class and target in module: use the already existing partialized version 

602 case_fun = funcopy(case_fun) 

603 else: 

604 if target_in_class: 604 ↛ 606line 604 didn't jump to line 606, because the condition on line 604 was never true

605 # case in module and target in class: create a static method 

606 case_fun = staticmethod(true_case_func) 

607 else: 

608 # none in class: direct copy 

609 case_fun = funcopy(true_case_func) 

610 

611 # place the special attribute __origcasefun__ so that `_FixtureCase.get_case_function` can find it back 

612 case_fun.__origcasefun__ = true_case_func 

613 

614 # handle @pytest.mark.usefixtures by creating a wrapper where the fixture is added to the signature 

615 if add_required_fixtures: 

616 # create a wrapper with an explicit requirement for the fixtures. TODO: maybe we should append and not prepend? 

617 case_fun = add_fixture_params(case_fun, add_required_fixtures) 

618 # remove the `usefixtures` mark: maybe we should leave it as it does no harm ? 

619 remove_pytest_mark(case_fun, "usefixtures") 

620 

621 # set all parametrization marks on the case function 

622 # get the list of all marks on this case 

623 case_marks = get_case_marks(case_fun, concatenate_with_fun_marks=True) 

624 

625 if case_marks: 

626 # remove all parametrization marks from this list since they will be handled here 

627 case_marks = filter_marks(case_marks, remove='parametrize') 

628 

629 # create a new fixture from a copy of the case function, and place it on the target host 

630 new_fix = fixture(name=fix_name, scope=scope)(case_fun) 

631 # mark as generated by pytest-cases so that we skip it during cases collection 

632 setattr(new_fix, GEN_BY_US, True) 

633 setattr(target_host, fix_name, new_fix) 

634 

635 # remember it for next time (one per scope) 

636 fix_cases_dct[(true_case_func, scope)] = fix_name, case_marks 

637 

638 # check that we did not touch the original case 

639 assert not is_fixture(orig_case) 

640 assert orig_case.__name__ == orig_name 

641 

642 return fix_name, case_marks 

643 

644 

645def _get_fixture_cases(module_or_class # type: Union[ModuleType, Type] 

646 ): 

647 """ 

648 Returns our 'storage unit' in a module or class, used to remember the fixtures created from case functions. 

649 That way we can reuse fixtures already created for cases, in a given module/class. 

650 

651 In addition, the host module of the class, or the module itself, is used to store a list of modules 

652 from where we imported fixtures already. This relates to the EXPERIMENTAL `import_fixtures=True` param. 

653 """ 

654 if ismodule(module_or_class): 654 ↛ 664line 654 didn't jump to line 664, because the condition on line 654 was never false

655 # module: everything is stored in the same place 

656 try: 

657 cache, imported_fixtures_list = module_or_class._fixture_cases 

658 except AttributeError: 

659 cache = dict() 

660 imported_fixtures_list = [] 

661 module_or_class._fixture_cases = (cache, imported_fixtures_list) 

662 else: 

663 # class: on class only the fixtures dict is stored 

664 try: 

665 cache = module_or_class._fixture_cases 

666 except AttributeError: 

667 cache = dict() 

668 module_or_class._fixture_cases = cache 

669 

670 # grab the imported fixtures list from the module host 

671 _, imported_fixtures_list = _get_fixture_cases(get_host_module(module_or_class)) 

672 

673 return cache, imported_fixtures_list 

674 

675 

676def import_default_cases_module(test_module_name): 

677 """ 

678 Implements the `module=AUTO` behaviour of `@parameterize_cases`. 

679 

680 `test_module_name` will have the format "test_<module>.py", the associated python module "test_<module>_cases.py" 

681 will be loaded to load the cases. 

682 

683 If "test_<module>_cases.py" module is not found it looks for the alternate file `cases_<module>.py`. 

684 

685 :param test_module_name: the test module 

686 :return: 

687 """ 

688 # First try `test_<name>_cases.py` 

689 cases_module_name1 = "%s_cases" % test_module_name 

690 

691 try: 

692 cases_module = import_module(cases_module_name1) 

693 except ModuleNotFoundError: 

694 # Then try `cases_<name>.py` 

695 parts = test_module_name.split('.') 

696 assert parts[-1][0:5] == 'test_' 

697 cases_module_name2 = "%s.cases_%s" % ('.'.join(parts[:-1]), parts[-1][5:]) 

698 try: 

699 cases_module = import_module(cases_module_name2) 

700 except ModuleNotFoundError: 

701 # Nothing worked 

702 raise ValueError("Error importing test cases module to parametrize %r: unable to import AUTO " 

703 "cases module %r nor %r. Maybe you wish to import cases from somewhere else ? In that case" 

704 " please specify `cases=...`." 

705 % (test_module_name, cases_module_name1, cases_module_name2)) 

706 

707 return cases_module 

708 

709 

710def hasinit(obj): 

711 init = getattr(obj, "__init__", None) 

712 if init: 712 ↛ exitline 712 didn't return from function 'hasinit', because the condition on line 712 was never false

713 return init != object.__init__ 

714 

715 

716def hasnew(obj): 

717 new = getattr(obj, "__new__", None) 

718 if new: 718 ↛ exitline 718 didn't return from function 'hasnew', because the condition on line 718 was never false

719 return new != object.__new__ 

720 

721 

722class CasesCollectionWarning(UserWarning): 

723 """ 

724 Warning emitted when pytest cases is not able to collect a file or symbol in a module. 

725 """ 

726 # Note: if we change this, then the symbol MUST be present in __init__ for import, see GH#249 

727 __module__ = "pytest_cases" 

728 

729 

730def extract_cases_from_class(cls, 

731 check_name=True, 

732 case_fun_prefix=CASE_PREFIX_FUN, 

733 _case_param_factory=None 

734 ): 

735 # type: (...) -> List[Callable] 

736 """ 

737 

738 :param cls: 

739 :param check_name: 

740 :param case_fun_prefix: 

741 :param _case_param_factory: 

742 :return: 

743 """ 

744 if is_case_class(cls, check_name=check_name): 744 ↛ 769line 744 didn't jump to line 769, because the condition on line 744 was never false

745 # see from _pytest.python import pytest_pycollect_makeitem 

746 

747 if hasinit(cls): 747 ↛ 748line 747 didn't jump to line 748, because the condition on line 747 was never true

748 warn( 

749 CasesCollectionWarning( 

750 "cannot collect cases class %r because it has a " 

751 "__init__ constructor" 

752 % (cls.__name__, ) 

753 ) 

754 ) 

755 return [] 

756 elif hasnew(cls): 756 ↛ 757line 756 didn't jump to line 757, because the condition on line 756 was never true

757 warn( 

758 CasesCollectionWarning( 

759 "cannot collect test class %r because it has a " 

760 "__new__ constructor" 

761 % (cls.__name__, ) 

762 ) 

763 ) 

764 return [] 

765 

766 return _extract_cases_from_module_or_class(cls=cls, case_fun_prefix=case_fun_prefix, 

767 _case_param_factory=_case_param_factory) 

768 else: 

769 return [] 

770 

771 

772def extract_cases_from_module(module, # type: ModuleRef 

773 package_name=None, # type: str 

774 case_fun_prefix=CASE_PREFIX_FUN, # type: str 

775 _case_param_factory=None 

776 ): 

777 # type: (...) -> List[Callable] 

778 """ 

779 Internal method used to create a list of case functions for all cases available from the given module. 

780 See `@cases_data` 

781 

782 See also `_pytest.python.PyCollector.collect` and `_pytest.python.PyCollector._makeitem` and 

783 `_pytest.python.pytest_pycollect_makeitem`: we could probably do this in a better way in pytest_pycollect_makeitem 

784 

785 :param module: 

786 :param package_name: 

787 :param _case_param_factory: 

788 :return: 

789 """ 

790 # optionally import module if passed as module name string 

791 if isinstance(module, string_types): 

792 try: 

793 module = import_module(module, package=package_name) 

794 except ModuleNotFoundError as e: 

795 raise ModuleNotFoundError( 

796 "Error loading cases from module. `import_module(%r, package=%r)` raised an error: %r" 

797 % (module, package_name, e) 

798 ) 

799 

800 return _extract_cases_from_module_or_class(module=module, _case_param_factory=_case_param_factory, 

801 case_fun_prefix=case_fun_prefix) 

802 

803 

804def _extract_cases_from_module_or_class(module=None, # type: ModuleRef 

805 cls=None, # type: Type 

806 case_fun_prefix=CASE_PREFIX_FUN, # type: str 

807 _case_param_factory=None 

808 ): 

809 """ 

810 

811 :param module: 

812 :param _case_param_factory: 

813 :return: 

814 """ 

815 if not ((cls is None) ^ (module is None)): 815 ↛ 816line 815 didn't jump to line 816, because the condition on line 815 was never true

816 raise ValueError("Only one of cls or module should be provided") 

817 

818 container = cls or module 

819 

820 # We will gather all cases in the reference module and put them in this dict (line no, case) 

821 cases_dct = dict() 

822 

823 # List members - only keep the functions from the module file (not the imported ones) 

824 if module is not None: 

825 def _of_interest(f): 

826 # check if the function is actually *defined* in this module (not imported from elsewhere) 

827 # Note: we used code.co_filename == module.__file__ in the past 

828 # but on some targets the file changes to a cached one so this does not work reliably, 

829 # see https://github.com/smarie/python-pytest-cases/issues/72 

830 try: 

831 return f.__module__ == module.__name__ 

832 except: # noqa 

833 return False 

834 else: 

835 def _of_interest(x): # noqa 

836 return True 

837 

838 for m_name, m in getmembers(container, _of_interest): 

839 if is_case_class(m): 

840 co_firstlineno = get_code_first_line(m) 

841 cls_cases = extract_cases_from_class(m, case_fun_prefix=case_fun_prefix, 

842 _case_param_factory=_case_param_factory) 

843 for _i, _m_item in enumerate(cls_cases): 

844 gen_line_nb = co_firstlineno + (_i / len(cls_cases)) 

845 cases_dct[gen_line_nb] = _m_item 

846 

847 elif is_case_function(m, prefix=case_fun_prefix): 

848 try: 

849 # read pytest magic attribute "place_as" to make sure this is placed correctly 

850 m_for_placing = m.place_as 

851 except AttributeError: 

852 # nominal: get the first line of code 

853 co_firstlineno = get_code_first_line(m) 

854 else: 

855 # currently we only support replacing inside the same module 

856 if m_for_placing.__module__ != m.__module__: 856 ↛ 857line 856 didn't jump to line 857, because the condition on line 856 was never true

857 raise ValueError("Unsupported value for 'place_as' special pytest attribute on case function %s: %s" 

858 ". Virtual placing in another module is not supported yet by pytest-cases." 

859 % (m, m_for_placing)) 

860 co_firstlineno = get_code_first_line(m_for_placing) 

861 

862 if cls is not None: 

863 if isinstance(cls.__dict__[m_name], (staticmethod, classmethod)): 

864 # no need to partialize a 'self' argument 

865 # BUT we'll need to recopy all marks from the holding class to the function 

866 # so let's partialize the function to get a safely editable copy of it 

867 new_m = functools.partial(m) 

868 

869 else: 

870 # Make sure that there is at least one argument 

871 try: 

872 s = signature(m) 

873 except Exception: # noqa 

874 # ignore any error here, this is optional. 

875 pass 

876 else: 

877 if len(s.parameters) < 1 or (tuple(s.parameters.keys())[0] != "self"): 

878 raise TypeError("case method is missing 'self' argument but is not static: %s" % m) 

879 # partialize the function to get one without the 'self' argument 

880 new_m = functools.partial(m, cls()) 

881 

882 # Remember the host class. We'll later use this flag to remember that this is a partial. 

883 setattr(new_m, _HOST_CLS_ATTR, cls) 

884 # Recopy all metadata concerning the case function, since partial does not copy the __dict__ by default 

885 new_m.__name__ = m.__name__ 

886 copy_case_info(m, new_m) 

887 copy_pytest_marks(m, new_m, override=True) 

888 m = new_m 

889 del new_m 

890 # Finally, propagate all marks from the holding case class to the case function 

891 copy_pytest_marks(cls, m, override=False) 

892 

893 if _case_param_factory is None: 893 ↛ 901line 893 didn't jump to line 901, because the condition on line 893 was never false

894 # Nominal usage: put the case in the dictionary 

895 if co_firstlineno in cases_dct: 895 ↛ 896line 895 didn't jump to line 896, because the condition on line 895 was never true

896 raise ValueError("Error collecting case functions, line number used by %r is already used by %r !" 

897 % (m, cases_dct[co_firstlineno])) 

898 cases_dct[co_firstlineno] = m 

899 else: 

900 # Legacy usage where the cases generators were expanded here and inserted with a virtual line no 

901 _case_param_factory(m, co_firstlineno, cases_dct) 

902 

903 # convert into a list, taking all cases in order of appearance in the code (sort by source code line number) 

904 cases = [cases_dct[k] for k in sorted(cases_dct.keys())] 

905 

906 return cases 

907 

908 

909def get_current_params(request_or_item): 

910 """ 

911 Returns a dictionary containing all parameters for the currently active `pytest` item. 

912 """ 

913 # (0) get pytest `request` and `item` 

914 item, request = get_pytest_request_and_item(request_or_item) 

915 

916 # (1) pre-scan for MultiParamAlternatives to store map of fixturename -> argnames 

917 mp_fix_to_args = dict() 

918 try: 

919 param_items = dict(item.callspec.params) 

920 except AttributeError: 

921 return {}, {}, {} 

922 

923 for argname_or_fixname, param_value in item.callspec.params.items(): 

924 if isinstance(param_value, MultiParamAlternative): 

925 # remember that the fixture named `param_value.alternative_name` represents the multiparam 

926 mp_fix_to_args[param_value.alternative_name] = param_value.argnames, param_value.decorated 

927 # we can discard this intermediate param now, it is useless 

928 del param_items[argname_or_fixname] 

929 

930 # (2) now extract all parameters available and their associated information 

931 test_fun = request.node.function 

932 results_testfun_and_unknown_fixtures = [] 

933 results_known_fixtures = dict() 

934 results_known_fixtures_but_not_found = dict() 

935 for argname_or_fixname, param_value in param_items.items(): 

936 # print(argname_or_fixturename, param_value) 

937 

938 if param_value in (NOT_USED, USED): 

939 continue # param induced by Fixture Union: ignore 

940 

941 elif not safe_isinstance(param_value, CombinedFixtureParamValue): 

942 # (a) Parameters on a test function, or parameters on a fixture with a fixture_ref inside (other fixturegen) 

943 argnames, actual_value, parametrized = get_current_param(param_value, argname_or_fixname, mp_fix_to_args) 

944 # - In nominal, we receive each (argname, value) pair independently and argnames = (argname_or_fixturename,) 

945 # - If a @parametrize containing `fixture_ref`s is present, various new parameters are received and the 

946 # `argname_or_fixturename` does not represent something useful. In this case, `argnames` may have length > 1 

947 

948 # Save each parameter one by one now 

949 for i, _argname in enumerate(argnames): 

950 _val = actual_value[i] if len(argnames) > 1 else actual_value 

951 if parametrized is None: 

952 # we are not able to know if the parameter is for the test function or a fixture 

953 results_testfun_and_unknown_fixtures.append((_argname, _val)) 

954 elif _is_same_parametrized_target(parametrized, test_fun): 

955 # the parameter is for the test function 

956 results_testfun_and_unknown_fixtures.append((_argname, _val)) 

957 else: 

958 # we are able to know that the parameter is for a fixture, but can we find that fixture ? 

959 try: 

960 fixname = _find_fixture_name(parametrized) 

961 except Exception: 

962 # we can't find the fixture. add it to the dict of "not found" 

963 # this is probably related to the fact that this is a case function or a dynamically 

964 # created fixture 

965 results_known_fixtures_but_not_found.setdefault(parametrized, []).append((_argname, _val)) 

966 else: 

967 results_known_fixtures.setdefault(fixname, []).append((_argname, _val)) 

968 else: 

969 # (b) (Combined) parameters on a fixture, except those including fixture_refs 

970 fixturename = argname_or_fixname 

971 # de-combine each distinct @parametrize that was made on that fixture 

972 for argnames, argvals in param_value.iterparams(): 

973 # this is a single @parametrize(argnames, argvals) 

974 # note: do not iterate on the argvals but on the argnames, as argvals can be a LazyTuple 

975 for item, argname in enumerate(argnames): 

976 value = argvals[item] if len(argnames) > 1 else argvals # argvals is already unpacked if single 

977 _name, actual_value, _target = get_current_param(value, fixturename, mp_fix_to_args) 

978 # the parameter is for a fixture 

979 # if argname != _names[0] or len(_names) > 1: 

980 # get_current_param(value, fixturename, mp_fix_to_args, test_fun_name) 

981 # raise ValueError("Please report") 

982 results_known_fixtures.setdefault(fixturename, []).append((argname, actual_value)) 

983 

984 # process the lists to create the outputs 

985 # First, the test function params and the legacy pytest fixture params (if not hidden by names of fun params) 

986 tests_and_legacy_fix_results_dict = dict(results_testfun_and_unknown_fixtures) 

987 if len(tests_and_legacy_fix_results_dict) != len(results_testfun_and_unknown_fixtures): 987 ↛ 988line 987 didn't jump to line 988, because the condition on line 987 was never true

988 raise ValueError("Error: multiple values found for the same parameter. Please report this issue") 

989 

990 # Then new style fixtures. since in some cases fixture names can conflict with param names, we use a separate dict. 

991 fixture_results_dict = dict() 

992 for fixture_name, results_list in results_known_fixtures.items(): 

993 fixture_results_dct = dict(results_list) 

994 if len(fixture_results_dct) != len(results_list): 994 ↛ 995line 994 didn't jump to line 995, because the condition on line 994 was never true

995 raise ValueError("Error: multiple values found for the same fixture parameter. Please report this issue") 

996 fixture_results_dict[fixture_name] = fixture_results_dct 

997 

998 # the remainder: fixtures that can't be found. 

999 results_unknown_dict = dict() 

1000 for function, results_list in results_known_fixtures_but_not_found.items(): 

1001 fixture_results_dct = dict(results_list) 

1002 if len(fixture_results_dct) != len(results_list): 1002 ↛ 1003line 1002 didn't jump to line 1003, because the condition on line 1002 was never true

1003 raise ValueError("Error: multiple values found for the same parameter. Please report this issue") 

1004 results_unknown_dict[function] = fixture_results_dct 

1005 

1006 return tests_and_legacy_fix_results_dict, fixture_results_dict, results_unknown_dict 

1007 

1008 

1009def _is_same_parametrized_target(parametrized, test_fun): 

1010 """ 

1011 

1012 :param parametrized: 

1013 :param test_fun: 

1014 :return: 

1015 """ 

1016 return parametrized.__name__ == test_fun.__name__ 

1017 

1018 

1019def _find_fixture_name(parametrized): 

1020 """ 

1021 Finds the actual fixture symbol whose implementation is this function. 

1022 :param parametrized: 

1023 :return: 

1024 """ 

1025 container = get_class_that_defined_method(parametrized) 

1026 if container is None: 1026 ↛ 1029line 1026 didn't jump to line 1029, because the condition on line 1026 was never false

1027 container = get_function_host(parametrized) 

1028 

1029 parametrized_fixture = getattr(container, parametrized.__name__) 

1030 

1031 return get_fixture_name(parametrized_fixture) 

1032 

1033 

1034def get_current_param(value, argname_or_fixturename, mp_fix_to_args): 

1035 """ 

1036 This function's primary role is to unpack the various parameter values (instances of `ParamAlternative`) created by 

1037 @parametrize when a fixture reference is used in the parametrization. 

1038 

1039 Returns the argnames, actual value, and parametrized fixture name if it can be known, 

1040 associated with parameter value `value`. 

1041 

1042 :param value: 

1043 :param argname_or_fixturename: 

1044 :param mp_fix_to_args: 

1045 :return: (argnames, actual_value, paramztrized_fixname) 

1046 """ 

1047 try: 

1048 # (1) Does this parameter correspond to a fixture *generated* by a MultiParamAlternative ? 

1049 # If so we already have its true argnames and parametrization target here, and the value is directly the param. 

1050 argnames, parametrized = mp_fix_to_args[argname_or_fixturename] 

1051 actual_value = value 

1052 except KeyError: 

1053 # (2) Is this parameter a ParamAlternative? (this happens when at least 1 param in the argvals is a fixture_ref) 

1054 if safe_isinstance(value, ParamAlternative): 

1055 # if isinstance(value, MultiParamAlternative): 

1056 # return False # ignore silently, already handled in the pass before the main loop 

1057 if isinstance(value, SingleParamAlternative): 1057 ↛ 1066line 1057 didn't jump to line 1066, because the condition on line 1057 was never false

1058 # extract the various info available 

1059 parametrized = value.decorated 

1060 # actual_id = value.get_alternative_id() 

1061 argnames = value.argnames 

1062 actual_value = value.argval 

1063 if len(argnames) == 1 and not isinstance(value, FixtureParamAlternative): 

1064 actual_value = actual_value[0] 

1065 else: 

1066 raise TypeError("Unsupported type, please report: %r" % type(value)) 

1067 else: 

1068 # (3) "normal" parameter: each (argname, value) pair is received independently 

1069 argnames = (argname_or_fixturename,) 

1070 parametrized = None 

1071 actual_value = value 

1072 

1073 return argnames, actual_value, parametrized 

1074 

1075 

1076Case = namedtuple("Case", ("id", "func", "params")) 

1077 

1078 

1079def get_current_cases(request_or_item): 

1080 """ 

1081 Returns a dictionary containing all case parameters for the currently active `pytest` item. 

1082 You can either pass the `pytest` item (available in some hooks) or the `request` (available in hooks, and also 

1083 directly as a fixture). 

1084 

1085 For each test function argument parametrized using a `@parametrize_with_case(<argname>, ...)` this dictionary 

1086 contains an entry `{<argname>: (case_id, case_function, case_params)}`. If several argnames are parametrized this 

1087 way, a dedicated entry will be present for each argname. The tuple is a `namedtuple` containing 

1088 

1089 - `id` a string containing the actual case id constructed by `@parametrize_with_cases`. 

1090 - `function` the original case function. 

1091 - `params` a dictionary, containing the parameters of the case, if itself is parametrized. Note that if the 

1092 case is parametrized with `@parametrize_with_cases`, the associated parameter value in the dictionary will also be 

1093 `(actual_id, case_function, case_params)`. 

1094 

1095 If a fixture parametrized with cases is active, the dictionary will contain an entry `{<fixturename>: <dct>}` where 

1096 `<dct>` is a dictionary `{<argname>: (case_id, case_function, case_params)}`. 

1097 

1098 To get more information on a case function, you can use `get_case_marks(f)`, `get_case_tags(f)`. 

1099 You can also use `matches_tag_query` to check if a case function matches some expectations either concerning its id 

1100 or its tags. See https://smarie.github.io/python-pytest-cases/#filters-and-tags 

1101 

1102 Note that you can get the same contents directly by using the `current_cases` fixture. 

1103 """ 

1104 # (0) get pytest `request` and `item` 

1105 item, request = get_pytest_request_and_item(request_or_item) 

1106 

1107 # (1) retrieve all parameters 

1108 test_res_dict, fixture_results_dict, res_unkfix_dict = get_current_params(request_or_item) 

1109 

1110 # multiple nesyed @parametrize with fixture refs might have created several wrappers. access the 

1111 res_unkfix_dict2 = {_get_place_as(k): v for k, v in res_unkfix_dict.items()} 

1112 

1113 # Now create the results containing the cases and their parameters only 

1114 case_fixture_names_to_remove = set() 

1115 

1116 def _do(name, value, dct, preserve=False): 

1117 if safe_isinstance(value, LazyTupleItem): 

1118 value = value.host._lazyvalue 

1119 elif safe_isinstance(value, FixtureRefItem): 

1120 value = value.host 

1121 

1122 if safe_isinstance(value, CaseParamValue): 

1123 # Case function 

1124 case_func = value.get_case_function(request) 

1125 

1126 # Case id 

1127 # we cannot use `get_case_id` because we do not know the prefix that was used 

1128 # case_id = get_case_id(case_func, prefix_for_default_ids=) 

1129 case_id = value.get_case_id() 

1130 

1131 # Case parameter(s) 

1132 case_params_dct = {} 

1133 if safe_isinstance(value, _FixtureRefCaseParamValue): 

1134 casefixname = value.fixture 

1135 if casefixname in fixture_results_dict: 

1136 # case is a fixture and is parametrized 1 

1137 case_fixture_names_to_remove.add(casefixname) 

1138 for _n, _v in fixture_results_dict[casefixname].items(): 

1139 _do(_n, _v, case_params_dct, preserve=True) 

1140 else: 

1141 case_impl_fun = _get_place_as(case_func) 

1142 try: 

1143 paramz = res_unkfix_dict2[case_impl_fun] 

1144 except KeyError: 

1145 # case is a fixture but is not parametrized 

1146 pass 

1147 else: 

1148 # case is a fixture and is parametrized 2 

1149 # it was harder to find its params because they did not directly link to the fixture 

1150 for _n, _v in paramz.items(): 

1151 _do(_n, _v, case_params_dct, preserve=True) 

1152 else: 

1153 # case is not a fixture: it cannot possibly be parametrized 

1154 pass 

1155 

1156 # Finally fill the results 

1157 dct[name] = Case(case_id, case_func, case_params_dct) 

1158 

1159 elif preserve: 

1160 # used in nested scenarii 

1161 dct[name] = value 

1162 

1163 cases_res_dict = dict() 

1164 for name, value in test_res_dict.items(): 

1165 # fill the main dict 

1166 _do(name, value, cases_res_dict) 

1167 

1168 # use a separate dict as name conflicts might happen 

1169 cases_res_dict_fixs = dict() 

1170 for name, value in fixture_results_dict.items(): 

1171 # fill a dedicated subdict 

1172 sub_dict = {} 

1173 for n, v in value.items(): 

1174 _do(n, v, sub_dict) 

1175 if len(sub_dict) > 0: 

1176 cases_res_dict_fixs[name] = sub_dict 

1177 

1178 # finally remove the case fixtures from the result dict 

1179 for f in case_fixture_names_to_remove: 

1180 try: 

1181 del cases_res_dict_fixs[f] 

1182 except KeyError: 

1183 pass 

1184 

1185 # merge the two - put the fixtures at the end 

1186 for k, v in cases_res_dict_fixs.items(): 

1187 if k not in cases_res_dict: 1187 ↛ 1186line 1187 didn't jump to line 1186, because the condition on line 1187 was never false

1188 cases_res_dict[k] = v 

1189 

1190 return cases_res_dict 

1191 

1192 

1193def _get_place_as(f): 

1194 while True: 

1195 try: 

1196 f = f.place_as 

1197 except AttributeError: 

1198 return f 

1199 

1200 

1201def get_current_case_id(request_or_item, 

1202 argnames # type: Union[Iterable[str], str] 

1203 ): 

1204 """ DEPRECATED - use `get_current_cases` instead 

1205 A helper function to return the current case id for a given `pytest` item (available in some hooks) or `request` 

1206 (available in hooks, and also directly as a fixture). 

1207 

1208 You need to provide the argname(s) used in the corresponding `@parametrize_with_cases` so that this method finds 

1209 the right id. 

1210 

1211 :param request_or_item: 

1212 :param argnames: 

1213 :return: 

1214 """ 

1215 warn("`get_current_case_id` is DEPRECATED - please use the `current_cases` fixture instead, or `get_current_cases`") 

1216 

1217 # process argnames 

1218 if isinstance(argnames, string_types): 

1219 argnames = get_param_argnames_as_list(argnames) 

1220 

1221 # retrieve the correct id 

1222 all_case_funcs = get_current_cases(request_or_item) 

1223 return all_case_funcs[argnames[0]][0] 

1224 

1225 

1226# Below is the beginning of a switch from our code scanning tool above to the same one than pytest. 

1227# from .common_pytest import is_fixture, safe_isclass, compat_get_real_func, compat_getfslineno 

1228# 

1229# 

1230# class PytestCasesWarning(UserWarning): 

1231# """ 

1232# Bases: :class:`UserWarning`. 

1233# 

1234# Base class for all warnings emitted by pytest cases. 

1235# """ 

1236# 

1237# __module__ = "pytest_cases" 

1238# 

1239# 

1240# class PytestCasesCollectionWarning(PytestCasesWarning): 

1241# """ 

1242# Bases: :class:`PytestCasesWarning`. 

1243# 

1244# Warning emitted when pytest cases is not able to collect a file or symbol in a module. 

1245# """ 

1246# 

1247# __module__ = "pytest_cases" 

1248# 

1249# 

1250# class CasesModule(object): 

1251# """ 

1252# A collector for test cases 

1253# This is a very lightweight version of `_pytest.python.Module`,the pytest collector for test functions and classes. 

1254# 

1255# See also pytest_collect_file and pytest_pycollect_makemodule hooks 

1256# """ 

1257# __slots__ = 'obj' 

1258# 

1259# def __init__(self, module): 

1260# self.obj = module 

1261# 

1262# def collect(self): 

1263# """ 

1264# A copy of pytest Module.collect (PyCollector.collect actually) 

1265# :return: 

1266# """ 

1267# if not getattr(self.obj, "__test__", True): 

1268# return [] 

1269# 

1270# # NB. we avoid random getattrs and peek in the __dict__ instead 

1271# # (XXX originally introduced from a PyPy need, still true?) 

1272# dicts = [getattr(self.obj, "__dict__", {})] 

1273# for basecls in getmro(self.obj.__class__): 

1274# dicts.append(basecls.__dict__) 

1275# seen = {} 

1276# values = [] 

1277# for dic in dicts: 

1278# for name, obj in list(dic.items()): 

1279# if name in seen: 

1280# continue 

1281# seen[name] = True 

1282# res = self._makeitem(name, obj) 

1283# if res is None: 

1284# continue 

1285# if not isinstance(res, list): 

1286# res = [res] 

1287# values.extend(res) 

1288# 

1289# def sort_key(item): 

1290# fspath, lineno, _ = item.reportinfo() 

1291# return (str(fspath), lineno) 

1292# 

1293# values.sort(key=sort_key) 

1294# return values 

1295# 

1296# def _makeitem(self, name, obj): 

1297# """ An adapted copy of _pytest.python.pytest_pycollect_makeitem """ 

1298# if safe_isclass(obj): 

1299# if self.iscaseclass(obj, name): 

1300# raise ValueError("Case classes are not yet supported: %r" % obj) 

1301# elif self.iscasefunction(obj, name): 

1302# # mock seems to store unbound methods (issue473), normalize it 

1303# obj = getattr(obj, "__func__", obj) 

1304# # We need to try and unwrap the function if it's a functools.partial 

1305# # or a functools.wrapped. 

1306# # We mustn't if it's been wrapped with mock.patch (python 2 only) 

1307# if not (isfunction(obj) or isfunction(compat_get_real_func(obj))): 

1308# filename, lineno = compat_getfslineno(obj) 

1309# warn_explicit( 

1310# message=PytestCasesCollectionWarning( 

1311# "cannot collect %r because it is not a function." % name 

1312# ), 

1313# category=None, 

1314# filename=str(filename), 

1315# lineno=lineno + 1, 

1316# ) 

1317# elif getattr(obj, "__test__", True): 

1318# if isgeneratorfunction(obj): 

1319# filename, lineno = compat_getfslineno(obj) 

1320# warn_explicit( 

1321# message=PytestCasesCollectionWarning( 

1322# "cannot collect %r because it is a generator function." % name 

1323# ), 

1324# category=None, 

1325# filename=str(filename), 

1326# lineno=lineno + 1, 

1327# ) 

1328# else: 

1329# res = list(self._gencases(name, obj)) 

1330# outcome.force_result(res) 

1331# 

1332# def iscasefunction(self, obj, name): 

1333# """Similar to PyCollector.istestfunction""" 

1334# if name.startswith("case_"): 

1335# if isinstance(obj, staticmethod): 

1336# # static methods need to be unwrapped 

1337# obj = getattr(obj, "__func__", False) 

1338# return ( 

1339# getattr(obj, "__call__", False) 

1340# and not is_fixture(obj) is None 

1341# ) 

1342# else: 

1343# return False 

1344# 

1345# def iscaseclass(self, obj, name): 

1346# """Similar to PyCollector.istestclass""" 

1347# return name.startswith("Case") 

1348# 

1349# def _gencases(self, name, funcobj): 

1350# # generate the case associated with a case function object. 

1351# # note: the original PyCollector._genfunctions has a "metafunc" mechanism here, we do not need it. 

1352# return [] 

1353# 

1354#