Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

gh-102251: add missing cleanups for test_import #104796

Closed
wants to merge 12 commits into from
124 changes: 67 additions & 57 deletions Lib/test/test_import/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)')
Comment on lines -1992 to -1994
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This trick cannot be used with ./python -m test -j n, thus causing failures on CI.


spec = importlib.util.find_spec(cls.NAME)
from importlib.machinery import ExtensionFileLoader
cls.FILE = spec.origin
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down