From bdd315f12f39a5e3033dad3c916ef0d9fc6b36b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Thu, 1 Oct 2026 17:07:03 +0800 Subject: [PATCH] =?UTF-8?q?AGC=20=E5=A3=B3=20Rust=20=E5=8D=95=E6=B5=8B?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=20mock=20provider=20=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E9=A2=84=E7=AE=97=EF=BC=8C=E4=BF=AE=E6=8E=89=E5=88=86=E7=89=87?= =?UTF-8?q?=204=20=E7=AC=AC=E4=BA=8C=E6=AC=A1=E5=BE=80=E8=BF=94=E8=B6=85?= =?UTF-8?q?=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tests/mod.rs 新增 MOCK_PROVIDER_REQUEST_TIMEOUT(30 秒),作为「等一条 mock provider 请求到达」的唯一预算 - tests/sessions.rs 的 7 处 recv_timeout 由散落的 2 秒 / 10 秒改为引用该常量,确认「不会有下一个请求」的 200ms 负向断言保持不变 - wait_for_captured_mock_request 的内联 10 秒预算改为引用同一常量 - pitfalls 记录该超时是 CI 分片并发放大所致,而非产品缺陷,并附本次复核证据 --- .../src-tauri/src/tests/mod.rs | 7 ++++++- .../src-tauri/src/tests/sessions.rs | 14 +++++++------- docs/project-memory/shared-memory/pitfalls.md | 8 ++++++++ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/mod.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/mod.rs index 5e9fbc633..7c0ea34e1 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/mod.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/mod.rs @@ -427,6 +427,11 @@ fn agent_goal_sidecar_path_for_test(root: &Path, agent_id: &str, session_id: &st /// 因此统一等「状态离开 running 且 lane 可获取」,并留出远大于单机耗时的硬预算。 const AGENT_RUNTIME_TERMINAL_WAIT_BUDGET: Duration = Duration::from_secs(60); +/// mock provider 是进程内 HTTP 服务,但一轮请求要等 Agent Runtime 后台线程跑到对应步骤才会发出。 +/// CI 上多个 rust shard 与前端任务同机并发时,单轮往返可能被拖过数秒;这里只断言请求确实到达, +/// 因此给出远大于单机耗时的硬预算,避免把调度抖动误判成链路故障。请求到达即返回,成功路径零成本。 +const MOCK_PROVIDER_REQUEST_TIMEOUT: Duration = Duration::from_secs(30); + fn wait_for_agent_runtime_lane_release( root: &Path, agent_id: &str, @@ -483,7 +488,7 @@ async fn wait_for_captured_mock_request( receiver: &mpsc::Receiver, description: &str, ) -> String { - let deadline = Instant::now() + Duration::from_secs(10); + let deadline = Instant::now() + MOCK_PROVIDER_REQUEST_TIMEOUT; loop { match receiver.try_recv() { Ok(request) => return request, diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/sessions.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/sessions.rs index 50d29fc09..91dbdb5cf 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/sessions.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/sessions.rs @@ -217,7 +217,7 @@ async fn background_agent_runtime_recovers_bound_context_for_the_same_session_an .expect("resume background task from durable context"); assert_eq!(resumed.len(), 1); let request = receiver - .recv_timeout(Duration::from_secs(2)) + .recv_timeout(MOCK_PROVIDER_REQUEST_TIMEOUT) .unwrap_or_else(|error| { let runtime = read_game_creator_agent_runtime_at(&root, "design-director") .expect("read runtime after recovery timeout") @@ -396,12 +396,12 @@ async fn background_agent_runtime_can_write_memory_and_project_files() { .expect("start background task"); let plan_request = receiver - .recv_timeout(Duration::from_secs(2)) + .recv_timeout(MOCK_PROVIDER_REQUEST_TIMEOUT) .expect("plan llm request"); assert!(plan_request.contains("memory.write")); assert!(plan_request.contains("file.write")); let second_request = receiver - .recv_timeout(Duration::from_secs(2)) + .recv_timeout(MOCK_PROVIDER_REQUEST_TIMEOUT) .expect("second llm request"); assert!(second_request.contains("memory.write")); assert!(second_request.contains("file.write")); @@ -409,7 +409,7 @@ async fn background_agent_runtime_can_write_memory_and_project_files() { assert!(second_request.contains("已写入 long 记忆")); assert!(second_request.contains("已写入 game/agent-notes.md")); let final_request = receiver - .recv_timeout(Duration::from_secs(10)) + .recv_timeout(MOCK_PROVIDER_REQUEST_TIMEOUT) .expect("final llm request after current revision verification"); assert!(final_request.contains("project.verify")); assert!(final_request.contains("AGENT_RUNTIME_CURRENT_REVISION_OK")); @@ -557,11 +557,11 @@ async fn background_agent_runtime_blocks_cross_agent_private_memory_write() { .expect("start background task"); let plan_request = receiver - .recv_timeout(Duration::from_secs(2)) + .recv_timeout(MOCK_PROVIDER_REQUEST_TIMEOUT) .expect("plan llm request"); assert!(plan_request.contains("Agent 私有记忆只能由本人写入")); let second_request = receiver - .recv_timeout(Duration::from_secs(2)) + .recv_timeout(MOCK_PROVIDER_REQUEST_TIMEOUT) .expect("second llm request"); assert!(second_request.contains("\\\"tool\\\": \\\"memory.write\\\"")); assert!(second_request.contains("\\\"status\\\": \\\"blocked\\\"")); @@ -648,7 +648,7 @@ async fn background_agent_runtime_file_and_memory_writes_respect_project_policy( .expect("start background task"); let _plan_request = receiver - .recv_timeout(Duration::from_secs(2)) + .recv_timeout(MOCK_PROVIDER_REQUEST_TIMEOUT) .expect("plan llm request"); assert!(receiver.recv_timeout(Duration::from_millis(200)).is_err()); diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 7a0f37a75..97b2987d6 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -6192,3 +6192,11 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` - **同时**:分片器的失败复核原先只认 `tests::` / `process_session::tests::` 前缀,而 bin 目标用例名是 `agent::…::tests::…`,于是 3143 条里 2218 条永远进不了复核,偶发假红直接判红;现在按本片名单精确匹配,并且复核也失败时保留首次现场。 - **验证**:本机用 CI 那一片(`--shards=4 --shard-index=1` 的第 3 块)连跑:修复前 5 次红 3 次,修复后 18 次全绿;`node --test apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.test.mjs` 3 项通过。 - **关联**:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/entrypoints.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/thread_manager/dispatch.rs`、`apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.mjs`。 + +## 2026-10-01 AGC 壳 Rust 单测里等 mock provider 请求的 2 秒预算,在 CI 分片并发下偏窄 + +- **现象**:`tests::sessions::background_agent_runtime_can_write_memory_and_project_files` 在 CI `agc-rust-shard-4` 偶发红,`sessions.rs:405` 报 `second llm request: Timeout`——首个请求已经通过,只有第二次 provider 往返超时;失败复核同样红,整条用例 3.6 秒就结束。 +- **原因**:该断言只等了 `Duration::from_secs(2)`。一轮 mock 往返要等 Agent Runtime 后台线程跑到发请求那一步,CI 上 4 个 rust shard 与前端任务同机并发时单轮耗时会被拖到数秒;本机空载实测该请求 0.99–1.29 秒,同机负载下曾到 4.87 秒 / 5.18 秒。链路本身是好的,缺的是预算——这是分片并发放大出来的抖动,不是产品缺陷(同一次失败切回基线 commit 照样复现)。 +- **处理(现行口径)**:`src/tests/mod.rs` 新增 `MOCK_PROVIDER_REQUEST_TIMEOUT`(30 秒)作为「等一条 mock provider 请求到达」的唯一预算,`wait_for_captured_mock_request` 与 `sessions.rs` 里 7 处 `recv_timeout` 统一引用它;负向断言(确认「不会有下一个请求」的 200ms 窗口)保持不动。请求到达即返回,成功路径零成本,真断链仍会失败,只是失败得更晚。 +- **验证**:`cargo test --bin genarrative-ai-game-creator-shell tests::sessions:: -- --test-threads=1` 44 passed;`node apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.mjs --shards=4 --shard-index=4` 787 tests ok in 88.7s(正是原先失败的那一片)。 +- **关联**:`apps/ai-game-creator-shell/src-tauri/src/tests/sessions.rs`、`apps/ai-game-creator-shell/src-tauri/src/tests/mod.rs`。