Coverage for src/pytest_cases/fixture_parametrize_plus.py: 88%

522 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> 

5from inspect import isgeneratorfunction 

6from warnings import warn 

7 

8 

9try: # python 3.3+ 

10 from inspect import signature, Parameter 

11except ImportError: 

12 from funcsigs import signature, Parameter # noqa 

13 

14try: # native coroutines, python 3.5+ 

15 from inspect import iscoroutinefunction 

16except ImportError: 

17 def iscoroutinefunction(obj): 

18 return False 

19 

20try: # native async generators, python 3.6+ 

21 from inspect import isasyncgenfunction 

22except ImportError: 

23 def isasyncgenfunction(obj): 

24 return False 

25 

26try: 

27 from collections.abc import Iterable 

28except ImportError: # noqa 

29 from collections import Iterable 

30 

31try: 

32 from typing import Union, Callable, List, Any, Sequence, Optional, Type, Tuple, TypeVar # noqa 

33 from types import ModuleType # noqa 

34 

35 T = TypeVar('T', bound=Union[Type, Callable]) 

36except ImportError: 

37 pass 

38 

39import pytest 

40import sys 

41from makefun import with_signature, remove_signature_parameters, add_signature_parameters, wraps 

42 

43from .common_mini_six import string_types 

44from .common_others import AUTO, robust_isinstance, replace_list_contents 

45from .common_pytest_marks import has_pytest_param, get_param_argnames_as_list 

46from .common_pytest_lazy_values import is_lazy_value, get_lazy_args 

47from .common_pytest import get_fixture_name, remove_duplicates, mini_idvalset, is_marked_parameter_value, \ 

48 extract_parameterset_info, ParameterSet, cart_product_pytest, mini_idval, inject_host, \ 

49 get_marked_parameter_values, resolve_ids, get_marked_parameter_id, get_marked_parameter_marks, is_fixture, \ 

50 safe_isclass 

51 

52from .fixture__creation import check_name_available, CHANGE, WARN 

53from .fixture_core1_unions import InvalidParamsList, NOT_USED, UnionFixtureAlternative, _make_fixture_union, \ 

54 _make_unpack_fixture, UnionIdMakers 

55from .fixture_core2 import _create_param_fixture, fixture 

56 

57 

58def _fixture_product(fixtures_dest, 

59 name, # type: str 

60 fixtures_or_values, 

61 fixture_positions, 

62 scope="function", # type: str 

63 unpack_into=None, # type: Iterable[str] 

64 autouse=False, # type: bool 

65 hook=None, # type: Callable[[Callable], Callable] 

66 caller=None, # type: Callable 

67 **kwargs): 

68 """ 

69 Internal implementation for fixture products created by pytest parametrize plus. 

70 

71 :param fixtures_dest: 

72 :param name: 

73 :param fixtures_or_values: 

74 :param fixture_positions: 

75 :param idstyle: 

76 :param scope: 

77 :param ids: 

78 :param unpack_into: 

79 :param autouse: 

80 :param kwargs: 

81 :return: 

82 """ 

83 # test the `fixtures` argument to avoid common mistakes 

84 if not isinstance(fixtures_or_values, (tuple, set, list)): 84 ↛ 85line 84 didn't jump to line 85, because the condition on line 84 was never true

85 raise TypeError("fixture_product: the `fixtures_or_values` argument should be a tuple, set or list") 

86 else: 

87 has_lazy_vals = any(is_lazy_value(v) for v in fixtures_or_values) 

88 

89 _tuple_size = len(fixtures_or_values) 

90 

91 # first get all required fixture names 

92 f_names = [None] * _tuple_size 

93 for f_pos in fixture_positions: 

94 # possibly get the fixture name if the fixture symbol was provided 

95 f = fixtures_or_values[f_pos] 

96 if isinstance(f, fixture_ref): 96 ↛ 99line 96 didn't jump to line 99, because the condition on line 96 was never false

97 f = f.fixture 

98 # and remember the position in the tuple 

99 f_names[f_pos] = get_fixture_name(f) 

100 

101 # remove duplicates by making it an ordered set 

102 all_names = remove_duplicates((n for n in f_names if n is not None)) 

103 if len(all_names) < 1: 103 ↛ 104line 103 didn't jump to line 104, because the condition on line 103 was never true

104 raise ValueError("Empty fixture products are not permitted") 

105 

106 def _tuple_generator(request, all_fixtures): 

107 for i in range(_tuple_size): 

108 fix_at_pos_i = f_names[i] 

109 if fix_at_pos_i is None: 

110 # fixed value 

111 # note: wouldn't it be almost as efficient but more readable to *always* call handle_lazy_args? 

112 yield get_lazy_args(fixtures_or_values[i], request) if has_lazy_vals else fixtures_or_values[i] 

113 else: 

114 # fixture value 

115 yield all_fixtures[fix_at_pos_i] 

116 

117 # then generate the body of our product fixture. It will require all of its dependent fixtures 

118 @with_signature("(request, %s)" % ', '.join(all_names)) 

119 def _new_fixture(request, **all_fixtures): 

120 return tuple(_tuple_generator(request, all_fixtures)) 

121 

122 _new_fixture.__name__ = name 

123 

124 # finally create the fixture per se. 

125 # WARNING we do not use pytest.fixture but fixture so that NOT_USED is discarded 

126 f_decorator = fixture(scope=scope, autouse=autouse, hook=hook, **kwargs) 

127 fix = f_decorator(_new_fixture) 

128 

129 # Dynamically add fixture to caller's module as explained in https://github.com/pytest-dev/pytest/issues/2424 

130 check_name_available(fixtures_dest, name, if_name_exists=WARN, caller=caller) 

131 setattr(fixtures_dest, name, fix) 

132 

133 # if unpacking is requested, do it here 

134 if unpack_into is not None: 134 ↛ 137line 134 didn't jump to line 137, because the condition on line 134 was never true

135 # note that as for fixture unions, we can not expose the `in_cls` parameter. 

136 # but there is an easy workaround if unpacking is needed: call unpack_fixture separately 

137 _make_unpack_fixture(fixtures_dest, argnames=unpack_into, fixture=name, hook=hook, in_cls=False) 

138 

139 return fix 

140 

141 

142_make_fixture_product = _fixture_product 

143"""A readable alias for callers not using the returned symbol""" 

144 

145 

146class fixture_ref(object): # noqa 

147 """ 

148 A reference to a fixture, to be used in `@parametrize`. 

149 You can create it from a fixture name or a fixture object (function). 

150 """ 

151 __slots__ = 'fixture', 'theoretical_size', '_id' 

152 

153 def __init__(self, 

154 fixture, # type: Union[str, Callable] 

155 id=None, # type: str # noqa 

156 ): 

157 """ 

158 

159 :param fixture: the name of the fixture to reference, or the fixture function itself 

160 :param id: an optional custom id to override the fixture name in ids. 

161 """ 

162 self.fixture = get_fixture_name(fixture) 

163 self._id = id 

164 self.theoretical_size = None # we dont know yet, will be filled by @parametrize 

165 

166 def get_name_for_id(self): 

167 """return the name to use in ids.""" 

168 return self._id if self._id is not None else self.fixture 

169 

170 def __str__(self): 

171 # used in mini_idval for example 

172 return self.get_name_for_id() 

173 

174 def __repr__(self): 

175 if self._id is not None: 175 ↛ 176line 175 didn't jump to line 176, because the condition on line 175 was never true

176 return "fixture_ref<%s, id=%s>" % (self.fixture, self._id) 

177 else: 

178 return "fixture_ref<%s>" % self.fixture 

179 

180 def _check_iterable(self): 

181 """Raise a TypeError if this fixture reference is not iterable, that is, it does not represent a tuple""" 

182 if self.theoretical_size is None: 

183 raise TypeError("This `fixture_ref` has not yet been initialized, so it cannot be unpacked/iterated upon. " 

184 "This is not supposed to happen when a `fixture_ref` is used correctly, i.e. as an item in" 

185 " the `argvalues` of a `@parametrize` decorator. Please check the documentation for " 

186 "details.") 

187 if self.theoretical_size == 1: 187 ↛ 188line 187 didn't jump to line 188, because the condition on line 187 was never true

188 raise TypeError("This fixture_ref does not represent a tuple of arguments, it is not iterable") 

189 

190 def __len__(self): 

191 self._check_iterable() 

192 return self.theoretical_size 

193 

194 def __getitem__(self, item): 

195 """ 

196 Returns an item in the tuple described by this fixture_ref. 

197 This is just a facade, a FixtureRefItem. 

198 Note: this is only used when a custom `idgen` is passed to @parametrized 

199 """ 

