diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index 993aaec1940..f19afef48d7 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -338,6 +338,16 @@ class _AssertLogsContext(_BaseTestCaseContext): .format(logging.getLevelName(self.level), self.logger.name)) +class _OrderedChainMap(collections.ChainMap): + def __iter__(self): + seen = set() + for mapping in self.maps: + for k in mapping: + if k not in seen: + seen.add(k) + yield k + + class TestCase(object): """A class whose instances are single test cases. @@ -514,7 +524,7 @@ class TestCase(object): return parent = self._subtest if parent is None: - params_map = collections.ChainMap(params) + params_map = _OrderedChainMap(params) else: params_map = parent.params.new_child(params) self._subtest = _SubTest(self, msg, params_map) @@ -1418,7 +1428,7 @@ class _SubTest(TestCase): if self.params: params_desc = ', '.join( "{}={!r}".format(k, v) - for (k, v) in sorted(self.params.items())) + for (k, v) in self.params.items()) parts.append("({})".format(params_desc)) return " ".join(parts) or '()' diff --git a/Lib/unittest/test/test_result.py b/Lib/unittest/test/test_result.py index 0a615535637..0ffb87b4025 100644 --- a/Lib/unittest/test/test_result.py +++ b/Lib/unittest/test/test_result.py @@ -307,7 +307,7 @@ class Test_TestResult(unittest.TestCase): self.assertEqual( result.getDescription(self._subtest), 'testGetSubTestDescriptionWithoutDocstring (' + __name__ + - '.Test_TestResult) (bar=2, foo=1)') + '.Test_TestResult) (foo=1, bar=2)') with self.subTest('some message'): result = unittest.TextTestResult(None, True, 1) self.assertEqual( @@ -335,12 +335,21 @@ class Test_TestResult(unittest.TestCase): def testGetNestedSubTestDescriptionWithoutDocstring(self): with self.subTest(foo=1): - with self.subTest(bar=2): + with self.subTest(baz=2, bar=3): result = unittest.TextTestResult(None, True, 1) self.assertEqual( result.getDescription(self._subtest), 'testGetNestedSubTestDescriptionWithoutDocstring ' - '(' + __name__ + '.Test_TestResult) (bar=2, foo=1)') + '(' + __name__ + '.Test_TestResult) (baz=2, bar=3, foo=1)') + + def testGetDuplicatedNestedSubTestDescriptionWithoutDocstring(self): + with self.subTest(foo=1, bar=2): + with self.subTest(baz=3, bar=4): + result = unittest.TextTestResult(None, True, 1) + self.assertEqual( + result.getDescription(self._subtest), + 'testGetDuplicatedNestedSubTestDescriptionWithoutDocstring ' + '(' + __name__ + '.Test_TestResult) (baz=3, bar=4, foo=1)') @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") @@ -362,7 +371,7 @@ class Test_TestResult(unittest.TestCase): self.assertEqual( result.getDescription(self._subtest), ('testGetSubTestDescriptionWithOneLineDocstring ' - '(' + __name__ + '.Test_TestResult) (bar=2, foo=1)\n' + '(' + __name__ + '.Test_TestResult) (foo=1, bar=2)\n' 'Tests getDescription() for a method with a docstring.')) @unittest.skipIf(sys.flags.optimize >= 2, @@ -390,7 +399,7 @@ class Test_TestResult(unittest.TestCase): self.assertEqual( result.getDescription(self._subtest), ('testGetSubTestDescriptionWithMultiLineDocstring ' - '(' + __name__ + '.Test_TestResult) (bar=2, foo=1)\n' + '(' + __name__ + '.Test_TestResult) (foo=1, bar=2)\n' 'Tests getDescription() for a method with a longer ' 'docstring.')) diff --git a/Misc/NEWS b/Misc/NEWS index fc9e1dee6fc..f33589d0085 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -374,6 +374,9 @@ Extension Modules Library ------- +- bpo-30664: The description of a unittest subtest now preserves the order of + keyword arguments of TestCase.subTest(). + - [Security] bpo-30730: Prevent environment variables injection in subprocess on Windows. Prevent passing other environment variables and command arguments.