Home | History | Annotate | Download | only in port
      1 # Copyright (C) 2010 Google Inc. All rights reserved.
      2 #
      3 # Redistribution and use in source and binary forms, with or without
      4 # modification, are permitted provided that the following conditions are
      5 # met:
      6 #
      7 #     * Redistributions of source code must retain the above copyright
      8 # notice, this list of conditions and the following disclaimer.
      9 #     * Redistributions in binary form must reproduce the above
     10 # copyright notice, this list of conditions and the following disclaimer
     11 # in the documentation and/or other materials provided with the
     12 # distribution.
     13 #     * Neither the Google name nor the names of its
     14 # contributors may be used to endorse or promote products derived from
     15 # this software without specific prior written permission.
     16 #
     17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28 
     29 """Abstract base class of Port-specific entry points for the layout tests
     30 test infrastructure (the Port and Driver classes)."""
     31 
     32 import cgi
     33 import difflib
     34 import errno
     35 import itertools
     36 import logging
     37 import os
     38 import operator
     39 import optparse
     40 import re
     41 import sys
     42 
     43 try:
     44     from collections import OrderedDict
     45 except ImportError:
     46     # Needed for Python < 2.7
     47     from webkitpy.thirdparty.ordered_dict import OrderedDict
     48 
     49 
     50 from webkitpy.common import find_files
     51 from webkitpy.common import read_checksum_from_png
     52 from webkitpy.common.memoized import memoized
     53 from webkitpy.common.system import path
     54 from webkitpy.common.system.executive import ScriptError
     55 from webkitpy.common.system.path import cygpath
     56 from webkitpy.common.system.systemhost import SystemHost
     57 from webkitpy.common.webkit_finder import WebKitFinder
     58 from webkitpy.layout_tests.layout_package.bot_test_expectations import BotTestExpectationsFactory
     59 from webkitpy.layout_tests.models import test_run_results
     60 from webkitpy.layout_tests.models.test_configuration import TestConfiguration
     61 from webkitpy.layout_tests.port import config as port_config
     62 from webkitpy.layout_tests.port import driver
     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., '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     CONTENT_SHELL_NAME = 'content_shell'
     89 
     90     # True if the port as aac and mp3 codecs built in.
     91     PORT_HAS_AUDIO_CODECS_BUILT_IN = False
     92 
     93     ALL_SYSTEMS = (
     94         ('snowleopard', 'x86'),
     95         ('lion', 'x86'),
     96 
     97         # FIXME: We treat Retina (High-DPI) devices as if they are running
     98         # a different operating system version. This isn't accurate, but will work until
     99         # we need to test and support baselines across multiple O/S versions.
    100         ('retina', 'x86'),
    101 
    102         ('mountainlion', 'x86'),
    103         ('mavericks', 'x86'),
    104         ('xp', 'x86'),
    105         ('win7', 'x86'),
    106         ('lucid', 'x86'),
    107         ('lucid', 'x86_64'),
    108         # FIXME: Technically this should be 'arm', but adding a third architecture type breaks TestConfigurationConverter.
    109         # If we need this to be 'arm' in the future, then we first have to fix TestConfigurationConverter.
    110         ('icecreamsandwich', 'x86'),
    111         )
    112 
    113     ALL_BASELINE_VARIANTS = [
    114         'mac-mavericks', 'mac-mountainlion', 'mac-retina', 'mac-lion', 'mac-snowleopard',
    115         'win-win7', 'win-xp',
    116         'linux-x86_64', 'linux-x86',
    117     ]
    118 
    119     CONFIGURATION_SPECIFIER_MACROS = {
    120         'mac': ['snowleopard', 'lion', 'retina', 'mountainlion', 'mavericks'],
    121         'win': ['xp', 'win7'],
    122         'linux': ['lucid'],
    123         'android': ['icecreamsandwich'],
    124     }
    125 
    126     DEFAULT_BUILD_DIRECTORIES = ('out',)
    127 
    128     # overridden in subclasses.
    129     FALLBACK_PATHS = {}
    130 
    131     SUPPORTED_VERSIONS = []
    132 
    133     @classmethod
    134     def latest_platform_fallback_path(cls):
    135         return cls.FALLBACK_PATHS[cls.SUPPORTED_VERSIONS[-1]]
    136 
    137     @classmethod
    138     def _static_build_path(cls, filesystem, build_directory, chromium_base, configuration, comps):
    139         if build_directory:
    140             return filesystem.join(build_directory, configuration, *comps)
    141 
    142         hits = []
    143         for directory in cls.DEFAULT_BUILD_DIRECTORIES:
    144             base_dir = filesystem.join(chromium_base, directory, configuration)
    145             path = filesystem.join(base_dir, *comps)
    146             if filesystem.exists(path):
    147                 hits.append((filesystem.mtime(path), path))
    148 
    149         if hits:
    150             hits.sort(reverse=True)
    151             return hits[0][1]  # Return the newest file found.
    152 
    153         # We have to default to something, so pick the last one.
    154         return filesystem.join(base_dir, *comps)
    155 
    156     @classmethod
    157     def determine_full_port_name(cls, host, options, port_name):
    158         """Return a fully-specified port name that can be used to construct objects."""
    159         # Subclasses will usually override this.
    160         assert port_name.startswith(cls.port_name)
    161         return port_name
    162 
    163     def __init__(self, host, port_name, options=None, **kwargs):
    164 
    165         # This value may be different from cls.port_name by having version modifiers
    166         # and other fields appended to it (for example, 'qt-arm' or 'mac-wk2').
    167         self._name = port_name
    168 
    169         # These are default values that should be overridden in a subclasses.
    170         self._version = ''
    171         self._architecture = 'x86'
    172 
    173         # FIXME: Ideally we'd have a package-wide way to get a
    174         # well-formed options object that had all of the necessary
    175         # options defined on it.
    176         self._options = options or optparse.Values()
    177 
    178         self.host = host
    179         self._executive = host.executive
    180         self._filesystem = host.filesystem
    181         self._webkit_finder = WebKitFinder(host.filesystem)
    182         self._config = port_config.Config(self._executive, self._filesystem, self.port_name)
    183 
    184         self._helper = None
    185         self._http_server = None
    186         self._websocket_server = None
    187         self._image_differ = None
    188         self._server_process_constructor = server_process.ServerProcess  # overridable for testing
    189         self._http_lock = None  # FIXME: Why does this live on the port object?
    190         self._dump_reader = None
    191 
    192         # Python's Popen has a bug that causes any pipes opened to a
    193         # process that can't be executed to be leaked.  Since this
    194         # code is specifically designed to tolerate exec failures
    195         # to gracefully handle cases where wdiff is not installed,
    196         # the bug results in a massive file descriptor leak. As a
    197         # workaround, if an exec failure is ever experienced for
    198         # wdiff, assume it's not available.  This will leak one
    199         # file descriptor but that's better than leaking each time
    200         # wdiff would be run.
    201         #
    202         # http://mail.python.org/pipermail/python-list/
    203         #    2008-August/505753.html
    204         # http://bugs.python.org/issue3210
    205         self._wdiff_available = None
    206 
    207         # FIXME: prettypatch.py knows this path, why is it copied here?
    208         self._pretty_patch_path = self.path_from_webkit_base("Tools", "Scripts", "webkitruby", "PrettyPatch", "prettify.rb")
    209         self._pretty_patch_available = None
    210 
    211         if not hasattr(options, 'configuration') or not options.configuration:
    212             self.set_option_default('configuration', self.default_configuration())
    213         self._test_configuration = None
    214         self._reftest_list = {}
    215         self._results_directory = None
    216 
    217     def buildbot_archives_baselines(self):
    218         return True
    219 
    220     def additional_drt_flag(self):
    221         if self.driver_name() == self.CONTENT_SHELL_NAME:
    222             return ['--dump-render-tree']
    223         return []
    224 
    225     def supports_per_test_timeout(self):
    226         return False
    227 
    228     def default_pixel_tests(self):
    229         return True
    230 
    231     def default_smoke_test_only(self):
    232         return False
    233 
    234     def default_timeout_ms(self):
    235         timeout_ms = 6 * 1000
    236         if self.get_option('configuration') == 'Debug':
    237             # Debug is usually 2x-3x slower than Release.
    238             return 3 * timeout_ms
    239         return timeout_ms
    240 
    241     def driver_stop_timeout(self):
    242         """ Returns the amount of time in seconds to wait before killing the process in driver.stop()."""
    243         # We want to wait for at least 3 seconds, but if we are really slow, we want to be slow on cleanup as
    244         # well (for things like ASAN, Valgrind, etc.)
    245         return 3.0 * float(self.get_option('time_out_ms', '0')) / self.default_timeout_ms()
    246 
    247     def wdiff_available(self):
    248         if self._wdiff_available is None:
    249             self._wdiff_available = self.check_wdiff(logging=False)
    250         return self._wdiff_available
    251 
    252     def pretty_patch_available(self):
    253         if self._pretty_patch_available is None:
    254             self._pretty_patch_available = self.check_pretty_patch(logging=False)
    255         return self._pretty_patch_available
    256 
    257     def default_child_processes(self):
    258         """Return the number of drivers to use for this port."""
    259         return self._executive.cpu_count()
    260 
    261     def default_max_locked_shards(self):
    262         """Return the number of "locked" shards to run in parallel (like the http tests)."""
    263         max_locked_shards = int(self.default_child_processes()) / 4
    264         if not max_locked_shards:
    265             return 1
    266         return max_locked_shards
    267 
    268     def baseline_path(self):
    269         """Return the absolute path to the directory to store new baselines in for this port."""
    270         # FIXME: remove once all callers are calling either baseline_version_dir() or baseline_platform_dir()
    271         return self.baseline_version_dir()
    272 
    273     def baseline_platform_dir(self):
    274         """Return the absolute path to the default (version-independent) platform-specific results."""
    275         return self._filesystem.join(self.layout_tests_dir(), 'platform', self.port_name)
    276 
    277     def baseline_version_dir(self):
    278         """Return the absolute path to the platform-and-version-specific results."""
    279         baseline_search_paths = self.baseline_search_path()
    280         return baseline_search_paths[0]
    281 
    282     def virtual_baseline_search_path(self, test_name):
    283         suite = self.lookup_virtual_suite(test_name)
    284         if not suite:
    285             return None
    286         return [self._filesystem.join(path, suite.name) for path in self.default_baseline_search_path()]
    287 
    288     def baseline_search_path(self):
    289         return self.get_option('additional_platform_directory', []) + self._compare_baseline() + self.default_baseline_search_path()
    290 
    291     def default_baseline_search_path(self):
    292         """Return a list of absolute paths to directories to search under for
    293         baselines. The directories are searched in order."""
    294         return map(self._webkit_baseline_path, self.FALLBACK_PATHS[self.version()])
    295 
    296     @memoized
    297     def _compare_baseline(self):
    298         factory = PortFactory(self.host)
    299         target_port = self.get_option('compare_port')
    300         if target_port:
    301             return factory.get(target_port).default_baseline_search_path()
    302         return []
    303 
    304     def _check_file_exists(self, path_to_file, file_description,
    305                            override_step=None, logging=True):
    306         """Verify the file is present where expected or log an error.
    307 
    308         Args:
    309             file_name: The (human friendly) name or description of the file
    310                 you're looking for (e.g., "HTTP Server"). Used for error logging.
    311             override_step: An optional string to be logged if the check fails.
    312             logging: Whether or not log the error messages."""
    313         if not self._filesystem.exists(path_to_file):
    314             if logging:
    315                 _log.error('Unable to find %s' % file_description)
    316                 _log.error('    at %s' % path_to_file)
    317                 if override_step:
    318                     _log.error('    %s' % override_step)
    319                     _log.error('')
    320             return False
    321         return True
    322 
    323     def check_build(self, needs_http, printer):
    324         result = True
    325 
    326         dump_render_tree_binary_path = self._path_to_driver()
    327         result = self._check_file_exists(dump_render_tree_binary_path,
    328                                          'test driver') and result
    329         if not result and self.get_option('build'):
    330             result = self._check_driver_build_up_to_date(
    331                 self.get_option('configuration'))
    332         else:
    333             _log.error('')
    334 
    335         helper_path = self._path_to_helper()
    336         if helper_path:
    337             result = self._check_file_exists(helper_path,
    338                                              'layout test helper') and result
    339 
    340         if self.get_option('pixel_tests'):
    341             result = self.check_image_diff(
    342                 'To override, invoke with --no-pixel-tests') and result
    343 
    344         # It's okay if pretty patch and wdiff aren't available, but we will at least log messages.
    345         self._pretty_patch_available = self.check_pretty_patch()
    346         self._wdiff_available = self.check_wdiff()
    347 
    348         if self._dump_reader:
    349             result = self._dump_reader.check_is_functional() and result
    350 
    351         return test_run_results.OK_EXIT_STATUS if result else test_run_results.UNEXPECTED_ERROR_EXIT_STATUS
    352 
    353     def _check_driver(self):
    354         driver_path = self._path_to_driver()
    355         if not self._filesystem.exists(driver_path):
    356             _log.error("%s was not found at %s" % (self.driver_name(), driver_path))
    357             return False
    358         return True
    359 
    360     def _check_port_build(self):
    361         # Ports can override this method to do additional checks.
    362         return True
    363 
    364     def check_sys_deps(self, needs_http):
    365         """If the port needs to do some runtime checks to ensure that the
    366         tests can be run successfully, it should override this routine.
    367         This step can be skipped with --nocheck-sys-deps.
    368 
    369         Returns whether the system is properly configured."""
    370         cmd = [self._path_to_driver(), '--check-layout-test-sys-deps']
    371 
    372         local_error = ScriptError()
    373 
    374         def error_handler(script_error):
    375             local_error.exit_code = script_error.exit_code
    376 
    377         output = self._executive.run_command(cmd, error_handler=error_handler)
    378         if local_error.exit_code:
    379             _log.error('System dependencies check failed.')
    380             _log.error('To override, invoke with --nocheck-sys-deps')
    381             _log.error('')
    382             _log.error(output)
    383             return test_run_results.SYS_DEPS_EXIT_STATUS
    384         return test_run_results.OK_EXIT_STATUS
    385 
    386     def check_image_diff(self, override_step=None, logging=True):
    387         """This routine is used to check whether image_diff binary exists."""
    388         image_diff_path = self._path_to_image_diff()
    389         if not self._filesystem.exists(image_diff_path):
    390             _log.error("image_diff was not found at %s" % image_diff_path)
    391             return False
    392         return True
    393 
    394     def check_pretty_patch(self, logging=True):
    395         """Checks whether we can use the PrettyPatch ruby script."""
    396         try:
    397             _ = self._executive.run_command(['ruby', '--version'])
    398         except OSError, e:
    399             if e.errno in [errno.ENOENT, errno.EACCES, errno.ECHILD]:
    400                 if logging:
    401                     _log.warning("Ruby is not installed; can't generate pretty patches.")
    402                     _log.warning('')
    403                 return False
    404 
    405         if not self._filesystem.exists(self._pretty_patch_path):
    406             if logging:
    407                 _log.warning("Unable to find %s; can't generate pretty patches." % self._pretty_patch_path)
    408                 _log.warning('')
    409             return False
    410 
    411         return True
    412 
    413     def check_wdiff(self, logging=True):
    414         if not self._path_to_wdiff():
    415             # Don't need to log here since this is the port choosing not to use wdiff.
    416             return False
    417 
    418         try:
    419             _ = self._executive.run_command([self._path_to_wdiff(), '--help'])
    420         except OSError:
    421             if logging:
    422                 message = self._wdiff_missing_message()
    423                 if message:
    424                     for line in message.splitlines():
    425                         _log.warning('    ' + line)
    426                         _log.warning('')
    427             return False
    428 
    429         return True
    430 
    431     def _wdiff_missing_message(self):
    432         return 'wdiff is not installed; please install it to generate word-by-word diffs.'
    433 
    434     def check_httpd(self):
    435         if self._uses_apache():
    436             httpd_path = self._path_to_apache()
    437         else:
    438             httpd_path = self._path_to_lighttpd()
    439 
    440         try:
    441             server_name = self._filesystem.basename(httpd_path)
    442             env = self.setup_environ_for_server(server_name)
    443             if self._executive.run_command([httpd_path, "-v"], env=env, return_exit_code=True) != 0:
    444                 _log.error("httpd seems broken. Cannot run http tests.")
    445                 return False
    446             return True
    447         except OSError:
    448             _log.error("No httpd found. Cannot run http tests.")
    449             return False
    450 
    451     def do_text_results_differ(self, expected_text, actual_text):
    452         return expected_text != actual_text
    453 
    454     def do_audio_results_differ(self, expected_audio, actual_audio):
    455         return expected_audio != actual_audio
    456 
    457     def diff_image(self, expected_contents, actual_contents):
    458         """Compare two images and return a tuple of an image diff, and an error string.
    459 
    460         If an error occurs (like image_diff isn't found, or crashes, we log an error and return True (for a diff).
    461         """
    462         # If only one of them exists, return that one.
    463         if not actual_contents and not expected_contents:
    464             return (None, None)
    465         if not actual_contents:
    466             return (expected_contents, None)
    467         if not expected_contents:
    468             return (actual_contents, None)
    469 
    470         tempdir = self._filesystem.mkdtemp()
    471 
    472         expected_filename = self._filesystem.join(str(tempdir), "expected.png")
    473         self._filesystem.write_binary_file(expected_filename, expected_contents)
    474 
    475         actual_filename = self._filesystem.join(str(tempdir), "actual.png")
    476         self._filesystem.write_binary_file(actual_filename, actual_contents)
    477 
    478         diff_filename = self._filesystem.join(str(tempdir), "diff.png")
    479 
    480         # image_diff needs native win paths as arguments, so we need to convert them if running under cygwin.
    481         native_expected_filename = self._convert_path(expected_filename)
    482         native_actual_filename = self._convert_path(actual_filename)
    483         native_diff_filename = self._convert_path(diff_filename)
    484 
    485         executable = self._path_to_image_diff()
    486         # Note that although we are handed 'old', 'new', image_diff wants 'new', 'old'.
    487         comand = [executable, '--diff', native_actual_filename, native_expected_filename, native_diff_filename]
    488 
    489         result = None
    490         err_str = None
    491         try:
    492             exit_code = self._executive.run_command(comand, return_exit_code=True)
    493             if exit_code == 0:
    494                 # The images are the same.
    495                 result = None
    496             elif exit_code == 1:
    497                 result = self._filesystem.read_binary_file(native_diff_filename)
    498             else:
    499                 err_str = "image diff returned an exit code of %s" % exit_code
    500         except OSError, e:
    501             err_str = 'error running image diff: %s' % str(e)
    502         finally:
    503             self._filesystem.rmtree(str(tempdir))
    504 
    505         return (result, err_str or None)
    506 
    507     def diff_text(self, expected_text, actual_text, expected_filename, actual_filename):
    508         """Returns a string containing the diff of the two text strings
    509         in 'unified diff' format."""
    510 
    511         # The filenames show up in the diff output, make sure they're
    512         # raw bytes and not unicode, so that they don't trigger join()
    513         # trying to decode the input.
    514         def to_raw_bytes(string_value):
    515             if isinstance(string_value, unicode):
    516                 return string_value.encode('utf-8')
    517             return string_value
    518         expected_filename = to_raw_bytes(expected_filename)
    519         actual_filename = to_raw_bytes(actual_filename)
    520         diff = difflib.unified_diff(expected_text.splitlines(True),
    521                                     actual_text.splitlines(True),
    522                                     expected_filename,
    523                                     actual_filename)
    524         return ''.join(diff)
    525 
    526     def driver_name(self):
    527         if self.get_option('driver_name'):
    528             return self.get_option('driver_name')
    529         return self.CONTENT_SHELL_NAME
    530 
    531     def expected_baselines_by_extension(self, test_name):
    532         """Returns a dict mapping baseline suffix to relative path for each baseline in
    533         a test. For reftests, it returns ".==" or ".!=" instead of the suffix."""
    534         # FIXME: The name similarity between this and expected_baselines() below, is unfortunate.
    535         # We should probably rename them both.
    536         baseline_dict = {}
    537         reference_files = self.reference_files(test_name)
    538         if reference_files:
    539             # FIXME: How should this handle more than one type of reftest?
    540             baseline_dict['.' + reference_files[0][0]] = self.relative_test_filename(reference_files[0][1])
    541 
    542         for extension in self.baseline_extensions():
    543             path = self.expected_filename(test_name, extension, return_default=False)
    544             baseline_dict[extension] = self.relative_test_filename(path) if path else path
    545 
    546         return baseline_dict
    547 
    548     def baseline_extensions(self):
    549         """Returns a tuple of all of the non-reftest baseline extensions we use. The extensions include the leading '.'."""
    550         return ('.wav', '.txt', '.png')
    551 
    552     def expected_baselines(self, test_name, suffix, all_baselines=False):
    553         """Given a test name, finds where the baseline results are located.
    554 
    555         Args:
    556         test_name: name of test file (usually a relative path under LayoutTests/)
    557         suffix: file suffix of the expected results, including dot; e.g.
    558             '.txt' or '.png'.  This should not be None, but may be an empty
    559             string.
    560         all_baselines: If True, return an ordered list of all baseline paths
    561             for the given platform. If False, return only the first one.
    562         Returns
    563         a list of ( platform_dir, results_filename ), where
    564             platform_dir - abs path to the top of the results tree (or test
    565                 tree)
    566             results_filename - relative path from top of tree to the results
    567                 file
    568             (port.join() of the two gives you the full path to the file,
    569                 unless None was returned.)
    570         Return values will be in the format appropriate for the current
    571         platform (e.g., "\\" for path separators on Windows). If the results
    572         file is not found, then None will be returned for the directory,
    573         but the expected relative pathname will still be returned.
    574 
    575         This routine is generic but lives here since it is used in
    576         conjunction with the other baseline and filename routines that are
    577         platform specific.
    578         """
    579         baseline_filename = self._filesystem.splitext(test_name)[0] + '-expected' + suffix
    580         baseline_search_path = self.baseline_search_path()
    581 
    582         baselines = []
    583         for platform_dir in baseline_search_path:
    584             if self._filesystem.exists(self._filesystem.join(platform_dir, baseline_filename)):
    585                 baselines.append((platform_dir, baseline_filename))
    586 
    587             if not all_baselines and baselines:
    588                 return baselines
    589 
    590         # If it wasn't found in a platform directory, return the expected
    591         # result in the test directory, even if no such file actually exists.
    592         platform_dir = self.layout_tests_dir()
    593         if self._filesystem.exists(self._filesystem.join(platform_dir, baseline_filename)):
    594             baselines.append((platform_dir, baseline_filename))
    595 
    596         if baselines:
    597             return baselines
    598 
    599         return [(None, baseline_filename)]
    600 
    601     def expected_filename(self, test_name, suffix, return_default=True):
    602         """Given a test name, returns an absolute path to its expected results.
    603 
    604         If no expected results are found in any of the searched directories,
    605         the directory in which the test itself is located will be returned.
    606         The return value is in the format appropriate for the platform
    607         (e.g., "\\" for path separators on windows).
    608 
    609         Args:
    610         test_name: name of test file (usually a relative path under LayoutTests/)
    611         suffix: file suffix of the expected results, including dot; e.g. '.txt'
    612             or '.png'.  This should not be None, but may be an empty string.
    613         platform: the most-specific directory name to use to build the
    614             search list of directories, e.g., 'win', or
    615             'chromium-cg-mac-leopard' (we follow the WebKit format)
    616         return_default: if True, returns the path to the generic expectation if nothing
    617             else is found; if False, returns None.
    618 
    619         This routine is generic but is implemented here to live alongside
    620         the other baseline and filename manipulation routines.
    621         """
    622         # FIXME: The [0] here is very mysterious, as is the destructured return.
    623         platform_dir, baseline_filename = self.expected_baselines(test_name, suffix)[0]
    624         if platform_dir:
    625             return self._filesystem.join(platform_dir, baseline_filename)
    626 
    627         actual_test_name = self.lookup_virtual_test_base(test_name)
    628         if actual_test_name:
    629             return self.expected_filename(actual_test_name, suffix)
    630 
    631         if return_default:
    632             return self._filesystem.join(self.layout_tests_dir(), baseline_filename)
    633         return None
    634 
    635     def expected_checksum(self, test_name):
    636         """Returns the checksum of the image we expect the test to produce, or None if it is a text-only test."""
    637         png_path = self.expected_filename(test_name, '.png')
    638 
    639         if self._filesystem.exists(png_path):
    640             with self._filesystem.open_binary_file_for_reading(png_path) as filehandle:
    641                 return read_checksum_from_png.read_checksum(filehandle)
    642 
    643         return None
    644 
    645     def expected_image(self, test_name):
    646         """Returns the image we expect the test to produce."""
    647         baseline_path = self.expected_filename(test_name, '.png')
    648         if not self._filesystem.exists(baseline_path):
    649             return None
    650         return self._filesystem.read_binary_file(baseline_path)
    651 
    652     def expected_audio(self, test_name):
    653         baseline_path = self.expected_filename(test_name, '.wav')
    654         if not self._filesystem.exists(baseline_path):
    655             return None
    656         return self._filesystem.read_binary_file(baseline_path)
    657 
    658     def expected_text(self, test_name):
    659         """Returns the text output we expect the test to produce, or None
    660         if we don't expect there to be any text output.
    661         End-of-line characters are normalized to '\n'."""
    662         # FIXME: DRT output is actually utf-8, but since we don't decode the
    663         # output from DRT (instead treating it as a binary string), we read the
    664         # baselines as a binary string, too.
    665         baseline_path = self.expected_filename(test_name, '.txt')
    666         if not self._filesystem.exists(baseline_path):
    667             return None
    668         text = self._filesystem.read_binary_file(baseline_path)
    669         return text.replace("\r\n", "\n")
    670 
    671     def _get_reftest_list(self, test_name):
    672         dirname = self._filesystem.join(self.layout_tests_dir(), self._filesystem.dirname(test_name))
    673         if dirname not in self._reftest_list:
    674             self._reftest_list[dirname] = Port._parse_reftest_list(self._filesystem, dirname)
    675         return self._reftest_list[dirname]
    676 
    677     @staticmethod
    678     def _parse_reftest_list(filesystem, test_dirpath):
    679         reftest_list_path = filesystem.join(test_dirpath, 'reftest.list')
    680         if not filesystem.isfile(reftest_list_path):
    681             return None
    682         reftest_list_file = filesystem.read_text_file(reftest_list_path)
    683 
    684         parsed_list = {}
    685         for line in reftest_list_file.split('\n'):
    686             line = re.sub('#.+$', '', line)
    687             split_line = line.split()
    688             if len(split_line) < 3:
    689                 continue
    690             expectation_type, test_file, ref_file = split_line
    691             parsed_list.setdefault(filesystem.join(test_dirpath, test_file), []).append((expectation_type, filesystem.join(test_dirpath, ref_file)))
    692         return parsed_list
    693 
    694     def reference_files(self, test_name):
    695         """Return a list of expectation (== or !=) and filename pairs"""
    696 
    697         reftest_list = self._get_reftest_list(test_name)
    698         if not reftest_list:
    699             reftest_list = []
    700             for expectation, prefix in (('==', ''), ('!=', '-mismatch')):
    701                 for extention in Port._supported_file_extensions:
    702                     path = self.expected_filename(test_name, prefix + extention)
    703                     if self._filesystem.exists(path):
    704                         reftest_list.append((expectation, path))
    705             return reftest_list
    706 
    707         return reftest_list.get(self._filesystem.join(self.layout_tests_dir(), test_name), [])  # pylint: disable=E1103
    708 
    709     def tests(self, paths):
    710         """Return the list of tests found matching paths."""
    711         tests = self._real_tests(paths)
    712         tests.extend(self._virtual_tests(paths, self.populated_virtual_test_suites()))
    713         return tests
    714 
    715     def _real_tests(self, paths):
    716         # When collecting test cases, skip these directories
    717         skipped_directories = set(['.svn', '_svn', 'platform', 'resources', 'script-tests', 'reference', 'reftest'])
    718         files = find_files.find(self._filesystem, self.layout_tests_dir(), paths, skipped_directories, Port.is_test_file, self.test_key)
    719         return [self.relative_test_filename(f) for f in files]
    720 
    721     # When collecting test cases, we include any file with these extensions.
    722     _supported_file_extensions = set(['.html', '.xml', '.xhtml', '.xht', '.pl',
    723                                       '.htm', '.php', '.svg', '.mht'])
    724 
    725     @staticmethod
    726     # If any changes are made here be sure to update the isUsedInReftest method in old-run-webkit-tests as well.
    727     def is_reference_html_file(filesystem, dirname, filename):
    728         if filename.startswith('ref-') or filename.startswith('notref-'):
    729             return True
    730         filename_wihout_ext, unused = filesystem.splitext(filename)
    731         for suffix in ['-expected', '-expected-mismatch', '-ref', '-notref']:
    732             if filename_wihout_ext.endswith(suffix):
    733                 return True
    734         return False
    735 
    736     @staticmethod
    737     def _has_supported_extension(filesystem, filename):
    738         """Return true if filename is one of the file extensions we want to run a test on."""
    739         extension = filesystem.splitext(filename)[1]
    740         return extension in Port._supported_file_extensions
    741 
    742     @staticmethod
    743     def is_test_file(filesystem, dirname, filename):
    744         return Port._has_supported_extension(filesystem, filename) and not Port.is_reference_html_file(filesystem, dirname, filename)
    745 
    746     ALL_TEST_TYPES = ['audio', 'harness', 'pixel', 'ref', 'text', 'unknown']
    747 
    748     def test_type(self, test_name):
    749         fs = self._filesystem
    750         if fs.exists(self.expected_filename(test_name, '.png')):
    751             return 'pixel'
    752         if fs.exists(self.expected_filename(test_name, '.wav')):
    753             return 'audio'
    754         if self.reference_files(test_name):
    755             return 'ref'
    756         txt = self.expected_text(test_name)
    757         if txt:
    758             if 'layer at (0,0) size 800x600' in txt:
    759                 return 'pixel'
    760             for line in txt.splitlines():
    761                 if line.startswith('FAIL') or line.startswith('TIMEOUT') or line.startswith('PASS'):
    762                     return 'harness'
    763             return 'text'
    764         return 'unknown'
    765 
    766     def test_key(self, test_name):
    767         """Turns a test name into a list with two sublists, the natural key of the
    768         dirname, and the natural key of the basename.
    769 
    770         This can be used when sorting paths so that files in a directory.
    771         directory are kept together rather than being mixed in with files in
    772         subdirectories."""
    773         dirname, basename = self.split_test(test_name)
    774         return (self._natural_sort_key(dirname + self.TEST_PATH_SEPARATOR), self._natural_sort_key(basename))
    775 
    776     def _natural_sort_key(self, string_to_split):
    777         """ Turns a string into a list of string and number chunks, i.e. "z23a" -> ["z", 23, "a"]
    778 
    779         This can be used to implement "natural sort" order. See:
    780         http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html
    781         http://nedbatchelder.com/blog/200712.html#e20071211T054956
    782         """
    783         def tryint(val):
    784             try:
    785                 return int(val)
    786             except ValueError:
    787                 return val
    788 
    789         return [tryint(chunk) for chunk in re.split('(\d+)', string_to_split)]
    790 
    791     def test_dirs(self):
    792         """Returns the list of top-level test directories."""
    793         layout_tests_dir = self.layout_tests_dir()
    794         return filter(lambda x: self._filesystem.isdir(self._filesystem.join(layout_tests_dir, x)),
    795                       self._filesystem.listdir(layout_tests_dir))
    796 
    797     @memoized
    798     def test_isfile(self, test_name):
    799         """Return True if the test name refers to a directory of tests."""
    800         # Used by test_expectations.py to apply rules to whole directories.
    801         if self._filesystem.isfile(self.abspath_for_test(test_name)):
    802             return True
    803         base = self.lookup_virtual_test_base(test_name)
    804         return base and self._filesystem.isfile(self.abspath_for_test(base))
    805 
    806     @memoized
    807     def test_isdir(self, test_name):
    808         """Return True if the test name refers to a directory of tests."""
    809         # Used by test_expectations.py to apply rules to whole directories.
    810         if self._filesystem.isdir(self.abspath_for_test(test_name)):
    811             return True
    812         base = self.lookup_virtual_test_base(test_name)
    813         return base and self._filesystem.isdir(self.abspath_for_test(base))
    814 
    815     @memoized
    816     def test_exists(self, test_name):
    817         """Return True if the test name refers to an existing test or baseline."""
    818         # Used by test_expectations.py to determine if an entry refers to a
    819         # valid test and by printing.py to determine if baselines exist.
    820         return self.test_isfile(test_name) or self.test_isdir(test_name)
    821 
    822     def split_test(self, test_name):
    823         """Splits a test name into the 'directory' part and the 'basename' part."""
    824         index = test_name.rfind(self.TEST_PATH_SEPARATOR)
    825         if index < 1:
    826             return ('', test_name)
    827         return (test_name[0:index], test_name[index:])
    828 
    829     def normalize_test_name(self, test_name):
    830         """Returns a normalized version of the test name or test directory."""
    831         if test_name.endswith('/'):
    832             return test_name
    833         if self.test_isdir(test_name):
    834             return test_name + '/'
    835         return test_name
    836 
    837     def driver_cmd_line(self):
    838         """Prints the DRT command line that will be used."""
    839         driver = self.create_driver(0)
    840         return driver.cmd_line(self.get_option('pixel_tests'), [])
    841 
    842     def update_baseline(self, baseline_path, data):
    843         """Updates the baseline for a test.
    844 
    845         Args:
    846             baseline_path: the actual path to use for baseline, not the path to
    847               the test. This function is used to update either generic or
    848               platform-specific baselines, but we can't infer which here.
    849             data: contents of the baseline.
    850         """
    851         self._filesystem.write_binary_file(baseline_path, data)
    852 
    853     # FIXME: update callers to create a finder and call it instead of these next five routines (which should be protected).
    854     def webkit_base(self):
    855         return self._webkit_finder.webkit_base()
    856 
    857     def path_from_webkit_base(self, *comps):
    858         return self._webkit_finder.path_from_webkit_base(*comps)
    859 
    860     def path_from_chromium_base(self, *comps):
    861         return self._webkit_finder.path_from_chromium_base(*comps)
    862 
    863     def path_to_script(self, script_name):
    864         return self._webkit_finder.path_to_script(script_name)
    865 
    866     def layout_tests_dir(self):
    867         return self._webkit_finder.layout_tests_dir()
    868 
    869     def perf_tests_dir(self):
    870         return self._webkit_finder.perf_tests_dir()
    871 
    872     def skipped_layout_tests(self, test_list):
    873         """Returns tests skipped outside of the TestExpectations files."""
    874         return set(self._skipped_tests_for_unsupported_features(test_list))
    875 
    876     def _tests_from_skipped_file_contents(self, skipped_file_contents):
    877         tests_to_skip = []
    878         for line in skipped_file_contents.split('\n'):
    879             line = line.strip()
    880             line = line.rstrip('/')  # Best to normalize directory names to not include the trailing slash.
    881             if line.startswith('#') or not len(line):
    882                 continue
    883             tests_to_skip.append(line)
    884         return tests_to_skip
    885 
    886     def _expectations_from_skipped_files(self, skipped_file_paths):
    887         tests_to_skip = []
    888         for search_path in skipped_file_paths:
    889             filename = self._filesystem.join(self._webkit_baseline_path(search_path), "Skipped")
    890             if not self._filesystem.exists(filename):
    891                 _log.debug("Skipped does not exist: %s" % filename)
    892                 continue
    893             _log.debug("Using Skipped file: %s" % filename)
    894             skipped_file_contents = self._filesystem.read_text_file(filename)
    895             tests_to_skip.extend(self._tests_from_skipped_file_contents(skipped_file_contents))
    896         return tests_to_skip
    897 
    898     @memoized
    899     def skipped_perf_tests(self):
    900         return self._expectations_from_skipped_files([self.perf_tests_dir()])
    901 
    902     def skips_perf_test(self, test_name):
    903         for test_or_category in self.skipped_perf_tests():
    904             if test_or_category == test_name:
    905                 return True
    906             category = self._filesystem.join(self.perf_tests_dir(), test_or_category)
    907             if self._filesystem.isdir(category) and test_name.startswith(test_or_category):
    908                 return True
    909         return False
    910 
    911     def is_chromium(self):
    912         return True
    913 
    914     def name(self):
    915         """Returns a name that uniquely identifies this particular type of port
    916         (e.g., "mac-snowleopard" or "linux-x86_x64" and can be passed
    917         to factory.get() to instantiate the port."""
    918         return self._name
    919 
    920     def operating_system(self):
    921         # Subclasses should override this default implementation.
    922         return 'mac'
    923 
    924     def version(self):
    925         """Returns a string indicating the version of a given platform, e.g.
    926         'leopard' or 'xp'.
    927 
    928         This is used to help identify the exact port when parsing test
    929         expectations, determining search paths, and logging information."""
    930         return self._version
    931 
    932     def architecture(self):
    933         return self._architecture
    934 
    935     def get_option(self, name, default_value=None):
    936         return getattr(self._options, name, default_value)
    937 
    938     def set_option_default(self, name, default_value):
    939         return self._options.ensure_value(name, default_value)
    940 
    941     @memoized
    942     def path_to_generic_test_expectations_file(self):
    943         return self._filesystem.join(self.layout_tests_dir(), 'TestExpectations')
    944 
    945     def relative_test_filename(self, filename):
    946         """Returns a test_name a relative unix-style path for a filename under the LayoutTests
    947         directory. Ports may legitimately return abspaths here if no relpath makes sense."""
    948         # Ports that run on windows need to override this method to deal with
    949         # filenames with backslashes in them.
    950         if filename.startswith(self.layout_tests_dir()):
    951             return self.host.filesystem.relpath(filename, self.layout_tests_dir())
    952         else:
    953             return self.host.filesystem.abspath(filename)
    954 
    955     @memoized
    956     def abspath_for_test(self, test_name):
    957         """Returns the full path to the file for a given test name. This is the
    958         inverse of relative_test_filename()."""
    959         return self._filesystem.join(self.layout_tests_dir(), test_name)
    960 
    961     def results_directory(self):
    962         """Absolute path to the place to store the test results (uses --results-directory)."""
    963         if not self._results_directory:
    964             option_val = self.get_option('results_directory') or self.default_results_directory()
    965             self._results_directory = self._filesystem.abspath(option_val)
    966         return self._results_directory
    967 
    968     def perf_results_directory(self):
    969         return self._build_path()
    970 
    971     def default_results_directory(self):
    972         """Absolute path to the default place to store the test results."""
    973         try:
    974             return self.path_from_chromium_base('webkit', self.get_option('configuration'), 'layout-test-results')
    975         except AssertionError:
    976             return self._build_path('layout-test-results')
    977 
    978     def setup_test_run(self):
    979         """Perform port-specific work at the beginning of a test run."""
    980         # Delete the disk cache if any to ensure a clean test run.
    981         dump_render_tree_binary_path = self._path_to_driver()
    982         cachedir = self._filesystem.dirname(dump_render_tree_binary_path)
    983         cachedir = self._filesystem.join(cachedir, "cache")
    984         if self._filesystem.exists(cachedir):
    985             self._filesystem.rmtree(cachedir)
    986 
    987         if self._dump_reader:
    988             self._filesystem.maybe_make_directory(self._dump_reader.crash_dumps_directory())
    989 
    990     def num_workers(self, requested_num_workers):
    991         """Returns the number of available workers (possibly less than the number requested)."""
    992         return requested_num_workers
    993 
    994     def clean_up_test_run(self):
    995         """Perform port-specific work at the end of a test run."""
    996         if self._image_differ:
    997             self._image_differ.stop()
    998             self._image_differ = None
    999 
   1000     # FIXME: os.environ access should be moved to onto a common/system class to be more easily mockable.
   1001     def _value_or_default_from_environ(self, name, default=None):
   1002         if name in os.environ:
   1003             return os.environ[name]
   1004         return default
   1005 
   1006     def _copy_value_from_environ_if_set(self, clean_env, name):
   1007         if name in os.environ:
   1008             clean_env[name] = os.environ[name]
   1009 
   1010     def setup_environ_for_server(self, server_name=None):
   1011         # We intentionally copy only a subset of os.environ when
   1012         # launching subprocesses to ensure consistent test results.
   1013         clean_env = {
   1014             'LOCAL_RESOURCE_ROOT': self.layout_tests_dir(),  # FIXME: Is this used?
   1015         }
   1016         variables_to_copy = [
   1017             'WEBKIT_TESTFONTS',  # FIXME: Is this still used?
   1018             'WEBKITOUTPUTDIR',   # FIXME: Is this still used?
   1019             'CHROME_DEVEL_SANDBOX',
   1020             'CHROME_IPC_LOGGING',
   1021             'ASAN_OPTIONS',
   1022             'VALGRIND_LIB',
   1023             'VALGRIND_LIB_INNER',
   1024         ]
   1025         if self.host.platform.is_linux() or self.host.platform.is_freebsd():
   1026             variables_to_copy += [
   1027                 'XAUTHORITY',
   1028                 'HOME',
   1029                 'LANG',
   1030                 'LD_LIBRARY_PATH',
   1031                 'DBUS_SESSION_BUS_ADDRESS',
   1032                 'XDG_DATA_DIRS',
   1033             ]
   1034             clean_env['DISPLAY'] = self._value_or_default_from_environ('DISPLAY', ':1')
   1035         if self.host.platform.is_mac():
   1036             clean_env['DYLD_LIBRARY_PATH'] = self._build_path()
   1037             clean_env['DYLD_FRAMEWORK_PATH'] = self._build_path()
   1038             variables_to_copy += [
   1039                 'HOME',
   1040             ]
   1041         if self.host.platform.is_win():
   1042             variables_to_copy += [
   1043                 'PATH',
   1044                 'GYP_DEFINES',  # Required to locate win sdk.
   1045             ]
   1046         if self.host.platform.is_cygwin():
   1047             variables_to_copy += [
   1048                 'HOMEDRIVE',
   1049                 'HOMEPATH',
   1050                 '_NT_SYMBOL_PATH',
   1051             ]
   1052 
   1053         for variable in variables_to_copy:
   1054             self._copy_value_from_environ_if_set(clean_env, variable)
   1055 
   1056         for string_variable in self.get_option('additional_env_var', []):
   1057             [name, value] = string_variable.split('=', 1)
   1058             clean_env[name] = value
   1059 
   1060         return clean_env
   1061 
   1062     def show_results_html_file(self, results_filename):
   1063         """This routine should display the HTML file pointed at by
   1064         results_filename in a users' browser."""
   1065         return self.host.user.open_url(path.abspath_to_uri(self.host.platform, results_filename))
   1066 
   1067     def create_driver(self, worker_number, no_timeout=False):
   1068         """Return a newly created Driver subclass for starting/stopping the test driver."""
   1069         return self._driver_class()(self, worker_number, pixel_tests=self.get_option('pixel_tests'), no_timeout=no_timeout)
   1070 
   1071     def start_helper(self):
   1072         """If a port needs to reconfigure graphics settings or do other
   1073         things to ensure a known test configuration, it should override this
   1074         method."""
   1075         helper_path = self._path_to_helper()
   1076         if helper_path:
   1077             _log.debug("Starting layout helper %s" % helper_path)
   1078             # Note: Not thread safe: http://bugs.python.org/issue2320
   1079             self._helper = self._executive.popen([helper_path],
   1080                 stdin=self._executive.PIPE, stdout=self._executive.PIPE, stderr=None)
   1081             is_ready = self._helper.stdout.readline()
   1082             if not is_ready.startswith('ready'):
   1083                 _log.error("layout_test_helper failed to be ready")
   1084 
   1085     def requires_http_server(self):
   1086         """Does the port require an HTTP server for running tests? This could
   1087         be the case when the tests aren't run on the host platform."""
   1088         return False
   1089 
   1090     def start_http_server(self, additional_dirs=None, number_of_servers=None):
   1091         """Start a web server. Raise an error if it can't start or is already running.
   1092 
   1093         Ports can stub this out if they don't need a web server to be running."""
   1094         assert not self._http_server, 'Already running an http server.'
   1095 
   1096         if self._uses_apache():
   1097             server = apache_http_server.LayoutTestApacheHttpd(self, self.results_directory(), additional_dirs=additional_dirs, number_of_servers=number_of_servers)
   1098         else:
   1099             server = http_server.Lighttpd(self, self.results_directory(), additional_dirs=additional_dirs, number_of_servers=number_of_servers)
   1100 
   1101         server.start()
   1102         self._http_server = server
   1103 
   1104     def start_websocket_server(self):
   1105         """Start a web server. Raise an error if it can't start or is already running.
   1106 
   1107         Ports can stub this out if they don't need a websocket server to be running."""
   1108         assert not self._websocket_server, 'Already running a websocket server.'
   1109 
   1110         server = websocket_server.PyWebSocket(self, self.results_directory())
   1111         server.start()
   1112         self._websocket_server = server
   1113 
   1114     def http_server_supports_ipv6(self):
   1115         # Cygwin is the only platform to still use Apache 1.3, which only supports IPV4.
   1116         # Once it moves to Apache 2, we can drop this method altogether.
   1117         if self.host.platform.is_cygwin():
   1118             return False
   1119         return True
   1120 
   1121     def stop_helper(self):
   1122         """Shut down the test helper if it is running. Do nothing if
   1123         it isn't, or it isn't available. If a port overrides start_helper()
   1124         it must override this routine as well."""
   1125         if self._helper:
   1126             _log.debug("Stopping layout test helper")
   1127             try:
   1128                 self._helper.stdin.write("x\n")
   1129                 self._helper.stdin.close()
   1130                 self._helper.wait()
   1131             except IOError, e:
   1132                 pass
   1133             finally:
   1134                 self._helper = None
   1135 
   1136     def stop_http_server(self):
   1137         """Shut down the http server if it is running. Do nothing if it isn't."""
   1138         if self._http_server:
   1139             self._http_server.stop()
   1140             self._http_server = None
   1141 
   1142     def stop_websocket_server(self):
   1143         """Shut down the websocket server if it is running. Do nothing if it isn't."""
   1144         if self._websocket_server:
   1145             self._websocket_server.stop()
   1146             self._websocket_server = None
   1147 
   1148     #
   1149     # TEST EXPECTATION-RELATED METHODS
   1150     #
   1151 
   1152     def test_configuration(self):
   1153         """Returns the current TestConfiguration for the port."""
   1154         if not self._test_configuration:
   1155             self._test_configuration = TestConfiguration(self._version, self._architecture, self._options.configuration.lower())
   1156         return self._test_configuration
   1157 
   1158     # FIXME: Belongs on a Platform object.
   1159     @memoized
   1160     def all_test_configurations(self):
   1161         """Returns a list of TestConfiguration instances, representing all available
   1162         test configurations for this port."""
   1163         return self._generate_all_test_configurations()
   1164 
   1165     # FIXME: Belongs on a Platform object.
   1166     def configuration_specifier_macros(self):
   1167         """Ports may provide a way to abbreviate configuration specifiers to conveniently
   1168         refer to them as one term or alias specific values to more generic ones. For example:
   1169 
   1170         (xp, vista, win7) -> win # Abbreviate all Windows versions into one namesake.
   1171         (lucid) -> linux  # Change specific name of the Linux distro to a more generic term.
   1172 
   1173         Returns a dictionary, each key representing a macro term ('win', for example),
   1174         and value being a list of valid configuration specifiers (such as ['xp', 'vista', 'win7'])."""
   1175         return self.CONFIGURATION_SPECIFIER_MACROS
   1176 
   1177     def all_baseline_variants(self):
   1178         """Returns a list of platform names sufficient to cover all the baselines.
   1179 
   1180         The list should be sorted so that a later platform  will reuse
   1181         an earlier platform's baselines if they are the same (e.g.,
   1182         'snowleopard' should precede 'leopard')."""
   1183         return self.ALL_BASELINE_VARIANTS
   1184 
   1185     def _generate_all_test_configurations(self):
   1186         """Returns a sequence of the TestConfigurations the port supports."""
   1187         # By default, we assume we want to test every graphics type in
   1188         # every configuration on every system.
   1189         test_configurations = []
   1190         for version, architecture in self.ALL_SYSTEMS:
   1191             for build_type in self.ALL_BUILD_TYPES:
   1192                 test_configurations.append(TestConfiguration(version, architecture, build_type))
   1193         return test_configurations
   1194 
   1195     try_builder_names = frozenset([
   1196         'linux_layout',
   1197         'mac_layout',
   1198         'win_layout',
   1199         'linux_layout_rel',
   1200         'mac_layout_rel',
   1201         'win_layout_rel',
   1202     ])
   1203 
   1204     def warn_if_bug_missing_in_test_expectations(self):
   1205         return True
   1206 
   1207     def _port_specific_expectations_files(self):
   1208         paths = []
   1209         paths.append(self.path_from_chromium_base('skia', 'skia_test_expectations.txt'))
   1210         paths.append(self._filesystem.join(self.layout_tests_dir(), 'NeverFixTests'))
   1211         paths.append(self._filesystem.join(self.layout_tests_dir(), 'StaleTestExpectations'))
   1212         paths.append(self._filesystem.join(self.layout_tests_dir(), 'SlowTests'))
   1213 
   1214         builder_name = self.get_option('builder_name', 'DUMMY_BUILDER_NAME')
   1215         if builder_name == 'DUMMY_BUILDER_NAME' or '(deps)' in builder_name or builder_name in self.try_builder_names:
   1216             paths.append(self.path_from_chromium_base('webkit', 'tools', 'layout_tests', 'test_expectations.txt'))
   1217         return paths
   1218 
   1219     def expectations_dict(self):
   1220         """Returns an OrderedDict of name -> expectations strings.
   1221         The names are expected to be (but not required to be) paths in the filesystem.
   1222         If the name is a path, the file can be considered updatable for things like rebaselining,
   1223         so don't use names that are paths if they're not paths.
   1224         Generally speaking the ordering should be files in the filesystem in cascade order
   1225         (TestExpectations followed by Skipped, if the port honors both formats),
   1226         then any built-in expectations (e.g., from compile-time exclusions), then --additional-expectations options."""
   1227         # FIXME: rename this to test_expectations() once all the callers are updated to know about the ordered dict.
   1228         expectations = OrderedDict()
   1229 
   1230         for path in self.expectations_files():
   1231             if self._filesystem.exists(path):
   1232                 expectations[path] = self._filesystem.read_text_file(path)
   1233 
   1234         for path in self.get_option('additional_expectations', []):
   1235             expanded_path = self._filesystem.expanduser(path)
   1236             if self._filesystem.exists(expanded_path):
   1237                 _log.debug("reading additional_expectations from path '%s'" % path)
   1238                 expectations[path] = self._filesystem.read_text_file(expanded_path)
   1239             else:
   1240                 _log.warning("additional_expectations path '%s' does not exist" % path)
   1241         return expectations
   1242 
   1243     def bot_expectations(self):
   1244         if not self.get_option('ignore_flaky_tests'):
   1245             return {}
   1246 
   1247         full_port_name = self.determine_full_port_name(self.host, self._options, self.port_name)
   1248         builder_category = self.get_option('ignore_builder_category', 'layout')
   1249         factory = BotTestExpectationsFactory()
   1250         expectations = factory.expectations_for_port(full_port_name, builder_category)
   1251 
   1252         if not expectations:
   1253             return {}
   1254 
   1255         ignore_mode = self.get_option('ignore_flaky_tests')
   1256         if ignore_mode == 'very-flaky' or ignore_mode == 'maybe-flaky':
   1257             return expectations.flakes_by_path(ignore_mode == 'very-flaky')
   1258         if ignore_mode == 'unexpected':
   1259             return expectations.unexpected_results_by_path()
   1260         _log.warning("Unexpected ignore mode: '%s'." % ignore_mode)
   1261         return {}
   1262 
   1263     def expectations_files(self):
   1264         return [self.path_to_generic_test_expectations_file()] + self._port_specific_expectations_files()
   1265 
   1266     def repository_paths(self):
   1267         """Returns a list of (repository_name, repository_path) tuples of its depending code base."""
   1268         return [('blink', self.layout_tests_dir()),
   1269                 ('chromium', self.path_from_chromium_base('build'))]
   1270 
   1271     _WDIFF_DEL = '##WDIFF_DEL##'
   1272     _WDIFF_ADD = '##WDIFF_ADD##'
   1273     _WDIFF_END = '##WDIFF_END##'
   1274 
   1275     def _format_wdiff_output_as_html(self, wdiff):
   1276         wdiff = cgi.escape(wdiff)
   1277         wdiff = wdiff.replace(self._WDIFF_DEL, "<span class=del>")
   1278         wdiff = wdiff.replace(self._WDIFF_ADD, "<span class=add>")
   1279         wdiff = wdiff.replace(self._WDIFF_END, "</span>")
   1280         html = "<head><style>.del { background: #faa; } "
   1281         html += ".add { background: #afa; }</style></head>"
   1282         html += "<pre>%s</pre>" % wdiff
   1283         return html
   1284 
   1285     def _wdiff_command(self, actual_filename, expected_filename):
   1286         executable = self._path_to_wdiff()
   1287         return [executable,
   1288                 "--start-delete=%s" % self._WDIFF_DEL,
   1289                 "--end-delete=%s" % self._WDIFF_END,
   1290                 "--start-insert=%s" % self._WDIFF_ADD,
   1291                 "--end-insert=%s" % self._WDIFF_END,
   1292                 actual_filename,
   1293                 expected_filename]
   1294 
   1295     @staticmethod
   1296     def _handle_wdiff_error(script_error):
   1297         # Exit 1 means the files differed, any other exit code is an error.
   1298         if script_error.exit_code != 1:
   1299             raise script_error
   1300 
   1301     def _run_wdiff(self, actual_filename, expected_filename):
   1302         """Runs wdiff and may throw exceptions.
   1303         This is mostly a hook for unit testing."""
   1304         # Diffs are treated as binary as they may include multiple files
   1305         # with conflicting encodings.  Thus we do not decode the output.
   1306         command = self._wdiff_command(actual_filename, expected_filename)
   1307         wdiff = self._executive.run_command(command, decode_output=False,
   1308             error_handler=self._handle_wdiff_error)
   1309         return self._format_wdiff_output_as_html(wdiff)
   1310 
   1311     _wdiff_error_html = "Failed to run wdiff, see error log."
   1312 
   1313     def wdiff_text(self, actual_filename, expected_filename):
   1314         """Returns a string of HTML indicating the word-level diff of the
   1315         contents of the two filenames. Returns an empty string if word-level
   1316         diffing isn't available."""
   1317         if not self.wdiff_available():
   1318             return ""
   1319         try:
   1320             # It's possible to raise a ScriptError we pass wdiff invalid paths.
   1321             return self._run_wdiff(actual_filename, expected_filename)
   1322         except OSError as e:
   1323             if e.errno in [errno.ENOENT, errno.EACCES, errno.ECHILD]:
   1324                 # Silently ignore cases where wdiff is missing.
   1325                 self._wdiff_available = False
   1326                 return ""
   1327             raise
   1328         except ScriptError as e:
   1329             _log.error("Failed to run wdiff: %s" % e)
   1330             self._wdiff_available = False
   1331             return self._wdiff_error_html
   1332 
   1333     # This is a class variable so we can test error output easily.
   1334     _pretty_patch_error_html = "Failed to run PrettyPatch, see error log."
   1335 
   1336     def pretty_patch_text(self, diff_path):
   1337         if self._pretty_patch_available is None:
   1338             self._pretty_patch_available = self.check_pretty_patch(logging=False)
   1339         if not self._pretty_patch_available:
   1340             return self._pretty_patch_error_html
   1341         command = ("ruby", "-I", self._filesystem.dirname(self._pretty_patch_path),
   1342                    self._pretty_patch_path, diff_path)
   1343         try:
   1344             # Diffs are treated as binary (we pass decode_output=False) as they
   1345             # may contain multiple files of conflicting encodings.
   1346             return self._executive.run_command(command, decode_output=False)
   1347         except OSError, e:
   1348             # If the system is missing ruby log the error and stop trying.
   1349             self._pretty_patch_available = False
   1350             _log.error("Failed to run PrettyPatch (%s): %s" % (command, e))
   1351             return self._pretty_patch_error_html
   1352         except ScriptError, e:
   1353             # If ruby failed to run for some reason, log the command
   1354             # output and stop trying.
   1355             self._pretty_patch_available = False
   1356             _log.error("Failed to run PrettyPatch (%s):\n%s" % (command, e.message_with_output()))
   1357             return self._pretty_patch_error_html
   1358 
   1359     def default_configuration(self):
   1360         return self._config.default_configuration()
   1361 
   1362     def clobber_old_port_specific_results(self):
   1363         pass
   1364 
   1365     #
   1366     # PROTECTED ROUTINES
   1367     #
   1368     # The routines below should only be called by routines in this class
   1369     # or any of its subclasses.
   1370     #
   1371 
   1372     def _uses_apache(self):
   1373         return True
   1374 
   1375     # FIXME: This does not belong on the port object.
   1376     @memoized
   1377     def _path_to_apache(self):
   1378         """Returns the full path to the apache binary.
   1379 
   1380         This is needed only by ports that use the apache_http_server module."""
   1381         raise NotImplementedError('Port._path_to_apache')
   1382 
   1383     # FIXME: This belongs on some platform abstraction instead of Port.
   1384     def _is_redhat_based(self):
   1385         return self._filesystem.exists('/etc/redhat-release')
   1386 
   1387     def _is_debian_based(self):
   1388         return self._filesystem.exists('/etc/debian_version')
   1389 
   1390     def _apache_version(self):
   1391         config = self._executive.run_command([self._path_to_apache(), '-v'])
   1392         return re.sub(r'(?:.|\n)*Server version: Apache/(\d+\.\d+)(?:.|\n)*', r'\1', config)
   1393 
   1394     # We pass sys_platform into this method to make it easy to unit test.
   1395     def _apache_config_file_name_for_platform(self, sys_platform):
   1396         if sys_platform == 'cygwin':
   1397             return 'cygwin-httpd.conf'  # CYGWIN is the only platform to still use Apache 1.3.
   1398         if sys_platform.startswith('linux'):
   1399             if self._is_redhat_based():
   1400                 return 'fedora-httpd-' + self._apache_version() + '.conf'
   1401             if self._is_debian_based():
   1402                 return 'debian-httpd-' + self._apache_version() + '.conf'
   1403         # All platforms use apache2 except for CYGWIN (and Mac OS X Tiger and prior, which we no longer support).
   1404         return "apache2-httpd.conf"
   1405 
   1406     def _path_to_apache_config_file(self):
   1407         """Returns the full path to the apache configuration file.
   1408 
   1409         If the WEBKIT_HTTP_SERVER_CONF_PATH environment variable is set, its
   1410         contents will be used instead.
   1411 
   1412         This is needed only by ports that use the apache_http_server module."""
   1413         config_file_from_env = os.environ.get('WEBKIT_HTTP_SERVER_CONF_PATH')
   1414         if config_file_from_env:
   1415             if not self._filesystem.exists(config_file_from_env):
   1416                 raise IOError('%s was not found on the system' % config_file_from_env)
   1417             return config_file_from_env
   1418 
   1419         config_file_name = self._apache_config_file_name_for_platform(sys.platform)
   1420         return self._filesystem.join(self.layout_tests_dir(), 'http', 'conf', config_file_name)
   1421 
   1422     def _path_to_driver(self, configuration=None):
   1423         """Returns the full path to the test driver."""
   1424         return self._build_path(self.driver_name())
   1425 
   1426     def _path_to_webcore_library(self):
   1427         """Returns the full path to a built copy of WebCore."""
   1428         return None
   1429 
   1430     def _path_to_helper(self):
   1431         """Returns the full path to the layout_test_helper binary, which
   1432         is used to help configure the system for the test run, or None
   1433         if no helper is needed.
   1434 
   1435         This is likely only used by start/stop_helper()."""
   1436         return None
   1437 
   1438     def _path_to_image_diff(self):
   1439         """Returns the full path to the image_diff binary, or None if it is not available.
   1440 
   1441         This is likely used only by diff_image()"""
   1442         return self._build_path('image_diff')
   1443 
   1444     def _path_to_lighttpd(self):
   1445         """Returns the path to the LigHTTPd binary.
   1446 
   1447         This is needed only by ports that use the http_server.py module."""
   1448         raise NotImplementedError('Port._path_to_lighttpd')
   1449 
   1450     def _path_to_lighttpd_modules(self):
   1451         """Returns the path to the LigHTTPd modules directory.
   1452 
   1453         This is needed only by ports that use the http_server.py module."""
   1454         raise NotImplementedError('Port._path_to_lighttpd_modules')
   1455 
   1456     def _path_to_lighttpd_php(self):
   1457         """Returns the path to the LigHTTPd PHP executable.
   1458 
   1459         This is needed only by ports that use the http_server.py module."""
   1460         raise NotImplementedError('Port._path_to_lighttpd_php')
   1461 
   1462     @memoized
   1463     def _path_to_wdiff(self):
   1464         """Returns the full path to the wdiff binary, or None if it is not available.
   1465 
   1466         This is likely used only by wdiff_text()"""
   1467         for path in ("/usr/bin/wdiff", "/usr/bin/dwdiff"):
   1468             if self._filesystem.exists(path):
   1469                 return path
   1470         return None
   1471 
   1472     def _webkit_baseline_path(self, platform):
   1473         """Return the  full path to the top of the baseline tree for a
   1474         given platform."""
   1475         return self._filesystem.join(self.layout_tests_dir(), 'platform', platform)
   1476 
   1477     def _driver_class(self):
   1478         """Returns the port's driver implementation."""
   1479         return driver.Driver
   1480 
   1481     def _get_crash_log(self, name, pid, stdout, stderr, newer_than):
   1482         if stderr and 'AddressSanitizer' in stderr:
   1483             # Running the AddressSanitizer take a lot of memory, so we need to
   1484             # serialize access to it across all the concurrently running drivers.
   1485 
   1486             # FIXME: investigate using LLVM_SYMBOLIZER_PATH here to reduce the overhead.
   1487             asan_filter_path = self.path_from_chromium_base('tools', 'valgrind', 'asan', 'asan_symbolize.py')
   1488             if self._filesystem.exists(asan_filter_path):
   1489                 output = self._executive.run_command(['flock', sys.executable, asan_filter_path], input=stderr, decode_output=False)
   1490                 stderr = self._executive.run_command(['c++filt'], input=output, decode_output=False)
   1491 
   1492         name_str = name or '<unknown process name>'
   1493         pid_str = str(pid or '<unknown>')
   1494         stdout_lines = (stdout or '<empty>').decode('utf8', 'replace').splitlines()
   1495         stderr_lines = (stderr or '<empty>').decode('utf8', 'replace').splitlines()
   1496         return (stderr, 'crash log for %s (pid %s):\n%s\n%s\n' % (name_str, pid_str,
   1497             '\n'.join(('STDOUT: ' + l) for l in stdout_lines),
   1498             '\n'.join(('STDERR: ' + l) for l in stderr_lines)))
   1499 
   1500     def look_for_new_crash_logs(self, crashed_processes, start_time):
   1501         pass
   1502 
   1503     def look_for_new_samples(self, unresponsive_processes, start_time):
   1504         pass
   1505 
   1506     def sample_process(self, name, pid):
   1507         pass
   1508 
   1509     def virtual_test_suites(self):
   1510         return [
   1511             VirtualTestSuite('gpu',
   1512                              'fast/canvas',
   1513                              ['--enable-accelerated-2d-canvas']),
   1514             VirtualTestSuite('gpu',
   1515                              'canvas/philip',
   1516                              ['--enable-accelerated-2d-canvas']),
   1517             VirtualTestSuite('threaded',
   1518                              'compositing/visibility',
   1519                              ['--enable-threaded-compositing']),
   1520             VirtualTestSuite('threaded',
   1521                              'compositing/webgl',
   1522                              ['--enable-threaded-compositing']),
   1523             VirtualTestSuite('gpu',
   1524                              'fast/hidpi',
   1525                              ['--force-compositing-mode']),
   1526             VirtualTestSuite('softwarecompositing',
   1527                              'compositing',
   1528                              ['--enable-software-compositing', '--disable-gpu-compositing'],
   1529                              use_legacy_naming=True),
   1530             VirtualTestSuite('deferred',
   1531                              'fast/images',
   1532                              ['--enable-deferred-image-decoding', '--enable-per-tile-painting', '--force-compositing-mode']),
   1533             VirtualTestSuite('deferred',
   1534                              'inspector/timeline',
   1535                              ['--enable-deferred-image-decoding', '--enable-per-tile-painting', '--force-compositing-mode']),
   1536             VirtualTestSuite('gpu/compositedscrolling/overflow',
   1537                              'compositing/overflow',
   1538                              ['--enable-accelerated-overflow-scroll'],
   1539                              use_legacy_naming=True),
   1540             VirtualTestSuite('gpu/compositedscrolling/scrollbars',
   1541                              'scrollbars',
   1542                              ['--enable-accelerated-overflow-scroll'],
   1543                              use_legacy_naming=True),
   1544             VirtualTestSuite('threaded',
   1545                              'animations',
   1546                              ['--enable-threaded-compositing']),
   1547             VirtualTestSuite('threaded',
   1548                              'transitions',
   1549                              ['--enable-threaded-compositing']),
   1550             VirtualTestSuite('legacy-animations-engine',
   1551                              'animations',
   1552                              ['--disable-web-animations-css']),
   1553             VirtualTestSuite('legacy-animations-engine',
   1554                              'transitions',
   1555                              ['--disable-web-animations-css']),
   1556             VirtualTestSuite('stable',
   1557                              'webexposed',
   1558                              ['--stable-release-mode']),
   1559             VirtualTestSuite('stable',
   1560                              'media/stable',
   1561                              ['--stable-release-mode']),
   1562             VirtualTestSuite('android',
   1563                              'fullscreen',
   1564                              ['--force-compositing-mode', '--allow-webui-compositing', '--enable-threaded-compositing',
   1565                               '--enable-fixed-position-compositing', '--enable-accelerated-overflow-scroll', '--enable-accelerated-scrollable-frames',
   1566                               '--enable-composited-scrolling-for-frames', '--enable-gesture-tap-highlight', '--enable-pinch',
   1567                               '--enable-overlay-fullscreen-video', '--enable-overlay-scrollbars', '--enable-overscroll-notifications',
   1568                               '--enable-fixed-layout', '--enable-viewport', '--disable-canvas-aa',
   1569                               '--disable-composited-antialiasing']),
   1570             VirtualTestSuite('implsidepainting',
   1571                              'inspector/timeline',
   1572                              ['--enable-threaded-compositing', '--enable-impl-side-painting', '--force-compositing-mode']),
   1573             VirtualTestSuite('fasttextautosizing',
   1574                              'fast/text-autosizing',
   1575                              ['--enable-fast-text-autosizing']),
   1576             VirtualTestSuite('serviceworker',
   1577                              'http/tests/serviceworker',
   1578                              ['--enable-service-worker']),
   1579         ]
   1580 
   1581     @memoized
   1582     def populated_virtual_test_suites(self):
   1583         suites = self.virtual_test_suites()
   1584 
   1585         # Sanity-check the suites to make sure they don't point to other suites.
   1586         suite_dirs = [suite.name for suite in suites]
   1587         for suite in suites:
   1588             assert suite.base not in suite_dirs
   1589 
   1590         for suite in suites:
   1591             base_tests = self._real_tests([suite.base])
   1592             suite.tests = {}
   1593             for test in base_tests:
   1594                 suite.tests[test.replace(suite.base, suite.name, 1)] = test
   1595         return suites
   1596 
   1597     def _virtual_tests(self, paths, suites):
   1598         virtual_tests = list()
   1599         for suite in suites:
   1600             if paths:
   1601                 for test in suite.tests:
   1602                     if any(test.startswith(p) for p in paths):
   1603                         virtual_tests.append(test)
   1604             else:
   1605                 virtual_tests.extend(suite.tests.keys())
   1606         return virtual_tests
   1607 
   1608     def is_virtual_test(self, test_name):
   1609         return bool(self.lookup_virtual_suite(test_name))
   1610 
   1611     def lookup_virtual_suite(self, test_name):
   1612         for suite in self.populated_virtual_test_suites():
   1613             if test_name.startswith(suite.name):
   1614                 return suite
   1615         return None
   1616 
   1617     def lookup_virtual_test_base(self, test_name):
   1618         suite = self.lookup_virtual_suite(test_name)
   1619         if not suite:
   1620             return None
   1621         return test_name.replace(suite.name, suite.base, 1)
   1622 
   1623     def lookup_virtual_test_args(self, test_name):
   1624         for suite in self.populated_virtual_test_suites():
   1625             if test_name.startswith(suite.name):
   1626                 return suite.args
   1627         return []
   1628 
   1629     def should_run_as_pixel_test(self, test_input):
   1630         if not self._options.pixel_tests:
   1631             return False
   1632         if self._options.pixel_test_directories:
   1633             return any(test_input.test_name.startswith(directory) for directory in self._options.pixel_test_directories)
   1634         return True
   1635 
   1636     def _modules_to_search_for_symbols(self):
   1637         path = self._path_to_webcore_library()
   1638         if path:
   1639             return [path]
   1640         return []
   1641 
   1642     def _symbols_string(self):
   1643         symbols = ''
   1644         for path_to_module in self._modules_to_search_for_symbols():
   1645             try:
   1646                 symbols += self._executive.run_command(['nm', path_to_module], error_handler=self._executive.ignore_error)
   1647             except OSError, e:
   1648                 _log.warn("Failed to run nm: %s.  Can't determine supported features correctly." % e)
   1649         return symbols
   1650 
   1651     # Ports which use compile-time feature detection should define this method and return
   1652     # a dictionary mapping from symbol substrings to possibly disabled test directories.
   1653     # When the symbol substrings are not matched, the directories will be skipped.
   1654     # If ports don't ever enable certain features, then those directories can just be
   1655     # in the Skipped list instead of compile-time-checked here.
   1656     def _missing_symbol_to_skipped_tests(self):
   1657         if self.PORT_HAS_AUDIO_CODECS_BUILT_IN:
   1658             return {}
   1659         else:
   1660             return {
   1661                 "ff_mp3_decoder": ["webaudio/codec-tests/mp3"],
   1662                 "ff_aac_decoder": ["webaudio/codec-tests/aac"],
   1663             }
   1664 
   1665     def _has_test_in_directories(self, directory_lists, test_list):
   1666         if not test_list:
   1667             return False
   1668 
   1669         directories = itertools.chain.from_iterable(directory_lists)
   1670         for directory, test in itertools.product(directories, test_list):
   1671             if test.startswith(directory):
   1672                 return True
   1673         return False
   1674 
   1675     def _skipped_tests_for_unsupported_features(self, test_list):
   1676         # Only check the symbols of there are tests in the test_list that might get skipped.
   1677         # This is a performance optimization to avoid the calling nm.
   1678         # Runtime feature detection not supported, fallback to static detection:
   1679         # Disable any tests for symbols missing from the executable or libraries.
   1680         if self._has_test_in_directories(self._missing_symbol_to_skipped_tests().values(), test_list):
   1681             symbols_string = self._symbols_string()
   1682             if symbols_string is not None:
   1683                 return reduce(operator.add, [directories for symbol_substring, directories in self._missing_symbol_to_skipped_tests().items() if symbol_substring not in symbols_string], [])
   1684         return []
   1685 
   1686     def _convert_path(self, path):
   1687         """Handles filename conversion for subprocess command line args."""
   1688         # See note above in diff_image() for why we need this.
   1689         if sys.platform == 'cygwin':
   1690             return cygpath(path)
   1691         return path
   1692 
   1693     def _build_path(self, *comps):
   1694         return self._build_path_with_configuration(None, *comps)
   1695 
   1696     def _build_path_with_configuration(self, configuration, *comps):
   1697         # Note that we don't do the option caching that the
   1698         # base class does, because finding the right directory is relatively
   1699         # fast.
   1700         configuration = configuration or self.get_option('configuration')
   1701         return self._static_build_path(self._filesystem, self.get_option('build_directory'),
   1702             self.path_from_chromium_base(), configuration, comps)
   1703 
   1704     def _check_driver_build_up_to_date(self, configuration):
   1705         if configuration in ('Debug', 'Release'):
   1706             try:
   1707                 debug_path = self._path_to_driver('Debug')
   1708                 release_path = self._path_to_driver('Release')
   1709 
   1710                 debug_mtime = self._filesystem.mtime(debug_path)
   1711                 release_mtime = self._filesystem.mtime(release_path)
   1712 
   1713                 if (debug_mtime > release_mtime and configuration == 'Release' or
   1714                     release_mtime > debug_mtime and configuration == 'Debug'):
   1715                     most_recent_binary = 'Release' if configuration == 'Debug' else 'Debug'
   1716                     _log.warning('You are running the %s binary. However the %s binary appears to be more recent. '
   1717                                  'Please pass --%s.', configuration, most_recent_binary, most_recent_binary.lower())
   1718                     _log.warning('')
   1719             # This will fail if we don't have both a debug and release binary.
   1720             # That's fine because, in this case, we must already be running the
   1721             # most up-to-date one.
   1722             except OSError:
   1723                 pass
   1724         return True
   1725 
   1726     def _chromium_baseline_path(self, platform):
   1727         if platform is None:
   1728             platform = self.name()
   1729         return self.path_from_webkit_base('LayoutTests', 'platform', platform)
   1730 
   1731 class VirtualTestSuite(object):
   1732     def __init__(self, name, base, args, use_legacy_naming=False, tests=None):
   1733         if use_legacy_naming:
   1734             self.name = 'virtual/' + name
   1735         else:
   1736             if name.find('/') != -1:
   1737                 _log.error("Virtual test suites names cannot contain /'s: %s" % name)
   1738                 return
   1739             self.name = 'virtual/' + name + '/' + base
   1740         self.base = base
   1741         self.args = args
   1742         self.tests = tests or set()
   1743 
   1744     def __repr__(self):
   1745         return "VirtualTestSuite('%s', '%s', %s)" % (self.name, self.base, self.args)
   1746