1  import contextlib
       2  import importlib
       3  import importlib.abc
       4  import importlib.machinery
       5  import os
       6  import sys
       7  import tempfile
       8  import unittest
       9  import warnings
      10  
      11  from test.test_importlib import util
      12  
      13  # needed tests:
      14  #
      15  # need to test when nested, so that the top-level path isn't sys.path
      16  # need to test dynamic path detection, both at top-level and nested
      17  # with dynamic path, check when a loader is returned on path reload (that is,
      18  #  trying to switch from a namespace package to a regular package)
      19  
      20  
      21  @contextlib.contextmanager
      22  def sys_modules_context():
      23      """
      24      Make sure sys.modules is the same object and has the same content
      25      when exiting the context as when entering.
      26  
      27      Similar to importlib.test.util.uncache, but doesn't require explicit
      28      names.
      29      """
      30      sys_modules_saved = sys.modules
      31      sys_modules_copy = sys.modules.copy()
      32      try:
      33          yield
      34      finally:
      35          sys.modules = sys_modules_saved
      36          sys.modules.clear()
      37          sys.modules.update(sys_modules_copy)
      38  
      39  
      40  @contextlib.contextmanager
      41  def namespace_tree_context(**kwargs):
      42      """
      43      Save import state and sys.modules cache and restore it on exit.
      44      Typical usage:
      45  
      46      >>> with namespace_tree_context(path=['/tmp/xxyy/portion1',
      47      ...         '/tmp/xxyy/portion2']):
      48      ...     pass
      49      """
      50      # use default meta_path and path_hooks unless specified otherwise
      51      kwargs.setdefault('meta_path', sys.meta_path)
      52      kwargs.setdefault('path_hooks', sys.path_hooks)
      53      import_context = util.import_state(**kwargs)
      54      with import_context, sys_modules_context():
      55          yield
      56  
      57  class ESC[4;38;5;81mNamespacePackageTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
      58      """
      59      Subclasses should define self.root and self.paths (under that root)
      60      to be added to sys.path.
      61      """
      62      root = os.path.join(os.path.dirname(__file__), 'namespace_pkgs')
      63  
      64      def setUp(self):
      65          self.resolved_paths = [
      66              os.path.join(self.root, path) for path in self.paths
      67          ]
      68          self.enterContext(namespace_tree_context(path=self.resolved_paths))
      69  
      70  
      71  class ESC[4;38;5;81mSingleNamespacePackage(ESC[4;38;5;149mNamespacePackageTest):
      72      paths = ['portion1']
      73  
      74      def test_simple_package(self):
      75          import foo.one
      76          self.assertEqual(foo.one.attr, 'portion1 foo one')
      77  
      78      def test_cant_import_other(self):
      79          with self.assertRaises(ImportError):
      80              import foo.two
      81  
      82      def test_simple_repr(self):
      83          import foo.one
      84          assert repr(foo).startswith("<module 'foo' (namespace) from [")
      85  
      86  
      87  class ESC[4;38;5;81mDynamicPathNamespacePackage(ESC[4;38;5;149mNamespacePackageTest):
      88      paths = ['portion1']
      89  
      90      def test_dynamic_path(self):
      91          # Make sure only 'foo.one' can be imported
      92          import foo.one
      93          self.assertEqual(foo.one.attr, 'portion1 foo one')
      94  
      95          with self.assertRaises(ImportError):
      96              import foo.two
      97  
      98          # Now modify sys.path
      99          sys.path.append(os.path.join(self.root, 'portion2'))
     100  
     101          # And make sure foo.two is now importable
     102          import foo.two
     103          self.assertEqual(foo.two.attr, 'portion2 foo two')
     104  
     105  
     106  class ESC[4;38;5;81mCombinedNamespacePackages(ESC[4;38;5;149mNamespacePackageTest):
     107      paths = ['both_portions']
     108  
     109      def test_imports(self):
     110          import foo.one
     111          import foo.two
     112          self.assertEqual(foo.one.attr, 'both_portions foo one')
     113          self.assertEqual(foo.two.attr, 'both_portions foo two')
     114  
     115  
     116  class ESC[4;38;5;81mSeparatedNamespacePackages(ESC[4;38;5;149mNamespacePackageTest):
     117      paths = ['portion1', 'portion2']
     118  
     119      def test_imports(self):
     120          import foo.one
     121          import foo.two
     122          self.assertEqual(foo.one.attr, 'portion1 foo one')
     123          self.assertEqual(foo.two.attr, 'portion2 foo two')
     124  
     125  
     126  class ESC[4;38;5;81mSeparatedNamespacePackagesCreatedWhileRunning(ESC[4;38;5;149mNamespacePackageTest):
     127      paths = ['portion1']
     128  
     129      def test_invalidate_caches(self):
     130          with tempfile.TemporaryDirectory() as temp_dir:
     131              # we manipulate sys.path before anything is imported to avoid
     132              # accidental cache invalidation when changing it
     133              sys.path.append(temp_dir)
     134  
     135              import foo.one
     136              self.assertEqual(foo.one.attr, 'portion1 foo one')
     137  
     138              # the module does not exist, so it cannot be imported
     139              with self.assertRaises(ImportError):
     140                  import foo.just_created
     141  
     142              # util.create_modules() manipulates sys.path
     143              # so we must create the modules manually instead
     144              namespace_path = os.path.join(temp_dir, 'foo')
     145              os.mkdir(namespace_path)
     146              module_path = os.path.join(namespace_path, 'just_created.py')
     147              with open(module_path, 'w', encoding='utf-8') as file:
     148                  file.write('attr = "just_created foo"')
     149  
     150              # the module is not known, so it cannot be imported yet
     151              with self.assertRaises(ImportError):
     152                  import foo.just_created
     153  
     154              # but after explicit cache invalidation, it is importable
     155              importlib.invalidate_caches()
     156              import foo.just_created
     157              self.assertEqual(foo.just_created.attr, 'just_created foo')
     158  
     159  
     160  class ESC[4;38;5;81mSeparatedOverlappingNamespacePackages(ESC[4;38;5;149mNamespacePackageTest):
     161      paths = ['portion1', 'both_portions']
     162  
     163      def test_first_path_wins(self):
     164          import foo.one
     165          import foo.two
     166          self.assertEqual(foo.one.attr, 'portion1 foo one')
     167          self.assertEqual(foo.two.attr, 'both_portions foo two')
     168  
     169      def test_first_path_wins_again(self):
     170          sys.path.reverse()
     171          import foo.one
     172          import foo.two
     173          self.assertEqual(foo.one.attr, 'both_portions foo one')
     174          self.assertEqual(foo.two.attr, 'both_portions foo two')
     175  
     176      def test_first_path_wins_importing_second_first(self):
     177          import foo.two
     178          import foo.one
     179          self.assertEqual(foo.one.attr, 'portion1 foo one')
     180          self.assertEqual(foo.two.attr, 'both_portions foo two')
     181  
     182  
     183  class ESC[4;38;5;81mSingleZipNamespacePackage(ESC[4;38;5;149mNamespacePackageTest):
     184      paths = ['top_level_portion1.zip']
     185  
     186      def test_simple_package(self):
     187          import foo.one
     188          self.assertEqual(foo.one.attr, 'portion1 foo one')
     189  
     190      def test_cant_import_other(self):
     191          with self.assertRaises(ImportError):
     192              import foo.two
     193  
     194  
     195  class ESC[4;38;5;81mSeparatedZipNamespacePackages(ESC[4;38;5;149mNamespacePackageTest):
     196      paths = ['top_level_portion1.zip', 'portion2']
     197  
     198      def test_imports(self):
     199          import foo.one
     200          import foo.two
     201          self.assertEqual(foo.one.attr, 'portion1 foo one')
     202          self.assertEqual(foo.two.attr, 'portion2 foo two')
     203          self.assertIn('top_level_portion1.zip', foo.one.__file__)
     204          self.assertNotIn('.zip', foo.two.__file__)
     205  
     206  
     207  class ESC[4;38;5;81mSingleNestedZipNamespacePackage(ESC[4;38;5;149mNamespacePackageTest):
     208      paths = ['nested_portion1.zip/nested_portion1']
     209  
     210      def test_simple_package(self):
     211          import foo.one
     212          self.assertEqual(foo.one.attr, 'portion1 foo one')
     213  
     214      def test_cant_import_other(self):
     215          with self.assertRaises(ImportError):
     216              import foo.two
     217  
     218  
     219  class ESC[4;38;5;81mSeparatedNestedZipNamespacePackages(ESC[4;38;5;149mNamespacePackageTest):
     220      paths = ['nested_portion1.zip/nested_portion1', 'portion2']
     221  
     222      def test_imports(self):
     223          import foo.one
     224          import foo.two
     225          self.assertEqual(foo.one.attr, 'portion1 foo one')
     226          self.assertEqual(foo.two.attr, 'portion2 foo two')
     227          fn = os.path.join('nested_portion1.zip', 'nested_portion1')
     228          self.assertIn(fn, foo.one.__file__)
     229          self.assertNotIn('.zip', foo.two.__file__)
     230  
     231  
     232  class ESC[4;38;5;81mLegacySupport(ESC[4;38;5;149mNamespacePackageTest):
     233      paths = ['not_a_namespace_pkg', 'portion1', 'portion2', 'both_portions']
     234  
     235      def test_non_namespace_package_takes_precedence(self):
     236          import foo.one
     237          with self.assertRaises(ImportError):
     238              import foo.two
     239          self.assertIn('__init__', foo.__file__)
     240          self.assertNotIn('namespace', str(foo.__loader__).lower())
     241  
     242  
     243  class ESC[4;38;5;81mDynamicPathCalculation(ESC[4;38;5;149mNamespacePackageTest):
     244      paths = ['project1', 'project2']
     245  
     246      def test_project3_fails(self):
     247          import parent.child.one
     248          self.assertEqual(len(parent.__path__), 2)
     249          self.assertEqual(len(parent.child.__path__), 2)
     250          import parent.child.two
     251          self.assertEqual(len(parent.__path__), 2)
     252          self.assertEqual(len(parent.child.__path__), 2)
     253  
     254          self.assertEqual(parent.child.one.attr, 'parent child one')
     255          self.assertEqual(parent.child.two.attr, 'parent child two')
     256  
     257          with self.assertRaises(ImportError):
     258              import parent.child.three
     259  
     260          self.assertEqual(len(parent.__path__), 2)
     261          self.assertEqual(len(parent.child.__path__), 2)
     262  
     263      def test_project3_succeeds(self):
     264          import parent.child.one
     265          self.assertEqual(len(parent.__path__), 2)
     266          self.assertEqual(len(parent.child.__path__), 2)
     267          import parent.child.two
     268          self.assertEqual(len(parent.__path__), 2)
     269          self.assertEqual(len(parent.child.__path__), 2)
     270  
     271          self.assertEqual(parent.child.one.attr, 'parent child one')
     272          self.assertEqual(parent.child.two.attr, 'parent child two')
     273  
     274          with self.assertRaises(ImportError):
     275              import parent.child.three
     276  
     277          # now add project3
     278          sys.path.append(os.path.join(self.root, 'project3'))
     279          import parent.child.three
     280  
     281          # the paths dynamically get longer, to include the new directories
     282          self.assertEqual(len(parent.__path__), 3)
     283          self.assertEqual(len(parent.child.__path__), 3)
     284  
     285          self.assertEqual(parent.child.three.attr, 'parent child three')
     286  
     287  
     288  class ESC[4;38;5;81mZipWithMissingDirectory(ESC[4;38;5;149mNamespacePackageTest):
     289      paths = ['missing_directory.zip']
     290  
     291      @unittest.expectedFailure
     292      def test_missing_directory(self):
     293          # This will fail because missing_directory.zip contains:
     294          #   Length      Date    Time    Name
     295          # ---------  ---------- -----   ----
     296          #        29  2012-05-03 18:13   foo/one.py
     297          #         0  2012-05-03 20:57   bar/
     298          #        38  2012-05-03 20:57   bar/two.py
     299          # ---------                     -------
     300          #        67                     3 files
     301  
     302          # Because there is no 'foo/', the zipimporter currently doesn't
     303          #  know that foo is a namespace package
     304  
     305          import foo.one
     306  
     307      def test_present_directory(self):
     308          # This succeeds because there is a "bar/" in the zip file
     309          import bar.two
     310          self.assertEqual(bar.two.attr, 'missing_directory foo two')
     311  
     312  
     313  class ESC[4;38;5;81mModuleAndNamespacePackageInSameDir(ESC[4;38;5;149mNamespacePackageTest):
     314      paths = ['module_and_namespace_package']
     315  
     316      def test_module_before_namespace_package(self):
     317          # Make sure we find the module in preference to the
     318          #  namespace package.
     319          import a_test
     320          self.assertEqual(a_test.attr, 'in module')
     321  
     322  
     323  class ESC[4;38;5;81mReloadTests(ESC[4;38;5;149mNamespacePackageTest):
     324      paths = ['portion1']
     325  
     326      def test_simple_package(self):
     327          import foo.one
     328          foo = importlib.reload(foo)
     329          self.assertEqual(foo.one.attr, 'portion1 foo one')
     330  
     331      def test_cant_import_other(self):
     332          import foo
     333          with self.assertRaises(ImportError):
     334              import foo.two
     335          foo = importlib.reload(foo)
     336          with self.assertRaises(ImportError):
     337              import foo.two
     338  
     339      def test_dynamic_path(self):
     340          import foo.one
     341          with self.assertRaises(ImportError):
     342              import foo.two
     343  
     344          # Now modify sys.path and reload.
     345          sys.path.append(os.path.join(self.root, 'portion2'))
     346          foo = importlib.reload(foo)
     347  
     348          # And make sure foo.two is now importable
     349          import foo.two
     350          self.assertEqual(foo.two.attr, 'portion2 foo two')
     351  
     352  
     353  class ESC[4;38;5;81mLoaderTests(ESC[4;38;5;149mNamespacePackageTest):
     354      paths = ['portion1']
     355  
     356      def test_namespace_loader_consistency(self):
     357          # bpo-32303
     358          import foo
     359          self.assertEqual(foo.__loader__, foo.__spec__.loader)
     360          self.assertIsNotNone(foo.__loader__)
     361  
     362      def test_namespace_origin_consistency(self):
     363          # bpo-32305
     364          import foo
     365          self.assertIsNone(foo.__spec__.origin)
     366          self.assertIsNone(foo.__file__)
     367  
     368      def test_path_indexable(self):
     369          # bpo-35843
     370          import foo
     371          expected_path = os.path.join(self.root, 'portion1', 'foo')
     372          self.assertEqual(foo.__path__[0], expected_path)
     373  
     374      def test_loader_abc(self):
     375          import foo
     376          self.assertTrue(isinstance(foo.__loader__, importlib.abc.Loader))
     377          self.assertTrue(isinstance(foo.__loader__, importlib.machinery.NamespaceLoader))
     378  
     379  
     380  if __name__ == "__main__":
     381      unittest.main()