Skip to content

Commit eab76de

Browse files
Merge pull request #13 from fedora-python/python_upstream
Changes from Python upstream
2 parents b2acb67 + 9bed13c commit eab76de

File tree

3 files changed

+85
-68
lines changed

3 files changed

+85
-68
lines changed

README.md

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,6 @@ ZeroDivisionError: division by zero
8484

8585
* ✓ Test it with Python packages in COPR
8686

87-
## ToDo
88-
8987
* Push it to Fedora rawhide
9088

9189
* ✓ %py_byte_compile RPM macro uses `compileall2` (done in [python-rpm-macros](https://src.fedoraproject.org/rpms/python-rpm-macros/pull-request/25))
@@ -96,17 +94,18 @@ ZeroDivisionError: division by zero
9694
* ✓ Prepare patches for upstream CPython
9795
* [Pull request](https://github.com/python/cpython/pull/16012) merged and will be part of Python 3.9
9896

97+
* ✓ Changes from upstream CPython backported back
98+
9999
## Testing
100100

101101
You can test it locally with tox or unittest directly:
102102

103103
```shell
104-
$ python3 -m unittest test_compileall_original.py
105-
............sss.......................sss.....................ss.................
106-
----------------------------------------------------------------------
107-
Ran 81 tests in 2.137s
104+
$ python3 -m unittest test_compileall2.py
105+
..............sss....ss.......................sss....ss.....................ss.............................----------------------------------------------------------------------
106+
Ran 107 tests in 3.714s
108107

109-
OK (skipped=8)
108+
OK (skipped=12)
110109
```
111110

112111
but running in a Docker container might be better because the superuser has privileges to write to `sys.path` which lowers the number of skipped tests.
@@ -125,23 +124,26 @@ and run tests in it:
125124

126125
```shell
127126
$ docker run --rm -it -e TOXENV=py37 -v $PWD:/src:Z -w /src compileall2
128-
py37 create: /src/.tox/py37
129-
py37 installdeps: pytest
130-
py37 installed: atomicwrites==1.3.0,attrs==19.1.0,more-itertools==6.0.0,pluggy==0.9.0,py==1.8.0,pytest==4.3.1,six==1.12.0
131-
py37 run-test-pre: PYTHONHASHSEED='519909491'
132-
py37 runtests: commands[0] | pytest -v -s
133-
========================================== test session starts ==========================================
134-
platform linux -- Python 3.7.2, pytest-4.3.1, py-1.8.0, pluggy-0.9.0 -- /src/.tox/py37/bin/python
127+
py37 installed: atomicwrites==1.3.0,attrs==19.3.0,compileall2==0.5.0,coverage==4.5.4,importlib-metadata==0.23,more-itertools==7.2.0,packaging==19.2,pluggy==0.13.0,py==1.8.0,pyparsing==2.4.5,pytest==5.2.3,six==1.13.0,wcwidth==0.1.7,zipp==0.6.0
128+
py37 run-test-pre: PYTHONHASHSEED='1615314833'
129+
py37 runtests: commands[0] | coverage run --append -m py.test
130+
==================================== test session starts =====================================
131+
platform linux -- Python 3.7.5, pytest-5.2.3, py-1.8.0, pluggy-0.13.0
135132
cachedir: .tox/py37/.pytest_cache
136-
rootdir: /src, inifile:
137-
collected 81 items
138-
139-
test_compileall_original.py::CompileallTestsWithSourceEpoch::test_compile_dir_pathlike PASSED
140-
test_compileall_original.py::CompileallTestsWithSourceEpoch::test_compile_file_pathlike PASSED
141-
test_compileall_original.py::CompileallTestsWithSourceEpoch::test_compile_file_pathlike_ddir PASSED
142-
... etc ...
143-
================================= 79 passed, 2 skipped in 5.15 seconds ==================================
144-
________________________________________________ summary ________________________________________________
133+
rootdir: /src
134+
collected 107 items
135+
test_compileall2.py ............ss..................................................ss [ 61%]
136+
..............................ss......... [100%]
137+
138+
=============================== 101 passed, 6 skipped in 7.40s ===============================
139+
py37 runtests: commands[1] | coverage report -i '--omit=.tox/*'
140+
Name Stmts Miss Cover
141+
-----------------------------------------
142+
compileall2.py 232 48 79%
143+
test_compileall2.py 621 8 99%
144+
-----------------------------------------
145+
TOTAL 853 56 93%
146+
__________________________________________ summary ___________________________________________
145147
py37: commands succeeded
146148
congratulations :)
147149
```

compileall2.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@
4343
pyc_header_lenght = 8
4444
pyc_header_format = (pyc_struct_format, importlib.util.MAGIC_NUMBER)
4545

46-
RECURSION_LIMIT = sys.getrecursionlimit()
47-
4846
__all__ = ["compile_dir","compile_file","compile_path"]
4947

5048
def optimization_kwarg(opt):
@@ -60,7 +58,7 @@ def optimization_kwarg(opt):
6058
else:
6159
return dict()
6260

63-
def _walk_dir(dir, maxlevels=RECURSION_LIMIT, quiet=0):
61+
def _walk_dir(dir, maxlevels, quiet=0):
6462
if PY36 and quiet < 2 and isinstance(dir, os.PathLike):
6563
dir = os.fspath(dir)
6664
else:
@@ -85,7 +83,7 @@ def _walk_dir(dir, maxlevels=RECURSION_LIMIT, quiet=0):
8583
yield from _walk_dir(fullname, maxlevels=maxlevels - 1,
8684
quiet=quiet)
8785

88-
def compile_dir(dir, maxlevels=RECURSION_LIMIT, ddir=None, force=False,
86+
def compile_dir(dir, maxlevels=None, ddir=None, force=False,
8987
rx=None, quiet=0, legacy=False, optimize=-1, workers=1,
9088
invalidation_mode=None, stripdir=None,
9189
prependdir=None, limit_sl_dest=None):
@@ -123,6 +121,8 @@ def compile_dir(dir, maxlevels=RECURSION_LIMIT, ddir=None, force=False,
123121
from concurrent.futures import ProcessPoolExecutor
124122
except ImportError:
125123
workers = 1
124+
if maxlevels is None:
125+
maxlevels = sys.getrecursionlimit()
126126
files = _walk_dir(dir, quiet=quiet, maxlevels=maxlevels)
127127
success = True
128128
if workers is not None and workers != 1 and ProcessPoolExecutor is not None:
@@ -173,6 +173,11 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
173173
limit_sl_dest: ignore symlinks if they are pointing outside of
174174
the defined path.
175175
"""
176+
177+
if ddir is not None and (stripdir is not None or prependdir is not None):
178+
raise ValueError(("Destination dir (ddir) cannot be used "
179+
"in combination with stripdir or prependdir"))
180+
176181
success = True
177182
if PY36 and quiet < 2 and isinstance(fullname, os.PathLike):
178183
fullname = os.fspath(fullname)
@@ -327,7 +332,7 @@ def main():
327332
parser = argparse.ArgumentParser(
328333
description='Utilities to support installing Python libraries.')
329334
parser.add_argument('-l', action='store_const', const=0,
330-
default=RECURSION_LIMIT, dest='maxlevels',
335+
default=None, dest='maxlevels',
331336
help="don't recurse into subdirectories")
332337
parser.add_argument('-r', type=int, dest='recursion',
333338
help=('control the maximum recursion level. '
@@ -349,16 +354,16 @@ def main():
349354
default=None,
350355
help=('part of path to left-strip from path '
351356
'to source file - for example buildroot. '
352-
'if `-d` and `-s` options are specified, '
353-
'then `-d` takes precedence.'))
357+
'`-d` and `-s` options cannot be '
358+
'specified together.'))
354359
parser.add_argument('-p', metavar='PREPENDDIR', dest='prependdir',
355360
default=None,
356361
help=('path to add as prefix to path '
357362
'to source file - for example / to make '
358363
'it absolute when some part is removed '
359-
'by `-s` option'
360-
'if `-d` and `-a` options are specified, '
361-
'then `-d` takes precedence.'))
364+
'by `-s` option. '
365+
'`-d` and `-p` options cannot be '
366+
'specified together.'))
362367
parser.add_argument('-x', metavar='REGEXP', dest='rx', default=None,
363368
help=('skip files matching the regular expression; '
364369
'the regexp is searched for in the full path '
@@ -408,6 +413,11 @@ def main():
408413
if args.opt_levels is None:
409414
args.opt_levels = [-1]
410415

416+
if args.ddir is not None and (
417+
args.stripdir is not None or args.prependdir is not None
418+
):
419+
parser.error("-d cannot be used in combination with -s or -p")
420+
411421
# if flist is provided then load it
412422
if args.flist:
413423
try:

test_compileall2.py

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,6 @@ def setUp(self):
103103
os.mkdir(self.subdirectory)
104104
self.source_path3 = os.path.join(self.subdirectory, '_test3.py')
105105
shutil.copyfile(self.source_path, self.source_path3)
106-
many_directories = [str(number) for number in range(1, 100)]
107-
self.long_path = os.path.join(self.directory,
108-
"long",
109-
*many_directories)
110-
os.makedirs(self.long_path)
111-
self.source_path_long = os.path.join(self.long_path, '_test4.py')
112-
shutil.copyfile(self.source_path, self.source_path_long)
113-
self.bc_path_long = importlib.util.cache_from_source(
114-
self.source_path_long
115-
)
116106

117107
def tearDown(self):
118108
shutil.rmtree(self.directory)
@@ -266,14 +256,22 @@ def test_compile_missing_multiprocessing(self, compile_file_mock):
266256
compileall.compile_dir(self.directory, quiet=True, workers=5)
267257
self.assertTrue(compile_file_mock.called)
268258

269-
def text_compile_dir_maxlevels(self):
270-
# Test the actual impact of maxlevels attr
271-
compileall.compile_dir(os.path.join(self.directory, "long"),
272-
maxlevels=10, quiet=True)
273-
self.assertFalse(os.path.isfile(self.bc_path_long))
274-
compileall.compile_dir(os.path.join(self.directory, "long"),
275-
maxlevels=110, quiet=True)
276-
self.assertTrue(os.path.isfile(self.bc_path_long))
259+
def test_compile_dir_maxlevels(self):
260+
# Test the actual impact of maxlevels parameter
261+
depth = 3
262+
path = self.directory
263+
for i in range(1, depth + 1):
264+
path = os.path.join(path, "dir_{}".format(i))
265+
source = os.path.join(path, 'script.py')
266+
os.mkdir(path)
267+
shutil.copyfile(self.source_path, source)
268+
pyc_filename = importlib.util.cache_from_source(source)
269+
270+
compileall.compile_dir(self.directory, quiet=True, maxlevels=depth - 1)
271+
self.assertFalse(os.path.isfile(pyc_filename))
272+
273+
compileall.compile_dir(self.directory, quiet=True, maxlevels=depth)
274+
self.assertTrue(os.path.isfile(pyc_filename))
277275

278276
def test_strip_only(self):
279277
fullpath = ["test", "build", "real", "path"]
@@ -330,6 +328,15 @@ def test_strip_and_prepend(self):
330328
str(err, encoding=sys.getdefaultencoding())
331329
)
332330

331+
def test_strip_prepend_and_ddir(self):
332+
fullpath = ["test", "build", "real", "path", "ddir"]
333+
path = os.path.join(self.directory, *fullpath)
334+
os.makedirs(path)
335+
script_helper.make_script(path, "test", "1 / 0")
336+
with self.assertRaises(ValueError):
337+
compileall.compile_dir(path, quiet=True, ddir="/bar",
338+
stripdir="/foo", prependdir="/bar")
339+
333340
def test_multiple_optimization_levels(self):
334341
script = script_helper.make_script(self.directory,
335342
"test_optimization",
@@ -341,7 +348,7 @@ def test_multiple_optimization_levels(self):
341348
test_combinations = [[0, 1], [1, 2], [0, 2], [0, 1, 2]]
342349

343350
for opt_combination in test_combinations:
344-
compileall.compile_file(script,
351+
compileall.compile_file(script, quiet=True,
345352
optimize=opt_combination)
346353
for opt_level in opt_combination:
347354
self.assertTrue(os.path.isfile(bc[opt_level]))
@@ -372,7 +379,7 @@ def test_ignore_symlink_destination(self):
372379
allowed_bc = importlib.util.cache_from_source(allowed_symlink)
373380
prohibited_bc = importlib.util.cache_from_source(prohibited_symlink)
374381

375-
compileall.compile_dir(symlinks_path, quiet=False, limit_sl_dest=allowed_path)
382+
compileall.compile_dir(symlinks_path, quiet=True, limit_sl_dest=allowed_path)
376383

377384
self.assertTrue(os.path.isfile(allowed_bc))
378385
self.assertFalse(os.path.isfile(prohibited_bc))
@@ -642,21 +649,19 @@ def test_recursion_limit(self):
642649
self.assertCompiled(spamfn)
643650
self.assertCompiled(eggfn)
644651

645-
def test_default_recursion_limit(self):
646-
many_directories = [str(number) for number in range(1, 100)]
647-
self.long_path = os.path.join(self.directory,
648-
"long",
649-
*many_directories)
650-
os.makedirs(self.long_path)
651-
self.source_path_long = script_helper.make_script(
652-
self.long_path, "deepscript", ""
653-
)
654-
self.bc_path_long = importlib.util.cache_from_source(
655-
self.source_path_long
656-
)
657-
self.assertFalse(os.path.isfile(self.bc_path_long))
658-
self.assertRunOK('-q', os.path.join(self.directory, "long"))
659-
self.assertTrue(os.path.isfile(self.bc_path_long))
652+
@support.skip_unless_symlink
653+
def test_symlink_loop(self):
654+
# Currently, compileall ignores symlinks to directories.
655+
# If that limitation is ever lifted, it should protect against
656+
# recursion in symlink loops.
657+
pkg = os.path.join(self.pkgdir, 'spam')
658+
script_helper.make_pkg(pkg)
659+
os.symlink('.', os.path.join(pkg, 'evil'))
660+
os.symlink('.', os.path.join(pkg, 'evil2'))
661+
self.assertRunOK('-q', self.pkgdir)
662+
self.assertCompiled(os.path.join(
663+
self.pkgdir, 'spam', 'evil', 'evil2', '__init__.py'
664+
))
660665

661666
def test_quiet(self):
662667
noisy = self.assertRunOK(self.pkgdir)

0 commit comments

Comments
 (0)