test(esp_hw_support): add esp_cpu api test app

Add a dedicated esp_cpu test app that covers the supported helper APIs,
interrupt helpers, debug helper validation, and destructive trigger cases.

Made-with: Cursor
This commit is contained in:
Marius Vikhammer
2026-04-03 14:01:22 +08:00
parent 4eb97bb84d
commit 63d18fad59
15 changed files with 714 additions and 0 deletions
@@ -1,5 +1,7 @@
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
components/esp_hw_support/test_apps/cpu:
components/esp_hw_support/test_apps/esp_hw_support_unity_tests:
components/esp_hw_support/test_apps/host_test_linux:
@@ -0,0 +1,14 @@
# This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.22)
set(COMPONENTS main)
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/test_apps/components")
list(PREPEND SDKCONFIG_DEFAULTS "$ENV{IDF_PATH}/tools/test_apps/configs/sdkconfig.debug_helpers" "sdkconfig.defaults")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
set(COMPONENTS main)
project(cpu)
@@ -0,0 +1,12 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- |
This test app collects `esp_cpu.h` coverage in one place.
It currently includes:
- safe Unity tests for basic CPU helpers and interrupt helpers
- a multi-stage reset test for `esp_cpu_reset()`
- a thread-pointer roundtrip test and per-core `esp_cpu_get_core_id()` coverage
- non-destructive sanity checks for breakpoint and watchpoint setup APIs
- non-recoverable breakpoint and watchpoint trigger tests, plus a multi-core stall test where supported
@@ -0,0 +1,14 @@
set(srcs "test_app_main.c"
"test_cpu_basic.c"
"test_cpu_interrupts.c"
"test_cpu_reset.c"
"test_cpu_watchpoint.c"
"test_cpu_breakpoint.c"
"test_cpu_non_recoverable.c"
"test_cpu_stall.c")
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
# the component can be registered as WHOLE_ARCHIVE
idf_component_register(SRCS ${srcs}
REQUIRES unity test_utils esp_hw_support esp_system
WHOLE_ARCHIVE)
@@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "unity.h"
#include "unity_test_runner.h"
#include "esp_log.h"
static const char *TAG = "cpu_test_app";
void app_main(void)
{
ESP_LOGI(TAG, "Running esp_cpu test app");
unity_run_menu();
}
@@ -0,0 +1,146 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "soc/soc_caps.h"
#include "esp_cpu.h"
#include "test_utils.h"
static intptr_t __attribute__((noinline)) get_call_addr_delta(void)
{
intptr_t return_addr = (intptr_t)__builtin_return_address(0);
intptr_t call_addr = esp_cpu_get_call_addr(return_addr);
return return_addr - call_addr;
}
typedef struct {
TaskHandle_t waiter;
int observed_core_id;
} core_id_task_ctx_t;
static void core_id_task(void *arg)
{
core_id_task_ctx_t *ctx = (core_id_task_ctx_t *)arg;
ctx->observed_core_id = esp_cpu_get_core_id();
xTaskNotifyGive(ctx->waiter);
vTaskDelete(NULL);
}
TEST_CASE("CPU basic: current core id matches pinned task core", "[cpu][cpu_basic]")
{
const TaskHandle_t waiter = xTaskGetCurrentTaskHandle();
core_id_task_ctx_t task_ctx[SOC_CPU_CORES_NUM] = {0};
for (int core_id = 0; core_id < SOC_CPU_CORES_NUM; ++core_id) {
task_ctx[core_id].waiter = waiter;
task_ctx[core_id].observed_core_id = -1;
TEST_ASSERT_EQUAL(pdPASS, xTaskCreatePinnedToCore(core_id_task,
"cpu_core_id",
2048,
&task_ctx[core_id],
UNITY_FREERTOS_PRIORITY + 1,
NULL,
core_id));
}
for (int core_id = 0; core_id < SOC_CPU_CORES_NUM; ++core_id) {
/* Use pdFALSE (counting mode) so accumulated notifications don't cause
* a false failure if both tasks complete before the first wait. */
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
}
for (int core_id = 0; core_id < SOC_CPU_CORES_NUM; ++core_id) {
TEST_ASSERT_EQUAL(core_id, task_ctx[core_id].observed_core_id);
}
}
TEST_CASE("CPU basic: stack pointer is near current stack frame", "[cpu][cpu_basic]")
{
uint32_t local_marker = 0;
uintptr_t sp = (uintptr_t)esp_cpu_get_sp();
uintptr_t local = (uintptr_t)&local_marker;
uintptr_t delta = (sp > local) ? (sp - local) : (local - sp);
TEST_ASSERT_TRUE(delta < 4096);
}
TEST_CASE("CPU basic: cycle count increases", "[cpu][cpu_basic]")
{
esp_cpu_cycle_count_t start = esp_cpu_get_cycle_count();
for (volatile int i = 0; i < 256; i++) {
__asm__ __volatile__("nop");
}
esp_cpu_cycle_count_t end = esp_cpu_get_cycle_count();
TEST_ASSERT_TRUE((end - start) > 0);
}
TEST_CASE("CPU basic: current privilege level is sane", "[cpu][cpu_basic]")
{
int privilege = esp_cpu_get_curr_privilege_level();
#if __XTENSA__
TEST_ASSERT_EQUAL(-1, privilege);
#else
TEST_ASSERT_TRUE(privilege >= 0);
#endif
}
TEST_CASE("CPU basic: pc to addr conversion matches architecture", "[cpu][cpu_basic]")
{
uintptr_t addr = (uintptr_t)esp_cpu_pc_to_addr(0x00001234U);
#if __XTENSA__
TEST_ASSERT_EQUAL_HEX32(0x40001234U, addr);
#else
TEST_ASSERT_EQUAL_HEX32(0x00001234U, addr);
#endif
}
TEST_CASE("CPU basic: call addr delta matches architecture", "[cpu][cpu_basic]")
{
#if __XTENSA__
TEST_ASSERT_EQUAL(3, get_call_addr_delta());
#else
TEST_ASSERT_EQUAL(4, get_call_addr_delta());
#endif
}
TEST_CASE("CPU basic: compare and set succeeds on matching value", "[cpu][cpu_basic]")
{
volatile uint32_t value = 1;
TEST_ASSERT_TRUE(esp_cpu_compare_and_set(&value, 1, 2));
TEST_ASSERT_EQUAL_UINT32(2, value);
}
TEST_CASE("CPU basic: compare and set fails on mismatched value", "[cpu][cpu_basic]")
{
volatile uint32_t value = 2;
TEST_ASSERT_FALSE(esp_cpu_compare_and_set(&value, 1, 3));
TEST_ASSERT_EQUAL_UINT32(2, value);
}
TEST_CASE("CPU basic: thread pointer can be set and restored", "[cpu][cpu_basic]")
{
uintptr_t marker_words[4] = {0};
void *original = esp_cpu_get_threadptr();
void *readback;
void *restored;
esp_cpu_set_threadptr((void *)marker_words);
readback = esp_cpu_get_threadptr();
esp_cpu_set_threadptr(original);
restored = esp_cpu_get_threadptr();
TEST_ASSERT_EQUAL_PTR(marker_words, readback);
TEST_ASSERT_EQUAL_PTR(original, restored);
}
@@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "unity.h"
#include "soc/soc_caps.h"
#include "esp_cpu.h"
#if SOC_CPU_BREAKPOINTS_NUM > 0
static void __attribute__((noinline)) breakpoint_target_function(void)
{
__asm__ __volatile__("" ::: "memory");
}
TEST_CASE("CPU breakpoint: all breakpoints can be set and cleared", "[cpu][cpu_breakpoint]")
{
for (int bp_num = 0; bp_num < SOC_CPU_BREAKPOINTS_NUM; ++bp_num) {
TEST_ASSERT_EQUAL(ESP_OK, esp_cpu_set_breakpoint(bp_num, (const void *)breakpoint_target_function));
}
for (int bp_num = 0; bp_num < SOC_CPU_BREAKPOINTS_NUM; ++bp_num) {
TEST_ASSERT_EQUAL(ESP_OK, esp_cpu_clear_breakpoint(bp_num));
}
}
TEST_CASE("CPU breakpoint: invalid index is rejected", "[cpu][cpu_breakpoint]")
{
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_cpu_set_breakpoint(-1, (const void *)breakpoint_target_function));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_cpu_clear_breakpoint(-1));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_cpu_set_breakpoint(SOC_CPU_BREAKPOINTS_NUM, (const void *)breakpoint_target_function));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_cpu_clear_breakpoint(SOC_CPU_BREAKPOINTS_NUM));
}
#endif
@@ -0,0 +1,158 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stdint.h>
#include "unity.h"
#include "soc/soc_caps.h"
#include "esp_bit_defs.h"
#include "esp_cpu.h"
static void dummy_handler(void *arg)
{
volatile uint32_t *counter = (volatile uint32_t *)arg;
if (counter != NULL) {
(*counter)++;
}
}
static bool is_valid_desc_type(esp_cpu_intr_type_t type)
{
return type == ESP_CPU_INTR_TYPE_LEVEL
|| type == ESP_CPU_INTR_TYPE_EDGE
|| type == ESP_CPU_INTR_TYPE_NA;
}
static int find_test_interrupt(void)
{
const int core_id = esp_cpu_get_core_id();
for (int intr_num = 0; intr_num < SOC_CPU_INTR_NUM; intr_num++) {
esp_cpu_intr_desc_t intr_desc;
esp_cpu_intr_get_desc(core_id, intr_num, &intr_desc);
if (intr_desc.flags & (ESP_CPU_INTR_DESC_FLAG_SPECIAL | ESP_CPU_INTR_DESC_FLAG_RESVD)) {
continue;
}
if (esp_cpu_intr_has_handler(intr_num)) {
continue;
}
return intr_num;
}
return -1;
}
TEST_CASE("CPU intr: descriptor is valid for all interrupt lines", "[cpu][cpu_intr]")
{
const int core_id = esp_cpu_get_core_id();
const uint32_t valid_flags = ESP_CPU_INTR_DESC_FLAG_SPECIAL | ESP_CPU_INTR_DESC_FLAG_RESVD;
for (int intr_num = 0; intr_num < SOC_CPU_INTR_NUM; intr_num++) {
esp_cpu_intr_desc_t intr_desc;
esp_cpu_intr_get_desc(core_id, intr_num, &intr_desc);
TEST_ASSERT_TRUE(is_valid_desc_type(intr_desc.type));
TEST_ASSERT_TRUE(intr_desc.priority >= -1);
TEST_ASSERT_EQUAL_HEX32(0, intr_desc.flags & ~valid_flags);
}
}
TEST_CASE("CPU intr: handler and arg can be set and cleared", "[cpu][cpu_intr]")
{
int intr_num = find_test_interrupt();
if (intr_num < 0) {
TEST_IGNORE_MESSAGE("No free interrupt line was found for the handler test");
}
volatile uint32_t counter = 0;
esp_cpu_intr_set_handler(intr_num, dummy_handler, (void *)&counter);
TEST_ASSERT_TRUE(esp_cpu_intr_has_handler(intr_num));
#if !__XTENSA__
TEST_ASSERT_EQUAL_PTR(&counter, esp_cpu_intr_get_handler_arg(intr_num));
#endif
esp_cpu_intr_set_handler(intr_num, NULL, NULL);
TEST_ASSERT_FALSE(esp_cpu_intr_has_handler(intr_num));
}
TEST_CASE("CPU intr: enabled mask can be toggled for a free line", "[cpu][cpu_intr]")
{
int intr_num = find_test_interrupt();
if (intr_num < 0) {
TEST_IGNORE_MESSAGE("No free interrupt line was found for the enabled mask test");
}
const uint32_t bit = BIT(intr_num);
const uint32_t old_mask = esp_cpu_intr_get_enabled_mask();
esp_cpu_intr_disable(bit);
TEST_ASSERT_EQUAL_HEX32(0, esp_cpu_intr_get_enabled_mask() & bit);
esp_cpu_intr_enable(bit);
TEST_ASSERT_NOT_EQUAL(0, esp_cpu_intr_get_enabled_mask() & bit);
if ((old_mask & bit) == 0) {
esp_cpu_intr_disable(bit);
} else {
esp_cpu_intr_enable(bit);
}
}
#if SOC_CPU_HAS_FLEXIBLE_INTC
TEST_CASE("CPU intr: type can be changed on a free line", "[cpu][cpu_intr]")
{
int intr_num = find_test_interrupt();
if (intr_num < 0) {
TEST_IGNORE_MESSAGE("No free interrupt line was found for the type test");
}
esp_cpu_intr_type_t old_type = esp_cpu_intr_get_type(intr_num);
esp_cpu_intr_type_t new_type = (old_type == ESP_CPU_INTR_TYPE_EDGE) ? ESP_CPU_INTR_TYPE_LEVEL : ESP_CPU_INTR_TYPE_EDGE;
esp_cpu_intr_set_type(intr_num, new_type);
TEST_ASSERT_EQUAL(new_type, esp_cpu_intr_get_type(intr_num));
esp_cpu_intr_set_type(intr_num, old_type);
}
TEST_CASE("CPU intr: priority can be changed on a free line", "[cpu][cpu_intr]")
{
int intr_num = find_test_interrupt();
if (intr_num < 0) {
TEST_IGNORE_MESSAGE("No free interrupt line was found for the priority test");
}
int old_priority = esp_cpu_intr_get_priority(intr_num);
int new_priority = (old_priority == 1) ? 2 : 1;
esp_cpu_intr_set_priority(intr_num, new_priority);
TEST_ASSERT_EQUAL(new_priority, esp_cpu_intr_get_priority(intr_num));
esp_cpu_intr_set_priority(intr_num, old_priority);
}
#endif
TEST_CASE("CPU intr: edge ack is callable", "[cpu][cpu_intr]")
{
int intr_num = find_test_interrupt();
if (intr_num < 0) {
TEST_IGNORE_MESSAGE("No free interrupt line was found for the edge ack test");
}
#if SOC_CPU_HAS_FLEXIBLE_INTC
esp_cpu_intr_type_t old_type = esp_cpu_intr_get_type(intr_num);
esp_cpu_intr_set_type(intr_num, ESP_CPU_INTR_TYPE_EDGE);
esp_cpu_intr_edge_ack(intr_num);
esp_cpu_intr_set_type(intr_num, old_type);
#else
const int core_id = esp_cpu_get_core_id();
esp_cpu_intr_desc_t intr_desc;
esp_cpu_intr_get_desc(core_id, intr_num, &intr_desc);
if (intr_desc.type != ESP_CPU_INTR_TYPE_EDGE) {
TEST_IGNORE_MESSAGE("No free edge interrupt line was found for the edge ack test");
}
esp_cpu_intr_edge_ack(intr_num);
#endif
}
@@ -0,0 +1,92 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "unity.h"
#include "unity_test_runner.h"
#include "soc/soc_caps.h"
#include "esp_cpu.h"
#include "esp_system.h"
#if __XTENSA__
#define CPU_DEBUG_EXCEPTION_REASON "Unhandled debug exception"
#else
#define CPU_DEBUG_EXCEPTION_REASON "Breakpoint"
#endif
#if SOC_CPU_BREAKPOINTS_NUM > 0
static void __attribute__((noinline)) breakpoint_target_function(void)
{
__asm__ __volatile__("" ::: "memory");
}
static void trigger_breakpoint_panic(void)
{
TEST_ASSERT_EQUAL(ESP_OK, esp_cpu_set_breakpoint(0, (const void *)breakpoint_target_function));
breakpoint_target_function();
while (true) {
}
}
static void check_breakpoint_reset_reason(void)
{
TEST_ASSERT_EQUAL(ESP_RST_PANIC, esp_reset_reason());
}
TEST_CASE_MULTIPLE_STAGES("CPU breakpoint: trigger causes panic",
"[cpu][cpu_breakpoint][cpu_non_recoverable][reset=" CPU_DEBUG_EXCEPTION_REASON ",SW_CPU_RESET]",
trigger_breakpoint_panic, check_breakpoint_reset_reason)
#endif
#if SOC_CPU_WATCHPOINTS_NUM > 0
static volatile uint32_t s_watchpoint_target;
static volatile uint32_t s_watchpoint_sink;
#define WATCHPOINT_TARGET_ADDR ((const void *)&s_watchpoint_target)
static void __attribute__((noinline)) trigger_watchpoint_store(void)
{
s_watchpoint_target++;
}
static void __attribute__((noinline)) trigger_watchpoint_load(void)
{
s_watchpoint_sink = s_watchpoint_target;
}
static void trigger_watchpoint_store_panic(void)
{
s_watchpoint_target = 0;
TEST_ASSERT_EQUAL(ESP_OK,
esp_cpu_set_watchpoint(0, WATCHPOINT_TARGET_ADDR, sizeof(s_watchpoint_target), ESP_CPU_WATCHPOINT_STORE));
trigger_watchpoint_store();
while (true) {
}
}
static void check_watchpoint_reset_reason(void)
{
TEST_ASSERT_EQUAL(ESP_RST_PANIC, esp_reset_reason());
}
TEST_CASE_MULTIPLE_STAGES("CPU watchpoint: store trigger causes panic",
"[cpu][cpu_watchpoint][cpu_non_recoverable][reset=" CPU_DEBUG_EXCEPTION_REASON ",SW_CPU_RESET]",
trigger_watchpoint_store_panic, check_watchpoint_reset_reason)
static void trigger_watchpoint_load_panic(void)
{
s_watchpoint_target = 0x12345678;
s_watchpoint_sink = 0;
TEST_ASSERT_EQUAL(ESP_OK,
esp_cpu_set_watchpoint(0, WATCHPOINT_TARGET_ADDR, sizeof(s_watchpoint_target), ESP_CPU_WATCHPOINT_LOAD));
trigger_watchpoint_load();
while (true) {
}
}
TEST_CASE_MULTIPLE_STAGES("CPU watchpoint: load trigger causes panic",
"[cpu][cpu_watchpoint][cpu_non_recoverable][reset=" CPU_DEBUG_EXCEPTION_REASON ",SW_CPU_RESET]",
trigger_watchpoint_load_panic, check_watchpoint_reset_reason)
#endif
@@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "unity.h"
#include "unity_test_runner.h"
#include "esp_cpu.h"
#include "esp_system.h"
#if SOC_CPU_CORES_NUM == 1
static void do_cpu_reset(void)
{
esp_cpu_reset(esp_cpu_get_core_id());
while (true) {
}
}
static void check_cpu_reset_reason(void)
{
TEST_ASSERT_EQUAL(ESP_RST_SW, esp_reset_reason());
}
TEST_CASE_MULTIPLE_STAGES("CPU reset: current core reset reboots the chip", "[cpu][cpu_reset][reset=SW_CPU_RESET]",
do_cpu_reset, check_cpu_reset_reason)
#endif
@@ -0,0 +1,73 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stdint.h>
#include "unity.h"
#include "soc/soc_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_rom_sys.h"
#include "esp_cpu.h"
#include "test_utils.h"
#if SOC_CPU_CORES_NUM > 1
static volatile bool s_stall_task_run;
static volatile bool s_stall_task_started;
static volatile uint32_t s_stall_counter;
static void stall_counter_task(void *arg)
{
s_stall_task_started = true;
while (s_stall_task_run) {
s_stall_counter++;
}
vTaskDelete(NULL);
}
TEST_CASE("CPU stall: other core stops and resumes", "[cpu][cpu_stall]")
{
const int current_core = esp_cpu_get_core_id();
const int other_core = !current_core;
TaskHandle_t task_handle = NULL;
s_stall_task_run = true;
s_stall_task_started = false;
s_stall_counter = 0;
TEST_ASSERT_EQUAL(pdPASS,
xTaskCreatePinnedToCore(stall_counter_task, "cpu_stall_counter", 2048, NULL,
UNITY_FREERTOS_PRIORITY + 1, &task_handle, other_core));
for (int i = 0; i < 1000 && !s_stall_task_started; i++) {
esp_rom_delay_us(100);
}
TEST_ASSERT_TRUE(s_stall_task_started);
uint32_t start_count = s_stall_counter;
for (int i = 0; i < 1000 && s_stall_counter == start_count; i++) {
esp_rom_delay_us(100);
}
TEST_ASSERT_TRUE(s_stall_counter > start_count);
esp_cpu_stall(other_core);
esp_rom_delay_us(1000);
uint32_t stalled_count = s_stall_counter;
esp_rom_delay_us(20000);
uint32_t stalled_count_late = s_stall_counter;
esp_cpu_unstall(other_core);
TEST_ASSERT_EQUAL_UINT32(stalled_count, stalled_count_late);
for (int i = 0; i < 1000 && s_stall_counter == stalled_count_late; i++) {
esp_rom_delay_us(100);
}
TEST_ASSERT_TRUE(s_stall_counter > stalled_count_late);
s_stall_task_run = false;
vTaskDelay(1);
}
#endif
@@ -0,0 +1,58 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "unity.h"
#include "soc/soc_caps.h"
#include "esp_cpu.h"
#if SOC_CPU_WATCHPOINTS_NUM > 0
static volatile uint32_t s_watchpoint_target;
#define WATCHPOINT_TARGET_ADDR ((const void *)&s_watchpoint_target)
TEST_CASE("CPU watchpoint: invalid index is rejected", "[cpu][cpu_watchpoint]")
{
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG,
esp_cpu_set_watchpoint(-1, WATCHPOINT_TARGET_ADDR, sizeof(s_watchpoint_target), ESP_CPU_WATCHPOINT_ACCESS));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG,
esp_cpu_set_watchpoint(SOC_CPU_WATCHPOINTS_NUM, WATCHPOINT_TARGET_ADDR, sizeof(s_watchpoint_target), ESP_CPU_WATCHPOINT_ACCESS));
}
TEST_CASE("CPU watchpoint: invalid non-power-of-two size is rejected", "[cpu][cpu_watchpoint]")
{
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG,
esp_cpu_set_watchpoint(0, WATCHPOINT_TARGET_ADDR, 0, ESP_CPU_WATCHPOINT_ACCESS));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG,
esp_cpu_set_watchpoint(0, WATCHPOINT_TARGET_ADDR, 3, ESP_CPU_WATCHPOINT_ACCESS));
}
TEST_CASE("CPU watchpoint: misaligned address is rejected", "[cpu][cpu_watchpoint]")
{
const void *misaligned_addr = (const void *)((uintptr_t)&s_watchpoint_target + 1);
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG,
esp_cpu_set_watchpoint(0, misaligned_addr, sizeof(s_watchpoint_target), ESP_CPU_WATCHPOINT_ACCESS));
}
TEST_CASE("CPU watchpoint: all watchpoints can be set and cleared", "[cpu][cpu_watchpoint]")
{
for (int wp_num = 0; wp_num < SOC_CPU_WATCHPOINTS_NUM; ++wp_num) {
TEST_ASSERT_EQUAL(ESP_OK,
esp_cpu_set_watchpoint(wp_num, WATCHPOINT_TARGET_ADDR, sizeof(s_watchpoint_target), ESP_CPU_WATCHPOINT_ACCESS));
}
for (int wp_num = 0; wp_num < SOC_CPU_WATCHPOINTS_NUM; ++wp_num) {
TEST_ASSERT_EQUAL(ESP_OK, esp_cpu_clear_watchpoint(wp_num));
}
}
TEST_CASE("CPU watchpoint: invalid clear index is rejected", "[cpu][cpu_watchpoint]")
{
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_cpu_clear_watchpoint(-1));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_cpu_clear_watchpoint(SOC_CPU_WATCHPOINTS_NUM));
}
#endif
@@ -0,0 +1,59 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded_idf import IdfDut
from pytest_embedded_idf.utils import idf_parametrize
def _run_non_recoverable_case(dut: IdfDut, test_name: str, expected_reason: str) -> None:
dut.skip_decode_panic = True
dut.expect_exact('Press ENTER to see the list of tests')
dut.write(f'"{test_name}"')
# TEST_CASE_MULTIPLE_STAGES shows a stage sub-menu before running; select stage 1 to trigger the fault
dut.expect_exact('(1)')
dut.write('1')
actual_reason = dut.expect(r"Core\s+\d+\s+panic'ed \(([^)]+)\)", timeout=10).group(1).decode()
assert actual_reason == expected_reason, f"expected panic reason '{expected_reason}', got '{actual_reason}'"
dut.expect_exact('Rebooting...')
@pytest.mark.generic
@idf_parametrize(
'config,target',
[('default', 'supported_targets')],
indirect=['config', 'target'],
)
def test_cpu(dut: IdfDut) -> None:
dut.run_all_single_board_cases(group='!cpu_reset&!cpu_non_recoverable', timeout=120)
@pytest.mark.generic
@idf_parametrize(
'config,target',
[('default', 'supported_targets')],
indirect=['config', 'target'],
)
def test_cpu_reset(dut: IdfDut) -> None:
dut.run_all_single_board_cases(group='cpu_reset', timeout=120)
@pytest.mark.generic
@idf_parametrize(
'config,target',
[('default', 'supported_targets')],
indirect=['config', 'target'],
)
def test_cpu_non_recoverable(dut: IdfDut) -> None:
expected_reason = 'Unhandled debug exception' if dut.target in {'esp32', 'esp32s2', 'esp32s3'} else 'Breakpoint'
present_cases = {case.name for case in dut.test_menu}
for case_name in (
'CPU breakpoint: trigger causes panic',
'CPU watchpoint: store trigger causes panic',
'CPU watchpoint: load trigger causes panic',
):
if case_name in present_cases:
_run_non_recoverable_case(dut, case_name, expected_reason)
@@ -0,0 +1,2 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
@@ -0,0 +1,4 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
CONFIG_ESP_TASK_WDT_EN=n