Issue #25768: Make compileall functions return booleans and document

the return values as well as test them.

Thanks to Nicholas Chammas for the bug report and initial patch.
This commit is contained in:
Brett Cannon 2015-12-27 13:17:04 -08:00
parent 4a4ca7c13f
commit 1e3c3e906c
6 changed files with 49 additions and 14 deletions

View File

@ -103,7 +103,8 @@ Public functions
.. function:: compile_dir(dir, maxlevels=10, ddir=None, force=False, rx=None, quiet=0, legacy=False, optimize=-1, workers=1)
Recursively descend the directory tree named by *dir*, compiling all :file:`.py`
files along the way.
files along the way. Return a true value if all the files compiled successfully,
and a false value otherwise.
The *maxlevels* parameter is used to limit the depth of the recursion; it
defaults to ``10``.
@ -155,7 +156,8 @@ Public functions
.. function:: compile_file(fullname, ddir=None, force=False, rx=None, quiet=0, legacy=False, optimize=-1)
Compile the file with path *fullname*.
Compile the file with path *fullname*. Return a true value if the file
compiled successfully, and a false value otherwise.
If *ddir* is given, it is prepended to the path to the file being compiled
for use in compilation time tracebacks, and is also compiled in to the
@ -191,8 +193,10 @@ Public functions
.. function:: compile_path(skip_curdir=True, maxlevels=0, force=False, quiet=0, legacy=False, optimize=-1)
Byte-compile all the :file:`.py` files found along ``sys.path``. If
*skip_curdir* is true (the default), the current directory is not included
Byte-compile all the :file:`.py` files found along ``sys.path``. Return a
true value if all the files compiled successfully, and a false value otherwise.
If *skip_curdir* is true (the default), the current directory is not included
in the search. All other parameters are passed to the :func:`compile_dir`
function. Note that unlike the other compile functions, ``maxlevels``
defaults to ``0``.

View File

@ -230,6 +230,11 @@ that may require changes to your code.
Changes in the Python API
-------------------------
* The functions in the :mod:`compileall` module now return booleans instead
of ``1`` or ``0`` to represent success or failure, respectively. Thanks to
booleans being a subclass of integers, this should only be an issue if you
were doing identity checks for ``1`` or ``0``. See :issue:`25768`.
* Reading the :attr:`~urllib.parse.SplitResult.port` attribute of
:func:`urllib.parse.urlsplit` and :func:`~urllib.parse.urlparse` results
now raises :exc:`ValueError` for out-of-range values, rather than

View File

@ -68,7 +68,7 @@ def compile_dir(dir, maxlevels=10, ddir=None, force=False, rx=None,
"""
files = _walk_dir(dir, quiet=quiet, maxlevels=maxlevels,
ddir=ddir)
success = 1
success = True
if workers is not None and workers != 1 and ProcessPoolExecutor is not None:
if workers < 0:
raise ValueError('workers must be greater or equal to 0')
@ -81,12 +81,12 @@ def compile_dir(dir, maxlevels=10, ddir=None, force=False, rx=None,
legacy=legacy,
optimize=optimize),
files)
success = min(results, default=1)
success = min(results, default=True)
else:
for file in files:
if not compile_file(file, ddir, force, rx, quiet,
legacy, optimize):
success = 0
success = False
return success
def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
@ -104,7 +104,7 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
legacy: if True, produce legacy pyc paths instead of PEP 3147 paths
optimize: optimization level or -1 for level of the interpreter
"""
success = 1
success = True
name = os.path.basename(fullname)
if ddir is not None:
dfile = os.path.join(ddir, name)
@ -144,7 +144,7 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
ok = py_compile.compile(fullname, cfile, dfile, True,
optimize=optimize)
except py_compile.PyCompileError as err:
success = 0
success = False
if quiet >= 2:
return success
elif quiet:
@ -157,7 +157,7 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
msg = msg.decode(sys.stdout.encoding)
print(msg)
except (SyntaxError, UnicodeError, OSError) as e:
success = 0
success = False
if quiet >= 2:
return success
elif quiet:
@ -167,7 +167,7 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
print(e.__class__.__name__ + ':', e)
else:
if ok == 0:
success = 0
success = False
return success
def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0,
@ -183,7 +183,7 @@ def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0,
legacy: as for compile_dir() (default False)
optimize: as for compile_dir() (default -1)
"""
success = 1
success = True
for dir in sys.path:
if (not dir or dir == os.curdir) and skip_curdir:
if quiet < 2:

View File

@ -1,6 +1,7 @@
import sys
import compileall
import importlib.util
import test.test_importlib.util
import os
import pathlib
import py_compile
@ -40,6 +41,11 @@ class CompileallTests(unittest.TestCase):
def tearDown(self):
shutil.rmtree(self.directory)
def add_bad_source_file(self):
self.bad_source_path = os.path.join(self.directory, '_test_bad.py')
with open(self.bad_source_path, 'w') as file:
file.write('x (\n')
def data(self):
with open(self.bc_path, 'rb') as file:
data = file.read(8)
@ -78,15 +84,31 @@ class CompileallTests(unittest.TestCase):
os.unlink(fn)
except:
pass
compileall.compile_file(self.source_path, force=False, quiet=True)
self.assertTrue(compileall.compile_file(self.source_path,
force=False, quiet=True))
self.assertTrue(os.path.isfile(self.bc_path) and
not os.path.isfile(self.bc_path2))
os.unlink(self.bc_path)
compileall.compile_dir(self.directory, force=False, quiet=True)
self.assertTrue(compileall.compile_dir(self.directory, force=False,
quiet=True))
self.assertTrue(os.path.isfile(self.bc_path) and
os.path.isfile(self.bc_path2))
os.unlink(self.bc_path)
os.unlink(self.bc_path2)
# Test against bad files
self.add_bad_source_file()
self.assertFalse(compileall.compile_file(self.bad_source_path,
force=False, quiet=2))
self.assertFalse(compileall.compile_dir(self.directory,
force=False, quiet=2))
def test_compile_path(self):
self.assertTrue(compileall.compile_path(quiet=2))
with test.test_importlib.util.import_state(path=[self.directory]):
self.add_bad_source_file()
self.assertFalse(compileall.compile_path(skip_curdir=False,
force=True, quiet=2))
def test_no_pycache_in_non_package(self):
# Bug 8563 reported that __pycache__ directories got created by

View File

@ -235,6 +235,7 @@ Octavian Cerna
Michael Cetrulo
Dave Chambers
Pascal Chambon
Nicholas Chammas
John Chandler
Hye-Shik Chang
Jeffrey Chang

View File

@ -123,6 +123,9 @@ Core and Builtins
Library
-------
- Issue #25768: Have the functions in compileall return booleans instead of
ints and add proper documentation and tests for the return values.
- Issue #24103: Fixed possible use after free in ElementTree.XMLPullParser.
- Issue #25860: os.fwalk() no longer skips remaining directories when error