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"""Tests for absl.testing.parameterized."""
16
17from collections import abc
18import os
19import sys
20import unittest
21
22from absl.testing import absltest
23from absl.testing import parameterized
24
25
26class MyOwnClass(object):
27  pass
28
29
30def dummy_decorator(method):
31  def decorated(*args, **kwargs):
32    return method(*args, **kwargs)
33
34  return decorated
35
36
37def dict_decorator(key, value):
38  """Sample implementation of a chained decorator.
39
40  Sets a single field in a dict on a test with a dict parameter.
41  Uses the exposed '_ParameterizedTestIter.testcases' field to
42  modify arguments from previous decorators to allow decorator chains.
43
44  Args:
45    key: key to map to
46    value: value to set
47
48  Returns:
49    The test decorator
50  """
51
52  def decorator(test_method):
53    # If decorating result of another dict_decorator
54    if isinstance(test_method, abc.Iterable):
55      actual_tests = []
56      for old_test in test_method.testcases:
57        # each test is a ('test_suffix', dict) tuple
58        new_dict = old_test[1].copy()
59        new_dict[key] = value
60        test_suffix = '%s_%s_%s' % (old_test[0], key, value)
61        actual_tests.append((test_suffix, new_dict))
62
63      test_method.testcases = actual_tests
64      return test_method
65    else:
66      test_suffix = '_%s_%s' % (key, value)
67      tests_to_make = ((test_suffix, {key: value}),)
68      # 'test_method' here is the original test method
69      return parameterized.named_parameters(*tests_to_make)(test_method)
70
71  return decorator
72
73
74class ParameterizedTestsTest(absltest.TestCase):
75  # The test testcases are nested so they're not
76  # picked up by the normal test case loader code.
77
78  class GoodAdditionParams(parameterized.TestCase):
79
80    @parameterized.parameters((1, 2, 3), (4, 5, 9))
81    def test_addition(self, op1, op2, result):
82      self.arguments = (op1, op2, result)
83      self.assertEqual(result, op1 + op2)
84
85  # This class does not inherit from TestCase.
86  class BadAdditionParams(absltest.TestCase):
87
88    @parameterized.parameters((1, 2, 3), (4, 5, 9))
89    def test_addition(self, op1, op2, result):
90      pass  # Always passes, but not called w/out TestCase.
91
92  class MixedAdditionParams(parameterized.TestCase):
93
94    @parameterized.parameters((1, 2, 1), (4, 5, 9))
95    def test_addition(self, op1, op2, result):
96      self.arguments = (op1, op2, result)
97      self.assertEqual(result, op1 + op2)
98
99  class DictionaryArguments(parameterized.TestCase):
100
101    @parameterized.parameters(
102        {'op1': 1, 'op2': 2, 'result': 3}, {'op1': 4, 'op2': 5, 'result': 9}
103    )
104    def test_addition(self, op1, op2, result):
105      self.assertEqual(result, op1 + op2)
106
107  class NoParameterizedTests(parameterized.TestCase):
108    # iterable member with non-matching name
109    a = 'BCD'
110    # member with matching name, but not a generator
111    testInstanceMember = None  # pylint: disable=invalid-name
112    test_instance_member = None
113
114    # member with a matching name and iterator, but not a generator
115    testString = 'foo'  # pylint: disable=invalid-name
116    test_string = 'foo'
117
118    # generator, but no matching name
119    def someGenerator(self):  # pylint: disable=invalid-name
120      yield
121      yield
122      yield
123
124    def some_generator(self):
125      yield
126      yield
127      yield
128
129    # Generator function, but not a generator instance.
130    def testGenerator(self):
131      yield
132      yield
133      yield
134
135    def test_generator(self):
136      yield
137      yield
138      yield
139
140    def testNormal(self):
141      self.assertEqual(3, 1 + 2)
142
143    def test_normal(self):
144      self.assertEqual(3, 1 + 2)
145
146  class ArgumentsWithAddresses(parameterized.TestCase):
147
148    @parameterized.parameters(
149        (object(),),
150        (MyOwnClass(),),
151    )
152    def test_something(self, case):
153      pass
154
155  class CamelCaseNamedTests(parameterized.TestCase):
156
157    @parameterized.named_parameters(
158        ('Interesting', 0),
159    )
160    def testSingle(self, case):
161      pass
162
163    @parameterized.named_parameters(
164        {'testcase_name': 'Interesting', 'case': 0},
165    )
166    def testDictSingle(self, case):
167      pass
168
169    @parameterized.named_parameters(
170        ('Interesting', 0),
171        ('Boring', 1),
172    )
173    def testSomething(self, case):
174      pass
175
176    @parameterized.named_parameters(
177        {'testcase_name': 'Interesting', 'case': 0},
178        {'testcase_name': 'Boring', 'case': 1},
179    )
180    def testDictSomething(self, case):
181      pass
182
183    @parameterized.named_parameters(
184        {'testcase_name': 'Interesting', 'case': 0},
185        ('Boring', 1),
186    )
187    def testMixedSomething(self, case):
188      pass
189
190    def testWithoutParameters(self):
191      pass
192
193  class NamedTests(parameterized.TestCase):
194    """Example tests using PEP-8 style names instead of camel-case."""
195
196    @parameterized.named_parameters(
197        ('interesting', 0),
198    )
199    def test_single(self, case):
200      pass
201
202    @parameterized.named_parameters(
203        {'testcase_name': 'interesting', 'case': 0},
204    )
205    def test_dict_single(self, case):
206      pass
207
208    @parameterized.named_parameters(
209        ('interesting', 0),
210        ('boring', 1),
211    )
212    def test_something(self, case):
213      pass
214
215    @parameterized.named_parameters(
216        {'testcase_name': 'interesting', 'case': 0},
217        {'testcase_name': 'boring', 'case': 1},
218    )
219    def test_dict_something(self, case):
220      pass
221
222    @parameterized.named_parameters(
223        {'testcase_name': 'interesting', 'case': 0},
224        ('boring', 1),
225    )
226    def test_mixed_something(self, case):
227      pass
228
229    def test_without_parameters(self):
230      pass
231
232  class ChainedTests(parameterized.TestCase):
233
234    @dict_decorator('cone', 'waffle')
235    @dict_decorator('flavor', 'strawberry')
236    def test_chained(self, dictionary):
237      self.assertDictEqual(
238          dictionary, {'cone': 'waffle', 'flavor': 'strawberry'}
239      )
240
241  class SingletonListExtraction(parameterized.TestCase):
242
243    @parameterized.parameters((i, i * 2) for i in range(10))
244    def test_something(self, unused_1, unused_2):
245      pass
246
247  class SingletonArgumentExtraction(parameterized.TestCase):
248
249    @parameterized.parameters(1, 2, 3, 4, 5, 6)
250    def test_numbers(self, unused_1):
251      pass
252
253    @parameterized.parameters('foo', 'bar', 'baz')
254    def test_strings(self, unused_1):
255      pass
256
257  class SingletonDictArgument(parameterized.TestCase):
258
259    @parameterized.parameters({'op1': 1, 'op2': 2})
260    def test_something(self, op1, op2):
261      del op1, op2
262
263  @parameterized.parameters((1, 2, 3), (4, 5, 9))
264  class DecoratedClass(parameterized.TestCase):
265
266    def test_add(self, arg1, arg2, arg3):
267      self.assertEqual(arg1 + arg2, arg3)
268
269    def test_subtract_fail(self, arg1, arg2, arg3):
270      self.assertEqual(arg3 + arg2, arg1)
271
272  @parameterized.parameters(
273      (a, b, a + b) for a in range(1, 5) for b in range(1, 5)
274  )
275  class GeneratorDecoratedClass(parameterized.TestCase):
276
277    def test_add(self, arg1, arg2, arg3):
278      self.assertEqual(arg1 + arg2, arg3)
279
280    def test_subtract_fail(self, arg1, arg2, arg3):
281      self.assertEqual(arg3 + arg2, arg1)
282
283  @parameterized.parameters(
284      (1, 2, 3),
285      (4, 5, 9),
286  )
287  class DecoratedBareClass(absltest.TestCase):
288
289    def test_add(self, arg1, arg2, arg3):
290      self.assertEqual(arg1 + arg2, arg3)
291
292  class OtherDecoratorUnnamed(parameterized.TestCase):
293
294    @dummy_decorator
295    @parameterized.parameters((1), (2))
296    def test_other_then_parameterized(self, arg1):
297      pass
298
299    @parameterized.parameters((1), (2))
300    @dummy_decorator
301    def test_parameterized_then_other(self, arg1):
302      pass
303
304  class OtherDecoratorNamed(parameterized.TestCase):
305
306    @dummy_decorator
307    @parameterized.named_parameters(('a', 1), ('b', 2))
308    def test_other_then_parameterized(self, arg1):
309      pass
310
311    @parameterized.named_parameters(('a', 1), ('b', 2))
312    @dummy_decorator
313    def test_parameterized_then_other(self, arg1):
314      pass
315
316  class OtherDecoratorNamedWithDict(parameterized.TestCase):
317
318    @dummy_decorator
319    @parameterized.named_parameters(
320        {'testcase_name': 'a', 'arg1': 1}, {'testcase_name': 'b', 'arg1': 2}
321    )
322    def test_other_then_parameterized(self, arg1):
323      pass
324
325    @parameterized.named_parameters(
326        {'testcase_name': 'a', 'arg1': 1}, {'testcase_name': 'b', 'arg1': 2}
327    )
328    @dummy_decorator
329    def test_parameterized_then_other(self, arg1):
330      pass
331
332  class UniqueDescriptiveNamesTest(parameterized.TestCase):
333
334    @parameterized.parameters(13, 13)
335    def test_normal(self, number):
336      del number
337
338  class MultiGeneratorsTestCase(parameterized.TestCase):
339
340    @parameterized.parameters((i for i in (1, 2, 3)), (i for i in (3, 2, 1)))
341    def test_sum(self, a, b, c):
342      self.assertEqual(6, sum([a, b, c]))
343
344  class NamedParametersReusableTestCase(parameterized.TestCase):
345    named_params_a = (
346        {'testcase_name': 'dict_a', 'unused_obj': 0},
347        ('list_a', 1),
348    )
349    named_params_b = (
350        {'testcase_name': 'dict_b', 'unused_obj': 2},
351        ('list_b', 3),
352    )
353    named_params_c = (
354        {'testcase_name': 'dict_c', 'unused_obj': 4},
355        ('list_b', 5),
356    )
357
358    @parameterized.named_parameters(*(named_params_a + named_params_b))
359    def testSomething(self, unused_obj):
360      pass
361
362    @parameterized.named_parameters(*(named_params_a + named_params_c))
363    def testSomethingElse(self, unused_obj):
364      pass
365
366  class SuperclassTestCase(parameterized.TestCase):
367
368    @parameterized.parameters('foo', 'bar')
369    def test_name(self, name):
370      del name
371
372  class SubclassTestCase(SuperclassTestCase):
373    pass
374
375  @unittest.skipIf(
376      (sys.version_info[:2] == (3, 7) and sys.version_info[2] in {0, 1, 2}),
377      'Python 3.7.0 to 3.7.2 have a bug that breaks this test, see '
378      'https://bugs.python.org/issue35767',
379  )
380  def test_missing_inheritance(self):
381    ts = unittest.makeSuite(self.BadAdditionParams)
382    self.assertEqual(1, ts.countTestCases())
383
384    res = unittest.TestResult()
385    ts.run(res)
386    self.assertEqual(1, res.testsRun)
387    self.assertFalse(res.wasSuccessful())
388    self.assertIn('without having inherited', str(res.errors[0]))
389
390  def test_correct_extraction_numbers(self):
391    ts = unittest.makeSuite(self.GoodAdditionParams)
392    self.assertEqual(2, ts.countTestCases())
393
394  def test_successful_execution(self):
395    ts = unittest.makeSuite(self.GoodAdditionParams)
396
397    res = unittest.TestResult()
398    ts.run(res)
399    self.assertEqual(2, res.testsRun)
400    self.assertTrue(res.wasSuccessful())
401
402  def test_correct_arguments(self):
403    ts = unittest.makeSuite(self.GoodAdditionParams)
404    res = unittest.TestResult()
405
406    params = set([(1, 2, 3), (4, 5, 9)])
407    for test in ts:
408      test(res)
409      self.assertIn(test.arguments, params)
410      params.remove(test.arguments)
411    self.assertEmpty(params)
412
413  def test_recorded_failures(self):
414    ts = unittest.makeSuite(self.MixedAdditionParams)
415    self.assertEqual(2, ts.countTestCases())
416
417    res = unittest.TestResult()
418    ts.run(res)
419    self.assertEqual(2, res.testsRun)
420    self.assertFalse(res.wasSuccessful())
421    self.assertLen(res.failures, 1)
422    self.assertEmpty(res.errors)
423
424  def test_short_description(self):
425    ts = unittest.makeSuite(self.GoodAdditionParams)
426    short_desc = list(ts)[0].shortDescription()
427
428    location = unittest.util.strclass(self.GoodAdditionParams).replace(
429        '__main__.', ''
430    )
431    expected = (
432        '{}.test_addition0 (1, 2, 3)\n'.format(location)
433        + 'test_addition(1, 2, 3)'
434    )
435    self.assertEqual(expected, short_desc)
436
437  def test_short_description_addresses_removed(self):
438    ts = unittest.makeSuite(self.ArgumentsWithAddresses)
439    short_desc = list(ts)[0].shortDescription().split('\n')
440    self.assertEqual('test_something(<object>)', short_desc[1])
441    short_desc = list(ts)[1].shortDescription().split('\n')
442    self.assertEqual('test_something(<__main__.MyOwnClass>)', short_desc[1])
443
444  def test_id(self):
445    ts = unittest.makeSuite(self.ArgumentsWithAddresses)
446    self.assertEqual(
447        (
448            unittest.util.strclass(self.ArgumentsWithAddresses)
449            + '.test_something0 (<object>)'
450        ),
451        list(ts)[0].id(),
452    )
453    ts = unittest.makeSuite(self.GoodAdditionParams)
454    self.assertEqual(
455        (
456            unittest.util.strclass(self.GoodAdditionParams)
457            + '.test_addition0 (1, 2, 3)'
458        ),
459        list(ts)[0].id(),
460    )
461
462  def test_str(self):
463    ts = unittest.makeSuite(self.GoodAdditionParams)
464    test = list(ts)[0]
465
466    expected = 'test_addition0 (1, 2, 3) ({})'.format(
467        unittest.util.strclass(self.GoodAdditionParams)
468    )
469    self.assertEqual(expected, str(test))
470
471  def test_dict_parameters(self):
472    ts = unittest.makeSuite(self.DictionaryArguments)
473    res = unittest.TestResult()
474    ts.run(res)
475    self.assertEqual(2, res.testsRun)
476    self.assertTrue(res.wasSuccessful())
477
478  def test_no_parameterized_tests(self):
479    ts = unittest.makeSuite(self.NoParameterizedTests)
480    self.assertEqual(4, ts.countTestCases())
481    short_descs = [x.shortDescription() for x in list(ts)]
482    full_class_name = unittest.util.strclass(self.NoParameterizedTests)
483    full_class_name = full_class_name.replace('__main__.', '')
484    self.assertSameElements(
485        [
486            '{}.testGenerator'.format(full_class_name),
487            '{}.test_generator'.format(full_class_name),
488            '{}.testNormal'.format(full_class_name),
489            '{}.test_normal'.format(full_class_name),
490        ],
491        short_descs,
492    )
493
494  def test_successful_product_test_testgrid(self):
495    class GoodProductTestCase(parameterized.TestCase):
496
497      @parameterized.product(num=(0, 20, 80), modulo=(2, 4), expected=(0,))
498      def testModuloResult(self, num, modulo, expected):
499        self.assertEqual(expected, num % modulo)
500
501    ts = unittest.makeSuite(GoodProductTestCase)
502    res = unittest.TestResult()
503    ts.run(res)
504    self.assertEqual(ts.countTestCases(), 6)
505    self.assertEqual(res.testsRun, 6)
506    self.assertTrue(res.wasSuccessful())
507
508  def test_successful_product_test_kwarg_seqs(self):
509    class GoodProductTestCase(parameterized.TestCase):
510
511      @parameterized.product(
512          (dict(num=0), dict(num=20), dict(num=0)),
513          (dict(modulo=2), dict(modulo=4)),
514          (dict(expected=0),),
515      )
516      def testModuloResult(self, num, modulo, expected):
517        self.assertEqual(expected, num % modulo)
518
519    ts = unittest.makeSuite(GoodProductTestCase)
520    res = unittest.TestResult()
521    ts.run(res)
522    self.assertEqual(ts.countTestCases(), 6)
523    self.assertEqual(res.testsRun, 6)
524    self.assertTrue(res.wasSuccessful())
525
526  def test_successful_product_test_kwarg_seq_and_testgrid(self):
527    class GoodProductTestCase(parameterized.TestCase):
528
529      @parameterized.product(
530          (
531              dict(num=5, modulo=3, expected=2),
532              dict(num=7, modulo=4, expected=3),
533          ),
534          dtype=(int, float),
535      )
536      def testModuloResult(self, num, dtype, modulo, expected):
537        self.assertEqual(expected, dtype(num) % modulo)
538
539    ts = unittest.makeSuite(GoodProductTestCase)
540    res = unittest.TestResult()
541    ts.run(res)
542    self.assertEqual(ts.countTestCases(), 4)
543    self.assertEqual(res.testsRun, 4)
544    self.assertTrue(res.wasSuccessful())
545
546  def test_inconsistent_arg_names_in_kwargs_seq(self):
547    with self.assertRaisesRegex(AssertionError, 'must all have the same keys'):
548
549      class BadProductParams(parameterized.TestCase):  # pylint: disable=unused-variable
550
551        @parameterized.product(
552            (dict(num=5, modulo=3), dict(num=7, modula=2)), dtype=(int, float)
553        )
554        def test_something(self):
555          pass  # not called because argnames are not the same
556
557  def test_duplicate_arg_names_in_kwargs_seqs(self):
558    with self.assertRaisesRegex(AssertionError, 'must all have distinct'):
559
560      class BadProductParams(parameterized.TestCase):  # pylint: disable=unused-variable
561
562        @parameterized.product(
563            (dict(num=5, modulo=3), dict(num=7, modulo=4)),
564            (dict(foo='bar', num=5), dict(foo='baz', num=7)),
565            dtype=(int, float),
566        )
567        def test_something(self):
568          pass  # not called because `num` is specified twice
569
570  def test_duplicate_arg_names_in_kwargs_seq_and_testgrid(self):
571    with self.assertRaisesRegex(AssertionError, 'duplicate argument'):
572
573      class BadProductParams(parameterized.TestCase):  # pylint: disable=unused-variable
574
575        @parameterized.product(
576            (dict(num=5, modulo=3), dict(num=7, modulo=4)),
577            (dict(foo='bar'), dict(foo='baz')),
578            dtype=(int, float),
579            foo=('a', 'b'),
580        )
581        def test_something(self):
582          pass  # not called because `foo` is specified twice
583
584  def test_product_recorded_failures(self):
585    class MixedProductTestCase(parameterized.TestCase):
586
587      @parameterized.product(num=(0, 10, 20), modulo=(2, 4), expected=(0,))
588      def testModuloResult(self, num, modulo, expected):
589        self.assertEqual(expected, num % modulo)
590
591    ts = unittest.makeSuite(MixedProductTestCase)
592    self.assertEqual(6, ts.countTestCases())
593
594    res = unittest.TestResult()
595    ts.run(res)
596    self.assertEqual(res.testsRun, 6)
597    self.assertFalse(res.wasSuccessful())
598    self.assertLen(res.failures, 1)
599    self.assertEmpty(res.errors)
600
601  def test_mismatched_product_parameter(self):
602    class MismatchedProductParam(parameterized.TestCase):
603
604      @parameterized.product(a=(1, 2), mismatch=(1, 2))
605      # will fail because of mismatch in parameter names.
606      def test_something(self, a, b):
607        pass
608
609    ts = unittest.makeSuite(MismatchedProductParam)
610    res = unittest.TestResult()
611    ts.run(res)
612    self.assertEqual(res.testsRun, 4)
613    self.assertFalse(res.wasSuccessful())
614    self.assertLen(res.errors, 4)
615
616  def test_no_test_error_empty_product_parameter(self):
617    with self.assertRaises(parameterized.NoTestsError):
618
619      class EmptyProductParam(parameterized.TestCase):  # pylint: disable=unused-variable
620
621        @parameterized.product(arg1=[1, 2], arg2=[])
622        def test_something(self, arg1, arg2):
623          pass  # not called because arg2 has empty list of values.
624
625  def test_bad_product_parameters(self):
626    with self.assertRaisesRegex(AssertionError, 'must be given as list or'):
627
628      class BadProductParams(parameterized.TestCase):  # pylint: disable=unused-variable
629
630        @parameterized.product(arg1=[1, 2], arg2='abcd')
631        def test_something(self, arg1, arg2):
632          pass  # not called because arg2 is not list or tuple.
633
634  def test_generator_tests_disallowed(self):
635    with self.assertRaisesRegex(RuntimeError, 'generated.*without'):
636
637      class GeneratorTests(parameterized.TestCase):  # pylint: disable=unused-variable
638        test_generator_method = (lambda self: None for _ in range(10))
639
640  def test_named_parameters_run(self):
641    ts = unittest.makeSuite(self.NamedTests)
642    self.assertEqual(9, ts.countTestCases())
643    res = unittest.TestResult()
644    ts.run(res)
645    self.assertEqual(9, res.testsRun)
646    self.assertTrue(res.wasSuccessful())
647
648  def test_named_parameters_id(self):
649    ts = sorted(
650        unittest.makeSuite(self.CamelCaseNamedTests), key=lambda t: t.id()
651    )
652    self.assertLen(ts, 9)
653    full_class_name = unittest.util.strclass(self.CamelCaseNamedTests)
654    self.assertEqual(full_class_name + '.testDictSingleInteresting', ts[0].id())
655    self.assertEqual(full_class_name + '.testDictSomethingBoring', ts[1].id())
656    self.assertEqual(
657        full_class_name + '.testDictSomethingInteresting', ts[2].id()
658    )
659    self.assertEqual(full_class_name + '.testMixedSomethingBoring', ts[3].id())
660    self.assertEqual(
661        full_class_name + '.testMixedSomethingInteresting', ts[4].id()
662    )
663    self.assertEqual(full_class_name + '.testSingleInteresting', ts[5].id())
664    self.assertEqual(full_class_name + '.testSomethingBoring', ts[6].id())
665    self.assertEqual(full_class_name + '.testSomethingInteresting', ts[7].id())
666    self.assertEqual(full_class_name + '.testWithoutParameters', ts[8].id())
667
668  def test_named_parameters_id_with_underscore_case(self):
669    ts = sorted(unittest.makeSuite(self.NamedTests), key=lambda t: t.id())
670    self.assertLen(ts, 9)
671    full_class_name = unittest.util.strclass(self.NamedTests)
672    self.assertEqual(
673        full_class_name + '.test_dict_single_interesting', ts[0].id()
674    )
675    self.assertEqual(
676        full_class_name + '.test_dict_something_boring', ts[1].id()
677    )
678    self.assertEqual(
679        full_class_name + '.test_dict_something_interesting', ts[2].id()
680    )
681    self.assertEqual(
682        full_class_name + '.test_mixed_something_boring', ts[3].id()
683    )
684    self.assertEqual(
685        full_class_name + '.test_mixed_something_interesting', ts[4].id()
686    )
687    self.assertEqual(full_class_name + '.test_single_interesting', ts[5].id())
688    self.assertEqual(full_class_name + '.test_something_boring', ts[6].id())
689    self.assertEqual(
690        full_class_name + '.test_something_interesting', ts[7].id()
691    )
692    self.assertEqual(full_class_name + '.test_without_parameters', ts[8].id())
693
694  def test_named_parameters_short_description(self):
695    ts = sorted(unittest.makeSuite(self.NamedTests), key=lambda t: t.id())
696    actual = {t._testMethodName: t.shortDescription() for t in ts}
697    expected = {
698        'test_dict_single_interesting': 'case=0',
699        'test_dict_something_boring': 'case=1',
700        'test_dict_something_interesting': 'case=0',
701        'test_mixed_something_boring': '1',
702        'test_mixed_something_interesting': 'case=0',
703        'test_something_boring': '1',
704        'test_something_interesting': '0',
705    }
706    for test_name, param_repr in expected.items():
707      short_desc = actual[test_name].split('\n')
708      self.assertIn(test_name, short_desc[0])
709      self.assertEqual('{}({})'.format(test_name, param_repr), short_desc[1])
710
711  def test_load_tuple_named_test(self):
712    loader = unittest.TestLoader()
713    ts = list(
714        loader.loadTestsFromName(
715            'NamedTests.test_something_interesting', module=self
716        )
717    )
718    self.assertLen(ts, 1)
719    self.assertEndsWith(ts[0].id(), '.test_something_interesting')
720
721  def test_load_dict_named_test(self):
722    loader = unittest.TestLoader()
723    ts = list(
724        loader.loadTestsFromName(
725            'NamedTests.test_dict_something_interesting', module=self
726        )
727    )
728    self.assertLen(ts, 1)
729    self.assertEndsWith(ts[0].id(), '.test_dict_something_interesting')
730
731  def test_load_mixed_named_test(self):
732    loader = unittest.TestLoader()
733    ts = list(
734        loader.loadTestsFromName(
735            'NamedTests.test_mixed_something_interesting', module=self
736        )
737    )
738    self.assertLen(ts, 1)
739    self.assertEndsWith(ts[0].id(), '.test_mixed_something_interesting')
740
741  def test_duplicate_named_test_fails(self):
742    with self.assertRaises(parameterized.DuplicateTestNameError):
743
744      class _(parameterized.TestCase):
745
746        @parameterized.named_parameters(
747            ('Interesting', 0),
748            ('Interesting', 1),
749        )
750        def test_something(self, unused_obj):
751          pass
752
753  def test_duplicate_dict_named_test_fails(self):
754    with self.assertRaises(parameterized.DuplicateTestNameError):
755
756      class _(parameterized.TestCase):
757
758        @parameterized.named_parameters(
759            {'testcase_name': 'Interesting', 'unused_obj': 0},
760            {'testcase_name': 'Interesting', 'unused_obj': 1},
761        )
762        def test_dict_something(self, unused_obj):
763          pass
764
765  def test_duplicate_mixed_named_test_fails(self):
766    with self.assertRaises(parameterized.DuplicateTestNameError):
767
768      class _(parameterized.TestCase):
769
770        @parameterized.named_parameters(
771            {'testcase_name': 'Interesting', 'unused_obj': 0},
772            ('Interesting', 1),
773        )
774        def test_mixed_something(self, unused_obj):
775          pass
776
777  def test_named_test_with_no_name_fails(self):
778    with self.assertRaises(RuntimeError):
779
780      class _(parameterized.TestCase):
781
782        @parameterized.named_parameters(
783            (0,),
784        )
785        def test_something(self, unused_obj):
786          pass
787
788  def test_named_test_dict_with_no_name_fails(self):
789    with self.assertRaises(RuntimeError):
790
791      class _(parameterized.TestCase):
792
793        @parameterized.named_parameters(
794            {'unused_obj': 0},
795        )
796        def test_something(self, unused_obj):
797          pass
798
799  def test_parameterized_test_iter_has_testcases_property(self):
800    @parameterized.parameters(1, 2, 3, 4, 5, 6)
801    def test_something(unused_self, unused_obj):  # pylint: disable=invalid-name
802      pass
803
804    expected_testcases = [1, 2, 3, 4, 5, 6]
805    self.assertTrue(hasattr(test_something, 'testcases'))
806    self.assertCountEqual(expected_testcases, test_something.testcases)
807
808  def test_chained_decorator(self):
809    ts = unittest.makeSuite(self.ChainedTests)
810    self.assertEqual(1, ts.countTestCases())
811    test = next(t for t in ts)
812    self.assertTrue(hasattr(test, 'test_chained_flavor_strawberry_cone_waffle'))
813    res = unittest.TestResult()
814
815    ts.run(res)
816    self.assertEqual(1, res.testsRun)
817    self.assertTrue(res.wasSuccessful())
818
819  def test_singleton_list_extraction(self):
820    ts = unittest.makeSuite(self.SingletonListExtraction)
821    res = unittest.TestResult()
822    ts.run(res)
823    self.assertEqual(10, res.testsRun)
824    self.assertTrue(res.wasSuccessful())
825
826  def test_singleton_argument_extraction(self):
827    ts = unittest.makeSuite(self.SingletonArgumentExtraction)
828    res = unittest.TestResult()
829    ts.run(res)
830    self.assertEqual(9, res.testsRun)
831    self.assertTrue(res.wasSuccessful())
832
833  def test_singleton_dict_argument(self):
834    ts = unittest.makeSuite(self.SingletonDictArgument)
835    res = unittest.TestResult()
836    ts.run(res)
837    self.assertEqual(1, res.testsRun)
838    self.assertTrue(res.wasSuccessful())
839
840  def test_decorated_bare_class(self):
841    ts = unittest.makeSuite(self.DecoratedBareClass)
842    res = unittest.TestResult()
843    ts.run(res)
844    self.assertEqual(2, res.testsRun)
845    self.assertTrue(res.wasSuccessful(), msg=str(res.failures))
846
847  def test_decorated_class(self):
848    ts = unittest.makeSuite(self.DecoratedClass)
849    res = unittest.TestResult()
850    ts.run(res)
851    self.assertEqual(4, res.testsRun)
852    self.assertLen(res.failures, 2)
853
854  def test_generator_decorated_class(self):
855    ts = unittest.makeSuite(self.GeneratorDecoratedClass)
856    res = unittest.TestResult()
857    ts.run(res)
858    self.assertEqual(32, res.testsRun)
859    self.assertLen(res.failures, 16)
860
861  def test_no_duplicate_decorations(self):
862    with self.assertRaises(AssertionError):
863
864      @parameterized.parameters(1, 2, 3, 4)
865      class _(parameterized.TestCase):
866
867        @parameterized.parameters(5, 6, 7, 8)
868        def test_something(self, unused_obj):
869          pass
870
871  def test_double_class_decorations_not_supported(self):
872    @parameterized.parameters('foo', 'bar')
873    class SuperclassWithClassDecorator(parameterized.TestCase):
874
875      def test_name(self, name):
876        del name
877
878    with self.assertRaises(AssertionError):
879
880      @parameterized.parameters('foo', 'bar')
881      class SubclassWithClassDecorator(SuperclassWithClassDecorator):
882        pass
883
884      del SubclassWithClassDecorator
885
886  def test_other_decorator_ordering_unnamed(self):
887    ts = unittest.makeSuite(self.OtherDecoratorUnnamed)
888    res = unittest.TestResult()
889    ts.run(res)
890    # Two for when the parameterized tests call the skip wrapper.
891    # One for when the skip wrapper is called first and doesn't iterate.
892    self.assertEqual(3, res.testsRun)
893    self.assertFalse(res.wasSuccessful())
894    self.assertEmpty(res.failures)
895    # One error from test_other_then_parameterized.
896    self.assertLen(res.errors, 1)
897
898  def test_other_decorator_ordering_named(self):
899    ts = unittest.makeSuite(self.OtherDecoratorNamed)
900    # Verify it generates the test method names from the original test method.
901    for test in ts:  # There is only one test.
902      ts_attributes = dir(test)
903      self.assertIn('test_parameterized_then_other_a', ts_attributes)
904      self.assertIn('test_parameterized_then_other_b', ts_attributes)
905
906    res = unittest.TestResult()
907    ts.run(res)
908    # Two for when the parameterized tests call the skip wrapper.
909    # One for when the skip wrapper is called first and doesn't iterate.
910    self.assertEqual(3, res.testsRun)
911    self.assertFalse(res.wasSuccessful())
912    self.assertEmpty(res.failures)
913    # One error from test_other_then_parameterized.
914    self.assertLen(res.errors, 1)
915
916  def test_other_decorator_ordering_named_with_dict(self):
917    ts = unittest.makeSuite(self.OtherDecoratorNamedWithDict)
918    # Verify it generates the test method names from the original test method.
919    for test in ts:  # There is only one test.
920      ts_attributes = dir(test)
921      self.assertIn('test_parameterized_then_other_a', ts_attributes)
922      self.assertIn('test_parameterized_then_other_b', ts_attributes)
923
924    res = unittest.TestResult()
925    ts.run(res)
926    # Two for when the parameterized tests call the skip wrapper.
927    # One for when the skip wrapper is called first and doesn't iterate.
928    self.assertEqual(3, res.testsRun)
929    self.assertFalse(res.wasSuccessful())
930    self.assertEmpty(res.failures)
931    # One error from test_other_then_parameterized.
932    self.assertLen(res.errors, 1)
933
934  def test_no_test_error_empty_parameters(self):
935    with self.assertRaises(parameterized.NoTestsError):
936
937      @parameterized.parameters()
938      def test_something():
939        pass
940
941      del test_something
942
943  def test_no_test_error_empty_generator(self):
944    with self.assertRaises(parameterized.NoTestsError):
945
946      @parameterized.parameters((i for i in []))
947      def test_something():
948        pass
949
950      del test_something
951
952  def test_unique_descriptive_names(self):
953    class RecordSuccessTestsResult(unittest.TestResult):
954
955      def __init__(self, *args, **kwargs):
956        super(RecordSuccessTestsResult, self).__init__(*args, **kwargs)
957        self.successful_tests = []
958
959      def addSuccess(self, test):
960        self.successful_tests.append(test)
961
962    ts = unittest.makeSuite(self.UniqueDescriptiveNamesTest)
963    res = RecordSuccessTestsResult()
964    ts.run(res)
965    self.assertTrue(res.wasSuccessful())
966    self.assertEqual(2, res.testsRun)
967    test_ids = [test.id() for test in res.successful_tests]
968    full_class_name = unittest.util.strclass(self.UniqueDescriptiveNamesTest)
969    expected_test_ids = [
970        full_class_name + '.test_normal0 (13)',
971        full_class_name + '.test_normal1 (13)',
972    ]
973    self.assertTrue(test_ids)
974    self.assertCountEqual(expected_test_ids, test_ids)
975
976  def test_multi_generators(self):
977    ts = unittest.makeSuite(self.MultiGeneratorsTestCase)
978    res = unittest.TestResult()
979    ts.run(res)
980    self.assertEqual(2, res.testsRun)
981    self.assertTrue(res.wasSuccessful(), msg=str(res.failures))
982
983  def test_named_parameters_reusable(self):
984    ts = unittest.makeSuite(self.NamedParametersReusableTestCase)
985    res = unittest.TestResult()
986    ts.run(res)
987    self.assertEqual(8, res.testsRun)
988    self.assertTrue(res.wasSuccessful(), msg=str(res.failures))
989
990  def test_subclass_inherits_superclass_test_params_reprs(self):
991    self.assertEqual(
992        {'test_name0': "('foo')", 'test_name1': "('bar')"},
993        self.SuperclassTestCase._test_params_reprs,
994    )
995    self.assertEqual(
996        {'test_name0': "('foo')", 'test_name1': "('bar')"},
997        self.SubclassTestCase._test_params_reprs,
998    )
999
1000
1001# IsolatedAsyncioTestCase is only available in Python 3.8+.
1002if sys.version_info[:2] >= (3, 8):
1003
1004  async def mult(x: float, y: float) -> float:
1005    return x * y
1006
1007  class AsyncTest(unittest.IsolatedAsyncioTestCase, parameterized.TestCase):
1008
1009    def setUp(self):
1010      super().setUp()
1011      self.verify_ran = False
1012
1013    def tearDown(self):
1014      super().tearDown()
1015
1016      # We need the additional check here because originally the test function
1017      # would run, but a coroutine results from the execution and is never
1018      # awaited, so it looked like a successful test run when in fact the
1019      # internal test logic never executed.  If you remove the check for
1020      # coroutine and run_until_complete, then set the parameters to fail they
1021      # never will.
1022      self.assertTrue(self.verify_ran)
1023
1024    @parameterized.parameters((1, 2, 2), (2, 2, 4), (3, 2, 6))
1025    async def test_multiply_expected_matches_actual(self, x, y, expected):
1026      self.assertEqual(await mult(x, y), expected)
1027      self.verify_ran = True
1028
1029
1030def _decorate_with_side_effects(func, self):
1031  self.sideeffect = True
1032  func(self)
1033
1034
1035class CoopMetaclassCreationTest(absltest.TestCase):
1036
1037  class TestBaseMetaclass(type):
1038
1039    def __init__(cls, name, bases, dct):
1040      type.__init__(cls, name, bases, dct)
1041      for member_name, obj in dct.items():
1042        if member_name.startswith('test'):
1043          setattr(
1044              cls,
1045              member_name,
1046              lambda self, f=obj: _decorate_with_side_effects(f, self),
1047          )
1048
1049  class TestBase(absltest.TestCase, metaclass=TestBaseMetaclass):
1050
1051    # This test simulates a metaclass that sets some attribute ('sideeffect')
1052    # on each member of the class that starts with 'test'. The test code then
1053    # checks that this attribute exists when the custom metaclass and
1054    # TestGeneratorMetaclass are combined with cooperative inheritance.
1055
1056    # The attribute has to be set in the __init__ method of the metaclass,
1057    # since the TestGeneratorMetaclass already overrides __new__. Only one
1058    # base metaclass can override __new__, but all can provide custom __init__
1059    # methods.
1060    pass
1061
1062  class MyParams(parameterized.CoopTestCase(TestBase)):
1063
1064    @parameterized.parameters((1, 2, 3), (4, 5, 9))
1065    def test_addition(self, op1, op2, result):
1066      self.assertEqual(result, op1 + op2)
1067
1068  class MySuite(unittest.TestSuite):
1069    # Under Python 3.4 the TestCases in the suite's list of tests to run are
1070    # destroyed and replaced with None after successful execution by default.
1071    # This disables that behavior.
1072    _cleanup = False
1073
1074  def test_successful_execution(self):
1075    ts = unittest.makeSuite(self.MyParams)
1076
1077    res = unittest.TestResult()
1078    ts.run(res)
1079    self.assertEqual(2, res.testsRun)
1080    self.assertTrue(res.wasSuccessful())
1081
1082  def test_metaclass_side_effects(self):
1083    ts = unittest.makeSuite(self.MyParams, suiteClass=self.MySuite)
1084
1085    res = unittest.TestResult()
1086    ts.run(res)
1087    self.assertTrue(list(ts)[0].sideeffect)
1088
1089  def test_no_metaclass(self):
1090    class SimpleMixinTestCase(absltest.TestCase):
1091      pass
1092
1093    with self.assertWarnsRegex(
1094        UserWarning,
1095        'CoopTestCase is only necessary when combining with a class that uses a'
1096        ' metaclass',
1097    ) as warning:
1098      parameterized.CoopTestCase(SimpleMixinTestCase)
1099    self.assertEqual(
1100        os.path.basename(warning.filename), 'parameterized_test.py'
1101    )
1102
1103
1104if __name__ == '__main__':
1105  absltest.main()
1106