mirror of
https://github.com/espressif/esp-idf.git
synced 2026-05-28 16:46:31 +03:00
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:
@@ -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:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
@@ -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
|
||||
10
tools/test_apps/system/panic/coredump/CMakeLists.txt
Normal file
10
tools/test_apps/system/panic/coredump/CMakeLists.txt
Normal 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)
|
||||
18
tools/test_apps/system/panic/coredump/README.md
Normal file
18
tools/test_apps/system/panic/coredump/README.md
Normal 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
|
||||
```
|
||||
28
tools/test_apps/system/panic/coredump/conftest.py
Normal file
28
tools/test_apps/system/panic/coredump/conftest.py
Normal 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)
|
||||
331
tools/test_apps/system/panic/coredump/pytest_panic_coredump.py
Normal file
331
tools/test_apps/system/panic/coredump/pytest_panic_coredump.py
Normal 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)
|
||||
@@ -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:
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
18
tools/test_apps/system/panic/panic_base/sdkconfig.defaults
Normal file
18
tools/test_apps/system/panic/panic_base/sdkconfig.defaults
Normal 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
|
||||
Reference in New Issue
Block a user