123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548 |
- # Copyright (C) 2010 Google Inc. All rights reserved.
- #
- # Redistribution and use in source and binary forms, with or without
- # modification, are permitted provided that the following conditions are
- # met:
- #
- # * Redistributions of source code must retain the above copyright
- # notice, this list of conditions and the following disclaimer.
- # * Redistributions in binary form must reproduce the above
- # copyright notice, this list of conditions and the following disclaimer
- # in the documentation and/or other materials provided with the
- # distribution.
- # * Neither the Google name nor the names of its
- # contributors may be used to endorse or promote products derived from
- # this software without specific prior written permission.
- #
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- """Abstract base class of Port-specific entry points for the layout tests
- test infrastructure (the Port and Driver classes)."""
- import cgi
- import difflib
- import errno
- import itertools
- import logging
- import os
- import operator
- import optparse
- import re
- import sys
- try:
- from collections import OrderedDict
- except ImportError:
- # Needed for Python < 2.7
- from webkitpy.thirdparty.ordered_dict import OrderedDict
- from webkitpy.common import find_files
- from webkitpy.common import read_checksum_from_png
- from webkitpy.common.memoized import memoized
- from webkitpy.common.system import path
- from webkitpy.common.system.executive import ScriptError
- from webkitpy.common.system.systemhost import SystemHost
- from webkitpy.common.webkit_finder import WebKitFinder
- from webkitpy.layout_tests.models.test_configuration import TestConfiguration
- from webkitpy.port import config as port_config
- from webkitpy.port import driver
- from webkitpy.port import http_lock
- from webkitpy.port import image_diff
- from webkitpy.port import server_process
- from webkitpy.port.factory import PortFactory
- from webkitpy.layout_tests.servers import apache_http_server
- from webkitpy.layout_tests.servers import http_server
- from webkitpy.layout_tests.servers import websocket_server
- _log = logging.getLogger(__name__)
- class Port(object):
- """Abstract class for Port-specific hooks for the layout_test package."""
- # Subclasses override this. This should indicate the basic implementation
- # part of the port name, e.g., 'win', 'gtk'; there is probably (?) one unique value per class.
- # FIXME: We should probably rename this to something like 'implementation_name'.
- port_name = None
- # Test names resemble unix relative paths, and use '/' as a directory separator.
- TEST_PATH_SEPARATOR = '/'
- ALL_BUILD_TYPES = ('debug', 'release')
- @classmethod
- def determine_full_port_name(cls, host, options, port_name):
- """Return a fully-specified port name that can be used to construct objects."""
- # Subclasses will usually override this.
- options = options or {}
- assert port_name.startswith(cls.port_name)
- if getattr(options, 'webkit_test_runner', False) and not '-wk2' in port_name:
- return port_name + '-wk2'
- return port_name
- def __init__(self, host, port_name, options=None, **kwargs):
- # This value may be different from cls.port_name by having version modifiers
- # and other fields appended to it (for example, 'qt-arm' or 'mac-wk2').
- self._name = port_name
- # These are default values that should be overridden in a subclasses.
- self._version = ''
- self._architecture = 'x86'
- # FIXME: Ideally we'd have a package-wide way to get a
- # well-formed options object that had all of the necessary
- # options defined on it.
- self._options = options or optparse.Values()
- if self._name and '-wk2' in self._name:
- self._options.webkit_test_runner = True
- self.host = host
- self._executive = host.executive
- self._filesystem = host.filesystem
- self._webkit_finder = WebKitFinder(host.filesystem)
- self._config = port_config.Config(self._executive, self._filesystem, self.port_name)
- self._helper = None
- self._http_server = None
- self._websocket_server = None
- self._image_differ = None
- self._server_process_constructor = server_process.ServerProcess # overridable for testing
- self._http_lock = None # FIXME: Why does this live on the port object?
- # Python's Popen has a bug that causes any pipes opened to a
- # process that can't be executed to be leaked. Since this
- # code is specifically designed to tolerate exec failures
- # to gracefully handle cases where wdiff is not installed,
- # the bug results in a massive file descriptor leak. As a
- # workaround, if an exec failure is ever experienced for
- # wdiff, assume it's not available. This will leak one
- # file descriptor but that's better than leaking each time
- # wdiff would be run.
- #
- # http://mail.python.org/pipermail/python-list/
- # 2008-August/505753.html
- # http://bugs.python.org/issue3210
- self._wdiff_available = None
- # FIXME: prettypatch.py knows this path, why is it copied here?
- self._pretty_patch_path = self.path_from_webkit_base("Websites", "bugs.webkit.org", "PrettyPatch", "prettify.rb")
- self._pretty_patch_available = None
- if not hasattr(options, 'configuration') or not options.configuration:
- self.set_option_default('configuration', self.default_configuration())
- self._test_configuration = None
- self._reftest_list = {}
- self._results_directory = None
- self._root_was_set = hasattr(options, 'root') and options.root
- def additional_drt_flag(self):
- return []
- def supports_per_test_timeout(self):
- return False
- def default_pixel_tests(self):
- # FIXME: Disable until they are run by default on build.webkit.org.
- return False
- def default_timeout_ms(self):
- if self.get_option('webkit_test_runner'):
- # Add some more time to WebKitTestRunner because it needs to syncronise the state
- # with the web process and we want to detect if there is a problem with that in the driver.
- return 80 * 1000
- return 35 * 1000
- def driver_stop_timeout(self):
- """ Returns the amount of time in seconds to wait before killing the process in driver.stop()."""
- # We want to wait for at least 3 seconds, but if we are really slow, we want to be slow on cleanup as
- # well (for things like ASAN, Valgrind, etc.)
- return 3.0 * float(self.get_option('time_out_ms', '0')) / self.default_timeout_ms()
- def wdiff_available(self):
- if self._wdiff_available is None:
- self._wdiff_available = self.check_wdiff(logging=False)
- return self._wdiff_available
- def pretty_patch_available(self):
- if self._pretty_patch_available is None:
- self._pretty_patch_available = self.check_pretty_patch(logging=False)
- return self._pretty_patch_available
- def should_retry_crashes(self):
- return False
- def default_child_processes(self):
- """Return the number of DumpRenderTree instances to use for this port."""
- return self._executive.cpu_count()
- def default_max_locked_shards(self):
- """Return the number of "locked" shards to run in parallel (like the http tests)."""
- return 1
- def worker_startup_delay_secs(self):
- # FIXME: If we start workers up too quickly, DumpRenderTree appears
- # to thrash on something and time out its first few tests. Until
- # we can figure out what's going on, sleep a bit in between
- # workers. See https://bugs.webkit.org/show_bug.cgi?id=79147 .
- return 0.1
- def baseline_path(self):
- """Return the absolute path to the directory to store new baselines in for this port."""
- # FIXME: remove once all callers are calling either baseline_version_dir() or baseline_platform_dir()
- return self.baseline_version_dir()
- def baseline_platform_dir(self):
- """Return the absolute path to the default (version-independent) platform-specific results."""
- return self._filesystem.join(self.layout_tests_dir(), 'platform', self.port_name)
- def baseline_version_dir(self):
- """Return the absolute path to the platform-and-version-specific results."""
- baseline_search_paths = self.baseline_search_path()
- return baseline_search_paths[0]
- def baseline_search_path(self):
- return self.get_option('additional_platform_directory', []) + self._compare_baseline() + self.default_baseline_search_path()
- def default_baseline_search_path(self):
- """Return a list of absolute paths to directories to search under for
- baselines. The directories are searched in order."""
- search_paths = []
- if self.get_option('webkit_test_runner'):
- search_paths.append(self._wk2_port_name())
- search_paths.append(self.name())
- if self.name() != self.port_name:
- search_paths.append(self.port_name)
- return map(self._webkit_baseline_path, search_paths)
- @memoized
- def _compare_baseline(self):
- factory = PortFactory(self.host)
- target_port = self.get_option('compare_port')
- if target_port:
- return factory.get(target_port).default_baseline_search_path()
- return []
- def check_build(self, needs_http):
- """This routine is used to ensure that the build is up to date
- and all the needed binaries are present."""
- # If we're using a pre-built copy of WebKit (--root), we assume it also includes a build of DRT.
- if not self._root_was_set and self.get_option('build') and not self._build_driver():
- return False
- if not self._check_driver():
- return False
- if self.get_option('pixel_tests'):
- if not self.check_image_diff():
- return False
- if not self._check_port_build():
- return False
- return True
- def _check_driver(self):
- driver_path = self._path_to_driver()
- if not self._filesystem.exists(driver_path):
- _log.error("%s was not found at %s" % (self.driver_name(), driver_path))
- return False
- return True
- def _check_port_build(self):
- # Ports can override this method to do additional checks.
- return True
- def check_sys_deps(self, needs_http):
- """If the port needs to do some runtime checks to ensure that the
- tests can be run successfully, it should override this routine.
- This step can be skipped with --nocheck-sys-deps.
- Returns whether the system is properly configured."""
- if needs_http:
- return self.check_httpd()
- return True
- def check_image_diff(self, override_step=None, logging=True):
- """This routine is used to check whether image_diff binary exists."""
- image_diff_path = self._path_to_image_diff()
- if not self._filesystem.exists(image_diff_path):
- _log.error("ImageDiff was not found at %s" % image_diff_path)
- return False
- return True
- def check_pretty_patch(self, logging=True):
- """Checks whether we can use the PrettyPatch ruby script."""
- try:
- _ = self._executive.run_command(['ruby', '--version'])
- except OSError, e:
- if e.errno in [errno.ENOENT, errno.EACCES, errno.ECHILD]:
- if logging:
- _log.warning("Ruby is not installed; can't generate pretty patches.")
- _log.warning('')
- return False
- if not self._filesystem.exists(self._pretty_patch_path):
- if logging:
- _log.warning("Unable to find %s; can't generate pretty patches." % self._pretty_patch_path)
- _log.warning('')
- return False
- return True
- def check_wdiff(self, logging=True):
- if not self._path_to_wdiff():
- # Don't need to log here since this is the port choosing not to use wdiff.
- return False
- try:
- _ = self._executive.run_command([self._path_to_wdiff(), '--help'])
- except OSError:
- if logging:
- message = self._wdiff_missing_message()
- if message:
- for line in message.splitlines():
- _log.warning(' ' + line)
- _log.warning('')
- return False
- return True
- def _wdiff_missing_message(self):
- return 'wdiff is not installed; please install it to generate word-by-word diffs.'
- def check_httpd(self):
- if self._uses_apache():
- httpd_path = self._path_to_apache()
- else:
- httpd_path = self._path_to_lighttpd()
- try:
- server_name = self._filesystem.basename(httpd_path)
- env = self.setup_environ_for_server(server_name)
- if self._executive.run_command([httpd_path, "-v"], env=env, return_exit_code=True) != 0:
- _log.error("httpd seems broken. Cannot run http tests.")
- return False
- return True
- except OSError:
- _log.error("No httpd found. Cannot run http tests.")
- return False
- def do_text_results_differ(self, expected_text, actual_text):
- return expected_text != actual_text
- def do_audio_results_differ(self, expected_audio, actual_audio):
- return expected_audio != actual_audio
- def diff_image(self, expected_contents, actual_contents, tolerance=None):
- """Compare two images and return a tuple of an image diff, a percentage difference (0-100), and an error string.
- |tolerance| should be a percentage value (0.0 - 100.0).
- If it is omitted, the port default tolerance value is used.
- If an error occurs (like ImageDiff isn't found, or crashes, we log an error and return True (for a diff).
- """
- if not actual_contents and not expected_contents:
- return (None, 0, None)
- if not actual_contents or not expected_contents:
- return (True, 0, None)
- if not self._image_differ:
- self._image_differ = image_diff.ImageDiffer(self)
- self.set_option_default('tolerance', 0.1)
- if tolerance is None:
- tolerance = self.get_option('tolerance')
- return self._image_differ.diff_image(expected_contents, actual_contents, tolerance)
- def diff_text(self, expected_text, actual_text, expected_filename, actual_filename):
- """Returns a string containing the diff of the two text strings
- in 'unified diff' format."""
- # The filenames show up in the diff output, make sure they're
- # raw bytes and not unicode, so that they don't trigger join()
- # trying to decode the input.
- def to_raw_bytes(string_value):
- if isinstance(string_value, unicode):
- return string_value.encode('utf-8')
- return string_value
- expected_filename = to_raw_bytes(expected_filename)
- actual_filename = to_raw_bytes(actual_filename)
- diff = difflib.unified_diff(expected_text.splitlines(True),
- actual_text.splitlines(True),
- expected_filename,
- actual_filename)
- return ''.join(diff)
- def check_for_leaks(self, process_name, process_pid):
- # Subclasses should check for leaks in the running process
- # and print any necessary warnings if leaks are found.
- # FIXME: We should consider moving much of this logic into
- # Executive and make it platform-specific instead of port-specific.
- pass
- def print_leaks_summary(self):
- # Subclasses can override this to print a summary of leaks found
- # while running the layout tests.
- pass
- def driver_name(self):
- if self.get_option('driver_name'):
- return self.get_option('driver_name')
- if self.get_option('webkit_test_runner'):
- return 'WebKitTestRunner'
- return 'DumpRenderTree'
- def expected_baselines_by_extension(self, test_name):
- """Returns a dict mapping baseline suffix to relative path for each baseline in
- a test. For reftests, it returns ".==" or ".!=" instead of the suffix."""
- # FIXME: The name similarity between this and expected_baselines() below, is unfortunate.
- # We should probably rename them both.
- baseline_dict = {}
- reference_files = self.reference_files(test_name)
- if reference_files:
- # FIXME: How should this handle more than one type of reftest?
- baseline_dict['.' + reference_files[0][0]] = self.relative_test_filename(reference_files[0][1])
- for extension in self.baseline_extensions():
- path = self.expected_filename(test_name, extension, return_default=False)
- baseline_dict[extension] = self.relative_test_filename(path) if path else path
- return baseline_dict
- def baseline_extensions(self):
- """Returns a tuple of all of the non-reftest baseline extensions we use. The extensions include the leading '.'."""
- return ('.wav', '.webarchive', '.txt', '.png')
- def expected_baselines(self, test_name, suffix, all_baselines=False):
- """Given a test name, finds where the baseline results are located.
- Args:
- test_name: name of test file (usually a relative path under LayoutTests/)
- suffix: file suffix of the expected results, including dot; e.g.
- '.txt' or '.png'. This should not be None, but may be an empty
- string.
- all_baselines: If True, return an ordered list of all baseline paths
- for the given platform. If False, return only the first one.
- Returns
- a list of ( platform_dir, results_filename ), where
- platform_dir - abs path to the top of the results tree (or test
- tree)
- results_filename - relative path from top of tree to the results
- file
- (port.join() of the two gives you the full path to the file,
- unless None was returned.)
- Return values will be in the format appropriate for the current
- platform (e.g., "\\" for path separators on Windows). If the results
- file is not found, then None will be returned for the directory,
- but the expected relative pathname will still be returned.
- This routine is generic but lives here since it is used in
- conjunction with the other baseline and filename routines that are
- platform specific.
- """
- baseline_filename = self._filesystem.splitext(test_name)[0] + '-expected' + suffix
- baseline_search_path = self.baseline_search_path()
- baselines = []
- for platform_dir in baseline_search_path:
- if self._filesystem.exists(self._filesystem.join(platform_dir, baseline_filename)):
- baselines.append((platform_dir, baseline_filename))
- if not all_baselines and baselines:
- return baselines
- # If it wasn't found in a platform directory, return the expected
- # result in the test directory, even if no such file actually exists.
- platform_dir = self.layout_tests_dir()
- if self._filesystem.exists(self._filesystem.join(platform_dir, baseline_filename)):
- baselines.append((platform_dir, baseline_filename))
- if baselines:
- return baselines
- return [(None, baseline_filename)]
- def expected_filename(self, test_name, suffix, return_default=True):
- """Given a test name, returns an absolute path to its expected results.
- If no expected results are found in any of the searched directories,
- the directory in which the test itself is located will be returned.
- The return value is in the format appropriate for the platform
- (e.g., "\\" for path separators on windows).
- Args:
- test_name: name of test file (usually a relative path under LayoutTests/)
- suffix: file suffix of the expected results, including dot; e.g. '.txt'
- or '.png'. This should not be None, but may be an empty string.
- platform: the most-specific directory name to use to build the
- search list of directories; e.g. 'mountainlion-wk2'
- return_default: if True, returns the path to the generic expectation if nothing
- else is found; if False, returns None.
- This routine is generic but is implemented here to live alongside
- the other baseline and filename manipulation routines.
- """
- # FIXME: The [0] here is very mysterious, as is the destructured return.
- platform_dir, baseline_filename = self.expected_baselines(test_name, suffix)[0]
- if platform_dir:
- return self._filesystem.join(platform_dir, baseline_filename)
- actual_test_name = self.lookup_virtual_test_base(test_name)
- if actual_test_name:
- return self.expected_filename(actual_test_name, suffix)
- if return_default:
- return self._filesystem.join(self.layout_tests_dir(), baseline_filename)
- return None
- def expected_checksum(self, test_name):
- """Returns the checksum of the image we expect the test to produce, or None if it is a text-only test."""
- png_path = self.expected_filename(test_name, '.png')
- if self._filesystem.exists(png_path):
- with self._filesystem.open_binary_file_for_reading(png_path) as filehandle:
- return read_checksum_from_png.read_checksum(filehandle)
- return None
- def expected_image(self, test_name):
- """Returns the image we expect the test to produce."""
- baseline_path = self.expected_filename(test_name, '.png')
- if not self._filesystem.exists(baseline_path):
- return None
- return self._filesystem.read_binary_file(baseline_path)
- def expected_audio(self, test_name):
- baseline_path = self.expected_filename(test_name, '.wav')
- if not self._filesystem.exists(baseline_path):
- return None
- return self._filesystem.read_binary_file(baseline_path)
- def expected_text(self, test_name):
- """Returns the text output we expect the test to produce, or None
- if we don't expect there to be any text output.
- End-of-line characters are normalized to '\n'."""
- # FIXME: DRT output is actually utf-8, but since we don't decode the
- # output from DRT (instead treating it as a binary string), we read the
- # baselines as a binary string, too.
- baseline_path = self.expected_filename(test_name, '.txt')
- if not self._filesystem.exists(baseline_path):
- baseline_path = self.expected_filename(test_name, '.webarchive')
- if not self._filesystem.exists(baseline_path):
- return None
- text = self._filesystem.read_binary_file(baseline_path)
- return text.replace("\r\n", "\n")
- def _get_reftest_list(self, test_name):
- dirname = self._filesystem.join(self.layout_tests_dir(), self._filesystem.dirname(test_name))
- if dirname not in self._reftest_list:
- self._reftest_list[dirname] = Port._parse_reftest_list(self._filesystem, dirname)
- return self._reftest_list[dirname]
- @staticmethod
- def _parse_reftest_list(filesystem, test_dirpath):
- reftest_list_path = filesystem.join(test_dirpath, 'reftest.list')
- if not filesystem.isfile(reftest_list_path):
- return None
- reftest_list_file = filesystem.read_text_file(reftest_list_path)
- parsed_list = {}
- for line in reftest_list_file.split('\n'):
- line = re.sub('#.+$', '', line)
- split_line = line.split()
- if len(split_line) < 3:
- continue
- expectation_type, test_file, ref_file = split_line
- parsed_list.setdefault(filesystem.join(test_dirpath, test_file), []).append((expectation_type, filesystem.join(test_dirpath, ref_file)))
- return parsed_list
- def reference_files(self, test_name):
- """Return a list of expectation (== or !=) and filename pairs"""
- reftest_list = self._get_reftest_list(test_name)
- if not reftest_list:
- reftest_list = []
- for expectation, prefix in (('==', ''), ('!=', '-mismatch')):
- for extention in Port._supported_reference_extensions:
- path = self.expected_filename(test_name, prefix + extention)
- if self._filesystem.exists(path):
- reftest_list.append((expectation, path))
- return reftest_list
- return reftest_list.get(self._filesystem.join(self.layout_tests_dir(), test_name), []) # pylint: disable=E1103
- def tests(self, paths):
- """Return the list of tests found. Both generic and platform-specific tests matching paths should be returned."""
- expanded_paths = self._expanded_paths(paths)
- tests = self._real_tests(expanded_paths)
- tests.extend(self._virtual_tests(expanded_paths, self.populated_virtual_test_suites()))
- return tests
- def _expanded_paths(self, paths):
- expanded_paths = []
- fs = self._filesystem
- all_platform_dirs = [path for path in fs.glob(fs.join(self.layout_tests_dir(), 'platform', '*')) if fs.isdir(path)]
- for path in paths:
- expanded_paths.append(path)
- if self.test_isdir(path) and not path.startswith('platform'):
- for platform_dir in all_platform_dirs:
- if fs.isdir(fs.join(platform_dir, path)) and platform_dir in self.baseline_search_path():
- expanded_paths.append(self.relative_test_filename(fs.join(platform_dir, path)))
- return expanded_paths
- def _real_tests(self, paths):
- # When collecting test cases, skip these directories
- skipped_directories = set(['.svn', '_svn', 'resources', 'script-tests', 'reference', 'reftest'])
- files = find_files.find(self._filesystem, self.layout_tests_dir(), paths, skipped_directories, Port._is_test_file, self.test_key)
- return [self.relative_test_filename(f) for f in files]
- # When collecting test cases, we include any file with these extensions.
- _supported_test_extensions = set(['.html', '.shtml', '.xml', '.xhtml', '.pl', '.htm', '.php', '.svg', '.mht'])
- _supported_reference_extensions = set(['.html', '.xml', '.xhtml', '.htm', '.svg'])
- @staticmethod
- # If any changes are made here be sure to update the isUsedInReftest method in old-run-webkit-tests as well.
- def is_reference_html_file(filesystem, dirname, filename):
- if filename.startswith('ref-') or filename.startswith('notref-'):
- return True
- filename_wihout_ext, ext = filesystem.splitext(filename)
- if ext not in Port._supported_reference_extensions:
- return False
- for suffix in ['-expected', '-expected-mismatch', '-ref', '-notref']:
- if filename_wihout_ext.endswith(suffix):
- return True
- return False
- @staticmethod
- def _has_supported_extension(filesystem, filename):
- """Return true if filename is one of the file extensions we want to run a test on."""
- extension = filesystem.splitext(filename)[1]
- return extension in Port._supported_test_extensions
- @staticmethod
- def _is_test_file(filesystem, dirname, filename):
- return Port._has_supported_extension(filesystem, filename) and not Port.is_reference_html_file(filesystem, dirname, filename)
- def test_key(self, test_name):
- """Turns a test name into a list with two sublists, the natural key of the
- dirname, and the natural key of the basename.
- This can be used when sorting paths so that files in a directory.
- directory are kept together rather than being mixed in with files in
- subdirectories."""
- dirname, basename = self.split_test(test_name)
- return (self._natural_sort_key(dirname + self.TEST_PATH_SEPARATOR), self._natural_sort_key(basename))
- def _natural_sort_key(self, string_to_split):
- """ Turns a string into a list of string and number chunks, i.e. "z23a" -> ["z", 23, "a"]
- This can be used to implement "natural sort" order. See:
- http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html
- http://nedbatchelder.com/blog/200712.html#e20071211T054956
- """
- def tryint(val):
- try:
- return int(val)
- except ValueError:
- return val
- return [tryint(chunk) for chunk in re.split('(\d+)', string_to_split)]
- def test_dirs(self):
- """Returns the list of top-level test directories."""
- layout_tests_dir = self.layout_tests_dir()
- return filter(lambda x: self._filesystem.isdir(self._filesystem.join(layout_tests_dir, x)),
- self._filesystem.listdir(layout_tests_dir))
- @memoized
- def test_isfile(self, test_name):
- """Return True if the test name refers to a directory of tests."""
- # Used by test_expectations.py to apply rules to whole directories.
- if self._filesystem.isfile(self.abspath_for_test(test_name)):
- return True
- base = self.lookup_virtual_test_base(test_name)
- return base and self._filesystem.isfile(self.abspath_for_test(base))
- @memoized
- def test_isdir(self, test_name):
- """Return True if the test name refers to a directory of tests."""
- # Used by test_expectations.py to apply rules to whole directories.
- if self._filesystem.isdir(self.abspath_for_test(test_name)):
- return True
- base = self.lookup_virtual_test_base(test_name)
- return base and self._filesystem.isdir(self.abspath_for_test(base))
- @memoized
- def test_exists(self, test_name):
- """Return True if the test name refers to an existing test or baseline."""
- # Used by test_expectations.py to determine if an entry refers to a
- # valid test and by printing.py to determine if baselines exist.
- return self.test_isfile(test_name) or self.test_isdir(test_name)
- def split_test(self, test_name):
- """Splits a test name into the 'directory' part and the 'basename' part."""
- index = test_name.rfind(self.TEST_PATH_SEPARATOR)
- if index < 1:
- return ('', test_name)
- return (test_name[0:index], test_name[index:])
- def normalize_test_name(self, test_name):
- """Returns a normalized version of the test name or test directory."""
- if test_name.endswith('/'):
- return test_name
- if self.test_isdir(test_name):
- return test_name + '/'
- return test_name
- def driver_cmd_line(self):
- """Prints the DRT command line that will be used."""
- driver = self.create_driver(0)
- return driver.cmd_line(self.get_option('pixel_tests'), [])
- def update_baseline(self, baseline_path, data):
- """Updates the baseline for a test.
- Args:
- baseline_path: the actual path to use for baseline, not the path to
- the test. This function is used to update either generic or
- platform-specific baselines, but we can't infer which here.
- data: contents of the baseline.
- """
- self._filesystem.write_binary_file(baseline_path, data)
- # FIXME: update callers to create a finder and call it instead of these next five routines (which should be protected).
- def webkit_base(self):
- return self._webkit_finder.webkit_base()
- def path_from_webkit_base(self, *comps):
- return self._webkit_finder.path_from_webkit_base(*comps)
- def path_to_script(self, script_name):
- return self._webkit_finder.path_to_script(script_name)
- def layout_tests_dir(self):
- return self._webkit_finder.layout_tests_dir()
- def perf_tests_dir(self):
- return self._webkit_finder.perf_tests_dir()
- def skipped_layout_tests(self, test_list):
- """Returns tests skipped outside of the TestExpectations files."""
- return set(self._tests_for_other_platforms()).union(self._skipped_tests_for_unsupported_features(test_list))
- def _tests_from_skipped_file_contents(self, skipped_file_contents):
- tests_to_skip = []
- for line in skipped_file_contents.split('\n'):
- line = line.strip()
- line = line.rstrip('/') # Best to normalize directory names to not include the trailing slash.
- if line.startswith('#') or not len(line):
- continue
- tests_to_skip.append(line)
- return tests_to_skip
- def _expectations_from_skipped_files(self, skipped_file_paths):
- tests_to_skip = []
- for search_path in skipped_file_paths:
- filename = self._filesystem.join(self._webkit_baseline_path(search_path), "Skipped")
- if not self._filesystem.exists(filename):
- _log.debug("Skipped does not exist: %s" % filename)
- continue
- _log.debug("Using Skipped file: %s" % filename)
- skipped_file_contents = self._filesystem.read_text_file(filename)
- tests_to_skip.extend(self._tests_from_skipped_file_contents(skipped_file_contents))
- return tests_to_skip
- @memoized
- def skipped_perf_tests(self):
- return self._expectations_from_skipped_files([self.perf_tests_dir()])
- def skips_perf_test(self, test_name):
- for test_or_category in self.skipped_perf_tests():
- if test_or_category == test_name:
- return True
- category = self._filesystem.join(self.perf_tests_dir(), test_or_category)
- if self._filesystem.isdir(category) and test_name.startswith(test_or_category):
- return True
- return False
- def name(self):
- """Returns a name that uniquely identifies this particular type of port
- (e.g., "mac-snowleopard" or "chromium-linux-x86_x64" and can be passed
- to factory.get() to instantiate the port."""
- return self._name
- def operating_system(self):
- # Subclasses should override this default implementation.
- return 'mac'
- def version(self):
- """Returns a string indicating the version of a given platform, e.g.
- 'leopard' or 'xp'.
- This is used to help identify the exact port when parsing test
- expectations, determining search paths, and logging information."""
- return self._version
- def architecture(self):
- return self._architecture
- def get_option(self, name, default_value=None):
- return getattr(self._options, name, default_value)
- def set_option_default(self, name, default_value):
- return self._options.ensure_value(name, default_value)
- @memoized
- def path_to_generic_test_expectations_file(self):
- return self._filesystem.join(self.layout_tests_dir(), 'TestExpectations')
- @memoized
- def path_to_test_expectations_file(self):
- """Update the test expectations to the passed-in string.
- This is used by the rebaselining tool. Raises NotImplementedError
- if the port does not use expectations files."""
- # FIXME: We need to remove this when we make rebaselining work with multiple files and just generalize expectations_files().
- # test_expectations are always in mac/ not mac-leopard/ by convention, hence we use port_name instead of name().
- return self._filesystem.join(self._webkit_baseline_path(self.port_name), 'TestExpectations')
- def relative_test_filename(self, filename):
- """Returns a test_name a relative unix-style path for a filename under the LayoutTests
- directory. Ports may legitimately return abspaths here if no relpath makes sense."""
- # Ports that run on windows need to override this method to deal with
- # filenames with backslashes in them.
- if filename.startswith(self.layout_tests_dir()):
- return self.host.filesystem.relpath(filename, self.layout_tests_dir())
- else:
- return self.host.filesystem.abspath(filename)
- @memoized
- def abspath_for_test(self, test_name):
- """Returns the full path to the file for a given test name. This is the
- inverse of relative_test_filename()."""
- return self._filesystem.join(self.layout_tests_dir(), test_name)
- def results_directory(self):
- """Absolute path to the place to store the test results (uses --results-directory)."""
- if not self._results_directory:
- option_val = self.get_option('results_directory') or self.default_results_directory()
- self._results_directory = self._filesystem.abspath(option_val)
- return self._results_directory
- def perf_results_directory(self):
- return self._build_path()
- def default_results_directory(self):
- """Absolute path to the default place to store the test results."""
- # Results are store relative to the built products to make it easy
- # to have multiple copies of webkit checked out and built.
- return self._build_path('layout-test-results')
- def setup_test_run(self):
- """Perform port-specific work at the beginning of a test run."""
- pass
- def clean_up_test_run(self):
- """Perform port-specific work at the end of a test run."""
- if self._image_differ:
- self._image_differ.stop()
- self._image_differ = None
- # FIXME: os.environ access should be moved to onto a common/system class to be more easily mockable.
- def _value_or_default_from_environ(self, name, default=None):
- if name in os.environ:
- return os.environ[name]
- return default
- def _copy_value_from_environ_if_set(self, clean_env, name):
- if name in os.environ:
- clean_env[name] = os.environ[name]
- def setup_environ_for_server(self, server_name=None):
- # We intentionally copy only a subset of os.environ when
- # launching subprocesses to ensure consistent test results.
- clean_env = {}
- variables_to_copy = [
- # For Linux:
- 'XAUTHORITY',
- 'HOME',
- 'LANG',
- 'LD_LIBRARY_PATH',
- 'DBUS_SESSION_BUS_ADDRESS',
- 'XDG_DATA_DIRS',
- # Darwin:
- 'DYLD_LIBRARY_PATH',
- 'HOME',
- # CYGWIN:
- 'HOMEDRIVE',
- 'HOMEPATH',
- '_NT_SYMBOL_PATH',
- # Windows:
- 'PATH',
- # Most ports (?):
- 'WEBKIT_TESTFONTS',
- 'WEBKITOUTPUTDIR',
- # Chromium:
- 'CHROME_DEVEL_SANDBOX',
- ]
- for variable in variables_to_copy:
- self._copy_value_from_environ_if_set(clean_env, variable)
- # For Linux:
- clean_env['DISPLAY'] = self._value_or_default_from_environ('DISPLAY', ':1')
- for string_variable in self.get_option('additional_env_var', []):
- [name, value] = string_variable.split('=', 1)
- clean_env[name] = value
- return clean_env
- def show_results_html_file(self, results_filename):
- """This routine should display the HTML file pointed at by
- results_filename in a users' browser."""
- return self.host.user.open_url(path.abspath_to_uri(self.host.platform, results_filename))
- def create_driver(self, worker_number, no_timeout=False):
- """Return a newly created Driver subclass for starting/stopping the test driver."""
- return driver.DriverProxy(self, worker_number, self._driver_class(), pixel_tests=self.get_option('pixel_tests'), no_timeout=no_timeout)
- def start_helper(self):
- """If a port needs to reconfigure graphics settings or do other
- things to ensure a known test configuration, it should override this
- method."""
- pass
- def start_http_server(self, additional_dirs=None, number_of_servers=None):
- """Start a web server. Raise an error if it can't start or is already running.
- Ports can stub this out if they don't need a web server to be running."""
- assert not self._http_server, 'Already running an http server.'
- if self._uses_apache():
- server = apache_http_server.LayoutTestApacheHttpd(self, self.results_directory(), additional_dirs=additional_dirs, number_of_servers=number_of_servers)
- else:
- server = http_server.Lighttpd(self, self.results_directory(), additional_dirs=additional_dirs, number_of_servers=number_of_servers)
- server.start()
- self._http_server = server
- def start_websocket_server(self):
- """Start a web server. Raise an error if it can't start or is already running.
- Ports can stub this out if they don't need a websocket server to be running."""
- assert not self._websocket_server, 'Already running a websocket server.'
- server = websocket_server.PyWebSocket(self, self.results_directory())
- server.start()
- self._websocket_server = server
- def http_server_supports_ipv6(self):
- # Cygwin is the only platform to still use Apache 1.3, which only supports IPV4.
- # Once it moves to Apache 2, we can drop this method altogether.
- if self.host.platform.is_cygwin():
- return False
- return True
- def acquire_http_lock(self):
- self._http_lock = http_lock.HttpLock(None, filesystem=self._filesystem, executive=self._executive)
- self._http_lock.wait_for_httpd_lock()
- def stop_helper(self):
- """Shut down the test helper if it is running. Do nothing if
- it isn't, or it isn't available. If a port overrides start_helper()
- it must override this routine as well."""
- pass
- def stop_http_server(self):
- """Shut down the http server if it is running. Do nothing if it isn't."""
- if self._http_server:
- self._http_server.stop()
- self._http_server = None
- def stop_websocket_server(self):
- """Shut down the websocket server if it is running. Do nothing if it isn't."""
- if self._websocket_server:
- self._websocket_server.stop()
- self._websocket_server = None
- def release_http_lock(self):
- if self._http_lock:
- self._http_lock.cleanup_http_lock()
- def exit_code_from_summarized_results(self, unexpected_results):
- """Given summarized results, compute the exit code to be returned by new-run-webkit-tests.
- Bots turn red when this function returns a non-zero value. By default, return the number of regressions
- to avoid turning bots red by flaky failures, unexpected passes, and missing results"""
- # Don't turn bots red for flaky failures, unexpected passes, and missing results.
- return unexpected_results['num_regressions']
- #
- # TEST EXPECTATION-RELATED METHODS
- #
- def test_configuration(self):
- """Returns the current TestConfiguration for the port."""
- if not self._test_configuration:
- self._test_configuration = TestConfiguration(self._version, self._architecture, self._options.configuration.lower())
- return self._test_configuration
- # FIXME: Belongs on a Platform object.
- @memoized
- def all_test_configurations(self):
- """Returns a list of TestConfiguration instances, representing all available
- test configurations for this port."""
- return self._generate_all_test_configurations()
- # FIXME: Belongs on a Platform object.
- def configuration_specifier_macros(self):
- """Ports may provide a way to abbreviate configuration specifiers to conveniently
- refer to them as one term or alias specific values to more generic ones. For example:
- (xp, vista, win7) -> win # Abbreviate all Windows versions into one namesake.
- (lucid) -> linux # Change specific name of the Linux distro to a more generic term.
- Returns a dictionary, each key representing a macro term ('win', for example),
- and value being a list of valid configuration specifiers (such as ['xp', 'vista', 'win7'])."""
- return {}
- def all_baseline_variants(self):
- """Returns a list of platform names sufficient to cover all the baselines.
- The list should be sorted so that a later platform will reuse
- an earlier platform's baselines if they are the same (e.g.,
- 'snowleopard' should precede 'leopard')."""
- raise NotImplementedError
- def uses_test_expectations_file(self):
- # This is different from checking test_expectations() is None, because
- # some ports have Skipped files which are returned as part of test_expectations().
- return self._filesystem.exists(self.path_to_test_expectations_file())
- def warn_if_bug_missing_in_test_expectations(self):
- return False
- def expectations_dict(self):
- """Returns an OrderedDict of name -> expectations strings.
- The names are expected to be (but not required to be) paths in the filesystem.
- If the name is a path, the file can be considered updatable for things like rebaselining,
- so don't use names that are paths if they're not paths.
- Generally speaking the ordering should be files in the filesystem in cascade order
- (TestExpectations followed by Skipped, if the port honors both formats),
- then any built-in expectations (e.g., from compile-time exclusions), then --additional-expectations options."""
- # FIXME: rename this to test_expectations() once all the callers are updated to know about the ordered dict.
- expectations = OrderedDict()
- for path in self.expectations_files():
- if self._filesystem.exists(path):
- expectations[path] = self._filesystem.read_text_file(path)
- for path in self.get_option('additional_expectations', []):
- expanded_path = self._filesystem.expanduser(path)
- if self._filesystem.exists(expanded_path):
- _log.debug("reading additional_expectations from path '%s'" % path)
- expectations[path] = self._filesystem.read_text_file(expanded_path)
- else:
- _log.warning("additional_expectations path '%s' does not exist" % path)
- return expectations
- def _port_specific_expectations_files(self):
- # Unlike baseline_search_path, we only want to search [WK2-PORT, PORT-VERSION, PORT] and any directories
- # included via --additional-platform-directory, not the full casade.
- search_paths = [self.port_name]
- non_wk2_name = self.name().replace('-wk2', '')
- if non_wk2_name != self.port_name:
- search_paths.append(non_wk2_name)
- if self.get_option('webkit_test_runner'):
- # Because nearly all of the skipped tests for WebKit 2 are due to cross-platform
- # issues, all wk2 ports share a skipped list under platform/wk2.
- search_paths.extend(["wk2", self._wk2_port_name()])
- search_paths.extend(self.get_option("additional_platform_directory", []))
- return [self._filesystem.join(self._webkit_baseline_path(d), 'TestExpectations') for d in search_paths]
- def expectations_files(self):
- return [self.path_to_generic_test_expectations_file()] + self._port_specific_expectations_files()
- def repository_paths(self):
- """Returns a list of (repository_name, repository_path) tuples of its depending code base.
- By default it returns a list that only contains a ('WebKit', <webkitRepositoryPath>) tuple."""
- # We use LayoutTest directory here because webkit_base isn't a part of WebKit repository in Chromium port
- # where turnk isn't checked out as a whole.
- return [('WebKit', self.layout_tests_dir())]
- _WDIFF_DEL = '##WDIFF_DEL##'
- _WDIFF_ADD = '##WDIFF_ADD##'
- _WDIFF_END = '##WDIFF_END##'
- def _format_wdiff_output_as_html(self, wdiff):
- wdiff = cgi.escape(wdiff)
- wdiff = wdiff.replace(self._WDIFF_DEL, "<span class=del>")
- wdiff = wdiff.replace(self._WDIFF_ADD, "<span class=add>")
- wdiff = wdiff.replace(self._WDIFF_END, "</span>")
- html = "<head><style>.del { background: #faa; } "
- html += ".add { background: #afa; }</style></head>"
- html += "<pre>%s</pre>" % wdiff
- return html
- def _wdiff_command(self, actual_filename, expected_filename):
- executable = self._path_to_wdiff()
- return [executable,
- "--start-delete=%s" % self._WDIFF_DEL,
- "--end-delete=%s" % self._WDIFF_END,
- "--start-insert=%s" % self._WDIFF_ADD,
- "--end-insert=%s" % self._WDIFF_END,
- actual_filename,
- expected_filename]
- @staticmethod
- def _handle_wdiff_error(script_error):
- # Exit 1 means the files differed, any other exit code is an error.
- if script_error.exit_code != 1:
- return
- def _run_wdiff(self, actual_filename, expected_filename):
- """Runs wdiff and may throw exceptions.
- This is mostly a hook for unit testing."""
- # Diffs are treated as binary as they may include multiple files
- # with conflicting encodings. Thus we do not decode the output.
- command = self._wdiff_command(actual_filename, expected_filename)
- wdiff = self._executive.run_command(command, decode_output=False,
- error_handler=self._handle_wdiff_error)
- return self._format_wdiff_output_as_html(wdiff)
- def wdiff_text(self, actual_filename, expected_filename):
- """Returns a string of HTML indicating the word-level diff of the
- contents of the two filenames. Returns an empty string if word-level
- diffing isn't available."""
- if not self.wdiff_available():
- return ""
- try:
- # It's possible to raise a ScriptError we pass wdiff invalid paths.
- return self._run_wdiff(actual_filename, expected_filename)
- except OSError, e:
- if e.errno in [errno.ENOENT, errno.EACCES, errno.ECHILD]:
- # Silently ignore cases where wdiff is missing.
- self._wdiff_available = False
- return ""
- raise
- # This is a class variable so we can test error output easily.
- _pretty_patch_error_html = "Failed to run PrettyPatch, see error log."
- def pretty_patch_text(self, diff_path):
- if self._pretty_patch_available is None:
- self._pretty_patch_available = self.check_pretty_patch(logging=False)
- if not self._pretty_patch_available:
- return self._pretty_patch_error_html
- command = ("ruby", "-I", self._filesystem.dirname(self._pretty_patch_path),
- self._pretty_patch_path, diff_path)
- try:
- # Diffs are treated as binary (we pass decode_output=False) as they
- # may contain multiple files of conflicting encodings.
- return self._executive.run_command(command, decode_output=False)
- except OSError, e:
- # If the system is missing ruby log the error and stop trying.
- self._pretty_patch_available = False
- _log.error("Failed to run PrettyPatch (%s): %s" % (command, e))
- return self._pretty_patch_error_html
- except ScriptError, e:
- # If ruby failed to run for some reason, log the command
- # output and stop trying.
- self._pretty_patch_available = False
- _log.error("Failed to run PrettyPatch (%s):\n%s" % (command, e.message_with_output()))
- return self._pretty_patch_error_html
- def default_configuration(self):
- return self._config.default_configuration()
- #
- # PROTECTED ROUTINES
- #
- # The routines below should only be called by routines in this class
- # or any of its subclasses.
- #
- def _uses_apache(self):
- return True
- # FIXME: This does not belong on the port object.
- @memoized
- def _path_to_apache(self):
- """Returns the full path to the apache binary.
- This is needed only by ports that use the apache_http_server module."""
- # The Apache binary path can vary depending on OS and distribution
- # See http://wiki.apache.org/httpd/DistrosDefaultLayout
- for path in ["/usr/sbin/httpd", "/usr/sbin/apache2"]:
- if self._filesystem.exists(path):
- return path
- _log.error("Could not find apache. Not installed or unknown path.")
- return None
- # FIXME: This belongs on some platform abstraction instead of Port.
- def _is_redhat_based(self):
- return self._filesystem.exists('/etc/redhat-release')
- def _is_debian_based(self):
- return self._filesystem.exists('/etc/debian_version')
- def _is_arch_based(self):
- return self._filesystem.exists('/etc/arch-release')
- def _apache_version(self):
- config = self._executive.run_command([self._path_to_apache(), '-v'])
- return re.sub(r'(?:.|\n)*Server version: Apache/(\d+\.\d+)(?:.|\n)*', r'\1', config)
- # We pass sys_platform into this method to make it easy to unit test.
- def _apache_config_file_name_for_platform(self, sys_platform):
- if sys_platform == 'cygwin':
- return 'cygwin-httpd.conf' # CYGWIN is the only platform to still use Apache 1.3.
- if sys_platform.startswith('linux'):
- if self._is_redhat_based():
- return 'fedora-httpd-' + self._apache_version() + '.conf'
- if self._is_debian_based():
- return 'apache2-debian-httpd.conf'
- if self._is_arch_based():
- return 'archlinux-httpd.conf'
- # All platforms use apache2 except for CYGWIN (and Mac OS X Tiger and prior, which we no longer support).
- return "apache2-httpd.conf"
- def _path_to_apache_config_file(self):
- """Returns the full path to the apache configuration file.
- If the WEBKIT_HTTP_SERVER_CONF_PATH environment variable is set, its
- contents will be used instead.
- This is needed only by ports that use the apache_http_server module."""
- config_file_from_env = os.environ.get('WEBKIT_HTTP_SERVER_CONF_PATH')
- if config_file_from_env:
- if not self._filesystem.exists(config_file_from_env):
- raise IOError('%s was not found on the system' % config_file_from_env)
- return config_file_from_env
- config_file_name = self._apache_config_file_name_for_platform(sys.platform)
- return self._filesystem.join(self.layout_tests_dir(), 'http', 'conf', config_file_name)
- def _build_path(self, *comps):
- root_directory = self.get_option('root')
- if not root_directory:
- build_directory = self.get_option('build_directory')
- if build_directory:
- root_directory = self._filesystem.join(build_directory, self.get_option('configuration'))
- else:
- root_directory = self._config.build_directory(self.get_option('configuration'))
- # Set --root so that we can pass this to subprocesses and avoid making the
- # slow call to config.build_directory() N times in each worker.
- # FIXME: This is like @memoized, but more annoying and fragile; there should be another
- # way to propagate values without mutating the options list.
- self.set_option_default('root', root_directory)
- return self._filesystem.join(self._filesystem.abspath(root_directory), *comps)
- def _path_to_driver(self, configuration=None):
- """Returns the full path to the test driver (DumpRenderTree)."""
- return self._build_path(self.driver_name())
- def _driver_tempdir(self):
- return self._filesystem.mkdtemp(prefix='%s-' % self.driver_name())
- def _driver_tempdir_for_environment(self):
- return self._driver_tempdir()
- def _path_to_webcore_library(self):
- """Returns the full path to a built copy of WebCore."""
- return None
- def _path_to_helper(self):
- """Returns the full path to the layout_test_helper binary, which
- is used to help configure the system for the test run, or None
- if no helper is needed.
- This is likely only used by start/stop_helper()."""
- return None
- def _path_to_image_diff(self):
- """Returns the full path to the image_diff binary, or None if it is not available.
- This is likely used only by diff_image()"""
- return self._build_path('ImageDiff')
- def _path_to_lighttpd(self):
- """Returns the path to the LigHTTPd binary.
- This is needed only by ports that use the http_server.py module."""
- raise NotImplementedError('Port._path_to_lighttpd')
- def _path_to_lighttpd_modules(self):
- """Returns the path to the LigHTTPd modules directory.
- This is needed only by ports that use the http_server.py module."""
- raise NotImplementedError('Port._path_to_lighttpd_modules')
- def _path_to_lighttpd_php(self):
- """Returns the path to the LigHTTPd PHP executable.
- This is needed only by ports that use the http_server.py module."""
- raise NotImplementedError('Port._path_to_lighttpd_php')
- @memoized
- def _path_to_wdiff(self):
- """Returns the full path to the wdiff binary, or None if it is not available.
- This is likely used only by wdiff_text()"""
- for path in ("/usr/bin/wdiff", "/usr/bin/dwdiff"):
- if self._filesystem.exists(path):
- return path
- return None
- def _webkit_baseline_path(self, platform):
- """Return the full path to the top of the baseline tree for a
- given platform."""
- return self._filesystem.join(self.layout_tests_dir(), 'platform', platform)
- # FIXME: Belongs on a Platform object.
- def _generate_all_test_configurations(self):
- """Generates a list of TestConfiguration instances, representing configurations
- for a platform across all OSes, architectures, build and graphics types."""
- raise NotImplementedError('Port._generate_test_configurations')
- def _driver_class(self):
- """Returns the port's driver implementation."""
- return driver.Driver
- def _get_crash_log(self, name, pid, stdout, stderr, newer_than):
- name_str = name or '<unknown process name>'
- pid_str = str(pid or '<unknown>')
- stdout_lines = (stdout or '<empty>').decode('utf8', 'replace').splitlines()
- stderr_lines = (stderr or '<empty>').decode('utf8', 'replace').splitlines()
- return (stderr, 'crash log for %s (pid %s):\n%s\n%s\n' % (name_str, pid_str,
- '\n'.join(('STDOUT: ' + l) for l in stdout_lines),
- '\n'.join(('STDERR: ' + l) for l in stderr_lines)))
- def look_for_new_crash_logs(self, crashed_processes, start_time):
- pass
- def look_for_new_samples(self, unresponsive_processes, start_time):
- pass
- def sample_process(self, name, pid):
- pass
- def virtual_test_suites(self):
- return []
- def find_system_pid(self, name, pid):
- # This is only overridden on Windows
- return pid
- @memoized
- def populated_virtual_test_suites(self):
- suites = self.virtual_test_suites()
- # Sanity-check the suites to make sure they don't point to other suites.
- suite_dirs = [suite.name for suite in suites]
- for suite in suites:
- assert suite.base not in suite_dirs
- for suite in suites:
- base_tests = self._real_tests([suite.base])
- suite.tests = {}
- for test in base_tests:
- suite.tests[test.replace(suite.base, suite.name, 1)] = test
- return suites
- def _virtual_tests(self, paths, suites):
- virtual_tests = list()
- for suite in suites:
- if paths:
- for test in suite.tests:
- if any(test.startswith(p) for p in paths):
- virtual_tests.append(test)
- else:
- virtual_tests.extend(suite.tests.keys())
- return virtual_tests
- def lookup_virtual_test_base(self, test_name):
- for suite in self.populated_virtual_test_suites():
- if test_name.startswith(suite.name):
- return test_name.replace(suite.name, suite.base, 1)
- return None
- def lookup_virtual_test_args(self, test_name):
- for suite in self.populated_virtual_test_suites():
- if test_name.startswith(suite.name):
- return suite.args
- return []
- def should_run_as_pixel_test(self, test_input):
- if not self._options.pixel_tests:
- return False
- if self._options.pixel_test_directories:
- return any(test_input.test_name.startswith(directory) for directory in self._options.pixel_test_directories)
- return self._should_run_as_pixel_test(test_input)
- def _should_run_as_pixel_test(self, test_input):
- # Default behavior is to allow all test to run as pixel tests if --pixel-tests is on and
- # --pixel-test-directory is not specified.
- return True
- # FIXME: Eventually we should standarize port naming, and make this method smart enough
- # to use for all port configurations (including architectures, graphics types, etc).
- def _port_flag_for_scripts(self):
- # This is overrriden by ports which need a flag passed to scripts to distinguish the use of that port.
- # For example --qt on linux, since a user might have both Gtk and Qt libraries installed.
- return None
- # This is modeled after webkitdirs.pm argumentsForConfiguration() from old-run-webkit-tests
- def _arguments_for_configuration(self):
- config_args = []
- config_args.append(self._config.flag_for_configuration(self.get_option('configuration')))
- # FIXME: We may need to add support for passing --32-bit like old-run-webkit-tests had.
- port_flag = self._port_flag_for_scripts()
- if port_flag:
- config_args.append(port_flag)
- return config_args
- def _run_script(self, script_name, args=None, include_configuration_arguments=True, decode_output=True, env=None):
- run_script_command = [self.path_to_script(script_name)]
- if include_configuration_arguments:
- run_script_command.extend(self._arguments_for_configuration())
- if args:
- run_script_command.extend(args)
- output = self._executive.run_command(run_script_command, cwd=self.webkit_base(), decode_output=decode_output, env=env)
- _log.debug('Output of %s:\n%s' % (run_script_command, output))
- return output
- def _build_driver(self):
- environment = self.host.copy_current_environment()
- environment.disable_gcc_smartquotes()
- env = environment.to_dictionary()
- # FIXME: We build both DumpRenderTree and WebKitTestRunner for
- # WebKitTestRunner runs because DumpRenderTree still includes
- # the DumpRenderTreeSupport module and the TestNetscapePlugin.
- # These two projects should be factored out into their own
- # projects.
- try:
- self._run_script("build-dumprendertree", args=self._build_driver_flags(), env=env)
- if self.get_option('webkit_test_runner'):
- self._run_script("build-webkittestrunner", args=self._build_driver_flags(), env=env)
- except ScriptError, e:
- _log.error(e.message_with_output(output_limit=None))
- return False
- return True
- def _build_driver_flags(self):
- return []
- def test_search_path(self):
- return self.baseline_search_path()
- def _tests_for_other_platforms(self):
- # By default we will skip any directory under LayoutTests/platform
- # that isn't in our baseline search path (this mirrors what
- # old-run-webkit-tests does in findTestsToRun()).
- # Note this returns LayoutTests/platform/*, not platform/*/*.
- entries = self._filesystem.glob(self._webkit_baseline_path('*'))
- dirs_to_skip = []
- for entry in entries:
- if self._filesystem.isdir(entry) and entry not in self.test_search_path():
- basename = self._filesystem.basename(entry)
- dirs_to_skip.append('platform/%s' % basename)
- return dirs_to_skip
- def _runtime_feature_list(self):
- """If a port makes certain features available only through runtime flags, it can override this routine to indicate which ones are available."""
- return None
- def nm_command(self):
- return 'nm'
- def _modules_to_search_for_symbols(self):
- path = self._path_to_webcore_library()
- if path:
- return [path]
- return []
- def _symbols_string(self):
- symbols = ''
- for path_to_module in self._modules_to_search_for_symbols():
- try:
- symbols += self._executive.run_command([self.nm_command(), path_to_module], error_handler=self._executive.ignore_error)
- except OSError, e:
- _log.warn("Failed to run nm: %s. Can't determine supported features correctly." % e)
- return symbols
- # Ports which use run-time feature detection should define this method and return
- # a dictionary mapping from Feature Names to skipped directoires. NRWT will
- # run DumpRenderTree --print-supported-features and parse the output.
- # If the Feature Names are not found in the output, the corresponding directories
- # will be skipped.
- def _missing_feature_to_skipped_tests(self):
- """Return the supported feature dictionary. Keys are feature names and values
- are the lists of directories to skip if the feature name is not matched."""
- # FIXME: This list matches WebKitWin and should be moved onto the Win port.
- return {
- "Accelerated Compositing": ["compositing"],
- "3D Rendering": ["animations/3d", "transforms/3d"],
- }
- def _has_test_in_directories(self, directory_lists, test_list):
- if not test_list:
- return False
- directories = itertools.chain.from_iterable(directory_lists)
- for directory, test in itertools.product(directories, test_list):
- if test.startswith(directory):
- return True
- return False
- def _skipped_tests_for_unsupported_features(self, test_list):
- # Only check the runtime feature list of there are tests in the test_list that might get skipped.
- # This is a performance optimization to avoid the subprocess call to DRT.
- # If the port supports runtime feature detection, disable any tests
- # for features missing from the runtime feature list.
- # If _runtime_feature_list returns a non-None value, then prefer
- # runtime feature detection over static feature detection.
- if self._has_test_in_directories(self._missing_feature_to_skipped_tests().values(), test_list):
- supported_feature_list = self._runtime_feature_list()
- if supported_feature_list is not None:
- return reduce(operator.add, [directories for feature, directories in self._missing_feature_to_skipped_tests().items() if feature not in supported_feature_list])
- return []
- def _wk2_port_name(self):
- # By current convention, the WebKit2 name is always mac-wk2, win-wk2, not mac-leopard-wk2, etc,
- # except for Qt because WebKit2 is only supported by Qt 5.0 (therefore: qt-5.0-wk2).
- return "%s-wk2" % self.port_name
- class VirtualTestSuite(object):
- def __init__(self, name, base, args, tests=None):
- self.name = name
- self.base = base
- self.args = args
- self.tests = tests or set()
- def __repr__(self):
- return "VirtualTestSuite('%s', '%s', %s)" % (self.name, self.base, self.args)
|