Issue #15767: Introduce ModuleNotFoundError, a subclass of
ImportError.

The exception is raised by import when a module could not be found.
Technically this is defined as no viable loader could be found for the
specified module. This includes ``from ... import`` statements so that
the module usage is consistent for all situations where import
couldn't find what was requested.

This should allow for the common idiom of::

  try:
    import something
  except ImportError:
    pass

to be updated to using ModuleNotFoundError and not accidentally mask
ImportError messages that should propagate (e.g. issues with a
loader).

This work was driven by the fact that the ``from ... import``
statement needed to be able to tell the difference between an
ImportError that simply couldn't find a module (and thus silence the
exception so that ceval can raise it) and an ImportError that
represented an actual problem.
diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py
index e477b55..cd41336 100644
--- a/Lib/importlib/_bootstrap.py
+++ b/Lib/importlib/_bootstrap.py
@@ -1553,11 +1553,7 @@
             raise ImportError(msg, name=name)
     loader = _find_module(name, path)
     if loader is None:
-        exc = ImportError(_ERR_MSG.format(name), name=name)
-        # TODO(brett): switch to a proper ModuleNotFound exception in Python
-        # 3.4.
-        exc._not_found = True
-        raise exc
+        raise ModuleNotFoundError(_ERR_MSG.format(name), name=name)
     elif name not in sys.modules:
         # The parent import may have already imported this module.
         loader.load_module(name)
@@ -1643,15 +1639,12 @@
                 from_name = '{}.{}'.format(module.__name__, x)
                 try:
                     _call_with_frames_removed(import_, from_name)
-                except ImportError as exc:
+                except ModuleNotFoundError as exc:
                     # Backwards-compatibility dictates we ignore failed
                     # imports triggered by fromlist for modules that don't
                     # exist.
-                    # TODO(brett): In Python 3.4, have import raise
-                    #   ModuleNotFound and catch that.
-                    if getattr(exc, '_not_found', False):
-                        if exc.name == from_name:
-                            continue
+                    if exc.name == from_name:
+                        continue
                     raise
     return module
 
diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index 3f6fdf0..6a2b44a 100755
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -317,7 +317,7 @@
         elif exc is SyntaxError:
             # A SyntaxError occurred before we could execute the module.
             raise ErrorDuringImport(value.filename, info)
-        elif exc is ImportError and value.name == path:
+        elif issubclass(exc, ImportError) and value.name == path:
             # No such module in the path.
             return None
         else:
diff --git a/Lib/test/exception_hierarchy.txt b/Lib/test/exception_hierarchy.txt
index 1c1f69f..88e1b81 100644
--- a/Lib/test/exception_hierarchy.txt
+++ b/Lib/test/exception_hierarchy.txt
@@ -13,6 +13,7 @@
       +-- BufferError
       +-- EOFError
       +-- ImportError
+           +-- ModuleNotFoundError
       +-- LookupError
       |    +-- IndexError
       |    +-- KeyError
diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py
index 2f39d4b..f0851bd 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -953,8 +953,5 @@
             self.assertEqual(str(arg), str(exc))
 
 
-def test_main():
-    run_unittest(ExceptionTests, ImportErrorTests)
-
 if __name__ == '__main__':
     unittest.main()
diff --git a/Lib/test/test_import.py b/Lib/test/test_import.py
index 9c14e15..805e9ed 100644
--- a/Lib/test/test_import.py
+++ b/Lib/test/test_import.py
@@ -68,7 +68,15 @@
     def tearDown(self):
         unload(TESTFN)
 
-    setUp = tearDown
+    def test_import_raises_ModuleNotFoundError(self):
+        with self.assertRaises(ModuleNotFoundError):
+            import something_that_should_not_exist_anywhere
+
+    def test_from_import_raises_ModuleNotFoundError(self):
+        with self.assertRaises(ModuleNotFoundError):
+            from something_that_should_not_exist_anywhere import blah
+        with self.assertRaises(ModuleNotFoundError):
+            from importlib import something_that_should_not_exist_anywhere
 
     def test_case_sensitivity(self):
         # Brief digression to test that import is case-sensitive:  if we got
