测试:补启动段可观测性与模拟器差距清单,run_all_suites 加只编译模式(S10+S13+S6)
This commit is contained in:
@@ -52,6 +52,7 @@ STM32F407ZGTx @168MHz(HSE 8MHz → PLL M=4/N=168/P=2);**仅 Keil MDK-ARM v
|
||||
- **自研代码禁用 C 库堆分配**(2026-09-18,P0-1):禁止 `malloc/calloc/realloc/strdup/free`——本机工具链 MicroLIB 的 C 堆由 `startup_stm32f407xx.s` 的 `Heap_Size EQU 0x200` 限定为 **512 B**,KB 级分配必然返回 NULL 且**模拟器(宿主 CRT 堆)完全正常、照不出来**。需要动态内存只用 `pvPortMalloc/vPortFree`(FreeRTOS 堆 `configTOTAL_HEAP_SIZE`),或像配置引擎那样把分配收进平台后端(`cfg_store_alloc/free/heap_free`:MCU=FreeRTOS 堆、模拟器=宿主堆,共享核心零分配符号);分配失败必须有 `FAIL` 日志 + 自检上报,暂态峰值要写编译期预算
|
||||
- **FatFs 打开模式陷阱(2026-09-20 台架实测,`Lib/FatFs` 移植版)**:`FA_OPEN_APPEND`(0x30) = `FA_OPEN_ALWAYS|FA_SEEKEND`,**不含 `FA_WRITE`(0x02)**——不带写位打开的文件 `f_write` 恒返回 `FR_DENIED`(7)(/log 写失败真根因);追加写必须用 `FA_WRITE|FA_OPEN_APPEND`。该移植的 `f_write` 簇分配失败时返回 `FR_OK`+短写(无错误码),判失败必须校验写回字节数。标志位为旧式编号(`FA_READ`=0x01 / `FA_WRITE`=0x02 / `FA_CREATE_NEW`=0x04 / `FA_CREATE_ALWAYS`=0x08 / `FA_OPEN_ALWAYS`=0x10 / `FA_OPEN_APPEND`=0x30 / `FA_SEEKEND`=0x20(ff.c 内部定义)),与上游 FatFs R0.14 不同,勿照抄上游代码示例
|
||||
- **FatFs 文件名约束(2026-09-20 台架实证,`.fschk` 无限重格式化事故根因)**:本移植 `Lib/FatFs/ffconf.h` 为 `FF_USE_LFN=0`,**只支持严格 8.3 短名**——主名≤8 字符、扩展≤3 字符、**不得以点开头**(`.fschk` 会被 `ff.c create_short` 把点后当扩展域解析超限 → `f_open` 恒返 `FR_INVALID_NAME`(6),纯 RAM 名字判定、不碰盘)。ff.c `create_short` 全部拒绝点已加 DEBUG 打印:`[ERR][FATFS] SFN reject (原因) seg='名字片段' bad_ch=坏字符`,非法名字调用看开机日志即可定位。自愈类逻辑**严禁**把名字/参数类错误码(`FR_INVALID_NAME` 等)归入卷损坏触发集(整卷重格式化不可逆)
|
||||
- **`DBG_*` 每条日志约 90 B Flash + 192 B 栈**(2026-10-02 S10 实测):`dbg_log.h` 的 `DBG_INFO/ERROR/DEBUG` 在**每个调用点内联** `char __con_line[DBG_CON_LINE_MAX]`(`console_log.h` 定义 **192 B**)+ 完整 `snprintf` 调用序列 → 单条约 **90 B Flash**(字符串常量另计)。给新模块接入日志时按"条数 × 90 B"估算 Flash 代价(实测 `sd2506_init` 加 6 条 = +576 B;`app_main` 加 3 条 = +180 B);**深调用链模块另需核算栈**(每条日志在栈上多占 192 B,会进 `stack_report.py` 的最坏链)。故**日志不是免费的**——已覆盖的步骤不要再加"进入前/完成后"重复锚点,只在真正黑洞(原先静默返回)处补
|
||||
|
||||
<!-- TASK_TABLE:BEGIN generated by scripts/gen_task_table.py -->
|
||||
| Task | 栈 (words) | 优先级 |
|
||||
|
||||
+13
-2
@@ -100,6 +100,10 @@ int app_main_init(void) {
|
||||
|
||||
/* 日志引擎初始化(创建待发队列;须先于任何 syslog_post) */
|
||||
(void)syslog_mcu_init();
|
||||
/* 启动锚点(2026-10-02 S10):本函数在内核启动前执行、不创建任何 RTOS 对象,
|
||||
* 恒成功且无副作用;本行是"启动进度轨迹"的起点——之后的每一步都有结果日志,
|
||||
* 故某次启动卡死时,最后一行日志即可指向卡住的步骤。 */
|
||||
DBG_INFO("boot: syslog init OK");
|
||||
|
||||
/* 0) 复位原因上报(锁存值由 Src/main.c 在 HAL_Init() 后立即采得)。
|
||||
* 放在日志引擎之后、业务初始化之前:任何一次非上电复位都值得在
|
||||
@@ -120,8 +124,15 @@ int app_main_init(void) {
|
||||
* 1) 时间子系统初始化(尽早完成,后续所有日志带时间戳)
|
||||
* sd2506_init() 从 RTC 读取初始时间
|
||||
* sys_clock_init() 将 RTC 时间转为 Unix epoch,建立墙钟基准
|
||||
* S10(2026-10-02):sd2506_init 原先所有失败路径静默返回,是启动段
|
||||
* 唯一无日志的初始化;现内部已逐路径打 DBG_ERROR,此处再补一行
|
||||
* 带错误码的结果回执,使 I2C 不通时能看到"在哪一步、什么错"。
|
||||
*/
|
||||
sd2506_init();
|
||||
{
|
||||
int rtc_ret = sd2506_init();
|
||||
DBG_INFO("SD2506 init: %s (ret=%d)",
|
||||
(rtc_ret == SD2506_OK) ? "OK" : "FAILED", rtc_ret);
|
||||
}
|
||||
if (sys_clock_init() != 0) {
|
||||
syslog_post(SYSLOG_SELFTEST, "clock chip fault");
|
||||
}
|
||||
@@ -130,7 +141,7 @@ int app_main_init(void) {
|
||||
rs485_init(&g_rs485, &huart4, ST_DIR4_GPIO_Port, ST_DIR4_Pin);
|
||||
DBG_INFO("RS485 init: OK (UART4, DE=PA15)");
|
||||
|
||||
/* 3) GD5F 初始化测试 */
|
||||
/* 3) GD5F 初始化测试(BBT 全片扫描,启动段最耗时的一步) */
|
||||
int ret = gd5f2gq5ue_init();
|
||||
DBG_INFO("NAND init: %s",
|
||||
(ret == GD5F_OK) ? "OK" :
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
#include "sd2506.h"
|
||||
#include "i2c.h"
|
||||
|
||||
#define DBG_TAG "[RTC]"
|
||||
#include "dbg_log.h"
|
||||
|
||||
extern I2C_HandleTypeDef hi2c1;
|
||||
|
||||
/* ======================== 内部辅助函数 ======================== */
|
||||
@@ -171,6 +174,10 @@ int sd2506_init(void) {
|
||||
/* 验证 I2C 通信: 尝试读取 8 字节 ID */
|
||||
ret = sd2506_read_regs(SD2506_REG_ID_START, a_id, SD2506_ID_SIZE);
|
||||
if (ret != SD2506_OK) {
|
||||
/* 启动段可观测性(2026-10-02 S10):本函数原先所有失败路径均静默返回,
|
||||
* 而它是启动死机最可疑的源头(I2C1 硬件不通即卡此步且无任何输出)。
|
||||
* 日志字符串必须纯 ASCII(文件无 BOM,ARMCC 按 CP936 解码)。 */
|
||||
DBG_ERROR("sd2506_init: ID read FAIL (I2C err=%d)", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -182,6 +189,7 @@ int sd2506_init(void) {
|
||||
|
||||
ret = sd2506_read_reg(SD2506_REG_CHARGE, &charge_val);
|
||||
if (ret != SD2506_OK) {
|
||||
DBG_ERROR("sd2506_init: charge reg read FAIL (err=%d)", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -190,26 +198,35 @@ int sd2506_init(void) {
|
||||
|
||||
ret = sd2506_write_enable();
|
||||
if (ret != SD2506_OK) {
|
||||
DBG_ERROR("sd2506_init: write-enable FAIL (err=%d)", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = sd2506_write_reg(SD2506_REG_CHARGE, SD2506_CHARGE_INIT_VALUE);
|
||||
if (ret != SD2506_OK) {
|
||||
(void)sd2506_write_disable();
|
||||
DBG_ERROR("sd2506_init: charge reg write FAIL (err=%d)", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret_read = sd2506_read_reg(SD2506_REG_CHARGE, &charge_val);
|
||||
ret = sd2506_write_disable();
|
||||
if (ret != SD2506_OK) {
|
||||
DBG_ERROR("sd2506_init: write-disable FAIL (err=%d)", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ret_read != SD2506_OK || charge_val != SD2506_CHARGE_INIT_VALUE) {
|
||||
DBG_ERROR("sd2506_init: charge reg verify FAIL (read=%d val=0x%02X)",
|
||||
ret_read, charge_val);
|
||||
return SD2506_ERROR;
|
||||
}
|
||||
|
||||
DBG_INFO("sd2506_init: charge reg 18H reset to 0x%02X (was drifted)",
|
||||
SD2506_CHARGE_INIT_VALUE);
|
||||
}
|
||||
|
||||
DBG_INFO("sd2506_init: OK");
|
||||
return SD2506_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -92,6 +92,7 @@ docs/
|
||||
| [`自动化测试流程.md`](测试说明/自动化测试流程.md) | `@test` 闭环细节:命令 / 串口标记 / 失败模式 |
|
||||
| [`测试文档模板规范.md`](测试说明/测试文档模板规范.md) | 测试文档模板与命名规则(TM / TC / DP) |
|
||||
| [`链路延迟测试说明.md`](测试说明/链路延迟测试说明.md) | 网络链路时延分段定义、判定基线、执行流程 |
|
||||
| [`模拟器差距清单.md`](测试说明/模拟器差距清单.md) | **PC 模拟器 vs 真机差距登记册**(L3 门禁可信度依据;改动涉及网络/日志/硬件层前必查 §3 决策规则) |
|
||||
|
||||
**主机侧单元测试(L0.5)**
|
||||
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
|
||||
| 区域 | 用量 | 容量 | 占比 |
|
||||
|---|---|---|---|
|
||||
| ER_IROM1 | 261,196 B (255.07 KB) | 1,048,576 B (1024 KB) | 24.9% |
|
||||
| ER_IROM1 | 262,100 B (255.96 KB) | 1,048,576 B (1024 KB) | 25.0% |
|
||||
| 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 合计)** | **261,896 B (255.8 KB)** | 1,048,576 B (1024 KB) | **25.0%** |
|
||||
| **Flash (ROM 合计)** | **262,800 B (256.6 KB)** | 1,048,576 B (1024 KB) | **25.1%** |
|
||||
<!-- MEM_SNAPSHOT:END -->
|
||||
|
||||
各区的容量与常量来源:
|
||||
|
||||
+24
-10
@@ -601,6 +601,14 @@ scripts/check_test_traceability.py
|
||||
|
||||
**完成判据**:文档建立且至少收录已知差距;UI 回归流程文档引用它。
|
||||
|
||||
> ✅ **已完成(2026-10-02)**:落地为 [`../测试说明/模拟器差距清单.md`](../测试说明/模拟器差距清单.md)
|
||||
> (放 `docs/测试说明/`——它是跨子系统的测试资产,比 `UI设计/` 更贴切)。
|
||||
> 收录 **6 条网络**(N-1~N-6,含 pending 缺失、代次号恒 0、9 个 MCU 函数 sim 未实现)+
|
||||
> **4 条日志/配置**(L-1~L-4,含 mtime 粒度、堆后端差异)+ **7 条硬件专属**(H-1~H-7)+
|
||||
> **4 条"有意对齐"**(防误判)。新增 **§3 六条"必须回真机"决策规则**(把该建议从"登记册"
|
||||
> 提升为"可执行判据")。`simulator/README.md` §五与 `docs/README.md` §5 已交叉引用;
|
||||
> 登记约定写入 §4(新增 `sim/` 后端差异必须登记)。
|
||||
|
||||
### 4.7 S14:CI 与体积门禁的关系澄清 🟡
|
||||
|
||||
**现状**:`mem-numbers` 在 CI 上被跳过(无 `.map`)。
|
||||
@@ -625,19 +633,23 @@ scripts/check_test_traceability.py
|
||||
|---|---|---|---|---|
|
||||
| **第 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) | 无 | 大 |
|
||||
| | S3 | CI 产物存档(零依赖方案) | 无 | 小 | ✅ 2026-10-02 |
|
||||
| | S14 | CI/体积门禁澄清 | 无 | 小 | ✅ 2026-10-02 |
|
||||
| **第 2 波**(补测试能力) | S1-b | `cfg_file` L0.5 单测 | 无 | 中 | ✅ 2026-10-02(**抓出并修复外科手术保存死码 P0**) |
|
||||
| | S2 | L0.5 适格台账 | S1-a 经验 | 小 | ✅ 2026-10-02 |
|
||||
| | S10 | 启动段可观测性核查 | 无 | 小 | ✅ 2026-10-02(补 `sd2506_init` 6 条 + `app_main` 3 条;Flash +904 B) |
|
||||
| | S6 | 测试构建可编译性(方案 A) | 无 | 小 | 🔄 2026-10-02 |
|
||||
| **第 3 波**(架构加固) | S7 | 真机回归加自检判定 | 无 | 中 | ⏸ 判据需真机 |
|
||||
| | S11 | 堆低水位告警 | 无 | 小 | ⏸ 判据需真机 |
|
||||
| | S9 | GD5F 错误通道(= 上游 C2) | 无 | 大 | ⏸ 判据需真机 |
|
||||
| | S5 | `test/` 宽松静态检查 | 先跑基线 | 小~中 |
|
||||
| | S4 | 用例追溯脚本 | 无 | 中 |
|
||||
| **第 4 波**(按需) | S12 | 模块级断言宏 | 试点验证 | 小(试点) |
|
||||
| | S13 | 模拟器差距清单 | 无 | 小 |
|
||||
| | S13 | 模拟器差距清单 | 无 | 小 | ✅ 2026-10-02 |
|
||||
|
||||
> **"⏸ 判据需真机"说明**(2026-10-02 补):S7 / S11 / S9 的**代码可离线编写**,但其**完成判据**
|
||||
> 均要求真机观测(`@test` 捕获自检失败 / 台架长跑观察到堆告警 / GD5F 套件回归 PASS)。
|
||||
> 在无 MCU 的环境下实施会留下"已改未验证"的债,故押后至有实机的窗口。
|
||||
|
||||
### 5.2 与上游任务的关系
|
||||
|
||||
@@ -670,5 +682,7 @@ scripts/check_test_traceability.py
|
||||
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-10-02 | 实施记录(第 2 波):**S1-b 已完成**(`cfg_file` L0.5 单测 20 用例 / 139 断言点 + 钩子 `cfg-file-unit-test`;**首跑抓出"外科手术保存死码"P0**,`s_encode` 返回值 `==0` 恒假 → 已存在配置文件的保存静默不落盘,修 3 处为 `>=0`,记入 E-63);**S2 已完成**(§4.2.1 L0.5 适格台账);**S10 已完成**(补 `sd2506_init` 6 条 + `app_main` 3 条启动日志,Flash +904 B,`项目启动顺序管理.md` §6.8 新增"启动段死机判读法");**S13 已完成**(`docs/测试说明/模拟器差距清单.md`,6 网络 + 4 日志配置 + 7 硬件 + 4 有意对齐,含 §3 六条"必须回真机"规则)。§5.1 路线图状态已更新,S7/S11/S9 标注"判据需真机"押后 |
|
||||
| 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 | 实施记录(第 1 波后半):**S3 已完成**(CI 两 job 产物存档 + 汇总行,零依赖方案);**S14 已完成**(CI 体积门禁盲区显式提示 + `CI配置指南.md` §0.0 声明) |
|
||||
| 2026-10-02 | 初版:测试体系量化盘点(4 套件 222 断言点 / L0.5 仅 1 模块 / 15 钩子)+ 5 个测试结构性问题 + 7 个架构改进点 + 14 条实施建议(S1~S14)+ 4 波路线图。所有论断基于当日源码实测(孤儿文件核查、断言点统计、CI workflow 走读)。**纯建议,未改任何代码** |
|
||||
|
||||
@@ -214,6 +214,27 @@ if (sys_evtHandle != NULL) {
|
||||
- **修法**:所有**状态位**等待加 `osFlagsNoClear`(已改:`Src/freertos.c` 的 ftpTask、`App/task/ui_task.c`、`App/sync/time_sync.c`、`App/task/net_task.c`、`test/board/ch395f_test_task.c`);**命令位**(`CFG_SAVE`/`NET_RECONFIG`)保持消费型等待。
|
||||
- **工程化防线**:① 关键消费者加"启动超时告警"(如 ftpTask 10s 内未到 listen 就 `DBG_ERROR`);② 用 `scripts/stack_report.py` 看任务栈水位——**某任务水位异常低 = 它根本没干活**(本次正是靠 ftpTask 只有 156B 才发现)。
|
||||
|
||||
8. **启动段(`osKernelStart` 之前)死机的判读法(2026-10-02 S10 落地)**:`app_main_init` 全程运行在**内核启动前**——此时没有任务、没有看门狗保护,一旦卡死就是"无输出 + LED 不闪"(N-03b 的显式代价)。得益于 S10 补齐的可观测性,现在**每一步都有结果日志**,判读规则很简单:
|
||||
|
||||
> **开机日志的最后一行 = 卡住的那一步**(该步的"结果日志"没打出来)。
|
||||
|
||||
启动进度轨迹(按顺序,每条都是"结果"而非"进入前"锚点):
|
||||
|
||||
| # | 日志行 | 对应步骤 | 卡此步的含义 |
|
||||
|---|---|---|---|
|
||||
| 1 | `boot: syslog init OK` | `syslog_mcu_init()` | (纯 RAM,不会卡) |
|
||||
| 2 | `reset cause: ... (CSR=...)` | 复位原因翻译 | (纯计算,不会卡) |
|
||||
| 3 | `SD2506 init: OK/FAILED (ret=N)` | `sd2506_init()` | **I2C1 不通**(PB6/PB7、上拉、供电)——失败时其内部还会先打 `sd2506_init: ... FAIL` 指明具体寄存器与错误码 |
|
||||
| 4 | `[CLK] sys_clock_init: ...` | `sys_clock_init()` | RTC 读/写异常(降级时会打 `RTC abnormal, fallback to default time`) |
|
||||
| 5 | `RS485 init: OK (UART4, DE=PA15)` | `rs485_init()` | UART4 / DE 引脚配置 |
|
||||
| 6 | `NAND init: OK/...` | `gd5f2gq5ue_init()` | **BBT 全片扫描**——启动段最耗时的一步,卡住多为此处(SPI2 与片选) |
|
||||
| 7 | `ADC init: OK/TIMEOUT` | `tpafe5160_init()` | TPAFE5160 SPI 通信 |
|
||||
| 8 | `App init OK` | 全部完成 | 此后问题属任务/调度层,不再是启动段 |
|
||||
|
||||
- **若一行日志都没有**:问题在 `fault_handler_enable()` 之前(`HAL_Init` / 时钟树 / `system_stm32f4xx.c`),或 USART1 本身未初始化(`MX_USART1_UART_Init` 在 `main.c` 的 CubeMX 初始化段)。
|
||||
- **若卡在某步且无该步结果日志**:看该步**内部**是否还有更细的日志(如 `sd2506_init` 的 `ID read FAIL` / `charge reg read FAIL` 区分了 I2C 失败与寄存器校验失败)。
|
||||
- **配套手段**:`sd2506_init` / `gd5f2gq5ue_init` 内部的失败日志已细化到错误码;`sys_clock_init` 的降级路径写 syslog 自检(`clock chip fault`)。**注意日志本身有成本**——`DBG_*` 单条约 90 B Flash + 192 B 栈(见根目录 `AGENTS.md`),故只补真正静默的黑洞,不加冗余的"进入前/完成后"重复锚点。
|
||||
|
||||
---
|
||||
|
||||
## 7. 总结
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
# 模拟器差距清单(PC 模拟器 vs 真机)
|
||||
|
||||
> **用途**:把散落在各子系统文档里的"模拟器无法建模 X"汇成**单一事实源**,回答一个关键问题——
|
||||
> **模拟器回归通过 ≠ 真机通过,那么差在哪?差多少?哪些必须真机补测?**
|
||||
> **性质**:**登记册**(living document)。新增模拟器后端差异时必须在此登记一行,不得只写在别处。
|
||||
> **上游依据**:`审查记录/测试与架构改进建议.md` §2.8 / §4.6(S13)、`审查记录/下一步任务规划.md` §3.1(N-4 二期门禁)。
|
||||
> **相关**:`UI设计/LVGL模拟器部署与使用说明.md`(部署与使用)、`simulator/README.md`(目录与架构)、
|
||||
> `测试说明/测试开发标准流程.md`(L3 门禁在测试分层中的位置)。
|
||||
|
||||
---
|
||||
|
||||
## 0. 一句话结论
|
||||
|
||||
> 模拟器覆盖的是 **「纯逻辑 + 平台抽象之上」的那一层**:UI 场景/渲染/导航、配置引擎、日志记录格式、
|
||||
> `net_*` BSD API 之上的协议状态机。它**不覆盖**「硬件时序 / 中断 / 真实复位 / CH395F 与 NAND 芯片行为」。
|
||||
> 因此 **L3(模拟器回归)与 L1/L2(板端套件 / `@test` 闭环)互补而非替代**——见 §3 的"什么时候必须回真机"。
|
||||
|
||||
---
|
||||
|
||||
## 1. 模拟器覆盖范围(基线)
|
||||
|
||||
| 覆盖 | 说明 |
|
||||
|---|---|
|
||||
| UI 场景层 | `Drivers/BSP/UI/ui_menu.c` 等**同一份源码**,MCU 与 PC 共用(`simulator/README.md` §2.3) |
|
||||
| 渲染链路 | LVGL I1 渲染 → SDL 窗口(MCU 端为帧缓冲 → RemoDispBus) |
|
||||
| 配置文件引擎 | `App/cfg/cfg_file.c` 共享核心 + `cfg_file_store_sim.c`(宿主文件 `sim_config.ini`) |
|
||||
| `net_*` BSD API 之上的协议 | `ui_net_sink.c`(RemoDispBus 服务端,TCP 7003)与 `net_socket_sim.c`(Winsock 后端) |
|
||||
| 日志记录格式 | `sys_log` 定长记录编解码(`sim_syslog.c` 写宿主文件) |
|
||||
|
||||
**不覆盖**:CH395F 真实网络栈、GD5F NAND、SD2506 RTC、TPAFE5160 ADC、RS-485、IWDG、中断/时序相关行为。
|
||||
|
||||
---
|
||||
|
||||
## 2. 差距清单
|
||||
|
||||
> **列含义**:`模拟器行为` / `真机行为` 描述差异本身;`影响` 说明它会让什么 bug 逃过模拟器回归;
|
||||
> `回归补测` 给出"模拟器过 ≠ 真机过"时应补的真机手段(`@test` / 板端套件 / 目检)。
|
||||
|
||||
### 2.1 网络(`net_socket_sim.c` vs `net_socket.c`)
|
||||
|
||||
| # | 差距 | 模拟器行为 | 真机行为 | 影响 | 回归补测 |
|
||||
|---|---|---|---|---|---|
|
||||
| N-1 | **无 pending 机制** | `net_accept`/`net_send` 直接失败或阻塞于宿主 socket;无"挂起队列"概念 | 异步 TCP 状态机,`accept`/`send` 可处于 pending,由 `net_poll` 推进 | **E-06**(accept 轮询计时失真)、**E-20**(lftpd 控制通道 errno 分类)、**E-21**(timeSync errno 分级)等在模拟器上无法复现 | `@test` + FTP 套件;N-4 二期专门建模 |
|
||||
| N-2 | **代次号恒为 0** | `net_get_generation()` 恒返 0(无"网络重建"概念) | 每次 `net_stack_bring_up` / 重配置递增,各 TCP 服务据此重建监听 | "网络重建后服务是否重建监听" 的逻辑**在模拟器上恒走同一分支** | `@test` 网络重配置用例(UI 改 IP 免重启生效) |
|
||||
| N-3 | **`net_process_messages` / `net_recv_to` 等缺失** | 未实现(MCU 专属:CH395F 消息队列驱动、带超时接收) | 由 `netMsgQueue` 串行化 SPI 事务、支持毫秒级超时接收 | 依赖"带超时接收"与"消息泵"的上层逻辑在模拟器上**不可编译/不可运行** | 板端 CH395F / NET 套件 |
|
||||
| N-4 | **Socket 失效语义不同** | 以 `in_use` 判活(`net_socket_sim.c` 设计要点 4) | 监听 Socket 随客户端一同失效,需 `net_relisten` 显式重建 | "客户端断线 → 监听失效 → 重连" 的黏滞状态在模拟器上不出现 | `check_remo_sink.py` 覆盖重连;真机 `@test` |
|
||||
| N-5 | **单连接 backlog** | `NET_SIM_BACKLOG = 1` | CH395F 多 socket 并发 | 多客户端并发场景(FTP + 推流 + timeSync 同时)不可在模拟器验证 | 板端 NET 套件 + 台架并发场景 |
|
||||
| N-6 | **非阻塞语义模拟** | 创建即 `FIONBIO`,使 `DONTWAIT` 语义与 MCU 对齐(设计要点 2) | 中断驱动 + 状态机,天然非阻塞 | ——(此项是**对齐**而非差距,登记以说明"为何模拟器能测非阻塞路径") | —— |
|
||||
|
||||
### 2.2 日志与配置
|
||||
|
||||
| # | 差距 | 模拟器行为 | 真机行为 | 影响 | 回归补测 |
|
||||
|---|---|---|---|---|---|
|
||||
| L-1 | **控制台单写者无对应实现** | `simulator/dbg_log.h` 把 `DBG_*` 映射到 `printf`;**不编译** `console_log.c` | 单写者环形缓冲 + defaultTask 唯一发送 | `console_log` 的环形缓冲溢出/丢弃计数逻辑**模拟器测不到**;共享代码若误调 `console_log.h` 声明,模拟器链接失败(已被此约束捕获) | `@test` + 板端串口观测;`docs/子系统设计/控制台单写者日志设计说明.md` §219 |
|
||||
| L-2 | **mtime 粒度不同** | 宿主文件 mtime(**秒级**) | FAT 打包时间(`fdate<<16\|ftime`,**分钟级**) | `cfg_file_poll` 的 `(size, mtime)` 变更令牌在两端触发时机不同;分钟级粒度下的"同分钟内两次改动"在真机可能被判为无变化 | 板端 STORAGE 套件;`配置文件设计说明.md` §变更检测 |
|
||||
| L-3 | **无应用回调(空操作)** | 模拟器不注册段回调,`cfg_file_poll` 重载后回调为空 | 各段回调真正执行(如网络重配置) | "配置变更 → 触发回调 → 副作用"链路在模拟器上**只验证到写盘**,回调逻辑不覆盖 | `@test`(配置保存 → UI 生效) |
|
||||
| L-4 | **堆后端不同** | `cfg_store_alloc` = 宿主 `malloc`(堆余量以 MB 计) | FreeRTOS 堆(`configTOTAL_HEAP_SIZE` 48 KB,实测最低水位 ~4.8 KB) | **分配失败路径在模拟器上永不触发**——P0-1 类"KB 级分配必然返回 NULL"缺陷模拟器照不出来(`AGENTS.md` 已列为铁律) | 内存预算门禁 + 真机 `[HEAP]` 水位告警(S11) |
|
||||
|
||||
### 2.3 硬件专属(模拟器完全无对应)
|
||||
|
||||
| # | 差距 | 模拟器行为 | 真机行为 | 影响 | 回归补测 |
|
||||
|---|---|---|---|---|---|
|
||||
| H-1 | **IWDG 硬件行为** | 无 IWDG;`sim_port.c` 桩(空实现 + 计数器) | 独立看门狗,LSI 时钟驱动,超时真复位 | **真实复位行为、LSI 漂移、`RCC_CSR` 标志均不可建模**;位图/判定逻辑可在模拟器跑(纯 RAM) | 真机 `@test`(复位原因日志)+ 看门狗单测(L0.5 已覆盖位图逻辑)<br>`看门狗设计说明.md` §4.9 |
|
||||
| H-2 | **CH395F 芯片** | 无(Winsock 代其 BSD API) | SPI 寄存器/命令/中断、PHY 协商、真实以太网帧 | 寄存器时序、命令回复、中断风暴、PHY 100M 全双工强制 | 板端 CH395F 套件(52 断言点)+ `@test` |
|
||||
| H-3 | **GD5F NAND** | 无 | SPI NAND:BBT 扫描、ECC、坏块管理、dhara FTL | 坏块/ECC/写入失败等路径完全不可测 | 板端 GD5F 套件(76 断言点)+ `@test` |
|
||||
| H-4 | **SD2506 RTC** | 无 | I2C RTC:时间读写、闹钟、温度、电池电压 | 时间源相关(`sys_clock_init` 降级路径)在模拟器上不可触发 | 板端 RTC 套件(18 断言点)+ `sys_clock` L0.5(已覆盖算术面) |
|
||||
| H-5 | **TPAFE5160 ADC** | 无 | SPI ADC + BUSY EXTI 中断 | 中断驱动采集链路 | 待功能实现(上游 B1) |
|
||||
| H-6 | **RS-485 半双工** | 无 | UART4 + DE 方向控制,DWT 微秒延时 | DE 收发切换时序(特有陷阱) | 待功能实现(上游 B2) |
|
||||
| H-7 | **启动段硬件初始化** | 只跑纯软件部分 | I2C/SPI 外设初始化、BBT 扫描耗时、FatFS 挂载 | 启动段死机(无看门狗保护窗口)**只能在真机复现** | 真机开机日志判读(`项目启动顺序管理.md` §6.8,S10 落地) |
|
||||
|
||||
### 2.4 哪些差异是"有意的对齐"(非缺陷)
|
||||
|
||||
登记以免后人误判为差距:
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
| `lv_conf.h` 独立 | 模拟器 `simulator/lv_conf.h` 与 MCU `Lib/lv_conf.h` 相互独立(SDL/主题差异),**有意不统一** |
|
||||
| `DBG_*` → `printf` | 模拟器 stub 覆盖 MCU 版头(`simulator/README.md` 文件职责表),**有意** |
|
||||
| `NET_NO_OS` 剥离 FreeRTOS | `net_socket_sim.c` 构建时定义,剥离 RTOS 依赖,**有意** |
|
||||
| 无 SDL_PollEvent | LVGL SDL 驱动内部统一轮询,主循环轮询会吞键盘事件(`simulator/README.md` 已知坑 7) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 什么时候必须回真机(决策规则)
|
||||
|
||||
模拟器回归(L3)通过后,**满足以下任一条件即必须走真机**(`@test` / 板端套件):
|
||||
|
||||
1. **改动触及硬件访问层**:`Drivers/BSP/` 下任何驱动的寄存器/时序/中断代码
|
||||
2. **改动触及 §2.3 的 H-1~H-7 任一子系统**的初始化或运行时行为
|
||||
3. **改动触及 §2.2 的 L-2/L-3/L-4**:变更检测令牌、段回调副作用、内存分配失败路径
|
||||
4. **涉及网络重建 / 多连接并发**(N-2、N-5)
|
||||
5. **涉及启动段(`osKernelStart` 之前)**(H-7)
|
||||
6. **涉及内存预算**:新增任务/缓冲/栈(L-4;模拟器堆不可作依据)
|
||||
|
||||
反之,**纯 UI 场景/渲染/导航、配置解析算术、日志记录编解码格式**的改动,模拟器 + L0.5 可给出足够信心。
|
||||
|
||||
---
|
||||
|
||||
## 4. 登记约定
|
||||
|
||||
- **何时登记**:新增/修改 `simulator/sim/` 下任何后端、或发现"某逻辑模拟器走了不同分支"时,**必须**在 §2 加一行。
|
||||
- **格式**:沿用本表列(差距 / 模拟器行为 / 真机行为 / 影响 / 回归补测)。
|
||||
- **联动**:若该差距影响 L3 门禁的可信度,须同步在 `UI设计/UI_自动化测试流程.md` 引用本清单。
|
||||
- **收敛**:当某差距被 N-4 二期之类的工作**消除**时,不删行——改标 ~~删除线~~ + 注明消除日期与提交,保留"曾经差在哪"的历史。
|
||||
|
||||
---
|
||||
|
||||
## 5. 变更记录
|
||||
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-10-02 | 初版(S13):收录 6 条网络差距(N-1~N-6)、4 条日志/配置差距(L-1~L-4)、7 条硬件专属差距(H-1~H-7)、4 条"有意对齐"项。证据来源:`net_socket_sim.c` 文件头设计要点 + 双端导出函数逐一对比(MCU 有而 sim 缺 9 个)+ 各子系统文档既有记载汇总。§3 给出 6 条"必须回真机"决策规则 |
|
||||
+34
-1
@@ -218,15 +218,46 @@ static void phaseN_run(void) {
|
||||
& '.venv\Scripts\python.exe' test/run_all_suites.py # 全部套件
|
||||
& '.venv\Scripts\python.exe' test/run_all_suites.py --suites STORAGE,GD5F
|
||||
& '.venv\Scripts\python.exe' test/run_all_suites.py --dry-run # 只看计划
|
||||
& '.venv\Scripts\python.exe' test/run_all_suites.py --check-build # 只编译不烧录(S6)
|
||||
```
|
||||
|
||||
**核心保障:`try/finally` 无条件还原 `test_config.h`**(含异常 / `KeyboardInterrupt`
|
||||
路径)——跑完忘还原 = **测试固件进库**。这与提交期门禁 `test-suite-switch`(§7.1)
|
||||
是**两条独立防线**:前者是流程自动化、后者是门禁兜底。
|
||||
|
||||
> ⚠️ **不挂 pre-commit**:每轮真机烧录 + 采集(8s 静默 + boot),4 套件约数分钟起,
|
||||
> ⚠️ **完整回归不挂 pre-commit**:每轮真机烧录 + 采集(8s 静默 + boot),4 套件约数分钟起,
|
||||
> 且破坏性套件会改设备持久状态。仅回归窗口手工使用。
|
||||
|
||||
#### 3.6.1 `--check-build`:只编译不烧录(2026-10-02 加 S6)
|
||||
|
||||
**解决的问题**:测试套件的**构建可编译性没有独立门禁**——产品构建 `@build` 天天跑,
|
||||
但测试构建可能长期"编不过"而无人知,直到某次要用才发现(2026-08~09 CH395F 套件
|
||||
7 个配置实例,见 `测试与架构改进建议.md` 问题 T-4)。
|
||||
|
||||
```powershell
|
||||
& '.venv\Scripts\python.exe' test/run_all_suites.py --check-build
|
||||
```
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
| 做什么 | 逐套件:改 `test_config.h` 宏 → `@build` → 记 PASS/FAIL;结束后无条件还原 |
|
||||
| **不需要** | MCU / 串口 / ST-LINK(**纯离机**,可在 CI 或任何有 Keil 的机器上跑) |
|
||||
| 判定 | `@build` 退出码 0 = PASS;非 0(含 `L6406E` 链接失败)= FAIL |
|
||||
| RAM 溢出 | P1-9 类"编译过但链接失败"会**自然暴露**为 `L6406E`——这正是想要的 |
|
||||
| 退出码 | 同完整模式:0=全 PASS / 1=有套件编不过 / 2=用法缺文件 |
|
||||
|
||||
> **2026-10-02 实测**:4 套件(CH395F / STORAGE / GD5F / RTC)**全部 `BUILD_OK: 0 errors, 0 warnings`**
|
||||
> ——即 T-4 提到的"CH395F 套件不可编译"问题**已修复**,现已纳入可自检范围。
|
||||
|
||||
> ⚠️ **隐藏陷阱:`.map` 必须复位为产品态**(2026-10-02 实测踩到)。脚本跑完最后一轮留下的
|
||||
> `STM32F407-Demo.map` 是**测试套件**的体积(RTC 套件下 ER_IROM1 达 266 KB),
|
||||
> 会被 `mem-numbers` 钩子判为"文档快照与 `.map` 不一致"→ **下次提交直接 FAIL**。
|
||||
> 故脚本在还原 `test_config.h` **之后**追加一次产品构建(`[..] 复位构建产物为产品态`)——
|
||||
> **仅还原源码不够,必须再编一次**。同理适用于完整回归模式。
|
||||
|
||||
**建议用法**:改动 `test/board/` 下任何测试代码、或改 `test_config.h` 后,跑一次
|
||||
`--check-build` 再提交;比逐个手工改宏构建省事,且不会忘记还原(含 `.map` 复位)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 主机侧测试工具
|
||||
@@ -237,6 +268,7 @@ static void phaseN_run(void) {
|
||||
|---|---|---|
|
||||
| Python 串口助手(serial_tool) | `tools/serial_tool/serial_tool.py`(.venv + pyserial) | 常驻 daemon + 薄 CLI:列端口 / 开关 / 收发 / 全量日志 / wait-idle |
|
||||
| `test/host/run_boot_test.py` | `test/host/` | 一键闭环:采集 → 烧录 → 提取日志 → 校验 |
|
||||
| **`test/run_all_suites.py`** | `test/` | **多套件回归编排**(T-5)+ **`--check-build` 只编译不烧录**(S6,纯离机,验证 4 套件可编译性);见 §3.6 |
|
||||
| `test/host/verify_log.py` | `test/host/` | 日志标记校验(Required / Forbidden → PASS / FAIL)+ 可选套件汇总行校验(T-2) |
|
||||
| **`test/host/common/unit_test_harness.py`** | `test/host/common/` | **L0.5 通用单测 harness**:工具链探测 / `INCLUDE`-`LIB` 拼接 / 编译运行 / 退出码契约。各模块运行器只做声明式配置(T-3) |
|
||||
| **`test/host/watchdog/run_unit_test.py`** | `test/host/watchdog/` | **L0.5 主机侧单测**:声明 `TestBuild` 后调 harness,穷举看门狗判定状态机(零硬件、秒级)。**需本机装有 VS(`cl.exe`)**,见 §4.2 环境要求 |
|
||||
@@ -582,6 +614,7 @@ CI 只在 push / PR 时跑,且需 runner 就绪。**提交前自查、或 runn
|
||||
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-10-02 | **S6**:`test/run_all_suites.py` 新增 **`--check-build`**(只编译不烧录,纯离机,不需要 MCU/串口/ST-LINK);新增 **§3.6.1** 说明其解决的问题(测试构建无独立门禁,2026-08~09 CH395F 套件实例)、判定语义与用法。**实测 4 套件全部 `BUILD_OK: 0 errors, 0 warnings`**(即 T-4 提到的"CH395F 不可编译"已修复) |
|
||||
| 2026-10-02 | **S1-b + S2**:新增 **§4.2.1「L0.5 适格模块台账」**(判据 + 模块状态表 + "两条实证":sys_clock / cfg_file 首测即各抓一处生产缺陷);§4.1 登记 `test/host/cfg_file/` 新单测。同步新增 [`配置文件单元测试说明.md`](配置文件单元测试说明.md)(含抓出的 `s_encode` 返回值误判 P0 缺陷记录) |
|
||||
| 2026-10-02 | **S3 + S14**:§9.1 第 2 条改正(原文"强制跑 `mem-numbers`"自相矛盾 → 改为"按 `.map` 有无自适应跳过")并加 🔴 门禁盲区声明;§9.2 补 S3 结果可见性(`Tee-Object` 落盘 + `$GITHUB_STEP_SUMMARY` + `::notice::` 汇总行,不用 `actions/upload-artifact`)。另:§4.1 登记 `test/host/sys_clock/` 新单测(S1-a) |
|
||||
| 2026-10-01 | 新增 **§9 CI 持续集成(Gitea Actions,T-6)**:`.gitea/workflows/ci.yml`(`l0-gates` + `l05-host-unit-tests`,Windows runner);**L0 + L0.5 上 CI、L1/L2/L3 留本地**;刻意不跑 `@build`、`.map` 缺失时自适应跳过 `mem-numbers`;新增 `scripts/ci_local.py` 本地等价命令(§4.1 登记);§1 补 CI 说明;`docs/待处理内容.md` T-6 销项 |
|
||||
|
||||
@@ -278,3 +278,8 @@ UI 改动的自动化门禁脚本(完整流程见 `docs/UI设计/UI_自动化
|
||||
可在模拟器上验证(`simulator/tests/check_remo_sink.py`);CH395F 真实网络栈仍只在 MCU 上验证
|
||||
- `build/` 为生成物,可随时删除重建(已在 `.gitignore` 中忽略)
|
||||
- `Lib/lvgl/CMakeLists.txt` 仅被本模拟器引用,**不影响** Keil 的 MCU 编译
|
||||
|
||||
> **模拟器跑过 ≠ 真机跑过**:本目录与真机的**全部已知差异**(网络 pending 机制缺失、代次号恒 0、
|
||||
> 日志/配置的 mtime 粒度与堆后端差异、IWDG/NAND/RTC 等硬件不可建模)统一登记在
|
||||
> [`../docs/测试说明/模拟器差距清单.md`](../docs/测试说明/模拟器差距清单.md)——
|
||||
> 改动触及 `sim/` 任何后端时,**必须同步在那里登记一行**;改动前先看该文档 §3 判断是否必须回真机。
|
||||
|
||||
+8
-1
@@ -10,7 +10,7 @@
|
||||
|---|---|---|
|
||||
| `board/` | 板端测试套件(编入 Keil 工程,占 RAM) | [`board/README.md`](board/README.md) |
|
||||
| `host/` | PC 端测试/判读脚本(Python) | [`host/README.md`](host/README.md) |
|
||||
| `run_all_suites.py` | **多套件回归编排**(改宏 → 构建 → 烧录 → 采集,`try/finally` 保证还原开关) | 本文 §多套件回归 |
|
||||
| `run_all_suites.py` | **多套件回归编排**(改宏 → 构建 → 烧录 → 采集,`try/finally` 保证还原开关);`--check-build` = 只编译不烧录(纯离机) | 本文 §多套件回归 |
|
||||
|
||||
## 运行方式
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
& '.venv\Scripts\python.exe' test/run_all_suites.py
|
||||
& '.venv\Scripts\python.exe' test/run_all_suites.py --suites STORAGE,GD5F
|
||||
& '.venv\Scripts\python.exe' test/run_all_suites.py --dry-run # 只看计划
|
||||
|
||||
# 只编译不烧录(S6;不需要 MCU / 串口 / ST-LINK,验证 4 套件可编译性)
|
||||
& '.venv\Scripts\python.exe' test/run_all_suites.py --check-build
|
||||
```
|
||||
|
||||
退出码约定:`0`=PASS / `1`=FAIL / `2`=采集或烧录错误 / `3`=用法或预检错误。
|
||||
@@ -38,6 +41,10 @@
|
||||
⚠️ 每轮要真机烧录 + 采集,4 套件约数分钟起;破坏性套件还会改设备持久状态。
|
||||
**仅回归窗口用,绝不挂 pre-commit**。
|
||||
|
||||
`--check-build` 则相反:**只改宏 + `@build`,不烧录不采集,无需任何硬件**,用于防止
|
||||
"测试套件长期编不过而无人知"(2026-08~09 CH395F 套件实例)。改动 `test/board/` 下测试
|
||||
代码后建议先跑它——实测 4 套件均 `BUILD_OK: 0 errors, 0 warnings`。
|
||||
|
||||
## ⚠️ 注意
|
||||
|
||||
- 本目录**被 pre-commit 门禁排除**(测试代码不受 C 规范约束)
|
||||
|
||||
+50
-6
@@ -31,8 +31,15 @@
|
||||
& '.venv\\Scripts\\python.exe' test/run_all_suites.py
|
||||
& '.venv\\Scripts\\python.exe' test/run_all_suites.py --suites STORAGE,GD5F
|
||||
& '.venv\\Scripts\\python.exe' test/run_all_suites.py --dry-run # 只打印计划
|
||||
& '.venv\\Scripts\\python.exe' test/run_all_suites.py --check-build # 只编译不烧录(S6,无需 MCU)
|
||||
& '.venv\\Scripts\\python.exe' test/run_all_suites.py --keep-config # 不还原(调试用,慎)
|
||||
|
||||
--check-build(S6 / 上游 P1-10 方案 A):
|
||||
只做"改宏 + @build",不烧录不采集,故**不需要 MCU / 串口 / ST-LINK**。
|
||||
用途:① 改动测试固件或 test_config.h 后自检 4 套件仍可编译;
|
||||
② 防止"某套件长期编不过而无人知"(2026-08~09 CH395F 套件实例)。
|
||||
注意 RAM 溢出(P1-9)会自然暴露为链接错误 L6406E——这正是想要的。
|
||||
|
||||
设计依据:docs/待处理内容.md T-5;docs/测试说明/测试开发标准流程.md §3.1
|
||||
创建日期:2026-10-01
|
||||
"""
|
||||
@@ -121,6 +128,9 @@ def main(argv=None):
|
||||
ap.add_argument("--build-timeout", type=int, default=300)
|
||||
ap.add_argument("--flash-timeout", type=int, default=300)
|
||||
ap.add_argument("--dry-run", action="store_true", help="只打印计划,不改配置不构建不烧录")
|
||||
ap.add_argument("--check-build", action="store_true",
|
||||
help="只编译不烧录(S6/P1-10):逐套件改宏 + @build,验证 4 套件可编译性;"
|
||||
"不需要 MCU / 串口 / ST-LINK,可用于 CI 或改动测试固件后自检")
|
||||
ap.add_argument("--keep-config", action="store_true",
|
||||
help="结束后不还原 test_config.h(调试用;⚠️ 会留下测试固件配置)")
|
||||
args = ap.parse_args(argv)
|
||||
@@ -133,22 +143,28 @@ def main(argv=None):
|
||||
",".join(bad), ",".join(SUITES)))
|
||||
return 2
|
||||
|
||||
for p in (CONFIG, BUILD_BAT, DOWNLOAD_BAT, RUN_BOOT):
|
||||
# --check-build 不需要串口/烧录工具,故不强求 run_boot_test.py 存在
|
||||
required = (CONFIG, BUILD_BAT) if args.check_build else (CONFIG, BUILD_BAT, DOWNLOAD_BAT, RUN_BOOT)
|
||||
for p in required:
|
||||
if not p.is_file():
|
||||
print("[ERROR] 缺文件:{}".format(p))
|
||||
return 2
|
||||
|
||||
mode = "只编译(--check-build,不烧录)" if args.check_build else "完整回归(编译+烧录+采集)"
|
||||
print("=== 多套件回归编排 ===")
|
||||
print("模式:{}".format(mode))
|
||||
print("套件:{}".format(", ".join(picked)))
|
||||
print("构建:{}".format(BUILD_BAT))
|
||||
print("烧录:{}".format(DOWNLOAD_BAT))
|
||||
print("判读:{}".format(RUN_BOOT))
|
||||
if not args.check_build:
|
||||
print("烧录:{}".format(DOWNLOAD_BAT))
|
||||
print("判读:{}".format(RUN_BOOT))
|
||||
if args.dry_run:
|
||||
print("[DRY-RUN] 仅打印计划,未改动任何文件。")
|
||||
return 0
|
||||
|
||||
results = {} # 套件 -> "PASS"/"FAIL"/"BUILD_FAIL"
|
||||
restored = False
|
||||
baseline = None # 进入时的 test_config.h 基线(try 内赋值;finally 依赖它,故先置 None)
|
||||
|
||||
try:
|
||||
# 先确保从产品构建(干净起点)开始,避免上一轮的残留干扰
|
||||
@@ -172,6 +188,12 @@ def main(argv=None):
|
||||
results[suite] = "BUILD_FAIL"
|
||||
continue
|
||||
|
||||
# --check-build:编译通过即达标,不烧录不采集(S6 方案 A)
|
||||
if args.check_build:
|
||||
results[suite] = "PASS"
|
||||
print("[{}] BUILD_OK -> PASS(--check-build,未烧录)".format(suite))
|
||||
continue
|
||||
|
||||
# 2) 烧录
|
||||
t0 = time.monotonic()
|
||||
frc, fout = _run(["cmd", "/c", str(DOWNLOAD_BAT)], timeout=args.flash_timeout)
|
||||
@@ -204,7 +226,7 @@ def main(argv=None):
|
||||
_rewrite_config(None)
|
||||
# 与进入时的基线一致性核对(防止还原逻辑本身出错)
|
||||
now = CONFIG.read_bytes()
|
||||
if now != baseline:
|
||||
if baseline is not None and now != baseline:
|
||||
print("[WARN] 还原后的配置与进入时基线不一致,请人工核对 {}".format(CONFIG))
|
||||
else:
|
||||
restored = True
|
||||
@@ -213,9 +235,26 @@ def main(argv=None):
|
||||
except Exception as e: # noqa: BLE001 - 还原失败必须显式报出
|
||||
print("[ERROR] 还原 test_config.h 失败:{}".format(e), file=sys.stderr)
|
||||
|
||||
# 5) 复位构建产物为产品态(2026-10-02):最后一轮构建留下的是**测试套件**的
|
||||
# .map/.axf,会污染 mem-numbers 体积门禁(下次提交即 FAIL)。仅还原源码不够
|
||||
# ——必须再编一次产品固件,使 .map 与 test_config.h 一起回到产品态。
|
||||
# --check-build 也要做:它每轮都 @build,同样会留下测试态 .map。
|
||||
if restored and not args.keep_config:
|
||||
print("")
|
||||
print("[..] 复位构建产物为产品态(重建一次产品固件,避免 .map 停留在测试套件态)...")
|
||||
rc, out = _run([str(BUILD_BAT)], timeout=args.build_timeout)
|
||||
tail = "\n".join(out.strip().splitlines()[-4:])
|
||||
if rc == 0:
|
||||
print("[OK] 产品固件已重建(.map/.axf 复位)")
|
||||
else:
|
||||
# 不判死:配置已还原,仅提示手工重建
|
||||
print("[WARN] 产品固件重建失败(rc={}),请手工跑 @build 复位 .map".format(rc))
|
||||
if tail:
|
||||
print(tail)
|
||||
|
||||
# 汇总
|
||||
print("")
|
||||
print("=== 编排结果 ===")
|
||||
print("=== 编排结果{} ===".format("(--check-build)" if args.check_build else ""))
|
||||
for s in picked:
|
||||
print(" {:<8} {}".format(s, results.get(s, "SKIPPED")))
|
||||
if not restored and not args.keep_config:
|
||||
@@ -224,8 +263,13 @@ def main(argv=None):
|
||||
failed = [s for s, r in results.items() if r != "PASS"]
|
||||
if failed:
|
||||
print("[FAIL] 未通过:{}".format(", ".join(failed)))
|
||||
if args.check_build:
|
||||
print(" (--check-build:失败 = 该套件编译不过,需修测试固件代码)")
|
||||
return 1
|
||||
print("[OK] 全部套件 PASS")
|
||||
if args.check_build:
|
||||
print("[OK] 全部套件编译通过(--check-build:未烧录、未采集)")
|
||||
else:
|
||||
print("[OK] 全部套件 PASS")
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user