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