200 self._check_iterable() 

201 return FixtureRefItem(self, item) 

202 

203 

204class FixtureRefItem(object): 

205 """An item in a fixture_ref when this fixture_ref is used as a tuple.""" 

206 __slots__ = 'host', 'item' 

207 

208 def __init__(self, 

209 host, # type: fixture_ref 

210 item # type: int 

211 ): 

212 self.host = host 

213 self.item = item 

214 

215 def __repr__(self): 

216 return "%r[%s]" % (self.host, self.item) 

217 

218 

219# Fix for https://github.com/smarie/python-pytest-cases/issues/71 

220# In order for pytest to allow users to import this symbol in conftest.py 

221# they should be declared as optional plugin hooks. 

222# A workaround otherwise would be to remove the 'pytest_' name prefix 

223# See https://github.com/pytest-dev/pytest/issues/6475 

224@pytest.hookimpl(optionalhook=True) 

225def pytest_parametrize_plus(*args, 

226 **kwargs): 

227 warn("`pytest_parametrize_plus` and `parametrize_plus` are deprecated. Please use the new alias `parametrize`. " 

228 "See https://github.com/pytest-dev/pytest/issues/6475", category=DeprecationWarning, stacklevel=2) 

229 return parametrize(*args, **kwargs) 

230 

231 

232parametrize_plus = pytest_parametrize_plus 

233 

234 

235class ParamAlternative(UnionFixtureAlternative): 

236 """Defines an "alternative", used to parametrize a fixture union in the context of parametrize 

237 

238 It is similar to a union fixture alternative, except that it also remembers the parameter argnames. 

239 They are used to generate the test id corresponding to this alternative. See `_get_minimal_id` implementations. 

240 `ParamIdMakers` overrides some of the idstyles in `UnionIdMakers` so as to adapt them to these `ParamAlternative` 

241 objects. 

242 """ 

243 __slots__ = ('argnames', 'decorated') 

244 

245 def __init__(self, 

246 union_name, # type: str 

247 alternative_name, # type: str 

248 param_index, # type: int 

249 argnames, # type: Sequence[str] 

250 decorated # type: Callable 

251 ): 

252 """ 

253 

254 :param union_name: the name of the union fixture created by @parametrize to switch between param alternatives 

255 :param alternative_name: the name of the fixture created by @parametrize to represent this alternative 

256 :param param_index: the index of this parameter in the list of argvalues passed to @parametrize 

257 :param argnames: the list of parameter names in @parametrize 

258 :param decorated: the test function or fixture that this alternative refers to 

259 """ 

260 super(ParamAlternative, self).__init__(union_name=union_name, alternative_name=alternative_name, 

261 alternative_index=param_index) 

262 self.argnames = argnames 

263 self.decorated = decorated 

264 

265 def get_union_id(self): 

266 return ("(%s)" % ",".join(self.argnames)) if len(self.argnames) > 1 else self.argnames[0] 

267 

268 def get_alternative_idx(self): 

269 return "P%s" % self.alternative_index 

270 

271 def get_alternative_id(self): 

272 """Subclasses should return the smallest id representing this parametrize fixture union alternative""" 

273 raise NotImplementedError() 

274 

275 

276class SingleParamAlternative(ParamAlternative): 

277 """alternative class for single parameter value""" 

278 __slots__ = 'argval', 'id' 

279 

280 def __init__(self, 

281 union_name, # type: str 

282 alternative_name, # type: str 

283 param_index, # type: int 

284 argnames, # type: Sequence[str] 

285 argval, # type: Any 

286 id, # type: Optional[str] 

287 decorated # type: Callable 

288 ): 

289 """ 

290 :param union_name: the name of the union fixture created by @parametrize to switch between param alternatives 

291 :param alternative_name: the name of the fixture created by @parametrize to represent this alternative 

292 :param param_index: the index of this parameter in the list of argvalues passed to @parametrize 

293 :param argnames: the list of parameter names in @parametrize 

294 :param argval: the value used by this parameter 

295 """ 

296 super(SingleParamAlternative, self).__init__(union_name=union_name, alternative_name=alternative_name, 

297 param_index=param_index, argnames=argnames, decorated=decorated) 

298 self.argval = argval 

299 self.id = id 

300 

301 def get_alternative_id(self): 

302 """Since this alternative has no further parametrization (simplification for 1-param alternative), 

303 we create here the equivalent of the id of the argvalue if it was used as a parameter""" 

304 if self.id is not None: 

305 # custom id from `@parametrize(ids=<callable_or_list>)` 

306 return self.id 

307 else: 

308 return mini_idvalset(self.argnames, self.argval, idx=self.alternative_index) 

309 

310 @classmethod 

311 def create(cls, 

312 new_fixture_host, # type: Union[Type, ModuleType] 

313 test_func, # type: Callable 

314 param_union_name, # type: str 

315 argnames, # type: Sequence[str] 

316 i, # type: int 

317 argvalue, # type: Any 

318 id, # type: Union[str, Callable] 

319 scope=None, # type: str 

320 hook=None, # type: Callable 

321 debug=False # type: bool 

322 ): 

323 # type: (...) -> SingleParamAlternative 

324 """ 

325 Creates an alternative for fixture union `param_union_name`, to handle single parameter value 

326 argvalue = argvalues[i] in @parametrize. 

327 

328 This alternative will refer to a newly created fixture in `new_fixture_host`, that will return `argvalue`. 

329 

330 :param new_fixture_host: host (class, module) where the new fixture should be created 

331 :param test_func: 

332 :param param_union_name: 

333 :param argnames: 

334 :param i: 

335 :param argvalue: a (possibly marked with pytest.param) argvalue 

336 :param hook: 

337 :param debug: 

338 :return: 

339 """ 

340 nb_params = len(argnames) 

341 param_names_str = '_'.join(argnames).replace(' ', '') 

342 

343 # Create a unique fixture name 

344 p_fix_name = "%s_%s_P%s" % (test_func.__name__, param_names_str, i) 

345 p_fix_name = check_name_available(new_fixture_host, p_fix_name, if_name_exists=CHANGE, caller=parametrize) 

346 

347 if debug: 

348 print(" - Creating new fixture %r to handle parameter %s" % (p_fix_name, i)) 

349 

350 # Now we'll create the fixture that will return the unique parameter value 

351 # since this parameter is unique, we do not parametrize the fixture (_create_param_fixture "auto_simplify" flag) 

352 # for this reason the possible pytest.param ids and marks have to be set somewhere else: we move them 

353 # to the alternative. 

354 

355 # unwrap possible pytest.param on the argvalue to move them on the SingleParamAlternative 

356 has_pytestparam_wrapper = is_marked_parameter_value(argvalue) 

357 if has_pytestparam_wrapper: 

358 p_id = get_marked_parameter_id(argvalue) 

359 p_marks = get_marked_parameter_marks(argvalue) 

360 argvalue = get_marked_parameter_values(argvalue, nbargs=nb_params) 

361 if nb_params == 1: 

362 argvalue = argvalue[0] 

363 

364 # Create the fixture. IMPORTANT auto_simplify=True : we create a NON-parametrized fixture. 

365 _create_param_fixture(new_fixture_host, argname=p_fix_name, argvalues=(argvalue,), 

366 scope=scope, hook=hook, auto_simplify=True, debug=debug) 

367 

368 # Create the alternative 

369 argvals = (argvalue,) if nb_params == 1 else argvalue 

370 p_fix_alt = SingleParamAlternative(union_name=param_union_name, alternative_name=p_fix_name, 

371 argnames=argnames, param_index=i, argval=argvals, id=id, 

372 decorated=test_func) 

373 

374 # Finally copy the custom id/marks on the ParamAlternative if any 

375 if has_pytestparam_wrapper: 

376 p_fix_alt = ParameterSet(values=(p_fix_alt,), id=p_id, marks=p_marks) # noqa 

377 

378 return p_fix_alt 

379 

380 

381class MultiParamAlternative(ParamAlternative): 

382 """alternative class for multiple parameter values""" 

383 __slots__ = 'param_index_from', 'param_index_to' 

384 

385 def __init__(self, 

386 union_name, # type: str 

387 alternative_name, # type: str 

388 argnames, # type: Sequence[str] 

389 param_index_from, # type: int 

390 param_index_to, # type: int 

391 decorated # type: Callable 

392 ): 

