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