(root)/
Python-3.12.0/
Lib/
test/
test_importlib/
extension/
test_loader.py
       1  from warnings import catch_warnings
       2  from test.test_importlib import abc, util
       3  
       4  machinery = util.import_importlib('importlib.machinery')
       5  
       6  import os.path
       7  import sys
       8  import types
       9  import unittest
      10  import warnings
      11  import importlib.util
      12  import importlib
      13  from test.support.script_helper import assert_python_failure
      14  
      15  
      16  class ESC[4;38;5;81mLoaderTests:
      17  
      18      """Test ExtensionFileLoader."""
      19  
      20      def setUp(self):
      21          if not self.machinery.EXTENSION_SUFFIXES:
      22              raise unittest.SkipTest("Requires dynamic loading support.")
      23          if util.EXTENSIONS.name in sys.builtin_module_names:
      24              raise unittest.SkipTest(
      25                  f"{util.EXTENSIONS.name} is a builtin module"
      26              )
      27          self.loader = self.machinery.ExtensionFileLoader(util.EXTENSIONS.name,
      28                                                           util.EXTENSIONS.file_path)
      29  
      30      def load_module(self, fullname):
      31          with warnings.catch_warnings():
      32              warnings.simplefilter("ignore", DeprecationWarning)
      33              return self.loader.load_module(fullname)
      34  
      35      def test_equality(self):
      36          other = self.machinery.ExtensionFileLoader(util.EXTENSIONS.name,
      37                                                     util.EXTENSIONS.file_path)
      38          self.assertEqual(self.loader, other)
      39  
      40      def test_inequality(self):
      41          other = self.machinery.ExtensionFileLoader('_' + util.EXTENSIONS.name,
      42                                                     util.EXTENSIONS.file_path)
      43          self.assertNotEqual(self.loader, other)
      44  
      45      def test_load_module_API(self):
      46          # Test the default argument for load_module().
      47          with warnings.catch_warnings():
      48              warnings.simplefilter("ignore", DeprecationWarning)
      49              self.loader.load_module()
      50              self.loader.load_module(None)
      51              with self.assertRaises(ImportError):
      52                  self.load_module('XXX')
      53  
      54      def test_module(self):
      55          with util.uncache(util.EXTENSIONS.name):
      56              module = self.load_module(util.EXTENSIONS.name)
      57              for attr, value in [('__name__', util.EXTENSIONS.name),
      58                                  ('__file__', util.EXTENSIONS.file_path),
      59                                  ('__package__', '')]:
      60                  self.assertEqual(getattr(module, attr), value)
      61              self.assertIn(util.EXTENSIONS.name, sys.modules)
      62              self.assertIsInstance(module.__loader__,
      63                                    self.machinery.ExtensionFileLoader)
      64  
      65      # No extension module as __init__ available for testing.
      66      test_package = None
      67  
      68      # No extension module in a package available for testing.
      69      test_lacking_parent = None
      70  
      71      # No easy way to trigger a failure after a successful import.
      72      test_state_after_failure = None
      73  
      74      def test_unloadable(self):
      75          name = 'asdfjkl;'
      76          with self.assertRaises(ImportError) as cm:
      77              self.load_module(name)
      78          self.assertEqual(cm.exception.name, name)
      79  
      80      def test_module_reuse(self):
      81          with util.uncache(util.EXTENSIONS.name):
      82              module1 = self.load_module(util.EXTENSIONS.name)
      83              module2 = self.load_module(util.EXTENSIONS.name)
      84              self.assertIs(module1, module2)
      85  
      86      def test_is_package(self):
      87          self.assertFalse(self.loader.is_package(util.EXTENSIONS.name))
      88          for suffix in self.machinery.EXTENSION_SUFFIXES:
      89              path = os.path.join('some', 'path', 'pkg', '__init__' + suffix)
      90              loader = self.machinery.ExtensionFileLoader('pkg', path)
      91              self.assertTrue(loader.is_package('pkg'))
      92  
      93  
      94  (Frozen_LoaderTests,
      95   Source_LoaderTests
      96   ) = util.test_both(LoaderTests, machinery=machinery)
      97  
      98  
      99  class ESC[4;38;5;81mSinglePhaseExtensionModuleTests(ESC[4;38;5;149mabcESC[4;38;5;149m.ESC[4;38;5;149mLoaderTests):
     100      # Test loading extension modules without multi-phase initialization.
     101  
     102      def setUp(self):
     103          if not self.machinery.EXTENSION_SUFFIXES:
     104              raise unittest.SkipTest("Requires dynamic loading support.")
     105          self.name = '_testsinglephase'
     106          if self.name in sys.builtin_module_names:
     107              raise unittest.SkipTest(
     108                  f"{self.name} is a builtin module"
     109              )
     110          finder = self.machinery.FileFinder(None)
     111          self.spec = importlib.util.find_spec(self.name)
     112          assert self.spec
     113          self.loader = self.machinery.ExtensionFileLoader(
     114              self.name, self.spec.origin)
     115  
     116      def load_module(self):
     117          with warnings.catch_warnings():
     118              warnings.simplefilter("ignore", DeprecationWarning)
     119              return self.loader.load_module(self.name)
     120  
     121      def load_module_by_name(self, fullname):
     122          # Load a module from the test extension by name.
     123          origin = self.spec.origin
     124          loader = self.machinery.ExtensionFileLoader(fullname, origin)
     125          spec = importlib.util.spec_from_loader(fullname, loader)
     126          module = importlib.util.module_from_spec(spec)
     127          loader.exec_module(module)
     128          return module
     129  
     130      def test_module(self):
     131          # Test loading an extension module.
     132          with util.uncache(self.name):
     133              module = self.load_module()
     134              for attr, value in [('__name__', self.name),
     135                                  ('__file__', self.spec.origin),
     136                                  ('__package__', '')]:
     137                  self.assertEqual(getattr(module, attr), value)
     138              with self.assertRaises(AttributeError):
     139                  module.__path__
     140              self.assertIs(module, sys.modules[self.name])
     141              self.assertIsInstance(module.__loader__,
     142                                    self.machinery.ExtensionFileLoader)
     143  
     144      # No extension module as __init__ available for testing.
     145      test_package = None
     146  
     147      # No extension module in a package available for testing.
     148      test_lacking_parent = None
     149  
     150      # No easy way to trigger a failure after a successful import.
     151      test_state_after_failure = None
     152  
     153      def test_unloadable(self):
     154          name = 'asdfjkl;'
     155          with self.assertRaises(ImportError) as cm:
     156              self.load_module_by_name(name)
     157          self.assertEqual(cm.exception.name, name)
     158  
     159      def test_unloadable_nonascii(self):
     160          # Test behavior with nonexistent module with non-ASCII name.
     161          name = 'fo\xf3'
     162          with self.assertRaises(ImportError) as cm:
     163              self.load_module_by_name(name)
     164          self.assertEqual(cm.exception.name, name)
     165  
     166      # It may make sense to add the equivalent to
     167      # the following MultiPhaseExtensionModuleTests tests:
     168      #
     169      #  * test_nonmodule
     170      #  * test_nonmodule_with_methods
     171      #  * test_bad_modules
     172      #  * test_nonascii
     173  
     174  
     175  (Frozen_SinglePhaseExtensionModuleTests,
     176   Source_SinglePhaseExtensionModuleTests
     177   ) = util.test_both(SinglePhaseExtensionModuleTests, machinery=machinery)
     178  
     179  
     180  class ESC[4;38;5;81mMultiPhaseExtensionModuleTests(ESC[4;38;5;149mabcESC[4;38;5;149m.ESC[4;38;5;149mLoaderTests):
     181      # Test loading extension modules with multi-phase initialization (PEP 489).
     182  
     183      def setUp(self):
     184          if not self.machinery.EXTENSION_SUFFIXES:
     185              raise unittest.SkipTest("Requires dynamic loading support.")
     186          self.name = '_testmultiphase'
     187          if self.name in sys.builtin_module_names:
     188              raise unittest.SkipTest(
     189                  f"{self.name} is a builtin module"
     190              )
     191          finder = self.machinery.FileFinder(None)
     192          self.spec = importlib.util.find_spec(self.name)
     193          assert self.spec
     194          self.loader = self.machinery.ExtensionFileLoader(
     195              self.name, self.spec.origin)
     196  
     197      def load_module(self):
     198          # Load the module from the test extension.
     199          with warnings.catch_warnings():
     200              warnings.simplefilter("ignore", DeprecationWarning)
     201              return self.loader.load_module(self.name)
     202  
     203      def load_module_by_name(self, fullname):
     204          # Load a module from the test extension by name.
     205          origin = self.spec.origin
     206          loader = self.machinery.ExtensionFileLoader(fullname, origin)
     207          spec = importlib.util.spec_from_loader(fullname, loader)
     208          module = importlib.util.module_from_spec(spec)
     209          loader.exec_module(module)
     210          return module
     211  
     212      # No extension module as __init__ available for testing.
     213      test_package = None
     214  
     215      # No extension module in a package available for testing.
     216      test_lacking_parent = None
     217  
     218      # Handling failure on reload is the up to the module.
     219      test_state_after_failure = None
     220  
     221      def test_module(self):
     222          # Test loading an extension module.
     223          with util.uncache(self.name):
     224              module = self.load_module()
     225              for attr, value in [('__name__', self.name),
     226                                  ('__file__', self.spec.origin),
     227                                  ('__package__', '')]:
     228                  self.assertEqual(getattr(module, attr), value)
     229              with self.assertRaises(AttributeError):
     230                  module.__path__
     231              self.assertIs(module, sys.modules[self.name])
     232              self.assertIsInstance(module.__loader__,
     233                                    self.machinery.ExtensionFileLoader)
     234  
     235      def test_functionality(self):
     236          # Test basic functionality of stuff defined in an extension module.
     237          with util.uncache(self.name):
     238              module = self.load_module()
     239              self.assertIsInstance(module, types.ModuleType)
     240              ex = module.Example()
     241              self.assertEqual(ex.demo('abcd'), 'abcd')
     242              self.assertEqual(ex.demo(), None)
     243              with self.assertRaises(AttributeError):
     244                  ex.abc
     245              ex.abc = 0
     246              self.assertEqual(ex.abc, 0)
     247              self.assertEqual(module.foo(9, 9), 18)
     248              self.assertIsInstance(module.Str(), str)
     249              self.assertEqual(module.Str(1) + '23', '123')
     250              with self.assertRaises(module.error):
     251                  raise module.error()
     252              self.assertEqual(module.int_const, 1969)
     253              self.assertEqual(module.str_const, 'something different')
     254  
     255      def test_reload(self):
     256          # Test that reload didn't re-set the module's attributes.
     257          with util.uncache(self.name):
     258              module = self.load_module()
     259              ex_class = module.Example
     260              importlib.reload(module)
     261              self.assertIs(ex_class, module.Example)
     262  
     263      def test_try_registration(self):
     264          # Assert that the PyState_{Find,Add,Remove}Module C API doesn't work.
     265          with util.uncache(self.name):
     266              module = self.load_module()
     267              with self.subTest('PyState_FindModule'):
     268                  self.assertEqual(module.call_state_registration_func(0), None)
     269              with self.subTest('PyState_AddModule'):
     270                  with self.assertRaises(SystemError):
     271                      module.call_state_registration_func(1)
     272              with self.subTest('PyState_RemoveModule'):
     273                  with self.assertRaises(SystemError):
     274                      module.call_state_registration_func(2)
     275  
     276      def test_load_submodule(self):
     277          # Test loading a simulated submodule.
     278          module = self.load_module_by_name('pkg.' + self.name)
     279          self.assertIsInstance(module, types.ModuleType)
     280          self.assertEqual(module.__name__, 'pkg.' + self.name)
     281          self.assertEqual(module.str_const, 'something different')
     282  
     283      def test_load_short_name(self):
     284          # Test loading module with a one-character name.
     285          module = self.load_module_by_name('x')
     286          self.assertIsInstance(module, types.ModuleType)
     287          self.assertEqual(module.__name__, 'x')
     288          self.assertEqual(module.str_const, 'something different')
     289          self.assertNotIn('x', sys.modules)
     290  
     291      def test_load_twice(self):
     292          # Test that 2 loads result in 2 module objects.
     293          module1 = self.load_module_by_name(self.name)
     294          module2 = self.load_module_by_name(self.name)
     295          self.assertIsNot(module1, module2)
     296  
     297      def test_unloadable(self):
     298          # Test nonexistent module.
     299          name = 'asdfjkl;'
     300          with self.assertRaises(ImportError) as cm:
     301              self.load_module_by_name(name)
     302          self.assertEqual(cm.exception.name, name)
     303  
     304      def test_unloadable_nonascii(self):
     305          # Test behavior with nonexistent module with non-ASCII name.
     306          name = 'fo\xf3'
     307          with self.assertRaises(ImportError) as cm:
     308              self.load_module_by_name(name)
     309          self.assertEqual(cm.exception.name, name)
     310  
     311      def test_nonmodule(self):
     312          # Test returning a non-module object from create works.
     313          name = self.name + '_nonmodule'
     314          mod = self.load_module_by_name(name)
     315          self.assertNotEqual(type(mod), type(unittest))
     316          self.assertEqual(mod.three, 3)
     317  
     318      # issue 27782
     319      def test_nonmodule_with_methods(self):
     320          # Test creating a non-module object with methods defined.
     321          name = self.name + '_nonmodule_with_methods'
     322          mod = self.load_module_by_name(name)
     323          self.assertNotEqual(type(mod), type(unittest))
     324          self.assertEqual(mod.three, 3)
     325          self.assertEqual(mod.bar(10, 1), 9)
     326  
     327      def test_null_slots(self):
     328          # Test that NULL slots aren't a problem.
     329          name = self.name + '_null_slots'
     330          module = self.load_module_by_name(name)
     331          self.assertIsInstance(module, types.ModuleType)
     332          self.assertEqual(module.__name__, name)
     333  
     334      def test_bad_modules(self):
     335          # Test SystemError is raised for misbehaving extensions.
     336          for name_base in [
     337                  'bad_slot_large',
     338                  'bad_slot_negative',
     339                  'create_int_with_state',
     340                  'negative_size',
     341                  'export_null',
     342                  'export_uninitialized',
     343                  'export_raise',
     344                  'export_unreported_exception',
     345                  'create_null',
     346                  'create_raise',
     347                  'create_unreported_exception',
     348                  'nonmodule_with_exec_slots',
     349                  'exec_err',
     350                  'exec_raise',
     351                  'exec_unreported_exception',
     352                  'multiple_create_slots',
     353                  'multiple_multiple_interpreters_slots',
     354                  ]:
     355              with self.subTest(name_base):
     356                  name = self.name + '_' + name_base
     357                  with self.assertRaises(SystemError) as cm:
     358                      self.load_module_by_name(name)
     359  
     360                  # If there is an unreported exception, it should be chained
     361                  # with the `SystemError`.
     362                  if "unreported_exception" in name_base:
     363                      self.assertIsNotNone(cm.exception.__cause__)
     364  
     365      def test_nonascii(self):
     366          # Test that modules with non-ASCII names can be loaded.
     367          # punycode behaves slightly differently in some-ASCII and no-ASCII
     368          # cases, so test both.
     369          cases = [
     370              (self.name + '_zkou\u0161ka_na\u010dten\xed', 'Czech'),
     371              ('\uff3f\u30a4\u30f3\u30dd\u30fc\u30c8\u30c6\u30b9\u30c8',
     372               'Japanese'),
     373              ]
     374          for name, lang in cases:
     375              with self.subTest(name):
     376                  module = self.load_module_by_name(name)
     377                  self.assertEqual(module.__name__, name)
     378                  self.assertEqual(module.__doc__, "Module named in %s" % lang)
     379  
     380  
     381  (Frozen_MultiPhaseExtensionModuleTests,
     382   Source_MultiPhaseExtensionModuleTests
     383   ) = util.test_both(MultiPhaseExtensionModuleTests, machinery=machinery)
     384  
     385  
     386  if __name__ == '__main__':
     387      unittest.main()