@@ -89,10 +89,12 @@ def get_output(self, code, filename=None, fd=None):
8989 output = output .decode ('ascii' , 'backslashreplace' )
9090 return output .splitlines (), exitcode
9191
92- def check_error (self , code , line_number , fatal_error , * ,
92+ def check_error (self , code , lineno , fatal_error , * ,
9393 filename = None , all_threads = True , other_regex = None ,
9494 fd = None , know_current_thread = True ,
95- py_fatal_error = False ):
95+ py_fatal_error = False ,
96+ garbage_collecting = False ,
97+ function = '<module>' ):
9698 """
9799 Check that the fault handler for fatal errors is enabled and check the
98100 traceback from the child process output.
@@ -106,20 +108,21 @@ def check_error(self, code, line_number, fatal_error, *,
106108 header = 'Thread 0x[0-9a-f]+'
107109 else :
108110 header = 'Stack'
109- regex = r"""
110- (?m)^{fatal_error}
111-
112- {header} \(most recent call first\):
113- File "<string>", line {lineno} in <module>
114- """
111+ regex = [f'^{ fatal_error } ' ]
115112 if py_fatal_error :
116- fatal_error += "\n Python runtime state: initialized"
117- regex = dedent (regex ).format (
118- lineno = line_number ,
119- fatal_error = fatal_error ,
120- header = header ).strip ()
113+ regex .append ("Python runtime state: initialized" )
114+ regex .append ('' )
115+ regex .append (fr'{ header } \(most recent call first\):' )
116+ if garbage_collecting :
117+ regex .append (' Garbage-collecting' )
118+ regex .append (fr' File "<string>", line { lineno } in { function } ' )
119+ regex = '\n ' .join (regex )
120+
121121 if other_regex :
122- regex += '|' + other_regex
122+ regex = f'(?:{ regex } |{ other_regex } )'
123+
124+ # Enable MULTILINE flag
125+ regex = f'(?m){ regex } '
123126 output , exitcode = self .get_output (code , filename = filename , fd = fd )
124127 output = '\n ' .join (output )
125128 self .assertRegex (output , regex )
@@ -168,6 +171,42 @@ def test_sigsegv(self):
168171 3 ,
169172 'Segmentation fault' )
170173
174+ @skip_segfault_on_android
175+ def test_gc (self ):
176+ # bpo-44466: Detect if the GC is running
177+ self .check_fatal_error ("""
178+ import faulthandler
179+ import gc
180+ import sys
181+
182+ faulthandler.enable()
183+
184+ class RefCycle:
185+ def __del__(self):
186+ faulthandler._sigsegv()
187+
188+ # create a reference cycle which triggers a fatal
189+ # error in a destructor
190+ a = RefCycle()
191+ b = RefCycle()
192+ a.b = b
193+ b.a = a
194+
195+ # Delete the objects, not the cycle
196+ a = None
197+ b = None
198+
199+ # Break the reference cycle: call __del__()
200+ gc.collect()
201+
202+ # Should not reach this line
203+ print("exit", file=sys.stderr)
204+ """ ,
205+ 9 ,
206+ 'Segmentation fault' ,
207+ function = '__del__' ,
208+ garbage_collecting = True )
209+
171210 def test_fatal_error_c_thread (self ):
172211 self .check_fatal_error ("""
173212 import faulthandler
0 commit comments