// Defensive coverage for the image-upload boundary. The GLES path requires a // live GL context (out of scope for these CPU-only tests) so the assertions // below exercise the software backend, which shares the same length-vs- // dimensions check before delegating to tiny-skia. Both paths: // // * refuse the upload when `data.len() != width * height * 4`, // * refuse zero-area uploads (`width == 0` or `height == 0`), // * never panic — they log to stderr and return without drawing. use std::sync::Arc; use ltk::core::{ RenderOptions, UiSurface }; use ltk::{ column, img_widget, Color, Element }; fn render_image( rgba: Arc>, w: u32, h: u32 ) -> UiSurface<()> { let mut surface = UiSurface::<()>::new( 64, 64 ); let view: Element<()> = column() .padding( 0.0 ) .push( img_widget( rgba, w, h ) ) .into(); let _ = surface.render( &view, RenderOptions::full_canvas( 64, 64 ).background( Color::BLACK ), ); surface } #[ test ] fn declared_size_matching_buffer_renders_without_panic() { // Sanity baseline: a 4×4 image with the expected 64-byte buffer must // render through the path that the validation tests below exercise. let bytes = Arc::new( vec![ 0xFFu8; 4 * 4 * 4 ] ); let _ = render_image( bytes, 4, 4 ); } #[ test ] fn shorter_buffer_is_rejected_without_panic() { // 10×10 declared but only 16 bytes provided. Without the boundary // check tiny-skia would walk past the slice end inside its premul // loop. With the check the renderer logs once and skips the draw. let bytes = Arc::new( vec![ 0u8; 16 ] ); let _ = render_image( bytes, 10, 10 ); } #[ test ] fn longer_buffer_is_rejected_without_panic() { // 2×2 declared, 256 bytes provided. The buffer is larger than the // driver would read, but the size mismatch still fails the strict // equality and the draw is refused. let bytes = Arc::new( vec![ 0u8; 256 ] ); let _ = render_image( bytes, 2, 2 ); } #[ test ] fn zero_width_image_does_not_blit() { // IntSize::from_wh rejects zero dimensions on tiny-skia, but the // boundary check fires earlier for a uniform error path on both // backends. let bytes = Arc::new( vec![] ); let _ = render_image( bytes, 0, 10 ); } #[ test ] fn zero_height_image_does_not_blit() { let bytes = Arc::new( vec![] ); let _ = render_image( bytes, 10, 0 ); } #[ test ] fn non_multiple_of_four_buffer_is_rejected() { // 1×1 should be 4 bytes; 3 bytes is shorter and not a multiple of 4 — // every byte counts. let bytes = Arc::new( vec![ 0xFF, 0xFF, 0xFF ] ); let _ = render_image( bytes, 1, 1 ); } #[ test ] fn dimensions_overflow_usize_are_rejected_without_panic() { // `u32::MAX × u32::MAX × 4` overflows. The boundary check uses // `saturating_mul` (or i64-promotion in the GLES path) so the // expected length never wraps to a small number that happens to // match a real buffer length. The provided buffer is small, so the // equality fails and the draw is refused. let bytes = Arc::new( vec![ 0u8; 16 ] ); let _ = render_image( bytes, u32::MAX, u32::MAX ); } #[ test ] fn renderer_recovers_after_a_rejected_upload() { // A frame with a malformed image must not poison the surface. The // next render with a valid view succeeds. let bad = Arc::new( vec![ 0u8; 5 ] ); let mut s = render_image( bad, 4, 4 ); let view: Element<()> = column().padding( 0.0 ).into(); let out = s.render( &view, RenderOptions::full_canvas( 64, 64 ).background( Color::rgb( 0.0, 1.0, 0.0 ) ), ); // Tree shape change → full redraw, but the important check is that // render() returned at all. let _ = out; }