2010-11-17 Hayato Ito <hayato@chromium.org>
[WebKit-https.git] / WebKitTools / Scripts / webkitpy / layout_tests / port / test.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 Google name 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 """Dummy Port implementation used for testing."""
31 from __future__ import with_statement
32
33 import codecs
34 import fnmatch
35 import os
36 import sys
37 import time
38
39 from webkitpy.layout_tests.layout_package import test_output
40
41 import base
42
43
44 # This sets basic expectations for a test. Each individual expectation
45 # can be overridden by a keyword argument in TestList.add().
46 class TestInstance:
47     def __init__(self, name):
48         self.name = name
49         self.base = name[(name.rfind("/") + 1):name.rfind(".html")]
50         self.crash = False
51         self.exception = False
52         self.hang = False
53         self.keyboard = False
54         self.error = ''
55         self.timeout = False
56         self.actual_text = self.base + '-txt\n'
57         self.actual_checksum = self.base + '-checksum\n'
58         self.actual_image = self.base + '-png\n'
59         self.expected_text = self.actual_text
60         self.expected_checksum = self.actual_checksum
61         self.expected_image = self.actual_image
62
63
64 # This is an in-memory list of tests, what we want them to produce, and
65 # what we want to claim are the expected results.
66 class TestList:
67     def __init__(self, port):
68         self.port = port
69         self.tests = {}
70
71     def add(self, name, **kwargs):
72         test = TestInstance(name)
73         for key, value in kwargs.items():
74             test.__dict__[key] = value
75         self.tests[name] = test
76
77     def keys(self):
78         return self.tests.keys()
79
80     def __contains__(self, item):
81         return item in self.tests
82
83     def __getitem__(self, item):
84         return self.tests[item]
85
86
87 class TestPort(base.Port):
88     """Test implementation of the Port interface."""
89
90     def __init__(self, **kwargs):
91         base.Port.__init__(self, **kwargs)
92         tests = TestList(self)
93         tests.add('passes/image.html')
94         tests.add('passes/text.html')
95         tests.add('failures/expected/checksum.html',
96                   actual_checksum='checksum_fail-checksum')
97         tests.add('failures/expected/crash.html', crash=True)
98         tests.add('failures/expected/exception.html', exception=True)
99         tests.add('failures/expected/timeout.html', timeout=True)
100         tests.add('failures/expected/hang.html', hang=True)
101         tests.add('failures/expected/missing_text.html',
102                   expected_text=None)
103         tests.add('failures/expected/image.html',
104                   actual_image='image_fail-png',
105                   expected_image='image-png')
106         tests.add('failures/expected/image_checksum.html',
107                   actual_checksum='image_checksum_fail-checksum',
108                   actual_image='image_checksum_fail-png')
109         tests.add('failures/expected/keyboard.html',
110                   keyboard=True)
111         tests.add('failures/expected/missing_check.html',
112                   expected_checksum=None)
113         tests.add('failures/expected/missing_image.html',
114                   expected_image=None)
115         tests.add('failures/expected/missing_text.html',
116                   expected_text=None)
117         tests.add('failures/expected/text.html',
118                   actual_text='text_fail-png')
119         tests.add('failures/unexpected/text-image-checksum.html',
120                   actual_text='text-image-checksum_fail-txt',
121                   actual_checksum='text-image-checksum_fail-checksum')
122         tests.add('http/tests/passes/text.html')
123         tests.add('http/tests/ssl/text.html')
124         tests.add('passes/error.html', error='stuff going to stderr')
125         tests.add('passes/image.html')
126         tests.add('passes/platform_image.html')
127         tests.add('passes/text.html')
128         tests.add('websocket/tests/passes/text.html')
129         self._tests = tests
130
131     def baseline_path(self):
132         return os.path.join(self.layout_tests_dir(), 'platform',
133                             self.name() + self.version())
134
135     def baseline_search_path(self):
136         return [self.baseline_path()]
137
138     def check_build(self, needs_http):
139         return True
140
141     def diff_image(self, expected_contents, actual_contents,
142                    diff_filename=None):
143         diffed = actual_contents != expected_contents
144         if diffed and diff_filename:
145             with codecs.open(diff_filename, "w", "utf-8") as diff_fh:
146                 diff_fh.write("< %s\n---\n> %s\n" %
147                               (expected_contents, actual_contents))
148         return diffed
149
150     def expected_checksum(self, test):
151         test = self.relative_test_filename(test)
152         return self._tests[test].expected_checksum
153
154     def expected_image(self, test):
155         test = self.relative_test_filename(test)
156         return self._tests[test].expected_image
157
158     def expected_text(self, test):
159         test = self.relative_test_filename(test)
160         text = self._tests[test].expected_text
161         if not text:
162             text = ''
163         return text
164
165     def tests(self, paths):
166         # Test the idea of port-specific overrides for test lists. Also
167         # keep in memory to speed up the test harness.
168         if not paths:
169             paths = ['*']
170
171         matched_tests = []
172         for p in paths:
173             if self.path_isdir(p):
174                 matched_tests.extend(fnmatch.filter(self._tests.keys(), p + '*'))
175             else:
176                 matched_tests.extend(fnmatch.filter(self._tests.keys(), p))
177         layout_tests_dir = self.layout_tests_dir()
178         return set([os.path.join(layout_tests_dir, p) for p in matched_tests])
179
180     def path_exists(self, path):
181         # used by test_expectations.py and printing.py
182         rpath = self.relative_test_filename(path)
183         if rpath in self._tests:
184             return True
185         if self.path_isdir(rpath):
186             return True
187         if rpath.endswith('-expected.txt'):
188             test = rpath.replace('-expected.txt', '.html')
189             return (test in self._tests and
190                     self._tests[test].expected_text)
191         if rpath.endswith('-expected.checksum'):
192             test = rpath.replace('-expected.checksum', '.html')
193             return (test in self._tests and
194                     self._tests[test].expected_checksum)
195         if rpath.endswith('-expected.png'):
196             test = rpath.replace('-expected.png', '.html')
197             return (test in self._tests and
198                     self._tests[test].expected_image)
199         return False
200
201     def layout_tests_dir(self):
202         return self.path_from_webkit_base('WebKitTools', 'Scripts',
203                                           'webkitpy', 'layout_tests', 'data')
204
205     def path_isdir(self, path):
206         # Used by test_expectations.py
207         #
208         # We assume that a path is a directory if we have any tests that
209         # whose prefix matches the path plus a directory modifier.
210         if path[-1] != '/':
211             path += '/'
212         return any([t.startswith(path) for t in self._tests.keys()])
213
214     def test_dirs(self):
215         return ['passes', 'failures']
216
217     def name(self):
218         return self._name
219
220     def _path_to_wdiff(self):
221         return None
222
223     def results_directory(self):
224         return '/tmp/' + self.get_option('results_directory')
225
226     def setup_test_run(self):
227         pass
228
229     def create_driver(self, image_path, options):
230         return TestDriver(self, image_path, options, executive=None)
231
232     def start_http_server(self):
233         pass
234
235     def start_websocket_server(self):
236         pass
237
238     def stop_http_server(self):
239         pass
240
241     def stop_websocket_server(self):
242         pass
243
244     def test_expectations(self):
245         """Returns the test expectations for this port.
246
247         Basically this string should contain the equivalent of a
248         test_expectations file. See test_expectations.py for more details."""
249         return """
250 WONTFIX : failures/expected/checksum.html = IMAGE
251 WONTFIX : failures/expected/crash.html = CRASH
252 // This one actually passes because the checksums will match.
253 WONTFIX : failures/expected/image.html = PASS
254 WONTFIX : failures/expected/image_checksum.html = IMAGE
255 WONTFIX : failures/expected/missing_check.html = MISSING PASS
256 WONTFIX : failures/expected/missing_image.html = MISSING PASS
257 WONTFIX : failures/expected/missing_text.html = MISSING PASS
258 WONTFIX : failures/expected/text.html = TEXT
259 WONTFIX : failures/expected/timeout.html = TIMEOUT
260 WONTFIX SKIP : failures/expected/hang.html = TIMEOUT
261 WONTFIX SKIP : failures/expected/keyboard.html = CRASH
262 WONTFIX SKIP : failures/expected/exception.html = CRASH
263 """
264
265     def test_base_platform_names(self):
266         return ('mac', 'win')
267
268     def test_platform_name(self):
269         return 'mac'
270
271     def test_platform_names(self):
272         return self.test_base_platform_names()
273
274     def test_platform_name_to_name(self, test_platform_name):
275         return test_platform_name
276
277     def version(self):
278         return ''
279
280
281 class TestDriver(base.Driver):
282     """Test/Dummy implementation of the DumpRenderTree interface."""
283
284     def __init__(self, port, image_path, options, executive):
285         self._port = port
286         self._image_path = image_path
287         self._executive = executive
288         self._image_written = False
289
290     def poll(self):
291         return True
292
293     def run_test(self, uri, timeoutms, image_hash):
294         start_time = time.time()
295         test_name = self._port.uri_to_test_name(uri)
296         test = self._port._tests[test_name]
297         if test.keyboard:
298             raise KeyboardInterrupt
299         if test.exception:
300             raise ValueError('exception from ' + test_name)
301         if test.hang:
302             time.sleep((float(timeoutms) * 4) / 1000.0)
303         return test_output.TestOutput(test.actual_text, test.actual_image,
304                                       test.actual_checksum, test.crash,
305                                       time.time() - start_time, test.timeout,
306                                       test.error)
307
308     def start(self):
309         pass
310
311     def stop(self):
312         pass