Files
pixstrip/pixstrip-core/tests/executor_tests.rs
lashman 8ced89a00f Add pipeline executor with progress reporting and cancellation
Sequential execution with per-image progress callbacks, cancellation
via atomic flag, batch result tracking (success/fail/sizes/timing).
Phase 5 complete - 85 tests passing, zero clippy warnings.
2026-03-06 02:08:11 +02:00

135 lines
4.3 KiB
Rust

use pixstrip_core::executor::PipelineExecutor;
use pixstrip_core::operations::*;
use pixstrip_core::pipeline::ProcessingJob;
use pixstrip_core::types::*;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Mutex};
fn create_test_jpeg(path: &std::path::Path) {
let img = image::RgbImage::from_fn(200, 150, |x, y| {
image::Rgb([(x % 256) as u8, (y % 256) as u8, 128])
});
img.save_with_format(path, image::ImageFormat::Jpeg).unwrap();
}
fn setup_test_dir() -> (tempfile::TempDir, std::path::PathBuf, std::path::PathBuf) {
let dir = tempfile::tempdir().unwrap();
let input = dir.path().join("input");
let output = dir.path().join("output");
std::fs::create_dir_all(&input).unwrap();
std::fs::create_dir_all(&output).unwrap();
(dir, input, output)
}
#[test]
fn execute_single_image_resize() {
let (_dir, input, output) = setup_test_dir();
create_test_jpeg(&input.join("photo.jpg"));
let mut job = ProcessingJob::new(&input, &output);
job.add_source(input.join("photo.jpg"));
job.resize = Some(ResizeConfig::ByWidth(100));
let executor = PipelineExecutor::new();
let result = executor.execute(&job, |_| {}).unwrap();
assert_eq!(result.succeeded, 1);
assert_eq!(result.failed, 0);
assert!(output.join("photo.jpg").exists());
}
#[test]
fn execute_resize_and_convert() {
let (_dir, input, output) = setup_test_dir();
create_test_jpeg(&input.join("photo.jpg"));
let mut job = ProcessingJob::new(&input, &output);
job.add_source(input.join("photo.jpg"));
job.resize = Some(ResizeConfig::ByWidth(100));
job.convert = Some(ConvertConfig::SingleFormat(ImageFormat::WebP));
let executor = PipelineExecutor::new();
let result = executor.execute(&job, |_| {}).unwrap();
assert_eq!(result.succeeded, 1);
assert!(output.join("photo.webp").exists());
}
#[test]
fn execute_multiple_images() {
let (_dir, input, output) = setup_test_dir();
create_test_jpeg(&input.join("a.jpg"));
create_test_jpeg(&input.join("b.jpg"));
create_test_jpeg(&input.join("c.jpg"));
let mut job = ProcessingJob::new(&input, &output);
job.add_source(input.join("a.jpg"));
job.add_source(input.join("b.jpg"));
job.add_source(input.join("c.jpg"));
job.resize = Some(ResizeConfig::ByWidth(50));
let executor = PipelineExecutor::new();
let result = executor.execute(&job, |_| {}).unwrap();
assert_eq!(result.succeeded, 3);
assert_eq!(result.failed, 0);
assert_eq!(result.total, 3);
}
#[test]
fn execute_collects_progress() {
let (_dir, input, output) = setup_test_dir();
create_test_jpeg(&input.join("photo.jpg"));
let mut job = ProcessingJob::new(&input, &output);
job.add_source(input.join("photo.jpg"));
job.resize = Some(ResizeConfig::ByWidth(100));
let updates = Arc::new(Mutex::new(Vec::new()));
let updates_clone = updates.clone();
let executor = PipelineExecutor::new();
executor.execute(&job, move |update| {
updates_clone.lock().unwrap().push(update);
}).unwrap();
let updates = updates.lock().unwrap();
assert!(!updates.is_empty());
}
#[test]
fn execute_with_cancellation() {
let (_dir, input, output) = setup_test_dir();
create_test_jpeg(&input.join("a.jpg"));
create_test_jpeg(&input.join("b.jpg"));
let mut job = ProcessingJob::new(&input, &output);
job.add_source(input.join("a.jpg"));
job.add_source(input.join("b.jpg"));
job.resize = Some(ResizeConfig::ByWidth(100));
let cancel = Arc::new(AtomicBool::new(true)); // cancel immediately
let executor = PipelineExecutor::with_cancel(cancel);
let result = executor.execute(&job, |_| {}).unwrap();
// With immediate cancellation, fewer images should be processed
assert!(result.succeeded + result.failed <= 2);
assert!(result.cancelled);
}
#[test]
fn batch_result_tracks_sizes() {
let (_dir, input, output) = setup_test_dir();
create_test_jpeg(&input.join("photo.jpg"));
let mut job = ProcessingJob::new(&input, &output);
job.add_source(input.join("photo.jpg"));
job.compress = Some(CompressConfig::Preset(QualityPreset::Low));
let executor = PipelineExecutor::new();
let result = executor.execute(&job, |_| {}).unwrap();
assert!(result.total_input_bytes > 0);
assert!(result.total_output_bytes > 0);
}