# -*- coding: utf-8 -*- #------------------------------------------------------------------------- # drawElements Quality Program utilities # -------------------------------------- # # Copyright 2015 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # #------------------------------------------------------------------------- import shlex import sys import xml.dom.minidom class StatusCode: PASS = 'Pass' FAIL = 'Fail' QUALITY_WARNING = 'QualityWarning' COMPATIBILITY_WARNING = 'CompatibilityWarning' PENDING = 'Pending' NOT_SUPPORTED = 'NotSupported' RESOURCE_ERROR = 'ResourceError' INTERNAL_ERROR = 'InternalError' CRASH = 'Crash' TIMEOUT = 'Timeout' STATUS_CODES = [ PASS, FAIL, QUALITY_WARNING, COMPATIBILITY_WARNING, PENDING, NOT_SUPPORTED, RESOURCE_ERROR, INTERNAL_ERROR, CRASH, TIMEOUT ] STATUS_CODE_SET = set(STATUS_CODES) @staticmethod def isValid (code): return code in StatusCode.STATUS_CODE_SET class TestCaseResult: def __init__ (self, name, statusCode, statusDetails, log): self.name = name self.statusCode = statusCode self.statusDetails = statusDetails self.log = log def __str__ (self): return "%s: %s (%s)" % (self.name, self.statusCode, self.statusDetails) class ParseError(Exception): def __init__ (self, filename, line, message): self.filename = filename self.line = line self.message = message def __str__ (self): return "%s:%d: %s" % (self.filename, self.line, self.message) def splitContainerLine (line): if sys.version_info > (3, 0): # In Python 3, shlex works better with unicode. return shlex.split(line) else: # In Python 2, shlex works better with bytes, so encode and decode again upon return. return [w.decode('utf-8') for w in shlex.split(line.encode('utf-8'))] def getNodeText (node): rc = [] for node in node.childNodes: if node.nodeType == node.TEXT_NODE: rc.append(node.data) return ''.join(rc) class BatchResultParser: def __init__ (self): pass def parseFile (self, filename): self.init(filename) f = open(filename, 'rb') for line in f: self.parseLine(line) self.curLine += 1 f.close() return self.testCaseResults def getNextTestCaseResult (self, file): try: del self.testCaseResults[:] self.curResultText = None isNextResult = self.parseLine(file.next()) while not isNextResult: isNextResult = self.parseLine(file.next()) # Return the next TestCaseResult return self.testCaseResults.pop() except StopIteration: # If end of file was reached and there is no log left, the parsing finished successful (return None). # Otherwise, if there is still log to be parsed, it means that there was a crash. if self.curResultText: return TestCaseResult(self.curCaseName, StatusCode.CRASH, StatusCode.CRASH, self.curResultText) else: return None def init (self, filename): # Results self.sessionInfo = [] self.testCaseResults = [] # State self.curResultText = None self.curCaseName = None # Error context self.curLine = 1 self.filename = filename def parseLine (self, line): # Some test shaders contain invalid characters. text = line.decode('utf-8', 'ignore') if len(text) > 0 and text[0] == '#': return self.parseContainerLine(line) elif self.curResultText != None: self.curResultText += line return None # else: just ignored def parseContainerLine (self, line): isTestCaseResult = False # Some test shaders contain invalid characters. text = line.decode('utf-8', 'ignore') args = splitContainerLine(text) if args[0] == "#sessionInfo": if len(args) < 3: print(args) self.parseError("Invalid #sessionInfo") self.sessionInfo.append((args[1], ' '.join(args[2:]))) elif args[0] == "#beginSession" or args[0] == "#endSession": pass # \todo [pyry] Validate elif args[0] == "#beginTestCaseResult": if len(args) != 2 or self.curCaseName != None: self.parseError("Invalid #beginTestCaseResult") self.curCaseName = args[1] self.curResultText = b"" elif args[0] == "#endTestCaseResult": if len(args) != 1 or self.curCaseName == None: self.parseError("Invalid #endTestCaseResult") self.parseTestCaseResult(self.curCaseName, self.curResultText) self.curCaseName = None self.curResultText = None isTestCaseResult = True elif args[0] == "#terminateTestCaseResult": if len(args) < 2 or self.curCaseName == None: self.parseError("Invalid #terminateTestCaseResult") statusCode = ' '.join(args[1:]) statusDetails = statusCode if not StatusCode.isValid(statusCode): # Legacy format if statusCode == "Watchdog timeout occurred.": statusCode = StatusCode.TIMEOUT else: statusCode = StatusCode.CRASH # Do not try to parse at all since XML is likely broken self.testCaseResults.append(TestCaseResult(self.curCaseName, statusCode, statusDetails, self.curResultText)) self.curCaseName = None self.curResultText = None isTestCaseResult = True else: # Assume this is result text if self.curResultText != None: self.curResultText += line return isTestCaseResult def parseTestCaseResult (self, name, log): try: # The XML parser has troubles with invalid characters deliberately included in the shaders. # This line removes such characters before calling the parser log = log.decode('utf-8','ignore').encode("utf-8") doc = xml.dom.minidom.parseString(log) resultItems = doc.getElementsByTagName('Result') if len(resultItems) != 1: self.parseError("Expected 1 , found %d" % len(resultItems)) statusCode = resultItems[0].getAttributeNode('StatusCode').nodeValue statusDetails = getNodeText(resultItems[0]) except Exception as e: statusCode = StatusCode.INTERNAL_ERROR statusDetails = "XML parsing failed: %s" % str(e) self.testCaseResults.append(TestCaseResult(name, statusCode, statusDetails, log)) def parseError (self, message): raise ParseError(self.filename, self.curLine, message)