修复自主构建测试项目锁竞态
将纯继续契约测试改为异步并等待子任务持久化终态。 等待 Agent lane 释放后再执行后续 manifest 写入,避免后台终态投影竞争项目锁。
This commit is contained in:
+14
-2
@@ -925,8 +925,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(
|
||||
@@ -1093,6 +1093,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");
|
||||
// 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