initial import
[vuplus_webkit] / Tools / Scripts / webkitpy / layout_tests / port / 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 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 """Abstract base class of Port-specific entrypoints for the layout tests
31 test infrastructure (the Port and Driver classes)."""
32
33 from __future__ import with_statement
34
35 import cgi
36 import difflib
37 import errno
38 import os
39
40 from webkitpy.common.checkout.scm import detect_scm_system
41 from webkitpy.common.memoized import memoized
42
43
44 # Handle Python < 2.6 where multiprocessing isn't available.
45 try:
46     import multiprocessing
47 except ImportError:
48     multiprocessing = None
49
50 from webkitpy.common import system
51 from webkitpy.common.system import logutils
52 from webkitpy.common.system import path
53 from webkitpy.common.system.executive import Executive, ScriptError
54 from webkitpy.common.system.user import User
55 from webkitpy.layout_tests import read_checksum_from_png
56 from webkitpy.layout_tests.models.test_configuration import TestConfiguration
57 from webkitpy.layout_tests.port import config as port_config
58 from webkitpy.layout_tests.port import http_lock
59 from webkitpy.layout_tests.port import test_files
60 from webkitpy.layout_tests.servers import apache_http_server
61 from webkitpy.layout_tests.servers import http_server
62 from webkitpy.layout_tests.servers import websocket_server
63
64 _log = logutils.get_logger(__file__)
65
66
67 class DummyOptions(object):
68     """Fake implementation of optparse.Values. Cloned from webkitpy.tool.mocktool.MockOptions."""
69
70     def __init__(self, **kwargs):
71         # The caller can set option values using keyword arguments. We don't
72         # set any values by default because we don't know how this
73         # object will be used. Generally speaking unit tests should
74         # subclass this or provider wrapper functions that set a common
75         # set of options.
76         for key, value in kwargs.items():
77             self.__dict__[key] = value
78
79
80 # FIXME: This class should merge with WebKitPort now that Chromium behaves mostly like other webkit ports.
81 class Port(object):
82     """Abstract class for Port-specific hooks for the layout_test package."""
83
84     port_name = None  # Subclasses override this
85
86     # Test names resemble unix relative paths, and use '/' as a directory separator.
87     TEST_PATH_SEPARATOR = '/'
88
89     ALL_BUILD_TYPES = ('debug', 'release')
90
91     def __init__(self, port_name=None, options=None,
92                  executive=None,
93                  user=None,
94                  filesystem=None,
95                  config=None,
96                  **kwargs):
97
98         # These are default values that should be overridden in a subclasses.
99         self._name = port_name or self.port_name  # Subclasses may append a -VERSION (like mac-leopard) or other qualifiers.
100         self._operating_system = 'mac'
101         self._version = ''
102         self._architecture = 'x86'
103         self._graphics_type = 'cpu'
104
105         # FIXME: Ideally we'd have a package-wide way to get a
106         # well-formed options object that had all of the necessary
107         # options defined on it.
108         self.options = options or DummyOptions()
109
110         self.executive = executive or Executive()
111         self.user = user or User()
112         self.filesystem = filesystem or system.filesystem.FileSystem()
113         self.config = config or port_config.Config(self.executive, self.filesystem)
114
115         # FIXME: Remove all of the old "protected" versions when we can.
116         self._options = self.options
117         self._executive = self.executive
118         self._filesystem = self.filesystem
119         self._user = self.user
120         self._config = self.config
121
122         self._helper = None
123         self._http_server = None
124         self._websocket_server = None
125         self._http_lock = None  # FIXME: Why does this live on the port object?
126
127         # Python's Popen has a bug that causes any pipes opened to a
128         # process that can't be executed to be leaked.  Since this
129         # code is specifically designed to tolerate exec failures
130         # to gracefully handle cases where wdiff is not installed,
131         # the bug results in a massive file descriptor leak. As a
132         # workaround, if an exec failure is ever experienced for
133         # wdiff, assume it's not available.  This will leak one
134         # file descriptor but that's better than leaking each time
135         # wdiff would be run.
136         #
137         # http://mail.python.org/pipermail/python-list/
138         #    2008-August/505753.html
139         # http://bugs.python.org/issue3210
140         self._wdiff_available = None
141
142         # FIXME: prettypatch.py knows this path, why is it copied here?
143         self._pretty_patch_path = self.path_from_webkit_base("Websites", "bugs.webkit.org", "PrettyPatch", "prettify.rb")
144         self._pretty_patch_available = None
145
146         self.set_option_default('configuration', self.default_configuration())
147         self._test_configuration = None
148         self._multiprocessing_is_available = (multiprocessing is not None)
149         self._results_directory = None
150
151     def wdiff_available(self):
152         if self._wdiff_available is None:
153             self._wdiff_available = self.check_wdiff(logging=False)
154         return self._wdiff_available
155
156     def pretty_patch_available(self):
157         if self._pretty_patch_available is None:
158             self._pretty_patch_available = self.check_pretty_patch(logging=False)
159         return self._pretty_patch_available
160
161     def default_child_processes(self):
162         """Return the number of DumpRenderTree instances to use for this port."""
163         return self._executive.cpu_count()
164
165     def default_worker_model(self):
166         if self._multiprocessing_is_available:
167             return 'processes'
168         return 'inline'
169
170     def baseline_path(self):
171         """Return the absolute path to the directory to store new baselines in for this port."""
172         baseline_search_paths = self.get_option('additional_platform_directory', []) + self.baseline_search_path()
173         return baseline_search_paths[0]
174
175     def baseline_search_path(self):
176         """Return a list of absolute paths to directories to search under for
177         baselines. The directories are searched in order."""
178         raise NotImplementedError('Port.baseline_search_path')
179
180     def check_build(self, needs_http):
181         """This routine is used to ensure that the build is up to date
182         and all the needed binaries are present."""
183         raise NotImplementedError('Port.check_build')
184
185     def check_sys_deps(self, needs_http):
186         """If the port needs to do some runtime checks to ensure that the
187         tests can be run successfully, it should override this routine.
188         This step can be skipped with --nocheck-sys-deps.
189
190         Returns whether the system is properly configured."""
191         if needs_http:
192             return self.check_httpd()
193         return True
194
195     def check_image_diff(self, override_step=None, logging=True):
196         """This routine is used to check whether image_diff binary exists."""
197         raise NotImplementedError('Port.check_image_diff')
198
199     def check_pretty_patch(self, logging=True):
200         """Checks whether we can use the PrettyPatch ruby script."""
201         try:
202             _ = self._executive.run_command(['ruby', '--version'])
203         except OSError, e:
204             if e.errno in [errno.ENOENT, errno.EACCES, errno.ECHILD]:
205                 if logging:
206                     _log.error("Ruby is not installed; can't generate pretty patches.")
207                     _log.error('')
208                 return False
209
210         if not self._filesystem.exists(self._pretty_patch_path):
211             if logging:
212                 _log.error("Unable to find %s; can't generate pretty patches." % self._pretty_patch_path)
213                 _log.error('')
214             return False
215
216         return True
217
218     def check_wdiff(self, logging=True):
219         if not self._path_to_wdiff():
220             # Don't need to log here since this is the port choosing not to use wdiff.
221             return False
222
223         try:
224             _ = self._executive.run_command([self._path_to_wdiff(), '--help'])
225         except OSError:
226             if logging:
227                 _log.error("wdiff is not installed.")
228             return False
229
230         return True
231
232     def check_httpd(self):
233         if self._uses_apache():
234             httpd_path = self._path_to_apache()
235         else:
236             httpd_path = self._path_to_lighttpd()
237
238         try:
239             server_name = self._filesystem.basename(httpd_path)
240             env = self.setup_environ_for_server(server_name)
241             return self._executive.run_command([httpd_path, "-v"], env=env, return_exit_code=True) == 0
242         except OSError:
243             _log.error("No httpd found. Cannot run http tests.")
244             return False
245
246     def compare_text(self, expected_text, actual_text):
247         """Return whether or not the two strings are *not* equal. This
248         routine is used to diff text output.
249
250         While this is a generic routine, we include it in the Port
251         interface so that it can be overriden for testing purposes."""
252         return expected_text != actual_text
253
254     def compare_audio(self, expected_audio, actual_audio):
255         # FIXME: If we give this method a better name it won't need this docstring (e.g. are_audio_results_equal()).
256         """Return whether the two audio files are *not* equal."""
257         return expected_audio != actual_audio
258
259     def diff_image(self, expected_contents, actual_contents, tolerance=0):
260         """Compare two images and return an image diff.
261
262         |tolerance| should be a percentage value (0.0 - 100.0).
263         If it is omitted, the port default tolerance value is used.
264         """
265         raise NotImplementedError('Port.diff_image')
266
267
268     def diff_text(self, expected_text, actual_text,
269                   expected_filename, actual_filename):
270         """Returns a string containing the diff of the two text strings
271         in 'unified diff' format.
272
273         While this is a generic routine, we include it in the Port
274         interface so that it can be overriden for testing purposes."""
275
276         # The filenames show up in the diff output, make sure they're
277         # raw bytes and not unicode, so that they don't trigger join()
278         # trying to decode the input.
279         def to_raw_bytes(string_value):
280             if isinstance(string_value, unicode):
281                 return string_value.encode('utf-8')
282             return string_value
283         expected_filename = to_raw_bytes(expected_filename)
284         actual_filename = to_raw_bytes(actual_filename)
285         diff = difflib.unified_diff(expected_text.splitlines(True),
286                                     actual_text.splitlines(True),
287                                     expected_filename,
288                                     actual_filename)
289         return ''.join(diff)
290
291     def is_crash_reporter(self, process_name):
292         return False
293
294     def check_for_leaks(self, process_name, process_pid):
295         # Subclasses should check for leaks in the running process
296         # and print any necessary warnings if leaks are found.
297         # FIXME: We should consider moving much of this logic into
298         # Executive and make it platform-specific instead of port-specific.
299         pass
300
301     def print_leaks_summary(self):
302         # Subclasses can override this to print a summary of leaks found
303         # while running the layout tests.
304         pass
305
306     def driver_name(self):
307         """Returns the name of the actual binary that is performing the test,
308         so that it can be referred to in log messages. In most cases this
309         will be DumpRenderTree, but if a port uses a binary with a different
310         name, it can be overridden here."""
311         return "DumpRenderTree"
312
313     def expected_baselines(self, test_name, suffix, all_baselines=False):
314         """Given a test name, finds where the baseline results are located.
315
316         Args:
317         test_name: name of test file (usually a relative path under LayoutTests/)
318         suffix: file suffix of the expected results, including dot; e.g.
319             '.txt' or '.png'.  This should not be None, but may be an empty
320             string.
321         all_baselines: If True, return an ordered list of all baseline paths
322             for the given platform. If False, return only the first one.
323         Returns
324         a list of ( platform_dir, results_filename ), where
325             platform_dir - abs path to the top of the results tree (or test
326                 tree)
327             results_filename - relative path from top of tree to the results
328                 file
329             (port.join() of the two gives you the full path to the file,
330                 unless None was returned.)
331         Return values will be in the format appropriate for the current
332         platform (e.g., "\\" for path separators on Windows). If the results
333         file is not found, then None will be returned for the directory,
334         but the expected relative pathname will still be returned.
335
336         This routine is generic but lives here since it is used in
337         conjunction with the other baseline and filename routines that are
338         platform specific.
339         """
340         baseline_filename = self._filesystem.splitext(test_name)[0] + '-expected' + suffix
341         baseline_search_path = self.get_option('additional_platform_directory', []) + self.baseline_search_path()
342
343         baselines = []
344         for platform_dir in baseline_search_path:
345             if self._filesystem.exists(self._filesystem.join(platform_dir, baseline_filename)):
346                 baselines.append((platform_dir, baseline_filename))
347
348             if not all_baselines and baselines:
349                 return baselines
350
351         # If it wasn't found in a platform directory, return the expected
352         # result in the test directory, even if no such file actually exists.
353         platform_dir = self.layout_tests_dir()
354         if self._filesystem.exists(self._filesystem.join(platform_dir, baseline_filename)):
355             baselines.append((platform_dir, baseline_filename))
356
357         if baselines:
358             return baselines
359
360         return [(None, baseline_filename)]
361
362     def expected_filename(self, test_name, suffix):
363         """Given a test name, returns an absolute path to its expected results.
364
365         If no expected results are found in any of the searched directories,
366         the directory in which the test itself is located will be returned.
367         The return value is in the format appropriate for the platform
368         (e.g., "\\" for path separators on windows).
369
370         Args:
371         test_name: name of test file (usually a relative path under LayoutTests/)
372         suffix: file suffix of the expected results, including dot; e.g. '.txt'
373             or '.png'.  This should not be None, but may be an empty string.
374         platform: the most-specific directory name to use to build the
375             search list of directories, e.g., 'chromium-win', or
376             'chromium-cg-mac-leopard' (we follow the WebKit format)
377
378         This routine is generic but is implemented here to live alongside
379         the other baseline and filename manipulation routines.
380         """
381         # FIXME: The [0] here is very mysterious, as is the destructured return.
382         platform_dir, baseline_filename = self.expected_baselines(test_name, suffix)[0]
383         if platform_dir:
384             return self._filesystem.join(platform_dir, baseline_filename)
385         return self._filesystem.join(self.layout_tests_dir(), baseline_filename)
386
387     def expected_checksum(self, test_name):
388         """Returns the checksum of the image we expect the test to produce, or None if it is a text-only test."""
389         png_path = self.expected_filename(test_name, '.png')
390
391         if self._filesystem.exists(png_path):
392             with self._filesystem.open_binary_file_for_reading(png_path) as filehandle:
393                 return read_checksum_from_png.read_checksum(filehandle)
394
395         return None
396
397     def expected_image(self, test_name):
398         """Returns the image we expect the test to produce."""
399         baseline_path = self.expected_filename(test_name, '.png')
400         if not self._filesystem.exists(baseline_path):
401             return None
402         return self._filesystem.read_binary_file(baseline_path)
403
404     def expected_audio(self, test_name):
405         baseline_path = self.expected_filename(test_name, '.wav')
406         if not self._filesystem.exists(baseline_path):
407             return None
408         return self._filesystem.read_binary_file(baseline_path)
409
410     def expected_text(self, test_name):
411         """Returns the text output we expect the test to produce, or None
412         if we don't expect there to be any text output.
413         End-of-line characters are normalized to '\n'."""
414         # FIXME: DRT output is actually utf-8, but since we don't decode the
415         # output from DRT (instead treating it as a binary string), we read the
416         # baselines as a binary string, too.
417         baseline_path = self.expected_filename(test_name, '.txt')
418         if not self._filesystem.exists(baseline_path):
419             baseline_path = self.expected_filename(test_name, '.webarchive')
420             if not self._filesystem.exists(baseline_path):
421                 return None
422         text = self._filesystem.read_binary_file(baseline_path)
423         return text.replace("\r\n", "\n")
424
425     def reftest_expected_filename(self, test_name):
426         """Return the filename of reference we expect the test matches."""
427         return self.expected_filename(test_name, '.html')
428
429     def reftest_expected_mismatch_filename(self, test_name):
430         """Return the filename of reference we don't expect the test matches."""
431         return self.expected_filename(test_name, '-mismatch.html')
432
433     def test_to_uri(self, test_name):
434         """Convert a test name to a URI."""
435         LAYOUTTEST_HTTP_DIR = "http/tests/"
436         LAYOUTTEST_WEBSOCKET_DIR = "http/tests/websocket/tests/"
437
438         port = None
439
440         relative_path = test_name
441         if (relative_path.startswith(LAYOUTTEST_WEBSOCKET_DIR)
442             or relative_path.startswith(LAYOUTTEST_HTTP_DIR)):
443             relative_path = relative_path[len(LAYOUTTEST_HTTP_DIR):]
444             port = 8000
445
446         # Make http/tests/local run as local files. This is to mimic the
447         # logic in run-webkit-tests.
448         #
449         # TODO(dpranke): remove the SSL reference?
450         if (port and not relative_path.startswith("local/")):
451             if relative_path.startswith("ssl/"):
452                 port += 443
453                 protocol = "https"
454             else:
455                 protocol = "http"
456             return "%s://127.0.0.1:%u/%s" % (protocol, port, relative_path)
457
458         return path.abspath_to_uri(self.abspath_for_test(test_name))
459
460     def tests(self, paths):
461         """Return the list of tests found."""
462         # FIXME: Should test_files.find() return normalized, relative test names?
463         return set([self.relative_test_filename(f) for f in test_files.find(self, paths)])
464
465     def test_dirs(self):
466         """Returns the list of top-level test directories."""
467         layout_tests_dir = self.layout_tests_dir()
468         return filter(lambda x: self._filesystem.isdir(self._filesystem.join(layout_tests_dir, x)),
469                       self._filesystem.listdir(layout_tests_dir))
470
471     def test_isdir(self, test_name):
472         """Return True if the test name refers to a directory of tests."""
473         # Used by test_expectations.py to apply rules to whole directories.
474         test_path = self.abspath_for_test(test_name)
475         return self._filesystem.isdir(test_path)
476
477     def test_exists(self, test_name):
478         """Return True if the test name refers to an existing test or baseline."""
479         # Used by test_expectations.py to determine if an entry refers to a
480         # valid test and by printing.py to determine if baselines exist.
481         test_path = self.abspath_for_test(test_name)
482         return self._filesystem.exists(test_path)
483
484     def split_test(self, test_name):
485         """Splits a test name into the 'directory' part and the 'basename' part."""
486         index = test_name.rfind(self.TEST_PATH_SEPARATOR)
487         if index < 1:
488             return ('', test_name)
489         return (test_name[0:index], test_name[index:])
490
491     def normalize_test_name(self, test_name):
492         """Returns a normalized version of the test name or test directory."""
493         if self.test_isdir(test_name) and not test_name.endswith('/'):
494             return test_name + '/'
495         return test_name
496
497     def driver_cmd_line(self):
498         """Prints the DRT command line that will be used."""
499         driver = self.create_driver(0)
500         return driver.cmd_line()
501
502     def update_baseline(self, baseline_path, data):
503         """Updates the baseline for a test.
504
505         Args:
506             baseline_path: the actual path to use for baseline, not the path to
507               the test. This function is used to update either generic or
508               platform-specific baselines, but we can't infer which here.
509             data: contents of the baseline.
510         """
511         self._filesystem.write_binary_file(baseline_path, data)
512
513     def uri_to_test_name(self, uri):
514         """Return the base layout test name for a given URI.
515
516         This returns the test name for a given URI, e.g., if you passed in
517         "file:///src/LayoutTests/fast/html/keygen.html" it would return
518         "fast/html/keygen.html".
519
520         """
521         test = uri
522         if uri.startswith("file:///"):
523             prefix = path.abspath_to_uri(self.layout_tests_dir()) + "/"
524             return test[len(prefix):]
525
526         if uri.startswith("http://127.0.0.1:8880/"):
527             # websocket tests
528             return test.replace('http://127.0.0.1:8880/', '')
529
530         if uri.startswith("http://"):
531             # regular HTTP test
532             return test.replace('http://127.0.0.1:8000/', 'http/tests/')
533
534         if uri.startswith("https://"):
535             return test.replace('https://127.0.0.1:8443/', 'http/tests/')
536
537         raise NotImplementedError('unknown url type: %s' % uri)
538
539     def layout_tests_dir(self):
540         """Return the absolute path to the top of the LayoutTests directory."""
541         return self.path_from_webkit_base('LayoutTests')
542
543     def webkit_base(self):
544         return self._filesystem.abspath(self.path_from_webkit_base('.'))
545
546     def skipped_layout_tests(self):
547         return []
548
549     def skips_layout_test(self, test_name):
550         """Figures out if the givent test is being skipped or not.
551
552         Test categories are handled as well."""
553         for test_or_category in self.skipped_layout_tests():
554             if test_or_category == test_name:
555                 return True
556             category = self._filesystem.join(self.layout_tests_dir(),
557                                              test_or_category)
558             if (self._filesystem.isdir(category) and
559                 test_name.startswith(test_or_category)):
560                 return True
561         return False
562
563     def maybe_make_directory(self, *comps):
564         """Creates the specified directory if it doesn't already exist."""
565         self._filesystem.maybe_make_directory(*comps)
566
567     def name(self):
568         """Returns a name that uniquely identifies this particular type of port
569         (e.g., "mac-snowleopard" or "chromium-gpu-linux-x86_x64" and can be passed
570         to factory.get() to instantiate the port."""
571         return self._name
572
573     def real_name(self):
574         # FIXME: Seems this is only used for MockDRT and should be removed.
575         """Returns the name of the port as passed to the --platform command line argument."""
576         return self.name()
577
578     def operating_system(self):
579         return self._operating_system
580
581     def version(self):
582         """Returns a string indicating the version of a given platform, e.g.
583         'leopard' or 'xp'.
584
585         This is used to help identify the exact port when parsing test
586         expectations, determining search paths, and logging information."""
587         return self._version
588
589     def graphics_type(self):
590         """Returns whether the port uses accelerated graphics ('gpu') or not ('cpu')."""
591         return self._graphics_type
592
593     def architecture(self):
594         return self._architecture
595
596     def get_option(self, name, default_value=None):
597         # FIXME: Eventually we should not have to do a test for
598         # hasattr(), and we should be able to just do
599         # self.options.value. See additional FIXME in the constructor.
600         if hasattr(self._options, name):
601             return getattr(self._options, name)
602         return default_value
603
604     def set_option_default(self, name, default_value):
605         # FIXME: Callers could also use optparse_parser.Values.ensure_value,
606         # since this should always be a optparse_parser.Values object.
607         if not hasattr(self._options, name) or getattr(self._options, name) is None:
608             return setattr(self._options, name, default_value)
609
610     def path_from_webkit_base(self, *comps):
611         """Returns the full path to path made by joining the top of the
612         WebKit source tree and the list of path components in |*comps|."""
613         return self._config.path_from_webkit_base(*comps)
614
615     def path_to_test_expectations_file(self):
616         """Update the test expectations to the passed-in string.
617
618         This is used by the rebaselining tool. Raises NotImplementedError
619         if the port does not use expectations files."""
620         raise NotImplementedError('Port.path_to_test_expectations_file')
621
622     def relative_test_filename(self, filename):
623         """Returns a test_name a realtive unix-style path for a filename under the LayoutTests
624         directory. Filenames outside the LayoutTests directory should raise
625         an error."""
626         # Ports that run on windows need to override this method to deal with
627         # filenames with backslashes in them.
628         assert filename.startswith(self.layout_tests_dir()), "%s did not start with %s" % (filename, self.layout_tests_dir())
629         return filename[len(self.layout_tests_dir()) + 1:]
630
631     def abspath_for_test(self, test_name):
632         """Returns the full path to the file for a given test name. This is the
633         inverse of relative_test_filename()."""
634         return self._filesystem.normpath(self._filesystem.join(self.layout_tests_dir(), test_name))
635
636     def results_directory(self):
637         """Absolute path to the place to store the test results (uses --results-directory)."""
638         if not self._results_directory:
639             option_val = self.get_option('results_directory') or self.default_results_directory()
640             self._results_directory = self._filesystem.abspath(option_val)
641         return self._results_directory
642
643     def default_results_directory(self):
644         """Absolute path to the default place to store the test results."""
645         raise NotImplementedError()
646
647     def setup_test_run(self):
648         """Perform port-specific work at the beginning of a test run."""
649         pass
650
651     def setup_environ_for_server(self, server_name=None):
652         """Perform port-specific work at the beginning of a server launch.
653
654         Returns:
655            Operating-system's environment.
656         """
657         return os.environ.copy()
658
659     def show_results_html_file(self, results_filename):
660         """This routine should display the HTML file pointed at by
661         results_filename in a users' browser."""
662         return self._user.open_url(results_filename)
663
664     def create_driver(self, worker_number):
665         """Return a newly created Driver subclass for starting/stopping the test driver."""
666         raise NotImplementedError('Port.create_driver')
667
668     def start_helper(self):
669         """If a port needs to reconfigure graphics settings or do other
670         things to ensure a known test configuration, it should override this
671         method."""
672         pass
673
674     def start_http_server(self):
675         """Start a web server. Raise an error if it can't start or is already running.
676
677         Ports can stub this out if they don't need a web server to be running."""
678         assert not self._http_server, 'Already running an http server.'
679
680         if self._uses_apache():
681             server = apache_http_server.LayoutTestApacheHttpd(self, self.results_directory())
682         else:
683             server = http_server.Lighttpd(self, self.results_directory())
684
685         server.start()
686         self._http_server = server
687
688     def start_websocket_server(self):
689         """Start a web server. Raise an error if it can't start or is already running.
690
691         Ports can stub this out if they don't need a websocket server to be running."""
692         assert not self._websocket_server, 'Already running a websocket server.'
693
694         server = websocket_server.PyWebSocket(self, self.results_directory())
695         server.start()
696         self._websocket_server = server
697
698     def acquire_http_lock(self):
699         self._http_lock = http_lock.HttpLock(None, filesystem=self._filesystem, executive=self._executive)
700         self._http_lock.wait_for_httpd_lock()
701
702     def stop_helper(self):
703         """Shut down the test helper if it is running. Do nothing if
704         it isn't, or it isn't available. If a port overrides start_helper()
705         it must override this routine as well."""
706         pass
707
708     def stop_http_server(self):
709         """Shut down the http server if it is running. Do nothing if it isn't."""
710         if self._http_server:
711             self._http_server.stop()
712             self._http_server = None
713
714     def stop_websocket_server(self):
715         """Shut down the websocket server if it is running. Do nothing if it isn't."""
716         if self._websocket_server:
717             self._websocket_server.stop()
718             self._websocket_server = None
719
720     def release_http_lock(self):
721         if self._http_lock:
722             self._http_lock.cleanup_http_lock()
723
724     #
725     # TEST EXPECTATION-RELATED METHODS
726     #
727
728     def test_configuration(self):
729         """Returns the current TestConfiguration for the port."""
730         if not self._test_configuration:
731             self._test_configuration = TestConfiguration.from_port(self)
732         return self._test_configuration
733
734     # FIXME: Belongs on a Platform object.
735     @memoized
736     def all_test_configurations(self):
737         """Returns a list of TestConfiguration instances, representing all available
738         test configurations for this port."""
739         return self._generate_all_test_configurations()
740
741     # FIXME: Belongs on a Platform object.
742     def configuration_specifier_macros(self):
743         """Ports may provide a way to abbreviate configuration specifiers to conveniently
744         refer to them as one term or alias specific values to more generic ones. For example:
745
746         (xp, vista, win7) -> win # Abbreviate all Windows versions into one namesake.
747         (lucid) -> linux  # Change specific name of the Linux distro to a more generic term.
748
749         Returns a dictionary, each key representing a macro term ('win', for example),
750         and value being a list of valid configuration specifiers (such as ['xp', 'vista', 'win7'])."""
751         return {}
752
753     def all_baseline_variants(self):
754         """Returns a list of platform names sufficient to cover all the baselines.
755
756         The list should be sorted so that a later platform  will reuse
757         an earlier platform's baselines if they are the same (e.g.,
758         'snowleopard' should precede 'leopard')."""
759         raise NotImplementedError
760
761     def uses_test_expectations_file(self):
762         # This is different from checking test_expectations() is None, because
763         # some ports have Skipped files which are returned as part of test_expectations().
764         return self._filesystem.exists(self.path_to_test_expectations_file())
765
766     def test_expectations(self):
767         """Returns the test expectations for this port.
768
769         Basically this string should contain the equivalent of a
770         test_expectations file. See test_expectations.py for more details."""
771         return self._filesystem.read_text_file(self.path_to_test_expectations_file())
772
773     def test_expectations_overrides(self):
774         """Returns an optional set of overrides for the test_expectations.
775
776         This is used by ports that have code in two repositories, and where
777         it is possible that you might need "downstream" expectations that
778         temporarily override the "upstream" expectations until the port can
779         sync up the two repos."""
780         return None
781
782     def test_repository_paths(self):
783         """Returns a list of (repository_name, repository_path) tuples
784         of its depending code base.  By default it returns a list that only
785         contains a ('webkit', <webkitRepossitoryPath>) tuple.
786         """
787         return [('webkit', self.layout_tests_dir())]
788
789
790     _WDIFF_DEL = '##WDIFF_DEL##'
791     _WDIFF_ADD = '##WDIFF_ADD##'
792     _WDIFF_END = '##WDIFF_END##'
793
794     def _format_wdiff_output_as_html(self, wdiff):
795         wdiff = cgi.escape(wdiff)
796         wdiff = wdiff.replace(self._WDIFF_DEL, "<span class=del>")
797         wdiff = wdiff.replace(self._WDIFF_ADD, "<span class=add>")
798         wdiff = wdiff.replace(self._WDIFF_END, "</span>")
799         html = "<head><style>.del { background: #faa; } "
800         html += ".add { background: #afa; }</style></head>"
801         html += "<pre>%s</pre>" % wdiff
802         return html
803
804     def _wdiff_command(self, actual_filename, expected_filename):
805         executable = self._path_to_wdiff()
806         return [executable,
807                 "--start-delete=%s" % self._WDIFF_DEL,
808                 "--end-delete=%s" % self._WDIFF_END,
809                 "--start-insert=%s" % self._WDIFF_ADD,
810                 "--end-insert=%s" % self._WDIFF_END,
811                 actual_filename,
812                 expected_filename]
813
814     @staticmethod
815     def _handle_wdiff_error(script_error):
816         # Exit 1 means the files differed, any other exit code is an error.
817         if script_error.exit_code != 1:
818             raise script_error
819
820     def _run_wdiff(self, actual_filename, expected_filename):
821         """Runs wdiff and may throw exceptions.
822         This is mostly a hook for unit testing."""
823         # Diffs are treated as binary as they may include multiple files
824         # with conflicting encodings.  Thus we do not decode the output.
825         command = self._wdiff_command(actual_filename, expected_filename)
826         wdiff = self._executive.run_command(command, decode_output=False,
827             error_handler=self._handle_wdiff_error)
828         return self._format_wdiff_output_as_html(wdiff)
829
830     def wdiff_text(self, actual_filename, expected_filename):
831         """Returns a string of HTML indicating the word-level diff of the
832         contents of the two filenames. Returns an empty string if word-level
833         diffing isn't available."""
834         if not self._wdiff_available:
835             return ""
836         try:
837             # It's possible to raise a ScriptError we pass wdiff invalid paths.
838             return self._run_wdiff(actual_filename, expected_filename)
839         except OSError, e:
840             if e.errno in [errno.ENOENT, errno.EACCES, errno.ECHILD]:
841                 # Silently ignore cases where wdiff is missing.
842                 self._wdiff_available = False
843                 return ""
844             raise
845
846     # This is a class variable so we can test error output easily.
847     _pretty_patch_error_html = "Failed to run PrettyPatch, see error log."
848
849     def pretty_patch_text(self, diff_path):
850         if self._pretty_patch_available is None:
851             self._pretty_patch_available = self.check_pretty_patch(logging=False)
852         if not self._pretty_patch_available:
853             return self._pretty_patch_error_html
854         command = ("ruby", "-I", self._filesystem.dirname(self._pretty_patch_path),
855                    self._pretty_patch_path, diff_path)
856         try:
857             # Diffs are treated as binary (we pass decode_output=False) as they
858             # may contain multiple files of conflicting encodings.
859             return self._executive.run_command(command, decode_output=False)
860         except OSError, e:
861             # If the system is missing ruby log the error and stop trying.
862             self._pretty_patch_available = False
863             _log.error("Failed to run PrettyPatch (%s): %s" % (command, e))
864             return self._pretty_patch_error_html
865         except ScriptError, e:
866             # If ruby failed to run for some reason, log the command
867             # output and stop trying.
868             self._pretty_patch_available = False
869             _log.error("Failed to run PrettyPatch (%s):\n%s" % (command, e.message_with_output()))
870             return self._pretty_patch_error_html
871
872     def default_configuration(self):
873         return self._config.default_configuration()
874
875     #
876     # PROTECTED ROUTINES
877     #
878     # The routines below should only be called by routines in this class
879     # or any of its subclasses.
880     #
881     def _webkit_build_directory(self, args):
882         return self._config.build_directory(args[0])
883
884     def _uses_apache(self):
885         return True
886
887     def _path_to_apache(self):
888         """Returns the full path to the apache binary.
889
890         This is needed only by ports that use the apache_http_server module."""
891         raise NotImplementedError('Port.path_to_apache')
892
893     def _path_to_apache_config_file(self):
894         """Returns the full path to the apache binary.
895
896         This is needed only by ports that use the apache_http_server module."""
897         raise NotImplementedError('Port.path_to_apache_config_file')
898
899     def _path_to_driver(self, configuration=None):
900         """Returns the full path to the test driver (DumpRenderTree)."""
901         raise NotImplementedError('Port._path_to_driver')
902
903     def _path_to_webcore_library(self):
904         """Returns the full path to a built copy of WebCore."""
905         raise NotImplementedError('Port.path_to_webcore_library')
906
907     def _path_to_helper(self):
908         """Returns the full path to the layout_test_helper binary, which
909         is used to help configure the system for the test run, or None
910         if no helper is needed.
911
912         This is likely only used by start/stop_helper()."""
913         raise NotImplementedError('Port._path_to_helper')
914
915     def _path_to_image_diff(self):
916         """Returns the full path to the image_diff binary, or None if it is not available.
917
918         This is likely used only by diff_image()"""
919         raise NotImplementedError('Port.path_to_image_diff')
920
921     def _path_to_lighttpd(self):
922         """Returns the path to the LigHTTPd binary.
923
924         This is needed only by ports that use the http_server.py module."""
925         raise NotImplementedError('Port._path_to_lighttpd')
926
927     def _path_to_lighttpd_modules(self):
928         """Returns the path to the LigHTTPd modules directory.
929
930         This is needed only by ports that use the http_server.py module."""
931         raise NotImplementedError('Port._path_to_lighttpd_modules')
932
933     def _path_to_lighttpd_php(self):
934         """Returns the path to the LigHTTPd PHP executable.
935
936         This is needed only by ports that use the http_server.py module."""
937         raise NotImplementedError('Port._path_to_lighttpd_php')
938
939     def _path_to_wdiff(self):
940         """Returns the full path to the wdiff binary, or None if it is not available.
941
942         This is likely used only by wdiff_text()"""
943         raise NotImplementedError('Port._path_to_wdiff')
944
945     def _webkit_baseline_path(self, platform):
946         """Return the  full path to the top of the baseline tree for a
947         given platform."""
948         return self._filesystem.join(self.layout_tests_dir(), 'platform', platform)
949
950     # FIXME: Belongs on a Platform object.
951     def _generate_all_test_configurations(self):
952         """Generates a list of TestConfiguration instances, representing configurations
953         for a platform across all OSes, architectures, build and graphics types."""
954         raise NotImplementedError('Port._generate_test_configurations')