|
1 | 1 | import atexit |
2 | 2 | import os |
| 3 | +import subprocess |
3 | 4 | import textwrap |
4 | 5 | import unittest |
| 6 | +from test.support import os_helper |
5 | 7 | from test import support |
6 | | -from test.support import script_helper |
| 8 | +from test.support import SuppressCrashReport, script_helper |
7 | 9 | from test.support import threading_helper |
8 | 10 |
|
9 | 11 | class GeneralTest(unittest.TestCase): |
@@ -133,6 +135,37 @@ def callback(): |
133 | 135 | self.assertEqual(os.read(r, len(expected)), expected) |
134 | 136 | os.close(r) |
135 | 137 |
|
| 138 | + # Python built with Py_TRACE_REFS fail with a fatal error in |
| 139 | + # _PyRefchain_Trace() on memory allocation error. |
| 140 | + @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build') |
| 141 | + def test_atexit_with_low_memory(self): |
| 142 | + # gh-140080: Test that setting low memory after registering an atexit |
| 143 | + # callback doesn't cause an infinite loop during finalization. |
| 144 | + code = textwrap.dedent(""" |
| 145 | + import atexit |
| 146 | + import _testcapi |
| 147 | +
|
| 148 | + def callback(): |
| 149 | + print("hello") |
| 150 | +
|
| 151 | + atexit.register(callback) |
| 152 | + # Simulate low memory condition |
| 153 | + _testcapi.set_nomemory(0) |
| 154 | + """) |
| 155 | + |
| 156 | + with os_helper.temp_dir() as temp_dir: |
| 157 | + script = script_helper.make_script(temp_dir, 'test_atexit_script', code) |
| 158 | + with SuppressCrashReport(): |
| 159 | + with script_helper.spawn_python(script, |
| 160 | + stderr=subprocess.PIPE) as proc: |
| 161 | + proc.wait() |
| 162 | + stdout = proc.stdout.read() |
| 163 | + stderr = proc.stderr.read() |
| 164 | + |
| 165 | + self.assertIn(proc.returncode, (0, 1)) |
| 166 | + self.assertNotIn(b"hello", stdout) |
| 167 | + self.assertIn(b"MemoryError", stderr) |
| 168 | + |
136 | 169 |
|
137 | 170 | if __name__ == "__main__": |
138 | 171 | unittest.main() |
0 commit comments