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;