393 """ 

394 

395 :param union_name: the name of the union fixture created by @parametrize to switch between param alternatives 

396 :param alternative_name: the name of the fixture created by @parametrize to represent this alternative 

397 :param argnames: the list of parameter names in @parametrize 

398 :param param_index_from: the beginning index of the parameters covered by <alternative_name> in the list of 

399 argvalues passed to @parametrize 

400 :param param_index_to: the ending index of the parameters covered by <alternative_name> in the list of 

401 argvalues passed to @parametrize 

402 """ 

403 # set the param_index to be None since here we represent several indices 

404 super(MultiParamAlternative, self).__init__(union_name=union_name, alternative_name=alternative_name, 

405 argnames=argnames, param_index=None, decorated=decorated # noqa 

406 ) 

407 self.param_index_from = param_index_from 

408 self.param_index_to = param_index_to 

409 

410 def __str__(self): 

411 return "%s/%s/" % (self.get_union_id(), self.get_alternative_idx()) 

412 

413 def get_alternative_idx(self): 

414 return "P%s:%s" % (self.param_index_from, self.param_index_to) 

415 

416 def get_alternative_id(self): 

417 # The alternative id is the parameter range - the parameter themselves appear on the referenced fixture 

418 return self.get_alternative_idx() 

419 

420 @classmethod 

421 def create(cls, 

422 new_fixture_host, # type: Union[Type, ModuleType] 

423 test_func, # type: Callable 

424 param_union_name, # type: str 

425 argnames, # type: Sequence[str] 

426 from_i, # type: int 

427 to_i, # type: int 

428 argvalues, # type: Any 

429 ids, # type: Union[Sequence[str], Callable] 

430 scope="function", # type: str 

431 hook=None, # type: Callable 

432 debug=False # type: bool 

433 ): 

434 # type: (...) -> MultiParamAlternative 

435 """ 

436 Creates an alternative for fixture union `param_union_name`, to handle a group of consecutive parameters 

437 argvalues[from_i:to_i] in @parametrize. Note that here the received `argvalues` should be already sliced 

438 

439 This alternative will refer to a newly created fixture in `new_fixture_host`, that will be parametrized to 

440 return each of `argvalues`. 

441 

442 :param new_fixture_host: 

443 :param test_func: 

444 :param param_union_name: 

445 :param argnames: 

446 :param from_i: 

447 :param to_i: 

448 :param argvalues: 

449 :param hook: 

450 :param debug: 

451 :return: 

452 """ 

453 nb_params = len(argnames) 

454 param_names_str = '_'.join(argnames).replace(' ', '') 

455 

456 # Create a unique fixture name 

457 p_fix_name = "%s_%s_is_P%stoP%s" % (test_func.__name__, param_names_str, from_i, to_i - 1) 

458 p_fix_name = check_name_available(new_fixture_host, p_fix_name, if_name_exists=CHANGE, caller=parametrize) 

459 

460 if debug: 

461 print(" - Creating new fixture %r to handle parameters %s to %s" % (p_fix_name, from_i, to_i - 1)) 

462 

463 # Create the fixture 

464 # - it will be parametrized to take all the values in argvalues 

465 # - therefore it will use the custom ids and marks if any 

466 # - it will be unique (not unfolded) so if there are more than 1 argnames we have to add a layer of tuple in the 

467 # values 

468 

469 if nb_params > 1: 

470 # we have to create a tuple around the vals because we have a SINGLE parameter that is a tuple 

471 unmarked_argvalues = [] 

472 new_argvals = [] 

473 for v in argvalues: 

474 if is_marked_parameter_value(v): 

475 # transform the parameterset so that it contains a tuple of length 1 

476 vals = get_marked_parameter_values(v, nbargs=nb_params) 

477 if nb_params == 1: 477 ↛ 478line 477 didn't jump to line 478, because the condition on line 477 was never true

478 vals = vals[0] 

479 unmarked_argvalues.append(vals) 

480 new_argvals.append(ParameterSet((vals,), 

481 id=get_marked_parameter_id(v), 

482 marks=get_marked_parameter_marks(v))) 

483 else: 

484 # nothing special to do since there is no pytest.param here 

485 new_argvals.append(v) 

486 unmarked_argvalues.append(v) 

487 argvalues = new_argvals 

488 

489 # we also have to generate the ids correctly "as if they were multiple" 

490 try: 

491 iter(ids) 

492 except TypeError: 

493 if ids is not None: 493 ↛ 494line 493 didn't jump to line 494, because the condition on line 493 was never true

494 ids = ["-".join(ids(vi) for vi in v) for v in unmarked_argvalues] 

495 else: 

496 ids = [mini_idvalset(argnames, vals, i) for i, vals in enumerate(unmarked_argvalues)] 

497 

498 _create_param_fixture(new_fixture_host, argname=p_fix_name, argvalues=argvalues, ids=ids, 

499 scope=scope, hook=hook, debug=debug) 

500 

501 # Create the corresponding alternative 

502 # note: as opposed to SingleParamAlternative, no need to move the custom id/marks to the ParamAlternative 

503 # since they are set on the created parametrized fixture above 

504 return MultiParamAlternative(union_name=param_union_name, alternative_name=p_fix_name, argnames=argnames, 

505 param_index_from=from_i, param_index_to=to_i, decorated=test_func) 

506 

507 

508class FixtureParamAlternative(SingleParamAlternative): 

509 """alternative class for a single parameter containing a fixture ref""" 

510 

511 def __init__(self, 

512 union_name, # type: str 

513 fixture_ref, # type: fixture_ref 

514 argnames, # type: Sequence[str] 

515 param_index, # type: int 

516 id, # type: Optional[str] 

517 decorated # type: Callable 

518 ): 

519 """ 

520 :param union_name: the name of the union fixture created by @parametrize to switch between param alternatives 

521 :param param_index: the index of this parameter in the list of argvalues passed to @parametrize 

522 :param argnames: the list of parameter names in @parametrize 

523 :param fixture_ref: the fixture reference used in this alternative 

524 """ 

525 # set alternative_name using the fixture name in fixture_ref 

526 super(FixtureParamAlternative, self).__init__(union_name=union_name, 

527 alternative_name=fixture_ref.fixture, 

528 argnames=argnames, param_index=param_index, 

529 argval=fixture_ref, id=id, decorated=decorated) 

530 

531 def get_alternative_idx(self): 

532 return "P%sF" % self.alternative_index 

533 

534 def get_alternative_id(self): 

535 if self.id is not None: 

536 # custom id from `@parametrize(ids=<callable_or_list>)` 

537 return self.id 

538 else: 

539 # ask the fixture_ref for an id: it can be the fixture name or a custom id 

540 return self.argval.get_name_for_id() 

541 

542 

543class ProductParamAlternative(SingleParamAlternative): 

544 """alternative class for a single product parameter containing fixture refs""" 

545 

546 def get_alternative_idx(self): 

547 return "P%sF" % self.alternative_index 

548 

549 def get_alternative_id(self): 

550 """Similar to SingleParamAlternative: create an id representing this tuple, since the fixture won't be 

551 parametrized""" 

552 if self.id is not None: 552 ↛ 554line 552 didn't jump to line 554, because the condition on line 552 was never true

553 # custom id from `@parametrize(ids=<callable_or_list>)` 

554 return self.id 

555 else: 

556 argval = tuple(t if not robust_isinstance(t, fixture_ref) else t.get_name_for_id() for t in self.argval) 

557 return mini_idvalset(self.argnames, argval, idx=self.alternative_index) 

558 

559 

560# if PYTEST54_OR_GREATER: 

561# # an empty string will be taken into account but NOT filtered out in CallSpec2.id. 

562# # so instead we create a dedicated unique string and return it. 

563# # Ugly but the only viable alternative seems worse: it would be to return an empty string 

564# # and in `remove_empty_ids` to always remove all empty strings (not necessary the ones set by us). 

565# # That is too much of a change. 

566 

567EMPTY_ID = "<pytest_cases_empty_id>" 

568 

569 

570if has_pytest_param: 570 ↛ 575line 570 didn't jump to line 575, because the condition on line 570 was never false

571 def remove_empty_ids(callspec): 

572 # used by plugin.py to remove the EMPTY_ID from the callspecs 

573 replace_list_contents(callspec._idlist, [c for c in callspec._idlist if not c.startswith(EMPTY_ID)]) 

574else: 

575 def remove_empty_ids(callspec): 

576 # used by plugin.py to remove the EMPTY_ID from the callspecs 

577 replace_list_contents(callspec._idlist, [c for c in callspec._idlist if not c.endswith(EMPTY_ID)]) 

578 

579 

580# elif PYTEST421_OR_GREATER: 

581# # an empty string will be taken into account and filtered out in CallSpec2.id. 

582# # but.... if this empty string appears several times in the tests it is appended with a number to become unique :( 

