From: Antoine Pitrou Date: Wed, 18 Mar 2015 23:01:37 +0000 (+0100) Subject: Issue #22903: The fake test case created by unittest.loader when it fails importing... X-Git-Tag: v3.5.0a3~137 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=8eef6a9ad04f6f81190f44ae3ded427e4083baa2;p=python Issue #22903: The fake test case created by unittest.loader when it fails importing a test module is now picklable. --- 8eef6a9ad04f6f81190f44ae3ded427e4083baa2 diff --cc Lib/unittest/loader.py index 8ee6c56153,af39216d26..8a1a2a71c5 --- a/Lib/unittest/loader.py +++ b/Lib/unittest/loader.py @@@ -20,23 -19,31 +20,34 @@@ __unittest = Tru VALID_MODULE_NAME = re.compile(r'[_a-z]\w*\.py$', re.IGNORECASE) + class _FailedTest(case.TestCase): + _testMethodName = None + + def __init__(self, method_name, exception): + self._exception = exception + super(_FailedTest, self).__init__(method_name) + + def __getattr__(self, name): + if name != self._testMethodName: + return super(_FailedTest, self).__getattr__(name) + def testFailure(): + raise self._exception + return testFailure + + def _make_failed_import_test(name, suiteClass): - message = 'Failed to import test module: %s\n%s' % (name, traceback.format_exc()) - return _make_failed_test(name, ImportError(message), suiteClass) + message = 'Failed to import test module: %s\n%s' % ( + name, traceback.format_exc()) - return _make_failed_test('ModuleImportFailure', name, ImportError(message), - suiteClass, message) ++ return _make_failed_test(name, ImportError(message), suiteClass, message) def _make_failed_load_tests(name, exception, suiteClass): - return _make_failed_test(name, exception, suiteClass) + message = 'Failed to call load_tests:\n%s' % (traceback.format_exc(),) + return _make_failed_test( - 'LoadTestsFailure', name, exception, suiteClass, message) ++ name, exception, suiteClass, message) - def _make_failed_test(classname, methodname, exception, suiteClass, message): - def testFailure(self): - raise exception - attrs = {methodname: testFailure} - TestClass = type(classname, (case.TestCase,), attrs) - return suiteClass((TestClass(methodname),)), message -def _make_failed_test(methodname, exception, suiteClass): ++def _make_failed_test(methodname, exception, suiteClass, message): + test = _FailedTest(methodname, exception) - return suiteClass((test,)) ++ return suiteClass((test,)), message def _make_skipped_test(methodname, exception, suiteClass): @case.skip(str(exception)) @@@ -153,27 -122,7 +164,27 @@@ class TestLoader(object) parts = parts[1:] obj = module for part in parts: - parent, obj = obj, getattr(obj, part) + try: + parent, obj = obj, getattr(obj, part) + except AttributeError as e: + # We can't traverse some part of the name. + if (getattr(obj, '__path__', None) is not None + and error_case is not None): + # This is a package (no __path__ per importlib docs), and we + # encountered an error importing something. We cannot tell + # the difference between package.WrongNameTestClass and + # package.wrong_module_name so we just report the + # ImportError - it is more informative. + self.errors.append(error_message) + return error_case + else: + # Otherwise, we signal that an AttributeError has occurred. + error_case, error_message = _make_failed_test( - 'AttributeError', part, e, self.suiteClass, ++ part, e, self.suiteClass, + 'Failed to access attribute:\n%s' % ( + traceback.format_exc(),)) + self.errors.append(error_message) + return error_case if isinstance(obj, types.ModuleType): return self.loadTestsFromModule(obj) diff --cc Lib/unittest/test/test_discovery.py index 4f61314ec6,f12e8983cd..8991f3851f --- a/Lib/unittest/test/test_discovery.py +++ b/Lib/unittest/test/test_discovery.py @@@ -456,32 -217,10 +457,36 @@@ class TestDiscovery(unittest.TestCase) with self.assertRaises(ImportError): test.test_this_does_not_exist() + def test_discover_with_init_modules_that_fail_to_import(self): + vfs = {abspath('/foo'): ['my_package'], + abspath('/foo/my_package'): ['__init__.py', 'test_module.py']} + self.setup_import_issue_package_tests(vfs) + import_calls = [] + def _get_module_from_name(name): + import_calls.append(name) + raise ImportError("Cannot import Name") + loader = unittest.TestLoader() + loader._get_module_from_name = _get_module_from_name + suite = loader.discover(abspath('/foo')) + + self.assertIn(abspath('/foo'), sys.path) + self.assertEqual(suite.countTestCases(), 1) + # Errors loading the suite are also captured for introspection. + self.assertNotEqual([], loader.errors) + self.assertEqual(1, len(loader.errors)) + error = loader.errors[0] + self.assertTrue( + 'Failed to import test module: my_package' in error, + 'missing error string in %r' % error) + test = list(list(suite)[0])[0] # extract test from suite + with self.assertRaises(ImportError): + test.my_package() + self.assertEqual(import_calls, ['my_package']) + + # Check picklability + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + pickle.loads(pickle.dumps(test, proto)) + def test_discover_with_module_that_raises_SkipTest_on_import(self): loader = unittest.TestLoader() @@@ -498,26 -237,10 +503,34 @@@ suite.run(result) self.assertEqual(len(result.skipped), 1) + # Check picklability + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + pickle.loads(pickle.dumps(suite, proto)) + + def test_discover_with_init_module_that_raises_SkipTest_on_import(self): + vfs = {abspath('/foo'): ['my_package'], + abspath('/foo/my_package'): ['__init__.py', 'test_module.py']} + self.setup_import_issue_package_tests(vfs) + import_calls = [] + def _get_module_from_name(name): + import_calls.append(name) + raise unittest.SkipTest('skipperoo') + loader = unittest.TestLoader() + loader._get_module_from_name = _get_module_from_name + suite = loader.discover(abspath('/foo')) + + self.assertIn(abspath('/foo'), sys.path) + self.assertEqual(suite.countTestCases(), 1) + result = unittest.TestResult() + suite.run(result) + self.assertEqual(len(result.skipped), 1) + self.assertEqual(result.testsRun, 1) + self.assertEqual(import_calls, ['my_package']) + ++ # Check picklability ++ for proto in range(pickle.HIGHEST_PROTOCOL + 1): ++ pickle.loads(pickle.dumps(suite, proto)) ++ def test_command_line_handling_parseArgs(self): program = TestableTestProgram() diff --cc Misc/NEWS index de3f381e7a,2ec41cd0c6..ccc294586d --- a/Misc/NEWS +++ b/Misc/NEWS @@@ -18,24 -18,9 +18,27 @@@ Core and Builtin Library ------- + - Issue #22903: The fake test case created by unittest.loader when it fails + importing a test module is now picklable. + +- Issue #22181: On Linux, os.urandom() now uses the new getrandom() syscall if + available, syscall introduced in the Linux kernel 3.17. It is more reliable + and more secure, because it avoids the need of a file descriptor and waits + until the kernel has enough entropy. + +- Issue #2211: Updated the implementation of the http.cookies.Morsel class. + Setting attributes key, value and coded_value directly now is deprecated. + update() and setdefault() now transform and check keys. Comparing for + equality now takes into account attributes key, value and coded_value. + copy() now returns a Morsel, not a dict. repr() now contains all attributes. + Optimized checking keys and quoting values. Added new tests. + Original patch by Demian Brecht. + +- Issue #18983: Allow selection of output units in timeit. + Patch by Julian Gindi. + +- Issue #23631: Fix traceback.format_list when a traceback has been mutated. + - Issue #23568: Add rdivmod support to MagicMock() objects. Patch by Håkan Lövdahl.