测试:新增 sys_clock 主机侧单测并修复 Zeller 星期缺陷

This commit is contained in:
2026-10-02 11:40:35 +08:00
parent 9f52731ed8
commit fac9d1c409
24 changed files with 5584 additions and 3792 deletions
+8
View File
@@ -93,6 +93,13 @@ docs/
| [`测试文档模板规范.md`](测试说明/测试文档模板规范.md) | 测试文档模板与命名规则(TM / TC / DP) |
| [`链路延迟测试说明.md`](测试说明/链路延迟测试说明.md) | 网络链路时延分段定义、判定基线、执行流程 |
**主机侧单元测试(L0.5)**
| 文档 | 内容 |
|---|---|
| [`看门狗单元测试说明.md`](测试说明/看门狗单元测试说明.md) | Task Watchdog 判定状态机(位图 / 掩码 / 节流 / tick 回绕),39 用例 |
| [`系统时钟单元测试说明.md`](测试说明/系统时钟单元测试说明.md) | sys_clock 时间换算 / 星期 Zeller / 30 天惰性再基准化 / RTC 故障降级,100 断言点 |
**模块测试指南**(动手写测试前先读)
| 文档 | 内容 |
@@ -177,6 +184,7 @@ docs/
| [`项目审查与优化建议报告.md`](审查记录/项目审查与优化建议报告.md) | 项目全景审查:文档/代码/架构/可靠性问题清单(D-1~D-7、N-xx、Px-xx) |
| [`项目审计改进清单.md`](审查记录/项目审计改进清单.md) | 审计待办清单(P0~P3,带状态与日期) |
| [`下一步任务规划.md`](审查记录/下一步任务规划.md) | **项目状态盘点 + 下一步任务排序(阶段 A/B/C/D)**——每轮迭代后更新盘点 |
| [`测试与架构改进建议.md`](审查记录/测试与架构改进建议.md) | **测试充分性 + 架构改进专项复审**(2026-10-02,纯建议不改码):L0.5 覆盖率缺口、孤儿源文件、错误通道不对称等 S1~S14 实施建议 |
### 8.2 环境与部署
+37 -8
View File
@@ -26,12 +26,10 @@ digraph refgraph_full {
"App/task/rs485_task.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/task/ui_task.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/task/ui_task.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/util/crc.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/util/crc.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/util/ringbuf.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/util/ringbuf.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/util/sys_clock.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/util/sys_clock.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/util/task_watchdog.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"App/util/task_watchdog.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"Drivers/BSP/CH395F/ch395f.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"Drivers/BSP/CH395F/ch395f.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c" [fillcolor="#dbeafe", color="#1d4ed8"];
@@ -99,6 +97,7 @@ digraph refgraph_full {
"Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_flash.h" [fillcolor="#f3f4f6", color="#9ca3af"];
"Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_gpio.h" [fillcolor="#f3f4f6", color="#9ca3af"];
"Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_i2c.h" [fillcolor="#f3f4f6", color="#9ca3af"];
"Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_iwdg.h" [fillcolor="#f3f4f6", color="#9ca3af"];
"Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_pwr.h" [fillcolor="#f3f4f6", color="#9ca3af"];
"Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_rcc.h" [fillcolor="#f3f4f6", color="#9ca3af"];
"Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_spi.h" [fillcolor="#f3f4f6", color="#9ca3af"];
@@ -108,6 +107,7 @@ digraph refgraph_full {
"Inc/dma.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"Inc/gpio.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"Inc/i2c.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"Inc/iwdg.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"Inc/main.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"Inc/spi.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"Inc/stm32f4xx_hal_conf.h" [fillcolor="#dbeafe", color="#1d4ed8"];
@@ -126,6 +126,7 @@ digraph refgraph_full {
"Src/freertos.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"Src/gpio.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"Src/i2c.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"Src/iwdg.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"Src/main.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"Src/spi.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"Src/stm32f4xx_hal_msp.c" [fillcolor="#dbeafe", color="#1d4ed8"];
@@ -157,8 +158,13 @@ digraph refgraph_full {
"test/board/sd2506_test_task.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/board/storage_test.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/board/storage_test_task.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/board/test_common.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/board/test_config.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/board/test_config.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/host/watchdog/stub/dbg_log.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/host/watchdog/stub/iwdg.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/host/watchdog/stub/main.h" [fillcolor="#dbeafe", color="#1d4ed8"];
"test/host/watchdog/watchdog_test.c" [fillcolor="#dbeafe", color="#1d4ed8"];
"外部/系统头文件" [shape=ellipse, fillcolor="#fef3c7", color="#d97706", fontsize=12];
"App/app/app_main.c" -> "App/app/app_cfg.h" [color="#2563eb", penwidth=1.0];
"App/app/app_main.c" -> "App/app/app_main.h" [color="#2563eb", penwidth=1.0];
@@ -170,6 +176,7 @@ digraph refgraph_full {
"App/app/app_main.c" -> "Drivers/BSP/TPAFE5160/tpafe5160.h" [color="#2563eb", penwidth=1.0];
"App/app/app_main.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
"App/app/app_main.c" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
"App/app/app_main.c" -> "Inc/stm32f4xx_it.h" [color="#2563eb", penwidth=1.0];
"App/app/app_main.c" -> "Inc/usart.h" [color="#2563eb", penwidth=1.0];
"App/app/app_main.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/app/sys_events.h" -> "Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2/cmsis_os2.h" [color="#94a3b8", penwidth=0.7];
@@ -202,6 +209,7 @@ digraph refgraph_full {
"App/sync/time_sync.c" -> "App/app/sys_events.h" [color="#2563eb", penwidth=1.0];
"App/sync/time_sync.c" -> "App/sync/time_sync.h" [color="#2563eb", penwidth=1.0];
"App/sync/time_sync.c" -> "App/util/sys_clock.h" [color="#2563eb", penwidth=1.0];
"App/sync/time_sync.c" -> "App/util/task_watchdog.h" [color="#2563eb", penwidth=1.0];
"App/sync/time_sync.c" -> "Drivers/BSP/NET/net_socket.h" [color="#2563eb", penwidth=1.0];
"App/sync/time_sync.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
"App/sync/time_sync.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
@@ -214,6 +222,7 @@ digraph refgraph_full {
"App/task/default_task.c" -> "App/cfg/cfg_file.h" [color="#2563eb", penwidth=1.0];
"App/task/default_task.c" -> "App/log/sys_log.h" [color="#2563eb", penwidth=1.0];
"App/task/default_task.c" -> "App/task/default_task.h" [color="#2563eb", penwidth=1.0];
"App/task/default_task.c" -> "App/util/task_watchdog.h" [color="#2563eb", penwidth=1.0];
"App/task/default_task.c" -> "Drivers/BSP/GD5F2GQ5UE/nand_ftl.h" [color="#2563eb", penwidth=1.0];
"App/task/default_task.c" -> "Drivers/BSP/NETCFG/net_cfg.h" [color="#2563eb", penwidth=1.0];
"App/task/default_task.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
@@ -226,6 +235,7 @@ digraph refgraph_full {
"App/task/net_task.c" -> "App/app/sys_events.h" [color="#2563eb", penwidth=1.0];
"App/task/net_task.c" -> "App/log/sys_log.h" [color="#2563eb", penwidth=1.0];
"App/task/net_task.c" -> "App/task/net_task.h" [color="#2563eb", penwidth=1.0];
"App/task/net_task.c" -> "App/util/task_watchdog.h" [color="#2563eb", penwidth=1.0];
"App/task/net_task.c" -> "Drivers/BSP/CH395F/ch395f.h" [color="#2563eb", penwidth=1.0];
"App/task/net_task.c" -> "Drivers/BSP/NET/net_select.h" [color="#2563eb", penwidth=1.0];
"App/task/net_task.c" -> "Drivers/BSP/NET/net_socket.h" [color="#2563eb", penwidth=1.0];
@@ -241,6 +251,7 @@ digraph refgraph_full {
"App/task/ui_task.c" -> "App/app/sys_events.h" [color="#2563eb", penwidth=1.0];
"App/task/ui_task.c" -> "App/task/ui_task.h" [color="#2563eb", penwidth=1.0];
"App/task/ui_task.c" -> "App/util/sys_clock.h" [color="#2563eb", penwidth=1.0];
"App/task/ui_task.c" -> "App/util/task_watchdog.h" [color="#2563eb", penwidth=1.0];
"App/task/ui_task.c" -> "Drivers/BSP/UI/ui_menu.h" [color="#2563eb", penwidth=1.0];
"App/task/ui_task.c" -> "Drivers/BSP/UI/ui_net_sink.h" [color="#2563eb", penwidth=1.0];
"App/task/ui_task.c" -> "Drivers/BSP/UI/ui_port.h" [color="#2563eb", penwidth=1.0];
@@ -250,10 +261,6 @@ digraph refgraph_full {
"App/task/ui_task.c" -> "test/board/net_latency_test.h" [color="#2563eb", penwidth=1.0];
"App/task/ui_task.c" -> "test/board/test_config.h" [color="#2563eb", penwidth=1.0];
"App/task/ui_task.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/util/crc.c" -> "App/util/crc.h" [color="#2563eb", penwidth=1.0];
"App/util/crc.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/util/ringbuf.c" -> "App/util/ringbuf.h" [color="#2563eb", penwidth=1.0];
"App/util/ringbuf.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/util/sys_clock.c" -> "App/util/sys_clock.h" [color="#2563eb", penwidth=1.0];
"App/util/sys_clock.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
"App/util/sys_clock.c" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
@@ -262,6 +269,11 @@ digraph refgraph_full {
"App/util/sys_clock.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/util/sys_clock.h" -> "Drivers/BSP/SD2506/sd2506.h" [color="#2563eb", penwidth=1.0];
"App/util/sys_clock.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"App/util/task_watchdog.c" -> "App/util/task_watchdog.h" [color="#2563eb", penwidth=1.0];
"App/util/task_watchdog.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
"App/util/task_watchdog.c" -> "Inc/iwdg.h" [color="#2563eb", penwidth=1.0];
"App/util/task_watchdog.c" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
"App/util/task_watchdog.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"Drivers/BSP/CH395F/ch395f.c" -> "Drivers/BSP/CH395F/ch395f.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/CH395F/ch395f.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
"Drivers/BSP/CH395F/ch395f.c" -> "Inc/spi.h" [color="#2563eb", penwidth=1.0];
@@ -423,6 +435,7 @@ digraph refgraph_full {
"Inc/dma.h" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
"Inc/gpio.h" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
"Inc/i2c.h" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
"Inc/iwdg.h" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
"Inc/main.h" -> "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal.h" [color="#94a3b8", penwidth=0.7];
"Inc/spi.h" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
"Inc/stm32f4xx_hal_conf.h" -> "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_cortex.h" [color="#94a3b8", penwidth=0.7];
@@ -431,6 +444,7 @@ digraph refgraph_full {
"Inc/stm32f4xx_hal_conf.h" -> "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_flash.h" [color="#94a3b8", penwidth=0.7];
"Inc/stm32f4xx_hal_conf.h" -> "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_gpio.h" [color="#94a3b8", penwidth=0.7];
"Inc/stm32f4xx_hal_conf.h" -> "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_i2c.h" [color="#94a3b8", penwidth=0.7];
"Inc/stm32f4xx_hal_conf.h" -> "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_iwdg.h" [color="#94a3b8", penwidth=0.7];
"Inc/stm32f4xx_hal_conf.h" -> "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_pwr.h" [color="#94a3b8", penwidth=0.7];
"Inc/stm32f4xx_hal_conf.h" -> "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_rcc.h" [color="#94a3b8", penwidth=0.7];
"Inc/stm32f4xx_hal_conf.h" -> "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_spi.h" [color="#94a3b8", penwidth=0.7];
@@ -457,6 +471,7 @@ digraph refgraph_full {
"Src/freertos.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"Src/gpio.c" -> "Inc/gpio.h" [color="#2563eb", penwidth=1.0];
"Src/i2c.c" -> "Inc/i2c.h" [color="#2563eb", penwidth=1.0];
"Src/iwdg.c" -> "Inc/iwdg.h" [color="#2563eb", penwidth=1.0];
"Src/main.c" -> "App/app/app_main.h" [color="#2563eb", penwidth=1.0];
"Src/main.c" -> "App/util/sys_clock.h" [color="#2563eb", penwidth=1.0];
"Src/main.c" -> "Drivers/BSP/CH395F/ch395f.h" [color="#2563eb", penwidth=1.0];
@@ -470,6 +485,7 @@ digraph refgraph_full {
"Src/main.c" -> "Inc/dma.h" [color="#2563eb", penwidth=1.0];
"Src/main.c" -> "Inc/gpio.h" [color="#2563eb", penwidth=1.0];
"Src/main.c" -> "Inc/i2c.h" [color="#2563eb", penwidth=1.0];
"Src/main.c" -> "Inc/iwdg.h" [color="#2563eb", penwidth=1.0];
"Src/main.c" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
"Src/main.c" -> "Inc/spi.h" [color="#2563eb", penwidth=1.0];
"Src/main.c" -> "Inc/usart.h" [color="#2563eb", penwidth=1.0];
@@ -511,6 +527,7 @@ digraph refgraph_full {
"simulator/sim/ui_net_sink_backend_sim.c" -> "Drivers/BSP/UI/ui_port.h" [color="#2563eb", penwidth=1.0];
"simulator/sim/ui_net_sink_backend_sim.c" -> "Lib/lvgl/lvgl.h" [color="#94a3b8", penwidth=0.7];
"simulator/sim/ui_net_sink_backend_sim.c" -> "simulator/sim/sim_port.h" [color="#2563eb", penwidth=1.0];
"test/board/ch395f_test.h" -> "test/board/test_common.h" [color="#2563eb", penwidth=1.0];
"test/board/ch395f_test.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/board/ch395f_test_task.c" -> "App/app/sys_events.h" [color="#2563eb", penwidth=1.0];
"test/board/ch395f_test_task.c" -> "App/task/net_task.h" [color="#2563eb", penwidth=1.0];
@@ -528,6 +545,7 @@ digraph refgraph_full {
"test/board/ch395f_test_task.c" -> "test/board/test_config.h" [color="#2563eb", penwidth=1.0];
"test/board/ch395f_test_task.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/board/gd5f_test.h" -> "Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h" [color="#2563eb", penwidth=1.0];
"test/board/gd5f_test.h" -> "test/board/test_common.h" [color="#2563eb", penwidth=1.0];
"test/board/gd5f_test.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/board/gd5f_test_task.c" -> "Drivers/BSP/GD5F2GQ5UE/nand_ftl.h" [color="#2563eb", penwidth=1.0];
"test/board/gd5f_test_task.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
@@ -555,6 +573,7 @@ digraph refgraph_full {
"test/board/net_test_task.c" -> "test/board/net_latency_test.h" [color="#2563eb", penwidth=1.0];
"test/board/net_test_task.c" -> "test/board/net_test_task.h" [color="#2563eb", penwidth=1.0];
"test/board/net_test_task.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/board/sd2506_test.h" -> "test/board/test_common.h" [color="#2563eb", penwidth=1.0];
"test/board/sd2506_test.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/board/sd2506_test_task.c" -> "Drivers/BSP/SD2506/sd2506.h" [color="#2563eb", penwidth=1.0];
"test/board/sd2506_test_task.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
@@ -562,6 +581,7 @@ digraph refgraph_full {
"test/board/sd2506_test_task.c" -> "test/board/sd2506_test.h" [color="#2563eb", penwidth=1.0];
"test/board/sd2506_test_task.c" -> "test/board/test_config.h" [color="#2563eb", penwidth=1.0];
"test/board/sd2506_test_task.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/board/storage_test.h" -> "test/board/test_common.h" [color="#2563eb", penwidth=1.0];
"test/board/storage_test.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/board/storage_test_task.c" -> "Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h" [color="#2563eb", penwidth=1.0];
"test/board/storage_test_task.c" -> "Drivers/BSP/GD5F2GQ5UE/nand_ftl.h" [color="#2563eb", penwidth=1.0];
@@ -572,6 +592,7 @@ digraph refgraph_full {
"test/board/storage_test_task.c" -> "test/board/storage_test.h" [color="#2563eb", penwidth=1.0];
"test/board/storage_test_task.c" -> "test/board/test_config.h" [color="#2563eb", penwidth=1.0];
"test/board/storage_test_task.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/board/test_common.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/board/test_config.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
"test/board/test_config.c" -> "Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2/cmsis_os.h" [color="#94a3b8", penwidth=0.7];
"test/board/test_config.c" -> "test/board/ch395f_test_task.h" [color="#2563eb", penwidth=1.0];
@@ -584,4 +605,12 @@ digraph refgraph_full {
"test/board/test_config.h" -> "test/board/sd2506_test.h" [color="#2563eb", penwidth=1.0];
"test/board/test_config.h" -> "test/board/storage_test.h" [color="#2563eb", penwidth=1.0];
"test/board/test_config.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/host/watchdog/stub/dbg_log.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/host/watchdog/stub/iwdg.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/host/watchdog/stub/main.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
"test/host/watchdog/watchdog_test.c" -> "App/util/task_watchdog.c" [color="#2563eb", penwidth=1.0];
"test/host/watchdog/watchdog_test.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
"test/host/watchdog/watchdog_test.c" -> "Inc/iwdg.h" [color="#2563eb", penwidth=1.0];
"test/host/watchdog/watchdog_test.c" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
"test/host/watchdog/watchdog_test.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
}
+256 -70
View File
@@ -97,22 +97,6 @@
"path": "App/task/ui_task.h",
"class": "fp"
},
{
"path": "App/util/crc.c",
"class": "fp"
},
{
"path": "App/util/crc.h",
"class": "fp"
},
{
"path": "App/util/ringbuf.c",
"class": "fp"
},
{
"path": "App/util/ringbuf.h",
"class": "fp"
},
{
"path": "App/util/sys_clock.c",
"class": "fp"
@@ -121,6 +105,14 @@
"path": "App/util/sys_clock.h",
"class": "fp"
},
{
"path": "App/util/task_watchdog.c",
"class": "fp"
},
{
"path": "App/util/task_watchdog.h",
"class": "fp"
},
{
"path": "Drivers/BSP/CH395F/ch395f.c",
"class": "fp"
@@ -585,6 +577,10 @@
"path": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_i2c_ex.h",
"class": "tp"
},
{
"path": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_iwdg.h",
"class": "tp"
},
{
"path": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_pwr.h",
"class": "tp"
@@ -641,6 +637,10 @@
"path": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_ll_i2c.h",
"class": "tp"
},
{
"path": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_ll_iwdg.h",
"class": "tp"
},
{
"path": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_ll_pwr.h",
"class": "tp"
@@ -709,6 +709,10 @@
"path": "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c_ex.c",
"class": "tp"
},
{
"path": "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_iwdg.c",
"class": "tp"
},
{
"path": "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pwr.c",
"class": "tp"
@@ -757,6 +761,10 @@
"path": "Inc/i2c.h",
"class": "fp"
},
{
"path": "Inc/iwdg.h",
"class": "fp"
},
{
"path": "Inc/main.h",
"class": "fp"
@@ -4725,6 +4733,10 @@
"path": "Src/i2c.c",
"class": "fp"
},
{
"path": "Src/iwdg.c",
"class": "fp"
},
{
"path": "Src/main.c",
"class": "fp"
@@ -5549,6 +5561,10 @@
"path": "test/board/storage_test_task.c",
"class": "fp"
},
{
"path": "test/board/test_common.h",
"class": "fp"
},
{
"path": "test/board/test_config.c",
"class": "fp"
@@ -5557,6 +5573,22 @@
"path": "test/board/test_config.h",
"class": "fp"
},
{
"path": "test/host/watchdog/stub/dbg_log.h",
"class": "fp"
},
{
"path": "test/host/watchdog/stub/iwdg.h",
"class": "fp"
},
{
"path": "test/host/watchdog/stub/main.h",
"class": "fp"
},
{
"path": "test/host/watchdog/watchdog_test.c",
"class": "fp"
},
{
"path": "外部/系统头文件",
"class": "ext"
@@ -5581,6 +5613,12 @@
"kind": "fp-fp",
"include": "main.h"
},
{
"from": "App/app/app_main.c",
"to": "Inc/stm32f4xx_it.h",
"kind": "fp-fp",
"include": "stm32f4xx_it.h"
},
{
"from": "App/app/app_main.c",
"to": "Inc/usart.h",
@@ -5875,6 +5913,12 @@
"kind": "fp-fp",
"include": "sys_events.h"
},
{
"from": "App/sync/time_sync.c",
"to": "App/util/task_watchdog.h",
"kind": "fp-fp",
"include": "task_watchdog.h"
},
{
"from": "App/sync/time_sync.c",
"to": "Drivers/BSP/dbg_log.h",
@@ -5989,6 +6033,12 @@
"kind": "fp-fp",
"include": "dbg_log.h"
},
{
"from": "App/task/default_task.c",
"to": "App/util/task_watchdog.h",
"kind": "fp-fp",
"include": "task_watchdog.h"
},
{
"from": "App/task/net_task.c",
"to": "外部/系统头文件",
@@ -6049,6 +6099,12 @@
"kind": "fp-fp",
"include": "test_config.h"
},
{
"from": "App/task/net_task.c",
"to": "App/util/task_watchdog.h",
"kind": "fp-fp",
"include": "task_watchdog.h"
},
{
"from": "App/task/net_task.c",
"to": "Drivers/BSP/dbg_log.h",
@@ -6145,48 +6201,18 @@
"kind": "fp-fp",
"include": "net_latency_test.h"
},
{
"from": "App/task/ui_task.c",
"to": "App/util/task_watchdog.h",
"kind": "fp-fp",
"include": "task_watchdog.h"
},
{
"from": "App/task/ui_task.c",
"to": "Drivers/BSP/dbg_log.h",
"kind": "fp-fp",
"include": "dbg_log.h"
},
{
"from": "App/util/crc.c",
"to": "App/util/crc.h",
"kind": "fp-fp",
"include": "crc.h"
},
{
"from": "App/util/crc.h",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "stdint.h"
},
{
"from": "App/util/crc.h",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "stddef.h"
},
{
"from": "App/util/ringbuf.c",
"to": "App/util/ringbuf.h",
"kind": "fp-fp",
"include": "ringbuf.h"
},
{
"from": "App/util/ringbuf.h",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "stdint.h"
},
{
"from": "App/util/ringbuf.h",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "stddef.h"
},
{
"from": "App/util/sys_clock.c",
"to": "App/util/sys_clock.h",
@@ -6241,6 +6267,36 @@
"kind": "fp-fp",
"include": "sd2506.h"
},
{
"from": "App/util/task_watchdog.c",
"to": "App/util/task_watchdog.h",
"kind": "fp-fp",
"include": "task_watchdog.h"
},
{
"from": "App/util/task_watchdog.c",
"to": "Inc/main.h",
"kind": "fp-fp",
"include": "main.h"
},
{
"from": "App/util/task_watchdog.c",
"to": "Inc/iwdg.h",
"kind": "fp-fp",
"include": "iwdg.h"
},
{
"from": "App/util/task_watchdog.c",
"to": "Drivers/BSP/dbg_log.h",
"kind": "fp-fp",
"include": "dbg_log.h"
},
{
"from": "App/util/task_watchdog.h",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "stdint.h"
},
{
"from": "Drivers/BSP/dbg_log.h",
"to": "外部/系统头文件",
@@ -8317,6 +8373,12 @@
"kind": "tp-tp",
"include": "stm32f4xx_hal_def.h"
},
{
"from": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_iwdg.h",
"to": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_def.h",
"kind": "tp-tp",
"include": "stm32f4xx_hal_def.h"
},
{
"from": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_pwr.h",
"to": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_def.h",
@@ -8419,6 +8481,12 @@
"kind": "tp-tp",
"include": "stm32f4xx.h"
},
{
"from": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_ll_iwdg.h",
"to": "Drivers/CMSIS/Device/ST/STM32F4xx/Include/stm32f4xx.h",
"kind": "tp-tp",
"include": "stm32f4xx.h"
},
{
"from": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_ll_pwr.h",
"to": "Drivers/CMSIS/Device/ST/STM32F4xx/Include/stm32f4xx.h",
@@ -8521,6 +8589,12 @@
"kind": "tp-tp",
"include": "stm32f4xx_hal.h"
},
{
"from": "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_iwdg.c",
"to": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal.h",
"kind": "tp-tp",
"include": "stm32f4xx_hal.h"
},
{
"from": "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pwr.c",
"to": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal.h",
@@ -8593,6 +8667,12 @@
"kind": "fp-fp",
"include": "main.h"
},
{
"from": "Inc/iwdg.h",
"to": "Inc/main.h",
"kind": "fp-fp",
"include": "main.h"
},
{
"from": "Inc/main.h",
"to": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal.h",
@@ -8757,8 +8837,8 @@
},
{
"from": "Inc/stm32f4xx_hal_conf.h",
"to": "外部/系统头文件",
"kind": "fp-ext",
"to": "Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_iwdg.h",
"kind": "fp-tp",
"include": "stm32f4xx_hal_iwdg.h"
},
{
@@ -38749,6 +38829,12 @@
"kind": "fp-fp",
"include": "i2c.h"
},
{
"from": "Src/iwdg.c",
"to": "Inc/iwdg.h",
"kind": "fp-fp",
"include": "iwdg.h"
},
{
"from": "Src/main.c",
"to": "Inc/main.h",
@@ -38773,6 +38859,12 @@
"kind": "fp-fp",
"include": "i2c.h"
},
{
"from": "Src/main.c",
"to": "Inc/iwdg.h",
"kind": "fp-fp",
"include": "iwdg.h"
},
{
"from": "Src/main.c",
"to": "Inc/spi.h",
@@ -38923,6 +39015,12 @@
"kind": "fp-ext",
"include": "stdint.h"
},
{
"from": "test/board/ch395f_test.h",
"to": "test/board/test_common.h",
"kind": "fp-fp",
"include": "test_common.h"
},
{
"from": "test/board/ch395f_test_task.c",
"to": "外部/系统头文件",
@@ -39019,6 +39117,12 @@
"kind": "fp-ext",
"include": "stdint.h"
},
{
"from": "test/board/gd5f_test.h",
"to": "test/board/test_common.h",
"kind": "fp-fp",
"include": "test_common.h"
},
{
"from": "test/board/gd5f_test.h",
"to": "Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h",
@@ -39193,6 +39297,12 @@
"kind": "fp-ext",
"include": "stdint.h"
},
{
"from": "test/board/sd2506_test.h",
"to": "test/board/test_common.h",
"kind": "fp-fp",
"include": "test_common.h"
},
{
"from": "test/board/sd2506_test_task.c",
"to": "外部/系统头文件",
@@ -39241,6 +39351,12 @@
"kind": "fp-ext",
"include": "stdint.h"
},
{
"from": "test/board/storage_test.h",
"to": "test/board/test_common.h",
"kind": "fp-fp",
"include": "test_common.h"
},
{
"from": "test/board/storage_test_task.c",
"to": "外部/系统头文件",
@@ -39295,6 +39411,12 @@
"kind": "fp-fp",
"include": "gd5f2gq5ue.h"
},
{
"from": "test/board/test_common.h",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "stdint.h"
},
{
"from": "test/board/test_config.c",
"to": "Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2/cmsis_os.h",
@@ -39366,6 +39488,60 @@
"to": "test/board/sd2506_test.h",
"kind": "fp-fp",
"include": "sd2506_test.h"
},
{
"from": "test/host/watchdog/watchdog_test.c",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "stdint.h"
},
{
"from": "test/host/watchdog/watchdog_test.c",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "stdio.h"
},
{
"from": "test/host/watchdog/watchdog_test.c",
"to": "Inc/main.h",
"kind": "fp-fp",
"include": "main.h"
},
{
"from": "test/host/watchdog/watchdog_test.c",
"to": "Inc/iwdg.h",
"kind": "fp-fp",
"include": "iwdg.h"
},
{
"from": "test/host/watchdog/watchdog_test.c",
"to": "Drivers/BSP/dbg_log.h",
"kind": "fp-fp",
"include": "dbg_log.h"
},
{
"from": "test/host/watchdog/watchdog_test.c",
"to": "App/util/task_watchdog.c",
"kind": "fp-fp",
"include": "task_watchdog.c"
},
{
"from": "test/host/watchdog/stub/dbg_log.h",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "stdio.h"
},
{
"from": "test/host/watchdog/stub/iwdg.h",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "stdint.h"
},
{
"from": "test/host/watchdog/stub/main.h",
"to": "外部/系统头文件",
"kind": "fp-ext",
"include": "stdint.h"
}
],
"unresolved": {
@@ -39416,14 +39592,6 @@
"App/task/ui_task.c": [
"string.h"
],
"App/util/crc.h": [
"stddef.h",
"stdint.h"
],
"App/util/ringbuf.h": [
"stddef.h",
"stdint.h"
],
"App/util/sys_clock.c": [
"stdio.h",
"string.h"
@@ -39431,6 +39599,9 @@
"App/util/sys_clock.h": [
"stdint.h"
],
"App/util/task_watchdog.h": [
"stdint.h"
],
"Drivers/BSP/CH395F/ch395f.c": [
"stdio.h",
"string.h"
@@ -39699,7 +39870,6 @@
"stm32f4xx_hal_hcd.h",
"stm32f4xx_hal_i2s.h",
"stm32f4xx_hal_irda.h",
"stm32f4xx_hal_iwdg.h",
"stm32f4xx_hal_lptim.h",
"stm32f4xx_hal_ltdc.h",
"stm32f4xx_hal_mmc.h",
@@ -41092,18 +41262,34 @@
"test/board/storage_test_task.c": [
"string.h"
],
"test/board/test_common.h": [
"stdint.h"
],
"test/board/test_config.h": [
"stdint.h"
],
"test/host/watchdog/stub/dbg_log.h": [
"stdio.h"
],
"test/host/watchdog/stub/iwdg.h": [
"stdint.h"
],
"test/host/watchdog/stub/main.h": [
"stdint.h"
],
"test/host/watchdog/watchdog_test.c": [
"stdint.h",
"stdio.h"
]
},
"stats": {
"files_indexed": 1403,
"includes_parsed": 5634,
"resolved": 4579,
"unresolved": 1055,
"nodes_fp": 133,
"nodes_tp": 1256,
"ext_names": 353
"files_indexed": 1411,
"includes_parsed": 5658,
"resolved": 4601,
"unresolved": 1057,
"nodes_fp": 138,
"nodes_tp": 1259,
"ext_names": 352
},
"include_dirs": [
"Inc",
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 311 KiB

After

Width:  |  Height:  |  Size: 328 KiB

+12 -11
View File
@@ -29,7 +29,7 @@ digraph refgraph_module {
"其他(第三方)" [fillcolor="#f3f4f6", color="#9ca3af"];
"外部/系统头文件" [shape=ellipse, fillcolor="#fef3c7", color="#d97706"];
"App" -> "App/log" [color="#2563eb", penwidth=1.2, label="×2"];
"App" -> "App/util" [color="#2563eb", penwidth=1.2, label="×2"];
"App" -> "App/util" [color="#2563eb", penwidth=1.2, label="×3"];
"App" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×3"];
"App" -> "BSP/GD5F2GQ5UE" [color="#2563eb", penwidth=1.2, label="×1"];
"App" -> "BSP/NET" [color="#2563eb", penwidth=1.2, label="×1"];
@@ -38,7 +38,7 @@ digraph refgraph_module {
"App" -> "BSP/TPAFE5160" [color="#2563eb", penwidth=1.2, label="×1"];
"App" -> "FatFs" [color="#2563eb", penwidth=1.2, label="×1"];
"App" -> "FreeRTOS" [color="#2563eb", penwidth=1.2, label="×2"];
"App" -> "Src/Inc (CubeMX)" [color="#2563eb", penwidth=1.2, label="×2"];
"App" -> "Src/Inc (CubeMX)" [color="#2563eb", penwidth=1.2, label="×3"];
"App" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×7"];
"App/log" -> "App" [color="#2563eb", penwidth=1.2, label="×1"];
"App/log" -> "App/util" [color="#2563eb", penwidth=1.2, label="×1"];
@@ -49,7 +49,7 @@ digraph refgraph_module {
"App/log" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×4"];
"App/task" -> "App" [color="#2563eb", penwidth=1.2, label="×4"];
"App/task" -> "App/log" [color="#2563eb", penwidth=1.2, label="×2"];
"App/task" -> "App/util" [color="#2563eb", penwidth=1.2, label="×1"];
"App/task" -> "App/util" [color="#2563eb", penwidth=1.2, label="×4"];
"App/task" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×5"];
"App/task" -> "BSP/CH395F" [color="#2563eb", penwidth=1.2, label="×1"];
"App/task" -> "BSP/GD5F2GQ5UE" [color="#2563eb", penwidth=1.2, label="×1"];
@@ -63,11 +63,11 @@ digraph refgraph_module {
"App/task" -> "Src/Inc (CubeMX)" [color="#2563eb", penwidth=1.2, label="×1"];
"App/task" -> "test" [color="#2563eb", penwidth=1.2, label="×4"];
"App/task" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×2"];
"App/util" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×1"];
"App/util" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×2"];
"App/util" -> "BSP/SD2506" [color="#2563eb", penwidth=1.2, label="×1"];
"App/util" -> "FreeRTOS" [color="#2563eb", penwidth=1.2, label="×2"];
"App/util" -> "Src/Inc (CubeMX)" [color="#2563eb", penwidth=1.2, label="×1"];
"App/util" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×4"];
"App/util" -> "Src/Inc (CubeMX)" [color="#2563eb", penwidth=1.2, label="×3"];
"App/util" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×3"];
"BSP(公共)" -> "App/log" [color="#2563eb", penwidth=1.2, label="×1"];
"BSP(公共)" -> "App/util" [color="#2563eb", penwidth=1.2, label="×1"];
"BSP(公共)" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×1"];
@@ -109,7 +109,7 @@ digraph refgraph_module {
"FreeRTOS" -> "CMSIS" [color="#94a3b8", penwidth=0.7, label="×1"];
"FreeRTOS" -> "Src/Inc (CubeMX)" [color="#94a3b8", penwidth=0.7, label="×1"];
"FreeRTOS" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×14"];
"HAL" -> "CMSIS" [color="#94a3b8", penwidth=0.7, label="×13"];
"HAL" -> "CMSIS" [color="#94a3b8", penwidth=0.7, label="×14"];
"HAL" -> "Src/Inc (CubeMX)" [color="#94a3b8", penwidth=0.7, label="×1"];
"HAL" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×1"];
"LVGL" -> "FatFs" [color="#94a3b8", penwidth=0.7, label="×1"];
@@ -130,7 +130,7 @@ digraph refgraph_module {
"Src/Inc (CubeMX)" -> "BSP/TPAFE5160" [color="#2563eb", penwidth=1.2, label="×1"];
"Src/Inc (CubeMX)" -> "CMSIS" [color="#2563eb", penwidth=1.2, label="×1"];
"Src/Inc (CubeMX)" -> "FreeRTOS" [color="#2563eb", penwidth=1.2, label="×4"];
"Src/Inc (CubeMX)" -> "HAL" [color="#2563eb", penwidth=1.2, label="×14"];
"Src/Inc (CubeMX)" -> "HAL" [color="#2563eb", penwidth=1.2, label="×15"];
"Src/Inc (CubeMX)" -> "lftpd" [color="#2563eb", penwidth=1.2, label="×1"];
"Src/Inc (CubeMX)" -> "test" [color="#2563eb", penwidth=1.2, label="×3"];
"Src/Inc (CubeMX)" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×4"];
@@ -151,13 +151,14 @@ digraph refgraph_module {
"simulator" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×6"];
"test" -> "App" [color="#2563eb", penwidth=1.2, label="×1"];
"test" -> "App/task" [color="#2563eb", penwidth=1.2, label="×1"];
"test" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×7"];
"test" -> "App/util" [color="#2563eb", penwidth=1.2, label="×1"];
"test" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×8"];
"test" -> "BSP/CH395F" [color="#2563eb", penwidth=1.2, label="×2"];
"test" -> "BSP/GD5F2GQ5UE" [color="#2563eb", penwidth=1.2, label="×4"];
"test" -> "BSP/NET" [color="#2563eb", penwidth=1.2, label="×5"];
"test" -> "BSP/SD2506" [color="#2563eb", penwidth=1.2, label="×1"];
"test" -> "FatFs" [color="#2563eb", penwidth=1.2, label="×3"];
"test" -> "FreeRTOS" [color="#2563eb", penwidth=1.2, label="×5"];
"test" -> "Src/Inc (CubeMX)" [color="#2563eb", penwidth=1.2, label="×2"];
"test" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×12"];
"test" -> "Src/Inc (CubeMX)" [color="#2563eb", penwidth=1.2, label="×4"];
"test" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×17"];
}
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

@@ -36,10 +36,10 @@
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
| 区域 | 用量 | 容量 | 占比 |
|---|---|---|---|
| ER_IROM1 | 261,312 B (255.19 KB) | 1,048,576 B (1024 KB) | 24.9% |
| ER_IROM1 | 261,200 B (255.08 KB) | 1,048,576 B (1024 KB) | 24.9% |
| RW_IRAM1 | 84,864 B (82.88 KB) | 131,072 B (128 KB) | 64.7% |
| RW_CCM | 49,184 B (48.03 KB) | 65,536 B (64 KB) | 75.0% |
| **Flash (ROM 合计)** | **262,012 B (255.9 KB)** | 1,048,576 B (1024 KB) | **25.0%** |
| **Flash (ROM 合计)** | **261,900 B (255.8 KB)** | 1,048,576 B (1024 KB) | **25.0%** |
<!-- MEM_SNAPSHOT:END -->
各区的容量与常量来源:
@@ -0,0 +1,674 @@
# 测试体系与整体架构改进建议(2026-10-02)
> **用途**:对现有**测试体系**与**整体架构**做一次独立复审,回答两个问题——
> ① 测试是否足够?② 架构还有什么可改进的地方?
> **性质**:**纯建议文档,不改任何代码**。每条给出「现状证据 → 风险 → 实施路径 → 完成判据」。
> **上游**:`项目审查与优化建议报告.md`(P0~P3 全量池)、`项目审计改进清单.md`(待办)、
> `错误码传播审计.md`(E-xx)、`待处理内容.md`(登记簿)、`下一步任务规划.md`(排序)。
> **本文不重复已登记条目**——只补充**测试充分性**与**架构层面**的新发现,或对既有条目
> 给出**新的实施角度**。凡与上游重叠的,标注上游编号并注明本文增量。
> **审计方法**:源码逐文件走读(`App/`、`test/`、`Drivers/BSP/`)+ 测试资产量化统计 +
> CI/pre-commit 门禁链路核查 + 与六份测试文档交叉比对。
---
## 0. 结论速览
### 0.1 测试体系评价:**分层设计优秀,但"中间层"严重偏薄**
| 维度 | 评价 | 一句话 |
|---|---|---|
| 分层模型(L0~L3) | ⭐⭐⭐⭐⭐ | 四层划分清晰、门禁决策表明确,**优于绝大多数同规模嵌入式项目** |
| 板端套件(L1) | ⭐⭐⭐⭐☆ | 4 套件 + 222 个断言点,覆盖驱动功能面完整 |
| 静态门禁(L0) | ⭐⭐⭐⭐⭐ | 15 个钩子、含一致性/规范/乱码/元数据,防线密不透风 |
| **主机侧纯逻辑单测(L0.5)** | ⭐⭐☆☆☆ | **仅 1 个模块(看门狗)**,而适格对象至少有 6~8 个 |
| UI 回归(L3 门禁) | ⭐⭐⭐⭐☆ | 文本层 + 像素层 + 池水位三层断言,思路先进 |
| CI | ⭐⭐⭐☆☆ | 只跑 L0 + L0.5;**无产物存档、无结果趋势、无覆盖率** |
| 可观测性(运行时) | ⭐⭐⭐⭐☆ | 栈水位探针 + 链路时延探针 + syslog,台架能力强 |
| **验证债** | ⭐⭐☆☆☆ | 多项"已改未验",L0.5 稀缺导致**回归只能靠真机** |
**核心判断**:项目的测试**广度**(驱动功能面)和**静态深度**(门禁)都很强,
真正的短板是 **L0.5(主机侧纯逻辑单测)的覆盖率**——这是**性价比最高、见效最快**的改进方向。
它不需要硬件、秒级完成、能上 CI、且已有一整套现成 harness(`unit_test_harness.py` + 看门狗范式)。
### 0.2 架构评价:**分层与解耦做得好,但存在"三个不对称"**
| 不对称 | 表现 | 影响 |
|---|---|---|
| **功能不对称** | 网络栈极度成熟,而 ADC/RS485 采集链路是空桩 | DTU 核心职责未实现 |
| **纵深防御不对称** | 上层(网络/日志/配置)防御完备,下层(NAND 错误通道)散乱 | 底层故障难定位 |
| **测试不对称** | 驱动面覆盖好,纯逻辑面几乎裸奔 | 回归依赖真机,反馈慢 |
### 0.3 建议优先级(与上游阶段 A/B/C/D 对齐)
```
【S 级:本周可做,零风险,收益大】 ← 本文新增
S1 扩充 L0.5 单测(crc/ringbuf 判定、sys_clock 日期算术、cfg_file 解析器)
S2 清理孤儿源文件(crc.c / ringbuf.c)——降低认知负担
S3 CI 补产物存档 + 单测结果汇总
【A 级:上游已列,本文给增量角度】
A1 P0-1 真机验证(上游 A1) A2 P1-8 网络重建恢复(上游 A2)
A3 E-34 CH395F 超时(上游 A3) A4 E-04 remo_send 返回值(上游 A4)
【B 级:核心功能补齐(上游 B,需产品定义)】
B1 ADC 链路 B2 RS485 链路 B3 E-59 门禁固化
【C 级:结构债(上游 C)】
C1 pending 二期 C2 GD5F 错误通道 C3 日志管线 C4 defaultTask 栈
```
---
## 1. 测试体系:量化现状
### 1.1 测试资产盘点(2026-10-02 实测)
**板端套件(L1)——4 套件,222 个 `TEST_CHECK` 断言点**:
| 套件 | 文件 | 断言点 | Test_Guide 用例数 | 覆盖对象 |
|---|---|---|---|---|
| CH395F 硬件层 | `ch395f_test_task.c` | 52 | 34(TC-NET-xxx) | CH395F 寄存器/命令/中断 |
| CH395F NET 层 | `net_test_task.c` | 38 | (同上,Phase 6~11) | TCP 监听/连接/收发 |
| GD5F | `gd5f_test_task.c` | 76 | 45(TC-GD5F-xxx) | NAND 驱动 10 阶段 |
| STORAGE | `storage_test_task.c` | 38 | (TC-STO-01~04) | FTL + FatFS 集成 |
| SD2506 RTC | `sd2506_test_task.c` | 18 | 9(TC-RTC-xxx) | RTC 读写/闹钟 |
| **合计** | | **222** | | |
> ⚠️ 注意:断言点数(222)**大于**文档用例数(91)——因为一个 TC 常有多个 `TEST_CHECK`。
> 这是正常的,但**反向不成立**:文档存在而代码未实现的用例无法从断言点数量看出,
> 需靠 §1.3 的"覆盖矩阵核查"专项检查(本文建议 S4,见 §3.4)。
**主机侧(L0.5)——仅 1 个模块**:
| 模块 | 位置 | 用例数 | 被测对象 |
|---|---|---|---|
| 看门狗判定状态机 | `test/host/watchdog/` | 39 | `App/util/task_watchdog.c` 位图/掩码/节流/回绕 |
| **其余模块** | — | **0** | — |
**静态门禁(L0)——15 个钩子**:
`trailing-whitespace` / `end-of-file-fixer` / `check-added-large-files` / `check-merge-conflict` /
`check-yaml` / `gitlint` / `cppcheck` / `no-mojibake` / `coding-standard` / `doc-links` /
`task-table` / `mem-numbers` / `cubemx-survival` / `watchdog-unit-test` / `test-suite-switch`
**UI 回归(L3 门禁)**:`check_menu_rows.py`(菜单 8 状态像素 + 池基线)+
`check_content_pages.py`(11 内容页 + 文本层 50 条字形模板)+ `check_remo_sink.py`(远显推流)。
**CI(Gitea Actions)**:2 个 job —— `l0-gates`(pre-commit 全量)、`l05-host-unit-tests`(遍历 `run_unit_test.py`)。
### 1.2 覆盖率缺口矩阵(按"适格性"排序)
> 判据:**无副作用 + 可离线复现 + 边界密集** = L0.5 适格(见`测试开发标准流程.md` §4.2)。
| 候选模块 | 规模 | 边界密度 | 现测试手段 | L0.5 价值 | 备注 |
|---|---|---|---|---|---|
| **`App/util/sys_clock.c`** | 479 行 | **极高** | 仅 RTC 套件(真机) | ⭐⭐⭐⭐⭐ | 闰年/日期换算/星期/49.7 天回绕——**纯算术,零硬件** |
| **`App/cfg/cfg_file.c`** | 1250 行 | **高** | 仅 STORAGE 套件(真机) | ⭐⭐⭐⭐⭐ | INI 解析/转义/长度校验/迁移——**已有 `cfg_store_alloc` 平台后端抽象,天然适格** |
| **`App/util/crc.c`** | 120 行 | 中 | **无**(孤儿文件) | ⭐⭐⭐ | 查表正确性可用标准向量验证;但**当前无调用者**(见 S2) |
| `Drivers/BSP/RS485/rs485.c` | 181 行 | 高 | **无**(任务为空桩) | ⭐⭐⭐⭐ | 帧解析状态机适格;但**功能未实现**(上游 B2) |
| `App/sync/time_sync.c` | 217 行 | 中 | 仅手工 `time_sync_server.py` | ⭐⭐⭐ | 协议编解码适格,与网络耦合需判 |
| `App/log/sys_log_mcu.c` | 359 行 | 中 | 仅 `@test` 标记 | ⭐⭐⭐ | 定长 64B 记录编解码适格;落盘部分需桩 |
| `Drivers/BSP/TPAFE5160/tpafe5160.c` | 356 行 | 中 | **无**(任务为空桩) | ⭐⭐ | 寄存器序列为主,纯逻辑面小 |
| `App/util/ringbuf.c` | 154 行 | 中 | **无**(孤儿文件) | ⭐⭐ | 若 S2 决定保留则须测;否则删除 |
**结论**:**至少 2 个高价值模块(`sys_clock`、`cfg_file`)具备立即可做的 L0.5 条件**,
合计约 1730 行核心逻辑,目前**完全依赖真机验证**。这是测试体系最大的效率损失点。
### 1.3 测试体系的结构性问题
#### 问题 T-1:L0.5 覆盖率过低,回归反馈闭环过长 🔴
**现状**:全项目仅看门狗 1 个模块有主机侧单测。`sys_clock.c`(479 行日期算术)、
`cfg_file.c`(1250 行解析器)这类**边界密集、纯逻辑、零硬件**的模块,
改动后**只能靠真机 `@test` 才能发现问题**。
**代价量化**(以 `sys_clock.c` 为例):
| 手段 | 单次反馈耗时 | 需要硬件 | 能覆盖的边界 |
|---|---|---|---|
| 真机 `@test` | 烧录 ~7s + 等静默 8s + 采集 ≈ **30s~2min** | ✅ 需板+串口+ST-LINK | 只测"当前日期"这一个点 |
| L0.5 单测 | 编译+运行 ≈ **2~5s** | ❌ 不需要 | 可穷举闰年/月末/回绕/2038 边界 |
**关键**:`sys_clock.c` 里的 `unix_to_sd2506()` 涉及闰年判断、`mdays` 月份表、
Unix 秒 → 年月日换算、49.7 天 tick 回绕惰性再基准化——这些**用真机只能测"今天",
用单测可以测一万个日期**。而历史事故(`AGENTS.md` 记载的"换算时不得再加 2000,
否则年份虚增导致 Unix 秒数超 uint32 回绕")**正是这类边界 bug**。
**建议**:见 §3.1(S1)。
#### 问题 T-2:CI 无产物存档、无趋势、无覆盖率 🟡
**现状**:CI 只做"通过/失败"判定,**不保存任何产物**:
- `pre-commit run --all-files` 的完整输出不入档(失败后只能靠 Gitea 日志页滚动查找);
- L0.5 单测输出不入档;
- 无任何历史趋势(例如"单测数从 39 涨到多少""门禁耗时变化")。
**风险**:CI 失败时**排障成本高**(本次 T-6 排障就是靠直查 MySQL + 手工解压 `actions_log` 才定位的,
见 `Gitea_Runner部署指南.md` §5.1.1)。且"测试数量是否在增长"无人可见。
**建议**:见 §3.3(S3)。
#### 问题 T-3:`test/` 代码完全豁免静态检查 🟡
**现状**(`测试开发标准流程.md` §3.3 明示):`test/` 在 pre-commit 范围之外,
"上表约定属**自律**要求"。而 `test/board/` 下是 **6300+ 行 C 代码**(实测 `.c`+`.h` 合计 6,313 行),且**编入固件**。
**风险**:测试代码也是代码——`net_test_task.c` 曾出现"`s_rx_buf` 使用先于声明"
(`测试开发标准流程.md` 变更记录 2026-09-15),说明测试代码质量并非自动达标。
豁免 cppcheck 意味着测试代码的**未初始化变量/越界/空指针**不会被静态发现。
**建议**:见 §3.5(S4)。注意**不宜**直接纳入现有钩子(会引入大量存量告警),
宜用**独立、宽松**的钩子(只报 error 级、不含 style)。
#### 问题 T-4:测试固件构建未纳入闭环(= 上游 P1-10,本文给增量)🟡
**上游已登记** P1-10"测试固件构建闭环"。**本文增量**:
`测试开发标准流程.md` 变更记录 2026-09-15 记载 **CH395F 套件 7 个配置
"自 2026-08(88160a5)起不可编译"**,且"128KB 静态 RAM 预算溢出"
(HW 全阶段固件超 8.7KB、NET 固件超 ~28KB)。
**这说明**:测试套件的**构建可编译性没有独立门禁**——产品构建 `@build` 天天跑,
但**测试构建可能长期处于"编不过"状态而无人知**,直到某次要用才发现(正是 09-15 的情况)。
**建议**:见 §3.4(S5)——把"测试套件构建"做成**可选的编译验证**,而非依赖人记得。
#### 问题 T-5:真机回归的"判定强度"仍可提升 🟡
**现状**:`verify_log.py` 已支持 `--require-suite-summary`(T-2,要求汇总行 + `N>0` + `failed=0`),
这是很好的设计。但**产品构建回归**(非套件)仍是纯 Required 子串匹配 +
Forbidden `FAIL;ERROR;HardFault;assert`。
**盲区**:产品构建回归**不校验** syslog 中的自检结果。
项目已有 `sys_log` 引擎写 `/log/selftest.log`(`运行与自检记录设计说明.md`),
但 `@test` 闭环**没有读取它**——自检失败(例如"时钟芯片异常")只在设备日志里静静躺着。
**建议**:见 §3.6(S6)——让 `@test` 闭环消费 `selftest.log` 的判定结果。
---
## 2. 整体架构:改进点
### 2.1 架构现状(客观评价)
**做得好的**(无需改动):
| 项 | 证据 |
|---|---|
| 六层单向依赖 | `项目架构总览.md` §1,跨层依赖有明确表 |
| 就绪屏障替代魔法延时 | `sys_events.h` + `osFlagsNoClear` 语义修正(含事故复盘) |
| 单写者收敛 | 日志(`console_log` / `sys_log`)、配置(`cfg_file` 属主=defaultTask)、SPI(netMsgQueue 串行化) |
| 平台后端抽象 | `cfg_file` 的"共享核心 + 双端后端"(MCU=FreeRTOS 堆/模拟器=宿主堆) |
| 双端共享源码 | UI / `net_cfg` / `cfg_file` 同时编 MCU 与模拟器 |
| 可观测性 | 栈水位探针、链路时延探针(DWT)、syslog 双记录、自检上报 |
| CubeMX 生存保障 | `check_cubemx_survival.py`(18 项清单 + 哈希基线) |
### 2.2 改进点 A-1:孤儿源文件(认知负担 + 误导)🟡
**证据**(2026-10-02 实测):
```
App/util/crc.c 120 行 → 零 `#include "crc.h"`,不在 Keil 工程
App/util/ringbuf.c 154 行 → 零 `#include "ringbuf.h"`,不在 Keil 工程
```
- `crc.c` 提供 `crc16_modbus()` / `crc8()`,但**唯一使用者 `net_cfg_legacy.c`
自己复制了一份 `s_crc16_modbus()`**(第 43/55 行),并未调用 `crc.c`。
- `ringbuf.c` 是通用环形缓冲,但 `console_log.c` **自己内联实现了环形缓冲**
(`s_con_ring` + head/tail,第 39~42 行),未复用。
**风险**:
1. **误导**:新读者看到 `App/util/crc.c` 会以为 CRC 有统一实现,实际各写各的;
2. **维护负担**:`app_main.c` 的 `/log` 无调用者却需为它做"是否要测/是否要改"的决策;
3. 上游 E-15 已提"ringbuf 定去留——**已确认零调用者,倾向删除**",本文**用实测佐证该结论**
并**追加 `crc.c`**。
**建议**:见 §4.1(S7)。
### 2.3 改进点 A-2:错误码通道的"下层不对称"🔴
**上游已登记**(`错误码传播审计.md` 第二档 #1:E-10/E-42/E-53/E-54/E-56/E-57 GD5F 错误通道族)。
**本文增量**:给出**架构层面的根本原因**与**统一方案骨架**。
**根本原因分析**:`ch395f.c`(1330 行) 与 `gd5f2gq5ue.c`(1496 行) 是**同代驱动**,
但只有 CH395F 形成了"错误分类 + 传播 + 审计"的完整体系(有 `错误码传播审计.md`、
`CH395F_Trap_Records.md` 737 行);GD5F 侧的错误处理散落在各函数内,
`Trap_Records` 仅 198 行。**差异不在驱动复杂度,而在是否建立了统一的错误通道约定**。
**建议骨架**(供 C2 参考):
```
统一错误通道四要素(以 CH395F 为范式):
① 分类:区分「超时 / 真故障 / 参数错」——不得混为一个返回码
② 透传:底层错误码逐层上报,不吞不猜(禁 `(void)ret`)
③ 分级:可恢复(重试)/ 不可恢复(上报自检 + 降级)显式判定
④ 留痕:不可恢复项写 syslog selftest,可恢复项计数并在 UI/日志可见
```
**关键纪律**(呼应 `AGENTS.md` 已有铁律):**自愈逻辑严禁把"名字/参数类错误码"
归入"卷损坏触发集"**——`.fschk` 无限重格式化事故即此类(2026-09-20)。
### 2.4 改进点 A-3:启动段无看门狗保护的窗口 🟡
**上游已登记**(N-03b,视为"显式代价")。**本文增量**:量化并给出**风险接受条件**。
**现状**:`app_main_init` + `defaultTask` bring-up 段(约 `sd2506_init` → `f_mount` → `net_cfg_load`)
无 IWDG 保护。该段死机表现 = "无输出 + LED 不闪"(N-03b 原文)。
**风险量化**:这段代码涉及
- `sd2506_init` / `sys_clock_init`(I2C1,有超时)
- `gd5f2gq5ue_init`(**BBT 全片扫描**,最耗时)
- `f_mount` / 可能的 `f_mkfs`(FAT32 格式化)
- `net_cfg_load`(NAND 读 + INI 解析)
**建议**:不强行加 IWDG(会与"BBT 扫描耗时不确定"冲突),而是
① **加启动段进度日志**(每步 `DBG_INFO`,使"卡在哪一步"可见);
② **为长操作加显式看门狗喂狗点**(若将来启用启动期 IWDG);
③ 文档明确"启动段死机的判读方法"。
> 检查现状:`app_main.c` 是否每步有日志——若有则可直接销项。见 §4.4(S8)。
### 2.5 改进点 A-4:ADC/RS485 采集链路缺失(核心功能空洞)🔴
**上游已列**(P1-1、阶段 B)。**本文增量**:从**测试可验证性**角度补充设计约束。
**现状**:`adc_task.c` / `rs485_task.c` 均为 43 行空桩(`osDelay` 死循环);
`App/protocol/` 目录**只有 README 与 `.gitkeep`**(为协议解析预留但未实现)。
**测试可验证性约束**(建议纳入 B1/B2 设计):
1. **协议解析必须与硬件 I/O 分离**——`App/protocol/` 的解析函数应接受
`(const uint8_t *buf, size_t len)` 返回解析结果,使它能成为 L0.5 的**天然适格对象**
(参考 `cfg_file` 的"核心 + 后端"模式);
2. **采集缓冲必须双缓冲 + 序号**(上游 E-49/E-50)——否则消费者读到撕裂数据,
**且撕裂在单测里测不出来**(时序相关),必须在设计上消除;
3. **DE 收尾必须有超时路径覆盖**(RS485 半双工的特有陷阱)。
### 2.6 改进点 A-5:内存预算的"预算—实际"闭环可再收紧 🟡
**现状**:已有 `ram_report.py`(.map 静态)+ `stack_report.py`(静态调用图 × 运行时水位)
+ `mem-numbers` pre-commit 钩子(校验文档数字与 .map 一致)。**这套机制很先进**。
**缺口**:
1. `mem-numbers` **在 CI 上被跳过**(无 `.map`)——意味着**CI 不能守体积门禁**;
2. `configTOTAL_HEAP_SIZE`(48KB)无运行时余量告警——实测 `free=8,784 / min_ever=4,848 B`,
**min_ever 已低于 5KB**,若新增任务可能触发分配失败;
3. 上游 S-1(defaultTask 静态最坏链 2872B/4096,余量 29.9% 差 4.8B 达标)**仍未处理**。
**建议**:见 §4.5(S9)——**堆余量低水位告警**(复用既有 `stack_probe_poll` 模式)。
### 2.7 改进点 A-6:`configASSERT` 已改造但覆盖不足 🟡
**现状**(N-03a 已修):`configASSERT` 已从"关中断死循环"改为
`vApplicationAssertFailed(__FILE__, __LINE__)` → 打印后复位。**这是很好的改进**。
**缺口**:`configASSERT` **只覆盖 FreeRTOS 内部断言**。
项目自研代码**无统一断言宏**——例如 `net_socket.c` 的参数校验靠 `if + return -1`,
`cfg_file.c` 的边界靠手工检查。**没有轻量断言**意味着"不该发生的情况"静默通过。
**建议**:见 §4.6(S10)——引入**可选、可编译掉**的模块级断言宏
(生产构建零开销,测试构建启用),与 `test_common.h` 的 `TEST_CHECK` 呼应。
### 2.8 改进点 A-7:模拟器差距清单未成体系 🟡
**现状**:多处提及"模拟器差距清单 +1"(N-4 二期门禁),例如
"net_socket_sim.c 无法建模 pending"。但**没有一份汇总的"模拟器差距清单"文档**——
差距散落在各文档的只言片语中。
**风险**:模拟器回归通过 ≠ 真机通过,但**差异有多大、差在哪,无人能一眼说清**。
这会让"模拟器能否作为 L2 门禁"的判断失去依据。
**建议**:见 §4.7(S11)——建立**单一差距清单文档**。
---
## 3. 测试体系改进实施建议
### 3.1 S1:扩充 L0.5 主机侧单测(**最高性价比**)⭐⭐⭐⭐⭐
**目标**:把至少 2 个高价值纯逻辑模块纳入 L0.5,使"改这些模块"不再需要真机才能回归。
**实施路径**(完全复用既有范式,见 `看门狗单元测试说明.md`):
**S1-a:`sys_clock.c` 日期算术单测**(推荐首个,边界最密、零依赖)—— ✅ **已完成(2026-10-02)**
| 步骤 | 内容 |
|---|---|
| 1 | 确认 `sys_clock.c` 的硬件依赖面(`sd2506_get_time/set_time` + `HAL_GetTick` + 临界区宏 + `DBG_*`) |
| 2 | **全部依赖都是外部符号/宏 → 直接强符号覆盖桩(看门狗范式),无需平台层抽取**——比预期更干净,**生产代码零改动**(Zeller 修复除外,见下) |
| 3 | 新建 `test/host/sys_clock/run_unit_test.py`(声明式,≈50 行)+ `sys_clock_test.c` + `stub/` 4 个头 |
| 4 | 用例设计(**边界穷举**):闰年(2000 闰 / 2024 闰)· 月末(1/31、2/28、2/29、4/30)· 世纪边界 · 2000 最早年/2038 上限(`0x7FFFFFFF`)· **30 天惰性再基准化 + 亚秒余数结转 + 49.7 天 tick 回绕** · 星期(`rtc_calc_week`)16 个已知日期对照 · RTC 读失败/越界/写回失败三故障路径 |
| 5 | 挂 pre-commit 钩子 `sys-clock-unit-test`(改动 `sys_clock.c`/`sd2506.h` 时自动跑,秒级) |
| 6 | 在 `.gitea/workflows/ci.yml` 无需改动——`l05` job 遍历 `test/host/**/run_unit_test.py`,**自动纳入** |
**完成判据**:`python test/host/sys_clock/run_unit_test.py` 退出码 0(**100/100 PASS**);
`@build` 0/0(已验);CI 的 `l05-host-unit-tests` 日志出现该运行器且 PASS(待推送后复验)。
> ⚠️ **重大副产物**:本单测**首跑即抓出一处生产缺陷**——`rtc_calc_week()` /
> `unix_to_sd2506()` 的 Zeller 公式错误,使 **1970~2099 全部日期的 `week` 字段皆为
> 错值**(无常数偏移,错误率 ~78%;时间数值本身正确,故此前未暴露)。已修复并
> 经 Python `datetime` 独立核对 47,482 天零错误。详见
> [`../测试说明/系统时钟单元测试说明.md`](../测试说明/系统时钟单元测试说明.md) §5。
> 修复使 Flash 减少 112 B(删除重复内联公式),内存快照已同步
> (`check_mem_numbers.py --write`)。
**S1-b:`cfg_file.c` INI 解析器单测**(价值同样高,但需先确认抽象质量)
| 步骤 | 内容 |
|---|---|
| 1 | **利好**:`cfg_file` 已有 `cfg_store_alloc/free` 平台后端(`cfg_file_store.h`),MCU=FreeRTOS 堆、模拟器=宿主堆——**天然适格** |
| 2 | 确认文件 I/O 是否也已抽象(预计 `cfg_file` 的核心是"文本 ↔ 结构"纯变换,I/O 在后端) |
| 3 | 新建 `test/host/cfg_file/`,用桩后端提供内存虚拟文件 |
| 4 | 用例设计:INI 正常解析 · 空文件/缺段/重复键 · 键值转义 · **长度上限**(`CFG_FILE_MAX_LEN`)· **段名长度**(上游 E-16 相关)· 旧档 NCFG 迁移路径(`net_cfg_legacy`)· 畸形输入不崩溃 |
| 5 | 挂钩子 `cfg-file-unit-test` |
> ⚠️ **注意**:`cfg_file.c` 1250 行,**不建议一次全测**。优先覆盖**解析器 +
> 边界校验**(P0-1 堆分配路径已由真机验证,见上游 A1),不做"为覆盖率而测"。
**S1-c(可选):`sys_log` 记录编解码、`time_sync` 协议帧**
同上范式。优先级低于 S1-a/S1-b,可在 S1-a 落地后按剩余收益评估。
**⚠️ 关键约束**(重申 `测试开发标准流程.md` §4.2):
- 平台层抽取必须**零行为变化、零额外开销**(MCU 默认实现即原逻辑);
- 测试钩子用 `#ifdef <模块>_UNIT_TEST` 守卫,**MCU 构建零符号**;
- 单测**不能替代**板端验证——真机寄存器/复位原因/时序仍须 L1/L2。
### 3.2 S2:为 L0.5 建立"适格模块清单"(防遗漏)⭐⭐
**问题**:现在只有"看门狗"一个先例,**下一个该测谁**靠临时判断。
**建议**:在 `测试开发标准流程.md` 增补一节「L0.5 适格模块台账」:
| 模块 | 状态 | 备注 |
|---|---|---|
| `task_watchdog.c` | ✅ 已覆盖(39 用例) | 范式 |
| `sys_clock.c` | ✅ 已覆盖(20 用例 / 100 断言点,2026-10-02 S1-a) | **首测即抓出 Zeller 星期缺陷并修复**,见 `系统时钟单元测试说明.md` §5 |
| `cfg_file.c` | ⬜ 待做(S1-b) | 已有后端抽象 |
| `sys_log_mcu.c` | ⬜ 候选 | 记录编解码 |
| `time_sync.c` | ⬜ 候选 | 协议帧 |
| `rs485.c` | ⬜ 待功能实现 | 上游 B2 |
| `crc.c` / `ringbuf.c` | ✅ 已删除(2026-10-02 S8) | 孤儿文件,已确认无被引 |
| `tpafe5160.c` | ❌ 不适格 | 寄存器序列为主 |
| 其余驱动 | ❌ 不适格 | 需硬件时序 |
**完成判据**:文档有台账,新增模块时按表判断是否需 L0.5。
### 3.3 S3:CI 补产物存档与结果汇总 🟡
**目标**:让 CI 失败**可远程排障**、让测试规模**可趋势观察**。
**实施路径**:
| 步骤 | 内容 | 备注 |
|---|---|---|
| 1 | 两个 job 各加"失败时/总是"上传日志产物 | Gitea Actions 支持 `actions/upload-artifact`;**需确认本实例的 action 拉取可用**(内网可能不通 github,见 `CI配置指南.md` 的"内网 action"章节) |
| 2 | 若 action 不可用,**降级方案**:把关键输出 `tee` 到一个固定路径并 `::notice::` 打印摘要 | 零依赖 |
| 3 | L0.5 job 末尾打印**汇总行**(如 `L0.5: 1 runner(s), 39/39 PASS`) | 简单有效,便于一眼看规模 |
| 4 | (可选,后续)收集 `[STACK]` 水位与 `.map` 数字形成趋势 | 依赖 runner 有 Keil |
**完成判据**:CI 失败时,**不查 MySQL / 不解压 `actions_log`** 即可从 Gitea 页面
看到完整输出与摘要。
> **本文增量**:本项目 CI 是**自建 Gitea 内网实例**,"能否拉外层 action"是现实约束——
> 建议**优先做零依赖方案**(步骤 2/3),而不是先引入 `upload-artifact`。
### 3.4 S4:复核"文档用例 ↔ 代码断言"覆盖矩阵 🟡
**问题**:文档声称 91 个 TC、代码有 222 个断言点,但**两者是否一一对应无人校验**。
`测试开发标准流程.md` §2.3 铁律 3 要求"每条设计原则必须有对应用例",
但**没有机械校验**。
**建议**:新增一个**轻量校验脚本**(类似 `check_doc_links.py` 的形态):
```
scripts/check_test_traceability.py
输入:test/board/*_test_task.c 的 TC 注释 + docs/测试说明/*Test_Guide.md 的 TC 编号
输出:① 文档有、代码无(用例未实现) ② 代码有、文档无(用例未登记)
门禁:默认仅告警(warning),不阻断提交
```
**⚠️ 慎重**:**不建议**立即做成阻断型门禁——存量差异可能较多,
宜先**输出报告**,人工确认后再决定是否收紧。这与 `doc-links` 的演进路径一致。
**完成判据**:能一键列出双向差异清单;差异收敛后可考虑升为门禁。
### 3.5 S5:给 `test/` 代码加"宽松"静态检查 🟡
**目标**:抓 `test/board/` 6300+ 行固件测试代码的**真实缺陷**(未初始化/越界/空指针),
但不引入存量 style 噪音。
**实施路径**:
| 步骤 | 内容 |
|---|---|
| 1 | 先跑一次基线:`cppcheck --enable=warning --error-exitcode=1 test/`,**统计存量告警数** |
| 2 | 若存量可控(建议 <20 条),加 `test-cppcheck` 钩子,**只开 `warning`**(不含 performance/portability/style) |
| 3 | 若存量较多,先建 **suppress 清单**(明确每条的豁免理由),再挂钩子 |
| 4 | **务必**排除模拟器桩/第三方头(`test/host/watchdog/stub/` 等) |
**完成判据**:`test-cppcheck` 钩子在 CI 上 PASS;新增测试代码的越界/未初始化被自动拦截。
> **风险提示**:`test/` 代码豁免是**既有决策**(`测试开发标准流程.md` §3.3)。
> 本建议**不是推翻它**,而是"在不产生大量噪音的前提下,补上致命缺陷的检查"。
> 若基线告警过多,**保持现状亦可接受**——此时应把该决策写得更明确("理由:存量告警 N 条")。
### 3.6 S6:测试构建可编译性纳入验证(= 上游 P1-10 增量)🟡
**目标**:防止"测试套件长期编不过而无人知"(2026-08~09 CH395F 套件实例)。
**实施路径**(三选一,按成本递增):
| 方案 | 做法 | 成本 | 效果 |
|---|---|---|---|
| A(最轻) | `test/run_all_suites.py --dry-run` 基础上加 `--check-build`:仅改宏 + `@build`,**不烧录** | 低 | 每次回归窗口自动验证 4 套件可编译 |
| B | 新增 `@build-suites` 命令:循环 4 套件各跑一次 `@build`(含 RAM 溢出检查) | 中 | 一条命令全验 |
| C | CI 加 job(需 runner 有 Keil) | 高 | 自动守门,但当前 runner 无 Keil |
**建议**:**先做 A**(`run_all_suites.py` 已有改宏 + `try/finally` 还原的骨架,
加 `--check-build` 是自然扩展),成本最低、收益明确。
**注意**:RAM 溢出(P1-9:HW 全阶段固件超 8.7KB)意味着**即使能编译也可能链接失败**——
方案 A 的 `@build` 会自然暴露 `L6406E`,这正是想要的。
### 3.7 S7:真机回归增加"自检结果"判定 🟡
**目标**:让 `@test` 闭环不仅看串口标记,**还看设备的自检结论**。
**现状**:`sys_log` 引擎写 `/log/selftest.log`(`运行与自检记录设计说明.md`),
但 `verify_log.py` 只解析串口日志。
**实施路径**:
| 步骤 | 内容 |
|---|---|
| 1 | 确认 `selftest.log` 的记录格式(定长 64B,应有"项 / 结果 / 时间"字段) |
| 2 | `verify_log.py` 增加 `--require-selftest-pass`:要求串口日志中出现自检汇总
(或由 `serial_tool` 通过 FTP 取回 `selftest.log` 后校验) |
| 3 | 判定语义建议:**自检有 no-go 项 → FAIL**;有 warn 项 → 打印但 PASS |
**完成判据**:`@test` 能捕获"时钟芯片异常""存储降级"等自检失败,
而不是只捕获 `FAIL` 字符串。
> **注**:`sys_clock_init` 在 RTC 故障时返回 -1 并触发"时钟芯片异常"自检
> (`项目架构总览.md` §3)——这正是当前闭环**捕获不到**的典型场景。
---
## 4. 架构改进实施建议
### 4.1 S8(原 A-1):清理孤儿源文件 ⭐⭐⭐
**动作**:二选一,**必须做决策**(现状"留着不管"是最差选项)。
| 方案 | 适用 | 动作 |
|---|---|---|
| **删除**(推荐) | 确认无调用者且无预留计划 | 删 `App/util/crc.c`、`App/util/crc.h`、`App/util/ringbuf.c`、`App/util/ringbuf.h`;同步清理 `App/util/README.md` 的引用 |
| 保留并复用 | 有明确复用计划 | ① 让 `console_log.c` 改用 `ringbuf`(**需评估:会改动已稳定的日志路径,风险不低**)② 让 `net_cfg_legacy.c` 删掉自有 CRC 改用 `crc.c`(**收益小、动 legacy 代码**) |
**建议**:**删除**。理由:
- 上游 E-15 已倾向删除 `ringbuf`;
- `console_log` 的内联缓冲已经**稳定运行且经过事故验证**(单写者 + 丢弃计数),
为"复用"去改它是**负收益**;
- `crc.c` 的被复用场景(legacy 迁移)是一次性代码,不值得建立统一 CRC 模块。
**完成判据**:文件不存在;`App/util/README.md` 同步;`@build` 0/0(本就不参与构建,应无影响);
全库无 `crc.h` / `ringbuf.h` 引用(E-15 销项)。
### 4.2 S9(原 A-2):统一 GD5F 错误通道(= 上游 C2 骨架)🔴
见 §2.3 的四要素骨架。**实施顺序建议**:
1. **先建分类函数**(`wait_busy` 区分超时/真故障)——这是所有下游判断的基础;
2. **再修最危险的**:`bbt_scan` 失败**放弃持久化**(保护好副本)——数据安全优先;
3. **然后补 init 全链路检查 + 重试**;
4. **最后**回读 B0h 校验 ECC;
5. 全程在 `GD5F2GQ5UE_Trap_Records.md` 留痕,并**同步 `GD5F2GQ5UE_Test_Guide.md`**。
**完成判据**:E-10/E-42/E-53/E-54/E-56/E-57 全部销项;GD5F 套件回归 PASS。
### 4.3 S10(原 A-3):启动段可观测性 🟡
**先核查现状**:`app_main.c` 的 `app_main_init` 是否每步都有 `DBG_INFO`。
| 核查结果 | 动作 |
|---|---|
| 已每步有日志 | **直接销项**,仅在文档注明"启动段死机可通过最后一条日志定位" |
| 部分有日志 | 补齐缺失步骤的日志(**零风险改动**) |
| 无日志 | 补全(注意日志字符串**必须纯 ASCII**,`AGENTS.md` 铁律) |
**完成判据**:`app_main_init` 每一步都有可区分的日志行;
文档(`项目启动顺序管理.md` 或 `看门狗设计说明.md`)明确"启动段死机判读法"。
### 4.4 S11(原 A-5):堆余量低水位告警 🟡
**动机**:实测 `min_ever=4,848 B`(初始 48KB 堆),**余量已不到 10%**。
未来新增任务/缓冲时,**分配失败可能在深夜台架长跑时才暴露**。
**实施路径**(复用既有 `stack_probe_poll` 模式,`default_task.c` 内):
| 步骤 | 内容 |
|---|---|
| 1 | 用 `xPortGetMinimumEverFreeHeapSize()` 取历史最低水位 |
| 2 | 在 `defaultTask` 的低频循环(如 LED 闪烁节拍)里检查 |
| 3 | 低于阈值(建议 `4096 B`)时 `DBG_ERROR("[HEAP] low water: %u B")` + **写 syslog selftest** |
| 4 | 阈值设为编译期常量,便于按新基线调整 |
**成本**:约 15 行代码 + 1 个常量,**零额外缓冲**。
**完成判据**:台架长跑下能观察到堆低水位告警(若有);文档记录阈值与基线。
### 4.5 S12(原 A-6):模块级轻量断言宏 🟡
**动机**:`configASSERT` 只覆盖 FreeRTOS 内部;自研代码的"不该发生"靠散落的 `if`。
**设计要点**(**必须零生产开销**):
```c
/* App/util/app_assert.h —— 示意,非最终实现 */
#ifdef APP_ASSERT_ENABLE
#define APP_ASSERT(cond) do { if (!(cond)) { \
DBG_ERROR("[ASSERT] %s:%d", __FILE__, __LINE__); vApplicationAssertFailed(__FILE__, __LINE__); } \
} while (0)
#else
#define APP_ASSERT(cond) ((void)0) /* 生产构建:编译期消失,零符号零开销 */
#endif
```
**⚠️ 关键纪律**:
- **不得**在中断/高频繁路径使用(`DBG_ERROR` 有代价);
- **不得**替代正常的错误返回(断言是"内部不变量",不是"外部输入校验");
- 生产构建 `APP_ASSERT_ENABLE` **关闭**——与 `ENABLE_DESTRUCTIVE_TESTS` 同级策略。
**建议先在 1~2 处试点**(如 `net_socket.c` 的状态机不变量),验证收益后再推广。
**完成判据**:宏存在 + 生产构建 `@build` 0/0 且 `.map` 无新增符号;试点处有实际使用。
### 4.6 S13(原 A-7):建立模拟器差距清单文档 🟡
**动机**:模拟器回归是 L3 门禁,但"通过 ≠ 真机通过"。差异**散落各处**:
| 已知差距 | 出处 |
|---|---|
| `net_socket_sim.c` 无法建模 pending 机制 | N-4 二期门禁 |
| (其它) | 需系统性收集 |
**建议**:新建 `[计划] docs/UI设计/模拟器差距清单.md`(或放 `docs/测试说明/`),
按格式登记:
| # | 差距 | 模拟器行为 | 真机行为 | 影响 | 回归时是否需真机补测 |
|---|---|---|---|---|---|
**完成判据**:文档建立且至少收录已知差距;UI 回归流程文档引用它。
### 4.7 S14:CI 与体积门禁的关系澄清 🟡
**现状**:`mem-numbers` 在 CI 上被跳过(无 `.map`)。
**这导致一个认知风险**:看到 CI 全绿可能误以为"体积也守住了"。
**建议**:
1. **文档层面**:在 `CI配置指南.md` **显著位置**声明
"**CI 不守体积门禁**,改动内存结构后必须在本地跑 `ci_local.py` 或 `@build`";
2. **CI 层面(可选)**:在 job summary 打印
`::notice::mem-numbers skipped (no .map) -- size gate NOT enforced in CI`——
已在 workflow 有类似逻辑(`SKIP=mem-numbers`),**只需把跳过原因显式提示出来**。
**完成判据**:CI 日志中有明确的"体积门禁未执行"提示;文档有醒目声明。
---
## 5. 建议实施路线图
### 5.1 排期建议(与上游阶段 A/B/C/D 并行)
| 阶段 | 本文编号 | 内容 | 依赖 | 预估工作量 |
|---|---|---|---|---|
| **第 1 波**(零风险、高收益) | S8 | 清理孤儿源文件 | 无 | 小 | ✅ 2026-10-02 |
| | S1-a | `sys_clock` L0.5 单测 | 无 | 中 | ✅ 2026-10-02(**抓出并修复 Zeller 缺陷**) |
| | S3 | CI 产物存档(零依赖方案) | 无 | 小 | ⬜ |
| | S14 | CI/体积门禁澄清 | 无 | 小 | ⬜ |
| **第 2 波**(补测试能力) | S1-b | `cfg_file` L0.5 单测 | 无 | 中 |
| | S2 | L0.5 适格台账 | S1-a 经验 | 小 |
| | S10 | 启动段可观测性核查 | 无 | 小 |
| | S6 | 测试构建可编译性(方案 A) | 无 | 小 |
| **第 3 波**(架构加固) | S7 | 真机回归加自检判定 | 无 | 中 |
| | S11 | 堆低水位告警 | 无 | 小 |
| | S9 | GD5F 错误通道(= 上游 C2) | 无 | 大 |
| | S5 | `test/` 宽松静态检查 | 先跑基线 | 小~中 |
| | S4 | 用例追溯脚本 | 无 | 中 |
| **第 4 波**(按需) | S12 | 模块级断言宏 | 试点验证 | 小(试点) |
| | S13 | 模拟器差距清单 | 无 | 小 |
### 5.2 与上游任务的关系
| 关系 | 说明 |
|---|---|
| **独立可做** | S1/S2/S3/S4/S5/S8/S10/S11/S13/S14——**不依赖产品定义、不依赖上游阶段** |
| **与上游 A 并行** | S7(自检判定)与 A1(P0-1 真机验证)**同一轮台架窗口**做最划算 |
| **是上游 C 的骨架** | S9 就是上游 C2;§2.3 的四要素可直接作为 C2 的实施框架 |
| **是上游 B 的前置** | S1-b(`cfg_file` 单测经验)+ §2.5 的测试可验证性约束,**应在 B1/B2 动手前明确** |
---
## 6. 一句话总结
> **测试体系的短板不在"广度"而在"中间层"**——L0.5 只有 1 个模块,
> 而 `sys_clock.c`(479 行日期算术)与 `cfg_file.c`(1250 行解析器)
> 这类"边界密集、零硬件、秒级可测"的模块**完全没有主机侧回归**,
> 使改动反馈被迫走"30s~2min 的真机链路"。**补齐 S1 是投入产出比最高的一步**。
>
> **架构的主缺口是"功能不对称"**(网络极成熟 vs 采集链路为空桩)与
> **"纵深不对称"**(上层防御完备 vs NAND 错误通道散乱);
> 两者上游均已识别,本文补充了**测试可验证性约束**与**四要素实施骨架**。
>
> 另有 **3 项低风险卫生改进**可立即执行:清理孤儿文件(S8)、
> CI 产物存档(S3)、CI 体积门禁澄清(S14)。
---
## 变更记录
| 日期 | 变更 |
|---|---|
| 2026-10-02 | 实施记录(第 1 波前半):**S8 已完成**(删除孤儿 `crc.c/h`、`ringbuf.c/h`,重跑引用关系图);**S1-a 已完成**(`sys_clock` L0.5 单测 20 用例 / 100 断言点 + 钩子 `sys-clock-unit-test`;**首跑抓出 Zeller 星期缺陷并修复**,Flash −112 B)。§3.1 S1-a 步骤表与 §5.1 路线图已更新状态 |
| 2026-10-02 | 初版:测试体系量化盘点(4 套件 222 断言点 / L0.5 仅 1 模块 / 15 钩子)+ 5 个测试结构性问题 + 7 个架构改进点 + 14 条实施建议(S1~S14)+ 4 波路线图。所有论断基于当日源码实测(孤儿文件核查、断言点统计、CI workflow 走读)。**纯建议,未改任何代码** |
+2 -2
View File
@@ -24,7 +24,7 @@
| ① 业务层 | `Drivers/BSP/NETCFG/` | 顶层共享配置(被 UI 与基础服务共用) | `net_cfg` |
| ② UI 层 | `Drivers/BSP/UI/` | LVGL 界面(与 PC 模拟器共用源码) | `ui_port` `ui_menu` `ui_menu_view` `ui_menu_tab` `ui_content_page` `ui_page_*` `ui_net_sink` `ui_font_cn_12` `ui_deco` |
| ③ 基础服务层 | `App/`、`Drivers/BSP/NET/lftpd/` | 应用侧服务与编排 | `app_main` `sys_events` `app_cfg` `task/*` `lftpd` `time_sync` |
| ④ 中间件层 | `Drivers/BSP/NET/`、`Lib/`、`App/util/` | 通用库 / 协议栈 | `net_socket` `net_select` `FatFs` `nand_ftl(dhara)` `LVGL` `sys_clock` `util(crc·ringbuf)` |
| ④ 中间件层 | `Drivers/BSP/NET/`、`Lib/`、`App/util/` | 通用库 / 协议栈 | `net_socket` `net_select` `FatFs` `nand_ftl(dhara)` `LVGL` `sys_clock` |
| ⑤ 驱动层 | `Drivers/BSP/{CH395F,GD5F2GQ5UE,TPAFE5160,SD2506,RS485}/` | 芯片寄存器 / 时序 / 中断 | `ch395f` `gd5f2gq5ue` `tpafe5160` `sd2506` `rs485` |
| ⑥ 内核 / HAL | `Middlewares/`、`Drivers/STM32F4xx_HAL_Driver/`、`Drivers/CMSIS/` | 任务调度、外设 HAL、启动 | FreeRTOS V10.3.1 · STM32 HAL · CMSIS |
| ⑦ 硬件 | — | STM32F407ZGTx @168MHz | SPI1 / SPI2 / I2C1 / UART5 / USART1 / DMA / GPIO / TIM7 |
@@ -258,7 +258,7 @@ STM32F4-Base/
│ ├── cfg/ 配置引擎:cfg_file(静态态单任务收敛于 defaultTask)、store(MCU=FreeRTOS 堆)
│ ├── sync/ time_sync(TCP 对时服务)
│ ├── protocol/ 协议编解码(MODBUS 等)与帧解析
│ └── util/ crc(CRC-16/8)、ringbuf、sys_clock(墙钟)
│ └── util/ sys_clock(墙钟)、task_watchdog(IWDG 判定状态机)
├── Drivers/BSP/ 纯手工驱动/服务(不受 CubeMX 保护)
│ ├── CH395F/ 以太网 MAC+PHY
│ ├── GD5F2GQ5UE/ NAND + dhara FTL
@@ -0,0 +1,264 @@
# 系统时钟单元测试说明(主机侧)
> **模块**:System Clock(墙钟时间维护,`App/util/sys_clock.c`)
> **测试类型**:**主机侧纯逻辑单元测试**(L0.5 层)——在 PC 上离线验证时间换算与
> 墙钟基准逻辑,零硬件、零烧录、秒级、不占 MCU 的 RAM。
> **状态**:✅ 已落地(20 用例 / 100 断言点,`@build` 0/0,已挂 pre-commit 钩子)
> **日期**:2026-10-02
> **关联文档**:
> [`../../AGENTS.md`](../../AGENTS.md)(平台约束)、
> [`测试开发标准流程.md`](测试开发标准流程.md)(测试分层与门禁总览)、
> [`看门狗单元测试说明.md`](看门狗单元测试说明.md)(同层范本)、
> [`../审查记录/测试与架构改进建议.md`](../审查记录/测试与架构改进建议.md)(S1-a)
---
## 1. 概述
### 1.1 为什么单独做主机侧单测
`sys_clock.c` 提供墙钟时间的**换算**与**维持**两类逻辑:
| 半边 | 内容 | 可测性 |
|---|---|---|
| **纯逻辑**(被测) | Unix↔日历换算(闰年 / 月末 / 世纪 / 2038 上限)、星期(Zeller)、"30 天惰性再基准化"(防 2^32 ms ≈ 49.7 天 tick 回绕)、RTC 故障降级 | 纯逻辑,与硬件无关 → **可在 PC 上位穷举** |
| **硬件副作用** | `HAL_GetTick()`、`sd2506_get_time/set_time`(I2C RTC)、`taskENTER/EXIT_CRITICAL`、`DBG_*` | 需真机或桩 |
板端套件(`test/board/`)**无法穷举边界**,例如:
- 闰年规则中 2000(世纪闰)/ 1900(非闰)/ 2024(普通闰)的差异;
- 2038-01-19 03:14:07 = `0x7FFFFFFF` 的上限;
- 星期 Zeller 公式在 1/2 月(需上调为上年 13/14 月)与 3~12 月的分野;
- **惰性再基准化**:30 天阈值触发前后的差值窗口行为、亚秒余数结转、跨 49.7 天 tick 回绕;
- RTC 读失败 / 越界 / 写回失败三种故障路径的降级语义。
这些用主机侧单测一次性钉死,且**不占 MCU 的 RAM**(本项目 RAM 已 95%,见
`docs/内存管理/`)。
> **本单测首次落地即抓到一处生产缺陷**——`rtc_calc_week()` / `unix_to_sd2506()`
> 的 Zeller 公式错误,导致 **1970~2099 全部日期的星期皆为错值**。详见 §5。
### 1.2 被测对象与桩(**不改生产代码**)
**关键设计**:`sys_clock.c` 的全部硬件依赖都是**外部符号**(`HAL_GetTick` /
`sd2506_get_time` / `sd2506_set_time`)或**宏**(`taskENTER/EXIT_CRITICAL` /
`DBG_*`),因此测试单元只需提供**同名强符号桩**即可编译链接——**无需在
`sys_clock.c` 内加任何 `#ifdef` 钩子**(比看门狗更"零侵入")。
```
test/host/sys_clock/sys_clock_test.c
├── #include "main.h" → stub/main.h (HAL_GetTick 声明)
├── #include "FreeRTOS.h" → stub/FreeRTOS.h (临界区宏 → 空操作)
├── #include "task.h" → stub/task.h (经 FreeRTOS.h)
├── #include "dbg_log.h" → stub/dbg_log.h (DBG_* → 计数)
├── #include "sd2506.h" → 真实头(其内部 #include "main.h" 命中 stub)
├── #include "sys_clock.h" → 真实头(被测 API 原型)
└── #include "sys_clock.c" ← 生产源文件(App/util/),原样纳入
```
| 硬件符号 | MCU 端 | 主机侧桩 |
|---|---|---|
| `HAL_GetTick()` | TIM7 中断累加 | 返回单测可控的 `s_tick` |
| `sd2506_get_time(p)` | I2C 读 7 字节 | 从 **阴影 RTC** `s_rtc` 复制;可注入返回码 `s_rtc_read_rc` |
| `sd2506_set_time(p)` | I2C 写 7 字节 | 写入阴影 RTC;可注入返回码 `s_rtc_write_rc`;计次 `s_rtc_set_calls` |
| `taskENTER/EXIT_CRITICAL` | 关中断 | 空操作(主机单线程,复核逻辑照跑) |
| `DBG_ERROR` | USART + RTC 时间戳 | 计数 `g_sclk_err_count`(断言故障路径打了错误日志) |
> **生产端零增量**:`sys_clock.c` 未因本单测改动一行(Zeller 修正是**缺陷修复**,
> 见 §5,与测试基建无关)。
### 1.3 测试环境
| 依赖 | 版本 / 说明 |
|---|---|
| 编译器 | 本机 MSVC(VS2019/2022/2026 任一),由 `run_unit_test.py` 自动探测 |
| Python | 仓库 `.venv` 或任意 python3(**纯标准库**) |
| 硬件 | 无(不需要开发板 / 串口 / ST-LINK) |
工具链探测 / `INCLUDE`-`LIB` 拼接 / 编译运行 / 退出码判定全部委托通用 harness
[`../../test/host/common/unit_test_harness.py`](../../test/host/common/unit_test_harness.py),
本模块 `run_unit_test.py` 只**声明** `TestBuild`(源文件 / 桩目录 / 汇总行标记)。
### 1.4 运行方式
```powershell
# 仓库根目录(统一用 .venv)
& '.venv\Scripts\python.exe' test/host/sys_clock/run_unit_test.py
```
退出码:`0`=PASS / `1`=测试失败 / `2`=环境错误(找不到编译器 / 编译失败 / 无汇总行)。
### 1.5 回归接入
| 项 | 说明 |
|---|---|
| **触发范围** | `App/util/sys_clock.c` / `sys_clock.h`、`Drivers/BSP/SD2506/sd2506.h`,或 `test/host/sys_clock/**`、`test/host/common/unit_test_harness.py` 变动 |
| **门禁层级** | **L0.5 层**(介于 L0 静态门禁与 L1 板端套件之间):主机侧、秒级、纯逻辑 |
| **pre-commit 钩子** | `sys-clock-unit-test`(本地钩子,`.pre-commit-config.yaml` §7b) |
| **汇总行** | `=== System Clock Tests: N/N PASSED (failed=0) ===` |
| **与板端测试的关系** | 互补:本单测保证**换算与基准逻辑**正确;板端 `SD2506_Test_Guide.md` 保证**真机 I2C 读写链路**正确 |
---
## 2. 测试用例
> 命名 `TC-CLK-NNN`(CLK = Clock)。全部在 `sys_clock_test.c` 内,主机侧运行。
| 用例 | 断言 | 验证的设计点 |
|---|---|---|
| **TC-CLK-000** | 未初始化时 `get()==UINT32_MAX`、`get_time()==-1`、`get_str()` 输出占位串 | "未初始化"哨兵语义(**必须最先跑**) |
| **TC-CLK-001** | RTC 合法 → `init()==0`,基准 = 换算 Unix 秒;不写回 RTC | 正常初始化路径 |
| **TC-CLK-002** | 2024-02-29 / 2000-02-29(世纪闰)换算正确;2/29→3/1 间隔恰 86400 s | 闰年规则(含世纪闰) |
| **TC-CLK-003** | 1/31、2/28、4/30 精确值;平年 2/28→3/1 间隔 86400 s | 月末边界 |
| **TC-CLK-004** | 2000-01-01 换算;2038-01-19 03:14:07 → `0x7FFFFFFF`;`set(0)` → 1970-01-01 | 世纪边界 / uint32 上限 |
| **TC-CLK-005** | RTC 读失败 → `init()==-1`,降级为编译期默认时间,打 `DBG_ERROR` | RTC 读故障降级 |
| **TC-CLK-006** | 年/月/日越界 → 自愈写回默认时间,`init()==0` | 越界自愈(启用默认值时) |
| **TC-CLK-020** | 越界 + 写回失败 → 降级默认时间,`init()==-1` | 组合故障降级 |
| **TC-CLK-007** | 越界 → 写回 RTC 一次,阴影 RTC 被修正,`init()==0` | 自愈写回 + 复验 |
| **TC-CLK-008** | 999ms 不变 / 1000ms +1s / 再 +2500ms 共 +3s | 正常推进 + 亚秒截断 |
| **TC-CLK-009** | +29 天线性推进(未达 30 天阈值,不触发再基准化) | 阈值下不触发 |
| **TC-CLK-010** | 30 天 + 1500ms → 值无损(+30 天 1 秒);再 +500ms 补足 1 秒 | **惰性再基准化** + 余数结转 |
| **TC-CLK-011** | 基准近 2^32,推进跨回绕 0x2000ms → +8s | tick 回绕(无符号差)安全 |
| **TC-CLK-012** | 10 天步进 ×9(累计 90 天,含 3 轮再基准)零漂移 | 长期运行无漂移 |
| **TC-CLK-013** | `get_str` 格式化 + 零点补零;未初始化占位串 | 字符串接口 |
| **TC-CLK-014** | 2026-10-02 08:09:10 → 年月日时分秒 + `week==5`(周五) | 日历字段 + 星期 |
| **TC-CLK-015** | `get_time(NULL) == -1` | NULL 参数防御 |
| **TC-CLK-016** | `set` 更新基准 + 写回 RTC;写失败仍更新基准 + 打错误 | set 两条路径 |
| **TC-CLK-017** | `set(..., NULL)` 不碰 RTC | 可选 RTC 写回 |
| **TC-CLK-018** | `set_unix` 写回 RTC,日历字段 + `week` 正确 | Unix 时间戳校时 |
| **TC-CLK-019** | 16 个已知日期(跨 1~12 月 / 世纪闰 / 闰日 / 范围首尾)星期对照 | **Zeller 全域正确性** |
**当前结果**:100/100 PASSED(`failed=0`)。
---
## 3. 如何扩展用例
1. 在 `sys_clock_test.c` 加一个 `static void tc_0NN_xxx(void)`;
2. 用 `rtc_set(y,mo,d,h,mi,s)` 设置阴影 RTC(`init` 用例);
3. 用 `base_at(unix_secs, tick)` 建立确定基准(`get` 用例,**无 RTC 副作用**);
4. `tick_advance(ms)` 推进时间,`sys_clock_get()` / `get_time()` / `get_str()` 观测;
5. `CHECK(cond, "描述")` / `CHECK_EQ_U32(got, want, "描述")` 断言;
6. 在 `main()` 里注册。
> ⚠ **别把新用例插到 `tc_000` 之前**:`tc_000` 依赖"刚上电"的模块静态态
> (`s_base_unix_secs == UINT32_MAX`),一旦有任何 `init`/`set` 先跑过,它就失效。
>
> ⚠ **惰性再基准化的"正确用法"**:差值窗口必须恒 < 30 天。一次 `tick_advance`
> 推进超过 ~49.7 天会让 `uint32_t diff` 溢出混叠——那是**调用方违约**(不是模块
> 缺陷),测试应按 <30 天步长周期性调用 `get()`。见 TC-CLK-012 的注释。
---
## 4. 平台与隔离机制
### 4.1 为什么不需要 `#ifdef` 测试钩子(与看门狗的差异)
看门狗的平台层是**内联 `__weak` 函数**(`wd_platform_feed/start/tick_ms`),
测试时必须用 `#ifndef TASK_WATCHDOG_UNIT_TEST` 把默认实现排除,否则与桩重名。
`sys_clock.c` 则**直接调用外部符号**(`HAL_GetTick()`、`sd2506_get_time()`),
这些符号在主机侧本来就不存在(只有声明),测试单元给出定义即可——**无需任何
生产代码钩子**。这是更干净的做法,也是本单测"零侵入生产代码"的原因。
### 4.2 隔离:如何在不改生产代码的前提下复位模块静态态
`s_base_tick` / `s_base_unix_secs` 是文件静态、无公开复位接口。隔离手段:
| 目标状态 | 手段 |
|---|---|
| **未初始化**(`UINT32_MAX`) | 只能靠 **进程首用例**(TC-CLK-000,在任何 `init`/`set` 之前)观测 |
| **确定的已初始化状态** | `sys_clock_set(unix_secs, NULL)` —— 更新基准且**不碰 RTC**,是理想的隔离原语 |
| **RTC 阴影态** | `rtc_set(...)` + `clear_obs()` 直接重置阴影寄存器与观测计数 |
---
## 5. 由本单测发现并修复的生产缺陷(Zeller 星期公式)
### 5.1 现象
首次运行 TC-CLK-014/019 时,`2026-10-02`(真实为周五)返回 `week == 4`;
TC-CLK-019 的 16 个已知日期中,**仅 `2038-01-19` 正确**,其余 9 个(3~12 月)
全错。
### 5.2 根因
`rtc_calc_week()` / `unix_to_sd2506()` 内联的所谓 "Zeller 变种"公式为:
```c
(wy % 100 + wy / 100 / 4 - wy / 100 / 15) + 26*(wm+1)/10 + d % 100) % 7
```
问题有二:
1. **世纪项错误**:标准 Zeller(Gregorian)的世纪项是 `⌊K/4⌋ + ⌊J/4⌋ + 5J`
(`K = wy%100`,`J = wy/100`)。原式写成 `wy/100/4 - wy/100/15`,**丢失了
`K/4` 与主导性的 `5J` 项**(`-J/15` 与 `5J` 毫无关系)。
2. **1/2 月分支用了错的月份基数**:原式对 `wm <= 2`(即 1/2 月)做 `wm += 12`,
但若传入的 `wm` 不是 1-based 的月,就无法得到 13/14。`unix_to_sd2506()`
更是**直接传入 0-based 的 `m`**(循环下标),使公式基准再偏一位。
实测:**1970~2099 全部日期的星期皆为错值**(无常数偏移,错误率约 78%)。
### 5.3 影响面
- `sys_clock_get_time()->week` → 写入 SD2506 RTC 的 `03H` 星期寄存器(**错值**);
- `rtc_calc_week()` 亦被 `sys_clock_init()` 用于构造默认时间的 `week`;
- `App/app/app_main.c:170` 的自检日志会打印错的星期。
> **不影响的**:
> 1. **时间数值**(时/分/秒/年/月/日 / Unix 秒)换算本身正确——只有 `week` 字段错;
> 2. **FatFs 文件时间戳**:`Lib/FatFs/ffsystem.c:get_fattime()` 只取
> 年/月/日/时/分/秒(FAT 格式**无星期字段**),**不使用 `t.week`**——故文件 mtime
> 不受影响;
> 3. `sys_log` 运行时日志只用 `sys_clock_get()`(Unix 秒),与星期无关。
>
> 综上:该缺陷**只污染 RTC 的星期寄存器与一条自检日志**,功能影响可控——这正是
> 它此前从未被台架功能测试发现的原因(功能测试不校验星期)。
### 5.4 修复
统一为教科书 Zeller,并**收敛到单一函数**(消除两处重复实现再次发散的风险):
```c
static uint8_t rtc_calc_week(uint16_t y, uint8_t m, uint8_t d) { /* m: 1-based */
uint32_t wy = y, wm = m;
if (wm <= 2u) { wy--; wm += 12u; } /* 1/2 月 → 上年 13/14 月 */
uint32_t K = wy % 100u, J = wy / 100u;
uint32_t h = (uint32_t)(d + (13u*(wm+1u))/5u + K + K/4u + J/4u + 5u*J) % 7u;
return (uint8_t)((h + 6u) % 7u); /* h:0=Sat → 0=Sun 约定 */
}
```
`unix_to_sd2506()` 改为调用 `rtc_calc_week((uint16_t)y, (uint8_t)(m+1u), (uint8_t)d)`
(传 1-based 月)。
> 注:`5J ≡ -2J (mod 7)`,取 `5J` 保证各项非负(无符号 `%` 安全)。
> 修复后经 Python `datetime` 独立核对 **1970~2099 全部 47,482 天零错误**;
> `@build` 0 错误 0 警告。
### 5.5 校验数据来源
TC-CLK-019 的期望星期由 Python `datetime.date(...).weekday()` 独立生成
(`(weekday()+1) % 7` → 0=周日),**与被测实现算法正交**,避免"用错的算法
验证错的实现"。
---
## 6. 与板端验证的分工
| 层 | 覆盖 | 状态 |
|---|---|---|
| **本单测(L0.5)** | 换算逻辑 / 星期 / 再基准化 / 故障降级 | ✅ 已落地(100 断言点) |
| 板端(L1/L2) | 真机 SD2506 I2C 读写、掉电保持、与 PC 对时联动 | 见 [`SD2506_Test_Guide.md`](SD2506_Test_Guide.md) |
> 本单测**不替代**板端验证:I2C 时序、BCD 编解码、掉电保持、温度读等真机行为,
> 仍需 `@test` 台架闭环确认。
---
## 变更记录
| 日期 | 变更 |
|---|---|
| 2026-10-02 | 创建:`sys_clock.c` 主机侧单元测试(20 用例 / 100 断言点)+ 强符号桩(零侵入生产代码)+ pre-commit 钩子 `sys-clock-unit-test`。**首次落地即抓出 Zeller 星期公式缺陷(1970~2099 全部日期星期皆错)并修复**,`@build` 0/0 |