2
# Copyright (C) 2010 Google Inc. All rights reserved.
4
# Redistribution and use in source and binary forms, with or without
5
# modification, are permitted provided that the following conditions are
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
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.
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.
30
"""Abstract base class of Port-specific entry points for the layout tests
31
test infrastructure (the Port and Driver classes)."""
45
from collections import OrderedDict
47
# Needed for Python < 2.7
48
from webkitpy.thirdparty.ordered_dict import OrderedDict
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
69
_log = logging.getLogger(__name__)
72
# FIXME: This class should merge with WebKitPort now that Chromium behaves mostly like other webkit ports.
74
"""Abstract class for Port-specific hooks for the layout_test package."""
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.
80
# FIXME: We should probably rename this to something like 'implementation_name'.
83
# Test names resemble unix relative paths, and use '/' as a directory separator.
84
TEST_PATH_SEPARATOR = '/'
86
ALL_BUILD_TYPES = ('debug', 'release')
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.
94
def __init__(self, host, port_name=None, options=None, **kwargs):
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').
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
102
# These are default values that should be overridden in a subclasses.
104
self._architecture = 'x86'
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()
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)
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?
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.
134
# http://mail.python.org/pipermail/python-list/
135
# 2008-August/505753.html
136
# http://bugs.python.org/issue3210
137
self._wdiff_available = None
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
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
150
def additional_drt_flag(self):
153
def default_pixel_tests(self):
154
# FIXME: Disable until they are run by default on build.webkit.org.
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.
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()
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
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
180
def should_retry_crashes(self):
183
def default_child_processes(self):
184
"""Return the number of DumpRenderTree instances to use for this port."""
185
return self._executive.cpu_count()
187
def default_max_locked_shards(self):
188
"""Return the number of "locked" shards to run in parallel (like the http tests)."""
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 .
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()
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)
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]
212
def baseline_search_path(self):
213
return self.get_option('additional_platform_directory', []) + self._compare_baseline() + self.default_baseline_search_path()
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."""
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)
227
def _compare_baseline(self):
228
factory = PortFactory(self.host)
229
target_port = self.get_option('compare_port')
231
return factory.get(target_port).default_baseline_search_path()
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():
240
if not self._check_driver():
242
if self.get_option('pixel_tests'):
243
if not self.check_image_diff():
245
if not self._check_port_build():
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))
256
def _check_port_build(self):
257
# Ports can override this method to do additional checks.
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.
265
Returns whether the system is properly configured."""
267
return self.check_httpd()
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)
278
def check_pretty_patch(self, logging=True):
279
"""Checks whether we can use the PrettyPatch ruby script."""
281
_ = self._executive.run_command(['ruby', '--version'])
283
if e.errno in [errno.ENOENT, errno.EACCES, errno.ECHILD]:
285
_log.warning("Ruby is not installed; can't generate pretty patches.")
289
if not self._filesystem.exists(self._pretty_patch_path):
291
_log.warning("Unable to find %s; can't generate pretty patches." % self._pretty_patch_path)
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.
303
_ = self._executive.run_command([self._path_to_wdiff(), '--help'])
306
message = self._wdiff_missing_message()
308
for line in message.splitlines():
309
_log.warning(' ' + line)
315
def _wdiff_missing_message(self):
316
return 'wdiff is not installed; please install it to generate word-by-word diffs.'
318
def check_httpd(self):
319
if self._uses_apache():
320
httpd_path = self._path_to_apache()
322
httpd_path = self._path_to_lighttpd()
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.")
332
_log.error("No httpd found. Cannot run http tests.")
335
def do_text_results_differ(self, expected_text, actual_text):
336
return expected_text != actual_text
338
def do_audio_results_differ(self, expected_audio, actual_audio):
339
return expected_audio != actual_audio
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.
344
|tolerance| should be a percentage value (0.0 - 100.0).
345
If it is omitted, the port default tolerance value is used.
347
If an error occurs (like ImageDiff isn't found, or crashes, we log an error and return True (for a diff).
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)
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."""
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')
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),
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.
386
def print_leaks_summary(self):
387
# Subclasses can override this to print a summary of leaks found
388
# while running the layout tests.
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'
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.
404
reference_files = self.reference_files(test_name)
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])
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
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')
419
def expected_baselines(self, test_name, suffix, all_baselines=False):
420
"""Given a test name, finds where the baseline results are located.
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
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.
430
a list of ( platform_dir, results_filename ), where
431
platform_dir - abs path to the top of the results tree (or test
433
results_filename - relative path from top of tree to the results
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.
442
This routine is generic but lives here since it is used in
443
conjunction with the other baseline and filename routines that are
446
baseline_filename = self._filesystem.splitext(test_name)[0] + '-expected' + suffix
447
baseline_search_path = self.baseline_search_path()
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))
454
if not all_baselines and baselines:
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))
466
return [(None, baseline_filename)]
468
def expected_filename(self, test_name, suffix, return_default=True):
469
"""Given a test name, returns an absolute path to its expected results.
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).
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.
486
This routine is generic but is implemented here to live alongside
487
the other baseline and filename manipulation routines.
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]
492
return self._filesystem.join(platform_dir, baseline_filename)
494
actual_test_name = self.lookup_virtual_test_base(test_name)
496
return self.expected_filename(actual_test_name, suffix)
499
return self._filesystem.join(self.layout_tests_dir(), baseline_filename)
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')
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)
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):
517
return self._filesystem.read_binary_file(baseline_path)
519
def expected_audio(self, test_name):
520
baseline_path = self.expected_filename(test_name, '.wav')
521
if not self._filesystem.exists(baseline_path):
523
return self._filesystem.read_binary_file(baseline_path)
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):
537
text = self._filesystem.read_binary_file(baseline_path)
538
return text.replace("\r\n", "\n")
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]
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):
551
reftest_list_file = filesystem.read_text_file(reftest_list_path)
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:
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)))
563
def reference_files(self, test_name):
564
"""Return a list of expectation (== or !=) and filename pairs"""
566
reftest_list = self._get_reftest_list(test_name)
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))
576
return reftest_list.get(self._filesystem.join(self.layout_tests_dir(), test_name), []) # pylint: disable-msg=E1103
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()))
585
def _expanded_paths(self, 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)]
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)))
596
return expanded_paths
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]
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'])
609
def is_reference_html_file(filesystem, dirname, filename):
610
if filename.startswith('ref-') or filename.endswith('notref-'):
612
filename_wihout_ext, unused = filesystem.splitext(filename)
613
for suffix in ['-expected', '-expected-mismatch', '-ref', '-notref']:
614
if filename_wihout_ext.endswith(suffix):
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
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)
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.
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
635
dirname, basename = self.split_test(test_name)
636
return (self._natural_sort_key(dirname + self.TEST_PATH_SEPARATOR), self._natural_sort_key(basename))
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"]
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
651
return [tryint(chunk) for chunk in re.split('(\d+)', string_to_split)]
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))
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)):
665
base = self.lookup_virtual_test_base(test_name)
666
return base and self._filesystem.isfile(self.abspath_for_test(base))
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)):
674
base = self.lookup_virtual_test_base(test_name)
675
return base and self._filesystem.isdir(self.abspath_for_test(base))
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)
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)
688
return ('', test_name)
689
return (test_name[0:index], test_name[index:])
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('/'):
695
if self.test_isdir(test_name):
696
return test_name + '/'
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'), [])
704
def update_baseline(self, baseline_path, data):
705
"""Updates the baseline for a test.
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.
713
self._filesystem.write_binary_file(baseline_path, data)
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()
719
def path_from_webkit_base(self, *comps):
720
return self._webkit_finder.path_from_webkit_base(*comps)
722
def path_to_script(self, script_name):
723
return self._webkit_finder.path_to_script(script_name)
725
def layout_tests_dir(self):
726
return self._webkit_finder.layout_tests_dir()
728
def perf_tests_dir(self):
729
return self._webkit_finder.perf_tests_dir()
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))
735
def _tests_from_skipped_file_contents(self, skipped_file_contents):
737
for line in skipped_file_contents.split('\n'):
739
line = line.rstrip('/') # Best to normalize directory names to not include the trailing slash.
740
if line.startswith('#') or not len(line):
742
tests_to_skip.append(line)
745
def _expectations_from_skipped_files(self, skipped_file_paths):
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)
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))
758
def skipped_perf_tests(self):
759
return self._expectations_from_skipped_files([self.perf_tests_dir()])
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:
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):
770
def is_chromium(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."""
779
def operating_system(self):
780
# Subclasses should override this default implementation.
784
"""Returns a string indicating the version of a given platform, e.g.
787
This is used to help identify the exact port when parsing test
788
expectations, determining search paths, and logging information."""
791
def architecture(self):
792
return self._architecture
794
def get_option(self, name, default_value=None):
795
return getattr(self._options, name, default_value)
797
def set_option_default(self, name, default_value):
798
return self._options.ensure_value(name, default_value)
801
def path_to_test_expectations_file(self):
802
"""Update the test expectations to the passed-in string.
804
This is used by the rebaselining tool. Raises NotImplementedError
805
if the port does not use expectations files."""
807
# FIXME: We need to remove this when we make rebaselining work with multiple files and just generalize expectations_files().
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'
814
return self._filesystem.join(self._webkit_baseline_path(port_name), 'TestExpectations')
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())
824
return self.host.filesystem.abspath(filename)
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())
830
return self.host.filesystem.abspath(filename)
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)
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
845
def perf_results_directory(self):
846
return self._build_path()
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')
854
def setup_test_run(self):
855
"""Perform port-specific work at the beginning of a test run."""
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
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]
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]
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.
878
variables_to_copy = [
884
'DBUS_SESSION_BUS_ADDRESS',
903
for variable in variables_to_copy:
904
self._copy_value_from_environ_if_set(clean_env, variable)
907
clean_env['DISPLAY'] = self._value_or_default_from_environ('DISPLAY', ':1')
909
for string_variable in self.get_option('additional_env_var', []):
910
[name, value] = string_variable.split('=', 1)
911
clean_env[name] = value
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))
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)
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
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."""
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.
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.'
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)
944
server = http_server.Lighttpd(self, self.results_directory(), additional_dirs=additional_dirs, number_of_servers=number_of_servers)
947
self._http_server = server
949
def start_websocket_server(self):
950
"""Start a web server. Raise an error if it can't start or is already running.
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.'
955
server = websocket_server.PyWebSocket(self, self.results_directory())
957
self._websocket_server = server
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():
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()
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."""
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
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
988
def release_http_lock(self):
990
self._http_lock.cleanup_http_lock()
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']
1000
# TEST EXPECTATION-RELATED METHODS
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
1009
# FIXME: Belongs on a Platform object.
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()
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:
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.
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'])."""
1028
def all_baseline_variants(self):
1029
"""Returns a list of platform names sufficient to cover all the baselines.
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
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())
1041
def warn_if_bug_missing_in_test_expectations(self):
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()
1055
for path in self.expectations_files():
1056
if self._filesystem.exists(path):
1057
expectations[path] = self._filesystem.read_text_file(path)
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)
1065
_log.warning("additional_expectations path '%s' does not exist" % path)
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())
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"])
1080
search_paths.extend(self.get_option("additional_platform_directory", []))
1082
return [self._filesystem.join(self._webkit_baseline_path(d), 'TestExpectations') for d in search_paths]
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."""
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())]
1092
_WDIFF_DEL = '##WDIFF_DEL##'
1093
_WDIFF_ADD = '##WDIFF_ADD##'
1094
_WDIFF_END = '##WDIFF_END##'
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
1106
def _wdiff_command(self, actual_filename, expected_filename):
1107
executable = self._path_to_wdiff()
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,
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:
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)
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():
1139
# It's possible to raise a ScriptError we pass wdiff invalid paths.
1140
return self._run_wdiff(actual_filename, expected_filename)
1142
if e.errno in [errno.ENOENT, errno.EACCES, errno.ECHILD]:
1143
# Silently ignore cases where wdiff is missing.
1144
self._wdiff_available = False
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."
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)
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)
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
1174
def default_configuration(self):
1175
return self._config.default_configuration()
1178
# PROTECTED ROUTINES
1180
# The routines below should only be called by routines in this class
1181
# or any of its subclasses.
1184
def _uses_apache(self):
1187
# FIXME: This does not belong on the port object.
1189
def _path_to_apache(self):
1190
"""Returns the full path to the apache binary.
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):
1198
_log.error("Could not find apache. Not installed or unknown path.")
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')
1205
def _is_debian_based(self):
1206
return self._filesystem.exists('/etc/debian_version')
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"
1220
def _path_to_apache_config_file(self):
1221
"""Returns the full path to the apache configuration file.
1223
If the WEBKIT_HTTP_SERVER_CONF_PATH environment variable is set, its
1224
contents will be used instead.
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
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)
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')
1241
root_directory = self._filesystem.join(build_directory, self.get_option('configuration'))
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)
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())
1255
def _path_to_webcore_library(self):
1256
"""Returns the full path to a built copy of WebCore."""
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.
1264
This is likely only used by start/stop_helper()."""
1267
def _path_to_image_diff(self):
1268
"""Returns the full path to the image_diff binary, or None if it is not available.
1270
This is likely used only by diff_image()"""
1271
return self._build_path('ImageDiff')
1273
def _path_to_lighttpd(self):
1274
"""Returns the path to the LigHTTPd binary.
1276
This is needed only by ports that use the http_server.py module."""
1277
raise NotImplementedError('Port._path_to_lighttpd')
1279
def _path_to_lighttpd_modules(self):
1280
"""Returns the path to the LigHTTPd modules directory.
1282
This is needed only by ports that use the http_server.py module."""
1283
raise NotImplementedError('Port._path_to_lighttpd_modules')
1285
def _path_to_lighttpd_php(self):
1286
"""Returns the path to the LigHTTPd PHP executable.
1288
This is needed only by ports that use the http_server.py module."""
1289
raise NotImplementedError('Port._path_to_lighttpd_php')
1292
def _path_to_wdiff(self):
1293
"""Returns the full path to the wdiff binary, or None if it is not available.
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):
1301
def _webkit_baseline_path(self, platform):
1302
"""Return the full path to the top of the baseline tree for a
1304
return self._filesystem.join(self.layout_tests_dir(), 'platform', platform)
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')
1312
def _driver_class(self):
1313
"""Returns the port's driver implementation."""
1314
return driver.Driver
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)))
1325
def look_for_new_crash_logs(self, crashed_processes, start_time):
1328
def sample_process(self, name, pid):
1331
def virtual_test_suites(self):
1335
def populated_virtual_test_suites(self):
1336
suites = self.virtual_test_suites()
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
1343
for suite in suites:
1344
base_tests = self._real_tests([suite.base])
1346
for test in base_tests:
1347
suite.tests[test.replace(suite.base, suite.name, 1)] = test
1350
def _virtual_tests(self, paths, suites):
1351
virtual_tests = list()
1352
for suite in suites:
1354
for test in suite.tests:
1355
if any(test.startswith(p) for p in paths):
1356
virtual_tests.append(test)
1358
virtual_tests.extend(suite.tests.keys())
1359
return virtual_tests
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)
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):
1373
def should_run_as_pixel_test(self, test_input):
1374
if not self._options.pixel_tests:
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)
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.
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.
1393
# This is modeled after webkitdirs.pm argumentsForConfiguration() from old-run-webkit-tests
1394
def _arguments_for_configuration(self):
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()
1400
config_args.append(port_flag)
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())
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))
1413
def _build_driver(self):
1414
environment = self.host.copy_current_environment()
1415
environment.disable_gcc_smartquotes()
1416
env = environment.to_dictionary()
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
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))
1432
def _build_driver_flags(self):
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('*'))
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)
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."""
1452
def nm_command(self):
1455
def _modules_to_search_for_symbols(self):
1456
path = self._path_to_webcore_library()
1461
def _symbols_string(self):
1463
for path_to_module in self._modules_to_search_for_symbols():
1465
symbols += self._executive.run_command([self.nm_command(), path_to_module], error_handler=self._executive.ignore_error)
1467
_log.warn("Failed to run nm: %s. Can't determine supported features correctly." % e)
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
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.
1480
"Accelerated Compositing": ["compositing"],
1481
"3D Rendering": ["animations/3d", "transforms/3d"],
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."""
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"],
1501
def _has_test_in_directories(self, directory_lists, test_list):
1505
directories = itertools.chain.from_iterable(directory_lists)
1506
for directory, test in itertools.product(directories, test_list):
1507
if test.startswith(directory):
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])
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], [])
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
1540
class VirtualTestSuite(object):
1541
def __init__(self, name, base, args, tests=None):
1545
self.tests = tests or set()
1548
return "VirtualTestSuite('%s', '%s', %s)" % (self.name, self.base, self.args)