There should be a way to specify additional repository paths in NRWT
[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         repository_paths = [('WebKit', self.layout_tests_dir())]
1094         if self.get_option('additional_repository_name') and self.get_option('additional_repository_path'):
1095             repository_paths += [(self._options.additional_repository_name, self.path_from_webkit_base(self._options.additional_repository_path))]
1096         return repository_paths
1097
1098     _WDIFF_DEL = '##WDIFF_DEL##'
1099     _WDIFF_ADD = '##WDIFF_ADD##'
1100     _WDIFF_END = '##WDIFF_END##'
1101
1102     def _format_wdiff_output_as_html(self, wdiff):
1103         wdiff = cgi.escape(wdiff)
1104         wdiff = wdiff.replace(self._WDIFF_DEL, "<span class=del>")
1105         wdiff = wdiff.replace(self._WDIFF_ADD, "<span class=add>")
1106         wdiff = wdiff.replace(self._WDIFF_END, "</span>")
1107         html = "<head><style>.del { background: #faa; } "
1108         html += ".add { background: #afa; }</style></head>"
1109         html += "<pre>%s</pre>" % wdiff
1110         return html
1111
1112     def _wdiff_command(self, actual_filename, expected_filename):
1113         executable = self._path_to_wdiff()
1114         return [executable,
1115                 "--start-delete=%s" % self._WDIFF_DEL,
1116                 "--end-delete=%s" % self._WDIFF_END,
1117                 "--start-insert=%s" % self._WDIFF_ADD,
1118                 "--end-insert=%s" % self._WDIFF_END,
1119                 actual_filename,
1120                 expected_filename]
1121
1122     @staticmethod
1123     def _handle_wdiff_error(script_error):
1124         # Exit 1 means the files differed, any other exit code is an error.
1125         if script_error.exit_code != 1:
1126             raise script_error
1127
1128     def _run_wdiff(self, actual_filename, expected_filename):
1129         """Runs wdiff and may throw exceptions.
1130         This is mostly a hook for unit testing."""
1131         # Diffs are treated as binary as they may include multiple files
1132         # with conflicting encodings.  Thus we do not decode the output.
1133         command = self._wdiff_command(actual_filename, expected_filename)
1134         wdiff = self._executive.run_command(command, decode_output=False,
1135             error_handler=self._handle_wdiff_error)
1136         return self._format_wdiff_output_as_html(wdiff)
1137
1138     def wdiff_text(self, actual_filename, expected_filename):
1139         """Returns a string of HTML indicating the word-level diff of the
1140         contents of the two filenames. Returns an empty string if word-level
1141         diffing isn't available."""
1142         if not self.wdiff_available():
1143             return ""
1144         try:
1145             # It's possible to raise a ScriptError we pass wdiff invalid paths.
1146             return self._run_wdiff(actual_filename, expected_filename)
1147         except OSError, e:
1148             if e.errno in [errno.ENOENT, errno.EACCES, errno.ECHILD]:
1149                 # Silently ignore cases where wdiff is missing.
1150                 self._wdiff_available = False
1151                 return ""
1152             raise
1153
1154     # This is a class variable so we can test error output easily.
1155     _pretty_patch_error_html = "Failed to run PrettyPatch, see error log."
1156
1157     def pretty_patch_text(self, diff_path):
1158         if self._pretty_patch_available is None:
1159             self._pretty_patch_available = self.check_pretty_patch(logging=False)
1160         if not self._pretty_patch_available:
1161             return self._pretty_patch_error_html
1162         command = ("ruby", "-I", self._filesystem.dirname(self._pretty_patch_path),
1163                    self._pretty_patch_path, diff_path)
1164         try:
1165             # Diffs are treated as binary (we pass decode_output=False) as they
1166             # may contain multiple files of conflicting encodings.
1167             return self._executive.run_command(command, decode_output=False)
1168         except OSError, e:
1169             # If the system is missing ruby log the error and stop trying.
1170             self._pretty_patch_available = False
1171             _log.error("Failed to run PrettyPatch (%s): %s" % (command, e))
1172             return self._pretty_patch_error_html
1173         except ScriptError, e:
1174             # If ruby failed to run for some reason, log the command
1175             # output and stop trying.
1176             self._pretty_patch_available = False
1177             _log.error("Failed to run PrettyPatch (%s):\n%s" % (command, e.message_with_output()))
1178             return self._pretty_patch_error_html
1179
1180     def default_configuration(self):
1181         return self._config.default_configuration()
1182
1183     #
1184     # PROTECTED ROUTINES
1185     #
1186     # The routines below should only be called by routines in this class
1187     # or any of its subclasses.
1188     #
1189
1190     def _uses_apache(self):
1191         return True
1192
1193     # FIXME: This does not belong on the port object.
1194     @memoized
1195     def _path_to_apache(self):
1196         """Returns the full path to the apache binary.
1197
1198         This is needed only by ports that use the apache_http_server module."""
1199         # The Apache binary path can vary depending on OS and distribution
1200         # See http://wiki.apache.org/httpd/DistrosDefaultLayout
1201         for path in ["/usr/sbin/httpd", "/usr/sbin/apache2"]:
1202             if self._filesystem.exists(path):
1203                 return path
1204         _log.error("Could not find apache. Not installed or unknown path.")
1205         return None
1206
1207     # FIXME: This belongs on some platform abstraction instead of Port.
1208     def _is_redhat_based(self):
1209         return self._filesystem.exists('/etc/redhat-release')
1210
1211     def _is_debian_based(self):
1212         return self._filesystem.exists('/etc/debian_version')
1213
1214     def _is_arch_based(self):
1215         return self._filesystem.exists('/etc/arch-release')
1216
1217     def _apache_version(self):
1218         config = self._executive.run_command([self._path_to_apache(), '-v'])
1219         return re.sub(r'(?:.|\n)*Server version: Apache/(\d+\.\d+)(?:.|\n)*', r'\1', config)
1220
1221     # We pass sys_platform into this method to make it easy to unit test.
1222     def _apache_config_file_name_for_platform(self, sys_platform):
1223         if sys_platform == 'cygwin':
1224             return 'cygwin-httpd.conf'  # CYGWIN is the only platform to still use Apache 1.3.
1225         if sys_platform.startswith('linux'):
1226             if self._is_redhat_based():
1227                 return 'fedora-httpd-' + self._apache_version() + '.conf'
1228             if self._is_debian_based():
1229                 return 'debian-httpd-' + self._apache_version() + '.conf'
1230             if self._is_arch_based():
1231                 return 'archlinux-httpd.conf'
1232         # All platforms use apache2 except for CYGWIN (and Mac OS X Tiger and prior, which we no longer support).
1233         return "apache2-httpd.conf"
1234
1235     def _path_to_apache_config_file(self):
1236         """Returns the full path to the apache configuration file.
1237
1238         If the WEBKIT_HTTP_SERVER_CONF_PATH environment variable is set, its
1239         contents will be used instead.
1240
1241         This is needed only by ports that use the apache_http_server module."""
1242         config_file_from_env = os.environ.get('WEBKIT_HTTP_SERVER_CONF_PATH')
1243         if config_file_from_env:
1244             if not self._filesystem.exists(config_file_from_env):
1245                 raise IOError('%s was not found on the system' % config_file_from_env)
1246             return config_file_from_env
1247
1248         config_file_name = self._apache_config_file_name_for_platform(sys.platform)
1249         return self._filesystem.join(self.layout_tests_dir(), 'http', 'conf', config_file_name)
1250
1251     def _build_path(self, *comps):
1252         root_directory = self.get_option('root')
1253         if not root_directory:
1254             build_directory = self.get_option('build_directory')
1255             if build_directory:
1256                 root_directory = self._filesystem.join(build_directory, self.get_option('configuration'))
1257             else:
1258                 root_directory = self._config.build_directory(self.get_option('configuration'))
1259             # Set --root so that we can pass this to subprocesses and avoid making the
1260             # slow call to config.build_directory() N times in each worker.
1261             # FIXME: This is like @memoized, but more annoying and fragile; there should be another
1262             # way to propagate values without mutating the options list.
1263             self.set_option_default('root', root_directory)
1264         return self._filesystem.join(self._filesystem.abspath(root_directory), *comps)
1265
1266     def _path_to_driver(self, configuration=None):
1267         """Returns the full path to the test driver (DumpRenderTree)."""
1268         return self._build_path(self.driver_name())
1269
1270     def _driver_tempdir(self):
1271         return self._filesystem.mkdtemp(prefix='%s-' % self.driver_name())
1272
1273     def _driver_tempdir_for_environment(self):
1274         return self._driver_tempdir()
1275
1276     def _path_to_webcore_library(self):
1277         """Returns the full path to a built copy of WebCore."""
1278         return None
1279
1280     def _path_to_helper(self):
1281         """Returns the full path to the layout_test_helper binary, which
1282         is used to help configure the system for the test run, or None
1283         if no helper is needed.
1284
1285         This is likely only used by start/stop_helper()."""
1286         return None
1287
1288     def _path_to_image_diff(self):
1289         """Returns the full path to the image_diff binary, or None if it is not available.
1290
1291         This is likely used only by diff_image()"""
1292         return self._build_path('ImageDiff')
1293
1294     def _path_to_lighttpd(self):
1295         """Returns the path to the LigHTTPd binary.
1296
1297         This is needed only by ports that use the http_server.py module."""
1298         raise NotImplementedError('Port._path_to_lighttpd')
1299
1300     def _path_to_lighttpd_modules(self):
1301         """Returns the path to the LigHTTPd modules directory.
1302
1303         This is needed only by ports that use the http_server.py module."""
1304         raise NotImplementedError('Port._path_to_lighttpd_modules')
1305
1306     def _path_to_lighttpd_php(self):
1307         """Returns the path to the LigHTTPd PHP executable.
1308
1309         This is needed only by ports that use the http_server.py module."""
1310         raise NotImplementedError('Port._path_to_lighttpd_php')
1311
1312     @memoized
1313     def _path_to_wdiff(self):
1314         """Returns the full path to the wdiff binary, or None if it is not available.
1315
1316         This is likely used only by wdiff_text()"""
1317         for path in ("/usr/bin/wdiff", "/usr/bin/dwdiff"):
1318             if self._filesystem.exists(path):
1319                 return path
1320         return None
1321
1322     def _webkit_baseline_path(self, platform):
1323         """Return the  full path to the top of the baseline tree for a
1324         given platform."""
1325         return self._filesystem.join(self.layout_tests_dir(), 'platform', platform)
1326
1327     # FIXME: Belongs on a Platform object.
1328     def _generate_all_test_configurations(self):
1329         """Generates a list of TestConfiguration instances, representing configurations
1330         for a platform across all OSes, architectures, build and graphics types."""
1331         raise NotImplementedError('Port._generate_test_configurations')
1332
1333     def _driver_class(self):
1334         """Returns the port's driver implementation."""
1335         return driver.Driver
1336
1337     def _get_crash_log(self, name, pid, stdout, stderr, newer_than):
1338         name_str = name or '<unknown process name>'
1339         pid_str = str(pid or '<unknown>')
1340         stdout_lines = (stdout or '<empty>').decode('utf8', 'replace').splitlines()
1341         stderr_lines = (stderr or '<empty>').decode('utf8', 'replace').splitlines()
1342         return (stderr, 'crash log for %s (pid %s):\n%s\n%s\n' % (name_str, pid_str,
1343             '\n'.join(('STDOUT: ' + l) for l in stdout_lines),
1344             '\n'.join(('STDERR: ' + l) for l in stderr_lines)))
1345
1346     def look_for_new_crash_logs(self, crashed_processes, start_time):
1347         pass
1348
1349     def look_for_new_samples(self, unresponsive_processes, start_time):
1350         pass
1351
1352     def sample_process(self, name, pid):
1353         pass
1354
1355     def virtual_test_suites(self):
1356         return []
1357
1358     def find_system_pid(self, name, pid):
1359         # This is only overridden on Windows
1360         return pid
1361
1362     @memoized
1363     def populated_virtual_test_suites(self):
1364         suites = self.virtual_test_suites()
1365
1366         # Sanity-check the suites to make sure they don't point to other suites.
1367         suite_dirs = [suite.name for suite in suites]
1368         for suite in suites:
1369             assert suite.base not in suite_dirs
1370
1371         for suite in suites:
1372             base_tests = self._real_tests([suite.base])
1373             suite.tests = {}
1374             for test in base_tests:
1375                 suite.tests[test.replace(suite.base, suite.name, 1)] = test
1376         return suites
1377
1378     def _virtual_tests(self, paths, suites):
1379         virtual_tests = list()
1380         for suite in suites:
1381             if paths:
1382                 for test in suite.tests:
1383                     if any(test.startswith(p) for p in paths):
1384                         virtual_tests.append(test)
1385             else:
1386                 virtual_tests.extend(suite.tests.keys())
1387         return virtual_tests
1388
1389     def lookup_virtual_test_base(self, test_name):
1390         for suite in self.populated_virtual_test_suites():
1391             if test_name.startswith(suite.name):
1392                 return test_name.replace(suite.name, suite.base, 1)
1393         return None
1394
1395     def lookup_virtual_test_args(self, test_name):
1396         for suite in self.populated_virtual_test_suites():
1397             if test_name.startswith(suite.name):
1398                 return suite.args
1399         return []
1400
1401     def should_run_as_pixel_test(self, test_input):
1402         if not self._options.pixel_tests:
1403             return False
1404         if self._options.pixel_test_directories:
1405             return any(test_input.test_name.startswith(directory) for directory in self._options.pixel_test_directories)
1406         return self._should_run_as_pixel_test(test_input)
1407
1408     def _should_run_as_pixel_test(self, test_input):
1409         # Default behavior is to allow all test to run as pixel tests if --pixel-tests is on and
1410         # --pixel-test-directory is not specified.
1411         return True
1412
1413     # FIXME: Eventually we should standarize port naming, and make this method smart enough
1414     # to use for all port configurations (including architectures, graphics types, etc).
1415     def _port_flag_for_scripts(self):
1416         # This is overrriden by ports which need a flag passed to scripts to distinguish the use of that port.
1417         return None
1418
1419     # This is modeled after webkitdirs.pm argumentsForConfiguration() from old-run-webkit-tests
1420     def _arguments_for_configuration(self):
1421         config_args = []
1422         config_args.append(self._config.flag_for_configuration(self.get_option('configuration')))
1423         # FIXME: We may need to add support for passing --32-bit like old-run-webkit-tests had.
1424         port_flag = self._port_flag_for_scripts()
1425         if port_flag:
1426             config_args.append(port_flag)
1427         return config_args
1428
1429     def _run_script(self, script_name, args=None, include_configuration_arguments=True, decode_output=True, env=None):
1430         run_script_command = [self.path_to_script(script_name)]
1431         if include_configuration_arguments:
1432             run_script_command.extend(self._arguments_for_configuration())
1433         if args:
1434             run_script_command.extend(args)
1435         output = self._executive.run_command(run_script_command, cwd=self.webkit_base(), decode_output=decode_output, env=env)
1436         _log.debug('Output of %s:\n%s' % (run_script_command, output))
1437         return output
1438
1439     def _build_driver(self):
1440         environment = self.host.copy_current_environment()
1441         environment.disable_gcc_smartquotes()
1442         env = environment.to_dictionary()
1443
1444         # FIXME: We build both DumpRenderTree and WebKitTestRunner for
1445         # WebKitTestRunner runs because DumpRenderTree still includes
1446         # the DumpRenderTreeSupport module and the TestNetscapePlugin.
1447         # These two projects should be factored out into their own
1448         # projects.
1449         try:
1450             self._run_script("build-dumprendertree", args=self._build_driver_flags(), env=env)
1451             if self.get_option('webkit_test_runner'):
1452                 self._run_script("build-webkittestrunner", args=self._build_driver_flags(), env=env)
1453         except ScriptError, e:
1454             _log.error(e.message_with_output(output_limit=None))
1455             return False
1456         return True
1457
1458     def _build_driver_flags(self):
1459         return []
1460
1461     def test_search_path(self):
1462         return self.baseline_search_path()
1463
1464     def _tests_for_other_platforms(self):
1465         # By default we will skip any directory under LayoutTests/platform
1466         # that isn't in our baseline search path (this mirrors what
1467         # old-run-webkit-tests does in findTestsToRun()).
1468         # Note this returns LayoutTests/platform/*, not platform/*/*.
1469         entries = self._filesystem.glob(self._webkit_baseline_path('*'))
1470         dirs_to_skip = []
1471         for entry in entries:
1472             if self._filesystem.isdir(entry) and entry not in self.test_search_path():
1473                 basename = self._filesystem.basename(entry)
1474                 dirs_to_skip.append('platform/%s' % basename)
1475         return dirs_to_skip
1476
1477     def _runtime_feature_list(self):
1478         """If a port makes certain features available only through runtime flags, it can override this routine to indicate which ones are available."""
1479         return None
1480
1481     def nm_command(self):
1482         return 'nm'
1483
1484     def _modules_to_search_for_symbols(self):
1485         path = self._path_to_webcore_library()
1486         if path:
1487             return [path]
1488         return []
1489
1490     def _symbols_string(self):
1491         symbols = ''
1492         for path_to_module in self._modules_to_search_for_symbols():
1493             try:
1494                 symbols += self._executive.run_command([self.nm_command(), path_to_module], error_handler=self._executive.ignore_error)
1495             except OSError, e:
1496                 _log.warn("Failed to run nm: %s.  Can't determine supported features correctly." % e)
1497         return symbols
1498
1499     # Ports which use run-time feature detection should define this method and return
1500     # a dictionary mapping from Feature Names to skipped directoires.  NRWT will
1501     # run DumpRenderTree --print-supported-features and parse the output.
1502     # If the Feature Names are not found in the output, the corresponding directories
1503     # will be skipped.
1504     def _missing_feature_to_skipped_tests(self):
1505         """Return the supported feature dictionary. Keys are feature names and values
1506         are the lists of directories to skip if the feature name is not matched."""
1507         # FIXME: This list matches WebKitWin and should be moved onto the Win port.
1508         return {
1509             "Accelerated Compositing": ["compositing"],
1510             "3D Rendering": ["animations/3d", "transforms/3d"],
1511         }
1512
1513     def _has_test_in_directories(self, directory_lists, test_list):
1514         if not test_list:
1515             return False
1516
1517         directories = itertools.chain.from_iterable(directory_lists)
1518         for directory, test in itertools.product(directories, test_list):
1519             if test.startswith(directory):
1520                 return True
1521         return False
1522
1523     def _skipped_tests_for_unsupported_features(self, test_list):
1524         # Only check the runtime feature list of there are tests in the test_list that might get skipped.
1525         # This is a performance optimization to avoid the subprocess call to DRT.
1526         # If the port supports runtime feature detection, disable any tests
1527         # for features missing from the runtime feature list.
1528         # If _runtime_feature_list returns a non-None value, then prefer
1529         # runtime feature detection over static feature detection.
1530         if self._has_test_in_directories(self._missing_feature_to_skipped_tests().values(), test_list):
1531             supported_feature_list = self._runtime_feature_list()
1532             if supported_feature_list is not None:
1533                 return reduce(operator.add, [directories for feature, directories in self._missing_feature_to_skipped_tests().items() if feature not in supported_feature_list])
1534
1535         return []
1536
1537     def _wk2_port_name(self):
1538         # By current convention, the WebKit2 name is always mac-wk2, win-wk2, not mac-leopard-wk2, etc,
1539         return "%s-wk2" % self.port_name
1540
1541
1542 class VirtualTestSuite(object):
1543     def __init__(self, name, base, args, tests=None):
1544         self.name = name
1545         self.base = base
1546         self.args = args
1547         self.tests = tests or set()
1548
1549     def __repr__(self):
1550         return "VirtualTestSuite('%s', '%s', %s)" % (self.name, self.base, self.args)