Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/absl/testing/parameterized.py: 24%

185 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-03 07:57 +0000

1# Copyright 2017 The Abseil Authors. 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15"""Adds support for parameterized tests to Python's unittest TestCase class. 

16 

17A parameterized test is a method in a test case that is invoked with different 

18argument tuples. 

19 

20A simple example:: 

21 

22 class AdditionExample(parameterized.TestCase): 

23 @parameterized.parameters( 

24 (1, 2, 3), 

25 (4, 5, 9), 

26 (1, 1, 3)) 

27 def testAddition(self, op1, op2, result): 

28 self.assertEqual(result, op1 + op2) 

29 

30Each invocation is a separate test case and properly isolated just 

31like a normal test method, with its own setUp/tearDown cycle. In the 

32example above, there are three separate testcases, one of which will 

33fail due to an assertion error (1 + 1 != 3). 

34 

35Parameters for individual test cases can be tuples (with positional parameters) 

36or dictionaries (with named parameters):: 

37 

38 class AdditionExample(parameterized.TestCase): 

39 @parameterized.parameters( 

40 {'op1': 1, 'op2': 2, 'result': 3}, 

41 {'op1': 4, 'op2': 5, 'result': 9}, 

42 ) 

43 def testAddition(self, op1, op2, result): 

44 self.assertEqual(result, op1 + op2) 

45 

46If a parameterized test fails, the error message will show the 

47original test name and the parameters for that test. 

48 

49The id method of the test, used internally by the unittest framework, is also 

