[PATCH 1/2] testbot/Submit: Allow submitting patches that touch zero or multiple test units.

Francois Gouget fgouget at codeweavers.com
Tue Oct 1 23:56:19 CDT 2019


Patches submitted through the submit page no longer need to impact one
and only one test unit. This means it's no longer necessary to split
longer patches in order to test them.
With the Wine VMs it also no longer makes sense to ban patches that
don't touch the tests as one may need to make sure they compile and
don't cause regressions in existing tests.
However it is still necessary to specify command line arguments for
the test executable which means picking a single test unit to run.

Signed-off-by: Francois Gouget <fgouget at codeweavers.com>
---
 testbot/bin/WineRunWineTest.pl        |   1 +
 testbot/bin/build/Build.pl            |   6 +
 testbot/bin/build/WineTest.pl         |  79 +++++++++----
 testbot/lib/Build/Utils.pm            |  43 +++++---
 testbot/lib/WineTestBot/PatchUtils.pm |   1 +
 testbot/web/Submit.pl                 | 153 ++++++++++++++++++--------
 6 files changed, 199 insertions(+), 84 deletions(-)

diff --git a/testbot/bin/WineRunWineTest.pl b/testbot/bin/WineRunWineTest.pl
index beb050276..ff5317c8b 100755
--- a/testbot/bin/WineRunWineTest.pl
+++ b/testbot/bin/WineRunWineTest.pl
@@ -484,6 +484,7 @@ if ($Step->Type eq "suite")
 elsif ($Step->FileType eq "patch")
 {
   $Script .= "--testpatch ". ShQuote($Task->Missions) ." patch.diff";
+  $Script .= " ". $Task->CmdLineArg if (defined $Task->CmdLineArg);
 }
 else
 {
diff --git a/testbot/bin/build/Build.pl b/testbot/bin/build/Build.pl
index f23b733d6..4c342a5ac 100755
--- a/testbot/bin/build/Build.pl
+++ b/testbot/bin/build/Build.pl
@@ -87,6 +87,12 @@ sub BuildTestExecutables($$$)
   }
 
   InfoMsg "\nBuilding the $Build Wine test executable(s)\n";
+  if (!@BuildDirs)
+  {
+    InfoMsg "Nothing to do\n";
+    return 1;
+  }
+
   my $CPUCount = GetCPUCount();
   system("cd '$DataDir/wine-$Build' && set -x && ".
          "time make -j$CPUCount ". join(" ", sort @BuildDirs));
diff --git a/testbot/bin/build/WineTest.pl b/testbot/bin/build/WineTest.pl
index 37c20d1b4..9948e0929 100755
--- a/testbot/bin/build/WineTest.pl
+++ b/testbot/bin/build/WineTest.pl
@@ -121,9 +121,7 @@ sub DailyWineTest($$$$)
 
 sub TestPatch($$)
 {
-  my ($Mission, $Impacts) = @_;
-
-  return 1 if ($Mission->{test} eq "build");
+  my ($Mission, $Impacts, $Args) = @_;
 
   my @TestList;
   if ($Mission->{test} eq "all" and
@@ -180,6 +178,24 @@ sub TestPatch($$)
   return 1;
 }
 
+sub GetExecutable($$$)
+{
+  my ($Mission, $Impacts, $Module) = @_;
+
+  my $TestInfo = $Impacts->{Tests}->{$Module};
+  my $TestExecutable = "$TestInfo->{ExeBase}.exe";
+  my $Dst = "$DataDir/staging/$TestExecutable";
+  unlink $Dst;
+
+  my $WineDir = GetWineDir($Mission);
+  if (!symlink "$WineDir/$TestInfo->{Path}/$TestExecutable", $Dst)
+  {
+    LogMsg "Could not find $TestExecutable: $!\n";
+    return undef;
+  }
+  return $TestExecutable;
+}
+
 sub TestExe($$$)
 {
   my ($Mission, $FileName, $Args) = @_;
@@ -196,6 +212,21 @@ sub TestExe($$$)
     }
   }
 
