use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use supercode::reduce::{project, ReductionKind, ReductionLog, ReductionPolicy};
use supercode::session::Session;
fn bin() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_supercode"))
}
fn fresh_home(tag: &str) -> PathBuf {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
let dir = std::env::temp_dir().join(format!(
"supercode-tr4-cli-{tag}-{}-{nanos}",
std::process::id()
));
std::fs::create_dir_all(&dir).unwrap();
dir
}
fn run(home: &Path, extra: &[&str]) -> Output {
Command::new(bin())
.env("SUPERCODE_HOME", home)
.env_remove("OPENROUTER_API_KEY")
.env_remove("OPENAI_API_KEY")
.env_remove("ANTHROPIC_API_KEY")
.args(["--api-key", "x", "--base-url", "http://127.0.0.1:1"])
.args(extra)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.expect("failed to spawn the supercode binary")
.wait_with_output()
.expect("child process failed")
}
fn stdout(out: &Output) -> String {
String::from_utf8_lossy(&out.stdout).into_owned()
}
fn stderr(out: &Output) -> String {
String::from_utf8_lossy(&out.stderr).into_owned()
}
fn sessions_dir(home: &Path) -> PathBuf {
home.join("sessions")
}
fn cargo_build_raw() -> String {
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../harness/tests/fixtures/terminal/cargo_build.raw");
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("reading {}: {e}", path.display()))
}
fn write_ansi_fixture(dir: &Path) -> PathBuf {
let sid = "77777777-8888-9999-aaaa-bbbbbbbbbbbb";
let noisy = cargo_build_raw();
let mut lines: Vec<String> = Vec::new();
lines.push(
serde_json::json!({
"type": "user",
"message": {"role": "user", "content": "please build the project"},
"uuid": "u0", "parentUuid": null,
"timestamp": "2026-07-07T18:38:00.000Z", "sessionId": sid,
"cwd": "/tmp/proj", "userType": "external",
})
.to_string(),
);
lines.push(
serde_json::json!({
"type": "assistant",
"message": {"role": "assistant", "content": [
{"type": "tool_use", "id": "toolu_00", "name": "bash", "input": {"command": "cargo build"}}
]},
"uuid": "a0", "parentUuid": "u0",
"timestamp": "2026-07-07T18:38:01.000Z", "sessionId": sid,
})
.to_string(),
);
lines.push(
serde_json::json!({
"type": "user",
"message": {"role": "user", "content": [
{"tool_use_id": "toolu_00", "type": "tool_result", "content": [{"type": "text", "text": noisy}]}
]},
"uuid": "t0", "parentUuid": "a0",
"timestamp": "2026-07-07T18:38:02.000Z", "sessionId": sid,
"toolUseResult": {"status": "completed"},
})
.to_string(),
);
let path = dir.join("ansi_claude_session.jsonl");
std::fs::write(&path, lines.join("\n") + "\n").unwrap();
path
}
fn mint_reduced_session(home: &Path, fixture: &Path) -> String {
let out = run(home, &["resume", fixture.to_str().unwrap(), "--reduced"]);
let err = stderr(&out);
let line = err
.lines()
.find(|l| l.contains("full copy:"))
.unwrap_or_else(|| panic!("no `full copy:` line in:\n{err}"));
let sidecar_path = line.split("full copy:").nth(1).unwrap().trim();
Path::new(sidecar_path)
.file_name()
.unwrap()
.to_str()
.unwrap()
.strip_suffix(".sidecar.jsonl")
.unwrap()
.to_string()
}
#[test]
fn show_reductions_attributes_savings_under_output_normalized() {
let home = fresh_home("output-normalized");
let fixture = write_ansi_fixture(&home);
let session = Session::load(&fixture).unwrap();
let (_, log) = project(
&session,
&ReductionPolicy::default(),
&ReductionLog::default(),
);
assert_eq!(log.reductions.len(), 1, "{:?}", log.reductions);
let (expected_id, expected_original_bytes) = match &log.reductions[0].kind {
ReductionKind::OutputNormalized { original_bytes, .. } => {
(log.reductions[0].id.clone(), *original_bytes)
}
other => panic!("expected OutputNormalized, got {other:?}"),
};
let name = mint_reduced_session(&home, &fixture);
let log_path = sessions_dir(&home).join(format!("{name}.reduction.json"));
let log_json: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&log_path).unwrap()).unwrap();
let records = log_json["reductions"].as_array().unwrap();
assert_eq!(records.len(), 1);
assert!(
records[0]["kind"]
.as_object()
.map(|o| o.contains_key("OutputNormalized"))
.unwrap_or(false),
"persisted log must record an OutputNormalized kind: {records:?}"
);
let out = run(&home, &["sessions", "show-reductions", &name]);
assert!(out.status.success(), "must exit 0: {}", stderr(&out));
let table = stdout(&out);
assert!(
table.contains(&expected_id),
"table must list id {expected_id}:\n{table}"
);
assert!(
table.contains("output-normalized"),
"table must show the output-normalized kind:\n{table}"
);
let comma_grouped = {
let digits = expected_original_bytes.to_string();
let bytes = digits.as_bytes();
let mut s = String::new();
for (i, b) in bytes.iter().enumerate() {
if i > 0 && (bytes.len() - i) % 3 == 0 {
s.push(',');
}
s.push(*b as char);
}
s
};
assert!(
table.contains(&comma_grouped),
"table must attribute {comma_grouped} B (the raw original size) as savings:\n{table}"
);
let json_out = run(&home, &["sessions", "show-reductions", &name, "--json"]);
assert!(json_out.status.success());
let log: serde_json::Value = serde_json::from_str(&stdout(&json_out)).unwrap();
assert_eq!(log["reductions"].as_array().unwrap().len(), 1);
let inspect_out = run(&home, &["inspect", &name]);
assert!(inspect_out.status.success(), "{}", stderr(&inspect_out));
let inspect_stdout = stdout(&inspect_out);
assert!(
inspect_stdout.contains("reduced") && inspect_stdout.contains("1 stubs"),
"inspect must show the reduced-session stub count: {inspect_stdout}"
);
std::fs::remove_dir_all(&home).ok();
}