issue 14660: Implement PEP 420, namespace packages.
diff --git a/Lib/test/namespace_pkgs/both_portions/foo/one.py b/Lib/test/namespace_pkgs/both_portions/foo/one.py
new file mode 100644
index 0000000..3080f6f
--- /dev/null
+++ b/Lib/test/namespace_pkgs/both_portions/foo/one.py
@@ -0,0 +1 @@
+attr = 'both_portions foo one'
diff --git a/Lib/test/namespace_pkgs/both_portions/foo/two.py b/Lib/test/namespace_pkgs/both_portions/foo/two.py
new file mode 100644
index 0000000..4131d3d
--- /dev/null
+++ b/Lib/test/namespace_pkgs/both_portions/foo/two.py
@@ -0,0 +1 @@
+attr = 'both_portions foo two'
diff --git a/Lib/test/namespace_pkgs/missing_directory.zip b/Lib/test/namespace_pkgs/missing_directory.zip
new file mode 100644
index 0000000..836a910
--- /dev/null
+++ b/Lib/test/namespace_pkgs/missing_directory.zip
Binary files differ
diff --git a/Lib/test/namespace_pkgs/nested_portion1.zip b/Lib/test/namespace_pkgs/nested_portion1.zip
new file mode 100644
index 0000000..8d22406
--- /dev/null
+++ b/Lib/test/namespace_pkgs/nested_portion1.zip
Binary files differ
diff --git a/Lib/test/namespace_pkgs/not_a_namespace_pkg/foo/__init__.py b/Lib/test/namespace_pkgs/not_a_namespace_pkg/foo/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Lib/test/namespace_pkgs/not_a_namespace_pkg/foo/__init__.py
diff --git a/Lib/test/namespace_pkgs/not_a_namespace_pkg/foo/one.py b/Lib/test/namespace_pkgs/not_a_namespace_pkg/foo/one.py
new file mode 100644
index 0000000..d8f5c83
--- /dev/null
+++ b/Lib/test/namespace_pkgs/not_a_namespace_pkg/foo/one.py
@@ -0,0 +1 @@
+attr = 'portion1 foo one'
diff --git a/Lib/test/namespace_pkgs/portion1/foo/one.py b/Lib/test/namespace_pkgs/portion1/foo/one.py
new file mode 100644
index 0000000..d8f5c83
--- /dev/null
+++ b/Lib/test/namespace_pkgs/portion1/foo/one.py
@@ -0,0 +1 @@
+attr = 'portion1 foo one'
diff --git a/Lib/test/namespace_pkgs/portion2/foo/two.py b/Lib/test/namespace_pkgs/portion2/foo/two.py
new file mode 100644
index 0000000..d092e1e
--- /dev/null
+++ b/Lib/test/namespace_pkgs/portion2/foo/two.py
@@ -0,0 +1 @@
+attr = 'portion2 foo two'
diff --git a/Lib/test/namespace_pkgs/project1/parent/child/one.py b/Lib/test/namespace_pkgs/project1/parent/child/one.py
new file mode 100644
index 0000000..2776fcd
--- /dev/null
+++ b/Lib/test/namespace_pkgs/project1/parent/child/one.py
@@ -0,0 +1 @@
+attr = 'parent child one'
diff --git a/Lib/test/namespace_pkgs/project2/parent/child/two.py b/Lib/test/namespace_pkgs/project2/parent/child/two.py
new file mode 100644
index 0000000..8b037bc
--- /dev/null
+++ b/Lib/test/namespace_pkgs/project2/parent/child/two.py
@@ -0,0 +1 @@
+attr = 'parent child two'
diff --git a/Lib/test/namespace_pkgs/project3/parent/child/three.py b/Lib/test/namespace_pkgs/project3/parent/child/three.py
new file mode 100644
index 0000000..f8abfe1
--- /dev/null
+++ b/Lib/test/namespace_pkgs/project3/parent/child/three.py
@@ -0,0 +1 @@
+attr = 'parent child three'
diff --git a/Lib/test/namespace_pkgs/top_level_portion1.zip b/Lib/test/namespace_pkgs/top_level_portion1.zip
new file mode 100644
index 0000000..3b866c9
--- /dev/null
+++ b/Lib/test/namespace_pkgs/top_level_portion1.zip
Binary files differ
diff --git a/Lib/test/test_frozen.py b/Lib/test/test_frozen.py
index dbd229b..fd6761c 100644
--- a/Lib/test/test_frozen.py
+++ b/Lib/test/test_frozen.py
@@ -7,7 +7,7 @@
 class FrozenTests(unittest.TestCase):
 
     module_attrs = frozenset(['__builtins__', '__cached__', '__doc__',
-                              '__file__', '__loader__', '__name__',
+                              '__loader__', '__name__',
                               '__package__'])
     package_attrs = frozenset(list(module_attrs) + ['__path__'])
 