+  my $TestDir = GetTestDir($Mission);
+  if ($TestDir eq ".")
+  {
+    $FileName = "$DataDir/staging/$FileName";
+  }
+  else
+  {
+    unlink "$TestDir/$FileName";
+    if (!symlink "$DataDir/staging/$FileName", "$TestDir/$FileName")
+    {
+      LogMsg "Could not create '$FileName' in the test directory: $!\n";
+      return 0;
+    }
+  }
+
   # Run the test executable
   RunWine($Mission, GetTestLauncher($Mission),
           "-t 120 ". ShArgv2Cmd($FileName, @$Args)
@@ -210,7 +241,7 @@ sub TestExe($$$)
 #
 
 my $Action = "";
-my ($Usage, $OptNoSubmit, $MissionStatement, $FileName, $BaseTag);
+my ($Usage, $OptNoSubmit, $MissionStatement, $FileName, $Module, $BaseTag);
 while (@ARGV)
 {
   my $Arg = shift @ARGV;
@@ -248,17 +279,16 @@ while (@ARGV)
   elsif ($Action eq "winetest")
   {
     $BaseTag = $Arg;
-    # The remaining arguments are meant for WineTest
-    last;
+    last; # The remaining arguments are meant for WineTest
   }
   elsif (!defined $FileName)
   {
     if (IsValidFileName($Arg))
     {
-      $FileName = "$DataDir/staging/$Arg";
-      if (!-r $FileName)
+      $FileName = $Arg;
+      if (!-r "$DataDir/staging/$FileName")
       {
-        Error "'$Arg' is not readable\n";
+        Error "'$FileName' is not readable\n";
         $Usage = 2;
       }
     }
@@ -266,14 +296,17 @@ while (@ARGV)
     {
       Error "the '$Arg' filename contains invalid characters\n";
       $Usage = 2;
-      last;
     }
     if ($Action eq "testexe")
     {
-      # The remainder are the executable's arguments
-      last;
+      last; # The remainder are the executable's arguments
     }
   }
+  elsif ($Action eq "testpatch" and !defined $Module)
+  {
+    $Module = $Arg;
+    last; # The remainder are the executable's arguments
+  }
   else
   {
     Error "unexpected argument '$Arg'\n";
@@ -372,7 +405,7 @@ if (defined $Usage)
     exit $Usage;
   }
   print "Usage: $Name0 [--help] --testexe MISSIONS EXE ARGS...\n";
-  print "or     $Name0 [--help] --testpatch MISSIONS PATCH\n";
+  print "or     $Name0 [--help] --testpatch MISSIONS PATCH [MODULE ARGS...]\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";
@@ -387,11 +420,10 @@ if (defined $Usage)
   print "               - wow32: The 32 bit WoW Wine build.\n";
   print "               - wow64: The 64 bit WoW Wine build.\n";
   print "  EXE          Is the staging file containing the executable to run.\n";
-  print "  ARGS         Are the arguments for the test executable.\n";
   print "  PATCH        Is the staging file containing the patch to test.\n";
   print "  BASETAG      Is the tag for this WineTest run. Note that the build type is\n";
   print "               automatically added to this tag.\n";
-  print "  ARGS         The WineTest arguments.\n";
+  print "  ARGS         Arguments for the test.\n";
   print "  --help       Shows this usage message.\n";
   exit $Usage;
 }
@@ -413,7 +445,7 @@ unlink map { GetMissionBaseName($_) .".report" } @{$TaskMissions->{Missions}};
 my $Impacts;
 if ($Action eq "testpatch")
 {
-  $Impacts = ApplyPatch("wine", $FileName);
+  $Impacts = ApplyPatch("wine", "$DataDir/staging/$FileName");
   exit(1) if (!$Impacts or
               !BuildWine($TaskMissions, "win32") or
               !BuildWine($TaskMissions, "wow64") or
@@ -421,18 +453,25 @@ if ($Action eq "testpatch")
 }
 foreach my $Mission (@{$TaskMissions->{Missions}})
 {
+  return 1 if ($Mission->{test} eq "build");
+
   if ($Action eq "testexe")
   {
     exit(1) if (!TestExe($Mission, $FileName, \@ARGV));
   }
-  elsif ($Action eq "testpatch")
-  {
-    exit(1) if (!TestPatch($Mission, $Impacts));
-  }
   elsif ($Action eq "winetest")
   {
     exit(1) if (!DailyWineTest($Mission,  $OptNoSubmit, $BaseTag, \@ARGV));
   }
+  elsif (@ARGV)
+  {
+    my $FileName = GetExecutable($Mission, $Impacts, $Module);
+    exit(1) if (!$FileName or !TestExe($Mission, $FileName, \@ARGV));
+  }
+  else
+  {
+    exit(1) if (!TestPatch($Mission, $Impacts));
+  }
 }
 
 LogMsg "ok\n";
diff --git a/testbot/lib/Build/Utils.pm b/testbot/lib/Build/Utils.pm
index 50146301a..15acf6783 100644
--- a/testbot/lib/Build/Utils.pm
+++ b/testbot/lib/Build/Utils.pm
@@ -30,7 +30,8 @@ our @EXPORT = qw(GetToolName InfoMsg LogMsg Error
                  GitPull ApplyPatch
                  GetCPUCount BuildNativeTestAgentd BuildWindowsTestAgentd
                  GetTestLauncher BuildTestLauncher UpdateAddOns
-                 SetupWineEnvironment RunWine CreateWinePrefix);
+                 SetupWineEnvironment GetWineDir GetTestDir RunWine
+                 CreateWinePrefix);
 
 use Digest::SHA;
 use File::Basename;
@@ -397,29 +398,37 @@ sub SetupWineEnvironment($)
   return $BaseName;
 }
 
+sub GetWineDir($)
+{
+  my ($Mission) = @_;
+  return "$DataDir/wine-$Mission->{Build}";
+}
+
+sub GetTestDir($)
+{
+  my ($Mission) = @_;
+
+  my $DevDir = "$ENV{WINEPREFIX}/dosdevices";
+  return "." if (!-d $DevDir);
+
+  my $Dir = $Mission->{dir} || "";
+  # We cannot put colons in missions so restore the drive letter
+  $Dir = "$DevDir/c:/$Dir" if ($Dir !~ s~^([a-z])/~$DevDir/$1:/~);
+  return -d $Dir ? $Dir : "$DevDir/c:";
+}
+
 sub RunWine($$$)
 {
   my ($Mission, $Cmd, $CmdArgs) = @_;
 
-  my $WineDir = "$DataDir/wine-$Mission->{Build}";
-  $Cmd = "$WineDir/$Cmd" if ($Cmd =~ /\.exe\.so$/);
-
-  my $CurDir = "$ENV{WINEPREFIX}/dosdevices";
-  if (!-d $CurDir)
-  {
-    $CurDir = ".";
-  }
-  else
-  {
-    my $Dir = $Mission->{dir} || "";
-    # We cannot put colons in missions so restore the drive letter
-    $Dir = "$CurDir/c:/$Dir" if ($Dir !~ s~^([a-z])/~$CurDir/$1:/~);
-    $CurDir = -d $Dir ? $Dir : "$ENV{WINEPREFIX}/dosdevices/c:";
-  }
+  my $TestDir = GetTestDir($Mission);
 
+  my $WineDir = GetWineDir($Mission);
+  $Cmd = "$WineDir/$Cmd" if ($Cmd =~ /\.exe\.so$/);
   my $Magic = `file '$Cmd'`;
   my $Wine = ($Magic =~ /ELF 64/ ? "$WineDir/wine64" : "$WineDir/wine");
-  return system("set -x && cd '$CurDir' && ".
+
+  return system("set -x && cd '$TestDir' && ".
                 "time '$Wine' '$Cmd' $CmdArgs");
 }
 
diff --git a/testbot/lib/WineTestBot/PatchUtils.pm b/testbot/lib/WineTestBot/PatchUtils.pm
index 71be9cf00..3c2f67011 100644
--- a/testbot/lib/WineTestBot/PatchUtils.pm
+++ b/testbot/lib/WineTestBot/PatchUtils.pm
@@ -441,6 +441,7 @@ sub GetPatchImpacts($)
       next if (exists $TestInfo->{Files}->{"$Base.spec"});
       # Don't try running a deleted test unit obviously
       next if ($TestInfo->{Files}->{$File} eq "rm");
+      $TestInfo->{AllUnits}->{$Base} = 1;
 
       if ($TestInfo->{All} or $TestInfo->{Files}->{$File})
       {
diff --git a/testbot/web/Submit.pl b/testbot/web/Submit.pl
index a51ace650..b67237528 100644
--- a/testbot/web/Submit.pl
+++ b/testbot/web/Submit.pl
@@ -32,6 +32,7 @@ use POSIX qw(:fcntl_h); # For SEEK_XXX
 use File::Basename;
 
 use ObjectModel::BasicPropertyDescriptor;
+use ObjectModel::EnumPropertyDescriptor;
 use WineTestBot::Branches;
 use WineTestBot::Config;
 use WineTestBot::Engine::Notify;
@@ -174,16 +175,10 @@ sub _AnalyzePatch($)
     $self->{ErrMessage} = "'$self->{FileName}' is not a valid patch";
     return undef;
   }
-  if ($self->{Impacts}->{TestUnitCount} == 0)
+  if ($self->{Impacts}->{ModuleUnitCount} == 0)
   {
     $self->{ErrField} = "FileName";
-    $self->{ErrMessage} = "The patch does not impact the tests";
-    return undef;
-  }
-  if ($self->{Impacts}->{TestUnitCount} > 1)
-  {
-    $self->{ErrField} = "FileName";
-    $self->{ErrMessage} = "The patch contains changes to multiple tests";
+    $self->{ErrMessage} = "The patch does not directly impact the Wine dlls and programs";
     return undef;
   }
   return 1;
@@ -271,6 +266,65 @@ sub _ValidateVMSelection($;$)
   return 1;
 }
 
+sub _GetTestExecutables($)
+{
+  my ($self, $ResetInvalid) = @_;
+
+  if (!$self->{TestExecutables})
+  {
+    if ($self->{FileType} eq "patch")
+    {
+      foreach my $Module (sort keys %{$self->{Impacts}->{Tests}})
+      {
+        my $TestInfo = $self->{Impacts}->{Tests}->{$Module};
+        next if (!%{$TestInfo->{Files}});
+        $self->{TestExecutables}->{"$TestInfo->{ExeBase}.exe"} = $Module;
+      }
+    }
+    else
+    {
+      $self->{TestExecutables}->{$self->{FileName}} = 1;
+    }
+  }
+}
+
+sub _ValidateTestExecutable($;$)
+{
+  my ($self, $ResetInvalid) = @_;
+
+  $self->_GetTestExecutables();
+  if ($ResetInvalid and (!defined $self->{TestExecutable} or
+                         !$self->{TestExecutables}->{$self->{TestExecutable}}))
+  {
+    $self->{TestExecutable} = (sort keys %{$self->{TestExecutables}})[0];
+    if (!defined $self->{CmdLineArg} and $self->{Impacts})
+    {
+      my $Module = $self->{TestExecutables}->{$self->{TestExecutable}};
+      my $TestInfo = $self->{Impacts}->{Tests}->{$Module};
+      $self->{CmdLineArg} = (sort keys %{$TestInfo->{PatchedUnits}})[0] ||
+                            (sort keys %{$TestInfo->{AllUnits}})[0];
+    }
+  }
+
+  if (!defined $self->{TestExecutable})
+  {
+    $self->{ErrField} = "TestExecutable";
+    $self->{ErrMessage} = "You must specify the test executable filename";
+    return undef;
+  }
+  elsif (!$self->{TestExecutables}->{$self->{TestExecutable}} or
+         # Make sure the test executable filename is valid
+         # to defend against malicious patches.
+         !IsValidFileName($self->{TestExecutable}))
+  {
+    $self->{ErrField} = "TestExecutable";
+    $self->{ErrMessage} = "Invalid test executable filename";
+    return undef;
+  }
+
+  return 1;
+}
+
 sub Validate($)
 {
   my ($self) = @_;
@@ -318,14 +372,7 @@ sub Validate($)
 
   if ($self->{Page} >= 3)
   {
-    if (($self->{FileType} eq "patch" and
-         $self->{TestExecutable} !~ m/^[\w_.]+_test\.exe$/) or
-        !IsValidFileName($self->{TestExecutable}))
-    {
-      $self->{ErrField} = "TestExecutable";
-      $self->{ErrMessage} = "Invalid test executable filename";
-      return undef;
-    }
+    return undef if (!$self->_ValidateTestExecutable());
 
     if ($self->{CmdLineArg} eq "" and !$self->{NoCmdLineArgWarn})
     {
@@ -493,6 +540,30 @@ sub GetHeaderText($)
     }
     return $HeaderText;
   }
+  elsif ($self->{Page} == 3 and $self->{Impacts})
+  {
+    my @TestExecutables;
+    $self->_GetTestExecutables();
+    foreach my $TestExecutable (sort keys %{$self->{TestExecutables}})
+    {
+      my $Module = $self->{TestExecutables}->{$TestExecutable};
+      my $TestInfo = $self->{Impacts}->{Tests}->{$Module};
+      my @TestUnits;
+      foreach my $TestUnit (sort keys %{$TestInfo->{AllUnits}})
+      {
+        if ($TestInfo->{PatchedUnits}->{$TestUnit})
+        {
+          push @TestUnits, "<i>$TestUnit</i>";
+        }
+        elsif ($TestInfo->{All} or $TestInfo->{PatchedModule})
+        {
+          push @TestUnits, $TestUnit;
+        }
+      }
+      push @TestExecutables, "$Module: ". join(" ", @TestUnits) if (@TestUnits);
+    }
+    return join("<br>\n", "Here is a list of the test executables impacted by the patch (patched test units, if any, are in italics).", @TestExecutables);
+  }
 
   return "";
 }
