Skip to content

Commit a308b41

Browse files
committed
Added a merger module to merge testkit result xml files
Signed-off-by: Nicolas Zingilé <[email protected]>
1 parent 53d579d commit a308b41

File tree

3 files changed

+334
-0
lines changed

3 files changed

+334
-0
lines changed

Diff for: testkit-merge

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/usr/bin/python
2+
#
3+
# Copyright (C) 2014 Intel Corporation
4+
#
5+
# This program is free software; you can redistribute it and/or
6+
# modify it under the terms of the GNU General Public License
7+
# as published by the Free Software Foundation; either version 2
8+
# of the License, or (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program; if not, write to the Free Software
17+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18+
#
19+
# Authors:
20+
# Nicolas Zingile <[email protected]>
21+
22+
import argparse
23+
from testkitmerge.merger import *
24+
25+
def main():
26+
argparser = argparse.ArgumentParser(description='Tool to merge testkit result xml files')
27+
argparser.add_argument('-f', '--files', help='list of testkit xml result files', nargs='+')
28+
argparser.add_argument('-o', '--outdir', help='output directory of final result file', required=True)
29+
argparser.add_argument('-n', '--name', help ='name of the final result file', default='result.xml')
30+
args = argparser.parse_args()
31+
print args
32+
33+
parser = etree.XMLParser(strip_cdata=False)
34+
sourcexmltree = None
35+
resultxmltree = None
36+
37+
for resultxml in args.files:
38+
if not os.path.isfile(resultxml):
39+
print "Error: the file '" + resultxml + "'doesn't exist !"
40+
exit(1)
41+
if not resultxml.endswith('.xml'):
42+
print "Error: '" + resultxml + "' is not an xml file !"
43+
exit(1)
44+
for index in range(len(args.files)):
45+
sourcexmltree = etree.parse(args.files[index], parser)
46+
resultxmltree = merge_testkitxml(sourcexmltree, resultxmltree)
47+
48+
resultxmltree.write(os.path.join(args.outdir, args.name), pretty_print=True, encoding='utf8', method='xml', xml_declaration=True)
49+
50+
if __name__ == "__main__":
51+
main()

Diff for: testkitmerge/__init__.py

Whitespace-only changes.

Diff for: testkitmerge/merger.py

