Package qm :: Package test :: Package classes :: Module xml_database
[hide private]
[frames] | no frames]

Source Code for Module qm.test.classes.xml_database

  1  ######################################################################## 
  2  # 
  3  # File:   xml_database.py 
  4  # Author: Alex Samuel 
  5  # Date:   2001-03-08 
  6  # 
  7  # Contents: 
  8  #   XML-based test database implementation. 
  9  # 
 10  # Copyright (c) 2001, 2002, 2003 by CodeSourcery, LLC.  All rights reserved.  
 11  # 
 12  # For license terms see the file COPYING. 
 13  # 
 14  ######################################################################## 
 15   
 16  ######################################################################## 
 17  # imports 
 18  ######################################################################## 
 19   
 20  import os 
 21  import qm.common 
 22  from   qm.extension import get_class_arguments 
 23  import qm.fields 
 24  import qm.label 
 25  import qm.structured_text 
 26  import qm.test.base 
 27  from   qm.test.database import * 
 28  from   qm.test.file_database import * 
 29  from   qm.test.suite import * 
 30  from   qm.test.classes.explicit_suite import ExplicitSuite 
 31  import qm.xmlutil 
 32  import shutil 
 33  import string 
 34  import xml 
 35  import xml.dom 
 36  import xml.sax 
 37   
 38  ######################################################################## 
 39  # classes 
 40  ######################################################################## 
 41   
