From 172b3f4a20ec3ff5c12dc5f3b2f5348509699c80 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Thu, 15 Oct 2026 10:05:37 +0800 Subject: [PATCH] =?UTF-8?q?fix(sim):=20UI=20=E5=9B=9E=E5=BD=92=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E8=87=AA=E6=B4=BD=E8=BF=90=E8=A1=8C=E2=80=94=E2=80=94?= =?UTF-8?q?=E4=BF=AE=20netcfg=5Fsave=20=E5=81=87=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现象:check_content_pages.py 只有 netcfg_save 一页 FAIL(保存行值列 显示 ERR 而非 OK),其余 10 页像素与两处往返池水位全过。 真因不是代码回归:模拟器按**当前工作目录**读写 sim_config.ini,而 --cfgtest 的 E-01 场景 2 会伪造一份 4000 B 纯注释"雷"文件、并断言 "拒写后原文件不动"(size == 4000)——连正常通过路径也不删除它。 实测仓库根那份是 2026-10-11 08:37 留下的。之后每次运行都会加载它 → 外科式保存要在 4000 B 注释后补写受管段 → 输出必超 CFG_FILE_MAX_LEN → 引擎按设计拒写 → 保存行显示 ERR,与"工厂默认 态"的黄金参考对不上。引擎行为是对的,错在自检留雷 + 回归不自洽。 修三处: - simulator/main.c:run_cfgtest 拆成 scenarios + 外壳,无论成败一律 cfg_store_remove 配置路径与临时路径,让配置回到"无文件 = 工厂默认" - check_menu_rows.py / check_content_pages.py:以空临时目录当工作 目录跑 lvgl_sim(cwd=tmp),调用目录里的残留档不再影响结果 - check_content_pages.py:作废"删 sim_netcfg.bin 残留"的旧兼容清理 (旧档迁移 2026-10-10 已删),改为只提示、不删别人的档 验证:--cfgtest PASS 且跑完不再留下 sim_config.ini; check_menu_rows.py PASS(8 状态 + 池基线 5,368 B); check_content_pages.py "PASS: 11 内容页 + 往返池水位全部通过", 往返 5,448 B。**未刷任何黄金参考**——参考本来就是对的。顺带删掉 本地两份 4000 B 残留(仓库根与 build/Debug,均 gitignore 产物)。 未跑 @build:本次只改 simulator/ 侧代码,固件工程无涉及;模拟器 CMake 构建 0 错 0 警,cppcheck 钩子覆盖 simulator/main.c。 文档:UI_自动化测试流程 §五 加排查行 + 变更记录;simulator/README §3.7 说明删除保证与原因;lvgl-simulator skill 同步;订正审查报告 R-9 那条把该差异误归给 cfg_file 改动的表述。 --- .dsh/skills/lvgl-simulator/SKILL.md | 4 +++- docs/UI设计/UI_自动化测试流程.md | 2 ++ docs/问题记录/项目审查与优化建议报告.md | 5 +++-- simulator/README.md | 4 +++- simulator/main.c | 23 +++++++++++++++++++- simulator/tests/check_content_pages.py | 29 +++++++++++++++++-------- simulator/tests/check_menu_rows.py | 5 ++++- 7 files changed, 57 insertions(+), 15 deletions(-) diff --git a/.dsh/skills/lvgl-simulator/SKILL.md b/.dsh/skills/lvgl-simulator/SKILL.md index f4cb2e9b..c169258e 100644 --- a/.dsh/skills/lvgl-simulator/SKILL.md +++ b/.dsh/skills/lvgl-simulator/SKILL.md @@ -26,9 +26,11 @@ PC 上编译运行与 MCU **同一份** UI 源码(CMake + MSVC + SDL2,x64, | `lvgl_sim.exe --screenshot shot.bin` | 离屏渲染首帧 → `simulator/i1_to_png.py` 转 PNG 目检 | | `lvgl_sim.exe --keys "ud" shot.bin` | 注入键序后截图:`u`=↑ `d`=↓ `l`=← `r`=→ `e`=Enter `s`=Esc(`main.c` 实锤) | | `lvgl_sim.exe --standby shot.bin` | 待机页截图 | -| `lvgl_sim.exe --cfgtest` | 配置引擎闭环:加载→轮询→外部改文件重载→外科式保存回验(在当前 CWD 产生 `sim_config.ini`) | +| `lvgl_sim.exe --cfgtest` | 配置引擎闭环:加载→轮询→外部改文件重载→外科式保存回验。临时用当前 CWD 的 `sim_config.ini`,**跑完无论成败一律删除**(旧行为连 PASS 路径也会留一份 4000 B 伪造档 → 之后每次运行都被加载 → 引擎按设计拒写超长文档 → 网口设置页保存行变 `ERR`,把像素回归带偏) | | `lvgl_sim.exe --logtest` | syslog 后端闭环(定长记录/时间戳/轮转) | +⚠ **模拟器按当前工作目录读写 `sim_config.ini`**:目录里残留一份配置就会改变页面内容。`simulator/tests/` 两套回归因此一律在**空临时目录**里跑(`cwd=tmp`);手工跑 `--keys`/`--screenshot` 做对比时,也要先确认当前目录没有 `sim_config.ini`,否则"复现不了别人的 FAIL"。 + ## UI 改动回归 → 用 `ui-verify` skill(勿在此零散手跑) 5 步门禁 = 构建 → `check_menu_rows.py`(菜单 8 状态像素 + 池基线)→ `check_content_pages.py`(文本断言 + 11 页黄金参考 + 往返池)→ `@build` → `coding-standard`。 diff --git a/docs/UI设计/UI_自动化测试流程.md b/docs/UI设计/UI_自动化测试流程.md index ab1a2b7a..2f3ec3d7 100644 --- a/docs/UI设计/UI_自动化测试流程.md +++ b/docs/UI设计/UI_自动化测试流程.md @@ -87,6 +87,7 @@ cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警 | 多页大面积像素差异 | 字体重生成(`ui_font_cn_12.c` 变化)→ 确认字形正确后 `--update` 刷参考 | | 单行/单字差异 | 几何或文案变更 → 先判断是否有意修改;无意 = bug | | 池水位门禁失败但像素一致 | 样式/结构变更(如动态样式转常量、增删框体)→ 确认新值合理后更新常量并登记 | +| **只有 `netcfg_save` 一页失败**(保存行值列是 `ERR` 而非 `OK`),其余 10 页全过 | **不是代码回归,是工作目录被污染**:模拟器按**当前工作目录**读写 `sim_config.ini`,而 `--cfgtest` 的 E-01 场景 2 会伪造一份 4000 B 纯注释"雷"文件、并断言"拒写后原文件不动"——**连通过路径也会把它留在原地**。之后每次运行都加载它 → 外科手术保存要在 4000 B 注释后补写受管段 → 输出必超 `CFG_FILE_MAX_LEN` → 引擎**按设计拒写** → 保存行显示 ERR,与"工厂默认态"的黄金参考对不上。处置:两套回归已改为**在空临时目录里跑**(传 `cwd=tmp`),`--cfgtest` 已加外壳无论成败一律删除伪造档;手工跑 `lvgl_sim` 时删掉当前目录的 `sim_config.ini` 即恢复。**禁止为此刷黄金参考**(参考是对的) | | `找不到 lvgl_sim.exe` | 模拟器未构建或 build/ 被删 → 先按 §一 重建 | | 终端输出中文乱码 | PowerShell 代码页渲染问题,不影响判定(看 PASS/FAIL 与退出码) | @@ -107,3 +108,4 @@ cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警 | 2026-09-10 | `simulator/tests/ref/` **整目录不入库**(`.bin` 黄金参考 + `.png` 目检图均为本地生成物):`git rm --cached` 原有 3 个 `.bin`,`.gitignore` 改为忽略整目录;首次/换机后先 `--update` 生成参考;`--update` 先跑文本断言防误刷;§四 重写 | | 2026-09-13 | 管线同步:`UI_界面制作流程.md` 移除 HTML 样机阶段(五阶段 → 四阶段 P0~P4),本文定位改为"P2/P3 门禁的可执行版本";§六 关系描述更新为 P0 规格 → P1 编码 → P2 本文回归 → P3 上机目检 → P4 归档;移除 `tools/ui_mock/` 引用 | | 2026-10-11 | **裁剪 LVGL 死代码(F-1/F-2/F-3)时的门禁记录**:先用**未裁剪**配置 `--update` 生成 11 页黄金参考,再切到裁剪配置重编重跑 → **逐字节全部一致**(菜单 8 状态像素一致),证明裁剪零视觉影响。池水位门禁随之 5440/5520 → **5368/5448**(−72 B;一次性残留仍 80 B,故非泄漏变化,见 §三 水位历史)。顺带揪出并修掉 `Lib/lv_conf.h` 与 `simulator/lv_conf.h` 的**长期漂移**(模拟器早已 I1-only、MCU 端未裁)→ 规则见 `UI_设计架构.md` §1.4 第 10 条,量化见 `../内存管理/RAM使用分析与优化方案.md` §2.6.4 | +| 2026-10-15 | **回归改为自洽运行(修 `netcfg_save` 假失败)**:`check_menu_rows.py` / `check_content_pages.py` 一律以**空临时目录当工作目录**跑 `lvgl_sim`(模拟器按 CWD 读写 `sim_config.ini`,残留档会改变页面内容);`simulator/main.c` 的 `--cfgtest` 加外壳,**无论成败一律删除伪造的 4000 B 配置档**——旧行为连 PASS 路径也会留雷,之后每次 UI 回归的保存行都从 `OK` 变 `ERR`(引擎拒写超长文档,属正确行为)。脚本里"删 `sim_netcfg.bin` 残留"的旧兼容清理随之作废(旧档迁移 2026-10-10 已删),改为**只提示不删除**。修后 11 页 + 往返池水位 5,448 B 全过,**未刷任何黄金参考**;排查口径见 §五 | diff --git a/docs/问题记录/项目审查与优化建议报告.md b/docs/问题记录/项目审查与优化建议报告.md index d05b3d29..8712f316 100644 --- a/docs/问题记录/项目审查与优化建议报告.md +++ b/docs/问题记录/项目审查与优化建议报告.md @@ -396,7 +396,7 @@ Select-String -Path 'MDK-ARM\STM32F407-Demo\STM32F407-Demo.map' -Pattern '__heap | **R-4** | UI 帧副本去重:同一 3,200 B 帧当前存 **4 份**(`g_ui_draw_buf` → `g_ui_fbuf` → `g_lcdmem_data` → `g_tx`)。让 `ui_flush_cb` 直接把 `color_p` 交给 sink、sink 发送时才拼帧头 | **−6.4 ~ −9.6 KB** | 中:需要 sink 与 LVGL 刷新生命周期对齐(同任务,可控) | P1 | ❌ 未办(`ui_port.o` 6,420 + `ui_net_sink.o` 6,766 = 13,186 B,副本数未变) | | **R-6** | 任务栈改**静态分配**(`stack_mem` 指向各自 `.bss` 数组),从堆里释放 ≈39 KB | 堆可用 +39 KB(**总量不变**,但把"池内碎片"变成"可核算静态区") | 低-中:静态后溢出检测更难发现;先测水位再定尺寸 | P2 | ❌ 未办。⚠ 前置已满足:观测三面常开 + `stack_report.py` 已给出每任务最坏链 → 可直接按实测值定尺寸 | -> ✅ **R-9 已落地并销项(2026-10-15)**:`LV_MEM_SIZE` 40→16 KB(两份 `lv_conf.h` 同步,`lv-conf-sync` 生效值比对 163 项 PASS)。依据 = 池水位门禁实测(菜单基线 5,368 B / 开页-关页往返 5,448 B / 最重页面峰值 6,568 B = 池的 40%),**≈2.5× 余量**;PC 侧 11 页黄金参考逐字节比对 10/11 一致(唯一差异是 `netcfg_save` 保存态文本,A/B 复测证明与本次改动无关,属并行未提交的 `cfg_file` 改动)、往返池水位 5,448 B **分毫未变**;`@build` 0 错 0 警,ZI 精确 −24,576 B。**真机复跑 ✅(2026-10-15 `@test` PASS)**:`menu build done: total=15020 free=11828 used=3192`——菜单根框只占建池后 15,020 B 的 **21%**(TLSF 元数据 ≈1,364 B),全程无 `[ERR]`/Fault。再往下试 8 KB 需先跑全量 UI 回归(PC + 真机),**不在本条范围**。 +> ✅ **R-9 已落地并销项(2026-10-15)**:`LV_MEM_SIZE` 40→16 KB(两份 `lv_conf.h` 同步,`lv-conf-sync` 生效值比对 163 项 PASS)。依据 = 池水位门禁实测(菜单基线 5,368 B / 开页-关页往返 5,448 B / 最重页面峰值 6,568 B = 池的 40%),**≈2.5× 余量**;PC 侧 11 页黄金参考逐字节比对 10/11 一致(唯一差异是 `netcfg_save` 保存态文本,A/B 复测证明与本次改动无关——真因是 `--cfgtest` 留在工作目录的 4000 B 伪造配置档触发了引擎"拒写超长文档"这条**正确**行为,同日已修,见 `../UI设计/UI_自动化测试流程.md` §五)、往返池水位 5,448 B **分毫未变**;`@build` 0 错 0 警,ZI 精确 −24,576 B。**真机复跑 ✅(2026-10-15 `@test` PASS)**:`menu build done: total=15020 free=11828 used=3192`——菜单根框只占建池后 15,020 B 的 **21%**(TLSF 元数据 ≈1,364 B),全程无 `[ERR]`/Fault。再往下试 8 KB 需先跑全量 UI 回归(PC + 真机),**不在本条范围**。 > ⚠️ **R-2 与 R-4 不可叠加计算**:R-4 省的是"副本份数",R-2 已把这四份缓冲整体搬到 CCM,因此 **R-4 在 R-2 之后省的是 CCM 而非 SRAM1**(CCM 侧 13,186 B → ≈6.8 KB,为后续 UI 扩展腾空间)。若只做 R-4 不做 R-2,则直接为 SRAM1 省 6.4 KB——两条路线对 RAM 总量等价,**但顺序决定收益落在哪个区域**。 > @@ -627,7 +627,8 @@ const ui_page_desc_t ui_page_deadzone = UI_FORM_PAGE("遥测死区", s_items_dea | 2026-09-18 | 初版 | 全量审查;基线 `HEAD = 2d0ec35`、`.map` 链接于 2026-09-16;产出 N-01..N-18 / D-1..D-7 / S-1..S-5 / R-1..R-9 / F-1..F-6 / U1..U5 编号体系 | | 2026-10-11 | 实施 + 销项:F-1/F-2/F-3 | 两份 `lv_conf.h` 对齐同一特性子集 → 省 64,576 B;验证链(`@build` / ui-verify / `@test`)与对象级对照见 `../内存管理/RAM使用分析与优化方案.md` §2.6.4 | | **2026-10-15** | **登记簿同步销项** | 本报告的 N-03c / N-11 对应登记簿 **N-20 / N-26** 两行已按「完成即销项」从 `../任务规划/登记簿.md` §5 删除(并清 `../任务规划/README.md` §1/§2 与 `排期详述.md` §1 的引用,W4 链由 6 项减为 5 项、结构/健壮性计数 9→7)。删除前补齐去处记录:`../子系统设计/看门狗设计说明.md` §4.4 新增 `Error_Handler` 统一降级一节、`../内存管理/内存内容分配策略.md` §4 R2 补编译期窗口断言与启动期自检两层 + §8 落点索引两行。同批订正登记簿三处过时内容:N-23 残余去掉已删除的 `lftpd_log.c`、N-25 去掉已删除的 `NET_RECV_TIMEOUT_MS`、N-27 剔除不可行的 R-5 并把 CCM 余量改为实测 7,904 B、N-29 的 `.git` 由 149 MB 订正为实测 235.3 MB | -| **2026-10-15** | **执行本报告条目:R-9 落地并销项** | `LV_MEM_SIZE` 40→16 KB(`Lib/lv_conf.h` + `simulator/lv_conf.h` 同步,`lv-conf-sync` 生效值比对 PASS)。**依据**:池水位门禁实测菜单基线 5,368 B / 开页-关页往返 5,448 B / 最重页面峰值 6,568 B。**验证**:`@build` 0 错 0 警,ZI 精确 −24,576 B → SRAM1 98,144/74.9% → **73,568/56.1%(余 57,504 B)**,CCM 不变;PC 侧 11 页黄金参考逐字节 10/11 一致、往返池水位 5,448 B 未变(唯一差异 `netcfg_save` 经 40 KB A/B 复测确认与本次改动无关)。**连带改写**:§0.2 Top3 第 2 条、§0.3 主线一、§0.4 内存两行、§5.1 基线表(追 2026-10-15 `.map`)、§5.2 诊断 1(瓶颈由 SRAM1 换到 CCM)、§5.3(R-9 行删除、R-2 收益改 −29,570 B)、§6.4 池容量行;同步 `../内存管理/RAM使用分析与优化方案.md`、`../内存管理/RAM预算台账.md` §3、`../UI设计/UI_设计架构.md`、`../UI设计/UI_界面制作流程.md`、`../UI设计/LVGL模拟器部署与使用说明.md`、`simulator/README.md`、`../产品需求/产品需求设计文档.md`。**真机复跑 ✅**:`@test` PASS(`fw build: Oct 15 2026 07:31:34`,无 `[ERR]`/Fault,开机 622 ms 到 `net init: OK`),`menu build done: used=3192` = 建池后 15,020 B 的 21%。台账受管区与内存快照区已 `--write` 回填 → `mem-budget` / `mem-numbers` 两钩子转绿 | +| **2026-10-15** | **执行本报告条目:R-9 落地并销项** | `LV_MEM_SIZE` 40→16 KB(`Lib/lv_conf.h` + `simulator/lv_conf.h` 同步,`lv-conf-sync` 生效值比对 PASS)。**依据**:池水位门禁实测菜单基线 5,368 B / 开页-关页往返 5,448 B / 最重页面峰值 6,568 B。**验证**:`@build` 0 错 0 警,ZI 精确 −24,576 B → SRAM1 98,144/74.9% → **73,568/56.1%(余 57,504 B)**,CCM 不变;PC 侧 11 页黄金参考逐字节 10/11 一致、往返池水位 5,448 B 未变(唯一差异 `netcfg_save` 经 40 KB A/B 复测确认与本次改动无关,真因见下一行)。**连带改写**:§0.2 Top3 第 2 条、§0.3 主线一、§0.4 内存两行、§5.1 基线表(追 2026-10-15 `.map`)、§5.2 诊断 1(瓶颈由 SRAM1 换到 CCM)、§5.3(R-9 行删除、R-2 收益改 −29,570 B)、§6.4 池容量行;同步 `../内存管理/RAM使用分析与优化方案.md`、`../内存管理/RAM预算台账.md` §3、`../UI设计/UI_设计架构.md`、`../UI设计/UI_界面制作流程.md`、`../UI设计/LVGL模拟器部署与使用说明.md`、`simulator/README.md`、`../产品需求/产品需求设计文档.md`。**真机复跑 ✅**:`@test` PASS(`fw build: Oct 15 2026 07:31:34`,无 `[ERR]`/Fault,开机 622 ms 到 `net init: OK`),`menu build done: used=3192` = 建池后 15,020 B 的 21%。台账受管区与内存快照区已 `--write` 回填 → `mem-budget` / `mem-numbers` 两钩子转绿 | +| **2026-10-15** | **修 `netcfg_save` 假失败:回归改为自洽运行** | 上一条那个"唯一差异"查到真因,**不是代码回归**:`--cfgtest` 的 E-01 场景 2 伪造一份 4000 B 纯注释配置档并断言"拒写后原文件不动",**连通过路径也不删除它**;留在工作目录后每次运行都被加载 → 外科手术保存要在 4000 B 注释后补写受管段 → 输出必超 `CFG_FILE_MAX_LEN` → 引擎**按设计拒写** → 保存行显示 `ERR`。修三处:`simulator/main.c` 给 `--cfgtest` 加外壳(无论成败一律删除伪造档)、`check_menu_rows.py` 与 `check_content_pages.py` 改在**空临时目录**里跑(`cwd=tmp`)、脚本里已作废的 `sim_netcfg.bin` 兼容清理改为只提示不删除。修后 **11/11 页 + 往返池水位 5,448 B 全过,未刷任何黄金参考**(参考本来就是对的)。排查口径 → `../UI设计/UI_自动化测试流程.md` §五 | | **2026-10-14** | **清理:删除已完成与过时内容** | ① **已修复项按销项删除**(去处见下表):N-02(FTP 鉴权)、N-03c(`Error_Handler` 降级)、**N-11(DMA 可达性三层防线 + 规则族 R1~R4)**、S-3(注册反向校验)、P2-5/P2-6(SD2506 负温度与 WRTC)、P2-8(`NET_RECV_TIMEOUT_MS` 已删)、P2-9(`mkdir` 幂等重试)、§5.5 前三条(内存治理三件套)、交付物清单已建项。② **删除过时层**:2026-09-16 / 2026-10-11 双列基线表与"复核期间未提交改动"口径说明、旧"组合结果预估"表、逐条"2026-10-11 复核"批注、历史变更记录行——内存数字改由 `../内存管理/RAM预算台账.md` 与 `scripts/ram_baseline.tsv` 持有。③ **按 2026-10-14 `.map` 与代码重测**全部保留结论的数字(含 `lftpd_log.c` 已删除并入 `lftpd_auth.c`,N-06 残余只剩 `rs485_task.c` 一条刻意缺口)。④ **修正三处错误**:R-5(`configMAX_PRIORITIES` 56→32)**判定不可行**(CMSIS-RTOS2 `#error`);原 §7 把报告 S-5(驱动统一错误码)与登记簿 S-5(卷锁覆盖面)混为一谈;原 §5.5 第 4 条建议的 `_Static_assert` 形态在 AC5 上取不到链接期地址,已被"窗口断言 + 启动自检 + 运行时守卫"三层取代。⑤ 报告自有编号**不回改、不重排**(`../编号规范.md`) | ### 已销项条目的记录去处 diff --git a/simulator/README.md b/simulator/README.md index 99f4c963..a82cef02 100644 --- a/simulator/README.md +++ b/simulator/README.md @@ -232,7 +232,7 @@ UI 改动的自动化门禁脚本(完整流程见 `docs/UI设计/UI_自动化 ### 3.7 配置文件自检(--cfgtest) -配置引擎"加载 → 轮询 → 重载 → 保存"的进程内闭环验证(无需窗口 / LVGL,退出码 0=PASS / 非 0=FAIL),在当前工作目录产生 `sim_config.ini`: +配置引擎"加载 → 轮询 → 重载 → 保存"的进程内闭环验证(无需窗口 / LVGL,退出码 0=PASS / 非 0=FAIL);自检期间在当前工作目录**临时**使用 `sim_config.ini`,**结束时无论成败一律删除**(2026-10-15 起,原因见下文 ⚠): ```powershell .\build\Debug\lvgl_sim.exe --cfgtest @@ -242,6 +242,8 @@ UI 改动的自动化门禁脚本(完整流程见 `docs/UI设计/UI_自动化 空目录运行 = 验证出厂模板生成;超限文件(>4096 B)= 判损坏并重建出厂模板;超限文档保存 = 拒绝且不动原文件。窗口模式主循环以与 MCU netTask 相同的 2s 周期调用 `cfg_file_poll()`:外部文件变更沉降后自动重载 `[network]` 段(模拟器无网络栈,变更回调为空操作,仅日志可见)。 +⚠ **为什么必须删掉伪造档**:超限文档保存那条断言要求"拒写后原文件不动"(`size == 4000`),所以**连通过路径也会把这份 4000 B 纯注释档留在工作目录**。之后每一次运行都会加载它 → 外科式保存要在 4000 B 注释后补写受管段 → 输出必超 `CFG_FILE_MAX_LEN` → 引擎按设计拒写 → 网口设置页保存行显示 `ERR`,把 UI 像素回归带偏(2026-10-15 实测踩中)。故 `run_cfgtest()` 只是外壳,跑完一律删除;`simulator/tests/` 两套回归另以**空临时目录当工作目录**运行做双保险 → `docs/UI设计/UI_自动化测试流程.md` §五。 + --- ## 四、已知坑(构建/运行) diff --git a/simulator/main.c b/simulator/main.c index 670efcb3..7cb8f82a 100644 --- a/simulator/main.c +++ b/simulator/main.c @@ -259,7 +259,7 @@ static const uint8_t s_cfgtest_ip[4] = { 10u, 0u, 0u, 1u }; * 说明:验证链 net_cfg_load(只读 INI)→ poll 建基线 → 伪造外部编辑 * → poll 两次(沉降去抖)→ 重载并更新 RAM 副本 */ -static int run_cfgtest(void) { +static int run_cfgtest_scenarios(void) { int i; const net_cfg_t *p_cfg; @@ -380,6 +380,27 @@ static int run_cfgtest(void) { return 0; } +/* + * 函数功能:配置自检外壳——保证不在工作目录留下伪造的配置档 + * 入口参数:无 + * 返回值:run_cfgtest_scenarios 的判定 int + * 说明:E-01 场景 2 会写一个 4000 B 纯注释"雷"文件,并断言"拒写后原文件不动" + * (size == 4000)——**因此连正常通过路径也会把它留在原地**。该文件是 + * gitignore 的本地产物,但之后每一次 UI 回归都会加载它:外科手术保存要在 + * 4000 B 注释后补写受管段,输出必然超 CFG_FILE_MAX_LEN → 引擎按设计拒写 + * → 网口设置页保存行显示 ERR → 黄金参考(工厂默认态)比对失败 + * (2026-10-15 实测踩中)。故无论成败一律删除,让配置路径回到 + * "无文件 = 工厂默认"这一确定态;回归脚本另以空工作目录运行做双保险 + * → docs/UI设计/UI_自动化测试流程.md §三 + */ +static int run_cfgtest(void) { + int rc = run_cfgtest_scenarios(); + + (void)cfg_store_remove(cfg_store_config_path()); + (void)cfg_store_remove(cfg_store_tmp_path()); + return rc; +} + /* * 函数功能:日志引擎自检(无窗口、进程内闭环) * 入口参数:无 diff --git a/simulator/tests/check_content_pages.py b/simulator/tests/check_content_pages.py index aa8c5405..8fe3b872 100644 --- a/simulator/tests/check_content_pages.py +++ b/simulator/tests/check_content_pages.py @@ -111,8 +111,16 @@ STRIDE = W // 8 # I1:20 字节/行 POOL_AFTER_ROUNDTRIP = 5448 -def run_sim(seq, out): - r = subprocess.run([str(EXE), "--keys", seq, out], capture_output=True) +def run_sim(seq, out, cwd): + """在指定工作目录里跑一次无头截图。 + + 模拟器按**工作目录**读写 `sim_config.ini`:目录里残留一份配置(尤其 + `--cfgtest` E-01 场景 2 伪造的 4000 B 纯注释"雷"文件)会让引擎按设计 + 拒绝保存 → 网口设置页保存行显示 ERR → 与"工厂默认态"的黄金参考对不上。 + 因此一律在空临时目录里跑,回到"无配置文件 = 工厂默认"这一确定态。 + """ + r = subprocess.run([str(EXE), "--keys", seq, out], capture_output=True, + cwd=str(cwd)) if r.returncode != 0: raise RuntimeError("lvgl_sim --keys %r rc=%d: %s" % (seq, r.returncode, r.stderr.decode(errors="replace")[:200])) @@ -263,17 +271,20 @@ def main(): tmp = tempfile.mkdtemp(prefix="dtu_ui_pages_") ok = True - # 清理手工运行残留的配置存储文件(模拟器工作目录),防"工厂默认值"像素参考被污染 - # (sim_netcfg.bin 是旧档时代的产物:旧档迁移已于 2026-10-10 删除,此处只做兼容清理) - stale = Path.cwd() / "sim_netcfg.bin" - if stale.exists(): - stale.unlink() + # 回归一律在空临时目录里跑(原因见 run_sim 的说明)。调用目录里残留的配置档 + # 不再影响像素,只提示一句,便于手工跑 lvgl_sim 的人排查。 + # (旧 sim_netcfg.bin 时代的"删除残留"做法已作废:旧档迁移 2026-10-10 删除, + # 且真正会污染结果的是 --cfgtest 留下的 sim_config.ini,删别人的档不合适) + for leftover in ("sim_config.ini", "sim_netcfg.bin"): + if (Path.cwd() / leftover).exists(): + print("[提示] 当前目录存在 %s(本脚本不读它;手工运行 lvgl_sim 时留意," + "--cfgtest 会伪造 4000 B 配置档)" % leftover) # 1) 跑全部页面 + 文本级断言预检(先于像素比对/刷新:失败即中止,避免误刷参考) shots = {} for name, seq in PAGES: shot = os.path.join(tmp, "shot_%s.bin" % name) - run_sim(seq, shot) + run_sim(seq, shot, tmp) shots[name] = open(shot, "rb").read() for name, _seq in PAGES: @@ -316,7 +327,7 @@ def main(): # 开页-关页往返池水位门禁(info 页 e/s;密码页 ddee e/s 后 ←← 弹框回根框) for rt_name, rt_seq in (("info", "es"), ("pwd", "ddeeesll")): - out = run_sim(rt_seq, os.path.join(tmp, "shot_rt_%s.bin" % rt_name)) + out = run_sim(rt_seq, os.path.join(tmp, "shot_rt_%s.bin" % rt_name), tmp) m = re.search(r"pool after keys: used=(\d+)", out.decode(errors="replace")) if not m: print("FAIL: 往返输出未找到 'pool after keys'(%s %r)" % (rt_name, rt_seq)) diff --git a/simulator/tests/check_menu_rows.py b/simulator/tests/check_menu_rows.py index 989e413a..5d14a1e9 100644 --- a/simulator/tests/check_menu_rows.py +++ b/simulator/tests/check_menu_rows.py @@ -126,7 +126,10 @@ def main(): try: for name, seq in STATES: out = os.path.join(tmp, "rows_%s.bin" % name) - r = subprocess.run([str(EXE), "--keys", seq, out], capture_output=True) + # 空临时目录当工作目录:模拟器按 cwd 读写 sim_config.ini, + # 残留档(--cfgtest 会伪造 4000 B)会改变页面内容 → 回归必须自洽 + r = subprocess.run([str(EXE), "--keys", seq, out], capture_output=True, + cwd=tmp) if r.returncode != 0: print("[%s] 运行失败 rc=%d: %s" % (name, r.returncode, r.stderr.decode(errors="replace")[:200]))