test_faulthandler.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896
  1. from contextlib import contextmanager
  2. import datetime
  3. import faulthandler
  4. import os
  5. import re
  6. import signal
  7. import subprocess
  8. import sys
  9. from test import support
  10. from test.support import os_helper
  11. from test.support import script_helper, is_android
  12. from test.support import skip_if_sanitizer
  13. import tempfile
  14. import unittest
  15. from textwrap import dedent
  16. try:
  17. import _testcapi
  18. except ImportError:
  19. _testcapi = None
  20. if not support.has_subprocess_support:
  21. raise unittest.SkipTest("test module requires subprocess")
  22. TIMEOUT = 0.5
  23. MS_WINDOWS = (os.name == 'nt')
  24. def expected_traceback(lineno1, lineno2, header, min_count=1):
  25. regex = header
  26. regex += ' File "<string>", line %s in func\n' % lineno1
  27. regex += ' File "<string>", line %s in <module>' % lineno2
  28. if 1 < min_count:
  29. return '^' + (regex + '\n') * (min_count - 1) + regex
  30. else:
  31. return '^' + regex + '$'
  32. def skip_segfault_on_android(test):
  33. # Issue #32138: Raising SIGSEGV on Android may not cause a crash.
  34. return unittest.skipIf(is_android,
  35. 'raising SIGSEGV on Android is unreliable')(test)
  36. @contextmanager
  37. def temporary_filename():
  38. filename = tempfile.mktemp()
  39. try:
  40. yield filename
  41. finally:
  42. os_helper.unlink(filename)
  43. class FaultHandlerTests(unittest.TestCase):
  44. def get_output(self, code, filename=None, fd=None):
  45. """
  46. Run the specified code in Python (in a new child process) and read the
  47. output from the standard error or from a file (if filename is set).
  48. Return the output lines as a list.
  49. Strip the reference count from the standard error for Python debug
  50. build, and replace "Current thread 0x00007f8d8fbd9700" by "Current
  51. thread XXX".
  52. """
  53. code = dedent(code).strip()
  54. pass_fds = []
  55. if fd is not None:
  56. pass_fds.append(fd)
  57. with support.SuppressCrashReport():
  58. process = script_helper.spawn_python('-c', code, pass_fds=pass_fds)
  59. with process:
  60. output, stderr = process.communicate()
  61. exitcode = process.wait()
  62. output = output.decode('ascii', 'backslashreplace')
  63. if filename:
  64. self.assertEqual(output, '')
  65. with open(filename, "rb") as fp:
  66. output = fp.read()
  67. output = output.decode('ascii', 'backslashreplace')
  68. elif fd is not None:
  69. self.assertEqual(output, '')
  70. os.lseek(fd, os.SEEK_SET, 0)
  71. with open(fd, "rb", closefd=False) as fp:
  72. output = fp.read()
  73. output = output.decode('ascii', 'backslashreplace')
  74. return output.splitlines(), exitcode
  75. def check_error(self, code, lineno, fatal_error, *,
  76. filename=None, all_threads=True, other_regex=None,
  77. fd=None, know_current_thread=True,
  78. py_fatal_error=False,
  79. garbage_collecting=False,
  80. function='<module>'):
  81. """
  82. Check that the fault handler for fatal errors is enabled and check the
  83. traceback from the child process output.
  84. Raise an error if the output doesn't match the expected format.
  85. """
  86. if all_threads:
  87. if know_current_thread:
  88. header = 'Current thread 0x[0-9a-f]+'
  89. else:
  90. header = 'Thread 0x[0-9a-f]+'
  91. else:
  92. header = 'Stack'
  93. regex = [f'^{fatal_error}']
  94. if py_fatal_error:
  95. regex.append("Python runtime state: initialized")
  96. regex.append('')
  97. regex.append(fr'{header} \(most recent call first\):')
  98. if garbage_collecting:
  99. regex.append(' Garbage-collecting')
  100. regex.append(fr' File "<string>", line {lineno} in {function}')
  101. regex = '\n'.join(regex)
  102. if other_regex:
  103. regex = f'(?:{regex}|{other_regex})'
  104. # Enable MULTILINE flag
  105. regex = f'(?m){regex}'
  106. output, exitcode = self.get_output(code, filename=filename, fd=fd)
  107. output = '\n'.join(output)
  108. self.assertRegex(output, regex)
  109. self.assertNotEqual(exitcode, 0)
  110. def check_fatal_error(self, code, line_number, name_regex, func=None, **kw):
  111. if func:
  112. name_regex = '%s: %s' % (func, name_regex)
  113. fatal_error = 'Fatal Python error: %s' % name_regex
  114. self.check_error(code, line_number, fatal_error, **kw)
  115. def check_windows_exception(self, code, line_number, name_regex, **kw):
  116. fatal_error = 'Windows fatal exception: %s' % name_regex
  117. self.check_error(code, line_number, fatal_error, **kw)
  118. @unittest.skipIf(sys.platform.startswith('aix'),
  119. "the first page of memory is a mapped read-only on AIX")
  120. def test_read_null(self):
  121. if not MS_WINDOWS:
  122. self.check_fatal_error("""
  123. import faulthandler
  124. faulthandler.enable()
  125. faulthandler._read_null()
  126. """,
  127. 3,
  128. # Issue #12700: Read NULL raises SIGILL on Mac OS X Lion
  129. '(?:Segmentation fault'
  130. '|Bus error'
  131. '|Illegal instruction)')
  132. else:
  133. self.check_windows_exception("""
  134. import faulthandler
  135. faulthandler.enable()
  136. faulthandler._read_null()
  137. """,
  138. 3,
  139. 'access violation')
  140. @skip_segfault_on_android
  141. def test_sigsegv(self):
  142. self.check_fatal_error("""
  143. import faulthandler
  144. faulthandler.enable()
  145. faulthandler._sigsegv()
  146. """,
  147. 3,
  148. 'Segmentation fault')
  149. @skip_segfault_on_android
  150. def test_gc(self):
  151. # bpo-44466: Detect if the GC is running
  152. self.check_fatal_error("""
  153. import faulthandler
  154. import gc
  155. import sys
  156. faulthandler.enable()
  157. class RefCycle:
  158. def __del__(self):
  159. faulthandler._sigsegv()
  160. # create a reference cycle which triggers a fatal
  161. # error in a destructor
  162. a = RefCycle()
  163. b = RefCycle()
  164. a.b = b
  165. b.a = a
  166. # Delete the objects, not the cycle
  167. a = None
  168. b = None
  169. # Break the reference cycle: call __del__()
  170. gc.collect()
  171. # Should not reach this line
  172. print("exit", file=sys.stderr)
  173. """,
  174. 9,
  175. 'Segmentation fault',
  176. function='__del__',
  177. garbage_collecting=True)
  178. def test_fatal_error_c_thread(self):
  179. self.check_fatal_error("""
  180. import faulthandler
  181. faulthandler.enable()
  182. faulthandler._fatal_error_c_thread()
  183. """,
  184. 3,
  185. 'in new thread',
  186. know_current_thread=False,
  187. func='faulthandler_fatal_error_thread',
  188. py_fatal_error=True)
  189. def test_sigabrt(self):
  190. self.check_fatal_error("""
  191. import faulthandler
  192. faulthandler.enable()
  193. faulthandler._sigabrt()
  194. """,
  195. 3,
  196. 'Aborted')
  197. @unittest.skipIf(sys.platform == 'win32',
  198. "SIGFPE cannot be caught on Windows")
  199. def test_sigfpe(self):
  200. self.check_fatal_error("""
  201. import faulthandler
  202. faulthandler.enable()
  203. faulthandler._sigfpe()
  204. """,
  205. 3,
  206. 'Floating point exception')
  207. @unittest.skipIf(_testcapi is None, 'need _testcapi')
  208. @unittest.skipUnless(hasattr(signal, 'SIGBUS'), 'need signal.SIGBUS')
  209. @skip_segfault_on_android
  210. def test_sigbus(self):
  211. self.check_fatal_error("""
  212. import faulthandler
  213. import signal
  214. faulthandler.enable()
  215. signal.raise_signal(signal.SIGBUS)
  216. """,
  217. 5,
  218. 'Bus error')
  219. @unittest.skipIf(_testcapi is None, 'need _testcapi')
  220. @unittest.skipUnless(hasattr(signal, 'SIGILL'), 'need signal.SIGILL')
  221. @skip_segfault_on_android
  222. def test_sigill(self):
  223. self.check_fatal_error("""
  224. import faulthandler
  225. import signal
  226. faulthandler.enable()
  227. signal.raise_signal(signal.SIGILL)
  228. """,
  229. 5,
  230. 'Illegal instruction')
  231. def check_fatal_error_func(self, release_gil):
  232. # Test that Py_FatalError() dumps a traceback
  233. with support.SuppressCrashReport():
  234. self.check_fatal_error(f"""
  235. import _testcapi
  236. _testcapi.fatal_error(b'xyz', {release_gil})
  237. """,
  238. 2,
  239. 'xyz',
  240. func='test_fatal_error',
  241. py_fatal_error=True)
  242. def test_fatal_error(self):
  243. self.check_fatal_error_func(False)
  244. def test_fatal_error_without_gil(self):
  245. self.check_fatal_error_func(True)
  246. @unittest.skipIf(sys.platform.startswith('openbsd'),
  247. "Issue #12868: sigaltstack() doesn't work on "
  248. "OpenBSD if Python is compiled with pthread")
  249. @unittest.skipIf(not hasattr(faulthandler, '_stack_overflow'),
  250. 'need faulthandler._stack_overflow()')
  251. def test_stack_overflow(self):
  252. self.check_fatal_error("""
  253. import faulthandler
  254. faulthandler.enable()
  255. faulthandler._stack_overflow()
  256. """,
  257. 3,
  258. '(?:Segmentation fault|Bus error)',
  259. other_regex='unable to raise a stack overflow')
  260. @skip_segfault_on_android
  261. def test_gil_released(self):
  262. self.check_fatal_error("""
  263. import faulthandler
  264. faulthandler.enable()
  265. faulthandler._sigsegv(True)
  266. """,
  267. 3,
  268. 'Segmentation fault')
  269. @skip_if_sanitizer(memory=True, ub=True, reason="sanitizer "
  270. "builds change crashing process output.")
  271. @skip_segfault_on_android
  272. def test_enable_file(self):
  273. with temporary_filename() as filename:
  274. self.check_fatal_error("""
  275. import faulthandler
  276. output = open({filename}, 'wb')
  277. faulthandler.enable(output)
  278. faulthandler._sigsegv()
  279. """.format(filename=repr(filename)),
  280. 4,
  281. 'Segmentation fault',
  282. filename=filename)
  283. @unittest.skipIf(sys.platform == "win32",
  284. "subprocess doesn't support pass_fds on Windows")
  285. @skip_if_sanitizer(memory=True, ub=True, reason="sanitizer "
  286. "builds change crashing process output.")
  287. @skip_segfault_on_android
  288. def test_enable_fd(self):
  289. with tempfile.TemporaryFile('wb+') as fp:
  290. fd = fp.fileno()
  291. self.check_fatal_error("""
  292. import faulthandler
  293. import sys
  294. faulthandler.enable(%s)
  295. faulthandler._sigsegv()
  296. """ % fd,
  297. 4,
  298. 'Segmentation fault',
  299. fd=fd)
  300. @skip_segfault_on_android
  301. def test_enable_single_thread(self):
  302. self.check_fatal_error("""
  303. import faulthandler
  304. faulthandler.enable(all_threads=False)
  305. faulthandler._sigsegv()
  306. """,
  307. 3,
  308. 'Segmentation fault',
  309. all_threads=False)
  310. @skip_segfault_on_android
  311. def test_disable(self):
  312. code = """
  313. import faulthandler
  314. faulthandler.enable()
  315. faulthandler.disable()
  316. faulthandler._sigsegv()
  317. """
  318. not_expected = 'Fatal Python error'
  319. stderr, exitcode = self.get_output(code)
  320. stderr = '\n'.join(stderr)
  321. self.assertTrue(not_expected not in stderr,
  322. "%r is present in %r" % (not_expected, stderr))
  323. self.assertNotEqual(exitcode, 0)
  324. @skip_segfault_on_android
  325. def test_dump_ext_modules(self):
  326. code = """
  327. import faulthandler
  328. import sys
  329. # Don't filter stdlib module names
  330. sys.stdlib_module_names = frozenset()
  331. faulthandler.enable()
  332. faulthandler._sigsegv()
  333. """
  334. stderr, exitcode = self.get_output(code)
  335. stderr = '\n'.join(stderr)
  336. match = re.search(r'^Extension modules:(.*) \(total: [0-9]+\)$',
  337. stderr, re.MULTILINE)
  338. if not match:
  339. self.fail(f"Cannot find 'Extension modules:' in {stderr!r}")
  340. modules = set(match.group(1).strip().split(', '))
  341. for name in ('sys', 'faulthandler'):
  342. self.assertIn(name, modules)
  343. def test_is_enabled(self):
  344. orig_stderr = sys.stderr
  345. try:
  346. # regrtest may replace sys.stderr by io.StringIO object, but
  347. # faulthandler.enable() requires that sys.stderr has a fileno()
  348. # method
  349. sys.stderr = sys.__stderr__
  350. was_enabled = faulthandler.is_enabled()
  351. try:
  352. faulthandler.enable()
  353. self.assertTrue(faulthandler.is_enabled())
  354. faulthandler.disable()
  355. self.assertFalse(faulthandler.is_enabled())
  356. finally:
  357. if was_enabled:
  358. faulthandler.enable()
  359. else:
  360. faulthandler.disable()
  361. finally:
  362. sys.stderr = orig_stderr
  363. @support.requires_subprocess()
  364. def test_disabled_by_default(self):
  365. # By default, the module should be disabled
  366. code = "import faulthandler; print(faulthandler.is_enabled())"
  367. args = (sys.executable, "-E", "-c", code)
  368. # don't use assert_python_ok() because it always enables faulthandler
  369. output = subprocess.check_output(args)
  370. self.assertEqual(output.rstrip(), b"False")
  371. @support.requires_subprocess()
  372. def test_sys_xoptions(self):
  373. # Test python -X faulthandler
  374. code = "import faulthandler; print(faulthandler.is_enabled())"
  375. args = filter(None, (sys.executable,
  376. "-E" if sys.flags.ignore_environment else "",
  377. "-X", "faulthandler", "-c", code))
  378. env = os.environ.copy()
  379. env.pop("PYTHONFAULTHANDLER", None)
  380. # don't use assert_python_ok() because it always enables faulthandler
  381. output = subprocess.check_output(args, env=env)
  382. self.assertEqual(output.rstrip(), b"True")
  383. @support.requires_subprocess()
  384. def test_env_var(self):
  385. # empty env var
  386. code = "import faulthandler; print(faulthandler.is_enabled())"
  387. args = (sys.executable, "-c", code)
  388. env = dict(os.environ)
  389. env['PYTHONFAULTHANDLER'] = ''
  390. env['PYTHONDEVMODE'] = ''
  391. # don't use assert_python_ok() because it always enables faulthandler
  392. output = subprocess.check_output(args, env=env)
  393. self.assertEqual(output.rstrip(), b"False")
  394. # non-empty env var
  395. env = dict(os.environ)
  396. env['PYTHONFAULTHANDLER'] = '1'
  397. env['PYTHONDEVMODE'] = ''
  398. output = subprocess.check_output(args, env=env)
  399. self.assertEqual(output.rstrip(), b"True")
  400. def check_dump_traceback(self, *, filename=None, fd=None):
  401. """
  402. Explicitly call dump_traceback() function and check its output.
  403. Raise an error if the output doesn't match the expected format.
  404. """
  405. code = """
  406. import faulthandler
  407. filename = {filename!r}
  408. fd = {fd}
  409. def funcB():
  410. if filename:
  411. with open(filename, "wb") as fp:
  412. faulthandler.dump_traceback(fp, all_threads=False)
  413. elif fd is not None:
  414. faulthandler.dump_traceback(fd,
  415. all_threads=False)
  416. else:
  417. faulthandler.dump_traceback(all_threads=False)
  418. def funcA():
  419. funcB()
  420. funcA()
  421. """
  422. code = code.format(
  423. filename=filename,
  424. fd=fd,
  425. )
  426. if filename:
  427. lineno = 9
  428. elif fd is not None:
  429. lineno = 11
  430. else:
  431. lineno = 14
  432. expected = [
  433. 'Stack (most recent call first):',
  434. ' File "<string>", line %s in funcB' % lineno,
  435. ' File "<string>", line 17 in funcA',
  436. ' File "<string>", line 19 in <module>'
  437. ]
  438. trace, exitcode = self.get_output(code, filename, fd)
  439. self.assertEqual(trace, expected)
  440. self.assertEqual(exitcode, 0)
  441. def test_dump_traceback(self):
  442. self.check_dump_traceback()
  443. def test_dump_traceback_file(self):
  444. with temporary_filename() as filename:
  445. self.check_dump_traceback(filename=filename)
  446. @unittest.skipIf(sys.platform == "win32",
  447. "subprocess doesn't support pass_fds on Windows")
  448. def test_dump_traceback_fd(self):
  449. with tempfile.TemporaryFile('wb+') as fp:
  450. self.check_dump_traceback(fd=fp.fileno())
  451. def test_truncate(self):
  452. maxlen = 500
  453. func_name = 'x' * (maxlen + 50)
  454. truncated = 'x' * maxlen + '...'
  455. code = """
  456. import faulthandler
  457. def {func_name}():
  458. faulthandler.dump_traceback(all_threads=False)
  459. {func_name}()
  460. """
  461. code = code.format(
  462. func_name=func_name,
  463. )
  464. expected = [
  465. 'Stack (most recent call first):',
  466. ' File "<string>", line 4 in %s' % truncated,
  467. ' File "<string>", line 6 in <module>'
  468. ]
  469. trace, exitcode = self.get_output(code)
  470. self.assertEqual(trace, expected)
  471. self.assertEqual(exitcode, 0)
  472. def check_dump_traceback_threads(self, filename):
  473. """
  474. Call explicitly dump_traceback(all_threads=True) and check the output.
  475. Raise an error if the output doesn't match the expected format.
  476. """
  477. code = """
  478. import faulthandler
  479. from threading import Thread, Event
  480. import time
  481. def dump():
  482. if {filename}:
  483. with open({filename}, "wb") as fp:
  484. faulthandler.dump_traceback(fp, all_threads=True)
  485. else:
  486. faulthandler.dump_traceback(all_threads=True)
  487. class Waiter(Thread):
  488. # avoid blocking if the main thread raises an exception.
  489. daemon = True
  490. def __init__(self):
  491. Thread.__init__(self)
  492. self.running = Event()
  493. self.stop = Event()
  494. def run(self):
  495. self.running.set()
  496. self.stop.wait()
  497. waiter = Waiter()
  498. waiter.start()
  499. waiter.running.wait()
  500. dump()
  501. waiter.stop.set()
  502. waiter.join()
  503. """
  504. code = code.format(filename=repr(filename))
  505. output, exitcode = self.get_output(code, filename)
  506. output = '\n'.join(output)
  507. if filename:
  508. lineno = 8
  509. else:
  510. lineno = 10
  511. regex = r"""
  512. ^Thread 0x[0-9a-f]+ \(most recent call first\):
  513. (?: File ".*threading.py", line [0-9]+ in [_a-z]+
  514. ){{1,3}} File "<string>", line 23 in run
  515. File ".*threading.py", line [0-9]+ in _bootstrap_inner
  516. File ".*threading.py", line [0-9]+ in _bootstrap
  517. Current thread 0x[0-9a-f]+ \(most recent call first\):
  518. File "<string>", line {lineno} in dump
  519. File "<string>", line 28 in <module>$
  520. """
  521. regex = dedent(regex.format(lineno=lineno)).strip()
  522. self.assertRegex(output, regex)
  523. self.assertEqual(exitcode, 0)
  524. def test_dump_traceback_threads(self):
  525. self.check_dump_traceback_threads(None)
  526. def test_dump_traceback_threads_file(self):
  527. with temporary_filename() as filename:
  528. self.check_dump_traceback_threads(filename)
  529. def check_dump_traceback_later(self, repeat=False, cancel=False, loops=1,
  530. *, filename=None, fd=None):
  531. """
  532. Check how many times the traceback is written in timeout x 2.5 seconds,
  533. or timeout x 3.5 seconds if cancel is True: 1, 2 or 3 times depending
  534. on repeat and cancel options.
  535. Raise an error if the output doesn't match the expect format.
  536. """
  537. timeout_str = str(datetime.timedelta(seconds=TIMEOUT))
  538. code = """
  539. import faulthandler
  540. import time
  541. import sys
  542. timeout = {timeout}
  543. repeat = {repeat}
  544. cancel = {cancel}
  545. loops = {loops}
  546. filename = {filename!r}
  547. fd = {fd}
  548. def func(timeout, repeat, cancel, file, loops):
  549. for loop in range(loops):
  550. faulthandler.dump_traceback_later(timeout, repeat=repeat, file=file)
  551. if cancel:
  552. faulthandler.cancel_dump_traceback_later()
  553. time.sleep(timeout * 5)
  554. faulthandler.cancel_dump_traceback_later()
  555. if filename:
  556. file = open(filename, "wb")
  557. elif fd is not None:
  558. file = sys.stderr.fileno()
  559. else:
  560. file = None
  561. func(timeout, repeat, cancel, file, loops)
  562. if filename:
  563. file.close()
  564. """
  565. code = code.format(
  566. timeout=TIMEOUT,
  567. repeat=repeat,
  568. cancel=cancel,
  569. loops=loops,
  570. filename=filename,
  571. fd=fd,
  572. )
  573. trace, exitcode = self.get_output(code, filename)
  574. trace = '\n'.join(trace)
  575. if not cancel:
  576. count = loops
  577. if repeat:
  578. count *= 2
  579. header = r'Timeout \(%s\)!\nThread 0x[0-9a-f]+ \(most recent call first\):\n' % timeout_str
  580. regex = expected_traceback(17, 26, header, min_count=count)
  581. self.assertRegex(trace, regex)
  582. else:
  583. self.assertEqual(trace, '')
  584. self.assertEqual(exitcode, 0)
  585. def test_dump_traceback_later(self):
  586. self.check_dump_traceback_later()
  587. def test_dump_traceback_later_repeat(self):
  588. self.check_dump_traceback_later(repeat=True)
  589. def test_dump_traceback_later_cancel(self):
  590. self.check_dump_traceback_later(cancel=True)
  591. def test_dump_traceback_later_file(self):
  592. with temporary_filename() as filename:
  593. self.check_dump_traceback_later(filename=filename)
  594. @unittest.skipIf(sys.platform == "win32",
  595. "subprocess doesn't support pass_fds on Windows")
  596. def test_dump_traceback_later_fd(self):
  597. with tempfile.TemporaryFile('wb+') as fp:
  598. self.check_dump_traceback_later(fd=fp.fileno())
  599. def test_dump_traceback_later_twice(self):
  600. self.check_dump_traceback_later(loops=2)
  601. @unittest.skipIf(not hasattr(faulthandler, "register"),
  602. "need faulthandler.register")
  603. def check_register(self, filename=False, all_threads=False,
  604. unregister=False, chain=False, fd=None):
  605. """
  606. Register a handler displaying the traceback on a user signal. Raise the
  607. signal and check the written traceback.
  608. If chain is True, check that the previous signal handler is called.
  609. Raise an error if the output doesn't match the expected format.
  610. """
  611. signum = signal.SIGUSR1
  612. code = """
  613. import faulthandler
  614. import os
  615. import signal
  616. import sys
  617. all_threads = {all_threads}
  618. signum = {signum:d}
  619. unregister = {unregister}
  620. chain = {chain}
  621. filename = {filename!r}
  622. fd = {fd}
  623. def func(signum):
  624. os.kill(os.getpid(), signum)
  625. def handler(signum, frame):
  626. handler.called = True
  627. handler.called = False
  628. if filename:
  629. file = open(filename, "wb")
  630. elif fd is not None:
  631. file = sys.stderr.fileno()
  632. else:
  633. file = None
  634. if chain:
  635. signal.signal(signum, handler)
  636. faulthandler.register(signum, file=file,
  637. all_threads=all_threads, chain={chain})
  638. if unregister:
  639. faulthandler.unregister(signum)
  640. func(signum)
  641. if chain and not handler.called:
  642. if file is not None:
  643. output = file
  644. else:
  645. output = sys.stderr
  646. print("Error: signal handler not called!", file=output)
  647. exitcode = 1
  648. else:
  649. exitcode = 0
  650. if filename:
  651. file.close()
  652. sys.exit(exitcode)
  653. """
  654. code = code.format(
  655. all_threads=all_threads,
  656. signum=signum,
  657. unregister=unregister,
  658. chain=chain,
  659. filename=filename,
  660. fd=fd,
  661. )
  662. trace, exitcode = self.get_output(code, filename)
  663. trace = '\n'.join(trace)
  664. if not unregister:
  665. if all_threads:
  666. regex = r'Current thread 0x[0-9a-f]+ \(most recent call first\):\n'
  667. else:
  668. regex = r'Stack \(most recent call first\):\n'
  669. regex = expected_traceback(14, 32, regex)
  670. self.assertRegex(trace, regex)
  671. else:
  672. self.assertEqual(trace, '')
  673. if unregister:
  674. self.assertNotEqual(exitcode, 0)
  675. else:
  676. self.assertEqual(exitcode, 0)
  677. def test_register(self):
  678. self.check_register()
  679. def test_unregister(self):
  680. self.check_register(unregister=True)
  681. def test_register_file(self):
  682. with temporary_filename() as filename:
  683. self.check_register(filename=filename)
  684. @unittest.skipIf(sys.platform == "win32",
  685. "subprocess doesn't support pass_fds on Windows")
  686. def test_register_fd(self):
  687. with tempfile.TemporaryFile('wb+') as fp:
  688. self.check_register(fd=fp.fileno())
  689. def test_register_threads(self):
  690. self.check_register(all_threads=True)
  691. def test_register_chain(self):
  692. self.check_register(chain=True)
  693. @contextmanager
  694. def check_stderr_none(self):
  695. stderr = sys.stderr
  696. try:
  697. sys.stderr = None
  698. with self.assertRaises(RuntimeError) as cm:
  699. yield
  700. self.assertEqual(str(cm.exception), "sys.stderr is None")
  701. finally:
  702. sys.stderr = stderr
  703. def test_stderr_None(self):
  704. # Issue #21497: provide a helpful error if sys.stderr is None,
  705. # instead of just an attribute error: "None has no attribute fileno".
  706. with self.check_stderr_none():
  707. faulthandler.enable()
  708. with self.check_stderr_none():
  709. faulthandler.dump_traceback()
  710. with self.check_stderr_none():
  711. faulthandler.dump_traceback_later(1e-3)
  712. if hasattr(faulthandler, "register"):
  713. with self.check_stderr_none():
  714. faulthandler.register(signal.SIGUSR1)
  715. @unittest.skipUnless(MS_WINDOWS, 'specific to Windows')
  716. def test_raise_exception(self):
  717. for exc, name in (
  718. ('EXCEPTION_ACCESS_VIOLATION', 'access violation'),
  719. ('EXCEPTION_INT_DIVIDE_BY_ZERO', 'int divide by zero'),
  720. ('EXCEPTION_STACK_OVERFLOW', 'stack overflow'),
  721. ):
  722. self.check_windows_exception(f"""
  723. import faulthandler
  724. faulthandler.enable()
  725. faulthandler._raise_exception(faulthandler._{exc})
  726. """,
  727. 3,
  728. name)
  729. @unittest.skipUnless(MS_WINDOWS, 'specific to Windows')
  730. def test_ignore_exception(self):
  731. for exc_code in (
  732. 0xE06D7363, # MSC exception ("Emsc")
  733. 0xE0434352, # COM Callable Runtime exception ("ECCR")
  734. ):
  735. code = f"""
  736. import faulthandler
  737. faulthandler.enable()
  738. faulthandler._raise_exception({exc_code})
  739. """
  740. code = dedent(code)
  741. output, exitcode = self.get_output(code)
  742. self.assertEqual(output, [])
  743. self.assertEqual(exitcode, exc_code)
  744. @unittest.skipUnless(MS_WINDOWS, 'specific to Windows')
  745. def test_raise_nonfatal_exception(self):
  746. # These exceptions are not strictly errors. Letting
  747. # faulthandler display the traceback when they are
  748. # raised is likely to result in noise. However, they
  749. # may still terminate the process if there is no
  750. # handler installed for them (which there typically
  751. # is, e.g. for debug messages).
  752. for exc in (
  753. 0x00000000,
  754. 0x34567890,
  755. 0x40000000,
  756. 0x40001000,
  757. 0x70000000,
  758. 0x7FFFFFFF,
  759. ):
  760. output, exitcode = self.get_output(f"""
  761. import faulthandler
  762. faulthandler.enable()
  763. faulthandler._raise_exception(0x{exc:x})
  764. """
  765. )
  766. self.assertEqual(output, [])
  767. # On Windows older than 7 SP1, the actual exception code has
  768. # bit 29 cleared.
  769. self.assertIn(exitcode,
  770. (exc, exc & ~0x10000000))
  771. @unittest.skipUnless(MS_WINDOWS, 'specific to Windows')
  772. def test_disable_windows_exc_handler(self):
  773. code = dedent("""
  774. import faulthandler
  775. faulthandler.enable()
  776. faulthandler.disable()
  777. code = faulthandler._EXCEPTION_ACCESS_VIOLATION
  778. faulthandler._raise_exception(code)
  779. """)
  780. output, exitcode = self.get_output(code)
  781. self.assertEqual(output, [])
  782. self.assertEqual(exitcode, 0xC0000005)
  783. def test_cancel_later_without_dump_traceback_later(self):
  784. # bpo-37933: Calling cancel_dump_traceback_later()
  785. # without dump_traceback_later() must not segfault.
  786. code = dedent("""
  787. import faulthandler
  788. faulthandler.cancel_dump_traceback_later()
  789. """)
  790. output, exitcode = self.get_output(code)
  791. self.assertEqual(output, [])
  792. self.assertEqual(exitcode, 0)
  793. if __name__ == "__main__":
  794. unittest.main()