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