583# EMPTY_ID = "" 

584# 

585# else: 

586# # an empty string will only be taken into account if its truth value is True 

587# # but.... if this empty string appears several times in the tests it is appended with a number to become unique :( 

588# # it will be filtered out in CallSpec2.id 

589# class EmptyId(str): 

590# def __new__(cls): 

591# return str.__new__(cls, "") 

592# 

593# def __nonzero__(self): 

594# # python 2 

595# return True 

596# 

597# def __bool__(self): 

598# # python 3 

599# return True 

600# 

601# EMPTY_ID = EmptyId() 

602 

603 

604class ParamIdMakers(UnionIdMakers): 

605 """ 'Enum' of id styles for param ids 

606 

607 It extends UnionIdMakers to adapt to the special fixture alternatives `ParamAlternative` we create 

608 in @parametrize 

609 """ 

610 @classmethod 

611 def nostyle(cls, 

612 param # type: ParamAlternative 

613 ): 

614 if isinstance(param, MultiParamAlternative): 

615 # make an empty minimal id since the parameter themselves will appear as ids separately 

616 # note if the final id is empty it will be dropped by the filter in CallSpec2.id 

617 return EMPTY_ID 

618 else: 

619 return UnionIdMakers.nostyle(param) 

620 

621 # @classmethod 

622 # def explicit(cls, 

623 # param # type: ParamAlternative 

624 # ): 

625 # """Same than parent but display the argnames as prefix instead of the fixture union name generated by 

626 # @parametrize, because the latter is too complex (for unicity reasons)""" 

627 # return "%s/%s" % (, param.get_id(prepend_index=True)) 

628 

629 

630_IDGEN = object() 

631 

632 

633def parametrize(argnames=None, # type: Union[str, Tuple[str], List[str]] 

634 argvalues=None, # type: Iterable[Any] 

635 indirect=False, # type: bool 

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

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

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

639 auto_refs=True, # type: bool 

640 scope=None, # type: str 

641 hook=None, # type: Callable[[Callable], Callable] 

642 debug=False, # type: bool 

643 **args): 

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

645 """ 

646 Equivalent to `@pytest.mark.parametrize` but also supports 

647 

648 (1) new alternate style for argnames/argvalues. One can also use `**args` to pass additional `{argnames: argvalues}` 

649 in the same parametrization call. This can be handy in combination with `idgen` to master the whole id template 

650 associated with several parameters. Note that you can pass coma-separated argnames too, by de-referencing a dict: 

651 e.g. `**{'a,b': [(0, True), (1, False)], 'c': [-1, 2]}`. 

652 

653 (2) new alternate style for ids. One can use `idgen` instead of `ids`. `idgen` can be a callable receiving all 

654 parameters at once (`**args`) and returning an id ; or it can be a string template using the new-style string 

655 formatting where the argnames can be used as variables (e.g. `idgen=lambda **args: "a={a}".format(**args)` or 

656 `idgen="my_id where a={a}"`). The special `idgen=AUTO` symbol can be used to generate a default string template 

657 equivalent to `lambda **args: "-".join("%s=%s" % (n, v) for n, v in args.items())`. This is enabled by default 

658 if you use the alternate style for argnames/argvalues (e.g. if `len(args) > 0`), and if there are no `fixture_ref`s 

659 in your argvalues. 

660 

661 (3) new possibilities in argvalues: 

662 

663 - one can include references to fixtures with `fixture_ref(<fixture>)` where <fixture> can be the fixture name or 

664 fixture function. When such a fixture reference is detected in the argvalues, a new function-scope "union" 

665 fixture will be created with a unique name, and the test function will be wrapped so as to be injected with the 

666 correct parameters from this fixture. Special test ids will be created to illustrate the switching between the 

667 various normal parameters and fixtures. You can see debug print messages about all fixtures created using 

668 `debug=True` 

669 

670 - one can include lazy argvalues with `lazy_value(<valuegetter>, [id=..., marks=...])`. A `lazy_value` is the same 

671 thing than a function-scoped fixture, except that the value getter function is not a fixture and therefore can 

672 neither be parametrized nor depend on fixtures. It should have no mandatory argument. 

673 

674 Both `fixture_ref` and `lazy_value` can be used to represent a single argvalue, or a whole tuple of argvalues when 

675 there are several argnames. Several of them can be used in a tuple. 

676 

677 Finally, `pytest.param` is supported even when there are `fixture_ref` and `lazy_value`. 

678 

679 An optional `hook` can be passed, to apply on each fixture function that is created during this call. The hook 

680 function will be called every time a fixture is about to be created. It will receive a single argument (the 

681 function implementing the fixture) and should return the function to use. For example you can use `saved_fixture` 

682 from `pytest-harvest` as a hook in order to save all such created fixtures in the fixture store. 

683 

684 :param argnames: same as in pytest.mark.parametrize 

685 :param argvalues: same as in pytest.mark.parametrize except that `fixture_ref` and `lazy_value` are supported 

686 :param indirect: same as in pytest.mark.parametrize. Note that it is not recommended and is not guaranteed to work 

687 in complex parametrization scenarii. 

688 :param ids: same as in pytest.mark.parametrize. Note that an alternative way to create ids exists with `idgen`. Only 

689 one non-None `ids` or `idgen should be provided. 

690 :param idgen: an id formatter. Either a string representing a template, or a callable receiving all argvalues 

691 at once (as opposed to the behaviour in pytest ids). This alternative way to generate ids can only be used when 

692 `ids` is not provided (None). You can use the special `AUTO` formatter to generate an automatic id with 

693 template <name>=<value>-<name2>=<value2>-etc. `AUTO` is enabled by default if you use the alternate style for 

694 argnames/argvalues (e.g. if `len(args) > 0`), and if there are no `fixture_ref`s in your argvalues. 

695 :param auto_refs: a boolean. If this is `True` (default), argvalues containing fixture symbols will automatically 

696 be wrapped into a `fixture_ref`, for convenience. 

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

698 `@parametrize` if at least one `fixture_ref` is found in the argvalues. `idstyle` possible values are 

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

700 are present in the argvalues. As opposed to `ids`, a callable provided here will receive a `ParamAlternative` 

701 object indicating which generated fixture should be used. See `ParamIdMakers`. 

702 :param scope: The scope of the union fixture to create if `fixture_ref`s are found in the argvalues. Otherwise same 

703 as in pytest.mark.parametrize. 

704 :param hook: an optional hook to apply to each fixture function that is created during this call. The hook function 

705 will be called every time a fixture is about to be created. It will receive a single argument (the function 

706 implementing the fixture) and should return the function to use. For example you can use `saved_fixture` from 

707 `pytest-harvest` as a hook in order to save all such created fixtures in the fixture store. 

708 :param debug: print debug messages on stdout to analyze fixture creation (use pytest -s to see them) 

709 :param args: additional {argnames: argvalues} definition 

710 :return: 

711 """ 

712 _decorate, needs_inject = _parametrize_plus(argnames, argvalues, indirect=indirect, ids=ids, idgen=idgen, 

713 auto_refs=auto_refs, idstyle=idstyle, scope=scope, 

714 hook=hook, debug=debug, **args) 

715 if needs_inject: 

716 @inject_host 

717 def _apply_parametrize_plus(f, host_class_or_module): 

718 return _decorate(f, host_class_or_module) 

719 return _apply_parametrize_plus 

720 else: 

721 return _decorate 

722 

723 

724class InvalidIdTemplateException(Exception): 

725 """ 

726 Raised when a string template provided in an `idgen` raises an error 

727 """ 

728 def __init__(self, idgen, params, caught): 

729 self.idgen = idgen 

730 self.params = params 

731 self.caught = caught 

732 super(InvalidIdTemplateException, self).__init__() 

733 

734 def __str__(self): 

735 return repr(self) 

736 

737 def __repr__(self): 

738 return "Error generating test id using name template '%s' with parameter values " \ 

739 "%r. Please check the name template. Caught: %s - %s" \ 

740 % (self.idgen, self.params, self.caught.__class__, self.caught) 

741 

742 

743def _parametrize_plus(argnames=None, # type: Union[str, Tuple[str], List[str]] 

744 argvalues=None, # type: Iterable[Any] 

745 indirect=False, # type: bool 

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

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

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

749 auto_refs=True, # type: bool 

750 scope=None, # type: str 

751 hook=None, # type: Callable[[Callable], Callable] 

752 debug=False, # type: bool 

753 **args): 

754 # type: (...) -> Tuple[Callable[[T], T], bool] 

755 """ 

756 

757 :return: a tuple (decorator, needs_inject) where needs_inject is True if decorator has signature (f, host) 

758 and False if decorator has signature (f) 

759 """ 

