Changeset 61270 in vbox
- Timestamp:
- May 29, 2016 12:34:45 AM (9 years ago)
- svn:sync-xref-src-repo-rev:
- 107562
- Location:
- trunk/src/VBox/ValidationKit/testmanager
- Files:
-
- 7 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/ValidationKit/testmanager/core/failurecategory.py
r61250 r61270 324 324 'FROM FailureCategories\n' 325 325 'WHERE idFailureCategory = %s\n' 326 'ORDER BY tsExpire \n'326 'ORDER BY tsExpire DESC\n' 327 327 'LIMIT 1\n' 328 328 , (idFailureCategory, )); -
trunk/src/VBox/ValidationKit/testmanager/core/failurereason.py
r61250 r61270 423 423 'FROM FailureReasons\n' 424 424 'WHERE idFailureReason = %s\n' 425 'ORDER BY tsExpire \n'425 'ORDER BY tsExpire DESC\n' 426 426 'LIMIT 1\n' 427 427 , (idFailureReason, )); -
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 -
trunk/src/VBox/ValidationKit/testmanager/core/testcase.py
r61255 r61270 934 934 Test case management logic. 935 935 """ 936 937 def __init__(self, oDb): 938 ModelLogicBase.__init__(self, oDb) 939 self.ahCache = None; 936 940 937 941 def getAll(self): … … 1349 1353 1350 1354 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 1351 1396 # 1352 1397 # Unit testing. -
trunk/src/VBox/ValidationKit/testmanager/core/useraccount.py
r61220 r61270 247 247 'FROM Users\n' 248 248 'WHERE uid = %s\n' 249 'ORDER BY tsExpire \n'249 'ORDER BY tsExpire DESC\n' 250 250 'LIMIT 1\n' 251 251 , (uid, )); -
trunk/src/VBox/ValidationKit/testmanager/webui/wuimain.py
r61267 r61270 80 80 ksActionReportSummary = 'ReportSummary'; 81 81 ksActionReportRate = 'ReportRate'; 82 ksActionReportTestCaseFailures = 'ReportTestCaseFailures'; 82 83 ksActionReportFailureReasons = 'ReportFailureReasons'; 83 84 ksActionGraphWiz = 'GraphWiz'; … … 248 249 d[self.ksActionViewLog] = self.actionViewLog; 249 250 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; 256 261 257 262 # Legacy. 258 d['TestResultDetails'] 263 d['TestResultDetails'] = d[self.ksActionTestSetDetails]; 259 264 260 265 … … 295 300 [ 'Summary', sActUrlBase + self.ksActionReportSummary ], 296 301 [ 'Success Rate', sActUrlBase + self.ksActionReportRate ], 302 [ 'Test Case Failures', sActUrlBase + self.ksActionReportTestCaseFailures ], 297 303 [ 'Failure Reasons', sActUrlBase + self.ksActionReportFailureReasons ], 298 304 ] -
trunk/src/VBox/ValidationKit/testmanager/webui/wuireport.py
r61269 r61270 128 128 129 129 130 class WuiReportFailureReasons(WuiReportBase): 131 """ 132 Generates a report displaying the failure reasons over time. 133 """ 130 class 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 ''; 134 143 135 144 def _formatEdgeOccurence(self, oTransient): 136 145 """ 137 146 Helper for formatting the transients. 138 oTransient is of type ReportFailureReasonTransient .147 oTransient is of type ReportFailureReasonTransient or ReportTestCaseFailureTransient. 139 148 """ 140 149 sHtml = u'<li>'; … … 143 152 sHtml += WuiSvnLinkWithTooltip(oTransient.iRevision, oTransient.sRepository, fBracketed = 'False').toHtml(); 144 153 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); 147 155 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' \ 180 163 u'<ul>\n'; 181 164 if len(oSet.aoEnterInfo) == 0 and len(oSet.aoLeaveInfo) == 0: 182 sHtml += u'<li> 165 sHtml += u'<li>No changes</li>\n'; 183 166 else: 184 167 for oTransient in oSet.aoEnterInfo: … … 188 171 sHtml += u'</ul>\n'; 189 172 173 return sHtml; 174 175 176 177 class 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 190 196 # 191 197 # Check if most of the stuff is without any assign reason, if so, skip … … 194 200 fIncludeWithoutReason = True; 195 201 for oPeriod in reversed(oSet.aoPeriods): 196 if oPeriod.cWithoutReason > oSet.cMax RowHits * 4:202 if oPeriod.cWithoutReason > oSet.cMaxHits * 4: 197 203 fIncludeWithoutReason = False; 198 204 sHtml += '<p>Warning: Many failures without assigned reason!</p>\n'; … … 200 206 201 207 # 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 209 212 asNames = []; 210 213 for idReason in aidSorted: 211 oReason = oSet.d Reasons[idReason];214 oReason = oSet.dSubjects[idReason]; 212 215 asNames.append('%s / %s' % (oReason.oCategory.sShort, oReason.sShort,) ) 213 216 if fIncludeWithoutReason: … … 216 219 oTable = WuiHlpGraphDataTable('Period', asNames); 217 220 218 cMax = oSet.cMax RowHits;219 for iPeriod, oPeriod in enumerate(reversed(oSet.aoPeriods)):221 cMax = oSet.cMaxHits; 222 for _, oPeriod in enumerate(reversed(oSet.aoPeriods)): 220 223 aiValues = []; 221 224 222 225 for idReason in aidSorted: 223 oRow = oPeriod.d ById.get(idReason, None);226 oRow = oPeriod.dRowsById.get(idReason, None); 224 227 iValue = oRow.cHits if oRow is not None else 0; 225 228 aiValues.append(iValue); … … 244 247 245 248 249 class 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 246 297 class WuiReportSummary(WuiReportBase): 247 298 """ … … 254 305 % (self._oModel.sSubject, self._oModel.aidSubjects,); 255 306 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, ]: 264 314 (sTitle, sContent) = oReport.show(); 265 315 sHtml += '<br>'; # drop this layout hack
Note:
See TracChangeset
for help on using the changeset viewer.