Repository navigation
test(tui): fix flaky needs_animation_gates_prompt_history_tick_delivery - #65
Conversation
…result activate() copies the daemon snapshot at once, so a quick daemon had already delivered both entries and tick() never saw a newer generation. Ask the daemon for a new generation after activate and wait for tick() to deliver it. Co-authored-by: z123385708 <[email protected]>
b414624 to
e354af5
Compare
|
Stack #66, bottom to top: #58, #59, #56, #57, #60, #61, #62, #63, #64, #65. One atomic squash merge ( Position 10 of 10. Base: Local checks of the stack tip before the push (Windows and WSL Linux): |
…ne on main (#67) Ten PRs (#56 to #65) were merged into main with one `gh stack merge` (stack #66, squash). main went from 7cb9e23 to 032ddbb. - Execution log: new section 10.31 (stack order, the eight rebuilt heads, conflicts, checks before the merge, CI on main), contents list, section 10 title and intro, revision log. - Plan: header and "current state" at r28; phase D started; D.5 and D.8 ticked; the sentences that said "not merged" now say "merged"; the section 10 index; the revision summary; link definitions. - docs/baselines/tui.md: numbers from the TUI CI run on main (8,558 passed, residue ceilings 239 and 282, test and job times). Documents only.
结果
修了随机失败的测试
pi-pager app::app_view::tests::needs_animation_gates_prompt_history_tick_delivery(app_view_tests.rs:474,tick() must poll the history daemon and deliver results)。只改了测试,没有改产品代码。测试数量不变(5651),所以不用改tui_baseline.toml与docs/baselines/tui.md。触发这次修复的失败:run 41097019349(PR #58)。
根因:测试的假设有竞争,产品行为没有问题
HistorySearchState::activate先给 daemon 线程发SetItems,马上把 daemon 已发布的快照拷贝到界面一侧(last_gen = snapshot.generation)。poll()(tick()调它)只在 generation 变大时返回true。app.tick()为true且result_count() == 2。tick()看到新 generation,通过。tick()永远是false,1000 次、约 1 秒后失败。activate已经把数据交给界面,调用方在激活后会重绘。所以不是产品竞争,是测试等了一个在「daemon 快」时永远不会出现的事件。证据(确定性复现):在
activate_inner的refresh_items与拷贝之间临时加sleep(50ms)(模拟 daemon 先到),旧测试第 1 次就失败,同样的 panic。移除后复原。修法
activate之后调用history_search.update_query("")。daemon 在拷贝之后一定会发布一个更大的 generation(两种情况都成立),tick()就一定会交付它。等待改成最长 30 秒的截止时间,只用来结束挂死;通过时立刻返回,不会变慢。没有加长 sleep。证据(本机,4 vCPU,内核 6.12;测试用
cargo nextest run -p pi-pager --lib -E 'test(needs_animation_gates_prompt_history_tick_delivery)' --stress-count N)taskset -c 0(单核),300 / 1000 次activate里sleep(50ms))sleep(100ms))说明:单核加载并不会提高失败率;32 个循环时失败率反而为 0(主线程更不容易在发送与拷贝之间被抢占)。失败率最高的是 8 个循环。所以「修之后 0 / 2000」的证据力有限,真正的证据是上面的确定性复现。
门禁(同
tui-ci.yml的内容):python scripts/tui_baseline.py check:全部 ✅(含cargo check --workspace --tests,0 warnings)。python scripts/tui_baseline.py test --suite pi-pager(已清掉PI_HOME等变量):5651 通过,0 失败,12 跳过。rustfmt --check通过;cargo clippy -p pi-pager --lib --tests退出 0,这个文件没有新告警(pi-pager里原有的 10 条告警不是这个改动引起的)。ruff check .、ruff format --check .通过。没有 Windows / WSL,没有跑。已清掉测试写到
~/.pi-python的东西。只报告、没有修(同样写法的等待)
views/history_search.rs:633-640、:643-651:activate_and_poll、query_and_poll只等 100 次 × 1 ms,超时不报错,直接往下走。预算很小,机器一卡就会让后面的断言失败。views/history_search.rs:688-696(refresh_items_and_query_are_applied_together):同样 100 × 1 ms,而且断言写在poll()为真时。app/agent_view/prompt.rs:1303(poll_results)与app/agent_view/prompt_stash.rs:196(await_history_results):各 500 × 1 ms,守着同一个 daemon。它们看result_count、selected_text,不依赖poll()的返回值,所以不会有这次的竞争,但预算只有约 0.5 秒。app/app_view_tests.rs:841-849(tick_scroll):200 × 5 ms,约 1 秒。scrollback/search.rs:721、:823、:1169与app/agent_view/links.rs:1471:1000 × 1 ms。它们都等poll()返回true,所以如果后台已先发布,与这次是同一类竞争,需要再看。最近 TUI CI 的随机失败
main上最近 20 次TUI CI都是 success(一次被取消,没有失败)。三个最近的失败:pi-file-utils queue::tests::cleanup_orphans_uses_sidecar_age_for_pairs。这是真缺陷,已由8ade6f6修复并合入main,不是随机失败。ci/tui-sandbox-gate):pi-sandbox deny_paths_e2e在设了SANDBOX_E2E_REQUIRE_ENFORCEMENT时 panic(deny_paths_e2e.rs:108,即 runner 上bwrap不可用)。这是确定性的失败,不是随机的,归另一个 worker 的分支。我在 test(tui): verify the Landlock layer of pi-sandbox (plan 0.9) #58 的汇报里说「runner 上有 bwrap」没有依据,请以这条为准。