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
« 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.
15"""Adds support for parameterized tests to Python's unittest TestCase class.
17A parameterized test is a method in a test case that is invoked with different
18argument tuples.
20A simple example::
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)
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).
35Parameters for individual test cases can be tuples (with positional parameters)
36or dictionaries (with named parameters)::
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)
46If a parameterized test fails, the error message will show the
47original test name and the parameters for that test.
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::
54 >>> class Foo(object):
55 ... pass
56 >>> repr(Foo())
57 '<__main__.Foo object at 0x23d8610>'
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.
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()``)::
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))
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))
91Named tests also have the benefit that they can be run individually
92from the command line::
94 $ testmodule.py NamedExample.testStartsWithNormal
95 .
96 --------------------------------------------------------------------
97 Ran 1 test in 0.000s
99 OK
101Parameterized Classes
102=====================
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::
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)
115 def testSubtract(self, arg1, arg2, result):
116 self.assertEqual(result - arg1, arg2)
118Inputs from Iterables
119=====================
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::
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)
133Single-Argument Test Methods
134============================
136If a test method takes only one argument, the single arguments must not be
137wrapped into a tuple::
139 class NegativeNumberExample(parameterized.TestCase):
140 @parameterized.parameters(
141 -1, -3, -4, -5
142 )
143 def testIsNegative(self, arg):
144 self.assertTrue(IsNegative(arg))
147List/tuple as a Single Argument
148===============================
150If a test method takes a single argument of a list/tuple, it must be wrapped
151inside a tuple::
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))
162Cartesian product of Parameter Values as Parameterized Test Cases
163=================================================================
165If required to test method over a cartesian product of parameters,
166`parameterized.product` may be used to facilitate generation of parameters
167test combinations::
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)
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::
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)
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.
194Async Support
195=============
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)::
201 import asynctest
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"""
213from collections import abc
214import functools
215import inspect
216import itertools
217import re
218import types
219import unittest
220import warnings
222from absl.testing import absltest
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'
231class NoTestsError(Exception):
232 """Raised when parameterized decorators do not generate any tests."""
235class DuplicateTestNameError(Exception):
236 """Raised when a parameterized test has the same test name multiple times."""
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))
247def _clean_repr(obj):
248 return _ADDR_RE.sub(r'<\1>', repr(obj))
251def _non_string_or_bytes_iterable(obj):
252 return (isinstance(obj, abc.Iterable) and not isinstance(obj, str) and
253 not isinstance(obj, bytes))
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,))
266def _async_wrapped(func):
267 @functools.wraps(func)
268 async def wrapper(*args, **kwargs):
269 return await func(*args, **kwargs)
270 return wrapper
273class _ParameterizedTestIter(object):
274 """Callable and iterable class for producing new test cases."""
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.
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.
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__
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.')
311 def __iter__(self):
312 test_method = self._test_method
313 naming_type = self._naming_type
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)
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
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.')
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 += '_'
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,))
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
382 return (make_bound_param_test(c) for c in self.testcases)
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)
405def _parameter_decorator(naming_type, testcases):
406 """Implementation of the parameterization decorators.
408 Args:
409 naming_type: The naming type.
410 testcases: Testcase parameters.
412 Raises:
413 NoTestsError: Raised when the decorator generates no tests.
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)
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]
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.')
443 return _apply
446def parameters(*testcases):
447 """A decorator for creating parameterized tests.
449 See the module docstring for a usage example.
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).
456 Raises:
457 NoTestsError: Raised when the decorator generates no tests.
459 Returns:
460 A test generator to be handled by TestGeneratorMetaclass.
461 """
462 return _parameter_decorator(_ARGUMENT_REPR, testcases)
465def named_parameters(*testcases):
466 """A decorator for creating parameterized tests.
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.
474 Args:
475 *testcases: Parameters for the decorated method, either a single iterable,
476 or a list of tuples or dicts.
478 Raises:
479 NoTestsError: Raised when the decorator generates no tests.
481 Returns:
482 A test generator to be handled by TestGeneratorMetaclass.
483 """
484 return _parameter_decorator(_NAMED, testcases)
487def product(*kwargs_seqs, **testgrid):
488 """A decorator for running tests over cartesian product of parameters values.
490 See the module docstring for a usage example. The test will be run for every
491 possible combination of the parameters.
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.
501 Raises:
502 NoTestsError: Raised when the decorator generates no tests.
504 Returns:
505 A test generator to be handled by TestGeneratorMetaclass.
506 """
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)))
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
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))))
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)
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)
552class TestGeneratorMetaclass(type):
553 """Metaclass for adding tests generated by parameterized decorators."""
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)
600 return type.__new__(cls, class_name, bases, dct)
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.
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.
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))
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)
635 if new_name in dct:
636 raise DuplicateTestNameError(test_class_name, new_name, original_name)
638 dct[new_name] = func
639 test_params_reprs[new_name] = getattr(func, '__x_params_repr__', '')
642class TestCase(absltest.TestCase, metaclass=TestGeneratorMetaclass):
643 """Base class for test cases using the parameters decorator."""
645 # visibility: private; do not call outside this class.
646 def _get_params_repr(self):
647 return self._test_params_reprs.get(self._testMethodName, '')
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__))
657 def id(self):
658 """Returns the descriptive ID of the test.
660 This is used internally by the unittesting framework to get a name
661 for the test to be used in reports.
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
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.
682 This enables the TestCase to be used in combination
683 with other base classes that have custom metaclasses, such as
684 ``mox.MoxTestBase``.
686 Only works with metaclasses that do not override ``type.__new__``.
688 Example::
690 from absl.testing import parameterized
692 class ExampleTest(parameterized.CoopTestCase(OtherTestCase)):
693 ...
695 Args:
696 other_base_class: (class) A test case base class.
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 )
712 class CoopTestCaseBase(other_base_class, TestCase):
713 pass
715 return CoopTestCaseBase
716 else:
718 class CoopMetaclass(type(other_base_class), TestGeneratorMetaclass): # pylint: disable=unused-variable
719 pass
721 class CoopTestCaseBase(other_base_class, TestCase, metaclass=CoopMetaclass):
722 pass
724 return CoopTestCaseBase