From f7f2edcdb32cb800567811de60eda1dad92c50e5 Mon Sep 17 00:00:00 2001 From: Zackery Spytz Date: Mon, 12 Aug 2019 19:08:41 -0600 Subject: [PATCH 1/3] bpo-15010, unittest: _top_level_dir is incorrectly persisted Co-Authored-By: Igor Sobreira --- Lib/unittest/loader.py | 2 ++ Lib/unittest/test/test_discovery.py | 26 ++++++++++++++++++- .../2019-08-12-19-08-06.bpo-15010.3bY2CF.rst | 3 +++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2019-08-12-19-08-06.bpo-15010.3bY2CF.rst diff --git a/Lib/unittest/loader.py b/Lib/unittest/loader.py index ba7105e1ad6039..5680dab2079b83 100644 --- a/Lib/unittest/loader.py +++ b/Lib/unittest/loader.py @@ -267,6 +267,7 @@ def discover(self, start_dir, pattern='test*.py', top_level_dir=None): 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 @@ -347,6 +348,7 @@ def discover(self, start_dir, pattern='test*.py', top_level_dir=None): if not is_namespace: 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/Lib/unittest/test/test_discovery.py b/Lib/unittest/test/test_discovery.py index 204043b493b5d2..10c72ee7681d03 100644 --- a/Lib/unittest/test/test_discovery.py +++ b/Lib/unittest/test/test_discovery.py @@ -406,10 +406,34 @@ def _find_tests(start_dir, pattern, namespace=None): 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, '/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). 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 index 00000000000000..f61a45ed98abad --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-08-12-19-08-06.bpo-15010.3bY2CF.rst @@ -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. From 53ef415a227ac358cd760a94c431738a8b927053 Mon Sep 17 00:00:00 2001 From: Zackery Spytz Date: Mon, 12 Aug 2019 21:41:28 -0600 Subject: [PATCH 2/3] Fix the tests on Windows. --- Lib/unittest/test/test_discovery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/unittest/test/test_discovery.py b/Lib/unittest/test/test_discovery.py index 10c72ee7681d03..ea85e37f7ce8be 100644 --- a/Lib/unittest/test/test_discovery.py +++ b/Lib/unittest/test/test_discovery.py @@ -406,7 +406,7 @@ def _find_tests(start_dir, pattern, namespace=None): 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, '/foo') + 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) From 4d5f9f8a8d30d8f767ac3171e4af1762a494303b Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 18 Mar 2024 15:14:20 +0100 Subject: [PATCH 3/3] Clarify docs and mention the change --- Doc/library/unittest.rst | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index e6140ac70eb87a..3af29f19c802c7 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -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 `. 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: