~ubuntu-branches/ubuntu/raring/qtwebkit-source/raring-proposed

« back to all changes in this revision

Viewing changes to Tools/Scripts/webkitpy/layout_tests/port/base.py

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell
  • Date: 2013-02-18 14:24:18 UTC
  • Revision ID: package-import@ubuntu.com-20130218142418-eon0jmjg3nj438uy
Tags: upstream-2.3
ImportĀ upstreamĀ versionĀ 2.3

Show diffs side-by-side

added added

removed removed

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