]> granicus.if.org Git - python/commitdiff
Issue #22903: The fake test case created by unittest.loader when it fails importing...
authorAntoine Pitrou <solipsis@pitrou.net>
Wed, 18 Mar 2015 23:01:37 +0000 (00:01 +0100)
committerAntoine Pitrou <solipsis@pitrou.net>
Wed, 18 Mar 2015 23:01:37 +0000 (00:01 +0100)
1  2 
Lib/unittest/loader.py
Lib/unittest/test/test_discovery.py
Misc/NEWS

index 8ee6c56153122f23498f7a94a757f782f357ef52,af39216d2638b0ed907b470de6376cf8557af5c2..8a1a2a71c5dfebfdb9a0e7612b16a1e4c20a9505
@@@ -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)
index 4f61314ec6faf32ba1116d72d61408c2db8e4d12,f12e8983cdb129ec1c1a0a565b42befb853de177..8991f3851f60745183dc635f2e3a9a770cc71f1d
@@@ -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()
  
          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 de3f381e7af0ea12b51f004a8aedb784bf59b076,2ec41cd0c623ebadd51ecb48748d5d7be0d01c6e..ccc294586d87062eb49032aa7eb551ea1c35fa69
+++ 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.