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); }