use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use super::conformance::{
EXIT_HF2Q_BINARY_NOT_RELEASE, EXIT_HF_REPO_UNRESOLVABLE, EXIT_HF_TOKEN_MISSING,
EXIT_INSUFFICIENT_DISK, EXIT_LLAMA_CLI_MISSING, EXIT_OK, EXIT_SMOKE_ASSERTION_FAILED,
EXIT_UNKNOWN_ARCH,
};
use super::registry::{ArchEntry, ArchRegistry};
#[derive(Debug, Clone)]
pub struct SmokeArgs {
pub arch: String,
pub quant: String,
pub with_vision: bool,
pub skip_convert: bool,
pub dry_run: bool,
pub fixtures_root: Option<PathBuf>,
pub local_dir: Option<PathBuf>,
pub convert_output_dir: Option<PathBuf>,
pub llama_cli_override: Option<PathBuf>,
}
pub trait SmokeEnv {
fn hf_token(&self) -> Option<String>;
fn free_disk_gb(&self, path: &Path) -> Option<u32>;
fn which(&self, program: &str) -> Option<PathBuf>;
fn is_release_build(&self) -> bool;
fn resolve_hf_repo(&self, repo: &str) -> bool;
}
pub struct RealSmokeEnv {
pub convert_dir: PathBuf,
}
impl SmokeEnv for RealSmokeEnv {
fn hf_token(&self) -> Option<String> {
if let Ok(t) = std::env::var("HF_TOKEN") {
if !t.is_empty() {
return Some(t);
}
}
if let Some(home) = std::env::var_os("HOME") {
let p = Path::new(&home).join(".cache/huggingface/token");
if let Ok(content) = std::fs::read_to_string(&p) {
let trimmed = content.trim();
if !trimmed.is_empty() {
return Some(trimmed.to_string());
}
}
}
None
}
fn free_disk_gb(&self, path: &Path) -> Option<u32> {
let _ = path;
None
}
fn which(&self, program: &str) -> Option<PathBuf> {
let path_env = std::env::var_os("PATH")?;
for dir in std::env::split_paths(&path_env) {
let candidate = dir.join(program);
if candidate.is_file() {
return Some(candidate);
}
}
None
}
fn is_release_build(&self) -> bool {
std::env::current_exe()
.ok()
.and_then(|p| p.to_str().map(|s| s.contains("/release/")))
.unwrap_or(false)
}
fn resolve_hf_repo(&self, _repo: &str) -> bool {
true
}
}
pub type PreflightResult = Result<(), (u8, String)>;
pub fn preflight_full(
entry: &ArchEntry,
env: &dyn SmokeEnv,
local_dir_provided: bool,
llama_cli_override: Option<&Path>,
) -> PreflightResult {
if !local_dir_provided && env.hf_token().map_or(true, |t| t.is_empty()) {
return Err((
EXIT_HF_TOKEN_MISSING,
format!(
"HF_TOKEN is not set (required to download {}). \
Export HF_TOKEN=<your token> and retry, or pass --local-dir \
to use a pre-downloaded safetensors directory.",
entry.hf_repos.first().unwrap_or(&"<repo>")
),
));
}
let required = entry.disk_floor_gb + 10;
let convert_dir = Path::new(".");
if let Some(avail) = env.free_disk_gb(convert_dir) {
if avail < required {
return Err((
EXIT_INSUFFICIENT_DISK,
format!(
"insufficient free disk: {} GB available, {} GB required \
(arch floor {} + 10 GB buffer)",
avail, required, entry.disk_floor_gb
),
));
}
}
if let Some(override_path) = llama_cli_override {
if !override_path.is_file() {
return Err((
EXIT_LLAMA_CLI_MISSING,
format!(
"--llama-cli-override path {:?} does not exist",
override_path
),
));
}
} else {
let llama_cli_candidates = &[
Path::new("/opt/llama.cpp/build/bin/llama-cli"),
Path::new("/usr/local/bin/llama-cli"),
];
let has_llama_cli =
llama_cli_candidates.iter().any(|p| p.is_file()) || env.which("llama-cli").is_some();
if !has_llama_cli {
return Err((
EXIT_LLAMA_CLI_MISSING,
"llama-cli not found (looked in /opt/llama.cpp/build/bin/, PATH). \
Build llama.cpp or install to /usr/local/bin/, or pass \
--llama-cli-override <path>."
.into(),
));
}
}
if !env.is_release_build() {
return Err((
EXIT_HF2Q_BINARY_NOT_RELEASE,
"hf2q smoke requires a release build. Run via `cargo run --release -- smoke ...` \
or install the release binary."
.into(),
));
}
if !local_dir_provided {
for repo in entry.hf_repos {
if !env.resolve_hf_repo(repo) {
return Err((
EXIT_HF_REPO_UNRESOLVABLE,
format!(
"HF repo {:?} unresolvable (no access or does not exist)",
repo
),
));
}
}
}
Ok(())
}
pub fn preflight(entry: &ArchEntry, env: &dyn SmokeEnv) -> PreflightResult {
preflight_full(entry, env, false, None)
}
pub fn preflight_with_local(
entry: &ArchEntry,
env: &dyn SmokeEnv,
local_dir_provided: bool,
) -> PreflightResult {
preflight_full(entry, env, local_dir_provided, None)
}
#[derive(Debug, Clone)]
pub enum SmokeOutcome {
Pass {
transcript_path: PathBuf,
},
PreflightFailed {
exit_code: u8,
reason: String,
},
UnknownArch {
requested: String,
known: Vec<&'static str>,
},
Skipped {
reason: String,
},
}
impl SmokeOutcome {
pub fn exit_code(&self) -> u8 {
match self {
SmokeOutcome::Pass { .. } => EXIT_OK,
SmokeOutcome::PreflightFailed { exit_code, .. } => *exit_code,
SmokeOutcome::UnknownArch { .. } => EXIT_UNKNOWN_ARCH,
SmokeOutcome::Skipped { .. } => EXIT_OK,
}
}
}
pub fn dispatch(args: &SmokeArgs, env: &dyn SmokeEnv) -> SmokeOutcome {
let entry = match ArchRegistry::global().get(&args.arch) {
Ok(e) => e,
Err(err) => {
let known = ArchRegistry::global().known_arches();
return SmokeOutcome::UnknownArch {
requested: match err {
super::registry::ArchError::UnknownArch { requested, .. } => requested,
},
known,
};
}
};
if args.dry_run {
print_dry_run_report(entry, args);
}
let local_dir_provided = args.local_dir.is_some();
if let Err((code, reason)) = preflight_full(
entry,
env,
local_dir_provided,
args.llama_cli_override.as_deref(),
) {
return SmokeOutcome::PreflightFailed {
exit_code: code,
reason,
};
}
if let Some(local) = &args.local_dir {
if !local.exists() {
return SmokeOutcome::PreflightFailed {
exit_code: EXIT_SMOKE_ASSERTION_FAILED,
reason: format!(
"--local-dir {:?} does not exist (no input safetensors directory \
to convert from). Pass a valid path or omit --local-dir to use \
the HF download path.",
local
),
};
}
if !local.is_dir() {
return SmokeOutcome::PreflightFailed {
exit_code: EXIT_SMOKE_ASSERTION_FAILED,
reason: format!(
"--local-dir {:?} is not a directory (expected a HuggingFace \
model directory containing config.json + safetensors).",
local
),
};
}
if !local.join("config.json").is_file() {
return SmokeOutcome::PreflightFailed {
exit_code: EXIT_SMOKE_ASSERTION_FAILED,
reason: format!(
"--local-dir {:?} has no config.json (expected a HuggingFace \
model directory). If this is a fresh download in progress, \
wait for it to finish; otherwise pass the correct path.",
local
),
};
}
}
if args.dry_run {
let path = resolve_transcript_path(args, entry);
return SmokeOutcome::Pass {
transcript_path: path,
};
}
if args.quant.starts_with("dwq") {
return SmokeOutcome::Skipped {
reason: format!(
"DWQ quality gate (ADR-012 P9) not yet wired — {} returns Skipped until \
RealActivationCapture lands. See docs/ADR-012-qwen35moe-conversion.md Decision 17.",
args.quant
),
};
}
match run_q4_0_pipeline(entry, args) {
Ok(transcript_path) => SmokeOutcome::Pass { transcript_path },
Err(reason) => SmokeOutcome::PreflightFailed {
exit_code: EXIT_SMOKE_ASSERTION_FAILED,
reason,
},
}
}
fn run_q4_0_pipeline(entry: &ArchEntry, args: &SmokeArgs) -> Result<PathBuf, String> {
use std::process::Command;
let input_dir = args.local_dir.clone().ok_or_else(|| {
format!(
"non-local smoke path (HF download) is not shipped in this commit; \
pass --local-dir <path> to convert a pre-downloaded safetensors dir \
for arch {}.",
entry.arch
)
})?;
if !input_dir.exists() {
return Err(format!("--local-dir {:?} does not exist", input_dir));
}
let keep_dir = args
.convert_output_dir
.clone()
.unwrap_or_else(|| std::env::temp_dir().join("hf2q-smoke-convert"));
let _ = std::fs::create_dir_all(&keep_dir);
let gguf_path = keep_dir.join(format!("{}-{}.gguf", entry.arch, args.quant));
if !args.skip_convert {
let hf2q_exe = std::env::current_exe().map_err(|e| format!("locate hf2q binary: {}", e))?;
let convert_args = build_convert_args(args, entry, &input_dir, &gguf_path)?;
let convert_out = Command::new(&hf2q_exe)
.args(&convert_args)
.env("HF2Q_QWEN35_DROP_MTP", "1")
.output()
.map_err(|e| format!("run hf2q convert: {}", e))?;
if !convert_out.status.success() {
return Err(format!(
"hf2q convert failed (exit {}): {}",
convert_out.status,
String::from_utf8_lossy(&convert_out.stderr)
));
}
} else if !gguf_path.exists() {
return Err(format!(
"--skip-convert set but no pre-existing GGUF at {:?}",
gguf_path
));
}
let llama_cli = match &args.llama_cli_override {
Some(p) => p.clone(),
None => find_llama_cli()?,
};
let prompt = entry
.smoke_prompts
.first()
.copied()
.unwrap_or("The quick brown fox");
let llama_out = Command::new(&llama_cli)
.args([
"--model",
gguf_path.to_str().ok_or("gguf_path not UTF-8")?,
"--prompt",
prompt,
"-n",
"8",
"--seed",
"42",
"--temp",
"0",
"--no-warmup",
"-no-cnv",
"--single-turn",
])
.output()
.map_err(|e| format!("run llama-cli: {}", e))?;
let combined_stderr = String::from_utf8_lossy(&llama_out.stderr);
let combined_stdout = String::from_utf8_lossy(&llama_out.stdout);
let sanitized_stderr = sanitize_timestamps(&combined_stderr);
let sanitized_stdout = sanitize_timestamps(&strip_terminal_control(&combined_stdout));
let transcript_body = format!(
"# hf2q smoke transcript\n\
# arch: {}\n\
# quant: {}\n\
# prompt: {:?}\n\
# (timestamps stripped; byte-stable across runs)\n\n\
---stdout---\n{}\n\
---stderr---\n{}\n",
entry.arch, args.quant, prompt, sanitized_stdout, sanitized_stderr
);
super::conformance::scan_llama_cli_stderr(&combined_stderr)
.map_err(|e| format!("llama-cli regression pattern: {}", e))?;
if let Some(n_eval) = super::conformance::extract_n_eval(&combined_stderr) {
if n_eval != 8 {
return Err(format!("llama-cli produced {} tokens, expected 8", n_eval));
}
} else {
}
let transcript_path = resolve_transcript_path(args, entry);
if let Some(parent) = transcript_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
std::fs::write(&transcript_path, transcript_body)
.map_err(|e| format!("write transcript {:?}: {}", transcript_path, e))?;
Ok(transcript_path)
}
fn build_convert_args(
args: &SmokeArgs,
_entry: &ArchEntry,
input_dir: &Path,
gguf_path: &Path,
) -> Result<Vec<String>, String> {
let convert_args: Vec<String> = vec![
"convert".into(),
input_dir.to_str().ok_or("input_dir not UTF-8")?.into(),
"--quant".into(),
args.quant.clone(),
"-o".into(),
gguf_path.to_str().ok_or("gguf_path not UTF-8")?.into(),
];
Ok(convert_args)
}
fn sanitize_timestamps(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let bytes = s.as_bytes();
let mut i = 0;
while i < bytes.len() {
let c = bytes[i];
if c.is_ascii_digit() {
let start = i;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
let has_decimal =
i + 1 < bytes.len() && bytes[i] == b'.' && bytes[i + 1].is_ascii_digit();
if has_decimal {
i += 1; while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
while out.ends_with(' ') || out.ends_with('\t') {
out.pop();
}
out.push_str("<X.XX>");
} else {
out.push_str(&s[start..i]);
}
} else {
let ch_start = i;
i += 1;
while i < bytes.len() && (bytes[i] & 0xC0) == 0x80 {
i += 1;
}
out.push_str(&s[ch_start..i]);
}
}
out
}
fn strip_terminal_control(s: &str) -> String {
let mut out = String::with_capacity(s.len() / 4);
let mut line_buf: Vec<char> = Vec::with_capacity(256);
for ch in s.chars() {
let b = ch as u32;
match b {
0x08 => {
line_buf.pop();
}
0x0D => {
line_buf.clear();
}
0x0A => {
for c in line_buf.drain(..) {
out.push(c);
}
out.push('\n');
}
0x09 => {
line_buf.push(ch);
}
b if b < 0x20 => {
}
_ => {
line_buf.push(ch);
}
}
}
for c in line_buf.drain(..) {
out.push(c);
}
out
}
fn find_llama_cli() -> Result<PathBuf, String> {
let candidates = [
"/opt/llama.cpp/build/bin/llama-cli",
"/usr/local/bin/llama-cli",
];
for c in candidates {
if std::path::Path::new(c).is_file() {
return Ok(PathBuf::from(c));
}
}
if let Some(path_env) = std::env::var_os("PATH") {
for dir in std::env::split_paths(&path_env) {
let cand = dir.join("llama-cli");
if cand.is_file() {
return Ok(cand);
}
}
}
Err("llama-cli not found".into())
}
pub fn print_dry_run_report(entry: &ArchEntry, args: &SmokeArgs) {
println!("═══ hf2q smoke dry-run ═══");
println!(" arch: {}", entry.arch);
println!(" quant: {}", args.quant);
println!(" has_mtp: {}", entry.has_mtp);
println!(" has_vision: {}", entry.has_vision);
println!(
" disk_floor_gb: {} (+10 GB buffer = {} required)",
entry.disk_floor_gb,
entry.disk_floor_gb + 10
);
println!(" hf_architectures: {}", entry.hf_architectures.join(", "));
println!(" hf_repos: {}", entry.hf_repos.join(", "));
println!(
" tensor_catalog: {} template entries",
entry.tensor_catalog.entries.len()
);
println!(" quality_thresholds:");
println!(
" ppl_ratio_dwq46: ≤ {:.2}×",
entry.quality_thresholds.ppl_ratio_dwq46
);
println!(
" ppl_ratio_dwq48: ≤ {:.2}×",
entry.quality_thresholds.ppl_ratio_dwq48
);
println!(
" max_median_kl: < {:.2} nats",
entry.quality_thresholds.max_median_kl
);
println!(
" smoke_prompts: {}",
entry.smoke_prompts.first().unwrap_or(&"(none)")
);
let path = resolve_transcript_path(args, entry);
println!(" transcript_path: {}", path.display());
if let Some(local_dir) = &args.local_dir {
println!(
" local_dir: {} (HF_TOKEN preflight skipped)",
local_dir.display()
);
}
println!("═══════════════════════════");
}
pub fn resolve_transcript_path(args: &SmokeArgs, entry: &ArchEntry) -> PathBuf {
let root = args
.fixtures_root
.clone()
.unwrap_or_else(|| PathBuf::from("tests/fixtures"));
root.join("smoke-transcripts")
.join(format!("{}-{}.txt", entry.arch, args.quant))
}
pub fn render_outcome(outcome: &SmokeOutcome) -> String {
match outcome {
SmokeOutcome::Pass { transcript_path } => {
format!("hf2q smoke: pass → {}", transcript_path.display())
}
SmokeOutcome::PreflightFailed { exit_code, reason } => {
format!(
"hf2q smoke: preflight failed (exit {}): {}",
exit_code, reason
)
}
SmokeOutcome::UnknownArch { requested, known } => format!(
"hf2q smoke: unknown arch {:?}; known arches: {}",
requested,
known.join(", ")
),
SmokeOutcome::Skipped { reason } => format!("hf2q smoke: skipped — {}", reason),
}
}
pub fn normalize_quant_label<S: AsRef<OsStr>>(s: S) -> String {
s.as_ref().to_string_lossy().to_ascii_lowercase()
}
#[cfg(test)]
mod tests {
use super::*;
fn env_lock() -> std::sync::MutexGuard<'static, ()> {
static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
LOCK.lock().unwrap_or_else(|e| e.into_inner())
}
#[test]
fn hf_token_falls_back_to_huggingface_cache_file() {
let _g = env_lock();
let prev_token = std::env::var("HF_TOKEN").ok();
let prev_home = std::env::var("HOME").ok();
let tmp = tempfile::tempdir().expect("tempdir");
let cache_dir = tmp.path().join(".cache/huggingface");
std::fs::create_dir_all(&cache_dir).expect("mkdir");
std::fs::write(cache_dir.join("token"), "hf_cached_token_xyz\n").expect("write token");
std::env::remove_var("HF_TOKEN");
std::env::set_var("HOME", tmp.path());
let env = RealSmokeEnv {
convert_dir: tmp.path().to_path_buf(),
};
let resolved = env.hf_token();
assert_eq!(resolved.as_deref(), Some("hf_cached_token_xyz"));
std::env::set_var("HF_TOKEN", "hf_env_wins");
assert_eq!(env.hf_token().as_deref(), Some("hf_env_wins"));
std::env::set_var("HF_TOKEN", "");
assert_eq!(env.hf_token().as_deref(), Some("hf_cached_token_xyz"));
match prev_token {
Some(v) => std::env::set_var("HF_TOKEN", v),
None => std::env::remove_var("HF_TOKEN"),
}
match prev_home {
Some(v) => std::env::set_var("HOME", v),
None => std::env::remove_var("HOME"),
}
}
#[test]
fn hf_token_empty_cache_file_returns_none() {
let _g = env_lock();
let prev_token = std::env::var("HF_TOKEN").ok();
let prev_home = std::env::var("HOME").ok();
let tmp = tempfile::tempdir().expect("tempdir");
let cache_dir = tmp.path().join(".cache/huggingface");
std::fs::create_dir_all(&cache_dir).expect("mkdir");
std::fs::write(cache_dir.join("token"), " \n ").expect("write empty");
std::env::remove_var("HF_TOKEN");
std::env::set_var("HOME", tmp.path());
let env = RealSmokeEnv {
convert_dir: tmp.path().to_path_buf(),
};
assert_eq!(env.hf_token(), None);
match prev_token {
Some(v) => std::env::set_var("HF_TOKEN", v),
None => std::env::remove_var("HF_TOKEN"),
}
match prev_home {
Some(v) => std::env::set_var("HOME", v),
None => std::env::remove_var("HOME"),
}
}
struct MockEnv {
hf_token: Option<String>,
free_disk_gb: Option<u32>,
llama_cli_present: bool,
release_build: bool,
repo_resolves: bool,
}
impl Default for MockEnv {
fn default() -> Self {
MockEnv {
hf_token: Some("hf_test".into()),
free_disk_gb: Some(500),
llama_cli_present: true,
release_build: true,
repo_resolves: true,
}
}
}
impl SmokeEnv for MockEnv {
fn hf_token(&self) -> Option<String> {
self.hf_token.clone()
}
fn free_disk_gb(&self, _: &Path) -> Option<u32> {
self.free_disk_gb
}
fn which(&self, program: &str) -> Option<PathBuf> {
if program == "llama-cli" && self.llama_cli_present {
Some(PathBuf::from("/usr/local/bin/llama-cli"))
} else {
None
}
}
fn is_release_build(&self) -> bool {
self.release_build
}
fn resolve_hf_repo(&self, _: &str) -> bool {
self.repo_resolves
}
}
fn args_for(arch: &str, quant: &str) -> SmokeArgs {
SmokeArgs {
arch: arch.to_string(),
quant: quant.to_string(),
with_vision: false,
skip_convert: false,
dry_run: true,
fixtures_root: Some(PathBuf::from("tests/fixtures")),
local_dir: None,
convert_output_dir: None,
llama_cli_override: None,
}
}
#[test]
fn local_dir_missing_path_returns_preflight_failure_in_dry_run() {
let env = MockEnv::default();
let mut args = args_for("qwen35", "q4_0");
args.local_dir = Some(PathBuf::from(
"/this/path/definitely/does/not/exist/qwen35-input",
));
args.dry_run = true;
let outcome = dispatch(&args, &env);
match outcome {
SmokeOutcome::PreflightFailed { exit_code, reason } => {
assert_eq!(
exit_code, EXIT_SMOKE_ASSERTION_FAILED,
"missing --local-dir path must trip exit 8 (parity with \
run_q4_0_pipeline's post-preflight check)"
);
assert!(
reason.contains("--local-dir"),
"error must name the offending flag, got: {reason}"
);
assert!(
reason.contains("does not exist"),
"error must explain the missing-dir condition, got: {reason}"
);
}
other => {
panic!("expected PreflightFailed with EXIT_SMOKE_ASSERTION_FAILED, got: {other:?}")
}
}
}
#[test]
fn local_dir_without_config_json_returns_preflight_failure() {
let env = MockEnv::default();
let tmp = tempfile::tempdir().unwrap();
let mut args = args_for("qwen35", "q4_0");
args.local_dir = Some(tmp.path().to_path_buf());
args.dry_run = true;
let outcome = dispatch(&args, &env);
match outcome {
SmokeOutcome::PreflightFailed { exit_code, reason } => {
assert_eq!(exit_code, EXIT_SMOKE_ASSERTION_FAILED);
assert!(
reason.contains("config.json"),
"error must name `config.json`, got: {reason}"
);
}
other => panic!("expected PreflightFailed, got: {other:?}"),
}
}
#[cfg(unix)]
#[test]
fn local_dir_pointing_at_a_file_returns_preflight_failure() {
let env = MockEnv::default();
let tmp = tempfile::tempdir().unwrap();
let file_path = tmp.path().join("not-a-dir.txt");
std::fs::write(&file_path, b"oops").unwrap();
assert!(file_path.is_file() && !file_path.is_dir());
let mut args = args_for("qwen35", "q4_0");
args.local_dir = Some(file_path.clone());
args.dry_run = true;
let outcome = dispatch(&args, &env);
match outcome {
SmokeOutcome::PreflightFailed { exit_code, reason } => {
assert_eq!(exit_code, EXIT_SMOKE_ASSERTION_FAILED);
assert!(
reason.contains("not a directory"),
"error must say 'not a directory', got: {reason}"
);
assert!(
reason.contains(file_path.to_str().unwrap()),
"error must name the offending path, got: {reason}"
);
}
other => panic!("expected PreflightFailed, got: {other:?}"),
}
}
#[test]
fn llama_cli_override_missing_path_returns_exit_4() {
let env = MockEnv::default();
let entry = ArchRegistry::global().get("qwen35").unwrap();
let nonexistent = Path::new("/this/path/definitely/does/not/exist/llama-cli");
let result = preflight_full(entry, &env, true, Some(nonexistent));
match result {
Err((code, reason)) => {
assert_eq!(
code, EXIT_LLAMA_CLI_MISSING,
"missing override path must trip EXIT_LLAMA_CLI_MISSING (4)"
);
assert!(
reason.contains("--llama-cli-override"),
"error must name the offending flag, got: {reason}"
);
assert!(
reason.contains("does not exist"),
"error must explain the missing-file condition, got: {reason}"
);
}
Ok(()) => panic!("preflight must fail on missing override path"),
}
}
#[test]
fn unknown_arch_returns_uniform_outcome_for_every_non_registered_key() {
let env = MockEnv::default();
for arch in &["gemma4", "ministral", "deepseekv3", "bogus", ""] {
let out = dispatch(&args_for(arch, "q4_0"), &env);
match out {
SmokeOutcome::UnknownArch { requested, known } => {
assert_eq!(requested, *arch);
assert_eq!(known, vec!["qwen35", "qwen35moe"]);
}
other => panic!("expected UnknownArch for {:?}, got {:?}", arch, other),
}
}
}
#[test]
fn unknown_arch_exit_code_is_seven() {
let env = MockEnv::default();
let out = dispatch(&args_for("bogus", "q4_0"), &env);
assert_eq!(out.exit_code(), EXIT_UNKNOWN_ARCH);
}
#[test]
fn missing_hf_token_exit_code_2() {
let env = MockEnv {
hf_token: None,
..MockEnv::default()
};
let out = dispatch(&args_for("qwen35", "q4_0"), &env);
assert_eq!(out.exit_code(), EXIT_HF_TOKEN_MISSING);
let rendered = render_outcome(&out);
assert!(rendered.contains("HF_TOKEN"));
}
#[test]
fn empty_hf_token_exit_code_2() {
let env = MockEnv {
hf_token: Some(String::new()),
..MockEnv::default()
};
let out = dispatch(&args_for("qwen35", "q4_0"), &env);
assert_eq!(out.exit_code(), EXIT_HF_TOKEN_MISSING);
}
#[test]
fn insufficient_disk_exit_code_3() {
let env = MockEnv {
free_disk_gb: Some(50),
..MockEnv::default()
};
let out = dispatch(&args_for("qwen35", "q4_0"), &env);
assert_eq!(out.exit_code(), EXIT_INSUFFICIENT_DISK);
let rendered = render_outcome(&out);
assert!(rendered.contains("insufficient free disk"));
}
#[test]
fn missing_llama_cli_exit_code_4() {
let env = MockEnv {
llama_cli_present: false,
..MockEnv::default()
};
let out = dispatch(&args_for("qwen35", "q4_0"), &env);
if !Path::new("/opt/llama.cpp/build/bin/llama-cli").is_file() {
assert_eq!(out.exit_code(), EXIT_LLAMA_CLI_MISSING);
}
}
#[test]
fn non_release_build_exit_code_5() {
let env = MockEnv {
release_build: false,
..MockEnv::default()
};
let out = dispatch(&args_for("qwen35", "q4_0"), &env);
if Path::new("/opt/llama.cpp/build/bin/llama-cli").is_file() {
assert_eq!(out.exit_code(), EXIT_HF2Q_BINARY_NOT_RELEASE);
}
}
#[test]
fn unresolvable_repo_exit_code_6() {
let env = MockEnv {
repo_resolves: false,
..MockEnv::default()
};
let out = dispatch(&args_for("qwen35", "q4_0"), &env);
if Path::new("/opt/llama.cpp/build/bin/llama-cli").is_file() {
assert_eq!(out.exit_code(), EXIT_HF_REPO_UNRESOLVABLE);
}
}
#[test]
fn dry_run_pass_returns_transcript_path_under_fixtures_root() {
let env = MockEnv::default();
let out = dispatch(&args_for("qwen35", "q4_0"), &env);
if Path::new("/opt/llama.cpp/build/bin/llama-cli").is_file() {
match out {
SmokeOutcome::Pass { transcript_path } => {
assert!(transcript_path.ends_with("smoke-transcripts/qwen35-q4_0.txt"));
}
other => panic!("expected Pass, got {:?}", other),
}
}
}
#[test]
fn dwq_quant_label_returns_skipped_pre_p9() {
let env = MockEnv::default();
let mut args = args_for("qwen35", "dwq-mixed-4-6");
args.dry_run = false; let out = dispatch(&args, &env);
if Path::new("/opt/llama.cpp/build/bin/llama-cli").is_file() {
match out {
SmokeOutcome::Skipped { reason } => {
assert!(reason.contains("DWQ"));
assert!(reason.contains("P9"));
}
other => panic!("expected Skipped pre-P9, got {:?}", other),
}
}
}
#[test]
fn render_outcome_contains_arch_name_for_unknown() {
let out = SmokeOutcome::UnknownArch {
requested: "gemma4".into(),
known: vec!["qwen35", "qwen35moe"],
};
let s = render_outcome(&out);
assert!(s.contains("gemma4"));
assert!(s.contains("qwen35"));
assert!(s.contains("qwen35moe"));
}
#[test]
fn render_outcome_is_single_line() {
let out = SmokeOutcome::PreflightFailed {
exit_code: 2,
reason: "HF_TOKEN is not set".into(),
};
let s = render_outcome(&out);
assert!(!s.contains('\n'), "rendered outcome must be single-line");
}
#[test]
fn resolve_transcript_path_honors_custom_fixtures_root() {
let mut args = args_for("qwen35", "q4_0");
args.fixtures_root = Some(PathBuf::from("/tmp/fx"));
let entry = ArchRegistry::global().get("qwen35").unwrap();
let p = resolve_transcript_path(&args, entry);
assert_eq!(
p,
PathBuf::from("/tmp/fx/smoke-transcripts/qwen35-q4_0.txt")
);
}
#[test]
fn normalize_quant_label_lowercases() {
assert_eq!(normalize_quant_label("Q4_0"), "q4_0");
assert_eq!(normalize_quant_label("DWQ-Mixed-4-6"), "dwq-mixed-4-6");
}
#[test]
fn sanitize_timestamps_strips_decimals_keeps_integers() {
let real_eval_line = "llama_perf_context_print: eval time = 58.90 ms / 8 runs ( 8.41 ms per token, 118.85 tokens per second)";
let s = sanitize_timestamps(real_eval_line);
assert!(!s.contains("58.90"), "got: {s}");
assert!(!s.contains("8.41"), "got: {s}");
assert!(!s.contains("118.85"), "got: {s}");
assert!(s.contains("<X.XX>"), "got: {s}");
assert!(s.contains("/ 8 runs"), "got: {s}");
assert!(s.contains("eval time"));
assert!(s.contains("ms per token"));
assert!(s.contains("tokens per second"));
}
#[test]
fn sanitize_timestamps_preserves_integer_only_tokens() {
let s = sanitize_timestamps("GGUF V3 (latest) and 737 tensors");
assert!(s.contains("V3"), "version preserved: {s}");
assert!(s.contains("737 tensors"), "tensor count preserved: {s}");
assert!(!s.contains("<X.XX>"), "no decimal seen: {s}");
}
#[test]
fn sanitize_timestamps_byte_identical_across_two_calls_with_different_decimals() {
let stderr_a = "eval time = 58.90 ms / 8 runs ( 8.41 ms per token, 118.85 tokens per second)\n";
let stderr_b = "eval time = 67.12 ms / 8 runs ( 9.55 ms per token, 119.20 tokens per second)\n";
assert_ne!(
stderr_a, stderr_b,
"raw inputs must differ for the test to be meaningful"
);
assert_eq!(
sanitize_timestamps(stderr_a),
sanitize_timestamps(stderr_b),
"sanitized transcripts must be byte-identical"
);
}
#[test]
fn build_convert_args_uses_convert_surface() {
let args = args_for("qwen35", "q4_0");
let entry = ArchRegistry::global().get("qwen35").unwrap();
let convert_args =
build_convert_args(&args, entry, Path::new("/in"), Path::new("/out.gguf")).unwrap();
assert_eq!(
convert_args.first().map(|s| s.as_str()),
Some("convert"),
"smoke harness must invoke `hf2q convert` (ADR-033 P6 + B4 rename); got {:?}",
convert_args
);
assert!(
convert_args.iter().any(|a| a == "--quant"),
"convert invocation must pass --quant; got {:?}",
convert_args
);
assert!(
convert_args.iter().any(|a| a == "-o"),
"convert invocation must pass -o; got {:?}",
convert_args
);
for legacy in [
"convert-v2",
"--emit-vision-tower",
"--yes",
"--skip-quality",
"--format",
"--input",
] {
assert!(
!convert_args.iter().any(|a| a == legacy),
"{legacy} is a legacy convert axis that must not appear post-P6/B4; got {:?}",
convert_args
);
}
}
#[test]
fn build_convert_args_with_vision_does_not_alter_argv() {
let mut args = args_for("qwen35", "q4_0");
args.with_vision = true;
let entry = ArchRegistry::global().get("qwen35").unwrap();
let convert_args =
build_convert_args(&args, entry, Path::new("/in"), Path::new("/out.gguf")).unwrap();
assert!(
!convert_args.iter().any(|a| a == "--emit-vision-tower"),
"convert has no --emit-vision-tower flag; got {:?}",
convert_args
);
}
#[test]
fn sanitize_timestamps_byte_identical_across_different_magnitudes() {
let stderr_a = "eval time = 58.90 ms / 8 runs ( 8.41 ms per token, 118.85 tokens per second)\n";
let stderr_b = "eval time = 158.90 ms / 8 runs ( 18.41 ms per token, 1118.85 tokens per second)\n";
let sa = sanitize_timestamps(stderr_a);
let sb = sanitize_timestamps(stderr_b);
assert_eq!(
sa, sb,
"magnitude-varying widths must collapse to identical sanitized output\nA: {sa}\nB: {sb}"
);
assert!(sa.contains("/ 8 runs"));
}
}