gh-59215: unittest: restore _top_level_dir at end of discovery (GH-15242)

This commit is contained in:
Zackery Spytz 2024-04-03 07:17:13 -07:00 committed by GitHub
parent ea94b3b149
commit fc5f68e58e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 40 additions and 6 deletions

View 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:

View 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).

View 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):

View File

@ -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.