cpython/Lib/test/test_importlib/source/test_finder.py
Barry Warsaw 326997829d
gh-98040: Remove find_loader, find_module and other deprecated APIs (#98059)
* Remove deprecated classes from pkgutil
* Remove some other PEP 302 obsolescence
* Use find_spec instead of load_module
* Remove more tests of PEP 302 obsolete APIs
* Remove another bunch of tests using obsolete load_modules()
* Remove deleted names from __all__
* Remove obsolete footnote
* imp is removed
* Remove `imp` from generated stdlib names
* What's new and blurb
* Update zipimport documentation for the removed methods
* Fix some Windows tests
* Remove any test (or part of a test) that references `find_module()`.
* Use assertIsNone() / assertIsNotNone() consistently.
* Update Doc/reference/import.rst
* We don't need pkgutil._get_spec() any more either
*  test.test_importlib.fixtures.NullFinder
* ...BadLoaderFinder.find_module
* ...test_api.InvalidatingNullFinder.find_module
* ...test.test_zipimport test of z.find_module
* Suppress cross-references to find_loader and find_module
* Suppress cross-references to Finder
* Suppress cross-references to pkgutil.ImpImporter and pkgutil.ImpLoader

---------

Co-authored-by: Oleg Iarygin <oleg@arhadthedev.net>
Co-authored-by: Adam Turner <9087854+aa-turner@users.noreply.github.com>
2023-05-03 04:55:22 -07:00

213 lines
7.9 KiB
Python

from test.test_importlib import abc, util
machinery = util.import_importlib('importlib.machinery')
import errno
import os
import py_compile
import stat
import sys
import tempfile
from test.support.import_helper 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])
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 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')
self.assertEqual(nothing, self.NOT_FOUND)
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))
with open('mod.py', 'w', encoding='utf-8') as file:
file.write("# test file for importlib")
try:
loader = self._find(finder, 'mod', loader_only=True)
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.assertEqual(found, self.NOT_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()
self.enterContext(tempdir)
# 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.
original_mode = os.stat(tempdir.name).st_mode
self.addCleanup(os.chmod, tempdir.name, original_mode)
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):
spec = finder.find_spec(name)
if spec is None:
return self.NOT_FOUND
if loader_only:
return spec.loader
return spec.loader, spec.submodule_search_locations
(Frozen_FinderTestsPEP420,
Source_FinderTestsPEP420
) = util.test_both(FinderTestsPEP420, machinery=machinery)
if __name__ == '__main__':
unittest.main()