1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 import os.path
21 import qm
22 from qm.common import *
23 import qm.extension
24 import qm.fields
25 from qm.label import *
26 from qm.test.base import *
27 from qm.test.directory_suite import DirectorySuite
28 from qm.test.runnable import Runnable
29 from qm.test.resource import Resource
30 from qm.test.suite import Suite
31 from qm.test.test import Test
32
33
34
35
36
37 __the_database = None
38 """The global 'Database' object."""
39 __the_db_path = '.'
40 """The path to the database."""
41
42
43
44
45
47 """An 'ItemDescriptor' describes a test, resource, or similar entity.
48
49 Some 'Database' operations return an instance of a class derived
50 from 'ItemDescriptor', rather than the object described. For
51 example, 'Database.GetTest' returns a 'TestDescriptor', not a
52 'Test'. This additional indirection is an optimization; the
53 creation of the actual 'Test' object may be relatively expensive,
54 and in many cases all that is needed is information that can be
55 gleaned from the descriptor."""
56
57 - def __init__(self,
58 database,
59 instance_id,
60 class_name = None,
61 arguments = None,
62 item = None):
63 """Construct an 'ItemDescriptor'.
64
65 'database' -- The 'Database' object in which this entity is
66 located.
67
68 'instance_id' -- The label for this entity.
69
70 'class_name' -- The name of the extension class for the entity.
71 For example, for a 'TestDescriptor', the 'class_name' is the
72 name of the test class. Omit this argument if 'item' is provided.
73
74 'arguments' -- A dictionary mapping argument names to argument
75 values. These arguments will be provided to the extension class
76 when the entity is constructed. Omit this argument if 'item' is
77 provided.
78
79 'item' -- The item class for this item instance."""
80
81 self.__database = database
82 self.__id = instance_id
83 assert(not item or not class_name)
84 self.__class_name = class_name
85 self.__arguments = arguments
86 self._item = item
87
88
90 """Return the 'Database' containing this entity.
91
92 returns -- The 'Database' object in which this entity is
93 located."""
94
95 return self.__database
96
97
99 """Return the class name of the entity.
100
101 returns -- The name of the extension class for the entity. For
102 example, for a 'TestDescriptor', this method returns the name of
103 the test class."""
104
105 if self._item:
106 return self._item.GetClassName()
107 else:
108 return self.__class_name
109
110
112 """Return the class of the entity.
113
114 returns -- The Python class object for the entity. For example,
115 for a 'TestDescriptor', this method returns the test class."""
116
117 raise NotImplementedError
118
119
121 """Return the arguments specified by the test class.
122
123 returns -- A list of 'Field' objects containing all the
124 arguments in the class hierarchy.
125
126 Derived classes should not override this method."""
127
128 return qm.extension.get_class_arguments(self.GetClass())
129
130
132 """Return the entity arguments.
133
134 returns -- A dictionary mapping argument names to argument
135 values. These arguments will be provided to the extension class
136 when the entity is constructed."""
137
138 if self._item:
139 return self._item.GetExplicitArguments()
140 else:
141 return self.__arguments
142
143
145 """Return the label for this entity.
146
147 returns -- The label for this entity."""
148
149 return self.__id
150
151
163
164
166 """Return the resources required by this item.
167
168 returns -- A sequence of resource names. Each name indicates a
169 resource that must be available to this item."""
170
171 return self.GetArguments().get(Runnable.RESOURCE_FIELD_ID, [])
172
173
174
175 - def _Execute(self, context, result, method):
176 """Execute the entity.
177
178 'context' -- The 'Context' in which the test should be executed,
179 or 'None' if the 'method' does not take a 'Context' argument.
180
181 'result' -- The 'Result' object corresponding to this execution.
182
183 'method' -- The method name of the method on the entity that
184 should be invoked to perform the execution."""
185
186
187 item = self.GetItem()
188 methobj = getattr(item, method)
189
190 if context is not None:
191 methobj(context, result)
192 else:
193 methobj(result)
194
195
196
198 """A test instance."""
199
200 - def __init__(self,
201 database,
202 test_id,
203 test_class_name = None,
204 arguments = None,
205 test = None):
206 """Create a new test instance.
207
208 'database' -- The 'Database' containing this test.
209
210 'test_id' -- The test ID.
211
212 'test_class_name' -- The name of the test class of which this is
213 an instance. Omit this argument if 'test' is provided.
214
215 'arguments' -- This test's arguments to the test class.
216 Omit this argument if 'test' is provided.
217
218 'test' -- The test class of which this is an instance."""
219
220
221 ItemDescriptor.__init__(self, database,
222 test_id, test_class_name, arguments,
223 test)
224
225 self.__prerequisites = {}
226 for p, o in \
227 self.GetArguments().get(Test.PREREQUISITES_FIELD_ID, []):
228 self.__prerequisites[p] = o
229
230
232 """Return the class of the entity.
233
234 returns -- The Python class object for the entity. For example,
235 for a 'TestDescriptor', this method returns the test class."""
236
237 if self._item: return type(self._item)
238 return get_extension_class(self.GetClassName(), 'test',
239 self.GetDatabase())
240
241
243 """Return the 'Test' object described by this descriptor."""
244
245 return self.GetItem()
246
247
249 """Return a map from prerequisite test IDs to required outcomes."""
250
251 return self.__prerequisites
252
253
255 """Returns the pattern for the targets that can run this test.
256
257 returns -- A regular expression (represented as a string) that
258 indicates the targets on which this test can be run. If the
259 pattern matches a particular group name, the test can be run
260 on targets in that group."""
261
262 return self.GetArguments().get("target_group", ".*")
263
264
265 - def Run(self, context, result):
266 """Execute this test.
267
268 'context' -- The 'Context' in which the test should be executed.
269
270 'result' -- The 'Result' object for this test."""
271
272 self._Execute(context, result, "Run")
273
274
275
277 """A resource instance."""
278
279 - def __init__(self,
280 database,
281 resource_id,
282 resource_class_name = None,
283 arguments = None,
284 resource = None):
285 """Create a new resource instance.
286
287 'database' -- The 'Database' containing this resource.
288
289 'resource_id' -- The resource ID.
290
291 'resource_class_name' -- The name of the resource class of which
292 this is an instance. Omit this argument if 'resource' is provided.
293
294 'arguments' -- This resource's arguments to the resource class.
295 Omit this argument if 'resource' is provided.
296
297 'resource' -- The resource class of which this is an instance."""
298
299
300 ItemDescriptor.__init__(self, database, resource_id,
301 resource_class_name, arguments,
302 resource)
303
304
306 """Return the class of the entity.
307
308 returns -- The Python class object for the entity. For example,
309 for a 'TestDescriptor', this method returns the test class."""
310
311 if self._item: return type(self._item)
312 return get_extension_class(self.GetClassName(), 'resource',
313 self.GetDatabase())
314
315
317 """Return the 'Resource' object described by this descriptor."""
318
319 return self.GetItem()
320
321
322 - def SetUp(self, context, result):
323 """Set up the resource.
324
325 'context' -- The 'Context' in which the resource should be executed.
326
327 'result' -- The 'Result' object for this resource."""
328
329 self._Execute(context, result, "SetUp")
330
331
333 """Clean up the resource.
334
335 'result' -- The 'Result' object for this resource."""
336
337 self._Execute(None, result, "CleanUp")
338
339
340
342 """An exception relating to a 'Database'.
343
344 All exceptions raised directly by 'Database', or its derived
345 classes, will be instances of 'DatabaseError', or a class derived
346 from 'DatabaseError'.
347
348 If QMTest catches the exception, it will treat the string
349 representation of the exception as an error message to be formatted
350 for the user."""
351
352
354 """An exception indicating that a particular item could not be found."""
355
357
358 self.kind = kind
359 self.item_id = item_id
360
361
363 """Return a string describing this exception."""
364
365 return qm.message("no such item",
366 kind = self.kind,
367 item_id = self.item_id)
368
369
370
372 """The specified test does not exist."""
373
380
381
382
384 """The specified suite does not exist."""
385
392
393
394
396 """The specified resource does not exist."""
397
404
405
406
408 """A 'Database' stores tests, testsuites, and resources.
409
410 A 'Database' has two primary functions:
411
412 1. Test storage and retrieval.
413
414 Every test has a unique name, called a "test id". When a new
415 test is created, the 'Database' is responsible for writing that
416 test to permanent storage. Later, QMTest will request the test
417 by providing the database with the test id. The database must
418 retrieve the test from permanent storage.
419
420 QMTest does not put any restrictions on *how* tests are stored.
421 The default database implementation uses XML to store tests,
422 but any storage format will do.
423
424 2. Test enumeration.
425
426 The 'Database' can tell QMTest what tests are stored in the
427 database. QMTest uses this information in its graphical user
428 interface to show the user what tests exist.
429
430 A 'Database' stores testsuites and resources in addition to tests.
431 The names for tests, testsuites, and resources are all "labels". A
432 label is a special kind of string that is designed to be easily
433 convertible to a file name. For more information, see the
434 'qm.label' module. The namespaces for tests, testsuites, and
435 resources are all distinct. For example, it is OK to have a test
436 with the same name as a testsuite.
437
438 Every 'Database' is associated with a particular directory on the
439 local machine. In most cases, the 'Database' will store all the
440 files it needs within this directory.
441
442 Every 'Database' has an associated 'AttachmentStore'. An
443 'AttachmentStore' is responsible for storing the attachments
444 associated with tests. See the module 'qm.attachment' for more
445 information about 'AttachmentStore'.
446
447 'Database' is an abstract class.
448
449 You can extend QMTest by providing your own database implementation.
450 One reason to do this is that you may want to store tests in a
451 format different from the XML format that QMTest uses by default.
452 For example, if you are testing a compiler, you might want to
453 represent each test as a source file. Or, if you are testing a
454 SQL database, you might want to represent each test as two files:
455 one containing SQL commands to run the test, and one containing
456 the output you expect.
457
458 Another reason to provide your own database implementation is that
459 you might want to store tests on a remote location. For example,
460 suppose you wanted to allow multiple users to access the same
461 central test database. You could create a test database that
462 created and retrieved tests by communicating with the central
463 server.
464
465 To create your own database implementation, you must create a Python
466 class derived (directly or indirectly) from 'Database'. The
467 documentation for each method of 'Database' indicates whether you
468 must override it in your database implementation. Some methods may
469 be overridden, but do not need to be. You might want to override
470 such a method to provide a more efficient implementation, but QMTest
471 will work fine if you just use the default version.
472
473 If QMTest calls a method on a database and that method raises an
474 exception that is not caught within the method itself, QMTest will
475 catch the exception and continue processing. Therefore, methods
476 here only have to handle exceptions themselves if that is necessary
477 to maintain the integrity of the database.
478
479 A single 'Database' may be accessed by multiple threads
480 simultaneously. Therefore, you must take appropriate steps to
481 ensure thread-safe access to shared data."""
482
483 arguments = [
484 qm.fields.TextField(
485 name = "label_class",
486 title = "Label Class",
487 description = """The name of the label class used by this database.
488
489 The label class is used to separate names of entities used
490 by the database into directories and basenames.""",
491 default_value = "python_label.PythonLabel"
492 ),
493 qm.fields.BooleanField(
494 name = "modifiable",
495 title = "Modifiable?",
496 description = """Whether or not the database can be modified.
497
498 If true, changes (such as the addition or removal of tests,
499 resources, or suites) can be made to the test database.
500 If false, tests can be viewed and run, but not modified.""",
501 default_value = "true")
502 ]
503
504 RESOURCE = "resource"
505 SUITE = "suite"
506 TEST = "test"
507
508 ITEM_KINDS = [RESOURCE, SUITE, TEST]
509 """The kinds of items that can be stored in a 'Database'."""
510
511 _item_exceptions = {
512 RESOURCE : NoSuchResourceError,
513 SUITE : NoSuchSuiteError,
514 TEST : NoSuchTestError
515 }
516 """The exceptions to be raised when a particular item cannot be found.
517
518 This map is indexed by the 'ITEM_KINDS'; the value indicates the
519 exception class to be used when the indicated kind cannot be found."""
520
521 _is_generic_database = False
522 """True if this database implements 'GetExtension' as a primitive.
523
524 Databases should implement 'GetExtension' and then override
525 '_is_generic_database', setting it to 'True'. However, legacy
526 databases implemented 'GetTest', 'GetResource', and 'GetSuite' as
527 primivites. These legacy databases should not override
528 '_generic_database'."""
529
530 kind = "database"
531 """The 'Extension' kind."""
532
534 """Construct a 'Database'.
535
536 'path' -- A string containing the absolute path to the directory
537 containing the database.
538
539 'arguments' -- A dictionary mapping attribute names to values.
540 The use of this parameter is deprecated. Use keyword arguments instead.
541
542 Derived classes must call this method from their own '__init__'
543 methods. Every derived class must have an '__init__' method that
544 takes the path to the directory containing the database as its
545 only argument. The path provided to the derived class '__init__'
546 function will always be an absolute path."""
547
548 if arguments: args.update(arguments)
549 qm.extension.Extension.__init__(self, **args)
550
551
552 assert os.path.isabs(path)
553 self.__path = path
554
555
556 self.__label_class \
557 = get_extension_class(self.label_class, "label", self)
558
559
560
562 """Return true if 'label' is valid.
563
564 'label' -- A string that is being considered as a label.
565
566 'is_component' -- True if the string being tested is just a
567 single component of a label path.
568
569 returns -- True if 'label' is a valid name for entities in this
570 database."""
571
572 return self.__label_class("").IsValid(label, is_component)
573
574
576 """Join the 'labels' together.
577
578 'labels' -- A sequence of strings corresponding to label
579 components.
580
581 returns -- A string containing the complete label."""
582
583 if not labels:
584 return ""
585
586 return str(apply(self.__label_class(labels[0]).Join,
587 labels[1:]))
588
589
591 """Split the label into a pair '(directory, basename)'.
592
593 returns -- A pair of strings '(directory, basename)'."""
594
595 return map(str, self.__label_class(label).Split())
596
597
599 """Split the label into a pair '(parent, subpath)'.
600 This is the same operation as SplitLabel, except the split
601 occurs at the leftmost separator, not the rightmost, and a
602 single-component label comes back in the parent slot.
603
604 returns -- A pair of strings '(parent, subpath)'."""
605
606 return map(str, self.__label_class(label).SplitLeft())
607
608
610 """Return all of the component directories of 'label'.
611
612 'label' -- A string naming an entity in the database.
613
614 returns -- A list of strings. The first string is the first
615 directory in 'label'; the last string is the basename."""
616
617 components = []
618 while label:
619 dirname, label = self.SplitLabelLeft(label)
620 if dirname:
621 components.append(dirname)
622 else:
623 components.append(label)
624 break
625
626 return components
627
628
629
630
631 - def GetExtension(self, id):
632 """Return the extension object named 'id'.
633
634 'id' -- The label for the extension.
635
636 returns -- The instance of 'Extension' with the indicated name,
637 or 'None' if there is no such entity.
638
639 Database classes should override this method. For backwards
640 compatibility, this base class implements this generic method
641 in terms of the special-purpose methods 'GetTest()' and 'GetResource()'.
642 Only if _is_generic_database is True are these implemented in terms
643 of 'GetExtension()'."""
644
645 for kind in (Database.TEST, Database.RESOURCE):
646 try:
647 return self.GetItem(kind, id).GetItem()
648 except NoSuchItemError:
649 pass
650
651 try:
652 return self.GetSuite(id)
653 except NoSuchSuiteError:
654 pass
655
656 return None
657
658
659 - def GetExtensions(self, directory, scan_subdirs):
660 """Return the extensions in 'directory'.
661
662 'directory' -- The name of a directory.
663
664 'scan_subdirs' -- True if (and only if) subdirectories of
665 'directory' should be scanned.
666
667 returns -- A dictionary mapping labels to 'Extension'
668 instances. The dictionary contains all extensions in
669 'directory', and, if 'scan_subdirs' is true, its
670 subdirectories."""
671
672 extensions = {}
673
674 for kind in self.ITEM_KINDS:
675 ids = self.GetIds(kind, directory, scan_subdirs)
676 for id in ids:
677 extensions[id] = self.GetExtension(id)
678
679 return extensions
680
681
683 """Remove the extension 'id' from the database.
684
685 'id' -- A label for the 'Extension' instance stored in the
686 database.
687
688 'kind' -- The kind of 'Extension' stored with the given 'id'."""
689
690 raise NotImplementedError
691
692
694 """Store 'extension' in the database, using the name 'id'.
695
696 'id' -- A label for the 'extension'.
697
698 'extension' -- An instance of 'Extension'.
699
700 The 'extension' is stored in the database. If there is a
701 previous item in the database with the same id, it is removed
702 and replaced with 'extension'. Some databases may not be able
703 to store all 'Extension' instances; those database must throw an
704 exception when an attempt is made to store such an
705 'extension'."""
706
707 raise NotImplementedError
708
709
710
711
713 """Return the 'TestDescriptor' for the test named 'test_id'.
714
715 'test_id' -- A label naming the test.
716
717 returns -- A 'TestDescriptor' corresponding to 'test_id'.
718
719 raises -- 'NoSuchTestError' if there is no test in the database
720 named 'test_id'."""
721
722 if self._is_generic_database:
723 test = self.GetExtension(test_id)
724 if isinstance(test, Test):
725 return TestDescriptor(self, test_id, test=test)
726
727 raise NoSuchTestError(test_id)
728
729
731 """Check whether or not the database has a test named 'test_id'.
732
733 'test_id' -- A label naming the test.
734
735 returns -- True if and only if the database contains a test
736 named 'test_id'. If this function returns true, 'GetTest' will
737 usually succeed. However, they may be circumstances where
738 'HasTest' returns true and 'GetTest' does not succeed. For
739 example, someone might remove a critical file from the database
740 between the time that 'HasTest' is called and the time that
741 'GetTest' is called.
742
743 Derived classes may override this method."""
744
745 try:
746 self.GetTest(test_id)
747 except NoSuchTestError:
748 return 0
749 else:
750 return 1
751
752
753 - def GetTestIds(self, directory="", scan_subdirs=1):
754 """Return all test IDs that begin with 'directory'.
755
756 'directory' -- A label indicating the directory in which to
757 begin the search.
758
759 'scan_subdirs' -- True if (and only if) subdirectories of
760 'directory' should be scanned.
761
762 'returns' -- A list of all tests located within 'directory',
763 as absolute labels."""
764
765 return self.GetIds(self.TEST, directory, scan_subdirs)
766
767
768
770 """Return the 'Suite' for the suite named 'suite_id'.
771
772 'suite_id' -- A label naming the suite.
773
774 returns -- An instance of 'Suite' (or a derived class of
775 'Suite') corresponding to 'suite_id'.
776
777 raises -- 'NoSuchSuiteError' if there is no test in the database
778 named 'test_id'.
779
780 All databases must have an implicit suite called '' that
781 contains all tests in the database. More generally, for each
782 directory in the database, there must be a corresponding suite
783 that contains all tests in that directory and its
784 subdirectories."""
785
786 if suite_id == "":
787 return DirectorySuite(self, "")
788
789 if self._is_generic_database:
790 suite = self.GetExtension(suite_id)
791 if isinstance(suite, Suite):
792 return suite
793
794 raise NoSuchSuiteError(suite_id)
795
796
798 """Check whether or not the database has a suite named 'suite_id'.
799
800 'suite_id' -- A label naming the suite.
801
802 returns -- True if and only if the database contains a suite
803 named 'suite_id'. If this function returns true, 'GetSuite'
804 will usually succeed. However, they may be circumstances where
805 'HasSuite' returns true and 'GetSuite' does not succeed. For
806 example, someone might remove a critical file from the database
807 between the time that 'HasSuite' is called and the time that
808 'GetSuite' is called.
809
810 All databases must have an implicit suite called "" that
811 contains all tests in the database. More generally, for each
812 directory in the database, there must be a corresponding suite
813 that contains all tests in that directory and its
814 subdirectories.
815
816 Derived classes may override this method."""
817
818 try:
819 self.GetSuite(suite_id)
820 except NoSuchSuiteError:
821 return 0
822 else:
823 return 1
824
825
827 """Return all suite IDs that begin with 'directory'.
828
829 'directory' -- A label indicating the directory in which to
830 begin the search.
831
832 'scan_subdirs' -- True if (and only if) subdirectories of
833 'directory' should be scanned.
834
835 'returns' -- A list of all suites located within 'directory',
836 as absolute labels."""
837
838 return self.GetIds(self.SUITE, directory, scan_subdirs)
839
840
841
842
844 """Return the 'ResourceDescriptor' for the resource 'resouce_id'.
845
846 'resource_id' -- A label naming the resource.
847
848 returns -- A 'ResourceDescriptor' corresponding to 'resource_id'.
849
850 raises -- 'NoSuchResourceError' if there is no resource in the
851 database named 'resource_id'."""
852
853 if self._is_generic_database:
854 resource = self.GetExtension(resource_id)
855 if isinstance(resource, Resource):
856 return ResourceDescriptor(self, resource_id, resource = resource)
857
858 raise NoSuchResourceError(resource_id)
859
860
862 """Check whether or not the database has a resource named
863 'resource_id'.
864
865 'resource_id' -- A label naming the resource.
866
867 returns -- True if and only if the database contains a resource
868 named 'resource_id'. If this function returns true,
869 'GetResource' will usually succeed. However, they may be
870 circumstances where 'HasResource' returns true and 'GetResource'
871 does not succeed. For example, someone might remove a critical
872 file from the database between the time that 'HasResource' is
873 called and the time that 'GetResource' is called.
874
875 Derived classes may override this method."""
876
877 try:
878 self.GetResource(resource_id)
879 except NoSuchResourceError:
880 return 0
881 else:
882 return 1
883
884
886 """Return all resource IDs that begin with 'directory'.
887
888 'directory' -- A label indicating the directory in which to
889 begin the search.
890
891 'scan_subdirs' -- True if (and only if) subdirectories of
892 'directory' should be scanned.
893
894 'returns' -- A list of all resources located within 'directory',
895 as absolute labels."""
896
897 return self.GetIds(self.RESOURCE, directory, scan_subdirs)
898
899
900
901 - def GetIds(self, kind, directory = "", scan_subdirs = 1):
902 """Return all IDs of the indicated 'kind' that begin with 'directory'.
903
904 'kind' -- One of the 'ITEM_KINDS'.
905
906 'directory' -- A label indicating the directory in which to
907 begin the search.
908
909 'scan_subdirs' -- True if (and only if) subdirectories of
910 'directory' should be scanned.
911
912 returns -- A list of all items of the indicated 'kind' located
913 within 'directory', as absolute labels.
914
915 Derived classes may override this method."""
916
917 if self._is_generic_database:
918 extensions = self.GetExtensions(directory, scan_subdirs)
919 extensions = filter(lambda e: e.kind == kind,
920 extensions.values())
921 return map(lambda e: e.GetId(), extensions)
922
923 return []
924
925
927 """Return the item of the indicated 'kind' with indicated 'item_id'.
928
929 'kind' -- One of the 'ITEM_KINDS'.
930
931 'item_id' -- The name of the item.
932
933 returns -- If 'kind' is 'Database.TEST' or 'Database.RESOURCE',
934 returns a test descriptor or resource descriptor, respectively.
935 If 'kind' is 'Database.SUITE', returns a 'Suite'.
936
937 Derived classes may override this method."""
938
939 return { Database.TEST : self.GetTest,
940 Database.RESOURCE : self.GetResource,
941 Database.SUITE : self.GetSuite } [kind] (item_id)
942
943
944
946 """Return the immediate subdirectories of 'directory'.
947
948 'directory' -- A label indicating a directory in the database.
949
950 returns -- A sequence of (relative) labels indictating the
951 immediate subdirectories of 'directory'. For example, if "a.b"
952 and "a.c" are directories in the database, this method will
953 return "b" and "c" given "a" as 'directory'.
954
955 Derived classes may override this method."""
956
957 return []
958
959
961 """Return the directory containing the database.
962
963 returns -- A string containing the absolute path to the
964 directory containing the database.
965
966 Derived classes must not override this method."""
967
968 return self.__path
969
970
972 """Return the directory containing configuration information.
973
974 returns -- The directory containing configuration information
975 for the database.
976
977 Derived classes must not override this method."""
978
979 return get_configuration_directory(self.GetPath())
980
981
983 """Returns the 'AttachmentStore' associated with the database.
984
985 returns -- The 'AttachmentStore' containing the attachments
986 associated with tests and resources in this database.
987
988 Derived classes may override this method."""
989
990 return None
991
992
994 """Return directories to search for test and resource classes.
995
996 returns -- A sequence of strings. Each string is a directory
997 that should be searched to locate test and resource classes.
998 The directories will be searched in the order they appear.
999 QMTest will search other directories (like those in the
1000 'QMTEST_CLASS_PATH' environment variable) in addition to these
1001 directories.
1002
1003 For a given database, this method should always return the same
1004 value; callers are permitted to cache the value returned.
1005
1006 Derived classes may override this method. The sequence returned
1007 by the derived class need not be a superset of the value
1008 returned by the default implementation (but probably should
1009 be)."""
1010
1011 return []
1012
1013
1015 """Return the kinds of tests that the database can store.
1016
1017 returns -- A sequence of strings. Each string names a
1018 class, including the containing module. Only classes
1019 of these types can be stored in the database.
1020
1021 Derived classes may override this method. The default
1022 implementation allows all available test classes, but the
1023 derived class may allow only a subset."""
1024
1025 return get_extension_class_names('test', self)
1026
1027
1029 """Return the kinds of resources that the database can store.
1030
1031 returns -- A sequence of strings. Each string names a
1032 class, including the containing module. Only resources
1033 of these types can be stored in the database.
1034
1035 Derived classes may override this method. The default
1036 implementation allows all available resource classes, but the
1037 derived class may allow only a subset."""
1038
1039 return get_extension_class_names('resource', self)
1040
1041
1043 """Expand test and suite IDs into test IDs.
1044
1045 'ids' -- A sequence of IDs of tests and suites, which may be mixed
1046 together.
1047
1048 returns -- A pair 'test_ids, suite_ids'. 'test_ids' is a
1049 sequence of test IDs including all test IDs mentioned in 'ids' plus
1050 all test IDs obtained from recursively expanding suites included in
1051 'ids'. 'suite_ids' is the set of IDs of suites included directly
1052 and indirectly in 'ids'.
1053
1054 raises -- 'ValueError' if an element in 'id' is neither a test or
1055 suite ID. The exception argument is the erroneous element."""
1056
1057
1058
1059 test_ids = {}
1060 suite_ids = {}
1061
1062 for id in ids:
1063
1064 if suite_ids.has_key(id) or test_ids.has_key(id):
1065 continue
1066
1067 if self.HasSuite(id):
1068 suite_ids[id] = None
1069
1070 suite = self.GetSuite(id)
1071
1072
1073 suite_test_ids, sub_suite_ids = suite.GetAllTestAndSuiteIds()
1074
1075 for test_id in suite_test_ids:
1076 test_ids[test_id] = None
1077 for suite_id in sub_suite_ids:
1078 suite_ids[suite_id] = None
1079
1080 elif self.HasTest(id):
1081
1082 test_ids[id] = None
1083 else:
1084
1085 raise ValueError, id
1086
1087
1088 return test_ids.keys(), suite_ids.keys()
1089
1090
1092 """Returns true iff this database is modifiable.
1093
1094 returns -- True iff this database is modifiable. If the
1095 database is modifiable, it supports operatings like 'Write'
1096 that make changes to the structure of the databaes itself.
1097 Otherwise, the contents of the database may be viewed, but not
1098 modified."""
1099
1100 return self.modifiable == "true"
1101
1102
1103
1104
1105
1107 """Return the configuration directory for the 'Database' rooted at 'path'.
1108
1109 'path' -- The path to the test database.
1110
1111 returns -- The path to the configuration directory."""
1112
1113 return os.path.join(path, "QMTest")
1114
1115
1117 """Return the configuration file for the 'Database' rooted at 'path'.
1118
1119 'path' -- The path to the test database.
1120
1121 returns -- The path to the configuration file."""
1122
1123 return os.path.join(get_configuration_directory(path),
1124 "configuration")
1125
1126
1128 """Returns true if 'db_path' looks like a test database."""
1129
1130
1131 if not os.path.isdir(db_path):
1132 return 0
1133
1134 if not os.path.isfile(get_configuration_file(db_path)):
1135 return 0
1136
1137 return 1
1138
1139
1141 """Load the database from 'db_path'.
1142
1143 'db_path' -- The path to the directory containing the database.
1144
1145 returns -- The new 'Database'."""
1146
1147
1148 if not is_database(db_path):
1149 raise QMException, \
1150 qm.error("not test database", path=db_path)
1151
1152
1153 config_path = get_configuration_file(db_path)
1154 document = qm.xmlutil.load_xml_file(config_path)
1155
1156
1157 database_class, arguments \
1158 = (qm.extension.parse_dom_element
1159 (document.documentElement,
1160 lambda n: qm.test.base.get_extension_class(n, "database",
1161 None, db_path)))
1162
1163
1164 for node in document.documentElement.getElementsByTagName("attribute"):
1165 name = node.getAttribute("name")
1166
1167
1168 value = qm.xmlutil.get_dom_text(node)
1169
1170
1171 arguments[str(name)] = value
1172
1173 return database_class(db_path, arguments)
1174
1175
1176
1177
1178
1179
1181 """Set the database path to be used when the database is loaded.
1182
1183 'path' -- A string containing the path to the database."""
1184
1185 global __the_db_path
1186
1187 __the_db_path = path
1188
1189
1202
1203
1204
1205
1206
1207
1208
1209
1210