summaryrefslogtreecommitdiffstats
path: root/Lib/importlib
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/importlib')
-rw-r--r--Lib/importlib/__init__.py126
-rw-r--r--Lib/importlib/_bootstrap.py890
-rw-r--r--Lib/importlib/abc.py304
-rw-r--r--Lib/importlib/machinery.py5
-rw-r--r--Lib/importlib/test/__init__.py0
-rw-r--r--Lib/importlib/test/__main__.py29
-rw-r--r--Lib/importlib/test/abc.py99
-rw-r--r--Lib/importlib/test/benchmark.py172
-rw-r--r--Lib/importlib/test/builtin/__init__.py12
-rw-r--r--Lib/importlib/test/builtin/test_finder.py55
-rw-r--r--Lib/importlib/test/builtin/test_loader.py102
-rw-r--r--Lib/importlib/test/builtin/util.py7
-rw-r--r--Lib/importlib/test/extension/__init__.py13
-rw-r--r--Lib/importlib/test/extension/test_case_sensitivity.py42
-rw-r--r--Lib/importlib/test/extension/test_finder.py47
-rw-r--r--Lib/importlib/test/extension/test_loader.py59
-rw-r--r--Lib/importlib/test/extension/test_path_hook.py31
-rw-r--r--Lib/importlib/test/extension/util.py21
-rw-r--r--Lib/importlib/test/frozen/__init__.py13
-rw-r--r--Lib/importlib/test/frozen/test_finder.py47
-rw-r--r--Lib/importlib/test/frozen/test_loader.py105
-rw-r--r--Lib/importlib/test/import_/__init__.py13
-rw-r--r--Lib/importlib/test/import_/test___package__.py119
-rw-r--r--Lib/importlib/test/import_/test_api.py22
-rw-r--r--Lib/importlib/test/import_/test_caching.py86
-rw-r--r--Lib/importlib/test/import_/test_fromlist.py123
-rw-r--r--Lib/importlib/test/import_/test_meta_path.py97
-rw-r--r--Lib/importlib/test/import_/test_packages.py37
-rw-r--r--Lib/importlib/test/import_/test_path.py131
-rw-r--r--Lib/importlib/test/import_/test_relative_imports.py203
-rw-r--r--Lib/importlib/test/import_/util.py29
-rw-r--r--Lib/importlib/test/regrtest.py35
-rw-r--r--Lib/importlib/test/source/__init__.py13
-rw-r--r--Lib/importlib/test/source/test_abc_loader.py876
-rw-r--r--Lib/importlib/test/source/test_case_sensitivity.py60
-rw-r--r--Lib/importlib/test/source/test_file_loader.py419
-rw-r--r--Lib/importlib/test/source/test_finder.py153
-rw-r--r--Lib/importlib/test/source/test_path_hook.py26
-rw-r--r--Lib/importlib/test/source/test_source_encoding.py123
-rw-r--r--Lib/importlib/test/source/util.py97
-rw-r--r--Lib/importlib/test/test_abc.py89
-rw-r--r--Lib/importlib/test/test_api.py93
-rw-r--r--Lib/importlib/test/test_util.py118
-rw-r--r--Lib/importlib/test/util.py136
-rw-r--r--Lib/importlib/util.py5
45 files changed, 5262 insertions, 20 deletions
diff --git a/Lib/importlib/__init__.py b/Lib/importlib/__init__.py
index ad31a1a..2baaf93 100644
--- a/Lib/importlib/__init__.py
+++ b/Lib/importlib/__init__.py
@@ -1,20 +1,108 @@
-"""Backport of importlib.import_module from 3.x."""
-# While not critical (and in no way guaranteed!), it would be nice to keep this
-# code compatible with Python 2.3.
-import sys
-
-def _resolve_name(name, package, level):
- """Return the absolute name of the module to be imported."""
- if not hasattr(package, 'rindex'):
- raise ValueError("'package' not set to a string")
- dot = len(package)
- for x in xrange(level, 1, -1):
+"""A pure Python implementation of import.
+
+References on import:
+
+ * Language reference
+ http://docs.python.org/ref/import.html
+ * __import__ function
+ http://docs.python.org/lib/built-in-funcs.html
+ * Packages
+ http://www.python.org/doc/essays/packages.html
+ * PEP 235: Import on Case-Insensitive Platforms
+ http://www.python.org/dev/peps/pep-0235
+ * PEP 275: Import Modules from Zip Archives
+ http://www.python.org/dev/peps/pep-0273
+ * PEP 302: New Import Hooks
+ http://www.python.org/dev/peps/pep-0302/
+ * PEP 328: Imports: Multi-line and Absolute/Relative
+ http://www.python.org/dev/peps/pep-0328
+
+"""
+__all__ = ['__import__', 'import_module']
+
+from . import _bootstrap
+
+import os
+import re
+import tokenize
+
+# Bootstrap help #####################################################
+
+def _case_ok(directory, check):
+ """Check if the directory contains something matching 'check'.
+
+ No check is done if the file/directory exists or not.
+
+ """
+ if 'PYTHONCASEOK' in os.environ:
+ return True
+ elif check in os.listdir(directory if directory else os.getcwd()):
+ return True
+ return False
+
+
+def _w_long(x):
+ """Convert a 32-bit integer to little-endian.
+
+ XXX Temporary until marshal's long functions are exposed.
+
+ """
+ x = int(x)
+ int_bytes = []
+ int_bytes.append(x & 0xFF)
+ int_bytes.append((x >> 8) & 0xFF)
+ int_bytes.append((x >> 16) & 0xFF)
+ int_bytes.append((x >> 24) & 0xFF)
+ return bytearray(int_bytes)
+
+
+def _r_long(int_bytes):
+ """Convert 4 bytes in little-endian to an integer.
+
+ XXX Temporary until marshal's long function are exposed.
+
+ """
+ x = int_bytes[0]
+ x |= int_bytes[1] << 8
+ x |= int_bytes[2] << 16
+ x |= int_bytes[3] << 24
+ return x
+
+
+# Required built-in modules.
+try:
+ import posix as _os
+except ImportError:
+ try:
+ import nt as _os
+ except ImportError:
try:
- dot = package.rindex('.', 0, dot)
- except ValueError:
- raise ValueError("attempted relative import beyond top-level "
- "package")
- return "%s.%s" % (package[:dot], name)
+ import os2 as _os
+ except ImportError:
+ raise ImportError('posix, nt, or os2 module required for importlib')
+_bootstrap._os = _os
+import imp, sys, marshal, errno, _io
+_bootstrap.imp = imp
+_bootstrap.sys = sys
+_bootstrap.marshal = marshal
+_bootstrap.errno = errno
+_bootstrap._io = _io
+import _warnings
+_bootstrap._warnings = _warnings
+
+
+from os import sep
+# For os.path.join replacement; pull from Include/osdefs.h:SEP .
+_bootstrap.path_sep = sep
+
+_bootstrap._case_ok = _case_ok
+marshal._w_long = _w_long
+marshal._r_long = _r_long
+
+
+# Public API #########################################################
+
+from ._bootstrap import __import__
def import_module(name, package=None):
@@ -25,14 +113,12 @@ def import_module(name, package=None):
relative import to an absolute import.
"""
+ level = 0
if name.startswith('.'):
if not package:
raise TypeError("relative imports require the 'package' argument")
- level = 0
for character in name:
if character != '.':
break
level += 1
- name = _resolve_name(name[level:], package, level)
- __import__(name)
- return sys.modules[name]
+ return _bootstrap._gcd_import(name[level:], package, level)
diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py
new file mode 100644
index 0000000..aa4032c
--- /dev/null
+++ b/Lib/importlib/_bootstrap.py
@@ -0,0 +1,890 @@
+"""Core implementation of import.
+
+This module is NOT meant to be directly imported! It has been designed such
+that it can be bootstrapped into Python as the implementation of import. As
+such it requires the injection of specific modules and attributes in order to
+work. One should use importlib as the public-facing version of this module.
+
+"""
+
+# Injected modules are '_warnings', 'imp', 'sys', 'marshal', 'errno', '_io',
+# and '_os' (a.k.a. 'posix', 'nt' or 'os2').
+# Injected attribute is path_sep.
+#
+# When editing this code be aware that code executed at import time CANNOT
+# reference any injected objects! This includes not only global code but also
+# anything specified at the class level.
+
+
+# Bootstrap-related code ######################################################
+
+# XXX Could also expose Modules/getpath.c:joinpath()
+def _path_join(*args):
+ """Replacement for os.path.join."""
+ return path_sep.join(x[:-len(path_sep)] if x.endswith(path_sep) else x
+ for x in args if x)
+
+
+def _path_exists(path):
+ """Replacement for os.path.exists."""
+ try:
+ _os.stat(path)
+ except OSError:
+ return False
+ else:
+ return True
+
+
+def _path_is_mode_type(path, mode):
+ """Test whether the path is the specified mode type."""
+ try:
+ stat_info = _os.stat(path)
+ except OSError:
+ return False
+ return (stat_info.st_mode & 0o170000) == mode
+
+
+# XXX Could also expose Modules/getpath.c:isfile()
+def _path_isfile(path):
+ """Replacement for os.path.isfile."""
+ return _path_is_mode_type(path, 0o100000)
+
+
+# XXX Could also expose Modules/getpath.c:isdir()
+def _path_isdir(path):
+ """Replacement for os.path.isdir."""
+ if not path:
+ path = _os.getcwd()
+ return _path_is_mode_type(path, 0o040000)
+
+
+def _path_without_ext(path, ext_type):
+ """Replacement for os.path.splitext()[0]."""
+ for suffix in _suffix_list(ext_type):
+ if path.endswith(suffix):
+ return path[:-len(suffix)]
+ else:
+ raise ValueError("path is not of the specified type")
+
+
+def _path_absolute(path):
+ """Replacement for os.path.abspath."""
+ if not path:
+ path = _os.getcwd()
+ try:
+ return _os._getfullpathname(path)
+ except AttributeError:
+ if path.startswith('/'):
+ return path
+ else:
+ return _path_join(_os.getcwd(), path)
+
+
+def _wrap(new, old):
+ """Simple substitute for functools.wraps."""
+ for replace in ['__module__', '__name__', '__doc__']:
+ setattr(new, replace, getattr(old, replace))
+ new.__dict__.update(old.__dict__)
+
+
+code_type = type(_wrap.__code__)
+
+# Finder/loader utility code ##################################################
+
+def set_package(fxn):
+ """Set __package__ on the returned module."""
+ def wrapper(*args, **kwargs):
+ module = fxn(*args, **kwargs)
+ if not hasattr(module, '__package__') or module.__package__ is None:
+ module.__package__ = module.__name__
+ if not hasattr(module, '__path__'):
+ module.__package__ = module.__package__.rpartition('.')[0]
+ return module
+ _wrap(wrapper, fxn)
+ return wrapper
+
+
+def set_loader(fxn):
+ """Set __loader__ on the returned module."""
+ def wrapper(self, *args, **kwargs):
+ module = fxn(self, *args, **kwargs)
+ if not hasattr(module, '__loader__'):
+ module.__loader__ = self
+ return module
+ _wrap(wrapper, fxn)
+ return wrapper
+
+
+def module_for_loader(fxn):
+ """Decorator to handle selecting the proper module for loaders.
+
+ The decorated function is passed the module to use instead of the module
+ name. The module passed in to the function is either from sys.modules if
+ it already exists or is a new module which has __name__ set and is inserted
+ into sys.modules. If an exception is raised and the decorator created the
+ module it is subsequently removed from sys.modules.
+
+ The decorator assumes that the decorated function takes the module name as
+ the second argument.
+
+ """
+ def decorated(self, fullname, *args, **kwargs):
+ module = sys.modules.get(fullname)
+ is_reload = bool(module)
+ if not is_reload:
+ # This must be done before open() is called as the 'io' module
+ # implicitly imports 'locale' and would otherwise trigger an
+ # infinite loop.
+ module = imp.new_module(fullname)
+ sys.modules[fullname] = module
+ try:
+ return fxn(self, module, *args, **kwargs)
+ except:
+ if not is_reload:
+ del sys.modules[fullname]
+ raise
+ _wrap(decorated, fxn)
+ return decorated
+
+
+def _check_name(method):
+ """Decorator to verify that the module being requested matches the one the
+ loader can handle.
+
+ The first argument (self) must define _name which the second argument is
+ compared against. If the comparison fails then ImportError is raised.
+
+ """
+ def inner(self, name, *args, **kwargs):
+ if self._name != name:
+ raise ImportError("loader cannot handle %s" % name)
+ return method(self, name, *args, **kwargs)
+ _wrap(inner, method)
+ return inner
+
+
+def _requires_builtin(fxn):
+ """Decorator to verify the named module is built-in."""
+ def wrapper(self, fullname):
+ if fullname not in sys.builtin_module_names:
+ raise ImportError("{0} is not a built-in module".format(fullname))
+ return fxn(self, fullname)
+ _wrap(wrapper, fxn)
+ return wrapper
+
+
+def _requires_frozen(fxn):
+ """Decorator to verify the named module is frozen."""
+ def wrapper(self, fullname):
+ if not imp.is_frozen(fullname):
+ raise ImportError("{0} is not a frozen module".format(fullname))
+ return fxn(self, fullname)
+ _wrap(wrapper, fxn)
+ return wrapper
+
+
+def _suffix_list(suffix_type):
+ """Return a list of file suffixes based on the imp file type."""
+ return [suffix[0] for suffix in imp.get_suffixes()
+ if suffix[2] == suffix_type]
+
+
+# Loaders #####################################################################
+
+class BuiltinImporter:
+
+ """Meta path import for built-in modules.
+
+ All methods are either class or static methods to avoid the need to
+ instantiate the class.
+
+ """
+
+ @classmethod
+ def find_module(cls, fullname, path=None):
+ """Find the built-in module.
+
+ If 'path' is ever specified then the search is considered a failure.
+
+ """
+ if path is not None:
+ return None
+ return cls if imp.is_builtin(fullname) else None
+
+ @classmethod
+ @set_package
+ @set_loader
+ @_requires_builtin
+ def load_module(cls, fullname):
+ """Load a built-in module."""
+ is_reload = fullname in sys.modules
+ try:
+ return imp.init_builtin(fullname)
+ except:
+ if not is_reload and fullname in sys.modules:
+ del sys.modules[fullname]
+ raise
+
+ @classmethod
+ @_requires_builtin
+ def get_code(cls, fullname):
+ """Return None as built-in modules do not have code objects."""
+ return None
+
+ @classmethod
+ @_requires_builtin
+ def get_source(cls, fullname):
+ """Return None as built-in modules do not have source code."""
+ return None
+
+ @classmethod
+ @_requires_builtin
+ def is_package(cls, fullname):
+ """Return None as built-in module are never packages."""
+ return False
+
+
+class FrozenImporter:
+
+ """Meta path import for frozen modules.
+
+ All methods are either class or static methods to avoid the need to
+ instantiate the class.
+
+ """
+
+ @classmethod
+ def find_module(cls, fullname, path=None):
+ """Find a frozen module."""
+ return cls if imp.is_frozen(fullname) else None
+
+ @classmethod
+ @set_package
+ @set_loader
+ @_requires_frozen
+ def load_module(cls, fullname):
+ """Load a frozen module."""
+ is_reload = fullname in sys.modules
+ try:
+ return imp.init_frozen(fullname)
+ except:
+ if not is_reload and fullname in sys.modules:
+ del sys.modules[fullname]
+ raise
+
+ @classmethod
+ @_requires_frozen
+ def get_code(cls, fullname):
+ """Return the code object for the frozen module."""
+ return imp.get_frozen_object(fullname)
+
+ @classmethod
+ @_requires_frozen
+ def get_source(cls, fullname):
+ """Return None as frozen modules do not have source code."""
+ return None
+
+ @classmethod
+ @_requires_frozen
+ def is_package(cls, fullname):
+ """Return if the frozen module is a package."""
+ return imp.is_frozen_package(fullname)
+
+
+class _LoaderBasics:
+
+ """Base class of common code needed by both SourceLoader and
+ _SourcelessFileLoader."""
+
+ def is_package(self, fullname):
+ """Concrete implementation of InspectLoader.is_package by checking if
+ the path returned by get_filename has a filename of '__init__.py'."""
+ filename = self.get_filename(fullname).rpartition(path_sep)[2]
+ return filename.rsplit('.', 1)[0] == '__init__'
+
+ def _bytes_from_bytecode(self, fullname, data, source_mtime):
+ """Return the marshalled bytes from bytecode, verifying the magic
+ number and timestamp along the way.
+
+ If source_mtime is None then skip the timestamp check.
+
+ """
+ magic = data[:4]
+ raw_timestamp = data[4:8]
+ if len(magic) != 4 or magic != imp.get_magic():
+ raise ImportError("bad magic number in {}".format(fullname))
+ elif len(raw_timestamp) != 4:
+ raise EOFError("bad timestamp in {}".format(fullname))
+ elif source_mtime is not None:
+ if marshal._r_long(raw_timestamp) != source_mtime:
+ raise ImportError("bytecode is stale for {}".format(fullname))
+ # Can't return the code object as errors from marshal loading need to
+ # propagate even when source is available.
+ return data[8:]
+
+ @module_for_loader
+ def _load_module(self, module, *, sourceless=False):
+ """Helper for load_module able to handle either source or sourceless
+ loading."""
+ name = module.__name__
+ code_object = self.get_code(name)
+ module.__file__ = self.get_filename(name)
+ if not sourceless:
+ module.__cached__ = imp.cache_from_source(module.__file__)
+ else:
+ module.__cached__ = module.__file__
+ module.__package__ = name
+ if self.is_package(name):
+ module.__path__ = [module.__file__.rsplit(path_sep, 1)[0]]
+ else:
+ module.__package__ = module.__package__.rpartition('.')[0]
+ module.__loader__ = self
+ exec(code_object, module.__dict__)
+ return module
+
+
+class SourceLoader(_LoaderBasics):
+
+ def path_mtime(self, path):
+ """Optional method that returns the modification time (an int) for the
+ specified path, where path is a str.
+
+ Implementing this method allows the loader to read bytecode files.
+
+ """
+ raise NotImplementedError
+
+ def set_data(self, path, data):
+ """Optional method which writes data (bytes) to a file path (a str).
+
+ Implementing this method allows for the writing of bytecode files.
+
+ """
+ raise NotImplementedError
+
+
+ def get_source(self, fullname):
+ """Concrete implementation of InspectLoader.get_source."""
+ import tokenize
+ path = self.get_filename(fullname)
+ try:
+ source_bytes = self.get_data(path)
+ except IOError:
+ raise ImportError("source not available through get_data()")
+ encoding = tokenize.detect_encoding(_io.BytesIO(source_bytes).readline)
+ newline_decoder = _io.IncrementalNewlineDecoder(None, True)
+ return newline_decoder.decode(source_bytes.decode(encoding[0]))
+
+ def get_code(self, fullname):
+ """Concrete implementation of InspectLoader.get_code.
+
+ Reading of bytecode requires path_mtime to be implemented. To write
+ bytecode, set_data must also be implemented.
+
+ """
+ source_path = self.get_filename(fullname)
+ bytecode_path = imp.cache_from_source(source_path)
+ source_mtime = None
+ if bytecode_path is not None:
+ try:
+ source_mtime = self.path_mtime(source_path)
+ except NotImplementedError:
+ pass
+ else:
+ try:
+ data = self.get_data(bytecode_path)
+ except IOError:
+ pass
+ else:
+ try:
+ bytes_data = self._bytes_from_bytecode(fullname, data,
+ source_mtime)
+ except (ImportError, EOFError):
+ pass
+ else:
+ found = marshal.loads(bytes_data)
+ if isinstance(found, code_type):
+ return found
+ else:
+ msg = "Non-code object in {}"
+ raise ImportError(msg.format(bytecode_path))
+ source_bytes = self.get_data(source_path)
+ code_object = compile(source_bytes, source_path, 'exec',
+ dont_inherit=True)
+ if (not sys.dont_write_bytecode and bytecode_path is not None and
+ source_mtime is not None):
+ # If e.g. Jython ever implements imp.cache_from_source to have
+ # their own cached file format, this block of code will most likely
+ # raise an exception.
+ data = bytearray(imp.get_magic())
+ data.extend(marshal._w_long(source_mtime))
+ data.extend(marshal.dumps(code_object))
+ try:
+ self.set_data(bytecode_path, data)
+ except NotImplementedError:
+ pass
+ return code_object
+
+ def load_module(self, fullname):
+ """Concrete implementation of Loader.load_module.
+
+ Requires ExecutionLoader.get_filename and ResourceLoader.get_data to be
+ implemented to load source code. Use of bytecode is dictated by whether
+ get_code uses/writes bytecode.
+
+ """
+ return self._load_module(fullname)
+
+
+class _FileLoader:
+
+ """Base file loader class which implements the loader protocol methods that
+ require file system usage."""
+
+ def __init__(self, fullname, path):
+ """Cache the module name and the path to the file found by the
+ finder."""
+ self._name = fullname
+ self._path = path
+
+ @_check_name
+ def get_filename(self, fullname):
+ """Return the path to the source file as found by the finder."""
+ return self._path
+
+ def get_data(self, path):
+ """Return the data from path as raw bytes."""
+ with _io.FileIO(path, 'r') as file:
+ return file.read()
+
+
+class _SourceFileLoader(_FileLoader, SourceLoader):
+
+ """Concrete implementation of SourceLoader using the file system."""
+
+ def path_mtime(self, path):
+ """Return the modification time for the path."""
+ return int(_os.stat(path).st_mtime)
+
+ def set_data(self, path, data):
+ """Write bytes data to a file."""
+ parent, _, filename = path.rpartition(path_sep)
+ path_parts = []
+ # Figure out what directories are missing.
+ while parent and not _path_isdir(parent):
+ parent, _, part = parent.rpartition(path_sep)
+ path_parts.append(part)
+ # Create needed directories.
+ for part in reversed(path_parts):
+ parent = _path_join(parent, part)
+ try:
+ _os.mkdir(parent)
+ except OSError as exc:
+ # Probably another Python process already created the dir.
+ if exc.errno == errno.EEXIST:
+ continue
+ else:
+ raise
+ except IOError as exc:
+ # If can't get proper access, then just forget about writing
+ # the data.
+ if exc.errno == errno.EACCES:
+ return
+ else:
+ raise
+ try:
+ with _io.FileIO(path, 'wb') as file:
+ file.write(data)
+ except IOError as exc:
+ # Don't worry if you can't write bytecode.
+ if exc.errno == errno.EACCES:
+ return
+ else:
+ raise
+
+
+class _SourcelessFileLoader(_FileLoader, _LoaderBasics):
+
+ """Loader which handles sourceless file imports."""
+
+ def load_module(self, fullname):
+ return self._load_module(fullname, sourceless=True)
+
+ def get_code(self, fullname):
+ path = self.get_filename(fullname)
+ data = self.get_data(path)
+ bytes_data = self._bytes_from_bytecode(fullname, data, None)
+ found = marshal.loads(bytes_data)
+ if isinstance(found, code_type):
+ return found
+ else:
+ raise ImportError("Non-code object in {}".format(path))
+
+ def get_source(self, fullname):
+ """Return None as there is no source code."""
+ return None
+
+
+class _ExtensionFileLoader:
+
+ """Loader for extension modules.
+
+ The constructor is designed to work with FileFinder.
+
+ """
+
+ def __init__(self, name, path):
+ """Initialize the loader.
+
+ If is_pkg is True then an exception is raised as extension modules
+ cannot be the __init__ module for an extension module.
+
+ """
+ self._name = name
+ self._path = path
+
+ @_check_name
+ @set_package
+ @set_loader
+ def load_module(self, fullname):
+ """Load an extension module."""
+ is_reload = fullname in sys.modules
+ try:
+ return imp.load_dynamic(fullname, self._path)
+ except:
+ if not is_reload and fullname in sys.modules:
+ del sys.modules[fullname]
+ raise
+
+ @_check_name
+ def is_package(self, fullname):
+ """Return False as an extension module can never be a package."""
+ return False
+
+ @_check_name
+ def get_code(self, fullname):
+ """Return None as an extension module cannot create a code object."""
+ return None
+
+ @_check_name
+ def get_source(self, fullname):
+ """Return None as extension modules have no source code."""
+ return None
+
+
+# Finders #####################################################################
+
+class PathFinder:
+
+ """Meta path finder for sys.(path|path_hooks|path_importer_cache)."""
+
+ @classmethod
+ def _path_hooks(cls, path, hooks=None):
+ """Search sequence of hooks for a finder for 'path'.
+
+ If 'hooks' is false then use sys.path_hooks.
+
+ """
+ if not hooks:
+ hooks = sys.path_hooks
+ for hook in hooks:
+ try:
+ return hook(path)
+ except ImportError:
+ continue
+ else:
+ raise ImportError("no path hook found for {0}".format(path))
+
+ @classmethod
+ def _path_importer_cache(cls, path, default=None):
+ """Get the finder for the path from sys.path_importer_cache.
+
+ If the path is not in the cache, find the appropriate finder and cache
+ it. If None is cached, get the default finder and cache that
+ (if applicable).
+
+ Because of NullImporter, some finder should be returned. The only
+ explicit fail case is if None is cached but the path cannot be used for
+ the default hook, for which ImportError is raised.
+
+ """
+ try:
+ finder = sys.path_importer_cache[path]
+ except KeyError:
+ finder = cls._path_hooks(path)
+ sys.path_importer_cache[path] = finder
+ else:
+ if finder is None and default:
+ # Raises ImportError on failure.
+ finder = default(path)
+ sys.path_importer_cache[path] = finder
+ return finder
+
+ @classmethod
+ def find_module(cls, fullname, path=None):
+ """Find the module on sys.path or 'path' based on sys.path_hooks and
+ sys.path_importer_cache."""
+ if not path:
+ path = sys.path
+ for entry in path:
+ try:
+ finder = cls._path_importer_cache(entry)
+ except ImportError:
+ continue
+ if finder:
+ loader = finder.find_module(fullname)
+ if loader:
+ return loader
+ else:
+ return None
+
+
+class _FileFinder:
+
+ """File-based finder.
+
+ Constructor takes a list of objects detailing what file extensions their
+ loader supports along with whether it can be used for a package.
+
+ """
+
+ def __init__(self, path, *details):
+ """Initialize with finder details."""
+ packages = []
+ modules = []
+ for detail in details:
+ modules.extend((suffix, detail.loader) for suffix in detail.suffixes)
+ if detail.supports_packages:
+ packages.extend((suffix, detail.loader)
+ for suffix in detail.suffixes)
+ self.packages = packages
+ self.modules = modules
+ self.path = path
+
+ def find_module(self, fullname):
+ """Try to find a loader for the specified module."""
+ tail_module = fullname.rpartition('.')[2]
+ base_path = _path_join(self.path, tail_module)
+ if _path_isdir(base_path) and _case_ok(self.path, tail_module):
+ for suffix, loader in self.packages:
+ init_filename = '__init__' + suffix
+ full_path = _path_join(base_path, init_filename)
+ if (_path_isfile(full_path) and
+ _case_ok(base_path, init_filename)):
+ return loader(fullname, full_path)
+ else:
+ msg = "Not importing directory {}: missing __init__"
+ _warnings.warn(msg.format(base_path), ImportWarning)
+ for suffix, loader in self.modules:
+ mod_filename = tail_module + suffix
+ full_path = _path_join(self.path, mod_filename)
+ if _path_isfile(full_path) and _case_ok(self.path, mod_filename):
+ return loader(fullname, full_path)
+ return None
+
+class _SourceFinderDetails:
+
+ loader = _SourceFileLoader
+ supports_packages = True
+
+ def __init__(self):
+ self.suffixes = _suffix_list(imp.PY_SOURCE)
+
+class _SourcelessFinderDetails:
+
+ loader = _SourcelessFileLoader
+ supports_packages = True
+
+ def __init__(self):
+ self.suffixes = _suffix_list(imp.PY_COMPILED)
+
+
+class _ExtensionFinderDetails:
+
+ loader = _ExtensionFileLoader
+ supports_packages = False
+
+ def __init__(self):
+ self.suffixes = _suffix_list(imp.C_EXTENSION)
+
+
+# Import itself ###############################################################
+
+def _file_path_hook(path):
+ """If the path is a directory, return a file-based finder."""
+ if _path_isdir(path):
+ return _FileFinder(path, _ExtensionFinderDetails(),
+ _SourceFinderDetails(),
+ _SourcelessFinderDetails())
+ else:
+ raise ImportError("only directories are supported")
+
+
+_DEFAULT_PATH_HOOK = _file_path_hook
+
+class _DefaultPathFinder(PathFinder):
+
+ """Subclass of PathFinder that implements implicit semantics for
+ __import__."""
+
+ @classmethod
+ def _path_hooks(cls, path):
+ """Search sys.path_hooks as well as implicit path hooks."""
+ try:
+ return super()._path_hooks(path)
+ except ImportError:
+ implicit_hooks = [_DEFAULT_PATH_HOOK, imp.NullImporter]
+ return super()._path_hooks(path, implicit_hooks)
+
+ @classmethod
+ def _path_importer_cache(cls, path):
+ """Use the default path hook when None is stored in
+ sys.path_importer_cache."""
+ return super()._path_importer_cache(path, _DEFAULT_PATH_HOOK)
+
+
+class _ImportLockContext:
+
+ """Context manager for the import lock."""
+
+ def __enter__(self):
+ """Acquire the import lock."""
+ imp.acquire_lock()
+
+ def __exit__(self, exc_type, exc_value, exc_traceback):
+ """Release the import lock regardless of any raised exceptions."""
+ imp.release_lock()
+
+
+_IMPLICIT_META_PATH = [BuiltinImporter, FrozenImporter, _DefaultPathFinder]
+
+_ERR_MSG = 'No module named {}'
+
+def _gcd_import(name, package=None, level=0):
+ """Import and return the module based on its name, the package the call is
+ being made from, and the level adjustment.
+
+ This function represents the greatest common denominator of functionality
+ between import_module and __import__. This includes settting __package__ if
+ the loader did not.
+
+ """
+ if package:
+ if not hasattr(package, 'rindex'):
+ raise ValueError("__package__ not set to a string")
+ elif package not in sys.modules:
+ msg = ("Parent module {0!r} not loaded, cannot perform relative "
+ "import")
+ raise SystemError(msg.format(package))
+ if not name and level == 0:
+ raise ValueError("Empty module name")
+ if level > 0:
+ dot = len(package)
+ for x in range(level, 1, -1):
+ try:
+ dot = package.rindex('.', 0, dot)
+ except ValueError:
+ raise ValueError("attempted relative import beyond "
+ "top-level package")
+ if name:
+ name = "{0}.{1}".format(package[:dot], name)
+ else:
+ name = package[:dot]
+ with _ImportLockContext():
+ try:
+ module = sys.modules[name]
+ if module is None:
+ message = ("import of {} halted; "
+ "None in sys.modules".format(name))
+ raise ImportError(message)
+ return module
+ except KeyError:
+ pass
+ parent = name.rpartition('.')[0]
+ path = None
+ if parent:
+ if parent not in sys.modules:
+ _gcd_import(parent)
+ # Backwards-compatibility; be nicer to skip the dict lookup.
+ parent_module = sys.modules[parent]
+ try:
+ path = parent_module.__path__
+ except AttributeError:
+ msg = (_ERR_MSG + '; {} is not a package').format(name, parent)
+ raise ImportError(msg)
+ meta_path = sys.meta_path + _IMPLICIT_META_PATH
+ for finder in meta_path:
+ loader = finder.find_module(name, path)
+ if loader is not None:
+ # The parent import may have already imported this module.
+ if name not in sys.modules:
+ loader.load_module(name)
+ break
+ else:
+ raise ImportError(_ERR_MSG.format(name))
+ # Backwards-compatibility; be nicer to skip the dict lookup.
+ module = sys.modules[name]
+ if parent:
+ # Set the module as an attribute on its parent.
+ setattr(parent_module, name.rpartition('.')[2], module)
+ # Set __package__ if the loader did not.
+ if not hasattr(module, '__package__') or module.__package__ is None:
+ # Watch out for what comes out of sys.modules to not be a module,
+ # e.g. an int.
+ try:
+ module.__package__ = module.__name__
+ if not hasattr(module, '__path__'):
+ module.__package__ = module.__package__.rpartition('.')[0]
+ except AttributeError:
+ pass
+ return module
+
+
+def __import__(name, globals={}, locals={}, fromlist=[], level=0):
+ """Import a module.
+
+ The 'globals' argument is used to infer where the import is occuring from
+ to handle relative imports. The 'locals' argument is ignored. The
+ 'fromlist' argument specifies what should exist as attributes on the module
+ being imported (e.g. ``from module import <fromlist>``). The 'level'
+ argument represents the package location to import from in a relative
+ import (e.g. ``from ..pkg import mod`` would have a 'level' of 2).
+
+ """
+ if not hasattr(name, 'rpartition'):
+ raise TypeError("module name must be str, not {}".format(type(name)))
+ if level == 0:
+ module = _gcd_import(name)
+ else:
+ # __package__ is not guaranteed to be defined or could be set to None
+ # to represent that it's proper value is unknown
+ package = globals.get('__package__')
+ if package is None:
+ package = globals['__name__']
+ if '__path__' not in globals:
+ package = package.rpartition('.')[0]
+ module = _gcd_import(name, package, level)
+ # The hell that is fromlist ...
+ if not fromlist:
+ # Return up to the first dot in 'name'. This is complicated by the fact
+ # that 'name' may be relative.
+ if level == 0:
+ return sys.modules[name.partition('.')[0]]
+ elif not name:
+ return module
+ else:
+ cut_off = len(name) - len(name.partition('.')[0])
+ return sys.modules[module.__name__[:-cut_off]]
+ else:
+ # If a package was imported, try to import stuff from fromlist.
+ if hasattr(module, '__path__'):
+ if '*' in fromlist and hasattr(module, '__all__'):
+ fromlist = list(fromlist)
+ fromlist.remove('*')
+ fromlist.extend(module.__all__)
+ for x in (y for y in fromlist if not hasattr(module,y)):
+ try:
+ _gcd_import('{0}.{1}'.format(module.__name__, x))
+ except ImportError:
+ pass
+ return module
diff --git a/Lib/importlib/abc.py b/Lib/importlib/abc.py
new file mode 100644
index 0000000..fa343f8
--- /dev/null
+++ b/Lib/importlib/abc.py
@@ -0,0 +1,304 @@
+"""Abstract base classes related to import."""
+from . import _bootstrap
+from . import machinery
+from . import util
+import abc
+import imp
+import io
+import marshal
+import os.path
+import sys
+import tokenize
+import types
+import warnings
+
+
+class Loader(metaclass=abc.ABCMeta):
+
+ """Abstract base class for import loaders."""
+
+ @abc.abstractmethod
+ def load_module(self, fullname):
+ """Abstract method which when implemented should load a module.
+ The fullname is a str."""
+ raise NotImplementedError
+
+
+class Finder(metaclass=abc.ABCMeta):
+
+ """Abstract base class for import finders."""
+
+ @abc.abstractmethod
+ def find_module(self, fullname, path=None):
+ """Abstract method which when implemented should find a module.
+ The fullname is a str and the optional path is a str or None.
+ Returns a Loader object.
+ """
+ raise NotImplementedError
+
+Finder.register(machinery.BuiltinImporter)
+Finder.register(machinery.FrozenImporter)
+Finder.register(machinery.PathFinder)
+
+
+class ResourceLoader(Loader):
+
+ """Abstract base class for loaders which can return data from their
+ back-end storage.
+
+ This ABC represents one of the optional protocols specified by PEP 302.
+
+ """
+
+ @abc.abstractmethod
+ def get_data(self, path):
+ """Abstract method which when implemented should return the bytes for
+ the specified path. The path must be a str."""
+ raise NotImplementedError
+
+
+class InspectLoader(Loader):
+
+ """Abstract base class for loaders which support inspection about the
+ modules they can load.
+
+ This ABC represents one of the optional protocols specified by PEP 302.
+
+ """
+
+ @abc.abstractmethod
+ def is_package(self, fullname):
+ """Abstract method which when implemented should return whether the
+ module is a package. The fullname is a str. Returns a bool."""
+ raise NotImplementedError
+
+ @abc.abstractmethod
+ def get_code(self, fullname):
+ """Abstract method which when implemented should return the code object
+ for the module. The fullname is a str. Returns a types.CodeType."""
+ raise NotImplementedError
+
+ @abc.abstractmethod
+ def get_source(self, fullname):
+ """Abstract method which should return the source code for the
+ module. The fullname is a str. Returns a str."""
+ raise NotImplementedError
+
+InspectLoader.register(machinery.BuiltinImporter)
+InspectLoader.register(machinery.FrozenImporter)
+
+
+class ExecutionLoader(InspectLoader):
+
+ """Abstract base class for loaders that wish to support the execution of
+ modules as scripts.
+
+ This ABC represents one of the optional protocols specified in PEP 302.
+
+ """
+
+ @abc.abstractmethod
+ def get_filename(self, fullname):
+ """Abstract method which should return the value that __file__ is to be
+ set to."""
+ raise NotImplementedError
+
+
+class SourceLoader(_bootstrap.SourceLoader, ResourceLoader, ExecutionLoader):
+
+ """Abstract base class for loading source code (and optionally any
+ corresponding bytecode).
+
+ To support loading from source code, the abstractmethods inherited from
+ ResourceLoader and ExecutionLoader need to be implemented. To also support
+ loading from bytecode, the optional methods specified directly by this ABC
+ is required.
+
+ Inherited abstractmethods not implemented in this ABC:
+
+ * ResourceLoader.get_data
+ * ExecutionLoader.get_filename
+
+ """
+
+ def path_mtime(self, path):
+ """Return the (int) modification time for the path (str)."""
+ raise NotImplementedError
+
+ def set_data(self, path, data):
+ """Write the bytes to the path (if possible).
+
+ Accepts a str path and data as bytes.
+
+ Any needed intermediary directories are to be created. If for some
+ reason the file cannot be written because of permissions, fail
+ silently.
+
+ """
+ raise NotImplementedError
+
+
+class PyLoader(SourceLoader):
+
+ """Implement the deprecated PyLoader ABC in terms of SourceLoader.
+
+ This class has been deprecated! It is slated for removal in Python 3.4.
+ If compatibility with Python 3.1 is not needed then implement the
+ SourceLoader ABC instead of this class. If Python 3.1 compatibility is
+ needed, then use the following idiom to have a single class that is
+ compatible with Python 3.1 onwards::
+
+ try:
+ from importlib.abc import SourceLoader
+ except ImportError:
+ from importlib.abc import PyLoader as SourceLoader
+
+
+ class CustomLoader(SourceLoader):
+ def get_filename(self, fullname):
+ # Implement ...
+
+ def source_path(self, fullname):
+ '''Implement source_path in terms of get_filename.'''
+ try:
+ return self.get_filename(fullname)
+ except ImportError:
+ return None
+
+ def is_package(self, fullname):
+ filename = os.path.basename(self.get_filename(fullname))
+ return os.path.splitext(filename)[0] == '__init__'
+
+ """
+
+ @abc.abstractmethod
+ def is_package(self, fullname):
+ raise NotImplementedError
+
+ @abc.abstractmethod
+ def source_path(self, fullname):
+ """Abstract method. Accepts a str module name and returns the path to
+ the source code for the module."""
+ raise NotImplementedError
+
+ def get_filename(self, fullname):
+ """Implement get_filename in terms of source_path.
+
+ As get_filename should only return a source file path there is no
+ chance of the path not existing but loading still being possible, so
+ ImportError should propagate instead of being turned into returning
+ None.
+
+ """
+ warnings.warn("importlib.abc.PyLoader is deprecated and is "
+ "slated for removal in Python 3.4; "
+ "use SourceLoader instead. "
+ "See the importlib documentation on how to be "
+ "compatible with Python 3.1 onwards.",
+ PendingDeprecationWarning)
+ path = self.source_path(fullname)
+ if path is None:
+ raise ImportError
+ else:
+ return path
+
+
+class PyPycLoader(PyLoader):
+
+ """Abstract base class to assist in loading source and bytecode by
+ requiring only back-end storage methods to be implemented.
+
+ This class has been deprecated! Removal is slated for Python 3.4. Implement
+ the SourceLoader ABC instead. If Python 3.1 compatibility is needed, see
+ PyLoader.
+
+ The methods get_code, get_source, and load_module are implemented for the
+ user.
+
+ """
+
+ def get_filename(self, fullname):
+ """Return the source or bytecode file path."""
+ path = self.source_path(fullname)
+ if path is not None:
+ return path
+ path = self.bytecode_path(fullname)
+ if path is not None:
+ return path
+ raise ImportError("no source or bytecode path available for "
+ "{0!r}".format(fullname))
+
+ def get_code(self, fullname):
+ """Get a code object from source or bytecode."""
+ warnings.warn("importlib.abc.PyPycLoader is deprecated and slated for "
+ "removal in Python 3.4; use SourceLoader instead. "
+ "If Python 3.1 compatibility is required, see the "
+ "latest documentation for PyLoader.",
+ PendingDeprecationWarning)
+ source_timestamp = self.source_mtime(fullname)
+ # Try to use bytecode if it is available.
+ bytecode_path = self.bytecode_path(fullname)
+ if bytecode_path:
+ data = self.get_data(bytecode_path)
+ try:
+ magic = data[:4]
+ if len(magic) < 4:
+ raise ImportError("bad magic number in {}".format(fullname))
+ raw_timestamp = data[4:8]
+ if len(raw_timestamp) < 4:
+ raise EOFError("bad timestamp in {}".format(fullname))
+ pyc_timestamp = marshal._r_long(raw_timestamp)
+ bytecode = data[8:]
+ # Verify that the magic number is valid.
+ if imp.get_magic() != magic:
+ raise ImportError("bad magic number in {}".format(fullname))
+ # Verify that the bytecode is not stale (only matters when
+ # there is source to fall back on.
+ if source_timestamp:
+ if pyc_timestamp < source_timestamp:
+ raise ImportError("bytecode is stale")
+ except (ImportError, EOFError):
+ # If source is available give it a shot.
+ if source_timestamp is not None:
+ pass
+ else:
+ raise
+ else:
+ # Bytecode seems fine, so try to use it.
+ return marshal.loads(bytecode)
+ elif source_timestamp is None:
+ raise ImportError("no source or bytecode available to create code "
+ "object for {0!r}".format(fullname))
+ # Use the source.
+ source_path = self.source_path(fullname)
+ if source_path is None:
+ message = "a source path must exist to load {0}".format(fullname)
+ raise ImportError(message)
+ source = self.get_data(source_path)
+ code_object = compile(source, source_path, 'exec', dont_inherit=True)
+ # Generate bytecode and write it out.
+ if not sys.dont_write_bytecode:
+ data = bytearray(imp.get_magic())
+ data.extend(marshal._w_long(source_timestamp))
+ data.extend(marshal.dumps(code_object))
+ self.write_bytecode(fullname, data)
+ return code_object
+
+ @abc.abstractmethod
+ def source_mtime(self, fullname):
+ """Abstract method. Accepts a str filename and returns an int
+ modification time for the source of the module."""
+ raise NotImplementedError
+
+ @abc.abstractmethod
+ def bytecode_path(self, fullname):
+ """Abstract method. Accepts a str filename and returns the str pathname
+ to the bytecode for the module."""
+ raise NotImplementedError
+
+ @abc.abstractmethod
+ def write_bytecode(self, fullname, bytecode):
+ """Abstract method. Accepts a str filename and bytes object
+ representing the bytecode for the module. Returns a boolean
+ representing whether the bytecode was written or not."""
+ raise NotImplementedError
diff --git a/Lib/importlib/machinery.py b/Lib/importlib/machinery.py
new file mode 100644
index 0000000..5197744
--- /dev/null
+++ b/Lib/importlib/machinery.py
@@ -0,0 +1,5 @@
+"""The machinery of importlib: finders, loaders, hooks, etc."""
+
+from ._bootstrap import BuiltinImporter
+from ._bootstrap import FrozenImporter
+from ._bootstrap import PathFinder
diff --git a/Lib/importlib/test/__init__.py b/Lib/importlib/test/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Lib/importlib/test/__init__.py
diff --git a/Lib/importlib/test/__main__.py b/Lib/importlib/test/__main__.py
new file mode 100644
index 0000000..decc53d
--- /dev/null
+++ b/Lib/importlib/test/__main__.py
@@ -0,0 +1,29 @@
+"""Run importlib's test suite.
+
+Specifying the ``--builtin`` flag will run tests, where applicable, with
+builtins.__import__ instead of importlib.__import__.
+
+"""
+import importlib
+from importlib.test.import_ import util
+import os.path
+from test.support import run_unittest
+import sys
+import unittest
+
+
+def test_main():
+ if '__pycache__' in __file__:
+ parts = __file__.split(os.path.sep)
+ start_dir = sep.join(parts[:-2])
+ else:
+ start_dir = os.path.dirname(__file__)
+ top_dir = os.path.dirname(os.path.dirname(start_dir))
+ test_loader = unittest.TestLoader()
+ if '--builtin' in sys.argv:
+ util.using___import__ = True
+ run_unittest(test_loader.discover(start_dir, top_level_dir=top_dir))
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/abc.py b/Lib/importlib/test/abc.py
new file mode 100644
index 0000000..2c17ac3
--- /dev/null
+++ b/Lib/importlib/test/abc.py
@@ -0,0 +1,99 @@
+import abc
+import unittest
+
+
+class FinderTests(unittest.TestCase, metaclass=abc.ABCMeta):
+
+ """Basic tests for a finder to pass."""
+
+ @abc.abstractmethod
+ def test_module(self):
+ # Test importing a top-level module.
+ pass
+
+ @abc.abstractmethod
+ def test_package(self):
+ # Test importing a package.
+ pass
+
+ @abc.abstractmethod
+ def test_module_in_package(self):
+ # Test importing a module contained within a package.
+ # A value for 'path' should be used if for a meta_path finder.
+ pass
+
+ @abc.abstractmethod
+ def test_package_in_package(self):
+ # Test importing a subpackage.
+ # A value for 'path' should be used if for a meta_path finder.
+ pass
+
+ @abc.abstractmethod
+ def test_package_over_module(self):
+ # Test that packages are chosen over modules.
+ pass
+
+ @abc.abstractmethod
+ def test_failure(self):
+ # Test trying to find a module that cannot be handled.
+ pass
+
+
+class LoaderTests(unittest.TestCase, metaclass=abc.ABCMeta):
+
+ @abc.abstractmethod
+ def test_module(self):
+ """A module should load without issue.
+
+ After the loader returns the module should be in sys.modules.
+
+ Attributes to verify:
+
+ * __file__
+ * __loader__
+ * __name__
+ * No __path__
+
+ """
+ pass
+
+ @abc.abstractmethod
+ def test_package(self):
+ """Loading a package should work.
+
+ After the loader returns the module should be in sys.modules.
+
+ Attributes to verify:
+
+ * __name__
+ * __file__
+ * __package__
+ * __path__
+ * __loader__
+
+ """
+ pass
+
+ @abc.abstractmethod
+ def test_lacking_parent(self):
+ """A loader should not be dependent on it's parent package being
+ imported."""
+ pass
+
+ @abc.abstractmethod
+ def test_module_reuse(self):
+ """If a module is already in sys.modules, it should be reused."""
+ pass
+
+ @abc.abstractmethod
+ def test_state_after_failure(self):
+ """If a module is already in sys.modules and a reload fails
+ (e.g. a SyntaxError), the module should be in the state it was before
+ the reload began."""
+ pass
+
+ @abc.abstractmethod
+ def test_unloadable(self):
+ """Test ImportError is raised when the loader is asked to load a module
+ it can't."""
+ pass
diff --git a/Lib/importlib/test/benchmark.py b/Lib/importlib/test/benchmark.py
new file mode 100644
index 0000000..b5de6c6
--- /dev/null
+++ b/Lib/importlib/test/benchmark.py
@@ -0,0 +1,172 @@
+"""Benchmark some basic import use-cases.
+
+The assumption is made that this benchmark is run in a fresh interpreter and
+thus has no external changes made to import-related attributes in sys.
+
+"""
+from . import util
+from .source import util as source_util
+import decimal
+import imp
+import importlib
+import os
+import py_compile
+import sys
+import timeit
+
+
+def bench(name, cleanup=lambda: None, *, seconds=1, repeat=3):
+ """Bench the given statement as many times as necessary until total
+ executions take one second."""
+ stmt = "__import__({!r})".format(name)
+ timer = timeit.Timer(stmt)
+ for x in range(repeat):
+ total_time = 0
+ count = 0
+ while total_time < seconds:
+ try:
+ total_time += timer.timeit(1)
+ finally:
+ cleanup()
+ count += 1
+ else:
+ # One execution too far
+ if total_time > seconds:
+ count -= 1
+ yield count // seconds
+
+def from_cache(seconds, repeat):
+ """sys.modules"""
+ name = '<benchmark import>'
+ module = imp.new_module(name)
+ module.__file__ = '<test>'
+ module.__package__ = ''
+ with util.uncache(name):
+ sys.modules[name] = module
+ for result in bench(name, repeat=repeat, seconds=seconds):
+ yield result
+
+
+def builtin_mod(seconds, repeat):
+ """Built-in module"""
+ name = 'errno'
+ if name in sys.modules:
+ del sys.modules[name]
+ # Relying on built-in importer being implicit.
+ for result in bench(name, lambda: sys.modules.pop(name), repeat=repeat,
+ seconds=seconds):
+ yield result
+
+
+def source_wo_bytecode(seconds, repeat):
+ """Source w/o bytecode: simple"""
+ sys.dont_write_bytecode = True
+ try:
+ name = '__importlib_test_benchmark__'
+ # Clears out sys.modules and puts an entry at the front of sys.path.
+ with source_util.create_modules(name) as mapping:
+ assert not os.path.exists(imp.cache_from_source(mapping[name]))
+ for result in bench(name, lambda: sys.modules.pop(name), repeat=repeat,
+ seconds=seconds):
+ yield result
+ finally:
+ sys.dont_write_bytecode = False
+
+
+def decimal_wo_bytecode(seconds, repeat):
+ """Source w/o bytecode: decimal"""
+ name = 'decimal'
+ decimal_bytecode = imp.cache_from_source(decimal.__file__)
+ if os.path.exists(decimal_bytecode):
+ os.unlink(decimal_bytecode)
+ sys.dont_write_bytecode = True
+ try:
+ for result in bench(name, lambda: sys.modules.pop(name), repeat=repeat,
+ seconds=seconds):
+ yield result
+ finally:
+ sys.dont_write_bytecode = False
+
+
+def source_writing_bytecode(seconds, repeat):
+ """Source writing bytecode: simple"""
+ assert not sys.dont_write_bytecode
+ name = '__importlib_test_benchmark__'
+ with source_util.create_modules(name) as mapping:
+ def cleanup():
+ sys.modules.pop(name)
+ os.unlink(imp.cache_from_source(mapping[name]))
+ for result in bench(name, cleanup, repeat=repeat, seconds=seconds):
+ assert not os.path.exists(imp.cache_from_source(mapping[name]))
+ yield result
+
+
+def decimal_writing_bytecode(seconds, repeat):
+ """Source writing bytecode: decimal"""
+ assert not sys.dont_write_bytecode
+ name = 'decimal'
+ def cleanup():
+ sys.modules.pop(name)
+ os.unlink(imp.cache_from_source(decimal.__file__))
+ for result in bench(name, cleanup, repeat=repeat, seconds=seconds):
+ yield result
+
+
+def source_using_bytecode(seconds, repeat):
+ """Bytecode w/ source: simple"""
+ name = '__importlib_test_benchmark__'
+ with source_util.create_modules(name) as mapping:
+ py_compile.compile(mapping[name])
+ assert os.path.exists(imp.cache_from_source(mapping[name]))
+ for result in bench(name, lambda: sys.modules.pop(name), repeat=repeat,
+ seconds=seconds):
+ yield result
+
+
+def decimal_using_bytecode(seconds, repeat):
+ """Bytecode w/ source: decimal"""
+ name = 'decimal'
+ py_compile.compile(decimal.__file__)
+ for result in bench(name, lambda: sys.modules.pop(name), repeat=repeat,
+ seconds=seconds):
+ yield result
+
+
+def main(import_):
+ __builtins__.__import__ = import_
+ benchmarks = (from_cache, builtin_mod,
+ source_using_bytecode, source_wo_bytecode,
+ source_writing_bytecode,
+ decimal_using_bytecode, decimal_writing_bytecode,
+ decimal_wo_bytecode,)
+ seconds = 1
+ seconds_plural = 's' if seconds > 1 else ''
+ repeat = 3
+ header = "Measuring imports/second over {} second{}, best out of {}\n"
+ print(header.format(seconds, seconds_plural, repeat))
+ for benchmark in benchmarks:
+ print(benchmark.__doc__, "[", end=' ')
+ sys.stdout.flush()
+ results = []
+ for result in benchmark(seconds=seconds, repeat=repeat):
+ results.append(result)
+ print(result, end=' ')
+ sys.stdout.flush()
+ assert not sys.dont_write_bytecode
+ print("]", "best is", format(max(results), ',d'))
+
+
+if __name__ == '__main__':
+ import optparse
+
+ parser = optparse.OptionParser()
+ parser.add_option('-b', '--builtin', dest='builtin', action='store_true',
+ default=False, help="use the built-in __import__")
+ options, args = parser.parse_args()
+ if args:
+ raise RuntimeError("unrecognized args: {}".format(args))
+ import_ = __import__
+ if not options.builtin:
+ import_ = importlib.__import__
+
+ main(import_)
diff --git a/Lib/importlib/test/builtin/__init__.py b/Lib/importlib/test/builtin/__init__.py
new file mode 100644
index 0000000..31a3b5f
--- /dev/null
+++ b/Lib/importlib/test/builtin/__init__.py
@@ -0,0 +1,12 @@
+import importlib.test
+import os
+
+
+def test_suite():
+ directory = os.path.dirname(__file__)
+ return importlib.test.test_suite('importlib.test.builtin', directory)
+
+
+if __name__ == '__main__':
+ from test.support import run_unittest
+ run_unittest(test_suite())
diff --git a/Lib/importlib/test/builtin/test_finder.py b/Lib/importlib/test/builtin/test_finder.py
new file mode 100644
index 0000000..40f690e
--- /dev/null
+++ b/Lib/importlib/test/builtin/test_finder.py
@@ -0,0 +1,55 @@
+from importlib import machinery
+from .. import abc
+from .. import util
+from . import util as builtin_util
+
+import sys
+import unittest
+
+class FinderTests(abc.FinderTests):
+
+ """Test find_module() for built-in modules."""
+
+ def test_module(self):
+ # Common case.
+ with util.uncache(builtin_util.NAME):
+ found = machinery.BuiltinImporter.find_module(builtin_util.NAME)
+ self.assertTrue(found)
+
+ def test_package(self):
+ # Built-in modules cannot be a package.
+ pass
+
+ def test_module_in_package(self):
+ # Built-in modules cannobt be in a package.
+ pass
+
+ def test_package_in_package(self):
+ # Built-in modules cannot be a package.
+ pass
+
+ def test_package_over_module(self):
+ # Built-in modules cannot be a package.
+ pass
+
+ def test_failure(self):
+ assert 'importlib' not in sys.builtin_module_names
+ loader = machinery.BuiltinImporter.find_module('importlib')
+ self.assertTrue(loader is None)
+
+ def test_ignore_path(self):
+ # The value for 'path' should always trigger a failed import.
+ with util.uncache(builtin_util.NAME):
+ loader = machinery.BuiltinImporter.find_module(builtin_util.NAME,
+ ['pkg'])
+ self.assertTrue(loader is None)
+
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(FinderTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/builtin/test_loader.py b/Lib/importlib/test/builtin/test_loader.py
new file mode 100644
index 0000000..1a8539b
--- /dev/null
+++ b/Lib/importlib/test/builtin/test_loader.py
@@ -0,0 +1,102 @@
+import importlib
+from importlib import machinery
+from .. import abc
+from .. import util
+from . import util as builtin_util
+
+import sys
+import types
+import unittest
+
+
+class LoaderTests(abc.LoaderTests):
+
+ """Test load_module() for built-in modules."""
+
+ verification = {'__name__': 'errno', '__package__': '',
+ '__loader__': machinery.BuiltinImporter}
+
+ def verify(self, module):
+ """Verify that the module matches against what it should have."""
+ self.assertTrue(isinstance(module, types.ModuleType))
+ for attr, value in self.verification.items():
+ self.assertEqual(getattr(module, attr), value)
+ self.assertTrue(module.__name__ in sys.modules)
+
+ load_module = staticmethod(lambda name:
+ machinery.BuiltinImporter.load_module(name))
+
+ def test_module(self):
+ # Common case.
+ with util.uncache(builtin_util.NAME):
+ module = self.load_module(builtin_util.NAME)
+ self.verify(module)
+
+ def test_package(self):
+ # Built-in modules cannot be a package.
+ pass
+
+ def test_lacking_parent(self):
+ # Built-in modules cannot be a package.
+ pass
+
+ def test_state_after_failure(self):
+ # Not way to force an imoprt failure.
+ pass
+
+ def test_module_reuse(self):
+ # Test that the same module is used in a reload.
+ with util.uncache(builtin_util.NAME):
+ module1 = self.load_module(builtin_util.NAME)
+ module2 = self.load_module(builtin_util.NAME)
+ self.assertTrue(module1 is module2)
+
+ def test_unloadable(self):
+ name = 'dssdsdfff'
+ assert name not in sys.builtin_module_names
+ with self.assertRaises(ImportError):
+ self.load_module(name)
+
+ def test_already_imported(self):
+ # Using the name of a module already imported but not a built-in should
+ # still fail.
+ assert hasattr(importlib, '__file__') # Not a built-in.
+ with self.assertRaises(ImportError):
+ self.load_module('importlib')
+
+
+class InspectLoaderTests(unittest.TestCase):
+
+ """Tests for InspectLoader methods for BuiltinImporter."""
+
+ def test_get_code(self):
+ # There is no code object.
+ result = machinery.BuiltinImporter.get_code(builtin_util.NAME)
+ self.assertTrue(result is None)
+
+ def test_get_source(self):
+ # There is no source.
+ result = machinery.BuiltinImporter.get_source(builtin_util.NAME)
+ self.assertTrue(result is None)
+
+ def test_is_package(self):
+ # Cannot be a package.
+ result = machinery.BuiltinImporter.is_package(builtin_util.NAME)
+ self.assertTrue(not result)
+
+ def test_not_builtin(self):
+ # Modules not built-in should raise ImportError.
+ for meth_name in ('get_code', 'get_source', 'is_package'):
+ method = getattr(machinery.BuiltinImporter, meth_name)
+ with self.assertRaises(ImportError):
+ method(builtin_util.BAD_NAME)
+
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(LoaderTests, InspectLoaderTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/builtin/util.py b/Lib/importlib/test/builtin/util.py
new file mode 100644
index 0000000..5704699
--- /dev/null
+++ b/Lib/importlib/test/builtin/util.py
@@ -0,0 +1,7 @@
+import sys
+
+assert 'errno' in sys.builtin_module_names
+NAME = 'errno'
+
+assert 'importlib' not in sys.builtin_module_names
+BAD_NAME = 'importlib'
diff --git a/Lib/importlib/test/extension/__init__.py b/Lib/importlib/test/extension/__init__.py
new file mode 100644
index 0000000..2ec5840
--- /dev/null
+++ b/Lib/importlib/test/extension/__init__.py
@@ -0,0 +1,13 @@
+import importlib.test
+import os.path
+import unittest
+
+
+def test_suite():
+ directory = os.path.dirname(__file__)
+ return importlib.test.test_suite('importlib.test.extension', directory)
+
+
+if __name__ == '__main__':
+ from test.support import run_unittest
+ run_unittest(test_suite())
diff --git a/Lib/importlib/test/extension/test_case_sensitivity.py b/Lib/importlib/test/extension/test_case_sensitivity.py
new file mode 100644
index 0000000..e062fb6
--- /dev/null
+++ b/Lib/importlib/test/extension/test_case_sensitivity.py
@@ -0,0 +1,42 @@
+import sys
+from test import support
+import unittest
+from importlib import _bootstrap
+from .. import util
+from . import util as ext_util
+
+
+@util.case_insensitive_tests
+class ExtensionModuleCaseSensitivityTest(unittest.TestCase):
+
+ def find_module(self):
+ good_name = ext_util.NAME
+ bad_name = good_name.upper()
+ assert good_name != bad_name
+ finder = _bootstrap._FileFinder(ext_util.PATH,
+ _bootstrap._ExtensionFinderDetails())
+ return finder.find_module(bad_name)
+
+ def test_case_sensitive(self):
+ with support.EnvironmentVarGuard() as env:
+ env.unset('PYTHONCASEOK')
+ loader = self.find_module()
+ self.assertIsNone(loader)
+
+ def test_case_insensitivity(self):
+ with support.EnvironmentVarGuard() as env:
+ env.set('PYTHONCASEOK', '1')
+ loader = self.find_module()
+ self.assertTrue(hasattr(loader, 'load_module'))
+
+
+
+
+def test_main():
+ if ext_util.FILENAME is None:
+ return
+ support.run_unittest(ExtensionModuleCaseSensitivityTest)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/extension/test_finder.py b/Lib/importlib/test/extension/test_finder.py
new file mode 100644
index 0000000..ea97483
--- /dev/null
+++ b/Lib/importlib/test/extension/test_finder.py
@@ -0,0 +1,47 @@
+from importlib import _bootstrap
+from .. import abc
+from . import util
+
+import unittest
+
+class FinderTests(abc.FinderTests):
+
+ """Test the finder for extension modules."""
+
+ def find_module(self, fullname):
+ importer = _bootstrap._FileFinder(util.PATH,
+ _bootstrap._ExtensionFinderDetails())
+ return importer.find_module(fullname)
+
+ def test_module(self):
+ self.assertTrue(self.find_module(util.NAME))
+
+ def test_package(self):
+ # Extension modules cannot be an __init__ for a package.
+ pass
+
+ def test_module_in_package(self):
+ # No extension module in a package available for testing.
+ pass
+
+ def test_package_in_package(self):
+ # Extension modules cannot be an __init__ for a package.
+ pass
+
+ def test_package_over_module(self):
+ # Extension modules cannot be an __init__ for a package.
+ pass
+
+ def test_failure(self):
+ self.assertTrue(self.find_module('asdfjkl;') is None)
+
+ # XXX Raise an exception if someone tries to use the 'path' argument?
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(FinderTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/extension/test_loader.py b/Lib/importlib/test/extension/test_loader.py
new file mode 100644
index 0000000..4a783db
--- /dev/null
+++ b/Lib/importlib/test/extension/test_loader.py
@@ -0,0 +1,59 @@
+from importlib import _bootstrap
+from . import util as ext_util
+from .. import abc
+from .. import util
+
+import sys
+import unittest
+
+
+class LoaderTests(abc.LoaderTests):
+
+ """Test load_module() for extension modules."""
+
+ def load_module(self, fullname):
+ loader = _bootstrap._ExtensionFileLoader(ext_util.NAME,
+ ext_util.FILEPATH)
+ return loader.load_module(fullname)
+
+ def test_module(self):
+ with util.uncache(ext_util.NAME):
+ module = self.load_module(ext_util.NAME)
+ for attr, value in [('__name__', ext_util.NAME),
+ ('__file__', ext_util.FILEPATH),
+ ('__package__', '')]:
+ self.assertEqual(getattr(module, attr), value)
+ self.assertTrue(ext_util.NAME in sys.modules)
+ self.assertTrue(isinstance(module.__loader__,
+ _bootstrap._ExtensionFileLoader))
+
+ def test_package(self):
+ # Extensions are not found in packages.
+ pass
+
+ def test_lacking_parent(self):
+ # Extensions are not found in packages.
+ pass
+
+ def test_module_reuse(self):
+ with util.uncache(ext_util.NAME):
+ module1 = self.load_module(ext_util.NAME)
+ module2 = self.load_module(ext_util.NAME)
+ self.assertTrue(module1 is module2)
+
+ def test_state_after_failure(self):
+ # No easy way to trigger a failure after a successful import.
+ pass
+
+ def test_unloadable(self):
+ with self.assertRaises(ImportError):
+ self.load_module('asdfjkl;')
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(LoaderTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/extension/test_path_hook.py b/Lib/importlib/test/extension/test_path_hook.py
new file mode 100644
index 0000000..4610420
--- /dev/null
+++ b/Lib/importlib/test/extension/test_path_hook.py
@@ -0,0 +1,31 @@
+from importlib import _bootstrap
+from . import util
+
+import collections
+import imp
+import sys
+import unittest
+
+
+class PathHookTests(unittest.TestCase):
+
+ """Test the path hook for extension modules."""
+ # XXX Should it only succeed for pre-existing directories?
+ # XXX Should it only work for directories containing an extension module?
+
+ def hook(self, entry):
+ return _bootstrap._file_path_hook(entry)
+
+ def test_success(self):
+ # Path hook should handle a directory where a known extension module
+ # exists.
+ self.assertTrue(hasattr(self.hook(util.PATH), 'find_module'))
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(PathHookTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/extension/util.py b/Lib/importlib/test/extension/util.py
new file mode 100644
index 0000000..d149169
--- /dev/null
+++ b/Lib/importlib/test/extension/util.py
@@ -0,0 +1,21 @@
+import imp
+import os
+import sys
+
+PATH = None
+EXT = None
+FILENAME = None
+NAME = '_testcapi'
+_file_exts = [x[0] for x in imp.get_suffixes() if x[2] == imp.C_EXTENSION]
+try:
+ for PATH in sys.path:
+ for EXT in _file_exts:
+ FILENAME = NAME + EXT
+ FILEPATH = os.path.join(PATH, FILENAME)
+ if os.path.exists(os.path.join(PATH, FILENAME)):
+ raise StopIteration
+ else:
+ PATH = EXT = FILENAME = FILEPATH = None
+except StopIteration:
+ pass
+del _file_exts
diff --git a/Lib/importlib/test/frozen/__init__.py b/Lib/importlib/test/frozen/__init__.py
new file mode 100644
index 0000000..2945eeb
--- /dev/null
+++ b/Lib/importlib/test/frozen/__init__.py
@@ -0,0 +1,13 @@
+import importlib.test
+import os.path
+import unittest
+
+
+def test_suite():
+ directory = os.path.dirname(__file__)
+ return importlib.test.test_suite('importlib.test.frozen', directory)
+
+
+if __name__ == '__main__':
+ from test.support import run_unittest
+ run_unittest(test_suite())
diff --git a/Lib/importlib/test/frozen/test_finder.py b/Lib/importlib/test/frozen/test_finder.py
new file mode 100644
index 0000000..db88379
--- /dev/null
+++ b/Lib/importlib/test/frozen/test_finder.py
@@ -0,0 +1,47 @@
+from ... import machinery
+from .. import abc
+
+import unittest
+
+
+class FinderTests(abc.FinderTests):
+
+ """Test finding frozen modules."""
+
+ def find(self, name, path=None):
+ finder = machinery.FrozenImporter
+ return finder.find_module(name, path)
+
+ def test_module(self):
+ name = '__hello__'
+ loader = self.find(name)
+ self.assertTrue(hasattr(loader, 'load_module'))
+
+ def test_package(self):
+ loader = self.find('__phello__')
+ self.assertTrue(hasattr(loader, 'load_module'))
+
+ def test_module_in_package(self):
+ loader = self.find('__phello__.spam', ['__phello__'])
+ self.assertTrue(hasattr(loader, 'load_module'))
+
+ def test_package_in_package(self):
+ # No frozen package within another package to test with.
+ pass
+
+ def test_package_over_module(self):
+ # No easy way to test.
+ pass
+
+ def test_failure(self):
+ loader = self.find('<not real>')
+ self.assertTrue(loader is None)
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(FinderTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/frozen/test_loader.py b/Lib/importlib/test/frozen/test_loader.py
new file mode 100644
index 0000000..b685ef5
--- /dev/null
+++ b/Lib/importlib/test/frozen/test_loader.py
@@ -0,0 +1,105 @@
+from importlib import machinery
+import imp
+import unittest
+from .. import abc
+from .. import util
+from test.support import captured_stdout
+
+class LoaderTests(abc.LoaderTests):
+
+ def test_module(self):
+ with util.uncache('__hello__'), captured_stdout() as stdout:
+ module = machinery.FrozenImporter.load_module('__hello__')
+ check = {'__name__': '__hello__', '__file__': '<frozen>',
+ '__package__': '', '__loader__': machinery.FrozenImporter}
+ for attr, value in check.items():
+ self.assertEqual(getattr(module, attr), value)
+ self.assertEqual(stdout.getvalue(), 'Hello world!\n')
+
+ def test_package(self):
+ with util.uncache('__phello__'), captured_stdout() as stdout:
+ module = machinery.FrozenImporter.load_module('__phello__')
+ check = {'__name__': '__phello__', '__file__': '<frozen>',
+ '__package__': '__phello__', '__path__': ['__phello__'],
+ '__loader__': machinery.FrozenImporter}
+ for attr, value in check.items():
+ attr_value = getattr(module, attr)
+ self.assertEqual(attr_value, value,
+ "for __phello__.%s, %r != %r" %
+ (attr, attr_value, value))
+ self.assertEqual(stdout.getvalue(), 'Hello world!\n')
+
+ def test_lacking_parent(self):
+ with util.uncache('__phello__', '__phello__.spam'), \
+ captured_stdout() as stdout:
+ module = machinery.FrozenImporter.load_module('__phello__.spam')
+ check = {'__name__': '__phello__.spam', '__file__': '<frozen>',
+ '__package__': '__phello__',
+ '__loader__': machinery.FrozenImporter}
+ for attr, value in check.items():
+ attr_value = getattr(module, attr)
+ self.assertEqual(attr_value, value,
+ "for __phello__.spam.%s, %r != %r" %
+ (attr, attr_value, value))
+ self.assertEqual(stdout.getvalue(), 'Hello world!\n')
+
+ def test_module_reuse(self):
+ with util.uncache('__hello__'), captured_stdout() as stdout:
+ module1 = machinery.FrozenImporter.load_module('__hello__')
+ module2 = machinery.FrozenImporter.load_module('__hello__')
+ self.assertTrue(module1 is module2)
+ self.assertEqual(stdout.getvalue(),
+ 'Hello world!\nHello world!\n')
+
+ def test_state_after_failure(self):
+ # No way to trigger an error in a frozen module.
+ pass
+
+ def test_unloadable(self):
+ assert machinery.FrozenImporter.find_module('_not_real') is None
+ with self.assertRaises(ImportError):
+ machinery.FrozenImporter.load_module('_not_real')
+
+
+class InspectLoaderTests(unittest.TestCase):
+
+ """Tests for the InspectLoader methods for FrozenImporter."""
+
+ def test_get_code(self):
+ # Make sure that the code object is good.
+ name = '__hello__'
+ with captured_stdout() as stdout:
+ code = machinery.FrozenImporter.get_code(name)
+ mod = imp.new_module(name)
+ exec(code, mod.__dict__)
+ self.assertTrue(hasattr(mod, 'initialized'))
+ self.assertEqual(stdout.getvalue(), 'Hello world!\n')
+
+ def test_get_source(self):
+ # Should always return None.
+ result = machinery.FrozenImporter.get_source('__hello__')
+ self.assertTrue(result is None)
+
+ def test_is_package(self):
+ # Should be able to tell what is a package.
+ test_for = (('__hello__', False), ('__phello__', True),
+ ('__phello__.spam', False))
+ for name, is_package in test_for:
+ result = machinery.FrozenImporter.is_package(name)
+ self.assertTrue(bool(result) == is_package)
+
+ def test_failure(self):
+ # Raise ImportError for modules that are not frozen.
+ for meth_name in ('get_code', 'get_source', 'is_package'):
+ method = getattr(machinery.FrozenImporter, meth_name)
+ with self.assertRaises(ImportError):
+ method('importlib')
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(LoaderTests, InspectLoaderTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/import_/__init__.py b/Lib/importlib/test/import_/__init__.py
new file mode 100644
index 0000000..fdf7661
--- /dev/null
+++ b/Lib/importlib/test/import_/__init__.py
@@ -0,0 +1,13 @@
+import importlib.test
+import os.path
+import unittest
+
+
+def test_suite():
+ directory = os.path.dirname(__file__)
+ return importlib.test.test_suite('importlib.test.import_', directory)
+
+
+if __name__ == '__main__':
+ from test.support import run_unittest
+ run_unittest(test_suite())
diff --git a/Lib/importlib/test/import_/test___package__.py b/Lib/importlib/test/import_/test___package__.py
new file mode 100644
index 0000000..5056ae5
--- /dev/null
+++ b/Lib/importlib/test/import_/test___package__.py
@@ -0,0 +1,119 @@
+"""PEP 366 ("Main module explicit relative imports") specifies the
+semantics for the __package__ attribute on modules. This attribute is
+used, when available, to detect which package a module belongs to (instead
+of using the typical __path__/__name__ test).
+
+"""
+import unittest
+from .. import util
+from . import util as import_util
+
+
+class Using__package__(unittest.TestCase):
+
+ """Use of __package__ supercedes the use of __name__/__path__ to calculate
+ what package a module belongs to. The basic algorithm is [__package__]::
+
+ def resolve_name(name, package, level):
+ level -= 1
+ base = package.rsplit('.', level)[0]
+ return '{0}.{1}'.format(base, name)
+
+ But since there is no guarantee that __package__ has been set (or not been
+ set to None [None]), there has to be a way to calculate the attribute's value
+ [__name__]::
+
+ def calc_package(caller_name, has___path__):
+ if has__path__:
+ return caller_name
+ else:
+ return caller_name.rsplit('.', 1)[0]
+
+ Then the normal algorithm for relative name imports can proceed as if
+ __package__ had been set.
+
+ """
+
+ def test_using___package__(self):
+ # [__package__]
+ with util.mock_modules('pkg.__init__', 'pkg.fake') as importer:
+ with util.import_state(meta_path=[importer]):
+ import_util.import_('pkg.fake')
+ module = import_util.import_('',
+ globals={'__package__': 'pkg.fake'},
+ fromlist=['attr'], level=2)
+ self.assertEqual(module.__name__, 'pkg')
+
+ def test_using___name__(self, package_as_None=False):
+ # [__name__]
+ globals_ = {'__name__': 'pkg.fake', '__path__': []}
+ if package_as_None:
+ globals_['__package__'] = None
+ with util.mock_modules('pkg.__init__', 'pkg.fake') as importer:
+ with util.import_state(meta_path=[importer]):
+ import_util.import_('pkg.fake')
+ module = import_util.import_('', globals= globals_,
+ fromlist=['attr'], level=2)
+ self.assertEqual(module.__name__, 'pkg')
+
+ def test_None_as___package__(self):
+ # [None]
+ self.test_using___name__(package_as_None=True)
+
+ def test_bad__package__(self):
+ globals = {'__package__': '<not real>'}
+ with self.assertRaises(SystemError):
+ import_util.import_('', globals, {}, ['relimport'], 1)
+
+ def test_bunk__package__(self):
+ globals = {'__package__': 42}
+ with self.assertRaises(ValueError):
+ import_util.import_('', globals, {}, ['relimport'], 1)
+
+
+@import_util.importlib_only
+class Setting__package__(unittest.TestCase):
+
+ """Because __package__ is a new feature, it is not always set by a loader.
+ Import will set it as needed to help with the transition to relying on
+ __package__.
+
+ For a top-level module, __package__ is set to None [top-level]. For a
+ package __name__ is used for __package__ [package]. For submodules the
+ value is __name__.rsplit('.', 1)[0] [submodule].
+
+ """
+
+ # [top-level]
+ def test_top_level(self):
+ with util.mock_modules('top_level') as mock:
+ with util.import_state(meta_path=[mock]):
+ del mock['top_level'].__package__
+ module = import_util.import_('top_level')
+ self.assertEqual(module.__package__, '')
+
+ # [package]
+ def test_package(self):
+ with util.mock_modules('pkg.__init__') as mock:
+ with util.import_state(meta_path=[mock]):
+ del mock['pkg'].__package__
+ module = import_util.import_('pkg')
+ self.assertEqual(module.__package__, 'pkg')
+
+ # [submodule]
+ def test_submodule(self):
+ with util.mock_modules('pkg.__init__', 'pkg.mod') as mock:
+ with util.import_state(meta_path=[mock]):
+ del mock['pkg.mod'].__package__
+ pkg = import_util.import_('pkg.mod')
+ module = getattr(pkg, 'mod')
+ self.assertEqual(module.__package__, 'pkg')
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(Using__package__, Setting__package__)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/import_/test_api.py b/Lib/importlib/test/import_/test_api.py
new file mode 100644
index 0000000..9075d42
--- /dev/null
+++ b/Lib/importlib/test/import_/test_api.py
@@ -0,0 +1,22 @@
+from . import util
+import unittest
+
+
+class APITest(unittest.TestCase):
+
+ """Test API-specific details for __import__ (e.g. raising the right
+ exception when passing in an int for the module name)."""
+
+ def test_name_requires_rparition(self):
+ # Raise TypeError if a non-string is passed in for the module name.
+ with self.assertRaises(TypeError):
+ util.import_(42)
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(APITest)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/import_/test_caching.py b/Lib/importlib/test/import_/test_caching.py
new file mode 100644
index 0000000..48dc643
--- /dev/null
+++ b/Lib/importlib/test/import_/test_caching.py
@@ -0,0 +1,86 @@
+"""Test that sys.modules is used properly by import."""
+from .. import util
+from . import util as import_util
+import sys
+from types import MethodType
+import unittest
+
+
+class UseCache(unittest.TestCase):
+
+ """When it comes to sys.modules, import prefers it over anything else.
+
+ Once a name has been resolved, sys.modules is checked to see if it contains
+ the module desired. If so, then it is returned [use cache]. If it is not
+ found, then the proper steps are taken to perform the import, but
+ sys.modules is still used to return the imported module (e.g., not what a
+ loader returns) [from cache on return]. This also applies to imports of
+ things contained within a package and thus get assigned as an attribute
+ [from cache to attribute] or pulled in thanks to a fromlist import
+ [from cache for fromlist]. But if sys.modules contains None then
+ ImportError is raised [None in cache].
+
+ """
+ def test_using_cache(self):
+ # [use cache]
+ module_to_use = "some module found!"
+ with util.uncache(module_to_use):
+ sys.modules['some_module'] = module_to_use
+ module = import_util.import_('some_module')
+ self.assertEqual(id(module_to_use), id(module))
+
+ def test_None_in_cache(self):
+ #[None in cache]
+ name = 'using_None'
+ with util.uncache(name):
+ sys.modules[name] = None
+ with self.assertRaises(ImportError):
+ import_util.import_(name)
+
+ def create_mock(self, *names, return_=None):
+ mock = util.mock_modules(*names)
+ original_load = mock.load_module
+ def load_module(self, fullname):
+ original_load(fullname)
+ return return_
+ mock.load_module = MethodType(load_module, mock)
+ return mock
+
+ # __import__ inconsistent between loaders and built-in import when it comes
+ # to when to use the module in sys.modules and when not to.
+ @import_util.importlib_only
+ def test_using_cache_after_loader(self):
+ # [from cache on return]
+ with self.create_mock('module') as mock:
+ with util.import_state(meta_path=[mock]):
+ module = import_util.import_('module')
+ self.assertEqual(id(module), id(sys.modules['module']))
+
+ # See test_using_cache_after_loader() for reasoning.
+ @import_util.importlib_only
+ def test_using_cache_for_assigning_to_attribute(self):
+ # [from cache to attribute]
+ with self.create_mock('pkg.__init__', 'pkg.module') as importer:
+ with util.import_state(meta_path=[importer]):
+ module = import_util.import_('pkg.module')
+ self.assertTrue(hasattr(module, 'module'))
+ self.assertTrue(id(module.module), id(sys.modules['pkg.module']))
+
+ # See test_using_cache_after_loader() for reasoning.
+ @import_util.importlib_only
+ def test_using_cache_for_fromlist(self):
+ # [from cache for fromlist]
+ with self.create_mock('pkg.__init__', 'pkg.module') as importer:
+ with util.import_state(meta_path=[importer]):
+ module = import_util.import_('pkg', fromlist=['module'])
+ self.assertTrue(hasattr(module, 'module'))
+ self.assertEqual(id(module.module),
+ id(sys.modules['pkg.module']))
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(UseCache)
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/import_/test_fromlist.py b/Lib/importlib/test/import_/test_fromlist.py
new file mode 100644
index 0000000..7ecde03
--- /dev/null
+++ b/Lib/importlib/test/import_/test_fromlist.py
@@ -0,0 +1,123 @@
+"""Test that the semantics relating to the 'fromlist' argument are correct."""
+from .. import util
+from . import util as import_util
+import unittest
+
+class ReturnValue(unittest.TestCase):
+
+ """The use of fromlist influences what import returns.
+
+ If direct ``import ...`` statement is used, the root module or package is
+ returned [import return]. But if fromlist is set, then the specified module
+ is actually returned (whether it is a relative import or not)
+ [from return].
+
+ """
+
+ def test_return_from_import(self):
+ # [import return]
+ with util.mock_modules('pkg.__init__', 'pkg.module') as importer:
+ with util.import_state(meta_path=[importer]):
+ module = import_util.import_('pkg.module')
+ self.assertEqual(module.__name__, 'pkg')
+
+ def test_return_from_from_import(self):
+ # [from return]
+ with util.mock_modules('pkg.__init__', 'pkg.module')as importer:
+ with util.import_state(meta_path=[importer]):
+ module = import_util.import_('pkg.module', fromlist=['attr'])
+ self.assertEqual(module.__name__, 'pkg.module')
+
+
+class HandlingFromlist(unittest.TestCase):
+
+ """Using fromlist triggers different actions based on what is being asked
+ of it.
+
+ If fromlist specifies an object on a module, nothing special happens
+ [object case]. This is even true if the object does not exist [bad object].
+
+ If a package is being imported, then what is listed in fromlist may be
+ treated as a module to be imported [module]. But once again, even if
+ something in fromlist does not exist as a module, no error is raised
+ [no module]. And this extends to what is contained in __all__ when '*' is
+ imported [using *]. And '*' does not need to be the only name in the
+ fromlist [using * with others].
+
+ """
+
+ def test_object(self):
+ # [object case]
+ with util.mock_modules('module') as importer:
+ with util.import_state(meta_path=[importer]):
+ module = import_util.import_('module', fromlist=['attr'])
+ self.assertEqual(module.__name__, 'module')
+
+ def test_unexistent_object(self):
+ # [bad object]
+ with util.mock_modules('module') as importer:
+ with util.import_state(meta_path=[importer]):
+ module = import_util.import_('module', fromlist=['non_existent'])
+ self.assertEqual(module.__name__, 'module')
+ self.assertTrue(not hasattr(module, 'non_existent'))
+
+ def test_module_from_package(self):
+ # [module]
+ with util.mock_modules('pkg.__init__', 'pkg.module') as importer:
+ with util.import_state(meta_path=[importer]):
+ module = import_util.import_('pkg', fromlist=['module'])
+ self.assertEqual(module.__name__, 'pkg')
+ self.assertTrue(hasattr(module, 'module'))
+ self.assertEqual(module.module.__name__, 'pkg.module')
+
+ def test_no_module_from_package(self):
+ # [no module]
+ with util.mock_modules('pkg.__init__') as importer:
+ with util.import_state(meta_path=[importer]):
+ module = import_util.import_('pkg', fromlist='non_existent')
+ self.assertEqual(module.__name__, 'pkg')
+ self.assertTrue(not hasattr(module, 'non_existent'))
+
+ def test_empty_string(self):
+ with util.mock_modules('pkg.__init__', 'pkg.mod') as importer:
+ with util.import_state(meta_path=[importer]):
+ module = import_util.import_('pkg.mod', fromlist=[''])
+ self.assertEqual(module.__name__, 'pkg.mod')
+
+ def basic_star_test(self, fromlist=['*']):
+ # [using *]
+ with util.mock_modules('pkg.__init__', 'pkg.module') as mock:
+ with util.import_state(meta_path=[mock]):
+ mock['pkg'].__all__ = ['module']
+ module = import_util.import_('pkg', fromlist=fromlist)
+ self.assertEqual(module.__name__, 'pkg')
+ self.assertTrue(hasattr(module, 'module'))
+ self.assertEqual(module.module.__name__, 'pkg.module')
+
+ def test_using_star(self):
+ # [using *]
+ self.basic_star_test()
+
+ def test_fromlist_as_tuple(self):
+ self.basic_star_test(('*',))
+
+ def test_star_with_others(self):
+ # [using * with others]
+ context = util.mock_modules('pkg.__init__', 'pkg.module1', 'pkg.module2')
+ with context as mock:
+ with util.import_state(meta_path=[mock]):
+ mock['pkg'].__all__ = ['module1']
+ module = import_util.import_('pkg', fromlist=['module2', '*'])
+ self.assertEqual(module.__name__, 'pkg')
+ self.assertTrue(hasattr(module, 'module1'))
+ self.assertTrue(hasattr(module, 'module2'))
+ self.assertEqual(module.module1.__name__, 'pkg.module1')
+ self.assertEqual(module.module2.__name__, 'pkg.module2')
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(ReturnValue, HandlingFromlist)
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/import_/test_meta_path.py b/Lib/importlib/test/import_/test_meta_path.py
new file mode 100644
index 0000000..3b130c9
--- /dev/null
+++ b/Lib/importlib/test/import_/test_meta_path.py
@@ -0,0 +1,97 @@
+from .. import util
+from . import util as import_util
+from types import MethodType
+import unittest
+
+
+class CallingOrder(unittest.TestCase):
+
+ """Calls to the importers on sys.meta_path happen in order that they are
+ specified in the sequence, starting with the first importer
+ [first called], and then continuing on down until one is found that doesn't
+ return None [continuing]."""
+
+
+ def test_first_called(self):
+ # [first called]
+ mod = 'top_level'
+ first = util.mock_modules(mod)
+ second = util.mock_modules(mod)
+ with util.mock_modules(mod) as first, util.mock_modules(mod) as second:
+ first.modules[mod] = 42
+ second.modules[mod] = -13
+ with util.import_state(meta_path=[first, second]):
+ self.assertEqual(import_util.import_(mod), 42)
+
+ def test_continuing(self):
+ # [continuing]
+ mod_name = 'for_real'
+ with util.mock_modules('nonexistent') as first, \
+ util.mock_modules(mod_name) as second:
+ first.find_module = lambda self, fullname, path=None: None
+ second.modules[mod_name] = 42
+ with util.import_state(meta_path=[first, second]):
+ self.assertEqual(import_util.import_(mod_name), 42)
+
+
+class CallSignature(unittest.TestCase):
+
+ """If there is no __path__ entry on the parent module, then 'path' is None
+ [no path]. Otherwise, the value for __path__ is passed in for the 'path'
+ argument [path set]."""
+
+ def log(self, fxn):
+ log = []
+ def wrapper(self, *args, **kwargs):
+ log.append([args, kwargs])
+ return fxn(*args, **kwargs)
+ return log, wrapper
+
+
+ def test_no_path(self):
+ # [no path]
+ mod_name = 'top_level'
+ assert '.' not in mod_name
+ with util.mock_modules(mod_name) as importer:
+ log, wrapped_call = self.log(importer.find_module)
+ importer.find_module = MethodType(wrapped_call, importer)
+ with util.import_state(meta_path=[importer]):
+ import_util.import_(mod_name)
+ assert len(log) == 1
+ args = log[0][0]
+ kwargs = log[0][1]
+ # Assuming all arguments are positional.
+ self.assertEqual(len(args), 2)
+ self.assertEqual(len(kwargs), 0)
+ self.assertEqual(args[0], mod_name)
+ self.assertTrue(args[1] is None)
+
+ def test_with_path(self):
+ # [path set]
+ pkg_name = 'pkg'
+ mod_name = pkg_name + '.module'
+ path = [42]
+ assert '.' in mod_name
+ with util.mock_modules(pkg_name+'.__init__', mod_name) as importer:
+ importer.modules[pkg_name].__path__ = path
+ log, wrapped_call = self.log(importer.find_module)
+ importer.find_module = MethodType(wrapped_call, importer)
+ with util.import_state(meta_path=[importer]):
+ import_util.import_(mod_name)
+ assert len(log) == 2
+ args = log[1][0]
+ kwargs = log[1][1]
+ # Assuming all arguments are positional.
+ self.assertTrue(not kwargs)
+ self.assertEqual(args[0], mod_name)
+ self.assertTrue(args[1] is path)
+
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(CallingOrder, CallSignature)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/import_/test_packages.py b/Lib/importlib/test/import_/test_packages.py
new file mode 100644
index 0000000..faadc32
--- /dev/null
+++ b/Lib/importlib/test/import_/test_packages.py
@@ -0,0 +1,37 @@
+from .. import util
+from . import util as import_util
+import sys
+import unittest
+import importlib
+
+
+class ParentModuleTests(unittest.TestCase):
+
+ """Importing a submodule should import the parent modules."""
+
+ def test_import_parent(self):
+ with util.mock_modules('pkg.__init__', 'pkg.module') as mock:
+ with util.import_state(meta_path=[mock]):
+ module = import_util.import_('pkg.module')
+ self.assertTrue('pkg' in sys.modules)
+
+ def test_bad_parent(self):
+ with util.mock_modules('pkg.module') as mock:
+ with util.import_state(meta_path=[mock]):
+ with self.assertRaises(ImportError):
+ import_util.import_('pkg.module')
+
+ def test_module_not_package(self):
+ # Try to import a submodule from a non-package should raise ImportError.
+ assert not hasattr(sys, '__path__')
+ with self.assertRaises(ImportError):
+ import_util.import_('sys.no_submodules_here')
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(ParentModuleTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/import_/test_path.py b/Lib/importlib/test/import_/test_path.py
new file mode 100644
index 0000000..2faa231
--- /dev/null
+++ b/Lib/importlib/test/import_/test_path.py
@@ -0,0 +1,131 @@
+from importlib import _bootstrap
+from importlib import machinery
+from .. import util
+from . import util as import_util
+import imp
+import os
+import sys
+import tempfile
+from test import support
+from types import MethodType
+import unittest
+
+
+class FinderTests(unittest.TestCase):
+
+ """Tests for PathFinder."""
+
+ def test_failure(self):
+ # Test None returned upon not finding a suitable finder.
+ module = '<test module>'
+ with util.import_state():
+ self.assertTrue(machinery.PathFinder.find_module(module) is None)
+
+ def test_sys_path(self):
+ # Test that sys.path is used when 'path' is None.
+ # Implicitly tests that sys.path_importer_cache is used.
+ module = '<test module>'
+ path = '<test path>'
+ importer = util.mock_modules(module)
+ with util.import_state(path_importer_cache={path: importer},
+ path=[path]):
+ loader = machinery.PathFinder.find_module(module)
+ self.assertTrue(loader is importer)
+
+ def test_path(self):
+ # Test that 'path' is used when set.
+ # Implicitly tests that sys.path_importer_cache is used.
+ module = '<test module>'
+ path = '<test path>'
+ importer = util.mock_modules(module)
+ with util.import_state(path_importer_cache={path: importer}):
+ loader = machinery.PathFinder.find_module(module, [path])
+ self.assertTrue(loader is importer)
+
+ def test_path_hooks(self):
+ # Test that sys.path_hooks is used.
+ # Test that sys.path_importer_cache is set.
+ module = '<test module>'
+ path = '<test path>'
+ importer = util.mock_modules(module)
+ hook = import_util.mock_path_hook(path, importer=importer)
+ with util.import_state(path_hooks=[hook]):
+ loader = machinery.PathFinder.find_module(module, [path])
+ self.assertTrue(loader is importer)
+ self.assertTrue(path in sys.path_importer_cache)
+ self.assertTrue(sys.path_importer_cache[path] is importer)
+
+ def test_path_importer_cache_has_None(self):
+ # Test that if sys.path_importer_cache has None that None is returned.
+ clear_cache = {path: None for path in sys.path}
+ with util.import_state(path_importer_cache=clear_cache):
+ for name in ('asynchat', 'sys', '<test module>'):
+ self.assertTrue(machinery.PathFinder.find_module(name) is None)
+
+ def test_path_importer_cache_has_None_continues(self):
+ # Test that having None in sys.path_importer_cache causes the search to
+ # continue.
+ path = '<test path>'
+ module = '<test module>'
+ importer = util.mock_modules(module)
+ with util.import_state(path=['1', '2'],
+ path_importer_cache={'1': None, '2': importer}):
+ loader = machinery.PathFinder.find_module(module)
+ self.assertTrue(loader is importer)
+
+
+
+class DefaultPathFinderTests(unittest.TestCase):
+
+ """Test importlib._bootstrap._DefaultPathFinder."""
+
+ def test_implicit_hooks(self):
+ # Test that the implicit path hooks are used.
+ bad_path = '<path>'
+ module = '<module>'
+ assert not os.path.exists(bad_path)
+ existing_path = tempfile.mkdtemp()
+ try:
+ with util.import_state():
+ nothing = _bootstrap._DefaultPathFinder.find_module(module,
+ path=[existing_path])
+ self.assertTrue(nothing is None)
+ self.assertTrue(existing_path in sys.path_importer_cache)
+ result = isinstance(sys.path_importer_cache[existing_path],
+ imp.NullImporter)
+ self.assertFalse(result)
+ nothing = _bootstrap._DefaultPathFinder.find_module(module,
+ path=[bad_path])
+ self.assertTrue(nothing is None)
+ self.assertTrue(bad_path in sys.path_importer_cache)
+ self.assertTrue(isinstance(sys.path_importer_cache[bad_path],
+ imp.NullImporter))
+ finally:
+ os.rmdir(existing_path)
+
+
+ def test_path_importer_cache_has_None(self):
+ # Test that the default hook is used when sys.path_importer_cache
+ # contains None for a path.
+ module = '<test module>'
+ importer = util.mock_modules(module)
+ path = '<test path>'
+ # XXX Not blackbox.
+ original_hook = _bootstrap._DEFAULT_PATH_HOOK
+ mock_hook = import_util.mock_path_hook(path, importer=importer)
+ _bootstrap._DEFAULT_PATH_HOOK = mock_hook
+ try:
+ with util.import_state(path_importer_cache={path: None}):
+ loader = _bootstrap._DefaultPathFinder.find_module(module,
+ path=[path])
+ self.assertTrue(loader is importer)
+ finally:
+ _bootstrap._DEFAULT_PATH_HOOK = original_hook
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(FinderTests, DefaultPathFinderTests)
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/import_/test_relative_imports.py b/Lib/importlib/test/import_/test_relative_imports.py
new file mode 100644
index 0000000..a0f6b2d
--- /dev/null
+++ b/Lib/importlib/test/import_/test_relative_imports.py
@@ -0,0 +1,203 @@
+"""Test relative imports (PEP 328)."""
+from .. import util
+from . import util as import_util
+import sys
+import unittest
+
+class RelativeImports(unittest.TestCase):
+
+ """PEP 328 introduced relative imports. This allows for imports to occur
+ from within a package without having to specify the actual package name.
+
+ A simple example is to import another module within the same package
+ [module from module]::
+
+ # From pkg.mod1 with pkg.mod2 being a module.
+ from . import mod2
+
+ This also works for getting an attribute from a module that is specified
+ in a relative fashion [attr from module]::
+
+ # From pkg.mod1.
+ from .mod2 import attr
+
+ But this is in no way restricted to working between modules; it works
+ from [package to module],::
+
+ # From pkg, importing pkg.module which is a module.
+ from . import module
+
+ [module to package],::
+
+ # Pull attr from pkg, called from pkg.module which is a module.
+ from . import attr
+
+ and [package to package]::
+
+ # From pkg.subpkg1 (both pkg.subpkg[1,2] are packages).
+ from .. import subpkg2
+
+ The number of dots used is in no way restricted [deep import]::
+
+ # Import pkg.attr from pkg.pkg1.pkg2.pkg3.pkg4.pkg5.
+ from ...... import attr
+
+ To prevent someone from accessing code that is outside of a package, one
+ cannot reach the location containing the root package itself::
+
+ # From pkg.__init__ [too high from package]
+ from .. import top_level
+
+ # From pkg.module [too high from module]
+ from .. import top_level
+
+ Relative imports are the only type of import that allow for an empty
+ module name for an import [empty name].
+
+ """
+
+ def relative_import_test(self, create, globals_, callback):
+ """Abstract out boilerplace for setting up for an import test."""
+ uncache_names = []
+ for name in create:
+ if not name.endswith('.__init__'):
+ uncache_names.append(name)
+ else:
+ uncache_names.append(name[:-len('.__init__')])
+ with util.mock_modules(*create) as importer:
+ with util.import_state(meta_path=[importer]):
+ for global_ in globals_:
+ with util.uncache(*uncache_names):
+ callback(global_)
+
+
+ def test_module_from_module(self):
+ # [module from module]
+ create = 'pkg.__init__', 'pkg.mod2'
+ globals_ = {'__package__': 'pkg'}, {'__name__': 'pkg.mod1'}
+ def callback(global_):
+ import_util.import_('pkg') # For __import__().
+ module = import_util.import_('', global_, fromlist=['mod2'], level=1)
+ self.assertEqual(module.__name__, 'pkg')
+ self.assertTrue(hasattr(module, 'mod2'))
+ self.assertEqual(module.mod2.attr, 'pkg.mod2')
+ self.relative_import_test(create, globals_, callback)
+
+ def test_attr_from_module(self):
+ # [attr from module]
+ create = 'pkg.__init__', 'pkg.mod2'
+ globals_ = {'__package__': 'pkg'}, {'__name__': 'pkg.mod1'}
+ def callback(global_):
+ import_util.import_('pkg') # For __import__().
+ module = import_util.import_('mod2', global_, fromlist=['attr'],
+ level=1)
+ self.assertEqual(module.__name__, 'pkg.mod2')
+ self.assertEqual(module.attr, 'pkg.mod2')
+ self.relative_import_test(create, globals_, callback)
+
+ def test_package_to_module(self):
+ # [package to module]
+ create = 'pkg.__init__', 'pkg.module'
+ globals_ = ({'__package__': 'pkg'},
+ {'__name__': 'pkg', '__path__': ['blah']})
+ def callback(global_):
+ import_util.import_('pkg') # For __import__().
+ module = import_util.import_('', global_, fromlist=['module'],
+ level=1)
+ self.assertEqual(module.__name__, 'pkg')
+ self.assertTrue(hasattr(module, 'module'))
+ self.assertEqual(module.module.attr, 'pkg.module')
+ self.relative_import_test(create, globals_, callback)
+
+ def test_module_to_package(self):
+ # [module to package]
+ create = 'pkg.__init__', 'pkg.module'
+ globals_ = {'__package__': 'pkg'}, {'__name__': 'pkg.module'}
+ def callback(global_):
+ import_util.import_('pkg') # For __import__().
+ module = import_util.import_('', global_, fromlist=['attr'], level=1)
+ self.assertEqual(module.__name__, 'pkg')
+ self.relative_import_test(create, globals_, callback)
+
+ def test_package_to_package(self):
+ # [package to package]
+ create = ('pkg.__init__', 'pkg.subpkg1.__init__',
+ 'pkg.subpkg2.__init__')
+ globals_ = ({'__package__': 'pkg.subpkg1'},
+ {'__name__': 'pkg.subpkg1', '__path__': ['blah']})
+ def callback(global_):
+ module = import_util.import_('', global_, fromlist=['subpkg2'],
+ level=2)
+ self.assertEqual(module.__name__, 'pkg')
+ self.assertTrue(hasattr(module, 'subpkg2'))
+ self.assertEqual(module.subpkg2.attr, 'pkg.subpkg2.__init__')
+
+ def test_deep_import(self):
+ # [deep import]
+ create = ['pkg.__init__']
+ for count in range(1,6):
+ create.append('{0}.pkg{1}.__init__'.format(
+ create[-1][:-len('.__init__')], count))
+ globals_ = ({'__package__': 'pkg.pkg1.pkg2.pkg3.pkg4.pkg5'},
+ {'__name__': 'pkg.pkg1.pkg2.pkg3.pkg4.pkg5',
+ '__path__': ['blah']})
+ def callback(global_):
+ import_util.import_(globals_[0]['__package__'])
+ module = import_util.import_('', global_, fromlist=['attr'], level=6)
+ self.assertEqual(module.__name__, 'pkg')
+ self.relative_import_test(create, globals_, callback)
+
+ def test_too_high_from_package(self):
+ # [too high from package]
+ create = ['top_level', 'pkg.__init__']
+ globals_ = ({'__package__': 'pkg'},
+ {'__name__': 'pkg', '__path__': ['blah']})
+ def callback(global_):
+ import_util.import_('pkg')
+ with self.assertRaises(ValueError):
+ import_util.import_('', global_, fromlist=['top_level'],
+ level=2)
+ self.relative_import_test(create, globals_, callback)
+
+ def test_too_high_from_module(self):
+ # [too high from module]
+ create = ['top_level', 'pkg.__init__', 'pkg.module']
+ globals_ = {'__package__': 'pkg'}, {'__name__': 'pkg.module'}
+ def callback(global_):
+ import_util.import_('pkg')
+ with self.assertRaises(ValueError):
+ import_util.import_('', global_, fromlist=['top_level'],
+ level=2)
+ self.relative_import_test(create, globals_, callback)
+
+ def test_empty_name_w_level_0(self):
+ # [empty name]
+ with self.assertRaises(ValueError):
+ import_util.import_('')
+
+ def test_import_from_different_package(self):
+ # Test importing from a different package than the caller.
+ # in pkg.subpkg1.mod
+ # from ..subpkg2 import mod
+ create = ['__runpy_pkg__.__init__',
+ '__runpy_pkg__.__runpy_pkg__.__init__',
+ '__runpy_pkg__.uncle.__init__',
+ '__runpy_pkg__.uncle.cousin.__init__',
+ '__runpy_pkg__.uncle.cousin.nephew']
+ globals_ = {'__package__': '__runpy_pkg__.__runpy_pkg__'}
+ def callback(global_):
+ import_util.import_('__runpy_pkg__.__runpy_pkg__')
+ module = import_util.import_('uncle.cousin', globals_, {},
+ fromlist=['nephew'],
+ level=2)
+ self.assertEqual(module.__name__, '__runpy_pkg__.uncle.cousin')
+ self.relative_import_test(create, globals_, callback)
+
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(RelativeImports)
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/import_/util.py b/Lib/importlib/test/import_/util.py
new file mode 100644
index 0000000..649c5ed
--- /dev/null
+++ b/Lib/importlib/test/import_/util.py
@@ -0,0 +1,29 @@
+import functools
+import importlib
+import importlib._bootstrap
+import unittest
+
+
+using___import__ = False
+
+
+def import_(*args, **kwargs):
+ """Delegate to allow for injecting different implementations of import."""
+ if using___import__:
+ return __import__(*args, **kwargs)
+ else:
+ return importlib.__import__(*args, **kwargs)
+
+
+def importlib_only(fxn):
+ """Decorator to skip a test if using __builtins__.__import__."""
+ return unittest.skipIf(using___import__, "importlib-specific test")(fxn)
+
+
+def mock_path_hook(*entries, importer):
+ """A mock sys.path_hooks entry."""
+ def hook(entry):
+ if entry not in entries:
+ raise ImportError
+ return importer
+ return hook
diff --git a/Lib/importlib/test/regrtest.py b/Lib/importlib/test/regrtest.py
new file mode 100644
index 0000000..b103ae7d
--- /dev/null
+++ b/Lib/importlib/test/regrtest.py
@@ -0,0 +1,35 @@
+"""Run Python's standard test suite using importlib.__import__.
+
+Tests known to fail because of assumptions that importlib (properly)
+invalidates are automatically skipped if the entire test suite is run.
+Otherwise all command-line options valid for test.regrtest are also valid for
+this script.
+
+XXX FAILING
+ * test_import
+ - test_incorrect_code_name
+ file name differing between __file__ and co_filename (r68360 on trunk)
+ - test_import_by_filename
+ exception for trying to import by file name does not match
+
+"""
+import importlib
+import sys
+from test import regrtest
+
+if __name__ == '__main__':
+ __builtins__.__import__ = importlib.__import__
+
+ exclude = ['--exclude',
+ 'test_frozen', # Does not expect __loader__ attribute
+ 'test_pkg', # Does not expect __loader__ attribute
+ 'test_pydoc', # Does not expect __loader__ attribute
+ ]
+
+ # Switching on --exclude implies running all test but the ones listed, so
+ # only use it when one is not running an explicit test
+ if len(sys.argv) == 1:
+ # No programmatic way to specify tests to exclude
+ sys.argv.extend(exclude)
+
+ regrtest.main(quiet=True, verbose2=True)
diff --git a/Lib/importlib/test/source/__init__.py b/Lib/importlib/test/source/__init__.py
new file mode 100644
index 0000000..8d7c49d
--- /dev/null
+++ b/Lib/importlib/test/source/__init__.py
@@ -0,0 +1,13 @@
+import importlib.test
+import os.path
+import unittest
+
+
+def test_suite():
+ directory = os.path.dirname(__file__)
+ return importlib.test.test_suite('importlib.test.source', directory)
+
+
+if __name__ == '__main__':
+ from test.support import run_unittest
+ run_unittest(test_suite())
diff --git a/Lib/importlib/test/source/test_abc_loader.py b/Lib/importlib/test/source/test_abc_loader.py
new file mode 100644
index 0000000..3245907
--- /dev/null
+++ b/Lib/importlib/test/source/test_abc_loader.py
@@ -0,0 +1,876 @@
+import importlib
+from importlib import abc
+
+from .. import abc as testing_abc
+from .. import util
+from . import util as source_util
+
+import imp
+import inspect
+import io
+import marshal
+import os
+import sys
+import types
+import unittest
+import warnings
+
+
+class SourceOnlyLoaderMock(abc.SourceLoader):
+
+ # Globals that should be defined for all modules.
+ source = (b"_ = '::'.join([__name__, __file__, __cached__, __package__, "
+ b"repr(__loader__)])")
+
+ def __init__(self, path):
+ self.path = path
+
+ def get_data(self, path):
+ assert self.path == path
+ return self.source
+
+ def get_filename(self, fullname):
+ return self.path
+
+
+class SourceLoaderMock(SourceOnlyLoaderMock):
+
+ source_mtime = 1
+
+ def __init__(self, path, magic=imp.get_magic()):
+ super().__init__(path)
+ self.bytecode_path = imp.cache_from_source(self.path)
+ data = bytearray(magic)
+ data.extend(marshal._w_long(self.source_mtime))
+ code_object = compile(self.source, self.path, 'exec',
+ dont_inherit=True)
+ data.extend(marshal.dumps(code_object))
+ self.bytecode = bytes(data)
+ self.written = {}
+
+ def get_data(self, path):
+ if path == self.path:
+ return super().get_data(path)
+ elif path == self.bytecode_path:
+ return self.bytecode
+ else:
+ raise IOError
+
+ def path_mtime(self, path):
+ assert path == self.path
+ return self.source_mtime
+
+ def set_data(self, path, data):
+ self.written[path] = bytes(data)
+ return path == self.bytecode_path
+
+
+class PyLoaderMock(abc.PyLoader):
+
+ # Globals that should be defined for all modules.
+ source = (b"_ = '::'.join([__name__, __file__, __package__, "
+ b"repr(__loader__)])")
+
+ def __init__(self, data):
+ """Take a dict of 'module_name: path' pairings.
+
+ Paths should have no file extension, allowing packages to be denoted by
+ ending in '__init__'.
+
+ """
+ self.module_paths = data
+ self.path_to_module = {val:key for key,val in data.items()}
+
+ def get_data(self, path):
+ if path not in self.path_to_module:
+ raise IOError
+ return self.source
+
+ def is_package(self, name):
+ filename = os.path.basename(self.get_filename(name))
+ return os.path.splitext(filename)[0] == '__init__'
+
+ def source_path(self, name):
+ try:
+ return self.module_paths[name]
+ except KeyError:
+ raise ImportError
+
+ def get_filename(self, name):
+ """Silence deprecation warning."""
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ path = super().get_filename(name)
+ assert len(w) == 1
+ assert issubclass(w[0].category, PendingDeprecationWarning)
+ return path
+
+
+class PyLoaderCompatMock(PyLoaderMock):
+
+ """Mock that matches what is suggested to have a loader that is compatible
+ from Python 3.1 onwards."""
+
+ def get_filename(self, fullname):
+ try:
+ return self.module_paths[fullname]
+ except KeyError:
+ raise ImportError
+
+ def source_path(self, fullname):
+ try:
+ return self.get_filename(fullname)
+ except ImportError:
+ return None
+
+
+class PyPycLoaderMock(abc.PyPycLoader, PyLoaderMock):
+
+ default_mtime = 1
+
+ def __init__(self, source, bc={}):
+ """Initialize mock.
+
+ 'bc' is a dict keyed on a module's name. The value is dict with
+ possible keys of 'path', 'mtime', 'magic', and 'bc'. Except for 'path',
+ each of those keys control if any part of created bytecode is to
+ deviate from default values.
+
+ """
+ super().__init__(source)
+ self.module_bytecode = {}
+ self.path_to_bytecode = {}
+ self.bytecode_to_path = {}
+ for name, data in bc.items():
+ self.path_to_bytecode[data['path']] = name
+ self.bytecode_to_path[name] = data['path']
+ magic = data.get('magic', imp.get_magic())
+ mtime = importlib._w_long(data.get('mtime', self.default_mtime))
+ if 'bc' in data:
+ bc = data['bc']
+ else:
+ bc = self.compile_bc(name)
+ self.module_bytecode[name] = magic + mtime + bc
+
+ def compile_bc(self, name):
+ source_path = self.module_paths.get(name, '<test>') or '<test>'
+ code = compile(self.source, source_path, 'exec')
+ return marshal.dumps(code)
+
+ def source_mtime(self, name):
+ if name in self.module_paths:
+ return self.default_mtime
+ elif name in self.module_bytecode:
+ return None
+ else:
+ raise ImportError
+
+ def bytecode_path(self, name):
+ try:
+ return self.bytecode_to_path[name]
+ except KeyError:
+ if name in self.module_paths:
+ return None
+ else:
+ raise ImportError
+
+ def write_bytecode(self, name, bytecode):
+ self.module_bytecode[name] = bytecode
+ return True
+
+ def get_data(self, path):
+ if path in self.path_to_module:
+ return super().get_data(path)
+ elif path in self.path_to_bytecode:
+ name = self.path_to_bytecode[path]
+ return self.module_bytecode[name]
+ else:
+ raise IOError
+
+ def is_package(self, name):
+ try:
+ return super().is_package(name)
+ except TypeError:
+ return '__init__' in self.bytecode_to_path[name]
+
+ def get_code(self, name):
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ code_object = super().get_code(name)
+ assert len(w) == 1
+ assert issubclass(w[0].category, PendingDeprecationWarning)
+ return code_object
+
+class PyLoaderTests(testing_abc.LoaderTests):
+
+ """Tests for importlib.abc.PyLoader."""
+
+ mocker = PyLoaderMock
+
+ def eq_attrs(self, ob, **kwargs):
+ for attr, val in kwargs.items():
+ found = getattr(ob, attr)
+ self.assertEqual(found, val,
+ "{} attribute: {} != {}".format(attr, found, val))
+
+ def test_module(self):
+ name = '<module>'
+ path = os.path.join('', 'path', 'to', 'module')
+ mock = self.mocker({name: path})
+ with util.uncache(name):
+ module = mock.load_module(name)
+ self.assertTrue(name in sys.modules)
+ self.eq_attrs(module, __name__=name, __file__=path, __package__='',
+ __loader__=mock)
+ self.assertTrue(not hasattr(module, '__path__'))
+ return mock, name
+
+ def test_package(self):
+ name = '<pkg>'
+ path = os.path.join('path', 'to', name, '__init__')
+ mock = self.mocker({name: path})
+ with util.uncache(name):
+ module = mock.load_module(name)
+ self.assertTrue(name in sys.modules)
+ self.eq_attrs(module, __name__=name, __file__=path,
+ __path__=[os.path.dirname(path)], __package__=name,
+ __loader__=mock)
+ return mock, name
+
+ def test_lacking_parent(self):
+ name = 'pkg.mod'
+ path = os.path.join('path', 'to', 'pkg', 'mod')
+ mock = self.mocker({name: path})
+ with util.uncache(name):
+ module = mock.load_module(name)
+ self.assertIn(name, sys.modules)
+ self.eq_attrs(module, __name__=name, __file__=path, __package__='pkg',
+ __loader__=mock)
+ self.assertFalse(hasattr(module, '__path__'))
+ return mock, name
+
+ def test_module_reuse(self):
+ name = 'mod'
+ path = os.path.join('path', 'to', 'mod')
+ module = imp.new_module(name)
+ mock = self.mocker({name: path})
+ with util.uncache(name):
+ sys.modules[name] = module
+ loaded_module = mock.load_module(name)
+ self.assertTrue(loaded_module is module)
+ self.assertTrue(sys.modules[name] is module)
+ return mock, name
+
+ def test_state_after_failure(self):
+ name = "mod"
+ module = imp.new_module(name)
+ module.blah = None
+ mock = self.mocker({name: os.path.join('path', 'to', 'mod')})
+ mock.source = b"1/0"
+ with util.uncache(name):
+ sys.modules[name] = module
+ with self.assertRaises(ZeroDivisionError):
+ mock.load_module(name)
+ self.assertTrue(sys.modules[name] is module)
+ self.assertTrue(hasattr(module, 'blah'))
+ return mock
+
+ def test_unloadable(self):
+ name = "mod"
+ mock = self.mocker({name: os.path.join('path', 'to', 'mod')})
+ mock.source = b"1/0"
+ with util.uncache(name):
+ with self.assertRaises(ZeroDivisionError):
+ mock.load_module(name)
+ self.assertTrue(name not in sys.modules)
+ return mock
+
+
+class PyLoaderCompatTests(PyLoaderTests):
+
+ """Test that the suggested code to make a loader that is compatible from
+ Python 3.1 forward works."""
+
+ mocker = PyLoaderCompatMock
+
+
+class PyLoaderInterfaceTests(unittest.TestCase):
+
+ """Tests for importlib.abc.PyLoader to make sure that when source_path()
+ doesn't return a path everything works as expected."""
+
+ def test_no_source_path(self):
+ # No source path should lead to ImportError.
+ name = 'mod'
+ mock = PyLoaderMock({})
+ with util.uncache(name), self.assertRaises(ImportError):
+ mock.load_module(name)
+
+ def test_source_path_is_None(self):
+ name = 'mod'
+ mock = PyLoaderMock({name: None})
+ with util.uncache(name), self.assertRaises(ImportError):
+ mock.load_module(name)
+
+ def test_get_filename_with_source_path(self):
+ # get_filename() should return what source_path() returns.
+ name = 'mod'
+ path = os.path.join('path', 'to', 'source')
+ mock = PyLoaderMock({name: path})
+ with util.uncache(name):
+ self.assertEqual(mock.get_filename(name), path)
+
+ def test_get_filename_no_source_path(self):
+ # get_filename() should raise ImportError if source_path returns None.
+ name = 'mod'
+ mock = PyLoaderMock({name: None})
+ with util.uncache(name), self.assertRaises(ImportError):
+ mock.get_filename(name)
+
+
+class PyPycLoaderTests(PyLoaderTests):
+
+ """Tests for importlib.abc.PyPycLoader."""
+
+ mocker = PyPycLoaderMock
+
+ @source_util.writes_bytecode_files
+ def verify_bytecode(self, mock, name):
+ assert name in mock.module_paths
+ self.assertIn(name, mock.module_bytecode)
+ magic = mock.module_bytecode[name][:4]
+ self.assertEqual(magic, imp.get_magic())
+ mtime = importlib._r_long(mock.module_bytecode[name][4:8])
+ self.assertEqual(mtime, 1)
+ bc = mock.module_bytecode[name][8:]
+ self.assertEqual(bc, mock.compile_bc(name))
+
+ def test_module(self):
+ mock, name = super().test_module()
+ self.verify_bytecode(mock, name)
+
+ def test_package(self):
+ mock, name = super().test_package()
+ self.verify_bytecode(mock, name)
+
+ def test_lacking_parent(self):
+ mock, name = super().test_lacking_parent()
+ self.verify_bytecode(mock, name)
+
+ def test_module_reuse(self):
+ mock, name = super().test_module_reuse()
+ self.verify_bytecode(mock, name)
+
+ def test_state_after_failure(self):
+ super().test_state_after_failure()
+
+ def test_unloadable(self):
+ super().test_unloadable()
+
+
+class PyPycLoaderInterfaceTests(unittest.TestCase):
+
+ """Test for the interface of importlib.abc.PyPycLoader."""
+
+ def get_filename_check(self, src_path, bc_path, expect):
+ name = 'mod'
+ mock = PyPycLoaderMock({name: src_path}, {name: {'path': bc_path}})
+ with util.uncache(name):
+ assert mock.source_path(name) == src_path
+ assert mock.bytecode_path(name) == bc_path
+ self.assertEqual(mock.get_filename(name), expect)
+
+ def test_filename_with_source_bc(self):
+ # When source and bytecode paths present, return the source path.
+ self.get_filename_check('source_path', 'bc_path', 'source_path')
+
+ def test_filename_with_source_no_bc(self):
+ # With source but no bc, return source path.
+ self.get_filename_check('source_path', None, 'source_path')
+
+ def test_filename_with_no_source_bc(self):
+ # With not source but bc, return the bc path.
+ self.get_filename_check(None, 'bc_path', 'bc_path')
+
+ def test_filename_with_no_source_or_bc(self):
+ # With no source or bc, raise ImportError.
+ name = 'mod'
+ mock = PyPycLoaderMock({name: None}, {name: {'path': None}})
+ with util.uncache(name), self.assertRaises(ImportError):
+ mock.get_filename(name)
+
+
+class SkipWritingBytecodeTests(unittest.TestCase):
+
+ """Test that bytecode is properly handled based on
+ sys.dont_write_bytecode."""
+
+ @source_util.writes_bytecode_files
+ def run_test(self, dont_write_bytecode):
+ name = 'mod'
+ mock = PyPycLoaderMock({name: os.path.join('path', 'to', 'mod')})
+ sys.dont_write_bytecode = dont_write_bytecode
+ with util.uncache(name):
+ mock.load_module(name)
+ self.assertTrue((name in mock.module_bytecode) is not
+ dont_write_bytecode)
+
+ def test_no_bytecode_written(self):
+ self.run_test(True)
+
+ def test_bytecode_written(self):
+ self.run_test(False)
+
+
+class RegeneratedBytecodeTests(unittest.TestCase):
+
+ """Test that bytecode is regenerated as expected."""
+
+ @source_util.writes_bytecode_files
+ def test_different_magic(self):
+ # A different magic number should lead to new bytecode.
+ name = 'mod'
+ bad_magic = b'\x00\x00\x00\x00'
+ assert bad_magic != imp.get_magic()
+ mock = PyPycLoaderMock({name: os.path.join('path', 'to', 'mod')},
+ {name: {'path': os.path.join('path', 'to',
+ 'mod.bytecode'),
+ 'magic': bad_magic}})
+ with util.uncache(name):
+ mock.load_module(name)
+ self.assertTrue(name in mock.module_bytecode)
+ magic = mock.module_bytecode[name][:4]
+ self.assertEqual(magic, imp.get_magic())
+
+ @source_util.writes_bytecode_files
+ def test_old_mtime(self):
+ # Bytecode with an older mtime should be regenerated.
+ name = 'mod'
+ old_mtime = PyPycLoaderMock.default_mtime - 1
+ mock = PyPycLoaderMock({name: os.path.join('path', 'to', 'mod')},
+ {name: {'path': 'path/to/mod.bytecode', 'mtime': old_mtime}})
+ with util.uncache(name):
+ mock.load_module(name)
+ self.assertTrue(name in mock.module_bytecode)
+ mtime = importlib._r_long(mock.module_bytecode[name][4:8])
+ self.assertEqual(mtime, PyPycLoaderMock.default_mtime)
+
+
+class BadBytecodeFailureTests(unittest.TestCase):
+
+ """Test import failures when there is no source and parts of the bytecode
+ is bad."""
+
+ def test_bad_magic(self):
+ # A bad magic number should lead to an ImportError.
+ name = 'mod'
+ bad_magic = b'\x00\x00\x00\x00'
+ bc = {name:
+ {'path': os.path.join('path', 'to', 'mod'),
+ 'magic': bad_magic}}
+ mock = PyPycLoaderMock({name: None}, bc)
+ with util.uncache(name), self.assertRaises(ImportError):
+ mock.load_module(name)
+
+ def test_no_bytecode(self):
+ # Missing code object bytecode should lead to an EOFError.
+ name = 'mod'
+ bc = {name: {'path': os.path.join('path', 'to', 'mod'), 'bc': b''}}
+ mock = PyPycLoaderMock({name: None}, bc)
+ with util.uncache(name), self.assertRaises(EOFError):
+ mock.load_module(name)
+
+ def test_bad_bytecode(self):
+ # Malformed code object bytecode should lead to a ValueError.
+ name = 'mod'
+ bc = {name: {'path': os.path.join('path', 'to', 'mod'), 'bc': b'1234'}}
+ mock = PyPycLoaderMock({name: None}, bc)
+ with util.uncache(name), self.assertRaises(ValueError):
+ mock.load_module(name)
+
+
+def raise_ImportError(*args, **kwargs):
+ raise ImportError
+
+class MissingPathsTests(unittest.TestCase):
+
+ """Test what happens when a source or bytecode path does not exist (either
+ from *_path returning None or raising ImportError)."""
+
+ def test_source_path_None(self):
+ # Bytecode should be used when source_path returns None, along with
+ # __file__ being set to the bytecode path.
+ name = 'mod'
+ bytecode_path = 'path/to/mod'
+ mock = PyPycLoaderMock({name: None}, {name: {'path': bytecode_path}})
+ with util.uncache(name):
+ module = mock.load_module(name)
+ self.assertEqual(module.__file__, bytecode_path)
+
+ # Testing for bytecode_path returning None handled by all tests where no
+ # bytecode initially exists.
+
+ def test_all_paths_None(self):
+ # If all *_path methods return None, raise ImportError.
+ name = 'mod'
+ mock = PyPycLoaderMock({name: None})
+ with util.uncache(name), self.assertRaises(ImportError):
+ mock.load_module(name)
+
+ def test_source_path_ImportError(self):
+ # An ImportError from source_path should trigger an ImportError.
+ name = 'mod'
+ mock = PyPycLoaderMock({}, {name: {'path': os.path.join('path', 'to',
+ 'mod')}})
+ with util.uncache(name), self.assertRaises(ImportError):
+ mock.load_module(name)
+
+ def test_bytecode_path_ImportError(self):
+ # An ImportError from bytecode_path should trigger an ImportError.
+ name = 'mod'
+ mock = PyPycLoaderMock({name: os.path.join('path', 'to', 'mod')})
+ bad_meth = types.MethodType(raise_ImportError, mock)
+ mock.bytecode_path = bad_meth
+ with util.uncache(name), self.assertRaises(ImportError):
+ mock.load_module(name)
+
+
+class SourceLoaderTestHarness(unittest.TestCase):
+
+ def setUp(self, *, is_package=True, **kwargs):
+ self.package = 'pkg'
+ if is_package:
+ self.path = os.path.join(self.package, '__init__.py')
+ self.name = self.package
+ else:
+ module_name = 'mod'
+ self.path = os.path.join(self.package, '.'.join(['mod', 'py']))
+ self.name = '.'.join([self.package, module_name])
+ self.cached = imp.cache_from_source(self.path)
+ self.loader = self.loader_mock(self.path, **kwargs)
+
+ def verify_module(self, module):
+ self.assertEqual(module.__name__, self.name)
+ self.assertEqual(module.__file__, self.path)
+ self.assertEqual(module.__cached__, self.cached)
+ self.assertEqual(module.__package__, self.package)
+ self.assertEqual(module.__loader__, self.loader)
+ values = module._.split('::')
+ self.assertEqual(values[0], self.name)
+ self.assertEqual(values[1], self.path)
+ self.assertEqual(values[2], self.cached)
+ self.assertEqual(values[3], self.package)
+ self.assertEqual(values[4], repr(self.loader))
+
+ def verify_code(self, code_object):
+ module = imp.new_module(self.name)
+ module.__file__ = self.path
+ module.__cached__ = self.cached
+ module.__package__ = self.package
+ module.__loader__ = self.loader
+ module.__path__ = []
+ exec(code_object, module.__dict__)
+ self.verify_module(module)
+
+
+class SourceOnlyLoaderTests(SourceLoaderTestHarness):
+
+ """Test importlib.abc.SourceLoader for source-only loading.
+
+ Reload testing is subsumed by the tests for
+ importlib.util.module_for_loader.
+
+ """
+
+ loader_mock = SourceOnlyLoaderMock
+
+ def test_get_source(self):
+ # Verify the source code is returned as a string.
+ # If an IOError is raised by get_data then raise ImportError.
+ expected_source = self.loader.source.decode('utf-8')
+ self.assertEqual(self.loader.get_source(self.name), expected_source)
+ def raise_IOError(path):
+ raise IOError
+ self.loader.get_data = raise_IOError
+ with self.assertRaises(ImportError):
+ self.loader.get_source(self.name)
+
+ def test_is_package(self):
+ # Properly detect when loading a package.
+ self.setUp(is_package=True)
+ self.assertTrue(self.loader.is_package(self.name))
+ self.setUp(is_package=False)
+ self.assertFalse(self.loader.is_package(self.name))
+
+ def test_get_code(self):
+ # Verify the code object is created.
+ code_object = self.loader.get_code(self.name)
+ self.verify_code(code_object)
+
+ def test_load_module(self):
+ # Loading a module should set __name__, __loader__, __package__,
+ # __path__ (for packages), __file__, and __cached__.
+ # The module should also be put into sys.modules.
+ with util.uncache(self.name):
+ module = self.loader.load_module(self.name)
+ self.verify_module(module)
+ self.assertEqual(module.__path__, [os.path.dirname(self.path)])
+ self.assertTrue(self.name in sys.modules)
+
+ def test_package_settings(self):
+ # __package__ needs to be set, while __path__ is set on if the module
+ # is a package.
+ # Testing the values for a package are covered by test_load_module.
+ self.setUp(is_package=False)
+ with util.uncache(self.name):
+ module = self.loader.load_module(self.name)
+ self.verify_module(module)
+ self.assertTrue(not hasattr(module, '__path__'))
+
+ def test_get_source_encoding(self):
+ # Source is considered encoded in UTF-8 by default unless otherwise
+ # specified by an encoding line.
+ source = "_ = 'ü'"
+ self.loader.source = source.encode('utf-8')
+ returned_source = self.loader.get_source(self.name)
+ self.assertEqual(returned_source, source)
+ source = "# coding: latin-1\n_ = ü"
+ self.loader.source = source.encode('latin-1')
+ returned_source = self.loader.get_source(self.name)
+ self.assertEqual(returned_source, source)
+
+
+@unittest.skipIf(sys.dont_write_bytecode, "sys.dont_write_bytecode is true")
+class SourceLoaderBytecodeTests(SourceLoaderTestHarness):
+
+ """Test importlib.abc.SourceLoader's use of bytecode.
+
+ Source-only testing handled by SourceOnlyLoaderTests.
+
+ """
+
+ loader_mock = SourceLoaderMock
+
+ def verify_code(self, code_object, *, bytecode_written=False):
+ super().verify_code(code_object)
+ if bytecode_written:
+ self.assertIn(self.cached, self.loader.written)
+ data = bytearray(imp.get_magic())
+ data.extend(marshal._w_long(self.loader.source_mtime))
+ data.extend(marshal.dumps(code_object))
+ self.assertEqual(self.loader.written[self.cached], bytes(data))
+
+ def test_code_with_everything(self):
+ # When everything should work.
+ code_object = self.loader.get_code(self.name)
+ self.verify_code(code_object)
+
+ def test_no_bytecode(self):
+ # If no bytecode exists then move on to the source.
+ self.loader.bytecode_path = "<does not exist>"
+ # Sanity check
+ with self.assertRaises(IOError):
+ bytecode_path = imp.cache_from_source(self.path)
+ self.loader.get_data(bytecode_path)
+ code_object = self.loader.get_code(self.name)
+ self.verify_code(code_object, bytecode_written=True)
+
+ def test_code_bad_timestamp(self):
+ # Bytecode is only used when the timestamp matches the source EXACTLY.
+ for source_mtime in (0, 2):
+ assert source_mtime != self.loader.source_mtime
+ original = self.loader.source_mtime
+ self.loader.source_mtime = source_mtime
+ # If bytecode is used then EOFError would be raised by marshal.
+ self.loader.bytecode = self.loader.bytecode[8:]
+ code_object = self.loader.get_code(self.name)
+ self.verify_code(code_object, bytecode_written=True)
+ self.loader.source_mtime = original
+
+ def test_code_bad_magic(self):
+ # Skip over bytecode with a bad magic number.
+ self.setUp(magic=b'0000')
+ # If bytecode is used then EOFError would be raised by marshal.
+ self.loader.bytecode = self.loader.bytecode[8:]
+ code_object = self.loader.get_code(self.name)
+ self.verify_code(code_object, bytecode_written=True)
+
+ def test_dont_write_bytecode(self):
+ # Bytecode is not written if sys.dont_write_bytecode is true.
+ # Can assume it is false already thanks to the skipIf class decorator.
+ try:
+ sys.dont_write_bytecode = True
+ self.loader.bytecode_path = "<does not exist>"
+ code_object = self.loader.get_code(self.name)
+ self.assertNotIn(self.cached, self.loader.written)
+ finally:
+ sys.dont_write_bytecode = False
+
+ def test_no_set_data(self):
+ # If set_data is not defined, one can still read bytecode.
+ self.setUp(magic=b'0000')
+ original_set_data = self.loader.__class__.set_data
+ try:
+ del self.loader.__class__.set_data
+ code_object = self.loader.get_code(self.name)
+ self.verify_code(code_object)
+ finally:
+ self.loader.__class__.set_data = original_set_data
+
+ def test_set_data_raises_exceptions(self):
+ # Raising NotImplementedError or IOError is okay for set_data.
+ def raise_exception(exc):
+ def closure(*args, **kwargs):
+ raise exc
+ return closure
+
+ self.setUp(magic=b'0000')
+ self.loader.set_data = raise_exception(NotImplementedError)
+ code_object = self.loader.get_code(self.name)
+ self.verify_code(code_object)
+
+
+class SourceLoaderGetSourceTests(unittest.TestCase):
+
+ """Tests for importlib.abc.SourceLoader.get_source()."""
+
+ def test_default_encoding(self):
+ # Should have no problems with UTF-8 text.
+ name = 'mod'
+ mock = SourceOnlyLoaderMock('mod.file')
+ source = 'x = "ü"'
+ mock.source = source.encode('utf-8')
+ returned_source = mock.get_source(name)
+ self.assertEqual(returned_source, source)
+
+ def test_decoded_source(self):
+ # Decoding should work.
+ name = 'mod'
+ mock = SourceOnlyLoaderMock("mod.file")
+ source = "# coding: Latin-1\nx='ü'"
+ assert source.encode('latin-1') != source.encode('utf-8')
+ mock.source = source.encode('latin-1')
+ returned_source = mock.get_source(name)
+ self.assertEqual(returned_source, source)
+
+ def test_universal_newlines(self):
+ # PEP 302 says universal newlines should be used.
+ name = 'mod'
+ mock = SourceOnlyLoaderMock('mod.file')
+ source = "x = 42\r\ny = -13\r\n"
+ mock.source = source.encode('utf-8')
+ expect = io.IncrementalNewlineDecoder(None, True).decode(source)
+ self.assertEqual(mock.get_source(name), expect)
+
+class AbstractMethodImplTests(unittest.TestCase):
+
+ """Test the concrete abstractmethod implementations."""
+
+ class Loader(abc.Loader):
+ def load_module(self, fullname):
+ super().load_module(fullname)
+
+ class Finder(abc.Finder):
+ def find_module(self, _):
+ super().find_module(_)
+
+ class ResourceLoader(Loader, abc.ResourceLoader):
+ def get_data(self, _):
+ super().get_data(_)
+
+ class InspectLoader(Loader, abc.InspectLoader):
+ def is_package(self, _):
+ super().is_package(_)
+
+ def get_code(self, _):
+ super().get_code(_)
+
+ def get_source(self, _):
+ super().get_source(_)
+
+ class ExecutionLoader(InspectLoader, abc.ExecutionLoader):
+ def get_filename(self, _):
+ super().get_filename(_)
+
+ class SourceLoader(ResourceLoader, ExecutionLoader, abc.SourceLoader):
+ pass
+
+ class PyLoader(ResourceLoader, InspectLoader, abc.PyLoader):
+ def source_path(self, _):
+ super().source_path(_)
+
+ class PyPycLoader(PyLoader, abc.PyPycLoader):
+ def bytecode_path(self, _):
+ super().bytecode_path(_)
+
+ def source_mtime(self, _):
+ super().source_mtime(_)
+
+ def write_bytecode(self, _, _2):
+ super().write_bytecode(_, _2)
+
+ def raises_NotImplementedError(self, ins, *args):
+ for method_name in args:
+ method = getattr(ins, method_name)
+ arg_count = len(inspect.getfullargspec(method)[0]) - 1
+ args = [''] * arg_count
+ try:
+ method(*args)
+ except NotImplementedError:
+ pass
+ else:
+ msg = "{}.{} did not raise NotImplementedError"
+ self.fail(msg.format(ins.__class__.__name__, method_name))
+
+ def test_Loader(self):
+ self.raises_NotImplementedError(self.Loader(), 'load_module')
+
+ # XXX misplaced; should be somewhere else
+ def test_Finder(self):
+ self.raises_NotImplementedError(self.Finder(), 'find_module')
+
+ def test_ResourceLoader(self):
+ self.raises_NotImplementedError(self.ResourceLoader(), 'load_module',
+ 'get_data')
+
+ def test_InspectLoader(self):
+ self.raises_NotImplementedError(self.InspectLoader(), 'load_module',
+ 'is_package', 'get_code', 'get_source')
+
+ def test_ExecutionLoader(self):
+ self.raises_NotImplementedError(self.ExecutionLoader(), 'load_module',
+ 'is_package', 'get_code', 'get_source',
+ 'get_filename')
+
+ def test_SourceLoader(self):
+ ins = self.SourceLoader()
+ # Required abstractmethods.
+ self.raises_NotImplementedError(ins, 'get_filename', 'get_data')
+ # Optional abstractmethods.
+ self.raises_NotImplementedError(ins,'path_mtime', 'set_data')
+
+ def test_PyLoader(self):
+ self.raises_NotImplementedError(self.PyLoader(), 'source_path',
+ 'get_data', 'is_package')
+
+ def test_PyPycLoader(self):
+ self.raises_NotImplementedError(self.PyPycLoader(), 'source_path',
+ 'source_mtime', 'bytecode_path',
+ 'write_bytecode')
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(PyLoaderTests, PyLoaderCompatTests,
+ PyLoaderInterfaceTests,
+ PyPycLoaderTests, PyPycLoaderInterfaceTests,
+ SkipWritingBytecodeTests, RegeneratedBytecodeTests,
+ BadBytecodeFailureTests, MissingPathsTests,
+ SourceOnlyLoaderTests,
+ SourceLoaderBytecodeTests,
+ SourceLoaderGetSourceTests,
+ AbstractMethodImplTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/source/test_case_sensitivity.py b/Lib/importlib/test/source/test_case_sensitivity.py
new file mode 100644
index 0000000..73777de
--- /dev/null
+++ b/Lib/importlib/test/source/test_case_sensitivity.py
@@ -0,0 +1,60 @@
+"""Test case-sensitivity (PEP 235)."""
+from importlib import _bootstrap
+from .. import util
+from . import util as source_util
+import os
+import sys
+from test import support as test_support
+import unittest
+
+
+@util.case_insensitive_tests
+class CaseSensitivityTest(unittest.TestCase):
+
+ """PEP 235 dictates that on case-preserving, case-insensitive file systems
+ that imports are case-sensitive unless the PYTHONCASEOK environment
+ variable is set."""
+
+ name = 'MoDuLe'
+ assert name != name.lower()
+
+ def find(self, path):
+ finder = _bootstrap._FileFinder(path,
+ _bootstrap._SourceFinderDetails(),
+ _bootstrap._SourcelessFinderDetails())
+ return finder.find_module(self.name)
+
+ def sensitivity_test(self):
+ """Look for a module with matching and non-matching sensitivity."""
+ sensitive_pkg = 'sensitive.{0}'.format(self.name)
+ insensitive_pkg = 'insensitive.{0}'.format(self.name.lower())
+ context = source_util.create_modules(insensitive_pkg, sensitive_pkg)
+ with context as mapping:
+ sensitive_path = os.path.join(mapping['.root'], 'sensitive')
+ insensitive_path = os.path.join(mapping['.root'], 'insensitive')
+ return self.find(sensitive_path), self.find(insensitive_path)
+
+ def test_sensitive(self):
+ with test_support.EnvironmentVarGuard() as env:
+ env.unset('PYTHONCASEOK')
+ sensitive, insensitive = self.sensitivity_test()
+ self.assertTrue(hasattr(sensitive, 'load_module'))
+ self.assertIn(self.name, sensitive.get_filename(self.name))
+ self.assertIsNone(insensitive)
+
+ def test_insensitive(self):
+ with test_support.EnvironmentVarGuard() as env:
+ env.set('PYTHONCASEOK', '1')
+ sensitive, insensitive = self.sensitivity_test()
+ self.assertTrue(hasattr(sensitive, 'load_module'))
+ self.assertIn(self.name, sensitive.get_filename(self.name))
+ self.assertTrue(hasattr(insensitive, 'load_module'))
+ self.assertIn(self.name, insensitive.get_filename(self.name))
+
+
+def test_main():
+ test_support.run_unittest(CaseSensitivityTest)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/source/test_file_loader.py b/Lib/importlib/test/source/test_file_loader.py
new file mode 100644
index 0000000..c7a7d8f
--- /dev/null
+++ b/Lib/importlib/test/source/test_file_loader.py
@@ -0,0 +1,419 @@
+import importlib
+from importlib import _bootstrap
+from .. import abc
+from .. import util
+from . import util as source_util
+
+import errno
+import imp
+import marshal
+import os
+import py_compile
+import shutil
+import stat
+import sys
+import unittest
+
+from test.support import make_legacy_pyc
+
+
+class SimpleTest(unittest.TestCase):
+
+ """Should have no issue importing a source module [basic]. And if there is
+ a syntax error, it should raise a SyntaxError [syntax error].
+
+ """
+
+ # [basic]
+ def test_module(self):
+ with source_util.create_modules('_temp') as mapping:
+ loader = _bootstrap._SourceFileLoader('_temp', mapping['_temp'])
+ module = loader.load_module('_temp')
+ self.assertTrue('_temp' in sys.modules)
+ check = {'__name__': '_temp', '__file__': mapping['_temp'],
+ '__package__': ''}
+ for attr, value in check.items():
+ self.assertEqual(getattr(module, attr), value)
+
+ def test_package(self):
+ with source_util.create_modules('_pkg.__init__') as mapping:
+ loader = _bootstrap._SourceFileLoader('_pkg',
+ mapping['_pkg.__init__'])
+ module = loader.load_module('_pkg')
+ self.assertTrue('_pkg' in sys.modules)
+ check = {'__name__': '_pkg', '__file__': mapping['_pkg.__init__'],
+ '__path__': [os.path.dirname(mapping['_pkg.__init__'])],
+ '__package__': '_pkg'}
+ for attr, value in check.items():
+ self.assertEqual(getattr(module, attr), value)
+
+
+ def test_lacking_parent(self):
+ with source_util.create_modules('_pkg.__init__', '_pkg.mod')as mapping:
+ loader = _bootstrap._SourceFileLoader('_pkg.mod',
+ mapping['_pkg.mod'])
+ module = loader.load_module('_pkg.mod')
+ self.assertTrue('_pkg.mod' in sys.modules)
+ check = {'__name__': '_pkg.mod', '__file__': mapping['_pkg.mod'],
+ '__package__': '_pkg'}
+ for attr, value in check.items():
+ self.assertEqual(getattr(module, attr), value)
+
+ def fake_mtime(self, fxn):
+ """Fake mtime to always be higher than expected."""
+ return lambda name: fxn(name) + 1
+
+ def test_module_reuse(self):
+ with source_util.create_modules('_temp') as mapping:
+ loader = _bootstrap._SourceFileLoader('_temp', mapping['_temp'])
+ module = loader.load_module('_temp')
+ module_id = id(module)
+ module_dict_id = id(module.__dict__)
+ with open(mapping['_temp'], 'w') as file:
+ file.write("testing_var = 42\n")
+ # For filesystems where the mtime is only to a second granularity,
+ # everything that has happened above can be too fast;
+ # force an mtime on the source that is guaranteed to be different
+ # than the original mtime.
+ loader.path_mtime = self.fake_mtime(loader.path_mtime)
+ module = loader.load_module('_temp')
+ self.assertTrue('testing_var' in module.__dict__,
+ "'testing_var' not in "
+ "{0}".format(list(module.__dict__.keys())))
+ self.assertEqual(module, sys.modules['_temp'])
+ self.assertEqual(id(module), module_id)
+ self.assertEqual(id(module.__dict__), module_dict_id)
+
+ def test_state_after_failure(self):
+ # A failed reload should leave the original module intact.
+ attributes = ('__file__', '__path__', '__package__')
+ value = '<test>'
+ name = '_temp'
+ with source_util.create_modules(name) as mapping:
+ orig_module = imp.new_module(name)
+ for attr in attributes:
+ setattr(orig_module, attr, value)
+ with open(mapping[name], 'w') as file:
+ file.write('+++ bad syntax +++')
+ loader = _bootstrap._SourceFileLoader('_temp', mapping['_temp'])
+ with self.assertRaises(SyntaxError):
+ loader.load_module(name)
+ for attr in attributes:
+ self.assertEqual(getattr(orig_module, attr), value)
+
+ # [syntax error]
+ def test_bad_syntax(self):
+ with source_util.create_modules('_temp') as mapping:
+ with open(mapping['_temp'], 'w') as file:
+ file.write('=')
+ loader = _bootstrap._SourceFileLoader('_temp', mapping['_temp'])
+ with self.assertRaises(SyntaxError):
+ loader.load_module('_temp')
+ self.assertTrue('_temp' not in sys.modules)
+
+ def test_file_from_empty_string_dir(self):
+ # Loading a module found from an empty string entry on sys.path should
+ # not only work, but keep all attributes relative.
+ file_path = '_temp.py'
+ with open(file_path, 'w') as file:
+ file.write("# test file for importlib")
+ try:
+ with util.uncache('_temp'):
+ loader = _bootstrap._SourceFileLoader('_temp', file_path)
+ mod = loader.load_module('_temp')
+ self.assertEqual(file_path, mod.__file__)
+ self.assertEqual(imp.cache_from_source(file_path),
+ mod.__cached__)
+ finally:
+ os.unlink(file_path)
+ pycache = os.path.dirname(imp.cache_from_source(file_path))
+ shutil.rmtree(pycache)
+
+ def test_timestamp_overflow(self):
+ # When a modification timestamp is larger than 2**32, it should be
+ # truncated rather than raise an OverflowError.
+ with source_util.create_modules('_temp') as mapping:
+ source = mapping['_temp']
+ compiled = imp.cache_from_source(source)
+ with open(source, 'w') as f:
+ f.write("x = 5")
+ try:
+ os.utime(source, (2 ** 33 - 5, 2 ** 33 - 5))
+ except OverflowError:
+ self.skipTest("cannot set modification time to large integer")
+ except OSError as e:
+ if e.errno != getattr(errno, 'EOVERFLOW', None):
+ raise
+ self.skipTest("cannot set modification time to large integer ({})".format(e))
+ loader = _bootstrap._SourceFileLoader('_temp', mapping['_temp'])
+ mod = loader.load_module('_temp')
+ # Sanity checks.
+ self.assertEqual(mod.__cached__, compiled)
+ self.assertEqual(mod.x, 5)
+ # The pyc file was created.
+ os.stat(compiled)
+
+
+class BadBytecodeTest(unittest.TestCase):
+
+ def import_(self, file, module_name):
+ loader = self.loader(module_name, file)
+ module = loader.load_module(module_name)
+ self.assertTrue(module_name in sys.modules)
+
+ def manipulate_bytecode(self, name, mapping, manipulator, *,
+ del_source=False):
+ """Manipulate the bytecode of a module by passing it into a callable
+ that returns what to use as the new bytecode."""
+ try:
+ del sys.modules['_temp']
+ except KeyError:
+ pass
+ py_compile.compile(mapping[name])
+ if not del_source:
+ bytecode_path = imp.cache_from_source(mapping[name])
+ else:
+ os.unlink(mapping[name])
+ bytecode_path = make_legacy_pyc(mapping[name])
+ if manipulator:
+ with open(bytecode_path, 'rb') as file:
+ bc = file.read()
+ new_bc = manipulator(bc)
+ with open(bytecode_path, 'wb') as file:
+ if new_bc is not None:
+ file.write(new_bc)
+ return bytecode_path
+
+ def _test_empty_file(self, test, *, del_source=False):
+ with source_util.create_modules('_temp') as mapping:
+ bc_path = self.manipulate_bytecode('_temp', mapping,
+ lambda bc: b'',
+ del_source=del_source)
+ test('_temp', mapping, bc_path)
+
+ @source_util.writes_bytecode_files
+ def _test_partial_magic(self, test, *, del_source=False):
+ # When their are less than 4 bytes to a .pyc, regenerate it if
+ # possible, else raise ImportError.
+ with source_util.create_modules('_temp') as mapping:
+ bc_path = self.manipulate_bytecode('_temp', mapping,
+ lambda bc: bc[:3],
+ del_source=del_source)
+ test('_temp', mapping, bc_path)
+
+ def _test_magic_only(self, test, *, del_source=False):
+ with source_util.create_modules('_temp') as mapping:
+ bc_path = self.manipulate_bytecode('_temp', mapping,
+ lambda bc: bc[:4],
+ del_source=del_source)
+ test('_temp', mapping, bc_path)
+
+ def _test_partial_timestamp(self, test, *, del_source=False):
+ with source_util.create_modules('_temp') as mapping:
+ bc_path = self.manipulate_bytecode('_temp', mapping,
+ lambda bc: bc[:7],
+ del_source=del_source)
+ test('_temp', mapping, bc_path)
+
+ def _test_no_marshal(self, *, del_source=False):
+ with source_util.create_modules('_temp') as mapping:
+ bc_path = self.manipulate_bytecode('_temp', mapping,
+ lambda bc: bc[:8],
+ del_source=del_source)
+ file_path = mapping['_temp'] if not del_source else bc_path
+ with self.assertRaises(EOFError):
+ self.import_(file_path, '_temp')
+
+ def _test_non_code_marshal(self, *, del_source=False):
+ with source_util.create_modules('_temp') as mapping:
+ bytecode_path = self.manipulate_bytecode('_temp', mapping,
+ lambda bc: bc[:8] + marshal.dumps(b'abcd'),
+ del_source=del_source)
+ file_path = mapping['_temp'] if not del_source else bytecode_path
+ with self.assertRaises(ImportError):
+ self.import_(file_path, '_temp')
+
+ def _test_bad_marshal(self, *, del_source=False):
+ with source_util.create_modules('_temp') as mapping:
+ bytecode_path = self.manipulate_bytecode('_temp', mapping,
+ lambda bc: bc[:8] + b'<test>',
+ del_source=del_source)
+ file_path = mapping['_temp'] if not del_source else bytecode_path
+ with self.assertRaises(EOFError):
+ self.import_(file_path, '_temp')
+
+ def _test_bad_magic(self, test, *, del_source=False):
+ with source_util.create_modules('_temp') as mapping:
+ bc_path = self.manipulate_bytecode('_temp', mapping,
+ lambda bc: b'\x00\x00\x00\x00' + bc[4:])
+ test('_temp', mapping, bc_path)
+
+
+class SourceLoaderBadBytecodeTest(BadBytecodeTest):
+
+ loader = _bootstrap._SourceFileLoader
+
+ @source_util.writes_bytecode_files
+ def test_empty_file(self):
+ # When a .pyc is empty, regenerate it if possible, else raise
+ # ImportError.
+ def test(name, mapping, bytecode_path):
+ self.import_(mapping[name], name)
+ with open(bytecode_path, 'rb') as file:
+ self.assertGreater(len(file.read()), 8)
+
+ self._test_empty_file(test)
+
+ def test_partial_magic(self):
+ def test(name, mapping, bytecode_path):
+ self.import_(mapping[name], name)
+ with open(bytecode_path, 'rb') as file:
+ self.assertGreater(len(file.read()), 8)
+
+ self._test_partial_magic(test)
+
+ @source_util.writes_bytecode_files
+ def test_magic_only(self):
+ # When there is only the magic number, regenerate the .pyc if possible,
+ # else raise EOFError.
+ def test(name, mapping, bytecode_path):
+ self.import_(mapping[name], name)
+ with open(bytecode_path, 'rb') as file:
+ self.assertGreater(len(file.read()), 8)
+
+ self._test_magic_only(test)
+
+ @source_util.writes_bytecode_files
+ def test_bad_magic(self):
+ # When the magic number is different, the bytecode should be
+ # regenerated.
+ def test(name, mapping, bytecode_path):
+ self.import_(mapping[name], name)
+ with open(bytecode_path, 'rb') as bytecode_file:
+ self.assertEqual(bytecode_file.read(4), imp.get_magic())
+
+ self._test_bad_magic(test)
+
+ @source_util.writes_bytecode_files
+ def test_partial_timestamp(self):
+ # When the timestamp is partial, regenerate the .pyc, else
+ # raise EOFError.
+ def test(name, mapping, bc_path):
+ self.import_(mapping[name], name)
+ with open(bc_path, 'rb') as file:
+ self.assertGreater(len(file.read()), 8)
+
+ self._test_partial_timestamp(test)
+
+ @source_util.writes_bytecode_files
+ def test_no_marshal(self):
+ # When there is only the magic number and timestamp, raise EOFError.
+ self._test_no_marshal()
+
+ @source_util.writes_bytecode_files
+ def test_non_code_marshal(self):
+ self._test_non_code_marshal()
+ # XXX ImportError when sourceless
+
+ # [bad marshal]
+ @source_util.writes_bytecode_files
+ def test_bad_marshal(self):
+ # Bad marshal data should raise a ValueError.
+ self._test_bad_marshal()
+
+ # [bad timestamp]
+ @source_util.writes_bytecode_files
+ def test_old_timestamp(self):
+ # When the timestamp is older than the source, bytecode should be
+ # regenerated.
+ zeros = b'\x00\x00\x00\x00'
+ with source_util.create_modules('_temp') as mapping:
+ py_compile.compile(mapping['_temp'])
+ bytecode_path = imp.cache_from_source(mapping['_temp'])
+ with open(bytecode_path, 'r+b') as bytecode_file:
+ bytecode_file.seek(4)
+ bytecode_file.write(zeros)
+ self.import_(mapping['_temp'], '_temp')
+ source_mtime = os.path.getmtime(mapping['_temp'])
+ source_timestamp = importlib._w_long(source_mtime)
+ with open(bytecode_path, 'rb') as bytecode_file:
+ bytecode_file.seek(4)
+ self.assertEqual(bytecode_file.read(4), source_timestamp)
+
+ # [bytecode read-only]
+ @source_util.writes_bytecode_files
+ def test_read_only_bytecode(self):
+ # When bytecode is read-only but should be rewritten, fail silently.
+ with source_util.create_modules('_temp') as mapping:
+ # Create bytecode that will need to be re-created.
+ py_compile.compile(mapping['_temp'])
+ bytecode_path = imp.cache_from_source(mapping['_temp'])
+ with open(bytecode_path, 'r+b') as bytecode_file:
+ bytecode_file.seek(0)
+ bytecode_file.write(b'\x00\x00\x00\x00')
+ # Make the bytecode read-only.
+ os.chmod(bytecode_path,
+ stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
+ try:
+ # Should not raise IOError!
+ self.import_(mapping['_temp'], '_temp')
+ finally:
+ # Make writable for eventual clean-up.
+ os.chmod(bytecode_path, stat.S_IWUSR)
+
+
+class SourcelessLoaderBadBytecodeTest(BadBytecodeTest):
+
+ loader = _bootstrap._SourcelessFileLoader
+
+ def test_empty_file(self):
+ def test(name, mapping, bytecode_path):
+ with self.assertRaises(ImportError):
+ self.import_(bytecode_path, name)
+
+ self._test_empty_file(test, del_source=True)
+
+ def test_partial_magic(self):
+ def test(name, mapping, bytecode_path):
+ with self.assertRaises(ImportError):
+ self.import_(bytecode_path, name)
+ self._test_partial_magic(test, del_source=True)
+
+ def test_magic_only(self):
+ def test(name, mapping, bytecode_path):
+ with self.assertRaises(EOFError):
+ self.import_(bytecode_path, name)
+
+ self._test_magic_only(test, del_source=True)
+
+ def test_bad_magic(self):
+ def test(name, mapping, bytecode_path):
+ with self.assertRaises(ImportError):
+ self.import_(bytecode_path, name)
+
+ self._test_bad_magic(test, del_source=True)
+
+ def test_partial_timestamp(self):
+ def test(name, mapping, bytecode_path):
+ with self.assertRaises(EOFError):
+ self.import_(bytecode_path, name)
+
+ self._test_partial_timestamp(test, del_source=True)
+
+ def test_no_marshal(self):
+ self._test_no_marshal(del_source=True)
+
+ def test_non_code_marshal(self):
+ self._test_non_code_marshal(del_source=True)
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(SimpleTest,
+ SourceLoaderBadBytecodeTest,
+ SourcelessLoaderBadBytecodeTest
+ )
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/source/test_finder.py b/Lib/importlib/test/source/test_finder.py
new file mode 100644
index 0000000..7b9088d
--- /dev/null
+++ b/Lib/importlib/test/source/test_finder.py
@@ -0,0 +1,153 @@
+from importlib import _bootstrap
+from .. import abc
+from . import util as source_util
+from test.support import make_legacy_pyc
+import os
+import errno
+import py_compile
+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 import_(self, root, module):
+ finder = _bootstrap._FileFinder(root,
+ _bootstrap._SourceFinderDetails(),
+ _bootstrap._SourcelessFinderDetails())
+ return finder.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'))
+
+ # [sub empty]
+ def test_empty_sub_directory(self):
+ context = source_util.create_modules('pkg.__init__', 'pkg.sub.__init__')
+ with warnings.catch_warnings():
+ warnings.simplefilter("error", ImportWarning)
+ with context as mapping:
+ os.unlink(mapping['pkg.sub.__init__'])
+ pkg_dir = os.path.dirname(mapping['pkg.__init__'])
+ with self.assertRaises(ImportWarning):
+ self.import_(pkg_dir, 'pkg.sub')
+
+ # [package over modules]
+ def test_package_over_module(self):
+ name = '_temp'
+ loader = self.run_test(name, {'{0}.__init__'.format(name), name})
+ self.assertTrue('__init__' in loader.get_filename(name))
+
+
+ def test_failure(self):
+ with source_util.create_modules('blah') as mapping:
+ nothing = self.import_(mapping['.root'], 'sdfsadsadf')
+ self.assertTrue(nothing is None)
+
+ # [empty dir]
+ def test_empty_dir(self):
+ with warnings.catch_warnings():
+ warnings.simplefilter("error", ImportWarning)
+ with self.assertRaises(ImportWarning):
+ self.run_test('pkg', {'pkg.__init__'}, unlink={'pkg.__init__'})
+
+ def test_empty_string_for_dir(self):
+ # The empty string from sys.path means to search in the cwd.
+ finder = _bootstrap._FileFinder('', _bootstrap._SourceFinderDetails())
+ 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_main():
+ from test.support import run_unittest
+ run_unittest(FinderTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/source/test_path_hook.py b/Lib/importlib/test/source/test_path_hook.py
new file mode 100644
index 0000000..374f7b6
--- /dev/null
+++ b/Lib/importlib/test/source/test_path_hook.py
@@ -0,0 +1,26 @@
+from importlib import _bootstrap
+from . import util as source_util
+import unittest
+
+
+class PathHookTest(unittest.TestCase):
+
+ """Test the path hook for source."""
+
+ def test_success(self):
+ with source_util.create_modules('dummy') as mapping:
+ self.assertTrue(hasattr(_bootstrap._file_path_hook(mapping['.root']),
+ 'find_module'))
+
+ def test_empty_string(self):
+ # The empty string represents the cwd.
+ self.assertTrue(hasattr(_bootstrap._file_path_hook(''), 'find_module'))
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(PathHookTest)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/source/test_source_encoding.py b/Lib/importlib/test/source/test_source_encoding.py
new file mode 100644
index 0000000..794a3df
--- /dev/null
+++ b/Lib/importlib/test/source/test_source_encoding.py
@@ -0,0 +1,123 @@
+from importlib import _bootstrap
+from . import util as source_util
+
+import codecs
+import re
+import sys
+# Because sys.path gets essentially blanked, need to have unicodedata already
+# imported for the parser to use.
+import unicodedata
+import unittest
+
+
+CODING_RE = re.compile(r'coding[:=]\s*([-\w.]+)')
+
+
+class EncodingTest(unittest.TestCase):
+
+ """PEP 3120 makes UTF-8 the default encoding for source code
+ [default encoding].
+
+ PEP 263 specifies how that can change on a per-file basis. Either the first
+ or second line can contain the encoding line [encoding first line]
+ encoding second line]. If the file has the BOM marker it is considered UTF-8
+ implicitly [BOM]. If any encoding is specified it must be UTF-8, else it is
+ an error [BOM and utf-8][BOM conflict].
+
+ """
+
+ variable = '\u00fc'
+ character = '\u00c9'
+ source_line = "{0} = '{1}'\n".format(variable, character)
+ module_name = '_temp'
+
+ def run_test(self, source):
+ with source_util.create_modules(self.module_name) as mapping:
+ with open(mapping[self.module_name], 'wb') as file:
+ file.write(source)
+ loader = _bootstrap._SourceFileLoader(self.module_name,
+ mapping[self.module_name])
+ return loader.load_module(self.module_name)
+
+ def create_source(self, encoding):
+ encoding_line = "# coding={0}".format(encoding)
+ assert CODING_RE.search(encoding_line)
+ source_lines = [encoding_line.encode('utf-8')]
+ source_lines.append(self.source_line.encode(encoding))
+ return b'\n'.join(source_lines)
+
+ def test_non_obvious_encoding(self):
+ # Make sure that an encoding that has never been a standard one for
+ # Python works.
+ encoding_line = "# coding=koi8-r"
+ assert CODING_RE.search(encoding_line)
+ source = "{0}\na=42\n".format(encoding_line).encode("koi8-r")
+ self.run_test(source)
+
+ # [default encoding]
+ def test_default_encoding(self):
+ self.run_test(self.source_line.encode('utf-8'))
+
+ # [encoding first line]
+ def test_encoding_on_first_line(self):
+ encoding = 'Latin-1'
+ source = self.create_source(encoding)
+ self.run_test(source)
+
+ # [encoding second line]
+ def test_encoding_on_second_line(self):
+ source = b"#/usr/bin/python\n" + self.create_source('Latin-1')
+ self.run_test(source)
+
+ # [BOM]
+ def test_bom(self):
+ self.run_test(codecs.BOM_UTF8 + self.source_line.encode('utf-8'))
+
+ # [BOM and utf-8]
+ def test_bom_and_utf_8(self):
+ source = codecs.BOM_UTF8 + self.create_source('utf-8')
+ self.run_test(source)
+
+ # [BOM conflict]
+ def test_bom_conflict(self):
+ source = codecs.BOM_UTF8 + self.create_source('latin-1')
+ with self.assertRaises(SyntaxError):
+ self.run_test(source)
+
+
+class LineEndingTest(unittest.TestCase):
+
+ r"""Source written with the three types of line endings (\n, \r\n, \r)
+ need to be readable [cr][crlf][lf]."""
+
+ def run_test(self, line_ending):
+ module_name = '_temp'
+ source_lines = [b"a = 42", b"b = -13", b'']
+ source = line_ending.join(source_lines)
+ with source_util.create_modules(module_name) as mapping:
+ with open(mapping[module_name], 'wb') as file:
+ file.write(source)
+ loader = _bootstrap._SourceFileLoader(module_name,
+ mapping[module_name])
+ return loader.load_module(module_name)
+
+ # [cr]
+ def test_cr(self):
+ self.run_test(b'\r')
+
+ # [crlf]
+ def test_crlf(self):
+ self.run_test(b'\r\n')
+
+ # [lf]
+ def test_lf(self):
+ self.run_test(b'\n')
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(EncodingTest, LineEndingTest)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/source/util.py b/Lib/importlib/test/source/util.py
new file mode 100644
index 0000000..ae65663
--- /dev/null
+++ b/Lib/importlib/test/source/util.py
@@ -0,0 +1,97 @@
+from .. import util
+import contextlib
+import errno
+import functools
+import imp
+import os
+import os.path
+import sys
+import tempfile
+from test import support
+
+
+def writes_bytecode_files(fxn):
+ """Decorator to protect sys.dont_write_bytecode from mutation and to skip
+ tests that require it to be set to False."""
+ if sys.dont_write_bytecode:
+ return lambda *args, **kwargs: None
+ @functools.wraps(fxn)
+ def wrapper(*args, **kwargs):
+ original = sys.dont_write_bytecode
+ sys.dont_write_bytecode = False
+ try:
+ to_return = fxn(*args, **kwargs)
+ finally:
+ sys.dont_write_bytecode = original
+ return to_return
+ return wrapper
+
+
+def ensure_bytecode_path(bytecode_path):
+ """Ensure that the __pycache__ directory for PEP 3147 pyc file exists.
+
+ :param bytecode_path: File system path to PEP 3147 pyc file.
+ """
+ try:
+ os.mkdir(os.path.dirname(bytecode_path))
+ except OSError as error:
+ if error.errno != errno.EEXIST:
+ raise
+
+
+@contextlib.contextmanager
+def create_modules(*names):
+ """Temporarily create each named module with an attribute (named 'attr')
+ that contains the name passed into the context manager that caused the
+ creation of the module.
+
+ All files are created in a temporary directory returned by
+ tempfile.mkdtemp(). This directory is inserted at the beginning of
+ sys.path. When the context manager exits all created files (source and
+ bytecode) are explicitly deleted.
+
+ No magic is performed when creating packages! This means that if you create
+ a module within a package you must also create the package's __init__ as
+ well.
+
+ """
+ source = 'attr = {0!r}'
+ created_paths = []
+ mapping = {}
+ state_manager = None
+ uncache_manager = None
+ try:
+ temp_dir = tempfile.mkdtemp()
+ mapping['.root'] = temp_dir
+ import_names = set()
+ for name in names:
+ if not name.endswith('__init__'):
+ import_name = name
+ else:
+ import_name = name[:-len('.__init__')]
+ import_names.add(import_name)
+ if import_name in sys.modules:
+ del sys.modules[import_name]
+ name_parts = name.split('.')
+ file_path = temp_dir
+ for directory in name_parts[:-1]:
+ file_path = os.path.join(file_path, directory)
+ if not os.path.exists(file_path):
+ os.mkdir(file_path)
+ created_paths.append(file_path)
+ file_path = os.path.join(file_path, name_parts[-1] + '.py')
+ with open(file_path, 'w') as file:
+ file.write(source.format(name))
+ created_paths.append(file_path)
+ mapping[name] = file_path
+ uncache_manager = util.uncache(*import_names)
+ uncache_manager.__enter__()
+ state_manager = util.import_state(path=[temp_dir])
+ state_manager.__enter__()
+ yield mapping
+ finally:
+ if state_manager is not None:
+ state_manager.__exit__(None, None, None)
+ if uncache_manager is not None:
+ uncache_manager.__exit__(None, None, None)
+ support.rmtree(temp_dir)
diff --git a/Lib/importlib/test/test_abc.py b/Lib/importlib/test/test_abc.py
new file mode 100644
index 0000000..0ecbe39
--- /dev/null
+++ b/Lib/importlib/test/test_abc.py
@@ -0,0 +1,89 @@
+from importlib import abc
+from importlib import machinery
+import inspect
+import unittest
+
+
+class InheritanceTests:
+
+ """Test that the specified class is a subclass/superclass of the expected
+ classes."""
+
+ subclasses = []
+ superclasses = []
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ assert self.subclasses or self.superclasses, self.__class__
+ self.__test = getattr(abc, self.__class__.__name__)
+
+ def test_subclasses(self):
+ # Test that the expected subclasses inherit.
+ for subclass in self.subclasses:
+ self.assertTrue(issubclass(subclass, self.__test),
+ "{0} is not a subclass of {1}".format(subclass, self.__test))
+
+ def test_superclasses(self):
+ # Test that the class inherits from the expected superclasses.
+ for superclass in self.superclasses:
+ self.assertTrue(issubclass(self.__test, superclass),
+ "{0} is not a superclass of {1}".format(superclass, self.__test))
+
+
+class Finder(InheritanceTests, unittest.TestCase):
+
+ subclasses = [machinery.BuiltinImporter, machinery.FrozenImporter,
+ machinery.PathFinder]
+
+
+class Loader(InheritanceTests, unittest.TestCase):
+
+ subclasses = [abc.PyLoader]
+
+
+class ResourceLoader(InheritanceTests, unittest.TestCase):
+
+ superclasses = [abc.Loader]
+
+
+class InspectLoader(InheritanceTests, unittest.TestCase):
+
+ superclasses = [abc.Loader]
+ subclasses = [abc.PyLoader, machinery.BuiltinImporter,
+ machinery.FrozenImporter]
+
+
+class ExecutionLoader(InheritanceTests, unittest.TestCase):
+
+ superclasses = [abc.InspectLoader]
+ subclasses = [abc.PyLoader]
+
+
+class SourceLoader(InheritanceTests, unittest.TestCase):
+
+ superclasses = [abc.ResourceLoader, abc.ExecutionLoader]
+
+
+class PyLoader(InheritanceTests, unittest.TestCase):
+
+ superclasses = [abc.Loader, abc.ResourceLoader, abc.ExecutionLoader]
+
+
+class PyPycLoader(InheritanceTests, unittest.TestCase):
+
+ superclasses = [abc.PyLoader]
+
+
+def test_main():
+ from test.support import run_unittest
+ classes = []
+ for class_ in globals().values():
+ if (inspect.isclass(class_) and
+ issubclass(class_, unittest.TestCase) and
+ issubclass(class_, InheritanceTests)):
+ classes.append(class_)
+ run_unittest(*classes)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/test_api.py b/Lib/importlib/test/test_api.py
new file mode 100644
index 0000000..a151626
--- /dev/null
+++ b/Lib/importlib/test/test_api.py
@@ -0,0 +1,93 @@
+from . import util
+import imp
+import importlib
+import sys
+import unittest
+
+
+class ImportModuleTests(unittest.TestCase):
+
+ """Test importlib.import_module."""
+
+ def test_module_import(self):
+ # Test importing a top-level module.
+ with util.mock_modules('top_level') as mock:
+ with util.import_state(meta_path=[mock]):
+ module = importlib.import_module('top_level')
+ self.assertEqual(module.__name__, 'top_level')
+
+ def test_absolute_package_import(self):
+ # Test importing a module from a package with an absolute name.
+ pkg_name = 'pkg'
+ pkg_long_name = '{0}.__init__'.format(pkg_name)
+ name = '{0}.mod'.format(pkg_name)
+ with util.mock_modules(pkg_long_name, name) as mock:
+ with util.import_state(meta_path=[mock]):
+ module = importlib.import_module(name)
+ self.assertEqual(module.__name__, name)
+
+ def test_shallow_relative_package_import(self):
+ # Test importing a module from a package through a relative import.
+ pkg_name = 'pkg'
+ pkg_long_name = '{0}.__init__'.format(pkg_name)
+ module_name = 'mod'
+ absolute_name = '{0}.{1}'.format(pkg_name, module_name)
+ relative_name = '.{0}'.format(module_name)
+ with util.mock_modules(pkg_long_name, absolute_name) as mock:
+ with util.import_state(meta_path=[mock]):
+ importlib.import_module(pkg_name)
+ module = importlib.import_module(relative_name, pkg_name)
+ self.assertEqual(module.__name__, absolute_name)
+
+ def test_deep_relative_package_import(self):
+ modules = ['a.__init__', 'a.b.__init__', 'a.c']
+ with util.mock_modules(*modules) as mock:
+ with util.import_state(meta_path=[mock]):
+ importlib.import_module('a')
+ importlib.import_module('a.b')
+ module = importlib.import_module('..c', 'a.b')
+ self.assertEqual(module.__name__, 'a.c')
+
+ def test_absolute_import_with_package(self):
+ # Test importing a module from a package with an absolute name with
+ # the 'package' argument given.
+ pkg_name = 'pkg'
+ pkg_long_name = '{0}.__init__'.format(pkg_name)
+ name = '{0}.mod'.format(pkg_name)
+ with util.mock_modules(pkg_long_name, name) as mock:
+ with util.import_state(meta_path=[mock]):
+ importlib.import_module(pkg_name)
+ module = importlib.import_module(name, pkg_name)
+ self.assertEqual(module.__name__, name)
+
+ def test_relative_import_wo_package(self):
+ # Relative imports cannot happen without the 'package' argument being
+ # set.
+ with self.assertRaises(TypeError):
+ importlib.import_module('.support')
+
+
+ def test_loaded_once(self):
+ # Issue #13591: Modules should only be loaded once when
+ # initializing the parent package attempts to import the
+ # module currently being imported.
+ b_load_count = 0
+ def load_a():
+ importlib.import_module('a.b')
+ def load_b():
+ nonlocal b_load_count
+ b_load_count += 1
+ code = {'a': load_a, 'a.b': load_b}
+ modules = ['a.__init__', 'a.b']
+ with util.mock_modules(*modules, module_code=code) as mock:
+ with util.import_state(meta_path=[mock]):
+ importlib.import_module('a.b')
+ self.assertEqual(b_load_count, 1)
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(ImportModuleTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/test_util.py b/Lib/importlib/test/test_util.py
new file mode 100644
index 0000000..602447f
--- /dev/null
+++ b/Lib/importlib/test/test_util.py
@@ -0,0 +1,118 @@
+from importlib import util
+from . import util as test_util
+import imp
+import sys
+import types
+import unittest
+
+
+class ModuleForLoaderTests(unittest.TestCase):
+
+ """Tests for importlib.util.module_for_loader."""
+
+ def return_module(self, name):
+ fxn = util.module_for_loader(lambda self, module: module)
+ return fxn(self, name)
+
+ def raise_exception(self, name):
+ def to_wrap(self, module):
+ raise ImportError
+ fxn = util.module_for_loader(to_wrap)
+ try:
+ fxn(self, name)
+ except ImportError:
+ pass
+
+ def test_new_module(self):
+ # Test that when no module exists in sys.modules a new module is
+ # created.
+ module_name = 'a.b.c'
+ with test_util.uncache(module_name):
+ module = self.return_module(module_name)
+ self.assertTrue(module_name in sys.modules)
+ self.assertTrue(isinstance(module, types.ModuleType))
+ self.assertEqual(module.__name__, module_name)
+
+ def test_reload(self):
+ # Test that a module is reused if already in sys.modules.
+ name = 'a.b.c'
+ module = imp.new_module('a.b.c')
+ with test_util.uncache(name):
+ sys.modules[name] = module
+ returned_module = self.return_module(name)
+ self.assertIs(returned_module, sys.modules[name])
+
+ def test_new_module_failure(self):
+ # Test that a module is removed from sys.modules if added but an
+ # exception is raised.
+ name = 'a.b.c'
+ with test_util.uncache(name):
+ self.raise_exception(name)
+ self.assertTrue(name not in sys.modules)
+
+ def test_reload_failure(self):
+ # Test that a failure on reload leaves the module in-place.
+ name = 'a.b.c'
+ module = imp.new_module(name)
+ with test_util.uncache(name):
+ sys.modules[name] = module
+ self.raise_exception(name)
+ self.assertIs(module, sys.modules[name])
+
+
+class SetPackageTests(unittest.TestCase):
+
+
+ """Tests for importlib.util.set_package."""
+
+ def verify(self, module, expect):
+ """Verify the module has the expected value for __package__ after
+ passing through set_package."""
+ fxn = lambda: module
+ wrapped = util.set_package(fxn)
+ wrapped()
+ self.assertTrue(hasattr(module, '__package__'))
+ self.assertEqual(expect, module.__package__)
+
+ def test_top_level(self):
+ # __package__ should be set to the empty string if a top-level module.
+ # Implicitly tests when package is set to None.
+ module = imp.new_module('module')
+ module.__package__ = None
+ self.verify(module, '')
+
+ def test_package(self):
+ # Test setting __package__ for a package.
+ module = imp.new_module('pkg')
+ module.__path__ = ['<path>']
+ module.__package__ = None
+ self.verify(module, 'pkg')
+
+ def test_submodule(self):
+ # Test __package__ for a module in a package.
+ module = imp.new_module('pkg.mod')
+ module.__package__ = None
+ self.verify(module, 'pkg')
+
+ def test_setting_if_missing(self):
+ # __package__ should be set if it is missing.
+ module = imp.new_module('mod')
+ if hasattr(module, '__package__'):
+ delattr(module, '__package__')
+ self.verify(module, '')
+
+ def test_leaving_alone(self):
+ # If __package__ is set and not None then leave it alone.
+ for value in (True, False):
+ module = imp.new_module('mod')
+ module.__package__ = value
+ self.verify(module, value)
+
+
+def test_main():
+ from test import support
+ support.run_unittest(ModuleForLoaderTests, SetPackageTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/util.py b/Lib/importlib/test/util.py
new file mode 100644
index 0000000..93b7cd2
--- /dev/null
+++ b/Lib/importlib/test/util.py
@@ -0,0 +1,136 @@
+from contextlib import contextmanager
+import imp
+import os.path
+from test import support
+import unittest
+import sys
+
+
+CASE_INSENSITIVE_FS = True
+# Windows is the only OS that is *always* case-insensitive
+# (OS X *can* be case-sensitive).
+if sys.platform not in ('win32', 'cygwin'):
+ changed_name = __file__.upper()
+ if changed_name == __file__:
+ changed_name = __file__.lower()
+ if not os.path.exists(changed_name):
+ CASE_INSENSITIVE_FS = False
+
+
+def case_insensitive_tests(test):
+ """Class decorator that nullifies tests requiring a case-insensitive
+ file system."""
+ return unittest.skipIf(not CASE_INSENSITIVE_FS,
+ "requires a case-insensitive filesystem")(test)
+
+
+@contextmanager
+def uncache(*names):
+ """Uncache a module from sys.modules.
+
+ A basic sanity check is performed to prevent uncaching modules that either
+ cannot/shouldn't be uncached.
+
+ """
+ for name in names:
+ if name in ('sys', 'marshal', 'imp'):
+ raise ValueError(
+ "cannot uncache {0} as it will break _importlib".format(name))
+ try:
+ del sys.modules[name]
+ except KeyError:
+ pass
+ try:
+ yield
+ finally:
+ for name in names:
+ try:
+ del sys.modules[name]
+ except KeyError:
+ pass
+
+@contextmanager
+def import_state(**kwargs):
+ """Context manager to manage the various importers and stored state in the
+ sys module.
+
+ The 'modules' attribute is not supported as the interpreter state stores a
+ pointer to the dict that the interpreter uses internally;
+ reassigning to sys.modules does not have the desired effect.
+
+ """
+ originals = {}
+ try:
+ for attr, default in (('meta_path', []), ('path', []),
+ ('path_hooks', []),
+ ('path_importer_cache', {})):
+ originals[attr] = getattr(sys, attr)
+ if attr in kwargs:
+ new_value = kwargs[attr]
+ del kwargs[attr]
+ else:
+ new_value = default
+ setattr(sys, attr, new_value)
+ if len(kwargs):
+ raise ValueError(
+ 'unrecognized arguments: {0}'.format(kwargs.keys()))
+ yield
+ finally:
+ for attr, value in originals.items():
+ setattr(sys, attr, value)
+
+
+class mock_modules:
+
+ """A mock importer/loader."""
+
+ def __init__(self, *names, module_code={}):
+ self.modules = {}
+ self.module_code = {}
+ for name in names:
+ if not name.endswith('.__init__'):
+ import_name = name
+ else:
+ import_name = name[:-len('.__init__')]
+ if '.' not in name:
+ package = None
+ elif import_name == name:
+ package = name.rsplit('.', 1)[0]
+ else:
+ package = import_name
+ module = imp.new_module(import_name)
+ module.__loader__ = self
+ module.__file__ = '<mock __file__>'
+ module.__package__ = package
+ module.attr = name
+ if import_name != name:
+ module.__path__ = ['<mock __path__>']
+ self.modules[import_name] = module
+ if import_name in module_code:
+ self.module_code[import_name] = module_code[import_name]
+
+ def __getitem__(self, name):
+ return self.modules[name]
+
+ def find_module(self, fullname, path=None):
+ if fullname not in self.modules:
+ return None
+ else:
+ return self
+
+ def load_module(self, fullname):
+ if fullname not in self.modules:
+ raise ImportError
+ else:
+ sys.modules[fullname] = self.modules[fullname]
+ if fullname in self.module_code:
+ self.module_code[fullname]()
+ return self.modules[fullname]
+
+ def __enter__(self):
+ self._uncache = uncache(*self.modules.keys())
+ self._uncache.__enter__()
+ return self
+
+ def __exit__(self, *exc_info):
+ self._uncache.__exit__(None, None, None)
diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py
new file mode 100644
index 0000000..7b44fa1
--- /dev/null
+++ b/Lib/importlib/util.py
@@ -0,0 +1,5 @@
+"""Utility code for constructing importers, etc."""
+
+from ._bootstrap import module_for_loader
+from ._bootstrap import set_loader
+from ._bootstrap import set_package