[tools 3/6] testbot: Match the failures with the test logs.

Francois Gouget fgouget at codeweavers.com
Wed Jun 15 11:21:35 CDT 2022


This augments the .errors files with information mapping errors to known
failures.

Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=48912
Signed-off-by: Francois Gouget <fgouget at codeweavers.com>
---
It would probably be more space-efficient to have one line per known
failure with a list of all the matching error indices. But that does not
match the way we use the information and this should not change all that
much in terms of space usage. So I went with the simpler error index to
known failures mapping.
---
 testbot/lib/WineTestBot/LogUtils.pm | 180 +++++++++++++++++++++++++++-
 1 file changed, 178 insertions(+), 2 deletions(-)

diff --git a/testbot/lib/WineTestBot/LogUtils.pm b/testbot/lib/WineTestBot/LogUtils.pm
index 2e3de83b8..b8aaaa43c 100644
--- a/testbot/lib/WineTestBot/LogUtils.pm
+++ b/testbot/lib/WineTestBot/LogUtils.pm
@@ -36,7 +36,10 @@ our @EXPORT = qw(GetLogFileNames GetLogLabel
 use Algorithm::Diff;
 use File::Basename;
 
+use ObjectModel::Collection; # For CombineKey()
 use WineTestBot::Config; # For $MaxUnitSize
+use WineTestBot::Failures;
+use WineTestBot::Tasks; # FIXME Hack for SaveLogFailures()
 use WineTestBot::Utils; # For LocaleName()
 
 
@@ -187,6 +190,12 @@ sub _AddLogError($$$;$$)
 Returns a hashtable containing a summary of the task log:
 =over
 
+=item LogName
+The log file basename.
+
+=item LogPath
+The full log file path.
+
 =item Type
 'tests' if the task ran Wine tests and 'build' otherwise.
 
@@ -913,6 +922,10 @@ copy of that line (with CR/LF converted to a simple LF).
 The format for new error lines is identical to that for old errors but with a
 different type.
 
+=item f <errindex> <failureid1>...
+Failure lines contain the index of the error in the error group and a list of
+the known failures they match (normally at most one).
+
 =back
 =back
 =cut
@@ -981,6 +994,10 @@ sub LoadLogErrorsFromFh($$)
     {
       _AddLogError($LogInfo, $LogInfo->{CurGroup}, $Value, $Property, "new");
     }
+    elsif ($Type eq "f")
+    {
+      $LogInfo->{CurGroup}->{Failures}->{$Property} = [ split / /, $Value ];
+    }
     else
     {
       $LogInfo->{BadLog} = "$LogInfo->{LineNo}: Found an unknown line type ($Type)";
@@ -1058,8 +1075,12 @@ sub _WriteLogErrorsToFh($$)
     print $Fh "g $Group->{LineNo} $GroupName\n";
     foreach my $Index (0..$#{$Group->{Errors}})
     {
+      my $LineNo = $Group->{LineNos}->[$Index];
       my $IsNew = $Group->{IsNew}->[$Index] ? "n" : "o";
-      print $Fh "$IsNew $Group->{LineNos}->[$Index] $Group->{Errors}->[$Index]\n";
+      print $Fh "$IsNew $LineNo $Group->{Errors}->[$Index]\n";
+
+      my $Failures = $Group->{Failures}->{$Index};
+      print $Fh "f $Index @$Failures\n" if ($Failures);
     }
   }
 }
@@ -1299,6 +1320,158 @@ sub TagNewErrors($$)
   }
 }
 