diff --git a/Lib/test/test_import.py b/Lib/test/test_import.py
index 890041f..a90e627 100644
--- a/Lib/test/test_import.py
+++ b/Lib/test/test_import.py
@@ -286,12 +286,6 @@
         import test.support as y
         self.assertIs(y, test.support, y.__name__)
 
-    def test_import_initless_directory_warning(self):
-        with check_warnings(('', ImportWarning)):
-            # Just a random non-package directory we always expect to be
-            # somewhere in sys.path...
-            self.assertRaises(ImportError, __import__, "site-packages")
-
     def test_import_by_filename(self):
         path = os.path.abspath(TESTFN)
         encoding = sys.getfilesystemencoding()
diff --git a/Lib/test/test_module.py b/Lib/test/test_module.py
index 5617789..e5a2525 100644
--- a/Lib/test/test_module.py
+++ b/Lib/test/test_module.py
@@ -5,6 +5,15 @@
 import sys
 ModuleType = type(sys)
 
+class FullLoader:
+    @classmethod
+    def module_repr(cls, m):
+        return "<module '{}' (crafted)>".format(m.__name__)
+
+class BareLoader:
+    pass
+
+
 class ModuleTests(unittest.TestCase):
     def test_uninitialized(self):
         # An uninitialized module has no __dict__ or __name__,
@@ -80,8 +89,90 @@
         gc_collect()
         self.assertEqual(destroyed, [1])
 
+    def test_module_repr_minimal(self):
+        # reprs when modules have no __file__, __name__, or __loader__
+        m = ModuleType('foo')
+        del m.__name__
+        self.assertEqual(repr(m), "<module '?'>")
+
+    def test_module_repr_with_name(self):
+        m = ModuleType('foo')
+        self.assertEqual(repr(m), "<module 'foo'>")
+
+    def test_module_repr_with_name_and_filename(self):
+        m = ModuleType('foo')
+        m.__file__ = '/tmp/foo.py'
+        self.assertEqual(repr(m), "<module 'foo' from '/tmp/foo.py'>")
+
+    def test_module_repr_with_filename_only(self):
+        m = ModuleType('foo')
+        del m.__name__
+        m.__file__ = '/tmp/foo.py'
+        self.assertEqual(repr(m), "<module '?' from '/tmp/foo.py'>")
+
+    def test_module_repr_with_bare_loader_but_no_name(self):
+        m = ModuleType('foo')
+        del m.__name__
+        # Yes, a class not an instance.
+        m.__loader__ = BareLoader
+        self.assertEqual(
+            repr(m), "<module '?' (<class 'test.test_module.BareLoader'>)>")
+
+    def test_module_repr_with_full_loader_but_no_name(self):
+        # m.__loader__.module_repr() will fail because the module has no
+        # m.__name__.  This exception will get suppressed and instead the
+        # loader's repr will be used.
+        m = ModuleType('foo')
+        del m.__name__
+        # Yes, a class not an instance.
+        m.__loader__ = FullLoader
+        self.assertEqual(
+            repr(m), "<module '?' (<class 'test.test_module.FullLoader'>)>")
+
+    def test_module_repr_with_bare_loader(self):
+        m = ModuleType('foo')
+        # Yes, a class not an instance.
+        m.__loader__ = BareLoader
+        self.assertEqual(
+            repr(m), "<module 'foo' (<class 'test.test_module.BareLoader'>)>")
+
+    def test_module_repr_with_full_loader(self):
+        m = ModuleType('foo')
+        # Yes, a class not an instance.
+        m.__loader__ = FullLoader
+        self.assertEqual(
+            repr(m), "<module 'foo' (crafted)>")
+
+    def test_module_repr_with_bare_loader_and_filename(self):
+        # Because the loader has no module_repr(), use the file name.
+        m = ModuleType('foo')
+        # Yes, a class not an instance.
+        m.__loader__ = BareLoader
+        m.__file__ = '/tmp/foo.py'
+        self.assertEqual(repr(m), "<module 'foo' from '/tmp/foo.py'>")
+
+    def test_module_repr_with_full_loader_and_filename(self):
+        # Even though the module has an __file__, use __loader__.module_repr()
+        m = ModuleType('foo')
+        # Yes, a class not an instance.
+        m.__loader__ = FullLoader
+        m.__file__ = '/tmp/foo.py'
+        self.assertEqual(repr(m), "<module 'foo' (crafted)>")
+
+    def test_module_repr_builtin(self):
+        self.assertEqual(repr(sys), "<module 'sys' (built-in)>")
+
+    def test_module_repr_source(self):
+        r = repr(unittest)
+        self.assertEqual(r[:25], "<module 'unittest' from '")
+        self.assertEqual(r[-13:], "__init__.py'>")
+
+    # frozen and namespace module reprs are tested in importlib.
+
+
 def test_main():
     run_unittest(ModuleTests)
 
