summaryrefslogtreecommitdiffstats
path: root/Lib/test
diff options
context:
space:
mode:
authorGregory P. Smith <greg@krypto.org>2020-03-01 18:42:56 (GMT)
committerGitHub <noreply@github.com>2020-03-01 18:42:56 (GMT)
commitce720d3e0674d6ac6f1b950c20a89be4cfde7853 (patch)
tree54dba81e8458c72a3e65886d69d688c9ed07b044 /Lib/test
parentfec6681f7ae3e8867bd0446aa993a0b5f23045f9 (diff)
downloadcpython-ce720d3e0674d6ac6f1b950c20a89be4cfde7853.zip
cpython-ce720d3e0674d6ac6f1b950c20a89be4cfde7853.tar.gz
cpython-ce720d3e0674d6ac6f1b950c20a89be4cfde7853.tar.bz2
bpo-39769: Fix compileall ddir for subpkgs. (GH-18676) (GH-18718)
Fix compileall.compile_dir() ddir= behavior on sub-packages. Fixes compileall.compile_dir's ddir parameter and compileall command line flag `-d` to no longer write the wrong pathname to the generated pyc file for submodules beneath the root of the directory tree being compiled. This fixes a regression introduced with Python 3.5. Tests backported from GH 02673352b5db6ca4d3dc804965facbedfe66425d, the implementation is different due to intervening code changes. But still quiet simple. Why was the bug ever introduced? The refactoring to add parallel execution kept the ddir -> dfile computations but discarded the results instead of sending them to compile_file(). This fixes that. Lack of tests meant this went unnoticed.
Diffstat (limited to 'Lib/test')
-rw-r--r--Lib/test/test_compileall.py41
-rw-r--r--Lib/test/test_importlib/util.py11
2 files changed, 52 insertions, 0 deletions
diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py
index 04f6e1e..64f092b 100644
--- a/Lib/test/test_compileall.py
+++ b/Lib/test/test_compileall.py
@@ -577,6 +577,47 @@ class CommandLineTestsBase:
self.assertTrue(compile_dir.called)
self.assertEqual(compile_dir.call_args[-1]['workers'], 0)
+ def _test_ddir_only(self, *, ddir, parallel=True):
+ """Recursive compile_dir ddir must contain package paths; bpo39769."""
+ fullpath = ["test", "foo"]
+ path = self.directory
+ mods = []
+ for subdir in fullpath:
+ path = os.path.join(path, subdir)
+ os.mkdir(path)
+ script_helper.make_script(path, "__init__", "")
+ mods.append(script_helper.make_script(path, "mod",
+ "def fn(): 1/0\nfn()\n"))
+ compileall.compile_dir(
+ self.directory, quiet=True, ddir=ddir,
+ workers=2 if parallel else 1)
+ self.assertTrue(mods)
+ for mod in mods:
+ self.assertTrue(mod.startswith(self.directory), mod)
+ modcode = importlib.util.cache_from_source(mod)
+ modpath = mod[len(self.directory+os.sep):]
+ _, _, err = script_helper.assert_python_failure(modcode)
+ expected_in = os.path.join(ddir, modpath)
+ mod_code_obj = test.test_importlib.util._get_code_from_pyc(modcode)
+ self.assertEqual(mod_code_obj.co_filename, expected_in)
+ self.assertIn(f'"{expected_in}"', os.fsdecode(err))
+
+ def test_ddir_only_one_worker(self):
+ """Recursive compile_dir ddir= contains package paths; bpo39769."""
+ return self._test_ddir_only(ddir="<a prefix>", parallel=False)
+
+ def test_ddir_multiple_workers(self):
+ """Recursive compile_dir ddir= contains package paths; bpo39769."""
+ return self._test_ddir_only(ddir="<a prefix>", parallel=True)
+
+ def test_ddir_empty_only_one_worker(self):
+ """Recursive compile_dir ddir='' contains package paths; bpo39769."""
+ return self._test_ddir_only(ddir="", parallel=False)
+
+ def test_ddir_empty_multiple_workers(self):
+ """Recursive compile_dir ddir='' contains package paths; bpo39769."""
+ return self._test_ddir_only(ddir="", parallel=True)
+
class CommmandLineTestsWithSourceEpoch(CommandLineTestsBase,
unittest.TestCase,
diff --git a/Lib/test/test_importlib/util.py b/Lib/test/test_importlib/util.py
index e016ea4..e6a1476 100644
--- a/Lib/test/test_importlib/util.py
+++ b/Lib/test/test_importlib/util.py
@@ -7,6 +7,7 @@ import importlib
from importlib import machinery, util, invalidate_caches
from importlib.abc import ResourceReader
import io
+import marshal
import os
import os.path
from pathlib import Path, PurePath
@@ -118,6 +119,16 @@ def submodule(parent, name, pkg_dir, content=''):
return '{}.{}'.format(parent, name), path
+def _get_code_from_pyc(pyc_path):
+ """Reads a pyc file and returns the unmarshalled code object within.
+
+ No header validation is performed.
+ """
+ with open(pyc_path, 'rb') as pyc_f:
+ pyc_f.seek(16)
+ return marshal.load(pyc_f)
+
+
@contextlib.contextmanager
def uncache(*names):
"""Uncache a module from sys.modules.