+=pod
+=over 12
+
+=item C<MatchLogFailures()>
+
+Checks the errors against known failures.
+
+The $LogInfo structure is augmented with the following fields:
+=over
+
+=item ErrGroups
+=over
+
+=item Failures
+A hashtable mapping error indices to the list of matching known
+failure ids.
+
+=back
+=back
+
+Returns a hashtable containing a summary of the log failures:
+=over
+
+=item LogName
+The log file basename.
+
+=item Collection
+A collection containing the relevant Failure objects.
+
+=item Failures
+A hashtable indexed by the failure ids. Each entry contains:
+
+=over
+
+=item Failure
+The failure object.
+
+=item NewCount
+A count errors matched by this known failure that were tagged as new.
+
+=item OldCount
+A count errors matched by this known failure that were tagged as old.
+
+=back
+
+=back
+=back
+=cut
+
+sub MatchLogFailures($$)
+{
+  my ($LogInfo, $Task) = @_;
+
+  my $LogFailures = {
+    Task => $Task,
+    LogName => $LogInfo->{LogName}
+  };
+  return $LogFailures if (!$LogInfo->{ErrCount});
+
+  my %FailureTree;
+  my $ConfigName = $Task->VMName .":$LogInfo->{LogName}";
+
+  $LogFailures->{Collection} = CreateFailures();
+  foreach my $Failure (@{$LogFailures->{Collection}->GetItems()})
+  {
+    # Ignore failures that don't apply to this configuration
+    my $ConfigRegExp = $Failure->ConfigRegExp;
+    my $Match = eval { $ConfigRegExp and $ConfigName =~ /$ConfigRegExp/ };
+    next if (!$Match);
+
+    my $UnitFailures = $FailureTree{$Failure->ErrorGroup}->{$Failure->TestUnit} ||= [];
+    push @$UnitFailures, $Failure;
+  }
+
+  foreach my $GroupName (@{$LogInfo->{ErrGroupNames}})
+  {
+    next if (!$FailureTree{$GroupName});
+
+    my $Group = $LogInfo->{ErrGroups}->{$GroupName};
+    foreach my $ErrIndex (0..$#{$Group->{Errors}})
+    {
+      my $Line = $Group->{Errors}->[$ErrIndex];
+      my $TestUnit = $Line =~ /^([_a-z0-9]+)\.c:\d+:/ ? $1 : "";
+      my $UnitFailures = $FailureTree{$GroupName}->{$TestUnit};
+      next if (!$UnitFailures);
+
+      foreach my $UnitFailure (@$UnitFailures)
+      {
+        my $RegExp = $UnitFailure->FailureRegExp;
+        my $Match = eval { $RegExp and $Line =~ /$RegExp/ };
+        next if (!$Match);
+
+        my $LineFailures = $Group->{Failures}->{$ErrIndex} ||= [];
+        push @$LineFailures, $UnitFailure->Id;
+
+        my $LogFailure = $LogFailures->{Failures}->{$UnitFailure->Id};
+        if (!$LogFailure)
+        {
+          $LogFailure = $LogFailures->{Failures}->{$UnitFailure->Id} =
+                        { Failure => $UnitFailure };
+        }
+        my $Count = $Group->{IsNew}->[$ErrIndex] ? "NewCount" : "OldCount";
+        $LogFailure->{$Count}++;
+      }
+    }
+  }
+
+  return $LogFailures;
+}
+
+=pod
+=over 12
+
+=item C<SaveLogFailures()>
+
+Updates the TaskFailures objects for the task and log specified by the
+$LogFailures structure.
+
+Note that this implies deleting any preexisting TaskFailure to avoid leaving
+obsolete data.
+
+=back
+=cut
+
+sub SaveLogFailures($)
+{
+  my ($LogFailures) = @_;
+
+  # FIXME $Task->Failures->AddFilter() cannot be undone and impacts every
+  #       future use of $Task->Failures. So add the filter on a throw away
+  #       clone to not end up with a nonsensical filter.
+  my $TaskFailures = $LogFailures->{Task}->Failures->Clone();
+  $TaskFailures->AddFilter("TaskLog", [$LogFailures->{LogName}]);
+  my $ErrMessage = $TaskFailures->DeleteAll();
+  return $ErrMessage if (defined $ErrMessage);
+  return undef if (!$LogFailures->{Failures});
+
+  foreach my $LogFailure (values %{$LogFailures->{Failures}})
+  {
+    my $TaskFailure = $LogFailure->{Failure}->TaskFailures->Add();
+    my $OldKey = $TaskFailure->GetKey();
+    $TaskFailure->Task($LogFailures->{Task});
+    $TaskFailure->TaskLog($LogFailures->{LogName});
+    $TaskFailure->KeyChanged($OldKey, $TaskFailure->GetKey());
+    $TaskFailure->NewCount($LogFailure->{NewCount});
+    $TaskFailure->OldCount($LogFailure->{OldCount});
+  }
+
+  (my $_ErrKey, my $_ErrProperty, $ErrMessage) = $LogFailures->{Collection}->Save();
+  return $ErrMessage;
+}
+
 
 #
 # Log errors caching [Part 2]
@@ -1322,8 +1495,11 @@ sub CreateLogErrorsCache($;$)
     # Don't mark the errors as new if there is no reference WineTest report
     # as this would cause false positives.
   }
+  my $LogFailures = MatchLogFailures($LogInfo, $Task) if ($Task);
 
-  return _SaveLogErrors($LogInfo);
+  my $ErrMessage = _SaveLogErrors($LogInfo);
+  $ErrMessage ||= SaveLogFailures($LogFailures) if ($Task);
+  return $ErrMessage;
 }
 
 
-- 
2.30.2




More information about the wine-devel mailing list