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