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