xref: /aosp_15_r20/external/deqp/scripts/log/log_to_xml.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 os
24import sys
25import codecs
26import xml.dom.minidom
27import xml.sax
28import xml.sax.handler
29from log_parser import BatchResultParser, StatusCode
30
31STYLESHEET_FILENAME = "testlog.xsl"
32LOG_VERSION = '0.3.2'
33
34class BuildXMLLogHandler(xml.sax.handler.ContentHandler):
35    def __init__ (self, doc):
36        self.doc = doc
37        self.elementStack = []
38        self.rootElements = []
39
40    def getRootElements (self):
41        return self.rootElements
42
43    def pushElement (self, elem):
44        if len(self.elementStack) == 0:
45            self.rootElements.append(elem)
46        else:
47            self.getCurElement().appendChild(elem)
48        self.elementStack.append(elem)
49
50    def popElement (self):
51        self.elementStack.pop()
52
53    def getCurElement (self):
54        if len(self.elementStack) > 0:
55            return self.elementStack[-1]
56        else:
57            return None
58
59    def startDocument (self):
60        pass
61
62    def endDocument (self):
63        pass
64
65    def startElement (self, name, attrs):
66        elem = self.doc.createElement(name)
67        for name in attrs.getNames():
68            value = attrs.getValue(name)
69            elem.setAttribute(name, value)
70        self.pushElement(elem)
71
72    def endElement (self, name):
73        self.popElement()
74
75    def characters (self, content):
76        # Discard completely empty content
77        if len(content.strip()) == 0:
78            return
79
80        # Append as text node (not pushed to stack)
81        if self.getCurElement() != None:
82            txt = self.doc.createTextNode(content)
83            self.getCurElement().appendChild(txt)
84
85class LogErrorHandler(xml.sax.handler.ErrorHandler):
86    def __init__ (self):
87        pass
88
89    def error (self, err):
90        #print("error(%s)" % str(err))
91        pass
92
93    def fatalError (self, err):
94        #print("fatalError(%s)" % str(err))
95        pass
96
97    def warning (self, warn):
98        #print("warning(%s)" % str(warn))
99        pass
100
101def findFirstElementByName (nodes, name):
102    for node in nodes:
103        if node.nodeName == name:
104            return node
105        chFound = findFirstElementByName(node.childNodes, name)
106        if chFound != None:
107            return chFound
108    return None
109
110# Normalizes potentially broken (due to crash for example) log data to XML element tree
111def normalizeToXml (result, doc):
112    handler = BuildXMLLogHandler(doc)
113    errHandler = LogErrorHandler()
114
115    xml.sax.parseString(result.log, handler, errHandler)
116
117    rootNodes = handler.getRootElements()
118
119    # Check if we have TestCaseResult
120    testCaseResult = findFirstElementByName(rootNodes, 'TestCaseResult')
121    if testCaseResult == None:
122        # Create TestCaseResult element
123        testCaseResult = doc.createElement('TestCaseResult')
124        testCaseResult.setAttribute('CasePath', result.name)
125        testCaseResult.setAttribute('CaseType', 'SelfValidate') # \todo [pyry] Not recoverable..
126        testCaseResult.setAttribute('Version', LOG_VERSION)
127        rootNodes.append(testCaseResult)
128
129    # Check if we have Result element
130    resultElem = findFirstElementByName(rootNodes, 'Result')
131    if resultElem == None:
132        # Create result element
133        resultElem = doc.createElement('Result')
134        resultElem.setAttribute('StatusCode', result.statusCode)
135        resultElem.appendChild(doc.createTextNode(result.statusDetails))
136        testCaseResult.appendChild(resultElem)
137
138    return rootNodes
139
140def logToXml (logFilePath, outFilePath):
141    # Initialize Xml Document
142    dstDoc = xml.dom.minidom.Document()
143    batchResultNode = dstDoc.createElement('BatchResult')
144    batchResultNode.setAttribute("FileName", os.path.basename(logFilePath))
145    dstDoc.appendChild(batchResultNode)
146
147    # Initialize dictionary for counting status codes
148    countByStatusCode = {}
149    for code in StatusCode.STATUS_CODES:
150        countByStatusCode[code] = 0
151
152    # Write custom headers
153    out = codecs.open(outFilePath, "wb", encoding="utf-8")
154    out.write("<?xml version=\"1.0\"?>\n")
155    out.write("<?xml-stylesheet href=\"%s\" type=\"text/xsl\"?>\n" % STYLESHEET_FILENAME)
156
157    summaryElem = dstDoc.createElement('ResultTotals')
158    batchResultNode.appendChild(summaryElem)
159
160    # Print the first line manually <BatchResult FileName=something.xml>
161    out.write(dstDoc.toprettyxml().splitlines()[1])
162    out.write("\n")
163
164    parser = BatchResultParser()
165    parser.init(logFilePath)
166    logFile = open(logFilePath, 'rb')
167
168    result = parser.getNextTestCaseResult(logFile)
169    while result is not None:
170
171        countByStatusCode[result.statusCode] += 1
172        rootNodes = normalizeToXml(result, dstDoc)
173
174        for node in rootNodes:
175
176            # Do not append TestResults to dstDoc to save memory.
177            # Instead print them directly to the file and add tabs manually.
178            for line in node.toprettyxml().splitlines():
179                out.write("\t" + line + "\n")
180
181        result = parser.getNextTestCaseResult(logFile)
182
183    # Calculate the totals to add at the end of the Xml file
184    for code in StatusCode.STATUS_CODES:
185        summaryElem.setAttribute(code, "%d" % countByStatusCode[code])
186    summaryElem.setAttribute('All', "%d" % sum(countByStatusCode.values()))
187
188    # Print the test totals and finish the Xml Document"
189    for line in dstDoc.toprettyxml().splitlines()[2:]:
190        out.write(line + "\n")
191
192    out.close()
193    logFile.close()
194
195if __name__ == "__main__":
196    if len(sys.argv) != 3:
197        print("%s: [test log] [dst file]" % sys.argv[0])
198        sys.exit(-1)
199
200    logToXml(sys.argv[1], sys.argv[2])
201