diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index e0e2354ae2f19f..44848da036acfd 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -1982,17 +1982,11 @@ def parse(cls, text): return self -@requires_singlephase_init -class SinglephaseInitTests(unittest.TestCase): - +class SinglephaseInitTestMixin: NAME = '_testsinglephase' @classmethod def setUpClass(cls): - if '-R' in sys.argv or '--huntrleaks' in sys.argv: - # https://github.com/python/cpython/issues/102251 - raise unittest.SkipTest('unresolved refleaks (see gh-102251)') - spec = importlib.util.find_spec(cls.NAME) from importlib.machinery import ExtensionFileLoader cls.FILE = spec.origin @@ -2240,6 +2234,8 @@ def check_copied(self, loaded, base): self.assertEqual(snap.state_initialized, base.snapshot.state_initialized) +@requires_singlephase_init +class SinglephaseInitTests(SinglephaseInitTestMixin, unittest.TestCase): ######################### # the tests @@ -2503,8 +2499,8 @@ def test_basic_multiple_interpreters_main_no_reset(self): # * module's global state was updated, not reset @requires_subinterpreters - def test_basic_multiple_interpreters_deleted_no_reset(self): - # without resetting; already loaded in a deleted interpreter + def test_basic_multiple_interpreters_reset_each(self): + # resetting between each interpreter # At this point: # * alive in 0 interpreters @@ -2518,61 +2514,65 @@ def test_basic_multiple_interpreters_deleted_no_reset(self): interpid1 = self.add_subinterpreter() interpid2 = self.add_subinterpreter() - # First, load in the main interpreter but then completely clear it. - loaded_main = self.load(self.NAME) - loaded_main.module._clear_globals() - _testinternalcapi.clear_extension(self.NAME, self.FILE) - - # At this point: - # * alive in 0 interpreters - # * module def loaded already - # * module def was in _PyRuntime.imports.extensions, but cleared - # * mod init func ran for the first time (since reset, at least) - # * m_copy was set, but cleared (was NULL) - # * module's global state was initialized but cleared - - # Start with an interpreter that gets destroyed right away. - base = self.import_in_subinterp(postscript=''' + # Use an interpreter that gets destroyed right away. + loaded = self.import_in_subinterp( + postscript=''' # Attrs set after loading are not in m_copy. mod.spam = 'spam, spam, mash, spam, eggs, and spam' - ''') - self.check_common(base) - self.check_fresh(base) + ''', + postcleanup=True, + ) + self.check_common(loaded) + self.check_fresh(loaded) # At this point: # * alive in 0 interpreters # * module def in _PyRuntime.imports.extensions - # * mod init func ran again + # * mod init func ran for the first time (since reset, at least) # * m_copy is NULL (claered when the interpreter was destroyed) # * module's global state was initialized, not reset # Use a subinterpreter that sticks around. - loaded_interp1 = self.import_in_subinterp(interpid1) - self.check_common(loaded_interp1) - self.check_semi_fresh(loaded_interp1, loaded_main, base) + loaded = self.import_in_subinterp(interpid1, postcleanup=True) + self.check_common(loaded) + self.check_fresh(loaded) # At this point: # * alive in 1 interpreter (interp1) # * module def still in _PyRuntime.imports.extensions # * mod init func ran again # * m_copy was copied from interp1 (was NULL) - # * module's global state was updated, not reset + # * module's global state was initialized, not reset # Use a subinterpreter while the previous one is still alive. - loaded_interp2 = self.import_in_subinterp(interpid2) - self.check_common(loaded_interp2) - self.check_copied(loaded_interp2, loaded_interp1) + loaded = self.import_in_subinterp(interpid2, postcleanup=True) + self.check_common(loaded) + self.check_fresh(loaded) # At this point: - # * alive in 2 interpreters (interp1, interp2) + # * alive in 2 interpreters (interp2, interp2) # * module def still in _PyRuntime.imports.extensions # * mod init func ran again # * m_copy was copied from interp2 (was from interp1) - # * module's global state was updated, not reset + # * module's global state was initialized, not reset + + +@requires_singlephase_init +class SinglephaseInitTestsNoRerun(SinglephaseInitTestMixin, unittest.TestCase): + # Tests does not support rerunning are collected in this class + _has_run = False + + @classmethod + def setUpClass(cls): + super().setUpClass() + if cls._has_run: + raise unittest.SkipTest("Tests do not support rerunning") + cls._has_run = True @requires_subinterpreters - def test_basic_multiple_interpreters_reset_each(self): - # resetting between each interpreter + def test_basic_multiple_interpreters_deleted_no_reset(self): + # without resetting; already loaded in a deleted interpreter + # This test intentionally leaks references # At this point: # * alive in 0 interpreters @@ -2586,47 +2586,57 @@ def test_basic_multiple_interpreters_reset_each(self): interpid1 = self.add_subinterpreter() interpid2 = self.add_subinterpreter() - # Use an interpreter that gets destroyed right away. - loaded = self.import_in_subinterp( - postscript=''' + # First, load in the main interpreter but then completely clear it. + loaded_main = self.load(self.NAME) + loaded_main.module._clear_globals() + _testinternalcapi.clear_extension(self.NAME, self.FILE) + + # At this point: + # * alive in 0 interpreters + # * module def loaded already + # * module def was in _PyRuntime.imports.extensions, but cleared + # * mod init func ran for the first time (since reset, at least) + # * m_copy was set, but cleared (was NULL) + # * module's global state was initialized but cleared + + # Start with an interpreter that gets destroyed right away. + base = self.import_in_subinterp(postscript=''' # Attrs set after loading are not in m_copy. mod.spam = 'spam, spam, mash, spam, eggs, and spam' - ''', - postcleanup=True, - ) - self.check_common(loaded) - self.check_fresh(loaded) + ''') + self.check_common(base) + self.check_fresh(base) # At this point: # * alive in 0 interpreters # * module def in _PyRuntime.imports.extensions - # * mod init func ran for the first time (since reset, at least) + # * mod init func ran again # * m_copy is NULL (claered when the interpreter was destroyed) # * module's global state was initialized, not reset # Use a subinterpreter that sticks around. - loaded = self.import_in_subinterp(interpid1, postcleanup=True) - self.check_common(loaded) - self.check_fresh(loaded) + loaded_interp1 = self.import_in_subinterp(interpid1) + self.check_common(loaded_interp1) + self.check_semi_fresh(loaded_interp1, loaded_main, base) # At this point: # * alive in 1 interpreter (interp1) # * module def still in _PyRuntime.imports.extensions # * mod init func ran again # * m_copy was copied from interp1 (was NULL) - # * module's global state was initialized, not reset + # * module's global state was updated, not reset # Use a subinterpreter while the previous one is still alive. - loaded = self.import_in_subinterp(interpid2, postcleanup=True) - self.check_common(loaded) - self.check_fresh(loaded) + loaded_interp2 = self.import_in_subinterp(interpid2) + self.check_common(loaded_interp2) + self.check_copied(loaded_interp2, loaded_interp1) # At this point: - # * alive in 2 interpreters (interp2, interp2) + # * alive in 2 interpreters (interp1, interp2) # * module def still in _PyRuntime.imports.extensions # * mod init func ran again # * m_copy was copied from interp2 (was from interp1) - # * module's global state was initialized, not reset + # * module's global state was updated, not reset @cpython_only