1import os 2import errno 3import importlib.machinery 4import py_compile 5import shutil 6import unittest 7import tempfile 8 9from test import support 10 11import modulefinder 12 13TEST_DIR = tempfile.mkdtemp() 14TEST_PATH = [TEST_DIR, os.path.dirname(tempfile.__file__)] 15 16# Each test description is a list of 5 items: 17# 18# 1. a module name that will be imported by modulefinder 19# 2. a list of module names that modulefinder is required to find 20# 3. a list of module names that modulefinder should complain 21# about because they are not found 22# 4. a list of module names that modulefinder should complain 23# about because they MAY be not found 24# 5. a string specifying packages to create; the format is obvious imo. 25# 26# Each package will be created in TEST_DIR, and TEST_DIR will be 27# removed after the tests again. 28# Modulefinder searches in a path that contains TEST_DIR, plus 29# the standard Lib directory. 30 31maybe_test = [ 32 "a.module", 33 ["a", "a.module", "sys", 34 "b"], 35 ["c"], ["b.something"], 36 """\ 37a/__init__.py 38a/module.py 39 from b import something 40 from c import something 41b/__init__.py 42 from sys import * 43""", 44] 45 46maybe_test_new = [ 47 "a.module", 48 ["a", "a.module", "sys", 49 "b", "__future__"], 50 ["c"], ["b.something"], 51 """\ 52a/__init__.py 53a/module.py 54 from b import something 55 from c import something 56b/__init__.py 57 from __future__ import absolute_import 58 from sys import * 59"""] 60 61package_test = [ 62 "a.module", 63 ["a", "a.b", "a.c", "a.module", "mymodule", "sys"], 64 ["blahblah", "c"], [], 65 """\ 66mymodule.py 67a/__init__.py 68 import blahblah 69 from a import b 70 import c 71a/module.py 72 import sys 73 from a import b as x 74 from a.c import sillyname 75a/b.py 76a/c.py 77 from a.module import x 78 import mymodule as sillyname 79 from sys import version_info 80"""] 81 82absolute_import_test = [ 83 "a.module", 84 ["a", "a.module", 85 "b", "b.x", "b.y", "b.z", 86 "__future__", "sys", "gc"], 87 ["blahblah", "z"], [], 88 """\ 89mymodule.py 90a/__init__.py 91a/module.py 92 from __future__ import absolute_import 93 import sys # sys 94 import blahblah # fails 95 import gc # gc 96 import b.x # b.x 97 from b import y # b.y 98 from b.z import * # b.z.* 99a/gc.py 100a/sys.py 101 import mymodule 102a/b/__init__.py 103a/b/x.py 104a/b/y.py 105a/b/z.py 106b/__init__.py 107 import z 108b/unused.py 109b/x.py 110b/y.py 111b/z.py 112"""] 113 114relative_import_test = [ 115 "a.module", 116 ["__future__", 117 "a", "a.module", 118 "a.b", "a.b.y", "a.b.z", 119 "a.b.c", "a.b.c.moduleC", 120 "a.b.c.d", "a.b.c.e", 121 "a.b.x", 122 "gc"], 123 [], [], 124 """\ 125mymodule.py 126a/__init__.py 127 from .b import y, z # a.b.y, a.b.z 128a/module.py 129 from __future__ import absolute_import # __future__ 130 import gc # gc 131a/gc.py 132a/sys.py 133a/b/__init__.py 134 from ..b import x # a.b.x 135 #from a.b.c import moduleC 136 from .c import moduleC # a.b.moduleC 137a/b/x.py 138a/b/y.py 139a/b/z.py 140a/b/g.py 141a/b/c/__init__.py 142 from ..c import e # a.b.c.e 143a/b/c/moduleC.py 144 from ..c import d # a.b.c.d 145a/b/c/d.py 146a/b/c/e.py 147a/b/c/x.py 148"""] 149 150relative_import_test_2 = [ 151 "a.module", 152 ["a", "a.module", 153 "a.sys", 154 "a.b", "a.b.y", "a.b.z", 155 "a.b.c", "a.b.c.d", 156 "a.b.c.e", 157 "a.b.c.moduleC", 158 "a.b.c.f", 159 "a.b.x", 160 "a.another"], 161 [], [], 162 """\ 163mymodule.py 164a/__init__.py 165 from . import sys # a.sys 166a/another.py 167a/module.py 168 from .b import y, z # a.b.y, a.b.z 169a/gc.py 170a/sys.py 171a/b/__init__.py 172 from .c import moduleC # a.b.c.moduleC 173 from .c import d # a.b.c.d 174a/b/x.py 175a/b/y.py 176a/b/z.py 177a/b/c/__init__.py 178 from . import e # a.b.c.e 179a/b/c/moduleC.py 180 # 181 from . import f # a.b.c.f 182 from .. import x # a.b.x 183 from ... import another # a.another 184a/b/c/d.py 185a/b/c/e.py 186a/b/c/f.py 187"""] 188 189relative_import_test_3 = [ 190 "a.module", 191 ["a", "a.module"], 192 ["a.bar"], 193 [], 194 """\ 195a/__init__.py 196 def foo(): pass 197a/module.py 198 from . import foo 199 from . import bar 200"""] 201 202relative_import_test_4 = [ 203 "a.module", 204 ["a", "a.module"], 205 [], 206 [], 207 """\ 208a/__init__.py 209 def foo(): pass 210a/module.py 211 from . import * 212"""] 213 214bytecode_test = [ 215 "a", 216 ["a"], 217 [], 218 [], 219 "" 220] 221 222syntax_error_test = [ 223 "a.module", 224 ["a", "a.module", "b"], 225 ["b.module"], [], 226 """\ 227a/__init__.py 228a/module.py 229 import b.module 230b/__init__.py 231b/module.py 232 ? # SyntaxError: invalid syntax 233"""] 234 235 236same_name_as_bad_test = [ 237 "a.module", 238 ["a", "a.module", "b", "b.c"], 239 ["c"], [], 240 """\ 241a/__init__.py 242a/module.py 243 import c 244 from b import c 245b/__init__.py 246b/c.py 247"""] 248 249coding_default_utf8_test = [ 250 "a_utf8", 251 ["a_utf8", "b_utf8"], 252 [], [], 253 """\ 254a_utf8.py 255 # use the default of utf8 256 print('Unicode test A code point 2090 \u2090 that is not valid in cp1252') 257 import b_utf8 258b_utf8.py 259 # use the default of utf8 260 print('Unicode test B code point 2090 \u2090 that is not valid in cp1252') 261"""] 262 263coding_explicit_utf8_test = [ 264 "a_utf8", 265 ["a_utf8", "b_utf8"], 266 [], [], 267 """\ 268a_utf8.py 269 # coding=utf8 270 print('Unicode test A code point 2090 \u2090 that is not valid in cp1252') 271 import b_utf8 272b_utf8.py 273 # use the default of utf8 274 print('Unicode test B code point 2090 \u2090 that is not valid in cp1252') 275"""] 276 277coding_explicit_cp1252_test = [ 278 "a_cp1252", 279 ["a_cp1252", "b_utf8"], 280 [], [], 281 b"""\ 282a_cp1252.py 283 # coding=cp1252 284 # 0xe2 is not allowed in utf8 285 print('CP1252 test P\xe2t\xe9') 286 import b_utf8 287""" + """\ 288b_utf8.py 289 # use the default of utf8 290 print('Unicode test A code point 2090 \u2090 that is not valid in cp1252') 291""".encode('utf-8')] 292 293def open_file(path): 294 dirname = os.path.dirname(path) 295 try: 296 os.makedirs(dirname) 297 except OSError as e: 298 if e.errno != errno.EEXIST: 299 raise 300 return open(path, 'wb') 301 302 303def create_package(source): 304 ofi = None 305 try: 306 for line in source.splitlines(): 307 if type(line) != bytes: 308 line = line.encode('utf-8') 309 if line.startswith(b' ') or line.startswith(b'\t'): 310 ofi.write(line.strip() + b'\n') 311 else: 312 if ofi: 313 ofi.close() 314 if type(line) == bytes: 315 line = line.decode('utf-8') 316 ofi = open_file(os.path.join(TEST_DIR, line.strip())) 317 finally: 318 if ofi: 319 ofi.close() 320 321class ModuleFinderTest(unittest.TestCase): 322 def _do_test(self, info, report=False, debug=0, replace_paths=[], modulefinder_class=modulefinder.ModuleFinder): 323 import_this, modules, missing, maybe_missing, source = info 324 create_package(source) 325 try: 326 mf = modulefinder_class(path=TEST_PATH, debug=debug, 327 replace_paths=replace_paths) 328 mf.import_hook(import_this) 329 if report: 330 mf.report() 331## # This wouldn't work in general when executed several times: 332## opath = sys.path[:] 333## sys.path = TEST_PATH 334## try: 335## __import__(import_this) 336## except: 337## import traceback; traceback.print_exc() 338## sys.path = opath 339## return 340 modules = sorted(set(modules)) 341 found = sorted(mf.modules) 342 # check if we found what we expected, not more, not less 343 self.assertEqual(found, modules) 344 345 # check for missing and maybe missing modules 346 bad, maybe = mf.any_missing_maybe() 347 self.assertEqual(bad, missing) 348 self.assertEqual(maybe, maybe_missing) 349 finally: 350 shutil.rmtree(TEST_DIR) 351 352 def test_package(self): 353 self._do_test(package_test) 354 355 def test_maybe(self): 356 self._do_test(maybe_test) 357 358 def test_maybe_new(self): 359 self._do_test(maybe_test_new) 360 361 def test_absolute_imports(self): 362 self._do_test(absolute_import_test) 363 364 def test_relative_imports(self): 365 self._do_test(relative_import_test) 366 367 def test_relative_imports_2(self): 368 self._do_test(relative_import_test_2) 369 370 def test_relative_imports_3(self): 371 self._do_test(relative_import_test_3) 372 373 def test_relative_imports_4(self): 374 self._do_test(relative_import_test_4) 375 376 def test_syntax_error(self): 377 self._do_test(syntax_error_test) 378 379 def test_same_name_as_bad(self): 380 self._do_test(same_name_as_bad_test) 381 382 def test_bytecode(self): 383 base_path = os.path.join(TEST_DIR, 'a') 384 source_path = base_path + importlib.machinery.SOURCE_SUFFIXES[0] 385 bytecode_path = base_path + importlib.machinery.BYTECODE_SUFFIXES[0] 386 with open_file(source_path) as file: 387 file.write('testing_modulefinder = True\n'.encode('utf-8')) 388 py_compile.compile(source_path, cfile=bytecode_path) 389 os.remove(source_path) 390 self._do_test(bytecode_test) 391 392 def test_replace_paths(self): 393 old_path = os.path.join(TEST_DIR, 'a', 'module.py') 394 new_path = os.path.join(TEST_DIR, 'a', 'spam.py') 395 with support.captured_stdout() as output: 396 self._do_test(maybe_test, debug=2, 397 replace_paths=[(old_path, new_path)]) 398 output = output.getvalue() 399 expected = "co_filename %r changed to %r" % (old_path, new_path) 400 self.assertIn(expected, output) 401 402 def test_extended_opargs(self): 403 extended_opargs_test = [ 404 "a", 405 ["a", "b"], 406 [], [], 407 """\ 408a.py 409 %r 410 import b 411b.py 412""" % list(range(2**16))] # 2**16 constants 413 self._do_test(extended_opargs_test) 414 415 def test_coding_default_utf8(self): 416 self._do_test(coding_default_utf8_test) 417 418 def test_coding_explicit_utf8(self): 419 self._do_test(coding_explicit_utf8_test) 420 421 def test_coding_explicit_cp1252(self): 422 self._do_test(coding_explicit_cp1252_test) 423 424 def test_load_module_api(self): 425 class CheckLoadModuleApi(modulefinder.ModuleFinder): 426 def __init__(self, *args, **kwds): 427 super().__init__(*args, **kwds) 428 429 def load_module(self, fqname, fp, pathname, file_info): 430 # confirm that the fileinfo is a tuple of 3 elements 431 suffix, mode, type = file_info 432 return super().load_module(fqname, fp, pathname, file_info) 433 434 self._do_test(absolute_import_test, modulefinder_class=CheckLoadModuleApi) 435 436if __name__ == "__main__": 437 unittest.main() 438