From 7f605f361e23de4170f98a2d4a992aea9db5e697 Mon Sep 17 00:00:00 2001 From: Anders Olsson Date: Mon, 10 Aug 2026 14:42:57 +0200 Subject: [PATCH] test(uptime): assert on the wire value, not the rendered table The test read uptime with split_whitespace().nth(4), which assumed the column held a single token. That held only because the test measures about a second of uptime; once the formatter emits two tokens above a minute, raising the sleep would have shifted every later column while the assertion kept passing. Call the list RPC directly and assert on ServerSummary::uptime_secs. The daemon produces that value and the test only reads it, so the assertion no longer depends on a layout the project wrote itself. Mutation-proved: replacing the handler's elapsed().as_secs() with 0 fails the test on uptime_secs: Some(0). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01D9BWMXKKNpid7XM6oFbyYq --- crates/xy/tests/common/mod.rs | 12 ++++++++++++ crates/xy/tests/uptime.rs | 31 ++++++++++++++----------------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/crates/xy/tests/common/mod.rs b/crates/xy/tests/common/mod.rs index bd6ee86..90515a1 100644 --- a/crates/xy/tests/common/mod.rs +++ b/crates/xy/tests/common/mod.rs @@ -5,6 +5,8 @@ use std::process::Stdio; use std::time::Duration; use tempfile::TempDir; use tokio::process::{Child, Command}; +use xy_ipc::Client; +use xy_protocol::rpc::{ServerSummary, methods}; pub struct Harness { pub tmp: TempDir, @@ -62,6 +64,16 @@ impl Harness { } } + /// The `list` RPC result straight off the wire, before any CLI rendering. + pub async fn list_rpc(&self) -> Vec { + let mut client = Client::connect(&self.socket).await.expect("connect daemon"); + + client + .call_no_params(methods::LIST) + .await + .expect("list rpc") + } + pub async fn run_cli(&self, xy_bin: &PathBuf, args: &[&str]) -> (i32, String, String) { let out = Command::new(xy_bin) .args(args) diff --git a/crates/xy/tests/uptime.rs b/crates/xy/tests/uptime.rs index 0f00e49..13cd838 100644 --- a/crates/xy/tests/uptime.rs +++ b/crates/xy/tests/uptime.rs @@ -1,5 +1,6 @@ mod common; use common::*; +use xy_protocol::ServerState; #[tokio::test] async fn uptime_grows_while_a_server_stays_running() { @@ -10,8 +11,11 @@ async fn uptime_grows_while_a_server_stays_running() { h.start_daemon(&xy).await; for _ in 0..40 { - let (_c, out, _e) = h.run_cli(&xy, &["list"]).await; - if out.contains("alpha") && out.contains("running") { + let rows = h.list_rpc().await; + if rows + .iter() + .any(|r| r.name == "alpha" && r.state == ServerState::Running) + { break; } tokio::time::sleep(std::time::Duration::from_millis(100)).await; @@ -19,22 +23,15 @@ async fn uptime_grows_while_a_server_stays_running() { tokio::time::sleep(std::time::Duration::from_millis(1_200)).await; - let (code, out, _e) = h.run_cli(&xy, &["list"]).await; + let rows = h.list_rpc().await; - assert_eq!(code, 0); + let alpha = rows + .iter() + .find(|r| r.name == "alpha") + .unwrap_or_else(|| panic!("no alpha row in: {rows:?}")); - let row = out - .lines() - .find(|l| l.starts_with("alpha")) - .unwrap_or_else(|| panic!("no alpha row in:\n{out}")); - - let uptime = row - .split_whitespace() - .nth(4) - .unwrap_or_else(|| panic!("no uptime column in row: {row:?}")); - - assert_ne!( - uptime, "0s", - "a server running for over a second must not report 0s uptime; row: {row:?}" + assert!( + alpha.uptime_secs.is_some_and(|secs| secs >= 1), + "a server running for over a second must report at least 1s of uptime; row: {alpha:?}" ); }