2010-08-24 Dirk Pranke <dpranke@chromium.org>
[WebKit.git] / WebKitTools / Scripts / webkitpy / layout_tests / test_types / image_diff.py
1 #!/usr/bin/env python
2 # Copyright (C) 2010 Google Inc. All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 #     * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 #     * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
13 # distribution.
14 #     * Neither the name of Google Inc. nor the names of its
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 """Compares the image output of a test to the expected image output.
31
32 Compares hashes for the generated and expected images. If the output doesn't
33 match, returns FailureImageHashMismatch and outputs both hashes into the layout
34 test results directory.
35 """
36
37 from __future__ import with_statement
38
39 import codecs
40 import errno
41 import logging
42 import os
43 import shutil
44
45 from webkitpy.layout_tests.layout_package import test_failures
46 from webkitpy.layout_tests.test_types import test_type_base
47
48 # Cache whether we have the image_diff executable available.
49 _compare_available = True
50 _compare_msg_printed = False
51
52 _log = logging.getLogger("webkitpy.layout_tests.test_types.image_diff")
53
54
55 class ImageDiff(test_type_base.TestTypeBase):
56
57     def _copy_output_png(self, test_filename, source_image, extension):
58         """Copies result files into the output directory with appropriate
59         names.
60
61         Args:
62           test_filename: the test filename
63           source_file: path to the image file (either actual or expected)
64           extension: extension to indicate -actual.png or -expected.png
65         """
66         self._make_output_directory(test_filename)
67         dest_image = self.output_filename(test_filename, extension)
68
69         if os.path.exists(source_image):
70             shutil.copyfile(source_image, dest_image)
71
72     def _save_baseline_files(self, filename, png_path, checksum,
73                              generate_new_baseline):
74         """Saves new baselines for the PNG and checksum.
75
76         Args:
77           filename: test filename
78           png_path: path to the actual PNG result file
79           checksum: value of the actual checksum result
80           generate_new_baseline: whether to generate a new, platform-specific
81             baseline, or update the existing one
82         """
83         with open(png_path, "rb") as png_file:
84             png_data = png_file.read()
85         self._save_baseline_data(filename, png_data, ".png", encoding=None,
86                                  generate_new_baseline=generate_new_baseline)
87         self._save_baseline_data(filename, checksum, ".checksum",
88                                  encoding="ascii",
89                                  generate_new_baseline=generate_new_baseline)
90
91     def _create_image_diff(self, port, filename, configuration):
92         """Creates the visual diff of the expected/actual PNGs.
93
94         Args:
95           filename: the name of the test
96           configuration: Debug or Release
97         Returns True if the files are different, False if they match
98         """
99         diff_filename = self.output_filename(filename,
100           self.FILENAME_SUFFIX_COMPARE)
101         actual_filename = self.output_filename(filename,
102           self.FILENAME_SUFFIX_ACTUAL + '.png')
103         expected_filename = self.output_filename(filename,
104           self.FILENAME_SUFFIX_EXPECTED + '.png')
105
106         result = port.diff_image(expected_filename, actual_filename,
107                                  diff_filename)
108         return result
109
110     def compare_output(self, port, filename, output, test_args, configuration):
111         """Implementation of CompareOutput that checks the output image and
112         checksum against the expected files from the LayoutTest directory.
113         """
114         failures = []
115
116         # If we didn't produce a hash file, this test must be text-only.
117         if test_args.hash is None:
118             return failures
119
120         # If we're generating a new baseline, we pass.
121         if test_args.new_baseline or test_args.reset_results:
122             self._save_baseline_files(filename, test_args.png_path,
123                                       test_args.hash, test_args.new_baseline)
124             return failures
125
126         # Compare hashes.
127         expected_hash_file = self._port.expected_filename(filename,
128                                                           '.checksum')
129         expected_png_file = self._port.expected_filename(filename, '.png')
130
131         # FIXME: We repeat this pattern often, we should share code.
132         expected_hash = ''
133         if os.path.exists(expected_hash_file):
134             with codecs.open(expected_hash_file, "r", "ascii") as file:
135                 expected_hash = file.read()
136
137         if not os.path.isfile(expected_png_file):
138             # Report a missing expected PNG file.
139             self.write_output_files(port, filename, '.checksum',
140                                     test_args.hash, expected_hash,
141                                     encoding="ascii",
142                                     print_text_diffs=False)
143             self._copy_output_png(filename, test_args.png_path, '-actual.png')
144             failures.append(test_failures.FailureMissingImage())
145             return failures
146         elif test_args.hash == expected_hash:
147             # Hash matched (no diff needed, okay to return).
148             return failures
149
150         self.write_output_files(port, filename, '.checksum',
151                                 test_args.hash, expected_hash,
152                                 encoding="ascii",
153                                 print_text_diffs=False)
154         self._copy_output_png(filename, test_args.png_path, '-actual.png')
155         self._copy_output_png(filename, expected_png_file, '-expected.png')
156
157         # Even though we only use the result in one codepath below but we
158         # still need to call CreateImageDiff for other codepaths.
159         images_are_different = self._create_image_diff(port, filename, configuration)
160         if expected_hash == '':
161             failures.append(test_failures.FailureMissingImageHash())
162         elif test_args.hash != expected_hash:
163             if images_are_different:
164                 failures.append(test_failures.FailureImageHashMismatch())
165             else:
166                 failures.append(test_failures.FailureImageHashIncorrect())
167
168         return failures