[PATCH] testbot: Let LogUtils detect and return the list of errors.

Francois Gouget fgouget at codeweavers.com
Thu Aug 23 15:58:21 CDT 2018


LogUtils now exports GetLogErrors() which analyzes the specified log and
associated extra errors file and returns per-module lists of errors. For
test reports modules correspond to dlls or programs and for other types
of log there is a single module called "". Errors from the extra errors
file go to an extra module.
JobDetails now uses that to offload most of the work of identifying
errors. It now can also show errors from more than one report / log when
in summary mode.

Signed-off-by: Francois Gouget <fgouget at codeweavers.com>
---
 testbot/lib/WineTestBot/LogUtils.pm | 140 +++++++++++++++-
 testbot/web/JobDetails.pl           | 237 +++++++++++++---------------
 2 files changed, 247 insertions(+), 130 deletions(-)

diff --git a/testbot/lib/WineTestBot/LogUtils.pm b/testbot/lib/WineTestBot/LogUtils.pm
index 6f7cef5e8..a5a1a68b5 100644
--- a/testbot/lib/WineTestBot/LogUtils.pm
+++ b/testbot/lib/WineTestBot/LogUtils.pm
@@ -27,7 +27,7 @@ WineTestBot::LogUtils - Provides functions to parse task logs
 
 
 use Exporter 'import';
-our @EXPORT = qw(GetLogFileNames GetLogLabel
+our @EXPORT = qw(GetLogFileNames GetLogLabel GetLogErrors
                  GetLogLineCategory GetReportLineCategory
                  RenameReferenceLogs RenameTaskLogs
                  ParseTaskLog ParseWineTestReport);
@@ -547,13 +547,17 @@ sub GetLogFileNames($;$)
 
   my @Candidates = ("exe32.report", "exe64.report",
                     "win32.report", "wow32.report", "wow64.report",
-                    "log", "log.err");
-  push @Candidates, "old_log", "old_log.err" if ($IncludeOld);
+                    "log");
+  push @Candidates, "old_log" if ($IncludeOld);
 
   my @Logs;
   foreach my $FileName (@Candidates)
   {
-    push @Logs, $FileName if (-f "$Dir/$FileName" and !-z "$Dir/$FileName");
+    if ((-f "$Dir/$FileName" and !-z "$Dir/$FileName") or
+        (-f "$Dir/$FileName.err" and !-z "$Dir/$FileName.err"))
+    {
+      push @Logs, $FileName;
+    }
   }
   return \@Logs;
 }
@@ -565,9 +569,7 @@ my %_LogFileLabels = (
   "wow32.report" => "32 bit WoW Wine report",
   "wow64.report" => "64 bit Wow Wine report",
   "log"          => "task log",
-  "log.err"      => "task errors",
   "old_log"      => "old logs",
-  "old_log.err"  => "old task errors",
 );
 
 =pod
@@ -586,4 +588,130 @@ sub GetLogLabel($)
   return $_LogFileLabels{$LogFileName};
 }
 