760 # first handle argnames / argvalues (new modes of input) 

761 argnames, argvalues = _get_argnames_argvalues(argnames, argvalues, **args) 

762 

763 # argnames related 

764 initial_argnames = ','.join(argnames) 

765 nb_params = len(argnames) 

766 

767 # extract all marks and custom ids. 

768 # Do not check consistency of sizes argname/argvalue as a fixture_ref can stand for several argvalues. 

769 marked_argvalues = argvalues 

770 has_cust_ids = (idgen is not _IDGEN or len(args) > 0) or (ids is not None) 

771 p_ids, p_marks, argvalues, fixture_indices, mod_lvid_indices = \ 

772 _process_argvalues(argnames, marked_argvalues, nb_params, has_cust_ids, auto_refs=auto_refs) 

773 

774 # idgen default 

775 if idgen is _IDGEN: 

776 # default: use the new id style only when some keyword **args are provided and there are no fixture refs 

777 idgen = AUTO if (len(args) > 0 and len(fixture_indices) == 0 and ids is None) else None 

778 

779 if idgen is AUTO: 

780 # note: we use a "trick" here with mini_idval to get the appropriate result (argname='', idx=v) 

781 def _make_ids(**args): 

782 for n, v in args.items(): 

783 yield "%s=%s" % (n, mini_idval(val=v, argname='', idx=v)) 

784 

785 idgen = lambda **args: "-".join(_make_ids(**args)) # noqa 

786 

787 # generate id 

788 if idgen is not None: 

789 if ids is not None: 789 ↛ 790line 789 didn't jump to line 790, because the condition on line 789 was never true

790 raise ValueError("Only one of `ids` and `idgen` should be provided") 

791 ids = _gen_ids(argnames, argvalues, idgen) 

792 

793 if len(fixture_indices) == 0: 

794 # No fixture reference: fallback to a standard pytest.mark.parametrize 

795 if debug: 795 ↛ 796line 795 didn't jump to line 796, because the condition on line 795 was never true

796 print("No fixture reference found. Calling @pytest.mark.parametrize...") 

797 print(" - argnames: %s" % initial_argnames) 

798 print(" - argvalues: %s" % marked_argvalues) 

799 print(" - ids: %s" % ids) 

800 

801 # handle infinite iterables like latest pytest, for convenience 

802 ids = resolve_ids(ids, marked_argvalues, full_resolve=False) 

803 

804 # no fixture reference: shortcut, do as usual (note that the hook won't be called since no fixture is created) 

805 _decorator = pytest.mark.parametrize(initial_argnames, marked_argvalues, indirect=indirect, 

806 ids=ids, scope=scope) 

807 if indirect: 

808 return _decorator, False 

809 else: 

810 # wrap the decorator to check if the test function has the parameters as arguments 

811 def _apply(test_func): 

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

813 if not safe_isclass(test_func): 

814 # a Function: raise a proper error message if improper use 

815 s = signature(test_func) 

816 for p in argnames: 

817 if p not in s.parameters: 

818 raise ValueError("parameter '%s' not found in test function signature '%s%s'" 

819 "" % (p, test_func.__name__, s)) 

820 else: 

821 # a Class: we cannot really perform any check. 

822 pass 

823 return _decorator(test_func) 

824 

825 return _apply, False 

826 

827 else: 

828 # there are fixture references: we will create a specific decorator replacing the params with a "union" fixture 

829 if indirect: 829 ↛ 830line 829 didn't jump to line 830, because the condition on line 829 was never true

830 warn("Using `indirect=True` at the same time as fixture references in `@parametrize` is not guaranteed to " 

831 "work and is strongly discouraged for readability reasons. See " 

832 "https://github.com/smarie/python-pytest-cases/issues/150") 

833 

834 # First unset the pytest.param id we have set earlier in _process_argvalues: indeed it is only needed in 

835 # the case above where we were defaulting to legacy @pytest.mark.parametrize . 

836 # Here we have fixture refs so we will create a fixture union with several ParamAlternative, and their id will 

837 # anyway be generated with `mini_idvalset` which tackles the case of lazy_value used for a tuple of args 

838 for i in mod_lvid_indices: 

839 p_ids[i] = None 

840 if p_marks[i]: 

841 marked_argvalues[i] = ParameterSet(values=marked_argvalues[i].values, id=None, marks=p_marks[i]) 

842 else: 

843 marked_argvalues[i] = argvalues[i] # we can even remove the pytest.param wrapper 

844 

845 if indirect: 845 ↛ 846line 845 didn't jump to line 846, because the condition on line 845 was never true

846 raise ValueError("Setting `indirect=True` is not yet supported when at least a `fixure_ref` is present in " 

847 "the `argvalues`.") 

848 

849 if debug: 

850 print("Fixture references found. Creating references and fixtures...") 

851 

852 param_names_str = '_'.join(argnames).replace(' ', '') 

853 

854 # Are there explicit ids provided ? 

855 explicit_ids_to_use = False 

856 ids = resolve_ids(ids, argvalues, full_resolve=False) 

857 if isinstance(ids, list): 

858 explicit_ids_to_use = True 

859 

860 # First define a few functions that will help us create the various fixtures to use in the final "union" 

861 

862 def _create_params_alt(fh, test_func, union_name, from_i, to_i, hook): # noqa 

863 """ Routine that will be used to create a parameter fixture for argvalues between prev_i and i""" 

864 

865 # is this about a single value or several values ? 

866 if to_i == from_i + 1: 

867 i = from_i 

868 del from_i 

869 

870 # If an explicit list of ids was provided, slice it. Otherwise use the provided callable 

871 if ids is not None: 

872 _id = ids[i] if explicit_ids_to_use else ids(argvalues[i]) 

873 else: 

874 _id = None 

875 

876 return SingleParamAlternative.create(new_fixture_host=fh, test_func=test_func, 

877 param_union_name=union_name, argnames=argnames, i=i, 

878 argvalue=marked_argvalues[i], id=_id, 

879 scope="function" if scope is None else scope, 

880 hook=hook, debug=debug) 

881 else: 

882 # If an explicit list of ids was provided, slice it. Otherwise the provided callable will be used later 

883 _ids = ids[from_i:to_i] if explicit_ids_to_use else ids 

884 

885 return MultiParamAlternative.create(new_fixture_host=fh, test_func=test_func, 

886 param_union_name=union_name, argnames=argnames, from_i=from_i, 

887 to_i=to_i, argvalues=marked_argvalues[from_i:to_i], ids=_ids, 

888 scope="function" if scope is None else scope, 

889 hook=hook, debug=debug) 

890 

891 

892 def _create_fixture_ref_alt(union_name, test_func, i): # noqa 

893 

894 # If an explicit list of ids was provided, slice it. Otherwise use the provided callable 

895 if ids is not None: 

896 _id = ids[i] if explicit_ids_to_use else ids(argvalues[i]) 

897 else: 

898 _id = None 

899 

900 # Get the referenced fixture name 

901 f_fix_name = argvalues[i].fixture 

902 

903 if debug: 

904 print(" - Creating reference to existing fixture %r" % (f_fix_name,)) 

905 

906 # Create the alternative 

907 f_fix_alt = FixtureParamAlternative(union_name=union_name, fixture_ref=argvalues[i], 

908 decorated=test_func, argnames=argnames, param_index=i, id=_id) 

909 # Finally copy the custom id/marks on the FixtureParamAlternative if any 

910 if is_marked_parameter_value(marked_argvalues[i]): 

911 f_fix_alt = ParameterSet(values=(f_fix_alt,), 

912 id=get_marked_parameter_id(marked_argvalues[i]), 

913 marks=get_marked_parameter_marks(marked_argvalues[i])) 

914 

915 return f_fix_alt 

916 

917 def _create_fixture_ref_product(fh, union_name, i, fixture_ref_positions, test_func, hook): # noqa 

918 

919 # If an explicit list of ids was provided, slice it. Otherwise the provided callable will be used 

920 _id = ids[i] if explicit_ids_to_use else ids 

921 

922 # values to use: 

923 param_values = argvalues[i] 

924 

925 # Create a unique fixture name 

926 p_fix_name = "%s_%s_P%s" % (test_func.__name__, param_names_str, i) 

927 p_fix_name = check_name_available(fh, p_fix_name, if_name_exists=CHANGE, caller=parametrize) 

928 

929 if debug: 

930 print(" - Creating new fixture %r to handle parameter %s that is a cross-product" % (p_fix_name, i)) 

931 

932 # Create the fixture 

