diff options
author | Iain Merrick <husky@google.com> | 2010-08-19 17:55:56 +0100 |
---|---|---|
committer | Iain Merrick <husky@google.com> | 2010-08-23 11:05:40 +0100 |
commit | f486d19d62f1bc33246748b14b14a9dfa617b57f (patch) | |
tree | 195485454c93125455a30e553a73981c3816144d /WebKitTools/Scripts | |
parent | 6ba0b43722d16bc295606bec39f396f596e4fef1 (diff) | |
download | external_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')
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) |