|
1 | 1 | """ Tests for the linecache module """ |
2 | 2 |
|
| 3 | +import importlib |
3 | 4 | import linecache |
4 | 5 | import unittest |
5 | 6 | import os.path |
| 7 | +import sys |
6 | 8 | import tempfile |
7 | 9 | import threading |
8 | 10 | import tokenize |
| 11 | +import zipfile |
| 12 | +import zipimport |
9 | 13 | from importlib.machinery import ModuleSpec |
10 | 14 | from test import support |
| 15 | +from test.support import import_helper |
11 | 16 | from test.support import os_helper |
12 | 17 | from test.support import threading_helper |
13 | 18 | from test.support.script_helper import assert_python_ok |
@@ -356,6 +361,17 @@ def test_linecache_python_string(self): |
356 | 361 | self.assertEqual(stdout, b'') |
357 | 362 | self.assertEqual(stderr, b'') |
358 | 363 |
|
| 364 | + def test_path_importer_cache_None(self): |
| 365 | + # sys.path_importer_cache is set to None while the interpreter is |
| 366 | + # shutting down, before objects with a __del__ that may end up here |
| 367 | + # are released. |
| 368 | + filename = os.path.abspath(os_helper.TESTFN + '.py') |
| 369 | + with support.swap_attr(sys, 'path_importer_cache', None): |
| 370 | + self.assertEqual(linecache.getlines(filename), []) |
| 371 | + self.assertEqual(linecache.getline(filename, 1), '') |
| 372 | + self.assertNotIn(filename, linecache.cache) |
| 373 | + |
| 374 | + |
359 | 375 | class LineCacheInvalidationTests(unittest.TestCase): |
360 | 376 | def setUp(self): |
361 | 377 | super().setUp() |
@@ -398,6 +414,98 @@ def test_checkcache_with_no_parameter(self): |
398 | 414 | self.assertIn(self.unchanged_file, linecache.cache) |
399 | 415 |
|
400 | 416 |
|
| 417 | +class ZipArchiveTests(unittest.TestCase): |
| 418 | + """Sources of modules imported from a zip archive on sys.path.""" |
| 419 | + |
| 420 | + MODULE_SOURCE = ( |
| 421 | + '"""A module inside a zip archive."""\n' |
| 422 | + '\n' |
| 423 | + 'def f():\n' |
| 424 | + ' return "from the zip"\n' |
| 425 | + ) |
| 426 | + PACKAGE_SOURCE = 'value = 42\n' |
| 427 | + LATIN1_SOURCE = ( |
| 428 | + '# -*- coding: latin-1 -*-\n' |
| 429 | + 'value = "caf\xe9"\n' |
| 430 | + ) |
| 431 | + |
| 432 | + def setUp(self): |
| 433 | + linecache.clearcache() |
| 434 | + self.addCleanup(linecache.clearcache) |
| 435 | + tmpdir = self.enterContext(os_helper.temp_dir()) |
| 436 | + self.zip_name = os.path.join(tmpdir, 'sources.zip') |
| 437 | + with zipfile.ZipFile(self.zip_name, 'w') as zf: |
| 438 | + zf.writestr('zipmod.py', self.MODULE_SOURCE) |
| 439 | + zf.writestr('zippkg/__init__.py', self.PACKAGE_SOURCE) |
| 440 | + zf.writestr('ziplatin1.py', self.LATIN1_SOURCE.encode('latin-1')) |
| 441 | + self.enterContext(import_helper.DirsOnSysPath(self.zip_name)) |
| 442 | + for name in 'zipmod', 'zippkg', 'ziplatin1': |
| 443 | + self.addCleanup(import_helper.unload, name) |
| 444 | + self.addCleanup(sys.path_importer_cache.pop, self.zip_name, None) |
| 445 | + self.addCleanup(zipimport._zip_directory_cache.pop, |
| 446 | + self.zip_name, None) |
| 447 | + self.zipmod = importlib.import_module('zipmod') |
| 448 | + |
| 449 | + def test_getlines_without_module_globals(self): |
| 450 | + filename = self.zipmod.__file__ |
| 451 | + self.assertEqual(filename, os.path.join(self.zip_name, 'zipmod.py')) |
| 452 | + self.assertFalse(os.path.exists(filename)) |
| 453 | + lines = self.MODULE_SOURCE.splitlines(keepends=True) |
| 454 | + self.assertEqual(linecache.getlines(filename), lines) |
| 455 | + self.assertEqual(linecache.getline(filename, 4), |
| 456 | + ' return "from the zip"\n') |
| 457 | + self.assertEqual(linecache.getline(filename, 5), '') |
| 458 | + code = self.zipmod.f.__code__ |
| 459 | + self.assertEqual(code.co_filename, filename) |
| 460 | + self.assertEqual(linecache.getline(filename, code.co_firstlineno), |
| 461 | + 'def f():\n') |
| 462 | + |
| 463 | + def test_relative_archive_path(self): |
| 464 | + # A relative sys.path entry gives its modules a relative __file__. |
| 465 | + tmpdir, zip_base = os.path.split(self.zip_name) |
| 466 | + self.addCleanup(sys.path_importer_cache.pop, zip_base, None) |
| 467 | + self.addCleanup(zipimport._zip_directory_cache.pop, zip_base, None) |
| 468 | + sys.path.insert(0, zip_base) |
| 469 | + self.addCleanup(sys.path.remove, zip_base) |
| 470 | + with os_helper.change_cwd(tmpdir): |
| 471 | + zippkg = importlib.import_module('zippkg') |
| 472 | + self.assertEqual(zippkg.__file__, |
| 473 | + os.path.join(zip_base, 'zippkg', '__init__.py')) |
| 474 | + self.assertEqual(linecache.getlines(zippkg.__file__), |
| 475 | + ['value = 42\n']) |
| 476 | + |
| 477 | + def test_package(self): |
| 478 | + zippkg = importlib.import_module('zippkg') |
| 479 | + self.assertEqual(linecache.getlines(zippkg.__file__), |
| 480 | + ['value = 42\n']) |
| 481 | + |
| 482 | + def test_encoding_declaration(self): |
| 483 | + ziplatin1 = importlib.import_module('ziplatin1') |
| 484 | + self.assertEqual(linecache.getlines(ziplatin1.__file__), |
| 485 | + self.LATIN1_SOURCE.splitlines(keepends=True)) |
| 486 | + |
| 487 | + def test_missing_file(self): |
| 488 | + filename = os.path.join(self.zip_name, 'missing.py') |
| 489 | + self.assertEqual(linecache.getlines(filename), []) |
| 490 | + self.assertEqual(linecache.getline(filename, 1), '') |
| 491 | + self.assertNotIn(filename, linecache.cache) |
| 492 | + |
| 493 | + def test_checkcache_and_clearcache(self): |
| 494 | + filename = self.zipmod.__file__ |
| 495 | + lines = linecache.getlines(filename) |
| 496 | + self.assertIn(filename, linecache.cache) |
| 497 | + # A file inside an archive has no mtime of its own, so checkcache() |
| 498 | + # keeps the entry, as it does for entries loaded through a loader. |
| 499 | + self.assertIsNone(linecache.cache[filename][1]) |
| 500 | + linecache.checkcache(filename) |
| 501 | + linecache.checkcache() |
| 502 | + self.assertIn(filename, linecache.cache) |
| 503 | + self.assertEqual(linecache.getlines(filename), lines) |
| 504 | + linecache.clearcache() |
| 505 | + self.assertNotIn(filename, linecache.cache) |
| 506 | + self.assertEqual(linecache.getlines(filename), lines) |
| 507 | + |
| 508 | + |
401 | 509 | class MultiThreadingTest(unittest.TestCase): |
402 | 510 | @threading_helper.reap_threads |
403 | 511 | @threading_helper.requires_working_threading() |
|
0 commit comments