933 _make_fixture_product(fh, name=p_fix_name, hook=hook, caller=parametrize, 

934 fixtures_or_values=param_values, fixture_positions=fixture_ref_positions) 

935 

936 # Create the corresponding alternative 

937 p_fix_alt = ProductParamAlternative(union_name=union_name, alternative_name=p_fix_name, decorated=test_func, 

938 argval=argvalues[i], argnames=argnames, param_index=i, id=_id) 

939 # copy the custom id/marks to the ParamAlternative if any 

940 if is_marked_parameter_value(marked_argvalues[i]): 

941 p_fix_alt = ParameterSet(values=(p_fix_alt,), 

942 id=get_marked_parameter_id(marked_argvalues[i]), 

943 marks=get_marked_parameter_marks(marked_argvalues[i])) 

944 return p_fix_alt 

945 

946 # Then create the decorator per se 

947 def parametrize_plus_decorate(test_func, fixtures_dest): 

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

949 """ 

950 A decorator that wraps the test function so that instead of receiving the parameter names, it receives the 

951 new fixture. All other decorations are unchanged. 

952 

953 :param test_func: 

954 :return: 

955 """ 

956 test_func_name = test_func.__name__ 

957 

958 # first check if the test function has the parameters as arguments 

959 if safe_isclass(test_func): 

960 # a test class: not supported yet 

961 raise NotImplementedError("@parametrize can not be used to decorate a Test class when the argvalues " 

962 "contain at least one reference to a fixture.") 

963 

964 old_sig = signature(test_func) 

965 for p in argnames: 

966 if p not in old_sig.parameters: 966 ↛ 967line 966 didn't jump to line 967, because the condition on line 966 was never true

967 raise ValueError("parameter '%s' not found in test function signature '%s%s'" 

968 "" % (p, test_func_name, old_sig)) 

969 

970 # The name for the final "union" fixture 

971 # style_template = "%s_param__%s" 

972 main_fixture_style_template = "%s_%s" 

973 fixture_union_name = main_fixture_style_template % (test_func_name, param_names_str) 

974 fixture_union_name = check_name_available(fixtures_dest, fixture_union_name, if_name_exists=CHANGE, 

975 caller=parametrize) 

976 

977 # Retrieve (if ref) or create (for normal argvalues) the fixtures that we will union 

978 fixture_alternatives = [] 

979 prev_i = -1 

980 for i, j_list in fixture_indices: # noqa 

981 # A/ Is there any non-empty group of 'normal' parameters before the fixture_ref at <i> ? If so, handle. 

982 if i > prev_i + 1: 

983 # create a new "param" fixture parametrized with all of that consecutive group. 

984 # Important note: we could either wish to create one fixture for parameter value or to create 

985 # one for each consecutive group as shown below. This should not lead to different results but perf 

986 # might differ. Maybe add a parameter in the signature so that users can test it ? 

987 # this would make the ids more readable by removing the "P2toP3"-like ids 

988 p_fix_alt = _create_params_alt(fixtures_dest, test_func=test_func, hook=hook, 

989 union_name=fixture_union_name, from_i=prev_i + 1, to_i=i) 

990 fixture_alternatives.append(p_fix_alt) 

991 

992 # B/ Now handle the fixture ref at position <i> 

993 if j_list is None: 

994 # argvalues[i] contains a single argvalue that is a fixture_ref : add the referenced fixture 

995 f_fix_alt = _create_fixture_ref_alt(union_name=fixture_union_name, test_func=test_func, i=i) 

996 fixture_alternatives.append(f_fix_alt) 

997 else: 

998 # argvalues[i] is a tuple, some of them being fixture_ref. create a fixture referring to all of them 

999 prod_fix_alt = _create_fixture_ref_product(fixtures_dest, union_name=fixture_union_name, i=i, 

1000 fixture_ref_positions=j_list, 

1001 test_func=test_func, hook=hook) 

1002 fixture_alternatives.append(prod_fix_alt) 

1003 

1004 prev_i = i 

1005 

1006 # C/ handle last consecutive group of normal parameters, if any 

1007 i = len(argvalues) # noqa 

1008 if i > prev_i + 1: 

1009 p_fix_alt = _create_params_alt(fixtures_dest, test_func=test_func, hook=hook, 

1010 union_name=fixture_union_name, from_i=prev_i + 1, to_i=i) 

1011 fixture_alternatives.append(p_fix_alt) 

1012 

1013 # if fixtures_to_union has length 1, simplify ? >> No, we leave such "optimization" to the end user 

1014 

1015 # Handle the list of alternative names. Duplicates should be removed here 

1016 fix_alt_names = [] 

1017 for alt in fixture_alternatives: 

1018 if is_marked_parameter_value(alt): 

1019 # wrapped by a pytest.param 

1020 alt = get_marked_parameter_values(alt, nbargs=1) 

1021 assert len(alt) == 1, "Error with alternative please report" 

1022 alt = alt[0] 

1023 if alt.alternative_name not in fix_alt_names: 

1024 fix_alt_names.append(alt.alternative_name) 

1025 else: 

1026 # non-unique alt fixture names should only happen when the alternative is a fixture reference 

1027 assert isinstance(alt, FixtureParamAlternative), "Created fixture names not unique, please report" 

1028 

1029 # Finally create a "main" fixture with a unique name for this test function 

1030 if debug: 

1031 print("Creating final union fixture %r with alternatives %r" 

1032 % (fixture_union_name, UnionFixtureAlternative.to_list_of_fixture_names(fixture_alternatives))) 

1033 

1034 # use the custom subclass of idstyle that was created for ParamAlternatives 

1035 if idstyle is None or isinstance(idstyle, string_types): 

1036 _idstyle = ParamIdMakers.get(idstyle) 

1037 else: 

1038 _idstyle = idstyle 

1039 

1040 # note: the function automatically registers it in the module 

1041 _make_fixture_union(fixtures_dest, name=fixture_union_name, hook=hook, caller=parametrize, 

1042 fix_alternatives=fixture_alternatives, unique_fix_alt_names=fix_alt_names, 

1043 idstyle=_idstyle, scope=scope) 

1044 

1045 # --create the new test function's signature that we want to expose to pytest 

1046 # it is the same than existing, except that we want to replace all parameters with the new fixture 

1047 # first check where we should insert the new parameters (where is the first param we remove) 

1048 _first_idx = -1 

1049 for _first_idx, _n in enumerate(old_sig.parameters): 1049 ↛ 1053line 1049 didn't jump to line 1053, because the loop on line 1049 didn't complete

1050 if _n in argnames: 

1051 break 

1052 # then remove all parameters that will be replaced by the new fixture 

1053 new_sig = remove_signature_parameters(old_sig, *argnames) 

1054 # finally insert the new fixture in that position. Indeed we can not insert first or last, because 

1055 # 'self' arg (case of test class methods) should stay first and exec order should be preserved when possible 

1056 new_sig = add_signature_parameters(new_sig, custom_idx=_first_idx, 

1057 custom=Parameter(fixture_union_name, 

1058 kind=Parameter.POSITIONAL_OR_KEYWORD)) 

1059 

1060 if debug: 

1061 print("Creating final test function wrapper with signature %s%s" % (test_func_name, new_sig)) 

1062 

1063 # --Finally create the fixture function, a wrapper of user-provided fixture with the new signature 

1064 def replace_paramfixture_with_values(kwargs): # noqa 

1065 # remove the created fixture value 

1066 encompassing_fixture = kwargs.pop(fixture_union_name) 

1067 # and add instead the parameter values 

1068 if nb_params > 1: 

1069 for i, p in enumerate(argnames): # noqa 

1070 try: 

1071 kwargs[p] = encompassing_fixture[i] 

1072 except TypeError: 

1073 raise Exception("Unable to unpack parameter value to a tuple: %r" % encompassing_fixture) 

1074 else: 

1075 kwargs[argnames[0]] = encompassing_fixture 

1076 # return 

1077 return kwargs 

1078 

1079 

1080 if isasyncgenfunction(test_func)and sys.version_info >= (3, 6): 1080 ↛ 1081line 1080 didn't jump to line 1081, because the condition on line 1080 was never true

1081 from .pep525 import _parametrize_plus_decorate_asyncgen_pep525 

1082 wrapped_test_func = _parametrize_plus_decorate_asyncgen_pep525(test_func, new_sig, fixture_union_name, 

1083 replace_paramfixture_with_values) 

1084 elif iscoroutinefunction(test_func) and sys.version_info >= (3, 5): 

1085 from .pep492 import _parametrize_plus_decorate_coroutine_pep492 

1086 wrapped_test_func = _parametrize_plus_decorate_coroutine_pep492(test_func, new_sig, fixture_union_name, 

1087 replace_paramfixture_with_values) 

