feat(cli): add support for continuous mode with zero duration
This commit is contained in:
2 files changed
+86
-3
No files matched your search
@@ -24,7 +24,7 @@ const MAX_UDP_PAYLOAD_LENGTH: u32 = 65_507;
|
|||||||
pub struct CliArgs {
|
pub struct CliArgs {
|
||||||
pub server: String,
|
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,
|
pub duration: Duration,
|
||||||
|
|
||||||
#[arg(long, default_value = "1s", value_parser = parse_duration)]
|
#[arg(long, default_value = "1s", value_parser = parse_duration)]
|
||||||
@@ -50,7 +50,7 @@ impl CliArgs {
|
|||||||
pub fn to_client_config(&self) -> ClientConfig {
|
pub fn to_client_config(&self) -> ClientConfig {
|
||||||
ClientConfig {
|
ClientConfig {
|
||||||
server_addr: self.server.clone(),
|
server_addr: self.server.clone(),
|
||||||
duration: Some(self.duration),
|
duration: (!self.is_continuous()).then_some(self.duration),
|
||||||
interval: self.interval,
|
interval: self.interval,
|
||||||
length: self.length,
|
length: self.length,
|
||||||
received_stats: ReceivedStats::Both,
|
received_stats: ReceivedStats::Both,
|
||||||
@@ -64,6 +64,10 @@ impl CliArgs {
|
|||||||
..ClientConfig::default()
|
..ClientConfig::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_continuous(&self) -> bool {
|
||||||
|
self.duration == Duration::ZERO
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
|
||||||
@@ -137,6 +141,30 @@ pub fn parse_duration(input: &str) -> Result<Duration, String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_test_duration(input: &str) -> Result<Duration, String> {
|
||||||
|
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> {
|
fn split_duration(input: &str) -> Result<(&str, &str), String> {
|
||||||
let split = input
|
let split = input
|
||||||
.find(|ch: char| !ch.is_ascii_digit())
|
.find(|ch: char| !ch.is_ascii_digit())
|
||||||
@@ -581,6 +609,7 @@ mod tests {
|
|||||||
assert_eq!(config.length, 0);
|
assert_eq!(config.length, 0);
|
||||||
assert_eq!(config.stamp_at, StampAt::Both);
|
assert_eq!(config.stamp_at, StampAt::Both);
|
||||||
assert_eq!(config.clock, Clock::Both);
|
assert_eq!(config.clock, Clock::Both);
|
||||||
|
assert!(!args.is_continuous());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -588,16 +617,29 @@ mod tests {
|
|||||||
assert_eq!(parse_duration("1ms").unwrap(), Duration::from_millis(1));
|
assert_eq!(parse_duration("1ms").unwrap(), Duration::from_millis(1));
|
||||||
assert_eq!(parse_duration("2s").unwrap(), Duration::from_secs(2));
|
assert_eq!(parse_duration("2s").unwrap(), Duration::from_secs(2));
|
||||||
assert_eq!(parse_duration("3m").unwrap(), Duration::from_secs(180));
|
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]
|
#[test]
|
||||||
fn rejects_invalid_duration() {
|
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", "-1s", "127.0.0.1:2112"]).is_err());
|
||||||
assert!(parse(&["--duration", "5", "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());
|
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]
|
#[test]
|
||||||
fn rejects_invalid_interval() {
|
fn rejects_invalid_interval() {
|
||||||
assert!(parse(&["--interval", "0ms", "127.0.0.1:2112"]).is_err());
|
assert!(parse(&["--interval", "0ms", "127.0.0.1:2112"]).is_err());
|
||||||
|
|||||||
@@ -1734,6 +1734,47 @@ mod tests {
|
|||||||
server.join();
|
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]
|
#[test]
|
||||||
fn next_send_deadline_advances_by_interval() {
|
fn next_send_deadline_advances_by_interval() {
|
||||||
let params = default_params();
|
let params = default_params();
|
||||||
|
|||||||
Reference in new issue
Block a user