aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2023-12-04 17:16:03 +0100
committerGitHub <noreply@github.com>2023-12-04 18:16:03 +0200
commit8dbda1cf3eab8696dfbb95f0ca60af8db39d51c4 (patch)
tree13e24de2146e4e550322ba5bd0ac056f6fa1e05d
parent[3.11] GH-112160: Pin to manifest of quay.io/tiran/cpython_autoconf (#112161) (diff)
downloadcpython-8dbda1cf3eab8696dfbb95f0ca60af8db39d51c4.tar.gz
cpython-8dbda1cf3eab8696dfbb95f0ca60af8db39d51c4.tar.bz2
cpython-8dbda1cf3eab8696dfbb95f0ca60af8db39d51c4.zip
[3.11] gh-108927: Fix removing testing modules from sys.modules (GH-108952) (ПР-112712)
It breaks import machinery if the test module has submodules used in other tests. (cherry picked from commit e08b70fab1fbc45fa498020aac522ae1d5da6136) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
-rw-r--r--Lib/test/libregrtest/main.py18
-rw-r--r--Lib/test/libregrtest/single.py4
-rw-r--r--Lib/test/regrtestdata/import_from_tests/test_regrtest_a.py11
-rw-r--r--Lib/test/regrtestdata/import_from_tests/test_regrtest_b/__init__.py9
-rw-r--r--Lib/test/regrtestdata/import_from_tests/test_regrtest_b/util.py0
-rw-r--r--Lib/test/regrtestdata/import_from_tests/test_regrtest_c.py11
-rw-r--r--Lib/test/test_regrtest.py19
-rw-r--r--Misc/NEWS.d/next/Tests/2023-09-05-20-46-35.gh-issue-108927.TpwWav.rst4
8 files changed, 67 insertions, 9 deletions
diff --git a/Lib/test/libregrtest/main.py b/Lib/test/libregrtest/main.py
index beee0fa0950..a9725fa9673 100644
--- a/Lib/test/libregrtest/main.py
+++ b/Lib/test/libregrtest/main.py
@@ -306,7 +306,7 @@ class Regrtest:
else:
tracer = None
- save_modules = sys.modules.keys()
+ save_modules = set(sys.modules)
jobs = runtests.get_jobs()
if jobs is not None:
@@ -330,10 +330,18 @@ class Regrtest:
result = self.run_test(test_name, runtests, tracer)
- # Unload the newly imported modules (best effort finalization)
- for module in sys.modules.keys():
- if module not in save_modules and module.startswith("test."):
- support.unload(module)
+ # Unload the newly imported test modules (best effort finalization)
+ new_modules = [module for module in sys.modules
+ if module not in save_modules and
+ module.startswith(("test.", "test_"))]
+ for module in new_modules:
+ sys.modules.pop(module, None)
+ # Remove the attribute of the parent module.
+ parent, _, name = module.rpartition('.')
+ try:
+ delattr(sys.modules[parent], name)
+ except (KeyError, AttributeError):
+ pass
if result.must_stop(self.fail_fast, self.fail_env_changed):
break
diff --git a/Lib/test/libregrtest/single.py b/Lib/test/libregrtest/single.py
index eafeb5fe26f..235029d8620 100644
--- a/Lib/test/libregrtest/single.py
+++ b/Lib/test/libregrtest/single.py
@@ -122,10 +122,6 @@ def _load_run_test(result: TestResult, runtests: RunTests) -> None:
# Load the test module and run the tests.
test_name = result.test_name
module_name = abs_module_name(test_name, runtests.test_dir)
-
- # Remove the module from sys.module to reload it if it was already imported
- sys.modules.pop(module_name, None)
-
test_mod = importlib.import_module(module_name)
if hasattr(test_mod, "test_main"):
diff --git a/Lib/test/regrtestdata/import_from_tests/test_regrtest_a.py b/Lib/test/regrtestdata/import_from_tests/test_regrtest_a.py
new file mode 100644
index 00000000000..9c3d0c7cf4b
--- /dev/null
+++ b/Lib/test/regrtestdata/import_from_tests/test_regrtest_a.py
@@ -0,0 +1,11 @@
+import sys
+import unittest
+import test_regrtest_b.util
+
+class Test(unittest.TestCase):
+ def test(self):
+ test_regrtest_b.util # does not fail
+ self.assertIn('test_regrtest_a', sys.modules)
+ self.assertIs(sys.modules['test_regrtest_b'], test_regrtest_b)
+ self.assertIs(sys.modules['test_regrtest_b.util'], test_regrtest_b.util)
+ self.assertNotIn('test_regrtest_c', sys.modules)
diff --git a/Lib/test/regrtestdata/import_from_tests/test_regrtest_b/__init__.py b/Lib/test/regrtestdata/import_from_tests/test_regrtest_b/__init__.py
new file mode 100644
index 00000000000..3dfba253455
--- /dev/null
+++ b/Lib/test/regrtestdata/import_from_tests/test_regrtest_b/__init__.py
@@ -0,0 +1,9 @@
+import sys
+import unittest
+
+class Test(unittest.TestCase):
+ def test(self):
+ self.assertNotIn('test_regrtest_a', sys.modules)
+ self.assertIn('test_regrtest_b', sys.modules)
+ self.assertNotIn('test_regrtest_b.util', sys.modules)
+ self.assertNotIn('test_regrtest_c', sys.modules)
diff --git a/Lib/test/regrtestdata/import_from_tests/test_regrtest_b/util.py b/Lib/test/regrtestdata/import_from_tests/test_regrtest_b/util.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/Lib/test/regrtestdata/import_from_tests/test_regrtest_b/util.py
diff --git a/Lib/test/regrtestdata/import_from_tests/test_regrtest_c.py b/Lib/test/regrtestdata/import_from_tests/test_regrtest_c.py
new file mode 100644
index 00000000000..de80769118d
--- /dev/null
+++ b/Lib/test/regrtestdata/import_from_tests/test_regrtest_c.py
@@ -0,0 +1,11 @@
+import sys
+import unittest
+import test_regrtest_b.util
+
+class Test(unittest.TestCase):
+ def test(self):
+ test_regrtest_b.util # does not fail
+ self.assertNotIn('test_regrtest_a', sys.modules)
+ self.assertIs(sys.modules['test_regrtest_b'], test_regrtest_b)
+ self.assertIs(sys.modules['test_regrtest_b.util'], test_regrtest_b.util)
+ self.assertIn('test_regrtest_c', sys.modules)
diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py
index 2f1bb03bc0b..2ab6f6a9862 100644
--- a/Lib/test/test_regrtest.py
+++ b/Lib/test/test_regrtest.py
@@ -2021,6 +2021,25 @@ class ArgsTestCase(BaseTestCase):
self.check_executed_tests(output, tests,
stats=len(tests), parallel=True)
+ def test_unload_tests(self):
+ # Test that unloading test modules does not break tests
+ # that import from other tests.
+ # The test execution order matters for this test.
+ # Both test_regrtest_a and test_regrtest_c which are executed before
+ # and after test_regrtest_b import a submodule from the test_regrtest_b
+ # package and use it in testing. test_regrtest_b itself does not import
+ # that submodule.
+ # Previously test_regrtest_c failed because test_regrtest_b.util in
+ # sys.modules was left after test_regrtest_a (making the import
+ # statement no-op), but new test_regrtest_b without the util attribute
+ # was imported for test_regrtest_b.
+ testdir = os.path.join(os.path.dirname(__file__),
+ 'regrtestdata', 'import_from_tests')
+ tests = [f'test_regrtest_{name}' for name in ('a', 'b', 'c')]
+ args = ['-Wd', '-E', '-bb', '-m', 'test', '--testdir=%s' % testdir, *tests]
+ output = self.run_python(args)
+ self.check_executed_tests(output, tests, stats=3)
+
def check_add_python_opts(self, option):
# --fast-ci and --slow-ci add "-u -W default -bb -E" options to Python
code = textwrap.dedent(r"""
diff --git a/Misc/NEWS.d/next/Tests/2023-09-05-20-46-35.gh-issue-108927.TpwWav.rst b/Misc/NEWS.d/next/Tests/2023-09-05-20-46-35.gh-issue-108927.TpwWav.rst
new file mode 100644
index 00000000000..b1a78370afe
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2023-09-05-20-46-35.gh-issue-108927.TpwWav.rst
@@ -0,0 +1,4 @@
+Fixed order dependence in running tests in the same process
+when a test that has submodules (e.g. test_importlib) follows a test that
+imports its submodule (e.g. test_importlib.util) and precedes a test
+(e.g. test_unittest or test_compileall) that uses that submodule.