7abd3e064536223f410f38e719ba26bfde53acb4
[WebKit-https.git] / Tools / Scripts / webkitpy / port / base.py
1 # Copyright (C) 2010 Google Inc. All rights reserved.
2 # Copyright (C) 2013 Apple 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 entry points for the layout tests
31 test infrastructure (the Port and Driver classes)."""
32
33 import cgi
34 import difflib
35 import errno
36 import itertools
37 import logging
38 import os
39 import operator
40 import optparse
41 import re
42 import sys
43
44 try:
45     from collections import OrderedDict
46 except ImportError:
47     # Needed for Python < 2.7
48     from webkitpy.thirdparty.ordered_dict import OrderedDict
49
50
51 from webkitpy.common import find_files
52 from webkitpy.common import read_checksum_from_png
53 from webkitpy.common.memoized import memoized
54 from webkitpy.common.system import path
55 from webkitpy.common.system.executive import ScriptError
56 from webkitpy.common.system.systemhost import SystemHost
57 from webkitpy.common.webkit_finder import WebKitFinder
58 from webkitpy.layout_tests.models.test_configuration import TestConfiguration
59 from webkitpy.port import config as port_config
60 from webkitpy.port import driver
61 from webkitpy.port import http_lock
62 from webkitpy.port import image_diff
63 from webkitpy.port import server_process
64 from webkitpy.port.factory import PortFactory
65 from webkitpy.layout_tests.servers import apache_http_server
66 from webkitpy.layout_tests.servers import http_server
67 from webkitpy.layout_tests.servers import websocket_server
68
69 _log = logging.getLogger(__name__)
70
71
72 class Port(object):
73     """Abstract class for Port-specific hooks for the layout_test package."""
74
75     # Subclasses override this. This should indicate the basic implementation
76     # part of the port name, e.g., 'win', 'gtk'; there is probably (?) one unique value per class.
77
78     # FIXME: We should probably rename this to something like 'implementation_name'.
79     port_name = None
80
81     # Test names resemble unix relative paths, and use '/' as a directory separator.
82     TEST_PATH_SEPARATOR = '/'
83
84     ALL_BUILD_TYPES = ('debug', 'release')
85
86     @classmethod
87     def determine_full_port_name(cls, host, options, port_name):
88         """Return a fully-specified port name that can be used to construct objects."""
89         # Subclasses will usually override this.
90         options = options or {}
91         assert port_name.startswith(cls.port_name)
92         if getattr(options, 'webkit_test_runner', False) and not '-wk2' in port_name:
93             return port_name + '-wk2'
94         return port_name
95
96     def __init__(self, host, port_name, options=None, **kwargs):
97
98         # This value may be different from cls.port_name by having version modifiers
99         # and other fields appended to it (for example, 'mac-wk2' or 'win').
100         self._name = port_name
101
102         # These are default values that should be overridden in a subclasses.
103         self._version = ''
104         self._architecture = 'x86'
105
106         # FIXME: Ideally we'd have a package-wide way to get a
107         # well-formed options object that had all of the necessary
108         # options defined on it.
109         self._options = options or optparse.Values()
110
111         if self._name and '-wk2' in self._name:
112             self._options.webkit_test_runner = True
113
114         self.host = host
115         self._executive = host.executive
116         self._filesystem = host.filesystem
117         self._webkit_finder = WebKitFinder(host.filesystem)
118         self._config = port_config.Config(self._executive, self._filesystem, self.port_name)
119
120         self._helper = None
121         self._http_server = None
122         self._websocket_server = None
123         self._image_differ = None
124         self._server_process_constructor = server_process.ServerProcess  # overridable for testing
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         if not hasattr(options, 'configuration') or not options.configuration:
147             self.set_option_default('configuration', self.default_configuration())
148         self._test_configuration = None
149         self._reftest_list = {}
150         self._results_directory = None
151         self._root_was_set = hasattr(options, 'root') and options.root
152
153     def additional_drt_flag(self):
154         return []
155
156     def supports_per_test_timeout(self):
157         return False
158
159     def default_pixel_tests(self):
160         # FIXME: Disable until they are run by default on build.webkit.org.
161         return False
162
163     def default_timeout_ms(self):
164         if self.get_option('webkit_test_runner'):
165             # Add some more time to WebKitTestRunner because it needs to syncronise the state
166             # with the web process and we want to detect if there is a problem with that in the driver.
167             return 80 * 1000
168         return 35 * 1000
169
170     def driver_stop_timeout(self):
171         """ Returns the amount of time in seconds to wait before killing the process in driver.stop()."""
172         # We want to wait for at least 3 seconds, but if we are really slow, we want to be slow on cleanup as
173         # well (for things like ASAN, Valgrind, etc.)
174         return 3.0 * float(self.get_option('time_out_ms', '0')) / self.default_timeout_ms()
175
176     def wdiff_available(self):
177         if self._wdiff_available is None:
178             self._wdiff_available = self.check_wdiff(logging=False)
179         return self._wdiff_available
180
181     def pretty_patch_available(self):
182         if self._pretty_patch_available is None:
183             self._pretty_patch_available = self.check_pretty_patch(logging=False)
184         return self._pretty_patch_available
185
186     def should_retry_crashes(self):
187         return False
188
189     def default_child_processes(self):
190         """Return the number of DumpRenderTree instances to use for this port."""
191         return self._executive.cpu_count()
192
193     def default_max_locked_shards(self):
194         """Return the number of "locked" shards to run in parallel (like the http tests)."""
195         return 1
196
197     def worker_startup_delay_secs(self):
198         # FIXME: If we start workers up too quickly, DumpRenderTree appears
199         # to thrash on something and time out its first few tests. Until
200         # we can figure out what's going on, sleep a bit in between
201         # workers. See https://bugs.webkit.org/show_bug.cgi?id=79147 .
202         return 0.1
203
204     def baseline_path(self):
205         """Return the absolute path to the directory to store new baselines in for this port."""
206         # FIXME: remove once all callers are calling either baseline_version_dir() or baseline_platform_dir()
207         return self.baseline_version_dir()
208
209     def baseline_platform_dir(self):
210         """Return the absolute path to the default (version-independent) platform-specific results."""
211         return self._filesystem.join(self.layout_tests_dir(), 'platform', self.port_name)
212
213     def baseline_version_dir(self):
214         """Return the absolute path to the platform-and-version-specific results."""
215         baseline_search_paths = self.baseline_search_path()
216         return baseline_search_paths[0]
217
218     def baseline_search_path(self):
219         return self.get_option('additional_platform_directory', []) + self._compare_baseline() + self.default_baseline_search_path()
220
221     def default_baseline_search_path(self):
222         """Return a list of absolute paths to directories to search under for
223         baselines. The directories are searched in order."""
224         search_paths = []
225         if self.get_option('webkit_test_runner'):
226             search_paths.append(self._wk2_port_name())
227         search_paths.append(self.name())
228         if self.name() != self.port_name:
229             search_paths.append(self.port_name)
230         return map(self._webkit_baseline_path, search_paths)
231
232     @memoized
233     def _compare_baseline(self):
234         factory = PortFactory(self.host)
235         target_port = self.get_option('compare_port')
236         if target_port:
237             return factory.get(target_port).default_baseline_search_path()
238         return []
239
240     def check_build(self, needs_http):
241         """This routine is used to ensure that the build is up to date
242         and all the needed binaries are present."""
243         # If we're using a pre-built copy of WebKit (--root), we assume it also includes a build of DRT.
244         if not self._root_was_set and self.get_option('build') and not self._build_driver():
245             return False
246         if not self._check_driver():
247             return False
248         if self.get_option('pixel_tests'):
249             if not self.check_image_diff():
250                 return False
251         if not self._check_port_build():
252             return False
253         return True
254
255     def _check_driver(self):
256         driver_path = self._path_to_driver()
257         if not self._filesystem.exists(driver_path):
258             _log.error("%s was not found at %s" % (self.driver_name(), driver_path))
259             return False
260         return True
261
262     def _check_port_build(self):
263         # Ports can override this method to do additional checks.
264         return True
265
266     def check_sys_deps(self, needs_http):
267         """If the port needs to do some runtime checks to ensure that the
268         tests can be run successfully, it should override this routine.
269         This step can be skipped with --nocheck-sys-deps.
270
271         Returns whether the system is properly configured."""
272         if needs_http:
273             return self.check_httpd()
274         return True
275
276     def check_image_diff(self, override_step=None, logging=True):
277         """This routine is used to check whether image_diff binary exists."""
278         image_diff_path = self._path_to_image_diff()
279         if not self._filesystem.exists(image_diff_path):
280             _log.error("ImageDiff was not found at %s" % image_diff_path)
281             return False
282         return True
283
284     def check_pretty_patch(self, logging=True):
285         """Checks whether we can use the PrettyPatch ruby script."""
286         try:
287             _ = self._executive.run_command(['ruby', '--version'])
288         except OSError, e:
289             if e.errno in [errno.ENOENT, errno.EACCES, errno.ECHILD]:
290                 if logging:
291                     _log.warning("Ruby is not installed; can't generate pretty patches.")
292                     _log.warning('')
293                 return False
294
295         if not self._filesystem.exists(self._pretty_patch_path):
296             if logging:
297                 _log.warning("Unable to find %s; can't generate pretty patches." % self._pretty_patch_path)
298                 _log.warning('')
299             return False
300
301         return True
302
303     def check_wdiff(self, logging=True):
304         if not self._path_to_wdiff():
305             # Don't need to log here since this is the port choosing not to use wdiff.
306             return False
307
308         try:
309             _ = self._executive.run_command([self._path_to_wdiff(), '--help'])
310         except OSError:
311             if logging:
312                 message = self._wdiff_missing_message()
313                 if message:
314                     for line in message.splitlines():
315                         _log.warning('    ' + line)
316                         _log.warning('')
317             return False
318
319         return True
320
321     def _wdiff_missing_message(self):
322         return 'wdiff is not installed; please install it to generate word-by-word diffs.'
323
324     def check_httpd(self):
325         if self._uses_apache():
326             httpd_path = self._path_to_apache()
327         else:
328             httpd_path = self._path_to_lighttpd()
329
330         try:
331             server_name = self._filesystem.basename(httpd_path)
332             env = self.setup_environ_for_server(server_name)
333             if self._executive.run_command([httpd_path, "-v"], env=env, return_exit_code=True) != 0:
334                 _log.error("httpd seems broken. Cannot run http tests.")
335                 return False
336             return True
337         except OSError:
338             _log.error("No httpd found. Cannot run http tests.")
339             return False
340
341     def do_text_results_differ(self, expected_text, actual_text):
342         return expected_text != actual_text
343
344     def do_audio_results_differ(self, expected_audio, actual_audio):
345         return expected_audio != actual_audio
346
347     def diff_image(self, expected_contents, actual_contents, tolerance=None):
348         """Compare two images and return a tuple of an image diff, a percentage difference (0-100), and an error string.
349
350         |tolerance| should be a percentage value (0.0 - 100.0).
351         If it is omitted, the port default tolerance value is used.
352
353         If an error occurs (like ImageDiff isn't found, or crashes, we log an error and return True (for a diff).
354         """
355         if not actual_contents and not expected_contents:
356             return (None, 0, None)
357         if not actual_contents or not expected_contents:
358             return (True, 0, None)
359         if not self._image_differ:
360             self._image_differ = image_diff.ImageDiffer(self)
361         self.set_option_default('tolerance', 0.1)
362         if tolerance is None:
363             tolerance = self.get_option('tolerance')
364         return self._image_differ.diff_image(expected_contents, actual_contents, tolerance)
365
366     def diff_text(self, expected_text, actual_text, expected_filename, actual_filename):
367         """Returns a string containing the diff of the two text strings
368         in 'unified diff' format."""
369
370         # The filenames show up in the diff output, make sure they're
371         # raw bytes and not unicode, so that they don't trigger join()
372         # trying to decode the input.
373         def to_raw_bytes(string_value):
374             if isinstance(string_value, unicode):
375                 return string_value.encode('utf-8')
376             return string_value
377         expected_filename = to_raw_bytes(expected_filename)
378         actual_filename = to_raw_bytes(actual_filename)
379         diff = difflib.unified_diff(expected_text.splitlines(True),
380                                     actual_text.splitlines(True),
381                                     expected_filename,
382                                     actual_filename)
383         return ''.join(diff)
384
385     def check_for_leaks(self, process_name, process_pid):
386         # Subclasses should check for leaks in the running process
387         # and print any necessary warnings if leaks are found.
388         # FIXME: We should consider moving much of this logic into
389         # Executive and make it platform-specific instead of port-specific.
390         pass
391
392     def print_leaks_summary(self):
393         # Subclasses can override this to print a summary of leaks found
394         # while running the layout tests.
395         pass
396
397     def driver_name(self):
398         if self.get_option('driver_name'):
399             return self.get_option('driver_name')
400         if self.get_option('webkit_test_runner'):
401             return 'WebKitTestRunner'
402         return 'DumpRenderTree'
403
404     def expected_baselines_by_extension(self, test_name):
405         """Returns a dict mapping baseline suffix to relative path for each baseline in
406         a test. For reftests, it returns ".==" or ".!=" instead of the suffix."""
407         # FIXME: The name similarity between this and expected_baselines() below, is unfortunate.
408         # We should probably rename them both.
409         baseline_dict = {}
410         reference_files = self.reference_files(test_name)
411         if reference_files:
412             # FIXME: How should this handle more than one type of reftest?
413             baseline_dict['.' + reference_files[0][0]] = self.relative_test_filename(reference_files[0][1])
414
415         for extension in self.baseline_extensions():
416             path = self.expected_filename(test_name, extension, return_default=False)
417             baseline_dict[extension] = self.relative_test_filename(path) if path else path
418
419         return baseline_dict
420
421     def baseline_extensions(self):
422         """Returns a tuple of all of the non-reftest baseline extensions we use. The extensions include the leading '.'."""
423         return ('.wav', '.webarchive', '.txt', '.png')
424
425     def expected_baselines(self, test_name, suffix, all_baselines=False):
426         """Given a test name, finds where the baseline results are located.
427
428         Args:
429         test_name: name of test file (usually a relative path under LayoutTests/)
430         suffix: file suffix of the expected results, including dot; e.g.
431             '.txt' or '.png'.  This should not be None, but may be an empty
432             string.
433         all_baselines: If True, return an ordered list of all baseline paths
434             for the given platform. If False, return only the first one.
435         Returns
436         a list of ( platform_dir, results_filename ), where
437             platform_dir - abs path to the top of the results tree (or test
438                 tree)
439             results_filename - relative path from top of tree to the results
440                 file
441             (port.join() of the two gives you the full path to the file,
442                 unless None was returned.)
443         Return values will be in the format appropriate for the current
444         platform (e.g., "\\" for path separators on Windows). If the results
445         file is not found, then None will be returned for the directory,
446         but the expected relative pathname will still be returned.
447
448         This routine is generic but lives here since it is used in
449         conjunction with the other baseline and filename routines that are
450         platform specific.
451         """
452         baseline_filename = self._filesystem.splitext(test_name)[0] + '-expected' + suffix
453         baseline_search_path = self.baseline_search_path()
454
455         baselines = []
456         for platform_dir in baseline_search_path:
457             if self._filesystem.exists(self._filesystem.join(platform_dir, baseline_filename)):
458                 baselines.append((platform_dir, baseline_filename))
459
460             if not all_baselines and baselines:
461                 return baselines
462
463         # If it wasn't found in a platform directory, return the expected
464         # result in the test directory, even if no such file actually exists.
465         platform_dir = self.layout_tests_dir()
466         if self._filesystem.exists(self._filesystem.join(platform_dir, baseline_filename)):
467             baselines.append((platform_dir, baseline_filename))
468
469         if baselines:
470             return baselines
471
472         return [(None, baseline_filename)]
473
474     def expected_filename(self, test_name, suffix, return_default=True):
475         """Given a test name, returns an absolute path to its expected results.
476
477         If no expected results are found in any of the searched directories,
478         the directory in which the test itself is located will be returned.
479         The return value is in the format appropriate for the platform
480         (e.g., "\\" for path separators on windows).
481
482         Args:
483         test_name: name of test file (usually a relative path under LayoutTests/)
484         suffix: file suffix of the expected results, including dot; e.g. '.txt'
485             or '.png'.  This should not be None, but may be an empty string.
486         platform: the most-specific directory name to use to build the
487             search list of directories; e.g. 'mountainlion-wk2'
488         return_default: if True, returns the path to the generic expectation if nothing
489             else is found; if False, returns None.
490
491         This routine is generic but is implemented here to live alongside
492         the other baseline and filename manipulation routines.
493         """
494         # FIXME: The [0] here is very mysterious, as is the destructured return.
495         platform_dir, baseline_filename = self.expected_baselines(test_name, suffix)[0]
496         if platform_dir:
497             return self._filesystem.join(platform_dir, baseline_filename)
498
499         actual_test_name = self.lookup_virtual_test_base(test_name)
500         if actual_test_name:
501             return self.expected_filename(actual_test_name, suffix)
502
503         if return_default:
504             return self._filesystem.join(self.layout_tests_dir(), baseline_filename)
505         return None
506
507     def expected_checksum(self, test_name):
508         """Returns the checksum of the image we expect the test to produce, or None if it is a text-only test."""
509         png_path = self.expected_filename(test_name, '.png')
510
511         if self._filesystem.exists(png_path):
512             with self._filesystem.open_binary_file_for_reading(png_path) as filehandle:
513                 return read_checksum_from_png.read_checksum(filehandle)
514
515         return None
516
517     def expected_image(self, test_name):
518         """Returns the image we expect the test to produce."""
519         baseline_path = self.expected_filename(test_name, '.png')
520         if not self._filesystem.exists(baseline_path):
521             return None
522         return self._filesystem.read_binary_file(baseline_path)
523
524     def expected_audio(self, test_name):
525         baseline_path = self.expected_filename(test_name, '.wav')
526         if not self._filesystem.exists(baseline_path):
527             return None
528         return self._filesystem.read_binary_file(baseline_path)
529
530     def expected_text(self, test_name):
531         """Returns the text output we expect the test to produce, or None
532         if we don't expect there to be any text output.
533         End-of-line characters are normalized to '\n'."""
534         # FIXME: DRT output is actually utf-8, but since we don't decode the
535         # output from DRT (instead treating it as a binary string), we read the
536         # baselines as a binary string, too.
537         baseline_path = self.expected_filename(test_name, '.txt')
538         if not self._filesystem.exists(baseline_path):
539             baseline_path = self.expected_filename(test_name, '.webarchive')
540             if not self._filesystem.exists(baseline_path):
541                 return None
542         text = self._filesystem.read_binary_file(baseline_path)
543         return text.replace("\r\n", "\n")
544
545     def _get_reftest_list(self, test_name):
546         dirname = self._filesystem.join(self.layout_tests_dir(), self._filesystem.dirname(test_name))
547         if dirname not in self._reftest_list:
548             self._reftest_list[dirname] = Port._parse_reftest_list(self._filesystem, dirname)
549         return self._reftest_list[dirname]
550
551     @staticmethod
552     def _parse_reftest_list(filesystem, test_dirpath):
553         reftest_list_path = filesystem.join(test_dirpath, 'reftest.list')
554         if not filesystem.isfile(reftest_list_path):
555             return None
556         reftest_list_file = filesystem.read_text_file(reftest_list_path)
557
558         parsed_list = {}
559         for line in reftest_list_file.split('\n'):
560             line = re.sub('#.+$', '', line)
561             split_line = line.split()
562             if len(split_line) < 3:
563                 continue
564             expectation_type, test_file, ref_file = split_line
565             parsed_list.setdefault(filesystem.join(test_dirpath, test_file), []).append((expectation_type, filesystem.join(test_dirpath, ref_file)))
566         return parsed_list
567
568     def reference_files(self, test_name):
569         """Return a list of expectation (== or !=) and filename pairs"""
570
571         reftest_list = self._get_reftest_list(test_name)
572         if not reftest_list:
573             reftest_list = []
574             for expectation, prefix in (('==', ''), ('!=', '-mismatch')):
575                 for extention in Port._supported_reference_extensions:
576                     path = self.expected_filename(test_name, prefix + extention)
577                     if self._filesystem.exists(path):
578                         reftest_list.append((expectation, path))
579             return reftest_list
580
581         return reftest_list.get(self._filesystem.join(self.layout_tests_dir(), test_name), [])  # pylint: disable=E1103
582
583     def tests(self, paths):
584         """Return the list of tests found. Both generic and platform-specific tests matching paths should be returned."""
585         expanded_paths = self._expanded_paths(paths)
586         tests = self._real_tests(expanded_paths)
587         tests.extend(self._virtual_tests(expanded_paths, self.populated_virtual_test_suites()))
588         return tests
589
590     def _expanded_paths(self, paths):
591         expanded_paths = []
592         fs = self._filesystem
593         all_platform_dirs = [path for path in fs.glob(fs.join(self.layout_tests_dir(), 'platform', '*')) if fs.isdir(path)]
594         for path in paths:
595             expanded_paths.append(path)
596             if self.test_isdir(path) and not path.startswith('platform'):
597                 for platform_dir in all_platform_dirs:
598                     if fs.isdir(fs.join(platform_dir, path)) and platform_dir in self.baseline_search_path():
599                         expanded_paths.append(self.relative_test_filename(fs.join(platform_dir, path)))
600
601         return expanded_paths
602
603     def _real_tests(self, paths):
604         # When collecting test cases, skip these directories
605         skipped_directories = set(['.svn', '_svn', 'resources', 'script-tests', 'reference', 'reftest'])
606         files = find_files.find(self._filesystem, self.layout_tests_dir(), paths, skipped_directories, Port._is_test_file, self.test_key)
607         return [self.relative_test_filename(f) for f in files]
608
609     # When collecting test cases, we include any file with these extensions.
610     _supported_test_extensions = set(['.html', '.shtml', '.xml', '.xhtml', '.pl', '.htm', '.php', '.svg', '.mht', '.xht'])
611     _supported_reference_extensions = set(['.html', '.xml', '.xhtml', '.htm', '.svg', '.xht'])
612
613     @staticmethod
614     # If any changes are made here be sure to update the isUsedInReftest method in old-run-webkit-tests as well.
615     def is_reference_html_file(filesystem, dirname, filename):
616         if filename.startswith('ref-') or filename.startswith('notref-'):
617             return True
618         filename_wihout_ext, ext = filesystem.splitext(filename)
619         if ext not in Port._supported_reference_extensions:
620             return False
621         for suffix in ['-expected', '-expected-mismatch', '-ref', '-notref']:
622             if filename_wihout_ext.endswith(suffix):
623                 return True
624         return False
625
626     @staticmethod
627     def _has_supported_extension(filesystem, filename):
628         """Return true if filename is one of the file extensions we want to run a test on."""
629         extension = filesystem.splitext(filename)[1]
630         return extension in Port._supported_test_extensions
631
632     @staticmethod
633     def _is_test_file(filesystem, dirname, filename):
634         return Port._has_supported_extension(filesystem, filename) and not Port.is_reference_html_file(filesystem, dirname, filename)
635
636     def test_key(self, test_name):
637         """Turns a test name into a list with two sublists, the natural key of the
638         dirname, and the natural key of the basename.
639
640         This can be used when sorting paths so that files in a directory.
641         directory are kept together rather than being mixed in with files in
642         subdirectories."""
643         dirname, basename = self.split_test(test_name)
644         return (self._natural_sort_key(dirname + self.TEST_PATH_SEPARATOR), self._natural_sort_key(basename))
645
646     def _natural_sort_key(self, string_to_split):
647         """ Turns a string into a list of string and number chunks, i.e. "z23a" -> ["z", 23, "a"]
648
649         This can be used to implement "natural sort" order. See:
650         http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html
651         http://nedbatchelder.com/blog/200712.html#e20071211T054956
652         """
653         def tryint(val):
654             try:
655                 return int(val)
656             except ValueError:
657                 return val
658
659         return [tryint(chunk) for chunk in re.split('(\d+)', string_to_split)]
660
661     def test_dirs(self):
662         """Returns the list of top-level test directories."""
663         layout_tests_dir = self.layout_tests_dir()
664         return filter(lambda x: self._filesystem.isdir(self._filesystem.join(layout_tests_dir, x)),
665                       self._filesystem.listdir(layout_tests_dir))
666
667     @memoized
668     def test_isfile(self, test_name):
669         """Return True if the test name refers to a directory of tests."""
670         # Used by test_expectations.py to apply rules to whole directories.
671         if self._filesystem.isfile(self.abspath_for_test(test_name)):
672             return True
673         base = self.lookup_virtual_test_base(test_name)
674         return base and self._filesystem.isfile(self.abspath_for_test(base))
675
676     @memoized
677     def test_isdir(self, test_name):
678         """Return True if the test name refers to a directory of tests."""
679         # Used by test_expectations.py to apply rules to whole directories.
680         if self._filesystem.isdir(self.abspath_for_test(test_name)):
681             return True
682         base = self.lookup_virtual_test_base(test_name)
683         return base and self._filesystem.isdir(self.abspath_for_test(base))
684
685     @memoized
686     def test_exists(self, test_name):
687         """Return True if the test name refers to an existing test or baseline."""
688         # Used by test_expectations.py to determine if an entry refers to a
689         # valid test and by printing.py to determine if baselines exist.
690         return self.test_isfile(test_name) or self.test_isdir(test_name)
691
692     def split_test(self, test_name):
693         """Splits a test name into the 'directory' part and the 'basename' part."""
694         index = test_name.rfind(self.TEST_PATH_SEPARATOR)
695         if index < 1:
696             return ('', test_name)
697         return (test_name[0:index], test_name[index:])
698
699     def normalize_test_name(self, test_name):
700         """Returns a normalized version of the test name or test directory."""
701         if test_name.endswith('/'):
702             return test_name
703         if self.test_isdir(test_name):
704             return test_name + '/'
705         return test_name
706
707     def driver_cmd_line(self):
708         """Prints the DRT command line that will be used."""
709         driver = self.create_driver(0)
710         return driver.cmd_line(self.get_option('pixel_tests'), [])
711
712     def update_baseline(self, baseline_path, data):
713         """Updates the baseline for a test.
714
715         Args:
716             baseline_path: the actual path to use for baseline, not the path to
717               the test. This function is used to update either generic or
718               platform-specific baselines, but we can't infer which here.
719             data: contents of the baseline.
720         """
721         self._filesystem.write_binary_file(baseline_path, data)
722
723     # FIXME: update callers to create a finder and call it instead of these next five routines (which should be protected).
724     def webkit_base(self):
725         return self._webkit_finder.webkit_base()
726
727     def path_from_webkit_base(self, *comps):
728         return self._webkit_finder.path_from_webkit_base(*comps)
729
730     def path_to_script(self, script_name):
731         return self._webkit_finder.path_to_script(script_name)
732
733     def layout_tests_dir(self):
734         return self._webkit_finder.layout_tests_dir()
735
736     def perf_tests_dir(self):
737         return self._webkit_finder.perf_tests_dir()
738
739     def skipped_layout_tests(self, test_list):
740         """Returns tests skipped outside of the TestExpectations files."""
741         return set(self._tests_for_other_platforms()).union(self._skipped_tests_for_unsupported_features(test_list))
742
743     def _tests_from_skipped_file_contents(self, skipped_file_contents):
744         tests_to_skip = []
745         for line in skipped_file_contents.split('\n'):
746             line = line.strip()
747             line = line.rstrip('/')  # Best to normalize directory names to not include the trailing slash.
748             if line.startswith('#') or not len(line):
749                 continue
750             tests_to_skip.append(line)
751         return tests_to_skip
752
753     def _expectations_from_skipped_files(self, skipped_file_paths):
754         tests_to_skip = []
755         for search_path in skipped_file_paths:
756             filename = self._filesystem.join(self._webkit_baseline_path(search_path), "Skipped")
757             if not self._filesystem.exists(filename):
758                 _log.debug("Skipped does not exist: %s" % filename)
759                 continue
760             _log.debug("Using Skipped file: %s" % filename)
761             skipped_file_contents = self._filesystem.read_text_file(filename)
762             tests_to_skip.extend(self._tests_from_skipped_file_contents(skipped_file_contents))
763         return tests_to_skip
764
765     @memoized
766     def skipped_perf_tests(self):
767         return self._expectations_from_skipped_files([self.perf_tests_dir()])
768
769     def skips_perf_test(self, test_name):
770         for test_or_category in self.skipped_perf_tests():
771             if test_or_category == test_name:
772                 return True
773             category = self._filesystem.join(self.perf_tests_dir(), test_or_category)
774             if self._filesystem.isdir(category) and test_name.startswith(test_or_category):
775                 return True
776         return False
777
778     def name(self):
779         """Returns a name that uniquely identifies this particular type of port
780         (e.g., "mac-snowleopard" or "chromium-linux-x86_x64" and can be passed
781         to factory.get() to instantiate the port."""
782         return self._name
783
784     def operating_system(self):
785         # Subclasses should override this default implementation.
786         return 'mac'
787
788     def version(self):
789         """Returns a string indicating the version of a given platform, e.g.
790         'leopard' or 'xp'.
791
792         This is used to help identify the exact port when parsing test
793         expectations, determining search paths, and logging information."""
794         return self._version
795
796     def architecture(self):
797         return self._architecture
798
799     def get_option(self, name, default_value=None):
800         return getattr(self._options, name, default_value)
801
802     def set_option_default(self, name, default_value):
803         return self._options.ensure_value(name, default_value)
804
805     @memoized
806     def path_to_generic_test_expectations_file(self):
807         return self._filesystem.join(self.layout_tests_dir(), 'TestExpectations')
808
809     @memoized
810     def path_to_test_expectations_file(self):
811         """Update the test expectations to the passed-in string.
812
813         This is used by the rebaselining tool. Raises NotImplementedError
814         if the port does not use expectations files."""
815
816         # FIXME: We need to remove this when we make rebaselining work with multiple files and just generalize expectations_files().
817
818         # test_expectations are always in mac/ not mac-leopard/ by convention, hence we use port_name instead of name().
819         return self._filesystem.join(self._webkit_baseline_path(self.port_name), 'TestExpectations')
820
821     def relative_test_filename(self, filename):
822         """Returns a test_name a relative unix-style path for a filename under the LayoutTests
823         directory. Ports may legitimately return abspaths here if no relpath makes sense."""
824         # Ports that run on windows need to override this method to deal with
825         # filenames with backslashes in them.
826         if filename.startswith(self.layout_tests_dir()):
827             return self.host.filesystem.relpath(filename, self.layout_tests_dir())
828         else:
829             return self.host.filesystem.abspath(filename)
830
831     @memoized
832     def abspath_for_test(self, test_name):
833         """Returns the full path to the file for a given test name. This is the
834         inverse of relative_test_filename()."""
835         return self._filesystem.join(self.layout_tests_dir(), test_name)
836
837     def results_directory(self):
838         """Absolute path to the place to store the test results (uses --results-directory)."""
839         if not self._results_directory:
840             option_val = self.get_option('results_directory') or self.default_results_directory()
841             self._results_directory = self._filesystem.abspath(option_val)
842         return self._results_directory
843
844     def perf_results_directory(self):
845         return self._build_path()
846
847     def default_results_directory(self):
848         """Absolute path to the default place to store the test results."""
849         # Results are store relative to the built products to make it easy
850         # to have multiple copies of webkit checked out and built.
851         return self._build_path('layout-test-results')
852
853     def setup_test_run(self):
854         """Perform port-specific work at the beginning of a test run."""
855         pass
856
857     def clean_up_test_run(self):
858         """Perform port-specific work at the end of a test run."""
859         if self._image_differ:
860             self._image_differ.stop()
861             self._image_differ = None
862
863     # FIXME: os.environ access should be moved to onto a common/system class to be more easily mockable.
864     def _value_or_default_from_environ(self, name, default=None):
865         if name in os.environ:
866             return os.environ[name]
867         return default
868
869     def _copy_value_from_environ_if_set(self, clean_env, name):
870         if name in os.environ:
871             clean_env[name] = os.environ[name]
872
873     def setup_environ_for_server(self, server_name=None):
874         # We intentionally copy only a subset of os.environ when
875         # launching subprocesses to ensure consistent test results.
876         clean_env = {}
877         variables_to_copy = [
878             # For Linux:
879             'XAUTHORITY',
880             'HOME',
881             'LANG',
882             'LD_LIBRARY_PATH',
883             'DBUS_SESSION_BUS_ADDRESS',
884             'XDG_DATA_DIRS',
885             'XDG_RUNTIME_DIR',
886
887             # Darwin:
888             'DYLD_LIBRARY_PATH',
889             'HOME',
890
891             # CYGWIN:
892             'HOMEDRIVE',
893             'HOMEPATH',
894             '_NT_SYMBOL_PATH',
895
896             # Windows:
897             'PATH',
898
899             # Most ports (?):
900             'WEBKIT_TESTFONTS',
901             'WEBKIT_OUTPUTDIR',
902
903             # Chromium:
904             'CHROME_DEVEL_SANDBOX',
905         ]
906         for variable in variables_to_copy:
907             self._copy_value_from_environ_if_set(clean_env, variable)
908
909         # For Linux:
910         clean_env['DISPLAY'] = self._value_or_default_from_environ('DISPLAY', ':1')
911
912         for string_variable in self.get_option('additional_env_var', []):
913             [name, value] = string_variable.split('=', 1)
914             clean_env[name] = value
915
916         return clean_env
917
918     def show_results_html_file(self, results_filename):
919         """This routine should display the HTML file pointed at by
920         results_filename in a users' browser."""
921         return self.host.user.open_url(path.abspath_to_uri(self.host.platform, results_filename))
922
923     def create_driver(self, worker_number, no_timeout=False):
924         """Return a newly created Driver subclass for starting/stopping the test driver."""
925         return driver.DriverProxy(self, worker_number, self._driver_class(), pixel_tests=self.get_option('pixel_tests'), no_timeout=no_timeout)
926
927     def start_helper(self, pixel_tests=False):
928         """If a port needs to reconfigure graphics settings or do other
929         things to ensure a known test configuration, it should override this
930         method."""
931         pass
932
933     def start_http_server(self, additional_dirs=None, number_of_servers=None):
934         """Start a web server. Raise an error if it can't start or is already running.
935
936         Ports can stub this out if they don't need a web server to be running."""
937         assert not self._http_server, 'Already running an http server.'
938
939         if self._uses_apache():
940             server = apache_http_server.LayoutTestApacheHttpd(self, self.results_directory(), additional_dirs=additional_dirs, number_of_servers=number_of_servers)
941         else:
942             server = http_server.Lighttpd(self, self.results_directory(), additional_dirs=additional_dirs, number_of_servers=number_of_servers)
943
944         server.start()
945         self._http_server = server
946
947     def start_websocket_server(self):
948         """Start a web server. Raise an error if it can't start or is already running.
949
950         Ports can stub this out if they don't need a websocket server to be running."""
951         assert not self._websocket_server, 'Already running a websocket server.'
952
953         server = websocket_server.PyWebSocket(self, self.results_directory())
954         server.start()
955         self._websocket_server = server
956
957     def http_server_supports_ipv6(self):
958         # Cygwin is the only platform to still use Apache 1.3, which only supports IPV4.
959         # Once it moves to Apache 2, we can drop this method altogether.
960         if self.host.platform.is_cygwin():
961             return False
962         return True
963
964     def acquire_http_lock(self):
965         self._http_lock = http_lock.HttpLock(None, filesystem=self._filesystem, executive=self._executive)
966         self._http_lock.wait_for_httpd_lock()
967
968     def stop_helper(self):
969         """Shut down the test helper if it is running. Do nothing if
970         it isn't, or it isn't available. If a port overrides start_helper()
971         it must override this routine as well."""
972         pass
973
974     def stop_http_server(self):
975         """Shut down the http server if it is running. Do nothing if it isn't."""
976         if self._http_server:
977             self._http_server.stop()
978             self._http_server = None
979
980     def stop_websocket_server(self):
981         """Shut down the websocket server if it is running. Do nothing if it isn't."""
982         if self._websocket_server:
983             self._websocket_server.stop()
984             self._websocket_server = None
985
986     def release_http_lock(self):
987         if self._http_lock:
988             self._http_lock.cleanup_http_lock()
989
990     def exit_code_from_summarized_results(self, unexpected_results):
991         """Given summarized results, compute the exit code to be returned by new-run-webkit-tests.
992         Bots turn red when this function returns a non-zero value. By default, return the number of regressions
993         to avoid turning bots red by flaky failures, unexpected passes, and missing results"""
994         # Don't turn bots red for flaky failures, unexpected passes, and missing results.
995         return unexpected_results['num_regressions']
996
997     #
998     # TEST EXPECTATION-RELATED METHODS
999     #
1000
1001     def test_configuration(self):
1002         """Returns the current TestConfiguration for the port."""
1003         if not self._test_configuration:
1004             self._test_configuration = TestConfiguration(self._version, self._architecture, self._options.configuration.lower())
1005         return self._test_configuration
1006
1007     # FIXME: Belongs on a Platform object.
1008     @memoized
1009     def all_test_configurations(self):
1010         """Returns a list of TestConfiguration instances, representing all available
1011         test configurations for this port."""
1012         return self._generate_all_test_configurations()
1013
1014     # FIXME: Belongs on a Platform object.
1015     def configuration_specifier_macros(self):
1016         """Ports may provide a way to abbreviate configuration specifiers to conveniently
1017         refer to them as one term or alias specific values to more generic ones. For example:
1018
1019         (xp, vista, win7) -> win # Abbreviate all Windows versions into one namesake.
1020         (lucid) -> linux  # Change specific name of the Linux distro to a more generic term.
1021
1022         Returns a dictionary, each key representing a macro term ('win', for example),
1023         and value being a list of valid configuration specifiers (such as ['xp', 'vista', 'win7'])."""
1024         return {}
1025
1026     def all_baseline_variants(self):
1027         """Returns a list of platform names sufficient to cover all the baselines.
1028
1029         The list should be sorted so that a later platform  will reuse
1030         an earlier platform's baselines if they are the same (e.g.,
1031         'snowleopard' should precede 'leopard')."""
1032         raise NotImplementedError
1033
1034     def uses_test_expectations_file(self):
1035         # This is different from checking test_expectations() is None, because
1036         # some ports have Skipped files which are returned as part of test_expectations().
1037         return self._filesystem.exists(self.path_to_test_expectations_file())
1038
1039     def warn_if_bug_missing_in_test_expectations(self):
1040         return False
1041
1042     def expectations_dict(self):
1043         """Returns an OrderedDict of name -> expectations strings.
1044         The names are expected to be (but not required to be) paths in the filesystem.
1045         If the name is a path, the file can be considered updatable for things like rebaselining,
1046         so don't use names that are paths if they're not paths.
1047         Generally speaking the ordering should be files in the filesystem in cascade order
1048         (TestExpectations followed by Skipped, if the port honors both formats),
1049         then any built-in expectations (e.g., from compile-time exclusions), then --additional-expectations options."""
1050         # FIXME: rename this to test_expectations() once all the callers are updated to know about the ordered dict.
1051         expectations = OrderedDict()
1052
1053         for path in self.expectations_files():
1054             if self._filesystem.exists(path):
1055                 expectations[path] = self._filesystem.read_text_file(path)
1056
1057         for path in self.get_option('additional_expectations', []):
1058             expanded_path = self._filesystem.expanduser(path)
1059             if self._filesystem.exists(expanded_path):
1060                 _log.debug("reading additional_expectations from path '%s'" % path)
1061                 expectations[path] = self._filesystem.read_text_file(expanded_path)
1062             else:
1063                 _log.warning("additional_expectations path '%s' does not exist" % path)
1064         return expectations
1065
1066     def _port_specific_expectations_files(self):
1067         # Unlike baseline_search_path, we only want to search [WK2-PORT, PORT-VERSION, PORT] and any directories
1068         # included via --additional-platform-directory, not the full casade.
1069         search_paths = [self.port_name]
1070
1071         non_wk2_name = self.name().replace('-wk2', '')
1072         if non_wk2_name != self.port_name:
1073             search_paths.append(non_wk2_name)
1074
1075         if self.get_option('webkit_test_runner'):
1076             # Because nearly all of the skipped tests for WebKit 2 are due to cross-platform
1077             # issues, all wk2 ports share a skipped list under platform/wk2.
1078             search_paths.extend(["wk2", self._wk2_port_name()])
1079
1080         search_paths.extend(self.get_option("additional_platform_directory", []))
1081
1082         return [self._filesystem.join(self._webkit_baseline_path(d), 'TestExpectations') for d in search_paths]
1083
1084     def expectations_files(self):
1085         return [self.path_to_generic_test_expectations_file()] + self._port_specific_expectations_files()
1086
1087     def repository_paths(self):
1088         """Returns a list of (repository_name, repository_path) tuples of its depending code base.
1089         By default it returns a list that only contains a ('WebKit', <webkitRepositoryPath>) tuple."""
1090
1091         # We use LayoutTest directory here because webkit_base isn't a part of WebKit repository in Chromium port
1092         # where turnk isn't checked out as a whole.
1093         return [('WebKit', self.layout_tests_dir())]
1094
1095     _WDIFF_DEL = '##WDIFF_DEL##'
1096     _WDIFF_ADD = '##WDIFF_ADD##'
1097     _WDIFF_END = '##WDIFF_END##'
1098
1099     def _format_wdiff_output_as_html(self, wdiff):
1100         wdiff = cgi.escape(wdiff)
1101         wdiff = wdiff.replace(self._WDIFF_DEL, "<span class=del>")
1102         wdiff = wdiff.replace(self._WDIFF_ADD, "<span class=add>")
1103         wdiff = wdiff.replace(self._WDIFF_END, "</span>")
1104         html = "<head><style>.del { background: #faa; } "
1105         html += ".add { background: #afa; }</style></head>"
1106         html += "<pre>%s</pre>" % wdiff
1107         return html
1108
1109     def _wdiff_command(self, actual_filename, expected_filename):
1110         executable = self._path_to_wdiff()
1111         return [executable,
1112                 "--start-delete=%s" % self._WDIFF_DEL,
1113                 "--end-delete=%s" % self._WDIFF_END,
1114                 "--start-insert=%s" % self._WDIFF_ADD,
1115                 "--end-insert=%s" % self._WDIFF_END,
1116                 actual_filename,
1117                 expected_filename]
1118
1119     @staticmethod
1120     def _handle_wdiff_error(script_error):
1121         # Exit 1 means the files differed, any other exit code is an error.
1122         if script_error.exit_code != 1:
1123             raise script_error
1124
1125     def _run_wdiff(self, actual_filename, expected_filename):
1126         """Runs wdiff and may throw exceptions.
1127         This is mostly a hook for unit testing."""
1128         # Diffs are treated as binary as they may include multiple files
1129         # with conflicting encodings.  Thus we do not decode the output.
1130         command = self._wdiff_command(actual_filename, expected_filename)
1131         wdiff = self._executive.run_command(command, decode_output=False,
1132             error_handler=self._handle_wdiff_error)
1133         return self._format_wdiff_output_as_html(wdiff)
1134
1135     def wdiff_text(self, actual_filename, expected_filename):
1136         """Returns a string of HTML indicating the word-level diff of the
1137         contents of the two filenames. Returns an empty string if word-level
1138         diffing isn't available."""
1139         if not self.wdiff_available():
1140             return ""
1141         try:
1142             # It's possible to raise a ScriptError we pass wdiff invalid paths.
1143             return self._run_wdiff(actual_filename, expected_filename)
1144         except OSError, e:
1145             if e.errno in [errno.ENOENT, errno.EACCES, errno.ECHILD]:
1146                 # Silently ignore cases where wdiff is missing.
1147                 self._wdiff_available = False
1148                 return ""
1149             raise
1150
1151     # This is a class variable so we can test error output easily.
1152     _pretty_patch_error_html = "Failed to run PrettyPatch, see error log."
1153
1154     def pretty_patch_text(self, diff_path):
1155         if self._pretty_patch_available is None:
1156             self._pretty_patch_available = self.check_pretty_patch(logging=False)
1157         if not self._pretty_patch_available:
1158             return self._pretty_patch_error_html
1159         command = ("ruby", "-I", self._filesystem.dirname(self._pretty_patch_path),
1160                    self._pretty_patch_path, diff_path)
1161         try:
1162             # Diffs are treated as binary (we pass decode_output=False) as they
1163             # may contain multiple files of conflicting encodings.
1164             return self._executive.run_command(command, decode_output=False)
1165         except OSError, e:
1166             # If the system is missing ruby log the error and stop trying.
1167             self._pretty_patch_available = False
1168             _log.error("Failed to run PrettyPatch (%s): %s" % (command, e))
1169             return self._pretty_patch_error_html
1170         except ScriptError, e:
1171             # If ruby failed to run for some reason, log the command
1172             # output and stop trying.
1173             self._pretty_patch_available = False
1174             _log.error("Failed to run PrettyPatch (%s):\n%s" % (command, e.message_with_output()))
1175             return self._pretty_patch_error_html
1176
1177     def default_configuration(self):
1178         return self._config.default_configuration()
1179
1180     #
1181     # PROTECTED ROUTINES
1182     #
1183     # The routines below should only be called by routines in this class
1184     # or any of its subclasses.
1185     #
1186
1187     def _uses_apache(self):
1188         return True
1189
1190     # FIXME: This does not belong on the port object.
1191     @memoized
1192     def _path_to_apache(self):
1193         """Returns the full path to the apache binary.
1194
1195         This is needed only by ports that use the apache_http_server module."""
1196         # The Apache binary path can vary depending on OS and distribution
1197         # See http://wiki.apache.org/httpd/DistrosDefaultLayout
1198         for path in ["/usr/sbin/httpd", "/usr/sbin/apache2"]:
1199             if self._filesystem.exists(path):
1200                 return path
1201         _log.error("Could not find apache. Not installed or unknown path.")
1202         return None
1203
1204     # FIXME: This belongs on some platform abstraction instead of Port.
1205     def _is_redhat_based(self):
1206         return self._filesystem.exists('/etc/redhat-release')
1207
1208     def _is_debian_based(self):
1209         return self._filesystem.exists('/etc/debian_version')
1210
1211     def _is_arch_based(self):
1212         return self._filesystem.exists('/etc/arch-release')
1213
1214     def _apache_version(self):
1215         config = self._executive.run_command([self._path_to_apache(), '-v'])
1216         return re.sub(r'(?:.|\n)*Server version: Apache/(\d+\.\d+)(?:.|\n)*', r'\1', config)
1217
1218     # We pass sys_platform into this method to make it easy to unit test.
1219     def _apache_config_file_name_for_platform(self, sys_platform):
1220         if sys_platform == 'cygwin':
1221             return 'cygwin-httpd.conf'  # CYGWIN is the only platform to still use Apache 1.3.
1222         if sys_platform.startswith('linux'):
1223             if self._is_redhat_based():
1224                 return 'fedora-httpd-' + self._apache_version() + '.conf'
1225             if self._is_debian_based():
1226                 return 'debian-httpd-' + self._apache_version() + '.conf'
1227             if self._is_arch_based():
1228                 return 'archlinux-httpd.conf'
1229         # All platforms use apache2 except for CYGWIN (and Mac OS X Tiger and prior, which we no longer support).
1230         return "apache2-httpd.conf"
1231
1232     def _path_to_apache_config_file(self):
1233         """Returns the full path to the apache configuration file.
1234
1235         If the WEBKIT_HTTP_SERVER_CONF_PATH environment variable is set, its
1236         contents will be used instead.
1237
1238         This is needed only by ports that use the apache_http_server module."""
1239         config_file_from_env = os.environ.get('WEBKIT_HTTP_SERVER_CONF_PATH')
1240         if config_file_from_env:
1241             if not self._filesystem.exists(config_file_from_env):
1242                 raise IOError('%s was not found on the system' % config_file_from_env)
1243             return config_file_from_env
1244
1245         config_file_name = self._apache_config_file_name_for_platform(sys.platform)
1246         return self._filesystem.join(self.layout_tests_dir(), 'http', 'conf', config_file_name)
1247
1248     def _build_path(self, *comps):
1249         root_directory = self.get_option('root')
1250         if not root_directory:
1251             build_directory = self.get_option('build_directory')
1252             if build_directory:
1253                 root_directory = self._filesystem.join(build_directory, self.get_option('configuration'))
1254             else:
1255                 root_directory = self._config.build_directory(self.get_option('configuration'))
1256             # Set --root so that we can pass this to subprocesses and avoid making the
1257             # slow call to config.build_directory() N times in each worker.
1258             # FIXME: This is like @memoized, but more annoying and fragile; there should be another
1259             # way to propagate values without mutating the options list.
1260             self.set_option_default('root', root_directory)
1261         return self._filesystem.join(self._filesystem.abspath(root_directory), *comps)
1262
1263     def _path_to_driver(self, configuration=None):
1264         """Returns the full path to the test driver (DumpRenderTree)."""
1265         return self._build_path(self.driver_name())
1266
1267     def _driver_tempdir(self):
1268         return self._filesystem.mkdtemp(prefix='%s-' % self.driver_name())
1269
1270     def _driver_tempdir_for_environment(self):
1271         return self._driver_tempdir()
1272
1273     def _path_to_webcore_library(self):
1274         """Returns the full path to a built copy of WebCore."""
1275         return None
1276
1277     def _path_to_helper(self):
1278         """Returns the full path to the layout_test_helper binary, which
1279         is used to help configure the system for the test run, or None
1280         if no helper is needed.
1281
1282         This is likely only used by start/stop_helper()."""
1283         return None
1284
1285     def _path_to_image_diff(self):
1286         """Returns the full path to the image_diff binary, or None if it is not available.
1287
1288         This is likely used only by diff_image()"""
1289         return self._build_path('ImageDiff')
1290
1291     def _path_to_lighttpd(self):
1292         """Returns the path to the LigHTTPd binary.
1293
1294         This is needed only by ports that use the http_server.py module."""
1295         raise NotImplementedError('Port._path_to_lighttpd')
1296
1297     def _path_to_lighttpd_modules(self):
1298         """Returns the path to the LigHTTPd modules directory.
1299
1300         This is needed only by ports that use the http_server.py module."""
1301         raise NotImplementedError('Port._path_to_lighttpd_modules')
1302
1303     def _path_to_lighttpd_php(self):
1304         """Returns the path to the LigHTTPd PHP executable.
1305
1306         This is needed only by ports that use the http_server.py module."""
1307         raise NotImplementedError('Port._path_to_lighttpd_php')
1308
1309     @memoized
1310     def _path_to_wdiff(self):
1311         """Returns the full path to the wdiff binary, or None if it is not available.
1312
1313         This is likely used only by wdiff_text()"""
1314         for path in ("/usr/bin/wdiff", "/usr/bin/dwdiff"):
1315             if self._filesystem.exists(path):
1316                 return path
1317         return None
1318
1319     def _webkit_baseline_path(self, platform):
1320         """Return the  full path to the top of the baseline tree for a
1321         given platform."""
1322         return self._filesystem.join(self.layout_tests_dir(), 'platform', platform)
1323
1324     # FIXME: Belongs on a Platform object.
1325     def _generate_all_test_configurations(self):
1326         """Generates a list of TestConfiguration instances, representing configurations
1327         for a platform across all OSes, architectures, build and graphics types."""
1328         raise NotImplementedError('Port._generate_test_configurations')
1329
1330     def _driver_class(self):
1331         """Returns the port's driver implementation."""
1332         return driver.Driver
1333
1334     def _get_crash_log(self, name, pid, stdout, stderr, newer_than):
1335         name_str = name or '<unknown process name>'
1336         pid_str = str(pid or '<unknown>')
1337         stdout_lines = (stdout or '<empty>').decode('utf8', 'replace').splitlines()
1338         stderr_lines = (stderr or '<empty>').decode('utf8', 'replace').splitlines()
1339         return (stderr, 'crash log for %s (pid %s):\n%s\n%s\n' % (name_str, pid_str,
1340             '\n'.join(('STDOUT: ' + l) for l in stdout_lines),
1341             '\n'.join(('STDERR: ' + l) for l in stderr_lines)))
1342
1343     def look_for_new_crash_logs(self, crashed_processes, start_time):
1344         pass
1345
1346     def look_for_new_samples(self, unresponsive_processes, start_time):
1347         pass
1348
1349     def sample_process(self, name, pid):
1350         pass
1351
1352     def virtual_test_suites(self):
1353         return []
1354
1355     def find_system_pid(self, name, pid):
1356         # This is only overridden on Windows
1357         return pid
1358
1359     @memoized
1360     def populated_virtual_test_suites(self):
1361         suites = self.virtual_test_suites()
1362
1363         # Sanity-check the suites to make sure they don't point to other suites.
1364         suite_dirs = [suite.name for suite in suites]
1365         for suite in suites:
1366             assert suite.base not in suite_dirs
1367
1368         for suite in suites:
1369             base_tests = self._real_tests([suite.base])
1370             suite.tests = {}
1371             for test in base_tests:
1372                 suite.tests[test.replace(suite.base, suite.name, 1)] = test
1373         return suites
1374
1375     def _virtual_tests(self, paths, suites):
1376         virtual_tests = list()
1377         for suite in suites:
1378             if paths:
1379                 for test in suite.tests:
1380                     if any(test.startswith(p) for p in paths):
1381                         virtual_tests.append(test)
1382             else:
1383                 virtual_tests.extend(suite.tests.keys())
1384         return virtual_tests
1385
1386     def lookup_virtual_test_base(self, test_name):
1387         for suite in self.populated_virtual_test_suites():
1388             if test_name.startswith(suite.name):
1389                 return test_name.replace(suite.name, suite.base, 1)
1390         return None
1391
1392     def lookup_virtual_test_args(self, test_name):
1393         for suite in self.populated_virtual_test_suites():
1394             if test_name.startswith(suite.name):
1395                 return suite.args
1396         return []
1397
1398     def should_run_as_pixel_test(self, test_input):
1399         if not self._options.pixel_tests:
1400             return False
1401         if self._options.pixel_test_directories:
1402             return any(test_input.test_name.startswith(directory) for directory in self._options.pixel_test_directories)
1403         return self._should_run_as_pixel_test(test_input)
1404
1405     def _should_run_as_pixel_test(self, test_input):
1406         # Default behavior is to allow all test to run as pixel tests if --pixel-tests is on and
1407         # --pixel-test-directory is not specified.
1408         return True
1409
1410     # FIXME: Eventually we should standarize port naming, and make this method smart enough
1411     # to use for all port configurations (including architectures, graphics types, etc).
1412     def _port_flag_for_scripts(self):
1413         # This is overrriden by ports which need a flag passed to scripts to distinguish the use of that port.
1414         return None
1415
1416     # This is modeled after webkitdirs.pm argumentsForConfiguration() from old-run-webkit-tests
1417     def _arguments_for_configuration(self):
1418         config_args = []
1419         config_args.append(self._config.flag_for_configuration(self.get_option('configuration')))
1420         # FIXME: We may need to add support for passing --32-bit like old-run-webkit-tests had.
1421         port_flag = self._port_flag_for_scripts()
1422         if port_flag:
1423             config_args.append(port_flag)
1424         return config_args
1425
1426     def _run_script(self, script_name, args=None, include_configuration_arguments=True, decode_output=True, env=None):
1427         run_script_command = [self.path_to_script(script_name)]
1428         if include_configuration_arguments:
1429             run_script_command.extend(self._arguments_for_configuration())
1430         if args:
1431             run_script_command.extend(args)
1432         output = self._executive.run_command(run_script_command, cwd=self.webkit_base(), decode_output=decode_output, env=env)
1433         _log.debug('Output of %s:\n%s' % (run_script_command, output))
1434         return output
1435
1436     def _build_driver(self):
1437         environment = self.host.copy_current_environment()
1438         environment.disable_gcc_smartquotes()
1439         env = environment.to_dictionary()
1440
1441         # FIXME: We build both DumpRenderTree and WebKitTestRunner for
1442         # WebKitTestRunner runs because DumpRenderTree still includes
1443         # the DumpRenderTreeSupport module and the TestNetscapePlugin.
1444         # These two projects should be factored out into their own
1445         # projects.
1446         try:
1447             self._run_script("build-dumprendertree", args=self._build_driver_flags(), env=env)
1448             if self.get_option('webkit_test_runner'):
1449                 self._run_script("build-webkittestrunner", args=self._build_driver_flags(), env=env)
1450         except ScriptError, e:
1451             _log.error(e.message_with_output(output_limit=None))
1452             return False
1453         return True
1454
1455     def _build_driver_flags(self):
1456         return []
1457
1458     def test_search_path(self):
1459         return self.baseline_search_path()
1460
1461     def _tests_for_other_platforms(self):
1462         # By default we will skip any directory under LayoutTests/platform
1463         # that isn't in our baseline search path (this mirrors what
1464         # old-run-webkit-tests does in findTestsToRun()).
1465         # Note this returns LayoutTests/platform/*, not platform/*/*.
1466         entries = self._filesystem.glob(self._webkit_baseline_path('*'))
1467         dirs_to_skip = []
1468         for entry in entries:
1469             if self._filesystem.isdir(entry) and entry not in self.test_search_path():
1470                 basename = self._filesystem.basename(entry)
1471                 dirs_to_skip.append('platform/%s' % basename)
1472         return dirs_to_skip
1473
1474     def _runtime_feature_list(self):
1475         """If a port makes certain features available only through runtime flags, it can override this routine to indicate which ones are available."""
1476         return None
1477
1478     def nm_command(self):
1479         return 'nm'
1480
1481     def _modules_to_search_for_symbols(self):
1482         path = self._path_to_webcore_library()
1483         if path:
1484             return [path]
1485         return []
1486
1487     def _symbols_string(self):
1488         symbols = ''
1489         for path_to_module in self._modules_to_search_for_symbols():
1490             try:
1491                 symbols += self._executive.run_command([self.nm_command(), path_to_module], error_handler=self._executive.ignore_error)
1492             except OSError, e:
1493                 _log.warn("Failed to run nm: %s.  Can't determine supported features correctly." % e)
1494         return symbols
1495
1496     # Ports which use run-time feature detection should define this method and return
1497     # a dictionary mapping from Feature Names to skipped directoires.  NRWT will
1498     # run DumpRenderTree --print-supported-features and parse the output.
1499     # If the Feature Names are not found in the output, the corresponding directories
1500     # will be skipped.
1501     def _missing_feature_to_skipped_tests(self):
1502         """Return the supported feature dictionary. Keys are feature names and values
1503         are the lists of directories to skip if the feature name is not matched."""
1504         # FIXME: This list matches WebKitWin and should be moved onto the Win port.
1505         return {
1506             "Accelerated Compositing": ["compositing"],
1507             "3D Rendering": ["animations/3d", "transforms/3d"],
1508         }
1509
1510     def _has_test_in_directories(self, directory_lists, test_list):
1511         if not test_list:
1512             return False
1513
1514         directories = itertools.chain.from_iterable(directory_lists)
1515         for directory, test in itertools.product(directories, test_list):
1516             if test.startswith(directory):
1517                 return True
1518         return False
1519
1520     def _skipped_tests_for_unsupported_features(self, test_list):
1521         # Only check the runtime feature list of there are tests in the test_list that might get skipped.
1522         # This is a performance optimization to avoid the subprocess call to DRT.
1523         # If the port supports runtime feature detection, disable any tests
1524         # for features missing from the runtime feature list.
1525         # If _runtime_feature_list returns a non-None value, then prefer
1526         # runtime feature detection over static feature detection.
1527         if self._has_test_in_directories(self._missing_feature_to_skipped_tests().values(), test_list):
1528             supported_feature_list = self._runtime_feature_list()
1529             if supported_feature_list is not None:
1530                 return reduce(operator.add, [directories for feature, directories in self._missing_feature_to_skipped_tests().items() if feature not in supported_feature_list])
1531
1532         return []
1533
1534     def _wk2_port_name(self):
1535         # By current convention, the WebKit2 name is always mac-wk2, win-wk2, not mac-leopard-wk2, etc,
1536         return "%s-wk2" % self.port_name
1537
1538
1539 class VirtualTestSuite(object):
1540     def __init__(self, name, base, args, tests=None):
1541         self.name = name
1542         self.base = base
1543         self.args = args
1544         self.tests = tests or set()
1545
1546     def __repr__(self):
1547         return "VirtualTestSuite('%s', '%s', %s)" % (self.name, self.base, self.args)