Changeset 61270 in vbox for trunk/src/VBox/ValidationKit/testmanager/core/report.py
- Timestamp:
- May 29, 2016 12:34:45 AM (9 years ago)
- svn:sync-xref-src-repo-rev:
- 107562
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/ValidationKit/testmanager/core/report.py
r61269 r61270 36 36 from testmanager.core.failurereason import FailureReasonLogic; 37 37 from testmanager.core.testbox import TestBoxData; 38 from testmanager.core.testcase import TestCaseLogic; 38 39 from common import constants; 39 40 … … 75 76 # Public so the report generator can easily access them. 76 77 self.tsNow = tsNow; # (Can be None.) 78 self.__tsNowDateTime = None; 77 79 self.cPeriods = cPeriods; 78 80 self.cHoursPerPeriod = cHoursPerPeriod; … … 127 129 return sWhere; 128 130 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 129 141 def getPeriodStart(self, iPeriod): 130 142 """ Gets the python timestamp for the start of the given period. """ 131 143 from datetime import timedelta; 132 tsNow = self.tsNow if self.tsNow is not None else self._oDb.getCurrentTimestamp();133 144 cHoursStart = (self.cPeriods - iPeriod ) * self.cHoursPerPeriod; 134 return tsNow- timedelta(hours = cHoursStart);145 return self.getNowAsDateTime() - timedelta(hours = cHoursStart); 135 146 136 147 def getPeriodEnd(self, iPeriod): 137 148 """ Gets the python timestamp for the end of the given period. """ 138 149 from datetime import timedelta; 139 tsNow = self.tsNow if self.tsNow is not None else self._oDb.getCurrentTimestamp();140 150 cHoursEnd = (self.cPeriods - iPeriod - 1) * self.cHoursPerPeriod; 141 return tsNow- timedelta(hours = cHoursEnd);151 return self.getNowAsDateTime() - timedelta(hours = cHoursEnd); 142 152 143 153 def getExtraWhereExprForPeriod(self, iPeriod): … … 180 190 181 191 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 196 class 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): 195 199 self.idBuild = idBuild; # Build ID. 196 200 self.iRevision = iRevision; # SVN revision for build. … … 199 203 self.idTestResult = idTestResult; # Test result. 200 204 self.tsDone = tsDone; # When the test set was done. 201 self.oReason = oReason; # FailureReasonDataEx202 205 self.iPeriod = iPeriod; # Data set period. 203 206 self.fEnter = fEnter; # True if enter event, False if leave event. 204 207 205 class ReportFailureReasonPeriod(object): 208 class 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 215 class 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 223 class 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 230 class 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 237 class 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 245 class ReportPeriodBase(object): 206 246 """ A period in ReportFailureReasonSet. """ 207 247 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 289 class 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); 220 293 self.cWithoutReason = 0; # Number of failed test sets without any assigned reason. 221 294 222 class ReportFailureReasonSet(object): 295 class 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 301 class 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 349 class ReportFailureReasonSet(ReportPeriodSetBase): 223 350 """ What ReportLazyModel.getFailureReasons returns. """ 224 351 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 354 class ReportTestCaseFailureSet(ReportPeriodSetBase): 355 """ What ReportLazyModel.getTestCaseFailures returns. """ 356 def __init__(self): 357 ReportPeriodSetBase.__init__(self, 'idTestCase'); 234 358 235 359 … … 300 424 Gets the failure reasons of the subject in the specified period. 301 425 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); 311 430 312 431 # Create a temporary table … … 337 456 self._oDb.execute('SELECT idFailureReason FROM TmpReasons;'); 338 457 339 340 458 # Retrieve the period results. 341 oFailureReasonLogic = FailureReasonLogic(self._oDb);342 459 oSet = ReportFailureReasonSet(); 343 460 for iPeriod in xrange(self.cPeriods): … … 354 471 oPeriod = ReportFailureReasonPeriod(oSet, iPeriod, self.getStraightPeriodDesc(iPeriod), 355 472 self.getPeriodStart(iPeriod), self.getPeriodEnd(iPeriod)); 356 oSet.aoPeriods.append(oPeriod); 473 357 474 for aoRow in aaoRows: 358 475 oReason = oFailureReasonLogic.cachedLookup(aoRow[0]); 359 476 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); 377 478 378 479 # Count how many test sets we've got without any reason associated with them. … … 388 489 oPeriod.cWithoutReason = self._oDb.fetchOne()[0]; 389 490 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 400 493 401 494 # … … 403 496 # test set it first occured with. 404 497 # 498 oSet.finalizePass1(); 499 405 500 for iPeriod in xrange(1, self.cPeriods): 406 501 oPeriod = oSet.aoPeriods[iPeriod]; 407 502 for oReason in oPeriod.dFirst.values(): 408 503 oSet.aoEnterInfo.append(self._getEdgeFailureReasonOccurence(oReason, iPeriod, fEnter = True)); 409 oSet.aoEnterInfo = sorted(oSet.aoEnterInfo, key = lambda oTrans: oTrans.iRevision);410 504 411 505 # Ditto for reasons leaving before the last. … … 414 508 for oReason in oPeriod.dLast.values(): 415 509 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(); 417 512 418 513 self._oDb.execute('DROP TABLE TmpReasons\n'); … … 431 526 432 527 """ 528 529 433 530 sSorting = 'ASC' if fEnter else 'DESC'; 434 531 self._oDb.execute('SELECT TmpReasons.idTestResult,\n' … … 452 549 aoRow = self._oDb.fetchOne(); 453 550 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); 456 553 return ReportFailureReasonTransient(idBuild = aoRow[3], iRevision = aoRow[4], sRepository = aoRow[5], 457 554 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); 459 651 460 652
Note:
See TracChangeset
for help on using the changeset viewer.