summaryrefslogtreecommitdiffstats
path: root/Tools/Scripts/webkitpy/layout_tests/port
diff options
context:
space:
mode:
authorBen Murdoch <benm@google.com>2011-05-05 14:36:32 +0100
committerBen Murdoch <benm@google.com>2011-05-10 15:38:30 +0100
commitf05b935882198ccf7d81675736e3aeb089c5113a (patch)
tree4ea0ca838d9ef1b15cf17ddb3928efb427c7e5a1 /Tools/Scripts/webkitpy/layout_tests/port
parent60fbdcc62bced8db2cb1fd233cc4d1e4ea17db1b (diff)
downloadexternal_webkit-f05b935882198ccf7d81675736e3aeb089c5113a.zip
external_webkit-f05b935882198ccf7d81675736e3aeb089c5113a.tar.gz
external_webkit-f05b935882198ccf7d81675736e3aeb089c5113a.tar.bz2
Merge WebKit at r74534: Initial merge by git.
Change-Id: I6ccd1154fa1b19c2ec2a66878eb675738735f1eb
Diffstat (limited to 'Tools/Scripts/webkitpy/layout_tests/port')
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/__init__.py32
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/apache_http_server.py230
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/base.py862
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py315
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium.py546
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py152
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py73
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py190
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py182
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium_mac_unittest.py40
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py193
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium_win.py173
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/chromium_win_unittest.py74
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/config.py169
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/config_mock.py50
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/config_standalone.py70
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/config_unittest.py202
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/dryrun.py132
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/factory.py113
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/factory_unittest.py188
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/google_chrome.py122
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/google_chrome_unittest.py103
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/gtk.py116
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/http_lock.py133
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/http_lock_unittest.py111
-rwxr-xr-xTools/Scripts/webkitpy/layout_tests/port/http_server.py233
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/http_server_base.py83
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/httpd2.pem41
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/lighttpd.conf90
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/mac.py152
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/mac_unittest.py81
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py97
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/qt.py119
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/server_process.py225
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/test.py343
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/test_files.py128
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/test_files_unittest.py75
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/webkit.py497
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py68
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/websocket_server.py257
-rw-r--r--Tools/Scripts/webkitpy/layout_tests/port/win.py75
41 files changed, 7135 insertions, 0 deletions
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/__init__.py b/Tools/Scripts/webkitpy/layout_tests/port/__init__.py
new file mode 100644
index 0000000..e3ad6f4
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/__init__.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+# 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 name of Google Inc. 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.
+
+"""Port-specific entrypoints for the layout tests test infrastructure."""
+
+from factory import get
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/apache_http_server.py b/Tools/Scripts/webkitpy/layout_tests/port/apache_http_server.py
new file mode 100644
index 0000000..46617f6
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/apache_http_server.py
@@ -0,0 +1,230 @@
+#!/usr/bin/env python
+# 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 name of Google Inc. 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.
+
+"""A class to start/stop the apache http server used by layout tests."""
+
+
+from __future__ import with_statement
+
+import codecs
+import logging
+import optparse
+import os
+import re
+import subprocess
+import sys
+
+import http_server_base
+
+_log = logging.getLogger("webkitpy.layout_tests.port.apache_http_server")
+
+
+class LayoutTestApacheHttpd(http_server_base.HttpServerBase):
+
+ def __init__(self, port_obj, output_dir):
+ """Args:
+ port_obj: handle to the platform-specific routines
+ output_dir: the absolute path to the layout test result directory
+ """
+ http_server_base.HttpServerBase.__init__(self, port_obj)
+ self._output_dir = output_dir
+ self._httpd_proc = None
+ port_obj.maybe_make_directory(output_dir)
+
+ self.mappings = [{'port': 8000},
+ {'port': 8080},
+ {'port': 8081},
+ {'port': 8443, 'sslcert': True}]
+
+ # The upstream .conf file assumed the existence of /tmp/WebKit for
+ # placing apache files like the lock file there.
+ self._runtime_path = os.path.join("/tmp", "WebKit")
+ port_obj.maybe_make_directory(self._runtime_path)
+
+ # The PID returned when Apache is started goes away (due to dropping
+ # privileges?). The proper controlling PID is written to a file in the
+ # apache runtime directory.
+ self._pid_file = os.path.join(self._runtime_path, 'httpd.pid')
+
+ test_dir = self._port_obj.layout_tests_dir()
+ js_test_resources_dir = self._cygwin_safe_join(test_dir, "fast", "js",
+ "resources")
+ mime_types_path = self._cygwin_safe_join(test_dir, "http", "conf",
+ "mime.types")
+ cert_file = self._cygwin_safe_join(test_dir, "http", "conf",
+ "webkit-httpd.pem")
+ access_log = self._cygwin_safe_join(output_dir, "access_log.txt")
+ error_log = self._cygwin_safe_join(output_dir, "error_log.txt")
+ document_root = self._cygwin_safe_join(test_dir, "http", "tests")
+
+ # FIXME: We shouldn't be calling a protected method of _port_obj!
+ executable = self._port_obj._path_to_apache()
+ if self._is_cygwin():
+ executable = self._get_cygwin_path(executable)
+
+ cmd = [executable,
+ '-f', "\"%s\"" % self._get_apache_config_file_path(test_dir, output_dir),
+ '-C', "\'DocumentRoot \"%s\"\'" % document_root,
+ '-c', "\'Alias /js-test-resources \"%s\"'" % js_test_resources_dir,
+ '-C', "\'Listen %s\'" % "127.0.0.1:8000",
+ '-C', "\'Listen %s\'" % "127.0.0.1:8081",
+ '-c', "\'TypesConfig \"%s\"\'" % mime_types_path,
+ '-c', "\'CustomLog \"%s\" common\'" % access_log,
+ '-c', "\'ErrorLog \"%s\"\'" % error_log,
+ '-C', "\'User \"%s\"\'" % os.environ.get("USERNAME",
+ os.environ.get("USER", ""))]
+
+ if self._is_cygwin():
+ cygbin = self._port_obj._path_from_base('third_party', 'cygwin',
+ 'bin')
+ # Not entirely sure why, but from cygwin we need to run the
+ # httpd command through bash.
+ self._start_cmd = [
+ os.path.join(cygbin, 'bash.exe'),
+ '-c',
+ 'PATH=%s %s' % (self._get_cygwin_path(cygbin), " ".join(cmd)),
+ ]
+ else:
+ # TODO(ojan): When we get cygwin using Apache 2, use set the
+ # cert file for cygwin as well.
+ cmd.extend(['-c', "\'SSLCertificateFile %s\'" % cert_file])
+ # Join the string here so that Cygwin/Windows and Mac/Linux
+ # can use the same code. Otherwise, we could remove the single
+ # quotes above and keep cmd as a sequence.
+ self._start_cmd = " ".join(cmd)
+
+ def _is_cygwin(self):
+ return sys.platform in ("win32", "cygwin")
+
+ def _cygwin_safe_join(self, *parts):
+ """Returns a platform appropriate path."""
+ path = os.path.join(*parts)
+ if self._is_cygwin():
+ return self._get_cygwin_path(path)
+ return path
+
+ def _get_cygwin_path(self, path):
+ """Convert a Windows path to a cygwin path.
+
+ The cygpath utility insists on converting paths that it thinks are
+ Cygwin root paths to what it thinks the correct roots are. So paths
+ such as "C:\b\slave\webkit-release\build\third_party\cygwin\bin"
+ are converted to plain "/usr/bin". To avoid this, we
+ do the conversion manually.
+
+ The path is expected to be an absolute path, on any drive.
+ """
+ drive_regexp = re.compile(r'([a-z]):[/\\]', re.IGNORECASE)
+
+ def lower_drive(matchobj):
+ return '/cygdrive/%s/' % matchobj.group(1).lower()
+ path = drive_regexp.sub(lower_drive, path)
+ return path.replace('\\', '/')
+
+ def _get_apache_config_file_path(self, test_dir, output_dir):
+ """Returns the path to the apache config file to use.
+ Args:
+ test_dir: absolute path to the LayoutTests directory.
+ output_dir: absolute path to the layout test results directory.
+ """
+ httpd_config = self._port_obj._path_to_apache_config_file()
+ httpd_config_copy = os.path.join(output_dir, "httpd.conf")
+ # httpd.conf is always utf-8 according to http://archive.apache.org/gnats/10125
+ with codecs.open(httpd_config, "r", "utf-8") as httpd_config_file:
+ httpd_conf = httpd_config_file.read()
+ if self._is_cygwin():
+ # This is a gross hack, but it lets us use the upstream .conf file
+ # and our checked in cygwin. This tells the server the root
+ # directory to look in for .so modules. It will use this path
+ # plus the relative paths to the .so files listed in the .conf
+ # file. We have apache/cygwin checked into our tree so
+ # people don't have to install it into their cygwin.
+ cygusr = self._port_obj._path_from_base('third_party', 'cygwin',
+ 'usr')
+ httpd_conf = httpd_conf.replace('ServerRoot "/usr"',
+ 'ServerRoot "%s"' % self._get_cygwin_path(cygusr))
+
+ with codecs.open(httpd_config_copy, "w", "utf-8") as file:
+ file.write(httpd_conf)
+
+ if self._is_cygwin():
+ return self._get_cygwin_path(httpd_config_copy)
+ return httpd_config_copy
+
+ def _get_virtual_host_config(self, document_root, port, ssl=False):
+ """Returns a <VirtualHost> directive block for an httpd.conf file.
+ It will listen to 127.0.0.1 on each of the given port.
+ """
+ return '\n'.join(('<VirtualHost 127.0.0.1:%s>' % port,
+ 'DocumentRoot "%s"' % document_root,
+ ssl and 'SSLEngine On' or '',
+ '</VirtualHost>', ''))
+
+ def _start_httpd_process(self):
+ """Starts the httpd process and returns whether there were errors."""
+ # Use shell=True because we join the arguments into a string for
+ # the sake of Window/Cygwin and it needs quoting that breaks
+ # shell=False.
+ # FIXME: We should not need to be joining shell arguments into strings.
+ # shell=True is a trail of tears.
+ # Note: Not thread safe: http://bugs.python.org/issue2320
+ self._httpd_proc = subprocess.Popen(self._start_cmd,
+ stderr=subprocess.PIPE,
+ shell=True)
+ err = self._httpd_proc.stderr.read()
+ if len(err):
+ _log.debug(err)
+ return False
+ return True
+
+ def start(self):
+ """Starts the apache http server."""
+ # Stop any currently running servers.
+ self.stop()
+
+ _log.debug("Starting apache http server")
+ server_started = self.wait_for_action(self._start_httpd_process)
+ if server_started:
+ _log.debug("Apache started. Testing ports")
+ server_started = self.wait_for_action(
+ self.is_server_running_on_all_ports)
+
+ if server_started:
+ _log.debug("Server successfully started")
+ else:
+ raise Exception('Failed to start http server')
+
+ def stop(self):
+ """Stops the apache http server."""
+ _log.debug("Shutting down any running http servers")
+ httpd_pid = None
+ if os.path.exists(self._pid_file):
+ httpd_pid = int(open(self._pid_file).readline())
+ # FIXME: We shouldn't be calling a protected method of _port_obj!
+ self._port_obj._shut_down_http_server(httpd_pid)
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/base.py b/Tools/Scripts/webkitpy/layout_tests/port/base.py
new file mode 100644
index 0000000..97b54c9
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/base.py
@@ -0,0 +1,862 @@
+#!/usr/bin/env python
+# 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 entrypoints for the layout tests
+test infrastructure (the Port and Driver classes)."""
+
+import cgi
+import difflib
+import errno
+import os
+import shlex
+import sys
+import time
+
+import apache_http_server
+import config as port_config
+import http_lock
+import http_server
+import test_files
+import websocket_server
+
+from webkitpy.common import system
+from webkitpy.common.system import filesystem
+from webkitpy.common.system import logutils
+from webkitpy.common.system import path
+from webkitpy.common.system.executive import Executive, ScriptError
+from webkitpy.common.system.user import User
+
+
+_log = logutils.get_logger(__file__)
+
+
+class DummyOptions(object):
+ """Fake implementation of optparse.Values. Cloned from
+ webkitpy.tool.mocktool.MockOptions.
+
+ """
+
+ def __init__(self, **kwargs):
+ # The caller can set option values using keyword arguments. We don't
+ # set any values by default because we don't know how this
+ # object will be used. Generally speaking unit tests should
+ # subclass this or provider wrapper functions that set a common
+ # set of options.
+ for key, value in kwargs.items():
+ self.__dict__[key] = value
+
+
+# FIXME: This class should merge with webkitpy.webkit_port at some point.
+class Port(object):
+ """Abstract class for Port-specific hooks for the layout_test package."""
+
+ def __init__(self, port_name=None, options=None,
+ executive=None,
+ user=None,
+ filesystem=None,
+ config=None,
+ **kwargs):
+ self._name = port_name
+ self._options = options
+ if self._options is None:
+ # 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 = DummyOptions()
+ self._executive = executive or Executive()
+ self._user = user or User()
+ self._filesystem = filesystem or system.filesystem.FileSystem()
+ self._config = config or port_config.Config(self._executive,
+ self._filesystem)
+ self._helper = None
+ self._http_server = None
+ self._webkit_base_dir = None
+ self._websocket_server = None
+ self._http_lock = None
+
+ # 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 = True
+
+ self._pretty_patch_path = self.path_from_webkit_base("Websites",
+ "bugs.webkit.org", "PrettyPatch", "prettify.rb")
+ self._pretty_patch_available = True
+ self.set_option_default('configuration', None)
+ if self._options.configuration is None:
+ self._options.configuration = self.default_configuration()
+
+ def default_child_processes(self):
+ """Return the number of DumpRenderTree instances to use for this
+ port."""
+ return self._executive.cpu_count()
+
+ def baseline_path(self):
+ """Return the absolute path to the directory to store new baselines
+ in for this port."""
+ raise NotImplementedError('Port.baseline_path')
+
+ def baseline_search_path(self):
+ """Return a list of absolute paths to directories to search under for
+ baselines. The directories are searched in order."""
+ raise NotImplementedError('Port.baseline_search_path')
+
+ 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."""
+ raise NotImplementedError('Port.check_build')
+
+ 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."""
+ return True
+
+ def check_image_diff(self, override_step=None, logging=True):
+ """This routine is used to check whether image_diff binary exists."""
+ raise NotImplementedError('Port.check_image_diff')
+
+ def check_pretty_patch(self):
+ """Checks whether we can use the PrettyPatch ruby script."""
+
+ # check if Ruby is installed
+ try:
+ result = self._executive.run_command(['ruby', '--version'])
+ except OSError, e:
+ if e.errno in [errno.ENOENT, errno.EACCES, errno.ECHILD]:
+ _log.error("Ruby is not installed; "
+ "can't generate pretty patches.")
+ _log.error('')
+ return False
+
+ if not self.path_exists(self._pretty_patch_path):
+ _log.error('Unable to find %s .' % self._pretty_patch_path)
+ _log.error("Can't generate pretty patches.")
+ _log.error('')
+ return False
+
+ return True
+
+ def compare_text(self, expected_text, actual_text):
+ """Return whether or not the two strings are *not* equal. This
+ routine is used to diff text output.
+
+ While this is a generic routine, we include it in the Port
+ interface so that it can be overriden for testing purposes."""
+ return expected_text != actual_text
+
+ def diff_image(self, expected_contents, actual_contents,
+ diff_filename=None, tolerance=0):
+ """Compare two images and produce a delta image file.
+
+ Return True if the two images are different, False if they are the same.
+ Also produce a delta image of the two images and write that into
+ |diff_filename| if it is not None.
+
+ |tolerance| should be a percentage value (0.0 - 100.0).
+ If it is omitted, the port default tolerance value is used.
+
+ """
+ raise NotImplementedError('Port.diff_image')
+
+
+ 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.
+
+ While this is a generic routine, we include it in the Port
+ interface so that it can be overriden for testing purposes."""
+
+ # 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(str):
+ if isinstance(str, unicode):
+ return str.encode('utf-8')
+ return str
+ 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 driver_name(self):
+ """Returns the name of the actual binary that is performing the test,
+ so that it can be referred to in log messages. In most cases this
+ will be DumpRenderTree, but if a port uses a binary with a different
+ name, it can be overridden here."""
+ return "DumpRenderTree"
+
+ def expected_baselines(self, filename, suffix, all_baselines=False):
+ """Given a test name, finds where the baseline results are located.
+
+ Args:
+ filename: absolute filename to test file
+ 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
+ (os.path.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.
+ """
+ testname = os.path.splitext(self.relative_test_filename(filename))[0]
+
+ baseline_filename = testname + '-expected' + suffix
+
+ baseline_search_path = self.baseline_search_path()
+
+ baselines = []
+ for platform_dir in baseline_search_path:
+ if self.path_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.path_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, filename, suffix):
+ """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:
+ filename: absolute filename to test file
+ 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., 'chromium-win', or
+ 'chromium-mac-leopard' (we follow the WebKit format)
+
+ This routine is generic but is implemented here to live alongside
+ the other baseline and filename manipulation routines.
+ """
+ platform_dir, baseline_filename = self.expected_baselines(
+ filename, suffix)[0]
+ if platform_dir:
+ return self._filesystem.join(platform_dir, baseline_filename)
+ return self._filesystem.join(self.layout_tests_dir(), baseline_filename)
+
+ def expected_checksum(self, test):
+ """Returns the checksum of the image we expect the test to produce, or None if it is a text-only test."""
+ path = self.expected_filename(test, '.checksum')
+ if not self.path_exists(path):
+ return None
+ return self._filesystem.read_text_file(path)
+
+ def expected_image(self, test):
+ """Returns the image we expect the test to produce."""
+ path = self.expected_filename(test, '.png')
+ if not self.path_exists(path):
+ return None
+ return self._filesystem.read_binary_file(path)
+
+ def expected_text(self, test):
+ """Returns the text output we expect the test to produce.
+ 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.
+ path = self.expected_filename(test, '.txt')
+ if not self.path_exists(path):
+ return ''
+ text = self._filesystem.read_binary_file(path)
+ return text.replace("\r\n", "\n")
+
+ def filename_to_uri(self, filename):
+ """Convert a test file (which is an absolute path) to a URI."""
+ LAYOUTTEST_HTTP_DIR = "http/tests/"
+ LAYOUTTEST_WEBSOCKET_DIR = "http/tests/websocket/tests/"
+
+ relative_path = self.relative_test_filename(filename)
+ port = None
+ use_ssl = False
+
+ if (relative_path.startswith(LAYOUTTEST_WEBSOCKET_DIR)
+ or relative_path.startswith(LAYOUTTEST_HTTP_DIR)):
+ relative_path = relative_path[len(LAYOUTTEST_HTTP_DIR):]
+ port = 8000
+
+ # Make http/tests/local run as local files. This is to mimic the
+ # logic in run-webkit-tests.
+ #
+ # TODO(dpranke): remove the media reference and the SSL reference?
+ if (port and not relative_path.startswith("local/") and
+ not relative_path.startswith("media/")):
+ if relative_path.startswith("ssl/"):
+ port += 443
+ protocol = "https"
+ else:
+ protocol = "http"
+ return "%s://127.0.0.1:%u/%s" % (protocol, port, relative_path)
+
+ return path.abspath_to_uri(os.path.abspath(filename))
+
+ def tests(self, paths):
+ """Return the list of tests found (relative to layout_tests_dir()."""
+ return test_files.find(self, paths)
+
+ def test_dirs(self):
+ """Returns the list of top-level test directories.
+
+ Used by --clobber-old-results."""
+ 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))
+
+ def path_isdir(self, path):
+ """Return True if the path refers to a directory of tests."""
+ # Used by test_expectations.py to apply rules to whole directories.
+ return self._filesystem.isdir(path)
+
+ def path_exists(self, path):
+ """Return True if the path 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._filesystem.exists(path)
+
+ def driver_cmd_line(self):
+ """Prints the DRT command line that will be used."""
+ driver = self.create_driver(0)
+ return driver.cmd_line()
+
+ def update_baseline(self, path, data, encoding):
+ """Updates the baseline for a test.
+
+ Args:
+ 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.
+ encoding: file encoding to use for the baseline.
+ """
+ # FIXME: remove the encoding parameter in favor of text/binary
+ # functions.
+ if encoding is None:
+ self._filesystem.write_binary_file(path, data)
+ else:
+ self._filesystem.write_text_file(path, data)
+
+ def uri_to_test_name(self, uri):
+ """Return the base layout test name for a given URI.
+
+ This returns the test name for a given URI, e.g., if you passed in
+ "file:///src/LayoutTests/fast/html/keygen.html" it would return
+ "fast/html/keygen.html".
+
+ """
+ test = uri
+ if uri.startswith("file:///"):
+ prefix = path.abspath_to_uri(self.layout_tests_dir()) + "/"
+ return test[len(prefix):]
+
+ if uri.startswith("http://127.0.0.1:8880/"):
+ # websocket tests
+ return test.replace('http://127.0.0.1:8880/', '')
+
+ if uri.startswith("http://"):
+ # regular HTTP test
+ return test.replace('http://127.0.0.1:8000/', 'http/tests/')
+
+ if uri.startswith("https://"):
+ return test.replace('https://127.0.0.1:8443/', 'http/tests/')
+
+ raise NotImplementedError('unknown url type: %s' % uri)
+
+ def layout_tests_dir(self):
+ """Return the absolute path to the top of the LayoutTests directory."""
+ return self.path_from_webkit_base('LayoutTests')
+
+ def skips_layout_test(self, test_name):
+ """Figures out if the givent test is being skipped or not.
+
+ Test categories are handled as well."""
+ for test_or_category in self.skipped_layout_tests():
+ if test_or_category == test_name:
+ return True
+ category = self._filesystem.join(self.layout_tests_dir(),
+ test_or_category)
+ if (self._filesystem.isdir(category) and
+ test_name.startswith(test_or_category)):
+ return True
+ return False
+
+ def maybe_make_directory(self, *path):
+ """Creates the specified directory if it doesn't already exist."""
+ self._filesystem.maybe_make_directory(*path)
+
+ def name(self):
+ """Return the name of the port (e.g., 'mac', 'chromium-win-xp').
+
+ Note that this is different from the test_platform_name(), which
+ may be different (e.g., 'win-xp' instead of 'chromium-win-xp'."""
+ return self._name
+
+ def get_option(self, name, default_value=None):
+ # FIXME: Eventually we should not have to do a test for
+ # hasattr(), and we should be able to just do
+ # self.options.value. See additional FIXME in the constructor.
+ if hasattr(self._options, name):
+ return getattr(self._options, name)
+ return default_value
+
+ def set_option_default(self, name, default_value):
+ if not hasattr(self._options, name):
+ return setattr(self._options, name, default_value)
+
+ def path_from_webkit_base(self, *comps):
+ """Returns the full path to path made by joining the top of the
+ WebKit source tree and the list of path components in |*comps|."""
+ return self._config.path_from_webkit_base(*comps)
+
+ def script_path(self, script_name):
+ return self._config.script_path(script_name)
+
+ 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."""
+ raise NotImplementedError('Port.path_to_test_expectations_file')
+
+ def relative_test_filename(self, filename):
+ """Relative unix-style path for a filename under the LayoutTests
+ directory. Filenames outside the LayoutTests directory should raise
+ an error."""
+ assert filename.startswith(self.layout_tests_dir()), "%s did not start with %s" % (filename, self.layout_tests_dir())
+ return filename[len(self.layout_tests_dir()) + 1:]
+
+ def results_directory(self):
+ """Absolute path to the place to store the test results."""
+ raise NotImplementedError('Port.results_directory')
+
+ def setup_test_run(self):
+ """Perform port-specific work at the beginning of a test run."""
+ pass
+
+ def setup_environ_for_server(self):
+ """Perform port-specific work at the beginning of a server launch.
+
+ Returns:
+ Operating-system's environment.
+ """
+ return os.environ.copy()
+
+ 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._user.open_url(results_filename)
+
+ def create_driver(self, worker_number):
+ """Return a newly created base.Driver subclass for starting/stopping
+ the test driver."""
+ raise NotImplementedError('Port.create_driver')
+
+ 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):
+ """Start a web server if it is available. Do nothing if
+ it isn't. This routine is allowed to (and may) fail if a server
+ is already running."""
+ if self.get_option('use_apache'):
+ self._http_server = apache_http_server.LayoutTestApacheHttpd(self,
+ self.get_option('results_directory'))
+ else:
+ self._http_server = http_server.Lighttpd(self,
+ self.get_option('results_directory'))
+ self._http_server.start()
+
+ def start_websocket_server(self):
+ """Start a websocket server if it is available. Do nothing if
+ it isn't. This routine is allowed to (and may) fail if a server
+ is already running."""
+ self._websocket_server = websocket_server.PyWebSocket(self,
+ self.get_option('results_directory'))
+ self._websocket_server.start()
+
+ def acquire_http_lock(self):
+ self._http_lock = http_lock.HttpLock(None)
+ 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, or it isn't available."""
+ if self._http_server:
+ self._http_server.stop()
+
+ def stop_websocket_server(self):
+ """Shut down the websocket server if it is running. Do nothing if
+ it isn't, or it isn't available."""
+ if self._websocket_server:
+ self._websocket_server.stop()
+
+ def release_http_lock(self):
+ if self._http_lock:
+ self._http_lock.cleanup_http_lock()
+
+ def test_expectations(self):
+ """Returns the test expectations for this port.
+
+ Basically this string should contain the equivalent of a
+ test_expectations file. See test_expectations.py for more details."""
+ raise NotImplementedError('Port.test_expectations')
+
+ def test_expectations_overrides(self):
+ """Returns an optional set of overrides for the test_expectations.
+
+ This is used by ports that have code in two repositories, and where
+ it is possible that you might need "downstream" expectations that
+ temporarily override the "upstream" expectations until the port can
+ sync up the two repos."""
+ return None
+
+ def test_base_platform_names(self):
+ """Return a list of the 'base' platforms on your port. The base
+ platforms represent different architectures, operating systems,
+ or implementations (as opposed to different versions of a single
+ platform). For example, 'mac' and 'win' might be different base
+ platforms, wherease 'mac-tiger' and 'mac-leopard' might be
+ different platforms. This routine is used by the rebaselining tool
+ and the dashboards, and the strings correspond to the identifiers
+ in your test expectations (*not* necessarily the platform names
+ themselves)."""
+ raise NotImplementedError('Port.base_test_platforms')
+
+ def test_platform_name(self):
+ """Returns the string that corresponds to the given platform name
+ in the test expectations. This may be the same as name(), or it
+ may be different. For example, chromium returns 'mac' for
+ 'chromium-mac'."""
+ raise NotImplementedError('Port.test_platform_name')
+
+ def test_platforms(self):
+ """Returns the list of test platform identifiers as used in the
+ test_expectations and on dashboards, the rebaselining tool, etc.
+
+ Note that this is not necessarily the same as the list of ports,
+ which must be globally unique (e.g., both 'chromium-mac' and 'mac'
+ might return 'mac' as a test_platform name'."""
+ raise NotImplementedError('Port.platforms')
+
+ def test_platform_name_to_name(self, test_platform_name):
+ """Returns the Port platform name that corresponds to the name as
+ referenced in the expectations file. E.g., "mac" returns
+ "chromium-mac" on the Chromium ports."""
+ raise NotImplementedError('Port.test_platform_name_to_name')
+
+ 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."""
+ raise NotImplementedError('Port.version')
+
+ def test_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', <webkitRepossitoryPath>) tuple.
+ """
+ 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:
+ raise script_error
+
+ 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 not self._pretty_patch_available:
+ return self._pretty_patch_error_html
+ command = ("ruby", "-I", os.path.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 _webkit_build_directory(self, args):
+ return self._config.build_directory(args[0])
+
+ 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."""
+ raise NotImplementedError('Port.path_to_apache')
+
+ def _path_to_apache_config_file(self):
+ """Returns the full path to the apache binary.
+
+ This is needed only by ports that use the apache_http_server module."""
+ raise NotImplementedError('Port.path_to_apache_config_file')
+
+ def _path_to_driver(self, configuration=None):
+ """Returns the full path to the test driver (DumpRenderTree)."""
+ raise NotImplementedError('Port._path_to_driver')
+
+ def _path_to_webcore_library(self):
+ """Returns the full path to a built copy of WebCore."""
+ raise NotImplementedError('Port.path_to_webcore_library')
+
+ 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()."""
+ raise NotImplementedError('Port._path_to_helper')
+
+ 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()"""
+ raise NotImplementedError('Port.path_to_image_diff')
+
+ 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')
+
+ 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()"""
+ raise NotImplementedError('Port._path_to_wdiff')
+
+ def _shut_down_http_server(self, pid):
+ """Forcefully and synchronously kills the web server.
+
+ This routine should only be called from http_server.py or its
+ subclasses."""
+ raise NotImplementedError('Port._shut_down_http_server')
+
+ 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)
+
+
+class Driver:
+ """Abstract interface for the DumpRenderTree interface."""
+
+ def __init__(self, port, worker_number):
+ """Initialize a Driver to subsequently run tests.
+
+ Typically this routine will spawn DumpRenderTree in a config
+ ready for subsequent input.
+
+ port - reference back to the port object.
+ worker_number - identifier for a particular worker/driver instance
+ """
+ raise NotImplementedError('Driver.__init__')
+
+ def run_test(self, test_input):
+ """Run a single test and return the results.
+
+ Note that it is okay if a test times out or crashes and leaves
+ the driver in an indeterminate state. The upper layers of the program
+ are responsible for cleaning up and ensuring things are okay.
+
+ Args:
+ test_input: a TestInput object
+
+ Returns a TestOutput object.
+ """
+ raise NotImplementedError('Driver.run_test')
+
+ # FIXME: This is static so we can test it w/o creating a Base instance.
+ @classmethod
+ def _command_wrapper(cls, wrapper_option):
+ # Hook for injecting valgrind or other runtime instrumentation,
+ # used by e.g. tools/valgrind/valgrind_tests.py.
+ wrapper = []
+ browser_wrapper = os.environ.get("BROWSER_WRAPPER", None)
+ if browser_wrapper:
+ # FIXME: There seems to be no reason to use BROWSER_WRAPPER over --wrapper.
+ # Remove this code any time after the date listed below.
+ _log.error("BROWSER_WRAPPER is deprecated, please use --wrapper instead.")
+ _log.error("BROWSER_WRAPPER will be removed any time after June 1st 2010 and your scripts will break.")
+ wrapper += [browser_wrapper]
+
+ if wrapper_option:
+ wrapper += shlex.split(wrapper_option)
+ return wrapper
+
+ def poll(self):
+ """Returns None if the Driver is still running. Returns the returncode
+ if it has exited."""
+ raise NotImplementedError('Driver.poll')
+
+ def stop(self):
+ raise NotImplementedError('Driver.stop')
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py
new file mode 100644
index 0000000..8d586e3
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py
@@ -0,0 +1,315 @@
+# 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 name of Google Inc. 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.
+
+import optparse
+import os
+import sys
+import tempfile
+import unittest
+
+from webkitpy.common.system.executive import Executive, ScriptError
+from webkitpy.common.system import executive_mock
+from webkitpy.common.system import filesystem
+from webkitpy.common.system import outputcapture
+from webkitpy.common.system.path import abspath_to_uri
+from webkitpy.thirdparty.mock import Mock
+from webkitpy.tool import mocktool
+
+import base
+import config
+import config_mock
+
+
+class PortTest(unittest.TestCase):
+ def test_format_wdiff_output_as_html(self):
+ output = "OUTPUT %s %s %s" % (base.Port._WDIFF_DEL, base.Port._WDIFF_ADD, base.Port._WDIFF_END)
+ html = base.Port()._format_wdiff_output_as_html(output)
+ expected_html = "<head><style>.del { background: #faa; } .add { background: #afa; }</style></head><pre>OUTPUT <span class=del> <span class=add> </span></pre>"
+ self.assertEqual(html, expected_html)
+
+ def test_wdiff_command(self):
+ port = base.Port()
+ port._path_to_wdiff = lambda: "/path/to/wdiff"
+ command = port._wdiff_command("/actual/path", "/expected/path")
+ expected_command = [
+ "/path/to/wdiff",
+ "--start-delete=##WDIFF_DEL##",
+ "--end-delete=##WDIFF_END##",
+ "--start-insert=##WDIFF_ADD##",
+ "--end-insert=##WDIFF_END##",
+ "/actual/path",
+ "/expected/path",
+ ]
+ self.assertEqual(command, expected_command)
+
+ def _file_with_contents(self, contents, encoding="utf-8"):
+ new_file = tempfile.NamedTemporaryFile()
+ new_file.write(contents.encode(encoding))
+ new_file.flush()
+ return new_file
+
+ def test_pretty_patch_os_error(self):
+ port = base.Port(executive=executive_mock.MockExecutive2(exception=OSError))
+ oc = outputcapture.OutputCapture()
+ oc.capture_output()
+ self.assertEqual(port.pretty_patch_text("patch.txt"),
+ port._pretty_patch_error_html)
+
+ # This tests repeated calls to make sure we cache the result.
+ self.assertEqual(port.pretty_patch_text("patch.txt"),
+ port._pretty_patch_error_html)
+ oc.restore_output()
+
+ def test_pretty_patch_script_error(self):
+ # FIXME: This is some ugly white-box test hacking ...
+ base._pretty_patch_available = True
+ port = base.Port(executive=executive_mock.MockExecutive2(exception=ScriptError))
+ self.assertEqual(port.pretty_patch_text("patch.txt"),
+ port._pretty_patch_error_html)
+
+ # This tests repeated calls to make sure we cache the result.
+ self.assertEqual(port.pretty_patch_text("patch.txt"),
+ port._pretty_patch_error_html)
+
+ def test_run_wdiff(self):
+ executive = Executive()
+ # This may fail on some systems. We could ask the port
+ # object for the wdiff path, but since we don't know what
+ # port object to use, this is sufficient for now.
+ try:
+ wdiff_path = executive.run_command(["which", "wdiff"]).rstrip()
+ except Exception, e:
+ wdiff_path = None
+
+ port = base.Port()
+ port._path_to_wdiff = lambda: wdiff_path
+
+ if wdiff_path:
+ # "with tempfile.NamedTemporaryFile() as actual" does not seem to work in Python 2.5
+ actual = self._file_with_contents(u"foo")
+ expected = self._file_with_contents(u"bar")
+ wdiff = port._run_wdiff(actual.name, expected.name)
+ expected_wdiff = "<head><style>.del { background: #faa; } .add { background: #afa; }</style></head><pre><span class=del>foo</span><span class=add>bar</span></pre>"
+ self.assertEqual(wdiff, expected_wdiff)
+ # Running the full wdiff_text method should give the same result.
+ port._wdiff_available = True # In case it's somehow already disabled.
+ wdiff = port.wdiff_text(actual.name, expected.name)
+ self.assertEqual(wdiff, expected_wdiff)
+ # wdiff should still be available after running wdiff_text with a valid diff.
+ self.assertTrue(port._wdiff_available)
+ actual.close()
+ expected.close()
+
+ # Bogus paths should raise a script error.
+ self.assertRaises(ScriptError, port._run_wdiff, "/does/not/exist", "/does/not/exist2")
+ self.assertRaises(ScriptError, port.wdiff_text, "/does/not/exist", "/does/not/exist2")
+ # wdiff will still be available after running wdiff_text with invalid paths.
+ self.assertTrue(port._wdiff_available)
+ base._wdiff_available = True
+
+ # If wdiff does not exist _run_wdiff should throw an OSError.
+ port._path_to_wdiff = lambda: "/invalid/path/to/wdiff"
+ self.assertRaises(OSError, port._run_wdiff, "foo", "bar")
+
+ # wdiff_text should not throw an error if wdiff does not exist.
+ self.assertEqual(port.wdiff_text("foo", "bar"), "")
+ # However wdiff should not be available after running wdiff_text if wdiff is missing.
+ self.assertFalse(port._wdiff_available)
+
+ def test_diff_text(self):
+ port = base.Port()
+ # Make sure that we don't run into decoding exceptions when the
+ # filenames are unicode, with regular or malformed input (expected or
+ # actual input is always raw bytes, not unicode).
+ port.diff_text('exp', 'act', 'exp.txt', 'act.txt')
+ port.diff_text('exp', 'act', u'exp.txt', 'act.txt')
+ port.diff_text('exp', 'act', u'a\xac\u1234\u20ac\U00008000', 'act.txt')
+
+ port.diff_text('exp' + chr(255), 'act', 'exp.txt', 'act.txt')
+ port.diff_text('exp' + chr(255), 'act', u'exp.txt', 'act.txt')
+
+ # Though expected and actual files should always be read in with no
+ # encoding (and be stored as str objects), test unicode inputs just to
+ # be safe.
+ port.diff_text(u'exp', 'act', 'exp.txt', 'act.txt')
+ port.diff_text(
+ u'a\xac\u1234\u20ac\U00008000', 'act', 'exp.txt', 'act.txt')
+
+ # And make sure we actually get diff output.
+ diff = port.diff_text('foo', 'bar', 'exp.txt', 'act.txt')
+ self.assertTrue('foo' in diff)
+ self.assertTrue('bar' in diff)
+ self.assertTrue('exp.txt' in diff)
+ self.assertTrue('act.txt' in diff)
+ self.assertFalse('nosuchthing' in diff)
+
+ def test_default_configuration_notfound(self):
+ # Test that we delegate to the config object properly.
+ port = base.Port(config=config_mock.MockConfig(default_configuration='default'))
+ self.assertEqual(port.default_configuration(), 'default')
+
+ def test_layout_tests_skipping(self):
+ port = base.Port()
+ port.skipped_layout_tests = lambda: ['foo/bar.html', 'media']
+ self.assertTrue(port.skips_layout_test('foo/bar.html'))
+ self.assertTrue(port.skips_layout_test('media/video-zoom.html'))
+ self.assertFalse(port.skips_layout_test('foo/foo.html'))
+
+ def test_setup_test_run(self):
+ port = base.Port()
+ # This routine is a no-op. We just test it for coverage.
+ port.setup_test_run()
+
+ def test_test_dirs(self):
+ port = base.Port()
+ dirs = port.test_dirs()
+ self.assertTrue('canvas' in dirs)
+ self.assertTrue('css2.1' in dirs)
+
+ def test_filename_to_uri(self):
+ port = base.Port()
+ layout_test_dir = port.layout_tests_dir()
+ test_file = os.path.join(layout_test_dir, "foo", "bar.html")
+
+ # On Windows, absolute paths are of the form "c:\foo.txt". However,
+ # all current browsers (except for Opera) normalize file URLs by
+ # prepending an additional "/" as if the absolute path was
+ # "/c:/foo.txt". This means that all file URLs end up with "file:///"
+ # at the beginning.
+ if sys.platform == 'win32':
+ prefix = "file:///"
+ path = test_file.replace("\\", "/")
+ else:
+ prefix = "file://"
+ path = test_file
+
+ self.assertEqual(port.filename_to_uri(test_file),
+ abspath_to_uri(test_file))
+
+ def test_get_option__set(self):
+ options, args = optparse.OptionParser().parse_args([])
+ options.foo = 'bar'
+ port = base.Port(options=options)
+ self.assertEqual(port.get_option('foo'), 'bar')
+
+ def test_get_option__unset(self):
+ port = base.Port()
+ self.assertEqual(port.get_option('foo'), None)
+
+ def test_get_option__default(self):
+ port = base.Port()
+ self.assertEqual(port.get_option('foo', 'bar'), 'bar')
+
+ def test_set_option_default__unset(self):
+ port = base.Port()
+ port.set_option_default('foo', 'bar')
+ self.assertEqual(port.get_option('foo'), 'bar')
+
+ def test_set_option_default__set(self):
+ options, args = optparse.OptionParser().parse_args([])
+ options.foo = 'bar'
+ port = base.Port(options=options)
+ # This call should have no effect.
+ port.set_option_default('foo', 'new_bar')
+ self.assertEqual(port.get_option('foo'), 'bar')
+
+ def test_name__unset(self):
+ port = base.Port()
+ self.assertEqual(port.name(), None)
+
+ def test_name__set(self):
+ port = base.Port(port_name='foo')
+ self.assertEqual(port.name(), 'foo')
+
+
+class VirtualTest(unittest.TestCase):
+ """Tests that various methods expected to be virtual are."""
+ def assertVirtual(self, method, *args, **kwargs):
+ self.assertRaises(NotImplementedError, method, *args, **kwargs)
+
+ def test_virtual_methods(self):
+ port = base.Port()
+ self.assertVirtual(port.baseline_path)
+ self.assertVirtual(port.baseline_search_path)
+ self.assertVirtual(port.check_build, None)
+ self.assertVirtual(port.check_image_diff)
+ self.assertVirtual(port.create_driver, 0)
+ self.assertVirtual(port.diff_image, None, None)
+ self.assertVirtual(port.path_to_test_expectations_file)
+ self.assertVirtual(port.test_platform_name)
+ self.assertVirtual(port.results_directory)
+ self.assertVirtual(port.test_expectations)
+ self.assertVirtual(port.test_base_platform_names)
+ self.assertVirtual(port.test_platform_name)
+ self.assertVirtual(port.test_platforms)
+ self.assertVirtual(port.test_platform_name_to_name, None)
+ self.assertVirtual(port.version)
+ self.assertVirtual(port._path_to_apache)
+ self.assertVirtual(port._path_to_apache_config_file)
+ self.assertVirtual(port._path_to_driver)
+ self.assertVirtual(port._path_to_helper)
+ self.assertVirtual(port._path_to_image_diff)
+ self.assertVirtual(port._path_to_lighttpd)
+ self.assertVirtual(port._path_to_lighttpd_modules)
+ self.assertVirtual(port._path_to_lighttpd_php)
+ self.assertVirtual(port._path_to_wdiff)
+ self.assertVirtual(port._shut_down_http_server, None)
+
+ def test_virtual_driver_method(self):
+ self.assertRaises(NotImplementedError, base.Driver, base.Port(),
+ 0)
+
+ def test_virtual_driver_methods(self):
+ class VirtualDriver(base.Driver):
+ def __init__(self):
+ pass
+
+ driver = VirtualDriver()
+ self.assertVirtual(driver.run_test, None)
+ self.assertVirtual(driver.poll)
+ self.assertVirtual(driver.stop)
+
+
+class DriverTest(unittest.TestCase):
+
+ def _assert_wrapper(self, wrapper_string, expected_wrapper):
+ wrapper = base.Driver._command_wrapper(wrapper_string)
+ self.assertEqual(wrapper, expected_wrapper)
+
+ def test_command_wrapper(self):
+ self._assert_wrapper(None, [])
+ self._assert_wrapper("valgrind", ["valgrind"])
+
+ # Validate that shlex works as expected.
+ command_with_spaces = "valgrind --smc-check=\"check with spaces!\" --foo"
+ expected_parse = ["valgrind", "--smc-check=check with spaces!", "--foo"]
+ self._assert_wrapper(command_with_spaces, expected_parse)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium.py
new file mode 100644
index 0000000..012e9cc
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium.py
@@ -0,0 +1,546 @@
+#!/usr/bin/env python
+# 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 name of Google Inc. 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.
+
+"""Chromium implementations of the Port interface."""
+
+from __future__ import with_statement
+
+import codecs
+import errno
+import logging
+import os
+import re
+import shutil
+import signal
+import subprocess
+import sys
+import tempfile
+import time
+import webbrowser
+
+from webkitpy.common.system.path import cygpath
+from webkitpy.layout_tests.layout_package import test_expectations
+from webkitpy.layout_tests.layout_package import test_output
+
+import base
+import http_server
+
+# Chromium DRT on OSX uses WebKitDriver.
+if sys.platform == 'darwin':
+ import webkit
+
+import websocket_server
+
+_log = logging.getLogger("webkitpy.layout_tests.port.chromium")
+
+
+# FIXME: This function doesn't belong in this package.
+def check_file_exists(path_to_file, file_description, override_step=None,
+ logging=True):
+ """Verify the file is present where expected or log an error.
+
+ Args:
+ file_name: The (human friendly) name or description of the file
+ you're looking for (e.g., "HTTP Server"). Used for error logging.
+ override_step: An optional string to be logged if the check fails.
+ logging: Whether or not log the error messages."""
+ if not os.path.exists(path_to_file):
+ if logging:
+ _log.error('Unable to find %s' % file_description)
+ _log.error(' at %s' % path_to_file)
+ if override_step:
+ _log.error(' %s' % override_step)
+ _log.error('')
+ return False
+ return True
+
+
+class ChromiumPort(base.Port):
+ """Abstract base class for Chromium implementations of the Port class."""
+
+ def __init__(self, **kwargs):
+ base.Port.__init__(self, **kwargs)
+ self._chromium_base_dir = None
+
+ def baseline_path(self):
+ return self._webkit_baseline_path(self._name)
+
+ def check_build(self, needs_http):
+ result = True
+
+ dump_render_tree_binary_path = self._path_to_driver()
+ result = check_file_exists(dump_render_tree_binary_path,
+ 'test driver') and result
+ if result and self.get_option('build'):
+ result = self._check_driver_build_up_to_date(
+ self.get_option('configuration'))
+ else:
+ _log.error('')
+
+ helper_path = self._path_to_helper()
+ if helper_path:
+ result = check_file_exists(helper_path,
+ 'layout test helper') and result
+
+ if self.get_option('pixel_tests'):
+ result = self.check_image_diff(
+ 'To override, invoke with --no-pixel-tests') and result
+
+ # It's okay if pretty patch isn't available, but we will at
+ # least log a message.
+ self.check_pretty_patch()
+
+ return result
+
+ def check_sys_deps(self, needs_http):
+ cmd = [self._path_to_driver(), '--check-layout-test-sys-deps']
+ if self._executive.run_command(cmd, return_exit_code=True):
+ _log.error('System dependencies check failed.')
+ _log.error('To override, invoke with --nocheck-sys-deps')
+ _log.error('')
+ return False
+ return True
+
+ def check_image_diff(self, override_step=None, logging=True):
+ image_diff_path = self._path_to_image_diff()
+ return check_file_exists(image_diff_path, 'image diff exe',
+ override_step, logging)
+
+ def diff_image(self, expected_contents, actual_contents,
+ diff_filename=None):
+ executable = self._path_to_image_diff()
+
+ tempdir = tempfile.mkdtemp()
+ expected_filename = os.path.join(tempdir, "expected.png")
+ with open(expected_filename, 'w+b') as file:
+ file.write(expected_contents)
+ actual_filename = os.path.join(tempdir, "actual.png")
+ with open(actual_filename, 'w+b') as file:
+ file.write(actual_contents)
+
+ if diff_filename:
+ cmd = [executable, '--diff', expected_filename,
+ actual_filename, diff_filename]
+ else:
+ cmd = [executable, expected_filename, actual_filename]
+
+ result = True
+ try:
+ exit_code = self._executive.run_command(cmd, return_exit_code=True)
+ if exit_code == 0:
+ # The images are the same.
+ result = False
+ elif exit_code != 1:
+ _log.error("image diff returned an exit code of "
+ + str(exit_code))
+ # Returning False here causes the script to think that we
+ # successfully created the diff even though we didn't. If
+ # we return True, we think that the images match but the hashes
+ # don't match.
+ # FIXME: Figure out why image_diff returns other values.
+ result = False
+ except OSError, e:
+ if e.errno == errno.ENOENT or e.errno == errno.EACCES:
+ _compare_available = False
+ else:
+ raise e
+ finally:
+ shutil.rmtree(tempdir, ignore_errors=True)
+ return result
+
+ def driver_name(self):
+ if self._options.use_test_shell:
+ return "test_shell"
+ return "DumpRenderTree"
+
+ def path_from_chromium_base(self, *comps):
+ """Returns the full path to path made by joining the top of the
+ Chromium source tree and the list of path components in |*comps|."""
+ if not self._chromium_base_dir:
+ abspath = os.path.abspath(__file__)
+ offset = abspath.find('third_party')
+ if offset == -1:
+ self._chromium_base_dir = os.path.join(
+ abspath[0:abspath.find('Tools')],
+ 'WebKit', 'chromium')
+ else:
+ self._chromium_base_dir = abspath[0:offset]
+ return os.path.join(self._chromium_base_dir, *comps)
+
+ def path_to_test_expectations_file(self):
+ return self.path_from_webkit_base('LayoutTests', 'platform',
+ 'chromium', 'test_expectations.txt')
+
+ def results_directory(self):
+ try:
+ return self.path_from_chromium_base('webkit',
+ self.get_option('configuration'),
+ self.get_option('results_directory'))
+ except AssertionError:
+ return self._build_path(self.get_option('configuration'),
+ self.get_option('results_directory'))
+
+ def setup_test_run(self):
+ # Delete the disk cache if any to ensure a clean test run.
+ dump_render_tree_binary_path = self._path_to_driver()
+ cachedir = os.path.split(dump_render_tree_binary_path)[0]
+ cachedir = os.path.join(cachedir, "cache")
+ if os.path.exists(cachedir):
+ shutil.rmtree(cachedir)
+
+ def create_driver(self, worker_number):
+ """Starts a new Driver and returns a handle to it."""
+ if not self.get_option('use_test_shell') and sys.platform == 'darwin':
+ return webkit.WebKitDriver(self, worker_number)
+ return ChromiumDriver(self, worker_number)
+
+ def start_helper(self):
+ helper_path = self._path_to_helper()
+ if helper_path:
+ _log.debug("Starting layout helper %s" % helper_path)
+ # Note: Not thread safe: http://bugs.python.org/issue2320
+ self._helper = subprocess.Popen([helper_path],
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=None)
+ is_ready = self._helper.stdout.readline()
+ if not is_ready.startswith('ready'):
+ _log.error("layout_test_helper failed to be ready")
+
+ def stop_helper(self):
+ if self._helper:
+ _log.debug("Stopping layout test helper")
+ self._helper.stdin.write("x\n")
+ self._helper.stdin.close()
+ # wait() is not threadsafe and can throw OSError due to:
+ # http://bugs.python.org/issue1731717
+ self._helper.wait()
+
+ def test_base_platform_names(self):
+ return ('linux', 'mac', 'win')
+
+ def test_expectations(self):
+ """Returns the test expectations for this port.
+
+ Basically this string should contain the equivalent of a
+ test_expectations file. See test_expectations.py for more details."""
+ expectations_path = self.path_to_test_expectations_file()
+ with codecs.open(expectations_path, "r", "utf-8") as file:
+ return file.read()
+
+ def test_expectations_overrides(self):
+ try:
+ overrides_path = self.path_from_chromium_base('webkit', 'tools',
+ 'layout_tests', 'test_expectations.txt')
+ except AssertionError:
+ return None
+ if not os.path.exists(overrides_path):
+ return None
+ with codecs.open(overrides_path, "r", "utf-8") as file:
+ return file.read()
+
+ def skipped_layout_tests(self, extra_test_files=None):
+ expectations_str = self.test_expectations()
+ overrides_str = self.test_expectations_overrides()
+ test_platform_name = self.test_platform_name()
+ is_debug_mode = False
+
+ all_test_files = self.tests([])
+ if extra_test_files:
+ all_test_files.update(extra_test_files)
+
+ expectations = test_expectations.TestExpectations(
+ self, all_test_files, expectations_str, test_platform_name,
+ is_debug_mode, is_lint_mode=False, overrides=overrides_str)
+ tests_dir = self.layout_tests_dir()
+ return [self.relative_test_filename(test)
+ for test in expectations.get_tests_with_result_type(test_expectations.SKIP)]
+
+ def test_platform_names(self):
+ return self.test_base_platform_names() + ('win-xp',
+ 'win-vista', 'win-7')
+
+ def test_platform_name_to_name(self, test_platform_name):
+ if test_platform_name in self.test_platform_names():
+ return 'chromium-' + test_platform_name
+ raise ValueError('Unsupported test_platform_name: %s' %
+ test_platform_name)
+
+ def test_repository_paths(self):
+ # Note: for JSON file's backward-compatibility we use 'chrome' rather
+ # than 'chromium' here.
+ repos = super(ChromiumPort, self).test_repository_paths()
+ repos.append(('chrome', self.path_from_chromium_base()))
+ return repos
+
+ #
+ # PROTECTED METHODS
+ #
+ # These routines should only be called by other methods in this file
+ # or any subclasses.
+ #
+
+ def _check_driver_build_up_to_date(self, configuration):
+ if configuration in ('Debug', 'Release'):
+ try:
+ debug_path = self._path_to_driver('Debug')
+ release_path = self._path_to_driver('Release')
+
+ debug_mtime = os.stat(debug_path).st_mtime
+ release_mtime = os.stat(release_path).st_mtime
+
+ if (debug_mtime > release_mtime and configuration == 'Release' or
+ release_mtime > debug_mtime and configuration == 'Debug'):
+ _log.warning('You are not running the most '
+ 'recent DumpRenderTree binary. You need to '
+ 'pass --debug or not to select between '
+ 'Debug and Release.')
+ _log.warning('')
+ # This will fail if we don't have both a debug and release binary.
+ # That's fine because, in this case, we must already be running the
+ # most up-to-date one.
+ except OSError:
+ pass
+ return True
+
+ def _chromium_baseline_path(self, platform):
+ if platform is None:
+ platform = self.name()
+ return self.path_from_webkit_base('LayoutTests', 'platform', platform)
+
+ def _convert_path(self, path):
+ """Handles filename conversion for subprocess command line args."""
+ # See note above in diff_image() for why we need this.
+ if sys.platform == 'cygwin':
+ return cygpath(path)
+ return path
+
+ def _path_to_image_diff(self):
+ binary_name = 'ImageDiff'
+ if self.get_option('use_test_shell'):
+ binary_name = 'image_diff'
+ return self._build_path(self.get_option('configuration'), binary_name)
+
+
+class ChromiumDriver(base.Driver):
+ """Abstract interface for test_shell."""
+
+ def __init__(self, port, worker_number):
+ self._port = port
+ self._worker_number = worker_number
+ self._image_path = None
+ if self._port.get_option('pixel_tests'):
+ self._image_path = os.path.join(
+ self._port.get_option('results_directory'),
+ 'png_result%s.png' % self._worker_number)
+
+ def cmd_line(self):
+ cmd = self._command_wrapper(self._port.get_option('wrapper'))
+ cmd.append(self._port._path_to_driver())
+ if self._port.get_option('pixel_tests'):
+ # See note above in diff_image() for why we need _convert_path().
+ cmd.append("--pixel-tests=" +
+ self._port._convert_path(self._image_path))
+
+ if self._port.get_option('use_test_shell'):
+ cmd.append('--layout-tests')
+ else:
+ cmd.append('--test-shell')
+
+ if self._port.get_option('startup_dialog'):
+ cmd.append('--testshell-startup-dialog')
+
+ if self._port.get_option('gp_fault_error_box'):
+ cmd.append('--gp-fault-error-box')
+
+ if self._port.get_option('js_flags') is not None:
+ cmd.append('--js-flags="' + self._port.get_option('js_flags') + '"')
+
+ if self._port.get_option('multiple_loads') > 0:
+ cmd.append('--multiple-loads=' + str(self._port.get_option('multiple_loads')))
+
+ # test_shell does not support accelerated compositing.
+ if not self._port.get_option("use_test_shell"):
+ if self._port.get_option('accelerated_compositing'):
+ cmd.append('--enable-accelerated-compositing')
+ if self._port.get_option('accelerated_2d_canvas'):
+ cmd.append('--enable-accelerated-2d-canvas')
+ return cmd
+
+ def start(self):
+ # FIXME: Should be an error to call this method twice.
+ cmd = self.cmd_line()
+
+ # We need to pass close_fds=True to work around Python bug #2320
+ # (otherwise we can hang when we kill DumpRenderTree when we are running
+ # multiple threads). See http://bugs.python.org/issue2320 .
+ # Note that close_fds isn't supported on Windows, but this bug only
+ # shows up on Mac and Linux.
+ close_flag = sys.platform not in ('win32', 'cygwin')
+ self._proc = subprocess.Popen(cmd, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ close_fds=close_flag)
+
+ def poll(self):
+ # poll() is not threadsafe and can throw OSError due to:
+ # http://bugs.python.org/issue1731717
+ return self._proc.poll()
+
+ def _write_command_and_read_line(self, input=None):
+ """Returns a tuple: (line, did_crash)"""
+ try:
+ if input:
+ if isinstance(input, unicode):
+ # TestShell expects utf-8
+ input = input.encode("utf-8")
+ self._proc.stdin.write(input)
+ # DumpRenderTree text output is always UTF-8. However some tests
+ # (e.g. webarchive) may spit out binary data instead of text so we
+ # don't bother to decode the output (for either DRT or test_shell).
+ line = self._proc.stdout.readline()
+ # We could assert() here that line correctly decodes as UTF-8.
+ return (line, False)
+ except IOError, e:
+ _log.error("IOError communicating w/ test_shell: " + str(e))
+ return (None, True)
+
+ def _test_shell_command(self, uri, timeoutms, checksum):
+ cmd = uri
+ if timeoutms:
+ cmd += ' ' + str(timeoutms)
+ if checksum:
+ cmd += ' ' + checksum
+ cmd += "\n"
+ return cmd
+
+ def _output_image(self):
+ """Returns the image output which driver generated."""
+ png_path = self._image_path
+ if png_path and os.path.isfile(png_path):
+ with open(png_path, 'rb') as image_file:
+ return image_file.read()
+ else:
+ return None
+
+ def _output_image_with_retry(self):
+ # Retry a few more times because open() sometimes fails on Windows,
+ # raising "IOError: [Errno 13] Permission denied:"
+ retry_num = 50
+ timeout_seconds = 5.0
+ for i in range(retry_num):
+ try:
+ return self._output_image()
+ except IOError, e:
+ if e.errno == errno.EACCES:
+ time.sleep(timeout_seconds / retry_num)
+ else:
+ raise e
+ return self._output_image()
+
+ def run_test(self, test_input):
+ output = []
+ error = []
+ crash = False
+ timeout = False
+ actual_uri = None
+ actual_checksum = None
+
+ start_time = time.time()
+
+ uri = self._port.filename_to_uri(test_input.filename)
+ cmd = self._test_shell_command(uri, test_input.timeout,
+ test_input.image_hash)
+ (line, crash) = self._write_command_and_read_line(input=cmd)
+
+ while not crash and line.rstrip() != "#EOF":
+ # Make sure we haven't crashed.
+ if line == '' and self.poll() is not None:
+ # This is hex code 0xc000001d, which is used for abrupt
+ # termination. This happens if we hit ctrl+c from the prompt
+ # and we happen to be waiting on test_shell.
+ # sdoyon: Not sure for which OS and in what circumstances the
+ # above code is valid. What works for me under Linux to detect
+ # ctrl+c is for the subprocess returncode to be negative
+ # SIGINT. And that agrees with the subprocess documentation.
+ if (-1073741510 == self._proc.returncode or
+ - signal.SIGINT == self._proc.returncode):
+ raise KeyboardInterrupt
+ crash = True
+ break
+
+ # Don't include #URL lines in our output
+ if line.startswith("#URL:"):
+ actual_uri = line.rstrip()[5:]
+ if uri != actual_uri:
+ # GURL capitalizes the drive letter of a file URL.
+ if (not re.search("^file:///[a-z]:", uri) or
+ uri.lower() != actual_uri.lower()):
+ _log.fatal("Test got out of sync:\n|%s|\n|%s|" %
+ (uri, actual_uri))
+ raise AssertionError("test out of sync")
+ elif line.startswith("#MD5:"):
+ actual_checksum = line.rstrip()[5:]
+ elif line.startswith("#TEST_TIMED_OUT"):
+ timeout = True
+ # Test timed out, but we still need to read until #EOF.
+ elif actual_uri:
+ output.append(line)
+ else:
+ error.append(line)
+
+ (line, crash) = self._write_command_and_read_line(input=None)
+
+ run_time = time.time() - start_time
+ return test_output.TestOutput(
+ ''.join(output), self._output_image_with_retry(), actual_checksum,
+ crash, run_time, timeout, ''.join(error))
+
+ def stop(self):
+ if self._proc:
+ self._proc.stdin.close()
+ self._proc.stdout.close()
+ if self._proc.stderr:
+ self._proc.stderr.close()
+ if sys.platform not in ('win32', 'cygwin'):
+ # Closing stdin/stdout/stderr hangs sometimes on OS X,
+ # (see __init__(), above), and anyway we don't want to hang
+ # the harness if test_shell is buggy, so we wait a couple
+ # seconds to give test_shell a chance to clean up, but then
+ # force-kill the process if necessary.
+ KILL_TIMEOUT = 3.0
+ timeout = time.time() + KILL_TIMEOUT
+ # poll() is not threadsafe and can throw OSError due to:
+ # http://bugs.python.org/issue1731717
+ while self._proc.poll() is None and time.time() < timeout:
+ time.sleep(0.1)
+ # poll() is not threadsafe and can throw OSError due to:
+ # http://bugs.python.org/issue1731717
+ if self._proc.poll() is None:
+ _log.warning('stopping test driver timed out, '
+ 'killing it')
+ self._port._executive.kill_process(self._proc.pid)
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py
new file mode 100644
index 0000000..c1f5c8d
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py
@@ -0,0 +1,152 @@
+#!/usr/bin/env python
+# 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.
+
+# 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.
+
+from __future__ import with_statement
+
+import codecs
+import os
+import sys
+
+import chromium_linux
+import chromium_mac
+import chromium_win
+
+
+def get(**kwargs):
+ """Some tests have slightly different results when run while using
+ hardware acceleration. In those cases, we prepend an additional directory
+ to the baseline paths."""
+ port_name = kwargs.get('port_name', None)
+ if port_name == 'chromium-gpu':
+ if sys.platform in ('cygwin', 'win32'):
+ port_name = 'chromium-gpu-win'
+ elif sys.platform == 'linux2':
+ port_name = 'chromium-gpu-linux'
+ elif sys.platform == 'darwin':
+ port_name = 'chromium-gpu-mac'
+ else:
+ raise NotImplementedError('unsupported platform: %s' %
+ sys.platform)
+
+ if port_name == 'chromium-gpu-linux':
+ return ChromiumGpuLinuxPort(**kwargs)
+
+ if port_name.startswith('chromium-gpu-mac'):
+ return ChromiumGpuMacPort(**kwargs)
+
+ if port_name.startswith('chromium-gpu-win'):
+ return ChromiumGpuWinPort(**kwargs)
+
+ raise NotImplementedError('unsupported port: %s' % port_name)
+
+
+def _set_gpu_options(options):
+ if options:
+ if options.accelerated_compositing is None:
+ options.accelerated_compositing = True
+ if options.accelerated_2d_canvas is None:
+ options.accelerated_2d_canvas = True
+
+ # FIXME: Remove this after http://codereview.chromium.org/5133001/ is enabled
+ # on the bots.
+ if options.builder_name is not None and not ' - GPU' in options.builder_name:
+ options.builder_name = options.builder_name + ' - GPU'
+
+
+def _gpu_overrides(port):
+ try:
+ overrides_path = port.path_from_chromium_base('webkit', 'tools',
+ 'layout_tests', 'test_expectations_gpu.txt')
+ except AssertionError:
+ return None
+ if not os.path.exists(overrides_path):
+ return None
+ with codecs.open(overrides_path, "r", "utf-8") as file:
+ return file.read()
+
+
+class ChromiumGpuLinuxPort(chromium_linux.ChromiumLinuxPort):
+ def __init__(self, **kwargs):
+ kwargs.setdefault('port_name', 'chromium-gpu-linux')
+ _set_gpu_options(kwargs.get('options'))
+ chromium_linux.ChromiumLinuxPort.__init__(self, **kwargs)
+
+ def baseline_search_path(self):
+ # Mimic the Linux -> Win expectations fallback in the ordinary Chromium port.
+ return (map(self._webkit_baseline_path, ['chromium-gpu-linux', 'chromium-gpu-win', 'chromium-gpu']) +
+ chromium_linux.ChromiumLinuxPort.baseline_search_path(self))
+
+ def default_child_processes(self):
+ return 1
+
+ def path_to_test_expectations_file(self):
+ return self.path_from_webkit_base('LayoutTests', 'platform',
+ 'chromium-gpu', 'test_expectations.txt')
+
+ def test_expectations_overrides(self):
+ return _gpu_overrides(self)
+
+
+class ChromiumGpuMacPort(chromium_mac.ChromiumMacPort):
+ def __init__(self, **kwargs):
+ kwargs.setdefault('port_name', 'chromium-gpu-mac')
+ _set_gpu_options(kwargs.get('options'))
+ chromium_mac.ChromiumMacPort.__init__(self, **kwargs)
+
+ def baseline_search_path(self):
+ return (map(self._webkit_baseline_path, ['chromium-gpu-mac', 'chromium-gpu']) +
+ chromium_mac.ChromiumMacPort.baseline_search_path(self))
+
+ def default_child_processes(self):
+ return 1
+
+ def path_to_test_expectations_file(self):
+ return self.path_from_webkit_base('LayoutTests', 'platform',
+ 'chromium-gpu', 'test_expectations.txt')
+
+ def test_expectations_overrides(self):
+ return _gpu_overrides(self)
+
+
+class ChromiumGpuWinPort(chromium_win.ChromiumWinPort):
+ def __init__(self, **kwargs):
+ kwargs.setdefault('port_name', 'chromium-gpu-win' + self.version())
+ _set_gpu_options(kwargs.get('options'))
+ chromium_win.ChromiumWinPort.__init__(self, **kwargs)
+
+ def baseline_search_path(self):
+ return (map(self._webkit_baseline_path, ['chromium-gpu-win', 'chromium-gpu']) +
+ chromium_win.ChromiumWinPort.baseline_search_path(self))
+
+ def default_child_processes(self):
+ return 1
+
+ def path_to_test_expectations_file(self):
+ return self.path_from_webkit_base('LayoutTests', 'platform',
+ 'chromium-gpu', 'test_expectations.txt')
+
+ def test_expectations_overrides(self):
+ return _gpu_overrides(self)
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py
new file mode 100644
index 0000000..ad0404c
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+# 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.
+
+# 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.
+
+import os
+import unittest
+
+from webkitpy.tool import mocktool
+import chromium_gpu
+
+
+class ChromiumGpuTest(unittest.TestCase):
+ def test_get_chromium_gpu_linux(self):
+ self.assertOverridesWorked('chromium-gpu-linux')
+
+ def test_get_chromium_gpu_mac(self):
+ self.assertOverridesWorked('chromium-gpu-mac')
+
+ def test_get_chromium_gpu_win(self):
+ self.assertOverridesWorked('chromium-gpu-win')
+
+ def assertOverridesWorked(self, port_name):
+ # test that we got the right port
+ mock_options = mocktool.MockOptions(accelerated_compositing=None,
+ accelerated_2d_canvas=None,
+ builder_name='foo',
+ child_processes=None)
+ port = chromium_gpu.get(port_name=port_name, options=mock_options)
+ self.assertTrue(port._options.accelerated_compositing)
+ self.assertTrue(port._options.accelerated_2d_canvas)
+ self.assertEqual(port.default_child_processes(), 1)
+ self.assertEqual(port._options.builder_name, 'foo - GPU')
+
+ # we use startswith() instead of Equal to gloss over platform versions.
+ self.assertTrue(port.name().startswith(port_name))
+
+ # test that it has the right directories in front of the search path.
+ paths = port.baseline_search_path()
+ self.assertEqual(port._webkit_baseline_path(port_name), paths[0])
+ if port_name == 'chromium-gpu-linux':
+ self.assertEqual(port._webkit_baseline_path('chromium-gpu-win'), paths[1])
+ self.assertEqual(port._webkit_baseline_path('chromium-gpu'), paths[2])
+ else:
+ self.assertEqual(port._webkit_baseline_path('chromium-gpu'), paths[1])
+
+ # Test that we have the right expectations file.
+ self.assertTrue('chromium-gpu' in
+ port.path_to_test_expectations_file())
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py
new file mode 100644
index 0000000..5d9dd87
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_linux.py
@@ -0,0 +1,190 @@
+#!/usr/bin/env python
+# 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 name of Google Inc. 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.
+
+"""Chromium Linux implementation of the Port interface."""
+
+import logging
+import os
+import signal
+
+import chromium
+
+_log = logging.getLogger("webkitpy.layout_tests.port.chromium_linux")
+
+
+class ChromiumLinuxPort(chromium.ChromiumPort):
+ """Chromium Linux implementation of the Port class."""
+
+ def __init__(self, **kwargs):
+ kwargs.setdefault('port_name', 'chromium-linux')
+ chromium.ChromiumPort.__init__(self, **kwargs)
+
+ def baseline_search_path(self):
+ port_names = ["chromium-linux", "chromium-win", "chromium", "win", "mac"]
+ return map(self._webkit_baseline_path, port_names)
+
+ def check_build(self, needs_http):
+ result = chromium.ChromiumPort.check_build(self, needs_http)
+ if needs_http:
+ if self.get_option('use_apache'):
+ result = self._check_apache_install() and result
+ else:
+ result = self._check_lighttpd_install() and result
+ result = self._check_wdiff_install() and result
+
+ if not result:
+ _log.error('For complete Linux build requirements, please see:')
+ _log.error('')
+ _log.error(' http://code.google.com/p/chromium/wiki/'
+ 'LinuxBuildInstructions')
+ return result
+
+ def test_platform_name(self):
+ # We use 'linux' instead of 'chromium-linux' in test_expectations.txt.
+ return 'linux'
+
+ def version(self):
+ # We don't have different versions on linux.
+ return ''
+
+ #
+ # PROTECTED METHODS
+ #
+
+ def _build_path(self, *comps):
+ base = self.path_from_chromium_base()
+ if os.path.exists(os.path.join(base, 'sconsbuild')):
+ return os.path.join(base, 'sconsbuild', *comps)
+ if os.path.exists(os.path.join(base, 'out', *comps)) or self.get_option('use_test_shell'):
+ return os.path.join(base, 'out', *comps)
+ base = self.path_from_webkit_base()
+ if os.path.exists(os.path.join(base, 'sconsbuild')):
+ return os.path.join(base, 'sconsbuild', *comps)
+ return os.path.join(base, 'out', *comps)
+
+ def _check_apache_install(self):
+ result = chromium.check_file_exists(self._path_to_apache(),
+ "apache2")
+ result = chromium.check_file_exists(self._path_to_apache_config_file(),
+ "apache2 config file") and result
+ if not result:
+ _log.error(' Please install using: "sudo apt-get install '
+ 'apache2 libapache2-mod-php5"')
+ _log.error('')
+ return result
+
+ def _check_lighttpd_install(self):
+ result = chromium.check_file_exists(
+ self._path_to_lighttpd(), "LigHTTPd executable")
+ result = chromium.check_file_exists(self._path_to_lighttpd_php(),
+ "PHP CGI executable") and result
+ result = chromium.check_file_exists(self._path_to_lighttpd_modules(),
+ "LigHTTPd modules") and result
+ if not result:
+ _log.error(' Please install using: "sudo apt-get install '
+ 'lighttpd php5-cgi"')
+ _log.error('')
+ return result
+
+ def _check_wdiff_install(self):
+ result = chromium.check_file_exists(self._path_to_wdiff(), 'wdiff')
+ if not result:
+ _log.error(' Please install using: "sudo apt-get install '
+ 'wdiff"')
+ _log.error('')
+ # FIXME: The ChromiumMac port always returns True.
+ return result
+
+ def _path_to_apache(self):
+ if self._is_redhat_based():
+ return '/usr/sbin/httpd'
+ else:
+ return '/usr/sbin/apache2'
+
+ def _path_to_apache_config_file(self):
+ if self._is_redhat_based():
+ config_name = 'fedora-httpd.conf'
+ else:
+ config_name = 'apache2-debian-httpd.conf'
+
+ return os.path.join(self.layout_tests_dir(), 'http', 'conf',
+ config_name)
+
+ def _path_to_lighttpd(self):
+ return "/usr/sbin/lighttpd"
+
+ def _path_to_lighttpd_modules(self):
+ return "/usr/lib/lighttpd"
+
+ def _path_to_lighttpd_php(self):
+ return "/usr/bin/php-cgi"
+
+ def _path_to_driver(self, configuration=None):
+ if not configuration:
+ configuration = self.get_option('configuration')
+ binary_name = 'DumpRenderTree'
+ if self.get_option('use_test_shell'):
+ binary_name = 'test_shell'
+ return self._build_path(configuration, binary_name)
+
+ def _path_to_helper(self):
+ return None
+
+ def _path_to_wdiff(self):
+ if self._is_redhat_based():
+ return '/usr/bin/dwdiff'
+ else:
+ return '/usr/bin/wdiff'
+
+ def _is_redhat_based(self):
+ return os.path.exists(os.path.join('/etc', 'redhat-release'))
+
+ def _shut_down_http_server(self, server_pid):
+ """Shut down the lighttpd web server. Blocks until it's fully
+ shut down.
+
+ Args:
+ server_pid: The process ID of the running server.
+ """
+ # server_pid is not set when "http_server.py stop" is run manually.
+ if server_pid is None:
+ # TODO(mmoss) This isn't ideal, since it could conflict with
+ # lighttpd processes not started by http_server.py,
+ # but good enough for now.
+ self._executive.kill_all("lighttpd")
+ self._executive.kill_all("apache2")
+ else:
+ try:
+ os.kill(server_pid, signal.SIGTERM)
+ # TODO(mmoss) Maybe throw in a SIGKILL just to be sure?
+ except OSError:
+ # Sometimes we get a bad PID (e.g. from a stale httpd.pid
+ # file), so if kill fails on the given PID, just try to
+ # 'killall' web servers.
+ self._shut_down_http_server(None)
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py
new file mode 100644
index 0000000..f638e01
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac.py
@@ -0,0 +1,182 @@
+#!/usr/bin/env python
+# 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 name of Google Inc. 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.
+
+"""Chromium Mac implementation of the Port interface."""
+
+import logging
+import os
+import platform
+import signal
+
+import chromium
+
+from webkitpy.common.system.executive import Executive
+
+_log = logging.getLogger("webkitpy.layout_tests.port.chromium_mac")
+
+
+class ChromiumMacPort(chromium.ChromiumPort):
+ """Chromium Mac implementation of the Port class."""
+
+ def __init__(self, **kwargs):
+ kwargs.setdefault('port_name', 'chromium-mac')
+ chromium.ChromiumPort.__init__(self, **kwargs)
+
+ def baseline_search_path(self):
+ port_names = [
+ "chromium-mac" + self.version(),
+ "chromium-mac",
+ "chromium",
+ "mac" + self.version(),
+ "mac",
+ ]
+ return map(self._webkit_baseline_path, port_names)
+
+ def check_build(self, needs_http):
+ result = chromium.ChromiumPort.check_build(self, needs_http)
+ result = self._check_wdiff_install() and result
+ if not result:
+ _log.error('For complete Mac build requirements, please see:')
+ _log.error('')
+ _log.error(' http://code.google.com/p/chromium/wiki/'
+ 'MacBuildInstructions')
+ return result
+
+ def default_child_processes(self):
+ # FIXME: we need to run single-threaded for now. See
+ # https://bugs.webkit.org/show_bug.cgi?id=38553. Unfortunately this
+ # routine is called right before the logger is configured, so if we
+ # try to _log.warning(), it gets thrown away.
+ import sys
+ sys.stderr.write("Defaulting to one child - see https://bugs.webkit.org/show_bug.cgi?id=38553\n")
+ return 1
+
+ def driver_name(self):
+ """name for this port's equivalent of DumpRenderTree."""
+ if self.get_option('use_test_shell'):
+ return "TestShell"
+ return "DumpRenderTree"
+
+ def test_platform_name(self):
+ # We use 'mac' instead of 'chromium-mac'
+ return 'mac'
+
+ def version(self):
+ # FIXME: It's strange that this string is -version, not just version.
+ os_version_string = platform.mac_ver()[0] # e.g. "10.5.6"
+ if not os_version_string:
+ return '-leopard'
+ release_version = int(os_version_string.split('.')[1])
+ # we don't support 'tiger' or earlier releases
+ if release_version == 5:
+ return '-leopard'
+ elif release_version == 6:
+ return '-snowleopard'
+ return ''
+
+ #
+ # PROTECTED METHODS
+ #
+
+ def _build_path(self, *comps):
+ path = self.path_from_chromium_base('xcodebuild', *comps)
+ if os.path.exists(path) or self.get_option('use_test_shell'):
+ return path
+ return self.path_from_webkit_base('WebKit', 'chromium', 'xcodebuild',
+ *comps)
+
+ def _check_wdiff_install(self):
+ try:
+ # We're ignoring the return and always returning True
+ self._executive.run_command([self._path_to_wdiff()], error_handler=Executive.ignore_error)
+ except OSError:
+ _log.warning('wdiff not found. Install using MacPorts or some '
+ 'other means')
+ return True
+
+ def _lighttpd_path(self, *comps):
+ return self.path_from_chromium_base('third_party', 'lighttpd',
+ 'mac', *comps)
+
+ def _path_to_apache(self):
+ return '/usr/sbin/httpd'
+
+ def _path_to_apache_config_file(self):
+ return os.path.join(self.layout_tests_dir(), 'http', 'conf',
+ 'apache2-httpd.conf')
+
+ def _path_to_lighttpd(self):
+ return self._lighttpd_path('bin', 'lighttpd')
+
+ def _path_to_lighttpd_modules(self):
+ return self._lighttpd_path('lib')
+
+ def _path_to_lighttpd_php(self):
+ return self._lighttpd_path('bin', 'php-cgi')
+
+ def _path_to_driver(self, configuration=None):
+ # FIXME: make |configuration| happy with case-sensitive file
+ # systems.
+ if not configuration:
+ configuration = self.get_option('configuration')
+ return self._build_path(configuration, self.driver_name() + '.app',
+ 'Contents', 'MacOS', self.driver_name())
+
+ def _path_to_helper(self):
+ binary_name = 'LayoutTestHelper'
+ if self.get_option('use_test_shell'):
+ binary_name = 'layout_test_helper'
+ return self._build_path(self.get_option('configuration'), binary_name)
+
+ def _path_to_wdiff(self):
+ return 'wdiff'
+
+ def _shut_down_http_server(self, server_pid):
+ """Shut down the lighttpd web server. Blocks until it's fully
+ shut down.
+
+ Args:
+ server_pid: The process ID of the running server.
+ """
+ # server_pid is not set when "http_server.py stop" is run manually.
+ if server_pid is None:
+ # TODO(mmoss) This isn't ideal, since it could conflict with
+ # lighttpd processes not started by http_server.py,
+ # but good enough for now.
+ self._executive.kill_all('lighttpd')
+ self._executive.kill_all('httpd')
+ else:
+ try:
+ os.kill(server_pid, signal.SIGTERM)
+ # TODO(mmoss) Maybe throw in a SIGKILL just to be sure?
+ except OSError:
+ # Sometimes we get a bad PID (e.g. from a stale httpd.pid
+ # file), so if kill fails on the given PID, just try to
+ # 'killall' web servers.
+ self._shut_down_http_server(None)
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac_unittest.py
new file mode 100644
index 0000000..d63faa0
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_mac_unittest.py
@@ -0,0 +1,40 @@
+# 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 name of Google Inc. 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.
+
+import chromium_mac
+import unittest
+
+from webkitpy.thirdparty.mock import Mock
+
+
+class ChromiumMacPortTest(unittest.TestCase):
+
+ def test_check_wdiff_install(self):
+ port = chromium_mac.ChromiumMacPort()
+ # Currently is always true, just logs if missing.
+ self.assertTrue(port._check_wdiff_install())
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py
new file mode 100644
index 0000000..c87984f
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py
@@ -0,0 +1,193 @@
+# 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 name of Google Inc. 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.
+
+import os
+import unittest
+import StringIO
+
+from webkitpy.tool import mocktool
+from webkitpy.thirdparty.mock import Mock
+
+import chromium
+import chromium_linux
+import chromium_mac
+import chromium_win
+
+class ChromiumDriverTest(unittest.TestCase):
+
+ def setUp(self):
+ mock_port = Mock()
+ mock_port.get_option = lambda option_name: ''
+ self.driver = chromium.ChromiumDriver(mock_port, worker_number=0)
+
+ def test_test_shell_command(self):
+ expected_command = "test.html 2 checksum\n"
+ self.assertEqual(self.driver._test_shell_command("test.html", 2, "checksum"), expected_command)
+
+ def _assert_write_command_and_read_line(self, input=None, expected_line=None, expected_stdin=None, expected_crash=False):
+ if not expected_stdin:
+ if input:
+ expected_stdin = input
+ else:
+ # We reset stdin, so we should expect stdin.getValue = ""
+ expected_stdin = ""
+ self.driver._proc.stdin = StringIO.StringIO()
+ line, did_crash = self.driver._write_command_and_read_line(input)
+ self.assertEqual(self.driver._proc.stdin.getvalue(), expected_stdin)
+ self.assertEqual(line, expected_line)
+ self.assertEqual(did_crash, expected_crash)
+
+ def test_write_command_and_read_line(self):
+ self.driver._proc = Mock()
+ # Set up to read 3 lines before we get an IOError
+ self.driver._proc.stdout = StringIO.StringIO("first\nsecond\nthird\n")
+
+ unicode_input = u"I \u2661 Unicode"
+ utf8_input = unicode_input.encode("utf-8")
+ # Test unicode input conversion to utf-8
+ self._assert_write_command_and_read_line(input=unicode_input, expected_stdin=utf8_input, expected_line="first\n")
+ # Test str() input.
+ self._assert_write_command_and_read_line(input="foo", expected_line="second\n")
+ # Test input=None
+ self._assert_write_command_and_read_line(expected_line="third\n")
+ # Test reading from a closed/empty stream.
+ # reading from a StringIO does not raise IOError like a real file would, so raise IOError manually.
+ def mock_readline():
+ raise IOError
+ self.driver._proc.stdout.readline = mock_readline
+ self._assert_write_command_and_read_line(expected_crash=True)
+
+
+class ChromiumPortTest(unittest.TestCase):
+ class TestMacPort(chromium_mac.ChromiumMacPort):
+ def __init__(self, options):
+ chromium_mac.ChromiumMacPort.__init__(self,
+ port_name='test-port',
+ options=options)
+
+ def default_configuration(self):
+ self.default_configuration_called = True
+ return 'default'
+
+ class TestLinuxPort(chromium_linux.ChromiumLinuxPort):
+ def __init__(self, options):
+ chromium_linux.ChromiumLinuxPort.__init__(self,
+ port_name='test-port',
+ options=options)
+
+ def default_configuration(self):
+ self.default_configuration_called = True
+ return 'default'
+
+ def test_path_to_image_diff(self):
+ mock_options = mocktool.MockOptions()
+ port = ChromiumPortTest.TestLinuxPort(options=mock_options)
+ self.assertTrue(port._path_to_image_diff().endswith(
+ '/out/default/ImageDiff'), msg=port._path_to_image_diff())
+ port = ChromiumPortTest.TestMacPort(options=mock_options)
+ self.assertTrue(port._path_to_image_diff().endswith(
+ '/xcodebuild/default/ImageDiff'))
+ mock_options = mocktool.MockOptions(use_test_shell=True)
+ port = ChromiumPortTest.TestLinuxPort(options=mock_options)
+ self.assertTrue(port._path_to_image_diff().endswith(
+ '/out/default/image_diff'), msg=port._path_to_image_diff())
+ port = ChromiumPortTest.TestMacPort(options=mock_options)
+ self.assertTrue(port._path_to_image_diff().endswith(
+ '/xcodebuild/default/image_diff'))
+ # FIXME: Figure out how this is going to work on Windows.
+ #port = chromium_win.ChromiumWinPort('test-port', options=MockOptions())
+
+ def test_skipped_layout_tests(self):
+ mock_options = mocktool.MockOptions()
+ port = ChromiumPortTest.TestLinuxPort(options=mock_options)
+
+ fake_test = os.path.join(port.layout_tests_dir(), "fast/js/not-good.js")
+
+ port.test_expectations = lambda: """BUG_TEST SKIP : fast/js/not-good.js = TEXT
+LINUX WIN : fast/js/very-good.js = TIMEOUT PASS"""
+ port.test_expectations_overrides = lambda: ''
+ port.tests = lambda paths: set()
+ port.path_exists = lambda test: True
+
+ skipped_tests = port.skipped_layout_tests(extra_test_files=[fake_test, ])
+ self.assertTrue("fast/js/not-good.js" in skipped_tests)
+
+ def test_default_configuration(self):
+ mock_options = mocktool.MockOptions()
+ port = ChromiumPortTest.TestLinuxPort(options=mock_options)
+ self.assertEquals(mock_options.configuration, 'default')
+ self.assertTrue(port.default_configuration_called)
+
+ mock_options = mocktool.MockOptions(configuration=None)
+ port = ChromiumPortTest.TestLinuxPort(mock_options)
+ self.assertEquals(mock_options.configuration, 'default')
+ self.assertTrue(port.default_configuration_called)
+
+ def test_diff_image(self):
+ class TestPort(ChromiumPortTest.TestLinuxPort):
+ def _path_to_image_diff(self):
+ return "/path/to/image_diff"
+
+ class MockExecute:
+ def __init__(self, result):
+ self._result = result
+
+ def run_command(self,
+ args,
+ cwd=None,
+ input=None,
+ error_handler=None,
+ return_exit_code=False,
+ return_stderr=True,
+ decode_output=False):
+ if return_exit_code:
+ return self._result
+ return ''
+
+ mock_options = mocktool.MockOptions()
+ port = ChromiumPortTest.TestLinuxPort(mock_options)
+
+ # Images are different.
+ port._executive = MockExecute(0)
+ self.assertEquals(False, port.diff_image("EXPECTED", "ACTUAL"))
+
+ # Images are the same.
+ port._executive = MockExecute(1)
+ self.assertEquals(True, port.diff_image("EXPECTED", "ACTUAL"))
+
+ # There was some error running image_diff.
+ port._executive = MockExecute(2)
+ exception_raised = False
+ try:
+ port.diff_image("EXPECTED", "ACTUAL")
+ except ValueError, e:
+ exception_raised = True
+ self.assertFalse(exception_raised)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_win.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_win.py
new file mode 100644
index 0000000..d080f82
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_win.py
@@ -0,0 +1,173 @@
+#!/usr/bin/env python
+# 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 name of Google Inc. 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.
+
+"""Chromium Win implementation of the Port interface."""
+
+import logging
+import os
+import sys
+
+import chromium
+
+_log = logging.getLogger("webkitpy.layout_tests.port.chromium_win")
+
+
+class ChromiumWinPort(chromium.ChromiumPort):
+ """Chromium Win implementation of the Port class."""
+
+ def __init__(self, **kwargs):
+ kwargs.setdefault('port_name', 'chromium-win' + self.version())
+ chromium.ChromiumPort.__init__(self, **kwargs)
+
+ def setup_environ_for_server(self):
+ env = chromium.ChromiumPort.setup_environ_for_server(self)
+ # Put the cygwin directory first in the path to find cygwin1.dll.
+ env["PATH"] = "%s;%s" % (
+ self.path_from_chromium_base("third_party", "cygwin", "bin"),
+ env["PATH"])
+ # Configure the cygwin directory so that pywebsocket finds proper
+ # python executable to run cgi program.
+ env["CYGWIN_PATH"] = self.path_from_chromium_base(
+ "third_party", "cygwin", "bin")
+ if (sys.platform == "win32" and self.get_option('register_cygwin')):
+ setup_mount = self.path_from_chromium_base("third_party",
+ "cygwin",
+ "setup_mount.bat")
+ self._executive.run_command([setup_mount])
+ return env
+
+ def baseline_search_path(self):
+ port_names = []
+ if self._name.endswith('-win-xp'):
+ port_names.append("chromium-win-xp")
+ if self._name.endswith('-win-xp') or self._name.endswith('-win-vista'):
+ port_names.append("chromium-win-vista")
+ # FIXME: This may need to include mac-snowleopard like win.py.
+ port_names.extend(["chromium-win", "chromium", "win", "mac"])
+ return map(self._webkit_baseline_path, port_names)
+
+ def check_build(self, needs_http):
+ result = chromium.ChromiumPort.check_build(self, needs_http)
+ if not result:
+ _log.error('For complete Windows build requirements, please '
+ 'see:')
+ _log.error('')
+ _log.error(' http://dev.chromium.org/developers/how-tos/'
+ 'build-instructions-windows')
+ return result
+
+ def relative_test_filename(self, filename):
+ path = filename[len(self.layout_tests_dir()) + 1:]
+ return path.replace('\\', '/')
+
+ def test_platform_name(self):
+ # We return 'win-xp', not 'chromium-win-xp' here, for convenience.
+ return 'win' + self.version()
+
+ def version(self):
+ if not hasattr(sys, 'getwindowsversion'):
+ return ''
+ winver = sys.getwindowsversion()
+ if winver[0] == 6 and (winver[1] == 1):
+ return '-7'
+ if winver[0] == 6 and (winver[1] == 0):
+ return '-vista'
+ if winver[0] == 5 and (winver[1] == 1 or winver[1] == 2):
+ return '-xp'
+ return ''
+
+ #
+ # PROTECTED ROUTINES
+ #
+ def _build_path(self, *comps):
+ p = self.path_from_chromium_base('webkit', *comps)
+ if os.path.exists(p):
+ return p
+ p = self.path_from_chromium_base('chrome', *comps)
+ if os.path.exists(p) or self.get_option('use_test_shell'):
+ return p
+ return os.path.join(self.path_from_webkit_base(), 'WebKit', 'chromium',
+ *comps)
+
+ def _lighttpd_path(self, *comps):
+ return self.path_from_chromium_base('third_party', 'lighttpd', 'win',
+ *comps)
+
+ def _path_to_apache(self):
+ return self.path_from_chromium_base('third_party', 'cygwin', 'usr',
+ 'sbin', 'httpd')
+
+ def _path_to_apache_config_file(self):
+ return os.path.join(self.layout_tests_dir(), 'http', 'conf',
+ 'cygwin-httpd.conf')
+
+ def _path_to_lighttpd(self):
+ return self._lighttpd_path('LightTPD.exe')
+
+ def _path_to_lighttpd_modules(self):
+ return self._lighttpd_path('lib')
+
+ def _path_to_lighttpd_php(self):
+ return self._lighttpd_path('php5', 'php-cgi.exe')
+
+ def _path_to_driver(self, configuration=None):
+ if not configuration:
+ configuration = self.get_option('configuration')
+ binary_name = 'DumpRenderTree.exe'
+ if self.get_option('use_test_shell'):
+ binary_name = 'test_shell.exe'
+ return self._build_path(configuration, binary_name)
+
+ def _path_to_helper(self):
+ binary_name = 'LayoutTestHelper.exe'
+ if self.get_option('use_test_shell'):
+ binary_name = 'layout_test_helper.exe'
+ return self._build_path(self.get_option('configuration'), binary_name)
+
+ def _path_to_image_diff(self):
+ binary_name = 'ImageDiff.exe'
+ if self.get_option('use_test_shell'):
+ binary_name = 'image_diff.exe'
+ return self._build_path(self.get_option('configuration'), binary_name)
+
+ def _path_to_wdiff(self):
+ return self.path_from_chromium_base('third_party', 'cygwin', 'bin',
+ 'wdiff.exe')
+
+ def _shut_down_http_server(self, server_pid):
+ """Shut down the lighttpd web server. Blocks until it's fully
+ shut down.
+
+ Args:
+ server_pid: The process ID of the running server.
+ """
+ # FIXME: Why are we ignoring server_pid and calling
+ # _kill_all instead of Executive.kill_process(pid)?
+ self._executive.kill_all("LightTPD.exe")
+ self._executive.kill_all("httpd.exe")
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/chromium_win_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/chromium_win_unittest.py
new file mode 100644
index 0000000..36f3c6b
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/chromium_win_unittest.py
@@ -0,0 +1,74 @@
+# 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 name of Google Inc. 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.
+
+import os
+import sys
+import unittest
+import chromium_win
+from webkitpy.common.system import outputcapture
+from webkitpy.tool import mocktool
+
+
+class ChromiumWinTest(unittest.TestCase):
+
+ class RegisterCygwinOption(object):
+ def __init__(self):
+ self.register_cygwin = True
+
+ def setUp(self):
+ self.orig_platform = sys.platform
+
+ def tearDown(self):
+ sys.platform = self.orig_platform
+
+ def _mock_path_from_chromium_base(self, *comps):
+ return os.path.join("/chromium/src", *comps)
+
+ def test_setup_environ_for_server(self):
+ port = chromium_win.ChromiumWinPort()
+ port._executive = mocktool.MockExecutive(should_log=True)
+ port.path_from_chromium_base = self._mock_path_from_chromium_base
+ output = outputcapture.OutputCapture()
+ orig_environ = os.environ.copy()
+ env = output.assert_outputs(self, port.setup_environ_for_server)
+ self.assertEqual(orig_environ["PATH"], os.environ["PATH"])
+ self.assertNotEqual(env["PATH"], os.environ["PATH"])
+
+ def test_setup_environ_for_server_register_cygwin(self):
+ sys.platform = "win32"
+ port = chromium_win.ChromiumWinPort(
+ options=ChromiumWinTest.RegisterCygwinOption())
+ port._executive = mocktool.MockExecutive(should_log=True)
+ port.path_from_chromium_base = self._mock_path_from_chromium_base
+ setup_mount = self._mock_path_from_chromium_base("third_party",
+ "cygwin",
+ "setup_mount.bat")
+ expected_stderr = "MOCK run_command: %s\n" % [setup_mount]
+ output = outputcapture.OutputCapture()
+ output.assert_outputs(self, port.setup_environ_for_server,
+ expected_stderr=expected_stderr)
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/config.py b/Tools/Scripts/webkitpy/layout_tests/port/config.py
new file mode 100644
index 0000000..e08ed9d
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/config.py
@@ -0,0 +1,169 @@
+#!/usr/bin/env python
+# 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 name of Google Inc. 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.
+
+"""Wrapper objects for WebKit-specific utility routines."""
+
+# FIXME: This file needs to be unified with common/checkout/scm.py and
+# common/config/ports.py .
+
+import os
+
+from webkitpy.common.system import logutils
+from webkitpy.common.system import executive
+
+
+_log = logutils.get_logger(__file__)
+
+#
+# FIXME: This is used to record if we've already hit the filesystem to look
+# for a default configuration. We cache this to speed up the unit tests,
+# but this can be reset with clear_cached_configuration(). This should be
+# replaced with us consistently using MockConfigs() for tests that don't
+# hit the filesystem at all and provide a reliable value.
+#
+_have_determined_configuration = False
+_configuration = "Release"
+
+
+def clear_cached_configuration():
+ global _have_determined_configuration, _configuration
+ _have_determined_configuration = False
+ _configuration = "Release"
+
+
+class Config(object):
+ _FLAGS_FROM_CONFIGURATIONS = {
+ "Debug": "--debug",
+ "Release": "--release",
+ }
+
+ def __init__(self, executive, filesystem):
+ self._executive = executive
+ self._filesystem = filesystem
+ self._webkit_base_dir = None
+ self._default_configuration = None
+ self._build_directories = {}
+
+ def build_directory(self, configuration):
+ """Returns the path to the build directory for the configuration."""
+ if configuration:
+ flags = ["--configuration",
+ self._FLAGS_FROM_CONFIGURATIONS[configuration]]
+ else:
+ configuration = ""
+ flags = ["--top-level"]
+
+ if not self._build_directories.get(configuration):
+ args = ["perl", self._script_path("webkit-build-directory")] + flags
+ self._build_directories[configuration] = (
+ self._executive.run_command(args).rstrip())
+
+ return self._build_directories[configuration]
+
+ def build_dumprendertree(self, configuration):
+ """Builds DRT in the given configuration.
+
+ Returns True if the build was successful and up-to-date."""
+ flag = self._FLAGS_FROM_CONFIGURATIONS[configuration]
+ exit_code = self._executive.run_command([
+ self._script_path("build-dumprendertree"), flag],
+ return_exit_code=True)
+ if exit_code != 0:
+ _log.error("Failed to build DumpRenderTree")
+ return False
+ return True
+
+ def default_configuration(self):
+ """Returns the default configuration for the user.
+
+ Returns the value set by 'set-webkit-configuration', or "Release"
+ if that has not been set. This mirrors the logic in webkitdirs.pm."""
+ if not self._default_configuration:
+ self._default_configuration = self._determine_configuration()
+ if not self._default_configuration:
+ self._default_configuration = 'Release'
+ if self._default_configuration not in self._FLAGS_FROM_CONFIGURATIONS:
+ _log.warn("Configuration \"%s\" is not a recognized value.\n" %
+ self._default_configuration)
+ _log.warn("Scripts may fail. "
+ "See 'set-webkit-configuration --help'.")
+ return self._default_configuration
+
+ def path_from_webkit_base(self, *comps):
+ return self._filesystem.join(self.webkit_base_dir(), *comps)
+
+ def webkit_base_dir(self):
+ """Returns the absolute path to the top of the WebKit tree.
+
+ Raises an AssertionError if the top dir can't be determined."""
+ # Note: this code somewhat duplicates the code in
+ # scm.find_checkout_root(). However, that code only works if the top
+ # of the SCM repository also matches the top of the WebKit tree. The
+ # Chromium ports, for example, only check out subdirectories like
+ # Tools/Scripts, and so we still have to do additional work
+ # to find the top of the tree.
+ #
+ # This code will also work if there is no SCM system at all.
+ if not self._webkit_base_dir:
+ abspath = os.path.abspath(__file__)
+ self._webkit_base_dir = abspath[0:abspath.find('Tools') - 1]
+ return self._webkit_base_dir
+
+ def _script_path(self, script_name):
+ return self._filesystem.join(self.webkit_base_dir(), "Tools",
+ "Scripts", script_name)
+
+ def _determine_configuration(self):
+ # This mirrors the logic in webkitdirs.pm:determineConfiguration().
+ #
+ # FIXME: See the comment at the top of the file regarding unit tests
+ # and our use of global mutable static variables.
+ global _have_determined_configuration, _configuration
+ if not _have_determined_configuration:
+ contents = self._read_configuration()
+ if not contents:
+ contents = "Release"
+ if contents == "Deployment":
+ contents = "Release"
+ if contents == "Development":
+ contents = "Debug"
+ _configuration = contents
+ _have_determined_configuration = True
+ return _configuration
+
+ def _read_configuration(self):
+ try:
+ configuration_path = self._filesystem.join(self.build_directory(None),
+ "Configuration")
+ if not self._filesystem.exists(configuration_path):
+ return None
+ except (OSError, executive.ScriptError):
+ return None
+
+ return self._filesystem.read_text_file(configuration_path).rstrip()
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/config_mock.py b/Tools/Scripts/webkitpy/layout_tests/port/config_mock.py
new file mode 100644
index 0000000..af71fa3
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/config_mock.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+# 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 name of Google Inc. 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.
+
+"""Wrapper objects for WebKit-specific utility routines."""
+
+
+class MockConfig(object):
+ def __init__(self, default_configuration='Release'):
+ self._default_configuration = default_configuration
+
+ def build_directory(self, configuration):
+ return "/build"
+
+ def build_dumprendertree(self, configuration):
+ return True
+
+ def default_configuration(self):
+ return self._default_configuration
+
+ def path_from_webkit_base(self, *comps):
+ return "/" + "/".join(list(comps))
+
+ def webkit_base_dir(self):
+ return "/"
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/config_standalone.py b/Tools/Scripts/webkitpy/layout_tests/port/config_standalone.py
new file mode 100644
index 0000000..3dec3b9
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/config_standalone.py
@@ -0,0 +1,70 @@
+# 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 name of Google Inc. 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.
+
+"""FIXME: This script is used by
+config_unittest.test_default_configuration__standalone() to read the
+default configuration to work around any possible caching / reset bugs. See
+https://bugs.webkit.org/show_bug?id=49360 for the motivation. We can remove
+this test when we remove the global configuration cache in config.py."""
+
+import os
+import unittest
+import sys
+
+
+# Ensure that webkitpy is in PYTHONPATH.
+this_dir = os.path.abspath(sys.path[0])
+up = os.path.dirname
+script_dir = up(up(up(this_dir)))
+if script_dir not in sys.path:
+ sys.path.append(script_dir)
+
+from webkitpy.common.system import executive
+from webkitpy.common.system import executive_mock
+from webkitpy.common.system import filesystem
+from webkitpy.common.system import filesystem_mock
+
+import config
+
+
+def main(argv=None):
+ if not argv:
+ argv = sys.argv
+
+ if len(argv) == 3 and argv[1] == '--mock':
+ e = executive_mock.MockExecutive2(output='foo')
+ fs = filesystem_mock.MockFileSystem({'foo/Configuration': argv[2]})
+ else:
+ e = executive.Executive()
+ fs = filesystem.FileSystem()
+
+ c = config.Config(e, fs)
+ print c.default_configuration()
+
+if __name__ == '__main__':
+ main()
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/config_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/config_unittest.py
new file mode 100644
index 0000000..2cce3cc
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/config_unittest.py
@@ -0,0 +1,202 @@
+# 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 name of Google Inc. 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.
+
+import os
+import sys
+import unittest
+
+from webkitpy.common.system import executive
+from webkitpy.common.system import executive_mock
+from webkitpy.common.system import filesystem
+from webkitpy.common.system import filesystem_mock
+from webkitpy.common.system import outputcapture
+
+import config
+
+
+def mock_run_command(arg_list):
+ # Set this to True to test actual output (where possible).
+ integration_test = False
+ if integration_test:
+ return executive.Executive().run_command(arg_list)
+
+ if 'webkit-build-directory' in arg_list[1]:
+ return mock_webkit_build_directory(arg_list[2:])
+ return 'Error'
+
+
+def mock_webkit_build_directory(arg_list):
+ if arg_list == ['--top-level']:
+ return '/WebKitBuild'
+ elif arg_list == ['--configuration', '--debug']:
+ return '/WebKitBuild/Debug'
+ elif arg_list == ['--configuration', '--release']:
+ return '/WebKitBuild/Release'
+ return 'Error'
+
+
+class ConfigTest(unittest.TestCase):
+ def tearDown(self):
+ config.clear_cached_configuration()
+
+ def make_config(self, output='', files={}, exit_code=0, exception=None,
+ run_command_fn=None):
+ e = executive_mock.MockExecutive2(output=output, exit_code=exit_code,
+ exception=exception,
+ run_command_fn=run_command_fn)
+ fs = filesystem_mock.MockFileSystem(files)
+ return config.Config(e, fs)
+
+ def assert_configuration(self, contents, expected):
+ # This tests that a configuration file containing
+ # _contents_ ends up being interpreted as _expected_.
+ c = self.make_config('foo', {'foo/Configuration': contents})
+ self.assertEqual(c.default_configuration(), expected)
+
+ def test_build_directory(self):
+ # --top-level
+ c = self.make_config(run_command_fn=mock_run_command)
+ self.assertTrue(c.build_directory(None).endswith('WebKitBuild'))
+
+ # Test again to check caching
+ self.assertTrue(c.build_directory(None).endswith('WebKitBuild'))
+
+ # Test other values
+ self.assertTrue(c.build_directory('Release').endswith('/Release'))
+ self.assertTrue(c.build_directory('Debug').endswith('/Debug'))
+ self.assertRaises(KeyError, c.build_directory, 'Unknown')
+
+ def test_build_dumprendertree__success(self):
+ c = self.make_config(exit_code=0)
+ self.assertTrue(c.build_dumprendertree("Debug"))
+ self.assertTrue(c.build_dumprendertree("Release"))
+ self.assertRaises(KeyError, c.build_dumprendertree, "Unknown")
+
+ def test_build_dumprendertree__failure(self):
+ c = self.make_config(exit_code=-1)
+
+ # FIXME: Build failures should log errors. However, the message we
+ # get depends on how we're being called; as a standalone test,
+ # we'll get the "no handlers found" message. As part of
+ # test-webkitpy, we get the actual message. Really, we need
+ # outputcapture to install its own handler.
+ oc = outputcapture.OutputCapture()
+ oc.capture_output()
+ self.assertFalse(c.build_dumprendertree('Debug'))
+ oc.restore_output()
+
+ oc.capture_output()
+ self.assertFalse(c.build_dumprendertree('Release'))
+ oc.restore_output()
+
+ def test_default_configuration__release(self):
+ self.assert_configuration('Release', 'Release')
+
+ def test_default_configuration__debug(self):
+ self.assert_configuration('Debug', 'Debug')
+
+ def test_default_configuration__deployment(self):
+ self.assert_configuration('Deployment', 'Release')
+
+ def test_default_configuration__development(self):
+ self.assert_configuration('Development', 'Debug')
+
+ def test_default_configuration__notfound(self):
+ # This tests what happens if the default configuration file
+ # doesn't exist.
+ c = self.make_config(output='foo', files={'foo/Configuration': None})
+ self.assertEqual(c.default_configuration(), "Release")
+
+ def test_default_configuration__unknown(self):
+ # Ignore the warning about an unknown configuration value.
+ oc = outputcapture.OutputCapture()
+ oc.capture_output()
+ self.assert_configuration('Unknown', 'Unknown')
+ oc.restore_output()
+
+ def test_default_configuration__standalone(self):
+ # FIXME: This test runs a standalone python script to test
+ # reading the default configuration to work around any possible
+ # caching / reset bugs. See https://bugs.webkit.org/show_bug?id=49360
+ # for the motivation. We can remove this test when we remove the
+ # global configuration cache in config.py.
+ e = executive.Executive()
+ fs = filesystem.FileSystem()
+ c = config.Config(e, fs)
+ script = c.path_from_webkit_base('Tools', 'Scripts',
+ 'webkitpy', 'layout_tests', 'port', 'config_standalone.py')
+
+ # Note: don't use 'Release' here, since that's the normal default.
+ expected = 'Debug'
+
+ args = [sys.executable, script, '--mock', expected]
+ actual = e.run_command(args).rstrip()
+ self.assertEqual(actual, expected)
+
+ def test_default_configuration__no_perl(self):
+ # We need perl to run webkit-build-directory to find out where the
+ # default configuration file is. See what happens if perl isn't
+ # installed. (We should get the default value, 'Release').
+ c = self.make_config(exception=OSError)
+ actual = c.default_configuration()
+ self.assertEqual(actual, 'Release')
+
+ def test_default_configuration__scripterror(self):
+ # We run webkit-build-directory to find out where the default
+ # configuration file is. See what happens if that script fails.
+ # (We should get the default value, 'Release').
+ c = self.make_config(exception=executive.ScriptError())
+ actual = c.default_configuration()
+ self.assertEqual(actual, 'Release')
+
+ def test_path_from_webkit_base(self):
+ # FIXME: We use a real filesystem here. Should this move to a
+ # mocked one?
+ c = config.Config(executive.Executive(), filesystem.FileSystem())
+ self.assertTrue(c.path_from_webkit_base('foo'))
+
+ def test_webkit_base_dir(self):
+ # FIXME: We use a real filesystem here. Should this move to a
+ # mocked one?
+ c = config.Config(executive.Executive(), filesystem.FileSystem())
+ base_dir = c.webkit_base_dir()
+ self.assertTrue(base_dir)
+ self.assertNotEqual(base_dir[-1], '/')
+
+ orig_cwd = os.getcwd()
+ os.chdir(os.environ['HOME'])
+ c = config.Config(executive.Executive(), filesystem.FileSystem())
+ try:
+ base_dir_2 = c.webkit_base_dir()
+ self.assertEqual(base_dir, base_dir_2)
+ finally:
+ os.chdir(orig_cwd)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/dryrun.py b/Tools/Scripts/webkitpy/layout_tests/port/dryrun.py
new file mode 100644
index 0000000..4ed34e6
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/dryrun.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python
+# 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.
+
+"""This is a test implementation of the Port interface that generates the
+ correct output for every test. It can be used for perf testing, because
+ it is pretty much a lower limit on how fast a port can possibly run.
+
+ This implementation acts as a wrapper around a real port (the real port
+ is held as a delegate object). To specify which port, use the port name
+ 'dryrun-XXX' (e.g., 'dryrun-chromium-mac-leopard'). If you use just
+ 'dryrun', it uses the default port.
+
+ Note that because this is really acting as a wrapper around the underlying
+ port, you must be able to run the underlying port as well
+ (check_build() and check_sys_deps() must pass and auxiliary binaries
+ like layout_test_helper and httpd must work).
+
+ This implementation also modifies the test expectations so that all
+ tests are either SKIPPED or expected to PASS."""
+
+from __future__ import with_statement
+
+import os
+import sys
+import time
+
+from webkitpy.layout_tests.layout_package import test_output
+
+import base
+import factory
+
+
+class DryRunPort(object):
+ """DryRun implementation of the Port interface."""
+
+ def __init__(self, **kwargs):
+ pfx = 'dryrun-'
+ if 'port_name' in kwargs:
+ if kwargs['port_name'].startswith(pfx):
+ kwargs['port_name'] = kwargs['port_name'][len(pfx):]
+ else:
+ kwargs['port_name'] = None
+ self.__delegate = factory.get(**kwargs)
+
+ def __getattr__(self, name):
+ return getattr(self.__delegate, name)
+
+ def check_build(self, needs_http):
+ return True
+
+ def check_sys_deps(self, needs_http):
+ return True
+
+ def start_helper(self):
+ pass
+
+ def start_http_server(self):
+ pass
+
+ def start_websocket_server(self):
+ pass
+
+ def stop_helper(self):
+ pass
+
+ def stop_http_server(self):
+ pass
+
+ def stop_websocket_server(self):
+ pass
+
+ def create_driver(self, worker_number):
+ return DryrunDriver(self, worker_number)
+
+
+class DryrunDriver(base.Driver):
+ """Dryrun implementation of the DumpRenderTree / Driver interface."""
+
+ def __init__(self, port, worker_number):
+ self._port = port
+ self._worker_number = worker_number
+
+ def cmd_line(self):
+ return ['None']
+
+ def poll(self):
+ return None
+
+ def run_test(self, test_input):
+ start_time = time.time()
+ text_output = self._port.expected_text(test_input.filename)
+
+ if test_input.image_hash is not None:
+ image = self._port.expected_image(test_input.filename)
+ hash = self._port.expected_checksum(test_input.filename)
+ else:
+ image = None
+ hash = None
+ return test_output.TestOutput(text_output, image, hash, False,
+ time.time() - start_time, False, None)
+
+ def start(self):
+ pass
+
+ def stop(self):
+ pass
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/factory.py b/Tools/Scripts/webkitpy/layout_tests/port/factory.py
new file mode 100644
index 0000000..6935744
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/factory.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python
+# 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 name of Google Inc. 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.
+
+"""Factory method to retrieve the appropriate port implementation."""
+
+
+import sys
+
+ALL_PORT_NAMES = ['test', 'dryrun', 'mac', 'win', 'gtk', 'qt', 'chromium-mac',
+ 'chromium-linux', 'chromium-win', 'google-chrome-win',
+ 'google-chrome-mac', 'google-chrome-linux32', 'google-chrome-linux64']
+
+
+def get(port_name=None, options=None, **kwargs):
+ """Returns an object implementing the Port interface. If
+ port_name is None, this routine attempts to guess at the most
+ appropriate port on this platform."""
+ # Wrapped for backwards-compatibility
+ if port_name:
+ kwargs['port_name'] = port_name
+ if options:
+ kwargs['options'] = options
+ return _get_kwargs(**kwargs)
+
+
+def _get_kwargs(**kwargs):
+ port_to_use = kwargs.get('port_name', None)
+ options = kwargs.get('options', None)
+ if port_to_use is None:
+ if sys.platform == 'win32' or sys.platform == 'cygwin':
+ if options and hasattr(options, 'chromium') and options.chromium:
+ port_to_use = 'chromium-win'
+ else:
+ port_to_use = 'win'
+ elif sys.platform == 'linux2':
+ port_to_use = 'chromium-linux'
+ elif sys.platform == 'darwin':
+ if options and hasattr(options, 'chromium') and options.chromium:
+ port_to_use = 'chromium-mac'
+ else:
+ port_to_use = 'mac'
+
+ if port_to_use is None:
+ raise NotImplementedError('unknown port; sys.platform = "%s"' %
+ sys.platform)
+
+ if port_to_use == 'test':
+ import test
+ maker = test.TestPort
+ elif port_to_use.startswith('dryrun'):
+ import dryrun
+ maker = dryrun.DryRunPort
+ elif port_to_use.startswith('mac'):
+ import mac
+ maker = mac.MacPort
+ elif port_to_use.startswith('win'):
+ import win
+ maker = win.WinPort
+ elif port_to_use.startswith('gtk'):
+ import gtk
+ maker = gtk.GtkPort
+ elif port_to_use.startswith('qt'):
+ import qt
+ maker = qt.QtPort
+ elif port_to_use.startswith('chromium-gpu'):
+ import chromium_gpu
+ maker = chromium_gpu.get
+ elif port_to_use.startswith('chromium-mac'):
+ import chromium_mac
+ maker = chromium_mac.ChromiumMacPort
+ elif port_to_use.startswith('chromium-linux'):
+ import chromium_linux
+ maker = chromium_linux.ChromiumLinuxPort
+ elif port_to_use.startswith('chromium-win'):
+ import chromium_win
+ maker = chromium_win.ChromiumWinPort
+ elif port_to_use.startswith('google-chrome'):
+ import google_chrome
+ maker = google_chrome.GetGoogleChromePort
+ else:
+ raise NotImplementedError('unsupported port: %s' % port_to_use)
+ return maker(**kwargs)
+
+def get_all(options=None):
+ """Returns all the objects implementing the Port interface."""
+ return dict([(port_name, get(port_name, options=options))
+ for port_name in ALL_PORT_NAMES])
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/factory_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/factory_unittest.py
new file mode 100644
index 0000000..978a557
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/factory_unittest.py
@@ -0,0 +1,188 @@
+# 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 name of Google Inc. 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.
+
+import sys
+import unittest
+
+from webkitpy.tool import mocktool
+
+import chromium_gpu
+import chromium_linux
+import chromium_mac
+import chromium_win
+import dryrun
+import factory
+import google_chrome
+import gtk
+import mac
+import qt
+import test
+import win
+
+
+class FactoryTest(unittest.TestCase):
+ """Test factory creates proper port object for the target.
+
+ Target is specified by port_name, sys.platform and options.
+
+ """
+ # FIXME: The ports themselves should expose what options they require,
+ # instead of passing generic "options".
+
+ def setUp(self):
+ self.real_sys_platform = sys.platform
+ self.webkit_options = mocktool.MockOptions(pixel_tests=False)
+ self.chromium_options = mocktool.MockOptions(pixel_tests=False,
+ chromium=True)
+
+ def tearDown(self):
+ sys.platform = self.real_sys_platform
+
+ def assert_port(self, port_name, expected_port, port_obj=None):
+ """Helper assert for port_name.
+
+ Args:
+ port_name: port name to get port object.
+ expected_port: class of expected port object.
+ port_obj: optional port object
+ """
+ port_obj = port_obj or factory.get(port_name=port_name)
+ self.assertTrue(isinstance(port_obj, expected_port))
+
+ def assert_platform_port(self, platform, options, expected_port):
+ """Helper assert for platform and options.
+
+ Args:
+ platform: sys.platform.
+ options: options to get port object.
+ expected_port: class of expected port object.
+
+ """
+ orig_platform = sys.platform
+ sys.platform = platform
+ self.assertTrue(isinstance(factory.get(options=options),
+ expected_port))
+ sys.platform = orig_platform
+
+ def test_test(self):
+ self.assert_port("test", test.TestPort)
+
+ def test_dryrun(self):
+ self.assert_port("dryrun-test", dryrun.DryRunPort)
+ self.assert_port("dryrun-mac", dryrun.DryRunPort)
+
+ def test_mac(self):
+ self.assert_port("mac", mac.MacPort)
+ self.assert_platform_port("darwin", None, mac.MacPort)
+ self.assert_platform_port("darwin", self.webkit_options, mac.MacPort)
+
+ def test_win(self):
+ self.assert_port("win", win.WinPort)
+ self.assert_platform_port("win32", None, win.WinPort)
+ self.assert_platform_port("win32", self.webkit_options, win.WinPort)
+ self.assert_platform_port("cygwin", None, win.WinPort)
+ self.assert_platform_port("cygwin", self.webkit_options, win.WinPort)
+
+ def test_google_chrome(self):
+ # The actual Chrome class names aren't available so we test that the
+ # objects we get are at least subclasses of the Chromium versions.
+ self.assert_port("google-chrome-linux32",
+ chromium_linux.ChromiumLinuxPort)
+ self.assert_port("google-chrome-linux64",
+ chromium_linux.ChromiumLinuxPort)
+ self.assert_port("google-chrome-win",
+ chromium_win.ChromiumWinPort)
+ self.assert_port("google-chrome-mac",
+ chromium_mac.ChromiumMacPort)
+
+ def test_gtk(self):
+ self.assert_port("gtk", gtk.GtkPort)
+
+ def test_qt(self):
+ self.assert_port("qt", qt.QtPort)
+
+ def test_chromium_gpu_linux(self):
+ self.assert_port("chromium-gpu-linux", chromium_gpu.ChromiumGpuLinuxPort)
+
+ def test_chromium_gpu_mac(self):
+ self.assert_port("chromium-gpu-mac", chromium_gpu.ChromiumGpuMacPort)
+
+ def test_chromium_gpu_win(self):
+ self.assert_port("chromium-gpu-win", chromium_gpu.ChromiumGpuWinPort)
+
+ def test_chromium_mac(self):
+ self.assert_port("chromium-mac", chromium_mac.ChromiumMacPort)
+ self.assert_platform_port("darwin", self.chromium_options,
+ chromium_mac.ChromiumMacPort)
+
+ def test_chromium_linux(self):
+ self.assert_port("chromium-linux", chromium_linux.ChromiumLinuxPort)
+ self.assert_platform_port("linux2", self.chromium_options,
+ chromium_linux.ChromiumLinuxPort)
+
+ def test_chromium_win(self):
+ self.assert_port("chromium-win", chromium_win.ChromiumWinPort)
+ self.assert_platform_port("win32", self.chromium_options,
+ chromium_win.ChromiumWinPort)
+ self.assert_platform_port("cygwin", self.chromium_options,
+ chromium_win.ChromiumWinPort)
+
+ def test_get_all_ports(self):
+ ports = factory.get_all()
+ for name in factory.ALL_PORT_NAMES:
+ self.assertTrue(name in ports.keys())
+ self.assert_port("test", test.TestPort, ports["test"])
+ self.assert_port("dryrun-test", dryrun.DryRunPort, ports["dryrun"])
+ self.assert_port("dryrun-mac", dryrun.DryRunPort, ports["dryrun"])
+ self.assert_port("mac", mac.MacPort, ports["mac"])
+ self.assert_port("win", win.WinPort, ports["win"])
+ self.assert_port("gtk", gtk.GtkPort, ports["gtk"])
+ self.assert_port("qt", qt.QtPort, ports["qt"])
+ self.assert_port("chromium-mac", chromium_mac.ChromiumMacPort,
+ ports["chromium-mac"])
+ self.assert_port("chromium-linux", chromium_linux.ChromiumLinuxPort,
+ ports["chromium-linux"])
+ self.assert_port("chromium-win", chromium_win.ChromiumWinPort,
+ ports["chromium-win"])
+
+ def test_unknown_specified(self):
+ # Test what happens when you specify an unknown port.
+ orig_platform = sys.platform
+ self.assertRaises(NotImplementedError, factory.get,
+ port_name='unknown')
+
+ def test_unknown_default(self):
+ # Test what happens when you're running on an unknown platform.
+ orig_platform = sys.platform
+ sys.platform = 'unknown'
+ self.assertRaises(NotImplementedError, factory.get)
+ sys.platform = orig_platform
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/google_chrome.py b/Tools/Scripts/webkitpy/layout_tests/port/google_chrome.py
new file mode 100644
index 0000000..8d94bb5
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/google_chrome.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env python
+# 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.
+
+# 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.
+
+from __future__ import with_statement
+
+import codecs
+import os
+
+
+def _test_expectations_overrides(port, super):
+ # The chrome ports use the regular overrides plus anything in the
+ # official test_expectations as well. Hopefully we don't get collisions.
+ chromium_overrides = super.test_expectations_overrides(port)
+
+ # FIXME: It used to be that AssertionError would get raised by
+ # path_from_chromium_base() if we weren't in a Chromium checkout, but
+ # this changed in r60427. This should probably be changed back.
+ overrides_path = port.path_from_chromium_base('webkit', 'tools',
+ 'layout_tests', 'test_expectations_chrome.txt')
+ if not os.path.exists(overrides_path):
+ return chromium_overrides
+
+ with codecs.open(overrides_path, "r", "utf-8") as file:
+ if chromium_overrides:
+ return chromium_overrides + file.read()
+ else:
+ return file.read()
+
+def GetGoogleChromePort(**kwargs):
+ """Some tests have slightly different results when compiled as Google
+ Chrome vs Chromium. In those cases, we prepend an additional directory to
+ to the baseline paths."""
+ port_name = kwargs['port_name']
+ del kwargs['port_name']
+ if port_name == 'google-chrome-linux32':
+ import chromium_linux
+
+ class GoogleChromeLinux32Port(chromium_linux.ChromiumLinuxPort):
+ def baseline_search_path(self):
+ paths = chromium_linux.ChromiumLinuxPort.baseline_search_path(
+ self)
+ paths.insert(0, self._webkit_baseline_path(
+ 'google-chrome-linux32'))
+ return paths
+
+ def test_expectations_overrides(self):
+ return _test_expectations_overrides(self,
+ chromium_linux.ChromiumLinuxPort)
+
+ return GoogleChromeLinux32Port(**kwargs)
+ elif port_name == 'google-chrome-linux64':
+ import chromium_linux
+
+ class GoogleChromeLinux64Port(chromium_linux.ChromiumLinuxPort):
+ def baseline_search_path(self):
+ paths = chromium_linux.ChromiumLinuxPort.baseline_search_path(
+ self)
+ paths.insert(0, self._webkit_baseline_path(
+ 'google-chrome-linux64'))
+ return paths
+
+ def test_expectations_overrides(self):
+ return _test_expectations_overrides(self,
+ chromium_linux.ChromiumLinuxPort)
+
+ return GoogleChromeLinux64Port(**kwargs)
+ elif port_name.startswith('google-chrome-mac'):
+ import chromium_mac
+
+ class GoogleChromeMacPort(chromium_mac.ChromiumMacPort):
+ def baseline_search_path(self):
+ paths = chromium_mac.ChromiumMacPort.baseline_search_path(
+ self)
+ paths.insert(0, self._webkit_baseline_path(
+ 'google-chrome-mac'))
+ return paths
+
+ def test_expectations_overrides(self):
+ return _test_expectations_overrides(self,
+ chromium_mac.ChromiumMacPort)
+
+ return GoogleChromeMacPort(**kwargs)
+ elif port_name.startswith('google-chrome-win'):
+ import chromium_win
+
+ class GoogleChromeWinPort(chromium_win.ChromiumWinPort):
+ def baseline_search_path(self):
+ paths = chromium_win.ChromiumWinPort.baseline_search_path(
+ self)
+ paths.insert(0, self._webkit_baseline_path(
+ 'google-chrome-win'))
+ return paths
+
+ def test_expectations_overrides(self):
+ return _test_expectations_overrides(self,
+ chromium_win.ChromiumWinPort)
+
+ return GoogleChromeWinPort(**kwargs)
+ raise NotImplementedError('unsupported port: %s' % port_name)
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/google_chrome_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/google_chrome_unittest.py
new file mode 100644
index 0000000..e60c274
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/google_chrome_unittest.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+# 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.
+
+# 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.
+
+import codecs
+import os
+import unittest
+
+from webkitpy.common import newstringio
+
+import factory
+import google_chrome
+
+
+class GetGoogleChromePortTest(unittest.TestCase):
+ def test_get_google_chrome_port(self):
+ test_ports = ('google-chrome-linux32', 'google-chrome-linux64',
+ 'google-chrome-mac', 'google-chrome-win')
+ for port in test_ports:
+ self._verify_baseline_path(port, port)
+ self._verify_expectations_overrides(port)
+
+ self._verify_baseline_path('google-chrome-mac', 'google-chrome-mac-leopard')
+ self._verify_baseline_path('google-chrome-win', 'google-chrome-win-xp')
+ self._verify_baseline_path('google-chrome-win', 'google-chrome-win-vista')
+
+ def _verify_baseline_path(self, expected_path, port_name):
+ port = google_chrome.GetGoogleChromePort(port_name=port_name,
+ options=None)
+ path = port.baseline_search_path()[0]
+ self.assertEqual(expected_path, os.path.split(path)[1])
+
+ def _verify_expectations_overrides(self, port_name):
+ # FIXME: make this more robust when we have the Tree() abstraction.
+ # we should be able to test for the files existing or not, and
+ # be able to control the contents better.
+
+ chromium_port = factory.get("chromium-mac")
+ chromium_overrides = chromium_port.test_expectations_overrides()
+ port = google_chrome.GetGoogleChromePort(port_name=port_name,
+ options=None)
+
+ orig_exists = os.path.exists
+ orig_open = codecs.open
+ expected_string = "// hello, world\n"
+
+ def mock_exists_chrome_not_found(path):
+ if 'test_expectations_chrome.txt' in path:
+ return False
+ return orig_exists(path)
+
+ def mock_exists_chrome_found(path):
+ if 'test_expectations_chrome.txt' in path:
+ return True
+ return orig_exists(path)
+
+ def mock_open(path, mode, encoding):
+ if 'test_expectations_chrome.txt' in path:
+ return newstringio.StringIO(expected_string)
+ return orig_open(path, mode, encoding)
+
+ try:
+ os.path.exists = mock_exists_chrome_not_found
+ chrome_overrides = port.test_expectations_overrides()
+ self.assertEqual(chromium_overrides, chrome_overrides)
+
+ os.path.exists = mock_exists_chrome_found
+ codecs.open = mock_open
+ chrome_overrides = port.test_expectations_overrides()
+ if chromium_overrides:
+ self.assertEqual(chrome_overrides,
+ chromium_overrides + expected_string)
+ else:
+ self.assertEqual(chrome_overrides, expected_string)
+ finally:
+ os.path.exists = orig_exists
+ codecs.open = orig_open
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/gtk.py b/Tools/Scripts/webkitpy/layout_tests/port/gtk.py
new file mode 100644
index 0000000..a18fdff
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/gtk.py
@@ -0,0 +1,116 @@
+# 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.
+
+"""WebKit Gtk implementation of the Port interface."""
+
+import logging
+import os
+import signal
+
+from webkitpy.layout_tests.port.webkit import WebKitPort
+
+_log = logging.getLogger("webkitpy.layout_tests.port.gtk")
+
+
+class GtkPort(WebKitPort):
+ """WebKit Gtk implementation of the Port class."""
+
+ def __init__(self, **kwargs):
+ kwargs.setdefault('port_name', 'gtk')
+ WebKitPort.__init__(self, **kwargs)
+
+ def _tests_for_other_platforms(self):
+ # FIXME: This list could be dynamic based on platform name and
+ # pushed into base.Port.
+ # This really need to be automated.
+ return [
+ "platform/chromium",
+ "platform/win",
+ "platform/qt",
+ "platform/mac",
+ ]
+
+ def _path_to_apache_config_file(self):
+ # FIXME: This needs to detect the distribution and change config files.
+ return os.path.join(self.layout_tests_dir(), 'http', 'conf',
+ 'apache2-debian-httpd.conf')
+
+ def _shut_down_http_server(self, server_pid):
+ """Shut down the httpd web server. Blocks until it's fully
+ shut down.
+
+ Args:
+ server_pid: The process ID of the running server.
+ """
+ # server_pid is not set when "http_server.py stop" is run manually.
+ if server_pid is None:
+ # FIXME: This isn't ideal, since it could conflict with
+ # lighttpd processes not started by http_server.py,
+ # but good enough for now.
+ self._executive.kill_all('apache2')
+ else:
+ try:
+ os.kill(server_pid, signal.SIGTERM)
+ # TODO(mmoss) Maybe throw in a SIGKILL just to be sure?
+ except OSError:
+ # Sometimes we get a bad PID (e.g. from a stale httpd.pid
+ # file), so if kill fails on the given PID, just try to
+ # 'killall' web servers.
+ self._shut_down_http_server(None)
+
+ def _path_to_driver(self):
+ return self._build_path('Programs', 'DumpRenderTree')
+
+ def check_build(self, needs_http):
+ if not self._check_driver():
+ return False
+ return True
+
+ def _path_to_apache(self):
+ if self._is_redhat_based():
+ return '/usr/sbin/httpd'
+ else:
+ return '/usr/sbin/apache2'
+
+ def _path_to_apache_config_file(self):
+ if self._is_redhat_based():
+ config_name = 'fedora-httpd.conf'
+ else:
+ config_name = 'apache2-debian-httpd.conf'
+
+ return os.path.join(self.layout_tests_dir(), 'http', 'conf',
+ config_name)
+
+ def _path_to_wdiff(self):
+ if self._is_redhat_based():
+ return '/usr/bin/dwdiff'
+ else:
+ return '/usr/bin/wdiff'
+
+ def _is_redhat_based(self):
+ return os.path.exists(os.path.join('/etc', 'redhat-release'))
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/http_lock.py b/Tools/Scripts/webkitpy/layout_tests/port/http_lock.py
new file mode 100644
index 0000000..f5946b6
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/http_lock.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+# Copyright (C) 2010 Gabor Rapcsanyi (rgabor@inf.u-szeged.hu), University of Szeged
+# Copyright (C) 2010 Andras Becsi (abecsi@inf.u-szeged.hu), University of Szeged
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. 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.
+#
+# THIS SOFTWARE IS PROVIDED BY UNIVERSITY OF SZEGED ``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 UNIVERSITY OF SZEGED 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.
+
+"""This class helps to block NRWT threads when more NRWTs run
+http and websocket tests in a same time."""
+
+import glob
+import logging
+import os
+import sys
+import tempfile
+import time
+
+from webkitpy.common.system.executive import Executive
+from webkitpy.common.system.file_lock import FileLock
+from webkitpy.common.system.filesystem import FileSystem
+
+
+_log = logging.getLogger("webkitpy.layout_tests.port.http_lock")
+
+
+class HttpLock(object):
+
+ def __init__(self, lock_path, lock_file_prefix="WebKitHttpd.lock.",
+ guard_lock="WebKit.lock"):
+ self._lock_path = lock_path
+ if not self._lock_path:
+ self._lock_path = tempfile.gettempdir()
+ self._lock_file_prefix = lock_file_prefix
+ self._lock_file_path_prefix = os.path.join(self._lock_path,
+ self._lock_file_prefix)
+ self._guard_lock_file = os.path.join(self._lock_path, guard_lock)
+ self._guard_lock = FileLock(self._guard_lock_file)
+ self._process_lock_file_name = ""
+ self._executive = Executive()
+
+ def cleanup_http_lock(self):
+ """Delete the lock file if exists."""
+ if os.path.exists(self._process_lock_file_name):
+ _log.debug("Removing lock file: %s" % self._process_lock_file_name)
+ FileSystem().remove(self._process_lock_file_name)
+
+ def _extract_lock_number(self, lock_file_name):
+ """Return the lock number from lock file."""
+ prefix_length = len(self._lock_file_path_prefix)
+ return int(lock_file_name[prefix_length:])
+
+ def _lock_file_list(self):
+ """Return the list of lock files sequentially."""
+ lock_list = glob.glob(self._lock_file_path_prefix + '*')
+ lock_list.sort(key=self._extract_lock_number)
+ return lock_list
+
+ def _next_lock_number(self):
+ """Return the next available lock number."""
+ lock_list = self._lock_file_list()
+ if not lock_list:
+ return 0
+ return self._extract_lock_number(lock_list[-1]) + 1
+
+ def _curent_lock_pid(self):
+ """Return with the current lock pid. If the lock is not valid
+ it deletes the lock file."""
+ lock_list = self._lock_file_list()
+ if not lock_list:
+ return
+ try:
+ current_lock_file = open(lock_list[0], 'r')
+ current_pid = current_lock_file.readline()
+ current_lock_file.close()
+ if not (current_pid and self._executive.check_running_pid(int(current_pid))):
+ _log.debug("Removing stuck lock file: %s" % lock_list[0])
+ FileSystem().remove(lock_list[0])
+ return
+ except (IOError, OSError):
+ return
+ return int(current_pid)
+
+ def _create_lock_file(self):
+ """The lock files are used to schedule the running test sessions in first
+ come first served order. The guard lock ensures that the lock numbers are
+ sequential."""
+ if not os.path.exists(self._lock_path):
+ _log.debug("Lock directory does not exist: %s" % self._lock_path)
+ return False
+
+ if not self._guard_lock.acquire_lock():
+ _log.debug("Guard lock timed out!")
+ return False
+
+ self._process_lock_file_name = (self._lock_file_path_prefix +
+ str(self._next_lock_number()))
+ _log.debug("Creating lock file: %s" % self._process_lock_file_name)
+ lock_file = open(self._process_lock_file_name, 'w')
+ lock_file.write(str(os.getpid()))
+ lock_file.close()
+ self._guard_lock.release_lock()
+ return True
+
+
+ def wait_for_httpd_lock(self):
+ """Create a lock file and wait until it's turn comes. If something goes wrong
+ it wont do any locking."""
+ if not self._create_lock_file():
+ _log.debug("Warning, http locking failed!")
+ return
+
+ while self._curent_lock_pid() != os.getpid():
+ time.sleep(1)
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/http_lock_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/http_lock_unittest.py
new file mode 100644
index 0000000..85c760a
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/http_lock_unittest.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python
+# Copyright (C) 2010 Gabor Rapcsanyi (rgabor@inf.u-szeged.hu), University of Szeged
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. 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.
+#
+# THIS SOFTWARE IS PROVIDED BY UNIVERSITY OF SZEGED ``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 UNIVERSITY OF SZEGED 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.
+
+import glob
+import http_lock
+import os
+import unittest
+
+
+class HttpLockTest(unittest.TestCase):
+
+ def __init__(self, testFunc):
+ self.http_lock_obj = http_lock.HttpLock(None, "WebKitTestHttpd.lock.", "WebKitTest.lock")
+ self.lock_file_path_prefix = os.path.join(self.http_lock_obj._lock_path,
+ self.http_lock_obj._lock_file_prefix)
+ self.lock_file_name = self.lock_file_path_prefix + "0"
+ self.guard_lock_file = self.http_lock_obj._guard_lock_file
+ self.clean_all_lockfile()
+ unittest.TestCase.__init__(self, testFunc)
+
+ def clean_all_lockfile(self):
+ if os.path.exists(self.guard_lock_file):
+ os.unlink(self.guard_lock_file)
+ lock_list = glob.glob(self.lock_file_path_prefix + '*')
+ for file_name in lock_list:
+ os.unlink(file_name)
+
+ def assertEqual(self, first, second):
+ if first != second:
+ self.clean_all_lockfile()
+ unittest.TestCase.assertEqual(self, first, second)
+
+ def _check_lock_file(self):
+ if os.path.exists(self.lock_file_name):
+ pid = os.getpid()
+ lock_file = open(self.lock_file_name, 'r')
+ lock_file_pid = lock_file.readline()
+ lock_file.close()
+ self.assertEqual(pid, int(lock_file_pid))
+ return True
+ return False
+
+ def test_lock_lifecycle(self):
+ self.http_lock_obj._create_lock_file()
+
+ self.assertEqual(True, self._check_lock_file())
+ self.assertEqual(1, self.http_lock_obj._next_lock_number())
+
+ self.http_lock_obj.cleanup_http_lock()
+
+ self.assertEqual(False, self._check_lock_file())
+ self.assertEqual(0, self.http_lock_obj._next_lock_number())
+
+ def test_extract_lock_number(self,):
+ lock_file_list = (
+ self.lock_file_path_prefix + "00",
+ self.lock_file_path_prefix + "9",
+ self.lock_file_path_prefix + "001",
+ self.lock_file_path_prefix + "021",
+ )
+
+ expected_number_list = (0, 9, 1, 21)
+
+ for lock_file, expected in zip(lock_file_list, expected_number_list):
+ self.assertEqual(self.http_lock_obj._extract_lock_number(lock_file), expected)
+
+ def test_lock_file_list(self):
+ lock_file_list = [
+ self.lock_file_path_prefix + "6",
+ self.lock_file_path_prefix + "1",
+ self.lock_file_path_prefix + "4",
+ self.lock_file_path_prefix + "3",
+ ]
+
+ expected_file_list = [
+ self.lock_file_path_prefix + "1",
+ self.lock_file_path_prefix + "3",
+ self.lock_file_path_prefix + "4",
+ self.lock_file_path_prefix + "6",
+ ]
+
+ for file_name in lock_file_list:
+ open(file_name, 'w')
+
+ self.assertEqual(self.http_lock_obj._lock_file_list(), expected_file_list)
+
+ for file_name in lock_file_list:
+ os.unlink(file_name)
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/http_server.py b/Tools/Scripts/webkitpy/layout_tests/port/http_server.py
new file mode 100755
index 0000000..bd75e27
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/http_server.py
@@ -0,0 +1,233 @@
+#!/usr/bin/env python
+# 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 name of Google Inc. 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.
+
+"""A class to help start/stop the lighttpd server used by layout tests."""
+
+from __future__ import with_statement
+
+import codecs
+import logging
+import optparse
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+import time
+import urllib
+
+import factory
+import http_server_base
+
+_log = logging.getLogger("webkitpy.layout_tests.port.http_server")
+
+
+class HttpdNotStarted(Exception):
+ pass
+
+
+class Lighttpd(http_server_base.HttpServerBase):
+
+ def __init__(self, port_obj, output_dir, background=False, port=None,
+ root=None, run_background=None):
+ """Args:
+ output_dir: the absolute path to the layout test result directory
+ """
+ # Webkit tests
+ http_server_base.HttpServerBase.__init__(self, port_obj)
+ self._output_dir = output_dir
+ self._process = None
+ self._port = port
+ self._root = root
+ self._run_background = run_background
+ if self._port:
+ self._port = int(self._port)
+
+ try:
+ self._webkit_tests = os.path.join(
+ self._port_obj.layout_tests_dir(), 'http', 'tests')
+ self._js_test_resource = os.path.join(
+ self._port_obj.layout_tests_dir(), 'fast', 'js', 'resources')
+ except:
+ self._webkit_tests = None
+ self._js_test_resource = None
+
+ # Self generated certificate for SSL server (for client cert get
+ # <base-path>\chrome\test\data\ssl\certs\root_ca_cert.crt)
+ self._pem_file = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)), 'httpd2.pem')
+
+ # One mapping where we can get to everything
+ self.VIRTUALCONFIG = []
+
+ if self._webkit_tests:
+ self.VIRTUALCONFIG.extend(
+ # Three mappings (one with SSL) for LayoutTests http tests
+ [{'port': 8000, 'docroot': self._webkit_tests},
+ {'port': 8080, 'docroot': self._webkit_tests},
+ {'port': 8443, 'docroot': self._webkit_tests,
+ 'sslcert': self._pem_file}])
+
+ def is_running(self):
+ return self._process != None
+
+ def start(self):
+ if self.is_running():
+ raise 'Lighttpd already running'
+
+ base_conf_file = self._port_obj.path_from_webkit_base('Tools',
+ 'Scripts', 'webkitpy', 'layout_tests', 'port', 'lighttpd.conf')
+ out_conf_file = os.path.join(self._output_dir, 'lighttpd.conf')
+ time_str = time.strftime("%d%b%Y-%H%M%S")
+ access_file_name = "access.log-" + time_str + ".txt"
+ access_log = os.path.join(self._output_dir, access_file_name)
+ log_file_name = "error.log-" + time_str + ".txt"
+ error_log = os.path.join(self._output_dir, log_file_name)
+
+ # Remove old log files. We only need to keep the last ones.
+ self.remove_log_files(self._output_dir, "access.log-")
+ self.remove_log_files(self._output_dir, "error.log-")
+
+ # Write out the config
+ with codecs.open(base_conf_file, "r", "utf-8") as file:
+ base_conf = file.read()
+
+ # FIXME: This should be re-worked so that this block can
+ # use with open() instead of a manual file.close() call.
+ # lighttpd.conf files seem to be UTF-8 without BOM:
+ # http://redmine.lighttpd.net/issues/992
+ f = codecs.open(out_conf_file, "w", "utf-8")
+ f.write(base_conf)
+
+ # Write out our cgi handlers. Run perl through env so that it
+ # processes the #! line and runs perl with the proper command
+ # line arguments. Emulate apache's mod_asis with a cat cgi handler.
+ f.write(('cgi.assign = ( ".cgi" => "/usr/bin/env",\n'
+ ' ".pl" => "/usr/bin/env",\n'
+ ' ".asis" => "/bin/cat",\n'
+ ' ".php" => "%s" )\n\n') %
+ self._port_obj._path_to_lighttpd_php())
+
+ # Setup log files
+ f.write(('server.errorlog = "%s"\n'
+ 'accesslog.filename = "%s"\n\n') % (error_log, access_log))
+
+ # Setup upload folders. Upload folder is to hold temporary upload files
+ # and also POST data. This is used to support XHR layout tests that
+ # does POST.
+ f.write(('server.upload-dirs = ( "%s" )\n\n') % (self._output_dir))
+
+ # Setup a link to where the js test templates are stored
+ f.write(('alias.url = ( "/js-test-resources" => "%s" )\n\n') %
+ (self._js_test_resource))
+
+ # dump out of virtual host config at the bottom.
+ if self._root:
+ if self._port:
+ # Have both port and root dir.
+ mappings = [{'port': self._port, 'docroot': self._root}]
+ else:
+ # Have only a root dir - set the ports as for LayoutTests.
+ # This is used in ui_tests to run http tests against a browser.
+
+ # default set of ports as for LayoutTests but with a
+ # specified root.
+ mappings = [{'port': 8000, 'docroot': self._root},
+ {'port': 8080, 'docroot': self._root},
+ {'port': 8443, 'docroot': self._root,
+ 'sslcert': self._pem_file}]
+ else:
+ mappings = self.VIRTUALCONFIG
+ for mapping in mappings:
+ ssl_setup = ''
+ if 'sslcert' in mapping:
+ ssl_setup = (' ssl.engine = "enable"\n'
+ ' ssl.pemfile = "%s"\n' % mapping['sslcert'])
+
+ f.write(('$SERVER["socket"] == "127.0.0.1:%d" {\n'
+ ' server.document-root = "%s"\n' +
+ ssl_setup +
+ '}\n\n') % (mapping['port'], mapping['docroot']))
+ f.close()
+
+ executable = self._port_obj._path_to_lighttpd()
+ module_path = self._port_obj._path_to_lighttpd_modules()
+ start_cmd = [executable,
+ # Newly written config file
+ '-f', os.path.join(self._output_dir, 'lighttpd.conf'),
+ # Where it can find its module dynamic libraries
+ '-m', module_path]
+
+ if not self._run_background:
+ start_cmd.append(# Don't background
+ '-D')
+
+ # Copy liblightcomp.dylib to /tmp/lighttpd/lib to work around the
+ # bug that mod_alias.so loads it from the hard coded path.
+ if sys.platform == 'darwin':
+ tmp_module_path = '/tmp/lighttpd/lib'
+ if not os.path.exists(tmp_module_path):
+ os.makedirs(tmp_module_path)
+ lib_file = 'liblightcomp.dylib'
+ shutil.copyfile(os.path.join(module_path, lib_file),
+ os.path.join(tmp_module_path, lib_file))
+
+ env = self._port_obj.setup_environ_for_server()
+ _log.debug('Starting http server')
+ # FIXME: Should use Executive.run_command
+ self._process = subprocess.Popen(start_cmd, env=env)
+
+ # Wait for server to start.
+ self.mappings = mappings
+ server_started = self.wait_for_action(
+ self.is_server_running_on_all_ports)
+
+ # Our process terminated already
+ if not server_started or self._process.returncode != None:
+ raise google.httpd_utils.HttpdNotStarted('Failed to start httpd.')
+
+ _log.debug("Server successfully started")
+
+ # TODO(deanm): Find a nicer way to shutdown cleanly. Our log files are
+ # probably not being flushed, etc... why doesn't our python have os.kill ?
+
+ def stop(self, force=False):
+ if not force and not self.is_running():
+ return
+
+ httpd_pid = None
+ if self._process:
+ httpd_pid = self._process.pid
+ self._port_obj._shut_down_http_server(httpd_pid)
+
+ if self._process:
+ # wait() is not threadsafe and can throw OSError due to:
+ # http://bugs.python.org/issue1731717
+ self._process.wait()
+ self._process = None
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/http_server_base.py b/Tools/Scripts/webkitpy/layout_tests/port/http_server_base.py
new file mode 100644
index 0000000..52a0403
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/http_server_base.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+# 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 name of Google Inc. 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.
+
+"""Base class with common routines between the Apache and Lighttpd servers."""
+
+import logging
+import os
+import time
+import urllib
+
+from webkitpy.common.system import filesystem
+
+_log = logging.getLogger("webkitpy.layout_tests.port.http_server_base")
+
+
+class HttpServerBase(object):
+
+ def __init__(self, port_obj):
+ self._port_obj = port_obj
+
+ def wait_for_action(self, action):
+ """Repeat the action for 20 seconds or until it succeeds. Returns
+ whether it succeeded."""
+ start_time = time.time()
+ while time.time() - start_time < 20:
+ if action():
+ return True
+ _log.debug("Waiting for action: %s" % action)
+ time.sleep(1)
+
+ return False
+
+ def is_server_running_on_all_ports(self):
+ """Returns whether the server is running on all the desired ports."""
+ for mapping in self.mappings:
+ if 'sslcert' in mapping:
+ http_suffix = 's'
+ else:
+ http_suffix = ''
+
+ url = 'http%s://127.0.0.1:%d/' % (http_suffix, mapping['port'])
+
+ try:
+ response = urllib.urlopen(url)
+ _log.debug("Server running at %s" % url)
+ except IOError, e:
+ _log.debug("Server NOT running at %s: %s" % (url, e))
+ return False
+
+ return True
+
+ def remove_log_files(self, folder, starts_with):
+ files = os.listdir(folder)
+ for file in files:
+ if file.startswith(starts_with):
+ full_path = os.path.join(folder, file)
+ filesystem.FileSystem().remove(full_path)
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/httpd2.pem b/Tools/Scripts/webkitpy/layout_tests/port/httpd2.pem
new file mode 100644
index 0000000..6349b78
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/httpd2.pem
@@ -0,0 +1,41 @@
+-----BEGIN CERTIFICATE-----
+MIIEZDCCAkygAwIBAgIBATANBgkqhkiG9w0BAQUFADBgMRAwDgYDVQQDEwdUZXN0
+IENBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMN
+TW91bnRhaW4gVmlldzESMBAGA1UEChMJQ2VydCBUZXN0MB4XDTA4MDcyODIyMzIy
+OFoXDTEzMDcyNzIyMzIyOFowSjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlm
+b3JuaWExEjAQBgNVBAoTCUNlcnQgVGVzdDESMBAGA1UEAxMJMTI3LjAuMC4xMIGf
+MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDQj2tPWPUgbuI4H3/3dnttqVbndwU3
+3BdRCd67DFM44GRrsjDSH4bY/EbFyX9D52d/iy6ZaAmDePcCz5k/fgP3DMujykYG
+qgNiV2ywxTlMj7NlN2C7SRt68fQMZr5iI7rypdxuaZt9lSMD3ENBffYtuLTyZd9a
+3JPJe1TaIab5GwIDAQABo4HCMIG/MAkGA1UdEwQCMAAwHQYDVR0OBBYEFCYLBv5K
+x5sLNVlpLh5FwTwhdDl7MIGSBgNVHSMEgYowgYeAFF3Of5nj1BlBMU/Gz7El9Vqv
+45cxoWSkYjBgMRAwDgYDVQQDEwdUZXN0IENBMQswCQYDVQQGEwJVUzETMBEGA1UE
+CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzESMBAGA1UEChMJ
+Q2VydCBUZXN0ggkA1FGT1D/e2U4wDQYJKoZIhvcNAQEFBQADggIBAEtkVmLObUgk
+b2cIA2S+QDtifq1UgVfBbytvR2lFmnADOR55mo0gHQG3HHqq4g034LmoVXDHhUk8
+Gb6aFiv4QubmVhLXcUelTRXwiNvGzkW7pC6Jrq105hdPjzXMKTcmiLaopm5Fqfc7
+hj5Cn1Sjspc8pdeQjrbeMdvca7KlFrGP8YkwCU2xOOX9PiN9G0966BWfjnr/fZZp
++OQVuUFHdiAZwthEMuDpAAXHqYXIsermgdOpgJaA53cf8NqBV2QGhtFgtsJCRoiu
+7DKqhyRWBGyz19VIH2b7y+6qvQVxuHk19kKRM0nftw/yNcJnm7gtttespMUPsOMa
+a2SD1G0hm0TND6vxaBhgR3cVqpl/qIpAdFi00Tm7hTyYE7I43zPW03t+/DpCt3Um
+EMRZsQ90co5q+bcx/vQ7YAtwUh30uMb0wpibeyCwDp8cqNmSiRkEuc/FjTYes5t8
+5gR//WX1l0+qjrjusO9NmoLnq2Yk6UcioX+z+q6Z/dudGfqhLfeWD2Q0LWYA242C
+d7km5Y3KAt1PJdVsof/aiVhVdddY/OIEKTRQhWEdDbosy2eh16BCKXT2FFvhNDg1
+AYFvn6I8nj9IldMJiIc3DdhacEAEzRMeRgPdzAa1griKUGknxsyTyRii8ru0WS6w
+DCNrlDOVXdzYGEZooBI76BDVY0W0akjV
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDQj2tPWPUgbuI4H3/3dnttqVbndwU33BdRCd67DFM44GRrsjDS
+H4bY/EbFyX9D52d/iy6ZaAmDePcCz5k/fgP3DMujykYGqgNiV2ywxTlMj7NlN2C7
+SRt68fQMZr5iI7rypdxuaZt9lSMD3ENBffYtuLTyZd9a3JPJe1TaIab5GwIDAQAB
+AoGANHXu8z2YIzlhE+bwhGm8MGBpKL3qhRuKjeriqMA36tWezOw8lY4ymEAU+Ulv
+BsCdaxqydQoTYou57m4TyUHEcxq9pq3H0zB0qL709DdHi/t4zbV9XIoAzC5v0/hG
+9+Ca29TwC02FCw+qLkNrtwCpwOcQmc+bPxqvFu1iMiahURECQQD2I/Hi2413CMZz
+TBjl8fMiVO9GhA2J0sc8Qi+YcgJakaLD9xcbaiLkTzPZDlA389C1b6Ia+poAr4YA
+Ve0FFbxpAkEA2OobayyHE/QtPEqoy6NLR57jirmVBNmSWWd4lAyL5UIHIYVttJZg
+8CLvbzaU/iDGwR+wKsM664rKPHEmtlyo4wJBAMeSqYO5ZOCJGu9NWjrHjM3fdAsG
+8zs2zhiLya+fcU0iHIksBW5TBmt71Jw/wMc9R5J1K0kYvFml98653O5si1ECQBCk
+RV4/mE1rmlzZzYFyEcB47DQkcM5ictvxGEsje0gnfKyRtAz6zI0f4QbDRUMJ+LWw
+XK+rMsYHa+SfOb0b9skCQQCLdeonsIpFDv/Uv+flHISy0WA+AFkLXrRkBKh6G/OD
+dMHaNevkJgUnpceVEnkrdenp5CcEoFTI17pd+nBgDm/B
+-----END RSA PRIVATE KEY-----
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/lighttpd.conf b/Tools/Scripts/webkitpy/layout_tests/port/lighttpd.conf
new file mode 100644
index 0000000..26ca22f
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/lighttpd.conf
@@ -0,0 +1,90 @@
+server.tag = "LightTPD/1.4.19 (Win32)"
+server.modules = ( "mod_accesslog",
+ "mod_alias",
+ "mod_cgi",
+ "mod_rewrite" )
+
+# default document root required
+server.document-root = "."
+
+# files to check for if .../ is requested
+index-file.names = ( "index.php", "index.pl", "index.cgi",
+ "index.html", "index.htm", "default.htm" )
+# mimetype mapping
+mimetype.assign = (
+ ".gif" => "image/gif",
+ ".jpg" => "image/jpeg",
+ ".jpeg" => "image/jpeg",
+ ".png" => "image/png",
+ ".svg" => "image/svg+xml",
+ ".css" => "text/css",
+ ".html" => "text/html",
+ ".htm" => "text/html",
+ ".xhtml" => "application/xhtml+xml",
+ ".xhtmlmp" => "application/vnd.wap.xhtml+xml",
+ ".js" => "application/x-javascript",
+ ".log" => "text/plain",
+ ".conf" => "text/plain",
+ ".text" => "text/plain",
+ ".txt" => "text/plain",
+ ".dtd" => "text/xml",
+ ".xml" => "text/xml",
+ ".manifest" => "text/cache-manifest",
+ )
+
+# Use the "Content-Type" extended attribute to obtain mime type if possible
+mimetype.use-xattr = "enable"
+
+##
+# which extensions should not be handle via static-file transfer
+#
+# .php, .pl, .fcgi are most often handled by mod_fastcgi or mod_cgi
+static-file.exclude-extensions = ( ".php", ".pl", ".cgi" )
+
+server.bind = "localhost"
+server.port = 8001
+
+## virtual directory listings
+dir-listing.activate = "enable"
+#dir-listing.encoding = "iso-8859-2"
+#dir-listing.external-css = "style/oldstyle.css"
+
+## enable debugging
+#debug.log-request-header = "enable"
+#debug.log-response-header = "enable"
+#debug.log-request-handling = "enable"
+#debug.log-file-not-found = "enable"
+
+#### SSL engine
+#ssl.engine = "enable"
+#ssl.pemfile = "server.pem"
+
+# Rewrite rule for utf-8 path test (LayoutTests/http/tests/uri/utf8-path.html)
+# See the apache rewrite rule at LayoutTests/http/tests/uri/intercept/.htaccess
+# Rewrite rule for LayoutTests/http/tests/appcache/cyrillic-uri.html.
+# See the apache rewrite rule at
+# LayoutTests/http/tests/appcache/resources/intercept/.htaccess
+url.rewrite-once = (
+ "^/uri/intercept/(.*)" => "/uri/resources/print-uri.php",
+ "^/appcache/resources/intercept/(.*)" => "/appcache/resources/print-uri.php"
+)
+
+# LayoutTests/http/tests/xmlhttprequest/response-encoding.html uses an htaccess
+# to override charset for reply2.txt, reply2.xml, and reply4.txt.
+$HTTP["url"] =~ "^/xmlhttprequest/resources/reply2.(txt|xml)" {
+ mimetype.assign = (
+ ".txt" => "text/plain; charset=windows-1251",
+ ".xml" => "text/xml; charset=windows-1251"
+ )
+}
+$HTTP["url"] =~ "^/xmlhttprequest/resources/reply4.txt" {
+ mimetype.assign = ( ".txt" => "text/plain; charset=koi8-r" )
+}
+
+# LayoutTests/http/tests/appcache/wrong-content-type.html uses an htaccess
+# to override mime type for wrong-content-type.manifest.
+$HTTP["url"] =~ "^/appcache/resources/wrong-content-type.manifest" {
+ mimetype.assign = ( ".manifest" => "text/plain" )
+}
+
+# Autogenerated test-specific config follows.
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/mac.py b/Tools/Scripts/webkitpy/layout_tests/port/mac.py
new file mode 100644
index 0000000..696e339
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/mac.py
@@ -0,0 +1,152 @@
+# 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.
+
+"""WebKit Mac implementation of the Port interface."""
+
+import logging
+import os
+import platform
+import signal
+
+import webkitpy.common.system.ospath as ospath
+import webkitpy.layout_tests.port.server_process as server_process
+from webkitpy.layout_tests.port.webkit import WebKitPort, WebKitDriver
+
+_log = logging.getLogger("webkitpy.layout_tests.port.mac")
+
+
+class MacPort(WebKitPort):
+ """WebKit Mac implementation of the Port class."""
+
+ def __init__(self, **kwargs):
+ kwargs.setdefault('port_name', 'mac' + self.version())
+ WebKitPort.__init__(self, **kwargs)
+
+ def default_child_processes(self):
+ # FIXME: new-run-webkit-tests is unstable on Mac running more than
+ # four threads in parallel.
+ # See https://bugs.webkit.org/show_bug.cgi?id=36622
+ child_processes = WebKitPort.default_child_processes(self)
+ if child_processes > 4:
+ return 4
+ return child_processes
+
+ def baseline_search_path(self):
+ port_names = []
+ if self._name == 'mac-tiger':
+ port_names.append("mac-tiger")
+ if self._name in ('mac-tiger', 'mac-leopard'):
+ port_names.append("mac-leopard")
+ if self._name in ('mac-tiger', 'mac-leopard', 'mac-snowleopard'):
+ port_names.append("mac-snowleopard")
+ port_names.append("mac")
+ return map(self._webkit_baseline_path, port_names)
+
+ def path_to_test_expectations_file(self):
+ return self.path_from_webkit_base('LayoutTests', 'platform',
+ 'mac', 'test_expectations.txt')
+
+ def _skipped_file_paths(self):
+ # FIXME: This method will need to be made work for non-mac
+ # platforms and moved into base.Port.
+ skipped_files = []
+ if self._name in ('mac-tiger', 'mac-leopard', 'mac-snowleopard'):
+ skipped_files.append(os.path.join(
+ self._webkit_baseline_path(self._name), 'Skipped'))
+ skipped_files.append(os.path.join(self._webkit_baseline_path('mac'),
+ 'Skipped'))
+ return skipped_files
+
+ def test_platform_name(self):
+ return 'mac' + self.version()
+
+ def version(self):
+ os_version_string = platform.mac_ver()[0] # e.g. "10.5.6"
+ if not os_version_string:
+ return '-leopard'
+ release_version = int(os_version_string.split('.')[1])
+ if release_version == 4:
+ return '-tiger'
+ elif release_version == 5:
+ return '-leopard'
+ elif release_version == 6:
+ return '-snowleopard'
+ return ''
+
+ def _build_java_test_support(self):
+ java_tests_path = os.path.join(self.layout_tests_dir(), "java")
+ build_java = ["/usr/bin/make", "-C", java_tests_path]
+ if self._executive.run_command(build_java, return_exit_code=True):
+ _log.error("Failed to build Java support files: %s" % build_java)
+ return False
+ return True
+
+ def _check_port_build(self):
+ return self._build_java_test_support()
+
+ def _tests_for_other_platforms(self):
+ # The original run-webkit-tests builds up a "whitelist" of tests to
+ # run, and passes that to DumpRenderTree. new-run-webkit-tests assumes
+ # we run *all* tests and test_expectations.txt functions as a
+ # blacklist.
+ # FIXME: This list could be dynamic based on platform name and
+ # pushed into base.Port.
+ return [
+ "platform/chromium",
+ "platform/gtk",
+ "platform/qt",
+ "platform/win",
+ ]
+
+ def _path_to_apache_config_file(self):
+ return os.path.join(self.layout_tests_dir(), 'http', 'conf',
+ 'apache2-httpd.conf')
+
+ # FIXME: This doesn't have anything to do with WebKit.
+ def _shut_down_http_server(self, server_pid):
+ """Shut down the lighttpd web server. Blocks until it's fully
+ shut down.
+
+ Args:
+ server_pid: The process ID of the running server.
+ """
+ # server_pid is not set when "http_server.py stop" is run manually.
+ if server_pid is None:
+ # FIXME: This isn't ideal, since it could conflict with
+ # lighttpd processes not started by http_server.py,
+ # but good enough for now.
+ self._executive.kill_all('httpd')
+ else:
+ try:
+ os.kill(server_pid, signal.SIGTERM)
+ # FIXME: Maybe throw in a SIGKILL just to be sure?
+ except OSError:
+ # Sometimes we get a bad PID (e.g. from a stale httpd.pid
+ # file), so if kill fails on the given PID, just try to
+ # 'killall' web servers.
+ self._shut_down_http_server(None)
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/mac_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/mac_unittest.py
new file mode 100644
index 0000000..d383a4c
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/mac_unittest.py
@@ -0,0 +1,81 @@
+# 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 name of Google Inc. 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.
+
+import StringIO
+import sys
+import unittest
+
+import mac
+import port_testcase
+
+
+class MacTest(port_testcase.PortTestCase):
+ def make_port(self, options=port_testcase.mock_options):
+ if sys.platform != 'darwin':
+ return None
+ port_obj = mac.MacPort(options=options)
+ port_obj._options.results_directory = port_obj.results_directory()
+ port_obj._options.configuration = 'Release'
+ return port_obj
+
+ def test_skipped_file_paths(self):
+ port = self.make_port()
+ if not port:
+ return
+ skipped_paths = port._skipped_file_paths()
+ # FIXME: _skipped_file_paths should return WebKit-relative paths.
+ # So to make it unit testable, we strip the WebKit directory from the path.
+ relative_paths = [path[len(port.path_from_webkit_base()):] for path in skipped_paths]
+ self.assertEqual(relative_paths, ['LayoutTests/platform/mac-leopard/Skipped', 'LayoutTests/platform/mac/Skipped'])
+
+ example_skipped_file = u"""
+# <rdar://problem/5647952> fast/events/mouseout-on-window.html needs mac DRT to issue mouse out events
+fast/events/mouseout-on-window.html
+
+# <rdar://problem/5643675> window.scrollTo scrolls a window with no scrollbars
+fast/events/attempt-scroll-with-no-scrollbars.html
+
+# see bug <rdar://problem/5646437> REGRESSION (r28015): svg/batik/text/smallFonts fails
+svg/batik/text/smallFonts.svg
+"""
+ example_skipped_tests = [
+ "fast/events/mouseout-on-window.html",
+ "fast/events/attempt-scroll-with-no-scrollbars.html",
+ "svg/batik/text/smallFonts.svg",
+ ]
+
+ def test_skipped_file_paths(self):
+ port = self.make_port()
+ if not port:
+ return
+ skipped_file = StringIO.StringIO(self.example_skipped_file)
+ self.assertEqual(port._tests_from_skipped_file(skipped_file), self.example_skipped_tests)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py b/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py
new file mode 100644
index 0000000..c4b36ac
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py
@@ -0,0 +1,97 @@
+# 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 name of Google Inc. 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.
+
+"""Unit testing base class for Port implementations."""
+
+import os
+import tempfile
+import unittest
+
+from webkitpy.tool import mocktool
+mock_options = mocktool.MockOptions(results_directory='layout-test-results',
+ use_apache=True,
+ configuration='Release')
+
+# FIXME: This should be used for all ports, not just WebKit Mac. See
+# https://bugs.webkit.org/show_bug.cgi?id=50043 .
+
+class PortTestCase(unittest.TestCase):
+ """Tests the WebKit port implementation."""
+ def make_port(self, options=mock_options):
+ """Override in subclass."""
+ raise NotImplementedError()
+
+ def test_driver_cmd_line(self):
+ port = self.make_port()
+ if not port:
+ return
+ self.assertTrue(len(port.driver_cmd_line()))
+
+ def test_http_server(self):
+ port = self.make_port()
+ if not port:
+ return
+ port.start_http_server()
+ port.stop_http_server()
+
+ def test_image_diff(self):
+ port = self.make_port()
+ if not port:
+ return
+
+ # FIXME: not sure why this shouldn't always be True
+ #self.assertTrue(port.check_image_diff())
+ if not port.check_image_diff():
+ return
+
+ dir = port.layout_tests_dir()
+ file1 = os.path.join(dir, 'fast', 'css', 'button_center.png')
+ fh1 = file(file1)
+ contents1 = fh1.read()
+ file2 = os.path.join(dir, 'fast', 'css',
+ 'remove-shorthand-expected.png')
+ fh2 = file(file2)
+ contents2 = fh2.read()
+ tmpfile = tempfile.mktemp()
+
+ self.assertFalse(port.diff_image(contents1, contents1))
+ self.assertTrue(port.diff_image(contents1, contents2))
+
+ self.assertTrue(port.diff_image(contents1, contents2, tmpfile))
+ fh1.close()
+ fh2.close()
+ # FIXME: this may not be being written?
+ # self.assertTrue(os.path.exists(tmpfile))
+ # os.remove(tmpfile)
+
+ def test_websocket_server(self):
+ port = self.make_port()
+ if not port:
+ return
+ port.start_websocket_server()
+ port.stop_websocket_server()
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/qt.py b/Tools/Scripts/webkitpy/layout_tests/port/qt.py
new file mode 100644
index 0000000..af94acc
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/qt.py
@@ -0,0 +1,119 @@
+# 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.
+
+"""QtWebKit implementation of the Port interface."""
+
+import logging
+import os
+import signal
+import sys
+
+import webkit
+
+from webkitpy.layout_tests.port.webkit import WebKitPort
+
+_log = logging.getLogger("webkitpy.layout_tests.port.qt")
+
+
+class QtPort(WebKitPort):
+ """QtWebKit implementation of the Port class."""
+
+ def __init__(self, **kwargs):
+ kwargs.setdefault('port_name', 'qt')
+ WebKitPort.__init__(self, **kwargs)
+
+ def baseline_search_path(self):
+ port_names = []
+ if sys.platform == 'linux2':
+ port_names.append("qt-linux")
+ elif sys.platform in ('win32', 'cygwin'):
+ port_names.append("qt-win")
+ elif sys.platform == 'darwin':
+ port_names.append("qt-mac")
+ port_names.append("qt")
+ return map(self._webkit_baseline_path, port_names)
+
+ def _tests_for_other_platforms(self):
+ # FIXME: This list could be dynamic based on platform name and
+ # pushed into base.Port.
+ # This really need to be automated.
+ return [
+ "platform/chromium",
+ "platform/win",
+ "platform/gtk",
+ "platform/mac",
+ ]
+
+ def _path_to_apache_config_file(self):
+ # FIXME: This needs to detect the distribution and change config files.
+ return os.path.join(self.layout_tests_dir(), 'http', 'conf',
+ 'apache2-debian-httpd.conf')
+
+ def _shut_down_http_server(self, server_pid):
+ """Shut down the httpd web server. Blocks until it's fully
+ shut down.
+
+ Args:
+ server_pid: The process ID of the running server.
+ """
+ # server_pid is not set when "http_server.py stop" is run manually.
+ if server_pid is None:
+ # FIXME: This isn't ideal, since it could conflict with
+ # lighttpd processes not started by http_server.py,
+ # but good enough for now.
+ self._executive.kill_all('apache2')
+ else:
+ try:
+ os.kill(server_pid, signal.SIGTERM)
+ # TODO(mmoss) Maybe throw in a SIGKILL just to be sure?
+ except OSError:
+ # Sometimes we get a bad PID (e.g. from a stale httpd.pid
+ # file), so if kill fails on the given PID, just try to
+ # 'killall' web servers.
+ self._shut_down_http_server(None)
+
+ def _build_driver(self):
+ # The Qt port builds DRT as part of the main build step
+ return True
+
+ def _path_to_driver(self):
+ return self._build_path('bin/DumpRenderTree')
+
+ def _path_to_image_diff(self):
+ return self._build_path('bin/ImageDiff')
+
+ def _path_to_webcore_library(self):
+ return self._build_path('lib/libQtWebKit.so')
+
+ def _runtime_feature_list(self):
+ return None
+
+ def setup_environ_for_server(self):
+ env = webkit.WebKitPort.setup_environ_for_server(self)
+ env['QTWEBKIT_PLUGIN_PATH'] = self._build_path('lib/plugins')
+ return env
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/server_process.py b/Tools/Scripts/webkitpy/layout_tests/port/server_process.py
new file mode 100644
index 0000000..5a0a40c
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/server_process.py
@@ -0,0 +1,225 @@
+#!/usr/bin/env python
+# 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.
+
+"""Package that implements the ServerProcess wrapper class"""
+
+import logging
+import os
+import select
+import signal
+import subprocess
+import sys
+import time
+if sys.platform != 'win32':
+ import fcntl
+
+from webkitpy.common.system.executive import Executive
+
+_log = logging.getLogger("webkitpy.layout_tests.port.server_process")
+
+
+class ServerProcess:
+ """This class provides a wrapper around a subprocess that
+ implements a simple request/response usage model. The primary benefit
+ is that reading responses takes a timeout, so that we don't ever block
+ indefinitely. The class also handles transparently restarting processes
+ as necessary to keep issuing commands."""
+
+ def __init__(self, port_obj, name, cmd, env=None, executive=Executive()):
+ self._port = port_obj
+ self._name = name
+ self._cmd = cmd
+ self._env = env
+ self._reset()
+ self._executive = executive
+
+ def _reset(self):
+ self._proc = None
+ self._output = ''
+ self.crashed = False
+ self.timed_out = False
+ self.error = ''
+
+ def _start(self):
+ if self._proc:
+ raise ValueError("%s already running" % self._name)
+ self._reset()
+ # close_fds is a workaround for http://bugs.python.org/issue2320
+ close_fds = sys.platform not in ('win32', 'cygwin')
+ self._proc = subprocess.Popen(self._cmd, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ close_fds=close_fds,
+ env=self._env)
+ fd = self._proc.stdout.fileno()
+ fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+ fd = self._proc.stderr.fileno()
+ fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+
+ def handle_interrupt(self):
+ """This routine checks to see if the process crashed or exited
+ because of a keyboard interrupt and raises KeyboardInterrupt
+ accordingly."""
+ if self.crashed:
+ # This is hex code 0xc000001d, which is used for abrupt
+ # termination. This happens if we hit ctrl+c from the prompt
+ # and we happen to be waiting on the DumpRenderTree.
+ # sdoyon: Not sure for which OS and in what circumstances the
+ # above code is valid. What works for me under Linux to detect
+ # ctrl+c is for the subprocess returncode to be negative
+ # SIGINT. And that agrees with the subprocess documentation.
+ if (-1073741510 == self._proc.returncode or
+ - signal.SIGINT == self._proc.returncode):
+ raise KeyboardInterrupt
+ return
+
+ def poll(self):
+ """Check to see if the underlying process is running; returns None
+ if it still is (wrapper around subprocess.poll)."""
+ if self._proc:
+ # poll() is not threadsafe and can throw OSError due to:
+ # http://bugs.python.org/issue1731717
+ return self._proc.poll()
+ return None
+
+ def write(self, input):
+ """Write a request to the subprocess. The subprocess is (re-)start()'ed
+ if is not already running."""
+ if not self._proc:
+ self._start()
+ self._proc.stdin.write(input)
+
+ def read_line(self, timeout):
+ """Read a single line from the subprocess, waiting until the deadline.
+ If the deadline passes, the call times out. Note that even if the
+ subprocess has crashed or the deadline has passed, if there is output
+ pending, it will be returned.
+
+ Args:
+ timeout: floating-point number of seconds the call is allowed
+ to block for. A zero or negative number will attempt to read
+ any existing data, but will not block. There is no way to
+ block indefinitely.
+ Returns:
+ output: data returned, if any. If no data is available and the
+ call times out or crashes, an empty string is returned. Note
+ that the returned string includes the newline ('\n')."""
+ return self._read(timeout, size=0)
+
+ def read(self, timeout, size):
+ """Attempts to read size characters from the subprocess, waiting until
+ the deadline passes. If the deadline passes, any available data will be
+ returned. Note that even if the deadline has passed or if the
+ subprocess has crashed, any available data will still be returned.
+
+ Args:
+ timeout: floating-point number of seconds the call is allowed
+ to block for. A zero or negative number will attempt to read
+ any existing data, but will not block. There is no way to
+ block indefinitely.
+ size: amount of data to read. Must be a postive integer.
+ Returns:
+ output: data returned, if any. If no data is available, an empty
+ string is returned.
+ """
+ if size <= 0:
+ raise ValueError('ServerProcess.read() called with a '
+ 'non-positive size: %d ' % size)
+ return self._read(timeout, size)
+
+ def _read(self, timeout, size):
+ """Internal routine that actually does the read."""
+ index = -1
+ out_fd = self._proc.stdout.fileno()
+ err_fd = self._proc.stderr.fileno()
+ select_fds = (out_fd, err_fd)
+ deadline = time.time() + timeout
+ while not self.timed_out and not self.crashed:
+ # poll() is not threadsafe and can throw OSError due to:
+ # http://bugs.python.org/issue1731717
+ if self._proc.poll() != None:
+ self.crashed = True
+ self.handle_interrupt()
+
+ now = time.time()
+ if now > deadline:
+ self.timed_out = True
+
+ # Check to see if we have any output we can return.
+ if size and len(self._output) >= size:
+ index = size
+ elif size == 0:
+ index = self._output.find('\n') + 1
+
+ if index > 0 or self.crashed or self.timed_out:
+ output = self._output[0:index]
+ self._output = self._output[index:]
+ return output
+
+ # Nope - wait for more data.
+ (read_fds, write_fds, err_fds) = select.select(select_fds, [],
+ select_fds,
+ deadline - now)
+ try:
+ if out_fd in read_fds:
+ self._output += self._proc.stdout.read()
+ if err_fd in read_fds:
+ self.error += self._proc.stderr.read()
+ except IOError, e:
+ pass
+
+ def stop(self):
+ """Stop (shut down) the subprocess), if it is running."""
+ pid = self._proc.pid
+ self._proc.stdin.close()
+ self._proc.stdout.close()
+ if self._proc.stderr:
+ self._proc.stderr.close()
+ if sys.platform not in ('win32', 'cygwin'):
+ # Closing stdin/stdout/stderr hangs sometimes on OS X,
+ # (see restart(), above), and anyway we don't want to hang
+ # the harness if DumpRenderTree is buggy, so we wait a couple
+ # seconds to give DumpRenderTree a chance to clean up, but then
+ # force-kill the process if necessary.
+ KILL_TIMEOUT = 3.0
+ timeout = time.time() + KILL_TIMEOUT
+ # poll() is not threadsafe and can throw OSError due to:
+ # http://bugs.python.org/issue1731717
+ while self._proc.poll() is None and time.time() < timeout:
+ time.sleep(0.1)
+ # poll() is not threadsafe and can throw OSError due to:
+ # http://bugs.python.org/issue1731717
+ if self._proc.poll() is None:
+ _log.warning('stopping %s timed out, killing it' %
+ self._name)
+ self._executive.kill_process(self._proc.pid)
+ _log.warning('killed')
+ self._reset()
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/test.py b/Tools/Scripts/webkitpy/layout_tests/port/test.py
new file mode 100644
index 0000000..935881c
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/test.py
@@ -0,0 +1,343 @@
+#!/usr/bin/env python
+# 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.
+
+"""Dummy Port implementation used for testing."""
+from __future__ import with_statement
+
+import codecs
+import fnmatch
+import os
+import sys
+import time
+
+from webkitpy.layout_tests.layout_package import test_output
+
+import base
+
+
+# This sets basic expectations for a test. Each individual expectation
+# can be overridden by a keyword argument in TestList.add().
+class TestInstance:
+ def __init__(self, name):
+ self.name = name
+ self.base = name[(name.rfind("/") + 1):name.rfind(".html")]
+ self.crash = False
+ self.exception = False
+ self.hang = False
+ self.keyboard = False
+ self.error = ''
+ self.timeout = False
+ self.actual_text = self.base + '-txt\n'
+ self.actual_checksum = self.base + '-checksum\n'
+ self.actual_image = self.base + '-png\n'
+ self.expected_text = self.actual_text
+ self.expected_checksum = self.actual_checksum
+ self.expected_image = self.actual_image
+
+
+# This is an in-memory list of tests, what we want them to produce, and
+# what we want to claim are the expected results.
+class TestList:
+ def __init__(self, port):
+ self.port = port
+ self.tests = {}
+
+ def add(self, name, **kwargs):
+ test = TestInstance(name)
+ for key, value in kwargs.items():
+ test.__dict__[key] = value
+ self.tests[name] = test
+
+ def keys(self):
+ return self.tests.keys()
+
+ def __contains__(self, item):
+ return item in self.tests
+
+ def __getitem__(self, item):
+ return self.tests[item]
+
+
+class TestPort(base.Port):
+ """Test implementation of the Port interface."""
+
+ def __init__(self, **kwargs):
+ base.Port.__init__(self, **kwargs)
+ tests = TestList(self)
+ tests.add('failures/expected/checksum.html',
+ actual_checksum='checksum_fail-checksum')
+ tests.add('failures/expected/crash.html', crash=True)
+ tests.add('failures/expected/exception.html', exception=True)
+ tests.add('failures/expected/timeout.html', timeout=True)
+ tests.add('failures/expected/hang.html', hang=True)
+ tests.add('failures/expected/missing_text.html',
+ expected_text=None)
+ tests.add('failures/expected/image.html',
+ actual_image='image_fail-png',
+ expected_image='image-png')
+ tests.add('failures/expected/image_checksum.html',
+ actual_checksum='image_checksum_fail-checksum',
+ actual_image='image_checksum_fail-png')
+ tests.add('failures/expected/keyboard.html',
+ keyboard=True)
+ tests.add('failures/expected/missing_check.html',
+ expected_checksum=None)
+ tests.add('failures/expected/missing_image.html',
+ expected_image=None)
+ tests.add('failures/expected/missing_text.html',
+ expected_text=None)
+ tests.add('failures/expected/newlines_leading.html',
+ expected_text="\nfoo\n",
+ actual_text="foo\n")
+ tests.add('failures/expected/newlines_trailing.html',
+ expected_text="foo\n\n",
+ actual_text="foo\n")
+ tests.add('failures/expected/newlines_with_excess_CR.html',
+ expected_text="foo\r\r\r\n",
+ actual_text="foo\n")
+ tests.add('failures/expected/text.html',
+ actual_text='text_fail-png')
+ tests.add('failures/unexpected/crash.html', crash=True)
+ tests.add('failures/unexpected/text-image-checksum.html',
+ actual_text='text-image-checksum_fail-txt',
+ actual_checksum='text-image-checksum_fail-checksum')
+ tests.add('failures/unexpected/timeout.html', timeout=True)
+ tests.add('http/tests/passes/text.html')
+ tests.add('http/tests/ssl/text.html')
+ tests.add('passes/error.html', error='stuff going to stderr')
+ tests.add('passes/image.html')
+ tests.add('passes/platform_image.html')
+ # Text output files contain "\r\n" on Windows. This may be
+ # helpfully filtered to "\r\r\n" by our Python/Cygwin tooling.
+ tests.add('passes/text.html',
+ expected_text='\nfoo\n\n',
+ actual_text='\nfoo\r\n\r\r\n')
+ tests.add('websocket/tests/passes/text.html')
+ self._tests = tests
+
+ def baseline_path(self):
+ return os.path.join(self.layout_tests_dir(), 'platform',
+ self.name() + self.version())
+
+ def baseline_search_path(self):
+ return [self.baseline_path()]
+
+ def check_build(self, needs_http):
+ return True
+
+ def diff_image(self, expected_contents, actual_contents,
+ diff_filename=None):
+ diffed = actual_contents != expected_contents
+ if diffed and diff_filename:
+ with codecs.open(diff_filename, "w", "utf-8") as diff_fh:
+ diff_fh.write("< %s\n---\n> %s\n" %
+ (expected_contents, actual_contents))
+ return diffed
+
+ def expected_checksum(self, test):
+ test = self.relative_test_filename(test)
+ return self._tests[test].expected_checksum
+
+ def expected_image(self, test):
+ test = self.relative_test_filename(test)
+ return self._tests[test].expected_image
+
+ def expected_text(self, test):
+ test = self.relative_test_filename(test)
+ text = self._tests[test].expected_text
+ if not text:
+ text = ''
+ return text
+
+ def tests(self, paths):
+ # Test the idea of port-specific overrides for test lists. Also
+ # keep in memory to speed up the test harness.
+ if not paths:
+ paths = ['*']
+
+ matched_tests = []
+ for p in paths:
+ if self.path_isdir(p):
+ matched_tests.extend(fnmatch.filter(self._tests.keys(), p + '*'))
+ else:
+ matched_tests.extend(fnmatch.filter(self._tests.keys(), p))
+ layout_tests_dir = self.layout_tests_dir()
+ return set([os.path.join(layout_tests_dir, p) for p in matched_tests])
+
+ def path_exists(self, path):
+ # used by test_expectations.py and printing.py
+ rpath = self.relative_test_filename(path)
+ if rpath in self._tests:
+ return True
+ if self.path_isdir(rpath):
+ return True
+ if rpath.endswith('-expected.txt'):
+ test = rpath.replace('-expected.txt', '.html')
+ return (test in self._tests and
+ self._tests[test].expected_text)
+ if rpath.endswith('-expected.checksum'):
+ test = rpath.replace('-expected.checksum', '.html')
+ return (test in self._tests and
+ self._tests[test].expected_checksum)
+ if rpath.endswith('-expected.png'):
+ test = rpath.replace('-expected.png', '.html')
+ return (test in self._tests and
+ self._tests[test].expected_image)
+ return False
+
+ def layout_tests_dir(self):
+ return self.path_from_webkit_base('Tools', 'Scripts',
+ 'webkitpy', 'layout_tests', 'data')
+
+ def path_isdir(self, path):
+ # Used by test_expectations.py
+ #
+ # We assume that a path is a directory if we have any tests
+ # that whose prefix matches the path plus a directory modifier
+ # and not a file extension.
+ if path[-1] != '/':
+ path += '/'
+
+ # FIXME: Directories can have a dot in the name. We should
+ # probably maintain a white list of known cases like CSS2.1
+ # and check it here in the future.
+ if path.find('.') != -1:
+ # extension separator found, assume this is a file
+ return False
+
+ # strip out layout tests directory path if found. The tests
+ # keys are relative to it.
+ tests_dir = self.layout_tests_dir()
+ if path.startswith(tests_dir):
+ path = path[len(tests_dir) + 1:]
+
+ return any([t.startswith(path) for t in self._tests.keys()])
+
+ def test_dirs(self):
+ return ['passes', 'failures']
+
+ def name(self):
+ return self._name
+
+ def _path_to_wdiff(self):
+ return None
+
+ def results_directory(self):
+ return '/tmp/' + self.get_option('results_directory')
+
+ def setup_test_run(self):
+ pass
+
+ def create_driver(self, worker_number):
+ return TestDriver(self, worker_number)
+
+ def start_http_server(self):
+ pass
+
+ def start_websocket_server(self):
+ pass
+
+ def stop_http_server(self):
+ pass
+
+ def stop_websocket_server(self):
+ pass
+
+ def test_expectations(self):
+ """Returns the test expectations for this port.
+
+ Basically this string should contain the equivalent of a
+ test_expectations file. See test_expectations.py for more details."""
+ return """
+WONTFIX : failures/expected/checksum.html = IMAGE
+WONTFIX : failures/expected/crash.html = CRASH
+// This one actually passes because the checksums will match.
+WONTFIX : failures/expected/image.html = PASS
+WONTFIX : failures/expected/image_checksum.html = IMAGE
+WONTFIX : failures/expected/missing_check.html = MISSING PASS
+WONTFIX : failures/expected/missing_image.html = MISSING PASS
+WONTFIX : failures/expected/missing_text.html = MISSING PASS
+WONTFIX : failures/expected/newlines_leading.html = TEXT
+WONTFIX : failures/expected/newlines_trailing.html = TEXT
+WONTFIX : failures/expected/newlines_with_excess_CR.html = TEXT
+WONTFIX : failures/expected/text.html = TEXT
+WONTFIX : failures/expected/timeout.html = TIMEOUT
+WONTFIX SKIP : failures/expected/hang.html = TIMEOUT
+WONTFIX SKIP : failures/expected/keyboard.html = CRASH
+WONTFIX SKIP : failures/expected/exception.html = CRASH
+"""
+
+ def test_base_platform_names(self):
+ return ('mac', 'win')
+
+ def test_platform_name(self):
+ return 'mac'
+
+ def test_platform_names(self):
+ return self.test_base_platform_names()
+
+ def test_platform_name_to_name(self, test_platform_name):
+ return test_platform_name
+
+ def version(self):
+ return ''
+
+
+class TestDriver(base.Driver):
+ """Test/Dummy implementation of the DumpRenderTree interface."""
+
+ def __init__(self, port, worker_number):
+ self._port = port
+
+ def cmd_line(self):
+ return ['None']
+
+ def poll(self):
+ return True
+
+ def run_test(self, test_input):
+ start_time = time.time()
+ test_name = self._port.relative_test_filename(test_input.filename)
+ test = self._port._tests[test_name]
+ if test.keyboard:
+ raise KeyboardInterrupt
+ if test.exception:
+ raise ValueError('exception from ' + test_name)
+ if test.hang:
+ time.sleep((float(test_input.timeout) * 4) / 1000.0)
+ return test_output.TestOutput(test.actual_text, test.actual_image,
+ test.actual_checksum, test.crash,
+ time.time() - start_time, test.timeout,
+ test.error)
+
+ def start(self):
+ pass
+
+ def stop(self):
+ pass
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/test_files.py b/Tools/Scripts/webkitpy/layout_tests/port/test_files.py
new file mode 100644
index 0000000..2c0a7b6
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/test_files.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python
+# 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 name of Google Inc. 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.
+
+"""This module is used to find all of the layout test files used by
+run-webkit-tests. It exposes one public function - find() -
+which takes an optional list of paths. If a list is passed in, the returned
+list of test files is constrained to those found under the paths passed in,
+i.e. calling find(["LayoutTests/fast"]) will only return files
+under that directory."""
+
+import glob
+import os
+import time
+
+from webkitpy.common.system import logutils
+
+
+_log = logutils.get_logger(__file__)
+
+
+# When collecting test cases, we include any file with these extensions.
+_supported_file_extensions = set(['.html', '.shtml', '.xml', '.xhtml', '.xhtmlmp', '.pl',
+ '.php', '.svg'])
+# When collecting test cases, skip these directories
+_skipped_directories = set(['.svn', '_svn', 'resources', 'script-tests'])
+
+
+def find(port, paths):
+ """Finds the set of tests under port.layout_tests_dir().
+
+ Args:
+ paths: a list of command line paths relative to the layout_tests_dir()
+ to limit the search to. glob patterns are ok.
+ """
+ gather_start_time = time.time()
+ paths_to_walk = set()
+ # if paths is empty, provide a pre-defined list.
+ if paths:
+ _log.debug("Gathering tests from: %s relative to %s" % (paths, port.layout_tests_dir()))
+ for path in paths:
+ # If there's an * in the name, assume it's a glob pattern.
+ path = os.path.join(port.layout_tests_dir(), path)
+ if path.find('*') > -1:
+ filenames = glob.glob(path)
+ paths_to_walk.update(filenames)
+ else:
+ paths_to_walk.add(path)
+ else:
+ _log.debug("Gathering tests from: %s" % port.layout_tests_dir())
+ paths_to_walk.add(port.layout_tests_dir())
+
+ # Now walk all the paths passed in on the command line and get filenames
+ test_files = set()
+ for path in paths_to_walk:
+ if os.path.isfile(path) and _is_test_file(path):
+ test_files.add(os.path.normpath(path))
+ continue
+
+ for root, dirs, files in os.walk(path):
+ # Don't walk skipped directories or their sub-directories.
+ if os.path.basename(root) in _skipped_directories:
+ del dirs[:]
+ continue
+ # This copy and for-in is slightly inefficient, but
+ # the extra walk avoidance consistently shaves .5 seconds
+ # off of total walk() time on my MacBook Pro.
+ for directory in dirs[:]:
+ if directory in _skipped_directories:
+ dirs.remove(directory)
+
+ for filename in files:
+ if _is_test_file(filename):
+ filename = os.path.join(root, filename)
+ filename = os.path.normpath(filename)
+ test_files.add(filename)
+
+ gather_time = time.time() - gather_start_time
+ _log.debug("Test gathering took %f seconds" % gather_time)
+
+ return test_files
+
+
+def _has_supported_extension(filename):
+ """Return true if filename is one of the file extensions we want to run a
+ test on."""
+ extension = os.path.splitext(filename)[1]
+ return extension in _supported_file_extensions
+
+
+def _is_reference_html_file(filename):
+ """Return true if the filename points to a reference HTML file."""
+ if (filename.endswith('-expected.html') or
+ filename.endswith('-expected-mismatch.html')):
+ _log.warn("Reftests are not supported - ignoring %s" % filename)
+ return True
+ return False
+
+
+def _is_test_file(filename):
+ """Return true if the filename points to a test file."""
+ return (_has_supported_extension(filename) and
+ not _is_reference_html_file(filename))
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/test_files_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/test_files_unittest.py
new file mode 100644
index 0000000..83525c8
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/test_files_unittest.py
@@ -0,0 +1,75 @@
+# 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 name of Google Inc. 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.
+
+import os
+import unittest
+
+import base
+import test_files
+
+
+class TestFilesTest(unittest.TestCase):
+ def test_find_no_paths_specified(self):
+ port = base.Port()
+ layout_tests_dir = port.layout_tests_dir()
+ port.layout_tests_dir = lambda: os.path.join(layout_tests_dir,
+ 'fast', 'html')
+ tests = test_files.find(port, [])
+ self.assertNotEqual(tests, 0)
+
+ def test_find_one_test(self):
+ port = base.Port()
+ # This is just a test picked at random but known to exist.
+ tests = test_files.find(port, ['fast/html/keygen.html'])
+ self.assertEqual(len(tests), 1)
+
+ def test_find_glob(self):
+ port = base.Port()
+ tests = test_files.find(port, ['fast/html/key*'])
+ self.assertEqual(len(tests), 1)
+
+ def test_find_with_skipped_directories(self):
+ port = base.Port()
+ tests = port.tests('userscripts')
+ self.assertTrue('userscripts/resources/frame1.html' not in tests)
+
+ def test_find_with_skipped_directories_2(self):
+ port = base.Port()
+ tests = test_files.find(port, ['userscripts/resources'])
+ self.assertEqual(tests, set([]))
+
+ def test_is_test_file(self):
+ self.assertTrue(test_files._is_test_file('foo.html'))
+ self.assertTrue(test_files._is_test_file('foo.shtml'))
+ self.assertFalse(test_files._is_test_file('foo.png'))
+ self.assertFalse(test_files._is_test_file('foo-expected.html'))
+ self.assertFalse(test_files._is_test_file('foo-expected-mismatch.html'))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/webkit.py b/Tools/Scripts/webkitpy/layout_tests/port/webkit.py
new file mode 100644
index 0000000..afdebeb
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/webkit.py
@@ -0,0 +1,497 @@
+#!/usr/bin/env python
+# Copyright (C) 2010 Google Inc. All rights reserved.
+# Copyright (C) 2010 Gabor Rapcsanyi <rgabor@inf.u-szeged.hu>, University of Szeged
+#
+# 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.
+
+"""WebKit implementations of the Port interface."""
+
+
+from __future__ import with_statement
+
+import codecs
+import logging
+import os
+import re
+import shutil
+import signal
+import sys
+import time
+import webbrowser
+import operator
+import tempfile
+import shutil
+
+import webkitpy.common.system.ospath as ospath
+import webkitpy.layout_tests.layout_package.test_output as test_output
+import webkitpy.layout_tests.port.base as base
+import webkitpy.layout_tests.port.server_process as server_process
+
+_log = logging.getLogger("webkitpy.layout_tests.port.webkit")
+
+
+class WebKitPort(base.Port):
+ """WebKit implementation of the Port class."""
+
+ def __init__(self, **kwargs):
+ base.Port.__init__(self, **kwargs)
+ self._cached_apache_path = None
+
+ # FIXME: disable pixel tests until they are run by default on the
+ # build machines.
+ self.set_option_default('pixel_tests', False)
+
+ def baseline_path(self):
+ return self._webkit_baseline_path(self._name)
+
+ def baseline_search_path(self):
+ return [self._webkit_baseline_path(self._name)]
+
+ def path_to_test_expectations_file(self):
+ return os.path.join(self._webkit_baseline_path(self._name),
+ 'test_expectations.txt')
+
+ # Only needed by ports which maintain versioned test expectations (like mac-tiger vs. mac-leopard)
+ def version(self):
+ return ''
+
+ def _build_driver(self):
+ configuration = self.get_option('configuration')
+ return self._config.build_dumprendertree(configuration)
+
+ def _check_driver(self):
+ driver_path = self._path_to_driver()
+ if not os.path.exists(driver_path):
+ _log.error("DumpRenderTree was not found at %s" % driver_path)
+ return False
+ return True
+
+ def check_build(self, needs_http):
+ if 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_port_build(self):
+ # Ports can override this method to do additional checks.
+ return True
+
+ def check_image_diff(self, override_step=None, logging=True):
+ image_diff_path = self._path_to_image_diff()
+ if not os.path.exists(image_diff_path):
+ _log.error("ImageDiff was not found at %s" % image_diff_path)
+ return False
+ return True
+
+ def diff_image(self, expected_contents, actual_contents,
+ diff_filename=None):
+ """Return True if the two files are different. Also write a delta
+ image of the two images into |diff_filename| if it is not None."""
+
+ # Handle the case where the test didn't actually generate an image.
+ if not actual_contents:
+ return True
+
+ sp = self._diff_image_request(expected_contents, actual_contents)
+ return self._diff_image_reply(sp, diff_filename)
+
+ def _diff_image_request(self, expected_contents, actual_contents):
+ # FIXME: use self.get_option('tolerance') and
+ # self.set_option_default('tolerance', 0.1) once that behaves correctly
+ # with default values.
+ if self.get_option('tolerance') is not None:
+ tolerance = self.get_option('tolerance')
+ else:
+ tolerance = 0.1
+ command = [self._path_to_image_diff(), '--tolerance', str(tolerance)]
+ sp = server_process.ServerProcess(self, 'ImageDiff', command)
+
+ sp.write('Content-Length: %d\n%sContent-Length: %d\n%s' %
+ (len(actual_contents), actual_contents,
+ len(expected_contents), expected_contents))
+
+ return sp
+
+ def _diff_image_reply(self, sp, diff_filename):
+ timeout = 2.0
+ deadline = time.time() + timeout
+ output = sp.read_line(timeout)
+ while not sp.timed_out and not sp.crashed and output:
+ if output.startswith('Content-Length'):
+ m = re.match('Content-Length: (\d+)', output)
+ content_length = int(m.group(1))
+ timeout = deadline - time.time()
+ output = sp.read(timeout, content_length)
+ break
+ elif output.startswith('diff'):
+ break
+ else:
+ timeout = deadline - time.time()
+ output = sp.read_line(deadline)
+
+ result = True
+ if output.startswith('diff'):
+ m = re.match('diff: (.+)% (passed|failed)', output)
+ if m.group(2) == 'passed':
+ result = False
+ elif output and diff_filename:
+ with open(diff_filename, 'w') as file:
+ file.write(output)
+ elif sp.timed_out:
+ _log.error("ImageDiff timed out")
+ elif sp.crashed:
+ _log.error("ImageDiff crashed")
+ sp.stop()
+ return result
+
+ def results_directory(self):
+ # 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(self.get_option('results_directory'))
+
+ def setup_test_run(self):
+ # This port doesn't require any specific configuration.
+ pass
+
+ def create_driver(self, worker_number):
+ return WebKitDriver(self, worker_number)
+
+ def test_base_platform_names(self):
+ # At the moment we don't use test platform names, but we have
+ # to return something.
+ return ('mac', 'win')
+
+ def _tests_for_other_platforms(self):
+ raise NotImplementedError('WebKitPort._tests_for_other_platforms')
+ # The original run-webkit-tests builds up a "whitelist" of tests to
+ # run, and passes that to DumpRenderTree. new-run-webkit-tests assumes
+ # we run *all* tests and test_expectations.txt functions as a
+ # blacklist.
+ # FIXME: This list could be dynamic based on platform name and
+ # pushed into base.Port.
+ return [
+ "platform/chromium",
+ "platform/gtk",
+ "platform/qt",
+ "platform/win",
+ ]
+
+ def _runtime_feature_list(self):
+ """Return the supported features of DRT. If a port doesn't support
+ this DRT switch, it has to override this method to return None"""
+ driver_path = self._path_to_driver()
+ feature_list = ' '.join(os.popen(driver_path + " --print-supported-features 2>&1").readlines())
+ if "SupportedFeatures:" in feature_list:
+ return feature_list
+ return None
+
+ def _supported_symbol_list(self):
+ """Return the supported symbols of WebCore."""
+ webcore_library_path = self._path_to_webcore_library()
+ if not webcore_library_path:
+ return None
+ symbol_list = ' '.join(os.popen("nm " + webcore_library_path).readlines())
+ return symbol_list
+
+ def _directories_for_features(self):
+ """Return the supported feature dictionary. The keys are the
+ features and the values are the directories in lists."""
+ directories_for_features = {
+ "Accelerated Compositing": ["compositing"],
+ "3D Rendering": ["animations/3d", "transforms/3d"],
+ }
+ return directories_for_features
+
+ def _directories_for_symbols(self):
+ """Return the supported feature dictionary. The keys are the
+ symbols and the values are the directories in lists."""
+ directories_for_symbol = {
+ "MathMLElement": ["mathml"],
+ "GraphicsLayer": ["compositing"],
+ "WebCoreHas3DRendering": ["animations/3d", "transforms/3d"],
+ "WebGLShader": ["fast/canvas/webgl", "compositing/webgl", "http/tests/canvas/webgl"],
+ "WMLElement": ["http/tests/wml", "fast/wml", "wml"],
+ "parseWCSSInputProperty": ["fast/wcss"],
+ "isXHTMLMPDocument": ["fast/xhtmlmp"],
+ }
+ return directories_for_symbol
+
+ def _skipped_tests_for_unsupported_features(self):
+ """Return the directories of unsupported tests. Search for the
+ symbols in the symbol_list, if found add the corresponding
+ directories to the skipped directory list."""
+ feature_list = self._runtime_feature_list()
+ directories = self._directories_for_features()
+
+ # if DRT feature detection not supported
+ if not feature_list:
+ feature_list = self._supported_symbol_list()
+ directories = self._directories_for_symbols()
+
+ if not feature_list:
+ return []
+
+ skipped_directories = [directories[feature]
+ for feature in directories.keys()
+ if feature not in feature_list]
+ return reduce(operator.add, skipped_directories)
+
+ def _tests_for_disabled_features(self):
+ # FIXME: This should use the feature detection from
+ # webkitperl/features.pm to match run-webkit-tests.
+ # For now we hard-code a list of features known to be disabled on
+ # the Mac platform.
+ disabled_feature_tests = [
+ "fast/xhtmlmp",
+ "http/tests/wml",
+ "mathml",
+ "wml",
+ ]
+ # FIXME: webarchive tests expect to read-write from
+ # -expected.webarchive files instead of .txt files.
+ # This script doesn't know how to do that yet, so pretend they're
+ # just "disabled".
+ webarchive_tests = [
+ "webarchive",
+ "svg/webarchive",
+ "http/tests/webarchive",
+ "svg/custom/image-with-prefix-in-webarchive.svg",
+ ]
+ unsupported_feature_tests = self._skipped_tests_for_unsupported_features()
+ return disabled_feature_tests + webarchive_tests + unsupported_feature_tests
+
+ def _tests_from_skipped_file(self, skipped_file):
+ tests_to_skip = []
+ for line in skipped_file.readlines():
+ line = line.strip()
+ if line.startswith('#') or not len(line):
+ continue
+ tests_to_skip.append(line)
+ return tests_to_skip
+
+ def _skipped_file_paths(self):
+ return [os.path.join(self._webkit_baseline_path(self._name),
+ 'Skipped')]
+
+ def _expectations_from_skipped_files(self):
+ tests_to_skip = []
+ for filename in self._skipped_file_paths():
+ if not os.path.exists(filename):
+ _log.warn("Failed to open Skipped file: %s" % filename)
+ continue
+ with codecs.open(filename, "r", "utf-8") as skipped_file:
+ tests_to_skip.extend(self._tests_from_skipped_file(skipped_file))
+ return tests_to_skip
+
+ def test_expectations(self):
+ # The WebKit mac port uses a combination of a test_expectations file
+ # and 'Skipped' files.
+ expectations_path = self.path_to_test_expectations_file()
+ with codecs.open(expectations_path, "r", "utf-8") as file:
+ return file.read() + self._skips()
+
+ def _skips(self):
+ # Each Skipped file contains a list of files
+ # or directories to be skipped during the test run. The total list
+ # of tests to skipped is given by the contents of the generic
+ # Skipped file found in platform/X plus a version-specific file
+ # found in platform/X-version. Duplicate entries are allowed.
+ # This routine reads those files and turns contents into the
+ # format expected by test_expectations.
+
+ tests_to_skip = self.skipped_layout_tests()
+ skip_lines = map(lambda test_path: "BUG_SKIPPED SKIP : %s = FAIL" %
+ test_path, tests_to_skip)
+ return "\n".join(skip_lines)
+
+ def skipped_layout_tests(self):
+ # Use a set to allow duplicates
+ tests_to_skip = set(self._expectations_from_skipped_files())
+ tests_to_skip.update(self._tests_for_other_platforms())
+ tests_to_skip.update(self._tests_for_disabled_features())
+ return tests_to_skip
+
+ def test_platform_name(self):
+ return self._name + self.version()
+
+ def test_platform_names(self):
+ return self.test_base_platform_names() + (
+ 'mac-tiger', 'mac-leopard', 'mac-snowleopard')
+
+ def _build_path(self, *comps):
+ return self._filesystem.join(self._config.build_directory(
+ self.get_option('configuration')), *comps)
+
+ def _path_to_driver(self):
+ return self._build_path('DumpRenderTree')
+
+ def _path_to_webcore_library(self):
+ return None
+
+ def _path_to_helper(self):
+ return None
+
+ def _path_to_image_diff(self):
+ return self._build_path('ImageDiff')
+
+ def _path_to_wdiff(self):
+ # FIXME: This does not exist on a default Mac OS X Leopard install.
+ return 'wdiff'
+
+ def _path_to_apache(self):
+ if not self._cached_apache_path:
+ # 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 os.path.exists(path):
+ self._cached_apache_path = path
+ break
+
+ if not self._cached_apache_path:
+ _log.error("Could not find apache. Not installed or unknown path.")
+
+ return self._cached_apache_path
+
+
+class WebKitDriver(base.Driver):
+ """WebKit implementation of the DumpRenderTree interface."""
+
+ def __init__(self, port, worker_number):
+ self._worker_number = worker_number
+ self._port = port
+ self._driver_tempdir = tempfile.mkdtemp(prefix='DumpRenderTree-')
+
+ def __del__(self):
+ shutil.rmtree(self._driver_tempdir)
+
+ def cmd_line(self):
+ cmd = self._command_wrapper(self._port.get_option('wrapper'))
+ cmd += [self._port._path_to_driver(), '-']
+
+ if self._port.get_option('pixel_tests'):
+ cmd.append('--pixel-tests')
+
+ return cmd
+
+ def start(self):
+ environment = self._port.setup_environ_for_server()
+ environment['DYLD_FRAMEWORK_PATH'] = self._port._build_path()
+ environment['DUMPRENDERTREE_TEMP'] = self._driver_tempdir
+ self._server_process = server_process.ServerProcess(self._port,
+ "DumpRenderTree", self.cmd_line(), environment)
+
+ def poll(self):
+ return self._server_process.poll()
+
+ def restart(self):
+ self._server_process.stop()
+ self._server_process.start()
+ return
+
+ # FIXME: This function is huge.
+ def run_test(self, test_input):
+ uri = self._port.filename_to_uri(test_input.filename)
+ if uri.startswith("file:///"):
+ command = uri[7:]
+ else:
+ command = uri
+
+ if test_input.image_hash:
+ command += "'" + test_input.image_hash
+ command += "\n"
+
+ start_time = time.time()
+ self._server_process.write(command)
+
+ have_seen_content_type = False
+ actual_image_hash = None
+ output = str() # Use a byte array for output, even though it should be UTF-8.
+ image = str()
+
+ timeout = int(test_input.timeout) / 1000.0
+ deadline = time.time() + timeout
+ line = self._server_process.read_line(timeout)
+ while (not self._server_process.timed_out
+ and not self._server_process.crashed
+ and line.rstrip() != "#EOF"):
+ if (line.startswith('Content-Type:') and not
+ have_seen_content_type):
+ have_seen_content_type = True
+ else:
+ # Note: Text output from DumpRenderTree is always UTF-8.
+ # However, some tests (e.g. webarchives) spit out binary
+ # data instead of text. So to make things simple, we
+ # always treat the output as binary.
+ output += line
+ line = self._server_process.read_line(timeout)
+ timeout = deadline - time.time()
+
+ # Now read a second block of text for the optional image data
+ remaining_length = -1
+ HASH_HEADER = 'ActualHash: '
+ LENGTH_HEADER = 'Content-Length: '
+ line = self._server_process.read_line(timeout)
+ while (not self._server_process.timed_out
+ and not self._server_process.crashed
+ and line.rstrip() != "#EOF"):
+ if line.startswith(HASH_HEADER):
+ actual_image_hash = line[len(HASH_HEADER):].strip()
+ elif line.startswith('Content-Type:'):
+ pass
+ elif line.startswith(LENGTH_HEADER):
+ timeout = deadline - time.time()
+ content_length = int(line[len(LENGTH_HEADER):])
+ image = self._server_process.read(timeout, content_length)
+ timeout = deadline - time.time()
+ line = self._server_process.read_line(timeout)
+
+ error_lines = self._server_process.error.splitlines()
+ # FIXME: This is a hack. It is unclear why sometimes
+ # we do not get any error lines from the server_process
+ # probably we are not flushing stderr.
+ if error_lines and error_lines[-1] == "#EOF":
+ error_lines.pop() # Remove the expected "#EOF"
+ error = "\n".join(error_lines)
+ # FIXME: This seems like the wrong section of code to be doing
+ # this reset in.
+ self._server_process.error = ""
+ return test_output.TestOutput(output, image, actual_image_hash,
+ self._server_process.crashed,
+ time.time() - start_time,
+ self._server_process.timed_out,
+ error)
+
+ def stop(self):
+ if self._server_process:
+ self._server_process.stop()
+ self._server_process = None
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py b/Tools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py
new file mode 100644
index 0000000..7b68310
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+# Copyright (C) 2010 Gabor Rapcsanyi <rgabor@inf.u-szeged.hu>, University of Szeged
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. 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.
+#
+# THIS SOFTWARE IS PROVIDED BY UNIVERSITY OF SZEGED ``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 UNIVERSITY OF SZEGED 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.
+
+import unittest
+
+from webkitpy.layout_tests.port.webkit import WebKitPort
+
+
+class TestWebKitPort(WebKitPort):
+ def __init__(self, symbol_list=None, feature_list=None):
+ self.symbol_list = symbol_list
+ self.feature_list = feature_list
+
+ def _runtime_feature_list(self):
+ return self.feature_list
+
+ def _supported_symbol_list(self):
+ return self.symbol_list
+
+ def _tests_for_other_platforms(self):
+ return ["media", ]
+
+ def _tests_for_disabled_features(self):
+ return ["accessibility", ]
+
+ def _skipped_file_paths(self):
+ return []
+
+class WebKitPortTest(unittest.TestCase):
+
+ def test_skipped_directories_for_symbols(self):
+ supported_symbols = ["GraphicsLayer", "WebCoreHas3DRendering", "isXHTMLMPDocument", "fooSymbol"]
+ expected_directories = set(["mathml", "fast/canvas/webgl", "compositing/webgl", "http/tests/canvas/webgl", "http/tests/wml", "fast/wml", "wml", "fast/wcss"])
+ result_directories = set(TestWebKitPort(supported_symbols, None)._skipped_tests_for_unsupported_features())
+ self.assertEqual(result_directories, expected_directories)
+
+ def test_skipped_directories_for_features(self):
+ supported_features = ["Accelerated Compositing", "Foo Feature"]
+ expected_directories = set(["animations/3d", "transforms/3d"])
+ result_directories = set(TestWebKitPort(None, supported_features)._skipped_tests_for_unsupported_features())
+ self.assertEqual(result_directories, expected_directories)
+
+ def test_skipped_layout_tests(self):
+ self.assertEqual(TestWebKitPort(None, None).skipped_layout_tests(),
+ set(["media", "accessibility"]))
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/websocket_server.py b/Tools/Scripts/webkitpy/layout_tests/port/websocket_server.py
new file mode 100644
index 0000000..926bc04
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/websocket_server.py
@@ -0,0 +1,257 @@
+#!/usr/bin/env python
+# 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 name of Google Inc. 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.
+
+"""A class to help start/stop the PyWebSocket server used by layout tests."""
+
+
+from __future__ import with_statement
+
+import codecs
+import logging
+import optparse
+import os
+import subprocess
+import sys
+import tempfile
+import time
+import urllib
+
+import factory
+import http_server
+
+from webkitpy.common.system.executive import Executive
+from webkitpy.thirdparty.autoinstalled.pywebsocket import mod_pywebsocket
+
+
+_log = logging.getLogger("webkitpy.layout_tests.port.websocket_server")
+
+_WS_LOG_PREFIX = 'pywebsocket.ws.log-'
+_WSS_LOG_PREFIX = 'pywebsocket.wss.log-'
+
+_DEFAULT_WS_PORT = 8880
+_DEFAULT_WSS_PORT = 9323
+
+
+def url_is_alive(url):
+ """Checks to see if we get an http response from |url|.
+ We poll the url 20 times with a 0.5 second delay. If we don't
+ get a reply in that time, we give up and assume the httpd
+ didn't start properly.
+
+ Args:
+ url: The URL to check.
+ Return:
+ True if the url is alive.
+ """
+ sleep_time = 0.5
+ wait_time = 10
+ while wait_time > 0:
+ try:
+ response = urllib.urlopen(url)
+ # Server is up and responding.
+ return True
+ except IOError:
+ pass
+ # Wait for sleep_time before trying again.
+ wait_time -= sleep_time
+ time.sleep(sleep_time)
+
+ return False
+
+
+class PyWebSocketNotStarted(Exception):
+ pass
+
+
+class PyWebSocketNotFound(Exception):
+ pass
+
+
+class PyWebSocket(http_server.Lighttpd):
+
+ def __init__(self, port_obj, output_dir, port=_DEFAULT_WS_PORT,
+ root=None, use_tls=False,
+ pidfile=None):
+ """Args:
+ output_dir: the absolute path to the layout test result directory
+ """
+ http_server.Lighttpd.__init__(self, port_obj, output_dir,
+ port=_DEFAULT_WS_PORT,
+ root=root)
+ self._output_dir = output_dir
+ self._process = None
+ self._port = port
+ self._root = root
+ self._use_tls = use_tls
+ self._private_key = self._pem_file
+ self._certificate = self._pem_file
+ if self._port:
+ self._port = int(self._port)
+ if self._use_tls:
+ self._server_name = 'PyWebSocket(Secure)'
+ else:
+ self._server_name = 'PyWebSocket'
+ self._pidfile = pidfile
+ self._wsout = None
+
+ # Webkit tests
+ if self._root:
+ self._layout_tests = os.path.abspath(self._root)
+ self._web_socket_tests = os.path.abspath(
+ os.path.join(self._root, 'http', 'tests',
+ 'websocket', 'tests'))
+ else:
+ try:
+ self._layout_tests = self._port_obj.layout_tests_dir()
+ self._web_socket_tests = os.path.join(self._layout_tests,
+ 'http', 'tests', 'websocket', 'tests')
+ except:
+ self._web_socket_tests = None
+
+ def start(self):
+ if not self._web_socket_tests:
+ _log.info('No need to start %s server.' % self._server_name)
+ return
+ if self.is_running():
+ raise PyWebSocketNotStarted('%s is already running.' %
+ self._server_name)
+
+ time_str = time.strftime('%d%b%Y-%H%M%S')
+ if self._use_tls:
+ log_prefix = _WSS_LOG_PREFIX
+ else:
+ log_prefix = _WS_LOG_PREFIX
+ log_file_name = log_prefix + time_str
+
+ # Remove old log files. We only need to keep the last ones.
+ self.remove_log_files(self._output_dir, log_prefix)
+
+ error_log = os.path.join(self._output_dir, log_file_name + "-err.txt")
+
+ output_log = os.path.join(self._output_dir, log_file_name + "-out.txt")
+ self._wsout = codecs.open(output_log, "w", "utf-8")
+
+ python_interp = sys.executable
+ pywebsocket_base = os.path.join(
+ os.path.dirname(os.path.dirname(os.path.dirname(
+ os.path.abspath(__file__)))), 'thirdparty',
+ 'autoinstalled', 'pywebsocket')
+ pywebsocket_script = os.path.join(pywebsocket_base, 'mod_pywebsocket',
+ 'standalone.py')
+ start_cmd = [
+ python_interp, '-u', pywebsocket_script,
+ '--server-host', '127.0.0.1',
+ '--port', str(self._port),
+ '--document-root', os.path.join(self._layout_tests, 'http', 'tests'),
+ '--scan-dir', self._web_socket_tests,
+ '--cgi-paths', '/websocket/tests',
+ '--log-file', error_log,
+ ]
+
+ handler_map_file = os.path.join(self._web_socket_tests,
+ 'handler_map.txt')
+ if os.path.exists(handler_map_file):
+ _log.debug('Using handler_map_file: %s' % handler_map_file)
+ start_cmd.append('--websock-handlers-map-file')
+ start_cmd.append(handler_map_file)
+ else:
+ _log.warning('No handler_map_file found')
+
+ if self._use_tls:
+ start_cmd.extend(['-t', '-k', self._private_key,
+ '-c', self._certificate])
+
+ env = self._port_obj.setup_environ_for_server()
+ env['PYTHONPATH'] = (pywebsocket_base + os.path.pathsep +
+ env.get('PYTHONPATH', ''))
+
+ _log.debug('Starting %s server on %d.' % (
+ self._server_name, self._port))
+ _log.debug('cmdline: %s' % ' '.join(start_cmd))
+ # FIXME: We should direct this call through Executive for testing.
+ # Note: Not thread safe: http://bugs.python.org/issue2320
+ self._process = subprocess.Popen(start_cmd,
+ stdin=open(os.devnull, 'r'),
+ stdout=self._wsout,
+ stderr=subprocess.STDOUT,
+ env=env)
+
+ if self._use_tls:
+ url = 'https'
+ else:
+ url = 'http'
+ url = url + '://127.0.0.1:%d/' % self._port
+ if not url_is_alive(url):
+ if self._process.returncode == None:
+ # FIXME: We should use a non-static Executive for easier
+ # testing.
+ Executive().kill_process(self._process.pid)
+ with codecs.open(output_log, "r", "utf-8") as fp:
+ for line in fp:
+ _log.error(line)
+ raise PyWebSocketNotStarted(
+ 'Failed to start %s server on port %s.' %
+ (self._server_name, self._port))
+
+ # Our process terminated already
+ if self._process.returncode != None:
+ raise PyWebSocketNotStarted(
+ 'Failed to start %s server.' % self._server_name)
+ if self._pidfile:
+ with codecs.open(self._pidfile, "w", "ascii") as file:
+ file.write("%d" % self._process.pid)
+
+ def stop(self, force=False):
+ if not force and not self.is_running():
+ return
+
+ pid = None
+ if self._process:
+ pid = self._process.pid
+ elif self._pidfile:
+ with codecs.open(self._pidfile, "r", "ascii") as file:
+ pid = int(file.read().strip())
+
+ if not pid:
+ raise PyWebSocketNotFound(
+ 'Failed to find %s server pid.' % self._server_name)
+
+ _log.debug('Shutting down %s server %d.' % (self._server_name, pid))
+ # FIXME: We should use a non-static Executive for easier testing.
+ Executive().kill_process(pid)
+
+ if self._process:
+ # wait() is not threadsafe and can throw OSError due to:
+ # http://bugs.python.org/issue1731717
+ self._process.wait()
+ self._process = None
+
+ if self._wsout:
+ self._wsout.close()
+ self._wsout = None
diff --git a/Tools/Scripts/webkitpy/layout_tests/port/win.py b/Tools/Scripts/webkitpy/layout_tests/port/win.py
new file mode 100644
index 0000000..9e30155
--- /dev/null
+++ b/Tools/Scripts/webkitpy/layout_tests/port/win.py
@@ -0,0 +1,75 @@
+# 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.
+
+"""WebKit Win implementation of the Port interface."""
+
+import logging
+import os
+
+from webkitpy.layout_tests.port.webkit import WebKitPort
+
+_log = logging.getLogger("webkitpy.layout_tests.port.win")
+
+
+class WinPort(WebKitPort):
+ """WebKit Win implementation of the Port class."""
+
+ def __init__(self, **kwargs):
+ kwargs.setdefault('port_name', 'win')
+ WebKitPort.__init__(self, **kwargs)
+
+ def baseline_search_path(self):
+ # Based on code from old-run-webkit-tests expectedDirectoryForTest()
+ port_names = ["win", "mac-snowleopard", "mac"]
+ return map(self._webkit_baseline_path, port_names)
+
+ def _tests_for_other_platforms(self):
+ # FIXME: This list could be dynamic based on platform name and
+ # pushed into base.Port.
+ # This really need to be automated.
+ return [
+ "platform/chromium",
+ "platform/gtk",
+ "platform/qt",
+ "platform/mac",
+ ]
+
+ def _path_to_apache_config_file(self):
+ return os.path.join(self.layout_tests_dir(), 'http', 'conf',
+ 'cygwin-httpd.conf')
+
+ def _shut_down_http_server(self, server_pid):
+ """Shut down the httpd web server. Blocks until it's fully
+ shut down.
+
+ Args:
+ server_pid: The process ID of the running server.
+ """
+ # Looks like we ignore server_pid.
+ # Copy/pasted from chromium-win.
+ self._executive.kill_all("httpd.exe")