OLD | NEW |
| (Empty) |
1 | |
2 from buildbot.status import tests | |
3 from buildbot.process.step import SUCCESS, FAILURE, BuildStep | |
4 from buildbot.process.step_twisted import RunUnitTests | |
5 | |
6 from zope.interface import implements | |
7 from twisted.python import log, failure | |
8 from twisted.spread import jelly | |
9 from twisted.pb.tokens import BananaError | |
10 from twisted.web.html import PRE | |
11 from twisted.web.error import NoResource | |
12 | |
13 class Null: pass | |
14 ResultTypes = Null() | |
15 ResultTypeNames = ["SKIP", | |
16 "EXPECTED_FAILURE", "FAILURE", "ERROR", | |
17 "UNEXPECTED_SUCCESS", "SUCCESS"] | |
18 try: | |
19 from twisted.trial import reporter # introduced in Twisted-1.0.5 | |
20 # extract the individual result types | |
21 for name in ResultTypeNames: | |
22 setattr(ResultTypes, name, getattr(reporter, name)) | |
23 except ImportError: | |
24 from twisted.trial import unittest # Twisted-1.0.4 has them here | |
25 for name in ResultTypeNames: | |
26 setattr(ResultTypes, name, getattr(unittest, name)) | |
27 | |
28 log._keepErrors = 0 | |
29 from twisted.trial import remote # for trial/jelly parsing | |
30 | |
31 import StringIO | |
32 | |
33 class OneJellyTest(tests.OneTest): | |
34 def html(self, request): | |
35 tpl = "<HTML><BODY>\n\n%s\n\n</body></html>\n" | |
36 pptpl = "<HTML><BODY>\n\n<pre>%s</pre>\n\n</body></html>\n" | |
37 t = request.postpath[0] # one of 'short', 'long' #, or 'html' | |
38 if isinstance(self.results, failure.Failure): | |
39 # it would be nice to remove unittest functions from the | |
40 # traceback like unittest.format_exception() does. | |
41 if t == 'short': | |
42 s = StringIO.StringIO() | |
43 self.results.printTraceback(s) | |
44 return pptpl % PRE(s.getvalue()) | |
45 elif t == 'long': | |
46 s = StringIO.StringIO() | |
47 self.results.printDetailedTraceback(s) | |
48 return pptpl % PRE(s.getvalue()) | |
49 #elif t == 'html': | |
50 # return tpl % formatFailure(self.results) | |
51 # ACK! source lines aren't stored in the Failure, rather, | |
52 # formatFailure pulls them (by filename) from the local | |
53 # disk. Feh. Even printTraceback() won't work. Double feh. | |
54 return NoResource("No such mode '%s'" % t) | |
55 if self.results == None: | |
56 return tpl % "No results to show: test probably passed." | |
57 # maybe results are plain text? | |
58 return pptpl % PRE(self.results) | |
59 | |
60 class TwistedJellyTestResults(tests.TestResults): | |
61 oneTestClass = OneJellyTest | |
62 def describeOneTest(self, testname): | |
63 return "%s: %s\n" % (testname, self.tests[testname][0]) | |
64 | |
65 class RunUnitTestsJelly(RunUnitTests): | |
66 """I run the unit tests with the --jelly option, which generates | |
67 machine-parseable results as the tests are run. | |
68 """ | |
69 trialMode = "--jelly" | |
70 implements(remote.IRemoteReporter) | |
71 | |
72 ourtypes = { ResultTypes.SKIP: tests.SKIP, | |
73 ResultTypes.EXPECTED_FAILURE: tests.EXPECTED_FAILURE, | |
74 ResultTypes.FAILURE: tests.FAILURE, | |
75 ResultTypes.ERROR: tests.ERROR, | |
76 ResultTypes.UNEXPECTED_SUCCESS: tests.UNEXPECTED_SUCCESS, | |
77 ResultTypes.SUCCESS: tests.SUCCESS, | |
78 } | |
79 | |
80 def start(self): | |
81 self.decoder = remote.DecodeReport(self) | |
82 # don't accept anything unpleasant from the (untrusted) build slave | |
83 # The jellied stream may have Failures, but everything inside should | |
84 # be a string | |
85 security = jelly.SecurityOptions() | |
86 security.allowBasicTypes() | |
87 security.allowInstancesOf(failure.Failure) | |
88 self.decoder.taster = security | |
89 self.results = TwistedJellyTestResults() | |
90 RunUnitTests.start(self) | |
91 | |
92 def logProgress(self, progress): | |
93 # XXX: track number of tests | |
94 BuildStep.logProgress(self, progress) | |
95 | |
96 def addStdout(self, data): | |
97 if not self.decoder: | |
98 return | |
99 try: | |
100 self.decoder.dataReceived(data) | |
101 except BananaError: | |
102 self.decoder = None | |
103 log.msg("trial --jelly output unparseable, traceback follows") | |
104 log.deferr() | |
105 | |
106 def remote_start(self, expectedTests, times=None): | |
107 print "remote_start", expectedTests | |
108 def remote_reportImportError(self, name, aFailure, times=None): | |
109 pass | |
110 def remote_reportStart(self, testClass, method, times=None): | |
111 print "reportStart", testClass, method | |
112 | |
113 def remote_reportResults(self, testClass, method, resultType, results, | |
114 times=None): | |
115 print "reportResults", testClass, method, resultType | |
116 which = testClass + "." + method | |
117 self.results.addTest(which, | |
118 self.ourtypes.get(resultType, tests.UNKNOWN), | |
119 results) | |
120 | |
121 def finished(self, rc): | |
122 # give self.results to our Build object | |
123 self.build.testsFinished(self.results) | |
124 total = self.results.countTests() | |
125 count = self.results.countFailures() | |
126 result = SUCCESS | |
127 if total == None: | |
128 result = (FAILURE, ['tests%s' % self.rtext(' (%s)')]) | |
129 if count: | |
130 result = (FAILURE, ["%d tes%s%s" % (count, | |
131 (count == 1 and 't' or 'ts'), | |
132 self.rtext(' (%s)'))]) | |
133 return self.stepComplete(result) | |
134 def finishStatus(self, result): | |
135 total = self.results.countTests() | |
136 count = self.results.countFailures() | |
137 text = [] | |
138 if count == 0: | |
139 text.extend(["%d %s" % \ | |
140 (total, | |
141 total == 1 and "test" or "tests"), | |
142 "passed"]) | |
143 else: | |
144 text.append("tests") | |
145 text.append("%d %s" % \ | |
146 (count, | |
147 count == 1 and "failure" or "failures")) | |
148 self.updateCurrentActivity(text=text) | |
149 self.addFileToCurrentActivity("tests", self.results) | |
150 #self.finishStatusSummary() | |
151 self.finishCurrentActivity() | |
152 | |
OLD | NEW |