[PATCH 3/3] testbot: Run WineTest on Wine VMs after commits.

Francois Gouget fgouget at codeweavers.com
Wed Jun 27 01:05:01 CDT 2018


WineReconfig.pl now knows how to retrieve and cache the Gecko and Mono
addons, and how to create new wineprefixes. These are then captured by
the updated snapshot and are thus ready to use to run tests.
CheckForWinetestUpdate.pl creates additional steps to rerun the full
WineTest suite on the Wine VMs if the Wine rebuild was successful.
Running WineTest is handled by WineTest.pl and WineRunWineTest.pl
collects the corresponding reports in the latest directory in files
named '<vm>_<build>.report' where build is one of win32, wow32 or
wow64.

Signed-off-by: Francois Gouget <fgouget at codeweavers.com>
---
 testbot/bin/CheckForWinetestUpdate.pl |  44 +++++-
 testbot/bin/WineRunWineTest.pl        | 218 ++++++++++++++++++++++----
 testbot/bin/build/WineReconfig.pl     | 189 ++++++++++++++++++++--
 testbot/bin/build/WineTest.pl         | 118 ++++++++++++--
 testbot/lib/WineTestBot/LogUtils.pm   |   8 +-
 testbot/lib/WineTestBot/Utils.pm      |  19 ++-
 6 files changed, 537 insertions(+), 59 deletions(-)

diff --git a/testbot/bin/CheckForWinetestUpdate.pl b/testbot/bin/CheckForWinetestUpdate.pl
index 74c59955f..d3ef384c7 100755
--- a/testbot/bin/CheckForWinetestUpdate.pl
+++ b/testbot/bin/CheckForWinetestUpdate.pl
@@ -273,10 +273,10 @@ sub AddReconfigJob($)
 
   # Add a step to the job
   my $Steps = $NewJob->Steps;
-  my $NewStep = $Steps->Add();
-  $NewStep->Type("reconfig");
-  $NewStep->FileType("none");
-  $NewStep->InStaging(!1);
+  my $BuildStep = $Steps->Add();
+  $BuildStep->Type("reconfig");
+  $BuildStep->FileType("none");
+  $BuildStep->InStaging(!1);
 
   # And a task for each VM
   my $SortedKeys = $VMs->SortKeysBySortOrder($VMs->GetKeys());
@@ -284,14 +284,46 @@ sub AddReconfigJob($)
   {
     my $VM = $VMs->GetItem($VMKey);
     Debug("  $VMKey $VMType reconfig\n");
-    my $Task = $NewStep->Tasks->Add();
+    my $Task = $BuildStep->Tasks->Add();
     $Task->VM($VM);
     $Task->Timeout($ReconfigTimeout);
   }
 
-  # Save it all
+  # Save the build step so the others can reference it.
   my ($ErrKey, $ErrProperty, $ErrMessage) = $Jobs->Save();
   if (defined $ErrMessage)
+  {
+    Error "Failed to save the build step: $ErrMessage\n";
+    return 0;
+  }
+
+  if ($VMType eq "wine")
+  {
+    # Add steps to run WineTest on Wine
+    foreach my $Build ("win32", "wow32", "wow64")
+    {
+      # Add a step to the job
+      my $NewStep = $Steps->Add();
+      $NewStep->PreviousNo($BuildStep->No);
+      $NewStep->Type("suite");
+      $NewStep->FileType("none");
+      $NewStep->InStaging(!1);
+
+      foreach my $VMKey (@$SortedKeys)
+      {
+        my $VM = $VMs->GetItem($VMKey);
+        Debug("  $VMKey $Build\n");
+        my $Task = $NewStep->Tasks->Add();
+        $Task->VM($VM);
+        $Task->CmdLineArg($Build);
+        $Task->Timeout($SuiteTimeout);
+      }
+    }
+  }
+
+  # Save it all
+  ($ErrKey, $ErrProperty, $ErrMessage) = $Jobs->Save();
+  if (defined $ErrMessage)
   {
     Error "Failed to save the Reconfig job: $ErrMessage\n";
     return 0;
diff --git a/testbot/bin/WineRunWineTest.pl b/testbot/bin/WineRunWineTest.pl
index 53a2a0929..83cf5821b 100755
--- a/testbot/bin/WineRunWineTest.pl
+++ b/testbot/bin/WineRunWineTest.pl
@@ -1,7 +1,7 @@
 #!/usr/bin/perl -Tw
 # -*- Mode: Perl; perl-indent-level: 2; indent-tabs-mode: nil -*-
 #
-# Makes sure the Wine patches compile.
+# Makes sure the Wine patches compile or run WineTest.
 # See the bin/build/WineTest.pl script.
 #
 # Copyright 2018 Francois Gouget
@@ -46,6 +46,7 @@ use WineTestBot::PatchUtils;
 use WineTestBot::VMs;
 use WineTestBot::Log;
 use WineTestBot::LogUtils;
+use WineTestBot::Utils;
 use WineTestBot::Engine::Notify;
 
 
@@ -67,6 +68,35 @@ sub Error(@)
 }
 
 
