xref: /aosp_15_r20/external/deqp/scripts/log/log_parser.py (revision 35238bce31c2a825756842865a792f8cf7f89930)
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