1088 elif isgeneratorfunction(test_func): 

1089 # generator function (with a yield statement) 

1090 if sys.version_info >= (3, 3): 1090 ↛ 1096line 1090 didn't jump to line 1096, because the condition on line 1090 was never false

1091 from .pep380 import _parametrize_plus_decorate_generator_pep380 

1092 wrapped_test_func = _parametrize_plus_decorate_generator_pep380(test_func, new_sig, 

1093 fixture_union_name, 

1094 replace_paramfixture_with_values) 

1095 else: 

1096 @wraps(test_func, new_sig=new_sig) 

1097 def wrapped_test_func(*args, **kwargs): # noqa 

1098 if kwargs.get(fixture_union_name, None) is NOT_USED: 

1099 # TODO why this ? it is probably useless: this fixture 

1100 # is private and will never end up in another union 

1101 yield NOT_USED 

1102 else: 

1103 replace_paramfixture_with_values(kwargs) 

1104 for res in test_func(*args, **kwargs): 

1105 yield res 

1106 else: 

1107 # normal function with return statement 

1108 @wraps(test_func, new_sig=new_sig) 

1109 def wrapped_test_func(*args, **kwargs): # noqa 

1110 if kwargs.get(fixture_union_name, None) is NOT_USED: 1110 ↛ 1113line 1110 didn't jump to line 1113, because the condition on line 1110 was never true

1111 # TODO why this ? it is probably useless: this fixture 

1112 # is private and will never end up in another union 

1113 return NOT_USED 

1114 else: 

1115 replace_paramfixture_with_values(kwargs) 

1116 return test_func(*args, **kwargs) 

1117 

1118 # move all pytest marks from the test function to the wrapper 

1119 # not needed because the __dict__ is automatically copied when we use @wraps 

1120 # move_all_pytest_marks(test_func, wrapped_test_func) 

1121 

1122 # With this hack we will be ordered correctly by pytest https://github.com/pytest-dev/pytest/issues/4429 

1123 try: 

1124 # propagate existing attribute if any 

1125 wrapped_test_func.place_as = test_func.place_as 

1126 except: # noqa 

1127 # position the test at the original function's position 

1128 wrapped_test_func.place_as = test_func 

1129 

1130 # return the new test function 

1131 return wrapped_test_func 

1132 

1133 return parametrize_plus_decorate, True 

1134 

1135 

1136def _get_argnames_argvalues( 

1137 argnames=None, # type: Union[str, Tuple[str], List[str]] 

1138 argvalues=None, # type: Iterable[Any] 

1139 **args 

1140): 

1141 """ 

1142 

1143 :param argnames: 

1144 :param argvalues: 

1145 :param args: 

1146 :return: argnames, argvalues - both guaranteed to be lists 

1147 """ 

1148 # handle **args - a dict of {argnames: argvalues} 

1149 if len(args) > 0: 

1150 kw_argnames, kw_argvalues = cart_product_pytest(tuple(args.keys()), tuple(args.values())) 

1151 else: 

1152 kw_argnames, kw_argvalues = (), () 

1153 

1154 if argnames is None: 

1155 # (1) all {argnames: argvalues} pairs are provided in **args 

1156 if argvalues is not None or len(args) == 0: 1156 ↛ 1157line 1156 didn't jump to line 1157, because the condition on line 1156 was never true

1157 raise ValueError("No parameters provided") 

1158 

1159 argnames = kw_argnames 

1160 argvalues = kw_argvalues 

1161 # simplify if needed to comply with pytest.mark.parametrize 

1162 if len(argnames) == 1: 

1163 argvalues = [_l[0] if not is_marked_parameter_value(_l) else _l for _l in argvalues] 

1164 return argnames, argvalues 

1165 

1166 if isinstance(argnames, string_types): 

1167 # (2) argnames + argvalues, as usual. However **args can also be passed and should be added 

1168 argnames = get_param_argnames_as_list(argnames) 

1169 

1170 if not isinstance(argnames, (list, tuple)): 

1171 raise TypeError("argnames should be a string, list or a tuple") 

1172 

1173 if any([not isinstance(argname, str) for argname in argnames]): 

1174 raise TypeError("all argnames should be strings") 

1175 

1176 if argvalues is None: 1176 ↛ 1177line 1176 didn't jump to line 1177, because the condition on line 1176 was never true

1177 raise ValueError("No argvalues provided while argnames are provided") 

1178 

1179 # transform argvalues to a list (it can be a generator) 

1180 try: 

1181 argvalues = list(argvalues) 

1182 except TypeError: 

1183 raise InvalidParamsList(argvalues) 

1184 

1185 # append **args 

1186 if len(kw_argnames) > 0: 

1187 argnames, argvalues = cart_product_pytest((argnames, kw_argnames), 

1188 (argvalues, kw_argvalues)) 

1189 

1190 return argnames, argvalues 

1191 

1192 

1193def _gen_ids(argnames, argvalues, idgen): 

1194 """ 

1195 Generates an explicit test ids list from a non-none `idgen`. 

1196 

1197 `idgen` should be either a callable of a string template. 

1198 

1199 :param argnames: 

1200 :param argvalues: 

1201 :param idgen: 

1202 :return: 

1203 """ 

1204 if not callable(idgen): 

1205 # idgen is a new-style string formatting template 

1206 if not isinstance(idgen, string_types): 1206 ↛ 1207line 1206 didn't jump to line 1207, because the condition on line 1206 was never true

1207 raise TypeError("idgen should be a callable or a string, found: %r" % idgen) 

1208 

1209 _formatter = idgen 

1210 

1211 def gen_id_using_str_formatter(**params): 

1212 try: 

1213 # format using the idgen template 

1214 return _formatter.format(**params) 

1215 except Exception as e: 

1216 raise InvalidIdTemplateException(_formatter, params, e) 

1217 

1218 idgen = gen_id_using_str_formatter 

1219 

1220 if len(argnames) > 1: 

1221 ids = [idgen(**{n: v for n, v in zip(argnames, _argvals)}) for _argvals in argvalues] 

1222 else: 

1223 _only_name = argnames[0] 

1224 ids = [idgen(**{_only_name: v}) for v in argvalues] 

1225 

1226 return ids 

1227 

1228 

1229def _process_argvalues(argnames, marked_argvalues, nb_params, has_custom_ids, auto_refs): 

1230 """Internal method to use in _pytest_parametrize_plus 

1231 

1232 Processes the provided marked_argvalues (possibly marked with pytest.param) and returns 

1233 p_ids, p_marks, argvalues (not marked with pytest.param), fixture_indices 

1234 

1235 Note: `marked_argvalues` is modified in the process if a `lazy_value` is found with a custom id or marks. 

1236 

1237 :param argnames: 

1238 :param marked_argvalues: 

1239 :param nb_params: 

1240 :param has_custom_ids: a boolean indicating if custom ids are provided separately in `ids` or `idgen` (see 

1241 @parametrize) 

1242 :param auto_refs: if True, a `fixture_ref` will be created around fixture symbols used as argvalues automatically 

1243 :return: 

1244 """ 

1245 p_ids, p_marks, argvalues = extract_parameterset_info(argnames, marked_argvalues, check_nb=False) 

1246 

1247 # find if there are fixture references or lazy values in the values provided 

1248 fixture_indices = [] 

1249 mod_lvid_indices = [] # indices of lazy_values for which we created a wrapper pytest.param with an id 

1250 if nb_params == 1: 

1251 for i, v in enumerate(argvalues): 

1252 if is_lazy_value(v): 

1253 # --- A lazy value is used for several parameters at the same time --- 

1254 # Users can declare custom marks in the lazy value API, we have to take these into account 

1255 # (1) if there was a pytest.param around it, we have to merge the marks from the lazy value into it 

1256 # (2) if there was no pytest.param around it and there are marks, we have to create the pytest.param 

1257 # Note: a custom id in lazy value does not require such processing as it does not need to take 

1258 # precedence over `ids` or `idgen` 

1259 

1260 # are there any marks ? (either added with lazy_value(marks=), or on the function itself) 

1261 _mks = v.get_marks(as_decorators=True) 

1262 if len(_mks) > 0: 

1263 # update/create the pytest.param marks on this value 

1264 p_marks[i] = (p_marks[i] + _mks) if p_marks[i] is not None else _mks 

1265 

1266 # update the original marked_argvalues. Note that argvalues[i] = v 

1267 marked_argvalues[i] = ParameterSet(values=(argvalues[i],), id=p_ids[i], marks=p_marks[i]) 

1268 else: 

