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