use std::path::{Path, PathBuf};
use std::process::Command;
fn cavs_bin() -> PathBuf {
let mut path = std::env::current_exe().unwrap();
path.pop(); path.pop(); path.push("cavs");
path
}
fn run(args: &[&str]) -> (bool, String) {
let out = Command::new(cavs_bin())
.args(args)
.output()
.expect("failed to run cavs binary");
let text = format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
(out.status.success(), text)
}
fn run_json(args: &[&str]) -> serde_json::Value {
let out = Command::new(cavs_bin())
.args(args)
.output()
.expect("failed to run cavs binary");
assert!(
out.status.success(),
"cavs {args:?} failed: {}",
String::from_utf8_lossy(&out.stderr)
);
serde_json::from_slice(&out.stdout).expect("stdout is not JSON")
}
fn prng(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 write(path: &Path, data: &[u8]) {
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, data).unwrap();
}
fn pack_scenarios(dir: &Path) -> (PathBuf, PathBuf, PathBuf) {
let assets: Vec<Vec<u8>> = (0..20).map(|i| prng(1 << 20, 500 + i as u32)).collect();
let v1 = dir.join("v1");
let localized = dir.join("v2-localized");
let shuffled = dir.join("v2-shuffled");
write(&v1.join("world.pak"), &assets.concat());
let mut edited = assets.clone();
edited[7][1000..1200].copy_from_slice(&prng(200, 9999));
write(&localized.join("world.pak"), &edited.concat());
let mut rotated: Vec<u8> = vec![0x42; 4096];
for a in assets.iter().skip(1).chain(assets.iter().take(1)) {
rotated.extend_from_slice(a);
}
write(&shuffled.join("world.pak"), &rotated);
(v1, localized, shuffled)
}
#[test]
fn steampipe_style_bench_localized_vs_shuffled() {
let dir = tempfile::tempdir().unwrap();
let (v1, localized, shuffled) = pack_scenarios(dir.path());
let local = run_json(&[
"bench",
"steampipe-style",
v1.to_str().unwrap(),
localized.to_str().unwrap(),
"--json",
]);
assert_eq!(local["new_or_changed_chunks"], 1);
assert_eq!(local["files_modified"], 1);
assert!(local["note"].as_str().unwrap().contains("not Valve"));
let shuf = run_json(&[
"bench",
"steampipe-style",
v1.to_str().unwrap(),
shuffled.to_str().unwrap(),
"--json",
]);
let changed = shuf["new_or_changed_chunks"].as_u64().unwrap();
let total = shuf["total_chunks_new"].as_u64().unwrap();
assert!(changed >= total - 1, "changed {changed} of {total}");
}
#[test]
fn analyze_steampipe_diagnoses_shuffling_with_recommendation() {
let dir = tempfile::tempdir().unwrap();
let (v1, _, shuffled) = pack_scenarios(dir.path());
let report = run_json(&[
"analyze",
"steampipe",
v1.to_str().unwrap(),
shuffled.to_str().unwrap(),
"--json",
]);
let kinds: Vec<&str> = report["findings"]
.as_array()
.unwrap()
.iter()
.map(|f| f["kind"].as_str().unwrap())
.collect();
assert!(kinds.contains(&"asset_shuffling"), "kinds: {kinds:?}");
let steam = report["estimated_steampipe_download"].as_u64().unwrap();
let cavs = report["estimated_cavs_download"].as_u64().unwrap();
assert!(cavs * 4 < steam, "cavs {cavs} vs steam {steam}");
let md_path = dir.path().join("analysis.md");
let (ok, _) = run(&[
"analyze",
"steampipe",
v1.to_str().unwrap(),
shuffled.to_str().unwrap(),
"--out",
md_path.to_str().unwrap(),
]);
assert!(ok);
let md = std::fs::read_to_string(&md_path).unwrap();
assert!(md.contains("not Valve's exact SteamPipe implementation"));
assert!(md.contains("Recommended fix"));
}
#[test]
fn analyze_packs_reports_scatteredness_table() {
let dir = tempfile::tempdir().unwrap();
let (v1, localized, _) = pack_scenarios(dir.path());
let report = run_json(&[
"analyze-packs",
v1.to_str().unwrap(),
localized.to_str().unwrap(),
"--json",
]);
let rows = report["rows"].as_array().unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["file"], "world.pak");
assert_eq!(rows[0]["changed_windows_1m"], 1);
assert_eq!(rows[0]["main_issue"], "localized");
}
#[test]
fn io_estimate_shows_rebuild_cost_dominating() {
let dir = tempfile::tempdir().unwrap();
let (v1, localized, _) = pack_scenarios(dir.path());
let report = run_json(&[
"io-estimate",
v1.to_str().unwrap(),
localized.to_str().unwrap(),
"--json",
]);
let routes = report["routes"].as_array().unwrap();
let steam = routes
.iter()
.find(|r| r["route"].as_str().unwrap().contains("SteamPipe-style"))
.unwrap();
assert_eq!(steam["write_bytes"].as_u64().unwrap(), 20 << 20);
assert!(steam["io_dominates_network"].as_bool().unwrap());
assert!(steam["device_seconds"]["hdd"].as_f64().unwrap() > 0.0);
let full = routes
.iter()
.find(|r| r["route"].as_str().unwrap().contains("full"))
.unwrap();
assert_eq!(full["read_old_bytes"].as_u64().unwrap(), 0);
}
#[test]
fn plan_update_policies_and_states() {
let dir = tempfile::tempdir().unwrap();
let (v1, localized, _) = pack_scenarios(dir.path());
let report = run_json(&[
"plan-update",
"--from",
v1.to_str().unwrap(),
"--to",
localized.to_str().unwrap(),
"--client-state",
"has-previous-install,low-ram",
"--policy",
"network_min",
"--json",
]);
let chosen = report["chosen"].as_str().unwrap();
assert!(
chosen.contains("cavsplan") || chosen.contains("hybrid"),
"chosen {chosen}"
);
assert!(!report["reason"].as_str().unwrap().is_empty());
let routes = report["routes"].as_array().unwrap();
assert!(routes.iter().any(|r| r["available"] == false));
for r in routes {
if r["available"] == false {
assert_ne!(r["route"].as_str().unwrap(), chosen);
}
}
}
#[test]
fn publish_preview_recommends_verified_route() {
let dir = tempfile::tempdir().unwrap();
let (v1, localized, _) = pack_scenarios(dir.path());
let report = run_json(&[
"publish-preview",
localized.to_str().unwrap(),
"--previous",
v1.to_str().unwrap(),
"--routes",
"cavs", "--json",
]);
assert!(report["note"].as_str().unwrap().contains("not Valve"));
assert!(report["steampipe_style"]["network_bytes"].as_u64().unwrap() > 0);
let recommended = report["recommended"].as_str().unwrap();
assert!(
recommended.contains("cavsplan") || recommended.contains("chunk"),
"recommended {recommended}"
);
}
#[test]
fn workspace_depots_branches_builds_and_install_plans() {
let dir = tempfile::tempdir().unwrap();
let ws = dir.path().join("ws");
let ws_s = ws.to_str().unwrap();
let shared = prng(2 << 20, 1);
write(&dir.path().join("v1/shared/data.bin"), &shared);
write(&dir.path().join("v1/win/game.exe"), &prng(1 << 20, 2));
write(&dir.path().join("v1/hd/tex.bin"), &prng(1 << 20, 3));
let mut shared2 = shared.clone();
shared2[100..200].copy_from_slice(&prng(100, 4));
write(&dir.path().join("v2/shared/data.bin"), &shared2);
write(&dir.path().join("v2/win/game.exe"), &prng(1 << 20, 2));
write(&dir.path().join("v2/hd/tex.bin"), &prng(1 << 20, 3));
let v = |p: &str| dir.path().join(p).display().to_string();
assert!(run(&["workspace", "init", ws_s, "--app", "my-game"]).0);
assert!(run(&["depot", "add", "base", "--workspace", ws_s]).0);
assert!(
run(&[
"depot",
"add",
"windows",
"--workspace",
ws_s,
"--platform",
"windows"
])
.0
);
assert!(
run(&[
"depot",
"add",
"hd-textures",
"--workspace",
ws_s,
"--optional"
])
.0
);
assert!(run(&["branch", "add", "public", "--workspace", ws_s]).0);
assert!(run(&["branch", "add", "beta", "--workspace", ws_s]).0);
let base1 = format!("base={}", v("v1/shared"));
let win1 = format!("windows={}", v("v1/win"));
let hd1 = format!("hd-textures={}", v("v1/hd"));
assert!(
run(&[
"build",
"create",
"--workspace",
ws_s,
"--branch",
"beta",
"--depot",
&base1,
"--depot",
&win1,
"--depot",
&hd1,
"--label",
"v1",
])
.0
);
let base2 = format!("base={}", v("v2/shared"));
let win2 = format!("windows={}", v("v2/win"));
let hd2 = format!("hd-textures={}", v("v2/hd"));
assert!(
run(&[
"build",
"create",
"--workspace",
ws_s,
"--branch",
"beta",
"--depot",
&base2,
"--depot",
&win2,
"--depot",
&hd2,
"--label",
"v2",
])
.0
);
assert!(
run(&[
"branch",
"promote",
"--workspace",
ws_s,
"--branch",
"public",
"--build",
"build_1002"
])
.0
);
let (ok, out) = run(&[
"branch",
"rollback",
"--workspace",
ws_s,
"--branch",
"public",
"--to",
"build_1001",
]);
assert!(!ok, "public never served build_1001");
assert!(out.contains("CAVS-E-BUILD-NOT-FOUND"), "{out}");
assert!(
run(&[
"branch",
"rollback",
"--workspace",
ws_s,
"--branch",
"beta",
"--to",
"build_1001"
])
.0
);
let plan = run_json(&[
"install-plan",
"--workspace",
ws_s,
"--branch",
"public",
"--platform",
"linux",
"--from",
"build_1001",
"--json",
]);
let depots: Vec<&str> = plan["depots"]
.as_array()
.unwrap()
.iter()
.map(|d| d["depot"].as_str().unwrap())
.collect();
assert_eq!(
depots,
vec!["base"],
"windows filtered by platform, hd by ownership"
);
assert_eq!(plan["depots"][0]["action"], "update");
assert!(plan["total_bytes"].as_u64().unwrap() < 1 << 20);
let plan = run_json(&[
"install-plan",
"--workspace",
ws_s,
"--branch",
"public",
"--platform",
"windows",
"--owned",
"base,windows,hd-textures",
"--from",
"build_1001",
"--json",
]);
assert_eq!(plan["depots"].as_array().unwrap().len(), 3);
let windows_plan = plan["depots"]
.as_array()
.unwrap()
.iter()
.find(|d| d["depot"] == "windows")
.unwrap();
assert_eq!(windows_plan["action"], "no-op");
let (ok, out) = run(&[
"branch",
"promote-preview",
"--workspace",
ws_s,
"--branch",
"beta",
"--build",
"build_1002",
]);
assert!(ok);
assert!(out.contains("base"), "{out}");
}
fn synth_pck(files: &[(&str, &[u8])]) -> Vec<u8> {
let mut h = Vec::new();
h.extend_from_slice(&0x4350_4447u32.to_le_bytes()); h.extend_from_slice(&2u32.to_le_bytes());
for v in [4u32, 2, 0] {
h.extend_from_slice(&v.to_le_bytes());
}
h.extend_from_slice(&0u32.to_le_bytes()); h.extend_from_slice(&0u64.to_le_bytes()); h.extend_from_slice(&[0u8; 64]);
h.extend_from_slice(&(files.len() as u32).to_le_bytes());
let dir_size: usize = files
.iter()
.map(|(p, _)| 4 + p.len() + 8 + 8 + 16 + 4)
.sum();
let mut offset = (h.len() + dir_size) as u64;
let mut payload = Vec::new();
for (path, bytes) in files {
h.extend_from_slice(&(path.len() as u32).to_le_bytes());
h.extend_from_slice(path.as_bytes());
h.extend_from_slice(&offset.to_le_bytes());
h.extend_from_slice(&(bytes.len() as u64).to_le_bytes());
h.extend_from_slice(&[0u8; 16]);
h.extend_from_slice(&0u32.to_le_bytes());
payload.extend_from_slice(bytes);
offset += bytes.len() as u64;
}
h.extend(payload);
h
}
#[test]
fn godot_pck_analyzer_maps_changes_to_resources() {
let dir = tempfile::tempdir().unwrap();
let hero = prng(256 * 1024, 11);
let mut hero2 = hero.clone();
hero2[1000..1100].copy_from_slice(&prng(100, 12));
let level = prng(512 * 1024, 13);
let old = synth_pck(&[
("res://textures/hero.png", &hero),
("res://levels/level01.scn", &level),
]);
let new = synth_pck(&[
("res://textures/hero.png", &hero2),
("res://levels/level01.scn", &level),
]);
let old_p = dir.path().join("old.pck");
let new_p = dir.path().join("new.pck");
std::fs::write(&old_p, &old).unwrap();
std::fs::write(&new_p, &new).unwrap();
let report = run_json(&[
"analyze",
"godot-pck",
old_p.to_str().unwrap(),
new_p.to_str().unwrap(),
"--json",
]);
assert_eq!(report["parsed"], true);
assert_eq!(report["resources_total"], 2);
let touched: Vec<&str> = report["changed_resources"]
.as_array()
.unwrap()
.iter()
.map(|v| v.as_str().unwrap())
.collect();
assert!(
touched.contains(&"res://textures/hero.png"),
"touched: {touched:?}"
);
assert!(report["note"].as_str().unwrap().contains("not Valve"));
}
#[test]
fn build_sign_verify_encrypt_via_binary() {
let dir = tempfile::tempdir().unwrap();
let key = dir.path().join("release.key");
let artifact = dir.path().join("build.cavs");
std::fs::write(&artifact, prng(50_000, 77)).unwrap();
assert!(run(&["keygen", "-o", key.to_str().unwrap()]).0);
assert!(
run(&[
"build",
"sign",
artifact.to_str().unwrap(),
"--key",
key.to_str().unwrap()
])
.0
);
let pubkey = dir.path().join("release.pub");
assert!(
run(&[
"build",
"verify",
artifact.to_str().unwrap(),
"--pub",
pubkey.to_str().unwrap()
])
.0
);
let mut bytes = std::fs::read(&artifact).unwrap();
bytes[0] ^= 0xff;
std::fs::write(&artifact, &bytes).unwrap();
let (ok, out) = run(&[
"build",
"verify",
artifact.to_str().unwrap(),
"--pub",
pubkey.to_str().unwrap(),
]);
assert!(!ok);
assert!(out.contains("CAVS-E-SIGNATURE-INVALID"), "{out}");
let ckey = dir.path().join("content.key");
let enc = dir.path().join("build.enc");
let dec = dir.path().join("build.dec");
assert!(run(&["build", "content-key", "--out", ckey.to_str().unwrap()]).0);
assert!(
run(&[
"build",
"encrypt",
artifact.to_str().unwrap(),
"--key",
ckey.to_str().unwrap(),
"--out",
enc.to_str().unwrap(),
])
.0
);
assert!(
run(&[
"build",
"decrypt",
enc.to_str().unwrap(),
"--key",
ckey.to_str().unwrap(),
"--out",
dec.to_str().unwrap(),
])
.0
);
assert_eq!(
std::fs::read(&artifact).unwrap(),
std::fs::read(&dec).unwrap()
);
}
#[test]
fn optimize_layout_writes_machine_plan() {
let dir = tempfile::tempdir().unwrap();
let (v1, _, shuffled) = pack_scenarios(dir.path());
let plan_path = dir.path().join("layout.json");
let (ok, _) = run(&[
"optimize-layout",
v1.to_str().unwrap(),
shuffled.to_str().unwrap(),
"--write-plan",
plan_path.to_str().unwrap(),
]);
assert!(ok);
let plan: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&plan_path).unwrap()).unwrap();
assert!(!plan["recommendations"].as_array().unwrap().is_empty());
assert!(plan["note"].as_str().unwrap().contains("Advisory only"));
assert!(!plan["general_rules"].as_array().unwrap().is_empty());
}