Merge branch 'ci/panic_test_split_coredump' into 'master'

ci: trim panic_coredump build-test dependencies

See merge request espressif/esp-idf!48442
This commit is contained in:
Marius Vikhammer
2026-05-20 15:36:42 +08:00
69 changed files with 533 additions and 291 deletions

View File

@@ -7,7 +7,7 @@ The unit tests are currently run only on the chips listed above just to save CI
When adding new test cases, check if the `depends_components` list in `.build-test-rules.yml` needs to be updated to include additional components. The test app will only be built and tested when these components are modified.
See also the [panic test app](../../../tools/test_apps/system/panic) which serves as an integration test for espcoredump and is run on all supported chips.
See also the [panic coredump test app](../../../tools/test_apps/system/panic/coredump) which serves as an integration test for espcoredump and is run on all supported chips.
To build and run this test app, using esp32c3 target for example:

View File

@@ -688,7 +688,7 @@ Usually, you may want to write a custom class under these conditions:
1. Add more reusable functions for a certain number of DUTs.
2. Add custom setup and teardown functions
This code example is taken from :idf_file:`panic/conftest.py <tools/test_apps/system/panic/conftest.py>`.
This code example is taken from :idf_file:`panic/panic_base/conftest.py <tools/test_apps/system/panic/panic_base/conftest.py>`.
.. code-block:: python
@@ -735,7 +735,7 @@ Sometimes, a test can consistently fail for the following reasons:
Now you may mark this test case with marker `xfail <https://docs.pytest.org/en/latest/how-to/skipping.html#xfail-mark-test-functions-as-expected-to-fail>`__ with a user-friendly readable reason.
This code example is taken from :idf_file:`pytest_panic.py <tools/test_apps/system/panic/pytest_panic.py>`
This code example is taken from :idf_file:`pytest_panic.py <tools/test_apps/system/panic/panic_base/pytest_panic.py>`
.. code-block:: python

View File

@@ -688,7 +688,7 @@ Pytest 使用技巧
1. 向一定数量的 DUT 添加更多可复用功能。
2. 为不同阶段添加自定义的前置和后置函数。
以下代码示例来自 :idf_file:`panic/conftest.py <tools/test_apps/system/panic/conftest.py>`
以下代码示例来自 :idf_file:`panic/panic_base/conftest.py <tools/test_apps/system/panic/panic_base/conftest.py>`
.. code-block:: python
@@ -735,7 +735,7 @@ Pytest 使用技巧
可使用 `xfail <https://docs.pytest.org/en/latest/how-to/skipping.html#xfail-mark-test-functions-as-expected-to-fail>`__ marker 来标记此测试用例,并写出原因。
以下代码来自 :idf_file:`pytest_panic.py <tools/test_apps/system/panic/pytest_panic.py>`
以下代码来自 :idf_file:`pytest_panic.py <tools/test_apps/system/panic/panic_base/pytest_panic.py>`
.. code-block:: python

View File

@@ -141,7 +141,14 @@ tools/test_apps/system/no_embedded_paths:
temporary: true
reason: the other targets are not tested yet
tools/test_apps/system/panic:
tools/test_apps/system/panic/coredump:
enable:
- if: IDF_TARGET in ["esp32", "esp32c2", "esp32c3", "esp32c5", "esp32c6", "esp32c61", "esp32h2", "esp32p4", "esp32s2", "esp32s3", "esp32s31"]
depends_components:
- espcoredump
- esp_system
tools/test_apps/system/panic/panic_base:
enable:
- if: INCLUDE_DEFAULT == 1 or IDF_TARGET in ["esp32s31", "esp32h4"]

View File

@@ -10,7 +10,7 @@ import pytest
from _pytest.fixtures import FixtureRequest
from _pytest.monkeypatch import MonkeyPatch
sys.path.append(os.path.expandvars(os.path.join('$IDF_PATH', 'tools', 'test_apps', 'system', 'panic')))
sys.path.append(os.path.normpath(os.path.join(os.path.dirname(__file__), '..', 'panic', 'panic_base')))
from test_panic_util import PanicTestDut # noqa: E402

View File

@@ -9,7 +9,7 @@ from typing import Any
import pytest
from pytest_embedded_idf.utils import idf_parametrize
sys.path.append(path.expandvars(path.join('$IDF_PATH', 'tools', 'test_apps', 'system', 'panic')))
sys.path.append(path.normpath(path.join(path.dirname(__file__), '..', 'panic', 'panic_base')))
from test_panic_util import PanicTestDut # noqa: E402

View File

@@ -1,4 +1,5 @@
set(srcs "test_app_main.c" "test_panic.c")
set(priv_requires esp_gdbstub espcoredump esp_hal_security)
if(CONFIG_TEST_MEMPROT)
list(APPEND srcs "test_memprot.c")
@@ -13,10 +14,14 @@ else()
list(APPEND srcs "panic_utils/panic_utils.c")
endif()
if(CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH)
list(APPEND srcs "test_panic_coredump_summary.c")
endif()
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include"
REQUIRES spi_flash esp_psram esp_system esp_partition
PRIV_REQUIRES esp_gdbstub espcoredump esp_hal_security)
PRIV_REQUIRES ${priv_requires})
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-unused-variable"
"-Wno-infinite-recursion"

View File

