test(client): add integration tests for compatibility with real IRTT backends
Introduce a mechanism to run tests against a live irtt binary to verify real-world protocol interoperability, including HMAC authentication and various timestamp modes.
This commit is contained in:
1 parent
797120056e
commit
3d49f725ae
4 files changed
+348
-6
No files matched your search
@@ -1,7 +1,10 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
mod real_irtt;
|
||||
|
||||
use std::{
|
||||
net::{SocketAddr, UdpSocket},
|
||||
process::Command,
|
||||
sync::mpsc,
|
||||
thread::{self, JoinHandle},
|
||||
time::{Duration, SystemTime},
|
||||
@@ -15,11 +18,27 @@ use irtt_proto::{
|
||||
ReceivedStats, ServerFill, StampAt, TimestampFields, HMAC_SIZE, MAGIC, PROTOCOL_VERSION,
|
||||
};
|
||||
|
||||
pub use real_irtt::RealIrtServer;
|
||||
|
||||
const HMAC_OFFSET: usize = 4;
|
||||
pub const TOKEN: u64 = 0x1234_5678_90ab_cdef;
|
||||
pub const RECV_COUNT: u32 = 42;
|
||||
pub const RECV_WINDOW: u64 = 0x07;
|
||||
|
||||
pub fn require_real_backend() -> bool {
|
||||
match std::env::var("IRTT_TEST_BACKEND").as_deref() {
|
||||
Ok("real") => {
|
||||
let irtt_bin = std::env::var("IRTT_BIN").unwrap_or_else(|_| "irtt".to_string());
|
||||
match Command::new(&irtt_bin).arg("version").output() {
|
||||
Ok(output) if output.status.success() => true,
|
||||
_ => panic!("IRTT_TEST_BACKEND=real but irtt binary not found at '{irtt_bin}'"),
|
||||
}
|
||||
}
|
||||
Ok("fake") | Err(_) => false,
|
||||
Ok(other) => panic!("unknown IRTT_TEST_BACKEND value: {other}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FakeServer {
|
||||
pub addr: SocketAddr,
|
||||
rx: mpsc::Receiver<ServerObservation>,
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::net::{SocketAddr, UdpSocket};
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub struct RealIrtServer {
|
||||
addr: SocketAddr,
|
||||
child: Child,
|
||||
}
|
||||
|
||||
impl RealIrtServer {
|
||||
pub fn start(hmac_key: Option<&[u8]>) -> Result<Self, String> {
|
||||
let port = Self::find_free_port()?;
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], port));
|
||||
let bind = format!("127.0.0.1:{}", port);
|
||||
|
||||
let irtt_bin = std::env::var("IRTT_BIN").unwrap_or_else(|_| "irtt".to_string());
|
||||
|
||||
let mut cmd = Command::new(&irtt_bin);
|
||||
cmd.arg("server")
|
||||
.arg("-b")
|
||||
.arg(&bind)
|
||||
.arg("--tstamp=dual")
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped());
|
||||
|
||||
if let Some(key) = hmac_key {
|
||||
let hex_key: String = key.iter().map(|b| format!("{:02x}", b)).collect();
|
||||
cmd.arg(format!("--hmac=0x{}", hex_key));
|
||||
}
|
||||
|
||||
let mut child = cmd
|
||||
.spawn()
|
||||
.map_err(|e| format!("failed to spawn irtt server: {}", e))?;
|
||||
|
||||
let stdout = child.stdout.take().ok_or("failed to capture stdout")?;
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let reader = BufReader::new(stdout);
|
||||
for line in reader.lines() {
|
||||
match line {
|
||||
Ok(l) => {
|
||||
if tx.send(l).is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let timeout = Duration::from_secs(5);
|
||||
let start = Instant::now();
|
||||
let mut stdout_lines = Vec::new();
|
||||
loop {
|
||||
let remaining = timeout
|
||||
.checked_sub(start.elapsed())
|
||||
.unwrap_or(Duration::ZERO);
|
||||
match rx.recv_timeout(remaining) {
|
||||
Ok(line) => {
|
||||
stdout_lines.push(line.clone());
|
||||
if line.contains("[ListenerStart]") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(mpsc::RecvTimeoutError::Timeout) => {
|
||||
child.kill().ok();
|
||||
child.wait().ok();
|
||||
return Err(format!(
|
||||
"timeout waiting for irtt server to start\nstdout:\n{}",
|
||||
stdout_lines.join("\n")
|
||||
));
|
||||
}
|
||||
Err(mpsc::RecvTimeoutError::Disconnected) => {
|
||||
child.kill().ok();
|
||||
child.wait().ok();
|
||||
return Err(format!(
|
||||
"irtt server exited unexpectedly\nstdout:\n{}",
|
||||
stdout_lines.join("\n")
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drop(rx);
|
||||
|
||||
Ok(Self { addr, child })
|
||||
}
|
||||
|
||||
pub fn addr(&self) -> SocketAddr {
|
||||
self.addr
|
||||
}
|
||||
|
||||
fn find_free_port() -> Result<u16, String> {
|
||||
let socket = UdpSocket::bind("127.0.0.1:0")
|
||||
.map_err(|e| format!("failed to find free port: {}", e))?;
|
||||
let port = socket
|
||||
.local_addr()
|
||||
.map_err(|e| format!("failed to get local addr: {}", e))?
|
||||
.port();
|
||||
drop(socket);
|
||||
Ok(port)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RealIrtServer {
|
||||
fn drop(&mut self) {
|
||||
self.child.kill().ok();
|
||||
self.child.wait().ok();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user