diff options
Diffstat (limited to 'WebKitTools/Scripts')
105 files changed, 2654 insertions, 4305 deletions
diff --git a/WebKitTools/Scripts/VCSUtils.pm b/WebKitTools/Scripts/VCSUtils.pm index 777fe7f..41bbf40 100644 --- a/WebKitTools/Scripts/VCSUtils.pm +++ b/WebKitTools/Scripts/VCSUtils.pm @@ -43,6 +43,7 @@ BEGIN { $VERSION = 1.00; @ISA = qw(Exporter); @EXPORT = qw( + &callSilently &canonicalizePath &changeLogEmailAddress &changeLogName @@ -67,6 +68,7 @@ BEGIN { &pathRelativeToSVNRepositoryRootForPath &prepareParsedPatch &runPatchCommand + &setChangeLogDateAndReviewer &svnRevisionForDirectory &svnStatus ); @@ -83,6 +85,14 @@ my $isGitBranchBuild; my $isSVN; my $svnVersion; +# Project time zone for Cupertino, CA, US +my $changeLogTimeZone = "PST8PDT"; + +my $gitDiffStartRegEx = qr#^diff --git (\w/)?(.+) (\w/)?([^\r\n]+)#; +my $svnDiffStartRegEx = qr#^Index: ([^\r\n]+)#; +my $svnPropertyStartRegEx = qr#^(Modified|Name|Added|Deleted): ([^\r\n]+)#; # $2 is the name of the property. +my $svnPropertyValueStartRegEx = qr#^ (\+|-) ([^\r\n]+)#; # $2 is the start of the property's value (which may span multiple lines). + # This method is for portability. Return the system-appropriate exit # status of a child process. # @@ -97,6 +107,25 @@ sub exitStatus($) return WEXITSTATUS($returnvalue); } +# Call a function while suppressing STDERR, and return the return values +# as an array. +sub callSilently($@) { + my ($func, @args) = @_; + + # The following pattern was taken from here: + # http://www.sdsc.edu/~moreland/courses/IntroPerl/docs/manual/pod/perlfunc/open.html + # + # Also see this Perl documentation (search for "open OLDERR"): + # http://perldoc.perl.org/functions/open.html + open(OLDERR, ">&STDERR"); + close(STDERR); + my @returnValue = &$func(@args); + open(STDERR, ">&OLDERR"); + close(OLDERR); + + return @returnValue; +} + # Note, this method will not error if the file corresponding to the path does not exist. sub scmToggleExecutableBit { @@ -425,14 +454,23 @@ sub isExecutable($) # # Returns ($headerHashRef, $lastReadLine): # $headerHashRef: a hash reference representing a diff header, as follows-- -# copiedFromPath: the path from which the file was copied if the diff -# is a copy. +# copiedFromPath: the path from which the file was copied or moved if +# the diff is a copy or move. # executableBitDelta: the value 1 or -1 if the executable bit was added or # removed, respectively. New and deleted files have # this value only if the file is executable, in which # case the value is 1 and -1, respectively. # indexPath: the path of the target file. # isBinary: the value 1 if the diff is for a binary file. +# isDeletion: the value 1 if the diff is a file deletion. +# isCopyWithChanges: the value 1 if the file was copied or moved and +# the target file was changed in some way after being +# copied or moved (e.g. if its contents or executable +# bit were changed). +# isNew: the value 1 if the diff is for a new file. +# shouldDeleteSource: the value 1 if the file was copied or moved and +# the source file was deleted -- i.e. if the copy +# was actually a move. # svnConvertedText: the header text with some lines converted to SVN # format. Git-specific lines are preserved. # $lastReadLine: the line last read from $fileHandle. @@ -442,9 +480,8 @@ sub parseGitDiffHeader($$) $_ = $line; - my $headerStartRegEx = qr#^diff --git (\w/)?(.+) (\w/)?([^\r\n]+)#; my $indexPath; - if (/$headerStartRegEx/) { + if (/$gitDiffStartRegEx/) { # The first and second paths can differ in the case of copies # and renames. We use the second file path because it is the # destination path. @@ -458,9 +495,12 @@ sub parseGitDiffHeader($$) my $copiedFromPath; my $foundHeaderEnding; my $isBinary; + my $isDeletion; + my $isNew; my $newExecutableBit = 0; my $oldExecutableBit = 0; - my $similarityIndex; + my $shouldDeleteSource = 0; + my $similarityIndex = 0; my $svnConvertedText; while (1) { # Temporarily strip off any end-of-line characters to simplify @@ -468,45 +508,60 @@ sub parseGitDiffHeader($$) s/([\n\r]+)$//; my $eol = $1; - if (/^(deleted file|old) mode ([0-9]{6})/) { + if (/^(deleted file|old) mode (\d+)/) { $oldExecutableBit = (isExecutable($2) ? 1 : 0); - } elsif (/^new( file)? mode ([0-9]{6})/) { + $isDeletion = 1 if $1 eq "deleted file"; + } elsif (/^new( file)? mode (\d+)/) { $newExecutableBit = (isExecutable($2) ? 1 : 0); + $isNew = 1 if $1; + } elsif (/^similarity index (\d+)%/) { + $similarityIndex = $1; + } elsif (/^copy from (\S+)/) { + $copiedFromPath = $1; + } elsif (/^rename from (\S+)/) { + # FIXME: Record this as a move rather than as a copy-and-delete. + # This will simplify adding rename support to svn-unapply. + # Otherwise, the hash for a deletion would have to know + # everything about the file being deleted in order to + # support undoing itself. Recording as a move will also + # permit us to use "svn move" and "git move". + $copiedFromPath = $1; + $shouldDeleteSource = 1; } elsif (/^--- \S+/) { $_ = "--- $indexPath"; # Convert to SVN format. } elsif (/^\+\+\+ \S+/) { $_ = "+++ $indexPath"; # Convert to SVN format. $foundHeaderEnding = 1; - } elsif (/^similarity index (\d+)%/) { - $similarityIndex = $1; - } elsif (/^copy from (\S+)/) { - $copiedFromPath = $1; + } elsif (/^GIT binary patch$/ ) { + $isBinary = 1; + $foundHeaderEnding = 1; # The "git diff" command includes a line of the form "Binary files # <path1> and <path2> differ" if the --binary flag is not used. } elsif (/^Binary files / ) { die("Error: the Git diff contains a binary file without the binary data in ". "line: \"$_\". Be sure to use the --binary flag when invoking \"git diff\" ". "with diffs containing binary files."); - } elsif (/^GIT binary patch$/ ) { - $isBinary = 1; - $foundHeaderEnding = 1; } $svnConvertedText .= "$_$eol"; # Also restore end-of-line characters. $_ = <$fileHandle>; # Not defined if end-of-file reached. - last if (!defined($_) || /$headerStartRegEx/ || $foundHeaderEnding); + last if (!defined($_) || /$gitDiffStartRegEx/ || $foundHeaderEnding); } my $executableBitDelta = $newExecutableBit - $oldExecutableBit; my %header; - $header{copiedFromPath} = $copiedFromPath if ($copiedFromPath && $similarityIndex == 100); + $header{copiedFromPath} = $copiedFromPath if $copiedFromPath; $header{executableBitDelta} = $executableBitDelta if $executableBitDelta; $header{indexPath} = $indexPath; $header{isBinary} = $isBinary if $isBinary; + $header{isCopyWithChanges} = 1 if ($copiedFromPath && ($similarityIndex != 100 || $executableBitDelta)); + $header{isDeletion} = $isDeletion if $isDeletion; + $header{isNew} = $isNew if $isNew; + $header{shouldDeleteSource} = $shouldDeleteSource if $shouldDeleteSource; $header{svnConvertedText} = $svnConvertedText; return (\%header, $_); @@ -531,6 +586,7 @@ sub parseGitDiffHeader($$) # indexPath: the path of the target file, which is the path found in # the "Index:" line. # isBinary: the value 1 if the diff is for a binary file. +# isNew: the value 1 if the diff is for a new file. # sourceRevision: the revision number of the source, if it exists. This # is the same as the revision number the file was copied # from, in the case of a file copy. @@ -543,16 +599,17 @@ sub parseSvnDiffHeader($$) $_ = $line; - my $headerStartRegEx = qr/^Index: /; - - if (!/$headerStartRegEx/) { + my $indexPath; + if (/$svnDiffStartRegEx/) { + $indexPath = $1; + } else { die("First line of SVN diff does not begin with \"Index \": \"$_\""); } my $copiedFromPath; my $foundHeaderEnding; - my $indexPath; my $isBinary; + my $isNew; my $sourceRevision; my $svnConvertedText; while (1) { @@ -563,12 +620,11 @@ sub parseSvnDiffHeader($$) # Fix paths on ""---" and "+++" lines to match the leading # index line. - if (/^Index: ([^\r\n]+)/) { - $indexPath = $1; - } elsif (s/^--- \S+/--- $indexPath/) { + if (s/^--- \S+/--- $indexPath/) { # --- if (/^--- .+\(revision (\d+)\)/) { $sourceRevision = $1; + $isNew = 1 if !$sourceRevision; # if revision 0. if (/\(from (\S+):(\d+)\)$/) { # The "from" clause is created by svn-create-patch, in # which case there is always also a "revision" clause. @@ -588,7 +644,7 @@ sub parseSvnDiffHeader($$) $_ = <$fileHandle>; # Not defined if end-of-file reached. - last if (!defined($_) || /$headerStartRegEx/ || $foundHeaderEnding); + last if (!defined($_) || /$svnDiffStartRegEx/ || $foundHeaderEnding); } if (!$foundHeaderEnding) { @@ -600,6 +656,7 @@ sub parseSvnDiffHeader($$) $header{copiedFromPath} = $copiedFromPath if $copiedFromPath; $header{indexPath} = $indexPath; $header{isBinary} = $isBinary if $isBinary; + $header{isNew} = $isNew if $isNew; $header{sourceRevision} = $sourceRevision if $sourceRevision; $header{svnConvertedText} = $svnConvertedText; @@ -646,10 +703,10 @@ sub parseDiffHeader($$) my $isSvn; my $lastReadLine; - if ($line =~ /^Index:/) { + if ($line =~ $svnDiffStartRegEx) { $isSvn = 1; ($header, $lastReadLine) = parseSvnDiffHeader($fileHandle, $line); - } elsif ($line =~ /^diff --git/) { + } elsif ($line =~ $gitDiffStartRegEx) { $isGit = 1; ($header, $lastReadLine) = parseGitDiffHeader($fileHandle, $line); } else { @@ -681,7 +738,9 @@ sub parseDiffHeader($$) # indexPath: the path of the target file. For SVN-formatted diffs, # this is the same as the path in the "Index:" line. # isBinary: the value 1 if the diff is for a binary file. +# isDeletion: the value 1 if the diff is known from the header to be a deletion. # isGit: the value 1 if the diff is Git-formatted. +# isNew: the value 1 if the dif is known from the header to be a new file. # isSvn: the value 1 if the diff is SVN-formatted. # sourceRevision: the revision number of the source, if it exists. This # is the same as the revision number the file was copied @@ -700,25 +759,27 @@ sub parseDiffHeader($$) # header block. Leading junk is okay. # $line: the line last read from $fileHandle. # -# Returns ($diffHashRef, $lastReadLine): -# $diffHashRef: A reference to a %diffHash. -# See the %diffHash documentation above. +# Returns ($diffHashRefs, $lastReadLine): +# $diffHashRefs: A reference to an array of references to %diffHash hashes. +# See the %diffHash documentation above. # $lastReadLine: the line last read from $fileHandle sub parseDiff($$) { + # FIXME: Adjust this method so that it dies if the first line does not + # match the start of a diff. This will require a change to + # parsePatch() so that parsePatch() skips over leading junk. my ($fileHandle, $line) = @_; - my $headerStartRegEx = qr#^Index: #; # SVN-style header for the default - my $gitHeaderStartRegEx = qr#^diff --git \w/#; + my $headerStartRegEx = $svnDiffStartRegEx; # SVN-style header for the default my $headerHashRef; # Last header found, as returned by parseDiffHeader(). my $svnText; while (defined($line)) { - if (!$headerHashRef && ($line =~ $gitHeaderStartRegEx)) { + if (!$headerHashRef && ($line =~ $gitDiffStartRegEx)) { # Then assume all diffs in the patch are Git-formatted. This # block was made to be enterable at most once since we assume # all diffs in the patch are formatted the same (SVN or Git). - $headerStartRegEx = $gitHeaderStartRegEx; + $headerStartRegEx = $gitDiffStartRegEx; } if ($line !~ $headerStartRegEx) { @@ -738,19 +799,191 @@ sub parseDiff($$) $svnText .= $headerHashRef->{svnConvertedText}; } - my %diffHashRef; - $diffHashRef{copiedFromPath} = $headerHashRef->{copiedFromPath} if $headerHashRef->{copiedFromPath}; - # FIXME: Add executableBitDelta as a key. - $diffHashRef{indexPath} = $headerHashRef->{indexPath}; - $diffHashRef{isBinary} = $headerHashRef->{isBinary} if $headerHashRef->{isBinary}; - $diffHashRef{isGit} = $headerHashRef->{isGit} if $headerHashRef->{isGit}; - $diffHashRef{isSvn} = $headerHashRef->{isSvn} if $headerHashRef->{isSvn}; - $diffHashRef{sourceRevision} = $headerHashRef->{sourceRevision} if $headerHashRef->{sourceRevision}; - # FIXME: Remove the need for svnConvertedText. See the %diffHash - # code comments above for more information. - $diffHashRef{svnConvertedText} = $svnText; - - return (\%diffHashRef, $line); + my @diffHashRefs; + + if ($headerHashRef->{shouldDeleteSource}) { + my %deletionHash; + $deletionHash{indexPath} = $headerHashRef->{copiedFromPath}; + $deletionHash{isDeletion} = 1; + push @diffHashRefs, \%deletionHash; + } + if ($headerHashRef->{copiedFromPath}) { + my %copyHash; + $copyHash{copiedFromPath} = $headerHashRef->{copiedFromPath}; + $copyHash{indexPath} = $headerHashRef->{indexPath}; + $copyHash{sourceRevision} = $headerHashRef->{sourceRevision} if $headerHashRef->{sourceRevision}; + push @diffHashRefs, \%copyHash; + } + if (!$headerHashRef->{copiedFromPath} || $headerHashRef->{isCopyWithChanges}) { + # Then add the usual file modification. + my %diffHash; + # FIXME: Add executableBitDelta as a key. + $diffHash{indexPath} = $headerHashRef->{indexPath}; + $diffHash{isBinary} = $headerHashRef->{isBinary} if $headerHashRef->{isBinary}; + $diffHash{isDeletion} = $headerHashRef->{isDeletion} if $headerHashRef->{isDeletion}; + $diffHash{isGit} = $headerHashRef->{isGit} if $headerHashRef->{isGit}; + $diffHash{isNew} = $headerHashRef->{isNew} if $headerHashRef->{isNew}; + $diffHash{isSvn} = $headerHashRef->{isSvn} if $headerHashRef->{isSvn}; + if (!$headerHashRef->{copiedFromPath}) { + # If the file was copied, then we have already incorporated the + # sourceRevision information into the change. + $diffHash{sourceRevision} = $headerHashRef->{sourceRevision} if $headerHashRef->{sourceRevision}; + } + # FIXME: Remove the need for svnConvertedText. See the %diffHash + # code comments above for more information. + $diffHash{svnConvertedText} = $svnText; + push @diffHashRefs, \%diffHash; + } + + return (\@diffHashRefs, $line); +} + +# Parse the next SVN property from the given file handle, and advance the handle so the last +# line read is the first line after the property. +# +# This subroutine dies if the first line is not a valid start of an SVN property, +# or the property is missing a value, or the property change type (e.g. "Added") +# does not correspond to the property value type (e.g. "+"). +# +# Args: +# $fileHandle: advanced so the last line read from the handle is the first +# line of the property to parse. This should be a line +# that matches $svnPropertyStartRegEx. +# $line: the line last read from $fileHandle. +# +# Returns ($propertyHashRef, $lastReadLine): +# $propertyHashRef: a hash reference representing a SVN property. +# name: the name of the property. +# value: the last property value. For instance, suppose the property is "Modified". +# Then it has both a '-' and '+' property value in that order. Therefore, +# the value of this key is the value of the '+' property by ordering (since +# it is the last value). +# propertyChangeDelta: the value 1 or -1 if the property was added or +# removed, respectively. +# $lastReadLine: the line last read from $fileHandle. +# +# FIXME: This method is unused as of (05/16/2010). We will call this function +# as part of parsing a SVN footer. See <https://bugs.webkit.org/show_bug.cgi?id=38885>. +sub parseSvnProperty($$) +{ + my ($fileHandle, $line) = @_; + + $_ = $line; + + my $propertyName; + my $propertyChangeType; + if (/$svnPropertyStartRegEx/) { + $propertyChangeType = $1; + $propertyName = $2; + } else { + die("Failed to find SVN property: \"$_\"."); + } + + $_ = <$fileHandle>; # Not defined if end-of-file reached. + + # The "svn diff" command neither inserts newline characters between property values + # nor between successive properties. + # + # FIXME: We do not support property values that contain tailing newline characters + # as it is difficult to disambiguate these trailing newlines from the empty + # line that precedes the contents of a binary patch. + my $propertyValue; + my $propertyValueType; + while (defined($_) && /$svnPropertyValueStartRegEx/) { + # Note, a '-' property may be followed by a '+' property in the case of a "Modified" + # or "Name" property. We only care about the ending value (i.e. the '+' property) + # in such circumstances. So, we take the property value for the property to be its + # last parsed property value. + # + # FIXME: We may want to consider strictly enforcing a '-', '+' property ordering or + # add error checking to prevent '+', '+', ..., '+' and other invalid combinations. + $propertyValueType = $1; + ($propertyValue, $_) = parseSvnPropertyValue($fileHandle, $_); + } + + if (!$propertyValue) { + die("Failed to find the property value for the SVN property \"$propertyName\": \"$_\"."); + } + + my $propertyChangeDelta; + if ($propertyValueType eq '+') { + $propertyChangeDelta = 1; + } elsif ($propertyValueType eq '-') { + $propertyChangeDelta = -1; + } else { + die("Not reached."); + } + + # We perform a simple validation that an "Added" or "Deleted" property + # change type corresponds with a "+" and "-" value type, respectively. + my $expectedChangeDelta; + if ($propertyChangeType eq "Added") { + $expectedChangeDelta = 1; + } elsif ($propertyChangeType eq "Deleted") { + $expectedChangeDelta = -1; + } + + if ($expectedChangeDelta && $propertyChangeDelta != $expectedChangeDelta) { + die("The final property value type found \"$propertyValueType\" does not " . + "correspond to the property change type found \"$propertyChangeType\"."); + } + + my %propertyHash; + $propertyHash{name} = $propertyName; + $propertyHash{propertyChangeDelta} = $propertyChangeDelta; + $propertyHash{value} = $propertyValue; + return (\%propertyHash, $_); +} + +# Parse the value of an SVN property from the given file handle, and advance +# the handle so the last line read is the first line after the property value. +# +# This subroutine dies if the first line is an invalid SVN property value line +# (i.e. a line that does not begin with " +" or " -"). +# +# Args: +# $fileHandle: advanced so the last line read from the handle is the first +# line of the property value to parse. This should be a line +# beginning with " +" or " -". +# $line: the line last read from $fileHandle. +# +# Returns ($propertyValue, $lastReadLine): +# $propertyValue: the value of the property. +# $lastReadLine: the line last read from $fileHandle. +sub parseSvnPropertyValue($$) +{ + my ($fileHandle, $line) = @_; + + $_ = $line; + + my $propertyValue; + my $eol; + if (/$svnPropertyValueStartRegEx/) { + $propertyValue = $2; # Does not include the end-of-line character(s). + $eol = $POSTMATCH; + } else { + die("Failed to find property value beginning with '+' or '-': \"$_\"."); + } + + while (<$fileHandle>) { + if (/^$/ || /$svnPropertyValueStartRegEx/ || /$svnPropertyStartRegEx/) { + # Note, we may encounter an empty line before the contents of a binary patch. + # Also, we check for $svnPropertyValueStartRegEx because a '-' property may be + # followed by a '+' property in the case of a "Modified" or "Name" property. + # We check for $svnPropertyStartRegEx because it indicates the start of the + # next property to parse. + last; + } + + # Temporarily strip off any end-of-line characters. We add the end-of-line characters + # from the previously processed line to the start of this line so that the last line + # of the property value does not end in end-of-line characters. + s/([\n\r]+)$//; + $propertyValue .= "$eol$_"; + $eol = $1; + } + + return ($propertyValue, $_); } # Parse a patch file created by svn-create-patch. @@ -766,16 +999,16 @@ sub parsePatch($) { my ($fileHandle) = @_; + my $newDiffHashRefs; my @diffHashRefs; # return value my $line = <$fileHandle>; while (defined($line)) { # Otherwise, at EOF. - my $diffHashRef; - ($diffHashRef, $line) = parseDiff($fileHandle, $line); + ($newDiffHashRefs, $line) = parseDiff($fileHandle, $line); - push @diffHashRefs, $diffHashRef; + push @diffHashRefs, @$newDiffHashRefs; } return @diffHashRefs; @@ -853,6 +1086,51 @@ sub prepareParsedPatch($@) return \%preparedPatchHash; } +# Return localtime() for the project's time zone, given an integer time as +# returned by Perl's time() function. +sub localTimeInProjectTimeZone($) +{ + my $epochTime = shift; + + # Change the time zone temporarily for the localtime() call. + my $savedTimeZone = $ENV{'TZ'}; + $ENV{'TZ'} = $changeLogTimeZone; + my @localTime = localtime($epochTime); + if (defined $savedTimeZone) { + $ENV{'TZ'} = $savedTimeZone; + } else { + delete $ENV{'TZ'}; + } + + return @localTime; +} + +# Set the reviewer and date in a ChangeLog patch, and return the new patch. +# +# Args: +# $patch: a ChangeLog patch as a string. +# $reviewer: the name of the reviewer, or undef if the reviewer should not be set. +# $epochTime: an integer time as returned by Perl's time() function. +sub setChangeLogDateAndReviewer($$$) +{ + my ($patch, $reviewer, $epochTime) = @_; + + my @localTime = localTimeInProjectTimeZone($epochTime); + my $newDate = strftime("%Y-%m-%d", @localTime); + + my $firstChangeLogLineRegEx = qr#(\n\+)\d{4}-[^-]{2}-[^-]{2}( )#; + $patch =~ s/$firstChangeLogLineRegEx/$1$newDate$2/; + + if (defined($reviewer)) { + # We include a leading plus ("+") in the regular expression to make + # the regular expression less likely to match text in the leading junk + # for the patch, if the patch has leading junk. + $patch =~ s/(\n\+.*)NOBODY \(OOPS!\)/$1$reviewer/; + } + + return $patch; +} + # If possible, returns a ChangeLog patch equivalent to the given one, # but with the newest ChangeLog entry inserted at the top of the # file -- i.e. no leading context and all lines starting with "+". diff --git a/WebKitTools/Scripts/build-dumprendertree b/WebKitTools/Scripts/build-dumprendertree index 6502916..87c720f 100755 --- a/WebKitTools/Scripts/build-dumprendertree +++ b/WebKitTools/Scripts/build-dumprendertree @@ -70,20 +70,9 @@ if (isAppleMacWebKit()) { $result = buildXCodeProject("DumpRenderTree", $clean, XcodeOptions(), @ARGV); } elsif (isAppleWinWebKit()) { $result = buildVisualStudioProject("DumpRenderTree.sln", $clean); -} elsif (isQt() || isGtk() || isWx()) { - # Qt, Gtk and wxWindows build everything in one shot. No need to build anything here. +} elsif (isQt() || isGtk() || isWx() || isChromium()) { + # Qt, Gtk wxWindows, and Chromium build everything in one shot. No need to build anything here. $result = 0; -} elsif (isChromium()) { - if (isDarwin()) { - $result = buildXCodeProject("DumpRenderTree.gyp/DumpRenderTree", $clean, "-configuration", configuration(), @ARGV); - } elsif (isCygwin() || isWindows()) { - # Windows build - builds the root visual studio solution. - $result = buildChromiumVisualStudioProject("DumpRenderTree.gyp/DumpRenderTree.sln", $clean); - } elsif (isLinux()) { - $result = buildChromiumMakefile("../../WebKit/chromium/", "DumpRenderTree", $clean); - } else { - die "This platform is not supported by Chromium.\n"; - } } else { die "Building not defined for this platform!\n"; } diff --git a/WebKitTools/Scripts/build-webkit b/WebKitTools/Scripts/build-webkit index 3a3edb9..e70117f 100755 --- a/WebKitTools/Scripts/build-webkit +++ b/WebKitTools/Scripts/build-webkit @@ -31,6 +31,7 @@ use strict; use File::Basename; +use File::Find; use File::Spec; use FindBin; use Getopt::Long qw(:config pass_through); @@ -48,6 +49,9 @@ chdirWebKit(); my $showHelp = 0; my $clean = 0; my $minimal = 0; +my $installHeaders; +my $installLibs; +my $prefixPath; my $makeArgs; my $startTime = time(); @@ -178,7 +182,7 @@ my @features = ( define => "ENABLE_XSLT", default => 1, value => \$xsltSupport }, { option => "file-reader", desc => "Toggle FileReader support", - define => "ENABLE_FILE_READER", default => 0, value => \$fileReaderSupport }, + define => "ENABLE_FILE_READER", default => isAppleWebKit(), value => \$fileReaderSupport }, { option => "file-writer", desc => "Toggle FileWriter support", define => "ENABLE_FILE_WRITER", default => 0, value => \$fileWriterSupport }, @@ -225,6 +229,10 @@ Usage: $programName [options] [options to pass to build system] --qt Build the Qt port --inspector-frontend Copy changes to the inspector front-end files to the build directory + --install-headers=<path> Set installation path for the headers (Qt only) + --install-libs=<path> Set installation path for the libraries (Qt only) + + --prefix=<path> Set installation prefix to the given path (Gtk only) --makeargs=<arguments> Optional Makefile flags --minimal No optional features, unless explicitly enabled. @@ -234,6 +242,9 @@ EOF my %options = ( 'help' => \$showHelp, 'clean' => \$clean, + 'install-headers=s' => \$installHeaders, + 'install-libs=s' => \$installLibs, + 'prefix=s' => \$prefixPath, 'makeargs=s' => \$makeArgs, 'minimal' => \$minimal, ); @@ -257,6 +268,18 @@ setConfiguration(); my $productDir = productDir(); +# Remove 0 byte sized files from productDir after slave lost for Qt buildbots. +File::Find::find(\&unlinkZeroFiles, $productDir) if isQt(); + +sub unlinkZeroFiles () +{ + my $file = $File::Find::name; + if (! -s $file) { + unlink $file; + print "0 byte sized file removed from build directory: $file\n"; + } +} + # Check that all the project directories are there. my @projects = ("JavaScriptCore", "WebCore", "WebKit"); @@ -276,13 +299,15 @@ if (isGtk()) { push @options, autotoolsFlag(${$_->{value}}, $_->{option}); } + push @options, "--prefix=" . $prefixPath if defined($prefixPath); push @options, "--makeargs=" . $makeArgs if defined($makeArgs); } elsif (isAppleMacWebKit()) { push @options, XcodeOptions(); - sub option($$) + sub option($$$) { - my ($feature, $isEnabled) = @_; + my ($feature, $isEnabled, $defaultValue) = @_; + return "" if $defaultValue == $isEnabled; return $feature . "=" . ($isEnabled ? $feature : " "); } @@ -290,7 +315,8 @@ if (isGtk()) { if ($_->{option} eq "coverage") { push @options, XcodeCoverageSupportOptions() if $coverageSupport; } else { - push @options, option($_->{define}, ${$_->{value}}); + my $option = option($_->{define}, ${$_->{value}}, $_->{default}); + push @options, $option unless $option eq ""; } } @@ -338,12 +364,14 @@ if (isGtk()) { (system("perl WebKitTools/Scripts/update-webkit-support-libs") == 0) or die; } elsif (isQt()) { @options = @ARGV; + push @options, "--install-headers=" . $installHeaders if defined($installHeaders); + push @options, "--install-libs=" . $installLibs if defined($installLibs); push @options, "--makeargs=" . $makeArgs if defined($makeArgs); foreach (@features) { push @options, "DEFINES+=$_->{define}=${$_->{value}}" if ${$_->{value}} != $_->{default}; } - + if ($minimal) { push @options, "CONFIG+=minimal"; } diff --git a/WebKitTools/Scripts/do-webcore-rename b/WebKitTools/Scripts/do-webcore-rename index 56d8bed..32dd05e 100755 --- a/WebKitTools/Scripts/do-webcore-rename +++ b/WebKitTools/Scripts/do-webcore-rename @@ -67,9 +67,31 @@ sub wanted push @paths, $File::Find::name; } -my $isDOMTypeRename = 0; + +# Setting isDOMTypeRename to 1 rather than 0 expands the regexps used +# below to handle custom JavaScript bindings. +my $isDOMTypeRename = 1; my %renames = ( - "m_sel" => "m_selection", + "WebGLArray" => "ArrayBufferView", + "WebGLArrayBuffer" => "ArrayBuffer", + "WebGLByteArray" => "Int8Array", + "WebGLFloatArray" => "FloatArray", + "WebGLIntArray" => "Int32Array", + "WebGLIntegralTypedArrayBase" => "IntegralTypedArrayBase", + "WebGLShortArray" => "Int16Array", + "WebGLUnsignedByteArray" => "Uint8Array", + "WebGLUnsignedIntArray" => "Uint32Array", + "WebGLUnsignedShortArray" => "Uint16Array", + "WebGLTypedArrayBase" => "TypedArrayBase", + # JSDOMWindow constructors. + "webGLArrayBuffer" => "arrayBuffer", + "webGLByteArray" => "int8Array", + "webGLFloatArray" => "floatArray", + "webGLIntArray" => "int32Array", + "webGLShortArray" => "int16Array", + "webGLUnsignedByteArray" => "uint8Array", + "webGLUnsignedIntArray" => "uint32Array", + "webGLUnsignedShortArray" => "uint16Array", ); my %renamesContemplatedForTheFuture = ( @@ -142,12 +164,39 @@ my %renamesContemplatedForTheFuture = ( "NativeFunction" => "HostFunction", ); +# Sort the keys of the renames hash in order of decreasing length. This +# handles the case where some of the renames are substrings of others; +# i.e., "Foo" => "Bar" and "FooBuffer" => "BarBuffer". +my @sortedRenameKeys = sort { length($b) - length($a) } keys %renames; + # rename files +sub renameFile +{ + my $file = shift; + + if ($isDOMTypeRename) { + # Find the longest key in %renames which matches this more permissive regexp. + # (The old regexp would match ".../Foo.cpp" but not ".../JSFooCustom.cpp".) + # This handles renaming of custom JavaScript bindings even when some of the + # renames are substrings of others. The only reason we don't do this all the + # time is to avoid accidental file renamings for short, non-DOM renames. + for my $key (@sortedRenameKeys) { + my $newFile = ""; + $newFile = "$1$renames{$2}$3" if $file =~ /^(.*\/\w*)($key)(\w*\.\w+)$/; + if ($newFile ne "") { + return $newFile; + } + } + } else { + $file = "$1$renames{$2}$3" if $file =~ /^(.*\/)(\w+)(\.\w+)$/ && $renames{$2}; + } + return $file; +} + my %newFile; for my $file (sort @paths) { - my $f = $file; - $f = "$1$renames{$2}$3" if $f =~ /^(.*\/)(\w+)(\.\w+)$/ && $renames{$2}; + my $f = renameFile($file); if ($f ne $file) { $newFile{$file} = $f; } @@ -182,11 +231,12 @@ for my $file (sort @paths) { my $newContents = $contents; if ($isDOMTypeRename) { - for my $from (keys %renames) { - $newContents =~ s/\b$from/$renames{$from}/g; + for my $from (@sortedRenameKeys) { + # Handle JavaScript custom bindings. + $newContents =~ s/\b(JS|V8|to|)$from/$1$renames{$from}/g; } } else { - for my $from (keys %renames) { + for my $from (@sortedRenameKeys) { $newContents =~ s/\b$from(?!["\w])/$renames{$from}/g; # this " unconfuses Xcode syntax highlighting } } diff --git a/WebKitTools/Scripts/ensure-valid-python b/WebKitTools/Scripts/ensure-valid-python new file mode 100644 index 0000000..c21ad4e --- /dev/null +++ b/WebKitTools/Scripts/ensure-valid-python @@ -0,0 +1,135 @@ +#!/usr/bin/perl -w +# Copyright (C) 2010 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 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. +# 3. Neither the name of Apple Inc. ("Apple") 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 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. + +use strict; + +use File::Basename; +use File::Spec; +use File::Temp qw(tempdir); +use FindBin; + +use lib $FindBin::Bin; +use webkitdirs; +use VCSUtils; + +my $macPythonURL = "http://www.python.org/ftp/python/2.6.5/python-2.6.5-macosx10.3-2010-03-24.dmg"; +my $macPythonMD5 = "84489bba813fdbb6041b69d4310a86da"; +my $macPythonInstallerName = "Python.mpkg"; + +# We could use a consistent download location, like the source or build directory. +my $tempDirectory = File::Temp->newdir(); +my $downloadDirectory = $tempDirectory; +my $mountPoint = File::Spec->join($tempDirectory, "mount"); + +sub checkPythonVersion() +{ + # Will exit 0 if Python is 2.5 or greater, non-zero otherwise. + `python -c "import sys;sys.exit(sys.version_info[:2] < (2,5))"`; + return exitStatus($?) == 0; +} + +sub downloadFileToPath($$) +{ + my ($remoteURL, $localPath) = @_; + print "Downloading $remoteURL to $localPath\n"; + my $exitCode = system("curl", "-o", $localPath, $remoteURL); + return exitStatus($exitCode) == 0; +} + +sub checkMD5($$) +{ + my ($path, $expectedMD5) = @_; + my $md5Output = `md5 -q "$path"`; + chomp($md5Output); + my $isValid = $md5Output eq $expectedMD5; + print "'$md5Output' does not match expected: '$expectedMD5'\n" unless $isValid; + return $isValid; +} + +sub mountDMG($$) +{ + my ($dmgPath, $mountPoint) = @_; + print "Mounting $dmgPath at $mountPoint\n"; + return system("hdiutil", "attach", "-mountpoint", $mountPoint, "-nobrowse", $dmgPath) == 0; +} + +sub unmountDMG($) +{ + my ($mountPoint) = @_; + print "Unmounting disk image from $mountPoint\n"; + my $exitCode = system("hdiutil", "detach", $mountPoint); + return exitStatus($exitCode) == 0; +} + +sub runInstaller($) +{ + my ($installerPackage) = @_; + print "sudo will now ask for your password to run the Python installer.\n"; + print "The installer will install Python in /Library/Frameworks/Python.framework\n"; + print "and add symlinks from /usr/local/bin.\n"; + return system("sudo", "installer", "-verbose", "-pkg", $installerPackage, "-target", "/") == 0; +} + +sub downloadAndMountMacPythonDMG($$) +{ + my ($pythonURL, $pythonMD5) = @_; + my $localFilename = basename($pythonURL); + my $localPath = File::Spec->join($downloadDirectory, $localFilename); + + downloadFileToPath($pythonURL, $localPath) or die "Failed to download $pythonURL"; + checkMD5($localPath, $pythonMD5) or die "MD5 check failed on $localPath"; + return mountDMG($localPath, $mountPoint); +} + +sub installMacPython() +{ + downloadAndMountMacPythonDMG($macPythonURL, $macPythonMD5) or die "Failed to download and mount disk image."; + print "Mounted python install image at: $mountPoint\n"; + my $installerPackage = File::Spec->join($mountPoint, $macPythonInstallerName); + my $installSuccess = runInstaller($installerPackage); + unmountDMG($mountPoint) or die "Failed to unmount disk image from $mountPoint"; + return $installSuccess; +} + +sub main() +{ + # Congrats, your Python is fine. + return 0 if checkPythonVersion(); + + if (!isTiger()) { + print "Your Python version is insuficient to run WebKit's Python code. Please update.\n"; + print "See http://trac.webkit.org/wiki/PythonGuidelines for more info.\n"; + return 1; + } + + installMacPython() or die "Failed to install Python."; + + checkPythonVersion() or die "Final version check failed, must have failed to update Python"; + print "Successfully updated python.\n"; +} + +exit(main()); diff --git a/WebKitTools/Scripts/new-run-webkit-httpd b/WebKitTools/Scripts/new-run-webkit-httpd index 88ae84e..f6ec164 100755 --- a/WebKitTools/Scripts/new-run-webkit-httpd +++ b/WebKitTools/Scripts/new-run-webkit-httpd @@ -65,7 +65,6 @@ def run(options): tempfile.gettempdir(), port=options.port, root=options.root, - register_cygwin=options.register_cygwin, run_background=options.run_background) if options.server == 'start': httpd.start() diff --git a/WebKitTools/Scripts/new-run-webkit-websocketserver b/WebKitTools/Scripts/new-run-webkit-websocketserver index 8e4aeaa..4f6deaa 100644 --- a/WebKitTools/Scripts/new-run-webkit-websocketserver +++ b/WebKitTools/Scripts/new-run-webkit-websocketserver @@ -42,25 +42,32 @@ def main(): option_parser = optparse.OptionParser() option_parser.add_option('--server', type='choice', choices=['start', 'stop'], default='start', - help='Server action (start|stop)') + help='Server action (start|stop).') option_parser.add_option('-p', '--port', dest='port', - default=None, help='Port to listen on') + default=None, help='Port to listen on.') option_parser.add_option('-r', '--root', help='Absolute path to DocumentRoot ' - '(overrides layout test roots)') + '(overrides layout test roots).') option_parser.add_option('-t', '--tls', dest='use_tls', action='store_true', - default=False, help='use TLS (wss://)') + default=False, help='use TLS (wss://).') option_parser.add_option('-k', '--private_key', dest='private_key', default='', help='TLS private key file.') option_parser.add_option('-c', '--certificate', dest='certificate', default='', help='TLS certificate file.') + option_parser.add_option('--chromium', action='store_true', + dest='chromium', + default=False, + help='Use the Chromium port.') option_parser.add_option('--register_cygwin', action="store_true", dest="register_cygwin", - help='Register Cygwin paths (on Win try bots)') + help='Register Cygwin paths (on Win try bots).') option_parser.add_option('--pidfile', help='path to pid file.') + option_parser.add_option('--output-dir', dest='output_dir', + default=None, help='output directory.') option_parser.add_option('-v', '--verbose', action='store_true', - default=False, help='include debug-level logging') + default=False, + help='Include debug-level logging.') options, args = option_parser.parse_args() if not options.port: @@ -71,6 +78,9 @@ def main(): # FIXME: We shouldn't grab at this private variable. options.port = websocket_server._DEFAULT_WS_PORT + if not options.output_dir: + options.output_dir = tempfile.gettempdir() + kwds = {'port': options.port, 'use_tls': options.use_tls} if options.root: kwds['root'] = options.root @@ -78,12 +88,11 @@ def main(): kwds['private_key'] = options.private_key if options.certificate: kwds['certificate'] = options.certificate - kwds['register_cygwin'] = options.register_cygwin if options.pidfile: kwds['pidfile'] = options.pidfile port_obj = factory.get() - pywebsocket = websocket_server.PyWebSocket(port_obj, tempfile.gettempdir(), **kwds) + pywebsocket = websocket_server.PyWebSocket(port_obj, options.output_dir, **kwds) log_level = logging.WARN if options.verbose: diff --git a/WebKitTools/Scripts/old-run-webkit-tests b/WebKitTools/Scripts/old-run-webkit-tests index f6dbf5b..783606d 100755 --- a/WebKitTools/Scripts/old-run-webkit-tests +++ b/WebKitTools/Scripts/old-run-webkit-tests @@ -557,7 +557,7 @@ my $lastDirectory = ""; my $isHttpdOpen = 0; my $isWebSocketServerOpen = 0; -my $webSocketServerPID = 0; +my $webSocketServerPidFile = 0; my $failedToStartWebSocketServer = 0; # wss is disabled until all platforms support pyOpenSSL. # my $webSocketSecureServerPID = 0; @@ -1426,54 +1426,19 @@ sub openWebSocketServerIfNeeded() return 1 if $isWebSocketServerOpen; return 0 if $failedToStartWebSocketServer; - my $webSocketServerPath = "/usr/bin/python"; - my $webSocketPythonPath = "WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket"; my $webSocketHandlerDir = "$testDirectory"; - my $webSocketHandlerScanDir = "$testDirectory/websocket/tests"; - my $webSocketHandlerMapFile = "$webSocketHandlerScanDir/handler_map.txt"; - my $sslCertificate = "$testDirectory/http/conf/webkit-httpd.pem"; my $absTestResultsDirectory = resolveAndMakeTestResultsDirectory(); - my $logFile = "$absTestResultsDirectory/pywebsocket_log.txt"; + $webSocketServerPidFile = "$absTestResultsDirectory/websocket.pid"; my @args = ( - "WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/standalone.py", - "--server-host", "127.0.0.1", + "WebKitTools/Scripts/new-run-webkit-websocketserver", + "--server", "start", "--port", "$webSocketPort", - "--document-root", "$webSocketHandlerDir", - "--scan-dir", "$webSocketHandlerScanDir", - "--websock-handlers-map-file", "$webSocketHandlerMapFile", - "--cgi-paths", "/websocket/tests", - "--log-file", "$logFile", - "--strict", + "--root", "$webSocketHandlerDir", + "--output-dir", "$absTestResultsDirectory", + "--pidfile", "$webSocketServerPidFile" ); - # wss is disabled until all platforms support pyOpenSSL. - # my @argsSecure = ( - # "WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/standalone.py", - # "-p", "$webSocketSecurePort", - # "-d", "$webSocketHandlerDir", - # "-t", - # "-k", "$sslCertificate", - # "-c", "$sslCertificate", - # ); - - $ENV{"PYTHONPATH"} = $webSocketPythonPath; - $webSocketServerPID = open3(\*WEBSOCKETSERVER_IN, \*WEBSOCKETSERVER_OUT, \*WEBSOCKETSERVER_ERR, $webSocketServerPath, @args); - # wss is disabled until all platforms support pyOpenSSL. - # $webSocketSecureServerPID = open3(\*WEBSOCKETSECURESERVER_IN, \*WEBSOCKETSECURESERVER_OUT, \*WEBSOCKETSECURESERVER_ERR, $webSocketServerPath, @argsSecure); - # my @listen = ("http://127.0.0.1:$webSocketPort", "https://127.0.0.1:$webSocketSecurePort"); - my @listen = ("http://127.0.0.1:$webSocketPort"); - for (my $i = 0; $i < @listen; $i++) { - my $retryCount = 10; - while (system("/usr/bin/curl -k -q --silent --stderr - --output /dev/null $listen[$i]") && $retryCount) { - sleep 1; - --$retryCount; - } - unless ($retryCount) { - print STDERR "Timed out waiting for WebSocketServer to start.\n"; - $failedToStartWebSocketServer = 1; - return 0; - } - } + system "/usr/bin/python", @args; $isWebSocketServerOpen = 1; return 1; @@ -1483,17 +1448,15 @@ sub closeWebSocketServer() { return if !$isWebSocketServerOpen; - close WEBSOCKETSERVER_IN; - close WEBSOCKETSERVER_OUT; - close WEBSOCKETSERVER_ERR; - kill 15, $webSocketServerPID; + my @args = ( + "WebKitTools/Scripts/new-run-webkit-websocketserver", + "--server", "stop", + "--pidfile", "$webSocketServerPidFile" + ); + system "/usr/bin/python", @args; + unlink "$webSocketServerPidFile"; # wss is disabled until all platforms support pyOpenSSL. - # close WEBSOCKETSECURESERVER_IN; - # close WEBSOCKETSECURESERVER_OUT; - # close WEBSOCKETSECURESERVER_ERR; - # kill 15, $webSocketSecureServerPID; - $isWebSocketServerOpen = 0; } @@ -2277,7 +2240,8 @@ sub stopRunningTestsEarlyIfNeeded() return 0 if !$exitAfterNFailures || $resetResults; my $passCount = $counts{match} || 0; # $counts{match} will be undefined if we've not yet passed a test (e.g. the first test fails). - my $failureCount = $count - $passCount; # "Failure" here includes new tests, timeouts, crashes, etc. + my $newCount = $counts{new} || 0; + my $failureCount = $count - $passCount - $newCount; # "Failure" here includes timeouts, crashes, etc. return 0 if $failureCount < $exitAfterNFailures; print "\nExiting early after $failureCount failures. $count tests run."; diff --git a/WebKitTools/Scripts/run-launcher b/WebKitTools/Scripts/run-launcher index bc00aac..414d4af 100755 --- a/WebKitTools/Scripts/run-launcher +++ b/WebKitTools/Scripts/run-launcher @@ -47,7 +47,7 @@ checkFrameworks(); # Set paths according to the build system used if (isQt()) { my $libDir = catdir(productDir(), 'lib'); - $launcherPath = catdir($launcherPath, "bin", "QtLauncher"); + $launcherPath = catdir($launcherPath, "bin", "QtTestBrowser"); $ENV{QTWEBKIT_PLUGIN_PATH} = catdir($libDir, 'plugins'); diff --git a/WebKitTools/Scripts/run-webkit-websocketserver b/WebKitTools/Scripts/run-webkit-websocketserver index 06f9079..08d430b 100755 --- a/WebKitTools/Scripts/run-webkit-websocketserver +++ b/WebKitTools/Scripts/run-webkit-websocketserver @@ -47,7 +47,7 @@ my $webSocketPort = 8880; my $srcDir = sourceDir(); my $layoutTestsName = "$srcDir/LayoutTests"; my $testDirectory = File::Spec->rel2abs($layoutTestsName); -my $webSocketServerPID = 0; +my $webSocketServerPidFile = "$testDirectory/websocket.pid"; print "Starting Web Socket server...\n"; @@ -60,41 +60,29 @@ closeWebSocketServer(); print "Stopped.\n"; exit 0; - sub openWebSocketServer() { - my $webSocketServerPath = "/usr/bin/python"; - my $webSocketPythonPath = "$srcDir/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket"; my $webSocketHandlerDir = "$testDirectory"; - my $webSocketHandlerScanDir = "$testDirectory/websocket/tests"; - my $webSocketHandlerMapFile = "$webSocketHandlerScanDir/handler_map.txt"; my @args = ( - "$srcDir/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/standalone.py", - "--server-host", "127.0.0.1", + "$srcDir/WebKitTools/Scripts/new-run-webkit-websocketserver", + "--server", "start", "--port", "$webSocketPort", - "--document-root", "$webSocketHandlerDir", - "--scan-dir", "$webSocketHandlerScanDir", - "--websock-handlers-map-file", "$webSocketHandlerMapFile", - "--cgi-paths", "/websocket/tests", + "--root", "$webSocketHandlerDir", + "--pidfile", "$webSocketServerPidFile" ); - - $ENV{"PYTHONPATH"} = $webSocketPythonPath; - $webSocketServerPID = open2(\*WEBSOCKETSERVER_IN, \*WEBSOCKETSERVER_OUT, $webSocketServerPath, @args); - - my $listen = "http://127.0.0.1:$webSocketPort"; - my $retryCount = 10; - while (system("/usr/bin/curl -k -q --silent --stderr - --output /dev/null $listen") && $retryCount) { - sleep 1; - --$retryCount; - } - die "Timed out waiting for WebSocketServer to start" unless $retryCount; + system "/usr/bin/python", @args; } sub closeWebSocketServer() { - close WEBSOCKETSERVER_IN; - close WEBSOCKETSERVER_OUT; - kill 15, $webSocketServerPID; + my @args = ( + "$srcDir/WebKitTools/Scripts/new-run-webkit-websocketserver", + "--server", "stop", + "--pidfile", "$webSocketServerPidFile" + ); + system "/usr/bin/python", @args; + unlink "$webSocketServerPidFile"; } + diff --git a/WebKitTools/Scripts/svn-apply b/WebKitTools/Scripts/svn-apply index b49ccec..33b2279 100755 --- a/WebKitTools/Scripts/svn-apply +++ b/WebKitTools/Scripts/svn-apply @@ -42,8 +42,7 @@ # Paths from Index: lines are used rather than the paths on the patch lines, which # makes patches generated by "cvs diff" work (increasingly unimportant since we # use Subversion now). -# ChangeLog patches use --fuzz=3 to prevent rejects, and the entry date is set in -# the patch to today's date using $changeLogTimeZone. +# ChangeLog patches use --fuzz=3 to prevent rejects. # Handles binary files (requires patches made by svn-create-patch). # Handles copied and moved files (requires patches made by svn-create-patch). # Handles git-diff patches (without binary changes) created at the top-level directory @@ -80,7 +79,6 @@ sub handleGitBinaryChange($$); sub isDirectoryEmptyForRemoval($); sub patch($); sub removeDirectoriesIfNeeded(); -sub setChangeLogDateAndReviewer($$); # These should be replaced by an scm class/module: sub scmKnowsOfFile($); @@ -88,10 +86,6 @@ sub scmCopy($$); sub scmAdd($); sub scmRemove($); - -# Project time zone for Cupertino, CA, US -my $changeLogTimeZone = "PST8PDT"; - my $merge = 0; my $showHelp = 0; my $reviewer; @@ -117,12 +111,12 @@ my %removeDirectoryIgnoreList = ( '_svn' => 1, ); +my $epochTime = time(); # This is used to set the date in ChangeLog files. my $globalExitStatus = 0; my $repositoryRootPath = determineVCSRoot(); my %checkedDirectories; -my %copiedFiles; # Need to use a typeglob to pass the file handle as a parameter, # otherwise get a bareword error. @@ -154,8 +148,6 @@ for my $copyDiffHashRef (@copyDiffHashRefs) { addDirectoriesIfNeeded(dirname($indexPath)); scmCopy($copiedFromPath, $indexPath); - - $copiedFiles{$indexPath} = $copiedFromPath; } for my $diffHashRef (@nonCopyDiffHashRefs) { @@ -247,14 +239,16 @@ sub handleBinaryChange($$) sub handleGitBinaryChange($$) { - my ($fullPath, $contents) = @_; + my ($fullPath, $diffHashRef) = @_; + + my $contents = $diffHashRef->{svnConvertedText}; my ($binaryChunkType, $binaryChunk, $reverseBinaryChunkType, $reverseBinaryChunk) = decodeGitBinaryPatch($contents, $fullPath); # FIXME: support "delta" type. die "only literal type is supported now" if ($binaryChunkType ne "literal" || $reverseBinaryChunkType ne "literal"); - my $isFileAddition = $contents =~ /\nnew file mode \d+\n/; - my $isFileDeletion = $contents =~ /\ndeleted file mode \d+\n/; + my $isFileAddition = $diffHashRef->{isNew}; + my $isFileDeletion = $diffHashRef->{isDeletion}; my $originalContents = ""; if (open FILE, $fullPath) { @@ -303,7 +297,10 @@ sub patch($) { my ($diffHashRef) = @_; - my $patch = $diffHashRef->{svnConvertedText}; + # Make sure $patch is initialized to some value. A deletion can have no + # svnConvertedText property in the case of a deletion resulting from a + # Git rename. + my $patch = $diffHashRef->{svnConvertedText} || ""; my $fullPath = $diffHashRef->{indexPath}; my $isBinary = $diffHashRef->{isBinary}; @@ -312,19 +309,18 @@ sub patch($) my $deletion = 0; my $addition = 0; - # FIXME: This information should be extracted from the diff file as - # part of the parsing stage, i.e. the call to parsePatch(). - $addition = 1 if ($patch =~ /\n--- .+\(revision 0\)\r?\n/ || $patch =~ /\n@@ -0,0 .* @@/) && !exists($copiedFiles{$fullPath}); - $deletion = 1 if $patch =~ /\n@@ .* \+0,0 @@/; + $addition = 1 if ($diffHashRef->{isNew} || $patch =~ /\n@@ -0,0 .* @@/); + $deletion = 1 if ($diffHashRef->{isDeletion} || $patch =~ /\n@@ .* \+0,0 @@/); if (!$addition && !$deletion && !$isBinary) { # Standard patch, patch tool can handle this. if (basename($fullPath) eq "ChangeLog") { my $changeLogDotOrigExisted = -f "${fullPath}.orig"; - applyPatch(setChangeLogDateAndReviewer(fixChangeLogPatch($patch), $reviewer), $fullPath, ["--fuzz=3"]); + my $newPatch = setChangeLogDateAndReviewer(fixChangeLogPatch($patch), $reviewer, $epochTime); + applyPatch($newPatch, $fullPath, ["--fuzz=3"]); unlink("${fullPath}.orig") if (! $changeLogDotOrigExisted); } else { - applyPatch($patch, $fullPath); + applyPatch($patch, $fullPath) if $patch; } } else { # Either a deletion, an addition or a binary change. @@ -333,18 +329,17 @@ sub patch($) if ($isBinary) { if ($isGit) { - handleGitBinaryChange($fullPath, $patch); + handleGitBinaryChange($fullPath, $diffHashRef); } else { - handleBinaryChange($fullPath, $patch); + handleBinaryChange($fullPath, $patch) if $patch; } } elsif ($deletion) { - # Deletion - applyPatch($patch, $fullPath, ["--force"]); + applyPatch($patch, $fullPath, ["--force"]) if $patch; scmRemove($fullPath); } else { # Addition rename($fullPath, "$fullPath.orig") if -e $fullPath; - applyPatch($patch, $fullPath); + applyPatch($patch, $fullPath) if $patch; unlink("$fullPath.orig") if -e "$fullPath.orig" && checksum($fullPath) eq checksum("$fullPath.orig"); scmAdd($fullPath); # What is this for? @@ -364,26 +359,6 @@ sub removeDirectoriesIfNeeded() } } -sub setChangeLogDateAndReviewer($$) -{ - my $patch = shift; - my $reviewer = shift; - my $savedTimeZone = $ENV{'TZ'}; - # Set TZ temporarily so that localtime() is in that time zone - $ENV{'TZ'} = $changeLogTimeZone; - my $newDate = strftime("%Y-%m-%d", localtime()); - if (defined $savedTimeZone) { - $ENV{'TZ'} = $savedTimeZone; - } else { - delete $ENV{'TZ'}; - } - $patch =~ s/(\n\+)\d{4}-[^-]{2}-[^-]{2}( )/$1$newDate$2/; - if (defined($reviewer)) { - $patch =~ s/NOBODY \(OOPS!\)/$reviewer/; - } - return $patch; -} - # This could be made into a more general "status" call, except svn and git # have different ideas about "moving" files which might get confusing. sub scmWillDeleteFile($) @@ -399,6 +374,22 @@ sub scmWillDeleteFile($) return 0; } +# Return whether the file at the given path is known to Git. +# +# This method outputs a message like the following to STDERR when +# returning false: +# +# "error: pathspec 'test.png' did not match any file(s) known to git. +# Did you forget to 'git add'?" +sub gitKnowsOfFile($) +{ + my $path = shift; + + `git ls-files --error-unmatch -- $path`; + my $exitStatus = exitStatus($?); + return $exitStatus == 0; +} + sub scmKnowsOfFile($) { my ($path) = @_; @@ -411,9 +402,8 @@ sub scmKnowsOfFile($) # This does not handle errors well. return 1; } elsif (isGit()) { - `git ls-files --error-unmatch -- $path`; - my $exitCode = $? >> 8; - return $exitCode == 0; + my @result = callSilently(\&gitKnowsOfFile, $path); + return $result[0]; } } diff --git a/WebKitTools/Scripts/svn-unapply b/WebKitTools/Scripts/svn-unapply index 2ef7249..e502560 100755 --- a/WebKitTools/Scripts/svn-unapply +++ b/WebKitTools/Scripts/svn-unapply @@ -149,10 +149,8 @@ sub patch($) my $deletion = 0; my $addition = 0; - # FIXME: This information should be extracted from the diff file as - # part of the parsing stage, i.e. the call to parsePatch(). - $addition = 1 if ($diffHashRef->{copiedFromPath} || $patch =~ /\n--- .+\(revision 0\)\n/ || $patch =~ /\n@@ -0,0 .* @@/); - $deletion = 1 if $patch =~ /\n@@ .* \+0,0 @@/; + $addition = 1 if ($diffHashRef->{isNew} || $diffHashRef->{copiedFromPath} || $patch =~ /\n@@ -0,0 .* @@/); + $deletion = 1 if ($diffHashRef->{isDeletion} || $patch =~ /\n@@ .* \+0,0 @@/); if (!$addition && !$deletion && !$isSvnBinary) { # Standard patch, patch tool can handle this. diff --git a/WebKitTools/Scripts/webkitdirs.pm b/WebKitTools/Scripts/webkitdirs.pm index 4fecf6b..c512009 100644 --- a/WebKitTools/Scripts/webkitdirs.pm +++ b/WebKitTools/Scripts/webkitdirs.pm @@ -891,7 +891,7 @@ sub launcherName() if (isGtk()) { return "GtkLauncher"; } elsif (isQt()) { - return "QtLauncher"; + return "QtTestBrowser"; } elsif (isWx()) { return "wxBrowser"; } elsif (isAppleWebKit()) { @@ -1239,7 +1239,7 @@ sub buildAutotoolsProject($@) my $make = 'make'; my $dir = productDir(); my $config = passedConfiguration() || configuration(); - my $prefix = $ENV{"WebKitInstallationPrefix"}; + my $prefix; my @buildArgs = (); my $makeArgs = $ENV{"WebKitMakeArguments"} || ""; @@ -1247,11 +1247,14 @@ sub buildAutotoolsProject($@) my $opt = $buildParams[$i]; if ($opt =~ /^--makeargs=(.*)/i ) { $makeArgs = $makeArgs . " " . $1; + } elsif ($opt =~ /^--prefix=(.*)/i ) { + $prefix = $1; } else { push @buildArgs, $opt; } } + $prefix = $ENV{"WebKitInstallationPrefix"} if !defined($prefix); push @buildArgs, "--prefix=" . $prefix if defined($prefix); # check if configuration is Debug @@ -1282,7 +1285,7 @@ sub buildAutotoolsProject($@) } print "Calling configure in " . $dir . "\n\n"; - print "Installation directory: $prefix\n" if(defined($prefix)); + print "Installation prefix directory: $prefix\n" if(defined($prefix)); # Make the path relative since it will appear in all -I compiler flags. # Long argument lists cause bizarre slowdowns in libtool. @@ -1311,6 +1314,8 @@ sub buildQMakeProject($@) my $qmakebin = "qmake"; # Allow override of the qmake binary from $PATH my $makeargs = ""; + my $installHeaders; + my $installLibs; for my $i (0 .. $#buildParams) { my $opt = $buildParams[$i]; if ($opt =~ /^--qmake=(.*)/i ) { @@ -1319,6 +1324,10 @@ sub buildQMakeProject($@) push @buildArgs, $1; } elsif ($opt =~ /^--makeargs=(.*)/i ) { $makeargs = $1; + } elsif ($opt =~ /^--install-headers=(.*)/i ) { + $installHeaders = $1; + } elsif ($opt =~ /^--install-libs=(.*)/i ) { + $installLibs = $1; } else { push @buildArgs, $opt; } @@ -1326,7 +1335,8 @@ sub buildQMakeProject($@) my $make = qtMakeCommand($qmakebin); my $config = configuration(); - my $prefix = $ENV{"WebKitInstallationPrefix"}; + push @buildArgs, "INSTALL_HEADERS=" . $installHeaders if defined($installHeaders); + push @buildArgs, "INSTALL_LIBS=" . $installLibs if defined($installLibs); my $dir = File::Spec->canonpath(baseProductDir()); $dir = File::Spec->catfile($dir, $config) unless isSymbian(); File::Path::mkpath($dir); @@ -1378,7 +1388,8 @@ sub buildQMakeProject($@) } print "Calling '$qmakebin @buildArgs' in " . $dir . "\n\n"; - print "Installation directory: $prefix\n" if(defined($prefix)); + print "Installation headers directory: $installHeaders\n" if(defined($installHeaders)); + print "Installation libraries directory: $installLibs\n" if(defined($installLibs)); $result = system "$qmakebin @buildArgs"; if ($result ne 0) { @@ -1427,7 +1438,8 @@ sub buildChromiumMakefile($$$) return system qw(rm -rf out); } my $config = configuration(); - my @command = ("make", "-j4", "BUILDTYPE=$config", $target); + my $numCpus = (grep /processor/, `cat /proc/cpuinfo`) || 1; + my @command = ("make", "-j$numCpus", "BUILDTYPE=$config", $target); print join(" ", @command) . "\n"; return system @command; } diff --git a/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parseDiff.pl b/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parseDiff.pl index 9f112b2..6af7da4 100644 --- a/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parseDiff.pl +++ b/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parseDiff.pl @@ -47,7 +47,7 @@ Index: Makefile all: END expectedReturn => [ -{ +[{ svnConvertedText => <<'END', # Same as input text Index: Makefile =================================================================== @@ -62,7 +62,7 @@ END indexPath => "Makefile", isSvn => 1, sourceRevision => "53052", -}, +}], undef], expectedNextLine => undef, }, @@ -84,7 +84,7 @@ Name: svn:mime-type Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA== END expectedReturn => [ -{ +[{ svnConvertedText => <<'END', # Same as input text Index: test_file.swf =================================================================== @@ -102,7 +102,7 @@ END indexPath => "test_file.swf", isBinary => 1, isSvn => 1, -}, +}], undef], expectedNextLine => undef, }, @@ -124,7 +124,7 @@ Index: Makefile all: END expectedReturn => [ -{ +[{ svnConvertedText => <<'END', # Same as input text LEADING JUNK @@ -142,7 +142,7 @@ END indexPath => "Makefile", isSvn => 1, sourceRevision => "53052", -}, +}], undef], expectedNextLine => undef, }, @@ -158,20 +158,11 @@ Index: Makefile_new +MODULES = JavaScriptCore JavaScriptGlue WebCore WebKit WebKitTools END expectedReturn => [ -{ - svnConvertedText => <<'END', # Same as input text -Index: Makefile_new -=================================================================== ---- Makefile_new (revision 53131) (from Makefile:53131) -+++ Makefile_new (working copy) -@@ -0,0 +1,1 @@ -+MODULES = JavaScriptCore JavaScriptGlue WebCore WebKit WebKitTools -END +[{ copiedFromPath => "Makefile", indexPath => "Makefile_new", - isSvn => 1, sourceRevision => "53131", -}, +}], undef], expectedNextLine => undef, }, @@ -190,7 +181,7 @@ Index: Makefile_new --- Makefile_new (revision 53131) (from Makefile:53131) END expectedReturn => [ -{ +[{ svnConvertedText => <<'END', Index: Makefile =================================================================== @@ -202,7 +193,7 @@ END indexPath => "Makefile", isSvn => 1, sourceRevision => "53131", -}, +}], "Index: Makefile_new\n"], expectedNextLine => "===================================================================\n", }, @@ -223,7 +214,7 @@ index f5d5e74..3b6aa92 100644 @@ -1,1 1,1 @@ public: END expectedReturn => [ -{ +[{ svnConvertedText => <<'END', # Same as input text Index: Makefile =================================================================== @@ -240,7 +231,7 @@ END indexPath => "Makefile", isSvn => 1, sourceRevision => "53131", -}, +}], undef], expectedNextLine => undef, }, @@ -258,7 +249,7 @@ index f5d5e74..3b6aa92 100644 @@ -1,1 1,1 @@ public: END expectedReturn => [ -{ +[{ svnConvertedText => <<'END', Index: Makefile index f5d5e74..3b6aa92 100644 @@ -268,10 +259,72 @@ index f5d5e74..3b6aa92 100644 END indexPath => "Makefile", isGit => 1, -}, +}], undef], expectedNextLine => undef, }, +{ # New test + diffName => "Git: new file", + inputText => <<'END', +diff --git a/foo.h b/foo.h +new file mode 100644 +index 0000000..3c9f114 +--- /dev/null ++++ b/foo.h +@@ -0,0 +1,34 @@ ++<html> +diff --git a/bar b/bar +index d45dd40..3494526 100644 +END + expectedReturn => [ +[{ + svnConvertedText => <<'END', +Index: foo.h +new file mode 100644 +index 0000000..3c9f114 +--- foo.h ++++ foo.h +@@ -0,0 +1,34 @@ ++<html> +END + indexPath => "foo.h", + isGit => 1, + isNew => 1, +}], +"diff --git a/bar b/bar\n"], + expectedNextLine => "index d45dd40..3494526 100644\n", +}, +{ # New test + diffName => "Git: file deletion", + inputText => <<'END', +diff --git a/foo b/foo +deleted file mode 100644 +index 1e50d1d..0000000 +--- a/foo ++++ /dev/null +@@ -1,1 +0,0 @@ +-line1 +diff --git a/bar b/bar +index d45dd40..3494526 100644 +END + expectedReturn => [ +[{ + svnConvertedText => <<'END', +Index: foo +deleted file mode 100644 +index 1e50d1d..0000000 +--- foo ++++ foo +@@ -1,1 +0,0 @@ +-line1 +END + indexPath => "foo", + isDeletion => 1, + isGit => 1, +}], +"diff --git a/bar b/bar\n"], + expectedNextLine => "index d45dd40..3494526 100644\n", +}, { # New test diffName => "Git: Git diff followed by SVN diff", # Should not recognize SVN start @@ -286,7 +339,7 @@ Index: Makefile_new --- Makefile_new (revision 53131) (from Makefile:53131) END expectedReturn => [ -{ +[{ svnConvertedText => <<'END', Index: Makefile index f5d5e74..3b6aa92 100644 @@ -299,10 +352,119 @@ Index: Makefile_new END indexPath => "Makefile", isGit => 1, -}, +}], undef], expectedNextLine => undef, }, +#### +# Git test cases: file moves (multiple return values) +## +{ + diffName => "Git: rename (with similarity index 100%)", + inputText => <<'END', +diff --git a/foo b/foo_new +similarity index 100% +rename from foo +rename to foo_new +diff --git a/bar b/bar +index d45dd40..3494526 100644 +END + expectedReturn => [ +[{ + indexPath => "foo", + isDeletion => 1, +}, +{ + copiedFromPath => "foo", + indexPath => "foo_new", +}], +"diff --git a/bar b/bar\n"], + expectedNextLine => "index d45dd40..3494526 100644\n", +}, +{ + diffName => "rename (with similarity index < 100%)", + inputText => <<'END', +diff --git a/foo b/foo_new +similarity index 99% +rename from foo +rename to foo_new +index 1e50d1d..1459d21 100644 +--- a/foo ++++ b/foo_new +@@ -15,3 +15,4 @@ release r deployment dep deploy: + line1 + line2 + line3 ++line4 +diff --git a/bar b/bar +index d45dd40..3494526 100644 +END + expectedReturn => [ +[{ + indexPath => "foo", + isDeletion => 1, +}, +{ + copiedFromPath => "foo", + indexPath => "foo_new", +}, +{ + indexPath => "foo_new", + isGit => 1, + svnConvertedText => <<'END', +Index: foo_new +similarity index 99% +rename from foo +rename to foo_new +index 1e50d1d..1459d21 100644 +--- foo_new ++++ foo_new +@@ -15,3 +15,4 @@ release r deployment dep deploy: + line1 + line2 + line3 ++line4 +END +}], +"diff --git a/bar b/bar\n"], + expectedNextLine => "index d45dd40..3494526 100644\n", +}, +{ + diffName => "rename (with executable bit change)", + inputText => <<'END', +diff --git a/foo b/foo_new +old mode 100644 +new mode 100755 +similarity index 100% +rename from foo +rename to foo_new +diff --git a/bar b/bar +index d45dd40..3494526 100644 +END + expectedReturn => [ +[{ + indexPath => "foo", + isDeletion => 1, +}, +{ + copiedFromPath => "foo", + indexPath => "foo_new", +}, +{ + indexPath => "foo_new", + isGit => 1, + svnConvertedText => <<'END', +Index: foo_new +old mode 100644 +new mode 100755 +similarity index 100% +rename from foo +rename to foo_new +END +}], +"diff --git a/bar b/bar\n"], + expectedNextLine => "index d45dd40..3494526 100644\n", +}, ); my $testCasesCount = @testCaseHashRefs; diff --git a/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parseGitDiffHeader.pl b/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parseGitDiffHeader.pl index 9e2a88d..bc0d4d4 100644 --- a/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parseGitDiffHeader.pl +++ b/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parseGitDiffHeader.pl @@ -57,7 +57,7 @@ END expectedNextLine => "-file contents\n", }, { # New test - diffName => "New file", + diffName => "new file", inputText => <<'END', diff --git a/foo.h b/foo.h new file mode 100644 @@ -77,11 +77,40 @@ index 0000000..3c9f114 +++ foo.h END indexPath => "foo.h", + isNew => 1, }, "@@ -0,0 +1,34 @@\n"], expectedNextLine => "+<html>\n", }, { # New test + diffName => "file deletion", + inputText => <<'END', +diff --git a/foo b/foo +deleted file mode 100644 +index 1e50d1d..0000000 +--- a/foo ++++ /dev/null +@@ -1,1 +0,0 @@ +-line1 +diff --git a/configure.ac b/configure.ac +index d45dd40..3494526 100644 +END + expectedReturn => [ +{ + svnConvertedText => <<'END', +Index: foo +deleted file mode 100644 +index 1e50d1d..0000000 +--- foo ++++ foo +END + indexPath => "foo", + isDeletion => 1, +}, +"@@ -1,1 +0,0 @@\n"], + expectedNextLine => "-line1\n", +}, +{ # New test diffName => "using --no-prefix", inputText => <<'END', diff --git foo.h foo.h @@ -149,7 +178,102 @@ similarity index 99% copy from foo copy to foo_new END + copiedFromPath => "foo", + indexPath => "foo_new", + isCopyWithChanges => 1, +}, +"diff --git a/bar b/bar\n"], + expectedNextLine => "index d45dd40..3494526 100644\n", +}, +{ # New test + diffName => "rename (with similarity index 100%)", + inputText => <<'END', +diff --git a/foo b/foo_new +similarity index 100% +rename from foo +rename to foo_new +diff --git a/bar b/bar +index d45dd40..3494526 100644 +END + expectedReturn => [ +{ + svnConvertedText => <<'END', +Index: foo_new +similarity index 100% +rename from foo +rename to foo_new +END + copiedFromPath => "foo", + indexPath => "foo_new", + shouldDeleteSource => 1, +}, +"diff --git a/bar b/bar\n"], + expectedNextLine => "index d45dd40..3494526 100644\n", +}, +{ # New test + diffName => "rename (with similarity index < 100%)", + inputText => <<'END', +diff --git a/foo b/foo_new +similarity index 99% +rename from foo +rename to foo_new +index 1e50d1d..1459d21 100644 +--- a/foo ++++ b/foo_new +@@ -15,3 +15,4 @@ release r deployment dep deploy: + line1 + line2 + line3 ++line4 +diff --git a/bar b/bar +index d45dd40..3494526 100644 +END + expectedReturn => [ +{ + svnConvertedText => <<'END', +Index: foo_new +similarity index 99% +rename from foo +rename to foo_new +index 1e50d1d..1459d21 100644 +--- foo_new ++++ foo_new +END + copiedFromPath => "foo", + indexPath => "foo_new", + isCopyWithChanges => 1, + shouldDeleteSource => 1, +}, +"@@ -15,3 +15,4 @@ release r deployment dep deploy:\n"], + expectedNextLine => " line1\n", +}, +{ # New test + diffName => "rename (with executable bit change)", + inputText => <<'END', +diff --git a/foo b/foo_new +old mode 100644 +new mode 100755 +similarity index 100% +rename from foo +rename to foo_new +diff --git a/bar b/bar +index d45dd40..3494526 100644 +END + expectedReturn => [ +{ + svnConvertedText => <<'END', +Index: foo_new +old mode 100644 +new mode 100755 +similarity index 100% +rename from foo +rename to foo_new +END + copiedFromPath => "foo", + executableBitDelta => 1, indexPath => "foo_new", + isCopyWithChanges => 1, + shouldDeleteSource => 1, }, "diff --git a/bar b/bar\n"], expectedNextLine => "index d45dd40..3494526 100644\n", @@ -182,6 +306,7 @@ GIT binary patch END indexPath => "foo.gif", isBinary => 1, + isNew => 1, }, "literal 7\n"], expectedNextLine => "OcmYex&reDa;sO8*F9L)B\n", @@ -211,6 +336,7 @@ GIT binary patch END indexPath => "foo.gif", isBinary => 1, + isDeletion => 1, }, "literal 0\n"], expectedNextLine => "HcmV?d00001\n", @@ -312,6 +438,7 @@ index 0000000..d03e242 END executableBitDelta => 1, indexPath => "foo", + isNew => 1, }, "@@ -0,0 +1 @@\n"], expectedNextLine => "+file contents\n", @@ -340,6 +467,7 @@ index d03e242..0000000 END executableBitDelta => -1, indexPath => "foo", + isDeletion => 1, }, "@@ -1 +0,0 @@\n"], expectedNextLine => "-file contents\n", diff --git a/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parsePatch.pl b/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parsePatch.pl index e6f82ca..8aae3d4 100644 --- a/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parsePatch.pl +++ b/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parsePatch.pl @@ -69,14 +69,6 @@ END sourceRevision => "53131", }, { - svnConvertedText => <<'END', -Index: Makefile_new -=================================================================== ---- Makefile_new (revision 53131) (from Makefile:53131) -+++ Makefile_new (working copy) -@@ -0,0 +1,1 @@ -+MODULES = JavaScriptCore JavaScriptGlue WebCore WebKit WebKitTools -END copiedFromPath => "Makefile", indexPath => "Makefile_new", sourceRevision => "53131", diff --git a/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parseSvnDiffHeader.pl b/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parseSvnDiffHeader.pl index b732889..ed8550d 100644 --- a/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parseSvnDiffHeader.pl +++ b/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parseSvnDiffHeader.pl @@ -83,6 +83,7 @@ Index: WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parseDiffHeader.pl +++ WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parseDiffHeader.pl (revision 0) END indexPath => "WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parseDiffHeader.pl", + isNew => 1, }, "@@ -0,0 +1,262 @@\n"], expectedNextLine => "+#!/usr/bin/perl -w\n", @@ -119,7 +120,7 @@ END inputText => <<END, # No single quotes to allow interpolation of "\r" Index: index_path.py\r ===================================================================\r ---- index_path.py (revision 53048) (from copied_from_path.py:53048)\r +--- index_path.py (revision 53048)\r +++ index_path.py (working copy)\r @@ -0,0 +1,7 @@\r +# Python file...\r @@ -129,10 +130,9 @@ END svnConvertedText => <<END, # No single quotes to allow interpolation of "\r" Index: index_path.py\r ===================================================================\r ---- index_path.py (revision 53048) (from copied_from_path.py:53048)\r +--- index_path.py (revision 53048)\r +++ index_path.py (working copy)\r END - copiedFromPath => "copied_from_path.py", indexPath => "index_path.py", sourceRevision => 53048, }, diff --git a/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parseSvnProperty.pl b/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parseSvnProperty.pl new file mode 100644 index 0000000..cff7c2e --- /dev/null +++ b/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parseSvnProperty.pl @@ -0,0 +1,419 @@ +#!/usr/bin/perl -w +# +# Copyright (C) Research in Motion Limited 2010. All Rights Reserved. +# Copyright (C) 2010 Chris Jerdonek (chris.jerdonek@gmail.com) +# +# 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 Apple Computer, Inc. ("Apple") 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 of parseSvnProperty(). + +use strict; +use warnings; + +use Test::More; +use VCSUtils; + +my @testCaseHashRefs = ( +#### +# Simple test cases +## +{ + # New test + diffName => "simple: add svn:executable", + inputText => <<'END', +Added: svn:executable + + * +END + expectedReturn => [ +{ + name => "svn:executable", + propertyChangeDelta => 1, + value => "*", +}, +undef], + expectedNextLine => undef, +}, +{ + # New test + diffName => "simple: delete svn:executable", + inputText => <<'END', +Deleted: svn:executable + - * +END + expectedReturn => [ +{ + name => "svn:executable", + propertyChangeDelta => -1, + value => "*", +}, +undef], + expectedNextLine => undef, +}, +#### +# Using SVN 1.4 syntax +## +{ + # New test + diffName => "simple: delete svn:executable using SVN 1.4 syntax", + inputText => <<'END', +Name: svn:executable + - * +END + expectedReturn => [ +{ + name => "svn:executable", + propertyChangeDelta => -1, + value => "*", +}, +undef], + expectedNextLine => undef, +}, +{ + # New test + diffName => "simple: add svn:executable using SVN 1.4 syntax", + inputText => <<'END', +Name: svn:executable + + * +END + expectedReturn => [ +{ + name => "svn:executable", + propertyChangeDelta => 1, + value => "*", +}, +undef], + expectedNextLine => undef, +}, +#### +# Property value followed by empty line and start of next diff +## +{ + # New test + diffName => "add svn:executable, followed by empty line and start of next diff", + inputText => <<'END', +Added: svn:executable + + * + +Index: Makefile.shared +END + expectedReturn => [ +{ + name => "svn:executable", + propertyChangeDelta => 1, + value => "*", +}, +"\n"], + expectedNextLine => "Index: Makefile.shared\n", +}, +{ + # New test + diffName => "add svn:executable, followed by empty line and start of next property diff", + inputText => <<'END', +Added: svn:executable + + * + +Property changes on: Makefile.shared +END + expectedReturn => [ +{ + name => "svn:executable", + propertyChangeDelta => 1, + value => "*", +}, +"\n"], + expectedNextLine => "Property changes on: Makefile.shared\n", +}, +{ + # New test + diffName => "multi-line '+' change, followed by empty line and start of next diff", + inputText => <<'END', +Name: documentation + + A +long sentence that spans +multiple lines. + +Index: Makefile.shared +END + expectedReturn => [ +{ + name => "documentation", + propertyChangeDelta => 1, + value => "A\nlong sentence that spans\nmultiple lines.", +}, +"\n"], + expectedNextLine => "Index: Makefile.shared\n", +}, +{ + # New test + diffName => "multi-line '+' change, followed by empty line and start of next property diff", + inputText => <<'END', +Name: documentation + + A +long sentence that spans +multiple lines. + +Property changes on: Makefile.shared +END + expectedReturn => [ +{ + name => "documentation", + propertyChangeDelta => 1, + value => "A\nlong sentence that spans\nmultiple lines.", +}, +"\n"], + expectedNextLine => "Property changes on: Makefile.shared\n", +}, +#### +# Property value followed by empty line and start of binary patch +## +{ + # New test + diffName => "add svn:executable, followed by empty line and start of binary patch", + inputText => <<'END', +Added: svn:executable + + * + +Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA== +END + expectedReturn => [ +{ + name => "svn:executable", + propertyChangeDelta => 1, + value => "*", +}, +"\n"], + expectedNextLine => "Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==\n", +}, +{ + # New test + diffName => "multi-line '+' change, followed by empty line and start of binary patch", + inputText => <<'END', +Name: documentation + + A +long sentence that spans +multiple lines. + +Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA== +END + expectedReturn => [ +{ + name => "documentation", + propertyChangeDelta => 1, + value => "A\nlong sentence that spans\nmultiple lines.", +}, +"\n"], + expectedNextLine => "Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==\n", +}, +{ + # New test + diffName => "multi-line '-' change, followed by multi-line '+' change, empty line, and start of binary patch", + inputText => <<'END', +Modified: documentation + - A +long sentence that spans +multiple lines. + + Another +long sentence that spans +multiple lines. + +Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA== +END + expectedReturn => [ +{ + name => "documentation", + propertyChangeDelta => 1, + value => "Another\nlong sentence that spans\nmultiple lines.", +}, +"\n"], + expectedNextLine => "Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==\n", +}, +#### +# Successive properties +## +{ + # New test + diffName => "single-line '+' change followed by custom property with single-line '+' change", + inputText => <<'END', +Added: svn:executable + + * +Added: documentation + + A sentence. +END + expectedReturn => [ +{ + name => "svn:executable", + propertyChangeDelta => 1, + value => "*", +}, +"Added: documentation\n"], + expectedNextLine => " + A sentence.\n", +}, +{ + # New test + diffName => "multi-line '+' change, followed by svn:executable", + inputText => <<'END', +Name: documentation + + A +long sentence that spans +multiple lines. +Name: svn:executable + + * +END + expectedReturn => [ +{ + name => "documentation", + propertyChangeDelta => 1, + value => "A\nlong sentence that spans\nmultiple lines.", +}, +"Name: svn:executable\n"], + expectedNextLine => " + *\n", +}, +{ + # New test + diffName => "multi-line '-' change, followed by multi-line '+' change and add svn:executable", + inputText => <<'END', +Modified: documentation + - A +long sentence that spans +multiple lines. + + Another +long sentence that spans +multiple lines. +Added: svn:executable + + * +END + expectedReturn => [ +{ + name => "documentation", + propertyChangeDelta => 1, + value => "Another\nlong sentence that spans\nmultiple lines.", +}, +"Added: svn:executable\n"], + expectedNextLine => " + *\n", +}, +#### +# Property values with trailing new lines. +## +# FIXME: We do not support property values with trailing new lines, since it is difficult to +# disambiguate them from the empty line that preceeds the contents of a binary patch as +# in the test case (above): "multi-line '+' change, followed by empty line and start of binary patch". +{ + # New test + diffName => "single-line '+' with trailing new line", + inputText => <<'END', +Added: documentation + + A sentence. + +END + expectedReturn => [ +{ + name => "documentation", + propertyChangeDelta => 1, + value => "A sentence.", +}, +"\n"], + expectedNextLine => undef, +}, +{ + # New test + diffName => "single-line '+' with trailing new line, followed by empty line and start of binary patch", + inputText => <<'END', +Added: documentation + + A sentence. + + +Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA== +END + expectedReturn => [ +{ + name => "documentation", + propertyChangeDelta => 1, + value => "A sentence.", +}, +"\n"], + expectedNextLine => "\n", +}, +{ + # New test + diffName => "single-line '-' change with trailing new line, and single-line '+' change", + inputText => <<'END', +Modified: documentation + - A long sentence. + + + A sentence. +END + expectedReturn => [ +{ + name => "documentation", + propertyChangeDelta => -1, # Since we only interpret the '-' property. + value => "A long sentence.", +}, +"\n"], + expectedNextLine => " + A sentence.\n", +}, +{ + # New test + diffName => "multi-line '-' change with trailing new line, and multi-line '+' change", + inputText => <<'END', +Modified: documentation + - A +long sentence that spans +multiple lines. + + + Another +long sentence that spans +multiple lines. +END + expectedReturn => [ +{ + name => "documentation", + propertyChangeDelta => -1, # Since we only interpret the '-' property. + value => "A\nlong sentence that spans\nmultiple lines.", +}, +"\n"], + expectedNextLine => " + Another\n", +}, +); + +my $testCasesCount = @testCaseHashRefs; +plan(tests => 2 * $testCasesCount); # Total number of assertions. + +foreach my $testCase (@testCaseHashRefs) { + my $testNameStart = "parseSvnProperty(): $testCase->{diffName}: comparing"; + + my $fileHandle; + open($fileHandle, "<", \$testCase->{inputText}); + my $line = <$fileHandle>; + + my @got = VCSUtils::parseSvnProperty($fileHandle, $line); + my $expectedReturn = $testCase->{expectedReturn}; + + is_deeply(\@got, $expectedReturn, "$testNameStart return value."); + + my $gotNextLine = <$fileHandle>; + is($gotNextLine, $testCase->{expectedNextLine}, "$testNameStart next read line."); +} diff --git a/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parseSvnPropertyValue.pl b/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parseSvnPropertyValue.pl new file mode 100644 index 0000000..5c79862 --- /dev/null +++ b/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/parseSvnPropertyValue.pl @@ -0,0 +1,149 @@ +#!/usr/bin/perl -w +# +# Copyright (C) Research in Motion Limited 2010. All Rights Reserved. +# Copyright (C) 2010 Chris Jerdonek (chris.jerdonek@gmail.com) +# +# 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 Apple Computer, Inc. ("Apple") 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 of parseSvnPropertyValue(). + +use strict; +use warnings; + +use Test::More; +use VCSUtils; + +my @testCaseHashRefs = ( +{ + # New test + diffName => "singe-line '+' change", + inputText => <<'END', + + * +END + expectedReturn => ["*", undef], + expectedNextLine => undef, +}, +{ + # New test + diffName => "single-line '-' change", + inputText => <<'END', + - * +END + expectedReturn => ["*", undef], + expectedNextLine => undef, +}, +{ + # New test + diffName => "single-line '-' change followed by empty line", + inputText => <<'END', + - * + +END + expectedReturn => ["*", "\n"], + expectedNextLine => undef, +}, +{ + # New test + diffName => "single-line '-' change followed by the next property", + inputText => <<'END', + - * +Deleted: svn:executable +END + expectedReturn => ["*", "Deleted: svn:executable\n"], + expectedNextLine => undef, +}, +{ + # New test + diffName => "multi-line '+' change and start of binary patch", + inputText => <<'END', + + A +long sentence that spans +multiple lines. + +Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA== +END + expectedReturn => ["A\nlong sentence that spans\nmultiple lines.", "\n"], + expectedNextLine => "Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==\n", +}, +{ + # New test + diffName => "multi-line '-' change followed by '+' single-line change", + inputText => <<'END', + - A +long sentence that spans +multiple lines. + + A single-line. +END + expectedReturn => ["A\nlong sentence that spans\nmultiple lines.", " + A single-line.\n"], + expectedNextLine => undef, +}, +{ + # New test + diffName => "multi-line '-' change followed by the next property", + inputText => <<'END', + - A +long sentence that spans +multiple lines. +Added: svn:executable +END + expectedReturn => ["A\nlong sentence that spans\nmultiple lines.", "Added: svn:executable\n"], + expectedNextLine => undef, +}, +{ + # New test + diffName => "multi-line '-' change followed by '+' multi-line change", + inputText => <<'END', + - A +long sentence that spans +multiple lines. + + Another +long sentence that spans +multiple lines. +END + expectedReturn => ["A\nlong sentence that spans\nmultiple lines.", " + Another\n"], + expectedNextLine => "long sentence that spans\n", +}, +); + +my $testCasesCount = @testCaseHashRefs; +plan(tests => 2 * $testCasesCount); # Total number of assertions. + +foreach my $testCase (@testCaseHashRefs) { + my $testNameStart = "parseSvnPropertyValue(): $testCase->{diffName}: comparing"; + + my $fileHandle; + open($fileHandle, "<", \$testCase->{inputText}); + my $line = <$fileHandle>; + + my @got = VCSUtils::parseSvnPropertyValue($fileHandle, $line); + my $expectedReturn = $testCase->{expectedReturn}; + + is_deeply(\@got, $expectedReturn, "$testNameStart return value."); + + my $gotNextLine = <$fileHandle>; + is($gotNextLine, $testCase->{expectedNextLine}, "$testNameStart next read line."); +} diff --git a/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/runPatchCommand.pl b/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/runPatchCommand.pl index 8111def..5acc517 100644 --- a/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/runPatchCommand.pl +++ b/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/runPatchCommand.pl @@ -33,19 +33,6 @@ use Test::Simple tests => 4; use VCSUtils; -# Call a function while suppressing STDERR. -sub callSilently($@) { - my ($func, @args) = @_; - - open(OLDERR, ">&STDERR"); - close(STDERR); - my @returnValue = &$func(@args); - open(STDERR, ">&OLDERR"); - close(OLDERR); # FIXME: Is this necessary? - - return @returnValue; -} - # New test $title = "runPatchCommand: Unsuccessful patch, forcing."; diff --git a/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/setChangeLogDateAndReviewer.pl b/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/setChangeLogDateAndReviewer.pl new file mode 100644 index 0000000..076d88c --- /dev/null +++ b/WebKitTools/Scripts/webkitperl/VCSUtils_unittest/setChangeLogDateAndReviewer.pl @@ -0,0 +1,128 @@ +#!/usr/bin/perl -w +# +# Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Unit tests of setChangeLogDateAndReviewer(). + +use strict; +use warnings; + +use Test::More; +use VCSUtils; + +my @testCaseHashRefs = ( +{ + testName => "reviewer defined and \"NOBODY (OOPS!)\" in leading junk", + reviewer => "John Doe", + epochTime => 1273414321, + patch => <<'END', +Subject: [PATCH] + +Reviewed by NOBODY (OOPS!). + +diff --git a/WebCore/ChangeLog b/WebCore/ChangeLog +--- a/WebCore/ChangeLog ++++ b/WebCore/ChangeLog +@@ -1,3 +1,15 @@ ++2010-05-08 Chris Jerdonek <cjerdonek@webkit.org> ++ ++ Reviewed by NOBODY (OOPS!). ++ + 2010-05-08 Chris Jerdonek <cjerdonek@webkit.org> + + Reviewed by Jane Doe. +END + expectedReturn => <<'END', +Subject: [PATCH] + +Reviewed by NOBODY (OOPS!). + +diff --git a/WebCore/ChangeLog b/WebCore/ChangeLog +--- a/WebCore/ChangeLog ++++ b/WebCore/ChangeLog +@@ -1,3 +1,15 @@ ++2010-05-09 Chris Jerdonek <cjerdonek@webkit.org> ++ ++ Reviewed by John Doe. ++ + 2010-05-08 Chris Jerdonek <cjerdonek@webkit.org> + + Reviewed by Jane Doe. +END +}, +{ + testName => "reviewer not defined and \"NOBODY (OOPS!)\" in leading junk", + reviewer => undef, + epochTime => 1273414321, + patch => <<'END', +Subject: [PATCH] + +Reviewed by NOBODY (OOPS!). + +diff --git a/WebCore/ChangeLog b/WebCore/ChangeLog +--- a/WebCore/ChangeLog ++++ b/WebCore/ChangeLog +@@ -1,3 +1,15 @@ ++2010-05-08 Chris Jerdonek <cjerdonek@webkit.org> ++ ++ Reviewed by NOBODY (OOPS!). ++ + 2010-05-08 Chris Jerdonek <cjerdonek@webkit.org> + + Reviewed by Jane Doe. +END + expectedReturn => <<'END', +Subject: [PATCH] + +Reviewed by NOBODY (OOPS!). + +diff --git a/WebCore/ChangeLog b/WebCore/ChangeLog +--- a/WebCore/ChangeLog ++++ b/WebCore/ChangeLog +@@ -1,3 +1,15 @@ ++2010-05-09 Chris Jerdonek <cjerdonek@webkit.org> ++ ++ Reviewed by NOBODY (OOPS!). ++ + 2010-05-08 Chris Jerdonek <cjerdonek@webkit.org> + + Reviewed by Jane Doe. +END +}, +); + +my $testCasesCount = @testCaseHashRefs; +plan(tests => 1 * $testCasesCount); # Total number of assertions. + +foreach my $testCase (@testCaseHashRefs) { + my $testNameStart = "setChangeLogDateAndReviewer(): $testCase->{testName}: comparing"; + + my $patch = $testCase->{patch}; + my $reviewer = $testCase->{reviewer}; + my $epochTime = $testCase->{epochTime}; + + my $got = VCSUtils::setChangeLogDateAndReviewer($patch, $reviewer, $epochTime); + my $expectedReturn = $testCase->{expectedReturn}; + + is($got, $expectedReturn, "$testNameStart return value."); +} diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/scm.py b/WebKitTools/Scripts/webkitpy/common/checkout/scm.py index 02e114a..ac9c42e 100644 --- a/WebKitTools/Scripts/webkitpy/common/checkout/scm.py +++ b/WebKitTools/Scripts/webkitpy/common/checkout/scm.py @@ -32,9 +32,6 @@ import os import re -# FIXME: Instead of using run_command directly, most places in this -# class would rather use an SCM.run method which automatically set -# cwd=self.checkout_root. from webkitpy.common.system.executive import Executive, run_command, ScriptError from webkitpy.common.system.user import User from webkitpy.common.system.deprecated_logging import error, log @@ -102,6 +99,18 @@ class SCM: self.checkout_root = self.find_checkout_root(self.cwd) self.dryrun = False + # A wrapper used by subclasses to create processes. + def run(self, args, cwd=None, input=None, error_handler=None, return_exit_code=False, return_stderr=True, decode_output=True): + # FIXME: We should set cwd appropriately. + # FIXME: We should use Executive. + return run_command(args, + cwd=cwd, + input=input, + error_handler=error_handler, + return_exit_code=return_exit_code, + return_stderr=return_stderr, + decode_output=decode_output) + # SCM always returns repository relative path, but sometimes we need # absolute paths to pass to rm, etc. def absolute_path(self, repository_relative_path): @@ -118,7 +127,7 @@ class SCM: def ensure_clean_working_directory(self, force_clean): if not force_clean and not self.working_directory_is_clean(): # FIXME: Shouldn't this use cwd=self.checkout_root? - print run_command(self.status_command(), error_handler=Executive.ignore_error) + print self.run(self.status_command(), error_handler=Executive.ignore_error) raise ScriptError(message="Working directory has modifications, pass --force-clean or --no-clean to continue.") log("Cleaning working directory") @@ -137,7 +146,7 @@ class SCM: def run_status_and_extract_filenames(self, status_command, status_regexp): filenames = [] # We run with cwd=self.checkout_root so that returned-paths are root-relative. - for line in run_command(status_command, cwd=self.checkout_root).splitlines(): + for line in self.run(status_command, cwd=self.checkout_root).splitlines(): match = re.search(status_regexp, line) if not match: continue @@ -297,17 +306,17 @@ class SVN(SCM): if not os.path.isdir(os.path.join(home_directory, ".subversion")): return False find_args = ["find", ".subversion", "-type", "f", "-exec", "grep", "-q", realm, "{}", ";", "-print"]; - find_output = run_command(find_args, cwd=home_directory, error_handler=Executive.ignore_error).rstrip() + find_output = self.run(find_args, cwd=home_directory, error_handler=Executive.ignore_error).rstrip() return find_output and os.path.isfile(os.path.join(home_directory, find_output)) def svn_version(self): if not self.cached_version: - self.cached_version = run_command(['svn', '--version', '--quiet']) + self.cached_version = self.run(['svn', '--version', '--quiet']) return self.cached_version def working_directory_is_clean(self): - return run_command(["svn", "diff"], cwd=self.checkout_root, decode_output=False) == "" + return self.run(["svn", "diff"], cwd=self.checkout_root, decode_output=False) == "" def clean_working_directory(self): # svn revert -R is not as awesome as git reset --hard. @@ -317,7 +326,7 @@ class SVN(SCM): added_files = reversed(sorted(self.added_files())) # added_files() returns directories for SVN, we walk the files in reverse path # length order so that we remove files before we try to remove the directories. - run_command(["svn", "revert", "-R", "."], cwd=self.checkout_root) + self.run(["svn", "revert", "-R", "."], cwd=self.checkout_root) for path in added_files: # This is robust against cwd != self.checkout_root absolute_path = self.absolute_path(path) @@ -336,14 +345,14 @@ class SVN(SCM): def add(self, path): # path is assumed to be cwd relative? - run_command(["svn", "add", path]) + self.run(["svn", "add", path]) def changed_files(self, git_commit=None, squash=None): return self.run_status_and_extract_filenames(self.status_command(), self._status_regexp("ACDMR")) def changed_files_for_revision(self, revision): # As far as I can tell svn diff --summarize output looks just like svn status output. - # No file contents printed, thus utf-8 auto-decoding in run_command is fine. + # No file contents printed, thus utf-8 auto-decoding in self.run is fine. status_command = ["svn", "diff", "--summarize", "-c", revision] return self.run_status_and_extract_filenames(status_command, self._status_regexp("ACDMR")) @@ -360,26 +369,27 @@ class SVN(SCM): def display_name(self): return "svn" + # FIXME: This method should be on Checkout. def create_patch(self, git_commit=None, squash=None): """Returns a byte array (str()) representing the patch file. Patch files are effectively binary since they may contain files of multiple different encodings.""" - return run_command([self.script_path("svn-create-patch")], + return self.run([self.script_path("svn-create-patch")], cwd=self.checkout_root, return_stderr=False, decode_output=False) def committer_email_for_revision(self, revision): - return run_command(["svn", "propget", "svn:author", "--revprop", "-r", revision]).rstrip() + return self.run(["svn", "propget", "svn:author", "--revprop", "-r", revision]).rstrip() def contents_at_revision(self, path, revision): """Returns a byte array (str()) containing the contents of path @ revision in the repository.""" remote_path = "%s/%s" % (self._repository_url(), path) - return run_command(["svn", "cat", "-r", revision, remote_path], decode_output=False) + return self.run(["svn", "cat", "-r", revision, remote_path], decode_output=False) def diff_for_revision(self, revision): # FIXME: This should probably use cwd=self.checkout_root - return run_command(['svn', 'diff', '-c', revision]) + return self.run(['svn', 'diff', '-c', revision]) def _repository_url(self): return self.value_from_svn_info(self.checkout_root, 'URL') @@ -390,11 +400,11 @@ class SVN(SCM): log("WARNING: svn merge has been known to take more than 10 minutes to complete. It is recommended you use git for rollouts.") log("Running '%s'" % " ".join(svn_merge_args)) # FIXME: Should this use cwd=self.checkout_root? - run_command(svn_merge_args) + self.run(svn_merge_args) def revert_files(self, file_paths): # FIXME: This should probably use cwd=self.checkout_root. - run_command(['svn', 'revert'] + file_paths) + self.run(['svn', 'revert'] + file_paths) def should_squash(self, squash): # SVN doesn't support the concept of squashing. @@ -414,11 +424,11 @@ class SVN(SCM): svn_commit_args.extend(["--username", username]) svn_commit_args.extend(["-m", message]) # FIXME: Should this use cwd=self.checkout_root? - return run_command(svn_commit_args, error_handler=commit_error_handler) + return self.run(svn_commit_args, error_handler=commit_error_handler) def svn_commit_log(self, svn_revision): svn_revision = self.strip_r_from_svn_revision(svn_revision) - return run_command(['svn', 'log', '--non-interactive', '--revision', svn_revision]); + return self.run(['svn', 'log', '--non-interactive', '--revision', svn_revision]) def last_svn_commit_log(self): # BASE is the checkout revision, HEAD is the remote repository revision @@ -455,30 +465,30 @@ class Git(SCM): def discard_local_commits(self): # FIXME: This should probably use cwd=self.checkout_root - run_command(['git', 'reset', '--hard', self.svn_branch_name()]) + self.run(['git', 'reset', '--hard', self.svn_branch_name()]) def local_commits(self): # FIXME: This should probably use cwd=self.checkout_root - return run_command(['git', 'log', '--pretty=oneline', 'HEAD...' + self.svn_branch_name()]).splitlines() + return self.run(['git', 'log', '--pretty=oneline', 'HEAD...' + self.svn_branch_name()]).splitlines() def rebase_in_progress(self): return os.path.exists(os.path.join(self.checkout_root, '.git/rebase-apply')) def working_directory_is_clean(self): # FIXME: This should probably use cwd=self.checkout_root - return run_command(['git', 'diff', 'HEAD', '--name-only']) == "" + return self.run(['git', 'diff', 'HEAD', '--name-only']) == "" def clean_working_directory(self): # FIXME: These should probably use cwd=self.checkout_root. # Could run git clean here too, but that wouldn't match working_directory_is_clean - run_command(['git', 'reset', '--hard', 'HEAD']) + self.run(['git', 'reset', '--hard', 'HEAD']) # Aborting rebase even though this does not match working_directory_is_clean if self.rebase_in_progress(): - run_command(['git', 'rebase', '--abort']) + self.run(['git', 'rebase', '--abort']) def status_command(self): # git status returns non-zero when there are changes, so we use git diff name --name-status HEAD instead. - # No file contents printed, thus utf-8 autodecoding in run_command is fine. + # No file contents printed, thus utf-8 autodecoding in self.run is fine. return ["git", "diff", "--name-status", "HEAD"] def _status_regexp(self, expected_types): @@ -486,7 +496,7 @@ class Git(SCM): def add(self, path): # path is assumed to be cwd relative? - run_command(["git", "add", path]) + self.run(["git", "add", path]) def _merge_base(self, git_commit, squash): if git_commit: @@ -510,7 +520,7 @@ class Git(SCM): def _changes_files_for_commit(self, git_commit): # --pretty="format:" makes git show not print the commit log header, - changed_files = run_command(["git", "show", "--pretty=format:", "--name-only", git_commit]).splitlines() + changed_files = self.run(["git", "show", "--pretty=format:", "--name-only", git_commit]).splitlines() # instead it just prints a blank line at the top, so we skip the blank line: return changed_files[1:] @@ -539,7 +549,7 @@ class Git(SCM): Patch files are effectively binary since they may contain files of multiple different encodings.""" # FIXME: This should probably use cwd=self.checkout_root - return run_command(['git', 'diff', '--binary', "--no-ext-diff", "--full-index", "-M", self._merge_base(git_commit, squash)], decode_output=False) + return self.run(['git', 'diff', '--binary', "--no-ext-diff", "--full-index", "-M", self._merge_base(git_commit, squash)], decode_output=False) @classmethod def git_commit_from_svn_revision(cls, revision): @@ -553,7 +563,7 @@ class Git(SCM): def contents_at_revision(self, path, revision): """Returns a byte array (str()) containing the contents of path @ revision in the repository.""" - return run_command(["git", "show", "%s:%s" % (self.git_commit_from_svn_revision(revision), path)], decode_output=False) + return self.run(["git", "show", "%s:%s" % (self.git_commit_from_svn_revision(revision), path)], decode_output=False) def diff_for_revision(self, revision): git_commit = self.git_commit_from_svn_revision(revision) @@ -561,7 +571,7 @@ class Git(SCM): def committer_email_for_revision(self, revision): git_commit = self.git_commit_from_svn_revision(revision) - committer_email = run_command(["git", "log", "-1", "--pretty=format:%ce", git_commit]) + committer_email = self.run(["git", "log", "-1", "--pretty=format:%ce", git_commit]) # Git adds an extra @repository_hash to the end of every committer email, remove it: return committer_email.rsplit("@", 1)[0] @@ -569,10 +579,10 @@ class Git(SCM): # Assume the revision is an svn revision. git_commit = self.git_commit_from_svn_revision(revision) # I think this will always fail due to ChangeLogs. - run_command(['git', 'revert', '--no-commit', git_commit], error_handler=Executive.ignore_error) + self.run(['git', 'revert', '--no-commit', git_commit], error_handler=Executive.ignore_error) def revert_files(self, file_paths): - run_command(['git', 'checkout', 'HEAD'] + file_paths) + self.run(['git', 'checkout', 'HEAD'] + file_paths) def should_squash(self, squash): if squash is not None: @@ -607,7 +617,7 @@ class Git(SCM): squash = self.should_squash(squash) if squash: - run_command(['git', 'reset', '--soft', self.svn_branch_name()]) + self.run(['git', 'reset', '--soft', self.svn_branch_name()]) self.commit_locally_with_message(message) elif not self.working_directory_is_clean(): if not len(self.local_commits()): @@ -627,7 +637,7 @@ class Git(SCM): return self.push_local_commits_to_server() def _commit_on_branch(self, message, git_commit): - branch_ref = run_command(['git', 'symbolic-ref', 'HEAD']).strip() + branch_ref = self.run(['git', 'symbolic-ref', 'HEAD']).strip() branch_name = branch_ref.replace('refs/heads/', '') commit_ids = self.commit_ids_from_commitish_arguments([git_commit]) @@ -645,16 +655,16 @@ class Git(SCM): # We wrap in a try...finally block so if anything goes wrong, we clean up the branches. commit_succeeded = True try: - run_command(['git', 'checkout', '-q', '-b', MERGE_BRANCH, self.svn_branch_name()]) + self.run(['git', 'checkout', '-q', '-b', MERGE_BRANCH, self.svn_branch_name()]) for commit in commit_ids: # We're on a different branch now, so convert "head" to the branch name. commit = re.sub(r'(?i)head', branch_name, commit) # FIXME: Once changed_files and create_patch are modified to separately handle each # commit in a commit range, commit each cherry pick so they'll get dcommitted separately. - run_command(['git', 'cherry-pick', '--no-commit', commit]) + self.run(['git', 'cherry-pick', '--no-commit', commit]) - run_command(['git', 'commit', '-m', message]) + self.run(['git', 'commit', '-m', message]) output = self.push_local_commits_to_server() except Exception, e: log("COMMIT FAILED: " + str(e)) @@ -663,26 +673,26 @@ class Git(SCM): finally: # And then swap back to the original branch and clean up. self.clean_working_directory() - run_command(['git', 'checkout', '-q', branch_name]) + self.run(['git', 'checkout', '-q', branch_name]) self.delete_branch(MERGE_BRANCH) return output def svn_commit_log(self, svn_revision): svn_revision = self.strip_r_from_svn_revision(svn_revision) - return run_command(['git', 'svn', 'log', '-r', svn_revision]) + return self.run(['git', 'svn', 'log', '-r', svn_revision]) def last_svn_commit_log(self): - return run_command(['git', 'svn', 'log', '--limit=1']) + return self.run(['git', 'svn', 'log', '--limit=1']) # Git-specific methods: def delete_branch(self, branch): - if run_command(['git', 'show-ref', '--quiet', '--verify', 'refs/heads/' + branch], return_exit_code=True) == 0: - run_command(['git', 'branch', '-D', branch]) + if self.run(['git', 'show-ref', '--quiet', '--verify', 'refs/heads/' + branch], return_exit_code=True) == 0: + self.run(['git', 'branch', '-D', branch]) def svn_merge_base(self): - return run_command(['git', 'merge-base', self.svn_branch_name(), 'HEAD']).strip() + return self.run(['git', 'merge-base', self.svn_branch_name(), 'HEAD']).strip() def svn_branch_name(self): # FIXME: This should so something like: Git.read_git_config('svn-remote.svn.fetch').split(':')[1] @@ -690,13 +700,13 @@ class Git(SCM): return 'trunk' def commit_locally_with_message(self, message): - run_command(['git', 'commit', '--all', '-F', '-'], input=message) + self.run(['git', 'commit', '--all', '-F', '-'], input=message) def push_local_commits_to_server(self): dcommit_command = ['git', 'svn', 'dcommit'] if self.dryrun: dcommit_command.append('--dry-run') - output = run_command(dcommit_command, error_handler=commit_error_handler) + output = self.run(dcommit_command, error_handler=commit_error_handler) # Return a string which looks like a commit so that things which parse this output will succeed. if self.dryrun: output += "\nCommitted r0" @@ -716,14 +726,14 @@ class Git(SCM): if '...' in commitish: raise ScriptError(message="'...' is not supported (found in '%s'). Did you mean '..'?" % commitish) elif '..' in commitish: - commit_ids += reversed(run_command(['git', 'rev-list', commitish]).splitlines()) + commit_ids += reversed(self.run(['git', 'rev-list', commitish]).splitlines()) else: # Turn single commits or branch or tag names into commit ids. - commit_ids += run_command(['git', 'rev-parse', '--revs-only', commitish]).splitlines() + commit_ids += self.run(['git', 'rev-parse', '--revs-only', commitish]).splitlines() return commit_ids def commit_message_for_local_commit(self, commit_id): - commit_lines = run_command(['git', 'cat-file', 'commit', commit_id]).splitlines() + commit_lines = self.run(['git', 'cat-file', 'commit', commit_id]).splitlines() # Skip the git headers. first_line_after_headers = 0 @@ -734,4 +744,4 @@ class Git(SCM): return CommitMessage(commit_lines[first_line_after_headers:]) def files_changed_summary_for_commit(self, commit_id): - return run_command(['git', 'diff-tree', '--shortstat', '--no-commit-id', commit_id]) + return self.run(['git', 'diff-tree', '--shortstat', '--no-commit-id', commit_id]) diff --git a/WebKitTools/Scripts/webkitpy/common/config/__init__.py b/WebKitTools/Scripts/webkitpy/common/config/__init__.py index 03f1bc7..62d129e 100644 --- a/WebKitTools/Scripts/webkitpy/common/config/__init__.py +++ b/WebKitTools/Scripts/webkitpy/common/config/__init__.py @@ -3,5 +3,4 @@ import re codereview_server_host = "wkrietveld.appspot.com" -codereview_server_regex = "https?://%s/" % re.sub('\.', '\\.', codereview_server_host) codereview_server_url = "https://%s/" % codereview_server_host diff --git a/WebKitTools/Scripts/webkitpy/common/config/committers.py b/WebKitTools/Scripts/webkitpy/common/config/committers.py index 56887ab..c33d2a6 100644 --- a/WebKitTools/Scripts/webkitpy/common/config/committers.py +++ b/WebKitTools/Scripts/webkitpy/common/config/committers.py @@ -116,6 +116,7 @@ committers_unable_to_review = [ Committer("James Hawkins", ["jhawkins@chromium.org", "jhawkins@google.com"], "jhawkins"), Committer("James Robinson", ["jamesr@chromium.org", "jamesr@google.com"]), Committer("Jens Alfke", ["snej@chromium.org", "jens@apple.com"]), + Committer("Jer Noble", "jer.noble@apple.com", "jernoble"), Committer("Jeremy Moskovich", ["playmobil@google.com", "jeremy@chromium.org"], "jeremymos"), Committer("Jessie Berlin", ["jberlin@webkit.org", "jberlin@apple.com"]), Committer("Jesus Sanchez-Palencia", ["jesus@webkit.org", "jesus.palencia@openbossa.org"], "jeez_"), @@ -128,7 +129,6 @@ committers_unable_to_review = [ Committer("Keishi Hattori", "keishi@webkit.org", "keishi"), Committer("Kelly Norton", "knorton@google.com"), Committer("Kenneth Russell", "kbr@google.com"), - Committer("Kent Tamura", "tkent@chromium.org", "tkent"), Committer("Kinuko Yasuda", "kinuko@chromium.org", "kinuko"), Committer("Krzysztof Kowalczyk", "kkowalczyk@gmail.com"), Committer("Levi Weintraub", "lweintraub@apple.com"), @@ -144,10 +144,9 @@ committers_unable_to_review = [ Committer("Mike Thole", ["mthole@mikethole.com", "mthole@apple.com"]), Committer("Mikhail Naganov", "mnaganov@chromium.org"), Committer("MORITA Hajime", "morrita@google.com", "morrita"), - Committer("Ojan Vafai", "ojan@chromium.org", "ojan"), Committer("Pam Greene", "pam@chromium.org", "pamg"), Committer("Peter Kasting", ["pkasting@google.com", "pkasting@chromium.org"], "pkasting"), - Committer("Philippe Normand", ["pnormand@igalia.com", "philn@webkit.org"], "pnormand"), + Committer("Philippe Normand", ["pnormand@igalia.com", "philn@webkit.org"], "philn-tp"), Committer("Pierre d'Herbemont", ["pdherbemont@free.fr", "pdherbemont@apple.com"], "pdherbemont"), Committer("Pierre-Olivier Latour", "pol@apple.com", "pol"), Committer("Robert Hogan", ["robert@webkit.org", "robert@roberthogan.net"], "mwenge"), @@ -222,6 +221,7 @@ reviewers_list = [ Reviewer("Justin Garcia", "justin.garcia@apple.com", "justing"), Reviewer("Ken Kocienda", "kocienda@apple.com"), Reviewer("Kenneth Rohde Christiansen", ["kenneth@webkit.org", "kenneth.christiansen@openbossa.org"], "kenne"), + Reviewer("Kent Tamura", "tkent@chromium.org", "tkent"), Reviewer("Kevin Decker", "kdecker@apple.com", "superkevin"), Reviewer("Kevin McCullough", "kmccullough@apple.com", "maculloch"), Reviewer("Kevin Ollivier", ["kevino@theolliviers.com", "kevino@webkit.org"], "kollivier"), @@ -231,6 +231,7 @@ reviewers_list = [ Reviewer("Mark Rowe", "mrowe@apple.com", "bdash"), 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"), Reviewer("Oliver Hunt", "oliver@apple.com", "olliej"), Reviewer("Pavel Feldman", "pfeldman@chromium.org", "pfeldman"), Reviewer("Richard Williamson", "rjw@apple.com", "rjw"), diff --git a/WebKitTools/Scripts/webkitpy/common/config/ports.py b/WebKitTools/Scripts/webkitpy/common/config/ports.py index a881a67..9d4ac3f 100644 --- a/WebKitTools/Scripts/webkitpy/common/config/ports.py +++ b/WebKitTools/Scripts/webkitpy/common/config/ports.py @@ -112,6 +112,16 @@ class MacPort(WebKitPort): def flag(cls): return "--port=mac" + @classmethod + def _system_version(cls): + version_string = platform.mac_ver()[0] # e.g. "10.5.6" + version_tuple = version_string.split('.') + return map(int, version_tuple) + + @classmethod + def is_leopard(cls): + return tuple(cls._system_version()[:2]) == (10, 5) + class WinPort(WebKitPort): diff --git a/WebKitTools/Scripts/webkitpy/common/config/ports_unittest.py b/WebKitTools/Scripts/webkitpy/common/config/ports_unittest.py index c42d2d0..42c4f2d 100644 --- a/WebKitTools/Scripts/webkitpy/common/config/ports_unittest.py +++ b/WebKitTools/Scripts/webkitpy/common/config/ports_unittest.py @@ -41,6 +41,12 @@ class WebKitPortTest(unittest.TestCase): self.assertEquals(MacPort.build_webkit_command(build_style="debug"), [WebKitPort.script_path("build-webkit"), "--debug"]) self.assertEquals(MacPort.build_webkit_command(build_style="release"), [WebKitPort.script_path("build-webkit"), "--release"]) + class TestIsLeopard(MacPort): + @classmethod + def _system_version(cls): + return [10, 5] + self.assertTrue(TestIsLeopard.is_leopard()) + def test_gtk_port(self): self.assertEquals(GtkPort.name(), "Gtk") self.assertEquals(GtkPort.flag(), "--port=gtk") diff --git a/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py b/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py index 4311a00..074a021 100644 --- a/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py +++ b/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py @@ -856,9 +856,13 @@ class Bugzilla(object): possible_bug_statuses = map(lambda item: item.name, bug_status.items) if "REOPENED" in possible_bug_statuses: bug_status.value = ["REOPENED"] + # If the bug was never confirmed it will not have a "REOPENED" + # state, but only an "UNCONFIRMED" state. + elif "UNCONFIRMED" in possible_bug_statuses: + bug_status.value = ["UNCONFIRMED"] else: - log("Did not reopen bug %s. " + - "It appears to already be open with status %s." % ( - bug_id, bug_status.value)) + # FIXME: This logic is slightly backwards. We won't print this + # message if the bug is already open with state "UNCONFIRMED". + log("Did not reopen bug %s, it appears to already be open with status %s." % (bug_id, bug_status.value)) self.browser['comment'] = comment_text self.browser.submit() diff --git a/WebKitTools/Scripts/webkitpy/common/net/bugzilla_unittest.py b/WebKitTools/Scripts/webkitpy/common/net/bugzilla_unittest.py index 4c44cdf..62a0746 100644 --- a/WebKitTools/Scripts/webkitpy/common/net/bugzilla_unittest.py +++ b/WebKitTools/Scripts/webkitpy/common/net/bugzilla_unittest.py @@ -259,6 +259,35 @@ ZEZpbmlzaExvYWRXaXRoUmVhc29uOnJlYXNvbl07Cit9CisKIEBlbmQKIAogI2VuZGlmCg== expected_stderr = "Adding ['adam@example.com'] to the CC list for bug 42\n" OutputCapture().assert_outputs(self, bugzilla.add_cc_to_bug, [42, ["adam@example.com"]], expected_stderr=expected_stderr) + def _mock_control_item(self, name): + mock_item = Mock() + mock_item.name = name + return mock_item + + def _mock_find_control(self, item_names=[], selected_index=0): + mock_control = Mock() + mock_control.items = [self._mock_control_item(name) for name in item_names] + mock_control.value = [item_names[selected_index]] if item_names else None + return lambda name, type: mock_control + + def _assert_reopen(self, item_names=None, selected_index=None, extra_stderr=None): + bugzilla = Bugzilla() + bugzilla.browser = MockBrowser() + bugzilla.authenticate = lambda: None + + mock_find_control = self._mock_find_control(item_names, selected_index) + bugzilla.browser.find_control = mock_find_control + expected_stderr = "Re-opening bug 42\n['comment']\n" + if extra_stderr: + expected_stderr += extra_stderr + OutputCapture().assert_outputs(self, bugzilla.reopen_bug, [42, ["comment"]], expected_stderr=expected_stderr) + + def test_reopen_bug(self): + self._assert_reopen(item_names=["REOPENED", "RESOLVED", "CLOSED"], selected_index=1) + self._assert_reopen(item_names=["UNCONFIRMED", "RESOLVED", "CLOSED"], selected_index=1) + extra_stderr = "Did not reopen bug 42, it appears to already be open with status ['NEW'].\n" + self._assert_reopen(item_names=["NEW", "RESOLVED"], selected_index=0, extra_stderr=extra_stderr) + class BugzillaQueriesTest(unittest.TestCase): _sample_request_page = """ @@ -341,7 +370,3 @@ class BugzillaQueriesTest(unittest.TestCase): def test_load_query(self): queries = BugzillaQueries(Mock()) queries._load_query("request.cgi?action=queue&type=review&group=type") - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/common/net/rietveld.py b/WebKitTools/Scripts/webkitpy/common/net/rietveld.py index 9cc97f2..c0d6119 100644 --- a/WebKitTools/Scripts/webkitpy/common/net/rietveld.py +++ b/WebKitTools/Scripts/webkitpy/common/net/rietveld.py @@ -37,16 +37,6 @@ from webkitpy.common.system.executive import ScriptError import webkitpy.thirdparty.autoinstalled.rietveld.upload as upload -def parse_codereview_issue(message): - if not message: - return None - match = re.search(config.codereview_server_regex + - "(?P<codereview_issue>\d+)", - message) - if match: - return int(match.group('codereview_issue')) - - class Rietveld(object): def __init__(self, executive, dryrun=False): self.dryrun = dryrun diff --git a/WebKitTools/Scripts/webkitpy/common/system/executive.py b/WebKitTools/Scripts/webkitpy/common/system/executive.py index 11eb051..c7a7aec 100644 --- a/WebKitTools/Scripts/webkitpy/common/system/executive.py +++ b/WebKitTools/Scripts/webkitpy/common/system/executive.py @@ -33,6 +33,8 @@ try: except ImportError: multiprocessing = None +import errno +import logging import os import platform import StringIO @@ -43,6 +45,9 @@ import sys from webkitpy.common.system.deprecated_logging import tee +_log = logging.getLogger("webkitpy.common.system") + + class ScriptError(Exception): def __init__(self, @@ -165,28 +170,48 @@ class Executive(object): def kill_process(self, pid): """Attempts to kill the given pid. Will fail silently if pid does not exist or insufficient permisssions.""" - if platform.system() == "Windows": - # According to http://docs.python.org/library/os.html - # os.kill isn't available on Windows. However, when I tried it - # using Cygwin, it worked fine. We should investigate whether - # we need this platform specific code here. - command = ["taskkill.exe", "/f", "/pid", str(pid)] - # taskkill will exit 128 if the process is not found. + if sys.platform == "win32": + # We only use taskkill.exe on windows (not cygwin) because subprocess.pid + # is a CYGWIN pid and taskkill.exe expects a windows pid. + # Thankfully os.kill on CYGWIN handles either pid type. + command = ["taskkill.exe", "/f", "/pid", pid] + # taskkill will exit 128 if the process is not found. We should log. self.run_command(command, error_handler=self.ignore_error) return - try: - os.kill(pid, signal.SIGKILL) - except OSError, e: - # FIXME: We should make non-silent failure an option. - pass + + # According to http://docs.python.org/library/os.html + # os.kill isn't available on Windows. python 2.5.5 os.kill appears + # to work in cygwin, however it occasionally raises EAGAIN. + retries_left = 3 if sys.platform == "cygwin" else 1 + while retries_left > 0: + try: + retries_left -= 1 + os.kill(pid, signal.SIGKILL) + except OSError, e: + if e.errno == errno.EAGAIN: + if retries_left <= 0: + _log.warn("Failed to kill pid %s. Too many EAGAIN errors." % pid) + continue + if e.errno == errno.ESRCH: # The process does not exist. + _log.warn("Called kill_process with a non-existant pid %s" % pid) + return + raise + + def _windows_image_name(self, process_name): + name, extension = os.path.splitext(process_name) + if not extension: + # taskkill expects processes to end in .exe + # If necessary we could add a flag to disable appending .exe. + process_name = "%s.exe" % name + return process_name def kill_all(self, process_name): """Attempts to kill processes matching process_name. Will fail silently if no process are found.""" - if platform.system() == "Windows": - # We might want to automatically append .exe? - command = ["taskkill.exe", "/f", "/im", process_name] - # taskkill will exit 128 if the process is not found. + if sys.platform in ("win32", "cygwin"): + image_name = self._windows_image_name(process_name) + command = ["taskkill.exe", "/f", "/im", image_name] + # taskkill will exit 128 if the process is not found. We should log. self.run_command(command, error_handler=self.ignore_error) return @@ -195,6 +220,9 @@ class Executive(object): # We should pick one mode, or add support for switching between them. # Note: Mac OS X 10.6 requires -SIGNALNAME before -u USER command = ["killall", "-TERM", "-u", os.getenv("USER"), process_name] + # killall returns 1 if no process can be found and 2 on command error. + # FIXME: We should pass a custom error_handler to allow only exit_code 1. + # We should log in exit_code == 1 self.run_command(command, error_handler=self.ignore_error) # Error handlers do not need to be static methods once all callers are diff --git a/WebKitTools/Scripts/webkitpy/common/system/executive_unittest.py b/WebKitTools/Scripts/webkitpy/common/system/executive_unittest.py index ce91269..30468ce 100644 --- a/WebKitTools/Scripts/webkitpy/common/system/executive_unittest.py +++ b/WebKitTools/Scripts/webkitpy/common/system/executive_unittest.py @@ -72,22 +72,44 @@ class ExecutiveTest(unittest.TestCase): def test_kill_process(self): executive = Executive() - # FIXME: This may need edits to work right on windows. # We use "yes" because it loops forever. process = subprocess.Popen(["yes"], stdout=subprocess.PIPE) self.assertEqual(process.poll(), None) # Process is running executive.kill_process(process.pid) - self.assertEqual(process.wait(), -signal.SIGKILL) + # Note: Can't use a ternary since signal.SIGKILL is undefined for sys.platform == "win32" + if sys.platform == "win32": + expected_exit_code = 0 # taskkill.exe results in exit(0) + else: + expected_exit_code = -signal.SIGKILL + self.assertEqual(process.wait(), expected_exit_code) # Killing again should fail silently. executive.kill_process(process.pid) + def _assert_windows_image_name(self, name, expected_windows_name): + executive = Executive() + windows_name = executive._windows_image_name(name) + self.assertEqual(windows_name, expected_windows_name) + + def test_windows_image_name(self): + self._assert_windows_image_name("foo", "foo.exe") + self._assert_windows_image_name("foo.exe", "foo.exe") + self._assert_windows_image_name("foo.com", "foo.com") + # If the name looks like an extension, even if it isn't + # supposed to, we have no choice but to return the original name. + self._assert_windows_image_name("foo.baz", "foo.baz") + self._assert_windows_image_name("foo.baz.exe", "foo.baz.exe") + def test_kill_all(self): executive = Executive() - # FIXME: This may need edits to work right on windows. # We use "yes" because it loops forever. process = subprocess.Popen(["yes"], stdout=subprocess.PIPE) self.assertEqual(process.poll(), None) # Process is running executive.kill_all("yes") - self.assertEqual(process.wait(), -signal.SIGTERM) + # Note: Can't use a ternary since signal.SIGTERM is undefined for sys.platform == "win32" + if sys.platform in ("win32", "cygwin"): + expected_exit_code = 0 # taskkill.exe results in exit(0) + else: + expected_exit_code = -signal.SIGTERM + self.assertEqual(process.wait(), expected_exit_code) # Killing again should fail silently. executive.kill_all("yes") diff --git a/WebKitTools/Scripts/webkitpy/common/system/user.py b/WebKitTools/Scripts/webkitpy/common/system/user.py index 64995bb..edce93d 100644 --- a/WebKitTools/Scripts/webkitpy/common/system/user.py +++ b/WebKitTools/Scripts/webkitpy/common/system/user.py @@ -26,17 +26,27 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import logging import os import shlex import subprocess import webbrowser + +_log = logging.getLogger("webkitpy.common.system.user") + + try: import readline except ImportError: - print "Unable to import readline. If you're using MacPorts, try running:" - print " sudo port install py25-readline" - exit(1) + if sys.platform != "win32": + # There is no readline module for win32, not much to do except cry. + _log.warn("Unable to import readline.") + # FIXME: We could give instructions for non-mac platforms. + # Lack of readline results in a very bad user experiance. + if sys.platform == "mac": + _log.warn("If you're using MacPorts, try running:") + _log.warn(" sudo port install py25-readline") class User(object): diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/metered_stream.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/metered_stream.py index 9c42d73..20646a1 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/metered_stream.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/metered_stream.py @@ -137,12 +137,10 @@ class MeteredStream: # Print the necessary number of backspaces to erase the previous # message. if len(self._last_update): - self._stream.write("\b" * len(self._last_update)) - if len(str): - self._stream.write(str) - num_remaining = len(self._last_update) - len(str) - if num_remaining > 0: - self._stream.write(" " * num_remaining + "\b" * num_remaining) + self._stream.write("\b" * len(self._last_update) + + " " * len(self._last_update) + + "\b" * len(self._last_update)) + self._stream.write(str) last_newline = str.rfind("\n") self._last_update = str[(last_newline + 1):] self._dirty = True diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/metered_stream_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/metered_stream_unittest.py index 926f9b3..a9c6d5b 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/metered_stream_unittest.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/metered_stream_unittest.py @@ -50,32 +50,36 @@ class TestMeteredStream(unittest.TestCase): # for coverage. m.write("foo") m.flush() - self.assertEquals(a.get(), ['foo']) + exp = ['foo'] + self.assertEquals(a.get(), exp) # now check that a second write() does not overwrite the first. m.write("bar") - self.assertEquals(a.get(), ['foo', 'bar']) + exp.append('bar') + self.assertEquals(a.get(), exp) m.update("batter") - self.assertEquals(a.get(), ['foo', 'bar', 'batter']) + exp.append('batter') + self.assertEquals(a.get(), exp) # The next update() should overwrite the laste update() but not the # other text. Note that the cursor is effectively positioned at the # end of 'foo', even though we had to erase three more characters. m.update("foo") - self.assertEquals(a.get(), ['foo', 'bar', 'batter', '\b\b\b\b\b\b', - 'foo', ' \b\b\b']) + exp.append('\b\b\b\b\b\b \b\b\b\b\b\b') + exp.append('foo') + self.assertEquals(a.get(), exp) m.progress("progress") - self.assertEquals(a.get(), ['foo', 'bar', 'batter', '\b\b\b\b\b\b', - 'foo', ' \b\b\b', '\b\b\b', 'progress']) + exp.append('\b\b\b \b\b\b') + exp.append('progress') + self.assertEquals(a.get(), exp) # now check that a write() does overwrite the progress bar m.write("foo") - self.assertEquals(a.get(), ['foo', 'bar', 'batter', '\b\b\b\b\b\b', - 'foo', ' \b\b\b', '\b\b\b', 'progress', - '\b\b\b\b\b\b\b\b', - 'foo', ' \b\b\b\b\b']) + exp.append('\b\b\b\b\b\b\b\b \b\b\b\b\b\b\b\b') + exp.append('foo') + self.assertEquals(a.get(), exp) # Now test that we only back up to the most recent newline. @@ -84,7 +88,7 @@ class TestMeteredStream(unittest.TestCase): a.reset() m.update("foo\nbar") m.update("baz") - self.assertEquals(a.get(), ['foo\nbar', '\b\b\b', 'baz']) + self.assertEquals(a.get(), ['foo\nbar', '\b\b\b \b\b\b', 'baz']) def test_verbose(self): a = ArrayStream() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing.py index 91d49c6..77de2e0 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing.py @@ -56,6 +56,8 @@ the output at the beginning of the run, during the run, or at the end: Overall options: nothing don't print anything. This overrides every other option + default include the default options. This is useful for logging + the default options plus additional settings. everything print everything (except the trace-* options and the detailed-progress option, see below for the full list ) misc print miscellaneous things like blank lines @@ -101,15 +103,19 @@ Notes: --print 'everything' is equivalent to --print '%(everything)s'. -The default is to --print '%(default)s'. +The default (--print default) is equivalent to --print '%(default)s'. """ % {'slowest': NUM_SLOW_TESTS_TO_LOG, 'everything': PRINT_EVERYTHING, 'default': PRINT_DEFAULT} def print_options(): return [ - # Note: we use print_options rather than just 'print' because print + # Note: We use print_options rather than just 'print' because print # is a reserved word. + # Note: Also, we don't specify a default value so we can detect when + # no flag is specified on the command line and use different defaults + # based on whether or not --verbose is specified (since --print + # overrides --verbose). optparse.make_option("--print", dest="print_options", help=("controls print output of test run. " "Use --help-printing for more.")), @@ -171,6 +177,10 @@ def parse_print_options(print_options, verbose, child_processes, switches.discard('everything') switches.update(set(PRINT_EVERYTHING.split(','))) + if 'default' in switches: + switches.discard('default') + switches.update(set(PRINT_DEFAULT.split(','))) + if 'detailed-progress' in switches: switches.discard('one-line-progress') @@ -310,7 +320,7 @@ class Printer(object): png_file = self._port.expected_filename(filename, '.png') if os.path.exists(png_file): self._write(' png: %s' % - self._port.relative_test_filename(filename)) + self._port.relative_test_filename(png_file)) else: self._write(' png: <none>') self._write(' exp: %s' % exp_str) @@ -486,10 +496,8 @@ class Printer(object): # from the logger :(. if self._options.verbose: _log.info(msg) - elif msg == "": - self._meter.write("\n") else: - self._meter.write(msg) + self._meter.write("%s\n" % msg) # # Utility routines used by the Controller class diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing_unittest.py index 8e6aa8f..dba1194 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing_unittest.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing_unittest.py @@ -98,6 +98,44 @@ class TestUtilityFunctions(unittest.TestCase): options, args = get_options([]) self.assertTrue(options is not None) + def test_parse_print_options(self): + def test_switches(args, verbose, child_processes, is_fully_parallel, + expected_switches_str): + options, args = get_options(args) + if expected_switches_str: + expected_switches = set(expected_switches_str.split(',')) + else: + expected_switches = set() + switches = printing.parse_print_options(options.print_options, + verbose, + child_processes, + is_fully_parallel) + self.assertEqual(expected_switches, switches) + + # test that we default to the default set of switches + test_switches([], False, 1, False, + printing.PRINT_DEFAULT) + + # test that verbose defaults to everything + test_switches([], True, 1, False, + printing.PRINT_EVERYTHING) + + # test that --print default does what it's supposed to + test_switches(['--print', 'default'], False, 1, False, + printing.PRINT_DEFAULT) + + # test that --print nothing does what it's supposed to + test_switches(['--print', 'nothing'], False, 1, False, + None) + + # test that --print everything does what it's supposed to + test_switches(['--print', 'everything'], False, 1, False, + printing.PRINT_EVERYTHING) + + # this tests that '--print X' overrides '--verbose' + test_switches(['--print', 'actual'], True, 1, False, + 'actual') + class Testprinter(unittest.TestCase): def get_printer(self, args=None, single_threaded=False, @@ -144,7 +182,7 @@ class Testprinter(unittest.TestCase): exp_bot = [message + "\n"] else: if exp_err is None: - exp_err = [message] + exp_err = [message + "\n"] if exp_bot is None: exp_bot = [] do_helper(method_name, 'nothing', 'hello', [], []) @@ -182,21 +220,21 @@ class Testprinter(unittest.TestCase): printer, err, out = self.get_printer(['--print', 'one-line-summary']) printer.print_one_line_summary(1, 1) - self.assertEquals(err.get(), ["All 1 tests ran as expected.", "\n"]) + self.assertEquals(err.get(), ["All 1 tests ran as expected.\n", "\n"]) printer, err, out = self.get_printer(['--print', 'everything']) printer.print_one_line_summary(1, 1) - self.assertEquals(err.get(), ["All 1 tests ran as expected.", "\n"]) + self.assertEquals(err.get(), ["All 1 tests ran as expected.\n", "\n"]) err.reset() printer.print_one_line_summary(2, 1) self.assertEquals(err.get(), - ["1 test ran as expected, 1 didn't:", "\n"]) + ["1 test ran as expected, 1 didn't:\n", "\n"]) err.reset() printer.print_one_line_summary(3, 2) self.assertEquals(err.get(), - ["2 tests ran as expected, 1 didn't:", "\n"]) + ["2 tests ran as expected, 1 didn't:\n", "\n"]) def test_print_test_result(self): result = get_result('foo.html') @@ -212,7 +250,7 @@ class Testprinter(unittest.TestCase): printer.print_test_result(result, expected=False, exp_str='', got_str='') self.assertEquals(err.get(), - [' foo.html -> unexpected pass']) + [' foo.html -> unexpected pass\n']) printer, err, out = self.get_printer(['--print', 'everything']) printer.print_test_result(result, expected=True, exp_str='', @@ -222,7 +260,7 @@ class Testprinter(unittest.TestCase): printer.print_test_result(result, expected=False, exp_str='', got_str='') self.assertEquals(err.get(), - [' foo.html -> unexpected pass']) + [' foo.html -> unexpected pass\n']) printer, err, out = self.get_printer(['--print', 'nothing']) printer.print_test_result(result, expected=False, exp_str='', @@ -318,7 +356,7 @@ class Testprinter(unittest.TestCase): err.reset() out.reset() printer.print_progress(rs, True, test_files) - self.assertEqual(err.get(), []) + self.assertEqual(err.get(), ['']) self.assertTrue(out.empty()) printer, err, out = self.get_printer( @@ -347,7 +385,7 @@ class Testprinter(unittest.TestCase): err.reset() out.reset() printer.print_progress(rs, True, test_files) - self.assertEqual(err.get(), []) + self.assertEqual(err.get(), ['']) self.assertTrue(out.empty()) def test_write(self): diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/test_memorizingfile.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_results_uploader.py index 2de77ba..680b848 100644 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/test_memorizingfile.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_results_uploader.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -# -# Copyright 2009, Google Inc. -# All rights reserved. +# 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 @@ -29,44 +27,45 @@ # (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 mimetypes +import socket -"""Tests for memorizingfile module.""" - - -import StringIO -import unittest - -import config # This must be imported before mod_pywebsocket. -from mod_pywebsocket import memorizingfile +from webkitpy.common.net.networktransaction import NetworkTransaction +from webkitpy.thirdparty.autoinstalled.mechanize import Browser -class UtilTest(unittest.TestCase): - def check(self, memorizing_file, num_read, expected_list): - for unused in range(num_read): - memorizing_file.readline() - actual_list = memorizing_file.get_memorized_lines() - self.assertEqual(len(expected_list), len(actual_list)) - for expected, actual in zip(expected_list, actual_list): - self.assertEqual(expected, actual) +def get_mime_type(filename): + return mimetypes.guess_type(filename)[0] or "text/plain" - def test_get_memorized_lines(self): - memorizing_file = memorizingfile.MemorizingFile(StringIO.StringIO( - 'Hello\nWorld\nWelcome')) - self.check(memorizing_file, 3, ['Hello\n', 'World\n', 'Welcome']) - def test_get_memorized_lines_limit_memorized_lines(self): - memorizing_file = memorizingfile.MemorizingFile(StringIO.StringIO( - 'Hello\nWorld\nWelcome'), 2) - self.check(memorizing_file, 3, ['Hello\n', 'World\n']) +class TestResultsUploader: + def __init__(self, host): + self._host = host + self._browser = Browser() - def test_get_memorized_lines_empty_file(self): - memorizing_file = memorizingfile.MemorizingFile(StringIO.StringIO( - '')) - self.check(memorizing_file, 10, []) + def _upload_files(self, attrs, file_objs): + self._browser.open("http://%s/testfile/uploadform" % self._host) + self._browser.select_form("test_result_upload") + for (name, data) in attrs: + self._browser[name] = str(data) + for (filename, handle) in file_objs: + self._browser.add_file(handle, get_mime_type(filename), filename, + "file") -if __name__ == '__main__': - unittest.main() + self._browser.submit() + def upload(self, params, files, timeout_seconds): + orig_timeout = socket.getdefaulttimeout() + file_objs = [] + try: + file_objs = [(filename, open(path, "rb")) for (filename, path) + in files] -# vi:sts=4 sw=4 et + socket.setdefaulttimeout(timeout_seconds) + NetworkTransaction(timeout_seconds=timeout_seconds).run( + lambda: self._upload_files(params, file_objs)) + finally: + socket.setdefaulttimeout(orig_timeout) + for (filename, handle) in file_objs: + handle.close() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py index 25946af..a4cbe42 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py @@ -414,10 +414,17 @@ class Port(object): raise NotImplemented('Port.results_directory') def setup_test_run(self): - """This routine can be overridden to perform any port-specific - work that shouuld be done at the beginning of a test run.""" + """Perform port-specific work at the beginning of a test run.""" pass + def setup_environ_for_server(self): + """Perform port-specific work at the beginning of a server launch. + + Returns: + Operating-system's environment. + """ + return os.environ + def show_html_results_file(self, results_filename): """This routine should display the HTML file pointed at by results_filename in a users' browser.""" diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_linux.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_linux.py index a01bd14..979e225 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_linux.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_linux.py @@ -49,11 +49,8 @@ class ChromiumLinuxPort(chromium.ChromiumPort): chromium.ChromiumPort.__init__(self, port_name, options) def baseline_search_path(self): - return [self._webkit_baseline_path('chromium-linux'), - self._webkit_baseline_path('chromium-win'), - self._webkit_baseline_path('chromium'), - self._webkit_baseline_path('win'), - self._webkit_baseline_path('mac')] + port_names = ["chromium-linux", "chromium-win", "chromium", "win", "mac"] + return map(self._webkit_baseline_path, port_names) def check_build(self, needs_http): result = chromium.ChromiumPort.check_build(self, needs_http) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_mac.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_mac.py index 4ead26f..ba67a3e 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_mac.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_mac.py @@ -52,10 +52,8 @@ class ChromiumMacPort(chromium.ChromiumPort): chromium.ChromiumPort.__init__(self, port_name, options) def baseline_search_path(self): - return [self._webkit_baseline_path('chromium-mac'), - self._webkit_baseline_path('chromium'), - self._webkit_baseline_path('mac' + self.version()), - self._webkit_baseline_path('mac')] + port_names = ["chromium-mac", "chromium", "mac" + self.version(), "mac"] + return map(self._webkit_baseline_path, port_names) def check_build(self, needs_http): result = chromium.ChromiumPort.check_build(self, needs_http) @@ -87,6 +85,7 @@ class ChromiumMacPort(chromium.ChromiumPort): return 'mac' def version(self): + # FIXME: It's strange that this string is -version, not just version. os_version_string = platform.mac_ver()[0] # e.g. "10.5.6" if not os_version_string: return '-leopard' diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win.py index 302af86..ad78e61 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win.py @@ -43,22 +43,39 @@ class ChromiumWinPort(chromium.ChromiumPort): def __init__(self, port_name=None, options=None): if port_name is None: - port_name = 'chromium-win' + self.version() - if options and not hasattr(options, 'configuration'): - options.configuration = 'Release' + port_name = "chromium-win" + self.version() + if options and not hasattr(options, "configuration"): + options.configuration = "Release" chromium.ChromiumPort.__init__(self, port_name, options) + def setup_environ_for_server(self): + env = chromium.ChromiumPort.setup_environ_for_server(self) + # Put the cygwin directory first in the path to find cygwin1.dll. + env["PATH"] = "%s;%s" % ( + self.path_from_chromium_base("third_party", "cygwin", "bin"), + env["PATH"]) + # Configure the cygwin directory so that pywebsocket finds proper + # python executable to run cgi program. + env["CYGWIN_PATH"] = self.path_from_chromium_base( + "third_party", "cygwin", "bin") + if (sys.platform == "win32" and self._options and + hasattr(self._options, "register_cygwin") and + self._options.register_cygwin): + setup_mount = self.path_from_chromium_base("third_party", + "cygwin", + "setup_mount.bat") + self._executive.run_command(setup_mount) + return env + def baseline_search_path(self): - dirs = [] + port_names = [] if self._name == 'chromium-win-xp': - dirs.append(self._webkit_baseline_path('chromium-win-xp')) + port_names.append("chromium-win-xp") if self._name in ('chromium-win-xp', 'chromium-win-vista'): - dirs.append(self._webkit_baseline_path('chromium-win-vista')) - dirs.append(self._webkit_baseline_path('chromium-win')) - dirs.append(self._webkit_baseline_path('chromium')) - dirs.append(self._webkit_baseline_path('win')) - dirs.append(self._webkit_baseline_path('mac')) - return dirs + port_names.append("chromium-win-vista") + # FIXME: This may need to include mac-snowleopard like win.py. + port_names.extend(["chromium-win", "chromium", "win", "mac"]) + return map(self._webkit_baseline_path, port_names) def check_build(self, needs_http): result = chromium.ChromiumPort.check_build(self, needs_http) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/dryrun.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/dryrun.py index 2cbb1b9..2c92865 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/dryrun.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/dryrun.py @@ -46,6 +46,8 @@ from __future__ import with_statement +import sys + import base import factory @@ -172,7 +174,11 @@ class DryrunDriver(base.Driver): test = uri if uri.startswith("file:///"): - test = test.replace('file://', '') + if sys.platform == 'win32': + test = test.replace('file:///', '') + test = test.replace('/', '\\') + else: + test = test.replace('file://', '') return test elif uri.startswith("http://127.0.0.1:8880/"): # websocket tests diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/factory_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/factory_unittest.py new file mode 100644 index 0000000..d8dffdf --- /dev/null +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/factory_unittest.py @@ -0,0 +1,138 @@ +# Copyright (C) 2010 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import sys +import unittest + +import chromium_linux +import chromium_mac +import chromium_win +import dryrun +import factory +import gtk +import mac +import qt +import test +import win + + +class FactoryTest(unittest.TestCase): + """Test factory creates proper port object for the target. + + Target is specified by port_name, sys.platform and options. + + """ + # FIXME: The ports themselves should expose what options they require, + # instead of passing generic "options". + + class WebKitOptions(object): + """Represents the minimum options for WebKit port.""" + def __init__(self): + self.pixel_tests = False + + class ChromiumOptions(WebKitOptions): + """Represents minimum options for Chromium port.""" + def __init__(self): + FactoryTest.WebKitOptions.__init__(self) + self.chromium = True + + def setUp(self): + self.real_sys_platform = sys.platform + self.webkit_options = FactoryTest.WebKitOptions() + self.chromium_options = FactoryTest.ChromiumOptions() + + def tearDown(self): + sys.platform = self.real_sys_platform + + def assert_port(self, port_name, expected_port): + """Helper assert for port_name. + + Args: + port_name: port name to get port object. + expected_port: class of expected port object. + + """ + self.assertTrue(isinstance(factory.get(port_name=port_name), + expected_port)) + + def assert_platform_port(self, platform, options, expected_port): + """Helper assert for platform and options. + + Args: + platform: sys.platform. + options: options to get port object. + expected_port: class of expected port object. + + """ + orig_platform = sys.platform + sys.platform = platform + self.assertTrue(isinstance(factory.get(options=options), + expected_port)) + sys.platform = orig_platform + + def test_test(self): + self.assert_port("test", test.TestPort) + + def test_dryrun(self): + self.assert_port("dryrun-test", dryrun.DryRunPort) + self.assert_port("dryrun-mac", dryrun.DryRunPort) + + def test_mac(self): + self.assert_port("mac", mac.MacPort) + self.assert_platform_port("darwin", None, mac.MacPort) + self.assert_platform_port("darwin", self.webkit_options, mac.MacPort) + + def test_win(self): + self.assert_port("win", win.WinPort) + self.assert_platform_port("win32", None, win.WinPort) + self.assert_platform_port("win32", self.webkit_options, win.WinPort) + self.assert_platform_port("cygwin", None, win.WinPort) + self.assert_platform_port("cygwin", self.webkit_options, win.WinPort) + + def test_gtk(self): + self.assert_port("gtk", gtk.GtkPort) + + def test_qt(self): + self.assert_port("qt", qt.QtPort) + + def test_chromium_mac(self): + self.assert_port("chromium-mac", chromium_mac.ChromiumMacPort) + self.assert_platform_port("darwin", self.chromium_options, + chromium_mac.ChromiumMacPort) + + def test_chromium_linux(self): + self.assert_port("chromium-linux", chromium_linux.ChromiumLinuxPort) + self.assert_platform_port("linux2", self.chromium_options, + chromium_linux.ChromiumLinuxPort) + + def test_chromium_win(self): + self.assert_port("chromium-win", chromium_win.ChromiumWinPort) + self.assert_platform_port("win32", self.chromium_options, + chromium_win.ChromiumWinPort) + self.assert_platform_port("cygwin", self.chromium_options, + chromium_win.ChromiumWinPort) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/http_server.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/http_server.py index fbe47e3..0f8a21e 100755 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/http_server.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/http_server.py @@ -55,7 +55,7 @@ class HttpdNotStarted(Exception): class Lighttpd(http_server_base.HttpServerBase): def __init__(self, port_obj, output_dir, background=False, port=None, - root=None, register_cygwin=None, run_background=None): + root=None, run_background=None): """Args: output_dir: the absolute path to the layout test result directory """ @@ -65,7 +65,6 @@ class Lighttpd(http_server_base.HttpServerBase): self._process = None self._port = port self._root = root - self._register_cygwin = register_cygwin self._run_background = run_background if self._port: self._port = int(self._port) @@ -199,20 +198,7 @@ class Lighttpd(http_server_base.HttpServerBase): shutil.copyfile(os.path.join(module_path, lib_file), os.path.join(tmp_module_path, lib_file)) - # Put the cygwin directory first in the path to find cygwin1.dll - env = os.environ - if sys.platform in ('cygwin', 'win32'): - env['PATH'] = '%s;%s' % ( - self._port_obj.path_from_chromium_base('third_party', - 'cygwin', 'bin'), - env['PATH']) - - if sys.platform == 'win32' and self._register_cygwin: - setup_mount = self._port_obj.path_from_chromium_base('third_party', - 'cygwin', 'setup_mount.bat') - # FIXME: Should use Executive.run_command - subprocess.Popen(setup_mount).wait() - + env = self._port_obj.setup_environ_for_server() _log.debug('Starting http server') # FIXME: Should use Executive.run_command self._process = subprocess.Popen(start_cmd, env=env) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/mac.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/mac.py index 350b088..413b5f2 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/mac.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/mac.py @@ -58,15 +58,15 @@ class MacPort(WebKitPort): return child_processes def baseline_search_path(self): - dirs = [] + port_names = [] if self._name == 'mac-tiger': - dirs.append(self._webkit_baseline_path(self._name)) + port_names.append("mac-tiger") if self._name in ('mac-tiger', 'mac-leopard'): - dirs.append(self._webkit_baseline_path('mac-leopard')) + port_names.append("mac-leopard") if self._name in ('mac-tiger', 'mac-leopard', 'mac-snowleopard'): - dirs.append(self._webkit_baseline_path('mac-snowleopard')) - dirs.append(self._webkit_baseline_path('mac')) - return dirs + port_names.append("mac-snowleopard") + port_names.append("mac") + return map(self._webkit_baseline_path, port_names) def path_to_test_expectations_file(self): return self.path_from_webkit_base('LayoutTests', 'platform', diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit.py index ada83ce..2097ce7 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit.py @@ -294,13 +294,33 @@ class WebKitPort(base.Port): return self.test_base_platform_names() + ( 'mac-tiger', 'mac-leopard', 'mac-snowleopard') + def _configuration_file_path(self): + build_root = self._webkit_build_directory(["--top-level"]) + return os.path.join(build_root, "Configuration") + + # Easy override for unit tests + def _open_configuration_file(self): + configuration_path = self._configuration_file_path() + return codecs.open(configuration_path, "r", "utf-8") + + def _read_configuration(self): + try: + with self._open_configuration_file() as file: + return file.readline().rstrip() + except IOError, e: + return None + + # FIXME: This list may be incomplete as Apple has some sekret configs. + _RECOGNIZED_CONFIGURATIONS = ("Debug", "Release") + def default_configuration(self): - # This is a bit of a hack. This state exists in a much nicer form in - # perl-land. - configuration = ospath.relpath( - self._webkit_build_directory(["--configuration"]), - self._webkit_build_directory(["--top-level"])) - assert(configuration == "Debug" or configuration == "Release") + # FIXME: Unify this with webkitdir.pm configuration reading code. + configuration = self._read_configuration() + if not configuration: + configuration = "Release" + if configuration not in self._RECOGNIZED_CONFIGURATIONS: + _log.warn("Configuration \"%s\" found in %s is not a recognized value.\n" % (configuration, self._configuration_file_path())) + _log.warn("Scripts may fail. See 'set-webkit-configuration --help'.") return configuration def _webkit_build_directory(self, args): diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/websocket_server.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/websocket_server.py index ad557bd..22ae780 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/websocket_server.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/websocket_server.py @@ -46,6 +46,8 @@ import factory import http_server from webkitpy.common.system.executive import Executive +from webkitpy.thirdparty.autoinstalled.pywebsocket import mod_pywebsocket + _log = logging.getLogger("webkitpy.layout_tests.port.websocket_server") @@ -95,15 +97,13 @@ class PyWebSocket(http_server.Lighttpd): def __init__(self, port_obj, output_dir, port=_DEFAULT_WS_PORT, root=None, use_tls=False, - register_cygwin=True, pidfile=None): """Args: output_dir: the absolute path to the layout test result directory """ http_server.Lighttpd.__init__(self, port_obj, output_dir, port=_DEFAULT_WS_PORT, - root=root, - register_cygwin=register_cygwin) + root=root) self._output_dir = output_dir self._process = None self._port = port @@ -159,7 +159,8 @@ class PyWebSocket(http_server.Lighttpd): python_interp = sys.executable pywebsocket_base = os.path.join( os.path.dirname(os.path.dirname(os.path.dirname( - os.path.abspath(__file__)))), 'thirdparty', 'pywebsocket') + os.path.abspath(__file__)))), 'thirdparty', + 'autoinstalled', 'pywebsocket') pywebsocket_script = os.path.join(pywebsocket_base, 'mod_pywebsocket', 'standalone.py') start_cmd = [ @@ -185,21 +186,7 @@ class PyWebSocket(http_server.Lighttpd): start_cmd.extend(['-t', '-k', self._private_key, '-c', self._certificate]) - # Put the cygwin directory first in the path to find cygwin1.dll - env = os.environ - if sys.platform in ('cygwin', 'win32'): - env['PATH'] = '%s;%s' % ( - self._port_obj.path_from_chromium_base('third_party', - 'cygwin', 'bin'), - env['PATH']) - env['CYGWIN_PATH'] = self._port_obj.path_from_chromium_base( - 'third_party', 'cygwin', 'bin') - - if sys.platform == 'win32' and self._register_cygwin: - setup_mount = self._port_obj.path_from_chromium_base( - 'third_party', 'cygwin', 'setup_mount.bat') - subprocess.Popen(setup_mount).wait() - + env = self._port_obj.setup_environ_for_server() env['PYTHONPATH'] = (pywebsocket_base + os.path.pathsep + env.get('PYTHONPATH', '')) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/win.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/win.py index 3b7a817..e05a69d 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/win.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/win.py @@ -44,6 +44,11 @@ class WinPort(WebKitPort): port_name = 'win' WebKitPort.__init__(self, port_name, options) + def baseline_search_path(self): + # Based on code from old-run-webkit-tests expectedDirectoryForTest() + port_names = ["win", "mac-snowleopard", "mac"] + return map(self._webkit_baseline_path, port_names) + def _tests_for_other_platforms(self): # FIXME: This list could be dynamic based on platform name and # pushed into base.Port. 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 211ce93..f11b8a9 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py @@ -970,6 +970,28 @@ class HtmlGenerator(object): return 'Other' +def get_host_port_object(options): + """Return a port object for the platform we're running on.""" + # The only thing we really need on the host is a way to diff + # text files and image files, which means we need to check that some + # version of ImageDiff has been built. We will look for either Debug + # or Release versions of the default port on the platform. + options.configuration = "Release" + port_obj = port.get(None, options) + if not port_obj.check_image_diff(override_step=None, logging=False): + _log.debug('No release version of the image diff binary was found.') + options.configuration = "Debug" + port_obj = port.get(None, options) + if not port_obj.check_image_diff(override_step=None, logging=False): + _log.error('No version of image diff was found. Check your build.') + return None + else: + _log.debug('Found the debug version of the image diff binary.') + else: + _log.debug('Found the release version of the image diff binary.') + return port_obj + + def main(): """Main function to produce new baselines.""" @@ -1034,19 +1056,11 @@ def main(): '%(levelname)s %(message)s'), datefmt='%y%m%d %H:%M:%S') - # options.configuration is used by port to locate image_diff binary. - # Check the imgage_diff release binary, if it does not exist, - # fallback to debug. - options.configuration = "Release" - port_obj = port.get(None, options) - if not port_obj.check_image_diff(override_step=None, logging=False): - _log.debug('No release version image diff binary found.') - options.configuration = "Debug" - port_obj = port.get(None, options) - else: - _log.debug('Found release version image diff binary.') + host_port_obj = get_host_port_object(options) + if not host_port_obj: + sys.exit(1) - # Verify 'platforms' option is valid + # Verify 'platforms' option is valid. if not options.platforms: _log.error('Invalid "platforms" option. --platforms must be ' 'specified in order to rebaseline.') @@ -1071,7 +1085,8 @@ def main(): rebaselining_tests = set() backup = options.backup for platform in rebaseline_platforms: - rebaseliner = Rebaseliner(port_obj, target_port_obj, platform, options) + rebaseliner = Rebaseliner(host_port_obj, target_port_obj, + platform, options) _log.info('') log_dashed_string('Rebaseline started', platform) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py new file mode 100644 index 0000000..fa03238 --- /dev/null +++ b/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py @@ -0,0 +1,88 @@ +#!/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 rebaseline_chromium_webkit_tests.py.""" + +import unittest + +from webkitpy.layout_tests import port +from webkitpy.layout_tests import rebaseline_chromium_webkit_tests + + +class MockPort(object): + def __init__(self, image_diff_exists): + self.image_diff_exists = image_diff_exists + + def check_image_diff(self, override_step, logging): + return self.image_diff_exists + + +class MockOptions(object): + def __init__(self): + self.configuration = None + + +def get_mock_get(config_expectations): + def mock_get(port_name, options): + return MockPort(config_expectations[options.configuration]) + return mock_get + + +class TestGetHostPortObject(unittest.TestCase): + def assert_result(self, release_present, debug_present, valid_port_obj): + # Tests whether we get a valid port object returned when we claim + # that Image diff is (or isn't) present in the two configs. + port.get = get_mock_get({'Release': release_present, + 'Debug': debug_present}) + options = MockOptions() + port_obj = rebaseline_chromium_webkit_tests.get_host_port_object( + options) + if valid_port_obj: + self.assertNotEqual(port_obj, None) + else: + self.assertEqual(port_obj, None) + + def test_get_host_port_object(self): + # Save the normal port.get() function for future testing. + old_get = port.get + + # Test whether we get a valid port object back for the four + # possible cases of having ImageDiffs built. It should work when + # there is at least one binary present. + self.assert_result(False, False, False) + self.assert_result(True, False, True) + self.assert_result(False, True, True) + self.assert_result(True, True, True) + + # Restore the normal port.get() function. + port.get = old_get + + +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 456c6f3..148174d 100755 --- a/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py @@ -62,16 +62,17 @@ import sys import time import traceback -from layout_package import test_expectations +from layout_package import dump_render_tree_thread from layout_package import json_layout_results_generator from layout_package import printing +from layout_package import test_expectations from layout_package import test_failures -from layout_package import dump_render_tree_thread 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 test_type_base from test_types import text_diff +from test_types import test_type_base from webkitpy.common.system.executive import Executive from webkitpy.thirdparty import simplejson @@ -790,6 +791,9 @@ class TestRunner: self._write_json_files(unexpected_results, result_summary, individual_test_timings) + # Upload generated JSON files to appengine server. + self._upload_json_files() + # Write the summary to disk (results.html) and display it if requested. wrote_results = self._write_results_html_file(result_summary) if self._options.show_results and wrote_results: @@ -878,6 +882,31 @@ class TestRunner: _log.debug("Finished writing JSON files.") + def _upload_json_files(self): + if not self._options.test_results_server: + return + + _log.info("Uploading JSON files for builder: %s", + self._options.builder_name) + + attrs = [('builder', self._options.builder_name)] + json_files = ["expectations.json", "results.json"] + + files = [(file, os.path.join(self._options.results_directory, file)) + for file in json_files] + + uploader = test_results_uploader.TestResultsUploader( + self._options.test_results_server) + try: + # Set uploading timeout in case appengine server is having problem. + # 120 seconds are more than enough to upload test results. + uploader.upload(attrs, files, 120) + except Exception, err: + _log.error("Upload failed: %s" % err) + return + + _log.info("JSON files uploaded.") + def _print_expected_results_of_type(self, result_summary, result_type, result_type_str): """Print the number of the tests in a given result class. @@ -1289,11 +1318,11 @@ def run(port_obj, options, args, regular_output=sys.stderr, if options.print_last_failures or options.retest_last_failures: unexpected_results_filename = os.path.join( options.results_directory, "unexpected_results.json") - with open(unexpected_results_filename, "r", "utf-8") as file: + with codecs.open(unexpected_results_filename, "r", "utf-8") as file: results = simplejson.load(file) last_unexpected_results = results['tests'].keys() if options.print_last_failures: - print "\n".join(last_unexpected_results) + "\n" + printer.write("\n".join(last_unexpected_results) + "\n") return 0 if options.clobber_old_results: @@ -1608,6 +1637,9 @@ def parse_args(args=None): "webkit-rel.")), optparse.make_option("--build-number", default="DUMMY_BUILD_NUMBER", help=("The build number of the builder running this script.")), + optparse.make_option("--test-results-server", default="", + help=("If specified, upload results json files to this appengine " + "server.")), ] option_list = (configuration_options + print_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 cd72fa3..e050cba 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py @@ -78,15 +78,21 @@ class MainTest(unittest.TestCase): (res, buildbot_output, regular_output) = logging_run( ['--platform', 'test', '--print', 'config', '--child-processes', '1', 'fast/html']) - self.assertTrue('Running one DumpRenderTree' + self.assertTrue('Running one DumpRenderTree\n' in regular_output.get()) (res, buildbot_output, regular_output) = logging_run( ['--platform', 'test', '--print', 'config', '--child-processes', '2', 'fast/html']) - self.assertTrue('Running 2 DumpRenderTrees in parallel' + self.assertTrue('Running 2 DumpRenderTrees in parallel\n' in regular_output.get()) + def test_last_results(self): + passing_run(['--platform', 'test', 'fast/html']) + (res, buildbot_output, regular_output) = logging_run( + ['--platform', 'test', '--print-last-failures']) + self.assertEqual(regular_output.get(), ['\n\n']) + self.assertEqual(buildbot_output.get(), []) class TestRunnerTest(unittest.TestCase): @@ -112,16 +118,17 @@ class TestRunnerTest(unittest.TestCase): class DryrunTest(unittest.TestCase): def test_basics(self): + # FIXME: it's hard to know which platforms are safe to test; the + # chromium platforms require a chromium checkout, and the mac platform + # requires fcntl, so it can't be tested on win32, etc. There is + # probably a better way of handling this. + if sys.platform != "mac": + return self.assertTrue(passing_run(['--platform', 'dryrun', 'fast/html'])) - #self.assertTrue(passing_run(['--platform', 'dryrun-mac', - # 'fast/html'])) - #self.assertTrue(passing_run(['--platform', 'dryrun-chromium-mac', - # 'fast/html'])) - #self.assertTrue(passing_run(['--platform', 'dryrun-chromium-win', - # 'fast/html'])) - #self.assertTrue(passing_run(['--platform', 'dryrun-chromium-linux', - # 'fast/html'])) + self.assertTrue(passing_run(['--platform', 'dryrun-mac', + 'fast/html'])) + if __name__ == '__main__': unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/__init__.py b/WebKitTools/Scripts/webkitpy/thirdparty/__init__.py index c052f00..3642286 100644 --- a/WebKitTools/Scripts/webkitpy/thirdparty/__init__.py +++ b/WebKitTools/Scripts/webkitpy/thirdparty/__init__.py @@ -89,6 +89,11 @@ installer.install(url="http://iweb.dl.sourceforge.net/project/python-irclib/pyth installer.install(url="http://iweb.dl.sourceforge.net/project/python-irclib/python-irclib/0.4.8/python-irclib-0.4.8.zip", url_subpath="ircbot.py") +pywebsocket_dir = os.path.join(autoinstalled_dir, "pywebsocket") +installer = AutoInstaller(target_dir=pywebsocket_dir) +installer.install(url="http://pywebsocket.googlecode.com/files/mod_pywebsocket-0.5.tar.gz", + url_subpath="pywebsocket-0.5/src/mod_pywebsocket") + readme_path = os.path.join(autoinstalled_dir, "README") if not os.path.exists(readme_path): with codecs.open(readme_path, "w", "ascii") as file: diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/COPYING b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/COPYING deleted file mode 100644 index ab9d52d..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/COPYING +++ /dev/null @@ -1,28 +0,0 @@ -Copyright 2009, 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. diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/MANIFEST.in b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/MANIFEST.in deleted file mode 100644 index 1925688..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/MANIFEST.in +++ /dev/null @@ -1,6 +0,0 @@ -include COPYING -include MANIFEST.in -include README -recursive-include example *.py -recursive-include mod_pywebsocket *.py -recursive-include test *.py diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/README b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/README deleted file mode 100644 index 1f9f05f..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/README +++ /dev/null @@ -1,6 +0,0 @@ -Install this package by: -$ python setup.py build -$ sudo python setup.py install - -Then read document by: -$ pydoc mod_pywebsocket diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/README.webkit b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/README.webkit deleted file mode 100644 index 83e3cee..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/README.webkit +++ /dev/null @@ -1,14 +0,0 @@ -This directory contains a copy of the pywebsocket Python module obtained -from the following location: - -http://code.google.com/p/pywebsocket/ - -This directory currently contains the following version: -0.4.9.2 - -The following modifications have been made to this local version: -minor updates in WebSocketRequestHandler.is_cgi - -More information on these local modifications can be found here: - -http://trac.webkit.org/browser/trunk/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/example/echo_client.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/example/echo_client.py deleted file mode 100644 index 2b976e1..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/example/echo_client.py +++ /dev/null @@ -1,209 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009, 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. - - -"""Web Socket Echo client. - -This is an example Web Socket client that talks with echo_wsh.py. -This may be useful for checking mod_pywebsocket installation. - -Note: -This code is far from robust, e.g., we cut corners in handshake. -""" - - -import codecs -from optparse import OptionParser -import socket -import sys - - -_TIMEOUT_SEC = 10 - -_DEFAULT_PORT = 80 -_DEFAULT_SECURE_PORT = 443 -_UNDEFINED_PORT = -1 - -_UPGRADE_HEADER = 'Upgrade: WebSocket\r\n' -_CONNECTION_HEADER = 'Connection: Upgrade\r\n' -_EXPECTED_RESPONSE = ( - 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n' + - _UPGRADE_HEADER + - _CONNECTION_HEADER) - -_GOODBYE_MESSAGE = 'Goodbye' - - -def _method_line(resource): - return 'GET %s HTTP/1.1\r\n' % resource - - -def _origin_header(origin): - return 'Origin: %s\r\n' % origin - - -class _TLSSocket(object): - """Wrapper for a TLS connection.""" - - def __init__(self, raw_socket): - self._ssl = socket.ssl(raw_socket) - - def send(self, bytes): - return self._ssl.write(bytes) - - def recv(self, size=-1): - return self._ssl.read(size) - - def close(self): - # Nothing to do. - pass - - -class EchoClient(object): - """Web Socket echo client.""" - - def __init__(self, options): - self._options = options - self._socket = None - - def run(self): - """Run the client. - - Shake hands and then repeat sending message and receiving its echo. - """ - self._socket = socket.socket() - self._socket.settimeout(self._options.socket_timeout) - try: - self._socket.connect((self._options.server_host, - self._options.server_port)) - if self._options.use_tls: - self._socket = _TLSSocket(self._socket) - self._handshake() - for line in self._options.message.split(',') + [_GOODBYE_MESSAGE]: - frame = '\x00' + line.encode('utf-8') + '\xff' - self._socket.send(frame) - if self._options.verbose: - print 'Send: %s' % line - received = self._socket.recv(len(frame)) - if received != frame: - raise Exception('Incorrect echo: %r' % received) - if self._options.verbose: - print 'Recv: %s' % received[1:-1].decode('utf-8', - 'replace') - finally: - self._socket.close() - - def _handshake(self): - self._socket.send(_method_line(self._options.resource)) - self._socket.send(_UPGRADE_HEADER) - self._socket.send(_CONNECTION_HEADER) - self._socket.send(self._format_host_header()) - self._socket.send(_origin_header(self._options.origin)) - self._socket.send('\r\n') - - for expected_char in _EXPECTED_RESPONSE: - received = self._socket.recv(1)[0] - if expected_char != received: - raise Exception('Handshake failure') - # We cut corners and skip other headers. - self._skip_headers() - - def _skip_headers(self): - terminator = '\r\n\r\n' - pos = 0 - while pos < len(terminator): - received = self._socket.recv(1)[0] - if received == terminator[pos]: - pos += 1 - elif received == terminator[0]: - pos = 1 - else: - pos = 0 - - def _format_host_header(self): - host = 'Host: ' + self._options.server_host - if ((not self._options.use_tls and - self._options.server_port != _DEFAULT_PORT) or - (self._options.use_tls and - self._options.server_port != _DEFAULT_SECURE_PORT)): - host += ':' + str(self._options.server_port) - host += '\r\n' - return host - - -def main(): - sys.stdout = codecs.getwriter('utf-8')(sys.stdout) - - parser = OptionParser() - parser.add_option('-s', '--server-host', '--server_host', - dest='server_host', type='string', - default='localhost', help='server host') - parser.add_option('-p', '--server-port', '--server_port', - dest='server_port', type='int', - default=_UNDEFINED_PORT, help='server port') - parser.add_option('-o', '--origin', dest='origin', type='string', - default='http://localhost/', help='origin') - parser.add_option('-r', '--resource', dest='resource', type='string', - default='/echo', help='resource path') - parser.add_option('-m', '--message', dest='message', type='string', - help=('comma-separated messages to send excluding "%s" ' - 'that is always sent at the end' % - _GOODBYE_MESSAGE)) - parser.add_option('-q', '--quiet', dest='verbose', action='store_false', - default=True, help='suppress messages') - parser.add_option('-t', '--tls', dest='use_tls', action='store_true', - default=False, help='use TLS (wss://)') - parser.add_option('-k', '--socket-timeout', '--socket_timeout', - dest='socket_timeout', type='int', default=_TIMEOUT_SEC, - help='Timeout(sec) for sockets') - - (options, unused_args) = parser.parse_args() - - # Default port number depends on whether TLS is used. - if options.server_port == _UNDEFINED_PORT: - if options.use_tls: - options.server_port = _DEFAULT_SECURE_PORT - else: - options.server_port = _DEFAULT_PORT - - # optparse doesn't seem to handle non-ascii default values. - # Set default message here. - if not options.message: - options.message = u'Hello,\u65e5\u672c' # "Japan" in Japanese - - EchoClient(options).run() - - -if __name__ == '__main__': - main() - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/example/echo_wsh.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/example/echo_wsh.py deleted file mode 100644 index 50cad31..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/example/echo_wsh.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright 2009, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -from mod_pywebsocket import msgutil - - -_GOODBYE_MESSAGE = 'Goodbye' - - -def web_socket_do_extra_handshake(request): - pass # Always accept. - - -def web_socket_transfer_data(request): - while True: - line = msgutil.receive_message(request) - msgutil.send_message(request, line) - if line == _GOODBYE_MESSAGE: - return - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/example/handler_map.txt b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/example/handler_map.txt deleted file mode 100644 index 21c4c09..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/example/handler_map.txt +++ /dev/null @@ -1,11 +0,0 @@ -# websocket handler map file, used by standalone.py -m option. -# A line starting with '#' is a comment line. -# Each line consists of 'alias_resource_path' and 'existing_resource_path' -# separated by spaces. -# Aliasing is processed from the top to the bottom of the line, and -# 'existing_resource_path' must exist before it is aliased. -# For example, -# / /echo -# means that a request to '/' will be handled by handlers for '/echo'. -/ /echo - diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/__init__.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/__init__.py deleted file mode 100644 index 05e80e8..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/__init__.py +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright 2009, 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. - - -"""Web Socket extension for Apache HTTP Server. - -mod_pywebsocket is a Web Socket extension for Apache HTTP Server -intended for testing or experimental purposes. mod_python is required. - -Installation: - -0. Prepare an Apache HTTP Server for which mod_python is enabled. - -1. Specify the following Apache HTTP Server directives to suit your - configuration. - - If mod_pywebsocket is not in the Python path, specify the following. - <websock_lib> is the directory where mod_pywebsocket is installed. - - PythonPath "sys.path+['<websock_lib>']" - - Always specify the following. <websock_handlers> is the directory where - user-written Web Socket handlers are placed. - - PythonOption mod_pywebsocket.handler_root <websock_handlers> - PythonHeaderParserHandler mod_pywebsocket.headerparserhandler - - To limit the search for Web Socket handlers to a directory <scan_dir> - under <websock_handlers>, configure as follows: - - PythonOption mod_pywebsocket.handler_scan <scan_dir> - - <scan_dir> is useful in saving scan time when <websock_handlers> - contains many non-Web Socket handler files. - - Example snippet of httpd.conf: - (mod_pywebsocket is in /websock_lib, Web Socket handlers are in - /websock_handlers, port is 80 for ws, 443 for wss.) - - <IfModule python_module> - PythonPath "sys.path+['/websock_lib']" - PythonOption mod_pywebsocket.handler_root /websock_handlers - PythonHeaderParserHandler mod_pywebsocket.headerparserhandler - </IfModule> - -Writing Web Socket handlers: - -When a Web Socket request comes in, the resource name -specified in the handshake is considered as if it is a file path under -<websock_handlers> and the handler defined in -<websock_handlers>/<resource_name>_wsh.py is invoked. - -For example, if the resource name is /example/chat, the handler defined in -<websock_handlers>/example/chat_wsh.py is invoked. - -A Web Socket handler is composed of the following two functions: - - web_socket_do_extra_handshake(request) - web_socket_transfer_data(request) - -where: - request: mod_python request. - -web_socket_do_extra_handshake is called during the handshake after the -headers are successfully parsed and Web Socket properties (ws_location, -ws_origin, ws_protocol, and ws_resource) are added to request. A handler -can reject the request by raising an exception. - -web_socket_transfer_data is called after the handshake completed -successfully. A handler can receive/send messages from/to the client -using request. mod_pywebsocket.msgutil module provides utilities -for data transfer. - -A Web Socket handler must be thread-safe if the server (Apache or -standalone.py) is configured to use threads. -""" - - -# vi:sts=4 sw=4 et tw=72 diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/dispatch.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/dispatch.py deleted file mode 100644 index c52e9eb..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/dispatch.py +++ /dev/null @@ -1,231 +0,0 @@ -# Copyright 2009, 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. - - -"""Dispatch Web Socket request. -""" - - -import os -import re - -import util - - -_SOURCE_PATH_PATTERN = re.compile(r'(?i)_wsh\.py$') -_SOURCE_SUFFIX = '_wsh.py' -_DO_EXTRA_HANDSHAKE_HANDLER_NAME = 'web_socket_do_extra_handshake' -_TRANSFER_DATA_HANDLER_NAME = 'web_socket_transfer_data' - - -class DispatchError(Exception): - """Exception in dispatching Web Socket request.""" - - pass - - -def _normalize_path(path): - """Normalize path. - - Args: - path: the path to normalize. - - Path is converted to the absolute path. - The input path can use either '\\' or '/' as the separator. - The normalized path always uses '/' regardless of the platform. - """ - - path = path.replace('\\', os.path.sep) - path = os.path.realpath(path) - path = path.replace('\\', '/') - return path - - -def _path_to_resource_converter(base_dir): - base_dir = _normalize_path(base_dir) - base_len = len(base_dir) - suffix_len = len(_SOURCE_SUFFIX) - def converter(path): - if not path.endswith(_SOURCE_SUFFIX): - return None - path = _normalize_path(path) - if not path.startswith(base_dir): - return None - return path[base_len:-suffix_len] - return converter - - -def _source_file_paths(directory): - """Yield Web Socket Handler source file names in the given directory.""" - - for root, unused_dirs, files in os.walk(directory): - for base in files: - path = os.path.join(root, base) - if _SOURCE_PATH_PATTERN.search(path): - yield path - - -def _source(source_str): - """Source a handler definition string.""" - - global_dic = {} - try: - exec source_str in global_dic - except Exception: - raise DispatchError('Error in sourcing handler:' + - util.get_stack_trace()) - return (_extract_handler(global_dic, _DO_EXTRA_HANDSHAKE_HANDLER_NAME), - _extract_handler(global_dic, _TRANSFER_DATA_HANDLER_NAME)) - - -def _extract_handler(dic, name): - if name not in dic: - raise DispatchError('%s is not defined.' % name) - handler = dic[name] - if not callable(handler): - raise DispatchError('%s is not callable.' % name) - return handler - - -class Dispatcher(object): - """Dispatches Web Socket requests. - - This class maintains a map from resource name to handlers. - """ - - def __init__(self, root_dir, scan_dir=None): - """Construct an instance. - - Args: - root_dir: The directory where handler definition files are - placed. - scan_dir: The directory where handler definition files are - searched. scan_dir must be a directory under root_dir, - including root_dir itself. If scan_dir is None, root_dir - is used as scan_dir. scan_dir can be useful in saving - scan time when root_dir contains many subdirectories. - """ - - self._handlers = {} - self._source_warnings = [] - if scan_dir is None: - scan_dir = root_dir - if not os.path.realpath(scan_dir).startswith( - os.path.realpath(root_dir)): - raise DispatchError('scan_dir:%s must be a directory under ' - 'root_dir:%s.' % (scan_dir, root_dir)) - self._source_files_in_dir(root_dir, scan_dir) - - def add_resource_path_alias(self, - alias_resource_path, existing_resource_path): - """Add resource path alias. - - Once added, request to alias_resource_path would be handled by - handler registered for existing_resource_path. - - Args: - alias_resource_path: alias resource path - existing_resource_path: existing resource path - """ - try: - handler = self._handlers[existing_resource_path] - self._handlers[alias_resource_path] = handler - except KeyError: - raise DispatchError('No handler for: %r' % existing_resource_path) - - def source_warnings(self): - """Return warnings in sourcing handlers.""" - - return self._source_warnings - - def do_extra_handshake(self, request): - """Do extra checking in Web Socket handshake. - - Select a handler based on request.uri and call its - web_socket_do_extra_handshake function. - - Args: - request: mod_python request. - """ - - do_extra_handshake_, unused_transfer_data = self._handler(request) - try: - do_extra_handshake_(request) - except Exception, e: - util.prepend_message_to_exception( - '%s raised exception for %s: ' % ( - _DO_EXTRA_HANDSHAKE_HANDLER_NAME, - request.ws_resource), - e) - raise - - def transfer_data(self, request): - """Let a handler transfer_data with a Web Socket client. - - Select a handler based on request.ws_resource and call its - web_socket_transfer_data function. - - Args: - request: mod_python request. - """ - - unused_do_extra_handshake, transfer_data_ = self._handler(request) - try: - transfer_data_(request) - except Exception, e: - util.prepend_message_to_exception( - '%s raised exception for %s: ' % ( - _TRANSFER_DATA_HANDLER_NAME, request.ws_resource), - e) - raise - - def _handler(self, request): - try: - ws_resource_path = request.ws_resource.split('?', 1)[0] - return self._handlers[ws_resource_path] - except KeyError: - raise DispatchError('No handler for: %r' % request.ws_resource) - - def _source_files_in_dir(self, root_dir, scan_dir): - """Source all the handler source files in the scan_dir directory. - - The resource path is determined relative to root_dir. - """ - - to_resource = _path_to_resource_converter(root_dir) - for path in _source_file_paths(scan_dir): - try: - handlers = _source(open(path).read()) - except DispatchError, e: - self._source_warnings.append('%s: %s' % (path, e)) - continue - self._handlers[to_resource(path)] = handlers - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/handshake.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/handshake.py deleted file mode 100644 index b86278e..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/handshake.py +++ /dev/null @@ -1,220 +0,0 @@ -# Copyright 2009, 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. - - -"""Web Socket handshaking. - -Note: request.connection.write/read are used in this module, even though -mod_python document says that they should be used only in connection handlers. -Unfortunately, we have no other options. For example, request.write/read are -not suitable because they don't allow direct raw bytes writing/reading. -""" - - -import re - -import util - - -_DEFAULT_WEB_SOCKET_PORT = 80 -_DEFAULT_WEB_SOCKET_SECURE_PORT = 443 -_WEB_SOCKET_SCHEME = 'ws' -_WEB_SOCKET_SECURE_SCHEME = 'wss' - -_MANDATORY_HEADERS = [ - # key, expected value or None - ['Upgrade', 'WebSocket'], - ['Connection', 'Upgrade'], - ['Host', None], - ['Origin', None], -] - -_FIRST_FIVE_LINES = map(re.compile, [ - r'^GET /[\S]* HTTP/1.1\r\n$', - r'^Upgrade: WebSocket\r\n$', - r'^Connection: Upgrade\r\n$', - r'^Host: [\S]+\r\n$', - r'^Origin: [\S]+\r\n$', -]) - -_SIXTH_AND_LATER = re.compile( - r'^' - r'(WebSocket-Protocol: [\x20-\x7e]+\r\n)?' - r'(Cookie: [^\r]*\r\n)*' - r'(Cookie2: [^\r]*\r\n)?' - r'(Cookie: [^\r]*\r\n)*' - r'\r\n') - - -def _default_port(is_secure): - if is_secure: - return _DEFAULT_WEB_SOCKET_SECURE_PORT - else: - return _DEFAULT_WEB_SOCKET_PORT - - -class HandshakeError(Exception): - """Exception in Web Socket Handshake.""" - - pass - - -def _validate_protocol(protocol): - """Validate WebSocket-Protocol string.""" - - if not protocol: - raise HandshakeError('Invalid WebSocket-Protocol: empty') - for c in protocol: - if not 0x20 <= ord(c) <= 0x7e: - raise HandshakeError('Illegal character in protocol: %r' % c) - - -class Handshaker(object): - """This class performs Web Socket handshake.""" - - def __init__(self, request, dispatcher, strict=False): - """Construct an instance. - - Args: - request: mod_python request. - dispatcher: Dispatcher (dispatch.Dispatcher). - strict: Strictly check handshake request. Default: False. - If True, request.connection must provide get_memorized_lines - method. - - Handshaker will add attributes such as ws_resource in performing - handshake. - """ - - self._request = request - self._dispatcher = dispatcher - self._strict = strict - - def do_handshake(self): - """Perform Web Socket Handshake.""" - - self._check_header_lines() - self._set_resource() - self._set_origin() - self._set_location() - self._set_protocol() - self._dispatcher.do_extra_handshake(self._request) - self._send_handshake() - - def _set_resource(self): - self._request.ws_resource = self._request.uri - - def _set_origin(self): - self._request.ws_origin = self._request.headers_in['Origin'] - - def _set_location(self): - location_parts = [] - if self._request.is_https(): - location_parts.append(_WEB_SOCKET_SECURE_SCHEME) - else: - location_parts.append(_WEB_SOCKET_SCHEME) - location_parts.append('://') - host, port = self._parse_host_header() - connection_port = self._request.connection.local_addr[1] - if port != connection_port: - raise HandshakeError('Header/connection port mismatch: %d/%d' % - (port, connection_port)) - location_parts.append(host) - if (port != _default_port(self._request.is_https())): - location_parts.append(':') - location_parts.append(str(port)) - location_parts.append(self._request.uri) - self._request.ws_location = ''.join(location_parts) - - def _parse_host_header(self): - fields = self._request.headers_in['Host'].split(':', 1) - if len(fields) == 1: - return fields[0], _default_port(self._request.is_https()) - try: - return fields[0], int(fields[1]) - except ValueError, e: - raise HandshakeError('Invalid port number format: %r' % e) - - def _set_protocol(self): - protocol = self._request.headers_in.get('WebSocket-Protocol') - if protocol is not None: - _validate_protocol(protocol) - self._request.ws_protocol = protocol - - def _send_handshake(self): - self._request.connection.write( - 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n') - self._request.connection.write('Upgrade: WebSocket\r\n') - self._request.connection.write('Connection: Upgrade\r\n') - self._request.connection.write('WebSocket-Origin: ') - self._request.connection.write(self._request.ws_origin) - self._request.connection.write('\r\n') - self._request.connection.write('WebSocket-Location: ') - self._request.connection.write(self._request.ws_location) - self._request.connection.write('\r\n') - if self._request.ws_protocol: - self._request.connection.write('WebSocket-Protocol: ') - self._request.connection.write(self._request.ws_protocol) - self._request.connection.write('\r\n') - self._request.connection.write('\r\n') - - def _check_header_lines(self): - for key, expected_value in _MANDATORY_HEADERS: - actual_value = self._request.headers_in.get(key) - if not actual_value: - raise HandshakeError('Header %s is not defined' % key) - if expected_value: - if actual_value != expected_value: - raise HandshakeError('Illegal value for header %s: %s' % - (key, actual_value)) - if self._strict: - try: - lines = self._request.connection.get_memorized_lines() - except AttributeError, e: - util.prepend_message_to_exception( - 'Strict handshake is specified but the connection ' - 'doesn\'t provide get_memorized_lines()', e) - raise - self._check_first_lines(lines) - - def _check_first_lines(self, lines): - if len(lines) < len(_FIRST_FIVE_LINES): - raise HandshakeError('Too few header lines: %d' % len(lines)) - for line, regexp in zip(lines, _FIRST_FIVE_LINES): - if not regexp.search(line): - raise HandshakeError('Unexpected header: %r doesn\'t match %r' - % (line, regexp.pattern)) - sixth_and_later = ''.join(lines[5:]) - if not _SIXTH_AND_LATER.search(sixth_and_later): - raise HandshakeError('Unexpected header: %r doesn\'t match %r' - % (sixth_and_later, - _SIXTH_AND_LATER.pattern)) - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/headerparserhandler.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/headerparserhandler.py deleted file mode 100644 index 124b9f1..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/headerparserhandler.py +++ /dev/null @@ -1,99 +0,0 @@ -# Copyright 2009, 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. - - -"""PythonHeaderParserHandler for mod_pywebsocket. - -Apache HTTP Server and mod_python must be configured such that this -function is called to handle Web Socket request. -""" - - -from mod_python import apache - -import dispatch -import handshake -import util - - -# PythonOption to specify the handler root directory. -_PYOPT_HANDLER_ROOT = 'mod_pywebsocket.handler_root' - -# PythonOption to specify the handler scan directory. -# This must be a directory under the root directory. -# The default is the root directory. -_PYOPT_HANDLER_SCAN = 'mod_pywebsocket.handler_scan' - - -def _create_dispatcher(): - _HANDLER_ROOT = apache.main_server.get_options().get( - _PYOPT_HANDLER_ROOT, None) - if not _HANDLER_ROOT: - raise Exception('PythonOption %s is not defined' % _PYOPT_HANDLER_ROOT, - apache.APLOG_ERR) - _HANDLER_SCAN = apache.main_server.get_options().get( - _PYOPT_HANDLER_SCAN, _HANDLER_ROOT) - dispatcher = dispatch.Dispatcher(_HANDLER_ROOT, _HANDLER_SCAN) - for warning in dispatcher.source_warnings(): - apache.log_error('mod_pywebsocket: %s' % warning, apache.APLOG_WARNING) - return dispatcher - - -# Initialize -_dispatcher = _create_dispatcher() - - -def headerparserhandler(request): - """Handle request. - - Args: - request: mod_python request. - - This function is named headerparserhandler because it is the default name - for a PythonHeaderParserHandler. - """ - - try: - handshaker = handshake.Handshaker(request, _dispatcher) - handshaker.do_handshake() - request.log_error('mod_pywebsocket: resource: %r' % request.ws_resource, - apache.APLOG_DEBUG) - _dispatcher.transfer_data(request) - except handshake.HandshakeError, e: - # Handshake for ws/wss failed. - # But the request can be valid http/https request. - request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO) - return apache.DECLINED - except dispatch.DispatchError, e: - request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_WARNING) - return apache.DECLINED - return apache.DONE # Return DONE such that no other handlers are invoked. - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/memorizingfile.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/memorizingfile.py deleted file mode 100644 index 2f8a54e..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/memorizingfile.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009, 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. - - -"""Memorizing file. - -A memorizing file wraps a file and memorizes lines read by readline. -""" - - -import sys - - -class MemorizingFile(object): - """MemorizingFile wraps a file and memorizes lines read by readline. - - Note that data read by other methods are not memorized. This behavior - is good enough for memorizing lines SimpleHTTPServer reads before - the control reaches WebSocketRequestHandler. - """ - def __init__(self, file_, max_memorized_lines=sys.maxint): - """Construct an instance. - - Args: - file_: the file object to wrap. - max_memorized_lines: the maximum number of lines to memorize. - Only the first max_memorized_lines are memorized. - Default: sys.maxint. - """ - self._file = file_ - self._memorized_lines = [] - self._max_memorized_lines = max_memorized_lines - - def __getattribute__(self, name): - if name in ('_file', '_memorized_lines', '_max_memorized_lines', - 'readline', 'get_memorized_lines'): - return object.__getattribute__(self, name) - return self._file.__getattribute__(name) - - def readline(self): - """Override file.readline and memorize the line read.""" - - line = self._file.readline() - if line and len(self._memorized_lines) < self._max_memorized_lines: - self._memorized_lines.append(line) - return line - - def get_memorized_lines(self): - """Get lines memorized so far.""" - return self._memorized_lines - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/msgutil.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/msgutil.py deleted file mode 100644 index 90ae715..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/msgutil.py +++ /dev/null @@ -1,250 +0,0 @@ -# Copyright 2009, 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. - - -"""Message related utilities. - -Note: request.connection.write/read are used in this module, even though -mod_python document says that they should be used only in connection handlers. -Unfortunately, we have no other options. For example, request.write/read are -not suitable because they don't allow direct raw bytes writing/reading. -""" - - -import Queue -import threading -import util - - -class MsgUtilException(Exception): - pass - - -def _read(request, length): - bytes = request.connection.read(length) - if not bytes: - raise MsgUtilException( - 'Failed to receive message from %r' % - (request.connection.remote_addr,)) - return bytes - - -def _write(request, bytes): - try: - request.connection.write(bytes) - except Exception, e: - util.prepend_message_to_exception( - 'Failed to send message to %r: ' % - (request.connection.remote_addr,), - e) - raise - - -def send_message(request, message): - """Send message. - - Args: - request: mod_python request. - message: unicode string to send. - """ - - _write(request, '\x00' + message.encode('utf-8') + '\xff') - - -def receive_message(request): - """Receive a Web Socket frame and return its payload as unicode string. - - Args: - request: mod_python request. - """ - - while True: - # Read 1 byte. - # mp_conn.read will block if no bytes are available. - # Timeout is controlled by TimeOut directive of Apache. - frame_type_str = _read(request, 1) - frame_type = ord(frame_type_str[0]) - if (frame_type & 0x80) == 0x80: - # The payload length is specified in the frame. - # Read and discard. - length = _payload_length(request) - _receive_bytes(request, length) - else: - # The payload is delimited with \xff. - bytes = _read_until(request, '\xff') - # The Web Socket protocol section 4.4 specifies that invalid - # characters must be replaced with U+fffd REPLACEMENT CHARACTER. - message = bytes.decode('utf-8', 'replace') - if frame_type == 0x00: - return message - # Discard data of other types. - - -def _payload_length(request): - length = 0 - while True: - b_str = _read(request, 1) - b = ord(b_str[0]) - length = length * 128 + (b & 0x7f) - if (b & 0x80) == 0: - break - return length - - -def _receive_bytes(request, length): - bytes = [] - while length > 0: - new_bytes = _read(request, length) - bytes.append(new_bytes) - length -= len(new_bytes) - return ''.join(bytes) - - -def _read_until(request, delim_char): - bytes = [] - while True: - ch = _read(request, 1) - if ch == delim_char: - break - bytes.append(ch) - return ''.join(bytes) - - -class MessageReceiver(threading.Thread): - """This class receives messages from the client. - - This class provides three ways to receive messages: blocking, non-blocking, - and via callback. Callback has the highest precedence. - - Note: This class should not be used with the standalone server for wss - because pyOpenSSL used by the server raises a fatal error if the socket - is accessed from multiple threads. - """ - def __init__(self, request, onmessage=None): - """Construct an instance. - - Args: - request: mod_python request. - onmessage: a function to be called when a message is received. - May be None. If not None, the function is called on - another thread. In that case, MessageReceiver.receive - and MessageReceiver.receive_nowait are useless because - they will never return any messages. - """ - threading.Thread.__init__(self) - self._request = request - self._queue = Queue.Queue() - self._onmessage = onmessage - self._stop_requested = False - self.setDaemon(True) - self.start() - - def run(self): - while not self._stop_requested: - message = receive_message(self._request) - if self._onmessage: - self._onmessage(message) - else: - self._queue.put(message) - - def receive(self): - """ Receive a message from the channel, blocking. - - Returns: - message as a unicode string. - """ - return self._queue.get() - - def receive_nowait(self): - """ Receive a message from the channel, non-blocking. - - Returns: - message as a unicode string if available. None otherwise. - """ - try: - message = self._queue.get_nowait() - except Queue.Empty: - message = None - return message - - def stop(self): - """Request to stop this instance. - - The instance will be stopped after receiving the next message. - This method may not be very useful, but there is no clean way - in Python to forcefully stop a running thread. - """ - self._stop_requested = True - - -class MessageSender(threading.Thread): - """This class sends messages to the client. - - This class provides both synchronous and asynchronous ways to send - messages. - - Note: This class should not be used with the standalone server for wss - because pyOpenSSL used by the server raises a fatal error if the socket - is accessed from multiple threads. - """ - def __init__(self, request): - """Construct an instance. - - Args: - request: mod_python request. - """ - threading.Thread.__init__(self) - self._request = request - self._queue = Queue.Queue() - self.setDaemon(True) - self.start() - - def run(self): - while True: - message, condition = self._queue.get() - condition.acquire() - send_message(self._request, message) - condition.notify() - condition.release() - - def send(self, message): - """Send a message, blocking.""" - - condition = threading.Condition() - condition.acquire() - self._queue.put((message, condition)) - condition.wait() - - def send_nowait(self, message): - """Send a message, non-blocking.""" - - self._queue.put((message, threading.Condition())) - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/standalone.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/standalone.py deleted file mode 100644 index f411910..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/standalone.py +++ /dev/null @@ -1,453 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009, 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. - - -"""Standalone Web Socket server. - -Use this server to run mod_pywebsocket without Apache HTTP Server. - -Usage: - python standalone.py [-p <ws_port>] [-w <websock_handlers>] - [-s <scan_dir>] - [-d <document_root>] - [-m <websock_handlers_map_file>] - ... for other options, see _main below ... - -<ws_port> is the port number to use for ws:// connection. - -<document_root> is the path to the root directory of HTML files. - -<websock_handlers> is the path to the root directory of Web Socket handlers. -See __init__.py for details of <websock_handlers> and how to write Web Socket -handlers. If this path is relative, <document_root> is used as the base. - -<scan_dir> is a path under the root directory. If specified, only the handlers -under scan_dir are scanned. This is useful in saving scan time. - -Note: -This server is derived from SocketServer.ThreadingMixIn. Hence a thread is -used for each request. -""" - -import BaseHTTPServer -import CGIHTTPServer -import SimpleHTTPServer -import SocketServer -import logging -import logging.handlers -import optparse -import os -import re -import socket -import sys - -_HAS_OPEN_SSL = False -try: - import OpenSSL.SSL - _HAS_OPEN_SSL = True -except ImportError: - pass - -import dispatch -import handshake -import memorizingfile -import util - - -_LOG_LEVELS = { - 'debug': logging.DEBUG, - 'info': logging.INFO, - 'warn': logging.WARN, - 'error': logging.ERROR, - 'critical': logging.CRITICAL}; - -_DEFAULT_LOG_MAX_BYTES = 1024 * 256 -_DEFAULT_LOG_BACKUP_COUNT = 5 - -_DEFAULT_REQUEST_QUEUE_SIZE = 128 - -# 1024 is practically large enough to contain WebSocket handshake lines. -_MAX_MEMORIZED_LINES = 1024 - -def _print_warnings_if_any(dispatcher): - warnings = dispatcher.source_warnings() - if warnings: - for warning in warnings: - logging.warning('mod_pywebsocket: %s' % warning) - - -class _StandaloneConnection(object): - """Mimic mod_python mp_conn.""" - - def __init__(self, request_handler): - """Construct an instance. - - Args: - request_handler: A WebSocketRequestHandler instance. - """ - self._request_handler = request_handler - - def get_local_addr(self): - """Getter to mimic mp_conn.local_addr.""" - return (self._request_handler.server.server_name, - self._request_handler.server.server_port) - local_addr = property(get_local_addr) - - def get_remote_addr(self): - """Getter to mimic mp_conn.remote_addr. - - Setting the property in __init__ won't work because the request - handler is not initialized yet there.""" - return self._request_handler.client_address - remote_addr = property(get_remote_addr) - - def write(self, data): - """Mimic mp_conn.write().""" - return self._request_handler.wfile.write(data) - - def read(self, length): - """Mimic mp_conn.read().""" - return self._request_handler.rfile.read(length) - - def get_memorized_lines(self): - """Get memorized lines.""" - return self._request_handler.rfile.get_memorized_lines() - - -class _StandaloneRequest(object): - """Mimic mod_python request.""" - - def __init__(self, request_handler, use_tls): - """Construct an instance. - - Args: - request_handler: A WebSocketRequestHandler instance. - """ - self._request_handler = request_handler - self.connection = _StandaloneConnection(request_handler) - self._use_tls = use_tls - - def get_uri(self): - """Getter to mimic request.uri.""" - return self._request_handler.path - uri = property(get_uri) - - def get_headers_in(self): - """Getter to mimic request.headers_in.""" - return self._request_handler.headers - headers_in = property(get_headers_in) - - def is_https(self): - """Mimic request.is_https().""" - return self._use_tls - - -class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): - """HTTPServer specialized for Web Socket.""" - - SocketServer.ThreadingMixIn.daemon_threads = True - - def __init__(self, server_address, RequestHandlerClass): - """Override SocketServer.BaseServer.__init__.""" - - SocketServer.BaseServer.__init__( - self, server_address, RequestHandlerClass) - self.socket = self._create_socket() - self.server_bind() - self.server_activate() - - def _create_socket(self): - socket_ = socket.socket(self.address_family, self.socket_type) - if WebSocketServer.options.use_tls: - ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) - ctx.use_privatekey_file(WebSocketServer.options.private_key) - ctx.use_certificate_file(WebSocketServer.options.certificate) - socket_ = OpenSSL.SSL.Connection(ctx, socket_) - return socket_ - - def handle_error(self, rquest, client_address): - """Override SocketServer.handle_error.""" - - logging.error( - ('Exception in processing request from: %r' % (client_address,)) + - '\n' + util.get_stack_trace()) - # Note: client_address is a tuple. To match it against %r, we need the - # trailing comma. - - -class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler): - """CGIHTTPRequestHandler specialized for Web Socket.""" - - def setup(self): - """Override SocketServer.StreamRequestHandler.setup.""" - - self.connection = self.request - self.rfile = memorizingfile.MemorizingFile( - socket._fileobject(self.request, 'rb', self.rbufsize), - max_memorized_lines=_MAX_MEMORIZED_LINES) - self.wfile = socket._fileobject(self.request, 'wb', self.wbufsize) - - def __init__(self, *args, **keywords): - self._request = _StandaloneRequest( - self, WebSocketRequestHandler.options.use_tls) - self._dispatcher = WebSocketRequestHandler.options.dispatcher - self._print_warnings_if_any() - self._handshaker = handshake.Handshaker( - self._request, self._dispatcher, - WebSocketRequestHandler.options.strict) - CGIHTTPServer.CGIHTTPRequestHandler.__init__( - self, *args, **keywords) - - def _print_warnings_if_any(self): - warnings = self._dispatcher.source_warnings() - if warnings: - for warning in warnings: - logging.warning('mod_pywebsocket: %s' % warning) - - def parse_request(self): - """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request. - - Return True to continue processing for HTTP(S), False otherwise. - """ - result = CGIHTTPServer.CGIHTTPRequestHandler.parse_request(self) - if result: - try: - self._handshaker.do_handshake() - self._dispatcher.transfer_data(self._request) - return False - except handshake.HandshakeError, e: - # Handshake for ws(s) failed. Assume http(s). - logging.info('mod_pywebsocket: %s' % e) - return True - except dispatch.DispatchError, e: - logging.warning('mod_pywebsocket: %s' % e) - return False - except Exception, e: - logging.warning('mod_pywebsocket: %s' % e) - logging.info('mod_pywebsocket: %s' % util.get_stack_trace()) - return False - return result - - def log_request(self, code='-', size='-'): - """Override BaseHTTPServer.log_request.""" - - logging.info('"%s" %s %s', - self.requestline, str(code), str(size)) - - def log_error(self, *args): - """Override BaseHTTPServer.log_error.""" - - # Despite the name, this method is for warnings than for errors. - # For example, HTTP status code is logged by this method. - logging.warn('%s - %s' % (self.address_string(), (args[0] % args[1:]))) - - def is_cgi(self): - """Test whether self.path corresponds to a CGI script. - - Add extra check that self.path doesn't contains .. - Also check if the file is a executable file or not. - If the file is not executable, it is handled as static file or dir - rather than a CGI script. - """ - if CGIHTTPServer.CGIHTTPRequestHandler.is_cgi(self): - if '..' in self.path: - return False - # strip query parameter from request path - resource_name = self.path.split('?', 2)[0] - # convert resource_name into real path name in filesystem. - scriptfile = self.translate_path(resource_name) - if not os.path.isfile(scriptfile): - return False - if not self.is_executable(scriptfile): - return False - return True - return False - - -def _configure_logging(options): - logger = logging.getLogger() - logger.setLevel(_LOG_LEVELS[options.log_level]) - if options.log_file: - handler = logging.handlers.RotatingFileHandler( - options.log_file, 'a', options.log_max, options.log_count) - else: - handler = logging.StreamHandler() - formatter = logging.Formatter( - "[%(asctime)s] [%(levelname)s] %(name)s: %(message)s") - handler.setFormatter(formatter) - logger.addHandler(handler) - -def _alias_handlers(dispatcher, websock_handlers_map_file): - """Set aliases specified in websock_handler_map_file in dispatcher. - - Args: - dispatcher: dispatch.Dispatcher instance - websock_handler_map_file: alias map file - """ - fp = open(websock_handlers_map_file) - try: - for line in fp: - if line[0] == '#' or line.isspace(): - continue - m = re.match('(\S+)\s+(\S+)', line) - if not m: - logging.warning('Wrong format in map file:' + line) - continue - try: - dispatcher.add_resource_path_alias( - m.group(1), m.group(2)) - except dispatch.DispatchError, e: - logging.error(str(e)) - finally: - fp.close() - - - -def _main(): - parser = optparse.OptionParser() - parser.add_option('-H', '--server-host', '--server_host', - dest='server_host', - default='', - help='server hostname to listen to') - parser.add_option('-p', '--port', dest='port', type='int', - default=handshake._DEFAULT_WEB_SOCKET_PORT, - help='port to listen to') - parser.add_option('-w', '--websock-handlers', '--websock_handlers', - dest='websock_handlers', - default='.', - help='Web Socket handlers root directory.') - parser.add_option('-m', '--websock-handlers-map-file', - '--websock_handlers_map_file', - dest='websock_handlers_map_file', - default=None, - help=('Web Socket handlers map file. ' - 'Each line consists of alias_resource_path and ' - 'existing_resource_path, separated by spaces.')) - parser.add_option('-s', '--scan-dir', '--scan_dir', dest='scan_dir', - default=None, - help=('Web Socket handlers scan directory. ' - 'Must be a directory under websock_handlers.')) - parser.add_option('-d', '--document-root', '--document_root', - dest='document_root', default='.', - help='Document root directory.') - parser.add_option('-x', '--cgi-paths', '--cgi_paths', dest='cgi_paths', - default=None, - help=('CGI paths relative to document_root.' - 'Comma-separated. (e.g -x /cgi,/htbin) ' - 'Files under document_root/cgi_path are handled ' - 'as CGI programs. Must be executable.')) - parser.add_option('-t', '--tls', dest='use_tls', action='store_true', - default=False, help='use TLS (wss://)') - parser.add_option('-k', '--private-key', '--private_key', - dest='private_key', - default='', help='TLS private key file.') - parser.add_option('-c', '--certificate', dest='certificate', - default='', help='TLS certificate file.') - parser.add_option('-l', '--log-file', '--log_file', dest='log_file', - default='', help='Log file.') - parser.add_option('--log-level', '--log_level', type='choice', - dest='log_level', default='warn', - choices=['debug', 'info', 'warn', 'error', 'critical'], - help='Log level.') - parser.add_option('--log-max', '--log_max', dest='log_max', type='int', - default=_DEFAULT_LOG_MAX_BYTES, - help='Log maximum bytes') - parser.add_option('--log-count', '--log_count', dest='log_count', - type='int', default=_DEFAULT_LOG_BACKUP_COUNT, - help='Log backup count') - parser.add_option('--strict', dest='strict', action='store_true', - default=False, help='Strictly check handshake request') - parser.add_option('-q', '--queue', dest='request_queue_size', type='int', - default=_DEFAULT_REQUEST_QUEUE_SIZE, - help='request queue size') - options = parser.parse_args()[0] - - os.chdir(options.document_root) - - _configure_logging(options) - - SocketServer.TCPServer.request_queue_size = options.request_queue_size - CGIHTTPServer.CGIHTTPRequestHandler.cgi_directories = [] - - if options.cgi_paths: - CGIHTTPServer.CGIHTTPRequestHandler.cgi_directories = \ - options.cgi_paths.split(',') - if sys.platform in ('cygwin', 'win32'): - cygwin_path = None - # For Win32 Python, it is expected that CYGWIN_PATH - # is set to a directory of cygwin binaries. - # For example, websocket_server.py in Chromium sets CYGWIN_PATH to - # full path of third_party/cygwin/bin. - if 'CYGWIN_PATH' in os.environ: - cygwin_path = os.environ['CYGWIN_PATH'] - util.wrap_popen3_for_win(cygwin_path) - def __check_script(scriptpath): - return util.get_script_interp(scriptpath, cygwin_path) - CGIHTTPServer.executable = __check_script - - if options.use_tls: - if not _HAS_OPEN_SSL: - logging.critical('To use TLS, install pyOpenSSL.') - sys.exit(1) - if not options.private_key or not options.certificate: - logging.critical( - 'To use TLS, specify private_key and certificate.') - sys.exit(1) - - if not options.scan_dir: - options.scan_dir = options.websock_handlers - - try: - # Share a Dispatcher among request handlers to save time for - # instantiation. Dispatcher can be shared because it is thread-safe. - options.dispatcher = dispatch.Dispatcher(options.websock_handlers, - options.scan_dir) - if options.websock_handlers_map_file: - _alias_handlers(options.dispatcher, - options.websock_handlers_map_file) - _print_warnings_if_any(options.dispatcher) - - WebSocketRequestHandler.options = options - WebSocketServer.options = options - - server = WebSocketServer((options.server_host, options.port), - WebSocketRequestHandler) - server.serve_forever() - except Exception, e: - logging.critical(str(e)) - sys.exit(1) - - -if __name__ == '__main__': - _main() - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/util.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/util.py deleted file mode 100644 index 8ec9dca..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/mod_pywebsocket/util.py +++ /dev/null @@ -1,121 +0,0 @@ -# Copyright 2009, 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. - - -"""Web Sockets utilities. -""" - - -import StringIO -import os -import re -import traceback - - -def get_stack_trace(): - """Get the current stack trace as string. - - This is needed to support Python 2.3. - TODO: Remove this when we only support Python 2.4 and above. - Use traceback.format_exc instead. - """ - - out = StringIO.StringIO() - traceback.print_exc(file=out) - return out.getvalue() - - -def prepend_message_to_exception(message, exc): - """Prepend message to the exception.""" - - exc.args = (message + str(exc),) - return - - -def __translate_interp(interp, cygwin_path): - """Translate interp program path for Win32 python to run cygwin program - (e.g. perl). Note that it doesn't support path that contains space, - which is typically true for Unix, where #!-script is written. - For Win32 python, cygwin_path is a directory of cygwin binaries. - - Args: - interp: interp command line - cygwin_path: directory name of cygwin binary, or None - Returns: - translated interp command line. - """ - if not cygwin_path: - return interp - m = re.match("^[^ ]*/([^ ]+)( .*)?", interp) - if m: - cmd = os.path.join(cygwin_path, m.group(1)) - return cmd + m.group(2) - return interp - - -def get_script_interp(script_path, cygwin_path=None): - """Gets #!-interpreter command line from the script. - - It also fixes command path. When Cygwin Python is used, e.g. in WebKit, - it could run "/usr/bin/perl -wT hello.pl". - When Win32 Python is used, e.g. in Chromium, it couldn't. So, fix - "/usr/bin/perl" to "<cygwin_path>\perl.exe". - - Args: - script_path: pathname of the script - cygwin_path: directory name of cygwin binary, or None - Returns: - #!-interpreter command line, or None if it is not #!-script. - """ - fp = open(script_path) - line = fp.readline() - fp.close() - m = re.match("^#!(.*)", line) - if m: - return __translate_interp(m.group(1), cygwin_path) - return None - -def wrap_popen3_for_win(cygwin_path): - """Wrap popen3 to support #!-script on Windows. - - Args: - cygwin_path: path for cygwin binary if command path is needed to be - translated. None if no translation required. - """ - __orig_popen3 = os.popen3 - def __wrap_popen3(cmd, mode='t', bufsize=-1): - cmdline = cmd.split(' ') - interp = get_script_interp(cmdline[0], cygwin_path) - if interp: - cmd = interp + " " + cmd - return __orig_popen3(cmd, mode, bufsize) - os.popen3 = __wrap_popen3 - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/setup.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/setup.py deleted file mode 100644 index a34a83b..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/setup.py +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009, 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. - - -"""Set up script for mod_pywebsocket. -""" - - -from distutils.core import setup -import sys - - -_PACKAGE_NAME = 'mod_pywebsocket' - -if sys.version < '2.3': - print >>sys.stderr, '%s requires Python 2.3 or later.' % _PACKAGE_NAME - sys.exit(1) - -setup(author='Yuzo Fujishima', - author_email='yuzo@chromium.org', - description='Web Socket extension for Apache HTTP Server.', - long_description=( - 'mod_pywebsocket is an Apache HTTP Server extension for ' - 'Web Socket (http://tools.ietf.org/html/' - 'draft-hixie-thewebsocketprotocol). ' - 'See mod_pywebsocket/__init__.py for more detail.'), - license='See COPYING', - name=_PACKAGE_NAME, - packages=[_PACKAGE_NAME], - url='http://code.google.com/p/pywebsocket/', - version='0.4.9.2', - ) - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/config.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/config.py deleted file mode 100644 index 5aaab8c..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/config.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2009, 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. - - -"""Configuration for testing. - -Test files should import this module before mod_pywebsocket. -""" - - -import os -import sys - - -# Add the parent directory to sys.path to enable importing mod_pywebsocket. -sys.path += [os.path.join(os.path.split(__file__)[0], '..')] - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/mock.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/mock.py deleted file mode 100644 index 3b85d64..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/mock.py +++ /dev/null @@ -1,205 +0,0 @@ -# Copyright 2009, 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. - - -"""Mocks for testing. -""" - - -import Queue -import threading - - -class _MockConnBase(object): - """Base class of mocks for mod_python.apache.mp_conn. - - This enables tests to check what is written to a (mock) mp_conn. - """ - - def __init__(self): - self._write_data = [] - - def write(self, data): - """Override mod_python.apache.mp_conn.write.""" - - self._write_data.append(data) - - def written_data(self): - """Get bytes written to this mock.""" - - return ''.join(self._write_data) - - -class MockConn(_MockConnBase): - """Mock for mod_python.apache.mp_conn. - - This enables tests to specify what should be read from a (mock) mp_conn as - well as to check what is written to it. - """ - - def __init__(self, read_data): - """Constructs an instance. - - Args: - read_data: bytes that should be returned when read* methods are - called. - """ - - _MockConnBase.__init__(self) - self._read_data = read_data - self._read_pos = 0 - - def readline(self): - """Override mod_python.apache.mp_conn.readline.""" - - if self._read_pos >= len(self._read_data): - return '' - end_index = self._read_data.find('\n', self._read_pos) + 1 - if not end_index: - end_index = len(self._read_data) - return self._read_up_to(end_index) - - def read(self, length): - """Override mod_python.apache.mp_conn.read.""" - - if self._read_pos >= len(self._read_data): - return '' - end_index = min(len(self._read_data), self._read_pos + length) - return self._read_up_to(end_index) - - def _read_up_to(self, end_index): - line = self._read_data[self._read_pos:end_index] - self._read_pos = end_index - return line - - -class MockBlockingConn(_MockConnBase): - """Blocking mock for mod_python.apache.mp_conn. - - This enables tests to specify what should be read from a (mock) mp_conn as - well as to check what is written to it. - Callers of read* methods will block if there is no bytes available. - """ - - def __init__(self): - _MockConnBase.__init__(self) - self._queue = Queue.Queue() - - def readline(self): - """Override mod_python.apache.mp_conn.readline.""" - line = '' - while True: - c = self._queue.get() - line += c - if c == '\n': - return line - - def read(self, length): - """Override mod_python.apache.mp_conn.read.""" - - data = '' - for unused in range(length): - data += self._queue.get() - return data - - def put_bytes(self, bytes): - """Put bytes to be read from this mock. - - Args: - bytes: bytes to be read. - """ - - for byte in bytes: - self._queue.put(byte) - - -class MockTable(dict): - """Mock table. - - This mimics mod_python mp_table. Note that only the methods used by - tests are overridden. - """ - - def __init__(self, copy_from={}): - if isinstance(copy_from, dict): - copy_from = copy_from.items() - for key, value in copy_from: - self.__setitem__(key, value) - - def __getitem__(self, key): - return super(MockTable, self).__getitem__(key.lower()) - - def __setitem__(self, key, value): - super(MockTable, self).__setitem__(key.lower(), value) - - def get(self, key, def_value=None): - return super(MockTable, self).get(key.lower(), def_value) - - -class MockRequest(object): - """Mock request. - - This mimics mod_python request. - """ - - def __init__(self, uri=None, headers_in={}, connection=None, - is_https=False): - """Construct an instance. - - Arguments: - uri: URI of the request. - headers_in: Request headers. - connection: Connection used for the request. - is_https: Whether this request is over SSL. - - See the document of mod_python Request for details. - """ - self.uri = uri - self.connection = connection - self.headers_in = MockTable(headers_in) - # self.is_https_ needs to be accessible from tests. To avoid name - # conflict with self.is_https(), it is named as such. - self.is_https_ = is_https - - def is_https(self): - """Return whether this request is over SSL.""" - return self.is_https_ - - -class MockDispatcher(object): - """Mock for dispatch.Dispatcher.""" - - def do_extra_handshake(self, conn_context): - pass - - def transfer_data(self, conn_context): - pass - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/test_dispatch.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/test_dispatch.py deleted file mode 100644 index 5403228..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/test_dispatch.py +++ /dev/null @@ -1,244 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009, 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. - - -"""Tests for dispatch module.""" - - - -import os -import unittest - -import config # This must be imported before mod_pywebsocket. -from mod_pywebsocket import dispatch - -import mock - - -_TEST_HANDLERS_DIR = os.path.join( - os.path.split(__file__)[0], 'testdata', 'handlers') - -_TEST_HANDLERS_SUB_DIR = os.path.join(_TEST_HANDLERS_DIR, 'sub') - -class DispatcherTest(unittest.TestCase): - def test_normalize_path(self): - self.assertEqual(os.path.abspath('/a/b').replace('\\', '/'), - dispatch._normalize_path('/a/b')) - self.assertEqual(os.path.abspath('/a/b').replace('\\', '/'), - dispatch._normalize_path('\\a\\b')) - self.assertEqual(os.path.abspath('/a/b').replace('\\', '/'), - dispatch._normalize_path('/a/c/../b')) - self.assertEqual(os.path.abspath('abc').replace('\\', '/'), - dispatch._normalize_path('abc')) - - def test_converter(self): - converter = dispatch._path_to_resource_converter('/a/b') - self.assertEqual('/h', converter('/a/b/h_wsh.py')) - self.assertEqual('/c/h', converter('/a/b/c/h_wsh.py')) - self.assertEqual(None, converter('/a/b/h.py')) - self.assertEqual(None, converter('a/b/h_wsh.py')) - - converter = dispatch._path_to_resource_converter('a/b') - self.assertEqual('/h', converter('a/b/h_wsh.py')) - - converter = dispatch._path_to_resource_converter('/a/b///') - self.assertEqual('/h', converter('/a/b/h_wsh.py')) - self.assertEqual('/h', converter('/a/b/../b/h_wsh.py')) - - converter = dispatch._path_to_resource_converter('/a/../a/b/../b/') - self.assertEqual('/h', converter('/a/b/h_wsh.py')) - - converter = dispatch._path_to_resource_converter(r'\a\b') - self.assertEqual('/h', converter(r'\a\b\h_wsh.py')) - self.assertEqual('/h', converter(r'/a/b/h_wsh.py')) - - def test_source_file_paths(self): - paths = list(dispatch._source_file_paths(_TEST_HANDLERS_DIR)) - paths.sort() - self.assertEqual(7, len(paths)) - expected_paths = [ - os.path.join(_TEST_HANDLERS_DIR, 'blank_wsh.py'), - os.path.join(_TEST_HANDLERS_DIR, 'origin_check_wsh.py'), - os.path.join(_TEST_HANDLERS_DIR, 'sub', - 'exception_in_transfer_wsh.py'), - os.path.join(_TEST_HANDLERS_DIR, 'sub', 'non_callable_wsh.py'), - os.path.join(_TEST_HANDLERS_DIR, 'sub', 'plain_wsh.py'), - os.path.join(_TEST_HANDLERS_DIR, 'sub', - 'wrong_handshake_sig_wsh.py'), - os.path.join(_TEST_HANDLERS_DIR, 'sub', - 'wrong_transfer_sig_wsh.py'), - ] - for expected, actual in zip(expected_paths, paths): - self.assertEqual(expected, actual) - - def test_source(self): - self.assertRaises(dispatch.DispatchError, dispatch._source, '') - self.assertRaises(dispatch.DispatchError, dispatch._source, 'def') - self.assertRaises(dispatch.DispatchError, dispatch._source, '1/0') - self.failUnless(dispatch._source( - 'def web_socket_do_extra_handshake(request):pass\n' - 'def web_socket_transfer_data(request):pass\n')) - - def test_source_warnings(self): - dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) - warnings = dispatcher.source_warnings() - warnings.sort() - expected_warnings = [ - (os.path.join(_TEST_HANDLERS_DIR, 'blank_wsh.py') + - ': web_socket_do_extra_handshake is not defined.'), - (os.path.join(_TEST_HANDLERS_DIR, 'sub', - 'non_callable_wsh.py') + - ': web_socket_do_extra_handshake is not callable.'), - (os.path.join(_TEST_HANDLERS_DIR, 'sub', - 'wrong_handshake_sig_wsh.py') + - ': web_socket_do_extra_handshake is not defined.'), - (os.path.join(_TEST_HANDLERS_DIR, 'sub', - 'wrong_transfer_sig_wsh.py') + - ': web_socket_transfer_data is not defined.'), - ] - self.assertEquals(4, len(warnings)) - for expected, actual in zip(expected_warnings, warnings): - self.assertEquals(expected, actual) - - def test_do_extra_handshake(self): - dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) - request = mock.MockRequest() - request.ws_resource = '/origin_check' - request.ws_origin = 'http://example.com' - dispatcher.do_extra_handshake(request) # Must not raise exception. - - request.ws_origin = 'http://bad.example.com' - self.assertRaises(Exception, dispatcher.do_extra_handshake, request) - - def test_transfer_data(self): - dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) - request = mock.MockRequest(connection=mock.MockConn('')) - request.ws_resource = '/origin_check' - request.ws_protocol = 'p1' - - dispatcher.transfer_data(request) - self.assertEqual('origin_check_wsh.py is called for /origin_check, p1', - request.connection.written_data()) - - request = mock.MockRequest(connection=mock.MockConn('')) - request.ws_resource = '/sub/plain' - request.ws_protocol = None - dispatcher.transfer_data(request) - self.assertEqual('sub/plain_wsh.py is called for /sub/plain, None', - request.connection.written_data()) - - request = mock.MockRequest(connection=mock.MockConn('')) - request.ws_resource = '/sub/plain?' - request.ws_protocol = None - dispatcher.transfer_data(request) - self.assertEqual('sub/plain_wsh.py is called for /sub/plain?, None', - request.connection.written_data()) - - request = mock.MockRequest(connection=mock.MockConn('')) - request.ws_resource = '/sub/plain?q=v' - request.ws_protocol = None - dispatcher.transfer_data(request) - self.assertEqual('sub/plain_wsh.py is called for /sub/plain?q=v, None', - request.connection.written_data()) - - def test_transfer_data_no_handler(self): - dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) - for resource in ['/blank', '/sub/non_callable', - '/sub/no_wsh_at_the_end', '/does/not/exist']: - request = mock.MockRequest(connection=mock.MockConn('')) - request.ws_resource = resource - request.ws_protocol = 'p2' - try: - dispatcher.transfer_data(request) - self.fail() - except dispatch.DispatchError, e: - self.failUnless(str(e).find('No handler') != -1) - except Exception: - self.fail() - - def test_transfer_data_handler_exception(self): - dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) - request = mock.MockRequest(connection=mock.MockConn('')) - request.ws_resource = '/sub/exception_in_transfer' - request.ws_protocol = 'p3' - try: - dispatcher.transfer_data(request) - self.fail() - except Exception, e: - self.failUnless(str(e).find('Intentional') != -1) - - def test_scan_dir(self): - disp = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) - self.assertEqual(3, len(disp._handlers)) - self.failUnless(disp._handlers.has_key('/origin_check')) - self.failUnless(disp._handlers.has_key('/sub/exception_in_transfer')) - self.failUnless(disp._handlers.has_key('/sub/plain')) - - def test_scan_sub_dir(self): - disp = dispatch.Dispatcher(_TEST_HANDLERS_DIR, _TEST_HANDLERS_SUB_DIR) - self.assertEqual(2, len(disp._handlers)) - self.failIf(disp._handlers.has_key('/origin_check')) - self.failUnless(disp._handlers.has_key('/sub/exception_in_transfer')) - self.failUnless(disp._handlers.has_key('/sub/plain')) - - def test_scan_sub_dir_as_root(self): - disp = dispatch.Dispatcher(_TEST_HANDLERS_SUB_DIR, - _TEST_HANDLERS_SUB_DIR) - self.assertEqual(2, len(disp._handlers)) - self.failIf(disp._handlers.has_key('/origin_check')) - self.failIf(disp._handlers.has_key('/sub/exception_in_transfer')) - self.failIf(disp._handlers.has_key('/sub/plain')) - self.failUnless(disp._handlers.has_key('/exception_in_transfer')) - self.failUnless(disp._handlers.has_key('/plain')) - - def test_scan_dir_must_under_root(self): - dispatch.Dispatcher('a/b', 'a/b/c') # OK - dispatch.Dispatcher('a/b///', 'a/b') # OK - self.assertRaises(dispatch.DispatchError, - dispatch.Dispatcher, 'a/b/c', 'a/b') - - def test_resource_path_alias(self): - disp = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) - disp.add_resource_path_alias('/', '/origin_check') - self.assertEqual(4, len(disp._handlers)) - self.failUnless(disp._handlers.has_key('/origin_check')) - self.failUnless(disp._handlers.has_key('/sub/exception_in_transfer')) - self.failUnless(disp._handlers.has_key('/sub/plain')) - self.failUnless(disp._handlers.has_key('/')) - self.assertRaises(dispatch.DispatchError, - disp.add_resource_path_alias, '/alias', '/not-exist') - - -if __name__ == '__main__': - unittest.main() - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/test_handshake.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/test_handshake.py deleted file mode 100644 index 8bf07be..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/test_handshake.py +++ /dev/null @@ -1,513 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009, 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. - - -"""Tests for handshake module.""" - - -import unittest - -import config # This must be imported before mod_pywebsocket. -from mod_pywebsocket import handshake - -import mock - - -_GOOD_REQUEST = ( - 80, - '/demo', - { - 'Upgrade':'WebSocket', - 'Connection':'Upgrade', - 'Host':'example.com', - 'Origin':'http://example.com', - 'WebSocket-Protocol':'sample', - } -) - -_GOOD_RESPONSE_DEFAULT_PORT = ( - 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n' - 'Upgrade: WebSocket\r\n' - 'Connection: Upgrade\r\n' - 'WebSocket-Origin: http://example.com\r\n' - 'WebSocket-Location: ws://example.com/demo\r\n' - 'WebSocket-Protocol: sample\r\n' - '\r\n') - -_GOOD_RESPONSE_SECURE = ( - 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n' - 'Upgrade: WebSocket\r\n' - 'Connection: Upgrade\r\n' - 'WebSocket-Origin: http://example.com\r\n' - 'WebSocket-Location: wss://example.com/demo\r\n' - 'WebSocket-Protocol: sample\r\n' - '\r\n') - -_GOOD_REQUEST_NONDEFAULT_PORT = ( - 8081, - '/demo', - { - 'Upgrade':'WebSocket', - 'Connection':'Upgrade', - 'Host':'example.com:8081', - 'Origin':'http://example.com', - 'WebSocket-Protocol':'sample', - } -) - -_GOOD_RESPONSE_NONDEFAULT_PORT = ( - 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n' - 'Upgrade: WebSocket\r\n' - 'Connection: Upgrade\r\n' - 'WebSocket-Origin: http://example.com\r\n' - 'WebSocket-Location: ws://example.com:8081/demo\r\n' - 'WebSocket-Protocol: sample\r\n' - '\r\n') - -_GOOD_RESPONSE_SECURE_NONDEF = ( - 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n' - 'Upgrade: WebSocket\r\n' - 'Connection: Upgrade\r\n' - 'WebSocket-Origin: http://example.com\r\n' - 'WebSocket-Location: wss://example.com:8081/demo\r\n' - 'WebSocket-Protocol: sample\r\n' - '\r\n') - -_GOOD_REQUEST_NO_PROTOCOL = ( - 80, - '/demo', - { - 'Upgrade':'WebSocket', - 'Connection':'Upgrade', - 'Host':'example.com', - 'Origin':'http://example.com', - } -) - -_GOOD_RESPONSE_NO_PROTOCOL = ( - 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n' - 'Upgrade: WebSocket\r\n' - 'Connection: Upgrade\r\n' - 'WebSocket-Origin: http://example.com\r\n' - 'WebSocket-Location: ws://example.com/demo\r\n' - '\r\n') - -_GOOD_REQUEST_WITH_OPTIONAL_HEADERS = ( - 80, - '/demo', - { - 'Upgrade':'WebSocket', - 'Connection':'Upgrade', - 'Host':'example.com', - 'Origin':'http://example.com', - 'WebSocket-Protocol':'sample', - 'AKey':'AValue', - 'EmptyValue':'', - } -) - -_BAD_REQUESTS = ( - ( # HTTP request - 80, - '/demo', - { - 'Host':'www.google.com', - 'User-Agent':'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5;' - ' en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3' - ' GTB6 GTBA', - 'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,' - '*/*;q=0.8', - 'Accept-Language':'en-us,en;q=0.5', - 'Accept-Encoding':'gzip,deflate', - 'Accept-Charset':'ISO-8859-1,utf-8;q=0.7,*;q=0.7', - 'Keep-Alive':'300', - 'Connection':'keep-alive', - } - ), - ( # Missing Upgrade - 80, - '/demo', - { - 'Connection':'Upgrade', - 'Host':'example.com', - 'Origin':'http://example.com', - 'WebSocket-Protocol':'sample', - } - ), - ( # Wrong Upgrade - 80, - '/demo', - { - 'Upgrade':'NonWebSocket', - 'Connection':'Upgrade', - 'Host':'example.com', - 'Origin':'http://example.com', - 'WebSocket-Protocol':'sample', - } - ), - ( # Empty WebSocket-Protocol - 80, - '/demo', - { - 'Upgrade':'WebSocket', - 'Connection':'Upgrade', - 'Host':'example.com', - 'Origin':'http://example.com', - 'WebSocket-Protocol':'', - } - ), - ( # Wrong port number format - 80, - '/demo', - { - 'Upgrade':'WebSocket', - 'Connection':'Upgrade', - 'Host':'example.com:0x50', - 'Origin':'http://example.com', - 'WebSocket-Protocol':'sample', - } - ), - ( # Header/connection port mismatch - 8080, - '/demo', - { - 'Upgrade':'WebSocket', - 'Connection':'Upgrade', - 'Host':'example.com', - 'Origin':'http://example.com', - 'WebSocket-Protocol':'sample', - } - ), - ( # Illegal WebSocket-Protocol - 80, - '/demo', - { - 'Upgrade':'WebSocket', - 'Connection':'Upgrade', - 'Host':'example.com', - 'Origin':'http://example.com', - 'WebSocket-Protocol':'illegal\x09protocol', - } - ), -) - -_STRICTLY_GOOD_REQUESTS = ( - ( - 'GET /demo HTTP/1.1\r\n', - 'Upgrade: WebSocket\r\n', - 'Connection: Upgrade\r\n', - 'Host: example.com\r\n', - 'Origin: http://example.com\r\n', - '\r\n', - ), - ( # WebSocket-Protocol - 'GET /demo HTTP/1.1\r\n', - 'Upgrade: WebSocket\r\n', - 'Connection: Upgrade\r\n', - 'Host: example.com\r\n', - 'Origin: http://example.com\r\n', - 'WebSocket-Protocol: sample\r\n', - '\r\n', - ), - ( # WebSocket-Protocol and Cookie - 'GET /demo HTTP/1.1\r\n', - 'Upgrade: WebSocket\r\n', - 'Connection: Upgrade\r\n', - 'Host: example.com\r\n', - 'Origin: http://example.com\r\n', - 'WebSocket-Protocol: sample\r\n', - 'Cookie: xyz\r\n' - '\r\n', - ), - ( # Cookie - 'GET /demo HTTP/1.1\r\n', - 'Upgrade: WebSocket\r\n', - 'Connection: Upgrade\r\n', - 'Host: example.com\r\n', - 'Origin: http://example.com\r\n', - 'Cookie: abc/xyz\r\n' - 'Cookie2: $Version=1\r\n' - 'Cookie: abc\r\n' - '\r\n', - ), - ( - 'GET / HTTP/1.1\r\n', - 'Upgrade: WebSocket\r\n', - 'Connection: Upgrade\r\n', - 'Host: example.com\r\n', - 'Origin: http://example.com\r\n', - '\r\n', - ), -) - -_NOT_STRICTLY_GOOD_REQUESTS = ( - ( # Extra space after GET - 'GET /demo HTTP/1.1\r\n', - 'Upgrade: WebSocket\r\n', - 'Connection: Upgrade\r\n', - 'Host: example.com\r\n', - 'Origin: http://example.com\r\n', - '\r\n', - ), - ( # Resource name doesn't stat with '/' - 'GET demo HTTP/1.1\r\n', - 'Upgrade: WebSocket\r\n', - 'Connection: Upgrade\r\n', - 'Host: example.com\r\n', - 'Origin: http://example.com\r\n', - '\r\n', - ), - ( # No space after : - 'GET /demo HTTP/1.1\r\n', - 'Upgrade:WebSocket\r\n', - 'Connection: Upgrade\r\n', - 'Host: example.com\r\n', - 'Origin: http://example.com\r\n', - '\r\n', - ), - ( # Lower case Upgrade header - 'GET /demo HTTP/1.1\r\n', - 'upgrade: WebSocket\r\n', - 'Connection: Upgrade\r\n', - 'Host: example.com\r\n', - 'Origin: http://example.com\r\n', - '\r\n', - ), - ( # Connection comes before Upgrade - 'GET /demo HTTP/1.1\r\n', - 'Connection: Upgrade\r\n', - 'Upgrade: WebSocket\r\n', - 'Host: example.com\r\n', - 'Origin: http://example.com\r\n', - '\r\n', - ), - ( # Origin comes before Host - 'GET /demo HTTP/1.1\r\n', - 'Upgrade: WebSocket\r\n', - 'Connection: Upgrade\r\n', - 'Origin: http://example.com\r\n', - 'Host: example.com\r\n', - '\r\n', - ), - ( # Host continued to the next line - 'GET /demo HTTP/1.1\r\n', - 'Upgrade: WebSocket\r\n', - 'Connection: Upgrade\r\n', - 'Host: example\r\n', - ' .com\r\n', - 'Origin: http://example.com\r\n', - '\r\n', - ), - ( # Cookie comes before WebSocket-Protocol - 'GET /demo HTTP/1.1\r\n', - 'Upgrade: WebSocket\r\n', - 'Connection: Upgrade\r\n', - 'Host: example.com\r\n', - 'Origin: http://example.com\r\n', - 'Cookie: xyz\r\n' - 'WebSocket-Protocol: sample\r\n', - '\r\n', - ), - ( # Unknown header - 'GET /demo HTTP/1.1\r\n', - 'Upgrade: WebSocket\r\n', - 'Connection: Upgrade\r\n', - 'Host: example.com\r\n', - 'Origin: http://example.com\r\n', - 'Content-Type: text/html\r\n' - '\r\n', - ), - ( # Cookie with continuation lines - 'GET /demo HTTP/1.1\r\n', - 'Upgrade: WebSocket\r\n', - 'Connection: Upgrade\r\n', - 'Host: example.com\r\n', - 'Origin: http://example.com\r\n', - 'Cookie: xyz\r\n', - ' abc\r\n', - ' defg\r\n', - '\r\n', - ), - ( # Wrong-case cookie - 'GET /demo HTTP/1.1\r\n', - 'Upgrade: WebSocket\r\n', - 'Connection: Upgrade\r\n', - 'Host: example.com\r\n', - 'Origin: http://example.com\r\n', - 'cookie: abc/xyz\r\n' - '\r\n', - ), - ( # Cookie, no space after colon - 'GET /demo HTTP/1.1\r\n', - 'Upgrade: WebSocket\r\n', - 'Connection: Upgrade\r\n', - 'Host: example.com\r\n', - 'Origin: http://example.com\r\n', - 'Cookie:abc/xyz\r\n' - '\r\n', - ), -) - - -def _create_request(request_def): - conn = mock.MockConn('') - conn.local_addr = ('0.0.0.0', request_def[0]) - return mock.MockRequest( - uri=request_def[1], - headers_in=request_def[2], - connection=conn) - - -def _create_get_memorized_lines(lines): - def get_memorized_lines(): - return lines - return get_memorized_lines - - -def _create_requests_with_lines(request_lines_set): - requests = [] - for lines in request_lines_set: - request = _create_request(_GOOD_REQUEST) - request.connection.get_memorized_lines = _create_get_memorized_lines( - lines) - requests.append(request) - return requests - - -class HandshakerTest(unittest.TestCase): - def test_validate_protocol(self): - handshake._validate_protocol('sample') # should succeed. - handshake._validate_protocol('Sample') # should succeed. - handshake._validate_protocol('sample\x20protocol') # should succeed. - handshake._validate_protocol('sample\x7eprotocol') # should succeed. - self.assertRaises(handshake.HandshakeError, - handshake._validate_protocol, - '') - self.assertRaises(handshake.HandshakeError, - handshake._validate_protocol, - 'sample\x19protocol') - self.assertRaises(handshake.HandshakeError, - handshake._validate_protocol, - 'sample\x7fprotocol') - self.assertRaises(handshake.HandshakeError, - handshake._validate_protocol, - # "Japan" in Japanese - u'\u65e5\u672c') - - def test_good_request_default_port(self): - request = _create_request(_GOOD_REQUEST) - handshaker = handshake.Handshaker(request, - mock.MockDispatcher()) - handshaker.do_handshake() - self.assertEqual(_GOOD_RESPONSE_DEFAULT_PORT, - request.connection.written_data()) - self.assertEqual('/demo', request.ws_resource) - self.assertEqual('http://example.com', request.ws_origin) - self.assertEqual('ws://example.com/demo', request.ws_location) - self.assertEqual('sample', request.ws_protocol) - - def test_good_request_secure_default_port(self): - request = _create_request(_GOOD_REQUEST) - request.connection.local_addr = ('0.0.0.0', 443) - request.is_https_ = True - handshaker = handshake.Handshaker(request, - mock.MockDispatcher()) - handshaker.do_handshake() - self.assertEqual(_GOOD_RESPONSE_SECURE, - request.connection.written_data()) - self.assertEqual('sample', request.ws_protocol) - - def test_good_request_nondefault_port(self): - request = _create_request(_GOOD_REQUEST_NONDEFAULT_PORT) - handshaker = handshake.Handshaker(request, - mock.MockDispatcher()) - handshaker.do_handshake() - self.assertEqual(_GOOD_RESPONSE_NONDEFAULT_PORT, - request.connection.written_data()) - self.assertEqual('sample', request.ws_protocol) - - def test_good_request_secure_non_default_port(self): - request = _create_request(_GOOD_REQUEST_NONDEFAULT_PORT) - request.is_https_ = True - handshaker = handshake.Handshaker(request, - mock.MockDispatcher()) - handshaker.do_handshake() - self.assertEqual(_GOOD_RESPONSE_SECURE_NONDEF, - request.connection.written_data()) - self.assertEqual('sample', request.ws_protocol) - - def test_good_request_default_no_protocol(self): - request = _create_request(_GOOD_REQUEST_NO_PROTOCOL) - handshaker = handshake.Handshaker(request, - mock.MockDispatcher()) - handshaker.do_handshake() - self.assertEqual(_GOOD_RESPONSE_NO_PROTOCOL, - request.connection.written_data()) - self.assertEqual(None, request.ws_protocol) - - def test_good_request_optional_headers(self): - request = _create_request(_GOOD_REQUEST_WITH_OPTIONAL_HEADERS) - handshaker = handshake.Handshaker(request, - mock.MockDispatcher()) - handshaker.do_handshake() - self.assertEqual('AValue', - request.headers_in['AKey']) - self.assertEqual('', - request.headers_in['EmptyValue']) - - def test_bad_requests(self): - for request in map(_create_request, _BAD_REQUESTS): - handshaker = handshake.Handshaker(request, - mock.MockDispatcher()) - self.assertRaises(handshake.HandshakeError, handshaker.do_handshake) - - def test_strictly_good_requests(self): - for request in _create_requests_with_lines(_STRICTLY_GOOD_REQUESTS): - strict_handshaker = handshake.Handshaker(request, - mock.MockDispatcher(), - True) - strict_handshaker.do_handshake() - - def test_not_strictly_good_requests(self): - for request in _create_requests_with_lines(_NOT_STRICTLY_GOOD_REQUESTS): - strict_handshaker = handshake.Handshaker(request, - mock.MockDispatcher(), - True) - self.assertRaises(handshake.HandshakeError, - strict_handshaker.do_handshake) - - - -if __name__ == '__main__': - unittest.main() - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/test_mock.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/test_mock.py deleted file mode 100644 index 8b137d1..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/test_mock.py +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009, 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. - - -"""Tests for mock module.""" - - -import Queue -import threading -import unittest - -import mock - - -class MockConnTest(unittest.TestCase): - def setUp(self): - self._conn = mock.MockConn('ABC\r\nDEFG\r\n\r\nHIJK') - - def test_readline(self): - self.assertEqual('ABC\r\n', self._conn.readline()) - self.assertEqual('DEFG\r\n', self._conn.readline()) - self.assertEqual('\r\n', self._conn.readline()) - self.assertEqual('HIJK', self._conn.readline()) - self.assertEqual('', self._conn.readline()) - - def test_read(self): - self.assertEqual('ABC\r\nD', self._conn.read(6)) - self.assertEqual('EFG\r\n\r\nHI', self._conn.read(9)) - self.assertEqual('JK', self._conn.read(10)) - self.assertEqual('', self._conn.read(10)) - - def test_read_and_readline(self): - self.assertEqual('ABC\r\nD', self._conn.read(6)) - self.assertEqual('EFG\r\n', self._conn.readline()) - self.assertEqual('\r\nHIJK', self._conn.read(9)) - self.assertEqual('', self._conn.readline()) - - def test_write(self): - self._conn.write('Hello\r\n') - self._conn.write('World\r\n') - self.assertEqual('Hello\r\nWorld\r\n', self._conn.written_data()) - - -class MockBlockingConnTest(unittest.TestCase): - def test_read(self): - class LineReader(threading.Thread): - def __init__(self, conn, queue): - threading.Thread.__init__(self) - self._queue = queue - self._conn = conn - self.setDaemon(True) - self.start() - def run(self): - while True: - data = self._conn.readline() - self._queue.put(data) - conn = mock.MockBlockingConn() - queue = Queue.Queue() - reader = LineReader(conn, queue) - self.failUnless(queue.empty()) - conn.put_bytes('Foo bar\r\n') - read = queue.get() - self.assertEqual('Foo bar\r\n', read) - - -class MockTableTest(unittest.TestCase): - def test_create_from_dict(self): - table = mock.MockTable({'Key':'Value'}) - self.assertEqual('Value', table.get('KEY')) - self.assertEqual('Value', table['key']) - - def test_create_from_list(self): - table = mock.MockTable([('Key', 'Value')]) - self.assertEqual('Value', table.get('KEY')) - self.assertEqual('Value', table['key']) - - def test_create_from_tuple(self): - table = mock.MockTable((('Key', 'Value'),)) - self.assertEqual('Value', table.get('KEY')) - self.assertEqual('Value', table['key']) - - def test_set_and_get(self): - table = mock.MockTable() - self.assertEqual(None, table.get('Key')) - table['Key'] = 'Value' - self.assertEqual('Value', table.get('Key')) - self.assertEqual('Value', table.get('key')) - self.assertEqual('Value', table.get('KEY')) - self.assertEqual('Value', table['Key']) - self.assertEqual('Value', table['key']) - self.assertEqual('Value', table['KEY']) - - -if __name__ == '__main__': - unittest.main() - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/test_msgutil.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/test_msgutil.py deleted file mode 100644 index 16b88e0..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/test_msgutil.py +++ /dev/null @@ -1,156 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009, 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. - - -"""Tests for msgutil module.""" - - -import Queue -import unittest - -import config # This must be imported before mod_pywebsocket. -from mod_pywebsocket import msgutil - -import mock - - -def _create_request(read_data): - return mock.MockRequest(connection=mock.MockConn(read_data)) - -def _create_blocking_request(): - return mock.MockRequest(connection=mock.MockBlockingConn()) - -class MessageTest(unittest.TestCase): - def test_send_message(self): - request = _create_request('') - msgutil.send_message(request, 'Hello') - self.assertEqual('\x00Hello\xff', request.connection.written_data()) - - def test_send_message_unicode(self): - request = _create_request('') - msgutil.send_message(request, u'\u65e5') - # U+65e5 is encoded as e6,97,a5 in UTF-8 - self.assertEqual('\x00\xe6\x97\xa5\xff', - request.connection.written_data()) - - def test_receive_message(self): - request = _create_request('\x00Hello\xff\x00World!\xff') - self.assertEqual('Hello', msgutil.receive_message(request)) - self.assertEqual('World!', msgutil.receive_message(request)) - - def test_receive_message_unicode(self): - request = _create_request('\x00\xe6\x9c\xac\xff') - # U+672c is encoded as e6,9c,ac in UTF-8 - self.assertEqual(u'\u672c', msgutil.receive_message(request)) - - def test_receive_message_erroneous_unicode(self): - # \x80 and \x81 are invalid as UTF-8. - request = _create_request('\x00\x80\x81\xff') - # Invalid characters should be replaced with - # U+fffd REPLACEMENT CHARACTER - self.assertEqual(u'\ufffd\ufffd', msgutil.receive_message(request)) - - def test_receive_message_discard(self): - request = _create_request('\x80\x06IGNORE\x00Hello\xff' - '\x01DISREGARD\xff\x00World!\xff') - self.assertEqual('Hello', msgutil.receive_message(request)) - self.assertEqual('World!', msgutil.receive_message(request)) - - def test_payload_length(self): - for length, bytes in ((0, '\x00'), (0x7f, '\x7f'), (0x80, '\x81\x00'), - (0x1234, '\x80\xa4\x34')): - self.assertEqual(length, - msgutil._payload_length(_create_request(bytes))) - - def test_receive_bytes(self): - request = _create_request('abcdefg') - self.assertEqual('abc', msgutil._receive_bytes(request, 3)) - self.assertEqual('defg', msgutil._receive_bytes(request, 4)) - - def test_read_until(self): - request = _create_request('abcXdefgX') - self.assertEqual('abc', msgutil._read_until(request, 'X')) - self.assertEqual('defg', msgutil._read_until(request, 'X')) - - -class MessageReceiverTest(unittest.TestCase): - def test_queue(self): - request = _create_blocking_request() - receiver = msgutil.MessageReceiver(request) - - self.assertEqual(None, receiver.receive_nowait()) - - request.connection.put_bytes('\x00Hello!\xff') - self.assertEqual('Hello!', receiver.receive()) - - def test_onmessage(self): - onmessage_queue = Queue.Queue() - def onmessage_handler(message): - onmessage_queue.put(message) - - request = _create_blocking_request() - receiver = msgutil.MessageReceiver(request, onmessage_handler) - - request.connection.put_bytes('\x00Hello!\xff') - self.assertEqual('Hello!', onmessage_queue.get()) - - -class MessageSenderTest(unittest.TestCase): - def test_send(self): - request = _create_blocking_request() - sender = msgutil.MessageSender(request) - - sender.send('World') - self.assertEqual('\x00World\xff', request.connection.written_data()) - - def test_send_nowait(self): - # Use a queue to check the bytes written by MessageSender. - # request.connection.written_data() cannot be used here because - # MessageSender runs in a separate thread. - send_queue = Queue.Queue() - def write(bytes): - send_queue.put(bytes) - request = _create_blocking_request() - request.connection.write = write - - sender = msgutil.MessageSender(request) - - sender.send_nowait('Hello') - sender.send_nowait('World') - self.assertEqual('\x00Hello\xff', send_queue.get()) - self.assertEqual('\x00World\xff', send_queue.get()) - - -if __name__ == '__main__': - unittest.main() - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/test_util.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/test_util.py deleted file mode 100644 index 61f0db5..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/test_util.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009, 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. - - -"""Tests for util module.""" - - -import os -import sys -import unittest - -import config # This must be imported before mod_pywebsocket. -from mod_pywebsocket import util - -_TEST_DATA_DIR = os.path.join(os.path.split(__file__)[0], 'testdata') - -class UtilTest(unittest.TestCase): - def test_get_stack_trace(self): - self.assertEqual('None\n', util.get_stack_trace()) - try: - a = 1 / 0 # Intentionally raise exception. - except Exception: - trace = util.get_stack_trace() - self.failUnless(trace.startswith('Traceback')) - self.failUnless(trace.find('ZeroDivisionError') != -1) - - def test_prepend_message_to_exception(self): - exc = Exception('World') - self.assertEqual('World', str(exc)) - util.prepend_message_to_exception('Hello ', exc) - self.assertEqual('Hello World', str(exc)) - - def test_get_script_interp(self): - cygwin_path = 'c:\\cygwin\\bin' - cygwin_perl = os.path.join(cygwin_path, 'perl') - self.assertEqual(None, util.get_script_interp( - os.path.join(_TEST_DATA_DIR, 'README'))) - self.assertEqual(None, util.get_script_interp( - os.path.join(_TEST_DATA_DIR, 'README'), cygwin_path)) - self.assertEqual('/usr/bin/perl -wT', util.get_script_interp( - os.path.join(_TEST_DATA_DIR, 'hello.pl'))) - self.assertEqual(cygwin_perl + ' -wT', util.get_script_interp( - os.path.join(_TEST_DATA_DIR, 'hello.pl'), cygwin_path)) - - -if __name__ == '__main__': - unittest.main() - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/README b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/README deleted file mode 100644 index c001aa5..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/README +++ /dev/null @@ -1 +0,0 @@ -Test data directory diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/blank_wsh.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/blank_wsh.py deleted file mode 100644 index 7f87c6a..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/blank_wsh.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright 2009, 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. - - -# intentionally left blank diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/origin_check_wsh.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/origin_check_wsh.py deleted file mode 100644 index 2c139fa..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/origin_check_wsh.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2009, 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. - - -def web_socket_do_extra_handshake(request): - if request.ws_origin == 'http://example.com': - return - raise ValueError('Unacceptable origin: %r' % request.ws_origin) - - -def web_socket_transfer_data(request): - request.connection.write('origin_check_wsh.py is called for %s, %s' % - (request.ws_resource, request.ws_protocol)) - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/sub/exception_in_transfer_wsh.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/sub/exception_in_transfer_wsh.py deleted file mode 100644 index b982d02..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/sub/exception_in_transfer_wsh.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright 2009, 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. - - -"""Exception in web_socket_transfer_data(). -""" - - -def web_socket_do_extra_handshake(request): - pass - - -def web_socket_transfer_data(request): - raise Exception('Intentional Exception for %s, %s' % - (request.ws_resource, request.ws_protocol)) - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/sub/no_wsh_at_the_end.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/sub/no_wsh_at_the_end.py deleted file mode 100644 index 17e7be1..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/sub/no_wsh_at_the_end.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2009, 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. - - -"""Correct signatures, wrong file name. -""" - - -def web_socket_do_extra_handshake(request): - pass - - -def web_socket_transfer_data(request): - request.connection.write( - 'sub/no_wsh_at_the_end.py is called for %s, %s' % - (request.ws_resource, request.ws_protocol)) - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/sub/non_callable_wsh.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/sub/non_callable_wsh.py deleted file mode 100644 index 26352eb..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/sub/non_callable_wsh.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2009, 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. - - -"""Non-callable handlers. -""" - - -web_socket_do_extra_handshake = True -web_socket_transfer_data = 1 - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/sub/plain_wsh.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/sub/plain_wsh.py deleted file mode 100644 index db3ff69..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/sub/plain_wsh.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2009, 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. - - -def web_socket_do_extra_handshake(request): - pass - - -def web_socket_transfer_data(request): - request.connection.write('sub/plain_wsh.py is called for %s, %s' % - (request.ws_resource, request.ws_protocol)) - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/sub/wrong_handshake_sig_wsh.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/sub/wrong_handshake_sig_wsh.py deleted file mode 100644 index 6bf659b..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/sub/wrong_handshake_sig_wsh.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2009, 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. - - -"""Wrong web_socket_do_extra_handshake signature. -""" - - -def no_web_socket_do_extra_handshake(request): - pass - - -def web_socket_transfer_data(request): - request.connection.write( - 'sub/wrong_handshake_sig_wsh.py is called for %s, %s' % - (request.ws_resource, request.ws_protocol)) - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/sub/wrong_transfer_sig_wsh.py b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/sub/wrong_transfer_sig_wsh.py deleted file mode 100644 index e0e2e55..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/handlers/sub/wrong_transfer_sig_wsh.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2009, 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. - - -"""Wrong web_socket_transfer_data() signature. -""" - - -def web_socket_do_extra_handshake(request): - pass - - -def no_web_socket_transfer_data(request): - request.connection.write( - 'sub/wrong_transfer_sig_wsh.py is called for %s, %s' % - (request.ws_resource, request.ws_protocol)) - - -# vi:sts=4 sw=4 et diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/hello.pl b/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/hello.pl deleted file mode 100644 index 9dd01b4..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/testdata/hello.pl +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/perl -wT -print "Hello\n"; diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/queueengine.py b/WebKitTools/Scripts/webkitpy/tool/bot/queueengine.py index 2c45ab6..ac7a760 100644 --- a/WebKitTools/Scripts/webkitpy/tool/bot/queueengine.py +++ b/WebKitTools/Scripts/webkitpy/tool/bot/queueengine.py @@ -77,8 +77,8 @@ class QueueEngine: self._output_tee = OutputTee() log_date_format = "%Y-%m-%d %H:%M:%S" - sleep_duration_text = "5 mins" - seconds_to_sleep = 300 + sleep_duration_text = "2 mins" # This could be generated from seconds_to_sleep + seconds_to_sleep = 120 handled_error_code = 2 # Child processes exit with a special code to the parent queue process can detect the error was handled. @@ -142,8 +142,12 @@ class QueueEngine: self._output_tee.remove_log(self._work_log) self._work_log = None + def _now(self): + """Overriden by the unit tests to allow testing _sleep_message""" + return datetime.now() + def _sleep_message(self, message): - wake_time = datetime.now() + timedelta(seconds=self.seconds_to_sleep) + wake_time = self._now() + timedelta(seconds=self.seconds_to_sleep) return "%s Sleeping until %s (%s)." % (message, wake_time.strftime(self.log_date_format), self.sleep_duration_text) def _sleep(self, message): diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/queueengine_unittest.py b/WebKitTools/Scripts/webkitpy/tool/bot/queueengine_unittest.py index 626181d..ec91bdb 100644 --- a/WebKitTools/Scripts/webkitpy/tool/bot/queueengine_unittest.py +++ b/WebKitTools/Scripts/webkitpy/tool/bot/queueengine_unittest.py @@ -26,6 +26,7 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import datetime import os import shutil import tempfile @@ -160,6 +161,17 @@ class QueueEngineTest(unittest.TestCase): expected_callbacks.append('should_continue_work_queue') self.assertEquals(delegate._callbacks, expected_callbacks) + def test_now(self): + """Make sure there are no typos in the QueueEngine.now() method.""" + engine = QueueEngine("test", None, None) + self.assertTrue(isinstance(engine._now(), datetime.datetime)) + + def test_sleep_message(self): + engine = QueueEngine("test", None, None) + engine._now = lambda: datetime.datetime(2010, 1, 1) + expected_sleep_message = "MESSAGE Sleeping until 2010-01-01 00:02:00 (2 mins)." + self.assertEqual(engine._sleep_message("MESSAGE"), expected_sleep_message) + def setUp(self): self.temp_dir = tempfile.mkdtemp(suffix="work_queue_test_logs") diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/sheriff_unittest.py b/WebKitTools/Scripts/webkitpy/tool/bot/sheriff_unittest.py index dd048a1..c375ff9 100644 --- a/WebKitTools/Scripts/webkitpy/tool/bot/sheriff_unittest.py +++ b/WebKitTools/Scripts/webkitpy/tool/bot/sheriff_unittest.py @@ -33,7 +33,7 @@ from webkitpy.common.net.buildbot import Builder from webkitpy.common.system.outputcapture import OutputCapture from webkitpy.thirdparty.mock import Mock from webkitpy.tool.bot.sheriff import Sheriff -from webkitpy.tool.mocktool import MockTool, mock_builder +from webkitpy.tool.mocktool import MockTool class MockSheriffBot(object): diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/download.py b/WebKitTools/Scripts/webkitpy/tool/commands/download.py index c66b95c..a283da9 100644 --- a/WebKitTools/Scripts/webkitpy/tool/commands/download.py +++ b/WebKitTools/Scripts/webkitpy/tool/commands/download.py @@ -260,8 +260,9 @@ class AbstractRolloutPrepCommand(AbstractSequencedCommand): # of create-rollout for bug URLs. It should do better # parsing instead. log("Preparing rollout for bug %s." % commit_info.bug_id()) - return commit_info - log("Unable to parse bug number from diff.") + else: + log("Unable to parse bug number from diff.") + return commit_info def _prepare_state(self, options, args, tool): revision = args[0] diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/download_unittest.py b/WebKitTools/Scripts/webkitpy/tool/commands/download_unittest.py index 926037c..4dd9d7f 100644 --- a/WebKitTools/Scripts/webkitpy/tool/commands/download_unittest.py +++ b/WebKitTools/Scripts/webkitpy/tool/commands/download_unittest.py @@ -26,9 +26,32 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import unittest + +from webkitpy.common.system.outputcapture import OutputCapture from webkitpy.thirdparty.mock import Mock from webkitpy.tool.commands.commandtest import CommandsTest from webkitpy.tool.commands.download import * +from webkitpy.tool.mocktool import MockTool + + +class AbstractRolloutPrepCommandTest(unittest.TestCase): + def test_commit_info(self): + command = AbstractRolloutPrepCommand() + tool = MockTool() + command.bind_to_tool(tool) + output = OutputCapture() + + expected_stderr = "Preparing rollout for bug 42.\n" + commit_info = output.assert_outputs(self, command._commit_info, [1234], expected_stderr=expected_stderr) + self.assertTrue(commit_info) + + mock_commit_info = Mock() + mock_commit_info.bug_id = lambda: None + tool._checkout.commit_info_for_revision = lambda revision: mock_commit_info + expected_stderr = "Unable to parse bug number from diff.\n" + commit_info = output.assert_outputs(self, command._commit_info, [1234], expected_stderr=expected_stderr) + self.assertEqual(commit_info, mock_commit_info) class DownloadCommandsTest(CommandsTest): diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/earlywarningsystem_unittest.py b/WebKitTools/Scripts/webkitpy/tool/commands/earlywarningsystem_unittest.py index 4d23a4c..3d0ddd1 100644 --- a/WebKitTools/Scripts/webkitpy/tool/commands/earlywarningsystem_unittest.py +++ b/WebKitTools/Scripts/webkitpy/tool/commands/earlywarningsystem_unittest.py @@ -39,37 +39,37 @@ class EarlyWarningSytemTest(QueuesTest): ews._can_build = lambda: True ews.review_patch(Mock()) - def test_chromium_linux_ews(self): + def _default_expected_stderr(self, ews): + string_replacemnts = { + "name": ews.name, + "checkout_dir": os.getcwd(), # FIXME: Use of os.getcwd() is wrong, should be scm.checkout_root + } expected_stderr = { - "begin_work_queue": "CAUTION: chromium-ews will discard all local changes in \"%s\"\nRunning WebKit chromium-ews.\n" % os.getcwd(), + "begin_work_queue": "CAUTION: %(name)s will discard all local changes in \"%(checkout_dir)s\"\nRunning WebKit %(name)s.\n" % string_replacemnts, "handle_unexpected_error": "Mock error message\n", + "process_work_item": "MOCK: update_status: %(name)s Pass\n" % string_replacemnts, } - self.assert_queue_outputs(ChromiumLinuxEWS(), expected_stderr=expected_stderr) + return expected_stderr + + def _test_ews(self, ews): + self.assert_queue_outputs(ews, expected_stderr=self._default_expected_stderr(ews)) + + # FIXME: If all EWSes are going to output the same text, we + # could test them all in one method with a for loop over an array. + def test_chromium_linux_ews(self): + self._test_ews(ChromiumLinuxEWS()) def test_chromium_windows_ews(self): - expected_stderr = { - "begin_work_queue": "CAUTION: cr-win-ews will discard all local changes in \"%s\"\nRunning WebKit cr-win-ews.\n" % os.getcwd(), - "handle_unexpected_error": "Mock error message\n", - } - self.assert_queue_outputs(ChromiumWindowsEWS(), expected_stderr=expected_stderr) + self._test_ews(ChromiumWindowsEWS()) def test_qt_ews(self): - expected_stderr = { - "begin_work_queue": "CAUTION: qt-ews will discard all local changes in \"%s\"\nRunning WebKit qt-ews.\n" % os.getcwd(), - "handle_unexpected_error": "Mock error message\n", - } - self.assert_queue_outputs(QtEWS(), expected_stderr=expected_stderr) + self._test_ews(QtEWS()) def test_gtk_ews(self): - expected_stderr = { - "begin_work_queue": "CAUTION: gtk-ews will discard all local changes in \"%s\"\nRunning WebKit gtk-ews.\n" % os.getcwd(), - "handle_unexpected_error": "Mock error message\n", - } - self.assert_queue_outputs(GtkEWS(), expected_stderr=expected_stderr) + self._test_ews(GtkEWS()) def test_mac_ews(self): - expected_stderr = { - "begin_work_queue": "CAUTION: mac-ews will discard all local changes in \"%s\"\nRunning WebKit mac-ews.\n" % os.getcwd(), - "handle_unexpected_error": "Mock error message\n", - } - self.assert_queue_outputs(MacEWS(), expected_stderr=expected_stderr) + ews = MacEWS() + expected_stderr = self._default_expected_stderr(ews) + expected_stderr["process_work_item"] = "MOCK: update_status: mac-ews Error: mac-ews cannot process patches from non-committers :(\n" + self.assert_queue_outputs(ews, expected_stderr=expected_stderr) diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/queues.py b/WebKitTools/Scripts/webkitpy/tool/commands/queues.py index 775aa44..78ca729 100644 --- a/WebKitTools/Scripts/webkitpy/tool/commands/queues.py +++ b/WebKitTools/Scripts/webkitpy/tool/commands/queues.py @@ -46,7 +46,6 @@ from webkitpy.tool.multicommandtool import Command class AbstractQueue(Command, QueueEngineDelegate): watchers = [ - "webkit-bot-watchers@googlegroups.com", ] _pass_status = "Pass" @@ -168,16 +167,17 @@ class CommitQueue(AbstractPatchQueue, StepSequenceErrorHandler): # Not using BugzillaQueries.fetch_patches_from_commit_queue() so we can reject patches with invalid committers/reviewers. bug_ids = self.tool.bugs.queries.fetch_bug_ids_from_commit_queue() all_patches = sum([self.tool.bugs.fetch_bug(bug_id).commit_queued_patches(include_invalid=True) for bug_id in bug_ids], []) - valid_patches = self.committer_validator.patches_after_rejecting_invalid_commiters_and_reviewers(all_patches) - if not self._builders_are_green(): - return filter(lambda patch: patch.is_rollout(), valid_patches) - return valid_patches + return self.committer_validator.patches_after_rejecting_invalid_commiters_and_reviewers(all_patches) def next_work_item(self): patches = self._validate_patches_in_commit_queue() + builders_are_green = self._builders_are_green() + if not builders_are_green: + patches = filter(lambda patch: patch.is_rollout(), patches) # FIXME: We could sort the patches in a specific order here, was suggested by https://bugs.webkit.org/show_bug.cgi?id=33395 if not patches: - self._update_status("Empty queue") + queue_text = "queue" if builders_are_green else "rollout queue" + self._update_status("Empty %s" % queue_text) return None # Only bother logging if we have patches in the queue. self.log_progress([patch.id() for patch in patches]) @@ -211,7 +211,8 @@ class CommitQueue(AbstractPatchQueue, StepSequenceErrorHandler): if not patch.is_rollout(): if not self._builders_are_green(): return False - self._update_status("Landing patch", patch) + patch_text = "rollout patch" if patch.is_rollout() else "patch" + self._update_status("Landing %s" % patch_text, patch) return True def _land(self, patch, first_run=False): @@ -226,7 +227,6 @@ class CommitQueue(AbstractPatchQueue, StepSequenceErrorHandler): "land-attachment", "--force-clean", "--build", - "--test", "--non-interactive", # The master process is responsible for checking the status # of the builders (see above call to _builders_are_green). @@ -235,6 +235,9 @@ class CommitQueue(AbstractPatchQueue, StepSequenceErrorHandler): "--quiet", patch.id() ] + # We don't bother to run tests for rollouts as that makes them too slow. + if not patch.is_rollout(): + args.append("--test") if not first_run: # The first time through, we don't reject the patch from the # commit queue because we want to make sure we can build and diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/queues_unittest.py b/WebKitTools/Scripts/webkitpy/tool/commands/queues_unittest.py index 16eb053..0bd42fb 100644 --- a/WebKitTools/Scripts/webkitpy/tool/commands/queues_unittest.py +++ b/WebKitTools/Scripts/webkitpy/tool/commands/queues_unittest.py @@ -118,11 +118,13 @@ class CommitQueueTest(QueuesTest): def test_commit_queue(self): expected_stderr = { "begin_work_queue" : "CAUTION: commit-queue will discard all local changes in \"%s\"\nRunning WebKit commit-queue.\n" % MockSCM.fake_checkout_root, + "should_proceed_with_work_item": "MOCK: update_status: commit-queue Landing patch\n", # FIXME: The commit-queue warns about bad committers twice. This is due to the fact that we access Attachment.reviewer() twice and it logs each time. "next_work_item" : """Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com) Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com) 2 patches in commit-queue [197, 106] """, + "process_work_item" : "MOCK: update_status: commit-queue Pass\n", } self.assert_queue_outputs(CommitQueue(), expected_stderr=expected_stderr) @@ -131,11 +133,14 @@ Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.c tool.buildbot.light_tree_on_fire() expected_stderr = { "begin_work_queue" : "CAUTION: commit-queue will discard all local changes in \"%s\"\nRunning WebKit commit-queue.\n" % MockSCM.fake_checkout_root, + "should_proceed_with_work_item": "MOCK: update_status: commit-queue Builders [\"Builder2\"] are red. See http://build.webkit.org\n", # FIXME: The commit-queue warns about bad committers twice. This is due to the fact that we access Attachment.reviewer() twice and it logs each time. "next_work_item" : """Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com) Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com) +MOCK: update_status: commit-queue Builders ["Builder2"] are red. See http://build.webkit.org 1 patch in commit-queue [106] """, + "process_work_item" : "MOCK: update_status: commit-queue Builders [\"Builder2\"] are red. See http://build.webkit.org\n", } self.assert_queue_outputs(CommitQueue(), tool=tool, expected_stderr=expected_stderr) @@ -145,20 +150,33 @@ Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.c rollout_patch = MockPatch() expected_stderr = { "begin_work_queue": "CAUTION: commit-queue will discard all local changes in \"%s\"\nRunning WebKit commit-queue.\n" % MockSCM.fake_checkout_root, + "should_proceed_with_work_item": "MOCK: update_status: commit-queue Landing rollout patch\n", # FIXME: The commit-queue warns about bad committers twice. This is due to the fact that we access Attachment.reviewer() twice and it logs each time. "next_work_item": """Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com) Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com) +MOCK: update_status: commit-queue Builders ["Builder2"] are red. See http://build.webkit.org 1 patch in commit-queue [106] """, - "process_work_item": "MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'land-attachment', '--force-clean', '--build', '--test', '--non-interactive', '--ignore-builders', '--build-style=both', '--quiet', 76543]\n", + "process_work_item": "MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'land-attachment', '--force-clean', '--build', '--non-interactive', '--ignore-builders', '--build-style=both', '--quiet', 76543]\nMOCK: update_status: commit-queue Pass\n", } self.assert_queue_outputs(CommitQueue(), tool=tool, work_item=rollout_patch, expected_stderr=expected_stderr) + def test_can_build_and_test(self): + queue = CommitQueue() + tool = MockTool() + tool.executive = Mock() + queue.bind_to_tool(tool) + self.assertTrue(queue._can_build_and_test()) + expected_run_args = ["echo", "--status-host=example.com", "build-and-test", "--force-clean", "--build", "--test", "--non-interactive", "--no-update", "--build-style=both", "--quiet"] + tool.executive.run_and_throw_if_fail.assert_called_with(expected_run_args) + class StyleQueueTest(QueuesTest): def test_style_queue(self): expected_stderr = { "begin_work_queue" : "CAUTION: style-queue will discard all local changes in \"%s\"\nRunning WebKit style-queue.\n" % MockSCM.fake_checkout_root, + "should_proceed_with_work_item": "MOCK: update_status: style-queue Checking style\n", + "process_work_item" : "MOCK: update_status: style-queue Pass\n", "handle_unexpected_error" : "Mock error message\n", } self.assert_queue_outputs(StyleQueue(), expected_stderr=expected_stderr) diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot.py b/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot.py index eb80d8f..24c8517 100644 --- a/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot.py +++ b/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot.py @@ -57,17 +57,46 @@ class SheriffBot(AbstractQueue, StepSequenceErrorHandler): def work_item_log_path(self, new_failures): return os.path.join("%s-logs" % self.name, "%s.log" % new_failures.keys()[0]) - def next_work_item(self): - self._irc_bot.process_pending_messages() - self._update() + def _new_failures(self, revisions_causing_failures, old_failing_svn_revisions): + # We ignore failures that might have been caused by svn_revisions that + # we've already complained about. This is conservative in the sense + # that we might be ignoring some new failures, but our experience has + # been that skipping this check causes a lot of spam for builders that + # take a long time to cycle. + old_failing_builder_names = [] + for svn_revision in old_failing_svn_revisions: + old_failing_builder_names.extend( + [builder.name() for builder in revisions_causing_failures[svn_revision]]) + new_failures = {} - revisions_causing_failures = self.tool.buildbot.revisions_causing_failures() for svn_revision, builders in revisions_causing_failures.items(): - if self.tool.status_server.svn_revision(svn_revision): + if svn_revision in old_failing_svn_revisions: # FIXME: We should re-process the work item after some time delay. # https://bugs.webkit.org/show_bug.cgi?id=36581 continue - new_failures[svn_revision] = builders + new_builders = [builder for builder in builders + if builder.name() not in old_failing_builder_names] + if new_builders: + new_failures[svn_revision] = new_builders + + return new_failures + + def next_work_item(self): + self._irc_bot.process_pending_messages() + self._update() + + # We do one read from buildbot to ensure a consistent view. + revisions_causing_failures = self.tool.buildbot.revisions_causing_failures() + + # Similarly, we read once from our the status_server. + old_failing_svn_revisions = [] + for svn_revision in revisions_causing_failures.keys(): + if self.tool.status_server.svn_revision(svn_revision): + old_failing_svn_revisions.append(svn_revision) + + new_failures = self._new_failures(revisions_causing_failures, + old_failing_svn_revisions) + self._sheriff.provoke_flaky_builders(revisions_causing_failures) return new_failures diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot_unittest.py b/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot_unittest.py index f121eda..4b4b8b6 100644 --- a/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot_unittest.py +++ b/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot_unittest.py @@ -30,18 +30,44 @@ import os from webkitpy.tool.commands.queuestest import QueuesTest from webkitpy.tool.commands.sheriffbot import SheriffBot -from webkitpy.tool.mocktool import mock_builder +from webkitpy.tool.mocktool import MockBuilder class SheriffBotTest(QueuesTest): + builder1 = MockBuilder("Builder1") + builder2 = MockBuilder("Builder2") + def test_sheriff_bot(self): mock_work_item = { - 29837: [mock_builder], + 29837: [self.builder1], } expected_stderr = { "begin_work_queue": "CAUTION: sheriff-bot will discard all local changes in \"%s\"\nRunning WebKit sheriff-bot.\n" % os.getcwd(), "next_work_item": "", - "process_work_item": "MOCK: irc.post: abarth, darin, eseidel: http://trac.webkit.org/changeset/29837 might have broken Mock builder name (Tests)\nMOCK bug comment: bug_id=42, cc=['webkit-bot-watchers@googlegroups.com', 'abarth@webkit.org', 'eric@webkit.org']\n--- Begin comment ---\\http://trac.webkit.org/changeset/29837 might have broken Mock builder name (Tests)\n--- End comment ---\n\n", + "process_work_item": "MOCK: irc.post: abarth, darin, eseidel: http://trac.webkit.org/changeset/29837 might have broken Builder1\nMOCK bug comment: bug_id=42, cc=['abarth@webkit.org', 'eric@webkit.org']\n--- Begin comment ---\\http://trac.webkit.org/changeset/29837 might have broken Builder1\n--- End comment ---\n\n", "handle_unexpected_error": "Mock error message\n" } self.assert_queue_outputs(SheriffBot(), work_item=mock_work_item, expected_stderr=expected_stderr) + + revisions_causing_failures = { + 1234: [builder1], + 1235: [builder1, builder2], + } + + def test_new_failures(self): + old_failing_svn_revisions = [] + self.assertEquals(SheriffBot()._new_failures(self.revisions_causing_failures, + old_failing_svn_revisions), + self.revisions_causing_failures) + + def test_new_failures_with_old_revisions(self): + old_failing_svn_revisions = [1234] + self.assertEquals(SheriffBot()._new_failures(self.revisions_causing_failures, + old_failing_svn_revisions), + {1235: [builder2]}) + + def test_new_failures_with_old_revisions(self): + old_failing_svn_revisions = [1235] + self.assertEquals(SheriffBot()._new_failures(self.revisions_causing_failures, + old_failing_svn_revisions), + {}) diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/upload.py b/WebKitTools/Scripts/webkitpy/tool/commands/upload.py index 99d45a6..4797ef6 100644 --- a/WebKitTools/Scripts/webkitpy/tool/commands/upload.py +++ b/WebKitTools/Scripts/webkitpy/tool/commands/upload.py @@ -46,14 +46,23 @@ from webkitpy.tool.comments import bug_comment_from_svn_revision from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand from webkitpy.common.system.deprecated_logging import error, log + class CommitMessageForCurrentDiff(AbstractDeclarativeCommand): name = "commit-message" help_text = "Print a commit message suitable for the uncommitted changes" + def __init__(self): + options = [ + steps.Options.squash, + steps.Options.git_commit, + ] + AbstractDeclarativeCommand.__init__(self, options=options) + def execute(self, options, args, tool): # This command is a useful test to make sure commit_message_for_this_commit # always returns the right value regardless of the current working directory. - print "%s" % tool.checkout().commit_message_for_this_commit().message() + print "%s" % tool.checkout().commit_message_for_this_commit(options.git_commit, options.squash).message() + class CleanPendingCommit(AbstractDeclarativeCommand): name = "clean-pending-commit" diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/upload_unittest.py b/WebKitTools/Scripts/webkitpy/tool/commands/upload_unittest.py index eec3751..8f6483a 100644 --- a/WebKitTools/Scripts/webkitpy/tool/commands/upload_unittest.py +++ b/WebKitTools/Scripts/webkitpy/tool/commands/upload_unittest.py @@ -34,10 +34,7 @@ from webkitpy.tool.mocktool import MockTool class UploadCommandsTest(CommandsTest): def test_commit_message_for_current_diff(self): tool = MockTool() - mock_commit_message_for_this_commit = Mock() - mock_commit_message_for_this_commit.message = lambda: "Mock message" - tool._checkout.commit_message_for_this_commit = lambda: mock_commit_message_for_this_commit - expected_stdout = "Mock message\n" + expected_stdout = "This is a fake commit message that is at least 50 characters.\n" self.assert_execute_outputs(CommitMessageForCurrentDiff(), [], expected_stdout=expected_stdout, tool=tool) def test_clean_pending_commit(self): diff --git a/WebKitTools/Scripts/webkitpy/tool/mocktool.py b/WebKitTools/Scripts/webkitpy/tool/mocktool.py index 128362a..47fff1b 100644 --- a/WebKitTools/Scripts/webkitpy/tool/mocktool.py +++ b/WebKitTools/Scripts/webkitpy/tool/mocktool.py @@ -184,15 +184,6 @@ _bug4 = { } -class MockBuilder(object): - - def name(self): - return "Mock builder name (Tests)" - - -mock_builder = MockBuilder() - - class MockBugzillaQueries(Mock): def __init__(self, bugzilla): @@ -319,6 +310,9 @@ class MockBuilder(object): def __init__(self, name): self._name = name + def name(self): + return self._name + def force_build(self, username, comments): log("MOCK: force_build: name=%s, username=%s, comments=%s" % ( self._name, username, comments)) @@ -369,7 +363,7 @@ class MockBuildBot(object): def revisions_causing_failures(self): return { - "29837": [mock_builder] + "29837": [self.builder_with_name("Builder1")], } @@ -484,6 +478,7 @@ class MockStatusServer(object): return None def update_status(self, queue_name, status, patch=None, results_file=None): + log("MOCK: update_status: %s %s" % (queue_name, status)) return 187 def update_svn_revision(self, svn_revision, broken_bot): @@ -491,7 +486,12 @@ class MockStatusServer(object): class MockExecute(Mock): + def __init__(self, should_log): + self._should_log = should_log + def run_and_throw_if_fail(self, args, quiet=False): + if self._should_log: + log("MOCK run_and_throw_if_fail: %s" % args) return "MOCK output of child process" def run_command(self, @@ -502,6 +502,8 @@ class MockExecute(Mock): return_exit_code=False, return_stderr=True, decode_output=False): + if self._should_log: + log("MOCK run_command: %s" % args) return "MOCK output of child process" @@ -511,9 +513,7 @@ class MockTool(): self.wakeup_event = threading.Event() self.bugs = MockBugzilla() self.buildbot = MockBuildBot() - self.executive = MockExecute() - if log_executive: - self.executive.run_and_throw_if_fail = lambda args: log("MOCK run_and_throw_if_fail: %s" % args) + self.executive = MockExecute(should_log=log_executive) self._irc = None self.user = MockUser() self._scm = MockSCM() diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/checkstyle.py b/WebKitTools/Scripts/webkitpy/tool/steps/checkstyle.py index 7b2be99..93e6215 100644 --- a/WebKitTools/Scripts/webkitpy/tool/steps/checkstyle.py +++ b/WebKitTools/Scripts/webkitpy/tool/steps/checkstyle.py @@ -48,16 +48,17 @@ class CheckStyle(AbstractStep): if not self._options.check_style: return os.chdir(self._tool.scm().checkout_root) - try: - args = [] - if self._options.git_commit: - args.append("--git-commit") - args.append(self._options.git_commit) - if self._tool.scm().should_squash(self._options.squash): - args.append("--squash") - else: - args.append("--no-squash") + args = [] + if self._options.git_commit: + args.append("--git-commit") + args.append(self._options.git_commit) + if self._tool.scm().should_squash(self._options.squash): + args.append("--squash") + else: + args.append("--no-squash") + + try: self._run_script("check-webkit-style", args) except ScriptError, e: if self._options.non_interactive: diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/run_all.py b/WebKitTools/Scripts/webkitpy/tool/steps/checkstyle_unittest.py index 3885618..a23ea1b 100644 --- a/WebKitTools/Scripts/webkitpy/thirdparty/pywebsocket/test/run_all.py +++ b/WebKitTools/Scripts/webkitpy/tool/steps/checkstyle_unittest.py @@ -1,19 +1,16 @@ -#!/usr/bin/env python -# -# Copyright 2009, Google Inc. -# All rights reserved. +# Copyright (C) 2009 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 +# * 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 +# * 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 +# * 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. # @@ -29,36 +26,21 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Run all tests in the same directory. -""" - - -import os -import re import unittest +from webkitpy.common.system.executive import ScriptError +from webkitpy.thirdparty.mock import Mock +from webkitpy.tool.mocktool import MockTool +from webkitpy.tool.steps.checkstyle import CheckStyle -_TEST_MODULE_PATTERN = re.compile(r'^(test_.+)\.py$') - - -def _list_test_modules(directory): - module_names = [] - for filename in os.listdir(directory): - match = _TEST_MODULE_PATTERN.search(filename) - if match: - module_names.append(match.group(1)) - return module_names - - -def _suite(): - loader = unittest.TestLoader() - return loader.loadTestsFromNames( - _list_test_modules(os.path.join(os.path.split(__file__)[0], '.'))) - - -if __name__ == '__main__': - unittest.main(defaultTest='_suite') +class CheckStyleTest(unittest.TestCase): + def test_should_squash_error(self): + """should_squash can throw an error. That error should not be eaten by CheckStyle.""" + def should_squash(squash): + raise ScriptError(message="Dummy error") -# vi:sts=4 sw=4 et + tool = MockTool() + tool._scm.should_squash = should_squash + step = CheckStyle(tool, Mock()) + self.assertRaises(ScriptError, step.run, []) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/postcodereview.py b/WebKitTools/Scripts/webkitpy/tool/steps/postcodereview.py index 198cfce..8397519 100644 --- a/WebKitTools/Scripts/webkitpy/tool/steps/postcodereview.py +++ b/WebKitTools/Scripts/webkitpy/tool/steps/postcodereview.py @@ -42,33 +42,32 @@ class PostCodeReview(AbstractStep): def run(self, state): if not self._options.fancy_review: return - # FIXME: This will always be None because we don't retrieve the issue - # number from the ChangeLog yet. - codereview_issue = state.get("codereview_issue") + + bug_id = state.get("bug_id") + if not bug_id: + raise ScriptError(message="Cannot upload a fancy review without a bug ID.") + message = self._options.description if not message: # If we have an issue number, then the message becomes the label # of the new patch. Otherwise, it becomes the title of the whole # issue. - if codereview_issue: - message = "Updated patch" - elif state.get("bug_title"): + if state.get("bug_title"): # This is the common case for the the first "upload" command. message = state.get("bug_title") - elif state.get("bug_id"): + elif bug_id: # This is the common case for the "post" command and - # subsequent runs of the "upload" command. In this case, - # I'd rather add the new patch to the existing issue, but - # that's not implemented yet. - message = "Code review for %s" % self._tool.bugs.bug_url_for_bug_id(state["bug_id"]) + # subsequent runs of the "upload" command. + message = "Code review for %s" % self._tool.bugs.bug_url_for_bug_id(bug_id) else: # Unreachable with our current commands, but we might hit # this case if we support bug-less code reviews. message = "Code review" + + # Use the bug ID as the rietveld issue number. This means rietveld code reviews + # when there are multiple different patches on a bug will be a bit wonky, but + # webkit-patch assumes one-patch-per-bug. created_issue = self._tool.codereview.post(diff=self.cached_lookup(state, "diff"), message=message, - codereview_issue=codereview_issue, + codereview_issue=bug_id, cc=self._options.cc) - if created_issue: - # FIXME: Record the issue number in the ChangeLog. - state["codereview_issue"] = created_issue diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/postdiff.py b/WebKitTools/Scripts/webkitpy/tool/steps/postdiff.py index a542dba..79739cd 100644 --- a/WebKitTools/Scripts/webkitpy/tool/steps/postdiff.py +++ b/WebKitTools/Scripts/webkitpy/tool/steps/postdiff.py @@ -44,11 +44,6 @@ class PostDiff(AbstractStep): diff = self.cached_lookup(state, "diff") description = self._options.description or "Patch" comment_text = None - codereview_issue = state.get("codereview_issue") - # Include codereview issue number in patch name. This is a bit of a hack, - # but it makes doing the rietveld integration a lot easier. - if codereview_issue: - description += "-%s" % state["codereview_issue"] self._tool.bugs.add_patch_to_bug(state["bug_id"], diff, description, comment_text=comment_text, mark_for_review=self._options.review, mark_for_commit_queue=self._options.request_commit) if self._options.open_bug: self._tool.user.open_url(self._tool.bugs.bug_url_for_bug_id(state["bug_id"])) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/runtests.py b/WebKitTools/Scripts/webkitpy/tool/steps/runtests.py index b1c2d3b..0734d8f 100644 --- a/WebKitTools/Scripts/webkitpy/tool/steps/runtests.py +++ b/WebKitTools/Scripts/webkitpy/tool/steps/runtests.py @@ -57,6 +57,11 @@ class RunTests(AbstractStep): if self._options.non_interactive: args.append("--no-launch-safari") args.append("--exit-after-n-failures=1") + # FIXME: Hack to work around https://bugs.webkit.org/show_bug.cgi?id=38912 + # when running the commit-queue on a mac leopard machine. + if self.port().name() == "Mac" and self.port().is_leopard(): + args.extend(["--ignore-tests", "compositing/iframes"]) + if self._options.quiet: args.append("--quiet") self._tool.executive.run_and_throw_if_fail(args) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/steps_unittest.py b/WebKitTools/Scripts/webkitpy/tool/steps/steps_unittest.py index 5abfc6d..eee183b 100644 --- a/WebKitTools/Scripts/webkitpy/tool/steps/steps_unittest.py +++ b/WebKitTools/Scripts/webkitpy/tool/steps/steps_unittest.py @@ -29,9 +29,11 @@ import unittest from webkitpy.common.system.outputcapture import OutputCapture +from webkitpy.common.config.ports import WebKitPort from webkitpy.thirdparty.mock import Mock from webkitpy.tool.mocktool import MockTool from webkitpy.tool.steps.update import Update +from webkitpy.tool.steps.runtests import RunTests from webkitpy.tool.steps.promptforbugortitle import PromptForBugOrTitle @@ -55,3 +57,27 @@ class StepsTest(unittest.TestCase): tool = MockTool() tool.user.prompt = lambda message: 42 self._run_step(PromptForBugOrTitle, tool=tool) + + def test_runtests_leopard_commit_queue_hack(self): + expected_stderr = "Running Python unit tests\nRunning Perl unit tests\nRunning JavaScriptCore tests\nRunning run-webkit-tests\n" + OutputCapture().assert_outputs(self, self._run_step, [RunTests], expected_stderr=expected_stderr) + + def test_runtests_leopard_commit_queue_hack(self): + mock_options = Mock() + mock_options.non_interactive = True + step = RunTests(MockTool(log_executive=True), mock_options) + # FIXME: We shouldn't use a real port-object here, but there is too much to mock at the moment. + mock_port = WebKitPort() + mock_port.name = lambda: "Mac" + mock_port.is_leopard = lambda: True + step.port = lambda: mock_port + expected_stderr = """Running Python unit tests +MOCK run_and_throw_if_fail: ['WebKitTools/Scripts/test-webkitpy'] +Running Perl unit tests +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/iframes', '--quiet'] +""" + OutputCapture().assert_outputs(self, step.run, [{}], expected_stderr=expected_stderr) |