use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use std::process::{Child, Command, Stdio};
fn bin(name: &str) -> PathBuf {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push(name);
path
}
fn run(binary: &str, args: &[&str]) -> (bool, String) {
let out = Command::new(bin(binary))
.args(args)
.output()
.unwrap_or_else(|e| panic!("failed to run {binary}: {e}"));
let text = format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
(out.status.success(), text)
}
struct ServerGuard(Child);
impl Drop for ServerGuard {
fn drop(&mut self) {
let _ = self.0.kill();
let _ = self.0.wait();
}
}
fn spawn_server(cavs_file: &str) -> (ServerGuard, String) {
let mut child = Command::new(bin("cavs-server"))
.args([cavs_file, "--listen", "127.0.0.1:0"])
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.expect("failed to spawn cavs-server");
let stdout = child.stdout.take().unwrap();
let mut line = String::new();
BufReader::new(stdout).read_line(&mut line).unwrap();
let url = line
.trim()
.strip_prefix("listening on ")
.expect("unexpected server banner")
.to_string();
(ServerGuard(child), url)
}
fn pseudo_random(len: usize, seed: u32) -> Vec<u8> {
let mut out = vec![0u8; len];
let mut state = seed;
for b in out.iter_mut() {
state = state.wrapping_mul(1664525).wrapping_add(1013904223);
*b = (state >> 24) as u8;
}
out
}
fn parse_inline_bytes(output: &str) -> f64 {
let line = output
.lines()
.find(|l| l.starts_with("egress"))
.unwrap_or_else(|| panic!("no egress line in:\n{output}"));
let rest = line.split(':').nth(1).unwrap().trim();
let mut parts = rest.split_whitespace();
let value: f64 = parts.next().unwrap().parse().unwrap();
let unit = parts.next().unwrap();
let mult = match unit {
"B" => 1.0,
"KiB" => 1024.0,
"MiB" => 1024.0 * 1024.0,
other => panic!("unexpected unit {other}"),
};
value * mult
}
#[test]
fn parallel_and_sequential_fetch_agree() {
let dir = tempfile::tempdir().unwrap();
let mut payload = pseudo_random(1_500_000, 91);
for w in payload.chunks_mut(4096) {
for b in w.iter_mut().take(2048) {
*b = 0;
}
}
let src = dir.path().join("game.bin");
std::fs::write(&src, &payload).unwrap();
let cavs = dir.path().join("game.cavs");
let (ok, out) = run(
"cavs",
&[
"pack",
"--raw",
src.to_str().unwrap(),
"--zstd-level",
"9",
"-o",
cavs.to_str().unwrap(),
],
);
assert!(ok, "pack failed:\n{out}");
let (_guard, url) = spawn_server(cavs.to_str().unwrap());
let seq_out = dir.path().join("seq");
let seq_cache = dir.path().join("seq-cache");
let (ok, seq) = run(
"cavs-client",
&[
"fetch",
&url,
"game",
"-o",
seq_out.to_str().unwrap(),
"--cache",
seq_cache.to_str().unwrap(),
"--connections",
"1",
],
);
assert!(ok, "sequential fetch failed:\n{seq}");
assert_eq!(std::fs::read(seq_out.join("game.bin")).unwrap(), payload);
let par_out = dir.path().join("par");
let par_cache = dir.path().join("par-cache");
let (ok, par) = run(
"cavs-client",
&[
"fetch",
&url,
"game",
"-o",
par_out.to_str().unwrap(),
"--cache",
par_cache.to_str().unwrap(),
"--connections",
"8",
],
);
assert!(ok, "parallel fetch failed:\n{par}");
assert_eq!(std::fs::read(par_out.join("game.bin")).unwrap(), payload);
let seq_bytes = parse_inline_bytes(&seq);
let par_bytes = parse_inline_bytes(&par);
assert!(
(seq_bytes - par_bytes).abs() <= seq_bytes * 0.02 + 4096.0,
"wire egress differs: seq {seq_bytes} vs par {par_bytes}"
);
assert!(
seq_bytes < payload.len() as f64,
"expected zstd wire savings, got {seq_bytes} for {} raw",
payload.len()
);
let warm_out = dir.path().join("warm");
let (ok, warm) = run(
"cavs-client",
&[
"fetch",
&url,
"game",
"-o",
warm_out.to_str().unwrap(),
"--cache",
par_cache.to_str().unwrap(),
"--connections",
"8",
],
);
assert!(ok, "warm parallel fetch failed:\n{warm}");
assert_eq!(
parse_inline_bytes(&warm),
0.0,
"warm fetch must be 0 wire:\n{warm}"
);
assert_eq!(std::fs::read(warm_out.join("game.bin")).unwrap(), payload);
}
#[test]
fn legacy_session_path_still_works() {
let dir = tempfile::tempdir().unwrap();
let payload = pseudo_random(800_000, 12);
let src = dir.path().join("b.bin");
std::fs::write(&src, &payload).unwrap();
let cavs = dir.path().join("b.cavs");
let (ok, out) = run(
"cavs",
&[
"pack",
"--raw",
src.to_str().unwrap(),
"-o",
cavs.to_str().unwrap(),
],
);
assert!(ok, "pack failed:\n{out}");
let (_guard, url) = spawn_server(cavs.to_str().unwrap());
let out_dir = dir.path().join("legacy");
let cache = dir.path().join("legacy-cache");
let (ok, log) = run(
"cavs-client",
&[
"fetch",
&url,
"b",
"-o",
out_dir.to_str().unwrap(),
"--cache",
cache.to_str().unwrap(),
"--connections",
"0",
],
);
assert!(ok, "legacy fetch failed:\n{log}");
assert_eq!(std::fs::read(out_dir.join("b.bin")).unwrap(), payload);
}