struct TestResult {
name: &'static str,
passed: bool,
details: Option<String>,
points: u32,
max_points: u32,
}
impl TestResult {
fn pass(name: &'static str, points: u32, details: String) -> Self {
Self {
name,
passed: true,
details: Some(details),
points,
max_points: points,
}
}
fn fail(name: &'static str, max_points: u32, details: String) -> Self {
Self {
name,
passed: false,
details: Some(details),
points: 0,
max_points,
}
}
fn skip(name: &'static str, max_points: u32, reason: String) -> Self {
Self {
name,
passed: true,
details: Some(format!("SKIP: {}", reason)),
points: 0,
max_points,
}
}
}
struct CellResult {
cell: MatrixCell,
tests: Vec<TestResult>,
total_points: u32,
max_points: u32,
elapsed: std::time::Duration,
}
impl CellResult {
fn passed(&self) -> bool {
self.tests.iter().all(|t| t.passed)
}
}
struct Config {
apr_binary: PathBuf,
trace_level: TraceLevel,
test_class: TestClass,
min_cpu_tps: f64,
min_gpu_tps: f64,
min_gpu_tps_float32: f64,
verbose: bool,
gguf_model: String,
safetensors_model: String,
apr_model: String,
with_ollama: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
apr_binary: find_apr_binary(),
trace_level: TraceLevel::None,
test_class: TestClass::Quantized, min_cpu_tps: 5.0, min_gpu_tps: 5.0, min_gpu_tps_float32: 5.0, verbose: false,
gguf_model: default_model_for_format(Format::Gguf),
safetensors_model: default_model_for_format(Format::SafeTensors),
apr_model: default_model_for_format(Format::Apr),
with_ollama: false,
}
}
}
fn find_apr_binary() -> PathBuf {
let mut candidates: Vec<PathBuf> = Vec::new();
if let Ok(target_dir) = std::env::var("CARGO_TARGET_DIR") {
candidates.push(PathBuf::from(&target_dir).join("release").join("apr"));
candidates.push(PathBuf::from(&target_dir).join("debug").join("apr"));
}
candidates.push(PathBuf::from("target/release/apr"));
candidates.push(PathBuf::from("target/debug/apr"));
for path in candidates {
if path.exists() {
return path;
}
}
PathBuf::from("apr")
}
const CANONICAL_GGUF: &str =
"hf://Qwen/Qwen2.5-Coder-1.5B-Instruct-GGUF/qwen2.5-coder-1.5b-instruct-q4_k_m.gguf";
const CANONICAL_SAFETENSORS: &str = "hf://Qwen/Qwen2.5-Coder-1.5B-Instruct";
fn default_model_for_format(format: Format) -> String {
match format {
Format::Gguf => CANONICAL_GGUF.to_string(),
Format::SafeTensors => CANONICAL_SAFETENSORS.to_string(),
Format::Apr => CANONICAL_GGUF.to_string(),
}
}
fn gpu_available() -> bool {
Command::new("nvidia-smi")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
fn wait_with_timeout(
child: &mut Child,
timeout: Duration,
) -> Result<std::process::ExitStatus, String> {
let start = Instant::now();
let poll_interval = Duration::from_millis(100);
loop {
match child.try_wait() {
Ok(Some(status)) => return Ok(status),
Ok(None) => {
if start.elapsed() >= timeout {
let _ = child.kill();
let _ = child.wait(); return Err(format!(
"HANG: Process killed after {}s timeout (PMAT-QA-PROTOCOL-001 violation)",
timeout.as_secs()
));
}
std::thread::sleep(poll_interval);
}
Err(e) => return Err(format!("Process error: {}", e)),
}
}
}
fn run_apr(config: &Config, args: &[&str]) -> Result<String, String> {
run_apr_with_timeout(config, args, DEFAULT_TIMEOUT)
}
fn run_apr_with_timeout(
config: &Config,
args: &[&str],
timeout: Duration,
) -> Result<String, String> {
let mut cmd = if config.apr_binary.to_string_lossy() == "cargo" {
let mut c = Command::new("cargo");
c.args(["run", "-p", "apr-cli", "--release", "--"]);
c.args(args);
c
} else {
let mut c = Command::new(&config.apr_binary);
c.args(args);
c
};
cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
if config.verbose {
eprintln!("{}DEBUG: {:?}{}", CYAN, cmd, NC);
}
let mut child = match cmd.spawn() {
Ok(child) => child,
Err(e) => return Err(format!("Failed to spawn: {}", e)),
};
let status = wait_with_timeout(&mut child, timeout)?;
let mut stdout_str = String::new();
let mut stderr_str = String::new();
if let Some(mut stdout) = child.stdout.take() {
let _ = stdout.read_to_string(&mut stdout_str);
}
if let Some(mut stderr) = child.stderr.take() {
let _ = stderr.read_to_string(&mut stderr_str);
}
if status.success() {
Ok(format!("{}{}", stdout_str, stderr_str))
} else {
Err(format!("Exit {}: {}{}", status, stdout_str, stderr_str))
}
}
fn run_chat_test(
config: &Config,
model: &str,
prompt: &str,
backend: Backend,
with_trace: bool,
timeout: Duration,
) -> Result<String, String> {
use std::io::Write;
let mut args: Vec<&str> = vec!["chat", model];
if let Some(flag) = backend.flag() {
args.push(flag);
}
if with_trace {
args.push("--trace");
}
let mut cmd = if config.apr_binary.to_string_lossy() == "cargo" {
let mut c = Command::new("cargo");
c.args(["run", "-p", "apr-cli", "--release", "--"]);
c.args(&args);
c
} else {
let mut c = Command::new(&config.apr_binary);
c.args(&args);
c
};
cmd.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
if config.verbose {
eprintln!("{}DEBUG (chat): {:?}{}", CYAN, cmd, NC);
}
let mut child = match cmd.spawn() {
Ok(child) => child,
Err(e) => return Err(format!("Failed to spawn chat: {}", e)),
};
let pid = child.id();
register_process(pid);
if let Some(mut stdin) = child.stdin.take() {
let _ = writeln!(stdin, "{}", prompt);
}
let status = wait_with_timeout(&mut child, timeout);
unregister_process(pid);
let status = status?;
let mut stdout_str = String::new();
let mut stderr_str = String::new();
if let Some(mut stdout) = child.stdout.take() {
let _ = stdout.read_to_string(&mut stdout_str);
}
if let Some(mut stderr) = child.stderr.take() {
let _ = stderr.read_to_string(&mut stderr_str);
}
if status.success() {
Ok(format!("{}{}", stdout_str, stderr_str))
} else {
Err(format!(
"Chat exit {}: {}{}",
status, stdout_str, stderr_str
))
}
}
fn wait_for_server_ready(server_guard: &mut ProcessGuard, port: u16) -> Result<(), String> {
let start = Instant::now();
let server_timeout = Duration::from_secs(30);
let health_url = format!("http://127.0.0.1:{port}/health");
loop {
if start.elapsed() >= server_timeout {
return Err("Server startup timeout (30s)".to_string());
}
if let Some(child) = server_guard.child_mut() {
if let Ok(Some(status)) = child.try_wait() {
return Err(format!("Server exited early: {status}"));
}
}
let health_check = Command::new("curl")
.args(["-s", "-o", "/dev/null", "-w", "%{http_code}", &health_url])
.output();
if let Ok(output) = health_check {
let code = String::from_utf8_lossy(&output.stdout);
if code.trim() == "200" {
return Ok(());
}
}
std::thread::sleep(Duration::from_millis(500));
}
}
fn run_serve_test(
config: &Config,
model: &str,
prompt: &str,
backend: Backend,
with_trace: bool,
timeout: Duration,
) -> Result<String, String> {
use std::net::TcpListener;
let port = TcpListener::bind("127.0.0.1:0")
.map(|l| {
l.local_addr()
.expect("bound listener must have local addr")
.port()
})
.unwrap_or(18080);
let port_str = port.to_string();
let mut args: Vec<&str> = vec!["serve", model, "--port", &port_str];
if let Some(flag) = backend.flag() {
args.push(flag);
}
let mut cmd = if config.apr_binary.to_string_lossy() == "cargo" {
let mut c = Command::new("cargo");
c.args(["run", "-p", "apr-cli", "--release", "--"]);
c.args(&args);
c
} else {
let mut c = Command::new(&config.apr_binary);
c.args(&args);
c
};
cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
if config.verbose {
eprintln!("{}DEBUG (serve): {:?} on port {}{}", CYAN, cmd, port, NC);
}
let mut server_guard = match cmd.spawn() {
Ok(child) => ProcessGuard::new(child),
Err(e) => return Err(format!("Failed to spawn serve: {}", e)),
};
wait_for_server_ready(&mut server_guard, port)?;
let body = format!(
r#"{{"model":"test","messages":[{{"role":"user","content":"{}"}}],"max_tokens":10}}"#,
prompt
);
let url = format!("http://127.0.0.1:{}/v1/chat/completions", port);
let mut curl_args = vec![
"-s",
"-X",
"POST",
&url,
"-H",
"Content-Type: application/json",
"-d",
&body,
];
if with_trace {
curl_args.extend(["-H", "X-Trace-Level: layer"]);
}
let request_start = Instant::now();
let response = Command::new("curl")
.args(&curl_args)
.output()
.map_err(|e| format!("curl failed: {}", e))?;
if request_start.elapsed() >= timeout {
return Err(format!("Request timeout ({}s)", timeout.as_secs()));
}
server_guard.kill_and_wait();
if response.status.success() {
Ok(String::from_utf8_lossy(&response.stdout).to_string())
} else {
Err(format!(
"curl error: {}",
String::from_utf8_lossy(&response.stderr)
))
}
}