pytest_panic.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. # SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: CC0-1.0
  3. import re
  4. from typing import List, Optional
  5. import pexpect
  6. import pytest
  7. from test_panic_util import PanicTestDut
  8. # Markers for all the targets this test currently runs on
  9. TARGETS_TESTED = [pytest.mark.esp32, pytest.mark.esp32s2, pytest.mark.esp32c3, pytest.mark.esp32s3, pytest.mark.esp32c2]
  10. # Most tests run on all targets and with all configs.
  11. # This list is passed to @pytest.mark.parametrize for each of the test cases.
  12. # It creates an outer product of the sets: [configs] x [targets],
  13. # with some exceptions.
  14. CONFIGS = [
  15. pytest.param('coredump_flash_bin_crc', marks=TARGETS_TESTED),
  16. pytest.param('coredump_flash_elf_sha', marks=[pytest.mark.esp32]), # sha256 only supported on esp32, IDF-1820
  17. pytest.param('coredump_uart_bin_crc', marks=TARGETS_TESTED),
  18. pytest.param('coredump_uart_elf_crc', marks=TARGETS_TESTED),
  19. pytest.param('gdbstub', marks=TARGETS_TESTED),
  20. pytest.param('panic', marks=TARGETS_TESTED),
  21. ]
  22. # Some tests only run on dual-core targets, they use the config below.
  23. TARGETS_DUAL_CORE = [pytest.mark.esp32, pytest.mark.esp32s3]
  24. CONFIGS_DUAL_CORE = [
  25. pytest.param('coredump_flash_bin_crc', marks=TARGETS_DUAL_CORE),
  26. pytest.param('coredump_flash_elf_sha', marks=[pytest.mark.esp32]), # sha256 only supported on esp32, IDF-1820
  27. pytest.param('coredump_uart_bin_crc', marks=TARGETS_DUAL_CORE),
  28. pytest.param('coredump_uart_elf_crc', marks=TARGETS_DUAL_CORE),
  29. pytest.param('gdbstub', marks=TARGETS_DUAL_CORE),
  30. pytest.param('panic', marks=TARGETS_DUAL_CORE),
  31. ]
  32. # Some tests run on all targets but need to behave differently on the dual-core ones.
  33. # This list is used to check if the target is a dual-core one.
  34. TARGETS_DUAL_CORE_NAMES = [x.mark.name for x in TARGETS_DUAL_CORE]
  35. # The tests which panic on external stack require PSRAM capable runners
  36. CONFIGS_EXTRAM_STACK = [
  37. pytest.param('coredump_extram_stack', marks=[pytest.mark.esp32, pytest.mark.esp32s2, pytest.mark.psram, pytest.mark.esp32s3, pytest.mark.quad_psram])
  38. ]
  39. def get_default_backtrace(config: str) -> List[str]:
  40. return [config, 'app_main', 'main_task', 'vPortTaskWrapper']
  41. def common_test(dut: PanicTestDut, config: str, expected_backtrace: Optional[List[str]] = None) -> None:
  42. if 'gdbstub' in config:
  43. dut.expect_exact('Entering gdb stub now.')
  44. dut.start_gdb()
  45. frames = dut.gdb_backtrace()
  46. if expected_backtrace is not None:
  47. dut.verify_gdb_backtrace(frames, expected_backtrace)
  48. dut.revert_log_level()
  49. return # don't expect "Rebooting" output below
  50. if 'uart' in config:
  51. dut.process_coredump_uart()
  52. elif 'flash' in config:
  53. dut.process_coredump_flash()
  54. elif 'panic' in config:
  55. pass
  56. dut.expect('Rebooting...')
  57. @pytest.mark.parametrize('config', CONFIGS, indirect=True)
  58. @pytest.mark.generic
  59. def test_task_wdt_cpu0(dut: PanicTestDut, config: str, test_func_name: str) -> None:
  60. dut.run_test_func(test_func_name)
  61. dut.expect_exact(
  62. 'Task watchdog got triggered. The following tasks/users did not reset the watchdog in time:'
  63. )
  64. dut.expect_exact('CPU 0: main')
  65. if dut.is_xtensa:
  66. # on Xtensa, dumping registers on abort is not necessary, we only need to dump the backtrace
  67. dut.expect_none('register dump:')
  68. dut.expect_exact('Print CPU 0 (current core) backtrace')
  69. dut.expect_backtrace()
  70. else:
  71. # on RISC-V, need to dump both registers and stack memory to reconstruct the backtrace
  72. dut.expect_reg_dump(core=0)
  73. dut.expect_stack_dump()
  74. dut.expect_elf_sha256()
  75. dut.expect_none('Guru Meditation')
  76. common_test(
  77. dut,
  78. config,
  79. expected_backtrace=get_default_backtrace(test_func_name),
  80. )
  81. @pytest.mark.parametrize('config', CONFIGS_DUAL_CORE, indirect=True)
  82. @pytest.mark.generic
  83. def test_task_wdt_cpu1(dut: PanicTestDut, config: str, test_func_name: str) -> None:
  84. dut.run_test_func(test_func_name)
  85. dut.expect_exact(
  86. 'Task watchdog got triggered. The following tasks/users did not reset the watchdog in time:'
  87. )
  88. dut.expect_exact('CPU 1: Infinite loop')
  89. if dut.is_xtensa:
  90. # see comment in test_task_wdt_cpu0
  91. dut.expect_none('register dump:')
  92. dut.expect_exact('Print CPU 1 backtrace')
  93. dut.expect_backtrace()
  94. # On Xtensa, we get incorrect backtrace from GDB in this test
  95. expected_backtrace = ['infinite_loop', 'vPortTaskWrapper']
  96. else:
  97. assert False, 'No dual-core RISC-V chips yet, check this test case later'
  98. dut.expect_elf_sha256()
  99. dut.expect_none('Guru Meditation')
  100. common_test(
  101. dut,
  102. config,
  103. expected_backtrace=expected_backtrace,
  104. )
  105. @pytest.mark.parametrize('config', CONFIGS_DUAL_CORE, indirect=True)
  106. @pytest.mark.generic
  107. def test_task_wdt_both_cpus(dut: PanicTestDut, config: str, test_func_name: str) -> None:
  108. if dut.target == 'esp32s3':
  109. pytest.xfail(reason='Only prints "Print CPU 1 backtrace", IDF-6560')
  110. dut.run_test_func(test_func_name)
  111. dut.expect_exact(
  112. 'Task watchdog got triggered. The following tasks/users did not reset the watchdog in time:'
  113. )
  114. dut.expect_exact('CPU 0: Infinite loop')
  115. dut.expect_exact('CPU 1: Infinite loop')
  116. if dut.is_xtensa:
  117. # see comment in test_task_wdt_cpu0
  118. dut.expect_none('register dump:')
  119. dut.expect_exact('Print CPU 0 (current core) backtrace')
  120. dut.expect_backtrace()
  121. dut.expect_exact('Print CPU 1 backtrace')
  122. dut.expect_backtrace()
  123. # On Xtensa, we get incorrect backtrace from GDB in this test
  124. expected_backtrace = ['infinite_loop', 'vPortTaskWrapper']
  125. else:
  126. assert False, 'No dual-core RISC-V chips yet, check this test case later'
  127. dut.expect_elf_sha256()
  128. dut.expect_none('Guru Meditation')
  129. common_test(
  130. dut,
  131. config,
  132. expected_backtrace=expected_backtrace,
  133. )
  134. @pytest.mark.parametrize('config', CONFIGS_EXTRAM_STACK, indirect=True)
  135. def test_panic_extram_stack(dut: PanicTestDut, config: str, test_func_name: str) -> None:
  136. dut.expect_test_func_name(test_func_name)
  137. dut.expect_none('Allocated stack is not in external RAM')
  138. dut.expect_none('Guru Meditation')
  139. dut.expect_backtrace()
  140. dut.expect_elf_sha256()
  141. # Check that coredump is getting written to flash
  142. dut.expect_exact('Save core dump to flash...')
  143. # And that the stack is replaced and restored
  144. dut.expect_exact('Backing up stack @')
  145. dut.expect_exact('Restoring stack')
  146. # The caller must be accessible after restoring the stack
  147. dut.expect_exact('Core dump has been saved to flash.')
  148. common_test(dut, config)
  149. @pytest.mark.parametrize('config', CONFIGS, indirect=True)
  150. @pytest.mark.generic
  151. def test_int_wdt(
  152. dut: PanicTestDut, target: str, config: str, test_func_name: str
  153. ) -> None:
  154. dut.run_test_func(test_func_name)
  155. dut.expect_gme('Interrupt wdt timeout on CPU0')
  156. dut.expect_reg_dump(0)
  157. if dut.is_xtensa:
  158. dut.expect_backtrace()
  159. else:
  160. dut.expect_stack_dump()
  161. if target in TARGETS_DUAL_CORE_NAMES:
  162. assert dut.is_xtensa, 'No dual-core RISC-V chips yet, check the test case'
  163. dut.expect_reg_dump(1)
  164. dut.expect_backtrace()
  165. dut.expect_elf_sha256()
  166. dut.expect_none('Guru Meditation')
  167. common_test(dut, config, expected_backtrace=get_default_backtrace(test_func_name))
  168. @pytest.mark.parametrize('config', CONFIGS, indirect=True)
  169. @pytest.mark.generic
  170. def test_int_wdt_cache_disabled(
  171. dut: PanicTestDut, target: str, config: str, test_func_name: str
  172. ) -> None:
  173. dut.run_test_func(test_func_name)
  174. dut.expect_gme('Interrupt wdt timeout on CPU0')
  175. dut.expect_reg_dump(0)
  176. if dut.is_xtensa:
  177. dut.expect_backtrace()
  178. else:
  179. dut.expect_stack_dump()
  180. if target in TARGETS_DUAL_CORE_NAMES:
  181. assert dut.is_xtensa, 'No dual-core RISC-V chips yet, check the test case'
  182. dut.expect_reg_dump(1)
  183. dut.expect_backtrace()
  184. dut.expect_elf_sha256()
  185. dut.expect_none('Guru Meditation')
  186. common_test(dut, config, expected_backtrace=get_default_backtrace(test_func_name))
  187. @pytest.mark.parametrize('config', CONFIGS, indirect=True)
  188. @pytest.mark.generic
  189. def test_cache_error(dut: PanicTestDut, config: str, test_func_name: str) -> None:
  190. dut.run_test_func(test_func_name)
  191. if dut.target in ['esp32c3', 'esp32c2']:
  192. # Cache error interrupt is not raised, IDF-6398
  193. dut.expect_gme('Illegal instruction')
  194. elif dut.target in ['esp32s2']:
  195. # Cache error interrupt is not enabled, IDF-1558
  196. dut.expect_gme('IllegalInstruction')
  197. else:
  198. dut.expect_gme('Cache disabled but cached memory region accessed')
  199. dut.expect_reg_dump(0)
  200. if dut.is_xtensa:
  201. dut.expect_backtrace()
  202. else:
  203. dut.expect_stack_dump()
  204. dut.expect_elf_sha256()
  205. dut.expect_none('Guru Meditation')
  206. expected_backtrace = ['die'] + get_default_backtrace(test_func_name)
  207. if dut.target in ['esp32s2', 'esp32s3']:
  208. # 'test_cache_error' missing from GDB backtrace on ESP32-S2 and ESP-S3, IDF-6561
  209. expected_backtrace = ['die', 'app_main', 'main_task', 'vPortTaskWrapper']
  210. common_test(
  211. dut, config, expected_backtrace=expected_backtrace
  212. )
  213. @pytest.mark.parametrize('config', CONFIGS, indirect=True)
  214. @pytest.mark.generic
  215. def test_stack_overflow(dut: PanicTestDut, config: str, test_func_name: str) -> None:
  216. dut.run_test_func(test_func_name)
  217. if dut.is_xtensa:
  218. dut.expect_gme('Unhandled debug exception')
  219. dut.expect_exact('Stack canary watchpoint triggered (main)')
  220. else:
  221. # Stack watchpoint handling missing on RISC-V, IDF-6397
  222. dut.expect_gme('Breakpoint')
  223. dut.expect_reg_dump(0)
  224. if dut.is_xtensa:
  225. dut.expect_backtrace()
  226. else:
  227. dut.expect_stack_dump()
  228. dut.expect_elf_sha256()
  229. dut.expect_none('Guru Meditation')
  230. common_test(dut, config, expected_backtrace=get_default_backtrace(test_func_name))
  231. @pytest.mark.parametrize('config', CONFIGS, indirect=True)
  232. @pytest.mark.generic
  233. def test_instr_fetch_prohibited(
  234. dut: PanicTestDut, config: str, test_func_name: str
  235. ) -> None:
  236. dut.run_test_func(test_func_name)
  237. if dut.is_xtensa:
  238. dut.expect_gme('InstrFetchProhibited')
  239. dut.expect_reg_dump(0)
  240. dut.expect_backtrace()
  241. expected_backtrace = ['_init'] + get_default_backtrace(test_func_name)
  242. else:
  243. dut.expect_gme('Instruction access fault')
  244. dut.expect_reg_dump(0)
  245. dut.expect_stack_dump()
  246. # On RISC-V, GDB is not able to determine the correct backtrace after
  247. # a jump to an invalid address.
  248. expected_backtrace = ['??']
  249. dut.expect_elf_sha256()
  250. dut.expect_none('Guru Meditation')
  251. common_test(
  252. dut,
  253. config,
  254. expected_backtrace=expected_backtrace,
  255. )
  256. @pytest.mark.parametrize('config', CONFIGS, indirect=True)
  257. @pytest.mark.generic
  258. def test_illegal_instruction(
  259. dut: PanicTestDut, config: str, test_func_name: str
  260. ) -> None:
  261. dut.run_test_func(test_func_name)
  262. if dut.is_xtensa:
  263. dut.expect_gme('IllegalInstruction')
  264. else:
  265. dut.expect_gme('Illegal instruction')
  266. dut.expect_reg_dump(0)
  267. if dut.is_xtensa:
  268. dut.expect_backtrace()
  269. else:
  270. dut.expect_stack_dump()
  271. dut.expect_elf_sha256()
  272. dut.expect_none('Guru Meditation')
  273. common_test(dut, config, expected_backtrace=get_default_backtrace(test_func_name))
  274. @pytest.mark.parametrize('config', CONFIGS, indirect=True)
  275. @pytest.mark.generic
  276. def test_storeprohibited(dut: PanicTestDut, config: str, test_func_name: str) -> None:
  277. dut.run_test_func(test_func_name)
  278. if dut.is_xtensa:
  279. dut.expect_gme('StoreProhibited')
  280. else:
  281. dut.expect_gme('Store access fault')
  282. dut.expect_reg_dump(0)
  283. if dut.is_xtensa:
  284. dut.expect_backtrace()
  285. else:
  286. dut.expect_stack_dump()
  287. dut.expect_elf_sha256()
  288. dut.expect_none('Guru Meditation')
  289. common_test(dut, config, expected_backtrace=get_default_backtrace(test_func_name))
  290. @pytest.mark.parametrize('config', CONFIGS, indirect=True)
  291. @pytest.mark.generic
  292. def test_abort(dut: PanicTestDut, config: str, test_func_name: str) -> None:
  293. dut.run_test_func(test_func_name)
  294. dut.expect(r'abort\(\) was called at PC [0-9xa-f]+ on core 0')
  295. if dut.is_xtensa:
  296. dut.expect_backtrace()
  297. else:
  298. dut.expect_stack_dump()
  299. dut.expect_elf_sha256()
  300. dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
  301. common_test(
  302. dut,
  303. config,
  304. expected_backtrace=[
  305. 'panic_abort',
  306. 'esp_system_abort',
  307. 'abort'
  308. ] + get_default_backtrace(test_func_name),
  309. )
  310. @pytest.mark.parametrize('config', CONFIGS, indirect=True)
  311. @pytest.mark.generic
  312. def test_ub(dut: PanicTestDut, config: str, test_func_name: str) -> None:
  313. dut.run_test_func(test_func_name)
  314. dut.expect('Undefined behavior of type out_of_bounds')
  315. if dut.is_xtensa:
  316. dut.expect_backtrace()
  317. else:
  318. dut.expect_stack_dump()
  319. dut.expect_elf_sha256()
  320. dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
  321. common_test(
  322. dut,
  323. config,
  324. expected_backtrace=[
  325. 'panic_abort',
  326. 'esp_system_abort',
  327. '__ubsan_default_handler',
  328. '__ubsan_handle_out_of_bounds'
  329. ] + get_default_backtrace(test_func_name),
  330. )
  331. @pytest.mark.parametrize('config', CONFIGS, indirect=True)
  332. @pytest.mark.generic
  333. def test_abort_cache_disabled(
  334. dut: PanicTestDut, config: str, test_func_name: str
  335. ) -> None:
  336. if dut.target == 'esp32s2':
  337. pytest.xfail(reason='Crashes in itoa which is not in ROM, IDF-3572')
  338. dut.run_test_func(test_func_name)
  339. dut.expect(r'abort\(\) was called at PC [0-9xa-f]+ on core 0')
  340. if dut.is_xtensa:
  341. dut.expect_backtrace()
  342. else:
  343. dut.expect_stack_dump()
  344. dut.expect_elf_sha256()
  345. dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
  346. common_test(
  347. dut,
  348. config,
  349. expected_backtrace=[
  350. 'panic_abort',
  351. 'esp_system_abort',
  352. 'abort'
  353. ] + get_default_backtrace(test_func_name),
  354. )
  355. @pytest.mark.parametrize('config', CONFIGS, indirect=True)
  356. @pytest.mark.generic
  357. def test_assert(dut: PanicTestDut, config: str, test_func_name: str) -> None:
  358. dut.run_test_func(test_func_name)
  359. dut.expect(
  360. re.compile(
  361. rb'assert failed:[\s\w()]*?\s[.\w/]*\.(?:c|cpp|h|hpp):\d.*$', re.MULTILINE
  362. )
  363. )
  364. if dut.is_xtensa:
  365. dut.expect_backtrace()
  366. else:
  367. dut.expect_stack_dump()
  368. dut.expect_elf_sha256()
  369. dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
  370. common_test(
  371. dut,
  372. config,
  373. expected_backtrace=[
  374. 'panic_abort',
  375. 'esp_system_abort',
  376. '__assert_func'
  377. ] + get_default_backtrace(test_func_name))
  378. @pytest.mark.parametrize('config', CONFIGS, indirect=True)
  379. @pytest.mark.generic
  380. def test_assert_cache_disabled(
  381. dut: PanicTestDut, config: str, test_func_name: str
  382. ) -> None:
  383. if dut.target == 'esp32s2':
  384. pytest.xfail(reason='Crashes in itoa which is not in ROM, IDF-3572')
  385. dut.run_test_func(test_func_name)
  386. dut.expect(re.compile(rb'assert failed: [0-9xa-fA-F]+.*$', re.MULTILINE))
  387. if dut.is_xtensa:
  388. dut.expect_backtrace()
  389. else:
  390. dut.expect_stack_dump()
  391. dut.expect_elf_sha256()
  392. dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
  393. common_test(
  394. dut,
  395. config,
  396. expected_backtrace=[
  397. 'panic_abort',
  398. 'esp_system_abort',
  399. '__assert_func'
  400. ] + get_default_backtrace(test_func_name))
  401. @pytest.mark.esp32
  402. @pytest.mark.parametrize('config', ['panic_delay'], indirect=True)
  403. def test_panic_delay(dut: PanicTestDut) -> None:
  404. dut.run_test_func('test_storeprohibited')
  405. try:
  406. dut.expect_exact('Rebooting...', timeout=4)
  407. except pexpect.TIMEOUT:
  408. # We are supposed to NOT find the output for the specified time
  409. pass
  410. else:
  411. # If we actually match the output within the timeout, it means the delay didn't work
  412. raise AssertionError('Rebooted too early, delay is too short')
  413. dut.expect_exact('Rebooting...', timeout=3)
  414. dut.expect_exact('rst:0xc (SW_CPU_RESET)')