+
+sub _DumpErrors($$$)
+{
+  my ($Label, $Groups, $Errors) = @_;
+
+  print STDERR "$Label:\n";
+  print STDERR "  Groups=", scalar(@$Groups), " [", join(",", @$Groups), "]\n";
+  my @ErrorKeys = sort keys %$Errors;
+  print STDERR "  Errors=", scalar(@ErrorKeys), " [", join(",", @ErrorKeys), "]\n";
+  foreach my $GroupName (@$Groups)
+  {
+    print STDERR "  [$GroupName]\n";
+    print STDERR "    [$_]\n" for (@{$Errors->{$GroupName}});
+  }
+}
+
+sub _AddErrorGroup($$$)
+{
+  my ($Groups, $Errors, $GroupName) = @_;
+
+  # In theory the error group names are all unique. But, just in case, make
+  # sure we don't overwrite $Errors->{$GroupName}.
+  if (!$Errors->{$GroupName})
+  {
+    push @$Groups, $GroupName;
+    $Errors->{$GroupName} = [];
+  }
+  return $Errors->{$GroupName};
+}
+
+=pod
+=over 12
+
+=item C<GetLogErrors()>
+
+Analyzes the specified log and associated error file to filter out unimportant
+messages and only return the errors, split by module (for Wine reports that's
+per dll / program being tested).
+
+Returns a list of modules containing errors, and a hashtable containing the list of errors for each module.
+
+=back
+=cut
+
+sub GetLogErrors($)
+{
+  my ($LogFileName) = @_;
+
+  my ($IsReport, $GetCategory);
+  if ($LogFileName =~ /\.report$/)
+  {
+    $IsReport = 1;
+    $GetCategory = \&GetReportLineCategory;
+  }
+  else
+  {
+    $GetCategory = \&GetLogLineCategory;
+  }
+
+  my $NoLog = 1;
+  my $Groups = [];
+  my $Errors = {};
+  if (open(my $LogFile, "<", $LogFileName))
+  {
+    $NoLog = 0;
+    my $CurrentModule = "";
+    my $CurrentGroup;
+    foreach my $Line (<$LogFile>)
+    {
+      $Line =~ s/\s*$//;
+      if ($IsReport and $Line =~ /^([_.a-z0-9-]+):[_a-z0-9]* start /)
+      {
+        $CurrentModule = $1;
+        $CurrentGroup = undef;
+        next;
+      }
+
+      next if ($GetCategory->($Line) !~ /error/);
+
+      if ($Line =~ m/^[^:]+:([^:]*)(?::[0-9a-f]+)? done \(258\)/)
+      {
+        my $Unit = $1;
+        $Line = $Unit ne "" ? "$Unit: Timeout" : "Timeout";
+      }
+      if (!$CurrentGroup)
+      {
+        $CurrentGroup = _AddErrorGroup($Groups, $Errors, $CurrentModule);
+      }
+      push @$CurrentGroup, $Line;
+    }
+    close($LogFile);
+  }
+  elsif (-f $LogFileName)
+  {
+    $NoLog = 0;
+    my $Group = _AddErrorGroup($Groups, $Errors, "TestBot errors");
+    push @$Group, "Could not open '". basename($LogFileName) ."' for reading: $!";
+  }
+
+  if (open(my $LogFile, "<", "$LogFileName.err"))
+  {
+    $NoLog = 0;
+    # Add the related extra errors
+    my $CurrentGroup;
+    foreach my $Line (<$LogFile>)
+    {
+      $Line =~ s/\s*$//;
+      if (!$CurrentGroup)
+      {
+        my $GroupName = $IsReport ? "Report errors" : "Task errors";
+        $CurrentGroup = _AddErrorGroup($Groups, $Errors, $GroupName);
+      }
+      push @$CurrentGroup, $Line;
+    }
+    close($LogFile);
+  }
+  elsif (-f "$LogFileName.err")
+  {
+    $NoLog = 0;
+    my $Group = _AddErrorGroup($Groups, $Errors, "TestBot errors");
+    push @$Group, "Could not open '". basename($LogFileName) .".err' for reading: $!";
+  }
+
+  return $NoLog ? (undef, undef) : ($Groups, $Errors);
+}
+
 1;
diff --git a/testbot/web/JobDetails.pl b/testbot/web/JobDetails.pl
index e1fe20f18..37185eb1f 100644
--- a/testbot/web/JobDetails.pl
+++ b/testbot/web/JobDetails.pl
@@ -2,6 +2,7 @@
 # Job details page
 #
 # Copyright 2009 Ge van Geldorp
+# Copyright 2012-2014,2017-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
@@ -229,45 +230,6 @@ sub GeneratePage($)
   $self->SUPER::GeneratePage();
 }
 
-=pod
-=over 12
-
-=item C<GetHtmlLine()>
-
-Determines if the log line should be shown, how, and escapes it so it is valid
-HTML.
-
-When not showing the full log, returns undef except for error messages which
-are the only lines tha should be shown.
-When showing the full log error messages and other lines of interest are
-highlighted to make the log more readable.
-
-=back
-=cut
-
-sub GetHtmlLine($$$$)
-{
-  my ($self, $LogName, $FullLog, $Line) = @_;
-
-  my $Category = $LogName =~ /\.report$/ ?
-                 GetReportLineCategory($Line) :
-                 GetLogLineCategory($Line);
-  return undef if ($Category !~ /error/ and !$FullLog);
-
-  my $Html = $self->escapeHTML($Line);
-  if (!$FullLog and $Html =~ m/^[^:]+:([^:]*)(?::[0-9a-f]+)? done \(258\)/)
-  {
-    my $Unit = $1;
-    return $Unit ne "" ? "$Unit: Timeout" : "Timeout";
-  }
-  if ($FullLog and $Category ne "none")
-  {
-    # Highlight all line categories in the full log
-    $Html =~ s~^(.*\S)\s*\r?$~<span class='log-$Category'>$1</span>~;
-  }
-  return $Html;
-}
-
 sub InitMoreInfo($)
 {
   my ($self) = @_;
@@ -283,12 +245,6 @@ sub InitMoreInfo($)
     my $TaskDir = $StepTask->GetTaskDir();
     foreach my $Log (@{GetLogFileNames($TaskDir, 1)})
     {
-      if ($Log =~ s/^err/log/)
-      {
-        # We don't want separate entries for log* and err* but we also want a
-        # log* entry even if only err* exists.
-        next if (($More->{$Key}->{Logs}->[-1] || "") eq $Log);
-      }
       push @{$More->{$Key}->{Logs}}, $Log;
       $More->{$Key}->{Full} = $Log if (uri_escape($Log) eq $Value);
     }
@@ -348,6 +304,47 @@ sub GenerateMoreInfoLink($$$;$)
   print "<div class='TaskMoreInfoLink'>$Html</div>\n";
 }
 
