1
use predicates::prelude::*;
2
use predicates::reflection::{Case, Child, PredicateReflection, Product};
3
use std::fmt;
4
use std::io::BufReader;
5
use std::path::{Path, PathBuf};
6

            
7
use rsvg::rsvg_convert_only::{SharedImageSurface, SurfaceType};
8

            
9
use rsvg::test_utils::compare_surfaces::BufferDiff;
10
use rsvg::test_utils::reference_utils::{surface_from_png, Compare, Deviation, Reference};
11

            
12
/// Checks that the variable of type [u8] can be parsed as a PNG file.
13
#[derive(Debug)]
14
pub struct PngPredicate {}
15

            
16
impl PngPredicate {
17
24
    pub fn with_size(self, w: u32, h: u32) -> SizePredicate<Self> {
18
        SizePredicate::<Self> { p: self, w, h }
19
24
    }
20

            
21
13
    pub fn with_contents<P: AsRef<Path>>(self, reference: P) -> ReferencePredicate<Self> {
22
13
        let mut path = PathBuf::new();
23
13
        path.push(reference);
24
13
        ReferencePredicate::<Self> { p: self, path }
25
13
    }
26
}
27

            
28
impl Predicate<[u8]> for PngPredicate {
29
    fn eval(&self, data: &[u8]) -> bool {
30
        let decoder = png::Decoder::new(data);
31
        decoder.read_info().is_ok()
32
    }
33

            
34
5
    fn find_case<'a>(&'a self, _expected: bool, data: &[u8]) -> Option<Case<'a>> {
35
5
        let decoder = png::Decoder::new(data);
36
5
        match decoder.read_info() {
37
5
            Ok(_) => None,
38
            Err(e) => Some(Case::new(Some(self), false).add_product(Product::new("Error", e))),
39
        }
40
5
    }
41
}
42

            
43
impl PredicateReflection for PngPredicate {}
44

            
45
impl fmt::Display for PngPredicate {
46
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
47
        write!(f, "is a PNG")
48
    }
49
}
50

            
51
/// Extends a PngPredicate by a check for a given size of the PNG file.
52
#[derive(Debug)]
53
pub struct SizePredicate<PngPredicate> {
54
    p: PngPredicate,
55
    w: u32,
56
    h: u32,
57
}
58

            
59
impl SizePredicate<PngPredicate> {
60
24
    fn eval_info(&self, info: &png::Info) -> bool {
61
24
        info.width == self.w && info.height == self.h
62
24
    }
63

            
64
24
    fn find_case_for_info<'a>(&'a self, expected: bool, info: &png::Info) -> Option<Case<'a>> {
65
24
        if self.eval_info(info) == expected {
66
            let product = self.product_for_info(info);
67
            Some(Case::new(Some(self), false).add_product(product))
68
        } else {
69
24
            None
70
        }
71
24
    }
72

            
73
    fn product_for_info(&self, info: &png::Info) -> Product {
74
        let actual_size = format!("{} x {}", info.width, info.height);
75
        Product::new("actual size", actual_size)
76
    }
77
}
78

            
79
impl Predicate<[u8]> for SizePredicate<PngPredicate> {
80
    fn eval(&self, data: &[u8]) -> bool {
81
        let decoder = png::Decoder::new(data);
82
        match decoder.read_info() {
83
            Ok(reader) => self.eval_info(reader.info()),
84
            _ => false,
85
        }
86
    }
87

            
88
24
    fn find_case<'a>(&'a self, expected: bool, data: &[u8]) -> Option<Case<'a>> {
89
24
        let decoder = png::Decoder::new(data);
90
24
        match decoder.read_info() {
91
24
            Ok(reader) => self.find_case_for_info(expected, reader.info()),
92
            Err(e) => Some(Case::new(Some(self), false).add_product(Product::new("Error", e))),
93
        }
94
24
    }
95
}
96

            
97
impl PredicateReflection for SizePredicate<PngPredicate> {
98
    fn children<'a>(&'a self) -> Box<dyn Iterator<Item = Child<'a>> + 'a> {
99
        let params = vec![Child::new("predicate", &self.p)];
100
        Box::new(params.into_iter())
101
    }
102
}
103

            
104
impl fmt::Display for SizePredicate<PngPredicate> {
105
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
106
        write!(f, "is a PNG with size {} x {}", self.w, self.h)
107
    }
108
}
109

            
110
/// Extends a PngPredicate by a comparison to the contents of a reference file
111
#[derive(Debug)]
112
pub struct ReferencePredicate<PngPredicate> {
113
    p: PngPredicate,
114
    path: PathBuf,
115
}
116

            
117
impl ReferencePredicate<PngPredicate> {
118
13
    fn diff_acceptable(diff: &BufferDiff) -> bool {
119
13
        match diff {
120
            BufferDiff::DifferentSizes => false,
121
13
            BufferDiff::Diff(diff) => !diff.inacceptable(),
122
        }
123
13
    }
124

            
125
13
    fn diff_surface(&self, surface: &SharedImageSurface) -> Option<BufferDiff> {
126
13
        let reference = Reference::from_png(&self.path);
127
13
        if let Ok(diff) = reference.compare(surface) {
128
13
            if !Self::diff_acceptable(&diff) {
129
                return Some(diff);
130
            }
131
13
        }
132
13
        None
133
13
    }
134

            
135
13
    fn find_case_for_surface<'a>(
136
        &'a self,
137
        expected: bool,
138
        surface: &SharedImageSurface,
139
    ) -> Option<Case<'a>> {
140
13
        let diff = self.diff_surface(surface);
141
13
        if diff.is_some() != expected {
142
            let product = self.product_for_diff(&diff.unwrap());
143
            Some(Case::new(Some(self), false).add_product(product))
144
        } else {
145
13
            None
146
        }
147
13
    }
148

            
149
    fn product_for_diff(&self, diff: &BufferDiff) -> Product {
150
        let difference = format!("{}", diff);
151
        Product::new("images differ", difference)
152
    }
153
}
154

            
155
impl Predicate<[u8]> for ReferencePredicate<PngPredicate> {
156
    fn eval(&self, data: &[u8]) -> bool {
157
        if let Ok(surface) = surface_from_png(&mut BufReader::new(data)) {
158
            let surface = SharedImageSurface::wrap(surface, SurfaceType::SRgb).unwrap();
159
            self.diff_surface(&surface).is_some()
160
        } else {
161
            false
162
        }
163
    }
164

            
165
13
    fn find_case<'a>(&'a self, expected: bool, data: &[u8]) -> Option<Case<'a>> {
166
13
        match surface_from_png(&mut BufReader::new(data)) {
167
13
            Ok(surface) => {
168
13
                let surface = SharedImageSurface::wrap(surface, SurfaceType::SRgb).unwrap();
169
13
                self.find_case_for_surface(expected, &surface)
170
13
            }
171
            Err(e) => Some(Case::new(Some(self), false).add_product(Product::new("Error", e))),
172
        }
173
13
    }
174
}
175

            
176
impl PredicateReflection for ReferencePredicate<PngPredicate> {
177
    fn children<'a>(&'a self) -> Box<dyn Iterator<Item = Child<'a>> + 'a> {
178
        let params = vec![Child::new("predicate", &self.p)];
179
        Box::new(params.into_iter())
180
    }
181
}
182

            
183
impl fmt::Display for ReferencePredicate<PngPredicate> {
184
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
185
        write!(
186
            f,
187
            "is a PNG that matches the reference {}",
188
            self.path.display()
189
        )
190
    }
191
}