@@ -512,15 +583,18 @@ sub GetPropertyDescriptors($)
   }
   elsif ($self->{Page} == 3)
   {
-    $self->_GetFileType();
-    my $IsPatch = ($self->{FileType} eq "patch");
-    $self->{PropertyDescriptors} ||= [
-      CreateBasicPropertyDescriptor("TestExecutable", "Test executable", !1, $IsPatch, "A", 50),
-      CreateBasicPropertyDescriptor("CmdLineArg", "Command line arguments", !1, !1, "A", 50),
-      CreateBasicPropertyDescriptor("Run64", "Run 64-bit tests in addition to 32-bit tests", !1, 1, "B", 1),
-      CreateBasicPropertyDescriptor("DebugLevel", "Debug level (WINETEST_DEBUG)", !1, 1, "N", 2),
-      CreateBasicPropertyDescriptor("ReportSuccessfulTests", "Report successful tests (WINETEST_REPORT_SUCCESS)", !1, 1, "B", 1),
-    ];
+    if (!$self->{PropertyDescriptors})
+    {
+      $self->_GetTestExecutables();
+      my @TestExecutables = sort keys %{$self->{TestExecutables}};
+      $self->{PropertyDescriptors} = [
+        CreateEnumPropertyDescriptor("TestExecutable", "Test executable", !1, 1, \@TestExecutables),
+        CreateBasicPropertyDescriptor("CmdLineArg", "Command line arguments", !1, !1, "A", 50),
+        CreateBasicPropertyDescriptor("Run64", "Run 64-bit tests in addition to 32-bit tests", !1, 1, "B", 1),
+        CreateBasicPropertyDescriptor("DebugLevel", "Debug level (WINETEST_DEBUG)", !1, 1, "N", 2),
+        CreateBasicPropertyDescriptor("ReportSuccessfulTests", "Report successful tests (WINETEST_REPORT_SUCCESS)", !1, 1, "B", 1),
+      ];
+    }
   }
   return $self->SUPER::GetPropertyDescriptors();
 }
@@ -601,7 +675,9 @@ sub GenerateFields($)
 
       # By default select the base VMs that are ready to run tasks
       if (!$self->{UserVMSelection} and !$VMRow->{Extra} and
-          $VMRow->{VM}->Status !~ /^(?:offline|maintenance)$/)
+          $VMRow->{VM}->Status !~ /^(?:offline|maintenance)$/ and
+          ($VMRow->{VM}->Type eq "wine" or !$self->{Impacts} or
+           $self->{Impacts}->{TestUnitCount}))
       {
         $VMRow->{Checked} = 1;
       }
@@ -764,8 +840,6 @@ sub OnUnset($)
     unlink($StagingFilePath) if ($StagingFilePath);
     delete $self->{FileName};
   }
-  delete $self->{TestExecutable};
-  delete $self->{CmdLineArg};
 
   return 1;
 }
@@ -783,29 +857,13 @@ sub OnPage1Next($)
     $self->{FileName} = $self->GetParam("Upload");
     return undef if (!$self->_ValidateFileName() or !$self->_Upload());
   }
-  if (!$self->Validate() or !$self->_ValidateVMSelection("deselect"))
+  if (!$self->Validate() or !$self->_ValidateVMSelection("deselect") or
+      !$self->_ValidateTestExecutable("reset"))
   {
     return undef;
   }
 
   # Set defaults
-  if ((!defined $self->{TestExecutable} or !defined $self->{CmdLineArg}) and
-      $self->{Impacts})
-  {
-    foreach my $TestInfo (values %{$self->{Impacts}->{Tests}})
-    {
-      next if (!$TestInfo->{UnitCount});
-      if (!defined $self->{TestExecutable})
-      {
-        $self->{TestExecutable} = "$TestInfo->{ExeBase}.exe";
-      }
-      if (!defined $self->{CmdLineArg})
-      {
-        $self->{CmdLineArg} = (keys %{$TestInfo->{PatchedUnits}})[0];
-      }
-      last;
-    }
-  }
   if (!defined $self->{Run64})
   {
     # Whether we show it or not the default is true
@@ -997,7 +1055,8 @@ sub _SubmitJob($$)
     $Task->VM($VM);
     $Task->Timeout($Timeout);
     $Task->Missions($MissionStatement);
-    $Task->CmdLineArg($self->{CmdLineArg}) if ($self->{FileType} ne "patch");
+    my $Module = $self->{TestExecutables}->{$self->{TestExecutable}};
+    $Task->CmdLineArg(($Module eq 1 ? "" : "$Module ") .$self->{CmdLineArg});
   }
 
   # Now save it all (or whatever's left to save)
-- 
2.20.1




More information about the wine-devel mailing list