+#
+# Task helpers
+#
+
+sub TakeScreenshot($$)
+{
+  my ($VM, $FileName) = @_;
+
+  my $Domain = $VM->GetDomain();
+  my ($ErrMessage, $ImageSize, $ImageBytes) = $Domain->CaptureScreenImage();
+  if (!defined $ErrMessage)
+  {
+    if (open(my $Screenshot, ">", $FileName))
+    {
+      print $Screenshot $ImageBytes;
+      close($Screenshot);
+    }
+    else
+    {
+      Error "Could not open the screenshot file for writing: $!\n";
+    }
+  }
+  elsif ($Domain->IsPoweredOn())
+  {
+    Error "Could not capture a screenshot: $ErrMessage\n";
+  }
+}
+
+
 #
 # Setup and command line processing
 #
@@ -190,15 +220,17 @@ sub LogTaskError($)
   }
 }
 
-sub WrapUpAndExit($;$$)
+sub WrapUpAndExit($;$$$)
 {
-  my ($Status, $Retry, $Timeout) = @_;
+  my ($Status, $TestFailures, $Retry, $TimedOut) = @_;
   my $NewVMStatus = $Status eq 'queued' ? 'offline' : 'dirty';
   my $VMResult = $Status eq "boterror" ? "boterror" :
                  $Status eq "queued" ? "error" :
-                 $Timeout ? "timeout" : "";
+                 $TimedOut ? "timeout" : "";
+
+  Debug(Elapsed($Start), " Taking a screenshot\n");
+  TakeScreenshot($VM, "$TaskDir/screenshot.png");
 
-  my $TestFailures;
   my $Tries = $Task->TestFailures || 0;
   if ($Retry)
   {
@@ -253,6 +285,30 @@ sub WrapUpAndExit($;$$)
     $VM->Save();
   }
 
+  if ($Step->Type eq 'suite' and $Status eq 'completed' and !$TimedOut)
+  {
+    my $BuildList = $Task->CmdLineArg;
+    $BuildList =~ s/ .*$//;
+    foreach my $Build (split /,/, $BuildList)
+    {
+      # Keep the old report if the new one is missing
+      my $RptFileName = "$Build.report";
+      if (-f "$TaskDir/$RptFileName" and !-z "$TaskDir/$RptFileName")
+      {
+        # Update the reference VM suite results for WineSendLog.pl
+        my $LatestBaseName = join("", "$DataDir/latest/", $Task->VM->Name,
+                                  "_$Build");
+        unlink("$LatestBaseName.log");
+        link("$TaskDir/$RptFileName", "$LatestBaseName.log");
+        unlink("$LatestBaseName.err");
+        if (-f "$TaskDir/err" and !-z "$TaskDir/err")
+        {
+          link("$TaskDir/err", "$LatestBaseName.err");
+        }
+      }
+    }
+  }
+
   my $Result = $VM->Name .": ". $VM->Status ." Status: $Status Failures: ". (defined $TestFailures ? $TestFailures : "unset");
   LogMsg "Task $JobId/$StepNo/$TaskNo done ($Result)\n";
   Debug(Elapsed($Start), " Done. $Result\n");
@@ -266,12 +322,12 @@ sub FatalError($;$)
   LogMsg "$JobId/$StepNo/$TaskNo $ErrMessage";
   LogTaskError($ErrMessage);
 
-  WrapUpAndExit('boterror', $Retry);
+  WrapUpAndExit('boterror', undef, $Retry);
 }
 
-sub FatalTAError($$)
+sub FatalTAError($$;$)
 {
-  my ($TA, $ErrMessage) = @_;
+  my ($TA, $ErrMessage, $PossibleCrash) = @_;
   $ErrMessage .= ": ". $TA->GetLastError() if (defined $TA);
 
   # A TestAgent operation failed, see if the VM is still accessible
@@ -297,7 +353,13 @@ sub FatalTAError($$)
   else
   {
     # Ignore the TestAgent error, it's irrelevant
-    $ErrMessage = "The test VM is powered off!\n";
+    $ErrMessage = "The test VM is powered off! Did the test shut it down?\n";
+  }
+  if ($PossibleCrash and !$Task->CanRetry())
+  {
+    # The test did it!
+    LogTaskError($ErrMessage);
+    WrapUpAndExit('completed', 1);
   }
   FatalError($ErrMessage, $Retry);
 }
