Commit 1e3c3e90 authored by Brett Cannon's avatar Brett Cannon

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.
parent 4a4ca7c1
......@@ -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``.
......
......@@ -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
......
......@@ -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:
......
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
......
......@@ -235,6 +235,7 @@ Octavian Cerna
Michael Cetrulo
Dave Chambers
Pascal Chambon
Nicholas Chammas
John Chandler
Hye-Shik Chang
Jeffrey Chang
......
......@@ -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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment