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