438 lines
12 KiB
Python
438 lines
12 KiB
Python
|
import os
|
||
|
import errno
|
||
|
import importlib.machinery
|
||
|
import py_compile
|
||
|
import shutil
|
||
|
import unittest
|
||
|
import tempfile
|
||
|
|
||
|
from test import support
|
||
|
|
||
|
import modulefinder
|
||
|
|
||
|
TEST_DIR = tempfile.mkdtemp()
|
||
|
TEST_PATH = [TEST_DIR, os.path.dirname(tempfile.__file__)]
|
||
|
|
||
|
# Each test description is a list of 5 items:
|
||
|
#
|
||
|
# 1. a module name that will be imported by modulefinder
|
||
|
# 2. a list of module names that modulefinder is required to find
|
||
|
# 3. a list of module names that modulefinder should complain
|
||
|
# about because they are not found
|
||
|
# 4. a list of module names that modulefinder should complain
|
||
|
# about because they MAY be not found
|
||
|
# 5. a string specifying packages to create; the format is obvious imo.
|
||
|
#
|
||
|
# Each package will be created in TEST_DIR, and TEST_DIR will be
|
||
|
# removed after the tests again.
|
||
|
# Modulefinder searches in a path that contains TEST_DIR, plus
|
||
|
# the standard Lib directory.
|
||
|
|
||
|
maybe_test = [
|
||
|
"a.module",
|
||
|
["a", "a.module", "sys",
|
||
|
"b"],
|
||
|
["c"], ["b.something"],
|
||
|
"""\
|
||
|
a/__init__.py
|
||
|
a/module.py
|
||
|
from b import something
|
||
|
from c import something
|
||
|
b/__init__.py
|
||
|
from sys import *
|
||
|
""",
|
||
|
]
|
||
|
|
||
|
maybe_test_new = [
|
||
|
"a.module",
|
||
|
["a", "a.module", "sys",
|
||
|
"b", "__future__"],
|
||
|
["c"], ["b.something"],
|
||
|
"""\
|
||
|
a/__init__.py
|
||
|
a/module.py
|
||
|
from b import something
|
||
|
from c import something
|
||
|
b/__init__.py
|
||
|
from __future__ import absolute_import
|
||
|
from sys import *
|
||
|
"""]
|
||
|
|
||
|
package_test = [
|
||
|
"a.module",
|
||
|
["a", "a.b", "a.c", "a.module", "mymodule", "sys"],
|
||
|
["blahblah", "c"], [],
|
||
|
"""\
|
||
|
mymodule.py
|
||
|
a/__init__.py
|
||
|
import blahblah
|
||
|
from a import b
|
||
|
import c
|
||
|
a/module.py
|
||
|
import sys
|
||
|
from a import b as x
|
||
|
from a.c import sillyname
|
||
|
a/b.py
|
||
|
a/c.py
|
||
|
from a.module import x
|
||
|
import mymodule as sillyname
|
||
|
from sys import version_info
|
||
|
"""]
|
||
|
|
||
|
absolute_import_test = [
|
||
|
"a.module",
|
||
|
["a", "a.module",
|
||
|
"b", "b.x", "b.y", "b.z",
|
||
|
"__future__", "sys", "gc"],
|
||
|
["blahblah", "z"], [],
|
||
|
"""\
|
||
|
mymodule.py
|
||
|
a/__init__.py
|
||
|
a/module.py
|
||
|
from __future__ import absolute_import
|
||
|
import sys # sys
|
||
|
import blahblah # fails
|
||
|
import gc # gc
|
||
|
import b.x # b.x
|
||
|
from b import y # b.y
|
||
|
from b.z import * # b.z.*
|
||
|
a/gc.py
|
||
|
a/sys.py
|
||
|
import mymodule
|
||
|
a/b/__init__.py
|
||
|
a/b/x.py
|
||
|
a/b/y.py
|
||
|
a/b/z.py
|
||
|
b/__init__.py
|
||
|
import z
|
||
|
b/unused.py
|
||
|
b/x.py
|
||
|
b/y.py
|
||
|
b/z.py
|
||
|
"""]
|
||
|
|
||
|
relative_import_test = [
|
||
|
"a.module",
|
||
|
["__future__",
|
||
|
"a", "a.module",
|
||
|
"a.b", "a.b.y", "a.b.z",
|
||
|
"a.b.c", "a.b.c.moduleC",
|
||
|
"a.b.c.d", "a.b.c.e",
|
||
|
"a.b.x",
|
||
|
"gc"],
|
||
|
[], [],
|
||
|
"""\
|
||
|
mymodule.py
|
||
|
a/__init__.py
|
||
|
from .b import y, z # a.b.y, a.b.z
|
||
|
a/module.py
|
||
|
from __future__ import absolute_import # __future__
|
||
|
import gc # gc
|
||
|
a/gc.py
|
||
|
a/sys.py
|
||
|
a/b/__init__.py
|
||
|
from ..b import x # a.b.x
|
||
|
#from a.b.c import moduleC
|
||
|
from .c import moduleC # a.b.moduleC
|
||
|
a/b/x.py
|
||
|
a/b/y.py
|
||
|
a/b/z.py
|
||
|
a/b/g.py
|
||
|
a/b/c/__init__.py
|
||
|
from ..c import e # a.b.c.e
|
||
|
a/b/c/moduleC.py
|
||
|
from ..c import d # a.b.c.d
|
||
|
a/b/c/d.py
|
||
|
a/b/c/e.py
|
||
|
a/b/c/x.py
|
||
|
"""]
|
||
|
|
||
|
relative_import_test_2 = [
|
||
|
"a.module",
|
||
|
["a", "a.module",
|
||
|
"a.sys",
|
||
|
"a.b", "a.b.y", "a.b.z",
|
||
|
"a.b.c", "a.b.c.d",
|
||
|
"a.b.c.e",
|
||
|
"a.b.c.moduleC",
|
||
|
"a.b.c.f",
|
||
|
"a.b.x",
|
||
|
"a.another"],
|
||
|
[], [],
|
||
|
"""\
|
||
|
mymodule.py
|
||
|
a/__init__.py
|
||
|
from . import sys # a.sys
|
||
|
a/another.py
|
||
|
a/module.py
|
||
|
from .b import y, z # a.b.y, a.b.z
|
||
|
a/gc.py
|
||
|
a/sys.py
|
||
|
a/b/__init__.py
|
||
|
from .c import moduleC # a.b.c.moduleC
|
||
|
from .c import d # a.b.c.d
|
||
|
a/b/x.py
|
||
|
a/b/y.py
|
||
|
a/b/z.py
|
||
|
a/b/c/__init__.py
|
||
|
from . import e # a.b.c.e
|
||
|
a/b/c/moduleC.py
|
||
|
#
|
||
|
from . import f # a.b.c.f
|
||
|
from .. import x # a.b.x
|
||
|
from ... import another # a.another
|
||
|
a/b/c/d.py
|
||
|
a/b/c/e.py
|
||
|
a/b/c/f.py
|
||
|
"""]
|
||
|
|
||
|
relative_import_test_3 = [
|
||
|
"a.module",
|
||
|
["a", "a.module"],
|
||
|
["a.bar"],
|
||
|
[],
|
||
|
"""\
|
||
|
a/__init__.py
|
||
|
def foo(): pass
|
||
|
a/module.py
|
||
|
from . import foo
|
||
|
from . import bar
|
||
|
"""]
|
||
|
|
||
|
relative_import_test_4 = [
|
||
|
"a.module",
|
||
|
["a", "a.module"],
|
||
|
[],
|
||
|
[],
|
||
|
"""\
|
||
|
a/__init__.py
|
||
|
def foo(): pass
|
||
|
a/module.py
|
||
|
from . import *
|
||
|
"""]
|
||
|
|
||
|
bytecode_test = [
|
||
|
"a",
|
||
|
["a"],
|
||
|
[],
|
||
|
[],
|
||
|
""
|
||
|
]
|
||
|
|
||
|
syntax_error_test = [
|
||
|
"a.module",
|
||
|
["a", "a.module", "b"],
|
||
|
["b.module"], [],
|
||
|
"""\
|
||
|
a/__init__.py
|
||
|
a/module.py
|
||
|
import b.module
|
||
|
b/__init__.py
|
||
|
b/module.py
|
||
|
? # SyntaxError: invalid syntax
|
||
|
"""]
|
||
|
|
||
|
|
||
|
same_name_as_bad_test = [
|
||
|
"a.module",
|
||
|
["a", "a.module", "b", "b.c"],
|
||
|
["c"], [],
|
||
|
"""\
|
||
|
a/__init__.py
|
||
|
a/module.py
|
||
|
import c
|
||
|
from b import c
|
||
|
b/__init__.py
|
||
|
b/c.py
|
||
|
"""]
|
||
|
|
||
|
coding_default_utf8_test = [
|
||
|
"a_utf8",
|
||
|
["a_utf8", "b_utf8"],
|
||
|
[], [],
|
||
|
"""\
|
||
|
a_utf8.py
|
||
|
# use the default of utf8
|
||
|
print('Unicode test A code point 2090 \u2090 that is not valid in cp1252')
|
||
|
import b_utf8
|
||
|
b_utf8.py
|
||
|
# use the default of utf8
|
||
|
print('Unicode test B code point 2090 \u2090 that is not valid in cp1252')
|
||
|
"""]
|
||
|
|
||
|
coding_explicit_utf8_test = [
|
||
|
"a_utf8",
|
||
|
["a_utf8", "b_utf8"],
|
||
|
[], [],
|
||
|
"""\
|
||
|
a_utf8.py
|
||
|
# coding=utf8
|
||
|
print('Unicode test A code point 2090 \u2090 that is not valid in cp1252')
|
||
|
import b_utf8
|
||
|
b_utf8.py
|
||
|
# use the default of utf8
|
||
|
print('Unicode test B code point 2090 \u2090 that is not valid in cp1252')
|
||
|
"""]
|
||
|
|
||
|
coding_explicit_cp1252_test = [
|
||
|
"a_cp1252",
|
||
|
["a_cp1252", "b_utf8"],
|
||
|
[], [],
|
||
|
b"""\
|
||
|
a_cp1252.py
|
||
|
# coding=cp1252
|
||
|
# 0xe2 is not allowed in utf8
|
||
|
print('CP1252 test P\xe2t\xe9')
|
||
|
import b_utf8
|
||
|
""" + """\
|
||
|
b_utf8.py
|
||
|
# use the default of utf8
|
||
|
print('Unicode test A code point 2090 \u2090 that is not valid in cp1252')
|
||
|
""".encode('utf-8')]
|
||
|
|
||
|
def open_file(path):
|
||
|
dirname = os.path.dirname(path)
|
||
|
try:
|
||
|
os.makedirs(dirname)
|
||
|
except OSError as e:
|
||
|
if e.errno != errno.EEXIST:
|
||
|
raise
|
||
|
return open(path, 'wb')
|
||
|
|
||
|
|
||
|
def create_package(source):
|
||
|
ofi = None
|
||
|
try:
|
||
|
for line in source.splitlines():
|
||
|
if type(line) != bytes:
|
||
|
line = line.encode('utf-8')
|
||
|
if line.startswith(b' ') or line.startswith(b'\t'):
|
||
|
ofi.write(line.strip() + b'\n')
|
||
|
else:
|
||
|
if ofi:
|
||
|
ofi.close()
|
||
|
if type(line) == bytes:
|
||
|
line = line.decode('utf-8')
|
||
|
ofi = open_file(os.path.join(TEST_DIR, line.strip()))
|
||
|
finally:
|
||
|
if ofi:
|
||
|
ofi.close()
|
||
|
|
||
|
class ModuleFinderTest(unittest.TestCase):
|
||
|
def _do_test(self, info, report=False, debug=0, replace_paths=[], modulefinder_class=modulefinder.ModuleFinder):
|
||
|
import_this, modules, missing, maybe_missing, source = info
|
||
|
create_package(source)
|
||
|
try:
|
||
|
mf = modulefinder_class(path=TEST_PATH, debug=debug,
|
||
|
replace_paths=replace_paths)
|
||
|
mf.import_hook(import_this)
|
||
|
if report:
|
||
|
mf.report()
|
||
|
## # This wouldn't work in general when executed several times:
|
||
|
## opath = sys.path[:]
|
||
|
## sys.path = TEST_PATH
|
||
|
## try:
|
||
|
## __import__(import_this)
|
||
|
## except:
|
||
|
## import traceback; traceback.print_exc()
|
||
|
## sys.path = opath
|
||
|
## return
|
||
|
modules = sorted(set(modules))
|
||
|
found = sorted(mf.modules)
|
||
|
# check if we found what we expected, not more, not less
|
||
|
self.assertEqual(found, modules)
|
||
|
|
||
|
# check for missing and maybe missing modules
|
||
|
bad, maybe = mf.any_missing_maybe()
|
||
|
self.assertEqual(bad, missing)
|
||
|
self.assertEqual(maybe, maybe_missing)
|
||
|
finally:
|
||
|
shutil.rmtree(TEST_DIR)
|
||
|
|
||
|
def test_package(self):
|
||
|
self._do_test(package_test)
|
||
|
|
||
|
def test_maybe(self):
|
||
|
self._do_test(maybe_test)
|
||
|
|
||
|
def test_maybe_new(self):
|
||
|
self._do_test(maybe_test_new)
|
||
|
|
||
|
def test_absolute_imports(self):
|
||
|
self._do_test(absolute_import_test)
|
||
|
|
||
|
def test_relative_imports(self):
|
||
|
self._do_test(relative_import_test)
|
||
|
|
||
|
def test_relative_imports_2(self):
|
||
|
self._do_test(relative_import_test_2)
|
||
|
|
||
|
def test_relative_imports_3(self):
|
||
|
self._do_test(relative_import_test_3)
|
||
|
|
||
|
def test_relative_imports_4(self):
|
||
|
self._do_test(relative_import_test_4)
|
||
|
|
||
|
def test_syntax_error(self):
|
||
|
self._do_test(syntax_error_test)
|
||
|
|
||
|
def test_same_name_as_bad(self):
|
||
|
self._do_test(same_name_as_bad_test)
|
||
|
|
||
|
def test_bytecode(self):
|
||
|
base_path = os.path.join(TEST_DIR, 'a')
|
||
|
source_path = base_path + importlib.machinery.SOURCE_SUFFIXES[0]
|
||
|
bytecode_path = base_path + importlib.machinery.BYTECODE_SUFFIXES[0]
|
||
|
with open_file(source_path) as file:
|
||
|
file.write('testing_modulefinder = True\n'.encode('utf-8'))
|
||
|
py_compile.compile(source_path, cfile=bytecode_path)
|
||
|
os.remove(source_path)
|
||
|
self._do_test(bytecode_test)
|
||
|
|
||
|
def test_replace_paths(self):
|
||
|
old_path = os.path.join(TEST_DIR, 'a', 'module.py')
|
||
|
new_path = os.path.join(TEST_DIR, 'a', 'spam.py')
|
||
|
with support.captured_stdout() as output:
|
||
|
self._do_test(maybe_test, debug=2,
|
||
|
replace_paths=[(old_path, new_path)])
|
||
|
output = output.getvalue()
|
||
|
expected = "co_filename %r changed to %r" % (old_path, new_path)
|
||
|
self.assertIn(expected, output)
|
||
|
|
||
|
def test_extended_opargs(self):
|
||
|
extended_opargs_test = [
|
||
|
"a",
|
||
|
["a", "b"],
|
||
|
[], [],
|
||
|
"""\
|
||
|
a.py
|
||
|
%r
|
||
|
import b
|
||
|
b.py
|
||
|
""" % list(range(2**16))] # 2**16 constants
|
||
|
self._do_test(extended_opargs_test)
|
||
|
|
||
|
def test_coding_default_utf8(self):
|
||
|
self._do_test(coding_default_utf8_test)
|
||
|
|
||
|
def test_coding_explicit_utf8(self):
|
||
|
self._do_test(coding_explicit_utf8_test)
|
||
|
|
||
|
def test_coding_explicit_cp1252(self):
|
||
|
self._do_test(coding_explicit_cp1252_test)
|
||
|
|
||
|
def test_load_module_api(self):
|
||
|
class CheckLoadModuleApi(modulefinder.ModuleFinder):
|
||
|
def __init__(self, *args, **kwds):
|
||
|
super().__init__(*args, **kwds)
|
||
|
|
||
|
def load_module(self, fqname, fp, pathname, file_info):
|
||
|
# confirm that the fileinfo is a tuple of 3 elements
|
||
|
suffix, mode, type = file_info
|
||
|
return super().load_module(fqname, fp, pathname, file_info)
|
||
|
|
||
|
self._do_test(absolute_import_test, modulefinder_class=CheckLoadModuleApi)
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
unittest.main()
|