VirtualBox

Changeset 61270 in vbox


Ignore:
Timestamp:
May 29, 2016 12:34:45 AM (9 years ago)
Author:
vboxsync
svn:sync-xref-src-repo-rev:
107562
Message:

testmanager: Generate list of test cases starting (and stopping) to fail, with failure count graph.

Location:
trunk/src/VBox/ValidationKit/testmanager
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/ValidationKit/testmanager/core/failurecategory.py

    r61250 r61270  
    324324                                  'FROM     FailureCategories\n'
    325325                                  'WHERE    idFailureCategory = %s\n'
    326                                   'ORDER BY tsExpire\n'
     326                                  'ORDER BY tsExpire DESC\n'
    327327                                  'LIMIT 1\n'
    328328                                  , (idFailureCategory, ));
  • trunk/src/VBox/ValidationKit/testmanager/core/failurereason.py

    r61250 r61270  
    423423                                  'FROM     FailureReasons\n'
    424424                                  'WHERE    idFailureReason = %s\n'
    425                                   'ORDER BY tsExpire\n'
     425                                  'ORDER BY tsExpire DESC\n'
    426426                                  'LIMIT 1\n'
    427427                                  , (idFailureReason, ));
  • trunk/src/VBox/ValidationKit/testmanager/core/report.py

    r61269 r61270  
    3636from testmanager.core.failurereason import FailureReasonLogic;
    3737from testmanager.core.testbox       import TestBoxData;
     38from testmanager.core.testcase      import TestCaseLogic;
    3839from common                         import constants;
    3940
     
    7576        # Public so the report generator can easily access them.
    7677        self.tsNow           = tsNow;               # (Can be None.)
     78        self.__tsNowDateTime = None;
    7779        self.cPeriods        = cPeriods;
    7880        self.cHoursPerPeriod = cHoursPerPeriod;
     
    127129        return sWhere;
    128130
     131    def getNowAsDateTime(self):
     132        """ Returns a datetime instance corresponding to tsNow. """
     133        if self.__tsNowDateTime is None:
     134            if self.tsNow is None:
     135                self.__tsNowDateTime = self._oDb.getCurrentTimestamp();
     136            else:
     137                self._oDb.execute('SELECT %s::TIMESTAMP WITH TIME ZONE', (self.tsNow,));
     138                self.__tsNowDateTime = self._oDb.fetchOne()[0];
     139        return self.__tsNowDateTime;
     140
    129141    def getPeriodStart(self, iPeriod):
    130142        """ Gets the python timestamp for the start of the given period. """
    131143        from datetime import timedelta;
    132         tsNow = self.tsNow if self.tsNow is not None else self._oDb.getCurrentTimestamp();
    133144        cHoursStart = (self.cPeriods - iPeriod    ) * self.cHoursPerPeriod;
    134         return tsNow - timedelta(hours = cHoursStart);
     145        return self.getNowAsDateTime() - timedelta(hours = cHoursStart);
    135146
    136147    def getPeriodEnd(self, iPeriod):
    137148        """ Gets the python timestamp for the end of the given period. """
    138149        from datetime import timedelta;
    139         tsNow = self.tsNow if self.tsNow is not None else self._oDb.getCurrentTimestamp();
    140150        cHoursEnd   = (self.cPeriods - iPeriod - 1) * self.cHoursPerPeriod;
    141         return tsNow - timedelta(hours = cHoursEnd);
     151        return self.getNowAsDateTime() - timedelta(hours = cHoursEnd);
    142152
    143153    def getExtraWhereExprForPeriod(self, iPeriod):
     
    180190
    181191
    182 class ReportFailureReasonRow(object):
    183     """ Simpler to return this than muck about with stupid arrays. """
    184     def __init__(self, aoRow, oReason):
    185         self.idFailureReason    = aoRow[0];
    186         self.cHits              = aoRow[1];
    187         self.tsMin              = aoRow[2];
    188         self.tsMax              = aoRow[3];
    189         self.oReason            = oReason; # FailureReasonDataEx
    190 
    191 class ReportFailureReasonTransient(object):
    192     """ Details the first or last occurence of a reason.  """
    193     def __init__(self, idBuild, iRevision, sRepository, idTestSet, idTestResult, tsDone,  # pylint: disable=R0913
    194                  oReason, iPeriod, fEnter):
     192#
     193# Data structures produced and returned by the ReportLazyModel.
     194#
     195
     196class ReportTransientBase(object):
     197    """ Details on the test where a problem was first/last seen.  """
     198    def __init__(self, idBuild, iRevision, sRepository, idTestSet, idTestResult, tsDone, iPeriod, fEnter):
    195199        self.idBuild            = idBuild;      # Build ID.
    196200        self.iRevision          = iRevision;    # SVN revision for build.
     
    199203        self.idTestResult       = idTestResult; # Test result.
    200204        self.tsDone             = tsDone;       # When the test set was done.
    201         self.oReason            = oReason;      # FailureReasonDataEx
    202205        self.iPeriod            = iPeriod;      # Data set period.
    203206        self.fEnter             = fEnter;       # True if enter event, False if leave event.
    204207
    205 class ReportFailureReasonPeriod(object):
     208class ReportFailureReasonTransient(ReportTransientBase):
     209    """ Details on the test where a failure reason was first/last seen.  """
     210    def __init__(self, idBuild, iRevision, sRepository, idTestSet, idTestResult, tsDone,  # pylint: disable=R0913
     211                 iPeriod, fEnter, oReason):
     212        ReportTransientBase.__init__(self, idBuild, iRevision, sRepository, idTestSet, idTestResult, tsDone, iPeriod, fEnter);
     213        self.oReason            = oReason;      # FailureReasonDataEx
     214
     215class ReportTestCaseFailureTransient(ReportTransientBase):
     216    """ Details on the test where a test case was first/last seen.  """
     217    def __init__(self, idBuild, iRevision, sRepository, idTestSet, idTestResult, tsDone,  # pylint: disable=R0913
     218                 iPeriod, fEnter, oTestCase):
     219        ReportTransientBase.__init__(self, idBuild, iRevision, sRepository, idTestSet, idTestResult, tsDone, iPeriod, fEnter);
     220        self.oTestCase          = oTestCase;      # TestCaseDataEx
     221
     222
     223class ReportHitRowBase(object):
     224    """ A row in a period. """
     225    def __init__(self, cHits, tsMin = None, tsMax = None):
     226        self.cHits              = cHits;
     227        self.tsMin              = tsMin;
     228        self.tsMax              = tsMax;
     229
     230class ReportFailureReasonRow(ReportHitRowBase):
     231    """ The account of one failure reason for a period. """
     232    def __init__(self, aoRow, oReason):
     233        ReportHitRowBase.__init__(self, aoRow[1], aoRow[2], aoRow[3]);
     234        self.idFailureReason    = aoRow[0];
     235        self.oReason            = oReason;      # FailureReasonDataEx
     236
     237class ReportTestCaseFailureRow(ReportHitRowBase):
     238    """ The account of one test case for a period. """
     239    def __init__(self, aoRow, aoTestCase):
     240        ReportHitRowBase.__init__(self, aoRow[1], aoRow[2], aoRow[3]);
     241        self.idTestCase         = aoRow[0];
     242        self.aoTestCase         = aoTestCase;   # TestCaseDataEx
     243
     244
     245class ReportPeriodBase(object):
    206246    """ A period in ReportFailureReasonSet. """
    207247    def __init__(self, oSet, iPeriod, sDesc, tsFrom, tsTo):
    208         self.oSet               = oSet          # Reference to the parent ReportFailureReasonSet.
    209         self.iPeriod            = iPeriod;
    210         self.sDesc              = sDesc;
    211         self.aoRows             = [];           # Rows in order the database returned them.
    212         self.dById              = {};           # Same as aoRows but indexed by idFailureReason.
    213         self.cHits              = 0;            # Total number of hits.
    214         self.dFirst             = {};           # The reasons seen for the first time (idFailureReason key).
    215         self.dLast              = {};           # The reasons seen for the last time (idFailureReason key).
    216         self.tsStart            = tsFrom;
    217         self.tsEnd              = tsTo;
    218         self.tsMin              = tsTo;
    219         self.tsMax              = tsFrom;
     248        self.oSet               = oSet          # Reference to the parent ReportSetBase derived object.
     249        self.iPeriod            = iPeriod;      # Period number in the set.
     250        self.sDesc              = sDesc;        # Short period description.
     251        self.tsStart            = tsFrom;       # Start of the period.
     252        self.tsEnd              = tsTo;         # End of the period (exclusive).
     253        self.tsMin              = tsTo;         # The earlierst hit of the period (only valid for cHits > 0).
     254        self.tsMax              = tsFrom;       # The latest hit of the period (only valid for cHits > 0).
     255        self.aoRows             = [];           # Rows in order the database returned them (ReportHitRowBase descendant).
     256        self.dRowsById          = {};           # Same as aoRows but indexed by object ID (see ReportSetBase::sIdAttr).
     257        self.dFirst             = {};           # The subjects seen for the first time - data object, keyed by ID.
     258        self.dLast              = {};           # The subjects seen for the last  time - data object, keyed by ID.
     259        self.cHits              = 0;            # Total number of hits in this period.
     260        self.cMaxHits           = 0;            # Max hits in a row.
     261        self.cMinHits           = 99999999;     # Min hits in a row (only valid for cHits > 0).
     262
     263    def appendRow(self, oRow, idRow, oData):
     264        """ Adds a row. """
     265        assert isinstance(oRow, ReportHitRowBase);
     266        self.aoRows.append(oRow);
     267        self.dRowsById[idRow] = oRow;
     268
     269        if oRow.tsMin is not None and oRow.tsMin < self.tsMin:
     270            self.tsMin = oRow.tsMin;
     271        if oRow.tsMax is not None and oRow.tsMax < self.tsMax:
     272            self.tsMax = oRow.tsMax;
     273
     274        self.cHits += oRow.cHits;
     275        if oRow.cHits > self.cMaxHits:
     276            self.cMaxHits = oRow.cHits;
     277        if oRow.cHits < self.cMinHits:
     278            self.cMinHits = oRow.cHits;
     279
     280        if idRow in self.oSet.dcTotalsPerId:
     281            self.oSet.dcTotalsPerId[idRow] += oRow.cHits;
     282        else:
     283            self.dFirst[idRow]              = oData;
     284            self.oSet.dSubjects[idRow]      = oData;
     285            self.oSet.dcTotalsPerId[idRow]  = oRow.cHits;
     286            self.oSet.diPeriodFirst[idRow]  = self.iPeriod;
     287        self.oSet.diPeriodLast[idRow]       = self.iPeriod;
     288
     289class ReportFailureReasonPeriod(ReportPeriodBase):
     290    """ A period in ReportFailureReasonSet. """
     291    def __init__(self, oSet, iPeriod, sDesc, tsFrom, tsTo):
     292        ReportPeriodBase.__init__(self, oSet, iPeriod, sDesc, tsFrom, tsTo);
    220293        self.cWithoutReason     = 0;            # Number of failed test sets without any assigned reason.
    221294
    222 class ReportFailureReasonSet(object):
     295class ReportTestCaseFailurePeriod(ReportPeriodBase):
     296    """ A period in ReportTestCaseFailureSet. """
     297    def __init__(self, oSet, iPeriod, sDesc, tsFrom, tsTo):
     298        ReportPeriodBase.__init__(self, oSet, iPeriod, sDesc, tsFrom, tsTo);
     299
     300
     301class ReportPeriodSetBase(object):
     302    """ Period data set base class. """
     303    def __init__(self, sIdAttr):
     304        self.sIdAttr            = sIdAttr;      # The name of the key attribute.  Mainly for documentation purposes.
     305        self.aoPeriods          = [];           # Periods (ReportPeriodBase descendant) in ascending order (time wise).
     306        self.dcTotalsPerId      = {};           # Totals per subject ID (key).
     307        self.dSubjects          = {};           # The subject data objects, keyed by the subject ID.
     308        self.cHits              = 0;            # Total number of hits in all periods and all reasons.
     309        self.cMaxHits           = 0;            # Max hits in a row.
     310        self.cMinHits           = 0;            # Min hits in a row.
     311        self.cMaxRows           = 0;            # Max number of rows in a period.
     312        self.cMinRows           = 0;            # Min number of rows in a period.
     313        self.diPeriodFirst      = {};           # The period number a reason was first seen (keyed by subject ID).
     314        self.diPeriodLast       = {};           # The period number a reason was last seen (keyed by subject ID).
     315        self.aoEnterInfo        = [];           # Array of ReportTransientBase children order by iRevision.  Excludes
     316                                                # the first period of course.  (Child class populates this.)
     317        self.aoLeaveInfo        = [];           # Array of ReportTransientBase children order in descending order by
     318                                                # iRevision. Excludes the last priod.  (Child class populates this.)
     319
     320    def appendPeriod(self, oPeriod):
     321        """ Appends a period to the set. """
     322        assert isinstance(oPeriod, ReportPeriodBase);
     323        self.aoPeriods.append(oPeriod);
     324
     325        self.cHits += oPeriod.cHits;
     326        if oPeriod.cHits > self.cMaxHits:
     327            self.cMaxHits = oPeriod.cHits;
     328        if oPeriod.cHits < self.cMinHits:
     329            self.cMinHits = oPeriod.cHits;
     330
     331        if len(oPeriod.aoRows) > self.cMaxHits:
     332            self.cMaxHits = len(oPeriod.aoRows);
     333        if len(oPeriod.aoRows) < self.cMinHits:
     334            self.cMinHits = len(oPeriod.aoRows);
     335
     336    def finalizePass1(self):
     337        """ Finished all but aoEnterInfo and aoLeaveInfo. """
     338        # All we need to do here is to populate the dLast members.
     339        for idKey, iPeriod in self.diPeriodLast.items():
     340            self.aoPeriods[iPeriod].dLast[idKey] = self.dSubjects[idKey];
     341        return self;
     342
     343    def finalizePass2(self):
     344        """ Called after aoEnterInfo and aoLeaveInfo has been populated to sort them. """
     345        self.aoEnterInfo = sorted(self.aoEnterInfo, key = lambda oTrans: oTrans.iRevision);
     346        self.aoLeaveInfo = sorted(self.aoLeaveInfo, key = lambda oTrans: oTrans.iRevision, reverse = True);
     347        return self;
     348
     349class ReportFailureReasonSet(ReportPeriodSetBase):
    223350    """ What ReportLazyModel.getFailureReasons returns. """
    224351    def __init__(self):
    225         self.aoPeriods   = [];  # Periods in ascending order (time wise).
    226         self.dReasons    = {};  # FailureReasonDataEx objected indexted by idFailureReason
    227         self.dTotals     = {};  # Totals per reason, indexed by idFailureReason.
    228         self.cHits       = 0;   # Total number of hits in all periods and all reasons.
    229         self.cMaxRowHits = 0;   # Max hits in a row.
    230         self.diFirst     = {};  # The period number a reason was first seen (idFailureReason key).
    231         self.diLast      = {};  # The period number a reason was last seen (idFailureReason key).
    232         self.aoEnterInfo = [];  # Array of ReportFailureReasonTransient order by iRevision. Excludes the first period.
    233         self.aoLeaveInfo = [];  # Array of ReportFailureReasonTransient order in descending order by iRevision.  Excludes last.
     352        ReportPeriodSetBase.__init__(self, 'idFailureReason');
     353
     354class ReportTestCaseFailureSet(ReportPeriodSetBase):
     355    """ What ReportLazyModel.getTestCaseFailures returns. """
     356    def __init__(self):
     357        ReportPeriodSetBase.__init__(self, 'idTestCase');
    234358
    235359
     
    300424        Gets the failure reasons of the subject in the specified period.
    301425
    302         Returns an array of data per period (0 is the oldes, self.cPeriods-1 is
    303         the latest) where each entry is a dicationary using failure reason ID as
    304         key.  The dictionary contains a tuple where the first element is the
    305         number of occurences and the second is the corresponding
    306         FailureReasonDataEx object from the cache.
    307 
    308         Note that reason IDs may not be present in every period, we only return
    309         those with actual occurences.
    310         """
     426        Returns a ReportFailureReasonSet instance.
     427        """
     428
     429        oFailureReasonLogic = FailureReasonLogic(self._oDb);
    311430
    312431        # Create a temporary table
     
    337456        self._oDb.execute('SELECT idFailureReason FROM TmpReasons;');
    338457
    339 
    340458        # Retrieve the period results.
    341         oFailureReasonLogic = FailureReasonLogic(self._oDb);
    342459        oSet = ReportFailureReasonSet();
    343460        for iPeriod in xrange(self.cPeriods):
     
    354471            oPeriod = ReportFailureReasonPeriod(oSet, iPeriod, self.getStraightPeriodDesc(iPeriod),
    355472                                                self.getPeriodStart(iPeriod), self.getPeriodEnd(iPeriod));
    356             oSet.aoPeriods.append(oPeriod);
     473
    357474            for aoRow in aaoRows:
    358475                oReason = oFailureReasonLogic.cachedLookup(aoRow[0]);
    359476                oPeriodRow = ReportFailureReasonRow(aoRow, oReason);
    360                 oPeriod.aoRows.append(oPeriodRow);
    361                 oPeriod.dById[oPeriodRow.idFailureReason] = oPeriodRow;
    362                 oPeriod.cHits += oPeriodRow.cHits;
    363                 if oPeriodRow.idFailureReason in oSet.dReasons:
    364                     oSet.dTotals[oPeriodRow.idFailureReason]  += oPeriodRow.cHits;
    365                 else:
    366                     oSet.dTotals[oPeriodRow.idFailureReason]   = oPeriodRow.cHits;
    367                     oSet.dReasons[oPeriodRow.idFailureReason]  = oReason;
    368                     oSet.diFirst[oPeriodRow.idFailureReason]   = iPeriod;
    369                     oPeriod.dFirst[oPeriodRow.idFailureReason] = oReason;
    370                 if oPeriodRow.cHits > oSet.cMaxRowHits:
    371                     oSet.cMaxRowHits = oPeriodRow.cHits;
    372                 if oPeriodRow.tsMin < oPeriod.tsMin:
    373                     oPeriod.tsMin = oPeriodRow.tsMin;
    374                 if oPeriodRow.tsMax > oPeriod.tsMax:
    375                     oPeriod.tsMax = oPeriodRow.tsMax;
    376             oSet.cHits += oPeriod.cHits;
     477                oPeriod.appendRow(oPeriodRow, oReason.idFailureReason, oReason);
    377478
    378479            # Count how many test sets we've got without any reason associated with them.
     
    388489            oPeriod.cWithoutReason = self._oDb.fetchOne()[0];
    389490
    390 
    391         #
    392         # construct the diLast and dLast bits.
    393         #
    394         for iPeriod in xrange(self.cPeriods - 1, 0, -1):
    395             oPeriod = oSet.aoPeriods[iPeriod];
    396             for oRow in oPeriod.aoRows:
    397                 if oRow.idFailureReason not in oSet.diLast:
    398                     oSet.diLast[oRow.idFailureReason]   = iPeriod;
    399                     oPeriod.dLast[oRow.idFailureReason] = oRow.oReason;
     491            oSet.appendPeriod(oPeriod);
     492
    400493
    401494        #
     
    403496        # test set it first occured with.
    404497        #
     498        oSet.finalizePass1();
     499
    405500        for iPeriod in xrange(1, self.cPeriods):
    406501            oPeriod = oSet.aoPeriods[iPeriod];
    407502            for oReason in oPeriod.dFirst.values():
    408503                oSet.aoEnterInfo.append(self._getEdgeFailureReasonOccurence(oReason, iPeriod, fEnter = True));
    409         oSet.aoEnterInfo = sorted(oSet.aoEnterInfo, key = lambda oTrans: oTrans.iRevision);
    410504
    411505        # Ditto for reasons leaving before the last.
     
    414508            for oReason in oPeriod.dLast.values():
    415509                oSet.aoLeaveInfo.append(self._getEdgeFailureReasonOccurence(oReason, iPeriod, fEnter = False));
    416         oSet.aoLeaveInfo = sorted(oSet.aoLeaveInfo, key = lambda oTrans: oTrans.iRevision, reverse = True);
     510
     511        oSet.finalizePass2();
    417512
    418513        self._oDb.execute('DROP TABLE TmpReasons\n');
     
    431526
    432527        """
     528
     529
    433530        sSorting = 'ASC' if fEnter else 'DESC';
    434531        self._oDb.execute('SELECT   TmpReasons.idTestResult,\n'
     
    452549        aoRow = self._oDb.fetchOne();
    453550        if aoRow is None:
    454             return ReportFailureReasonTransient(-1, -1, 'internal-error', -1, -1,
    455                                                 self._oDb.getCurrentTimestamp(), oReason, iPeriod, fEnter);
     551            return ReportFailureReasonTransient(-1, -1, 'internal-error', -1, -1, self._oDb.getCurrentTimestamp(),
     552                                                iPeriod, fEnter, oReason);
    456553        return ReportFailureReasonTransient(idBuild = aoRow[3], iRevision = aoRow[4], sRepository = aoRow[5],
    457554                                            idTestSet = aoRow[1], idTestResult = aoRow[0], tsDone = aoRow[2],
    458                                             oReason = oReason, iPeriod = iPeriod, fEnter = fEnter);
     555                                            iPeriod = iPeriod, fEnter = fEnter, oReason = oReason);
     556
     557
     558    def getTestCaseFailures(self):
     559        """
     560        Gets the test case failures of the subject in the specified period.
     561
     562        Returns a ReportTestCaseFailureSet instance.
     563
     564        """
     565
     566        oTestCaseLogic = TestCaseLogic(self._oDb);
     567
     568        # Retrieve the period results.
     569        oSet = ReportTestCaseFailureSet();
     570        for iPeriod in xrange(self.cPeriods):
     571            self._oDb.execute('SELECT   idTestCase,\n'
     572                              '         COUNT(idTestResult),\n'
     573                              '         MIN(tsDone),\n'
     574                              '         MAX(tsDone)\n'
     575                              'FROM     TestSets\n'
     576                              'WHERE    TRUE\n'
     577                              + self.getExtraWhereExprForPeriod(iPeriod) +
     578                              'GROUP BY idTestCase\n');
     579            aaoRows = self._oDb.fetchAll()
     580
     581            oPeriod = ReportTestCaseFailurePeriod(oSet, iPeriod, self.getStraightPeriodDesc(iPeriod),
     582                                                  self.getPeriodStart(iPeriod), self.getPeriodEnd(iPeriod));
     583
     584            for aoRow in aaoRows:
     585                oTestCase = oTestCaseLogic.cachedLookup(aoRow[0]);
     586                oPeriodRow = ReportTestCaseFailureRow(aoRow, oTestCase);
     587                oPeriod.appendRow(oPeriodRow, oTestCase.idTestCase, oTestCase);
     588
     589            oSet.appendPeriod(oPeriod);
     590
     591
     592        #
     593        # For reasons entering after the first period, look up the build and
     594        # test set it first occured with.
     595        #
     596        oSet.finalizePass1();
     597
     598        for iPeriod in xrange(1, self.cPeriods):
     599            oPeriod = oSet.aoPeriods[iPeriod];
     600            for oTestCase in oPeriod.dFirst.values():
     601                oSet.aoEnterInfo.append(self._getEdgeTestCaseFailureOccurence(oTestCase, iPeriod, fEnter = True));
     602
     603        # Ditto for reasons leaving before the last.
     604        for iPeriod in xrange(self.cPeriods - 1):
     605            oPeriod = oSet.aoPeriods[iPeriod];
     606            for oTestCase in oPeriod.dLast.values():
     607                oSet.aoLeaveInfo.append(self._getEdgeTestCaseFailureOccurence(oTestCase, iPeriod, fEnter = False));
     608
     609        oSet.finalizePass2();
     610
     611        return oSet;
     612
     613
     614    def _getEdgeTestCaseFailureOccurence(self, oTestCase, iPeriod, fEnter = True):
     615        """
     616        Helper for the failure reason report that finds the oldest or newest build
     617        (SVN rev) and test set (start time) it occured with.
     618
     619        If fEnter is set the oldest occurence is return, if fEnter clear the newest
     620        is is returned.
     621
     622        Returns ReportFailureReasonTransient instant.
     623
     624        """
     625        sSorting = 'ASC' if fEnter else 'DESC';
     626        self._oDb.execute('SELECT   TestSets.idTestResult,\n'
     627                          '         TestSets.idTestSet,\n'
     628                          '         TestSets.tsDone,\n'
     629                          '         TestSets.idBuild,\n'
     630                          '         Builds.iRevision,\n'
     631                          '         BuildCategories.sRepository\n'
     632                          'FROM     TestSets,\n'
     633                          '         Builds,\n'
     634                          '         BuildCategories\n'
     635                          'WHERE    TestSets.idTestCase       = %s\n'
     636                          '     AND TestSets.idBuild          = Builds.idBuild\n'
     637                          '     AND Builds.tsExpire             > TestSets.tsCreated\n'
     638                          '     AND Builds.tsEffective         <= TestSets.tsCreated\n'
     639                          '     AND Builds.idBuildCategory      = BuildCategories.idBuildCategory\n'
     640                          'ORDER BY Builds.iRevision ' + sSorting + ',\n'
     641                          '         TestSets.tsCreated ' + sSorting + '\n'
     642                          'LIMIT 1\n'
     643                          , ( oTestCase.idTestCase, ));
     644        aoRow = self._oDb.fetchOne();
     645        if aoRow is None:
     646            return ReportTestCaseFailureTransient(-1, -1, 'internal-error', -1, -1,
     647                                                  self._oDb.getCurrentTimestamp(), oTestCase, iPeriod, fEnter);
     648        return ReportTestCaseFailureTransient(idBuild = aoRow[3], iRevision = aoRow[4], sRepository = aoRow[5],
     649                                              idTestSet = aoRow[1], idTestResult = aoRow[0], tsDone = aoRow[2],
     650                                              oTestCase = oTestCase, iPeriod = iPeriod, fEnter = fEnter);
    459651
    460652
  • trunk/src/VBox/ValidationKit/testmanager/core/testcase.py

    r61255 r61270  
    934934    Test case management logic.
    935935    """
     936
     937    def __init__(self, oDb):
     938        ModelLogicBase.__init__(self, oDb)
     939        self.ahCache = None;
    936940
    937941    def getAll(self):
     
    13491353
    13501354
     1355    def cachedLookup(self, idTestCase):
     1356        """
     1357        Looks up the most recent TestCaseDataEx object for uid idTestCase
     1358        an object cache.
     1359
     1360        Returns a shared TestCaseDataEx object.  None if not found.
     1361        Raises exception on DB error.
     1362        """
     1363        if self.ahCache is None:
     1364            self.ahCache = self._oDb.getCache('TestCaseDataEx');
     1365        oEntry = self.ahCache.get(idTestCase, None);
     1366        if oEntry is None:
     1367            ##fNeedTsNow = False;
     1368            fNeedTsNow = True;
     1369            self._oDb.execute('SELECT   *\n'
     1370                              'FROM     TestCases\n'
     1371                              'WHERE    idTestCase = %s\n'
     1372                              '     AND tsExpire   = \'infinity\'::TIMESTAMP\n'
     1373                              , (idTestCase, ));
     1374            if self._oDb.getRowCount() == 0:
     1375                # Maybe it was deleted, try get the last entry.
     1376                self._oDb.execute('SELECT   *\n'
     1377                                  'FROM     TestCases\n'
     1378                                  'WHERE    idTestCase = %s\n'
     1379                                  'ORDER BY tsExpire DESC\n'
     1380                                  'LIMIT 1\n'
     1381                                  , (idTestCase, ));
     1382                fNeedTsNow = True;
     1383            elif self._oDb.getRowCount() > 1:
     1384                raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idTestCase));
     1385
     1386            if self._oDb.getRowCount() == 1:
     1387                aaoRow = self._oDb.fetchOne();
     1388                oEntry = TestCaseDataEx();
     1389                tsNow  = oEntry.initFromDbRow(aaoRow).tsEffective if fNeedTsNow else None;
     1390                oEntry.initFromDbRowEx(aaoRow, self._oDb, tsNow);
     1391                self.ahCache[idTestCase] = oEntry;
     1392        return oEntry;
     1393
     1394
     1395
    13511396#
    13521397# Unit testing.
  • trunk/src/VBox/ValidationKit/testmanager/core/useraccount.py

    r61220 r61270  
    247247                                  'FROM     Users\n'
    248248                                  'WHERE    uid = %s\n'
    249                                   'ORDER BY tsExpire\n'
     249                                  'ORDER BY tsExpire DESC\n'
    250250                                  'LIMIT 1\n'
    251251                                  , (uid, ));
  • trunk/src/VBox/ValidationKit/testmanager/webui/wuimain.py

    r61267 r61270  
    8080    ksActionReportSummary               = 'ReportSummary';
    8181    ksActionReportRate                  = 'ReportRate';
     82    ksActionReportTestCaseFailures      = 'ReportTestCaseFailures';
    8283    ksActionReportFailureReasons        = 'ReportFailureReasons';
    8384    ksActionGraphWiz                    = 'GraphWiz';
     
    248249        d[self.ksActionViewLog]                     = self.actionViewLog;
    249250        d[self.ksActionGetFile]                     = self.actionGetFile;
    250         from testmanager.webui.wuireport import WuiReportSummary, WuiReportSuccessRate, WuiReportFailureReasons;
    251         d[self.ksActionReportSummary]               = lambda: self._actionGenericReport(ReportLazyModel, WuiReportSummary);
    252         d[self.ksActionReportRate]                  = lambda: self._actionGenericReport(ReportLazyModel, WuiReportSuccessRate);
    253         d[self.ksActionReportFailureReasons]        = lambda: self._actionGenericReport(ReportLazyModel, WuiReportFailureReasons);
    254         d[self.ksActionGraphWiz]                    = self._actionGraphWiz;
    255         d[self.ksActionVcsHistoryTooltip]           = self._actionVcsHistoryTooltip;
     251
     252        from testmanager.webui.wuireport import WuiReportSummary, WuiReportSuccessRate, WuiReportTestCaseFailures, \
     253                                                WuiReportFailureReasons;
     254        d[self.ksActionReportSummary]          = lambda: self._actionGenericReport(ReportLazyModel, WuiReportSummary);
     255        d[self.ksActionReportRate]             = lambda: self._actionGenericReport(ReportLazyModel, WuiReportSuccessRate);
     256        d[self.ksActionReportTestCaseFailures] = lambda: self._actionGenericReport(ReportLazyModel, WuiReportTestCaseFailures);
     257        d[self.ksActionReportFailureReasons]   = lambda: self._actionGenericReport(ReportLazyModel, WuiReportFailureReasons);
     258        d[self.ksActionGraphWiz]               = self._actionGraphWiz;
     259
     260        d[self.ksActionVcsHistoryTooltip]      = self._actionVcsHistoryTooltip;
    256261
    257262        # Legacy.
    258         d['TestResultDetails']                      = d[self.ksActionTestSetDetails];
     263        d['TestResultDetails']                 = d[self.ksActionTestSetDetails];
    259264
    260265
     
    295300                    [ 'Summary',                  sActUrlBase + self.ksActionReportSummary ],
    296301                    [ 'Success Rate',             sActUrlBase + self.ksActionReportRate ],
     302                    [ 'Test Case Failures',       sActUrlBase + self.ksActionReportTestCaseFailures ],
    297303                    [ 'Failure Reasons',          sActUrlBase + self.ksActionReportFailureReasons ],
    298304                ]
  • trunk/src/VBox/ValidationKit/testmanager/webui/wuireport.py

    r61269 r61270  
    128128
    129129
    130 class WuiReportFailureReasons(WuiReportBase):
    131     """
    132     Generates a report displaying the failure reasons over time.
    133     """
     130class WuiReportFailuresBase(WuiReportBase):
     131    """
     132    Common parent of WuiReportFailureReasons and WuiReportTestCaseFailures.
     133    """
     134
     135    def _formatEdgeOccurenceSubject(self, oTransient):
     136        """
     137        Worker for _formatEdgeOccurence that child classes overrides to format
     138        their type of subject data in the best possible way.
     139        """
     140        _ = oTransient;
     141        assert False;
     142        return '';
    134143
    135144    def _formatEdgeOccurence(self, oTransient):
    136145        """
    137146        Helper for formatting the transients.
    138         oTransient is of type ReportFailureReasonTransient.
     147        oTransient is of type ReportFailureReasonTransient or ReportTestCaseFailureTransient.
    139148        """
    140149        sHtml = u'<li>';
     
    143152        sHtml += WuiSvnLinkWithTooltip(oTransient.iRevision, oTransient.sRepository, fBracketed = 'False').toHtml();
    144153        sHtml += u', %s: ' % (self.formatTsShort(oTransient.tsDone),);
    145         sHtml += u'%s / %s' % (webutils.escapeElem(oTransient.oReason.oCategory.sShort),
    146                                webutils.escapeElem(oTransient.oReason.sShort),);
     154        sHtml += self._formatEdgeOccurenceSubject(oTransient);
    147155        sHtml += u'</li>\n';
    148 
    149         return sHtml;
    150 
    151 
    152     def generateReportBody(self):
    153         self._sTitle = 'Failure reasons';
    154 
    155         sHtml = u'';
    156 
    157         #
    158         # The array of periods we get have the oldest period first [0].
    159         #
    160         oSet = self._oModel.getFailureReasons();
    161 
    162         #
    163         # List failure reasons starting or stopping to appear within the data set.
    164         #
    165         dtFirstLast = {};
    166         for iPeriod, oPeriod in enumerate(oSet.aoPeriods):
    167             for oRow in oPeriod.aoRows:
    168                 tIt = dtFirstLast.get(oRow.idFailureReason, (iPeriod, iPeriod));
    169                 #sHtml += u'<!-- %d: %d,%d -- %d -->\n' % (oRow.idFailureReason, tIt[0], tIt[1], iPeriod);
    170                 dtFirstLast[oRow.idFailureReason] = (tIt[0], iPeriod);
    171 
    172         sHtml += '<!-- \n';
    173         for iPeriod, oPeriod in enumerate(oSet.aoPeriods):
    174             sHtml += ' iPeriod=%d tsStart=%s tsEnd=%s\n' % (iPeriod, oPeriod.tsStart, oPeriod.tsEnd,);
    175             sHtml += '             tsMin=%s tsMax=%s\n' % (oPeriod.tsMin, oPeriod.tsMax,);
    176             sHtml += '              %d / %s\n' % (oPeriod.iPeriod, oPeriod.sDesc,)
    177         sHtml += '-->\n';
    178 
    179         sHtml += u'<h4>Changes:</h4>\n' \
     156        return sHtml;
     157
     158    def _generateTransitionList(self, oSet):
     159        """
     160        Generates the enter and leave lists.
     161        """
     162        sHtml  = u'<h4>Movements:</h4>\n' \
    180163                 u'<ul>\n';
    181164        if len(oSet.aoEnterInfo) == 0 and len(oSet.aoLeaveInfo) == 0:
    182             sHtml += u'<li> No changes</li>\n';
     165            sHtml += u'<li>No changes</li>\n';
    183166        else:
    184167            for oTransient in oSet.aoEnterInfo:
     
    188171        sHtml += u'</ul>\n';
    189172
     173        return sHtml;
     174
     175
     176
     177class WuiReportFailureReasons(WuiReportFailuresBase):
     178    """
     179    Generates a report displaying the failure reasons over time.
     180    """
     181
     182    def _formatEdgeOccurenceSubject(self, oTransient):
     183        return u'%s / %s' % ( webutils.escapeElem(oTransient.oReason.oCategory.sShort),
     184                              webutils.escapeElem(oTransient.oReason.sShort),);
     185
     186
     187    def generateReportBody(self):
     188        self._sTitle = 'Failure reasons';
     189
     190        #
     191        # Get the data and generate transition list.
     192        #
     193        oSet = self._oModel.getFailureReasons();
     194        sHtml = self._generateTransitionList(oSet);
     195
    190196        #
    191197        # Check if most of the stuff is without any assign reason, if so, skip
     
    194200        fIncludeWithoutReason = True;
    195201        for oPeriod in reversed(oSet.aoPeriods):
    196             if oPeriod.cWithoutReason > oSet.cMaxRowHits * 4:
     202            if oPeriod.cWithoutReason > oSet.cMaxHits * 4:
    197203                fIncludeWithoutReason = False;
    198204                sHtml += '<p>Warning: Many failures without assigned reason!</p>\n';
     
    200206
    201207        #
    202         # Graph.
    203         #
    204         if True: # pylint: disable=W0125
    205             aidSorted = sorted(oSet.dReasons, key = lambda idReason: oSet.dTotals[idReason], reverse = True);
    206         else:
    207             aidSorted = sorted(oSet.dReasons, key = lambda idReason: '%s / %s' % ( oSet.dReasons[idReason].oCategory.sShort,
    208                                                                                    oSet.dReasons[idReason].sShort, ));
     208        # Generate the graph.
     209        #
     210        aidSorted = sorted(oSet.dSubjects, key = lambda idReason: oSet.dcTotalsPerId[idReason], reverse = True);
     211
    209212        asNames = [];
    210213        for idReason in aidSorted:
    211             oReason = oSet.dReasons[idReason];
     214            oReason = oSet.dSubjects[idReason];
    212215            asNames.append('%s / %s' % (oReason.oCategory.sShort, oReason.sShort,) )
    213216        if fIncludeWithoutReason:
     
    216219        oTable = WuiHlpGraphDataTable('Period', asNames);
    217220
    218         cMax = oSet.cMaxRowHits;
    219         for iPeriod, oPeriod in enumerate(reversed(oSet.aoPeriods)):
     221        cMax = oSet.cMaxHits;
     222        for _, oPeriod in enumerate(reversed(oSet.aoPeriods)):
    220223            aiValues = [];
    221224
    222225            for idReason in aidSorted:
    223                 oRow = oPeriod.dById.get(idReason, None);
     226                oRow = oPeriod.dRowsById.get(idReason, None);
    224227                iValue = oRow.cHits if oRow is not None else 0;
    225228                aiValues.append(iValue);
     
    244247
    245248
     249class WuiReportTestCaseFailures(WuiReportFailuresBase):
     250    """
     251    Generates a report displaying the failure reasons over time.
     252    """
     253
     254    def _formatEdgeOccurenceSubject(self, oTransient):
     255        return u'%s' % ( webutils.escapeElem(oTransient.oTestCase.sName),);
     256
     257
     258    def generateReportBody(self):
     259        self._sTitle = 'Test Case Failures';
     260
     261        #
     262        # Get the data and generate transition list.
     263        #
     264        oSet = self._oModel.getTestCaseFailures();
     265        sHtml = self._generateTransitionList(oSet);
     266
     267        #
     268        # Generate the graph.
     269        #
     270        aidSorted = sorted(oSet.dSubjects, key = lambda idTestCase: oSet.dcTotalsPerId[idTestCase], reverse = True);
     271
     272        asNames = [];
     273        for idKey in aidSorted:
     274            oSubject = oSet.dSubjects[idKey];
     275            asNames.append(oSubject.sName);
     276
     277        oTable = WuiHlpGraphDataTable('Period', asNames);
     278
     279        cMax = oSet.cMaxHits;
     280        for _, oPeriod in enumerate(reversed(oSet.aoPeriods)):
     281            aiValues = [];
     282
     283            for idKey in aidSorted:
     284                oRow = oPeriod.dRowsById.get(idKey, None);
     285                iValue = oRow.cHits if oRow is not None else 0;
     286                aiValues.append(iValue);
     287
     288            oTable.addRow(oPeriod.sDesc, aiValues);
     289
     290        oGraph = WuiHlpBarGraph('testcase-failures', oTable, self._oDisp);
     291        oGraph.setRangeMax(max(cMax + 1, 3));
     292        sHtml += oGraph.renderGraph();
     293
     294        return sHtml;
     295
     296
    246297class WuiReportSummary(WuiReportBase):
    247298    """
     
    254305             % (self._oModel.sSubject, self._oModel.aidSubjects,);
    255306
    256         oSuccessRate = WuiReportSuccessRate(self._oModel, self._dParams, fSubReport = True,
    257                                             fnDPrint = self._fnDPrint, oDisp = self._oDisp);
    258 
    259 
    260 
    261         oFailureReasons = WuiReportFailureReasons(self._oModel, self._dParams, fSubReport = True,
    262                                                   fnDPrint = self._fnDPrint, oDisp = self._oDisp);
    263         for oReport in [oSuccessRate, oFailureReasons, ]:
     307        oSuccessRate      = WuiReportSuccessRate(     self._oModel, self._dParams, fSubReport = True,
     308                                                      fnDPrint = self._fnDPrint, oDisp = self._oDisp);
     309        oTestCaseFailures = WuiReportTestCaseFailures(self._oModel, self._dParams, fSubReport = True,
     310                                                      fnDPrint = self._fnDPrint, oDisp = self._oDisp);
     311        oFailureReasons   = WuiReportFailureReasons(  self._oModel, self._dParams, fSubReport = True,
     312                                                      fnDPrint = self._fnDPrint, oDisp = self._oDisp);
     313        for oReport in [oSuccessRate, oTestCaseFailures, oFailureReasons, ]:
    264314            (sTitle, sContent) = oReport.show();
    265315            sHtml += '<br>'; # drop this layout hack
Note: See TracChangeset for help on using the changeset viewer.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette