use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
fn pxl_binary() -> PathBuf {
let release = Path::new("target/release/pxl");
if release.exists() {
return release.to_path_buf();
}
let debug = Path::new("target/debug/pxl");
if debug.exists() {
return debug.to_path_buf();
}
panic!("pxl binary not found. Run 'cargo build' first.");
}
fn get_jsonl_files(dir: &Path) -> Vec<PathBuf> {
let mut files = Vec::new();
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().is_some_and(|e| e == "jsonl") {
files.push(path);
}
}
}
files.sort();
files
}
fn run_pxl_render(fixture: &Path, strict: bool) -> std::process::Output {
let output_dir = std::env::temp_dir().join("pxl_integration_tests");
fs::create_dir_all(&output_dir).ok();
let output_path = output_dir.join("test_output.png");
let mut cmd = Command::new(pxl_binary());
cmd.arg("render").arg(fixture).arg("-o").arg(&output_path);
if strict {
cmd.arg("--strict");
}
cmd.output().expect("Failed to execute pxl")
}
#[test]
fn test_valid_fixtures_render() {
let fixtures_dir = Path::new("tests/fixtures/valid");
let files = get_jsonl_files(fixtures_dir);
assert!(!files.is_empty(), "No valid fixtures found");
for fixture in &files {
let output = run_pxl_render(fixture, false);
assert!(
output.status.success(),
"Expected success for {:?}, got exit code {:?}\nstderr: {}",
fixture,
output.status.code(),
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(!stderr.contains("Warning:"), "Unexpected warnings for {fixture:?}: {stderr}");
}
println!("✓ All {} valid fixtures rendered successfully", files.len());
}
#[test]
fn test_invalid_fixtures_error() {
let fixtures_dir = Path::new("tests/fixtures/invalid");
let files = get_jsonl_files(fixtures_dir);
assert!(!files.is_empty(), "No invalid fixtures found");
for fixture in &files {
let output = run_pxl_render(fixture, true);
assert!(
!output.status.success(),
"Expected error for {:?} in strict mode, got success\nstdout: {}\nstderr: {}",
fixture,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("Error:"), "Expected error message for {fixture:?}: {stderr}");
}
println!("✓ All {} invalid fixtures produced errors", files.len());
}
#[test]
fn test_lenient_fixtures_warn() {
let fixtures_dir = Path::new("tests/fixtures/lenient");
let files = get_jsonl_files(fixtures_dir);
assert!(!files.is_empty(), "No lenient fixtures found");
let mut files_with_warnings = 0;
for fixture in &files {
let output = run_pxl_render(fixture, false);
assert!(
output.status.success(),
"Expected success for {:?} in lenient mode, got exit code {:?}\nstderr: {}",
fixture,
output.status.code(),
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
if stderr.contains("Warning:") {
files_with_warnings += 1;
}
}
assert!(
files_with_warnings > 0,
"Expected at least some lenient fixtures to produce warnings, but none did"
);
println!(
"✓ All {} lenient fixtures succeeded ({} with warnings)",
files.len(),
files_with_warnings
);
}
#[test]
fn test_strict_mode_fails_on_warnings() {
let fixtures_dir = Path::new("tests/fixtures/lenient");
let files = get_jsonl_files(fixtures_dir);
assert!(!files.is_empty(), "No lenient fixtures found");
let mut failed_count = 0;
for fixture in &files {
let lenient_output = run_pxl_render(fixture, false);
let lenient_stderr = String::from_utf8_lossy(&lenient_output.stderr);
let produces_warnings = lenient_stderr.contains("Warning:");
let strict_output = run_pxl_render(fixture, true);
if produces_warnings {
assert!(
!strict_output.status.success(),
"Expected failure for {:?} in strict mode (has warnings), got success\nstdout: {}\nstderr: {}",
fixture,
String::from_utf8_lossy(&strict_output.stdout),
String::from_utf8_lossy(&strict_output.stderr)
);
failed_count += 1;
}
}
assert!(failed_count > 0, "Expected at least some lenient fixtures to fail in strict mode");
println!("✓ {failed_count} lenient fixtures (with warnings) failed in strict mode as expected");
}
#[test]
fn test_cli_help() {
let output = Command::new(pxl_binary()).arg("--help").output().expect("Failed to execute pxl");
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("render"));
assert!(stdout.contains("Pixelsrc"));
}
#[test]
fn test_cli_version() {
let output =
Command::new(pxl_binary()).arg("--version").output().expect("Failed to execute pxl");
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("pxl"));
}
#[test]
fn test_missing_input_file() {
let output = Command::new(pxl_binary())
.arg("render")
.arg("nonexistent_file.jsonl")
.output()
.expect("Failed to execute pxl");
assert!(!output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("Cannot open") || stderr.contains("Error"),
"Expected file not found error, got: {stderr}"
);
}
#[test]
fn test_output_file_naming() {
let output_dir = std::env::temp_dir().join("pxl_naming_test");
fs::create_dir_all(&output_dir).ok();
for entry in fs::read_dir(&output_dir).into_iter().flatten().flatten() {
fs::remove_file(entry.path()).ok();
}
let explicit_output = output_dir.join("explicit.png");
let output = Command::new(pxl_binary())
.arg("render")
.arg("tests/fixtures/valid/minimal_dot.jsonl")
.arg("-o")
.arg(&explicit_output)
.output()
.expect("Failed to execute pxl");
assert!(output.status.success());
assert!(explicit_output.exists(), "Output file not created at {explicit_output:?}");
}
#[test]
fn test_include_palette() {
let output_dir = std::env::temp_dir().join("pxl_include_test");
fs::create_dir_all(&output_dir).ok();
let output_path = output_dir.join("include_test.png");
let output = Command::new(pxl_binary())
.arg("render")
.arg("tests/fixtures/valid/include_palette.jsonl")
.arg("-o")
.arg(&output_path)
.output()
.expect("Failed to execute pxl");
assert!(
output.status.success(),
"Failed to render with @include: {}",
String::from_utf8_lossy(&output.stderr)
);
assert!(output_path.exists(), "Output file not created at {output_path:?}");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(!stderr.contains("Warning:"), "Unexpected warnings: {stderr}");
}
#[test]
fn test_sprite_filter() {
let output_dir = std::env::temp_dir().join("pxl_filter_test");
fs::create_dir_all(&output_dir).ok();
let output_path = output_dir.join("filtered.png");
let output = Command::new(pxl_binary())
.arg("render")
.arg("tests/fixtures/valid/multiple_sprites.jsonl")
.arg("-o")
.arg(&output_path)
.arg("--sprite")
.arg("green_dot")
.output()
.expect("Failed to execute pxl");
assert!(
output.status.success(),
"Failed to filter sprite: {}",
String::from_utf8_lossy(&output.stderr)
);
let output = Command::new(pxl_binary())
.arg("render")
.arg("tests/fixtures/valid/multiple_sprites.jsonl")
.arg("-o")
.arg(&output_path)
.arg("--sprite")
.arg("nonexistent_sprite")
.output()
.expect("Failed to execute pxl");
assert!(!output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("No sprite named"));
}