1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 use crate::{WindowWrapper, NotifierEvent};
6 use base64;
7 use semver;
8 use image::load as load_piston_image;
9 use image::png::PNGEncoder;
10 use image::{ColorType, ImageFormat};
11 use crate::parse_function::parse_function;
12 use crate::png::save_flipped;
13 use std::{cmp, env};
14 use std::fmt::{Display, Error, Formatter};
15 use std::fs::File;
16 use std::io::{BufRead, BufReader};
17 use std::path::{Path, PathBuf};
18 use std::process::Command;
19 use std::sync::mpsc::Receiver;
20 use webrender::RenderResults;
21 use webrender::api::*;
22 use webrender::render_api::*;
23 use webrender::api::units::*;
24 use crate::wrench::{Wrench, WrenchThing};
25 use crate::yaml_frame_reader::YamlFrameReader;
26 
27 
28 const OPTION_DISABLE_SUBPX: &str = "disable-subpixel";
29 const OPTION_DISABLE_AA: &str = "disable-aa";
30 const OPTION_DISABLE_DUAL_SOURCE_BLENDING: &str = "disable-dual-source-blending";
31 const OPTION_ALLOW_MIPMAPS: &str = "allow-mipmaps";
32 
33 pub struct ReftestOptions {
34     // These override values that are lower.
35     pub allow_max_difference: usize,
36     pub allow_num_differences: usize,
37 }
38 
39 impl ReftestOptions {
default() -> Self40     pub fn default() -> Self {
41         ReftestOptions {
42             allow_max_difference: 0,
43             allow_num_differences: 0,
44         }
45     }
46 }
47 
48 #[derive(Debug, Copy, Clone)]
49 pub enum ReftestOp {
50     /// Expect that the images match the reference
51     Equal,
52     /// Expect that the images *don't* match the reference
53     NotEqual,
54     /// Expect that drawing the reference at different tiles sizes gives the same pixel exact result.
55     Accurate,
56     /// Expect that drawing the reference at different tiles sizes gives a *different* pixel exact result.
57     Inaccurate,
58 }
59 
60 impl Display for ReftestOp {
fmt(&self, f: &mut Formatter) -> Result<(), Error>61     fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
62         write!(
63             f,
64             "{}",
65             match *self {
66                 ReftestOp::Equal => "==".to_owned(),
67                 ReftestOp::NotEqual => "!=".to_owned(),
68                 ReftestOp::Accurate => "**".to_owned(),
69                 ReftestOp::Inaccurate => "!*".to_owned(),
70             }
71         )
72     }
73 }
74 
75 #[derive(Debug)]
76 enum ExtraCheck {
77     DrawCalls(usize),
78     AlphaTargets(usize),
79     ColorTargets(usize),
80 }
81 
82 impl ExtraCheck {
run(&self, results: &[RenderResults]) -> bool83     fn run(&self, results: &[RenderResults]) -> bool {
84         match *self {
85             ExtraCheck::DrawCalls(x) =>
86                 x == results.last().unwrap().stats.total_draw_calls,
87             ExtraCheck::AlphaTargets(x) =>
88                 x == results.last().unwrap().stats.alpha_target_count,
89             ExtraCheck::ColorTargets(x) =>
90                 x == results.last().unwrap().stats.color_target_count,
91         }
92     }
93 }
94 
95 pub struct RefTestFuzzy {
96     max_difference: usize,
97     num_differences: usize,
98 }
99 
100 pub struct Reftest {
101     op: ReftestOp,
102     test: Vec<PathBuf>,
103     reference: PathBuf,
104     font_render_mode: Option<FontRenderMode>,
105     fuzziness: Vec<RefTestFuzzy>,
106     extra_checks: Vec<ExtraCheck>,
107     disable_dual_source_blending: bool,
108     allow_mipmaps: bool,
109     force_subpixel_aa_where_possible: Option<bool>,
110 }
111 
112 impl Reftest {
113     /// Check the positive case (expecting equality) and report details if different
check_and_report_equality_failure( &self, comparison: ReftestImageComparison, test: &ReftestImage, reference: &ReftestImage, ) -> bool114     fn check_and_report_equality_failure(
115         &self,
116         comparison: ReftestImageComparison,
117         test: &ReftestImage,
118         reference: &ReftestImage,
119     ) -> bool {
120         match comparison {
121             ReftestImageComparison::Equal => {
122                 true
123             }
124             ReftestImageComparison::NotEqual { difference_histogram, max_difference, count_different } => {
125                 // Each entry in the sorted self.fuzziness list represents a bucket which
126                 // allows at most num_differences pixels with a difference of at most
127                 // max_difference -- but with the caveat that a difference which is small
128                 // enough to be less than a max_difference of an earlier bucket, must be
129                 // counted against that bucket.
130                 //
131                 // Thus the test will fail if the number of pixels with a difference
132                 // > fuzzy[j-1].max_difference and <= fuzzy[j].max_difference
133                 // exceeds fuzzy[j].num_differences.
134                 //
135                 // (For the first entry, consider fuzzy[j-1] to allow zero pixels of zero
136                 // difference).
137                 //
138                 // For example, say we have this histogram of differences:
139                 //
140                 //       | [0] [1] [2] [3] [4] [5] [6] ... [255]
141                 // ------+------------------------------------------
142                 // Hist. |  0   3   2   1   6   2   0  ...   0
143                 //
144                 // Ie. image comparison found 3 pixels that differ by 1, 2 that differ by 2, etc.
145                 // (Note that entry 0 is always zero, we don't count matching pixels.)
146                 //
147                 // First we calculate an inclusive prefix sum:
148                 //
149                 //       | [0] [1] [2] [3] [4] [5] [6] ... [255]
150                 // ------+------------------------------------------
151                 // Hist. |  0   3   2   1   6   2   0  ...   0
152                 // Sum   |  0   3   5   6  12  14  14  ...  14
153                 //
154                 // Let's say the fuzzy statements are:
155                 // Fuzzy( 2, 6 )    -- allow up to 6 pixels that differ by 2 or less
156                 // Fuzzy( 4, 8 )    -- allow up to 8 pixels that differ by 4 or less _but_
157                 //                     also by more than 2 (= by 3 or 4).
158                 //
159                 // The first  check is Sum[2] <= max 6  which passes: 5 <= 6.
160                 // The second check is Sum[4] - Sum[2] <= max 8  which passes: 12-5 <= 8.
161                 // Finally we check if there are any pixels that exceed the max difference (4)
162                 // by checking Sum[255] - Sum[4] which shows there are 14-12 == 2 so we fail.
163 
164                 let prefix_sum = difference_histogram.iter()
165                                                      .scan(0, |sum, i| { *sum += i; Some(*sum) })
166                                                      .collect::<Vec<_>>();
167 
168                 // check each fuzzy statement for violations.
169                 assert_eq!(0, difference_histogram[0]);
170                 assert_eq!(0, prefix_sum[0]);
171 
172                 // loop invariant: this is the max_difference of the previous iteration's 'fuzzy'
173                 let mut previous_max_diff = 0;
174 
175                 // loop invariant: this is the number of pixels to ignore as they have been counted
176                 // against previous iterations' fuzzy statements.
177                 let mut previous_sum_fail = 0;  // ==  prefix_sum[previous_max_diff]
178 
179                 let mut is_failing = false;
180                 let mut fail_text = String::new();
181 
182                 for fuzzy in &self.fuzziness {
183                     let fuzzy_max_difference = cmp::min(255, fuzzy.max_difference);
184                     let num_differences = prefix_sum[fuzzy_max_difference] - previous_sum_fail;
185                     if num_differences > fuzzy.num_differences {
186                         fail_text.push_str(
187                             &format!("{} differences > {} and <= {} (allowed {}); ",
188                                      num_differences,
189                                      previous_max_diff, fuzzy_max_difference,
190                                      fuzzy.num_differences));
191                         is_failing = true;
192                     }
193                     previous_max_diff = fuzzy_max_difference;
194                     previous_sum_fail = prefix_sum[previous_max_diff];
195                 }
196                 // do we have any pixels with a difference above the highest allowed
197                 // max difference? if so, we fail the test:
198                 let num_differences = prefix_sum[255] - previous_sum_fail;
199                 if num_differences > 0 {
200                     fail_text.push_str(
201                         &format!("{} num_differences > {} and <= {} (allowed {}); ",
202                                 num_differences,
203                                 previous_max_diff, 255,
204                                 0));
205                     is_failing = true;
206                 }
207 
208                 if is_failing {
209                     println!(
210                         "{} | {} | {}: {}, {}: {} | {}",
211                         "REFTEST TEST-UNEXPECTED-FAIL",
212                         self,
213                         "image comparison, max difference",
214                         max_difference,
215                         "number of differing pixels",
216                         count_different,
217                         fail_text,
218                     );
219                     println!("REFTEST   IMAGE 1 (TEST): {}", test.clone().create_data_uri());
220                     println!(
221                         "REFTEST   IMAGE 2 (REFERENCE): {}",
222                         reference.clone().create_data_uri()
223                     );
224                     println!("REFTEST TEST-END | {}", self);
225 
226                     false
227                 } else {
228                     true
229                 }
230             }
231         }
232     }
233 
234     /// Report details of the negative case
report_unexpected_equality(&self)235     fn report_unexpected_equality(&self) {
236         println!("REFTEST TEST-UNEXPECTED-FAIL | {} | image comparison", self);
237         println!("REFTEST TEST-END | {}", self);
238     }
239 }
240 
241 impl Display for Reftest {
fmt(&self, f: &mut Formatter) -> Result<(), Error>242     fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
243         let paths: Vec<String> = self.test.iter().map(|t| t.display().to_string()).collect();
244         write!(
245             f,
246             "{} {} {}",
247             paths.join(", "),
248             self.op,
249             self.reference.display()
250         )
251     }
252 }
253 
254 #[derive(Clone)]
255 pub struct ReftestImage {
256     pub data: Vec<u8>,
257     pub size: DeviceIntSize,
258 }
259 
260 #[derive(Debug, Clone)]
261 pub enum ReftestImageComparison {
262     Equal,
263     NotEqual {
264         /// entry[j] = number of pixels with a difference of exactly j
265         difference_histogram: Vec<usize>,
266         max_difference: usize,
267         count_different: usize,
268     },
269 }
270 
271 impl ReftestImage {
compare(&self, other: &ReftestImage) -> ReftestImageComparison272     pub fn compare(&self, other: &ReftestImage) -> ReftestImageComparison {
273         assert_eq!(self.size, other.size);
274         assert_eq!(self.data.len(), other.data.len());
275         assert_eq!(self.data.len() % 4, 0);
276 
277         let mut histogram = [0usize; 256];
278         let mut count = 0;
279         let mut max = 0;
280 
281         for (a, b) in self.data.chunks(4).zip(other.data.chunks(4)) {
282             if a != b {
283                 let pixel_max = a.iter()
284                     .zip(b.iter())
285                     .map(|(x, y)| (*x as isize - *y as isize).abs() as usize)
286                     .max()
287                     .unwrap();
288 
289                 count += 1;
290                 assert!(pixel_max < 256, "pixel values are not 8 bit, update the histogram binning code");
291                 // deliberately avoid counting pixels that match --
292                 // histogram[0] stays at zero.
293                 // this helps our prefix sum later during analysis to
294                 // only count actual differences.
295                 histogram[pixel_max as usize] += 1;
296                 max = cmp::max(max, pixel_max);
297             }
298         }
299 
300         if count != 0 {
301             ReftestImageComparison::NotEqual {
302                 difference_histogram: histogram.to_vec(),
303                 max_difference: max,
304                 count_different: count,
305             }
306         } else {
307             ReftestImageComparison::Equal
308         }
309     }
310 
create_data_uri(mut self) -> String311     pub fn create_data_uri(mut self) -> String {
312         let width = self.size.width;
313         let height = self.size.height;
314 
315         // flip image vertically (texture is upside down)
316         let orig_pixels = self.data.clone();
317         let stride = width as usize * 4;
318         for y in 0 .. height as usize {
319             let dst_start = y * stride;
320             let src_start = (height as usize - y - 1) * stride;
321             let src_slice = &orig_pixels[src_start .. src_start + stride];
322             (&mut self.data[dst_start .. dst_start + stride])
323                 .clone_from_slice(&src_slice[.. stride]);
324         }
325 
326         let mut png: Vec<u8> = vec![];
327         {
328             let encoder = PNGEncoder::new(&mut png);
329             encoder
330                 .encode(&self.data[..], width as u32, height as u32, ColorType::Rgba8)
331                 .expect("Unable to encode PNG!");
332         }
333         let png_base64 = base64::encode(&png);
334         format!("data:image/png;base64,{}", png_base64)
335     }
336 }
337 
338 struct ReftestManifest {
339     reftests: Vec<Reftest>,
340 }
341 impl ReftestManifest {
new(manifest: &Path, environment: &ReftestEnvironment, options: &ReftestOptions) -> ReftestManifest342     fn new(manifest: &Path, environment: &ReftestEnvironment, options: &ReftestOptions) -> ReftestManifest {
343         let dir = manifest.parent().unwrap();
344         let f =
345             File::open(manifest).expect(&format!("couldn't open manifest: {}", manifest.display()));
346         let file = BufReader::new(&f);
347 
348         let mut reftests = Vec::new();
349 
350         for line in file.lines() {
351             let l = line.unwrap();
352 
353             // strip the comments
354             let s = &l[0 .. l.find('#').unwrap_or(l.len())];
355             let s = s.trim();
356             if s.is_empty() {
357                 continue;
358             }
359 
360             let tokens: Vec<&str> = s.split_whitespace().collect();
361 
362             let mut fuzziness = Vec::new();
363             let mut op = None;
364             let mut font_render_mode = None;
365             let mut extra_checks = vec![];
366             let mut disable_dual_source_blending = false;
367             let mut allow_mipmaps = false;
368             let mut force_subpixel_aa_where_possible = None;
369 
370             let mut parse_command = |token: &str| -> bool {
371                 match token {
372                     function if function.starts_with("force_subpixel_aa_where_possible(") => {
373                         let (_, args, _) = parse_function(function);
374                         force_subpixel_aa_where_possible = Some(args[0].parse().unwrap());
375                     }
376                     function if function.starts_with("fuzzy-range(") ||
377                                 function.starts_with("fuzzy-range-if(") => {
378                         let (_, mut args, _) = parse_function(function);
379                         if function.starts_with("fuzzy-range-if(") {
380                             if !environment.parse_condition(args.remove(0)).expect("unknown condition") {
381                                 return true;
382                             }
383                             fuzziness.clear();
384                         }
385                         let num_range = args.len() / 2;
386                         for range in 0..num_range {
387                             let mut max = args[range * 2 + 0];
388                             let mut num = args[range * 2 + 1];
389                             if max.starts_with("<=") { // trim_start_matches would allow <=<=123
390                                 max = &max[2..];
391                             }
392                             if num.starts_with("*") {
393                                 num = &num[1..];
394                             }
395                             let max_difference  = max.parse().unwrap();
396                             let num_differences = num.parse().unwrap();
397                             fuzziness.push(RefTestFuzzy { max_difference, num_differences });
398                         }
399                     }
400                     function if function.starts_with("fuzzy(") ||
401                                 function.starts_with("fuzzy-if(") => {
402                         let (_, mut args, _) = parse_function(function);
403                         if function.starts_with("fuzzy-if(") {
404                             if !environment.parse_condition(args.remove(0)).expect("unknown condition") {
405                                 return true;
406                             }
407                             fuzziness.clear();
408                         }
409                         let max_difference = args[0].parse().unwrap();
410                         let num_differences = args[1].parse().unwrap();
411                         assert!(fuzziness.is_empty()); // if this fires, consider fuzzy-range instead
412                         fuzziness.push(RefTestFuzzy { max_difference, num_differences });
413                     }
414                     function if function.starts_with("draw_calls(") => {
415                         let (_, args, _) = parse_function(function);
416                         extra_checks.push(ExtraCheck::DrawCalls(args[0].parse().unwrap()));
417                     }
418                     function if function.starts_with("alpha_targets(") => {
419                         let (_, args, _) = parse_function(function);
420                         extra_checks.push(ExtraCheck::AlphaTargets(args[0].parse().unwrap()));
421                     }
422                     function if function.starts_with("color_targets(") => {
423                         let (_, args, _) = parse_function(function);
424                         extra_checks.push(ExtraCheck::ColorTargets(args[0].parse().unwrap()));
425                     }
426                     options if options.starts_with("options(") => {
427                         let (_, args, _) = parse_function(options);
428                         if args.iter().any(|arg| arg == &OPTION_DISABLE_SUBPX) {
429                             font_render_mode = Some(FontRenderMode::Alpha);
430                         }
431                         if args.iter().any(|arg| arg == &OPTION_DISABLE_AA) {
432                             font_render_mode = Some(FontRenderMode::Mono);
433                         }
434                         if args.iter().any(|arg| arg == &OPTION_DISABLE_DUAL_SOURCE_BLENDING) {
435                             disable_dual_source_blending = true;
436                         }
437                         if args.iter().any(|arg| arg == &OPTION_ALLOW_MIPMAPS) {
438                             allow_mipmaps = true;
439                         }
440                     }
441                     _ => return false,
442                 }
443                 return true;
444             };
445 
446             let mut paths = vec![];
447             for (i, token) in tokens.iter().enumerate() {
448                 match *token {
449                     "include" => {
450                         assert!(i == 0, "include must be by itself");
451                         let include = dir.join(tokens[1]);
452 
453                         reftests.append(
454                             &mut ReftestManifest::new(include.as_path(), environment, options).reftests,
455                         );
456 
457                         break;
458                     }
459                     "==" => {
460                         op = Some(ReftestOp::Equal);
461                     }
462                     "!=" => {
463                         op = Some(ReftestOp::NotEqual);
464                     }
465                     "**" => {
466                         op = Some(ReftestOp::Accurate);
467                     }
468                     "!*" => {
469                         op = Some(ReftestOp::Inaccurate);
470                     }
471                     cond if cond.starts_with("if(") => {
472                         let (_, args, _) = parse_function(cond);
473                         if environment.parse_condition(args[0]).expect("unknown condition") {
474                             for command in &args[1..] {
475                                 parse_command(command);
476                             }
477                         }
478                     }
479                     command if parse_command(command) => {}
480                     _ => {
481                         match environment.parse_condition(*token) {
482                             Some(true) => {}
483                             Some(false) => break,
484                             _ => paths.push(dir.join(*token)),
485                         }
486                     }
487                 }
488             }
489 
490             // Don't try to add tests for include lines.
491             let op = match op {
492                 Some(op) => op,
493                 None => {
494                     assert!(paths.is_empty(), "paths = {:?}", paths);
495                     continue;
496                 }
497             };
498 
499             // The reference is the last path provided. If multiple paths are
500             // passed for the test, they render sequentially before being
501             // compared to the reference, which is useful for testing
502             // invalidation.
503             let reference = paths.pop().unwrap();
504             let test = paths;
505 
506             if environment.platform == "android" {
507                 // Add some fuzz on mobile as we do for non-wrench reftests.
508                 // First remove the ranges with difference <= 2, otherwise they might cause the
509                 // test to fail before the new range is picked up.
510                 fuzziness.retain(|fuzzy| fuzzy.max_difference > 2);
511                 fuzziness.push(RefTestFuzzy { max_difference: 2, num_differences: std::usize::MAX });
512             }
513 
514             // to avoid changing the meaning of existing tests, the case of
515             // only a single (or no) 'fuzzy' keyword means we use the max
516             // of that fuzzy and options.allow_.. (we don't want that to
517             // turn into a test that allows fuzzy.allow_ *plus* options.allow_):
518             match fuzziness.len() {
519                 0 => fuzziness.push(RefTestFuzzy {
520                         max_difference: options.allow_max_difference,
521                         num_differences: options.allow_num_differences }),
522                 1 => {
523                     let mut fuzzy = &mut fuzziness[0];
524                     fuzzy.max_difference = cmp::max(fuzzy.max_difference, options.allow_max_difference);
525                     fuzzy.num_differences = cmp::max(fuzzy.num_differences, options.allow_num_differences);
526                 },
527                 _ => {
528                     // ignore options, use multiple fuzzy keywords instead. make sure
529                     // the list is sorted to speed up counting violations.
530                     fuzziness.sort_by(|a, b| a.max_difference.cmp(&b.max_difference));
531                     for pair in fuzziness.windows(2) {
532                         if pair[0].max_difference == pair[1].max_difference {
533                             println!("Warning: repeated fuzzy of max_difference {} ignored.",
534                                      pair[1].max_difference);
535                         }
536                     }
537                 }
538             }
539 
540             reftests.push(Reftest {
541                 op,
542                 test,
543                 reference,
544                 font_render_mode,
545                 fuzziness,
546                 extra_checks,
547                 disable_dual_source_blending,
548                 allow_mipmaps,
549                 force_subpixel_aa_where_possible,
550             });
551         }
552 
553         ReftestManifest { reftests: reftests }
554     }
555 
find(&self, prefix: &Path) -> Vec<&Reftest>556     fn find(&self, prefix: &Path) -> Vec<&Reftest> {
557         self.reftests
558             .iter()
559             .filter(|x| {
560                 x.test.iter().any(|t| t.starts_with(prefix)) || x.reference.starts_with(prefix)
561             })
562             .collect()
563     }
564 }
565 
566 struct YamlRenderOutput {
567     image: ReftestImage,
568     results: RenderResults,
569 }
570 
571 struct ReftestEnvironment {
572     pub platform: &'static str,
573     pub version: Option<semver::Version>,
574     pub mode: &'static str,
575 }
576 
577 impl ReftestEnvironment {
new(wrench: &Wrench, window: &WindowWrapper) -> Self578     fn new(wrench: &Wrench, window: &WindowWrapper) -> Self {
579         Self {
580             platform: Self::platform(wrench, window),
581             version: Self::version(wrench, window),
582             mode: Self::mode(),
583         }
584     }
585 
has(&self, condition: &str) -> bool586     fn has(&self, condition: &str) -> bool {
587         if self.platform == condition || self.mode == condition {
588             return true;
589         }
590         match (&self.version, &semver::VersionReq::parse(condition)) {
591             (Some(v), Ok(r)) => {
592                 if r.matches(v) {
593                     return true;
594                 }
595             },
596             _ => (),
597         };
598         let envkey = format!("WRENCH_REFTEST_CONDITION_{}", condition.to_uppercase());
599         env::var(envkey).is_ok()
600     }
601 
platform(_wrench: &Wrench, window: &WindowWrapper) -> &'static str602     fn platform(_wrench: &Wrench, window: &WindowWrapper) -> &'static str {
603         if window.is_software() {
604             "swgl"
605         } else if cfg!(target_os = "windows") {
606             "win"
607         } else if cfg!(target_os = "linux") {
608             "linux"
609         } else if cfg!(target_os = "macos") {
610             "mac"
611         } else if cfg!(target_os = "android") {
612             "android"
613         } else {
614             "other"
615         }
616     }
617 
version(_wrench: &Wrench, window: &WindowWrapper) -> Option<semver::Version>618     fn version(_wrench: &Wrench, window: &WindowWrapper) -> Option<semver::Version> {
619         if window.is_software() {
620             None
621         } else if cfg!(target_os = "macos") {
622             use std::str;
623             let version_bytes = Command::new("defaults")
624                 .arg("read")
625                 .arg("loginwindow")
626                 .arg("SystemVersionStampAsString")
627                 .output()
628                 .expect("Failed to get macOS version")
629                 .stdout;
630             let mut version_string = str::from_utf8(&version_bytes)
631                 .expect("Failed to read macOS version")
632                 .trim()
633                 .to_string();
634             // On some machines this produces just the major.minor and on
635             // some machines this gives major.minor.patch. But semver requires
636             // the patch so we fake one if it's not there.
637             if version_string.chars().filter(|c| *c == '.').count() == 1 {
638                 version_string.push_str(".0");
639             }
640             Some(semver::Version::parse(&version_string)
641                  .expect(&format!("Failed to parse macOS version {}", version_string)))
642         } else {
643             None
644         }
645     }
646 
mode() -> &'static str647     fn mode() -> &'static str {
648         if cfg!(debug_assertions) {
649             "debug"
650         } else {
651             "release"
652         }
653     }
654 
parse_condition(&self, token: &str) -> Option<bool>655     fn parse_condition(&self, token: &str) -> Option<bool> {
656         match token {
657             platform if platform.starts_with("skip_on(") => {
658                 // e.g. skip_on(android,debug) will skip only when
659                 // running on a debug android build.
660                 let (_, args, _) = parse_function(platform);
661                 Some(!args.iter().all(|arg| self.has(arg)))
662             }
663             platform if platform.starts_with("env(") => {
664                 // non-negated version of skip_on for nested conditions
665                 let (_, args, _) = parse_function(platform);
666                 Some(args.iter().all(|arg| self.has(arg)))
667             }
668             platform if platform.starts_with("platform(") => {
669                 let (_, args, _) = parse_function(platform);
670                 // Skip due to platform not matching
671                 Some(args.iter().any(|arg| arg == &self.platform))
672             }
673             op if op.starts_with("not(") => {
674                 let (_, args, _) = parse_function(op);
675                 Some(!self.parse_condition(args[0]).expect("unknown condition"))
676             }
677             op if op.starts_with("or(") => {
678                 let (_, args, _) = parse_function(op);
679                 Some(args.iter().any(|arg| self.parse_condition(arg).expect("unknown condition")))
680             }
681             op if op.starts_with("and(") => {
682                 let (_, args, _) = parse_function(op);
683                 Some(args.iter().all(|arg| self.parse_condition(arg).expect("unknown condition")))
684             }
685             _ => None,
686         }
687     }
688 }
689 
690 pub struct ReftestHarness<'a> {
691     wrench: &'a mut Wrench,
692     window: &'a mut WindowWrapper,
693     rx: &'a Receiver<NotifierEvent>,
694     environment: ReftestEnvironment,
695 }
696 impl<'a> ReftestHarness<'a> {
new(wrench: &'a mut Wrench, window: &'a mut WindowWrapper, rx: &'a Receiver<NotifierEvent>) -> Self697     pub fn new(wrench: &'a mut Wrench, window: &'a mut WindowWrapper, rx: &'a Receiver<NotifierEvent>) -> Self {
698         let environment = ReftestEnvironment::new(wrench, window);
699         ReftestHarness { wrench, window, rx, environment }
700     }
701 
run(mut self, base_manifest: &Path, reftests: Option<&Path>, options: &ReftestOptions) -> usize702     pub fn run(mut self, base_manifest: &Path, reftests: Option<&Path>, options: &ReftestOptions) -> usize {
703         let manifest = ReftestManifest::new(base_manifest, &self.environment, options);
704         let reftests = manifest.find(reftests.unwrap_or(&PathBuf::new()));
705 
706         let mut total_passing = 0;
707         let mut failing = Vec::new();
708 
709         for t in reftests {
710             if self.run_reftest(t) {
711                 total_passing += 1;
712             } else {
713                 failing.push(t);
714             }
715         }
716 
717         println!(
718             "REFTEST INFO | {} passing, {} failing",
719             total_passing,
720             failing.len()
721         );
722 
723         if !failing.is_empty() {
724             println!("\nReftests with unexpected results:");
725 
726             for reftest in &failing {
727                 println!("\t{}", reftest);
728             }
729         }
730 
731         failing.len()
732     }
733 
run_reftest(&mut self, t: &Reftest) -> bool734     fn run_reftest(&mut self, t: &Reftest) -> bool {
735         let test_name = t.to_string();
736         println!("REFTEST {}", test_name);
737         profile_scope!("wrench reftest", text: &test_name);
738 
739         self.wrench
740             .api
741             .send_debug_cmd(
742                 DebugCommand::ClearCaches(ClearCache::all())
743             );
744 
745         let quality_settings = match t.force_subpixel_aa_where_possible {
746             Some(force_subpixel_aa_where_possible) => {
747                 QualitySettings {
748                     force_subpixel_aa_where_possible,
749                 }
750             }
751             None => {
752                 QualitySettings::default()
753             }
754         };
755 
756         self.wrench.set_quality_settings(quality_settings);
757 
758         if t.disable_dual_source_blending {
759             self.wrench
760                 .api
761                 .send_debug_cmd(
762                     DebugCommand::EnableDualSourceBlending(false)
763                 );
764         }
765 
766         let window_size = self.window.get_inner_size();
767         let reference_image = match t.reference.extension().unwrap().to_str().unwrap() {
768             "yaml" => None,
769             "png" => Some(self.load_image(t.reference.as_path(), ImageFormat::Png)),
770             other => panic!("Unknown reftest extension: {}", other),
771         };
772         let test_size = reference_image.as_ref().map_or(window_size, |img| img.size);
773 
774         // The reference can be smaller than the window size, in which case
775         // we only compare the intersection.
776         //
777         // Note also that, when we have multiple test scenes in sequence, we
778         // want to test the picture caching machinery. But since picture caching
779         // only takes effect after the result has been the same several frames in
780         // a row, we need to render the scene multiple times.
781         let mut images = vec![];
782         let mut results = vec![];
783 
784         match t.op {
785             ReftestOp::Equal | ReftestOp::NotEqual => {
786                 // For equality tests, render each test image and store result
787                 for filename in t.test.iter() {
788                     let output = self.render_yaml(
789                         &filename,
790                         test_size,
791                         t.font_render_mode,
792                         t.allow_mipmaps,
793                     );
794                     images.push(output.image);
795                     results.push(output.results);
796                 }
797             }
798             ReftestOp::Accurate | ReftestOp::Inaccurate => {
799                 // For accuracy tests, render the reference yaml at an arbitrary series
800                 // of tile sizes, and compare to the reference drawn at normal tile size.
801                 let tile_sizes = [
802                     DeviceIntSize::new(128, 128),
803                     DeviceIntSize::new(256, 256),
804                     DeviceIntSize::new(512, 512),
805                 ];
806 
807                 for tile_size in &tile_sizes {
808                     self.wrench
809                         .api
810                         .send_debug_cmd(
811                             DebugCommand::SetPictureTileSize(Some(*tile_size))
812                         );
813 
814                     let output = self.render_yaml(
815                         &t.reference,
816                         test_size,
817                         t.font_render_mode,
818                         t.allow_mipmaps,
819                     );
820                     images.push(output.image);
821                     results.push(output.results);
822                 }
823 
824                 self.wrench
825                     .api
826                     .send_debug_cmd(
827                         DebugCommand::SetPictureTileSize(None)
828                     );
829             }
830         }
831 
832         let reference = match reference_image {
833             Some(image) => {
834                 let save_all_png = false; // flip to true to update all the tests!
835                 if save_all_png {
836                     let img = images.last().unwrap();
837                     save_flipped(&t.reference, img.data.clone(), img.size);
838                 }
839                 image
840             }
841             None => {
842                 let output = self.render_yaml(
843                     &t.reference,
844                     test_size,
845                     t.font_render_mode,
846                     t.allow_mipmaps,
847                 );
848                 output.image
849             }
850         };
851 
852         if t.disable_dual_source_blending {
853             self.wrench
854                 .api
855                 .send_debug_cmd(
856                     DebugCommand::EnableDualSourceBlending(true)
857                 );
858         }
859 
860         for extra_check in t.extra_checks.iter() {
861             if !extra_check.run(&results) {
862                 println!(
863                     "REFTEST TEST-UNEXPECTED-FAIL | {} | Failing Check: {:?} | Actual Results: {:?}",
864                     t,
865                     extra_check,
866                     results,
867                 );
868                 println!("REFTEST TEST-END | {}", t);
869                 return false;
870             }
871         }
872 
873         match t.op {
874             ReftestOp::Equal => {
875                 // Ensure that the final image matches the reference
876                 let test = images.pop().unwrap();
877                 let comparison = test.compare(&reference);
878                 t.check_and_report_equality_failure(
879                     comparison,
880                     &test,
881                     &reference,
882                 )
883             }
884             ReftestOp::NotEqual => {
885                 // Ensure that the final image *doesn't* match the reference
886                 let test = images.pop().unwrap();
887                 let comparison = test.compare(&reference);
888                 match comparison {
889                     ReftestImageComparison::Equal => {
890                         t.report_unexpected_equality();
891                         false
892                     }
893                     ReftestImageComparison::NotEqual { .. } => {
894                         true
895                     }
896                 }
897             }
898             ReftestOp::Accurate => {
899                 // Ensure that *all* images match the reference
900                 for test in images.drain(..) {
901                     let comparison = test.compare(&reference);
902 
903                     if !t.check_and_report_equality_failure(
904                         comparison,
905                         &test,
906                         &reference,
907                     ) {
908                         return false;
909                     }
910                 }
911 
912                 true
913             }
914             ReftestOp::Inaccurate => {
915                 // Ensure that at least one of the images doesn't match the reference
916                 let all_same = images.iter().all(|image| {
917                     match image.compare(&reference) {
918                         ReftestImageComparison::Equal => true,
919                         ReftestImageComparison::NotEqual { .. } => false,
920                     }
921                 });
922 
923                 if all_same {
924                     t.report_unexpected_equality();
925                 }
926 
927                 !all_same
928             }
929         }
930     }
931 
load_image(&mut self, filename: &Path, format: ImageFormat) -> ReftestImage932     fn load_image(&mut self, filename: &Path, format: ImageFormat) -> ReftestImage {
933         let file = BufReader::new(File::open(filename).unwrap());
934         let img_raw = load_piston_image(file, format).unwrap();
935         let img = img_raw.flipv().to_rgba();
936         let size = img.dimensions();
937         ReftestImage {
938             data: img.into_raw(),
939             size: DeviceIntSize::new(size.0 as i32, size.1 as i32),
940         }
941     }
942 
render_yaml( &mut self, filename: &Path, size: DeviceIntSize, font_render_mode: Option<FontRenderMode>, allow_mipmaps: bool, ) -> YamlRenderOutput943     fn render_yaml(
944         &mut self,
945         filename: &Path,
946         size: DeviceIntSize,
947         font_render_mode: Option<FontRenderMode>,
948         allow_mipmaps: bool,
949     ) -> YamlRenderOutput {
950         let mut reader = YamlFrameReader::new(filename);
951         reader.set_font_render_mode(font_render_mode);
952         reader.allow_mipmaps(allow_mipmaps);
953         reader.do_frame(self.wrench);
954 
955         self.wrench.api.flush_scene_builder();
956 
957         // wait for the frame
958         self.rx.recv().unwrap();
959         let results = self.wrench.render();
960 
961         let window_size = self.window.get_inner_size();
962         assert!(
963             size.width <= window_size.width &&
964             size.height <= window_size.height,
965             "size={:?} ws={:?}", size, window_size
966         );
967 
968         // taking the bottom left sub-rectangle
969         let rect = FramebufferIntRect::from_origin_and_size(
970             FramebufferIntPoint::new(0, window_size.height - size.height),
971             FramebufferIntSize::new(size.width, size.height),
972         );
973         let pixels = self.wrench.renderer.read_pixels_rgba8(rect);
974         self.window.swap_buffers();
975 
976         let write_debug_images = false;
977         if write_debug_images {
978             let debug_path = filename.with_extension("yaml.png");
979             save_flipped(debug_path, pixels.clone(), size);
980         }
981 
982         reader.deinit(self.wrench);
983 
984         YamlRenderOutput {
985             image: ReftestImage { data: pixels, size },
986             results,
987         }
988     }
989 }
990