1 use png; 2 use predicates::prelude::*; 3 use predicates::reflection::{Case, Child, PredicateReflection, Product}; 4 use std::fmt; 5 use std::io::BufReader; 6 use std::path::{Path, PathBuf}; 7 8 use librsvg::surface_utils::shared_surface::{SharedImageSurface, SurfaceType}; 9 10 use crate::compare_surfaces::BufferDiff; 11 use crate::reference_utils::{surface_from_png, Compare, Deviation, Reference}; 12 13 /// Checks that the variable of type [u8] can be parsed as a PNG file. 14 #[derive(Debug)] 15 pub struct PngPredicate {} 16 17 impl PngPredicate { with_size(self: Self, w: u32, h: u32) -> SizePredicate<Self>18 pub fn with_size(self: Self, w: u32, h: u32) -> SizePredicate<Self> { 19 SizePredicate::<Self> { p: self, w, h } 20 } 21 with_contents<P: AsRef<Path>>(self: Self, reference: P) -> ReferencePredicate<Self>22 pub fn with_contents<P: AsRef<Path>>(self: Self, reference: P) -> ReferencePredicate<Self> { 23 let mut path = PathBuf::new(); 24 path.push(reference); 25 ReferencePredicate::<Self> { p: self, path } 26 } 27 } 28 29 impl Predicate<[u8]> for PngPredicate { eval(&self, data: &[u8]) -> bool30 fn eval(&self, data: &[u8]) -> bool { 31 let decoder = png::Decoder::new(data); 32 decoder.read_info().is_ok() 33 } 34 find_case<'a>(&'a self, _expected: bool, data: &[u8]) -> Option<Case<'a>>35 fn find_case<'a>(&'a self, _expected: bool, data: &[u8]) -> Option<Case<'a>> { 36 let decoder = png::Decoder::new(data); 37 match decoder.read_info() { 38 Ok(_) => None, 39 Err(e) => Some(Case::new(Some(self), false).add_product(Product::new("Error", e))), 40 } 41 } 42 } 43 44 impl PredicateReflection for PngPredicate {} 45 46 impl fmt::Display for PngPredicate { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result47 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 48 write!(f, "is a PNG") 49 } 50 } 51 52 /// Extends a PngPredicate by a check for a given size of the PNG file. 53 #[derive(Debug)] 54 pub struct SizePredicate<PngPredicate> { 55 p: PngPredicate, 56 w: u32, 57 h: u32, 58 } 59 60 impl SizePredicate<PngPredicate> { eval_info(&self, info: &png::Info) -> bool61 fn eval_info(&self, info: &png::Info) -> bool { 62 info.width == self.w && info.height == self.h 63 } 64 find_case_for_info<'a>(&'a self, expected: bool, info: &png::Info) -> Option<Case<'a>>65 fn find_case_for_info<'a>(&'a self, expected: bool, info: &png::Info) -> Option<Case<'a>> { 66 if self.eval_info(info) == expected { 67 let product = self.product_for_info(info); 68 Some(Case::new(Some(self), false).add_product(product)) 69 } else { 70 None 71 } 72 } 73 product_for_info(&self, info: &png::Info) -> Product74 fn product_for_info(&self, info: &png::Info) -> Product { 75 let actual_size = format!("{} x {}", info.width, info.height); 76 Product::new("actual size", actual_size) 77 } 78 } 79 80 impl Predicate<[u8]> for SizePredicate<PngPredicate> { eval(&self, data: &[u8]) -> bool81 fn eval(&self, data: &[u8]) -> bool { 82 let decoder = png::Decoder::new(data); 83 match decoder.read_info() { 84 Ok(reader) => self.eval_info(&reader.info()), 85 _ => false, 86 } 87 } 88 find_case<'a>(&'a self, expected: bool, data: &[u8]) -> Option<Case<'a>>89 fn find_case<'a>(&'a self, expected: bool, data: &[u8]) -> Option<Case<'a>> { 90 let decoder = png::Decoder::new(data); 91 match decoder.read_info() { 92 Ok(reader) => self.find_case_for_info(expected, reader.info()), 93 Err(e) => Some(Case::new(Some(self), false).add_product(Product::new("Error", e))), 94 } 95 } 96 } 97 98 impl PredicateReflection for SizePredicate<PngPredicate> { children<'a>(&'a self) -> Box<dyn Iterator<Item = Child<'a>> + 'a>99 fn children<'a>(&'a self) -> Box<dyn Iterator<Item = Child<'a>> + 'a> { 100 let params = vec![Child::new("predicate", &self.p)]; 101 Box::new(params.into_iter()) 102 } 103 } 104 105 impl fmt::Display for SizePredicate<PngPredicate> { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result106 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 107 write!(f, "is a PNG with size {} x {}", self.w, self.h) 108 } 109 } 110 111 /// Extends a PngPredicate by a comparison to the contents of a reference file 112 #[derive(Debug)] 113 pub struct ReferencePredicate<PngPredicate> { 114 p: PngPredicate, 115 path: PathBuf, 116 } 117 118 impl ReferencePredicate<PngPredicate> { diff_acceptable(diff: &BufferDiff) -> bool119 fn diff_acceptable(diff: &BufferDiff) -> bool { 120 match diff { 121 BufferDiff::DifferentSizes => false, 122 BufferDiff::Diff(diff) => !diff.inacceptable(), 123 } 124 } 125 diff_surface(&self, surface: &SharedImageSurface) -> Option<BufferDiff>126 fn diff_surface(&self, surface: &SharedImageSurface) -> Option<BufferDiff> { 127 let reference = Reference::from_png(&self.path) 128 .unwrap_or_else(|_| panic!("could not open {:?}", self.path)); 129 if let Ok(diff) = reference.compare(&surface) { 130 if !Self::diff_acceptable(&diff) { 131 return Some(diff); 132 } 133 } 134 None 135 } 136 find_case_for_surface<'a>( &'a self, expected: bool, surface: &SharedImageSurface, ) -> Option<Case<'a>>137 fn find_case_for_surface<'a>( 138 &'a self, 139 expected: bool, 140 surface: &SharedImageSurface, 141 ) -> Option<Case<'a>> { 142 let diff = self.diff_surface(&surface); 143 if diff.is_some() != expected { 144 let product = self.product_for_diff(&diff.unwrap()); 145 Some(Case::new(Some(self), false).add_product(product)) 146 } else { 147 None 148 } 149 } 150 product_for_diff(&self, diff: &BufferDiff) -> Product151 fn product_for_diff(&self, diff: &BufferDiff) -> Product { 152 let difference = format!("{}", diff); 153 Product::new("images differ", difference) 154 } 155 } 156 157 impl Predicate<[u8]> for ReferencePredicate<PngPredicate> { eval(&self, data: &[u8]) -> bool158 fn eval(&self, data: &[u8]) -> bool { 159 if let Ok(surface) = surface_from_png(&mut BufReader::new(data)) { 160 let surface = SharedImageSurface::wrap(surface, SurfaceType::SRgb).unwrap(); 161 self.diff_surface(&surface).is_some() 162 } else { 163 false 164 } 165 } 166 find_case<'a>(&'a self, expected: bool, data: &[u8]) -> Option<Case<'a>>167 fn find_case<'a>(&'a self, expected: bool, data: &[u8]) -> Option<Case<'a>> { 168 match surface_from_png(&mut BufReader::new(data)) { 169 Ok(surface) => { 170 let surface = SharedImageSurface::wrap(surface, SurfaceType::SRgb).unwrap(); 171 self.find_case_for_surface(expected, &surface) 172 } 173 Err(e) => Some(Case::new(Some(self), false).add_product(Product::new("Error", e))), 174 } 175 } 176 } 177 178 impl PredicateReflection for ReferencePredicate<PngPredicate> { children<'a>(&'a self) -> Box<dyn Iterator<Item = Child<'a>> + 'a>179 fn children<'a>(&'a self) -> Box<dyn Iterator<Item = Child<'a>> + 'a> { 180 let params = vec![Child::new("predicate", &self.p)]; 181 Box::new(params.into_iter()) 182 } 183 } 184 185 impl fmt::Display for ReferencePredicate<PngPredicate> { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result186 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 187 write!( 188 f, 189 "is a PNG that matches the reference {}", 190 self.path.display() 191 ) 192 } 193 } 194