From 2065fe5e1126fb2e5a33b362fb2a2a94a5d99f2e Mon Sep 17 00:00:00 2001 From: Nils Andreas Svee Date: Tue, 23 Jun 2026 21:38:23 +0700 Subject: [PATCH] test(cli): add comprehensive tests for multi-target resolution and output Add unit tests for target normalization and address family filtering, and expand integration tests to verify column rendering and CSV output for multiple targets. Update subscriber config to drop oldest events under backpressure for better CLI stability. --- crates/irtt-cli/src/cmd/client/args.rs | 51 +++++++++++ crates/irtt-cli/src/cmd/client/run.rs | 6 +- crates/irtt-cli/tests/client_multi_target.rs | 95 ++++++++++++++++++++ 3 files changed, 151 insertions(+), 1 deletion(-) diff --git a/crates/irtt-cli/src/cmd/client/args.rs b/crates/irtt-cli/src/cmd/client/args.rs index f2bc7f9..11e7530 100644 --- a/crates/irtt-cli/src/cmd/client/args.rs +++ b/crates/irtt-cli/src/cmd/client/args.rs @@ -398,6 +398,57 @@ mod tests { assert_eq!(specs[1].label, "host-a:2112#2"); } + #[test] + fn duplicate_resolved_target_addresses_are_rejected() { + let args = parse(&["127.0.0.1:2112", "127.0.0.1"]).unwrap(); + let err = args.resolved_managed_targets().unwrap_err(); + + assert!(err.contains("duplicate resolved target address 127.0.0.1:2112")); + } + + #[test] + fn target_normalization_matches_single_target_forms() { + assert_eq!(normalize_target_addr("127.0.0.1"), "127.0.0.1:2112"); + assert_eq!(normalize_target_addr("127.0.0.1:1234"), "127.0.0.1:1234"); + assert_eq!(normalize_target_addr("example.com"), "example.com:2112"); + assert_eq!( + normalize_target_addr("example.com:1234"), + "example.com:1234" + ); + assert_eq!(normalize_target_addr("[::1]"), "[::1]:2112"); + assert_eq!(normalize_target_addr("[::1]:1234"), "[::1]:1234"); + assert_eq!(normalize_target_addr("::1"), "[::1]:2112"); + } + + #[test] + fn target_resolution_respects_family_filters() { + let config = ClientConfig::default(); + assert_eq!( + resolve_cli_target("127.0.0.1", &config).unwrap(), + "127.0.0.1:2112".parse().unwrap() + ); + assert_eq!( + resolve_cli_target("[::1]", &config).unwrap(), + "[::1]:2112".parse().unwrap() + ); + assert_eq!( + resolve_cli_target("::1", &config).unwrap(), + "[::1]:2112".parse().unwrap() + ); + + let mut ipv4_only = ClientConfig::default(); + ipv4_only.socket_config.ipv4_only = true; + assert!(resolve_cli_target("127.0.0.1", &ipv4_only) + .unwrap() + .is_ipv4()); + assert!(resolve_cli_target("::1", &ipv4_only).is_err()); + + let mut ipv6_only = ClientConfig::default(); + ipv6_only.socket_config.ipv6_only = true; + assert!(resolve_cli_target("::1", &ipv6_only).unwrap().is_ipv6()); + assert!(resolve_cli_target("127.0.0.1", &ipv6_only).is_err()); + } + #[test] fn invalid_labelled_target_syntax_is_rejected() { assert!(parse(&["--target", "missing-equals"]).is_err()); diff --git a/crates/irtt-cli/src/cmd/client/run.rs b/crates/irtt-cli/src/cmd/client/run.rs index 5dc9834..8fa61fa 100644 --- a/crates/irtt-cli/src/cmd/client/run.rs +++ b/crates/irtt-cli/src/cmd/client/run.rs @@ -285,7 +285,11 @@ fn run_group_stream( managed_targets, SubscriberConfig { capacity: 16_384, - overflow: SubscriberOverflow::Disconnect, + // CLI output is best-effort under sustained backpressure. Dropping + // stale rows keeps continuous runs attached to the running group + // instead of turning a full output queue into a disconnected + // subscriber and a potentially blocking join. + overflow: SubscriberOverflow::DropOldest, }, )?; diff --git a/crates/irtt-cli/tests/client_multi_target.rs b/crates/irtt-cli/tests/client_multi_target.rs index 4c32305..79b2051 100644 --- a/crates/irtt-cli/tests/client_multi_target.rs +++ b/crates/irtt-cli/tests/client_multi_target.rs @@ -28,6 +28,101 @@ impl FakeServer { } } +#[test] +fn list_columns_succeeds_without_target() { + let output = Command::new(env!("CARGO_BIN_EXE_irtt-cli")) + .arg("--list-columns") + .output() + .unwrap(); + + assert!( + output.status.success(), + "status={:?}\nstderr={}", + output.status.code(), + String::from_utf8_lossy(&output.stderr) + ); + let stdout = String::from_utf8(output.stdout).unwrap(); + assert!(stdout.contains("Available event columns:"), "{stdout}"); + assert!(stdout.contains("target"), "{stdout}"); +} + +#[test] +fn single_target_default_table_omits_target_and_accepts_pacing() { + let server = start_echo_server(test_params( + Some(Duration::from_millis(30)), + Duration::from_millis(10), + )); + + let output = Command::new(env!("CARGO_BIN_EXE_irtt-cli")) + .args([ + "--duration", + "30ms", + "--interval", + "10ms", + "--pacing", + "burst", + "--header", + "always", + &server.addr.to_string(), + ]) + .output() + .unwrap(); + + server.join(); + + assert!( + output.status.success(), + "status={:?}\nstderr={}", + output.status.code(), + String::from_utf8_lossy(&output.stderr) + ); + let stdout = String::from_utf8(output.stdout).unwrap(); + let header = stdout.lines().next().unwrap_or_default(); + assert!(!header.split_whitespace().any(|column| column == "target")); +} + +#[test] +fn single_target_custom_target_column_renders_positional_label() { + let server = start_echo_server(test_params( + Some(Duration::from_millis(30)), + Duration::from_millis(10), + )); + let target = server.addr.to_string(); + + let output = Command::new(env!("CARGO_BIN_EXE_irtt-cli")) + .args([ + "--duration", + "30ms", + "--interval", + "10ms", + "--format", + "csv", + "--columns", + "target,seq", + "--header", + "never", + &target, + ]) + .output() + .unwrap(); + + server.join(); + + assert!( + output.status.success(), + "status={:?}\nstderr={}", + output.status.code(), + String::from_utf8_lossy(&output.stderr) + ); + let stdout = String::from_utf8(output.stdout).unwrap(); + assert!( + stdout + .lines() + .any(|line| line.starts_with(&format!("{target},"))), + "{stdout}" + ); +} + #[test] fn multi_target_csv_emits_rows_for_both_labels() { let params = test_params(Some(Duration::from_millis(40)), Duration::from_millis(10));