2011-01-19 Dirk Pranke <dpranke@chromium.org>
[WebKit-https.git] / Tools / Scripts / webkitpy / layout_tests / test_types / test_type_base.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 """Defines the interface TestTypeBase which other test types inherit from.
31
32 Also defines the TestArguments "struct" to pass them additional arguments.
33 """
34
35 import cgi
36 import errno
37 import logging
38
39 _log = logging.getLogger("webkitpy.layout_tests.test_types.test_type_base")
40
41
42 class TestArguments(object):
43     """Struct-like wrapper for additional arguments needed by
44     specific tests."""
45     # Whether to save new baseline results.
46     new_baseline = False
47
48     # Path to the actual PNG file generated by pixel tests
49     png_path = None
50
51     # Value of checksum generated by pixel tests.
52     hash = None
53
54     # Whether to use wdiff to generate by-word diffs.
55     wdiff = False
56
57 # Python bug workaround.  See the wdiff code in WriteOutputFiles for an
58 # explanation.
59 _wdiff_available = True
60
61
62 class TestTypeBase(object):
63
64     # Filename pieces when writing failures to the test results directory.
65     FILENAME_SUFFIX_ACTUAL = "-actual"
66     FILENAME_SUFFIX_EXPECTED = "-expected"
67     FILENAME_SUFFIX_DIFF = "-diff"
68     FILENAME_SUFFIX_WDIFF = "-wdiff.html"
69     FILENAME_SUFFIX_PRETTY_PATCH = "-pretty-diff.html"
70     FILENAME_SUFFIX_COMPARE = "-diff.png"
71
72     def __init__(self, port, root_output_dir):
73         """Initialize a TestTypeBase object.
74
75         Args:
76           port: object implementing port-specific information and methods
77           root_output_dir: The unix style path to the output dir.
78         """
79         self._root_output_dir = root_output_dir
80         self._port = port
81
82     def _make_output_directory(self, filename):
83         """Creates the output directory (if needed) for a given test
84         filename."""
85         fs = self._port._filesystem
86         output_filename = fs.join(self._root_output_dir,
87             self._port.relative_test_filename(filename))
88         fs.maybe_make_directory(fs.dirname(output_filename))
89
90     def _save_baseline_data(self, filename, data, modifier, encoding,
91                             generate_new_baseline=True):
92         """Saves a new baseline file into the port's baseline directory.
93
94         The file will be named simply "<test>-expected<modifier>", suitable for
95         use as the expected results in a later run.
96
97         Args:
98           filename: path to the test file
99           data: result to be saved as the new baseline
100           modifier: type of the result file, e.g. ".txt" or ".png"
101           encoding: file encoding (none, "utf-8", etc.)
102           generate_new_baseline: whether to enerate a new, platform-specific
103             baseline, or update the existing one
104         """
105
106         port = self._port
107         fs = self._port._filesystem
108         if generate_new_baseline:
109             relative_dir = fs.dirname(port.relative_test_filename(filename))
110             baseline_path = port.baseline_path()
111             output_dir = fs.join(baseline_path, relative_dir)
112             output_file = fs.basename(fs.splitext(filename)[0] +
113                 self.FILENAME_SUFFIX_EXPECTED + modifier)
114             fs.maybe_make_directory(output_dir)
115             output_path = fs.join(output_dir, output_file)
116             _log.debug('writing new baseline result "%s"' % (output_path))
117         else:
118             output_path = port.expected_filename(filename, modifier)
119             _log.debug('resetting baseline result "%s"' % output_path)
120
121         port.update_baseline(output_path, data, encoding)
122
123     def output_filename(self, filename, modifier):
124         """Returns a filename inside the output dir that contains modifier.
125
126         For example, if filename is c:/.../fast/dom/foo.html and modifier is
127         "-expected.txt", the return value is
128         c:/cygwin/tmp/layout-test-results/fast/dom/foo-expected.txt
129
130         Args:
131           filename: absolute filename to test file
132           modifier: a string to replace the extension of filename with
133
134         Return:
135           The absolute windows path to the output filename
136         """
137         fs = self._port._filesystem
138         output_filename = fs.join(self._root_output_dir,
139             self._port.relative_test_filename(filename))
140         return fs.splitext(output_filename)[0] + modifier
141
142     def compare_output(self, port, filename, test_args, actual_test_output,
143                         expected_test_output):
144         """Method that compares the output from the test with the
145         expected value.
146
147         This is an abstract method to be implemented by all sub classes.
148
149         Args:
150           port: object implementing port-specific information and methods
151           filename: absolute filename to test file
152           test_args: a TestArguments object holding optional additional
153               arguments
154           actual_test_output: a TestOutput object which represents actual test
155               output
156           expected_test_output: a TestOutput object which represents a expected
157               test output
158
159         Return:
160           a list of TestFailure objects, empty if the test passes
161         """
162         raise NotImplementedError
163
164     def _write_into_file_at_path(self, file_path, contents, encoding):
165         """This method assumes that byte_array is already encoded
166         into the right format."""
167         fs = self._port._filesystem
168         if encoding is None:
169             fs.write_binary_file(file_path, contents)
170             return
171         fs.write_text_file(file_path, contents)
172
173     def write_output_files(self, filename, file_type,
174                            output, expected, encoding,
175                            print_text_diffs=False):
176         """Writes the test output, the expected output and optionally the diff
177         between the two to files in the results directory.
178
179         The full output filename of the actual, for example, will be
180           <filename>-actual<file_type>
181         For instance,
182           my_test-actual.txt
183
184         Args:
185           filename: The test filename
186           file_type: A string describing the test output file type, e.g. ".txt"
187           output: A string containing the test output
188           expected: A string containing the expected test output
189           print_text_diffs: True for text diffs. (FIXME: We should be able to get this from the file type?)
190         """
191         self._make_output_directory(filename)
192         actual_filename = self.output_filename(filename, self.FILENAME_SUFFIX_ACTUAL + file_type)
193         expected_filename = self.output_filename(filename, self.FILENAME_SUFFIX_EXPECTED + file_type)
194         # FIXME: This function is poorly designed.  We should be passing in some sort of
195         # encoding information from the callers.
196         if output:
197             self._write_into_file_at_path(actual_filename, output, encoding)
198         if expected:
199             self._write_into_file_at_path(expected_filename, expected, encoding)
200
201         if not output or not expected:
202             return
203
204         if not print_text_diffs:
205             return
206
207         # Note: We pass encoding=None for all diff writes, as we treat diff
208         # output as binary.  Diff output may contain multiple files in
209         # conflicting encodings.
210         diff = self._port.diff_text(expected, output, expected_filename, actual_filename)
211         diff_filename = self.output_filename(filename, self.FILENAME_SUFFIX_DIFF + file_type)
212         self._write_into_file_at_path(diff_filename, diff, encoding=None)
213
214         # Shell out to wdiff to get colored inline diffs.
215         wdiff = self._port.wdiff_text(expected_filename, actual_filename)
216         wdiff_filename = self.output_filename(filename, self.FILENAME_SUFFIX_WDIFF)
217         self._write_into_file_at_path(wdiff_filename, wdiff, encoding=None)
218
219         # Use WebKit's PrettyPatch.rb to get an HTML diff.
220         pretty_patch = self._port.pretty_patch_text(diff_filename)
221         pretty_patch_filename = self.output_filename(filename, self.FILENAME_SUFFIX_PRETTY_PATCH)
222         self._write_into_file_at_path(pretty_patch_filename, pretty_patch, encoding=None)