合并 master 的自主构建测试锁竞态修复
冲突在 game_chat_pure_continue_inherits_failed_root_semantics_and_manifest_progress: 两侧是同一竞态的互斥策略。M0A-2 持有 code-prototype 的 lane 锁让 child 起不来,再释放;master 改为让 child 跑到终态并等待其持久 manifest 投影, 为此把用例改成 tokio::test。 取 master 方案并移除本侧 lane 锁:两者并存会自相矛盾——锁着 lane 时 child 到不了终态,等待必然空转。且如 master 注释所述,lane 释放本身可能早于迟到 的 manifest 投影,仅靠 lane 锁并不能保证投影已落地。 验证:game_chat_pure_continue_inherits_failed_root_semantics_and_manifest_progress 与 agent_runtime_reject_revalidates_pending_action_after_lock_wait 均通过, 后者正是本次 master 修复针对的 CI 失败用例。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+14
-7
@@ -1011,8 +1011,8 @@ fn autonomous_continuation_intent_is_exact_and_does_not_swallow_new_requirements
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn game_chat_pure_continue_inherits_failed_root_semantics_and_manifest_progress() {
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn game_chat_pure_continue_inherits_failed_root_semantics_and_manifest_progress() {
|
||||
let _config_guard = crate::tests::write_test_local_config("{}".to_string());
|
||||
let original_task = "做一个水晶主题的俄罗斯方块,完成移动、旋转、消行和重开";
|
||||
let (_temporary, root, original_state, original_contract) = autonomous_fixture_with_source(
|
||||
@@ -1170,10 +1170,6 @@ fn game_chat_pure_continue_inherits_failed_root_semantics_and_manifest_progress(
|
||||
"继续完成继承的现有游戏目标",
|
||||
)
|
||||
.expect("persist continued root Supervisor decision");
|
||||
let code_runtime_lock =
|
||||
try_acquire_game_creator_agent_runtime_task_lock(&root, "code-prototype")
|
||||
.expect("acquire continued code-prototype runtime lane")
|
||||
.expect("continued code-prototype runtime lane is free");
|
||||
let scheduled = schedule_autonomous_game_build_ready_tasks_at(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
@@ -1183,7 +1179,18 @@ fn game_chat_pure_continue_inherits_failed_root_semantics_and_manifest_progress(
|
||||
.expect("continued root must pass the scheduler contract gate");
|
||||
assert_eq!(scheduled.len(), 1);
|
||||
assert_eq!(scheduled[0].state.agent_id, "code-prototype");
|
||||
drop(code_runtime_lock);
|
||||
// Scheduling starts the child worker asynchronously. Wait for its
|
||||
// durable terminal projection and lane release before mutating the same
|
||||
// project; a lane release alone can precede a late manifest projection.
|
||||
let _ = crate::tests::wait_for_agent_runtime_manifest_projection_async(
|
||||
&root,
|
||||
"code-prototype",
|
||||
&scheduled[0].state.run_id,
|
||||
"failed",
|
||||
"budget-exhausted",
|
||||
GameCreationAppTaskStatus::Failed,
|
||||
)
|
||||
.await;
|
||||
update_manifest_task_status_at(
|
||||
&root,
|
||||
"code-prototype",
|
||||
|
||||
@@ -475,7 +475,7 @@ fn wait_for_agent_runtime_terminal_and_lane_release(
|
||||
);
|
||||
}
|
||||
|
||||
async fn wait_for_agent_runtime_terminal_and_lane_release_async(
|
||||
pub(crate) async fn wait_for_agent_runtime_terminal_and_lane_release_async(
|
||||
root: &Path,
|
||||
agent_id: &str,
|
||||
run_id: &str,
|
||||
@@ -526,6 +526,58 @@ async fn wait_for_agent_runtime_terminal_and_lane_release_async(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn wait_for_agent_runtime_manifest_projection_async(
|
||||
root: &Path,
|
||||
agent_id: &str,
|
||||
run_id: &str,
|
||||
runtime_status: &str,
|
||||
phase: &str,
|
||||
manifest_status: GameCreationAppTaskStatus,
|
||||
) -> AgentRuntimeResult {
|
||||
let deadline = Instant::now() + Duration::from_secs(10);
|
||||
let mut terminal = wait_for_agent_runtime_terminal_and_lane_release_async(
|
||||
root, agent_id, run_id, runtime_status, phase,
|
||||
)
|
||||
.await;
|
||||
let mut stable_samples = 0_u8;
|
||||
loop {
|
||||
if let Ok(project_lock) = acquire_game_creator_agent_runtime_project_write_lock_with_wait(
|
||||
root,
|
||||
"test.wait_runtime_manifest_projection",
|
||||
) {
|
||||
let manifest = read_manifest_for_project(root)
|
||||
.expect("read manifest while waiting for terminal projection");
|
||||
let projected = manifest
|
||||
.tasks
|
||||
.iter()
|
||||
.find(|task| task.id == agent_id)
|
||||
.is_some_and(|task| task.status == manifest_status);
|
||||
let reread = read_game_creator_agent_runtime_at(root, agent_id)
|
||||
.expect("reread runtime while waiting for terminal projection");
|
||||
drop(project_lock);
|
||||
if projected
|
||||
&& reread.state.run_id == run_id
|
||||
&& reread.state.status == runtime_status
|
||||
&& reread.state.phase == phase
|
||||
{
|
||||
stable_samples = stable_samples.saturating_add(1);
|
||||
terminal = reread;
|
||||
if stable_samples >= 2 {
|
||||
return terminal;
|
||||
}
|
||||
} else {
|
||||
stable_samples = 0;
|
||||
terminal = reread;
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
Instant::now() < deadline,
|
||||
"runtime terminal manifest projection did not settle for {agent_id}/{run_id}"
|
||||
);
|
||||
tokio::time::sleep(Duration::from_millis(20)).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_agent_runtime_lane_release_async(
|
||||
root: &Path,
|
||||
agent_id: &str,
|
||||
|
||||
Reference in New Issue
Block a user