#![cfg(feature = "test-utils")]
use std::env;
use std::fs;
use std::io::Write;
use std::process::Command;
use tempfile::TempDir;
use xchecker::test_support;
fn get_xchecker_bin() -> std::path::PathBuf {
let manifest_dir =
env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR should be set by cargo");
if let Ok(bin_path) = env::var("CARGO_BIN_EXE_xchecker") {
return std::path::PathBuf::from(bin_path);
}
let mut bin_path = std::path::PathBuf::from(manifest_dir);
bin_path.push("target");
bin_path.push("debug");
bin_path.push("xchecker");
if cfg!(windows) {
bin_path.set_extension("exe");
}
bin_path
}
fn run_xchecker(args: &[&str]) -> Command {
let manifest_dir =
env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR should be set by cargo");
let mut cmd = Command::new("cargo");
cmd.current_dir(&manifest_dir);
cmd.arg("run").arg("--bin").arg("xchecker").arg("--");
for arg in args {
cmd.arg(arg);
}
cmd
}
fn run_xchecker_in_dir(args: &[&str], work_dir: &std::path::Path) -> Command {
let bin_path = get_xchecker_bin();
let mut cmd = Command::new(&bin_path);
cmd.current_dir(work_dir);
for arg in args {
cmd.arg(arg);
}
cmd
}
fn real_llm_tests_enabled() -> bool {
if test_support::llm_tests_enabled() {
true
} else {
println!(
"Skipping real LLM smoke tests. Set XCHECKER_REAL_LLM_TESTS=1 to enable (and ensure XCHECKER_SKIP_LLM_TESTS is unset)."
);
false
}
}
#[test]
fn test_smoke_doctor_json() {
println!("🔥 Smoke test: xchecker doctor --json");
let output = run_xchecker(&["doctor", "--json"])
.output()
.expect("Failed to execute xchecker doctor --json");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
println!("Exit code: {:?}", output.status.code());
println!("Stdout: {}", stdout);
if !stderr.is_empty() {
println!("Stderr: {}", stderr);
}
let json_result: Result<serde_json::Value, _> = serde_json::from_str(&stdout);
assert!(
json_result.is_ok(),
"Doctor output should be valid JSON: {}",
stdout
);
let json = json_result.unwrap();
assert!(
json.get("checks").is_some(),
"Doctor JSON should have 'checks' field"
);
assert!(
json.get("ok").is_some(),
"Doctor JSON should have 'ok' field"
);
println!("✓ Doctor command produces valid JSON output");
}
#[test]
fn test_smoke_init_with_lock() {
println!("🔥 Smoke test: xchecker init demo --create-lock");
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let spec_id = "smoke-test-init";
let output = run_xchecker_in_dir(&["init", spec_id, "--create-lock"], temp_dir.path())
.output()
.expect("Failed to execute xchecker init");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
println!("Exit code: {:?}", output.status.code());
println!("Stdout: {}", stdout);
if !stderr.is_empty() {
println!("Stderr: {}", stderr);
}
assert!(
output.status.success(),
"Init command should succeed: {}",
stderr
);
let spec_dir = temp_dir.path().join(".xchecker/specs").join(spec_id);
assert!(
spec_dir.exists(),
"Spec directory should be created: {}",
spec_dir.display()
);
assert!(
spec_dir.join("artifacts").exists(),
"Artifacts directory should be created"
);
assert!(
spec_dir.join("receipts").exists(),
"Receipts directory should be created"
);
assert!(
spec_dir.join("context").exists(),
"Context directory should be created"
);
let lock_path = spec_dir.join("lock.json");
assert!(
lock_path.exists(),
"Lockfile should be created: {}",
lock_path.display()
);
let lock_content = fs::read_to_string(&lock_path).expect("Failed to read lockfile");
let lock_json: serde_json::Value =
serde_json::from_str(&lock_content).expect("Lockfile should be valid JSON");
assert!(
lock_json.get("model_full_name").is_some(),
"Lockfile should have model_full_name"
);
assert!(
lock_json.get("claude_cli_version").is_some(),
"Lockfile should have claude_cli_version"
);
assert!(
lock_json.get("schema_version").is_some(),
"Lockfile should have schema_version"
);
println!("✓ Init command creates directory structure and lockfile");
}
#[test]
fn test_smoke_spec_dry_run() {
println!("🔥 Smoke test: xchecker spec demo --dry-run");
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let spec_id = "smoke-test-spec";
let input_content = "Create a simple calculator application with basic arithmetic operations";
let mut child = run_xchecker_in_dir(&["spec", spec_id, "--dry-run"], temp_dir.path())
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.expect("Failed to spawn xchecker spec");
if let Some(stdin) = child.stdin.as_mut() {
stdin
.write_all(input_content.as_bytes())
.expect("Failed to write to stdin");
}
let output = child
.wait_with_output()
.expect("Failed to wait for xchecker spec");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
println!("Exit code: {:?}", output.status.code());
println!("Stdout: {}", stdout);
if !stderr.is_empty() {
println!("Stderr: {}", stderr);
}
assert!(
output.status.success(),
"Spec dry-run should succeed: {}",
stderr
);
assert!(
stdout.contains("dry-run") || stdout.contains("Requirements phase completed"),
"Output should indicate dry-run execution or completion"
);
println!("✓ Spec command runs successfully in dry-run mode");
}
#[test]
fn test_smoke_status_json() {
println!("🔥 Smoke test: xchecker status demo --json");
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let spec_id = "smoke-test-status";
let init_output = run_xchecker_in_dir(&["init", spec_id], temp_dir.path())
.output()
.expect("Failed to execute xchecker init");
assert!(
init_output.status.success(),
"Init should succeed before status check"
);
let output = run_xchecker_in_dir(&["status", spec_id, "--json"], temp_dir.path())
.output()
.expect("Failed to execute xchecker status");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
println!("Exit code: {:?}", output.status.code());
println!("Stdout: {}", stdout);
if !stderr.is_empty() {
println!("Stderr: {}", stderr);
}
assert!(
output.status.success(),
"Status command should succeed: {}",
stderr
);
let json_result: Result<serde_json::Value, _> = serde_json::from_str(&stdout);
assert!(
json_result.is_ok(),
"Status output should be valid JSON: {}",
stdout
);
let json = json_result.unwrap();
assert!(
json.get("schema_version").is_some(),
"Status JSON should have schema_version"
);
assert!(
json.get("spec_id").is_some(),
"Status JSON should have spec_id"
);
assert!(
json.get("phase_statuses").is_some(),
"Status JSON should have phase_statuses"
);
assert!(
json.get("pending_fixups").is_some(),
"Status JSON should have pending_fixups"
);
assert!(
json.get("has_errors").is_some(),
"Status JSON should have has_errors"
);
println!("✓ Status command produces valid JSON output");
}
#[test]
fn test_smoke_clean_hard() {
println!("🔥 Smoke test: xchecker clean demo --hard");
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let spec_id = "smoke-test-clean";
let init_output = run_xchecker_in_dir(&["init", spec_id], temp_dir.path())
.output()
.expect("Failed to execute xchecker init");
assert!(
init_output.status.success(),
"Init should succeed before clean"
);
let spec_dir = temp_dir.path().join(".xchecker/specs").join(spec_id);
assert!(
spec_dir.exists(),
"Spec directory should exist before clean"
);
let output = run_xchecker_in_dir(&["clean", spec_id, "--hard"], temp_dir.path())
.output()
.expect("Failed to execute xchecker clean");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
println!("Exit code: {:?}", output.status.code());
println!("Stdout: {}", stdout);
if !stderr.is_empty() {
println!("Stderr: {}", stderr);
}
assert!(
output.status.success(),
"Clean command should succeed: {}",
stderr
);
assert!(
!spec_dir.exists(),
"Spec directory should be removed after clean"
);
println!("✓ Clean command removes spec artifacts");
}
#[test]
#[ignore = "flaky in CI - environment-dependent timing"]
fn test_smoke_benchmark() {
println!("🔥 Smoke test: xchecker benchmark");
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let output = run_xchecker_in_dir(
&[
"benchmark",
"--file-count",
"10",
"--iterations",
"2",
"--json",
],
temp_dir.path(),
)
.output()
.expect("Failed to execute xchecker benchmark");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
println!("Exit code: {:?}", output.status.code());
println!("Stdout: {}", stdout);
if !stderr.is_empty() {
println!("Stderr: {}", stderr);
}
assert!(
output.status.success(),
"Benchmark command should succeed: {}",
stderr
);
let json_result: Result<serde_json::Value, _> = serde_json::from_str(&stdout);
assert!(
json_result.is_ok(),
"Benchmark output should be valid JSON: {}",
stdout
);
let json = json_result.unwrap();
assert!(
json.get("ok").is_some(),
"Benchmark JSON should have 'ok' field"
);
assert!(
json.get("timings_ms").is_some(),
"Benchmark JSON should have 'timings_ms' field"
);
assert!(
json.get("rss_mb").is_some(),
"Benchmark JSON should have 'rss_mb' field"
);
println!("✓ Benchmark command produces valid output");
}
#[test]
fn test_smoke_all_commands_exit_codes() {
println!("🔥 Smoke test: Verify all commands exit codes");
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let help_output = run_xchecker(&["--help"])
.output()
.expect("Failed to execute --help");
assert!(
help_output.status.success(),
"--help should exit with code 0"
);
let version_output = run_xchecker(&["--version"])
.output()
.expect("Failed to execute --version");
assert!(
version_output.status.success(),
"--version should exit with code 0"
);
let doctor_output = run_xchecker(&["doctor"])
.output()
.expect("Failed to execute doctor");
let doctor_code = doctor_output.status.code().unwrap_or(-1);
assert!(
doctor_code == 0 || doctor_code == 1,
"doctor should exit with code 0 or 1, got {}",
doctor_code
);
let init_output = run_xchecker_in_dir(&["init", "exit-code-test"], temp_dir.path())
.output()
.expect("Failed to execute init");
assert!(init_output.status.success(), "init should exit with code 0");
let status_output = run_xchecker_in_dir(&["status", "exit-code-test"], temp_dir.path())
.output()
.expect("Failed to execute status");
assert!(
status_output.status.success(),
"status should exit with code 0"
);
let clean_output = run_xchecker_in_dir(&["clean", "exit-code-test", "--hard"], temp_dir.path())
.output()
.expect("Failed to execute clean");
assert!(
clean_output.status.success(),
"clean should exit with code 0"
);
let benchmark_output = run_xchecker_in_dir(
&["benchmark", "--file-count", "5", "--iterations", "1"],
temp_dir.path(),
)
.output()
.expect("Failed to execute benchmark");
assert!(
benchmark_output.status.success(),
"benchmark should exit with code 0"
);
println!("✓ All commands produce correct exit codes");
}
#[test]
fn test_smoke_json_output_validity() {
println!("🔥 Smoke test: Verify JSON output validity");
let temp_dir = TempDir::new().expect("Failed to create temp dir");
run_xchecker_in_dir(&["init", "json-test"], temp_dir.path())
.output()
.expect("Failed to init spec");
let doctor_output = run_xchecker(&["doctor", "--json"])
.output()
.expect("Failed to execute doctor --json");
let doctor_json = String::from_utf8_lossy(&doctor_output.stdout);
assert!(
serde_json::from_str::<serde_json::Value>(&doctor_json).is_ok(),
"doctor --json should produce valid JSON"
);
let status_output = run_xchecker_in_dir(&["status", "json-test", "--json"], temp_dir.path())
.output()
.expect("Failed to execute status --json");
let status_json = String::from_utf8_lossy(&status_output.stdout);
assert!(
serde_json::from_str::<serde_json::Value>(&status_json).is_ok(),
"status --json should produce valid JSON"
);
let benchmark_output = run_xchecker_in_dir(
&[
"benchmark",
"--file-count",
"5",
"--iterations",
"1",
"--json",
],
temp_dir.path(),
)
.output()
.expect("Failed to execute benchmark --json");
let benchmark_json = String::from_utf8_lossy(&benchmark_output.stdout);
assert!(
serde_json::from_str::<serde_json::Value>(&benchmark_json).is_ok(),
"benchmark --json should produce valid JSON"
);
println!("✓ All JSON outputs are valid");
}
#[test]
#[ignore = "requires_real_claude"]
fn test_claude_cli_available() {
if !real_llm_tests_enabled() {
return;
}
if env::var("ANTHROPIC_API_KEY").is_err() {
println!("Skipping smoke test: ANTHROPIC_API_KEY not set");
return;
}
println!("Running smoke test with real Claude CLI...");
let output = std::process::Command::new("claude")
.arg("--version")
.output();
match output {
Ok(result) => {
if result.status.success() {
let version = String::from_utf8_lossy(&result.stdout);
println!("Claude CLI version: {}", version.trim());
println!("✓ Claude CLI is accessible");
} else {
let stderr = String::from_utf8_lossy(&result.stderr);
panic!(
"Claude CLI failed with exit code {:?}: {}",
result.status.code(),
stderr
);
}
}
Err(e) => {
panic!("Failed to execute claude command: {}", e);
}
}
}
#[test]
#[ignore = "requires_real_claude"]
fn test_xchecker_with_real_claude() {
if !real_llm_tests_enabled() {
return;
}
if env::var("ANTHROPIC_API_KEY").is_err() {
println!("Skipping smoke test: ANTHROPIC_API_KEY not set");
return;
}
println!("Smoke test: xchecker with real Claude CLI");
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let spec_id = "real-claude-test";
let input_content = "Create a simple hello world application";
let mut child = run_xchecker_in_dir(&["spec", spec_id], temp_dir.path())
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.expect("Failed to spawn xchecker spec");
if let Some(stdin) = child.stdin.as_mut() {
stdin
.write_all(input_content.as_bytes())
.expect("Failed to write to stdin");
}
let output = child
.wait_with_output()
.expect("Failed to wait for xchecker spec");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
println!("Exit code: {:?}", output.status.code());
println!("Stdout: {}", stdout);
if !stderr.is_empty() {
println!("Stderr: {}", stderr);
}
assert!(
output.status.success(),
"Spec execution with real Claude should succeed: {}",
stderr
);
let spec_dir = temp_dir.path().join(".xchecker/specs").join(spec_id);
let artifacts_dir = spec_dir.join("artifacts");
assert!(
artifacts_dir.exists(),
"Artifacts directory should exist after execution"
);
let receipts_dir = spec_dir.join("receipts");
assert!(
receipts_dir.exists(),
"Receipts directory should exist after execution"
);
println!("✓ xchecker works with real Claude CLI");
}