summaryrefslogtreecommitdiffstats
path: root/WebKitTools/Scripts
diff options
context:
space:
mode:
authorIain Merrick <husky@google.com>2010-08-19 17:55:56 +0100
committerIain Merrick <husky@google.com>2010-08-23 11:05:40 +0100
commitf486d19d62f1bc33246748b14b14a9dfa617b57f (patch)
tree195485454c93125455a30e553a73981c3816144d /WebKitTools/Scripts
parent6ba0b43722d16bc295606bec39f396f596e4fef1 (diff)
downloadexternal_webkit-f486d19d62f1bc33246748b14b14a9dfa617b57f.zip
external_webkit-f486d19d62f1bc33246748b14b14a9dfa617b57f.tar.gz
external_webkit-f486d19d62f1bc33246748b14b14a9dfa617b57f.tar.bz2
Merge WebKit at r65615 : Initial merge by git.
Change-Id: Ifbf384f4531e3b58475a662e38195c2d9152ae79
Diffstat (limited to 'WebKitTools/Scripts')
-rwxr-xr-xWebKitTools/Scripts/build-webkit23
-rwxr-xr-xWebKitTools/Scripts/commit-log-editor51
-rwxr-xr-xWebKitTools/Scripts/create-html-entity-table183
-rw-r--r--WebKitTools/Scripts/deduplicate-tests84
-rwxr-xr-xWebKitTools/Scripts/old-run-webkit-tests71
-rwxr-xr-xWebKitTools/Scripts/prepare-ChangeLog11
-rwxr-xr-xWebKitTools/Scripts/webkit-patch6
-rw-r--r--WebKitTools/Scripts/webkitdirs.pm25
-rw-r--r--WebKitTools/Scripts/webkitpy/common/checkout/scm.py34
-rw-r--r--WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py55
-rw-r--r--WebKitTools/Scripts/webkitpy/common/config/committers.py9
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests.py167
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests_unittest.py147
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py90
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py4
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py93
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing.py6
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_expectations.py3
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_failures.py9
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win.py4
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/factory.py3
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome.py74
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome_unittest.py46
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/port/test.py3
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py3
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py14
-rwxr-xr-xWebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py142
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py113
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/test_types/fuzzy_image_diff.py71
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/test_types/image_diff.py6
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/test_types/test_type_base.py3
-rw-r--r--WebKitTools/Scripts/webkitpy/layout_tests/test_types/text_diff.py22
-rw-r--r--WebKitTools/Scripts/webkitpy/style/checker.py15
-rw-r--r--WebKitTools/Scripts/webkitpy/style/checkers/common.py19
-rw-r--r--WebKitTools/Scripts/webkitpy/style/checkers/common_unittest.py32
-rw-r--r--WebKitTools/Scripts/webkitpy/style/checkers/test_expectations.py124
-rw-r--r--WebKitTools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py172
-rw-r--r--WebKitTools/Scripts/webkitpy/style/checkers/text.py11
-rw-r--r--WebKitTools/Scripts/webkitpy/style_references.py2
-rw-r--r--WebKitTools/Scripts/webkitpy/thirdparty/simplejson/decoder.py2
-rwxr-xr-xWebKitTools/Scripts/webkitpy/tool/main.py14
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/steps/runtests.py1
-rw-r--r--WebKitTools/Scripts/webkitpy/tool/steps/steps_unittest.py2
43 files changed, 1668 insertions, 301 deletions
diff --git a/WebKitTools/Scripts/build-webkit b/WebKitTools/Scripts/build-webkit
index 3b8dd9c..acd7736 100755
--- a/WebKitTools/Scripts/build-webkit
+++ b/WebKitTools/Scripts/build-webkit
@@ -341,6 +341,9 @@ if (isGtk()) {
# Apple builds JavaScriptGlue, and only on the Mac.
splice @projects, 1, 0, "JavaScriptGlue";
+ # ANGLE must come before WebCore
+ splice @projects, 0, 0, "ANGLE";
+
# WebKit2 is only supported in SnowLeopard and later at present.
push @projects, ("WebKit2", "WebKitTools/MiniBrowser") if osXVersion()->{"minor"} >= 6;
@@ -404,9 +407,13 @@ if (isInspectorFrontend()) {
if (isWx()) {
downloadWafIfNeeded();
- push @projects, 'WebKitTools/DumpRenderTree';
- push @projects, 'WebKitTools/wx/browser';
- push @projects, 'WebKit/wx/bindings/python';
+ @options = ();
+ if (defined($makeArgs)) {
+ @options = split(/ /, $makeArgs);
+ }
+ @projects = ();
+ my $result = buildWafProject('.', $clean, @options);
+ exit exitStatus($result) if exitStatus($result);
}
if (isChromium()) {
@@ -439,16 +446,6 @@ for my $dir (@projects) {
if ($dir eq "WebKit") {
$result = buildVisualStudioProject("win/WebKit.vcproj/WebKit.sln", $clean);
}
- } elsif (isWx()) {
- @options = ();
- if (defined($makeArgs)) {
- @options = split(/ /, $makeArgs);
- }
- if ($dir eq "WebKit" && isWx()) {
- chdir 'wx' or die;
- }
-
- $result = buildWafProject($dir, $clean, @options);
}
if (exitStatus($result)) {
diff --git a/WebKitTools/Scripts/commit-log-editor b/WebKitTools/Scripts/commit-log-editor
index a642731..1be0fd0 100755
--- a/WebKitTools/Scripts/commit-log-editor
+++ b/WebKitTools/Scripts/commit-log-editor
@@ -62,26 +62,40 @@ if (!$log) {
my $baseDir = baseProductDir();
my $editor = $ENV{SVN_LOG_EDITOR};
-if (!$editor || isCommitLogEditor($editor)) {
- $editor = $ENV{CVS_LOG_EDITOR};
-}
-if (!$editor || isCommitLogEditor($editor)) {
+$editor = $ENV{CVS_LOG_EDITOR} if !$editor;
+$editor = "" if isCommitLogEditor($editor);
+
+my $splitEditor = 1;
+if (!$editor) {
my $builtEditorApplication = "$baseDir/Release/Commit Log Editor.app/Contents/MacOS/Commit Log Editor";
- $editor = $builtEditorApplication if -x $builtEditorApplication;
+ if (-x $builtEditorApplication) {
+ $editor = $builtEditorApplication;
+ $splitEditor = 0;
+ }
}
-if (!$editor || isCommitLogEditor($editor)) {
+if (!$editor) {
my $builtEditorApplication = "$baseDir/Debug/Commit Log Editor.app/Contents/MacOS/Commit Log Editor";
- $editor = $builtEditorApplication if -x $builtEditorApplication;
-}
-if (!$editor || isCommitLogEditor($editor)) {
- my $installedEditorApplication = "$ENV{HOME}/Applications/Commit Log Editor.app/Contents/MacOS/Commit Log Editor";
- $editor = $installedEditorApplication if -x $installedEditorApplication;
+ if (-x $builtEditorApplication) {
+ $editor = $builtEditorApplication;
+ $splitEditor = 0;
+ }
}
-if (!$editor || isCommitLogEditor($editor)) {
- $editor = $ENV{EDITOR};
+if (!$editor) {
+ my $builtEditorApplication = "$ENV{HOME}/Applications/Commit Log Editor.app/Contents/MacOS/Commit Log Editor";
+ if (-x $builtEditorApplication) {
+ $editor = $builtEditorApplication;
+ $splitEditor = 0;
+ }
}
-if (!$editor || isCommitLogEditor($editor)) {
- $editor = "/usr/bin/vi";
+
+$editor = $ENV{EDITOR} if !$editor;
+$editor = "/usr/bin/vi" if !$editor;
+
+my @editor;
+if ($splitEditor) {
+ @editor = split ' ', $editor;
+} else {
+ @editor = ($editor);
}
my $inChangesToBeCommitted = !isGit();
@@ -124,9 +138,8 @@ if ($regenerateLog && $existingLog && scalar(@changeLogs) > 0) {
$keepExistingLog = 0 if ($key eq "r");
}
-# Don't change anything if there's already a log message
-# (as can happen with git-commit --amend)
-exec $editor, @ARGV if $existingLog && $keepExistingLog;
+# Don't change anything if there's already a log message (as can happen with git-commit --amend).
+exec (@editor, @ARGV) if $existingLog && $keepExistingLog;
my $topLevel = determineVCSRoot();
@@ -248,7 +261,7 @@ if (isGit() && scalar keys %changeLogSort == 0) {
print NEWLOG $logContents;
close NEWLOG;
-system $editor, "$log.edit";
+system (@editor, "$log.edit");
open NEWLOG, "$log.edit" or exit;
my $foundComment = 0;
diff --git a/WebKitTools/Scripts/create-html-entity-table b/WebKitTools/Scripts/create-html-entity-table
new file mode 100755
index 0000000..46c8c52
--- /dev/null
+++ b/WebKitTools/Scripts/create-html-entity-table
@@ -0,0 +1,183 @@
+#!/usr/bin/env python
+# Copyright (c) 2010 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import os.path
+import string
+import sys
+
+# Hack sys.path to avoid executing webkitpy __init__.py code which may
+# use Python 2.5 features. This code needs to run on Python 2.3 in order
+# to support Mac OS X Tiger.
+scripts_directory = sys.path[0]
+sys.path.append("%s/webkitpy/thirdparty" % scripts_directory)
+
+import simplejson
+
+
+def convert_entity_to_cpp_name(entity):
+ postfix = "EntityName"
+ if entity[-1] == ";":
+ return "%sSemicolon%s" % (entity[:-1], postfix)
+ return "%s%s" % (entity, postfix)
+
+
+def convert_entity_to_uchar_array(entity):
+ return "{'%s'}" % "', '".join(entity)
+
+
+def convert_value_to_int(value):
+ assert(value[0] == "U")
+ assert(value[1] == "+")
+ return "0x" + value[2:]
+
+
+def offset_table_entry(offset):
+ return " &staticEntityTable[%s]," % offset
+
+
+program_name = os.path.basename(__file__)
+if len(sys.argv) < 4 or sys.argv[1] != "-o":
+ print >> sys.stderr, "Usage: %s -o OUTPUT_FILE INPUT_FILE" % program_name
+ exit(1)
+
+output_path = sys.argv[2]
+input_path = sys.argv[3]
+
+html_entity_names_file = open(input_path)
+entries = simplejson.load(html_entity_names_file)
+html_entity_names_file.close()
+
+entries = sorted(entries, key=lambda entry: entry['entity'])
+entity_count = len(entries)
+
+output_file = open(output_path, "w")
+
+print >> output_file, """/*
+ * 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:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// THIS FILE IS GENERATED BY WebKitTools/Scripts/create-html-entity-table
+// DO NOT EDIT (unless you are a ninja)!
+
+#include "config.h"
+#include "HTMLEntityTable.h"
+
+namespace WebCore {
+
+namespace {
+"""
+
+for entry in entries:
+ print >> output_file, "const UChar %sEntityName[] = %s;" % (
+ convert_entity_to_cpp_name(entry["entity"]),
+ convert_entity_to_uchar_array(entry["entity"]))
+
+print >> output_file, """
+HTMLEntityTableEntry staticEntityTable[%s] = {""" % entity_count
+
+index = {}
+offset = 0
+for entry in entries:
+ letter = entry["entity"][0]
+ if not index.get(letter):
+ index[letter] = offset
+ print >> output_file, ' { %sEntityName, %s, %s },' % (
+ convert_entity_to_cpp_name(entry["entity"]),
+ len(entry["entity"]),
+ convert_value_to_int(entry["value"]))
+ offset += 1
+
+print >> output_file, """};
+"""
+
+print >> output_file, "const HTMLEntityTableEntry* uppercaseOffset[] = {"
+for letter in string.uppercase:
+ print >> output_file, offset_table_entry(index[letter])
+print >> output_file, offset_table_entry(index['a'])
+print >> output_file, """};
+
+const HTMLEntityTableEntry* lowercaseOffset[] = {"""
+for letter in string.lowercase:
+ print >> output_file, offset_table_entry(index[letter])
+print >> output_file, offset_table_entry(entity_count)
+print >> output_file, """};
+
+}
+
+const HTMLEntityTableEntry* HTMLEntityTable::firstEntryStartingWith(UChar c)
+{
+ if (c >= 'A' && c <= 'Z')
+ return uppercaseOffset[c - 'A'];
+ if (c >= 'a' && c <= 'z')
+ return lowercaseOffset[c - 'a'];
+ return 0;
+}
+
+const HTMLEntityTableEntry* HTMLEntityTable::lastEntryStartingWith(UChar c)
+{
+ if (c >= 'A' && c <= 'Z')
+ return uppercaseOffset[c - 'A' + 1] - 1;
+ if (c >= 'a' && c <= 'z')
+ return lowercaseOffset[c - 'a' + 1] - 1;
+ return 0;
+}
+
+const HTMLEntityTableEntry* HTMLEntityTable::firstEntry()
+{
+ return &staticEntityTable[0];
+}
+
+const HTMLEntityTableEntry* HTMLEntityTable::lastEntry()
+{
+ return &staticEntityTable[%s - 1];
+}
+
+}
+""" % entity_count
diff --git a/WebKitTools/Scripts/deduplicate-tests b/WebKitTools/Scripts/deduplicate-tests
new file mode 100644
index 0000000..f0afe13
--- /dev/null
+++ b/WebKitTools/Scripts/deduplicate-tests
@@ -0,0 +1,84 @@
+#!/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:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""deduplicate-tests -- print test results duplicated between platforms.
+
+If platform/mac-leopard is missing an expected test output, we fall back on
+platform/mac. This means it's possible to grow redundant test outputs,
+where we have the same expected data in both a platform directory and another
+platform it falls back on.
+
+This command dumps out all such files. You can use it like this:
+ deduplicate-tests --verbose # print out the duplicated files
+ deduplicate-tests | xargs git rm # delete them
+"""
+
+
+import optparse
+import webkitpy.common.system.logutils as logutils
+import webkitpy.layout_tests.deduplicate_tests as deduplicate_tests
+
+
+def parse_args():
+ """Provides a default set of command line args.
+
+ Returns a tuple of options, args from optparse"""
+
+ configuration_options = [
+ optparse.make_option("-v", "--verbose", dest="verbose",
+ action="store_true", default=False,
+ help="Verbose output."),
+ optparse.make_option("-g", "--glob", dest="glob_pattern",
+ default="*-expected*",
+ help="Specify the glob to filter the files, defaults to *-expected*."),
+ ]
+
+ option_list = (configuration_options)
+ option_parser = optparse.OptionParser(option_list=option_list)
+
+ options, _ = option_parser.parse_args()
+
+ return options
+
+
+def run(options):
+ logutils.configure_logging()
+ if options.verbose:
+ format = ("* %(test)s\n"
+ "\tredundantly on %(platform)s and %(fallback)s\n"
+ "\tconsider deleting %(path)s")
+ else:
+ format = "%(path)s"
+
+ for dupe in deduplicate_tests.deduplicate(options.glob_pattern):
+ print(format % dupe)
+
+
+def main():
+ options = parse_args()
+ run(options)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/WebKitTools/Scripts/old-run-webkit-tests b/WebKitTools/Scripts/old-run-webkit-tests
index 97ef3dc..68aa6ed 100755
--- a/WebKitTools/Scripts/old-run-webkit-tests
+++ b/WebKitTools/Scripts/old-run-webkit-tests
@@ -105,12 +105,14 @@ sub readSkippedFiles($);
sub recordActualResultsAndDiff($$);
sub sampleDumpTool();
sub setFileHandleNonBlocking(*$);
+sub setUpWindowsCrashLogSaving();
sub slowestcmp($$);
sub splitpath($);
sub stopRunningTestsEarlyIfNeeded();
sub stripExtension($);
sub stripMetrics($$);
sub testCrashedOrTimedOut($$$$$);
+sub toCygwinPath($);
sub toURL($);
sub toWindowsPath($);
sub validateSkippedArg($$;$);
@@ -149,7 +151,7 @@ my $root;
my $runSample = 1;
my $shouldCheckLeaks = 0;
my $showHelp = 0;
-my $stripEditingCallbacks = isCygwin();
+my $stripEditingCallbacks;
my $testHTTP = 1;
my $testWebSocket = 1;
my $testMedia = 1;
@@ -184,6 +186,12 @@ my $prettyDiffTag = "pretty-diff";
my $diffsTag = "diffs";
my $errorTag = "stderr";
+# These are defined here instead of closer to where they are used so that they
+# will always be accessible from the END block that uses them, even if the user
+# presses Ctrl-C before Perl has finished evaluating this whole file.
+my $windowsPostMortemDebuggerKey = "/HKLM/SOFTWARE/Microsoft/Windows NT/CurrentVersion/AeDebug";
+my %previousWindowsPostMortemDebuggerValues;
+
my $realPlatform;
my @macPlatforms = ("mac-tiger", "mac-leopard", "mac-snowleopard", "mac");
@@ -355,11 +363,13 @@ if ($useWebKitTestRunner) {
$realPlatform = $platform;
$platform = "mac-wk2";
} elsif (isAppleWinWebKit()) {
+ $stripEditingCallbacks = 0 unless defined $stripEditingCallbacks;
$realPlatform = $platform;
$platform = "win-wk2";
}
}
+$stripEditingCallbacks = isCygwin() unless defined $stripEditingCallbacks;
my $ignoreSkipped = $treatSkipped eq "ignore";
my $skippedOnly = $treatSkipped eq "only";
@@ -380,6 +390,8 @@ $testMedia = 0 if $shouldCheckLeaks && isTiger();
# Generating remote links causes a lot of unnecessary spew on GTK build bot
$useRemoteLinksToTests = 0 if isGtk();
+setUpWindowsCrashLogSaving() if isCygwin();
+
setConfigurationProductDir(Cwd::abs_path($root)) if (defined($root));
my $productDir = productDir();
$productDir .= "/bin" if isQt();
@@ -1397,6 +1409,7 @@ sub openDumpTool()
} elsif (isCygwin()) {
$CLEAN_ENV{HOMEDRIVE} = $ENV{'HOMEDRIVE'};
$CLEAN_ENV{HOMEPATH} = $ENV{'HOMEPATH'};
+ $CLEAN_ENV{_NT_SYMBOL_PATH} = $ENV{_NT_SYMBOL_PATH};
setPathForRunningWebKitApp(\%CLEAN_ENV);
}
@@ -1719,6 +1732,14 @@ sub convertPathUsingCygpath($$)
return $convertedPath;
}
+sub toCygwinPath($)
+{
+ my ($path) = @_;
+ return unless isCygwin();
+
+ return convertPathUsingCygpath($path, "-u");
+}
+
sub toWindowsPath($)
{
my ($path) = @_;
@@ -2069,6 +2090,10 @@ sub readFromDumpToolWithTimer(**)
}
}
if (defined($lineError)) {
+ if ($lineError =~ /#CRASHED/) {
+ $status = "crashed";
+ last;
+ }
if ($lineError =~ /#EOF/) {
$haveSeenEofError = 1;
} else {
@@ -2345,3 +2370,47 @@ sub stopRunningTestsEarlyIfNeeded()
return 0;
}
+
+sub setUpWindowsCrashLogSaving()
+{
+ return unless isCygwin();
+
+ unless (defined $ENV{_NT_SYMBOL_PATH}) {
+ print "The _NT_SYMBOL_PATH environment variable is not set. Crash logs will not be saved.\nSee <http://trac.webkit.org/wiki/BuildingOnWindows#GettingCrashLogs>.\n";
+ return;
+ }
+
+ my $ntsdPath = File::Spec->catfile(toCygwinPath($ENV{PROGRAMFILES}), "Debugging Tools for Windows (x86)", "ntsd.exe");
+ unless (-f $ntsdPath) {
+ $ntsdPath = File::Spec->catfile(toCygwinPath($ENV{SYSTEMROOT}), "system32", "ntsd.exe");
+ unless (-f $ntsdPath) {
+ print STDERR "Can't find ntsd.exe. Crash logs will not be saved.\nSee <http://trac.webkit.org/wiki/BuildingOnWindows#GettingCrashLogs>.\n";
+ return;
+ }
+ }
+
+ my %values = (
+ Debugger => '"' . toWindowsPath($ntsdPath) . '" -p %ld -e %ld -g -lines -c ".logopen /t \"' . toWindowsPath($testResultsDirectory) . '\CrashLog.txt\";!analyze -vv;~*kpn;q"',
+ Auto => 1
+ );
+
+ foreach my $value (keys %values) {
+ chomp($previousWindowsPostMortemDebuggerValues{$value} = `regtool get "$windowsPostMortemDebuggerKey/$value"`);
+ my $result = system "regtool", "set", "-s", "$windowsPostMortemDebuggerKey/$value", $values{$value};
+ next unless $result;
+
+ print "Failed to set \"$windowsPostMortemDebuggerKey/$value\". Crash logs will not be saved.\nSee <http://trac.webkit.org/wiki/BuildingOnWindows#GettingCrashLogs>.\n";
+ return;
+ }
+
+ print "Crash logs will be saved to $testResultsDirectory.\n";
+}
+
+END {
+ return unless isCygwin();
+
+ foreach my $value (keys %previousWindowsPostMortemDebuggerValues) {
+ my $result = system "regtool", "set", "-s", "$windowsPostMortemDebuggerKey/$value", $previousWindowsPostMortemDebuggerValues{$value};
+ !$result or print "Failed to restore \"$windowsPostMortemDebuggerKey/$value\" to its previous value \"$previousWindowsPostMortemDebuggerValues{$value}\"\n.";
+ }
+}
diff --git a/WebKitTools/Scripts/prepare-ChangeLog b/WebKitTools/Scripts/prepare-ChangeLog
index 1488939..45aca1b 100755
--- a/WebKitTools/Scripts/prepare-ChangeLog
+++ b/WebKitTools/Scripts/prepare-ChangeLog
@@ -433,11 +433,16 @@ if ($spewDiff && @changed_files) {
# Open ChangeLogs.
if ($openChangeLogs && @logs) {
print STDERR " Opening the edited ChangeLog files.\n";
- my $editor = $ENV{"CHANGE_LOG_EDIT_APPLICATION"};
+ my $editor = $ENV{CHANGE_LOG_EDITOR};
if ($editor) {
- system "open", "-a", $editor, @logs;
+ system ((split ' ', $editor), @logs);
} else {
- system "open", "-e", @logs;
+ $editor = $ENV{CHANGE_LOG_EDIT_APPLICATION};
+ if ($editor) {
+ system "open", "-a", $editor, @logs;
+ } else {
+ system "open", "-e", @logs;
+ }
}
}
diff --git a/WebKitTools/Scripts/webkit-patch b/WebKitTools/Scripts/webkit-patch
index 8300b9f..007f919 100755
--- a/WebKitTools/Scripts/webkit-patch
+++ b/WebKitTools/Scripts/webkit-patch
@@ -38,6 +38,7 @@ import sys
from webkitpy.common.system.logutils import configure_logging
import webkitpy.python24.versioning as versioning
+_log = logging.getLogger("webkit-patch")
def main():
# This is a hack to let us enable DEBUG logging as early as possible.
@@ -50,6 +51,11 @@ def main():
configure_logging(logging_level=logging_level)
versioning.check_version()
+
+ if sys.platform == "win32":
+ _log.fatal("webkit-patch is only supported under Cygwin Python, "
+ "not Win32 Python")
+ sys.exit(1)
# Import webkit-patch code only after version-checking so that
# script doesn't error out before having a chance to report the
diff --git a/WebKitTools/Scripts/webkitdirs.pm b/WebKitTools/Scripts/webkitdirs.pm
index 028d63d..6530244 100644
--- a/WebKitTools/Scripts/webkitdirs.pm
+++ b/WebKitTools/Scripts/webkitdirs.pm
@@ -1302,6 +1302,21 @@ sub autotoolsFlag($$)
return $prefix . '-' . $feature;
}
+sub autogenArgumentsHaveChanged($@)
+{
+ my ($filename, @currentArguments) = @_;
+
+ if (! -e $filename) {
+ return 1;
+ }
+
+ open(AUTOTOOLS_ARGUMENTS, $filename);
+ chomp(my $previousArguments = <AUTOTOOLS_ARGUMENTS>);
+ close(AUTOTOOLS_ARGUMENTS);
+
+ return $previousArguments ne join(" ", @currentArguments);
+}
+
sub buildAutotoolsProject($@)
{
my ($clean, @buildParams) = @_;
@@ -1357,8 +1372,16 @@ sub buildAutotoolsProject($@)
# If GNUmakefile exists, don't run autogen.sh. The makefile should be
# smart enough to track autotools dependencies and re-run autogen.sh
# when build files change.
+ my $autogenArgumentsFile = "previous-autogen-arguments.txt";
my $result;
- if (! -e "GNUmakefile") {
+ if (!(-e "GNUmakefile") or autogenArgumentsHaveChanged($autogenArgumentsFile, @buildArgs)) {
+
+ # Write autogen.sh arguments to a file so that we can detect
+ # when they change and automatically re-run it.
+ open(AUTOTOOLS_ARGUMENTS, ">$autogenArgumentsFile");
+ print AUTOTOOLS_ARGUMENTS join(" ", @buildArgs);
+ close(AUTOTOOLS_ARGUMENTS);
+
print "Calling configure in " . $dir . "\n\n";
print "Installation prefix directory: $prefix\n" if(defined($prefix));
diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/scm.py b/WebKitTools/Scripts/webkitpy/common/checkout/scm.py
index 569558a..5a6c48c 100644
--- a/WebKitTools/Scripts/webkitpy/common/checkout/scm.py
+++ b/WebKitTools/Scripts/webkitpy/common/checkout/scm.py
@@ -38,6 +38,40 @@ from webkitpy.common.system.executive import Executive, run_command, ScriptError
from webkitpy.common.system.deprecated_logging import error, log
+def find_checkout_root():
+ """Returns the current checkout root (as determined by default_scm().
+
+ Returns the absolute path to the top of the WebKit checkout, or None
+ if it cannot be determined.
+
+ """
+ scm_system = default_scm()
+ if scm_system:
+ return scm_system.checkout_root
+ return None
+
+
+def default_scm():
+ """Return the default SCM object as determined by the CWD and running code.
+
+ Returns the default SCM object for the current working directory; if the
+ CWD is not in a checkout, then we attempt to figure out if the SCM module
+ itself is part of a checkout, and return that one. If neither is part of
+ a checkout, None is returned.
+
+ """
+ cwd = os.getcwd()
+ scm_system = detect_scm_system(cwd)
+ if not scm_system:
+ script_directory = os.path.abspath(sys.path[0])
+ scm_system = detect_scm_system(script_directory)
+ if scm_system:
+ log("The current directory (%s) is not a WebKit checkout, using %s" % (cwd, scm_system.checkout_root))
+ else:
+ error("FATAL: Failed to determine the SCM system for either %s or %s" % (cwd, script_directory))
+ return scm_system
+
+
def detect_scm_system(path):
absolute_path = os.path.abspath(path)
diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py b/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py
index 852f838..87d5539 100644
--- a/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py
@@ -36,6 +36,7 @@ import os
import os.path
import re
import stat
+import sys
import subprocess
import tempfile
import unittest
@@ -44,10 +45,11 @@ import shutil
from datetime import date
from webkitpy.common.checkout.api import Checkout
-from webkitpy.common.checkout.scm import detect_scm_system, SCM, SVN, CheckoutNeedsUpdate, commit_error_handler, AuthenticationError, AmbiguousCommitError
+from webkitpy.common.checkout.scm import detect_scm_system, SCM, SVN, CheckoutNeedsUpdate, commit_error_handler, AuthenticationError, AmbiguousCommitError, find_checkout_root, default_scm
from webkitpy.common.config.committers import Committer # FIXME: This should not be needed
from webkitpy.common.net.bugzilla import Attachment # FIXME: This should not be needed
from webkitpy.common.system.executive import Executive, run_command, ScriptError
+from webkitpy.common.system.outputcapture import OutputCapture
# Eventually we will want to write tests which work for both scms. (like update_webkit, changed_files, etc.)
# Perhaps through some SCMTest base-class which both SVNTest and GitTest inherit from.
@@ -174,6 +176,57 @@ class SVNTestRepository:
# Change back to a valid directory so that later calls to os.getcwd() do not fail.
os.chdir(detect_scm_system(os.path.dirname(__file__)).checkout_root)
+
+class StandaloneFunctionsTest(unittest.TestCase):
+ """This class tests any standalone/top-level functions in the package."""
+ def setUp(self):
+ self.orig_cwd = os.path.abspath(os.getcwd())
+ self.orig_abspath = os.path.abspath
+
+ # We capture but ignore the output from stderr to reduce unwanted
+ # logging.
+ self.output = OutputCapture()
+ self.output.capture_output()
+
+ def tearDown(self):
+ os.chdir(self.orig_cwd)
+ os.path.abspath = self.orig_abspath
+ self.output.restore_output()
+
+ def test_find_checkout_root(self):
+ # Test from inside the tree.
+ os.chdir(sys.path[0])
+ dir = find_checkout_root()
+ self.assertNotEqual(dir, None)
+ self.assertTrue(os.path.exists(dir))
+
+ # Test from outside the tree.
+ os.chdir(os.path.expanduser("~"))
+ dir = find_checkout_root()
+ self.assertNotEqual(dir, None)
+ self.assertTrue(os.path.exists(dir))
+
+ # Mock out abspath() to test being not in a checkout at all.
+ os.path.abspath = lambda x: "/"
+ self.assertRaises(SystemExit, find_checkout_root)
+ os.path.abspath = self.orig_abspath
+
+ def test_default_scm(self):
+ # Test from inside the tree.
+ os.chdir(sys.path[0])
+ scm = default_scm()
+ self.assertNotEqual(scm, None)
+
+ # Test from outside the tree.
+ os.chdir(os.path.expanduser("~"))
+ dir = find_checkout_root()
+ self.assertNotEqual(dir, None)
+
+ # Mock out abspath() to test being not in a checkout at all.
+ os.path.abspath = lambda x: "/"
+ self.assertRaises(SystemExit, default_scm)
+ os.path.abspath = self.orig_abspath
+
# For testing the SCM baseclass directly.
class SCMClassTests(unittest.TestCase):
def setUp(self):
diff --git a/WebKitTools/Scripts/webkitpy/common/config/committers.py b/WebKitTools/Scripts/webkitpy/common/config/committers.py
index 5ebf18a..7543d69 100644
--- a/WebKitTools/Scripts/webkitpy/common/config/committers.py
+++ b/WebKitTools/Scripts/webkitpy/common/config/committers.py
@@ -139,6 +139,7 @@ committers_unable_to_review = [
Committer("Krzysztof Kowalczyk", "kkowalczyk@gmail.com"),
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"),
Committer("Luiz Agostini", ["luiz@webkit.org", "luiz.agostini@openbossa.org"], "lca"),
Committer("Mads Ager", "ager@chromium.org"),
Committer("Marcus Voltis Bulach", "bulach@chromium.org"),
@@ -146,7 +147,6 @@ committers_unable_to_review = [
Committer("Matt Perry", "mpcomplete@chromium.org"),
Committer("Maxime Britto", ["maxime.britto@gmail.com", "britto@apple.com"]),
Committer("Maxime Simon", ["simon.maxime@gmail.com", "maxime.simon@webkit.org"], "maxime.simon"),
- Committer("Martin Robinson", ["mrobinson@igalia.com", "mrobinson@webkit.org", "martin.james.robinson@gmail.com"], "mrobinson"),
Committer("Michelangelo De Simone", "michelangelo@webkit.org", "michelangelo"),
Committer("Mike Belshe", ["mbelshe@chromium.org", "mike@belshe.com"]),
Committer("Mike Fenton", ["mifenton@rim.com", "mike.fenton@torchmobile.com"], "mfenton"),
@@ -179,7 +179,7 @@ committers_unable_to_review = [
Committer("Yuzo Fujishima", "yuzo@google.com", "yuzo"),
Committer("Zhenyao Mo", "zmo@google.com", "zhenyao"),
Committer("Zoltan Herczeg", "zherczeg@webkit.org", "zherczeg"),
- Committer("Zoltan Horvath", "zoltan@webkit.org", "zoltan"),
+ Committer("Zoltan Horvath", ["zoltan@webkit.org", "hzoltan@inf.u-szeged.hu", "horvath.zoltan.6@stud.u-szeged.hu"], "zoltan"),
]
@@ -200,7 +200,7 @@ reviewers_list = [
Reviewer("Anders Carlsson", ["andersca@apple.com", "acarlsson@apple.com"], "andersca"),
Reviewer("Antonio Gomes", "tonikitoo@webkit.org", "tonikitoo"),
Reviewer("Antti Koivisto", ["koivisto@iki.fi", "antti@apple.com"], "anttik"),
- Reviewer("Ariya Hidayat", ["ariya.hidayat@gmail.com", "ariya@webkit.org"], "ariya"),
+ Reviewer("Ariya Hidayat", ["ariya@sencha.com", "ariya.hidayat@gmail.com", "ariya@webkit.org"], "ariya"),
Reviewer("Beth Dakin", "bdakin@apple.com", "dethbakin"),
Reviewer("Brady Eidson", "beidson@apple.com", "bradee-oh"),
Reviewer("Cameron Zwarich", ["zwarich@apple.com", "cwzwarich@apple.com", "cwzwarich@webkit.org"]),
@@ -225,7 +225,7 @@ reviewers_list = [
Reviewer("Gavin Barraclough", "barraclough@apple.com", "gbarra"),
Reviewer("Geoffrey Garen", "ggaren@apple.com", "ggaren"),
Reviewer("George Staikos", ["staikos@kde.org", "staikos@webkit.org"]),
- Reviewer("Gustavo Noronha Silva", ["gns@gnome.org", "kov@webkit.org"], "kov"),
+ Reviewer("Gustavo Noronha Silva", ["gns@gnome.org", "kov@webkit.org", "gustavo.noronha@collabora.co.uk"], "kov"),
Reviewer("Holger Freyther", ["zecke@selfish.org", "zecke@webkit.org"], "zecke"),
Reviewer("Jan Alonzo", ["jmalonzo@gmail.com", "jmalonzo@webkit.org"], "janm"),
Reviewer("Jeremy Orlow", "jorlow@chromium.org", "jorlow"),
@@ -244,6 +244,7 @@ reviewers_list = [
Reviewer("Laszlo Gombos", "laszlo.1.gombos@nokia.com", "lgombos"),
Reviewer("Maciej Stachowiak", "mjs@apple.com", "othermaciej"),
Reviewer("Mark Rowe", "mrowe@apple.com", "bdash"),
+ Reviewer("Martin Robinson", ["mrobinson@igalia.com", "mrobinson@webkit.org", "martin.james.robinson@gmail.com"], "mrobinson"),
Reviewer("Nate Chapin", "japhet@chromium.org", "japhet"),
Reviewer("Nikolas Zimmermann", ["zimmermann@kde.org", "zimmermann@physik.rwth-aachen.de", "zimmermann@webkit.org"], "wildfox"),
Reviewer("Ojan Vafai", "ojan@chromium.org", "ojan"),
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests.py b/WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests.py
new file mode 100644
index 0000000..bb63f5e
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests.py
@@ -0,0 +1,167 @@
+#!/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:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""deduplicate_tests -- lists duplicated between platforms.
+
+If platform/mac-leopard is missing an expected test output, we fall back on
+platform/mac. This means it's possible to grow redundant test outputs,
+where we have the same expected data in both a platform directory and another
+platform it falls back on.
+"""
+
+import collections
+import fnmatch
+import os
+import subprocess
+import sys
+import re
+import webkitpy.common.system.executive as executive
+import webkitpy.common.system.logutils as logutils
+import webkitpy.layout_tests.port.factory as port_factory
+
+_log = logutils.get_logger(__file__)
+
+_BASE_PLATFORM = 'base'
+
+
+def port_fallbacks():
+ """Get the port fallback information.
+ Returns:
+ A dictionary mapping platform name to a list of other platforms to fall
+ back on. All platforms fall back on 'base'.
+ """
+ fallbacks = {_BASE_PLATFORM: []}
+ for port_name in os.listdir(os.path.join('LayoutTests', 'platform')):
+ try:
+ platforms = port_factory.get(port_name).baseline_search_path()
+ except NotImplementedError:
+ _log.error("'%s' lacks baseline_search_path(), please fix." % port_name)
+ fallbacks[port_name] = [_BASE_PLATFORM]
+ continue
+ fallbacks[port_name] = [os.path.basename(p) for p in platforms][1:]
+ fallbacks[port_name].append(_BASE_PLATFORM)
+ return fallbacks
+
+
+def parse_git_output(git_output, glob_pattern):
+ """Parses the output of git ls-tree and filters based on glob_pattern.
+ Args:
+ git_output: result of git ls-tree -r HEAD LayoutTests.
+ glob_pattern: a pattern to filter the files.
+ Returns:
+ A dictionary mapping (test name, hash of content) => [paths]
+ """
+ hashes = collections.defaultdict(set)
+ for line in git_output.split('\n'):
+ if not line:
+ break
+ attrs, path = line.strip().split('\t')
+ if not fnmatch.fnmatch(path, glob_pattern):
+ continue
+ path = path[len('LayoutTests/'):]
+ match = re.match(r'^(platform/.*?/)?(.*)', path)
+ test = match.group(2)
+ _, _, hash = attrs.split(' ')
+ hashes[(test, hash)].add(path)
+ return hashes
+
+
+def cluster_file_hashes(glob_pattern):
+ """Get the hashes of all the test expectations in the tree.
+ We cheat and use git's hashes.
+ Args:
+ glob_pattern: a pattern to filter the files.
+ Returns:
+ A dictionary mapping (test name, hash of content) => [paths]
+ """
+
+ # A map of file hash => set of all files with that hash.
+ hashes = collections.defaultdict(set)
+
+ # Fill in the map.
+ cmd = ('git', 'ls-tree', '-r', 'HEAD', 'LayoutTests')
+ try:
+ git_output = executive.Executive().run_command(cmd)
+ except OSError, e:
+ if e.errno == 2: # No such file or directory.
+ _log.error("Error: 'No such file' when running git.")
+ _log.error("This script requires git.")
+ sys.exit(1)
+ raise e
+ return parse_git_output(git_output, glob_pattern)
+
+
+def extract_platforms(paths):
+ """Extracts the platforms from a list of paths matching ^platform/(.*?)/.
+ Args:
+ paths: a list of paths.
+ Returns:
+ A dictionary containing all platforms from paths.
+ """
+ platforms = {}
+ for path in paths:
+ match = re.match(r'^platform/(.*?)/', path)
+ if match:
+ platform = match.group(1)
+ else:
+ platform = _BASE_PLATFORM
+ platforms[platform] = path
+ return platforms
+
+
+def find_dups(hashes, port_fallbacks):
+ """Yields info about redundant test expectations.
+ Args:
+ hashes: a list of hashes as returned by cluster_file_hashes.
+ port_fallbacks: a list of fallback information as returned by get_port_fallbacks.
+ Returns:
+ a tuple containing (test, platform, fallback, platforms)
+ """
+ for (test, hash), cluster in hashes.items():
+ if len(cluster) < 2:
+ continue # Common case: only one file with that hash.
+
+ # Compute the list of platforms we have this particular hash for.
+ platforms = extract_platforms(cluster)
+ if len(platforms) == 1:
+ continue
+
+ # See if any of the platforms are redundant with each other.
+ for platform in platforms.keys():
+ for fallback in port_fallbacks[platform]:
+ if fallback in platforms.keys():
+ yield test, platform, fallback, platforms[platform]
+
+
+def deduplicate(glob_pattern):
+ """Traverses LayoutTests and returns information about duplicated files.
+ Args:
+ glob pattern to filter the files in LayoutTests.
+ Returns:
+ a dictionary containing test, path, platform and fallback.
+ """
+ fallbacks = port_fallbacks()
+ hashes = cluster_file_hashes(glob_pattern)
+ return [{'test': test, 'path': path, 'platform': platform, 'fallback': fallback}
+ for test, platform, fallback, path in find_dups(hashes, fallbacks)]
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests_unittest.py
new file mode 100644
index 0000000..66dda32
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests_unittest.py
@@ -0,0 +1,147 @@
+#!/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:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Unit tests for deduplicate_tests.py."""
+
+import deduplicate_tests
+import os
+import unittest
+import webkitpy.common.checkout.scm as scm
+
+
+class MockExecutive(object):
+ last_run_command = []
+ response = ''
+
+ class Executive(object):
+ def run_command(self,
+ args,
+ cwd=None,
+ input=None,
+ error_handler=None,
+ return_exit_code=False,
+ return_stderr=True,
+ decode_output=True):
+ MockExecutive.last_run_command += [args]
+ return MockExecutive.response
+
+
+class ListDuplicatesTest(unittest.TestCase):
+ def setUp(self):
+ MockExecutive.last_run_command = []
+ MockExecutive.response = ''
+ deduplicate_tests.executive = MockExecutive
+ self._original_cwd = os.getcwd()
+ checkout_root = scm.find_checkout_root()
+ self.assertNotEqual(checkout_root, None)
+ os.chdir(checkout_root)
+
+ def tearDown(self):
+ os.chdir(self._original_cwd)
+
+ def test_parse_git_output(self):
+ git_output = (
+ '100644 blob 5053240b3353f6eb39f7cb00259785f16d121df2\tLayoutTests/mac/foo-expected.txt\n'
+ '100644 blob a004548d107ecc4e1ea08019daf0a14e8634a1ff\tLayoutTests/platform/chromium/foo-expected.txt\n'
+ '100644 blob d6bb0bc762e3aec5df03b5c04485b2cb3b65ffb1\tLayoutTests/platform/chromium-linux/foo-expected.txt\n'
+ '100644 blob abcdebc762e3aec5df03b5c04485b2cb3b65ffb1\tLayoutTests/platform/chromium-linux/animage.png\n'
+ '100644 blob d6bb0bc762e3aec5df03b5c04485b2cb3b65ffb1\tLayoutTests/platform/chromium-win/foo-expected.txt\n'
+ '100644 blob abcdebc762e3aec5df03b5c04485b2cb3b65ffb1\tLayoutTests/platform/chromium-win/animage.png\n'
+ '100644 blob 4303df5389ca87cae83dd3236b8dd84e16606517\tLayoutTests/platform/mac/foo-expected.txt\n')
+ hashes = deduplicate_tests.parse_git_output(git_output, '*')
+ expected = {('mac/foo-expected.txt', '5053240b3353f6eb39f7cb00259785f16d121df2'): set(['mac/foo-expected.txt']),
+ ('animage.png', 'abcdebc762e3aec5df03b5c04485b2cb3b65ffb1'): set(['platform/chromium-linux/animage.png', 'platform/chromium-win/animage.png']),
+ ('foo-expected.txt', '4303df5389ca87cae83dd3236b8dd84e16606517'): set(['platform/mac/foo-expected.txt']),
+ ('foo-expected.txt', 'd6bb0bc762e3aec5df03b5c04485b2cb3b65ffb1'): set(['platform/chromium-linux/foo-expected.txt', 'platform/chromium-win/foo-expected.txt']),
+ ('foo-expected.txt', 'a004548d107ecc4e1ea08019daf0a14e8634a1ff'): set(['platform/chromium/foo-expected.txt'])}
+ self.assertEquals(expected, hashes)
+
+ hashes = deduplicate_tests.parse_git_output(git_output, '*.png')
+ expected = {('animage.png', 'abcdebc762e3aec5df03b5c04485b2cb3b65ffb1'): set(['platform/chromium-linux/animage.png', 'platform/chromium-win/animage.png'])}
+ self.assertEquals(expected, hashes)
+
+ def test_extract_platforms(self):
+ self.assertEquals({'foo': 'platform/foo/bar',
+ 'zoo': 'platform/zoo/com'},
+ deduplicate_tests.extract_platforms(['platform/foo/bar', 'platform/zoo/com']))
+ self.assertEquals({'foo': 'platform/foo/bar',
+ deduplicate_tests._BASE_PLATFORM: 'what/'},
+ deduplicate_tests.extract_platforms(['platform/foo/bar', 'what/']))
+
+ def test_unique(self):
+ MockExecutive.response = (
+ '100644 blob 5053240b3353f6eb39f7cb00259785f16d121df2\tLayoutTests/mac/foo-expected.txt\n'
+ '100644 blob a004548d107ecc4e1ea08019daf0a14e8634a1ff\tLayoutTests/platform/chromium/foo-expected.txt\n'
+ '100644 blob abcd0bc762e3aec5df03b5c04485b2cb3b65ffb1\tLayoutTests/platform/chromium-linux/foo-expected.txt\n'
+ '100644 blob d6bb0bc762e3aec5df03b5c04485b2cb3b65ffb1\tLayoutTests/platform/chromium-win/foo-expected.txt\n'
+ '100644 blob 4303df5389ca87cae83dd3236b8dd84e16606517\tLayoutTests/platform/mac/foo-expected.txt\n')
+ result = deduplicate_tests.deduplicate('*')
+ self.assertEquals(1, len(MockExecutive.last_run_command))
+ self.assertEquals(('git', 'ls-tree', '-r', 'HEAD', 'LayoutTests'), MockExecutive.last_run_command[-1])
+ self.assertEquals(0, len(result))
+
+ def test_duplicates(self):
+ MockExecutive.response = (
+ '100644 blob 5053240b3353f6eb39f7cb00259785f16d121df2\tLayoutTests/mac/foo-expected.txt\n'
+ '100644 blob a004548d107ecc4e1ea08019daf0a14e8634a1ff\tLayoutTests/platform/chromium/foo-expected.txt\n'
+ '100644 blob d6bb0bc762e3aec5df03b5c04485b2cb3b65ffb1\tLayoutTests/platform/chromium-linux/foo-expected.txt\n'
+ '100644 blob abcdebc762e3aec5df03b5c04485b2cb3b65ffb1\tLayoutTests/platform/chromium-linux/animage.png\n'
+ '100644 blob d6bb0bc762e3aec5df03b5c04485b2cb3b65ffb1\tLayoutTests/platform/chromium-win/foo-expected.txt\n'
+ '100644 blob abcdebc762e3aec5df03b5c04485b2cb3b65ffb1\tLayoutTests/platform/chromium-win/animage.png\n'
+ '100644 blob 4303df5389ca87cae83dd3236b8dd84e16606517\tLayoutTests/platform/mac/foo-expected.txt\n')
+
+ result = deduplicate_tests.deduplicate('*')
+ self.assertEquals(1, len(MockExecutive.last_run_command))
+ self.assertEquals(('git', 'ls-tree', '-r', 'HEAD', 'LayoutTests'), MockExecutive.last_run_command[-1])
+ self.assertEquals(2, len(result))
+ self.assertEquals({'test': 'animage.png',
+ 'path': 'platform/chromium-linux/animage.png',
+ 'fallback': 'chromium-win',
+ 'platform': 'chromium-linux'},
+ result[0])
+ self.assertEquals({'test': 'foo-expected.txt',
+ 'path': 'platform/chromium-linux/foo-expected.txt',
+ 'fallback': 'chromium-win',
+ 'platform': 'chromium-linux'},
+ result[1])
+
+ result = deduplicate_tests.deduplicate('*.txt')
+ self.assertEquals(2, len(MockExecutive.last_run_command))
+ self.assertEquals(('git', 'ls-tree', '-r', 'HEAD', 'LayoutTests'), MockExecutive.last_run_command[-1])
+ self.assertEquals(1, len(result))
+ self.assertEquals({'test': 'foo-expected.txt',
+ 'path': 'platform/chromium-linux/foo-expected.txt',
+ 'fallback': 'chromium-win',
+ 'platform': 'chromium-linux'},
+ result[0])
+
+ result = deduplicate_tests.deduplicate('*.png')
+ self.assertEquals(3, len(MockExecutive.last_run_command))
+ self.assertEquals(('git', 'ls-tree', '-r', 'HEAD', 'LayoutTests'), MockExecutive.last_run_command[-1])
+ self.assertEquals(1, len(result))
+ self.assertEquals({'test': 'animage.png',
+ 'path': 'platform/chromium-linux/animage.png',
+ 'fallback': 'chromium-win',
+ 'platform': 'chromium-linux'},
+ result[0])
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py
index 6364511..6343400 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py
@@ -54,9 +54,9 @@ _log = logging.getLogger("webkitpy.layout_tests.layout_package."
"dump_render_tree_thread")
-def process_output(port, test_info, test_types, test_args, configuration,
- output_dir, crash, timeout, test_run_time, actual_checksum,
- output, error):
+def _process_output(port, test_info, test_types, test_args, configuration,
+ output_dir, crash, timeout, test_run_time, actual_checksum,
+ output, error):
"""Receives the output from a DumpRenderTree process, subjects it to a
number of tests, and returns a list of failure types the test produced.
@@ -118,6 +118,21 @@ def process_output(port, test_info, test_types, test_args, configuration,
total_time_for_all_diffs, time_for_diffs)
+def _pad_timeout(timeout):
+ """Returns a safe multiple of the per-test timeout value to use
+ to detect hung test threads.
+
+ """
+ # When we're running one test per DumpRenderTree process, we can
+ # enforce a hard timeout. The DumpRenderTree watchdog uses 2.5x
+ # the timeout; we want to be larger than that.
+ return timeout * 3
+
+
+def _milliseconds_to_seconds(msecs):
+ return float(msecs) / 1000.0
+
+
class TestResult(object):
def __init__(self, filename, failures, test_run_time,
@@ -162,7 +177,7 @@ class SingleTestThread(threading.Thread):
driver.run_test(test_info.uri.strip(), test_info.timeout,
test_info.image_hash())
end = time.time()
- self._test_result = process_output(self._port,
+ self._test_result = _process_output(self._port,
test_info, self._test_types, self._test_args,
self._configuration, self._output_dir, crash, timeout, end - start,
actual_checksum, output, error)
@@ -172,8 +187,42 @@ class SingleTestThread(threading.Thread):
return self._test_result
-class TestShellThread(threading.Thread):
+class WatchableThread(threading.Thread):
+ """This class abstracts an interface used by
+ run_webkit_tests.TestRunner._wait_for_threads_to_finish for thread
+ management."""
+ def __init__(self):
+ threading.Thread.__init__(self)
+ self._canceled = False
+ self._exception_info = None
+ self._next_timeout = None
+ self._thread_id = None
+
+ def cancel(self):
+ """Set a flag telling this thread to quit."""
+ self._canceled = True
+
+ def clear_next_timeout(self):
+ """Mark a flag telling this thread to stop setting timeouts."""
+ self._timeout = 0
+
+ def exception_info(self):
+ """If run() terminated on an uncaught exception, return it here
+ ((type, value, traceback) tuple).
+ Returns None if run() terminated normally. Meant to be called after
+ joining this thread."""
+ return self._exception_info
+
+ def id(self):
+ """Return a thread identifier."""
+ return self._thread_id
+
+ def next_timeout(self):
+ """Return the time the test is supposed to finish by."""
+ return self._next_timeout
+
+class TestShellThread(WatchableThread):
def __init__(self, port, filename_list_queue, result_queue,
test_types, test_args, image_path, shell_args, options):
"""Initialize all the local state for this DumpRenderTree thread.
@@ -192,7 +241,7 @@ class TestShellThread(threading.Thread):
command-line options should match those expected by
run_webkit_tests; they are typically passed via the
run_webkit_tests.TestRunner class."""
- threading.Thread.__init__(self)
+ WatchableThread.__init__(self)
self._port = port
self._filename_list_queue = filename_list_queue
self._result_queue = result_queue
@@ -203,8 +252,6 @@ class TestShellThread(threading.Thread):
self._image_path = image_path
self._shell_args = shell_args
self._options = options
- self._canceled = False
- self._exception_info = None
self._directory_timing_stats = {}
self._test_results = []
self._num_tests = 0
@@ -231,17 +278,6 @@ class TestShellThread(threading.Thread):
"""
return self._test_results
- def cancel(self):
- """Set a flag telling this thread to quit."""
- self._canceled = True
-
- def get_exception_info(self):
- """If run() terminated on an uncaught exception, return it here
- ((type, value, traceback) tuple).
- Returns None if run() terminated normally. Meant to be called after
- joining this thread."""
- return self._exception_info
-
def get_total_time(self):
return max(self._stop_time - self._start_time, 0.0)
@@ -251,6 +287,7 @@ class TestShellThread(threading.Thread):
def run(self):
"""Delegate main work to a helper method and watch for uncaught
exceptions."""
+ self._thread_id = thread.get_ident()
self._start_time = time.time()
self._num_tests = 0
try:
@@ -384,10 +421,10 @@ class TestShellThread(threading.Thread):
worker.start()
- # When we're running one test per DumpRenderTree process, we can
- # enforce a hard timeout. The DumpRenderTree watchdog uses 2.5x
- # the timeout; we want to be larger than that.
- worker.join(int(test_info.timeout) * 3.0 / 1000.0)
+ thread_timeout = _milliseconds_to_seconds(
+ _pad_timeout(test_info.timeout))
+ thread._next_timeout = time.time() + thread_timeout
+ worker.join(thread_timeout)
if worker.isAlive():
# If join() returned with the thread still running, the
# DumpRenderTree is completely hung and there's nothing
@@ -433,11 +470,16 @@ class TestShellThread(threading.Thread):
not self._options.pixel_tests)):
image_hash = ""
start = time.time()
+
+ thread_timeout = _milliseconds_to_seconds(
+ _pad_timeout(test_info.timeout))
+ self._next_timeout = start + thread_timeout
+
crash, timeout, actual_checksum, output, error = \
self._driver.run_test(test_info.uri, test_info.timeout, image_hash)
end = time.time()
- result = process_output(self._port, test_info, self._test_types,
+ result = _process_output(self._port, test_info, self._test_types,
self._test_args, self._options.configuration,
self._options.results_directory, crash,
timeout, end - start, actual_checksum,
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py
index 6c36c93..c6c3066 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py
@@ -57,7 +57,7 @@ class JSONLayoutResultsGenerator(json_results_generator.JSONResultsGeneratorBase
def __init__(self, port, builder_name, build_name, build_number,
results_file_base_path, builder_base_url,
test_timings, expectations, result_summary, all_tests,
- generate_incremental_results=False):
+ generate_incremental_results=False, test_results_server=None):
"""Modifies the results.json file. Grabs it off the archive directory
if it is not found locally.
@@ -68,7 +68,7 @@ class JSONLayoutResultsGenerator(json_results_generator.JSONResultsGeneratorBase
super(JSONLayoutResultsGenerator, self).__init__(
builder_name, build_name, build_number, results_file_base_path,
builder_base_url, {}, port.test_repository_paths(),
- generate_incremental_results)
+ generate_incremental_results, test_results_server)
self._port = port
self._expectations = expectations
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py
index e746bc0..15eceee 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py
@@ -84,10 +84,14 @@ class JSONResultsGeneratorBase(object):
RESULTS_FILENAME = "results.json"
INCREMENTAL_RESULTS_FILENAME = "incremental_results.json"
+ URL_FOR_TEST_LIST_JSON = \
+ "http://%s/testfile?builder=%s&name=%s&testlistjson=1"
+
def __init__(self, builder_name, build_name, build_number,
results_file_base_path, builder_base_url,
test_results_map, svn_repositories=None,
- generate_incremental_results=False):
+ generate_incremental_results=False,
+ test_results_server=None):
"""Modifies the results.json file. Grabs it off the archive directory
if it is not found locally.
@@ -103,6 +107,9 @@ class JSONResultsGeneratorBase(object):
svn_repositories: A (json_field_name, svn_path) pair for SVN
repositories that tests rely on. The SVN revision will be
included in the JSON with the given json_field_name.
+ generate_incremental_results: If true, generate incremental json file
+ from current run results.
+ test_results_server: server that hosts test results json.
"""
self._builder_name = builder_name
self._build_name = build_name
@@ -121,6 +128,8 @@ class JSONResultsGeneratorBase(object):
if not self._svn_repositories:
self._svn_repositories = {}
+ self._test_results_server = test_results_server
+
self._json = None
self._archived_results = None
@@ -144,25 +153,24 @@ class JSONResultsGeneratorBase(object):
def get_json(self, incremental=False):
"""Gets the results for the results.json file."""
- if incremental:
- results_json = {}
- else:
+ results_json = {}
+ if not incremental:
if self._json:
return self._json
- if not self._archived_results:
- self._archived_results, error = \
- self._get_archived_json_results()
- if error:
- # If there was an error don't write a results.json
- # file at all as it would lose all the information on the
- # bot.
- _log.error("Archive directory is inaccessible. Not "
- "modifying or clobbering the results.json "
- "file: " + str(error))
- return None
+ if self._archived_results:
+ results_json = self._archived_results
- results_json = self._archived_results
+ if not results_json:
+ results_json, error = self._get_archived_json_results(incremental)
+ if error:
+ # If there was an error don't write a results.json
+ # file at all as it would lose all the information on the
+ # bot.
+ _log.error("Archive directory is inaccessible. Not "
+ "modifying or clobbering the results.json "
+ "file: " + str(error))
+ return None
builder_name = self._builder_name
if results_json and builder_name not in results_json:
@@ -186,7 +194,7 @@ class JSONResultsGeneratorBase(object):
all_failing_tests = self._get_failed_test_names()
all_failing_tests.update(tests.iterkeys())
for test in all_failing_tests:
- self._insert_test_time_and_result(test, tests)
+ self._insert_test_time_and_result(test, tests, incremental)
return results_json
@@ -253,24 +261,40 @@ class JSONResultsGeneratorBase(object):
return ""
return ""
- def _get_archived_json_results(self):
+ def _get_archived_json_results(self, for_incremental=False):
"""Reads old results JSON file if it exists.
Returns (archived_results, error) tuple where error is None if results
were successfully read.
+
+ if for_incremental is True, download JSON file that only contains test
+ name list from test-results server. This is for generating incremental
+ JSON so the file generated has info for tests that failed before but
+ pass or are skipped from current run.
"""
results_json = {}
old_results = None
error = None
- if os.path.exists(self._results_file_path):
+ if os.path.exists(self._results_file_path) and not for_incremental:
with codecs.open(self._results_file_path, "r", "utf-8") as file:
old_results = file.read()
- elif self._builder_base_url:
- # Check if we have the archived JSON file on the buildbot server.
- results_file_url = (self._builder_base_url +
- self._build_name + "/" + self.RESULTS_FILENAME)
- _log.error("Local results.json file does not exist. Grabbing "
- "it off the archive at " + results_file_url)
+ elif self._builder_base_url or for_incremental:
+ if for_incremental:
+ if not self._test_results_server:
+ # starting from fresh if no test results server specified.
+ return {}, None
+
+ results_file_url = (self.URL_FOR_TEST_LIST_JSON %
+ (urllib2.quote(self._test_results_server),
+ urllib2.quote(self._builder_name),
+ self.RESULTS_FILENAME))
+ else:
+ # Check if we have the archived JSON file on the buildbot
+ # server.
+ results_file_url = (self._builder_base_url +
+ self._build_name + "/" + self.RESULTS_FILENAME)
+ _log.error("Local results.json file does not exist. Grabbing "
+ "it off the archive at " + results_file_url)
try:
results_file = urllib2.urlopen(results_file_url)
@@ -387,7 +411,7 @@ class JSONResultsGeneratorBase(object):
int(time.time()),
self.TIME)
- def _insert_test_time_and_result(self, test_name, tests):
+ def _insert_test_time_and_result(self, test_name, tests, incremental=False):
""" Insert a test item with its results to the given tests dictionary.
Args:
@@ -401,9 +425,20 @@ class JSONResultsGeneratorBase(object):
tests[test_name] = self._create_results_and_times_json()
thisTest = tests[test_name]
- self._insert_item_run_length_encoded(result, thisTest[self.RESULTS])
- self._insert_item_run_length_encoded(time, thisTest[self.TIMES])
- self._normalize_results_json(thisTest, test_name, tests)
+ if self.RESULTS in thisTest:
+ self._insert_item_run_length_encoded(result, thisTest[self.RESULTS])
+ else:
+ thisTest[self.RESULTS] = [[1, result]]
+
+ if self.TIMES in thisTest:
+ self._insert_item_run_length_encoded(time, thisTest[self.TIMES])
+ else:
+ thisTest[self.TIMES] = [[1, time]]
+
+ # Don't normalize the incremental results json because we need results
+ # for tests that pass or have no data from current run.
+ if not incremental:
+ self._normalize_results_json(thisTest, test_name, tests)
def _convert_json_to_current_version(self, results_json):
"""If the JSON does not match the current version, converts it to the
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing.py
index f838a7b..81cdc9b 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing.py
@@ -123,12 +123,6 @@ def print_options():
help="show detailed help on controlling print output"),
optparse.make_option("-v", "--verbose", action="store_true",
default=False, help="include debug-level logging"),
-
- # FIXME: we should remove this; it's pretty much obsolete with the
- # --print trace-everything option.
- optparse.make_option("--sources", action="store_true",
- help=("show expected result file path for each test "
- "(implies --verbose)")),
]
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 38223dd..e154932 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_expectations.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_expectations.py
@@ -460,6 +460,9 @@ class TestExpectationsFile:
return ExpectationsJsonEncoder(separators=(',', ':')).encode(
self._all_expectations)
+ def get_non_fatal_errors(self):
+ return self._non_fatal_errors
+
def contains(self, test):
return test in self._test_to_expectations
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_failures.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_failures.py
index 60bdbca..3be9240 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_failures.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_failures.py
@@ -250,15 +250,6 @@ class FailureImageHashMismatch(FailureWithType):
return "Image mismatch"
-class FailureFuzzyFailure(FailureWithType):
- """Image hashes didn't match."""
- OUT_FILENAMES = ["-actual.png", "-expected.png"]
-
- @staticmethod
- def message():
- return "Fuzzy image match also failed"
-
-
class FailureImageHashIncorrect(FailureWithType):
"""Actual result hash is incorrect."""
# Chrome doesn't know to display a .checksum file as text, so don't bother
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win.py
index 8072bc0..e9a81e7 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win.py
@@ -69,9 +69,9 @@ class ChromiumWinPort(chromium.ChromiumPort):
def baseline_search_path(self):
port_names = []
- if self._name == 'chromium-win-xp':
+ if self._name.endswith('-win-xp'):
port_names.append("chromium-win-xp")
- if self._name in ('chromium-win-xp', 'chromium-win-vista'):
+ if self._name.endswith('-win-xp') or self._name.endswith('-win-vista'):
port_names.append("chromium-win-vista")
# FIXME: This may need to include mac-snowleopard like win.py.
port_names.extend(["chromium-win", "chromium", "win", "mac"])
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/factory.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/factory.py
index 95b90da..258bf33 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/factory.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/factory.py
@@ -83,5 +83,8 @@ def get(port_name=None, options=None):
elif port_to_use.startswith('chromium-win'):
import chromium_win
return chromium_win.ChromiumWinPort(port_name, options)
+ 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)
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome.py
new file mode 100644
index 0000000..1ea053b
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome.py
@@ -0,0 +1,74 @@
+#!/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.
+
+
+def GetGoogleChromePort(port_name, options):
+ """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."""
+ if port_name == 'google-chrome-linux32':
+ import chromium_linux
+
+ class GoogleChromeLinux32Port(chromium_linux.ChromiumLinuxPort):
+ def baseline_search_path(self):
+ paths = chromium_linux.ChromiumLinuxPort.baseline_search_path(
+ self)
+ paths.insert(0, self._webkit_baseline_path(self._name))
+ return paths
+ return GoogleChromeLinux32Port(port_name, options)
+ elif port_name == 'google-chrome-linux64':
+ import chromium_linux
+
+ class GoogleChromeLinux64Port(chromium_linux.ChromiumLinuxPort):
+ def baseline_search_path(self):
+ paths = chromium_linux.ChromiumLinuxPort.baseline_search_path(
+ self)
+ paths.insert(0, self._webkit_baseline_path(self._name))
+ return paths
+ return GoogleChromeLinux64Port(port_name, options)
+ elif port_name.startswith('google-chrome-mac'):
+ import chromium_mac
+
+ class GoogleChromeMacPort(chromium_mac.ChromiumMacPort):
+ def baseline_search_path(self):
+ paths = chromium_mac.ChromiumMacPort.baseline_search_path(
+ self)
+ paths.insert(0, self._webkit_baseline_path(
+ 'google-chrome-mac'))
+ return paths
+ return GoogleChromeMacPort(port_name, options)
+ elif port_name.startswith('google-chrome-win'):
+ import chromium_win
+
+ class GoogleChromeWinPort(chromium_win.ChromiumWinPort):
+ def baseline_search_path(self):
+ paths = chromium_win.ChromiumWinPort.baseline_search_path(
+ self)
+ paths.insert(0, self._webkit_baseline_path(
+ 'google-chrome-win'))
+ return paths
+ return GoogleChromeWinPort(port_name, options)
+ 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
new file mode 100644
index 0000000..a2d7056
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome_unittest.py
@@ -0,0 +1,46 @@
+#!/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 google_chrome
+
+
+class GetGoogleChromePortTest(unittest.TestCase):
+ def test_get_google_chrome_port(self):
+ test_ports = ('google-chrome-linux32', 'google-chrome-linux64',
+ 'google-chrome-mac', 'google-chrome-win')
+ for port in test_ports:
+ self._verify_baseline_path(port, port)
+
+ self._verify_baseline_path('google-chrome-mac', 'google-chrome-mac-leopard')
+ self._verify_baseline_path('google-chrome-win', 'google-chrome-win-xp')
+ self._verify_baseline_path('google-chrome-win', 'google-chrome-win-vista')
+
+ def _verify_baseline_path(self, expected_path, port_name):
+ port = google_chrome.GetGoogleChromePort(port_name, 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/test.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/test.py
index 6eef54e..9c9ab0a 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/port/test.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/test.py
@@ -124,6 +124,9 @@ class TestPort(base.Port):
def test_platform_names(self):
return self.test_base_platform_names()
+ def test_platform_name_to_name(self, test_platform_name):
+ return test_platform_name
+
def version():
return ''
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 fa4df9b..92f1032 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py
@@ -59,7 +59,6 @@ import webbrowser
import zipfile
from webkitpy.common.system.executive import run_command, ScriptError
-from webkitpy.common.checkout.scm import detect_scm_system
import webkitpy.common.checkout.scm as scm
import port
@@ -240,7 +239,7 @@ class Rebaseliner(object):
self._platform,
False,
False)
- self._scm = detect_scm_system(os.getcwd())
+ self._scm = scm.default_scm()
def run(self, backup):
"""Run rebaseline process."""
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 fa03238..121b64e 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
@@ -84,5 +84,19 @@ class TestGetHostPortObject(unittest.TestCase):
port.get = old_get
+class TestRebaseliner(unittest.TestCase):
+
+ def test_noop(self):
+ # this method tests that was can at least instantiate an object, even
+ # if there is nothing to do.
+ options = MockOptions()
+ host_port_obj = port.get('test', options)
+ target_options = options
+ target_port_obj = port.get('test', target_options)
+ platform = 'test'
+ rebaseliner = rebaseline_chromium_webkit_tests.Rebaseliner(
+ host_port_obj, target_port_obj, platform, options)
+ self.assertNotEqual(rebaseliner, None)
+
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 490ac3c..b26bc6c 100755
--- a/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py
@@ -53,6 +53,7 @@ import logging
import math
import optparse
import os
+import pdb
import platform
import Queue
import random
@@ -70,7 +71,6 @@ 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 fuzzy_image_diff
from test_types import image_diff
from test_types import text_diff
from test_types import test_type_base
@@ -578,8 +578,6 @@ class TestRunner:
test_args.new_baseline = self._options.new_baseline
test_args.reset_results = self._options.reset_results
- test_args.show_sources = self._options.sources
-
if self._options.startup_dialog:
shell_args.append('--testshell-startup-dialog')
@@ -629,35 +627,12 @@ class TestRunner:
"""Returns whether we should run all the tests in the main thread."""
return int(self._options.child_processes) == 1
- def _dump_thread_states(self):
- for thread_id, stack in sys._current_frames().items():
- # FIXME: Python 2.6 has thread.ident which we could
- # use to map from thread_id back to thread.name
- print "\n# Thread: %d" % thread_id
- for filename, lineno, name, line in traceback.extract_stack(stack):
- print 'File: "%s", line %d, in %s' % (filename, lineno, name)
- if line:
- print " %s" % (line.strip())
-
- def _dump_thread_states_if_necessary(self):
- # HACK: Dump thread states every minute to figure out what's
- # hanging on the bots.
- if not self._options.verbose:
- return
- dump_threads_every = 60 # Dump every minute
- if not self._last_thread_dump:
- self._last_thread_dump = time.time()
- time_since_last_dump = time.time() - self._last_thread_dump
- if time_since_last_dump > dump_threads_every:
- self._dump_thread_states()
- self._last_thread_dump = time.time()
-
def _run_tests(self, file_list, result_summary):
"""Runs the tests in the file_list.
- Return: A tuple (failures, thread_timings, test_timings,
+ Return: A tuple (keyboard_interrupted, thread_timings, test_timings,
individual_test_timings)
- failures is a map from test to list of failure types
+ keyboard_interrupted is whether someone typed Ctrl^C
thread_timings is a list of dicts with the total runtime
of each thread with 'name', 'num_tests', 'total_time' properties
test_timings is a list of timings for each sharded subdirectory
@@ -676,44 +651,55 @@ class TestRunner:
result_summary)
self._printer.print_update("Starting testing ...")
- # Wait for the threads to finish and collect test failures.
- failures = {}
- test_timings = {}
- individual_test_timings = []
- thread_timings = []
+ keyboard_interrupted = self._wait_for_threads_to_finish(threads,
+ result_summary)
+ (thread_timings, test_timings, individual_test_timings) = \
+ self._collect_timing_info(threads)
+
+ return (keyboard_interrupted, thread_timings, test_timings,
+ individual_test_timings)
+
+ def _wait_for_threads_to_finish(self, threads, result_summary):
keyboard_interrupted = False
try:
# Loop through all the threads waiting for them to finish.
- for thread in threads:
- # FIXME: We'll end up waiting on the first thread the whole
- # time. That means we won't notice exceptions on other
- # threads until the first one exits.
- # We should instead while True: in the outer loop
- # and then loop through threads joining and checking
- # isAlive and get_exception_info. Exiting on any exception.
- while thread.isAlive():
- # Wake the main thread every 0.1 seconds so we
- # can call update_summary in a timely fashion.
- thread.join(0.1)
- # HACK: Used for debugging threads on the bots.
- self._dump_thread_states_if_necessary()
- self.update_summary(result_summary)
+ some_thread_is_alive = True
+ while some_thread_is_alive:
+ some_thread_is_alive = False
+ t = time.time()
+ for thread in threads:
+ exception_info = thread.exception_info()
+ if exception_info is not None:
+ # Re-raise the thread's exception here to make it
+ # clear that testing was aborted. Otherwise,
+ # the tests that did not run would be assumed
+ # to have passed.
+ raise (exception_info[0], exception_info[1],
+ exception_info[2])
+
+ if thread.isAlive():
+ some_thread_is_alive = True
+ next_timeout = thread.next_timeout()
+ if (next_timeout and t > next_timeout):
+ _log_wedged_thread(thread)
+ thread.clear_next_timeout()
+
+ self.update_summary(result_summary)
+
+ if some_thread_is_alive:
+ time.sleep(0.1)
except KeyboardInterrupt:
keyboard_interrupted = True
for thread in threads:
thread.cancel()
- if not keyboard_interrupted:
- for thread in threads:
- # Check whether a thread died before normal completion.
- exception_info = thread.get_exception_info()
- if exception_info is not None:
- # Re-raise the thread's exception here to make it clear
- # something went wrong. Otherwise, the tests that did not
- # run would be assumed to have passed.
- raise (exception_info[0], exception_info[1],
- exception_info[2])
+ return keyboard_interrupted
+
+ def _collect_timing_info(self, threads):
+ test_timings = {}
+ individual_test_timings = []
+ thread_timings = []
for thread in threads:
thread_timings.append({'name': thread.getName(),
@@ -721,8 +707,8 @@ class TestRunner:
'total_time': thread.get_total_time()})
test_timings.update(thread.get_directory_timing_stats())
individual_test_timings.extend(thread.get_test_results())
- return (keyboard_interrupted, thread_timings, test_timings,
- individual_test_timings)
+
+ return (thread_timings, test_timings, individual_test_timings)
def needs_http(self):
"""Returns whether the test runner needs an HTTP server."""
@@ -890,7 +876,8 @@ class TestRunner:
self._options.build_number, self._options.results_directory,
BUILDER_BASE_URL, individual_test_timings,
self._expectations, result_summary, self._test_files_list,
- not self._options.upload_full_results)
+ not self._options.upload_full_results,
+ self._options.test_results_server)
_log.debug("Finished writing JSON files.")
@@ -1440,8 +1427,6 @@ def run(port_obj, options, args, regular_output=sys.stderr,
test_runner.add_test_type(text_diff.TestTextDiff)
if options.pixel_tests:
test_runner.add_test_type(image_diff.ImageDiff)
- if options.fuzzy_pixel_tests:
- test_runner.add_test_type(fuzzy_image_diff.FuzzyImageDiff)
num_unexpected_results = test_runner.run(result_summary)
@@ -1524,9 +1509,6 @@ def parse_args(args=None):
dest="pixel_tests", help="Enable pixel-to-pixel PNG comparisons"),
optparse.make_option("--no-pixel-tests", action="store_false",
dest="pixel_tests", help="Disable pixel-to-pixel PNG comparisons"),
- optparse.make_option("--fuzzy-pixel-tests", action="store_true",
- default=False,
- help="Also use fuzzy matching to compare pixel test outputs."),
# old-run-webkit-tests allows a specific tolerance: --tolerance t
# Ignore image differences less than this percentage (default: 0.1)
optparse.make_option("--results-directory",
@@ -1674,12 +1656,38 @@ def parse_args(args=None):
option_parser = optparse.OptionParser(option_list=option_list)
options, args = option_parser.parse_args(args)
- if options.sources:
- options.verbose = True
return options, args
+def _find_thread_stack(id):
+ """Returns a stack object that can be used to dump a stack trace for
+ the given thread id (or None if the id is not found)."""
+ for thread_id, stack in sys._current_frames().items():
+ if thread_id == id:
+ return stack
+ return None
+
+
+def _log_stack(stack):
+ """Log a stack trace to log.error()."""
+ for filename, lineno, name, line in traceback.extract_stack(stack):
+ _log.error('File: "%s", line %d, in %s' % (filename, lineno, name))
+ if line:
+ _log.error(' %s' % line.strip())
+
+
+def _log_wedged_thread(thread):
+ """Log information about the given thread state."""
+ id = thread.id()
+ stack = _find_thread_stack(id)
+ assert(stack is not None)
+ _log.error("")
+ _log.error("thread %s (%d) is wedged" % (thread.getName(), id))
+ _log_stack(stack)
+ _log.error("")
+
+
def main():
options, args = parse_args()
port_obj = port.get(options.platform, 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 1c751d6..e1b3746 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py
@@ -30,13 +30,20 @@
"""Unit tests for run_webkit_tests."""
import codecs
+import logging
import os
+import pdb
+import Queue
import sys
+import thread
+import time
+import threading
import unittest
from webkitpy.common import array_stream
from webkitpy.layout_tests import port
from webkitpy.layout_tests import run_webkit_tests
+from webkitpy.layout_tests.layout_package import dump_render_tree_thread
from webkitpy.thirdparty.mock import Mock
@@ -92,6 +99,7 @@ class MainTest(unittest.TestCase):
self.assertEqual(buildbot_output.get(), [])
+
def _mocked_open(original_open, file_list):
def _wrapper(name, mode, encoding):
if name.find("-expected.") != -1 and mode == "w":
@@ -191,5 +199,110 @@ class DryrunTest(unittest.TestCase):
'fast/html']))
+class TestThread(dump_render_tree_thread.WatchableThread):
+ def __init__(self, started_queue, stopping_queue):
+ dump_render_tree_thread.WatchableThread.__init__(self)
+ self._started_queue = started_queue
+ self._stopping_queue = stopping_queue
+ self._timeout = False
+ self._timeout_queue = Queue.Queue()
+
+ def run(self):
+ self._thread_id = thread.get_ident()
+ try:
+ self._started_queue.put('')
+ msg = self._stopping_queue.get()
+ if msg == 'KeyboardInterrupt':
+ raise KeyboardInterrupt
+ elif msg == 'Exception':
+ raise ValueError()
+ elif msg == 'Timeout':
+ self._timeout = True
+ self._timeout_queue.get()
+ except:
+ self._exception_info = sys.exc_info()
+
+ def next_timeout(self):
+ if self._timeout:
+ self._timeout_queue.put('done')
+ return time.time() - 10
+ return time.time()
+
+
+class TestHandler(logging.Handler):
+ def __init__(self, astream):
+ logging.Handler.__init__(self)
+ self._stream = astream
+
+ def emit(self, record):
+ self._stream.write(self.format(record))
+
+
+class WaitForThreadsToFinishTest(unittest.TestCase):
+ class MockTestRunner(run_webkit_tests.TestRunner):
+ def __init__(self):
+ pass
+
+ def __del__(self):
+ pass
+
+ def update_summary(self, result_summary):
+ pass
+
+ def run_one_thread(self, msg):
+ runner = self.MockTestRunner()
+ starting_queue = Queue.Queue()
+ stopping_queue = Queue.Queue()
+ child_thread = TestThread(starting_queue, stopping_queue)
+ child_thread.start()
+ started_msg = starting_queue.get()
+ stopping_queue.put(msg)
+ threads = [child_thread]
+ return runner._wait_for_threads_to_finish(threads, None)
+
+ def test_basic(self):
+ interrupted = self.run_one_thread('')
+ self.assertFalse(interrupted)
+
+ def test_interrupt(self):
+ interrupted = self.run_one_thread('KeyboardInterrupt')
+ self.assertTrue(interrupted)
+
+ def test_timeout(self):
+ interrupted = self.run_one_thread('Timeout')
+ self.assertFalse(interrupted)
+
+ def test_exception(self):
+ self.assertRaises(ValueError, self.run_one_thread, 'Exception')
+
+
+class StandaloneFunctionsTest(unittest.TestCase):
+ def test_log_wedged_thread(self):
+ logger = run_webkit_tests._log
+ astream = array_stream.ArrayStream()
+ handler = TestHandler(astream)
+ logger.addHandler(handler)
+
+ starting_queue = Queue.Queue()
+ stopping_queue = Queue.Queue()
+ child_thread = TestThread(starting_queue, stopping_queue)
+ child_thread.start()
+ msg = starting_queue.get()
+
+ run_webkit_tests._log_wedged_thread(child_thread)
+ stopping_queue.put('')
+ child_thread.join(timeout=1.0)
+
+ self.assertFalse(astream.empty())
+ self.assertFalse(child_thread.isAlive())
+
+ def test_find_thread_stack(self):
+ id, stack = sys._current_frames().items()[0]
+ found_stack = run_webkit_tests._find_thread_stack(id)
+ self.assertNotEqual(found_stack, None)
+
+ found_stack = run_webkit_tests._find_thread_stack(0)
+ self.assertEqual(found_stack, None)
+
if __name__ == '__main__':
unittest.main()
diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/test_types/fuzzy_image_diff.py b/WebKitTools/Scripts/webkitpy/layout_tests/test_types/fuzzy_image_diff.py
deleted file mode 100644
index 64dfb20..0000000
--- a/WebKitTools/Scripts/webkitpy/layout_tests/test_types/fuzzy_image_diff.py
+++ /dev/null
@@ -1,71 +0,0 @@
-#!/usr/bin/env python
-# Copyright (C) 2010 Google Inc. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-# * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-"""Compares the image output of a test to the expected image output using
-fuzzy matching.
-"""
-
-import errno
-import logging
-import os
-import shutil
-
-from webkitpy.layout_tests.layout_package import test_failures
-from webkitpy.layout_tests.test_types import test_type_base
-
-_log = logging.getLogger("webkitpy.layout_tests.test_types.fuzzy_image_diff")
-
-
-class FuzzyImageDiff(test_type_base.TestTypeBase):
-
- def compare_output(self, filename, output, test_args, configuration):
- """Implementation of CompareOutput that checks the output image and
- checksum against the expected files from the LayoutTest directory.
- """
- failures = []
-
- # If we didn't produce a hash file, this test must be text-only.
- if test_args.hash is None:
- return failures
-
- expected_png_file = self._port.expected_filename(filename, '.png')
-
- if test_args.show_sources:
- _log.debug('Using %s' % expected_png_file)
-
- # Also report a missing expected PNG file.
- if not os.path.isfile(expected_png_file):
- failures.append(test_failures.FailureMissingImage(self))
-
- # Run the fuzzymatcher
- r = self._port.fuzzy_diff(test_args.png_path, expected_png_file)
- if r != 0:
- failures.append(test_failures.FailureFuzzyFailure(self))
-
- return failures
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 65f8f3a..c9f4107 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/test_types/image_diff.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/test_types/image_diff.py
@@ -136,7 +136,7 @@ class ImageDiff(test_type_base.TestTypeBase):
# If we're generating a new baseline, we pass.
if test_args.new_baseline or test_args.reset_results:
self._save_baseline_files(filename, test_args.png_path,
- test_args.hash, test_args.new_baseline)
+ test_args.hash, test_args.new_baseline)
return failures
# Compare hashes.
@@ -144,10 +144,6 @@ class ImageDiff(test_type_base.TestTypeBase):
'.checksum')
expected_png_file = self._port.expected_filename(filename, '.png')
- if test_args.show_sources:
- _log.debug('Using %s' % expected_hash_file)
- _log.debug('Using %s' % expected_png_file)
-
# FIXME: We repeat this pattern often, we should share code.
try:
with codecs.open(expected_hash_file, "r", "ascii") as file:
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 8db2e3d..dd44642 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
@@ -58,9 +58,6 @@ class TestArguments(object):
# Whether to use wdiff to generate by-word diffs.
wdiff = False
- # Whether to report the locations of the expected result files used.
- show_sources = False
-
# Python bug workaround. See the wdiff code in WriteOutputFiles for an
# explanation.
_wdiff_available = True
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 18f74b8..d06ec8d 100644
--- a/WebKitTools/Scripts/webkitpy/layout_tests/test_types/text_diff.py
+++ b/WebKitTools/Scripts/webkitpy/layout_tests/test_types/text_diff.py
@@ -48,25 +48,22 @@ _log = logging.getLogger("webkitpy.layout_tests.test_types.text_diff")
class TestTextDiff(test_type_base.TestTypeBase):
- def get_normalized_output_text(self, output):
+ def _get_normalized_output_text(self, output):
# Some tests produce "\r\n" explicitly. Our system (Python/Cygwin)
# helpfully changes the "\n" to "\r\n", resulting in "\r\r\n".
norm = output.replace("\r\r\n", "\r\n").strip("\r\n").replace(
"\r\n", "\n")
return norm + "\n"
- def get_normalized_expected_text(self, filename, show_sources):
+ def _get_normalized_expected_text(self, filename):
"""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')
- if show_sources:
- _log.debug('Using %s' % expected_filename)
+ return self._get_normalized_text(expected_filename)
- return self.get_normalized_text(expected_filename)
-
- def get_normalized_text(self, filename):
+ def _get_normalized_text(self, filename):
# FIXME: We repeat this pattern often, we should share code.
try:
# NOTE: -expected.txt files are ALWAYS utf-8. However,
@@ -94,13 +91,12 @@ class TestTextDiff(test_type_base.TestTypeBase):
# we do not ever decode it inside run-webkit-tests. For some tests
# DumpRenderTree may not output utf-8 text (e.g. webarchives).
self._save_baseline_data(filename, output, ".txt", encoding=None,
- generate_new_baseline=test_args.new_baseline)
+ generate_new_baseline=test_args.new_baseline)
return failures
# Normalize text to diff
- output = self.get_normalized_output_text(output)
- expected = self.get_normalized_expected_text(filename,
- test_args.show_sources)
+ output = self._get_normalized_output_text(output)
+ expected = self._get_normalized_expected_text(filename)
# Write output files for new tests, too.
if port.compare_text(output, expected):
@@ -127,5 +123,5 @@ class TestTextDiff(test_type_base.TestTypeBase):
False otherwise.
"""
- return port.compare_text(self.get_normalized_text(file1),
- self.get_normalized_text(file2))
+ return port.compare_text(self._get_normalized_text(file1),
+ self._get_normalized_text(file2))
diff --git a/WebKitTools/Scripts/webkitpy/style/checker.py b/WebKitTools/Scripts/webkitpy/style/checker.py
index e3c56c5..ee33003 100644
--- a/WebKitTools/Scripts/webkitpy/style/checker.py
+++ b/WebKitTools/Scripts/webkitpy/style/checker.py
@@ -38,6 +38,7 @@ from checkers.common import categories as CommonCategories
from checkers.common import CarriageReturnChecker
from checkers.cpp import CppChecker
from checkers.python import PythonChecker
+from checkers.test_expectations import TestExpectationsChecker
from checkers.text import TextChecker
from error_handlers import DefaultStyleErrorHandler
from filter import FilterConfiguration
@@ -234,6 +235,7 @@ def _all_categories():
"""Return the set of all categories used by check-webkit-style."""
# Take the union across all checkers.
categories = CommonCategories.union(CppChecker.categories)
+ categories = categories.union(TestExpectationsChecker.categories)
# FIXME: Consider adding all of the pep8 categories. Since they
# are not too meaningful for documentation purposes, for
@@ -399,10 +401,15 @@ class CheckerDispatcher(object):
# Since "LayoutTests" is in _SKIPPED_FILES_WITHOUT_WARNING, make
# an exception to prevent files like "LayoutTests/ChangeLog" and
# "LayoutTests/ChangeLog-2009-06-16" from being skipped.
+ # Files like 'test_expectations.txt' and 'drt_expectations.txt'
+ # are also should not be skipped.
#
# FIXME: Figure out a good way to avoid having to add special logic
# for this special case.
- if os.path.basename(file_path).startswith('ChangeLog'):
+ basename = os.path.basename(file_path)
+ if basename.startswith('ChangeLog'):
+ return False
+ elif basename == 'test_expectations.txt' or basename == 'drt_expectations.txt':
return False
for skipped_file in _SKIPPED_FILES_WITHOUT_WARNING:
if file_path.find(skipped_file) >= 0:
@@ -442,7 +449,11 @@ class CheckerDispatcher(object):
elif file_type == FileType.PYTHON:
checker = PythonChecker(file_path, handle_style_error)
elif file_type == FileType.TEXT:
- checker = TextChecker(file_path, handle_style_error)
+ basename = os.path.basename(file_path)
+ if basename == 'test_expectations.txt' or basename == 'drt_expectations.txt':
+ checker = TestExpectationsChecker(file_path, handle_style_error)
+ else:
+ checker = TextChecker(file_path, handle_style_error)
else:
raise ValueError('Invalid file type "%(file_type)s": the only valid file types '
"are %(NONE)s, %(CPP)s, and %(TEXT)s."
diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/common.py b/WebKitTools/Scripts/webkitpy/style/checkers/common.py
index a2d933f..76aa956 100644
--- a/WebKitTools/Scripts/webkitpy/style/checkers/common.py
+++ b/WebKitTools/Scripts/webkitpy/style/checkers/common.py
@@ -30,7 +30,7 @@
# into a shared location and refactoring appropriately.
categories = set([
"whitespace/carriage_return",
-])
+ "whitespace/tab"])
class CarriageReturnChecker(object):
@@ -55,3 +55,20 @@ class CarriageReturnChecker(object):
lines[line_number] = lines[line_number].rstrip("\r")
return lines
+
+
+class TabChecker(object):
+
+ """Supports checking for and handling tabs."""
+
+ def __init__(self, file_path, handle_style_error):
+ self.file_path = file_path
+ self.handle_style_error = handle_style_error
+
+ def check(self, lines):
+ # FIXME: share with cpp_style.
+ for line_number, line in enumerate(lines):
+ if "\t" in line:
+ self.handle_style_error(line_number + 1,
+ "whitespace/tab", 5,
+ "Line contains tab character.")
diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/common_unittest.py b/WebKitTools/Scripts/webkitpy/style/checkers/common_unittest.py
index b67b7b0..1fe1263 100644
--- a/WebKitTools/Scripts/webkitpy/style/checkers/common_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/style/checkers/common_unittest.py
@@ -25,7 +25,7 @@
import unittest
from common import CarriageReturnChecker
-
+from common import TabChecker
# FIXME: The unit tests for the cpp, text, and common checkers should
# share supporting test code. This can include, for example, the
@@ -92,3 +92,33 @@ class CarriageReturnCheckerTest(unittest.TestCase):
self.assert_carriage_return(["line1", "line2\r", "line3\r"],
["line1", "line2", "line3"],
[2, 3])
+
+
+class TabCheckerTest(unittest.TestCase):
+
+ """Tests for TabChecker."""
+
+ def assert_tab(self, input_lines, error_lines):
+ """Assert when the given lines contain tabs."""
+ self._error_lines = []
+
+ def style_error_handler(line_number, category, confidence, message):
+ self.assertEqual(category, 'whitespace/tab')
+ self.assertEqual(confidence, 5)
+ self.assertEqual(message, 'Line contains tab character.')
+ self._error_lines.append(line_number)
+
+ checker = TabChecker('', style_error_handler)
+ checker.check(input_lines)
+ self.assertEquals(self._error_lines, error_lines)
+
+ def test_notab(self):
+ self.assert_tab([''], [])
+ self.assert_tab(['foo', 'bar'], [])
+
+ def test_tab(self):
+ self.assert_tab(['\tfoo'], [1])
+ self.assert_tab(['line1', '\tline2', 'line3\t'], [2, 3])
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations.py b/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations.py
new file mode 100644
index 0000000..ddc3983
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations.py
@@ -0,0 +1,124 @@
+# 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.
+
+"""Checks WebKit style for test_expectations files."""
+
+import logging
+import os
+import re
+import sys
+
+from common import TabChecker
+from webkitpy.style_references import port
+from webkitpy.style_references import test_expectations
+
+_log = logging.getLogger("webkitpy.style.checkers.test_expectations")
+
+
+class ChromiumOptions(object):
+ """A mock object for creating chromium port object.
+
+ port.get() requires an options object which has 'chromium' attribute to create
+ chromium port object for each platform. This class mocks such object.
+ """
+ def __init__(self):
+ self.chromium = True
+ self.use_drt = True
+
+
+class TestExpectationsChecker(object):
+ """Processes test_expectations.txt lines for validating the syntax."""
+
+ categories = set(['test/expectations'])
+
+ def __init__(self, file_path, handle_style_error):
+ self._file_path = file_path
+ self._handle_style_error = handle_style_error
+ self._tab_checker = TabChecker(file_path, handle_style_error)
+ self._output_regex = re.compile('Line:(?P<line>\d+)\s*(?P<message>.+)')
+ # Determining the port of this expectations.
+ try:
+ port_name = self._file_path.split(os.sep)[-2]
+ if port_name == "chromium":
+ options = ChromiumOptions()
+ self._port_obj = port.get(port_name=None, options=options)
+ else:
+ self._port_obj = port.get(port_name=port_name)
+ except:
+ # Using 'test' port when we couldn't determine the port for this
+ # expectations.
+ _log.warn("Could not determine the port for %s. "
+ "Using 'test' port, but platform-specific expectations "
+ "will fail the check." % self._file_path)
+ self._port_obj = port.get('test')
+ self._port_to_check = self._port_obj.test_platform_name()
+ # Suppress error messages of test_expectations module since they will be
+ # reported later.
+ log = logging.getLogger("webkitpy.layout_tests.layout_package."
+ "test_expectations")
+ log.setLevel(logging.CRITICAL)
+
+ def _handle_error_message(self, lineno, message, confidence):
+ pass
+
+ def check_test_expectations(self, expectations_str, tests=None, overrides=None):
+ errors = []
+ expectations = None
+ try:
+ 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)
+ except SyntaxError, error:
+ errors = str(error).splitlines()
+
+ for error in errors:
+ matched = self._output_regex.match(error)
+ if matched:
+ lineno, message = matched.group('line', 'message')
+ self._handle_style_error(int(lineno), 'test/expectations', 5, message)
+
+ if expectations:
+ for error in expectations.get_non_fatal_errors():
+ matched = self._output_regex.match(error)
+ if matched:
+ lineno, message = matched.group('line', 'message')
+ self._handle_style_error(int(lineno), 'test/expectations', 2, message)
+
+ def check_tabs(self, lines):
+ self._tab_checker.check(lines)
+
+ def check(self, lines):
+ overrides = self._port_obj.test_expectations_overrides()
+ expectations = '\n'.join(lines)
+ self.check_test_expectations(expectations_str=expectations,
+ tests=None,
+ overrides=overrides)
+ # Warn tabs in lines as well
+ self.check_tabs(lines)
diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py b/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py
new file mode 100644
index 0000000..aa219b2
--- /dev/null
+++ b/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py
@@ -0,0 +1,172 @@
+#!/usr/bin/python
+# Copyright (C) 2010 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Unit tests for test_expectations.py."""
+
+import os
+import sys
+import unittest
+
+# We need following workaround hack to run this unit tests in stand-alone.
+try:
+ d = os.path.dirname(__file__)
+except NameError:
+ d = os.path.dirname(sys.argv[0])
+sys.path.append(os.path.abspath(os.path.join(d, '../../../')))
+
+from test_expectations import TestExpectationsChecker
+from webkitpy.style_references import port
+from webkitpy.style_references import test_expectations as test_expectations_style
+
+
+class ErrorCollector(object):
+ """An error handler class for unit tests."""
+
+ def __init__(self):
+ self._errors = []
+
+ def __call__(self, lineno, category, confidence, message):
+ self._errors.append('%s [%s] [%d]' % (message, category, confidence))
+
+ def get_errors(self):
+ return ''.join(self._errors)
+
+ def reset_errors(self):
+ self._errors = []
+
+
+class TestExpectationsTestCase(unittest.TestCase):
+ """TestCase for test_expectations.py"""
+
+ def setUp(self):
+ self._error_collector = ErrorCollector()
+ port_obj = port.get('test')
+ self._test_file = os.path.join(port_obj.layout_tests_dir(), 'misc/passing.html')
+
+ def process_expectations(self, expectations, overrides=None):
+ self._checker = TestExpectationsChecker()
+
+ def assert_lines_lint(self, lines, expected):
+ self._error_collector.reset_errors()
+ checker = TestExpectationsChecker('test/test_expectations.txt',
+ self._error_collector)
+ checker.check_test_expectations(expectations_str='\n'.join(lines),
+ tests=[self._test_file],
+ overrides=None)
+ checker.check_tabs(lines)
+ self.assertEqual(expected, self._error_collector.get_errors())
+
+ def test_valid_expectations(self):
+ self.assert_lines_lint(
+ ["misc/passing.html = PASS"],
+ "")
+ self.assert_lines_lint(
+ ["misc/passing.html = FAIL PASS"],
+ "")
+ self.assert_lines_lint(
+ ["misc/passing.html = CRASH TIMEOUT FAIL PASS"],
+ "")
+ self.assert_lines_lint(
+ ["BUG1234 TEST : misc/passing.html = PASS FAIL"],
+ "")
+ self.assert_lines_lint(
+ ["SKIP BUG1234 : misc/passing.html = TIMEOUT PASS"],
+ "")
+ self.assert_lines_lint(
+ ["BUG1234 DEBUG : misc/passing.html = TIMEOUT PASS"],
+ "")
+ self.assert_lines_lint(
+ ["BUG1234 DEBUG SKIP : misc/passing.html = TIMEOUT PASS"],
+ "")
+ self.assert_lines_lint(
+ ["BUG1234 TEST DEBUG SKIP : misc/passing.html = TIMEOUT PASS"],
+ "")
+ self.assert_lines_lint(
+ ["BUG1234 DEBUG TEST : misc/passing.html = TIMEOUT PASS"],
+ "")
+ self.assert_lines_lint(
+ ["SLOW DEFER BUG1234 : misc/passing.html = PASS"],
+ "")
+ self.assert_lines_lint(
+ ["WONTFIX SKIP : misc/passing.html = TIMEOUT"],
+ "")
+
+ def test_valid_modifiers(self):
+ self.assert_lines_lint(
+ ["INVALID-MODIFIER : misc/passing.html = PASS"],
+ "Invalid modifier for test: invalid-modifier "
+ "misc/passing.html [test/expectations] [5]")
+ self.assert_lines_lint(
+ ["SKIP : misc/passing.html = PASS"],
+ "Test lacks BUG modifier. "
+ "misc/passing.html [test/expectations] [2]")
+ self.assert_lines_lint(
+ ["WONTFIX DEFER : misc/passing.html = PASS"],
+ "Test cannot be both DEFER and WONTFIX. "
+ "misc/passing.html [test/expectations] [5]")
+
+ def test_expectation_errors(self):
+ self.assert_lines_lint(
+ ["missing expectations"],
+ "Missing expectations. ['missing expectations'] [test/expectations] [5]")
+ self.assert_lines_lint(
+ ["SLOW : misc/passing.html = TIMEOUT"],
+ "A test can not be both slow and timeout. "
+ "If it times out indefinitely, then it should be just timeout. "
+ "misc/passing.html [test/expectations] [5]")
+ self.assert_lines_lint(
+ ["does/not/exist.html = FAIL"],
+ "Path does not exist. does/not/exist.html [test/expectations] [2]")
+
+ def test_parse_expectations(self):
+ self.assert_lines_lint(
+ ["misc/passing.html = PASS"],
+ "")
+ self.assert_lines_lint(
+ ["misc/passing.html = UNSUPPORTED"],
+ "Unsupported expectation: unsupported "
+ "misc/passing.html [test/expectations] [5]")
+ self.assert_lines_lint(
+ ["misc/passing.html = PASS UNSUPPORTED"],
+ "Unsupported expectation: unsupported "
+ "misc/passing.html [test/expectations] [5]")
+
+ def test_already_seen_test(self):
+ self.assert_lines_lint(
+ ["misc/passing.html = PASS",
+ "misc/passing.html = TIMEOUT"],
+ "Duplicate expectation. %s [test/expectations] [5]" % self._test_file)
+
+ def test_tab(self):
+ self.assert_lines_lint(
+ ["\tmisc/passing.html = PASS"],
+ "Line contains tab character. [whitespace/tab] [5]")
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/text.py b/WebKitTools/Scripts/webkitpy/style/checkers/text.py
index 0d03938..1147658 100644
--- a/WebKitTools/Scripts/webkitpy/style/checkers/text.py
+++ b/WebKitTools/Scripts/webkitpy/style/checkers/text.py
@@ -29,6 +29,7 @@
"""Checks WebKit style for text files."""
+from common import TabChecker
class TextChecker(object):
@@ -37,16 +38,10 @@ class TextChecker(object):
def __init__(self, file_path, handle_style_error):
self.file_path = file_path
self.handle_style_error = handle_style_error
+ self._tab_checker = TabChecker(file_path, handle_style_error)
def check(self, lines):
- lines = (["// adjust line numbers to make the first line 1."] + lines)
-
- # FIXME: share with cpp_style.
- for line_number, line in enumerate(lines):
- if "\t" in line:
- self.handle_style_error(line_number,
- "whitespace/tab", 5,
- "Line contains tab character.")
+ self._tab_checker.check(lines)
# FIXME: Remove this function (requires refactoring unit tests).
diff --git a/WebKitTools/Scripts/webkitpy/style_references.py b/WebKitTools/Scripts/webkitpy/style_references.py
index a42b69d..34f3bff 100644
--- a/WebKitTools/Scripts/webkitpy/style_references.py
+++ b/WebKitTools/Scripts/webkitpy/style_references.py
@@ -45,6 +45,8 @@ from webkitpy.common.system.logtesting import LogTesting
from webkitpy.common.system.logtesting import TestLogStream
from webkitpy.common.system.logutils import configure_logging
from webkitpy.common.checkout.scm import detect_scm_system
+from webkitpy.layout_tests import port
+from webkitpy.layout_tests.layout_package import test_expectations
from webkitpy.thirdparty.autoinstalled import pep8
diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/decoder.py b/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/decoder.py
index b887b58..63f70cb 100644
--- a/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/decoder.py
+++ b/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/decoder.py
@@ -3,7 +3,7 @@ Implementation of JSONDecoder
"""
import re
-from .scanner import Scanner, pattern
+from scanner import Scanner, pattern
FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
diff --git a/WebKitTools/Scripts/webkitpy/tool/main.py b/WebKitTools/Scripts/webkitpy/tool/main.py
index 1f43145..0dd5017 100755
--- a/WebKitTools/Scripts/webkitpy/tool/main.py
+++ b/WebKitTools/Scripts/webkitpy/tool/main.py
@@ -33,7 +33,7 @@ import os
import threading
from webkitpy.common.checkout.api import Checkout
-from webkitpy.common.checkout.scm import detect_scm_system
+from webkitpy.common.checkout.scm import default_scm
from webkitpy.common.net.bugzilla import Bugzilla
from webkitpy.common.net.buildbot import BuildBot
from webkitpy.common.net.rietveld import Rietveld
@@ -79,18 +79,8 @@ class WebKitPatch(MultiCommandTool):
def scm(self):
# Lazily initialize SCM to not error-out before command line parsing (or when running non-scm commands).
- original_cwd = os.path.abspath(".")
if not self._scm:
- self._scm = detect_scm_system(original_cwd)
-
- if not self._scm:
- script_directory = os.path.abspath(sys.path[0])
- self._scm = detect_scm_system(script_directory)
- if self._scm:
- log("The current directory (%s) is not a WebKit checkout, using %s" % (original_cwd, self._scm.checkout_root))
- else:
- error("FATAL: Failed to determine the SCM system for either %s or %s" % (original_cwd, script_directory))
-
+ self._scm = default_scm()
return self._scm
def checkout(self):
diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/runtests.py b/WebKitTools/Scripts/webkitpy/tool/steps/runtests.py
index 22b9452..0f57439 100644
--- a/WebKitTools/Scripts/webkitpy/tool/steps/runtests.py
+++ b/WebKitTools/Scripts/webkitpy/tool/steps/runtests.py
@@ -57,6 +57,7 @@ class RunTests(AbstractStep):
if self._options.non_interactive:
args.append("--no-launch-safari")
args.append("--exit-after-n-failures=1")
+ args.append("--wait-for-httpd")
# FIXME: Hack to work around https://bugs.webkit.org/show_bug.cgi?id=38912
# when running the commit-queue on a mac leopard machine since compositing
# does not work reliably on Leopard due to various graphics driver/system bugs.
diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/steps_unittest.py b/WebKitTools/Scripts/webkitpy/tool/steps/steps_unittest.py
index 766801b..f4c955d 100644
--- a/WebKitTools/Scripts/webkitpy/tool/steps/steps_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/tool/steps/steps_unittest.py
@@ -77,6 +77,6 @@ MOCK run_and_throw_if_fail: ['WebKitTools/Scripts/test-webkitperl']
Running JavaScriptCore tests
MOCK run_and_throw_if_fail: ['WebKitTools/Scripts/run-javascriptcore-tests']
Running run-webkit-tests
-MOCK run_and_throw_if_fail: ['WebKitTools/Scripts/run-webkit-tests', '--no-launch-safari', '--exit-after-n-failures=1', '--ignore-tests', 'compositing', '--quiet']
+MOCK run_and_throw_if_fail: ['WebKitTools/Scripts/run-webkit-tests', '--no-launch-safari', '--exit-after-n-failures=1', '--wait-for-httpd', '--ignore-tests', 'compositing', '--quiet']
"""
OutputCapture().assert_outputs(self, step.run, [{}], expected_stderr=expected_stderr)