Missions allow the TestBot administrator to specify which tests a given VM should perform for mailing list patches. For instance this can be used to restrict a 64 bit Windows VM to 32 bit tests (useful when it duplicates another VM with just a different locale). Furthermore missions can get options. Currently only the 'nosubmit' option is supported and it disables submitting the WineTest results online.
Signed-off-by: Francois Gouget fgouget@codeweavers.com ---
Eventually missions will make it possible to specify how much testing a Wine VM should do, from running only patched tests to running every test for every patch; against which Wine build; only in the English locale, or in other locales too.
testbot/bin/CheckForWinetestUpdate.pl | 105 ++++++++++++++------ testbot/bin/WineRunTask.pl | 12 ++- testbot/bin/WineRunWineTest.pl | 31 +++--- testbot/bin/build/WineTest.pl | 131 +++++++++++++------------ testbot/ddl/update41.sql | 48 +++++++++ testbot/ddl/winetestbot.sql | 2 + testbot/doc/winetestbot-schema.dia | 46 +++++++++ testbot/lib/WineTestBot/Missions.pm | 99 +++++++++++++++++++ testbot/lib/WineTestBot/PatchUtils.pm | 8 +- testbot/lib/WineTestBot/Patches.pm | 134 +++++++++++++++----------- testbot/lib/WineTestBot/StepsTasks.pm | 55 +++++++---- testbot/lib/WineTestBot/Tasks.pm | 1 + testbot/lib/WineTestBot/VMs.pm | 41 ++++++++ testbot/web/JobDetails.pl | 1 + testbot/web/Submit.pl | 80 ++++++++------- testbot/web/admin/VMDetails.pl | 16 +++ 16 files changed, 587 insertions(+), 223 deletions(-) create mode 100644 testbot/ddl/update41.sql create mode 100644 testbot/lib/WineTestBot/Missions.pm
diff --git a/testbot/bin/CheckForWinetestUpdate.pl b/testbot/bin/CheckForWinetestUpdate.pl index 29953642ef..95cb13d8ed 100755 --- a/testbot/bin/CheckForWinetestUpdate.pl +++ b/testbot/bin/CheckForWinetestUpdate.pl @@ -53,6 +53,7 @@ use WineTestBot::Config; use WineTestBot::Engine::Notify; use WineTestBot::Jobs; use WineTestBot::Log; +use WineTestBot::Missions; use WineTestBot::PatchUtils; use WineTestBot::Users; use WineTestBot::Utils; @@ -201,21 +202,38 @@ sub AddJob($$$) $NewJob->Priority($BaseJob && $Build eq "exe32" ? 8 : 9); $NewJob->Remarks($Remarks);
- # Add a step to the job - my $Steps = $NewJob->Steps; - my $NewStep = $Steps->Add(); - $NewStep->Type("suite"); - $NewStep->FileName($LatestBaseName); - $NewStep->FileType($Build); - # Add a task for each VM - my $Tasks = $NewStep->Tasks; + my $Tasks; foreach my $VMKey (@{$VMs->SortKeysBySortOrder($VMs->GetKeys())}) { - Debug(" $VMKey $Build\n"); - my $Task = $Tasks->Add(); - $Task->VM($VMs->GetItem($VMKey)); - $Task->Timeout(GetTestTimeout(undef, { $Build => 1 })); + my $VM = $VMs->GetItem($VMKey); + my ($ErrMessage, $Missions) = ParseMissionStatement($VM->Missions); + if (defined $ErrMessage) + { + Debug("$VMKey has an invalid mission statement: $!\n"); + next; + } + + foreach my $TaskMissions (@$Missions) + { + next if (!$TaskMissions->{Builds}->{$Build}); + + if (!$Tasks) + { + # Add a step to the job + my $TestStep = $NewJob->Steps->Add(); + $TestStep->Type("suite"); + $TestStep->FileName($LatestBaseName); + $TestStep->FileType($Build); + $Tasks = $TestStep->Tasks; + } + + Debug(" $VMKey $Build\n"); + my $Task = $Tasks->Add(); + $Task->VM($VM); + $Task->Timeout($SuiteTimeout); + $Task->Missions($TaskMissions->{Statement}); + } }
# Save it all @@ -246,9 +264,6 @@ sub AddJob($$$) return 1; }
-my @ExeBuilds = qw(exe32 exe64); -my @WineBuilds = qw(win32 wow32 wow64); - sub AddReconfigJob($) { my ($VMType) = @_; @@ -287,9 +302,23 @@ sub AddReconfigJob($) Debug(" $VMKey $VMType reconfig\n"); my $Task = $BuildStep->Tasks->Add(); $Task->VM($VM); - my $Builds; - map { $Builds->{$_} = 1 } ($VMType eq "wine" ? @WineBuilds : @ExeBuilds); - $Task->Timeout(GetBuildTimeout(undef, $Builds)); + + # Merge all the tasks into one so we only recreate the base snapshot once + my $MissionStatement = $VMType ne "wine" ? "exe32:exe64" : + MergeMissionStatementTasks($VM->Missions); + my ($ErrMessage, $Missions) = ParseMissionStatement($MissionStatement); + if (defined $ErrMessage) + { + Debug("$VMKey has an invalid mission statement: $!\n"); + next; + } + if (@$Missions != 1) + { + Debug("Found no mission or too many task missions for $VMKey\n"); + next; + } + $Task->Timeout(GetBuildTimeout(undef, $Missions->[0])); + $Task->Missions($Missions->[0]->{Statement}); }
# Save the build step so the others can reference it. @@ -303,22 +332,38 @@ sub AddReconfigJob($) if ($VMType eq "wine") { # Add steps to run WineTest on Wine - foreach my $Build (@WineBuilds) + my $Tasks; + foreach my $VMKey (@$SortedKeys) { - # Add a step to the job - my $NewStep = $Steps->Add(); - $NewStep->PreviousNo($BuildStep->No); - $NewStep->Type("suite"); - $NewStep->FileType("none"); + my $VM = $VMs->GetItem($VMKey); + # Move all the missions into separate tasks so we don't have one very + # long task hogging the VM forever. Note that this also ok because the + # WineTest tasks don't have to recompile Wine. + my $MissionStatement = SplitMissionStatementTasks($VM->Missions); + my ($ErrMessage, $Missions) = ParseMissionStatement($MissionStatement); + if (defined $ErrMessage) + { + Debug("$VMKey has an invalid mission statement: $!\n"); + next; + }
- foreach my $VMKey (@$SortedKeys) + foreach my $TaskMissions (@$Missions) { - my $VM = $VMs->GetItem($VMKey); - Debug(" $VMKey $Build\n"); - my $Task = $NewStep->Tasks->Add(); + if (!$Tasks) + { + # Add a step to the job + my $TestStep = $Steps->Add(); + $TestStep->PreviousNo($BuildStep->No); + $TestStep->Type("suite"); + $TestStep->FileType("none"); + $Tasks = $TestStep->Tasks; + } + + Debug(" $VMKey $TaskMissions->{Statement}\n"); + my $Task = $Tasks->Add(); $Task->VM($VM); - $Task->CmdLineArg($Build); - $Task->Timeout(GetTestTimeout(undef, { $Build => 1 })); + $Task->Timeout(GetTestTimeout(undef, $TaskMissions)); + $Task->Missions($TaskMissions->{Statement}); } } } diff --git a/testbot/bin/WineRunTask.pl b/testbot/bin/WineRunTask.pl index 077426b232..fea8c03f5e 100755 --- a/testbot/bin/WineRunTask.pl +++ b/testbot/bin/WineRunTask.pl @@ -45,6 +45,7 @@ use WineTestBot::Engine::Notify; use WineTestBot::Jobs; use WineTestBot::Log; use WineTestBot::LogUtils; +use WineTestBot::Missions; use WineTestBot::Utils; use WineTestBot::VMs;
@@ -391,6 +392,13 @@ if ($Step->FileType ne "exe32" and $Step->FileType ne "exe64") FatalError("Unexpected file type '". $Step->FileType ."' found for ". $Step->Type ." step\n"); }
+my ($ErrMessage, $Missions) = ParseMissionStatement($Task->Missions); +FatalError "$ErrMessage\n" if (defined $ErrMessage); +FatalError "Empty mission statement\n" if (!@$Missions); +FatalError "Cannot specify missions for multiple tasks\n" if (@$Missions > 1); +FatalError "Cannot specify multiple missions\n" if (@{$Missions->[0]->{Missions}} > 1); +my $Mission = $Missions->[0]->{Missions}->[0]; +
# # Setup the VM @@ -477,8 +485,8 @@ elsif ($Step->Type eq "suite") $Info =~ s/"/\"/g; $Info =~ s/%/%%/g; $Info =~ s/%/%%/g; - $Script .= "-q -o $RptFileName -t $Tag -m "$EMail" -i "$Info"\r\n". - "$FileName -q -s $RptFileName\r\n"; + $Script .= "-q -o $RptFileName -t $Tag -m "$EMail" -i "$Info"\r\n"; + $Script .= "$FileName -q -s $RptFileName\r\n" if (!$Mission->{nosubmit}); } Debug(Elapsed($Start), " Sending the script: [$Script]\n"); if (!$TA->SendFileFromString($Script, "script.bat", $TestAgent::SENDFILE_EXE)) diff --git a/testbot/bin/WineRunWineTest.pl b/testbot/bin/WineRunWineTest.pl index 0e438f957c..f47a02413a 100755 --- a/testbot/bin/WineRunWineTest.pl +++ b/testbot/bin/WineRunWineTest.pl @@ -41,13 +41,14 @@ $Name0 =~ s+^.*/++;
use WineTestBot::Config; +use WineTestBot::Engine::Notify; use WineTestBot::Jobs; +use WineTestBot::Missions; use WineTestBot::PatchUtils; -use WineTestBot::VMs; use WineTestBot::Log; use WineTestBot::LogUtils; use WineTestBot::Utils; -use WineTestBot::Engine::Notify; +use WineTestBot::VMs;
# @@ -220,6 +221,8 @@ sub LogTaskError($) } }
+my $TaskMissions; + sub WrapUpAndExit($;$$$) { my ($Status, $TestFailures, $Retry, $TimedOut) = @_; @@ -287,9 +290,7 @@ sub WrapUpAndExit($;$$$)
if ($Step->Type eq 'suite' and $Status eq 'completed' and !$TimedOut) { - my $BuildList = $Task->CmdLineArg; - $BuildList =~ s/ .*$//; - foreach my $Build (split /,/, $BuildList) + foreach my $Build (keys %{$TaskMissions->{Builds}}) { # Keep the old report if the new one is missing my $RptFileName = "$Build.report"; @@ -392,6 +393,12 @@ if (($Step->Type eq "suite" and $Step->FileType ne "none") or FatalError("Unexpected file type '". $Step->FileType ."' found for ". $Step->Type ." step\n"); }
+my ($ErrMessage, $Missions) = ParseMissionStatement($Task->Missions); +FatalError "$ErrMessage\n" if (defined $ErrMessage); +FatalError "Empty mission statement\n" if (!@$Missions); +FatalError "Cannot specify missions for multiple tasks\n" if (@$Missions > 1); +$TaskMissions = $Missions->[0]; +
# # Setup the VM @@ -431,7 +438,7 @@ $Script .= " ../bin/build/WineTest.pl "; if ($Step->Type eq "suite") { my $BaseTag = BuildTag($VM->Name); - $Script .= "--winetest ". $Task->CmdLineArg ." $BaseTag "; + $Script .= "--winetest ". $Task->Missions ." $BaseTag "; if (defined $WebHostName) { my $StepTask = 100 * $StepNo + $TaskNo; @@ -447,7 +454,7 @@ if ($Step->Type eq "suite") } else { - $Script .= "--testpatch ". $Task->CmdLineArg ." patch.diff"; + $Script .= "--testpatch ". $Task->Missions ." patch.diff"; } $Script .= "\n) >Task.log 2>&1\n"; Debug(Elapsed($Start), " Sending the script: [$Script]\n"); @@ -475,7 +482,7 @@ if (!$Pid) #
my $NewStatus = 'completed'; -my ($TaskFailures, $TaskTimedOut, $ErrMessage, $TAError, $PossibleCrash); +my ($TaskFailures, $TaskTimedOut, $TAError, $PossibleCrash); Debug(Elapsed($Start), " Waiting for the script (", $Task->Timeout, "s timeout)\n"); if (!defined $TA->Wait($Pid, $Task->Timeout, 60)) { @@ -542,9 +549,7 @@ elsif (!defined $TAError)
if ($Step->Type ne "build") { - my $BuildList = $Task->CmdLineArg; - $BuildList =~ s/ .*$//; - foreach my $Build (split /,/, $BuildList) + foreach my $Build (keys %{$TaskMissions->{Builds}}) { my $RptFileName = "$Build.report"; Debug(Elapsed($Start), " Retrieving '$RptFileName'\n"); @@ -602,9 +607,7 @@ if ($NewStatus eq 'completed') { my $LatestDir = "$DataDir/latest"; my $StepDir = $Step->GetDir(); - my $BuildList = $Task->CmdLineArg; - $BuildList =~ s/ .*$//; - foreach my $Build (split /,/, $BuildList) + foreach my $Build (keys %{$TaskMissions->{Builds}}) { my $RptFileName = "$Build.report"; my $RefReport = $Task->VM->Name ."_$RptFileName"; diff --git a/testbot/bin/build/WineTest.pl b/testbot/bin/build/WineTest.pl index 712341b339..c5d38df9d4 100755 --- a/testbot/bin/build/WineTest.pl +++ b/testbot/bin/build/WineTest.pl @@ -43,6 +43,7 @@ sub BEGIN
use Build::Utils; use WineTestBot::Config; +use WineTestBot::Missions; use WineTestBot::Utils;
@@ -52,9 +53,9 @@ use WineTestBot::Utils;
sub BuildWine($$) { - my ($Targets, $Build) = @_; + my ($TaskMissions, $Build) = @_;
- return 1 if (!$Targets->{$Build}); + return 1 if (!$TaskMissions->{Builds}->{$Build});
InfoMsg "\nRebuilding the $Build Wine\n"; my $CPUCount = GetCPUCount(); @@ -74,44 +75,40 @@ sub BuildWine($$) # Test helpers #
- -sub DailyWineTest($$$$$) +sub DailyWineTest($$$$) { - my ($Targets, $Build, $NoSubmit, $BaseTag, $Args) = @_; - - return 1 if (!$Targets->{$Build}); + my ($Mission, $NoSubmit, $BaseTag, $Args) = @_;
- InfoMsg "\nRunning WineTest in the $Build Wine\n"; - SetupWineEnvironment($Build); + InfoMsg "\nRunning WineTest in the $Mission->{Build} Wine\n"; + SetupWineEnvironment($Mission->{Build});
# Run WineTest. Ignore the exit code since it returns non-zero whenever # there are test failures. - my $Tag = SanitizeTag("$BaseTag-$Build"); - RunWine($Build, "./programs/winetest/winetest.exe.so", - "-c -o '../$Build.report' -t $Tag ". ShArgv2Cmd(@$Args)); - if (!-f "$Build.report") + my $Tag = SanitizeTag("$BaseTag-$Mission->{Build}"); + RunWine($Mission->{Build}, "./programs/winetest/winetest.exe.so", + "-c -o '../$Mission->{Build}.report' -t $Tag ". + ShArgv2Cmd(@$Args)); + if (!-f "$Mission->{Build}.report") { LogMsg "WineTest did not produce a report file\n"; return 0; }
# Send the report to the website - if (!$NoSubmit and - RunWine($Build, "./programs/winetest/winetest.exe.so", - "-c -s '../$Build.report'")) + if ((!$NoSubmit and !$Mission->{nosubmit}) and + RunWine($Mission->{Build}, "./programs/winetest/winetest.exe.so", + "-c -s '../$Mission->{Build}.report'")) { - LogMsg "WineTest failed to send the $Build report\n"; + LogMsg "WineTest failed to send the $Mission->{Build} report\n"; # Soldier on in case it's just a network issue }
return 1; }
-sub TestPatch($$$) +sub TestPatch($$) { - my ($Targets, $Build, $Impacts) = @_; - - return 1 if (!$Targets->{"test$Build"}); + my ($Mission, $Impacts) = @_;
my @TestList; foreach my $Module (sort keys %{$Impacts->{Tests}}) @@ -131,14 +128,15 @@ sub TestPatch($$$) } return 1 if (!@TestList);
- InfoMsg "\nRunning the tests in the $Build Wine\n"; - SetupWineEnvironment($Build); + InfoMsg "\nRunning the tests in the $Mission->{Build} Wine\n"; + SetupWineEnvironment($Mission->{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 test-$Build ". join(" ", @TestList)); - if (!-f "$Build.report") + RunWine($Mission->{Build}, "./programs/winetest/winetest.exe.so", + "-c -o '../$Mission->{Build}.report' -t test-$Mission->{Build} ". + join(" ", @TestList)); + if (!-f "$Mission->{Build}.report") { LogMsg "WineTest did not produce a report file\n"; return 0; @@ -155,11 +153,8 @@ sub TestPatch($$$) $ENV{PATH} = "/usr/lib/ccache:/usr/bin:/bin"; delete $ENV{ENV};
-my %AllTargets; -map { $AllTargets{$_} = 1 } qw(win32 wow32 wow64); - my $Action = ""; -my ($Usage, $OptNoSubmit, $TargetList, $FileName, $BaseTag); +my ($Usage, $OptNoSubmit, $MissionStatement, $FileName, $BaseTag); while (@ARGV) { my $Arg = shift @ARGV; @@ -186,9 +181,9 @@ while (@ARGV) $Usage = 2; last; } - elsif (!defined $TargetList) + elsif (!defined $MissionStatement) { - $TargetList = $Arg; + $MissionStatement = $Arg; } elsif ($Action eq "winetest") { @@ -223,24 +218,35 @@ while (@ARGV) }
# Check and untaint parameters -my $Targets; +my $TaskMissions; if (!defined $Usage) { - if (defined $TargetList) + if (defined $MissionStatement) { - foreach my $Target (split /[,:]/, $TargetList) + my ($ErrMessage, $Missions) = ParseMissionStatement($MissionStatement); + if (defined $ErrMessage) { - if (!$AllTargets{$Target}) - { - Error "invalid target name $Target\n"; - $Usage = 2; - } - $Targets->{$Target} = 1; + Error "$ErrMessage\n"; + $Usage = 2; + } + elsif (!@$Missions) + { + Error "empty mission statement\n"; + $Usage = 2; + } + elsif (@$Missions > 1) + { + Error "cannot specify missions for multiple tasks\n"; + $Usage = 2; + } + else + { + $TaskMissions = $Missions->[0]; } } else { - Error "specify at least one target\n"; + Error "you must specify the mission statement\n"; $Usage = 2; }
@@ -268,16 +274,13 @@ if (!defined $Usage) } else { - foreach my $Build ("win32", "wow32", "wow64") - { - $Targets->{"test$Build"} = 1 if ($Targets->{$Build}); - } - if ($Targets->{"wow32"} or $Targets->{"wow64"}) + my $Builds = $TaskMissions->{Builds}; + if ($Builds->{"wow32"} or $Builds->{"wow64"}) { # Always rebuild both WoW targets before running the tests to make sure # we don't run into issues caused by the two Wine builds being out of # sync. - $Targets->{"wow32"} = $Targets->{"wow64"} = 1; + $Builds->{"wow32"} = $Builds->{"wow64"} = 1; } }
@@ -295,8 +298,8 @@ if (defined $Usage) Error "try '$Name0 --help' for more information\n"; exit $Usage; } - print "Usage: $Name0 [--help] --testpatch TARGETS PATCH\n"; - print "or $Name0 [--help] --winetest [--no-submit] TARGETS BASETAG ARGS\n"; + print "Usage: $Name0 [--help] --testpatch MISSIONS PATCH\n"; + print "or $Name0 [--help] --winetest [--no-submit] MISSIONS BASETAG ARGS\n"; print "\n"; print "Tests the specified patch or runs WineTest in Wine.\n"; print "\n"; @@ -304,7 +307,7 @@ if (defined $Usage) print " --testpatch Verify that the patch compiles and run the impacted tests.\n"; print " --winetest Run WineTest and submit the result to the website.\n"; print " --no-submit Do not submit the WineTest results to the website.\n"; - print " TARGETS Is a comma-separated list of targets for the specified action.\n"; + print " MISSIONS Is a colon-separated list of missions 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"; @@ -328,26 +331,26 @@ if ($DataDir =~ /'/) #
# Clean up old reports -map { unlink("$_.report") } keys %AllTargets; +map { unlink("$_.report") } keys %{$TaskMissions->{Builds}};
+my $Impacts; if ($Action eq "testpatch") { - my $Impacts = ApplyPatch("wine", $FileName); + $Impacts = ApplyPatch("wine", $FileName); exit(1) if (!$Impacts or - !BuildWine($Targets, "win32") or - !BuildWine($Targets, "wow64") or - !BuildWine($Targets, "wow32") or - !TestPatch($Targets, "win32", $Impacts) or - !TestPatch($Targets, "wow64", $Impacts) or - !TestPatch($Targets, "wow32", $Impacts)); + !BuildWine($TaskMissions, "win32") or + !BuildWine($TaskMissions, "wow64") or + !BuildWine($TaskMissions, "wow32")); } -elsif ($Action eq "winetest") +foreach my $Mission (@{$TaskMissions->{Missions}}) { - if (!DailyWineTest($Targets, "win32", $OptNoSubmit, $BaseTag, @ARGV) or - !DailyWineTest($Targets, "wow64", $OptNoSubmit, $BaseTag, @ARGV) or - !DailyWineTest($Targets, "wow32", $OptNoSubmit, $BaseTag, @ARGV)) + if ($Action eq "testpatch") { - exit(1); + exit(1) if (!TestPatch($Mission, $Impacts)); + } + elsif ($Action eq "winetest") + { + exit(1) if (!DailyWineTest($Mission, $OptNoSubmit, $BaseTag, @ARGV)); } }
diff --git a/testbot/ddl/update41.sql b/testbot/ddl/update41.sql new file mode 100644 index 0000000000..f1a5e1a240 --- /dev/null +++ b/testbot/ddl/update41.sql @@ -0,0 +1,48 @@ +USE winetestbot; + +ALTER TABLE Tasks + ADD Missions VARCHAR(256) NULL + AFTER Timeout; + +UPDATE Tasks, VMs + SET Tasks.Missions = 'build' + WHERE Tasks.Missions is NULL AND Tasks.VMName = VMs.Name AND VMs.Type = 'build'; + +UPDATE Tasks, VMs + SET Tasks.Missions = 'exe32' + WHERE Tasks.Missions is NULL AND Tasks.VMName = VMs.Name AND VMs.Type = 'win32'; + +UPDATE Tasks, VMs + SET Tasks.Missions = 'exe32|exe64' + WHERE Tasks.Missions is NULL AND Tasks.VMName = VMs.Name AND VMs.Type = 'win64'; + +UPDATE Tasks, VMs + SET Tasks.Missions = 'win32:wow64' + WHERE Tasks.Missions is NULL AND Tasks.VMName = VMs.Name AND VMs.Type = 'wine'; + +ALTER TABLE Tasks + MODIFY Missions VARCHAR(256) NOT NULL; + + +ALTER TABLE VMs + ADD Missions VARCHAR(256) NULL + AFTER Role; + +UPDATE VMs + SET Missions = 'build' + WHERE Missions is NULL AND Type = 'build'; + +UPDATE VMs + SET Missions = 'exe32' + WHERE Missions is NULL AND Type = 'win32'; + +UPDATE VMs + SET Missions = 'exe32|exe64' + WHERE Missions is NULL AND Type = 'win64'; + +UPDATE VMs + SET Missions = 'win32|wow64' + WHERE Missions is NULL AND Type = 'wine'; + +ALTER TABLE VMs + MODIFY Missions VARCHAR(256) NOT NULL; diff --git a/testbot/ddl/winetestbot.sql b/testbot/ddl/winetestbot.sql index 8862c6c9dc..e6114e0917 100644 --- a/testbot/ddl/winetestbot.sql +++ b/testbot/ddl/winetestbot.sql @@ -48,6 +48,7 @@ CREATE TABLE VMs SortOrder INT(3) NOT NULL, Type ENUM('win32', 'win64', 'build', 'wine') NOT NULL, Role ENUM('extra', 'base', 'winetest', 'retired', 'deleted') NOT NULL, + Missions VARCHAR(256) NOT NULL, Status ENUM('dirty', 'reverting', 'sleeping', 'idle', 'running', 'off', 'offline', 'maintenance') NOT NULL, Errors INT(2) NULL, ChildPid INT(5) NULL, @@ -149,6 +150,7 @@ CREATE TABLE Tasks Status ENUM('queued', 'running', 'completed', 'badpatch', 'badbuild', 'boterror', 'canceled', 'skipped') NOT NULL, VMName VARCHAR(20) NOT NULL, Timeout INT(4) NOT NULL, + Missions VARCHAR(256) NOT NULL, CmdLineArg VARCHAR(256) NULL, Started DATETIME NULL, Ended DATETIME NULL, diff --git a/testbot/doc/winetestbot-schema.dia b/testbot/doc/winetestbot-schema.dia index c54897a831..372d3bf006 100644 --- a/testbot/doc/winetestbot-schema.dia +++ b/testbot/doc/winetestbot-schema.dia @@ -2106,6 +2106,29 @@ dia:string##</dia:string> </dia:attribute> </dia:composite> + <dia:composite type="table_attribute"> + <dia:attribute name="name"> + dia:string#Missions#</dia:string> + </dia:attribute> + <dia:attribute name="type"> + dia:string#VARCHAR(256)#</dia:string> + </dia:attribute> + <dia:attribute name="comment"> + dia:string##</dia:string> + </dia:attribute> + <dia:attribute name="primary_key"> + <dia:boolean val="false"/> + </dia:attribute> + <dia:attribute name="nullable"> + <dia:boolean val="false"/> + </dia:attribute> + <dia:attribute name="unique"> + <dia:boolean val="false"/> + </dia:attribute> + <dia:attribute name="default_value"> + dia:string##</dia:string> + </dia:attribute> + </dia:composite> <dia:composite type="table_attribute"> <dia:attribute name="name"> dia:string#CmdLineArg#</dia:string> @@ -2360,6 +2383,29 @@ dia:string##</dia:string> </dia:attribute> </dia:composite> + <dia:composite type="table_attribute"> + <dia:attribute name="name"> + dia:string#Missions#</dia:string> + </dia:attribute> + <dia:attribute name="type"> + dia:string#VARCHAR(256)#</dia:string> + </dia:attribute> + <dia:attribute name="comment"> + dia:string##</dia:string> + </dia:attribute> + <dia:attribute name="primary_key"> + <dia:boolean val="false"/> + </dia:attribute> + <dia:attribute name="nullable"> + <dia:boolean val="false"/> + </dia:attribute> + <dia:attribute name="unique"> + <dia:boolean val="false"/> + </dia:attribute> + <dia:attribute name="default_value"> + dia:string##</dia:string> + </dia:attribute> + </dia:composite> <dia:composite type="table_attribute"> <dia:attribute name="name"> dia:string#Status#</dia:string> diff --git a/testbot/lib/WineTestBot/Missions.pm b/testbot/lib/WineTestBot/Missions.pm new file mode 100644 index 0000000000..58b33403fb --- /dev/null +++ b/testbot/lib/WineTestBot/Missions.pm @@ -0,0 +1,99 @@ +# -*- Mode: Perl; perl-indent-level: 2; indent-tabs-mode: nil -*- +# Copyright 2018 Francois Gouget +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +use strict; + +package WineTestBot::Missions; + +=head1 NAME + +WineTestBot::Missions - Missions parser and helper functions + +=cut + +use Exporter 'import'; +our @EXPORT = qw(DumpMissions ParseMissionStatement + MergeMissionStatementTasks SplitMissionStatementTasks); + + +sub DumpMissions($$) +{ + my ($Label, $Missions) = @_; + + print STDERR "$Label:\n"; + foreach my $TaskMissions (@$Missions) + { + print STDERR "Builds=", join(",", sort keys %{$TaskMissions->{Builds}}), "\n"; + foreach my $Mission (@{$TaskMissions->{Missions}}) + { + print STDERR " [$Mission->{Build}]\n"; + print STDERR " "$_"="$Mission->{$_}"\n" for (sort grep(!/^Build$/,keys %$Mission)); + } + } +} + +sub ParseMissionStatement($) +{ + my ($MissionStatement) = @_; + + my @Missions; + foreach my $TaskStatement (split /[|]/, $MissionStatement) + { + my $TaskMissions = { Statement => $TaskStatement }; + push @Missions, $TaskMissions; + foreach my $Statement (split /:/, $TaskStatement) + { + my ($Build, @Options) = split /,/, $Statement; + if ($Build !~ /^([a-z0-9]+)$/) + { + return ("Invalid mission name '$Build'", undef); + } + $Build = $1; # untaint + $TaskMissions->{Builds}->{$Build} = 1; + my $Mission = { Build => $Build, Statement => $Statement }; + push @{$TaskMissions->{Missions}}, $Mission; + + foreach my $Option (@Options) + { + if ($Option !~ s/^([a-z0-9_]+)//) + { + return ("Invalid option name '$Option'", undef); + } + my $Name = $1; # untaint + # do not untaint the value + $Mission->{$Name} = ($Option =~ s/^=//) ? $Option : 1; + } + } + } + return (undef, @Missions); +} + +sub MergeMissionStatementTasks($) +{ + my ($MissionStatement) = @_; + $MissionStatement =~ s/|/:/g; + return $MissionStatement; +} + +sub SplitMissionStatementTasks($) +{ + my ($MissionStatement) = @_; + $MissionStatement =~ s/:/|/g; + return $MissionStatement; +} + +1; diff --git a/testbot/lib/WineTestBot/PatchUtils.pm b/testbot/lib/WineTestBot/PatchUtils.pm index 7df50f9b21..444ffaf491 100644 --- a/testbot/lib/WineTestBot/PatchUtils.pm +++ b/testbot/lib/WineTestBot/PatchUtils.pm @@ -457,10 +457,10 @@ sub GetPatchImpacts($)
sub GetBuildTimeout($$) { - my ($Impacts, $Builds) = @_; + my ($Impacts, $TaskMissions) = @_;
my ($ExeCount, $WineCount); - map {$_ =~ /^exe/ ? $ExeCount++ : $WineCount++ } keys %$Builds; + map {$_ =~ /^exe/ ? $ExeCount++ : $WineCount++ } keys %{$TaskMissions->{Builds}};
# Set $ModuleCount to 0 if a full rebuild is needed my $ModuleCount = (!$Impacts or $Impacts->{RebuildRoot}) ? 0 : @@ -486,7 +486,7 @@ sub GetBuildTimeout($$)
sub GetTestTimeout($$) { - my ($Impacts, $Builds) = @_; + my ($Impacts, $TaskMissions) = @_;
my $Timeout = $SuiteTimeout; if ($Impacts) @@ -496,7 +496,7 @@ sub GetTestTimeout($$) max(0, $UnitCount - 2) * $SingleAvgTime; $Timeout = min($SuiteTimeout, $TestsTimeout); } - return scalar(keys %$Builds) * $Timeout; + return @{$TaskMissions->{Missions}} * $Timeout; }
1; diff --git a/testbot/lib/WineTestBot/Patches.pm b/testbot/lib/WineTestBot/Patches.pm index f685e86b32..e621e8845d 100644 --- a/testbot/lib/WineTestBot/Patches.pm +++ b/testbot/lib/WineTestBot/Patches.pm @@ -40,12 +40,13 @@ use Encode qw/decode/; use File::Basename;
use WineTestBot::Config; +use WineTestBot::Engine::Notify; use WineTestBot::Jobs; +use WineTestBot::Missions; use WineTestBot::PatchUtils; use WineTestBot::Users; use WineTestBot::Utils; use WineTestBot::VMs; -use WineTestBot::Engine::Notify;
sub InitializeNew($$) @@ -175,23 +176,8 @@ sub Submit($$$) $BuildVMs->AddFilter("Role", ["base"]); if ($Impacts->{TestUnitCount} and !$BuildVMs->IsEmpty()) { - # Create the Build Step - my $BuildStep = $NewJob->Steps->Add(); - $BuildStep->FileName("patch.diff"); - $BuildStep->FileType("patch"); - $BuildStep->Type("build"); - $BuildStep->DebugLevel(0); - - # Save the build step so the others can reference it. - my ($ErrKey, $ErrProperty, $ErrMessage) = $Jobs->Save(); - if (defined($ErrMessage)) - { - $self->Disposition("Failed to submit build step"); - return $ErrMessage; - } - # Create steps for the Windows tests - my $Builds; + my ($BuildStep, $BuildMissions); foreach my $Module (sort keys %{$Impacts->{Tests}}) { my $TestInfo = $Impacts->{Tests}->{$Module}; @@ -202,26 +188,54 @@ sub Submit($$$) my $WinVMs = CreateVMs(); $WinVMs->AddFilter("Type", $Bits eq "32" ? ["win32", "win64"] : ["win64"]); $WinVMs->AddFilter("Role", ["base"]); - if (!$WinVMs->IsEmpty()) + my $SortedKeys = $WinVMs->SortKeysBySortOrder($WinVMs->GetKeys()); + + my $Tasks; + foreach my $VMKey (@$SortedKeys) { - # Create one Step per (module, unit, bitness) combination - my $NewStep = $NewJob->Steps->Add(); - $NewStep->PreviousNo($BuildStep->No); - my $FileName = $TestInfo->{ExeBase}; - $FileName .= "64" if ($Bits eq "64"); - $NewStep->FileName("$FileName.exe"); - $NewStep->FileType("exe$Bits"); - $Builds->{"exe$Bits"} = 1; - - # And a task for each VM - my $Tasks = $NewStep->Tasks; - my $SortedKeys = $WinVMs->SortKeysBySortOrder($WinVMs->GetKeys()); - foreach my $VMKey (@$SortedKeys) + my $VM = $WinVMs->GetItem($VMKey); + my ($ErrMessage, $Missions) = ParseMissionStatement($VM->Missions); + next if (defined $ErrMessage); + + foreach my $TaskMissions (@$Missions) { - my $VM = $WinVMs->GetItem($VMKey); + next if (!$TaskMissions->{Builds}->{"exe$Bits"}); + + if (!$BuildStep) + { + # Create the Build Step + $BuildStep = $NewJob->Steps->Add(); + $BuildStep->FileName("patch.diff"); + $BuildStep->FileType("patch"); + $BuildStep->Type("build"); + $BuildStep->DebugLevel(0); + + # Save the build step so the others can reference it. + my ($ErrKey, $ErrProperty, $ErrMessage) = $Jobs->Save(); + if (defined($ErrMessage)) + { + $self->Disposition("Failed to submit build step"); + return $ErrMessage; + } + } + + if (!$Tasks) + { + # Create one Step per (module, unit, bitness) combination + my $NewStep = $NewJob->Steps->Add(); + $NewStep->PreviousNo($BuildStep->No); + my $FileName = $TestInfo->{ExeBase}; + $FileName .= "64" if ($Bits eq "64"); + $NewStep->FileName("$FileName.exe"); + $NewStep->FileType("exe$Bits"); + $BuildMissions->{Builds}->{"exe$Bits"} = 1; + $Tasks = $NewStep->Tasks; + } + my $Task = $Tasks->Add(); $Task->VM($VM); $Task->Timeout($SingleTimeout); + $Task->Missions($TaskMissions->{Statement}); $Task->CmdLineArg($Unit); } } @@ -229,38 +243,48 @@ sub Submit($$$) } }
- # Add the build task - my $BuildVM = ${$BuildVMs->GetItems()}[0]; - my $BuildTask = $BuildStep->Tasks->Add(); - $BuildTask->VM($BuildVM); - $BuildTask->Timeout(GetBuildTimeout($Impacts, $Builds)); + if ($BuildStep) + { + # Add the build task + my $BuildVM = ${$BuildVMs->GetItems()}[0]; + my $BuildTask = $BuildStep->Tasks->Add(); + $BuildTask->VM($BuildVM); + $BuildMissions->{Statement} = join(":", keys %{$BuildMissions->{Builds}}); + $BuildTask->Timeout(GetBuildTimeout($Impacts, $BuildMissions)); + $BuildTask->Missions($BuildMissions->{Statement}); + } }
my $WineVMs = CreateVMs(); $WineVMs->AddFilter("Type", ["wine"]); $WineVMs->AddFilter("Role", ["base"]); - if (!$WineVMs->IsEmpty()) + my $SortedKeys = $WineVMs->SortKeysBySortOrder($WineVMs->GetKeys()); + + my $Tasks; + foreach my $VMKey (@$SortedKeys) { - # Add a Wine step to the job - my $NewStep = $NewJob->Steps->Add(); - $NewStep->FileName("patch.diff"); - $NewStep->FileType("patch"); - $NewStep->Type("single"); - $NewStep->DebugLevel(0); - - # And a task for each VM - my $Tasks = $NewStep->Tasks; - my $SortedKeys = $WineVMs->SortKeysBySortOrder($WineVMs->GetKeys()); - foreach my $VMKey (@$SortedKeys) + my $VM = $WineVMs->GetItem($VMKey); + my ($ErrMessage, $Missions) = ParseMissionStatement($VM->Missions); + next if (defined $ErrMessage); + + foreach my $TaskMissions (@$Missions) { - my $VM = $WineVMs->GetItem($VMKey); + if (!$Tasks) + { + # Add a Wine step to the job + my $TestStep = $NewJob->Steps->Add(); + $TestStep->FileName("patch.diff"); + $TestStep->FileType("patch"); + $TestStep->Type("single"); + $TestStep->DebugLevel(0); + $Tasks = $TestStep->Tasks; + } + my $Task = $Tasks->Add(); $Task->VM($VM); - # Only verify that the win32 version compiles - my $Builds = { "win32" => 1 }; - $Task->Timeout(GetBuildTimeout($Impacts, $Builds) + - GetTestTimeout($Impacts, $Builds)); - $Task->CmdLineArg(join(",", keys %$Builds)); + $Task->Timeout(GetBuildTimeout($Impacts, $TaskMissions) + + GetTestTimeout($Impacts, $TaskMissions)); + $Task->Missions($TaskMissions->{Statement}); } }
diff --git a/testbot/lib/WineTestBot/StepsTasks.pm b/testbot/lib/WineTestBot/StepsTasks.pm index a50f61ada3..cfd78e52e2 100644 --- a/testbot/lib/WineTestBot/StepsTasks.pm +++ b/testbot/lib/WineTestBot/StepsTasks.pm @@ -30,6 +30,7 @@ use WineTestBot::WineTestBotObjects; our @ISA = qw(WineTestBot::WineTestBotItem);
use WineTestBot::Config; +use WineTestBot::Missions;
sub GetStepDir($) @@ -68,34 +69,50 @@ sub GetTitle($) { my ($self) = @_;
- my $Title = ""; - if ($self->Type eq "single") + my @TitleParts; + if ($self->Type eq "build") { - if ($self->FileType eq "exe32") - { - $Title .= "32 bit "; - } - elsif ($self->FileType eq "exe64") - { - $Title .= "64 bit "; - } - $Title .= $self->CmdLineArg || ""; + push @TitleParts, "build"; } - elsif ($self->Type eq "build") + elsif ($self->FileType eq "exe32") { - $Title = "build"; + push @TitleParts, "32 bit"; } - $Title =~ s/\s*$//; - - if ($Title) + elsif ($self->FileType eq "exe64") { - $Title = $self->VM->Name . " (" . $Title . ")"; + push @TitleParts, "64 bit"; } else { - $Title = $self->VM->Name; + my ($ErrMessage, $Missions) = ParseMissionStatement($self->Missions); + if (!defined $ErrMessage and @$Missions == 1) + { + my $Builds = $Missions->[0]->{Builds}; + if ($Builds->{build}) + { + push @TitleParts, "build"; + } + elsif ($Builds->{wow64} and ($Builds->{win32} or $Builds->{wow32})) + { + push @TitleParts, "32 & 64 bit"; + } + elsif ($Builds->{win32} or $Builds->{wow32}) + { + push @TitleParts, "32 bit"; + } + elsif ($Builds->{wow64}) + { + push @TitleParts, "64 bit"; + } + } + } + if ($self->Type ne "suite" and $self->CmdLineArg) + { + push @TitleParts, $self->CmdLineArg; }
+ my $Title = $self->VM->Name; + $Title .= " (@TitleParts)" if (@TitleParts); return $Title; }
@@ -148,6 +165,7 @@ sub _initialize($$) $StepTask->Timeout($Task->Timeout); $StepTask->FileName($Step->FileName); $StepTask->FileType($Step->FileType); + $StepTask->Missions($Task->Missions); $StepTask->CmdLineArg($Task->CmdLineArg); $StepTask->Started($Task->Started); $StepTask->Ended($Task->Ended); @@ -179,6 +197,7 @@ my @PropertyDescriptors = ( CreateBasicPropertyDescriptor("Timeout", "Timeout", !1, 1, "N", 4), CreateBasicPropertyDescriptor("FileName", "File name", !1, !1, "A", 100), CreateBasicPropertyDescriptor("FileType", "File Type", !1, 1, "A", 32), + CreateBasicPropertyDescriptor("Missions", "Missions", !1, 1, "A", 256), CreateBasicPropertyDescriptor("CmdLineArg", "Command line args", !1, !1, "A", 256), CreateBasicPropertyDescriptor("Started", "Execution started", !1, !1, "DT", 19), CreateBasicPropertyDescriptor("Ended", "Execution ended", !1, !1, "DT", 19), diff --git a/testbot/lib/WineTestBot/Tasks.pm b/testbot/lib/WineTestBot/Tasks.pm index 084784a3ba..f9e84d471b 100644 --- a/testbot/lib/WineTestBot/Tasks.pm +++ b/testbot/lib/WineTestBot/Tasks.pm @@ -298,6 +298,7 @@ my @PropertyDescriptors = ( CreateEnumPropertyDescriptor("Status", "Status", !1, 1, ['queued', 'running', 'completed', 'badpatch', 'badbuild', 'boterror', 'canceled', 'skipped']), CreateItemrefPropertyDescriptor("VM", "VM", !1, 1, &CreateVMs, ["VMName"]), CreateBasicPropertyDescriptor("Timeout", "Timeout", !1, 1, "N", 4), + CreateBasicPropertyDescriptor("Missions", "Missions", !1, 1, "A", 256), CreateBasicPropertyDescriptor("CmdLineArg", "Command line args", !1, !1, "A", 256), CreateBasicPropertyDescriptor("Started", "Execution started", !1, !1, "DT", 19), CreateBasicPropertyDescriptor("Ended", "Execution ended", !1, !1, "DT", 19), diff --git a/testbot/lib/WineTestBot/VMs.pm b/testbot/lib/WineTestBot/VMs.pm index cf7bd5ab75..e7cd277494 100644 --- a/testbot/lib/WineTestBot/VMs.pm +++ b/testbot/lib/WineTestBot/VMs.pm @@ -151,6 +151,7 @@ use ObjectModel::BackEnd; use WineTestBot::Config; use WineTestBot::Engine::Notify; use WineTestBot::LibvirtDomain; +use WineTestBot::Missions; use WineTestBot::RecordGroups; use WineTestBot::TestAgent;
@@ -302,6 +303,24 @@ sub KillChild($) $self->ChildPid(undef); }
+sub PutColValue($$$) +{ + my ($self, $ColName, $Value) = @_; + + $self->SUPER::PutColValue($ColName, $Value); + if ($self->{IsModified} and ($ColName eq "Type" or $ColName eq "Missions")) + { + $self->{ValidateMissions} = 1; + } +} + +my $_SupportedMissions = { + "build" => { "build" => 1 }, + "win32" => { "exe32" => 1 }, + "win64" => { "exe32" => 1, "exe64" => 1 }, + "wine" => { "win32" => 1, "wow32" => 1, "wow64" => 1 }, +}; + sub Validate($) { my ($self) = @_; @@ -311,6 +330,27 @@ sub Validate($) { return ("Role", "Only win32, win64 and wine VMs can have a role of '" . $self->Role . "'"); } + if ($self->{ValidateMissions}) + { + my ($ErrMessage, $Missions) = ParseMissionStatement($self->Missions); + return ("Missions", $ErrMessage) if (defined $ErrMessage); + foreach my $TaskMissions (@$Missions) + { + if ($self->Type ne "wine" and @{$TaskMissions->{Missions}} > 1) + { + return ("Missions", "Only wine VMs can handle more than one mission per task"); + } + foreach my $Mission (@{$TaskMissions->{Missions}}) + { + if (!$_SupportedMissions->{$self->Type}->{$Mission->{Build}}) + { + return ("Missions", ucfirst($self->Type) ." VMs only support ". join(", ", sort keys %{$_SupportedMissions->{$self->Type}}) ." missions"); + } + } + } + delete $self->{ValidateMissions}; + } + return $self->SUPER::Validate(); }
@@ -666,6 +706,7 @@ my @PropertyDescriptors = ( CreateBasicPropertyDescriptor("SortOrder", "Display order", !1, 1, "N", 3), CreateEnumPropertyDescriptor("Type", "Type of VM", !1, 1, ['win32', 'win64', 'build', 'wine']), CreateEnumPropertyDescriptor("Role", "VM Role", !1, 1, ['extra', 'base', 'winetest', 'retired', 'deleted']), + CreateBasicPropertyDescriptor("Missions", "Missions", !1, 1, "A", 256), CreateEnumPropertyDescriptor("Status", "Current status", !1, 1, ['dirty', 'reverting', 'sleeping', 'idle', 'running', 'off', 'offline', 'maintenance']), CreateBasicPropertyDescriptor("Errors", "Errors", !1, !1, "N", 2), CreateBasicPropertyDescriptor("ChildPid", "Child process id", !1, !1, "N", 5), diff --git a/testbot/web/JobDetails.pl b/testbot/web/JobDetails.pl index 8efe5eefcf..de2f9ccb40 100644 --- a/testbot/web/JobDetails.pl +++ b/testbot/web/JobDetails.pl @@ -398,6 +398,7 @@ EOF print "<details><summary>", $self->CGI->escapeHTML($VM->Description || $VM->Name), "</summary>", $self->CGI->escapeHTML($VM->Details || "No details!"), + ($StepTask->Missions ? "<br>Missions: ". $StepTask->Missions : ""), "</details>\n";
my $MoreInfo = $self->{More}->{$Key}; diff --git a/testbot/web/Submit.pl b/testbot/web/Submit.pl index c10a267909..bcb5458ad5 100644 --- a/testbot/web/Submit.pl +++ b/testbot/web/Submit.pl @@ -36,6 +36,7 @@ use WineTestBot::Branches; use WineTestBot::Config; use WineTestBot::Engine::Notify; use WineTestBot::Jobs; +use WineTestBot::Missions; use WineTestBot::PatchUtils; use WineTestBot::Steps; use WineTestBot::Utils; @@ -814,13 +815,18 @@ sub SubmitJob($$$) my $Task = $BuildStep->Tasks->Add(); $Task->VM($BuildVM);
- my $Builds = { "exe32" => 1 }; - $Builds->{"exe64"} = 1 if defined $self->GetParam("Run64"); - $Task->Timeout(GetBuildTimeout($Impacts, $Builds)); + my $MissionStatement = "exe32"; + $MissionStatement .= ":exe64" if (defined $self->GetParam("Run64")); + my ($ErrMessage, $Missions) = ParseMissionStatement($MissionStatement); + if (!defined $ErrMessage) + { + $Task->Timeout(GetBuildTimeout($Impacts, $Missions->[0])); + $Task->Missions($Missions->[0]);
- # Save the build step so the others can reference it - my ($ErrKey, $ErrProperty, $ErrMessage) = $Jobs->Save(); - if (defined($ErrMessage)) + # Save the build step so the others can reference it + (my $ErrKey, my $ErrProperty, $ErrMessage) = $Jobs->Save(); + } + if (defined $ErrMessage) { $self->{ErrMessage} = $ErrMessage; return !1; @@ -851,51 +857,53 @@ sub SubmitJob($$$) my $Task = $Tasks->Add(); $Task->VM($VM); $Task->Timeout($SingleTimeout); + $Task->Missions("exe$Bits"); $Task->CmdLineArg($self->GetParam("CmdLineArg")); } }
if ($FileType eq "patch") { - my $Tasks; + my ($Tasks, $MissionStatement, $Timeout); my $VMs = CreateVMs(); $VMs->AddFilter("Type", ["wine"]); my $SortedKeys = $VMs->SortKeysBySortOrder($VMs->GetKeys()); - foreach my $Build ("win32", "wow64") + foreach my $VMKey (@$SortedKeys) { - next if ($Build eq "wow64" and !defined($self->GetParam("Run64"))); + my $VM = $VMs->GetItem($VMKey); + my $FieldName = "vm_" . $self->CGI->escapeHTML($VMKey); + next if (!$self->GetParam($FieldName)); # skip unselected VMs
- my $Timeout; - foreach my $VMKey (@$SortedKeys) + if (!$Tasks) { - my $VM = $VMs->GetItem($VMKey); - my $FieldName = "vm_" . $self->CGI->escapeHTML($VMKey); - next if (!$self->GetParam($FieldName)); # skip unselected VMs - - if (!$Tasks) + # First create the Wine test step + my $WineStep = $Steps->Add(); + $WineStep->FileName($BaseName); + $WineStep->FileType($FileType); + $WineStep->Type("single"); + $WineStep->DebugLevel($self->GetParam("DebugLevel")); + $WineStep->ReportSuccessfulTests(defined($self->GetParam("ReportSuccessfulTests"))); + $Tasks = $WineStep->Tasks; + + $MissionStatement = "win32"; + $MissionStatement.= ":wow64" if (defined $self->GetParam("Run64")); + + my ($ErrMessage, $Missions) = ParseMissionStatement($MissionStatement); + if (defined $ErrMessage) { - # First create the Wine test step - my $WineStep = $Steps->Add(); - $WineStep->FileName($BaseName); - $WineStep->FileType($FileType); - $WineStep->Type("single"); - $WineStep->DebugLevel($self->GetParam("DebugLevel")); - $WineStep->ReportSuccessfulTests(defined($self->GetParam("ReportSuccessfulTests"))); - $Tasks = $WineStep->Tasks; + $self->{ErrMessage} = $ErrMessage; + return !1; } - if (!defined $Timeout) - { - my $Builds = { $Build => 1 }; - $Timeout = GetBuildTimeout($Impacts, $Builds) + - GetTestTimeout($Impacts, $Builds); - } - - # Then add a task for this VM - my $Task = $Tasks->Add(); - $Task->VM($VM); - $Task->CmdLineArg($Build); - $Task->Timeout($Timeout); + $Missions = $Missions->[0]; + $Timeout = GetBuildTimeout($Impacts, $Missions) + + GetTestTimeout($Impacts, $Missions); } + + # Then add a task for this VM + my $Task = $Tasks->Add(); + $Task->VM($VM); + $Task->Timeout($Timeout); + $Task->Missions($MissionStatement); } }
diff --git a/testbot/web/admin/VMDetails.pl b/testbot/web/admin/VMDetails.pl index 9603e346a8..5f49c684d8 100644 --- a/testbot/web/admin/VMDetails.pl +++ b/testbot/web/admin/VMDetails.pl @@ -67,6 +67,22 @@ sub Save($) return ! defined($self->{ErrMessage}); }
+sub GenerateFooter($) +{ + my ($self) = @_; + print "<p></p><div class='CollectionBlock'><table>\n"; + print "<thead><tr><th class='Record'>Legend</th></tr></thead>\n"; + print "<tbody><tr><td class='Record'>\n"; + + print "<p>The Missions syntax is <i>mission1:mission2:...|mission3|...</i> where <i>mission1</i> and <i>mission2</i> will be run in the same task, and <i>mission3</i> in a separate task.<br>\n"; + print "Each mission is composed of a build and options separated by commas: <i>build,option1=value,option2,...</i>. The value can be omitted for boolean options and defaults to true.<br>\n"; + print "The supported builds are <i>build</i> for build VMs; <i>exe32</i> and <i>exe64</i> for Windows VMs;<i> win32</i>, <i>wow32</i> and <i>wow64</i> for Wine VMs.</p>\n"; + print "<p>On Wine VMs:<br>\n"; + print "If set, the <i>nosubmit</i> option specifies that the WineTest results should not be published online.</p>\n"; + print "</td></tr></tbody>\n"; + print "</table></div>\n"; + $self->SUPER::GenerateFooter(); +}
package main;
This makes it possible to specify mission options on the command line if necessary.
Signed-off-by: Francois Gouget fgouget@codeweavers.com --- testbot/bin/WineRunBuild.pl | 6 ++-- testbot/bin/build/Build.pl | 59 +++++++++++++++++++++---------------- 2 files changed, 36 insertions(+), 29 deletions(-)
diff --git a/testbot/bin/WineRunBuild.pl b/testbot/bin/WineRunBuild.pl index ec36b629a6..21d2e8b0a2 100755 --- a/testbot/bin/WineRunBuild.pl +++ b/testbot/bin/WineRunBuild.pl @@ -339,13 +339,13 @@ if ($Step->FileType ne "patch") # Figure out what to build #
-my (%Builds, %TestExes); +my (%Missions, %TestExes); foreach my $TestStep (@{$Job->Steps->GetItems()}) { if (($TestStep->PreviousNo || 0) == $Step->No and $TestStep->FileType =~ /^exe/) { - $Builds{$TestStep->FileType} = 1; + $Missions{$TestStep->FileType} = 1; $TestExes{$TestStep->FileName} = $TestStep->FileType; } } @@ -364,7 +364,7 @@ if (!$TA->SendFile($FileName, "staging/patch.diff", 0)) } my $Script = "#!/bin/sh\n". "( set -x\n". - " ../bin/build/Build.pl patch.diff ". join(":", sort keys %Builds) ."\n". + " ../bin/build/Build.pl patch.diff ". join(":", sort keys %Missions) ."\n". ") >Build.log 2>&1\n"; Debug(Elapsed($Start), " Sending the script: [$Script]\n"); if (!$TA->SendFileFromString($Script, "task", $TestAgent::SENDFILE_EXE)) diff --git a/testbot/bin/build/Build.pl b/testbot/bin/build/Build.pl index 54b1aeae12..b50a25ca0b 100755 --- a/testbot/bin/build/Build.pl +++ b/testbot/bin/build/Build.pl @@ -46,6 +46,7 @@ sub BEGIN
use Build::Utils; use WineTestBot::Config; +use WineTestBot::Missions; use WineTestBot::Utils;
@@ -71,9 +72,9 @@ sub BuildNative()
sub BuildTestExecutables($$$) { - my ($Targets, $Impacts, $Build) = @_; + my ($TaskMissions, $Impacts, $Build) = @_;
- return 1 if (!$Targets->{$Build}); + return 1 if (!$TaskMissions->{Builds}->{$Build});
my (@BuildDirs, @TestExes); foreach my $TestInfo (values %{$Impacts->{Tests}}) @@ -116,10 +117,7 @@ sub BuildTestExecutables($$$) $ENV{PATH} = "/usr/lib/ccache:/usr/bin:/bin"; delete $ENV{ENV};
-my %AllTargets; -map { $AllTargets{$_} = 1 } qw(exe32 exe64); - -my ($Usage, $PatchFile, $TargetList); +my ($Usage, $PatchFile, $MissionStatement); while (@ARGV) { my $Arg = shift @ARGV; @@ -152,9 +150,9 @@ while (@ARGV) last; } } - elsif (!defined $TargetList) + elsif (!defined $MissionStatement) { - $TargetList = $Arg; + $MissionStatement = $Arg; } else { @@ -165,7 +163,7 @@ while (@ARGV) }
# Check and untaint parameters -my $Targets; +my $TaskMissions; if (!defined $Usage) { if (!defined $PatchFile) @@ -174,17 +172,26 @@ if (!defined $Usage) $Usage = 2; }
- $TargetList = join(",", keys %AllTargets) if (!defined $TargetList); - foreach my $Target (split /[,:]/, $TargetList) + $MissionStatement = "exe32:exe64" if (!defined $MissionStatement); + my ($ErrMessage, $Missions) = ParseMissionStatement($MissionStatement); + if (defined $ErrMessage) { - $Target = "exe$1" if ($Target =~ /^(32|64)$/); - if (!$AllTargets{$Target}) - { - Error "invalid target name $Target\n"; - $Usage = 2; - last; - } - $Targets->{$Target} = 1; + Error "$ErrMessage\n"; + $Usage = 2; + } + elsif (!@$Missions) + { + Error "Empty mission statement\n"; + $Usage = 2; + } + elsif (@$Missions > 1) + { + Error "Cannot specify missions for multiple tasks\n"; + $Usage = 2; + } + else + { + $TaskMissions = $Missions->[0]; } } if (defined $Usage) @@ -195,16 +202,16 @@ if (defined $Usage) Error "try '$Name0 --help' for more information\n"; exit $Usage; } - print "Usage: $Name0 [--help] PATCHFILE TARGETS\n"; + print "Usage: $Name0 [--help] PATCHFILE [MISSIONS]\n"; print "\n"; print "Applies the specified patch and rebuilds the Wine test executables.\n"; print "\n"; print "Where:\n"; print " PATCHFILE Is the staging file containing the patch to build.\n"; - print " TARGETS Is a comma-separated list of build targets. By default every\n"; - print " target is run.\n"; - print " - exe32: Rebuild the 32 bit Windows test executables.\n"; - print " - exe64: Rebuild the 64 bit Windows test executables.\n"; + print " MISSIONS Is a colon-separated list of missions. By default all supported\n"; + print " missions are run.\n"; + print " - exe32: Build the 32 bit Windows test executables.\n"; + print " - exe64: Build the 64 bit Windows test executables.\n"; print " --help Shows this usage message.\n"; exit 0; } @@ -224,8 +231,8 @@ my $Impacts = ApplyPatch("wine", $PatchFile);
if (!$Impacts or ($Impacts->{PatchedRoot} and !BuildNative()) or - !BuildTestExecutables($Targets, $Impacts, "exe32") or - !BuildTestExecutables($Targets, $Impacts, "exe64")) + !BuildTestExecutables($TaskMissions, $Impacts, "exe32") or + !BuildTestExecutables($TaskMissions, $Impacts, "exe64")) { exit(1); }
This makes it possible to specify the mission options on the command line if necessary.
Signed-off-by: Francois Gouget fgouget@codeweavers.com --- testbot/bin/build/WineReconfig.pl | 72 +++++++++++++++++-------------- 1 file changed, 40 insertions(+), 32 deletions(-)
diff --git a/testbot/bin/build/WineReconfig.pl b/testbot/bin/build/WineReconfig.pl index 63d72e89d3..74fe4eab81 100755 --- a/testbot/bin/build/WineReconfig.pl +++ b/testbot/bin/build/WineReconfig.pl @@ -42,6 +42,7 @@ use File::Path;
use Build::Utils; use WineTestBot::Config; +use WineTestBot::Missions;
# @@ -50,9 +51,9 @@ use WineTestBot::Config;
sub BuildWine($$$$) { - my ($Targets, $NoRm, $Build, $Extras) = @_; + my ($TaskMissions, $NoRm, $Build, $Extras) = @_;
- return 1 if (!$Targets->{$Build}); + return 1 if (!$TaskMissions->{Builds}->{$Build}); # FIXME Temporary code to ensure compatibility during the transition my $OldDir = "build-$Build"; if (-d "$DataDir/$OldDir" and !-d "$DataDir/wine-$Build") @@ -82,11 +83,11 @@ sub BuildWine($$$$)
sub UpdateWineBuilds($$) { - my ($Targets, $NoRm) = @_; + my ($TaskMissions, $NoRm) = @_;
- return BuildWine($Targets, $NoRm, "win32", "") && - BuildWine($Targets, $NoRm, "wow64", "--enable-win64") && - BuildWine($Targets, $NoRm, "wow32", "--with-wine64='$DataDir/wine-wow64'"); + return BuildWine($TaskMissions, $NoRm, "win32", "") && + BuildWine($TaskMissions, $NoRm, "wow64", "--enable-win64") && + BuildWine($TaskMissions, $NoRm, "wow32", "--with-wine64='$DataDir/wine-wow64'"); }
@@ -96,7 +97,7 @@ sub UpdateWineBuilds($$)
sub UpdateWinePrefixes($) { - my ($Targets) = @_; + my ($TaskMissions) = @_;
# Make sure no obsolete wineprefix is left behind in case WineReconfig # is called with a different set of targets @@ -113,7 +114,7 @@ sub UpdateWinePrefixes($) # time. Note that this requires using a different wineprefix for each build. foreach my $Build ("win32", "wow64", "wow32") { - next if (!$Targets->{$Build}); + next if (!$TaskMissions->{Builds}->{$Build});
# Wait for the wineprefix creation to complete so it is really done # before the snapshot gets updated. @@ -136,10 +137,7 @@ sub UpdateWinePrefixes($) $ENV{PATH} = "/usr/lib/ccache:/usr/bin:/bin"; delete $ENV{ENV};
-my %AllTargets; -map { $AllTargets{$_} = 1 } qw(win32 wow32 wow64); - -my ($Usage, $OptUpdate, $OptBuild, $OptNoRm, $OptAddOns, $OptWinePrefix, $TargetList); +my ($Usage, $OptUpdate, $OptBuild, $OptNoRm, $OptAddOns, $OptWinePrefix, $MissionStatement); while (@ARGV) { my $Arg = shift @ARGV; @@ -174,9 +172,9 @@ while (@ARGV) $Usage = 2; last; } - elsif (!defined $TargetList) + elsif (!defined $MissionStatement) { - $TargetList = $Arg; + $MissionStatement = $Arg; } else { @@ -187,23 +185,33 @@ while (@ARGV) }
# Check and untaint parameters -my $Targets; +my $TaskMissions; if (!defined $Usage) { if (!$OptUpdate and !$OptBuild and !$OptAddOns and !$OptWinePrefix) { $OptUpdate = $OptBuild = $OptAddOns = $OptWinePrefix = 1; } - $TargetList = join(",", keys %AllTargets) if (!defined $TargetList); - foreach my $Target (split /,/, $TargetList) + $MissionStatement ||= "win32:wow32:wow64"; + my ($ErrMessage, $Missions) = ParseMissionStatement($MissionStatement); + if (defined $ErrMessage) { - if (!$AllTargets{$Target}) - { - Error "invalid target name $Target\n"; - $Usage = 2; - last; - } - $Targets->{$Target} = 1; + Error "$ErrMessage\n"; + $Usage = 2; + } + elsif (!@$Missions) + { + Error "Empty mission statement\n"; + $Usage = 2; + } + elsif (@$Missions > 1) + { + Error "Cannot specify missions for multiple tasks\n"; + $Usage = 2; + } + else + { + $TaskMissions = $Missions->[0]; } } if (defined $Usage) @@ -215,7 +223,7 @@ if (defined $Usage) exit $Usage; } print "Usage: $Name0 [--update] [--build [--no-rm]] [--addons] [--wineprefix]\n"; - print " [--help] [TARGETS]\n"; + print " [--help] [MISSIONS]\n"; print "\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"; @@ -225,11 +233,11 @@ if (defined $Usage) print " --addons Update the Gecko and Mono Wine addons.\n"; print " --wineprefix Update the wineprefixes.\n"; print "If none of the above actions is specified they are all performed.\n"; - print " TARGETS Is a comma-separated list of targets to process. By default all\n"; - print " targets are processed.\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 " MISSIONS Is a colon-separated list of missions. By default the\n"; + print " following missions are run.\n"; + print " - win32: Build the regular 32 bit Wine.\n"; + print " - wow32: Build the 32 bit WoW Wine.\n"; + print " - wow64: Build the 64 bit WoW Wine.\n"; print " --no-rm Don't rebuild from scratch.\n"; print " --help Shows this usage message.\n"; exit 0; @@ -255,8 +263,8 @@ if ($DataDir =~ /'/) exit(1) if (!BuildNativeTestAgentd()); exit(1) if ($OptUpdate and !GitPull("wine")); exit(1) if ($OptAddOns and !UpdateAddOns()); -exit(1) if ($OptBuild and !UpdateWineBuilds($Targets, $OptNoRm)); -exit(1) if ($OptWinePrefix and !UpdateWinePrefixes($Targets)); +exit(1) if ($OptBuild and !UpdateWineBuilds($TaskMissions, $OptNoRm)); +exit(1) if ($OptWinePrefix and !UpdateWinePrefixes($TaskMissions));
LogMsg "ok\n"; exit;
This makes it possible to specify the mission options on the command line if necessary.
Signed-off-by: Francois Gouget fgouget@codeweavers.com --- testbot/bin/build/Reconfig.pl | 62 ++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 27 deletions(-)
diff --git a/testbot/bin/build/Reconfig.pl b/testbot/bin/build/Reconfig.pl index f2d63fa24a..8e50e2f82b 100755 --- a/testbot/bin/build/Reconfig.pl +++ b/testbot/bin/build/Reconfig.pl @@ -42,6 +42,7 @@ sub BEGIN
use Build::Utils; use WineTestBot::Config; +use WineTestBot::Missions;
# @@ -81,9 +82,9 @@ sub BuildNative($)
sub BuildCross($$$) { - my ($Targets, $NoRm, $Build) = @_; + my ($TaskMissions, $NoRm, $Build) = @_;
- return 1 if (!$Targets->{$Build}); + return 1 if (!$TaskMissions->{Builds}->{$Build}); # FIXME Temporary code to ensure compatibility during the transition my $OldDir = $Build eq "exe32" ? "build-mingw32" : "build-mingw64"; if (-d "$DataDir/$OldDir" and !-d "$DataDir/wine-$Build") @@ -113,11 +114,11 @@ sub BuildCross($$$)
sub UpdateWineBuilds($$) { - my ($Targets, $NoRm) = @_; + my ($TaskMissions, $NoRm) = @_;
return BuildNative($NoRm) && - BuildCross($Targets, $NoRm, "exe32") && - BuildCross($Targets, $NoRm, "exe64"); + BuildCross($TaskMissions, $NoRm, "exe32") && + BuildCross($TaskMissions, $NoRm, "exe64"); }
@@ -128,10 +129,7 @@ sub UpdateWineBuilds($$) $ENV{PATH} = "/usr/lib/ccache:/usr/bin:/bin"; delete $ENV{ENV};
-my %AllTargets; -map { $AllTargets{$_} = 1 } qw(exe32 exe64); - -my ($Usage, $OptUpdate, $OptBuild, $OptNoRm, $TargetList); +my ($Usage, $OptUpdate, $OptBuild, $OptNoRm, $MissionStatement); while (@ARGV) { my $Arg = shift @ARGV; @@ -158,9 +156,9 @@ while (@ARGV) $Usage = 2; last; } - elsif (!defined $TargetList) + elsif (!defined $MissionStatement) { - $TargetList = $Arg; + $MissionStatement = $Arg; } else { @@ -171,23 +169,33 @@ while (@ARGV) }
# Check and untaint parameters -my $Targets; +my $TaskMissions; if (!defined $Usage) { if (!$OptUpdate and !$OptBuild) { $OptUpdate = $OptBuild = 1; } - $TargetList = join(",", keys %AllTargets) if (!defined $TargetList); - foreach my $Target (split /,/, $TargetList) + $MissionStatement ||= "exe32:exe64"; + my ($ErrMessage, $Missions) = ParseMissionStatement($MissionStatement); + if (defined $ErrMessage) + { + Error "$ErrMessage\n"; + $Usage = 2; + } + elsif (!@$Missions) + { + Error "Empty mission statement\n"; + $Usage = 2; + } + elsif (@$Missions > 1) + { + Error "Cannot specify missions for multiple tasks\n"; + $Usage = 2; + } + else { - if (!$AllTargets{$Target}) - { - Error "invalid target name $Target\n"; - $Usage = 2; - last; - } - $Targets->{$Target} = 1; + $TaskMissions = $Missions->[0]; } } if (defined $Usage) @@ -198,17 +206,17 @@ if (defined $Usage) Error "try '$Name0 --help' for more information\n"; exit $Usage; } - print "Usage: $Name0 [--update] [--build [--no-rm]] [--help] [TARGETS]\n"; + print "Usage: $Name0 [--update] [--build [--no-rm]] [--help] [MISSIONS]\n"; print "\n"; print "Updates Wine to the latest version and recompiles it so the host is ready to build executables for the Windows tests.\n"; print "\n"; print "Where:\n"; print " --update Update Wine's source code.\n"; print " --build Update the Wine builds.\n"; - print " TARGETS Is a comma-separated list of reconfiguration targets. By default\n"; - print " every target is run.\n"; - print " - exe32: Rebuild the 32 bit Windows test executables.\n"; - print " - exe64: Rebuild the 64 bit Windows test executables.\n"; + print " MISSIONS Is a colon-separated list of missions. By default the following\n"; + print " missions are run.\n"; + print " - exe32: Build the 32 bit Windows test executables.\n"; + print " - exe64: Build the 64 bit Windows test executables.\n"; print " --no-rm Don't rebuild from scratch.\n"; print " --help Shows this usage message.\n"; exit 0; @@ -233,7 +241,7 @@ if (! -d "$DataDir/staging" and ! mkdir "$DataDir/staging") exit(1) if (!BuildNativeTestAgentd() or !BuildWindowsTestAgentd()); exit(1) if (!BuildTestLauncher()); exit(1) if ($OptUpdate and !GitPull("wine")); -exit(1) if ($OptBuild and !UpdateWineBuilds($Targets, $OptNoRm)); +exit(1) if ($OptBuild and !UpdateWineBuilds($TaskMissions, $OptNoRm));
LogMsg "ok\n"; exit;
I forgot to mention that this patch requires updating the TestBot database schema (I guess it's obvious from the testbot/ddl chunks). So after applying this patch and updating the database the Engine and web server will need to be restarted.
So the patch add a Missions field to the VMs and Tasks tables. Since the Missions field is mandatory update41.sql sets default values. Since I've been going back and forth between the pre and post Missions patch in my tree update41.sql is careful not to overwrite existing Missions values. Hopefully we won't need that on the official TestBot server.