VirtualBox

Changeset 61424 in vbox


Ignore:
Timestamp:
Jun 3, 2016 2:22:30 AM (9 years ago)
Author:
vboxsync
svn:sync-xref-src-repo-rev:
107741
Message:

vsheriff: Some progress.

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

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/ValidationKit/testmanager/batch/virtual_test_sheriff.py

    r61404 r61424  
    5454from testmanager.core.testgroup             import TestGroupData;
    5555from testmanager.core.testset               import TestSetLogic, TestSetData;
    56 from testmanager.core.testresults           import TestResultLogic;
     56from testmanager.core.testresults           import TestResultLogic, TestResultFileData;
    5757from testmanager.core.testresultfailures    import TestResultFailureLogic, TestResultFailureData;
    5858from testmanager.core.useraccount           import UserAccountLogic;
     
    6868    ## Max log file we'll read into memory. (256 MB)
    6969    kcbMaxLogRead = 0x10000000;
    70 
    7170
    7271    def __init__(self, oSheriff, oTestSet, oTree, oBuild, oTestBox, oTestGroup, oTestCase):
     
    9796
    9897        # Investigation notes.
    99         self.tReason            = None;     # None or one of the ktReason_XXX constants.
    100         self.dReasonForResultId = {};       # Reason assignments indexed by idTestResult.
     98        self.tReason                = None; # None or one of the ktReason_XXX constants.
     99        self.dReasonForResultId     = {};   # Reason assignments indexed by idTestResult.
     100        self.dCommentForResultId    = {};   # Comment assignments indexed by idTestResult.
    101101
    102102    #
     
    108108        self.oSheriff.dprint('noteReason: %s -> %s' % (self.tReason, tReason,));
    109109        self.tReason = tReason;
    110 
    111     def noteReasonForId(self, tReason, idTestResult):
     110        return True;
     111
     112    def noteReasonForId(self, tReason, idTestResult, sComment = None):
    112113        """ Notes down a possible reason for a specific test result. """
    113         self.oSheriff.dprint('noteReasonForId: %u: %s -> %s'
    114                              % (idTestResult, self.dReasonForResultId.get(idTestResult, None), tReason,));
     114        self.oSheriff.dprint('noteReasonForId: %u: %s -> %s%s'
     115                             % (idTestResult, self.dReasonForResultId.get(idTestResult, None), tReason,
     116                                (' (%s)' % (sComment,)) if sComment is not None else ''));
    115117        self.dReasonForResultId[idTestResult] = tReason;
     118        if sComment is not None:
     119            self.dCommentForResultId[idTestResult] = sComment;
     120        return True;
    116121
    117122
     
    161166        return self.sMainLog;
    162167
     168    def getLogFile(self, oFile):
     169        """
     170        Tries to reads the given file as a utf-8 log file.
     171        oFile is a TestFileDataEx instance.
     172        Returns empty string if problems opening or reading the file.
     173        """
     174        sContent = '';
     175        (oFile, oSizeOrError, _) = self.oTestSet.openFile(oFile.sFile, 'rb');
     176        if oFile is not None:
     177            try:
     178                sContent = oFile.read(min(self.kcbMaxLogRead, oSizeOrError)).decode('utf-8', 'replace');
     179            except Exception as oXcpt:
     180                self.oSheriff.vprint('Error reading the "%s" log file: %s' % (oFile.sFile, oXcpt,))
     181        else:
     182            self.oSheriff.vprint('Error opening the "%s" log file: %s' % (oFile.sFile, oSizeOrError,));
     183        return sContent;
     184
     185
    163186    def isSingleTestFailure(self):
    164187        """
     
    195218        self.uidSelf                 = -1;
    196219        self.oLogFile                = None;
     220        self.asBsodReasons           = [];
    197221
    198222        oParser = OptionParser();
     
    355379    ktReason_Guru_VERR_IEM_INSTR_NOT_IMPLEMENTED       = ( 'Guru Meditations',  'VERR_IEM_INSTR_NOT_IMPLEMENTED' );
    356380    ktReason_Guru_VERR_IEM_ASPECT_NOT_IMPLEMENTED      = ( 'Guru Meditations',  'VERR_IEM_ASPECT_NOT_IMPLEMENTED' );
     381    ktReason_Guru_VERR_TRPM_DONT_PANIC                 = ( 'Guru Meditations',  'VERR_TRPM_DONT_PANIC' );
    357382    ktReason_Guru_VINF_EM_TRIPLE_FAULT                 = ( 'Guru Meditations',  'VINF_EM_TRIPLE_FAULT' );
    358383    ktReason_XPCOM_Exit_Minus_11                       = ( 'API / (XP)COM',     'exit -11' );
     384    ktReason_XPCOM_VBoxSVC_Hang                        = ( 'API / (XP)COM',     'VBoxSVC hang' );
     385    ktReason_XPCOM_VBoxSVC_Hang_Plus_Heap_Corruption   = ( 'API / (XP)COM',     'VBoxSVC hang + heap corruption' );
     386    ktReason_Unknown_Heap_Corruption                   = ( 'Unknown',           'Heap corruption' );
     387    ktReason_Unknown_Reboot_Loop                       = ( 'Unknown',           'Reboot loop' );
    359388    ## @}
    360389
     390    ## BSOD category.
     391    ksBsodCategory    = 'BSOD';
     392    ## Special reason indicating that the flesh and blood sheriff has work to do.
     393    ksBsodAddNew      = 'Add new BSOD';
     394
     395    ## Used for indica that we shouldn't report anything for this test result ID and
     396    ## consider promoting the previous error to test set level if it's the only one.
     397    ktHarmless = ( 'Probably', 'Caused by previous error' );
     398
     399
    361400    def caseClosed(self, oCaseFile):
    362401        """
     
    366405        # Log it and create a dReasonForReasultId we can use below.
    367406        #
    368         if len(oCaseFile.dReasonForResultId):
    369             self.vprint('Closing %s with following reasons: %s' % (oCaseFile.sName, oCaseFile.dReasonForResultId,));
    370             dReasonForReasultId = oCaseFile.dReasonForResultId;
     407        dCommentForResultId = oCaseFile.dCommentForResultId;
     408        if len(oCaseFile.dReasonForResultId) > 0:
     409            # Must weed out ktHarmless.
     410            dReasonForResultId = {};
     411            for idKey, tReason in oCaseFile.dReasonForResultId.items():
     412                if tReason is not self.ktHarmless:
     413                    dReasonForResultId[idKey] = tReason;
     414            if len(dReasonForResultId) == 0:
     415                self.vprint('TODO: Closing %s without a real reason, only %s.' % (oCaseFile.sName, oCaseFile.dReasonForResultId));
     416                return False;
     417
     418            # Try promote to single reason.
     419            if len(dReasonForResultId) > 1:
     420                atValues = dReasonForResultId.values();
     421                if len(atValues) == atValues.count(atValues[0]):
     422                    self.dprint('Merged %d reasons to a single one: %s' % (len(atValues), atValues[0]));
     423                    dReasonForResultId = { oCaseFile.oTestSet.idTestResult: atValues[0], };
     424                    if len(dCommentForResultId) > 0:
     425                        dCommentForResultId = { oCaseFile.oTestSet.idTestResult: dCommentForResultId.values()[0], };
    371426        elif oCaseFile.tReason is not None:
    372             self.vprint('Closing %s with following reason: %s'  % (oCaseFile.sName, oCaseFile.tReason,));
    373             dReasonForReasultId = { oCaseFile.oTestSet.idTestResult: oCaseFile.tReason, };
     427            dReasonForResultId = { oCaseFile.oTestSet.idTestResult: oCaseFile.tReason, };
    374428        else:
    375             self.vprint('Closing %s without a reason ... weird!' % (oCaseFile.sName,));
     429            self.vprint('Closing %s without a reason - this should not happen!' % (oCaseFile.sName,));
    376430            return False;
    377431
     432        self.vprint('Closing %s with following reason%s: %s'
     433                    % ( oCaseFile.sName, 's' if dReasonForResultId > 0 else '', dReasonForResultId, ));
     434
    378435        #
    379436        # Add the test failure reason record(s).
    380437        #
    381         for idTestResult, tReason in dReasonForReasultId.items():
     438        for idTestResult, tReason in dReasonForResultId.items():
    382439            oFailureReason = self.oFailureReasonLogic.cachedLookupByNameAndCategory(tReason[1], tReason[0]);
    383440            if oFailureReason is not None:
     441                sComment = 'Set by $Revision$' # Handy for reverting later.
     442                if idTestResult in dCommentForResultId:
     443                    sComment += ': ' + dCommentForResultId[idTestResult];
     444
    384445                oAdd = TestResultFailureData();
    385                 oAdd.initFromValues(idTestResult     = idTestResult,
    386                                     idFailureReason  = oFailureReason.idFailureReason,
    387                                     uidAuthor        = self.uidSelf,
    388                                     idTestSet        = oCaseFile.oTestSet.idTestSet,
    389                                     sComment         = 'Set by $Revision$',); # Handy for reverting later.
     446                oAdd.initFromValues(idTestResult    = idTestResult,
     447                                    idFailureReason = oFailureReason.idFailureReason,
     448                                    uidAuthor       = self.uidSelf,
     449                                    idTestSet       = oCaseFile.oTestSet.idTestSet,
     450                                    sComment        = sComment,);
    390451                if self.oConfig.fRealRun:
    391452                    try:
     
    398459        return True;
    399460
     461    #
     462    # Tools for assiting log parsing.
     463    #
     464
     465    @staticmethod
     466    def matchFollowedByLines(sStr, off, asFollowingLines):
     467        """ Worker for isThisFollowedByTheseLines. """
     468
     469        # Advance off to the end of the line.
     470        off = sStr.find('\n', off);
     471        if off < 0:
     472            return False;
     473        off += 1;
     474
     475        # Match each string with the subsequent lines.
     476        for iLine, sLine in enumerate(asFollowingLines):
     477            offEnd = sStr.find('\n', off);
     478            if offEnd < 0:
     479                return  iLine + 1 == len(asFollowingLines) and sStr.find(sLine, off) < 0;
     480            if len(sLine) > 0 and sStr.find(sLine, off, offEnd) < 0:
     481                return False;
     482
     483            # next line.
     484            off = offEnd + 1;
     485
     486        return True;
     487
     488    @staticmethod
     489    def isThisFollowedByTheseLines(sStr, sFirst, asFollowingLines):
     490        """
     491        Looks for a line contining sFirst which is then followed by lines
     492        with the strings in asFollowingLines.  (No newline chars anywhere!)
     493        Returns True / False.
     494        """
     495        off = sStr.find(sFirst, 0);
     496        while off >= 0:
     497            if VirtualTestSheriff.matchFollowedByLines(sStr, off, asFollowingLines):
     498                return True;
     499            off = sStr.find(sFirst, off + 1);
     500        return False;
     501
     502    @staticmethod
     503    def findAndReturnResetOfLine(sHaystack, sNeedle):
     504        """
     505        Looks for sNeedle in sHaystack.
     506        Returns The text following the needle up to the end of the line.
     507        Returns None if not found.
     508        """
     509        off = sHaystack.find(sNeedle);
     510        if off < 0:
     511            return None;
     512        off += len(sNeedle)
     513        offEol = sHaystack.find('\n', off);
     514        if offEol < 0:
     515            offEol = len(sHaystack);
     516        return sHaystack[off:offEol]
     517
     518    @staticmethod
     519    def findInAnyAndReturnResetOfLine(asHaystacks, sNeedle):
     520        """
     521        Looks for sNeedle in zeroe or more haystacks (asHaystack).
     522        Returns The text following the first needed found up to the end of the line.
     523        Returns None if not found.
     524        """
     525        for sHaystack in asHaystacks:
     526            sRet = VirtualTestSheriff.findAndReturnResetOfLine(sHaystack, sNeedle);
     527            if sRet is not None:
     528                return sRet;
     529        return None;
     530
     531
     532    #
     533    # The investigative units.
     534    #
    400535
    401536    def investigateBadTestBox(self, oCaseFile):
     
    434569        ( True,  'VERR_IEM_INSTR_NOT_IMPLEMENTED',                  ktReason_Guru_VERR_IEM_INSTR_NOT_IMPLEMENTED ),
    435570        ( True,  'VERR_IEM_ASPECT_NOT_IMPLEMENTED',                 ktReason_Guru_VERR_IEM_ASPECT_NOT_IMPLEMENTED ),
     571        ( True,  'VERR_TRPM_DONT_PANIC',                            ktReason_Guru_VERR_TRPM_DONT_PANIC ),
    436572        ( True,  'VINF_EM_TRIPLE_FAULT',                            ktReason_Guru_VINF_EM_TRIPLE_FAULT ),
    437         ( True,  'vboxinstaller: Exit code: -11 (',                 ktReason_XPCOM_Exit_Minus_11),
    438573    ];
     574
     575    def investigateVMResult(self, oCaseFile, oFailedResult, sResultLog):
     576        """
     577        Investigates a failed VM run.
     578        """
     579
     580        def investigateLogSet():
     581            """
     582            Investigates the current set of VM related logs.
     583            """
     584            self.dprint('investigateLogSet: %u chars result log, %u chars VM log, %u chars kernel log'
     585                        % ( len(sResultLog) if sResultLog is not None else 0,
     586                            len(sVMLog)     if sVMLog is not None else 0,
     587                            len(sKrnlLog)   if sKrnlLog is not None else 0), );
     588            #self.dprint('main.log<<<\n%s\n<<<\n' % (sResultLog,));
     589            #self.dprint('vbox.log<<<\n%s\n<<<\n' % (sVMLog,));
     590            #self.dprint('krnl.log<<<\n%s\n<<<\n' % (sKrnlLog,));
     591
     592            # TODO: more
     593
     594            #
     595            # Look for BSODs. Some stupid stupid inconsistencies in reason and log messages here, so don't try prettify this.
     596            #
     597            sDetails = self.findInAnyAndReturnResetOfLine([ sVMLog, sResultLog ],
     598                                                          'GIM: HyperV: Guest indicates a fatal condition! P0=');
     599            if sDetails is not None:
     600                # P0=%#RX64 P1=%#RX64 P2=%#RX64 P3=%#RX64 P4=%#RX64 "
     601                sKey = sDetails.split(' ', 1)[0];
     602                try:    sKey = '0x%08X' % (int(sKey, 16),);
     603                except: pass;
     604                if sKey in self.asBsodReasons or sKey.lower() in self.asBsodReasons:
     605                    tReason = ( self.ksBsodCategory, sKey );
     606                else:
     607                    self.dprint('BSOD "%s" not found in %s;' % (sKey, self.asBsodReasons));
     608                    tReason = ( self.ksBsodCategory, self.ksBsodAddNew );
     609                return oCaseFile.noteReasonForId(tReason, oFailedResult.idTestResult, sComment = sDetails.strip());
     610
     611            #
     612            # Look for linux panic.
     613            #
     614            if sKrnlLog is not None:
     615                pass; ## @todo
     616
     617            #
     618            # Loop thru the simple stuff.
     619            #
     620            fFoundSomething = False;
     621            for fStopOnHit, sNeedle, tReason in self.katSimpleMainAndVmLogReasons:
     622                if sResultLog.find(sNeedle) > 0 or sVMLog.find(sNeedle) > 0:
     623                    oCaseFile.noteReasonForId(tReason, oFailedResult.idTestResult);
     624                    if fStopOnHit:
     625                        return True;
     626                    fFoundSomething = True;
     627
     628            #
     629            # Check for repeated reboots...
     630            #
     631            cResets = sVMLog.count('Changing the VM state from \'RUNNING\' to \'RESETTING\'');
     632            if cResets > 10:
     633                return oCaseFile.noteReasonForId(self.ktReason_Unknown_Reboot_Loop, oFailedResult.idTestResult,
     634                                                 sComment = 'Counted %s reboots' % (cResets,));
     635
     636            return fFoundSomething;
     637
     638        #
     639        # Check if we got any VM or/and kernel logs.  Treat them as sets in
     640        # case we run multiple VMs here.
     641        #
     642        sVMLog   = None;
     643        sKrnlLog = None;
     644        for oFile in oFailedResult.aoFiles:
     645            if oFile.sKind == TestResultFileData.ksKind_LogReleaseVm:
     646                if sVMLog is not None:
     647                    if investigateLogSet() is True:
     648                        return True;
     649                sKrnlLog = None;
     650                sVMLog   = oCaseFile.getLogFile(oFile);
     651            elif oFile.sKind == TestResultFileData.ksKind_LogGuestKernel:
     652                sKrnlLog = oCaseFile.getLogFile(oFile);
     653        if sVMLog is not None and investigateLogSet() is True:
     654            return True;
     655
     656        return None;
     657
     658
     659    def isResultFromVMRun(self, oFailedResult, sResultLog):
     660        """
     661        Checks if this result and corresponding log snippet looks like a VM run.
     662        """
     663
     664        # Look for startVmEx/ startVmAndConnectToTxsViaTcp and similar output in the log.
     665        if sResultLog.find(' startVm') > 0:
     666            return True;
     667
     668        # Any other indicators? No?
     669        _ = oFailedResult;
     670        return False;
     671
    439672
    440673    def investigateVBoxVMTest(self, oCaseFile, fSingleVM):
     
    450683
    451684        #
    452         # Do some quick searches thru the main log to see if there is anything
    453         # immediately incriminating evidence there.
    454         #
    455         sMainLog = oCaseFile.getMainLog();
    456         for fStopOnHit, sNeedle, tReason in self.katSimpleMainAndVmLogReasons:
    457             if sMainLog.find(sNeedle) > 0:
    458                 oCaseFile.noteReason(tReason);
    459                 if fStopOnHit:
    460                     if oCaseFile.isSingleTestFailure():
    461                         return self.caseClosed(oCaseFile);
    462                     break;
    463 
     685        # Get a list of test result failures we should be looking into and the main log.
     686        #
     687        aoFailedResults = oCaseFile.oTree.getListOfFailures();
     688        sMainLog        = oCaseFile.getMainLog();
     689
     690        #
     691        # There are a set of errors ending up on the top level result record.
     692        # Should deal with these first.
     693        #
     694        if len(aoFailedResults) == 1 and aoFailedResults[0] == oCaseFile.oTree:
     695            # Check if we've just got that XPCOM client smoke test shutdown issue.  This will currently always
     696            # be reported on the top result because vboxinstall.py doesn't add an error for it.  It is easy to
     697            # ignore other failures in the test if we're not a little bit careful here.
     698            if sMainLog.find('vboxinstaller: Exit code: -11 (') > 0:
     699                oCaseFile.noteReason(self.ktReason_XPCOM_Exit_Minus_11);
     700                return self.caseClosed(oCaseFile);
     701
     702            # Hang after starting VBoxSVC (e.g. idTestSet=136307258)
     703            if self.isThisFollowedByTheseLines(sMainLog, 'oVBoxMgr=<vboxapi.VirtualBoxManager object at',
     704                                               (' Timeout: ', ' Attempting to abort child...',) ):
     705                if sMainLog.find('*** glibc detected *** /') > 0:
     706                    oCaseFile.noteReason(self.ktReason_XPCOM_VBoxSVC_Hang_Plus_Heap_Corruption);
     707                else:
     708                    oCaseFile.noteReason(self.ktReason_XPCOM_VBoxSVC_Hang);
     709                return self.caseClosed(oCaseFile);
     710
     711
     712
     713            # Look for heap corruption without visible hang.
     714            if   sMainLog.find('*** glibc detected *** /') > 0 \
     715              or sMainLog.find("-1073740940"): # STATUS_HEAP_CORRUPTION / 0xc0000374
     716                oCaseFile.noteReason(self.ktReason_Unknown_Heap_Corruption);
     717                return self.caseClosed(oCaseFile);
     718
     719        #
     720        # Go thru each failed result.
     721        #
     722        for oFailedResult in aoFailedResults:
     723            self.dprint('Looking at test result #%u - %s' % (oFailedResult.idTestResult, oFailedResult.getFullName(),));
     724            sResultLog = TestSetData.extractLogSectionElapsed(sMainLog, oFailedResult.tsCreated, oFailedResult.tsElapsed);
     725            if oFailedResult.sName == 'Installing VirtualBox':
     726                self.vprint('TODO: Installation failure');
     727            elif oFailedResult.sName == 'Uninstalling VirtualBox':
     728                self.vprint('TODO: Uninstallation failure');
     729            elif self.isResultFromVMRun(oFailedResult, sResultLog):
     730                self.investigateVMResult(oCaseFile, oFailedResult, sResultLog);
     731            elif sResultLog.find('The machine is not mutable (state is ') > 0:
     732                self.vprint('Ignorining "machine not mutable" error as it is probably due to an earlier problem');
     733                oCaseFile.noteReasonForId(self.ktHarmless, oFailedResult.idTestResult);
     734            else:
     735                self.vprint('TODO: Cannot place idTestResult=%u - %s' % (oFailedResult.idTestResult, oFailedResult.sName,));
     736                self.dprint('%s + %s <<\n%s\n<<' % (oFailedResult.tsCreated, oFailedResult.tsElapsed, sResultLog,));
     737
     738        #
     739        # Report home and close the case if we got them all, otherwise log it.
     740        #
     741        if len(oCaseFile.dReasonForResultId) >= len(aoFailedResults):
     742            return self.caseClosed(oCaseFile);
     743
     744        if len(oCaseFile.dReasonForResultId) > 0:
     745            self.vprint('TODO: Got %u out of %u - close, but no cigar. :-/'
     746                        % (len(oCaseFile.dReasonForResultId), len(aoFailedResults)));
     747        else:
     748            self.vprint('XXX: Could not figure out anything at all! :-(');
    464749        return False;
    465750
     
    472757        # Get a list of failed test sets without any assigned failure reason.
    473758        #
     759        cGot = 0;
    474760        aoTestSets = self.oTestSetLogic.fetchFailedSetsWithoutReason(cHoursBack = self.oConfig.cHoursBack, tsNow = self.tsNow);
    475761        for oTestSet in aoTestSets:
     
    490776            if oTestSet.enmStatus == TestSetData.ksTestStatus_BadTestBox:
    491777                self.dprint('investigateBadTestBox is taking over %s.' % (oCaseFile.sLongName,));
    492                 self.investigateBadTestBox(oCaseFile);
     778                fRc = self.investigateBadTestBox(oCaseFile);
    493779            elif oCaseFile.isVBoxUnitTest():
    494780                self.dprint('investigateVBoxUnitTest is taking over %s.' % (oCaseFile.sLongName,));
    495                 self.investigateVBoxUnitTest(oCaseFile);
     781                fRc = self.investigateVBoxUnitTest(oCaseFile);
    496782            elif oCaseFile.isVBoxInstallTest():
    497783                self.dprint('investigateVBoxVMTest is taking over %s.' % (oCaseFile.sLongName,));
    498                 self.investigateVBoxVMTest(oCaseFile, fSingleVM = True);
     784                fRc = self.investigateVBoxVMTest(oCaseFile, fSingleVM = True);
    499785            elif oCaseFile.isVBoxSmokeTest():
    500786                self.dprint('investigateVBoxVMTest is taking over %s.' % (oCaseFile.sLongName,));
    501                 self.investigateVBoxVMTest(oCaseFile, fSingleVM = False);
     787                fRc = self.investigateVBoxVMTest(oCaseFile, fSingleVM = False);
    502788            else:
    503789                self.vprint('reasoningFailures: Unable to classify test set: %s' % (oCaseFile.sLongName,));
     790                fRc = False;
     791            cGot += fRc is True;
     792        self.vprint('reasoningFailures: Got %u out of %u' % (cGot, len(aoTestSets), ));
    504793        return 0;
    505794
     
    516805        self.oFailureReasonLogic     = FailureReasonLogic(self.oDb);
    517806        self.oTestResultFailureLogic = TestResultFailureLogic(self.oDb);
     807        self.asBsodReasons           = self.oFailureReasonLogic.fetchForSheriffByNamedCategory(self.ksBsodCategory);
    518808
    519809        # Get a fix on our 'now' before we do anything..
  • trunk/src/VBox/ValidationKit/testmanager/core/failurereason.py

    r61286 r61424  
    219219            aoRows.append(FailureReasonDataEx().initFromDbRowEx(aoRow, self.oCategoryLogic, self.oUserAccountLogic));
    220220        return aoRows
     221
     222
     223    def fetchForSheriffByNamedCategory(self, sFailureCategory):
     224        """
     225        Fetches the short names of the reasons in the named category.
     226
     227        Returns array of strings.
     228        Raises exception on error.
     229        """
     230        self._oDb.execute('SELECT   FailureReasons.sShort\n'
     231                          'FROM     FailureReasons,\n'
     232                          '         FailureCategories\n'
     233                          'WHERE    FailureReasons.tsExpire          = \'infinity\'::TIMESTAMP\n'
     234                          '     AND FailureReasons.idFailureCategory = FailureCategories.idFailureCategory\n'
     235                          '     AND FailureCategories.sShort         = %s\n'
     236                          'ORDER BY FailureReasons.sShort ASC\n'
     237                          , ( sFailureCategory,));
     238        return [aoRow[0] for aoRow in self._oDb.fetchAll()];
     239
    221240
    222241    def fetchForCombo(self, sFirstEntry = 'Select a failure reason', tsEffective = None):
  • trunk/src/VBox/ValidationKit/testmanager/core/testresults.py

    r61284 r61424  
    199199        cChildErrors = 0;
    200200        for oChild in self.aoChildren:
    201             cChanges += oChild.deepCountErrorContributers();
    202             cChildErrors += oChild.cErrors;
     201            if oChild.cErrors > 0:
     202                cChildErrors += oChild.cErrors;
     203                cChanges     += oChild.deepCountErrorContributers();
    203204
    204205        # Did we contribute as well?
    205         if self.cErrors != cChildErrors:
    206             assert self.cErrors >= cChildErrors;
     206        if self.cErrors > cChildErrors:
    207207            cChanges += 1;
    208208        return cChanges;
     209
     210    def getListOfFailures(self):
     211        """
     212        Get a list of test results insances actually contributing to cErrors.
     213
     214        Returns a list of TestResultDataEx insance from this tree. (shared!)
     215        """
     216        # Check each child (if any).
     217        aoRet = [];
     218        cChildErrors = 0;
     219        for oChild in self.aoChildren:
     220            if oChild.cErrors > 0:
     221                cChildErrors += oChild.cErrors;
     222                aoRet.extend(oChild.getListOfFailures());
     223
     224        # Did we contribute as well?
     225        if self.cErrors > cChildErrors:
     226            aoRet.append(self);
     227
     228        return aoRet;
     229
     230    def getFullName(self):
     231        """ Constructs the full name of this test result. """
     232        if self.oParent is None:
     233            return self.sName;
     234        return self.oParent.getFullName() + ' / ' + self.sName;
     235
    209236
    210237
     
    351378    ksParam_idStrKind           = 'TestResultFile_idStrKind';
    352379    ksParam_idStrMime           = 'TestResultFile_idStrMime';
     380
     381    ## @name Kind of files.
     382    ## @{
     383    ksKind_LogReleaseVm         = 'log/release/vm';
     384    ksKind_LogDebugVm           = 'log/debug/vm';
     385    ksKind_LogReleaseSvc        = 'log/release/svc';
     386    ksKind_LogRebugSvc          = 'log/debug/svc';
     387    ksKind_LogReleaseClient     = 'log/release/client';
     388    ksKind_LogDebugClient       = 'log/debug/client';
     389    ksKind_LogInstaller         = 'log/installer';
     390    ksKind_LogUninstaller       = 'log/uninstaller';
     391    ksKind_LogGuestKernel       = 'log/guest/kernel';
     392    ksKind_CrashReportVm        = 'crash/report/vm';
     393    ksKind_CrashDumpVm          = 'crash/dump/vm';
     394    ksKind_CrashReportSvc       = 'crash/report/svc';
     395    ksKind_CrashDumpSvc         = 'crash/dump/svc';
     396    ksKind_CrashReportClient    = 'crash/report/client';
     397    ksKind_CrashDumpClient      = 'crash/dump/client';
     398    ksKind_MiscOther            = 'misc/other';
     399    ksKind_ScreenshotFailure    = 'screenshot/failure';
     400    ksKind_ScreenshotSuccesss   = 'screenshot/success';
     401    #kSkind_ScreenCaptureFailure = 'screencapture/failure';
     402    ## @}
    353403
    354404    def __init__(self):
  • trunk/src/VBox/ValidationKit/testmanager/core/testset.py

    r61327 r61424  
    3838from common                         import utils;
    3939from testmanager                    import config;
     40from testmanager.core               import db;
    4041from testmanager.core.base          import ModelDataBase, ModelDataBaseTestCase, ModelLogicBase,  \
    4142                                           TMExceptionBase, TMTooManyRows, TMRowNotFound;
     
    229230        return oFile;
    230231
     232    @staticmethod
     233    def findLogOffsetForTimestamp(sLogContent, tsTimestamp, offStart = 0, fAfter = False):
     234        """
     235        Log parsing utility function for finding the offset for the given timestamp.
     236
     237        We ASSUME the log lines are prefixed with UTC timestamps on the format
     238        '09:43:55.789353'.
     239
     240        Return index into the sLogContent string, 0 if not found.
     241        """
     242        # Turn tsTimestamp into a string compatible with what we expect to find in the log.
     243        oTsZulu   = db.dbTimestampToZuluDatetime(tsTimestamp);
     244        sWantedTs = oTsZulu.strftime('%H:%M:%S.%f');
     245        assert len(sWantedTs) == 15;
     246
     247        # Now loop thru the string, line by line.
     248        offRet  = offStart;
     249        off     = offStart;
     250        while True:
     251            sThisTs = sLogContent[off : off + 15];
     252            if    len(sThisTs) >= 15 \
     253              and sThisTs[2]  == ':' \
     254              and sThisTs[5]  == ':' \
     255              and sThisTs[8]  == '.' \
     256              and sThisTs[14] in '0123456789':
     257                if sThisTs < sWantedTs:
     258                    offRet = off;
     259                elif sThisTs == sWantedTs:
     260                    if not fAfter:
     261                        return off;
     262                    offRet = off;
     263                else:
     264                    if fAfter:
     265                        offRet = off;
     266                    break;
     267
     268            # next line.
     269            off = sLogContent.find('\n', off);
     270            if off < 0:
     271                if fAfter:
     272                    offRet = len(sLogContent);
     273                break;
     274            off += 1;
     275
     276        return offRet;
     277
     278    @staticmethod
     279    def extractLogSection(sLogContent, tsStart, tsLast):
     280        """
     281        Returns log section from tsStart to tsLast (or all if we cannot make sense of it).
     282        """
     283        offStart = TestSetData.findLogOffsetForTimestamp(sLogContent, tsStart);
     284        offEnd   = TestSetData.findLogOffsetForTimestamp(sLogContent, tsLast, offStart, fAfter = True);
     285        return sLogContent[offStart : offEnd];
     286
     287    @staticmethod
     288    def extractLogSectionElapsed(sLogContent, tsStart, tsElapsed):
     289        """
     290        Returns log section from tsStart and tsElapsed forward (or all if we cannot make sense of it).
     291        """
     292        tsStart = db.dbTimestampToZuluDatetime(tsStart);
     293        tsLast  = tsStart + tsElapsed;
     294        return TestSetData.extractLogSection(sLogContent, tsStart, tsLast);
     295
    231296
    232297
  • trunk/src/VBox/ValidationKit/testmanager/webui/wuiadmin.py

    r61422 r61424  
    405405        return False
    406406
    407     def _actionGenericDoRemove(self, oLogicType, sParamId, sRedirAction):
    408         """
    409         Delete entry (using oLogicType.removeEntry).
    410 
    411         oLogicType is a class that implements addEntry.
    412 
    413         sParamId is the name (ksParam_...) of the HTTP variable hold the ID of
    414         the database entry to delete.
    415 
    416         sRedirAction is what action to redirect to on success.
    417         """
    418         idEntry = self.getIntParam(sParamId, iMin = 1, iMax = 0x7fffffe)
    419         fCascade = self.getBoolParam('fCascadeDelete', False);
    420         self._checkForUnknownParameters()
    421 
    422         try:
    423             self._sPageTitle  = None
    424             self._sPageBody   = None
    425             self._sRedirectTo = self._sActionUrlBase + sRedirAction;
    426             return oLogicType(self._oDb).removeEntry(self._oCurUser.uid, idEntry, fCascade = fCascade, fCommit = True);
    427         except Exception as oXcpt:
    428             self._oDb.rollback();
    429             self._sPageTitle  = 'Unable to delete entry';
    430             self._sPageBody   = str(oXcpt);
    431             if config.g_kfDebugDbXcpt:
    432                 self._sPageBody += cgitb.html(sys.exc_info());
    433             self._sRedirectTo = None;
    434         return False;
    435 
    436407
    437408    #
  • trunk/src/VBox/ValidationKit/testmanager/webui/wuibase.py

    r61267 r61424  
    822822        return True
    823823
     824    def _actionGenericDoRemove(self, oLogicType, sParamId, sRedirAction):
     825        """
     826        Delete entry (using oLogicType.removeEntry).
     827
     828        oLogicType is a class that implements addEntry.
     829
     830        sParamId is the name (ksParam_...) of the HTTP variable hold the ID of
     831        the database entry to delete.
     832
     833        sRedirAction is what action to redirect to on success.
     834        """
     835        idEntry = self.getIntParam(sParamId, iMin = 1, iMax = 0x7ffffffe)
     836        fCascade = self.getBoolParam('fCascadeDelete', False);
     837        self._checkForUnknownParameters()
     838
     839        try:
     840            self._sPageTitle  = None
     841            self._sPageBody   = None
     842            self._sRedirectTo = self._sActionUrlBase + sRedirAction;
     843            return oLogicType(self._oDb).removeEntry(self._oCurUser.uid, idEntry, fCascade = fCascade, fCommit = True);
     844        except Exception as oXcpt:
     845            self._oDb.rollback();
     846            self._sPageTitle  = 'Unable to delete entry';
     847            self._sPageBody   = str(oXcpt);
     848            if config.g_kfDebugDbXcpt:
     849                self._sPageBody += cgitb.html(sys.exc_info());
     850            self._sRedirectTo = None;
     851        return False;
    824852
    825853    def _actionGenericFormEdit(self, oDataType, oFormType, sIdParamName = None, sRedirectTo = None):
  • trunk/src/VBox/ValidationKit/testmanager/webui/wuicontentbase.py

    r61272 r61424  
    606606            aoActions.append(WuiTmLink('Details', '', dParams));
    607607
     608            # Add delete operation if available.
     609            if hasattr(self._oDisp, self._sActionBase + 'DoRemove'):
     610                dParams = self._oDisp.getParameters();
     611                dParams[WuiDispatcherBase.ksParamAction] = getattr(self._oDisp, self._sActionBase + 'DoRemove');
     612                dParams[getattr(oData, 'ksParam_' + oData.ksIdAttr)] = getattr(oData, oData.ksIdAttr);
     613                aoActions.append(WuiTmLink('Delete', '', dParams, sConfirm = "Are you absolutely sure?"));
     614
    608615        return aoActions;
    609616
  • trunk/src/VBox/ValidationKit/testmanager/webui/wuimain.py

    r61422 r61424  
    6868    ksActionTestResultFailureEdit       = 'TestResultFailureEdit'
    6969    ksActionTestResultFailureEditPost   = 'TestResultFailureEditPost'
     70    ksActionTestResultFailureDoRemove   = 'TestResultFailureDoRemove'
    7071    ksActionViewLog                     = 'ViewLog'
    7172    ksActionGetFile                     = 'GetFile'
     
    208209        self._dDispatch[self.ksActionTestResultFailureAddPost]      = self._actionTestResultFailureAddPost;
    209210        self._dDispatch[self.ksActionTestResultFailureDetails]      = self._actionTestResultFailureDetails;
     211        self._dDispatch[self.ksActionTestResultFailureDoRemove]     = self._actionTestResultFailureDoRemove;
    210212        self._dDispatch[self.ksActionTestResultFailureEdit]         = self._actionTestResultFailureEdit;
    211213        self._dDispatch[self.ksActionTestResultFailureEditPost]     = self._actionTestResultFailureEditPost;
     
    969971                                              WuiTestResultFailure, self.ksActionResultsUnGrouped);
    970972
     973    def _actionTestResultFailureDoRemove(self):
     974        """ Action wrapper. """
     975        from testmanager.core.testresultfailures import TestResultFailureData, TestResultFailureLogic;
     976        from testmanager.webui.wuitestresultfailure import WuiTestResultFailure;
     977        return self._actionGenericDoRemove(TestResultFailureLogic, TestResultFailureData.ksParam_idTestResult,
     978                                           self.ksActionResultsUnGrouped);
     979
    971980    def _actionTestResultFailureDetails(self):
    972981        """ Pro forma. """
  • trunk/src/VBox/ValidationKit/testmanager/webui/wuitestresult.py

    r61306 r61424  
    182182            if oTestResult.tsElapsed is not None:
    183183                tsEvent += oTestResult.tsElapsed;
    184             sHtml  = ' <tr class="%s tmtbl-events-leaf tmtbl-events-lvl%s tmstatusrow-%s">\n' \
    185                      '  <td>%s</td>\n' \
     184            sHtml  = ' <tr class="%s tmtbl-events-leaf tmtbl-events-lvl%s tmstatusrow-%s" id="S%u">\n' \
     185                     '  <td id="E%u">%s</td>\n' \
    186186                     '  <td>%s</td>\n' \
    187187                     '  <td>%s</td>\n' \
     
    190190                     '  <td>%s</td>\n' \
    191191                     ' </tr>\n' \
    192                    % ( 'tmodd' if iRow & 1 else 'tmeven', iDepth, oTestResult.enmStatus,
    193                        webutils.escapeElem(self.formatTsShort(tsEvent)),
     192                   % ( 'tmodd' if iRow & 1 else 'tmeven', iDepth, oTestResult.enmStatus, oTestResult.idTestResult,
     193                       oTestResult.idTestResult, webutils.escapeElem(self.formatTsShort(tsEvent)),
    194194                       sElapsedGraph,
    195195                       webutils.escapeElem(self.formatIntervalShort(oTestResult.tsElapsed)) if oTestResult.tsElapsed is not None
     
    307307            # Done?
    308308            if oTestResult.tsElapsed is not None:
    309                 sHtml += ' <tr class="%s tmtbl-events-final tmtbl-events-lvl%s tmstatusrow-%s">\n' \
     309                sHtml += ' <tr class="%s tmtbl-events-final tmtbl-events-lvl%s tmstatusrow-%s" id="E%d">\n' \
    310310                         '  <td>%s</td>\n' \
    311311                         '  <td>%s</td>\n' \
     
    315315                         '  <td>%s</td>\n' \
    316316                         ' </tr>\n' \
    317                        % ( 'tmodd' if iRow & 1 else 'tmeven', iDepth, oTestResult.enmStatus,
     317                       % ( 'tmodd' if iRow & 1 else 'tmeven', iDepth, oTestResult.enmStatus, oTestResult.idTestResult,
    318318                           webutils.escapeElem(self.formatTsShort(oTestResult.tsCreated + oTestResult.tsElapsed)),
    319319                           sElapsedGraph,
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