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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D9BWMXKKNpid7XM6oFbyYq
This commit is contained in:
2026-08-10 14:42:57 +02:00
co-authored by Claude Opus 5
parent 29df988d9d
commit 7f605f361e
2 changed files with 26 additions and 17 deletions
+12
View File
@@ -5,6 +5,8 @@ use std::process::Stdio;
use std::time::Duration; use std::time::Duration;
use tempfile::TempDir; use tempfile::TempDir;
use tokio::process::{Child, Command}; use tokio::process::{Child, Command};
use xy_ipc::Client;
use xy_protocol::rpc::{ServerSummary, methods};
pub struct Harness { pub struct Harness {
pub tmp: TempDir, 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<ServerSummary> {
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) { pub async fn run_cli(&self, xy_bin: &PathBuf, args: &[&str]) -> (i32, String, String) {
let out = Command::new(xy_bin) let out = Command::new(xy_bin)
.args(args) .args(args)
+14 -17
View File
@@ -1,5 +1,6 @@
mod common; mod common;
use common::*; use common::*;
use xy_protocol::ServerState;
#[tokio::test] #[tokio::test]
async fn uptime_grows_while_a_server_stays_running() { 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; h.start_daemon(&xy).await;
for _ in 0..40 { for _ in 0..40 {
let (_c, out, _e) = h.run_cli(&xy, &["list"]).await; let rows = h.list_rpc().await;
if out.contains("alpha") && out.contains("running") { if rows
.iter()
.any(|r| r.name == "alpha" && r.state == ServerState::Running)
{
break; break;
} }
tokio::time::sleep(std::time::Duration::from_millis(100)).await; 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; 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 assert!(
.lines() alpha.uptime_secs.is_some_and(|secs| secs >= 1),
.find(|l| l.starts_with("alpha")) "a server running for over a second must report at least 1s of uptime; row: {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:?}"
); );
} }