# -*- coding: utf-8 -*-
# $Id$
# pylint: disable=C0302
## @todo Rename this file to testresult.py!
"""
Test Manager - Fetch test results.
"""
__copyright__ = \
"""
Copyright (C) 2012-2014 Oracle Corporation
This file is part of VirtualBox Open Source Edition (OSE), as
available from http://www.virtualbox.org. This file is free software;
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.
"""
# Standard python imports.
import unittest;
# Validation Kit imports.
from testmanager.core.base import ModelDataBase, ModelLogicBase, ModelDataBaseTestCase, TMExceptionBase, TMTooManyRows;
"""
Test case execution result data
"""
## @name TestStatus_T
# @{
## @}
## List of bad statuses.
];
self.idTestResult = None
self.idTestResultParent = None
self.iNestingDepth = None
"""
Reinitialize from a SELECT * FROM TestResults.
Return self. Raises exception if no row.
"""
if aoRow is None:
raise TMExceptionBase('Test result record not found.')
return self;
""" Check if it's a real failure. """
"""
Extended test result data class.
This is intended for use as a node in a result tree. This is not intended
for serialization to parameters or vice versa. Use TestResultLogic to
construct the tree.
"""
"""
Initialize from a query like this:
SELECT TestResults.*, TestResultStrTab.sValue
FROM TestResults, TestResultStrTab
WHERE TestResultStrTab.idStr = TestResults.idStrName
Note! The caller is expected to fetch children, values, failure
details, and files.
"""
self.aoChildren = [];
return self;
"""
Test result value data.
"""
self.idTestResultValue = None;
self.idTestResult = None;
"""
Reinitialize from a SELECT * FROM TestResultValues.
Return self. Raises exception if no row.
"""
if aoRow is None:
raise TMExceptionBase('Test result value record not found.')
return self;
"""
Extends TestResultValue by resolving the value name and unit string.
"""
"""
Reinitialize from a query like this:
SELECT TestResultValues.*, TestResultStrTab.sValue
FROM TestResultValues, TestResultStrTab
WHERE TestResultStrTab.idStr = TestResultValues.idStrName
Return self. Raises exception if no row.
"""
else:
return self;
"""
Test result message data.
"""
self.idTestResultMsg = None;
self.idTestResult = None;
"""
Reinitialize from a SELECT * FROM TestResultMsgs.
Return self. Raises exception if no row.
"""
if aoRow is None:
raise TMExceptionBase('Test result value record not found.')
return self;
"""
Extends TestResultMsg by resolving the message string.
"""
"""
Reinitialize from a query like this:
SELECT TestResultMsg.*, TestResultStrTab.sValue
FROM TestResultMsg, TestResultStrTab
WHERE TestResultStrTab.idStr = TestResultMsgs.idStrName
Return self. Raises exception if no row.
"""
return self;
"""
Test result message data.
"""
self.idTestResultFile = None;
self.idTestResult = None;
self.idStrDescription = None;
"""
Reinitialize from a SELECT * FROM TestResultFiles.
Return self. Raises exception if no row.
"""
if aoRow is None:
raise TMExceptionBase('Test result file record not found.')
return self;
"""
Extends TestResultFile by resolving the strings.
"""
self.sDescription = None;
"""
Reinitialize from a query like this:
SELECT TestResultFiles.*,
StrTabFile.sValue AS sFile,
StrTabDesc.sValue AS sDescription
StrTabKind.sValue AS sKind,
StrTabMime.sValue AS sMime,
FROM ...
Return self. Raises exception if no row.
"""
return self;
"""
Reinitializes to represent the main.log object (not in DB).
Returns self.
"""
self.idStrDescription = None;
return self;
"""
Checks if the file is likely to be UTF-8 encoded.
"""
return True;
return False;
"""
Gets the MIME type with encoding if likely to be UTF-8.
"""
if self.isProbablyUtf8Encoded():
"""
Test case result data representation for table listing
"""
"""Initialize"""
self.idBuildCategory = None;
self.sRepository = None;
self.sOsVersion = None;
self.sCpuVendor = None;
self.fCpuHwVirt = None;
self.fCpuNestedPaging = None;
self.fCpu64BitGuest = None;
self.sTestBoxName = None
self.idTestCase = None
self.sTestCaseName = None
self.idBuildTestSuite = None;
self.iRevisionTestSuite = None;
"""
Reinitialize from a database query.
Return self. Raises exception if no row.
"""
if aoRow is None:
raise TMExceptionBase('Test result record not found.')
return self
"""Hanging offence committed by test case."""
pass;
"""
Results grouped by scheduling group.
"""
#
# Result grinding for displaying in the WUI.
#
ksBaseTables = 'BuildCategories, Builds, TestBoxes, TestResults, TestCases, TestCaseArgs,\n' \
+ ' TestSets LEFT OUTER JOIN Builds AS TestSuiteBits\n' \
' ON TestSets.idBuildTestSuite = TestSuiteBits.idBuild\n';
ksBasePreCondition = 'TestSets.idTestSet = TestResults.idTestSet\n' \
+ ' AND TestResults.idTestResultParent is NULL\n' \
+ ' AND TestSets.idBuild = Builds.idBuild\n' \
+ ' AND Builds.tsExpire > TestSets.tsCreated\n' \
+ ' AND Builds.tsEffective <= TestSets.tsCreated\n' \
+ ' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n' \
+ ' AND TestSets.idGenTestBox = TestBoxes.idGenTestBox\n' \
+ ' AND TestSets.idGenTestCase = TestCases.idGenTestCase\n' \
+ ' AND TestSets.idGenTestCaseArgs = TestCaseArgs.idGenTestCaseArgs\n'
ksBasePreCondition + ' AND TestSets.idTestGroup',),
ksBasePreCondition + ' AND Builds.iRevision',),
ksBasePreCondition + ' AND TestSets.idTestBox',),
ksBasePreCondition + ' AND TestSets.idTestCase',),
ksBasePreCondition + ' AND TestBoxes.idSchedGroup',),
}
"""
Get part of SQL query responsible for SELECT data within
specified period of time.
"""
assert sInterval is not None; # too many rows.
cMonthsMourningPeriod = 2; # Stop reminding everyone about testboxes after 2 months. (May also speed up the query.)
if tsNow is None:
sRet = '(TestSets.tsDone IS NULL OR TestSets.tsDone >= (CURRENT_TIMESTAMP - \'%s\'::interval))\n' \
' AND TestSets.tsCreated >= (CURRENT_TIMESTAMP - \'%s\'::interval - \'%u months\'::interval)\n' \
else:
sRet = 'TestSets.tsCreated <= %s\n' \
' AND TestSets.tsCreated >= (%s - \'%s\'::interval - \'%u months\'::interval)\n' \
' AND (TestSets.tsDone IS NULL OR TestSets.tsDone >= (%s - \'%s\'::interval))\n' \
% ( sTsNow,
return sRet
def _getSqlQueryForGroupSearch(self, sWhat, tsNow, sInterval, enmResultsGroupingType, iResultsGroupingValue, fOnlyFailures):
"""
Returns an SQL query that limits SELECT result
in order to satisfy @param enmResultsGroupingType.
"""
if enmResultsGroupingType is None:
raise TMExceptionBase('Unknown grouping type')
raise TMExceptionBase('Unknown grouping type')
# Get SQL query parameters
# Extend SQL query with time period limitation
if iResultsGroupingValue is not None:
# Extend the condition with test status limitations if requested.
if fOnlyFailures:
sCondition += '\n AND TestSets.enmStatus != \'success\'::TestStatus_T' \
'\n AND TestSets.enmStatus != \'running\'::TestStatus_T';
# Assemble the query.
return sQuery
def fetchResultsForListing(self, iStart, cMaxRows, tsNow, sInterval, enmResultsGroupingType, iResultsGroupingValue,
"""
Fetches TestResults table content.
If @param enmResultsGroupingType and @param iResultsGroupingValue
are not None, then resulting (returned) list contains only records
that match specified @param enmResultsGroupingType.
If @param enmResultsGroupingType is None, then
@param iResultsGroupingValue is ignored.
Returns an array (list) of TestResultData items, empty list if none.
Raises exception on error.
"""
sWhat = 'TestSets.idTestSet,\n' \
' BuildCategories.idBuildCategory,\n' \
' BuildCategories.sProduct,\n' \
' BuildCategories.sRepository,\n' \
' BuildCategories.sBranch,\n' \
' BuildCategories.sType,\n' \
' Builds.idBuild,\n' \
' Builds.sVersion,\n' \
' Builds.iRevision,\n' \
' TestBoxes.sOs,\n' \
' TestBoxes.sOsVersion,\n' \
' TestBoxes.sCpuArch,\n' \
' TestBoxes.sCpuVendor,\n' \
' TestBoxes.sCpuName,\n' \
' TestBoxes.cCpus,\n' \
' TestBoxes.fCpuHwVirt,\n' \
' TestBoxes.fCpuNestedPaging,\n' \
' TestBoxes.fCpu64BitGuest,\n' \
' TestBoxes.idTestBox,\n' \
' TestBoxes.sName,\n' \
' TestResults.tsCreated,\n' \
' COALESCE(TestResults.tsElapsed, CURRENT_TIMESTAMP - TestResults.tsCreated),\n' \
' TestSets.enmStatus,\n' \
' TestResults.cErrors,\n' \
' TestCases.idTestCase,\n' \
' TestCases.sName,\n' \
' TestCases.sBaseCmd,\n' \
' TestCaseArgs.sArgs,\n' \
' TestSuiteBits.idBuild AS idBuildTestSuite,\n' \
' TestSuiteBits.iRevision AS iRevisionTestSuite,\n' \
' (TestSets.tsDone IS NULL) SortRunningFirst' \
;
sSqlQuery = self._getSqlQueryForGroupSearch(sWhat, tsNow, sInterval, enmResultsGroupingType, iResultsGroupingValue,
sSqlQuery += 'ORDER BY SortRunningFirst DESC, TestSets.idTestSet DESC\n';
aoRows = [];
return aoRows
def getEntriesCount(self, tsNow, sInterval, enmResultsGroupingType, iResultsGroupingValue, fOnlyFailures):
"""
Get number of table records.
If @param enmResultsGroupingType and @param iResultsGroupingValue
are not None, then we count only only those records
that match specified @param enmResultsGroupingType.
If @param enmResultsGroupingType is None, then
@param iResultsGroupingValue is ignored.
"""
"""
Get list of uniq TestGroupData objects which
found in all test results.
"""
'FROM TestGroups, TestSets\n'
'WHERE TestSets.idTestGroup = TestGroups.idTestGroup\n'
' AND TestGroups.tsExpire > TestSets.tsCreated\n'
' AND TestGroups.tsEffective <= TestSets.tsCreated'
aoRet = []
## @todo Need to take time into consideration. Will go belly up if we delete a testgroup.
return aoRet
"""
Get list of uniq BuildDataEx objects which
found in all test results.
"""
'FROM Builds, BuildCategories, TestSets\n'
'WHERE TestSets.idBuild = Builds.idBuild\n'
' AND Builds.idBuildCategory = BuildCategories.idBuildCategory\n'
' AND Builds.tsExpire > TestSets.tsCreated\n'
' AND Builds.tsEffective <= TestSets.tsCreated'
aoRet = []
return aoRet
"""
Get list of uniq TestBoxData objects which
found in all test results.
"""
## @todo do all in one query.
'FROM TestBoxes, TestSets\n'
'WHERE TestSets.idGenTestBox = TestBoxes.idGenTestBox\n'
'ORDER BY TestBoxes.idTestBox, TestBoxes.idGenTestBox DESC' );
idPrevTestBox = -1;
asIdGenTestBoxes = [];
aoRet = []
'FROM TestBoxes\n'
'ORDER BY sName');
return aoRet
"""
Get a list of unique TestCaseData objects which is appears in the test
specified result period.
"""
self._oDb.execute('SELECT DISTINCT TestCases.idTestCase, TestCases.idGenTestCase, TestSets.tsConfig\n'
'FROM TestCases, TestSets\n'
'WHERE TestSets.idTestCase = TestCases.idTestCase\n'
' AND TestCases.tsExpire > TestSets.tsCreated\n'
' AND TestCases.tsEffective <= TestSets.tsCreated\n'
'ORDER BY TestCases.idTestCase, TestCases.idGenTestCase DESC\n');
aoRet = []
idPrevTestCase = -1;
## @todo reduce subqueries
return aoRet
"""
Get list of uniq SchedGroupData objects which
found in all test results.
"""
'FROM TestBoxes, TestSets\n'
'WHERE TestSets.idGenTestBox = TestBoxes.idGenTestBox\n'
' AND TestBoxes.tsExpire > TestSets.tsCreated\n'
' AND TestBoxes.tsEffective <= TestSets.tsCreated'
aoRet = []
## @todo reduce subqueries
return aoRet
"""
Get build record by its id
"""
'FROM TestResults\n'
'WHERE idTestResult = %s\n',
(idTestResult,))
raise TMExceptionBase('Found more than one test result with the same credentials. Database structure is corrupted.')
try:
except IndexError:
return None
#
# Details view and interface.
#
"""
Fetches the result tree for the given test set.
Returns a tree of TestResultDataEx nodes.
Raises exception on invalid input and database issues.
"""
# Depth first, i.e. just like the XML added them.
## @todo this still isn't performing extremely well, consider optimizations.
'SELECT TestResults.*,\n'
' TestResultStrTab.sValue,\n'
' EXISTS ( SELECT idTestResultValue\n'
' FROM TestResultValues\n'
' WHERE TestResultValues.idTestResult = TestResults.idTestResult ) AS fHasValues,\n'
' EXISTS ( SELECT idTestResultMsg\n'
' FROM TestResultMsgs\n'
' WHERE TestResultMsgs.idTestResult = TestResults.idTestResult ) AS fHasMsgs,\n'
' EXISTS ( SELECT idTestResultFile\n'
' FROM TestResultFiles\n'
' WHERE TestResultFiles.idTestResult = TestResults.idTestResult ) AS fHasFiles\n'
'FROM TestResults, TestResultStrTab\n'
'WHERE TestResults.idTestSet = %s\n'
' AND TestResults.idStrName = TestResultStrTab.idStr\n'
, ( idTestSet, ));
if cMaxDepth is not None:
sQuery += 'ORDER BY idTestResult ASC\n'
if cRows > 65536:
# Set up the root node first.
if oRoot.idTestResultParent is not None:
# The chilren (if any).
# Figure out and vet the parent.
if oParent is None:
# Link it up.
"""
fetchResultTree worker that fetches values, message and files for the
specified node.
"""
if fHasValues:
' TestResultStrTab.sValue\n'
'FROM TestResultValues, TestResultStrTab\n'
'WHERE TestResultValues.idTestResult = %s\n'
' AND TestResultValues.idStrName = TestResultStrTab.idStr\n'
'ORDER BY idTestResultValue ASC\n'
, ( oCurNode.idTestResult, ));
if fHasMsgs:
' TestResultStrTab.sValue\n'
'FROM TestResultMsgs, TestResultStrTab\n'
'WHERE TestResultMsgs.idTestResult = %s\n'
' AND TestResultMsgs.idStrMsg = TestResultStrTab.idStr\n'
'ORDER BY idTestResultMsg ASC\n'
, ( oCurNode.idTestResult, ));
if fHasFiles:
' StrTabFile.sValue AS sFile,\n'
' StrTabDesc.sValue AS sDescription,\n'
' StrTabKind.sValue AS sKind,\n'
' StrTabMime.sValue AS sMime\n'
'FROM TestResultFiles,\n'
' TestResultStrTab AS StrTabFile,\n'
' TestResultStrTab AS StrTabDesc,\n'
' TestResultStrTab AS StrTabKind,\n'
' TestResultStrTab AS StrTabMime\n'
'WHERE TestResultFiles.idTestResult = %s\n'
' AND TestResultFiles.idStrFile = StrTabFile.idStr\n'
' AND TestResultFiles.idStrDescription = StrTabDesc.idStr\n'
' AND TestResultFiles.idStrKind = StrTabKind.idStr\n'
' AND TestResultFiles.idStrMime = StrTabMime.idStr\n'
'ORDER BY idTestResultFile ASC\n'
, ( oCurNode.idTestResult, ));
return True;
#
# TestBoxController interface(s).
#
"""
The test produces too much output, kill and bury it.
Note! We leave the test set open, only the test result records are
completed. Thus, _getResultStack will return an empty stack and
cause XML processing to fail immediately, while we can still
record when it actually completed in the test set the normal way.
"""
self._oDb.dprint('** _inhumeTestResults: idTestSet=%d\n%s' % (idTestSet, self._stringifyStack(aoStack),));
#
# First add a message.
#
#
# The complete all open test results.
#
for oTestResult in aoStack:
self._completeTestResults(oTestResult, None, TestResultData.ksTestStatus_Failure, oTestResult.cErrors);
# A bit of paranoia.
'SET cErrors = cErrors + 1,\n'
' enmStatus = \'failure\'::TestStatus_T,\n'
' tsElapsed = CURRENT_TIMESTAMP - tsCreated\n'
'WHERE idTestSet = %s\n'
' AND enmStatus = \'running\'::TestStatus_T\n'
, ( idTestSet, ));
return None;
"""
Gets the string table id for the given string, adding it if new.
Note! A copy of this code is also in TestSetLogic.
"""
## @todo move this and make a stored procedure for it.
'FROM TestResultStrTab\n'
'WHERE sValue = %s'
, (sString,));
'VALUES (%s)\n'
'RETURNING idStr\n'
, (sString,));
if fCommit:
"""Returns a string rep of the stack."""
sRet = '';
return sRet;
"""
Gets the current stack of result sets.
"""
'FROM TestResults\n'
'WHERE idTestSet = %s\n'
' AND enmStatus = \'running\'::TestStatus_T\n'
'ORDER BY idTestResult DESC'
, ( idTestSet, ));
aoStack = [];
return aoStack;
def _newTestResult(self, idTestResultParent, idTestSet, iNestingDepth, tsCreated, sName, dCounts, fCommit = False):
"""
Creates a new test result.
Returns the TestResultData object for the new record.
May raise exception on database error.
"""
assert idTestResultParent is not None;
assert idTestResultParent > 1;
#
# This isn't necessarily very efficient, but it's necessary to prevent
# a wild test or testbox from filling up the database.
#
sCountName = 'cTestResults';
if sCountName not in dCounts:
'FROM TestResults\n'
'WHERE idTestSet = %s\n'
, ( idTestSet,));
raise TestResultHangingOffence('Too many sub-tests in total!');
if sCountName not in dCounts:
'FROM TestResults\n'
'WHERE idTestResultParent = %s\n'
, ( idTestResultParent,));
raise TestResultHangingOffence('Too many immediate sub-tests!');
# This is also a hanging offence.
raise TestResultHangingOffence('To deep sub-test nesting!');
# Ditto.
#
# Within bounds, do the job.
#
' idTestResultParent,\n'
' idTestSet,\n'
' tsCreated,\n'
' idStrName,\n'
' iNestingDepth )\n'
'VALUES (%s, %s, TIMESTAMP WITH TIME ZONE %s, %s, %s)\n'
'RETURNING *\n'
return oData;
def _newTestValue(self, idTestResult, idTestSet, sName, lValue, sUnit, dCounts, tsCreated = None, fCommit = False):
"""
Creates a test value.
May raise exception on database error.
"""
#
# Bounds checking.
#
sCountName = 'cTestValues';
if sCountName not in dCounts:
'FROM TestResultValues, TestResults\n'
'WHERE TestResultValues.idTestResult = TestResults.idTestResult\n'
' AND TestResults.idTestSet = %s\n'
, ( idTestSet,));
raise TestResultHangingOffence('Too many values in total!');
if sCountName not in dCounts:
'FROM TestResultValues\n'
'WHERE idTestResult = %s\n'
, ( idTestResult,));
raise TestResultHangingOffence('Too many immediate values for one test result!');
#
# Do the job.
#
if tsCreated is None:
' idTestResult,\n'
' idTestSet,\n'
' idStrName,\n'
' lValue,\n'
' iUnit)\n'
'VALUES ( %s, %s, %s, %s, %s )\n'
else:
' idTestResult,\n'
' idTestSet,\n'
' tsCreated,\n'
' idStrName,\n'
' lValue,\n'
' iUnit)\n'
'VALUES ( %s, %s, TIMESTAMP WITH TIME ZONE %s, %s, %s, %s )\n'
return True;
"""
Creates a record detailing cause of failure.
May raise exception on database error.
"""
#
# Overflow protection.
#
if dCounts is not None:
if sCountName not in dCounts:
'FROM TestResultMsgs\n'
'WHERE idTestResult = %s\n'
, ( idTestResult,));
raise TestResultHangingOffence('Too many messages under for one test result!');
raise TestResultHangingOffence('Failure details message is too long: %d chars - "%s"' % (len(sText), sText));
#
# Do the job.
#
if tsCreated is None:
' idTestResult,\n'
' idStrMsg,\n'
' enmLevel)\n'
'VALUES ( %s, %s, %s)\n'
else:
' idTestResult,\n'
' tsCreated,\n'
' idStrMsg,\n'
' enmLevel)\n'
'VALUES ( %s, TIMESTAMP WITH TIME ZONE %s, %s, %s)\n'
return True;
"""
Completes a test result. Updates the oTestResult object.
May raise exception on database error.
"""
#
# Sanity check: No open sub tests (aoStack should make sure about this!).
#
'FROM TestResults\n'
'WHERE idTestResultParent = %s\n'
' AND enmStatus = %s\n'
#
# Make sure the reporter isn't lying about successes or error counts.
#
'FROM TestResults\n'
'WHERE idTestResultParent = %s\n'
, ( oTestResult.idTestResult, ));
if cErrors < cMinErrors:
#
# Do the update.
#
if tsDone is None:
'SET cErrors = %s,\n'
' enmStatus = %s,\n'
' tsElapsed = CURRENT_TIMESTAMP - tsCreated\n'
'WHERE idTestResult = %s\n'
'RETURNING tsElapsed'
else:
'SET cErrors = %s,\n'
' enmStatus = %s,\n'
' tsElapsed = TIMESTAMP WITH TIME ZONE %s - tsCreated\n'
'WHERE idTestResult = %s\n'
'RETURNING tsElapsed'
return None;
""" Executes a PopHint. """
assert cStackEntries >= 0;
return True;
"""
Validates an element and its attributes.
"""
#
# Validate attributes by name.
#
# Validate integer attributes.
try:
except:
# Validate long attributes.
for sAttr in [ 'value', ]:
try:
except:
# Validate string attributes.
# Validate the timestamp attribute.
if 'timestamp' in dAttribs:
(dAttribs['timestamp'], sError) = ModelDataBase.validateTs(dAttribs['timestamp'], fAllowNull = False);
if sError is not None:
#
# Check that attributes that are required are present.
# We ignore extra attributes.
#
dElementAttribs = \
{
'Test': [ 'timestamp', 'name', ],
'Value': [ 'timestamp', 'name', 'unit', 'value', ],
'FailureDetails': [ 'timestamp', 'text', ],
'Passed': [ 'timestamp', ],
'Skipped': [ 'timestamp', ],
'Failed': [ 'timestamp', 'errors', ],
'TimedOut': [ 'timestamp', 'errors', ],
'End': [ 'timestamp', ],
'PushHint': [ 'testdepth', ],
'PopHint': [ 'testdepth', ],
};
if sName not in dElementAttribs:
return 'Unknown element "%s".' % (sName,);
#
# Only the Test element can (and must) remain open.
#
return '<Test/> is not allowed.';
return 'All elements except <Test> must be closed.';
return None;
"""
Parses an element.
"""
#
# Element level bits.
#
if fClosed:
#
# Attributes.
#
sError = None;
dAttribs = {};
# Extract attribute name.
sError = 'Attributes shall have alpha numberical names and have values.';
break;
# Extract attribute value.
break;
off += 2;
if offEndQuote < 0:
break;
# Check for duplicates.
break;
# Unescape the value.
# Done.
# advance
#
# Validate the element before we return.
#
if sError is None:
"""
Worker for processXmlStream that handles one element.
Returns None on success, error string on bad XML or similar.
Raises exception on hanging offence and on database error.
"""
if sName == 'Test':
aoStack.insert(0, self._newTestResult(idTestResultParent = aoStack[0].idTestResult, idTestSet = idTestSet,
elif sName == 'Value':
self._newTestValue(idTestResult = aoStack[0].idTestResult, idTestSet = idTestSet, tsCreated = dAttribs['timestamp'],
elif sName == 'FailureDetails':
elif sName == 'Passed':
elif sName == 'Skipped':
elif sName == 'Failed':
self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'], cErrors = int(dAttribs['errors']),
elif sName == 'TimedOut':
self._completeTestResults(aoStack[0], tsDone = dAttribs['timestamp'], cErrors = int(dAttribs['errors']),
elif sName == 'End':
elif sName == 'PushHint':
return 'PushHint cannot be nested.'
elif sName == 'PopHint':
return 'No hint to pop.'
if iDesiredTestDepth != iTestDepth:
else:
return 'Unexpected element "%s".' % (sName,);
return None;
"""
Processes the "XML" stream section given in sXml.
The sXml isn't a complete XML document, even should we save up all sXml
for a given set, they may not form a complete and well formed XML
document since the test may be aborted, abend or simply be buggy. We
therefore do our own parsing and treat the XML tags as commands more
than anything else.
Returns (sError, fUnforgivable), where sError is None on success.
May raise database exception.
"""
return ('No open results', True);
#self._oDb.dprint('processXmlStream: %s' % (self._stringifyStack(aoStack),));
#self._oDb.dprint('processXmlStream: sXml=%s' % (sXml,));
dCounts = {};
aaiHints = [];
sError = None;
sError = 'Trying to close the top test results.'
break;
# ASSUMES that we've just seen an <End/>, <Passed/>, <Failed/>,
# <TimedOut/> or <Skipped/> tag earlier in this call!
sError = 'Missing <End/>, <Passed/>, <Failed/>, <TimedOut/> or <Skipped/> tag.';
break;
elif fExpectCloseTest:
sError = 'Expected </Test>.'
break;
if offNext < 0:
sError = 'Unterminated <?xml ?> element.';
break;
offNext += 2;
# Parse and check the tag.
sError = 'Malformed element.';
break;
if offNext < 0:
sError = 'Unterminated element.';
break;
offNext += 1;
if sError is not None:
break;
# Handle it.
try:
except TestResultHangingOffence as oXcpt:
else:
sError = 'Unexpected content.';
break;
# Advance.
#
# Post processing checks.
#
if sError is None and fExpectCloseTest:
sError = 'Expected </Test> before the end of the XML section.'
sError = 'Expected </PopHint> before the end of the XML section.'
#
# Log the error.
#
if sError is not None:
'idTestSet=%s idTestResult=%s XML="%s" %s'
% ( idTestSet,
sError, ),
#
# Unit testing.
#
# pylint: disable=C0111
if __name__ == '__main__':
# not reached.