@@ -320,33 +382,78 @@ elsif (!$VM->GetDomain()->IsPoweredOn())
   FatalError("The VM is not powered on\n");
 }
 
-if ($Step->FileType ne "patchdlls")
+if (($Step->Type eq "suite" and $Step->FileType ne "none") or
+    ($Step->Type ne "suite" and $Step->FileType ne "patchdlls"))
 {
   FatalError("Unexpected file type '". $Step->FileType ."' found\n");
 }
 
 
 #
-# Run the task
+# Setup the VM
 #
+my $TA = $VM->GetAgent();
+Debug(Elapsed($Start), " Setting the time\n");
+if (!$TA->SetTime())
+{
+  # Not a fatal error. Try the next port in case the VM runs a privileged
+  # TestAgentd daemon there.
+  my $PrivilegedTA = $VM->GetAgent(1);
+  if (!$PrivilegedTA->SetTime())
+  {
+    LogTaskError("Unable to set the VM system time: ". $PrivilegedTA->GetLastError() .". Maybe the TestAgentd process is missing the required privileges.\n");
+    $PrivilegedTA->Disconnect();
+  }
+}
 
 my $FileName = $Step->GetFullFileName();
-my $TA = $VM->GetAgent();
-Debug(Elapsed($Start), " Sending '$FileName'\n");
-if (!$TA->SendFile($FileName, "staging/patch.diff", 0))
+if (defined $FileName)
 {
-  FatalTAError($TA, "Could not copy the patch to the VM");
+  Debug(Elapsed($Start), " Sending '$FileName'\n");
+  if (!$TA->SendFile($FileName, "staging/patch.diff", 0))
+  {
+    FatalTAError($TA, "Could not copy the patch to the VM");
+  }
 }
+
 my $Script = "#!/bin/sh\n".
-             "( set -x\n" .
-             "  ../bin/build/WineTest.pl ". $Task->CmdLineArg ." build patch.diff\n".
-             ") >Task.log 2>&1\n";
+             "( set -x\n".
+             "  ../bin/build/WineTest.pl ";
+if ($Step->Type eq "suite")
+{
+  my $Tag = lc($VM->Name);
+  $Tag =~ s/^$TagPrefix//;
+  $Tag =~ s/[^a-zA-Z0-9]/-/g;
+  $Script .= $Task->CmdLineArg .",submit winetest $TagPrefix-$Tag ";
+  if (defined $WebHostName)
+  {
+    my $StepTask = 100 * $StepNo + $TaskNo;
+    $Script .= "-u \"http://$WebHostName/JobDetails.pl?Key=$JobId&s$StepTask=1#k$StepTask\" ";
+  }
+  my $Info = $VM->Description ? $VM->Description : "";
+  if ($VM->Details)
+  {
+      $Info .= ": " if ($Info ne "");
+      $Info .=  $VM->Details;
+  }
+  $Script .= join(" ", "-m", ShQuote($AdminEMail), "-i", ShQuote($Info));
+}
+else
+{
+  $Script .= $Task->CmdLineArg ." build patch.diff";
+}
+$Script .= "\n) >Task.log 2>&1\n";
 Debug(Elapsed($Start), " Sending the script: [$Script]\n");
 if (!$TA->SendFileFromString($Script, "task", $TestAgent::SENDFILE_EXE))
 {
   FatalTAError($TA, "Could not send the task script to the VM");
 }
 
+
+#
+# Run the test
+#
+
 Debug(Elapsed($Start), " Starting the script\n");
 my $Pid = $TA->Run(["./task"], 0);
 if (!$Pid)
@@ -357,10 +464,11 @@ if (!$Pid)
 
 #
 # From that point on we want to at least try to grab the task log
-# before giving up
+# and a screenshot before giving up
 #
 