+sub GetErrorCategory($)
+{
+  return "error";
+}
+
+sub GenerateFullLog($$;$)
+{
+  my ($self, $FileName, $Header) = @_;
+
+  my $GetCategory = $FileName =~ /\.err$/ ? \&GetErrorCategory :
+                    $FileName =~ /\.report$/ ? \&GetReportLineCategory :
+                    \&GetLogLineCategory;
+
+  my $IsEmpty = 1;
+  if (open(my $LogFile, "<", $FileName))
+  {
+    foreach my $Line (<$LogFile>)
+    {
+      $Line =~ s/\s*$//;
+      if ($IsEmpty)
+      {
+        print $Header if (defined $Header);
+        print "<pre><code>";
+        $IsEmpty = 0;
+      }
+
+      my $Category = $GetCategory->($Line);
+      my $Html = $self->escapeHTML($Line);
+      if ($Category ne "none")
+      {
+        $Html =~ s~^(.*\S)\s*\r?$~<span class='log-$Category'>$1</span>~;
+      }
+      print "$Html\n";
+    }
+    close($LogFile);
+  }
+  print "</code></pre>\n" if (!$IsEmpty);
+
+  return $IsEmpty;
+}
+
 sub GenerateBody($)
 {
   my ($self) = @_;
@@ -386,109 +383,101 @@ sub GenerateBody($)
       $self->GenerateMoreInfoLink($Key, "final screenshot", "Screenshot");
     }
 
-    foreach my $Log (@{$MoreInfo->{Logs}})
+    my $ReportCount;
+    foreach my $LogName (@{$MoreInfo->{Logs}})
     {
-      $self->GenerateMoreInfoLink($Key, GetLogLabel($Log), "Full", $Log);
+      $self->GenerateMoreInfoLink($Key, GetLogLabel($LogName), "Full", $LogName);
+      $ReportCount++ if ($LogName !~ /^old_/ and $LogName =~ /\.report$/);
     }
     print "</div>\n";
 
