test: address review gaps in timing and dynamic targets
This commit is contained in:
4 files changed
+179
-1
No files matched your search
@@ -256,3 +256,108 @@ fn server_command_line_binds_replace_the_environment_bind() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exercise stdin replacement and EOF through the actual application process.
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
#[test]
|
||||||
|
fn targets_stdin_supersedes_the_active_set_and_stops_on_eof() {
|
||||||
|
use std::{
|
||||||
|
io::{BufRead, BufReader, Read, Write},
|
||||||
|
process::{Child, Stdio},
|
||||||
|
sync::mpsc,
|
||||||
|
thread,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
struct ClientProcess(Child);
|
||||||
|
impl Drop for ClientProcess {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = self.0.kill();
|
||||||
|
let _ = self.0.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let server = in_tree_server::InTreeServer::start(ServerConfig::default());
|
||||||
|
let mut client = ClientProcess(
|
||||||
|
Command::new(env!("CARGO_BIN_EXE_irtt-client"))
|
||||||
|
.env_clear()
|
||||||
|
.args([
|
||||||
|
"--duration",
|
||||||
|
"0",
|
||||||
|
"--targets-stdin",
|
||||||
|
"--interval",
|
||||||
|
"20ms",
|
||||||
|
"--format",
|
||||||
|
"jsonl",
|
||||||
|
"--columns",
|
||||||
|
"target,event",
|
||||||
|
])
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
let stdout = client.0.stdout.take().unwrap();
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
let reader = thread::spawn(move || {
|
||||||
|
for line in BufReader::new(stdout).lines() {
|
||||||
|
if tx.send(line.unwrap()).is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let mut stdin = client.0.stdin.take().unwrap();
|
||||||
|
let mut rows = Vec::new();
|
||||||
|
for target in ["first", "second"] {
|
||||||
|
writeln!(stdin, "{target}={}", server.addr).unwrap();
|
||||||
|
stdin.flush().unwrap();
|
||||||
|
let deadline = Instant::now() + Duration::from_secs(5);
|
||||||
|
loop {
|
||||||
|
let row = rx
|
||||||
|
.recv_timeout(deadline.saturating_duration_since(Instant::now()))
|
||||||
|
.expect("stdin target set did not produce a reply");
|
||||||
|
let replied = row.contains(&format!("\"target\":\"{target}\""))
|
||||||
|
&& row.contains("\"event\":\"echo_reply\"");
|
||||||
|
rows.push(row);
|
||||||
|
if replied {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Retirement can finish after the newer target's first reply.
|
||||||
|
let deadline = Instant::now() + Duration::from_secs(5);
|
||||||
|
while !rows.iter().any(|row| {
|
||||||
|
row.contains("\"target\":\"first\"") && row.contains("\"event\":\"session_closed\"")
|
||||||
|
}) {
|
||||||
|
rows.push(
|
||||||
|
rx.recv_timeout(deadline.saturating_duration_since(Instant::now()))
|
||||||
|
.expect("superseded stdin target did not retire"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
drop(stdin);
|
||||||
|
let deadline = Instant::now() + Duration::from_secs(5);
|
||||||
|
let status = loop {
|
||||||
|
if let Some(status) = client.0.try_wait().unwrap() {
|
||||||
|
break status;
|
||||||
|
}
|
||||||
|
assert!(
|
||||||
|
Instant::now() < deadline,
|
||||||
|
"client did not terminate on stdin EOF"
|
||||||
|
);
|
||||||
|
thread::sleep(Duration::from_millis(10));
|
||||||
|
};
|
||||||
|
reader.join().unwrap();
|
||||||
|
rows.extend(rx.try_iter());
|
||||||
|
let mut stderr = String::new();
|
||||||
|
client
|
||||||
|
.0
|
||||||
|
.stderr
|
||||||
|
.take()
|
||||||
|
.unwrap()
|
||||||
|
.read_to_string(&mut stderr)
|
||||||
|
.unwrap();
|
||||||
|
assert!(status.success(), "{stderr}");
|
||||||
|
assert!(rows
|
||||||
|
.iter()
|
||||||
|
.any(|row| row.contains("\"target\":\"second\"")
|
||||||
|
&& row.contains("\"event\":\"session_closed\"")));
|
||||||
|
}
|
||||||
@@ -1139,6 +1139,56 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use irtt_proto::{FLAG_OPEN, FLAG_REPLY};
|
use irtt_proto::{FLAG_OPEN, FLAG_REPLY};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn timing_arithmetic_preserves_negative_samples() {
|
||||||
|
let mono = Instant::now();
|
||||||
|
let sent = ClientTimestamp {
|
||||||
|
mono,
|
||||||
|
wall: UNIX_EPOCH + Duration::from_nanos(100),
|
||||||
|
};
|
||||||
|
let received = ClientTimestamp {
|
||||||
|
mono: mono + Duration::from_nanos(10),
|
||||||
|
wall: UNIX_EPOCH + Duration::from_nanos(110),
|
||||||
|
};
|
||||||
|
// Processing longer than raw RTT must remain negative with either clock.
|
||||||
|
for timestamps in [
|
||||||
|
TimestampFields {
|
||||||
|
recv_mono: Some(100),
|
||||||
|
send_mono: Some(120),
|
||||||
|
..TimestampFields::default()
|
||||||
|
},
|
||||||
|
TimestampFields {
|
||||||
|
recv_wall: Some(100),
|
||||||
|
send_wall: Some(120),
|
||||||
|
..TimestampFields::default()
|
||||||
|
},
|
||||||
|
] {
|
||||||
|
let rtt = compute_rtt(&sent, &received, ×tamps);
|
||||||
|
assert_eq!(rtt.raw, Duration::from_nanos(10));
|
||||||
|
assert_eq!(rtt.adjusted.unwrap().as_nanos(), -10);
|
||||||
|
assert_eq!(rtt.effective.as_nanos(), -10);
|
||||||
|
}
|
||||||
|
// A server clock behind or ahead can make either direction negative.
|
||||||
|
for (recv_wall, send_wall, upstream, downstream) in [(90, 95, -10, 15), (115, 120, 15, -10)]
|
||||||
|
{
|
||||||
|
let sample = compute_one_way(
|
||||||
|
&sent,
|
||||||
|
sent.wall,
|
||||||
|
&received,
|
||||||
|
None,
|
||||||
|
&ReceiveMeta::default(),
|
||||||
|
&TimestampFields {
|
||||||
|
recv_wall: Some(recv_wall),
|
||||||
|
send_wall: Some(send_wall),
|
||||||
|
..TimestampFields::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(sample.client_to_server.unwrap().as_nanos(), upstream);
|
||||||
|
assert_eq!(sample.server_to_client.unwrap().as_nanos(), downstream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn opened() -> SessionMachine {
|
fn opened() -> SessionMachine {
|
||||||
let mut machine = SessionMachine::new(
|
let mut machine = SessionMachine::new(
|
||||||
ClientConfig {
|
ClientConfig {
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ async fn target_updates_preserve_identical_sessions_and_retire_removed_generatio
|
|||||||
..ClientConfig::default()
|
..ClientConfig::default()
|
||||||
},
|
},
|
||||||
completion: ManagedCompletionPolicy::ExplicitStop,
|
completion: ManagedCompletionPolicy::ExplicitStop,
|
||||||
|
outcome_history_limit: 1,
|
||||||
..ManagedClientConfig::default()
|
..ManagedClientConfig::default()
|
||||||
};
|
};
|
||||||
let configured = ManagedTargetConfig::new("one", server.addr.to_string());
|
let configured = ManagedTargetConfig::new("one", server.addr.to_string());
|
||||||
@@ -173,6 +174,17 @@ async fn target_updates_preserve_identical_sessions_and_retire_removed_generatio
|
|||||||
stop.await;
|
stop.await;
|
||||||
assert_eq!(outcome.end_reason, ManagedEndReason::StopRequested);
|
assert_eq!(outcome.end_reason, ManagedEndReason::StopRequested);
|
||||||
assert_eq!(outcome.total_target_outcomes, 2);
|
assert_eq!(outcome.total_target_outcomes, 2);
|
||||||
|
assert_eq!(outcome.successful_target_outcomes, 2);
|
||||||
|
assert_eq!(outcome.failed_target_outcomes, 0);
|
||||||
|
assert_eq!(outcome.discarded_target_outcomes, 1);
|
||||||
|
assert_eq!(outcome.recent_target_outcomes.len(), 1);
|
||||||
|
let status = handle.status();
|
||||||
|
assert_eq!(status.total_target_outcomes, 2);
|
||||||
|
assert_eq!(status.successful_target_outcomes, 2);
|
||||||
|
assert_eq!(status.failed_target_outcomes, 0);
|
||||||
|
assert_eq!(status.discarded_target_outcomes, 1);
|
||||||
|
assert_eq!(status.recent_target_outcomes.len(), 1);
|
||||||
|
assert_eq!(status.recent_target_outcomes[0].target, second);
|
||||||
assert!(outcome.recent_target_outcomes.iter().any(|target| {
|
assert!(outcome.recent_target_outcomes.iter().any(|target| {
|
||||||
target.target == second
|
target.target == second
|
||||||
&& target.end_reason == ManagedTargetEndReason::Stopped
|
&& target.end_reason == ManagedTargetEndReason::Stopped
|
||||||
|
|||||||
@@ -321,6 +321,7 @@ fn negotiated_timestamp_modes_emit_only_the_selected_fields_in_order() {
|
|||||||
fn duration_close_waits_for_rate_allowance_after_the_deadline() {
|
fn duration_close_waits_for_rate_allowance_after_the_deadline() {
|
||||||
let refill = Duration::from_secs(4);
|
let refill = Duration::from_secs(4);
|
||||||
let maximum = Duration::from_millis(1);
|
let maximum = Duration::from_millis(1);
|
||||||
|
for _ in 0..3 {
|
||||||
let mut server = ServerCore::new(
|
let mut server = ServerCore::new(
|
||||||
ServerConfig::default()
|
ServerConfig::default()
|
||||||
.with_min_send_interval(refill)
|
.with_min_send_interval(refill)
|
||||||
@@ -328,6 +329,7 @@ fn duration_close_waits_for_rate_allowance_after_the_deadline() {
|
|||||||
.with_idle_timeout(Duration::MAX)
|
.with_idle_timeout(Duration::MAX)
|
||||||
.with_max_test_duration(maximum),
|
.with_max_test_duration(maximum),
|
||||||
);
|
);
|
||||||
|
let started = Instant::now();
|
||||||
let opened = open(
|
let opened = open(
|
||||||
&mut server,
|
&mut server,
|
||||||
&Params {
|
&Params {
|
||||||
@@ -338,8 +340,14 @@ fn duration_close_waits_for_rate_allowance_after_the_deadline() {
|
|||||||
let first = echo(&mut server, &opened, 0).unwrap();
|
let first = echo(&mut server, &opened, 0).unwrap();
|
||||||
assert_eq!(first.flags & FLAG_CLOSE, 0);
|
assert_eq!(first.flags & FLAG_CLOSE, 0);
|
||||||
thread::sleep(maximum + Duration::from_secs(2));
|
thread::sleep(maximum + Duration::from_secs(2));
|
||||||
|
let refused = echo(&mut server, &opened, 63);
|
||||||
|
// Include open/setup and the request itself in the elapsed bound. If the
|
||||||
|
// scheduler carried us past refill, the reply may legitimately close.
|
||||||
|
if started.elapsed() >= refill {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
assert!(
|
assert!(
|
||||||
echo(&mut server, &opened, 63).is_none(),
|
refused.is_none(),
|
||||||
"exhausted allowance must suppress the deadline-crossing reply"
|
"exhausted allowance must suppress the deadline-crossing reply"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -354,6 +362,9 @@ fn duration_close_waits_for_rate_allowance_after_the_deadline() {
|
|||||||
assert_eq!(closed.recv_count, Some(2));
|
assert_eq!(closed.recv_count, Some(2));
|
||||||
assert_eq!(closed.recv_window, Some(3));
|
assert_eq!(closed.recv_window, Some(3));
|
||||||
assert_eq!(server.session_count(), 0);
|
assert_eq!(server.session_count(), 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
panic!("scheduler crossed the refill boundary in all three fresh sessions");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in new issue
Block a user