-my ($NewStatus, $ErrMessage, $TAError, $TaskTimedOut);
+my $NewStatus = 'completed';
+my ($TaskFailures, $TaskTimedOut, $ErrMessage, $TAError, $PossibleCrash);
 Debug(Elapsed($Start), " Waiting for the script (", $Task->Timeout, "s timeout)\n");
 if (!defined $TA->Wait($Pid, $Task->Timeout, 60))
 {
@@ -368,11 +476,19 @@ if (!defined $TA->Wait($Pid, $Task->Timeout, 60))
   if ($ErrMessage =~ /timed out waiting for the child process/)
   {
     $ErrMessage = "The task timed out\n";
-    $NewStatus = "badbuild";
+    if ($Step->Type eq "build")
+    {
+      $NewStatus = "badbuild";
+    }
+    else
+    {
+      $TaskFailures = 1;
+    }
     $TaskTimedOut = 1;
   }
   else
   {
+    $PossibleCrash = 1 if ($Step->Type ne "build");
     $TAError = "An error occurred while waiting for the task to complete: $ErrMessage";
     $ErrMessage = undef;
   }
@@ -399,10 +515,11 @@ if ($TA->GetFile("Task.log", "$TaskDir/log"))
   {
     FatalError("$Result\n", "retry");
   }
-  else
+  elsif ($Result ne "missing" or $Step->Type ne "suite")
   {
-    # If the result line is missing we probably already have an error message
-    # that explains why.
+    # There is no build and thus no result line when running WineTest.
+    # Otherwise if the result line is missing we probably already have an
+    # error message that explains why.
     $NewStatus = "badbuild";
   }
 }
@@ -411,15 +528,62 @@ elsif (!defined $TAError)
   $TAError = "An error occurred while retrieving the task log: ". $TA->GetLastError();
 }
 
+#
+# Grab the test logs if any
+#
+
+my $TimedOut;
+if ($Step->Type ne "build")
+{
+  my $TaskDir = $Task->CreateDir();
+  my $BuildList = $Task->CmdLineArg;
+  $BuildList =~ s/ .*$//;
+  foreach my $Build (split /,/, $BuildList)
+  {
+    my $RptFileName = "$Build.report";
+    Debug(Elapsed($Start), " Retrieving '$RptFileName'\n");
+    if ($TA->GetFile($RptFileName, "$TaskDir/$RptFileName"))
+    {
+      chmod 0664, "$TaskDir/$RptFileName";
+
+      (my $LogFailures, my $LogErrors, $TimedOut) = ParseWineTestReport("$TaskDir/$RptFileName", 1, $Step->Type eq "suite", $TaskTimedOut);
+      if (!defined $LogFailures and @$LogErrors == 1)
+      {
+        # Could not open the file
+        $NewStatus = 'boterror';
+        Error "Unable to open '$RptFileName' for reading: $!\n";
+        LogTaskError("Unable to open '$RptFileName' for reading: $!\n");
+      }
+      else
+      {
+        # $LogFailures can legitimately be undefined in case of a timeout
+        $TaskFailures += $LogFailures || 0;
+        foreach my $Error (@$LogErrors)
+        {
+          LogTaskError("$Error\n");
+        }
+      }
+    }
+    elsif (!defined $TAError and
+           $TA->GetLastError() !~ /: No such file or directory/)
+    {
+      $TAError = "An error occurred while retrieving $RptFileName: ". $TA->GetLastError();
+      $NewStatus = 'boterror';
+    }
+  }
+}
+
+Debug(Elapsed($Start), " Disconnecting\n");
+$TA->Disconnect();
+
 # Report the task errors even though they may have been caused by
 # TestAgent trouble.
 LogTaskError($ErrMessage) if (defined $ErrMessage);
-FatalTAError(undef, $TAError) if (defined $TAError);
-$TA->Disconnect();
+FatalTAError(undef, $TAError, $PossibleCrash) if (defined $TAError);
 
 
 #
 # Wrap up
 #
 
-WrapUpAndExit($NewStatus, undef, $TaskTimedOut);
+WrapUpAndExit($NewStatus, $TaskFailures, undef, $TaskTimedOut || $TimedOut);
diff --git a/testbot/bin/build/WineReconfig.pl b/testbot/bin/build/WineReconfig.pl
index b030051c6..e3f14e243 100755
--- a/testbot/bin/build/WineReconfig.pl
+++ b/testbot/bin/build/WineReconfig.pl
@@ -40,6 +40,9 @@ my $Name0 = $0;
 $Name0 =~ s+^.*/++;
 
 
+use Digest::SHA;
+use File::Path;
+
 use WineTestBot::Config;
 use WineTestBot::PatchUtils;
 
