| # -*- 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 <Result>, 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) |