Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(198704)

Delta Between Two Patch Sets: Lib/test/test_faulthandler.py

Issue 16510: Using appropriate checks in tests
Left Patch Set: Created 6 years, 3 months ago
Right Patch Set: Created 5 years, 10 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « Lib/test/test_epoll.py ('k') | Lib/test/test_filecmp.py » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 from contextlib import contextmanager 1 from contextlib import contextmanager
2 import datetime 2 import datetime
3 import faulthandler 3 import faulthandler
4 import os 4 import os
5 import re 5 import re
6 import signal 6 import signal
7 import subprocess 7 import subprocess
8 import sys 8 import sys
9 from test import support, script_helper 9 from test import support, script_helper
10 from test.script_helper import assert_python_ok 10 from test.script_helper import assert_python_ok
11 import tempfile 11 import tempfile
12 import unittest 12 import unittest
13 13
14 try: 14 try:
15 import threading 15 import threading
16 HAVE_THREADS = True 16 HAVE_THREADS = True
17 except ImportError: 17 except ImportError:
18 HAVE_THREADS = False 18 HAVE_THREADS = False
19 19
20 TIMEOUT = 0.5 20 TIMEOUT = 0.5
21 21
22 try:
23 from resource import setrlimit, RLIMIT_CORE, error as resource_error
24 except ImportError:
25 prepare_subprocess = None
26 else:
27 def prepare_subprocess():
28 # don't create core file
29 try:
30 setrlimit(RLIMIT_CORE, (0, 0))
31 except (ValueError, resource_error):
32 pass
33
34 def expected_traceback(lineno1, lineno2, header, min_count=1): 22 def expected_traceback(lineno1, lineno2, header, min_count=1):
35 regex = header 23 regex = header
36 regex += ' File "<string>", line %s in func\n' % lineno1 24 regex += ' File "<string>", line %s in func\n' % lineno1
37 regex += ' File "<string>", line %s in <module>' % lineno2 25 regex += ' File "<string>", line %s in <module>' % lineno2
38 if 1 < min_count: 26 if 1 < min_count:
39 return '^' + (regex + '\n') * (min_count - 1) + regex 27 return '^' + (regex + '\n') * (min_count - 1) + regex
40 else: 28 else:
41 return '^' + regex + '$' 29 return '^' + regex + '$'
42 30
43 @contextmanager 31 @contextmanager
44 def temporary_filename(): 32 def temporary_filename():
45 filename = tempfile.mktemp() 33 filename = tempfile.mktemp()
46 try: 34 try:
47 yield filename 35 yield filename
48 finally: 36 finally:
49 support.unlink(filename) 37 support.unlink(filename)
50 38
51 class FaultHandlerTests(unittest.TestCase): 39 class FaultHandlerTests(unittest.TestCase):
52 def get_output(self, code, filename=None): 40 def get_output(self, code, filename=None):
53 """ 41 """
54 Run the specified code in Python (in a new child process) and read the 42 Run the specified code in Python (in a new child process) and read the
55 output from the standard error or from a file (if filename is set). 43 output from the standard error or from a file (if filename is set).
56 Return the output lines as a list. 44 Return the output lines as a list.
57 45
58 Strip the reference count from the standard error for Python debug 46 Strip the reference count from the standard error for Python debug
59 build, and replace "Current thread 0x00007f8d8fbd9700" by "Current 47 build, and replace "Current thread 0x00007f8d8fbd9700" by "Current
60 thread XXX". 48 thread XXX".
61 """ 49 """
62 options = {} 50 with support.SuppressCrashReport():
63 if prepare_subprocess: 51 process = script_helper.spawn_python('-c', code)
64 options['preexec_fn'] = prepare_subprocess
65 process = script_helper.spawn_python('-c', code, **options)
66 stdout, stderr = process.communicate() 52 stdout, stderr = process.communicate()
67 exitcode = process.wait() 53 exitcode = process.wait()
68 output = support.strip_python_stderr(stdout) 54 output = support.strip_python_stderr(stdout)
69 output = output.decode('ascii', 'backslashreplace') 55 output = output.decode('ascii', 'backslashreplace')
70 if filename: 56 if filename:
71 self.assertEqual(output, '') 57 self.assertEqual(output, '')
72 with open(filename, "rb") as fp: 58 with open(filename, "rb") as fp:
73 output = fp.read() 59 output = fp.read()
74 output = output.decode('ascii', 'backslashreplace') 60 output = output.decode('ascii', 'backslashreplace')
75 output = re.sub('Current thread 0x[0-9a-f]+', 61 output = re.sub('Current thread 0x[0-9a-f]+',
76 'Current thread XXX', 62 'Current thread XXX',
77 output) 63 output)
78 return output.splitlines(), exitcode 64 return output.splitlines(), exitcode
79 65
80 def check_fatal_error(self, code, line_number, name_regex, 66 def check_fatal_error(self, code, line_number, name_regex,
81 filename=None, all_threads=True, other_regex=None): 67 filename=None, all_threads=True, other_regex=None):
82 """ 68 """
83 Check that the fault handler for fatal errors is enabled and check the 69 Check that the fault handler for fatal errors is enabled and check the
84 traceback from the child process output. 70 traceback from the child process output.
85 71
86 Raise an error if the output doesn't match the expected format. 72 Raise an error if the output doesn't match the expected format.
87 """ 73 """
88 if all_threads: 74 if all_threads:
89 header = 'Current thread XXX' 75 header = 'Current thread XXX (most recent call first)'
90 else: 76 else:
91 header = 'Traceback (most recent call first)' 77 header = 'Stack (most recent call first)'
92 regex = """ 78 regex = """
93 ^Fatal Python error: {name} 79 ^Fatal Python error: {name}
94 80
95 {header}: 81 {header}:
96 File "<string>", line {lineno} in <module> 82 File "<string>", line {lineno} in <module>
97 """.strip() 83 """.strip()
98 regex = regex.format( 84 regex = regex.format(
99 lineno=line_number, 85 lineno=line_number,
100 name=name_regex, 86 name=name_regex,
101 header=re.escape(header)) 87 header=re.escape(header))
102 if other_regex: 88 if other_regex:
103 regex += '|' + other_regex 89 regex += '|' + other_regex
104 output, exitcode = self.get_output(code, filename) 90 output, exitcode = self.get_output(code, filename)
105 output = '\n'.join(output) 91 output = '\n'.join(output)
106 self.assertRegex(output, regex) 92 self.assertRegex(output, regex)
107 self.assertNotEqual(exitcode, 0) 93 self.assertNotEqual(exitcode, 0)
108 94
95 @unittest.skipIf(sys.platform.startswith('aix'),
96 "the first page of memory is a mapped read-only on AIX")
109 def test_read_null(self): 97 def test_read_null(self):
110 self.check_fatal_error(""" 98 self.check_fatal_error("""
111 import faulthandler 99 import faulthandler
112 faulthandler.enable() 100 faulthandler.enable()
113 faulthandler._read_null() 101 faulthandler._read_null()
114 """.strip(), 102 """.strip(),
115 3, 103 3,
116 # Issue #12700: Read NULL raises SIGILL on Mac OS X Lion 104 # Issue #12700: Read NULL raises SIGILL on Mac OS X Lion
117 '(?:Segmentation fault|Bus error|Illegal instruction)') 105 '(?:Segmentation fault|Bus error|Illegal instruction)')
118 106
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after
198 """.strip(), 186 """.strip(),
199 3, 187 3,
200 '(?:Segmentation fault|Bus error|Illegal instruction)') 188 '(?:Segmentation fault|Bus error|Illegal instruction)')
201 189
202 def test_enable_file(self): 190 def test_enable_file(self):
203 with temporary_filename() as filename: 191 with temporary_filename() as filename:
204 self.check_fatal_error(""" 192 self.check_fatal_error("""
205 import faulthandler 193 import faulthandler
206 output = open({filename}, 'wb') 194 output = open({filename}, 'wb')
207 faulthandler.enable(output) 195 faulthandler.enable(output)
208 faulthandler._read_null() 196 faulthandler._sigsegv()
209 """.strip().format(filename=repr(filename)), 197 """.strip().format(filename=repr(filename)),
210 4, 198 4,
211 '(?:Segmentation fault|Bus error|Illegal instruction)', 199 'Segmentation fault',
212 filename=filename) 200 filename=filename)
213 201
214 def test_enable_single_thread(self): 202 def test_enable_single_thread(self):
215 self.check_fatal_error(""" 203 self.check_fatal_error("""
216 import faulthandler 204 import faulthandler
217 faulthandler.enable(all_threads=False) 205 faulthandler.enable(all_threads=False)
218 faulthandler._read_null() 206 faulthandler._sigsegv()
219 """.strip(), 207 """.strip(),
220 3, 208 3,
221 '(?:Segmentation fault|Bus error|Illegal instruction)', 209 'Segmentation fault',
222 all_threads=False) 210 all_threads=False)
223 211
224 def test_disable(self): 212 def test_disable(self):
225 code = """ 213 code = """
226 import faulthandler 214 import faulthandler
227 faulthandler.enable() 215 faulthandler.enable()
228 faulthandler.disable() 216 faulthandler.disable()
229 faulthandler._read_null() 217 faulthandler._sigsegv()
230 """.strip() 218 """.strip()
231 not_expected = 'Fatal Python error' 219 not_expected = 'Fatal Python error'
232 stderr, exitcode = self.get_output(code) 220 stderr, exitcode = self.get_output(code)
233 stder = '\n'.join(stderr) 221 stder = '\n'.join(stderr)
234 self.assertNotIn(not_expected, stderr, 222 self.assertNotIn(not_expected, stderr,
235 "%r is present in %r" % (not_expected, stderr)) 223 "%r is present in %r" % (not_expected, stderr))
236 self.assertNotEqual(exitcode, 0) 224 self.assertNotEqual(exitcode, 0)
237 225
238 def test_is_enabled(self): 226 def test_is_enabled(self):
239 orig_stderr = sys.stderr 227 orig_stderr = sys.stderr
(...skipping 13 matching lines...) Expand all
253 if was_enabled: 241 if was_enabled:
254 faulthandler.enable() 242 faulthandler.enable()
255 else: 243 else:
256 faulthandler.disable() 244 faulthandler.disable()
257 finally: 245 finally:
258 sys.stderr = orig_stderr 246 sys.stderr = orig_stderr
259 247
260 def test_disabled_by_default(self): 248 def test_disabled_by_default(self):
261 # By default, the module should be disabled 249 # By default, the module should be disabled
262 code = "import faulthandler; print(faulthandler.is_enabled())" 250 code = "import faulthandler; print(faulthandler.is_enabled())"
263 rc, stdout, stderr = assert_python_ok("-c", code) 251 args = (sys.executable, '-E', '-c', code)
264 stdout = (stdout + stderr).strip() 252 # don't use assert_python_ok() because it always enable faulthandler
265 self.assertEqual(stdout, b"False") 253 output = subprocess.check_output(args)
254 self.assertEqual(output.rstrip(), b"False")
266 255
267 def test_sys_xoptions(self): 256 def test_sys_xoptions(self):
268 # Test python -X faulthandler 257 # Test python -X faulthandler
269 code = "import faulthandler; print(faulthandler.is_enabled())" 258 code = "import faulthandler; print(faulthandler.is_enabled())"
270 rc, stdout, stderr = assert_python_ok("-X", "faulthandler", "-c", code) 259 args = (sys.executable, "-E", "-X", "faulthandler", "-c", code)
271 stdout = (stdout + stderr).strip() 260 # don't use assert_python_ok() because it always enable faulthandler
272 self.assertEqual(stdout, b"True") 261 output = subprocess.check_output(args)
262 self.assertEqual(output.rstrip(), b"True")
263
264 def test_env_var(self):
265 # empty env var
266 code = "import faulthandler; print(faulthandler.is_enabled())"
267 args = (sys.executable, "-c", code)
268 env = os.environ.copy()
269 env['PYTHONFAULTHANDLER'] = ''
270 # don't use assert_python_ok() because it always enable faulthandler
271 output = subprocess.check_output(args, env=env)
272 self.assertEqual(output.rstrip(), b"False")
273
274 # non-empty env var
275 env = os.environ.copy()
276 env['PYTHONFAULTHANDLER'] = '1'
277 output = subprocess.check_output(args, env=env)
278 self.assertEqual(output.rstrip(), b"True")
273 279
274 def check_dump_traceback(self, filename): 280 def check_dump_traceback(self, filename):
275 """ 281 """
276 Explicitly call dump_traceback() function and check its output. 282 Explicitly call dump_traceback() function and check its output.
277 Raise an error if the output doesn't match the expected format. 283 Raise an error if the output doesn't match the expected format.
278 """ 284 """
279 code = """ 285 code = """
280 import faulthandler 286 import faulthandler
281 287
282 def funcB(): 288 def funcB():
(...skipping 10 matching lines...) Expand all
293 """.strip() 299 """.strip()
294 code = code.format( 300 code = code.format(
295 filename=repr(filename), 301 filename=repr(filename),
296 has_filename=bool(filename), 302 has_filename=bool(filename),
297 ) 303 )
298 if filename: 304 if filename:
299 lineno = 6 305 lineno = 6
300 else: 306 else:
301 lineno = 8 307 lineno = 8
302 expected = [ 308 expected = [
303 'Traceback (most recent call first):', 309 'Stack (most recent call first):',
304 ' File "<string>", line %s in funcB' % lineno, 310 ' File "<string>", line %s in funcB' % lineno,
305 ' File "<string>", line 11 in funcA', 311 ' File "<string>", line 11 in funcA',
306 ' File "<string>", line 13 in <module>' 312 ' File "<string>", line 13 in <module>'
307 ] 313 ]
308 trace, exitcode = self.get_output(code, filename) 314 trace, exitcode = self.get_output(code, filename)
309 self.assertEqual(trace, expected) 315 self.assertEqual(trace, expected)
310 self.assertEqual(exitcode, 0) 316 self.assertEqual(exitcode, 0)
311 317
312 def test_dump_traceback(self): 318 def test_dump_traceback(self):
313 self.check_dump_traceback(None) 319 self.check_dump_traceback(None)
(...skipping 11 matching lines...) Expand all
325 331
326 def {func_name}(): 332 def {func_name}():
327 faulthandler.dump_traceback(all_threads=False) 333 faulthandler.dump_traceback(all_threads=False)
328 334
329 {func_name}() 335 {func_name}()
330 """.strip() 336 """.strip()
331 code = code.format( 337 code = code.format(
332 func_name=func_name, 338 func_name=func_name,
333 ) 339 )
334 expected = [ 340 expected = [
335 'Traceback (most recent call first):', 341 'Stack (most recent call first):',
336 ' File "<string>", line 4 in %s' % truncated, 342 ' File "<string>", line 4 in %s' % truncated,
337 ' File "<string>", line 6 in <module>' 343 ' File "<string>", line 6 in <module>'
338 ] 344 ]
339 trace, exitcode = self.get_output(code) 345 trace, exitcode = self.get_output(code)
340 self.assertEqual(trace, expected) 346 self.assertEqual(trace, expected)
341 self.assertEqual(exitcode, 0) 347 self.assertEqual(exitcode, 0)
342 348
343 @unittest.skipIf(not HAVE_THREADS, 'need threads') 349 @unittest.skipIf(not HAVE_THREADS, 'need threads')
344 def check_dump_traceback_threads(self, filename): 350 def check_dump_traceback_threads(self, filename):
345 """ 351 """
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
379 waiter.join() 385 waiter.join()
380 """.strip() 386 """.strip()
381 code = code.format(filename=repr(filename)) 387 code = code.format(filename=repr(filename))
382 output, exitcode = self.get_output(code, filename) 388 output, exitcode = self.get_output(code, filename)
383 output = '\n'.join(output) 389 output = '\n'.join(output)
384 if filename: 390 if filename:
385 lineno = 8 391 lineno = 8
386 else: 392 else:
387 lineno = 10 393 lineno = 10
388 regex = """ 394 regex = """
389 ^Thread 0x[0-9a-f]+: 395 ^Thread 0x[0-9a-f]+ \(most recent call first\):
390 (?: File ".*threading.py", line [0-9]+ in [_a-z]+ 396 (?: File ".*threading.py", line [0-9]+ in [_a-z]+
391 ){{1,3}} File "<string>", line 23 in run 397 ){{1,3}} File "<string>", line 23 in run
392 File ".*threading.py", line [0-9]+ in _bootstrap_inner 398 File ".*threading.py", line [0-9]+ in _bootstrap_inner
393 File ".*threading.py", line [0-9]+ in _bootstrap 399 File ".*threading.py", line [0-9]+ in _bootstrap
394 400
395 Current thread XXX: 401 Current thread XXX \(most recent call first\):
396 File "<string>", line {lineno} in dump 402 File "<string>", line {lineno} in dump
397 File "<string>", line 28 in <module>$ 403 File "<string>", line 28 in <module>$
398 """.strip() 404 """.strip()
399 regex = regex.format(lineno=lineno) 405 regex = regex.format(lineno=lineno)
400 self.assertRegex(output, regex) 406 self.assertRegex(output, regex)
401 self.assertEqual(exitcode, 0) 407 self.assertEqual(exitcode, 0)
402 408
403 def test_dump_traceback_threads(self): 409 def test_dump_traceback_threads(self):
404 self.check_dump_traceback_threads(None) 410 self.check_dump_traceback_threads(None)
405 411
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
448 has_filename=bool(filename), 454 has_filename=bool(filename),
449 filename=repr(filename), 455 filename=repr(filename),
450 ) 456 )
451 trace, exitcode = self.get_output(code, filename) 457 trace, exitcode = self.get_output(code, filename)
452 trace = '\n'.join(trace) 458 trace = '\n'.join(trace)
453 459
454 if not cancel: 460 if not cancel:
455 count = loops 461 count = loops
456 if repeat: 462 if repeat:
457 count *= 2 463 count *= 2
458 header = r'Timeout \(%s\)!\nThread 0x[0-9a-f]+:\n' % timeout_str 464 header = r'Timeout \(%s\)!\nThread 0x[0-9a-f]+ \(most recent call fi rst\):\n' % timeout_str
459 regex = expected_traceback(9, 20, header, min_count=count) 465 regex = expected_traceback(9, 20, header, min_count=count)
460 self.assertRegex(trace, regex) 466 self.assertRegex(trace, regex)
461 else: 467 else:
462 self.assertEqual(trace, '') 468 self.assertEqual(trace, '')
463 self.assertEqual(exitcode, 0) 469 self.assertEqual(exitcode, 0)
464 470
465 @unittest.skipIf(not hasattr(faulthandler, 'dump_traceback_later'), 471 @unittest.skipIf(not hasattr(faulthandler, 'dump_traceback_later'),
466 'need faulthandler.dump_traceback_later()') 472 'need faulthandler.dump_traceback_later()')
467 def check_dump_traceback_later(self, repeat=False, cancel=False, 473 def check_dump_traceback_later(self, repeat=False, cancel=False,
468 file=False, twice=False): 474 file=False, twice=False):
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after
550 has_filename=bool(filename), 556 has_filename=bool(filename),
551 all_threads=all_threads, 557 all_threads=all_threads,
552 signum=signum, 558 signum=signum,
553 unregister=unregister, 559 unregister=unregister,
554 chain=chain, 560 chain=chain,
555 ) 561 )
556 trace, exitcode = self.get_output(code, filename) 562 trace, exitcode = self.get_output(code, filename)
557 trace = '\n'.join(trace) 563 trace = '\n'.join(trace)
558 if not unregister: 564 if not unregister:
559 if all_threads: 565 if all_threads:
560 regex = 'Current thread XXX:\n' 566 regex = 'Current thread XXX \(most recent call first\):\n'
561 else: 567 else:
562 regex = 'Traceback \(most recent call first\):\n' 568 regex = 'Stack \(most recent call first\):\n'
563 regex = expected_traceback(7, 28, regex) 569 regex = expected_traceback(7, 28, regex)
564 self.assertRegex(trace, regex) 570 self.assertRegex(trace, regex)
565 else: 571 else:
566 self.assertEqual(trace, '') 572 self.assertEqual(trace, '')
567 if unregister: 573 if unregister:
568 self.assertNotEqual(exitcode, 0) 574 self.assertNotEqual(exitcode, 0)
569 else: 575 else:
570 self.assertEqual(exitcode, 0) 576 self.assertEqual(exitcode, 0)
571 577
572 def test_register(self): 578 def test_register(self):
573 self.check_register() 579 self.check_register()
574 580
575 def test_unregister(self): 581 def test_unregister(self):
576 self.check_register(unregister=True) 582 self.check_register(unregister=True)
577 583
578 def test_register_file(self): 584 def test_register_file(self):
579 with temporary_filename() as filename: 585 with temporary_filename() as filename:
580 self.check_register(filename=filename) 586 self.check_register(filename=filename)
581 587
582 def test_register_threads(self): 588 def test_register_threads(self):
583 self.check_register(all_threads=True) 589 self.check_register(all_threads=True)
584 590
585 def test_register_chain(self): 591 def test_register_chain(self):
586 self.check_register(chain=True) 592 self.check_register(chain=True)
587 593
588 594
589 def test_main():
590 support.run_unittest(FaultHandlerTests)
591
592 if __name__ == "__main__": 595 if __name__ == "__main__":
593 test_main() 596 unittest.main()
LEFTRIGHT

RSS Feeds Recent Issues | This issue
This is Rietveld 894c83f36cb7+