- Timestamp:
- May 26, 2016 8:04:05 PM (9 years ago)
- Location:
- trunk/src/VBox/ValidationKit/testmanager
- Files:
-
- 1 added
- 14 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/ValidationKit/testmanager/core/db.py
r61185 r61217 33 33 import datetime; 34 34 import os; 35 import sys; 35 36 import psycopg2; 36 37 import psycopg2.extensions; 37 import sys;38 38 39 39 # Validation Kit imports. … … 222 222 oSrvGlue.registerDebugInfoCallback(self.debugInfoCallback); 223 223 224 # Object caches (used by database logic classes). 225 self.ddCaches = dict(); 226 224 227 def isAutoCommitting(self): 225 228 """ Work around missing autocommit attribute in older versions.""" … … 528 531 oCursor = self._oConn.cursor(); 529 532 return TMDatabaseCursor(self, oCursor); 533 534 # 535 # Cache support. 536 # 537 def getCache(self, sType): 538 """ Returns the cache dictionary for this data type. """ 539 dRet = self.ddCaches.get(sType, None); 540 if dRet is None: 541 dRet = dict(); 542 self.ddCaches[sType] = dRet; 543 return dRet; 544 530 545 531 546 # -
trunk/src/VBox/ValidationKit/testmanager/core/failurecategory.py
r56295 r61217 87 87 Failure Category logic. 88 88 """ 89 90 def __init__(self, oDb): 91 ModelLogicBase.__init__(self, oDb) 92 self.ahCache = None; 89 93 90 94 def fetchForListing(self, iStart, cMaxRows, tsNow): … … 193 197 (idFailureCategory,)) 194 198 for iFailureReasonId in self._oDb.fetchAll(): 195 FailureReasonLogic(self._oDb).remove( 196 uidAuthor, iFailureReasonId, fNeedCommit=False) 199 FailureReasonLogic(self._oDb).remove(uidAuthor, iFailureReasonId, fNeedCommit = False) 197 200 198 201 self._oDb.execute('UPDATE FailureCategories\n' … … 238 241 239 242 return True 243 244 def cachedLookup(self, idFailureCategory): 245 """ 246 Looks up the most recent FailureCategoryData object for idFailureCategory 247 via an object cache. 248 249 Returns a shared FailureCategoryData object. None if not found. 250 Raises exception on DB error. 251 """ 252 if self.ahCache is None: 253 self.ahCache = self._oDb.getCache('FailureCategory'); 254 255 oEntry = self.ahCache.get(idFailureCategory, None); 256 if oEntry is None: 257 self._oDb.execute('SELECT *\n' 258 'FROM FailureCategories\n' 259 'WHERE idFailureCategory = %s\n' 260 ' AND tsExpire = \'infinity\'::TIMESTAMP\n' 261 , (idFailureCategory, )); 262 if self._oDb.getRowCount() == 0: 263 # Maybe it was deleted, try get the last entry. 264 self._oDb.execute('SELECT *\n' 265 'FROM FailureCategories\n' 266 'WHERE idFailureCategory = %s\n' 267 'ORDER BY tsExpire\n' 268 'LIMIT 1\n' 269 , (idFailureCategory, )); 270 elif self._oDb.getRowCount() > 1: 271 raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idFailureCategory)); 272 273 if self._oDb.getRowCount() == 1: 274 oEntry = FailureCategoryData().initFromDbRow(self._oDb.fetchOne()); 275 self.ahCache[idFailureCategory] = oEntry; 276 return oEntry; 277 -
trunk/src/VBox/ValidationKit/testmanager/core/failurereason.py
r56295 r61217 31 31 32 32 # Validation Kit imports. 33 from testmanager.core.base import ModelDataBase, ModelLogicBase, TMExceptionBase 33 from testmanager.core.base import ModelDataBase, ModelLogicBase, TMExceptionBase 34 from testmanager.core.useraccount import UserAccountLogic; 35 34 36 35 37 … … 92 94 self.asUrls = aoRow[8] 93 95 94 return self 96 return self; 97 98 99 class FailureReasonDataEx(FailureReasonData): 100 """ 101 Failure Reason Data, extended version that includes the category. 102 """ 103 104 def __init__(self): 105 FailureReasonData.__init__(self); 106 self.oCategory = None; 107 self.oAuthor = None; 108 109 def initFromDbRowEx(self, aoRow, oCategoryLogic, oUserAccountLogic): 110 """ 111 Re-initializes the data with a row from a SELECT * FROM FailureReasons. 112 113 Returns self. Raises exception if the row is None or otherwise invalid. 114 """ 115 116 self.initFromDbRow(aoRow); 117 self.oCategory = oCategoryLogic.cachedLookup(self.idFailureCategory); 118 self.oAuthor = oUserAccountLogic.cachedLookup(self.uidAuthor); 119 120 return self; 95 121 96 122 … … 99 125 Failure Reason logic. 100 126 """ 127 128 def __init__(self, oDb): 129 ModelLogicBase.__init__(self, oDb) 130 self.ahCache = None; 131 self.oCategoryLogic = None; 132 self.oUserAccountLogic = None; 101 133 102 134 def fetchForListing(self, iStart, cMaxRows, tsNow): … … 129 161 return aoRows 130 162 131 def fetchForCombo(self, tsEffective = None):163 def fetchForCombo(self, sFirstEntry = 'Select a failure reason', tsEffective = None): 132 164 """ 133 165 Gets the list of Failure Reasons for a combo box. … … 136 168 """ 137 169 if tsEffective is None: 138 self._oDb.execute('SELECT idFailureReason, sShort, sFull\n' 139 'FROM FailureReasons\n' 140 'WHERE tsExpire = \'infinity\'::TIMESTAMP\n' 141 'ORDER BY sShort') 170 self._oDb.execute('SELECT fr.idFailureReason, CONCAT(fc.sShort, \' / \', fr.sShort) as sComboText, fr.sFull\n' 171 'FROM FailureReasons fr,\n' 172 ' FailureCategories fc\n' 173 'WHERE fr.idFailureCategory = fc.idFailureCategory\n' 174 ' AND fr.tsExpire = \'infinity\'::TIMESTAMP\n' 175 ' AND fc.tsExpire = \'infinity\'::TIMESTAMP\n' 176 'ORDER BY sComboText') 142 177 else: 143 self._oDb.execute('SELECT idFailureReason, sShort, sFull\n' 144 'FROM FailureReasons\n' 145 'WHERE tsExpire > %s\n' 146 ' AND tsEffective <= %s\n' 147 'ORDER BY sShort' 148 , (tsEffective, tsEffective)) 149 return self._oDb.fetchAll() 178 self._oDb.execute('SELECT fr.idFailureReason, CONCAT(fc.sShort, \' / \', fr.sShort) as sComboText, fr.sFull\n' 179 'FROM FailureReasons fr,\n' 180 ' FailureCategories fc\n' 181 'WHERE fr.idFailureCategory = fc.idFailureCategory\n' 182 ' AND fr.tsExpire > %s\n' 183 ' AND fr.tsEffective <= %s\n' 184 ' AND fc.tsExpire > %s\n' 185 ' AND fc.tsEffective <= %s\n' 186 'ORDER BY sComboText' 187 , (tsEffective, tsEffective, tsEffective, tsEffective)); 188 aoRows = self._oDb.fetchAll(); 189 return [(-1, sFirstEntry, '')] + aoRows; 150 190 151 191 def getById(self, idFailureReason): … … 305 345 306 346 return True 347 348 def cachedLookup(self, idFailureReason): 349 """ 350 Looks up the most recent FailureReasonDataEx object for uid idFailureReason 351 an object cache. 352 353 Returns a shared FailureReasonData object. None if not found. 354 Raises exception on DB error. 355 """ 356 if self.ahCache is None: 357 self.ahCache = self._oDb.getCache('FailureReasonDataEx'); 358 oEntry = self.ahCache.get(idFailureReason, None); 359 if oEntry is None: 360 self._oDb.execute('SELECT *\n' 361 'FROM FailureReasons\n' 362 'WHERE idFailureReason = %s\n' 363 ' AND tsExpire = \'infinity\'::TIMESTAMP\n' 364 , (idFailureReason, )); 365 if self._oDb.getRowCount() == 0: 366 # Maybe it was deleted, try get the last entry. 367 self._oDb.execute('SELECT *\n' 368 'FROM FailureReasons\n' 369 'WHERE idFailureReason = %s\n' 370 'ORDER BY tsExpire\n' 371 'LIMIT 1\n' 372 , (idFailureReason, )); 373 elif self._oDb.getRowCount() > 1: 374 raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), idFailureReason)); 375 376 if self._oDb.getRowCount() == 1: 377 if self.oCategoryLogic is None: 378 from testmanager.core.failurecategory import FailureCategoryLogic; 379 self.oCategoryLogic = FailureCategoryLogic(self._oDb); 380 if self.oUserAccountLogic is None: 381 self.oUserAccountLogic = UserAccountLogic(self._oDb); 382 oEntry = FailureReasonDataEx().initFromDbRowEx(self._oDb.fetchOne(), self.oCategoryLogic, 383 self.oUserAccountLogic); 384 self.ahCache[idFailureReason] = oEntry; 385 return oEntry; 386 -
trunk/src/VBox/ValidationKit/testmanager/core/testresults.py
r57680 r61217 38 38 from testmanager import config; 39 39 from testmanager.core.base import ModelDataBase, ModelLogicBase, ModelDataBaseTestCase, TMExceptionBase, TMTooManyRows; 40 from testmanager.core.testgroup import TestGroupData 41 from testmanager.core.build import BuildDataEx 42 from testmanager.core.testbox import TestBoxData 43 from testmanager.core.testcase import TestCaseData 44 from testmanager.core.schedgroup import SchedGroupData 40 from testmanager.core.testgroup import TestGroupData; 41 from testmanager.core.build import BuildDataEx; 42 from testmanager.core.failurereason import FailureReasonLogic; 43 from testmanager.core.testbox import TestBoxData; 44 from testmanager.core.testcase import TestCaseData; 45 from testmanager.core.schedgroup import SchedGroupData; 45 46 from testmanager.core.systemlog import SystemLogData, SystemLogLogic; 47 from testmanager.core.useraccount import UserAccountLogic; 46 48 47 49 … … 143 145 144 146 self.aoChildren = []; # TestResultDataEx; 145 self.aoValues = []; # TestResultValue; 146 self.aoMsgs = []; # TestResultMsg; 147 self.aoFiles = []; # TestResultFile; 147 self.aoValues = []; # TestResultValueDataEx; 148 self.aoMsgs = []; # TestResultMsgDataEx; 149 self.aoFiles = []; # TestResultFileDataEx; 150 self.oReason = None; # TestResultReasonDataEx; 148 151 149 152 def initFromDbRow(self, aoRow): … … 163 166 self.aoMsgs = []; 164 167 self.aoFiles = []; 168 self.oReason = None; 165 169 166 170 TestResultData.initFromDbRow(self, aoRow); … … 406 410 return '%s; charset=utf-8' % (self.sMime,); 407 411 return self.sMime; 412 413 414 class TestResultFailureData(ModelDataBase): 415 """ 416 Test result failure reason data. 417 """ 418 419 ksIdAttr = 'idTestResult'; 420 421 ksParam_idTestResult = 'TestResultFailure_idTestResult'; 422 ksParam_tsEffective = 'TestResultFailure_tsEffective'; 423 ksParam_tsExpire = 'TestResultFailure_tsExpire'; 424 ksParam_uidAuthor = 'TestResultFailure_uidAuthor'; 425 ksParam_idFailureReason = 'TestResultFailure_idFailureReason'; 426 ksParam_sComment = 'TestResultFailure_sComment'; 427 428 kasAllowNullAttributes = ['tsEffective', 'tsExpire', 'uidAuthor', 'sComment' ]; 429 430 def __init__(self): 431 ModelDataBase.__init__(self) 432 self.idTestResult = None; 433 self.tsEffective = None; 434 self.tsExpire = None; 435 self.uidAuthor = None; 436 self.idFailureReason = None; 437 self.sComment = None; 438 439 def initFromDbRow(self, aoRow): 440 """ 441 Reinitialize from a SELECT * FROM TestResultFailures. 442 Return self. Raises exception if no row. 443 """ 444 if aoRow is None: 445 raise TMExceptionBase('Test result file record not found.') 446 447 self.idTestResult = aoRow[0]; 448 self.tsEffective = aoRow[1]; 449 self.tsExpire = aoRow[2]; 450 self.uidAuthor = aoRow[3]; 451 self.idFailureReason = aoRow[4]; 452 self.sComment = aoRow[5]; 453 return self; 454 455 def initFromDbWithId(self, oDb, idTestResult, tsNow = None, sPeriodBack = None): 456 """ 457 Initialize the object from the database. 458 """ 459 oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb, 460 'SELECT *\n' 461 'FROM TestResultFailures\n' 462 'WHERE idTestResult = %s\n' 463 , ( idTestResult,), tsNow, sPeriodBack)); 464 aoRow = oDb.fetchOne() 465 if aoRow is None: 466 raise TMExceptionBase('idTestResult=%s not found (tsNow=%s, sPeriodBack=%s)' % (idTestResult, tsNow, sPeriodBack)); 467 return self.initFromDbRow(aoRow); 468 469 470 class TestResultFailureDataEx(TestResultFailureData): 471 """ 472 Extends TestResultFailureData by resolving reasons and user. 473 """ 474 475 def __init__(self): 476 TestResultFailureData.__init__(self); 477 self.oFailureReason = None; 478 self.oAuthor = None; 479 480 def initFromDbRowEx(self, aoRow, oFailureReasonLogic, oUserAccountLogic): 481 """ 482 Reinitialize from a query like this: 483 SELECT TestResultFiles.*, 484 StrTabFile.sValue AS sFile, 485 StrTabDesc.sValue AS sDescription 486 StrTabKind.sValue AS sKind, 487 StrTabMime.sValue AS sMime, 488 FROM ... 489 490 Return self. Raises exception if no row. 491 """ 492 self.initFromDbRow(aoRow); 493 self.oFailureReason = oFailureReasonLogic.cachedLookup(self.idFailureReason); 494 self.oAuthor = oUserAccountLogic.cachedLookup(self.uidAuthor); 495 return self; 408 496 409 497 … … 453 541 self.iRevisionTestSuite = None; 454 542 455 def initFromDbRow(self, aoRow): 543 self.oFailureReason = None; 544 self.oFailureReasonAssigner = None; 545 self.tsFailureReasonAssigned = None; 546 self.sFailureReasonComment = None; 547 548 def initFromDbRowEx(self, aoRow, oFailureReasonLogic, oUserAccountLogic): 456 549 """ 457 550 Reinitialize from a database query. … … 497 590 self.iRevisionTestSuite = aoRow[29]; 498 591 592 self.oFailureReason = None; 593 if aoRow[30] is not None: 594 self.oFailureReason = oFailureReasonLogic.cachedLookup(aoRow[30]); 595 self.oFailureReasonAssigner = None; 596 if aoRow[31] is not None: 597 self.oFailureReasonAssigner = oUserAccountLogic.cachedLookup(aoRow[31]); 598 self.tsFailureReasonAssigned = aoRow[32]; 599 self.sFailureReasonComment = aoRow[33]; 600 499 601 return self 500 602 … … 503 605 """Hanging offence committed by test case.""" 504 606 pass; 607 505 608 506 609 class TestResultLogic(ModelLogicBase): # pylint: disable=R0903 … … 534 637 ksResultsSortByTestBoxCpuFeatures = 'ResultsSortByTestBoxCpuFeatures'; 535 638 ksResultsSortByTestCaseName = 'ResultsSortByTestCaseName'; 639 ksResultsSortByFailureReason = 'ResultsSortByFailureReason'; 536 640 kasResultsSortBy = { 537 641 ksResultsSortByRunningAndStart, … … 547 651 ksResultsSortByTestBoxCpuFeatures, 548 652 ksResultsSortByTestCaseName, 653 ksResultsSortByFailureReason, 549 654 }; 550 655 ## Used by the WUI for generating the drop down. … … 562 667 ( ksResultsSortByTestBoxCpuFeatures, 'CPU Features' ), 563 668 ( ksResultsSortByTestCaseName, 'Test Case Name' ), 669 ( ksResultsSortByFailureReason, 'Failure Reason' ), 564 670 ); 565 671 ## @} … … 629 735 ' TestCases.sName', 630 736 '' ), 737 ksResultsSortByFailureReason: ( 738 '', '', 739 'sSortByFailureReason ASC', 740 ', FailureReasons.sShort AS sSortByFailureReason' ), 631 741 }; 632 742 … … 668 778 669 779 780 def __init__(self, oDb): 781 ModelLogicBase.__init__(self, oDb) 782 self.oFailureReasonLogic = None; 783 self.oUserAccountLogic = None; 784 670 785 def _getTimePeriodQueryPart(self, tsNow, sInterval): 671 786 """ … … 752 867 ' TestSuiteBits.idBuild AS idBuildTestSuite,\n' \ 753 868 ' TestSuiteBits.iRevision AS iRevisionTestSuite,\n' \ 869 ' TestResultFailures.idFailureReason as idFailureReason,\n' \ 870 ' TestResultFailures.uidAuthor as uidFailureReasonAssigner,\n' \ 871 ' TestResultFailures.tsEffective as tsFailureReasonAssigned,\n' \ 872 ' TestResultFailures.sComment as sFailureReasonComment,\n' \ 754 873 ' (TestSets.tsDone IS NULL) SortRunningFirst' + sSortingColumns + '\n' \ 755 874 'FROM BuildCategories,\n' \ 756 875 ' Builds,\n' \ 757 876 ' TestBoxes,\n' \ 758 ' TestResults,\n' \ 877 ' TestResults LEFT OUTER JOIN TestResultFailures\n' \ 878 ' ON TestResults.idTestResult = TestResultFailures.idTestResult\n' \ 879 ' AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP'; 880 if sSortingOrderBy.find('FailureReason') >= 0: 881 sQuery += '\n' \ 882 ' LEFT OUTER JOIN FailureReasons\n' \ 883 ' ON TestResultFailures.idFailureReason = FailureReasons.idFailureReason\n' \ 884 ' AND FailureReasons.tsExpire = \'infinity\'::TIMESTAMP'; 885 sQuery += ',\n'\ 759 886 ' TestCases,\n' \ 760 887 ' TestCaseArgs,\n' \ … … 780 907 sQuery += sSortingWhere.replace(' AND ', ' AND '); 781 908 sQuery += ' ORDER BY '; 782 if sSortingOrderBy is not None :909 if sSortingOrderBy is not None and sSortingOrderBy.find('FailureReason') < 0: 783 910 sQuery += sSortingOrderBy + ',\n '; 784 911 sQuery += '(TestSets.tsDone IS NULL) DESC, TestSets.idTestSet DESC\n' \ … … 807 934 self._oDb.execute(sQuery); 808 935 936 if self.oFailureReasonLogic is None: 937 self.oFailureReasonLogic = FailureReasonLogic(self._oDb); 938 if self.oUserAccountLogic is None: 939 self.oUserAccountLogic = UserAccountLogic(self._oDb); 940 809 941 aoRows = []; 810 942 for aoRow in self._oDb.fetchAll(): 811 aoRows.append(TestResultListingData().initFromDbRow (aoRow))943 aoRows.append(TestResultListingData().initFromDbRowEx(aoRow, self.oFailureReasonLogic, self.oUserAccountLogic)); 812 944 813 945 return aoRows … … 1014 1146 ' EXISTS ( SELECT idTestResultFile\n' 1015 1147 ' FROM TestResultFiles\n' 1016 ' WHERE TestResultFiles.idTestResult = TestResults.idTestResult ) AS fHasFiles\n' 1148 ' WHERE TestResultFiles.idTestResult = TestResults.idTestResult ) AS fHasFiles,\n' 1149 ' EXISTS ( SELECT idTestResult\n' 1150 ' FROM TestResultFailures\n' 1151 ' WHERE TestResultFailures.idTestResult = TestResults.idTestResult ) AS fHasReasons\n' 1017 1152 'FROM TestResults, TestResultStrTab\n' 1018 1153 'WHERE TestResults.idTestSet = %s\n' … … 1038 1173 raise self._oDb.integrityException('The root TestResult (#%s) has a parent (#%s)!' 1039 1174 % (oRoot.idTestResult, oRoot.idTestResultParent)); 1040 self._fetchResultTreeNodeExtras(oRoot, aoRow[- 3], aoRow[-2], aoRow[-1]);1175 self._fetchResultTreeNodeExtras(oRoot, aoRow[-4], aoRow[-3], aoRow[-2], aoRow[-1]); 1041 1176 1042 1177 # The chilren (if any). … … 1046 1181 aoRow = aaoRows[iRow]; 1047 1182 oCur = TestResultDataEx().initFromDbRow(aoRow); 1048 self._fetchResultTreeNodeExtras(oCur, aoRow[- 3], aoRow[-2], aoRow[-1]);1183 self._fetchResultTreeNodeExtras(oCur, aoRow[-4], aoRow[-3], aoRow[-2], aoRow[-1]); 1049 1184 1050 1185 # Figure out and vet the parent. … … 1065 1200 return (oRoot, dLookup); 1066 1201 1067 def _fetchResultTreeNodeExtras(self, oCurNode, fHasValues, fHasMsgs, fHasFiles ):1202 def _fetchResultTreeNodeExtras(self, oCurNode, fHasValues, fHasMsgs, fHasFiles, fHasReasons): 1068 1203 """ 1069 1204 fetchResultTree worker that fetches values, message and files for the 1070 1205 specified node. 1071 1206 """ 1072 assert(oCurNode.aoValues == []); 1073 assert(oCurNode.aoMsgs == []); 1074 assert(oCurNode.aoFiles == []); 1207 assert(oCurNode.aoValues == []); 1208 assert(oCurNode.aoMsgs == []); 1209 assert(oCurNode.aoFiles == []); 1210 assert(oCurNode.oReason is None); 1075 1211 1076 1212 if fHasValues: … … 1117 1253 oCurNode.aoFiles.append(TestResultFileDataEx().initFromDbRow(aoRow)); 1118 1254 1255 if fHasReasons or True: 1256 if self.oFailureReasonLogic is None: 1257 self.oFailureReasonLogic = FailureReasonLogic(self._oDb); 1258 if self.oUserAccountLogic is None: 1259 self.oUserAccountLogic = UserAccountLogic(self._oDb); 1260 self._oDb.execute('SELECT *\n' 1261 'FROM TestResultFailures\n' 1262 'WHERE idTestResult = %s\n' 1263 ' AND tsExpire = \'infinity\'::TIMESTAMP\n' 1264 , ( oCurNode.idTestResult, )); 1265 if self._oDb.getRowCount() > 0: 1266 oCurNode.oReason = TestResultFailureDataEx().initFromDbRowEx(self._oDb.fetchOne(), self.oFailureReasonLogic, 1267 self.oUserAccountLogic); 1268 1119 1269 return True; 1120 1270 … … 1184 1334 """Returns a string rep of the stack.""" 1185 1335 sRet = ''; 1186 for i in range(len(aoStack)):1336 for i, _ in enumerate(aoStack): 1187 1337 sRet += 'aoStack[%d]=%s\n' % (i, aoStack[i]); 1188 1338 return sRet; … … 1202 1352 aoStack.append(TestResultData().initFromDbRow(aoRow)); 1203 1353 1204 for i in range(len(aoStack)):1354 for i, _ in enumerate(aoStack): 1205 1355 assert aoStack[i].iNestingDepth == len(aoStack) - i - 1, self._stringifyStack(aoStack); 1206 1356 … … 1473 1623 if sAttr in dAttribs: 1474 1624 try: 1475 _ = long(dAttribs[sAttr]); 1625 _ = long(dAttribs[sAttr]); # pylint: disable=R0204 1476 1626 except: 1477 1627 return 'Element %s has an invalid %s attribute value: %s.' % (sName, sAttr, dAttribs[sAttr],); … … 1761 1911 1762 1912 1913 1914 class TestResultFailureLogic(ModelLogicBase): # pylint: disable=R0903 1915 """ 1916 Test result failure reason logic. 1917 """ 1918 1919 def __init__(self, oDb): 1920 ModelLogicBase.__init__(self, oDb) 1921 1922 def getById(self, idTestResult): 1923 """Get Test result failure reason data by idTestResult""" 1924 1925 self._oDb.execute('SELECT *\n' 1926 'FROM TestResultFailures\n' 1927 'WHERE tsExpire = \'infinity\'::timestamp\n' 1928 ' AND idTestResult = %s;', (idTestResult,)) 1929 aRows = self._oDb.fetchAll() 1930 if len(aRows) not in (0, 1): 1931 raise self._oDb.integrityException( 1932 'Found more than one failure reasons with the same credentials. Database structure is corrupted.') 1933 try: 1934 return TestResultFailureData().initFromDbRow(aRows[0]) 1935 except IndexError: 1936 return None 1937 1938 def addEntry(self, oData, uidAuthor, fCommit = False): 1939 """ 1940 Add a test result failure reason record. 1941 """ 1942 1943 # Check if it exist first (we're adding, not editing, collisions not allowed). 1944 oOldData = self.getById(oData.idTestResult); 1945 if oOldData is not None: 1946 raise TMExceptionBase('TestResult %d already have a failure reason associated with it:' 1947 '%s\n' 1948 'Perhaps someone else beat you to it? Or did you try resubmit?' 1949 % (oData.idTestResult, oOldData)); 1950 1951 # 1952 # Add record. 1953 # 1954 self._readdEntry(uidAuthor, oData); 1955 self._oDb.maybeCommit(fCommit); 1956 return True; 1957 1958 def editEntry(self, oData, uidAuthor, fCommit = False): 1959 """ 1960 Modifies a test result failure reason. 1961 """ 1962 1963 # 1964 # Validate inputs and read in the old(/current) data. 1965 # 1966 assert isinstance(oData, TestResultFailureData); 1967 dErrors = oData.validateAndConvert(self._oDb); 1968 if len(dErrors) > 0: 1969 raise TMExceptionBase('editEntry invalid input: %s' % (dErrors,)); 1970 1971 oOldData = self.getById(oData.idTestResult) 1972 1973 # 1974 # Update the data that needs updating. 1975 # 1976 if not oData.isEqualEx(oOldData, [ 'tsEffective', 'tsExpire', 'uidAuthor', ]): 1977 self._historizeEntry(oData.idTestResult); 1978 self._readdEntry(uidAuthor, oData); 1979 self._oDb.maybeCommit(fCommit); 1980 return True; 1981 1982 1983 def removeEntry(self, uidAuthor, idTestResult, fCascade = False, fCommit = False): 1984 """ 1985 Deletes a test result failure reason. 1986 """ 1987 _ = fCascade; # Not applicable. 1988 1989 oData = self.getById(idTestResult) 1990 (tsCur, tsCurMinusOne) = self._oDb.getCurrentTimestamps(); 1991 if oData.tsEffective != tsCur and oData.tsEffective != tsCurMinusOne: 1992 self._historizeEntry(idTestResult, tsCurMinusOne); 1993 self._readdEntry(uidAuthor, oData, tsCurMinusOne); 1994 self._historizeEntry(idTestResult); 1995 self._oDb.execute('UPDATE TestResultFaillures\n' 1996 'SET tsExpire = CURRENT_TIMESTAMP\n' 1997 'WHERE idTestResult = %s\n' 1998 ' AND tsExpire = \'infinity\'::TIMESTAMP\n' 1999 , (idTestResult,)); 2000 self._oDb.maybeCommit(fCommit); 2001 return True; 2002 2003 # 2004 # Helpers. 2005 # 2006 2007 def _readdEntry(self, uidAuthor, oData, tsEffective = None): 2008 """ 2009 Re-adds the TestResultFailure entry. Used by addEntry, editEntry and removeEntry. 2010 """ 2011 if tsEffective is None: 2012 tsEffective = self._oDb.getCurrentTimestamp(); 2013 self._oDb.execute('INSERT INTO TestResultFailures (\n' 2014 ' uidAuthor,\n' 2015 ' tsEffective,\n' 2016 ' idTestResult,\n' 2017 ' idFailureReason,\n' 2018 ' sComment)\n' 2019 'VALUES (%s, %s, %s, %s, %s)\n' 2020 , ( uidAuthor, 2021 tsEffective, 2022 oData.idTestResult, 2023 oData.idFailureReason, 2024 oData.sComment,) ); 2025 return True; 2026 2027 2028 def _historizeEntry(self, idTestResult, tsExpire = None): 2029 """ Historizes the current entry. """ 2030 if tsExpire is None: 2031 tsExpire = self._oDb.getCurrentTimestamp(); 2032 self._oDb.execute('UPDATE TestResultFailures\n' 2033 'SET tsExpire = %s\n' 2034 'WHERE idTestResult = %s\n' 2035 ' AND tsExpire = \'infinity\'::TIMESTAMP\n' 2036 , (tsExpire, idTestResult,)); 2037 return True; 2038 2039 2040 1763 2041 # 1764 2042 # Unit testing. -
trunk/src/VBox/ValidationKit/testmanager/core/useraccount.py
r56295 r61217 116 116 class UserAccountLogic(ModelLogicBase): 117 117 """ 118 SystemLog logic. 119 """ 118 User account logic (for the Users table). 119 """ 120 121 def __init__(self, oDb): 122 ModelLogicBase.__init__(self, oDb) 123 self.ahCache = None; 120 124 121 125 def fetchForListing(self, iStart, cMaxRows, tsNow): … … 221 225 return UserAccountData().initFromDbRow(self._oDb.fetchOne()); 222 226 227 def cachedLookup(self, uid): 228 """ 229 Looks up the current UserAccountData object for uid via an object cache. 230 231 Returns a shared UserAccountData object. None if not found. 232 Raises exception on DB error. 233 """ 234 if self.ahCache is None: 235 self.ahCache = self._oDb.getCache('UserAccount'); 236 237 oUser = self.ahCache.get(uid, None); 238 if oUser is None: 239 self._oDb.execute('SELECT *\n' 240 'FROM Users\n' 241 'WHERE uid = %s\n' 242 ' AND tsExpire = \'infinity\'::TIMESTAMP\n' 243 , (uid, )); 244 if self._oDb.getRowCount() == 0: 245 # Maybe it was deleted, try get the last entry. 246 self._oDb.execute('SELECT *\n' 247 'FROM Users\n' 248 'WHERE uid = %s\n' 249 'ORDER BY tsExpire\n' 250 'LIMIT 1\n' 251 , (uid, )); 252 elif self._oDb.getRowCount() > 1: 253 raise self._oDb.integrityException('%s infinity rows for %s' % (self._oDb.getRowCount(), uid)); 254 255 if self._oDb.getRowCount() == 1: 256 oUser = UserAccountData().initFromDbRow(self._oDb.fetchOne()); 257 self.ahCache[uid] = oUser; 258 return oUser; 259 223 260 def resolveChangeLogAuthors(self, aoEntries): 224 261 """ … … 229 266 Raises exception on DB error. 230 267 """ 231 ahCache = dict();232 268 for oEntry in aoEntries: 233 oEntry.sAuthor = ahCache.get(oEntry.uidAuthor, None); 234 if oEntry.sAuthor is None and oEntry.uidAuthor is not None: 235 try: 236 oUser = UserAccountData().initFromDbWithId(self._oDb, oEntry.uidAuthor, oEntry.tsEffective); 237 except: 238 pass; 239 else: 240 ahCache[oEntry.uidAuthor] = oUser.sUsername; 241 oEntry.sAuthor = oUser.sUsername; 269 oUser = self.cachedLookup(oEntry.uidAuthor) 270 if oUser is not None: 271 oEntry.sAuthor = oUser.sUsername; 242 272 return aoEntries; 243 273 -
trunk/src/VBox/ValidationKit/testmanager/db/Makefile.kmk
r61181 r61217 28 28 # Need proper shell on windows. 29 29 DEPTH = ../../../../.. 30 include $(KBUILD_PATH)/header.kmk30 #include $(KBUILD_PATH)/header.kmk 31 31 32 32 -
trunk/src/VBox/ValidationKit/testmanager/db/partial-db-dump.py
r61184 r61217 262 262 oDb.commit(); 263 263 264 # Correct sequences. 265 atSequences = [ 266 ( 'UserIdSeq', 'Users', 'uid' ), 267 ( 'GlobalResourceIdSeq', 'GlobalResources', 'idGlobalRsrc' ), 268 ( 'BuildSourceIdSeq', 'BuildSources', 'idBuildSrc' ), 269 ( 'TestCaseIdSeq', 'TestCases', 'idTestCase' ), 270 ( 'TestCaseGenIdSeq', 'TestCases', 'idGenTestCase' ), 271 ( 'TestCaseArgsIdSeq', 'TestCaseArgs', 'idTestCaseArgs' ), 272 ( 'TestCaseArgsGenIdSeq', 'TestCaseArgs', 'idGenTestCaseArgs' ), 273 ( 'TestGroupIdSeq', 'TestGroups', 'idTestGroup' ), 274 ( 'SchedGroupIdSeq', 'SchedGroups', 'idSchedGroup' ), 275 ( 'TestBoxIdSeq', 'TestBoxes', 'idTestBox' ), 276 ( 'TestBoxGenIdSeq', 'TestBoxes', 'idGenTestBox' ), 277 ( 'FailureCategoryIdSeq', 'FailureCategories', 'idFailureCategory' ), 278 ( 'FailureReasonIdSeq', 'FailureReasons', 'idFailureReason' ), 279 ( 'BuildBlacklistIdSeq', 'BuildBlacklist', 'idBlacklisting' ), 280 ( 'BuildCategoryIdSeq', 'BuildCategories', 'idBuildCategory' ), 281 ( 'BuildIdSeq', 'Builds', 'idBuild' ), 282 ( 'TestResultStrTabIdSeq', 'TestResultStrTab', 'idStr' ), 283 ( 'TestResultIdSeq', 'TestResults', 'idTestResult' ), 284 ( 'TestResultValueIdSeq', 'TestResultValues', 'idTestResultValue' ), 285 ( 'TestResultFileId', 'TestResultFiles', 'idTestResultFile' ), 286 ( 'TestResultMsgIdSeq', 'TestResultMsgs', 'idTestResultMsg' ), 287 ( 'TestSetIdSeq', 'TestSets', 'idTestSet' ), 288 ( 'SchedQueueItemIdSeq', 'SchedQueues', 'idItem' ), 289 ]; 290 for (sSeq, sTab, sCol) in atSequences: 291 oDb.execute('SELECT MAX(%s) FROM %s' % (sCol, sTab,)); 292 idMax = oDb.fetchOne()[0]; 293 print '%s: idMax=%s' % (sSeq, idMax); 294 if idMax is not None: 295 oDb.execute('SELECT setval(\'%s\', %s)' % (sSeq, idMax)); 296 264 297 # Last step. 265 298 print 'Analyzing...' -
trunk/src/VBox/ValidationKit/testmanager/htdocs/js/common.js
r56295 r61217 27 27 28 28 29 /********************************************************************************************************************************* 30 * Global Variables * 31 *********************************************************************************************************************************/ 32 /** Same as WuiDispatcherBase.ksParamRedirectTo. */ 33 var g_ksParamRedirectTo = 'RedirectTo'; 34 29 35 30 36 /** … … 161 167 { 162 168 return getUnscaledElementWidth(document.getElementById(sElementId)); 169 } 170 171 /** 172 * Gets the part of the URL needed for a RedirectTo parameter. 173 * 174 * @returns URL string. 175 */ 176 function getCurrentBrowerUrlPartForRedirectTo() 177 { 178 var sWhere = window.location.href; 179 var offTmp; 180 var offPathKeep; 181 182 /* Find the end of that URL 'path' component. */ 183 var offPathEnd = sWhere.indexOf('?'); 184 if (offPathEnd < 0) 185 offPathEnd = sWhere.indexOf('#'); 186 if (offPathEnd < 0) 187 offPathEnd = sWhere.length; 188 189 /* Go backwards from the end of the and find the start of the last component. */ 190 offPathKeep = sWhere.lastIndexOf("/", offPathEnd); 191 offTmp = sWhere.lastIndexOf(":", offPathEnd); 192 if (offPathKeep < offTmp) 193 offPathKeep = offTmp; 194 offTmp = sWhere.lastIndexOf("\\", offPathEnd); 195 if (offPathKeep < offTmp) 196 offPathKeep = offTmp; 197 198 return sWhere.substring(offPathKeep + 1); 163 199 } 164 200 … … 266 302 267 303 } 304 305 /** 306 * Adds the RedirecTo field with the current URL to the form. 307 * 308 * This is a 'onsubmit' action. 309 * 310 * @returns Returns success indicator (true/false). 311 * @param oForm The form being submitted. 312 */ 313 function addRedirectToInputFieldWithCurrentUrl(oForm) 314 { 315 /* Constant used here is duplicated in WuiDispatcherBase.ksParamRedirectTo */ 316 return addHiddenInputFieldToForm(oForm, 'RedirectTo', getCurrentBrowerUrlPartForRedirectTo(), null); 317 } 318 319 /** 320 * Adds the RedirecTo parameter to the href of the given anchor. 321 * 322 * This is a 'onclick' action. 323 * 324 * @returns Returns success indicator (true/false). 325 * @param oAnchor The anchor element being clicked on. 326 */ 327 function addRedirectToAnchorHref(oAnchor) 328 { 329 var sRedirectToParam = g_ksParamRedirectTo + '=' + encodeURIComponent(getCurrentBrowerUrlPartForRedirectTo()); 330 var sHref = oAnchor.href; 331 if (sHref.indexOf(sRedirectToParam) < 0) 332 { 333 var sHash; 334 var offHash = sHref.indexOf('#'); 335 if (offHash >= 0) 336 sHash = sHref.substring(offHash); 337 else 338 { 339 sHash = ''; 340 offHash = sHref.length; 341 } 342 sHref = sHref.substring(0, offHash) 343 if (sHref.indexOf('?') >= 0) 344 sHref += '&'; 345 else 346 sHref += '?'; 347 sHref += sRedirectToParam; 348 sHref += sHash; 349 oAnchor.href = sHref; 350 } 351 return true; 352 } 353 268 354 269 355 -
trunk/src/VBox/ValidationKit/testmanager/webui/wuiadmin.py
r56295 r61217 138 138 139 139 ksActionFailureReasonList = 'FailureReasonList' 140 ksActionFailureReasonDetails = 'FailureReasonDetails' 140 141 ksActionFailureReasonShowAdd = 'FailureReasonShowAdd' 141 142 ksActionFailureReasonShowEdit = 'FailureReasonShowEdit' … … 365 366 WuiAdminFailureReasonList) 366 367 367 d[self.ksActionFailureReason ShowAdd] = lambda: self._actionGenericFormAdd(368 FailureReasonData,369 WuiAdminFailureReason)370 368 d[self.ksActionFailureReasonDetails] = lambda: self._actionGenericFormDetails(FailureReasonData, 369 FailureReasonLogic, 370 WuiAdminFailureReason, 371 'idFailureReason'); 371 372 d[self.ksActionFailureReasonShowEdit] = lambda: self._actionGenericFormEditL( 372 373 FailureReasonLogic, -
trunk/src/VBox/ValidationKit/testmanager/webui/wuibase.py
r56295 r61217 74 74 ## The name of the effective date (timestamp) parameter. 75 75 ksParamEffectiveDate = 'EffectiveDate'; 76 77 ## The name of the redirect-to (test manager relative url) parameter. 78 ksParamRedirectTo = 'RedirectTo'; 76 79 77 80 ## The name of the list-action parameter (WuiListContentWithActionBase). … … 546 549 return str(oDate); 547 550 551 def getRedirectToParameter(self, sDefault = None): 552 """ 553 Gets the special redirect to parameter if it exists, will Return default 554 if not, with None being a valid default. 555 556 Makes sure the it doesn't got offsite. 557 Raises exception if invalid. 558 """ 559 if sDefault is not None or self.ksParamRedirectTo in self._dParams: 560 sValue = self.getStringParam(self.ksParamRedirectTo, sDefault = sDefault); 561 cch = sValue.find("?"); 562 if cch < 0: 563 cch = sValue.find("#"); 564 if cch < 0: 565 cch = len(sValue); 566 for ch in (':', '/', '\\', '..'): 567 if sValue.find(ch, 0, cch) >= 0: 568 raise WuiException('Invalid character (%c) in redirect-to url: %s' % (ch, sValue,)); 569 else: 570 sValue = None; 571 return sValue; 572 548 573 549 574 def _checkForUnknownParameters(self): … … 701 726 return True; 702 727 703 def _actionGenericFormAdd(self, oDataType, oFormType ):728 def _actionGenericFormAdd(self, oDataType, oFormType, sRedirectTo = None): 704 729 """ 705 730 Generic add something form display request handler. … … 709 734 """ 710 735 oData = oDataType().initFromParams(oDisp = self, fStrict = False); 736 sRedirectTo = self.getRedirectToParameter(sRedirectTo); 711 737 self._checkForUnknownParameters(); 712 738 713 739 oForm = oFormType(oData, oFormType.ksMode_Add, oDisp = self); 740 oForm.setRedirectTo(sRedirectTo); 714 741 (self._sPageTitle, self._sPageBody) = oForm.showForm(); 715 742 return True … … 757 784 758 785 759 def _actionGenericFormEdit(self, oDataType, oFormType, sIdParamName ):786 def _actionGenericFormEdit(self, oDataType, oFormType, sIdParamName, sRedirectTo = None): 760 787 """ 761 788 Generic edit something form display request handler. … … 766 793 """ 767 794 795 tsNow = self.getEffectiveDateParam(); 768 796 idObject = self.getIntParam(sIdParamName, 0, 0x7ffffffe); 797 sRedirectTo = self.getRedirectToParameter(sRedirectTo); 769 798 self._checkForUnknownParameters(); 770 oData = oDataType().initFromDbWithId(self._oDb, idObject );799 oData = oDataType().initFromDbWithId(self._oDb, idObject, tsNow = tsNow); 771 800 772 801 oContent = oFormType(oData, oFormType.ksMode_Edit, oDisp = self); 802 oContent.setRedirectTo(sRedirectTo); 773 803 (self._sPageTitle, self._sPageBody) = oContent.showForm(); 774 804 return True … … 850 880 # 851 881 oData = oDataType().initFromParams(oDisp = self, fStrict = fStrict); 882 sRedirectTo = self.getRedirectToParameter(sRedirectTo); 852 883 self._checkForUnknownParameters(); 853 884 self._assertPostRequest(); … … 864 895 self._oDb.rollback(); 865 896 oForm = oFormType(oData, sMode, oDisp = self); 897 oForm.setRedirectTo(sRedirectTo); 866 898 sErrorMsg = str(oXcpt) if not config.g_kfDebugDbXcpt else '\n'.join(utils.getXcptInfo(4)); 867 899 (self._sPageTitle, self._sPageBody) = oForm.showForm(sErrorMsg = sErrorMsg); … … 961 993 self._sAction = self.ksActionDefault; 962 994 963 if self._sAction not in self._dDispatch:995 if isinstance(self._sAction, list) or self._sAction not in self._dDispatch: 964 996 raise WuiException('Unknown action "%s" requested' % (self._sAction,)); 965 997 -
trunk/src/VBox/ValidationKit/testmanager/webui/wuicontentbase.py
r56807 r61217 190 190 191 191 ## The text/symbol for a very short edit link. 192 ksShortEditLink = u'\u270D' 192 ksShortEditLink = u'\u270D' 193 ## HTML hex entity string for ksShortDetailsLink. 194 ksShortEditLinkHtml = '✍' 193 195 ## The text/symbol for a very short details link. 194 ksShortDetailsLink = u'\u2318' 196 ksShortDetailsLink = u'\u2318' 197 ## HTML hex entity string for ksShortDetailsLink. 198 ksShortDetailsLinkHtml = '⌘' 195 199 196 200 … … 327 331 if sSubmitAction is None and sMode != self.ksMode_Show: 328 332 self._sSubmitAction = getattr(oDisp, self._sActionBase + self.kdSubmitActionMappings[sMode]); 333 self._sRedirectTo = None; 329 334 330 335 … … 469 474 '</div>\n'; 470 475 return sNavigation; 476 477 def setRedirectTo(self, sRedirectTo): 478 """ 479 For setting the hidden redirect-to field. 480 """ 481 self._sRedirectTo = sRedirectTo; 482 return True; 471 483 472 484 def showChangeLog(self, aoEntries, fMoreEntries, iPageNo, cEntriesPerPage, tsNow, fShowNavigation = True): … … 521 533 try: 522 534 self._populateForm(oForm, self._oData); 535 if self._sRedirectTo is not None: 536 oForm.addTextHidden(self._oDisp.ksParamRedirectTo, self._sRedirectTo); 523 537 except WuiException, oXcpt: 524 538 sContent = unicode(oXcpt) … … 558 572 dParams[WuiDispatcherBase.ksParamEffectiveDate] = self._oData.tsEffective; 559 573 dParams[getattr(self._oData, 'ksParam_' + self._oData.ksIdAttr)] = getattr(self._oData, self._oData.ksIdAttr); 560 dParams[WuiDispatcherBase.ksParamAction] = getattr(self._oDisp, self._sActionBase + ' Edit');574 dParams[WuiDispatcherBase.ksParamAction] = getattr(self._oDisp, self._sActionBase + 'Details'); 561 575 aoActions.append(WuiTmLink('Details', '', dParams)); 562 576 … … 629 643 assert len(aoValues) == len(self._asColumnHeaders), '%s vs %s' % (len(aoValues), len(self._asColumnHeaders)); 630 644 631 for i in range(len(aoValues)):645 for i, _ in enumerate(aoValues): 632 646 if i < len(self._asColumnAttribs) and len(self._asColumnAttribs[i]) > 0: 633 647 sRow += u' <td ' + self._asColumnAttribs[i] + '>'; -
trunk/src/VBox/ValidationKit/testmanager/webui/wuihlpform.py
r56295 r61217 48 48 ksItemsList = 'ksItemsList' 49 49 50 def __init__(self, sId, sAction, dErrors = None, fReadOnly = False): 50 ksOnSubmit_AddReturnToFieldWithCurrentUrl = '+AddReturnToFieldWithCurrentUrl+'; 51 52 def __init__(self, sId, sAction, dErrors = None, fReadOnly = False, sOnSubmit = None): 51 53 self._fFinalized = False; 52 54 self._fReadOnly = fReadOnly; 53 55 self._dErrors = dErrors if dErrors is not None else dict(); 56 57 if sOnSubmit == self.ksOnSubmit_AddReturnToFieldWithCurrentUrl: 58 sOnSubmit = 'return addRedirectToInputFieldWithCurrentUrl(this)'; 59 if sOnSubmit is None: sOnSubmit = u''; 60 else: sOnSubmit = u' onsubmit=\"%s\"' % (escapeAttr(sOnSubmit),); 61 54 62 self._sBody = u'\n' \ 55 63 u'<div id="%s" class="tmform">\n' \ 56 u' <form action="%s" method="post" >\n' \64 u' <form action="%s" method="post"%s>\n' \ 57 65 u' <ul>\n' \ 58 % (sId, sAction );66 % (sId, sAction, sOnSubmit); 59 67 60 68 def _add(self, sText): … … 70 78 if sText.find('<br>') >= 0: 71 79 asParts = sText.split('<br>'); 72 for i in range(len(asParts)):80 for i, _ in enumerate(asParts): 73 81 asParts[i] = escapeElem(asParts[i].strip()); 74 82 sText = '<br>\n'.join(asParts); … … 114 122 ' </li>\n' 115 123 % ( escapeAttr(sName), escapeAttr(sName), sExtraAttribs, escapeElem(str(sValue)) )); 124 # 125 # Non-input stuff. 126 # 127 def addNonText(self, sValue, sLabel, sPostHtml = ''): 128 """Adds a read-only text input.""" 129 self._addLabel('non-text', sLabel, 'string'); 130 if sValue is None: sValue = ''; 131 return self._add(' <p>%s</p>%s\n' 132 ' </div></div>\n' 133 ' </li>\n' 134 % (escapeElem(str(sValue)), sPostHtml )); 135 116 136 117 137 # … … 124 144 if sSubClass not in ('int', 'long', 'string', 'uuid', 'timestamp', 'wide'): raise Exception(sSubClass); 125 145 self._addLabel(sName, sLabel, sSubClass); 146 if sValue is None: sValue = ''; 126 147 return self._add(' <input name="%s" id="%s" type="text"%s value="%s">%s\n' 127 148 ' </div></div>\n' … … 133 154 if sSubClass not in ('int', 'long', 'string', 'uuid', 'timestamp', 'wide'): raise Exception(sSubClass); 134 155 self._addLabel(sName, sLabel, sSubClass); 156 if sValue is None: sValue = ''; 135 157 return self._add(' <input name="%s" id="%s" type="text" readonly%s value="%s" class="tmform-input-readonly">%s\n' 136 158 ' </div></div>\n' … … 153 175 if sSubClass not in ('int', 'long', 'string', 'uuid', 'timestamp'): raise Exception(sSubClass) 154 176 self._addLabel(sName, sLabel, sSubClass) 177 if sValue is None: sValue = ''; 155 178 sNewValue = str(sValue) if not isinstance(sValue, list) else '\n'.join(sValue) 156 179 return self._add(' <textarea name="%s" id="%s" %s>%s</textarea>\n' … … 163 186 if sSubClass not in ('int', 'long', 'string', 'uuid', 'timestamp'): raise Exception(sSubClass) 164 187 self._addLabel(sName, sLabel, sSubClass) 188 if sValue is None: sValue = ''; 165 189 sNewValue = str(sValue) if not isinstance(sValue, list) else '\n'.join(sValue) 166 190 return self._add(' <textarea name="%s" id="%s" readonly %s>%s</textarea>\n' … … 213 237 self._add(' <select name="%s" id="%s" class="tmform-combobox"%s>\n' 214 238 % (escapeAttr(sName), escapeAttr(sName), sExtraAttribs)); 239 sSelected = str(sSelected); 215 240 for iValue, sText, _ in aoOptions: 216 241 sValue = str(iValue); 217 242 self._add(' <option value="%s"%s>%s</option>\n' 218 % (escapeAttr(sValue), ' selected' if sValue == s tr(sSelected)else '',243 % (escapeAttr(sValue), ' selected' if sValue == sSelected else '', 219 244 escapeElem(sText))); 220 245 return self._add(' </select>\n' … … 229 254 self._add(' <select name="%s" id="%s" disabled class="tmform-combobox"%s>\n' 230 255 % (escapeAttr(sName), escapeAttr(sName), sExtraAttribs)); 256 sSelected = str(sSelected); 231 257 for iValue, sText, _ in aoOptions: 232 258 sValue = str(iValue); 233 259 self._add(' <option value="%s"%s>%s</option>\n' 234 % (escapeAttr(sValue), ' selected' if sValue == s tr(sSelected)else '',260 % (escapeAttr(sValue), ' selected' if sValue == sSelected else '', 235 261 escapeElem(sText))); 236 262 return self._add(' </select>\n' … … 526 552 dSubErrors = self._dErrors[sName]; 527 553 528 for iVar in range(len(aoVariations)):554 for iVar, _ in enumerate(aoVariations): 529 555 oVar = copy.copy(aoVariations[iVar]); 530 556 oVar.convertToParamNull(); … … 623 649 oDefMember = TestGroupMemberData(); 624 650 aoTestGroupMembers = list(aoTestGroupMembers); # Copy it so we can pop. 625 for iTestCase in range(len(aoAllTestCases)):651 for iTestCase, _ in enumerate(aoAllTestCases): 626 652 oTestCase = aoAllTestCases[iTestCase]; 627 653 628 654 # Is it a member? 629 655 oMember = None; 630 for i in range(len(aoTestGroupMembers)):656 for i, _ in enumerate(aoTestGroupMembers): 631 657 if aoTestGroupMembers[i].oTestCase.idTestCase == oTestCase.idTestCase: 632 658 oMember = aoTestGroupMembers.pop(i); … … 735 761 oDefMember = SchedGroupMemberData(); 736 762 aoSchedGroupMembers = list(aoSchedGroupMembers); # Copy it so we can pop. 737 for iTestGroup in range(len(aoAllTestGroups)):763 for iTestGroup, _ in enumerate(aoAllTestGroups): 738 764 oTestGroup = aoAllTestGroups[iTestGroup]; 739 765 740 766 # Is it a member? 741 767 oMember = None; 742 for i in range(len(aoSchedGroupMembers)):768 for i, _ in enumerate(aoSchedGroupMembers): 743 769 if aoSchedGroupMembers[i].oTestGroup.idTestGroup == oTestGroup.idTestGroup: 744 770 oMember = aoSchedGroupMembers.pop(i); -
trunk/src/VBox/ValidationKit/testmanager/webui/wuimain.py
r56809 r61217 69 69 ksActionResultsGroupedByTestCase = 'ResultsGroupedByTestCase' 70 70 ksActionTestResultDetails = 'TestResultDetails' 71 ksActionTestResultFailureDetails = 'TestResultFailureDetails' 72 ksActionTestResultFailureAdd = 'TestResultFailureAdd' 73 ksActionTestResultFailureAddPost = 'TestResultFailureAddPost' 74 ksActionTestResultFailureEdit = 'TestResultFailureEdit' 75 ksActionTestResultFailureEditPost = 'TestResultFailureEditPost' 71 76 ksActionViewLog = 'ViewLog' 72 77 ksActionGetFile = 'GetFile' … … 228 233 WuiGroupedResultList) 229 234 230 d[self.ksActionTestResultDetails] = self.actionTestResultDetails 235 d[self.ksActionTestResultDetails] = self._actionTestResultDetails; 236 237 d[self.ksActionTestResultFailureAdd] = self._actionTestResultFailureAdd; 238 d[self.ksActionTestResultFailureAddPost] = self._actionTestResultFailureAddPost; 239 d[self.ksActionTestResultFailureDetails] = self._actionTestResultFailureDetails; 240 d[self.ksActionTestResultFailureEdit] = self._actionTestResultFailureEdit; 241 d[self.ksActionTestResultFailureEditPost] = self._actionTestResultFailureEditPost; 231 242 232 243 d[self.ksActionViewLog] = self.actionViewLog; … … 813 824 return WuiDispatcherBase._generatePage(self) 814 825 815 def actionTestResultDetails(self):826 def _actionTestResultDetails(self): 816 827 """Show test case execution result details.""" 817 828 from testmanager.webui.wuitestresult import WuiTestResult; … … 849 860 return True 850 861 862 def _actionTestResultFailureAdd(self): 863 """ Pro forma. """ 864 from testmanager.core.testresults import TestResultFailureLogic, TestResultFailureData; 865 from testmanager.webui.wuitestresultfailure import WuiTestResultFailure; 866 return self._actionGenericFormAdd(TestResultFailureData, WuiTestResultFailure); 867 868 def _actionTestResultFailureAddPost(self): 869 """Add test result failure result""" 870 from testmanager.core.testresults import TestResultFailureLogic, TestResultFailureData; 871 from testmanager.webui.wuitestresultfailure import WuiTestResultFailure; 872 if self.ksParamRedirectTo not in self._dParams: 873 raise WuiException('Missing parameter ' + self.ksParamRedirectTo); 874 875 return self._actionGenericFormAddPost(TestResultFailureData, TestResultFailureLogic, 876 WuiTestResultFailure, self.ksActionResultsUnGrouped); 877 878 def _actionTestResultFailureDetails(self): 879 """ Pro forma. """ 880 from testmanager.core.testresults import TestResultFailureLogic, TestResultFailureData; 881 from testmanager.webui.wuitestresultfailure import WuiTestResultFailure; 882 return self._actionGenericFormDetails(TestResultFailureData, TestResultFailureLogic, 883 WuiTestResultFailure, 'idTestResult'); 884 885 def _actionTestResultFailureEdit(self): 886 """ Pro forma. """ 887 from testmanager.core.testresults import TestResultFailureData; 888 from testmanager.webui.wuitestresultfailure import WuiTestResultFailure; 889 return self._actionGenericFormEdit(TestResultFailureData, WuiTestResultFailure, 890 TestResultFailureData.ksParam_idTestResult); 891 892 def _actionTestResultFailureEditPost(self): 893 """Edit test result failure result""" 894 from testmanager.core.testresults import TestResultFailureLogic, TestResultFailureData; 895 from testmanager.webui.wuitestresultfailure import WuiTestResultFailure; 896 if self.ksParamRedirectTo not in self._dParams: 897 raise WuiException('Missing parameter ' + self.ksParamRedirectTo); 898 899 return self._actionGenericFormEditPost(TestResultFailureData, TestResultFailureLogic, 900 WuiTestResultFailure, self.ksActionResultsUnGrouped); 901 851 902 def actionViewLog(self): 852 903 """ -
trunk/src/VBox/ValidationKit/testmanager/webui/wuitestresult.py
r56806 r61217 35 35 WuiSvnLink, WuiSvnLinkWithTooltip, WuiBuildLogLink, WuiRawHtml; 36 36 from testmanager.webui.wuimain import WuiMain; 37 from testmanager.webui.wuihlpform import WuiHlpForm; 38 from testmanager.core.failurereason import FailureReasonData, FailureReasonLogic; 37 39 from testmanager.core.report import ReportGraphModel; 38 40 from testmanager.core.testbox import TestBoxData; … … 40 42 from testmanager.core.testset import TestSetData; 41 43 from testmanager.core.testgroup import TestGroupData; 44 from testmanager.core.testresults import TestResultFailureData; 42 45 from testmanager.core.build import BuildData; 43 46 from testmanager.core import db; … … 137 140 sErrCnt = ' (1 error)' if oTestResult.cErrors == 1 else ' (%d errors)' % oTestResult.cErrors; 138 141 142 # Format bits for adding or editing the failure reason. Level 0 is handled at the top of the page. 143 sChangeReason = ''; 144 if oTestResult.cErrors > 0 and iDepth > 0: 145 dTmp = { 146 self._oDisp.ksParamAction: self._oDisp.ksActionTestResultFailureAdd if oTestResult.oReason is None else 147 self._oDisp.ksActionTestResultFailureEdit, 148 TestResultFailureData.ksParam_idTestResult: oTestResult.idTestResult, 149 }; 150 sChangeReason = ' <a href="?%s" class="tmtbl-edit-reason" onclick="addRedirectToAnchorHref(this)">%s</a> ' \ 151 % ( webutils.encodeUrlParams(dTmp), WuiContentBase.ksShortEditLinkHtml ); 152 139 153 # Format the include in graph checkboxes. 140 154 sLineage += ':%u' % (oTestResult.idStrName,); … … 148 162 149 163 if len(oTestResult.aoChildren) == 0 \ 150 and len(oTestResult.aoValues) == 0 \ 151 and len(oTestResult.aoMsgs) == 0 \ 152 and len(oTestResult.aoFiles) == 0: 164 and len(oTestResult.aoValues) + len(oTestResult.aoMsgs) + len(oTestResult.aoFiles) == 0: 153 165 # Leaf - single row. 154 166 tsEvent = oTestResult.tsCreated; … … 160 172 ' <td>%s</td>\n' \ 161 173 ' <td>%s</td>\n' \ 162 ' <td colspan="2"%s>%s%s </td>\n' \174 ' <td colspan="2"%s>%s%s%s</td>\n' \ 163 175 ' <td>%s</td>\n' \ 164 176 ' </tr>\n' \ … … 170 182 sDisplayName, 171 183 ' id="failure-%u"' % (iFailure,) if oTestResult.isFailure() else '', 172 webutils.escapeElem(oTestResult.enmStatus), webutils.escapeElem(sErrCnt), 184 webutils.escapeElem(oTestResult.enmStatus), webutils.escapeElem(sErrCnt), sChangeReason, 173 185 sResultGraph ); 174 186 iRow += 1; … … 189 201 iRow += 1; 190 202 191 # Depth. 203 # Depth. Check if our error count is just reflecting the one of our children. 204 cErrorsBelow = 0; 192 205 for oChild in oTestResult.aoChildren: 193 206 (sChildHtml, iRow, iFailure) = self._recursivelyGenerateEvents(oChild, sName, sLineage, 194 207 iRow, iFailure, oTestSet, iDepth + 1); 195 208 sHtml += sChildHtml; 196 209 cErrorsBelow += oChild.cErrors; 210 211 if cErrorsBelow >= oTestResult.cErrors: 212 sChangeReason = ''; 197 213 198 214 # Messages. … … 263 279 264 280 sHtml += ' <tr class="%s tmtbl-events-file tmtbl-events-lvl%s">\n' \ 265 ' <td> </td>\n' \266 ' <td> %s</td>\n' \281 ' <td>%s</td>\n' \ 282 ' <td></td>\n' \ 267 283 ' <td></td>\n' \ 268 284 ' <td>%s</td>\n' \ … … 296 312 iRow += 1; 297 313 314 # Failure reason. 315 if oTestResult.oReason is not None: 316 sReasonText = '%s / %s' % ( oTestResult.oReason.oFailureReason.oCategory.sShort, 317 oTestResult.oReason.oFailureReason.sShort, ); 318 sCommentHtml = ''; 319 if oTestResult.oReason.sComment is not None and len(oTestResult.oReason.sComment.strip()) > 0: 320 sCommentHtml = '<br>' + webutils.escapeElem(oTestResult.oReason.sComment.strip()); 321 sCommentHtml = sCommentHtml.replace('\n', '<br>'); 322 323 sDetailedReason = ' <a href="?%s" class="tmtbl-show-reason">%s</a>' \ 324 % ( webutils.encodeUrlParams({ self._oDisp.ksParamAction: 325 self._oDisp.ksActionTestResultFailureDetails, 326 TestResultFailureData.ksParam_idTestResult: 327 oTestResult.idTestResult,}), 328 WuiContentBase.ksShortDetailsLinkHtml,); 329 330 331 sHtml += ' <tr class="%s tmtbl-events-reason tmtbl-events-lvl%s">\n' \ 332 ' <td>%s</td>\n' \ 333 ' <td colspan="2">%s</td>\n' \ 334 ' <td colspan="3">%s%s%s%s</td>\n' \ 335 ' <td>%s</td>\n' \ 336 ' </tr>\n' \ 337 % ( 'tmodd' if iRow & 1 else 'tmeven', iDepth, 338 webutils.escapeElem(self.formatTsShort(oTestResult.oReason.tsEffective)), 339 oTestResult.oReason.oAuthor.sUsername, 340 webutils.escapeElem(sReasonText), sDetailedReason, sChangeReason, 341 sCommentHtml, 342 'todo'); 343 iRow += 1; 344 298 345 if oTestResult.isFailure(): 299 346 iFailure += 1; 300 347 301 348 return (sHtml, iRow, iFailure); 349 350 351 def _generateMainReason(self, oTestResultTree, oTestSet): 352 """ 353 Generates the form for displaying and updating the main failure reason. 354 355 oTestResultTree is an instance TestResultDataEx. 356 oTestSet is an instance of TestSetData. 357 358 """ 359 _ = oTestSet; 360 sHtml = ' '; 361 362 if oTestResultTree.isFailure() or oTestResultTree.cErrors > 0: 363 sHtml += ' <h2>Failure Reason:</h2>\n'; 364 oData = oTestResultTree.oReason; 365 366 # We need the failure reasons for the combobox. 367 aoFailureReasons = FailureReasonLogic(self._oDisp.getDb()).fetchForCombo('Todo: Figure out why'); 368 assert len(aoFailureReasons) > 0; 369 370 # For now we'll use the standard form helper. 371 sFormActionUrl = '%s?%s=%s' % ( self._oDisp.ksScriptName, self._oDisp.ksParamAction, 372 WuiMain.ksActionTestResultFailureAddPost if oData is None else 373 WuiMain.ksActionTestResultFailureEditPost ) 374 oForm = WuiHlpForm('failure-reason', sFormActionUrl, 375 sOnSubmit = WuiHlpForm.ksOnSubmit_AddReturnToFieldWithCurrentUrl); 376 oForm.addTextHidden(TestResultFailureData.ksParam_idTestResult, oTestResultTree.idTestResult); 377 oForm.addComboBox(TestResultFailureData.ksParam_idFailureReason, oData.idFailureReason if oData is not None else -1, 378 'Reason', aoFailureReasons); 379 oForm.addMultilineText(TestResultFailureData.ksParam_sComment, 380 oData.sComment if oData is not None else '', 'Comment'); 381 if oData is not None: 382 oForm.addNonText('%s (%s)' % (oData.oAuthor.sUsername, oData.oAuthor.sUsername), 'Sheriff'); 383 oForm.addNonText(oData.tsEffective, 'When'); 384 oForm.addTextHidden(TestResultFailureData.ksParam_tsEffective, oData.tsEffective); 385 oForm.addTextHidden(TestResultFailureData.ksParam_tsExpire, oData.tsExpire); 386 oForm.addTextHidden(TestResultFailureData.ksParam_uidAuthor, oData.uidAuthor); 387 else: 388 oForm.addTextHidden(TestResultFailureData.ksParam_tsEffective, ''); 389 oForm.addTextHidden(TestResultFailureData.ksParam_tsExpire, ''); 390 oForm.addTextHidden(TestResultFailureData.ksParam_uidAuthor, ''); 391 392 oForm.addSubmit('Change Reason', ); 393 sHtml += oForm.finalize(); 394 return sHtml; 395 302 396 303 397 def showTestCaseResultDetails(self, # pylint: disable=R0914,R0915 … … 534 628 535 629 sHtml += ' <td valign="top" width="80%" style="padding-left:6px">\n'; 630 sHtml += self._generateMainReason(oTestResultTree, oTestSet); 631 536 632 sHtml += ' <h2>Events:</h2>\n'; 537 633 sHtml += ' <form action="#" method="get" id="graph-form">\n' \ … … 614 710 'Start', 615 711 'Product Build', 616 ' ValidationKit',617 ' TestBox OS',618 ' TestBox Name',712 'Kit', 713 'Box', 714 'OS.Arch', 619 715 'Test Case', 620 716 'Elapsed', 621 717 'Result', 718 'Reason', 622 719 ]; 623 720 self._asColumnAttribs = ['align="center"', 'align="center"', 'align="center"', 624 721 'align="center"', 'align="center"', 'align="center"', 625 722 'align="center"', 'align="center"', 'align="center"', 626 'align="center"', 'align="center"', 'align="center"' ] 723 'align="center"', 'align="center"', 'align="center"', 724 'align="center"', ]; 627 725 628 726 … … 650 748 oValidationKit = None; 651 749 if oEntry.idBuildTestSuite is not None: 652 oValidationKit = WuiTmLink(' #%d - r%s' % (oEntry.idBuildTestSuite, oEntry.iRevisionTestSuite),750 oValidationKit = WuiTmLink('r%s' % (oEntry.iRevisionTestSuite,), 653 751 WuiAdmin.ksScriptName, 654 752 { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildDetails, … … 656 754 fBracketed = False); 657 755 658 659 aoTestSetLinks = [WuiTmLink(oEntry.enmStatus,660 WuiMain.ksScriptName,661 { WuiMain.ksParamAction: WuiMain.ksActionTestResultDetails,662 TestSetData.ksParam_idTestSet: oEntry.idTestSet },663 fBracketed = False),];756 aoTestSetLinks = []; 757 aoTestSetLinks.append(WuiTmLink(oEntry.enmStatus, 758 WuiMain.ksScriptName, 759 { WuiMain.ksParamAction: WuiMain.ksActionTestResultDetails, 760 TestSetData.ksParam_idTestSet: oEntry.idTestSet }, 761 fBracketed = False)); 664 762 if oEntry.cErrors > 0: 665 aoTestSetLinks.append(WuiTmLink('- %d error(s)' % (oEntry.cErrors, ), 763 aoTestSetLinks.append(WuiRawHtml('-')); 764 aoTestSetLinks.append(WuiTmLink('%d error%s' % (oEntry.cErrors, '' if oEntry.cErrors == 1 else 's', ), 666 765 WuiMain.ksScriptName, 667 766 { WuiMain.ksParamAction: WuiMain.ksActionTestResultDetails, … … 688 787 sTestBoxTitle += u'CPU features:\t' + u', '.join(asFeatures); 689 788 789 # Reason: 790 oReason = None; 791 if oEntry.oFailureReason is not None: 792 sReasonTitle = 'Reason: \t%s\n' % ( oEntry.oFailureReason.sShort, ); 793 sReasonTitle += 'Category:\t%s\n' % ( oEntry.oFailureReason.oCategory.sShort, ); 794 sReasonTitle += 'Assigned:\t%s\n' % ( self.formatTsShort(oEntry.tsFailureReasonAssigned), ); 795 sReasonTitle += 'By User: \t%s\n' % ( oEntry.oFailureReasonAssigner.sUsername, ); 796 if oEntry.sFailureReasonComment is not None and len(oEntry.sFailureReasonComment) > 0: 797 sReasonTitle += 'Comment: \t%s\n' % ( self.formatTsShort(oEntry.sFailureReasonComment), ); 798 if oEntry.oFailureReason.iTicket is not None and oEntry.oFailureReason.iTicket > 0: 799 sReasonTitle += 'xTracker:\t#%s\n' % ( oEntry.oFailureReason.iTicket, ); 800 for i, sUrl in enumerate(oEntry.oFailureReason.asUrls): 801 sUrl = sUrl.strip(); 802 if len(sUrl) > 0: 803 sReasonTitle += 'URL#%u: \t%s\n' % ( i, sUrl, ); 804 oReason = WuiTmLink(oEntry.oFailureReason.sShort, WuiAdmin.ksScriptName, 805 { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureReasonDetails, 806 FailureReasonData.ksParam_idFailureReason: oEntry.oFailureReason.idFailureReason }, 807 sTitle = sReasonTitle); 808 690 809 return [ 691 810 oEntry.tsCreated, 692 [ WuiTmLink(' #%d - %s %s (%s)' % (oEntry.idBuild,oEntry.sProduct, oEntry.sVersion, oEntry.sType,),811 [ WuiTmLink('%s %s (%s)' % (oEntry.sProduct, oEntry.sVersion, oEntry.sType,), 693 812 WuiMain.ksScriptName, self._dRevLinkParams, sTitle = '%s' % (oEntry.sBranch,), fBracketed = False), 694 813 WuiSvnLinkWithTooltip(oEntry.iRevision, 'vbox'), ## @todo add sRepository TestResultListingData … … 699 818 ], 700 819 oValidationKit, 701 '%s.%s' % (oEntry.sOs, oEntry.sArch),702 820 [ WuiTmLink(oEntry.sTestBoxName, WuiMain.ksScriptName, self._dTestBoxLinkParams, fBracketed = False, 703 821 sTitle = sTestBoxTitle), … … 706 824 TestBoxData.ksParam_idTestBox: oEntry.idTestBox }, 707 825 fBracketed = False) ], 826 '%s.%s' % (oEntry.sOs, oEntry.sArch), 708 827 [ WuiTmLink(oEntry.sTestCaseName, WuiMain.ksScriptName, self._dTestCaseLinkParams, fBracketed = False, 709 828 sTitle = (oEntry.sBaseCmd + ' ' + oEntry.sArgs) if oEntry.sArgs else oEntry.sBaseCmd), … … 713 832 fBracketed = False), ], 714 833 oEntry.tsElapsed, 715 aoTestSetLinks 834 aoTestSetLinks, 835 oReason 716 836 ];
Note:
See TracChangeset
for help on using the changeset viewer.