|
| 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