summaryrefslogtreecommitdiffstats
path: root/WebKitTools/Scripts
diff options
context:
space:
mode:
authorTeng-Hui Zhu <ztenghui@google.com>2010-11-10 15:31:59 -0800
committerTeng-Hui Zhu <ztenghui@google.com>2010-11-17 13:35:59 -0800
commit28040489d744e0c5d475a88663056c9040ed5320 (patch)
treec463676791e4a63e452a95f0a12b2a8519730693 /WebKitTools/Scripts
parenteff9be92c41913c92fb1d3b7983c071f3e718678 (diff)
downloadexternal_webkit-28040489d744e0c5d475a88663056c9040ed5320.zip
external_webkit-28040489d744e0c5d475a88663056c9040ed5320.tar.gz
external_webkit-28040489d744e0c5d475a88663056c9040ed5320.tar.bz2
Merge WebKit at r71558: Initial merge by git.
Change-Id: Ib345578fa29df7e4bc72b4f00e4a6fddcb754c4c
Diffstat (limited to 'WebKitTools/Scripts')
-rwxr-xr-xWebKitTools/Scripts/check-webkit-style5
-rwxr-xr-xWebKitTools/Scripts/old-run-webkit-tests9
-rw-r--r--WebKitTools/Scripts/webkitpy/common/checkout/scm.py2
-rw-r--r--WebKitTools/Scripts/webkitpy/common/config/committers.py4
-rw-r--r--WebKitTools/Scripts/webkitpy/common/net/bugzilla.py4
-rw-r--r--WebKitTools/Scripts/webkitpy/common/system/executive.py109
-rw-r--r--WebKitTools/Scripts/webkitpy/common/system/executive_unittest.py77
-rw-r--r--WebKitTools/Scripts/webkitpy/common/system/filesystem.py117
-rw-r--r--WebKitTools/Scripts/webkitpy/common/system/filesystem_unittest.py157
-rw-r--r--WebKitTools/Scripts/webkitpy/common/system/fileutils.py33
-rw-r--r--WebKitTools/Scripts/webkitpy/common/system/path.py8
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py39
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/base.py21
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py11
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py6
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/config.py154
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/config_unittest.py176
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/http_lock.py47
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/webkit.py9
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py31
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py9
-rwxr-xr-xWebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py57
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py146
-rw-r--r--WebKitTools/Scripts/webkitpy/style/checker.py8
-rw-r--r--WebKitTools/Scripts/webkitpy/style/optparser.py15
-rw-r--r--WebKitTools/Scripts/webkitpy/style/optparser_unittest.py8
-rw-r--r--WebKitTools/Scripts/webkitpy/style_references.py6
-rw-r--r--WebKitTools/Scripts/webkitpy/test/cat.py42
-rw-r--r--WebKitTools/Scripts/webkitpy/test/cat_unittest.py52
-rw-r--r--WebKitTools/Scripts/webkitpy/test/echo.py52
-rw-r--r--WebKitTools/Scripts/webkitpy/test/echo_unittest.py64
-rw-r--r--WebKitTools/Scripts/webkitpy/test/skip.py52
-rw-r--r--WebKitTools/Scripts/webkitpy/test/skip_unittest.py77
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/commands/__init__.py1
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/index.html40
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/main.css56
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/main.js36
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/commands/rebaselineserver.py157
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/commands/upload.py26
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/mocktool.py3
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/steps/checkstyle.py3
41 files changed, 1735 insertions, 194 deletions
diff --git a/WebKitTools/Scripts/check-webkit-style b/WebKitTools/Scripts/check-webkit-style
index e29d4b1..076c712 100755
--- a/WebKitTools/Scripts/check-webkit-style
+++ b/WebKitTools/Scripts/check-webkit-style
@@ -112,10 +112,11 @@ def main():
file_reader = TextFileReader(style_processor)
- if paths:
+ if paths and not options.diff_files:
file_reader.process_paths(paths)
else:
- patch = checkout.create_patch(options.git_commit)
+ changed_files = paths if options.diff_files else None
+ patch = checkout.create_patch(options.git_commit, changed_files=changed_files)
patch_checker = PatchReader(file_reader)
patch_checker.check(patch)
diff --git a/WebKitTools/Scripts/old-run-webkit-tests b/WebKitTools/Scripts/old-run-webkit-tests
index e40b3ad..2cef18b 100755
--- a/WebKitTools/Scripts/old-run-webkit-tests
+++ b/WebKitTools/Scripts/old-run-webkit-tests
@@ -373,6 +373,8 @@ if ($useWebKitTestRunner) {
}
}
+$timeoutSeconds *= 10 if $guardMalloc;
+
$stripEditingCallbacks = isCygwin() unless defined $stripEditingCallbacks;
my $ignoreSkipped = $treatSkipped eq "ignore";
@@ -1483,6 +1485,12 @@ sub openDumpTool()
unshift @args, "valgrind", "--suppressions=$platformBaseDirectory/qt/SuppressedValgrindErrors";
}
+ if ($useWebKitTestRunner) {
+ # Make WebKitTestRunner use a similar timeout. We don't use the exact same timeout to avoid
+ # race conditions.
+ push @args, "--timeout", $timeoutSeconds - 5;
+ }
+
$CLEAN_ENV{MallocStackLogging} = 1 if $shouldCheckLeaks;
$dumpToolPID = open3(\*OUT, \*IN, \*ERROR, launchWithEnv(@args, %CLEAN_ENV)) or die "Failed to start tool: $dumpTool\n";
@@ -2089,7 +2097,6 @@ sub readFromDumpToolWithTimer(**)
setFileHandleNonBlocking($fhError, 1);
my $maximumSecondsWithoutOutput = $timeoutSeconds;
- $maximumSecondsWithoutOutput *= 10 if $guardMalloc;
my $microsecondsToWaitBeforeReadingAgain = 1000;
my $timeOfLastSuccessfulRead = time;
diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/scm.py b/WebKitTools/Scripts/webkitpy/common/checkout/scm.py
index 9b602c3..8aadcb8 100644
--- a/WebKitTools/Scripts/webkitpy/common/checkout/scm.py
+++ b/WebKitTools/Scripts/webkitpy/common/checkout/scm.py
@@ -64,7 +64,7 @@ def default_scm():
cwd = os.getcwd()
scm_system = detect_scm_system(cwd)
if not scm_system:
- script_directory = os.path.abspath(sys.path[0])
+ script_directory = os.path.dirname(os.path.abspath(__file__))
scm_system = detect_scm_system(script_directory)
if scm_system:
log("The current directory (%s) is not a WebKit checkout, using %s" % (cwd, scm_system.checkout_root))
diff --git a/WebKitTools/Scripts/webkitpy/common/config/committers.py b/WebKitTools/Scripts/webkitpy/common/config/committers.py
index 71d764c..446c2b1 100644
--- a/WebKitTools/Scripts/webkitpy/common/config/committers.py
+++ b/WebKitTools/Scripts/webkitpy/common/config/committers.py
@@ -112,6 +112,7 @@ committers_unable_to_review = [
Committer("Girish Ramakrishnan", ["girish@forwardbias.in", "ramakrishnan.girish@gmail.com"]),
Committer("Graham Dennis", ["Graham.Dennis@gmail.com", "gdennis@webkit.org"]),
Committer("Greg Bolsinga", "bolsinga@apple.com"),
+ Committer("Gyuyoung Kim", ["gyuyoung.kim@samsung.com", "gyuyoung@gmail.com", "gyuyoung@webkit.org"], "gyuyoung"),
Committer("Hans Wennborg", "hans@chromium.org", "hwennborg"),
Committer("Hayato Ito", "hayato@chromium.org", "hayato"),
Committer("Hin-Chung Lam", ["hclam@google.com", "hclam@chromium.org"]),
@@ -147,7 +148,7 @@ committers_unable_to_review = [
Committer("Luiz Agostini", ["luiz@webkit.org", "luiz.agostini@openbossa.org"], "lca"),
Committer("Mads Ager", "ager@chromium.org"),
Committer("Marcus Voltis Bulach", "bulach@chromium.org"),
- Committer("Mario Sanchez Prada", ["msanchez@igalia.com", "mario@webkit.org"]),
+ Committer("Mario Sanchez Prada", ["msanchez@igalia.com", "mario@webkit.org"], "msanchez"),
Committer("Matt Delaney", "mdelaney@apple.com"),
Committer("Matt Lilek", ["webkit@mattlilek.com", "pewtermoose@webkit.org"]),
Committer("Matt Perry", "mpcomplete@chromium.org"),
@@ -171,6 +172,7 @@ committers_unable_to_review = [
Committer("Philippe Normand", ["pnormand@igalia.com", "philn@webkit.org"], "philn-tp"),
Committer("Pierre d'Herbemont", ["pdherbemont@free.fr", "pdherbemont@apple.com"], "pdherbemont"),
Committer("Pierre-Olivier Latour", "pol@apple.com", "pol"),
+ Committer("Renata Hodovan", "reni@webkit.org", "reni"),
Committer("Robert Hogan", ["robert@webkit.org", "robert@roberthogan.net", "lists@roberthogan.net"], "mwenge"),
Committer("Roland Steiner", "rolandsteiner@chromium.org"),
Committer("Ryosuke Niwa", "rniwa@webkit.org", "rniwa"),
diff --git a/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py b/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py
index 1cc8e2e..a7dc1b7 100644
--- a/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py
+++ b/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py
@@ -281,7 +281,7 @@ class BugzillaQueries(object):
return sum([self._fetch_bug(bug_id).commit_queued_patches()
for bug_id in self.fetch_bug_ids_from_commit_queue()], [])
- def _fetch_bug_ids_from_review_queue(self):
+ def fetch_bug_ids_from_review_queue(self):
review_queue_url = "buglist.cgi?query_format=advanced&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&field0-0-0=flagtypes.name&type0-0-0=equals&value0-0-0=review?"
return self._fetch_bug_ids_advanced_query(review_queue_url)
@@ -289,7 +289,7 @@ class BugzillaQueries(object):
def fetch_patches_from_review_queue(self, limit=None):
# [:None] returns the whole array.
return sum([self._fetch_bug(bug_id).unreviewed_patches()
- for bug_id in self._fetch_bug_ids_from_review_queue()[:limit]], [])
+ for bug_id in self.fetch_bug_ids_from_review_queue()[:limit]], [])
# NOTE: This is the only client of _fetch_attachment_ids_request_query
# This method only makes one request to bugzilla.
diff --git a/WebKitTools/Scripts/webkitpy/common/system/executive.py b/WebKitTools/Scripts/webkitpy/common/system/executive.py
index 216cf58..37f4e53 100644
--- a/WebKitTools/Scripts/webkitpy/common/system/executive.py
+++ b/WebKitTools/Scripts/webkitpy/common/system/executive.py
@@ -33,6 +33,7 @@ try:
except ImportError:
multiprocessing = None
+import ctypes
import errno
import logging
import os
@@ -44,6 +45,7 @@ import sys
import time
from webkitpy.common.system.deprecated_logging import tee
+from webkitpy.python24 import versioning
_log = logging.getLogger("webkitpy.common.system")
@@ -103,13 +105,8 @@ class Executive(object):
def _run_command_with_teed_output(self, args, teed_output):
args = map(unicode, args) # Popen will throw an exception if args are non-strings (like int())
- if sys.platform == 'cygwin':
- # Cygwin's Python's os.execv doesn't support unicode command
- # arguments, and neither does Cygwin's execv itself.
- # FIXME: Using UTF-8 here will confuse Windows-native commands
- # which will expect arguments to be encoded using the current code
- # page.
- args = [arg.encode('utf-8') for arg in args]
+ args = map(self._encode_argument_if_needed, args)
+
child_process = subprocess.Popen(args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
@@ -148,9 +145,8 @@ class Executive(object):
child_output = child_out_file.getvalue()
child_out_file.close()
- # We assume the child process output utf-8
if decode_output:
- child_output = child_output.decode("utf-8")
+ child_output = child_output.decode(self._child_process_encoding())
if exit_code:
raise ScriptError(script_args=args,
@@ -205,6 +201,55 @@ class Executive(object):
return
raise
+ def _win32_check_running_pid(self):
+
+ class PROCESSENTRY32(ctypes.Structure):
+ _fields_ = [("dwSize", ctypes.c_ulong),
+ ("cntUsage", ctypes.c_ulong),
+ ("th32ProcessID", ctypes.c_ulong),
+ ("th32DefaultHeapID", ctypes.c_ulong),
+ ("th32ModuleID", ctypes.c_ulong),
+ ("cntThreads", ctypes.c_ulong),
+ ("th32ParentProcessID", ctypes.c_ulong),
+ ("pcPriClassBase", ctypes.c_ulong),
+ ("dwFlags", ctypes.c_ulong),
+ ("szExeFile", ctypes.c_char * 260)]
+
+ CreateToolhelp32Snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot
+ Process32First = ctypes.windll.kernel32.Process32First
+ Process32Next = ctypes.windll.kernel32.Process32Next
+ CloseHandle = ctypes.windll.kernel32.CloseHandle
+ TH32CS_SNAPPROCESS = 0x00000002 # win32 magic number
+ hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
+ pe32 = PROCESSENTRY32()
+ pe32.dwSize = ctypes.sizeof(PROCESSENTRY32)
+ result = False
+ if not Process32First(hProcessSnap, ctypes.byref(pe32)):
+ _log.debug("Failed getting first process.")
+ CloseHandle(hProcessSnap)
+ return result
+ while True:
+ if pe32.th32ProcessID == pid:
+ result = True
+ break
+ if not Process32Next(hProcessSnap, ctypes.byref(pe32)):
+ break
+ CloseHandle(hProcessSnap)
+ return result
+
+ def check_running_pid(self, pid):
+ """Return True if pid is alive, otherwise return False."""
+ if sys.platform in ('darwin', 'linux2', 'cygwin'):
+ try:
+ os.kill(pid, 0)
+ return True
+ except OSError:
+ return False
+ elif sys.platform == 'win32':
+ return self._win32_check_running_pid()
+
+ assert(False)
+
def _windows_image_name(self, process_name):
name, extension = os.path.splitext(process_name)
if not extension:
@@ -260,7 +305,7 @@ class Executive(object):
# for an example of a regresion caused by passing a unicode string directly.
# FIXME: We may need to encode differently on different platforms.
if isinstance(input, unicode):
- input = input.encode("utf-8")
+ input = input.encode(self._child_process_encoding())
return (subprocess.PIPE, input)
def _command_for_printing(self, args):
@@ -288,13 +333,8 @@ class Executive(object):
assert(isinstance(args, list) or isinstance(args, tuple))
start_time = time.time()
args = map(unicode, args) # Popen will throw an exception if args are non-strings (like int())
- if sys.platform == 'cygwin':
- # Cygwin's Python's os.execv doesn't support unicode command
- # arguments, and neither does Cygwin's execv itself.
- # FIXME: Using UTF-8 here will confuse Windows-native commands
- # which will expect arguments to be encoded using the current code
- # page.
- args = [arg.encode('utf-8') for arg in args]
+ args = map(self._encode_argument_if_needed, args)
+
stdin, string_to_communicate = self._compute_stdin(input)
stderr = subprocess.STDOUT if return_stderr else None
@@ -305,9 +345,11 @@ class Executive(object):
cwd=cwd,
close_fds=self._should_close_fds())
output = process.communicate(string_to_communicate)[0]
+
# run_command automatically decodes to unicode() unless explicitly told not to.
if decode_output:
- output = output.decode("utf-8")
+ output = output.decode(self._child_process_encoding())
+
# wait() is not threadsafe and can throw OSError due to:
# http://bugs.python.org/issue1731717
exit_code = process.wait()
@@ -324,3 +366,34 @@ class Executive(object):
cwd=cwd)
(error_handler or self.default_error_handler)(script_error)
return output
+
+ def _child_process_encoding(self):
+ # Win32 Python 2.x uses CreateProcessA rather than CreateProcessW
+ # to launch subprocesses, so we have to encode arguments using the
+ # current code page.
+ if sys.platform == 'win32' and versioning.compare_version(sys, '3.0')[0] < 0:
+ return 'mbcs'
+ # All other platforms use UTF-8.
+ # FIXME: Using UTF-8 on Cygwin will confuse Windows-native commands
+ # which will expect arguments to be encoded using the current code
+ # page.
+ return 'utf-8'
+
+ def _should_encode_child_process_arguments(self):
+ # Cygwin's Python's os.execv doesn't support unicode command
+ # arguments, and neither does Cygwin's execv itself.
+ if sys.platform == 'cygwin':
+ return True
+
+ # Win32 Python 2.x uses CreateProcessA rather than CreateProcessW
+ # to launch subprocesses, so we have to encode arguments using the
+ # current code page.
+ if sys.platform == 'win32' and versioning.compare_version(sys, '3.0')[0] < 0:
+ return True
+
+ return False
+
+ def _encode_argument_if_needed(self, argument):
+ if not self._should_encode_child_process_arguments():
+ return argument
+ return argument.encode(self._child_process_encoding())
diff --git a/WebKitTools/Scripts/webkitpy/common/system/executive_unittest.py b/WebKitTools/Scripts/webkitpy/common/system/executive_unittest.py
index 32f8f51..b8fd82e 100644
--- a/WebKitTools/Scripts/webkitpy/common/system/executive_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/common/system/executive_unittest.py
@@ -27,12 +27,23 @@
# (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 signal
import subprocess
import sys
import unittest
from webkitpy.common.system.executive import Executive, run_command, ScriptError
+from webkitpy.test import cat, echo
+
+
+def never_ending_command():
+ """Arguments for a command that will never end (useful for testing process
+ killing). It should be a process that is unlikely to already be running
+ because all instances will be killed."""
+ if sys.platform == 'win32':
+ return ['wmic']
+ return ['yes']
class ExecutiveTest(unittest.TestCase):
@@ -46,46 +57,56 @@ class ExecutiveTest(unittest.TestCase):
executive = Executive()
self.assertRaises(AssertionError, executive.run_command, "echo")
self.assertRaises(AssertionError, executive.run_command, u"echo")
- executive.run_command(["echo", "foo"])
- executive.run_command(("echo", "foo"))
+ executive.run_command(echo.command_arguments('foo'))
+ executive.run_command(tuple(echo.command_arguments('foo')))
def test_run_command_with_unicode(self):
"""Validate that it is safe to pass unicode() objects
to Executive.run* methods, and they will return unicode()
objects by default unless decode_output=False"""
+ unicode_tor_input = u"WebKit \u2661 Tor Arne Vestb\u00F8!"
+ if sys.platform == 'win32':
+ encoding = 'mbcs'
+ else:
+ encoding = 'utf-8'
+ encoded_tor = unicode_tor_input.encode(encoding)
+ # On Windows, we expect the unicode->mbcs->unicode roundtrip to be
+ # lossy. On other platforms, we expect a lossless roundtrip.
+ if sys.platform == 'win32':
+ unicode_tor_output = encoded_tor.decode(encoding)
+ else:
+ unicode_tor_output = unicode_tor_input
+
executive = Executive()
- unicode_tor = u"WebKit \u2661 Tor Arne Vestb\u00F8!"
- utf8_tor = unicode_tor.encode("utf-8")
- output = executive.run_command(["cat"], input=unicode_tor)
- self.assertEquals(output, unicode_tor)
+ output = executive.run_command(cat.command_arguments(), input=unicode_tor_input)
+ self.assertEquals(output, unicode_tor_output)
- output = executive.run_command(["echo", "-n", unicode_tor])
- self.assertEquals(output, unicode_tor)
+ output = executive.run_command(echo.command_arguments("-n", unicode_tor_input))
+ self.assertEquals(output, unicode_tor_output)
- output = executive.run_command(["echo", "-n", unicode_tor], decode_output=False)
- self.assertEquals(output, utf8_tor)
+ output = executive.run_command(echo.command_arguments("-n", unicode_tor_input), decode_output=False)
+ self.assertEquals(output, encoded_tor)
# Make sure that str() input also works.
- output = executive.run_command(["cat"], input=utf8_tor, decode_output=False)
- self.assertEquals(output, utf8_tor)
+ output = executive.run_command(cat.command_arguments(), input=encoded_tor, decode_output=False)
+ self.assertEquals(output, encoded_tor)
# FIXME: We should only have one run* method to test
- output = executive.run_and_throw_if_fail(["echo", "-n", unicode_tor], quiet=True)
- self.assertEquals(output, unicode_tor)
+ output = executive.run_and_throw_if_fail(echo.command_arguments("-n", unicode_tor_input), quiet=True)
+ self.assertEquals(output, unicode_tor_output)
- output = executive.run_and_throw_if_fail(["echo", "-n", unicode_tor], quiet=True, decode_output=False)
- self.assertEquals(output, utf8_tor)
+ output = executive.run_and_throw_if_fail(echo.command_arguments("-n", unicode_tor_input), quiet=True, decode_output=False)
+ self.assertEquals(output, encoded_tor)
def test_kill_process(self):
executive = Executive()
- # We use "yes" because it loops forever.
- process = subprocess.Popen(["yes"], stdout=subprocess.PIPE)
+ process = subprocess.Popen(never_ending_command(), stdout=subprocess.PIPE)
self.assertEqual(process.poll(), None) # Process is running
executive.kill_process(process.pid)
# Note: Can't use a ternary since signal.SIGKILL is undefined for sys.platform == "win32"
if sys.platform == "win32":
- expected_exit_code = 0 # taskkill.exe results in exit(0)
+ expected_exit_code = 1
else:
expected_exit_code = -signal.SIGKILL
self.assertEqual(process.wait(), expected_exit_code)
@@ -109,14 +130,22 @@ class ExecutiveTest(unittest.TestCase):
def test_kill_all(self):
executive = Executive()
# We use "yes" because it loops forever.
- process = subprocess.Popen(["yes"], stdout=subprocess.PIPE)
+ process = subprocess.Popen(never_ending_command(), stdout=subprocess.PIPE)
self.assertEqual(process.poll(), None) # Process is running
- executive.kill_all("yes")
+ executive.kill_all(never_ending_command()[0])
# Note: Can't use a ternary since signal.SIGTERM is undefined for sys.platform == "win32"
- if sys.platform in ("win32", "cygwin"):
- expected_exit_code = 0 # taskkill.exe results in exit(0)
+ if sys.platform == "cygwin":
+ expected_exit_code = 0 # os.kill results in exit(0) for this process.
+ elif sys.platform == "win32":
+ expected_exit_code = 1
else:
expected_exit_code = -signal.SIGTERM
self.assertEqual(process.wait(), expected_exit_code)
# Killing again should fail silently.
- executive.kill_all("yes")
+ executive.kill_all(never_ending_command()[0])
+
+ def test_check_running_pid(self):
+ executive = Executive()
+ self.assertTrue(executive.check_running_pid(os.getpid()))
+ # Maximum pid number on Linux is 32768 by default
+ self.assertFalse(executive.check_running_pid(100000))
diff --git a/WebKitTools/Scripts/webkitpy/common/system/filesystem.py b/WebKitTools/Scripts/webkitpy/common/system/filesystem.py
new file mode 100644
index 0000000..c7efde3
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/common/system/filesystem.py
@@ -0,0 +1,117 @@
+# 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 object for the file system / source tree."""
+
+from __future__ import with_statement
+
+import codecs
+import errno
+import os
+import tempfile
+
+
+class FileSystem(object):
+ """FileSystem interface for webkitpy.
+
+ Unless otherwise noted, all paths are allowed to be either absolute
+ or relative."""
+
+ def exists(self, path):
+ """Return whether the path exists in the filesystem."""
+ return os.path.exists(path)
+
+ def isdir(self, path):
+ """Return whether the path refers to a directory."""
+ return os.path.isdir(path)
+
+ def join(self, *comps):
+ """Return the path formed by joining the components."""
+ return os.path.join(*comps)
+
+ def listdir(self, path):
+ """Return the contents of the directory pointed to by path."""
+ return os.listdir(path)
+
+ def mkdtemp(self, **kwargs):
+ """Create and return a uniquely named directory.
+
+ This is like tempfile.mkdtemp, but if used in a with statement
+ the directory will self-delete at the end of the block (if the
+ directory is empty; non-empty directories raise errors). The
+ directory can be safely deleted inside the block as well, if so
+ desired."""
+ class TemporaryDirectory(object):
+ def __init__(self, **kwargs):
+ self._kwargs = kwargs
+ self._directory_path = None
+
+ def __enter__(self):
+ self._directory_path = tempfile.mkdtemp(**self._kwargs)
+ return self._directory_path
+
+ def __exit__(self, type, value, traceback):
+ # Only self-delete if necessary.
+
+ # FIXME: Should we delete non-empty directories?
+ if os.path.exists(self._directory_path):
+ os.rmdir(self._directory_path)
+
+ return TemporaryDirectory(**kwargs)
+
+ def maybe_make_directory(self, *path):
+ """Create the specified directory if it doesn't already exist."""
+ try:
+ os.makedirs(os.path.join(*path))
+ except OSError, e:
+ if e.errno != errno.EEXIST:
+ raise
+
+ def read_binary_file(self, path):
+ """Return the contents of the file at the given path as a byte string."""
+ with file(path, 'rb') as f:
+ return f.read()
+
+ def read_text_file(self, path):
+ """Return the contents of the file at the given path as a Unicode string.
+
+ The file is read assuming it is a UTF-8 encoded file with no BOM."""
+ with codecs.open(path, 'r', 'utf8') as f:
+ return f.read()
+
+ def write_binary_file(self, path, contents):
+ """Write the contents to the file at the given location."""
+ with file(path, 'wb') as f:
+ f.write(contents)
+
+ def write_text_file(self, path, contents):
+ """Write the contents to the file at the given location.
+
+ The file is written encoded as UTF-8 with no BOM."""
+ with codecs.open(path, 'w', 'utf8') as f:
+ f.write(contents)
diff --git a/WebKitTools/Scripts/webkitpy/common/system/filesystem_unittest.py b/WebKitTools/Scripts/webkitpy/common/system/filesystem_unittest.py
new file mode 100644
index 0000000..95684b7
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/common/system/filesystem_unittest.py
@@ -0,0 +1,157 @@
+# vim: set fileencoding=utf-8 :
+# 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.
+
+# NOTE: The fileencoding comment on the first line of the file is
+# important; without it, Python will choke while trying to parse the file,
+# since it includes non-ASCII characters.
+
+from __future__ import with_statement
+
+import os
+import stat
+import sys
+import tempfile
+import unittest
+
+from filesystem import FileSystem
+
+
+class FileSystemTest(unittest.TestCase):
+ def setUp(self):
+ self._this_dir = os.path.dirname(os.path.abspath(__file__))
+ self._missing_file = os.path.join(self._this_dir, 'missing_file.py')
+ self._this_file = os.path.join(self._this_dir, 'filesystem_unittest.py')
+
+ def test_exists__true(self):
+ fs = FileSystem()
+ self.assertTrue(fs.exists(self._this_file))
+
+ def test_exists__false(self):
+ fs = FileSystem()
+ self.assertFalse(fs.exists(self._missing_file))
+
+ def test_isdir__true(self):
+ fs = FileSystem()
+ self.assertTrue(fs.isdir(self._this_dir))
+
+ def test_isdir__false(self):
+ fs = FileSystem()
+ self.assertFalse(fs.isdir(self._this_file))
+
+ def test_join(self):
+ fs = FileSystem()
+ self.assertEqual(fs.join('foo', 'bar'),
+ os.path.join('foo', 'bar'))
+
+ def test_listdir(self):
+ fs = FileSystem()
+ with fs.mkdtemp(prefix='filesystem_unittest_') as d:
+ self.assertEqual(fs.listdir(d), [])
+ new_file = os.path.join(d, 'foo')
+ fs.write_text_file(new_file, u'foo')
+ self.assertEqual(fs.listdir(d), ['foo'])
+ os.remove(new_file)
+
+ def test_maybe_make_directory__success(self):
+ fs = FileSystem()
+
+ with fs.mkdtemp(prefix='filesystem_unittest_') as base_path:
+ sub_path = os.path.join(base_path, "newdir")
+ self.assertFalse(os.path.exists(sub_path))
+ self.assertFalse(fs.isdir(sub_path))
+
+ fs.maybe_make_directory(sub_path)
+ self.assertTrue(os.path.exists(sub_path))
+ self.assertTrue(fs.isdir(sub_path))
+
+ # Make sure we can re-create it.
+ fs.maybe_make_directory(sub_path)
+ self.assertTrue(os.path.exists(sub_path))
+ self.assertTrue(fs.isdir(sub_path))
+
+ # Clean up.
+ os.rmdir(sub_path)
+
+ self.assertFalse(os.path.exists(base_path))
+ self.assertFalse(fs.isdir(base_path))
+
+ def test_maybe_make_directory__failure(self):
+ # FIXME: os.chmod() doesn't work on Windows to set directories
+ # as readonly, so we skip this test for now.
+ if sys.platform in ('win32', 'cygwin'):
+ return
+
+ fs = FileSystem()
+ with fs.mkdtemp(prefix='filesystem_unittest_') as d:
+ # Remove write permissions on the parent directory.
+ os.chmod(d, stat.S_IRUSR)
+
+ # Now try to create a sub directory - should fail.
+ sub_dir = fs.join(d, 'subdir')
+ self.assertRaises(OSError, fs.maybe_make_directory, sub_dir)
+
+ # Clean up in case the test failed and we did create the
+ # directory.
+ if os.path.exists(sub_dir):
+ os.rmdir(sub_dir)
+
+ def test_read_and_write_file(self):
+ fs = FileSystem()
+ text_path = None
+ binary_path = None
+
+ unicode_text_string = u'Ūnĭcōde̽'
+ hex_equivalent = '\xC5\xAA\x6E\xC4\xAD\x63\xC5\x8D\x64\x65\xCC\xBD'
+ try:
+ text_path = tempfile.mktemp(prefix='tree_unittest_')
+ binary_path = tempfile.mktemp(prefix='tree_unittest_')
+ fs.write_text_file(text_path, unicode_text_string)
+ contents = fs.read_binary_file(text_path)
+ self.assertEqual(contents, hex_equivalent)
+
+ fs.write_text_file(binary_path, hex_equivalent)
+ text_contents = fs.read_text_file(binary_path)
+ self.assertEqual(text_contents, unicode_text_string)
+ except:
+ if text_path:
+ os.remove(text_path)
+ if binary_path:
+ os.remove(binary_path)
+
+ def test_read_binary_file__missing(self):
+ fs = FileSystem()
+ self.assertRaises(IOError, fs.read_binary_file, self._missing_file)
+
+ def test_read_text_file__missing(self):
+ fs = FileSystem()
+ self.assertRaises(IOError, fs.read_text_file, self._missing_file)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/WebKitTools/Scripts/webkitpy/common/system/fileutils.py b/WebKitTools/Scripts/webkitpy/common/system/fileutils.py
new file mode 100644
index 0000000..55821f8
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/common/system/fileutils.py
@@ -0,0 +1,33 @@
+# Copyright (C) 2010 Apple 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:
+# 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 APPLE INC. AND ITS 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 APPLE INC. OR ITS 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
+
+
+def make_stdout_binary():
+ """Puts sys.stdout into binary mode (on platforms that have a distinction
+ between text and binary mode)."""
+ if sys.platform != 'win32' or not hasattr(sys.stdout, 'fileno'):
+ return
+ import msvcrt
+ import os
+ msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
diff --git a/WebKitTools/Scripts/webkitpy/common/system/path.py b/WebKitTools/Scripts/webkitpy/common/system/path.py
index 43c6410..09787d7 100644
--- a/WebKitTools/Scripts/webkitpy/common/system/path.py
+++ b/WebKitTools/Scripts/webkitpy/common/system/path.py
@@ -44,7 +44,7 @@ def abspath_to_uri(path, platform=None):
def cygpath(path):
- """Converts a cygwin path to Windows path."""
+ """Converts an absolute cygwin path to an absolute Windows path."""
return _CygPath.convert_using_singleton(path)
@@ -103,7 +103,11 @@ class _CygPath(object):
self.start()
self._child_process.stdin.write("%s\r\n" % path)
self._child_process.stdin.flush()
- return self._child_process.stdout.readline().rstrip()
+ windows_path = self._child_process.stdout.readline().rstrip()
+ # Some versions of cygpath use lowercase drive letters while others
+ # use uppercase. We always convert to uppercase for consistency.
+ windows_path = '%s%s' % (windows_path[0].upper(), windows_path[1:])
+ return windows_path
def _escape(path):
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py
index 3e3ba0b..9f2de7e 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py
@@ -341,7 +341,7 @@ class TestShellThread(WatchableThread):
def cancel(self):
"""Clean up http lock and set a flag telling this thread to quit."""
- self._stop_http_lock()
+ self._stop_servers_with_lock()
WatchableThread.cancel(self)
def next_timeout(self):
@@ -389,23 +389,15 @@ class TestShellThread(WatchableThread):
self._current_group, self._filename_list = \
self._filename_list_queue.get_nowait()
except Queue.Empty:
- self._stop_http_lock()
+ self._stop_servers_with_lock()
self._kill_dump_render_tree()
tests_run_file.close()
return
- if self._options.wait_for_httpd:
- if self._current_group == "tests_to_http_lock":
- self._http_lock_wait_begin = time.time()
- self._port.acquire_http_lock()
-
- self._port.start_http_server()
- self._port.start_websocket_server()
-
- self._have_http_lock = True
- self._http_lock_wait_end = time.time()
- elif self._have_http_lock:
- self._stop_http_lock()
+ if self._current_group == "tests_to_http_lock":
+ self._start_servers_with_lock()
+ elif self._have_http_lock:
+ self._stop_servers_with_lock()
self._num_tests_in_current_group = len(self._filename_list)
self._current_group_start_time = time.time()
@@ -440,7 +432,7 @@ class TestShellThread(WatchableThread):
self._port.relative_test_filename(filename)))
self._result_queue.put(result.dumps())
- if batch_size > 0 and batch_count > batch_size:
+ if batch_size > 0 and batch_count >= batch_size:
# Bounce the shell and reset count.
self._kill_dump_render_tree()
batch_count = 0
@@ -545,11 +537,26 @@ class TestShellThread(WatchableThread):
self._options)
self._driver.start()
- def _stop_http_lock(self):
+ def _start_servers_with_lock(self):
+ """Acquire http lock and start the servers."""
+ self._http_lock_wait_begin = time.time()
+ _log.debug('Acquire http lock ...')
+ self._port.acquire_http_lock()
+ _log.debug('Starting HTTP server ...')
+ self._port.start_http_server()
+ _log.debug('Starting WebSocket server ...')
+ self._port.start_websocket_server()
+ self._http_lock_wait_end = time.time()
+ self._have_http_lock = True
+
+ def _stop_servers_with_lock(self):
"""Stop the servers and release http lock."""
if self._have_http_lock:
+ _log.debug('Stopping HTTP server ...')
self._port.stop_http_server()
+ _log.debug('Stopping WebSocket server ...')
self._port.stop_websocket_server()
+ _log.debug('Release http lock ...')
self._port.release_http_lock()
self._have_http_lock = False
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py
index cd7d663..a98b858 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py
@@ -47,6 +47,7 @@ import http_server
import test_files
import websocket_server
+from webkitpy.common.memoized import memoized
from webkitpy.common.system import logutils
from webkitpy.common.system.executive import Executive, ScriptError
from webkitpy.common.system.path import abspath_to_uri
@@ -725,13 +726,25 @@ class Port(object):
e.message_with_output()))
return self._pretty_patch_error_html
- def _webkit_build_directory(self, args):
- args = ["perl", self.script_path("webkit-build-directory")] + args
+ def _webkit_build_directory_command(self, args):
+ return ["perl", self.script_path("webkit-build-directory")] + args
+
+ @memoized
+ def _webkit_top_level_build_directory(self, top_level=True):
+ """This directory is above where products are built to and contains things like the Configuration file."""
+ args = self._webkit_build_directory_command(["--top-level"])
+ return self._executive.run_command(args).rstrip()
+
+ @memoized
+ def _webkit_configuration_build_directory(self, configuration=None):
+ """This is where products are normally built to."""
+ if not configuration:
+ configuration = self.flag_from_configuration(self.get_option('configuration'))
+ args = self._webkit_build_directory_command(["--configuration", configuration])
return self._executive.run_command(args).rstrip()
def _configuration_file_path(self):
- build_root = self._webkit_build_directory(["--top-level"])
- return os.path.join(build_root, "Configuration")
+ return os.path.join(self._webkit_top_level_build_directory(), "Configuration")
# Easy override for unit tests
def _open_configuration_file(self):
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py
index 5d28fae..57b6989 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py
@@ -71,6 +71,8 @@ def _set_gpu_options(options):
options.accelerated_2d_canvas = True
if options.builder_name is not None:
options.builder_name = options.builder_name + ' - GPU'
+ if options.use_drt is None:
+ options.use_drt = True
def _gpu_overrides(port):
@@ -96,6 +98,9 @@ class ChromiumGpuLinuxPort(chromium_linux.ChromiumLinuxPort):
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')
@@ -114,6 +119,9 @@ class ChromiumGpuMacPort(chromium_mac.ChromiumMacPort):
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')
@@ -132,6 +140,9 @@ class ChromiumGpuWinPort(chromium_win.ChromiumWinPort):
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')
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py
index 88524fc..03bc98a 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py
@@ -45,10 +45,14 @@ class ChromiumGpuTest(unittest.TestCase):
# test that we got the right port
mock_options = mocktool.MockOptions(accelerated_compositing=None,
accelerated_2d_canvas=None,
- builder_name='foo')
+ builder_name='foo',
+ use_drt=None,
+ 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.assertTrue(port._options.use_drt)
+ 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.
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/config.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/config.py
new file mode 100644
index 0000000..2364098
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/config.py
@@ -0,0 +1,154 @@
+#!/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 .
+
+from __future__ import with_statement
+
+import codecs
+import os
+
+from webkitpy.common.checkout import scm
+from webkitpy.common.system import logutils
+
+
+_log = logutils.get_logger(__file__)
+
+#
+# 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().
+#
+_determined_configuration = None
+
+
+def clear_cached_configuration():
+ global _determined_configuration
+ _determined_configuration = -1
+
+
+class Config(object):
+ _FLAGS_FROM_CONFIGURATIONS = {
+ "Debug": "--debug",
+ "Release": "--release",
+ }
+
+ def __init__(self, executive):
+ self._executive = executive
+ 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]]
+ 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 os.path.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."""
+ # FIXME: Consider determining this independently of scm in order
+ # to be able to run in a bare tree.
+ if not self._webkit_base_dir:
+ self._webkit_base_dir = scm.find_checkout_root()
+ assert self._webkit_base_dir, "Could not determine the top of the WebKit checkout"
+ return self._webkit_base_dir
+
+ def _script_path(self, script_name):
+ return os.path.join(self.webkit_base_dir(), "WebKitTools",
+ "Scripts", script_name)
+
+ def _determine_configuration(self):
+ # This mirrors the logic in webkitdirs.pm:determineConfiguration().
+ global _determined_configuration
+ if _determined_configuration == -1:
+ contents = self._read_configuration()
+ if contents == "Deployment":
+ contents = "Release"
+ if contents == "Development":
+ contents = "Debug"
+ _determined_configuration = contents
+ return _determined_configuration
+
+ def _read_configuration(self):
+ configuration_path = os.path.join(self.build_directory(None),
+ "Configuration")
+ if not os.path.exists(configuration_path):
+ return None
+
+ with codecs.open(configuration_path, "r", "utf-8") as fh:
+ return fh.read().rstrip()
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/config_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/config_unittest.py
new file mode 100644
index 0000000..4674cba
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/config_unittest.py
@@ -0,0 +1,176 @@
+# 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 codecs
+import os
+import StringIO
+import unittest
+
+from webkitpy.common.system import outputcapture
+
+import config
+
+
+# FIXME: This makes StringIO objects work with "with". Remove
+# when we upgrade to 2.6.
+class NewStringIO(StringIO.StringIO):
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ pass
+
+
+class MockExecutive(object):
+ def __init__(self, output='', exit_code=0):
+ self._output = output
+ self._exit_code = exit_code
+
+ def run_command(self, arg_list, return_exit_code=False,
+ decode_output=False):
+ if return_exit_code:
+ return self._exit_code
+ return self._output
+
+
+class ConfigTest(unittest.TestCase):
+ def tearDown(self):
+ config.clear_cached_configuration()
+
+ def assertConfiguration(self, contents, expected):
+ # This tests that a configuration file containing
+ # _contents_ endsd up being interpreted as _expected_.
+ #
+ # FIXME: rewrite this when we have a filesystem abstraction we
+ # can mock out more easily.
+ config.clear_cached_configuration()
+ orig_open = codecs.open
+
+ def wrap_open(contents):
+ def open_wrapper(path, mode, encoding):
+ return NewStringIO(contents)
+ return open_wrapper
+
+ try:
+ orig_exists = os.path.exists
+ os.path.exists = lambda p: True
+ codecs.open = wrap_open(contents)
+
+ e = MockExecutive(output='foo')
+ c = config.Config(e)
+ self.assertEqual(c.default_configuration(), expected)
+ finally:
+ os.path.exists = orig_exists
+ codecs.open = orig_open
+
+ def test_build_directory_toplevel(self):
+ e = MockExecutive(output="toplevel")
+ c = config.Config(e)
+ self.assertEqual(c.build_directory(None), 'toplevel')
+
+ # Test again to check caching
+ self.assertEqual(c.build_directory(None), 'toplevel')
+
+ def test_build_directory__release(self):
+ e = MockExecutive(output="release")
+ c = config.Config(e)
+ self.assertEqual(c.build_directory('Release'), 'release')
+
+ def test_build_directory__debug(self):
+ e = MockExecutive(output="debug")
+ c = config.Config(e)
+ self.assertEqual(c.build_directory('Debug'), 'debug')
+
+ def test_build_directory__unknown(self):
+ e = MockExecutive(output="unknown")
+ c = config.Config(e)
+ self.assertRaises(KeyError, c.build_directory, 'Unknown')
+
+ def test_build_dumprendertree__success(self):
+ e = MockExecutive(exit_code=0)
+ c = config.Config(e)
+ 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):
+ e = MockExecutive(exit_code=-1)
+ c = config.Config(e)
+
+ oc = outputcapture.OutputCapture()
+ oc.capture_output()
+ self.assertFalse(c.build_dumprendertree('Debug'))
+ (out, err) = oc.restore_output()
+
+ oc.capture_output()
+ self.assertFalse(c.build_dumprendertree('Release'))
+ oc.restore_output()
+
+ def test_default_configuration__release(self):
+ self.assertConfiguration('Release', 'Release')
+
+ def test_default_configuration__debug(self):
+ self.assertConfiguration('Debug', 'Debug')
+
+ def test_default_configuration__deployment(self):
+ self.assertConfiguration('Deployment', 'Release')
+
+ def test_default_configuration__development(self):
+ self.assertConfiguration('Development', 'Debug')
+
+ def test_default_configuration__notfound(self):
+ # This tests what happens if the default configuration file
+ # doesn't exist.
+ config.clear_cached_configuration()
+ try:
+ orig_exists = os.path.exists
+ os.path.exists = lambda p: False
+ e = MockExecutive(output="foo")
+ c = config.Config(e)
+ self.assertEqual(c.default_configuration(), "Release")
+ finally:
+ os.path.exists = orig_exists
+
+ def test_default_configuration__unknown(self):
+ # Ignore the warning about an unknown configuration value.
+ oc = outputcapture.OutputCapture()
+ oc.capture_output()
+ self.assertConfiguration('Unknown', 'Unknown')
+ oc.restore_output()
+
+ def test_path_from_webkit_base(self, *comps):
+ c = config.Config(None)
+ self.assertTrue(c.path_from_webkit_base('foo'))
+
+ def test_webkit_base_dir(self):
+ c = config.Config(None)
+ self.assertTrue(c.webkit_base_dir())
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/http_lock.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/http_lock.py
index b2615a3..d65801d 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/http_lock.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/http_lock.py
@@ -29,27 +29,38 @@
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
+
+
+_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"):
- if not lock_path:
+ 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._process_lock_file_name = ""
+ self._executive = Executive()
+ # maximum wait time for the lock creation
+ self._guard_lock_max_wait = 1 * 60
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)
os.unlink(self._process_lock_file_name)
def _extract_lock_number(self, lock_file_name):
@@ -70,17 +81,6 @@ class HttpLock(object):
return 0
return self._extract_lock_number(lock_list[-1]) + 1
- def _check_pid(self, current_pid):
- """Return True if pid is alive, otherwise return False.
- FIXME: os.kill() doesn't work on Windows for checking if
- a pid is alive, so always return True"""
- if sys.platform in ('darwin', 'linux2'):
- try:
- os.kill(current_pid, 0)
- except OSError:
- return False
- return True
-
def _curent_lock_pid(self):
"""Return with the current lock pid. If the lock is not valid
it deletes the lock file."""
@@ -91,7 +91,8 @@ class HttpLock(object):
current_lock_file = open(lock_list[0], 'r')
current_pid = current_lock_file.readline()
current_lock_file.close()
- if not (current_pid and self._check_pid(int(current_pid))):
+ if not (current_pid and self._executive.check_running_pid(int(current_pid))):
+ _log.debug("Removing stuck lock file: %s" % lock_list[0])
os.unlink(lock_list[0])
return
except IOError, OSError:
@@ -102,22 +103,34 @@ class HttpLock(object):
"""The lock files are used to schedule the running test sessions in first
come first served order. The sequential 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
+
+ start_time = time.time()
while(True):
try:
sequential_guard_lock = os.open(self._guard_lock_file, os.O_CREAT | os.O_EXCL)
self._process_lock_file_name = (self._lock_file_path_prefix +
str(self._next_lock_number()))
lock_file = open(self._process_lock_file_name, 'w')
+ _log.debug("Creating lock file: %s" % self._process_lock_file_name)
lock_file.write(str(os.getpid()))
lock_file.close()
os.close(sequential_guard_lock)
os.unlink(self._guard_lock_file)
- break
+ return True
except OSError:
- pass
+ if time.time() - start_time > self._guard_lock_max_wait:
+ _log.debug("Lock does not created: %s" % str(sys.exc_info()))
+ return False
def wait_for_httpd_lock(self):
- """Create a lock file and wait until it's turn comes."""
- self._create_lock_file()
+ """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/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit.py
index 0d0d3e0..0b324f5 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit.py
@@ -60,7 +60,6 @@ class WebKitPort(base.Port):
def __init__(self, **kwargs):
base.Port.__init__(self, **kwargs)
- self._cached_build_root = None
self._cached_apache_path = None
# FIXME: disable pixel tests until they are run by default on the
@@ -358,12 +357,8 @@ class WebKitPort(base.Port):
'mac-tiger', 'mac-leopard', 'mac-snowleopard')
def _build_path(self, *comps):
- if not self._cached_build_root:
- self._cached_build_root = self._webkit_build_directory([
- "--configuration",
- self.flag_from_configuration(self.get_option('configuration')),
- ])
- return os.path.join(self._cached_build_root, *comps)
+ build_root = self._webkit_configuration_build_directory()
+ return os.path.join(build_root, *comps)
def _path_to_driver(self):
return self._build_path('DumpRenderTree')
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py b/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py
index 434058e..55c4558 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py
@@ -70,11 +70,11 @@ _log = logging.getLogger("webkitpy.layout_tests."
BASELINE_SUFFIXES = ['.txt', '.png', '.checksum']
REBASELINE_PLATFORM_ORDER = ['mac', 'win', 'win-xp', 'win-vista', 'linux']
-ARCHIVE_DIR_NAME_DICT = {'win': 'webkit-rel',
+ARCHIVE_DIR_NAME_DICT = {'win': 'Webkit_Win',
'win-vista': 'webkit-dbg-vista',
- 'win-xp': 'webkit-rel',
- 'mac': 'webkit-rel-mac5',
- 'linux': 'webkit-rel-linux',
+ 'win-xp': 'Webkit_Win',
+ 'mac': 'Webkit_Mac10_5',
+ 'linux': 'webkit-rel-linux64',
'win-canary': 'webkit-rel-webkit-org',
'win-vista-canary': 'webkit-dbg-vista',
'win-xp-canary': 'webkit-rel-webkit-org',
@@ -819,9 +819,8 @@ def get_host_port_object(options):
return port_obj
-def main(executive=Executive()):
- """Main function to produce new baselines."""
-
+def parse_options(args):
+ """Parse options and return a pair of host options and target options."""
option_parser = optparse.OptionParser()
option_parser.add_option('-v', '--verbose',
action='store_true',
@@ -874,7 +873,20 @@ def main(executive=Executive()):
help=('The target platform to rebaseline '
'("mac", "chromium", "qt", etc.). Defaults '
'to "chromium".'))
- options = option_parser.parse_args()[0]
+ options = option_parser.parse_args(args)[0]
+
+ target_options = copy.copy(options)
+ if options.target_platform == 'chromium':
+ target_options.chromium = True
+ options.tolerance = 0
+
+ return (options, target_options)
+
+
+def main(executive=Executive()):
+ """Main function to produce new baselines."""
+
+ (options, target_options) = parse_options(sys.argv[1:])
# We need to create three different Port objects over the life of this
# script. |target_port_obj| is used to determine configuration information:
@@ -882,9 +894,6 @@ def main(executive=Executive()):
# |port_obj| is used for runtime functionality like actually diffing
# Then we create a rebaselining port to actual find and manage the
# baselines.
- target_options = copy.copy(options)
- if options.target_platform == 'chromium':
- target_options.chromium = True
target_port_obj = port.get(None, target_options)
# Set up our logging format.
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py
index 8db31a6..7c55b94 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py
@@ -95,6 +95,15 @@ class TestRebaseliner(unittest.TestCase):
return rebaseline_chromium_webkit_tests.Rebaseliner(
host_port_obj, target_port_obj, platform, options)
+ def test_parse_options(self):
+ (options, target_options) = rebaseline_chromium_webkit_tests.parse_options([])
+ self.assertTrue(target_options.chromium)
+ self.assertEqual(options.tolerance, 0)
+
+ (options, target_options) = rebaseline_chromium_webkit_tests.parse_options(['--target-platform', 'qt'])
+ self.assertFalse(hasattr(target_options, 'chromium'))
+ self.assertEqual(options.tolerance, 0)
+
def test_noop(self):
# this method tests that was can at least instantiate an object, even
# if there is nothing to do.
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py b/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py
index 704180c..f360adc 100755
--- a/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py
@@ -85,6 +85,8 @@ _log = logging.getLogger("webkitpy.layout_tests.run_webkit_tests")
# Builder base URL where we have the archived test results.
BUILDER_BASE_URL = "http://build.chromium.org/buildbot/layout_test_results/"
+LAYOUT_TESTS_DIRECTORY = "LayoutTests" + os.sep
+
TestExpectationsFile = test_expectations.TestExpectationsFile
@@ -283,12 +285,17 @@ class TestRunner:
last_unexpected_results: list of unexpected results to retest, if any
"""
- paths = [arg for arg in args if arg and arg != '']
+ paths = [self._strip_test_dir_prefix(arg) for arg in args if arg and arg != '']
paths += last_unexpected_results
if self._options.test_list:
paths += read_test_files(self._options.test_list)
self._test_files = self._port.tests(paths)
+ def _strip_test_dir_prefix(self, path):
+ if path.startswith(LAYOUT_TESTS_DIRECTORY):
+ return path[len(LAYOUT_TESTS_DIRECTORY):]
+ return path
+
def lint(self):
# Creating the expecations for each platform/configuration pair does
# all the test list parsing and ensures it's correct syntax (e.g. no
@@ -404,9 +411,8 @@ class TestRunner:
# If we reached the end and we don't have enough tests, we run some
# from the beginning.
- if (self._options.run_chunk and
- (slice_end - slice_start < chunk_len)):
- extra = 1 + chunk_len - (slice_end - slice_start)
+ if slice_end - slice_start < chunk_len:
+ extra = chunk_len - (slice_end - slice_start)
extra_msg = (' last chunk is partial, appending [0:%d]' %
extra)
self._printer.print_expected(extra_msg)
@@ -470,9 +476,9 @@ class TestRunner:
def _get_dir_for_test_file(self, test_file):
"""Returns the highest-level directory by which to shard the given
test file."""
- index = test_file.rfind(os.sep + 'LayoutTests' + os.sep)
+ index = test_file.rfind(os.sep + LAYOUT_TESTS_DIRECTORY)
- test_file = test_file[index + len('LayoutTests/'):]
+ test_file = test_file[index + len(LAYOUT_TESTS_DIRECTORY):]
test_file_parts = test_file.split(os.sep, 1)
directory = test_file_parts[0]
test_file = test_file_parts[1]
@@ -741,17 +747,6 @@ class TestRunner:
if not result_summary:
return None
- # Do not start when http locking is enabled.
- if not self._options.wait_for_httpd:
- if self.needs_http():
- self._printer.print_update('Starting HTTP server ...')
- self._port.start_http_server()
-
- if self.needs_websocket():
- self._printer.print_update('Starting WebSocket server ...')
- self._port.start_websocket_server()
- # self._websocket_secure_server.Start()
-
return result_summary
def run(self, result_summary):
@@ -841,11 +836,6 @@ class TestRunner:
sys.stdout.flush()
_log.debug("flushing stderr")
sys.stderr.flush()
- if not self._options.wait_for_httpd:
- _log.debug("stopping http server")
- self._port.stop_http_server()
- _log.debug("stopping websocket server")
- self._port.stop_websocket_server()
_log.debug("stopping helper")
self._port.stop_helper()
@@ -948,14 +938,15 @@ class TestRunner:
if not self._options.test_results_server:
return
+ if not self._options.master_name:
+ _log.error("--test-results-server was set, but --master-name was not. Not uploading JSON files.")
+ return
+
_log.info("Uploading JSON files for builder: %s",
self._options.builder_name)
- attrs = [("builder", self._options.builder_name), ("testtype", "layout-tests")]
- # FIXME: master_name should be required if test_results_server is set.
- # Throw an error if master_name isn't set.
- if self._options.master_name:
- attrs.append(("master", self._options.master_name))
+ attrs = [("builder", self._options.builder_name), ("testtype", "layout-tests"),
+ ("master", self._options.master_name)]
json_files = ["expectations.json"]
if self._options.upload_full_results:
@@ -966,13 +957,6 @@ class TestRunner:
files = [(file, os.path.join(self._options.results_directory, file))
for file in json_files]
- # FIXME: Remove this. This is temporary debug logging.
- if self._options.builder_name.startswith("Webkit Linux"):
- for filename in files:
- _log.debug(filename[1])
- with codecs.open(filename[1], "r") as results_file:
- _log.debug("%s:\n%s" % (filename[0], results_file.read()))
-
uploader = test_results_uploader.TestResultsUploader(
self._options.test_results_server)
try:
@@ -1530,7 +1514,7 @@ def parse_args(args=None):
default=False,
help="Don't check the system dependencies (themes)"),
optparse.make_option("--use-drt", action="store_true",
- default=False,
+ default=None,
help="Use DumpRenderTree instead of test_shell"),
optparse.make_option("--accelerated-compositing",
action="store_true",
@@ -1612,9 +1596,6 @@ def parse_args(args=None):
optparse.make_option("--no-record-results", action="store_false",
default=True, dest="record_results",
help="Don't record the results."),
- optparse.make_option("--wait-for-httpd", action="store_true",
- default=False, dest="wait_for_httpd",
- help="Wait for http locks."),
# old-run-webkit-tests also has HTTP toggle options:
# --[no-]http Run (or do not run) http tests
# (default: run)
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py
index 0f09ffa..f21e7a5 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py
@@ -31,6 +31,7 @@
"""Unit tests for run_webkit_tests."""
import codecs
+import itertools
import logging
import os
import Queue
@@ -48,7 +49,9 @@ from webkitpy.common.system import user
from webkitpy.layout_tests import port
from webkitpy.layout_tests import run_webkit_tests
from webkitpy.layout_tests.layout_package import dump_render_tree_thread
-from webkitpy.layout_tests.port.test import TestPort
+from webkitpy.layout_tests.port.test import TestPort, TestDriver
+from webkitpy.python24.versioning import compare_version
+from webkitpy.test.skip import skip_if
from webkitpy.thirdparty.mock import Mock
@@ -61,41 +64,44 @@ class MockUser():
self.url = url
-def passing_run(args=[], port_obj=None, record_results=False,
+def passing_run(extra_args=None, port_obj=None, record_results=False,
tests_included=False):
- new_args = ['--print', 'nothing']
- if not '--platform' in args:
- new_args.extend(['--platform', 'test'])
+ extra_args = extra_args or []
+ args = ['--print', 'nothing']
+ if not '--platform' in extra_args:
+ args.extend(['--platform', 'test'])
if not record_results:
- new_args.append('--no-record-results')
- new_args.extend(args)
+ args.append('--no-record-results')
+ args.extend(extra_args)
if not tests_included:
# We use the glob to test that globbing works.
- new_args.extend(['passes',
- 'http/tests',
- 'http/tests/websocket/tests',
- 'failures/expected/*'])
- options, parsed_args = run_webkit_tests.parse_args(new_args)
- if port_obj is None:
+ args.extend(['passes',
+ 'http/tests',
+ 'http/tests/websocket/tests',
+ 'failures/expected/*'])
+ options, parsed_args = run_webkit_tests.parse_args(args)
+ if not port_obj:
port_obj = port.get(port_name=options.platform, options=options,
user=MockUser())
res = run_webkit_tests.run(port_obj, options, parsed_args)
return res == 0
-def logging_run(args=[], tests_included=False):
- new_args = ['--no-record-results']
- if not '--platform' in args:
- new_args.extend(['--platform', 'test'])
- new_args.extend(args)
+def logging_run(extra_args=None, port_obj=None, tests_included=False):
+ extra_args = extra_args or []
+ args = ['--no-record-results']
+ if not '--platform' in extra_args:
+ args.extend(['--platform', 'test'])
+ args.extend(extra_args)
if not tests_included:
- new_args.extend(['passes',
- 'http/tests',
- 'http/tests/websocket/tests',
- 'failures/expected/*'])
- options, parsed_args = run_webkit_tests.parse_args(new_args)
+ args.extend(['passes',
+ 'http/tests',
+ 'http/tests/websocket/tests',
+ 'failures/expected/*'])
+ options, parsed_args = run_webkit_tests.parse_args(args)
user = MockUser()
- port_obj = port.get(port_name=options.platform, options=options, user=user)
+ if not port_obj:
+ port_obj = port.get(port_name=options.platform, options=options, user=user)
buildbot_output = array_stream.ArrayStream()
regular_output = array_stream.ArrayStream()
res = run_webkit_tests.run(port_obj, options, parsed_args,
@@ -104,6 +110,54 @@ def logging_run(args=[], tests_included=False):
return (res, buildbot_output, regular_output, user)
+def get_tests_run(extra_args=None, tests_included=False, flatten_batches=False):
+ extra_args = extra_args or []
+ args = [
+ '--print', 'nothing',
+ '--platform', 'test',
+ '--no-record-results',
+ '--child-processes', '1']
+ args.extend(extra_args)
+ if not tests_included:
+ # Not including http tests since they get run out of order (that
+ # behavior has its own test, see test_get_test_file_queue)
+ args.extend(['passes', 'failures'])
+ options, parsed_args = run_webkit_tests.parse_args(args)
+ user = MockUser()
+
+ test_batches = []
+
+ class RecordingTestDriver(TestDriver):
+ def __init__(self, port, image_path, options):
+ TestDriver.__init__(self, port, image_path, options, executive=None)
+ self._current_test_batch = None
+
+ def poll(self):
+ # So that we don't create a new driver for every test
+ return None
+
+ def stop(self):
+ self._current_test_batch = None
+
+ def run_test(self, uri, timeoutms, image_hash):
+ if self._current_test_batch is None:
+ self._current_test_batch = []
+ test_batches.append(self._current_test_batch)
+ self._current_test_batch.append(self._port.uri_to_test_name(uri))
+ return TestDriver.run_test(self, uri, timeoutms, image_hash)
+
+ class RecordingTestPort(TestPort):
+ def create_driver(self, image_path, options):
+ return RecordingTestDriver(self, image_path, options)
+
+ recording_port = RecordingTestPort(options=options, user=user)
+ logging_run(extra_args=args, port_obj=recording_port, tests_included=True)
+
+ if flatten_batches:
+ return list(itertools.chain(*test_batches))
+
+ return test_batches
+
class MainTest(unittest.TestCase):
def test_accelerated_compositing(self):
# This just tests that we recognize the command line args
@@ -119,8 +173,9 @@ class MainTest(unittest.TestCase):
self.assertTrue(passing_run())
def test_batch_size(self):
- # FIXME: verify # of tests run
- self.assertTrue(passing_run(['--batch-size', '2']))
+ batch_tests_run = get_tests_run(['--batch-size', '2'])
+ for batch in batch_tests_run:
+ self.assertTrue(len(batch) <= 2, '%s had too many tests' % ', '.join(batch))
def test_child_process_1(self):
(res, buildbot_output, regular_output, user) = logging_run(
@@ -194,8 +249,15 @@ class MainTest(unittest.TestCase):
self.assertTrue(passing_run(['--randomize-order']))
def test_run_chunk(self):
- # FIXME: verify # of tests run
- self.assertTrue(passing_run(['--run-chunk', '1:4']))
+ # Test that we actually select the right chunk
+ all_tests_run = get_tests_run(flatten_batches=True)
+ chunk_tests_run = get_tests_run(['--run-chunk', '1:4'], flatten_batches=True)
+ self.assertEquals(all_tests_run[4:8], chunk_tests_run)
+
+ # Test that we wrap around if the number of tests is not evenly divisible by the chunk size
+ tests_to_run = ['passes/error.html', 'passes/image.html', 'passes/platform_image.html', 'passes/text.html']
+ chunk_tests_run = get_tests_run(['--run-chunk', '1:3'] + tests_to_run, tests_included=True, flatten_batches=True)
+ self.assertEquals(['passes/text.html', 'passes/error.html', 'passes/image.html'], chunk_tests_run)
def test_run_force(self):
# This raises an exception because we run
@@ -203,23 +265,33 @@ class MainTest(unittest.TestCase):
self.assertRaises(ValueError, logging_run, ['--force'])
def test_run_part(self):
- # FIXME: verify # of tests run
- self.assertTrue(passing_run(['--run-part', '1:2']))
+ # Test that we actually select the right part
+ tests_to_run = ['passes/error.html', 'passes/image.html', 'passes/platform_image.html', 'passes/text.html']
+ tests_run = get_tests_run(['--run-part', '1:2'] + tests_to_run, tests_included=True, flatten_batches=True)
+ self.assertEquals(['passes/error.html', 'passes/image.html'], tests_run)
+
+ # Test that we wrap around if the number of tests is not evenly divisible by the chunk size
+ # (here we end up with 3 parts, each with 2 tests, and we only have 4 tests total, so the
+ # last part repeats the first two tests).
+ chunk_tests_run = get_tests_run(['--run-part', '3:3'] + tests_to_run, tests_included=True, flatten_batches=True)
+ self.assertEquals(['passes/error.html', 'passes/image.html'], chunk_tests_run)
def test_run_singly(self):
- self.assertTrue(passing_run(['--run-singly']))
+ batch_tests_run = get_tests_run(['--run-singly'])
+ for batch in batch_tests_run:
+ self.assertEquals(len(batch), 1, '%s had too many tests' % ', '.join(batch))
def test_single_file(self):
- # FIXME: verify # of tests run
- self.assertTrue(passing_run(['passes/text.html'], tests_included=True))
+ tests_run = get_tests_run(['passes/text.html'], tests_included=True, flatten_batches=True)
+ self.assertEquals(['passes/text.html'], tests_run)
def test_test_list(self):
filename = tempfile.mktemp()
tmpfile = file(filename, mode='w+')
tmpfile.write('passes/text.html')
tmpfile.close()
- self.assertTrue(passing_run(['--test-list=%s' % filename],
- tests_included=True))
+ tests_run = get_tests_run(['--test-list=%s' % filename], tests_included=True, flatten_batches=True)
+ self.assertEquals(['passes/text.html'], tests_run)
os.remove(filename)
res, out, err, user = logging_run(['--test-list=%s' % filename],
tests_included=True)
@@ -271,7 +343,7 @@ class MainTest(unittest.TestCase):
def get_port_for_run(args):
options, parsed_args = run_webkit_tests.parse_args(args)
test_port = ImageDiffTestPort(options=options, user=MockUser())
- passing_run(args=args, port_obj=test_port, tests_included=True)
+ passing_run(args, port_obj=test_port, tests_included=True)
return test_port
base_args = ['--pixel-tests', 'failures/expected/*']
@@ -287,6 +359,8 @@ class MainTest(unittest.TestCase):
test_port = get_port_for_run(base_args)
self.assertEqual(None, test_port.tolerance_used_for_diff_image)
+MainTest = skip_if(MainTest, sys.platform == 'cygwin' and compare_version(sys, '2.6')[0] < 0, 'new-run-webkit-tests tests hang on Cygwin Python 2.5.2')
+
def _mocked_open(original_open, file_list):
def _wrapper(name, mode, encoding):
diff --git a/WebKitTools/Scripts/webkitpy/style/checker.py b/WebKitTools/Scripts/webkitpy/style/checker.py
index 11e3e33..fb93eb9 100644
--- a/WebKitTools/Scripts/webkitpy/style/checker.py
+++ b/WebKitTools/Scripts/webkitpy/style/checker.py
@@ -115,7 +115,13 @@ _PATH_RULES_SPECIFIER = [
# Files in these directories are consumers of the WebKit
# API and therefore do not follow the same header including
# discipline as WebCore.
- (["WebKitTools/WebKitAPITest/",
+
+ ([# TestNetscapePlugIn has no config.h and uses funny names like
+ # NPP_SetWindow.
+ "WebKitTools/DumpRenderTree/TestNetscapePlugIn/",
+ # The API test harnesses have no config.h and use funny macros like
+ # TEST_CLASS_NAME.
+ "WebKitTools/WebKitAPITest/",
"WebKitTools/TestWebKitAPI/"],
["-build/include",
"-readability/naming"]),
diff --git a/WebKitTools/Scripts/webkitpy/style/optparser.py b/WebKitTools/Scripts/webkitpy/style/optparser.py
index 3ba0fae..f4e9923 100644
--- a/WebKitTools/Scripts/webkitpy/style/optparser.py
+++ b/WebKitTools/Scripts/webkitpy/style/optparser.py
@@ -145,6 +145,7 @@ class CommandOptionValues(object):
def __init__(self,
filter_rules=None,
git_commit=None,
+ diff_files=None,
is_verbose=False,
min_confidence=1,
output_format="emacs"):
@@ -163,6 +164,7 @@ class CommandOptionValues(object):
self.filter_rules = filter_rules
self.git_commit = git_commit
+ self.diff_files = diff_files
self.is_verbose = is_verbose
self.min_confidence = min_confidence
self.output_format = output_format
@@ -174,6 +176,8 @@ class CommandOptionValues(object):
return False
if self.git_commit != other.git_commit:
return False
+ if self.diff_files != other.diff_files:
+ return False
if self.is_verbose != other.is_verbose:
return False
if self.min_confidence != other.min_confidence:
@@ -214,6 +218,8 @@ class ArgumentPrinter(object):
flags['filter'] = ",".join(filter_rules)
if options.git_commit:
flags['git-commit'] = options.git_commit
+ if options.diff_files:
+ flags['diff_files'] = options.diff_files
flag_string = ''
# Alphabetizing lets us unit test this method.
@@ -308,6 +314,9 @@ class ArgumentParser(object):
parser.add_option("-g", "--git-diff", "--git-commit",
metavar="COMMIT", dest="git_commit", help=git_commit_help,)
+ diff_files_help = "diff the files passed on the command line rather than checking the style of every line"
+ parser.add_option("--diff-files", action="store_true", dest="diff_files", default=False, help=diff_files_help)
+
min_confidence_help = ("set the minimum confidence of style errors "
"to report. Can be an integer 1-5, with 1 "
"displaying all errors. Defaults to %default.")
@@ -409,6 +418,7 @@ class ArgumentParser(object):
filter_value = options.filter_value
git_commit = options.git_commit
+ diff_files = options.diff_files
is_verbose = options.is_verbose
min_confidence = options.min_confidence
output_format = options.output_format
@@ -420,10 +430,6 @@ class ArgumentParser(object):
# Validate user-provided values.
- if paths and git_commit:
- self._parse_error('You cannot provide both paths and a git '
- 'commit at the same time.')
-
min_confidence = int(min_confidence)
if (min_confidence < 1) or (min_confidence > 5):
self._parse_error('option --min-confidence: invalid integer: '
@@ -442,6 +448,7 @@ class ArgumentParser(object):
options = CommandOptionValues(filter_rules=filter_rules,
git_commit=git_commit,
+ diff_files=diff_files,
is_verbose=is_verbose,
min_confidence=min_confidence,
output_format=output_format)
diff --git a/WebKitTools/Scripts/webkitpy/style/optparser_unittest.py b/WebKitTools/Scripts/webkitpy/style/optparser_unittest.py
index b7e3eda..a6b64da 100644
--- a/WebKitTools/Scripts/webkitpy/style/optparser_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/style/optparser_unittest.py
@@ -136,11 +136,6 @@ class ArgumentParserTest(LoggingTestCase):
self.assertLog(['ERROR: Invalid filter rule "build": '
'every rule must start with + or -.\n'])
parse(['--filter=+build']) # works
- # Pass files and git-commit at the same time.
- self.assertRaises(SystemExit, parse, ['--git-commit=committish',
- 'file.txt'])
- self.assertLog(['ERROR: You cannot provide both paths and '
- 'a git commit at the same time.\n'])
def test_parse_default_arguments(self):
parse = self._parse
@@ -151,6 +146,7 @@ class ArgumentParserTest(LoggingTestCase):
self.assertEquals(options.filter_rules, [])
self.assertEquals(options.git_commit, None)
+ self.assertEquals(options.diff_files, False)
self.assertEquals(options.is_verbose, False)
self.assertEquals(options.min_confidence, 3)
self.assertEquals(options.output_format, 'vs7')
@@ -171,6 +167,8 @@ class ArgumentParserTest(LoggingTestCase):
self.assertEquals(options.git_commit, 'commit')
(files, options) = parse(['--verbose'])
self.assertEquals(options.is_verbose, True)
+ (files, options) = parse(['--diff-files', 'file.txt'])
+ self.assertEquals(options.diff_files, True)
# Pass user_rules.
(files, options) = parse(['--filter=+build,-whitespace'])
diff --git a/WebKitTools/Scripts/webkitpy/style_references.py b/WebKitTools/Scripts/webkitpy/style_references.py
index 34f3bff..a21e931 100644
--- a/WebKitTools/Scripts/webkitpy/style_references.py
+++ b/WebKitTools/Scripts/webkitpy/style_references.py
@@ -69,6 +69,6 @@ class WebKitCheckout(object):
"""Return the checkout root as an absolute path."""
return self._scm.checkout_root
- def create_patch(self, git_commit):
- return self._scm.create_patch(git_commit)
-
+ def create_patch(self, git_commit, changed_files=None):
+ # FIXME: SCM.create_patch should understand how to handle None.
+ return self._scm.create_patch(git_commit, changed_files=changed_files or [])
diff --git a/WebKitTools/Scripts/webkitpy/test/cat.py b/WebKitTools/Scripts/webkitpy/test/cat.py
new file mode 100644
index 0000000..ae1e143
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/test/cat.py
@@ -0,0 +1,42 @@
+# Copyright (C) 2010 Apple 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:
+# 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 APPLE INC. AND ITS 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 APPLE INC. OR ITS 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.path
+import sys
+
+# Add WebKitTools/Scripts to the path to ensure we can find webkitpy.
+sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
+
+from webkitpy.common.system import fileutils
+
+
+def command_arguments(*args):
+ return ['python', __file__] + list(args)
+
+
+def main():
+ fileutils.make_stdout_binary()
+ sys.stdout.write(sys.stdin.read())
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/WebKitTools/Scripts/webkitpy/test/cat_unittest.py b/WebKitTools/Scripts/webkitpy/test/cat_unittest.py
new file mode 100644
index 0000000..4ed1f67
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/test/cat_unittest.py
@@ -0,0 +1,52 @@
+# Copyright (C) 2010 Apple 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:
+# 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 APPLE INC. AND ITS 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 APPLE INC. OR ITS 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 os.path
+import sys
+import unittest
+
+from webkitpy.common.system import executive, outputcapture
+from webkitpy.test import cat
+
+
+class CatTest(outputcapture.OutputCaptureTestCaseBase):
+ def assert_cat(self, input):
+ saved_stdin = sys.stdin
+ sys.stdin = StringIO.StringIO(input)
+ cat.main()
+ self.assertStdout(input)
+ sys.stdin = saved_stdin
+
+ def test_basic(self):
+ self.assert_cat('foo bar baz\n')
+
+ def test_no_newline(self):
+ self.assert_cat('foo bar baz')
+
+ def test_unicode(self):
+ self.assert_cat(u'WebKit \u2661 Tor Arne Vestb\u00F8!')
+
+ def test_as_command(self):
+ input = 'foo bar baz\n'
+ output = executive.Executive().run_command(cat.command_arguments(), input=input)
+ self.assertEqual(input, output)
diff --git a/WebKitTools/Scripts/webkitpy/test/echo.py b/WebKitTools/Scripts/webkitpy/test/echo.py
new file mode 100644
index 0000000..f7468f7
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/test/echo.py
@@ -0,0 +1,52 @@
+# Copyright (C) 2010 Apple 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:
+# 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 APPLE INC. AND ITS 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 APPLE INC. OR ITS 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.path
+import sys
+
+# Add WebKitTools/Scripts to the path to ensure we can find webkitpy.
+sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
+
+from webkitpy.common.system import fileutils
+
+
+def command_arguments(*args):
+ return ['python', __file__] + list(args)
+
+
+def main(args=None):
+ if args is None:
+ args = sys.argv[1:]
+
+ fileutils.make_stdout_binary()
+
+ print_newline = True
+ if len(args) and args[0] == '-n':
+ print_newline = False
+ del args[0]
+ sys.stdout.write(' '.join(args))
+ if print_newline:
+ sys.stdout.write('\n')
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/WebKitTools/Scripts/webkitpy/test/echo_unittest.py b/WebKitTools/Scripts/webkitpy/test/echo_unittest.py
new file mode 100644
index 0000000..bc13b5e
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/test/echo_unittest.py
@@ -0,0 +1,64 @@
+# Copyright (C) 2010 Apple 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:
+# 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 APPLE INC. AND ITS 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 APPLE INC. OR ITS 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.path
+import sys
+import unittest
+
+from webkitpy.common.system import executive, outputcapture
+from webkitpy.test import echo
+
+
+class EchoTest(outputcapture.OutputCaptureTestCaseBase):
+ def test_basic(self):
+ echo.main(['foo', 'bar', 'baz'])
+ self.assertStdout('foo bar baz\n')
+
+ def test_no_newline(self):
+ echo.main(['-n', 'foo', 'bar', 'baz'])
+ self.assertStdout('foo bar baz')
+
+ def test_unicode(self):
+ echo.main([u'WebKit \u2661', 'Tor Arne', u'Vestb\u00F8!'])
+ self.assertStdout(u'WebKit \u2661 Tor Arne Vestb\u00F8!\n')
+
+ def test_argument_order(self):
+ echo.main(['foo', '-n', 'bar'])
+ self.assertStdout('foo -n bar\n')
+
+ def test_empty_arguments(self):
+ old_argv = sys.argv
+ sys.argv = ['echo.py', 'foo', 'bar', 'baz']
+ echo.main([])
+ self.assertStdout('\n')
+ sys.argv = old_argv
+
+ def test_no_arguments(self):
+ old_argv = sys.argv
+ sys.argv = ['echo.py', 'foo', 'bar', 'baz']
+ echo.main()
+ self.assertStdout('foo bar baz\n')
+ sys.argv = old_argv
+
+ def test_as_command(self):
+ output = executive.Executive().run_command(echo.command_arguments('foo', 'bar', 'baz'))
+ self.assertEqual(output, 'foo bar baz\n')
diff --git a/WebKitTools/Scripts/webkitpy/test/skip.py b/WebKitTools/Scripts/webkitpy/test/skip.py
new file mode 100644
index 0000000..8587d56
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/test/skip.py
@@ -0,0 +1,52 @@
+# Copyright (C) 2010 Apple 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:
+# 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 APPLE INC. AND ITS 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 APPLE INC. OR ITS 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 logging
+
+_log = logging.getLogger(__name__)
+
+
+def skip_if(klass, condition, message=None, logger=None):
+ """Makes all test_* methods in a given class no-ops if the given condition
+ is False. Backported from Python 3.1+'s unittest.skipIf decorator."""
+ if not logger:
+ logger = _log
+ if not condition:
+ return klass
+ for name in dir(klass):
+ attr = getattr(klass, name)
+ if not callable(attr):
+ continue
+ if not name.startswith('test_'):
+ continue
+ setattr(klass, name, _skipped_method(attr, message, logger))
+ klass._printed_skipped_message = False
+ return klass
+
+
+def _skipped_method(method, message, logger):
+ def _skip(*args):
+ if method.im_class._printed_skipped_message:
+ return
+ method.im_class._printed_skipped_message = True
+ logger.info('Skipping %s.%s: %s' % (method.__module__, method.im_class.__name__, message))
+ return _skip
diff --git a/WebKitTools/Scripts/webkitpy/test/skip_unittest.py b/WebKitTools/Scripts/webkitpy/test/skip_unittest.py
new file mode 100644
index 0000000..f61a1bb
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/test/skip_unittest.py
@@ -0,0 +1,77 @@
+# Copyright (C) 2010 Apple 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:
+# 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 APPLE INC. AND ITS 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 APPLE INC. OR ITS 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 logging
+import unittest
+
+from webkitpy.test.skip import skip_if
+
+
+class SkipTest(unittest.TestCase):
+ def setUp(self):
+ self.logger = logging.getLogger(__name__)
+
+ self.old_level = self.logger.level
+ self.logger.setLevel(logging.INFO)
+
+ self.old_propagate = self.logger.propagate
+ self.logger.propagate = False
+
+ self.log_stream = StringIO.StringIO()
+ self.handler = logging.StreamHandler(self.log_stream)
+ self.logger.addHandler(self.handler)
+
+ self.foo_was_called = False
+
+ def tearDown(self):
+ self.logger.removeHandler(self.handler)
+ self.propagate = self.old_propagate
+ self.logger.setLevel(self.old_level)
+
+ def create_fixture_class(self):
+ class TestSkipFixture(object):
+ def __init__(self, callback):
+ self.callback = callback
+
+ def test_foo(self):
+ self.callback()
+
+ return TestSkipFixture
+
+ def foo_callback(self):
+ self.foo_was_called = True
+
+ def test_skip_if_false(self):
+ klass = skip_if(self.create_fixture_class(), False, 'Should not see this message.', logger=self.logger)
+ klass(self.foo_callback).test_foo()
+ self.assertEqual(self.log_stream.getvalue(), '')
+ self.assertTrue(self.foo_was_called)
+
+ def test_skip_if_true(self):
+ klass = skip_if(self.create_fixture_class(), True, 'Should see this message.', logger=self.logger)
+ klass(self.foo_callback).test_foo()
+ self.assertEqual(self.log_stream.getvalue(), 'Skipping webkitpy.test.skip_unittest.TestSkipFixture: Should see this message.\n')
+ self.assertFalse(self.foo_was_called)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/__init__.py b/WebKitTools/Scripts/webkitpy/tool/commands/__init__.py
index 9bdec8f..a070324 100644
--- a/WebKitTools/Scripts/webkitpy/tool/commands/__init__.py
+++ b/WebKitTools/Scripts/webkitpy/tool/commands/__init__.py
@@ -2,4 +2,5 @@
from webkitpy.tool.commands.prettydiff import PrettyDiff
from webkitpy.tool.commands.rebaseline import Rebaseline
+from webkitpy.tool.commands.rebaselineserver import RebaselineServer
# FIXME: Add the rest of the commands here.
diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/index.html b/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/index.html
new file mode 100644
index 0000000..5667cd2
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/index.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!--
+ 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.
+-->
+<html>
+<head>
+ <title>Layout Test Rebaseline Server</title>
+ <link rel="stylesheet" href="/main.css" type="text/css">
+ <script src="/main.js"></script>
+</head>
+<body class="loading">
+ <a href="/quitquitquit">Exit</a>
+</body>
+</html>
diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/main.css b/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/main.css
new file mode 100644
index 0000000..35bd6a5
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/main.css
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+body {
+ font-size: 12px;
+ font-family: Helvetica, Arial, sans-serif;
+ padding: 0;
+ margin: 0;
+}
+
+.loading {
+ opacity: 0.5;
+}
+
+div {
+ margin: 0;
+}
+
+a, .link {
+ color: #aaf;
+ text-decoration: underline;
+ cursor: pointer;
+}
+
+.link.selected {
+ color: #fff;
+ font-weight: bold;
+ text-decoration: none;
+}
diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/main.js b/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/main.js
new file mode 100644
index 0000000..55f19a4
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/main.js
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+function main()
+{
+ document.body.classList.remove('loading');
+}
+
+window.addEventListener('DOMContentLoaded', main);
diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/rebaselineserver.py b/WebKitTools/Scripts/webkitpy/tool/commands/rebaselineserver.py
new file mode 100644
index 0000000..0a37677
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/tool/commands/rebaselineserver.py
@@ -0,0 +1,157 @@
+# 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.
+
+"""Starts a local HTTP server which displays layout test failures (given a test
+results directory), provides comparisons of expected and actual results (both
+images and text) and allows one-click rebaselining of tests."""
+from __future__ import with_statement
+
+import codecs
+import datetime
+import mimetypes
+import os
+import os.path
+import shutil
+import threading
+import time
+import urlparse
+import BaseHTTPServer
+
+from optparse import make_option
+from wsgiref.handlers import format_date_time
+
+from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand
+
+
+class RebaselineHTTPServer(BaseHTTPServer.HTTPServer):
+ def __init__(self, httpd_port, results_directory):
+ BaseHTTPServer.HTTPServer.__init__(self, ("", httpd_port), RebaselineHTTPRequestHandler)
+ self.results_directory = results_directory
+
+
+class RebaselineHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ STATIC_FILE_NAMES = frozenset([
+ "index.html",
+ "main.js",
+ "main.css",
+ ])
+
+ STATIC_FILE_DIRECTORY = os.path.join(
+ os.path.dirname(__file__), "data", "rebaselineserver")
+
+ def do_GET(self):
+ self._handle_request()
+
+ def do_POST(self):
+ self._handle_request()
+
+ def _handle_request(self):
+ # Parse input.
+ if "?" in self.path:
+ path, query_string = self.path.split("?", 1)
+ self.query = urlparse.parse_qs(query_string)
+ else:
+ path = self.path
+ self.query = {}
+ function_or_file_name = path[1:] or "index.html"
+
+ # See if a static file matches.
+ if function_or_file_name in RebaselineHTTPRequestHandler.STATIC_FILE_NAMES:
+ self._serve_static_file(function_or_file_name)
+ return
+
+ # See if a class method matches.
+ function_name = function_or_file_name.replace(".", "_")
+ if not hasattr(self, function_name):
+ self.send_error(404, "Unknown function %s" % function_name)
+ return
+ if function_name[0] == "_":
+ self.send_error(
+ 401, "Not allowed to invoke private or protected methods")
+ return
+ function = getattr(self, function_name)
+ function()
+
+ def _serve_static_file(self, static_path):
+ self._serve_file(os.path.join(
+ RebaselineHTTPRequestHandler.STATIC_FILE_DIRECTORY, static_path))
+
+ def quitquitquit(self):
+ self.send_response(200)
+ self.send_header("Content-type", "text/plain")
+ self.end_headers()
+ self.wfile.write("Quit.\n")
+
+ # Shutdown has to happen on another thread from the server's thread,
+ # otherwise there's a deadlock
+ threading.Thread(target=lambda: self.server.shutdown()).start()
+
+ def _serve_file(self, file_path, cacheable_seconds=0):
+ if not os.path.exists(file_path):
+ self.send_error(404, "File not found")
+ return
+ with codecs.open(file_path, "rb") as static_file:
+ self.send_response(200)
+ self.send_header("Content-Length", os.path.getsize(file_path))
+ mime_type, encoding = mimetypes.guess_type(file_path)
+ if mime_type:
+ self.send_header("Content-type", mime_type)
+
+ if cacheable_seconds:
+ expires_time = (datetime.datetime.now() +
+ datetime.timedelta(0, cacheable_seconds))
+ expires_formatted = format_date_time(
+ time.mktime(expires_time.timetuple()))
+ self.send_header("Expires", expires_formatted)
+ self.end_headers()
+
+ shutil.copyfileobj(static_file, self.wfile)
+
+
+class RebaselineServer(AbstractDeclarativeCommand):
+ name = "rebaseline-server"
+ help_text = __doc__
+ argument_names = "/path/to/results/directory"
+
+ def __init__(self):
+ options = [
+ make_option("--httpd-port", action="store", type="int", default=8127, help="Port to use for the the rebaseline HTTP server"),
+ ]
+ AbstractDeclarativeCommand.__init__(self, options=options)
+
+ def execute(self, options, args, tool):
+ results_directory = args[0]
+
+ print "Starting server at http://localhost:%d/" % options.httpd_port
+ print ("Use the 'Exit' link in the UI, http://localhost:%d/"
+ "quitquitquit or Ctrl-C to stop") % options.httpd_port
+
+ httpd = RebaselineHTTPServer(
+ httpd_port=options.httpd_port,
+ results_directory=results_directory)
+ httpd.serve_forever()
diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/upload.py b/WebKitTools/Scripts/webkitpy/tool/commands/upload.py
index ed91f5a..e12c8e2 100644
--- a/WebKitTools/Scripts/webkitpy/tool/commands/upload.py
+++ b/WebKitTools/Scripts/webkitpy/tool/commands/upload.py
@@ -92,6 +92,32 @@ class CleanPendingCommit(AbstractDeclarativeCommand):
self._tool.bugs.obsolete_attachment(patch.id(), message)
+# FIXME: This should be share more logic with AssignToCommitter and CleanPendingCommit
+class CleanReviewQueue(AbstractDeclarativeCommand):
+ name = "clean-review-queue"
+ help_text = "Clear r? on obsolete patches so they do not appear in the pending-commit list."
+
+ def execute(self, options, args, tool):
+ queue_url = "http://webkit.org/pending-review"
+ # We do this inefficient dance to be more like webkit.org/pending-review
+ # bugs.queries.fetch_bug_ids_from_review_queue() doesn't return
+ # closed bugs, but folks using /pending-review will see them. :(
+ for patch_id in tool.bugs.queries.fetch_attachment_ids_from_review_queue():
+ patch = self._tool.bugs.fetch_attachment(patch_id)
+ if not patch.review() == "?":
+ continue
+ attachment_obsolete_modifier = ""
+ if patch.is_obsolete():
+ attachment_obsolete_modifier = "obsolete "
+ elif patch.bug().is_closed():
+ bug_closed_explanation = " If you would like this patch reviewed, please attach it to a new bug (or re-open this bug before marking it for review again)."
+ else:
+ # Neither the patch was obsolete or the bug was closed, next patch...
+ continue
+ message = "Cleared review? from %sattachment %s so that this bug does not appear in %s.%s" % (attachment_obsolete_modifier, patch.id(), queue_url, bug_closed_explanation)
+ self._tool.bugs.obsolete_attachment(patch.id(), message)
+
+
class AssignToCommitter(AbstractDeclarativeCommand):
name = "assign-to-committer"
help_text = "Assign bug to whoever attached the most recent r+'d patch"
diff --git a/WebKitTools/Scripts/webkitpy/tool/mocktool.py b/WebKitTools/Scripts/webkitpy/tool/mocktool.py
index af232d9..b6ee95f 100644
--- a/WebKitTools/Scripts/webkitpy/tool/mocktool.py
+++ b/WebKitTools/Scripts/webkitpy/tool/mocktool.py
@@ -424,6 +424,9 @@ class MockSCM(Mock):
# will actually be the root. Since getcwd() is wrong, use a globally fake root for now.
self.checkout_root = self.fake_checkout_root
+ def changed_files(self, git_commit=None):
+ return ["MockFile1"]
+
def create_patch(self, git_commit, changed_files=None):
return "Patch1"
diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/checkstyle.py b/WebKitTools/Scripts/webkitpy/tool/steps/checkstyle.py
index af38214..af66c50 100644
--- a/WebKitTools/Scripts/webkitpy/tool/steps/checkstyle.py
+++ b/WebKitTools/Scripts/webkitpy/tool/steps/checkstyle.py
@@ -52,6 +52,9 @@ class CheckStyle(AbstractStep):
args.append("--git-commit")
args.append(self._options.git_commit)
+ args.append("--diff-files")
+ args.extend(self._changed_files(state))
+
try:
self._run_script("check-webkit-style", args)
except ScriptError, e: