diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
commit | f8fe689a81f906d1b91bb3220acde2a4ecb14c5b (patch) | |
tree | 26484e9d7e2c67806c2d1760196ff01aaa858e8c /src/VBox/ValidationKit/testmanager/webui/wuitestresult.py | |
parent | Initial commit. (diff) | |
download | virtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.tar.xz virtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.zip |
Adding upstream version 6.0.4-dfsg.upstream/6.0.4-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/ValidationKit/testmanager/webui/wuitestresult.py')
-rwxr-xr-x | src/VBox/ValidationKit/testmanager/webui/wuitestresult.py | 912 |
1 files changed, 912 insertions, 0 deletions
diff --git a/src/VBox/ValidationKit/testmanager/webui/wuitestresult.py b/src/VBox/ValidationKit/testmanager/webui/wuitestresult.py new file mode 100755 index 00000000..0506bfb5 --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuitestresult.py @@ -0,0 +1,912 @@ +# -*- coding: utf-8 -*- +# $Id: wuitestresult.py $ + +""" +Test Manager WUI - Test Results. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2019 Oracle Corporation + +This file is part of VirtualBox Open Source Edition (OSE), as +available from http://www.virtualbox.org. This file is free software; +you can redistribute it and/or modify it under the terms of the GNU +General Public License (GPL) as published by the Free Software +Foundation, in version 2 as it comes in the "COPYING" file of the +VirtualBox OSE distribution. VirtualBox OSE is distributed in the +hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL) only, as it comes in the "COPYING.CDDL" file of the +VirtualBox OSE distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. +""" +__version__ = "$Revision: 127855 $" + +# Python imports. +import datetime; + +# Validation Kit imports. +from testmanager.webui.wuicontentbase import WuiContentBase, WuiListContentBase, WuiHtmlBase, WuiTmLink, WuiLinkBase, \ + WuiSvnLink, WuiSvnLinkWithTooltip, WuiBuildLogLink, WuiRawHtml, \ + WuiHtmlKeeper; +from testmanager.webui.wuimain import WuiMain; +from testmanager.webui.wuihlpform import WuiHlpForm; +from testmanager.webui.wuiadminfailurereason import WuiFailureReasonAddLink, WuiFailureReasonDetailsLink; +from testmanager.webui.wuitestresultfailure import WuiTestResultFailureDetailsLink; +from testmanager.core.failurereason import FailureReasonData, FailureReasonLogic; +from testmanager.core.report import ReportGraphModel, ReportModelBase; +from testmanager.core.testbox import TestBoxData; +from testmanager.core.testcase import TestCaseData; +from testmanager.core.testset import TestSetData; +from testmanager.core.testgroup import TestGroupData; +from testmanager.core.testresultfailures import TestResultFailureData; +from testmanager.core.build import BuildData; +from testmanager.core import db; +from testmanager import config; +from common import webutils, utils; + + +class WuiTestSetLink(WuiTmLink): + """ Test set link. """ + + def __init__(self, idTestSet, sName = WuiContentBase.ksShortDetailsLink, fBracketed = False): + WuiTmLink.__init__(self, sName, WuiMain.ksScriptName, + { WuiMain.ksParamAction: WuiMain.ksActionTestResultDetails, + TestSetData.ksParam_idTestSet: idTestSet, }, fBracketed = fBracketed); + self.idTestSet = idTestSet; + + + +class WuiTestResult(WuiContentBase): + """Display test case result""" + + def __init__(self, fnDPrint = None, oDisp = None): + WuiContentBase.__init__(self, fnDPrint = fnDPrint, oDisp = oDisp); + + # Cyclic import hacks. + from testmanager.webui.wuiadmin import WuiAdmin; + self.oWuiAdmin = WuiAdmin; + + def _toHtml(self, oObject): + """Translate some object to HTML.""" + if isinstance(oObject, WuiHtmlBase): + return oObject.toHtml(); + if db.isDbTimestamp(oObject): + return webutils.escapeElem(self.formatTsShort(oObject)); + if db.isDbInterval(oObject): + return webutils.escapeElem(self.formatIntervalShort(oObject)); + if utils.isString(oObject): + return webutils.escapeElem(oObject); + return webutils.escapeElem(str(oObject)); + + def _htmlTable(self, aoTableContent): + """Generate HTML code for table""" + sHtml = u' <table class="tmtbl-testresult-details" width="100%%">\n'; + + for aoSubRows in aoTableContent: + if not aoSubRows: + continue; # Can happen if there is no testsuit. + oCaption = aoSubRows[0]; + sHtml += u' \n' \ + u' <tr class="tmtbl-result-details-caption">\n' \ + u' <td colspan="2">%s</td>\n' \ + u' </tr>\n' \ + % (self._toHtml(oCaption),); + + iRow = 0; + for aoRow in aoSubRows[1:]: + iRow += 1; + sHtml += u' <tr class="%s">\n' % ('tmodd' if iRow & 1 else 'tmeven',); + if len(aoRow) == 1: + sHtml += u' <td class="tmtbl-result-details-subcaption" colspan="2">%s</td>\n' \ + % (self._toHtml(aoRow[0]),); + else: + sHtml += u' <th scope="row">%s</th>\n' % (webutils.escapeElem(aoRow[0]),); + if len(aoRow) > 2: + sHtml += u' <td>%s</td>\n' % (aoRow[2](aoRow[1]),); + else: + sHtml += u' <td>%s</td>\n' % (self._toHtml(aoRow[1]),); + sHtml += u' </tr>\n'; + + sHtml += u' </table>\n'; + + return sHtml + + def _highlightStatus(self, sStatus): + """Return sStatus string surrounded by HTML highlight code """ + sTmp = '<font color=%s><b>%s</b></font>' \ + % ('red' if sStatus == 'failure' else 'green', webutils.escapeElem(sStatus.upper())) + return sTmp + + def _anchorAndAppendBinaries(self, sBinaries, aoRows): + """ Formats each binary (if any) into a row with a download link. """ + if sBinaries is not None: + for sBinary in sBinaries.split(','): + if not webutils.hasSchema(sBinary): + sBinary = config.g_ksBuildBinUrlPrefix + sBinary; + aoRows.append([WuiLinkBase(webutils.getFilename(sBinary), sBinary, fBracketed = False),]); + return aoRows; + + + def _formatEventTimestampHtml(self, tsEvent, tsLog, idEvent, oTestSet): + """ Formats an event timestamp with a main log link. """ + tsEvent = db.dbTimestampToZuluDatetime(tsEvent); + #sFormattedTimestamp = u'%04u\u2011%02u\u2011%02u\u00a0%02u:%02u:%02uZ' \ + # % ( tsEvent.year, tsEvent.month, tsEvent.day, + # tsEvent.hour, tsEvent.minute, tsEvent.second,); + sFormattedTimestamp = u'%02u:%02u:%02uZ' \ + % ( tsEvent.hour, tsEvent.minute, tsEvent.second,); + sTitle = u'#%u - %04u\u2011%02u\u2011%02u\u00a0%02u:%02u:%02u.%06uZ' \ + % ( idEvent, tsEvent.year, tsEvent.month, tsEvent.day, + tsEvent.hour, tsEvent.minute, tsEvent.second, tsEvent.microsecond, ); + tsLog = db.dbTimestampToZuluDatetime(tsLog); + sFragment = u'%02u_%02u_%02u_%06u' % ( tsLog.hour, tsLog.minute, tsLog.second, tsLog.microsecond); + return WuiTmLink(sFormattedTimestamp, '', + { WuiMain.ksParamAction: WuiMain.ksActionViewLog, + WuiMain.ksParamLogSetId: oTestSet.idTestSet, }, + sFragmentId = sFragment, sTitle = sTitle, fBracketed = False, ).toHtml(); + + def _recursivelyGenerateEvents(self, oTestResult, sParentName, sLineage, iRow, + iFailure, oTestSet, iDepth): # pylint: disable=R0914 + """ + Recursively generate event table rows for the result set. + + oTestResult is an object of the type TestResultDataEx. + """ + # Hack: Replace empty outer test result name with (pretty) command line. + if iRow == 1: + sName = ''; + sDisplayName = sParentName; + else: + sName = oTestResult.sName if sParentName == '' else '%s, %s' % (sParentName, oTestResult.sName,); + sDisplayName = webutils.escapeElem(sName); + + # Format error count. + sErrCnt = ''; + if oTestResult.cErrors > 0: + sErrCnt = ' (1 error)' if oTestResult.cErrors == 1 else ' (%d errors)' % oTestResult.cErrors; + + # Format bits for adding or editing the failure reason. Level 0 is handled at the top of the page. + sChangeReason = ''; + if oTestResult.cErrors > 0 and iDepth > 0 and self._oDisp is not None and not self._oDisp.isReadOnlyUser(): + dTmp = { + self._oDisp.ksParamAction: self._oDisp.ksActionTestResultFailureAdd if oTestResult.oReason is None else + self._oDisp.ksActionTestResultFailureEdit, + TestResultFailureData.ksParam_idTestResult: oTestResult.idTestResult, + }; + sChangeReason = ' <a href="?%s" class="tmtbl-edit-reason" onclick="addRedirectToAnchorHref(this)">%s</a> ' \ + % ( webutils.encodeUrlParams(dTmp), WuiContentBase.ksShortEditLinkHtml ); + + # Format the include in graph checkboxes. + sLineage += ':%u' % (oTestResult.idStrName,); + sResultGraph = '<input type="checkbox" name="%s" value="%s%s" title="Include result in graph."/>' \ + % (WuiMain.ksParamReportSubjectIds, ReportGraphModel.ksTypeResult, sLineage,); + sElapsedGraph = ''; + if oTestResult.tsElapsed is not None: + sElapsedGraph = '<input type="checkbox" name="%s" value="%s%s" title="Include elapsed time in graph."/>' \ + % ( WuiMain.ksParamReportSubjectIds, ReportGraphModel.ksTypeElapsed, sLineage); + + + if not oTestResult.aoChildren \ + and len(oTestResult.aoValues) + len(oTestResult.aoMsgs) + len(oTestResult.aoFiles) == 0: + # Leaf - single row. + tsEvent = oTestResult.tsCreated; + if oTestResult.tsElapsed is not None: + tsEvent += oTestResult.tsElapsed; + sHtml = ' <tr class="%s tmtbl-events-leaf tmtbl-events-lvl%s tmstatusrow-%s" id="S%u">\n' \ + ' <td id="E%u">%s</td>\n' \ + ' <td>%s</td>\n' \ + ' <td>%s</td>\n' \ + ' <td>%s</td>\n' \ + ' <td colspan="2"%s>%s%s%s</td>\n' \ + ' <td>%s</td>\n' \ + ' </tr>\n' \ + % ( 'tmodd' if iRow & 1 else 'tmeven', iDepth, oTestResult.enmStatus, oTestResult.idTestResult, + oTestResult.idTestResult, + self._formatEventTimestampHtml(tsEvent, oTestResult.tsCreated, oTestResult.idTestResult, oTestSet), + sElapsedGraph, + webutils.escapeElem(self.formatIntervalShort(oTestResult.tsElapsed)) if oTestResult.tsElapsed is not None + else '', + sDisplayName, + ' id="failure-%u"' % (iFailure,) if oTestResult.isFailure() else '', + webutils.escapeElem(oTestResult.enmStatus), webutils.escapeElem(sErrCnt), + sChangeReason if oTestResult.oReason is None else '', + sResultGraph ); + iRow += 1; + else: + # Multiple rows. + sHtml = ' <tr class="%s tmtbl-events-first tmtbl-events-lvl%s ">\n' \ + ' <td>%s</td>\n' \ + ' <td></td>\n' \ + ' <td></td>\n' \ + ' <td>%s</td>\n' \ + ' <td colspan="2">%s</td>\n' \ + ' <td></td>\n' \ + ' </tr>\n' \ + % ( 'tmodd' if iRow & 1 else 'tmeven', iDepth, + self._formatEventTimestampHtml(oTestResult.tsCreated, oTestResult.tsCreated, + oTestResult.idTestResult, oTestSet), + sDisplayName, + 'running' if oTestResult.tsElapsed is None else '', ); + iRow += 1; + + # Depth. Check if our error count is just reflecting the one of our children. + cErrorsBelow = 0; + for oChild in oTestResult.aoChildren: + (sChildHtml, iRow, iFailure) = self._recursivelyGenerateEvents(oChild, sName, sLineage, + iRow, iFailure, oTestSet, iDepth + 1); + sHtml += sChildHtml; + cErrorsBelow += oChild.cErrors; + + # Messages. + for oMsg in oTestResult.aoMsgs: + sHtml += ' <tr class="%s tmtbl-events-message tmtbl-events-lvl%s">\n' \ + ' <td>%s</td>\n' \ + ' <td></td>\n' \ + ' <td></td>\n' \ + ' <td colspan="3">%s: %s</td>\n' \ + ' <td></td>\n' \ + ' </tr>\n' \ + % ( 'tmodd' if iRow & 1 else 'tmeven', iDepth, + self._formatEventTimestampHtml(oMsg.tsCreated, oMsg.tsCreated, oMsg.idTestResultMsg, oTestSet), + webutils.escapeElem(oMsg.enmLevel), + webutils.escapeElem(oMsg.sMsg), ); + iRow += 1; + + # Values. + for oValue in oTestResult.aoValues: + sHtml += ' <tr class="%s tmtbl-events-value tmtbl-events-lvl%s">\n' \ + ' <td>%s</td>\n' \ + ' <td></td>\n' \ + ' <td></td>\n' \ + ' <td>%s</td>\n' \ + ' <td class="tmtbl-events-number">%s</td>\n' \ + ' <td class="tmtbl-events-unit">%s</td>\n' \ + ' <td><input type="checkbox" name="%s" value="%s%s:%u" title="Include value in graph."></td>\n' \ + ' </tr>\n' \ + % ( 'tmodd' if iRow & 1 else 'tmeven', iDepth, + self._formatEventTimestampHtml(oValue.tsCreated, oValue.tsCreated, oValue.idTestResultValue, oTestSet), + webutils.escapeElem(oValue.sName), + utils.formatNumber(oValue.lValue).replace(' ', ' '), + webutils.escapeElem(oValue.sUnit), + WuiMain.ksParamReportSubjectIds, ReportGraphModel.ksTypeValue, sLineage, oValue.idStrName, ); + iRow += 1; + + # Files. + for oFile in oTestResult.aoFiles: + if oFile.sMime in [ 'text/plain', ]: + aoLinks = [ + WuiTmLink('%s (%s)' % (oFile.sFile, oFile.sKind), '', + { self._oDisp.ksParamAction: self._oDisp.ksActionViewLog, + self._oDisp.ksParamLogSetId: oTestSet.idTestSet, + self._oDisp.ksParamLogFileId: oFile.idTestResultFile, }, + sTitle = oFile.sDescription), + WuiTmLink('View Raw', '', + { self._oDisp.ksParamAction: self._oDisp.ksActionGetFile, + self._oDisp.ksParamGetFileSetId: oTestSet.idTestSet, + self._oDisp.ksParamGetFileId: oFile.idTestResultFile, + self._oDisp.ksParamGetFileDownloadIt: False, }, + sTitle = oFile.sDescription), + ] + else: + aoLinks = [ + WuiTmLink('%s (%s)' % (oFile.sFile, oFile.sKind), '', + { self._oDisp.ksParamAction: self._oDisp.ksActionGetFile, + self._oDisp.ksParamGetFileSetId: oTestSet.idTestSet, + self._oDisp.ksParamGetFileId: oFile.idTestResultFile, + self._oDisp.ksParamGetFileDownloadIt: False, }, + sTitle = oFile.sDescription), + ] + aoLinks.append(WuiTmLink('Download', '', + { self._oDisp.ksParamAction: self._oDisp.ksActionGetFile, + self._oDisp.ksParamGetFileSetId: oTestSet.idTestSet, + self._oDisp.ksParamGetFileId: oFile.idTestResultFile, + self._oDisp.ksParamGetFileDownloadIt: True, }, + sTitle = oFile.sDescription)); + + sHtml += ' <tr class="%s tmtbl-events-file tmtbl-events-lvl%s">\n' \ + ' <td>%s</td>\n' \ + ' <td></td>\n' \ + ' <td></td>\n' \ + ' <td>%s</td>\n' \ + ' <td></td>\n' \ + ' <td></td>\n' \ + ' <td></td>\n' \ + ' </tr>\n' \ + % ( 'tmodd' if iRow & 1 else 'tmeven', iDepth, + self._formatEventTimestampHtml(oFile.tsCreated, oFile.tsCreated, oFile.idTestResultFile, oTestSet), + '\n'.join(oLink.toHtml() for oLink in aoLinks),); + iRow += 1; + + # Done? + if oTestResult.tsElapsed is not None: + tsEvent = oTestResult.tsCreated + oTestResult.tsElapsed; + sHtml += ' <tr class="%s tmtbl-events-final tmtbl-events-lvl%s tmstatusrow-%s" id="E%d">\n' \ + ' <td>%s</td>\n' \ + ' <td>%s</td>\n' \ + ' <td>%s</td>\n' \ + ' <td>%s</td>\n' \ + ' <td colspan="2"%s>%s%s%s</td>\n' \ + ' <td>%s</td>\n' \ + ' </tr>\n' \ + % ( 'tmodd' if iRow & 1 else 'tmeven', iDepth, oTestResult.enmStatus, oTestResult.idTestResult, + self._formatEventTimestampHtml(tsEvent, tsEvent, oTestResult.idTestResult, oTestSet), + sElapsedGraph, + webutils.escapeElem(self.formatIntervalShort(oTestResult.tsElapsed)), + sDisplayName, + ' id="failure-%u"' % (iFailure,) if oTestResult.isFailure() else '', + webutils.escapeElem(oTestResult.enmStatus), webutils.escapeElem(sErrCnt), + sChangeReason if cErrorsBelow < oTestResult.cErrors and oTestResult.oReason is None else '', + sResultGraph); + iRow += 1; + + # Failure reason. + if oTestResult.oReason is not None: + sReasonText = '%s / %s' % ( oTestResult.oReason.oFailureReason.oCategory.sShort, + oTestResult.oReason.oFailureReason.sShort, ); + sCommentHtml = ''; + if oTestResult.oReason.sComment and oTestResult.oReason.sComment.strip(): + sCommentHtml = '<br>' + webutils.escapeElem(oTestResult.oReason.sComment.strip()); + sCommentHtml = sCommentHtml.replace('\n', '<br>'); + + sDetailedReason = ' <a href="?%s" class="tmtbl-show-reason">%s</a>' \ + % ( webutils.encodeUrlParams({ self._oDisp.ksParamAction: + self._oDisp.ksActionTestResultFailureDetails, + TestResultFailureData.ksParam_idTestResult: + oTestResult.idTestResult,}), + WuiContentBase.ksShortDetailsLinkHtml,); + + sHtml += ' <tr class="%s tmtbl-events-reason tmtbl-events-lvl%s">\n' \ + ' <td>%s</td>\n' \ + ' <td colspan="2">%s</td>\n' \ + ' <td colspan="3">%s%s%s%s</td>\n' \ + ' <td>%s</td>\n' \ + ' </tr>\n' \ + % ( 'tmodd' if iRow & 1 else 'tmeven', iDepth, + webutils.escapeElem(self.formatTsShort(oTestResult.oReason.tsEffective)), + oTestResult.oReason.oAuthor.sUsername, + webutils.escapeElem(sReasonText), sDetailedReason, sChangeReason, + sCommentHtml, + 'todo'); + iRow += 1; + + if oTestResult.isFailure(): + iFailure += 1; + + return (sHtml, iRow, iFailure); + + + def _generateMainReason(self, oTestResultTree, oTestSet): + """ + Generates the form for displaying and updating the main failure reason. + + oTestResultTree is an instance TestResultDataEx. + oTestSet is an instance of TestSetData. + + """ + _ = oTestSet; + sHtml = ' '; + + if oTestResultTree.isFailure() or oTestResultTree.cErrors > 0: + sHtml += ' <h2>Failure Reason:</h2>\n'; + oData = oTestResultTree.oReason; + + # We need the failure reasons for the combobox. + aoFailureReasons = FailureReasonLogic(self._oDisp.getDb()).fetchForCombo('Test Sheriff, you figure out why!'); + assert aoFailureReasons; + + # For now we'll use the standard form helper. + sFormActionUrl = '%s?%s=%s' % ( self._oDisp.ksScriptName, self._oDisp.ksParamAction, + WuiMain.ksActionTestResultFailureAddPost if oData is None else + WuiMain.ksActionTestResultFailureEditPost ) + fReadOnly = not self._oDisp or self._oDisp.isReadOnlyUser(); + oForm = WuiHlpForm('failure-reason', sFormActionUrl, + sOnSubmit = WuiHlpForm.ksOnSubmit_AddReturnToFieldWithCurrentUrl, fReadOnly = fReadOnly); + oForm.addTextHidden(TestResultFailureData.ksParam_idTestResult, oTestResultTree.idTestResult); + oForm.addTextHidden(TestResultFailureData.ksParam_idTestSet, oTestSet.idTestSet); + if oData is not None: + oForm.addComboBox(TestResultFailureData.ksParam_idFailureReason, oData.idFailureReason, 'Reason', + aoFailureReasons, + sPostHtml = u' ' + WuiFailureReasonDetailsLink(oData.idFailureReason).toHtml() + + (u' ' + WuiFailureReasonAddLink('New', fBracketed = False).toHtml() + if not fReadOnly else u'')); + oForm.addMultilineText(TestResultFailureData.ksParam_sComment, oData.sComment, 'Comment') + + oForm.addNonText(u'%s (%s), %s' + % ( oData.oAuthor.sUsername, oData.oAuthor.sUsername, + self.formatTsShort(oData.tsEffective),), + 'Sheriff', + sPostHtml = ' ' + WuiTestResultFailureDetailsLink(oData.idTestResult, "Show Details").toHtml() ) + + oForm.addTextHidden(TestResultFailureData.ksParam_tsEffective, oData.tsEffective); + oForm.addTextHidden(TestResultFailureData.ksParam_tsExpire, oData.tsExpire); + oForm.addTextHidden(TestResultFailureData.ksParam_uidAuthor, oData.uidAuthor); + oForm.addSubmit('Change Reason'); + else: + oForm.addComboBox(TestResultFailureData.ksParam_idFailureReason, -1, 'Reason', aoFailureReasons, + sPostHtml = ' ' + WuiFailureReasonAddLink('New').toHtml() if not fReadOnly else ''); + oForm.addMultilineText(TestResultFailureData.ksParam_sComment, '', 'Comment'); + oForm.addTextHidden(TestResultFailureData.ksParam_tsEffective, ''); + oForm.addTextHidden(TestResultFailureData.ksParam_tsExpire, ''); + oForm.addTextHidden(TestResultFailureData.ksParam_uidAuthor, ''); + oForm.addSubmit('Add Reason'); + + sHtml += oForm.finalize(); + return sHtml; + + + def showTestCaseResultDetails(self, # pylint: disable=R0914,R0915 + oTestResultTree, + oTestSet, + oBuildEx, + oValidationKitEx, + oTestBox, + oTestGroup, + oTestCaseEx, + oTestVarEx): + """Show detailed result""" + def getTcDepsHtmlList(aoTestCaseData): + """Get HTML <ul> list of Test Case name items""" + if aoTestCaseData: + sTmp = '<ul>' + for oTestCaseData in aoTestCaseData: + sTmp += '<li>%s</li>' % (webutils.escapeElem(oTestCaseData.sName),); + sTmp += '</ul>' + else: + sTmp = 'No items' + return sTmp + + def getGrDepsHtmlList(aoGlobalResourceData): + """Get HTML <ul> list of Global Resource name items""" + if aoGlobalResourceData: + sTmp = '<ul>' + for oGlobalResourceData in aoGlobalResourceData: + sTmp += '<li>%s</li>' % (webutils.escapeElem(oGlobalResourceData.sName),); + sTmp += '</ul>' + else: + sTmp = 'No items' + return sTmp + + + asHtml = [] + + from testmanager.webui.wuireport import WuiReportSummaryLink; + tsReportEffectiveDate = None; + if oTestSet.tsDone is not None: + tsReportEffectiveDate = oTestSet.tsDone + datetime.timedelta(days = 4); + if tsReportEffectiveDate >= self.getNowTs(): + tsReportEffectiveDate = None; + + # Test result + test set details. + aoResultRows = [ + WuiHtmlKeeper([ WuiTmLink(oTestCaseEx.sName, self.oWuiAdmin.ksScriptName, + { self.oWuiAdmin.ksParamAction: self.oWuiAdmin.ksActionTestCaseDetails, + TestCaseData.ksParam_idTestCase: oTestCaseEx.idTestCase, + self.oWuiAdmin.ksParamEffectiveDate: oTestSet.tsConfig, }, + fBracketed = False), + WuiReportSummaryLink(ReportModelBase.ksSubTestCase, oTestCaseEx.idTestCase, + tsNow = tsReportEffectiveDate, fBracketed = False), + ]), + ]; + if oTestCaseEx.sDescription: + aoResultRows.append([oTestCaseEx.sDescription,]); + aoResultRows.append([ 'Status:', WuiRawHtml('<span class="tmspan-status-%s">%s</span>' + % (oTestResultTree.enmStatus, oTestResultTree.enmStatus,))]); + if oTestResultTree.cErrors > 0: + aoResultRows.append(( 'Errors:', oTestResultTree.cErrors )); + aoResultRows.append([ 'Elapsed:', oTestResultTree.tsElapsed ]); + cSecCfgTimeout = oTestCaseEx.cSecTimeout if oTestVarEx.cSecTimeout is None else oTestVarEx.cSecTimeout; + cSecEffTimeout = cSecCfgTimeout * oTestBox.pctScaleTimeout / 100; + aoResultRows.append([ 'Timeout:', + '%s (%s sec)' % (utils.formatIntervalSeconds(cSecEffTimeout), cSecEffTimeout,) ]); + if cSecEffTimeout != cSecCfgTimeout: + aoResultRows.append([ 'Cfg Timeout:', + '%s (%s sec)' % (utils.formatIntervalSeconds(cSecCfgTimeout), cSecCfgTimeout,) ]); + aoResultRows += [ + ( 'Started:', WuiTmLink(self.formatTsShort(oTestSet.tsCreated), WuiMain.ksScriptName, + { WuiMain.ksParamAction: WuiMain.ksActionResultsUnGrouped, + WuiMain.ksParamEffectiveDate: oTestSet.tsCreated, }, + fBracketed = False) ), + ]; + if oTestSet.tsDone is not None: + aoResultRows += [ ( 'Done:', + WuiTmLink(self.formatTsShort(oTestSet.tsDone), WuiMain.ksScriptName, + { WuiMain.ksParamAction: WuiMain.ksActionResultsUnGrouped, + WuiMain.ksParamEffectiveDate: oTestSet.tsDone, }, + fBracketed = False) ) ]; + else: + aoResultRows += [( 'Done:', 'Still running...')]; + aoResultRows += [( 'Config:', oTestSet.tsConfig )]; + if oTestVarEx.cGangMembers > 1: + aoResultRows.append([ 'Member No:', '#%s (of %s)' % (oTestSet.iGangMemberNo, oTestVarEx.cGangMembers) ]); + + aoResultRows += [ + ( 'Test Group:', + WuiHtmlKeeper([ WuiTmLink(oTestGroup.sName, self.oWuiAdmin.ksScriptName, + { self.oWuiAdmin.ksParamAction: self.oWuiAdmin.ksActionTestGroupDetails, + TestGroupData.ksParam_idTestGroup: oTestGroup.idTestGroup, + self.oWuiAdmin.ksParamEffectiveDate: oTestSet.tsConfig, }, + fBracketed = False), + WuiReportSummaryLink(ReportModelBase.ksSubTestGroup, oTestGroup.idTestGroup, + tsNow = tsReportEffectiveDate, fBracketed = False), + ]), ), + ]; + if oTestVarEx.sTestBoxReqExpr is not None: + aoResultRows.append([ 'TestBox reqs:', oTestVarEx.sTestBoxReqExpr ]); + elif oTestCaseEx.sTestBoxReqExpr is not None or oTestVarEx.sTestBoxReqExpr is not None: + aoResultRows.append([ 'TestBox reqs:', oTestCaseEx.sTestBoxReqExpr ]); + if oTestVarEx.sBuildReqExpr is not None: + aoResultRows.append([ 'Build reqs:', oTestVarEx.sBuildReqExpr ]); + elif oTestCaseEx.sBuildReqExpr is not None or oTestVarEx.sBuildReqExpr is not None: + aoResultRows.append([ 'Build reqs:', oTestCaseEx.sBuildReqExpr ]); + if oTestCaseEx.sValidationKitZips is not None and oTestCaseEx.sValidationKitZips != '@VALIDATIONKIT_ZIP@': + aoResultRows.append([ 'Validation Kit:', oTestCaseEx.sValidationKitZips ]); + if oTestCaseEx.aoDepTestCases: + aoResultRows.append([ 'Prereq. Test Cases:', oTestCaseEx.aoDepTestCases, getTcDepsHtmlList ]); + if oTestCaseEx.aoDepGlobalResources: + aoResultRows.append([ 'Global Resources:', oTestCaseEx.aoDepGlobalResources, getGrDepsHtmlList ]); + + # Builds. + aoBuildRows = []; + if oBuildEx is not None: + aoBuildRows += [ + WuiHtmlKeeper([ WuiTmLink('Build', self.oWuiAdmin.ksScriptName, + { self.oWuiAdmin.ksParamAction: self.oWuiAdmin.ksActionBuildDetails, + BuildData.ksParam_idBuild: oBuildEx.idBuild, + self.oWuiAdmin.ksParamEffectiveDate: oTestSet.tsCreated, }, + fBracketed = False), + WuiReportSummaryLink(ReportModelBase.ksSubBuild, oBuildEx.idBuild, + tsNow = tsReportEffectiveDate, fBracketed = False), ]), + ]; + self._anchorAndAppendBinaries(oBuildEx.sBinaries, aoBuildRows); + aoBuildRows += [ + ( 'Revision:', WuiSvnLinkWithTooltip(oBuildEx.iRevision, oBuildEx.oCat.sRepository, + fBracketed = False) ), + ( 'Product:', oBuildEx.oCat.sProduct ), + ( 'Branch:', oBuildEx.oCat.sBranch ), + ( 'Type:', oBuildEx.oCat.sType ), + ( 'Version:', oBuildEx.sVersion ), + ( 'Created:', oBuildEx.tsCreated ), + ]; + if oBuildEx.uidAuthor is not None: + aoBuildRows += [ ( 'Author ID:', oBuildEx.uidAuthor ), ]; + if oBuildEx.sLogUrl is not None: + aoBuildRows += [ ( 'Log:', WuiBuildLogLink(oBuildEx.sLogUrl, fBracketed = False) ), ]; + + aoValidationKitRows = []; + if oValidationKitEx is not None: + aoValidationKitRows += [ + WuiTmLink('Validation Kit', self.oWuiAdmin.ksScriptName, + { self.oWuiAdmin.ksParamAction: self.oWuiAdmin.ksActionBuildDetails, + BuildData.ksParam_idBuild: oValidationKitEx.idBuild, + self.oWuiAdmin.ksParamEffectiveDate: oTestSet.tsCreated, }, + fBracketed = False), + ]; + self._anchorAndAppendBinaries(oValidationKitEx.sBinaries, aoValidationKitRows); + aoValidationKitRows += [ ( 'Revision:', WuiSvnLink(oValidationKitEx.iRevision, fBracketed = False) ) ]; + if oValidationKitEx.oCat.sProduct != 'VBox TestSuite': + aoValidationKitRows += [ ( 'Product:', oValidationKitEx.oCat.sProduct ), ]; + if oValidationKitEx.oCat.sBranch != 'trunk': + aoValidationKitRows += [ ( 'Product:', oValidationKitEx.oCat.sBranch ), ]; + if oValidationKitEx.oCat.sType != 'release': + aoValidationKitRows += [ ( 'Type:', oValidationKitEx.oCat.sType), ]; + if oValidationKitEx.sVersion != '0.0.0': + aoValidationKitRows += [ ( 'Version:', oValidationKitEx.sVersion ), ]; + aoValidationKitRows += [ + ( 'Created:', oValidationKitEx.tsCreated ), + ]; + if oValidationKitEx.uidAuthor is not None: + aoValidationKitRows += [ ( 'Author ID:', oValidationKitEx.uidAuthor ), ]; + if oValidationKitEx.sLogUrl is not None: + aoValidationKitRows += [ ( 'Log:', WuiBuildLogLink(oValidationKitEx.sLogUrl, fBracketed = False) ), ]; + + # TestBox. + aoTestBoxRows = [ + WuiHtmlKeeper([ WuiTmLink(oTestBox.sName, self.oWuiAdmin.ksScriptName, + { self.oWuiAdmin.ksParamAction: self.oWuiAdmin.ksActionTestBoxDetails, + TestBoxData.ksParam_idGenTestBox: oTestSet.idGenTestBox, }, + fBracketed = False), + WuiReportSummaryLink(ReportModelBase.ksSubTestBox, oTestSet.idTestBox, + tsNow = tsReportEffectiveDate, fBracketed = False), ]), + ]; + if oTestBox.sDescription: + aoTestBoxRows.append([oTestBox.sDescription, ]); + aoTestBoxRows += [ + ( 'IP:', oTestBox.ip ), + #( 'UUID:', oTestBox.uuidSystem ), + #( 'Enabled:', oTestBox.fEnabled ), + #( 'Lom Kind:', oTestBox.enmLomKind ), + #( 'Lom IP:', oTestBox.ipLom ), + ( 'OS/Arch:', '%s.%s' % (oTestBox.sOs, oTestBox.sCpuArch) ), + ( 'OS Version:', oTestBox.sOsVersion ), + ( 'CPUs:', oTestBox.cCpus ), + ]; + if oTestBox.sCpuName is not None: + aoTestBoxRows.append(['CPU Name', oTestBox.sCpuName.replace(' ', ' ')]); + if oTestBox.lCpuRevision is not None: + sMarch = oTestBox.queryCpuMicroarch(); + if sMarch is not None: + aoTestBoxRows.append( ('CPU Microarch', sMarch) ); + uFamily = oTestBox.getCpuFamily(); + uModel = oTestBox.getCpuModel(); + uStepping = oTestBox.getCpuStepping(); + aoTestBoxRows += [ + ( 'CPU Family', '%u (%#x)' % ( uFamily, uFamily, ) ), + ( 'CPU Model', '%u (%#x)' % ( uModel, uModel, ) ), + ( 'CPU Stepping', '%u (%#x)' % ( uStepping, uStepping, ) ), + ]; + asFeatures = [ oTestBox.sCpuVendor, ]; + if oTestBox.fCpuHwVirt is True: asFeatures.append(u'HW\u2011Virt'); + if oTestBox.fCpuNestedPaging is True: asFeatures.append(u'Nested\u2011Paging'); + if oTestBox.fCpu64BitGuest is True: asFeatures.append(u'64\u2011bit\u2011Guest'); + if oTestBox.fChipsetIoMmu is True: asFeatures.append(u'I/O\u2011MMU'); + aoTestBoxRows += [ + ( 'Features:', u' '.join(asFeatures) ), + ( 'RAM size:', '%s MB' % (oTestBox.cMbMemory,) ), + ( 'Scratch Size:', '%s MB' % (oTestBox.cMbScratch,) ), + ( 'Scale Timeout:', '%s%%' % (oTestBox.pctScaleTimeout,) ), + ( 'Script Rev:', WuiSvnLink(oTestBox.iTestBoxScriptRev, fBracketed = False) ), + ( 'Python:', oTestBox.formatPythonVersion() ), + ( 'Pending Command:', oTestBox.enmPendingCmd ), + ]; + + aoRows = [ + aoResultRows, + aoBuildRows, + aoValidationKitRows, + aoTestBoxRows, + ]; + + asHtml.append(self._htmlTable(aoRows)); + + # + # Convert the tree to a list of events, values, message and files. + # + sHtmlEvents = ''; + sHtmlEvents += '<table class="tmtbl-events" id="tmtbl-events" width="100%">\n'; + sHtmlEvents += ' <tr class="tmheader">\n' \ + ' <th>When</th>\n' \ + ' <th></th>\n' \ + ' <th>Elapsed</th>\n' \ + ' <th>Event name</th>\n' \ + ' <th colspan="2">Value (status)</th>' \ + ' <th></th>\n' \ + ' </tr>\n'; + sPrettyCmdLine = ' \\<br> \n'.join(webutils.escapeElem(oTestCaseEx.sBaseCmd + + ' ' + + oTestVarEx.sArgs).split() ); + (sTmp, _, cFailures) = self._recursivelyGenerateEvents(oTestResultTree, sPrettyCmdLine, '', 1, 0, oTestSet, 0); + sHtmlEvents += sTmp; + + sHtmlEvents += '</table>\n' + + # + # Put it all together. + # + sHtml = '<table class="tmtbl-testresult-details-base" width="100%">\n'; + sHtml += ' <tr>\n' + sHtml += ' <td valign="top" width="20%%">\n%s\n</td>\n' % ' <br>\n'.join(asHtml); + + sHtml += ' <td valign="top" width="80%" style="padding-left:6px">\n'; + sHtml += self._generateMainReason(oTestResultTree, oTestSet); + + sHtml += ' <h2>Events:</h2>\n'; + sHtml += ' <form action="#" method="get" id="graph-form">\n' \ + ' <input type="hidden" name="%s" value="%s"/>\n' \ + ' <input type="hidden" name="%s" value="%u"/>\n' \ + ' <input type="hidden" name="%s" value="%u"/>\n' \ + ' <input type="hidden" name="%s" value="%u"/>\n' \ + ' <input type="hidden" name="%s" value="%u"/>\n' \ + % ( WuiMain.ksParamAction, WuiMain.ksActionGraphWiz, + WuiMain.ksParamGraphWizTestBoxIds, oTestBox.idTestBox, + WuiMain.ksParamGraphWizBuildCatIds, oBuildEx.idBuildCategory, + WuiMain.ksParamGraphWizTestCaseIds, oTestSet.idTestCase, + WuiMain.ksParamGraphWizSrcTestSetId, oTestSet.idTestSet, + ); + if oTestSet.tsDone is not None: + sHtml += ' <input type="hidden" name="%s" value="%s"/>\n' \ + % ( WuiMain.ksParamEffectiveDate, oTestSet.tsDone, ); + sHtml += ' <p>\n'; + sFormButton = '<button type="submit" onclick="%s">Show graphs</button>' \ + % ( webutils.escapeAttr('addDynamicGraphInputs("graph-form", "main", "%s", "%s");' + % (WuiMain.ksParamGraphWizWidth, WuiMain.ksParamGraphWizDpi, )) ); + sHtml += ' ' + sFormButton + '\n'; + sHtml += ' %s %s %s\n' \ + % ( WuiTmLink('Log File', '', + { WuiMain.ksParamAction: WuiMain.ksActionViewLog, + WuiMain.ksParamLogSetId: oTestSet.idTestSet, + }), + WuiTmLink('Raw Log', '', + { WuiMain.ksParamAction: WuiMain.ksActionGetFile, + WuiMain.ksParamGetFileSetId: oTestSet.idTestSet, + WuiMain.ksParamGetFileDownloadIt: False, + }), + WuiTmLink('Download Log', '', + { WuiMain.ksParamAction: WuiMain.ksActionGetFile, + WuiMain.ksParamGetFileSetId: oTestSet.idTestSet, + WuiMain.ksParamGetFileDownloadIt: True, + }), + ); + sHtml += ' </p>\n'; + if cFailures == 1: + sHtml += ' <p>%s</p>\n' % ( WuiTmLink('Jump to failure', '#failure-0'), ) + elif cFailures > 1: + sHtml += ' <p>Jump to failure: '; + if cFailures <= 13: + for iFailure in range(0, cFailures): + sHtml += ' ' + WuiTmLink('#%u' % (iFailure,), '#failure-%u' % (iFailure,)).toHtml(); + else: + for iFailure in range(0, 6): + sHtml += ' ' + WuiTmLink('#%u' % (iFailure,), '#failure-%u' % (iFailure,)).toHtml(); + sHtml += ' ... '; + for iFailure in range(cFailures - 6, cFailures): + sHtml += ' ' + WuiTmLink('#%u' % (iFailure,), '#failure-%u' % (iFailure,)).toHtml(); + sHtml += ' </p>\n'; + + sHtml += sHtmlEvents; + sHtml += ' <p>' + sFormButton + '</p>\n'; + sHtml += ' </form>\n'; + sHtml += ' </td>\n'; + + sHtml += ' </tr>\n'; + sHtml += '</table>\n'; + + return ('Test Case result details', sHtml) + + +class WuiGroupedResultList(WuiListContentBase): + """ + WUI results content generator. + """ + + def __init__(self, aoEntries, cEntriesCount, iPage, cItemsPerPage, tsEffective, fnDPrint, oDisp, + aiSelectedSortColumns = None): + """Override initialization""" + WuiListContentBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, + sTitle = 'Ungrouped (%d)' % cEntriesCount, sId = 'results', + fnDPrint = fnDPrint, oDisp = oDisp, aiSelectedSortColumns = aiSelectedSortColumns); + + self._cEntriesCount = cEntriesCount + + self._asColumnHeaders = [ + 'Start', + 'Product Build', + 'Kit', + 'Box', + 'OS.Arch', + 'Test Case', + 'Elapsed', + 'Result', + 'Reason', + ]; + self._asColumnAttribs = ['align="center"', 'align="center"', 'align="center"', + 'align="center"', 'align="center"', 'align="center"', + 'align="center"', 'align="center"', 'align="center"', + 'align="center"', 'align="center"', 'align="center"', + 'align="center"', ]; + + + # Prepare parameter lists. + self._dTestBoxLinkParams = self._oDisp.getParameters(); + self._dTestBoxLinkParams[WuiMain.ksParamAction] = WuiMain.ksActionResultsGroupedByTestBox; + + self._dTestCaseLinkParams = self._oDisp.getParameters(); + self._dTestCaseLinkParams[WuiMain.ksParamAction] = WuiMain.ksActionResultsGroupedByTestCase; + + self._dRevLinkParams = self._oDisp.getParameters(); + self._dRevLinkParams[WuiMain.ksParamAction] = WuiMain.ksActionResultsGroupedByBuildRev; + + + + def _formatListEntry(self, iEntry): + """ + Format *show all* table entry + """ + oEntry = self._aoEntries[iEntry]; + + from testmanager.webui.wuiadmin import WuiAdmin; + from testmanager.webui.wuireport import WuiReportSummaryLink; + + oValidationKit = None; + if oEntry.idBuildTestSuite is not None: + oValidationKit = WuiTmLink('r%s' % (oEntry.iRevisionTestSuite,), + WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildDetails, + BuildData.ksParam_idBuild: oEntry.idBuildTestSuite }, + fBracketed = False); + + aoTestSetLinks = []; + aoTestSetLinks.append(WuiTmLink(oEntry.enmStatus, + WuiMain.ksScriptName, + { WuiMain.ksParamAction: WuiMain.ksActionTestResultDetails, + TestSetData.ksParam_idTestSet: oEntry.idTestSet }, + fBracketed = False)); + if oEntry.cErrors > 0: + aoTestSetLinks.append(WuiRawHtml('-')); + aoTestSetLinks.append(WuiTmLink('%d error%s' % (oEntry.cErrors, '' if oEntry.cErrors == 1 else 's', ), + WuiMain.ksScriptName, + { WuiMain.ksParamAction: WuiMain.ksActionTestResultDetails, + TestSetData.ksParam_idTestSet: oEntry.idTestSet }, + sFragmentId = 'failure-0', fBracketed = False)); + + + self._dTestBoxLinkParams[WuiMain.ksParamGroupMemberId] = oEntry.idTestBox; + self._dTestCaseLinkParams[WuiMain.ksParamGroupMemberId] = oEntry.idTestCase; + self._dRevLinkParams[WuiMain.ksParamGroupMemberId] = oEntry.iRevision; + + sTestBoxTitle = u''; + if oEntry.sCpuVendor is not None: + sTestBoxTitle += 'CPU vendor:\t%s\n' % ( oEntry.sCpuVendor, ); + if oEntry.sCpuName is not None: + sTestBoxTitle += 'CPU name:\t%s\n' % ( ' '.join(oEntry.sCpuName.split()), ); + if oEntry.sOsVersion is not None: + sTestBoxTitle += 'OS version:\t%s\n' % ( oEntry.sOsVersion, ); + asFeatures = []; + if oEntry.fCpuHwVirt is True: asFeatures.append(u'HW\u2011Virt'); + if oEntry.fCpuNestedPaging is True: asFeatures.append(u'Nested\u2011Paging'); + if oEntry.fCpu64BitGuest is True: asFeatures.append(u'64\u2011bit\u2011Guest'); + #if oEntry.fChipsetIoMmu is True: asFeatures.append(u'I/O\u2011MMU'); + sTestBoxTitle += u'CPU features:\t' + u', '.join(asFeatures); + + # Testcase + if oEntry.sSubName: + sTestCaseName = '%s / %s' % (oEntry.sTestCaseName, oEntry.sSubName,); + else: + sTestCaseName = oEntry.sTestCaseName; + + # Reason: + aoReasons = []; + for oIt in oEntry.aoFailureReasons: + sReasonTitle = 'Reason: \t%s\n' % ( oIt.oFailureReason.sShort, ); + sReasonTitle += 'Category:\t%s\n' % ( oIt.oFailureReason.oCategory.sShort, ); + sReasonTitle += 'Assigned:\t%s\n' % ( self.formatTsShort(oIt.tsFailureReasonAssigned), ); + sReasonTitle += 'By User: \t%s\n' % ( oIt.oFailureReasonAssigner.sUsername, ); + if oIt.sFailureReasonComment: + sReasonTitle += 'Comment: \t%s\n' % ( oIt.sFailureReasonComment, ); + if oIt.oFailureReason.iTicket is not None and oIt.oFailureReason.iTicket > 0: + sReasonTitle += 'xTracker:\t#%s\n' % ( oIt.oFailureReason.iTicket, ); + for i, sUrl in enumerate(oIt.oFailureReason.asUrls): + sUrl = sUrl.strip(); + if sUrl: + sReasonTitle += 'URL#%u: \t%s\n' % ( i, sUrl, ); + aoReasons.append(WuiTmLink(oIt.oFailureReason.sShort, WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionFailureReasonDetails, + FailureReasonData.ksParam_idFailureReason: oIt.oFailureReason.idFailureReason }, + sTitle = sReasonTitle)); + + return [ + oEntry.tsCreated, + [ WuiTmLink('%s %s (%s)' % (oEntry.sProduct, oEntry.sVersion, oEntry.sType,), + WuiMain.ksScriptName, self._dRevLinkParams, sTitle = '%s' % (oEntry.sBranch,), fBracketed = False), + WuiSvnLinkWithTooltip(oEntry.iRevision, 'vbox'), ## @todo add sRepository TestResultListingData + WuiTmLink(self.ksShortDetailsLink, WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionBuildDetails, + BuildData.ksParam_idBuild: oEntry.idBuild }, + fBracketed = False), + ], + oValidationKit, + [ WuiTmLink(oEntry.sTestBoxName, WuiMain.ksScriptName, self._dTestBoxLinkParams, fBracketed = False, + sTitle = sTestBoxTitle), + WuiTmLink(self.ksShortDetailsLink, WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxDetails, + TestBoxData.ksParam_idTestBox: oEntry.idTestBox }, + fBracketed = False), + WuiReportSummaryLink(ReportModelBase.ksSubTestBox, oEntry.idTestBox, fBracketed = False), ], + '%s.%s' % (oEntry.sOs, oEntry.sArch), + [ WuiTmLink(sTestCaseName, WuiMain.ksScriptName, self._dTestCaseLinkParams, fBracketed = False, + sTitle = (oEntry.sBaseCmd + ' ' + oEntry.sArgs) if oEntry.sArgs else oEntry.sBaseCmd), + WuiTmLink(self.ksShortDetailsLink, WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestCaseDetails, + TestCaseData.ksParam_idTestCase: oEntry.idTestCase }, + fBracketed = False), + WuiReportSummaryLink(ReportModelBase.ksSubTestCase, oEntry.idTestCase, fBracketed = False), ], + oEntry.tsElapsed, + aoTestSetLinks, + aoReasons + ]; |