1269 if auto_refs and is_fixture(v): 

1270 # auto create wrapper fixture_refs 

1271 argvalues[i] = v = fixture_ref(v) 

1272 if p_ids[i] is None and p_marks[i] is None: 

1273 marked_argvalues[i] = v 

1274 else: 

1275 marked_argvalues[i] = ParameterSet(values=(v,), id=p_ids[i], marks=p_marks[i]) 

1276 

1277 if isinstance(v, fixture_ref): 

1278 # Fix the referenced fixture length 

1279 v.theoretical_size = nb_params 

1280 fixture_indices.append((i, None)) 

1281 

1282 elif nb_params > 1: 1282 ↛ 1417line 1282 didn't jump to line 1417, because the condition on line 1282 was never false

1283 for i, v in enumerate(argvalues): 

1284 

1285 # A/ First analyze what is the case at hand 

1286 _lazyvalue_used_as_tuple = False 

1287 _fixtureref_used_as_tuple = False 

1288 if is_lazy_value(v): 

1289 _lazyvalue_used_as_tuple = True 

1290 else: 

1291 if auto_refs and is_fixture(v): 

1292 # auto create wrapper fixture_refs 

1293 argvalues[i] = v = fixture_ref(v) 

1294 if p_ids[i] is None and p_marks[i] is None: 1294 ↛ 1297line 1294 didn't jump to line 1297, because the condition on line 1294 was never false

1295 marked_argvalues[i] = v 

1296 else: 

1297 marked_argvalues[i] = ParameterSet(values=(v,), id=p_ids[i], marks=p_marks[i]) 

1298 

1299 if isinstance(v, fixture_ref): 

1300 # Fix the referenced fixture length 

1301 v.theoretical_size = nb_params 

1302 _fixtureref_used_as_tuple = True 

1303 elif len(v) == 1: 

1304 # same than above but it was in a pytest.param 

1305 if is_lazy_value(v[0]): 

1306 argvalues[i] = v = v[0] 

1307 _lazyvalue_used_as_tuple = True 

1308 else: 

1309 if auto_refs and is_fixture(v[0]): 

1310 # auto create wrapper fixture_refs 

1311 v = (fixture_ref(v[0]),) 

1312 

1313 if isinstance(v[0], fixture_ref): 1313 ↛ 1324line 1313 didn't jump to line 1324, because the condition on line 1313 was never false

1314 _fixtureref_used_as_tuple = True 

1315 argvalues[i] = v = v[0] 

1316 if p_ids[i] is None and p_marks[i] is None: 1316 ↛ 1317line 1316 didn't jump to line 1317, because the condition on line 1316 was never true

1317 marked_argvalues[i] = v 

1318 else: 

1319 marked_argvalues[i] = ParameterSet(values=(v,), id=p_ids[i], marks=p_marks[i]) 

1320 # Fix the referenced fixture length 

1321 v.theoretical_size = nb_params 

1322 

1323 # B/ Now process it 

1324 if _lazyvalue_used_as_tuple: 

1325 # --- A lazy value is used for several parameters at the same time --- 

1326 

1327 # Since users have the possibility in the lazy value API to declare a custom id or custom marks, 

1328 # we have to take these into account. 

1329 # MARKS: 

1330 # (1) if there was a pytest.param around it, we have to merge the marks from the lazy value into it 

1331 # (2) if there was no pytest.param around it and there are marks, we have to create the pytest.param 

1332 # IDS: 

1333 # As opposed to the case of nb_params=1, we can not let pytest generate the id as it would create a 

1334 # tuple of LazyTupleItem ids (e.g. <id>[0]-<id>[1]-...). So 

1335 # (1) if there is a custom id list or generator, do not care about this. 

1336 # (2) if there is a pytest.param with a custom id, do not care about this 

1337 # (3) if there is nothing OR if there is a pytest.param with no id, we should create a pytest.param with 

1338 # the id. 

1339 

1340 # in this particular case we have to modify the initial list 

1341 argvalues[i] = v.as_lazy_tuple(nb_params) 

1342 

1343 # TUPLE usage: if the id is not provided elsewhere we HAVE to set an id to avoid <id>[0]-<id>[1]... 

1344 if p_ids[i] is None and not has_custom_ids: 

1345 if not has_pytest_param: 1345 ↛ 1346line 1345 didn't jump to line 1346, because the condition on line 1345 was never true

1346 if v._id is not None: 

1347 # (on pytest 2 we cannot do it since pytest.param does not exist) 

1348 warn("The custom id %r in `lazy_value` will be ignored as this version of pytest is too old" 

1349 " to support `pytest.param`." % v._id) 

1350 else: 

1351 pass # no warning, but no p_id update 

1352 else: 

1353 # update/create the pytest.param id on this value 

1354 p_ids[i] = v.get_id() 

1355 mod_lvid_indices.append(i) 

1356 

1357 # handle marks 

1358 _mks = v.get_marks(as_decorators=True) 

1359 if len(_mks) > 0: 

1360 # update/create the pytest.param marks on this value 

1361 p_marks[i] = (p_marks[i] + _mks) if p_marks[i] is not None else _mks 

1362 

1363 # update the marked_argvalues 

1364 # - at least with the unpacked lazytuple if no pytest.param is there or needs to be created 

1365 # - with a pytest.param if one is needed 

1366 if p_ids[i] is None and p_marks[i] is None: 

1367 marked_argvalues[i] = argvalues[i] 

1368 else: 

1369 # note that here argvalues[i] IS a tuple-like so we do not create a tuple around it 

1370 marked_argvalues[i] = ParameterSet(values=argvalues[i], id=p_ids[i], marks=p_marks[i] or ()) 

1371 

1372 elif _fixtureref_used_as_tuple: 

1373 # a fixture ref is used for several parameters at the same time 

1374 fixture_indices.append((i, None)) 

1375 else: 

1376 # Tuple: check nb params for consistency 

1377 if len(v) != len(argnames): 1377 ↛ 1378line 1377 didn't jump to line 1378, because the condition on line 1377 was never true

1378 raise ValueError("Inconsistent number of values in pytest parametrize: %s items found while the " 

1379 "number of parameters is %s: %s." % (len(v), len(argnames), v)) 

1380 

1381 # let's dig into the tuple to check if there are fixture_refs or lazy_values 

1382 lv_pos_list = [j for j, _pval in enumerate(v) if is_lazy_value(_pval)] 

1383 if len(lv_pos_list) > 0: 

1384 _mks = [mk for _lv in lv_pos_list for mk in v[_lv].get_marks(as_decorators=True)] 

1385 if len(_mks) > 0: 

1386 # update/create the pytest.param marks on this value (global). (id not taken into account) 

1387 p_marks[i] = (list(p_marks[i]) + _mks) if p_marks[i] is not None else list(_mks) 

1388 marked_argvalues[i] = ParameterSet(values=argvalues[i], id=p_ids[i], marks=p_marks[i] or ()) 

1389 

1390 # auto create fixtures 

1391 if auto_refs: 

1392 autofix_pos_list = [j for j, _pval in enumerate(v) if is_fixture(_pval)] 

1393 if len(autofix_pos_list) > 0: 

1394 # there is at least one fixture without wrapping ref inside the tuple 

1395 autov = list(v) 

1396 for _k in autofix_pos_list: 

1397 autov[_k] = fixture_ref(autov[_k]) 

1398 argvalues[i] = v = tuple(autov) 

1399 if p_ids[i] is None and p_marks[i] is None: 

1400 marked_argvalues[i] = argvalues[i] 

1401 else: 

1402 # note that here argvalues[i] IS a tuple-like so we do not create a tuple around it 

1403 marked_argvalues[i] = ParameterSet(values=argvalues[i], id=p_ids[i], marks=p_marks[i] or ()) 

1404 

1405 fix_pos_list = [j for j, _pval in enumerate(v) if isinstance(_pval, fixture_ref)] 

1406 if len(fix_pos_list) > 0: 

1407 # there is at least one fixture ref inside the tuple 

1408 fixture_indices.append((i, fix_pos_list)) 

1409 

1410 # let's dig into the tuple 

1411 # has_val_ref = any(isinstance(_pval, lazy_value) for _pval in v) 

1412 # val_pos_list = [j for j, _pval in enumerate(v) if isinstance(_pval, lazy_value)] 

1413 # if len(val_pos_list) > 0: 

1414 # # there is at least one value ref inside the tuple 

1415 # argvalues[i] = tuple_with_value_refs(v, theoreticalsize=nb_params, positions=val_pos_list) 

1416 

1417 return p_ids, p_marks, argvalues, fixture_indices, mod_lvid_indices