• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 sys
25import xml.dom.minidom
26
27class StatusCode:
28	PASS					= 'Pass'
29	FAIL					= 'Fail'
30	QUALITY_WARNING			= 'QualityWarning'
31	COMPATIBILITY_WARNING	= 'CompatibilityWarning'
32	PENDING					= 'Pending'
33	NOT_SUPPORTED			= 'NotSupported'
34	RESOURCE_ERROR			= 'ResourceError'
35	INTERNAL_ERROR			= 'InternalError'
36	CRASH					= 'Crash'
37	TIMEOUT					= 'Timeout'
38
39	STATUS_CODES			= [
40		PASS,
41		FAIL,
42		QUALITY_WARNING,
43		COMPATIBILITY_WARNING,
44		PENDING,
45		NOT_SUPPORTED,
46		RESOURCE_ERROR,
47		INTERNAL_ERROR,
48		CRASH,
49		TIMEOUT
50		]
51	STATUS_CODE_SET			= set(STATUS_CODES)
52
53	@staticmethod
54	def isValid (code):
55		return code in StatusCode.STATUS_CODE_SET
56
57class TestCaseResult:
58	def __init__ (self, name, statusCode, statusDetails, log):
59		self.name			= name
60		self.statusCode		= statusCode
61		self.statusDetails	= statusDetails
62		self.log			= log
63
64	def __str__ (self):
65		return "%s: %s (%s)" % (self.name, self.statusCode, self.statusDetails)
66
67class ParseError(Exception):
68	def __init__ (self, filename, line, message):
69		self.filename	= filename
70		self.line		= line
71		self.message	= message
72
73	def __str__ (self):
74		return "%s:%d: %s" % (self.filename, self.line, self.message)
75
76def splitContainerLine (line):
77	if sys.version_info > (3, 0):
78		# In Python 3, shlex works better with unicode.
79		return shlex.split(line)
80	else:
81		# In Python 2, shlex works better with bytes, so encode and decode again upon return.
82		return [w.decode('utf-8') for w in shlex.split(line.encode('utf-8'))]
83
84def getNodeText (node):
85	rc = []
86	for node in node.childNodes:
87		if node.nodeType == node.TEXT_NODE:
88			rc.append(node.data)
89	return ''.join(rc)
90
91class BatchResultParser:
92	def __init__ (self):
93		pass
94
95	def parseFile (self, filename):
96		self.init(filename)
97
98		f = open(filename, 'rb')
99		for line in f:
100			self.parseLine(line)
101			self.curLine += 1
102		f.close()
103
104		return self.testCaseResults
105
106	def getNextTestCaseResult (self, file):
107		try:
108			del self.testCaseResults[:]
109			self.curResultText = None
110
111			isNextResult = self.parseLine(next(file))
112			while not isNextResult:
113				isNextResult = self.parseLine(next(file))
114
115			# Return the next TestCaseResult
116			return self.testCaseResults.pop()
117
118		except StopIteration:
119			# If end of file was reached and there is no log left, the parsing finished successful (return None).
120			# Otherwise, if there is still log to be parsed, it means that there was a crash.
121			if self.curResultText:
122				return TestCaseResult(self.curCaseName, StatusCode.CRASH, StatusCode.CRASH, self.curResultText)
123			else:
124				return None
125
126	def init (self, filename):
127		# Results
128		self.sessionInfo		= []
129		self.testCaseResults	= []
130
131		# State
132		self.curResultText		= None
133		self.curCaseName		= None
134
135		# Error context
136		self.curLine			= 1
137		self.filename			= filename
138
139	def parseLine (self, line):
140		# Some test shaders contain invalid characters.
141		text = line.decode('utf-8', 'ignore')
142		if len(text) > 0 and text[0] == '#':
143			return self.parseContainerLine(line)
144		elif self.curResultText != None:
145			self.curResultText += line
146			return None
147		# else: just ignored
148
149	def parseContainerLine (self, line):
150		isTestCaseResult = False
151		# Some test shaders contain invalid characters.
152		text = line.decode('utf-8', 'ignore')
153		args = splitContainerLine(text)
154		if args[0] == "#sessionInfo":
155			if len(args) < 3:
156				print(args)
157				self.parseError("Invalid #sessionInfo")
158			self.sessionInfo.append((args[1], ' '.join(args[2:])))
159		elif args[0] == "#beginSession" or args[0] == "#endSession":
160			pass # \todo [pyry] Validate
161		elif args[0] == "#beginTestCaseResult":
162			if len(args) != 2 or self.curCaseName != None:
163				self.parseError("Invalid #beginTestCaseResult")
164			self.curCaseName	= args[1]
165			self.curResultText	= b""
166		elif args[0] == "#endTestCaseResult":
167			if len(args) != 1 or self.curCaseName == None:
168				self.parseError("Invalid #endTestCaseResult")
169			self.parseTestCaseResult(self.curCaseName, self.curResultText)
170			self.curCaseName	= None
171			self.curResultText	= None
172			isTestCaseResult	= True
173		elif args[0] == "#terminateTestCaseResult":
174			if len(args) < 2 or self.curCaseName == None:
175				self.parseError("Invalid #terminateTestCaseResult")
176			statusCode		= ' '.join(args[1:])
177			statusDetails	= statusCode
178
179			if not StatusCode.isValid(statusCode):
180				# Legacy format
181				if statusCode == "Watchdog timeout occurred.":
182					statusCode = StatusCode.TIMEOUT
183				else:
184					statusCode = StatusCode.CRASH
185
186			# Do not try to parse at all since XML is likely broken
187			self.testCaseResults.append(TestCaseResult(self.curCaseName, statusCode, statusDetails, self.curResultText))
188
189			self.curCaseName	= None
190			self.curResultText	= None
191			isTestCaseResult	= True
192		else:
193			# Assume this is result text
194			if self.curResultText != None:
195				self.curResultText += line
196
197		return isTestCaseResult
198
199	def parseTestCaseResult (self, name, log):
200		try:
201			# The XML parser has troubles with invalid characters deliberately included in the shaders.
202			# This line removes such characters before calling the parser
203			log = log.decode('utf-8','ignore').encode("utf-8")
204			doc = xml.dom.minidom.parseString(log)
205			resultItems = doc.getElementsByTagName('Result')
206			if len(resultItems) != 1:
207				self.parseError("Expected 1 <Result>, found %d" % len(resultItems))
208
209			statusCode		= resultItems[0].getAttributeNode('StatusCode').nodeValue
210			statusDetails	= getNodeText(resultItems[0])
211		except Exception as e:
212			statusCode		= StatusCode.INTERNAL_ERROR
213			statusDetails	= "XML parsing failed: %s" % str(e)
214
215		self.testCaseResults.append(TestCaseResult(name, statusCode, statusDetails, log))
216
217	def parseError (self, message):
218		raise ParseError(self.filename, self.curLine, message)
219