Repository: pypa/setuptools

Issue:
Distutils hack still causing test failures in CPython
In python/cpython#91169, we learn that despite #2965 and #3007, when the tests are run outside of the source directory, some tests will still fail because Setuptools is unable to detect that these tests are implicated.

For at least three of these tests (`test_distutils` and on Python 3.9, `test_importlib` and `test_peg_generator`), these issues may be avoidable by further suppressing the supplying of distutils.

`test_bdb` is also affected, but cannot be addressed due to python/cpython#93951.
Distutils hack still causing test failures in CPython
In python/cpython#91169, we learn that despite #2965 and #3007, when the tests are run outside of the source directory, some tests will still fail because Setuptools is unable to detect that these tests are implicated.

For at least three of these tests (`test_distutils` and on Python 3.9, `test_importlib` and `test_peg_generator`), these issues may be avoidable by further suppressing the supplying of distutils.

`test_bdb` is also affected, but cannot be addressed due to python/cpython#93951.


Discussion:

Resolving patch:
diff --git a/_distutils_hack/__init__.py b/_distutils_hack/__init__.py
--- a/_distutils_hack/__init__.py
+++ b/_distutils_hack/__init__.py
@@ -14,22 +14,26 @@ def warn_distutils_present():
         # https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250
         return
     import warnings
+
     warnings.warn(
         "Distutils was imported before Setuptools, but importing Setuptools "
         "also replaces the `distutils` module in `sys.modules`. This may lead "
         "to undesirable behaviors or errors. To avoid these issues, avoid "
         "using distutils directly, ensure that setuptools is installed in the "
         "traditional way (e.g. not an editable install), and/or make sure "
-        "that setuptools is always imported before distutils.")
+        "that setuptools is always imported before distutils."
+    )
 
 
 def clear_distutils():
     if 'distutils' not in sys.modules:
         return
     import warnings
+
     warnings.warn("Setuptools is replacing distutils.")
     mods = [
-        name for name in sys.modules
+        name
+        for name in sys.modules
         if name == "distutils" or name.startswith("distutils.")
     ]
     for name in mods:
@@ -46,6 +50,7 @@ def enabled():
 
 def ensure_local_distutils():
     import importlib
+
     clear_distutils()
 
     # With the DistutilsMetaFinder in place,
@@ -82,7 +87,9 @@ def match(self, string):
 
 class DistutilsMetaFinder:
     def find_spec(self, fullname, path, target=None):
-        if path is not None:
+        # optimization: only consider top level modules and those
+        # found in the CPython test suite.
+        if path is not None and not fullname.startswith('test.'):
             return
 
         method_name = 'spec_for_{fullname}'.format(**locals())
@@ -111,7 +118,6 @@ def spec_for_distutils(self):
             return
 
         class DistutilsLoader(importlib.abc.Loader):
-
             def create_module(self, spec):
                 mod.__name__ = 'distutils'
                 return mod
@@ -147,9 +153,9 @@ def pip_imported_during_build(cls):
         Detect if pip is being imported in a build script. Ref #2355.
         """
         import traceback
+
         return any(
-            cls.frame_file_is_setup(frame)
-            for frame, line in traceback.walk_stack(None)
+            cls.frame_file_is_setup(frame) for frame, line in traceback.walk_stack(None)
         )
 
     @staticmethod
@@ -160,6 +166,35 @@ def frame_file_is_setup(frame):
         # some frames may not have __file__ (#2940)
         return frame.f_globals.get('__file__', '').endswith('setup.py')
 
+    def spec_for_sensitive_tests(self):
+        """
+        Ensure stdlib distutils when running select tests under CPython.
+
+        python/cpython#91169
+        """
+        clear_distutils()
+        self.spec_for_distutils = lambda: None
+
+    sensitive_tests = (
+        [
+            'test.test_distutils',
+            'test.test_peg_generator',
+            'test.test_importlib',
+        ]
+        if sys.version_info < (3, 10)
+        else [
+            'test.test_distutils',
+        ]
+    )
+
+
+for name in DistutilsMetaFinder.sensitive_tests:
+    setattr(
+        DistutilsMetaFinder,
+        f'spec_for_{name}',
+        DistutilsMetaFinder.spec_for_sensitive_tests,
+    )
+
 
 DISTUTILS_FINDER = DistutilsMetaFinder()
 


Test patch: