Package pysys :: Package xml :: Module descriptor
[hide private]
[frames] | no frames]

Source Code for Module pysys.xml.descriptor

  1  #!/usr/bin/env pytho 
  2  # PySys System Test Framework, Copyright (C) 2006-2016  M.B.Grieve 
  3   
  4  # This library is free software; you can redistribute it and/or 
  5  # modify it under the terms of the GNU Lesser General Public 
  6  # License as published by the Free Software Foundation; either 
  7  # version 2.1 of the License, or (at your option) any later version. 
  8   
  9  # This library is distributed in the hope that it will be useful, 
 10  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 11  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 12  # Lesser General Public License for more details. 
 13   
 14  # You should have received a copy of the GNU Lesser General Public 
 15  # License along with this library; if not, write to the Free Software 
 16  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 
 17   
 18  # Contact: moraygrieve@users.sourceforge.net 
 19   
 20  import os, os.path, sys, logging, xml.dom.minidom 
 21   
 22  from pysys.constants import * 
 23   
 24  log = logging.getLogger('pysys.xml.descriptor') 
 25   
 26  DTD=''' 
 27  <!ELEMENT pysystest (description, classification?, data?, traceability?) >  
 28  <!ELEMENT description (title, purpose) > 
 29  <!ELEMENT classification (groups?, modes?) > 
 30  <!ELEMENT data (class?, input?, output?, reference?) > 
 31  <!ELEMENT traceability (requirements) > 
 32  <!ELEMENT title (#PCDATA) > 
 33  <!ELEMENT purpose (#PCDATA) > 
 34  <!ELEMENT groups (group)+ > 
 35  <!ELEMENT modes (mode)+ > 
 36  <!ELEMENT class EMPTY > 
 37  <!ELEMENT input EMPTY > 
 38  <!ELEMENT output EMPTY > 
 39  <!ELEMENT reference EMPTY > 
 40  <!ELEMENT requirements (requirement)+ >   
 41  <!ELEMENT group (#PCDATA) > 
 42  <!ELEMENT mode (#PCDATA) > 
 43  <!ELEMENT requirement EMPTY > 
 44  <!ATTLIST pysystest type (auto | manual ) "auto" > 
 45  <!ATTLIST pysystest state (runnable | deprecated | skipped) "runnable" > 
 46  <!ATTLIST class name CDATA #REQUIRED 
 47                  module CDATA #REQUIRED > 
 48  <!ATTLIST input path CDATA #REQUIRED > 
 49  <!ATTLIST output path CDATA #REQUIRED > 
 50  <!ATTLIST reference path CDATA #REQUIRED > 
 51  <!ATTLIST requirement id CDATA #REQUIRED > 
 52  ''' 
 53   
 54   
 55  DESCRIPTOR_TEMPLATE ='''<?xml version="1.0" standalone="yes"?> 
 56  <pysystest type="%s" state="runnable"> 
 57       
 58    <description>  
 59      <title></title>     
 60      <purpose><![CDATA[ 
 61  ]]> 
 62      </purpose> 
 63    </description> 
 64   
 65    <classification> 
 66      <groups> 
 67        <group>%s</group> 
 68      </groups> 
 69    </classification> 
 70   
 71    <data> 
 72      <class name="%s" module="%s"/> 
 73    </data> 
 74     
 75    <traceability> 
 76      <requirements> 
 77        <requirement id=""/>      
 78      </requirements> 
 79    </traceability> 
 80  </pysystest> 
 81  '''  
 82   
 83   
84 -class XMLDescriptorContainer:
85 """Holder class for the contents of a testcase descriptor. """ 86
87 - def __init__(self, file, id, type, state, title, purpose, groups, modes, classname, module, input, output, reference, traceability):
88 """Create an instance of the XMLDescriptorContainer class. 89 90 @param id: The testcase identifier 91 @param type: The type of the testcase (automated or manual) 92 @param state: The state of the testcase (runable, deprecated or skipped) 93 @param title: The title of the testcase 94 @param purpose: The purpose of the testcase 95 @param groups: A list of the user defined groups the testcase belongs to 96 @param modes: A list of the user defined modes the testcase can be run in 97 @param classname: The classname of the testcase 98 @param module: The full path to the python module containing the testcase class 99 @param input: The full path to the input directory of the testcase 100 @param output: The full path to the output parent directory of the testcase 101 @param reference: The full path to the reference directory of the testcase 102 @param traceability: A list of the requirements covered by the testcase 103 104 """ 105 self.file = file 106 self.id = id 107 self.type = type 108 self.state = state 109 self.title = title 110 self.purpose = purpose 111 self.groups = groups 112 self.modes = modes 113 self.classname = classname 114 self.module = module 115 self.input = input 116 self.output = output 117 self.reference = reference 118 self.traceability = traceability
119 120
121 - def __str__(self):
122 """Return an informal string representation of the xml descriptor container object 123 124 @return: The string represention 125 @rtype: string 126 """ 127 128 str= "Test id: %s\n" % self.id 129 str=str+"Test type: %s\n" % self.type 130 str=str+"Test state: %s\n" % self.state 131 str=str+"Test title: %s\n" % self.title 132 str=str+"Test purpose: " 133 purpose = self.purpose.split('\n') 134 for index in range(0, len(purpose)): 135 if index == 0: str=str+"%s\n" % purpose[index] 136 if index != 0: str=str+" %s\n" % purpose[index] 137 str=str+"Test groups: %s\n" % self.groups 138 str=str+"Test modes: %s\n" % self.modes 139 str=str+"Test classname: %s\n" % self.classname 140 str=str+"Test module: %s\n" % self.module 141 str=str+"Test input: %s\n" % self.input 142 str=str+"Test output: %s\n" % self.output 143 str=str+"Test reference: %s\n" % self.reference 144 str=str+"Test traceability: %s\n" % self.traceability 145 str=str+"" 146 return str
147 148
149 -class XMLDescriptorCreator:
150 '''Helper class to create a test descriptor template.''' 151
152 - def __init__(self, file, type="auto", group=DEFAULT_GROUP, testclass=DEFAULT_TESTCLASS, module=DEFAULT_MODULE):
153 '''Class constructor.''' 154 self.file=file 155 self.type = type 156 self.group = group 157 self.testclass = testclass 158 self.module = module
159
160 - def writeXML(self):
161 '''Write a test descriptor template to file.''' 162 fp = open(self.file, 'w') 163 fp.writelines(DESCRIPTOR_TEMPLATE % (self.type, self.group, self.testclass, self.module)) 164 fp.close
165 166 167
168 -class XMLDescriptorParser:
169 '''Helper class to parse an XML test descriptor. 170 171 The class uses the minidom DOM (Document Object Model) non validating 172 parser to provide accessor methods to return element attributes and character 173 data from the test descriptor file. The class is instantiated with the filename 174 of the test descriptor. It is the responsibility of the user of the class to 175 call the unlink() method of the class on completion in order to free the memory 176 used in the parsing. 177 178 ''' 179
180 - def __init__(self, xmlfile):
181 '''Class constructor.''' 182 self.file = xmlfile 183 self.dirname = os.path.dirname(xmlfile) 184 if not os.path.exists(xmlfile): 185 raise Exception, "Unable to find supplied test descriptor \"%s\"" % xmlfile 186 187 try: 188 self.doc = xml.dom.minidom.parse(xmlfile) 189 except: 190 raise Exception, "%s " % (sys.exc_info()[1]) 191 else: 192 if self.doc.getElementsByTagName('pysystest') == []: 193 raise Exception, "No <pysystest> element supplied in XML descriptor" 194 else: 195 self.root = self.doc.getElementsByTagName('pysystest')[0]
196 197
198 - def getContainer(self):
199 '''Create and return an instance of XMLDescriptorContainer for the contents of the descriptor.''' 200 return XMLDescriptorContainer(self.getFile(), self.getID(), self.getType(), self.getState(), 201 self.getTitle(), self.getPurpose(), 202 self.getGroups(), self.getModes(), 203 self.getClassDetails()[0], 204 os.path.join(self.dirname, self.getClassDetails()[1]), 205 os.path.join(self.dirname, self.getTestInput()), 206 os.path.join(self.dirname, self.getTestOutput()), 207 os.path.join(self.dirname, self.getTestReference()), 208 self.getRequirements())
209 210 214 215
216 - def getFile(self):
217 '''Return the filename of the test descriptor.''' 218 return self.file
219 220
221 - def getID(self):
222 '''Return the id of the test.''' 223 return os.path.basename(self.dirname)
224 225
226 - def getType(self):
227 '''Return the type attribute of the test element.''' 228 type = self.root.getAttribute("type") 229 if type == "": 230 type = "auto" 231 elif type not in ["auto", "manual"]: 232 raise Exception, "The type attribute of the test element should be \"auto\" or \"manual\"" 233 return type
234 235
236 - def getState(self):
237 '''Return the state attribute of the test element.''' 238 state = self.root.getAttribute("state") 239 if state == "": 240 state = "runnable" 241 elif state not in ["runnable", "deprecated", "skipped"]: 242 raise Exception, "The state attribute of the test element should be \"runnable\", \"deprecated\" or \"skipped\"" 243 return state
244 245
246 - def getTitle(self):
247 '''Return the test titlecharacter data of the description element.''' 248 descriptionNodeList = self.root.getElementsByTagName('description') 249 if descriptionNodeList == []: 250 raise Exception, "No <description> element supplied in XML descriptor" 251 252 if descriptionNodeList[0].getElementsByTagName('title') == []: 253 raise Exception, "No <title> child element of <description> supplied in XML descriptor" 254 else: 255 try: 256 title = descriptionNodeList[0].getElementsByTagName('title')[0] 257 return title.childNodes[0].data 258 except: 259 return ""
260 261
262 - def getPurpose(self):
263 '''Return the test purpose character data of the description element.''' 264 descriptionNodeList = self.root.getElementsByTagName('description') 265 if descriptionNodeList == []: 266 raise Exception, "No <description> element supplied in XML descriptor" 267 268 if descriptionNodeList[0].getElementsByTagName('purpose') == []: 269 raise Exception, "No <purpose> child element of <description> supplied in XML descriptor" 270 else: 271 try: 272 purpose = descriptionNodeList[0].getElementsByTagName('purpose')[0] 273 return purpose.childNodes[0].data 274 except: 275 return ""
276 277
278 - def getGroups(self):
279 '''Return a list of the group names, contained in the character data of the group elements.''' 280 classificationNodeList = self.root.getElementsByTagName('classification') 281 if classificationNodeList == []: 282 raise Exception, "No <classification> element supplied in XML descriptor" 283 284 groupList = [] 285 try: 286 groups = classificationNodeList[0].getElementsByTagName('groups')[0] 287 for node in groups.getElementsByTagName('group'): 288 groupList.append(node.childNodes[0].data) 289 return groupList 290 except: 291 return []
292 293
294 - def getModes(self):
295 '''Return a list of the mode names, contained in the character data of the mode elements.''' 296 classificationNodeList = self.root.getElementsByTagName('classification') 297 if classificationNodeList == []: 298 raise Exception, "No <classification> element supplied in XML descriptor" 299 300 modeList = [] 301 try: 302 modes = classificationNodeList[0].getElementsByTagName('modes')[0] 303 for node in modes.getElementsByTagName('mode'): 304 modeList.append(node.childNodes[0].data) 305 return modeList 306 except: 307 return []
308 309
310 - def getClassDetails(self):
311 '''Return the test class attributes (name, module, searchpath), contained in the class element.''' 312 try: 313 dataNodeList = self.root.getElementsByTagName('data') 314 el = dataNodeList[0].getElementsByTagName('class')[0] 315 return [el.getAttribute('name'), el.getAttribute('module')] 316 except: 317 return [DEFAULT_TESTCLASS, DEFAULT_MODULE]
318 319
320 - def getTestInput(self):
321 '''Return the test input path, contained in the input element.''' 322 try: 323 dataNodeList = self.root.getElementsByTagName('data') 324 input = dataNodeList[0].getElementsByTagName('input')[0] 325 return input.getAttribute('path') 326 except: 327 return DEFAULT_INPUT
328 329
330 - def getTestOutput(self):
331 '''Return the test output path, contained in the output element.''' 332 try: 333 dataNodeList = self.root.getElementsByTagName('data') 334 output = dataNodeList[0].getElementsByTagName('output')[0] 335 return output.getAttribute('path') 336 except: 337 return DEFAULT_OUTPUT
338 339
340 - def getTestReference(self):
341 '''Return the test reference path, contained in the reference element.''' 342 try: 343 dataNodeList = self.root.getElementsByTagName('data') 344 ref = dataNodeList[0].getElementsByTagName('reference')[0] 345 return ref.getAttribute('path') 346 except: 347 return DEFAULT_REFERENCE
348 349
350 - def getRequirements(self):
351 '''Return a list of the requirement ids, contained in the character data of the requirement elements.''' 352 reqList = [] 353 try: 354 traceabilityNodeList = self.root.getElementsByTagName('traceability') 355 requirements = traceabilityNodeList[0].getElementsByTagName('requirements')[0] 356 for node in requirements.getElementsByTagName('requirement'): 357 reqList.append(node.getAttribute('id')) 358 return reqList 359 except: 360 return []
361 362 363 # entry point when running the class from the command line 364 # (used for development, testing, demonstration etc) 365 if __name__ == "__main__": 366 367 if ( len(sys.argv) < 2 ) or ( sys.argv[1] not in ("create", "parse", "validate") ): 368 print "Usage: %s (create | parse ) filename" % os.path.basename(sys.argv[0]) 369 sys.exit() 370 371 if sys.argv[1] == "parse": 372 parser = XMLDescriptorParser(sys.argv[2]) 373 print parser.getContainer() 374 parser.unlink() 375 376 elif sys.argv[1] == "create": 377 creator = XMLDescriptorCreator(sys.argv[2]) 378 creator.writeXML() 379 380 elif sys.argv[1] == "validate": 381 from xml.parsers.xmlproc.xmlval import XMLValidator 382 XMLValidator().parse_resource(sys.argv[2]) 383