1from test.test_importlib import abc, util 2 3machinery = util.import_importlib('importlib.machinery') 4 5import errno 6import os 7import py_compile 8import stat 9import sys 10import tempfile 11from test.support.import_helper import make_legacy_pyc 12import unittest 13import warnings 14 15 16class FinderTests(abc.FinderTests): 17 18 """For a top-level module, it should just be found directly in the 19 directory being searched. This is true for a directory with source 20 [top-level source], bytecode [top-level bc], or both [top-level both]. 21 There is also the possibility that it is a package [top-level package], in 22 which case there will be a directory with the module name and an 23 __init__.py file. If there is a directory without an __init__.py an 24 ImportWarning is returned [empty dir]. 25 26 For sub-modules and sub-packages, the same happens as above but only use 27 the tail end of the name [sub module] [sub package] [sub empty]. 28 29 When there is a conflict between a package and module having the same name 30 in the same directory, the package wins out [package over module]. This is 31 so that imports of modules within the package can occur rather than trigger 32 an import error. 33 34 When there is a package and module with the same name, always pick the 35 package over the module [package over module]. This is so that imports from 36 the package have the possibility of succeeding. 37 38 """ 39 40 def get_finder(self, root): 41 loader_details = [(self.machinery.SourceFileLoader, 42 self.machinery.SOURCE_SUFFIXES), 43 (self.machinery.SourcelessFileLoader, 44 self.machinery.BYTECODE_SUFFIXES)] 45 return self.machinery.FileFinder(root, *loader_details) 46 47 def import_(self, root, module): 48 finder = self.get_finder(root) 49 return self._find(finder, module, loader_only=True) 50 51 def run_test(self, test, create=None, *, compile_=None, unlink=None): 52 """Test the finding of 'test' with the creation of modules listed in 53 'create'. 54 55 Any names listed in 'compile_' are byte-compiled. Modules 56 listed in 'unlink' have their source files deleted. 57 58 """ 59 if create is None: 60 create = {test} 61 with util.create_modules(*create) as mapping: 62 if compile_: 63 for name in compile_: 64 py_compile.compile(mapping[name]) 65 if unlink: 66 for name in unlink: 67 os.unlink(mapping[name]) 68 try: 69 make_legacy_pyc(mapping[name]) 70 except OSError as error: 71 # Some tests do not set compile_=True so the source 72 # module will not get compiled and there will be no 73 # PEP 3147 pyc file to rename. 74 if error.errno != errno.ENOENT: 75 raise 76 loader = self.import_(mapping['.root'], test) 77 self.assertTrue(hasattr(loader, 'load_module')) 78 return loader 79 80 def test_module(self): 81 # [top-level source] 82 self.run_test('top_level') 83 # [top-level bc] 84 self.run_test('top_level', compile_={'top_level'}, 85 unlink={'top_level'}) 86 # [top-level both] 87 self.run_test('top_level', compile_={'top_level'}) 88 89 # [top-level package] 90 def test_package(self): 91 # Source. 92 self.run_test('pkg', {'pkg.__init__'}) 93 # Bytecode. 94 self.run_test('pkg', {'pkg.__init__'}, compile_={'pkg.__init__'}, 95 unlink={'pkg.__init__'}) 96 # Both. 97 self.run_test('pkg', {'pkg.__init__'}, compile_={'pkg.__init__'}) 98 99 # [sub module] 100 def test_module_in_package(self): 101 with util.create_modules('pkg.__init__', 'pkg.sub') as mapping: 102 pkg_dir = os.path.dirname(mapping['pkg.__init__']) 103 loader = self.import_(pkg_dir, 'pkg.sub') 104 self.assertTrue(hasattr(loader, 'load_module')) 105 106 # [sub package] 107 def test_package_in_package(self): 108 context = util.create_modules('pkg.__init__', 'pkg.sub.__init__') 109 with context as mapping: 110 pkg_dir = os.path.dirname(mapping['pkg.__init__']) 111 loader = self.import_(pkg_dir, 'pkg.sub') 112 self.assertTrue(hasattr(loader, 'load_module')) 113 114 # [package over modules] 115 def test_package_over_module(self): 116 name = '_temp' 117 loader = self.run_test(name, {'{0}.__init__'.format(name), name}) 118 self.assertIn('__init__', loader.get_filename(name)) 119 120 def test_failure(self): 121 with util.create_modules('blah') as mapping: 122 nothing = self.import_(mapping['.root'], 'sdfsadsadf') 123 self.assertIsNone(nothing) 124 125 def test_empty_string_for_dir(self): 126 # The empty string from sys.path means to search in the cwd. 127 finder = self.machinery.FileFinder('', (self.machinery.SourceFileLoader, 128 self.machinery.SOURCE_SUFFIXES)) 129 with open('mod.py', 'w', encoding='utf-8') as file: 130 file.write("# test file for importlib") 131 try: 132 loader = self._find(finder, 'mod', loader_only=True) 133 self.assertTrue(hasattr(loader, 'load_module')) 134 finally: 135 os.unlink('mod.py') 136 137 def test_invalidate_caches(self): 138 # invalidate_caches() should reset the mtime. 139 finder = self.machinery.FileFinder('', (self.machinery.SourceFileLoader, 140 self.machinery.SOURCE_SUFFIXES)) 141 finder._path_mtime = 42 142 finder.invalidate_caches() 143 self.assertEqual(finder._path_mtime, -1) 144 145 # Regression test for http://bugs.python.org/issue14846 146 def test_dir_removal_handling(self): 147 mod = 'mod' 148 with util.create_modules(mod) as mapping: 149 finder = self.get_finder(mapping['.root']) 150 found = self._find(finder, 'mod', loader_only=True) 151 self.assertIsNotNone(found) 152 found = self._find(finder, 'mod', loader_only=True) 153 self.assertIsNone(found) 154 155 @unittest.skipUnless(sys.platform != 'win32', 156 'os.chmod() does not support the needed arguments under Windows') 157 def test_no_read_directory(self): 158 # Issue #16730 159 tempdir = tempfile.TemporaryDirectory() 160 self.enterContext(tempdir) 161 # Since we muck with the permissions, we want to set them back to 162 # their original values to make sure the directory can be properly 163 # cleaned up. 164 original_mode = os.stat(tempdir.name).st_mode 165 self.addCleanup(os.chmod, tempdir.name, original_mode) 166 os.chmod(tempdir.name, stat.S_IWUSR | stat.S_IXUSR) 167 finder = self.get_finder(tempdir.name) 168 found = self._find(finder, 'doesnotexist') 169 self.assertEqual(found, self.NOT_FOUND) 170 171 def test_ignore_file(self): 172 # If a directory got changed to a file from underneath us, then don't 173 # worry about looking for submodules. 174 with tempfile.NamedTemporaryFile() as file_obj: 175 finder = self.get_finder(file_obj.name) 176 found = self._find(finder, 'doesnotexist') 177 self.assertEqual(found, self.NOT_FOUND) 178 179 180class FinderTestsPEP451(FinderTests): 181 182 NOT_FOUND = None 183 184 def _find(self, finder, name, loader_only=False): 185 spec = finder.find_spec(name) 186 return spec.loader if spec is not None else spec 187 188 189(Frozen_FinderTestsPEP451, 190 Source_FinderTestsPEP451 191 ) = util.test_both(FinderTestsPEP451, machinery=machinery) 192 193 194class FinderTestsPEP420(FinderTests): 195 196 NOT_FOUND = (None, []) 197 198 def _find(self, finder, name, loader_only=False): 199 with warnings.catch_warnings(): 200 warnings.simplefilter("ignore", DeprecationWarning) 201 loader_portions = finder.find_loader(name) 202 return loader_portions[0] if loader_only else loader_portions 203 204 205(Frozen_FinderTestsPEP420, 206 Source_FinderTestsPEP420 207 ) = util.test_both(FinderTestsPEP420, machinery=machinery) 208 209 210class FinderTestsPEP302(FinderTests): 211 212 NOT_FOUND = None 213 214 def _find(self, finder, name, loader_only=False): 215 with warnings.catch_warnings(): 216 warnings.simplefilter("ignore", DeprecationWarning) 217 return finder.find_module(name) 218 219 220(Frozen_FinderTestsPEP302, 221 Source_FinderTestsPEP302 222 ) = util.test_both(FinderTestsPEP302, machinery=machinery) 223 224 225if __name__ == '__main__': 226 unittest.main() 227