use std::fs;
use std::io::{Read, Write};
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Output, Stdio};
use std::sync::mpsc;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
const PRE_UNIFIED_BASELINE: &str = "74d5950";
#[derive(Clone, Copy)]
struct CompatibilityCase {
name: &'static str,
args: &'static [&'static str],
stdin: &'static [u8],
provider: bool,
expected_stdout: &'static [u8],
}
fn bin() -> PathBuf {
std::env::var_os("SUPERCODE_COMPAT_BIN")
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from(env!("CARGO_BIN_EXE_supercode")))
}
fn fresh_home(case: &str) -> PathBuf {
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let home = std::env::temp_dir().join(format!(
"supercode-unified-compat-{case}-{}-{nonce}",
std::process::id()
));
let supercode_home = home.join("supercode-home");
fs::create_dir_all(&supercode_home).unwrap();
fs::write(
supercode_home.join("config.toml"),
"[capabilities.tui]\nenabled = false\n\
[capabilities.server]\nenabled = true\n",
)
.unwrap();
home
}
fn read_http_request(socket: &mut TcpStream) {
let deadline = Instant::now() + Duration::from_secs(5);
let mut request = Vec::new();
let mut buffer = [0_u8; 8192];
loop {
let remaining = deadline.saturating_duration_since(Instant::now());
assert!(!remaining.is_zero(), "provider request read timed out");
socket.set_read_timeout(Some(remaining)).unwrap();
let count = socket
.read(&mut buffer)
.unwrap_or_else(|error| panic!("provider request read failed: {error}"));
if count == 0 {
return;
}
request.extend_from_slice(&buffer[..count]);
assert!(
request.len() <= 1024 * 1024,
"provider request exceeded 1 MiB"
);
let Some(headers_end) = request.windows(4).position(|bytes| bytes == b"\r\n\r\n") else {
continue;
};
let headers = String::from_utf8_lossy(&request[..headers_end]);
let content_length = headers
.lines()
.find_map(|line| {
let (name, value) = line.split_once(':')?;
name.eq_ignore_ascii_case("content-length")
.then(|| value.trim().parse::<usize>().ok())
.flatten()
})
.unwrap_or_default();
if request.len() >= headers_end + 4 + content_length {
return;
}
}
}
fn spawn_provider() -> (SocketAddr, std::thread::JoinHandle<()>) {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
listener.set_nonblocking(true).unwrap();
let address = listener.local_addr().unwrap();
let task = std::thread::spawn(move || {
let deadline = Instant::now() + Duration::from_secs(10);
let mut socket = loop {
match listener.accept() {
Ok((socket, _)) => break socket,
Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {
assert!(Instant::now() < deadline, "provider was never contacted");
std::thread::sleep(Duration::from_millis(10));
}
Err(error) => panic!("provider accept failed: {error}"),
}
};
socket.set_nonblocking(false).unwrap();
socket
.set_read_timeout(Some(Duration::from_secs(5)))
.unwrap();
socket
.set_write_timeout(Some(Duration::from_secs(5)))
.unwrap();
read_http_request(&mut socket);
let sse = "data: {\"choices\":[{\"delta\":{\"content\":\"COMPAT_REPLY\"}}]}\n\n\
data: {\"choices\":[{\"delta\":{}}],\"usage\":{\"prompt_tokens\":11,\"completion_tokens\":2,\"total_tokens\":13}}\n\n\
data: [DONE]\n\n";
write!(
socket,
"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{sse}",
sse.len()
)
.unwrap();
});
(address, task)
}
fn capture_pipe<R: Read + Send + 'static>(reader: R) -> mpsc::Receiver<std::io::Result<Vec<u8>>> {
let (sender, receiver) = mpsc::sync_channel(1);
std::thread::spawn(move || {
const MAX_CAPTURE_BYTES: u64 = 1024 * 1024;
let mut bytes = Vec::new();
let result = reader
.take(MAX_CAPTURE_BYTES + 1)
.read_to_end(&mut bytes)
.and_then(|_| {
if bytes.len() as u64 > MAX_CAPTURE_BYTES {
Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"compatibility child output exceeded 1 MiB",
))
} else {
Ok(bytes)
}
});
let _ = sender.send(result);
});
receiver
}
fn wait_with_output_bounded(mut child: Child, timeout: Duration) -> Output {
let stdout = capture_pipe(child.stdout.take().unwrap());
let stderr = capture_pipe(child.stderr.take().unwrap());
let deadline = Instant::now() + timeout;
let status = loop {
if let Some(status) = child.try_wait().unwrap() {
break status;
}
if Instant::now() >= deadline {
child.kill().ok();
child.wait().ok();
panic!("compatibility child did not exit within {timeout:?}");
}
std::thread::sleep(Duration::from_millis(10));
};
let capture_timeout = Duration::from_secs(2);
let stdout = stdout
.recv_timeout(capture_timeout)
.expect("stdout capture did not finish after child exit")
.unwrap();
let stderr = stderr
.recv_timeout(capture_timeout)
.expect("stderr capture did not finish after child exit")
.unwrap();
Output {
status,
stdout,
stderr,
}
}
fn join_provider_bounded(task: std::thread::JoinHandle<()>) {
let deadline = Instant::now() + Duration::from_secs(2);
while !task.is_finished() {
assert!(
Instant::now() < deadline,
"compatibility provider did not finish"
);
std::thread::sleep(Duration::from_millis(10));
}
task.join().unwrap();
}
fn run_case(case: CompatibilityCase) -> Output {
let home = fresh_home(case.name);
let supercode_home = home.join("supercode-home");
let provider = case.provider.then(spawn_provider);
let mut command = Command::new(bin());
command
.env("HOME", &home)
.env("SUPERCODE_HOME", &supercode_home)
.env("NO_COLOR", "1")
.env_remove("OPENROUTER_API_KEY")
.env_remove("OPENAI_API_KEY")
.env_remove("ANTHROPIC_API_KEY")
.env_remove("SUPERCODE_QUIET")
.args([
"--quiet",
"--disallow-tool",
"bash",
"--disallow-tool",
"shell",
]);
if let Some((address, _)) = &provider {
command.args(["--api-key", "x", "--base-url", &format!("http://{address}")]);
}
command
.args(case.args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let mut child = command.spawn().unwrap();
child.stdin.as_mut().unwrap().write_all(case.stdin).unwrap();
drop(child.stdin.take());
let output = wait_with_output_bounded(child, Duration::from_secs(15));
if let Some((_, task)) = provider {
join_provider_bounded(task);
}
fs::remove_dir_all(home).ok();
output
}
#[test]
fn machine_surfaces_are_byte_exact_except_for_the_bounded_acp_schema_residue() {
let replaying_historical_binary = std::env::var_os("SUPERCODE_COMPAT_BIN").is_some();
let cases = [
CompatibilityCase {
name: "non_tty_text_run",
args: &["run", "--output-format", "text", "COMPAT_PROMPT"],
stdin: b"",
provider: true,
expected_stdout: include_bytes!(
"fixtures/unified_frontend_prechange/non_tty_text.stdout"
),
},
CompatibilityCase {
name: "json",
args: &["run", "--output-format", "json", "COMPAT_PROMPT"],
stdin: b"",
provider: true,
expected_stdout: include_bytes!("fixtures/unified_frontend_prechange/json.stdout"),
},
CompatibilityCase {
name: "ndjson",
args: &["run", "--output-format", "stream-json", "COMPAT_PROMPT"],
stdin: b"",
provider: true,
expected_stdout: include_bytes!("fixtures/unified_frontend_prechange/ndjson.stdout"),
},
CompatibilityCase {
name: "rpc_stdio",
args: &["--api-key", "x", "run", "--output-format", "rpc"],
stdin: b"{\"id\":7,\"method\":\"interrupt\",\"params\":{}}\n",
provider: false,
expected_stdout: include_bytes!("fixtures/unified_frontend_prechange/rpc.stdout"),
},
CompatibilityCase {
name: "acp_stdio",
args: &["--api-key", "x", "acp"],
stdin: b"{\"jsonrpc\":\"2.0\",\"id\":9,\"method\":\"initialize\",\"params\":{\"protocolVersion\":1,\"clientCapabilities\":{}}}\n",
provider: false,
expected_stdout: include_bytes!("fixtures/unified_frontend_prechange/acp.stdout"),
},
];
for case in cases {
let output = run_case(case);
assert!(
output.status.success(),
"{} failed against baseline {PRE_UNIFIED_BASELINE}: stdout={:?} stderr={:?}",
case.name,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
if case.name == "acp_stdio" {
if replaying_historical_binary {
assert_eq!(
output.stdout, case.expected_stdout,
"historical ACP binary did not reproduce the checked-in baseline bytes"
);
let baseline_json: serde_json::Value =
serde_json::from_slice(case.expected_stdout).unwrap();
assert_eq!(
baseline_json.pointer("/result/agentCapabilities/sessionCapabilities/resume"),
Some(&serde_json::json!(true))
);
assert_eq!(output.stderr, b"");
continue;
}
let canonical_current =
include_bytes!("fixtures/unified_frontend_prechange/acp.current.stdout");
let actual = String::from_utf8(output.stdout.clone()).unwrap();
let current_version = format!("\"version\":\"{}\"", env!("CARGO_PKG_VERSION"));
assert_eq!(
actual.matches(¤t_version).count(),
1,
"ACP output did not carry exactly one current crate version"
);
let canonicalized_actual =
actual.replacen(¤t_version, "\"version\":\"0.2.0\"", 1);
assert_eq!(
canonicalized_actual.as_bytes(),
canonical_current,
"ACP current bytes changed outside the pinned reviewed residue"
);
let baseline_json: serde_json::Value =
serde_json::from_slice(case.expected_stdout).unwrap();
let current_json: serde_json::Value =
serde_json::from_slice(canonical_current).unwrap();
assert_eq!(
baseline_json.pointer("/result/agentCapabilities/sessionCapabilities/resume"),
Some(&serde_json::json!(true))
);
assert_eq!(
current_json.pointer("/result/agentCapabilities/sessionCapabilities/resume"),
Some(&serde_json::json!({}))
);
assert_eq!(
current_json
.pointer("/result/agentCapabilities/_meta/supercode/frontend/eventMethod"),
Some(&serde_json::json!("frontend.v2.event"))
);
assert_eq!(
current_json.pointer(
"/result/agentCapabilities/_meta/supercode/frontend/runtimeOwnedByClient"
),
Some(&serde_json::json!(false))
);
for method in [
"frontend.v2.lease",
"frontend.v2.take_control",
"frontend.v2.heartbeat",
] {
assert!(current_json
.pointer("/result/agentCapabilities/_meta/supercode/frontend/methods")
.and_then(serde_json::Value::as_array)
.is_some_and(|methods| methods.iter().any(|value| value == method)));
}
assert_eq!(
actual
.matches("\"sessionCapabilities\":{\"resume\":{}}")
.count(),
1,
"ACP output drift was not the one reviewed resume capability residue: {actual}"
);
let mut restored: serde_json::Value = serde_json::from_str(&actual).unwrap();
restored["result"]["agentCapabilities"]["sessionCapabilities"]["resume"] =
serde_json::json!(true);
restored["result"]["agentCapabilities"]
.as_object_mut()
.unwrap()
.remove("_meta");
restored["result"]["agentInfo"]["version"] = serde_json::json!("0.1.0");
let restored_baseline = serde_json::to_string(&restored).unwrap() + "\n";
assert_eq!(
restored_baseline.as_bytes(),
case.expected_stdout,
"ACP changed bytes outside the reviewed resume capability, frontend extension, and release-version corrections"
);
} else {
assert_eq!(
output.stdout, case.expected_stdout,
"{} changed bytes from immediate pre-unified baseline {PRE_UNIFIED_BASELINE}",
case.name
);
}
assert_eq!(
output.stderr, b"",
"{} added stderr bytes relative to baseline {PRE_UNIFIED_BASELINE}",
case.name
);
}
}
#[test]
fn legacy_text_repl_fallback_retains_semantics_without_machine_or_terminal_leakage() {
let output = run_case(CompatibilityCase {
name: "legacy_text_repl",
args: &["chat"],
stdin: b"COMPAT_PROMPT\n",
provider: true,
expected_stdout: b"",
});
assert!(
output.status.success(),
"legacy REPL failed: stdout={:?} stderr={:?}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("COMPAT_REPLY"), "{stdout}");
assert!(!stdout.contains('\u{1b}'), "{stdout:?}");
assert!(!stdout.contains("\"type\":"), "{stdout}");
assert!(!stdout.contains("\"payload\":"), "{stdout}");
assert!(output.stderr.is_empty(), "{:?}", output.stderr);
}
#[test]
fn compatibility_fixture_provenance_names_the_first_unified_commit_parent() {
assert_eq!(PRE_UNIFIED_BASELINE, "74d5950");
assert!(Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/unified_frontend_prechange/PROVENANCE.md")
.is_file());
}