@@ -126,7 +129,7 @@ sub BuildWine($$$$)
 {
   my ($Targets, $NoRm, $Build, $Extras) = @_;
 
-  return 1 if (!$Targets->{$Build});
+  return 1 if (!$Targets->{build} or !$Targets->{$Build});
   mkdir "$DataDir/build-$Build" if (!-d "$DataDir/build-$Build");
 
   # If $NoRm is not set, rebuild from scratch to make sure cruft will not
@@ -134,7 +137,7 @@ sub BuildWine($$$$)
   InfoMsg "\nRebuilding the $Build Wine\n";
   system("cd '$DataDir/build-$Build' && set -x && ".
          ($NoRm ? "" : "rm -rf * && ") .
-         "time ../wine/configure $Extras --disable-winetest && ".
+         "time ../wine/configure $Extras && ".
          "time make -j$ncpus");
   if ($? != 0)
   {
@@ -146,6 +149,154 @@ sub BuildWine($$$$)
 }
 
 
+#
+# WinePrefix helpers
+#
+
+sub VerifyAddOn($$)
+{
+  my ($AddOn, $Arch) = @_;
+
+  my $Sha256 = Digest::SHA->new(256);
+  eval { $Sha256->addfile("$DataDir/$AddOn->{name}/$AddOn->{filename}") };
+  return "$@" if ($@);
+
+  my $Checksum = $Sha256->hexdigest();
+  return undef if ($Checksum eq $AddOn->{$Arch});
+  return "Bad checksum for '$AddOn->{filename}'";
+}
+
+sub UpdateAddOn($$$)
+{
+  my ($AddOn, $Name, $Arch) = @_;
+
+  if (!defined $AddOn)
+  {
+    LogMsg "Could not get information on the $Name addon\n";
+    return 0;
+  }
+  if (!$AddOn->{version})
+  {
+    LogMsg "Could not get the $Name version\n";
+    return 0;
+  }
+  if (!$AddOn->{$Arch})
+  {
+    LogMsg "Could not get the $Name $Arch checksum\n";
+    return 0;
+  }
+
+  $AddOn->{filename} = "wine". ($Name eq "gecko" ? "_" : "-") .
+                       "$Name-$AddOn->{version}".
+                       ($Arch eq "" ? "" : "-$Arch") .".msi";
+  return 1 if (!VerifyAddOn($AddOn, $Arch));
+
+  InfoMsg "Downloading $AddOn->{filename}\n";
+  mkdir "$DataDir/$Name";
+
+  my $Url="http://dl.winehq.org/wine/wine-$Name/$AddOn->{version}/$AddOn->{filename}";
+  for (1..3)
+  {
+    system("cd '$DataDir/$Name' && set -x && ".
+           "wget --no-verbose -O- '$Url' >'$AddOn->{filename}'");
+    last if ($? == 0);
+  }
+  my $ErrMessage = VerifyAddOn($AddOn, $Arch);
+  return 1 if (!defined $ErrMessage);
+  LogMsg "$ErrMessage\n";
+  return 0;
+}
+
+sub UpdateAddOns($)
+{
+  my ($Targets) = @_;
+  return 1 if (!$Targets->{addons});
+
+  my %AddOns;
+  if (open(my $fh, "<", "$DataDir/wine/dlls/appwiz.cpl/addons.c"))
+  {
+    my $Arch = "";
+    while (my $Line= <$fh>)
+    {
+      if ($Line =~ /^\s*#\s*define\s+ARCH_STRING\s+"([^"]+)"/)
+      {
+        $Arch = $1;
+      }
+      elsif ($Line =~ /^\s*#\s*define\s*(GECKO|MONO)_VERSION\s*"([^"]+)"/)
+      {
+        my ($AddOn, $Version) = ($1, $2);
+        $AddOn =~ tr/A-Z/a-z/;
+        $AddOns{$AddOn}->{name} = $AddOn;
+        $AddOns{$AddOn}->{version} = $Version;
+      }
+      elsif ($Line =~ /^\s*#\s*define\s*(GECKO|MONO)_SHA\s*"([^"]+)"/)
+      {
+        my ($AddOn, $Checksum) = ($1, $2);
+        $AddOn =~ tr/A-Z/a-z/;
+        $AddOns{$AddOn}->{$Arch} = $Checksum;
+        $Arch = "";
+      }
+    }
+    close($fh);
+  }
+  else
+  {
+    LogMsg "Could not open 'wine/dlls/appwiz.cpl/addons.c': $!\n";
+    return 0;
+  }
+
+  return UpdateAddOn($AddOns{gecko}, "gecko", "x86") &&
+         UpdateAddOn($AddOns{gecko}, "gecko", "x86_64") &&
+         UpdateAddOn($AddOns{mono},  "mono",  "");
+}
+
+# See also WineTest.pl
+sub SetupWineEnvironment($)
+{
+  my ($Build) = @_;
+
+  $ENV{WINEPREFIX} = "$DataDir/wineprefix-$Build";
+  $ENV{DISPLAY} ||= ":0.0";
+}
+
+# See also WineTest.pl
+sub RunWine($$$)
+{
+  my ($Build, $Cmd, $CmdArgs) = @_;
+
+  my $Magic = `cd '$DataDir/build-$Build' && file $Cmd`;
+  my $Wine = ($Magic =~ /ELF 64/ ? "./wine64" : "./wine");
+  return system("cd '$DataDir/build-$Build' && set -x && ".
+                "time $Wine $Cmd $CmdArgs");
+}
+
+# Setup a brand new WinePrefix ready for use for testing.
+# This way we do it once instead of doing it for every test, thus saving
+# time. Note that this requires using a different wineprefix for each build.
+sub NewWinePrefix($$)
+{
+  my ($Targets, $Build) = @_;
+
+  return 1 if (!$Targets->{wineprefix} or !$Targets->{$Build});
+
+  InfoMsg "\nRecreating the $Build wineprefix\n";
+  SetupWineEnvironment($Build);
+  rmtree($ENV{WINEPREFIX});
+
+  # Crash dialogs cause delays so disable them
+  if (RunWine($Build, "./programs/reg/reg.exe.so", "ADD HKCU\\\\Software\\\\Wine\\\\WineDbg /v ShowCrashDialog /t REG_DWORD /d 0"))
+  {
+    LogMsg "Failed to disable the $Build build crash dialogs: $!\n";
+    return 0;
+  }
+
+  # Ensure the WinePrefix has been fully created before updating the snapshot
+  system("cd '$DataDir/build-$Build' && ./server/wineserver -w");
+
+  return 1;
+}
+
+
 #
 # Setup and command line processing
 #
@@ -154,7 +305,7 @@ $ENV{PATH} = "/usr/lib/ccache:/usr/bin:/bin";
 delete $ENV{ENV};
 
 my %AllTargets;
-map { $AllTargets{$_} = 1 } qw(update win32 wow32 wow64);
+map { $AllTargets{$_} = 1 } qw(update addons build wineprefix win32 wow32 wow64);
 
 my ($Usage, $TargetList, $NoRm);
 while (@ARGV)
@@ -212,15 +363,18 @@ if (defined $Usage)
   }
   print "Usage: $Name0 [--no-rm] [--help] [TARGETS]\n";
   print "\n";
-  print "Performs all the tasks needed for the host to be ready to test new patches: update the Wine source and rebuild the Wine binaries.\n";
+  print "Performs all the tasks needed for the host to be ready to test new patches: update the Wine source and addons, and rebuild the Wine binaries.\n";
   print "\n";
   print "Where:\n";
   print "  TARGETS   Is a comma-separated list of targets to process. By default all\n";
   print "            targets are processed.\n";
   print "            - update: Update Wine's source code.\n";
-  print "            - win32: Rebuild the regular 32 bit Wine.\n";
-  print "            - wow32: Rebuild the 32 bit WoW Wine.\n";
-  print "            - wow64: Rebuild the 64 bit WoW Wine.\n";
+  print "            - build: Update the Wine builds.\n";
+  print "            - addons: Update the Gecko and Mono Wine addons.\n";
+  print "            - wineprefix: Update the wineprefixes.\n";
+  print "            - win32: Apply the above to the regular 32 bit Wine.\n";
+  print "            - wow32: Apply the above to the 32 bit WoW Wine.\n";
+  print "            - wow64: Apply the above to the 64 bit WoW Wine.\n";
   print "  --no-rm   Don't rebuild from scratch.\n";
   print "  --help    Shows this usage message.\n";
   exit 0;
@@ -240,16 +394,25 @@ if ($DataDir =~ /'/)
 
 
 #
-# Run the builds
+# Run the builds and/or tests
 #
 
 CountCPUs();
 
-if (!BuildTestAgentd() ||
-    !GitPull($Targets) ||
-    !BuildWine($Targets, $NoRm, "win32", "") ||
-    !BuildWine($Targets, $NoRm, "wow64", "--enable-win64") ||
-    !BuildWine($Targets, $NoRm, "wow32", "--with-wine64='$DataDir/build-wow64'"))
+if (!BuildTestAgentd() or
+    !GitPull($Targets) or
+    !UpdateAddOns($Targets) or
+    !BuildWine($Targets, $NoRm, "win32", "") or
+    !BuildWine($Targets, $NoRm, "wow64", "--enable-win64") or
+    !BuildWine($Targets, $NoRm, "wow32", "--with-wine64='$DataDir/build-wow64'") or
+    !NewWinePrefix($Targets, "win32") or
+    # The wow32 and wow64 wineprefixes:
+    # - Are essentially identical.
+    # - Must be created after both WoW builds have been updated.
+    # - Make it possible to run the wow32 and wow64 tests in separate prefixes,
+    #   thus ensuring they don't interfere with each other.
+    !NewWinePrefix($Targets, "wow64") or
+    !NewWinePrefix($Targets, "wow32"))
 {
   exit(1);
 }
diff --git a/testbot/bin/build/WineTest.pl b/testbot/bin/build/WineTest.pl
index 3be81485e..518deae99 100755
--- a/testbot/bin/build/WineTest.pl
+++ b/testbot/bin/build/WineTest.pl
@@ -144,6 +144,62 @@ sub BuildWine($$)
 }
 
 
+#
+# Test helpers
+#
+
+# See also WineReconfig.pl
+sub SetupWineEnvironment($)
+{
+  my ($Build) = @_;
+
+  $ENV{WINEPREFIX} = "$DataDir/wineprefix-$Build";
+  $ENV{DISPLAY} ||= ":0.0";
+}
+
+# See also WineReconfig.pl
+sub RunWine($$$)
+{
+  my ($Build, $Cmd, $CmdArgs) = @_;
+
+  my $Magic = `cd '$DataDir/build-$Build' && file $Cmd`;
+  my $Wine = ($Magic =~ /ELF 64/ ? "./wine64" : "./wine");
+  return system("cd '$DataDir/build-$Build' && set -x && ".
+                "time $Wine $Cmd $CmdArgs");
+}
+
+sub DailyWineTest($$$$)
+{
+  my ($Targets, $Build, $BaseTag, $Args) = @_;
+
+  return 1 if (!$Targets->{$Build});
+
+  InfoMsg "\nRunning WineTest in the $Build Wine\n";
+  SetupWineEnvironment($Build);
+
+  # Run WineTest. Ignore the exit code since it returns non-zero whenever
+  # there are test failures.
+  RunWine($Build, "./programs/winetest/winetest.exe.so",
+          "-c -o '../$Build.report' -t $BaseTag-$Build ". ShArgv2Cmd(@$Args));
+  if (!-f "$Build.report")
+  {
+    LogMsg "WineTest did not produce a report file\n";
+    return 0;
+  }
+
+  # Send the report to the website
+  if ($Targets->{submit} and
+      RunWine($Build, "./programs/winetest/winetest.exe.so",
+              "-c -s '../$Build.report'"))
+  {
+    LogMsg "WineTest failed to send the $Build report\n";
+    # Soldier on in case it's just a network issue
+  }
+
+  return 1;
+}
+
+
 #
 # Setup and command line processing
 #
@@ -152,9 +208,9 @@ $ENV{PATH} = "/usr/lib/ccache:/usr/bin:/bin";
 delete $ENV{ENV};
 
 my %AllTargets;
-map { $AllTargets{$_} = 1 } qw(win32 wow32 wow64);
+map { $AllTargets{$_} = 1 } qw(win32 wow32 wow64 submit);
 
-my ($Usage, $TargetList, $Action, $FileName);
+my ($Usage, $TargetList, $Action, $FileName, $BaseTag);
 while (@ARGV)
 {
   my $Arg = shift @ARGV;
@@ -177,6 +233,12 @@ while (@ARGV)
   {
     $Action = $Arg;
   }
+  elsif (($Action || "") eq "winetest")
+  {
+    $BaseTag = $Arg;
+    # The remaining arguments are meant for WineTest
+    last;
+  }
   elsif (!defined $FileName)
   {
     if (IsValidFileName($Arg))
@@ -230,6 +292,23 @@ if (!defined $Usage)
     Error "you must specify the action to perform\n";
     $Usage = 2;
   }
+  elsif ($Action eq "winetest")
+  {
+    if (!defined $BaseTag)
+    {
+      Error "you must specify a base tag for WineTest\n";
+      $Usage = 2;
+    }
+    elsif ($BaseTag =~ m/^([\w_.\-]+)$/)
+    {
+      $BaseTag = $1;
+    }
+    else
+    {
+      Error "invalid WineTest base tag '$BaseTag'\n";
+      $Usage = 2;
+    }
+  }
   elsif ($Action ne "build")
   {
     Error "invalid '$Action' action\n";
@@ -245,16 +324,23 @@ if (!defined $Usage)
 if (defined $Usage)
 {
   print "Usage: $Name0 [--help] TARGETS build PATCH\n";
+  print "or     $Name0 [--help] TARGETS winetest BASETAG ARGS\n";
   print "\n";
-  print "Applies the specified patch and rebuilds Wine.\n";
+  print "Tests the specified patch or runs WineTest in Wine.\n";
   print "\n";
   print "Where:\n";
-  print "  TARGETS   Is a comma-separated list of targets for the build.\n";
+  print "  TARGETS   Is a comma-separated list of targets for the specified action.\n";
   print "            - win32: The regular 32 bit Wine build.\n";
   print "            - wow32: The 32 bit WoW Wine build.\n";
   print "            - wow64: The 64 bit WoW Wine build.\n";
+  print "            - submit: Send the WineTest result to the website.\n";
   print "  build     Verify that the patch compiles.\n";
   print "  PATCH     Is the staging file containing the patch to test.\n";
+  print "  winetest  Run WineTest and submit the result to the website if the submit\n";
+  print "            task was specified.\n";
+  print "  BASETAG   Is the tag for this WineTest run. Note that the build type is\n";
+  print "            automatically added to this tag.\n";
+  print "  ARGS      The WineTest arguments.\n";
   print "  --help    Shows this usage message.\n";
   exit $Usage;
 }
@@ -267,16 +353,28 @@ if ($DataDir =~ /'/)
 
 
 #
-# Run the builds
+# Run the builds and tests
 #
 
+# Clean up old reports
+map { unlink("$_.report") } keys %AllTargets;
+
 CountCPUs();
 
-my $Impacts = ApplyPatch($FileName);
-exit(1) if (!$Impacts or
-            !BuildWine($Targets, "win32") or
-            !BuildWine($Targets, "wow64") or
-            !BuildWine($Targets, "wow32"));
+if ($Action eq "build")
+{
+  my $Impacts = ApplyPatch($FileName);
+  exit(1) if (!$Impacts or
+              !BuildWine($Targets, "win32") or
+              !BuildWine($Targets, "wow64") or
+              !BuildWine($Targets, "wow32"));
+}
+elsif ($Action eq "winetest")
+{
+  exit(1) if (!DailyWineTest($Targets, "win32", $BaseTag, \@ARGV) or
+              !DailyWineTest($Targets, "wow64", $BaseTag, \@ARGV) or
+              !DailyWineTest($Targets, "wow32", $BaseTag, \@ARGV));
+}
 
 LogMsg "ok\n";
 exit;
diff --git a/testbot/lib/WineTestBot/LogUtils.pm b/testbot/lib/WineTestBot/LogUtils.pm
index 363f80827..2d3042ec7 100644
--- a/testbot/lib/WineTestBot/LogUtils.pm
+++ b/testbot/lib/WineTestBot/LogUtils.pm
@@ -458,6 +458,7 @@ sub GetLogFileNames($;$)
   my ($Dir, $IncludeOld) = @_;
 
   my @Candidates = ("exe32.report", "exe64.report",
+                    "win32.report", "wow32.report", "wow64.report",
                     "log", "err");
   push @Candidates, "log.old", "err.old" if ($IncludeOld);
 
@@ -472,10 +473,13 @@ sub GetLogFileNames($;$)
 my %_LogFileLabels = (
   "exe32.report" => "32 bit Windows report",
   "exe64.report" => "64 bit Windows report",
-  "err"          => "task errors",
+  "win32.report" => "32 bit Wine report",
+  "wow32.report" => "32 bit WoW Wine report",
+  "wow64.report" => "64 bit Wow Wine report",
   "log"          => "task log",
-  "err.old"      => "old task errors",
+  "err"          => "task errors",
   "log.old"      => "old logs",
+  "err.old"      => "old task errors",
 );
 
 =pod
diff --git a/testbot/lib/WineTestBot/Utils.pm b/testbot/lib/WineTestBot/Utils.pm
index 111a56589..f64310b6b 100644
--- a/testbot/lib/WineTestBot/Utils.pm
+++ b/testbot/lib/WineTestBot/Utils.pm
@@ -29,7 +29,7 @@ use Exporter 'import';
 our @EXPORT = qw(MakeSecureURL SecureConnection GenerateRandomString
                  OpenNewFile CreateNewFile CreateNewLink CreateNewDir
                  DurationToString BuildEMailRecipient IsValidFileName
-                 ShQuote);
+                 ShQuote ShArgv2Cmd);
 
 use Fcntl;
 
@@ -220,4 +220,21 @@ sub ShQuote($)
   return "\"$Str\"";
 }
 
+=pod
+=over 12
+
+=item C<ShArgv2Cmd()>
+
+Converts an argument list into a command line suitable for use in a shell.
+
+See also ShQuote().
+
+=back
+=cut
+
+sub ShArgv2Cmd(@)
+{
+  return join(' ', map { /[^a-zA-Z0-9\/.,+_-]/ ? ShQuote($_) : $_ } @_);
+}
+
 1;
-- 
2.18.0



More information about the wine-devel mailing list