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