1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 import os
21 import qm
22 import qm.attachment
23 import qm.common
24 from qm.extension import *
25 import qm.fields
26 import qm.label
27 import qm.test.base
28 import qm.test.cmdline
29 from qm.test.context import *
30 from qm.test.database import *
31 from qm.test.classes.previous_testrun import PreviousTestRun
32 from qm.test.execution_thread import *
33 from qm.test.result import *
34 from qm.test.result_stream import *
35 from qm.test.suite import *
36 import qm.web
37 import string
38 import StringIO
39 import sys
40 import time
41
42
43
44
45
47 """An 'Item' provides a convenient way to pass around named values.
48 It is iterated over in listings generated by various dtml templates."""
49
51 """Construct a new 'Item'.
52
53 'id' -- The identifier.
54
55 'kwds' -- A dictionary of named values to store."""
56
57 self.id = id
58 self.__dict__.update(kwds)
59
60
61
62 -class DefaultDtmlPage(qm.web.DtmlPage):
63 """Subclass of DTML page class for QMTest pages."""
64
65 html_generator = "QMTest"
66
67 NEGATIVE_UNEXPECTED = Result.FAIL
68 """A test's result was unfavorably unexpected."""
69
70 POSITIVE_UNEXPECTED = Result.PASS
71 """A test's result was favorably unexpected."""
72
73 EXPECTED = "EXPECTED"
74 """A test's result was as expected."""
75
76 EXPECTATION_KINDS \
77 = [ NEGATIVE_UNEXPECTED, EXPECTED, POSITIVE_UNEXPECTED ]
78 """The kinds of expectations."""
79
80 outcomes = Result.outcomes + [EXPECTED]
81
82 - def __init__(self, dtml_template, server, **attributes):
83 """Construct a new 'QMTestPage'.
84
85 'server' -- The 'QMTestServer' creating this page.
86
87 'dtml_template' -- The file name of the DTML template, relative
88 to the DTML directory."""
89
90 self.server = server
91
92
93 if not server.GetRunDatabase():
94 if server.GetDatabase().IsModifiable():
95 self.file_menu_items = [
96 ('New Test', "new-test"),
97 ('New Suite', "new-suite"),
98 ('New Resource', "new-resource"),
99 ]
100 else:
101 self.file_menu_items = [
102 ('New Test', ""),
103 ('New Suite', ""),
104 ('New Resource', ""),
105 ]
106 self.file_menu_items.extend([
107 ('Load Results', "javascript:load_results();"),
108 ('Save Results', qm.test.cmdline.QMTest.results_file_name),
109 ('Load Expectations', "javascript:load_expected_results();")])
110 if self.HasModifiableExpectations():
111 self.file_menu_items.extend([
112 ('Save Expectations',
113 qm.test.cmdline.QMTest.expectations_file_name)])
114 else:
115 self.file_menu_items.extend([('Save Expectations', "")])
116
117 self.file_menu_items.extend([
118 ('Load Context', "javascript:load_context();"),
119 ('Save Context', qm.test.cmdline.QMTest.context_file_name),
120 ('Exit', 'shutdown')
121 ])
122 self.edit_menu_items = [
123 ('Clear Results', "clear-results"),
124 ('Edit Context', "edit-context"),
125 ]
126 self.run_menu_items = [
127 ('All Tests', "run-tests")
128 ]
129 self.view_menu_items = [
130 ('Directory', "dir"),
131 ('Results', "show-results"),
132 ('Report', "")
133 ]
134 else:
135
136 self.file_menu_items = [
137 ('New Test', ""),
138 ('New Suite', ""),
139 ('New Resource', ""),
140 ('Load Results', ""),
141 ('Save Results', ""),
142 ('Load Expectations', ""),
143 ('Save Expectations', ""),
144 ('Load Context', ""),
145 ('Save Context', ""),
146 ('Exit', 'shutdown')
147 ]
148 self.edit_menu_items = [
149 ('Clear Results', ""),
150 ('Edit Context', ""),
151 ]
152 self.run_menu_items = [
153 ('All Tests', "")
154 ]
155 self.view_menu_items = [
156 ('Directory', "/test/dir"),
157 ('Results', ""),
158 ('Report', "/report/dir")
159 ]
160
161 self.help_menu_items = [
162 ('Tutorial', "javascript:popup_tutorial();"),
163 ('QMTest Web Site', "http://www.qmtest.com")
164 ]
165
166 qm.web.DtmlPage.__init__(self, dtml_template, **attributes)
167
168
170 """Return the name of the application."""
171
172 return self.html_generator
173
174
175 - def MakeListingUrl(self):
176 return qm.web.WebRequest("dir", base=self.request).AsUrl()
177
178
179 - def GetMainPageUrl(self):
180 return self.MakeListingUrl()
181
182
183 - def GetDatabase(self):
184 """Returns the 'Database' in use.
185
186 returns -- The 'Database' in use."""
187
188 return self.server.GetDatabase()
189
190
191 - def IsLabelInDirectory(self, id, directory):
192 """Returns true if 'id' is in 'directory'.
193
194 returns -- True if 'id' indicates a test contained in
195 'directory', or one of its subdirectories."""
196
197 while len(id) >= len(directory):
198 if id == directory:
199 return 1
200 id = self.GetDatabase().SplitLabel(id)[0]
201
202 return 0
203
204
205 - def FormatId(self, id, type, style="basic"):
206 """Format 'id' as HTML.
207
208 'id' -- The name of a test or resource.
209
210 'type' -- The kind of item named by 'id'. Either 'resource',
211 'suite', or 'test'.
212
213 'style' -- The formatting style to use. One of 'plain',
214 'basic', 'navigation', or 'tree'.
215
216 returns -- A string containing HTML to use for 'id'."""
217
218 script = "show-" + type
219 request = qm.web.WebRequest(script, self.request, True, id=id)
220 url = request.AsUrl()
221 parent_suite_id, name = self.GetDatabase().SplitLabel(id)
222
223 if style == "plain":
224 return '<span class="id">%s</span>' % id
225
226 elif style == "basic":
227 return '<a href="%s"><span class="id">%s</span></a>' % (url, id)
228
229 elif style == "navigation":
230 if parent_suite_id == "":
231 parent = ""
232 else:
233 parent = self.FormatId(parent_suite_id, "dir", style)
234 parent += id[len(parent_suite_id)]
235 return parent \
236 + '<a href="%s"><span class="id">%s</span></a>' \
237 % (url, name)
238
239 elif style == "tree":
240 return '<a href="%s"><span class="id">%s</span></a>' \
241 % (url, name)
242
243 assert None
244
245
246 - def GetResultsByOutcome(self, results):
247 """Compute the tests in 'results' with each outcome.
248
249 'results' -- A sequence of 'Result' instances.
250
251 returns -- A dictionary mapping outcomes to the sequence of
252 tests that have the indicated outcome in 'results'."""
253
254 results_by_outcome = {}
255
256 for o in self.outcomes:
257 results_by_outcome[o] = []
258
259
260
261 for r in results:
262 results_by_outcome[r.GetOutcome()].append(r)
263
264 return results_by_outcome
265
266
267 - def GetOutcomePercentages(self, results):
268 """Compute the percentage (by outcome) of the 'results'.
269
270 'results' -- A sequence of 'Result' instances.
271
272 returns -- A dictionary mapping outcomes to the percentage (as
273 a floating point number) of tests in 'results' that have
274 that outcome."""
275
276
277
278 total = len(results)
279
280
281 results = self.GetResultsByOutcome(results)
282
283
284 percentages = {}
285 for o in self.outcomes:
286 if total:
287 percentages[o] = float(len(results[o])) / float(total)
288 else:
289 percentages[o] = 0.0
290
291 return percentages
292
294 """Return True if expectations are modifiable."""
295
296 return type(self.server.GetExpectationDatabase()) in (PreviousTestRun,)
297
298
299 -class QMTestPage(DefaultDtmlPage):
300 """A 'QMTestPage' is a 'DtmlPage' for pages generated by QMTest.
301
302 A 'QMTestPage' automatically looks for DTML templates in the
303 directory that contains QMTest DTML templates."""
304
305 - def __init__(self, dtml_template, server):
306 """Construct a new 'QMTestPage'.
307
308 'dtml_template' -- The file name of the DTML template, relative
309 to the directory that contains QMTest DTML templates. (Usually,
310 this is just a basename.)
311
312 'server' -- The 'QMTestServer' creating this page."""
313
314
315 DefaultDtmlPage.__init__(self,
316 os.path.join("test", dtml_template),
317 server)
318
319
320 self.qmtest = qm.test.cmdline.get_qmtest()
321
322
323 - def GenerateStartBody(self, decorations=1):
324 if decorations:
325
326
327 if not self.server.GetResultsStream().IsFinished():
328
329 edit_menu_items = self.edit_menu_items[0:2]
330
331 run_menu_items = [
332 ('Stop Tests', "stop-tests")
333 ]
334
335 else:
336 edit_menu_items = self.edit_menu_items
337 run_menu_items = self.run_menu_items
338
339
340 click_menus = 0
341 if qm.common.rc.has_option("common", "click_menus"):
342 try:
343 click_menus = qm.common.rc.getboolean("common",
344 "click_menus")
345 except ValueError:
346 pass
347
348
349 navigation_bar = \
350 DefaultDtmlPage(os.path.join("test", "navigation-bar.dtml"),
351 self.server,
352 file_menu_items=self.file_menu_items,
353 edit_menu_items=edit_menu_items,
354 view_menu_items=self.view_menu_items,
355 run_menu_items=run_menu_items,
356 help_menu_items=self.help_menu_items,
357 click_menus = click_menus)
358 return "<body>%s<br />" % navigation_bar(self.request)
359 else:
360 return "<body>"
361
362
363
364 - def IsFinished(self):
365 """Return true iff no more results are forthcoming.
366
367 returns -- True if no more tests are running."""
368
369 return 1
370
371
372 - def GetRefreshDelay(self):
373 """Returns the number of seconds to wait before refreshing the page.
374
375 returns -- The number of seconds to wait before refreshing this
376 page. A value of zero means that te page should never be
377 refreshed. This function is only called if 'IsFinished' returns
378 true."""
379
380 return 0
381
382
384 """Return the header for an HTML document.
385
386 'description' -- A string describing this page.
387
388 'headers' -- Any additional HTML headers to place in the
389 '<head>' section of the HTML document."""
390
391
392
393 if not self.IsFinished():
394 headers = (headers
395 + ('<meta http-equiv="refresh" content="%d" />'
396 % self.GetRefreshDelay()))
397
398 return DefaultDtmlPage.GenerateHtmlHeader(self, description,
399 headers)
400
401
402 - def GetExpectationUrl(self, id, expectation):
403 """Return the URL for setting the expectation associated with 'id'.
404
405 'id' -- The name of a test.
406
407 'expectation' -- The current expectation associated with the
408 test, or 'None' if there is no associated expectation."""
409
410 return qm.web.WebRequest("set-expectation",
411 base=self.request,
412 id=id,
413 expectation=expectation or "None",
414 url=self.request.AsUrl()).AsUrl()
415
416
417
418
419 -class QMTestReportPage(DefaultDtmlPage):
420 """A 'QMTestReportPage' is a 'DtmlPage' for pages generated by QMTest.
421
422 A 'QMTestReportPage' automatically looks for DTML templates in the
423 directory that contains QMTest DTML templates."""
424
425 - def __init__(self, dtml_template, server):
426 """Construct a new 'QMTestReportPage'.
427
428 'dtml_template' -- The file name of the DTML template, relative
429 to the directory that contains QMTest DTML templates. (Usually,
430 this is just a basename.)
431
432 'server' -- The 'QMTestServer' creating this page."""
433
434
435 DefaultDtmlPage.__init__(self,
436 os.path.join("report", dtml_template),
437 server)
438
439 self.qmtest = qm.test.cmdline.get_qmtest()
440
441
442 - def GetRunDatabase(self):
443 """Returns the 'RunDatabase' in use.
444
445 returns -- The 'RunDatabase' in use."""
446
447 return self.server.GetRunDatabase()
448
449
450 - def GenerateStartBody(self, decorations=1):
451 if decorations:
452 edit_menu_items = self.edit_menu_items
453 run_menu_items = self.run_menu_items
454
455
456 click_menus = 0
457 if qm.common.rc.has_option("common", "click_menus"):
458 try:
459 click_menus = qm.common.rc.getboolean("common",
460 "click_menus")
461 except ValueError:
462 pass
463
464
465 navigation_bar = \
466 DefaultDtmlPage(os.path.join("test", "navigation-bar.dtml"),
467 self.server,
468 file_menu_items=self.file_menu_items,
469 edit_menu_items=edit_menu_items,
470 view_menu_items=self.view_menu_items,
471 run_menu_items=run_menu_items,
472 help_menu_items=self.help_menu_items,
473 click_menus = click_menus)
474 return "<body>%s<br />" % navigation_bar(self.request)
475 else:
476 return "<body>"
477
478
479
481 """Return the header for an HTML document.
482
483 'description' -- A string describing this page.
484
485 'headers' -- Any additional HTML headers to place in the
486 '<head>' section of the HTML document."""
487
488 return DefaultDtmlPage.GenerateHtmlHeader(self, description,
489 headers)
490
491
492 - def GetResultURL(self, id, kind):
493 """Generate a URL for the result page for 'id'.
494
495 'id' -- The name of a test or resource.
496
497 'kind' -- either 'test' or 'resource'.
498
499 returns -- A url string for the result page for 'id'."""
500
501 row = self.request.get('row', 'qmtest.run.uname')
502 script = {"test": "show-test",
503 "resource": "show-resource"}[kind]
504 request = qm.web.WebRequest(script, self.request, True,
505 id=id,
506 row=row)
507 return request.AsUrl()
508
509
513
514
515
516 -class ContextPage(QMTestPage):
517 """DTML page for setting the context."""
518
519 - def __init__(self, server):
520 """Construct a new 'ContextPage'.
521
522 'server' -- The 'QMTestServer' creating this page."""
523
524 QMTestPage.__init__(self, "context.dtml", server)
525
526 self.context = server.GetContext()
527
528
529
530 -class DirPage(QMTestPage):
531 """A test database directory page.
532
533 These attributes are available in DTML:
534
535 'path' -- The label directory that is being displayed.
536
537 'subdirs' -- A sequence of labels giving the subdirectories of
538 this directory.
539
540 'test_ids' -- A sequence of labels giving the tests in this
541 directory.
542
543 'suite_ids' -- A sequence of labels giving the suites in this
544 directory.
545
546 'resource_ids' -- A sequence of labels giving the resources in
547 this directory."""
548
549 SORT_NAME = 'name'
550 """Sort by name."""
551
552 SORT_OUTCOME = 'outcome'
553 """Sort by outcome."""
554
555 SORT_EXPECTATION = 'expectation'
556 """Sort by expectation. In other words, put unexpected outcomes
557 before expected outcomes."""
558
559 SORT_KINDS = [ SORT_NAME, SORT_OUTCOME, SORT_EXPECTATION ]
560 """The kinds of sorting available."""
561
562 - def __init__(self, server, path):
563 """Construct a 'DirPage'.
564
565 'server' -- The 'QMTestServer' creating this page.
566
567 'path' -- The label directory to display."""
568
569
570 QMTestPage.__init__(self, "dir.dtml", server)
571
572 self.path = path
573 self.database = server.GetDatabase()
574 self.subdir_ids = self.database.GetSubdirectories(path)
575 self.subdir_ids = map(lambda l: self.database.JoinLabels(path, l),
576 self.subdir_ids)
577 self.test_ids = self.database.GetTestIds(path, scan_subdirs=0)
578 self.suite_ids = self.database.GetSuiteIds(path, scan_subdirs=0)
579
580
581
582 self.suite_ids = filter(lambda s, d=self.database: \
583 not d.GetSuite(s).IsImplicit(),
584 self.suite_ids)
585 self.resource_ids = self.database.GetResourceIds(path, scan_subdirs=0)
586
587
588 results_stream = server.GetResultsStream()
589
590
591
592
593
594 self.__is_finished = results_stream.IsFinished()
595 self.test_results = results_stream.GetTestResults()
596 self.expected_outcomes = server.GetExpectedOutcomes()
597 self.expectation_is_modifiable = \
598 type(server.GetExpectationDatabase()) in (PreviousTestRun,)
599
600
601
602
603
604
605 if self.server.GetRunDatabase():
606 self.run_menu_items.append(("This Directory", ""))
607 else:
608 self.run_menu_items.append(("This Directory", "javascript:run_dir();"))
609
610
611 - def GetRunUrl(self):
612 """Return the URL for running this directory."""
613
614 return qm.web.WebRequest("run-tests",
615 self.request,
616 ids=self.path).AsUrl()
617
618
619 - def GetTestResultsForDirectory(self, directory):
620 """Return all of the test results for tests in 'directory'.
621
622 'directory' -- A string giving the label for a directory.
623
624 returns -- A sequence of 'Result' instances corresponding to
625 results for tests from the indicated directory."""
626
627
628 test_run = self.request.get('test_run')
629 run_db = self.server.GetRunDatabase()
630 if test_run and run_db:
631 return run_db.GetAllRuns()[int(test_run)].GetAllResults(directory)
632
633
634 if directory == "":
635 return self.test_results.values()
636 else:
637 return [r for r in self.test_results.values()
638 if self.IsLabelInDirectory(r.GetId(), directory)]
639
640
642 """Compute the tests in 'results' with each outcome.
643
644 'results' -- A sequence of 'Result' instances.
645
646 returns -- A dictionary mapping outcomes to the results with
647 that outcome -- and for which that outcome is unexpected.
648 The (fake) outcome 'self.EXPECTED' is mapped to expected
649 results."""
650
651 results_by_outcome = {}
652
653 for o in self.outcomes:
654 results_by_outcome[o] = []
655
656 for r in results:
657
658 expectation = self.GetExpectation(r.GetId()) or Result.PASS
659
660 if r.GetOutcome() != expectation:
661 results_by_outcome[r.GetOutcome()].append(r)
662 else:
663 results_by_outcome[self.EXPECTED].append(r)
664
665 return results_by_outcome
666
667
669 """Compute percentages of unexpected 'results'.
670
671 'results' -- A sequence of 'Result' instances.
672
673 returns -- A dictionary mapping the 'EXPECTATION_KINDS' to the
674 percentage (as a floating point number) of tests in 'results'
675 that have that expectation."""
676
677
678
679 total = len(results)
680
681
682 results_by_outcome \
683 = self.GetUnexpectedResultsByOutcome(results)
684
685
686 percentages = {}
687 percentages[self.POSITIVE_UNEXPECTED] \
688 = len(results_by_outcome[Result.PASS])
689 percentages[self.NEGATIVE_UNEXPECTED] \
690 = (len(results_by_outcome[Result.FAIL])
691 + len(results_by_outcome[Result.ERROR])
692 + len(results_by_outcome[Result.UNTESTED]))
693 percentages[self.EXPECTED] \
694 = len(results_by_outcome[self.EXPECTED])
695
696
697 for e in self.EXPECTATION_KINDS:
698 if percentages[e]:
699 percentages[e] = float(percentages[e]) / float(total)
700 else:
701 percentages[e] = 0.0
702
703 return percentages
704
705
706 - def CountUnexpected(self, results):
707 """Count the unexpected 'results'.
708
709 'results' -- A dictionary of the form returned by
710 'GetUnexpectedResultsByOutcome'.
711
712 returns -- The total number of unexpected results."""
713
714 total = 0
715
716 for o in Result.outcomes:
717 total += len(results[o])
718
719 return total
720
721
722 - def GetResultURL(self, id, kind):
723 """Generate a URL for the result page for 'id'.
724
725 'id' -- The name of a test or resource.
726
727 'kind' -- either 'test' or 'resource'.
728
729 returns -- A url string for the result page for 'id'."""
730
731 script = {"test": "show-test",
732 "resource": "show-resource"}[kind]
733 request = qm.web.WebRequest(script, base=self.request, id=id)
734 return request.AsUrl()
735
736
737 - def GetTests(self, sort):
738 """Return information about all of the tests.
739
740 'sort' -- One of the 'SORT_KINDS' indicating how the results
741 should be sorted.
742
743 returns -- A sequence of 'Item' instances
744 corresponding to all of the tests in this diretory."""
745
746
747 tests = []
748
749
750 for id in self.test_ids:
751 outcome = self.GetTestOutcome(id)
752 expectation = self.GetExpectation(id)
753 tests.append(Item(id,
754 outcome=outcome,
755 expectation=expectation))
756
757 if sort == self.SORT_NAME:
758
759 pass
760 elif sort == self.SORT_OUTCOME:
761
762 buckets = {}
763 for o in Result.outcomes + [None]:
764 buckets[o] = []
765
766
767 for t in tests:
768 buckets[t.outcome].append(t)
769
770
771 tests = []
772 for o in Result.outcomes + [None]:
773 tests += buckets[o]
774 elif sort == self.SORT_EXPECTATION:
775
776
777 buckets = {}
778 for o in ['UNEXPECTED', self.EXPECTED, None]:
779 buckets[o] = []
780
781
782 for t in tests:
783 if (t.outcome == (t.expectation or Result.PASS)):
784 buckets[self.EXPECTED].append(t)
785 elif t.outcome:
786 buckets['UNEXPECTED'].append(t)
787 else:
788 buckets[None].append(t)
789
790
791 tests = []
792 for o in ['UNEXPECTED', self.EXPECTED, None]:
793 tests += buckets[o]
794 else:
795
796
797
798 pass
799
800 return tests
801
802
803 - def GetTestOutcome(self, test_id):
804 """Return the 'Result' for 'test_id'.
805
806 'test_id' -- The name of the test whose result is requested.
807
808 'result' -- The result associated with the 'test_id', or
809 'None' if no result is available."""
810
811 test_run = self.request.get('test_run')
812 run_db = self.server.GetRunDatabase()
813 if test_run and run_db:
814 result = run_db.GetAllRuns()[int(test_run)].GetResult(test_id)
815 else:
816 result = self.test_results.get(test_id)
817 return result and result.GetOutcome()
818
819
820 - def GetDetailURL(self, test_id):
821 """Return the detail URL for 'test_id'.
822
823 'test_id' -- The name of the test.
824
825 returns -- The URL that contains details about the 'test_id'."""
826
827 return qm.web.WebRequest("show-result",
828 self.request,
829 True,
830 id=test_id).AsUrl()
831
832
833 - def GetExpectation(self, test_id):
834 """Return the expected outcome for 'test_id'.
835
836 'test_id' -- The name of the test.
837
838 returns -- A string giving the expected outcome for 'test_id',
839 or 'None' if there is no expectation."""
840
841 return self.expected_outcomes.get(test_id)
842
843
844 - def GetSortURL(self, sort):
845 """Get the URL for this page, but sorted as indicated.
846
847 'sort' -- One of the 'SORT_KINDS'.
848
849 returns -- A URL indicating this page, but sorted as
850 indicated."""
851
852 return qm.web.WebRequest("show-dir",
853 base=self.request,
854 id=self.path,
855 sort=sort).AsUrl()
856
857 - def IsFinished(self):
858 """Return true iff no more results are forthcoming.
859
860 returns -- True if no more tests are running."""
861
862 return self.__is_finished
863
864
865 - def GetRefreshDelay(self):
866 """Returns the number of seconds to wait before refreshing the page.
867
868 returns -- The number of seconds to wait before refreshing this
869 page. A value of zero means that te page should never be
870 refreshed. This function is only called if 'IsFinished' returns
871 true."""
872
873 if len(self.test_results.items()) < 50:
874 return 10
875 else:
876 return 30
877
878
879 -class DirReportPage(QMTestReportPage):
880 """A run database directory page.
881
882 These attributes are available in DTML:
883
884 'path' -- The label directory that is being displayed.
885
886 'subdirs' -- A sequence of labels giving the subdirectories of
887 this directory.
888
889 'test_ids' -- A sequence of labels giving the tests in this
890 directory.
891
892 'suite_ids' -- A sequence of labels giving the suites in this
893 directory.
894
895 'resource_ids' -- A sequence of labels giving the resources in
896 this directory."""
897
898 SORT_NAME = 'name'
899 """Sort by name."""
900
901 SORT_OUTCOME = 'outcome'
902 """Sort by outcome."""
903
904 SORT_EXPECTATION = 'expectation'
905 """Sort by expectation. In other words, put unexpected outcomes
906 before expected outcomes."""
907
908 SORT_KINDS = [ SORT_NAME, SORT_OUTCOME, SORT_EXPECTATION ]
909 """The kinds of sorting available."""
910
911 - def __init__(self, server, path):
912 """Construct a 'DirPage'.
913
914 'server' -- The 'QMTestServer' creating this page.
915
916 'path' -- The label directory to display."""
917
918
919 QMTestReportPage.__init__(self, "dir.dtml", server)
920
921 self.path = path
922 database = server.GetDatabase()
923 self.database = database
924 self.run_db = server.GetRunDatabase()
925 self.subdir_ids = [database.JoinLabels(path, l)
926 for l in database.GetSubdirectories(path)]
927 self.test_ids = self.database.GetTestIds(path, scan_subdirs=0)
928 self.suite_ids = [s for s in database.GetSuiteIds(path, scan_subdirs=0)
929 if not database.GetSuite(s).IsImplicit()]
930 self.resource_ids = self.database.GetResourceIds(path, scan_subdirs=0)
931
932
933 - def GetItems(self, kind = Result.TEST):
934 """Return information about all of the items.
935
936 returns -- A sequence of 'Item' instances
937 corresponding to all of the tests in this diretory."""
938
939
940 items = []
941
942
943 test_runs = len(self.run_db.GetAllRuns())
944 for id in self.test_ids:
945 outcomes = self.run_db.GetOutcomes(id, kind)
946 if outcomes[Result.PASS] == test_runs:
947 outcome = Result.PASS
948 outcome_class = 'qmtest_pass'
949 elif outcomes[Result.FAIL] == test_runs:
950 outcome = Result.FAIL
951 outcome_class = 'qmtest_fail'
952 elif outcomes[Result.UNTESTED] == test_runs:
953 outcome = Result.UNTESTED
954 outcome_class = 'qmtest_untested'
955 elif outcomes[Result.ERROR] == test_runs:
956 outcome = Result.ERROR
957 outcome_class = 'qmtest_error'
958 else:
959 outcome = 'MIXED'
960 outcome_class = 'qmtest_mixed'
961 items.append(Item(id,
962 outcome=outcome,
963 outcome_class=outcome_class))
964
965 return items
966
967
968 - def MakeTestRunUrl(self, test_run):
969 """Return the URL for navigating a particular test run."""
970
971 return qm.web.WebRequest("/test/dir",
972 self.request, True,
973 test_run=test_run,
974 id=self.path).AsUrl()
975
976
977
978
979 -class ShowItemReportPage(QMTestReportPage):
980 """DTML page for showing tests and resources."""
981
982 - def __init__(self, server, item, type, field_errors={}):
983 """Construct a new DTML context.
984
985 These parameters are also available in DTML under the same name:
986
987 'server' -- The 'QMTestServer' creating this page.
988
989 'item' -- The 'TestDescriptor' or 'ResourceDescriptor' for the
990 test being shown.
991
992 'type' -- Either "test" or "resource".
993
994 'field_errors' -- A map from field names to corresponding error
995 messages."""
996
997
998 QMTestReportPage.__init__(self, "show.dtml", server)
999 self.item = item
1000 self.fields = item.GetClassArguments()
1001 assert type in ["test", "resource"]
1002 self.type = type
1003 self.field_errors = field_errors
1004
1005
1006 - def GetTitle(self):
1007 """Return the page title for this page."""
1008
1009 url = self.request.GetScriptName()
1010 title = {"show-test": "Show Test Report ",
1011 "show-resource": "Show Resource Report "}[url]
1012 return title + self.item.GetId()
1013
1014
1027
1028
1030 """Return a full description of the test or resource class.
1031
1032 returns -- The description, formatted as HTML."""
1033
1034 d = qm.extension.get_class_description(self.item.GetClass())
1035 return qm.web.format_structured_text(d)
1036
1037
1039 """Return a brief description of the test or resource class.
1040
1041 returns -- The brief description, formatted as HTML."""
1042
1043 d = qm.extension.get_class_description(self.item.GetClass(),
1044 brief=1)
1045 return qm.web.format_structured_text(d)
1046
1047
1048 - def MakeShowUrl(self):
1049 """Return the URL for showing this item."""
1050
1051 return qm.web.WebRequest("show-" + self.type,
1052 self.request, True,
1053 id=self.item.GetId()).AsUrl()
1054
1055
1056 - def GetDetailUrl(self, test_run):
1057 """Return the detail URL for a test.
1058
1059 'test_id' -- The name of the test.
1060
1061 returns -- The URL that contains details about the 'test_id'."""
1062
1063 return qm.web.WebRequest("show-result",
1064 self.request, True,
1065 id=self.item.GetId(),
1066 test_run=test_run).AsUrl()
1067
1068
1069 - def GetResults(self, key=None):
1070 """Return the results from all runs that correpond to the current id."""
1071
1072 result_set = []
1073 test_runs = self.server.GetRunDatabase().GetAllRuns()
1074 for r in range(len(test_runs)):
1075 k = test_runs[r].GetAnnotation(key or self.request['row'])
1076 result = None
1077 try:
1078 result = test_runs[r].GetResult(self.item.GetId(), self.type)
1079 except KeyError:
1080 pass
1081 result_set.append(Item(k, test_run=r, result=result))
1082 return result_set
1083
1084
1085
1086 -class LoadContextPage(QMTestPage):
1087 """DTML page for uploading a context."""
1088
1089 title = "Load Context"
1090 """The title for the page."""
1091
1092 heading = "Load the context from a file."
1093 """The heading printed across the top of the page."""
1094
1095 prompt = "The file from which to load the context."
1096 """The prompt for the file name."""
1097
1098 submit_url = "submit-context-file"
1099 """The URL to which the file should be submitted."""
1100
1101 - def __init__(self, server):
1102 """Construct a new 'LoadContextPage'.
1103
1104 'server' -- The 'QMTestServer' creating this page."""
1105
1106 QMTestPage.__init__(self, "load.dtml", server)
1107
1108
1109
1110 -class LoadExpectationsPage(QMTestPage):
1111 """DTML page for uploading a context."""
1112
1113 title = "Load Expectations"
1114 """The title for the page."""
1115
1116 heading = "Load expectations from a file."
1117 """The heading printed across the top of the page."""
1118
1119 prompt = "The file from which to load expectations."""
1120 """The prompt for the file name."""
1121
1122 submit_url = "submit-expectations"
1123 """The URL to which the file should be submitted."""
1124
1125 - def __init__(self, server):
1126 """Construct a new 'LoadExpectationsPage'.
1127
1128 'server' -- The 'QMTestServer' creating this page."""
1129
1130 QMTestPage.__init__(self, "load.dtml", server)
1131
1132
1133
1134 -class LoadResultsPage(QMTestPage):
1135 """DTML page for uploading a context."""
1136
1137 title = "Load Results"
1138 """The title for the page."""
1139
1140 heading = "Load results from a file."
1141 """The heading printed across the top of the page."""
1142
1143 prompt = "The file from which to load the results."""
1144 """The prompt for the file name."""
1145
1146 submit_url = "submit-results"
1147 """The URL to which the file should be submitted."""
1148
1149 - def __init__(self, server):
1150 """Construct a new 'LoadContextPage'.
1151
1152 'server' -- The 'QMTestServer' creating this page."""
1153
1154 QMTestPage.__init__(self, "load.dtml", server)
1155
1156
1157
1158 -class NewItemPage(QMTestPage):
1159 """Page for creating a new test or resource."""
1160
1161 - def __init__(self,
1162 server,
1163 type,
1164 item_id="",
1165 class_name="",
1166 field_errors={}):
1167 """Create a new DTML context.
1168
1169 'type' -- Either "test" or "resource".
1170
1171 'server' -- The 'QMTestServer' creating this page.
1172
1173 'item_id' -- The item ID to show.
1174
1175 'class_name' -- The class name to show.
1176
1177 'field_errors' -- A mapping of error messages for fields. Keys
1178 may be "_id" or "_class"."""
1179
1180
1181 QMTestPage.__init__(self, "new.dtml", server)
1182
1183 assert type in ["test", "resource"]
1184 self.database = server.GetDatabase()
1185 self.type = type
1186 self.item_id = item_id
1187 self.class_name = class_name
1188 if type == "test":
1189 self.class_names = self.database.GetTestClassNames()
1190 elif type == "resource":
1191 self.class_names = self.database.GetResourceClassNames()
1192 self.field_errors = field_errors
1193
1194
1195 - def GetTitle(self):
1196 """Return the title this page."""
1197
1198 return "Create a New %s" % string.capwords(self.type)
1199
1200
1202 """Return a description of the available classes.
1203
1204 returns -- Structured text describing each of the available
1205 test or resource classes."""
1206
1207 desc = "**Available Classes**\n\n"
1208 for n in self.class_names:
1209 c = qm.test.base.get_extension_class(n, self.type,
1210 self.database)
1211 d = qm.extension.get_class_description(c, brief=1)
1212 desc = desc + " * " + n + "\n\n " + d + "\n\n"
1213
1214 return desc
1215
1216
1217 - def MakeSubmitUrl(self):
1218 """Return the URL for submitting the form.
1219
1220 The URL is for the script 'create-test' or 'create-resource' as
1221 appropriate."""
1222
1223 return qm.web.WebRequest("create-" + self.type,
1224 base=self.request).AsUrl()
1225
1226
1227
1228 -class NewSuitePage(QMTestPage):
1229 """Page for creating a new test suite."""
1230
1231 - def __init__(self, server, suite_id="", field_errors={}):
1232 """Create a new DTML context.
1233
1234 'server' -- The 'QMTestServer' creating this page.
1235
1236 'suite_id' -- Initial value for the new test suite ID field.
1237
1238 'field_errors' -- A mapping of error messages to fields. If
1239 empty, there are no errors."""
1240
1241
1242 QMTestPage.__init__(self, "new-suite.dtml", server)
1243
1244 self.suite_id = suite_id
1245 self.field_errors = field_errors
1246
1247
1248
1249 -class ResultPage(QMTestPage):
1250 """DTML page for showing result detail."""
1251
1252 - def __init__(self, server, result):
1253 """Construct a new 'ResultPage'
1254
1255 'server' -- The 'QMTestServer' creating this page.
1256
1257 'result' -- The result to display."""
1258
1259 QMTestPage.__init__(self, "result.dtml", server)
1260 self.result = result
1261 if result.GetKind() == Result.TEST:
1262 self.run_menu_items.append(("This Test",
1263 "javascript:run_test();"))
1264
1265 - def GetResultURL(self, id):
1266
1267 return qm.web.WebRequest("show-result",
1268 base = self.request,
1269 id = id).AsUrl()
1270
1271
1272 - def GetRunURL(self):
1273
1274 return qm.web.WebRequest("run-tests",
1275 base = self.request,
1276 ids = self.result.GetId()).AsUrl()
1277
1278
1279
1280 -class SetExpectationPage(QMTestPage):
1281 """DTML page for setting the expectation associated with a test."""
1282
1283 - def __init__(self, server, id):
1284 """Construct a new 'SetExpectationPage'.
1285
1286 'server' -- The 'QMTestServer' creating this page.
1287
1288 'id' -- The name of the test whose expectation is being set."""
1289
1290 QMTestPage.__init__(self, "set-expectation.dtml", server)
1291 self.outcomes = ["None"] + Result.outcomes
1292
1293
1294
1295 -class ShowItemPage(QMTestPage):
1296 """DTML page for showing and editing tests and resources."""
1297
1298 - def __init__(self, server, item, edit, new, type, field_errors={}):
1299 """Construct a new DTML context.
1300
1301 These parameters are also available in DTML under the same name:
1302
1303 'server' -- The 'QMTestServer' creating this page.
1304
1305 'item' -- The 'TestDescriptor' or 'ResourceDescriptor' for the
1306 test being shown.
1307
1308 'edit' -- True for editing the item; false for displaying it
1309 only.
1310
1311 'new' -- True for editing a newly-created item ('edit' is then
1312 also true).
1313
1314 'type' -- Either "test" or "resource".
1315
1316 'field_errors' -- A map from field names to corresponding error
1317 messages."""
1318
1319
1320 QMTestPage.__init__(self, "show.dtml", server)
1321
1322 self.__database = server.GetDatabase()
1323 self.item = item
1324 self.fields = item.GetClassArguments()
1325 self.edit = edit
1326 self.new = new
1327 assert type in ["test", "resource"]
1328 self.type = type
1329 self.field_errors = field_errors
1330
1331 if self.__database.IsModifiable():
1332 self.edit_menu_items.append(("Edit %s" % string.capitalize(type),
1333 "javascript:edit_item();"))
1334 self.edit_menu_items.append(("Delete %s" % string.capitalize(type),
1335 "javascript:delete_item();"))
1336
1337 if type == "test" and not edit:
1338 self.run_menu_items.append(("This Test", "javascript:run_test();"))
1339
1340
1341 - def GetTitle(self):
1342 """Return the page title for this page."""
1343
1344
1345 url = self.request.GetScriptName()
1346 title = {
1347 "show-test": "Show Test",
1348 "edit-test": "Edit Test",
1349 "create-test": "New Test",
1350 "show-resource": "Show Resource",
1351 "edit-resource": "Edit Resource",
1352 "create-resource": "New Resource",
1353 }[url]
1354
1355 title = title + " " + self.item.GetId()
1356 return title
1357
1358
1360 """Return an HTML rendering of the value for 'field'."""
1361
1362
1363 arguments = self.item.GetArguments()
1364 field_name = field.GetName()
1365 try:
1366 value = arguments[field_name]
1367 except KeyError:
1368
1369 value = field.GetDefaultValue()
1370
1371 server = self.server
1372 if self.edit:
1373 if field.IsHidden():
1374 return field.FormatValueAsHtml(server, value, "hidden")
1375 elif field.IsReadOnly():
1376
1377
1378
1379
1380 return field.FormatValueAsHtml(server, value, "hidden") \
1381 + field.FormatValueAsHtml(server, value, "full")
1382 else:
1383 return field.FormatValueAsHtml(server, value, "edit")
1384 else:
1385 return field.FormatValueAsHtml(server, value, "full")
1386
1387
1389 """Return a full description of the test or resource class.
1390
1391 returns -- The description, formatted as HTML."""
1392
1393 d = qm.extension.get_class_description(self.item.GetClass())
1394 return qm.web.format_structured_text(d)
1395
1396
1398 """Return a brief description of the test or resource class.
1399
1400 returns -- The brief description, formatted as HTML."""
1401
1402 d = qm.extension.get_class_description(self.item.GetClass(),
1403 brief=1)
1404 return qm.web.format_structured_text(d)
1405
1406
1407 - def MakeEditUrl(self):
1408 """Return the URL for editing this item."""
1409
1410 return qm.web.WebRequest("edit-" + self.type,
1411 base=self.request,
1412 id=self.item.GetId()).AsUrl()
1413
1414
1415 - def MakeRunUrl(self):
1416 """Return the URL for running this item."""
1417
1418 return qm.web.WebRequest("run-tests",
1419 base=self.request,
1420 ids=self.item.GetId()).AsUrl()
1421
1422
1423 - def MakeShowUrl(self):
1424 """Return the URL for showing this item."""
1425
1426 return qm.web.WebRequest("show-" + self.type,
1427 base=self.request,
1428 id=self.item.GetId()).AsUrl()
1429
1430
1431 - def MakeSubmitUrl(self):
1432 """Return the URL for submitting edits."""
1433
1434 return qm.web.WebRequest("submit-" + self.type,
1435 base=self.request).AsUrl()
1436
1437
1438 - def MakeDeleteScript(self):
1439 """Make a script to confirm deletion of the test or resource.
1440
1441 returns -- JavaScript source to handle deletion of the
1442 test or resource."""
1443
1444 item_id = self.item.GetId()
1445 delete_url = qm.web.make_url("delete-" + self.type,
1446 base_request=self.request,
1447 id=item_id)
1448 message = """
1449 <p>Are you sure you want to delete the %s %s?</p>
1450 """ % (self.type, item_id)
1451 return self.server.MakeConfirmationDialog(message, delete_url)
1452
1453
1454
1455 -class ShowSuitePage(QMTestPage):
1456 """Page for displaying the contents of a test suite."""
1457
1458 - def __init__(self, server, suite, edit, is_new_suite):
1459 """Construct a new DTML context.
1460
1461 'server' -- The 'QMTestServer' creating this page.
1462
1463 'suite' -- The 'Suite' instance to display.
1464
1465 'edit' -- If true, display controls for editing the suite.
1466
1467 'is_new_suite' -- If true, the suite being displayed is being
1468 created at this time."""
1469
1470
1471 QMTestPage.__init__(self, "suite.dtml", server)
1472
1473
1474
1475 assert edit or not is_new_suite
1476
1477
1478 database = server.GetDatabase()
1479 self.suite = suite
1480 self.test_ids = suite.GetTestIds()
1481 self.suite_ids = suite.GetSuiteIds()
1482 self.edit = edit
1483 self.is_new_suite = is_new_suite
1484
1485 if not suite.IsImplicit() and database.IsModifiable():
1486 self.edit_menu_items.append(("Edit Suite",
1487 "javascript:edit_suite();"))
1488 self.edit_menu_items.append(("Delete Suite",
1489 "javascript:delete_suite();"))
1490
1491 if not edit:
1492 self.run_menu_items.append(("This Suite",
1493 "javascript:run_suite();"))
1494
1495 if edit:
1496
1497 (dirname, basename) = self.GetDatabase().SplitLabel(suite.GetId())
1498
1499
1500
1501 excluded_test_ids = database.GetTestIds(dirname)
1502 for test_id in self.test_ids:
1503 if test_id in excluded_test_ids:
1504 excluded_test_ids.remove(test_id)
1505
1506 self.test_id_controls = qm.web.make_choose_control(
1507 "test_ids",
1508 "Included Tests",
1509 self.test_ids,
1510 "Available Tests",
1511 excluded_test_ids)
1512
1513
1514 excluded_suite_ids = database.GetSuiteIds(dirname)
1515 for suite_id in self.suite_ids:
1516 if suite_id in excluded_suite_ids:
1517 excluded_suite_ids.remove(suite_id)
1518
1519
1520 self_suite_id = basename
1521 if self_suite_id in excluded_suite_ids:
1522 excluded_suite_ids.remove(self_suite_id)
1523
1524 self.suite_id_controls = qm.web.make_choose_control(
1525 "suite_ids",
1526 "Included Suites",
1527 self.suite_ids,
1528 "Available Suites",
1529 excluded_suite_ids)
1530
1531
1532 - def MakeEditUrl(self):
1533 """Return the URL for editing this suite."""
1534
1535 return qm.web.WebRequest("edit-suite",
1536 base=self.request,
1537 id=self.suite.GetId()) \
1538 .AsUrl()
1539
1540
1541 - def MakeRunUrl(self):
1542 """Return the URL for running this suite."""
1543
1544 return qm.web.WebRequest("run-tests",
1545 base=self.request,
1546 ids=self.suite.GetId()) \
1547 .AsUrl()
1548
1549
1550 - def MakeDeleteScript(self):
1551 """Make a script to confirm deletion of the suite.
1552
1553 returns -- JavaScript source for a function, 'delete_script',
1554 which shows a popup confirmation window."""
1555
1556 suite_id = self.suite.GetId()
1557 delete_url = qm.web.make_url("delete-suite",
1558 base_request=self.request,
1559 id=suite_id)
1560 message = """
1561 <p>Are you sure you want to delete the suite %s?</p>
1562 """ % suite_id
1563 return self.server.MakeConfirmationDialog(message, delete_url)
1564
1565
1566
1568 """A 'StorageResultsStream' stores results.
1569
1570 A 'StorageResultsStream' does not write any output. It simply
1571 stores the results for future display."""
1572
1574 """Construct a 'StorageResultsStream'."""
1575
1576 super(StorageResultsStream, self).__init__({})
1577 self.__test_results = {}
1578 self.__test_results_in_order = []
1579 self.__resource_results = {}
1580
1581 self.__is_finished = 0
1582
1583 self.__annotations = {}
1584
1585
1586
1587
1588
1589 self.__lock = Lock()
1590
1591
1593 """Return the annotations for this run."""
1594
1595 return self.__annotations
1596
1597
1599
1600 self.__annotations[key] = value
1601
1602
1604 """Output a test result.
1605
1606 'result' -- A 'Result'."""
1607
1608 self.__lock.acquire()
1609 try:
1610 if result.GetKind() == Result.TEST:
1611 self.__test_results[result.GetId()] = result
1612 self.__test_results_in_order.append(result)
1613 else:
1614 self.__resource_results[result.GetId()] = result
1615 finally:
1616 self.__lock.release()
1617
1618
1620 """Output summary information about the results.
1621
1622 When this method is called, the test run is complete. Summary
1623 information should be displayed for the user, if appropriate.
1624 Any finalization, such as the closing of open files, should
1625 also be performed at this point.
1626
1627 Derived class methods may override this method. They should,
1628 however, invoke this version before returning."""
1629
1630
1631 self.__lock.acquire()
1632 ResultStream.Summarize(self)
1633 self.__is_finished = 1
1634 self.__lock.release()
1635
1636
1637 - def Start(self, test_ids):
1638 """Start collecting results.
1639
1640 'test_ids' -- The names of the tests that we are about to run.
1641
1642 Start collecting new results. Discard results for the
1643 'test_ids', but not for other tests."""
1644
1645 self.__lock.acquire()
1646 self.__is_finished = 0
1647
1648
1649 for id in test_ids:
1650 if self.__test_results.has_key(id):
1651 del self.__test_results[id]
1652 self.__test_results_in_order \
1653 = filter(lambda r, rs=self.__test_results: \
1654 rs.has_key(r.GetId()),
1655 self.__test_results_in_order)
1656 self.__lock.release()
1657
1658
1660 """Return true iff no more results are forthcoming.
1661
1662 returns -- True if no more results will be written to this
1663 stream."""
1664
1665 self.__lock.acquire()
1666 finished = self.__is_finished
1667 self.__lock.release()
1668 return finished
1669
1670
1672 """Return the accumulated test results.
1673
1674 returns -- A dictionary mapping test names to 'Result' objects."""
1675
1676 self.__lock.acquire()
1677 results = self.__test_results
1678 self.__lock.release()
1679 return results
1680
1681
1683 """Return the test results in the order they appeared.
1684
1685 returns -- A sequence of test results, in the order that they
1686 appeared."""
1687
1688 self.__lock.acquire()
1689 results = self.__test_results_in_order
1690 self.__lock.release()
1691 return results
1692
1693
1695 """Return the accumulated resource results.
1696
1697 returns -- A dictionary mapping resource names to 'Result'
1698 objects."""
1699
1700 self.__lock.acquire()
1701 results = self.__resource_results
1702 self.__lock.release()
1703 return results
1704
1705
1707 """Return the 'Result' with the indicated 'name'.
1708
1709 'name' -- A string giving the name of a test or resource result.
1710
1711 returns -- The 'Result' instance corresponding to 'name'."""
1712
1713 self.__lock.acquire()
1714 result = self.__test_results.get(name)
1715 if not result:
1716 result = self.__resource_results.get(name)
1717 self.__lock.release()
1718
1719 return result
1720
1721
1722 -class TestResultsPage(QMTestPage):
1723 """DTML page for displaying test results."""
1724
1725 - def __init__(self, server):
1726 """Construct a new 'TestResultsPage'.
1727
1728 'server' -- The 'QMTestServer' creating this page."""
1729
1730
1731 QMTestPage.__init__(self, "results.dtml", server)
1732
1733 results_stream = server.GetResultsStream()
1734
1735
1736
1737
1738
1739 self.__is_finished = results_stream.IsFinished()
1740 self.test_results = results_stream.GetTestResultsInOrder()
1741 self.expected_outcomes = server.GetExpectedOutcomes()
1742
1743
1744 - def GetOutcomes(self):
1745 """Return the list of result outcomes.
1746
1747 returns -- A sequence of result outcomes."""
1748
1749 return Result.outcomes
1750
1751
1752 - def GetTotal(self):
1753 """Return the total number of tests.
1754
1755 returns -- The total number of tests."""
1756
1757 return len(self.test_results)
1758
1759
1761 """Return the total number of unexpected results.
1762
1763 returns -- The total number of unexpected results."""
1764
1765 return len(self.GetRelativeResults(self.test_results, 0))
1766
1767
1768 - def GetResultsWithOutcome(self, outcome):
1769 """Return the number of tests with the given 'outcome'.
1770
1771 'outcome' -- One of the 'Result.outcomes'.
1772
1773 returns -- The results with the given 'outcome'."""
1774
1775 return filter(lambda r, o=outcome: r.GetOutcome() == o,
1776 self.test_results)
1777
1778
1779 - def GetCount(self, outcome):
1780 """Return the number of tests with the given 'outcome'.
1781
1782 'outcome' -- One of the 'Result.outcomes'.
1783
1784 returns -- The number of tests with the given 'outcome'."""
1785
1786 return len(self.GetResultsWithOutcome(outcome))
1787
1788
1789 - def GetUnexpectedCount(self, outcome):
1790 """Return the number of tests with the given 'outcome'.
1791
1792 'outcome' -- One of the 'Result.outcomes'.
1793
1794 returns -- The number of tests with the given 'outcome' that
1795 were expected to have some other outcome."""
1796
1797 results = self.GetResultsWithOutcome(outcome)
1798 results = self.GetRelativeResults(results, 0)
1799 return len(results)
1800
1801
1802 - def GetRelativeResults(self, results, expected):
1803 """Return the results that match, or fail to match, expectations.
1804
1805 'results' -- A sequence of 'Result' objects.
1806
1807 'expected' -- A boolean. If true, expected results are
1808 returned. If false, unexpected results are returned."""
1809
1810 if expected:
1811 return filter(lambda r, er=self.expected_outcomes: \
1812 r.GetOutcome() == er.get(r.GetId(),
1813 Result.PASS),
1814 results)
1815 else:
1816 return filter(lambda r, er=self.expected_outcomes: \
1817 r.GetOutcome() != er.get(r.GetId(),
1818 Result.PASS),
1819 results)
1820
1821
1822 - def GetDetailUrl(self, test_id):
1823 """Return the detail URL for a test.
1824
1825 'test_id' -- The name of the test.
1826
1827 returns -- The URL that contains details about the 'test_id'."""
1828
1829 return qm.web.WebRequest("show-result",
1830 base=self.request,
1831 id=test_id).AsUrl()
1832
1833
1834 - def IsFinished(self):
1835 """Return true iff no more results are forthcoming.
1836
1837 returns -- True if no more tests are running."""
1838
1839 return self.__is_finished
1840
1841
1842 - def GetRefreshDelay(self):
1843 """Returns the number of seconds to wait before refreshing the page.
1844
1845 returns -- The number of seconds to wait before refreshing this
1846 page. A value of zero means that te page should never be
1847 refreshed. This function is only called if 'IsFinished' returns
1848 true."""
1849
1850 return 10
1851
1852
1853
1855 """A 'QMTestServer' is the web GUI interface to QMTest."""
1856
1857 - def __init__(self, database, port, address, log_file,
1858 targets, context, expectations,
1859 run_db):
1860 """Create and bind an HTTP server.
1861
1862 'database' -- The test database to serve.
1863
1864 'port' -- The port number on which to accept HTTP requests.
1865
1866 'address' -- The local address to which to bind the server. An
1867 empty string indicates all local addresses.
1868
1869 'log_file' -- A file object to which the server will log requests.
1870 'None' for no logging.
1871
1872 'targets' -- A sequence of 'Target' objects to use when running
1873 tests.
1874
1875 'context' -- The 'Context' in which tests will execute."""
1876
1877 qm.web.WebServer.__init__(self, port, address, log_file=log_file)
1878
1879 self.__database = database
1880 self.__targets = targets
1881 self.__context = context
1882
1883
1884 for name, function in [
1885 ( "/test/clear-results", self.HandleClearResults ),
1886 ( "/test/create-resource", self.HandleShowItem ),
1887 ( "/test/create-suite", self.HandleCreateSuite ),
1888 ( "/test/create-test", self.HandleShowItem ),
1889 ( "/test/delete-resource", self.HandleDeleteItem ),
1890 ( "/test/delete-suite", self.HandleDeleteSuite ),
1891 ( "/test/delete-test", self.HandleDeleteItem ),
1892 ( "/test/dir", self.HandleDir ),
1893 ( "/test/edit-context", self.HandleEditContext ),
1894 ( "/test/edit-resource", self.HandleShowItem ),
1895 ( "/test/edit-suite", self.HandleEditSuite ),
1896 ( "/test/edit-test", self.HandleShowItem ),
1897 ( "/test/load-context", self.HandleLoadContext ),
1898 ( "/test/load-expectations", self.HandleLoadExpectations ),
1899 ( "/test/load-results", self.HandleLoadResults ),
1900 ( "/test/new-resource", self.HandleNewResource ),
1901 ( "/test/new-suite", self.HandleNewSuite ),
1902 ( "/test/new-test", self.HandleNewTest ),
1903 ( "/test/run-tests", self.HandleRunTests ),
1904 ( "/test/set-expectation", self.HandleSetExpectation ),
1905 ( "/test/show-dir", self.HandleDir ),
1906 ( "/test/show-resource", self.HandleShowItem ),
1907 ( "/test/show-result", self.HandleShowResult ),
1908 ( "/test/show-results", self.HandleShowResults ),
1909 ( "/test/show-suite", self.HandleShowSuite ),
1910 ( "/test/show-test", self.HandleShowItem ),
1911 ( "/test/shutdown", self.HandleShutdown ),
1912 ( "/test/stop-tests", self.HandleStopTests ),
1913 ( "/test/submit-context", self.HandleSubmitContext ),
1914 ( "/test/submit-context-file", self.HandleSubmitContextFile ),
1915 ( "/test/submit-expectation", self.HandleSubmitExpectation ),
1916 ( "/test/submit-resource", self.HandleSubmitItem ),
1917 ( "/test/submit-expectations", self.HandleSubmitExpectations ),
1918 ( "/test/submit-expectations-form", self.HandleSubmitExpectationsForm ),
1919 ( "/test/submit-results", self.HandleSubmitResults ),
1920 ( "/test/submit-suite", self.HandleSubmitSuite ),
1921 ( "/test/submit-test", self.HandleSubmitItem ),
1922 ( "/test/" + qm.test.cmdline.QMTest.context_file_name,
1923 self.HandleSaveContext ),
1924 ( "/test/" + qm.test.cmdline.QMTest.expectations_file_name,
1925 self.HandleSaveExpectations ),
1926 ( "/test/" + qm.test.cmdline.QMTest.results_file_name,
1927 self.HandleSaveResults ),
1928 ( "/report/dir", self.HandleDirReport ),
1929 ( "/report/show-dir", self.HandleDirReport ),
1930 ( "/report/show-test", self.HandleShowItemReport ),
1931 ( "/report/show-resource", self.HandleShowItemReport ),
1932 ( "/report/show-result", self.HandleShowResultReport ),
1933 ( "/report/show-result", self.HandleShowResult ),
1934 ]:
1935 self.RegisterScript(name, function)
1936
1937
1938 self.RegisterPathTranslation(
1939 "/stylesheets", qm.get_share_directory("web", "stylesheets"))
1940 self.RegisterPathTranslation(
1941 "/images", qm.get_share_directory("web", "images"))
1942 self.RegisterPathTranslation(
1943 "/static", qm.get_share_directory("web", "static"))
1944
1945 self.RegisterPathTranslation(
1946 "/tutorial", qm.get_doc_directory("html", "tutorial"))
1947
1948
1949
1950 attachment_store = database.GetAttachmentStore()
1951 if attachment_store:
1952 self.RegisterScript(qm.fields.AttachmentField.download_url,
1953 attachment_store.HandleDownloadRequest)
1954
1955 self.__expectation_db = expectations
1956 self.__expected_outcomes = {}
1957 for test_id in self.__database.GetTestIds():
1958 result = expectations.Lookup(test_id)
1959 self.__expected_outcomes[test_id] = result.GetOutcome()
1960 self.__run_db = run_db
1961
1962 self.__results_stream = StorageResultsStream()
1963 self.__results_stream.Summarize()
1964
1965 self.__execution_thread = None
1966
1967
1968 try:
1969 self.Bind()
1970 except qm.web.AddressInUseError, address:
1971 raise RuntimeError, qm.error("address in use", address=address)
1972 except qm.web.PrivilegedPortError:
1973 raise RuntimeError, qm.error("privileged port", port=port)
1974
1975
1976 - def GetContext(self):
1977 """Return the 'Context' in which tests will be run.
1978
1979 returns -- The 'Context' in which tests will be run."""
1980
1981 return self.__context
1982
1983
1985 """Return the 'Database' handled by this server.
1986
1987 returns -- The 'Database' handled by this server."""
1988
1989 return self.__database
1990
1991
1993 """Return the 'RunDatabase' handled by this server.
1994
1995 returns -- The 'RunDatabase' handled by this server."""
1996
1997 return self.__run_db
1998
1999
2001 """Return the current ExpectationDatabase.
2002
2003 returns -- The ExpectationDatabase instance."""
2004
2005 return self.__expectation_db
2006
2007
2009 """Return the current expected outcomes for the test database.
2010
2011 returns -- A map from test IDs to outcomes. Some tests may have
2012 not have an entry in the map."""
2013
2014 return self.__expected_outcomes
2015
2016
2018 """Return the CSS class for the 'outcome'.
2019
2020 'outcome' -- One of the result outcomes.
2021
2022 returns -- The name of a CSS class. These are used with <span>
2023 elements. See 'qm.css'."""
2024
2025 return {
2026 Result.PASS: "qmtest_pass",
2027 Result.FAIL: "qmtest_fail",
2028 Result.UNTESTED: "qmtest_untested",
2029 Result.ERROR: "qmtest_error",
2030 "EXPECTED" : "qmtest_expected"
2031 }[outcome]
2032
2033
2035 """Return the 'StorageResultsStream' containing test results.
2036
2037 returns -- The 'StorageResultsStream' associated with this
2038 server."""
2039
2040 return self.__results_stream
2041
2042
2044 """Handle a request to clear the current test results.
2045
2046 'request' -- A 'WebRequest' object."""
2047
2048
2049 del self.__results_stream
2050
2051 self.__results_stream = StorageResultsStream()
2052 self.__results_stream.Summarize()
2053
2054
2055 request = qm.web.WebRequest("dir", base=request)
2056 raise qm.web.HttpRedirect, request
2057
2058
2060 """Handle a submission of a new test suite.
2061
2062 'request' -- A 'WebRequest' object."""
2063
2064 field_errors = {}
2065 database = self.__database
2066
2067
2068 suite_id = request["id"]
2069
2070 if not database.IsValidLabel(suite_id, is_component = 0):
2071 field_errors["_id"] = qm.error("invalid id", id=suite_id)
2072
2073 elif database.HasSuite(suite_id):
2074 field_errors["_id"] = qm.error("suite already exists",
2075 suite_id=suite_id)
2076
2077
2078 if len(field_errors) > 0:
2079
2080
2081 return NewSuitePage(self, suite_id, field_errors)(request)
2082 else:
2083
2084 suite_class = qm.test.base.get_extension_class(
2085 "explicit_suite.ExplicitSuite",
2086 "suite",
2087 self.GetDatabase())
2088 extras = { suite_class.EXTRA_DATABASE : self.GetDatabase(),
2089 suite_class.EXTRA_ID : suite_id }
2090 suite = suite_class({}, **extras)
2091
2092 return ShowSuitePage(self, suite, edit=1, is_new_suite=1)(request)
2093
2094
2096 """Handle a request to delete a test or resource.
2097
2098 This function handles the script requests 'delete-test' and
2099 'delete-resource'.
2100
2101 'request' -- A 'WebRequest' object.
2102
2103 The ID of the test or resource to delete is specified in the 'id'
2104 field of the request."""
2105
2106 database = self.__database
2107
2108 item_id = request["id"]
2109
2110
2111 script_name = request.GetScriptName()
2112 if script_name == "delete-test":
2113 database.RemoveExtension(item_id, database.TEST)
2114 elif script_name == "delete-resource":
2115 database.RemoveExtension(item_id, database.RESOURCE)
2116 else:
2117 raise RuntimeError, "unrecognized script name"
2118
2119 request = qm.web.WebRequest("dir", base=request)
2120 raise qm.web.HttpRedirect, request
2121
2122
2137
2138
2140 """Generate a directory page.
2141
2142 'request' -- A 'WebRequest' object.
2143
2144 The request has these fields:
2145
2146 'path' -- A path in test/resource/suite ID space. If specified,
2147 only tests and resources in this subtree are displayed, and their
2148 IDs are displayed relative to this path. If omitted, the entire
2149 contents of the test database are shown."""
2150
2151 path = request.get("id", "")
2152 return DirPage(self, path)(request)
2153
2154
2156 """Generate a directory report page.
2157
2158 'request' -- A 'WebRequest' object.
2159
2160 The request has these fields:
2161
2162 'path' -- A path in test/resource/suite ID space. If specified,
2163 only tests and resources in this subtree are displayed, and their
2164 IDs are displayed relative to this path. If omitted, the entire
2165 contents of the test database are shown."""
2166
2167 path = request.get("id", "")
2168 return DirReportPage(self, path)(request)
2169
2170
2171 - def HandleEditContext(self, request):
2172 """Handle a request to edit the context.
2173
2174 'request' -- The 'WebRequest' that caused the event."""
2175
2176 context_page = ContextPage(self)
2177 return context_page(request)
2178
2179
2181 """Generate the page for editing a test suite."""
2182
2183 return self.HandleShowSuite(request, edit=1)
2184
2185
2186 - def HandleLoadContext(self, request):
2187 """Handle a request to upload a context file.
2188
2189 'request' -- The 'WebRequest' that caused the event."""
2190
2191 return LoadContextPage(self)(request)
2192
2193
2195 """Handle a request to upload results.
2196
2197 'request' -- The 'WebRequest' that caused the event."""
2198
2199 return LoadExpectationsPage(self)(request)
2200
2201
2203 """Handle a request to upload results.
2204
2205 'request' -- The 'WebRequest' that caused the event."""
2206
2207 return LoadResultsPage(self)(request)
2208
2209
2211 """Handle a request to create a new test.
2212
2213 'request' -- The 'WebRequest' that caused the event."""
2214
2215 return NewItemPage(self, "resource")(request)
2216
2217
2219 """Handle a request to create a new test.
2220
2221 'request' -- The 'WebRequest' that caused the event."""
2222
2223 return NewItemPage(self, "test")(request)
2224
2225
2227 """Handle a request to create a new suite.
2228
2229 'request' -- The 'WebRequest' that caused the event."""
2230
2231 return NewSuitePage(self)(request)
2232
2233
2235 """Handle a request to run tests.
2236
2237 'request' -- The 'WebRequest' that caused the event.
2238
2239 These fields in 'request' are used:
2240
2241 'ids' -- A comma-separated list of test and suite IDs. These IDs
2242 are expanded into the list of IDs of tests to run.
2243
2244 """
2245
2246
2247 if request.has_key("ids"):
2248 ids = string.split(request["ids"], ",")
2249
2250 if '.' in ids:
2251 ids = [""]
2252 else:
2253 ids = [""]
2254 test_ids = self.GetDatabase().ExpandIds(ids)[0]
2255
2256
2257
2258 self.__results_stream.Start(test_ids)
2259
2260
2261 del self.__execution_thread
2262 test_ids.sort()
2263 self.__execution_thread = \
2264 ExecutionThread(self.__database, test_ids, self.__context,
2265 self.__targets, [self.__results_stream],
2266 self.__expectation_db)
2267
2268 self.__execution_thread.start()
2269
2270
2271
2272
2273 time.sleep(5)
2274
2275
2276 request = qm.web.WebRequest("show-results", base=request)
2277 raise qm.web.HttpRedirect, request
2278
2279
2280 - def HandleSaveContext(self, request):
2281 """Handlea request to save the context to a file.
2282
2283 'request' -- The 'WebRequest' that caused the event."""
2284
2285
2286 s = ""
2287
2288 for (name, value) in self.__context.items():
2289 s = s + "%s=%s\n" % (name, value)
2290
2291 return ("application/x-qmtest-context", s)
2292
2293
2316
2317
2343
2344
2346 """Handle a request to set expectations.
2347
2348 'request' -- A 'WebRequest' object."""
2349
2350 return SetExpectationPage(self, request["id"])(request)
2351
2352
2354 """Handle a request to show a test or resource.
2355
2356 'request' -- A 'WebRequest' object.
2357
2358 This function generates pages to handle these requests:
2359
2360 'create-test' -- Generate a form for initial editing of a test
2361 about to be created, given its test ID and test class.
2362
2363 'create-resource' -- Likewise for an resource.
2364
2365 'show-test' -- Display a test.
2366
2367 'show-resource' -- Likewise for an resource.
2368
2369 'edit-test' -- Generate a form for editing an existing test.
2370
2371 'edit-resource' -- Likewise for an resource.
2372
2373 This function distinguishes among these cases by checking the script
2374 name of the request object.
2375
2376 The request must have the following fields:
2377
2378 'id' -- A test or resource ID. For show or edit pages, the ID of an
2379 existing item. For create pages, the ID of the item being
2380 created.
2381
2382 'class' -- For create pages, the name of the test or resource
2383 class.
2384
2385 """
2386
2387
2388 url = request.GetScriptName()
2389 edit, create, type = {
2390 "show-test": (0, 0, "test"),
2391 "edit-test": (1, 0, "test"),
2392 "create-test": (1, 1, "test"),
2393 "show-resource": (0, 0, "resource"),
2394 "edit-resource": (1, 0, "resource"),
2395 "create-resource": (1, 1, "resource"),
2396 }[url]
2397
2398 database = self.__database
2399
2400 try:
2401
2402 item_id = request["id"]
2403 except KeyError:
2404
2405 message = qm.error("no id for show")
2406 return qm.web.generate_error_page(request, message)
2407
2408 if create:
2409
2410 class_name = request["class"]
2411
2412
2413 field_errors = {}
2414
2415 if not database.IsValidLabel(item_id, is_component = 0):
2416 field_errors["_id"] = qm.error("invalid id", id=item_id)
2417 else:
2418
2419 if type == "resource":
2420 if database.HasResource(item_id):
2421 field_errors["_id"] \
2422 = qm.error("resource already exists",
2423 resource_id=item_id)
2424 elif type == "test":
2425 if database.HasTest(item_id):
2426 field_errors["_id"] = qm.error("test already exists",
2427 test_id=item_id)
2428
2429 try:
2430 qm.test.base.get_extension_class(class_name, type,
2431 database)
2432 except ValueError:
2433
2434 field_errors["_class"] = qm.error("invalid class name",
2435 class_name=class_name)
2436 except:
2437
2438 field_errors["_class"] = qm.error("class not found",
2439 class_name=class_name)
2440
2441 if len(field_errors) > 0:
2442
2443
2444 page = NewItemPage(server=self,
2445 type=type,
2446 item_id=item_id,
2447 class_name=class_name,
2448 field_errors=field_errors)
2449 return page(request)
2450
2451
2452
2453 if type == "resource":
2454 item = self.MakeNewResource(class_name, item_id)
2455 elif type == "test":
2456 item = self.MakeNewTest(class_name, item_id)
2457 else:
2458
2459
2460 if type == "resource":
2461 try:
2462 item = database.GetResource(item_id)
2463 except qm.test.database.NoSuchTestError, e:
2464
2465
2466 return qm.web.generate_error_page(request, str(e))
2467 elif type == "test":
2468 try:
2469 item = database.GetTest(item_id)
2470 except qm.test.database.NoSuchResourceError, e:
2471
2472
2473 return qm.web.generate_error_page(request, str(e))
2474
2475
2476 return ShowItemPage(self, item, edit, create, type)(request)
2477
2478
2494
2495
2497 """Handle a request to show results.
2498
2499 'request' -- The 'WebRequest' that caused the event."""
2500
2501
2502 results_page = TestResultsPage(self)
2503 return results_page(request)
2504
2505
2507 """Handle a request to show a test or resource report.
2508
2509 'request' -- A 'WebRequest' object.
2510
2511 This function generates pages to handle these requests:
2512
2513 'show-test' -- Display a test.
2514
2515 'show-resource' -- Likewise for an resource.
2516
2517 This function distinguishes among these cases by checking the script
2518 name of the request object.
2519
2520 The request must have the following fields:
2521
2522 'id' -- A test or resource ID. For show or edit pages, the ID of an
2523 existing item. For create pages, the ID of the item being
2524 created."""
2525
2526
2527 url = request.GetScriptName()
2528 type = {"show-test": "test",
2529 "show-resource": "resource"}[url]
2530
2531 database = self.GetDatabase()
2532
2533 try:
2534
2535 item_id = request["id"]
2536 except KeyError:
2537
2538 message = qm.error("no id for show")
2539 return qm.web.generate_error_page(request, message)
2540
2541 if type == "resource":
2542 try:
2543 item = database.GetResource(item_id)
2544 except qm.test.database.NoSuchTestError, e:
2545 return qm.web.generate_error_page(request, str(e))
2546 elif type == "test":
2547 try:
2548 item = database.GetTest(item_id)
2549 except qm.test.database.NoSuchResourceError, e:
2550 return qm.web.generate_error_page(request, str(e))
2551
2552
2553 return ShowItemReportPage(self, item, type)(request)
2554
2555
2557 """Handle a request to show result report.
2558
2559 'request' -- The 'WebRequest' that caused the event."""
2560
2561 return xxx
2562
2563
2564
2565
2566
2568 """Generate the page for displaying or editing a test suite.
2569
2570 'request' -- A 'WebRequest' object.
2571
2572 'edit' -- If true, display the page for editing the suite.
2573 Otherwise, just display the suite.
2574
2575 The request has the following fields:
2576
2577 'id' -- The ID of the suite to display or edit."""
2578
2579 database = self.__database
2580
2581 try:
2582
2583 suite_id = request["id"]
2584 except KeyError:
2585
2586 message = qm.error("no id for show")
2587 return qm.web.generate_error_page(request, message)
2588 else:
2589 suite = database.GetSuite(suite_id)
2590
2591 return ShowSuitePage(self, suite, edit, is_new_suite=0)(request)
2592
2593
2595 """Handle a request to shut down the server.
2596
2597 'request' -- The 'WebRequest' that caused the event."""
2598
2599 raise SystemExit, None
2600
2601
2603 """Handle a request to stop test execution.
2604
2605 'request' -- The 'WebRequest' that caused the event."""
2606
2607
2608 self.__execution_thread.RequestTermination()
2609
2610 request = qm.web.WebRequest("show-results", base=request)
2611 raise qm.web.HttpRedirect, request
2612
2613
2614 - def HandleSubmitContext(self, request):
2615 """Handle a context submission..
2616
2617 'request' -- The 'WebRequest' that caused the event. The
2618 'request' must have a 'context_vars' key, whose value is the
2619 the context variables."""
2620
2621 vars = qm.web.decode_properties(request["context_vars"])
2622 self.__context = Context()
2623 for k in vars.keys():
2624 self.__context[k] = vars[k]
2625
2626
2627 request = qm.web.WebRequest("dir", base=request)
2628 raise qm.web.HttpRedirect, request
2629
2630
2631 - def HandleSubmitContextFile(self, request):
2632 """Handle a context file submission..
2633
2634 'request' -- The 'WebRequest' that caused the event."""
2635
2636
2637 data = request["file"]
2638
2639 file = StringIO.StringIO(data)
2640
2641 assignments = qm.common.read_assignments(file)
2642
2643 for (name, value) in assignments.items():
2644 try:
2645 self.__context[name] = value
2646 except ValueError:
2647
2648 pass
2649
2650 return self._ClosePopupAndRedirect("dir")
2651
2652
2654 """Handle setting a single expectation.
2655
2656 'request' -- The 'WebRequest' that caused the event."""
2657
2658 id = request["id"]
2659 outcome = request["outcome"]
2660 self.__expected_outcomes[id] = outcome
2661
2662 return self._ClosePopupAndRedirect(request["url"])
2663
2664
2684
2685
2704
2705
2707 """Handle a test or resource submission.
2708
2709 This function handles submission of the test or resource editing form
2710 generated by 'handle_show'. The script name in 'request' should be
2711 'submit-test' or 'submit-resource'. It constructs the appropriate
2712 'Test' or 'Resource' object and writes it to the database, either as a
2713 new item or overwriting an existing item.
2714
2715 The request must have the following form fields:
2716
2717 'id' -- The test or resource ID of the item being edited or created.
2718
2719 'class' -- The name of the test or resource class of this item.
2720
2721 arguments -- Argument values are encoded in fields whose names start
2722 with 'qm.fields.Field.form_field_prefix'."""
2723
2724 if request.GetScriptName() == "submit-test":
2725 type = "test"
2726 elif request.GetScriptName() == "submit-resource":
2727 type = "resource"
2728
2729
2730 try:
2731 item_id = request["id"]
2732 except KeyError:
2733 message = qm.error("no id for submit")
2734 return qm.web.generate_error_page(request, message)
2735
2736 database = self.__database
2737
2738 is_new = int(request["is_new"])
2739
2740 item_class_name = request["class"]
2741 item_class = qm.test.base.get_extension_class(item_class_name,
2742 type,
2743 database)
2744 fields = get_class_arguments(item_class)
2745
2746
2747
2748 field_errors = {}
2749 redisplay = 0
2750
2751
2752
2753 arguments = {}
2754 temporary_store = self.GetTemporaryAttachmentStore()
2755 main_store = database.GetAttachmentStore()
2756 attachment_stores = { id(temporary_store): temporary_store,
2757 id(main_store): main_store }
2758 for field in fields:
2759
2760 field_name = field.GetName()
2761 form_field_name = field.GetHtmlFormFieldName()
2762
2763 try:
2764 value, r = field.ParseFormValue(request, form_field_name,
2765 attachment_stores)
2766 if r:
2767 redisplay = 1
2768 arguments[field_name] = value
2769 except:
2770
2771
2772 message = str(sys.exc_info()[1])
2773 field_errors[field_name] = message
2774 redisplay = 1
2775
2776 if type == "test":
2777
2778 item = TestDescriptor(
2779 database,
2780 test_id=item_id,
2781 test_class_name=item_class_name,
2782 arguments=arguments)
2783
2784 elif type == "resource":
2785
2786 item = ResourceDescriptor(database, item_id,
2787 item_class_name, arguments)
2788
2789
2790 if redisplay:
2791 request = qm.web.WebRequest("edit-" + type, base=request,
2792 id=item_id)
2793 return ShowItemPage(self, item, 1, is_new, type,
2794 field_errors)(request)
2795
2796
2797 database.WriteExtension(item_id, item.GetItem())
2798
2799
2800 request = qm.web.WebRequest("show-" + type, base=request, id=item_id)
2801 raise qm.web.HttpRedirect, request
2802
2803
2825
2826
2828 """Handle test suite submission.
2829
2830 'request' -- A 'WebRequest' object.
2831
2832 The request object has these fields:
2833
2834 'id' -- The ID of the test suite being edited. If a suite with
2835 this ID exists, it is replaced (it must not be an implicit suite
2836 though). Otherwise a new suite is edited.
2837
2838 'test_ids' -- A comma-separated list of test IDs to include in the
2839 suite, relative to the suite's own ID.
2840
2841 'suite_ids' -- A comma-separated list of other test suite IDs to
2842 include in the suite, relative to the suite's own ID.
2843 """
2844
2845 database = self.__database
2846
2847 suite_id = request["id"]
2848 test_ids = request["test_ids"]
2849 if string.strip(test_ids) == "":
2850 test_ids = []
2851 else:
2852 test_ids = string.split(test_ids, ",")
2853 suite_ids = request["suite_ids"]
2854 if string.strip(suite_ids) == "":
2855 suite_ids = []
2856 else:
2857 suite_ids = string.split(suite_ids, ",")
2858
2859 suite_class = qm.test.base.get_extension_class(
2860 "explicit_suite.ExplicitSuite",
2861 "suite",
2862 self.GetDatabase())
2863 extras = { suite_class.EXTRA_DATABASE : self.GetDatabase(),
2864 suite_class.EXTRA_ID : suite_id }
2865 suite = suite_class({ "test_ids" : test_ids,
2866 "suite_ids" : suite_ids },
2867 **extras)
2868
2869 database.WriteExtension(suite_id, suite)
2870
2871 raise qm.web.HttpRedirect, \
2872 qm.web.WebRequest("show-suite", base=request, id=suite_id)
2873
2874
2900
2901
2903 """Create a new resource with default arguments.
2904
2905 'resource_class_name' -- The name of the resource class of which to
2906 create a new resource.
2907
2908 'resource_id' -- The resource ID of the new resource.
2909
2910 returns -- A new 'ResourceDescriptor' object."""
2911
2912 resource_class \
2913 = qm.test.base.get_resource_class(resource_class_name,
2914 self.GetDatabase())
2915
2916 if self.GetDatabase().HasResource(resource_id):
2917 raise RuntimeError, qm.error("resource already exists",
2918 resource_id=resource_id)
2919
2920 arguments = {}
2921 for field in get_class_arguments(resource_class):
2922 name = field.GetName()
2923 value = field.GetDefaultValue()
2924 arguments[name] = value
2925
2926 return ResourceDescriptor(self.GetDatabase(), resource_id,
2927 resource_class_name, arguments)
2928
2929
2934
2935
2937 """Close the current window. Redirect the main window to 'url'.
2938
2939 'url' -- A string giving the URL to which the main window should
2940 be redirected.
2941
2942 returns -- A string giving HTML that will close the current
2943 window and redirect the main window to 'url'."""
2944
2945 return """<html><body><script language="JavaScript">
2946 window.opener.location = '%s';
2947 window.close();</script></body></html>""" % url
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963