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 # The XML parser has troubles with invalid characters deliberately included in the shaders. 166 # This line removes such characters before calling the parser 167 log = log.decode('utf-8','ignore').encode("utf-8") 168 doc = xml.dom.minidom.parseString(log) 169 resultItems = doc.getElementsByTagName('Result') 170 if len(resultItems) != 1: 171 self.parseError("Expected 1 <Result>, found %d" % len(resultItems)) 172 173 statusCode = resultItems[0].getAttributeNode('StatusCode').nodeValue 174 statusDetails = getNodeText(resultItems[0]) 175 except Exception as e: 176 statusCode = StatusCode.INTERNAL_ERROR 177 statusDetails = "XML parsing failed: %s" % str(e) 178 179 self.testCaseResults.append(TestCaseResult(name, statusCode, statusDetails, log)) 180 181 def parseError (self, message): 182 raise ParseError(self.filename, self.curLine, message) 183