Wine-Devel
By thread
wine-devel@list.winehq.org
By month
Messages by month
- ----- 2026 -----
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2006 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2005 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2004 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2003 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2002 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2001 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
June 2022
- 68 participants
- 3274 messages
[tools 4/6] testbot/web: Link job details to the related known failures.
by Francois Gouget
When a log contains a known failure, link it to the corresponding
failure details page(s).
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=48912
Signed-off-by: Francois Gouget <fgouget(a)codeweavers.com>
---
testbot/web/JobDetails.pl | 54 +++++++++++++++++++++++++++++++++++----
1 file changed, 49 insertions(+), 5 deletions(-)
diff --git a/testbot/web/JobDetails.pl b/testbot/web/JobDetails.pl
index 7860a4c9d..efdd4d9b0 100644
--- a/testbot/web/JobDetails.pl
+++ b/testbot/web/JobDetails.pl
@@ -237,6 +237,7 @@ our @ISA = qw(ObjectModel::CGI::CollectionPage);
use URI::Escape;
use WineTestBot::Config;
+use WineTestBot::Failures;
use WineTestBot::Jobs;
use WineTestBot::Log; # For Elapsed()
use WineTestBot::LogUtils;
@@ -260,6 +261,7 @@ sub _initialize($$$)
{
$self->SetRefreshInterval(30);
}
+ $self->{Failures} = CreateFailures();
}
sub GetJob($)
@@ -378,6 +380,30 @@ sub GenerateMoreInfoLink($$$$;$$)
print "<div class='TaskMoreInfoLink'>$Html</div>\n";
}
+sub GetFailureLink($$;$)
+{
+ my ($self, $Id, $Str) = @_;
+
+ my $Failure = $self->{Failures}->GetItem($Id);
+ $Str ||= $Failure ? $Failure->BugId : "F$Id";
+ my $Desc = $Failure->BugDescription if ($Failure);
+ $Desc = ": ". $self->escapeHTML($Desc) if ($Desc);
+ return "<a href='FailureDetails.pl?Key=$Id' title='Failure $Id$Desc'>$Str</a>";
+}
+
+sub GetFailureLinks($$)
+{
+ my ($self, $FailureIds) = @_;
+
+ return join(" ", map { $self->GetFailureLink($_) }
+ sort {
+ my $FA = $self->{Failures}->GetItem($a);
+ my $FB = $self->{Failures}->GetItem($b);
+ return ($FA ? $FA->BugId : 0) <=> ($FB ? $FB->BugId : 0)
+ || $a <=> $b;
+ } @$FailureIds);
+}
+
sub GenerateFullLog($$$$)
{
my ($self, $Dir, $LogName, $HideLog) = @_;
@@ -400,14 +426,18 @@ sub GenerateFullLog($$$$)
print "<pre class='log-note'>Some WineTest results could not be used to detect new errors: $LogInfo->{BadRef}</pre>\n";
}
- my %ErrCategory;
+ my (%ErrCategory, %ErrFailures);
foreach my $GroupName (@{$LogInfo->{ErrGroupNames}})
{
my $Group = $LogInfo->{ErrGroups}->{$GroupName};
for my $ErrIndex (0..$#{$Group->{Errors}})
{
my $LineNo = $Group->{LineNos}->[$ErrIndex];
- $ErrCategory{$LineNo} = $Group->{IsNew}->[$ErrIndex] ? "fullnew" : "error";
+ if ($LineNo)
+ {
+ $ErrCategory{$LineNo} = $Group->{IsNew}->[$ErrIndex] ? "fullnew" : "error";
+ $ErrFailures{$LineNo} = $Group->{Failures}->{$ErrIndex};
+ }
}
}
@@ -435,6 +465,11 @@ sub GenerateFullLog($$$$)
{
$Html =~ s~^(.*\S)\s*\r?$~<span class='log-$Category'>$1</span>~;
}
+ if ($ErrFailures{$LineNo})
+ {
+ print "[", $self->GetFailureLinks($ErrFailures{$LineNo}), "] ";
+ $Html = $self->GetFailureLink($ErrFailures{$LineNo}->[0], $Html);
+ }
print "$Html\n";
}
close($LogFile);
@@ -464,6 +499,11 @@ sub GenerateFullLog($$$$)
my $Line = $Group->{Errors}->[$ErrIndex];
my $Category = $Group->{IsNew}->[$ErrIndex] ? "new" : "error";
my $Html = $self->escapeHTML($Line);
+ if ($Group->{Failures}->{$ErrIndex})
+ {
+ print "[", $self->GetFailureLinks($Group->{Failures}->{$ErrIndex}), "] ";
+ $Html = $self->GetFailureLink($Group->{Failures}->{$ErrIndex}->[0], $Html);
+ }
print "<span class='log-$Category'>$Html</span>\n";
}
}
@@ -629,14 +669,18 @@ EOF
my $Group = $LogInfo->{ErrGroups}->{$GroupName};
foreach my $Line (@{$Group->{Errors}})
{
+ my $Html = $self->escapeHTML($Line);
if ($Group->{IsNew}->[$ErrIndex])
{
- print "<span class='log-new'>", $self->escapeHTML($Line), "</span>\n";
+ $Html = "<span class='log-new'>$Html</span>";
}
- else
+ my $FailureIds = $Group->{Failures}->{$ErrIndex};
+ if ($FailureIds)
{
- print $self->escapeHTML($Line), "\n";
+ print "[", $self->GetFailureLinks($FailureIds), "] ";
+ $Html = $self->GetFailureLink($FailureIds->[0], $Html);
}
+ print "$Html\n";
$ErrIndex++;
}
print "</code></pre>\n";
--
2.30.2
June 15, 2022
[tools 3/6] testbot: Match the failures with the test logs.
by Francois Gouget
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(a)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
June 15, 2022
[tools 2/6] testbot/web: Add pages to add, edit and show known failures.
by Francois Gouget
The pages allow everyone to see the known failures.
But only TestBot administrators can create and modify them.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=48912
Signed-off-by: Francois Gouget <fgouget(a)codeweavers.com>
---
The plan is for the failure details page to later show all tasks where
the failure was found. That would allow developers to compare the logs
for diagnosis.
Adding a known failure is a sensitive operation as it causes that
failure to essentially be ignored. This means if the regular expression
is too broad whole classes of failures could end up being ignored. So
for now only the TestBot administrators can add them.
Another option would be to allow anyone with a login to add them, or
allow adding them but requiring a validation step by a TestBot
administrator (a bit like when new users are added).
But I think that can wait until we see how this goes.
---
testbot/lib/WineTestBot/CGI/PageBase.pm | 1 +
testbot/lib/WineTestBot/Config.pm | 6 +-
testbot/web/FailureDetails.pl | 119 ++++++++++++++++++
testbot/web/FailuresList.pl | 160 ++++++++++++++++++++++++
testbot/web/WineTestBot.css | 3 +
5 files changed, 287 insertions(+), 2 deletions(-)
create mode 100644 testbot/web/FailureDetails.pl
create mode 100644 testbot/web/FailuresList.pl
diff --git a/testbot/lib/WineTestBot/CGI/PageBase.pm b/testbot/lib/WineTestBot/CGI/PageBase.pm
index d6f33e2ca..0486bbefc 100644
--- a/testbot/lib/WineTestBot/CGI/PageBase.pm
+++ b/testbot/lib/WineTestBot/CGI/PageBase.pm
@@ -521,6 +521,7 @@ EOF
print " <li><p><a href='/PatchesList.pl'>",
ucfirst($PatchesMailingList), "</a></p></li>\n";
}
+ print " <li><p><a href='/FailuresList.pl'>Failures</a></p></li>\n";
my $Session = $self->GetCurrentSession();
if ($self->SessionActive())
diff --git a/testbot/lib/WineTestBot/Config.pm b/testbot/lib/WineTestBot/Config.pm
index 089d792c5..6f57995e8 100644
--- a/testbot/lib/WineTestBot/Config.pm
+++ b/testbot/lib/WineTestBot/Config.pm
@@ -31,7 +31,7 @@ use vars qw (@ISA @EXPORT @EXPORT_OK $UseSSL $LogDir $DataDir $BinDir
$MaxVMsWhenIdle $WaitForBoot $SleepAfterBoot $SleepAfterRevert
$VMToolTimeout $WaitForShutdown $MaxVMErrors $MaxTaskTries
$AdminEMail $RobotEMail
- $WinePatchToOverride $WinePatchCc
+ $WinePatchToOverride $WinePatchCc $WineBugUrl
$ReconfigBuildTimeout $ExeBuildTimeout $ExeModuleTimeout
$WineBuildTimeout $WineModuleTimeout $TimeoutMargin
$SuiteTimeout $SingleTimeout $SingleAvgTime $MaxUnitSize
@@ -48,7 +48,7 @@ require Exporter;
$MaxRunningVMs $MaxVMsWhenIdle $WaitForBoot $SleepAfterBoot
$SleepAfterRevert $VMToolTimeout $WaitForShutdown
$MaxVMErrors $MaxTaskTries $AdminEMail
- $RobotEMail $WinePatchToOverride $WinePatchCc $SuiteTimeout
+ $RobotEMail $WinePatchToOverride $WinePatchCc $WineBugUrl
$ReconfigBuildTimeout $ExeBuildTimeout $ExeModuleTimeout
$WineBuildTimeout $WineModuleTimeout $TimeoutMargin
$SuiteTimeout $SingleTimeout $SingleAvgTime $MaxUnitSize
@@ -85,6 +85,8 @@ sub PrependPaths(@)
"wine" => "git://source.winehq.org/git/wine.git",
);
+$WineBugUrl = "https://bugs.winehq.org/show_bug.cgi?id=";
+
# See the ScheduleOnHost() documentation in lib/WineTestBot/Jobs.pm
$MaxRevertingVMs = 1;
$MaxRevertsWhileRunningVMs = 0;
diff --git a/testbot/web/FailureDetails.pl b/testbot/web/FailureDetails.pl
new file mode 100644
index 000000000..7cab1d2a6
--- /dev/null
+++ b/testbot/web/FailureDetails.pl
@@ -0,0 +1,119 @@
+# -*- Mode: Perl; perl-indent-level: 2; indent-tabs-mode: nil -*-
+# Failure details page
+#
+# Copyright 2022 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 FailureDetailsPage;
+
+use ObjectModel::CGI::ItemPage;
+our @ISA = qw(ObjectModel::CGI::ItemPage);
+
+use WineTestBot::Config;
+use WineTestBot::Failures;
+
+
+sub _initialize($$$)
+{
+ my ($self, $Request, $RequiredRole) = @_;
+
+ $self->SUPER::_initialize($Request, $RequiredRole, CreateFailures());
+
+ my $Session = $self->GetCurrentSession();
+ if (!$Session or !$Session->User->HasRole("admin"))
+ {
+ # Only admin users can create and modify failures
+ exit($self->RedirectToParent()) if ($self->{Item}->GetIsNew());
+ $self->SetReadWrite(0);
+ }
+}
+
+sub DisplayProperty($$)
+{
+ my ($self, $PropertyDescriptor) = @_;
+
+ my $PropertyName = $PropertyDescriptor->GetName();
+ return # Already shown in the title
+ $PropertyName eq "Id" ? "" :
+ # Showing these makes no sense for new items since they are read-only
+ $PropertyName =~ /^(?:LastNew|LastOld|BugStatus|BugDescription)$/ ?
+ ($self->{Item}->GetIsNew() ? "" : "ro") :
+ $self->SUPER::DisplayProperty($PropertyDescriptor);
+}
+
+sub GenerateValueView($$$$)
+{
+ my ($self, $PropertyDescriptor, $Value, $Format) = @_;
+
+ if ($PropertyDescriptor->GetName() =~ /^Bug(?:Id|Status|Description)$/)
+ {
+ print "<a href='$WineBugUrl", $self->{Item}->BugId, "' target='_blank'>",
+ $self->escapeHTML($Value || "Not fetched yet..."), "</a>";
+ }
+ else
+ {
+ $self->SUPER::GenerateValueView($PropertyDescriptor, $Value, $Format);
+ }
+}
+
+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>\n";
+
+ print "<p>The <b>Test module</b> is in fact the name of the error group the failure may occur in. Most of the time this is a test module but it may also be the name of the group containing the extra errors.<br>\n";
+ print "The <b>Test unit</b> is the name of the test unit the failure may occur in. For extra errors this should be an empty string.<br>\n";
+ print "The <b>Configurations RegExp</b> defines which test configurations are known to exhibit this failure. It should match the configuration names which are of the form <i>VM:logfile</i> where <i>VM</i> is the name of the virtual machine (which for Windows VMs includes the test configuration); and <i>logfile</i> is a task log file, the most important of which are the test reports: <i>exe32</i> or <i>exe64</i> for Windows, and <i>win32</i>, <i>wow32</i> or <i>wow64</i> for Wine. On Wine the log filename may contain test configuration information, typically the locale, in an underscore separated suffix.<br>\n";
+ print "The <b>Failures RegExp</b> should match the failure lines. Matching the test unit name is not necessary. Beware of the presence of wildcards in the error message such as: *+|()[]{}<br>\n";
+
+ print "</td></tr></tbody>\n";
+ print "</table></div>\n";
+ $self->SUPER::GenerateFooter();
+}
+
+sub OnAction($$)
+{
+ my ($self, $Action) = @_;
+
+ if ($Action eq "Save")
+ {
+ if (!$self->{RW})
+ {
+ $self->{ErrMessage} = "Only admin users can create or modify failures";
+ return 0;
+ }
+ if (($self->{Item}->BugId || "") ne $self->GetParam("BugId"))
+ {
+ $self->{Item}->BugStatus("");
+ $self->{Item}->BugDescription("");
+ }
+ return undef if (!$self->Save());
+ # FIXME Notify the TestBot Engine so it updates the bug information
+ exit($self->RedirectToParent());
+ }
+ return $self->SUPER::OnAction($Action);
+}
+
+
+package main;
+
+my $Request = shift;
+my $Page = FailureDetailsPage->new($Request, "");
+$Page->GeneratePage();
diff --git a/testbot/web/FailuresList.pl b/testbot/web/FailuresList.pl
new file mode 100644
index 000000000..f2608dc45
--- /dev/null
+++ b/testbot/web/FailuresList.pl
@@ -0,0 +1,160 @@
+# -*- Mode: Perl; perl-indent-level: 2; indent-tabs-mode: nil -*-
+# Known failures index page
+#
+# Copyright 2022 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 FailuresBlock;
+
+use ObjectModel::CGI::CollectionBlock;
+our @ISA = qw(ObjectModel::CGI::CollectionBlock);
+
+use WineTestBot::Config;
+use WineTestBot::Failures;
+
+
+sub _initialize($$$)
+{
+ my ($self, $Collection, $EnclosingPage) = @_;
+
+ $self->SUPER::_initialize($Collection, $EnclosingPage);
+
+ my $Session = $EnclosingPage->GetCurrentSession();
+ if (!$Session or !$Session->User->HasRole("admin"))
+ {
+ $self->SetReadWrite(0);
+ # and hide the deleted entries
+ $self->{Collection}->AddFilter("BugStatus", ["deleted"], "<>");
+ }
+}
+
+sub Create($$)
+{
+ my ($Collection, $EnclosingPage) = @_;
+
+ return FailuresBlock->new($Collection, $EnclosingPage);
+}
+
+sub DisplayProperty($$)
+{
+ my ($self, $PropertyDescriptor) = @_;
+
+ my $PropertyName = $PropertyDescriptor->GetName();
+ return $PropertyName =~ /^(?:Notes|ConfigRegExp|FailureRegExp|BugId)$/ ? "" :
+ $self->SUPER::DisplayProperty($PropertyDescriptor);
+}
+
+sub GenerateHeaderView($$$)
+{
+ my ($self, $Row, $Col) = @_;
+
+ my $PropertyName = $Col->{Descriptor}->GetName();
+ if ($PropertyName eq "ErrorGroup")
+ {
+ print "<a class='title' title='or Error group'>Test module</a>";
+ }
+ else
+ {
+ $self->SUPER::GenerateHeaderView($Row, $Col);
+ }
+}
+
+sub GenerateDataView($$$)
+{
+ my ($self, $Row, $Col) = @_;
+
+ my $PropertyName = $Col->{Descriptor}->GetName();
+ if ($PropertyName =~ /^Test(?:Module|Unit)$/)
+ {
+ print "<a href='", $self->escapeHTML($self->GetDetailsLink($Row)), "'>",
+ $self->escapeHTML($Row->{Item}->$PropertyName), "</a>";
+ }
+ elsif ($PropertyName eq "BugStatus")
+ {
+ my $Status = $Row->{Item}->BugStatus || "Not fetched yet...";
+ my $Class = ($Status eq "Does not exist") ? "-missing" :
+ ($Status =~ /^(?:CLOSED|RESOLVED)/) ? "-closed" : "";
+ print "<span class='bug$Class'>", $self->escapeHTML($Status), "</span>";
+ }
+ elsif ($PropertyName eq "BugId")
+ {
+ print "<a href='$WineBugUrl", $Row->{Item}->BugId, "' target='_blank'>",
+ $self->escapeHTML($Row->{Item}->BugId), "</a>";
+ }
+ elsif ($PropertyName eq "BugDescription")
+ {
+ my $Value = $Row->{Item}->BugDescription || "Not fetched yet...";
+ my $Tooltip ="";
+ if ($Row->{Item}->Notes)
+ {
+ $Tooltip = " title='". $self->escapeHTML($Row->{Item}->Notes) ."'";
+ }
+ print "<a href='", $self->escapeHTML($self->GetDetailsLink($Row)),
+ "'$Tooltip>", $self->escapeHTML($Value), "</a>";
+ }
+ else
+ {
+ $self->SUPER::GenerateDataView($Row, $Col);
+ }
+}
+
+sub GetItemActions($)
+{
+ my ($self) = @_;
+
+ return ["Delete", "Restore"];
+}
+
+sub OnItemAction($$$)
+{
+ my ($self, $Failure, $Action) = @_;
+
+ if ($self->{RW})
+ {
+ return 1 if ($Action eq "Restore" and $Failure->BugStatus ne "deleted");
+
+ my $NewStatus = $Action eq "Delete" ? "deleted" :
+ $Action eq "Restore" ? "unknown" :
+ undef;
+ if ($NewStatus)
+ {
+ $Failure->BugStatus($NewStatus);
+ my ($_ErrProperty, $ErrMessage) = $Failure->Save();
+ if (defined $ErrMessage)
+ {
+ # Setting ErrField is only useful on form pages
+ $self->{EnclosingPage}->SetError(undef, $ErrMessage);
+ return 0;
+ }
+ # FIXME Notify the TestBot Engine so it updates the bug information
+ return 1;
+ }
+ }
+
+ return $self->SUPER::OnItemAction($Failure, $Action);
+}
+
+
+package main;
+
+use ObjectModel::CGI::CollectionPage;
+use WineTestBot::Failures;
+
+my $Request = shift;
+my $Page = ObjectModel::CGI::CollectionPage->new($Request, "", CreateFailures(), \&FailuresBlock::Create);
+$Page->GeneratePage();
diff --git a/testbot/web/WineTestBot.css b/testbot/web/WineTestBot.css
index fd6fca359..5e0c39ff4 100644
--- a/testbot/web/WineTestBot.css
+++ b/testbot/web/WineTestBot.css
@@ -407,6 +407,9 @@ pre
.log-new { color: #e56e00; font-weight: bold; }
.log-fullnew { color: red; font-weight: bold; }
+.bug-missing { color: red; font-weight: bold; }
+.bug-closed { color: red; }
+
a.title { color:inherit; text-decoration: none; }
a.tipdatetime { color:inherit; text-decoration: none; }
a.timetipdate { color:inherit; text-decoration: none; }
--
2.30.2
June 15, 2022
[tools 1/6] testbot: Add tables to track the test failures.
by Francois Gouget
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=48912
Signed-off-by: Francois Gouget <fgouget(a)codeweavers.com>
---
This patch requires updating the database schema and restarting the
TestBot Engine and web server.
Notes:
- This first patch only introduces the core infrastructure. The other
parts are:
- Providing a way to enter known failures.
- Matching the known failures in the logs and recording the matches
in the database.
- Taking the known failures into account in the report emails and
patches status.
- Identifying the known failures on the JobDetails page.
- Update the associated bug information.
- Most bug tracking systems use integers to identify bugs. If we ever
want to move Wine's bug tracking away from Bugzilla we may have to
change the type of the BugId field. We may also need a way to know
which bug tracker a given BugId refers to which may involve adding a
BugTracker field or replacing the BugId field with the whole bug URL.
- The Jobs::Restart() chunk depends on the patch that turns Itemrefs
into virtual properties. Without it it would be unable to filter the
TaskFailures table on the JobId property.
---
testbot/ddl/update47.sql | 33 +
testbot/ddl/winetestbot.sql | 32 +
testbot/doc/winetestbot-schema.dia | 852 +++++++++++++++++++++---
testbot/lib/WineTestBot/Failures.pm | 184 +++++
testbot/lib/WineTestBot/Jobs.pm | 6 +
testbot/lib/WineTestBot/TaskFailures.pm | 120 ++++
testbot/lib/WineTestBot/Tasks.pm | 4 +
7 files changed, 1142 insertions(+), 89 deletions(-)
create mode 100644 testbot/ddl/update47.sql
create mode 100644 testbot/lib/WineTestBot/Failures.pm
create mode 100644 testbot/lib/WineTestBot/TaskFailures.pm
diff --git a/testbot/ddl/update47.sql b/testbot/ddl/update47.sql
new file mode 100644
index 000000000..177eadc28
--- /dev/null
+++ b/testbot/ddl/update47.sql
@@ -0,0 +1,33 @@
+USE winetestbot;
+
+CREATE TABLE Failures
+(
+ Id INT NOT NULL AUTO_INCREMENT,
+ ErrorGroup VARCHAR(64) NULL,
+ TestUnit VARCHAR(32) NULL,
+ ConfigRegExp VARCHAR(64) NULL,
+ FailureRegExp VARCHAR(256) NOT NULL,
+ Notes VARCHAR(128) NULL,
+ LastNew DATETIME NULL,
+ LastOld DATETIME NULL,
+ BugId INT NOT NULL,
+ BugStatus VARCHAR(32) NULL,
+ BugDescription VARCHAR(128) NULL,
+ PRIMARY KEY (Id)
+)
+ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE TaskFailures
+(
+ FailureId INT NOT NULL,
+ JobId INT NOT NULL,
+ StepNo INT(2) NOT NULL,
+ TaskNo INT(2) NOT NULL,
+ TaskLog VARCHAR(32) NOT NULL,
+ NewCount INT NULL,
+ OldCount INT NULL,
+ FOREIGN KEY(FailureId) REFERENCES Failures(Id),
+ FOREIGN KEY(JobId, StepNo, TaskNo) REFERENCES Tasks(JobId, StepNo, No),
+ PRIMARY KEY (FailureId, JobId, StepNo, TaskNo, TaskLog)
+)
+ENGINE=InnoDB DEFAULT CHARSET=utf8;
diff --git a/testbot/ddl/winetestbot.sql b/testbot/ddl/winetestbot.sql
index 2d085247c..0cf70484c 100644
--- a/testbot/ddl/winetestbot.sql
+++ b/testbot/ddl/winetestbot.sql
@@ -164,6 +164,38 @@ CREATE TABLE Tasks
)
ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE Failures
+(
+ Id INT NOT NULL AUTO_INCREMENT,
+ ErrorGroup VARCHAR(64) NULL,
+ TestUnit VARCHAR(32) NULL,
+ ConfigRegExp VARCHAR(64) NULL,
+ FailureRegExp VARCHAR(256) NOT NULL,
+ Notes VARCHAR(128) NULL,
+ LastNew DATETIME NULL,
+ LastOld DATETIME NULL,
+ BugId INT NOT NULL,
+ BugStatus VARCHAR(32) NULL,
+ BugDescription VARCHAR(128) NULL,
+ PRIMARY KEY (Id)
+)
+ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE TaskFailures
+(
+ FailureId INT NOT NULL,
+ JobId INT NOT NULL,
+ StepNo INT(2) NOT NULL,
+ TaskNo INT(2) NOT NULL,
+ TaskLog VARCHAR(32) NOT NULL,
+ NewCount INT NULL,
+ OldCount INT NULL,
+ FOREIGN KEY(FailureId) REFERENCES Failures(Id),
+ FOREIGN KEY(JobId, StepNo, TaskNo) REFERENCES Tasks(JobId, StepNo, No),
+ PRIMARY KEY (FailureId, JobId, StepNo, TaskNo, TaskLog)
+)
+ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
CREATE TABLE RecordGroups
(
Id INT NOT NULL AUTO_INCREMENT,
diff --git a/testbot/doc/winetestbot-schema.dia b/testbot/doc/winetestbot-schema.dia
index b2a686285..effbb9a82 100644
--- a/testbot/doc/winetestbot-schema.dia
+++ b/testbot/doc/winetestbot-schema.dia
@@ -669,7 +669,7 @@
<dia:point val="-7.87083,-21.5"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="-7.92083,-23;-4.3375,-21.45"/>
+ <dia:rectangle val="-7.92083,-22.9955;-4.3375,-21.45"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
@@ -729,7 +729,7 @@
<dia:point val="4.6125,-22.35"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="4.5625,-23;11.0958,-22.2834"/>
+ <dia:rectangle val="4.5625,-22.9955;11.0958,-22.2834"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
@@ -789,7 +789,7 @@
<dia:point val="19.6608,-21.5334"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="19.6108,-22.2;25.4458,-21.4834"/>
+ <dia:rectangle val="19.6108,-22.1955;25.4458,-21.4834"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
@@ -846,16 +846,16 @@
</dia:object>
<dia:object type="Database - Table" version="0" id="O7">
<dia:attribute name="obj_pos">
- <dia:point val="-16.8208,-13.3167"/>
+ <dia:point val="-16.8208,-6.6667"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="-16.8208,-13.3167;-7.8208,-10.3167"/>
+ <dia:rectangle val="-16.8208,-6.6667;-7.8208,-3.6667"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
</dia:attribute>
<dia:attribute name="elem_corner">
- <dia:point val="-16.8208,-13.3167"/>
+ <dia:point val="-16.8208,-6.6667"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="9"/>
@@ -962,16 +962,16 @@
</dia:object>
<dia:object type="Database - Table" version="0" id="O8">
<dia:attribute name="obj_pos">
- <dia:point val="-16.3208,-2.55002"/>
+ <dia:point val="-16.3208,3.89998"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="-16.3208,-2.55002;-6.1658,5.24998"/>
+ <dia:rectangle val="-16.3208,3.89998;-6.1658,11.7"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
</dia:attribute>
<dia:attribute name="elem_corner">
- <dia:point val="-16.3208,-2.55002"/>
+ <dia:point val="-16.3208,3.89998"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="10.155000000000001"/>
@@ -1216,16 +1216,16 @@
</dia:object>
<dia:object type="Database - Table" version="0" id="O9">
<dia:attribute name="obj_pos">
- <dia:point val="-4.57083,-14.9167"/>
+ <dia:point val="-4.57083,-8.2667"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="-4.57083,-14.9167;4.81417,-6.3167"/>
+ <dia:rectangle val="-4.57083,-8.2667;4.81417,0.3333"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
</dia:attribute>
<dia:attribute name="elem_corner">
- <dia:point val="-4.57083,-14.9167"/>
+ <dia:point val="-4.57083,-8.2667"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="9.3850000000000016"/>
@@ -1493,16 +1493,16 @@
</dia:object>
<dia:object type="Database - Table" version="0" id="O10">
<dia:attribute name="obj_pos">
- <dia:point val="-2.55417,-2.51669"/>
+ <dia:point val="-2.55417,3.88331"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="-2.55417,-2.51669;12.6058,2.88331"/>
+ <dia:rectangle val="-2.55417,3.88331;12.6058,9.28331"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
</dia:attribute>
<dia:attribute name="elem_corner">
- <dia:point val="-2.55417,-2.51669"/>
+ <dia:point val="-2.55417,3.88331"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="15.16"/>
@@ -1678,16 +1678,16 @@
</dia:object>
<dia:object type="Database - Table" version="0" id="O11">
<dia:attribute name="obj_pos">
- <dia:point val="8.24583,-14.8834"/>
+ <dia:point val="8.19583,-8.2334"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="8.24583,-14.8834;22.2508,-6.2834"/>
+ <dia:rectangle val="8.19583,-8.2334;22.2008,0.3666"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
</dia:attribute>
<dia:attribute name="elem_corner">
- <dia:point val="8.24583,-14.8834"/>
+ <dia:point val="8.19583,-8.2334"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="14.004999999999999"/>
@@ -1955,16 +1955,16 @@
</dia:object>
<dia:object type="Database - Table" version="0" id="O12">
<dia:attribute name="obj_pos">
- <dia:point val="25.4792,-14.8834"/>
+ <dia:point val="25.4792,-8.2334"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="25.4792,-14.8834;36.0192,-4.6834"/>
+ <dia:rectangle val="25.4792,-8.2334;36.0192,1.9666"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
</dia:attribute>
<dia:attribute name="elem_corner">
- <dia:point val="25.4792,-14.8834"/>
+ <dia:point val="25.4792,-8.2334"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="10.539999999999999"/>
@@ -2278,16 +2278,16 @@
</dia:object>
<dia:object type="Database - Table" version="0" id="O13">
<dia:attribute name="obj_pos">
- <dia:point val="25.6708,-2.02919"/>
+ <dia:point val="25.6708,3.87081"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="25.6708,-2.02919;36.5958,12.1708"/>
+ <dia:rectangle val="25.6708,3.87081;36.5958,18.0708"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
</dia:attribute>
<dia:attribute name="elem_corner">
- <dia:point val="25.6708,-2.02919"/>
+ <dia:point val="25.6708,3.87081"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="10.925000000000001"/>
@@ -2716,19 +2716,19 @@
</dia:object>
<dia:object type="Database - Reference" version="0" id="O14">
<dia:attribute name="obj_pos">
- <dia:point val="-7.8208,-11.6167"/>
+ <dia:point val="-7.8208,-4.9667"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="-7.8708,-12.2667;-4.52083,-11.5667"/>
+ <dia:rectangle val="-7.8708,-5.61225;-4.52083,-4.9167"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
</dia:attribute>
<dia:attribute name="orth_points">
- <dia:point val="-7.8208,-11.6167"/>
- <dia:point val="-5.92083,-11.6167"/>
- <dia:point val="-5.92083,-11.6167"/>
- <dia:point val="-4.57083,-11.6167"/>
+ <dia:point val="-7.8208,-4.9667"/>
+ <dia:point val="-5.92083,-4.9667"/>
+ <dia:point val="-5.92083,-4.9667"/>
+ <dia:point val="-4.57083,-4.9667"/>
</dia:attribute>
<dia:attribute name="orth_orient">
<dia:enum val="0"/>
@@ -2776,19 +2776,19 @@
</dia:object>
<dia:object type="Database - Reference" version="0" id="O15">
<dia:attribute name="obj_pos">
- <dia:point val="4.81417,-13.2167"/>
+ <dia:point val="4.81417,-6.5667"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="4.76417,-13.8667;8.29583,-13.1334"/>
+ <dia:rectangle val="4.76417,-7.21225;8.24583,-6.4834"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
</dia:attribute>
<dia:attribute name="orth_points">
- <dia:point val="4.81417,-13.2167"/>
- <dia:point val="6.4625,-13.2167"/>
- <dia:point val="6.4625,-13.1834"/>
- <dia:point val="8.24583,-13.1834"/>
+ <dia:point val="4.81417,-6.5667"/>
+ <dia:point val="6.4625,-6.5667"/>
+ <dia:point val="6.4625,-6.5334"/>
+ <dia:point val="8.19583,-6.5334"/>
</dia:attribute>
<dia:attribute name="orth_orient">
<dia:enum val="0"/>
@@ -2836,19 +2836,19 @@
</dia:object>
<dia:object type="Database - Reference" version="0" id="O16">
<dia:attribute name="obj_pos">
- <dia:point val="22.2508,-12.3834"/>
+ <dia:point val="22.2008,-5.7334"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="22.2008,-13.0334;25.5292,-12.3334"/>
+ <dia:rectangle val="22.1508,-6.37895;25.5292,-5.6834"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
</dia:attribute>
<dia:attribute name="orth_points">
- <dia:point val="22.2508,-12.3834"/>
- <dia:point val="23.7334,-12.3834"/>
- <dia:point val="23.7334,-12.3834"/>
- <dia:point val="25.4792,-12.3834"/>
+ <dia:point val="22.2008,-5.7334"/>
+ <dia:point val="23.7334,-5.7334"/>
+ <dia:point val="23.7334,-5.7334"/>
+ <dia:point val="25.4792,-5.7334"/>
</dia:attribute>
<dia:attribute name="orth_orient">
<dia:enum val="0"/>
@@ -2896,19 +2896,19 @@
</dia:object>
<dia:object type="Database - Reference" version="0" id="O17">
<dia:attribute name="obj_pos">
- <dia:point val="36.0192,-9.98336"/>
+ <dia:point val="36.0192,-3.3334"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="35.9692,-10.6334;37.6693,-0.27919"/>
+ <dia:rectangle val="35.9692,-3.97895;37.6693,5.62081"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
</dia:attribute>
<dia:attribute name="orth_points">
- <dia:point val="36.0192,-9.98336"/>
- <dia:point val="37.6193,-9.98336"/>
- <dia:point val="37.6193,-0.32919"/>
- <dia:point val="36.5958,-0.32919"/>
+ <dia:point val="36.0192,-3.3334"/>
+ <dia:point val="37.6193,-3.3334"/>
+ <dia:point val="37.6193,5.57081"/>
+ <dia:point val="36.5958,5.57081"/>
</dia:attribute>
<dia:attribute name="orth_orient">
<dia:enum val="0"/>
@@ -2959,7 +2959,7 @@
<dia:point val="0.1125,-17.85"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="0.0625,-17.9;1.48167,-14.8667"/>
+ <dia:rectangle val="0.0625,-17.9;1.48167,-8.2167"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
@@ -2968,7 +2968,7 @@
<dia:point val="0.1125,-17.85"/>
<dia:point val="0.1125,-15.3584"/>
<dia:point val="0.12167,-15.3584"/>
- <dia:point val="0.12167,-14.9167"/>
+ <dia:point val="0.12167,-8.2667"/>
</dia:attribute>
<dia:attribute name="orth_orient">
<dia:enum val="1"/>
@@ -3016,19 +3016,19 @@
</dia:object>
<dia:object type="Database - Reference" version="0" id="O19">
<dia:attribute name="obj_pos">
- <dia:point val="-4.57083,-6.8167"/>
+ <dia:point val="-4.57083,-0.1667"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="-18.2807,-7.4667;-4.52083,-0.80002"/>
+ <dia:rectangle val="-18.2807,-0.812247;-4.52083,5.64998"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
</dia:attribute>
<dia:attribute name="orth_points">
- <dia:point val="-4.57083,-6.8167"/>
- <dia:point val="-18.2307,-6.8167"/>
- <dia:point val="-18.2307,-0.85002"/>
- <dia:point val="-16.3208,-0.85002"/>
+ <dia:point val="-4.57083,-0.1667"/>
+ <dia:point val="-18.2307,-0.1667"/>
+ <dia:point val="-18.2307,5.59998"/>
+ <dia:point val="-16.3208,5.59998"/>
</dia:attribute>
<dia:attribute name="orth_orient">
<dia:enum val="0"/>
@@ -3076,19 +3076,19 @@
</dia:object>
<dia:object type="Database - Reference" version="0" id="O20">
<dia:attribute name="obj_pos">
- <dia:point val="-6.1658,-0.85002"/>
+ <dia:point val="-6.1658,5.59998"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="-6.2158,-1.50002;-2.50417,1.63331"/>
+ <dia:rectangle val="-6.2158,4.95443;-2.50417,8.03331"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
</dia:attribute>
<dia:attribute name="orth_points">
- <dia:point val="-6.1658,-0.85002"/>
- <dia:point val="-4.29249,-0.85002"/>
- <dia:point val="-4.29249,1.58331"/>
- <dia:point val="-2.55417,1.58331"/>
+ <dia:point val="-6.1658,5.59998"/>
+ <dia:point val="-4.29249,5.59998"/>
+ <dia:point val="-4.29249,7.98331"/>
+ <dia:point val="-2.55417,7.98331"/>
</dia:attribute>
<dia:attribute name="orth_orient">
<dia:enum val="0"/>
@@ -3136,19 +3136,19 @@
</dia:object>
<dia:object type="Database - Reference" version="0" id="O21">
<dia:attribute name="obj_pos">
- <dia:point val="12.6058,-0.81669"/>
+ <dia:point val="12.6058,5.58331"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="12.5558,-1.50002;15.1193,-0.76669"/>
+ <dia:rectangle val="12.5558,4.93776;15.1193,5.64998"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
</dia:attribute>
<dia:attribute name="orth_points">
- <dia:point val="12.6058,-0.81669"/>
- <dia:point val="13.7696,-0.81669"/>
- <dia:point val="13.7696,-0.85002"/>
- <dia:point val="15.0693,-0.85002"/>
+ <dia:point val="12.6058,5.58331"/>
+ <dia:point val="13.7696,5.58331"/>
+ <dia:point val="13.7696,5.59998"/>
+ <dia:point val="15.0693,5.59998"/>
</dia:attribute>
<dia:attribute name="orth_orient">
<dia:enum val="0"/>
@@ -3196,16 +3196,16 @@
</dia:object>
<dia:object type="Database - Table" version="0" id="O22">
<dia:attribute name="obj_pos">
- <dia:point val="15.0693,-2.55002"/>
+ <dia:point val="15.0693,3.89998"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="15.0693,-2.55002;24.4543,1.24998"/>
+ <dia:rectangle val="15.0693,3.89998;24.4543,7.69998"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
</dia:attribute>
<dia:attribute name="elem_corner">
- <dia:point val="15.0693,-2.55002"/>
+ <dia:point val="15.0693,3.89998"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="9.3850000000000016"/>
@@ -3335,16 +3335,16 @@
</dia:object>
<dia:object type="Database - Table" version="0" id="O23">
<dia:attribute name="obj_pos">
- <dia:point val="14.12,3.7"/>
+ <dia:point val="11.32,12.4"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="14.12,3.7;24.66,8.3"/>
+ <dia:rectangle val="11.32,12.4;21.86,17"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
</dia:attribute>
<dia:attribute name="elem_corner">
- <dia:point val="14.12,3.7"/>
+ <dia:point val="11.32,12.4"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="10.539999999999999"/>
@@ -3497,16 +3497,16 @@
</dia:object>
<dia:object type="Database - Table" version="0" id="O24">
<dia:attribute name="obj_pos">
- <dia:point val="2.61,3.7125"/>
+ <dia:point val="-0.19,12.3625"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="2.61,3.7125;10.455,6.7125"/>
+ <dia:rectangle val="-0.19,12.3625;7.655,15.3625"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
</dia:attribute>
<dia:attribute name="elem_corner">
- <dia:point val="2.61,3.7125"/>
+ <dia:point val="-0.19,12.3625"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="7.8449999999999998"/>
@@ -3613,19 +3613,19 @@
</dia:object>
<dia:object type="Database - Reference" version="0" id="O25">
<dia:attribute name="obj_pos">
- <dia:point val="10.455,5.4125"/>
+ <dia:point val="7.655,14.0625"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="10.405,4.75;14.17,5.4625"/>
+ <dia:rectangle val="7.605,13.417;11.37,14.15"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
</dia:attribute>
<dia:attribute name="orth_points">
- <dia:point val="10.455,5.4125"/>
- <dia:point val="11.95,5.4125"/>
- <dia:point val="11.95,5.4"/>
- <dia:point val="14.12,5.4"/>
+ <dia:point val="7.655,14.0625"/>
+ <dia:point val="9.8693,14.0625"/>
+ <dia:point val="9.8693,14.1"/>
+ <dia:point val="11.32,14.1"/>
</dia:attribute>
<dia:attribute name="orth_orient">
<dia:enum val="0"/>
@@ -3673,19 +3673,19 @@
</dia:object>
<dia:object type="Database - Reference" version="0" id="O26">
<dia:attribute name="obj_pos">
- <dia:point val="8.24583,-12.3834"/>
+ <dia:point val="8.19583,-5.7334"/>
</dia:attribute>
<dia:attribute name="obj_bb">
- <dia:rectangle val="5.87763,-13.0334;8.29583,-11.5334"/>
+ <dia:rectangle val="5.87763,-6.37895;8.24583,-4.8834"/>
</dia:attribute>
<dia:attribute name="meta">
<dia:composite type="dict"/>
</dia:attribute>
<dia:attribute name="orth_points">
- <dia:point val="8.24583,-12.3834"/>
- <dia:point val="5.92763,-12.3834"/>
- <dia:point val="5.92763,-11.5834"/>
- <dia:point val="8.24583,-11.5834"/>
+ <dia:point val="8.19583,-5.7334"/>
+ <dia:point val="5.92763,-5.7334"/>
+ <dia:point val="5.92763,-4.9334"/>
+ <dia:point val="8.19583,-4.9334"/>
</dia:attribute>
<dia:attribute name="orth_orient">
<dia:enum val="0"/>
@@ -3731,5 +3731,679 @@
<dia:connection handle="1" to="O11" connection="16"/>
</dia:connections>
</dia:object>
+ <dia:object type="Database - Table" version="0" id="O27">
+ <dia:attribute name="obj_pos">
+ <dia:point val="25.4168,-19.2167"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="25.4168,-19.2167;34.4168,-12.2167"/>
+ </dia:attribute>
+ <dia:attribute name="meta">
+ <dia:composite type="dict"/>
+ </dia:attribute>
+ <dia:attribute name="elem_corner">
+ <dia:point val="25.4168,-19.2167"/>
+ </dia:attribute>
+ <dia:attribute name="elem_width">
+ <dia:real val="9"/>
+ </dia:attribute>
+ <dia:attribute name="elem_height">
+ <dia:real val="7"/>
+ </dia:attribute>
+ <dia:attribute name="name">
+ <dia:string>#TaskFailures#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="comment">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="visible_comment">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="underline_primary_key">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="tagging_comment">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="bold_primary_keys">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="attributes">
+ <dia:composite type="table_attribute">
+ <dia:attribute name="name">
+ <dia:string>#FailureId#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#INT#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="comment">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="primary_key">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="nullable">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="unique">
+ <dia:boolean val="true"/>
+ </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>#JobId#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#INT#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="comment">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="primary_key">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="nullable">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="unique">
+ <dia:boolean val="true"/>
+ </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>#StepNo#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#INT(2)#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="comment">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="primary_key">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="nullable">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="unique">
+ <dia:boolean val="true"/>
+ </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>#TaskNo#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#INT(2)#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="comment">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="primary_key">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="nullable">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="unique">
+ <dia:boolean val="true"/>
+ </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>#TaskLog#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#VARCHAR(32)#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="comment">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="primary_key">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="nullable">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="unique">
+ <dia:boolean val="true"/>
+ </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>#NewCount#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#INT#</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>#OldCount#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#INT#</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:attribute>
+ <dia:attribute name="normal_font">
+ <dia:font family="monospace" style="0" name="Courier"/>
+ </dia:attribute>
+ <dia:attribute name="name_font">
+ <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+ </dia:attribute>
+ <dia:attribute name="comment_font">
+ <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+ </dia:attribute>
+ <dia:attribute name="normal_font_height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="name_font_height">
+ <dia:real val="0.99999999999999989"/>
+ </dia:attribute>
+ <dia:attribute name="comment_font_height">
+ <dia:real val="0.69999999999999996"/>
+ </dia:attribute>
+ <dia:attribute name="text_colour">
+ <dia:color val="#000000ff"/>
+ </dia:attribute>
+ <dia:attribute name="line_colour">
+ <dia:color val="#000000ff"/>
+ </dia:attribute>
+ <dia:attribute name="fill_colour">
+ <dia:color val="#ffffffff"/>
+ </dia:attribute>
+ <dia:attribute name="line_width">
+ <dia:real val="0.10000000000000001"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Database - Table" version="0" id="O28">
+ <dia:attribute name="obj_pos">
+ <dia:point val="9.8001,-19.2167"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="9.8001,-19.2167;21.1101,-9.0167"/>
+ </dia:attribute>
+ <dia:attribute name="meta">
+ <dia:composite type="dict"/>
+ </dia:attribute>
+ <dia:attribute name="elem_corner">
+ <dia:point val="9.8001,-19.2167"/>
+ </dia:attribute>
+ <dia:attribute name="elem_width">
+ <dia:real val="11.309999999999999"/>
+ </dia:attribute>
+ <dia:attribute name="elem_height">
+ <dia:real val="10.199999999999999"/>
+ </dia:attribute>
+ <dia:attribute name="name">
+ <dia:string>#Failures#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="comment">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="visible_comment">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="underline_primary_key">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="tagging_comment">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="bold_primary_keys">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="attributes">
+ <dia:composite type="table_attribute">
+ <dia:attribute name="name">
+ <dia:string>#Id#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#INT#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="comment">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="primary_key">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="nullable">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="unique">
+ <dia:boolean val="true"/>
+ </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>#ErrorGroup#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#VARCHAR(64)#</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="true"/>
+ </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>#TestUnit#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#VARCHAR(32)#</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="true"/>
+ </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>#ConfigRegExp#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#VARCHAR(64)#</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="true"/>
+ </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>#FailureRegExp#</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>#Notes#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#VARCHAR(128)#</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="true"/>
+ </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>#LastNew#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#DATETIME#</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="true"/>
+ </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>#LastOld#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#DATETIME#</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>#BugId#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#INT#</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>#BugStatus#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#VARCHAR(32)#</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="true"/>
+ </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>#BugDescription#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#VARCHAR(128)#</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="true"/>
+ </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:attribute>
+ <dia:attribute name="normal_font">
+ <dia:font family="monospace" style="0" name="Courier"/>
+ </dia:attribute>
+ <dia:attribute name="name_font">
+ <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+ </dia:attribute>
+ <dia:attribute name="comment_font">
+ <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+ </dia:attribute>
+ <dia:attribute name="normal_font_height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="name_font_height">
+ <dia:real val="0.99999999999999989"/>
+ </dia:attribute>
+ <dia:attribute name="comment_font_height">
+ <dia:real val="0.69999999999999996"/>
+ </dia:attribute>
+ <dia:attribute name="text_colour">
+ <dia:color val="#000000ff"/>
+ </dia:attribute>
+ <dia:attribute name="line_colour">
+ <dia:color val="#000000ff"/>
+ </dia:attribute>
+ <dia:attribute name="fill_colour">
+ <dia:color val="#ffffffff"/>
+ </dia:attribute>
+ <dia:attribute name="line_width">
+ <dia:real val="0.10000000000000001"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Database - Reference" version="0" id="O29">
+ <dia:attribute name="obj_pos">
+ <dia:point val="34.4168,-15.9167"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="34.3668,-16.5622;37.6001,-5.6834"/>
+ </dia:attribute>
+ <dia:attribute name="meta">
+ <dia:composite type="dict"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="34.4168,-15.9167"/>
+ <dia:point val="37.5501,-15.9167"/>
+ <dia:point val="37.5501,-5.7334"/>
+ <dia:point val="36.0192,-5.7334"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="0"/>
+ <dia:enum val="1"/>
+ <dia:enum val="0"/>
+ </dia:attribute>
+ <dia:attribute name="orth_autoroute">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="text_colour">
+ <dia:color val="#000000ff"/>
+ </dia:attribute>
+ <dia:attribute name="line_colour">
+ <dia:color val="#000000ff"/>
+ </dia:attribute>
+ <dia:attribute name="line_width">
+ <dia:real val="0.10000000000000001"/>
+ </dia:attribute>
+ <dia:attribute name="line_style">
+ <dia:enum val="0"/>
+ <dia:real val="1"/>
+ </dia:attribute>
+ <dia:attribute name="corner_radius">
+ <dia:real val="0"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ <dia:attribute name="start_point_desc">
+ <dia:string>#0..n#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="end_point_desc">
+ <dia:string>#1#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="normal_font">
+ <dia:font family="monospace" style="0" name="Courier"/>
+ </dia:attribute>
+ <dia:attribute name="normal_font_height">
+ <dia:real val="0.59999999999999998"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O27" connection="17"/>
+ <dia:connection handle="1" to="O12" connection="15"/>
+ </dia:connections>
+ </dia:object>
+ <dia:object type="Database - Reference" version="0" id="O30">
+ <dia:attribute name="obj_pos">
+ <dia:point val="21.1101,-17.5167"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="21.0601,-18.1622;25.4668,-17.4667"/>
+ </dia:attribute>
+ <dia:attribute name="meta">
+ <dia:composite type="dict"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="21.1101,-17.5167"/>
+ <dia:point val="22.6693,-17.5167"/>
+ <dia:point val="22.6693,-17.5167"/>
+ <dia:point val="25.4168,-17.5167"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="0"/>
+ <dia:enum val="1"/>
+ <dia:enum val="0"/>
+ </dia:attribute>
+ <dia:attribute name="orth_autoroute">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="text_colour">
+ <dia:color val="#000000ff"/>
+ </dia:attribute>
+ <dia:attribute name="line_colour">
+ <dia:color val="#000000ff"/>
+ </dia:attribute>
+ <dia:attribute name="line_width">
+ <dia:real val="0.10000000000000001"/>
+ </dia:attribute>
+ <dia:attribute name="line_style">
+ <dia:enum val="0"/>
+ <dia:real val="1"/>
+ </dia:attribute>
+ <dia:attribute name="corner_radius">
+ <dia:real val="0"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ <dia:attribute name="start_point_desc">
+ <dia:string>#1#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="end_point_desc">
+ <dia:string>#0..n#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="normal_font">
+ <dia:font family="monospace" style="0" name="Courier"/>
+ </dia:attribute>
+ <dia:attribute name="normal_font_height">
+ <dia:real val="0.59999999999999998"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O28" connection="13"/>
+ <dia:connection handle="1" to="O27" connection="12"/>
+ </dia:connections>
+ </dia:object>
</dia:layer>
</dia:diagram>
diff --git a/testbot/lib/WineTestBot/Failures.pm b/testbot/lib/WineTestBot/Failures.pm
new file mode 100644
index 000000000..b37b9e046
--- /dev/null
+++ b/testbot/lib/WineTestBot/Failures.pm
@@ -0,0 +1,184 @@
+# -*- Mode: Perl; perl-indent-level: 2; indent-tabs-mode: nil -*-
+# Copyright 2022 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::Failure;
+
+=head1 NAME
+
+WineTestBot::Failure - Describes a known Wine test failure
+
+=head1 DESCRIPTION
+
+Failure objects document known test failures and link them to the corresponding
+bug describing the issue. They are mainly used to avoid reporting a test
+failure as new when it has in fact happened before despite not being present
+in the reference WineTest logs, either because it is just too rare, or because
+its text is ever changing.
+
+Finally Failure objects are linked to the Task+Log they match by means of the
+TaskFailure objects.
+
+=cut
+
+use WineTestBot::WineTestBotObjects;
+our @ISA = qw(WineTestBot::WineTestBotItem);
+
+
+sub Compare($$)
+{
+ my ($self, $B) = @_;
+
+ # Sort deleted entries last
+ my %StatusOrders = ("deleted" => 1);
+
+ my $Cmp = ($StatusOrders{$self->GetColValue("BugStatus")} || 0) <=> ($StatusOrders{$B->GetColValue("BugStatus")} || 0) ||
+ $self->GetColValue("ErrorGroup") cmp $B->GetColValue("ErrorGroup") ||
+ $self->GetColValue("TestUnit") cmp $B->GetColValue("TestUnit") ||
+ $self->GetColValue("BugDescription") <=> $B->GetColValue("BugDescription");
+ return $Cmp
+}
+
+sub Validate($)
+{
+ my ($self) = @_;
+
+ if ($self->ErrorGroup =~ /^ / or $self->ErrorGroup =~ / $/)
+ {
+ return ("ErrorGroup", "The error group '". $self->ErrorGroup ."' should not have leading or trailing spaces");
+ }
+ if ($self->TestUnit !~ /^[_a-z0-9]*$/)
+ {
+ return ("TestUnit", "The test unit '". $self->TestUnit ."' contains invalid characters");
+ }
+ foreach my $Pair (["ConfigRegExp", "configuration"],
+ ["FailureRegExp", "failure"])
+ {
+ my ($Field, $Name) = @$Pair;
+ my $RegExp = $self->$Field;
+ my $ErrMessage = eval {
+ if ($RegExp and "" =~ /$RegExp/)
+ {
+ return "The $Name regular expression should not match empty strings";
+ }
+ };
+ $ErrMessage = "The $Name regular expression is invalid" if ($@);
+ return ($Field, $ErrMessage) if ($ErrMessage);
+ }
+ return $self->SUPER::Validate();
+}
+
+
+package WineTestBot::Failures;
+
+=head1 NAME
+
+WineTestBot::Failures - A Failure collection
+
+=head1 DESCRIPTION
+
+This collection contains all known failures.
+
+=cut
+
+use Exporter 'import';
+use WineTestBot::WineTestBotObjects;
+BEGIN
+{
+ our @ISA = qw(WineTestBot::WineTestBotCollection);
+ our @EXPORT = qw(CreateFailures);
+}
+
+use ObjectModel::BasicPropertyDescriptor;
+use ObjectModel::EnumPropertyDescriptor;
+use ObjectModel::DetailrefPropertyDescriptor;
+use WineTestBot::TaskFailures;
+
+
+sub CreateItem($)
+{
+ my ($self) = @_;
+
+ return WineTestBot::Failure->new($self);
+}
+
+my @PropertyDescriptors = (
+ # Rather than using a combination of the other fields, give each entry a
+ # unique id. This allows having multiple entries for a single Wine bug in
+ # case there are too many failures to match for the regular expression to
+ # fit in a single regular expression field. This also avoids using the
+ # regular expression as part of the primary key which would be troublesome
+ # as it may need to be adjusted in case it is buggy or if the test changes.
+ CreateBasicPropertyDescriptor("Id", "Id", 1, 1, "S", 10),
+
+ # Identify the error group the failure can occur in. This is usually a
+ # test module but it may also be a group containing extra errors. Also some
+ # log files have a single nameless error group.
+ CreateBasicPropertyDescriptor("ErrorGroup", "Test module", !1, !1, "A", 64),
+ # For test modules, identify the test unit the failure can occur in. In the
+ # other cases this should be an empty string.
+ CreateBasicPropertyDescriptor("TestUnit", "Test unit", !1, !1, "A", 32),
+
+ # A regular expression to match the configurations the failure can happen in.
+ # A configuration name is of the form 'VMNAME:REPORTNAME'.
+ CreateBasicPropertyDescriptor("ConfigRegExp", "Configurations RegExp", !1, !1, "A", 64),
+
+ # A regular expression to match the troublesome failures.
+ CreateBasicPropertyDescriptor("FailureRegExp", "Failures RegExp", !1, 1, "A", 256),
+
+ # Can be used for documentation when multiple entries are needed to match
+ # all the failures associated with a given bug.
+ # Can also be used to document when a regular expression has been modified,
+ # for instance to match changes in a test.
+ CreateBasicPropertyDescriptor("Notes", "Notes", !1, !1, "A", 128),
+
+ # Record when an entry was last identified as a new or old failure, even
+ # after the corresponding tasks have expired (see the TaskFailures table).
+ CreateBasicPropertyDescriptor("LastNew", "Last new", !1, !1, "DT", 19),
+ CreateBasicPropertyDescriptor("LastOld", "Last old", !1, !1, "DT", 19),
+
+ # Every entry must be associated with a Wine bug.
+ # Note: The 'deleted' bug status means this failure entry should be
+ # deleted as soon as it is not referenced anymore.
+ CreateBasicPropertyDescriptor("BugId", "WineHQ bug id", !1, 1, "N", 10),
+ CreateBasicPropertyDescriptor("BugStatus", "WineHQ bug status", !1, !1, "A", 32),
+ CreateBasicPropertyDescriptor("BugDescription", "WineHQ bug description", !1, !1, "A", 128),
+
+ CreateDetailrefPropertyDescriptor("TaskFailures", "Tasks", \&CreateTaskFailures),
+);
+SetDetailrefKeyPrefix("Failure", @PropertyDescriptors);
+
+=pod
+=over 12
+
+=item C<CreateFailures()>
+
+Creates a collection of Failure objects.
+
+=back
+=cut
+
+sub CreateFailures(;$)
+{
+ my ($ScopeObject) = @_;
+ return WineTestBot::Failures->new("Failures", "Failures", "Failure",
+ \@PropertyDescriptors, $ScopeObject);
+}
+
+1;
diff --git a/testbot/lib/WineTestBot/Jobs.pm b/testbot/lib/WineTestBot/Jobs.pm
index 7f15a9c57..3da6ae7b0 100644
--- a/testbot/lib/WineTestBot/Jobs.pm
+++ b/testbot/lib/WineTestBot/Jobs.pm
@@ -89,6 +89,7 @@ use File::Path;
use WineTestBot::Config;
use WineTestBot::Branches;
use WineTestBot::Engine::Notify;
+use WineTestBot::TaskFailures;
sub _initialize($$)
@@ -435,6 +436,11 @@ sub Restart($)
my ($ErrProperty, $ErrMessage) = $self->Save(); # Save it all
return "$ErrMessage ($ErrProperty)" if ($ErrMessage);
+ # Remove the corresponding TaskFailures if any
+ my $TaskFailures = CreateTaskFailures();
+ $TaskFailures->AddFilter("JobId", [$self->Id]);
+ $TaskFailures->DeleteAll();
+
return undef;
}
diff --git a/testbot/lib/WineTestBot/TaskFailures.pm b/testbot/lib/WineTestBot/TaskFailures.pm
new file mode 100644
index 000000000..bdbfd6e2e
--- /dev/null
+++ b/testbot/lib/WineTestBot/TaskFailures.pm
@@ -0,0 +1,120 @@
+# -*- Mode: Perl; perl-indent-level: 2; indent-tabs-mode: nil -*-
+# Copyright 2022 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::TaskFailure;
+
+=head1 NAME
+
+WineTestBot::TaskFailure - Ties a failure to its task+log matches
+
+=head1 DESCRIPTION
+
+A TaskFailure is created when a WineTestBot::Failure matches a line in a
+task's log. It also records whether the matching failures occurred in previous
+logs or not. This makes it possible to detect fixed failures.
+
+=cut
+
+use WineTestBot::WineTestBotObjects;
+our @ISA = qw(WineTestBot::WineTestBotItem);
+
+
+sub Compare($$)
+{
+ my ($self, $B) = @_;
+
+ return $B->Task->Started <=> $self->Task->Started || # newest first by default
+ $self->TaskLog cmp $B->TaskLog;
+}
+
+
+package WineTestBot::TaskFailures;
+
+=head1 NAME
+
+WineTestBot::TaskFailures - A TaskFailure collection
+
+=head1 DESCRIPTION
+
+This collection cross references the known failures with the tasks and logs they
+matched.
+
+=cut
+
+use Exporter 'import';
+use WineTestBot::WineTestBotObjects;
+BEGIN
+{
+ our @ISA = qw(WineTestBot::WineTestBotCollection);
+ our @EXPORT = qw(CreateTaskFailures);
+}
+
+use ObjectModel::BasicPropertyDescriptor;
+use ObjectModel::ItemrefPropertyDescriptor;
+use WineTestBot::Tasks;
+
+
+sub CreateItem($)
+{
+ my ($self) = @_;
+
+ return WineTestBot::TaskFailure->new($self);
+}
+
+my @PropertyDescriptors = (
+ # Identifies the task which has matching failures
+ CreateBasicPropertyDescriptor("JobId", "Job id", 1, 1, "N", 10),
+ CreateBasicPropertyDescriptor("StepNo", "Step no", 1, 1, "N", 2),
+ CreateBasicPropertyDescriptor("TaskNo", "Task", 1, 1, "N", 2),
+ CreateItemrefPropertyDescriptor("Task", "Task", 1, \&WineTestBot::Tasks::CreateTasks, ["JobId", "StepNo", "TaskNo"]),
+
+ # and more specifically in which of its logs
+ CreateBasicPropertyDescriptor("TaskLog", "Task log", 1, 1, "A", 32),
+
+ # Also store a count of matching new and old failures
+ CreateBasicPropertyDescriptor("NewCount", "Count of matching new failures", !1, !1, "N", 10),
+ CreateBasicPropertyDescriptor("OldCount", "Count of matching old failures", !1, !1, "N", 10),
+);
+SetupItemrefColumns(\@PropertyDescriptors);
+my @FlatPropertyDescriptors = (
+ CreateBasicPropertyDescriptor("FailureId", "Failure id", 1, 1, "N", 10),
+ @PropertyDescriptors
+);
+
+=pod
+=over 12
+
+=item C<CreateTaskFailures()>
+
+Creates a collection of TaskFailure objects.
+
+=back
+=cut
+
+sub CreateTaskFailures(;$$)
+{
+ my ($ScopeObject, $Failure) = @_;
+ return WineTestBot::TaskFailures->new("TaskFailures",
+ "TaskFailures", "TaskFailure",
+ $Failure ? \@PropertyDescriptors : \@FlatPropertyDescriptors,
+ $ScopeObject, $Failure);
+}
+
+1;
diff --git a/testbot/lib/WineTestBot/Tasks.pm b/testbot/lib/WineTestBot/Tasks.pm
index 0360e4ebc..5b87c782b 100644
--- a/testbot/lib/WineTestBot/Tasks.pm
+++ b/testbot/lib/WineTestBot/Tasks.pm
@@ -341,7 +341,9 @@ BEGIN
use ObjectModel::BasicPropertyDescriptor;
use ObjectModel::EnumPropertyDescriptor;
use ObjectModel::ItemrefPropertyDescriptor;
+use ObjectModel::DetailrefPropertyDescriptor;
use WineTestBot::VMs;
+use WineTestBot::TaskFailures;
sub CreateItem($)
@@ -362,8 +364,10 @@ my @PropertyDescriptors = (
CreateBasicPropertyDescriptor("Started", "Started", !1, !1, "DT", 19),
CreateBasicPropertyDescriptor("Ended", "Ended", !1, !1, "DT", 19),
CreateBasicPropertyDescriptor("TestFailures", "Failures", !1, !1, "N", 6),
+ CreateDetailrefPropertyDescriptor("Failures", "Known failures", \&CreateTaskFailures),
);
SetupItemrefColumns(\@PropertyDescriptors);
+SetDetailrefKeyPrefix("Task", @PropertyDescriptors);
my @FlatPropertyDescriptors = (
CreateBasicPropertyDescriptor("JobId", "Job id", 1, 1, "N", 10),
CreateBasicPropertyDescriptor("StepNo", "Step no", 1, 1, "N", 2),
--
2.30.2
June 15, 2022
[tools 0/6] Known failures tracking
by Francois Gouget
This patchset provides support for tracking known failures in the
TestBot results. The main goal is to avoid false positives but there
are some side benefits as well:
* The job reports sent to developers link to the relevant WineHQ bugs
which gives them more exposure and may help get them fixed faster.
* The failure details pages will eventually link to all the tasks where
the failure happened, which could be valuable to find patterns.
* The failure page tracks when it was last seen. This simplifies
spotting bugs which are fixed and could be closed.
(Or failures where the regular expression needs to be updated / fixed)
The patchset mostly follows the scheme described in bug 48912:
https://bugs.winehq.org/show_bug.cgi?id=48912
There are some differences though:
* The bug envisionned this scheme purely as a blacklist to avoid false
positives. The implementation can certainly be used in this way but
it is not limited to failures that cause false positives.
Tracking other failures can provide the side benefits mentionned
above. The only limit is the maintenance work required to track
more failures.
* As a consequence the FailureBlacklist class was renamed to Failure,
and the FailureBlacklistUses was renamed to TaskFailures since it
links Tasks with the related Failures.
* The FailureBlacklistVMs purpose is not really described in the bug but
I think the goal was to provide a way to deckare that the failure is
expected to only happen on specific test configurations.
Instead the Failures class has a ConfigRegExp regular expression
which specifies which test configurations the failure applies to.
The test 'configuration name' is of the form 'VM:logfile' which
allows matching specific Windows versions (thanks to the TestBot VM
naming scheme), specific Windows locales (part of the VM name),
specific bitnesses (part of the log filename), or specific Wine
locales (part of the log filename). For instance:
^w8 -> all Windows 8 VMs
:exe -> all Windows tests (exe32.report and exe64.report)
^w10.*_ja.*: -> all Windows 10 Japanese test configurations
win|wow -> all Wine test configurations (win32.report, ...)
(win|wow).*_fr -> French Wine test configurations
* Changes in FailureBlacklists -> Failures
- Primary key: (Bug, TestModule, TestUnit) -> Id
Some issues generate quite a few failures. So in case trying to
match them all results in a regular expression that's too long
(either because of the database limit or for readability), this
change makes it possible to add multiple Failure objects to track
them all.
- TestModule -> ErrorGroup
The .errors files split the errors in groups. Each group
corresponds to a test module, but extra errors (too much output,
missing summary lines, etc) are in a separate group. So I renamed
TestModule to ErrorGroup as a reminder to the coder that this is
meant to match the .errors group names, not just test modules.
- Name -> (BugStatus, BugDescription)
Instead of having a name describing the failure, this patchset uses
the corresponding WineHQ bug description.
The TestBot also retrieves the bug status which allows checking
that closed bugs dont happen anymore, or open bugs that should be
closed.
- LastUsed -> (LastNew, LastOld)
The LastUse field was split into LastNew and LastOld to track
whether the failure would cause false positives without the known
failures support.
* Changes in FailureBlacklistUses -> TaskFailures
- (Bug, TestModule, TestUnit) -> FailureId
This is a consequence of the Failure primary key change described
above.
- () -> (TaskLog)
This allows tracking which task log the failure was found in. This
is quite important for Wine tasks since they run all the test
configurations in just one task.
- () -> (NewCount, OldCount)
This provides a count of matching failures and whether they would
have been tagged as old or new without this patchset.
This provides a quick way to know whether there is the same number
of failures every time or whether the number of failures changes
from one run to the next.
It also provides a quick way to know how many failures to look for
when jumping to the log.
Francois Gouget (6):
testbot: Add tables to track the test failures.
testbot/web: Add pages to add, edit and show known failures.
testbot: Match the failures with the test logs.
testbot/web: Link job details to the related known failures.
testbot/WineSendLog: Take into account the known failures.
testbot/UpdateFailures: Add a script to refresh the failures' bug
information.
testbot/bin/UpdateFailures.pl | 192 ++++++
testbot/bin/WineSendLog.pl | 78 ++-
testbot/ddl/update47.sql | 33 +
testbot/ddl/winetestbot.sql | 32 +
testbot/doc/INSTALL.txt | 5 +
testbot/doc/winetestbot-schema.dia | 852 +++++++++++++++++++++---
testbot/lib/WineTestBot/CGI/PageBase.pm | 1 +
testbot/lib/WineTestBot/Config.pm | 6 +-
testbot/lib/WineTestBot/Failures.pm | 184 +++++
testbot/lib/WineTestBot/Jobs.pm | 6 +
testbot/lib/WineTestBot/LogUtils.pm | 180 ++++-
testbot/lib/WineTestBot/TaskFailures.pm | 120 ++++
testbot/lib/WineTestBot/Tasks.pm | 4 +
testbot/web/FailureDetails.pl | 119 ++++
testbot/web/FailuresList.pl | 160 +++++
testbot/web/JobDetails.pl | 54 +-
testbot/web/WineTestBot.css | 3 +
17 files changed, 1907 insertions(+), 122 deletions(-)
create mode 100755 testbot/bin/UpdateFailures.pl
create mode 100644 testbot/ddl/update47.sql
create mode 100644 testbot/lib/WineTestBot/Failures.pm
create mode 100644 testbot/lib/WineTestBot/TaskFailures.pm
create mode 100644 testbot/web/FailureDetails.pl
create mode 100644 testbot/web/FailuresList.pl
--
2.30.2
June 15, 2022
Re: [PATCH] comctl32: Add partial support for LVM_SETBKIMAGE.
by Zhiyi Zhang
On 6/15/22 15:05, Dmitry Timoshkov wrote:
> Dmitry Timoshkov <dmitry(a)baikal.ru> wrote:
>
>> Zhiyi Zhang <zzhang(a)codeweavers.com> wrote:
>>
>>>>>>>>> Also, I found LVM_SETBKIMAGE is acting a bit weird on Windows. For example,
>>>>>>>>> if a LVM_SETBKIMAGE message trying to set a bitmap
>>>>>>>>> is sent the second time. The second message returns FALSE and the background
>>>>>>>>> image gets reset to none(from LVM_GETBKIMAGE). I don't know if we should
>>>>>>>>> implement such a behavior but I would be more comfortable with some tests.
>>>>>>>> According to my testing LVM_SETBKIMAGE always returns 0.
>>>>>>> If LVM_SETBKIMAGE for a bitmap succeeds, it does return TRUE.
>>>>>> Where do you see this? In my tests LVM_SETBKIMAGE always returns 0:
>>>>>> https://testbot.winehq.org/JobDetails.pl?Key=116659
>>>>> You're only testing with comctl32 v5. Please see https://testbot.winehq.org/JobDetails.pl?Key=116666&f204=exe32.report#k204
>>>> Thanks. Do you think it's really necessary to change return value for the v6 case?
>>>>
>>> Yes, I think it's necessary. I don't think comctl32 v5 support LVM_SETBKIMAGE at all. The FALSE is probably from DefWindowProc().
>> I don't think that explains destroying the bitmap on the 2nd call. It looks
>> like v5 really supports LVM_SETBKIMAGE, and the only difference in v6 is
>> the return value.
> Is there anything else that should be addressed in this patch?
>
I see. No. I sent a v4 https://source.winehq.org/patches/data/238807.
June 15, 2022
Re: Gitlab experiment wrapup
by Alexandre Julliard
Huw Davies <huw(a)codeweavers.com> writes:
> On Wed, Jun 15, 2022 at 04:06:03PM +0200, Alexandre Julliard wrote:
>> Huw Davies <huw(a)codeweavers.com> writes:
>>
>> > On Tue, Jun 14, 2022 at 10:01:23PM +0200, Alexandre Julliard wrote:
>> >> Another option would be to require the submitter to explicitly approve
>> >> their own MR if it contains a patch written by someone else; that would
>> >> be similar to a reviewer pushing fixups and then approving the MR. It
>> >> would require some mechanism to detect missing approvals though, so it
>> >> may not be worth the trouble.
>> >
>> > Could the (yet to be written) auto-add-reviewer script check whether
>> > the submitter and author are different and in that case auto add the
>> > submitter as a reviewer (as well as any other appropriate reviewers)?
>> > Then the approval check just becomes the same as for regular
>> > reviewers.
>>
>> Yes, that would be a way of doing it, if we decide that we want that
>> info in the git notes. Do you feel that it would be useful to have it
>> there?
>
> If you're sending in someone else's work then you're essentially
> acting as a reviewer, so it seems logical to add an Approved-by tag in
> the notes.
Makes sense.
> Of course I'm not the one writing the bot, so if it's a
> pain then perhaps it's not worth the hassle as this doesn't happen
> that often.
Well, I volunteered Jer to write the bot, so I'm sure he will enjoy a
little challenge ;-)
--
Alexandre Julliard
julliard(a)winehq.org
June 15, 2022
Re: [PATCH v4 1/1] shell32: Partially implement IShellItemImageFactory (icon only, no thumbnail).
by Marvin
Hi,
It looks like your patch introduced the new failures shown below.
Please investigate and fix them if they are indeed new. Note that rare
failures and failures with always changing text (e.g. because of memory
addresses) can cause false positives. If this is what happened, then
fixing those would really help.
The tests also ran into some preexisting test failures. If you know how
to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at:
https://testbot.winehq.org/JobDetails.pl?Key=117053
Your paranoid android.
=== w1064v1809 (32 bit report) ===
shell32:
shlfolder.c:4959: Test failed: CREATE: expected notification type 2, got: 40000
shlfolder.c:4966: Test failed: GetDisplayNameOf failed: 0x80070057
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 2)
=== w1064 (32 bit report) ===
shell32:
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 3)
=== w1064_tsign (32 bit report) ===
shell32:
shlfolder.c:4959: Test failed: CREATE: expected notification type 2, got: 40000
shlfolder.c:4966: Test failed: GetDisplayNameOf failed: 0x80070057
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 2)
=== w10pro64 (32 bit report) ===
shell32:
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 4000000)
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 4000000)
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 4000000)
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 40000)
=== w1064v1809 (64 bit report) ===
shell32:
shlfolder.c:4959: Test failed: RMDIR: expected notification type 10, got: 40000
shlfolder.c:4966: Test failed: GetDisplayNameOf failed: 0x80070057
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 10)
=== w1064 (64 bit report) ===
shell32:
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 3)
=== w1064_2qxl (64 bit report) ===
shell32:
shlfolder.c:4959: Test failed: MKDIR: expected notification type 8, got: 40000
shlfolder.c:4966: Test failed: GetDisplayNameOf failed: 0x80070057
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 3)
=== w1064_adm (64 bit report) ===
shell32:
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 40000)
=== w10pro64 (64 bit report) ===
shell32:
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 3)
shlfolder.c:4959: Test failed: CREATE: expected notification type 2, got: 40000
shlfolder.c:4966: Test failed: GetDisplayNameOf failed: 0x80070057
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 3)
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 3)
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 3)
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 3)
shlfolder.c:4959: Test failed: RMDIR: expected notification type 10, got: 40000
shlfolder.c:4966: Test failed: GetDisplayNameOf failed: 0x80070057
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 3)
=== w10pro64_en_AE_u8 (64 bit report) ===
shell32:
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 10)
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 3)
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 3)
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 3)
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 3)
shlfolder.c:4959: Test failed: RMDIR: expected notification type 10, got: 40000
shlfolder.c:4966: Test failed: GetDisplayNameOf failed: 0x80070057
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 3)
=== w10pro64_ar (64 bit report) ===
shell32:
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 40000)
=== w10pro64_ja (64 bit report) ===
shell32:
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 10)
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 3)
=== w10pro64_zh_CN (64 bit report) ===
shell32:
shlfolder.c:4959: Test failed: RMDIR: expected notification type 10, got: 40000
shlfolder.c:4966: Test failed: GetDisplayNameOf failed: 0x80070057
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 10)
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 3)
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 3)
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 3)
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 3)
shlfolder.c:4959: Test failed: CREATE: expected notification type 2, got: 40000
shlfolder.c:4966: Test failed: GetDisplayNameOf failed: 0x80070057
shlfolder.c:4976: Test failed: Didn't expect a WM_USER_NOTIFY message (event: 3)
June 15, 2022
Re: [PATCH 0/1] MR249: include: Emit CFI directives when building on Mac with gcc.
by Jinoh Kang (@iamahuman)
> No, FSF gcc on macos does not define \__GCC_HAVE_DWARF2_CFI_ASM.
Isn't this an upstream bug?
--
https://gitlab.winehq.org/wine/wine/-/merge_requests/249#note_2125
June 15, 2022
[PATCH v4 1/1] shell32: Partially implement IShellItemImageFactory (icon only, no thumbnail).
by Jinoh Kang
From: Jinoh Kang <jinoh.kang.kr(a)gmail.com>
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=52673
Signed-off-by: Jinoh Kang <jinoh.kang.kr(a)gmail.com>
---
dlls/shell32/Makefile.in | 2 +-
dlls/shell32/shellitem.c | 177 ++++++++++++++++++++++++++++++++-
dlls/shell32/tests/shlfolder.c | 1 -
3 files changed, 175 insertions(+), 5 deletions(-)
diff --git a/dlls/shell32/Makefile.in b/dlls/shell32/Makefile.in
index 9e2395126fc..95afa482de5 100644
--- a/dlls/shell32/Makefile.in
+++ b/dlls/shell32/Makefile.in
@@ -1,7 +1,7 @@
MODULE = shell32.dll
IMPORTLIB = shell32
IMPORTS = uuid shlwapi user32 gdi32 advapi32
-DELAYIMPORTS = ole32 oleaut32 shdocvw version comctl32 gdiplus
+DELAYIMPORTS = ole32 oleaut32 shdocvw version comctl32 gdiplus windowscodecs
C_SRCS = \
appbar.c \
diff --git a/dlls/shell32/shellitem.c b/dlls/shell32/shellitem.c
index 3e8bd24a429..84dd597b02c 100644
--- a/dlls/shell32/shellitem.c
+++ b/dlls/shell32/shellitem.c
@@ -31,9 +31,13 @@
#include "pidl.h"
#include "shell32_main.h"
#include "debughlp.h"
+#include "wincodec.h"
+#include "commoncontrols.h"
WINE_DEFAULT_DEBUG_CHANNEL(shell);
+HRESULT WINAPI WICCreateImagingFactory_Proxy(UINT, IWICImagingFactory**);
+
typedef struct _ShellItem {
IShellItem2 IShellItem2_iface;
LONG ref;
@@ -565,17 +569,184 @@ static ULONG WINAPI ShellItem_IShellItemImageFactory_Release(IShellItemImageFact
return IShellItem2_Release(&This->IShellItem2_iface);
}
+static IImageList *get_system_imagelist_by_size(SIZE size)
+{
+ int i;
+
+ for (i = 0; i <= SHIL_LAST; i++)
+ {
+ IImageList *cur_list;
+
+ if (SUCCEEDED(SHGetImageList(i, &IID_IImageList, (void **)&cur_list)))
+ {
+ int cx, cy;
+
+ if (SUCCEEDED(IImageList_GetIconSize(cur_list, &cx, &cy)) &&
+ cx == size.cx && cy == size.cy)
+ {
+ return cur_list;
+ }
+
+ IImageList_Release(cur_list);
+ }
+ }
+
+ return NULL;
+}
+
+static HICON get_icon_via_cache(LPCWSTR icon_file, INT source_index, DWORD flags, SIZE size)
+{
+ IImageList *icon_list;
+ int ic_index;
+ HICON icon;
+
+ if (!(icon_list = get_system_imagelist_by_size(size))) return NULL;
+
+ if ((ic_index = SIC_GetIconIndex(icon_file, source_index, flags)) != INVALID_INDEX)
+ {
+ if (FAILED(IImageList_GetIcon(icon_list, ic_index, ILD_NORMAL, &icon)))
+ {
+ icon = NULL;
+ }
+ }
+ IImageList_Release(icon_list);
+
+ return icon;
+}
+
+static HRESULT ShellItem_get_icon(ShellItem *This, SIZE size, HICON *icon)
+{
+ IExtractIconW *extracticon;
+ WCHAR icon_file[MAX_PATH];
+ INT icon_source_index;
+ UINT gil_out_flags;
+ HICON cached_icon;
+ HRESULT hr;
+
+ if (FAILED(hr = IShellItem2_BindToHandler(&This->IShellItem2_iface, NULL, &BHID_SFUIObject,
+ &IID_IExtractIconW, (void **)&extracticon)))
+ {
+ goto done;
+ }
+
+ if (FAILED(hr = IExtractIconW_GetIconLocation(extracticon, 0, icon_file, ARRAY_SIZE(icon_file),
+ &icon_source_index, &gil_out_flags)))
+ {
+ goto free_extracticon;
+ }
+
+ if (!(gil_out_flags & (GIL_NOTFILENAME | GIL_DONTCACHE)) &&
+ (cached_icon = get_icon_via_cache(icon_file, icon_source_index, 0, size)))
+ {
+ *icon = cached_icon;
+ hr = S_OK;
+ }
+ else
+ {
+ LONG iconsize = min(size.cx, size.cy);
+ if (iconsize <= 0 || iconsize > 0x7fff)
+ iconsize = 0x7fff;
+ hr = IExtractIconW_Extract(extracticon, icon_file, icon_source_index,
+ icon, NULL, MAKELONG(0, iconsize));
+ }
+
+free_extracticon:
+ IExtractIconW_Release(extracticon);
+done:
+ return hr;
+}
+
+static HRESULT convert_wicbitmapsource_to_gdi(IWICImagingFactory *imgfactory,
+ IWICBitmapSource *bitmapsource, HBITMAP *gdibitmap)
+{
+ IWICBitmapSource *newsrc;
+ UINT width, height;
+ BITMAPINFO bmi;
+ HBITMAP bm;
+ void *bits;
+ HRESULT hr;
+
+ *gdibitmap = NULL;
+
+ if (FAILED(hr = WICConvertBitmapSource(&GUID_WICPixelFormat32bppBGRA, bitmapsource, &newsrc)))
+ {
+ goto done;
+ }
+
+ if (FAILED(hr = IWICBitmapSource_GetSize(newsrc, &width, &height)))
+ {
+ goto free_newsrc;
+ }
+
+ memset(&bmi, 0, sizeof(bmi));
+ bmi.bmiHeader.biSize = sizeof(bmi);
+ bmi.bmiHeader.biWidth = width;
+ bmi.bmiHeader.biHeight = -height;
+ bmi.bmiHeader.biPlanes = 1;
+ bmi.bmiHeader.biBitCount = 32;
+ bmi.bmiHeader.biCompression = BI_RGB;
+
+ if (!(bm = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, &bits, NULL, 0)))
+ {
+ WARN("Cannot create bitmap.\n");
+ hr = E_FAIL;
+ goto free_newsrc;
+ }
+
+ if (FAILED(hr = IWICBitmapSource_CopyPixels(newsrc, NULL, width * 4, width * height * 4, bits)))
+ {
+ DeleteObject(bm);
+ goto free_newsrc;
+ }
+
+ hr = S_OK;
+ *gdibitmap = bm;
+
+free_newsrc:
+ IWICBitmapSource_Release(newsrc);
+done:
+ return hr;
+}
+
static HRESULT WINAPI ShellItem_IShellItemImageFactory_GetImage(IShellItemImageFactory *iface,
- SIZE size, SIIGBF flags, HBITMAP *phbm)
+ SIZE size, SIIGBF flags, HBITMAP *phbm)
{
ShellItem *This = impl_from_IShellItemImageFactory(iface);
+ IWICImagingFactory *imgfactory;
+ IWICBitmap *bitmap = NULL;
static int once;
+ HICON hicon;
+ HRESULT hr;
if (!once++)
- FIXME("%p ({%lu, %lu} %d %p): stub\n", This, size.cx, size.cy, flags, phbm);
+ FIXME("%p ({%lu, %lu} %d %p): partial stub\n", This, size.cx, size.cy, flags, phbm);
*phbm = NULL;
- return E_NOTIMPL;
+
+ if (flags != SIIGBF_BIGGERSIZEOK) return E_NOTIMPL;
+
+ if (FAILED(hr = WICCreateImagingFactory_Proxy(WINCODEC_SDK_VERSION, &imgfactory)))
+ {
+ return hr;
+ }
+
+ if (SUCCEEDED(hr = ShellItem_get_icon(This, size, &hicon)))
+ {
+ if (FAILED(hr = IWICImagingFactory_CreateBitmapFromHICON(imgfactory, hicon, &bitmap)))
+ {
+ bitmap = NULL;
+ }
+ DeleteObject(hicon);
+ }
+
+ if (bitmap)
+ {
+ hr = convert_wicbitmapsource_to_gdi(imgfactory, (IWICBitmapSource *)bitmap, phbm);
+ IWICBitmap_Release(bitmap);
+ }
+ IWICImagingFactory_Release(imgfactory);
+
+ return hr;
}
static const IShellItemImageFactoryVtbl ShellItem_IShellItemImageFactory_Vtbl = {
diff --git a/dlls/shell32/tests/shlfolder.c b/dlls/shell32/tests/shlfolder.c
index da606a9e707..49614aed3c4 100644
--- a/dlls/shell32/tests/shlfolder.c
+++ b/dlls/shell32/tests/shlfolder.c
@@ -4456,7 +4456,6 @@ static void test_IShellItemImageFactory(void)
ret = IShellItemImageFactory_GetImage(siif, size, SIIGBF_BIGGERSIZEOK, &hbm);
IShellItemImageFactory_Release(siif);
- todo_wine
ok(ret == S_OK, "GetImage returned %lx\n", ret);
ok(FAILED(ret) == !hbm, "result = %lx but bitmap = %p\n", ret, hbm);
--
GitLab
https://gitlab.winehq.org/wine/wine/-/merge_requests/190
June 15, 2022