diff --git a/AGENTS.md b/AGENTS.md
index 735cd06c..283be213 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -84,7 +84,7 @@ STM32F407ZGTx @168MHz(HSE 8MHz → PLL M=4/N=168/P=2);**仅 Keil MDK-ARM v
- 所有 `net_*` socket 操作经 `netMsgQueue` 消息队列在 netTask 串行执行(线程安全);非 netTask 任务直接调 `net_*` API,勿直接碰 CH395F 驱动
- 网口配置:`/config.ini` [network] 段(工厂默认 192.168.1.100/24、gw .1;MAC 全零 = 芯片内置;UI 保存立即生效;旧档 `/cfg/netcfg.bin` 首启自动迁移)→ `docs/网络配置说明.md`
- **新 .c 文件必须手动注册到两处**才能双端编译:`MDK-ARM/STM32F407-Demo.uvprojx` + `simulator/CMakeLists.txt`(用 `new-source-file` skill)
-- 测试构建:`test/test_config.h` 的 `TEST_SUITE_CH395F/STORAGE/GD5F/RTC` **互斥**(一次只能启用一个,`#error`);全部注释 = 产品构建;CH395F 套件 `test/ch395f_test.h`:硬件层 Phase 1~5 与 NET 层 Phase 6~11 互斥;CH395F 硬件层模式下 netTask(基础线程:无条件创建、全构建唯一实例)入口自挂起(不运行),testTask 独占 CH395F
+- 测试构建:`test/test_config.h` 的 `TEST_SUITE_CH395F/STORAGE/GD5F/RTC` **互斥**(一次只能启用一个,`#error`);全部注释 = 产品构建;**破坏性用例默认关闭**:同文件 `ENABLE_DESTRUCTIVE_TESTS`(默认注释)控制写/擦 NAND、格式化、坏块注入与 FatFS 写类用例——关闭时套件为**只读模式**(开机横幅 `mode: READ-ONLY`,可随时在在用设备上跑、不动卷内容与容量),只在**产品首次上电/产线首启**或明确需要时打开(`mode: DESTRUCTIVE`);2026-09-21 实测一轮破坏性 GD5F 运行即消耗一个块(容量 95248 → 95200,TC-STO-04 恢复不回来);CH395F 套件 `test/ch395f_test.h`:硬件层 Phase 1~5 与 NET 层 Phase 6~11 互斥;CH395F 硬件层模式下 netTask(基础线程:无条件创建、全构建唯一实例)入口自挂起(不运行),testTask 独占 CH395F
- **改驱动前先读对应 `docs/问题记录/*_Trap_Records.md`**(CH395F / GD5F2GQ5UE / SD2506);各模块测试指南见 `docs/测试说明/`
## 串口助手 serial_tool
diff --git a/docs/内存管理/内存内容分配策略.md b/docs/内存管理/内存内容分配策略.md
index 847255b4..0402fe8e 100644
--- a/docs/内存管理/内存内容分配策略.md
+++ b/docs/内存管理/内存内容分配策略.md
@@ -125,10 +125,10 @@ LVGL 集合明细(均在 SRAM):LVGL 池 40,960B(`lv_mem_core_builtin.o`
1. `@build`(0 错误 0 警告)
2. 产品构建 `@test`(`test/run_boot_test.py`):标记校验 PASS,且**零 `[ERR]`**(默认 Forbidden 已含 `[ERR]`)
-3. 存储套件(`test_config.h` → `TEST_SUITE_STORAGE`):期望 **20/20 PASSED**
-4. GD5F 套件(`TEST_SUITE_GD5F`):期望 **68/68 PASSED**,并确认日志出现 2 条守卫负路径 `[ERR]`(TC-GD5F-704/705)——套件运行须放宽 Forbidden(`--forbidden "FAIL;HardFault"`)
+3. 存储套件(`test_config.h` → `TEST_SUITE_STORAGE`):只读模式实测 **3/3 PASSED**(破坏性关,卷内容不变);开启 `ENABLE_DESTRUCTIVE_TESTS` 后为全量集成用例(2026-09-20 实测 **20/20 PASSED**,含 TC-STO-01/02/03/05/06)
+4. GD5F 套件(`TEST_SUITE_GD5F`):只读模式实测 **16/16 PASSED**(含只读的守卫负路径 TC-GD5F-704);开启 `ENABLE_DESTRUCTIVE_TESTS` 后 **68/68 PASSED**,并确认日志出现 2 条守卫负路径 `[ERR]`(TC-GD5F-704/705)——破坏性模式运行须放宽 Forbidden(`--forbidden "FAIL;HardFault"`)
5. 恢复跑(`ENABLE_STORAGE_RECOVERY_TESTS`):期望 **3/3 PASSED**
-6. 套件**不得**在未做恢复的情况下连续重复运行:TC-STO-03 与 GD5F 套件的大写入都会累积运行期坏块(2026-09-21 实测一次 GD5F 整轮运行容量 95248 → 95200,且阶段 8 会 `nand_ftl_format` 清空卷)。注意 §7.5 的恢复跑只重建 BBT + 重新格式化,**不恢复容量**(实测恢复后仍 95200,断电重扫亦然——被排除的块会被 dhara 每次扫描重新判定为坏);容量基线以 95200 pages 为准。
+6. **破坏性用例默认关闭**(见 `test_config.h` 的 `ENABLE_DESTRUCTIVE_TESTS`):写/擦 NAND、格式化、坏块注入与 FatFS 写类用例只在首次上电/产线首启或明确需要时开启——2026-09-21 实测一轮破坏性 GD5F 运行即消耗一个块(容量 95248 → 95200,TC-STO-04 恢复不回来);日常回归请保持只读模式
通过全部关卡后:更新 §6 台账、`AGENTS.md` 栈表、`.sct` 头注释,并把本次裁决补入 §5。
diff --git a/docs/测试说明/GD5F2GQ5UE_Test_Guide.md b/docs/测试说明/GD5F2GQ5UE_Test_Guide.md
index 719045a3..9396df3d 100644
--- a/docs/测试说明/GD5F2GQ5UE_Test_Guide.md
+++ b/docs/测试说明/GD5F2GQ5UE_Test_Guide.md
@@ -53,27 +53,40 @@ ECC 状态(`ECCS1:ECCS0`):`00`=无错;`01/10/11`=纠正 1/2/3 bit;`10`
所有测试套件(CH395F / 存储 / GD5F)统一收归 `ch395fTestTask` 线程(`test/ch395f_test_task.c`
的 `StartCh395fTestTask`),通过 `test/test_config.h` 中的 `TEST_SUITE_*` 宏在**编译期**选择要跑的套件。
-选中 `TEST_SUITE_GD5F` 时,`test_config.h` 会自动 `#define ENABLE_GD5F_TESTS` 及全部
-`ENABLE_GD5F_PHASE*_TESTS`,随后由 `gd5f_test_run()`(`test/gd5f_test_task.c`)按阶段串行执行。
+选中 `TEST_SUITE_GD5F` 时,`test_config.h` 会自动 `#define ENABLE_GD5F_TESTS` 及**只读阶段**宏
+(PHASE1/2/10),随后由 `gd5f_test_run()`(`test/gd5f_test_task.c`)按阶段串行执行。
-> `defaultTask` **不再承担任何测试**(仅空闲);所有测试都只在 `ch395fTestTask` 中运行。
+> **破坏性用例开关(2026-09-21 新增,默认关闭)**:`test_config.h` 的 `ENABLE_DESTRUCTIVE_TESTS`
+> 控制所有**会改变设备持久状态**的用例——写/擦 NAND、`nand_ftl_format`、FatFS 建文件写文件、
+> 向 BBT 持久化坏块标记(永久减少可用容量)。关闭时为**只读模式**,可随时在在用设备上跑;
+> 打开后才编译并运行 PHASE3~9,以及 PHASE2 的 TC-206/207、PHASE10 的 TC-1003/1004。
+> 依据:2026-09-21 实测一轮破坏性 GD5F 运行即消耗一个块(容量 95248 → 95200,TC-STO-04 恢复不回来),
+> 故破坏性用例只在**产品首次上电/产线首启**或明确需要时开启。
`test/test_config.h` 配置示例(三选一,互斥,多选触发 `#error`):
```c
// #define TEST_SUITE_CH395F
// #define TEST_SUITE_STORAGE
-#define TEST_SUITE_GD5F /* 选中后自动开启 gd5f 全部阶段宏 */
+#define TEST_SUITE_GD5F /* 选中后自带 gd5f 只读阶段宏 */
+// #define ENABLE_DESTRUCTIVE_TESTS /* 需写/擦/格式化/落盘坏块时才打开(默认关闭) */
```
执行顺序(由 `gd5f_test_run()` 按阶段串行,与函数依赖一致):
-`init/read_id/reset` → `BBT` → `页读写` → `块擦除` → `ECC` → `SPI 原语` → `DMA 边界` → `FTL` → `FatFS` → `边界条件`。
+
+- **只读(默认)**:`init/read_id/reset` → `BBT(读取与 RAM 内标记)` → `DMA 守卫读方向(TC-704)` → `边界条件(越界 / size=0)`
+- **破坏性(开关打开时插入其间)**:`页读写` → `块擦除` → `ECC` → `SPI 原语` → `DMA 边界(701~703、705)` → `FTL(含 format)` → `FatFS`
+
+开机横幅会明确打印本次模式:`mode: READ-ONLY (...)` 或 `mode: DESTRUCTIVE (...)`。
+
+> `defaultTask` **不再承担任何测试**(仅空闲);所有测试都只在 `ch395fTestTask` 中运行。
### 启用测试
-1. 在 `test/test_config.h` 取消注释所需的 `TEST_SUITE_*` 宏(如 `TEST_SUITE_GD5F`);
+1. 在 `test/test_config.h` 取消注释所需的 `TEST_SUITE_*` 宏(如 `TEST_SUITE_GD5F`),
+ 需要写/擦类用例时再打开 `ENABLE_DESTRUCTIVE_TESTS`;
2. 编译(`@build`)确认 0 错误 0 警告;
-3. 烧录后串口观察 `[NAND-TEST]` 日志与各 TC 的 `PASS/FAIL`,结尾打印
+3. 烧录后串口观察 `mode: ...` 横幅、`[NAND-TEST]` 日志与各 TC 的 `PASS/FAIL`,结尾打印
`=== GD5F2GQ5UE Tests: X/Y PASSED ===`;
4. 不选任何 `TEST_SUITE_*` 时为正常产品构建(`ch395fTestTask` 空闲,不跑测试)。
@@ -468,13 +481,13 @@ ECC 状态(`ECCS1:ECCS0`):`00`=无错;`01/10/11`=纠正 1/2/3 bit;`10`
| **通过标准** | 数据一致 |
| **覆盖原则** | DMA 大数据路径 |
-### TC-GD5F-704: DMA 可达性守卫负路径(读方向)
+### TC-GD5F-704: DMA 可达性守卫负路径(读方向,只读,两种模式都跑)
| 字段 | 值 |
|------|-----|
| **ID** | TC-GD5F-704 |
| **优先级** | P0 |
-| **类型** | 边界/健壮性测试 |
+| **类型** | 边界/健壮性测试(**只读**,不受 `ENABLE_DESTRUCTIVE_TESTS` 门控) |
| **标题** | 目的缓冲位于 CCM(不可达区)时守卫应拒绝 DMA 并回落轮询,数据仍正确 |
| **前置条件** | FreeRTOS 堆驻留 CCM(scatter `RW_CCM`);探针缓冲由 `pvPortMalloc(2048)` 取得(堆不在 CCM 时用例跳过) |
| **测试步骤** | 1. 先用 SRAM 静态缓冲参考读同页
2. 探针缓冲预填参考值的反码
3. `gd5f2gq5ue_read()` 整页读入 CCM 探针缓冲
4. 与参考缓冲逐字节比对 |
@@ -496,7 +509,8 @@ ECC 状态(`ECCS1:ECCS0`):`00`=无错;`01/10/11`=纠正 1/2/3 bit;`10`
| **判定依据** | 同 TC-GD5F-704(写方向 DMA 读 CCM 同样不落地) |
| **覆盖原则** | DMA 可达性守卫(写方向);堆缓冲禁入 DMA 的运行时强制(内存分配策略 R3) |
-> ⚠ **卷破坏警告**:阶段 8 会调用 `nand_ftl_format()` 清空 dhara map,阶段 9 会写入文件——
+> ⚠ **卷破坏警告(仅 `ENABLE_DESTRUCTIVE_TESTS` 打开时)**:阶段 8 会调用 `nand_ftl_format()`
+> 清空 dhara map,阶段 9 会写入文件——
> 整轮 GD5F 套件(含 Phase 8/9)运行后卷数据即失效,且大写入可能使 dhara 把若干块记为
> 运行期坏块(2026-09-21 实测:一次整轮运行容量 95248 → 95200,日志见
> `[ERR][NAND_FTL] program verify: data mismatch, blk 305`,该块由 dhara 自行隔离,测试仍 68/68 通过)。
@@ -732,7 +746,17 @@ ECC 状态(`ECCS1:ECCS0`):`00`=无错;`01/10/11`=纠正 1/2/3 bit;`10`
| TC-STO-03 | 坏块注入下存储写入 | P1 | PASS |
| TC-STO-04 | BBT 重建 + 重新格式化(恢复) | P1 | 受 `ENABLE_STORAGE_RECOVERY_TESTS` 控制(默认关) |
-> 当前实测:`TEST_SUITE_GD5F` 构建一次运行全部阶段,结尾输出 `=== GD5F2GQ5UE Tests: 68/68 PASSED (failed=0) ===`(68 为各 TC 内部断言总数,含 TC-GD5F-205~207 BBT 持久化用例、TC-GD5F-704/705 DMA 守卫负路径;TC 覆盖见上表)。`TEST_SUITE_STORAGE` 套件结尾输出 `=== Storage Tests: 11/11 PASSED (failed=0) ===`(TC-STO-01/02/03 各含若干内部断言,且 TC-STO-01 依赖的 `s_cached_lpn` 哨兵 bug 已修复)。两套件均 0 错误 0 警告通过 MDK 编译。
+> 当前实测(2026-09-21,`ENABLE_DESTRUCTIVE_TESTS` 关闭 / 打开两种配置):
+> 只读模式 `=== GD5F2GQ5UE Tests: 16/16 PASSED (failed=0) ===`(PHASE1/2/7 的 TC-704/10,无任何写操作,
+> 卷内容与容量均不变);破坏性模式 `=== GD5F2GQ5UE Tests: 68/68 PASSED (failed=0) ===`(全部阶段)。
+> `TEST_SUITE_STORAGE` 只读模式实测 `=== Storage Tests: 3/3 PASSED (failed=0) ===`(mount + TC-STO-05,
+> 卷内容与容量不变);破坏性模式为全量集成用例(2026-09-20 实测 **20/20 PASSED**,含 TC-STO-01/02/03/05/06)。
+> 各套件在两种模式下均 0 错误 0 警告通过 MDK 编译。
+>
+> **破坏性用例清单**(仅在 `ENABLE_DESTRUCTIVE_TESTS` 打开时编译运行):
+> GD5F:TC-GD5F-206/207、301~305、401~404、501~502、601~605、701~703、705、801~802、901、1003~1004
+> (TC-GD5F-704 为**只读**,两次运行都跑);存储:TC-STO-01、02、03、06 与内建文件系统写读/
+> 性能压测(TC-STO-05 为只读,始终运行);RTC:TC-RTC-002(写时间,跑完复原)、014(写 SRAM)、015(写报警)。
---
diff --git a/docs/测试说明/SD2506_Test_Guide.md b/docs/测试说明/SD2506_Test_Guide.md
index 589890bb..9b8f5e1d 100644
--- a/docs/测试说明/SD2506_Test_Guide.md
+++ b/docs/测试说明/SD2506_Test_Guide.md
@@ -22,8 +22,15 @@
//#define TEST_SUITE_STORAGE
//#define TEST_SUITE_GD5F
#define TEST_SUITE_RTC /* 启用 RTC 测试套件 */
+//#define ENABLE_DESTRUCTIVE_TESTS /* 需跑改设备状态的用例时才打开(默认关闭) */
```
+> **破坏性用例开关(2026-09-21 新增,默认关闭)**:本套件有 3 处会**改变设备状态**——
+> TC-RTC-002(写 RTC 时间,套件结束会复原)、TC-RTC-014(写芯片 SRAM)、TC-RTC-015(写报警寄存器)。
+> 关闭 `ENABLE_DESTRUCTIVE_TESTS` 时这三条跳过(开机打印 `mode: READ-ONLY` 与 `skipped` 提示),
+> 只跑只读用例:init/读时间、BCD 互转、温度、电池电压、ID、走时校验;
+> 打开后(`mode: DESTRUCTIVE`)全跑并在结尾复原原始时间——**中途断电可能把板子时间留在测试值**。
+
`test/sd2506_test.h` 中控制各阶段(默认全开):
```c
diff --git a/docs/测试说明/自动化测试流程.md b/docs/测试说明/自动化测试流程.md
index c902609d..a5d0c1a6 100644
--- a/docs/测试说明/自动化测试流程.md
+++ b/docs/测试说明/自动化测试流程.md
@@ -78,10 +78,13 @@
`-Required "App init OK;Network Init;net init: OK;PHY: 100M FULL"`。
- **测试套件构建**:`test/test_config.h` 开启某个 `TEST_SUITE_*` 后,把套件打印的
`=== ... Test:` 标题作为 Required 标记,配合 `test/` 下各用例输出判断。
+ **破坏性用例默认不跑**:`test_config.h` 的 `ENABLE_DESTRUCTIVE_TESTS`(默认关闭)控制写/擦 NAND、
+ 格式化、坏块注入与 FatFS 写类用例;关闭时套件为只读模式(开机打印 `mode: READ-ONLY`),
+ 可随时在在用设备上执行且不改动卷内容。首次上电/产线验证时才打开(打印 `mode: DESTRUCTIVE`)。
**注意**:套件内**预期**的错误日志会打印 `[ERR]`(GD5F 的 `TC-GD5F-704/705` DMA 守卫
负路径与 FTL 运行期坏块、FatFs 非法名校验等),与默认 Forbidden 冲突,须显式放宽:
`--forbidden "FAIL;HardFault"`(`[FAIL]` 仍会拦截真实断言失败);
- 例:GD5F 套件 `--required "=== GD5F2GQ5UE Tests: 68/68 PASSED"`。
+ 例:GD5F 只读 `--required "=== GD5F2GQ5UE Tests: 16/16 PASSED"`,破坏性 `68/68`。
- **长时间采集**:daemon 全量持续写入 `state/serial.log`(20MB 上限自动截头保尾 10MB),直接读文件即可;或用 `log --tail 0 --grep <标记>` 过滤。
## 变更记录
diff --git a/test/gd5f_test_task.c b/test/gd5f_test_task.c
index 2bc3b699..83f9d99a 100644
--- a/test/gd5f_test_task.c
+++ b/test/gd5f_test_task.c
@@ -170,10 +170,6 @@ static void gd5f_phase2(void)
{
uint32_t usable = gd5f_get_usable_blocks();
uint32_t X = gd5f_find_run(1); /* 管理区内的一个好块 */
- uint8_t flash_bbt[GD5F_TOTAL_BLOCKS / 8];
- uint32_t ver = 0;
- uint32_t cnt_a, cnt_b;
- int32_t marked;
/* TC-GD5F-205: 保留块池已预留,dhara 可用块数 < 总块数;
* 且测试用块 X 落在管理区内(不会擦到 BBT 存储区) */
@@ -182,36 +178,48 @@ static void gd5f_phase2(void)
GD5F_TEST_CHECK(X < usable,
"TC-GD5F-205 test block X=%lu < usable=%lu", X, usable);
- /* TC-GD5F-206: 标记坏块后 RAM 生效,且已落盘(闪存副本含该位、版本>=1) */
- gd5f2gq5ue_mark_block_bad(X);
- marked = gd5f2gq5ue_is_block_bad(X);
- GD5F_TEST_CHECK(marked == 1, "TC-GD5F-206 RAM mark X=%lu -> %d", X, marked);
- if (GD5F_OK == gd5f_bbt_dump_flash(flash_bbt, &ver)) {
- int32_t flash_bit = (flash_bbt[X >> 3] >> (X & 7)) & 1;
- GD5F_TEST_CHECK(flash_bit == 1 && ver >= 1,
- "TC-GD5F-206 flash persist X bit=%d ver=%lu", flash_bit, ver);
- } else {
- GD5F_TEST_CHECK(0, "TC-GD5F-206 dump_flash failed");
- }
+#ifdef ENABLE_DESTRUCTIVE_TESTS
+ {
+ uint8_t flash_bbt[GD5F_TOTAL_BLOCKS / 8];
+ uint32_t ver = 0;
+ uint32_t cnt_a, cnt_b;
+ int32_t marked;
- /* TC-GD5F-207: 清空 RAM 后从闪存重载,坏块记录被恢复(验证 load 链路) */
- cnt_a = gd5f_count_bad();
- gd5f2gq5ue_bbt_clear();
- gd5f_bbt_reload();
- marked = gd5f2gq5ue_is_block_bad(X);
- cnt_b = gd5f_count_bad();
- GD5F_TEST_CHECK(marked == 1 && cnt_b >= cnt_a,
- "TC-GD5F-207 reload X=%lu restored (cnt %lu->%lu)", X, cnt_a, cnt_b);
-
- /* 清理:还原出厂快照(RAM)并擦除保留池(闪存),设备回到干净态,
- * 保证测试可重复且不在 NAND 上留下伪坏块 */
- gd5f2gq5ue_bbt_clear();
- for (blk = 0; blk < GD5F_TOTAL_BLOCKS; blk++) {
- if ((orig_bad[blk >> 5] & (1U << (blk & 31))) != 0) {
- gd5f2gq5ue_mark_block_bad(blk);
+ /* TC-GD5F-206: 标记坏块后 RAM 生效,且已落盘(闪存副本含该位、版本>=1) */
+ gd5f2gq5ue_mark_block_bad(X);
+ marked = gd5f2gq5ue_is_block_bad(X);
+ GD5F_TEST_CHECK(marked == 1, "TC-GD5F-206 RAM mark X=%lu -> %d", X, marked);
+ if (GD5F_OK == gd5f_bbt_dump_flash(flash_bbt, &ver)) {
+ int32_t flash_bit = (flash_bbt[X >> 3] >> (X & 7)) & 1;
+ GD5F_TEST_CHECK(flash_bit == 1 && ver >= 1,
+ "TC-GD5F-206 flash persist X bit=%d ver=%lu", flash_bit, ver);
+ } else {
+ GD5F_TEST_CHECK(0, "TC-GD5F-206 dump_flash failed");
}
+
+ /* TC-GD5F-207: 清空 RAM 后从闪存重载,坏块记录被恢复(验证 load 链路) */
+ cnt_a = gd5f_count_bad();
+ gd5f2gq5ue_bbt_clear();
+ gd5f_bbt_reload();
+ marked = gd5f2gq5ue_is_block_bad(X);
+ cnt_b = gd5f_count_bad();
+ GD5F_TEST_CHECK(marked == 1 && cnt_b >= cnt_a,
+ "TC-GD5F-207 reload X=%lu restored (cnt %lu->%lu)", X, cnt_a, cnt_b);
+
+ /* 清理:还原出厂快照(RAM)并擦除保留池(闪存),设备回到干净态,
+ * 保证测试可重复且不在 NAND 上留下伪坏块 */
+ gd5f2gq5ue_bbt_clear();
+ for (blk = 0; blk < GD5F_TOTAL_BLOCKS; blk++) {
+ if ((orig_bad[blk >> 5] & (1U << (blk & 31))) != 0) {
+ gd5f2gq5ue_mark_block_bad(blk);
+ }
+ }
+ gd5f_bbt_wipe_pool();
}
- gd5f_bbt_wipe_pool();
+#else
+ DBG_INFO("TC-GD5F-206/207 skipped: destructive tests disabled "
+ "(BBT flash persist + pool wipe)");
+#endif
}
}
#endif /* ENABLE_GD5F_PHASE2_TESTS */
@@ -536,6 +544,7 @@ static void gd5f_phase7(void)
blk = gd5f_find_run(1);
base = (int32_t)blk * GD5F_BLOCK_SIZE;
+#ifdef ENABLE_DESTRUCTIVE_TESTS
/* TC-GD5F-701: 阈值下界(= 阈值,轮询路径) */
gd5f2gq5ue_erase(base, GD5F_BLOCK_SIZE);
for (i = 0; i < (int32_t)GD5F_DMA_THRESHOLD; i++) {
@@ -582,6 +591,9 @@ static void gd5f_phase7(void)
if (s_rbuf[i] != (uint8_t)(i & 0xFF)) { ok = 0; break; }
}
GD5F_TEST_CHECK(ok, "TC-GD5F-703 DMA whole page %s", ok ? "OK" : "MISMATCH");
+#else
+ DBG_INFO("TC-GD5F-701/702/703 skipped: destructive tests disabled (write/erase)");
+#endif
/* ================= DMA 可达性守卫(负路径) =================
* 探针缓冲取自 FreeRTOS 堆:堆驻留 CCM(0x10000000),DMA1/DMA2 不可达。
@@ -598,7 +610,7 @@ static void gd5f_phase7(void)
DBG_INFO("TC-GD5F-704/705 SKIP: heap buf not in CCM (p=0x%08lX)",
(unsigned long)p_addr);
} else {
- /* TC-GD5F-704: 读方向 —— 目的缓冲在 CCM,应回落轮询 */
+ /* TC-GD5F-704: 读方向 —— 目的缓冲在 CCM,应回落轮询(只读,日常回归亦运行) */
memset(s_rbuf, 0, GD5F_PAGE_SIZE);
(void)gd5f2gq5ue_read(base, s_rbuf, GD5F_PAGE_SIZE); /* 参考读(SRAM 静态) */
for (i = 0; i < GD5F_PAGE_SIZE; i++) {
@@ -610,7 +622,8 @@ static void gd5f_phase7(void)
GD5F_TEST_CHECK(ok, "TC-GD5F-704 CCM(heap) rx poll fallback %s",
ok ? "OK" : "MISMATCH");
- /* TC-GD5F-705: 写方向 —— 源缓冲在 CCM,应回落轮询 */
+#ifdef ENABLE_DESTRUCTIVE_TESTS
+ /* TC-GD5F-705: 写方向 —— 源缓冲在 CCM,应回落轮询(需擦写) */
gd5f2gq5ue_erase(base, GD5F_BLOCK_SIZE);
for (i = 0; i < GD5F_PAGE_SIZE; i++) {
p_heap[i] = (uint8_t)(i ^ 0x5A);
@@ -625,6 +638,9 @@ static void gd5f_phase7(void)
}
GD5F_TEST_CHECK(ok, "TC-GD5F-705 CCM(heap) tx poll fallback %s",
ok ? "OK" : "MISMATCH");
+#else
+ DBG_INFO("TC-GD5F-705 skipped: destructive tests disabled (write/erase)");
+#endif
}
vPortFree(p_heap);
}
@@ -709,7 +725,7 @@ static void gd5f_phase10(void)
{
uint32_t blk;
int32_t base;
- int32_t ret, i, ne, ok;
+ int32_t ret;
DBG_INFO("=== GD5F Phase 10: boundary conditions ===");
blk = gd5f_find_run(1);
@@ -729,35 +745,43 @@ static void gd5f_phase10(void)
ret = gd5f2gq5ue_write(base, s_wbuf, 0);
GD5F_TEST_CHECK(ret == GD5F_OK, "TC-GD5F-1002 write size=0 ret=%d", ret);
- /* TC-GD5F-1003: 写前未擦除 → 数据不可预期(NAND 不可原地覆盖)
- * 先擦除写入全 0x00,再不清擦写入全 0xFF(需要 0→1,物理不可行),
- * 读回应仍 != 0xFF。 */
- gd5f2gq5ue_erase(base, GD5F_BLOCK_SIZE);
- memset(s_wbuf, 0x00, GD5F_PAGE_SIZE);
- gd5f2gq5ue_write(base, s_wbuf, GD5F_PAGE_SIZE);
- memset(s_wbuf, 0xFF, GD5F_PAGE_SIZE);
- gd5f2gq5ue_write(base, s_wbuf, GD5F_PAGE_SIZE); /* 未擦除,0→1 不可能 */
- memset(s_rbuf, 0, GD5F_PAGE_SIZE);
- gd5f2gq5ue_read(base, s_rbuf, GD5F_PAGE_SIZE);
- ne = 0;
- for (i = 0; i < GD5F_PAGE_SIZE; i++) {
- if (s_rbuf[i] != 0xFF) ne++;
- }
- GD5F_TEST_CHECK(ne > 0,
- "TC-GD5F-1003 write-without-erase not applied (bytes!=0xFF: %d)", ne);
+#ifdef ENABLE_DESTRUCTIVE_TESTS
+ {
+ int32_t i, ne, ok;
- /* TC-GD5F-1004: 写保护解除验证(init 后 Protect=0x00,可正常编程) */
- gd5f2gq5ue_erase(base, GD5F_BLOCK_SIZE);
- memset(s_wbuf, 0x5A, GD5F_PAGE_SIZE);
- ret = gd5f2gq5ue_write(base, s_wbuf, GD5F_PAGE_SIZE);
- memset(s_rbuf, 0, GD5F_PAGE_SIZE);
- gd5f2gq5ue_read(base, s_rbuf, GD5F_PAGE_SIZE);
- ok = 1;
- for (i = 0; i < GD5F_PAGE_SIZE; i++) {
- if (s_rbuf[i] != 0x5A) { ok = 0; break; }
+ /* TC-GD5F-1003: 写前未擦除 → 数据不可预期(NAND 不可原地覆盖)
+ * 先擦除写入全 0x00,再不清擦写入全 0xFF(需要 0→1,物理不可行),
+ * 读回应仍 != 0xFF。 */
+ gd5f2gq5ue_erase(base, GD5F_BLOCK_SIZE);
+ memset(s_wbuf, 0x00, GD5F_PAGE_SIZE);
+ gd5f2gq5ue_write(base, s_wbuf, GD5F_PAGE_SIZE);
+ memset(s_wbuf, 0xFF, GD5F_PAGE_SIZE);
+ gd5f2gq5ue_write(base, s_wbuf, GD5F_PAGE_SIZE); /* 未擦除,0→1 不可能 */
+ memset(s_rbuf, 0, GD5F_PAGE_SIZE);
+ gd5f2gq5ue_read(base, s_rbuf, GD5F_PAGE_SIZE);
+ ne = 0;
+ for (i = 0; i < GD5F_PAGE_SIZE; i++) {
+ if (s_rbuf[i] != 0xFF) ne++;
+ }
+ GD5F_TEST_CHECK(ne > 0,
+ "TC-GD5F-1003 write-without-erase not applied (bytes!=0xFF: %d)", ne);
+
+ /* TC-GD5F-1004: 写保护解除验证(init 后 Protect=0x00,可正常编程) */
+ gd5f2gq5ue_erase(base, GD5F_BLOCK_SIZE);
+ memset(s_wbuf, 0x5A, GD5F_PAGE_SIZE);
+ ret = gd5f2gq5ue_write(base, s_wbuf, GD5F_PAGE_SIZE);
+ memset(s_rbuf, 0, GD5F_PAGE_SIZE);
+ gd5f2gq5ue_read(base, s_rbuf, GD5F_PAGE_SIZE);
+ ok = 1;
+ for (i = 0; i < GD5F_PAGE_SIZE; i++) {
+ if (s_rbuf[i] != 0x5A) { ok = 0; break; }
+ }
+ GD5F_TEST_CHECK(ret == GD5F_OK && ok,
+ "TC-GD5F-1004 write unprotected (ret=%d) %s", ret, ok ? "OK" : "FAIL");
}
- GD5F_TEST_CHECK(ret == GD5F_OK && ok,
- "TC-GD5F-1004 write unprotected (ret=%d) %s", ret, ok ? "OK" : "FAIL");
+#else
+ DBG_INFO("TC-GD5F-1003/1004 skipped: destructive tests disabled (write/erase)");
+#endif
}
#endif /* ENABLE_GD5F_PHASE10_TESTS */
@@ -768,6 +792,12 @@ void gd5f_test_run(void)
{
memset(&g_gd5f_test_stats, 0, sizeof(g_gd5f_test_stats));
DBG_INFO("########## GD5F2GQ5UE Test Start ##########");
+#ifdef ENABLE_DESTRUCTIVE_TESTS
+ DBG_INFO("mode: DESTRUCTIVE (write/erase/format/BBT-persist enabled)");
+#else
+ DBG_INFO("mode: READ-ONLY (destructive cases skipped; set "
+ "ENABLE_DESTRUCTIVE_TESTS in test_config.h to run them)");
+#endif
#ifdef ENABLE_GD5F_PHASE1_TESTS
gd5f_phase1();
diff --git a/test/sd2506_test_task.c b/test/sd2506_test_task.c
index 583d5d1c..c95eb4b3 100644
--- a/test/sd2506_test_task.c
+++ b/test/sd2506_test_task.c
@@ -49,28 +49,36 @@ static void sd2506_phase_basic(void)
(unsigned int)r.second, (unsigned int)r.week);
}
- /* TC-RTC-002: 写固定时间,立即回读比对。
+ /* TC-RTC-002: 写固定时间,立即回读比对(破坏性:改设备 RTC 时间,套件结束会复原)。
* 若 FAIL -> 先查 sd2506.h 寄存器宏后缀是否为 H(曾因 0x0FH 被 KEIL 编译为 0
* 导致 SD2506_REG_CTR1 指向秒寄存器、WRTC 写错地址)。详见 docs/SD2506_Trap_Records.md。 */
- sd2506_time_t t;
- t.year = 2026; t.month = 8; t.day = 28; t.hour = 12; t.minute = 0; t.second = 0; t.week = 4;
- ret = sd2506_set_time(&t);
- SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_set_time() ret=%d (expect 0)", ret);
+#ifdef ENABLE_DESTRUCTIVE_TESTS
+ {
+ sd2506_time_t t;
+ int match;
- ret = sd2506_get_time(&r);
- SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_get_time() after set ret=%d (expect 0)", ret);
- if (ret == SD2506_OK) {
- DBG_INFO(" set : %04u-%02u-%02u %02u:%02u:%02u",
- (unsigned int)t.year, (unsigned int)t.month, (unsigned int)t.day,
- (unsigned int)t.hour, (unsigned int)t.minute, (unsigned int)t.second);
- DBG_INFO(" readback: %04u-%02u-%02u %02u:%02u:%02u",
- (unsigned int)r.year, (unsigned int)r.month, (unsigned int)r.day,
- (unsigned int)r.hour, (unsigned int)r.minute, (unsigned int)r.second);
+ t.year = 2026; t.month = 8; t.day = 28; t.hour = 12; t.minute = 0; t.second = 0; t.week = 4;
+ ret = sd2506_set_time(&t);
+ SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_set_time() ret=%d (expect 0)", ret);
+
+ ret = sd2506_get_time(&r);
+ SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_get_time() after set ret=%d (expect 0)", ret);
+ if (ret == SD2506_OK) {
+ DBG_INFO(" set : %04u-%02u-%02u %02u:%02u:%02u",
+ (unsigned int)t.year, (unsigned int)t.month, (unsigned int)t.day,
+ (unsigned int)t.hour, (unsigned int)t.minute, (unsigned int)t.second);
+ DBG_INFO(" readback: %04u-%02u-%02u %02u:%02u:%02u",
+ (unsigned int)r.year, (unsigned int)r.month, (unsigned int)r.day,
+ (unsigned int)r.hour, (unsigned int)r.minute, (unsigned int)r.second);
+ }
+ match = (r.year == t.year && r.month == t.month && r.day == t.day &&
+ r.hour == t.hour && r.minute == t.minute &&
+ (r.second == t.second || r.second == ((t.second + 1U) % 60U)));
+ SD2506_TEST_CHECK(match, "set==readback (in-session write takes effect)");
}
- int match = (r.year == t.year && r.month == t.month && r.day == t.day &&
- r.hour == t.hour && r.minute == t.minute &&
- (r.second == t.second || r.second == ((t.second + 1U) % 60U)));
- SD2506_TEST_CHECK(match, "set==readback (in-session write takes effect)");
+#else
+ DBG_INFO("TC-RTC-002 skipped: destructive tests disabled (RTC time write)");
+#endif
/* TC-RTC-003: 走时。写后等 3s,应前进约 3 秒 */
if (ret == SD2506_OK) {
@@ -147,25 +155,37 @@ static void sd2506_phase_api(void)
id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7]);
}
- /* TC-RTC-014: 用户 SRAM 读写回环(addr=0, len=8) */
- uint8_t wbuf[8] = {0xA1U, 0xB2U, 0xC3U, 0xD4U, 0xE5U, 0xF6U, 0x07U, 0x18U};
- uint8_t rbuf[8] = {0};
- ret = sd2506_write_sram(0U, wbuf, 8U);
- SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_write_sram() ret=%d (expect 0)", ret);
- if (ret == SD2506_OK) {
- ret = sd2506_read_sram(0U, rbuf, 8U);
- SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_read_sram() ret=%d (expect 0)", ret);
- int sram_ok = (memcmp(wbuf, rbuf, 8U) == 0);
- SD2506_TEST_CHECK(sram_ok, "SRAM write==readback");
+ /* TC-RTC-014: 用户 SRAM 读写回环(addr=0, len=8;破坏性:写芯片 SRAM) */
+#ifdef ENABLE_DESTRUCTIVE_TESTS
+ {
+ uint8_t wbuf[8] = {0xA1U, 0xB2U, 0xC3U, 0xD4U, 0xE5U, 0xF6U, 0x07U, 0x18U};
+ uint8_t rbuf[8] = {0};
+ ret = sd2506_write_sram(0U, wbuf, 8U);
+ SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_write_sram() ret=%d (expect 0)", ret);
+ if (ret == SD2506_OK) {
+ ret = sd2506_read_sram(0U, rbuf, 8U);
+ SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_read_sram() ret=%d (expect 0)", ret);
+ int sram_ok = (memcmp(wbuf, rbuf, 8U) == 0);
+ SD2506_TEST_CHECK(sram_ok, "SRAM write==readback");
+ }
}
+#else
+ DBG_INFO("TC-RTC-014 skipped: destructive tests disabled (RTC SRAM write)");
+#endif
- /* TC-RTC-015: 报警设置 + 清除(验证 API 通路,不影响时间寄存器) */
- sd2506_time_t at;
- at.year = 2026; at.month = 8; at.day = 28; at.hour = 12; at.minute = 0; at.second = 30; at.week = 4;
- ret = sd2506_set_alarm(&at, (uint8_t)(SD2506_AL_EN_EAS | SD2506_AL_EN_EAMN));
- SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_set_alarm() ret=%d (expect 0)", ret);
- ret = sd2506_clear_alarm();
- SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_clear_alarm() ret=%d (expect 0)", ret);
+ /* TC-RTC-015: 报警设置 + 清除(破坏性:写报警寄存器;验证 API 通路,不影响时间寄存器) */
+#ifdef ENABLE_DESTRUCTIVE_TESTS
+ {
+ sd2506_time_t at;
+ at.year = 2026; at.month = 8; at.day = 28; at.hour = 12; at.minute = 0; at.second = 30; at.week = 4;
+ ret = sd2506_set_alarm(&at, (uint8_t)(SD2506_AL_EN_EAS | SD2506_AL_EN_EAMN));
+ SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_set_alarm() ret=%d (expect 0)", ret);
+ ret = sd2506_clear_alarm();
+ SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_clear_alarm() ret=%d (expect 0)", ret);
+ }
+#else
+ DBG_INFO("TC-RTC-015 skipped: destructive tests disabled (RTC alarm write)");
+#endif
SD2506_TEST_REPORT("Phase 2 API");
}
@@ -175,6 +195,12 @@ static void sd2506_phase_api(void)
void sd2506_test_run(void)
{
DBG_INFO("SD2506 test suite started");
+#ifdef ENABLE_DESTRUCTIVE_TESTS
+ DBG_INFO("mode: DESTRUCTIVE (RTC time / SRAM / alarm writes enabled)");
+#else
+ DBG_INFO("mode: READ-ONLY (destructive cases skipped; set "
+ "ENABLE_DESTRUCTIVE_TESTS in test_config.h to run them)");
+#endif
int ret = sd2506_init();
SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_init() ret=%d (expect 0)", ret);
@@ -188,12 +214,14 @@ void sd2506_test_run(void)
(unsigned int)((c1 >> 7) & 1U));
}
+#ifdef ENABLE_DESTRUCTIVE_TESTS
/* 记录原始时间,测试结束恢复,避免把板子时间留在测试值 */
sd2506_time_t orig;
int have_orig = (sd2506_get_time(&orig) == SD2506_OK);
if (!have_orig) {
DBG_INFO(" WARN: cannot read original time; restore step will be skipped");
}
+#endif
memset(&g_sd2506_test_stats, 0, sizeof(g_sd2506_test_stats));
@@ -205,11 +233,13 @@ void sd2506_test_run(void)
sd2506_phase_api();
#endif
+#ifdef ENABLE_DESTRUCTIVE_TESTS
/* 恢复原始时间(用公共 API,写保护逻辑已由 sd2506.c 正确处理) */
if (have_orig) {
int r = sd2506_set_time(&orig);
DBG_INFO(" restored original time (ret=%d)", r);
}
+#endif
DBG_INFO("SD2506 test suite done");
for (;;) {
diff --git a/test/storage_test_task.c b/test/storage_test_task.c
index 652730fa..131ec9e0 100644
--- a/test/storage_test_task.c
+++ b/test/storage_test_task.c
@@ -12,6 +12,7 @@
#define DBG_TAG "[STORAGE-TEST]"
#include "dbg_log.h"
+#include "test_config.h" /* 破坏性用例开关 ENABLE_DESTRUCTIVE_TESTS 的唯一来源 */
#include "storage_test.h"
#include "nand_ftl.h"
#include "diskio.h"
@@ -26,6 +27,7 @@ storage_test_stats_t g_storage_stats;
static FATFS fs;
static FIL fil;
+#ifdef ENABLE_DESTRUCTIVE_TESTS
/* 性能缓冲 2048B = 恰好一个 NAND 页(RAM 预算:4096 时套件链接溢出 0x604,
* L6406E 实测;2048 分块仍覆盖整页写路径,128KB 总量不变) */
static uint8_t s_perf_buf[2048];
@@ -252,6 +254,8 @@ static void storage_test_badblock(void)
* 后跑一次套件,即可清除运行时注入坏块 + 清 dhara map 回出厂态) */
}
+#endif /* ENABLE_DESTRUCTIVE_TESTS: s_perf_buf + TC-STO-01/02/03(写、擦、坏块注入) */
+
/* TC-STO-05: 文件名/路径合法性校验(守护 ".fschk 无限重格式化" 真根因)
* 本移植 FF_USE_LFN=0,只支持严格 8.3 短名(主名≤8、扩展≤3、不以点开头)。
* 非法名字 f_open 必返 FR_INVALID_NAME(纯 RAM 名字判定、不碰盘);合法但
@@ -324,6 +328,7 @@ static void storage_test_name_validation(void)
STORAGE_TEST_CHECK(bok, "TC-STO-05 invalid names rejected (FR_INVALID_NAME)");
}
+#ifdef ENABLE_DESTRUCTIVE_TESTS
/* TC-STO-06: 打开模式与写回字节数语义(守护 FA_OPEN_APPEND / 短写陷阱)
* 本移植 FA_OPEN_APPEND 不含 FA_WRITE → 不带写位打开 f_write 必返 FR_DENIED;
* 追加写必须 FA_WRITE|FA_OPEN_APPEND。另 f_write 簇耗尽返 FR_OK+短写(无错误
@@ -381,6 +386,7 @@ static void storage_test_open_modes(void)
(void)f_unlink("mode.tmp");
}
+#endif /* ENABLE_DESTRUCTIVE_TESTS: TC-STO-06 */
#endif /* !ENABLE_STORAGE_RECOVERY_TESTS: 共享缓冲 + 正常测试函数 */
#ifdef ENABLE_STORAGE_RECOVERY_TESTS
@@ -391,45 +397,65 @@ static void storage_test_recover(void);
/* 正常存储测试套件主体(恢复模式见文件末尾 storage_test_run 分发) */
static void storage_test_run_normal(void)
{
- static uint8_t work[FF_MAX_SS];
- char read_buf[64];
- const char *test_str = "Hello FatFS + dhara FTL!";
+ FRESULT res;
memset(&g_storage_stats, 0, sizeof(g_storage_stats));
- /* ==================== FTL + FatFS 文件系统测试 ==================== */
+#ifdef ENABLE_DESTRUCTIVE_TESTS
+ DBG_INFO("mode: DESTRUCTIVE (write/erase/format/badblock-inject enabled)");
+#else
+ DBG_INFO("mode: READ-ONLY (destructive cases skipped; set "
+ "ENABLE_DESTRUCTIVE_TESTS in test_config.h to run them)");
+#endif
- FRESULT res = f_mount(&fs, "", 1);
+ /* ==================== 挂载 ==================== */
+
+ res = f_mount(&fs, "", 1);
+#ifdef ENABLE_DESTRUCTIVE_TESTS
if (res == FR_NO_FILESYSTEM) {
+ static uint8_t work[FF_MAX_SS];
MKFS_PARM opts = { FM_FAT32, 0, 0, 0, 0 };
res = f_mkfs("", &opts, work, sizeof(work));
STORAGE_TEST_CHECK(res == FR_OK, "f_mkfs");
res = f_mount(&fs, "", 1);
}
+#else
+ if (res == FR_NO_FILESYSTEM) {
+ DBG_INFO("read-only mode: no filesystem on volume, skip write cases "
+ "(mkfs self-heal disabled)");
+ }
+#endif
STORAGE_TEST_CHECK(res == FR_OK, "f_mount");
- if (res == FR_OK) {
- UINT bw, br;
+#ifdef ENABLE_DESTRUCTIVE_TESTS
+ /* ==================== FTL + FatFS 文件系统测试 ==================== */
+ {
+ char read_buf[64];
+ const char *test_str = "Hello FatFS + dhara FTL!";
- res = f_open(&fil, "test.txt", FA_CREATE_ALWAYS | FA_WRITE);
- STORAGE_TEST_CHECK(res == FR_OK, "f_open(write)");
if (res == FR_OK) {
- f_write(&fil, test_str, (UINT)strlen(test_str), &bw);
- STORAGE_TEST_CHECK(bw == (UINT)strlen(test_str), "f_write (%u bytes)", bw);
- f_close(&fil);
+ UINT bw, br;
- res = f_open(&fil, "test.txt", FA_READ);
- STORAGE_TEST_CHECK(res == FR_OK, "f_open(read)");
+ res = f_open(&fil, "test.txt", FA_CREATE_ALWAYS | FA_WRITE);
+ STORAGE_TEST_CHECK(res == FR_OK, "f_open(write)");
if (res == FR_OK) {
- f_read(&fil, read_buf, (UINT)(sizeof(read_buf) - 1), &br);
+ f_write(&fil, test_str, (UINT)strlen(test_str), &bw);
+ STORAGE_TEST_CHECK(bw == (UINT)strlen(test_str), "f_write (%u bytes)", bw);
f_close(&fil);
- read_buf[br] = '\0';
- STORAGE_TEST_CHECK(br == (UINT)strlen(test_str) &&
- memcmp(test_str, read_buf, br) == 0,
- "data verify \"%s\"", read_buf);
+
+ res = f_open(&fil, "test.txt", FA_READ);
+ STORAGE_TEST_CHECK(res == FR_OK, "f_open(read)");
+ if (res == FR_OK) {
+ f_read(&fil, read_buf, (UINT)(sizeof(read_buf) - 1), &br);
+ f_close(&fil);
+ read_buf[br] = '\0';
+ STORAGE_TEST_CHECK(br == (UINT)strlen(test_str) &&
+ memcmp(test_str, read_buf, br) == 0,
+ "data verify \"%s\"", read_buf);
+ }
}
+ f_unlink("test.txt");
}
- f_unlink("test.txt");
}
/* ==================== FTL + FatFS 性能测试 ==================== */
@@ -478,19 +504,19 @@ static void storage_test_run_normal(void)
total_kb, tr, (total_kb * 1000) / tr);
}
}
+#endif /* ENABLE_DESTRUCTIVE_TESTS: 文件系统写读 + 性能压测 */
- /* ==================== 存储集成测试(TC-STO-05~06 契约 + 01~03 集成) ========== */
+ /* ==================== 存储集成测试(TC-STO-05 契约 + 01~03 集成) ========== */
- /* 纯语义契约测试(非破坏性,先跑):名字合法性 + 打开模式 */
+ /* 纯语义契约测试(非破坏性):文件名合法性 */
storage_test_name_validation();
- storage_test_open_modes();
+#ifdef ENABLE_DESTRUCTIVE_TESTS
+ /* 破坏性用例:打开模式契约 + 掉电恢复 / 大文件 / 坏块注入集成 */
+ storage_test_open_modes();
storage_test_powerloss();
storage_test_largefile();
storage_test_badblock();
-
-#ifdef ENABLE_STORAGE_RECOVERY_TESTS
- storage_test_recover();
#endif
/* ==================== 汇总报告 ==================== */
diff --git a/test/test_config.h b/test/test_config.h
index cec7a9a1..3202eb13 100644
--- a/test/test_config.h
+++ b/test/test_config.h
@@ -28,6 +28,16 @@ extern "C" {
//#define TEST_SUITE_GD5F
//#define TEST_SUITE_RTC
+/* === 破坏性测试总开关(默认关闭)===
+ * 破坏性用例会改变设备的**持久状态**,跑一次即有代价(2026-09-21 实测:GD5F 整轮
+ * 破坏性用例跑一次即消耗一个 NAND 块,容量 95248 → 95200,且 TC-STO-04 恢复不回来):
+ * - 向 BBT 持久化坏块标记 / 注入坏块(永久减少可用容量)
+ * - 写、擦 NAND 块(覆盖卷数据);格式化卷(nand_ftl_format / f_mkfs)
+ * - FatFS 建文件、写文件、性能压测
+ * 默认关闭:套件只跑**只读与参数校验**类用例,可随时在在用设备上执行。
+ * 仅在**产品首次上电/产线首启**或明确需要时打开;打开跑完建议做一次恢复。 */
+//#define ENABLE_DESTRUCTIVE_TESTS
+
#if defined(TEST_SUITE_CH395F) + defined(TEST_SUITE_STORAGE) + defined(TEST_SUITE_GD5F) + defined(TEST_SUITE_RTC) > 1
#error "测试套件互斥:一次构建只能启用一个 TEST_SUITE_*"
#endif
@@ -47,18 +57,26 @@ extern "C" {
#endif
#ifdef TEST_SUITE_GD5F
-/* 选中 GD5F 套件时,自动开启 gd5f 测试总开关与全部阶段 */
+/* 选中 GD5F 套件时,自动开启 gd5f 测试总开关;
+ * 只读阶段无条件启用,破坏性阶段(写/擦/格式化/BBT 落盘)受 ENABLE_DESTRUCTIVE_TESTS 门控 */
#define ENABLE_GD5F_TESTS
-#define ENABLE_GD5F_PHASE1_TESTS
-#define ENABLE_GD5F_PHASE2_TESTS
-#define ENABLE_GD5F_PHASE3_TESTS
-#define ENABLE_GD5F_PHASE4_TESTS
-#define ENABLE_GD5F_PHASE5_TESTS
-#define ENABLE_GD5F_PHASE6_TESTS
-#define ENABLE_GD5F_PHASE7_TESTS
-#define ENABLE_GD5F_PHASE8_TESTS
-#define ENABLE_GD5F_PHASE9_TESTS
-#define ENABLE_GD5F_PHASE10_TESTS
+
+/* --- 只读阶段:随时可跑,不改设备状态 --- */
+#define ENABLE_GD5F_PHASE1_TESTS /* init / read_id / reset */
+#define ENABLE_GD5F_PHASE2_TESTS /* BBT 读取与 RAM 内标记(206/207 落盘另受门控) */
+#define ENABLE_GD5F_PHASE7_TESTS /* DMA 边界与守卫(701~703/705 写擦受门控;704 只读,日常回归亦跑) */
+#define ENABLE_GD5F_PHASE10_TESTS /* 越界与 size=0 参数校验(1003/1004 写入另受门控) */
+
+#ifdef ENABLE_DESTRUCTIVE_TESTS
+/* --- 破坏性阶段:写/擦页与块、格式化、FatFS 写入 --- */
+#define ENABLE_GD5F_PHASE3_TESTS /* 页/跨页/多页读写 */
+#define ENABLE_GD5F_PHASE4_TESTS /* 块擦除 */
+#define ENABLE_GD5F_PHASE5_TESTS /* ECC 写读 */
+#define ENABLE_GD5F_PHASE6_TESTS /* SPI 原语(块擦除 + 编程) */
+#define ENABLE_GD5F_PHASE8_TESTS /* FTL init + nand_ftl_format(格式化清卷) */
+#define ENABLE_GD5F_PHASE9_TESTS /* FatFS 挂载 + 写文件 */
+#endif
+
#include "gd5f_test.h"
#define TEST_SUITE_NAME "GD5F"
#endif