bpo-31351: Set return code in ensurepip when pip fails (GH-3626)

Previously ensurepip would always report success, even if the
pip installation failed.
This commit is contained in:
Igor Filatov 2017-09-21 13:07:45 +03:00 committed by Nick Coghlan
parent a96c96f5da
commit 9adda0cdf8
6 changed files with 46 additions and 9 deletions

View File

@ -78,6 +78,9 @@ options:
Providing both of the script selection options will trigger an exception. Providing both of the script selection options will trigger an exception.
.. versionchanged:: 3.7.0
The exit status is non-zero if the command fails.
Module API Module API
---------- ----------

View File

@ -25,7 +25,7 @@ def _run_pip(args, additional_paths=None):
# Install the bundled software # Install the bundled software
import pip import pip
pip.main(args) return pip.main(args)
def version(): def version():
@ -53,6 +53,21 @@ def bootstrap(*, root=None, upgrade=False, user=False,
Bootstrap pip into the current Python installation (or the given root Bootstrap pip into the current Python installation (or the given root
directory). directory).
Note that calling this function will alter both sys.path and os.environ.
"""
# Discard the return value
_bootstrap(root=root, upgrade=upgrade, user=user,
altinstall=altinstall, default_pip=default_pip,
verbosity=verbosity)
def _bootstrap(*, root=None, upgrade=False, user=False,
altinstall=False, default_pip=False,
verbosity=0):
"""
Bootstrap pip into the current Python installation (or the given root
directory). Returns pip command status code.
Note that calling this function will alter both sys.path and os.environ. Note that calling this function will alter both sys.path and os.environ.
""" """
if altinstall and default_pip: if altinstall and default_pip:
@ -99,7 +114,7 @@ def bootstrap(*, root=None, upgrade=False, user=False,
if verbosity: if verbosity:
args += ["-" + "v" * verbosity] args += ["-" + "v" * verbosity]
_run_pip(args + [p[0] for p in _PROJECTS], additional_paths) return _run_pip(args + [p[0] for p in _PROJECTS], additional_paths)
def _uninstall_helper(*, verbosity=0): def _uninstall_helper(*, verbosity=0):
"""Helper to support a clean default uninstall process on Windows """Helper to support a clean default uninstall process on Windows
@ -126,7 +141,7 @@ def _uninstall_helper(*, verbosity=0):
if verbosity: if verbosity:
args += ["-" + "v" * verbosity] args += ["-" + "v" * verbosity]
_run_pip(args + [p[0] for p in reversed(_PROJECTS)]) return _run_pip(args + [p[0] for p in reversed(_PROJECTS)])
def _main(argv=None): def _main(argv=None):
@ -180,7 +195,7 @@ def _main(argv=None):
args = parser.parse_args(argv) args = parser.parse_args(argv)
bootstrap( return _bootstrap(
root=args.root, root=args.root,
upgrade=args.upgrade, upgrade=args.upgrade,
user=args.user, user=args.user,

View File

@ -1,4 +1,5 @@
import ensurepip import ensurepip
import sys
if __name__ == "__main__": if __name__ == "__main__":
ensurepip._main() sys.exit(ensurepip._main())

View File

@ -2,6 +2,7 @@
import argparse import argparse
import ensurepip import ensurepip
import sys
def _main(argv=None): def _main(argv=None):
@ -23,8 +24,8 @@ def _main(argv=None):
args = parser.parse_args(argv) args = parser.parse_args(argv)
ensurepip._uninstall_helper(verbosity=args.verbosity) return ensurepip._uninstall_helper(verbosity=args.verbosity)
if __name__ == "__main__": if __name__ == "__main__":
_main() sys.exit(_main())

View File

@ -20,6 +20,7 @@ class EnsurepipMixin:
def setUp(self): def setUp(self):
run_pip_patch = unittest.mock.patch("ensurepip._run_pip") run_pip_patch = unittest.mock.patch("ensurepip._run_pip")
self.run_pip = run_pip_patch.start() self.run_pip = run_pip_patch.start()
self.run_pip.return_value = 0
self.addCleanup(run_pip_patch.stop) self.addCleanup(run_pip_patch.stop)
# Avoid side effects on the actual os module # Avoid side effects on the actual os module
@ -255,7 +256,7 @@ class TestBootstrappingMainFunction(EnsurepipMixin, unittest.TestCase):
self.assertFalse(self.run_pip.called) self.assertFalse(self.run_pip.called)
def test_basic_bootstrapping(self): def test_basic_bootstrapping(self):
ensurepip._main([]) exit_code = ensurepip._main([])
self.run_pip.assert_called_once_with( self.run_pip.assert_called_once_with(
[ [
@ -267,6 +268,13 @@ class TestBootstrappingMainFunction(EnsurepipMixin, unittest.TestCase):
additional_paths = self.run_pip.call_args[0][1] additional_paths = self.run_pip.call_args[0][1]
self.assertEqual(len(additional_paths), 2) self.assertEqual(len(additional_paths), 2)
self.assertEqual(exit_code, 0)
def test_bootstrapping_error_code(self):
self.run_pip.return_value = 2
exit_code = ensurepip._main([])
self.assertEqual(exit_code, 2)
class TestUninstallationMainFunction(EnsurepipMixin, unittest.TestCase): class TestUninstallationMainFunction(EnsurepipMixin, unittest.TestCase):
@ -280,7 +288,7 @@ class TestUninstallationMainFunction(EnsurepipMixin, unittest.TestCase):
def test_basic_uninstall(self): def test_basic_uninstall(self):
with fake_pip(): with fake_pip():
ensurepip._uninstall._main([]) exit_code = ensurepip._uninstall._main([])
self.run_pip.assert_called_once_with( self.run_pip.assert_called_once_with(
[ [
@ -289,6 +297,13 @@ class TestUninstallationMainFunction(EnsurepipMixin, unittest.TestCase):
] ]
) )
self.assertEqual(exit_code, 0)
def test_uninstall_error_code(self):
with fake_pip():
self.run_pip.return_value = 2
exit_code = ensurepip._uninstall._main([])
self.assertEqual(exit_code, 2)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -0,0 +1,2 @@
python -m ensurepip now exits with non-zero exit code if pip bootstrapping
has failed.