示例#1
0
    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 len(aoTestCaseData) > 0:
                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 len(aoGlobalResourceData) > 0:
                sTmp = '<ul>'
                for oGlobalResourceData in aoGlobalResourceData:
                    sTmp += '<li>%s</li>' % (webutils.escapeElem(
                        oGlobalResourceData.sName), )
                sTmp += '</ul>'
            else:
                sTmp = 'No items'
            return sTmp

        asHtml = []

        # Test result + test set details.
        aoResultRows = [
            WuiTmLink(
                oTestCaseEx.sName,
                self.oWuiAdmin.ksScriptName, {
                    self.oWuiAdmin.ksParamAction:
                    self.oWuiAdmin.ksActionTestCaseDetails,
                    TestCaseData.ksParam_idTestCase: oTestCaseEx.idTestCase,
                    self.oWuiAdmin.ksParamEffectiveDate: oTestSet.tsConfig,
                },
                fBracketed=False),
        ]
        if oTestCaseEx.sDescription is not None and len(
                oTestCaseEx.sDescription) > 0:
            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:',
             WuiTmLink(
                 oTestGroup.sName,
                 self.oWuiAdmin.ksScriptName, {
                     self.oWuiAdmin.ksParamAction:
                     self.oWuiAdmin.ksActionTestGroupDetails,
                     TestGroupData.ksParam_idTestGroup: oTestGroup.idTestGroup,
                     self.oWuiAdmin.ksParamEffectiveDate: oTestSet.tsConfig,
                 },
                 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 is not None and len(
                oTestCaseEx.aoDepTestCases) > 0:
            aoResultRows.append([
                'Prereq. Test Cases:', oTestCaseEx.aoDepTestCases,
                getTcDepsHtmlList
            ])
        if oTestCaseEx.aoDepGlobalResources is not None and len(
                oTestCaseEx.aoDepGlobalResources) > 0:
            aoResultRows.append([
                'Global Resources:', oTestCaseEx.aoDepGlobalResources,
                getGrDepsHtmlList
            ])

        # Builds.
        aoBuildRows = []
        if oBuildEx is not None:
            aoBuildRows += [
                WuiTmLink(
                    'Build',
                    self.oWuiAdmin.ksScriptName, {
                        self.oWuiAdmin.ksParamAction:
                        self.oWuiAdmin.ksActionBuildDetails,
                        BuildData.ksParam_idBuild: oBuildEx.idBuild,
                        self.oWuiAdmin.ksParamEffectiveDate:
                        oTestSet.tsCreated,
                    },
                    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 = [
            WuiTmLink(
                oTestBox.sName,
                self.oWuiAdmin.ksScriptName, {
                    self.oWuiAdmin.ksParamAction:
                    self.oWuiAdmin.ksActionTestBoxDetails,
                    TestBoxData.ksParam_idGenTestBox: oTestSet.idGenTestBox,
                },
                fBracketed=False),
        ]
        if oTestBox.sDescription is not None and len(
                oTestBox.sDescription) > 0:
            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:
            # ASSUMING x86+AMD64 versioning scheme here.
            uFamily = (oTestBox.lCpuRevision >> 24) & 0xff
            uModel = (oTestBox.lCpuRevision >> 8) & 0xffff
            uStepping = oTestBox.lCpuRevision & 0xff
            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 = '&nbsp;\\<br>&nbsp;&nbsp;&nbsp;&nbsp;\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 += '   <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)
示例#2
0
    def _formatListEntry(self, iEntry):  # pylint: disable=R0914
        from testmanager.webui.wuiadmin import WuiAdmin
        oEntry = self._aoEntries[iEntry]

        # Lights outs managment.
        if oEntry.enmLomKind == TestBoxData.ksLomKind_ILOM:
            aoLom = [
                WuiLinkBase('ILOM',
                            'https://%s/' % (oEntry.ipLom, ),
                            fBracketed=False),
            ]
        elif oEntry.enmLomKind == TestBoxData.ksLomKind_ELOM:
            aoLom = [
                WuiLinkBase('ELOM',
                            'http://%s/' % (oEntry.ipLom, ),
                            fBracketed=False),
            ]
        elif oEntry.enmLomKind == TestBoxData.ksLomKind_AppleXserveLom:
            aoLom = ['Apple LOM']
        elif oEntry.enmLomKind == TestBoxData.ksLomKind_None:
            aoLom = ['none']
        else:
            aoLom = [
                'Unexpected enmLomKind value "%s"' % (oEntry.enmLomKind, )
            ]
        if oEntry.ipLom is not None:
            if oEntry.enmLomKind in [
                    TestBoxData.ksLomKind_ILOM, TestBoxData.ksLomKind_ELOM
            ]:
                aoLom += [
                    WuiLinkBase('(ssh)',
                                'ssh://%s' % (oEntry.ipLom, ),
                                fBracketed=False)
                ]
            aoLom += [WuiRawHtml('<br>'),
                      '%s' % (oEntry.ipLom, )]

        # State and Last seen.
        if oEntry.oStatus is None:
            oSeen = WuiSpanText('tmspan-offline', 'Never')
            oState = ''
        else:
            oDelta = oEntry.tsCurrent - oEntry.oStatus.tsUpdated
            if oDelta.days <= 0 and oDelta.seconds <= self.kcSecMaxStatusDeltaAlive:
                oSeen = WuiSpanText(
                    'tmspan-online', u'%s\u00a0s\u00a0ago' %
                    (oDelta.days * 24 * 3600 + oDelta.seconds, ))
            else:
                oSeen = WuiSpanText(
                    'tmspan-offline',
                    u'%s' % (self.formatTsShort(oEntry.oStatus.tsUpdated), ))

            if oEntry.oStatus.idTestSet is None:
                oState = str(oEntry.oStatus.enmState)
            else:
                from testmanager.webui.wuimain import WuiMain
                oState = WuiTmLink(
                    oEntry.oStatus.enmState,
                    WuiMain.ksScriptName,  # pylint: disable=R0204
                    {
                        WuiMain.ksParamAction:
                        WuiMain.ksActionTestResultDetails,
                        TestSetData.ksParam_idTestSet:
                        oEntry.oStatus.idTestSet,
                    },
                    sTitle='#%u' % (oEntry.oStatus.idTestSet, ),
                    fBracketed=False)
        # Comment
        oComment = self._formatCommentCell(oEntry.sComment)

        # Group links.
        aoGroups = []
        for oInGroup in oEntry.aoInSchedGroups:
            oSchedGroup = oInGroup.oSchedGroup
            aoGroups.append(
                WuiTmLink(oSchedGroup.sName,
                          WuiAdmin.ksScriptName, {
                              WuiAdmin.ksParamAction:
                              WuiAdmin.ksActionSchedGroupEdit,
                              SchedGroupData.ksParam_idSchedGroup:
                              oSchedGroup.idSchedGroup,
                          },
                          sTitle='#%u' % (oSchedGroup.idSchedGroup, ),
                          fBracketed=len(oEntry.aoInSchedGroups) > 1))

        # Reformat the OS version to take less space.
        aoOs = ['N/A']
        if oEntry.sOs is not None and oEntry.sOsVersion is not None and oEntry.sCpuArch:
            sOsVersion = oEntry.sOsVersion
            if      sOsVersion[0] not in [ 'v', 'V', 'r', 'R'] \
                and sOsVersion[0].isdigit() \
                and sOsVersion.find('.') in range(4) \
                and oEntry.sOs in [ 'linux', 'solaris', 'darwin', ]:
                sOsVersion = 'v' + sOsVersion

            sVer1 = sOsVersion
            sVer2 = None
            if oEntry.sOs == 'linux' or oEntry.sOs == 'darwin':
                iSep = sOsVersion.find(' / ')
                if iSep > 0:
                    sVer1 = sOsVersion[:iSep].strip()
                    sVer2 = sOsVersion[iSep + 3:].strip()
                    sVer2 = sVer2.replace('Red Hat Enterprise Linux Server',
                                          'RHEL')
                    sVer2 = sVer2.replace('Oracle Linux Server', 'OL')
            elif oEntry.sOs == 'solaris':
                iSep = sOsVersion.find(' (')
                if iSep > 0 and sOsVersion[-1] == ')':
                    sVer1 = sOsVersion[:iSep].strip()
                    sVer2 = sOsVersion[iSep + 2:-1].strip()
            elif oEntry.sOs == 'win':
                iSep = sOsVersion.find('build')
                if iSep > 0:
                    sVer1 = sOsVersion[:iSep].strip()
                    sVer2 = 'B' + sOsVersion[iSep + 1:].strip()
            aoOs = [
                WuiSpanText('tmspan-osarch', u'%s.%s' % (
                    oEntry.sOs,
                    oEntry.sCpuArch,
                )),
                WuiSpanText(
                    'tmspan-osver1',
                    sVer1.replace('-', u'\u2011'),
                ),
            ]
            if sVer2 is not None:
                aoOs += [
                    WuiRawHtml('<br>'),
                    WuiSpanText('tmspan-osver2', sVer2.replace('-',
                                                               u'\u2011')),
                ]

        # Format the CPU revision.
        oCpu = None
        if oEntry.lCpuRevision is not None and oEntry.sCpuVendor is not None and oEntry.sCpuName is not None:
            oCpu = [
                u'%s (fam:%xh\u00a0m:%xh\u00a0s:%xh)' % (
                    oEntry.sCpuVendor,
                    oEntry.getCpuFamily(),
                    oEntry.getCpuModel(),
                    oEntry.getCpuStepping(),
                ),
                WuiRawHtml('<br>'),
                oEntry.sCpuName,
            ]
        else:
            oCpu = []
            if oEntry.sCpuVendor is not None:
                oCpu.append(oEntry.sCpuVendor)
            if oEntry.lCpuRevision is not None:
                oCpu.append('%#x' % (oEntry.lCpuRevision, ))
            if oEntry.sCpuName is not None:
                oCpu.append(oEntry.sCpuName)

        # Stuff cpu vendor and cpu/box features into one field.
        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')
        sFeatures = u' '.join(asFeatures) if asFeatures else u''

        # Collection applicable actions.
        aoActions = [
            WuiTmLink(
                'Details', WuiAdmin.ksScriptName, {
                    WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxDetails,
                    TestBoxData.ksParam_idTestBox: oEntry.idTestBox,
                    WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate,
                }),
        ]

        if self._oDisp is None or not self._oDisp.isReadOnlyUser():
            if isDbTimestampInfinity(oEntry.tsExpire):
                aoActions += [
                    WuiTmLink(
                        'Edit', WuiAdmin.ksScriptName, {
                            WuiAdmin.ksParamAction:
                            WuiAdmin.ksActionTestBoxEdit,
                            TestBoxData.ksParam_idTestBox: oEntry.idTestBox,
                        }),
                    WuiTmLink(
                        'Remove',
                        WuiAdmin.ksScriptName, {
                            WuiAdmin.ksParamAction:
                            WuiAdmin.ksActionTestBoxRemovePost,
                            TestBoxData.ksParam_idTestBox: oEntry.idTestBox
                        },
                        sConfirm='Are you sure that you want to remove %s (%s)?'
                        % (oEntry.sName, oEntry.ip)),
                ]

            if oEntry.sOs not in [
                    'win',
                    'os2',
            ] and oEntry.ip is not None:
                aoActions.append(
                    WuiLinkBase(
                        'ssh',
                        'ssh://vbox@%s' % (oEntry.ip, ),
                    ))

        return [
            self._getCheckBoxColumn(iEntry, oEntry.idTestBox),
            [
                WuiSpanText('tmspan-name', oEntry.sName),
                WuiRawHtml('<br>'),
                '%s' % (oEntry.ip, ),
            ],
            aoLom,
            [
                '' if oEntry.fEnabled else 'disabled / ',
                oState,
                WuiRawHtml('<br>'),
                oSeen,
            ],
            oEntry.enmPendingCmd,
            oComment,
            WuiSvnLink(oEntry.iTestBoxScriptRev),
            oEntry.formatPythonVersion(),
            aoGroups,
            aoOs,
            oCpu,
            sFeatures,
            oEntry.cCpus if oEntry.cCpus is not None else 'N/A',
            utils.formatNumberNbsp(oEntry.cMbMemory) +
            u'\u00a0MB' if oEntry.cMbMemory is not None else 'N/A',
            utils.formatNumberNbsp(oEntry.cMbScratch) +
            u'\u00a0MB' if oEntry.cMbScratch is not None else 'N/A',
            aoActions,
        ]