+
 if __name__ == '__main__':
     test_main()
diff --git a/Lib/test/test_namespace_pkgs.py b/Lib/test/test_namespace_pkgs.py
new file mode 100644
index 0000000..176ddd3
--- /dev/null
+++ b/Lib/test/test_namespace_pkgs.py
@@ -0,0 +1,239 @@
+import sys
+import contextlib
+import unittest
+import os
+
+import importlib.test.util
+from test.support import run_unittest
+
+# needed tests:
+#
+# need to test when nested, so that the top-level path isn't sys.path
+# need to test dynamic path detection, both at top-level and nested
+# with dynamic path, check when a loader is returned on path reload (that is,
+#  trying to switch from a namespace package to a regular package)
+
+
+@contextlib.contextmanager
+def sys_modules_context():
+    """
+    Make sure sys.modules is the same object and has the same content
+    when exiting the context as when entering.
+
+    Similar to importlib.test.util.uncache, but doesn't require explicit
+    names.
+    """
+    sys_modules_saved = sys.modules
+    sys_modules_copy = sys.modules.copy()
+    try:
+        yield
+    finally:
+        sys.modules = sys_modules_saved
+        sys.modules.clear()
+        sys.modules.update(sys_modules_copy)
+
+
+@contextlib.contextmanager
+def namespace_tree_context(**kwargs):
+    """
+    Save import state and sys.modules cache and restore it on exit.
+    Typical usage:
+
+    >>> with namespace_tree_context(path=['/tmp/xxyy/portion1',
+    ...         '/tmp/xxyy/portion2']):
+    ...     pass
+    """
+    # use default meta_path and path_hooks unless specified otherwise
+    kwargs.setdefault('meta_path', sys.meta_path)
+    kwargs.setdefault('path_hooks', sys.path_hooks)
+    import_context = importlib.test.util.import_state(**kwargs)
+    with import_context, sys_modules_context():
+        yield
+
+class NamespacePackageTest(unittest.TestCase):
+    """
+    Subclasses should define self.root and self.paths (under that root)
+    to be added to sys.path.
+    """
+    root = os.path.join(os.path.dirname(__file__), 'namespace_pkgs')
+
+    def setUp(self):
+        self.resolved_paths = [
+            os.path.join(self.root, path) for path in self.paths
+        ]
+        self.ctx = namespace_tree_context(path=self.resolved_paths)
+        self.ctx.__enter__()
+
+    def tearDown(self):
+        # TODO: will we ever want to pass exc_info to __exit__?
+        self.ctx.__exit__(None, None, None)
+
+class SingleNamespacePackage(NamespacePackageTest):
+    paths = ['portion1']
+
+    def test_simple_package(self):
+        import foo.one
+        self.assertEqual(foo.one.attr, 'portion1 foo one')
+
+    def test_cant_import_other(self):
+        with self.assertRaises(ImportError):
+            import foo.two
+
+    def test_module_repr(self):
+        import foo.one
+        self.assertEqual(repr(foo), "<module 'foo' (namespace)>")
+
+
+class DynamicPatheNamespacePackage(NamespacePackageTest):
+    paths = ['portion1']
+
+    def test_dynamic_path(self):
+        # Make sure only 'foo.one' can be imported
+        import foo.one
+        self.assertEqual(foo.one.attr, 'portion1 foo one')
+
+        with self.assertRaises(ImportError):
+            import foo.two
+
+        # Now modify sys.path
+        sys.path.append(os.path.join(self.root, 'portion2'))
+
+        # And make sure foo.two is now importable
+        import foo.two
+        self.assertEqual(foo.two.attr, 'portion2 foo two')
+
+
+class CombinedNamespacePackages(NamespacePackageTest):
+    paths = ['both_portions']
+
+    def test_imports(self):
+        import foo.one
+        import foo.two
+        self.assertEqual(foo.one.attr, 'both_portions foo one')
+        self.assertEqual(foo.two.attr, 'both_portions foo two')
+
+
+class SeparatedNamespacePackages(NamespacePackageTest):
+    paths = ['portion1', 'portion2']
+
+    def test_imports(self):
+        import foo.one
+        import foo.two
+        self.assertEqual(foo.one.attr, 'portion1 foo one')
+        self.assertEqual(foo.two.attr, 'portion2 foo two')
+
+
+class SeparatedOverlappingNamespacePackages(NamespacePackageTest):
+    paths = ['portion1', 'both_portions']
+
+    def test_first_path_wins(self):
+        import foo.one
+        import foo.two
+        self.assertEqual(foo.one.attr, 'portion1 foo one')
+        self.assertEqual(foo.two.attr, 'both_portions foo two')
+
+    def test_first_path_wins_again(self):
+        sys.path.reverse()
+        import foo.one
+        import foo.two
+        self.assertEqual(foo.one.attr, 'both_portions foo one')
+        self.assertEqual(foo.two.attr, 'both_portions foo two')
+
+    def test_first_path_wins_importing_second_first(self):
+        import foo.two
+        import foo.one
+        self.assertEqual(foo.one.attr, 'portion1 foo one')
+        self.assertEqual(foo.two.attr, 'both_portions foo two')
+
+
+class SingleZipNamespacePackage(NamespacePackageTest):
+    paths = ['top_level_portion1.zip']
+
+    def test_simple_package(self):
+        import foo.one
+        self.assertEqual(foo.one.attr, 'portion1 foo one')
+
+    def test_cant_import_other(self):
+        with self.assertRaises(ImportError):
+            import foo.two
+
+
+class SeparatedZipNamespacePackages(NamespacePackageTest):
+    paths = ['top_level_portion1.zip', 'portion2']
+
+    def test_imports(self):
+        import foo.one
+        import foo.two
+        self.assertEqual(foo.one.attr, 'portion1 foo one')
+        self.assertEqual(foo.two.attr, 'portion2 foo two')
+        self.assertIn('top_level_portion1.zip', foo.one.__file__)
+        self.assertNotIn('.zip', foo.two.__file__)
+
+
+class SingleNestedZipNamespacePackage(NamespacePackageTest):
+    paths = ['nested_portion1.zip/nested_portion1']
+
+    def test_simple_package(self):
+        import foo.one
+        self.assertEqual(foo.one.attr, 'portion1 foo one')
+
+    def test_cant_import_other(self):
+        with self.assertRaises(ImportError):
+            import foo.two
+
+
+class SeparatedNestedZipNamespacePackages(NamespacePackageTest):
+    paths = ['nested_portion1.zip/nested_portion1', 'portion2']
+
+    def test_imports(self):
+        import foo.one
+        import foo.two
+        self.assertEqual(foo.one.attr, 'portion1 foo one')
+        self.assertEqual(foo.two.attr, 'portion2 foo two')
+        fn = os.path.join('nested_portion1.zip', 'nested_portion1')
+        self.assertIn(fn, foo.one.__file__)
+        self.assertNotIn('.zip', foo.two.__file__)
+
+
+class LegacySupport(NamespacePackageTest):
+    paths = ['not_a_namespace_pkg', 'portion1', 'portion2', 'both_portions']
+
+    def test_non_namespace_package_takes_precedence(self):
+        import foo.one
+        with self.assertRaises(ImportError):
+            import foo.two
+        self.assertIn('__init__', foo.__file__)
+        self.assertNotIn('namespace', str(foo.__loader__).lower())
+
+
+class ZipWithMissingDirectory(NamespacePackageTest):
+    paths = ['missing_directory.zip']
+
+    @unittest.expectedFailure
+    def test_missing_directory(self):
+        # This will fail because missing_directory.zip contains:
+        #   Length      Date    Time    Name
+        # ---------  ---------- -----   ----
+        #        29  2012-05-03 18:13   foo/one.py
+        #         0  2012-05-03 20:57   bar/
+        #        38  2012-05-03 20:57   bar/two.py
+        # ---------                     -------
+        #        67                     3 files
+
+        # Because there is no 'foo/', the zipimporter currently doesn't
+        #  know that foo is a namespace package
+
+        import foo.one
+
+    def test_present_directory(self):
+        # This succeeds because there is a "bar/" in the zip file
+        import bar.two
+        self.assertEqual(bar.two.attr, 'missing_directory foo two')
+
+
+def test_main():
+    run_unittest(*NamespacePackageTest.__subclasses__())
+
+
+if __name__ == "__main__":
+    test_main()
diff --git a/Lib/test/test_pkgutil.py b/Lib/test/test_pkgutil.py
index 6025bcd..a41b5f5 100644
--- a/Lib/test/test_pkgutil.py
+++ b/Lib/test/test_pkgutil.py
@@ -138,10 +138,11 @@
         del sys.modules['foo']
 
 
