]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-59215: unittest: restore _top_level_dir at end of discovery (GH-15242)
authorZackery Spytz <zspytz@gmail.com>
Wed, 3 Apr 2024 14:17:13 +0000 (07:17 -0700)
committerGitHub <noreply@github.com>
Wed, 3 Apr 2024 14:17:13 +0000 (16:17 +0200)
Doc/library/unittest.rst
Lib/test/test_unittest/test_discovery.py
Lib/unittest/loader.py
Misc/NEWS.d/next/Library/2019-08-12-19-08-06.bpo-15010.3bY2CF.rst [new file with mode: 0644]

index e6140ac70eb87ac13aafd5204cd40cfd741a1d2c..3af29f19c802c7adbdb8c9050394994a9e24f3a1 100644 (file)
@@ -1880,8 +1880,8 @@ Loading and running tests
       Python identifiers) will be loaded.
 
       All test modules must be importable from the top level of the project. If
-      the start directory is not the top level directory then the top level
-      directory must be specified separately.
+      the start directory is not the top level directory then *top_level_dir*
+      must be specified separately.
 
       If importing a module fails, for example due to a syntax error, then
       this will be recorded as a single error and discovery will continue.  If
@@ -1901,9 +1901,11 @@ Loading and running tests
       package.
 
       The pattern is deliberately not stored as a loader attribute so that
-      packages can continue discovery themselves. *top_level_dir* is stored so
-      ``load_tests`` does not need to pass this argument in to
-      ``loader.discover()``.
+      packages can continue discovery themselves.
+
+      *top_level_dir* is stored internally, and used as a default to any
+      nested calls to ``discover()``. That is, if a package's ``load_tests``
+      calls ``loader.discover()``, it does not need to pass this argument.
 
       *start_dir* can be a dotted module name as well as a directory.
 
@@ -1930,6 +1932,9 @@ Loading and running tests
          *start_dir* can not be a :term:`namespace packages <namespace package>`.
          It has been broken since Python 3.7 and Python 3.11 officially remove it.
 
+      .. versionchanged:: 3.13
+         *top_level_dir* is only stored for the duration of *discover* call.
+
 
    The following attributes of a :class:`TestLoader` can be configured either by
    subclassing or assignment on an instance:
index 004898ed43183485e2eb649ce3a94721e200a67d..a44b18406c08be84165dc1feeccf1423566b7bd6 100644 (file)
@@ -406,10 +406,34 @@ class TestDiscovery(unittest.TestCase):
         top_level_dir = os.path.abspath('/foo/bar')
         start_dir = os.path.abspath('/foo/bar/baz')
         self.assertEqual(suite, "['tests']")
-        self.assertEqual(loader._top_level_dir, top_level_dir)
+        self.assertEqual(loader._top_level_dir, os.path.abspath('/foo'))
         self.assertEqual(_find_tests_args, [(start_dir, 'pattern')])
         self.assertIn(top_level_dir, sys.path)
 
+    def test_discover_should_not_persist_top_level_dir_between_calls(self):
+        original_isfile = os.path.isfile
+        original_isdir = os.path.isdir
+        original_sys_path = sys.path[:]
+        def restore():
+            os.path.isfile = original_isfile
+            os.path.isdir = original_isdir
+            sys.path[:] = original_sys_path
+        self.addCleanup(restore)
+
+        os.path.isfile = lambda path: True
+        os.path.isdir = lambda path: True
+        loader = unittest.TestLoader()
+        loader.suiteClass = str
+        dir = '/foo/bar'
+        top_level_dir = '/foo'
+
+        loader.discover(dir, top_level_dir=top_level_dir)
+        self.assertEqual(loader._top_level_dir, None)
+
+        loader._top_level_dir = dir2 = '/previous/dir'
+        loader.discover(dir, top_level_dir=top_level_dir)
+        self.assertEqual(loader._top_level_dir, dir2)
+
     def test_discover_start_dir_is_package_calls_package_load_tests(self):
         # This test verifies that the package load_tests in a package is indeed
         # invoked when the start_dir is a package (and not the top level).
index 9a3e5cc4bf30e57511dd10acdb4042fe91cc79e7..22797b83a68bc8720ac15bc518ec3e6bff5f1962 100644 (file)
@@ -254,6 +254,7 @@ class TestLoader(object):
         Paths are sorted before being imported to ensure reproducible execution
         order even on filesystems with non-alphabetical ordering like ext3/4.
         """
+        original_top_level_dir = self._top_level_dir
         set_implicit_top = False
         if top_level_dir is None and self._top_level_dir is not None:
             # make top_level_dir optional if called from load_tests in a package
@@ -307,6 +308,7 @@ class TestLoader(object):
             raise ImportError('Start directory is not importable: %r' % start_dir)
 
         tests = list(self._find_tests(start_dir, pattern))
+        self._top_level_dir = original_top_level_dir
         return self.suiteClass(tests)
 
     def _get_directory_containing_module(self, module_name):
diff --git a/Misc/NEWS.d/next/Library/2019-08-12-19-08-06.bpo-15010.3bY2CF.rst b/Misc/NEWS.d/next/Library/2019-08-12-19-08-06.bpo-15010.3bY2CF.rst
new file mode 100644 (file)
index 0000000..f61a45e
--- /dev/null
@@ -0,0 +1,3 @@
+:meth:`unittest.TestLoader.discover` now saves the original value of
+``unittest.TestLoader._top_level_dir`` and restores it at the end of the
+call.