from .. import abc from . import util as source_util from importlib import machinery import errno import imp import os import py_compile import stat import sys import tempfile from test.support import make_legacy_pyc import unittest import warnings class FinderTests(abc.FinderTests): """For a top-level module, it should just be found directly in the directory being searched. This is true for a directory with source [top-level source], bytecode [top-level bc], or both [top-level both]. There is also the possibility that it is a package [top-level package], in which case there will be a directory with the module name and an __init__.py file. If there is a directory without an __init__.py an ImportWarning is returned [empty dir]. For sub-modules and sub-packages, the same happens as above but only use the tail end of the name [sub module] [sub package] [sub empty]. When there is a conflict between a package and module having the same name in the same directory, the package wins out [package over module]. This is so that imports of modules within the package can occur rather than trigger an import error. When there is a package and module with the same name, always pick the package over the module [package over module]. This is so that imports from the package have the possibility of succeeding. """ def get_finder(self, root): loader_details = [(machinery.SourceFileLoader, machinery.SOURCE_SUFFIXES), (machinery.SourcelessFileLoader, machinery.BYTECODE_SUFFIXES)] return machinery.FileFinder(root, *loader_details) def import_(self, root, module): return self.get_finder(root).find_module(module) def run_test(self, test, create=None, *, compile_=None, unlink=None): """Test the finding of 'test' with the creation of modules listed in 'create'. Any names listed in 'compile_' are byte-compiled. Modules listed in 'unlink' have their source files deleted. """ if create is None: create = {test} with source_util.create_modules(*create) as mapping: if compile_: for name in compile_: py_compile.compile(mapping[name]) if unlink: for name in unlink: os.unlink(mapping[name]) try: make_legacy_pyc(mapping[name]) except OSError as error: # Some tests do not set compile_=True so the source # module will not get compiled and there will be no # PEP 3147 pyc file to rename. if error.errno != errno.ENOENT: raise loader = self.import_(mapping['.root'], test) self.assertTrue(hasattr(loader, 'load_module')) return loader def test_module(self): # [top-level source] self.run_test('top_level') # [top-level bc] self.run_test('top_level', compile_={'top_level'}, unlink={'top_level'}) # [top-level both] self.run_test('top_level', compile_={'top_level'}) # [top-level package] def test_package(self): # Source. self.run_test('pkg', {'pkg.__init__'}) # Bytecode. self.run_test('pkg', {'pkg.__init__'}, compile_={'pkg.__init__'}, unlink={'pkg.__init__'}) # Both. self.run_test('pkg', {'pkg.__init__'}, compile_={'pkg.__init__'}) # [sub module] def test_module_in_package(self): with source_util.create_modules('pkg.__init__', 'pkg.sub') as mapping: pkg_dir = os.path.dirname(mapping['pkg.__init__']) loader = self.import_(pkg_dir, 'pkg.sub') self.assertTrue(hasattr(loader, 'load_module')) # [sub package] def test_package_in_package(self): context = source_util.create_modules('pkg.__init__', 'pkg.sub.__init__') with context as mapping: pkg_dir = os.path.dirname(mapping['pkg.__init__']) loader = self.import_(pkg_dir, 'pkg.sub') self.assertTrue(hasattr(loader, 'load_module')) # [package over modules] def test_package_over_module(self): name = '_temp' loader = self.run_test(name, {'{0}.__init__'.format(name), name}) self.assertIn('__init__', loader.get_filename(name)) def test_failure(self): with source_util.create_modules('blah') as mapping: nothing = self.import_(mapping['.root'], 'sdfsadsadf') self.assertIsNone(nothing) def test_empty_string_for_dir(self): # The empty string from sys.path means to search in the cwd. finder = machinery.FileFinder('', (machinery.SourceFileLoader, machinery.SOURCE_SUFFIXES)) with open('mod.py', 'w') as file: file.write("# test file for importlib") try: loader = finder.find_module('mod') self.assertTrue(hasattr(loader, 'load_module')) finally: os.unlink('mod.py') def test_invalidate_caches(self): # invalidate_caches() should reset the mtime. finder = machinery.FileFinder('', (machinery.SourceFileLoader, machinery.SOURCE_SUFFIXES)) finder._path_mtime = 42 finder.invalidate_caches() self.assertEqual(finder._path_mtime, -1) # Regression test for http://bugs.python.org/issue14846 def test_dir_removal_handling(self): mod = 'mod' with source_util.create_modules(mod) as mapping: finder = self.get_finder(mapping['.root']) self.assertIsNotNone(finder.find_module(mod)) self.assertIsNone(finder.find_module(mod)) @unittest.skipUnless(sys.platform != 'win32', 'os.chmod() does not support the needed arguments under Windows') def test_no_read_directory(self): # Issue #16730 tempdir = tempfile.TemporaryDirectory() original_mode = os.stat(tempdir.name).st_mode def cleanup(tempdir): """Cleanup function for the temporary directory. Since we muck with the permissions, we want to set them back to their original values to make sure the directory can be properly cleaned up. """ os.chmod(tempdir.name, original_mode) # If this is not explicitly called then the __del__ method is used, # but since already mucking around might as well explicitly clean # up. tempdir.__exit__(None, None, None) self.addCleanup(cleanup, tempdir) os.chmod(tempdir.name, stat.S_IWUSR | stat.S_IXUSR) finder = self.get_finder(tempdir.name) self.assertEqual((None, []), finder.find_loader('doesnotexist')) def test_ignore_file(self): # If a directory got changed to a file from underneath us, then don't # worry about looking for submodules. with tempfile.NamedTemporaryFile() as file_obj: finder = self.get_finder(file_obj.name) self.assertEqual((None, []), finder.find_loader('doesnotexist')) def test_main(): from test.support import run_unittest run_unittest(FinderTests) if __name__ == '__main__': test_main()