diff --git a/crates/irtt-cli/src/lib.rs b/crates/irtt-cli/src/lib.rs index f8e9687..57e4294 100644 --- a/crates/irtt-cli/src/lib.rs +++ b/crates/irtt-cli/src/lib.rs @@ -24,7 +24,7 @@ const MAX_UDP_PAYLOAD_LENGTH: u32 = 65_507; pub struct CliArgs { pub server: String, - #[arg(long, default_value = "10s", value_parser = parse_duration)] + #[arg(long, default_value = "10s", value_parser = parse_test_duration)] pub duration: Duration, #[arg(long, default_value = "1s", value_parser = parse_duration)] @@ -50,7 +50,7 @@ impl CliArgs { pub fn to_client_config(&self) -> ClientConfig { ClientConfig { server_addr: self.server.clone(), - duration: Some(self.duration), + duration: (!self.is_continuous()).then_some(self.duration), interval: self.interval, length: self.length, received_stats: ReceivedStats::Both, @@ -64,6 +64,10 @@ impl CliArgs { ..ClientConfig::default() } } + + pub fn is_continuous(&self) -> bool { + self.duration == Duration::ZERO + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)] @@ -137,6 +141,30 @@ pub fn parse_duration(input: &str) -> Result { } } +pub fn parse_test_duration(input: &str) -> Result { + if input == "0" { + return Ok(Duration::ZERO); + } + let (number, unit) = split_duration(input)?; + let value: u64 = number + .parse() + .map_err(|_| format!("invalid duration value {input:?}"))?; + if value == 0 { + return Ok(Duration::ZERO); + } + match unit { + "ms" => Ok(Duration::from_millis(value)), + "s" => Ok(Duration::from_secs(value)), + "m" => value + .checked_mul(60) + .map(Duration::from_secs) + .ok_or_else(|| "duration is too large".to_owned()), + _ => Err(format!( + "unsupported duration unit {unit:?}; use ms, s, or m" + )), + } +} + fn split_duration(input: &str) -> Result<(&str, &str), String> { let split = input .find(|ch: char| !ch.is_ascii_digit()) @@ -581,6 +609,7 @@ mod tests { assert_eq!(config.length, 0); assert_eq!(config.stamp_at, StampAt::Both); assert_eq!(config.clock, Clock::Both); + assert!(!args.is_continuous()); } #[test] @@ -588,16 +617,29 @@ mod tests { assert_eq!(parse_duration("1ms").unwrap(), Duration::from_millis(1)); assert_eq!(parse_duration("2s").unwrap(), Duration::from_secs(2)); assert_eq!(parse_duration("3m").unwrap(), Duration::from_secs(180)); + assert_eq!(parse_test_duration("0").unwrap(), Duration::ZERO); + assert_eq!(parse_test_duration("0s").unwrap(), Duration::ZERO); + assert_eq!( + parse_test_duration("1ms").unwrap(), + Duration::from_millis(1) + ); } #[test] fn rejects_invalid_duration() { - assert!(parse(&["--duration", "0s", "127.0.0.1:2112"]).is_err()); assert!(parse(&["--duration", "-1s", "127.0.0.1:2112"]).is_err()); assert!(parse(&["--duration", "5", "127.0.0.1:2112"]).is_err()); assert!(parse(&["--duration", "1h", "127.0.0.1:2112"]).is_err()); } + #[test] + fn duration_zero_selects_continuous_config() { + let args = parse(&["--duration", "0", "127.0.0.1:2112"]).unwrap(); + assert!(args.is_continuous()); + assert_eq!(args.duration, Duration::ZERO); + assert_eq!(args.to_client_config().duration, None); + } + #[test] fn rejects_invalid_interval() { assert!(parse(&["--interval", "0ms", "127.0.0.1:2112"]).is_err()); diff --git a/crates/irtt-client/src/client.rs b/crates/irtt-client/src/client.rs index 4d1063c..0dc577f 100644 --- a/crates/irtt-client/src/client.rs +++ b/crates/irtt-client/src/client.rs @@ -1734,6 +1734,47 @@ mod tests { server.join(); } + #[test] + fn continuous_duration_keeps_generating_send_deadlines() { + let params = Params { + protocol_version: 1, + duration_ns: 0, + interval_ns: 500_000_000, + received_stats: ReceivedStats::Both, + stamp_at: StampAt::Both, + clock: Clock::Both, + ..Params::default() + }; + let server = silent_open_server(params); + let config = ClientConfig { + duration: None, + interval: Duration::from_millis(500), + socket_config: crate::SocketConfig { + recv_timeout: Some(Duration::from_millis(200)), + ..Default::default() + }, + ..default_test_config(server.addr) + }; + let mut client = Client::connect(config).unwrap(); + assert_open_started(client.open(ClientTimestamp::now()).unwrap()); + + let start = client.session.as_ref().unwrap().start_mono; + let interval = Duration::from_millis(500); + for seq in 0..4 { + let now = ClientTimestamp { + mono: start + interval * seq, + wall: SystemTime::now(), + }; + let events = client.send_probe_at(now).unwrap(); + assert_eq!(events.len(), 1); + assert!(client.next_send_deadline().is_some()); + assert!(!client.session.as_ref().unwrap().sending_done); + } + + client.close(ClientTimestamp::now()).unwrap(); + server.join(); + } + #[test] fn next_send_deadline_advances_by_interval() { let params = default_params();