1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
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
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
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
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
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
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
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
212 '''Clean up the DOM on completion.'''
213 if self.doc: self.doc.unlink()
214
215
217 '''Return the filename of the test descriptor.'''
218 return self.file
219
220
222 '''Return the id of the test.'''
223 return os.path.basename(self.dirname)
224
225
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
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
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
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
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
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
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
328
329
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
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
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
364
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