Rename WebKitTools to Tools
[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 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._port.update_baseline(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, test_args, actual_test_output,
144                         expected_test_output):
145         """Method that compares the output from the test with the
146         expected value.
147
148         This is an abstract method to be implemented by all sub classes.
149
150         Args:
151           port: object implementing port-specific information and methods
152           filename: absolute filename to test file
153           test_args: a TestArguments object holding optional additional
154               arguments
155           actual_test_output: a TestOutput object which represents actual test
156               output
157           expected_test_output: a TestOutput object which represents a expected
158               test output
159
160         Return:
161           a list of TestFailure objects, empty if the test passes
162         """
163         raise NotImplementedError
164
165     def _write_into_file_at_path(self, file_path, contents, encoding):
166         """This method assumes that byte_array is already encoded
167         into the right format."""
168         open_mode = 'w'
169         if encoding is None:
170             open_mode = 'w+b'
171         with codecs.open(file_path, open_mode, encoding=encoding) as file:
172             file.write(contents)
173
174     def write_output_files(self, filename, file_type,
175                            output, expected, encoding,
176                            print_text_diffs=False):
177         """Writes the test output, the expected output and optionally the diff
178         between the two to files in the results directory.
179
180         The full output filename of the actual, for example, will be
181           <filename>-actual<file_type>
182         For instance,
183           my_test-actual.txt
184
185         Args:
186           filename: The test filename
187           file_type: A string describing the test output file type, e.g. ".txt"
188           output: A string containing the test output
189           expected: A string containing the expected test output
190           print_text_diffs: True for text diffs. (FIXME: We should be able to get this from the file type?)
191         """
192         self._make_output_directory(filename)
193         actual_filename = self.output_filename(filename, self.FILENAME_SUFFIX_ACTUAL + file_type)
194         expected_filename = self.output_filename(filename, self.FILENAME_SUFFIX_EXPECTED + file_type)
195         # FIXME: This function is poorly designed.  We should be passing in some sort of
196         # encoding information from the callers.
197         if output:
198             self._write_into_file_at_path(actual_filename, output, encoding)
199         if expected:
200             self._write_into_file_at_path(expected_filename, expected, encoding)
201
202         if not output or not expected:
203             return
204
205         if not print_text_diffs:
206             return
207
208         # Note: We pass encoding=None for all diff writes, as we treat diff
209         # output as binary.  Diff output may contain multiple files in
210         # conflicting encodings.
211         diff = self._port.diff_text(expected, output, expected_filename, actual_filename)
212         diff_filename = self.output_filename(filename, self.FILENAME_SUFFIX_DIFF + file_type)
213         self._write_into_file_at_path(diff_filename, diff, encoding=None)
214
215         # Shell out to wdiff to get colored inline diffs.
216         wdiff = self._port.wdiff_text(expected_filename, actual_filename)
217         wdiff_filename = self.output_filename(filename, self.FILENAME_SUFFIX_WDIFF)
218         self._write_into_file_at_path(wdiff_filename, wdiff, encoding=None)
219
220         # Use WebKit's PrettyPatch.rb to get an HTML diff.
221         pretty_patch = self._port.pretty_patch_text(diff_filename)
222         pretty_patch_filename = self.output_filename(filename, self.FILENAME_SUFFIX_PRETTY_PATCH)
223         self._write_into_file_at_path(pretty_patch_filename, pretty_patch, encoding=None)