diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 38b655f2f7b9d2b..6fe2ec98fb40888 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -145,13 +145,13 @@ def __init__(self, returncode, cmd, output=None, stderr=None): def __str__(self): if self.returncode and self.returncode < 0: try: - return "Command '%s' died with %r." % ( + return "Command %r died with %r." % ( self.cmd, signal.Signals(-self.returncode)) except ValueError: - return "Command '%s' died with unknown signal %d." % ( + return "Command %r died with unknown signal %d." % ( self.cmd, -self.returncode) else: - return "Command '%s' returned non-zero exit status %d." % ( + return "Command %r returned non-zero exit status %d." % ( self.cmd, self.returncode) @property diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index d41cb1294a3dafe..d066ae85dfc51a6 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -2428,26 +2428,26 @@ def test_run_abort(self): p.wait() self.assertEqual(-p.returncode, signal.SIGABRT) - def test_CalledProcessError_str_signal(self): + def test_CalledProcessError_str(self): + # command string + err = subprocess.CalledProcessError(2, "fake cmd") + self.assertEqual(str(err), "Command 'fake cmd' returned non-zero exit status 2.") + + # command string with a single-quote + err = subprocess.CalledProcessError(2, "fake ' cmd") + self.assertEqual(str(err), 'Command "fake \' cmd" returned non-zero exit status 2.') + + # command list + err = subprocess.CalledProcessError(2, ["fake", "cmd"]) + self.assertEqual(str(err), "Command ['fake', 'cmd'] returned non-zero exit status 2.") + + # signal err = subprocess.CalledProcessError(-int(signal.SIGABRT), "fake cmd") - error_string = str(err) - # We're relying on the repr() of the signal.Signals intenum to provide - # the word signal, the signal name and the numeric value. - self.assertIn("signal", error_string.lower()) - # We're not being specific about the signal name as some signals have - # multiple names and which name is revealed can vary. - self.assertIn("SIG", error_string) - self.assertIn(str(signal.SIGABRT), error_string) - - def test_CalledProcessError_str_unknown_signal(self): - err = subprocess.CalledProcessError(-9876543, "fake cmd") - error_string = str(err) - self.assertIn("unknown signal 9876543.", error_string) + self.assertEqual(str(err), f"Command 'fake cmd' died with {signal.SIGABRT!r}.") - def test_CalledProcessError_str_non_zero(self): - err = subprocess.CalledProcessError(2, "fake cmd") - error_string = str(err) - self.assertIn("non-zero exit status 2.", error_string) + # unknown signal + err = subprocess.CalledProcessError(-9876543, "fake cmd") + self.assertEqual(str(err), "Command 'fake cmd' died with unknown signal 9876543.") def test_preexec(self): # DISCLAIMER: Setting environment variables is *not* a good use diff --git a/Misc/NEWS.d/next/Library/2026-06-15-07-42-12.gh-issue-151485.NiYQPZ.rst b/Misc/NEWS.d/next/Library/2026-06-15-07-42-12.gh-issue-151485.NiYQPZ.rst new file mode 100644 index 000000000000000..134499107ab2b78 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-15-07-42-12.gh-issue-151485.NiYQPZ.rst @@ -0,0 +1 @@ +Fix command quoting in :exc:`subprocess.CalledProcessError`. Contributed by Benjy Wiener.