From c20420b96d0c9b475c3ab2870dd64af0c6186c94 Mon Sep 17 00:00:00 2001 From: Leonardo Araujo dos Santos Date: Sun, 8 Nov 2026 00:18:02 -0400 Subject: [PATCH] test: wait for activity deregistration; give the Windows rewind gate time The binary worker returns its path permit before its activity scope drops and deregisters on the worker thread, so a single read of the registry right after shutdown could still see it on a loaded runner. Wait up to 5 s for it to empty, at both checks. reviewed_recovery_busy_confirmation_requires_interrupt_ack_and_a_new_review gave a background exec 10 s to reach the scripted model, which a loaded Windows runner can exceed (it failed main's Windows build and passed on others). Allow 60 s, as other Windows paths do, and report the exec's exit status and stderr if it ends first. --- crates/cyber-cli/tests/branches_cli.rs | 10 ++++++++-- .../src/runtime/binary_documents_tests.rs | 18 ++++++++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/crates/cyber-cli/tests/branches_cli.rs b/crates/cyber-cli/tests/branches_cli.rs index 1587e28c..45e07821 100644 --- a/crates/cyber-cli/tests/branches_cli.rs +++ b/crates/cyber-cli/tests/branches_cli.rs @@ -672,11 +672,17 @@ fn reviewed_recovery_busy_confirmation_requires_interrupt_ack_and_a_new_review() .command(&["exec", "--session", &id, "--format", "json", "GATE"]) .stdin(std::process::Stdio::null()) .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) + .stderr(std::process::Stdio::piped()) .spawn() .unwrap(); - let deadline = std::time::Instant::now() + Duration::from_secs(10); + // A loaded Windows runner can take well over 10 s to start exec and reach the model. + let deadline = std::time::Instant::now() + Duration::from_secs(60); while model.held().is_empty() { + if let Some(status) = gated.try_wait().unwrap() { + let mut stderr = String::new(); + std::io::Read::read_to_string(gated.stderr.as_mut().unwrap(), &mut stderr).unwrap(); + panic!("exec ended before GATE reached the provider ({status}): {stderr}"); + } assert!( std::time::Instant::now() < deadline, "GATE never reached provider" diff --git a/crates/cyber-server/src/runtime/binary_documents_tests.rs b/crates/cyber-server/src/runtime/binary_documents_tests.rs index 2be545b7..c275f81c 100644 --- a/crates/cyber-server/src/runtime/binary_documents_tests.rs +++ b/crates/cyber-server/src/runtime/binary_documents_tests.rs @@ -245,7 +245,7 @@ async fn disposed_binary_request_retains_worker_ownership_until_actual_exit() { .unwrap() .unwrap(); assert_eq!(paths.available_permits(), 1); - assert!(runtime.inner.activities.lock().unwrap().is_empty()); + activities_drained(&runtime).await; } #[tokio::test] @@ -377,7 +377,7 @@ async fn unwound_binary_worker_does_not_claim_file_success_or_poison_native_proc .location_uncertain .load(std::sync::atomic::Ordering::SeqCst) ); - assert!(runtime.inner.activities.lock().unwrap().is_empty()); + activities_drained(&runtime).await; runtime.shutdown().await; } @@ -543,3 +543,17 @@ async fn recovery_requires_the_configured_exclusive_document_and_leaves_generic_ drop((generic, binary)); runtime.shutdown().await; } + +/// The worker returns its path permit before its activity scope drops and deregisters +/// on the worker's own thread, so the registry empties shortly after; wait for that +/// rather than read it once. +async fn activities_drained(runtime: &Runtime) { + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5); + while !runtime.inner.activities.lock().unwrap().is_empty() { + assert!( + std::time::Instant::now() < deadline, + "an activity outlived its worker" + ); + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + } +}