13c827367444ee418f129b2c238299f49d3264554Jarkko Poyry# -*- coding: utf-8 -*-
23c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
33c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport shlex
43c827367444ee418f129b2c238299f49d3264554Jarkko Poyryimport xml.dom.minidom
53c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
63c827367444ee418f129b2c238299f49d3264554Jarkko Poyryclass StatusCode:
73c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	PASS					= 'Pass'
83c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	FAIL					= 'Fail'
93c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	QUALITY_WARNING			= 'QualityWarning'
103c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	COMPATIBILITY_WARNING	= 'CompatibilityWarning'
113c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	PENDING					= 'Pending'
123c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	NOT_SUPPORTED			= 'NotSupported'
133c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	RESOURCE_ERROR			= 'ResourceError'
143c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	INTERNAL_ERROR			= 'InternalError'
153c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	CRASH					= 'Crash'
163c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	TIMEOUT					= 'Timeout'
173c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
183c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	STATUS_CODES			= [
193c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		PASS,
203c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		FAIL,
213c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		QUALITY_WARNING,
223c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		COMPATIBILITY_WARNING,
233c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		PENDING,
243c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		NOT_SUPPORTED,
253c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		RESOURCE_ERROR,
263c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		INTERNAL_ERROR,
273c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		CRASH,
283c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		TIMEOUT
293c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		]
303c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	STATUS_CODE_SET			= set(STATUS_CODES)
313c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
323c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	@staticmethod
333c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	def isValid (code):
343c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		return code in StatusCode.STATUS_CODE_SET
353c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
363c827367444ee418f129b2c238299f49d3264554Jarkko Poyryclass TestCaseResult:
373c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	def __init__ (self, name, statusCode, statusDetails, log):
383c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		self.name			= name
393c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		self.statusCode		= statusCode
403c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		self.statusDetails	= statusDetails
413c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		self.log			= log
423c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
433c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	def __str__ (self):
443c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		return "%s: %s (%s)" % (self.name, self.statusCode, self.statusDetails)
453c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
463c827367444ee418f129b2c238299f49d3264554Jarkko Poyryclass ParseError(Exception):
473c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	def __init__ (self, filename, line, message):
483c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		self.filename	= filename
493c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		self.line		= line
503c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		self.message	= message
513c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
523c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	def __str__ (self):
533c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		return "%s:%d: %s" % (self.filename, self.line, self.message)
543c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
553c827367444ee418f129b2c238299f49d3264554Jarkko Poyrydef splitContainerLine (line):
563c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	return shlex.split(line)
573c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
583c827367444ee418f129b2c238299f49d3264554Jarkko Poyrydef getNodeText (node):
593c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	rc = []
603c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	for node in node.childNodes:
613c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		if node.nodeType == node.TEXT_NODE:
623c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			rc.append(node.data)
633c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	return ''.join(rc)
643c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
653c827367444ee418f129b2c238299f49d3264554Jarkko Poyryclass BatchResultParser:
663c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	def __init__ (self):
673c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		pass
683c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
693c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	def parseFile (self, filename):
703c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		self.init(filename)
713c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
723c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		f = open(filename, 'rb')
733c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		for line in f:
743c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			self.parseLine(line)
753c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			self.curLine += 1
763c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		f.close()
773c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
783c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		return self.testCaseResults
793c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
803c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	def init (self, filename):
813c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		# Results
823c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		self.sessionInfo		= []
833c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		self.testCaseResults	= []
843c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
853c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		# State
863c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		self.curResultText		= None
873c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		self.curCaseName		= None
883c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
893c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		# Error context
903c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		self.curLine			= 1
913c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		self.filename			= filename
923c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
933c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	def parseLine (self, line):
943c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		if len(line) > 0 and line[0] == '#':
953c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			self.parseContainerLine(line)
963c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		elif self.curResultText != None:
973c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			self.curResultText += line
983c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		# else: just ignored
993c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
1003c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	def parseContainerLine (self, line):
1013c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		args = splitContainerLine(line)
1023c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		if args[0] == "#sessionInfo":
1033c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			if len(args) < 3:
1043c827367444ee418f129b2c238299f49d3264554Jarkko Poyry				print args
1053c827367444ee418f129b2c238299f49d3264554Jarkko Poyry				self.parseError("Invalid #sessionInfo")
1063c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			self.sessionInfo.append((args[1], ' '.join(args[2:])))
1073c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		elif args[0] == "#beginSession" or args[0] == "#endSession":
1083c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			pass # \todo [pyry] Validate
1093c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		elif args[0] == "#beginTestCaseResult":
1103c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			if len(args) != 2 or self.curCaseName != None:
1113c827367444ee418f129b2c238299f49d3264554Jarkko Poyry				self.parseError("Invalid #beginTestCaseResult")
1123c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			self.curCaseName	= args[1]
1133c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			self.curResultText	= ""
1143c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		elif args[0] == "#endTestCaseResult":
1153c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			if len(args) != 1 or self.curCaseName == None:
1163c827367444ee418f129b2c238299f49d3264554Jarkko Poyry				self.parseError("Invalid #endTestCaseResult")
1173c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			self.parseTestCaseResult(self.curCaseName, self.curResultText)
1183c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			self.curCaseName	= None
1193c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			self.curResultText	= None
1203c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		elif args[0] == "#terminateTestCaseResult":
1213c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			if len(args) < 2 or self.curCaseName == None:
1223c827367444ee418f129b2c238299f49d3264554Jarkko Poyry				self.parseError("Invalid #terminateTestCaseResult")
1233c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			statusCode		= ' '.join(args[1:])
1243c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			statusDetails	= statusCode
1253c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
1263c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			if not StatusCode.isValid(statusCode):
1273c827367444ee418f129b2c238299f49d3264554Jarkko Poyry				# Legacy format
1283c827367444ee418f129b2c238299f49d3264554Jarkko Poyry				if statusCode == "Watchdog timeout occurred.":
1293c827367444ee418f129b2c238299f49d3264554Jarkko Poyry					statusCode = StatusCode.TIMEOUT
1303c827367444ee418f129b2c238299f49d3264554Jarkko Poyry				else:
1313c827367444ee418f129b2c238299f49d3264554Jarkko Poyry					statusCode = StatusCode.CRASH
1323c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
1333c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			# Do not try to parse at all since XML is likely broken
1343c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			self.testCaseResults.append(TestCaseResult(self.curCaseName, statusCode, statusDetails, self.curResultText))
1353c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
1363c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			self.curCaseName	= None
1373c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			self.curResultText	= None
1383c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		else:
1393c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			# Assume this is result text
1403c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			if self.curResultText != None:
1413c827367444ee418f129b2c238299f49d3264554Jarkko Poyry				self.curResultText += line
1423c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
1433c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	def parseTestCaseResult (self, name, log):
1443c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		try:
1453c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			doc = xml.dom.minidom.parseString(log)
1463c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			resultItems = doc.getElementsByTagName('Result')
1473c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			if len(resultItems) != 1:
1483c827367444ee418f129b2c238299f49d3264554Jarkko Poyry				self.parseError("Expected 1 <Result>, found %d" % len(resultItems))
1493c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
1503c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			statusCode		= resultItems[0].getAttributeNode('StatusCode').nodeValue
1513c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			statusDetails	= getNodeText(resultItems[0])
1523c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		except Exception as e:
1533c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			statusCode		= TestStatusCode.INTERNAL_ERROR
1543c827367444ee418f129b2c238299f49d3264554Jarkko Poyry			statusDetails	= "XML parsing failed: %s" % str(e)
1553c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
1563c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		self.testCaseResults.append(TestCaseResult(name, statusCode, statusDetails, log))
1573c827367444ee418f129b2c238299f49d3264554Jarkko Poyry
1583c827367444ee418f129b2c238299f49d3264554Jarkko Poyry	def parseError (self, message):
1593c827367444ee418f129b2c238299f49d3264554Jarkko Poyry		raise ParseError(self.filename, self.curLine, message)
160