@@ -13,10 +13,6 @@
#include "esp_flash.h"
#include "esp_system.h"
#include "spi_flash_mmap.h"
#if CONFIG_ESP_COREDUMP_ENABLE
#include "esp_core_dump.h"
#endif
#include "esp_private/cache_utils.h"
#include "esp_memory_utils.h"
#include "esp_heap_caps.h"
@@ -321,38 +317,6 @@ void test_ub(void)
printf("%d\n", stuff[rand()]);
}
#if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
void test_setup_coredump_summary(void)
{
if (esp_core_dump_image_erase() != ESP_OK)
die("Coredump image can not be erased!");
assert(0);
}
void test_coredump_summary(void)
{
esp_core_dump_summary_t *summary = malloc(sizeof(esp_core_dump_summary_t));
if (summary) {
esp_err_t err = esp_core_dump_get_summary(summary);
if (err == ESP_OK) {
printf("App ELF file SHA256: %s\n", (char *)summary->app_elf_sha256);
printf("Crashed task: %s\n", summary->exc_task);
#if __XTENSA__
printf("Exception cause: %ld\n", summary->ex_info.exc_cause);
#else
printf("Exception cause: %ld\n", summary->ex_info.mcause);
#endif
char panic_reason[200];
err = esp_core_dump_get_panic_reason(panic_reason, sizeof(panic_reason));
if (err == ESP_OK) {
printf("Panic reason: %s\n", panic_reason);
}
}
free(summary);
}
}
#endif
void test_tcb_corrupted(void)
{
StaticTask_t *tcb = (StaticTask_t *)xTaskGetIdleTaskHandleForCore(0);

View File

@@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include "esp_core_dump.h"
#include "esp_err.h"
#include "test_panic.h"
#if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
void test_setup_coredump_summary(void)
{
if (esp_core_dump_image_erase() != ESP_OK) {
die("Coredump image can not be erased!");
}
assert(0);
}
void test_coredump_summary(void)
{
esp_core_dump_summary_t *summary = malloc(sizeof(esp_core_dump_summary_t));
if (summary) {
esp_err_t err = esp_core_dump_get_summary(summary);
if (err == ESP_OK) {
printf("App ELF file SHA256: %s\n", (char *)summary->app_elf_sha256);
printf("Crashed task: %s\n", summary->exc_task);
#if __XTENSA__
printf("Exception cause: %ld\n", summary->ex_info.exc_cause);
#else
printf("Exception cause: %ld\n", summary->ex_info.mcause);
#endif
char panic_reason[200];
err = esp_core_dump_get_panic_reason(panic_reason, sizeof(panic_reason));
if (err == ESP_OK) {
printf("Panic reason: %s\n", panic_reason);
}
}
free(summary);
}
}
#endif

View File

@@ -0,0 +1,10 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.22)
set(COMPONENTS main esp_gdbstub espcoredump)
set(EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/../common")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(test_panic_coredump)

View File

@@ -0,0 +1,18 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- |
# Introduction
The `coredump` test app contains the coredump-enabled panic coverage that was split out of the `panic_base` app so CI can route it separately with `depends_components`.
The C test logic is shared with the base panic app through [common/main](../common/main). Pytest wrappers live in [pytest_panic_coredump.py](pytest_panic_coredump.py) and reuse the panic test bodies for scenarios that should run with both panic and coredump configurations.
For guidance on where to add new tests, see [panic_base/README.md](../panic_base/README.md#where-to-add-a-new-test).
# Building
For example, to build the coredump panic app with configuration `coredump_flash_default` for ESP32-C3, run:
```bash
idf.py -DIDF_TARGET=esp32c3 -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.ci.coredump_flash_default" build
```

View File

@@ -0,0 +1,28 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# pylint: disable=W0621 # redefined-outer-name
import sys
from pathlib import Path
import pytest
from _pytest.fixtures import FixtureRequest
from _pytest.monkeypatch import MonkeyPatch
PANIC_BASE_APP = Path(__file__).resolve().parent.parent / 'panic_base'
sys.path.insert(0, str(PANIC_BASE_APP))
from test_panic_util import PanicTestDut # noqa: E402
@pytest.fixture(scope='module')
def monkeypatch_module(request: FixtureRequest) -> MonkeyPatch:
mp = MonkeyPatch()
request.addfinalizer(mp.undo)
return mp
@pytest.fixture(scope='module', autouse=True)
def replace_dut_class(monkeypatch_module: MonkeyPatch) -> None:
monkeypatch_module.setattr('pytest_embedded_idf.IdfDut', PanicTestDut)

View File

@@ -0,0 +1,331 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import re
import sys
from pathlib import Path
import pytest
from pytest_embedded_idf.utils import idf_parametrize
PANIC_BASE_APP = Path(__file__).resolve().parent.parent / 'panic_base'
sys.path.insert(0, str(PANIC_BASE_APP))
import pytest_panic as panic_tests # noqa: E402
from pytest_panic import PANIC_ABORT_PREFIX # noqa: E402
from pytest_panic import common_test # noqa: E402
from pytest_panic import expect_coredump_flash_write_logs # noqa: E402
from pytest_panic import expect_coredump_uart_write_logs # noqa: E402
from pytest_panic import get_default_backtrace # noqa: E402
from test_panic_util import PanicTestDut # noqa: E402
TARGETS_ALL = panic_tests.TARGETS_ALL
TARGETS_DUAL_CORE = panic_tests.TARGETS_DUAL_CORE
TARGETS_RISCV = panic_tests.TARGETS_RISCV
TARGETS_RISCV_DUAL_CORE = panic_tests.TARGETS_RISCV_DUAL_CORE
COREDUMP_APP = str(Path(__file__).resolve().parent)
COREDUMP_UNSUPPORTED_TARGETS = {'esp32h4'}
COREDUMP_TARGETS_ALL = [target for target in TARGETS_ALL if target not in COREDUMP_UNSUPPORTED_TARGETS]
COREDUMP_TARGETS_DUAL_CORE = [target for target in TARGETS_DUAL_CORE if target not in COREDUMP_UNSUPPORTED_TARGETS]
COREDUMP_TARGETS_RISCV = [target for target in TARGETS_RISCV if target not in COREDUMP_UNSUPPORTED_TARGETS]
COREDUMP_TARGETS_RISCV_DUAL_CORE = [
target for target in TARGETS_RISCV_DUAL_CORE if target not in COREDUMP_UNSUPPORTED_TARGETS
]
CONFIGS = panic_tests.configs_for_app(
COREDUMP_APP,
[
'coredump_flash_default',
'coredump_flash_soft_sha',
'coredump_uart_default',
'coredump_flash_custom_stack',
],
)
CONFIGS_DUAL_CORE = panic_tests.configs_for_app(
COREDUMP_APP,
[
'coredump_flash_default',
'coredump_uart_default',
],
)
CONFIGS_HW_STACK_GUARD = panic_tests.configs_for_app(COREDUMP_APP, ['coredump_uart_default'])
CONFIGS_HW_STACK_GUARD_DUAL_CORE = panic_tests.configs_for_app(COREDUMP_APP, ['coredump_uart_default'])
CONFIG_CAPTURE_DRAM = panic_tests.configs_for_app(
COREDUMP_APP, ['coredump_flash_capture_dram', 'coredump_uart_capture_dram']
)
CONFIG_COREDUMP_SUMMARY = panic_tests.configs_for_app(COREDUMP_APP, ['coredump_flash_default'])
CONFIG_COREDUMP_SUMMARY_FLASH_ENCRYPTED = panic_tests.configs_for_app(
COREDUMP_APP, ['coredump_flash_encrypted', 'coredump_flash_encrypted_coredump_plain']
)
CONFIG_GDBSTUB_COREDUMP = panic_tests.configs_for_app(COREDUMP_APP, ['gdbstub_coredump'])
CONFIG_TCB_CORRUPTED = panic_tests.configs_for_app(COREDUMP_APP, ['coredump_flash_default'])
@pytest.mark.generic
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', COREDUMP_TARGETS_ALL, indirect=['target'])
def test_task_wdt_cpu0(dut: PanicTestDut, config: str, test_func_name: str) -> None:
panic_tests.test_task_wdt_cpu0(dut, config, test_func_name)
@pytest.mark.generic
@pytest.mark.parametrize('app_path, config', CONFIGS_DUAL_CORE, indirect=True)
@idf_parametrize('target', COREDUMP_TARGETS_DUAL_CORE, indirect=['target'])
def test_task_wdt_cpu1(dut: PanicTestDut, config: str, test_func_name: str) -> None:
panic_tests.test_task_wdt_cpu1(dut, config, test_func_name)
@pytest.mark.parametrize(
'app_path, config, target',
[
pytest.param(COREDUMP_APP, 'coredump_flash_extram_stack_heap_esp32', 'esp32', marks=(pytest.mark.psram,)),
pytest.param(COREDUMP_APP, 'coredump_flash_extram_stack_heap_esp32s2', 'esp32s2', marks=(pytest.mark.generic,)),
pytest.param(
COREDUMP_APP, 'coredump_flash_extram_stack_heap_esp32s3', 'esp32s3', marks=(pytest.mark.quad_psram,)
),
pytest.param(COREDUMP_APP, 'coredump_flash_extram_stack_bss_esp32', 'esp32', marks=(pytest.mark.psram,)),
pytest.param(COREDUMP_APP, 'coredump_flash_extram_stack_bss_esp32s2', 'esp32s2', marks=(pytest.mark.generic,)),
pytest.param(
COREDUMP_APP, 'coredump_flash_extram_stack_bss_esp32s3', 'esp32s3', marks=(pytest.mark.quad_psram,)
),
],
indirect=True,
)
def test_panic_extram_stack(dut: PanicTestDut, config: str) -> None:
if 'heap' in config:
dut.run_test_func('test_panic_extram_stack_heap')
else:
dut.run_test_func('test_panic_extram_stack_bss')
dut.expect_none('Allocated stack is not in external RAM')
dut.expect_none('Guru Meditation')
dut.expect_backtrace()
dut.expect_elf_sha256()
if dut.target == 'esp32':
# ESP32 External data memory range [0x3f800000-0x3fc00000)
coredump_pattern = re.compile('.coredump.tasks.data (0x3[fF][8-9a-bA-B][0-9a-fA-F]{5}) (0x[a-fA-F0-9]+) RW')
elif dut.target == 'esp32s2':
# ESP32-S2 External data memory range [0x3f500000-0x3ff80000)
coredump_pattern = re.compile(
'.coredump.tasks.data (0x3[fF][5-9a-fA-F][0-7][0-9a-fA-F]{4}) (0x[a-fA-F0-9]+) RW'
)
else:
# ESP32-S3 External data memory range [0x3c000000-0x3e000000)
coredump_pattern = re.compile('.coredump.tasks.data (0x3[c-dC-D][0-9a-fA-F]{6}) (0x[a-fA-F0-9]+) RW')
common_test(dut, config, expected_backtrace=None, expected_coredump=[coredump_pattern])
@pytest.mark.generic
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', COREDUMP_TARGETS_ALL, indirect=['target'])
def test_int_wdt(dut: PanicTestDut, target: str, config: str, test_func_name: str) -> None:
panic_tests.test_int_wdt(dut, target, config, test_func_name)
@pytest.mark.generic
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', COREDUMP_TARGETS_ALL, indirect=['target'])
def test_int_wdt_cache_disabled(dut: PanicTestDut, target: str, config: str, test_func_name: str) -> None:
panic_tests.test_int_wdt_cache_disabled(dut, target, config, test_func_name)
@pytest.mark.generic
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', COREDUMP_TARGETS_ALL, indirect=['target'])
def test_cache_error(dut: PanicTestDut, config: str, test_func_name: str) -> None:
panic_tests.test_cache_error(dut, config, test_func_name)
@pytest.mark.generic
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', COREDUMP_TARGETS_ALL, indirect=['target'])
def test_stack_overflow(dut: PanicTestDut, config: str, test_func_name: str) -> None:
panic_tests.test_stack_overflow(dut, config, test_func_name)
@pytest.mark.generic
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', COREDUMP_TARGETS_ALL, indirect=['target'])
def test_instr_fetch_prohibited(dut: PanicTestDut, config: str, test_func_name: str) -> None:
panic_tests.test_instr_fetch_prohibited(dut, config, test_func_name)
@pytest.mark.generic
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', COREDUMP_TARGETS_ALL, indirect=['target'])
def test_illegal_instruction(dut: PanicTestDut, config: str, test_func_name: str) -> None:
panic_tests.test_illegal_instruction(dut, config, test_func_name)
@pytest.mark.generic
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', COREDUMP_TARGETS_ALL, indirect=['target'])
def test_storeprohibited(dut: PanicTestDut, config: str, test_func_name: str) -> None:
panic_tests.test_storeprohibited(dut, config, test_func_name)
@pytest.mark.generic
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', COREDUMP_TARGETS_ALL, indirect=['target'])
def test_loadprohibited(dut: PanicTestDut, config: str, test_func_name: str) -> None:
panic_tests.test_loadprohibited(dut, config, test_func_name)
@pytest.mark.generic
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', COREDUMP_TARGETS_ALL, indirect=['target'])
def test_abort(dut: PanicTestDut, config: str, test_func_name: str) -> None:
panic_tests.test_abort(dut, config, test_func_name)
@pytest.mark.generic
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', COREDUMP_TARGETS_ALL, indirect=['target'])
def test_abort_cache_disabled(dut: PanicTestDut, config: str, test_func_name: str) -> None:
panic_tests.test_abort_cache_disabled(dut, config, test_func_name)
@pytest.mark.generic
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', COREDUMP_TARGETS_ALL, indirect=['target'])
def test_assert(dut: PanicTestDut, config: str, test_func_name: str) -> None:
panic_tests.test_assert(dut, config, test_func_name)
@pytest.mark.generic
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', COREDUMP_TARGETS_ALL, indirect=['target'])
def test_assert_cache_disabled(dut: PanicTestDut, config: str, test_func_name: str) -> None:
panic_tests.test_assert_cache_disabled(dut, config, test_func_name)
@pytest.mark.generic
@pytest.mark.parametrize('app_path, config', CONFIG_GDBSTUB_COREDUMP, indirect=True)
@idf_parametrize('target', ['esp32'], indirect=['target'])
def test_gdbstub_coredump(dut: PanicTestDut) -> None:
test_func_name = 'test_storeprohibited'
dut.run_test_func(test_func_name)
common_test(dut, 'gdbstub_coredump', get_default_backtrace(test_func_name))
@pytest.mark.generic
@pytest.mark.parametrize('app_path, config', CONFIGS_HW_STACK_GUARD, indirect=True)
@idf_parametrize('target', COREDUMP_TARGETS_RISCV, indirect=['target'])
def test_hw_stack_guard_cpu0(dut: PanicTestDut, config: str, test_func_name: str) -> None:
panic_tests.test_hw_stack_guard_cpu0(dut, config, test_func_name)
@pytest.mark.generic
@pytest.mark.parametrize('app_path, config', CONFIGS_HW_STACK_GUARD_DUAL_CORE, indirect=True)
@idf_parametrize('target', COREDUMP_TARGETS_RISCV_DUAL_CORE, indirect=['target'])
def test_hw_stack_guard_cpu1(dut: PanicTestDut, config: str, test_func_name: str) -> None:
panic_tests.test_hw_stack_guard_cpu1(dut, config, test_func_name)
@pytest.mark.generic
@pytest.mark.parametrize('app_path, config', CONFIG_CAPTURE_DRAM, indirect=True)
@idf_parametrize('target', COREDUMP_TARGETS_ALL, indirect=['target'])
def test_capture_dram(dut: PanicTestDut, config: str, test_func_name: str) -> None:
dut.run_test_func(test_func_name)
regex_pattern = rb'assert failed:[\s\w()]*?\s[.\w/]*\.(?:c|cpp|h|hpp):\d.*$'
dut.expect(re.compile(regex_pattern, re.MULTILINE))
if dut.is_xtensa:
dut.expect_backtrace()
else:
dut.expect_stack_dump()
dut.expect_elf_sha256()
dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
core_elf_file = None
if 'flash' in config:
expect_coredump_flash_write_logs(dut, config)
core_elf_file = dut.process_coredump_flash()
elif 'uart' in config:
coredump_base64 = expect_coredump_uart_write_logs(dut)
core_elf_file = dut.process_coredump_uart(coredump_base64)
assert core_elf_file is not None
dut.start_gdb_for_coredump(core_elf_file)
assert dut.gdb_data_eval_expr('g_data_var') == '43'
assert dut.gdb_data_eval_expr('g_bss_var') == '55'
assert re.search(r'0x[0-9a-fA-F]+ "Coredump Test"', dut.gdb_data_eval_expr('g_heap_ptr'))
assert int(dut.gdb_data_eval_expr('g_cd_iram')) == 0x4243
assert int(dut.gdb_data_eval_expr('g_cd_dram')) == 0x4344
assert int(dut.gdb_data_eval_expr('g_noinit_var')) == 0xCAFEBABE
buffer_value = str(dut.gdb_data_eval_expr('g_noinit_buffer'))
assert 'NOINIT_TEST_STRING' in buffer_value
if dut.target not in ['esp32c61', 'esp32c2']:
assert int(dut.gdb_data_eval_expr('g_rtc_data_var')) == 0x55AA
assert int(dut.gdb_data_eval_expr('g_rtc_fast_var')) == 0xAABBCCDD
def _test_coredump_summary(dut: PanicTestDut, flash_encrypted: bool, coredump_encrypted: bool) -> None:
dut.run_test_func('test_setup_coredump_summary')
dut.expect_cpu_reset()
if flash_encrypted:
dut.expect_exact('Flash encryption mode is DEVELOPMENT (not secure)')
dut.run_test_func('test_coredump_summary')
if flash_encrypted and not coredump_encrypted:
dut.expect_exact('Flash encryption enabled in hardware and core dump partition is not encrypted!')
return
dut.expect_elf_sha256('App ELF file SHA256: ')
dut.expect_exact('Crashed task: main')
if dut.is_xtensa:
dut.expect_exact('Exception cause: 0')
else:
dut.expect_exact('Exception cause: 2')
dut.expect(PANIC_ABORT_PREFIX + r'assert failed:[\s\w()]*?\s[.\w/]*\.(?:c|cpp|h|hpp):\d.*$')
@pytest.mark.generic
@pytest.mark.parametrize('app_path, config', CONFIG_COREDUMP_SUMMARY, indirect=True)
@idf_parametrize('target', COREDUMP_TARGETS_ALL, indirect=['target'])
def test_coredump_summary(dut: PanicTestDut) -> None:
_test_coredump_summary(dut, False, False)
@pytest.mark.flash_encryption
@pytest.mark.parametrize('app_path, config', CONFIG_COREDUMP_SUMMARY_FLASH_ENCRYPTED, indirect=True)
@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target'])
def test_coredump_summary_flash_encrypted(dut: PanicTestDut, config: str) -> None:
_test_coredump_summary(dut, True, config == 'coredump_flash_encrypted')
@pytest.mark.generic
@pytest.mark.parametrize('app_path, config', CONFIG_TCB_CORRUPTED, indirect=True)
@idf_parametrize('target', COREDUMP_TARGETS_ALL, indirect=['target'])
def test_tcb_corrupted(dut: PanicTestDut, target: str, config: str, test_func_name: str) -> None:
dut.run_test_func(test_func_name)
if dut.is_xtensa:
dut.expect(re.compile(rb"Guru Meditation Error: Core\s+\d\s+panic'ed \((LoadProhibited|StoreProhibited)\)"))
dut.expect_reg_dump()
dut.expect_backtrace()
else:
dut.expect(re.compile(rb"Guru Meditation Error: Core\s+\d\s+panic'ed \((Load|Store) access fault\)"))
dut.expect_reg_dump()
dut.expect_stack_dump()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation')
# Verify that valid tasks are captured in coredump despite IDLE task corruption
# TCB NAME
# ---------- ----------------
if dut.is_multi_core:
regex_patterns = [
rb'[0-9xa-fA-F] main',
rb'[0-9xa-fA-F] ipc0',
rb'[0-9xa-fA-F] ipc1',
]
else:
regex_patterns = [rb'[0-9xa-fA-F] main']
coredump_pattern = [re.compile(pattern.decode('utf-8')) for pattern in regex_patterns]
common_test(dut, config, expected_backtrace=None, expected_coredump=coredump_pattern)

View File

@@ -3,10 +3,7 @@
cmake_minimum_required(VERSION 3.22)
set(COMPONENTS main esp_gdbstub)
if(CONFIG_ESP_COREDUMP_ENABLE)
list(APPEND COMPONENTS espcoredump)
endif()
set(EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/../common")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
@@ -20,9 +17,7 @@ if(CONFIG_TEST_MEMPROT)
target_link_libraries(${project_elf} PRIVATE "-Wl,--wrap=panic_arch_fill_info")
endif()
endif()
endif()
if(NOT CONFIG_TEST_MEMPROT AND NOT CONFIG_ESP_COREDUMP_ENABLE)
else()
# Enable UBSAN checks
#
# shift-base sanitizer is disabled due to the following pattern found in register header files:

View File

@@ -10,9 +10,17 @@ This test app is relatively complex because it has to check many possible combin
- Chip target: esp32, esp32c3, ...
- Configuration: default, GDB Stub, Core Dump to UART, ...
Failure scenarios are implemented in [test_panic.c](main/test_panic.c). The test application receives the name of the scenario from console (e.g. `test_illegal_instruction` ). The failure scenario is executed and the app panics. Once the panic output is printed, the pytest-based test case parses the output and verifies that the behavior of the panic handler was correct.
Failure scenarios are implemented in the shared component at [common/main/test_panic.c](../common/main/test_panic.c). The test application receives the name of the scenario from console (e.g. `test_illegal_instruction` ). The failure scenario is executed and the app panics. Once the panic output is printed, the pytest-based test case parses the output and verifies that the behavior of the panic handler was correct.
In [pytest_panic.py](pytest_panic.py), there typically is one test function for each failure scenario. Each test function is then parametrized by `config` parameter. This creates "copies" of the test case for each of the configurations (default, GDB Stub, etc.) Tests are also parametrized with target-specific markers. Most tests can run on every target, but there are a few exceptions, such as failure scenarios specific to the dual-core chips.
The `panic_base` app owns the non-coredump configurations. Coredump-specific sdkconfigs, partitions, and pytest wrappers live in the sibling [coredump](../coredump) app so CI can route them separately.
In [pytest_panic.py](pytest_panic.py), there typically is one test function for each failure scenario. Each test function is then parametrized by `config` parameter. This creates "copies" of the test case for each of the non-coredump configurations (default, GDB Stub, etc.) Tests are also parametrized with target-specific markers. Most tests can run on every target, but there are a few exceptions, such as failure scenarios specific to the dual-core chips.
## Where to add a new test
- **Scenario that should run under both panic and coredump configs** (e.g. a new watchdog or fault test): put the test body in `panic_base/pytest_panic.py`, then add a thin wrapper in `coredump/pytest_panic_coredump.py` that calls into it with coredump configs and `COREDUMP_TARGETS_ALL`. Shared helpers (`common_test`, `expect_coredump_*`) also live in `pytest_panic.py`.
- **Scenario that only makes sense with coredump enabled** (extram stack, capture_dram, coredump_summary, tcb_corrupted, gdbstub_coredump, …): add the test directly in `coredump/pytest_panic_coredump.py`. Do not put it in `panic_base`.
- **New C-level failure scenario**: add it to [common/main/test_panic.c](../common/main/test_panic.c) so both apps pick it up.
The test cases use a customized DUT class `PanicTestDut`, defined in [panic_dut.py](test_panic_util/panic_dut.py). This class is derived from [`IdfDut`](https://docs.espressif.com/projects/pytest-embedded/en/latest/references/pytest_embedded_idf/#pytest_embedded_idf.dut.IdfDut). It defines several helper functions to make the test cases easier to read.

View File

@@ -42,10 +42,7 @@ def verify_valid_gdb_subprocess(gdb_process: Popen) -> None:
raise NoGdbProcessError('gdb process is not attached')
elif gdb_process.poll() is not None:
raise NoGdbProcessError(
'gdb process has already finished with return code: %s'
% str(gdb_process.poll())
)
raise NoGdbProcessError(f'gdb process has already finished with return code: {gdb_process.poll()}')
def attach_logger() -> logging.Logger:

View File

@@ -1,6 +1,7 @@
# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import itertools
import os
import re
from collections.abc import Sequence
from re import Pattern
@@ -27,33 +28,16 @@ TARGETS_ALL = TARGETS_XTENSA + TARGETS_RISCV
# Some tests only run on dual-core targets, they use the config below.
TARGETS_DUAL_CORE = TARGETS_XTENSA_DUAL_CORE + TARGETS_RISCV_DUAL_CORE
CONFIGS = list(
itertools.chain(
itertools.product(
[
'coredump_flash_default',
'coredump_flash_soft_sha',
'coredump_uart_default',
'coredump_flash_custom_stack',
'gdbstub',
'panic',
],
TARGETS_ALL,
)
)
)
PANIC_APP = os.path.dirname(__file__)
CONFIGS_UBSAN = list(
itertools.chain(
itertools.product(
[
'gdbstub',
'panic',
],
TARGETS_ALL,
)
)
)
def configs_for_app(app_path: str, configs: Sequence[str]) -> list[tuple[str, str]]:
return [(app_path, config) for config in configs]
CONFIGS = configs_for_app(PANIC_APP, ['gdbstub', 'panic'])
CONFIGS_UBSAN = configs_for_app(PANIC_APP, ['gdbstub', 'panic'])
CONFIG_PANIC = list(
itertools.chain(
@@ -78,50 +62,11 @@ CONFIG_PANIC = list(
CONFIG_PANIC_DUAL_CORE = list(itertools.chain(itertools.product(['panic'], TARGETS_DUAL_CORE)))
CONFIG_PANIC_HALT = list(itertools.chain(itertools.product(['panic_halt'], TARGETS_ALL)))
CONFIGS_DUAL_CORE = list(
itertools.chain(
itertools.product(
[
'coredump_flash_default',
'coredump_uart_default',
'gdbstub',
'panic',
],
TARGETS_DUAL_CORE,
)
)
)
CONFIGS_DUAL_CORE = configs_for_app(PANIC_APP, ['gdbstub', 'panic'])
CONFIGS_HW_STACK_GUARD = list(
itertools.chain(
itertools.product(
['coredump_uart_default', 'gdbstub', 'panic'],
TARGETS_RISCV,
)
)
)
CONFIGS_HW_STACK_GUARD = configs_for_app(PANIC_APP, ['gdbstub', 'panic'])
CONFIGS_HW_STACK_GUARD_DUAL_CORE = list(
itertools.chain(
itertools.product(
['coredump_uart_default', 'gdbstub', 'panic'],
TARGETS_RISCV_DUAL_CORE,
)
)
)
CONFIG_CAPTURE_DRAM = list(
itertools.chain(itertools.product(['coredump_flash_capture_dram', 'coredump_uart_capture_dram'], TARGETS_ALL))
)
CONFIG_COREDUMP_SUMMARY = list(itertools.chain(itertools.product(['coredump_flash_default'], TARGETS_ALL)))
CONFIG_COREDUMP_SUMMARY_FLASH_ENCRYPTED = list(
itertools.chain(
itertools.product(['coredump_flash_encrypted'], ['esp32', 'esp32c3']),
itertools.product(['coredump_flash_encrypted_coredump_plain'], ['esp32', 'esp32c3']),
)
)
CONFIGS_HW_STACK_GUARD_DUAL_CORE = configs_for_app(PANIC_APP, ['gdbstub', 'panic'])
# Panic abort information will start with this string.
PANIC_ABORT_PREFIX = 'Panic reason: '
@@ -199,7 +144,8 @@ def common_test(
@pytest.mark.generic
@idf_parametrize('config, target', CONFIGS, indirect=['config', 'target'])
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', TARGETS_ALL, indirect=['target'])
def test_task_wdt_cpu0(dut: PanicTestDut, config: str, test_func_name: str) -> None:
dut.run_test_func(test_func_name)
dut.expect_exact('Task watchdog got triggered. The following tasks/users did not reset the watchdog in time:')
@@ -231,7 +177,8 @@ def test_task_wdt_cpu0(dut: PanicTestDut, config: str, test_func_name: str) -> N
@pytest.mark.generic
@idf_parametrize('config, target', CONFIGS_DUAL_CORE, indirect=['config', 'target'])
@pytest.mark.parametrize('app_path, config', CONFIGS_DUAL_CORE, indirect=True)
@idf_parametrize('target', TARGETS_DUAL_CORE, indirect=['target'])
def test_task_wdt_cpu1(dut: PanicTestDut, config: str, test_func_name: str) -> None:
dut.run_test_func(test_func_name)
dut.expect_exact('Task watchdog got triggered. The following tasks/users did not reset the watchdog in time:')
@@ -257,45 +204,9 @@ def test_task_wdt_cpu1(dut: PanicTestDut, config: str, test_func_name: str) -> N
common_test(dut, config, expected_backtrace=expected_backtrace, expected_coredump=[coredump_pattern])
@idf_parametrize(
'config,target,markers',
[
('coredump_flash_extram_stack_heap_esp32', 'esp32', (pytest.mark.psram,)),
('coredump_flash_extram_stack_heap_esp32s2', 'esp32s2', (pytest.mark.generic,)),
('coredump_flash_extram_stack_heap_esp32s3', 'esp32s3', (pytest.mark.quad_psram,)),
('coredump_flash_extram_stack_bss_esp32', 'esp32', (pytest.mark.psram,)),
('coredump_flash_extram_stack_bss_esp32s2', 'esp32s2', (pytest.mark.generic,)),
('coredump_flash_extram_stack_bss_esp32s3', 'esp32s3', (pytest.mark.quad_psram,)),
],
indirect=['config', 'target'],
)
def test_panic_extram_stack(dut: PanicTestDut, config: str) -> None:
if 'heap' in config:
dut.run_test_func('test_panic_extram_stack_heap')
else:
dut.run_test_func('test_panic_extram_stack_bss')
dut.expect_none('Allocated stack is not in external RAM')
dut.expect_none('Guru Meditation')
dut.expect_backtrace()
dut.expect_elf_sha256()
if dut.target == 'esp32':
# ESP32 External data memory range [0x3f800000-0x3fc00000)
coredump_pattern = re.compile('.coredump.tasks.data (0x3[fF][8-9a-bA-B][0-9a-fA-F]{5}) (0x[a-fA-F0-9]+) RW')
elif dut.target == 'esp32s2':
# ESP32-S2 External data memory range [0x3f500000-0x3ff80000)
coredump_pattern = re.compile(
'.coredump.tasks.data (0x3[fF][5-9a-fA-F][0-7][0-9a-fA-F]{4}) (0x[a-fA-F0-9]+) RW'
)
else:
# ESP32-S3 External data memory range [0x3c000000-0x3e000000)
coredump_pattern = re.compile('.coredump.tasks.data (0x3[c-dC-D][0-9a-fA-F]{6}) (0x[a-fA-F0-9]+) RW')
common_test(dut, config, expected_backtrace=None, expected_coredump=[coredump_pattern])
@pytest.mark.generic
@idf_parametrize('config, target', CONFIGS, indirect=['config', 'target'])
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', TARGETS_ALL, indirect=['target'])
def test_int_wdt(dut: PanicTestDut, target: str, config: str, test_func_name: str) -> None:
dut.run_test_func(test_func_name)
dut.expect_gme('Interrupt wdt timeout on CPU0')
@@ -317,7 +228,8 @@ def test_int_wdt(dut: PanicTestDut, target: str, config: str, test_func_name: st
@pytest.mark.generic
@idf_parametrize('config, target', CONFIGS, indirect=['config', 'target'])
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', TARGETS_ALL, indirect=['target'])
def test_int_wdt_cache_disabled(dut: PanicTestDut, target: str, config: str, test_func_name: str) -> None:
dut.run_test_func(test_func_name)
dut.expect_gme('Interrupt wdt timeout on CPU0')
@@ -339,7 +251,8 @@ def test_int_wdt_cache_disabled(dut: PanicTestDut, target: str, config: str, tes
@pytest.mark.generic
@idf_parametrize('config, target', CONFIGS, indirect=['config', 'target'])
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', TARGETS_ALL, indirect=['target'])
def test_cache_error(dut: PanicTestDut, config: str, test_func_name: str) -> None:
dut.run_test_func(test_func_name)
if dut.target in ['esp32c3', 'esp32c2']:
@@ -370,7 +283,8 @@ def test_cache_error(dut: PanicTestDut, config: str, test_func_name: str) -> Non
@pytest.mark.generic
@idf_parametrize('config, target', CONFIGS, indirect=['config', 'target'])
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', TARGETS_ALL, indirect=['target'])
def test_stack_overflow(dut: PanicTestDut, config: str, test_func_name: str) -> None:
dut.run_test_func(test_func_name)
if dut.is_xtensa:
@@ -391,7 +305,8 @@ def test_stack_overflow(dut: PanicTestDut, config: str, test_func_name: str) ->
@pytest.mark.generic
@idf_parametrize('config, target', CONFIGS, indirect=['config', 'target'])
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', TARGETS_ALL, indirect=['target'])
def test_instr_fetch_prohibited(dut: PanicTestDut, config: str, test_func_name: str) -> None:
dut.run_test_func(test_func_name)
if dut.is_xtensa:
@@ -418,7 +333,8 @@ def test_instr_fetch_prohibited(dut: PanicTestDut, config: str, test_func_name:
@pytest.mark.generic
@idf_parametrize('config, target', CONFIGS, indirect=['config', 'target'])
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', TARGETS_ALL, indirect=['target'])
def test_illegal_instruction(dut: PanicTestDut, config: str, test_func_name: str) -> None:
dut.run_test_func(test_func_name)
if dut.is_xtensa:
@@ -454,19 +370,22 @@ def check_x_prohibited(dut: PanicTestDut, config: str, test_func_name: str, oper
@pytest.mark.generic
@idf_parametrize('config, target', CONFIGS, indirect=['config', 'target'])
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', TARGETS_ALL, indirect=['target'])
def test_storeprohibited(dut: PanicTestDut, config: str, test_func_name: str) -> None:
check_x_prohibited(dut, config, test_func_name, 'Store')
@pytest.mark.generic
@idf_parametrize('config, target', CONFIGS, indirect=['config', 'target'])
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', TARGETS_ALL, indirect=['target'])
def test_loadprohibited(dut: PanicTestDut, config: str, test_func_name: str) -> None:
check_x_prohibited(dut, config, test_func_name, 'Load')
@pytest.mark.generic
@idf_parametrize('config, target', CONFIGS, indirect=['config', 'target'])
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', TARGETS_ALL, indirect=['target'])
def test_abort(dut: PanicTestDut, config: str, test_func_name: str) -> None:
dut.run_test_func(test_func_name)
regex_pattern = rb'abort\(\) was called at PC [0-9xa-f]+ on core 0'
@@ -488,7 +407,8 @@ def test_abort(dut: PanicTestDut, config: str, test_func_name: str) -> None:
@pytest.mark.generic
@idf_parametrize('config, target', CONFIGS_UBSAN, indirect=['config', 'target'])
@pytest.mark.parametrize('app_path, config', CONFIGS_UBSAN, indirect=True)
@idf_parametrize('target', TARGETS_ALL, indirect=['target'])
def test_ub(dut: PanicTestDut, config: str, test_func_name: str) -> None:
dut.run_test_func(test_func_name)
regex_pattern = rb'Undefined behavior of type out_of_bounds'
@@ -514,7 +434,8 @@ def test_ub(dut: PanicTestDut, config: str, test_func_name: str) -> None:
@pytest.mark.generic
@idf_parametrize('config, target', CONFIGS, indirect=['config', 'target'])
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', TARGETS_ALL, indirect=['target'])
def test_abort_cache_disabled(dut: PanicTestDut, config: str, test_func_name: str) -> None:
if dut.target == 'esp32s2':
pytest.xfail(reason='Crashes in itoa which is not in ROM, IDF-3572')
@@ -538,7 +459,8 @@ def test_abort_cache_disabled(dut: PanicTestDut, config: str, test_func_name: st
@pytest.mark.generic
@idf_parametrize('config, target', CONFIGS, indirect=['config', 'target'])
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', TARGETS_ALL, indirect=['target'])
def test_assert(dut: PanicTestDut, config: str, test_func_name: str) -> None:
dut.run_test_func(test_func_name)
regex_pattern = rb'assert failed:[\s\w()]*?\s[.\w/]*\.(?:c|cpp|h|hpp):\d.*$'
@@ -560,7 +482,8 @@ def test_assert(dut: PanicTestDut, config: str, test_func_name: str) -> None:
@pytest.mark.generic
@idf_parametrize('config, target', CONFIGS, indirect=['config', 'target'])
@pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True)
@idf_parametrize('target', TARGETS_ALL, indirect=['target'])
def test_assert_cache_disabled(dut: PanicTestDut, config: str, test_func_name: str) -> None:
if dut.target == 'esp32s2':
pytest.xfail(reason='Crashes in itoa which is not in ROM, IDF-3572')
@@ -1267,15 +1190,6 @@ def test_invalid_memory_region_execute_violation(dut: PanicTestDut, test_func_na
dut.expect_cpu_reset()
@pytest.mark.generic
@pytest.mark.parametrize('config', ['gdbstub_coredump'], indirect=True)
@idf_parametrize('target', ['esp32'], indirect=['target'])
def test_gdbstub_coredump(dut: PanicTestDut) -> None:
test_func_name = 'test_storeprohibited'
dut.run_test_func(test_func_name)
common_test(dut, 'gdbstub_coredump', get_default_backtrace(test_func_name))
def test_hw_stack_guard_cpu(dut: PanicTestDut, cpu: int) -> None:
dut.expect_exact(f"Guru Meditation Error: Core {cpu} panic'ed (Stack protection fault).")
dut.expect_none('ASSIST_DEBUG is not triggered BUT interrupt occurred!')
@@ -1294,7 +1208,8 @@ def test_hw_stack_guard_cpu(dut: PanicTestDut, cpu: int) -> None:
@pytest.mark.generic
@idf_parametrize('config, target', CONFIGS_HW_STACK_GUARD, indirect=['config', 'target'])
@pytest.mark.parametrize('app_path, config', CONFIGS_HW_STACK_GUARD, indirect=True)
@idf_parametrize('target', TARGETS_RISCV, indirect=['target'])
def test_hw_stack_guard_cpu0(dut: PanicTestDut, config: str, test_func_name: str) -> None:
dut.run_test_func(test_func_name)
test_hw_stack_guard_cpu(dut, 0)
@@ -1302,7 +1217,8 @@ def test_hw_stack_guard_cpu0(dut: PanicTestDut, config: str, test_func_name: str
@pytest.mark.generic
@idf_parametrize('config, target', CONFIGS_HW_STACK_GUARD_DUAL_CORE, indirect=['config', 'target'])
@pytest.mark.parametrize('app_path, config', CONFIGS_HW_STACK_GUARD_DUAL_CORE, indirect=True)
@idf_parametrize('target', TARGETS_RISCV_DUAL_CORE, indirect=['target'])
def test_hw_stack_guard_cpu1(dut: PanicTestDut, config: str, test_func_name: str) -> None:
dut.run_test_func(test_func_name)
test_hw_stack_guard_cpu(dut, 1)
@@ -1323,108 +1239,6 @@ def test_illegal_access(dut: PanicTestDut, config: str, test_func_name: str) ->
dut.expect_none('Guru Meditation')
@pytest.mark.generic
@idf_parametrize('config, target', CONFIG_CAPTURE_DRAM, indirect=['config', 'target'])
def test_capture_dram(dut: PanicTestDut, config: str, test_func_name: str) -> None:
dut.run_test_func(test_func_name)
regex_pattern = rb'assert failed:[\s\w()]*?\s[.\w/]*\.(?:c|cpp|h|hpp):\d.*$'
dut.expect(re.compile(regex_pattern, re.MULTILINE))
if dut.is_xtensa:
dut.expect_backtrace()
else:
dut.expect_stack_dump()
dut.expect_elf_sha256()
dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
core_elf_file = None
if 'flash' in config:
expect_coredump_flash_write_logs(dut, config)
core_elf_file = dut.process_coredump_flash()
elif 'uart' in config:
coredump_base64 = expect_coredump_uart_write_logs(dut)
core_elf_file = dut.process_coredump_uart(coredump_base64)
assert core_elf_file is not None
dut.start_gdb_for_coredump(core_elf_file)
assert dut.gdb_data_eval_expr('g_data_var') == '43'
assert dut.gdb_data_eval_expr('g_bss_var') == '55'
assert re.search(r'0x[0-9a-fA-F]+ "Coredump Test"', dut.gdb_data_eval_expr('g_heap_ptr'))
assert int(dut.gdb_data_eval_expr('g_cd_iram')) == 0x4243
assert int(dut.gdb_data_eval_expr('g_cd_dram')) == 0x4344
assert int(dut.gdb_data_eval_expr('g_noinit_var')) == 0xCAFEBABE
buffer_value = str(dut.gdb_data_eval_expr('g_noinit_buffer'))
assert 'NOINIT_TEST_STRING' in buffer_value
if dut.target not in ['esp32c61', 'esp32c2']:
assert int(dut.gdb_data_eval_expr('g_rtc_data_var')) == 0x55AA
assert int(dut.gdb_data_eval_expr('g_rtc_fast_var')) == 0xAABBCCDD
def _test_coredump_summary(dut: PanicTestDut, flash_encrypted: bool, coredump_encrypted: bool) -> None:
dut.run_test_func('test_setup_coredump_summary')
dut.expect_cpu_reset()
if flash_encrypted:
dut.expect_exact('Flash encryption mode is DEVELOPMENT (not secure)')
dut.run_test_func('test_coredump_summary')
if flash_encrypted and not coredump_encrypted:
dut.expect_exact('Flash encryption enabled in hardware and core dump partition is not encrypted!')
return
dut.expect_elf_sha256('App ELF file SHA256: ')
dut.expect_exact('Crashed task: main')
if dut.is_xtensa:
dut.expect_exact('Exception cause: 0')
else:
dut.expect_exact('Exception cause: 2')
dut.expect(PANIC_ABORT_PREFIX + r'assert failed:[\s\w()]*?\s[.\w/]*\.(?:c|cpp|h|hpp):\d.*$')
@pytest.mark.generic
@idf_parametrize('config, target', CONFIG_COREDUMP_SUMMARY, indirect=['config', 'target'])
def test_coredump_summary(dut: PanicTestDut) -> None:
_test_coredump_summary(dut, False, False)
@pytest.mark.flash_encryption
@idf_parametrize('config, target', CONFIG_COREDUMP_SUMMARY_FLASH_ENCRYPTED, indirect=['config', 'target'])
def test_coredump_summary_flash_encrypted(dut: PanicTestDut, config: str) -> None:
_test_coredump_summary(dut, True, config == 'coredump_flash_encrypted')
@pytest.mark.generic
@idf_parametrize('config', ['coredump_flash_default'], indirect=['config'])
@idf_parametrize('target', TARGETS_ALL, indirect=['target'])
def test_tcb_corrupted(dut: PanicTestDut, target: str, config: str, test_func_name: str) -> None:
dut.run_test_func(test_func_name)
if dut.is_xtensa:
dut.expect(re.compile(rb"Guru Meditation Error: Core\s+\d\s+panic'ed \((LoadProhibited|StoreProhibited)\)"))
dut.expect_reg_dump()
dut.expect_backtrace()
else:
dut.expect(re.compile(rb"Guru Meditation Error: Core\s+\d\s+panic'ed \((Load|Store) access fault\)"))
dut.expect_reg_dump()
dut.expect_stack_dump()
dut.expect_elf_sha256()
dut.expect_none('Guru Meditation')
# Verify that valid tasks are captured in coredump despite IDLE task corruption
# TCB NAME
# ---------- ----------------
if dut.is_multi_core:
regex_patterns = [
rb'[0-9xa-fA-F] main',
rb'[0-9xa-fA-F] ipc0',
rb'[0-9xa-fA-F] ipc1',
]
else:
regex_patterns = [rb'[0-9xa-fA-F] main']
coredump_pattern = [re.compile(pattern.decode('utf-8')) for pattern in regex_patterns]
common_test(dut, config, expected_backtrace=None, expected_coredump=coredump_pattern)
@pytest.mark.generic
@idf_parametrize('config, target', CONFIG_PANIC_HALT, indirect=['config', 'target'])
def test_panic_halt(dut: PanicTestDut) -> None:

View File

@@ -0,0 +1,18 @@
# Flash DOUT mode (QEMU limitation)
CONFIG_ESPTOOLPY_FLASHMODE_DOUT=y
# Less noisy output
CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y
CONFIG_LOG_DEFAULT_LEVEL_WARN=y
# To check for stack overflows
CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y
# To panic on task WDT
CONFIG_ESP_TASK_WDT_PANIC=y
# For vTaskGetInfo() used in test_stack_overflow()
CONFIG_FREERTOS_USE_TRACE_FACILITY=y
# Increase main task stack size
CONFIG_ESP_MAIN_TASK_STACK_SIZE=4096