50modified to show the arguments (but note that the name reported by `id()` 

51doesn't match the actual test name, see below). To make sure that test names 

52stay the same across several invocations, object representations like:: 

53 

54 >>> class Foo(object): 

55 ... pass 

56 >>> repr(Foo()) 

57 '<__main__.Foo object at 0x23d8610>' 

58 

59are turned into ``__main__.Foo``. When selecting a subset of test cases to run 

60on the command-line, the test cases contain an index suffix for each argument 

61in the order they were passed to :func:`parameters` (eg. testAddition0, 

62testAddition1, etc.) This naming scheme is subject to change; for more reliable 

63and stable names, especially in test logs, use :func:`named_parameters` instead. 

64 

65Tests using :func:`named_parameters` are similar to :func:`parameters`, except 

66only tuples or dicts of args are supported. For tuples, the first parameter arg 

67has to be a string (or an object that returns an apt name when converted via 

68``str()``). For dicts, a value for the key ``testcase_name`` must be present and 

69must be a string (or an object that returns an apt name when converted via 

70``str()``):: 

71 

72 class NamedExample(parameterized.TestCase): 

73 @parameterized.named_parameters( 

74 ('Normal', 'aa', 'aaa', True), 

75 ('EmptyPrefix', '', 'abc', True), 

76 ('BothEmpty', '', '', True)) 

77 def testStartsWith(self, prefix, string, result): 

78 self.assertEqual(result, string.startswith(prefix)) 

79 

80 class NamedExample(parameterized.TestCase): 

81 @parameterized.named_parameters( 

82 {'testcase_name': 'Normal', 

83 'result': True, 'string': 'aaa', 'prefix': 'aa'}, 

84 {'testcase_name': 'EmptyPrefix', 

85 'result': True, 'string': 'abc', 'prefix': ''}, 

86 {'testcase_name': 'BothEmpty', 

87 'result': True, 'string': '', 'prefix': ''}) 

88 def testStartsWith(self, prefix, string, result): 

89 self.assertEqual(result, string.startswith(prefix)) 

90 

91Named tests also have the benefit that they can be run individually 

92from the command line:: 

93 

94 $ testmodule.py NamedExample.testStartsWithNormal 

95 . 

96 -------------------------------------------------------------------- 

97 Ran 1 test in 0.000s 

98 

99 OK 

100 

101Parameterized Classes 

102===================== 

103 

104If invocation arguments are shared across test methods in a single 

105TestCase class, instead of decorating all test methods 

106individually, the class itself can be decorated:: 

107 

108 @parameterized.parameters( 

109 (1, 2, 3), 

110 (4, 5, 9)) 

111 class ArithmeticTest(parameterized.TestCase): 

112 def testAdd(self, arg1, arg2, result): 

113 self.assertEqual(arg1 + arg2, result) 

114 

115 def testSubtract(self, arg1, arg2, result): 

116 self.assertEqual(result - arg1, arg2) 

117 

118Inputs from Iterables 

119===================== 

120 

121If parameters should be shared across several test cases, or are dynamically 

122created from other sources, a single non-tuple iterable can be passed into 

123the decorator. This iterable will be used to obtain the test cases:: 

124 

125 class AdditionExample(parameterized.TestCase): 

126 @parameterized.parameters( 

127 c.op1, c.op2, c.result for c in testcases 

128 ) 

129 def testAddition(self, op1, op2, result): 

130 self.assertEqual(result, op1 + op2) 

131 

132 

133Single-Argument Test Methods 

134============================ 

135 

136If a test method takes only one argument, the single arguments must not be 

137wrapped into a tuple:: 

138 

139 class NegativeNumberExample(parameterized.TestCase): 

140 @parameterized.parameters( 

141 -1, -3, -4, -5 

142 ) 

143 def testIsNegative(self, arg): 

144 self.assertTrue(IsNegative(arg)) 

145 

146 

147List/tuple as a Single Argument 

148=============================== 

149 

150If a test method takes a single argument of a list/tuple, it must be wrapped 

151inside a tuple:: 

152 

153 class ZeroSumExample(parameterized.TestCase): 

154 @parameterized.parameters( 

155 ([-1, 0, 1], ), 

156 ([-2, 0, 2], ), 

157 ) 

158 def testSumIsZero(self, arg): 

159 self.assertEqual(0, sum(arg)) 

160 

161 

162Cartesian product of Parameter Values as Parameterized Test Cases 

163================================================================= 

164 

165If required to test method over a cartesian product of parameters, 

166`parameterized.product` may be used to facilitate generation of parameters 

167test combinations:: 

168 

169 class TestModuloExample(parameterized.TestCase): 

170 @parameterized.product( 

171 num=[0, 20, 80], 

172 modulo=[2, 4], 

173 expected=[0] 

174 ) 

175 def testModuloResult(self, num, modulo, expected): 

176 self.assertEqual(expected, num % modulo) 

177 

178This results in 6 test cases being created - one for each combination of the 

179parameters. It is also possible to supply sequences of keyword argument dicts 

180as elements of the cartesian product:: 

181 

182 @parameterized.product( 

183 (dict(num=5, modulo=3, expected=2), 

184 dict(num=7, modulo=4, expected=3)), 

185 dtype=(int, float) 

186 ) 

187 def testModuloResult(self, num, modulo, expected, dtype): 

188 self.assertEqual(expected, dtype(num) % modulo) 

189 

190This results in 4 test cases being created - for each of the two sets of test 

191data (supplied as kwarg dicts) and for each of the two data types (supplied as 

192a named parameter). Multiple keyword argument dicts may be supplied if required. 

193 

194Async Support 

195============= 

196 

197If a test needs to call async functions, it can inherit from both 

198parameterized.TestCase and another TestCase that supports async calls, such 

199as [asynctest](https://github.com/Martiusweb/asynctest):: 

200 

201 import asynctest 

202 

203 class AsyncExample(parameterized.TestCase, asynctest.TestCase): 

204 @parameterized.parameters( 

205 ('a', 1), 

206 ('b', 2), 

207 ) 

208 async def testSomeAsyncFunction(self, arg, expected): 

209 actual = await someAsyncFunction(arg) 

210 self.assertEqual(actual, expected) 

211""" 

212 

213from collections import abc 

214import functools 

215import inspect 

216import itertools 

217import re 

218import types 

219import unittest 

220import warnings 

221 

222from absl.testing import absltest 

223 

224 

225_ADDR_RE = re.compile(r'\<([a-zA-Z0-9_\-\.]+) object at 0x[a-fA-F0-9]+\>') 

226_NAMED = object() 

227_ARGUMENT_REPR = object() 

228_NAMED_DICT_KEY = 'testcase_name' 

229 

230 

231class NoTestsError(Exception): 

232 """Raised when parameterized decorators do not generate any tests.""" 

233 

234 

235class DuplicateTestNameError(Exception): 

236 """Raised when a parameterized test has the same test name multiple times.""" 

237 

238 def __init__(self, test_class_name, new_test_name, original_test_name): 

239 super(DuplicateTestNameError, self).__init__( 

240 'Duplicate parameterized test name in {}: generated test name {!r} ' 

241 '(generated from {!r}) already exists. Consider using ' 

242 'named_parameters() to give your tests unique names and/or renaming ' 

243 'the conflicting test method.'.format( 

244 test_class_name, new_test_name, original_test_name)) 

245 

246 

247def _clean_repr(obj): 

248 return _ADDR_RE.sub(r'<\1>', repr(obj)) 

249 

250 

251def _non_string_or_bytes_iterable(obj): 

252 return (isinstance(obj, abc.Iterable) and not isinstance(obj, str) and 

253 not isinstance(obj, bytes)) 

254 

255 

256def _format_parameter_list(testcase_params): 

257 if isinstance(testcase_params, abc.Mapping): 

258 return ', '.join('%s=%s' % (argname, _clean_repr(value)) 

259 for argname, value in testcase_params.items()) 

260 elif _non_string_or_bytes_iterable(testcase_params): 

261 return ', '.join(map(_clean_repr, testcase_params)) 

262 else: 

263 return _format_parameter_list((testcase_params,)) 

264 

265 

266def _async_wrapped(func): 

267 @functools.wraps(func) 

268 async def wrapper(*args, **kwargs): 

269 return await func(*args, **kwargs) 

270 return wrapper 

271 

272 

273class _ParameterizedTestIter(object): 

274 """Callable and iterable class for producing new test cases.""" 

275 

276 def __init__(self, test_method, testcases, naming_type, original_name=None): 

277 """Returns concrete test functions for a test and a list of parameters. 

278 

279 The naming_type is used to determine the name of the concrete 

280 functions as reported by the unittest framework. If naming_type is 

281 _FIRST_ARG, the testcases must be tuples, and the first element must 

282 have a string representation that is a valid Python identifier. 

283 

284 Args: 

285 test_method: The decorated test method. 

286 testcases: (list of tuple/dict) A list of parameter tuples/dicts for 

287 individual test invocations. 

288 naming_type: The test naming type, either _NAMED or _ARGUMENT_REPR. 

289 original_name: The original test method name. When decorated on a test 

290 method, None is passed to __init__ and test_method.__name__ is used. 

291 Note test_method.__name__ might be different than the original defined 

292 test method because of the use of other decorators. A more accurate 

293 value is set by TestGeneratorMetaclass.__new__ later. 

294 """ 

295 self._test_method = test_method 

296 self.testcases = testcases 

297 self._naming_type = naming_type 

298 if original_name is None: 

299 original_name = test_method.__name__ 

300 self._original_name = original_name 

301 self.__name__ = _ParameterizedTestIter.__name__ 

302 

303 def __call__(self, *args, **kwargs): 

304 raise RuntimeError('You appear to be running a parameterized test case ' 

305 'without having inherited from parameterized.' 

306 'TestCase. This is bad because none of ' 

307 'your test cases are actually being run. You may also ' 

308 'be using another decorator before the parameterized ' 

309 'one, in which case you should reverse the order.') 

310 

311 def __iter__(self): 

312 test_method = self._test_method 

313 naming_type = self._naming_type 

314 

315 def make_bound_param_test(testcase_params): 

316 @functools.wraps(test_method) 

317 def bound_param_test(self): 

318 if isinstance(testcase_params, abc.Mapping): 

319 return test_method(self, **testcase_params) 

320 elif _non_string_or_bytes_iterable(testcase_params): 

321 return test_method(self, *testcase_params) 

322 else: 

323 return test_method(self, testcase_params) 

324 

325 if naming_type is _NAMED: 

326 # Signal the metaclass that the name of the test function is unique 

327 # and descriptive. 

328 bound_param_test.__x_use_name__ = True 

329 

330 testcase_name = None 

331 if isinstance(testcase_params, abc.Mapping): 

332 if _NAMED_DICT_KEY not in testcase_params: 

333 raise RuntimeError( 

334 'Dict for named tests must contain key "%s"' % _NAMED_DICT_KEY) 

335 # Create a new dict to avoid modifying the supplied testcase_params. 

336 testcase_name = testcase_params[_NAMED_DICT_KEY] 

337 testcase_params = { 

338 k: v for k, v in testcase_params.items() if k != _NAMED_DICT_KEY 

339 } 

340 elif _non_string_or_bytes_iterable(testcase_params): 

341 if not isinstance(testcase_params[0], str): 

342 raise RuntimeError( 

343 'The first element of named test parameters is the test name ' 

344 'suffix and must be a string') 

345 testcase_name = testcase_params[0] 

346 testcase_params = testcase_params[1:] 

347 else: 

348 raise RuntimeError( 

349 'Named tests must be passed a dict or non-string iterable.') 

350 

351 test_method_name = self._original_name 

352 # Support PEP-8 underscore style for test naming if used. 

353 if (test_method_name.startswith('test_') 

354 and testcase_name 

355 and not testcase_name.startswith('_')): 

356 test_method_name += '_' 

357 

358 bound_param_test.__name__ = test_method_name + str(testcase_name) 

359 elif naming_type is _ARGUMENT_REPR: 

360 # If it's a generator, convert it to a tuple and treat them as 

361 # parameters. 

362 if isinstance(testcase_params, types.GeneratorType): 

363 testcase_params = tuple(testcase_params) 

364 # The metaclass creates a unique, but non-descriptive method name for 

365 # _ARGUMENT_REPR tests using an indexed suffix. 

366 # To keep test names descriptive, only the original method name is used. 

367 # To make sure test names are unique, we add a unique descriptive suffix 

368 # __x_params_repr__ for every test. 

369 params_repr = '(%s)' % (_format_parameter_list(testcase_params),) 

370 bound_param_test.__x_params_repr__ = params_repr 

371 else: 

372 raise RuntimeError('%s is not a valid naming type.' % (naming_type,)) 

373 

374 bound_param_test.__doc__ = '%s(%s)' % ( 

375 bound_param_test.__name__, _format_parameter_list(testcase_params)) 

376 if test_method.__doc__: 

377 bound_param_test.__doc__ += '\n%s' % (test_method.__doc__,) 

378 if inspect.iscoroutinefunction(test_method): 

379 return _async_wrapped(bound_param_test) 

380 return bound_param_test 

381 

382 return (make_bound_param_test(c) for c in self.testcases) 

383 

384 

385def _modify_class(class_object, testcases, naming_type): 

386 assert not getattr(class_object, '_test_params_reprs', None), ( 

387 'Cannot add parameters to %s. Either it already has parameterized ' 

388 'methods, or its super class is also a parameterized class.' % ( 

389 class_object,)) 

390 # NOTE: _test_params_repr is private to parameterized.TestCase and it's 

391 # metaclass; do not use it outside of those classes. 

392 class_object._test_params_reprs = test_params_reprs = {} 

393 for name, obj in class_object.__dict__.copy().items(): 

394 if (name.startswith(unittest.TestLoader.testMethodPrefix) 

395 and isinstance(obj, types.FunctionType)): 

396 delattr(class_object, name) 

397 methods = {} 

398 _update_class_dict_for_param_test_case( 

399 class_object.__name__, methods, test_params_reprs, name, 

400 _ParameterizedTestIter(obj, testcases, naming_type, name)) 

401 for meth_name, meth in methods.items(): 

402 setattr(class_object, meth_name, meth) 

403 

404 

405def _parameter_decorator(naming_type, testcases): 

406 """Implementation of the parameterization decorators. 

407 

408 Args: 

409 naming_type: The naming type. 

410 testcases: Testcase parameters. 

411 

412 Raises: 

413 NoTestsError: Raised when the decorator generates no tests. 

414 

415 Returns: 

416 A function for modifying the decorated object. 

417 """ 

418 def _apply(obj): 

419 if isinstance(obj, type): 

420 _modify_class(obj, testcases, naming_type) 

421 return obj 

422 else: 

423 return _ParameterizedTestIter(obj, testcases, naming_type) 

424 

425 if (len(testcases) == 1 and 

426 not isinstance(testcases[0], tuple) and 

427 not isinstance(testcases[0], abc.Mapping)): 

428 # Support using a single non-tuple parameter as a list of test cases. 

429 # Note that the single non-tuple parameter can't be Mapping either, which 

430 # means a single dict parameter case. 

431 assert _non_string_or_bytes_iterable(testcases[0]), ( 

432 'Single parameter argument must be a non-string non-Mapping iterable') 

433 testcases = testcases[0] 

434 

435 if not isinstance(testcases, abc.Sequence): 

436 testcases = list(testcases) 

437 if not testcases: 

438 raise NoTestsError( 

439 'parameterized test decorators did not generate any tests. ' 

440 'Make sure you specify non-empty parameters, ' 

441 'and do not reuse generators more than once.') 

442 

443 return _apply 

444 

445 

446def parameters(*testcases): 

447 """A decorator for creating parameterized tests. 

448 

449 See the module docstring for a usage example. 

450 

451 Args: 

452 *testcases: Parameters for the decorated method, either a single 

453 iterable, or a list of tuples/dicts/objects (for tests with only one 

454 argument). 

455 

456 Raises: 

457 NoTestsError: Raised when the decorator generates no tests. 

458 

459 Returns: 

460 A test generator to be handled by TestGeneratorMetaclass. 

461 """ 

462 return _parameter_decorator(_ARGUMENT_REPR, testcases) 

463 

464 

465def named_parameters(*testcases): 

466 """A decorator for creating parameterized tests. 

467 

468 See the module docstring for a usage example. For every parameter tuple 

469 passed, the first element of the tuple should be a string and will be appended 

470 to the name of the test method. Each parameter dict passed must have a value 

471 for the key "testcase_name", the string representation of that value will be 

472 appended to the name of the test method. 

473 

474 Args: 

475 *testcases: Parameters for the decorated method, either a single iterable, 

476 or a list of tuples or dicts. 

477 

478 Raises: 

479 NoTestsError: Raised when the decorator generates no tests. 

480 

481 Returns: 

482 A test generator to be handled by TestGeneratorMetaclass. 

483 """ 

484 return _parameter_decorator(_NAMED, testcases) 

485 

486 

487def product(*kwargs_seqs, **testgrid): 

488 """A decorator for running tests over cartesian product of parameters values. 

489 

490 See the module docstring for a usage example. The test will be run for every 

491 possible combination of the parameters. 

492 

493 Args: 

494 *kwargs_seqs: Each positional parameter is a sequence of keyword arg dicts; 

495 every test case generated will include exactly one kwargs dict from each 

496 positional parameter; these will then be merged to form an overall list 

497 of arguments for the test case. 

498 **testgrid: A mapping of parameter names and their possible values. Possible 

499 values should given as either a list or a tuple. 

500 

501 Raises: 

502 NoTestsError: Raised when the decorator generates no tests. 

503 

504 Returns: 

505 A test generator to be handled by TestGeneratorMetaclass. 

506 """ 

507 

508 for name, values in testgrid.items(): 

509 assert isinstance(values, (list, tuple)), ( 

510 'Values of {} must be given as list or tuple, found {}'.format( 

511 name, type(values))) 

512 

513 prior_arg_names = set() 

514 for kwargs_seq in kwargs_seqs: 

515 assert ((isinstance(kwargs_seq, (list, tuple))) and 

516 all(isinstance(kwargs, dict) for kwargs in kwargs_seq)), ( 

517 'Positional parameters must be a sequence of keyword arg' 

518 'dicts, found {}' 

519 .format(kwargs_seq)) 

520 if kwargs_seq: 

521 arg_names = set(kwargs_seq[0]) 

522 assert all(set(kwargs) == arg_names for kwargs in kwargs_seq), ( 

523 'Keyword argument dicts within a single parameter must all have the ' 

524 'same keys, found {}'.format(kwargs_seq)) 

525 assert not (arg_names & prior_arg_names), ( 

526 'Keyword argument dict sequences must all have distinct argument ' 

527 'names, found duplicate(s) {}' 

528 .format(sorted(arg_names & prior_arg_names))) 

529 prior_arg_names |= arg_names 

530 

531 assert not (prior_arg_names & set(testgrid)), ( 

532 'Arguments supplied in kwargs dicts in positional parameters must not ' 

533 'overlap with arguments supplied as named parameters; found duplicate ' 

534 'argument(s) {}'.format(sorted(prior_arg_names & set(testgrid)))) 

535 

536 # Convert testgrid into a sequence of sequences of kwargs dicts and combine 

537 # with the positional parameters. 

538 # So foo=[1,2], bar=[3,4] --> [[{foo: 1}, {foo: 2}], [{bar: 3, bar: 4}]] 

539 testgrid = (tuple({k: v} for v in vs) for k, vs in testgrid.items()) 

540 testgrid = tuple(kwargs_seqs) + tuple(testgrid) 

541 

542 # Create all possible combinations of parameters as a cartesian product 

543 # of parameter values. 

544 testcases = [ 

545 dict(itertools.chain.from_iterable(case.items() 

546 for case in cases)) 

547 for cases in itertools.product(*testgrid) 

548 ] 

549 return _parameter_decorator(_ARGUMENT_REPR, testcases) 

550 

551 

552class TestGeneratorMetaclass(type): 

553 """Metaclass for adding tests generated by parameterized decorators.""" 

554 

555 def __new__(cls, class_name, bases, dct): 

556 # NOTE: _test_params_repr is private to parameterized.TestCase and it's 

557 # metaclass; do not use it outside of those classes. 

558 test_params_reprs = dct.setdefault('_test_params_reprs', {}) 

559 for name, obj in dct.copy().items(): 

560 if (name.startswith(unittest.TestLoader.testMethodPrefix) and 

561 _non_string_or_bytes_iterable(obj)): 

562 # NOTE: `obj` might not be a _ParameterizedTestIter in two cases: 

563 # 1. a class-level iterable named test* that isn't a test, such as 

564 # a list of something. Such attributes get deleted from the class. 

565 # 

566 # 2. If a decorator is applied to the parameterized test, e.g. 

567 # @morestuff 

568 # @parameterized.parameters(...) 

569 # def test_foo(...): ... 

570 # 

571 # This is OK so long as the underlying parameterized function state 

572 # is forwarded (e.g. using functool.wraps() and **without** 

573 # accessing explicitly accessing the internal attributes. 

574 if isinstance(obj, _ParameterizedTestIter): 

575 # Update the original test method name so it's more accurate. 

576 # The mismatch might happen when another decorator is used inside 

577 # the parameterized decrators, and the inner decorator doesn't 

578 # preserve its __name__. 

579 obj._original_name = name 

580 iterator = iter(obj) 

581 dct.pop(name) 

582 _update_class_dict_for_param_test_case( 

583 class_name, dct, test_params_reprs, name, iterator) 

584 # If the base class is a subclass of parameterized.TestCase, inherit its 

585 # _test_params_reprs too. 

586 for base in bases: 

587 # Check if the base has _test_params_reprs first, then check if it's a 

588 # subclass of parameterized.TestCase. Otherwise when this is called for 

589 # the parameterized.TestCase definition itself, this raises because 

590 # itself is not defined yet. This works as long as absltest.TestCase does 

591 # not define _test_params_reprs. 

592 base_test_params_reprs = getattr(base, '_test_params_reprs', None) 

593 if base_test_params_reprs and issubclass(base, TestCase): 

594 for test_method, test_method_id in base_test_params_reprs.items(): 

595 # test_method may both exists in base and this class. 

596 # This class's method overrides base class's. 

597 # That's why it should only inherit it if it does not exist. 

598 test_params_reprs.setdefault(test_method, test_method_id) 

599 

600 return type.__new__(cls, class_name, bases, dct) 

601 

602 

603def _update_class_dict_for_param_test_case( 

604 test_class_name, dct, test_params_reprs, name, iterator): 

605 """Adds individual test cases to a dictionary. 

606 

607 Args: 

608 test_class_name: The name of the class tests are added to. 

609 dct: The target dictionary. 

610 test_params_reprs: The dictionary for mapping names to test IDs. 

611 name: The original name of the test case. 

612 iterator: The iterator generating the individual test cases. 

613 

614 Raises: 

615 DuplicateTestNameError: Raised when a test name occurs multiple times. 

616 RuntimeError: If non-parameterized functions are generated. 

617 """ 

618 for idx, func in enumerate(iterator): 

619 assert callable(func), 'Test generators must yield callables, got %r' % ( 

620 func,) 

621 if not (getattr(func, '__x_use_name__', None) or 

622 getattr(func, '__x_params_repr__', None)): 

623 raise RuntimeError( 

624 '{}.{} generated a test function without using the parameterized ' 

625 'decorators. Only tests generated using the decorators are ' 

626 'supported.'.format(test_class_name, name)) 

627 

628 if getattr(func, '__x_use_name__', False): 

629 original_name = func.__name__ 

630 new_name = original_name 

631 else: 

632 original_name = name 

633 new_name = '%s%d' % (original_name, idx) 

634 

635 if new_name in dct: 

636 raise DuplicateTestNameError(test_class_name, new_name, original_name) 

637 

638 dct[new_name] = func 

639 test_params_reprs[new_name] = getattr(func, '__x_params_repr__', '') 

640 

641 

642class TestCase(absltest.TestCase, metaclass=TestGeneratorMetaclass): 

643 """Base class for test cases using the parameters decorator.""" 

644 

645 # visibility: private; do not call outside this class. 

646 def _get_params_repr(self): 

647 return self._test_params_reprs.get(self._testMethodName, '') 

648 

649 def __str__(self): 

650 params_repr = self._get_params_repr() 

651 if params_repr: 

652 params_repr = ' ' + params_repr 

653 return '{}{} ({})'.format( 

654 self._testMethodName, params_repr, 

655 unittest.util.strclass(self.__class__)) 

656 

657 def id(self): 

658 """Returns the descriptive ID of the test. 

659 

660 This is used internally by the unittesting framework to get a name 

661 for the test to be used in reports. 

662 

663 Returns: 

664 The test id. 

665 """ 

666 base = super(TestCase, self).id() 

667 params_repr = self._get_params_repr() 

668 if params_repr: 

669 # We include the params in the id so that, when reported in the 

670 # test.xml file, the value is more informative than just "test_foo0". 

671 # Use a space to separate them so that it's copy/paste friendly and 

672 # easy to identify the actual test id. 

673 return '{} {}'.format(base, params_repr) 

674 else: 

675 return base 

676 

677 

678# This function is kept CamelCase because it's used as a class's base class. 

679def CoopTestCase(other_base_class): # pylint: disable=invalid-name 

680 """Returns a new base class with a cooperative metaclass base. 

681 

682 This enables the TestCase to be used in combination 

683 with other base classes that have custom metaclasses, such as 

684 ``mox.MoxTestBase``. 

685 

686 Only works with metaclasses that do not override ``type.__new__``. 

687 

688 Example:: 

689 

690 from absl.testing import parameterized 

691 

692 class ExampleTest(parameterized.CoopTestCase(OtherTestCase)): 

693 ... 

694 

695 Args: 

696 other_base_class: (class) A test case base class. 

697 

698 Returns: 

699 A new class object. 

700 """ 

701 # If the other base class has a metaclass of 'type' then trying to combine 

702 # the metaclasses will result in an MRO error. So simply combine them and 

703 # return. 

704 if type(other_base_class) == type: # pylint: disable=unidiomatic-typecheck 

705 warnings.warn( 

706 'CoopTestCase is only necessary when combining with a class that uses' 

707 ' a metaclass. Use multiple inheritance like this instead: class' 

708 f' ExampleTest(paramaterized.TestCase, {other_base_class.__name__}):', 

709 stacklevel=2, 

710 ) 

711 

712 class CoopTestCaseBase(other_base_class, TestCase): 

713 pass 

714 

715 return CoopTestCaseBase 

716 else: 

717 

718 class CoopMetaclass(type(other_base_class), TestGeneratorMetaclass): # pylint: disable=unused-variable 

719 pass 

720 

721 class CoopTestCaseBase(other_base_class, TestCase, metaclass=CoopMetaclass): 

722 pass 

723 

724 return CoopTestCaseBase