@@ -487,7 +495,7 @@
             header = f.read(12)
             code = marshal.load(f)
         constants = list(code.co_consts)
-        foreign_code = test_main.__code__
+        foreign_code = importlib.import_module.__code__
         pos = constants.index(1)
         constants[pos] = foreign_code
         code = type(code)(code.co_argcount, code.co_kwonlyargcount,
@@ -1014,16 +1022,5 @@
             importlib.SourceLoader.load_module = old_load_module
 
 
-def test_main(verbose=None):
-    run_unittest(ImportTests, PycacheTests, FilePermissionTests,
-                 PycRewritingTests, PathsTests, RelativeImportTests,
-                 OverridingImportBuiltinTests,
-                 ImportlibBootstrapTests,
-                 TestSymbolicallyLinkedPackage,
-                 ImportTracebackTests)
-
-
 if __name__ == '__main__':
-    # Test needs to be a package, so we can do relative imports.
-    from test.test_import import test_main
-    test_main()
+    unittest.main()
diff --git a/Lib/test/test_importlib/import_/test_api.py b/Lib/test/test_importlib/import_/test_api.py
index 3d4cd94..6aca6dc 100644
--- a/Lib/test/test_importlib/import_/test_api.py
+++ b/Lib/test/test_importlib/import_/test_api.py
@@ -22,6 +22,10 @@
     """Test API-specific details for __import__ (e.g. raising the right
     exception when passing in an int for the module name)."""
 
+    def test_raises_ModuleNotFoundError(self):
+        with self.assertRaises(ModuleNotFoundError):
+            util.import_('some module that does not exist')
+
     def test_name_requires_rparition(self):
         # Raise TypeError if a non-string is passed in for the module name.
         with self.assertRaises(TypeError):
diff --git a/Lib/test/test_importlib/import_/test_fromlist.py b/Lib/test/test_importlib/import_/test_fromlist.py
index c16c337..9aa27a0 100644
--- a/Lib/test/test_importlib/import_/test_fromlist.py
+++ b/Lib/test/test_importlib/import_/test_fromlist.py
@@ -69,16 +69,16 @@
                 self.assertTrue(hasattr(module, 'module'))
                 self.assertEqual(module.module.__name__, 'pkg.module')
 
-    def test_module_from_package_triggers_ImportError(self):
-        # If a submodule causes an ImportError because it tries to import
-        # a module which doesn't exist, that should let the ImportError
+    def test_module_from_package_triggers_ModuleNotFoundError(self):
+        # If a submodule causes an ModuleNotFoundError because it tries to import
+        # a module which doesn't exist, that should let the ModuleNotFoundError
         # propagate.
         def module_code():
             import i_do_not_exist
         with util.mock_modules('pkg.__init__', 'pkg.mod',
                                module_code={'pkg.mod': module_code}) as importer:
             with util.import_state(meta_path=[importer]):
-                with self.assertRaises(ImportError) as exc:
+                with self.assertRaises(ModuleNotFoundError) as exc:
                     import_util.import_('pkg', fromlist=['mod'])
                 self.assertEqual('i_do_not_exist', exc.exception.name)
 
diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py
index 399f818..95ef115 100644
--- a/Lib/test/test_pydoc.py
+++ b/Lib/test/test_pydoc.py
@@ -206,7 +206,7 @@
 missing_pattern = "no Python documentation found for '%s'"
 
 # output pattern for module with bad imports
-badimport_pattern = "problem in %s - ImportError: No module named %r"
+badimport_pattern = "problem in %s - ModuleNotFoundError: No module named %r"
 
 def run_pydoc(module_name, *args, **env):
     """
diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py
index 9c7840f..06d7bd7 100644
--- a/Lib/test/test_site.py
+++ b/Lib/test/test_site.py
@@ -131,7 +131,7 @@
             re.escape(os.path.join(pth_dir, pth_fn)))
         # XXX: ditto previous XXX comment.
         self.assertRegex(err_out.getvalue(), 'Traceback')
-        self.assertRegex(err_out.getvalue(), 'ImportError')
+        self.assertRegex(err_out.getvalue(), 'ModuleNotFoundError')
 
     @unittest.skipIf(sys.platform == "win32", "Windows does not raise an "
                       "error for file paths containing null characters")