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 (inFile, outFile):
141	parser	= BatchResultParser()
142	results	= parser.parseFile(inFile)
143
144	dstDoc			= xml.dom.minidom.Document()
145	batchResultNode	= dstDoc.createElement('BatchResult')
146	batchResultNode.setAttribute("FileName", os.path.basename(inFile))
147
148	dstDoc.appendChild(batchResultNode)
149
150	for result in results:
151		# Normalize log to valid XML
152		rootNodes = normalizeToXml(result, dstDoc)
153		for node in rootNodes:
154			batchResultNode.appendChild(node)
155
156	# Summary
157	countByStatusCode = {}
158	for code in StatusCode.STATUS_CODES:
159		countByStatusCode[code] = 0
160
161	for result in results:
162		countByStatusCode[result.statusCode] += 1
163
164	summaryElem = dstDoc.createElement('ResultTotals')
165	for code in StatusCode.STATUS_CODES:
166		summaryElem.setAttribute(code, "%d" % countByStatusCode[code])
167	summaryElem.setAttribute('All', "%d" % len(results))
168	batchResultNode.appendChild(summaryElem)
169
170	text = dstDoc.toprettyxml()
171
172	out = codecs.open(outFile, "wb", encoding="utf-8")
173
174	# Write custom headers
175	out.write("<?xml version=\"1.0\"?>\n")
176	out.write("<?xml-stylesheet href=\"%s\" type=\"text/xsl\"?>\n" % STYLESHEET_FILENAME)
177
178	for line in text.splitlines()[1:]:
179		out.write(line)
180		out.write("\n")
181
182	out.close()
183
184if __name__ == "__main__":
185	if len(sys.argv) != 3:
186		print "%s: [test log] [dst file]" % sys.argv[0]
187		sys.exit(-1)
188
189	logToXml(sys.argv[1], sys.argv[2])
190