42 -class TestFileError(RuntimeError):
43 """An error in the format or contents of an XML test file.""" 44 45 pass
46 47 48
49 -class XMLDatabase(ExtensionDatabase):
50 """A database representing tests as XML files in a directory tree.""" 51
52 - def __init__(self, path, arguments):
53 54 # Initialize base classes. 55 ExtensionDatabase.__init__(self, path, arguments) 56 # Create an AttachmentStore for this database. 57 self.__store = qm.attachment.FileAttachmentStore(path)
58 59
60 - def _GetTestFromPath(self, test_id, test_path):
61 try: 62 return self.__LoadItem(test_id, test_path, 63 self.__ParseTestDocument) 64 except Exception, exception: 65 # Problem while parsing XML. 66 message = qm.error("error loading xml test", 67 test_id=test_id, 68 message=str(exception)) 69 raise TestFileError, message
70 71
72 - def _GetResourceFromPath(self, resource_id, resource_path):
73 try: 74 return self.__LoadItem(resource_id, resource_path, 75 self.__ParseResourceDocument) 76 except Exception, exception: 77 # Problem while parsing XML. 78 message = qm.error("error loading xml resource", 79 resource_id=resource_id, 80 message=str(exception)) 81 raise TestFileError, message
82 83 # Helper functions. 84
85 - def __StoreAttachments(self, item):
86 """Store all attachments in 'item' in the attachment store. 87 88 'item' -- A 'Test' or 'Resource'. If any of its fields contain 89 attachments, add them to the 'AttachmentStore'.""" 90 91 # Get all of the attachments associated with the new item. 92 new_attachments = item.GetAttachments() 93 94 # Remove old attachments that are not also among the new 95 # attachments. 96 store = self.GetAttachmentStore() 97 try: 98 old_item = self.GetItem(item.kind, item.GetId()) 99 except: 100 old_item = None 101 if old_item: 102 old_attachments = old_item.GetItem().GetAttachments() 103 for o in old_attachments: 104 found = 0 105 for n in new_attachments: 106 if (n.GetStore() == store 107 and n.GetFileName() == o.GetFileName()): 108 found = 1 109 break 110 if not found: 111 store.Remove(o.GetLocation()) 112 113 # Put any new attachments into the attachment store. 114 for a in new_attachments: 115 if a.GetStore() != store: 116 location = self.__MakeDataFilePath(item.GetId(), 117 a.GetFileName()) 118 a.Move(store, location)
119 120
121 - def __MakeDataFilePath(self, item_id, file_name):
122 """Construct the path to an attachment data file. 123 124 'item_id' -- The test or resource item of which the attachment 125 is part. 126 127 'file_name' -- The file name specified for the attachment.""" 128 129 # Convert the item's containing suite to a path. 130 parent_suite_path \ 131 = os.path.dirname(self._GetPathFromLabel(item_id)) 132 # The relative part of the eventual full file name will be 133 # the part after the parent_suite_path and the directory 134 # name separator character(s). 135 abs_len = len(parent_suite_path) + len(os.sep) 136 137 # Construct a file name free of suspicious characters. 138 base, extension = os.path.splitext(file_name) 139 safe_file_name = qm.label.thunk(base) + extension 140 141 data_file_path = os.path.join(parent_suite_path, safe_file_name) 142 # Is the file name by itself OK in this directory? It must not 143 # have a file extension used by the XML database itself, and 144 # there must be no other file with the same name there. 145 if extension not in [self.GetTestExtension(), 146 self.GetSuiteExtension(), 147 self.GetResourceExtension()] \ 148 and not os.path.exists(data_file_path): 149 return data_file_path[abs_len:] 150 151 # No good. Construct alternate names by appending numbers 152 # incrementally. 153 index = 0 154 while 1: 155 data_file_path = os.path.join(parent_suite_path, 156 safe_file_name + ".%d" % index) 157 if not os.path.exists(data_file_path): 158 return data_file_path[abs_len:] 159 index = index + 1
160 161
162 - def __LoadItem(self, item_id, path, document_parser):
163 """Load an item (a test or resource) from an XML file. 164 165 This function is used for logic common to tests and resources. 166 167 'item_id' -- The ID of the item to get. 168 169 'path' -- The path to the test or resource file. 170 171 'document_parser' -- A function that takes an XML DOM document 172 as its argument and returns the constructed item object.""" 173 174 # Load and parse the XML item representation. 175 document = qm.xmlutil.load_xml_file(path) 176 # Turn it into an object. 177 item = document_parser(item_id, document) 178 179 return item
180 181
182 - def __ParseTestDocument(self, test_id, document):
183 """Return a test object constructed from a test document. 184 185 'test_id' -- The test ID of the test. 186 187 'document' -- A DOM document containing a single test element 188 from which the test is constructed.""" 189 190 # Parse the DOM node. 191 test_class, arguments \ 192 = (qm.extension.parse_dom_element 193 (document.documentElement, 194 lambda n : qm.test.base.get_test_class(n, self), 195 self.__store)) 196 test_class_name = qm.extension.get_extension_class_name(test_class) 197 # For backwards compatibility, look for "prerequisite" elements. 198 for p in document.documentElement.getElementsByTagName("prerequisite"): 199 if not arguments.has_key("prerequisites"): 200 arguments["prerequisites"] = [] 201 arguments["prerequisites"].append((qm.xmlutil.get_dom_text(p), 202 p.getAttribute("outcome"))) 203 # For backwards compatibility, look for "resource" elements. 204 for r in document.documentElement.getElementsByTagName("resource"): 205 if not arguments.has_key("resources"): 206 arguments["resources"] = [] 207 arguments["resources"].append(qm.xmlutil.get_dom_text(r)) 208 # Construct a descriptor for it. 209 test = TestDescriptor(self, 210 test_id, 211 test_class_name, 212 arguments) 213 return test
214 215
216 - def __ParseResourceDocument(self, resource_id, document):
217 """Return a resource object constructed from a resource document. 218 219 'resource_id' -- The resource ID of the resource. 220 221 'document' -- A DOM document node containing a single resource 222 element from which the resource object is constructed.""" 223 224 # Parse the DOM node. 225 resource_class, arguments \ 226 = (qm.extension.parse_dom_element 227 (document.documentElement, 228 lambda n : qm.test.base.get_resource_class(n, self))) 229 resource_class_name \ 230 = qm.extension.get_extension_class_name(resource_class) 231 # Construct a descriptor for it. 232 resource = ResourceDescriptor(self, 233 resource_id, 234 resource_class_name, 235 arguments) 236 return resource
237 238
239 - def _GetSuiteFromPath(self, suite_id, path):
240 """Load the test suite file at 'path' with suite ID 'suite_id'. 241 242 returns -- A 'Suite' object.""" 243 244 # Make sure there is a file by that name. 245 if not os.path.isfile(path): 246 raise NoSuchSuiteError, "no suite file %s" % path 247 # Load and parse the suite file. 248 document = qm.xmlutil.load_xml_file(path) 249 # For backwards compatibility, handle XML files using the 250 # "suite" tag. New databases will have Suite files using the 251 # "extension" tag. 252 suite = document.documentElement 253 if suite.tagName == "suite": 254 assert suite.tagName == "suite" 255 # Extract the test and suite IDs. 256 test_ids = qm.xmlutil.get_child_texts(suite, "test_id") 257 suite_ids = qm.xmlutil.get_child_texts(suite, "suite_id") 258 # Make sure they're all valid. 259 for id_ in test_ids + suite_ids: 260 if not self.IsValidLabel(id_, is_component = 0): 261 raise RuntimeError, qm.error("invalid id", id=id_) 262 # Construct the suite. 263 return ExplicitSuite({ "is_implicit" : "false", 264 "test_ids" : test_ids, 265 "suite_ids" : suite_ids }, 266 **{ ExplicitSuite.EXTRA_ID : suite_id, 267 ExplicitSuite.EXTRA_DATABASE : self }) 268 else: 269 # Load the extension. 270 extension_class, arguments = \ 271 qm.extension.parse_dom_element( 272 suite, 273 lambda n: get_extension_class(n, "suite", self), 274 self.GetAttachmentStore()) 275 # Construct the actual instance. 276 extras = { extension_class.EXTRA_ID : suite_id, 277 extension_class.EXTRA_DATABASE : self } 278 return extension_class(arguments, **extras)
279 280
281 - def WriteExtension(self, id, extension):
282 283 kind = extension.kind 284 if kind in ("resource", "test"): 285 self.__StoreAttachments(extension) 286 # Figure out what path should be used to store the test. 287 path = self._GetPath(kind, id) 288 # If the file is in a new subdirectory, create it. 289 containing_directory = os.path.dirname(path) 290 if not os.path.isdir(containing_directory): 291 os.makedirs(containing_directory) 292 extension.Write(open(path, "w"))
293 294
295 - def GetAttachmentStore(self):
296 """Returns the 'AttachmentStore' associated with the database. 297 298 returns -- The 'AttachmentStore' containing the attachments 299 associated with tests and resources in this database.""" 300 301 return self.__store
302 303
304 - def _Trace(self, message,):
305 """Write a trace 'message'. 306 307 'message' -- A string to be output as a trace message.""" 308 309 tracer = qm.test.cmdline.get_qmtest().GetTracer() 310 tracer.Write(message, "xmldb")
311 312 313 ######################################################################## 314 # Local Variables: 315 # mode: python 316 # indent-tabs-mode: nil 317 # fill-column: 72 318 # End: 319