-    my $LogName = $MoreInfo->{Full} || $MoreInfo->{Logs}->[0] || "log";
-    my $ErrName = "$LogName.err";
-
-    my ($EmptyDiag, $LogFirst) = (undef, 1);
-    if (open(my $LogFile, "<", "$TaskDir/$LogName"))
+    if ($MoreInfo->{Full})
     {
-      my $HasLogEntries;
-      my ($CurrentDll, $PrintedDll) = ("", "");
-      foreach my $Line (<$LogFile>)
-      {
-        $HasLogEntries = 1;
-        chomp $Line;
-        $CurrentDll = $1 if ($Line =~ m/^([_.a-z0-9-]+):[_a-z0-9]* start /);
-        my $Html = $self->GetHtmlLine($LogName, $MoreInfo->{Full}, $Line);
-        next if (!defined $Html);
+      #
+      # Show this log in full, highlighting the important lines
+      #
 
-        if ($PrintedDll ne $CurrentDll && !$MoreInfo->{Full})
+      my $LogIsEmpty = $self->GenerateFullLog("$TaskDir/$MoreInfo->{Full}");
+      my $EmptyDiag;
+      if ($LogIsEmpty)
+      {
+        if ($StepTask->Status eq "canceled")
         {
-          print "</code></pre>" if (!$LogFirst);
-          print "<div class='LogDllName'>$CurrentDll:</div><pre><code>";
-          $PrintedDll = $CurrentDll;
-          $LogFirst = 0;
+          $EmptyDiag = "No log, task was canceled\n";
         }
-        elsif ($LogFirst)
+        elsif ($StepTask->Status eq "skipped")
         {
-          print "<pre><code>";
-          $LogFirst = 0;
+          $EmptyDiag = "No log, task skipped\n";
         }
-        print "$Html\n";
-      }
-      close($LogFile);
-
-      if (!$LogFirst)
-      {
-        print "</code></pre>\n";
-      }
-      elsif ($HasLogEntries)
-      {
-        # Here we know we did not show the full log since it was not empty,
-        # and yet we did not show anything to the user. But don't claim there
-        # is no failure if the error log is not empty.
-        if (-z "$TaskDir/$ErrName")
+        else
         {
-          print "No ". ($StepTask->Type eq "single" ||
-                        $StepTask->Type eq "suite" ? "test" : "build") .
-                " failures found";
-          $LogFirst = 0;
+          print "Empty log\n";
+          $LogIsEmpty = 0;
         }
       }
-      elsif ($StepTask->Status eq "canceled")
-      {
-        $EmptyDiag = "<p>No log, task was canceled</p>\n";
-      }
-      elsif ($StepTask->Status eq "skipped")
-      {
-        $EmptyDiag = "<p>No log, task skipped</p>\n";
-      }
-      else
-      {
-        print "Empty log";
-        $LogFirst = 0;
-      }
+
+      # And append the associated extra errors
+      my $ErrHeader = $MoreInfo->{Full} =~ /\.report/ ? "report" : "task";
+      $ErrHeader = "old $ErrHeader" if ($MoreInfo->{Full} =~ /^old_/);
+      $ErrHeader = "<div class='HrTitle'>". ucfirst($ErrHeader) ." errors<div class='HrLine'></div></div>";
+      my $ErrIsEmpty = $self->GenerateFullLog("$TaskDir/$MoreInfo->{Full}.err", $ErrHeader);
+      print $EmptyDiag if ($ErrIsEmpty and defined $EmptyDiag);
     }
     else
     {
-      print "No log". ($StepTask->Status =~ /^(?:queued|running)$/ ? " yet" : "");
-      $LogFirst = 0;
-    }
+      #
+      # Show a summary of the errors from all the reports and logs
+      #
 
-    if (open(my $ErrFile, "<", "$TaskDir/$ErrName"))
-    {
-      my $ErrFirst = 1;
-      foreach my $Line (<$ErrFile>)
+      # Figure out which logs / reports actually have errors
+      my $LogSummaries;
+      foreach my $LogName (@{$MoreInfo->{Logs}})
+      {
+        next if ($LogName =~ /^old_/);
+        my ($Groups, $Errors) = GetLogErrors("$TaskDir/$LogName");
+        next if (!$Groups or !@$Groups);
+        $LogSummaries->{$LogName}->{Groups} = $Groups;
+        $LogSummaries->{$LogName}->{Errors} = $Errors;
+      }
+      my $ShowLogName = ($ReportCount > 1 or scalar(keys %$LogSummaries) > 1);
+
+      my $LogIsEmpty = 1;
+      foreach my $LogName (@{$MoreInfo->{Logs}})
       {
-        chomp $Line;
-        if ($ErrFirst)
+        next if (!$LogSummaries->{$LogName});
+        $LogIsEmpty = 0;
+
+        if ($ShowLogName)
+        {
+          # Show the log / report name to avoid ambiguity
+          my $Label = ucfirst(GetLogLabel($LogName));
+          print "<div class='HrTitle'>$Label<div class='HrLine'></div></div>\n";
+        }
+
+        my $Summary = $LogSummaries->{$LogName};
+        foreach my $GroupName (@{$Summary->{Groups}})
         {
-          if (!$LogFirst)
-          {
-            print "<div class='HrTitle'>".
-                  ($ErrName =~ /^old_/ ? "Old errors" : "Extra errors") .
-                  "<div class='HrLine'></div></div>\n";
-          }
+          print "<div class='LogDllName'>$GroupName:</div>\n" if ($GroupName);
           print "<pre><code>";
-          $ErrFirst = 0;
+          print $self->escapeHTML($_), "\n" for (@{$Summary->{Errors}->{$GroupName}});
+          print "</code></pre>\n";
         }
-        print $self->GetHtmlLine($ErrName, 1, $Line), "\n";
       }
-      close($ErrFile);
 
-      if (!$ErrFirst)
+      if ($LogIsEmpty)
       {
-        print "</code></pre>\n";
-      }
-      elsif (defined $EmptyDiag)
-      {
-        print $EmptyDiag;
+        if ($StepTask->Status eq "canceled")
+        {
+          print "No log, task was canceled\n";
+        }
+        elsif ($StepTask->Status eq "skipped")
+        {
+          print "No log, task skipped\n";
+        }
+        else
+        {
+          print "No errors\n";
+        }
       }
     }
   }
-- 
2.18.0



More information about the wine-devel mailing list