Just like finding a success line in the task log proves that it was successful no matter what happened before, a 'badpatch' line conclusively determines the task status.
Signed-off-by: Francois Gouget fgouget@codeweavers.com ---
This is the same patch as yesterday but integrated into this series since they conflict if applied out of order.
testbot/bin/WineRunWineTest.pl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/testbot/bin/WineRunWineTest.pl b/testbot/bin/WineRunWineTest.pl index 0aba7ecfc..53a2a0929 100755 --- a/testbot/bin/WineRunWineTest.pl +++ b/testbot/bin/WineRunWineTest.pl @@ -356,8 +356,8 @@ if (!$Pid)
# -# From that point on we want to at least try to grab the task -# log before giving up +# From that point on we want to at least try to grab the task log +# before giving up #
my ($NewStatus, $ErrMessage, $TAError, $TaskTimedOut); @@ -391,7 +391,9 @@ if ($TA->GetFile("Task.log", "$TaskDir/log")) } elsif ($Result eq "badpatch") { + # This too is conclusive enough to ignore other errors. $NewStatus = "badpatch"; + $TAError = $ErrMessage = undef; } elsif ($Result =~ s/^nolog://) {
Moving it to LogUtils.pm makes it possible to reuse it in future scripts.
Signed-off-by: Francois Gouget fgouget@codeweavers.com --- testbot/bin/WineRunTask.pl | 276 ++----------------------- testbot/lib/WineTestBot/LogUtils.pm | 310 +++++++++++++++++++++++++++- 2 files changed, 322 insertions(+), 264 deletions(-)
diff --git a/testbot/bin/WineRunTask.pl b/testbot/bin/WineRunTask.pl index 9faae28c3..821ca55ea 100755 --- a/testbot/bin/WineRunTask.pl +++ b/testbot/bin/WineRunTask.pl @@ -44,6 +44,7 @@ use WineTestBot::Config; use WineTestBot::Jobs; use WineTestBot::VMs; use WineTestBot::Log; +use WineTestBot::LogUtils; use WineTestBot::Engine::Notify;
@@ -527,275 +528,24 @@ Debug(Elapsed($Start), " Retrieving the report file to '$RptFileName'\n"); if ($TA->GetFile($RptFileName, "$TaskDir/$RptFileName")) { chmod 0664, "$TaskDir/$RptFileName"; - if (open(my $LogFile, "<", "$TaskDir/$RptFileName")) - { - # There is more than one test unit when running the full test suite so keep - # track of the current one. Note that for the TestBot we don't count or - # complain about misplaced skips. - my ($CurrentDll, $CurrentUnit) = ("", ""); - my $UnitSize = 0; - my ($LineFailures, $LineTodos, $LineSkips) = (0, 0, 0); - my ($SummaryFailures, $SummaryTodos, $SummarySkips) = (0, 0, 0); - my ($CurrentIsBroken, %CurrentPids, $CurrentRc, $LogFailures); - - sub CheckUnit($$) - { - my ($Unit, $Type) = @_; - if ($Unit eq $CurrentUnit or $CurrentUnit eq "") - { - $IsWineTest = 1; - } - # To avoid issuing many duplicate errors, - # only report the first misplaced message. - elsif ($IsWineTest and !$CurrentIsBroken) - { - LogTaskError("$CurrentDll:$CurrentUnit contains a misplaced $Type message for $Unit\n"); - $LogFailures++; - $CurrentIsBroken = 1; - } - } - - sub CheckSummaryCounter($$$) - { - my ($Count, $SCount, $Type) = @_; - - if ($Count != 0 and $SCount == 0) - { - LogTaskError("$CurrentDll:$CurrentUnit has unaccounted for $Type messages\n"); - $LogFailures++; - } - elsif ($Count == 0 and $SCount != 0) - { - LogTaskError("$CurrentDll:$CurrentUnit is missing some $Type messages\n"); - $LogFailures++; - } - } - - sub CloseTestUnit($) - { - my ($Last) = @_; - - # Verify the summary lines - if (!$CurrentIsBroken) - { - CheckSummaryCounter($LineFailures, $SummaryFailures, "failure"); - CheckSummaryCounter($LineTodos, $SummaryTodos, "todo"); - CheckSummaryCounter($LineSkips, $SummarySkips, "skip"); - } - - # Note that the summary lines may count some failures twice - # so only use them as a fallback. - $LineFailures ||= $SummaryFailures; - - if ($UnitSize > $MaxUnitSize) - { - LogTaskError("$CurrentDll:$CurrentUnit prints too much data ($UnitSize bytes)\n"); - $LogFailures++; - } - if (!$CurrentIsBroken and defined $CurrentRc) - { - # Check the exit code, particularly against failures reported - # after the 'done' line (e.g. by subprocesses). - if ($LineFailures != 0 and $CurrentRc == 0) - { - LogTaskError("$CurrentDll:$CurrentUnit returned success despite having failures\n"); - $LogFailures++; - } - elsif (!$IsWineTest and $CurrentRc != 0) - { - LogTaskError("The test returned a non-zero exit code\n"); - $LogFailures++; - } - elsif ($IsWineTest and $LineFailures == 0 and $CurrentRc != 0) - { - LogTaskError("$CurrentDll:$CurrentUnit returned a non-zero exit code despite reporting no failures\n"); - $LogFailures++; - } - } - # For executables TestLauncher's done line may not be recognizable. - elsif ($IsWineTest and !defined $CurrentRc) - { - if (!$Last) - { - LogTaskError("$CurrentDll:$CurrentUnit has no done line (or it is garbled)\n"); - } - elsif ($Last and !$TaskTimedOut) - { - LogTaskError("The report seems to have been truncated\n"); - } - $LogFailures++; - } - - $LogFailures += $LineFailures; - - $CurrentDll = $CurrentUnit = ""; - $UnitSize = 0; - $LineFailures = $LineTodos = $LineSkips = 0; - $SummaryFailures = $SummaryTodos = $SummarySkips = 0; - $CurrentIsBroken = 0; - $CurrentRc = undef; - %CurrentPids = (); - } - - foreach my $Line (<$LogFile>) - { - $UnitSize += length($Line); - if ($Line =~ m%^([_.a-z0-9-]+):([_a-z0-9]*) (start|skipped) (?:-|[/_.a-z0-9]+) (?:-|[.0-9a-f]+)\r?$%) - { - my ($Dll, $Unit, $Type) = ($1, $2, $3); - - # Close the previous test unit - CloseTestUnit(0) if ($CurrentDll ne ""); - - ($CurrentDll, $CurrentUnit) = ($Dll, $Unit); - - # Recognize skipped messages in case we need to skip tests in the VMs - $CurrentRc = 0 if ($Type eq "skipped"); - } - elsif ($Line =~ /^([_a-z0-9]+).c:\d+: Test (?:failed|succeeded inside todo block): / or - ($CurrentUnit ne "" and - $Line =~ /($CurrentUnit).c:\d+: Test (?:failed|succeeded inside todo block): /)) - { - CheckUnit($1, "failure"); - $LineFailures++; - } - elsif ($Line =~ /^([_a-z0-9]+).c:\d+: Test marked todo: / or - ($CurrentUnit ne "" and - $Line =~ /($CurrentUnit).c:\d+: Test marked todo: /)) - { - CheckUnit($1, "todo"); - $LineTodos++; - } - # TestLauncher's skip message is quite broken - elsif ($Line =~ /^([_a-z0-9]+)(?:.c)?:\d+:? Tests? skipped: / or - ($CurrentUnit ne "" and - $Line =~ /($CurrentUnit)(?:.c)?:\d+:? Tests? skipped: /)) - { - my $Unit = $1; - # Don't complain and don't count misplaced skips. Only complain if they - # are misreported (see CloseTestUnit). Also TestLauncher uses the wrong - # name in its skip message when skipping tests. - if ($Unit eq $CurrentUnit or $CurrentUnit eq "" or $Unit eq $CurrentDll) - { - $LineSkips++; - } - } - elsif ($Line =~ /^Fatal: test '([_a-z0-9]+)' does not exist/) - { - # This also replaces a test summary line. - $CurrentPids{0} = 1; - $SummaryFailures++; - $IsWineTest = 1; - - $LineFailures++; - } - elsif ($Line =~ /^(?:([0-9a-f]+):)?([_.a-z0-9]+): unhandled exception [0-9a-fA-F]{8} at / or - ($CurrentUnit ne "" and - $Line =~ /(?:([0-9a-f]+):)?($CurrentUnit): unhandled exception [0-9a-fA-F]{8} at /)) - { - my ($Pid, $Unit) = ($1, $2); - - if ($Unit eq $CurrentUnit) - { - # This also replaces a test summary line. - $CurrentPids{$Pid || 0} = 1; - $SummaryFailures++; - } - CheckUnit($Unit, "unhandled exception"); - $LineFailures++; - } - elsif ($Line =~ /^(?:([0-9a-f]+):)?([_a-z0-9]+): \d+ tests? executed ((\d+) marked as todo, (\d+) failures?), (\d+) skipped./ or - ($CurrentUnit ne "" and - $Line =~ /(?:([0-9a-f]+):)?($CurrentUnit): \d+ tests? executed ((\d+) marked as todo, (\d+) failures?), (\d+) skipped./)) - { - my ($Pid, $Unit, $Todos, $Failures, $Skips) = ($1, $2, $3, $4, $5); - - # Dlls that have only one test unit will run it even if there is - # no argument. Also TestLauncher uses the wrong name in its test - # summary line when skipping tests. - if ($Unit eq $CurrentUnit or $CurrentUnit eq "" or $Unit eq $CurrentDll) - { - # There may be more than one summary line due to child processes - $CurrentPids{$Pid || 0} = 1; - $SummaryFailures += $Failures; - $SummaryTodos += $Todos; - $SummarySkips += $Skips; - $IsWineTest = 1; - } - else - { - CheckUnit($Unit, "test summary") if ($Todos or $Failures); - } - } - elsif ($Line =~ /^([_.a-z0-9-]+):([_a-z0-9]*)(?::([0-9a-f]+))? done ((-?\d+))(?:\r?$| in)/ or - ($CurrentDll ne "" and - $Line =~ /(\Q$CurrentDll\E):([_a-z0-9]*)(?::([0-9a-f]+))? done ((-?\d+))(?:\r?$| in)/)) - { - my ($Dll, $Unit, $Pid, $Rc) = ($1, $2, $3, $4); - - if ($IsWineTest and ($Dll ne $CurrentDll or $Unit ne $CurrentUnit)) - { - # First close the current test unit taking into account - # it may have been polluted by the new one. - $LogFailures++; - $CurrentIsBroken = 1; - CloseTestUnit(0); - - # Then switch to the new one, warning it's missing a start line, - # and that its results may be inconsistent. - ($CurrentDll, $CurrentUnit) = ($Dll, $Unit); - LogTaskError("$Dll:$Unit had no start line (or it is garbled)\n"); - $CurrentIsBroken = 1; - } - - if ($Rc == 258) - { - # The done line will already be shown as a timeout (see JobDetails) - # so record the failure but don't add an error message. - $LogFailures++; - $CurrentIsBroken = 1; - $TimedOut = ($Step->Type ne "suite"); - } - elsif ((!$Pid and !%CurrentPids) or - ($Pid and !$CurrentPids{$Pid} and !$CurrentPids{0})) - { - # The main summary line is missing - if ($Rc & 0xc0000000) - { - LogTaskError(sprintf("%s:%s crashed (%08x)\n", $Dll, $Unit, $Rc & 0xffffffff)); - $LogFailures++; - $CurrentIsBroken = 1; - } - elsif ($IsWineTest and !$CurrentIsBroken) - { - LogTaskError("$Dll:$Unit has no test summary line (early exit of the main process?)\n"); - $LogFailures++; - } - } - elsif ($Rc & 0xc0000000) - { - # We know the crash happened in the main process which means we got - # an "unhandled exception" message. So there is no need to add an - # extra message or to increment the failure count. Still note that - # there may be inconsistencies (e.g. unreported todos or skips). - $CurrentIsBroken = 1; - } - $CurrentRc = $Rc; - } - } - $CurrentIsBroken = 1 if ($TaskTimedOut); - CloseTestUnit(1); - close($LogFile);
- # $LogFailures can legitimately be undefined in case of a timeout - $TaskFailures += $LogFailures || 0; - } - else + (my $LogFailures, my $LogErrors, $TimedOut) = ParseWineTestReport("$TaskDir/$RptFileName", $IsWineTest, $Step->Type eq "suite", $TaskTimedOut); + if (!defined $LogFailures and @$LogErrors == 1) { + # Could not open the file $NewStatus = 'boterror'; Error "Unable to open '$RptFileName' for reading: $!\n"; LogTaskError("Unable to open '$RptFileName' for reading: $!\n"); } + else + { + # $LogFailures can legitimately be undefined in case of a timeout + $TaskFailures += $LogFailures || 0; + foreach my $Error (@$LogErrors) + { + LogTaskError("$Error\n"); + } + } } elsif (!defined $TAError) { diff --git a/testbot/lib/WineTestBot/LogUtils.pm b/testbot/lib/WineTestBot/LogUtils.pm index 3dfd7c5e7..363f80827 100644 --- a/testbot/lib/WineTestBot/LogUtils.pm +++ b/testbot/lib/WineTestBot/LogUtils.pm @@ -27,7 +27,12 @@ WineTestBot::LogUtils - Provides functions to parse task logs
use Exporter 'import'; -our @EXPORT = qw(GetLogFileNames GetLogLabel GetLogLineCategory ParseTaskLog); +our @EXPORT = qw(GetLogFileNames GetLogLabel GetLogLineCategory + ParseTaskLog ParseWineTestReport); + +use File::Basename; + +use WineTestBot::Config; # For $MaxUnitSize
# @@ -75,6 +80,309 @@ sub ParseTaskLog($$) }
+# +# WineTest report parser +# + +sub _NewCurrentUnit($$) +{ + my ($Dll, $Unit) = @_; + + return { + # There is more than one test unit when running the full test suite so keep + # track of the current one. Note that for the TestBot we don't count or + # complain about misplaced skips. + Dll => $Dll, + Unit => $Unit, + UnitSize => 0, + LineFailures => 0, + LineTodos => 0, + LineSkips => 0, + SummaryFailures => 0, + SummaryTodos => 0, + SummarySkips => 0, + IsBroken => 0, + Rc => undef, + Pids => {}, + }; +} + +sub _AddError($$;$) +{ + my ($Parser, $Error, $Cur) = @_; + + $Error = "$Cur->{Dll}:$Cur->{Unit} $Error" if (defined $Cur); + push @{$Parser->{Errors}}, $Error; + $Parser->{Failures}++; +} + +sub _CheckUnit($$$$) +{ + my ($Parser, $Cur, $Unit, $Type) = @_; + + if ($Unit eq $Cur->{Unit} or $Cur->{Unit} eq "") + { + $Parser->{IsWineTest} = 1; + } + # To avoid issuing many duplicate errors, + # only report the first misplaced message. + elsif ($Parser->{IsWineTest} and !$Cur->{IsBroken}) + { + _AddError($Parser, "contains a misplaced $Type message for $Unit", $Cur); + $Cur->{IsBroken} = 1; + } +} + +sub _CheckSummaryCounter($$$$) +{ + my ($Parser, $Cur, $Field, $Type) = @_; + + if ($Cur->{"Line$Field"} != 0 and $Cur->{"Summary$Field"} == 0) + { + _AddError($Parser, "has unaccounted for $Type messages", $Cur); + } + elsif ($Cur->{"Line$Field"} == 0 and $Cur->{"Summary$Field"} != 0) + { + _AddError($Parser, "is missing some $Type messages", $Cur); + } +} + +sub _CloseTestUnit($$$) +{ + my ($Parser, $Cur, $Last) = @_; + + # Verify the summary lines + if (!$Cur->{IsBroken}) + { + _CheckSummaryCounter($Parser, $Cur, "Failures", "failure"); + _CheckSummaryCounter($Parser, $Cur, "Todos", "todo"); + _CheckSummaryCounter($Parser, $Cur, "Skips", "skip"); + } + + # Note that the summary lines may count some failures twice + # so only use them as a fallback. + $Cur->{LineFailures} ||= $Cur->{SummaryFailures}; + + if ($Cur->{UnitSize} > $MaxUnitSize) + { + _AddError($Parser, "prints too much data ($Cur->{UnitSize} bytes)", $Cur); + } + if (!$Cur->{IsBroken} and defined $Cur->{Rc}) + { + # Check the exit code, particularly against failures reported + # after the 'done' line (e.g. by subprocesses). + if ($Cur->{LineFailures} != 0 and $Cur->{Rc} == 0) + { + _AddError($Parser, "returned success despite having failures", $Cur); + } + elsif (!$Parser->{IsWineTest} and $Cur->{Rc} != 0) + { + _AddError($Parser, "The test returned a non-zero exit code"); + } + elsif ($Parser->{IsWineTest} and $Cur->{LineFailures} == 0 and $Cur->{Rc} != 0) + { + _AddError($Parser, "returned a non-zero exit code despite reporting no failures", $Cur); + } + } + # For executables TestLauncher's done line may not be recognizable. + elsif ($Parser->{IsWineTest} and !defined $Cur->{Rc}) + { + if (!$Last) + { + _AddError($Parser, "has no done line (or it is garbled)", $Cur); + } + elsif ($Last and !$Parser->{TaskTimedOut}) + { + _AddError($Parser, "The report seems to have been truncated"); + } + } + + $Parser->{Failures} += $Cur->{LineFailures}; +} + +=pod +=over 12 + +=item C<ParseWineTestReport()> + +Parses a Wine test report and returns the number of failures and extra errors, +a list of extra errors, and whether the test timed out. + +=back +=cut + +sub ParseWineTestReport($$$$) +{ + my ($FileName, $IsWineTest, $IsSuite, $TaskTimedOut) = @_; + + my $LogFile; + if (!open($LogFile, "<", $FileName)) + { + my $BaseName = basename($FileName); + return (undef, ["Unable to open '$BaseName' for reading: $!"], undef); + } + + my $Parser = { + IsWineTest => $IsWineTest, + IsSuite => $IsSuite, + TaskTimedOut => $TaskTimedOut, + + TimedOut => undef, + Failures => undef, + Errors => [], + }; + + my $Cur = _NewCurrentUnit("", ""); + foreach my $Line (<$LogFile>) + { + $Cur->{UnitSize} += length($Line); + if ($Line =~ m%^([_.a-z0-9-]+):([_a-z0-9]*) (start|skipped) (?:-|[/_.a-z0-9]+) (?:-|[.0-9a-f]+)\r?$%) + { + my ($Dll, $Unit, $Type) = ($1, $2, $3); + + # Close the previous test unit + _CloseTestUnit($Parser, $Cur, 0) if ($Cur->{Dll} ne ""); + $Cur = _NewCurrentUnit($Dll, $Unit); + + # Recognize skipped messages in case we need to skip tests in the VMs + $Cur->{Rc} = 0 if ($Type eq "skipped"); + } + elsif ($Line =~ /^([_a-z0-9]+).c:\d+: Test (?:failed|succeeded inside todo block): / or + ($Cur->{Unit} ne "" and + $Line =~ /($Cur->{Unit}).c:\d+: Test (?:failed|succeeded inside todo block): /)) + { + _CheckUnit($Parser, $Cur, $1, "failure"); + $Cur->{LineFailures}++; + } + elsif ($Line =~ /^([_a-z0-9]+).c:\d+: Test marked todo: / or + ($Cur->{Unit} ne "" and + $Line =~ /($Cur->{Unit}).c:\d+: Test marked todo: /)) + { + _CheckUnit($Parser, $Cur, $1, "todo"); + $Cur->{LineTodos}++; + } + # TestLauncher's skip message is quite broken + elsif ($Line =~ /^([_a-z0-9]+)(?:.c)?:\d+:? Tests? skipped: / or + ($Cur->{Unit} ne "" and + $Line =~ /($Cur->{Unit})(?:.c)?:\d+:? Tests? skipped: /)) + { + my $Unit = $1; + # Don't complain and don't count misplaced skips. Only complain if they + # are misreported (see _CloseTestUnit). Also TestLauncher uses the wrong + # name in its skip message when skipping tests. + if ($Unit eq $Cur->{Unit} or $Cur->{Unit} eq "" or $Unit eq $Cur->{Dll}) + { + $Cur->{LineSkips}++; + } + } + elsif ($Line =~ /^Fatal: test '([_a-z0-9]+)' does not exist/) + { + # This also replaces a test summary line. + $Cur->{Pids}->{0} = 1; + $Cur->{SummaryFailures}++; + $Parser->{IsWineTest} = 1; + + $Cur->{LineFailures}++; + } + elsif ($Line =~ /^(?:([0-9a-f]+):)?([_.a-z0-9]+): unhandled exception [0-9a-fA-F]{8} at / or + ($Cur->{Unit} ne "" and + $Line =~ /(?:([0-9a-f]+):)?($Cur->{Unit}): unhandled exception [0-9a-fA-F]{8} at /)) + { + my ($Pid, $Unit) = ($1, $2); + + if ($Unit eq $Cur->{Unit}) + { + # This also replaces a test summary line. + $Cur->{Pids}->{$Pid || 0} = 1; + $Cur->{SummaryFailures}++; + } + _CheckUnit($Parser, $Cur, $Unit, "unhandled exception"); + $Cur->{LineFailures}++; + } + elsif ($Line =~ /^(?:([0-9a-f]+):)?([_a-z0-9]+): \d+ tests? executed ((\d+) marked as todo, (\d+) failures?), (\d+) skipped./ or + ($Cur->{Unit} ne "" and + $Line =~ /(?:([0-9a-f]+):)?($Cur->{Unit}): \d+ tests? executed ((\d+) marked as todo, (\d+) failures?), (\d+) skipped./)) + { + my ($Pid, $Unit, $Todos, $Failures, $Skips) = ($1, $2, $3, $4, $5); + + # Dlls that have only one test unit will run it even if there is + # no argument. Also TestLauncher uses the wrong name in its test + # summary line when skipping tests. + if ($Unit eq $Cur->{Unit} or $Cur->{Unit} eq "" or $Unit eq $Cur->{Dll}) + { + # There may be more than one summary line due to child processes + $Cur->{Pids}->{$Pid || 0} = 1; + $Cur->{SummaryFailures} += $Failures; + $Cur->{SummaryTodos} += $Todos; + $Cur->{SummarySkips} += $Skips; + $Parser->{IsWineTest} = 1; + } + else + { + _CheckUnit($Parser, $Cur, $Unit, "test summary") if ($Todos or $Failures); + } + } + elsif ($Line =~ /^([_.a-z0-9-]+):([_a-z0-9]*)(?::([0-9a-f]+))? done ((-?\d+))(?:\r?$| in)/ or + ($Cur->{Dll} ne "" and + $Line =~ /(\Q$Cur->{Dll}\E):([_a-z0-9]*)(?::([0-9a-f]+))? done ((-?\d+))(?:\r?$| in)/)) + { + my ($Dll, $Unit, $Pid, $Rc) = ($1, $2, $3, $4); + + if ($Parser->{IsWineTest} and ($Dll ne $Cur->{Dll} or $Unit ne $Cur->{Unit})) + { + # First close the current test unit taking into account + # it may have been polluted by the new one. + $Cur->{IsBroken} = 1; + _CloseTestUnit($Parser, $Cur, 0); + + # Then switch to the new one, warning it's missing a start line, + # and that its results may be inconsistent. + ($Cur->{Dll}, $Cur->{Unit}) = ($Dll, $Unit); + _AddError($Parser, "had no start line (or it is garbled)", $Cur); + $Cur->{IsBroken} = 1; + } + + if ($Rc == 258) + { + # The done line will already be shown as a timeout (see JobDetails) + # so record the failure but don't add an error message. + $Parser->{Failures}++; + $Cur->{IsBroken} = 1; + $Parser->{TimedOut} = $Parser->{IsSuite}; + } + elsif ((!$Pid and !%{$Cur->{Pids}}) or + ($Pid and !$Cur->{Pids}->{$Pid} and !$Cur->{Pids}->{0})) + { + # The main summary line is missing + if ($Rc & 0xc0000000) + { + _AddError($Parser, sprintf("%s:%s crashed (%08x)", $Dll, $Unit, $Rc & 0xffffffff)); + $Cur->{IsBroken} = 1; + } + elsif ($Parser->{IsWineTest} and !$Cur->{IsBroken}) + { + _AddError($Parser, "$Dll:$Unit has no test summary line (early exit of the main process?)"); + } + } + elsif ($Rc & 0xc0000000) + { + # We know the crash happened in the main process which means we got + # an "unhandled exception" message. So there is no need to add an + # extra message or to increment the failure count. Still note that + # there may be inconsistencies (e.g. unreported todos or skips). + $Cur->{IsBroken} = 1; + } + $Cur->{Rc} = $Rc; + } + } + $Cur->{IsBroken} = 1 if ($Parser->{TaskTimedOut}); + _CloseTestUnit($Parser, $Cur, 1); + close($LogFile); + + return ($Parser->{Failures}, $Parser->{Errors}, $Parser->{TimedOut}); +} + + # # Log querying and formatting #
WineReconfig.pl now knows how to retrieve and cache the Gecko and Mono addons, and how to create new wineprefixes. These are then captured by the updated snapshot and are thus ready to use to run tests. CheckForWinetestUpdate.pl creates additional steps to rerun the full WineTest suite on the Wine VMs if the Wine rebuild was successful. Running WineTest is handled by WineTest.pl and WineRunWineTest.pl collects the corresponding reports in the latest directory in files named '<vm>_<build>.report' where build is one of win32, wow32 or wow64.
Signed-off-by: Francois Gouget fgouget@codeweavers.com --- testbot/bin/CheckForWinetestUpdate.pl | 44 +++++- testbot/bin/WineRunWineTest.pl | 218 ++++++++++++++++++++++---- testbot/bin/build/WineReconfig.pl | 189 ++++++++++++++++++++-- testbot/bin/build/WineTest.pl | 118 ++++++++++++-- testbot/lib/WineTestBot/LogUtils.pm | 8 +- testbot/lib/WineTestBot/Utils.pm | 19 ++- 6 files changed, 537 insertions(+), 59 deletions(-)
diff --git a/testbot/bin/CheckForWinetestUpdate.pl b/testbot/bin/CheckForWinetestUpdate.pl index 74c59955f..d3ef384c7 100755 --- a/testbot/bin/CheckForWinetestUpdate.pl +++ b/testbot/bin/CheckForWinetestUpdate.pl @@ -273,10 +273,10 @@ sub AddReconfigJob($)
# Add a step to the job my $Steps = $NewJob->Steps; - my $NewStep = $Steps->Add(); - $NewStep->Type("reconfig"); - $NewStep->FileType("none"); - $NewStep->InStaging(!1); + my $BuildStep = $Steps->Add(); + $BuildStep->Type("reconfig"); + $BuildStep->FileType("none"); + $BuildStep->InStaging(!1);
# And a task for each VM my $SortedKeys = $VMs->SortKeysBySortOrder($VMs->GetKeys()); @@ -284,14 +284,46 @@ sub AddReconfigJob($) { my $VM = $VMs->GetItem($VMKey); Debug(" $VMKey $VMType reconfig\n"); - my $Task = $NewStep->Tasks->Add(); + my $Task = $BuildStep->Tasks->Add(); $Task->VM($VM); $Task->Timeout($ReconfigTimeout); }
- # Save it all + # Save the build step so the others can reference it. my ($ErrKey, $ErrProperty, $ErrMessage) = $Jobs->Save(); if (defined $ErrMessage) + { + Error "Failed to save the build step: $ErrMessage\n"; + return 0; + } + + if ($VMType eq "wine") + { + # Add steps to run WineTest on Wine + foreach my $Build ("win32", "wow32", "wow64") + { + # Add a step to the job + my $NewStep = $Steps->Add(); + $NewStep->PreviousNo($BuildStep->No); + $NewStep->Type("suite"); + $NewStep->FileType("none"); + $NewStep->InStaging(!1); + + foreach my $VMKey (@$SortedKeys) + { + my $VM = $VMs->GetItem($VMKey); + Debug(" $VMKey $Build\n"); + my $Task = $NewStep->Tasks->Add(); + $Task->VM($VM); + $Task->CmdLineArg($Build); + $Task->Timeout($SuiteTimeout); + } + } + } + + # Save it all + ($ErrKey, $ErrProperty, $ErrMessage) = $Jobs->Save(); + if (defined $ErrMessage) { Error "Failed to save the Reconfig job: $ErrMessage\n"; return 0; diff --git a/testbot/bin/WineRunWineTest.pl b/testbot/bin/WineRunWineTest.pl index 53a2a0929..83cf5821b 100755 --- a/testbot/bin/WineRunWineTest.pl +++ b/testbot/bin/WineRunWineTest.pl @@ -1,7 +1,7 @@ #!/usr/bin/perl -Tw # -*- Mode: Perl; perl-indent-level: 2; indent-tabs-mode: nil -*- # -# Makes sure the Wine patches compile. +# Makes sure the Wine patches compile or run WineTest. # See the bin/build/WineTest.pl script. # # Copyright 2018 Francois Gouget @@ -46,6 +46,7 @@ use WineTestBot::PatchUtils; use WineTestBot::VMs; use WineTestBot::Log; use WineTestBot::LogUtils; +use WineTestBot::Utils; use WineTestBot::Engine::Notify;
@@ -67,6 +68,35 @@ sub Error(@) }
+# +# Task helpers +# + +sub TakeScreenshot($$) +{ + my ($VM, $FileName) = @_; + + my $Domain = $VM->GetDomain(); + my ($ErrMessage, $ImageSize, $ImageBytes) = $Domain->CaptureScreenImage(); + if (!defined $ErrMessage) + { + if (open(my $Screenshot, ">", $FileName)) + { + print $Screenshot $ImageBytes; + close($Screenshot); + } + else + { + Error "Could not open the screenshot file for writing: $!\n"; + } + } + elsif ($Domain->IsPoweredOn()) + { + Error "Could not capture a screenshot: $ErrMessage\n"; + } +} + + # # Setup and command line processing # @@ -190,15 +220,17 @@ sub LogTaskError($) } }
-sub WrapUpAndExit($;$$) +sub WrapUpAndExit($;$$$) { - my ($Status, $Retry, $Timeout) = @_; + my ($Status, $TestFailures, $Retry, $TimedOut) = @_; my $NewVMStatus = $Status eq 'queued' ? 'offline' : 'dirty'; my $VMResult = $Status eq "boterror" ? "boterror" : $Status eq "queued" ? "error" : - $Timeout ? "timeout" : ""; + $TimedOut ? "timeout" : ""; + + Debug(Elapsed($Start), " Taking a screenshot\n"); + TakeScreenshot($VM, "$TaskDir/screenshot.png");
- my $TestFailures; my $Tries = $Task->TestFailures || 0; if ($Retry) { @@ -253,6 +285,30 @@ sub WrapUpAndExit($;$$) $VM->Save(); }
+ if ($Step->Type eq 'suite' and $Status eq 'completed' and !$TimedOut) + { + my $BuildList = $Task->CmdLineArg; + $BuildList =~ s/ .*$//; + foreach my $Build (split /,/, $BuildList) + { + # Keep the old report if the new one is missing + my $RptFileName = "$Build.report"; + if (-f "$TaskDir/$RptFileName" and !-z "$TaskDir/$RptFileName") + { + # Update the reference VM suite results for WineSendLog.pl + my $LatestBaseName = join("", "$DataDir/latest/", $Task->VM->Name, + "_$Build"); + unlink("$LatestBaseName.log"); + link("$TaskDir/$RptFileName", "$LatestBaseName.log"); + unlink("$LatestBaseName.err"); + if (-f "$TaskDir/err" and !-z "$TaskDir/err") + { + link("$TaskDir/err", "$LatestBaseName.err"); + } + } + } + } + my $Result = $VM->Name .": ". $VM->Status ." Status: $Status Failures: ". (defined $TestFailures ? $TestFailures : "unset"); LogMsg "Task $JobId/$StepNo/$TaskNo done ($Result)\n"; Debug(Elapsed($Start), " Done. $Result\n"); @@ -266,12 +322,12 @@ sub FatalError($;$) LogMsg "$JobId/$StepNo/$TaskNo $ErrMessage"; LogTaskError($ErrMessage);
- WrapUpAndExit('boterror', $Retry); + WrapUpAndExit('boterror', undef, $Retry); }
-sub FatalTAError($$) +sub FatalTAError($$;$) { - my ($TA, $ErrMessage) = @_; + my ($TA, $ErrMessage, $PossibleCrash) = @_; $ErrMessage .= ": ". $TA->GetLastError() if (defined $TA);
# A TestAgent operation failed, see if the VM is still accessible @@ -297,7 +353,13 @@ sub FatalTAError($$) else { # Ignore the TestAgent error, it's irrelevant - $ErrMessage = "The test VM is powered off!\n"; + $ErrMessage = "The test VM is powered off! Did the test shut it down?\n"; + } + if ($PossibleCrash and !$Task->CanRetry()) + { + # The test did it! + LogTaskError($ErrMessage); + WrapUpAndExit('completed', 1); } FatalError($ErrMessage, $Retry); } @@ -320,33 +382,78 @@ elsif (!$VM->GetDomain()->IsPoweredOn()) FatalError("The VM is not powered on\n"); }
-if ($Step->FileType ne "patchdlls") +if (($Step->Type eq "suite" and $Step->FileType ne "none") or + ($Step->Type ne "suite" and $Step->FileType ne "patchdlls")) { FatalError("Unexpected file type '". $Step->FileType ."' found\n"); }
# -# Run the task +# Setup the VM # +my $TA = $VM->GetAgent(); +Debug(Elapsed($Start), " Setting the time\n"); +if (!$TA->SetTime()) +{ + # Not a fatal error. Try the next port in case the VM runs a privileged + # TestAgentd daemon there. + my $PrivilegedTA = $VM->GetAgent(1); + if (!$PrivilegedTA->SetTime()) + { + LogTaskError("Unable to set the VM system time: ". $PrivilegedTA->GetLastError() .". Maybe the TestAgentd process is missing the required privileges.\n"); + $PrivilegedTA->Disconnect(); + } +}
my $FileName = $Step->GetFullFileName(); -my $TA = $VM->GetAgent(); -Debug(Elapsed($Start), " Sending '$FileName'\n"); -if (!$TA->SendFile($FileName, "staging/patch.diff", 0)) +if (defined $FileName) { - FatalTAError($TA, "Could not copy the patch to the VM"); + Debug(Elapsed($Start), " Sending '$FileName'\n"); + if (!$TA->SendFile($FileName, "staging/patch.diff", 0)) + { + FatalTAError($TA, "Could not copy the patch to the VM"); + } } + my $Script = "#!/bin/sh\n". - "( set -x\n" . - " ../bin/build/WineTest.pl ". $Task->CmdLineArg ." build patch.diff\n". - ") >Task.log 2>&1\n"; + "( set -x\n". + " ../bin/build/WineTest.pl "; +if ($Step->Type eq "suite") +{ + my $Tag = lc($VM->Name); + $Tag =~ s/^$TagPrefix//; + $Tag =~ s/[^a-zA-Z0-9]/-/g; + $Script .= $Task->CmdLineArg .",submit winetest $TagPrefix-$Tag "; + if (defined $WebHostName) + { + my $StepTask = 100 * $StepNo + $TaskNo; + $Script .= "-u "http://$WebHostName/JobDetails.pl?Key=$JobId&s$StepTask=1#k$StepTask%5C" "; + } + my $Info = $VM->Description ? $VM->Description : ""; + if ($VM->Details) + { + $Info .= ": " if ($Info ne ""); + $Info .= $VM->Details; + } + $Script .= join(" ", "-m", ShQuote($AdminEMail), "-i", ShQuote($Info)); +} +else +{ + $Script .= $Task->CmdLineArg ." build patch.diff"; +} +$Script .= "\n) >Task.log 2>&1\n"; Debug(Elapsed($Start), " Sending the script: [$Script]\n"); if (!$TA->SendFileFromString($Script, "task", $TestAgent::SENDFILE_EXE)) { FatalTAError($TA, "Could not send the task script to the VM"); }
+ +# +# Run the test +# + Debug(Elapsed($Start), " Starting the script\n"); my $Pid = $TA->Run(["./task"], 0); if (!$Pid) @@ -357,10 +464,11 @@ if (!$Pid)
# # From that point on we want to at least try to grab the task log -# before giving up +# and a screenshot before giving up #
-my ($NewStatus, $ErrMessage, $TAError, $TaskTimedOut); +my $NewStatus = 'completed'; +my ($TaskFailures, $TaskTimedOut, $ErrMessage, $TAError, $PossibleCrash); Debug(Elapsed($Start), " Waiting for the script (", $Task->Timeout, "s timeout)\n"); if (!defined $TA->Wait($Pid, $Task->Timeout, 60)) { @@ -368,11 +476,19 @@ if (!defined $TA->Wait($Pid, $Task->Timeout, 60)) if ($ErrMessage =~ /timed out waiting for the child process/) { $ErrMessage = "The task timed out\n"; - $NewStatus = "badbuild"; + if ($Step->Type eq "build") + { + $NewStatus = "badbuild"; + } + else + { + $TaskFailures = 1; + } $TaskTimedOut = 1; } else { + $PossibleCrash = 1 if ($Step->Type ne "build"); $TAError = "An error occurred while waiting for the task to complete: $ErrMessage"; $ErrMessage = undef; } @@ -399,10 +515,11 @@ if ($TA->GetFile("Task.log", "$TaskDir/log")) { FatalError("$Result\n", "retry"); } - else + elsif ($Result ne "missing" or $Step->Type ne "suite") { - # If the result line is missing we probably already have an error message - # that explains why. + # There is no build and thus no result line when running WineTest. + # Otherwise if the result line is missing we probably already have an + # error message that explains why. $NewStatus = "badbuild"; } } @@ -411,15 +528,62 @@ elsif (!defined $TAError) $TAError = "An error occurred while retrieving the task log: ". $TA->GetLastError(); }
+# +# Grab the test logs if any +# + +my $TimedOut; +if ($Step->Type ne "build") +{ + my $TaskDir = $Task->CreateDir(); + my $BuildList = $Task->CmdLineArg; + $BuildList =~ s/ .*$//; + foreach my $Build (split /,/, $BuildList) + { + my $RptFileName = "$Build.report"; + Debug(Elapsed($Start), " Retrieving '$RptFileName'\n"); + if ($TA->GetFile($RptFileName, "$TaskDir/$RptFileName")) + { + chmod 0664, "$TaskDir/$RptFileName"; + + (my $LogFailures, my $LogErrors, $TimedOut) = ParseWineTestReport("$TaskDir/$RptFileName", 1, $Step->Type eq "suite", $TaskTimedOut); + if (!defined $LogFailures and @$LogErrors == 1) + { + # Could not open the file + $NewStatus = 'boterror'; + Error "Unable to open '$RptFileName' for reading: $!\n"; + LogTaskError("Unable to open '$RptFileName' for reading: $!\n"); + } + else + { + # $LogFailures can legitimately be undefined in case of a timeout + $TaskFailures += $LogFailures || 0; + foreach my $Error (@$LogErrors) + { + LogTaskError("$Error\n"); + } + } + } + elsif (!defined $TAError and + $TA->GetLastError() !~ /: No such file or directory/) + { + $TAError = "An error occurred while retrieving $RptFileName: ". $TA->GetLastError(); + $NewStatus = 'boterror'; + } + } +} + +Debug(Elapsed($Start), " Disconnecting\n"); +$TA->Disconnect(); + # Report the task errors even though they may have been caused by # TestAgent trouble. LogTaskError($ErrMessage) if (defined $ErrMessage); -FatalTAError(undef, $TAError) if (defined $TAError); -$TA->Disconnect(); +FatalTAError(undef, $TAError, $PossibleCrash) if (defined $TAError);
# # Wrap up #
-WrapUpAndExit($NewStatus, undef, $TaskTimedOut); +WrapUpAndExit($NewStatus, $TaskFailures, undef, $TaskTimedOut || $TimedOut); diff --git a/testbot/bin/build/WineReconfig.pl b/testbot/bin/build/WineReconfig.pl index b030051c6..e3f14e243 100755 --- a/testbot/bin/build/WineReconfig.pl +++ b/testbot/bin/build/WineReconfig.pl @@ -40,6 +40,9 @@ my $Name0 = $0; $Name0 =~ s+^.*/++;
+use Digest::SHA; +use File::Path; + use WineTestBot::Config; use WineTestBot::PatchUtils;
@@ -126,7 +129,7 @@ sub BuildWine($$$$) { my ($Targets, $NoRm, $Build, $Extras) = @_;
- return 1 if (!$Targets->{$Build}); + return 1 if (!$Targets->{build} or !$Targets->{$Build}); mkdir "$DataDir/build-$Build" if (!-d "$DataDir/build-$Build");
# If $NoRm is not set, rebuild from scratch to make sure cruft will not @@ -134,7 +137,7 @@ sub BuildWine($$$$) InfoMsg "\nRebuilding the $Build Wine\n"; system("cd '$DataDir/build-$Build' && set -x && ". ($NoRm ? "" : "rm -rf * && ") . - "time ../wine/configure $Extras --disable-winetest && ". + "time ../wine/configure $Extras && ". "time make -j$ncpus"); if ($? != 0) { @@ -146,6 +149,154 @@ sub BuildWine($$$$) }
+# +# WinePrefix helpers +# + +sub VerifyAddOn($$) +{ + my ($AddOn, $Arch) = @_; + + my $Sha256 = Digest::SHA->new(256); + eval { $Sha256->addfile("$DataDir/$AddOn->{name}/$AddOn->{filename}") }; + return "$@" if ($@); + + my $Checksum = $Sha256->hexdigest(); + return undef if ($Checksum eq $AddOn->{$Arch}); + return "Bad checksum for '$AddOn->{filename}'"; +} + +sub UpdateAddOn($$$) +{ + my ($AddOn, $Name, $Arch) = @_; + + if (!defined $AddOn) + { + LogMsg "Could not get information on the $Name addon\n"; + return 0; + } + if (!$AddOn->{version}) + { + LogMsg "Could not get the $Name version\n"; + return 0; + } + if (!$AddOn->{$Arch}) + { + LogMsg "Could not get the $Name $Arch checksum\n"; + return 0; + } + + $AddOn->{filename} = "wine". ($Name eq "gecko" ? "_" : "-") . + "$Name-$AddOn->{version}". + ($Arch eq "" ? "" : "-$Arch") .".msi"; + return 1 if (!VerifyAddOn($AddOn, $Arch)); + + InfoMsg "Downloading $AddOn->{filename}\n"; + mkdir "$DataDir/$Name"; + + my $Url="http://dl.winehq.org/wine/wine-$Name/$AddOn-%3E%7Bversion%7D/$AddOn-%3E%7Bfi..."; + for (1..3) + { + system("cd '$DataDir/$Name' && set -x && ". + "wget --no-verbose -O- '$Url' >'$AddOn->{filename}'"); + last if ($? == 0); + } + my $ErrMessage = VerifyAddOn($AddOn, $Arch); + return 1 if (!defined $ErrMessage); + LogMsg "$ErrMessage\n"; + return 0; +} + +sub UpdateAddOns($) +{ + my ($Targets) = @_; + return 1 if (!$Targets->{addons}); + + my %AddOns; + if (open(my $fh, "<", "$DataDir/wine/dlls/appwiz.cpl/addons.c")) + { + my $Arch = ""; + while (my $Line= <$fh>) + { + if ($Line =~ /^\s*#\s*define\s+ARCH_STRING\s+"([^"]+)"/) + { + $Arch = $1; + } + elsif ($Line =~ /^\s*#\s*define\s*(GECKO|MONO)_VERSION\s*"([^"]+)"/) + { + my ($AddOn, $Version) = ($1, $2); + $AddOn =~ tr/A-Z/a-z/; + $AddOns{$AddOn}->{name} = $AddOn; + $AddOns{$AddOn}->{version} = $Version; + } + elsif ($Line =~ /^\s*#\s*define\s*(GECKO|MONO)_SHA\s*"([^"]+)"/) + { + my ($AddOn, $Checksum) = ($1, $2); + $AddOn =~ tr/A-Z/a-z/; + $AddOns{$AddOn}->{$Arch} = $Checksum; + $Arch = ""; + } + } + close($fh); + } + else + { + LogMsg "Could not open 'wine/dlls/appwiz.cpl/addons.c': $!\n"; + return 0; + } + + return UpdateAddOn($AddOns{gecko}, "gecko", "x86") && + UpdateAddOn($AddOns{gecko}, "gecko", "x86_64") && + UpdateAddOn($AddOns{mono}, "mono", ""); +} + +# See also WineTest.pl +sub SetupWineEnvironment($) +{ + my ($Build) = @_; + + $ENV{WINEPREFIX} = "$DataDir/wineprefix-$Build"; + $ENV{DISPLAY} ||= ":0.0"; +} + +# See also WineTest.pl +sub RunWine($$$) +{ + my ($Build, $Cmd, $CmdArgs) = @_; + + my $Magic = `cd '$DataDir/build-$Build' && file $Cmd`; + my $Wine = ($Magic =~ /ELF 64/ ? "./wine64" : "./wine"); + return system("cd '$DataDir/build-$Build' && set -x && ". + "time $Wine $Cmd $CmdArgs"); +} + +# Setup a brand new WinePrefix ready for use for testing. +# This way we do it once instead of doing it for every test, thus saving +# time. Note that this requires using a different wineprefix for each build. +sub NewWinePrefix($$) +{ + my ($Targets, $Build) = @_; + + return 1 if (!$Targets->{wineprefix} or !$Targets->{$Build}); + + InfoMsg "\nRecreating the $Build wineprefix\n"; + SetupWineEnvironment($Build); + rmtree($ENV{WINEPREFIX}); + + # Crash dialogs cause delays so disable them + if (RunWine($Build, "./programs/reg/reg.exe.so", "ADD HKCU\\Software\\Wine\\WineDbg /v ShowCrashDialog /t REG_DWORD /d 0")) + { + LogMsg "Failed to disable the $Build build crash dialogs: $!\n"; + return 0; + } + + # Ensure the WinePrefix has been fully created before updating the snapshot + system("cd '$DataDir/build-$Build' && ./server/wineserver -w"); + + return 1; +} + + # # Setup and command line processing # @@ -154,7 +305,7 @@ $ENV{PATH} = "/usr/lib/ccache:/usr/bin:/bin"; delete $ENV{ENV};
my %AllTargets; -map { $AllTargets{$_} = 1 } qw(update win32 wow32 wow64); +map { $AllTargets{$_} = 1 } qw(update addons build wineprefix win32 wow32 wow64);
my ($Usage, $TargetList, $NoRm); while (@ARGV) @@ -212,15 +363,18 @@ if (defined $Usage) } print "Usage: $Name0 [--no-rm] [--help] [TARGETS]\n"; print "\n"; - print "Performs all the tasks needed for the host to be ready to test new patches: update the Wine source and rebuild the Wine binaries.\n"; + print "Performs all the tasks needed for the host to be ready to test new patches: update the Wine source and addons, and rebuild the Wine binaries.\n"; print "\n"; print "Where:\n"; print " TARGETS Is a comma-separated list of targets to process. By default all\n"; print " targets are processed.\n"; print " - update: Update Wine's source code.\n"; - print " - win32: Rebuild the regular 32 bit Wine.\n"; - print " - wow32: Rebuild the 32 bit WoW Wine.\n"; - print " - wow64: Rebuild the 64 bit WoW Wine.\n"; + print " - build: Update the Wine builds.\n"; + print " - addons: Update the Gecko and Mono Wine addons.\n"; + print " - wineprefix: Update the wineprefixes.\n"; + print " - win32: Apply the above to the regular 32 bit Wine.\n"; + print " - wow32: Apply the above to the 32 bit WoW Wine.\n"; + print " - wow64: Apply the above to the 64 bit WoW Wine.\n"; print " --no-rm Don't rebuild from scratch.\n"; print " --help Shows this usage message.\n"; exit 0; @@ -240,16 +394,25 @@ if ($DataDir =~ /'/)
# -# Run the builds +# Run the builds and/or tests #
CountCPUs();
-if (!BuildTestAgentd() || - !GitPull($Targets) || - !BuildWine($Targets, $NoRm, "win32", "") || - !BuildWine($Targets, $NoRm, "wow64", "--enable-win64") || - !BuildWine($Targets, $NoRm, "wow32", "--with-wine64='$DataDir/build-wow64'")) +if (!BuildTestAgentd() or + !GitPull($Targets) or + !UpdateAddOns($Targets) or + !BuildWine($Targets, $NoRm, "win32", "") or + !BuildWine($Targets, $NoRm, "wow64", "--enable-win64") or + !BuildWine($Targets, $NoRm, "wow32", "--with-wine64='$DataDir/build-wow64'") or + !NewWinePrefix($Targets, "win32") or + # The wow32 and wow64 wineprefixes: + # - Are essentially identical. + # - Must be created after both WoW builds have been updated. + # - Make it possible to run the wow32 and wow64 tests in separate prefixes, + # thus ensuring they don't interfere with each other. + !NewWinePrefix($Targets, "wow64") or + !NewWinePrefix($Targets, "wow32")) { exit(1); } diff --git a/testbot/bin/build/WineTest.pl b/testbot/bin/build/WineTest.pl index 3be81485e..518deae99 100755 --- a/testbot/bin/build/WineTest.pl +++ b/testbot/bin/build/WineTest.pl @@ -144,6 +144,62 @@ sub BuildWine($$) }
+# +# Test helpers +# + +# See also WineReconfig.pl +sub SetupWineEnvironment($) +{ + my ($Build) = @_; + + $ENV{WINEPREFIX} = "$DataDir/wineprefix-$Build"; + $ENV{DISPLAY} ||= ":0.0"; +} + +# See also WineReconfig.pl +sub RunWine($$$) +{ + my ($Build, $Cmd, $CmdArgs) = @_; + + my $Magic = `cd '$DataDir/build-$Build' && file $Cmd`; + my $Wine = ($Magic =~ /ELF 64/ ? "./wine64" : "./wine"); + return system("cd '$DataDir/build-$Build' && set -x && ". + "time $Wine $Cmd $CmdArgs"); +} + +sub DailyWineTest($$$$) +{ + my ($Targets, $Build, $BaseTag, $Args) = @_; + + return 1 if (!$Targets->{$Build}); + + InfoMsg "\nRunning WineTest in the $Build Wine\n"; + SetupWineEnvironment($Build); + + # Run WineTest. Ignore the exit code since it returns non-zero whenever + # there are test failures. + RunWine($Build, "./programs/winetest/winetest.exe.so", + "-c -o '../$Build.report' -t $BaseTag-$Build ". ShArgv2Cmd(@$Args)); + if (!-f "$Build.report") + { + LogMsg "WineTest did not produce a report file\n"; + return 0; + } + + # Send the report to the website + if ($Targets->{submit} and + RunWine($Build, "./programs/winetest/winetest.exe.so", + "-c -s '../$Build.report'")) + { + LogMsg "WineTest failed to send the $Build report\n"; + # Soldier on in case it's just a network issue + } + + return 1; +} + + # # Setup and command line processing # @@ -152,9 +208,9 @@ $ENV{PATH} = "/usr/lib/ccache:/usr/bin:/bin"; delete $ENV{ENV};
my %AllTargets; -map { $AllTargets{$_} = 1 } qw(win32 wow32 wow64); +map { $AllTargets{$_} = 1 } qw(win32 wow32 wow64 submit);
-my ($Usage, $TargetList, $Action, $FileName); +my ($Usage, $TargetList, $Action, $FileName, $BaseTag); while (@ARGV) { my $Arg = shift @ARGV; @@ -177,6 +233,12 @@ while (@ARGV) { $Action = $Arg; } + elsif (($Action || "") eq "winetest") + { + $BaseTag = $Arg; + # The remaining arguments are meant for WineTest + last; + } elsif (!defined $FileName) { if (IsValidFileName($Arg)) @@ -230,6 +292,23 @@ if (!defined $Usage) Error "you must specify the action to perform\n"; $Usage = 2; } + elsif ($Action eq "winetest") + { + if (!defined $BaseTag) + { + Error "you must specify a base tag for WineTest\n"; + $Usage = 2; + } + elsif ($BaseTag =~ m/^([\w_.-]+)$/) + { + $BaseTag = $1; + } + else + { + Error "invalid WineTest base tag '$BaseTag'\n"; + $Usage = 2; + } + } elsif ($Action ne "build") { Error "invalid '$Action' action\n"; @@ -245,16 +324,23 @@ if (!defined $Usage) if (defined $Usage) { print "Usage: $Name0 [--help] TARGETS build PATCH\n"; + print "or $Name0 [--help] TARGETS winetest BASETAG ARGS\n"; print "\n"; - print "Applies the specified patch and rebuilds Wine.\n"; + print "Tests the specified patch or runs WineTest in Wine.\n"; print "\n"; print "Where:\n"; - print " TARGETS Is a comma-separated list of targets for the build.\n"; + print " TARGETS Is a comma-separated list of targets for the specified action.\n"; print " - win32: The regular 32 bit Wine build.\n"; print " - wow32: The 32 bit WoW Wine build.\n"; print " - wow64: The 64 bit WoW Wine build.\n"; + print " - submit: Send the WineTest result to the website.\n"; print " build Verify that the patch compiles.\n"; print " PATCH Is the staging file containing the patch to test.\n"; + print " winetest Run WineTest and submit the result to the website if the submit\n"; + print " task was specified.\n"; + print " BASETAG Is the tag for this WineTest run. Note that the build type is\n"; + print " automatically added to this tag.\n"; + print " ARGS The WineTest arguments.\n"; print " --help Shows this usage message.\n"; exit $Usage; } @@ -267,16 +353,28 @@ if ($DataDir =~ /'/)
# -# Run the builds +# Run the builds and tests #
+# Clean up old reports +map { unlink("$_.report") } keys %AllTargets; + CountCPUs();
-my $Impacts = ApplyPatch($FileName); -exit(1) if (!$Impacts or - !BuildWine($Targets, "win32") or - !BuildWine($Targets, "wow64") or - !BuildWine($Targets, "wow32")); +if ($Action eq "build") +{ + my $Impacts = ApplyPatch($FileName); + exit(1) if (!$Impacts or + !BuildWine($Targets, "win32") or + !BuildWine($Targets, "wow64") or + !BuildWine($Targets, "wow32")); +} +elsif ($Action eq "winetest") +{ + exit(1) if (!DailyWineTest($Targets, "win32", $BaseTag, @ARGV) or + !DailyWineTest($Targets, "wow64", $BaseTag, @ARGV) or + !DailyWineTest($Targets, "wow32", $BaseTag, @ARGV)); +}
LogMsg "ok\n"; exit; diff --git a/testbot/lib/WineTestBot/LogUtils.pm b/testbot/lib/WineTestBot/LogUtils.pm index 363f80827..2d3042ec7 100644 --- a/testbot/lib/WineTestBot/LogUtils.pm +++ b/testbot/lib/WineTestBot/LogUtils.pm @@ -458,6 +458,7 @@ sub GetLogFileNames($;$) my ($Dir, $IncludeOld) = @_;
my @Candidates = ("exe32.report", "exe64.report", + "win32.report", "wow32.report", "wow64.report", "log", "err"); push @Candidates, "log.old", "err.old" if ($IncludeOld);
@@ -472,10 +473,13 @@ sub GetLogFileNames($;$) my %_LogFileLabels = ( "exe32.report" => "32 bit Windows report", "exe64.report" => "64 bit Windows report", - "err" => "task errors", + "win32.report" => "32 bit Wine report", + "wow32.report" => "32 bit WoW Wine report", + "wow64.report" => "64 bit Wow Wine report", "log" => "task log", - "err.old" => "old task errors", + "err" => "task errors", "log.old" => "old logs", + "err.old" => "old task errors", );
=pod diff --git a/testbot/lib/WineTestBot/Utils.pm b/testbot/lib/WineTestBot/Utils.pm index 111a56589..f64310b6b 100644 --- a/testbot/lib/WineTestBot/Utils.pm +++ b/testbot/lib/WineTestBot/Utils.pm @@ -29,7 +29,7 @@ use Exporter 'import'; our @EXPORT = qw(MakeSecureURL SecureConnection GenerateRandomString OpenNewFile CreateNewFile CreateNewLink CreateNewDir DurationToString BuildEMailRecipient IsValidFileName - ShQuote); + ShQuote ShArgv2Cmd);
use Fcntl;
@@ -220,4 +220,21 @@ sub ShQuote($) return ""$Str""; }
+=pod +=over 12 + +=item C<ShArgv2Cmd()> + +Converts an argument list into a command line suitable for use in a shell. + +See also ShQuote(). + +=back +=cut + +sub ShArgv2Cmd(@) +{ + return join(' ', map { /[^a-zA-Z0-9/.,+_-]/ ? ShQuote($_) : $_ } @_); +} + 1;