+# These tests, especially the setup and cleanup, are hideous. They
+# need to be cleaned up once issue 14715 is addressed.
 class ExtendPathTests(unittest.TestCase):
     def create_init(self, pkgname):
         dirname = tempfile.mkdtemp()
-        self.addCleanup(shutil.rmtree, dirname)
         sys.path.insert(0, dirname)
 
         pkgdir = os.path.join(dirname, pkgname)
@@ -156,22 +157,12 @@
         with open(module_name, 'w') as fl:
             print('value={}'.format(value), file=fl)
 
-    def setUp(self):
-        # Create 2 directories on sys.path
-        self.pkgname = 'foo'
-        self.dirname_0 = self.create_init(self.pkgname)
-        self.dirname_1 = self.create_init(self.pkgname)
-
-    def tearDown(self):
-        del sys.path[0]
-        del sys.path[0]
-        del sys.modules['foo']
-        del sys.modules['foo.bar']
-        del sys.modules['foo.baz']
-
     def test_simple(self):
-        self.create_submodule(self.dirname_0, self.pkgname, 'bar', 0)
-        self.create_submodule(self.dirname_1, self.pkgname, 'baz', 1)
+        pkgname = 'foo'
+        dirname_0 = self.create_init(pkgname)
+        dirname_1 = self.create_init(pkgname)
+        self.create_submodule(dirname_0, pkgname, 'bar', 0)
+        self.create_submodule(dirname_1, pkgname, 'baz', 1)
         import foo.bar
         import foo.baz
         # Ensure we read the expected values
