#![allow(clippy::unwrap_used)]
mod fixtures;
use std::path::PathBuf;
use ff_decode::{ImageDecoder, ImageDecoderBuilder};
fn png_path() -> PathBuf {
fixtures::assets_dir().join("img/hello-triangle.png")
}
fn jpeg_path() -> PathBuf {
fixtures::test_jpeg_path()
}
#[test]
fn crate_root_should_export_image_decoder_types() {
let _: fn(&str) -> ImageDecoderBuilder = |p| ImageDecoder::open(p);
}
#[test]
fn open_missing_file_should_return_file_not_found_error() {
let path = PathBuf::from("/nonexistent/path/does_not_exist.png");
let result = ImageDecoder::open(&path).build();
assert!(
matches!(result, Err(ff_decode::DecodeError::FileNotFound { .. })),
"expected FileNotFound"
);
}
#[test]
fn open_audio_only_file_should_return_error() {
let path = fixtures::test_audio_path();
if !path.exists() {
println!("Skipping: audio asset not found");
return;
}
let result = ImageDecoder::open(&path).build();
assert!(
result.is_err(),
"opening an audio-only file should fail, got Ok"
);
}
#[test]
fn open_png_should_succeed() {
match ImageDecoder::open(png_path()).build() {
Ok(_) => {}
Err(e) => {
println!("Skipping: {e}");
}
}
}
#[test]
fn open_png_should_report_positive_width() {
let decoder = match ImageDecoder::open(png_path()).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
assert!(decoder.width() > 0, "width must be positive after open");
}
#[test]
fn open_png_should_report_positive_height() {
let decoder = match ImageDecoder::open(png_path()).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
assert!(decoder.height() > 0, "height must be positive after open");
}
#[test]
fn open_returns_builder_then_build_opens_file() {
let builder = ImageDecoder::open(png_path());
match builder.build() {
Ok(decoder) => assert!(decoder.width() > 0),
Err(e) => println!("Skipping: {e}"),
}
}
#[test]
fn decode_png_should_return_video_frame() {
let decoder = match ImageDecoder::open(png_path()).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
match decoder.decode() {
Ok(_) => {}
Err(e) => println!("Skipping: {e}"),
}
}
#[test]
fn decode_png_frame_width_should_match_open_width() {
let decoder = match ImageDecoder::open(png_path()).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
let expected = decoder.width();
let frame = match decoder.decode() {
Ok(f) => f,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
assert_eq!(
frame.width(),
expected,
"frame width must match reported width"
);
}
#[test]
fn decode_png_frame_height_should_match_open_height() {
let decoder = match ImageDecoder::open(png_path()).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
let expected = decoder.height();
let frame = match decoder.decode() {
Ok(f) => f,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
assert_eq!(
frame.height(),
expected,
"frame height must match reported height"
);
}
#[test]
fn decode_png_frame_should_be_key_frame() {
let decoder = match ImageDecoder::open(png_path()).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
let frame = match decoder.decode() {
Ok(f) => f,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
assert!(
frame.is_key_frame(),
"decoded image must be marked as a key frame"
);
}
#[test]
fn decode_png_frame_should_have_at_least_one_plane() {
let decoder = match ImageDecoder::open(png_path()).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
let frame = match decoder.decode() {
Ok(f) => f,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
assert!(
frame.num_planes() > 0,
"decoded frame must have at least one plane"
);
}
#[test]
fn decode_png_frame_planes_should_be_non_empty() {
let decoder = match ImageDecoder::open(png_path()).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
let frame = match decoder.decode() {
Ok(f) => f,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
for (i, plane) in frame.planes().iter().enumerate() {
assert!(!plane.is_empty(), "plane {i} must contain pixel data");
}
}
#[test]
fn decode_png_frame_total_size_should_be_positive() {
let decoder = match ImageDecoder::open(png_path()).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
let frame = match decoder.decode() {
Ok(f) => f,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
assert!(
frame.total_size() > 0,
"decoded frame must have a positive total byte size"
);
}
#[test]
fn decode_png_frame_pixel_format_should_be_supported() {
use ff_format::PixelFormat;
let supported = [
PixelFormat::Yuv420p,
PixelFormat::Yuv422p,
PixelFormat::Yuv444p,
PixelFormat::Rgb24,
PixelFormat::Bgr24,
PixelFormat::Rgba,
PixelFormat::Bgra,
PixelFormat::Gray8,
];
let decoder = match ImageDecoder::open(png_path()).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
let frame = match decoder.decode() {
Ok(f) => f,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
assert!(
supported.contains(&frame.format()),
"pixel format {:?} must be one of the supported formats",
frame.format()
);
}
#[test]
fn decode_one_first_call_should_return_some_frame() {
let mut decoder = match ImageDecoder::open(png_path()).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
let result = decoder.decode_one();
assert!(
matches!(result, Ok(Some(_))),
"first decode_one must return Ok(Some(frame)), got {result:?}"
);
}
#[test]
fn decode_one_second_call_should_return_none() {
let mut decoder = match ImageDecoder::open(png_path()).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
let _ = decoder.decode_one();
let result = decoder.decode_one();
assert!(
matches!(result, Ok(None)),
"second decode_one must return Ok(None), got {result:?}"
);
}
#[test]
fn decode_one_frame_dimensions_should_match_reported_dimensions() {
let mut decoder = match ImageDecoder::open(png_path()).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
let w = decoder.width();
let h = decoder.height();
let frame = match decoder.decode_one() {
Ok(Some(f)) => f,
Ok(None) => {
println!("Skipping: no frame returned");
return;
}
Err(e) => {
println!("Skipping: {e}");
return;
}
};
assert_eq!(frame.width(), w);
assert_eq!(frame.height(), h);
}
#[test]
fn frames_iterator_should_yield_exactly_one_frame() {
let mut decoder = match ImageDecoder::open(png_path()).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
let frames: Vec<_> = decoder.frames().collect();
assert_eq!(
frames.len(),
1,
"frames() must yield exactly one frame for a still image"
);
assert!(frames[0].is_ok(), "the single frame must be Ok");
}
#[test]
fn frames_iterator_second_call_should_yield_nothing() {
let mut decoder = match ImageDecoder::open(png_path()).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
let _ = decoder.frames().collect::<Vec<_>>();
let second: Vec<_> = decoder.frames().collect();
assert!(
second.is_empty(),
"frames() after image already decoded must yield nothing"
);
}
#[test]
fn frames_iterator_frame_should_be_key_frame() {
let mut decoder = match ImageDecoder::open(png_path()).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
for result in decoder.frames() {
let frame = match result {
Ok(f) => f,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
assert!(
frame.is_key_frame(),
"frame from iterator must be a key frame"
);
}
}
#[test]
fn frames_iterator_frame_should_have_correct_dimensions() {
let mut decoder = match ImageDecoder::open(png_path()).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
let w = decoder.width();
let h = decoder.height();
for result in decoder.frames() {
let frame = match result {
Ok(f) => f,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
assert_eq!(frame.width(), w, "iterator frame width must match");
assert_eq!(frame.height(), h, "iterator frame height must match");
}
}
#[test]
fn decoder_drop_after_decode_should_not_panic() {
let mut decoder = match ImageDecoder::open(png_path()).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
let _ = decoder.decode_one();
}
#[test]
fn decoder_drop_without_decode_should_not_panic() {
match ImageDecoder::open(png_path()).build() {
Ok(_decoder) => {} Err(e) => println!("Skipping: {e}"),
}
}
#[test]
fn open_jpeg_should_succeed() {
let path = jpeg_path();
if !path.exists() {
println!("Skipping: JPEG asset not found");
return;
}
match ImageDecoder::open(&path).build() {
Ok(_) => {}
Err(e) => println!("Skipping: {e}"),
}
}
#[test]
fn decode_jpeg_frame_should_have_positive_width_and_height() {
let path = jpeg_path();
if !path.exists() {
println!("Skipping: JPEG asset not found");
return;
}
let decoder = match ImageDecoder::open(&path).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
assert!(decoder.width() > 0, "JPEG width must be positive");
assert!(decoder.height() > 0, "JPEG height must be positive");
}
#[test]
fn decode_jpeg_frame_dimensions_should_match_reported_dimensions() {
let path = jpeg_path();
if !path.exists() {
println!("Skipping: JPEG asset not found");
return;
}
let decoder = match ImageDecoder::open(&path).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
let expected_w = decoder.width();
let expected_h = decoder.height();
let frame = match decoder.decode() {
Ok(f) => f,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
assert_eq!(
frame.width(),
expected_w,
"decoded JPEG frame width must match reported width"
);
assert_eq!(
frame.height(),
expected_h,
"decoded JPEG frame height must match reported height"
);
}
#[test]
fn decode_jpeg_frame_pixel_format_should_be_supported() {
use ff_format::PixelFormat;
let supported = [
PixelFormat::Yuv420p,
PixelFormat::Yuv422p,
PixelFormat::Yuv444p,
PixelFormat::Rgb24,
PixelFormat::Bgr24,
PixelFormat::Rgba,
PixelFormat::Bgra,
PixelFormat::Gray8,
];
let path = jpeg_path();
if !path.exists() {
println!("Skipping: JPEG asset not found");
return;
}
let decoder = match ImageDecoder::open(&path).build() {
Ok(d) => d,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
let frame = match decoder.decode() {
Ok(f) => f,
Err(e) => {
println!("Skipping: {e}");
return;
}
};
assert!(
supported.contains(&frame.format()),
"JPEG pixel format {:?} must be one of the supported formats",
frame.format()
);
}