1 import importlib
2 from importlib import abc
3 from importlib import util
4 import sys
5 import types
6 import unittest
7
8 from test.test_importlib import util as test_util
9
10
11 class ESC[4;38;5;81mCollectInit:
12
13 def __init__(self, *args, **kwargs):
14 self.args = args
15 self.kwargs = kwargs
16
17 def exec_module(self, module):
18 return self
19
20
21 class ESC[4;38;5;81mLazyLoaderFactoryTests(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
22
23 def test_init(self):
24 factory = util.LazyLoader.factory(CollectInit)
25 # E.g. what importlib.machinery.FileFinder instantiates loaders with
26 # plus keyword arguments.
27 lazy_loader = factory('module name', 'module path', kw='kw')
28 loader = lazy_loader.loader
29 self.assertEqual(('module name', 'module path'), loader.args)
30 self.assertEqual({'kw': 'kw'}, loader.kwargs)
31
32 def test_validation(self):
33 # No exec_module(), no lazy loading.
34 with self.assertRaises(TypeError):
35 util.LazyLoader.factory(object)
36
37
38 class ESC[4;38;5;81mTestingImporter(ESC[4;38;5;149mabcESC[4;38;5;149m.ESC[4;38;5;149mMetaPathFinder, ESC[4;38;5;149mabcESC[4;38;5;149m.ESC[4;38;5;149mLoader):
39
40 module_name = 'lazy_loader_test'
41 mutated_name = 'changed'
42 loaded = None
43 source_code = 'attr = 42; __name__ = {!r}'.format(mutated_name)
44
45 def find_spec(self, name, path, target=None):
46 if name != self.module_name:
47 return None
48 return util.spec_from_loader(name, util.LazyLoader(self))
49
50 def exec_module(self, module):
51 exec(self.source_code, module.__dict__)
52 self.loaded = module
53
54
55 class ESC[4;38;5;81mLazyLoaderTests(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
56
57 def test_init(self):
58 with self.assertRaises(TypeError):
59 # Classes that don't define exec_module() trigger TypeError.
60 util.LazyLoader(object)
61
62 def new_module(self, source_code=None):
63 loader = TestingImporter()
64 if source_code is not None:
65 loader.source_code = source_code
66 spec = util.spec_from_loader(TestingImporter.module_name,
67 util.LazyLoader(loader))
68 module = spec.loader.create_module(spec)
69 if module is None:
70 module = types.ModuleType(TestingImporter.module_name)
71 module.__spec__ = spec
72 module.__loader__ = spec.loader
73 spec.loader.exec_module(module)
74 # Module is now lazy.
75 self.assertIsNone(loader.loaded)
76 return module
77
78 def test_e2e(self):
79 # End-to-end test to verify the load is in fact lazy.
80 importer = TestingImporter()
81 assert importer.loaded is None
82 with test_util.uncache(importer.module_name):
83 with test_util.import_state(meta_path=[importer]):
84 module = importlib.import_module(importer.module_name)
85 self.assertIsNone(importer.loaded)
86 # Trigger load.
87 self.assertEqual(module.__loader__, importer)
88 self.assertIsNotNone(importer.loaded)
89 self.assertEqual(module, importer.loaded)
90
91 def test_attr_unchanged(self):
92 # An attribute only mutated as a side-effect of import should not be
93 # changed needlessly.
94 module = self.new_module()
95 self.assertEqual(TestingImporter.mutated_name, module.__name__)
96
97 def test_new_attr(self):
98 # A new attribute should persist.
99 module = self.new_module()
100 module.new_attr = 42
101 self.assertEqual(42, module.new_attr)
102
103 def test_mutated_preexisting_attr(self):
104 # Changing an attribute that already existed on the module --
105 # e.g. __name__ -- should persist.
106 module = self.new_module()
107 module.__name__ = 'bogus'
108 self.assertEqual('bogus', module.__name__)
109
110 def test_mutated_attr(self):
111 # Changing an attribute that comes into existence after an import
112 # should persist.
113 module = self.new_module()
114 module.attr = 6
115 self.assertEqual(6, module.attr)
116
117 def test_delete_eventual_attr(self):
118 # Deleting an attribute should stay deleted.
119 module = self.new_module()
120 del module.attr
121 self.assertFalse(hasattr(module, 'attr'))
122
123 def test_delete_preexisting_attr(self):
124 module = self.new_module()
125 del module.__name__
126 self.assertFalse(hasattr(module, '__name__'))
127
128 def test_module_substitution_error(self):
129 with test_util.uncache(TestingImporter.module_name):
130 fresh_module = types.ModuleType(TestingImporter.module_name)
131 sys.modules[TestingImporter.module_name] = fresh_module
132 module = self.new_module()
133 with self.assertRaisesRegex(ValueError, "substituted"):
134 module.__name__
135
136 def test_module_already_in_sys(self):
137 with test_util.uncache(TestingImporter.module_name):
138 module = self.new_module()
139 sys.modules[TestingImporter.module_name] = module
140 # Force the load; just care that no exception is raised.
141 module.__name__
142
143
144 if __name__ == '__main__':
145 unittest.main()