VirtualBox

Changeset 61278 in vbox for trunk/src/VBox/ValidationKit


Ignore:
Timestamp:
May 29, 2016 4:52:40 PM (9 years ago)
Author:
vboxsync
Message:

split TestResultFailures related bits out of testresults.py. removed some lint.

Location:
trunk/src/VBox/ValidationKit/testmanager
Files:
6 edited
1 copied

Legend:

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

    r61272 r61278  
    177177        """
    178178        if iPeriod == 0:
    179             return 'now';
     179            return 'now' if self.tsNow is None else 'then';
    180180        if self.cHoursPerPeriod == 24:
    181181            return '%dd ago' % (iPeriod, );
     182        if (iPeriod * self.cHoursPerPeriod) % 24 == 0:
     183            return '%dd ago' % (iPeriod * self.cHoursPerPeriod / 24, );
    182184        return '%dh ago' % (iPeriod * self.cHoursPerPeriod, );
    183185
     
    748750
    749751            oSet.appendPeriod(oPeriod);
    750         cDeleted = oSet.pruneRowsWithZeroSumHits();
     752        oSet.pruneRowsWithZeroSumHits();
    751753
    752754
  • trunk/src/VBox/ValidationKit/testmanager/core/testresultfailures.py

    r61268 r61278  
    66
    77"""
    8 Test Manager - Fetch test results.
     8Test Manager - Test result failures.
    99"""
    1010
     
    3535
    3636# Validation Kit imports.
    37 from common                         import constants;
    38 from testmanager                    import config;
    39 from testmanager.core.base          import ModelDataBase, ModelLogicBase, ModelDataBaseTestCase, TMExceptionBase, \
    40                                            TMTooManyRows, TMInvalidData, TMRowNotFound, TMRowAlreadyExists, \
    41                                            ChangeLogEntry, AttributeChangeEntry;
    42 from testmanager.core.testgroup     import TestGroupData;
    43 from testmanager.core.build         import BuildDataEx;
    44 from testmanager.core.failurereason import FailureReasonLogic, FailureReasonData;
    45 from testmanager.core.testbox       import TestBoxData;
    46 from testmanager.core.testcase      import TestCaseData;
    47 from testmanager.core.schedgroup    import SchedGroupData;
    48 from testmanager.core.systemlog     import SystemLogData, SystemLogLogic;
     37from testmanager.core.base          import ModelDataBase, ModelLogicBase, ModelDataBaseTestCase, TMInvalidData, TMRowNotFound, \
     38                                           TMRowAlreadyExists, ChangeLogEntry, AttributeChangeEntry;
     39from testmanager.core.failurereason import FailureReasonData;
    4940from testmanager.core.useraccount   import UserAccountLogic;
    5041
    51 
    52 class TestResultData(ModelDataBase):
    53     """
    54     Test case execution result data
    55     """
    56 
    57     ## @name TestStatus_T
    58     # @{
    59     ksTestStatus_Running    = 'running';
    60     ksTestStatus_Success    = 'success';
    61     ksTestStatus_Skipped    = 'skipped';
    62     ksTestStatus_BadTestBox = 'bad-testbox';
    63     ksTestStatus_Aborted    = 'aborted';
    64     ksTestStatus_Failure    = 'failure';
    65     ksTestStatus_TimedOut   = 'timed-out';
    66     ksTestStatus_Rebooted   = 'rebooted';
    67     ## @}
    68 
    69     ## List of relatively harmless (to testgroup/case) statuses.
    70     kasHarmlessTestStatuses = [ ksTestStatus_Skipped, ksTestStatus_BadTestBox, ksTestStatus_Aborted, ];
    71     ## List of bad statuses.
    72     kasBadTestStatuses      = [ ksTestStatus_Failure, ksTestStatus_TimedOut,   ksTestStatus_Rebooted, ];
    73 
    74 
    75     ksIdAttr    = 'idTestResult';
    76 
    77     ksParam_idTestResult        = 'TestResultData_idTestResult';
    78     ksParam_idTestResultParent  = 'TestResultData_idTestResultParent';
    79     ksParam_idTestSet           = 'TestResultData_idTestSet';
    80     ksParam_tsCreated           = 'TestResultData_tsCreated';
    81     ksParam_tsElapsed           = 'TestResultData_tsElapsed';
    82     ksParam_idStrName           = 'TestResultData_idStrName';
    83     ksParam_cErrors             = 'TestResultData_cErrors';
    84     ksParam_enmStatus           = 'TestResultData_enmStatus';
    85     ksParam_iNestingDepth       = 'TestResultData_iNestingDepth';
    86     kasValidValues_enmStatus    = [
    87         ksTestStatus_Running,
    88         ksTestStatus_Success,
    89         ksTestStatus_Skipped,
    90         ksTestStatus_BadTestBox,
    91         ksTestStatus_Aborted,
    92         ksTestStatus_Failure,
    93         ksTestStatus_TimedOut,
    94         ksTestStatus_Rebooted
    95     ];
    96 
    97 
    98     def __init__(self):
    99         ModelDataBase.__init__(self)
    100         self.idTestResult       = None
    101         self.idTestResultParent = None
    102         self.idTestSet          = None
    103         self.tsCreated          = None
    104         self.tsElapsed          = None
    105         self.idStrName          = None
    106         self.cErrors            = 0;
    107         self.enmStatus          = None
    108         self.iNestingDepth      = None
    109 
    110     def initFromDbRow(self, aoRow):
    111         """
    112         Reinitialize from a SELECT * FROM TestResults.
    113         Return self. Raises exception if no row.
    114         """
    115         if aoRow is None:
    116             raise TMRowNotFound('Test result record not found.')
    117 
    118         self.idTestResult       = aoRow[0]
    119         self.idTestResultParent = aoRow[1]
    120         self.idTestSet          = aoRow[2]
    121         self.tsCreated          = aoRow[3]
    122         self.tsElapsed          = aoRow[4]
    123         self.idStrName          = aoRow[5]
    124         self.cErrors            = aoRow[6]
    125         self.enmStatus          = aoRow[7]
    126         self.iNestingDepth      = aoRow[8]
    127         return self;
    128 
    129     def initFromDbWithId(self, oDb, idTestResult, tsNow = None, sPeriodBack = None):
    130         """
    131         Initialize from the database, given the ID of a row.
    132         """
    133         _ = tsNow;
    134         _ = sPeriodBack;
    135         oDb.execute('SELECT *\n'
    136                     'FROM   TestResults\n'
    137                     'WHERE  idTestResult = %s\n'
    138                     , ( idTestResult,));
    139         aoRow = oDb.fetchOne()
    140         if aoRow is None:
    141             raise TMRowNotFound('idTestResult=%s not found' % (idTestResult,));
    142         return self.initFromDbRow(aoRow);
    143 
    144     def isFailure(self):
    145         """ Check if it's a real failure. """
    146         return self.enmStatus in self.kasBadTestStatuses;
    147 
    148 
    149 class TestResultDataEx(TestResultData):
    150     """
    151     Extended test result data class.
    152 
    153     This is intended for use as a node in a result tree.  This is not intended
    154     for serialization to parameters or vice versa.  Use TestResultLogic to
    155     construct the tree.
    156     """
    157 
    158     def __init__(self):
    159         TestResultData.__init__(self)
    160         self.sName      = None; # idStrName resolved.
    161         self.oParent    = None; # idTestResultParent within the tree.
    162 
    163         self.aoChildren = [];   # TestResultDataEx;
    164         self.aoValues   = [];   # TestResultValueDataEx;
    165         self.aoMsgs     = [];   # TestResultMsgDataEx;
    166         self.aoFiles    = [];   # TestResultFileDataEx;
    167         self.oReason    = None; # TestResultReasonDataEx;
    168 
    169     def initFromDbRow(self, aoRow):
    170         """
    171         Initialize from a query like this:
    172             SELECT TestResults.*, TestResultStrTab.sValue
    173             FROM TestResults, TestResultStrTab
    174             WHERE TestResultStrTab.idStr = TestResults.idStrName
    175 
    176         Note! The caller is expected to fetch children, values, failure
    177               details, and files.
    178         """
    179         self.sName      = None;
    180         self.oParent    = None;
    181         self.aoChildren = [];
    182         self.aoValues   = [];
    183         self.aoMsgs     = [];
    184         self.aoFiles    = [];
    185         self.oReason    = None;
    186 
    187         TestResultData.initFromDbRow(self, aoRow);
    188 
    189         self.sName = aoRow[9];
    190         return self;
    191 
    192 
    193 class TestResultValueData(ModelDataBase):
    194     """
    195     Test result value data.
    196     """
    197 
    198     ksIdAttr    = 'idTestResultValue';
    199 
    200     ksParam_idTestResultValue   = 'TestResultValue_idTestResultValue';
    201     ksParam_idTestResult        = 'TestResultValue_idTestResult';
    202     ksParam_idTestSet           = 'TestResultValue_idTestSet';
    203     ksParam_tsCreated           = 'TestResultValue_tsCreated';
    204     ksParam_idStrName           = 'TestResultValue_idStrName';
    205     ksParam_lValue              = 'TestResultValue_lValue';
    206     ksParam_iUnit               = 'TestResultValue_iUnit';
    207 
    208     def __init__(self):
    209         ModelDataBase.__init__(self)
    210         self.idTestResultValue  = None;
    211         self.idTestResult       = None;
    212         self.idTestSet          = None;
    213         self.tsCreated          = None;
    214         self.idStrName          = None;
    215         self.lValue             = None;
    216         self.iUnit              = 0;
    217 
    218     def initFromDbRow(self, aoRow):
    219         """
    220         Reinitialize from a SELECT * FROM TestResultValues.
    221         Return self. Raises exception if no row.
    222         """
    223         if aoRow is None:
    224             raise TMRowNotFound('Test result value record not found.')
    225 
    226         self.idTestResultValue  = aoRow[0];
    227         self.idTestResult       = aoRow[1];
    228         self.idTestSet          = aoRow[2];
    229         self.tsCreated          = aoRow[3];
    230         self.idStrName          = aoRow[4];
    231         self.lValue             = aoRow[5];
    232         self.iUnit              = aoRow[6];
    233         return self;
    234 
    235 
    236 class TestResultValueDataEx(TestResultValueData):
    237     """
    238     Extends TestResultValue by resolving the value name and unit string.
    239     """
    240 
    241     def __init__(self):
    242         TestResultValueData.__init__(self)
    243         self.sName = None;
    244         self.sUnit = '';
    245 
    246     def initFromDbRow(self, aoRow):
    247         """
    248         Reinitialize from a query like this:
    249             SELECT TestResultValues.*, TestResultStrTab.sValue
    250             FROM TestResultValues, TestResultStrTab
    251             WHERE TestResultStrTab.idStr = TestResultValues.idStrName
    252 
    253         Return self. Raises exception if no row.
    254         """
    255         TestResultValueData.initFromDbRow(self, aoRow);
    256         self.sName = aoRow[7];
    257         if self.iUnit < len(constants.valueunit.g_asNames):
    258             self.sUnit = constants.valueunit.g_asNames[self.iUnit];
    259         else:
    260             self.sUnit = '<%d>' % (self.iUnit,);
    261         return self;
    262 
    263 class TestResultMsgData(ModelDataBase):
    264     """
    265     Test result message data.
    266     """
    267 
    268     ksIdAttr    = 'idTestResultMsg';
    269 
    270     ksParam_idTestResultMsg    = 'TestResultValue_idTestResultMsg';
    271     ksParam_idTestResult       = 'TestResultValue_idTestResult';
    272     ksParam_tsCreated          = 'TestResultValue_tsCreated';
    273     ksParam_idStrMsg           = 'TestResultValue_idStrMsg';
    274     ksParam_enmLevel           = 'TestResultValue_enmLevel';
    275 
    276     def __init__(self):
    277         ModelDataBase.__init__(self)
    278         self.idTestResultMsg    = None;
    279         self.idTestResult       = None;
    280         self.tsCreated          = None;
    281         self.idStrMsg           = None;
    282         self.enmLevel           = None;
    283 
    284     def initFromDbRow(self, aoRow):
    285         """
    286         Reinitialize from a SELECT * FROM TestResultMsgs.
    287         Return self. Raises exception if no row.
    288         """
    289         if aoRow is None:
    290             raise TMRowNotFound('Test result value record not found.')
    291 
    292         self.idTestResultMsg    = aoRow[0];
    293         self.idTestResult       = aoRow[1];
    294         self.tsCreated          = aoRow[2];
    295         self.idStrMsg           = aoRow[3];
    296         self.enmLevel           = aoRow[4];
    297         return self;
    298 
    299 class TestResultMsgDataEx(TestResultMsgData):
    300     """
    301     Extends TestResultMsg by resolving the message string.
    302     """
    303 
    304     def __init__(self):
    305         TestResultMsgData.__init__(self)
    306         self.sMsg = None;
    307 
    308     def initFromDbRow(self, aoRow):
    309         """
    310         Reinitialize from a query like this:
    311             SELECT TestResultMsg.*, TestResultStrTab.sValue
    312             FROM   TestResultMsg, TestResultStrTab
    313             WHERE  TestResultStrTab.idStr = TestResultMsgs.idStrName
    314 
    315         Return self. Raises exception if no row.
    316         """
    317         TestResultMsgData.initFromDbRow(self, aoRow);
    318         self.sMsg = aoRow[5];
    319         return self;
    320 
    321 class TestResultFileData(ModelDataBase):
    322     """
    323     Test result message data.
    324     """
    325 
    326     ksIdAttr    = 'idTestResultFile';
    327 
    328     ksParam_idTestResultFile    = 'TestResultFile_idTestResultFile';
    329     ksParam_idTestResult        = 'TestResultFile_idTestResult';
    330     ksParam_tsCreated           = 'TestResultFile_tsCreated';
    331     ksParam_idStrFile           = 'TestResultFile_idStrFile';
    332     ksParam_idStrDescription    = 'TestResultFile_idStrDescription';
    333     ksParam_idStrKind           = 'TestResultFile_idStrKind';
    334     ksParam_idStrMime           = 'TestResultFile_idStrMime';
    335 
    336     def __init__(self):
    337         ModelDataBase.__init__(self)
    338         self.idTestResultFile   = None;
    339         self.idTestResult       = None;
    340         self.tsCreated          = None;
    341         self.idStrFile          = None;
    342         self.idStrDescription   = None;
    343         self.idStrKind          = None;
    344         self.idStrMime          = None;
    345 
    346     def initFromDbRow(self, aoRow):
    347         """
    348         Reinitialize from a SELECT * FROM TestResultFiles.
    349         Return self. Raises exception if no row.
    350         """
    351         if aoRow is None:
    352             raise TMRowNotFound('Test result file record not found.')
    353 
    354         self.idTestResultFile   = aoRow[0];
    355         self.idTestResult       = aoRow[1];
    356         self.tsCreated          = aoRow[2];
    357         self.idStrFile          = aoRow[3];
    358         self.idStrDescription   = aoRow[4];
    359         self.idStrKind          = aoRow[5];
    360         self.idStrMime          = aoRow[6];
    361         return self;
    362 
    363 class TestResultFileDataEx(TestResultFileData):
    364     """
    365     Extends TestResultFile by resolving the strings.
    366     """
    367 
    368     def __init__(self):
    369         TestResultFileData.__init__(self)
    370         self.sFile          = None;
    371         self.sDescription   = None;
    372         self.sKind          = None;
    373         self.sMime          = None;
    374 
    375     def initFromDbRow(self, aoRow):
    376         """
    377         Reinitialize from a query like this:
    378             SELECT   TestResultFiles.*,
    379                      StrTabFile.sValue AS sFile,
    380                      StrTabDesc.sValue AS sDescription
    381                      StrTabKind.sValue AS sKind,
    382                      StrTabMime.sValue AS sMime,
    383             FROM ...
    384 
    385         Return self. Raises exception if no row.
    386         """
    387         TestResultFileData.initFromDbRow(self, aoRow);
    388         self.sFile          = aoRow[7];
    389         self.sDescription   = aoRow[8];
    390         self.sKind          = aoRow[9];
    391         self.sMime          = aoRow[10];
    392         return self;
    393 
    394     def initFakeMainLog(self, oTestSet):
    395         """
    396         Reinitializes to represent the main.log object (not in DB).
    397 
    398         Returns self.
    399         """
    400         self.idTestResultFile   = 0;
    401         self.idTestResult       = oTestSet.idTestResult;
    402         self.tsCreated          = oTestSet.tsCreated;
    403         self.idStrFile          = None;
    404         self.idStrDescription   = None;
    405         self.idStrKind          = None;
    406         self.idStrMime          = None;
    407 
    408         self.sFile              = 'main.log';
    409         self.sDescription       = '';
    410         self.sKind              = 'log/main';
    411         self.sMime              = 'text/plain';
    412         return self;
    413 
    414     def isProbablyUtf8Encoded(self):
    415         """
    416         Checks if the file is likely to be UTF-8 encoded.
    417         """
    418         if self.sMime in [ 'text/plain', 'text/html' ]:
    419             return True;
    420         return False;
    421 
    422     def getMimeWithEncoding(self):
    423         """
    424         Gets the MIME type with encoding if likely to be UTF-8.
    425         """
    426         if self.isProbablyUtf8Encoded():
    427             return '%s; charset=utf-8' % (self.sMime,);
    428         return self.sMime;
    42942
    43043
     
    626239
    627240        return self
    628 
    629 
    630 class TestResultHangingOffence(TMExceptionBase):
    631     """Hanging offence committed by test case."""
    632     pass;
    633 
    634 
    635 class TestResultLogic(ModelLogicBase): # pylint: disable=R0903
    636     """
    637     Results grouped by scheduling group.
    638     """
    639 
    640     #
    641     # Result grinding for displaying in the WUI.
    642     #
    643 
    644     ksResultsGroupingTypeNone       = 'ResultsGroupingTypeNone';
    645     ksResultsGroupingTypeTestGroup  = 'ResultsGroupingTypeTestGroup';
    646     ksResultsGroupingTypeBuildRev   = 'ResultsGroupingTypeBuild';
    647     ksResultsGroupingTypeTestBox    = 'ResultsGroupingTypeTestBox';
    648     ksResultsGroupingTypeTestCase   = 'ResultsGroupingTypeTestCase';
    649     ksResultsGroupingTypeSchedGroup = 'ResultsGroupingTypeSchedGroup';
    650 
    651     ## @name Result sorting options.
    652     ## @{
    653     ksResultsSortByRunningAndStart      = 'ResultsSortByRunningAndStart'; ##< Default
    654     ksResultsSortByBuildRevision        = 'ResultsSortByBuildRevision';
    655     ksResultsSortByTestBoxName          = 'ResultsSortByTestBoxName';
    656     ksResultsSortByTestBoxOs            = 'ResultsSortByTestBoxOs';
    657     ksResultsSortByTestBoxOsVersion     = 'ResultsSortByTestBoxOsVersion';
    658     ksResultsSortByTestBoxOsArch        = 'ResultsSortByTestBoxOsArch';
    659     ksResultsSortByTestBoxArch          = 'ResultsSortByTestBoxArch';
    660     ksResultsSortByTestBoxCpuVendor     = 'ResultsSortByTestBoxCpuVendor';
    661     ksResultsSortByTestBoxCpuName       = 'ResultsSortByTestBoxCpuName';
    662     ksResultsSortByTestBoxCpuRev        = 'ResultsSortByTestBoxCpuRev';
    663     ksResultsSortByTestBoxCpuFeatures   = 'ResultsSortByTestBoxCpuFeatures';
    664     ksResultsSortByTestCaseName         = 'ResultsSortByTestCaseName';
    665     ksResultsSortByFailureReason        = 'ResultsSortByFailureReason';
    666     kasResultsSortBy = {
    667         ksResultsSortByRunningAndStart,
    668         ksResultsSortByBuildRevision,
    669         ksResultsSortByTestBoxName,
    670         ksResultsSortByTestBoxOs,
    671         ksResultsSortByTestBoxOsVersion,
    672         ksResultsSortByTestBoxOsArch,
    673         ksResultsSortByTestBoxArch,
    674         ksResultsSortByTestBoxCpuVendor,
    675         ksResultsSortByTestBoxCpuName,
    676         ksResultsSortByTestBoxCpuRev,
    677         ksResultsSortByTestBoxCpuFeatures,
    678         ksResultsSortByTestCaseName,
    679         ksResultsSortByFailureReason,
    680     };
    681     ## Used by the WUI for generating the drop down.
    682     kaasResultsSortByTitles = (
    683         ( ksResultsSortByRunningAndStart,       'Running & Start TS' ),
    684         ( ksResultsSortByBuildRevision,         'Build Revision' ),
    685         ( ksResultsSortByTestBoxName,           'TestBox Name' ),
    686         ( ksResultsSortByTestBoxOs,             'O/S' ),
    687         ( ksResultsSortByTestBoxOsVersion,      'O/S Version' ),
    688         ( ksResultsSortByTestBoxOsArch,         'O/S & Architecture' ),
    689         ( ksResultsSortByTestBoxArch,           'Architecture' ),
    690         ( ksResultsSortByTestBoxCpuVendor,      'CPU Vendor' ),
    691         ( ksResultsSortByTestBoxCpuName,        'CPU Vendor & Name' ),
    692         ( ksResultsSortByTestBoxCpuRev,         'CPU Vendor & Revision' ),
    693         ( ksResultsSortByTestBoxCpuFeatures,    'CPU Features' ),
    694         ( ksResultsSortByTestCaseName,          'Test Case Name' ),
    695         ( ksResultsSortByFailureReason,         'Failure Reason' ),
    696     );
    697     ## @}
    698 
    699     ## Default sort by map.
    700     kdResultSortByMap = {
    701         ksResultsSortByRunningAndStart:  ('', None, None, ''),
    702         ksResultsSortByBuildRevision: (
    703             # Sorting tables.
    704             ', Builds',
    705             # Sorting table join(s).
    706             ' AND TestSets.idBuild    = Builds.idBuild'
    707             ' AND Builds.tsExpire    >= TestSets.tsCreated'
    708             ' AND Builds.tsEffective <= TestSets.tsCreated',
    709             # Start of ORDER BY statement.
    710             ' Builds.iRevision DESC',
    711             # Extra columns to fetch for the above ORDER BY to work in a SELECT DISTINCT statement.
    712             ''  ),
    713         ksResultsSortByTestBoxName: (
    714             ', TestBoxes',
    715             ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
    716             ' TestBoxes.sName DESC',
    717             '' ),
    718         ksResultsSortByTestBoxOsArch: (
    719             ', TestBoxes',
    720             ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
    721             ' TestBoxes.sOs, TestBoxes.sCpuArch',
    722             ''  ),
    723         ksResultsSortByTestBoxOs: (
    724             ', TestBoxes',
    725             ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
    726             ' TestBoxes.sOs',
    727             ''  ),
    728         ksResultsSortByTestBoxOsVersion: (
    729             ', TestBoxes',
    730             ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
    731             ' TestBoxes.sOs, TestBoxes.sOsVersion DESC',
    732             ''  ),
    733         ksResultsSortByTestBoxArch: (
    734             ', TestBoxes',
    735             ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
    736             ' TestBoxes.sCpuArch',
    737             ''  ),
    738         ksResultsSortByTestBoxCpuVendor: (
    739             ', TestBoxes',
    740             ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
    741             ' TestBoxes.sCpuVendor',
    742             ''  ),
    743         ksResultsSortByTestBoxCpuName: (
    744             ', TestBoxes',
    745             ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
    746             ' TestBoxes.sCpuVendor, TestBoxes.sCpuName',
    747             ''  ),
    748         ksResultsSortByTestBoxCpuRev: (
    749             ', TestBoxes',
    750             ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
    751             ' TestBoxes.sCpuVendor, TestBoxes.lCpuRevision DESC',
    752             ', TestBoxes.lCpuRevision'  ),
    753         ksResultsSortByTestBoxCpuFeatures: (
    754             ', TestBoxes',
    755             ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
    756             ' TestBoxes.fCpuHwVirt DESC, TestBoxes.fCpuNestedPaging DESC, TestBoxes.fCpu64BitGuest DESC, TestBoxes.cCpus DESC',
    757             ', TestBoxes.cCpus' ),
    758         ksResultsSortByTestCaseName: (
    759             ', TestCases',
    760             ' AND TestSets.idGenTestCase = TestCases.idGenTestCase',
    761             ' TestCases.sName',
    762             ''  ),
    763         ksResultsSortByFailureReason: (
    764             '', '',
    765             'sSortByFailureReason ASC',
    766             ', FailureReasons.sShort AS sSortByFailureReason' ),
    767     };
    768 
    769     kdResultGroupingMap = {
    770         ksResultsGroupingTypeNone: (
    771             # Grouping tables;                # Grouping field;          # Grouping where addition.  # Sort by overrides.
    772             '',                                None,                      None,                       {}
    773         ),
    774         ksResultsGroupingTypeTestGroup:  ('', 'TestSets.idTestGroup',     None,                {}),
    775         ksResultsGroupingTypeTestBox:    ('', 'TestSets.idTestBox',       None,                {}),
    776         ksResultsGroupingTypeTestCase:   ('', 'TestSets.idTestCase',      None,                {}),
    777         ksResultsGroupingTypeBuildRev: (
    778             ', Builds',
    779             'Builds.iRevision',
    780             ' AND Builds.idBuild      = TestSets.idBuild'
    781             ' AND Builds.tsExpire     > TestSets.tsCreated'
    782             ' AND Builds.tsEffective <= TestSets.tsCreated',
    783             { ksResultsSortByBuildRevision: ( '', None,  ' Builds.iRevision DESC' ), }
    784         ),
    785         ksResultsGroupingTypeSchedGroup: (
    786             ', TestBoxes',
    787             'TestBoxes.idSchedGroup',
    788             ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox',
    789             { ksResultsSortByTestBoxName:       ( '', None, ' TestBoxes.sName DESC', '' ),
    790               ksResultsSortByTestBoxOsArch:     ( '', None, ' TestBoxes.sOs, TestBoxes.sCpuArch', ''  ),
    791               ksResultsSortByTestBoxOs:         ( '', None,  ' TestBoxes.sOs', ''  ),
    792               ksResultsSortByTestBoxOsVersion:  ( '', None, ' TestBoxes.sOs, TestBoxes.sOsVersion DESC', ''  ),
    793               ksResultsSortByTestBoxArch:       ( '', None, ' TestBoxes.sCpuArch', ''  ),
    794               ksResultsSortByTestBoxCpuVendor:  ( '', None, ' TestBoxes.sCpuVendor', ''  ),
    795               ksResultsSortByTestBoxCpuName:    ( '', None, ' TestBoxes.sCpuVendor, TestBoxes.sCpuName', ''  ),
    796               ksResultsSortByTestBoxCpuRev: (
    797                   '', None,  ' TestBoxes.sCpuVendor, TestBoxes.lCpuRevision DESC', ', TestBoxes.lCpuRevision'  ),
    798               ksResultsSortByTestBoxCpuFeatures: (
    799                   ' TestBoxes.fCpuHwVirt DESC, TestBoxes.fCpuNestedPaging DESC, TestBoxes.fCpu64BitGuest DESC, '
    800                   + 'TestBoxes.cCpus DESC',
    801                   ', TestBoxes.cCpus' ), }
    802         ),
    803     };
    804 
    805 
    806     def __init__(self, oDb):
    807         ModelLogicBase.__init__(self, oDb)
    808         self.oFailureReasonLogic = None;
    809         self.oUserAccountLogic   = None;
    810 
    811     def _getTimePeriodQueryPart(self, tsNow, sInterval, sExtraIndent = ''):
    812         """
    813         Get part of SQL query responsible for SELECT data within
    814         specified period of time.
    815         """
    816         assert sInterval is not None; # too many rows.
    817 
    818         cMonthsMourningPeriod = 2;  # Stop reminding everyone about testboxes after 2 months.  (May also speed up the query.)
    819         if tsNow is None:
    820             sRet =        '(TestSets.tsDone IS NULL OR TestSets.tsDone >= (CURRENT_TIMESTAMP - \'%s\'::interval))\n' \
    821                    '%s   AND TestSets.tsCreated >= (CURRENT_TIMESTAMP  - \'%s\'::interval - \'%u months\'::interval)\n' \
    822                  % ( sInterval,
    823                      sExtraIndent, sInterval, cMonthsMourningPeriod);
    824         else:
    825             sTsNow = '\'%s\'::TIMESTAMP' % (tsNow,); # It's actually a string already. duh.
    826             sRet =        'TestSets.tsCreated <= %s\n' \
    827                    '%s   AND TestSets.tsCreated >= (%s  - \'%s\'::interval - \'%u months\'::interval)\n' \
    828                    '%s   AND (TestSets.tsDone IS NULL OR TestSets.tsDone >= (%s - \'%s\'::interval))\n' \
    829                  % ( sTsNow,
    830                      sExtraIndent, sTsNow, sInterval, cMonthsMourningPeriod,
    831                      sExtraIndent, sTsNow, sInterval );
    832         return sRet
    833 
    834     def fetchResultsForListing(self, iStart, cMaxRows, tsNow, sInterval, enmResultSortBy, # pylint: disable=R0913
    835                                enmResultsGroupingType, iResultsGroupingValue, fOnlyFailures, fOnlyNeedingReason):
    836         """
    837         Fetches TestResults table content.
    838 
    839         If @param enmResultsGroupingType and @param iResultsGroupingValue
    840         are not None, then resulting (returned) list contains only records
    841         that match specified @param enmResultsGroupingType.
    842 
    843         If @param enmResultsGroupingType is None, then
    844         @param iResultsGroupingValue is ignored.
    845 
    846         Returns an array (list) of TestResultData items, empty list if none.
    847         Raises exception on error.
    848         """
    849 
    850         #
    851         # Get SQL query parameters
    852         #
    853         if enmResultsGroupingType is None or enmResultsGroupingType not in self.kdResultGroupingMap:
    854             raise TMExceptionBase('Unknown grouping type');
    855         if enmResultSortBy is None or enmResultSortBy not in self.kasResultsSortBy:
    856             raise TMExceptionBase('Unknown sorting');
    857         sGroupingTables, sGroupingField, sGroupingCondition, dSortingOverrides = self.kdResultGroupingMap[enmResultsGroupingType];
    858         if enmResultSortBy in dSortingOverrides:
    859             sSortingTables, sSortingWhere, sSortingOrderBy, sSortingColumns = dSortingOverrides[enmResultSortBy];
    860         else:
    861             sSortingTables, sSortingWhere, sSortingOrderBy, sSortingColumns = self.kdResultSortByMap[enmResultSortBy];
    862 
    863         #
    864         # Construct the query.
    865         #
    866         sQuery  = 'SELECT DISTINCT TestSets.idTestSet,\n' \
    867                   '       BuildCategories.idBuildCategory,\n' \
    868                   '       BuildCategories.sProduct,\n' \
    869                   '       BuildCategories.sRepository,\n' \
    870                   '       BuildCategories.sBranch,\n' \
    871                   '       BuildCategories.sType,\n' \
    872                   '       Builds.idBuild,\n' \
    873                   '       Builds.sVersion,\n' \
    874                   '       Builds.iRevision,\n' \
    875                   '       TestBoxes.sOs,\n' \
    876                   '       TestBoxes.sOsVersion,\n' \
    877                   '       TestBoxes.sCpuArch,\n' \
    878                   '       TestBoxes.sCpuVendor,\n' \
    879                   '       TestBoxes.sCpuName,\n' \
    880                   '       TestBoxes.cCpus,\n' \
    881                   '       TestBoxes.fCpuHwVirt,\n' \
    882                   '       TestBoxes.fCpuNestedPaging,\n' \
    883                   '       TestBoxes.fCpu64BitGuest,\n' \
    884                   '       TestBoxes.idTestBox,\n' \
    885                   '       TestBoxes.sName,\n' \
    886                   '       TestResults.tsCreated,\n' \
    887                   '       COALESCE(TestResults.tsElapsed, CURRENT_TIMESTAMP - TestResults.tsCreated),\n' \
    888                   '       TestSets.enmStatus,\n' \
    889                   '       TestResults.cErrors,\n' \
    890                   '       TestCases.idTestCase,\n' \
    891                   '       TestCases.sName,\n' \
    892                   '       TestCases.sBaseCmd,\n' \
    893                   '       TestCaseArgs.sArgs,\n' \
    894                   '       TestCaseArgs.sSubName,\n' \
    895                   '       TestSuiteBits.idBuild AS idBuildTestSuite,\n' \
    896                   '       TestSuiteBits.iRevision AS iRevisionTestSuite,\n' \
    897                   '       TestResultFailures.idFailureReason as idFailureReason,\n' \
    898                   '       TestResultFailures.uidAuthor as uidFailureReasonAssigner,\n' \
    899                   '       TestResultFailures.tsEffective as tsFailureReasonAssigned,\n' \
    900                   '       TestResultFailures.sComment as sFailureReasonComment,\n' \
    901                   '       (TestSets.tsDone IS NULL) SortRunningFirst' + sSortingColumns + '\n' \
    902                   'FROM   BuildCategories,\n' \
    903                   '       Builds,\n' \
    904                   '       TestBoxes,\n' \
    905                   '       TestResults\n' \
    906                   '       LEFT OUTER JOIN TestResultFailures\n' \
    907                   '                    ON     TestResults.idTestResult    = TestResultFailures.idTestResult\n' \
    908                   '                       AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP';
    909         if sSortingOrderBy is not None and sSortingOrderBy.find('FailureReason') >= 0:
    910             sQuery += '\n' \
    911                       '       LEFT OUTER JOIN FailureReasons\n' \
    912                       '                    ON     TestResultFailures.idFailureReason = FailureReasons.idFailureReason\n' \
    913                       '                       AND FailureReasons.tsExpire            = \'infinity\'::TIMESTAMP';
    914         sQuery += ',\n'\
    915                   '       TestCases,\n' \
    916                   '       TestCaseArgs,\n' \
    917                   '       (  SELECT TestSets.idTestSet AS idTestSet,\n' \
    918                   '                 TestSets.tsDone AS tsDone,\n' \
    919                   '                 TestSets.tsCreated AS tsCreated,\n' \
    920                   '                 TestSets.enmStatus AS enmStatus,\n' \
    921                   '                 TestSets.idBuild AS idBuild,\n' \
    922                   '                 TestSets.idBuildTestSuite AS idBuildTestSuite,\n' \
    923                   '                 TestSets.idGenTestBox AS idGenTestBox,\n' \
    924                   '                 TestSets.idGenTestCase AS idGenTestCase,\n' \
    925                   '                 TestSets.idGenTestCaseArgs AS idGenTestCaseArgs\n' \
    926                   '          FROM  TestSets';
    927         if fOnlyNeedingReason:
    928             sQuery += '\n' \
    929                       '          LEFT OUTER JOIN TestResultFailures\n' \
    930                       '                       ON     TestSets.idTestSet          = TestResultFailures.idTestSet\n' \
    931                       '                          AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP';
    932         sQuery += sGroupingTables.replace(',', ',\n                ');
    933         sQuery += sSortingTables.replace( ',', ',\n                ');
    934         sQuery += '\n' \
    935                   '          WHERE ' + self._getTimePeriodQueryPart(tsNow, sInterval, '         ');
    936         if fOnlyFailures or fOnlyNeedingReason:
    937             sQuery += '            AND TestSets.enmStatus != \'success\'::TestStatus_T\n' \
    938                       '            AND TestSets.enmStatus != \'running\'::TestStatus_T\n';
    939         if fOnlyNeedingReason:
    940             sQuery += '            AND TestResultFailures.idTestSet IS NULL\n';
    941         if sGroupingField is not None:
    942             sQuery += '            AND %s = %d\n' % (sGroupingField, iResultsGroupingValue,);
    943         if sGroupingCondition is not None:
    944             sQuery += sGroupingCondition.replace(' AND ', '            AND ');
    945         if sSortingWhere is not None:
    946             sQuery += sSortingWhere.replace(' AND ', '            AND ');
    947         sQuery += '          ORDER BY ';
    948         if sSortingOrderBy is not None and sSortingOrderBy.find('FailureReason') < 0:
    949             sQuery += sSortingOrderBy + ',\n                ';
    950         sQuery += '(TestSets.tsDone IS NULL) DESC, TestSets.idTestSet DESC\n' \
    951                   '          LIMIT %s OFFSET %s\n' % (cMaxRows, iStart,);
    952 
    953         sQuery += '       ) AS TestSets\n' \
    954                   '       LEFT OUTER JOIN Builds AS TestSuiteBits\n' \
    955                   '                    ON TestSets.idBuildTestSuite = TestSuiteBits.idBuild\n' \
    956                   'WHERE  TestSets.idTestSet         = TestResults.idTestSet\n' \
    957                   '   AND TestResults.idTestResultParent is NULL\n' \
    958                   '   AND TestSets.idBuild           = Builds.idBuild\n' \
    959                   '   AND Builds.tsExpire            > TestSets.tsCreated\n' \
    960                   '   AND Builds.tsEffective        <= TestSets.tsCreated\n' \
    961                   '   AND Builds.idBuildCategory     = BuildCategories.idBuildCategory\n' \
    962                   '   AND TestSets.idGenTestBox      = TestBoxes.idGenTestBox\n' \
    963                   '   AND TestSets.idGenTestCase     = TestCases.idGenTestCase\n' \
    964                   '   AND TestSets.idGenTestCaseArgs = TestCaseArgs.idGenTestCaseArgs\n' \
    965                   'ORDER BY ';
    966         if sSortingOrderBy is not None:
    967             sQuery += sSortingOrderBy + ',\n       ';
    968         sQuery += '(TestSets.tsDone IS NULL) DESC, TestSets.idTestSet DESC\n';
    969 
    970         #
    971         # Execute the query and return the wrapped results.
    972         #
    973         self._oDb.execute(sQuery);
    974 
    975         if self.oFailureReasonLogic is None:
    976             self.oFailureReasonLogic = FailureReasonLogic(self._oDb);
    977         if self.oUserAccountLogic is None:
    978             self.oUserAccountLogic = UserAccountLogic(self._oDb);
    979 
    980         aoRows = [];
    981         for aoRow in self._oDb.fetchAll():
    982             aoRows.append(TestResultListingData().initFromDbRowEx(aoRow, self.oFailureReasonLogic, self.oUserAccountLogic));
    983 
    984         return aoRows
    985 
    986     def getEntriesCount(self, tsNow, sInterval, enmResultsGroupingType, iResultsGroupingValue, fOnlyFailures, fOnlyNeedingReason):
    987         """
    988         Get number of table records.
    989 
    990         If @param enmResultsGroupingType and @param iResultsGroupingValue
    991         are not None, then we count only only those records
    992         that match specified @param enmResultsGroupingType.
    993 
    994         If @param enmResultsGroupingType is None, then
    995         @param iResultsGroupingValue is ignored.
    996         """
    997 
    998         #
    999         # Get SQL query parameters
    1000         #
    1001         if enmResultsGroupingType is None:
    1002             raise TMExceptionBase('Unknown grouping type')
    1003 
    1004         if enmResultsGroupingType not in self.kdResultGroupingMap:
    1005             raise TMExceptionBase('Unknown grouping type')
    1006         sGroupingTables, sGroupingField, sGroupingCondition, _  = self.kdResultGroupingMap[enmResultsGroupingType];
    1007 
    1008         #
    1009         # Construct the query.
    1010         #
    1011         sQuery = 'SELECT COUNT(TestSets.idTestSet)\n' \
    1012                  'FROM   TestSets';
    1013         if fOnlyNeedingReason:
    1014             sQuery += '\n' \
    1015                       '       LEFT OUTER JOIN TestResultFailures\n' \
    1016                       '                    ON     TestSets.idTestSet          = TestResultFailures.idTestSet\n' \
    1017                       '                       AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP';
    1018         sQuery += sGroupingTables.replace(',', ',\n       ');
    1019         sQuery += '\n' \
    1020                   'WHERE  ' + self._getTimePeriodQueryPart(tsNow, sInterval);
    1021         if fOnlyFailures or fOnlyNeedingReason:
    1022             sQuery += '   AND TestSets.enmStatus != \'success\'::TestStatus_T\n' \
    1023                       '   AND TestSets.enmStatus != \'running\'::TestStatus_T\n';
    1024         if fOnlyNeedingReason:
    1025             sQuery += '   AND TestResultFailures.idTestSet IS NULL\n';
    1026         if sGroupingField is not None:
    1027             sQuery += '   AND %s = %d\n' % (sGroupingField, iResultsGroupingValue,);
    1028         if sGroupingCondition is not None:
    1029             sQuery += sGroupingCondition.replace(' AND ', '   AND ');
    1030 
    1031         #
    1032         # Execute the query and return the result.
    1033         #
    1034         self._oDb.execute(sQuery)
    1035         return self._oDb.fetchOne()[0]
    1036 
    1037     def getTestGroups(self, tsNow, sPeriod):
    1038         """
    1039         Get list of uniq TestGroupData objects which
    1040         found in all test results.
    1041         """
    1042 
    1043         self._oDb.execute('SELECT DISTINCT TestGroups.*\n'
    1044                           'FROM   TestGroups, TestSets\n'
    1045                           'WHERE  TestSets.idTestGroup   =  TestGroups.idTestGroup\n'
    1046                           '   AND TestGroups.tsExpire    >  TestSets.tsCreated\n'
    1047                           '   AND TestGroups.tsEffective <= TestSets.tsCreated'
    1048                           '   AND ' + self._getTimePeriodQueryPart(tsNow, sPeriod))
    1049         aaoRows = self._oDb.fetchAll()
    1050         aoRet = []
    1051         for aoRow in aaoRows:
    1052             aoRet.append(TestGroupData().initFromDbRow(aoRow))
    1053         return aoRet
    1054 
    1055     def getBuilds(self, tsNow, sPeriod):
    1056         """
    1057         Get list of uniq BuildDataEx objects which
    1058         found in all test results.
    1059         """
    1060 
    1061         self._oDb.execute('SELECT DISTINCT Builds.*, BuildCategories.*\n'
    1062                           'FROM     Builds, BuildCategories, TestSets\n'
    1063                           'WHERE    TestSets.idBuild       =  Builds.idBuild\n'
    1064                           '     AND Builds.idBuildCategory =  BuildCategories.idBuildCategory\n'
    1065                           '     AND Builds.tsExpire        >  TestSets.tsCreated\n'
    1066                           '     AND Builds.tsEffective     <= TestSets.tsCreated'
    1067                           '     AND ' + self._getTimePeriodQueryPart(tsNow, sPeriod))
    1068         aaoRows = self._oDb.fetchAll()
    1069         aoRet = []
    1070         for aoRow in aaoRows:
    1071             aoRet.append(BuildDataEx().initFromDbRow(aoRow))
    1072         return aoRet
    1073 
    1074     def getTestBoxes(self, tsNow, sPeriod):
    1075         """
    1076         Get list of uniq TestBoxData objects which
    1077         found in all test results.
    1078         """
    1079 
    1080         self._oDb.execute('SELECT TestBoxes.*\n'
    1081                           'FROM   TestBoxes,\n'
    1082                           '       ( SELECT idTestBox         AS idTestBox,\n'
    1083                           '                MAX(idGenTestBox) AS idGenTestBox\n'
    1084                           '         FROM   TestSets\n'
    1085                           '         WHERE  ' + self._getTimePeriodQueryPart(tsNow, sPeriod, '        ') +
    1086                           '         GROUP BY idTestBox\n'
    1087                           '       ) AS TestBoxIDs\n'
    1088                           'WHERE  TestBoxes.idGenTestBox = TestBoxIDs.idGenTestBox\n'
    1089                           'ORDER BY TestBoxes.sName\n' );
    1090         aoRet = []
    1091         for aoRow in self._oDb.fetchAll():
    1092             aoRet.append(TestBoxData().initFromDbRow(aoRow));
    1093         return aoRet
    1094 
    1095     def getTestCases(self, tsNow, sPeriod):
    1096         """
    1097         Get a list of unique TestCaseData objects which is appears in the test
    1098         specified result period.
    1099         """
    1100 
    1101         self._oDb.execute('SELECT TestCases.*\n'
    1102                           'FROM   TestCases,\n'
    1103                           '       ( SELECT idTestCase         AS idTestCase,\n'
    1104                           '                MAX(idGenTestCase) AS idGenTestCase\n'
    1105                           '         FROM   TestSets\n'
    1106                           '         WHERE  ' + self._getTimePeriodQueryPart(tsNow, sPeriod, '        ') +
    1107                           '         GROUP BY idTestCase\n'
    1108                           '       ) AS TestCasesIDs\n'
    1109                           'WHERE  TestCases.idGenTestCase = TestCasesIDs.idGenTestCase\n'
    1110                           'ORDER BY TestCases.sName\n' );
    1111         aoRet = [];
    1112         for aoRow in self._oDb.fetchAll():
    1113             aoRet.append(TestCaseData().initFromDbRow(aoRow));
    1114         return aoRet
    1115 
    1116     def getSchedGroups(self, tsNow, sPeriod):
    1117         """
    1118         Get list of uniq SchedGroupData objects which
    1119         found in all test results.
    1120         """
    1121 
    1122         self._oDb.execute('SELECT SchedGroups.*\n'
    1123                           'FROM   SchedGroups,\n'
    1124                           '       ( SELECT TestBoxes.idSchedGroup  AS idSchedGroup,\n'
    1125                           '                MAX(TestSets.tsCreated) AS tsNow\n'
    1126                           '         FROM   TestSets,\n'
    1127                           '                TestBoxes\n'
    1128                           '         WHERE  TestSets.idGenTestBox = TestBoxes.idGenTestBox\n'
    1129                           '            AND ' + self._getTimePeriodQueryPart(tsNow, sPeriod, '         ') +
    1130                           '         GROUP BY idSchedGroup\n'
    1131                           '       ) AS SchedGroupIDs\n'
    1132                           'WHERE  SchedGroups.idSchedGroup = SchedGroupIDs.idSchedGroup\n'
    1133                           '   AND SchedGroups.tsExpire     > SchedGroupIDs.tsNow\n'
    1134                           '   AND SchedGroups.tsEffective <= SchedGroupIDs.tsNow\n'
    1135                           'ORDER BY SchedGroups.sName\n' );
    1136         aoRet = []
    1137         for aoRow in self._oDb.fetchAll():
    1138             aoRet.append(SchedGroupData().initFromDbRow(aoRow));
    1139         return aoRet
    1140 
    1141     def getById(self, idTestResult):
    1142         """
    1143         Get build record by its id
    1144         """
    1145         self._oDb.execute('SELECT *\n'
    1146                           'FROM   TestResults\n'
    1147                           'WHERE  idTestResult = %s\n',
    1148                           (idTestResult,))
    1149 
    1150         aRows = self._oDb.fetchAll()
    1151         if len(aRows) not in (0, 1):
    1152             raise TMTooManyRows('Found more than one test result with the same credentials. Database structure is corrupted.')
    1153         try:
    1154             return TestResultData().initFromDbRow(aRows[0])
    1155         except IndexError:
    1156             return None
    1157 
    1158 
    1159     #
    1160     # Details view and interface.
    1161     #
    1162 
    1163     def fetchResultTree(self, idTestSet, cMaxDepth = None):
    1164         """
    1165         Fetches the result tree for the given test set.
    1166 
    1167         Returns a tree of TestResultDataEx nodes.
    1168         Raises exception on invalid input and database issues.
    1169         """
    1170         # Depth first, i.e. just like the XML added them.
    1171         ## @todo this still isn't performing extremely well, consider optimizations.
    1172         sQuery = self._oDb.formatBindArgs(
    1173             'SELECT   TestResults.*,\n'
    1174             '         TestResultStrTab.sValue,\n'
    1175             '         EXISTS ( SELECT idTestResultValue\n'
    1176             '           FROM   TestResultValues\n'
    1177             '           WHERE  TestResultValues.idTestResult = TestResults.idTestResult ) AS fHasValues,\n'
    1178             '         EXISTS ( SELECT idTestResultMsg\n'
    1179             '           FROM   TestResultMsgs\n'
    1180             '           WHERE  TestResultMsgs.idTestResult   = TestResults.idTestResult ) AS fHasMsgs,\n'
    1181             '         EXISTS ( SELECT idTestResultFile\n'
    1182             '           FROM   TestResultFiles\n'
    1183             '           WHERE  TestResultFiles.idTestResult  = TestResults.idTestResult ) AS fHasFiles,\n'
    1184             '         EXISTS ( SELECT idTestResult\n'
    1185             '           FROM   TestResultFailures\n'
    1186             '           WHERE  TestResultFailures.idTestResult = TestResults.idTestResult ) AS fHasReasons\n'
    1187             'FROM     TestResults, TestResultStrTab\n'
    1188             'WHERE    TestResults.idTestSet = %s\n'
    1189             '     AND TestResults.idStrName = TestResultStrTab.idStr\n'
    1190             , ( idTestSet, ));
    1191         if cMaxDepth is not None:
    1192             sQuery += self._oDb.formatBindArgs('     AND TestResults.iNestingDepth <= %s\n', (cMaxDepth,));
    1193         sQuery += 'ORDER BY idTestResult ASC\n'
    1194 
    1195         self._oDb.execute(sQuery);
    1196         cRows = self._oDb.getRowCount();
    1197         if cRows > 65536:
    1198             raise TMTooManyRows('Too many rows returned for idTestSet=%d: %d' % (idTestSet, cRows,));
    1199 
    1200         aaoRows = self._oDb.fetchAll();
    1201         if len(aaoRows) == 0:
    1202             raise TMRowNotFound('No test results for idTestSet=%d.' % (idTestSet,));
    1203 
    1204         # Set up the root node first.
    1205         aoRow = aaoRows[0];
    1206         oRoot = TestResultDataEx().initFromDbRow(aoRow);
    1207         if oRoot.idTestResultParent is not None:
    1208             raise self._oDb.integrityException('The root TestResult (#%s) has a parent (#%s)!'
    1209                                                % (oRoot.idTestResult, oRoot.idTestResultParent));
    1210         self._fetchResultTreeNodeExtras(oRoot, aoRow[-4], aoRow[-3], aoRow[-2], aoRow[-1]);
    1211 
    1212         # The chilren (if any).
    1213         dLookup = { oRoot.idTestResult: oRoot };
    1214         oParent = oRoot;
    1215         for iRow in range(1, len(aaoRows)):
    1216             aoRow = aaoRows[iRow];
    1217             oCur = TestResultDataEx().initFromDbRow(aoRow);
    1218             self._fetchResultTreeNodeExtras(oCur, aoRow[-4], aoRow[-3], aoRow[-2], aoRow[-1]);
    1219 
    1220             # Figure out and vet the parent.
    1221             if oParent.idTestResult != oCur.idTestResultParent:
    1222                 oParent = dLookup.get(oCur.idTestResultParent, None);
    1223                 if oParent is None:
    1224                     raise self._oDb.integrityException('TestResult #%d is orphaned from its parent #%s.'
    1225                                                        % (oCur.idTestResult, oCur.idTestResultParent,));
    1226             if oParent.iNestingDepth + 1 != oCur.iNestingDepth:
    1227                 raise self._oDb.integrityException('TestResult #%d has incorrect nesting depth (%d instead of %d)'
    1228                                                    % (oCur.idTestResult, oCur.iNestingDepth, oParent.iNestingDepth + 1,));
    1229 
    1230             # Link it up.
    1231             oCur.oParent = oParent;
    1232             oParent.aoChildren.append(oCur);
    1233             dLookup[oCur.idTestResult] = oCur;
    1234 
    1235         return (oRoot, dLookup);
    1236 
    1237     def _fetchResultTreeNodeExtras(self, oCurNode, fHasValues, fHasMsgs, fHasFiles, fHasReasons):
    1238         """
    1239         fetchResultTree worker that fetches values, message and files for the
    1240         specified node.
    1241         """
    1242         assert(oCurNode.aoValues  == []);
    1243         assert(oCurNode.aoMsgs    == []);
    1244         assert(oCurNode.aoFiles   == []);
    1245         assert(oCurNode.oReason is None);
    1246 
    1247         if fHasValues:
    1248             self._oDb.execute('SELECT   TestResultValues.*,\n'
    1249                               '         TestResultStrTab.sValue\n'
    1250                               'FROM     TestResultValues, TestResultStrTab\n'
    1251                               'WHERE    TestResultValues.idTestResult = %s\n'
    1252                               '     AND TestResultValues.idStrName    = TestResultStrTab.idStr\n'
    1253                               'ORDER BY idTestResultValue ASC\n'
    1254                               , ( oCurNode.idTestResult, ));
    1255             for aoRow in self._oDb.fetchAll():
    1256                 oCurNode.aoValues.append(TestResultValueDataEx().initFromDbRow(aoRow));
    1257 
    1258         if fHasMsgs:
    1259             self._oDb.execute('SELECT   TestResultMsgs.*,\n'
    1260                               '         TestResultStrTab.sValue\n'
    1261                               'FROM     TestResultMsgs, TestResultStrTab\n'
    1262                               'WHERE    TestResultMsgs.idTestResult = %s\n'
    1263                               '     AND TestResultMsgs.idStrMsg     = TestResultStrTab.idStr\n'
    1264                               'ORDER BY idTestResultMsg ASC\n'
    1265                               , ( oCurNode.idTestResult, ));
    1266             for aoRow in self._oDb.fetchAll():
    1267                 oCurNode.aoMsgs.append(TestResultMsgDataEx().initFromDbRow(aoRow));
    1268 
    1269         if fHasFiles:
    1270             self._oDb.execute('SELECT   TestResultFiles.*,\n'
    1271                               '         StrTabFile.sValue AS sFile,\n'
    1272                               '         StrTabDesc.sValue AS sDescription,\n'
    1273                               '         StrTabKind.sValue AS sKind,\n'
    1274                               '         StrTabMime.sValue AS sMime\n'
    1275                               'FROM     TestResultFiles,\n'
    1276                               '         TestResultStrTab AS StrTabFile,\n'
    1277                               '         TestResultStrTab AS StrTabDesc,\n'
    1278                               '         TestResultStrTab AS StrTabKind,\n'
    1279                               '         TestResultStrTab AS StrTabMime\n'
    1280                               'WHERE    TestResultFiles.idTestResult     = %s\n'
    1281                               '     AND TestResultFiles.idStrFile        = StrTabFile.idStr\n'
    1282                               '     AND TestResultFiles.idStrDescription = StrTabDesc.idStr\n'
    1283                               '     AND TestResultFiles.idStrKind        = StrTabKind.idStr\n'
    1284                               '     AND TestResultFiles.idStrMime        = StrTabMime.idStr\n'
    1285                               'ORDER BY idTestResultFile ASC\n'
    1286                               , ( oCurNode.idTestResult, ));
    1287             for aoRow in self._oDb.fetchAll():
    1288                 oCurNode.aoFiles.append(TestResultFileDataEx().initFromDbRow(aoRow));
    1289 
    1290         if fHasReasons or True:
    1291             if self.oFailureReasonLogic is None:
    1292                 self.oFailureReasonLogic = FailureReasonLogic(self._oDb);
    1293             if self.oUserAccountLogic is None:
    1294                 self.oUserAccountLogic = UserAccountLogic(self._oDb);
    1295             self._oDb.execute('SELECT   *\n'
    1296                               'FROM     TestResultFailures\n'
    1297                               'WHERE    idTestResult = %s\n'
    1298                               '     AND tsExpire = \'infinity\'::TIMESTAMP\n'
    1299                               , ( oCurNode.idTestResult, ));
    1300             if self._oDb.getRowCount() > 0:
    1301                 oCurNode.oReason = TestResultFailureDataEx().initFromDbRowEx(self._oDb.fetchOne(), self.oFailureReasonLogic,
    1302                                                                              self.oUserAccountLogic);
    1303 
    1304         return True;
    1305 
    1306 
    1307 
    1308     #
    1309     # TestBoxController interface(s).
    1310     #
    1311 
    1312     def _inhumeTestResults(self, aoStack, idTestSet, sError):
    1313         """
    1314         The test produces too much output, kill and bury it.
    1315 
    1316         Note! We leave the test set open, only the test result records are
    1317               completed.  Thus, _getResultStack will return an empty stack and
    1318               cause XML processing to fail immediately, while we can still
    1319               record when it actually completed in the test set the normal way.
    1320         """
    1321         self._oDb.dprint('** _inhumeTestResults: idTestSet=%d\n%s' % (idTestSet, self._stringifyStack(aoStack),));
    1322 
    1323         #
    1324         # First add a message.
    1325         #
    1326         self._newFailureDetails(aoStack[0].idTestResult, sError, None);
    1327 
    1328         #
    1329         # The complete all open test results.
    1330         #
    1331         for oTestResult in aoStack:
    1332             oTestResult.cErrors += 1;
    1333             self._completeTestResults(oTestResult, None, TestResultData.ksTestStatus_Failure, oTestResult.cErrors);
    1334 
    1335         # A bit of paranoia.
    1336         self._oDb.execute('UPDATE TestResults\n'
    1337                           'SET    cErrors = cErrors + 1,\n'
    1338                           '       enmStatus = \'failure\'::TestStatus_T,\n'
    1339                           '       tsElapsed = CURRENT_TIMESTAMP - tsCreated\n'
    1340                           'WHERE  idTestSet = %s\n'
    1341                           '   AND enmStatus = \'running\'::TestStatus_T\n'
    1342                           , ( idTestSet, ));
    1343         self._oDb.commit();
    1344 
    1345         return None;
    1346 
    1347     def strTabString(self, sString, fCommit = False):
    1348         """
    1349         Gets the string table id for the given string, adding it if new.
    1350 
    1351         Note! A copy of this code is also in TestSetLogic.
    1352         """
    1353         ## @todo move this and make a stored procedure for it.
    1354         self._oDb.execute('SELECT   idStr\n'
    1355                           'FROM     TestResultStrTab\n'
    1356                           'WHERE    sValue = %s'
    1357                           , (sString,));
    1358         if self._oDb.getRowCount() == 0:
    1359             self._oDb.execute('INSERT INTO TestResultStrTab (sValue)\n'
    1360                               'VALUES   (%s)\n'
    1361                               'RETURNING idStr\n'
    1362                               , (sString,));
    1363             if fCommit:
    1364                 self._oDb.commit();
    1365         return self._oDb.fetchOne()[0];
    1366 
    1367     @staticmethod
    1368     def _stringifyStack(aoStack):
    1369         """Returns a string rep of the stack."""
    1370         sRet = '';
    1371         for i, _ in enumerate(aoStack):
    1372             sRet += 'aoStack[%d]=%s\n' % (i, aoStack[i]);
    1373         return sRet;
    1374 
    1375     def _getResultStack(self, idTestSet):
    1376         """
    1377         Gets the current stack of result sets.
    1378         """
    1379         self._oDb.execute('SELECT *\n'
    1380                           'FROM   TestResults\n'
    1381                           'WHERE  idTestSet = %s\n'
    1382                           '   AND enmStatus = \'running\'::TestStatus_T\n'
    1383                           'ORDER BY idTestResult DESC'
    1384                           , ( idTestSet, ));
    1385         aoStack = [];
    1386         for aoRow in self._oDb.fetchAll():
    1387             aoStack.append(TestResultData().initFromDbRow(aoRow));
    1388 
    1389         for i, _ in enumerate(aoStack):
    1390             assert aoStack[i].iNestingDepth == len(aoStack) - i - 1, self._stringifyStack(aoStack);
    1391 
    1392         return aoStack;
    1393 
    1394     def _newTestResult(self, idTestResultParent, idTestSet, iNestingDepth, tsCreated, sName, dCounts, fCommit = False):
    1395         """
    1396         Creates a new test result.
    1397         Returns the TestResultData object for the new record.
    1398         May raise exception on database error.
    1399         """
    1400         assert idTestResultParent is not None;
    1401         assert idTestResultParent > 1;
    1402 
    1403         #
    1404         # This isn't necessarily very efficient, but it's necessary to prevent
    1405         # a wild test or testbox from filling up the database.
    1406         #
    1407         sCountName = 'cTestResults';
    1408         if sCountName not in dCounts:
    1409             self._oDb.execute('SELECT   COUNT(idTestResult)\n'
    1410                               'FROM     TestResults\n'
    1411                               'WHERE    idTestSet = %s\n'
    1412                               , ( idTestSet,));
    1413             dCounts[sCountName] = self._oDb.fetchOne()[0];
    1414         dCounts[sCountName] += 1;
    1415         if dCounts[sCountName] > config.g_kcMaxTestResultsPerTS:
    1416             raise TestResultHangingOffence('Too many sub-tests in total!');
    1417 
    1418         sCountName = 'cTestResultsIn%d' % (idTestResultParent,);
    1419         if sCountName not in dCounts:
    1420             self._oDb.execute('SELECT   COUNT(idTestResult)\n'
    1421                               'FROM     TestResults\n'
    1422                               'WHERE    idTestResultParent = %s\n'
    1423                               , ( idTestResultParent,));
    1424             dCounts[sCountName] = self._oDb.fetchOne()[0];
    1425         dCounts[sCountName] += 1;
    1426         if dCounts[sCountName] > config.g_kcMaxTestResultsPerTR:
    1427             raise TestResultHangingOffence('Too many immediate sub-tests!');
    1428 
    1429         # This is also a hanging offence.
    1430         if iNestingDepth > config.g_kcMaxTestResultDepth:
    1431             raise TestResultHangingOffence('To deep sub-test nesting!');
    1432 
    1433         # Ditto.
    1434         if len(sName) > config.g_kcchMaxTestResultName:
    1435             raise TestResultHangingOffence('Test name is too long: %d chars - "%s"' % (len(sName), sName));
    1436 
    1437         #
    1438         # Within bounds, do the job.
    1439         #
    1440         idStrName = self.strTabString(sName, fCommit);
    1441         self._oDb.execute('INSERT INTO TestResults (\n'
    1442                           '         idTestResultParent,\n'
    1443                           '         idTestSet,\n'
    1444                           '         tsCreated,\n'
    1445                           '         idStrName,\n'
    1446                           '         iNestingDepth )\n'
    1447                           'VALUES (%s, %s, TIMESTAMP WITH TIME ZONE %s, %s, %s)\n'
    1448                           'RETURNING *\n'
    1449                           , ( idTestResultParent, idTestSet, tsCreated, idStrName, iNestingDepth) )
    1450         oData = TestResultData().initFromDbRow(self._oDb.fetchOne());
    1451 
    1452         self._oDb.maybeCommit(fCommit);
    1453         return oData;
    1454 
    1455     def _newTestValue(self, idTestResult, idTestSet, sName, lValue, sUnit, dCounts, tsCreated = None, fCommit = False):
    1456         """
    1457         Creates a test value.
    1458         May raise exception on database error.
    1459         """
    1460 
    1461         #
    1462         # Bounds checking.
    1463         #
    1464         sCountName = 'cTestValues';
    1465         if sCountName not in dCounts:
    1466             self._oDb.execute('SELECT   COUNT(idTestResultValue)\n'
    1467                               'FROM     TestResultValues, TestResults\n'
    1468                               'WHERE    TestResultValues.idTestResult = TestResults.idTestResult\n'
    1469                               '     AND TestResults.idTestSet = %s\n'
    1470                               , ( idTestSet,));
    1471             dCounts[sCountName] = self._oDb.fetchOne()[0];
    1472         dCounts[sCountName] += 1;
    1473         if dCounts[sCountName] > config.g_kcMaxTestValuesPerTS:
    1474             raise TestResultHangingOffence('Too many values in total!');
    1475 
    1476         sCountName = 'cTestValuesIn%d' % (idTestResult,);
    1477         if sCountName not in dCounts:
    1478             self._oDb.execute('SELECT   COUNT(idTestResultValue)\n'
    1479                               'FROM     TestResultValues\n'
    1480                               'WHERE    idTestResult = %s\n'
    1481                               , ( idTestResult,));
    1482             dCounts[sCountName] = self._oDb.fetchOne()[0];
    1483         dCounts[sCountName] += 1;
    1484         if dCounts[sCountName] > config.g_kcMaxTestValuesPerTR:
    1485             raise TestResultHangingOffence('Too many immediate values for one test result!');
    1486 
    1487         if len(sName) > config.g_kcchMaxTestValueName:
    1488             raise TestResultHangingOffence('Value name is too long: %d chars - "%s"' % (len(sName), sName));
    1489 
    1490         #
    1491         # Do the job.
    1492         #
    1493         iUnit = constants.valueunit.g_kdNameToConst.get(sUnit, constants.valueunit.NONE);
    1494 
    1495         idStrName = self.strTabString(sName, fCommit);
    1496         if tsCreated is None:
    1497             self._oDb.execute('INSERT INTO TestResultValues (\n'
    1498                               '         idTestResult,\n'
    1499                               '         idTestSet,\n'
    1500                               '         idStrName,\n'
    1501                               '         lValue,\n'
    1502                               '         iUnit)\n'
    1503                               'VALUES (  %s, %s, %s, %s, %s )\n'
    1504                               , ( idTestResult, idTestSet, idStrName, lValue, iUnit,) );
    1505         else:
    1506             self._oDb.execute('INSERT INTO TestResultValues (\n'
    1507                               '         idTestResult,\n'
    1508                               '         idTestSet,\n'
    1509                               '         tsCreated,\n'
    1510                               '         idStrName,\n'
    1511                               '         lValue,\n'
    1512                               '         iUnit)\n'
    1513                               'VALUES ( %s, %s, TIMESTAMP WITH TIME ZONE %s, %s, %s, %s )\n'
    1514                               , ( idTestResult, idTestSet, tsCreated, idStrName, lValue, iUnit,) );
    1515         self._oDb.maybeCommit(fCommit);
    1516         return True;
    1517 
    1518     def _newFailureDetails(self, idTestResult, sText, dCounts, tsCreated = None, fCommit = False):
    1519         """
    1520         Creates a record detailing cause of failure.
    1521         May raise exception on database error.
    1522         """
    1523 
    1524         #
    1525         # Overflow protection.
    1526         #
    1527         if dCounts is not None:
    1528             sCountName = 'cTestMsgsIn%d' % (idTestResult,);
    1529             if sCountName not in dCounts:
    1530                 self._oDb.execute('SELECT   COUNT(idTestResultMsg)\n'
    1531                                   'FROM     TestResultMsgs\n'
    1532                                   'WHERE    idTestResult = %s\n'
    1533                                   , ( idTestResult,));
    1534                 dCounts[sCountName] = self._oDb.fetchOne()[0];
    1535             dCounts[sCountName] += 1;
    1536             if dCounts[sCountName] > config.g_kcMaxTestMsgsPerTR:
    1537                 raise TestResultHangingOffence('Too many messages under for one test result!');
    1538 
    1539             if len(sText) > config.g_kcchMaxTestMsg:
    1540                 raise TestResultHangingOffence('Failure details message is too long: %d chars - "%s"' % (len(sText), sText));
    1541 
    1542         #
    1543         # Do the job.
    1544         #
    1545         idStrMsg = self.strTabString(sText, fCommit);
    1546         if tsCreated is None:
    1547             self._oDb.execute('INSERT INTO TestResultMsgs (\n'
    1548                               '         idTestResult,\n'
    1549                               '         idStrMsg,\n'
    1550                               '         enmLevel)\n'
    1551                               'VALUES ( %s, %s, %s)\n'
    1552                               , ( idTestResult, idStrMsg, 'failure',) );
    1553         else:
    1554             self._oDb.execute('INSERT INTO TestResultMsgs (\n'
    1555                               '         idTestResult,\n'
    1556                               '         tsCreated,\n'
    1557                               '         idStrMsg,\n'
    1558                               '         enmLevel)\n'
    1559                               'VALUES ( %s, TIMESTAMP WITH TIME ZONE %s, %s, %s)\n'
    1560                               , ( idTestResult, tsCreated, idStrMsg, 'failure',) );
    1561 
    1562         self._oDb.maybeCommit(fCommit);
    1563         return True;
    1564 
    1565 
    1566     def _completeTestResults(self, oTestResult, tsDone, enmStatus, cErrors = 0, fCommit = False):
    1567         """
    1568         Completes a test result.  Updates the oTestResult object.
    1569         May raise exception on database error.
    1570         """
    1571         self._oDb.dprint('** _completeTestResults: cErrors=%s tsDone=%s enmStatus=%s oTestResults=\n%s'
    1572                          % (cErrors, tsDone, enmStatus, oTestResult,));
    1573 
    1574         #
    1575         # Sanity check: No open sub tests (aoStack should make sure about this!).
    1576         #
    1577         self._oDb.execute('SELECT   COUNT(idTestResult)\n'
    1578                           'FROM     TestResults\n'
    1579                           'WHERE    idTestResultParent = %s\n'
    1580                           '     AND enmStatus = %s\n'
    1581                           , ( oTestResult.idTestResult, TestResultData.ksTestStatus_Running,));
    1582         cOpenSubTest = self._oDb.fetchOne()[0];
    1583         assert cOpenSubTest == 0, 'cOpenSubTest=%d - %s' % (cOpenSubTest, oTestResult,);
    1584         assert oTestResult.enmStatus == TestResultData.ksTestStatus_Running;
    1585 
    1586         #
    1587         # Make sure the reporter isn't lying about successes or error counts.
    1588         #
    1589         self._oDb.execute('SELECT   COALESCE(SUM(cErrors), 0)\n'
    1590                           'FROM     TestResults\n'
    1591                           'WHERE    idTestResultParent = %s\n'
    1592                           , ( oTestResult.idTestResult, ));
    1593         cMinErrors = self._oDb.fetchOne()[0] + oTestResult.cErrors;
    1594         if cErrors < cMinErrors:
    1595             cErrors = cMinErrors;
    1596         if cErrors > 0 and enmStatus == TestResultData.ksTestStatus_Success:
    1597             enmStatus = TestResultData.ksTestStatus_Failure
    1598 
    1599         #
    1600         # Do the update.
    1601         #
    1602         if tsDone is None:
    1603             self._oDb.execute('UPDATE   TestResults\n'
    1604                               'SET      cErrors = %s,\n'
    1605                               '         enmStatus = %s,\n'
    1606                               '         tsElapsed = CURRENT_TIMESTAMP - tsCreated\n'
    1607                               'WHERE    idTestResult = %s\n'
    1608                               'RETURNING tsElapsed'
    1609                               , ( cErrors, enmStatus, oTestResult.idTestResult,) );
    1610         else:
    1611             self._oDb.execute('UPDATE   TestResults\n'
    1612                               'SET      cErrors = %s,\n'
    1613                               '         enmStatus = %s,\n'
    1614                               '         tsElapsed = TIMESTAMP WITH TIME ZONE %s - tsCreated\n'
    1615                               'WHERE    idTestResult = %s\n'
    1616                               'RETURNING tsElapsed'
    1617                               , ( cErrors, enmStatus, tsDone, oTestResult.idTestResult,) );
    1618 
    1619         oTestResult.tsElapsed = self._oDb.fetchOne()[0];
    1620         oTestResult.enmStatus = enmStatus;
    1621         oTestResult.cErrors   = cErrors;
    1622 
    1623         self._oDb.maybeCommit(fCommit);
    1624         return None;
    1625 
    1626     def _doPopHint(self, aoStack, cStackEntries, dCounts):
    1627         """ Executes a PopHint. """
    1628         assert cStackEntries >= 0;
    1629         while len(aoStack) > cStackEntries:
    1630             if aoStack[0].enmStatus == TestResultData.ksTestStatus_Running:
    1631                 self._newFailureDetails(aoStack[0].idTestResult, 'XML error: Missing </Test>', dCounts);
    1632                 self._completeTestResults(aoStack[0], tsDone = None, cErrors = 1,
    1633                                           enmStatus = TestResultData.ksTestStatus_Failure, fCommit = True);
    1634             aoStack.pop(0);
    1635         return True;
    1636 
    1637 
    1638     @staticmethod
    1639     def _validateElement(sName, dAttribs, fClosed):
    1640         """
    1641         Validates an element and its attributes.
    1642         """
    1643 
    1644         #
    1645         # Validate attributes by name.
    1646         #
    1647 
    1648         # Validate integer attributes.
    1649         for sAttr in [ 'errors', 'testdepth' ]:
    1650             if sAttr in dAttribs:
    1651                 try:
    1652                     _ = int(dAttribs[sAttr]);
    1653                 except:
    1654                     return 'Element %s has an invalid %s attribute value: %s.' % (sName, sAttr, dAttribs[sAttr],);
    1655 
    1656         # Validate long attributes.
    1657         for sAttr in [ 'value', ]:
    1658             if sAttr in dAttribs:
    1659                 try:
    1660                     _ = long(dAttribs[sAttr]);  # pylint: disable=R0204
    1661                 except:
    1662                     return 'Element %s has an invalid %s attribute value: %s.' % (sName, sAttr, dAttribs[sAttr],);
    1663 
    1664         # Validate string attributes.
    1665         for sAttr in [ 'name', 'text' ]: # 'unit' can be zero length.
    1666             if sAttr in dAttribs and len(dAttribs[sAttr]) == 0:
    1667                 return 'Element %s has an empty %s attribute value.' % (sName, sAttr,);
    1668 
    1669         # Validate the timestamp attribute.
    1670         if 'timestamp' in dAttribs:
    1671             (dAttribs['timestamp'], sError) = ModelDataBase.validateTs(dAttribs['timestamp'], fAllowNull = False);
    1672             if sError is not None:
    1673                 return 'Element %s has an invalid timestamp ("%s"): %s' % (sName, dAttribs['timestamp'], sError,);
    1674 
    1675 
    1676         #
    1677         # Check that attributes that are required are present.
    1678         # We ignore extra attributes.
    1679         #
    1680         dElementAttribs = \
    1681         {
    1682             'Test':             [ 'timestamp', 'name', ],
    1683             'Value':            [ 'timestamp', 'name', 'unit', 'value', ],
    1684             'FailureDetails':   [ 'timestamp', 'text', ],
    1685             'Passed':           [ 'timestamp', ],
    1686             'Skipped':          [ 'timestamp', ],
    1687             'Failed':           [ 'timestamp', 'errors', ],
    1688             'TimedOut':         [ 'timestamp', 'errors', ],
    1689             'End':              [ 'timestamp', ],
    1690             'PushHint':         [ 'testdepth', ],
    1691             'PopHint':          [ 'testdepth', ],
    1692         };
    1693         if sName not in dElementAttribs:
    1694             return 'Unknown element "%s".' % (sName,);
    1695         for sAttr in dElementAttribs[sName]:
    1696             if sAttr not in dAttribs:
    1697                 return 'Element %s requires attribute "%s".' % (sName, sAttr);
    1698 
    1699         #
    1700         # Only the Test element can (and must) remain open.
    1701         #
    1702         if sName == 'Test' and fClosed:
    1703             return '<Test/> is not allowed.';
    1704         if sName != 'Test' and not fClosed:
    1705             return 'All elements except <Test> must be closed.';
    1706 
    1707         return None;
    1708 
    1709     @staticmethod
    1710     def _parseElement(sElement):
    1711         """
    1712         Parses an element.
    1713 
    1714         """
    1715         #
    1716         # Element level bits.
    1717         #
    1718         sName    = sElement.split()[0];
    1719         sElement = sElement[len(sName):];
    1720 
    1721         fClosed  = sElement[-1] == '/';
    1722         if fClosed:
    1723             sElement = sElement[:-1];
    1724 
    1725         #
    1726         # Attributes.
    1727         #
    1728         sError   = None;
    1729         dAttribs = {};
    1730         sElement = sElement.strip();
    1731         while len(sElement) > 0:
    1732             # Extract attribute name.
    1733             off = sElement.find('=');
    1734             if off < 0 or not sElement[:off].isalnum():
    1735                 sError = 'Attributes shall have alpha numberical names and have values.';
    1736                 break;
    1737             sAttr = sElement[:off];
    1738 
    1739             # Extract attribute value.
    1740             if off + 2 >= len(sElement) or sElement[off + 1] != '"':
    1741                 sError = 'Attribute (%s) value is missing or not in double quotes.' % (sAttr,);
    1742                 break;
    1743             off += 2;
    1744             offEndQuote = sElement.find('"', off);
    1745             if offEndQuote < 0:
    1746                 sError = 'Attribute (%s) value is missing end quotation mark.' % (sAttr,);
    1747                 break;
    1748             sValue = sElement[off:offEndQuote];
    1749 
    1750             # Check for duplicates.
    1751             if sAttr in dAttribs:
    1752                 sError = 'Attribute "%s" appears more than once.' % (sAttr,);
    1753                 break;
    1754 
    1755             # Unescape the value.
    1756             sValue = sValue.replace('&lt;',   '<');
    1757             sValue = sValue.replace('&gt;',   '>');
    1758             sValue = sValue.replace('&apos;', '\'');
    1759             sValue = sValue.replace('&quot;', '"');
    1760             sValue = sValue.replace('&#xA;',  '\n');
    1761             sValue = sValue.replace('&#xD;',  '\r');
    1762             sValue = sValue.replace('&amp;',  '&'); # last
    1763 
    1764             # Done.
    1765             dAttribs[sAttr] = sValue;
    1766 
    1767             # advance
    1768             sElement = sElement[offEndQuote + 1:];
    1769             sElement = sElement.lstrip();
    1770 
    1771         #
    1772         # Validate the element before we return.
    1773         #
    1774         if sError is None:
    1775             sError = TestResultLogic._validateElement(sName, dAttribs, fClosed);
    1776 
    1777         return (sName, dAttribs, sError)
    1778 
    1779     def _handleElement(self, sName, dAttribs, idTestSet, aoStack, aaiHints, dCounts):
    1780         """
    1781         Worker for processXmlStream that handles one element.
    1782 
    1783         Returns None on success, error string on bad XML or similar.
    1784         Raises exception on hanging offence and on database error.
    1785         """
    1786         if sName == 'Test':
    1787             iNestingDepth = aoStack[0].iNestingDepth + 1 if len(aoStack) > 0 else 0;
    1788             aoStack.insert(0, self._newTestResult(idTestResultParent = aoStack[0].idTestResult, idTestSet = idTestSet,
    1789                                                   tsCreated = dAttribs['timestamp'], sName = dAttribs['name'],
    1790                                                   iNestingDepth = iNestingDepth, dCounts = dCounts, fCommit = True) );
    1791 
    1792         elif sName == 'Value':
    1793             self._newTestValue(idTestResult = aoStack[0].idTestResult, idTestSet = idTestSet, tsCreated = dAttribs['timestamp'],
    1794                                sName = dAttribs['name'], sUnit = dAttribs['unit'], lValue = long(dAttribs['value']),
    1795                                dCounts = dCounts, fCommit = True);
    1796 
    1797         elif sName == 'FailureDetails':
    1798             self._newFailureDetails(idTestResult = aoStack[0].idTestResult, tsCreated = dAttribs['timestamp'],
    1799                                     sText = dAttribs['text'], dCounts = dCounts, fCommit = True);
    1800 
    1801         elif sName == 'Passed':
    1802             self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'],
    1803                                       enmStatus = TestResultData.ksTestStatus_Success, fCommit = True);
    1804 
    1805         elif sName == 'Skipped':
    1806             self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'],
    1807                                       enmStatus = TestResultData.ksTestStatus_Skipped, fCommit = True);
    1808 
    1809         elif sName == 'Failed':
    1810             self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'], cErrors = int(dAttribs['errors']),
    1811                                       enmStatus = TestResultData.ksTestStatus_Failure, fCommit = True);
    1812 
    1813         elif sName == 'TimedOut':
    1814             self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'], cErrors = int(dAttribs['errors']),
    1815                                       enmStatus = TestResultData.ksTestStatus_TimedOut, fCommit = True);
    1816 
    1817         elif sName == 'End':
    1818             self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'],
    1819                                       cErrors = int(dAttribs.get('errors', '1')),
    1820                                       enmStatus = TestResultData.ksTestStatus_Success, fCommit = True);
    1821 
    1822         elif sName == 'PushHint':
    1823             if len(aaiHints) > 1:
    1824                 return 'PushHint cannot be nested.'
    1825 
    1826             aaiHints.insert(0, [len(aoStack), int(dAttribs['testdepth'])]);
    1827 
    1828         elif sName == 'PopHint':
    1829             if len(aaiHints) < 1:
    1830                 return 'No hint to pop.'
    1831 
    1832             iDesiredTestDepth = int(dAttribs['testdepth']);
    1833             cStackEntries, iTestDepth = aaiHints.pop(0);
    1834             self._doPopHint(aoStack, cStackEntries, dCounts); # Fake the necessary '<End/></Test>' tags.
    1835             if iDesiredTestDepth != iTestDepth:
    1836                 return 'PopHint tag has different testdepth: %d, on stack %d.' % (iDesiredTestDepth, iTestDepth);
    1837         else:
    1838             return 'Unexpected element "%s".' % (sName,);
    1839         return None;
    1840 
    1841 
    1842     def processXmlStream(self, sXml, idTestSet):
    1843         """
    1844         Processes the "XML" stream section given in sXml.
    1845 
    1846         The sXml isn't a complete XML document, even should we save up all sXml
    1847         for a given set, they may not form a complete and well formed XML
    1848         document since the test may be aborted, abend or simply be buggy. We
    1849         therefore do our own parsing and treat the XML tags as commands more
    1850         than anything else.
    1851 
    1852         Returns (sError, fUnforgivable), where sError is None on success.
    1853         May raise database exception.
    1854         """
    1855         aoStack    = self._getResultStack(idTestSet); # [0] == top; [-1] == bottom.
    1856         if len(aoStack) == 0:
    1857             return ('No open results', True);
    1858         self._oDb.dprint('** processXmlStream len(aoStack)=%s' % (len(aoStack),));
    1859         #self._oDb.dprint('processXmlStream: %s' % (self._stringifyStack(aoStack),));
    1860         #self._oDb.dprint('processXmlStream: sXml=%s' % (sXml,));
    1861 
    1862         dCounts    = {};
    1863         aaiHints   = [];
    1864         sError     = None;
    1865 
    1866         fExpectCloseTest = False;
    1867         sXml = sXml.strip();
    1868         while len(sXml) > 0:
    1869             if sXml.startswith('</Test>'): # Only closing tag.
    1870                 offNext = len('</Test>');
    1871                 if len(aoStack) <= 1:
    1872                     sError = 'Trying to close the top test results.'
    1873                     break;
    1874                 # ASSUMES that we've just seen an <End/>, <Passed/>, <Failed/>,
    1875                 # <TimedOut/> or <Skipped/> tag earlier in this call!
    1876                 if aoStack[0].enmStatus == TestResultData.ksTestStatus_Running  or  not fExpectCloseTest:
    1877                     sError = 'Missing <End/>, <Passed/>, <Failed/>, <TimedOut/> or <Skipped/> tag.';
    1878                     break;
    1879                 aoStack.pop(0);
    1880                 fExpectCloseTest = False;
    1881 
    1882             elif fExpectCloseTest:
    1883                 sError = 'Expected </Test>.'
    1884                 break;
    1885 
    1886             elif   sXml.startswith('<?xml '):  # Ignore (included files).
    1887                 offNext = sXml.find('?>');
    1888                 if offNext < 0:
    1889                     sError = 'Unterminated <?xml ?> element.';
    1890                     break;
    1891                 offNext += 2;
    1892 
    1893             elif sXml[0] == '<':
    1894                 # Parse and check the tag.
    1895                 if not sXml[1].isalpha():
    1896                     sError = 'Malformed element.';
    1897                     break;
    1898                 offNext = sXml.find('>')
    1899                 if offNext < 0:
    1900                     sError = 'Unterminated element.';
    1901                     break;
    1902                 (sName, dAttribs, sError) = self._parseElement(sXml[1:offNext]);
    1903                 offNext += 1;
    1904                 if sError is not None:
    1905                     break;
    1906 
    1907                 # Handle it.
    1908                 try:
    1909                     sError = self._handleElement(sName, dAttribs, idTestSet, aoStack, aaiHints, dCounts);
    1910                 except TestResultHangingOffence as oXcpt:
    1911                     self._inhumeTestResults(aoStack, idTestSet, str(oXcpt));
    1912                     return (str(oXcpt), True);
    1913 
    1914 
    1915                 fExpectCloseTest = sName in [ 'End', 'Passed', 'Failed', 'TimedOut', 'Skipped', ];
    1916             else:
    1917                 sError = 'Unexpected content.';
    1918                 break;
    1919 
    1920             # Advance.
    1921             sXml = sXml[offNext:];
    1922             sXml = sXml.lstrip();
    1923 
    1924         #
    1925         # Post processing checks.
    1926         #
    1927         if sError is None and fExpectCloseTest:
    1928             sError = 'Expected </Test> before the end of the XML section.'
    1929         elif sError is None and len(aaiHints) > 0:
    1930             sError = 'Expected </PopHint> before the end of the XML section.'
    1931         if len(aaiHints) > 0:
    1932             self._doPopHint(aoStack, aaiHints[-1][0], dCounts);
    1933 
    1934         #
    1935         # Log the error.
    1936         #
    1937         if sError is not None:
    1938             SystemLogLogic(self._oDb).addEntry(SystemLogData.ksEvent_XmlResultMalformed,
    1939                                                'idTestSet=%s idTestResult=%s XML="%s" %s'
    1940                                                % ( idTestSet,
    1941                                                    aoStack[0].idTestResult if len(aoStack) > 0 else -1,
    1942                                                    sXml[:30 if len(sXml) >= 30 else len(sXml)],
    1943                                                    sError, ),
    1944                                                cHoursRepeat = 6, fCommit = True);
    1945         return (sError, False);
    1946241
    1947242
     
    2202497
    2203498# pylint: disable=C0111
    2204 class TestResultDataTestCase(ModelDataBaseTestCase):
     499class TestResultFailureDataTestCase(ModelDataBaseTestCase):
    2205500    def setUp(self):
    2206         self.aoSamples = [TestResultData(),];
    2207 
    2208 class TestResultValueDataTestCase(ModelDataBaseTestCase):
    2209     def setUp(self):
    2210         self.aoSamples = [TestResultValueData(),];
     501        self.aoSamples = [TestResultFailureData(),];
    2211502
    2212503if __name__ == '__main__':
  • trunk/src/VBox/ValidationKit/testmanager/core/testresults.py

    r61268 r61278  
    3535
    3636# Validation Kit imports.
    37 from common                         import constants;
    38 from testmanager                    import config;
    39 from testmanager.core.base          import ModelDataBase, ModelLogicBase, ModelDataBaseTestCase, TMExceptionBase, \
    40                                            TMTooManyRows, TMInvalidData, TMRowNotFound, TMRowAlreadyExists, \
    41                                            ChangeLogEntry, AttributeChangeEntry;
    42 from testmanager.core.testgroup     import TestGroupData;
    43 from testmanager.core.build         import BuildDataEx;
    44 from testmanager.core.failurereason import FailureReasonLogic, FailureReasonData;
    45 from testmanager.core.testbox       import TestBoxData;
    46 from testmanager.core.testcase      import TestCaseData;
    47 from testmanager.core.schedgroup    import SchedGroupData;
    48 from testmanager.core.systemlog     import SystemLogData, SystemLogLogic;
    49 from testmanager.core.useraccount   import UserAccountLogic;
     37from common                                 import constants;
     38from testmanager                            import config;
     39from testmanager.core.base                  import ModelDataBase, ModelLogicBase, ModelDataBaseTestCase, TMExceptionBase, \
     40                                                   TMTooManyRows, TMRowNotFound;
     41from testmanager.core.testgroup             import TestGroupData;
     42from testmanager.core.build                 import BuildDataEx;
     43from testmanager.core.failurereason         import FailureReasonLogic;
     44from testmanager.core.testbox               import TestBoxData;
     45from testmanager.core.testcase              import TestCaseData;
     46from testmanager.core.schedgroup            import SchedGroupData;
     47from testmanager.core.systemlog             import SystemLogData, SystemLogLogic;
     48from testmanager.core.testresultfailures    import TestResultFailureDataEx;
     49from testmanager.core.useraccount           import UserAccountLogic;
    5050
    5151
     
    428428        return self.sMime;
    429429
    430 
    431 class TestResultFailureData(ModelDataBase):
    432     """
    433     Test result failure reason data.
    434     """
    435 
    436     ksIdAttr                    = 'idTestResult';
    437     kfIdAttrIsForForeign        = True; # Modifies the 'add' validation.
    438 
    439     ksParam_idTestResult        = 'TestResultFailure_idTestResult';
    440     ksParam_tsEffective         = 'TestResultFailure_tsEffective';
    441     ksParam_tsExpire            = 'TestResultFailure_tsExpire';
    442     ksParam_uidAuthor           = 'TestResultFailure_uidAuthor';
    443     ksParam_idTestSet           = 'TestResultFailure_idTestSet';
    444     ksParam_idFailureReason     = 'TestResultFailure_idFailureReason';
    445     ksParam_sComment            = 'TestResultFailure_sComment';
    446 
    447     kasAllowNullAttributes      = ['tsEffective', 'tsExpire', 'uidAuthor', 'sComment', 'idTestSet' ];
    448 
    449     kcDbColumns                 = 7;
    450 
    451     def __init__(self):
    452         ModelDataBase.__init__(self)
    453         self.idTestResult       = None;
    454         self.tsEffective        = None;
    455         self.tsExpire           = None;
    456         self.uidAuthor          = None;
    457         self.idTestSet          = None;
    458         self.idFailureReason    = None;
    459         self.sComment           = None;
    460 
    461     def initFromDbRow(self, aoRow):
    462         """
    463         Reinitialize from a SELECT * FROM TestResultFailures.
    464         Return self. Raises exception if no row.
    465         """
    466         if aoRow is None:
    467             raise TMRowNotFound('Test result file record not found.')
    468 
    469         self.idTestResult       = aoRow[0];
    470         self.tsEffective        = aoRow[1];
    471         self.tsExpire           = aoRow[2];
    472         self.uidAuthor          = aoRow[3];
    473         self.idTestSet          = aoRow[4];
    474         self.idFailureReason    = aoRow[5];
    475         self.sComment           = aoRow[6];
    476         return self;
    477 
    478     def initFromDbWithId(self, oDb, idTestResult, tsNow = None, sPeriodBack = None):
    479         """
    480         Initialize the object from the database.
    481         """
    482         oDb.execute(self.formatSimpleNowAndPeriodQuery(oDb,
    483                                                        'SELECT *\n'
    484                                                        'FROM   TestResultFailures\n'
    485                                                        'WHERE  idTestResult = %s\n'
    486                                                        , ( idTestResult,), tsNow, sPeriodBack));
    487         aoRow = oDb.fetchOne()
    488         if aoRow is None:
    489             raise TMRowNotFound('idTestResult=%s not found (tsNow=%s, sPeriodBack=%s)' % (idTestResult, tsNow, sPeriodBack));
    490         assert len(aoRow) == self.kcDbColumns;
    491         return self.initFromDbRow(aoRow);
    492 
    493 
    494 class TestResultFailureDataEx(TestResultFailureData):
    495     """
    496     Extends TestResultFailureData by resolving reasons and user.
    497     """
    498 
    499     def __init__(self):
    500         TestResultFailureData.__init__(self);
    501         self.oFailureReason     = None;
    502         self.oAuthor            = None;
    503 
    504     def initFromDbRowEx(self, aoRow, oFailureReasonLogic, oUserAccountLogic):
    505         """
    506         Reinitialize from a query like this:
    507             SELECT   TestResultFiles.*,
    508                      StrTabFile.sValue AS sFile,
    509                      StrTabDesc.sValue AS sDescription
    510                      StrTabKind.sValue AS sKind,
    511                      StrTabMime.sValue AS sMime,
    512             FROM ...
    513 
    514         Return self. Raises exception if no row.
    515         """
    516         self.initFromDbRow(aoRow);
    517         self.oFailureReason = oFailureReasonLogic.cachedLookup(self.idFailureReason);
    518         self.oAuthor        = oUserAccountLogic.cachedLookup(self.uidAuthor);
    519         return self;
    520430
    521431
     
    19471857
    19481858
    1949 class TestResultFailureLogic(ModelLogicBase): # pylint: disable=R0903
    1950     """
    1951     Test result failure reason logic.
    1952     """
    1953 
    1954     def __init__(self, oDb):
    1955         ModelLogicBase.__init__(self, oDb)
    1956 
    1957     def fetchForChangeLog(self, idTestResult, iStart, cMaxRows, tsNow): # pylint: disable=R0914
    1958         """
    1959         Fetches change log entries for a failure reason.
    1960 
    1961         Returns an array of ChangeLogEntry instance and an indicator whether
    1962         there are more entries.
    1963         Raises exception on error.
    1964         """
    1965 
    1966         if tsNow is None:
    1967             tsNow = self._oDb.getCurrentTimestamp();
    1968 
    1969         # 1. Get a list of the changes from both TestResultFailures and assoicated
    1970         #    FailureReasons.  The latter is useful since the failure reason
    1971         #    description may evolve along side the invidiual failure analysis.
    1972         self._oDb.execute('( SELECT trf.tsEffective AS tsEffectiveChangeLog,\n'
    1973                           '         trf.uidAuthor   AS uidAuthorChangeLog,\n'
    1974                           '         trf.*,\n'
    1975                           '         fr.*\n'
    1976                           '  FROM   TestResultFailures trf,\n'
    1977                           '         FailureReasons fr\n'
    1978                           '  WHERE  trf.idTestResult = %s\n'
    1979                           '     AND trf.tsEffective <= %s\n'
    1980                           '     AND trf.idFailureReason = fr.idFailureReason\n'
    1981                           '     AND fr.tsEffective      <= trf.tsEffective\n'
    1982                           '     AND fr.tsExpire         >  trf.tsEffective\n'
    1983                           ')\n'
    1984                           'UNION\n'
    1985                           '( SELECT fr.tsEffective AS tsEffectiveChangeLog,\n'
    1986                           '         fr.uidAuthor   AS uidAuthorChangeLog,\n'
    1987                           '         trf.*,\n'
    1988                           '         fr.*\n'
    1989                           '  FROM   TestResultFailures trf,\n'
    1990                           '         FailureReasons fr\n'
    1991                           '  WHERE  trf.idTestResult    = %s\n'
    1992                           '     AND trf.tsEffective    <= %s\n'
    1993                           '     AND trf.idFailureReason = fr.idFailureReason\n'
    1994                           '     AND fr.tsEffective      > trf.tsEffective\n'
    1995                           '     AND fr.tsEffective      < trf.tsExpire\n'
    1996                           ')\n'
    1997                           'ORDER BY tsEffectiveChangeLog DESC\n'
    1998                           'LIMIT %s OFFSET %s\n'
    1999                           , ( idTestResult, tsNow, idTestResult, tsNow, cMaxRows + 1, iStart, ));
    2000 
    2001         aaoRows = [];
    2002         for aoChange in self._oDb.fetchAll():
    2003             oTrf = TestResultFailureDataEx().initFromDbRow(aoChange[2:]);
    2004             oFr  = FailureReasonData().initFromDbRow(aoChange[(2+TestResultFailureData.kcDbColumns):]);
    2005             oTrf.oFailureReason = oFr;
    2006             aaoRows.append([aoChange[0], aoChange[1], oTrf, oFr]);
    2007 
    2008         # 2. Calculate the changes.
    2009         oFailureCategoryLogic = None;
    2010         aoEntries = [];
    2011         for i in xrange(0, len(aaoRows) - 1):
    2012             aoNew = aaoRows[i];
    2013             aoOld = aaoRows[i + 1];
    2014 
    2015             aoChanges = [];
    2016             oNew = aoNew[2];
    2017             oOld = aoOld[2];
    2018             for sAttr in oNew.getDataAttributes():
    2019                 if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', 'oFailureReason', 'oAuthor' ]:
    2020                     oOldAttr = getattr(oOld, sAttr);
    2021                     oNewAttr = getattr(oNew, sAttr);
    2022                     if oOldAttr != oNewAttr:
    2023                         if sAttr == 'idFailureReason':
    2024                             oNewAttr = '%s (%s)' % (oNewAttr, oNew.oFailureReason.sShort, );
    2025                             oOldAttr = '%s (%s)' % (oOldAttr, oOld.oFailureReason.sShort, );
    2026                         aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr)));
    2027             if oOld.idFailureReason == oNew.idFailureReason:
    2028                 oNew = aoNew[3];
    2029                 oOld = aoOld[3];
    2030                 for sAttr in oNew.getDataAttributes():
    2031                     if sAttr not in [ 'tsEffective', 'tsExpire', 'uidAuthor', ]:
    2032                         oOldAttr = getattr(oOld, sAttr);
    2033                         oNewAttr = getattr(oNew, sAttr);
    2034                         if oOldAttr != oNewAttr:
    2035                             if sAttr == 'idFailureCategory':
    2036                                 if oFailureCategoryLogic is None:
    2037                                     from testmanager.core.failurecategory import FailureCategoryLogic;
    2038                                     oFailureCategoryLogic = FailureCategoryLogic(self._oDb);
    2039                                 oCat = oFailureCategoryLogic.cachedLookup(oNewAttr);
    2040                                 if oCat is not None:
    2041                                     oNewAttr = '%s (%s)' % (oNewAttr, oCat.sShort, );
    2042                                 oCat = oFailureCategoryLogic.cachedLookup(oOldAttr);
    2043                                 if oCat is not None:
    2044                                     oOldAttr = '%s (%s)' % (oOldAttr, oCat.sShort, );
    2045                             aoChanges.append(AttributeChangeEntry(sAttr, oNewAttr, oOldAttr, str(oNewAttr), str(oOldAttr)));
    2046 
    2047 
    2048             tsExpire    = aaoRows[i - 1][0] if i > 0 else aoNew[2].tsExpire;
    2049             aoEntries.append(ChangeLogEntry(aoNew[1], None, aoNew[0], tsExpire, aoNew[2], aoOld[2], aoChanges));
    2050 
    2051         # If we're at the end of the log, add the initial entry.
    2052         if len(aaoRows) <= cMaxRows and len(aaoRows) > 0:
    2053             aoNew    = aaoRows[-1];
    2054             tsExpire = aaoRows[-1 - 1][0] if len(aaoRows) > 1 else aoNew[2].tsExpire;
    2055             aoEntries.append(ChangeLogEntry(aoNew[1], None, aoNew[0], tsExpire, aoNew[2], None, []));
    2056 
    2057         return (UserAccountLogic(self._oDb).resolveChangeLogAuthors(aoEntries), len(aaoRows) > cMaxRows);
    2058 
    2059 
    2060     def getById(self, idTestResult):
    2061         """Get Test result failure reason data by idTestResult"""
    2062 
    2063         self._oDb.execute('SELECT   *\n'
    2064                           'FROM     TestResultFailures\n'
    2065                           'WHERE    tsExpire   = \'infinity\'::timestamp\n'
    2066                           '  AND    idTestResult = %s;', (idTestResult,))
    2067         aRows = self._oDb.fetchAll()
    2068         if len(aRows) not in (0, 1):
    2069             raise self._oDb.integrityException(
    2070                 'Found more than one failure reasons with the same credentials. Database structure is corrupted.')
    2071         try:
    2072             return TestResultFailureData().initFromDbRow(aRows[0])
    2073         except IndexError:
    2074             return None
    2075 
    2076     def addEntry(self, oData, uidAuthor, fCommit = False):
    2077         """
    2078         Add a test result failure reason record.
    2079         """
    2080 
    2081         #
    2082         # Validate inputs and read in the old(/current) data.
    2083         #
    2084         assert isinstance(oData, TestResultFailureData);
    2085         dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_AddForeignId);
    2086         if len(dErrors) > 0:
    2087             raise TMInvalidData('editEntry invalid input: %s' % (dErrors,));
    2088 
    2089         # Check if it exist first (we're adding, not editing, collisions not allowed).
    2090         oOldData = self.getById(oData.idTestResult);
    2091         if oOldData is not None:
    2092             raise TMRowAlreadyExists('TestResult %d already have a failure reason associated with it:'
    2093                                      '%s\n'
    2094                                      'Perhaps someone else beat you to it? Or did you try resubmit?'
    2095                                      % (oData.idTestResult, oOldData));
    2096         oData = self._resolveSetTestIdIfMissing(oData);
    2097 
    2098         #
    2099         # Add record.
    2100         #
    2101         self._readdEntry(uidAuthor, oData);
    2102         self._oDb.maybeCommit(fCommit);
    2103         return True;
    2104 
    2105     def editEntry(self, oData, uidAuthor, fCommit = False):
    2106         """
    2107         Modifies a test result failure reason.
    2108         """
    2109 
    2110         #
    2111         # Validate inputs and read in the old(/current) data.
    2112         #
    2113         assert isinstance(oData, TestResultFailureData);
    2114         dErrors = oData.validateAndConvert(self._oDb, oData.ksValidateFor_Edit);
    2115         if len(dErrors) > 0:
    2116             raise TMInvalidData('editEntry invalid input: %s' % (dErrors,));
    2117 
    2118         oOldData = self.getById(oData.idTestResult)
    2119         oData.idTestSet = oOldData.idTestSet;
    2120 
    2121         #
    2122         # Update the data that needs updating.
    2123         #
    2124         if not oData.isEqualEx(oOldData, [ 'tsEffective', 'tsExpire', 'uidAuthor', ]):
    2125             self._historizeEntry(oData.idTestResult);
    2126             self._readdEntry(uidAuthor, oData);
    2127         self._oDb.maybeCommit(fCommit);
    2128         return True;
    2129 
    2130 
    2131     def removeEntry(self, uidAuthor, idTestResult, fCascade = False, fCommit = False):
    2132         """
    2133         Deletes a test result failure reason.
    2134         """
    2135         _ = fCascade; # Not applicable.
    2136 
    2137         oData = self.getById(idTestResult)
    2138         (tsCur, tsCurMinusOne) = self._oDb.getCurrentTimestamps();
    2139         if oData.tsEffective != tsCur and oData.tsEffective != tsCurMinusOne:
    2140             self._historizeEntry(idTestResult, tsCurMinusOne);
    2141             self._readdEntry(uidAuthor, oData, tsCurMinusOne);
    2142             self._historizeEntry(idTestResult);
    2143         self._oDb.execute('UPDATE   TestResultFailures\n'
    2144                           'SET      tsExpire       = CURRENT_TIMESTAMP\n'
    2145                           'WHERE    idTestResult   = %s\n'
    2146                           '     AND tsExpire       = \'infinity\'::TIMESTAMP\n'
    2147                           , (idTestResult,));
    2148         self._oDb.maybeCommit(fCommit);
    2149         return True;
    2150 
    2151     #
    2152     # Helpers.
    2153     #
    2154 
    2155     def _readdEntry(self, uidAuthor, oData, tsEffective = None):
    2156         """
    2157         Re-adds the TestResultFailure entry. Used by addEntry, editEntry and removeEntry.
    2158         """
    2159         if tsEffective is None:
    2160             tsEffective = self._oDb.getCurrentTimestamp();
    2161         self._oDb.execute('INSERT INTO TestResultFailures (\n'
    2162                           '         uidAuthor,\n'
    2163                           '         tsEffective,\n'
    2164                           '         idTestResult,\n'
    2165                           '         idTestSet,\n'
    2166                           '         idFailureReason,\n'
    2167                           '         sComment)\n'
    2168                           'VALUES (%s, %s, %s, %s, %s, %s)\n'
    2169                           , ( uidAuthor,
    2170                               tsEffective,
    2171                               oData.idTestResult,
    2172                               oData.idTestSet,
    2173                               oData.idFailureReason,
    2174                               oData.sComment,) );
    2175         return True;
    2176 
    2177 
    2178     def _historizeEntry(self, idTestResult, tsExpire = None):
    2179         """ Historizes the current entry. """
    2180         if tsExpire is None:
    2181             tsExpire = self._oDb.getCurrentTimestamp();
    2182         self._oDb.execute('UPDATE TestResultFailures\n'
    2183                           'SET    tsExpire   = %s\n'
    2184                           'WHERE  idTestResult = %s\n'
    2185                           '   AND tsExpire     = \'infinity\'::TIMESTAMP\n'
    2186                           , (tsExpire, idTestResult,));
    2187         return True;
    2188 
    2189 
    2190     def _resolveSetTestIdIfMissing(self, oData):
    2191         """ Resolve any missing idTestSet reference (it's a duplicate for speed efficiency). """
    2192         if oData.idTestSet is None and oData.idTestResult is not None:
    2193             self._oDb.execute('SELECT idTestSet FROM TestResults WHERE idTestResult = %s', (oData.idTestResult,));
    2194             oData.idTestSet = self._oDb.fetchOne()[0];
    2195         return oData;
    2196 
    2197 
    2198 
    21991859#
    22001860# Unit testing.
  • trunk/src/VBox/ValidationKit/testmanager/webui/wuihlpform.py

    r61259 r61278  
    452452            sHtml += u'}\n';
    453453            sHtml += u'\n';
    454             sHtml += u'function %s_extendListEx(sSubName, cGangMembers, cSecTimeout, sArgs, sTestBoxReqExpr, sBuildReqExpr)\n' % (sName,);
     454            sHtml += u'function %s_extendListEx(sSubName, cGangMembers, cSecTimeout, sArgs, sTestBoxReqExpr, sBuildReqExpr)\n' \
     455                     % (sName,);
    455456            sHtml += u'{\n';
    456457            sHtml += u'    var oElement = document.getElementById(\'%s\');\n' % (sTableId,);
  • trunk/src/VBox/ValidationKit/testmanager/webui/wuimain.py

    r61270 r61278  
    920920    def _actionTestResultFailureAdd(self):
    921921        """ Pro forma. """
    922         from testmanager.core.testresults import TestResultFailureData;
     922        from testmanager.core.testresultfailures import TestResultFailureData;
    923923        from testmanager.webui.wuitestresultfailure import WuiTestResultFailure;
    924924        return self._actionGenericFormAdd(TestResultFailureData, WuiTestResultFailure);
     
    926926    def _actionTestResultFailureAddPost(self):
    927927        """Add test result failure result"""
    928         from testmanager.core.testresults import TestResultFailureLogic, TestResultFailureData;
     928        from testmanager.core.testresultfailures import TestResultFailureLogic, TestResultFailureData;
    929929        from testmanager.webui.wuitestresultfailure import WuiTestResultFailure;
    930930        if self.ksParamRedirectTo not in self._dParams:
     
    936936    def _actionTestResultFailureDetails(self):
    937937        """ Pro forma. """
    938         from testmanager.core.testresults import TestResultFailureLogic, TestResultFailureData;
     938        from testmanager.core.testresultfailures import TestResultFailureLogic, TestResultFailureData;
    939939        from testmanager.webui.wuitestresultfailure import WuiTestResultFailure;
    940940        return self._actionGenericFormDetails(TestResultFailureData, TestResultFailureLogic,
     
    943943    def _actionTestResultFailureEdit(self):
    944944        """ Pro forma. """
    945         from testmanager.core.testresults import TestResultFailureData;
     945        from testmanager.core.testresultfailures import TestResultFailureData;
    946946        from testmanager.webui.wuitestresultfailure import WuiTestResultFailure;
    947947        return self._actionGenericFormEdit(TestResultFailureData, WuiTestResultFailure,
     
    950950    def _actionTestResultFailureEditPost(self):
    951951        """Edit test result failure result"""
    952         from testmanager.core.testresults import TestResultFailureLogic, TestResultFailureData;
     952        from testmanager.core.testresultfailures import TestResultFailureLogic, TestResultFailureData;
    953953        from testmanager.webui.wuitestresultfailure import WuiTestResultFailure;
    954954        return self._actionGenericFormEditPost(TestResultFailureData, TestResultFailureLogic,
  • trunk/src/VBox/ValidationKit/testmanager/webui/wuitestresult.py

    r61272 r61278  
    4646from testmanager.core.testset                   import TestSetData;
    4747from testmanager.core.testgroup                 import TestGroupData;
    48 from testmanager.core.testresults               import TestResultFailureData;
     48from testmanager.core.testresultfailures        import TestResultFailureData;
    4949from testmanager.core.build                     import BuildData;
    5050from testmanager.core                           import db;
  • trunk/src/VBox/ValidationKit/testmanager/webui/wuitestresultfailure.py

    r61255 r61278  
    3333from testmanager.webui.wuimain                  import WuiMain;
    3434from testmanager.webui.wuiadminfailurereason    import WuiFailureReasonDetailsLink, WuiFailureReasonAddLink;
    35 from testmanager.core.testresults               import TestResultFailureData;
     35from testmanager.core.testresultfailures        import TestResultFailureData;
    3636from testmanager.core.testset                   import TestSetData;
    3737from testmanager.core.failurereason             import FailureReasonLogic;
Note: See TracChangeset for help on using the changeset viewer.

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