diff --git a/Cargo.lock b/Cargo.lock index 25d56f3c7..d74a84167 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2006,7 +2006,7 @@ dependencies = [ "progenitor-client", "regress", "reqwest 0.13.4", - "sandbox-driver", + "sandbox-driver 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c)", "serde", "serde_json", "serde_yaml", @@ -2145,7 +2145,7 @@ dependencies = [ "ring", "rmcp", "rustls", - "sandbox-driver", + "sandbox-driver 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c)", "sandbox-driver-host", "scopeguard", "semver", @@ -2523,7 +2523,7 @@ dependencies = [ "fabro-types", "fabro-util", "pebble-coding-agent", - "sandbox-driver", + "sandbox-driver 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c)", "sandbox-driver-host", "sandbox-driver-testing", "serde_json", @@ -2674,11 +2674,11 @@ dependencies = [ "percent-encoding", "rand 0.9.4", "reqwest 0.12.28", - "sandbox-driver", + "sandbox-driver 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c)", "sandbox-driver-daytona", "sandbox-driver-docker", "sandbox-driver-host", - "sandbox-driver-protocol", + "sandbox-driver-protocol 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c)", "sandbox-driver-testing", "serde", "serde_json", @@ -2880,7 +2880,7 @@ dependencies = [ "hex", "lithos-llm", "pebble-coding-agent", - "sandbox-driver", + "sandbox-driver 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c)", "serde", "serde_json", "sha2 0.10.9", @@ -2987,7 +2987,7 @@ dependencies = [ "httpmock", "lithos-llm", "pebble-coding-agent", - "sandbox-driver", + "sandbox-driver 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c)", "sandbox-driver-host", "scopeguard", "serde", @@ -5284,10 +5284,10 @@ dependencies = [ "async-trait", "petri-executor", "petri-ir", - "sandbox-driver", - "sandbox-driver-daytona-config", - "sandbox-driver-docker-config", - "sandbox-driver-protocol", + "sandbox-driver 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver.git?rev=64c14b89d078a4b34d1555092ad01d41541f7a7d)", + "sandbox-driver-daytona-config 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver.git?rev=64c14b89d078a4b34d1555092ad01d41541f7a7d)", + "sandbox-driver-docker-config 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver.git?rev=64c14b89d078a4b34d1555092ad01d41541f7a7d)", + "sandbox-driver-protocol 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver.git?rev=64c14b89d078a4b34d1555092ad01d41541f7a7d)", "serde", "serde_json", "sha2 0.10.9", @@ -5434,7 +5434,7 @@ dependencies = [ "petri-ir", "petri-steps", "petri-store", - "sandbox-driver", + "sandbox-driver 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver.git?rev=64c14b89d078a4b34d1555092ad01d41541f7a7d)", "serde", "serde_json", "smol_str", @@ -6303,7 +6303,24 @@ dependencies = [ [[package]] name = "sandbox-driver" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/sandbox-driver?rev=64c14b89d078a4b34d1555092ad01d41541f7a7d#64c14b89d078a4b34d1555092ad01d41541f7a7d" +source = "git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c#07600aa5c6695ec4c999da93c05d2cb78fe11b0c" +dependencies = [ + "async-trait", + "globset", + "humantime", + "rand 0.10.1", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "sandbox-driver" +version = "0.1.0" +source = "git+https://github.com/lithoscomputer/sandbox-driver.git?rev=64c14b89d078a4b34d1555092ad01d41541f7a7d#64c14b89d078a4b34d1555092ad01d41541f7a7d" dependencies = [ "async-trait", "globset", @@ -6320,7 +6337,7 @@ dependencies = [ [[package]] name = "sandbox-driver-daytona" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/sandbox-driver?rev=64c14b89d078a4b34d1555092ad01d41541f7a7d#64c14b89d078a4b34d1555092ad01d41541f7a7d" +source = "git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c#07600aa5c6695ec4c999da93c05d2cb78fe11b0c" dependencies = [ "anyhow", "async-trait", @@ -6330,11 +6347,11 @@ dependencies = [ "hmac 0.12.1", "rand 0.10.1", "reqwest 0.13.4", - "sandbox-driver", - "sandbox-driver-daytona-config", + "sandbox-driver 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c)", + "sandbox-driver-daytona-config 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c)", "sandbox-driver-docker", - "sandbox-driver-docker-config", - "sandbox-driver-protocol", + "sandbox-driver-docker-config 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c)", + "sandbox-driver-protocol 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c)", "serde", "serde_json", "sha2 0.10.9", @@ -6347,9 +6364,19 @@ dependencies = [ [[package]] name = "sandbox-driver-daytona-config" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/sandbox-driver?rev=64c14b89d078a4b34d1555092ad01d41541f7a7d#64c14b89d078a4b34d1555092ad01d41541f7a7d" +source = "git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c#07600aa5c6695ec4c999da93c05d2cb78fe11b0c" dependencies = [ - "sandbox-driver-docker-config", + "sandbox-driver-docker-config 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c)", + "serde", + "serde_json", +] + +[[package]] +name = "sandbox-driver-daytona-config" +version = "0.1.0" +source = "git+https://github.com/lithoscomputer/sandbox-driver.git?rev=64c14b89d078a4b34d1555092ad01d41541f7a7d#64c14b89d078a4b34d1555092ad01d41541f7a7d" +dependencies = [ + "sandbox-driver-docker-config 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver.git?rev=64c14b89d078a4b34d1555092ad01d41541f7a7d)", "serde", "serde_json", ] @@ -6357,15 +6384,15 @@ dependencies = [ [[package]] name = "sandbox-driver-docker" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/sandbox-driver?rev=64c14b89d078a4b34d1555092ad01d41541f7a7d#64c14b89d078a4b34d1555092ad01d41541f7a7d" +source = "git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c#07600aa5c6695ec4c999da93c05d2cb78fe11b0c" dependencies = [ "anyhow", "async-trait", "bollard", "futures-util", - "sandbox-driver", - "sandbox-driver-docker-config", - "sandbox-driver-protocol", + "sandbox-driver 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c)", + "sandbox-driver-docker-config 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c)", + "sandbox-driver-protocol 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c)", "serde", "serde_json", "tar", @@ -6378,7 +6405,16 @@ dependencies = [ [[package]] name = "sandbox-driver-docker-config" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/sandbox-driver?rev=64c14b89d078a4b34d1555092ad01d41541f7a7d#64c14b89d078a4b34d1555092ad01d41541f7a7d" +source = "git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c#07600aa5c6695ec4c999da93c05d2cb78fe11b0c" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "sandbox-driver-docker-config" +version = "0.1.0" +source = "git+https://github.com/lithoscomputer/sandbox-driver.git?rev=64c14b89d078a4b34d1555092ad01d41541f7a7d#64c14b89d078a4b34d1555092ad01d41541f7a7d" dependencies = [ "serde", "serde_json", @@ -6387,13 +6423,13 @@ dependencies = [ [[package]] name = "sandbox-driver-host" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/sandbox-driver?rev=64c14b89d078a4b34d1555092ad01d41541f7a7d#64c14b89d078a4b34d1555092ad01d41541f7a7d" +source = "git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c#07600aa5c6695ec4c999da93c05d2cb78fe11b0c" dependencies = [ "anyhow", "async-trait", "nix 0.30.1", - "sandbox-driver", - "sandbox-driver-protocol", + "sandbox-driver 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c)", + "sandbox-driver-protocol 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c)", "serde", "serde_json", "tokio", @@ -6405,12 +6441,29 @@ dependencies = [ [[package]] name = "sandbox-driver-protocol" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/sandbox-driver?rev=64c14b89d078a4b34d1555092ad01d41541f7a7d#64c14b89d078a4b34d1555092ad01d41541f7a7d" +source = "git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c#07600aa5c6695ec4c999da93c05d2cb78fe11b0c" dependencies = [ "async-trait", "base64", "rand 0.10.1", - "sandbox-driver", + "sandbox-driver 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c)", + "serde", + "serde_json", + "sha2 0.10.9", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "sandbox-driver-protocol" +version = "0.1.0" +source = "git+https://github.com/lithoscomputer/sandbox-driver.git?rev=64c14b89d078a4b34d1555092ad01d41541f7a7d#64c14b89d078a4b34d1555092ad01d41541f7a7d" +dependencies = [ + "async-trait", + "base64", + "rand 0.10.1", + "sandbox-driver 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver.git?rev=64c14b89d078a4b34d1555092ad01d41541f7a7d)", "serde", "serde_json", "sha2 0.10.9", @@ -6422,10 +6475,10 @@ dependencies = [ [[package]] name = "sandbox-driver-testing" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/sandbox-driver?rev=64c14b89d078a4b34d1555092ad01d41541f7a7d#64c14b89d078a4b34d1555092ad01d41541f7a7d" +source = "git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c#07600aa5c6695ec4c999da93c05d2cb78fe11b0c" dependencies = [ "async-trait", - "sandbox-driver", + "sandbox-driver 0.1.0 (git+https://github.com/lithoscomputer/sandbox-driver?rev=07600aa5c6695ec4c999da93c05d2cb78fe11b0c)", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index 03a1548a4..d4abc7913 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,16 +90,19 @@ futures-util = "0.3" # before a generation serves, and a refusal of a replacement that reports # another resource namespace; #22 made the Daytona exec decoder resync on a # torn frame and report what it dropped as `ExecStreamingResult::output_loss` -# instead of failing the command). The CI plugin job installs the driver -# executables at the same rev, read from this file. -sandbox-driver = { git = "https://github.com/lithoscomputer/sandbox-driver", rev = "64c14b89d078a4b34d1555092ad01d41541f7a7d" } -sandbox-driver-protocol = { git = "https://github.com/lithoscomputer/sandbox-driver", rev = "64c14b89d078a4b34d1555092ad01d41541f7a7d" } -sandbox-driver-host = { git = "https://github.com/lithoscomputer/sandbox-driver", rev = "64c14b89d078a4b34d1555092ad01d41541f7a7d" } -sandbox-driver-docker = { git = "https://github.com/lithoscomputer/sandbox-driver", rev = "64c14b89d078a4b34d1555092ad01d41541f7a7d" } -sandbox-driver-docker-config = { git = "https://github.com/lithoscomputer/sandbox-driver", rev = "64c14b89d078a4b34d1555092ad01d41541f7a7d" } -sandbox-driver-daytona = { git = "https://github.com/lithoscomputer/sandbox-driver", rev = "64c14b89d078a4b34d1555092ad01d41541f7a7d" } -sandbox-driver-daytona-config = { git = "https://github.com/lithoscomputer/sandbox-driver", rev = "64c14b89d078a4b34d1555092ad01d41541f7a7d" } -sandbox-driver-testing = { git = "https://github.com/lithoscomputer/sandbox-driver", rev = "64c14b89d078a4b34d1555092ad01d41541f7a7d" } +# instead of failing the command; #23 made an idle Host sentinel reap its +# own process group once its owning provider pid is gone, so a provider +# that is killed rather than stopped leaves no sentinel behind). The CI +# plugin job installs the driver executables at the same rev, read from +# this file. +sandbox-driver = { git = "https://github.com/lithoscomputer/sandbox-driver", rev = "07600aa5c6695ec4c999da93c05d2cb78fe11b0c" } +sandbox-driver-protocol = { git = "https://github.com/lithoscomputer/sandbox-driver", rev = "07600aa5c6695ec4c999da93c05d2cb78fe11b0c" } +sandbox-driver-host = { git = "https://github.com/lithoscomputer/sandbox-driver", rev = "07600aa5c6695ec4c999da93c05d2cb78fe11b0c" } +sandbox-driver-docker = { git = "https://github.com/lithoscomputer/sandbox-driver", rev = "07600aa5c6695ec4c999da93c05d2cb78fe11b0c" } +sandbox-driver-docker-config = { git = "https://github.com/lithoscomputer/sandbox-driver", rev = "07600aa5c6695ec4c999da93c05d2cb78fe11b0c" } +sandbox-driver-daytona = { git = "https://github.com/lithoscomputer/sandbox-driver", rev = "07600aa5c6695ec4c999da93c05d2cb78fe11b0c" } +sandbox-driver-daytona-config = { git = "https://github.com/lithoscomputer/sandbox-driver", rev = "07600aa5c6695ec4c999da93c05d2cb78fe11b0c" } +sandbox-driver-testing = { git = "https://github.com/lithoscomputer/sandbox-driver", rev = "07600aa5c6695ec4c999da93c05d2cb78fe11b0c" } # pebble: the coding agent loop fabro runs its agent stages, Ask Fabro # sessions, hook evaluators, and `fabro exec` on. Pinned by rev to pebble # `main`. Pebble owns the `PortRoutes` trait fabro implements over its run diff --git a/lib/apps/fabro-cli/src/commands/run/runner.rs b/lib/apps/fabro-cli/src/commands/run/runner.rs index 275e5166c..e8120b462 100644 --- a/lib/apps/fabro-cli/src/commands/run/runner.rs +++ b/lib/apps/fabro-cli/src/commands/run/runner.rs @@ -20,6 +20,8 @@ use fabro_vault::{SecretStore, Vault}; use fabro_workflow::services::FabroRunToolServices; use futures::{SinkExt, StreamExt}; use jsonwebtoken::dangerous::insecure_decode; +#[cfg(unix)] +use nix::unistd; #[cfg(test)] use tokio::io::DuplexStream; use tokio::net::TcpStream; @@ -155,6 +157,17 @@ pub(super) async fn load_worker_vault(storage_dir: &Path) -> Result = None; while !done.is_cancelled() { let request = match build_worker_control_stream_request( @@ -367,6 +384,7 @@ async fn run_worker_control_manager( let _ = first_tx.send(Ok(())); } backoff = WORKER_CONTROL_RECONNECT_INITIAL_BACKOFF; + failing_since = None; match handle_worker_control_socket( &mut socket, &interviewer, @@ -407,6 +425,25 @@ async fn run_worker_control_manager( } Err(WorkerControlConnectError::Other(err)) => { tracing::debug!(error = %err, "Worker control stream connection failed"); + let now = Instant::now(); + failing_since.get_or_insert(now); + if control_loss_exceeded(failing_since, now, worker_is_orphaned()) { + let elapsed = + failing_since.map_or(Duration::ZERO, |since| now.duration_since(since)); + tracing::warn!( + elapsed = ?elapsed, + "Worker control stream unreachable; giving up" + ); + report_fatal_control_loss( + &interviewer, + &cancel_token, + &mut first_tx, + &mut fatal_tx, + format!("worker control stream unreachable for {elapsed:?}; giving up"), + ) + .await; + return; + } } } @@ -440,6 +477,33 @@ async fn sleep_or_done(done: &CancellationToken, delay: Duration) { } } +/// Whether the continuous connection-failure window that began at +/// `failing_since` has outlasted the applicable give-up limit. `None` means +/// no failure run is in progress, so it never fires. +fn control_loss_exceeded(failing_since: Option, now: Instant, orphaned: bool) -> bool { + let Some(since) = failing_since else { + return false; + }; + let limit = if orphaned { + WORKER_CONTROL_ORPHAN_GIVE_UP + } else { + WORKER_CONTROL_GIVE_UP + }; + now.duration_since(since) > limit +} + +/// Whether this worker has been reparented to pid 1. +fn worker_is_orphaned() -> bool { + #[cfg(unix)] + { + unistd::getppid().as_raw() == 1 + } + #[cfg(not(unix))] + { + false + } +} + fn next_worker_control_reconnect_backoff(current: Duration) -> Duration { current .saturating_mul(2) @@ -780,17 +844,19 @@ mod tests { use fabro_store::PlatformRecord; use fabro_types::{Principal, QuestionType, SystemActorKind, fixtures}; use fabro_vault::{SecretType, Vault}; - use tokio::time; + use tokio::time::{self, Instant}; use tokio_tungstenite::tungstenite::protocol::{Message as TestWebSocketMessage, Role}; use tokio_util::sync::CancellationToken; use super::super::petri_worker::PetriControls; use super::{ - AppliedWorkerControlDeliveryIds, RunControls, WorkerControlConnectError, - WorkerControlSocket, WorkerControls, WorkerTitlePhase, apply_worker_control_delivery_frame, - apply_worker_control_message, build_worker_control_stream_request, - connect_worker_control_stream, handle_worker_control_socket, initial_worker_title_phase, - load_worker_vault, next_worker_control_reconnect_backoff, worker_title, + AppliedWorkerControlDeliveryIds, RunControls, WORKER_CONTROL_GIVE_UP, + WORKER_CONTROL_ORPHAN_GIVE_UP, WORKER_CONTROL_RECONNECT_MAX_BACKOFF, + WorkerControlConnectError, WorkerControlSocket, WorkerControls, WorkerTitlePhase, + apply_worker_control_delivery_frame, apply_worker_control_message, + build_worker_control_stream_request, connect_worker_control_stream, control_loss_exceeded, + handle_worker_control_socket, initial_worker_title_phase, load_worker_vault, + next_worker_control_reconnect_backoff, spawn_worker_control_manager, worker_title, }; use crate::args::RunWorkerMode; @@ -1205,6 +1271,74 @@ mod tests { ); } + #[test] + fn control_loss_exceeded_fires_only_past_the_applicable_limit() { + let start = Instant::now(); + let at = |secs: u64| start + Duration::from_secs(secs); + + assert!(!control_loss_exceeded(None, at(600), false)); + assert!(!control_loss_exceeded(None, at(600), true)); + + assert!(!control_loss_exceeded(Some(start), at(5), false)); + assert!(!control_loss_exceeded(Some(start), at(5), true)); + + assert!(!control_loss_exceeded(Some(start), at(11), false)); + assert!(control_loss_exceeded(Some(start), at(11), true)); + + assert!(control_loss_exceeded(Some(start), at(61), false)); + assert!(control_loss_exceeded(Some(start), at(61), true)); + } + + #[cfg(unix)] + #[tokio::test(start_paused = true)] + async fn worker_control_manager_gives_up_when_server_stays_unreachable() { + let temp = tempfile::tempdir().unwrap(); + let socket_path = temp.path().join("missing.sock"); + let interviewer = Arc::new(ControlInterviewer::new()); + let cancel_token = CancellationToken::new(); + let mut question = Question::new("Approve?", QuestionType::YesNo); + question.id = "q-1".to_string(); + let ask_interviewer = Arc::clone(&interviewer); + let answer_task = tokio::spawn(async move { ask_interviewer.ask(question).await }); + tokio::task::yield_now().await; + + let started = Instant::now(); + let mut handle = spawn_worker_control_manager( + ServerTarget::unix_socket_path(&socket_path).unwrap(), + fixtures::RUN_1, + "worker-token".to_string(), + Arc::clone(&interviewer), + cancel_token.clone(), + test_controls(), + ); + + let first = handle.wait_for_first_connection().await; + let fatal = handle.fatal_control_loss().await; + let elapsed = started.elapsed(); + handle.finish(); + + assert!( + first.is_err(), + "first connection should fail with the manager" + ); + assert!( + fatal.to_string().contains("unreachable for"), + "unexpected fatal error: {fatal}" + ); + assert!(cancel_token.is_cancelled()); + assert_eq!( + answer_task.await.unwrap().answer.value, + AnswerValue::Interrupted + ); + // The orphan limit is the earliest the manager may give up; the full + // limit plus one capped backoff sleep is the latest. + assert!( + elapsed > WORKER_CONTROL_ORPHAN_GIVE_UP + && elapsed <= WORKER_CONTROL_GIVE_UP + WORKER_CONTROL_RECONNECT_MAX_BACKOFF, + "gave up after {elapsed:?}" + ); + } + #[tokio::test(start_paused = true)] async fn worker_control_socket_times_out_without_liveness() { let (worker_io, _server_io) = tokio::io::duplex(1024); diff --git a/lib/foundation/fabro-test/src/lib.rs b/lib/foundation/fabro-test/src/lib.rs index cef2d1a8c..96f901de4 100644 --- a/lib/foundation/fabro-test/src/lib.rs +++ b/lib/foundation/fabro-test/src/lib.rs @@ -1286,10 +1286,18 @@ fn parse_tmp_daemon_ps_line(line: &str) -> Option<(u32, u64, &str)> { Some((pid, elapsed_secs, command)) } +/// Matches the process title of a test-spawned `fabro server` daemon: a Unix +/// socket under a `/tmp/.tmp*` tempdir, or anywhere below a +/// `TestContext` root (`.ft-