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