diff options
author | Martin v. L?wis <martin@v.loewis.de> | 2012-06-03 12:00:48 +0200 |
---|---|---|
committer | Martin v. L?wis <martin@v.loewis.de> | 2012-06-03 12:00:48 +0200 |
commit | 183bc7b78cb50c73b3142710b3b8fbafde4a3833 (patch) | |
tree | 8a29bbfd852dccb5f14340c8d1c6309cf8fdfb72 /Lib/importlib/test | |
parent | 15670dc71126aabce6daca6c8d32f55ea146cc11 (diff) | |
parent | 9fe4e3d311149c5f07073f7c55509fcfd7dde47e (diff) | |
download | cpython-183bc7b78cb50c73b3142710b3b8fbafde4a3833.tar.gz |
Merge 3.2: issue #14937.
Diffstat (limited to 'Lib/importlib/test')
31 files changed, 837 insertions, 250 deletions
diff --git a/Lib/importlib/test/__init__.py b/Lib/importlib/test/__init__.py index e69de29bb2..815a706c54 100644 --- a/Lib/importlib/test/__init__.py +++ b/Lib/importlib/test/__init__.py @@ -0,0 +1,25 @@ +import os +import sys +import unittest + +def test_suite(package=__package__, directory=os.path.dirname(__file__)): + suite = unittest.TestSuite() + for name in os.listdir(directory): + if name.startswith(('.', '__')): + continue + path = os.path.join(directory, name) + if (os.path.isfile(path) and name.startswith('test_') and + name.endswith('.py')): + submodule_name = os.path.splitext(name)[0] + module_name = "{0}.{1}".format(package, submodule_name) + __import__(module_name, level=0) + module_tests = unittest.findTestCases(sys.modules[module_name]) + suite.addTest(module_tests) + elif os.path.isdir(path): + package_name = "{0}.{1}".format(package, name) + __import__(package_name, level=0) + package_tests = getattr(sys.modules[package_name], 'test_suite')() + suite.addTest(package_tests) + else: + continue + return suite diff --git a/Lib/importlib/test/__main__.py b/Lib/importlib/test/__main__.py index decc53d8c5..92171b25ca 100644 --- a/Lib/importlib/test/__main__.py +++ b/Lib/importlib/test/__main__.py @@ -4,26 +4,27 @@ 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__) + 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__': + import argparse + + parser = argparse.ArgumentParser(description='Execute the importlib test ' + 'suite') + parser.add_argument('-b', '--builtin', action='store_true', default=False, + help='use builtins.__import__() instead of importlib') + args = parser.parse_args() + if args.builtin: + util.using___import__ = True test_main() diff --git a/Lib/importlib/test/benchmark.py b/Lib/importlib/test/benchmark.py index b5de6c6b01..183e8180c9 100644 --- a/Lib/importlib/test/benchmark.py +++ b/Lib/importlib/test/benchmark.py @@ -9,9 +9,12 @@ from .source import util as source_util import decimal import imp import importlib +import importlib.machinery +import json import os import py_compile import sys +import tabnanny import timeit @@ -59,13 +62,17 @@ def builtin_mod(seconds, repeat): def source_wo_bytecode(seconds, repeat): - """Source w/o bytecode: simple""" + """Source w/o bytecode: small""" 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])) + sys.meta_path.append(importlib.machinery.PathFinder) + loader = (importlib.machinery.SourceFileLoader, + importlib.machinery.SOURCE_SUFFIXES, True) + sys.path_hooks.append(importlib.machinery.FileFinder.path_hook(loader)) for result in bench(name, lambda: sys.modules.pop(name), repeat=repeat, seconds=seconds): yield result @@ -73,26 +80,37 @@ def source_wo_bytecode(seconds, repeat): 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 _wo_bytecode(module): + name = module.__name__ + def benchmark_wo_bytecode(seconds, repeat): + """Source w/o bytecode: {}""" + bytecode_path = imp.cache_from_source(module.__file__) + if os.path.exists(bytecode_path): + os.unlink(bytecode_path) + 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 + + benchmark_wo_bytecode.__doc__ = benchmark_wo_bytecode.__doc__.format(name) + return benchmark_wo_bytecode + +tabnanny_wo_bytecode = _wo_bytecode(tabnanny) +decimal_wo_bytecode = _wo_bytecode(decimal) def source_writing_bytecode(seconds, repeat): - """Source writing bytecode: simple""" + """Source writing bytecode: small""" assert not sys.dont_write_bytecode name = '__importlib_test_benchmark__' with source_util.create_modules(name) as mapping: + sys.meta_path.append(importlib.machinery.PathFinder) + loader = (importlib.machinery.SourceFileLoader, + importlib.machinery.SOURCE_SUFFIXES, True) + sys.path_hooks.append(importlib.machinery.FileFinder.path_hook(loader)) def cleanup(): sys.modules.pop(name) os.unlink(imp.cache_from_source(mapping[name])) @@ -101,21 +119,33 @@ def source_writing_bytecode(seconds, repeat): 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 _writing_bytecode(module): + name = module.__name__ + def writing_bytecode_benchmark(seconds, repeat): + """Source writing bytecode: {}""" + assert not sys.dont_write_bytecode + def cleanup(): + sys.modules.pop(name) + os.unlink(imp.cache_from_source(module.__file__)) + for result in bench(name, cleanup, repeat=repeat, seconds=seconds): + yield result + + writing_bytecode_benchmark.__doc__ = ( + writing_bytecode_benchmark.__doc__.format(name)) + return writing_bytecode_benchmark + +tabnanny_writing_bytecode = _writing_bytecode(tabnanny) +decimal_writing_bytecode = _writing_bytecode(decimal) def source_using_bytecode(seconds, repeat): - """Bytecode w/ source: simple""" + """Source w/ bytecode: small""" name = '__importlib_test_benchmark__' with source_util.create_modules(name) as mapping: + sys.meta_path.append(importlib.machinery.PathFinder) + loader = (importlib.machinery.SourceFileLoader, + importlib.machinery.SOURCE_SUFFIXES, True) + sys.path_hooks.append(importlib.machinery.FileFinder.path_hook(loader)) 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, @@ -123,27 +153,56 @@ def source_using_bytecode(seconds, repeat): 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 _using_bytecode(module): + name = module.__name__ + def using_bytecode_benchmark(seconds, repeat): + """Source w/ bytecode: {}""" + py_compile.compile(module.__file__) + for result in bench(name, lambda: sys.modules.pop(name), repeat=repeat, + seconds=seconds): + yield result + using_bytecode_benchmark.__doc__ = ( + using_bytecode_benchmark.__doc__.format(name)) + return using_bytecode_benchmark -def main(import_): +tabnanny_using_bytecode = _using_bytecode(tabnanny) +decimal_using_bytecode = _using_bytecode(decimal) + + +def main(import_, options): + if options.source_file: + with options.source_file: + prev_results = json.load(options.source_file) + else: + prev_results = {} __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,) + source_wo_bytecode, source_using_bytecode, + tabnanny_writing_bytecode, + tabnanny_wo_bytecode, tabnanny_using_bytecode, + decimal_writing_bytecode, + decimal_wo_bytecode, decimal_using_bytecode, + ) + if options.benchmark: + for b in benchmarks: + if b.__doc__ == options.benchmark: + benchmarks = [b] + break + else: + print('Unknown benchmark: {!r}'.format(options.benchmark, + file=sys.stderr)) + sys.exit(1) 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)) + header = ('Measuring imports/second over {} second{}, best out of {}\n' + 'Entire benchmark run should take about {} seconds\n' + 'Using {!r} as __import__\n') + print(header.format(seconds, seconds_plural, repeat, + len(benchmarks) * seconds * repeat, __import__)) + new_results = {} for benchmark in benchmarks: print(benchmark.__doc__, "[", end=' ') sys.stdout.flush() @@ -154,19 +213,40 @@ def main(import_): sys.stdout.flush() assert not sys.dont_write_bytecode print("]", "best is", format(max(results), ',d')) + new_results[benchmark.__doc__] = results + if prev_results: + print('\n\nComparing new vs. old\n') + for benchmark in benchmarks: + benchmark_name = benchmark.__doc__ + old_result = max(prev_results[benchmark_name]) + new_result = max(new_results[benchmark_name]) + result = '{:,d} vs. {:,d} ({:%})'.format(new_result, + old_result, + new_result/old_result) + print(benchmark_name, ':', result) + if options.dest_file: + with options.dest_file: + json.dump(new_results, options.dest_file, indent=2) if __name__ == '__main__': - import optparse + import argparse - parser = optparse.OptionParser() - parser.add_option('-b', '--builtin', dest='builtin', action='store_true', + parser = argparse.ArgumentParser() + parser.add_argument('-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)) + parser.add_argument('-r', '--read', dest='source_file', + type=argparse.FileType('r'), + help='file to read benchmark data from to compare ' + 'against') + parser.add_argument('-w', '--write', dest='dest_file', + type=argparse.FileType('w'), + help='file to write benchmark data to') + parser.add_argument('--benchmark', dest='benchmark', + help='specific benchmark to run') + options = parser.parse_args() import_ = __import__ if not options.builtin: import_ = importlib.__import__ - main(import_) + main(import_, options) diff --git a/Lib/importlib/test/builtin/test_loader.py b/Lib/importlib/test/builtin/test_loader.py index 1a8539b1e8..ec126c9ccc 100644 --- a/Lib/importlib/test/builtin/test_loader.py +++ b/Lib/importlib/test/builtin/test_loader.py @@ -54,15 +54,17 @@ class LoaderTests(abc.LoaderTests): def test_unloadable(self): name = 'dssdsdfff' assert name not in sys.builtin_module_names - with self.assertRaises(ImportError): + with self.assertRaises(ImportError) as cm: self.load_module(name) + self.assertEqual(cm.exception.name, 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): + with self.assertRaises(ImportError) as cm: self.load_module('importlib') + self.assertEqual(cm.exception.name, 'importlib') class InspectLoaderTests(unittest.TestCase): @@ -88,8 +90,9 @@ class InspectLoaderTests(unittest.TestCase): # 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): + with self.assertRaises(ImportError) as cm: method(builtin_util.BAD_NAME) + self.assertRaises(builtin_util.BAD_NAME) diff --git a/Lib/importlib/test/extension/test_case_sensitivity.py b/Lib/importlib/test/extension/test_case_sensitivity.py index e062fb6597..bdc21e7f99 100644 --- a/Lib/importlib/test/extension/test_case_sensitivity.py +++ b/Lib/importlib/test/extension/test_case_sensitivity.py @@ -1,3 +1,4 @@ +import imp import sys from test import support import unittest @@ -13,19 +14,27 @@ class ExtensionModuleCaseSensitivityTest(unittest.TestCase): good_name = ext_util.NAME bad_name = good_name.upper() assert good_name != bad_name - finder = _bootstrap._FileFinder(ext_util.PATH, - _bootstrap._ExtensionFinderDetails()) + finder = _bootstrap.FileFinder(ext_util.PATH, + (_bootstrap.ExtensionFileLoader, + imp.extension_suffixes(), + False)) return finder.find_module(bad_name) def test_case_sensitive(self): with support.EnvironmentVarGuard() as env: env.unset('PYTHONCASEOK') + if b'PYTHONCASEOK' in _bootstrap._os.environ: + self.skipTest('os.environ changes not reflected in ' + '_os.environ') loader = self.find_module() self.assertIsNone(loader) def test_case_insensitivity(self): with support.EnvironmentVarGuard() as env: env.set('PYTHONCASEOK', '1') + if b'PYTHONCASEOK' not in _bootstrap._os.environ: + self.skipTest('os.environ changes not reflected in ' + '_os.environ') loader = self.find_module() self.assertTrue(hasattr(loader, 'load_module')) diff --git a/Lib/importlib/test/extension/test_finder.py b/Lib/importlib/test/extension/test_finder.py index ea97483317..804d8628e2 100644 --- a/Lib/importlib/test/extension/test_finder.py +++ b/Lib/importlib/test/extension/test_finder.py @@ -2,6 +2,7 @@ from importlib import _bootstrap from .. import abc from . import util +import imp import unittest class FinderTests(abc.FinderTests): @@ -9,8 +10,10 @@ class FinderTests(abc.FinderTests): """Test the finder for extension modules.""" def find_module(self, fullname): - importer = _bootstrap._FileFinder(util.PATH, - _bootstrap._ExtensionFinderDetails()) + importer = _bootstrap.FileFinder(util.PATH, + (_bootstrap.ExtensionFileLoader, + imp.extension_suffixes(), + False)) return importer.find_module(fullname) def test_module(self): diff --git a/Lib/importlib/test/extension/test_loader.py b/Lib/importlib/test/extension/test_loader.py index 4a783db8a5..4f486ce6af 100644 --- a/Lib/importlib/test/extension/test_loader.py +++ b/Lib/importlib/test/extension/test_loader.py @@ -1,4 +1,4 @@ -from importlib import _bootstrap +from importlib import machinery from . import util as ext_util from .. import abc from .. import util @@ -11,10 +11,20 @@ class LoaderTests(abc.LoaderTests): """Test load_module() for extension modules.""" + def setUp(self): + self.loader = machinery.ExtensionFileLoader(ext_util.NAME, + ext_util.FILEPATH) + def load_module(self, fullname): - loader = _bootstrap._ExtensionFileLoader(ext_util.NAME, - ext_util.FILEPATH) - return loader.load_module(fullname) + return self.loader.load_module(fullname) + + def test_load_module_API(self): + # Test the default argument for load_module(). + self.loader.load_module() + self.loader.load_module(None) + with self.assertRaises(ImportError): + self.load_module('XXX') + def test_module(self): with util.uncache(ext_util.NAME): @@ -25,7 +35,7 @@ class LoaderTests(abc.LoaderTests): self.assertEqual(getattr(module, attr), value) self.assertTrue(ext_util.NAME in sys.modules) self.assertTrue(isinstance(module.__loader__, - _bootstrap._ExtensionFileLoader)) + machinery.ExtensionFileLoader)) def test_package(self): # Extensions are not found in packages. @@ -46,8 +56,10 @@ class LoaderTests(abc.LoaderTests): pass def test_unloadable(self): - with self.assertRaises(ImportError): - self.load_module('asdfjkl;') + name = 'asdfjkl;' + with self.assertRaises(ImportError) as cm: + self.load_module(name) + self.assertEqual(cm.exception.name, name) def test_main(): diff --git a/Lib/importlib/test/extension/test_path_hook.py b/Lib/importlib/test/extension/test_path_hook.py index 4610420d29..129e6e2975 100644 --- a/Lib/importlib/test/extension/test_path_hook.py +++ b/Lib/importlib/test/extension/test_path_hook.py @@ -14,7 +14,8 @@ class PathHookTests(unittest.TestCase): # XXX Should it only work for directories containing an extension module? def hook(self, entry): - return _bootstrap._file_path_hook(entry) + return _bootstrap.FileFinder.path_hook((_bootstrap.ExtensionFileLoader, + imp.extension_suffixes(), False))(entry) def test_success(self): # Path hook should handle a directory where a known extension module diff --git a/Lib/importlib/test/extension/util.py b/Lib/importlib/test/extension/util.py index d149169748..a266dd98c8 100644 --- a/Lib/importlib/test/extension/util.py +++ b/Lib/importlib/test/extension/util.py @@ -1,4 +1,5 @@ import imp +from importlib import machinery import os import sys @@ -6,10 +7,9 @@ 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: + for EXT in machinery.EXTENSION_SUFFIXES: FILENAME = NAME + EXT FILEPATH = os.path.join(PATH, FILENAME) if os.path.exists(os.path.join(PATH, FILENAME)): @@ -18,4 +18,3 @@ try: PATH = EXT = FILENAME = FILEPATH = None except StopIteration: pass -del _file_exts diff --git a/Lib/importlib/test/frozen/test_loader.py b/Lib/importlib/test/frozen/test_loader.py index b685ef5708..6819a09b87 100644 --- a/Lib/importlib/test/frozen/test_loader.py +++ b/Lib/importlib/test/frozen/test_loader.py @@ -10,38 +10,46 @@ 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} + check = {'__name__': '__hello__', + '__package__': '', + '__loader__': machinery.FrozenImporter, + } for attr, value in check.items(): self.assertEqual(getattr(module, attr), value) self.assertEqual(stdout.getvalue(), 'Hello world!\n') + self.assertFalse(hasattr(module, '__file__')) 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} + check = {'__name__': '__phello__', + '__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') + self.assertFalse(hasattr(module, '__file__')) 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>', + check = {'__name__': '__phello__.spam', '__package__': '__phello__', - '__loader__': machinery.FrozenImporter} + '__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') + self.assertFalse(hasattr(module, '__file__')) def test_module_reuse(self): with util.uncache('__hello__'), captured_stdout() as stdout: @@ -51,14 +59,21 @@ class LoaderTests(abc.LoaderTests): self.assertEqual(stdout.getvalue(), 'Hello world!\nHello world!\n') + def test_module_repr(self): + with util.uncache('__hello__'), captured_stdout(): + module = machinery.FrozenImporter.load_module('__hello__') + self.assertEqual(repr(module), + "<module '__hello__' (frozen)>") + 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): + with self.assertRaises(ImportError) as cm: machinery.FrozenImporter.load_module('_not_real') + self.assertEqual(cm.exception.name, '_not_real') class InspectLoaderTests(unittest.TestCase): @@ -92,8 +107,9 @@ class InspectLoaderTests(unittest.TestCase): # 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): + with self.assertRaises(ImportError) as cm: method('importlib') + self.assertEqual(cm.exception.name, 'importlib') def test_main(): diff --git a/Lib/importlib/test/import_/test___package__.py b/Lib/importlib/test/import_/test___package__.py index 5056ae59cc..783cde1729 100644 --- a/Lib/importlib/test/import_/test___package__.py +++ b/Lib/importlib/test/import_/test___package__.py @@ -67,7 +67,7 @@ class Using__package__(unittest.TestCase): def test_bunk__package__(self): globals = {'__package__': 42} - with self.assertRaises(ValueError): + with self.assertRaises(TypeError): import_util.import_('', globals, {}, ['relimport'], 1) diff --git a/Lib/importlib/test/import_/test_api.py b/Lib/importlib/test/import_/test_api.py index 9075d42759..2fa1f90954 100644 --- a/Lib/importlib/test/import_/test_api.py +++ b/Lib/importlib/test/import_/test_api.py @@ -12,6 +12,13 @@ class APITest(unittest.TestCase): with self.assertRaises(TypeError): util.import_(42) + def test_negative_level(self): + # Raise ValueError when a negative level is specified. + # PEP 328 did away with sys.module None entries and the ambiguity of + # absolute/relative imports. + with self.assertRaises(ValueError): + util.import_('os', globals(), level=-1) + def test_main(): from test.support import run_unittest diff --git a/Lib/importlib/test/import_/test_caching.py b/Lib/importlib/test/import_/test_caching.py index 48dc64311a..3baff5501e 100644 --- a/Lib/importlib/test/import_/test_caching.py +++ b/Lib/importlib/test/import_/test_caching.py @@ -34,8 +34,9 @@ class UseCache(unittest.TestCase): name = 'using_None' with util.uncache(name): sys.modules[name] = None - with self.assertRaises(ImportError): + with self.assertRaises(ImportError) as cm: import_util.import_(name) + self.assertEqual(cm.exception.name, name) def create_mock(self, *names, return_=None): mock = util.mock_modules(*names) diff --git a/Lib/importlib/test/import_/test_fromlist.py b/Lib/importlib/test/import_/test_fromlist.py index b903e8e611..4ff5f5e825 100644 --- a/Lib/importlib/test/import_/test_fromlist.py +++ b/Lib/importlib/test/import_/test_fromlist.py @@ -1,6 +1,7 @@ """Test that the semantics relating to the 'fromlist' argument are correct.""" from .. import util from . import util as import_util +import imp import unittest class ReturnValue(unittest.TestCase): @@ -73,7 +74,8 @@ class HandlingFromlist(unittest.TestCase): def test_no_module_from_package(self): # [no module] with util.mock_modules('pkg.__init__') as importer: - with util.import_state(meta_path=[importer]): + with util.import_state(meta_path=[importer], + path_hooks=[imp.NullImporter]): module = import_util.import_('pkg', fromlist='non_existent') self.assertEqual(module.__name__, 'pkg') self.assertTrue(not hasattr(module, 'non_existent')) diff --git a/Lib/importlib/test/import_/test_meta_path.py b/Lib/importlib/test/import_/test_meta_path.py index 3b130c9a13..2f65af99a9 100644 --- a/Lib/importlib/test/import_/test_meta_path.py +++ b/Lib/importlib/test/import_/test_meta_path.py @@ -1,7 +1,10 @@ from .. import util from . import util as import_util +import importlib._bootstrap +import sys from types import MethodType import unittest +import warnings class CallingOrder(unittest.TestCase): @@ -33,6 +36,21 @@ class CallingOrder(unittest.TestCase): with util.import_state(meta_path=[first, second]): self.assertEqual(import_util.import_(mod_name), 42) + def test_empty(self): + # Raise an ImportWarning if sys.meta_path is empty. + module_name = 'nothing' + try: + del sys.modules[module_name] + except KeyError: + pass + with util.import_state(meta_path=[]): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + self.assertIsNone(importlib._bootstrap._find_module('nothing', + None)) + self.assertEqual(len(w), 1) + self.assertTrue(issubclass(w[-1].category, ImportWarning)) + class CallSignature(unittest.TestCase): diff --git a/Lib/importlib/test/import_/test_packages.py b/Lib/importlib/test/import_/test_packages.py index faadc32172..931494d6f7 100644 --- a/Lib/importlib/test/import_/test_packages.py +++ b/Lib/importlib/test/import_/test_packages.py @@ -3,6 +3,7 @@ from . import util as import_util import sys import unittest import importlib +from test import support class ParentModuleTests(unittest.TestCase): @@ -18,14 +19,88 @@ class ParentModuleTests(unittest.TestCase): def test_bad_parent(self): with util.mock_modules('pkg.module') as mock: with util.import_state(meta_path=[mock]): - with self.assertRaises(ImportError): + with self.assertRaises(ImportError) as cm: import_util.import_('pkg.module') + self.assertEqual(cm.exception.name, 'pkg') + + def test_raising_parent_after_importing_child(self): + def __init__(): + import pkg.module + 1/0 + mock = util.mock_modules('pkg.__init__', 'pkg.module', + module_code={'pkg': __init__}) + with mock: + with util.import_state(meta_path=[mock]): + with self.assertRaises(ZeroDivisionError): + import_util.import_('pkg') + self.assertFalse('pkg' in sys.modules) + self.assertTrue('pkg.module' in sys.modules) + with self.assertRaises(ZeroDivisionError): + import_util.import_('pkg.module') + self.assertFalse('pkg' in sys.modules) + self.assertTrue('pkg.module' in sys.modules) + + def test_raising_parent_after_relative_importing_child(self): + def __init__(): + from . import module + 1/0 + mock = util.mock_modules('pkg.__init__', 'pkg.module', + module_code={'pkg': __init__}) + with mock: + with util.import_state(meta_path=[mock]): + with self.assertRaises((ZeroDivisionError, ImportError)): + # This raises ImportError on the "from . import module" + # line, not sure why. + import_util.import_('pkg') + self.assertFalse('pkg' in sys.modules) + with self.assertRaises((ZeroDivisionError, ImportError)): + import_util.import_('pkg.module') + self.assertFalse('pkg' in sys.modules) + # XXX False + #self.assertTrue('pkg.module' in sys.modules) + + def test_raising_parent_after_double_relative_importing_child(self): + def __init__(): + from ..subpkg import module + 1/0 + mock = util.mock_modules('pkg.__init__', 'pkg.subpkg.__init__', + 'pkg.subpkg.module', + module_code={'pkg.subpkg': __init__}) + with mock: + with util.import_state(meta_path=[mock]): + with self.assertRaises((ZeroDivisionError, ImportError)): + # This raises ImportError on the "from ..subpkg import module" + # line, not sure why. + import_util.import_('pkg.subpkg') + self.assertFalse('pkg.subpkg' in sys.modules) + with self.assertRaises((ZeroDivisionError, ImportError)): + import_util.import_('pkg.subpkg.module') + self.assertFalse('pkg.subpkg' in sys.modules) + # XXX False + #self.assertTrue('pkg.subpkg.module' in sys.modules) 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): + with self.assertRaises(ImportError) as cm: import_util.import_('sys.no_submodules_here') + self.assertEqual(cm.exception.name, 'sys.no_submodules_here') + + def test_module_not_package_but_side_effects(self): + # If a module injects something into sys.modules as a side-effect, then + # pick up on that fact. + name = 'mod' + subname = name + '.b' + def module_injection(): + sys.modules[subname] = 'total bunk' + mock_modules = util.mock_modules('mod', + module_code={'mod': module_injection}) + with mock_modules as mock: + with util.import_state(meta_path=[mock]): + try: + submodule = import_util.import_(subname) + finally: + support.unload(subname) def test_main(): diff --git a/Lib/importlib/test/import_/test_path.py b/Lib/importlib/test/import_/test_path.py index 2faa23174b..723f5b57a8 100644 --- a/Lib/importlib/test/import_/test_path.py +++ b/Lib/importlib/test/import_/test_path.py @@ -9,6 +9,7 @@ import tempfile from test import support from types import MethodType import unittest +import warnings class FinderTests(unittest.TestCase): @@ -42,6 +43,15 @@ class FinderTests(unittest.TestCase): loader = machinery.PathFinder.find_module(module, [path]) self.assertTrue(loader is importer) + def test_empty_list(self): + # An empty list should not count as asking for sys.path. + module = 'module' + path = '<test path>' + importer = util.mock_modules(module) + with util.import_state(path_importer_cache={path: importer}, + path=[path]): + self.assertIsNone(machinery.PathFinder.find_module('module', [])) + def test_path_hooks(self): # Test that sys.path_hooks is used. # Test that sys.path_importer_cache is set. @@ -55,77 +65,34 @@ class FinderTests(unittest.TestCase): 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>' + def test_empty_path_hooks(self): + # Test that if sys.path_hooks is empty a warning is raised, + # sys.path_importer_cache gets None set, and PathFinder returns None. + path_entry = 'bogus_path' + with util.import_state(path_importer_cache={}, path_hooks=[], + path=[path_entry]): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + self.assertIsNone(machinery.PathFinder.find_module('os')) + self.assertIsNone(sys.path_importer_cache[path_entry]) + self.assertEqual(len(w), 1) + self.assertTrue(issubclass(w[-1].category, ImportWarning)) + + def test_path_importer_cache_empty_string(self): + # The empty string should create a finder using the cwd. + path = '' module = '<test module>' importer = util.mock_modules(module) - with util.import_state(path=['1', '2'], - path_importer_cache={'1': None, '2': importer}): + hook = import_util.mock_path_hook(os.curdir, importer=importer) + with util.import_state(path=[path], path_hooks=[hook]): 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 + self.assertIs(loader, importer) + self.assertIn(os.curdir, sys.path_importer_cache) def test_main(): from test.support import run_unittest - run_unittest(FinderTests, DefaultPathFinderTests) + run_unittest(FinderTests) 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 index a0f6b2d827..4569c26424 100644 --- a/Lib/importlib/test/import_/test_relative_imports.py +++ b/Lib/importlib/test/import_/test_relative_imports.py @@ -193,6 +193,20 @@ class RelativeImports(unittest.TestCase): self.assertEqual(module.__name__, '__runpy_pkg__.uncle.cousin') self.relative_import_test(create, globals_, callback) + def test_import_relative_import_no_fromlist(self): + # Import a relative module w/ no fromlist. + create = ['crash.__init__', 'crash.mod'] + globals_ = [{'__package__': 'crash', '__name__': 'crash'}] + def callback(global_): + import_util.import_('crash') + mod = import_util.import_('mod', global_, {}, [], 1) + self.assertEqual(mod.__name__, 'crash.mod') + self.relative_import_test(create, globals_, callback) + + def test_relative_import_no_globals(self): + # No globals for a relative import is an error. + with self.assertRaises(KeyError): + import_util.import_('sys', level=1) def test_main(): diff --git a/Lib/importlib/test/import_/util.py b/Lib/importlib/test/import_/util.py index 649c5ed27c..86ac065e64 100644 --- a/Lib/importlib/test/import_/util.py +++ b/Lib/importlib/test/import_/util.py @@ -1,6 +1,5 @@ import functools import importlib -import importlib._bootstrap import unittest diff --git a/Lib/importlib/test/regrtest.py b/Lib/importlib/test/regrtest.py index b103ae7d0e..a5be11fd4e 100644 --- a/Lib/importlib/test/regrtest.py +++ b/Lib/importlib/test/regrtest.py @@ -5,13 +5,6 @@ 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 @@ -19,17 +12,6 @@ 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) + sys.path_importer_cache.clear() regrtest.main(quiet=True, verbose2=True) diff --git a/Lib/importlib/test/source/test_abc_loader.py b/Lib/importlib/test/source/test_abc_loader.py index 32459074a0..b1b1204197 100644 --- a/Lib/importlib/test/source/test_abc_loader.py +++ b/Lib/importlib/test/source/test_abc_loader.py @@ -40,8 +40,10 @@ class SourceLoaderMock(SourceOnlyLoaderMock): def __init__(self, path, magic=imp.get_magic()): super().__init__(path) self.bytecode_path = imp.cache_from_source(self.path) + self.source_size = len(self.source) data = bytearray(magic) - data.extend(marshal._w_long(self.source_mtime)) + data.extend(importlib._w_long(self.source_mtime)) + data.extend(importlib._w_long(self.source_size)) code_object = compile(self.source, self.path, 'exec', dont_inherit=True) data.extend(marshal.dumps(code_object)) @@ -56,9 +58,9 @@ class SourceLoaderMock(SourceOnlyLoaderMock): else: raise IOError - def path_mtime(self, path): + def path_stats(self, path): assert path == self.path - return self.source_mtime + return {'mtime': self.source_mtime, 'size': self.source_size} def set_data(self, path, data): self.written[path] = bytes(data) @@ -102,7 +104,7 @@ class PyLoaderMock(abc.PyLoader): warnings.simplefilter("always") path = super().get_filename(name) assert len(w) == 1 - assert issubclass(w[0].category, PendingDeprecationWarning) + assert issubclass(w[0].category, DeprecationWarning) return path @@ -198,7 +200,7 @@ class PyPycLoaderMock(abc.PyPycLoader, PyLoaderMock): warnings.simplefilter("always") code_object = super().get_code(name) assert len(w) == 1 - assert issubclass(w[0].category, PendingDeprecationWarning) + assert issubclass(w[0].category, DeprecationWarning) return code_object class PyLoaderTests(testing_abc.LoaderTests): @@ -469,8 +471,9 @@ class BadBytecodeFailureTests(unittest.TestCase): {'path': os.path.join('path', 'to', 'mod'), 'magic': bad_magic}} mock = PyPycLoaderMock({name: None}, bc) - with util.uncache(name), self.assertRaises(ImportError): + with util.uncache(name), self.assertRaises(ImportError) as cm: mock.load_module(name) + self.assertEqual(cm.exception.name, name) def test_no_bytecode(self): # Missing code object bytecode should lead to an EOFError. @@ -514,8 +517,9 @@ class MissingPathsTests(unittest.TestCase): # If all *_path methods return None, raise ImportError. name = 'mod' mock = PyPycLoaderMock({name: None}) - with util.uncache(name), self.assertRaises(ImportError): + with util.uncache(name), self.assertRaises(ImportError) as cm: mock.load_module(name) + self.assertEqual(cm.exception.name, name) def test_source_path_ImportError(self): # An ImportError from source_path should trigger an ImportError. @@ -531,7 +535,7 @@ class MissingPathsTests(unittest.TestCase): 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): + with util.uncache(name), self.assertRaises(ImportError) as cm: mock.load_module(name) @@ -592,8 +596,9 @@ class SourceOnlyLoaderTests(SourceLoaderTestHarness): def raise_IOError(path): raise IOError self.loader.get_data = raise_IOError - with self.assertRaises(ImportError): + with self.assertRaises(ImportError) as cm: self.loader.get_source(self.name) + self.assertEqual(cm.exception.name, self.name) def test_is_package(self): # Properly detect when loading a package. @@ -656,7 +661,8 @@ class SourceLoaderBytecodeTests(SourceLoaderTestHarness): 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(importlib._w_long(self.loader.source_mtime)) + data.extend(importlib._w_long(self.loader.source_size)) data.extend(marshal.dumps(code_object)) self.assertEqual(self.loader.written[self.cached], bytes(data)) @@ -847,7 +853,7 @@ class AbstractMethodImplTests(unittest.TestCase): # Required abstractmethods. self.raises_NotImplementedError(ins, 'get_filename', 'get_data') # Optional abstractmethods. - self.raises_NotImplementedError(ins,'path_mtime', 'set_data') + self.raises_NotImplementedError(ins,'path_stats', 'set_data') def test_PyLoader(self): self.raises_NotImplementedError(self.PyLoader(), 'source_path', diff --git a/Lib/importlib/test/source/test_case_sensitivity.py b/Lib/importlib/test/source/test_case_sensitivity.py index 73777de4ba..21a4378ce9 100644 --- a/Lib/importlib/test/source/test_case_sensitivity.py +++ b/Lib/importlib/test/source/test_case_sensitivity.py @@ -1,7 +1,9 @@ """Test case-sensitivity (PEP 235).""" from importlib import _bootstrap +from importlib import machinery from .. import util from . import util as source_util +import imp import os import sys from test import support as test_support @@ -19,9 +21,13 @@ class CaseSensitivityTest(unittest.TestCase): assert name != name.lower() def find(self, path): - finder = _bootstrap._FileFinder(path, - _bootstrap._SourceFinderDetails(), - _bootstrap._SourcelessFinderDetails()) + finder = machinery.FileFinder(path, + (machinery.SourceFileLoader, + machinery.SOURCE_SUFFIXES, + True), + (machinery.SourcelessFileLoader, + machinery.BYTECODE_SUFFIXES, + True)) return finder.find_module(self.name) def sensitivity_test(self): @@ -37,6 +43,9 @@ class CaseSensitivityTest(unittest.TestCase): def test_sensitive(self): with test_support.EnvironmentVarGuard() as env: env.unset('PYTHONCASEOK') + if b'PYTHONCASEOK' in _bootstrap._os.environ: + self.skipTest('os.environ changes not reflected in ' + '_os.environ') sensitive, insensitive = self.sensitivity_test() self.assertTrue(hasattr(sensitive, 'load_module')) self.assertIn(self.name, sensitive.get_filename(self.name)) @@ -45,6 +54,9 @@ class CaseSensitivityTest(unittest.TestCase): def test_insensitive(self): with test_support.EnvironmentVarGuard() as env: env.set('PYTHONCASEOK', '1') + if b'PYTHONCASEOK' not in _bootstrap._os.environ: + self.skipTest('os.environ changes not reflected in ' + '_os.environ') sensitive, insensitive = self.sensitivity_test() self.assertTrue(hasattr(sensitive, 'load_module')) self.assertIn(self.name, sensitive.get_filename(self.name)) diff --git a/Lib/importlib/test/source/test_file_loader.py b/Lib/importlib/test/source/test_file_loader.py index c7a7d8fbca..ffa0c24e09 100644 --- a/Lib/importlib/test/source/test_file_loader.py +++ b/Lib/importlib/test/source/test_file_loader.py @@ -1,5 +1,6 @@ +from ... import _bootstrap import importlib -from importlib import _bootstrap +import importlib.abc from .. import abc from .. import util from . import util as source_util @@ -24,10 +25,44 @@ class SimpleTest(unittest.TestCase): """ + def test_load_module_API(self): + # If fullname is not specified that assume self.name is desired. + class TesterMixin(importlib.abc.Loader): + def load_module(self, fullname): return fullname + + class Tester(importlib.abc.FileLoader, TesterMixin): + def get_code(self, _): pass + def get_source(self, _): pass + def is_package(self, _): pass + + name = 'mod_name' + loader = Tester(name, 'some_path') + self.assertEqual(name, loader.load_module()) + self.assertEqual(name, loader.load_module(None)) + self.assertEqual(name, loader.load_module(name)) + with self.assertRaises(ImportError): + loader.load_module(loader.name + 'XXX') + + def test_get_filename_API(self): + # If fullname is not set then assume self.path is desired. + class Tester(importlib.abc.FileLoader): + def get_code(self, _): pass + def get_source(self, _): pass + def is_package(self, _): pass + + path = 'some_path' + name = 'some_name' + loader = Tester(name, path) + self.assertEqual(path, loader.get_filename(name)) + self.assertEqual(path, loader.get_filename()) + self.assertEqual(path, loader.get_filename(None)) + with self.assertRaises(ImportError): + loader.get_filename(name + 'XXX') + # [basic] def test_module(self): with source_util.create_modules('_temp') as mapping: - loader = _bootstrap._SourceFileLoader('_temp', mapping['_temp']) + loader = _bootstrap.SourceFileLoader('_temp', mapping['_temp']) module = loader.load_module('_temp') self.assertTrue('_temp' in sys.modules) check = {'__name__': '_temp', '__file__': mapping['_temp'], @@ -37,7 +72,7 @@ class SimpleTest(unittest.TestCase): def test_package(self): with source_util.create_modules('_pkg.__init__') as mapping: - loader = _bootstrap._SourceFileLoader('_pkg', + loader = _bootstrap.SourceFileLoader('_pkg', mapping['_pkg.__init__']) module = loader.load_module('_pkg') self.assertTrue('_pkg' in sys.modules) @@ -50,7 +85,7 @@ class SimpleTest(unittest.TestCase): def test_lacking_parent(self): with source_util.create_modules('_pkg.__init__', '_pkg.mod')as mapping: - loader = _bootstrap._SourceFileLoader('_pkg.mod', + loader = _bootstrap.SourceFileLoader('_pkg.mod', mapping['_pkg.mod']) module = loader.load_module('_pkg.mod') self.assertTrue('_pkg.mod' in sys.modules) @@ -65,17 +100,12 @@ class SimpleTest(unittest.TestCase): def test_module_reuse(self): with source_util.create_modules('_temp') as mapping: - loader = _bootstrap._SourceFileLoader('_temp', mapping['_temp']) + 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 " @@ -95,7 +125,7 @@ class SimpleTest(unittest.TestCase): setattr(orig_module, attr, value) with open(mapping[name], 'w') as file: file.write('+++ bad syntax +++') - loader = _bootstrap._SourceFileLoader('_temp', mapping['_temp']) + loader = _bootstrap.SourceFileLoader('_temp', mapping['_temp']) with self.assertRaises(SyntaxError): loader.load_module(name) for attr in attributes: @@ -106,7 +136,7 @@ class SimpleTest(unittest.TestCase): with source_util.create_modules('_temp') as mapping: with open(mapping['_temp'], 'w') as file: file.write('=') - loader = _bootstrap._SourceFileLoader('_temp', mapping['_temp']) + loader = _bootstrap.SourceFileLoader('_temp', mapping['_temp']) with self.assertRaises(SyntaxError): loader.load_module('_temp') self.assertTrue('_temp' not in sys.modules) @@ -119,7 +149,7 @@ class SimpleTest(unittest.TestCase): file.write("# test file for importlib") try: with util.uncache('_temp'): - loader = _bootstrap._SourceFileLoader('_temp', file_path) + 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), @@ -145,7 +175,7 @@ class SimpleTest(unittest.TestCase): 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']) + loader = _bootstrap.SourceFileLoader('_temp', mapping['_temp']) mod = loader.load_module('_temp') # Sanity checks. self.assertEqual(mod.__cached__, compiled) @@ -215,10 +245,17 @@ class BadBytecodeTest(unittest.TestCase): del_source=del_source) test('_temp', mapping, bc_path) + def _test_partial_size(self, test, *, del_source=False): + with source_util.create_modules('_temp') as mapping: + bc_path = self.manipulate_bytecode('_temp', mapping, + lambda bc: bc[:11], + 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], + lambda bc: bc[:12], del_source=del_source) file_path = mapping['_temp'] if not del_source else bc_path with self.assertRaises(EOFError): @@ -227,16 +264,18 @@ class BadBytecodeTest(unittest.TestCase): 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'), + lambda bc: bc[:12] + marshal.dumps(b'abcd'), del_source=del_source) file_path = mapping['_temp'] if not del_source else bytecode_path - with self.assertRaises(ImportError): + with self.assertRaises(ImportError) as cm: self.import_(file_path, '_temp') + self.assertEqual(cm.exception.name, '_temp') + self.assertEqual(cm.exception.path, bytecode_path) 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>', + lambda bc: bc[:12] + b'<test>', del_source=del_source) file_path = mapping['_temp'] if not del_source else bytecode_path with self.assertRaises(EOFError): @@ -251,7 +290,7 @@ class BadBytecodeTest(unittest.TestCase): class SourceLoaderBadBytecodeTest(BadBytecodeTest): - loader = _bootstrap._SourceFileLoader + loader = _bootstrap.SourceFileLoader @source_util.writes_bytecode_files def test_empty_file(self): @@ -260,7 +299,7 @@ class SourceLoaderBadBytecodeTest(BadBytecodeTest): 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.assertGreater(len(file.read()), 12) self._test_empty_file(test) @@ -268,7 +307,7 @@ class SourceLoaderBadBytecodeTest(BadBytecodeTest): 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.assertGreater(len(file.read()), 12) self._test_partial_magic(test) @@ -279,7 +318,7 @@ class SourceLoaderBadBytecodeTest(BadBytecodeTest): 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.assertGreater(len(file.read()), 12) self._test_magic_only(test) @@ -301,11 +340,22 @@ class SourceLoaderBadBytecodeTest(BadBytecodeTest): 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.assertGreater(len(file.read()), 12) self._test_partial_timestamp(test) @source_util.writes_bytecode_files + def test_partial_size(self): + # When the size 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()), 12) + + self._test_partial_size(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() @@ -364,19 +414,23 @@ class SourceLoaderBadBytecodeTest(BadBytecodeTest): class SourcelessLoaderBadBytecodeTest(BadBytecodeTest): - loader = _bootstrap._SourcelessFileLoader + loader = _bootstrap.SourcelessFileLoader def test_empty_file(self): def test(name, mapping, bytecode_path): - with self.assertRaises(ImportError): + with self.assertRaises(ImportError) as cm: self.import_(bytecode_path, name) + self.assertEqual(cm.exception.name, name) + self.assertEqual(cm.exception.path, bytecode_path) self._test_empty_file(test, del_source=True) def test_partial_magic(self): def test(name, mapping, bytecode_path): - with self.assertRaises(ImportError): + with self.assertRaises(ImportError) as cm: self.import_(bytecode_path, name) + self.assertEqual(cm.exception.name, name) + self.assertEqual(cm.exception.path, bytecode_path) self._test_partial_magic(test, del_source=True) def test_magic_only(self): @@ -388,8 +442,10 @@ class SourcelessLoaderBadBytecodeTest(BadBytecodeTest): def test_bad_magic(self): def test(name, mapping, bytecode_path): - with self.assertRaises(ImportError): + with self.assertRaises(ImportError) as cm: self.import_(bytecode_path, name) + self.assertEqual(cm.exception.name, name) + self.assertEqual(cm.exception.path, bytecode_path) self._test_bad_magic(test, del_source=True) @@ -400,6 +456,13 @@ class SourcelessLoaderBadBytecodeTest(BadBytecodeTest): self._test_partial_timestamp(test, del_source=True) + def test_partial_size(self): + def test(name, mapping, bytecode_path): + with self.assertRaises(EOFError): + self.import_(bytecode_path, name) + + self._test_partial_size(test, del_source=True) + def test_no_marshal(self): self._test_no_marshal(del_source=True) diff --git a/Lib/importlib/test/source/test_finder.py b/Lib/importlib/test/source/test_finder.py index 7b9088da0c..a3fa21d65e 100644 --- a/Lib/importlib/test/source/test_finder.py +++ b/Lib/importlib/test/source/test_finder.py @@ -1,10 +1,12 @@ -from importlib import _bootstrap from .. import abc from . import util as source_util -from test.support import make_legacy_pyc -import os + +from importlib import machinery import errno +import imp +import os import py_compile +from test.support import make_legacy_pyc import unittest import warnings @@ -34,9 +36,11 @@ class FinderTests(abc.FinderTests): """ def import_(self, root, module): - finder = _bootstrap._FileFinder(root, - _bootstrap._SourceFinderDetails(), - _bootstrap._SourcelessFinderDetails()) + loader_details = [(machinery.SourceFileLoader, + machinery.SOURCE_SUFFIXES, True), + (machinery.SourcelessFileLoader, + machinery.BYTECODE_SUFFIXES, True)] + finder = machinery.FileFinder(root, *loader_details) return finder.find_module(module) def run_test(self, test, create=None, *, compile_=None, unlink=None): @@ -102,39 +106,21 @@ class FinderTests(abc.FinderTests): 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()) + finder = machinery.FileFinder('', (machinery.SourceFileLoader, + machinery.SOURCE_SUFFIXES, True)) with open('mod.py', 'w') as file: file.write("# test file for importlib") try: @@ -143,6 +129,14 @@ class FinderTests(abc.FinderTests): finally: os.unlink('mod.py') + def test_invalidate_caches(self): + # invalidate_caches() should reset the mtime. + finder = machinery.FileFinder('', (machinery.SourceFileLoader, + machinery.SOURCE_SUFFIXES, True)) + finder._path_mtime = 42 + finder.invalidate_caches() + self.assertEqual(finder._path_mtime, -1) + def test_main(): from test.support import run_unittest diff --git a/Lib/importlib/test/source/test_path_hook.py b/Lib/importlib/test/source/test_path_hook.py index 374f7b6ad3..54c0699501 100644 --- a/Lib/importlib/test/source/test_path_hook.py +++ b/Lib/importlib/test/source/test_path_hook.py @@ -1,5 +1,7 @@ -from importlib import _bootstrap from . import util as source_util + +from importlib import machinery +import imp import unittest @@ -7,14 +9,18 @@ class PathHookTest(unittest.TestCase): """Test the path hook for source.""" + def path_hook(self): + return machinery.FileFinder.path_hook((machinery.SourceFileLoader, + machinery.SOURCE_SUFFIXES, True)) + def test_success(self): with source_util.create_modules('dummy') as mapping: - self.assertTrue(hasattr(_bootstrap._file_path_hook(mapping['.root']), + self.assertTrue(hasattr(self.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')) + self.assertTrue(hasattr(self.path_hook()(''), 'find_module')) def test_main(): diff --git a/Lib/importlib/test/source/test_source_encoding.py b/Lib/importlib/test/source/test_source_encoding.py index 794a3df246..0ca5195439 100644 --- a/Lib/importlib/test/source/test_source_encoding.py +++ b/Lib/importlib/test/source/test_source_encoding.py @@ -1,6 +1,6 @@ -from importlib import _bootstrap from . import util as source_util +from importlib import _bootstrap import codecs import re import sys @@ -35,8 +35,8 @@ class EncodingTest(unittest.TestCase): 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]) + loader = _bootstrap.SourceFileLoader(self.module_name, + mapping[self.module_name]) return loader.load_module(self.module_name) def create_source(self, encoding): @@ -97,7 +97,7 @@ class LineEndingTest(unittest.TestCase): 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, + loader = _bootstrap.SourceFileLoader(module_name, mapping[module_name]) return loader.load_module(module_name) diff --git a/Lib/importlib/test/test_abc.py b/Lib/importlib/test/test_abc.py index 0ecbe390ad..008bd21ff8 100644 --- a/Lib/importlib/test/test_abc.py +++ b/Lib/importlib/test/test_abc.py @@ -50,7 +50,7 @@ class InspectLoader(InheritanceTests, unittest.TestCase): superclasses = [abc.Loader] subclasses = [abc.PyLoader, machinery.BuiltinImporter, - machinery.FrozenImporter] + machinery.FrozenImporter, machinery.ExtensionFileLoader] class ExecutionLoader(InheritanceTests, unittest.TestCase): @@ -59,9 +59,16 @@ class ExecutionLoader(InheritanceTests, unittest.TestCase): subclasses = [abc.PyLoader] +class FileLoader(InheritanceTests, unittest.TestCase): + + superclasses = [abc.ResourceLoader, abc.ExecutionLoader] + subclasses = [machinery.SourceFileLoader, machinery.SourcelessFileLoader] + + class SourceLoader(InheritanceTests, unittest.TestCase): superclasses = [abc.ResourceLoader, abc.ExecutionLoader] + subclasses = [machinery.SourceFileLoader] class PyLoader(InheritanceTests, unittest.TestCase): diff --git a/Lib/importlib/test/test_api.py b/Lib/importlib/test/test_api.py index a151626de7..b7d6cb4eff 100644 --- a/Lib/importlib/test/test_api.py +++ b/Lib/importlib/test/test_api.py @@ -84,9 +84,85 @@ class ImportModuleTests(unittest.TestCase): importlib.import_module('a.b') self.assertEqual(b_load_count, 1) + +class FindLoaderTests(unittest.TestCase): + + class FakeMetaFinder: + @staticmethod + def find_module(name, path=None): return name, path + + def test_sys_modules(self): + # If a module with __loader__ is in sys.modules, then return it. + name = 'some_mod' + with util.uncache(name): + module = imp.new_module(name) + loader = 'a loader!' + module.__loader__ = loader + sys.modules[name] = module + found = importlib.find_loader(name) + self.assertEqual(loader, found) + + def test_sys_modules_loader_is_None(self): + # If sys.modules[name].__loader__ is None, raise ValueError. + name = 'some_mod' + with util.uncache(name): + module = imp.new_module(name) + module.__loader__ = None + sys.modules[name] = module + with self.assertRaises(ValueError): + importlib.find_loader(name) + + def test_success(self): + # Return the loader found on sys.meta_path. + name = 'some_mod' + with util.uncache(name): + with util.import_state(meta_path=[self.FakeMetaFinder]): + self.assertEqual((name, None), importlib.find_loader(name)) + + def test_success_path(self): + # Searching on a path should work. + name = 'some_mod' + path = 'path to some place' + with util.uncache(name): + with util.import_state(meta_path=[self.FakeMetaFinder]): + self.assertEqual((name, path), + importlib.find_loader(name, path)) + + def test_nothing(self): + # None is returned upon failure to find a loader. + self.assertIsNone(importlib.find_loader('nevergoingtofindthismodule')) + + +class InvalidateCacheTests(unittest.TestCase): + + def test_method_called(self): + # If defined the method should be called. + class InvalidatingNullFinder: + def __init__(self, *ignored): + self.called = False + def find_module(self, *args): + return None + def invalidate_caches(self): + self.called = True + + key = 'gobledeegook' + ins = InvalidatingNullFinder() + sys.path_importer_cache[key] = ins + self.addCleanup(lambda: sys.path_importer_cache.__delitem__(key)) + importlib.invalidate_caches() + self.assertTrue(ins.called) + + def test_method_lacking(self): + # There should be no issues if the method is not defined. + key = 'gobbledeegook' + sys.path_importer_cache[key] = imp.NullImporter('abc') + self.addCleanup(lambda: sys.path_importer_cache.__delitem__(key)) + importlib.invalidate_caches() # Shouldn't trigger an exception. + + def test_main(): from test.support import run_unittest - run_unittest(ImportModuleTests) + run_unittest(ImportModuleTests, FindLoaderTests, InvalidateCacheTests) if __name__ == '__main__': diff --git a/Lib/importlib/test/test_locks.py b/Lib/importlib/test/test_locks.py new file mode 100644 index 0000000000..35a72d4cad --- /dev/null +++ b/Lib/importlib/test/test_locks.py @@ -0,0 +1,115 @@ +from importlib import _bootstrap +import time +import unittest +import weakref + +from test import support + +try: + import threading +except ImportError: + threading = None +else: + from test import lock_tests + + +LockType = _bootstrap._ModuleLock +DeadlockError = _bootstrap._DeadlockError + + +if threading is not None: + class ModuleLockAsRLockTests(lock_tests.RLockTests): + locktype = staticmethod(lambda: LockType("some_lock")) + + # _is_owned() unsupported + test__is_owned = None + # acquire(blocking=False) unsupported + test_try_acquire = None + test_try_acquire_contended = None + # `with` unsupported + test_with = None + # acquire(timeout=...) unsupported + test_timeout = None + # _release_save() unsupported + test_release_save_unacquired = None + +else: + class ModuleLockAsRLockTests(unittest.TestCase): + pass + + +@unittest.skipUnless(threading, "threads needed for this test") +class DeadlockAvoidanceTests(unittest.TestCase): + + def run_deadlock_avoidance_test(self, create_deadlock): + NLOCKS = 10 + locks = [LockType(str(i)) for i in range(NLOCKS)] + pairs = [(locks[i], locks[(i+1)%NLOCKS]) for i in range(NLOCKS)] + if create_deadlock: + NTHREADS = NLOCKS + else: + NTHREADS = NLOCKS - 1 + barrier = threading.Barrier(NTHREADS) + results = [] + def _acquire(lock): + """Try to acquire the lock. Return True on success, False on deadlock.""" + try: + lock.acquire() + except DeadlockError: + return False + else: + return True + def f(): + a, b = pairs.pop() + ra = _acquire(a) + barrier.wait() + rb = _acquire(b) + results.append((ra, rb)) + if rb: + b.release() + if ra: + a.release() + lock_tests.Bunch(f, NTHREADS).wait_for_finished() + self.assertEqual(len(results), NTHREADS) + return results + + def test_deadlock(self): + results = self.run_deadlock_avoidance_test(True) + # One of the threads detected a potential deadlock on its second + # acquire() call. + self.assertEqual(results.count((True, False)), 1) + self.assertEqual(results.count((True, True)), len(results) - 1) + + def test_no_deadlock(self): + results = self.run_deadlock_avoidance_test(False) + self.assertEqual(results.count((True, False)), 0) + self.assertEqual(results.count((True, True)), len(results)) + + +class LifetimeTests(unittest.TestCase): + + def test_lock_lifetime(self): + name = "xyzzy" + self.assertNotIn(name, _bootstrap._module_locks) + lock = _bootstrap._get_module_lock(name) + self.assertIn(name, _bootstrap._module_locks) + wr = weakref.ref(lock) + del lock + support.gc_collect() + self.assertNotIn(name, _bootstrap._module_locks) + self.assertIs(wr(), None) + + def test_all_locks(self): + support.gc_collect() + self.assertEqual(0, len(_bootstrap._module_locks)) + + +@support.reap_threads +def test_main(): + support.run_unittest(ModuleLockAsRLockTests, + DeadlockAvoidanceTests, + LifetimeTests) + + +if __name__ == '__main__': + test_main() diff --git a/Lib/importlib/test/test_util.py b/Lib/importlib/test/test_util.py index 602447f09e..e477f171f0 100644 --- a/Lib/importlib/test/test_util.py +++ b/Lib/importlib/test/test_util.py @@ -59,10 +59,57 @@ class ModuleForLoaderTests(unittest.TestCase): self.raise_exception(name) self.assertIs(module, sys.modules[name]) + def test_decorator_attrs(self): + def fxn(self, module): pass + wrapped = util.module_for_loader(fxn) + self.assertEqual(wrapped.__name__, fxn.__name__) + self.assertEqual(wrapped.__qualname__, fxn.__qualname__) + + def test_false_module(self): + # If for some odd reason a module is considered false, still return it + # from sys.modules. + class FalseModule(types.ModuleType): + def __bool__(self): return False + + name = 'mod' + module = FalseModule(name) + with test_util.uncache(name): + self.assertFalse(module) + sys.modules[name] = module + given = self.return_module(name) + self.assertTrue(given is module) + + def test_attributes_set(self): + # __name__, __loader__, and __package__ should be set (when + # is_package() is defined; undefined implicitly tested elsewhere). + class FakeLoader: + def __init__(self, is_package): + self._pkg = is_package + def is_package(self, name): + return self._pkg + @util.module_for_loader + def load_module(self, module): + return module + + name = 'pkg.mod' + with test_util.uncache(name): + loader = FakeLoader(False) + module = loader.load_module(name) + self.assertEqual(module.__name__, name) + self.assertIs(module.__loader__, loader) + self.assertEqual(module.__package__, 'pkg') -class SetPackageTests(unittest.TestCase): + name = 'pkg.sub' + with test_util.uncache(name): + loader = FakeLoader(True) + module = loader.load_module(name) + self.assertEqual(module.__name__, name) + self.assertIs(module.__loader__, loader) + self.assertEqual(module.__package__, name) +class SetPackageTests(unittest.TestCase): + """Tests for importlib.util.set_package.""" def verify(self, module, expect): @@ -108,10 +155,53 @@ class SetPackageTests(unittest.TestCase): module.__package__ = value self.verify(module, value) + def test_decorator_attrs(self): + def fxn(module): pass + wrapped = util.set_package(fxn) + self.assertEqual(wrapped.__name__, fxn.__name__) + self.assertEqual(wrapped.__qualname__, fxn.__qualname__) + + +class ResolveNameTests(unittest.TestCase): + + """Tests importlib.util.resolve_name().""" + + def test_absolute(self): + # bacon + self.assertEqual('bacon', util.resolve_name('bacon', None)) + + def test_aboslute_within_package(self): + # bacon in spam + self.assertEqual('bacon', util.resolve_name('bacon', 'spam')) + + def test_no_package(self): + # .bacon in '' + with self.assertRaises(ValueError): + util.resolve_name('.bacon', '') + + def test_in_package(self): + # .bacon in spam + self.assertEqual('spam.eggs.bacon', + util.resolve_name('.bacon', 'spam.eggs')) + + def test_other_package(self): + # ..bacon in spam.bacon + self.assertEqual('spam.bacon', + util.resolve_name('..bacon', 'spam.eggs')) + + def test_escape(self): + # ..bacon in spam + with self.assertRaises(ValueError): + util.resolve_name('..bacon', 'spam') + def test_main(): from test import support - support.run_unittest(ModuleForLoaderTests, SetPackageTests) + support.run_unittest( + ModuleForLoaderTests, + SetPackageTests, + ResolveNameTests + ) if __name__ == '__main__': diff --git a/Lib/importlib/test/util.py b/Lib/importlib/test/util.py index 93b7cd2861..ef32f7d690 100644 --- a/Lib/importlib/test/util.py +++ b/Lib/importlib/test/util.py @@ -35,7 +35,7 @@ def uncache(*names): for name in names: if name in ('sys', 'marshal', 'imp'): raise ValueError( - "cannot uncache {0} as it will break _importlib".format(name)) + "cannot uncache {0}".format(name)) try: del sys.modules[name] except KeyError: @@ -124,7 +124,11 @@ class mock_modules: else: sys.modules[fullname] = self.modules[fullname] if fullname in self.module_code: - self.module_code[fullname]() + try: + self.module_code[fullname]() + except Exception: + del sys.modules[fullname] + raise return self.modules[fullname] def __enter__(self): |