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