From f6a3969219f97a8f5a4e3e5dca4fe48159a6a9d8 Mon Sep 17 00:00:00 2001 From: M4n5ter Date: Sun, 1 Nov 2026 17:04:56 +0800 Subject: [PATCH] test(runtime): align cleanup and crash recovery coverage Generated-by: OpenAI Codex --- .../tests/application_services/main.rs | 35 +++++++++++------ .../operations/resource_mutations.rs | 2 +- .../application_services/resource_ingress.rs | 2 +- .../tests/integration/native_requests.rs | 32 +-------------- .../cli/tests/integration/candidate/crash.rs | 39 ++++++++++++++++--- 5 files changed, 61 insertions(+), 49 deletions(-) diff --git a/platform/host-js/tests/application_services/main.rs b/platform/host-js/tests/application_services/main.rs index c67ca2bff8e..3fafe80e359 100644 --- a/platform/host-js/tests/application_services/main.rs +++ b/platform/host-js/tests/application_services/main.rs @@ -1536,7 +1536,7 @@ async fn javascript_forwarding_uses_exact_local_imports_original_evidence_and_ac .unwrap(); } #[tokio::test] -async fn javascript_catch_cannot_acknowledge_child_cleanup_or_release_parent_and_child_capacity() { +async fn javascript_caught_child_cleanup_retains_custody_without_poisoning_the_caller() { tokio::time::timeout(Duration::from_secs(15), async { let (provider, consumer) = attached_packages(); let wrapper = script_forwarder(Realm::Dedicated); @@ -1575,7 +1575,7 @@ async fn javascript_catch_cannot_acknowledge_child_cleanup_or_release_parent_and assert_eq!( client .start( - binding, + binding.clone(), "echo".into(), json!("fail-cleanup-error"), scope(&issuer, true), @@ -1591,21 +1591,34 @@ async fn javascript_catch_cannot_acknowledge_child_cleanup_or_release_parent_and assert!(weak.upgrade().is_some()); let snapshot = client.snapshot(); assert!(snapshot.cleanup_error.unwrap().contains("unconfirmed")); - for instance in [id(1), id(4)] { + for (instance, failed) in [(id(1), true), (id(4), false)] { let state = snapshot .instances .iter() .find(|s| s.activation.instance() == &instance) .unwrap(); - assert_eq!( - state.calls, 1, - "both actual child and parent keep their core work" - ); - assert_eq!(state.phase, Phase::Retiring); + assert_eq!(state.failures.is_empty(), !failed); } - // Use a real binding captured before retirement: input preparation runs - // before final admission, so this proves both actual 32 MiB charges remain. - let (attachments, rejected) = bytes(5); + let (attachments, released) = bytes(5); + assert_eq!( + client + .start( + binding, + "echo".into(), + json!("healthy"), + scope(&issuer, true), + attachments.into(), + ) + .await + .unwrap() + .result() + .await + .unwrap(), + json!("xxxxx") + ); + assert!(released.upgrade().is_none()); + // The failed leaf still holds half of the 64 MiB Host window. + let (attachments, rejected) = bytes(32 * 1024 * 1024 + 1); assert!( client .start( diff --git a/platform/host-js/tests/application_services/operations/resource_mutations.rs b/platform/host-js/tests/application_services/operations/resource_mutations.rs index 66d0a635de7..20c64ed8fd1 100644 --- a/platform/host-js/tests/application_services/operations/resource_mutations.rs +++ b/platform/host-js/tests/application_services/operations/resource_mutations.rs @@ -343,7 +343,7 @@ async fn failed_resource_mutation_keeps_original_snapshot_and_capacity_after_bus let completed=client.mutate(prepared,recorded).await.unwrap().completion().await; assert!(completed.cleanup.is_err()); assert!(matches!(completed.reply.receipt,Receipt::Recorded {outcome:OperationOutcome::Returned {ref value},cleanup:OperationCleanup::Failed {..}} if value==&json!("xxxxx"))); - assert!(original_scope.finish().await.is_err());assert!(weak.upgrade().is_some()); + original_scope.finish().await.unwrap();assert!(weak.upgrade().is_some()); let (resources,rejected)=bytes(1); let failure=client.prepare_mutation(binding,"echo".into(),json!("public"),scope(&issuer,true),Uuid::new_v4(),resources.into()).await.err().unwrap(); assert!(failure.contains("budget"));assert!(rejected.upgrade().is_none()); diff --git a/platform/host-js/tests/application_services/resource_ingress.rs b/platform/host-js/tests/application_services/resource_ingress.rs index 72a2097dce1..197128eb686 100644 --- a/platform/host-js/tests/application_services/resource_ingress.rs +++ b/platform/host-js/tests/application_services/resource_ingress.rs @@ -477,7 +477,7 @@ async fn failed_captured_cleanup_retains_all_payload_capacity_without_a_completi permit(&f.started).await; drop(accepted); f.release.add_permits(1); - assert!(source.finish().await.is_err()); + source.finish().await.unwrap(); let next = scope(&f.issuer, true); assert!( f.client diff --git a/platform/transport/tests/integration/native_requests.rs b/platform/transport/tests/integration/native_requests.rs index 2d3b4d3fcea..34749b70ef7 100644 --- a/platform/transport/tests/integration/native_requests.rs +++ b/platform/transport/tests/integration/native_requests.rs @@ -43,7 +43,6 @@ use serde_json::{Value, json}; use std::{ collections::BTreeMap, sync::{Arc, Mutex}, - time::Duration, }; use tokio_util::sync::CancellationToken; use uuid::Uuid; @@ -534,28 +533,9 @@ async fn native_requests_preserve_unknown_receipts_and_independent_failed_cleanu cleanup: maka_plugin_core::operation::OperationCleanup::Failed { .. } } )); - assert!( - job.finish() - .await - .unwrap_err() - .contains("cleanup unconfirmed") - ); + job.finish().await.unwrap(); assert!(app.shutdown().await.is_err()); - let deadline = std::time::Instant::now() + Duration::from_secs(3); - let snapshot = loop { - let snapshot = app.client().snapshot(); - if snapshot.instances.iter().any(|status| { - status.activation.instance() == &support::identity(13) - && !status.failures.is_empty() - }) { - break snapshot; - } - assert!( - std::time::Instant::now() < deadline, - "native owner close did not publish original request cleanup failure" - ); - tokio::task::yield_now().await; - }; + let snapshot = app.client().snapshot(); assert!(!snapshot.closed); assert!(snapshot.cleanup_error.is_some()); assert_eq!(effects.load(std::sync::atomic::Ordering::SeqCst), 1); @@ -566,14 +546,6 @@ async fn native_requests_preserve_unknown_receipts_and_independent_failed_cleanu "retained negative cleanup fixture Root: {}", retained.display() ); - assert!( - snapshot - .instances - .iter() - .filter(|status| status.activation.instance() == &support::identity(13)) - .any(|status| status.resources > 0 && !status.failures.is_empty()), - "original native caller retains its failed owner resource fence" - ); assert!( snapshot .instances diff --git a/products/maka/cli/tests/integration/candidate/crash.rs b/products/maka/cli/tests/integration/candidate/crash.rs index 50f4b9cbc7e..f46872b16f1 100644 --- a/products/maka/cli/tests/integration/candidate/crash.rs +++ b/products/maka/cli/tests/integration/candidate/crash.rs @@ -22,8 +22,16 @@ use crate::terminal::support::{ModelRequest, configure, mutate}; use maka_app_client::{Client as AppClient, Journal}; use maka_host_runtime::application::host::startup; use serde_json::json; -use std::sync::{Arc, atomic::AtomicUsize}; -use tokio::{net::TcpListener, sync::mpsc}; +use std::{ + os::unix::process::ExitStatusExt, + process::Stdio, + sync::{Arc, atomic::AtomicUsize}, +}; +use tokio::{ + io::{AsyncBufReadExt, BufReader}, + net::TcpListener, + sync::mpsc, +}; async fn activity(client: &AppClient, session: &str) -> Value { client @@ -60,7 +68,23 @@ async fn killed_host_leaves_an_ended_turn_and_a_session_that_takes_messages() { } }); tokio::time::timeout(Duration::from_secs(90), async { - let host = startup::connect_root(executable, &root).await.unwrap(); + let mut first_host = tokio::process::Command::new(executable) + .args(["host", "serve", "--root"]) + .arg(&root) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .kill_on_drop(true) + .spawn() + .unwrap(); + let mut ready = String::new(); + BufReader::new(first_host.stdout.take().unwrap()) + .read_line(&mut ready).await.unwrap(); + let ready: Value = serde_json::from_str(&ready).unwrap(); + let first_epoch = uuid::Uuid::parse_str(ready["hostEpoch"].as_str().unwrap()).unwrap(); + let host = Client::connect( + Path::new(ready["socketPath"].as_str().unwrap()), Some(first_epoch), + ).await.unwrap(); let client = AppClient::new( host, Journal::open(&journal).await.unwrap(), @@ -77,13 +101,16 @@ async fn killed_host_leaves_an_ended_turn_and_a_session_that_takes_messages() { let held = requests.recv().await.unwrap(); assert_ne!(activity(&client, &session).await, "idle"); - let pid = discovery::read(&root).unwrap().pid.get(); - assert_eq!(unsafe { libc::kill(pid as libc::pid_t, libc::SIGKILL) }, 0); + assert_eq!(discovery::read(&root).unwrap().pid.get(), first_host.id().unwrap()); + first_host.start_kill().unwrap(); + assert_eq!(first_host.wait().await.unwrap().signal(), Some(libc::SIGKILL)); drop(held); drop(client); + fixture.begin_admission(); let host = startup::connect_root(executable, &root).await.unwrap(); - assert_ne!(discovery::read(&root).unwrap().pid.get(), pid); + fixture.adopt(); + assert_ne!(host.epoch(), first_epoch); let client = AppClient::new( host, Journal::open(&journal).await.unwrap(),