Skip to content

Commit 4442747

Browse files
authored
test: add unit tests (#45)
1 parent 378cb10 commit 4442747

File tree

7 files changed

+446
-20
lines changed

7 files changed

+446
-20
lines changed

.github/workflows/rust.yml

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,22 @@ name: Rust
22

33
on:
44
push:
5-
branches: [ "main" ]
5+
branches: ["main"]
66
pull_request:
7-
branches: [ "main" ]
7+
branches: ["main"]
88

99
env:
1010
CARGO_TERM_COLOR: always
1111

1212
jobs:
1313
build:
14-
1514
runs-on: ubuntu-latest
1615

1716
steps:
18-
- uses: actions/checkout@v3
19-
- name: Build
20-
run: cargo build --verbose
21-
- name: Run tests
22-
run: cargo test --verbose
17+
- uses: actions/checkout@v3
18+
- name: Build
19+
run: cargo build --verbose
20+
- name: Run tests
21+
run: cargo test --verbose
22+
- name: Format check
23+
run: cargo fmt --check

examples/convert_by_args.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ use image::*;
22
use std::{env::args, fmt::format, path::Path};
33
use webp::*;
44

5-
65
/// cargo run --example convert_by_args lake.jpg.
76
fn main() {
8-
97
//Add a get args functions
108
let arg: Vec<String> = args().collect();
119
if arg.len() != 2 {
@@ -17,7 +15,6 @@ fn main() {
1715
let path = format(format_args!("assets/{}", arg[1]));
1816
let path = Path::new(&path);
1917

20-
2118
// Using `image` crate, open the included .jpg file
2219
let img = image::open(path).unwrap();
2320
let (w, h) = img.dimensions();
@@ -62,4 +59,4 @@ fn test_convert() {
6259
// Define and write the WebP-encoded file to a given path
6360
let output_path = Path::new("assets").join("lake").with_extension("webp");
6461
std::fs::write(&output_path, &*webp).unwrap();
65-
}
62+
}

src/animation_decoder.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,90 @@ impl<'a> IntoIterator for &'a DecodeAnimImage {
152152
}
153153
}
154154
}
155+
156+
#[cfg(test)]
157+
mod tests {
158+
use super::*;
159+
160+
fn minimal_webp_animation() -> Vec<u8> {
161+
vec![
162+
0x52, 0x49, 0x46, 0x46, 0x84, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50,
163+
0x38, 0x58, 0x0a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
164+
0x00, 0x00, 0x41, 0x4e, 0x49, 0x4d, 0x06, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
165+
0x00, 0x00, 0x41, 0x4e, 0x4d, 0x46, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
166+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x02, 0x56, 0x50,
167+
0x38, 0x4c, 0x0f, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x07, 0x10, 0xfd,
168+
0x8f, 0xfe, 0x07, 0x22, 0xa2, 0xff, 0x01, 0x00, 0x41, 0x4e, 0x4d, 0x46, 0x28, 0x00,
169+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
170+
0x64, 0x00, 0x00, 0x00, 0x56, 0x50, 0x38, 0x4c, 0x0f, 0x00, 0x00, 0x00, 0x2f, 0x00,
171+
0x00, 0x00, 0x00, 0x07, 0x10, 0xd1, 0xff, 0xfe, 0x07, 0x22, 0xa2, 0xff, 0x01, 0x00,
172+
]
173+
}
174+
175+
#[test]
176+
fn test_decoder_creation() {
177+
let data = minimal_webp_animation();
178+
let decoder = AnimDecoder::new(&data);
179+
assert_eq!(decoder.data, &data[..]);
180+
}
181+
182+
#[test]
183+
fn test_decode_success_and_metadata() {
184+
let data = minimal_webp_animation();
185+
let decoder = AnimDecoder::new(&data);
186+
let result = decoder.decode();
187+
assert!(result.is_ok(), "Decoding should succeed for valid data");
188+
let anim = result.unwrap();
189+
assert!(anim.len() > 0, "Animation should have at least one frame");
190+
let _ = anim.loop_count;
191+
let _ = anim.bg_color;
192+
}
193+
194+
#[test]
195+
fn test_get_frame_and_get_frames() {
196+
let data = minimal_webp_animation();
197+
let decoder = AnimDecoder::new(&data);
198+
let anim = decoder.decode().unwrap();
199+
let frame = anim.get_frame(0);
200+
assert!(frame.is_some(), "Should retrieve first frame");
201+
let frames = anim.get_frames(0..1);
202+
assert!(frames.is_some(), "Should retrieve frame range");
203+
assert_eq!(frames.unwrap().len(), 1);
204+
}
205+
206+
#[test]
207+
fn test_has_animation_and_len() {
208+
let data = minimal_webp_animation();
209+
let decoder = AnimDecoder::new(&data);
210+
let anim = decoder.decode().unwrap();
211+
assert_eq!(anim.has_animation(), anim.len() > 1);
212+
}
213+
214+
#[test]
215+
fn test_sort_by_time_stamp() {
216+
let data = minimal_webp_animation();
217+
let decoder = AnimDecoder::new(&data);
218+
let mut anim = decoder.decode().unwrap();
219+
anim.frames.reverse();
220+
anim.sort_by_time_stamp();
221+
let timestamps: Vec<_> = anim.frames.iter().map(|f| f.timestamp).collect();
222+
assert!(timestamps.windows(2).all(|w| w[0] <= w[1]));
223+
}
224+
225+
#[test]
226+
fn test_iteration() {
227+
let data = minimal_webp_animation();
228+
let decoder = AnimDecoder::new(&data);
229+
let anim = decoder.decode().unwrap();
230+
let count = anim.into_iter().count();
231+
assert_eq!(count, anim.len());
232+
}
233+
234+
#[test]
235+
fn test_decode_failure_on_invalid_data() {
236+
let data = vec![0u8; 10];
237+
let decoder = AnimDecoder::new(&data);
238+
let result = decoder.decode();
239+
assert!(result.is_err(), "Decoding should fail for invalid data");
240+
}
241+
}

src/animation_encoder.rs

Lines changed: 91 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::ffi::CString;
2-
31
#[cfg(feature = "img")]
42
use image::DynamicImage;
53
use libwebp_sys::*;
@@ -36,7 +34,7 @@ impl<'a> AnimFrame<'a> {
3634
}
3735
}
3836
#[cfg(feature = "img")]
39-
pub fn from_image(image: &'a DynamicImage, timestamp: i32) -> Result<Self, &str> {
37+
pub fn from_image(image: &'a DynamicImage, timestamp: i32) -> Result<Self, &'a str> {
4038
match image {
4139
DynamicImage::ImageLuma8(_) => Err("Unimplemented"),
4240
DynamicImage::ImageLumaA8(_) => Err("Unimplemented"),
@@ -182,10 +180,14 @@ unsafe fn anim_encode(all_frame: &AnimEncoder) -> Result<WebPMemory, AnimEncodeE
182180
let mut webp_data = std::mem::MaybeUninit::<WebPData>::uninit();
183181
let ok = WebPAnimEncoderAssemble(encoder, webp_data.as_mut_ptr());
184182
if ok == 0 {
185-
//ok == false
186-
let cstring = WebPAnimEncoderGetError(encoder);
187-
let cstring = CString::from_raw(cstring as *mut _);
188-
let string = cstring.to_string_lossy().to_string();
183+
let err_ptr = WebPAnimEncoderGetError(encoder);
184+
let string = if !err_ptr.is_null() {
185+
unsafe { std::ffi::CStr::from_ptr(err_ptr) }
186+
.to_string_lossy()
187+
.into_owned()
188+
} else {
189+
String::from("Unknown error")
190+
};
189191
WebPAnimEncoderDelete(encoder);
190192
return Err(AnimEncodeError::WebPAnimEncoderGetError(string));
191193
}
@@ -203,3 +205,85 @@ unsafe fn anim_encode(all_frame: &AnimEncoder) -> Result<WebPMemory, AnimEncodeE
203205
let raw_data: WebPData = webp_data.assume_init();
204206
Ok(WebPMemory(raw_data.bytes as *mut u8, raw_data.size))
205207
}
208+
209+
#[cfg(test)]
210+
mod tests {
211+
use super::*;
212+
use crate::shared::PixelLayout;
213+
use crate::AnimDecoder;
214+
215+
fn default_config() -> WebPConfig {
216+
let mut config = unsafe { std::mem::zeroed() };
217+
let ok = unsafe {
218+
WebPConfigInitInternal(
219+
&mut config,
220+
WebPPreset::WEBP_PRESET_DEFAULT,
221+
75.0,
222+
WEBP_ENCODER_ABI_VERSION as i32,
223+
)
224+
};
225+
assert_ne!(ok, 0, "WebPConfigInitInternal failed");
226+
config
227+
}
228+
229+
#[test]
230+
fn test_animframe_new_and_accessors() {
231+
let img = [255u8, 0, 0, 255, 0, 255, 0, 255];
232+
let frame = AnimFrame::new(&img, PixelLayout::Rgba, 2, 1, 42, None);
233+
assert_eq!(frame.get_image(), &img);
234+
assert_eq!(frame.get_layout(), PixelLayout::Rgba);
235+
assert_eq!(frame.width(), 2);
236+
assert_eq!(frame.height(), 1);
237+
assert_eq!(frame.get_time_ms(), 42);
238+
}
239+
240+
#[test]
241+
fn test_animframe_from_rgb_and_rgba() {
242+
let rgb = [1u8, 2, 3, 4, 5, 6];
243+
let rgba = [1u8, 2, 3, 4, 5, 6, 7, 8];
244+
let f_rgb = AnimFrame::from_rgb(&rgb, 2, 1, 100);
245+
let f_rgba = AnimFrame::from_rgba(&rgba, 2, 1, 200);
246+
assert_eq!(f_rgb.get_layout(), PixelLayout::Rgb);
247+
assert_eq!(f_rgba.get_layout(), PixelLayout::Rgba);
248+
assert_eq!(f_rgb.get_time_ms(), 100);
249+
assert_eq!(f_rgba.get_time_ms(), 200);
250+
}
251+
252+
#[test]
253+
fn test_animencoder_add_and_configure() {
254+
let config = default_config();
255+
let mut encoder = AnimEncoder::new(2, 1, &config);
256+
encoder.set_bgcolor([1, 2, 3, 4]);
257+
encoder.set_loop_count(3);
258+
259+
let frame = AnimFrame::from_rgb(&[1, 2, 3, 4, 5, 6], 2, 1, 0);
260+
encoder.add_frame(frame);
261+
262+
assert_eq!(encoder.frames.len(), 1);
263+
assert_eq!(encoder.width, 2);
264+
assert_eq!(encoder.height, 1);
265+
assert_eq!(encoder.muxparams.loop_count, 3);
266+
267+
let expected_bg = (4u32 << 24) | (3u32 << 16) | (2u32 << 8) | 1u32;
268+
assert_eq!(encoder.muxparams.bgcolor, expected_bg);
269+
}
270+
271+
#[test]
272+
fn test_animencoder_encode_error_on_empty() {
273+
let config = default_config();
274+
let encoder = AnimEncoder::new(2, 1, &config);
275+
let result = encoder.try_encode();
276+
assert!(
277+
result.is_err(),
278+
"Encoding with no frames should fail or error"
279+
);
280+
}
281+
282+
#[test]
283+
fn test_animdecoder_decode_failure_on_invalid_data() {
284+
let data = vec![0u8; 10];
285+
let decoder = AnimDecoder::new(&data);
286+
let result = decoder.decode();
287+
assert!(result.is_err(), "Decoding should fail for invalid data");
288+
}
289+
}

src/decoder.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,109 @@ pub enum BitstreamFormat {
144144
Lossy = 1,
145145
Lossless = 2,
146146
}
147+
148+
#[cfg(test)]
149+
mod tests {
150+
use super::*;
151+
152+
fn minimal_webp_rgb() -> Vec<u8> {
153+
vec![
154+
0x52, 0x49, 0x46, 0x46, 0x24, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50,
155+
0x38, 0x20, 0x18, 0x00, 0x00, 0x00, 0x30, 0x01, 0x00, 0x9d, 0x01, 0x2a, 0x01, 0x00,
156+
0x01, 0x00, 0x02, 0x00, 0x34, 0x25, 0xa4, 0x00, 0x03, 0x70, 0x00, 0xfe, 0xfb, 0x94,
157+
0x00, 0x00,
158+
]
159+
}
160+
161+
#[test]
162+
fn test_bitstream_features_basic() {
163+
let data = minimal_webp_rgb();
164+
let features = BitstreamFeatures::new(&data).expect("Should parse features");
165+
assert_eq!(features.width(), 1);
166+
assert_eq!(features.height(), 1);
167+
assert!(!features.has_alpha());
168+
assert!(!features.has_animation());
169+
assert!(matches!(
170+
features.format(),
171+
Some(BitstreamFormat::Lossy)
172+
| Some(BitstreamFormat::Lossless)
173+
| Some(BitstreamFormat::Undefined)
174+
));
175+
}
176+
177+
#[test]
178+
fn test_decoder_decode_success() {
179+
let mut data = minimal_webp_rgb();
180+
data.extend_from_slice(&[0u8; 32]); // Add padding
181+
let decoder = Decoder::new(&data);
182+
let image = decoder.decode();
183+
assert!(image.is_some(), "Should decode minimal WebP");
184+
let image = image.unwrap();
185+
assert_eq!(image.width(), 1);
186+
assert_eq!(image.height(), 1);
187+
assert_eq!(image.layout(), PixelLayout::Rgb);
188+
}
189+
190+
#[test]
191+
fn test_decoder_rejects_animation() {
192+
let data = minimal_webp_rgb();
193+
let decoder = Decoder::new(&data);
194+
let image = decoder.decode();
195+
assert!(image.is_some());
196+
}
197+
198+
#[test]
199+
fn test_bitstream_features_invalid_data() {
200+
let data = vec![0u8; 8];
201+
let features = BitstreamFeatures::new(&data);
202+
assert!(features.is_none(), "Should not parse invalid WebP");
203+
}
204+
205+
#[test]
206+
fn test_decoder_invalid_data() {
207+
let data = vec![0u8; 8];
208+
let decoder = Decoder::new(&data);
209+
assert!(decoder.decode().is_none(), "Should not decode invalid WebP");
210+
}
211+
212+
#[test]
213+
fn test_bitstreamfeatures_debug_output() {
214+
fn make_features(
215+
width: i32,
216+
height: i32,
217+
has_alpha: i32,
218+
has_animation: i32,
219+
format: i32,
220+
) -> BitstreamFeatures {
221+
BitstreamFeatures(WebPBitstreamFeatures {
222+
width,
223+
height,
224+
has_alpha,
225+
has_animation,
226+
format,
227+
pad: [0; 5],
228+
})
229+
}
230+
231+
let cases = [
232+
(make_features(1, 2, 1, 0, 1), "format: \"Lossy\""),
233+
(make_features(3, 4, 0, 1, 2), "format: \"Lossless\""),
234+
(make_features(5, 6, 0, 0, 0), "format: \"Undefined\""),
235+
(make_features(7, 8, 1, 1, 42), "format: \"Error\""),
236+
];
237+
238+
for (features, format_str) in &cases {
239+
let dbg = format!("{features:?}");
240+
assert!(dbg.contains("BitstreamFeatures"));
241+
assert!(dbg.contains(&format!("width: {}", features.width())));
242+
assert!(dbg.contains(&format!("height: {}", features.height())));
243+
assert!(dbg.contains(&format!("has_alpha: {}", features.has_alpha())));
244+
assert!(dbg.contains(&format!("has_animation: {}", features.has_animation())));
245+
assert!(
246+
dbg.contains(format_str),
247+
"Debug output missing expected format string: {}",
248+
format_str
249+
);
250+
}
251+
}
252+
}

0 commit comments

Comments
 (0)