1# -*- coding: utf-8 -*-
2
3#-------------------------------------------------------------------------
4# drawElements Quality Program utilities
5# --------------------------------------
6#
7# Copyright 2015 The Android Open Source Project
8#
9# Licensed under the Apache License, Version 2.0 (the "License");
10# you may not use this file except in compliance with the License.
11# You may obtain a copy of the License at
12#
13#      http://www.apache.org/licenses/LICENSE-2.0
14#
15# Unless required by applicable law or agreed to in writing, software
16# distributed under the License is distributed on an "AS IS" BASIS,
17# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18# See the License for the specific language governing permissions and
19# limitations under the License.
20#
21#-------------------------------------------------------------------------
22
23import shlex
24import xml.dom.minidom
25
26class StatusCode:
27	PASS					= 'Pass'
28	FAIL					= 'Fail'
29	QUALITY_WARNING			= 'QualityWarning'
30	COMPATIBILITY_WARNING	= 'CompatibilityWarning'
31	PENDING					= 'Pending'
32	NOT_SUPPORTED			= 'NotSupported'
33	RESOURCE_ERROR			= 'ResourceError'
34	INTERNAL_ERROR			= 'InternalError'
35	CRASH					= 'Crash'
36	TIMEOUT					= 'Timeout'
37
38	STATUS_CODES			= [
39		PASS,
40		FAIL,
41		QUALITY_WARNING,
42		COMPATIBILITY_WARNING,
43		PENDING,
44		NOT_SUPPORTED,
45		RESOURCE_ERROR,
46		INTERNAL_ERROR,
47		CRASH,
48		TIMEOUT
49		]
50	STATUS_CODE_SET			= set(STATUS_CODES)
51
52	@staticmethod
53	def isValid (code):
54		return code in StatusCode.STATUS_CODE_SET
55
56class TestCaseResult:
57	def __init__ (self, name, statusCode, statusDetails, log):
58		self.name			= name
59		self.statusCode		= statusCode
60		self.statusDetails	= statusDetails
61		self.log			= log
62
63	def __str__ (self):
64		return "%s: %s (%s)" % (self.name, self.statusCode, self.statusDetails)
65
66class ParseError(Exception):
67	def __init__ (self, filename, line, message):
68		self.filename	= filename
69		self.line		= line
70		self.message	= message
71
72	def __str__ (self):
73		return "%s:%d: %s" % (self.filename, self.line, self.message)
74
75def splitContainerLine (line):
76	return shlex.split(line)
77
78def getNodeText (node):
79	rc = []
80	for node in node.childNodes:
81		if node.nodeType == node.TEXT_NODE:
82			rc.append(node.data)
83	return ''.join(rc)
84
85class BatchResultParser:
86	def __init__ (self):
87		pass
88
89	def parseFile (self, filename):
90		self.init(filename)
91
92		f = open(filename, 'rb')
93		for line in f:
94			self.parseLine(line)
95			self.curLine += 1
96		f.close()
97
98		return self.testCaseResults
99
100	def init (self, filename):
101		# Results
102		self.sessionInfo		= []
103		self.testCaseResults	= []
104
105		# State
106		self.curResultText		= None
107		self.curCaseName		= None
108
109		# Error context
110		self.curLine			= 1
111		self.filename			= filename
112
113	def parseLine (self, line):
114		if len(line) > 0 and line[0] == '#':
115			self.parseContainerLine(line)
116		elif self.curResultText != None:
117			self.curResultText += line
118		# else: just ignored
119
120	def parseContainerLine (self, line):
121		args = splitContainerLine(line)
122		if args[0] == "#sessionInfo":
123			if len(args) < 3:
124				print args
125				self.parseError("Invalid #sessionInfo")
126			self.sessionInfo.append((args[1], ' '.join(args[2:])))
127		elif args[0] == "#beginSession" or args[0] == "#endSession":
128			pass # \todo [pyry] Validate
129		elif args[0] == "#beginTestCaseResult":
130			if len(args) != 2 or self.curCaseName != None:
131				self.parseError("Invalid #beginTestCaseResult")
132			self.curCaseName	= args[1]
133			self.curResultText	= ""
134		elif args[0] == "#endTestCaseResult":
135			if len(args) != 1 or self.curCaseName == None:
136				self.parseError("Invalid #endTestCaseResult")
137			self.parseTestCaseResult(self.curCaseName, self.curResultText)
138			self.curCaseName	= None
139			self.curResultText	= None
140		elif args[0] == "#terminateTestCaseResult":
141			if len(args) < 2 or self.curCaseName == None:
142				self.parseError("Invalid #terminateTestCaseResult")
143			statusCode		= ' '.join(args[1:])
144			statusDetails	= statusCode
145
146			if not StatusCode.isValid(statusCode):
147				# Legacy format
148				if statusCode == "Watchdog timeout occurred.":
149					statusCode = StatusCode.TIMEOUT
150				else:
151					statusCode = StatusCode.CRASH
152
153			# Do not try to parse at all since XML is likely broken
154			self.testCaseResults.append(TestCaseResult(self.curCaseName, statusCode, statusDetails, self.curResultText))
155
156			self.curCaseName	= None
157			self.curResultText	= None
158		else:
159			# Assume this is result text
160			if self.curResultText != None:
161				self.curResultText += line
162
163	def parseTestCaseResult (self, name, log):
164		try:
165			doc = xml.dom.minidom.parseString(log)
166			resultItems = doc.getElementsByTagName('Result')
167			if len(resultItems) != 1:
168				self.parseError("Expected 1 <Result>, found %d" % len(resultItems))
169
170			statusCode		= resultItems[0].getAttributeNode('StatusCode').nodeValue
171			statusDetails	= getNodeText(resultItems[0])
172		except Exception as e:
173			statusCode		= TestStatusCode.INTERNAL_ERROR
174			statusDetails	= "XML parsing failed: %s" % str(e)
175
176		self.testCaseResults.append(TestCaseResult(name, statusCode, statusDetails, log))
177
178	def parseError (self, message):
179		raise ParseError(self.filename, self.curLine, message)
180