From dd4016eb14d18c7fa92aaf9c0f89998c0006e148 Mon Sep 17 00:00:00 2001
From: "Jason R. Coombs" <jaraco@jaraco.com>
Date: Sat, 18 Jun 2022 16:47:27 -0400
Subject: [PATCH] [3.10] gh-93975: Nicer error reporting in test_venv
 (GH-93959)

- gh-93957: Provide nicer error reporting from subprocesses in test_venv.EnsurePipTest.test_with_pip.
- Update changelog

This change does three things:

1. Extract a function for trapping output in subprocesses.
2. Emit both stdout and stderr when encountering an error.
3. Apply the change to `ensurepip._uninstall` check..
(cherry picked from commit 6066f450b91f1cbebf33a245c14e660052ccd90a)

Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>
---
 Lib/test/test_venv.py                         | 44 +++++++++++++------
 ...2-06-17-13-55-11.gh-issue-93957.X4ovYV.rst |  2 +
 2 files changed, 32 insertions(+), 14 deletions(-)
 create mode 100644 Misc/NEWS.d/next/Tests/2022-06-17-13-55-11.gh-issue-93957.X4ovYV.rst

diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
index 94d626598bac38..eca35ec4bfd1ab 100644
--- a/Lib/test/test_venv.py
+++ b/Lib/test/test_venv.py
@@ -5,6 +5,7 @@
 Licensed to the PSF under a contributor agreement.
 """
 
+import contextlib
 import ensurepip
 import os
 import os.path
@@ -478,16 +479,10 @@ def do_test_with_pip(self, system_site_packages):
 
                 # Actually run the create command with all that unhelpful
                 # config in place to ensure we ignore it
-                try:
+                with self.nicer_error():
                     self.run_with_capture(venv.create, self.env_dir,
                                           system_site_packages=system_site_packages,
                                           with_pip=True)
-                except subprocess.CalledProcessError as exc:
-                    # The output this produces can be a little hard to read,
-                    # but at least it has all the details
-                    details = exc.output.decode(errors="replace")
-                    msg = "{}\n\n**Subprocess Output**\n{}"
-                    self.fail(msg.format(exc, details))
         # Ensure pip is available in the virtual environment
         envpy = os.path.join(os.path.realpath(self.env_dir), self.bindir, self.exe)
         # Ignore DeprecationWarning since pip code is not part of Python
@@ -508,13 +503,14 @@ def do_test_with_pip(self, system_site_packages):
         # Check the private uninstall command provided for the Windows
         # installers works (at least in a virtual environment)
         with EnvironmentVarGuard() as envvars:
-            # It seems ensurepip._uninstall calls subprocesses which do not
-            # inherit the interpreter settings.
-            envvars["PYTHONWARNINGS"] = "ignore"
-            out, err = check_output([envpy,
-                '-W', 'ignore::DeprecationWarning',
-                '-W', 'ignore::ImportWarning', '-I',
-                '-m', 'ensurepip._uninstall'])
+            with self.nicer_error():
+                # It seems ensurepip._uninstall calls subprocesses which do not
+                # inherit the interpreter settings.
+                envvars["PYTHONWARNINGS"] = "ignore"
+                out, err = check_output([envpy,
+                    '-W', 'ignore::DeprecationWarning',
+                    '-W', 'ignore::ImportWarning', '-I',
+                    '-m', 'ensurepip._uninstall'])
         # We force everything to text, so unittest gives the detailed diff
         # if we get unexpected results
         err = err.decode("latin-1") # Force to text, prevent decoding errors
@@ -540,6 +536,25 @@ def do_test_with_pip(self, system_site_packages):
         if not system_site_packages:
             self.assert_pip_not_installed()
 
+    @contextlib.contextmanager
+    def nicer_error(self):
+        """
+        Capture output from a failed subprocess for easier debugging.
+
+        The output this handler produces can be a little hard to read,
+        but at least it has all the details.
+        """
+        try:
+            yield
+        except subprocess.CalledProcessError as exc:
+            out = exc.output.decode(errors="replace")
+            err = exc.stderr.decode(errors="replace")
+            self.fail(
+                f"{exc}\n\n"
+                f"**Subprocess Output**\n{out}\n\n"
+                f"**Subprocess Error**\n{err}"
+            )
+
     # Issue #26610: pip/pep425tags.py requires ctypes
     @unittest.skipUnless(ctypes, 'pip requires ctypes')
     @requires_zlib()
@@ -547,5 +562,6 @@ def test_with_pip(self):
         self.do_test_with_pip(False)
         self.do_test_with_pip(True)
 
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/Tests/2022-06-17-13-55-11.gh-issue-93957.X4ovYV.rst b/Misc/NEWS.d/next/Tests/2022-06-17-13-55-11.gh-issue-93957.X4ovYV.rst
new file mode 100644
index 00000000000000..2719933f6b94c7
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2022-06-17-13-55-11.gh-issue-93957.X4ovYV.rst
@@ -0,0 +1,2 @@
+Provide nicer error reporting from subprocesses in
+test_venv.EnsurePipTest.test_with_pip.