@@ -180,8 +171,45 @@
 
         # Ensure the path is set up correctly
         self.assertEqual(sorted(foo.__path__),
-                         sorted([os.path.join(self.dirname_0, self.pkgname),
-                                 os.path.join(self.dirname_1, self.pkgname)]))
+                         sorted([os.path.join(dirname_0, pkgname),
+                                 os.path.join(dirname_1, pkgname)]))
+
+        # Cleanup
+        shutil.rmtree(dirname_0)
+        shutil.rmtree(dirname_1)
+        del sys.path[0]
+        del sys.path[0]
+        del sys.modules['foo']
+        del sys.modules['foo.bar']
+        del sys.modules['foo.baz']
+
+    def test_mixed_namespace(self):
+        pkgname = 'foo'
+        dirname_0 = self.create_init(pkgname)
+        dirname_1 = self.create_init(pkgname)
+        self.create_submodule(dirname_0, pkgname, 'bar', 0)
+        # Turn this into a PEP 420 namespace package
+        os.unlink(os.path.join(dirname_0, pkgname, '__init__.py'))
+        self.create_submodule(dirname_1, pkgname, 'baz', 1)
+        import foo.bar
+        import foo.baz
+        # Ensure we read the expected values
+        self.assertEqual(foo.bar.value, 0)
+        self.assertEqual(foo.baz.value, 1)
+
+        # Ensure the path is set up correctly
+        self.assertEqual(sorted(foo.__path__),
+                         sorted([os.path.join(dirname_0, pkgname),
+                                 os.path.join(dirname_1, pkgname)]))
+
+        # Cleanup
+        shutil.rmtree(dirname_0)
+        shutil.rmtree(dirname_1)
+        del sys.path[0]
+        del sys.path[0]
+        del sys.modules['foo']
+        del sys.modules['foo.bar']
+        del sys.modules['foo.baz']
 
     # XXX: test .pkg files