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