summaryrefslogtreecommitdiffstats
path: root/WebKitTools/Scripts
diff options
context:
space:
mode:
Diffstat (limited to 'WebKitTools/Scripts')
-rwxr-xr-xWebKitTools/Scripts/build-webkit21
-rwxr-xr-xWebKitTools/Scripts/old-run-webkit-tests3
-rwxr-xr-xWebKitTools/Scripts/prepare-ChangeLog2
-rwxr-xr-xWebKitTools/Scripts/run-chromium-webkit-unit-tests51
-rwxr-xr-xWebKitTools/Scripts/test-webkitpy19
-rw-r--r--WebKitTools/Scripts/webkitdirs.pm79
-rw-r--r--WebKitTools/Scripts/webkitpy/common/config/committers.py3
-rw-r--r--WebKitTools/Scripts/webkitpy/common/net/buildbot.py64
-rw-r--r--WebKitTools/Scripts/webkitpy/common/net/buildbot_unittest.py73
-rw-r--r--WebKitTools/Scripts/webkitpy/common/net/credentials.py20
-rw-r--r--WebKitTools/Scripts/webkitpy/common/net/credentials_unittest.py23
-rw-r--r--WebKitTools/Scripts/webkitpy/common/net/failuremap.py48
-rw-r--r--WebKitTools/Scripts/webkitpy/common/net/regressionwindow.py48
-rw-r--r--WebKitTools/Scripts/webkitpy/common/system/user.py35
-rw-r--r--WebKitTools/Scripts/webkitpy/common/system/user_unittest.py47
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/checksum-expected.checksum1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/checksum-expected.png1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/checksum-expected.txt1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/checksum.html1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/crash.html1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/exception.html1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/hang.html1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image-expected.checksum1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image-expected.png1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image-expected.txt1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image.html1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image_checksum-expected.checksum1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image_checksum-expected.png1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image_checksum-expected.txt1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image_checksum.html1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/keyboard.html1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/missing_check-expected.png1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/missing_check-expected.txt1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/missing_check.html1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/missing_image.html1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/missing_text.html1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/text-expected.txt1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/text.html1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/timeout.html1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/unexpected/text-image-checksum-expected.checksum1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/unexpected/text-image-checksum-expected.png1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/unexpected/text-image-checksum-expected.txt1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/failures/unexpected/text-image-checksum.html1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/http/tests/passes/text-expected.txt1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/http/tests/passes/text.html1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/http/tests/ssl/text-expected.txt1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/http/tests/ssl/text.html1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/passes/error-expected.txt1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/passes/error.html1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/passes/image-expected.checksum1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/passes/image-expected.png1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/passes/image-expected.txt1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/passes/image.html1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/passes/platform_image-expected.checksum1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/passes/platform_image-expected.png1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/passes/platform_image-expected.txt1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/passes/platform_image.html1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/passes/text-expected.txt1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/passes/text.html1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/platform/test/passes/platform_image-expected.checksum1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/platform/test/passes/platform_image-expected.png1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/platform/test/passes/platform_image-expected.txt1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/platform/test/test_expectations.txt13
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/resources/README.txt2
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/websocket/tests/passes/text-expected.txt1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/data/websocket/tests/passes/text.html1
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing.py17
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing_unittest.py12
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_expectations.py39
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_expectations_unittest.py7
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/base.py115
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/base_unittest.py16
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/chromium.py48
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py137
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py58
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_linux.py9
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_mac.py9
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py53
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win.py9
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/dryrun.py95
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/factory.py43
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/factory_unittest.py10
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome.py12
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome_unittest.py3
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/gtk.py7
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/mac.py7
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/port_testcase.py12
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/qt.py7
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/server_process.py2
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/test.py262
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/test_files.py (renamed from WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_files.py)14
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/test_files_unittest.py68
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/webkit.py59
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py2
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/win.py7
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py39
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py17
-rwxr-xr-xWebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py46
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py66
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/test_types/image_diff.py31
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/test_types/test_type_base.py10
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/test_types/text_diff.py21
-rw-r--r--WebKitTools/Scripts/webkitpy/style/checkers/test_expectations.py3
-rw-r--r--WebKitTools/Scripts/webkitpy/test/main.py12
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/bot/commitqueuetask.py158
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/bot/commitqueuetask_unittest.py194
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/bot/feeders.py73
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/bot/feeders_unittest.py70
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/bot/queueengine.py2
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/commands/download.py2
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/commands/openbugs.py4
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/commands/queries.py47
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/commands/queues.py253
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/commands/queues_unittest.py160
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/commands/queuestest.py5
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/commands/rebaseline.py9
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot.py14
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/commands/upload.py8
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/mocktool.py20
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/multicommandtool.py14
120 files changed, 2112 insertions, 905 deletions
diff --git a/WebKitTools/Scripts/build-webkit b/WebKitTools/Scripts/build-webkit
index cd43499..bc1e8ad 100755
--- a/WebKitTools/Scripts/build-webkit
+++ b/WebKitTools/Scripts/build-webkit
@@ -106,7 +106,7 @@ my @features = (
define => "ENABLE_EVENTSOURCE", default => 1, value => \$eventsourceSupport },
{ option => "filters", desc => "Toggle Filters support",
- define => "ENABLE_FILTERS", default => (isAppleWebKit() || isGtk() || isQt()), value => \$filtersSupport },
+ define => "ENABLE_FILTERS", default => (isAppleWebKit() || isGtk() || isQt() || isEfl()), value => \$filtersSupport },
{ option => "geolocation", desc => "Toggle Geolocation support",
define => "ENABLE_GEOLOCATION", default => (isAppleWebKit() || isGtk()), value => \$geolocationSupport },
@@ -245,13 +245,14 @@ Usage: $programName [options] [options to pass to build system]
--chromium Build the Chromium port on Mac/Win/Linux
--gtk Build the GTK+ port
--qt Build the Qt port
+ --efl Build the EFL port
--inspector-frontend Copy changes to the inspector front-end files to the build directory
--install-headers=<path> Set installation path for the headers (Qt only)
--install-libs=<path> Set installation path for the libraries (Qt only)
--v8 Use V8 as JavaScript engine (Qt only)
- --prefix=<path> Set installation prefix to the given path (Gtk only)
+ --prefix=<path> Set installation prefix to the given path (Gtk/Efl only)
--makeargs=<arguments> Optional Makefile flags
--minimal No optional features, unless explicitly enabled.
@@ -428,6 +429,22 @@ if (isChromium()) {
exit exitStatus($result) if exitStatus($result);
}
+if (isEfl()) {
+ @options = ();
+ @projects = ();
+ foreach (@features) {
+ my $featureName = $_->{define};
+ if ($featureName) {
+ my $featureEnabled = ${$_->{value}} ? "ON" : "OFF";
+ push @options, "-D$featureName=$featureEnabled";
+ }
+ }
+ push @options, "--makeargs=" . $makeArgs if defined($makeArgs);
+ push @options, "--prefix=" . $prefixPath if defined($prefixPath);
+ my $result = buildCMakeEflProject($clean, @options);
+ exit exitStatus($result) if exitStatus($result);
+}
+
# Build, and abort if the build fails.
for my $dir (@projects) {
chdir $dir or die;
diff --git a/WebKitTools/Scripts/old-run-webkit-tests b/WebKitTools/Scripts/old-run-webkit-tests
index 886b4a8..80801dc 100755
--- a/WebKitTools/Scripts/old-run-webkit-tests
+++ b/WebKitTools/Scripts/old-run-webkit-tests
@@ -236,7 +236,7 @@ if (isAppleMacWebKit()) {
}
}
-if (isQt() || isGtk() || isCygwin()) {
+if (isQt() || isCygwin()) {
my $testfontPath = $ENV{"WEBKIT_TESTFONTS"};
if (!$testfontPath || !-d "$testfontPath") {
print "The WEBKIT_TESTFONTS environment variable is not defined or not set properly\n";
@@ -528,6 +528,7 @@ if (!$has3DRendering) {
if (!checkWebCoreFeatureSupport("3D Canvas", 0)) {
$ignoredDirectories{'fast/canvas/webgl'} = 1;
$ignoredDirectories{'compositing/webgl'} = 1;
+ $ignoredDirectories{'http/tests/canvas/webgl'} = 1;
}
if (checkWebCoreFeatureSupport("WML", 0)) {
diff --git a/WebKitTools/Scripts/prepare-ChangeLog b/WebKitTools/Scripts/prepare-ChangeLog
index c2adaf5..608c9ce 100755
--- a/WebKitTools/Scripts/prepare-ChangeLog
+++ b/WebKitTools/Scripts/prepare-ChangeLog
@@ -269,7 +269,7 @@ if ($bugNumber) {
if (`curl --version | grep ^Protocols` !~ /\bhttps\b/) {
print STDERR " Could not get description for bug $bugNumber.\n";
print STDERR " It looks like your version of curl does not support ssl.\n";
- print STDERR " If you are using macports, this can be fixed with sudo port install curl+ssl.\n";
+ print STDERR " If you are using macports, this can be fixed with sudo port install curl +ssl.\n";
} else {
print STDERR " Bug $bugNumber has no bug description. Maybe you set wrong bug ID?\n";
print STDERR " The bug URL: $bugXMLURL\n";
diff --git a/WebKitTools/Scripts/run-chromium-webkit-unit-tests b/WebKitTools/Scripts/run-chromium-webkit-unit-tests
new file mode 100755
index 0000000..62646af
--- /dev/null
+++ b/WebKitTools/Scripts/run-chromium-webkit-unit-tests
@@ -0,0 +1,51 @@
+#!/usr/bin/perl -w
+# 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.
+
+use strict;
+use File::Spec;
+use FindBin;
+use lib $FindBin::Bin;
+use webkitdirs;
+
+# Allow running this script from any directory.
+my $sourceRootDir = File::Spec->catfile($FindBin::Bin, "../..");
+chdir($sourceRootDir);
+
+setConfiguration();
+
+my $pathToBinary;
+if (isDarwin()) {
+ $pathToBinary = "WebKit/chromium/xcodebuild/" . configuration() . "/webkit_unit_tests";
+} elsif (isCygwin() || isWindows()) {
+ $pathToBinary = "WebKit/chromium/" . configuration() . "/webkit_unit_tests.exe";
+} elsif (isLinux()) {
+ $pathToBinary = "out/" . configuration() . "/webkit_unit_tests";
+}
+
+exit system ($pathToBinary, @ARGV);
diff --git a/WebKitTools/Scripts/test-webkitpy b/WebKitTools/Scripts/test-webkitpy
index e35c6e6..be7e870 100755
--- a/WebKitTools/Scripts/test-webkitpy
+++ b/WebKitTools/Scripts/test-webkitpy
@@ -137,8 +137,9 @@ def _clean_pyc_files(dir_to_clean, paths_not_to_log):
# As a substitute for a unit test, this method tests _clean_pyc_files()
# in addition to calling it. We chose not to use the unittest module
# because _clean_pyc_files() is called only once and is not used elsewhere.
-def _clean_webkitpy_with_test():
+def _clean_packages_with_test(external_package_paths):
webkitpy_dir = os.path.join(os.path.dirname(__file__), "webkitpy")
+ package_paths = [webkitpy_dir] + external_package_paths
# The test .pyc file is--
# webkitpy/python24/TEMP_test-webkitpy_test_pyc_file.pyc.
@@ -156,13 +157,14 @@ def _clean_webkitpy_with_test():
if not os.path.exists(test_path):
raise Exception("Test .pyc file not created: %s" % test_path)
- _clean_pyc_files(webkitpy_dir, [test_path])
+ for path in package_paths:
+ _clean_pyc_files(path, [test_path])
if os.path.exists(test_path):
raise Exception("Test .pyc file not deleted: %s" % test_path)
-def init(command_args):
+def init(command_args, external_package_paths):
"""Execute code prior to importing from webkitpy.unittests.
Args:
@@ -186,8 +188,8 @@ def init(command_args):
configure_logging(is_verbose_logging)
_log.debug("Verbose WebKit logging enabled.")
- # We clean orphaned *.pyc files from webkitpy prior to importing from
- # webkitpy to make sure that no import statements falsely succeed.
+ # We clean orphaned *.pyc files from the packages prior to importing from
+ # them to make sure that no import statements falsely succeed.
# This helps to check that import statements have been updated correctly
# after any file moves. Otherwise, incorrect import statements can
# be masked.
@@ -208,7 +210,7 @@ def init(command_args):
#
# Deleting the orphaned .pyc file prior to importing, however, would
# cause an ImportError to occur on import as desired.
- _clean_webkitpy_with_test()
+ _clean_packages_with_test(external_package_paths)
import webkitpy.python24.versioning as versioning
@@ -227,7 +229,8 @@ def init(command_args):
if __name__ == "__main__":
- init(sys.argv[1:])
+ external_package_paths = [os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'WebKit2', 'Scripts', 'webkit2')]
+ init(sys.argv[1:], external_package_paths)
# We import the unit test code after init() to ensure that any
# Python version warnings are displayed in case an error occurs
@@ -237,4 +240,4 @@ if __name__ == "__main__":
# running the unit tests.
from webkitpy.test.main import Tester
- Tester().run_tests(sys.argv)
+ Tester().run_tests(sys.argv, external_package_paths)
diff --git a/WebKitTools/Scripts/webkitdirs.pm b/WebKitTools/Scripts/webkitdirs.pm
index 2980750..08e14ab 100644
--- a/WebKitTools/Scripts/webkitdirs.pm
+++ b/WebKitTools/Scripts/webkitdirs.pm
@@ -1408,6 +1408,72 @@ sub buildAutotoolsProject($@)
return $result;
}
+sub buildCMakeProject($@)
+{
+ my ($port, $clean, @buildParams) = @_;
+ my $dir = File::Spec->canonpath(baseProductDir());
+ my $config = configuration();
+ my $result;
+ my $makeArgs = "";
+ my @buildArgs;
+
+ $makeArgs .= " -j" . numberOfCPUs() if ($makeArgs !~ m/-j\s*\d+/);
+
+ if ($clean) {
+ print "Cleaning the build directory '$dir'\n";
+ $dir = File::Spec->catfile($dir, $config);
+ File::Path::remove_tree($dir, {keep_root => 1});
+ $result = 0;
+ } else {
+ my $cmakebin = "cmake";
+ my $make = "make";
+
+ push @buildArgs, "-DPORT=$port";
+
+ for my $i (0 .. $#buildParams) {
+ my $opt = $buildParams[$i];
+ if ($opt =~ /^--makeargs=(.*)/i ) {
+ $makeArgs = $1;
+ } elsif ($opt =~ /^--prefix=(.*)/i ) {
+ push @buildArgs, "-DCMAKE_INSTALL_PREFIX=$1";
+ } else {
+ push @buildArgs, $opt;
+ }
+ }
+
+ if ($config =~ m/debug/i) {
+ push @buildArgs, "-DCMAKE_BUILD_TYPE=Debug";
+ } elsif ($config =~ m/release/i) {
+ push @buildArgs, "-DCMAKE_BUILD_TYPE=Release";
+ }
+
+ push @buildArgs, sourceDir();
+
+ $dir = File::Spec->catfile($dir, $config);
+ File::Path::mkpath($dir);
+ chdir $dir or die "Failed to cd into " . $dir . "\n";
+
+ print "Calling '$cmakebin @buildArgs' in " . $dir . "\n\n";
+ my $result = system "$cmakebin @buildArgs";
+ if ($result ne 0) {
+ die "Failed while running $cmakebin to generate makefiles!\n";
+ }
+
+ print "Calling '$make $makeArgs' in " . $dir . "\n\n";
+ $result = system "$make $makeArgs";
+
+ chdir ".." or die;
+ }
+
+ return $result;
+}
+
+sub buildCMakeEflProject($@)
+{
+ my ($clean, @buildArgs) = @_;
+ return buildCMakeProject("Efl", $clean, @buildArgs);
+}
+
sub buildQMakeProject($@)
{
my ($clean, @buildParams) = @_;
@@ -1480,7 +1546,6 @@ sub buildQMakeProject($@)
}
}
- push @buildArgs, sourceDir() . "/WebKit.pro";
if ($config =~ m/debug/i) {
push @buildArgs, "CONFIG-=release";
push @buildArgs, "CONFIG+=debug";
@@ -1495,6 +1560,8 @@ sub buildQMakeProject($@)
}
}
+ push @buildArgs, sourceDir() . "/WebKit.pro";
+
print "Calling '$qmakebin @buildArgs' in " . $dir . "\n\n";
print "Installation headers directory: $installHeaders\n" if(defined($installHeaders));
print "Installation libraries directory: $installLibs\n" if(defined($installLibs));
@@ -1504,6 +1571,16 @@ sub buildQMakeProject($@)
die "Failed to setup build environment using $qmakebin!\n";
}
+ # Manually create makefiles for the examples so we don't build by default
+ my $examplesDir = $dir . "/WebKit/qt/examples";
+ File::Path::mkpath($examplesDir);
+ $buildArgs[-1] = sourceDir() . "/WebKit/qt/examples/examples.pro";
+ chdir $examplesDir or die;
+ print "Calling '$qmakebin @buildArgs' in " . $examplesDir . "\n\n";
+ $result = system "$qmakebin @buildArgs";
+ die "Failed to create makefiles for the examples!\n" if $result ne 0;
+ chdir $dir or die;
+
if ($clean) {
print "Calling '$make $makeargs distclean' in " . $dir . "\n\n";
$result = system "$make $makeargs distclean";
diff --git a/WebKitTools/Scripts/webkitpy/common/config/committers.py b/WebKitTools/Scripts/webkitpy/common/config/committers.py
index 113131f..2d07158 100644
--- a/WebKitTools/Scripts/webkitpy/common/config/committers.py
+++ b/WebKitTools/Scripts/webkitpy/common/config/committers.py
@@ -111,6 +111,7 @@ committers_unable_to_review = [
Committer("Graham Dennis", ["Graham.Dennis@gmail.com", "gdennis@webkit.org"]),
Committer("Greg Bolsinga", "bolsinga@apple.com"),
Committer("Hans Wennborg", "hans@chromium.org", "hwennborg"),
+ Committer("Hayato Ito", "hayato@chromium.org", "hayato"),
Committer("Hin-Chung Lam", ["hclam@google.com", "hclam@chromium.org"]),
Committer("Ilya Tikhonovsky", "loislo@chromium.org", "loislo"),
Committer("Jakob Petsovits", ["jpetsovits@rim.com", "jpetso@gmx.at"], "jpetso"),
@@ -136,6 +137,7 @@ committers_unable_to_review = [
Committer("Kent Hansen", "kent.hansen@nokia.com", "khansen"),
Committer("Kinuko Yasuda", "kinuko@chromium.org", "kinuko"),
Committer("Krzysztof Kowalczyk", "kkowalczyk@gmail.com"),
+ Committer("Kwang Yul Seo", ["kwangyul.seo@gmail.com", "skyul@company100.net", "kseo@webkit.org"], "kwangseo"),
Committer("Leandro Pereira", ["leandro@profusion.mobi", "leandro@webkit.org"], "acidx"),
Committer("Levi Weintraub", "lweintraub@apple.com"),
Committer("Lucas De Marchi", ["lucas.demarchi@profusion.mobi", "demarchi@webkit.org"], "demarchi"),
@@ -158,6 +160,7 @@ committers_unable_to_review = [
Committer("Noam Rosenthal", "noam.rosenthal@nokia.com", "noamr"),
Committer("Pam Greene", "pam@chromium.org", "pamg"),
Committer("Patrick Gansterer", ["paroga@paroga.com", "paroga@webkit.org"], "paroga"),
+ Committer("Pavel Podivilov", "podivilov@chromium.org", "podivilov"),
Committer("Peter Kasting", ["pkasting@google.com", "pkasting@chromium.org"], "pkasting"),
Committer("Philippe Normand", ["pnormand@igalia.com", "philn@webkit.org"], "philn-tp"),
Committer("Pierre d'Herbemont", ["pdherbemont@free.fr", "pdherbemont@apple.com"], "pdherbemont"),
diff --git a/WebKitTools/Scripts/webkitpy/common/net/buildbot.py b/WebKitTools/Scripts/webkitpy/common/net/buildbot.py
index 593ebc1..17f6c7a 100644
--- a/WebKitTools/Scripts/webkitpy/common/net/buildbot.py
+++ b/WebKitTools/Scripts/webkitpy/common/net/buildbot.py
@@ -34,6 +34,8 @@ import urllib
import urllib2
import xmlrpclib
+from webkitpy.common.net.failuremap import FailureMap
+from webkitpy.common.net.regressionwindow import RegressionWindow
from webkitpy.common.system.logutils import get_logger
from webkitpy.thirdparty.autoinstalled.mechanize import Browser
from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup
@@ -145,9 +147,9 @@ class Builder(object):
)
return build
- def find_failure_transition(self, red_build, look_back_limit=30):
+ def find_regression_window(self, red_build, look_back_limit=30):
if not red_build or red_build.is_green():
- return (None, None)
+ return RegressionWindow(None, None)
common_failures = None
current_build = red_build
build_after_current_build = None
@@ -172,34 +174,25 @@ class Builder(object):
break
look_back_count += 1
if look_back_count > look_back_limit:
- return (None, current_build)
+ return RegressionWindow(None, current_build, common_failures=common_failures)
build_after_current_build = current_build
current_build = current_build.previous_build()
# We must iterate at least once because red_build is red.
assert(build_after_current_build)
# Current build must either be green or have no failures in common
# with red build, so we've found our failure transition.
- return (current_build, build_after_current_build)
+ return RegressionWindow(current_build, build_after_current_build, common_failures=common_failures)
- # FIXME: This likely does not belong on Builder
- def suspect_revisions_for_transition(self, last_good_build, first_bad_build):
- suspect_revisions = range(first_bad_build.revision(),
- last_good_build.revision(),
- -1)
- suspect_revisions.reverse()
- return suspect_revisions
-
- def blameworthy_revisions(self, red_build_number, look_back_limit=30, avoid_flakey_tests=True):
+ def find_blameworthy_regression_window(self, red_build_number, look_back_limit=30, avoid_flakey_tests=True):
red_build = self.build(red_build_number)
- (last_good_build, first_bad_build) = \
- self.find_failure_transition(red_build, look_back_limit)
- if not last_good_build:
- return [] # We ran off the limit of our search
+ regression_window = self.find_regression_window(red_build, look_back_limit)
+ if not regression_window.build_before_failure():
+ return None # We ran off the limit of our search
# If avoid_flakey_tests, require at least 2 bad builds before we
# suspect a real failure transition.
- if avoid_flakey_tests and first_bad_build == red_build:
- return []
- return self.suspect_revisions_for_transition(last_good_build, first_bad_build)
+ if avoid_flakey_tests and regression_window.failing_build() == red_build:
+ return None
+ return regression_window
# FIXME: This should be unified with all the layout test results code in the layout_tests package
@@ -414,20 +407,27 @@ class BuildBot(object):
build_status_url = "http://%s/one_box_per_builder" % self.buildbot_host
return urllib2.urlopen(build_status_url)
+ def _file_cell_text(self, file_cell):
+ """Traverses down through firstChild elements until one containing a string is found, then returns that string"""
+ element = file_cell
+ while element.string is None and element.contents:
+ element = element.contents[0]
+ return element.string
+
def _parse_twisted_file_row(self, file_row):
- string_or_empty = lambda soup: unicode(soup.string) if soup.string else u""
+ string_or_empty = lambda string: unicode(string) if string else u""
file_cells = file_row.findAll('td')
return {
- "filename": string_or_empty(file_cells[0].find("a")),
- "size": string_or_empty(file_cells[1]),
- "type": string_or_empty(file_cells[2]),
- "encoding": string_or_empty(file_cells[3]),
+ "filename": string_or_empty(self._file_cell_text(file_cells[0])),
+ "size": string_or_empty(self._file_cell_text(file_cells[1])),
+ "type": string_or_empty(self._file_cell_text(file_cells[2])),
+ "encoding": string_or_empty(self._file_cell_text(file_cells[3])),
}
def _parse_twisted_directory_listing(self, page):
soup = BeautifulSoup(page)
# HACK: Match only table rows with a class to ignore twisted header/footer rows.
- file_rows = soup.find('table').findAll('tr', { "class" : True })
+ file_rows = soup.find('table').findAll('tr', {'class': re.compile(r'\b(?:directory|file)\b')})
return [self._parse_twisted_file_row(file_row) for file_row in file_rows]
# FIXME: There should be a better way to get this information directly from twisted.
@@ -452,19 +452,17 @@ class BuildBot(object):
self._builder_by_name[name] = builder
return builder
- def revisions_causing_failures(self, only_core_builders=True):
+ def failure_map(self, only_core_builders=True):
builder_statuses = self.core_builder_statuses() if only_core_builders else self.builder_statuses()
+ failure_map = FailureMap()
revision_to_failing_bots = {}
for builder_status in builder_statuses:
if builder_status["is_green"]:
continue
builder = self.builder_with_name(builder_status["name"])
- revisions = builder.blameworthy_revisions(builder_status["build_number"])
- for revision in revisions:
- failing_bots = revision_to_failing_bots.get(revision, [])
- failing_bots.append(builder)
- revision_to_failing_bots[revision] = failing_bots
- return revision_to_failing_bots
+ regression_window = builder.find_blameworthy_regression_window(builder_status["build_number"])
+ failure_map.add_regression_window(builder, regression_window)
+ return failure_map
# This makes fewer requests than calling Builder.latest_build would. It grabs all builder
# statuses in one request using self.builder_statuses (fetching /one_box_per_builder instead of builder pages).
diff --git a/WebKitTools/Scripts/webkitpy/common/net/buildbot_unittest.py b/WebKitTools/Scripts/webkitpy/common/net/buildbot_unittest.py
index b48f0e4..c99ab32 100644
--- a/WebKitTools/Scripts/webkitpy/common/net/buildbot_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/common/net/buildbot_unittest.py
@@ -54,53 +54,53 @@ class BuilderTest(unittest.TestCase):
self.builder = Builder(u"Test Builder \u2661", self.buildbot)
self._install_fetch_build(lambda build_number: ["test1", "test2"])
- def test_find_failure_transition(self):
- (green_build, red_build) = self.builder.find_failure_transition(self.builder.build(10))
- self.assertEqual(green_build.revision(), 1003)
- self.assertEqual(red_build.revision(), 1004)
+ def test_find_regression_window(self):
+ regression_window = self.builder.find_regression_window(self.builder.build(10))
+ self.assertEqual(regression_window.build_before_failure().revision(), 1003)
+ self.assertEqual(regression_window.failing_build().revision(), 1004)
- (green_build, red_build) = self.builder.find_failure_transition(self.builder.build(10), look_back_limit=2)
- self.assertEqual(green_build, None)
- self.assertEqual(red_build.revision(), 1008)
+ regression_window = self.builder.find_regression_window(self.builder.build(10), look_back_limit=2)
+ self.assertEqual(regression_window.build_before_failure(), None)
+ self.assertEqual(regression_window.failing_build().revision(), 1008)
def test_none_build(self):
self.builder._fetch_build = lambda build_number: None
- (green_build, red_build) = self.builder.find_failure_transition(self.builder.build(10))
- self.assertEqual(green_build, None)
- self.assertEqual(red_build, None)
+ regression_window = self.builder.find_regression_window(self.builder.build(10))
+ self.assertEqual(regression_window.build_before_failure(), None)
+ self.assertEqual(regression_window.failing_build(), None)
def test_flaky_tests(self):
self._install_fetch_build(lambda build_number: ["test1"] if build_number % 2 else ["test2"])
- (green_build, red_build) = self.builder.find_failure_transition(self.builder.build(10))
- self.assertEqual(green_build.revision(), 1009)
- self.assertEqual(red_build.revision(), 1010)
+ regression_window = self.builder.find_regression_window(self.builder.build(10))
+ self.assertEqual(regression_window.build_before_failure().revision(), 1009)
+ self.assertEqual(regression_window.failing_build().revision(), 1010)
def test_failure_and_flaky(self):
self._install_fetch_build(lambda build_number: ["test1", "test2"] if build_number % 2 else ["test2"])
- (green_build, red_build) = self.builder.find_failure_transition(self.builder.build(10))
- self.assertEqual(green_build.revision(), 1003)
- self.assertEqual(red_build.revision(), 1004)
+ regression_window = self.builder.find_regression_window(self.builder.build(10))
+ self.assertEqual(regression_window.build_before_failure().revision(), 1003)
+ self.assertEqual(regression_window.failing_build().revision(), 1004)
def test_no_results(self):
self._install_fetch_build(lambda build_number: ["test1", "test2"] if build_number % 2 else ["test2"])
- (green_build, red_build) = self.builder.find_failure_transition(self.builder.build(10))
- self.assertEqual(green_build.revision(), 1003)
- self.assertEqual(red_build.revision(), 1004)
+ regression_window = self.builder.find_regression_window(self.builder.build(10))
+ self.assertEqual(regression_window.build_before_failure().revision(), 1003)
+ self.assertEqual(regression_window.failing_build().revision(), 1004)
def test_failure_after_flaky(self):
self._install_fetch_build(lambda build_number: ["test1", "test2"] if build_number > 6 else ["test3"])
- (green_build, red_build) = self.builder.find_failure_transition(self.builder.build(10))
- self.assertEqual(green_build.revision(), 1006)
- self.assertEqual(red_build.revision(), 1007)
+ regression_window = self.builder.find_regression_window(self.builder.build(10))
+ self.assertEqual(regression_window.build_before_failure().revision(), 1006)
+ self.assertEqual(regression_window.failing_build().revision(), 1007)
- def test_blameworthy_revisions(self):
- self.assertEqual(self.builder.blameworthy_revisions(10), [1004])
- self.assertEqual(self.builder.blameworthy_revisions(10, look_back_limit=2), [])
+ def test_find_blameworthy_regression_window(self):
+ self.assertEqual(self.builder.find_blameworthy_regression_window(10).revisions(), [1004])
+ self.assertEqual(self.builder.find_blameworthy_regression_window(10, look_back_limit=2), None)
# Flakey test avoidance requires at least 2 red builds:
- self.assertEqual(self.builder.blameworthy_revisions(4), [])
- self.assertEqual(self.builder.blameworthy_revisions(4, avoid_flakey_tests=False), [1004])
+ self.assertEqual(self.builder.find_blameworthy_regression_window(4), None)
+ self.assertEqual(self.builder.find_blameworthy_regression_window(4, avoid_flakey_tests=False).revisions(), [1004])
# Green builder:
- self.assertEqual(self.builder.blameworthy_revisions(3), [])
+ self.assertEqual(self.builder.find_blameworthy_regression_window(3), None)
def test_build_caching(self):
self.assertEqual(self.builder.build(10), self.builder.build(10))
@@ -361,22 +361,19 @@ class BuildBotTest(unittest.TestCase):
<h1>Directory listing for /results/SnowLeopard Intel Leaks/</h1>
<table>
- <thead>
- <tr>
+ <tr class="alt">
<th>Filename</th>
<th>Size</th>
<th>Content type</th>
<th>Content encoding</th>
</tr>
- </thead>
- <tbody>
-<tr class="odd">
- <td><a href="r47483%20%281%29/">r47483 (1)/</a></td>
- <td></td>
- <td>[Directory]</td>
- <td></td>
+<tr class="directory ">
+ <td><a href="r47483%20%281%29/"><b>r47483 (1)/</b></a></td>
+ <td><b></b></td>
+ <td><b>[Directory]</b></td>
+ <td><b></b></td>
</tr>
-<tr class="odd">
+<tr class="file alt">
<td><a href="r47484%20%282%29.zip">r47484 (2).zip</a></td>
<td>89K</td>
<td>[application/zip]</td>
diff --git a/WebKitTools/Scripts/webkitpy/common/net/credentials.py b/WebKitTools/Scripts/webkitpy/common/net/credentials.py
index 1d5f83d..1c3e6c0 100644
--- a/WebKitTools/Scripts/webkitpy/common/net/credentials.py
+++ b/WebKitTools/Scripts/webkitpy/common/net/credentials.py
@@ -39,14 +39,23 @@ from webkitpy.common.system.executive import Executive, ScriptError
from webkitpy.common.system.user import User
from webkitpy.common.system.deprecated_logging import log
+try:
+ # Use keyring, a cross platform keyring interface, as a fallback:
+ # http://pypi.python.org/pypi/keyring
+ import keyring
+except ImportError:
+ keyring = None
+
class Credentials(object):
- def __init__(self, host, git_prefix=None, executive=None, cwd=os.getcwd()):
+ def __init__(self, host, git_prefix=None, executive=None, cwd=os.getcwd(),
+ keyring=keyring):
self.host = host
self.git_prefix = "%s." % git_prefix if git_prefix else ""
self.executive = executive or Executive()
self.cwd = cwd
+ self._keyring = keyring
def _credentials_from_git(self):
return [Git.read_git_config(self.git_prefix + "username"),
@@ -117,10 +126,19 @@ class Credentials(object):
if not username or not password:
(username, password) = self._credentials_from_keychain(username)
+ if username and not password and self._keyring:
+ password = self._keyring.get_password(self.host, username)
+
if not username:
username = User.prompt("%s login: " % self.host)
if not password:
password = getpass.getpass("%s password for %s: " % (self.host,
username))
+ if self._keyring:
+ store_password = User().confirm(
+ "Store password in system keyring?", User.DEFAULT_NO)
+ if store_password:
+ self._keyring.set_password(self.host, username, password)
+
return [username, password]
diff --git a/WebKitTools/Scripts/webkitpy/common/net/credentials_unittest.py b/WebKitTools/Scripts/webkitpy/common/net/credentials_unittest.py
index 9a42bdd..d30291b 100644
--- a/WebKitTools/Scripts/webkitpy/common/net/credentials_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/common/net/credentials_unittest.py
@@ -113,5 +113,28 @@ password: "SECRETSAUCE"
self.assertEqual(credentials.read_credentials(), ["test@webkit.org", "SECRETSAUCE"])
os.rmdir(temp_dir_path)
+ def test_keyring_without_git_repo(self):
+ class MockKeyring(object):
+ def get_password(self, host, username):
+ return "NOMNOMNOM"
+
+ class FakeCredentials(Credentials):
+ def __init__(self, cwd):
+ Credentials.__init__(self, "fake.hostname", cwd=cwd,
+ keyring=MockKeyring())
+
+ def _is_mac_os_x(self):
+ return True
+
+ def _credentials_from_keychain(self, username):
+ return ("test@webkit.org", None)
+
+ temp_dir_path = tempfile.mkdtemp(suffix="not_a_git_repo")
+ credentials = FakeCredentials(temp_dir_path)
+ try:
+ self.assertEqual(credentials.read_credentials(), ["test@webkit.org", "NOMNOMNOM"])
+ finally:
+ os.rmdir(temp_dir_path)
+
if __name__ == '__main__':
unittest.main()
diff --git a/WebKitTools/Scripts/webkitpy/common/net/failuremap.py b/WebKitTools/Scripts/webkitpy/common/net/failuremap.py
new file mode 100644
index 0000000..98e4b8f
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/common/net/failuremap.py
@@ -0,0 +1,48 @@
+# 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.
+
+
+class FailureMap(object):
+ def __init__(self):
+ self._failures = []
+
+ def add_regression_window(self, builder, regression_window):
+ self._failures.append({
+ 'builder': builder,
+ 'regression_window': regression_window,
+ })
+
+ def revisions_causing_failures(self):
+ revision_to_failing_bots = {}
+ for failure_info in self._failures:
+ revisions = failure_info['regression_window'].revisions()
+ for revision in revisions:
+ failing_bots = revision_to_failing_bots.get(revision, [])
+ failing_bots.append(failure_info['builder'])
+ revision_to_failing_bots[revision] = failing_bots
+ return revision_to_failing_bots
diff --git a/WebKitTools/Scripts/webkitpy/common/net/regressionwindow.py b/WebKitTools/Scripts/webkitpy/common/net/regressionwindow.py
new file mode 100644
index 0000000..231459f
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/common/net/regressionwindow.py
@@ -0,0 +1,48 @@
+# 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.
+
+
+class RegressionWindow(object):
+ def __init__(self, build_before_failure, failing_build, common_failures=None):
+ self._build_before_failure = build_before_failure
+ self._failing_build = failing_build
+ self._common_failures = common_failures
+
+ def build_before_failure(self):
+ return self._build_before_failure
+
+ def failing_build(self):
+ return self._failing_build
+
+ def common_failures(self):
+ return self._common_failures
+
+ def revisions(self):
+ revisions = range(self._failing_build.revision(), self._build_before_failure.revision(), -1)
+ revisions.reverse()
+ return revisions
diff --git a/WebKitTools/Scripts/webkitpy/common/system/user.py b/WebKitTools/Scripts/webkitpy/common/system/user.py
index 9444c00..240b67b 100644
--- a/WebKitTools/Scripts/webkitpy/common/system/user.py
+++ b/WebKitTools/Scripts/webkitpy/common/system/user.py
@@ -28,6 +28,7 @@
import logging
import os
+import re
import shlex
import subprocess
import sys
@@ -51,6 +52,9 @@ except ImportError:
class User(object):
+ DEFAULT_NO = 'n'
+ DEFAULT_YES = 'y'
+
# FIXME: These are @classmethods because bugzilla.py doesn't have a Tool object (thus no User instance).
@classmethod
def prompt(cls, message, repeat=1, raw_input=raw_input):
@@ -61,14 +65,30 @@ class User(object):
return response
@classmethod
- def prompt_with_list(cls, list_title, list_items):
+ def prompt_with_list(cls, list_title, list_items, can_choose_multiple=False, raw_input=raw_input):
print list_title
i = 0
for item in list_items:
i += 1
print "%2d. %s" % (i, item)
- result = int(cls.prompt("Enter a number: ")) - 1
- return list_items[result]
+
+ # Loop until we get valid input
+ while True:
+ if can_choose_multiple:
+ response = cls.prompt("Enter one or more numbers (comma-separated), or \"all\": ", raw_input=raw_input)
+ if not response.strip() or response == "all":
+ return list_items
+ try:
+ indices = [int(r) - 1 for r in re.split("\s*,\s*", response)]
+ except ValueError, err:
+ continue
+ return [list_items[i] for i in indices]
+ else:
+ try:
+ result = int(cls.prompt("Enter a number: ", raw_input=raw_input)) - 1
+ except ValueError, err:
+ continue
+ return list_items[result]
def edit(self, files):
editor = os.environ.get("EDITOR") or "vi"
@@ -98,11 +118,14 @@ class User(object):
except IOError, e:
pass
- def confirm(self, message=None):
+ def confirm(self, message=None, default=DEFAULT_YES, raw_input=raw_input):
if not message:
message = "Continue?"
- response = raw_input("%s [Y/n]: " % message)
- return not response or response.lower() == "y"
+ choice = {'y': 'Y/n', 'n': 'y/N'}[default]
+ response = raw_input("%s [%s]: " % (message, choice))
+ if not response:
+ response = default
+ return response.lower() == 'y'
def can_open_url(self):
try:
diff --git a/WebKitTools/Scripts/webkitpy/common/system/user_unittest.py b/WebKitTools/Scripts/webkitpy/common/system/user_unittest.py
index dadead3..ae1bad5 100644
--- a/WebKitTools/Scripts/webkitpy/common/system/user_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/common/system/user_unittest.py
@@ -28,6 +28,7 @@
import unittest
+from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.common.system.user import User
class UserTest(unittest.TestCase):
@@ -50,5 +51,51 @@ class UserTest(unittest.TestCase):
return None
self.assertEqual(User.prompt("input", repeat=self.repeatsRemaining, raw_input=mock_raw_input), None)
+ def test_prompt_with_list(self):
+ def run_prompt_test(inputs, expected_result, can_choose_multiple=False):
+ def mock_raw_input(message):
+ return inputs.pop(0)
+ output_capture = OutputCapture()
+ actual_result = output_capture.assert_outputs(
+ self,
+ User.prompt_with_list,
+ args=["title", ["foo", "bar"]],
+ kwargs={"can_choose_multiple": can_choose_multiple, "raw_input": mock_raw_input},
+ expected_stdout="title\n 1. foo\n 2. bar\n")
+ self.assertEqual(actual_result, expected_result)
+ self.assertEqual(len(inputs), 0)
+
+ run_prompt_test(["1"], "foo")
+ run_prompt_test(["badinput", "2"], "bar")
+
+ run_prompt_test(["1,2"], ["foo", "bar"], can_choose_multiple=True)
+ run_prompt_test([" 1, 2 "], ["foo", "bar"], can_choose_multiple=True)
+ run_prompt_test(["all"], ["foo", "bar"], can_choose_multiple=True)
+ run_prompt_test([""], ["foo", "bar"], can_choose_multiple=True)
+ run_prompt_test([" "], ["foo", "bar"], can_choose_multiple=True)
+ run_prompt_test(["badinput", "all"], ["foo", "bar"], can_choose_multiple=True)
+
+ def test_confirm(self):
+ test_cases = (
+ (("Continue? [Y/n]: ", True), (User.DEFAULT_YES, 'y')),
+ (("Continue? [Y/n]: ", False), (User.DEFAULT_YES, 'n')),
+ (("Continue? [Y/n]: ", True), (User.DEFAULT_YES, '')),
+ (("Continue? [Y/n]: ", False), (User.DEFAULT_YES, 'q')),
+ (("Continue? [y/N]: ", True), (User.DEFAULT_NO, 'y')),
+ (("Continue? [y/N]: ", False), (User.DEFAULT_NO, 'n')),
+ (("Continue? [y/N]: ", False), (User.DEFAULT_NO, '')),
+ (("Continue? [y/N]: ", False), (User.DEFAULT_NO, 'q')),
+ )
+ for test_case in test_cases:
+ expected, inputs = test_case
+
+ def mock_raw_input(message):
+ self.assertEquals(expected[0], message)
+ return inputs[1]
+
+ result = User().confirm(default=inputs[0],
+ raw_input=mock_raw_input)
+ self.assertEquals(expected[1], result)
+
if __name__ == '__main__':
unittest.main()
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/checksum-expected.checksum b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/checksum-expected.checksum
deleted file mode 100644
index 5890112..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/checksum-expected.checksum
+++ /dev/null
@@ -1 +0,0 @@
-checksum-checksum
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/checksum-expected.png b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/checksum-expected.png
deleted file mode 100644
index 83a5de3..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/checksum-expected.png
+++ /dev/null
@@ -1 +0,0 @@
-checksum-png
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/checksum-expected.txt b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/checksum-expected.txt
deleted file mode 100644
index 5628d69..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/checksum-expected.txt
+++ /dev/null
@@ -1 +0,0 @@
-checksum-txt
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/checksum.html b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/checksum.html
deleted file mode 100644
index 2b78d31..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/checksum.html
+++ /dev/null
@@ -1 +0,0 @@
-image_checksum
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/crash.html b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/crash.html
deleted file mode 100644
index 0bc3798..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/crash.html
+++ /dev/null
@@ -1 +0,0 @@
-crash
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/exception.html b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/exception.html
deleted file mode 100644
index 38c54e3..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/exception.html
+++ /dev/null
@@ -1 +0,0 @@
-exception
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/hang.html b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/hang.html
deleted file mode 100644
index 4e0de08..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/hang.html
+++ /dev/null
@@ -1 +0,0 @@
-timeout-thread
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image-expected.checksum b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image-expected.checksum
deleted file mode 100644
index 24b887a..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image-expected.checksum
+++ /dev/null
@@ -1 +0,0 @@
-image-checksum
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image-expected.png b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image-expected.png
deleted file mode 100644
index 4c23996..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image-expected.png
+++ /dev/null
@@ -1 +0,0 @@
-image-png
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image-expected.txt b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image-expected.txt
deleted file mode 100644
index c6ee718..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image-expected.txt
+++ /dev/null
@@ -1 +0,0 @@
-image-txt
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image.html b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image.html
deleted file mode 100644
index 53e4b27..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image.html
+++ /dev/null
@@ -1 +0,0 @@
-image_failure
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image_checksum-expected.checksum b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image_checksum-expected.checksum
deleted file mode 100644
index 8fa0851..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image_checksum-expected.checksum
+++ /dev/null
@@ -1 +0,0 @@
-image_checksum-checksum
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image_checksum-expected.png b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image_checksum-expected.png
deleted file mode 100644
index d677d2e..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image_checksum-expected.png
+++ /dev/null
@@ -1 +0,0 @@
-image_checksum-png
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image_checksum-expected.txt b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image_checksum-expected.txt
deleted file mode 100644
index 453f213..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image_checksum-expected.txt
+++ /dev/null
@@ -1 +0,0 @@
-image_checksum-txt
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image_checksum.html b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image_checksum.html
deleted file mode 100644
index 2b78d31..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/image_checksum.html
+++ /dev/null
@@ -1 +0,0 @@
-image_checksum
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/keyboard.html b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/keyboard.html
deleted file mode 100644
index c253983..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/keyboard.html
+++ /dev/null
@@ -1 +0,0 @@
-keyboard
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/missing_check-expected.png b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/missing_check-expected.png
deleted file mode 100644
index e45c7af..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/missing_check-expected.png
+++ /dev/null
@@ -1 +0,0 @@
-missing_check-png
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/missing_check-expected.txt b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/missing_check-expected.txt
deleted file mode 100644
index 0ea9227..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/missing_check-expected.txt
+++ /dev/null
@@ -1 +0,0 @@
-missing_check-txt
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/missing_check.html b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/missing_check.html
deleted file mode 100644
index 0af8000..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/missing_check.html
+++ /dev/null
@@ -1 +0,0 @@
-missing_image
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/missing_image.html b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/missing_image.html
deleted file mode 100644
index 0af8000..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/missing_image.html
+++ /dev/null
@@ -1 +0,0 @@
-missing_image
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/missing_text.html b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/missing_text.html
deleted file mode 100644
index 47b8ad6..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/missing_text.html
+++ /dev/null
@@ -1 +0,0 @@
-missing_text
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/text-expected.txt b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/text-expected.txt
deleted file mode 100644
index e21ea45..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/text-expected.txt
+++ /dev/null
@@ -1 +0,0 @@
-text_failures-txt
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/text.html b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/text.html
deleted file mode 100644
index 91f5fc7..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/text.html
+++ /dev/null
@@ -1 +0,0 @@
-text_failure
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/timeout.html b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/timeout.html
deleted file mode 100644
index 790851a..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/expected/timeout.html
+++ /dev/null
@@ -1 +0,0 @@
-timeout
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/unexpected/text-image-checksum-expected.checksum b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/unexpected/text-image-checksum-expected.checksum
deleted file mode 100644
index 0c4f6da..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/unexpected/text-image-checksum-expected.checksum
+++ /dev/null
@@ -1 +0,0 @@
-fail_checksum
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/unexpected/text-image-checksum-expected.png b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/unexpected/text-image-checksum-expected.png
deleted file mode 100644
index db483ee..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/unexpected/text-image-checksum-expected.png
+++ /dev/null
@@ -1 +0,0 @@
-fail_png
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/unexpected/text-image-checksum-expected.txt b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/unexpected/text-image-checksum-expected.txt
deleted file mode 100644
index a1f3c24..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/unexpected/text-image-checksum-expected.txt
+++ /dev/null
@@ -1 +0,0 @@
-fail_output
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/unexpected/text-image-checksum.html b/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/unexpected/text-image-checksum.html
deleted file mode 100644
index b325924..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/failures/unexpected/text-image-checksum.html
+++ /dev/null
@@ -1 +0,0 @@
-Google
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/http/tests/passes/text-expected.txt b/WebKitTools/Scripts/webkitpy/layout_tests/data/http/tests/passes/text-expected.txt
deleted file mode 100644
index 2b38a06..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/http/tests/passes/text-expected.txt
+++ /dev/null
@@ -1 +0,0 @@
-text-txt
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/http/tests/passes/text.html b/WebKitTools/Scripts/webkitpy/layout_tests/data/http/tests/passes/text.html
deleted file mode 100644
index 8e27be7..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/http/tests/passes/text.html
+++ /dev/null
@@ -1 +0,0 @@
-text
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/http/tests/ssl/text-expected.txt b/WebKitTools/Scripts/webkitpy/layout_tests/data/http/tests/ssl/text-expected.txt
deleted file mode 100644
index 2b38a06..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/http/tests/ssl/text-expected.txt
+++ /dev/null
@@ -1 +0,0 @@
-text-txt
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/http/tests/ssl/text.html b/WebKitTools/Scripts/webkitpy/layout_tests/data/http/tests/ssl/text.html
deleted file mode 100644
index 8e27be7..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/http/tests/ssl/text.html
+++ /dev/null
@@ -1 +0,0 @@
-text
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/error-expected.txt b/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/error-expected.txt
deleted file mode 100644
index 9427269..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/error-expected.txt
+++ /dev/null
@@ -1 +0,0 @@
-error-txt
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/error.html b/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/error.html
deleted file mode 100644
index 8276753..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/error.html
+++ /dev/null
@@ -1 +0,0 @@
-error
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/image-expected.checksum b/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/image-expected.checksum
deleted file mode 100644
index 24b887a..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/image-expected.checksum
+++ /dev/null
@@ -1 +0,0 @@
-image-checksum
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/image-expected.png b/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/image-expected.png
deleted file mode 100644
index 4c23996..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/image-expected.png
+++ /dev/null
@@ -1 +0,0 @@
-image-png
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/image-expected.txt b/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/image-expected.txt
deleted file mode 100644
index c6ee718..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/image-expected.txt
+++ /dev/null
@@ -1 +0,0 @@
-image-txt
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/image.html b/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/image.html
deleted file mode 100644
index 773b222..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/image.html
+++ /dev/null
@@ -1 +0,0 @@
-image
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/platform_image-expected.checksum b/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/platform_image-expected.checksum
deleted file mode 100644
index 52038ae..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/platform_image-expected.checksum
+++ /dev/null
@@ -1 +0,0 @@
-platform_image-generic-checksum
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/platform_image-expected.png b/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/platform_image-expected.png
deleted file mode 100644
index 087872b..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/platform_image-expected.png
+++ /dev/null
@@ -1 +0,0 @@
-platform_image-generic-png
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/platform_image-expected.txt b/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/platform_image-expected.txt
deleted file mode 100644
index f71680c..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/platform_image-expected.txt
+++ /dev/null
@@ -1 +0,0 @@
-platform_image-generic-txt
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/platform_image.html b/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/platform_image.html
deleted file mode 100644
index ca48a7b..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/platform_image.html
+++ /dev/null
@@ -1 +0,0 @@
-platform_image
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/text-expected.txt b/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/text-expected.txt
deleted file mode 100644
index 2b38a06..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/text-expected.txt
+++ /dev/null
@@ -1 +0,0 @@
-text-txt
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/text.html b/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/text.html
deleted file mode 100644
index 8e27be7..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/passes/text.html
+++ /dev/null
@@ -1 +0,0 @@
-text
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/platform/test/passes/platform_image-expected.checksum b/WebKitTools/Scripts/webkitpy/layout_tests/data/platform/test/passes/platform_image-expected.checksum
deleted file mode 100644
index ea557cf..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/platform/test/passes/platform_image-expected.checksum
+++ /dev/null
@@ -1 +0,0 @@
-platform_image-checksum
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/platform/test/passes/platform_image-expected.png b/WebKitTools/Scripts/webkitpy/layout_tests/data/platform/test/passes/platform_image-expected.png
deleted file mode 100644
index ec42fc1..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/platform/test/passes/platform_image-expected.png
+++ /dev/null
@@ -1 +0,0 @@
-platform_image-png
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/platform/test/passes/platform_image-expected.txt b/WebKitTools/Scripts/webkitpy/layout_tests/data/platform/test/passes/platform_image-expected.txt
deleted file mode 100644
index ff8bf43..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/platform/test/passes/platform_image-expected.txt
+++ /dev/null
@@ -1 +0,0 @@
-platform_image-txt
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/platform/test/test_expectations.txt b/WebKitTools/Scripts/webkitpy/layout_tests/data/platform/test/test_expectations.txt
deleted file mode 100644
index 0619fde..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/platform/test/test_expectations.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-WONTFIX : failures/expected/checksum.html = IMAGE
-WONTFIX : failures/expected/crash.html = CRASH
-// This one actually passes because the checksums will match.
-WONTFIX : failures/expected/image.html = PASS
-WONTFIX : failures/expected/image_checksum.html = IMAGE
-WONTFIX : failures/expected/missing_check.html = MISSING PASS
-WONTFIX : failures/expected/missing_image.html = MISSING PASS
-WONTFIX : failures/expected/missing_text.html = MISSING PASS
-WONTFIX : failures/expected/text.html = TEXT
-WONTFIX : failures/expected/timeout.html = TIMEOUT
-WONTFIX SKIP : failures/expected/hang.html = TIMEOUT
-WONTFIX SKIP : failures/expected/keyboard.html = CRASH
-WONTFIX SKIP : failures/expected/exception.html = CRASH
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/resources/README.txt b/WebKitTools/Scripts/webkitpy/layout_tests/data/resources/README.txt
deleted file mode 100644
index b806b06..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/resources/README.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-This directory exists solely to make sure that when we gather the lists of
-tests, we skip over directories named 'resources'.
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/websocket/tests/passes/text-expected.txt b/WebKitTools/Scripts/webkitpy/layout_tests/data/websocket/tests/passes/text-expected.txt
deleted file mode 100644
index 2b38a06..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/websocket/tests/passes/text-expected.txt
+++ /dev/null
@@ -1 +0,0 @@
-text-txt
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/data/websocket/tests/passes/text.html b/WebKitTools/Scripts/webkitpy/layout_tests/data/websocket/tests/passes/text.html
deleted file mode 100644
index 8e27be7..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/data/websocket/tests/passes/text.html
+++ /dev/null
@@ -1 +0,0 @@
-text
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing.py
index d420631..00ff211 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing.py
@@ -351,11 +351,20 @@ class Printer(object):
filename = result.filename
test_name = self._port.relative_test_filename(filename)
self._write('trace: %s' % test_name)
- self._write(' txt: %s' %
- self._port.relative_test_filename(
- self._port.expected_filename(filename, '.txt')))
+ txt_file = self._port.expected_filename(filename, '.txt')
+ if self._port.path_exists(txt_file):
+ self._write(' txt: %s' %
+ self._port.relative_test_filename(txt_file))
+ else:
+ self._write(' txt: <none>')
+ checksum_file = self._port.expected_filename(filename, '.checksum')
+ if self._port.path_exists(checksum_file):
+ self._write(' sum: %s' %
+ self._port.relative_test_filename(checksum_file))
+ else:
+ self._write(' sum: <none>')
png_file = self._port.expected_filename(filename, '.png')
- if os.path.exists(png_file):
+ if self._port.path_exists(png_file):
self._write(' png: %s' %
self._port.relative_test_filename(png_file))
else:
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing_unittest.py
index 29139d0..0344aa7 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing_unittest.py
@@ -151,7 +151,7 @@ class Testprinter(unittest.TestCase):
expectations = test_expectations.TestExpectations(
self._port, test_paths, expectations_str,
self._port.test_platform_name(), is_debug_mode=False,
- is_lint_mode=False, tests_are_present=False)
+ is_lint_mode=False)
rs = run_webkit_tests.ResultSummary(expectations, test_paths)
return test_paths, rs, expectations
@@ -318,6 +318,16 @@ class Testprinter(unittest.TestCase):
self.assertFalse(err.empty())
printer, err, out = self.get_printer(['--print', 'trace-everything'])
+ result = self.get_result('passes/image.html')
+ printer.print_test_result(result, expected=True, exp_str='',
+ got_str='')
+ result = self.get_result('failures/expected/missing_text.html')
+ printer.print_test_result(result, expected=True, exp_str='',
+ got_str='')
+ result = self.get_result('failures/expected/missing_check.html')
+ printer.print_test_result(result, expected=True, exp_str='',
+ got_str='')
+ result = self.get_result('failures/expected/missing_image.html')
printer.print_test_result(result, expected=True, exp_str='',
got_str='')
self.assertFalse(err.empty())
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_expectations.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_expectations.py
index 3d8349b..508a6ad 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_expectations.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_expectations.py
@@ -87,8 +87,7 @@ class TestExpectations:
TEST_LIST = "test_expectations.txt"
def __init__(self, port, tests, expectations, test_platform_name,
- is_debug_mode, is_lint_mode, tests_are_present=True,
- overrides=None):
+ is_debug_mode, is_lint_mode, overrides=None):
"""Loads and parses the test expectations given in the string.
Args:
port: handle to object containing platform-specific functionality
@@ -101,10 +100,6 @@ class TestExpectations:
in the expectations
is_lint_mode: If True, just parse the expectations string
looking for errors.
- tests_are_present: whether the test files exist in the file
- system and can be probed for. This is useful for distinguishing
- test files from directories, and is needed by the LTTF
- dashboard, where the files aren't actually locally present.
overrides: test expectations that are allowed to override any
entries in |expectations|. This is used by callers
that need to manage two sets of expectations (e.g., upstream
@@ -112,7 +107,7 @@ class TestExpectations:
"""
self._expected_failures = TestExpectationsFile(port, expectations,
tests, test_platform_name, is_debug_mode, is_lint_mode,
- tests_are_present=tests_are_present, overrides=overrides)
+ overrides=overrides)
# TODO(ojan): Allow for removing skipped tests when getting the list of
# tests to run, but not when getting metrics.
@@ -302,8 +297,7 @@ class TestExpectationsFile:
'flaky': FLAKY}
def __init__(self, port, expectations, full_test_list, test_platform_name,
- is_debug_mode, is_lint_mode, suppress_errors=False,
- tests_are_present=True, overrides=None):
+ is_debug_mode, is_lint_mode, suppress_errors=False, overrides=None):
"""
expectations: Contents of the expectations file
full_test_list: The list of all tests to be run pending processing of
@@ -314,9 +308,6 @@ class TestExpectationsFile:
is_debug_mode: Whether we testing a test_shell built debug mode.
is_lint_mode: Whether this is just linting test_expecatations.txt.
suppress_errors: Whether to suppress lint errors.
- tests_are_present: Whether the test files are present in the local
- filesystem. The LTTF Dashboard uses False here to avoid having to
- keep a local copy of the tree.
overrides: test expectations that are allowed to override any
entries in |expectations|. This is used by callers
that need to manage two sets of expectations (e.g., upstream
@@ -329,7 +320,6 @@ class TestExpectationsFile:
self._test_platform_name = test_platform_name
self._is_debug_mode = is_debug_mode
self._is_lint_mode = is_lint_mode
- self._tests_are_present = tests_are_present
self._overrides = overrides
self._suppress_errors = suppress_errors
self._errors = []
@@ -462,7 +452,7 @@ class TestExpectationsFile:
def remove_platform_from_expectations(self, tests, platform):
"""Returns a copy of the expectations with the tests matching the
- platform remove.
+ platform removed.
If a test is in the test list and has an option that matches the given
platform, remove the matching platform and save the updated test back
@@ -699,8 +689,8 @@ class TestExpectationsFile:
# WebKit's way of skipping tests is to add a -disabled suffix.
# So we should consider the path existing if the path or the
# -disabled version exists.
- if (self._tests_are_present and not os.path.exists(full_path)
- and not os.path.exists(full_path + '-disabled')):
+ if (not self._port.path_exists(full_path)
+ and not self._port.path_exists(full_path + '-disabled')):
# Log a non fatal error here since you hit this case any
# time you update test_expectations.txt without syncing
# the LayoutTests directory
@@ -735,7 +725,8 @@ class TestExpectationsFile:
path and make sure directories end with the OS path separator."""
path = os.path.join(self._port.layout_tests_dir(), test_list_path)
path = os.path.normpath(path)
- path = self._fix_dir(path)
+ if self._port.path_isdir(path):
+ path = os.path.join(path, '')
result = []
for test in self._full_test_list:
@@ -743,20 +734,6 @@ class TestExpectationsFile:
result.append(test)
return result
- def _fix_dir(self, path):
- """Check to see if the path points to a directory, and if so, append
- the directory separator if necessary."""
- if self._tests_are_present:
- if os.path.isdir(path):
- path = os.path.join(path, '')
- else:
- # If we can't check the filesystem to see if this is a directory,
- # we assume that files w/o an extension are directories.
- # TODO(dpranke): What happens w/ LayoutTests/css2.1 ?
- if os.path.splitext(path)[1] == '':
- path = os.path.join(path, '')
- return path
-
def _add_tests(self, tests, expectations, test_list_path, lineno,
modifiers, options, overrides_allowed):
for test in tests:
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_expectations_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_expectations_unittest.py
index 26eb18d..2e1b6ec 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_expectations_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_expectations_unittest.py
@@ -106,14 +106,13 @@ BUG_TEST WONTFIX WIN : failures/expected/image.html = IMAGE
"""
def parse_exp(self, expectations, overrides=None, is_lint_mode=False,
- is_debug_mode=False, tests_are_present=True):
+ is_debug_mode=False):
self._exp = TestExpectations(self._port,
tests=self.get_basic_tests(),
expectations=expectations,
test_platform_name=self._port.test_platform_name(),
is_debug_mode=is_debug_mode,
is_lint_mode=is_lint_mode,
- tests_are_present=tests_are_present,
overrides=overrides)
def assert_exp(self, test, result):
@@ -151,10 +150,6 @@ BUGX DEFER : failures/expected = IMAGE
self.assert_exp('failures/expected/text.html', TEXT)
self.assert_exp('failures/expected/crash.html', IMAGE)
- self.parse_exp(exp_str, tests_are_present=False)
- self.assert_exp('failures/expected/text.html', TEXT)
- self.assert_exp('failures/expected/crash.html', IMAGE)
-
def test_release_mode(self):
self.parse_exp('BUGX DEBUG : failures/expected/text.html = TEXT',
is_debug_mode=True)
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py
index 70beac3..6a5d43b 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py
@@ -42,11 +42,13 @@ import sys
import time
import apache_http_server
+import test_files
import http_server
import websocket_server
from webkitpy.common.system import logutils
from webkitpy.common.system.executive import Executive, ScriptError
+from webkitpy.common.system.user import User
_log = logutils.get_logger(__file__)
@@ -81,14 +83,15 @@ class Port(object):
}
return flags_by_configuration[configuration]
- def __init__(self, port_name=None, options=None, executive=Executive()):
- self._name = port_name
- self._options = options
+ def __init__(self, **kwargs):
+ self._name = kwargs.get('port_name', None)
+ self._options = kwargs.get('options', None)
+ self._executive = kwargs.get('executive', Executive())
+ self._user = kwargs.get('user', User())
self._helper = None
self._http_server = None
self._webkit_base_dir = None
self._websocket_server = None
- self._executive = executive
def default_child_processes(self):
"""Return the number of DumpRenderTree instances to use for this
@@ -130,11 +133,11 @@ class Port(object):
interface so that it can be overriden for testing purposes."""
return expected_text != actual_text
- def diff_image(self, expected_filename, actual_filename,
+ def diff_image(self, expected_contents, actual_contents,
diff_filename=None, tolerance=0):
- """Compare two image files and produce a delta image file.
+ """Compare two images and produce a delta image file.
- Return True if the two files are different, False if they are the same.
+ Return True if the two images are different, False if they are the same.
Also produce a delta image of the two images and write that into
|diff_filename| if it is not None.
@@ -252,6 +255,31 @@ class Port(object):
return os.path.join(platform_dir, baseline_filename)
return os.path.join(self.layout_tests_dir(), baseline_filename)
+ def _expected_file_contents(self, test, extension, encoding):
+ path = self.expected_filename(test, extension)
+ if not os.path.exists(path):
+ return None
+ with codecs.open(path, 'r', encoding) as file:
+ return file.read()
+
+ def expected_checksum(self, test):
+ """Returns the checksum of the image we expect the test to produce, or None if it is a text-only test."""
+ return self._expected_file_contents(test, '.checksum', 'ascii')
+
+ def expected_image(self, test):
+ """Returns the image we expect the test to produce."""
+ return self._expected_file_contents(test, '.png', None)
+
+ def expected_text(self, test):
+ """Returns the text output we expect the test to produce."""
+ # NOTE: -expected.txt files are ALWAYS utf-8. However,
+ # we do not decode the output from DRT, so we should not
+ # decode the -expected.txt values either to allow comparisons.
+ text = self._expected_file_contents(test, '.txt', None)
+ if not text:
+ return ''
+ return text.strip("\r\n").replace("\r\n", "\n") + "\n"
+
def filename_to_uri(self, filename):
"""Convert a test file to a URI."""
LAYOUTTEST_HTTP_DIR = "http/tests/"
@@ -287,6 +315,73 @@ class Port(object):
return "file:///" + self.get_absolute_path(filename)
return "file://" + self.get_absolute_path(filename)
+ def tests(self, paths):
+ """Return the list of tests found (relative to layout_tests_dir()."""
+ return test_files.find(self, paths)
+
+ def test_dirs(self):
+ """Returns the list of top-level test directories.
+
+ Used by --clobber-old-results."""
+ layout_tests_dir = self.layout_tests_dir()
+ return filter(lambda x: os.path.isdir(os.path.join(layout_tests_dir, x)),
+ os.listdir(layout_tests_dir))
+
+ def path_isdir(self, path):
+ """Returns whether the path refers to a directory of tests.
+
+ Used by test_expectations.py to apply rules to whole directories."""
+ return os.path.isdir(path)
+
+ def path_exists(self, path):
+ """Returns whether the path refers to an existing test or baseline."""
+ # Used by test_expectations.py to determine if an entry refers to a
+ # valid test and by printing.py to determine if baselines exist."""
+ return os.path.exists(path)
+
+ def update_baseline(self, path, data, encoding):
+ """Updates the baseline for a test.
+
+ Args:
+ path: the actual path to use for baseline, not the path to
+ the test. This function is used to update either generic or
+ platform-specific baselines, but we can't infer which here.
+ data: contents of the baseline.
+ encoding: file encoding to use for the baseline.
+ """
+ with codecs.open(path, "w", encoding=encoding) as file:
+ file.write(data)
+
+ def uri_to_test_name(self, uri):
+ """Return the base layout test name for a given URI.
+
+ This returns the test name for a given URI, e.g., if you passed in
+ "file:///src/LayoutTests/fast/html/keygen.html" it would return
+ "fast/html/keygen.html".
+
+ """
+ test = uri
+ if uri.startswith("file:///"):
+ if sys.platform == 'win32':
+ test = test.replace('file:///', '')
+ test = test.replace('/', '\\')
+ else:
+ test = test.replace('file://', '')
+ return self.relative_test_filename(test)
+
+ if uri.startswith("http://127.0.0.1:8880/"):
+ # websocket tests
+ return test.replace('http://127.0.0.1:8880/', '')
+
+ if uri.startswith("http://"):
+ # regular HTTP test
+ return test.replace('http://127.0.0.1:8000/', 'http/tests/')
+
+ if uri.startswith("https://"):
+ return test.replace('https://127.0.0.1:8443/', 'http/tests/')
+
+ raise NotImplementedError('unknown url type: %s' % uri)
+
def get_absolute_path(self, filename):
"""Return the absolute path in unix format for the given filename.
@@ -369,10 +464,10 @@ class Port(object):
"""
return os.environ.copy()
- def show_html_results_file(self, results_filename):
+ def show_results_html_file(self, results_filename):
"""This routine should display the HTML file pointed at by
results_filename in a users' browser."""
- raise NotImplementedError('Port.show_html_results_file')
+ return self._user.open_url(results_filename)
def create_driver(self, image_path, options):
"""Return a newly created base.Driver subclass for starting/stopping
@@ -588,7 +683,7 @@ class Port(object):
try:
with self._open_configuration_file() as file:
return file.readline().rstrip()
- except IOError, e:
+ except:
return None
# FIXME: This list may be incomplete as Apple has some sekret configs.
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/base_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/base_unittest.py
index 780cd22..71877b3 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/base_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/base_unittest.py
@@ -57,16 +57,17 @@ class MockExecutive():
class UnitTestPort(base.Port):
"""Subclass of base.Port used for unit testing."""
- def __init__(self, configuration_contents=None, executive_exception=None):
+ def __init__(self, configuration_contents=None, configuration_exception=IOError, executive_exception=None):
base.Port.__init__(self)
self._configuration_contents = configuration_contents
+ self._configuration_exception = configuration_exception
if executive_exception:
self._executive = MockExecutive(executive_exception)
def _open_configuration_file(self):
if self._configuration_contents:
return NewStringIO(self._configuration_contents)
- raise IOError
+ raise self._configuration_exception
class PortTest(unittest.TestCase):
@@ -191,9 +192,14 @@ class PortTest(unittest.TestCase):
self.assertFalse('nosuchthing' in diff)
def test_default_configuration_notfound(self):
+ # Regular IOError thrown while trying to get the configuration.
port = UnitTestPort()
self.assertEqual(port.default_configuration(), "Release")
+ # More exotic OSError thrown.
+ port = UnitTestPort(configuration_exception=OSError)
+ self.assertEqual(port.default_configuration(), "Release")
+
def test_layout_tests_skipping(self):
port = base.Port()
port.skipped_layout_tests = lambda: ['foo/bar.html', 'media']
@@ -214,6 +220,11 @@ class PortTest(unittest.TestCase):
# This routine is a no-op. We just test it for coverage.
port.setup_test_run()
+ def test_test_dirs(self):
+ port = base.Port()
+ dirs = port.test_dirs()
+ self.assertTrue('canvas' in dirs)
+ self.assertTrue('css2.1' in dirs)
class VirtualTest(unittest.TestCase):
"""Tests that various methods expected to be virtual are."""
@@ -231,7 +242,6 @@ class VirtualTest(unittest.TestCase):
self.assertVirtual(port.path_to_test_expectations_file)
self.assertVirtual(port.test_platform_name)
self.assertVirtual(port.results_directory)
- self.assertVirtual(port.show_html_results_file, None)
self.assertVirtual(port.test_expectations)
self.assertVirtual(port.test_base_platform_names)
self.assertVirtual(port.test_platform_name)
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium.py
index 3fc4613..a72627a 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium.py
@@ -39,6 +39,7 @@ import shutil
import signal
import subprocess
import sys
+import tempfile
import time
import webbrowser
@@ -46,7 +47,6 @@ import base
import http_server
from webkitpy.common.system.executive import Executive
-from webkitpy.layout_tests.layout_package import test_files
from webkitpy.layout_tests.layout_package import test_expectations
# Chromium DRT on OSX uses WebKitDriver.
@@ -82,8 +82,13 @@ def check_file_exists(path_to_file, file_description, override_step=None,
class ChromiumPort(base.Port):
"""Abstract base class for Chromium implementations of the Port class."""
- def __init__(self, port_name=None, options=None, **kwargs):
- base.Port.__init__(self, port_name, options, **kwargs)
+ def __init__(self, **kwargs):
+ base.Port.__init__(self, **kwargs)
+ if 'options' in kwargs:
+ options = kwargs['options']
+ if (options and (not hasattr(options, 'configuration') or
+ options.configuration is None)):
+ options.configuration = self.default_configuration()
self._chromium_base_dir = None
def baseline_path(self):
@@ -126,14 +131,18 @@ class ChromiumPort(base.Port):
return check_file_exists(image_diff_path, 'image diff exe',
override_step, logging)
- def diff_image(self, expected_filename, actual_filename,
+ def diff_image(self, expected_contents, actual_contents,
diff_filename=None, tolerance=0):
executable = self._path_to_image_diff()
+ expected_tmpfile = tempfile.NamedTemporaryFile()
+ expected_tmpfile.write(expected_contents)
+ actual_tmpfile = tempfile.NamedTemporaryFile()
+ actual_tmpfile.write(actual_contents)
if diff_filename:
- cmd = [executable, '--diff', expected_filename, actual_filename,
- diff_filename]
+ cmd = [executable, '--diff', expected_tmpfile.name,
+ actual_tmpfile.name, diff_filename]
else:
- cmd = [executable, expected_filename, actual_filename]
+ cmd = [executable, expected_tmpfile.name, actual_tmpfile.name]
result = True
try:
@@ -144,6 +153,9 @@ class ChromiumPort(base.Port):
_compare_available = False
else:
raise e
+ finally:
+ expected_tmpfile.close()
+ actual_tmpfile.close()
return result
def driver_name(self):
@@ -183,15 +195,6 @@ class ChromiumPort(base.Port):
if os.path.exists(cachedir):
shutil.rmtree(cachedir)
- def show_results_html_file(self, results_filename):
- uri = self.get_absolute_path(results_filename)
- if self._options.use_drt:
- # FIXME: This should use User.open_url
- webbrowser.open(uri, new=1)
- else:
- # Note: Not thread safe: http://bugs.python.org/issue2320
- subprocess.Popen([self._path_to_driver(), uri])
-
def create_driver(self, image_path, options):
"""Starts a new Driver and returns a handle to it."""
if options.use_drt and sys.platform == 'darwin':
@@ -236,7 +239,7 @@ class ChromiumPort(base.Port):
# FIXME: This drt_overrides handling should be removed when we switch
# from tes_shell to DRT.
drt_overrides = ''
- if self._options.use_drt:
+ if self._options and self._options.use_drt:
drt_overrides_path = self.path_from_webkit_base('LayoutTests',
'platform', 'chromium', 'drt_expectations.txt')
if os.path.exists(drt_overrides_path):
@@ -259,14 +262,13 @@ class ChromiumPort(base.Port):
test_platform_name = self.test_platform_name()
is_debug_mode = False
- all_test_files = test_files.gather_test_files(self, '*')
+ all_test_files = self.tests([])
if extra_test_files:
all_test_files.update(extra_test_files)
expectations = test_expectations.TestExpectations(
self, all_test_files, expectations_str, test_platform_name,
- is_debug_mode, is_lint_mode=True,
- tests_are_present=False, overrides=overrides_str)
+ is_debug_mode, is_lint_mode=True, overrides=overrides_str)
tests_dir = self.layout_tests_dir()
return [self.relative_test_filename(test)
for test in expectations.get_tests_with_result_type(test_expectations.SKIP)]
@@ -354,6 +356,12 @@ class ChromiumDriver(base.Driver):
if self._options.gp_fault_error_box:
driver_args.append('--gp-fault-error-box')
+
+ if self._options.accelerated_compositing:
+ driver_args.append('--enable-accelerated-compositing')
+
+ if self._options.accelerated_2d_canvas:
+ driver_args.append('--enable-accelerated-2d-canvas')
return driver_args
def start(self):
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py
new file mode 100644
index 0000000..80602d9
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python
+# Copyright (C) 2010 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from __future__ import with_statement
+
+import codecs
+import os
+import sys
+
+import chromium_linux
+import chromium_mac
+import chromium_win
+
+
+def get(**kwargs):
+ """Some tests have slightly different results when run while using
+ hardware acceleration. In those cases, we prepend an additional directory
+ to the baseline paths."""
+ port_name = kwargs.get('port_name', None)
+ if port_name == 'chromium-gpu':
+ if sys.platform in ('cygwin', 'win32'):
+ port_name = 'chromium-gpu-win'
+ elif sys.platform == 'linux2':
+ port_name = 'chromium-gpu-linux'
+ elif sys.platform == 'darwin':
+ port_name = 'chromium-gpu-mac'
+ else:
+ raise NotImplementedError('unsupported platform: %s' %
+ sys.platform)
+
+ if port_name == 'chromium-gpu-linux':
+ return ChromiumGpuLinuxPort(**kwargs)
+
+ if port_name.startswith('chromium-gpu-mac'):
+ return ChromiumGpuMacPort(**kwargs)
+
+ if port_name.startswith('chromium-gpu-win'):
+ return ChromiumGpuWinPort(**kwargs)
+
+ raise NotImplementedError('unsupported port: %s' % port_name)
+
+
+def _set_gpu_options(options):
+ if options:
+ if options.accelerated_compositing is None:
+ options.accelerated_composting = True
+ if options.accelerated_2d_canvas is None:
+ options.accelerated_2d_canvas = True
+
+
+def _gpu_overrides(port):
+ try:
+ overrides_path = port.path_from_chromium_base('webkit', 'tools',
+ 'layout_tests', 'test_expectations_gpu.txt')
+ except AssertionError:
+ return None
+ if not os.path.exists(overrides_path):
+ return None
+ with codecs.open(overrides_path, "r", "utf-8") as file:
+ return file.read()
+
+
+class ChromiumGpuLinuxPort(chromium_linux.ChromiumLinuxPort):
+ def __init__(self, **kwargs):
+ kwargs.setdefault('port_name', 'chromium-gpu-linux')
+ _set_gpu_options(kwargs.get('options'))
+ chromium_linux.ChromiumLinuxPort.__init__(self, **kwargs)
+
+ def baseline_search_path(self):
+ return ([self._webkit_baseline_path('chromium-gpu-linux')] +
+ chromium_linux.ChromiumLinuxPort.baseline_search_path(self))
+
+ def path_to_test_expectations_file(self):
+ return self.path_from_webkit_base('LayoutTests', 'platform',
+ 'chromium-gpu', 'test_expectations.txt')
+
+ def test_expectations_overrides(self):
+ return _gpu_overrides(self)
+
+
+class ChromiumGpuMacPort(chromium_mac.ChromiumMacPort):
+ def __init__(self, **kwargs):
+ kwargs.setdefault('port_name', 'chromium-gpu-mac')
+ _set_gpu_options(kwargs.get('options'))
+ chromium_mac.ChromiumMacPort.__init__(self, **kwargs)
+
+ def baseline_search_path(self):
+ return ([self._webkit_baseline_path('chromium-gpu-mac')] +
+ chromium_mac.ChromiumMacPort.baseline_search_path(self))
+
+ def path_to_test_expectations_file(self):
+ return self.path_from_webkit_base('LayoutTests', 'platform',
+ 'chromium-gpu', 'test_expectations.txt')
+
+ def test_expectations_overrides(self):
+ return _gpu_overrides(self)
+
+
+class ChromiumGpuWinPort(chromium_win.ChromiumWinPort):
+ def __init__(self, **kwargs):
+ kwargs.setdefault('port_name', 'chromium-gpu-win' + self.version())
+ _set_gpu_options(kwargs.get('options'))
+ chromium_win.ChromiumWinPort.__init__(self, **kwargs)
+
+ def baseline_search_path(self):
+ return ([self._webkit_baseline_path('chromium-gpu-win')] +
+ chromium_win.ChromiumWinPort.baseline_search_path(self))
+
+ def path_to_test_expectations_file(self):
+ return self.path_from_webkit_base('LayoutTests', 'platform',
+ 'chromium-gpu', 'test_expectations.txt')
+
+ def test_expectations_overrides(self):
+ return _gpu_overrides(self)
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py
new file mode 100644
index 0000000..5c79a3f
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+# Copyright (C) 2010 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import os
+import unittest
+import chromium_gpu
+
+
+class ChromiumGpuTest(unittest.TestCase):
+ def test_get_chromium_gpu_linux(self):
+ self.assertOverridesWorked('chromium-gpu-linux')
+
+ def test_get_chromium_gpu_mac(self):
+ self.assertOverridesWorked('chromium-gpu-mac')
+
+ def test_get_chromium_gpu_win(self):
+ self.assertOverridesWorked('chromium-gpu-win')
+
+ def assertOverridesWorked(self, port_name):
+ # test that we got the right port
+ port = chromium_gpu.get(port_name=port_name, options=None)
+
+ # we use startswith() instead of Equal to gloss over platform versions.
+ self.assertTrue(port.name().startswith(port_name))
+
+ # test that it has the right directory in front of the search path.
+ path = port.baseline_search_path()[0]
+ self.assertEqual(port._webkit_baseline_path(port_name), path)
+
+ # test that we have the right expectations file.
+ self.assertTrue('chromium-gpu' in
+ port.path_to_test_expectations_file())
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_linux.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_linux.py
index 4df43e0..176991b 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_linux.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_linux.py
@@ -41,12 +41,9 @@ _log = logging.getLogger("webkitpy.layout_tests.port.chromium_linux")
class ChromiumLinuxPort(chromium.ChromiumPort):
"""Chromium Linux implementation of the Port class."""
- def __init__(self, port_name=None, options=None):
- if port_name is None:
- port_name = 'chromium-linux'
- if options and not hasattr(options, 'configuration'):
- options.configuration = 'Release'
- chromium.ChromiumPort.__init__(self, port_name, options)
+ def __init__(self, **kwargs):
+ kwargs.setdefault('port_name', 'chromium-linux')
+ chromium.ChromiumPort.__init__(self, **kwargs)
def baseline_search_path(self):
port_names = ["chromium-linux", "chromium-win", "chromium", "win", "mac"]
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_mac.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_mac.py
index abd84ae..64016ab 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_mac.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_mac.py
@@ -44,12 +44,9 @@ _log = logging.getLogger("webkitpy.layout_tests.port.chromium_mac")
class ChromiumMacPort(chromium.ChromiumPort):
"""Chromium Mac implementation of the Port class."""
- def __init__(self, port_name=None, options=None):
- if port_name is None:
- port_name = 'chromium-mac'
- if options and not hasattr(options, 'configuration'):
- options.configuration = 'Release'
- chromium.ChromiumPort.__init__(self, port_name, options)
+ def __init__(self, **kwargs):
+ kwargs.setdefault('port_name', 'chromium-mac')
+ chromium.ChromiumPort.__init__(self, **kwargs)
def baseline_search_path(self):
port_names = ["chromium-mac", "chromium", "mac" + self.version(), "mac"]
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py
index 7a005b1..a4a9ea6 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py
@@ -83,17 +83,39 @@ class ChromiumDriverTest(unittest.TestCase):
self.driver._proc.stdout.readline = mock_readline
self._assert_write_command_and_read_line(expected_crash=True)
+
+class ChromiumPortTest(unittest.TestCase):
+ class TestMacPort(chromium_mac.ChromiumMacPort):
+ def __init__(self, options):
+ chromium_mac.ChromiumMacPort.__init__(self,
+ port_name='test-port',
+ options=options)
+
+ def default_configuration(self):
+ self.default_configuration_called = True
+ return 'default'
+
+ class TestLinuxPort(chromium_linux.ChromiumLinuxPort):
+ def __init__(self, options):
+ chromium_linux.ChromiumLinuxPort.__init__(self,
+ port_name='test-port',
+ options=options)
+
+ def default_configuration(self):
+ self.default_configuration_called = True
+ return 'default'
+
def test_path_to_image_diff(self):
class MockOptions:
def __init__(self):
self.use_drt = True
- port = chromium_linux.ChromiumLinuxPort('test-port', options=MockOptions())
+ port = ChromiumPortTest.TestLinuxPort(options=MockOptions())
self.assertTrue(port._path_to_image_diff().endswith(
- '/out/Release/ImageDiff'))
- port = chromium_mac.ChromiumMacPort('test-port', options=MockOptions())
+ '/out/default/ImageDiff'), msg=port._path_to_image_diff())
+ port = ChromiumPortTest.TestMacPort(options=MockOptions())
self.assertTrue(port._path_to_image_diff().endswith(
- '/xcodebuild/Release/ImageDiff'))
+ '/xcodebuild/default/ImageDiff'))
# FIXME: Figure out how this is going to work on Windows.
#port = chromium_win.ChromiumWinPort('test-port', options=MockOptions())
@@ -102,16 +124,37 @@ class ChromiumDriverTest(unittest.TestCase):
def __init__(self):
self.use_drt = True
- port = chromium_linux.ChromiumLinuxPort('test-port', options=MockOptions())
+ port = ChromiumPortTest.TestLinuxPort(options=MockOptions())
fake_test = os.path.join(port.layout_tests_dir(), "fast/js/not-good.js")
port.test_expectations = lambda: """BUG_TEST SKIP : fast/js/not-good.js = TEXT
DEFER LINUX WIN : fast/js/very-good.js = TIMEOUT PASS"""
port.test_expectations_overrides = lambda: ''
+ port.tests = lambda paths: set()
+ port.path_exists = lambda test: True
skipped_tests = port.skipped_layout_tests(extra_test_files=[fake_test, ])
self.assertTrue("fast/js/not-good.js" in skipped_tests)
+ def test_default_configuration(self):
+ class EmptyOptions:
+ def __init__(self):
+ pass
+
+ options = EmptyOptions()
+ port = ChromiumPortTest.TestLinuxPort(options)
+ self.assertEquals(options.configuration, 'default')
+ self.assertTrue(port.default_configuration_called)
+
+ class OptionsWithUnsetConfiguration:
+ def __init__(self):
+ self.configuration = None
+
+ options = OptionsWithUnsetConfiguration()
+ port = ChromiumPortTest.TestLinuxPort(options)
+ self.assertEquals(options.configuration, 'default')
+ self.assertTrue(port.default_configuration_called)
+
if __name__ == '__main__':
unittest.main()
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win.py
index e9a81e7..d2b0265 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win.py
@@ -41,12 +41,9 @@ _log = logging.getLogger("webkitpy.layout_tests.port.chromium_win")
class ChromiumWinPort(chromium.ChromiumPort):
"""Chromium Win implementation of the Port class."""
- def __init__(self, port_name=None, options=None):
- if port_name is None:
- port_name = "chromium-win" + self.version()
- if options and not hasattr(options, "configuration"):
- options.configuration = "Release"
- chromium.ChromiumPort.__init__(self, port_name, options)
+ def __init__(self, **kwargs):
+ kwargs.setdefault('port_name', 'chromium-win' + self.version())
+ chromium.ChromiumPort.__init__(self, **kwargs)
def setup_environ_for_server(self):
env = chromium.ChromiumPort.setup_environ_for_server(self)
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/dryrun.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/dryrun.py
index 4940e4c..648ccad 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/dryrun.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/dryrun.py
@@ -46,48 +46,24 @@
from __future__ import with_statement
+import os
import sys
import base
import factory
-def _read_file(path, mode='r'):
- """Return the contents of a file as a string.
-
- Returns '' if anything goes wrong, instead of throwing an IOError.
-
- """
- contents = ''
- try:
- with open(path, mode) as f:
- contents = f.read()
- except IOError:
- pass
- return contents
-
-
-def _write_file(path, contents, mode='w'):
- """Write the string to the specified path.
-
- Writes should never fail, so we may raise IOError.
-
- """
- with open(path, mode) as f:
- f.write(contents)
-
-
class DryRunPort(object):
"""DryRun implementation of the Port interface."""
- def __init__(self, port_name=None, options=None):
+ def __init__(self, **kwargs):
pfx = 'dryrun-'
- if port_name.startswith(pfx):
- port_name = port_name[len(pfx):]
- else:
- port_name = None
- self._options = options
- self.__delegate = factory.get(port_name, options)
+ if 'port_name' in kwargs:
+ if kwargs['port_name'].startswith(pfx):
+ kwargs['port_name'] = kwargs['port_name'][len(pfx):]
+ else:
+ kwargs['port_name'] = None
+ self.__delegate = factory.get(**kwargs)
def __getattr__(self, name):
return getattr(self.__delegate, name)
@@ -134,19 +110,16 @@ class DryrunDriver(base.Driver):
return None
def run_test(self, uri, timeoutms, image_hash):
- test_name = self._uri_to_test(uri)
-
- text_filename = self._port.expected_filename(test_name, '.txt')
- text_output = _read_file(text_filename)
+ test_name = self._port.uri_to_test_name(uri)
+ path = os.path.join(self._port.layout_tests_dir(), test_name)
+ text_output = self._port.expected_text(path)
if image_hash is not None:
- image_filename = self._port.expected_filename(test_name, '.png')
- image = _read_file(image_filename, 'rb')
- if self._image_path:
- _write_file(self._image_path, image)
- hash_filename = self._port.expected_filename(test_name,
- '.checksum')
- hash = _read_file(hash_filename)
+ image = self._port.expected_image(path)
+ if image and self._image_path:
+ with open(self._image_path, 'w') as f:
+ f.write(image)
+ hash = self._port.expected_checksum(path)
else:
hash = None
return (False, False, hash, text_output, None)
@@ -156,39 +129,3 @@ class DryrunDriver(base.Driver):
def stop(self):
pass
-
- def _uri_to_test(self, uri):
- """Return the base layout test name for a given URI.
-
- This returns the test name for a given URI, e.g., if you passed in
- "file:///src/LayoutTests/fast/html/keygen.html" it would return
- "fast/html/keygen.html".
-
- """
- if not self._layout_tests_dir:
- self._layout_tests_dir = self._port.layout_tests_dir()
- test = uri
-
- if uri.startswith("file:///"):
- if sys.platform == 'win32':
- test = test.replace('file:///', '')
- test = test.replace('/', '\\')
- else:
- test = test.replace('file://', '')
- return test
- elif uri.startswith("http://127.0.0.1:8880/"):
- # websocket tests
- test = test.replace('http://127.0.0.1:8880/',
- self._layout_tests_dir + '/')
- return test
- elif uri.startswith("http://"):
- # regular HTTP test
- test = test.replace('http://127.0.0.1:8000/',
- self._layout_tests_dir + '/http/tests/')
- return test
- elif uri.startswith("https://"):
- test = test.replace('https://127.0.0.1:8443/',
- self._layout_tests_dir + '/http/tests/')
- return test
- else:
- raise NotImplementedError('unknown url type: %s' % uri)
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/factory.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/factory.py
index 5704f65..6935744 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/factory.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/factory.py
@@ -37,11 +37,21 @@ ALL_PORT_NAMES = ['test', 'dryrun', 'mac', 'win', 'gtk', 'qt', 'chromium-mac',
'google-chrome-mac', 'google-chrome-linux32', 'google-chrome-linux64']
-def get(port_name=None, options=None):
+def get(port_name=None, options=None, **kwargs):
"""Returns an object implementing the Port interface. If
port_name is None, this routine attempts to guess at the most
appropriate port on this platform."""
- port_to_use = port_name
+ # Wrapped for backwards-compatibility
+ if port_name:
+ kwargs['port_name'] = port_name
+ if options:
+ kwargs['options'] = options
+ return _get_kwargs(**kwargs)
+
+
+def _get_kwargs(**kwargs):
+ port_to_use = kwargs.get('port_name', None)
+ options = kwargs.get('options', None)
if port_to_use is None:
if sys.platform == 'win32' or sys.platform == 'cygwin':
if options and hasattr(options, 'chromium') and options.chromium:
@@ -62,37 +72,40 @@ def get(port_name=None, options=None):
if port_to_use == 'test':
import test
- return test.TestPort(port_name, options)
+ maker = test.TestPort
elif port_to_use.startswith('dryrun'):
import dryrun
- return dryrun.DryRunPort(port_name, options)
+ maker = dryrun.DryRunPort
elif port_to_use.startswith('mac'):
import mac
- return mac.MacPort(port_name, options)
+ maker = mac.MacPort
elif port_to_use.startswith('win'):
import win
- return win.WinPort(port_name, options)
+ maker = win.WinPort
elif port_to_use.startswith('gtk'):
import gtk
- return gtk.GtkPort(port_name, options)
+ maker = gtk.GtkPort
elif port_to_use.startswith('qt'):
import qt
- return qt.QtPort(port_name, options)
+ maker = qt.QtPort
+ elif port_to_use.startswith('chromium-gpu'):
+ import chromium_gpu
+ maker = chromium_gpu.get
elif port_to_use.startswith('chromium-mac'):
import chromium_mac
- return chromium_mac.ChromiumMacPort(port_name, options)
+ maker = chromium_mac.ChromiumMacPort
elif port_to_use.startswith('chromium-linux'):
import chromium_linux
- return chromium_linux.ChromiumLinuxPort(port_name, options)
+ maker = chromium_linux.ChromiumLinuxPort
elif port_to_use.startswith('chromium-win'):
import chromium_win
- return chromium_win.ChromiumWinPort(port_name, options)
+ maker = chromium_win.ChromiumWinPort
elif port_to_use.startswith('google-chrome'):
import google_chrome
- return google_chrome.GetGoogleChromePort(port_name, options)
-
- raise NotImplementedError('unsupported port: %s' % port_to_use)
-
+ maker = google_chrome.GetGoogleChromePort
+ else:
+ raise NotImplementedError('unsupported port: %s' % port_to_use)
+ return maker(**kwargs)
def get_all(options=None):
"""Returns all the objects implementing the Port interface."""
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/factory_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/factory_unittest.py
index c0a4c5e..81c3732 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/factory_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/factory_unittest.py
@@ -29,6 +29,7 @@
import sys
import unittest
+import chromium_gpu
import chromium_linux
import chromium_mac
import chromium_win
@@ -133,6 +134,15 @@ class FactoryTest(unittest.TestCase):
def test_qt(self):
self.assert_port("qt", qt.QtPort)
+ def test_chromium_gpu_linux(self):
+ self.assert_port("chromium-gpu-linux", chromium_gpu.ChromiumGpuLinuxPort)
+
+ def test_chromium_gpu_mac(self):
+ self.assert_port("chromium-gpu-mac", chromium_gpu.ChromiumGpuMacPort)
+
+ def test_chromium_gpu_win(self):
+ self.assert_port("chromium-gpu-win", chromium_gpu.ChromiumGpuWinPort)
+
def test_chromium_mac(self):
self.assert_port("chromium-mac", chromium_mac.ChromiumMacPort)
self.assert_platform_port("darwin", self.chromium_options,
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome.py
index 46ab3ed..bffc860 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome.py
@@ -25,10 +25,12 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-def GetGoogleChromePort(port_name, options):
+def GetGoogleChromePort(**kwargs):
"""Some tests have slightly different results when compiled as Google
Chrome vs Chromium. In those cases, we prepend an additional directory to
to the baseline paths."""
+ port_name = kwargs['port_name']
+ del kwargs['port_name']
if port_name == 'google-chrome-linux32':
import chromium_linux
@@ -39,7 +41,7 @@ def GetGoogleChromePort(port_name, options):
paths.insert(0, self._webkit_baseline_path(
'google-chrome-linux32'))
return paths
- return GoogleChromeLinux32Port(None, options)
+ return GoogleChromeLinux32Port(**kwargs)
elif port_name == 'google-chrome-linux64':
import chromium_linux
@@ -50,7 +52,7 @@ def GetGoogleChromePort(port_name, options):
paths.insert(0, self._webkit_baseline_path(
'google-chrome-linux64'))
return paths
- return GoogleChromeLinux64Port(None, options)
+ return GoogleChromeLinux64Port(**kwargs)
elif port_name.startswith('google-chrome-mac'):
import chromium_mac
@@ -61,7 +63,7 @@ def GetGoogleChromePort(port_name, options):
paths.insert(0, self._webkit_baseline_path(
'google-chrome-mac'))
return paths
- return GoogleChromeMacPort(None, options)
+ return GoogleChromeMacPort(**kwargs)
elif port_name.startswith('google-chrome-win'):
import chromium_win
@@ -72,5 +74,5 @@ def GetGoogleChromePort(port_name, options):
paths.insert(0, self._webkit_baseline_path(
'google-chrome-win'))
return paths
- return GoogleChromeWinPort(None, options)
+ return GoogleChromeWinPort(**kwargs)
raise NotImplementedError('unsupported port: %s' % port_name)
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome_unittest.py
index a2d7056..85e9338 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome_unittest.py
@@ -41,6 +41,7 @@ class GetGoogleChromePortTest(unittest.TestCase):
self._verify_baseline_path('google-chrome-win', 'google-chrome-win-vista')
def _verify_baseline_path(self, expected_path, port_name):
- port = google_chrome.GetGoogleChromePort(port_name, None)
+ port = google_chrome.GetGoogleChromePort(port_name=port_name,
+ options=None)
path = port.baseline_search_path()[0]
self.assertEqual(expected_path, os.path.split(path)[1])
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/gtk.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/gtk.py
index 59dc1d9..c60909e 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/gtk.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/gtk.py
@@ -39,10 +39,9 @@ _log = logging.getLogger("webkitpy.layout_tests.port.gtk")
class GtkPort(WebKitPort):
"""WebKit Gtk implementation of the Port class."""
- def __init__(self, port_name=None, options=None):
- if port_name is None:
- port_name = 'gtk'
- WebKitPort.__init__(self, port_name, options)
+ def __init__(self, **kwargs):
+ kwargs.setdefault('port_name', 'gtk')
+ WebKitPort.__init__(self, **kwargs)
def _tests_for_other_platforms(self):
# FIXME: This list could be dynamic based on platform name and
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/mac.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/mac.py
index 413b5f2..696e339 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/mac.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/mac.py
@@ -43,10 +43,9 @@ _log = logging.getLogger("webkitpy.layout_tests.port.mac")
class MacPort(WebKitPort):
"""WebKit Mac implementation of the Port class."""
- def __init__(self, port_name=None, options=None):
- if port_name is None:
- port_name = 'mac' + self.version()
- WebKitPort.__init__(self, port_name, options)
+ def __init__(self, **kwargs):
+ kwargs.setdefault('port_name', 'mac' + self.version())
+ WebKitPort.__init__(self, **kwargs)
def default_child_processes(self):
# FIXME: new-run-webkit-tests is unstable on Mac running more than
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/port_testcase.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/port_testcase.py
index 2d650f5..47597d6 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/port_testcase.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/port_testcase.py
@@ -68,14 +68,20 @@ class PortTestCase(unittest.TestCase):
dir = port.layout_tests_dir()
file1 = os.path.join(dir, 'fast', 'css', 'button_center.png')
+ fh1 = file(file1)
+ contents1 = fh1.read()
file2 = os.path.join(dir, 'fast', 'css',
'remove-shorthand-expected.png')
+ fh2 = file(file2)
+ contents2 = fh2.read()
tmpfile = tempfile.mktemp()
- self.assertFalse(port.diff_image(file1, file1))
- self.assertTrue(port.diff_image(file1, file2))
+ self.assertFalse(port.diff_image(contents1, contents1))
+ self.assertTrue(port.diff_image(contents1, contents2))
- self.assertTrue(port.diff_image(file1, file2, tmpfile))
+ self.assertTrue(port.diff_image(contents1, contents2, tmpfile))
+ fh1.close()
+ fh2.close()
# FIXME: this may not be being written?
# self.assertTrue(os.path.exists(tmpfile))
# os.remove(tmpfile)
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/qt.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/qt.py
index 158c633..4c8fa0a 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/qt.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/qt.py
@@ -42,10 +42,9 @@ _log = logging.getLogger("webkitpy.layout_tests.port.qt")
class QtPort(WebKitPort):
"""QtWebKit implementation of the Port class."""
- def __init__(self, port_name=None, options=None):
- if port_name is None:
- port_name = 'qt'
- WebKitPort.__init__(self, port_name, options)
+ def __init__(self, **kwargs):
+ kwargs.setdefault('port_name', 'qt')
+ WebKitPort.__init__(self, **kwargs)
def _tests_for_other_platforms(self):
# FIXME: This list could be dynamic based on platform name and
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/server_process.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/server_process.py
index 8e0bc11..5a0a40c 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/server_process.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/server_process.py
@@ -179,7 +179,7 @@ class ServerProcess:
elif size == 0:
index = self._output.find('\n') + 1
- if index or self.crashed or self.timed_out:
+ if index > 0 or self.crashed or self.timed_out:
output = self._output[0:index]
self._output = self._output[index:]
return output
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/test.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/test.py
index 2ccddb0..3b81167 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/test.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/test.py
@@ -31,17 +31,100 @@
from __future__ import with_statement
import codecs
+import fnmatch
import os
+import sys
import time
import base
+# This sets basic expectations for a test. Each individual expectation
+# can be overridden by a keyword argument in TestList.add().
+class TestInstance:
+ def __init__(self, name):
+ self.name = name
+ self.base = name[(name.rfind("/") + 1):name.rfind(".html")]
+ self.crash = False
+ self.exception = False
+ self.hang = False
+ self.keyboard = False
+ self.error = ''
+ self.timeout = False
+ self.actual_text = self.base + '-txt\n'
+ self.actual_checksum = self.base + '-checksum\n'
+ self.actual_image = self.base + '-png\n'
+ self.expected_text = self.actual_text
+ self.expected_checksum = self.actual_checksum
+ self.expected_image = self.actual_image
+
+
+# This is an in-memory list of tests, what we want them to produce, and
+# what we want to claim are the expected results.
+class TestList:
+ def __init__(self, port):
+ self.port = port
+ self.tests = {}
+
+ def add(self, name, **kwargs):
+ test = TestInstance(name)
+ for key, value in kwargs.items():
+ test.__dict__[key] = value
+ self.tests[name] = test
+
+ def keys(self):
+ return self.tests.keys()
+
+ def __contains__(self, item):
+ return item in self.tests
+
+ def __getitem__(self, item):
+ return self.tests[item]
+
+
class TestPort(base.Port):
"""Test implementation of the Port interface."""
- def __init__(self, port_name=None, options=None):
- base.Port.__init__(self, port_name, options)
+ def __init__(self, **kwargs):
+ base.Port.__init__(self, **kwargs)
+ tests = TestList(self)
+ tests.add('passes/image.html')
+ tests.add('passes/text.html')
+ tests.add('failures/expected/checksum.html',
+ actual_checksum='checksum_fail-checksum')
+ tests.add('failures/expected/crash.html', crash=True)
+ tests.add('failures/expected/exception.html', exception=True)
+ tests.add('failures/expected/timeout.html', timeout=True)
+ tests.add('failures/expected/hang.html', hang=True)
+ tests.add('failures/expected/missing_text.html',
+ expected_text=None)
+ tests.add('failures/expected/image.html',
+ actual_image='image_fail-png',
+ expected_image='image-png')
+ tests.add('failures/expected/image_checksum.html',
+ actual_checksum='image_checksum_fail-checksum',
+ actual_image='image_checksum_fail-png')
+ tests.add('failures/expected/keyboard.html',
+ keyboard=True)
+ tests.add('failures/expected/missing_check.html',
+ expected_checksum=None)
+ tests.add('failures/expected/missing_image.html',
+ expected_image=None)
+ tests.add('failures/expected/missing_text.html',
+ expected_text=None)
+ tests.add('failures/expected/text.html',
+ actual_text='text_fail-png')
+ tests.add('failures/unexpected/text-image-checksum.html',
+ actual_text='text-image-checksum_fail-txt',
+ actual_checksum='text-image-checksum_fail-checksum')
+ tests.add('http/tests/passes/text.html')
+ tests.add('http/tests/ssl/text.html')
+ tests.add('passes/error.html', error='stuff going to stderr')
+ tests.add('passes/image.html')
+ tests.add('passes/platform_image.html')
+ tests.add('passes/text.html')
+ tests.add('websocket/tests/passes/text.html')
+ self._tests = tests
def baseline_path(self):
return os.path.join(self.layout_tests_dir(), 'platform',
@@ -53,12 +136,8 @@ class TestPort(base.Port):
def check_build(self, needs_http):
return True
- def diff_image(self, expected_filename, actual_filename,
+ def diff_image(self, expected_contents, actual_contents,
diff_filename=None, tolerance=0):
- with codecs.open(actual_filename, "r", "utf-8") as actual_fh:
- actual_contents = actual_fh.read()
- with codecs.open(expected_filename, "r", "utf-8") as expected_fh:
- expected_contents = expected_fh.read()
diffed = actual_contents != expected_contents
if diffed and diff_filename:
with codecs.open(diff_filename, "w", "utf-8") as diff_fh:
@@ -66,24 +145,79 @@ class TestPort(base.Port):
(expected_contents, actual_contents))
return diffed
+ def expected_checksum(self, test):
+ test = self.relative_test_filename(test)
+ return self._tests[test].expected_checksum
+
+ def expected_image(self, test):
+ test = self.relative_test_filename(test)
+ return self._tests[test].expected_image
+
+ def expected_text(self, test):
+ test = self.relative_test_filename(test)
+ text = self._tests[test].expected_text
+ if not text:
+ text = ''
+ return text
+
+ def tests(self, paths):
+ # Test the idea of port-specific overrides for test lists. Also
+ # keep in memory to speed up the test harness.
+ if not paths:
+ paths = ['*']
+
+ matched_tests = []
+ for p in paths:
+ if self.path_isdir(p):
+ matched_tests.extend(fnmatch.filter(self._tests.keys(), p + '*'))
+ else:
+ matched_tests.extend(fnmatch.filter(self._tests.keys(), p))
+ layout_tests_dir = self.layout_tests_dir()
+ return set([os.path.join(layout_tests_dir, p) for p in matched_tests])
+
+ def path_exists(self, path):
+ # used by test_expectations.py and printing.py
+ rpath = self.relative_test_filename(path)
+ if rpath in self._tests:
+ return True
+ if self.path_isdir(rpath):
+ return True
+ if rpath.endswith('-expected.txt'):
+ test = rpath.replace('-expected.txt', '.html')
+ return (test in self._tests and
+ self._tests[test].expected_text)
+ if rpath.endswith('-expected.checksum'):
+ test = rpath.replace('-expected.checksum', '.html')
+ return (test in self._tests and
+ self._tests[test].expected_checksum)
+ if rpath.endswith('-expected.png'):
+ test = rpath.replace('-expected.png', '.html')
+ return (test in self._tests and
+ self._tests[test].expected_image)
+ return False
+
def layout_tests_dir(self):
return self.path_from_webkit_base('WebKitTools', 'Scripts',
'webkitpy', 'layout_tests', 'data')
+ def path_isdir(self, path):
+ # Used by test_expectations.py
+ #
+ # We assume that a path is a directory if we have any tests that
+ # whose prefix matches the path plus a directory modifier.
+ if path[-1] != '/':
+ path += '/'
+ return any([t.startswith(path) for t in self._tests.keys()])
+
+ def test_dirs(self):
+ return ['passes', 'failures']
+
def name(self):
return self._name
def options(self):
return self._options
- def skipped_layout_tests(self):
- return []
-
- def path_to_test_expectations_file(self):
- return self.path_from_webkit_base('WebKitTools', 'Scripts',
- 'webkitpy', 'layout_tests', 'data', 'platform', 'test',
- 'test_expectations.txt')
-
def _path_to_wdiff(self):
return None
@@ -93,9 +227,6 @@ class TestPort(base.Port):
def setup_test_run(self):
pass
- def show_results_html_file(self, filename):
- pass
-
def create_driver(self, image_path, options):
return TestDriver(self, image_path, options, executive=None)
@@ -116,9 +247,21 @@ class TestPort(base.Port):
Basically this string should contain the equivalent of a
test_expectations file. See test_expectations.py for more details."""
- expectations_path = self.path_to_test_expectations_file()
- with codecs.open(expectations_path, "r", "utf-8") as file:
- return file.read()
+ return """
+WONTFIX : failures/expected/checksum.html = IMAGE
+WONTFIX : failures/expected/crash.html = CRASH
+// This one actually passes because the checksums will match.
+WONTFIX : failures/expected/image.html = PASS
+WONTFIX : failures/expected/image_checksum.html = IMAGE
+WONTFIX : failures/expected/missing_check.html = MISSING PASS
+WONTFIX : failures/expected/missing_image.html = MISSING PASS
+WONTFIX : failures/expected/missing_text.html = MISSING PASS
+WONTFIX : failures/expected/text.html = TEXT
+WONTFIX : failures/expected/timeout.html = TIMEOUT
+WONTFIX SKIP : failures/expected/hang.html = TIMEOUT
+WONTFIX SKIP : failures/expected/keyboard.html = CRASH
+WONTFIX SKIP : failures/expected/exception.html = CRASH
+"""
def test_base_platform_names(self):
return ('mac', 'win')
@@ -150,68 +293,21 @@ class TestDriver(base.Driver):
return True
def run_test(self, uri, timeoutms, image_hash):
- basename = uri[(uri.rfind("/") + 1):uri.rfind(".html")]
-
- if 'error' in basename:
- error = basename + "_error\n"
- else:
- error = ''
- checksum = None
- # There are four currently supported types of tests: text, image,
- # image hash (checksum), and stderr output. The fake output
- # is the basename of the file + "-" plus the type of test output
- # (or a blank string for stderr).
- #
- # If 'image' or 'check' appears in the basename, we assume this is
- # simulating a pixel test.
- #
- # If 'failures' appears in the URI, then we assume this test should
- # fail. Which type of failures are determined by which strings appear
- # in the basename of the test. For failures that produce outputs,
- # we change the fake output to basename + "_failed-".
- #
- # The fact that each test produces (more or less) unique output data
- # will allow us to see if any results get crossed by the rest of the
- # program.
- if 'failures' in uri:
- if 'keyboard' in basename:
- raise KeyboardInterrupt
- if 'exception' in basename:
- raise ValueError('exception from ' + basename)
-
- crash = 'crash' in basename
- timeout = 'timeout' in basename or 'hang' in basename
- timeout = 'timeout' in basename
- if 'text' in basename:
- output = basename + '_failed-txt\n'
- else:
- output = basename + '-txt\n'
- if self._port.options().pixel_tests:
- if ('image' in basename or 'check' in basename):
- checksum = basename + "-checksum\n"
-
- if 'image' in basename:
- with open(self._image_path, "w") as f:
- f.write(basename + "_failed-png\n")
- elif 'check' in basename:
- with open(self._image_path, "w") as f:
- f.write(basename + "-png\n")
- if 'checksum' in basename:
- checksum = basename + "_failed-checksum\n"
-
- if 'hang' in basename:
- time.sleep((float(timeoutms) * 4) / 1000.0)
- else:
- crash = False
- timeout = False
- output = basename + '-txt\n'
- if self._options.pixel_tests and (
- 'image' in basename or 'check' in basename):
- checksum = basename + '-checksum\n'
- with open(self._image_path, "w") as f:
- f.write(basename + "-png")
-
- return (crash, timeout, checksum, output, error)
+ test_name = self._port.uri_to_test_name(uri)
+ test = self._port._tests[test_name]
+ if test.keyboard:
+ raise KeyboardInterrupt
+ if test.exception:
+ raise ValueError('exception from ' + test_name)
+ if test.hang:
+ time.sleep((float(timeoutms) * 4) / 1000.0)
+
+ if self._port.options().pixel_tests and test.actual_image:
+ with open(self._image_path, 'w') as file:
+ file.write(test.actual_image)
+
+ return (test.crash, test.timeout, test.actual_checksum,
+ test.actual_text, test.error)
def start(self):
pass
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_files.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/test_files.py
index 8f79505..3fa0fb3 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_files.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/test_files.py
@@ -27,11 +27,11 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""This module is used to find all of the layout test files used by Chromium
-(across all platforms). It exposes one public function - GatherTestFiles() -
+"""This module is used to find all of the layout test files used by
+run-webkit-tests. It exposes one public function - find() -
which takes an optional list of paths. If a list is passed in, the returned
list of test files is constrained to those found under the paths passed in,
-i.e. calling GatherTestFiles(["LayoutTests/fast"]) will only return files
+i.e. calling find(["LayoutTests/fast"]) will only return files
under that directory."""
import glob
@@ -51,12 +51,12 @@ _supported_file_extensions = set(['.html', '.shtml', '.xml', '.xhtml', '.xhtmlmp
_skipped_directories = set(['.svn', '_svn', 'resources', 'script-tests'])
-def gather_test_files(port, paths):
- """Generate a set of test files and return them.
+def find(port, paths):
+ """Finds the set of tests under port.layout_tests_dir().
Args:
- paths: a list of command line paths relative to the webkit/tests
- directory. glob patterns are ok.
+ paths: a list of command line paths relative to the layout_tests_dir()
+ to limit the search to. glob patterns are ok.
"""
gather_start_time = time.time()
paths_to_walk = set()
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/test_files_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/test_files_unittest.py
new file mode 100644
index 0000000..c37eb92
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/test_files_unittest.py
@@ -0,0 +1,68 @@
+# Copyright (C) 2010 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import os
+import unittest
+
+import base
+import test_files
+
+
+class TestFilesTest(unittest.TestCase):
+ def test_find_no_paths_specified(self):
+ port = base.Port()
+ layout_tests_dir = port.layout_tests_dir()
+ port.layout_tests_dir = lambda: os.path.join(layout_tests_dir,
+ 'fast', 'html')
+ tests = test_files.find(port, [])
+ self.assertNotEqual(tests, 0)
+
+ def test_find_one_test(self):
+ port = base.Port()
+ # This is just a test picked at random but known to exist.
+ tests = test_files.find(port, ['fast/html/keygen.html'])
+ self.assertEqual(len(tests), 1)
+
+ def test_find_glob(self):
+ port = base.Port()
+ tests = test_files.find(port, ['fast/html/key*'])
+ self.assertEqual(len(tests), 1)
+
+ def test_find_with_skipped_directories(self):
+ port = base.Port()
+ tests = port.tests('userscripts')
+ self.assertTrue('userscripts/resources/frame1.html' not in tests)
+
+ def test_find_with_skipped_directories_2(self):
+ port = base.Port()
+ tests = test_files.find(port, ['userscripts/resources'])
+ self.assertEqual(tests, set([]))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit.py
index 88c9bdf..ed19c09 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit.py
@@ -58,16 +58,16 @@ _log = logging.getLogger("webkitpy.layout_tests.port.webkit")
class WebKitPort(base.Port):
"""WebKit implementation of the Port class."""
- def __init__(self, port_name=None, options=None, **kwargs):
- base.Port.__init__(self, port_name, options, **kwargs)
+ 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
# build machines.
- if options and (not hasattr(options, "pixel_tests") or
- options.pixel_tests is None):
- options.pixel_tests = False
+ if self._options and (not hasattr(self._options, "pixel_tests") or
+ self._options.pixel_tests is None):
+ self._options.pixel_tests = False
def baseline_path(self):
return self._webkit_baseline_path(self._name)
@@ -84,10 +84,14 @@ class WebKitPort(base.Port):
return ''
def _build_driver(self):
- return not self._executive.run_command([
+ exit_code = self._executive.run_command([
self.script_path("build-dumprendertree"),
self.flag_from_configuration(self._options.configuration),
], return_exit_code=True)
+ if exit_code != 0:
+ _log.error("Failed to build DumpRenderTree")
+ return False
+ return True
def _check_driver(self):
driver_path = self._path_to_driver()
@@ -119,7 +123,7 @@ class WebKitPort(base.Port):
return False
return True
- def diff_image(self, expected_filename, actual_filename,
+ def diff_image(self, expected_contents, actual_contents,
diff_filename=None, tolerance=0.1):
"""Return True if the two files are different. Also write a delta
image of the two images into |diff_filename| if it is not None."""
@@ -128,31 +132,24 @@ class WebKitPort(base.Port):
# parameter, or make it go away and always use exact matches.
# Handle the case where the test didn't actually generate an image.
- actual_length = os.stat(actual_filename).st_size
- if actual_length == 0:
- if diff_filename:
- shutil.copyfile(actual_filename, expected_filename)
+ if not actual_contents:
return True
- sp = self._diff_image_request(expected_filename, actual_filename, tolerance)
- return self._diff_image_reply(sp, expected_filename, diff_filename)
+ sp = self._diff_image_request(expected_contents, actual_contents,
+ tolerance)
+ return self._diff_image_reply(sp, diff_filename)
- def _diff_image_request(self, expected_filename, actual_filename, tolerance):
+ def _diff_image_request(self, expected_contents, actual_contents, tolerance):
command = [self._path_to_image_diff(), '--tolerance', str(tolerance)]
sp = server_process.ServerProcess(self, 'ImageDiff', command)
- actual_length = os.stat(actual_filename).st_size
- with open(actual_filename) as file:
- actual_file = file.read()
- expected_length = os.stat(expected_filename).st_size
- with open(expected_filename) as file:
- expected_file = file.read()
sp.write('Content-Length: %d\n%sContent-Length: %d\n%s' %
- (actual_length, actual_file, expected_length, expected_file))
+ (len(actual_contents), actual_contents,
+ len(expected_contents), expected_contents))
return sp
- def _diff_image_reply(self, sp, expected_filename, diff_filename):
+ def _diff_image_reply(self, sp, diff_filename):
timeout = 2.0
deadline = time.time() + timeout
output = sp.read_line(timeout)
@@ -178,7 +175,7 @@ class WebKitPort(base.Port):
with open(diff_filename, 'w') as file:
file.write(output)
elif sp.timed_out:
- _log.error("ImageDiff timed out on %s" % expected_filename)
+ _log.error("ImageDiff timed out")
elif sp.crashed:
_log.error("ImageDiff crashed")
sp.stop()
@@ -193,11 +190,6 @@ class WebKitPort(base.Port):
# This port doesn't require any specific configuration.
pass
- def show_results_html_file(self, results_filename):
- uri = self.filename_to_uri(results_filename)
- # FIXME: We should open results in the version of WebKit we built.
- webbrowser.open(uri, new=1)
-
def create_driver(self, image_path, options):
return WebKitDriver(self, image_path, options,
executive=self._executive)
@@ -255,7 +247,7 @@ class WebKitPort(base.Port):
"MathMLElement": ["mathml"],
"GraphicsLayer": ["compositing"],
"WebCoreHas3DRendering": ["animations/3d", "transforms/3d"],
- "WebGLShader": ["fast/canvas/webgl"],
+ "WebGLShader": ["fast/canvas/webgl", "compositing/webgl", "http/tests/canvas/webgl"],
"WMLElement": ["http/tests/wml", "fast/wml", "wml"],
"parseWCSSInputProperty": ["fast/wcss"],
"isXHTMLMPDocument": ["fast/xhtmlmp"],
@@ -418,12 +410,17 @@ class WebKitDriver(base.Driver):
def _driver_args(self):
driver_args = []
+
if self._image_path:
driver_args.append('--pixel-tests')
- # These are used by the Chromium DRT port
if self._options.use_drt:
- driver_args.append('--test-shell')
+ if self._options.accelerated_compositing:
+ driver_args.append('--enable-accelerated-compositing')
+
+ if self._options.accelerated_2d_canvas:
+ driver_args.append('--enable-accelerated-2d-canvas')
+
return driver_args
def start(self):
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py
index fbfadc3..7b68310 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py
@@ -53,7 +53,7 @@ class WebKitPortTest(unittest.TestCase):
def test_skipped_directories_for_symbols(self):
supported_symbols = ["GraphicsLayer", "WebCoreHas3DRendering", "isXHTMLMPDocument", "fooSymbol"]
- expected_directories = set(["mathml", "fast/canvas/webgl", "http/tests/wml", "fast/wml", "wml", "fast/wcss"])
+ expected_directories = set(["mathml", "fast/canvas/webgl", "compositing/webgl", "http/tests/canvas/webgl", "http/tests/wml", "fast/wml", "wml", "fast/wcss"])
result_directories = set(TestWebKitPort(supported_symbols, None)._skipped_tests_for_unsupported_features())
self.assertEqual(result_directories, expected_directories)
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/win.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/win.py
index e05a69d..9e30155 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/win.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/win.py
@@ -39,10 +39,9 @@ _log = logging.getLogger("webkitpy.layout_tests.port.win")
class WinPort(WebKitPort):
"""WebKit Win implementation of the Port class."""
- def __init__(self, port_name=None, options=None):
- if port_name is None:
- port_name = 'win'
- WebKitPort.__init__(self, port_name, options)
+ def __init__(self, **kwargs):
+ kwargs.setdefault('port_name', 'win')
+ WebKitPort.__init__(self, **kwargs)
def baseline_search_path(self):
# Based on code from old-run-webkit-tests expectedDirectoryForTest()
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 3a9f923..e57ceb2 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py
@@ -55,9 +55,9 @@ import sys
import tempfile
import time
import urllib
-import webbrowser
import zipfile
+from webkitpy.common.system import user
from webkitpy.common.system.executive import run_command, ScriptError
import webkitpy.common.checkout.scm as scm
@@ -518,8 +518,15 @@ class Rebaseliner(object):
fallback_fullpath = os.path.normpath(
os.path.join(fallback_dir, fallback_file))
if fallback_fullpath.lower() != baseline_path.lower():
- if not self._diff_baselines(new_baseline,
- fallback_fullpath):
+ with codecs.open(new_baseline, "r",
+ None) as file_handle1:
+ new_output = file_handle1.read()
+ with codecs.open(fallback_fullpath, "r",
+ None) as file_handle2:
+ fallback_output = file_handle2.read()
+ is_image = baseline_path.lower().endswith('.png')
+ if not self._diff_baselines(new_output, fallback_output,
+ is_image):
_log.info(' Found same baseline at %s',
fallback_fullpath)
return True
@@ -528,31 +535,20 @@ class Rebaseliner(object):
return False
- def _diff_baselines(self, file1, file2):
+ def _diff_baselines(self, output1, output2, is_image):
"""Check whether two baselines are different.
Args:
- file1, file2: full paths of the baselines to compare.
+ output1, output2: contents of the baselines to compare.
Returns:
True if two files are different or have different extensions.
False otherwise.
"""
- ext1 = os.path.splitext(file1)[1].upper()
- ext2 = os.path.splitext(file2)[1].upper()
- if ext1 != ext2:
- _log.warn('Files to compare have different ext. '
- 'File1: %s; File2: %s', file1, file2)
- return True
-
- if ext1 == '.PNG':
- return self._port.diff_image(file1, file2)
+ if is_image:
+ return self._port.diff_image(output1, output2)
else:
- with codecs.open(file1, "r", "utf8") as file_handle1:
- output1 = file_handle1.read()
- with codecs.open(file2, "r", "utf8") as file_handle2:
- output2 = file_handle2.read()
return self._port.compare_text(output1, output2)
def _delete_baseline(self, filename):
@@ -593,7 +589,7 @@ class Rebaseliner(object):
# Or is new_expectations always a byte array?
with open(path, "w") as file:
file.write(new_expectations)
- self._scm.add(path)
+ # self._scm.add(path)
else:
_log.info('No test was rebaselined so nothing to remove.')
@@ -737,10 +733,7 @@ class HtmlGenerator(object):
"""Launch the rebaselining html in brwoser."""
_log.info('Launching html: "%s"', self._html_file)
-
- html_uri = self._target_port.filename_to_uri(self._html_file)
- webbrowser.open(html_uri, 1)
-
+ user.User().open_url(self._html_file)
_log.info('Html launched.')
def _generate_baseline_links(self, test_basename, suffix, platform):
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 dbb2b91..9ba3d6b 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
@@ -103,15 +103,20 @@ class TestRebaseliner(unittest.TestCase):
def test_diff_baselines_txt(self):
rebaseliner = self.make_rebaseliner()
- path = os.path.join(rebaseliner._port.layout_tests_dir(),
- "passes", "text-expected.txt")
- self.assertFalse(rebaseliner._diff_baselines(path, path))
+ output = rebaseliner._port.expected_text(
+ os.path.join(rebaseliner._port.layout_tests_dir(),
+ 'passes/text.html'))
+ self.assertFalse(rebaseliner._diff_baselines(output, output,
+ is_image=False))
def test_diff_baselines_png(self):
+ return
rebaseliner = self.make_rebaseliner()
- path = os.path.join(rebaseliner._port.layout_tests_dir(),
- "passes", "image-expected.png")
- self.assertFalse(rebaseliner._diff_baselines(path, path))
+ image = rebaseliner._port.expected_image(
+ os.path.join(rebaseliner._port.layout_tests_dir(),
+ 'passes/image.html'))
+ self.assertFalse(rebaseliner._diff_baselines(image, image,
+ is_image=True))
if __name__ == '__main__':
unittest.main()
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py b/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py
index 14d4f0e..e9c6d2c 100755
--- a/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py
@@ -68,12 +68,12 @@ from layout_package import json_layout_results_generator
from layout_package import printing
from layout_package import test_expectations
from layout_package import test_failures
-from layout_package import test_files
from layout_package import test_results_uploader
from test_types import image_diff
from test_types import text_diff
from test_types import test_type_base
+from webkitpy.common.system import user
from webkitpy.thirdparty import simplejson
import port
@@ -96,28 +96,21 @@ class TestInfo:
timeout: Timeout for running the test in TestShell.
"""
self.filename = filename
+ self._port = port
self.uri = port.filename_to_uri(filename)
self.timeout = timeout
- # FIXME: Confusing that the file is .checksum and we call it "hash"
- self._expected_hash_path = port.expected_filename(filename, '.checksum')
- self._have_read_expected_hash = False
- self._image_hash = None
-
- def _read_image_hash(self):
- if not os.path.exists(self._expected_hash_path):
- return None
-
- with codecs.open(self._expected_hash_path, "r", "ascii") as hash_file:
- return hash_file.read()
+ self._image_checksum = -1
def image_hash(self):
# Read the image_hash lazily to reduce startup time.
# This class is accessed across threads, but only one thread should
# ever be dealing with any given TestInfo so no locking is needed.
- if not self._have_read_expected_hash:
- self._have_read_expected_hash = True
- self._image_hash = self._read_image_hash()
- return self._image_hash
+ #
+ # Note that we use -1 to indicate that we haven't read the value,
+ # because expected_checksum() returns a string or None.
+ if self._image_checksum == -1:
+ self._image_checksum = self._port.expected_checksum(self.filename)
+ return self._image_checksum
class ResultSummary(object):
@@ -292,7 +285,7 @@ class TestRunner:
paths += last_unexpected_results
if self._options.test_list:
paths += read_test_files(self._options.test_list)
- self._test_files = test_files.gather_test_files(self._port, paths)
+ self._test_files = self._port.tests(paths)
def lint(self):
# Creating the expecations for each platform/configuration pair does
@@ -321,7 +314,7 @@ class TestRunner:
self._expectations = test_expectations.TestExpectations(
self._port, test_files, expectations_str, test_platform_name,
is_debug_mode, self._options.lint_test_files,
- tests_are_present=True, overrides=overrides_str)
+ overrides=overrides_str)
return self._expectations
except SyntaxError, err:
if self._options.lint_test_files:
@@ -865,7 +858,7 @@ class TestRunner:
self._printer.print_update("Clobbering old results in %s" %
self._options.results_directory)
layout_tests_dir = self._port.layout_tests_dir()
- possible_dirs = os.listdir(layout_tests_dir)
+ possible_dirs = self._port.test_dirs()
for dirname in possible_dirs:
if os.path.isdir(os.path.join(layout_tests_dir, dirname)):
shutil.rmtree(os.path.join(self._options.results_directory,
@@ -1408,6 +1401,7 @@ def run(port, options, args, regular_output=sys.stderr,
printer.print_update("Checking build ...")
if not port.check_build(test_runner.needs_http()):
+ _log.error("Build check failed")
return -1
result_summary = test_runner.set_up_run()
@@ -1515,6 +1509,20 @@ def parse_args(args=None):
optparse.make_option("--use-drt", action="store_true",
default=False,
help="Use DumpRenderTree instead of test_shell"),
+ optparse.make_option("--accelerated-compositing",
+ action="store_true",
+ help="Use hardware-accelated compositing for rendering"),
+ optparse.make_option("--no-accelerated-compositing",
+ action="store_false",
+ dest="accelerated_compositing",
+ help="Don't use hardware-accelerated compositing for rendering"),
+ optparse.make_option("--accelerated-2d-canvas",
+ action="store_true",
+ help="Use hardware-accelerated 2D Canvas calls"),
+ optparse.make_option("--no-accelerated-2d-canvas",
+ action="store_false",
+ dest="accelerated_2d_canvas",
+ help="Don't use hardware-accelerated 2D Canvas calls"),
]
# Missing Mac-specific old-run-webkit-tests options:
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 aa96962..6fe99d6 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py
@@ -42,6 +42,7 @@ import unittest
from webkitpy.common import array_stream
from webkitpy.common.system import outputcapture
+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
@@ -49,6 +50,14 @@ from webkitpy.layout_tests.layout_package import dump_render_tree_thread
from webkitpy.thirdparty.mock import Mock
+class MockUser():
+ def __init__(self):
+ self.url = None
+
+ def open_url(self, url):
+ self.url = url
+
+
def passing_run(args=[], port_obj=None, record_results=False,
tests_included=False):
new_args = ['--print', 'nothing']
@@ -65,7 +74,8 @@ def passing_run(args=[], port_obj=None, record_results=False,
'failures/expected/*'])
options, parsed_args = run_webkit_tests.parse_args(new_args)
if port_obj is None:
- port_obj = port.get(options.platform, options)
+ 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
@@ -77,20 +87,31 @@ def logging_run(args=[], tests_included=False):
new_args.extend(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)
- port_obj = port.get(options.platform, options)
+ user = MockUser()
+ 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,
buildbot_output=buildbot_output,
regular_output=regular_output)
- return (res, buildbot_output, regular_output)
+ return (res, buildbot_output, regular_output, user)
class MainTest(unittest.TestCase):
+ def test_accelerated_compositing(self):
+ # This just tests that we recognize the command line args
+ self.assertTrue(passing_run(['--accelerated-compositing']))
+ self.assertTrue(passing_run(['--no-accelerated-compositing']))
+
+ def test_accelerated_2d_canvas(self):
+ # This just tests that we recognize the command line args
+ self.assertTrue(passing_run(['--accelerated-2d-canvas']))
+ self.assertTrue(passing_run(['--no-accelerated-2d-canvas']))
+
def test_basic(self):
self.assertTrue(passing_run())
@@ -99,13 +120,13 @@ class MainTest(unittest.TestCase):
self.assertTrue(passing_run(['--batch-size', '2']))
def test_child_process_1(self):
- (res, buildbot_output, regular_output) = logging_run(
+ (res, buildbot_output, regular_output, user) = logging_run(
['--print', 'config', '--child-processes', '1'])
self.assertTrue('Running one DumpRenderTree\n'
in regular_output.get())
def test_child_processes_2(self):
- (res, buildbot_output, regular_output) = logging_run(
+ (res, buildbot_output, regular_output, user) = logging_run(
['--print', 'config', '--child-processes', '2'])
self.assertTrue('Running 2 DumpRenderTrees in parallel\n'
in regular_output.get())
@@ -119,15 +140,15 @@ class MainTest(unittest.TestCase):
self.assertTrue(passing_run(['--full-results-html']))
def test_help_printing(self):
- res, out, err = logging_run(['--help-printing'])
+ res, out, err, user = logging_run(['--help-printing'])
self.assertEqual(res, 0)
self.assertTrue(out.empty())
self.assertFalse(err.empty())
def test_hung_thread(self):
- res, out, err = logging_run(['--run-singly', '--time-out-ms=50',
- 'failures/expected/hang.html'],
- tests_included=True)
+ res, out, err, user = logging_run(['--run-singly', '--time-out-ms=50',
+ 'failures/expected/hang.html'],
+ tests_included=True)
self.assertEqual(res, 0)
self.assertFalse(out.empty())
self.assertFalse(err.empty())
@@ -140,26 +161,27 @@ class MainTest(unittest.TestCase):
def test_last_results(self):
passing_run(['--clobber-old-results'], record_results=True)
- (res, buildbot_output, regular_output) = logging_run(
+ (res, buildbot_output, regular_output, user) = logging_run(
['--print-last-failures'])
self.assertEqual(regular_output.get(), ['\n\n'])
self.assertEqual(buildbot_output.get(), [])
def test_lint_test_files(self):
# FIXME: add errors?
- res, out, err = logging_run(['--lint-test-files'], tests_included=True)
+ res, out, err, user = logging_run(['--lint-test-files'],
+ tests_included=True)
self.assertEqual(res, 0)
self.assertTrue(out.empty())
self.assertTrue(any(['lint succeeded' in msg for msg in err.get()]))
def test_no_tests_found(self):
- res, out, err = logging_run(['resources'], tests_included=True)
+ res, out, err, user = logging_run(['resources'], tests_included=True)
self.assertEqual(res, -1)
self.assertTrue(out.empty())
self.assertTrue('No tests to run.\n' in err.get())
def test_no_tests_found_2(self):
- res, out, err = logging_run(['foo'], tests_included=True)
+ res, out, err, user = logging_run(['foo'], tests_included=True)
self.assertEqual(res, -1)
self.assertTrue(out.empty())
self.assertTrue('No tests to run.\n' in err.get())
@@ -196,17 +218,19 @@ class MainTest(unittest.TestCase):
self.assertTrue(passing_run(['--test-list=%s' % filename],
tests_included=True))
os.remove(filename)
- res, out, err = logging_run(['--test-list=%s' % filename],
- tests_included=True)
+ res, out, err, user = logging_run(['--test-list=%s' % filename],
+ tests_included=True)
self.assertEqual(res, -1)
self.assertFalse(err.empty())
def test_unexpected_failures(self):
# Run tests including the unexpected failures.
- res, out, err = logging_run(tests_included=True)
+ self._url_opened = None
+ res, out, err, user = logging_run(tests_included=True)
self.assertEqual(res, 1)
self.assertFalse(out.empty())
self.assertFalse(err.empty())
+ self.assertEqual(user.url, '/tmp/layout-test-results/results.html')
def _mocked_open(original_open, file_list):
@@ -269,6 +293,7 @@ class RebaselineTest(unittest.TestCase):
finally:
codecs.open = original_open
+
class TestRunnerTest(unittest.TestCase):
def test_results_html(self):
mock_port = Mock()
@@ -306,11 +331,8 @@ class DryrunTest(unittest.TestCase):
'fast/html']))
def test_test(self):
- res, out, err = logging_run(['--platform', 'dryrun-test',
- '--pixel-tests'])
- self.assertEqual(res, 2)
- self.assertFalse(out.empty())
- self.assertFalse(err.empty())
+ self.assertTrue(passing_run(['--platform', 'dryrun-test',
+ '--pixel-tests']))
class TestThread(dump_render_tree_thread.WatchableThread):
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/test_types/image_diff.py b/WebKitTools/Scripts/webkitpy/layout_tests/test_types/image_diff.py
index 879646c..1ad0fe6 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/test_types/image_diff.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/test_types/image_diff.py
@@ -103,7 +103,11 @@ class ImageDiff(test_type_base.TestTypeBase):
expected_filename = self.output_filename(filename,
self.FILENAME_SUFFIX_EXPECTED + '.png')
- result = port.diff_image(expected_filename, actual_filename,
+ expected_image = port.expected_image(filename)
+ with codecs.open(actual_filename, 'r', None) as file:
+ actual_image = file.read()
+
+ result = port.diff_image(expected_image, actual_image,
diff_filename)
return result
@@ -124,19 +128,12 @@ class ImageDiff(test_type_base.TestTypeBase):
return failures
# Compare hashes.
- expected_hash_file = self._port.expected_filename(filename,
- '.checksum')
- expected_png_file = self._port.expected_filename(filename, '.png')
-
- # FIXME: We repeat this pattern often, we should share code.
- expected_hash = ''
- if os.path.exists(expected_hash_file):
- with codecs.open(expected_hash_file, "r", "ascii") as file:
- expected_hash = file.read()
+ expected_hash = self._port.expected_checksum(filename)
+ expected_png = self._port.expected_image(filename)
- if not os.path.isfile(expected_png_file):
+ if not expected_png:
# Report a missing expected PNG file.
- self.write_output_files(port, filename, '.checksum',
+ self.write_output_files(filename, '.checksum',
test_args.hash, expected_hash,
encoding="ascii",
print_text_diffs=False)
@@ -147,17 +144,21 @@ class ImageDiff(test_type_base.TestTypeBase):
# Hash matched (no diff needed, okay to return).
return failures
- self.write_output_files(port, filename, '.checksum',
+ self.write_output_files(filename, '.checksum',
test_args.hash, expected_hash,
encoding="ascii",
print_text_diffs=False)
+
+ # FIXME: combine next two lines
self._copy_output_png(filename, test_args.png_path, '-actual.png')
- self._copy_output_png(filename, expected_png_file, '-expected.png')
+ self.write_output_files(filename, '.png', output=None,
+ expected=expected_png,
+ encoding=None, print_text_diffs=False)
# Even though we only use the result in one codepath below but we
# still need to call CreateImageDiff for other codepaths.
images_are_different = self._create_image_diff(port, filename, configuration)
- if expected_hash == '':
+ if not expected_hash:
failures.append(test_failures.FailureMissingImageHash())
elif test_args.hash != expected_hash:
if images_are_different:
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/test_types/test_type_base.py b/WebKitTools/Scripts/webkitpy/layout_tests/test_types/test_type_base.py
index 753dbee..3a6e92b 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/test_types/test_type_base.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/test_types/test_type_base.py
@@ -120,7 +120,7 @@ class TestTypeBase(object):
output_path = self._port.expected_filename(filename, modifier)
_log.debug('resetting baseline result "%s"' % output_path)
- self._write_into_file_at_path(output_path, data, encoding)
+ self._port.update_baseline(output_path, data, encoding)
def output_filename(self, filename, modifier):
"""Returns a filename inside the output dir that contains modifier.
@@ -164,7 +164,7 @@ class TestTypeBase(object):
with codecs.open(file_path, "w", encoding=encoding) as file:
file.write(contents)
- def write_output_files(self, port, filename, file_type,
+ def write_output_files(self, filename, file_type,
output, expected, encoding,
print_text_diffs=False):
"""Writes the test output, the expected output and optionally the diff
@@ -201,16 +201,16 @@ class TestTypeBase(object):
# Note: We pass encoding=None for all diff writes, as we treat diff
# output as binary. Diff output may contain multiple files in
# conflicting encodings.
- diff = port.diff_text(expected, output, expected_filename, actual_filename)
+ diff = self._port.diff_text(expected, output, expected_filename, actual_filename)
diff_filename = self.output_filename(filename, self.FILENAME_SUFFIX_DIFF + file_type)
self._write_into_file_at_path(diff_filename, diff, encoding=None)
# Shell out to wdiff to get colored inline diffs.
- wdiff = port.wdiff_text(expected_filename, actual_filename)
+ wdiff = self._port.wdiff_text(expected_filename, actual_filename)
wdiff_filename = self.output_filename(filename, self.FILENAME_SUFFIX_WDIFF)
self._write_into_file_at_path(wdiff_filename, wdiff, encoding=None)
# Use WebKit's PrettyPatch.rb to get an HTML diff.
- pretty_patch = port.pretty_patch_text(diff_filename)
+ pretty_patch = self._port.pretty_patch_text(diff_filename)
pretty_patch_filename = self.output_filename(filename, self.FILENAME_SUFFIX_PRETTY_PATCH)
self._write_into_file_at_path(pretty_patch_filename, pretty_patch, encoding=None)
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/test_types/text_diff.py b/WebKitTools/Scripts/webkitpy/layout_tests/test_types/text_diff.py
index 50a9995..b1f621e 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/test_types/text_diff.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/test_types/text_diff.py
@@ -59,24 +59,7 @@ class TestTextDiff(test_type_base.TestTypeBase):
"""Given the filename of the test, read the expected output from a file
and normalize the text. Returns a string with the expected text, or ''
if the expected output file was not found."""
- # Read the port-specific expected text.
- expected_filename = self._port.expected_filename(filename, '.txt')
- return self._get_normalized_text(expected_filename)
-
- def _get_normalized_text(self, filename):
- # FIXME: We repeat this pattern often, we should share code.
- if not os.path.exists(filename):
- return ''
-
- # NOTE: -expected.txt files are ALWAYS utf-8. However,
- # we do not decode the output from DRT, so we should not
- # decode the -expected.txt values either to allow comparisons.
- with codecs.open(filename, "r", encoding=None) as file:
- text = file.read()
- # We could assert that the text is valid utf-8.
-
- # Normalize line endings
- return text.strip("\r\n").replace("\r\n", "\n") + "\n"
+ return self._port.expected_text(filename)
def compare_output(self, port, filename, output, test_args, configuration):
"""Implementation of CompareOutput that checks the output text against
@@ -99,7 +82,7 @@ class TestTextDiff(test_type_base.TestTypeBase):
# Write output files for new tests, too.
if port.compare_text(output, expected):
# Text doesn't match, write output files.
- self.write_output_files(port, filename, ".txt", output,
+ self.write_output_files(filename, ".txt", output,
expected, encoding=None,
print_text_diffs=True)
diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations.py b/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations.py
index ddc3983..d2d67f3 100644
--- a/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations.py
+++ b/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations.py
@@ -93,8 +93,7 @@ class TestExpectationsChecker(object):
expectations = test_expectations.TestExpectationsFile(
port=self._port_obj, expectations=expectations_str, full_test_list=tests,
test_platform_name=self._port_to_check, is_debug_mode=False,
- is_lint_mode=True, suppress_errors=False, tests_are_present=True,
- overrides=overrides)
+ is_lint_mode=True, suppress_errors=False, overrides=overrides)
except SyntaxError, error:
errors = str(error).splitlines()
diff --git a/WebKitTools/Scripts/webkitpy/test/main.py b/WebKitTools/Scripts/webkitpy/test/main.py
index daf255f..9351768 100644
--- a/WebKitTools/Scripts/webkitpy/test/main.py
+++ b/WebKitTools/Scripts/webkitpy/test/main.py
@@ -78,7 +78,7 @@ class Tester(object):
return modules
- def run_tests(self, sys_argv):
+ def run_tests(self, sys_argv, external_package_paths=None):
"""Run the unit tests in all *_unittest.py modules in webkitpy.
This method excludes "webkitpy.common.checkout.scm_unittest" unless
@@ -88,6 +88,11 @@ class Tester(object):
sys_argv: A reference to sys.argv.
"""
+ if external_package_paths is None:
+ external_package_paths = []
+ else:
+ sys.path.extend(set(os.path.dirname(path) for path in external_package_paths))
+
if len(sys_argv) > 1 and not sys_argv[-1].startswith("-"):
# Then explicit modules or test names were provided, which
# the unittest module is equipped to handle.
@@ -97,9 +102,10 @@ class Tester(object):
# Otherwise, auto-detect all unit tests.
webkitpy_dir = os.path.dirname(webkitpy.__file__)
- unittest_paths = self._find_unittest_files(webkitpy_dir)
- modules = self._modules_from_paths(webkitpy_dir, unittest_paths)
+ modules = []
+ for path in [webkitpy_dir] + external_package_paths:
+ modules.extend(self._modules_from_paths(path, self._find_unittest_files(path)))
modules.sort()
# This is a sanity check to ensure that the unit-test discovery
diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/commitqueuetask.py b/WebKitTools/Scripts/webkitpy/tool/bot/commitqueuetask.py
new file mode 100644
index 0000000..a347972
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/tool/bot/commitqueuetask.py
@@ -0,0 +1,158 @@
+# 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.
+
+from webkitpy.common.system.executive import ScriptError
+
+
+class CommitQueueTask(object):
+ def __init__(self, tool, commit_queue, patch):
+ self._tool = tool
+ self._commit_queue = commit_queue
+ self._patch = patch
+ self._script_error = None
+
+ def _validate(self):
+ # Bugs might get closed, or patches might be obsoleted or r-'d while the
+ # commit-queue is processing.
+ self._patch = self._tool.bugs.fetch_attachment(self._patch.id())
+ if self._patch.is_obsolete():
+ return False
+ if self._patch.bug().is_closed():
+ return False
+ if not self._patch.committer():
+ return False
+ # Reviewer is not required. Missing reviewers will be caught during
+ # the ChangeLog check during landing.
+ return True
+
+ def _run_command(self, command, success_message, failure_message):
+ try:
+ self._commit_queue.run_webkit_patch(command)
+ self._commit_queue.command_passed(success_message, patch=self._patch)
+ return True
+ except ScriptError, e:
+ self._script_error = e
+ self.failure_status_id = self._commit_queue.command_failed(failure_message, script_error=self._script_error, patch=self._patch)
+ return False
+
+ def _apply(self):
+ return self._run_command([
+ "apply-attachment",
+ "--force-clean",
+ "--non-interactive",
+ "--quiet",
+ self._patch.id(),
+ ],
+ "Applied patch",
+ "Patch does not apply")
+
+ def _build(self):
+ return self._run_command([
+ "build",
+ "--no-clean",
+ "--no-update",
+ "--build",
+ "--build-style=both",
+ "--quiet",
+ ],
+ "Built patch",
+ "Patch does not build")
+
+ def _build_without_patch(self):
+ return self._run_command([
+ "build",
+ "--force-clean",
+ "--no-update",
+ "--build",
+ "--build-style=both",
+ "--quiet",
+ ],
+ "Able to build without patch",
+ "Unable to build without patch")
+
+ def _test(self):
+ return self._run_command([
+ "build-and-test",
+ "--no-clean",
+ "--no-update",
+ # Notice that we don't pass --build, which means we won't build!
+ "--test",
+ "--quiet",
+ "--non-interactive",
+ ],
+ "Passed tests",
+ "Patch does not pass tests")
+
+ def _build_and_test_without_patch(self):
+ return self._run_command([
+ "build-and-test",
+ "--force-clean",
+ "--no-update",
+ "--build",
+ "--test",
+ "--quiet",
+ "--non-interactive",
+ ],
+ "Able to pass tests without patch",
+ "Unable to pass tests without patch (tree is red?)")
+
+ def _land(self):
+ return self._run_command([
+ "land-attachment",
+ "--force-clean",
+ "--ignore-builders",
+ "--quiet",
+ "--non-interactive",
+ "--parent-command=commit-queue",
+ self._patch.id(),
+ ],
+ "Landed patch",
+ "Unable to land patch")
+
+ def run(self):
+ if not self._validate():
+ return False
+ if not self._apply():
+ raise self._script_error
+ if not self._build():
+ if not self._build_without_patch():
+ return False
+ raise self._script_error
+ if not self._patch.is_rollout():
+ if not self._test():
+ if not self._test():
+ if not self._build_and_test_without_patch():
+ return False
+ raise self._script_error
+ # Make sure the patch is still valid before landing (e.g., make sure
+ # no one has set commit-queue- since we started working on the patch.)
+ if not self._validate():
+ return False
+ if not self._land():
+ raise self._script_error
+ return True
diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/commitqueuetask_unittest.py b/WebKitTools/Scripts/webkitpy/tool/bot/commitqueuetask_unittest.py
new file mode 100644
index 0000000..8b46146
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/tool/bot/commitqueuetask_unittest.py
@@ -0,0 +1,194 @@
+# 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.
+
+from datetime import datetime
+import unittest
+
+from webkitpy.common.system.deprecated_logging import error, log
+from webkitpy.common.system.outputcapture import OutputCapture
+from webkitpy.thirdparty.mock import Mock
+from webkitpy.tool.bot.commitqueuetask import *
+from webkitpy.tool.mocktool import MockTool
+
+
+class MockCommitQueue:
+ def __init__(self, error_plan):
+ self._error_plan = error_plan
+
+ def run_webkit_patch(self, command):
+ log("run_webkit_patch: %s" % command)
+ if self._error_plan:
+ error = self._error_plan.pop(0)
+ if error:
+ raise error
+
+ def command_passed(self, success_message, patch):
+ log("command_passed: success_message='%s' patch='%s'" % (
+ success_message, patch.id()))
+
+ def command_failed(self, failure_message, script_error, patch):
+ log("command_failed: failure_message='%s' script_error='%s' patch='%s'" % (
+ failure_message, script_error, patch.id()))
+ return 3947
+
+
+class CommitQueueTaskTest(unittest.TestCase):
+ def _run_through_task(self, commit_queue, expected_stderr, expected_exception=None):
+ tool = MockTool(log_executive=True)
+ patch = tool.bugs.fetch_attachment(197)
+ task = CommitQueueTask(tool, commit_queue, patch)
+ OutputCapture().assert_outputs(self, task.run, expected_stderr=expected_stderr, expected_exception=expected_exception)
+
+ def test_success_case(self):
+ commit_queue = MockCommitQueue([])
+ expected_stderr = """run_webkit_patch: ['apply-attachment', '--force-clean', '--non-interactive', '--quiet', 197]
+command_passed: success_message='Applied patch' patch='197'
+run_webkit_patch: ['build', '--no-clean', '--no-update', '--build', '--build-style=both', '--quiet']
+command_passed: success_message='Built patch' patch='197'
+run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--quiet', '--non-interactive']
+command_passed: success_message='Passed tests' patch='197'
+run_webkit_patch: ['land-attachment', '--force-clean', '--ignore-builders', '--quiet', '--non-interactive', '--parent-command=commit-queue', 197]
+command_passed: success_message='Landed patch' patch='197'
+"""
+ self._run_through_task(commit_queue, expected_stderr)
+
+ def test_apply_failure(self):
+ commit_queue = MockCommitQueue([
+ ScriptError("MOCK apply failure"),
+ ])
+ expected_stderr = """run_webkit_patch: ['apply-attachment', '--force-clean', '--non-interactive', '--quiet', 197]
+command_failed: failure_message='Patch does not apply' script_error='MOCK apply failure' patch='197'
+"""
+ self._run_through_task(commit_queue, expected_stderr, ScriptError)
+
+ def test_build_failure(self):
+ commit_queue = MockCommitQueue([
+ None,
+ ScriptError("MOCK build failure"),
+ ])
+ expected_stderr = """run_webkit_patch: ['apply-attachment', '--force-clean', '--non-interactive', '--quiet', 197]
+command_passed: success_message='Applied patch' patch='197'
+run_webkit_patch: ['build', '--no-clean', '--no-update', '--build', '--build-style=both', '--quiet']
+command_failed: failure_message='Patch does not build' script_error='MOCK build failure' patch='197'
+run_webkit_patch: ['build', '--force-clean', '--no-update', '--build', '--build-style=both', '--quiet']
+command_passed: success_message='Able to build without patch' patch='197'
+"""
+ self._run_through_task(commit_queue, expected_stderr, ScriptError)
+
+ def test_red_build_failure(self):
+ commit_queue = MockCommitQueue([
+ None,
+ ScriptError("MOCK build failure"),
+ ScriptError("MOCK clean build failure"),
+ ])
+ expected_stderr = """run_webkit_patch: ['apply-attachment', '--force-clean', '--non-interactive', '--quiet', 197]
+command_passed: success_message='Applied patch' patch='197'
+run_webkit_patch: ['build', '--no-clean', '--no-update', '--build', '--build-style=both', '--quiet']
+command_failed: failure_message='Patch does not build' script_error='MOCK build failure' patch='197'
+run_webkit_patch: ['build', '--force-clean', '--no-update', '--build', '--build-style=both', '--quiet']
+command_failed: failure_message='Unable to build without patch' script_error='MOCK clean build failure' patch='197'
+"""
+ self._run_through_task(commit_queue, expected_stderr)
+
+ def test_flaky_test_failure(self):
+ commit_queue = MockCommitQueue([
+ None,
+ None,
+ ScriptError("MOCK tests failure"),
+ ])
+ expected_stderr = """run_webkit_patch: ['apply-attachment', '--force-clean', '--non-interactive', '--quiet', 197]
+command_passed: success_message='Applied patch' patch='197'
+run_webkit_patch: ['build', '--no-clean', '--no-update', '--build', '--build-style=both', '--quiet']
+command_passed: success_message='Built patch' patch='197'
+run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--quiet', '--non-interactive']
+command_failed: failure_message='Patch does not pass tests' script_error='MOCK tests failure' patch='197'
+run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--quiet', '--non-interactive']
+command_passed: success_message='Passed tests' patch='197'
+run_webkit_patch: ['land-attachment', '--force-clean', '--ignore-builders', '--quiet', '--non-interactive', '--parent-command=commit-queue', 197]
+command_passed: success_message='Landed patch' patch='197'
+"""
+ self._run_through_task(commit_queue, expected_stderr)
+
+ def test_test_failure(self):
+ commit_queue = MockCommitQueue([
+ None,
+ None,
+ ScriptError("MOCK test failure"),
+ ScriptError("MOCK test failure again"),
+ ])
+ expected_stderr = """run_webkit_patch: ['apply-attachment', '--force-clean', '--non-interactive', '--quiet', 197]
+command_passed: success_message='Applied patch' patch='197'
+run_webkit_patch: ['build', '--no-clean', '--no-update', '--build', '--build-style=both', '--quiet']
+command_passed: success_message='Built patch' patch='197'
+run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--quiet', '--non-interactive']
+command_failed: failure_message='Patch does not pass tests' script_error='MOCK test failure' patch='197'
+run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--quiet', '--non-interactive']
+command_failed: failure_message='Patch does not pass tests' script_error='MOCK test failure again' patch='197'
+run_webkit_patch: ['build-and-test', '--force-clean', '--no-update', '--build', '--test', '--quiet', '--non-interactive']
+command_passed: success_message='Able to pass tests without patch' patch='197'
+"""
+ self._run_through_task(commit_queue, expected_stderr, ScriptError)
+
+ def test_red_test_failure(self):
+ commit_queue = MockCommitQueue([
+ None,
+ None,
+ ScriptError("MOCK test failure"),
+ ScriptError("MOCK test failure again"),
+ ScriptError("MOCK clean test failure"),
+ ])
+ expected_stderr = """run_webkit_patch: ['apply-attachment', '--force-clean', '--non-interactive', '--quiet', 197]
+command_passed: success_message='Applied patch' patch='197'
+run_webkit_patch: ['build', '--no-clean', '--no-update', '--build', '--build-style=both', '--quiet']
+command_passed: success_message='Built patch' patch='197'
+run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--quiet', '--non-interactive']
+command_failed: failure_message='Patch does not pass tests' script_error='MOCK test failure' patch='197'
+run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--quiet', '--non-interactive']
+command_failed: failure_message='Patch does not pass tests' script_error='MOCK test failure again' patch='197'
+run_webkit_patch: ['build-and-test', '--force-clean', '--no-update', '--build', '--test', '--quiet', '--non-interactive']
+command_failed: failure_message='Unable to pass tests without patch (tree is red?)' script_error='MOCK clean test failure' patch='197'
+"""
+ self._run_through_task(commit_queue, expected_stderr)
+
+ def test_land_failure(self):
+ commit_queue = MockCommitQueue([
+ None,
+ None,
+ None,
+ ScriptError("MOCK land failure"),
+ ])
+ expected_stderr = """run_webkit_patch: ['apply-attachment', '--force-clean', '--non-interactive', '--quiet', 197]
+command_passed: success_message='Applied patch' patch='197'
+run_webkit_patch: ['build', '--no-clean', '--no-update', '--build', '--build-style=both', '--quiet']
+command_passed: success_message='Built patch' patch='197'
+run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--quiet', '--non-interactive']
+command_passed: success_message='Passed tests' patch='197'
+run_webkit_patch: ['land-attachment', '--force-clean', '--ignore-builders', '--quiet', '--non-interactive', '--parent-command=commit-queue', 197]
+command_failed: failure_message='Unable to land patch' script_error='MOCK land failure' patch='197'
+"""
+ self._run_through_task(commit_queue, expected_stderr, ScriptError)
diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/feeders.py b/WebKitTools/Scripts/webkitpy/tool/bot/feeders.py
new file mode 100644
index 0000000..15eaaf3
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/tool/bot/feeders.py
@@ -0,0 +1,73 @@
+# 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.
+
+from webkitpy.common.system.deprecated_logging import log
+from webkitpy.common.net.bugzilla import CommitterValidator
+
+
+class AbstractFeeder(object):
+ def __init__(self, tool):
+ self._tool = tool
+
+ def feed(tool):
+ raise NotImplementedError, "subclasses must implement"
+
+ def update_work_items(self, item_ids):
+ self._tool.status_server.update_work_items(self.queue_name, item_ids)
+ log("Feeding %s items %s" % (self.queue_name, item_ids))
+
+
+class CommitQueueFeeder(AbstractFeeder):
+ queue_name = "commit-queue"
+
+ def __init__(self, tool):
+ AbstractFeeder.__init__(self, tool)
+ self.committer_validator = CommitterValidator(self._tool.bugs)
+
+ def feed(self):
+ patches = self._validate_patches()
+ patches = sorted(patches, self._patch_cmp)
+ patch_ids = [patch.id() for patch in patches]
+ self.update_work_items(patch_ids)
+
+ def _patches_for_bug(self, bug_id):
+ return self._tool.bugs.fetch_bug(bug_id).commit_queued_patches(include_invalid=True)
+
+ def _validate_patches(self):
+ # Not using BugzillaQueries.fetch_patches_from_commit_queue() so we can reject patches with invalid committers/reviewers.
+ bug_ids = self._tool.bugs.queries.fetch_bug_ids_from_commit_queue()
+ all_patches = sum([self._patches_for_bug(bug_id) for bug_id in bug_ids], [])
+ return self.committer_validator.patches_after_rejecting_invalid_commiters_and_reviewers(all_patches)
+
+ def _patch_cmp(self, a, b):
+ # Sort first by is_rollout, then by attach_date.
+ # Reversing the order so that is_rollout is first.
+ rollout_cmp = cmp(b.is_rollout(), a.is_rollout())
+ if rollout_cmp != 0:
+ return rollout_cmp
+ return cmp(a.attach_date(), b.attach_date())
diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/feeders_unittest.py b/WebKitTools/Scripts/webkitpy/tool/bot/feeders_unittest.py
new file mode 100644
index 0000000..5ce00b4
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/tool/bot/feeders_unittest.py
@@ -0,0 +1,70 @@
+# Copyright (c) 2010 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from datetime import datetime
+import unittest
+
+from webkitpy.common.system.outputcapture import OutputCapture
+from webkitpy.thirdparty.mock import Mock
+from webkitpy.tool.bot.feeders import *
+from webkitpy.tool.mocktool import MockTool
+
+
+class FeedersTest(unittest.TestCase):
+ def test_commit_queue_feeder(self):
+ feeder = CommitQueueFeeder(MockTool())
+ expected_stderr = u"""Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com)
+Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com)
+MOCK setting flag 'commit-queue' to '-' on attachment '128' with comment 'Rejecting patch 128 from commit-queue.' and additional comment 'non-committer@example.com does not have committer permissions according to http://trac.webkit.org/browser/trunk/WebKitTools/Scripts/webkitpy/common/config/committers.py.
+
+- If you do not have committer rights please read http://webkit.org/coding/contributing.html for instructions on how to use bugzilla flags.
+
+- If you have committer rights please correct the error in WebKitTools/Scripts/webkitpy/common/config/committers.py by adding yourself to the file (no review needed). The commit-queue restarts itself every 2 hours. After restart the commit-queue will correctly respect your committer rights.'
+MOCK: update_work_items: commit-queue [106, 197]
+Feeding commit-queue items [106, 197]
+"""
+ OutputCapture().assert_outputs(self, feeder.feed, expected_stderr=expected_stderr)
+
+ def _mock_attachment(self, is_rollout, attach_date):
+ attachment = Mock()
+ attachment.is_rollout = lambda: is_rollout
+ attachment.attach_date = lambda: attach_date
+ return attachment
+
+ def test_patch_cmp(self):
+ long_ago_date = datetime(1900, 1, 21)
+ recent_date = datetime(2010, 1, 21)
+ attachment1 = self._mock_attachment(is_rollout=False, attach_date=recent_date)
+ attachment2 = self._mock_attachment(is_rollout=False, attach_date=long_ago_date)
+ attachment3 = self._mock_attachment(is_rollout=True, attach_date=recent_date)
+ attachment4 = self._mock_attachment(is_rollout=True, attach_date=long_ago_date)
+ attachments = [attachment1, attachment2, attachment3, attachment4]
+ expected_sort = [attachment4, attachment3, attachment2, attachment1]
+ queue = CommitQueueFeeder(MockTool())
+ attachments.sort(queue._patch_cmp)
+ self.assertEqual(attachments, expected_sort)
diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/queueengine.py b/WebKitTools/Scripts/webkitpy/tool/bot/queueengine.py
index 289dc4a..8118653 100644
--- a/WebKitTools/Scripts/webkitpy/tool/bot/queueengine.py
+++ b/WebKitTools/Scripts/webkitpy/tool/bot/queueengine.py
@@ -141,6 +141,8 @@ class QueueEngine:
def _open_work_log(self, work_item):
work_item_log_path = self._delegate.work_item_log_path(work_item)
+ if not work_item_log_path:
+ return
self._work_log = self._output_tee.add_log(work_item_log_path)
def _ensure_work_log_closed(self):
diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/download.py b/WebKitTools/Scripts/webkitpy/tool/commands/download.py
index ed0e3d6..9916523 100644
--- a/WebKitTools/Scripts/webkitpy/tool/commands/download.py
+++ b/WebKitTools/Scripts/webkitpy/tool/commands/download.py
@@ -289,7 +289,7 @@ class AbstractRolloutPrepCommand(AbstractSequencedCommand):
argument_names = "REVISION REASON"
def _commit_info(self, revision):
- commit_info = self.tool.checkout().commit_info_for_revision(revision)
+ commit_info = self._tool.checkout().commit_info_for_revision(revision)
if commit_info and commit_info.bug_id():
# Note: Don't print a bug URL here because it will confuse the
# SheriffBot because the SheriffBot just greps the output
diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/openbugs.py b/WebKitTools/Scripts/webkitpy/tool/commands/openbugs.py
index 5da5bbb..1b51c9f 100644
--- a/WebKitTools/Scripts/webkitpy/tool/commands/openbugs.py
+++ b/WebKitTools/Scripts/webkitpy/tool/commands/openbugs.py
@@ -41,8 +41,8 @@ class OpenBugs(AbstractDeclarativeCommand):
def _open_bugs(self, bug_ids):
for bug_id in bug_ids:
- bug_url = self.tool.bugs.bug_url_for_bug_id(bug_id)
- self.tool.user.open_url(bug_url)
+ bug_url = self._tool.bugs.bug_url_for_bug_id(bug_id)
+ self._tool.user.open_url(bug_url)
# _find_bugs_in_string mostly exists for easy unit testing.
def _find_bugs_in_string(self, string):
diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/queries.py b/WebKitTools/Scripts/webkitpy/tool/commands/queries.py
index 9b8d162..c6e45aa 100644
--- a/WebKitTools/Scripts/webkitpy/tool/commands/queries.py
+++ b/WebKitTools/Scripts/webkitpy/tool/commands/queries.py
@@ -33,6 +33,7 @@ from optparse import make_option
from webkitpy.common.checkout.commitinfo import CommitInfo
from webkitpy.common.config.committers import CommitterList
from webkitpy.common.net.buildbot import BuildBot
+from webkitpy.common.net.regressionwindow import RegressionWindow
from webkitpy.common.system.user import User
from webkitpy.tool.grammar import pluralize
from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand
@@ -112,7 +113,7 @@ class LastGreenRevision(AbstractDeclarativeCommand):
help_text = "Prints the last known good revision"
def execute(self, options, args, tool):
- print self.tool.buildbot.last_green_revision()
+ print self._tool.buildbot.last_green_revision()
class WhatBroke(AbstractDeclarativeCommand):
@@ -122,29 +123,26 @@ class WhatBroke(AbstractDeclarativeCommand):
def _print_builder_line(self, builder_name, max_name_width, status_message):
print "%s : %s" % (builder_name.ljust(max_name_width), status_message)
- # FIXME: This is slightly different from Builder.suspect_revisions_for_green_to_red_transition
- # due to needing to detect the "hit the limit" case an print a special message.
def _print_blame_information_for_builder(self, builder_status, name_width, avoid_flakey_tests=True):
- builder = self.tool.buildbot.builder_with_name(builder_status["name"])
+ builder = self._tool.buildbot.builder_with_name(builder_status["name"])
red_build = builder.build(builder_status["build_number"])
- (last_green_build, first_red_build) = builder.find_failure_transition(red_build)
- if not first_red_build:
+ regression_window = builder.find_regression_window(red_build)
+ if not regression_window.failing_build():
self._print_builder_line(builder.name(), name_width, "FAIL (error loading build information)")
return
- if not last_green_build:
- self._print_builder_line(builder.name(), name_width, "FAIL (blame-list: sometime before %s?)" % first_red_build.revision())
+ if not regression_window.build_before_failure():
+ self._print_builder_line(builder.name(), name_width, "FAIL (blame-list: sometime before %s?)" % regression_window.failing_build().revision())
return
- suspect_revisions = range(first_red_build.revision(), last_green_build.revision(), -1)
- suspect_revisions.reverse()
+ revisions = regression_window.revisions()
first_failure_message = ""
- if (first_red_build == builder.build(builder_status["build_number"])):
+ if (regression_window.failing_build() == builder.build(builder_status["build_number"])):
first_failure_message = " FIRST FAILURE, possibly a flaky test"
- self._print_builder_line(builder.name(), name_width, "FAIL (blame-list: %s%s)" % (suspect_revisions, first_failure_message))
- for revision in suspect_revisions:
- commit_info = self.tool.checkout().commit_info_for_revision(revision)
+ self._print_builder_line(builder.name(), name_width, "FAIL (blame-list: %s%s)" % (revisions, first_failure_message))
+ for revision in revisions:
+ commit_info = self._tool.checkout().commit_info_for_revision(revision)
if commit_info:
- print commit_info.blame_string(self.tool.bugs)
+ print commit_info.blame_string(self._tool.bugs)
else:
print "FAILED to fetch CommitInfo for r%s, likely missing ChangeLog" % revision
@@ -169,7 +167,7 @@ class WhoBrokeIt(AbstractDeclarativeCommand):
help_text = "Print a list of revisions causing failures on %s" % BuildBot.default_host
def execute(self, options, args, tool):
- for revision, builders in self.tool.buildbot.revisions_causing_failures(False).items():
+ for revision, builders in self._tool.buildbot.failure_map(False).revisions_causing_failures().items():
print "r%s appears to have broken %s" % (revision, [builder.name() for builder in builders])
@@ -188,7 +186,7 @@ class ResultsFor(AbstractDeclarativeCommand):
print " %s" % filename
def execute(self, options, args, tool):
- builders = self.tool.buildbot.builders()
+ builders = self._tool.buildbot.builders()
for builder in builders:
print "%s:" % builder.name()
build = builder.build_for_revision(args[0], allow_failed_lookups=True)
@@ -200,13 +198,14 @@ class FailureReason(AbstractDeclarativeCommand):
help_text = "Lists revisions where individual test failures started at %s" % BuildBot.default_host
def _print_blame_information_for_transition(self, green_build, red_build, failing_tests):
- suspect_revisions = green_build.builder().suspect_revisions_for_transition(green_build, red_build)
+ regression_window = RegressionWindow(green_build, red_build)
+ revisions = regression_window.revisions()
print "SUCCESS: Build %s (r%s) was the first to show failures: %s" % (red_build._number, red_build.revision(), failing_tests)
print "Suspect revisions:"
- for revision in suspect_revisions:
- commit_info = self.tool.checkout().commit_info_for_revision(revision)
+ for revision in revisions:
+ commit_info = self._tool.checkout().commit_info_for_revision(revision)
if commit_info:
- print commit_info.blame_string(self.tool.bugs)
+ print commit_info.blame_string(self._tool.bugs)
else:
print "FAILED to fetch CommitInfo for r%s, likely missing ChangeLog" % revision
@@ -255,7 +254,7 @@ class FailureReason(AbstractDeclarativeCommand):
return 0
def _builder_to_explain(self):
- builder_statuses = self.tool.buildbot.builder_statuses()
+ builder_statuses = self._tool.buildbot.builder_statuses()
red_statuses = [status for status in builder_statuses if not status["is_green"]]
print "%s failing" % (pluralize("builder", len(red_statuses)))
builder_choices = [status["name"] for status in red_statuses]
@@ -264,11 +263,11 @@ class FailureReason(AbstractDeclarativeCommand):
# FIXME: prompt_with_list should really take a set of objects and a set of names and then return the object.
for status in red_statuses:
if status["name"] == chosen_name:
- return (self.tool.buildbot.builder_with_name(chosen_name), status["built_revision"])
+ return (self._tool.buildbot.builder_with_name(chosen_name), status["built_revision"])
def execute(self, options, args, tool):
(builder, latest_revision) = self._builder_to_explain()
- start_revision = self.tool.user.prompt("Revision to walk backwards from? [%s] " % latest_revision) or latest_revision
+ start_revision = self._tool.user.prompt("Revision to walk backwards from? [%s] " % latest_revision) or latest_revision
if not start_revision:
print "Revision required."
return 1
diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/queues.py b/WebKitTools/Scripts/webkitpy/tool/commands/queues.py
index bc9ee42..80fd2ea 100644
--- a/WebKitTools/Scripts/webkitpy/tool/commands/queues.py
+++ b/WebKitTools/Scripts/webkitpy/tool/commands/queues.py
@@ -27,6 +27,7 @@
# (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 time
import traceback
import os
@@ -39,6 +40,8 @@ from webkitpy.common.net.statusserver import StatusServer
from webkitpy.common.system.executive import ScriptError
from webkitpy.common.system.deprecated_logging import error, log
from webkitpy.tool.commands.stepsequence import StepSequenceErrorHandler
+from webkitpy.tool.bot.commitqueuetask import CommitQueueTask
+from webkitpy.tool.bot.feeders import CommitQueueFeeder
from webkitpy.tool.bot.patchcollection import PersistentPatchCollection, PersistentPatchCollectionDelegate
from webkitpy.tool.bot.queueengine import QueueEngine, QueueEngineDelegate
from webkitpy.tool.grammar import pluralize
@@ -50,6 +53,7 @@ class AbstractQueue(Command, QueueEngineDelegate):
_pass_status = "Pass"
_fail_status = "Fail"
+ _retry_status = "Retry"
_error_status = "Error"
def __init__(self, options=None): # Default values should never be collections (like []) as default values are shared between invocations
@@ -62,20 +66,20 @@ class AbstractQueue(Command, QueueEngineDelegate):
def _cc_watchers(self, bug_id):
try:
- self.tool.bugs.add_cc_to_bug(bug_id, self.watchers)
+ self._tool.bugs.add_cc_to_bug(bug_id, self.watchers)
except Exception, e:
traceback.print_exc()
log("Failed to CC watchers.")
def run_webkit_patch(self, args):
- webkit_patch_args = [self.tool.path()]
+ webkit_patch_args = [self._tool.path()]
# FIXME: This is a hack, we should have a more general way to pass global options.
# FIXME: We must always pass global options and their value in one argument
# because our global option code looks for the first argument which does
# not begin with "-" and assumes that is the command name.
- webkit_patch_args += ["--status-host=%s" % self.tool.status_server.host]
+ webkit_patch_args += ["--status-host=%s" % self._tool.status_server.host]
webkit_patch_args.extend(args)
- return self.tool.executive.run_and_throw_if_fail(webkit_patch_args)
+ return self._tool.executive.run_and_throw_if_fail(webkit_patch_args)
def _log_directory(self):
return "%s-logs" % self.name
@@ -89,16 +93,16 @@ class AbstractQueue(Command, QueueEngineDelegate):
raise NotImplementedError, "subclasses must implement"
def begin_work_queue(self):
- log("CAUTION: %s will discard all local changes in \"%s\"" % (self.name, self.tool.scm().checkout_root))
+ log("CAUTION: %s will discard all local changes in \"%s\"" % (self.name, self._tool.scm().checkout_root))
if self.options.confirm:
- response = self.tool.user.prompt("Are you sure? Type \"yes\" to continue: ")
+ response = self._tool.user.prompt("Are you sure? Type \"yes\" to continue: ")
if (response != "yes"):
error("User declined.")
log("Running WebKit %s." % self.name)
- self.tool.status_server.update_status(self.name, "Starting Queue")
+ self._tool.status_server.update_status(self.name, "Starting Queue")
def stop_work_queue(self, reason):
- self.tool.status_server.update_status(self.name, "Stopping Queue, reason: %s" % reason)
+ self._tool.status_server.update_status(self.name, "Stopping Queue, reason: %s" % reason)
def should_continue_work_queue(self):
self._iteration_count += 1
@@ -120,8 +124,8 @@ class AbstractQueue(Command, QueueEngineDelegate):
def execute(self, options, args, tool, engine=QueueEngine):
self.options = options # FIXME: This code is wrong. Command.options is a list, this assumes an Options element!
- self.tool = tool # FIXME: This code is wrong too! Command.bind_to_tool handles this!
- return engine(self.name, self, self.tool.wakeup_event).run()
+ self._tool = tool # FIXME: This code is wrong too! Command.bind_to_tool handles this!
+ return engine(self.name, self, self._tool.wakeup_event).run()
@classmethod
def _log_from_script_error_for_upload(cls, script_error, output_limit=None):
@@ -144,20 +148,47 @@ class AbstractQueue(Command, QueueEngineDelegate):
return tool.status_server.update_status(cls.name, message, state["patch"], failure_log)
+class FeederQueue(AbstractQueue):
+ name = "feeder-queue"
+
+ _sleep_duration = 30 # seconds
+
+ # AbstractPatchQueue methods
+
+ def begin_work_queue(self):
+ AbstractQueue.begin_work_queue(self)
+ self.feeders = [
+ CommitQueueFeeder(self._tool),
+ ]
+
+ def next_work_item(self):
+ # This really show inherit from some more basic class that doesn't
+ # understand work items, but the base class in the heirarchy currently
+ # understands work items.
+ return "synthetic-work-item"
+
+ def should_proceed_with_work_item(self, work_item):
+ return True
+
+ def process_work_item(self, work_item):
+ for feeder in self.feeders:
+ feeder.feed()
+ time.sleep(self._sleep_duration)
+ return True
+
+ def work_item_log_path(self, work_item):
+ return None
+
+ def handle_unexpected_error(self, work_item, message):
+ log(message)
+
+
class AbstractPatchQueue(AbstractQueue):
def _update_status(self, message, patch=None, results_file=None):
- self.tool.status_server.update_status(self.name, message, patch, results_file)
-
- # Note, eventually this will be done by a separate "feeder" queue
- # whose job it is to poll bugzilla and feed work items into the
- # status server for other queues to munch on.
- def _update_work_items(self, patch_ids):
- self.tool.status_server.update_work_items(self.name, patch_ids)
- if patch_ids:
- self.log_progress(patch_ids)
+ return self._tool.status_server.update_status(self.name, message, patch, results_file)
def _fetch_next_work_item(self):
- return self.tool.status_server.next_work_item(self.name)
+ return self._tool.status_server.next_work_item(self.name)
def _did_pass(self, patch):
self._update_status(self._pass_status, patch)
@@ -165,6 +196,9 @@ class AbstractPatchQueue(AbstractQueue):
def _did_fail(self, patch):
self._update_status(self._fail_status, patch)
+ def _did_retry(self, patch):
+ self._update_status(self._retry_status, patch)
+
def _did_error(self, patch, reason):
message = "%s: %s" % (self._error_status, reason)
self._update_status(message, patch)
@@ -172,178 +206,63 @@ class AbstractPatchQueue(AbstractQueue):
def work_item_log_path(self, patch):
return os.path.join(self._log_directory(), "%s.log" % patch.bug_id())
- def log_progress(self, patch_ids):
- log("%s in %s [%s]" % (pluralize("patch", len(patch_ids)), self.name, ", ".join(map(str, patch_ids))))
-
class CommitQueue(AbstractPatchQueue, StepSequenceErrorHandler):
name = "commit-queue"
- def __init__(self):
- AbstractPatchQueue.__init__(self)
# AbstractPatchQueue methods
def begin_work_queue(self):
AbstractPatchQueue.begin_work_queue(self)
- self.committer_validator = CommitterValidator(self.tool.bugs)
-
- def _validate_patches_in_commit_queue(self):
- # Not using BugzillaQueries.fetch_patches_from_commit_queue() so we can reject patches with invalid committers/reviewers.
- bug_ids = self.tool.bugs.queries.fetch_bug_ids_from_commit_queue()
- all_patches = sum([self.tool.bugs.fetch_bug(bug_id).commit_queued_patches(include_invalid=True) for bug_id in bug_ids], [])
- return self.committer_validator.patches_after_rejecting_invalid_commiters_and_reviewers(all_patches)
-
- def _patch_cmp(self, a, b):
- # Sort first by is_rollout, then by attach_date.
- # Reversing the order so that is_rollout is first.
- rollout_cmp = cmp(b.is_rollout(), a.is_rollout())
- if (rollout_cmp != 0):
- return rollout_cmp
- return cmp(a.attach_date(), b.attach_date())
-
- def _feed_work_items_to_server(self):
- # Grab the set of patches from bugzilla, sort them, and update the status server.
- # Eventually this will all be done by a separate feeder queue.
- patches = self._validate_patches_in_commit_queue()
- patches = sorted(patches, self._patch_cmp)
- self._update_work_items([patch.id() for patch in patches])
+ self.committer_validator = CommitterValidator(self._tool.bugs)
def next_work_item(self):
- self._feed_work_items_to_server()
- # The grab the next patch to work on back from the status server.
patch_id = self._fetch_next_work_item()
if not patch_id:
return None
- return self.tool.bugs.fetch_attachment(patch_id)
-
- def _can_build_and_test_without_patch(self):
- try:
- self.run_webkit_patch([
- "build-and-test",
- "--force-clean",
- "--build",
- "--test",
- "--non-interactive",
- "--no-update",
- "--build-style=both",
- "--quiet"])
- return True
- except ScriptError, e:
- failure_log = self._log_from_script_error_for_upload(e)
- self._update_status("Unable to build and test without patch", results_file=failure_log)
- return False
+ return self._tool.bugs.fetch_attachment(patch_id)
def should_proceed_with_work_item(self, patch):
patch_text = "rollout patch" if patch.is_rollout() else "patch"
- self._update_status("Landing %s" % patch_text, patch)
+ self._update_status("Processing %s" % patch_text, patch)
return True
- def _build_and_test_patch(self, patch, first_run=False):
+ def process_work_item(self, patch):
+ self._cc_watchers(patch.bug_id())
+ task = CommitQueueTask(self._tool, self, patch)
try:
- args = [
- "build-and-test-attachment",
- "--force-clean",
- "--build",
- "--non-interactive",
- "--build-style=both",
- "--quiet",
- patch.id()
- ]
- # We don't bother to run tests for rollouts as that makes them too slow.
- if not patch.is_rollout():
- args.append("--test")
- if not first_run:
- # The first time through, we don't reject the patch from the
- # commit queue because we want to make sure we can build and
- # test ourselves. However, the second time through, we
- # register ourselves as the parent-command so we can reject
- # the patch on failure.
- args.append("--parent-command=commit-queue")
- # The second time through, we also don't want to update so we
- # know we're testing the same revision that we successfully
- # built and tested.
- args.append("--no-update")
- self.run_webkit_patch(args)
- return True
+ if task.run():
+ self._did_pass(patch)
+ return True
+ self._did_retry(patch)
except ScriptError, e:
- failure_log = self._log_from_script_error_for_upload(e)
- self._update_status("Unable to build and test patch", patch=patch, results_file=failure_log)
- if first_run:
- return False
+ validator = CommitterValidator(self._tool.bugs)
+ validator.reject_patch_from_commit_queue(patch.id(), self._error_message_for_bug(task.failure_status_id, e))
self._did_fail(patch)
- raise
-
- def _revalidate_patch(self, patch):
- # Bugs might get closed, or patches might be obsoleted or r-'d while the
- # commit-queue is processing. Do one last minute check before landing.
- patch = self.tool.bugs.fetch_attachment(patch.id())
- if patch.is_obsolete():
- return None
- if patch.bug().is_closed():
- return None
- if not patch.committer():
- return None
- # Reviewer is not required. Misisng reviewers will be caught during the ChangeLog check during landing.
- return patch
-
- def _land(self, patch):
- try:
- args = [
- "land-attachment",
- "--force-clean",
- "--non-interactive",
- "--ignore-builders",
- "--quiet",
- "--parent-command=commit-queue",
- patch.id(),
- ]
- self.run_webkit_patch(args)
- self._did_pass(patch)
- except ScriptError, e:
- failure_log = self._log_from_script_error_for_upload(e)
- self._update_status("Unable to land patch", patch=patch, results_file=failure_log)
- self._did_fail(patch)
- raise
-
- def process_work_item(self, patch):
- self._cc_watchers(patch.bug_id())
- if not self._build_and_test_patch(patch, first_run=True):
- self._update_status("Building and testing without the patch as a sanity check", patch)
- # The patch failed to build and test. It's possible that the
- # tree is busted. To check that case, we try to build and test
- # without the patch.
- if not self._can_build_and_test_without_patch():
- return False
- self._update_status("Build and test succeeded, trying again with patch", patch)
- # Hum, looks like the patch is actually bad. Of course, we could
- # have been bitten by a flaky test the first time around. We try
- # to build and test again. If it fails a second time, we're pretty
- # sure its a bad test and re can reject it outright.
- self._build_and_test_patch(patch)
- # Do one last check to catch any bug changes (cq-, closed, reviewer changed, etc.)
- # This helps catch races between the bots if locks expire.
- patch = self._revalidate_patch(patch)
- if not patch:
- return False
- self._land(patch)
- return True
def handle_unexpected_error(self, patch, message):
self.committer_validator.reject_patch_from_commit_queue(patch.id(), message)
- # StepSequenceErrorHandler methods
- @staticmethod
- def _error_message_for_bug(tool, status_id, script_error):
+ def command_passed(self, message, patch):
+ self._update_status(message, patch=patch)
+
+ def command_failed(self, message, script_error, patch):
+ failure_log = self._log_from_script_error_for_upload(script_error)
+ return self._update_status(message, patch=patch, results_file=failure_log)
+
+ def _error_message_for_bug(self, status_id, script_error):
if not script_error.output:
return script_error.message_with_output()
- results_link = tool.status_server.results_url_for_status(status_id)
+ results_link = self._tool.status_server.results_url_for_status(status_id)
return "%s\nFull output: %s" % (script_error.message_with_output(), results_link)
- @classmethod
+ # StepSequenceErrorHandler methods
+
def handle_script_error(cls, tool, state, script_error):
- status_id = cls._update_status_for_script_error(tool, state, script_error)
- validator = CommitterValidator(tool.bugs)
- validator.reject_patch_from_commit_queue(state["patch"].id(), cls._error_message_for_bug(tool, status_id, script_error))
+ # Hitting this error handler should be pretty rare. It does occur,
+ # however, when a patch no longer applies to top-of-tree in the final
+ # land step.
+ log(script_error.message_with_output())
@classmethod
def handle_checkout_needs_update(cls, tool, state, options, error):
@@ -368,7 +287,7 @@ class RietveldUploadQueue(AbstractPatchQueue, StepSequenceErrorHandler):
# AbstractPatchQueue methods
def next_work_item(self):
- patch_id = self.tool.bugs.queries.fetch_first_patch_from_rietveld_queue()
+ patch_id = self._tool.bugs.queries.fetch_first_patch_from_rietveld_queue()
if patch_id:
return patch_id
self._update_status("Empty queue")
@@ -393,7 +312,7 @@ class RietveldUploadQueue(AbstractPatchQueue, StepSequenceErrorHandler):
def handle_unexpected_error(self, patch, message):
log(message)
- self._reject_patch(self.tool, patch.id())
+ self._reject_patch(self._tool, patch.id())
# StepSequenceErrorHandler methods
@@ -417,10 +336,10 @@ class AbstractReviewQueue(AbstractPatchQueue, PersistentPatchCollectionDelegate,
return self.name
def fetch_potential_patch_ids(self):
- return self.tool.bugs.queries.fetch_attachment_ids_from_review_queue()
+ return self._tool.bugs.queries.fetch_attachment_ids_from_review_queue()
def status_server(self):
- return self.tool.status_server
+ return self._tool.status_server
def is_terminal_status(self, status):
return status == "Pass" or status == "Fail" or status.startswith("Error:")
@@ -434,7 +353,7 @@ class AbstractReviewQueue(AbstractPatchQueue, PersistentPatchCollectionDelegate,
def next_work_item(self):
patch_id = self._patches.next()
if patch_id:
- return self.tool.bugs.fetch_attachment(patch_id)
+ return self._tool.bugs.fetch_attachment(patch_id)
def should_proceed_with_work_item(self, patch):
raise NotImplementedError, "subclasses must implement"
diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/queues_unittest.py b/WebKitTools/Scripts/webkitpy/tool/commands/queues_unittest.py
index 2deee76..029814e 100644
--- a/WebKitTools/Scripts/webkitpy/tool/commands/queues_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/tool/commands/queues_unittest.py
@@ -47,20 +47,16 @@ class TestReviewQueue(AbstractReviewQueue):
name = "test-review-queue"
+class TestFeederQueue(FeederQueue):
+ _sleep_duration = 0
+
+
class MockRolloutPatch(MockPatch):
def is_rollout(self):
return True
class AbstractQueueTest(CommandsTest):
- def _assert_log_progress_output(self, patch_ids, progress_output):
- OutputCapture().assert_outputs(self, TestQueue().log_progress, [patch_ids], expected_stderr=progress_output)
-
- def test_log_progress(self):
- self._assert_log_progress_output([1,2,3], "3 patches in test-queue [1, 2, 3]\n")
- self._assert_log_progress_output(["1","2","3"], "3 patches in test-queue [1, 2, 3]\n")
- self._assert_log_progress_output([1], "1 patch in test-queue [1]\n")
-
def test_log_directory(self):
self.assertEquals(TestQueue()._log_directory(), "test-queue-logs")
@@ -115,6 +111,29 @@ class AbstractQueueTest(CommandsTest):
self._assert_log_message(script_error, expected_output)
+class FeederQueueTest(QueuesTest):
+ def test_feeder_queue(self):
+ queue = TestFeederQueue()
+ tool = MockTool(log_executive=True)
+ expected_stderr = {
+ "begin_work_queue": self._default_begin_work_queue_stderr("feeder-queue", MockSCM.fake_checkout_root),
+ "should_proceed_with_work_item": "",
+ "next_work_item": "",
+ "process_work_item": """Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com)
+Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com)
+MOCK setting flag 'commit-queue' to '-' on attachment '128' with comment 'Rejecting patch 128 from commit-queue.' and additional comment 'non-committer@example.com does not have committer permissions according to http://trac.webkit.org/browser/trunk/WebKitTools/Scripts/webkitpy/common/config/committers.py.
+
+- If you do not have committer rights please read http://webkit.org/coding/contributing.html for instructions on how to use bugzilla flags.
+
+- If you have committer rights please correct the error in WebKitTools/Scripts/webkitpy/common/config/committers.py by adding yourself to the file (no review needed). The commit-queue restarts itself every 2 hours. After restart the commit-queue will correctly respect your committer rights.'
+MOCK: update_work_items: commit-queue [106, 197]
+Feeding commit-queue items [106, 197]
+""",
+ "handle_unexpected_error": "Mock error message\n",
+ }
+ self.assert_queue_outputs(queue, tool=tool, expected_stderr=expected_stderr)
+
+
class AbstractPatchQueueTest(CommandsTest):
def test_fetch_next_work_item(self):
queue = AbstractPatchQueue()
@@ -167,7 +186,7 @@ class SecondThoughtsCommitQueue(CommitQueue):
"attacher_email": "Contributer1",
}
patch = Attachment(attachment_dictionary, None)
- self.tool.bugs.set_override_patch(patch)
+ self._tool.bugs.set_override_patch(patch)
return True
@@ -175,40 +194,58 @@ class CommitQueueTest(QueuesTest):
def test_commit_queue(self):
expected_stderr = {
"begin_work_queue": self._default_begin_work_queue_stderr("commit-queue", MockSCM.fake_checkout_root),
- "should_proceed_with_work_item": "MOCK: update_status: commit-queue Landing patch\n",
- # FIXME: The commit-queue warns about bad committers twice. This is due to the fact that we access Attachment.reviewer() twice and it logs each time.
- "next_work_item": """Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com)
-Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com)
-MOCK setting flag 'commit-queue' to '-' on attachment '128' with comment 'Rejecting patch 128 from commit-queue.' and additional comment 'non-committer@example.com does not have committer permissions according to http://trac.webkit.org/browser/trunk/WebKitTools/Scripts/webkitpy/common/config/committers.py.\n\n- If you do not have committer rights please read http://webkit.org/coding/contributing.html for instructions on how to use bugzilla flags.\n\n- If you have committer rights please correct the error in WebKitTools/Scripts/webkitpy/common/config/committers.py by adding yourself to the file (no review needed). The commit-queue restarts itself every 2 hours. After restart the commit-queue will correctly respect your committer rights.'
-MOCK: update_work_items: commit-queue [106, 197]
-2 patches in commit-queue [106, 197]
+ "should_proceed_with_work_item": "MOCK: update_status: commit-queue Processing patch\n",
+ "next_work_item": "",
+ "process_work_item": """MOCK: update_status: commit-queue Applied patch
+MOCK: update_status: commit-queue Built patch
+MOCK: update_status: commit-queue Passed tests
+MOCK: update_status: commit-queue Landed patch
+MOCK: update_status: commit-queue Pass
""",
- "process_work_item": "MOCK: update_status: commit-queue Pass\n",
"handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '197' with comment 'Rejecting patch 197 from commit-queue.' and additional comment 'Mock error message'\n",
- "handle_script_error": "MOCK: update_status: commit-queue ScriptError error message\nMOCK setting flag 'commit-queue' to '-' on attachment '197' with comment 'Rejecting patch 197 from commit-queue.' and additional comment 'ScriptError error message'\n",
+ "handle_script_error": "ScriptError error message\n",
}
self.assert_queue_outputs(CommitQueue(), expected_stderr=expected_stderr)
+ def test_commit_queue_failure(self):
+ expected_stderr = {
+ "begin_work_queue": self._default_begin_work_queue_stderr("commit-queue", MockSCM.fake_checkout_root),
+ "should_proceed_with_work_item": "MOCK: update_status: commit-queue Processing patch\n",
+ "next_work_item": "",
+ "process_work_item": """MOCK: update_status: commit-queue Patch does not apply
+MOCK setting flag 'commit-queue' to '-' on attachment '197' with comment 'Rejecting patch 197 from commit-queue.' and additional comment 'MOCK script error'
+MOCK: update_status: commit-queue Fail
+""",
+ "handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '197' with comment 'Rejecting patch 197 from commit-queue.' and additional comment 'Mock error message'\n",
+ "handle_script_error": "ScriptError error message\n",
+ }
+ queue = CommitQueue()
+
+ def mock_run_webkit_patch(command):
+ raise ScriptError('MOCK script error')
+
+ queue.run_webkit_patch = mock_run_webkit_patch
+ self.assert_queue_outputs(queue, expected_stderr=expected_stderr)
+
def test_rollout(self):
tool = MockTool(log_executive=True)
tool.buildbot.light_tree_on_fire()
expected_stderr = {
"begin_work_queue": self._default_begin_work_queue_stderr("commit-queue", MockSCM.fake_checkout_root),
- "should_proceed_with_work_item": "MOCK: update_status: commit-queue Landing patch\n",
- # FIXME: The commit-queue warns about bad committers twice. This is due to the fact that we access Attachment.reviewer() twice and it logs each time.
- "next_work_item": """Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com)
-Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com)
-MOCK setting flag 'commit-queue' to '-' on attachment '128' with comment 'Rejecting patch 128 from commit-queue.' and additional comment 'non-committer@example.com does not have committer permissions according to http://trac.webkit.org/browser/trunk/WebKitTools/Scripts/webkitpy/common/config/committers.py.
-
-- If you do not have committer rights please read http://webkit.org/coding/contributing.html for instructions on how to use bugzilla flags.
-
-- If you have committer rights please correct the error in WebKitTools/Scripts/webkitpy/common/config/committers.py by adding yourself to the file (no review needed). The commit-queue restarts itself every 2 hours. After restart the commit-queue will correctly respect your committer rights.'
-MOCK: update_work_items: commit-queue [106, 197]
-2 patches in commit-queue [106, 197]
+ "should_proceed_with_work_item": "MOCK: update_status: commit-queue Processing patch\n",
+ "next_work_item": "",
+ "process_work_item": """MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'apply-attachment', '--force-clean', '--non-interactive', '--quiet', 197]
+MOCK: update_status: commit-queue Applied patch
+MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'build', '--no-clean', '--no-update', '--build', '--build-style=both', '--quiet']
+MOCK: update_status: commit-queue Built patch
+MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'build-and-test', '--no-clean', '--no-update', '--test', '--quiet', '--non-interactive']
+MOCK: update_status: commit-queue Passed tests
+MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'land-attachment', '--force-clean', '--ignore-builders', '--quiet', '--non-interactive', '--parent-command=commit-queue', 197]
+MOCK: update_status: commit-queue Landed patch
+MOCK: update_status: commit-queue Pass
""",
- "process_work_item": "MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'build-and-test-attachment', '--force-clean', '--build', '--non-interactive', '--build-style=both', '--quiet', 197, '--test']\nMOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'land-attachment', '--force-clean', '--non-interactive', '--ignore-builders', '--quiet', '--parent-command=commit-queue', 197]\nMOCK: update_status: commit-queue Pass\n",
"handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '197' with comment 'Rejecting patch 197 from commit-queue.' and additional comment 'Mock error message'\n",
- "handle_script_error": "MOCK: update_status: commit-queue ScriptError error message\nMOCK setting flag 'commit-queue' to '-' on attachment '197' with comment 'Rejecting patch 197 from commit-queue.' and additional comment 'ScriptError error message'\n",
+ "handle_script_error": "ScriptError error message\n",
}
self.assert_queue_outputs(CommitQueue(), tool=tool, expected_stderr=expected_stderr)
@@ -218,52 +255,23 @@ MOCK: update_work_items: commit-queue [106, 197]
rollout_patch = MockRolloutPatch()
expected_stderr = {
"begin_work_queue": self._default_begin_work_queue_stderr("commit-queue", MockSCM.fake_checkout_root),
- "should_proceed_with_work_item": "MOCK: update_status: commit-queue Landing rollout patch\n",
- # FIXME: The commit-queue warns about bad committers twice. This is due to the fact that we access Attachment.reviewer() twice and it logs each time.
- "next_work_item": """Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com)
-Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com)
-MOCK setting flag 'commit-queue' to '-' on attachment '128' with comment 'Rejecting patch 128 from commit-queue.' and additional comment 'non-committer@example.com does not have committer permissions according to http://trac.webkit.org/browser/trunk/WebKitTools/Scripts/webkitpy/common/config/committers.py.
-
-- If you do not have committer rights please read http://webkit.org/coding/contributing.html for instructions on how to use bugzilla flags.
-
-- If you have committer rights please correct the error in WebKitTools/Scripts/webkitpy/common/config/committers.py by adding yourself to the file (no review needed). The commit-queue restarts itself every 2 hours. After restart the commit-queue will correctly respect your committer rights.'
-MOCK: update_work_items: commit-queue [106, 197]
-2 patches in commit-queue [106, 197]
+ "should_proceed_with_work_item": "MOCK: update_status: commit-queue Processing rollout patch\n",
+ "next_work_item": "",
+ "process_work_item": """MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'apply-attachment', '--force-clean', '--non-interactive', '--quiet', 197]
+MOCK: update_status: commit-queue Applied patch
+MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'build', '--no-clean', '--no-update', '--build', '--build-style=both', '--quiet']
+MOCK: update_status: commit-queue Built patch
+MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'build-and-test', '--no-clean', '--no-update', '--test', '--quiet', '--non-interactive']
+MOCK: update_status: commit-queue Passed tests
+MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'land-attachment', '--force-clean', '--ignore-builders', '--quiet', '--non-interactive', '--parent-command=commit-queue', 197]
+MOCK: update_status: commit-queue Landed patch
+MOCK: update_status: commit-queue Pass
""",
- "process_work_item": "MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'build-and-test-attachment', '--force-clean', '--build', '--non-interactive', '--build-style=both', '--quiet', 197]\nMOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'land-attachment', '--force-clean', '--non-interactive', '--ignore-builders', '--quiet', '--parent-command=commit-queue', 197]\nMOCK: update_status: commit-queue Pass\n",
"handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '197' with comment 'Rejecting patch 197 from commit-queue.' and additional comment 'Mock error message'\n",
- "handle_script_error": "MOCK: update_status: commit-queue ScriptError error message\nMOCK setting flag 'commit-queue' to '-' on attachment '197' with comment 'Rejecting patch 197 from commit-queue.' and additional comment 'ScriptError error message'\n",
+ "handle_script_error": "ScriptError error message\n",
}
self.assert_queue_outputs(CommitQueue(), tool=tool, work_item=rollout_patch, expected_stderr=expected_stderr)
- def test_can_build_and_test(self):
- queue = CommitQueue()
- tool = MockTool()
- tool.executive = Mock()
- queue.bind_to_tool(tool)
- self.assertTrue(queue._can_build_and_test_without_patch())
- expected_run_args = ["echo", "--status-host=example.com", "build-and-test", "--force-clean", "--build", "--test", "--non-interactive", "--no-update", "--build-style=both", "--quiet"]
- tool.executive.run_and_throw_if_fail.assert_called_with(expected_run_args)
-
- def _mock_attachment(self, is_rollout, attach_date):
- attachment = Mock()
- attachment.is_rollout = lambda: is_rollout
- attachment.attach_date = lambda: attach_date
- return attachment
-
- def test_patch_cmp(self):
- long_ago_date = datetime(1900, 1, 21)
- recent_date = datetime(2010, 1, 21)
- attachment1 = self._mock_attachment(is_rollout=False, attach_date=recent_date)
- attachment2 = self._mock_attachment(is_rollout=False, attach_date=long_ago_date)
- attachment3 = self._mock_attachment(is_rollout=True, attach_date=recent_date)
- attachment4 = self._mock_attachment(is_rollout=True, attach_date=long_ago_date)
- attachments = [attachment1, attachment2, attachment3, attachment4]
- expected_sort = [attachment4, attachment3, attachment2, attachment1]
- queue = CommitQueue()
- attachments.sort(queue._patch_cmp)
- self.assertEqual(attachments, expected_sort)
-
def test_auto_retry(self):
queue = CommitQueue()
options = Mock()
@@ -282,7 +290,13 @@ MOCK: update_work_items: commit-queue [106, 197]
def test_manual_reject_during_processing(self):
queue = SecondThoughtsCommitQueue()
queue.bind_to_tool(MockTool())
- queue.process_work_item(MockPatch())
+ expected_stderr = """MOCK: update_status: commit-queue Applied patch
+MOCK: update_status: commit-queue Built patch
+MOCK: update_status: commit-queue Passed tests
+MOCK: update_status: commit-queue Landed patch
+MOCK: update_status: commit-queue Pass
+"""
+ OutputCapture().assert_outputs(self, queue.process_work_item, [MockPatch()], expected_stderr=expected_stderr)
class RietveldUploadQueueTest(QueuesTest):
diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/queuestest.py b/WebKitTools/Scripts/webkitpy/tool/commands/queuestest.py
index aa3cef4..9f3583d 100644
--- a/WebKitTools/Scripts/webkitpy/tool/commands/queuestest.py
+++ b/WebKitTools/Scripts/webkitpy/tool/commands/queuestest.py
@@ -32,6 +32,7 @@ from webkitpy.common.net.bugzilla import Attachment
from webkitpy.common.system.outputcapture import OutputCapture
from webkitpy.common.system.executive import ScriptError
from webkitpy.thirdparty.mock import Mock
+from webkitpy.tool.commands.stepsequence import StepSequenceErrorHandler
from webkitpy.tool.mocktool import MockTool
@@ -100,4 +101,6 @@ class QueuesTest(unittest.TestCase):
self.assert_outputs(queue.should_proceed_with_work_item, "should_proceed_with_work_item", [work_item], expected_stdout, expected_stderr, expected_exceptions)
self.assert_outputs(queue.process_work_item, "process_work_item", [work_item], expected_stdout, expected_stderr, expected_exceptions)
self.assert_outputs(queue.handle_unexpected_error, "handle_unexpected_error", [work_item, "Mock error message"], expected_stdout, expected_stderr, expected_exceptions)
- self.assert_outputs(queue.handle_script_error, "handle_script_error", [tool, {"patch": MockPatch()}, ScriptError(message="ScriptError error message", script_args="MockErrorCommand")], expected_stdout, expected_stderr, expected_exceptions)
+ # Should we have a different function for testing StepSequenceErrorHandlers?
+ if isinstance(queue, StepSequenceErrorHandler):
+ self.assert_outputs(queue.handle_script_error, "handle_script_error", [tool, {"patch": MockPatch()}, ScriptError(message="ScriptError error message", script_args="MockErrorCommand")], expected_stdout, expected_stderr, expected_exceptions)
diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/rebaseline.py b/WebKitTools/Scripts/webkitpy/tool/commands/rebaseline.py
index 78e06c6..abfa850 100644
--- a/WebKitTools/Scripts/webkitpy/tool/commands/rebaseline.py
+++ b/WebKitTools/Scripts/webkitpy/tool/commands/rebaseline.py
@@ -72,15 +72,15 @@ class Rebaseline(AbstractDeclarativeCommand):
# FIXME: This should share more code with FailureReason._builder_to_explain
def _builder_to_pull_from(self):
- builder_statuses = self.tool.buildbot.builder_statuses()
+ builder_statuses = self._tool.buildbot.builder_statuses()
red_statuses = [status for status in builder_statuses if not status["is_green"]]
print "%s failing" % (pluralize("builder", len(red_statuses)))
builder_choices = [status["name"] for status in red_statuses]
- chosen_name = self.tool.user.prompt_with_list("Which builder to pull results from:", builder_choices)
+ chosen_name = self._tool.user.prompt_with_list("Which builder to pull results from:", builder_choices)
# FIXME: prompt_with_list should really take a set of objects and a set of names and then return the object.
for status in red_statuses:
if status["name"] == chosen_name:
- return (self.tool.buildbot.builder_with_name(chosen_name), status["build_number"])
+ return (self._tool.buildbot.builder_with_name(chosen_name), status["build_number"])
def _replace_expectation_with_remote_result(self, local_file, remote_file):
(downloaded_file, headers) = urllib.urlretrieve(remote_file)
@@ -90,7 +90,8 @@ class Rebaseline(AbstractDeclarativeCommand):
parsed_results = build.layout_test_results().parsed_results()
# FIXME: This probably belongs as API on LayoutTestResults
# but .failing_tests() already means something else.
- return parsed_results[LayoutTestResults.fail_key]
+ failing_tests = parsed_results[LayoutTestResults.fail_key]
+ return self._tool.user.prompt_with_list("Which test(s) to rebaseline:", failing_tests, can_choose_multiple=True)
def _results_url_for_test(self, build, test):
test_base = os.path.splitext(test)[0]
diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot.py b/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot.py
index 24c8517..23d013d 100644
--- a/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot.py
+++ b/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot.py
@@ -50,9 +50,9 @@ class SheriffBot(AbstractQueue, StepSequenceErrorHandler):
def begin_work_queue(self):
AbstractQueue.begin_work_queue(self)
- self._sheriff = Sheriff(self.tool, self)
- self._irc_bot = SheriffIRCBot(self.tool, self._sheriff)
- self.tool.ensure_irc_connected(self._irc_bot.irc_delegate())
+ self._sheriff = Sheriff(self._tool, self)
+ self._irc_bot = SheriffIRCBot(self._tool, self._sheriff)
+ self._tool.ensure_irc_connected(self._irc_bot.irc_delegate())
def work_item_log_path(self, new_failures):
return os.path.join("%s-logs" % self.name, "%s.log" % new_failures.keys()[0])
@@ -86,12 +86,12 @@ class SheriffBot(AbstractQueue, StepSequenceErrorHandler):
self._update()
# We do one read from buildbot to ensure a consistent view.
- revisions_causing_failures = self.tool.buildbot.revisions_causing_failures()
+ revisions_causing_failures = self._tool.buildbot.failure_map().revisions_causing_failures()
# Similarly, we read once from our the status_server.
old_failing_svn_revisions = []
for svn_revision in revisions_causing_failures.keys():
- if self.tool.status_server.svn_revision(svn_revision):
+ if self._tool.status_server.svn_revision(svn_revision):
old_failing_svn_revisions.append(svn_revision)
new_failures = self._new_failures(revisions_causing_failures,
@@ -108,7 +108,7 @@ class SheriffBot(AbstractQueue, StepSequenceErrorHandler):
blame_list = new_failures.keys()
for svn_revision, builders in new_failures.items():
try:
- commit_info = self.tool.checkout().commit_info_for_revision(svn_revision)
+ commit_info = self._tool.checkout().commit_info_for_revision(svn_revision)
if not commit_info:
print "FAILED to fetch CommitInfo for r%s, likely missing ChangeLog" % revision
continue
@@ -120,7 +120,7 @@ class SheriffBot(AbstractQueue, StepSequenceErrorHandler):
builders)
finally:
for builder in builders:
- self.tool.status_server.update_svn_revision(svn_revision,
+ self._tool.status_server.update_svn_revision(svn_revision,
builder.name())
return True
diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/upload.py b/WebKitTools/Scripts/webkitpy/tool/commands/upload.py
index 4a15ed6..107d8db 100644
--- a/WebKitTools/Scripts/webkitpy/tool/commands/upload.py
+++ b/WebKitTools/Scripts/webkitpy/tool/commands/upload.py
@@ -82,14 +82,14 @@ class CleanPendingCommit(AbstractDeclarativeCommand):
def execute(self, options, args, tool):
committers = CommitterList()
for bug_id in tool.bugs.queries.fetch_bug_ids_from_pending_commit_list():
- bug = self.tool.bugs.fetch_bug(bug_id)
+ bug = self._tool.bugs.fetch_bug(bug_id)
patches = bug.patches(include_obsolete=True)
for patch in patches:
flags_to_clear = self._flags_to_clear_on_patch(patch)
if not flags_to_clear:
continue
message = "Cleared %s from obsolete attachment %s so that this bug does not appear in http://webkit.org/pending-commit." % (flags_to_clear, patch.id())
- self.tool.bugs.obsolete_attachment(patch.id(), message)
+ self._tool.bugs.obsolete_attachment(patch.id(), message)
class AssignToCommitter(AbstractDeclarativeCommand):
@@ -104,7 +104,7 @@ class AssignToCommitter(AbstractDeclarativeCommand):
def _assign_bug_to_last_patch_attacher(self, bug_id):
committers = CommitterList()
- bug = self.tool.bugs.fetch_bug(bug_id)
+ bug = self._tool.bugs.fetch_bug(bug_id)
if not bug.is_unassigned():
assigned_to_email = bug.assigned_to_email()
log("Bug %s is already assigned to %s (%s)." % (bug_id, assigned_to_email, committers.committer_by_email(assigned_to_email)))
@@ -128,7 +128,7 @@ class AssignToCommitter(AbstractDeclarativeCommand):
return
reassign_message = "Attachment %s was posted by a committer and has review+, assigning to %s for commit." % (latest_patch.id(), committer.full_name)
- self.tool.bugs.reassign_bug(bug_id, committer.bugzilla_email(), reassign_message)
+ self._tool.bugs.reassign_bug(bug_id, committer.bugzilla_email(), reassign_message)
def execute(self, options, args, tool):
for bug_id in tool.bugs.queries.fetch_bug_ids_from_pending_commit_list():
diff --git a/WebKitTools/Scripts/webkitpy/tool/mocktool.py b/WebKitTools/Scripts/webkitpy/tool/mocktool.py
index 8a6188a..277bd08 100644
--- a/WebKitTools/Scripts/webkitpy/tool/mocktool.py
+++ b/WebKitTools/Scripts/webkitpy/tool/mocktool.py
@@ -350,6 +350,16 @@ class MockBuilder(object):
self._name, username, comments))
+class MockFailureMap():
+ def __init__(self, buildbot):
+ self._buildbot = buildbot
+
+ def revisions_causing_failures(self):
+ return {
+ "29837": [self._buildbot.builder_with_name("Builder1")],
+ }
+
+
class MockBuildBot(object):
buildbot_host = "dummy_buildbot_host"
def __init__(self):
@@ -394,10 +404,8 @@ class MockBuildBot(object):
def light_tree_on_fire(self):
self._mock_builder2_status["is_green"] = False
- def revisions_causing_failures(self):
- return {
- "29837": [self.builder_with_name("Builder1")],
- }
+ def failure_map(self):
+ return MockFailureMap(self)
class MockSCM(Mock):
@@ -483,8 +491,8 @@ class MockUser(object):
def page(self, message):
pass
- def confirm(self, message=None):
- return True
+ def confirm(self, message=None, default='y'):
+ return default == 'y'
def can_open_url(self):
return True
diff --git a/WebKitTools/Scripts/webkitpy/tool/multicommandtool.py b/WebKitTools/Scripts/webkitpy/tool/multicommandtool.py
index 12ede2e..4848ae5 100644
--- a/WebKitTools/Scripts/webkitpy/tool/multicommandtool.py
+++ b/WebKitTools/Scripts/webkitpy/tool/multicommandtool.py
@@ -53,7 +53,7 @@ class Command(object):
self.required_arguments = self._parse_required_arguments(argument_names)
self.options = options
self.requires_local_commits = requires_local_commits
- self.tool = None
+ self._tool = None
# option_parser can be overriden by the tool using set_option_parser
# This default parser will be used for standalone_help printing.
self.option_parser = HelpPrintingOptionParser(usage=SUPPRESS_USAGE, add_help_option=False, option_list=self.options)
@@ -73,9 +73,9 @@ class Command(object):
# The tool calls bind_to_tool on each Command after adding it to its list.
def bind_to_tool(self, tool):
# Command instances can only be bound to one tool at a time.
- if self.tool and tool != self.tool:
+ if self._tool and tool != self._tool:
raise Exception("Command already bound to tool!")
- self.tool = tool
+ self._tool = tool
@staticmethod
def _parse_required_arguments(argument_names):
@@ -179,17 +179,17 @@ class HelpCommand(AbstractDeclarativeCommand):
# Only show commands which are relevant to this checkout's SCM system. Might this be confusing to some users?
if self.show_all_commands:
epilog = "All %prog commands:\n"
- relevant_commands = self.tool.commands[:]
+ relevant_commands = self._tool.commands[:]
else:
epilog = "Common %prog commands:\n"
- relevant_commands = filter(self.tool.should_show_in_main_help, self.tool.commands)
+ relevant_commands = filter(self._tool.should_show_in_main_help, self._tool.commands)
longest_name_length = max(map(lambda command: len(command.name), relevant_commands))
relevant_commands.sort(lambda a, b: cmp(a.name, b.name))
command_help_texts = map(lambda command: " %s %s\n" % (command.name.ljust(longest_name_length), command.help_text), relevant_commands)
epilog += "%s\n" % "".join(command_help_texts)
epilog += "See '%prog help --all-commands' to list all commands.\n"
epilog += "See '%prog help COMMAND' for more information on a specific command.\n"
- return epilog.replace("%prog", self.tool.name()) # Use of %prog here mimics OptionParser.expand_prog_name().
+ return epilog.replace("%prog", self._tool.name()) # Use of %prog here mimics OptionParser.expand_prog_name().
# FIXME: This is a hack so that we don't show --all-commands as a global option:
def _remove_help_options(self):
@@ -198,7 +198,7 @@ class HelpCommand(AbstractDeclarativeCommand):
def execute(self, options, args, tool):
if args:
- command = self.tool.command_by_name(args[0])
+ command = self._tool.command_by_name(args[0])
if command:
print command.standalone_help()
return 0