cpython/Lib/test/test_importlib/source/test_finder.py

237 lines
8.6 KiB
Python
Raw Normal View History

from .. import abc
from .. import util
machinery = util.import_importlib('importlib.machinery')
2010-04-16 21:19:56 -03:00
import errno
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 = [(self.machinery.SourceFileLoader,
self.machinery.SOURCE_SUFFIXES),
(self.machinery.SourcelessFileLoader,
self.machinery.BYTECODE_SUFFIXES)]
return self.machinery.FileFinder(root, *loader_details)
def import_(self, root, module):
finder = self.get_finder(root)
return self._find(finder, module, loader_only=True)
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 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])
2010-04-16 21:19:56 -03:00
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]
2010-04-16 21:19:56 -03:00
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 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 = 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 util.create_modules('blah') as mapping:
nothing = self.import_(mapping['.root'], 'sdfsadsadf')
2012-06-28 07:15:01 -03:00
self.assertIsNone(nothing)
Make importlib.abc.SourceLoader the primary mechanism for importlib. This required moving the class from importlib/abc.py into importlib/_bootstrap.py and jiggering some code to work better with the class. This included changing how the file finder worked to better meet import semantics. This also led to fixing importlib to handle the empty string from sys.path as import currently does (and making me wish we didn't support that instead just required people to insert '.' instead to represent cwd). It also required making the new set_data abstractmethod create any needed subdirectories implicitly thanks to __pycache__ (it was either this or grow the SourceLoader ABC to gain an 'exists' method and either a mkdir method or have set_data with no data arg mean to create a directory). Lastly, as an optimization the file loaders cache the file path where the finder found something to use for loading (this is thanks to having a sourceless loader separate from the source loader to simplify the code and cut out stat calls). Unfortunately test_runpy assumed a loader would always work for a module, even if you changed from underneath it what it was expected to work with. By simply dropping the previous loader in test_runpy so the proper loader can be returned by the finder fixed the failure. At this point importlib deviates from import on two points: 1. The exception raised when trying to import a file is different (import does an explicit file check to print a special message, importlib just says the path cannot be imported as if it was just some module name). 2. the co_filename on a code object is not being set to where bytecode was actually loaded from instead of where the marshalled code object originally came from (a solution for this has already been agreed upon on python-dev but has not been implemented yet; issue8611).
2010-07-03 19:18:47 -03:00
def test_empty_string_for_dir(self):
# The empty string from sys.path means to search in the cwd.
finder = self.machinery.FileFinder('', (self.machinery.SourceFileLoader,
self.machinery.SOURCE_SUFFIXES))
Make importlib.abc.SourceLoader the primary mechanism for importlib. This required moving the class from importlib/abc.py into importlib/_bootstrap.py and jiggering some code to work better with the class. This included changing how the file finder worked to better meet import semantics. This also led to fixing importlib to handle the empty string from sys.path as import currently does (and making me wish we didn't support that instead just required people to insert '.' instead to represent cwd). It also required making the new set_data abstractmethod create any needed subdirectories implicitly thanks to __pycache__ (it was either this or grow the SourceLoader ABC to gain an 'exists' method and either a mkdir method or have set_data with no data arg mean to create a directory). Lastly, as an optimization the file loaders cache the file path where the finder found something to use for loading (this is thanks to having a sourceless loader separate from the source loader to simplify the code and cut out stat calls). Unfortunately test_runpy assumed a loader would always work for a module, even if you changed from underneath it what it was expected to work with. By simply dropping the previous loader in test_runpy so the proper loader can be returned by the finder fixed the failure. At this point importlib deviates from import on two points: 1. The exception raised when trying to import a file is different (import does an explicit file check to print a special message, importlib just says the path cannot be imported as if it was just some module name). 2. the co_filename on a code object is not being set to where bytecode was actually loaded from instead of where the marshalled code object originally came from (a solution for this has already been agreed upon on python-dev but has not been implemented yet; issue8611).
2010-07-03 19:18:47 -03:00
with open('mod.py', 'w') as file:
file.write("# test file for importlib")
try:
loader = self._find(finder, 'mod', loader_only=True)
Make importlib.abc.SourceLoader the primary mechanism for importlib. This required moving the class from importlib/abc.py into importlib/_bootstrap.py and jiggering some code to work better with the class. This included changing how the file finder worked to better meet import semantics. This also led to fixing importlib to handle the empty string from sys.path as import currently does (and making me wish we didn't support that instead just required people to insert '.' instead to represent cwd). It also required making the new set_data abstractmethod create any needed subdirectories implicitly thanks to __pycache__ (it was either this or grow the SourceLoader ABC to gain an 'exists' method and either a mkdir method or have set_data with no data arg mean to create a directory). Lastly, as an optimization the file loaders cache the file path where the finder found something to use for loading (this is thanks to having a sourceless loader separate from the source loader to simplify the code and cut out stat calls). Unfortunately test_runpy assumed a loader would always work for a module, even if you changed from underneath it what it was expected to work with. By simply dropping the previous loader in test_runpy so the proper loader can be returned by the finder fixed the failure. At this point importlib deviates from import on two points: 1. The exception raised when trying to import a file is different (import does an explicit file check to print a special message, importlib just says the path cannot be imported as if it was just some module name). 2. the co_filename on a code object is not being set to where bytecode was actually loaded from instead of where the marshalled code object originally came from (a solution for this has already been agreed upon on python-dev but has not been implemented yet; issue8611).
2010-07-03 19:18:47 -03:00
self.assertTrue(hasattr(loader, 'load_module'))
finally:
os.unlink('mod.py')
def test_invalidate_caches(self):
# invalidate_caches() should reset the mtime.
finder = self.machinery.FileFinder('', (self.machinery.SourceFileLoader,
self.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 util.create_modules(mod) as mapping:
finder = self.get_finder(mapping['.root'])
found = self._find(finder, 'mod', loader_only=True)
self.assertIsNotNone(found)
found = self._find(finder, 'mod', loader_only=True)
self.assertIsNone(found)
@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)
found = self._find(finder, 'doesnotexist')
self.assertEqual(found, self.NOT_FOUND)
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)
found = self._find(finder, 'doesnotexist')
self.assertEqual(found, self.NOT_FOUND)
class FinderTestsPEP451(FinderTests):
NOT_FOUND = None
def _find(self, finder, name, loader_only=False):
spec = finder.find_spec(name)
return spec.loader if spec is not None else spec
(Frozen_FinderTestsPEP451,
Source_FinderTestsPEP451
) = util.test_both(FinderTestsPEP451, machinery=machinery)
class FinderTestsPEP420(FinderTests):
NOT_FOUND = (None, [])
def _find(self, finder, name, loader_only=False):
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
loader_portions = finder.find_loader(name)
return loader_portions[0] if loader_only else loader_portions
(Frozen_FinderTestsPEP420,
Source_FinderTestsPEP420
) = util.test_both(FinderTestsPEP420, machinery=machinery)
class FinderTestsPEP302(FinderTests):
NOT_FOUND = None
def _find(self, finder, name, loader_only=False):
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
return finder.find_module(name)
(Frozen_FinderTestsPEP302,
Source_FinderTestsPEP302
) = util.test_both(FinderTestsPEP302, machinery=machinery)
if __name__ == '__main__':
unittest.main()