+283
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
#!/usr/bin/python
2+
#
3+
# Copyright (C) 2014 Intel Corporation
4+
#
5+
# This program is free software; you can redistribute it and/or
6+
# modify it under the terms of the GNU General Public License
7+
# as published by the Free Software Foundation; either version 2
8+
# of the License, or (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program; if not, write to the Free Software
17+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18+
#
19+
# Authors:
20+
# Nicolas Zingile <[email protected]>
21+
22+
"""Merger module for testkit xml files"""
23+
24+
from lxml import etree
25+
import os
26+
27+
#------------------------- Global variables -------------------------#
28+
29+
TESTDEFATTRS = {}
30+
TESTDEFCHILDS = {"environment" : False, "summary" : False, "suite" : False}
31+
ENVATTRS = {"build_id" : False, "device_id" : False, "device_model" : False,
32+
"device_name" : False, "host" : False, "lite_version" : False,
33+
"manufacturer" : False, "resolution" : False, "screen_size" : False}
34+
ENVCHILDS = {"other" : False}
35+
SUMATTRS = {"test_plan_name" : False}
36+
SUMCHILDS = {"start_at" : True, "end_at" : True}
37+
SUITEATTRS = {"name" : True, "launcher" : False}
38+
SUITECHILDS = {"set" : True}
39+
SETATTRS = {"name" : True, "set_debug_msg" : False}
40+
SETCHILDS = {"testcase" : False}
41+
TCATTRS = {"component" : True, "execution_type" : True, "id" : True,
42+
"name" : False, "priority" : True, "purpose" : True,
43+
"result" : True, "status" : True, "type" : True}
44+
TCCHILDS = {"description" : False, "categories" : False, "result_info" : False,
45+
"categories" : False}
46+
DESCATTRS = {}
47+
DESCCHILDS = {"pre_condition" : False, "post_condition" : False, "steps" : False,
48+
"notes" : False, "test_script_entry" : True}
49+
RESINFOATTRS = {}
50+
RESINFOCHILDS = {"actual_result" : True, "start" : True, "end" : True,
51+
"stdout" : False, "stderr" : False}
52+
53+
class ElementError(Exception):
54+
"""Custom class to handle the merging exceptions"""
55+
pass
56+
57+
def check_element(element, attrdico, childsdico):
58+
"""Checks a node of the testkit result xml tree.
59+
60+
Allows to verify the integrity of a testkit result xml node.
61+
Check if element contains allowed attributes and if the value of
62+
those attributes is filled if it should be.
63+
Check if the child nodes of element are allowed and if so, checks
64+
if the child node contains text if it should.
65+
66+
Args:
67+
element : Element to check
68+
attrdico: A dict that contains information on attributes of element
69+
keys: String - allowed attributes of the element
70+
values: Booleans indicating if the keys should be filled
71+
childsdico: A dict that contains information on sub elements of element
72+
keys: String - allowed sub elements of element
73+
values: Booleans indicating if the keys should contain text
74+
Returns:
75+
76+
Raises:
77+
ElementError
78+
"""
79+
for attrname, attrvalue in element.attrib.items():
80+
if attrname in attrdico.keys():
81+
if not attrvalue and attrdico.get(attrname):
82+
raise ElementError("Attribute '" + attrname + "' of element '" + element.tag
83+
+ "' is not defined")
84+
else:
85+
raise ElementError("Attribute '" + attrname + "' is not authorized "
86+
+ "as an attribute of the '" + element.tag + "' element")
87+
for child in list(element):
88+
if (child.tag not in childsdico.keys()):
89+
raise ElementError("Element '" + child.tag + " should not be a child element of '" + element.tag + "'")
90+
elif (not child.text and childsdico.get(child.tag)):
91+
raise ElementError("The element '" + child.tag + "' should contain some text")
92+
93+
def create_xmltree():
94+
"""Creates an ElementTree object.
95+
Args:
96+
97+
Return:
98+
An ElementTree object that represents en empty testkit result xml file.
99+
"""
100+
root = etree.Element("test_definition")
101+
xmltree = etree.ElementTree(root)
102+
print "xml tree created !"
103+
104+
return xmltree
105+
106+
def create_envandsum(srcxmltree, destxmltree):
107+
"""Creates the environment and the summary nodes of
108+
an ElementTree object.
109+
110+
Copy the environment and the summary nodes of srcxmltree in
111+
the destxmltree. The destxmltree only contains the root element.
112+
113+
Args:
114+
srcxmltree: Source xmltree from where we want to copy some nodes.
115+
destxmltree: Destination xmltree to augment with some nodes.
116+
117+
Returns:
118+
An ElementTree that partially represents a testkit result xml file.
119+
"""
120+
testdef = destxmltree.getroot()
121+
environment = srcxmltree.find("/environment")
122+
summary = srcxmltree.find("/summary")
123+
testdef.append(environment)
124+
testdef.append(summary)
125+
126+
return destxmltree
127+
128+
def check_testdefinition(xmltree):
129+
"""Checks the test_definition node of a testkit result xml.
130+
131+
Checkis that all the sub elements of the test_definition node are present
132+
and that integrity of that sub elements is good.
133+
134+
Args:
135+
xmltree: An ElementTree that represents a testkit result xml tree.
136+
137+
Returns:
138+
139+
Raises:
140+
ElementError
141+
"""
142+
print "- checking test_definition node"
143+
testdef = xmltree.getroot()
144+
check_element(testdef, TESTDEFATTRS, TESTDEFCHILDS)
145+
environment = testdef.find("./environment")
146+
summary = testdef.find("summary")
147+
if environment is not None:
148+
print " -- checking environment node"
149+
check_element(environment, ENVATTRS, ENVCHILDS)
150+
else:
151+
raise ElementError("Element 'test_definition' should contain an 'environment' element")
152+
if summary is not None:
153+
print " -- checking summary node"
154+
check_element(summary, SUMATTRS, SUMCHILDS)
155+
else:
156+
raise ElementError("Element 'test_definition' should contain a 'summary' element")
157+
for asuite in testdef.findall("./suite"):
158+
print "- checking suite node : " + asuite.get("name")
159+
check_suite(asuite)
160+
161+
def check_suite(eltsuite):
162+
"""Checks the integrity of a suite element.
163+
164+
Args:
165+
eltsuite: A suite element to check
166+
167+
Returns:
168+
"""
169+
check_element(eltsuite, SUITEATTRS, SUITECHILDS)
170+
for child in list(eltsuite):
171+
print "- checking set node : " + child.get("name")
172+
check_set(child)
173+
174+
def check_set(eltset):
175+
"""Checks the integrity of a set element.
176+
177+
Args:
178+
eltset: A set element to check.
179+
180+
Returns:
181+
"""
182+
check_element(eltset, SETATTRS, SETCHILDS)
183+
for child in list (eltset):
184+
print "- checking testcase node : " + child.get("id")
185+
check_testcase(child)
186+
187+
def check_testcase(eltcase):
188+
"""Checks the integrity of a testcase element.
189+
190+
Also verify that the result of the eltcase is present and consistent
191+
192+
Args:
193+
eltcase: A testcase element to check
194+
195+
Returns:
196+
197+
Raises:
198+
ElementError
199+
"""
200+
print "-- checking result"
201+
try:
202+
result = eltcase.get("result")
203+
actual_result = eltcase.find("./result_info/actual_result").text
204+
allowed_results = ["PASS", "FAIL", "N/A"]
205+
except AttributeError:
206+
raise ElementError("result of the testcase is not valid !")
207+
if result not in allowed_results or result != actual_result:
208+
raise ElementError("The testcase '" + eltcase.get("id") + "' doesn't have a consistent result")
209+
check_element(eltcase, TCATTRS, TCCHILDS)
210+
for child in list (eltcase):
211+
if child.tag == "description":
212+
print "-- checking description node"
213+
check_element(child, DESCATTRS, DESCCHILDS)
214+
elif child.tag == "result_info":
215+
print "-- checking result_info node"
216+
check_element(child, RESINFOATTRS, RESINFOCHILDS)
217+
elif child.tag == "categories":
218+
pass
219+
else:
220+
raise ElementError("Element '" + child.tag + "' is not allowed")
221+
222+
def solve_conflicts(sourcecase, destcase):
223+
"""Selects a result when same testcase is encountered in both source and destination
224+
testkit xml files.
225+
226+
The result is chosen according to the following priority : FAIL > N/A > PASS.
227+
228+
Args:
229+
sourcecase: A testcase element of the source testkit xml result file
230+
destcase: A testcase element of the destination testkit xml result file
231+
232+
Returns:
233+
"""
234+
srcresult = sourcecase.get('result')
235+
destresult = destcase.get('result')
236+
if (srcresult == destresult) or (destresult == 'FAIL') \
237+
or (srcresult == 'PASS' and destresult == 'N/A'):
238+
pass
239+
else:
240+
destcase.set('result', srcresult)
241+
destcase.find('./result_info/actual_result').text = srcresult
242+
243+
def merge_testkitxml (sourcexmltree, destxmltree=None):
244+
"""Merge two testkit xml result files.
245+
246+
Merge the information of sourcexmltree in destxmltree. If destxmltree is
247+
not definded, creates a new ElementTree and copy all the information of
248+
sourcexmltree in it.
249+
250+
Args:
251+
sourcexmltree: The source ElementTree object that represents the
252+
testkit result xml source file
253+
destxmltree: The destination ElementTree object that represents the
254+
testkit result xml destination file
255+
Returns:
256+
An ElementTree that represents the result of the merging of the sourcexmltree
257+
and destxmltree
258+
"""
259+
print "## Checking source xml file ..."
260+
check_testdefinition(sourcexmltree)
261+
print "source xml file is correct. Ok\n"
262+
if destxmltree is None:
263+
print "Destination xml file doesn't exist... will be created"
264+
destxmltree = create_xmltree()
265+
create_envandsum(sourcexmltree, destxmltree)
266+
for asuite in sourcexmltree.iter('suite'):
267+
destsuite = destxmltree.find("/suite[@name='" + asuite.get('name') + "']")
268+
if destsuite is not None:
269+
for aset in asuite.iter('set'):
270+
destset = destsuite.find("./set[@name='" + aset.get('name') + "']")
271+
if destset is not None:
272+
for acase in aset.iter('testcase'):
273+
destcase = destset.find("./testcase[@id='" + acase.get('id') + "']")
274+
if destcase is not None:
275+
solve_conflicts(acase, destcase)
276+
else:
277+
destset.append(acase)
278+
else:
279+
destsuite.append(aset)
280+
else:
281+
destxmltree.getroot().append(asuite)
282+
283+
return destxmltree

0 commit comments

Comments
 (0)