OLD | NEW |
| (Empty) |
1 # -*- test-case-name: buildbot.test.test_status -*- | |
2 | |
3 import email, os | |
4 import operator | |
5 | |
6 from zope.interface import implements | |
7 from twisted.internet import defer, reactor | |
8 from twisted.trial import unittest | |
9 | |
10 from buildbot import interfaces | |
11 from buildbot.sourcestamp import SourceStamp | |
12 from buildbot.process.base import BuildRequest, Build | |
13 from buildbot.status import builder, base, words, progress | |
14 from buildbot.changes.changes import Change | |
15 from buildbot.process.builder import Builder | |
16 from time import sleep | |
17 | |
18 import sys | |
19 if sys.version_info[:3] < (2,4,0): | |
20 from sets import Set as set | |
21 | |
22 mail = None | |
23 try: | |
24 from buildbot.status import mail | |
25 except ImportError: | |
26 pass | |
27 from buildbot.status import progress, client # NEEDS COVERAGE | |
28 from buildbot.test.runutils import RunMixin, setupBuildStepStatus, rmtree | |
29 | |
30 class MyStep: | |
31 build = None | |
32 def getName(self): | |
33 return "step" | |
34 def getResults(self): | |
35 return (builder.SUCCESS, "yay") | |
36 | |
37 class MyLogFileProducer(builder.LogFileProducer): | |
38 # The reactor.callLater(0) in LogFileProducer.resumeProducing is a bit of | |
39 # a nuisance from a testing point of view. This subclass adds a Deferred | |
40 # to that call so we can find out when it is complete. | |
41 def resumeProducing(self): | |
42 d = defer.Deferred() | |
43 reactor.callLater(0, self._resumeProducing, d) | |
44 return d | |
45 def _resumeProducing(self, d): | |
46 builder.LogFileProducer._resumeProducing(self) | |
47 reactor.callLater(0, d.callback, None) | |
48 | |
49 class MyLog(builder.LogFile): | |
50 def __init__(self, basedir, name, text=None, step=None): | |
51 self.fakeBuilderBasedir = basedir | |
52 if not step: | |
53 step = MyStep() | |
54 builder.LogFile.__init__(self, step, name, name) | |
55 if text: | |
56 self.addStdout(text) | |
57 self.finish() | |
58 def getFilename(self): | |
59 return os.path.join(self.fakeBuilderBasedir, self.name) | |
60 | |
61 def subscribeConsumer(self, consumer): | |
62 p = MyLogFileProducer(self, consumer) | |
63 d = p.resumeProducing() | |
64 return d | |
65 | |
66 class MyHTMLLog(builder.HTMLLogFile): | |
67 def __init__(self, basedir, name, html): | |
68 step = MyStep() | |
69 builder.HTMLLogFile.__init__(self, step, name, name, html) | |
70 | |
71 class MyLogSubscriber: | |
72 def __init__(self): | |
73 self.chunks = [] | |
74 def logChunk(self, build, step, log, channel, text): | |
75 self.chunks.append((channel, text)) | |
76 | |
77 class MyLogConsumer: | |
78 def __init__(self, limit=None): | |
79 self.chunks = [] | |
80 self.finished = False | |
81 self.limit = limit | |
82 def registerProducer(self, producer, streaming): | |
83 self.producer = producer | |
84 self.streaming = streaming | |
85 def unregisterProducer(self): | |
86 self.producer = None | |
87 def writeChunk(self, chunk): | |
88 self.chunks.append(chunk) | |
89 if self.limit: | |
90 self.limit -= 1 | |
91 if self.limit == 0: | |
92 self.producer.pauseProducing() | |
93 def finish(self): | |
94 self.finished = True | |
95 | |
96 if mail: | |
97 class MyMailer(mail.MailNotifier): | |
98 def sendMessage(self, m, recipients): | |
99 self.parent.messages.append((m, recipients)) | |
100 | |
101 class MyStatus: | |
102 def getBuildbotURL(self): | |
103 return self.url | |
104 def getURLForThing(self, thing): | |
105 return None | |
106 def getProjectName(self): | |
107 return "myproj" | |
108 | |
109 class MyBuilder(builder.BuilderStatus): | |
110 nextBuildNumber = 0 | |
111 def newBuild(self): | |
112 number = self.nextBuildNumber | |
113 self.nextBuildNumber += 1 | |
114 s = MyBuild(self, number, None, False) | |
115 s.waitUntilFinished().addCallback(self._buildFinished) | |
116 return s | |
117 | |
118 class MyBuild(builder.BuildStatus): | |
119 testlogs = [] | |
120 def __init__(self, parent, number, results, finished=True): | |
121 builder.BuildStatus.__init__(self, parent, number) | |
122 self.results = results | |
123 self.source = SourceStamp(revision="1.14") | |
124 self.reason = "build triggered by changes" | |
125 self.finished = finished | |
126 def getLogs(self): | |
127 return self.testlogs | |
128 def buildStarted(self, build, started): | |
129 self.started = started | |
130 self.builder.buildStarted(self) | |
131 | |
132 class MyLookup: | |
133 implements(interfaces.IEmailLookup) | |
134 | |
135 def getAddress(self, user): | |
136 d = defer.Deferred() | |
137 # With me now is Mr Thomas Walters of West Hartlepool who is totally | |
138 # invisible. | |
139 if user == "Thomas_Walters": | |
140 d.callback(None) | |
141 else: | |
142 d.callback(user + "@" + "dev.com") | |
143 return d | |
144 | |
145 def customTextMailMessage(attrs): | |
146 logLines = 3 | |
147 text = list() | |
148 text.append("STATUS: %s" % attrs['result'].title()) | |
149 text.append("") | |
150 text.extend([c.asText() for c in attrs['changes']]) | |
151 text.append("") | |
152 name, url, lines, status = attrs['logs'][-1] | |
153 text.append("Last %d lines of '%s':" % (logLines, name)) | |
154 text.extend(["\t%s\n" % line for line in lines[len(lines)-logLines:]]) | |
155 text.append("") | |
156 text.append("Build number was: %s" % attrs['buildProperties']['buildnumber']
) | |
157 text.append("") | |
158 text.append("-buildbot") | |
159 return ("\n".join(text), 'plain') | |
160 | |
161 def customHTMLMailMessage(attrs): | |
162 logLines = 3 | |
163 text = list() | |
164 text.append("<h3>STATUS <a href='%s'>%s</a>:</h3>" % (attrs['buildURL'], | |
165 attrs['result'].title(
))) | |
166 text.append("<h4>Recent Changes:</h4>") | |
167 text.extend([c.asHTML() for c in attrs['changes']]) | |
168 name, url, lines, status = attrs['logs'][-1] | |
169 text.append("<h4>Last %d lines of '%s':</h4>" % (logLines, name)) | |
170 text.append("<p>") | |
171 text.append("<br>".join([line for line in lines[len(lines)-logLines:]])) | |
172 text.append("</p>") | |
173 text.append("<p>Build number was: %s</p>" % attrs['buildProperties']['buildn
umber']) | |
174 text.append("<br>") | |
175 text.append("<b>-<a href='%s'>buildbot</a></b>" % attrs['buildbotURL']) | |
176 return ("\n".join(text), 'html') | |
177 | |
178 class Mail(unittest.TestCase): | |
179 | |
180 def setUp(self): | |
181 self.builder = MyBuilder("builder1") | |
182 | |
183 def stall(self, res, timeout): | |
184 d = defer.Deferred() | |
185 reactor.callLater(timeout, d.callback, res) | |
186 return d | |
187 | |
188 def makeBuild(self, number, results): | |
189 return MyBuild(self.builder, number, results) | |
190 | |
191 def failUnlessIn(self, substring, string): | |
192 self.failUnless(string.find(substring) != -1, | |
193 "didn't see '%s' in '%s'" % (substring, string)) | |
194 | |
195 def getProjectName(self): | |
196 return "PROJECT" | |
197 | |
198 def getBuildbotURL(self): | |
199 return "BUILDBOT_URL" | |
200 | |
201 def getURLForThing(self, thing): | |
202 return None | |
203 | |
204 def testBuild1(self): | |
205 mailer = MyMailer(fromaddr="buildbot@example.com", | |
206 extraRecipients=["recip@example.com", | |
207 "recip2@example.com"], | |
208 lookup=mail.Domain("dev.com")) | |
209 mailer.parent = self | |
210 mailer.master_status = self | |
211 self.messages = [] | |
212 | |
213 b1 = self.makeBuild(3, builder.SUCCESS) | |
214 b1.blamelist = ["bob"] | |
215 | |
216 mailer.buildFinished("builder1", b1, b1.results) | |
217 self.failUnless(len(self.messages) == 1) | |
218 m,r = self.messages.pop() | |
219 t = m.as_string() | |
220 self.failUnlessIn("To: bob@dev.com\n", t) | |
221 self.failUnlessIn("CC: recip2@example.com, recip@example.com\n", t) | |
222 self.failUnlessIn("From: buildbot@example.com\n", t) | |
223 self.failUnlessIn("Subject: buildbot success in PROJECT on builder1\n",
t) | |
224 self.failUnlessIn("Date: ", t) | |
225 self.failUnlessIn("Build succeeded!\n", t) | |
226 self.failUnlessIn("Buildbot URL: BUILDBOT_URL\n", t) | |
227 | |
228 def testBuild2(self): | |
229 mailer = MyMailer(fromaddr="buildbot@example.com", | |
230 extraRecipients=["recip@example.com", | |
231 "recip2@example.com"], | |
232 lookup="dev.com", | |
233 sendToInterestedUsers=False) | |
234 mailer.parent = self | |
235 mailer.master_status = self | |
236 self.messages = [] | |
237 | |
238 b1 = self.makeBuild(3, builder.SUCCESS) | |
239 b1.blamelist = ["bob"] | |
240 | |
241 mailer.buildFinished("builder1", b1, b1.results) | |
242 self.failUnless(len(self.messages) == 1) | |
243 m,r = self.messages.pop() | |
244 t = m.as_string() | |
245 self.failUnlessIn("To: recip2@example.com, " | |
246 "recip@example.com\n", t) | |
247 self.failUnlessIn("From: buildbot@example.com\n", t) | |
248 self.failUnlessIn("Subject: buildbot success in PROJECT on builder1\n",
t) | |
249 self.failUnlessIn("Build succeeded!\n", t) | |
250 self.failUnlessIn("Buildbot URL: BUILDBOT_URL\n", t) | |
251 | |
252 def testBuildStatusCategory(self): | |
253 # a status client only interested in a category should only receive | |
254 # from that category | |
255 mailer = MyMailer(fromaddr="buildbot@example.com", | |
256 extraRecipients=["recip@example.com", | |
257 "recip2@example.com"], | |
258 lookup="dev.com", | |
259 sendToInterestedUsers=False, | |
260 categories=["debug"]) | |
261 | |
262 mailer.parent = self | |
263 mailer.master_status = self | |
264 self.messages = [] | |
265 | |
266 b1 = self.makeBuild(3, builder.SUCCESS) | |
267 b1.blamelist = ["bob"] | |
268 | |
269 mailer.buildFinished("builder1", b1, b1.results) | |
270 self.failIf(self.messages) | |
271 | |
272 def testBuilderCategory(self): | |
273 # a builder in a certain category should notify status clients that | |
274 # did not list categories, or categories including this one | |
275 mailer1 = MyMailer(fromaddr="buildbot@example.com", | |
276 extraRecipients=["recip@example.com", | |
277 "recip2@example.com"], | |
278 lookup="dev.com", | |
279 sendToInterestedUsers=False) | |
280 mailer2 = MyMailer(fromaddr="buildbot@example.com", | |
281 extraRecipients=["recip@example.com", | |
282 "recip2@example.com"], | |
283 lookup="dev.com", | |
284 sendToInterestedUsers=False, | |
285 categories=["active"]) | |
286 mailer3 = MyMailer(fromaddr="buildbot@example.com", | |
287 extraRecipients=["recip@example.com", | |
288 "recip2@example.com"], | |
289 lookup="dev.com", | |
290 sendToInterestedUsers=False, | |
291 categories=["active", "debug"]) | |
292 | |
293 builderd = MyBuilder("builder2", "debug") | |
294 | |
295 mailer1.parent = self | |
296 mailer1.master_status = self | |
297 mailer2.parent = self | |
298 mailer2.master_status = self | |
299 mailer3.parent = self | |
300 mailer3.master_status = self | |
301 self.messages = [] | |
302 | |
303 t = mailer1.builderAdded("builder2", builderd) | |
304 self.assertEqual(len(mailer1.watched), 1) | |
305 self.assertEqual(t, mailer1) | |
306 t = mailer2.builderAdded("builder2", builderd) | |
307 self.assertEqual(len(mailer2.watched), 0) | |
308 self.assertEqual(t, None) | |
309 t = mailer3.builderAdded("builder2", builderd) | |
310 self.assertEqual(len(mailer3.watched), 1) | |
311 self.assertEqual(t, mailer3) | |
312 | |
313 b2 = MyBuild(builderd, 3, builder.SUCCESS) | |
314 b2.blamelist = ["bob"] | |
315 | |
316 mailer1.buildFinished("builder2", b2, b2.results) | |
317 self.failUnlessEqual(len(self.messages), 1) | |
318 self.messages = [] | |
319 mailer2.buildFinished("builder2", b2, b2.results) | |
320 self.failUnlessEqual(len(self.messages), 0) | |
321 self.messages = [] | |
322 mailer3.buildFinished("builder2", b2, b2.results) | |
323 self.failUnlessEqual(len(self.messages), 1) | |
324 | |
325 def testCustomTextMessage(self): | |
326 basedir = "test_custom_text_mesg" | |
327 os.mkdir(basedir) | |
328 mailer = MyMailer(fromaddr="buildbot@example.com", mode="problem", | |
329 extraRecipients=["recip@example.com", | |
330 "recip2@example.com"], | |
331 lookup=MyLookup(), | |
332 customMesg=customTextMailMessage) | |
333 mailer.parent = self | |
334 mailer.master_status = self | |
335 self.messages = [] | |
336 | |
337 b1 = self.makeBuild(4, builder.FAILURE) | |
338 b1.setProperty('buildnumber', 1, 'Build') | |
339 b1.setText(["snarkleack", "polarization", "failed"]) | |
340 b1.blamelist = ["dev3", "dev3", "dev3", "dev4", | |
341 "Thomas_Walters"] | |
342 b1.source.changes = (Change(who = 'author1', files = ['file1'], comments
= 'comment1', revision = 123), | |
343 Change(who = 'author2', files = ['file2'], comments
= 'comment2', revision = 456)) | |
344 b1.testlogs = [MyLog(basedir, 'compile', "Compile log here\n"), | |
345 MyLog(basedir, 'test', "Test log here\nTest 1 failed\nTes
t 2 failed\nTest 3 failed\nTest 4 failed\n")] | |
346 | |
347 mailer.buildFinished("builder1", b1, b1.results) | |
348 m,r = self.messages.pop() | |
349 t = m.as_string() | |
350 # | |
351 # Uncomment to review custom message | |
352 # | |
353 #self.fail(t) | |
354 self.failUnlessIn("comment1", t) | |
355 self.failUnlessIn("comment2", t) | |
356 self.failUnlessIn("Test 4 failed", t) | |
357 self.failUnlessIn("number was: 1", t) | |
358 | |
359 | |
360 def testCustomHTMLMessage(self): | |
361 basedir = "test_custom_HTML_mesg" | |
362 os.mkdir(basedir) | |
363 mailer = MyMailer(fromaddr="buildbot@example.com", mode="problem", | |
364 extraRecipients=["recip@example.com", | |
365 "recip2@example.com"], | |
366 lookup=MyLookup(), | |
367 customMesg=customHTMLMailMessage) | |
368 mailer.parent = self | |
369 mailer.master_status = self | |
370 self.messages = [] | |
371 | |
372 b1 = self.makeBuild(4, builder.FAILURE) | |
373 b1.setProperty('buildnumber', 1, 'Build') | |
374 b1.setText(["snarkleack", "polarization", "failed"]) | |
375 b1.blamelist = ["dev3", "dev3", "dev3", "dev4", | |
376 "Thomas_Walters"] | |
377 b1.source.changes = (Change(who = 'author1', files = ['file1'], comments
= 'comment1', revision = 123), | |
378 Change(who = 'author2', files = ['file2'], comments
= 'comment2', revision = 456)) | |
379 b1.testlogs = [MyLog(basedir, 'compile', "Compile log here\n"), | |
380 MyLog(basedir, 'test', "Test log here\nTest 1 failed\nTes
t 2 failed\nTest 3 failed\nTest 4 failed\n")] | |
381 | |
382 mailer.buildFinished("builder1", b1, b1.results) | |
383 m,r = self.messages.pop() | |
384 t = m.as_string() | |
385 # | |
386 # Uncomment to review custom message | |
387 # | |
388 #self.fail(t) | |
389 self.failUnlessIn("<h4>Last 3 lines of 'step.test':</h4>", t) | |
390 self.failUnlessIn("<p>Changed by: <b>author2</b><br />", t) | |
391 self.failUnlessIn("Test 3 failed", t) | |
392 self.failUnlessIn("number was: 1", t) | |
393 | |
394 def testShouldAttachLog(self): | |
395 mailer = mail.MailNotifier(fromaddr="buildbot@example.com", addLogs=True
) | |
396 self.assertTrue(mailer._shouldAttachLog('anything')) | |
397 mailer = mail.MailNotifier(fromaddr="buildbot@example.com", addLogs=Fals
e) | |
398 self.assertFalse(mailer._shouldAttachLog('anything')) | |
399 mailer = mail.MailNotifier(fromaddr="buildbot@example.com", addLogs=['so
mething']) | |
400 self.assertFalse(mailer._shouldAttachLog('anything')) | |
401 self.assertTrue(mailer._shouldAttachLog('something')) | |
402 | |
403 def testShouldAttachPatches(self): | |
404 basedir = "test_should_attach_patches" | |
405 os.mkdir(basedir) | |
406 b1 = self.makeBuild(4, builder.FAILURE) | |
407 b1.setProperty('buildnumber', 1, 'Build') | |
408 b1.setText(["snarkleack", "polarization", "failed"]) | |
409 b1.blamelist = ["dev3", "dev3", "dev3", "dev4", | |
410 "Thomas_Walters"] | |
411 b1.source.changes = (Change(who = 'author1', files = ['file1'], comments
= 'comment1', revision = 123), | |
412 Change(who = 'author2', files = ['file2'], comments
= 'comment2', revision = 456)) | |
413 b1.testlogs = [MyLog(basedir, 'compile', "Compile log here\n"), | |
414 MyLog(basedir, 'test', "Test log here\nTest 1 failed\nTes
t 2 failed\nTest 3 failed\nTest 4 failed\n")] | |
415 b1.source.patch = (0, '--- /dev/null\n+++ a_file\n', None) | |
416 | |
417 mailer = MyMailer(fromaddr="buildbot@example.com", addPatch=True) | |
418 mailer.parent = self | |
419 mailer.master_status = self | |
420 self.messages = [] | |
421 mailer.buildFinished("builder1", b1, b1.results) | |
422 m,r = self.messages.pop() | |
423 self.assertTrue(m.is_multipart()) | |
424 self.assertEqual(len([True for i in m.walk()]), 3) | |
425 | |
426 mailer = MyMailer(fromaddr="buildbot@example.com", addPatch=False) | |
427 mailer.parent = self | |
428 mailer.master_status = self | |
429 self.messages = [] | |
430 mailer.buildFinished("builder1", b1, b1.results) | |
431 m,r = self.messages.pop() | |
432 self.assertFalse(m.is_multipart()) | |
433 self.assertEqual(len([True for i in m.walk()]), 1) | |
434 | |
435 mailer = MyMailer(fromaddr="buildbot@example.com") | |
436 mailer.parent = self | |
437 mailer.master_status = self | |
438 self.messages = [] | |
439 mailer.buildFinished("builder1", b1, b1.results) | |
440 m,r = self.messages.pop() | |
441 self.assertTrue(m.is_multipart()) | |
442 self.assertEqual(len([True for i in m.walk()]), 3) | |
443 | |
444 | |
445 def testFailure(self): | |
446 mailer = MyMailer(fromaddr="buildbot@example.com", mode="problem", | |
447 extraRecipients=["recip@example.com", | |
448 "recip2@example.com"], | |
449 lookup=MyLookup()) | |
450 mailer.parent = self | |
451 mailer.master_status = self | |
452 self.messages = [] | |
453 | |
454 b1 = self.makeBuild(3, builder.SUCCESS) | |
455 b1.blamelist = ["dev1", "dev2"] | |
456 b2 = self.makeBuild(4, builder.FAILURE) | |
457 b2.setText(["snarkleack", "polarization", "failed"]) | |
458 b2.blamelist = ["dev3", "dev3", "dev3", "dev4", | |
459 "Thomas_Walters"] | |
460 mailer.buildFinished("builder1", b1, b1.results) | |
461 self.failIf(self.messages) | |
462 mailer.buildFinished("builder1", b2, b2.results) | |
463 self.failUnless(len(self.messages) == 1) | |
464 m,r = self.messages.pop() | |
465 t = m.as_string() | |
466 self.failUnlessIn("To: dev3@dev.com, dev4@dev.com\n", t) | |
467 self.failUnlessIn("CC: recip2@example.com, recip@example.com\n", t) | |
468 self.failUnlessIn("From: buildbot@example.com\n", t) | |
469 self.failUnlessIn("Subject: buildbot failure in PROJECT on builder1\n",
t) | |
470 self.failUnlessIn("The Buildbot has detected a new failure", t) | |
471 self.failUnlessIn("BUILD FAILED: snarkleack polarization failed\n", t) | |
472 self.failUnlessEqual(set(r), set(["dev3@dev.com", "dev4@dev.com", | |
473 "recip2@example.com", "recip@example.com"])) | |
474 | |
475 | |
476 def testChange(self): | |
477 raise unittest.SkipTest("TODO: Fix Build/Builder mock objects to support
getPrevBuild()") | |
478 | |
479 mailer = MyMailer(fromaddr="buildbot@example.com", mode="change", | |
480 extraRecipients=["bah@bah.bah"], | |
481 lookup=MyLookup()) | |
482 mailer.parent = self | |
483 mailer.master_status = self | |
484 self.messages = [] | |
485 | |
486 b1 = self.makeBuild(1, builder.SUCCESS) | |
487 b2 = self.makeBuild(2, builder.SUCCESS) | |
488 b3 = self.makeBuild(3, builder.FAILURE) | |
489 b4 = self.makeBuild(4, builder.FAILURE) | |
490 b5 = self.makeBuild(5, builder.SUCCESS) | |
491 b6 = self.makeBuild(6, builder.SUCCESS) | |
492 | |
493 # no message on first or repetetive success | |
494 mailer.buildFinished("builder1", b1, b1.results) | |
495 self.failIf(self.messages) | |
496 mailer.buildFinished("builder1", b2, b2.results) | |
497 self.failIf(self.messages) | |
498 | |
499 # message on first fail only | |
500 mailer.buildFinished("builder1", b3, b3.results) | |
501 self.failUnless(len(self.messages) == 1) | |
502 self.messages.pop() | |
503 mailer.buildFinished("builder1", b4, b4.results) | |
504 self.failIf(self.messages) | |
505 | |
506 # message on first following success | |
507 mailer.buildFinished("builder1", b5, b5.results) | |
508 self.failUnless(len(self.messages) == 1) | |
509 self.messages.pop() | |
510 mailer.buildFinished("builder1", b6, b6.results) | |
511 self.failIf(self.messages) | |
512 | |
513 | |
514 def testLogs(self): | |
515 basedir = "test_status_logs" | |
516 os.mkdir(basedir) | |
517 mailer = MyMailer(fromaddr="buildbot@example.com", addLogs=True, | |
518 extraRecipients=["recip@example.com", | |
519 "recip2@example.com"]) | |
520 mailer.parent = self | |
521 mailer.master_status = self | |
522 self.messages = [] | |
523 | |
524 b1 = self.makeBuild(3, builder.WARNINGS) | |
525 b1.testlogs = [MyLog(basedir, 'compile', "Compile log here\n"), | |
526 MyLog(basedir, | |
527 'test', "Test log here\nTest 4 failed\n"), | |
528 ] | |
529 b1.text = ["unusual", "gnarzzler", "output"] | |
530 mailer.buildFinished("builder1", b1, b1.results) | |
531 self.failUnless(len(self.messages) == 1) | |
532 m,r = self.messages.pop() | |
533 t = m.as_string() | |
534 self.failUnlessIn("Subject: buildbot warnings in PROJECT on builder1\n",
t) | |
535 m2 = email.message_from_string(t) | |
536 p = m2.get_payload() | |
537 self.failUnlessEqual(len(p), 3) | |
538 | |
539 self.failUnlessIn("Build Had Warnings: unusual gnarzzler output\n", | |
540 p[0].get_payload()) | |
541 | |
542 self.failUnlessEqual(p[1].get_filename(), "step.compile") | |
543 self.failUnlessEqual(p[1].get_payload(), "Compile log here\n") | |
544 | |
545 self.failUnlessEqual(p[2].get_filename(), "step.test") | |
546 self.failUnlessIn("Test log here\n", p[2].get_payload()) | |
547 | |
548 def testMail(self): | |
549 basedir = "test_status_mail" | |
550 os.mkdir(basedir) | |
551 dest = os.environ.get("BUILDBOT_TEST_MAIL") | |
552 if not dest: | |
553 raise unittest.SkipTest("define BUILDBOT_TEST_MAIL=dest to run this"
) | |
554 mailer = mail.MailNotifier(fromaddr="buildbot@example.com", | |
555 addLogs=True, | |
556 extraRecipients=[dest]) | |
557 s = MyStatus() | |
558 s.url = "project URL" | |
559 mailer.master_status = s | |
560 | |
561 b1 = self.makeBuild(3, builder.SUCCESS) | |
562 b1.testlogs = [MyLog(basedir, 'compile', "Compile log here\n"), | |
563 MyLog(basedir, | |
564 'test', "Test log here\nTest 4 failed\n"), | |
565 ] | |
566 | |
567 d = mailer.buildFinished("builder1", b1, b1.results) | |
568 # When this fires, the mail has been sent, but the SMTP connection is | |
569 # still up (because smtp.sendmail relies upon the server to hang up). | |
570 # Spin for a moment to avoid the "unclean reactor" warning that Trial | |
571 # gives us if we finish before the socket is disconnected. Really, | |
572 # sendmail() ought to hang up the connection once it is finished: | |
573 # otherwise a malicious SMTP server could make us consume lots of | |
574 # memory. | |
575 d.addCallback(self.stall, 0.1) | |
576 return d | |
577 | |
578 if not mail: | |
579 Mail.skip = "the Twisted Mail package is not installed" | |
580 | |
581 class Progress(unittest.TestCase): | |
582 def testWavg(self): | |
583 bp = progress.BuildProgress([]) | |
584 e = progress.Expectations(bp) | |
585 # wavg(old, current) | |
586 self.failUnlessEqual(e.wavg(None, None), None) | |
587 self.failUnlessEqual(e.wavg(None, 3), 3) | |
588 self.failUnlessEqual(e.wavg(3, None), 3) | |
589 self.failUnlessEqual(e.wavg(3, 4), 3.5) | |
590 e.decay = 0.1 | |
591 self.failUnlessEqual(e.wavg(3, 4), 3.1) | |
592 | |
593 | |
594 class Results(unittest.TestCase): | |
595 | |
596 def testAddResults(self): | |
597 b = builder.BuildStatus(builder.BuilderStatus("test"), 12) | |
598 testname = ("buildbot", "test", "test_status", "Results", | |
599 "testAddResults") | |
600 r1 = builder.TestResult(name=testname, | |
601 results=builder.SUCCESS, | |
602 text=["passed"], | |
603 logs={'output': ""}, | |
604 ) | |
605 b.addTestResult(r1) | |
606 | |
607 res = b.getTestResults() | |
608 self.failUnlessEqual(res.keys(), [testname]) | |
609 t = res[testname] | |
610 self.failUnless(interfaces.ITestResult.providedBy(t)) | |
611 self.failUnlessEqual(t.getName(), testname) | |
612 self.failUnlessEqual(t.getResults(), builder.SUCCESS) | |
613 self.failUnlessEqual(t.getText(), ["passed"]) | |
614 self.failUnlessEqual(t.getLogs(), {'output': ""}) | |
615 | |
616 class Log(unittest.TestCase): | |
617 basedir = "status_log_add" | |
618 | |
619 def setUp(self): | |
620 self.tearDown() | |
621 os.mkdir(self.basedir) | |
622 | |
623 def tearDown(self): | |
624 if os.path.exists(self.basedir): | |
625 rmtree(self.basedir) | |
626 | |
627 def testAdd(self): | |
628 l = MyLog(self.basedir, "compile", step=13) | |
629 self.failUnlessEqual(l.getName(), "compile") | |
630 self.failUnlessEqual(l.getStep(), 13) | |
631 l.addHeader("HEADER\n") | |
632 l.addStdout("Some text\n") | |
633 l.addStderr("Some error\n") | |
634 l.addStdout("Some more text\n") | |
635 self.failIf(l.isFinished()) | |
636 l.finish() | |
637 self.failUnless(l.isFinished()) | |
638 self.failUnlessEqual(l.getText(), | |
639 "Some text\nSome error\nSome more text\n") | |
640 self.failUnlessEqual(l.getTextWithHeaders(), | |
641 "HEADER\n" + | |
642 "Some text\nSome error\nSome more text\n") | |
643 self.failUnlessEqual(len(list(l.getChunks())), 4) | |
644 | |
645 self.failUnless(l.hasContents()) | |
646 try: | |
647 os.unlink(l.getFilename()) | |
648 except OSError: | |
649 os.unlink(l.getFilename() + ".bz2") | |
650 self.failIf(l.hasContents()) | |
651 | |
652 def TODO_testDuplicate(self): | |
653 # create multiple logs for the same step with the same logname, make | |
654 # sure their on-disk filenames are suitably uniquified. This | |
655 # functionality actually lives in BuildStepStatus and BuildStatus, so | |
656 # this test must involve more than just the MyLog class. | |
657 | |
658 # naieve approach, doesn't work | |
659 l1 = MyLog(self.basedir, "duplicate") | |
660 l1.addStdout("Some text\n") | |
661 l1.finish() | |
662 l2 = MyLog(self.basedir, "duplicate") | |
663 l2.addStdout("Some more text\n") | |
664 l2.finish() | |
665 self.failIfEqual(l1.getFilename(), l2.getFilename()) | |
666 | |
667 def testMerge1(self): | |
668 l = MyLog(self.basedir, "merge1") | |
669 l.addHeader("HEADER\n") | |
670 l.addStdout("Some text\n") | |
671 l.addStdout("Some more text\n") | |
672 l.addStdout("more\n") | |
673 l.finish() | |
674 self.failUnlessEqual(l.getText(), | |
675 "Some text\nSome more text\nmore\n") | |
676 self.failUnlessEqual(l.getTextWithHeaders(), | |
677 "HEADER\n" + | |
678 "Some text\nSome more text\nmore\n") | |
679 self.failUnlessEqual(len(list(l.getChunks())), 2) | |
680 | |
681 def testMerge2(self): | |
682 l = MyLog(self.basedir, "merge2") | |
683 l.addHeader("HEADER\n") | |
684 for i in xrange(1000): | |
685 l.addStdout("aaaa") | |
686 for i in xrange(30): | |
687 l.addStderr("bbbb") | |
688 for i in xrange(10): | |
689 l.addStdout("cc") | |
690 target = 1000*"aaaa" + 30 * "bbbb" + 10 * "cc" | |
691 self.failUnlessEqual(len(l.getText()), len(target)) | |
692 self.failUnlessEqual(l.getText(), target) | |
693 l.finish() | |
694 self.failUnlessEqual(len(l.getText()), len(target)) | |
695 self.failUnlessEqual(l.getText(), target) | |
696 self.failUnlessEqual(len(list(l.getChunks())), 4) | |
697 | |
698 def testMerge3(self): | |
699 l = MyLog(self.basedir, "merge3") | |
700 l.chunkSize = 100 | |
701 l.addHeader("HEADER\n") | |
702 for i in xrange(8): | |
703 l.addStdout(10*"a") | |
704 for i in xrange(8): | |
705 l.addStdout(10*"a") | |
706 self.failUnlessEqual(list(l.getChunks()), | |
707 [(builder.HEADER, "HEADER\n"), | |
708 (builder.STDOUT, 100*"a"), | |
709 (builder.STDOUT, 60*"a")]) | |
710 l.finish() | |
711 self.failUnlessEqual(l.getText(), 160*"a") | |
712 | |
713 def testReadlines(self): | |
714 l = MyLog(self.basedir, "chunks1") | |
715 l.addHeader("HEADER\n") # should be ignored | |
716 l.addStdout("Some text\n") | |
717 l.addStdout("Some More Text\nAnd Some More\n") | |
718 l.addStderr("Some Stderr\n") | |
719 l.addStdout("Last line\n") | |
720 l.finish() | |
721 alllines = list(l.readlines()) | |
722 self.failUnlessEqual(len(alllines), 4) | |
723 self.failUnlessEqual(alllines[0], "Some text\n") | |
724 self.failUnlessEqual(alllines[2], "And Some More\n") | |
725 self.failUnlessEqual(alllines[3], "Last line\n") | |
726 stderr = list(l.readlines(interfaces.LOG_CHANNEL_STDERR)) | |
727 self.failUnlessEqual(len(stderr), 1) | |
728 self.failUnlessEqual(stderr[0], "Some Stderr\n") | |
729 lines = l.readlines() | |
730 if False: # TODO: l.readlines() is not yet an iterator | |
731 # verify that it really is an iterator | |
732 line0 = lines.next() | |
733 self.failUnlessEqual(line0, "Some text\n") | |
734 line1 = lines.next() | |
735 line2 = lines.next() | |
736 self.failUnlessEqual(line2, "And Some More\n") | |
737 | |
738 | |
739 def testChunks(self): | |
740 l = MyLog(self.basedir, "chunks2") | |
741 c1 = l.getChunks() | |
742 l.addHeader("HEADER\n") | |
743 l.addStdout("Some text\n") | |
744 self.failUnlessEqual("".join(l.getChunks(onlyText=True)), | |
745 "HEADER\nSome text\n") | |
746 c2 = l.getChunks() | |
747 | |
748 l.addStdout("Some more text\n") | |
749 self.failUnlessEqual("".join(l.getChunks(onlyText=True)), | |
750 "HEADER\nSome text\nSome more text\n") | |
751 c3 = l.getChunks() | |
752 | |
753 l.addStdout("more\n") | |
754 l.finish() | |
755 | |
756 self.failUnlessEqual(list(c1), []) | |
757 self.failUnlessEqual(list(c2), [(builder.HEADER, "HEADER\n"), | |
758 (builder.STDOUT, "Some text\n")]) | |
759 self.failUnlessEqual(list(c3), [(builder.HEADER, "HEADER\n"), | |
760 (builder.STDOUT, | |
761 "Some text\nSome more text\n")]) | |
762 | |
763 self.failUnlessEqual(l.getText(), | |
764 "Some text\nSome more text\nmore\n") | |
765 self.failUnlessEqual(l.getTextWithHeaders(), | |
766 "HEADER\n" + | |
767 "Some text\nSome more text\nmore\n") | |
768 self.failUnlessEqual(len(list(l.getChunks())), 2) | |
769 | |
770 def testUpgrade(self): | |
771 l = MyLog(self.basedir, "upgrade") | |
772 l.addHeader("HEADER\n") | |
773 l.addStdout("Some text\n") | |
774 l.addStdout("Some more text\n") | |
775 l.addStdout("more\n") | |
776 l.finish() | |
777 self.failUnless(l.hasContents()) | |
778 # now doctor it to look like a 0.6.4-era non-upgraded logfile | |
779 l.entries = list(l.getChunks()) | |
780 del l.filename | |
781 try: | |
782 os.unlink(l.getFilename() + ".bz2") | |
783 except OSError: | |
784 os.unlink(l.getFilename()) | |
785 # now make sure we can upgrade it | |
786 l.upgrade("upgrade") | |
787 self.failUnlessEqual(l.getText(), | |
788 "Some text\nSome more text\nmore\n") | |
789 self.failUnlessEqual(len(list(l.getChunks())), 2) | |
790 self.failIf(l.entries) | |
791 | |
792 # now, do it again, but make it look like an upgraded 0.6.4 logfile | |
793 # (i.e. l.filename is missing, but the contents are there on disk) | |
794 l.entries = list(l.getChunks()) | |
795 del l.filename | |
796 l.upgrade("upgrade") | |
797 self.failUnlessEqual(l.getText(), | |
798 "Some text\nSome more text\nmore\n") | |
799 self.failUnlessEqual(len(list(l.getChunks())), 2) | |
800 self.failIf(l.entries) | |
801 self.failUnless(l.hasContents()) | |
802 | |
803 def testHTMLUpgrade(self): | |
804 l = MyHTMLLog(self.basedir, "upgrade", "log contents") | |
805 l.upgrade("filename") | |
806 | |
807 def testSubscribe(self): | |
808 l1 = MyLog(self.basedir, "subscribe1") | |
809 l1.finish() | |
810 self.failUnless(l1.isFinished()) | |
811 | |
812 s = MyLogSubscriber() | |
813 l1.subscribe(s, True) | |
814 l1.unsubscribe(s) | |
815 self.failIf(s.chunks) | |
816 | |
817 s = MyLogSubscriber() | |
818 l1.subscribe(s, False) | |
819 l1.unsubscribe(s) | |
820 self.failIf(s.chunks) | |
821 | |
822 finished = [] | |
823 l2 = MyLog(self.basedir, "subscribe2") | |
824 l2.waitUntilFinished().addCallback(finished.append) | |
825 l2.addHeader("HEADER\n") | |
826 s1 = MyLogSubscriber() | |
827 l2.subscribe(s1, True) | |
828 s2 = MyLogSubscriber() | |
829 l2.subscribe(s2, False) | |
830 self.failUnlessEqual(s1.chunks, [(builder.HEADER, "HEADER\n")]) | |
831 self.failUnlessEqual(s2.chunks, []) | |
832 | |
833 l2.addStdout("Some text\n") | |
834 self.failUnlessEqual(s1.chunks, [(builder.HEADER, "HEADER\n"), | |
835 (builder.STDOUT, "Some text\n")]) | |
836 self.failUnlessEqual(s2.chunks, [(builder.STDOUT, "Some text\n")]) | |
837 l2.unsubscribe(s1) | |
838 | |
839 l2.addStdout("Some more text\n") | |
840 self.failUnlessEqual(s1.chunks, [(builder.HEADER, "HEADER\n"), | |
841 (builder.STDOUT, "Some text\n")]) | |
842 self.failUnlessEqual(s2.chunks, [(builder.STDOUT, "Some text\n"), | |
843 (builder.STDOUT, "Some more text\n"), | |
844 ]) | |
845 self.failIf(finished) | |
846 l2.finish() | |
847 self.failUnlessEqual(finished, [l2]) | |
848 | |
849 def testConsumer(self): | |
850 l1 = MyLog(self.basedir, "consumer1") | |
851 l1.finish() | |
852 self.failUnless(l1.isFinished()) | |
853 | |
854 s = MyLogConsumer() | |
855 d = l1.subscribeConsumer(s) | |
856 d.addCallback(self._testConsumer_1, s) | |
857 return d | |
858 testConsumer.timeout = 5 | |
859 def _testConsumer_1(self, res, s): | |
860 self.failIf(s.chunks) | |
861 self.failUnless(s.finished) | |
862 self.failIf(s.producer) # producer should be registered and removed | |
863 | |
864 l2 = MyLog(self.basedir, "consumer2") | |
865 l2.addHeader("HEADER\n") | |
866 l2.finish() | |
867 self.failUnless(l2.isFinished()) | |
868 | |
869 s = MyLogConsumer() | |
870 d = l2.subscribeConsumer(s) | |
871 d.addCallback(self._testConsumer_2, s) | |
872 return d | |
873 def _testConsumer_2(self, res, s): | |
874 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n")]) | |
875 self.failUnless(s.finished) | |
876 self.failIf(s.producer) # producer should be registered and removed | |
877 | |
878 | |
879 l2 = MyLog(self.basedir, "consumer3") | |
880 l2.chunkSize = 1000 | |
881 l2.addHeader("HEADER\n") | |
882 l2.addStdout(800*"a") | |
883 l2.addStdout(800*"a") # should now have two chunks on disk, 1000+600 | |
884 l2.addStdout(800*"b") # HEADER,1000+600*a on disk, 800*a in memory | |
885 l2.addStdout(800*"b") # HEADER,1000+600*a,1000+600*b on disk | |
886 l2.addStdout(200*"c") # HEADER,1000+600*a,1000+600*b on disk, | |
887 # 200*c in memory | |
888 | |
889 s = MyLogConsumer(limit=1) | |
890 d = l2.subscribeConsumer(s) | |
891 d.addCallback(self._testConsumer_3, l2, s) | |
892 return d | |
893 def _testConsumer_3(self, res, l2, s): | |
894 self.failUnless(s.streaming) | |
895 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n")]) | |
896 s.limit = 1 | |
897 d = s.producer.resumeProducing() | |
898 d.addCallback(self._testConsumer_4, l2, s) | |
899 return d | |
900 def _testConsumer_4(self, res, l2, s): | |
901 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n"), | |
902 (builder.STDOUT, 1000*"a"), | |
903 ]) | |
904 s.limit = None | |
905 d = s.producer.resumeProducing() | |
906 d.addCallback(self._testConsumer_5, l2, s) | |
907 return d | |
908 def _testConsumer_5(self, res, l2, s): | |
909 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n"), | |
910 (builder.STDOUT, 1000*"a"), | |
911 (builder.STDOUT, 600*"a"), | |
912 (builder.STDOUT, 1000*"b"), | |
913 (builder.STDOUT, 600*"b"), | |
914 (builder.STDOUT, 200*"c")]) | |
915 l2.addStdout(1000*"c") # HEADER,1600*a,1600*b,1200*c on disk | |
916 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n"), | |
917 (builder.STDOUT, 1000*"a"), | |
918 (builder.STDOUT, 600*"a"), | |
919 (builder.STDOUT, 1000*"b"), | |
920 (builder.STDOUT, 600*"b"), | |
921 (builder.STDOUT, 200*"c"), | |
922 (builder.STDOUT, 1000*"c")]) | |
923 l2.finish() | |
924 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n"), | |
925 (builder.STDOUT, 1000*"a"), | |
926 (builder.STDOUT, 600*"a"), | |
927 (builder.STDOUT, 1000*"b"), | |
928 (builder.STDOUT, 600*"b"), | |
929 (builder.STDOUT, 200*"c"), | |
930 (builder.STDOUT, 1000*"c")]) | |
931 self.failIf(s.producer) | |
932 self.failUnless(s.finished) | |
933 | |
934 def testLargeSummary(self): | |
935 bigtext = "a" * 200000 # exceed the NetstringReceiver 100KB limit | |
936 l = MyLog(self.basedir, "large", bigtext) | |
937 s = MyLogConsumer() | |
938 d = l.subscribeConsumer(s) | |
939 def _check(res): | |
940 for ctype,chunk in s.chunks: | |
941 self.failUnless(len(chunk) < 100000) | |
942 merged = "".join([c[1] for c in s.chunks]) | |
943 self.failUnless(merged == bigtext) | |
944 d.addCallback(_check) | |
945 # when this fails, it fails with a timeout, and there is an exception | |
946 # sent to log.err(). This AttributeError exception is in | |
947 # NetstringReceiver.dataReceived where it does | |
948 # self.transport.loseConnection() because of the NetstringParseError, | |
949 # however self.transport is None | |
950 return d | |
951 testLargeSummary.timeout = 5 | |
952 | |
953 def testLimit(self): | |
954 l = MyLog(self.basedir, "limit") | |
955 l.logMaxSize = 150 | |
956 for i in range(1000): | |
957 l.addStdout("Some data") | |
958 l.finish() | |
959 t = l.getText() | |
960 # Compare against 175 since we truncate logs based on chunks, so we may | |
961 # go slightly over the limit | |
962 self.failIf(len(t) > 175, "Text too long (%i)" % len(t)) | |
963 self.failUnless("truncated" in l.getTextWithHeaders(), | |
964 "No truncated message found") | |
965 | |
966 class CompressLog(unittest.TestCase): | |
967 # compression is not supported unless bz2 is installed | |
968 try: | |
969 import bz2 | |
970 except: | |
971 skip = "compression not supported (no bz2 module available)" | |
972 | |
973 def testCompressLogs(self): | |
974 bss = setupBuildStepStatus("test-compress") | |
975 bss.build.builder.setLogCompressionLimit(1024) | |
976 l = bss.addLog('not-compress') | |
977 l.addStdout('a' * 512) | |
978 l.finish() | |
979 lc = bss.addLog('to-compress') | |
980 lc.addStdout('b' * 1024) | |
981 lc.finish() | |
982 d = bss.stepFinished(builder.SUCCESS) | |
983 self.failUnless(d is not None) | |
984 d.addCallback(self._verifyCompression, bss) | |
985 return d | |
986 | |
987 def _verifyCompression(self, result, bss): | |
988 self.failUnless(len(bss.getLogs()), 2) | |
989 (ncl, cl) = bss.getLogs() # not compressed, compressed log | |
990 self.failUnless(os.path.isfile(ncl.getFilename())) | |
991 self.failIf(os.path.isfile(ncl.getFilename() + ".bz2")) | |
992 self.failIf(os.path.isfile(cl.getFilename())) | |
993 self.failUnless(os.path.isfile(cl.getFilename() + ".bz2")) | |
994 content = ncl.getText() | |
995 self.failUnless(len(content), 512) | |
996 content = cl.getText() | |
997 self.failUnless(len(content), 1024) | |
998 pass | |
999 | |
1000 config_base = """ | |
1001 from buildbot.process import factory | |
1002 from buildbot.steps import dummy | |
1003 from buildbot.buildslave import BuildSlave | |
1004 from buildbot.config import BuilderConfig | |
1005 s = factory.s | |
1006 | |
1007 f1 = factory.QuickBuildFactory('fakerep', 'cvsmodule', configure=None) | |
1008 | |
1009 f2 = factory.BuildFactory([ | |
1010 s(dummy.Dummy, timeout=1), | |
1011 s(dummy.RemoteDummy, timeout=2), | |
1012 ]) | |
1013 | |
1014 BuildmasterConfig = c = {} | |
1015 c['slaves'] = [BuildSlave('bot1', 'sekrit')] | |
1016 c['schedulers'] = [] | |
1017 c['builders'] = [ | |
1018 BuilderConfig(name='quick', slavename='bot1', factory=f1), | |
1019 ] | |
1020 c['slavePortnum'] = 0 | |
1021 """ | |
1022 | |
1023 config_2 = config_base + """ | |
1024 c['builders'] = [ | |
1025 BuilderConfig(name='dummy', slavename='bot1', factory=f2), | |
1026 BuilderConfig(name='testdummy', slavename='bot1', | |
1027 factory=f2, category='test'), | |
1028 ] | |
1029 """ | |
1030 | |
1031 class STarget(base.StatusReceiver): | |
1032 debug = False | |
1033 | |
1034 def __init__(self, mode): | |
1035 self.mode = mode | |
1036 self.events = [] | |
1037 def announce(self): | |
1038 if self.debug: | |
1039 print self.events[-1] | |
1040 | |
1041 def builderAdded(self, name, builder): | |
1042 self.events.append(("builderAdded", name, builder)) | |
1043 self.announce() | |
1044 if "builder" in self.mode: | |
1045 return self | |
1046 def builderChangedState(self, name, state): | |
1047 self.events.append(("builderChangedState", name, state)) | |
1048 self.announce() | |
1049 def buildStarted(self, name, build): | |
1050 self.events.append(("buildStarted", name, build)) | |
1051 self.announce() | |
1052 if "eta" in self.mode: | |
1053 self.eta_build = build.getETA() | |
1054 if "build" in self.mode: | |
1055 return self | |
1056 def buildETAUpdate(self, build, ETA): | |
1057 self.events.append(("buildETAUpdate", build, ETA)) | |
1058 self.announce() | |
1059 def changeAdded(self, change): | |
1060 self.events.append(("changeAdded", change)) | |
1061 self.announce() | |
1062 def stepStarted(self, build, step): | |
1063 self.events.append(("stepStarted", build, step)) | |
1064 self.announce() | |
1065 if 0 and "eta" in self.mode: | |
1066 print "TIMES", step.getTimes() | |
1067 print "ETA", step.getETA() | |
1068 print "EXP", step.getExpectations() | |
1069 if "step" in self.mode: | |
1070 return self | |
1071 def stepTextChanged(self, build, step, text): | |
1072 self.events.append(("stepTextChanged", step, text)) | |
1073 def stepText2Changed(self, build, step, text2): | |
1074 self.events.append(("stepText2Changed", step, text2)) | |
1075 def stepETAUpdate(self, build, step, ETA, expectations): | |
1076 self.events.append(("stepETAUpdate", build, step, ETA, expectations)) | |
1077 self.announce() | |
1078 def logStarted(self, build, step, log): | |
1079 self.events.append(("logStarted", build, step, log)) | |
1080 self.announce() | |
1081 def logFinished(self, build, step, log): | |
1082 self.events.append(("logFinished", build, step, log)) | |
1083 self.announce() | |
1084 def stepFinished(self, build, step, results): | |
1085 self.events.append(("stepFinished", build, step, results)) | |
1086 if 0 and "eta" in self.mode: | |
1087 print "post-EXP", step.getExpectations() | |
1088 self.announce() | |
1089 def buildFinished(self, name, build, results): | |
1090 self.events.append(("buildFinished", name, build, results)) | |
1091 self.announce() | |
1092 def builderRemoved(self, name): | |
1093 self.events.append(("builderRemoved", name)) | |
1094 self.announce() | |
1095 def slaveConnected(self, name): | |
1096 self.events.append(("slaveConnected", name)) | |
1097 self.announce() | |
1098 def slaveDisconnected(self, name): | |
1099 self.events.append(("slaveDisconnected", name)) | |
1100 self.announce() | |
1101 | |
1102 class VerifyChangeAdded(RunMixin, unittest.TestCase): | |
1103 def testChangeAdded(self): | |
1104 m = self.master | |
1105 | |
1106 s = m.getStatus() | |
1107 self.t1 = t1 = STarget(["builder"]) | |
1108 s.subscribe(t1) | |
1109 | |
1110 m.loadConfig(config_2) | |
1111 m.readConfig = True | |
1112 m.startService() | |
1113 | |
1114 cm = self.master.change_svc | |
1115 c = Change("bob", ["Makefile", "foo/bar.c"], "changed stuff") | |
1116 cm.addChange(c) | |
1117 self.failUnlessEqual(t1.events[-1], ("changeAdded", c)) | |
1118 | |
1119 class Subscription(RunMixin, unittest.TestCase): | |
1120 # verify that StatusTargets can subscribe/unsubscribe properly | |
1121 | |
1122 def testSlave(self): | |
1123 m = self.master | |
1124 s = m.getStatus() | |
1125 self.t1 = t1 = STarget(["builder"]) | |
1126 #t1.debug = True; print | |
1127 s.subscribe(t1) | |
1128 self.failUnlessEqual(len(t1.events), 0) | |
1129 | |
1130 self.t3 = t3 = STarget(["builder", "build", "step"]) | |
1131 s.subscribe(t3) | |
1132 | |
1133 m.loadConfig(config_2) | |
1134 m.readConfig = True | |
1135 m.startService() | |
1136 | |
1137 self.failUnlessEqual(len(t1.events), 4) | |
1138 self.failUnlessEqual(t1.events[0][0:2], ("builderAdded", "dummy")) | |
1139 self.failUnlessEqual(t1.events[1], | |
1140 ("builderChangedState", "dummy", "offline")) | |
1141 self.failUnlessEqual(t1.events[2][0:2], ("builderAdded", "testdummy")) | |
1142 self.failUnlessEqual(t1.events[3], | |
1143 ("builderChangedState", "testdummy", "offline")) | |
1144 t1.events = [] | |
1145 | |
1146 self.failUnlessEqual(s.getBuilderNames(), ["dummy", "testdummy"]) | |
1147 self.failUnlessEqual(s.getBuilderNames(categories=['test']), | |
1148 ["testdummy"]) | |
1149 self.s1 = s1 = s.getBuilder("dummy") | |
1150 self.failUnlessEqual(s1.getName(), "dummy") | |
1151 self.failUnlessEqual(s1.getState(), ("offline", [])) | |
1152 self.failUnlessEqual(s1.getCurrentBuilds(), []) | |
1153 self.failUnlessEqual(s1.getLastFinishedBuild(), None) | |
1154 self.failUnlessEqual(s1.getBuild(-1), None) | |
1155 #self.failUnlessEqual(s1.getEvent(-1), foo("created")) | |
1156 | |
1157 # status targets should, upon being subscribed, immediately get a | |
1158 # list of all current builders matching their category | |
1159 self.t2 = t2 = STarget([]) | |
1160 s.subscribe(t2) | |
1161 self.failUnlessEqual(len(t2.events), 2) | |
1162 self.failUnlessEqual(t2.events[0][0:2], ("builderAdded", "dummy")) | |
1163 self.failUnlessEqual(t2.events[1][0:2], ("builderAdded", "testdummy")) | |
1164 | |
1165 d = self.connectSlave(builders=["dummy", "testdummy"]) | |
1166 d.addCallback(self._testSlave_1, t1) | |
1167 return d | |
1168 | |
1169 def _testSlave_1(self, res, t1): | |
1170 self.failUnlessEqual(len(t1.events), 3) | |
1171 self.failUnlessEqual(t1.events[0], | |
1172 ("slaveConnected", "bot1")) | |
1173 self.failUnlessEqual(t1.events[1], | |
1174 ("builderChangedState", "dummy", "idle")) | |
1175 self.failUnlessEqual(t1.events[2], | |
1176 ("builderChangedState", "testdummy", "idle")) | |
1177 t1.events = [] | |
1178 | |
1179 c = interfaces.IControl(self.master) | |
1180 req = BuildRequest("forced build for testing", SourceStamp(), 'test_buil
der') | |
1181 c.getBuilder("dummy").requestBuild(req) | |
1182 d = req.waitUntilFinished() | |
1183 d2 = self.master.botmaster.waitUntilBuilderIdle("dummy") | |
1184 dl = defer.DeferredList([d, d2]) | |
1185 dl.addCallback(self._testSlave_2) | |
1186 return dl | |
1187 | |
1188 def _testSlave_2(self, res): | |
1189 # t1 subscribes to builds, but not anything lower-level | |
1190 ev = self.t1.events | |
1191 self.failUnlessEqual(len(ev), 4) | |
1192 self.failUnlessEqual(ev[0][0:3], | |
1193 ("builderChangedState", "dummy", "building")) | |
1194 self.failUnlessEqual(ev[1][0], "buildStarted") | |
1195 self.failUnlessEqual(ev[2][0:2]+ev[2][3:4], | |
1196 ("buildFinished", "dummy", builder.SUCCESS)) | |
1197 self.failUnlessEqual(ev[3][0:3], | |
1198 ("builderChangedState", "dummy", "idle")) | |
1199 | |
1200 self.failUnlessEqual([ev[0] for ev in self.t3.events], | |
1201 ["builderAdded", | |
1202 "builderChangedState", # offline | |
1203 "builderAdded", | |
1204 "builderChangedState", # idle | |
1205 "slaveConnected", | |
1206 "builderChangedState", # offline | |
1207 "builderChangedState", # idle | |
1208 "builderChangedState", # building | |
1209 "buildStarted", | |
1210 "stepStarted", "stepETAUpdate", | |
1211 "stepTextChanged", "stepFinished", | |
1212 "stepStarted", "stepETAUpdate", | |
1213 "stepTextChanged", "logStarted", "logFinished", | |
1214 "stepTextChanged", "stepText2Changed", | |
1215 "stepFinished", | |
1216 "buildFinished", | |
1217 "builderChangedState", # idle | |
1218 ]) | |
1219 | |
1220 b = self.s1.getLastFinishedBuild() | |
1221 self.failUnless(b) | |
1222 self.failUnlessEqual(b.getBuilder().getName(), "dummy") | |
1223 self.failUnlessEqual(b.getNumber(), 0) | |
1224 self.failUnlessEqual(b.getSourceStamp().branch, None) | |
1225 self.failUnlessEqual(b.getSourceStamp().patch, None) | |
1226 self.failUnlessEqual(b.getSourceStamp().revision, None) | |
1227 self.failUnlessEqual(b.getReason(), "forced build for testing") | |
1228 self.failUnlessEqual(b.getChanges(), ()) | |
1229 self.failUnlessEqual(b.getResponsibleUsers(), []) | |
1230 self.failUnless(b.isFinished()) | |
1231 self.failUnlessEqual(b.getText(), ['build', 'successful']) | |
1232 self.failUnlessEqual(b.getResults(), builder.SUCCESS) | |
1233 | |
1234 steps = b.getSteps() | |
1235 self.failUnlessEqual(len(steps), 2) | |
1236 | |
1237 eta = 0 | |
1238 st1 = steps[0] | |
1239 self.failUnlessEqual(st1.getName(), "dummy") | |
1240 self.failUnless(st1.isFinished()) | |
1241 self.failUnlessEqual(st1.getText(), ["delay", "1 secs"]) | |
1242 start,finish = st1.getTimes() | |
1243 self.failUnless(0.5 < (finish-start) < 10) | |
1244 self.failUnlessEqual(st1.getExpectations(), []) | |
1245 self.failUnlessEqual(st1.getLogs(), []) | |
1246 eta += finish-start | |
1247 | |
1248 st2 = steps[1] | |
1249 self.failUnlessEqual(st2.getName(), "remote dummy") | |
1250 self.failUnless(st2.isFinished()) | |
1251 self.failUnlessEqual(st2.getText(), | |
1252 ["remote", "delay", "2 secs"]) | |
1253 start,finish = st2.getTimes() | |
1254 self.failUnless(1.5 < (finish-start) < 10) | |
1255 eta += finish-start | |
1256 self.failUnlessEqual(st2.getExpectations(), [('output', 38, None)]) | |
1257 logs = st2.getLogs() | |
1258 self.failUnlessEqual(len(logs), 1) | |
1259 self.failUnlessEqual(logs[0].getName(), "stdio") | |
1260 self.failUnlessEqual(logs[0].getText(), "data") | |
1261 | |
1262 self.eta = eta | |
1263 # now we run it a second time, and we should have an ETA | |
1264 | |
1265 self.t4 = t4 = STarget(["builder", "build", "eta"]) | |
1266 self.master.getStatus().subscribe(t4) | |
1267 c = interfaces.IControl(self.master) | |
1268 req = BuildRequest("forced build for testing", SourceStamp(), 'test_buil
der') | |
1269 c.getBuilder("dummy").requestBuild(req) | |
1270 d = req.waitUntilFinished() | |
1271 d2 = self.master.botmaster.waitUntilBuilderIdle("dummy") | |
1272 dl = defer.DeferredList([d, d2]) | |
1273 dl.addCallback(lambda ign: self.shutdownAllSlaves()) | |
1274 dl.addCallback(self._testSlave_3) | |
1275 return dl | |
1276 | |
1277 def _testSlave_3(self, res): | |
1278 t4 = self.t4 | |
1279 eta = self.eta | |
1280 self.failUnless(eta-1 < t4.eta_build < eta+1, # should be 3 seconds | |
1281 "t4.eta_build was %g, not in (%g,%g)" | |
1282 % (t4.eta_build, eta-1, eta+1)) | |
1283 | |
1284 self.failUnlessEqual([ev[0] for ev in self.t4.events], | |
1285 ["builderAdded", | |
1286 "builderChangedState", # offline | |
1287 "builderAdded", | |
1288 "builderChangedState", # idle | |
1289 "builderChangedState", # building | |
1290 "buildStarted", | |
1291 "stepStarted", "stepFinished", | |
1292 "stepStarted", "stepFinished", | |
1293 "buildFinished", | |
1294 "builderChangedState", | |
1295 "slaveDisconnected", | |
1296 "builderChangedState", | |
1297 "builderChangedState", | |
1298 ]) | |
1299 | |
1300 class Client(unittest.TestCase): | |
1301 def testAdaptation(self): | |
1302 b = builder.BuilderStatus("bname") | |
1303 b2 = client.makeRemote(b) | |
1304 self.failUnless(isinstance(b2, client.RemoteBuilder)) | |
1305 b3 = client.makeRemote(None) | |
1306 self.failUnless(b3 is None) | |
1307 | |
1308 | |
1309 class ContactTester(unittest.TestCase): | |
1310 def test_notify_invalid_syntax(self): | |
1311 irc = MyContact() | |
1312 self.assertRaises(words.UsageError, lambda args, who: irc.command_NOTIFY
(args, who), "", "mynick") | |
1313 | |
1314 def test_notify_list(self): | |
1315 irc = MyContact() | |
1316 irc.command_NOTIFY("list", "mynick") | |
1317 self.failUnlessEqual(irc.message, "The following events are being notifi
ed: []", "empty notify list") | |
1318 | |
1319 irc.message = "" | |
1320 irc.command_NOTIFY("on started", "mynick") | |
1321 self.failUnlessEqual(irc.message, "The following events are being notifi
ed: ['started']", "on started") | |
1322 | |
1323 irc.message = "" | |
1324 irc.command_NOTIFY("on finished", "mynick") | |
1325 self.failUnlessEqual(irc.message, "The following events are being notifi
ed: ['started', 'finished']", "on finished") | |
1326 | |
1327 irc.message = "" | |
1328 irc.command_NOTIFY("off", "mynick") | |
1329 self.failUnlessEqual(irc.message, "The following events are being notifi
ed: []", "off all") | |
1330 | |
1331 irc.message = "" | |
1332 irc.command_NOTIFY("on", "mynick") | |
1333 self.failUnlessEqual(irc.message, "The following events are being notifi
ed: ['started', 'finished']", "on default set") | |
1334 | |
1335 irc.message = "" | |
1336 irc.command_NOTIFY("off started", "mynick") | |
1337 self.failUnlessEqual(irc.message, "The following events are being notifi
ed: ['finished']", "off started") | |
1338 | |
1339 irc.message = "" | |
1340 irc.command_NOTIFY("on success failure exception", "mynick") | |
1341 self.failUnlessEqual(irc.message, "The following events are being notifi
ed: ['failure', 'finished', 'exception', 'success']", "on multiple events") | |
1342 | |
1343 def test_notification_default(self): | |
1344 irc = MyContact() | |
1345 | |
1346 my_builder = MyBuilder("builder78") | |
1347 my_build = MyIrcBuild(my_builder, 23, builder.SUCCESS) | |
1348 | |
1349 irc.buildStarted(my_builder.getName(), my_build) | |
1350 self.failUnlessEqual(irc.message, "", "No notification with default sett
ings") | |
1351 | |
1352 irc.buildFinished(my_builder.getName(), my_build, None) | |
1353 self.failUnlessEqual(irc.message, "", "No notification with default sett
ings") | |
1354 | |
1355 def test_notification_started(self): | |
1356 irc = MyContact() | |
1357 | |
1358 my_builder = MyBuilder("builder78") | |
1359 my_build = MyIrcBuild(my_builder, 23, builder.SUCCESS) | |
1360 my_build.changes = ( | |
1361 Change(who = 'author1', files = ['file1'], comments = 'comment1', re
vision = 123), | |
1362 Change(who = 'author2', files = ['file2'], comments = 'comment2', re
vision = 456), | |
1363 ) | |
1364 | |
1365 irc.command_NOTIFY("on started", "mynick") | |
1366 | |
1367 irc.message = "" | |
1368 irc.buildStarted(my_builder.getName(), my_build) | |
1369 self.failUnlessEqual(irc.message, "build #23 of builder78 started includ
ing [123, 456]", "Start notification generated with notify_events=['started']") | |
1370 | |
1371 irc.message = "" | |
1372 irc.buildFinished(my_builder.getName(), my_build, None) | |
1373 self.failUnlessEqual(irc.message, "", "No finished notification with not
ify_events=['started']") | |
1374 | |
1375 def test_notification_finished(self): | |
1376 irc = MyContact() | |
1377 | |
1378 my_builder = MyBuilder("builder834") | |
1379 my_build = MyIrcBuild(my_builder, 862, builder.SUCCESS) | |
1380 my_build.changes = ( | |
1381 Change(who = 'author1', files = ['file1'], comments = 'comment1', re
vision = 943), | |
1382 ) | |
1383 | |
1384 irc.command_NOTIFY("on finished", "mynick") | |
1385 | |
1386 irc.message = "" | |
1387 irc.buildStarted(my_builder.getName(), my_build) | |
1388 self.failUnlessEqual(irc.message, "", "No started notification with noti
fy_events=['finished']") | |
1389 | |
1390 irc.message = "" | |
1391 irc.buildFinished(my_builder.getName(), my_build, None) | |
1392 self.failUnlessEqual(irc.message, "build #862 of builder834 is complete:
Success [step1 step2] Build details are at http://myserver/mypath?build=765",
"Finish notification generated with notify_events=['finished']") | |
1393 | |
1394 def test_notification_success(self): | |
1395 irc = MyContact() | |
1396 | |
1397 my_builder = MyBuilder("builder834") | |
1398 my_build = MyIrcBuild(my_builder, 862, builder.SUCCESS) | |
1399 my_build.changes = ( | |
1400 Change(who = 'author1', files = ['file1'], comments = 'comment1', re
vision = 943), | |
1401 ) | |
1402 | |
1403 irc.command_NOTIFY("on success", "mynick") | |
1404 | |
1405 irc.message = "" | |
1406 irc.buildStarted(my_builder.getName(), my_build) | |
1407 self.failUnlessEqual(irc.message, "", "No started notification with noti
fy_events=['success']") | |
1408 | |
1409 irc.message = "" | |
1410 irc.buildFinished(my_builder.getName(), my_build, None) | |
1411 self.failUnlessEqual(irc.message, "build #862 of builder834 is complete:
Success [step1 step2] Build details are at http://myserver/mypath?build=765",
"Finish notification generated on success with notify_events=['success']") | |
1412 | |
1413 irc.message = "" | |
1414 my_build.results = builder.FAILURE | |
1415 irc.buildFinished(my_builder.getName(), my_build, None) | |
1416 self.failUnlessEqual(irc.message, "", "No finish notification generated
on failure with notify_events=['success']") | |
1417 | |
1418 irc.message = "" | |
1419 my_build.results = builder.EXCEPTION | |
1420 irc.buildFinished(my_builder.getName(), my_build, None) | |
1421 self.failUnlessEqual(irc.message, "", "No finish notification generated
on exception with notify_events=['success']") | |
1422 | |
1423 def test_notification_failed(self): | |
1424 irc = MyContact() | |
1425 | |
1426 my_builder = MyBuilder("builder834") | |
1427 my_build = MyIrcBuild(my_builder, 862, builder.FAILURE) | |
1428 my_build.changes = ( | |
1429 Change(who = 'author1', files = ['file1'], comments = 'comment1', re
vision = 943), | |
1430 ) | |
1431 | |
1432 irc.command_NOTIFY("on failure", "mynick") | |
1433 | |
1434 irc.message = "" | |
1435 irc.buildStarted(my_builder.getName(), my_build) | |
1436 self.failUnlessEqual(irc.message, "", "No started notification with noti
fy_events=['failed']") | |
1437 | |
1438 irc.message = "" | |
1439 irc.channel.showBlameList = True | |
1440 irc.buildFinished(my_builder.getName(), my_build, None) | |
1441 self.failUnlessEqual(irc.message, "build #862 of builder834 is complete:
Failure [step1 step2] Build details are at http://myserver/mypath?build=765 b
lamelist: author1", "Finish notification generated on failure with notify_events
=['failed']") | |
1442 irc.channel.showBlameList = False | |
1443 | |
1444 irc.message = "" | |
1445 my_build.results = builder.SUCCESS | |
1446 irc.buildFinished(my_builder.getName(), my_build, None) | |
1447 self.failUnlessEqual(irc.message, "", "No finish notification generated
on success with notify_events=['failed']") | |
1448 | |
1449 irc.message = "" | |
1450 my_build.results = builder.EXCEPTION | |
1451 irc.buildFinished(my_builder.getName(), my_build, None) | |
1452 self.failUnlessEqual(irc.message, "", "No finish notification generated
on exception with notify_events=['failed']") | |
1453 | |
1454 def test_notification_exception(self): | |
1455 irc = MyContact() | |
1456 | |
1457 my_builder = MyBuilder("builder834") | |
1458 my_build = MyIrcBuild(my_builder, 862, builder.EXCEPTION) | |
1459 my_build.changes = ( | |
1460 Change(who = 'author1', files = ['file1'], comments = 'comment1', re
vision = 943), | |
1461 ) | |
1462 | |
1463 irc.command_NOTIFY("on exception", "mynick") | |
1464 | |
1465 irc.message = "" | |
1466 irc.buildStarted(my_builder.getName(), my_build) | |
1467 self.failUnlessEqual(irc.message, "", "No started notification with noti
fy_events=['exception']") | |
1468 | |
1469 irc.message = "" | |
1470 irc.buildFinished(my_builder.getName(), my_build, None) | |
1471 self.failUnlessEqual(irc.message, "build #862 of builder834 is complete:
Exception [step1 step2] Build details are at http://myserver/mypath?build=765"
, "Finish notification generated on failure with notify_events=['exception']") | |
1472 | |
1473 irc.message = "" | |
1474 irc.channel.showBlameList = True | |
1475 irc.buildFinished(my_builder.getName(), my_build, None) | |
1476 self.failUnlessEqual(irc.message, "build #862 of builder834 is complete:
Exception [step1 step2] Build details are at http://myserver/mypath?build=765
blamelist: author1", "Finish notification generated on failure with notify_even
ts=['exception']") | |
1477 irc.channel.showBlameList = False | |
1478 | |
1479 irc.message = "" | |
1480 my_build.results = builder.SUCCESS | |
1481 irc.buildFinished(my_builder.getName(), my_build, None) | |
1482 self.failUnlessEqual(irc.message, "", "No finish notification generated
on success with notify_events=['exception']") | |
1483 | |
1484 irc.message = "" | |
1485 my_build.results = builder.FAILURE | |
1486 irc.buildFinished(my_builder.getName(), my_build, None) | |
1487 self.failUnlessEqual(irc.message, "", "No finish notification generated
on exception with notify_events=['exception']") | |
1488 | |
1489 def do_x_to_y_notification_test(self, notify, previous_result, new_result, e
xpected_msg): | |
1490 irc = MyContact() | |
1491 irc.command_NOTIFY("on %s" % notify, "mynick") | |
1492 | |
1493 my_builder = MyBuilder("builder834") | |
1494 my_build = MyIrcBuild(my_builder, 862, builder.FAILURE) | |
1495 my_build.changes = ( | |
1496 Change(who = 'author1', files = ['file1'], comments = 'comment1', re
vision = 943), | |
1497 ) | |
1498 | |
1499 previous_build = MyIrcBuild(my_builder, 861, previous_result) | |
1500 my_build.setPreviousBuild(previous_build) | |
1501 | |
1502 irc.message = "" | |
1503 my_build.results = new_result | |
1504 irc.buildFinished(my_builder.getName(), my_build, None) | |
1505 self.failUnlessEqual(irc.message, expected_msg, "Finish notification gen
erated on failure with notify_events=['successToFailure']") | |
1506 | |
1507 def test_notification_successToFailure(self): | |
1508 self.do_x_to_y_notification_test(notify="successToFailure", previous_res
ult=builder.SUCCESS, new_result=builder.FAILURE, | |
1509 expected_msg="build #862 of builder834
is complete: Failure [step1 step2] Build details are at http://myserver/mypath?
build=765" ) | |
1510 | |
1511 self.do_x_to_y_notification_test(notify="successToFailure", previous_res
ult=builder.SUCCESS, new_result=builder.SUCCESS, | |
1512 expected_msg = "" ) | |
1513 | |
1514 self.do_x_to_y_notification_test(notify="successToFailure", previous_res
ult=builder.SUCCESS, new_result=builder.WARNINGS, | |
1515 expected_msg = "" ) | |
1516 | |
1517 self.do_x_to_y_notification_test(notify="successToFailure", previous_res
ult=builder.SUCCESS, new_result=builder.EXCEPTION, | |
1518 expected_msg = "" ) | |
1519 | |
1520 def test_notification_successToWarnings(self): | |
1521 self.do_x_to_y_notification_test(notify="successToWarnings", previous_re
sult=builder.SUCCESS, new_result=builder.WARNINGS, | |
1522 expected_msg="build #862 of builder834
is complete: Warnings [step1 step2] Build details are at http://myserver/mypath
?build=765" ) | |
1523 | |
1524 self.do_x_to_y_notification_test(notify="successToWarnings", previous_re
sult=builder.SUCCESS, new_result=builder.SUCCESS, | |
1525 expected_msg = "" ) | |
1526 | |
1527 self.do_x_to_y_notification_test(notify="successToWarnings", previous_re
sult=builder.SUCCESS, new_result=builder.FAILURE, | |
1528 expected_msg = "" ) | |
1529 | |
1530 self.do_x_to_y_notification_test(notify="successToWarnings", previous_re
sult=builder.SUCCESS, new_result=builder.EXCEPTION, | |
1531 expected_msg = "" ) | |
1532 | |
1533 def test_notification_successToException(self): | |
1534 self.do_x_to_y_notification_test(notify="successToException", previous_r
esult=builder.SUCCESS, new_result=builder.EXCEPTION, | |
1535 expected_msg="build #862 of builder834
is complete: Exception [step1 step2] Build details are at http://myserver/mypat
h?build=765" ) | |
1536 | |
1537 self.do_x_to_y_notification_test(notify="successToException", previous_r
esult=builder.SUCCESS, new_result=builder.SUCCESS, | |
1538 expected_msg = "" ) | |
1539 | |
1540 self.do_x_to_y_notification_test(notify="successToException", previous_r
esult=builder.SUCCESS, new_result=builder.FAILURE, | |
1541 expected_msg = "" ) | |
1542 | |
1543 self.do_x_to_y_notification_test(notify="successToException", previous_r
esult=builder.SUCCESS, new_result=builder.WARNINGS, | |
1544 expected_msg = "" ) | |
1545 | |
1546 | |
1547 | |
1548 | |
1549 | |
1550 def test_notification_failureToSuccess(self): | |
1551 self.do_x_to_y_notification_test(notify="failureToSuccess", previous_res
ult=builder.FAILURE,new_result=builder.SUCCESS, | |
1552 expected_msg="build #862 of builder834
is complete: Success [step1 step2] Build details are at http://myserver/mypath?
build=765" ) | |
1553 | |
1554 self.do_x_to_y_notification_test(notify="failureToSuccess", previous_res
ult=builder.FAILURE,new_result=builder.FAILURE, | |
1555 expected_msg = "" ) | |
1556 | |
1557 self.do_x_to_y_notification_test(notify="failureToSuccess", previous_res
ult=builder.FAILURE,new_result=builder.WARNINGS, | |
1558 expected_msg = "" ) | |
1559 | |
1560 self.do_x_to_y_notification_test(notify="failureToSuccess", previous_res
ult=builder.FAILURE,new_result=builder.EXCEPTION, | |
1561 expected_msg = "" ) | |
1562 | |
1563 def test_notification_failureToWarnings(self): | |
1564 self.do_x_to_y_notification_test(notify="failureToWarnings", previous_re
sult=builder.FAILURE, new_result=builder.WARNINGS, | |
1565 expected_msg="build #862 of builder834
is complete: Warnings [step1 step2] Build details are at http://myserver/mypath
?build=765" ) | |
1566 | |
1567 self.do_x_to_y_notification_test(notify="failureToWarnings", previous_re
sult=builder.FAILURE, new_result=builder.SUCCESS, | |
1568 expected_msg = "" ) | |
1569 | |
1570 self.do_x_to_y_notification_test(notify="failureToWarnings", previous_re
sult=builder.FAILURE, new_result=builder.FAILURE, | |
1571 expected_msg = "" ) | |
1572 | |
1573 self.do_x_to_y_notification_test(notify="failureToWarnings", previous_re
sult=builder.FAILURE, new_result=builder.EXCEPTION, | |
1574 expected_msg = "" ) | |
1575 | |
1576 def test_notification_failureToException(self): | |
1577 self.do_x_to_y_notification_test(notify="failureToException", previous_r
esult=builder.FAILURE, new_result=builder.EXCEPTION, | |
1578 expected_msg="build #862 of builder834
is complete: Exception [step1 step2] Build details are at http://myserver/mypat
h?build=765" ) | |
1579 | |
1580 self.do_x_to_y_notification_test(notify="failureToException", previous_r
esult=builder.FAILURE, new_result=builder.SUCCESS, | |
1581 expected_msg = "" ) | |
1582 | |
1583 self.do_x_to_y_notification_test(notify="failureToException", previous_r
esult=builder.FAILURE, new_result=builder.FAILURE, | |
1584 expected_msg = "" ) | |
1585 | |
1586 self.do_x_to_y_notification_test(notify="failureToException", previous_r
esult=builder.FAILURE, new_result=builder.WARNINGS, | |
1587 expected_msg = "" ) | |
1588 | |
1589 | |
1590 | |
1591 | |
1592 | |
1593 def test_notification_warningsToFailure(self): | |
1594 self.do_x_to_y_notification_test(notify="warningsToFailure", previous_re
sult=builder.WARNINGS, new_result=builder.FAILURE, | |
1595 expected_msg="build #862 of builder834
is complete: Failure [step1 step2] Build details are at http://myserver/mypath?
build=765" ) | |
1596 | |
1597 self.do_x_to_y_notification_test(notify="warningsToFailure", previous_re
sult=builder.WARNINGS, new_result=builder.SUCCESS, | |
1598 expected_msg = "" ) | |
1599 | |
1600 self.do_x_to_y_notification_test(notify="warningsToFailure", previous_re
sult=builder.WARNINGS, new_result=builder.WARNINGS, | |
1601 expected_msg = "" ) | |
1602 | |
1603 self.do_x_to_y_notification_test(notify="warningsToFailure", previous_re
sult=builder.WARNINGS, new_result=builder.EXCEPTION, | |
1604 expected_msg = "" ) | |
1605 | |
1606 def test_notification_warningsToSuccess(self): | |
1607 self.do_x_to_y_notification_test(notify="warningsToSuccess", previous_re
sult=builder.WARNINGS, new_result=builder.SUCCESS, | |
1608 expected_msg="build #862 of builder834
is complete: Success [step1 step2] Build details are at http://myserver/mypath?
build=765" ) | |
1609 | |
1610 self.do_x_to_y_notification_test(notify="warningsToSuccess", previous_re
sult=builder.WARNINGS, new_result=builder.WARNINGS, | |
1611 expected_msg = "" ) | |
1612 | |
1613 self.do_x_to_y_notification_test(notify="warningsToSuccess", previous_re
sult=builder.WARNINGS, new_result=builder.FAILURE, | |
1614 expected_msg = "" ) | |
1615 | |
1616 self.do_x_to_y_notification_test(notify="warningsToSuccess", previous_re
sult=builder.WARNINGS, new_result=builder.EXCEPTION, | |
1617 expected_msg = "" ) | |
1618 | |
1619 def test_notification_warningsToException(self): | |
1620 self.do_x_to_y_notification_test(notify="warningsToException", previous_
result=builder.WARNINGS, new_result=builder.EXCEPTION, | |
1621 expected_msg="build #862 of builder834
is complete: Exception [step1 step2] Build details are at http://myserver/mypat
h?build=765" ) | |
1622 | |
1623 self.do_x_to_y_notification_test(notify="warningsToException", previous_
result=builder.WARNINGS, new_result=builder.SUCCESS, | |
1624 expected_msg = "" ) | |
1625 | |
1626 self.do_x_to_y_notification_test(notify="warningsToException", previous_
result=builder.WARNINGS, new_result=builder.FAILURE, | |
1627 expected_msg = "" ) | |
1628 | |
1629 self.do_x_to_y_notification_test(notify="warningsToException", previous_
result=builder.WARNINGS, new_result=builder.WARNINGS, | |
1630 expected_msg = "" ) | |
1631 | |
1632 | |
1633 | |
1634 | |
1635 def test_notification_exceptionToFailure(self): | |
1636 self.do_x_to_y_notification_test(notify="exceptionToFailure", previous_r
esult=builder.EXCEPTION, new_result=builder.FAILURE, | |
1637 expected_msg="build #862 of builder834
is complete: Failure [step1 step2] Build details are at http://myserver/mypath?
build=765" ) | |
1638 | |
1639 self.do_x_to_y_notification_test(notify="exceptionToFailure", previous_r
esult=builder.EXCEPTION, new_result=builder.SUCCESS, | |
1640 expected_msg = "" ) | |
1641 | |
1642 self.do_x_to_y_notification_test(notify="exceptionToFailure", previous_r
esult=builder.EXCEPTION, new_result=builder.WARNINGS, | |
1643 expected_msg = "" ) | |
1644 | |
1645 self.do_x_to_y_notification_test(notify="exceptionToFailure", previous_r
esult=builder.EXCEPTION, new_result=builder.EXCEPTION, | |
1646 expected_msg = "" ) | |
1647 | |
1648 def test_notification_exceptionToWarnings(self): | |
1649 self.do_x_to_y_notification_test(notify="exceptionToWarnings", previous_
result=builder.EXCEPTION, new_result=builder.WARNINGS, | |
1650 expected_msg="build #862 of builder834
is complete: Warnings [step1 step2] Build details are at http://myserver/mypath
?build=765" ) | |
1651 | |
1652 self.do_x_to_y_notification_test(notify="exceptionToWarnings", previous_
result=builder.EXCEPTION, new_result=builder.SUCCESS, | |
1653 expected_msg = "" ) | |
1654 | |
1655 self.do_x_to_y_notification_test(notify="exceptionToWarnings", previous_
result=builder.EXCEPTION, new_result=builder.FAILURE, | |
1656 expected_msg = "" ) | |
1657 | |
1658 self.do_x_to_y_notification_test(notify="exceptionToWarnings", previous_
result=builder.EXCEPTION, new_result=builder.EXCEPTION, | |
1659 expected_msg = "" ) | |
1660 | |
1661 def test_notification_exceptionToSuccess(self): | |
1662 self.do_x_to_y_notification_test(notify="exceptionToSuccess", previous_r
esult=builder.EXCEPTION, new_result=builder.SUCCESS, | |
1663 expected_msg="build #862 of builder834
is complete: Success [step1 step2] Build details are at http://myserver/mypath?
build=765" ) | |
1664 | |
1665 self.do_x_to_y_notification_test(notify="exceptionToSuccess", previous_r
esult=builder.EXCEPTION, new_result=builder.EXCEPTION, | |
1666 expected_msg = "" ) | |
1667 | |
1668 self.do_x_to_y_notification_test(notify="exceptionToSuccess", previous_r
esult=builder.EXCEPTION, new_result=builder.FAILURE, | |
1669 expected_msg = "" ) | |
1670 | |
1671 self.do_x_to_y_notification_test(notify="exceptionToSuccess", previous_r
esult=builder.EXCEPTION, new_result=builder.WARNINGS, | |
1672 expected_msg = "" ) | |
1673 | |
1674 def test_notification_set_in_config(self): | |
1675 irc = MyContact(channel = MyChannel(notify_events = {'success': 1})) | |
1676 | |
1677 my_builder = MyBuilder("builder834") | |
1678 my_build = MyIrcBuild(my_builder, 862, builder.SUCCESS) | |
1679 my_build.changes = ( | |
1680 Change(who = 'author1', files = ['file1'], comments = 'comment1', re
vision = 943), | |
1681 ) | |
1682 | |
1683 irc.message = "" | |
1684 irc.buildFinished(my_builder.getName(), my_build, None) | |
1685 self.failUnlessEqual(irc.message, "build #862 of builder834 is complete:
Success [step1 step2] Build details are at http://myserver/mypath?build=765",
"Finish notification generated on success with notify_events=['success']") | |
1686 | |
1687 class MyIrcBuild(builder.BuildStatus): | |
1688 results = None | |
1689 | |
1690 def __init__(self, parent, number, results): | |
1691 builder.BuildStatus.__init__(self, parent, number) | |
1692 self.results = results | |
1693 self.previousBuild = None | |
1694 | |
1695 def getResults(self): | |
1696 return self.results | |
1697 | |
1698 def getText(self): | |
1699 return ('step1', 'step2') | |
1700 | |
1701 def setPreviousBuild(self, pb): | |
1702 self.previousBuild = pb | |
1703 | |
1704 def getPreviousBuild(self): | |
1705 return self.previousBuild | |
1706 | |
1707 class URLProducer: | |
1708 def getURLForThing(self, build): | |
1709 return 'http://myserver/mypath?build=765' | |
1710 | |
1711 class MyChannel: | |
1712 categories = None | |
1713 status = URLProducer() | |
1714 notify_events = {} | |
1715 | |
1716 def __init__(self, notify_events = {}): | |
1717 self.notify_events = notify_events | |
1718 self.showBlameList = False | |
1719 | |
1720 class MyContact(words.Contact): | |
1721 message = "" | |
1722 | |
1723 def __init__(self, channel = MyChannel()): | |
1724 words.Contact.__init__(self, channel) | |
1725 self.message = "" | |
1726 | |
1727 def subscribe_to_build_events(self): | |
1728 pass | |
1729 | |
1730 def unsubscribe_from_build_events(self): | |
1731 pass | |
1732 | |
1733 def send(self, msg): | |
1734 self.message += msg | |
1735 | |
1736 class MyIrcStatusBot(words.IrcStatusBot): | |
1737 def msg(self, dest, message): | |
1738 self.message = ['msg', dest, message] | |
1739 | |
1740 def notice(self, dest, message): | |
1741 self.message = ['notice', dest, message] | |
1742 | |
1743 class IrcStatusBotTester(unittest.TestCase): | |
1744 def testMsgOrNotice(self): | |
1745 channel = MyIrcStatusBot('alice', 'pa55w0od', ['#here'], | |
1746 builder.SUCCESS, None, {}) | |
1747 channel.msgOrNotice('bob', 'hello') | |
1748 self.failUnlessEqual(channel.message, ['msg', 'bob', 'hello']) | |
1749 | |
1750 channel.msgOrNotice('#here', 'hello') | |
1751 self.failUnlessEqual(channel.message, ['msg', '#here', 'hello']) | |
1752 | |
1753 channel.noticeOnChannel = True | |
1754 | |
1755 channel.msgOrNotice('bob', 'hello') | |
1756 self.failUnlessEqual(channel.message, ['msg', 'bob', 'hello']) | |
1757 | |
1758 channel.msgOrNotice('#here', 'hello') | |
1759 self.failUnlessEqual(channel.message, ['notice', '#here', 'hello']) | |
1760 | |
1761 class StepStatistics(unittest.TestCase): | |
1762 def testStepStatistics(self): | |
1763 status = builder.BuildStatus(builder.BuilderStatus("test"), 123) | |
1764 status.addStepWithName('step1') | |
1765 status.addStepWithName('step2') | |
1766 status.addStepWithName('step3') | |
1767 status.addStepWithName('step4') | |
1768 | |
1769 steps = status.getSteps() | |
1770 (step1, step2, step3, step4) = steps | |
1771 | |
1772 step1.setStatistic('test-prop', 1) | |
1773 step3.setStatistic('test-prop', 2) | |
1774 step4.setStatistic('test-prop', 4) | |
1775 | |
1776 step1.setStatistic('other-prop', 27) | |
1777 # Just to have some other properties around | |
1778 | |
1779 self.failUnlessEqual(step1.getStatistic('test-prop'), 1, | |
1780 'Retrieve an existing property') | |
1781 self.failUnlessEqual(step1.getStatistic('test-prop', 99), 1, | |
1782 "Don't default an existing property") | |
1783 self.failUnlessEqual(step2.getStatistic('test-prop', 99), 99, | |
1784 'Default a non-existant property') | |
1785 | |
1786 self.failUnlessEqual( | |
1787 status.getSummaryStatistic('test-prop', operator.add), 7, | |
1788 'Sum property across the build') | |
1789 | |
1790 self.failUnlessEqual( | |
1791 status.getSummaryStatistic('test-prop', operator.add, 13), 20, | |
1792 'Sum property across the build with initial value') | |
1793 | |
1794 class BuildExpectation(unittest.TestCase): | |
1795 class MyBuilderStatus: | |
1796 implements(interfaces.IBuilderStatus) | |
1797 | |
1798 def setSlavenames(self, slaveName): | |
1799 pass | |
1800 | |
1801 class MyBuilder(Builder): | |
1802 def __init__(self, name): | |
1803 Builder.__init__(self, { | |
1804 'name': name, | |
1805 'builddir': '/tmp/somewhere', | |
1806 'slavebuilddir': '/tmp/somewhere_else', | |
1807 'factory': 'aFactory' | |
1808 }, BuildExpectation.MyBuilderStatus()) | |
1809 | |
1810 class MyBuild(Build): | |
1811 def __init__(self, b): | |
1812 self.builder = b | |
1813 self.remote = None | |
1814 | |
1815 step1_progress = progress.StepProgress('step1', ['elapsed']) | |
1816 self.progress = progress.BuildProgress([step1_progress]) | |
1817 step1_progress.setBuildProgress(self.progress) | |
1818 | |
1819 step1_progress.start() | |
1820 sleep(1); | |
1821 step1_progress.finish() | |
1822 | |
1823 self.deferred = defer.Deferred() | |
1824 self.locks = [] | |
1825 self.build_status = builder.BuildStatus(b.builder_status, 1) | |
1826 | |
1827 | |
1828 def testBuildExpectation_BuildSuccess(self): | |
1829 b = BuildExpectation.MyBuilder("builder1") | |
1830 build = BuildExpectation.MyBuild(b) | |
1831 | |
1832 build.buildFinished(['sometext'], builder.SUCCESS) | |
1833 self.failIfEqual(b.expectations.expectedBuildTime(), 0, 'Non-Zero expect
ation for a failed build') | |
1834 | |
1835 def testBuildExpectation_BuildFailure(self): | |
1836 b = BuildExpectation.MyBuilder("builder1") | |
1837 build = BuildExpectation.MyBuild(b) | |
1838 | |
1839 build.buildFinished(['sometext'], builder.FAILURE) | |
1840 self.failUnlessEqual(b.expectations, None, 'Zero expectation for a faile
d build') | |
1841 | |
1842 class Pruning(unittest.TestCase): | |
1843 def runTest(self, files, buildHorizon, logHorizon): | |
1844 bstat = builder.BuilderStatus("foo") | |
1845 bstat.buildHorizon = buildHorizon | |
1846 bstat.logHorizon = logHorizon | |
1847 bstat.basedir = "prune-test" | |
1848 | |
1849 rmtree(bstat.basedir) | |
1850 os.mkdir(bstat.basedir) | |
1851 for filename in files: | |
1852 open(os.path.join(bstat.basedir, filename), "w").write("TEST") | |
1853 bstat.determineNextBuildNumber() | |
1854 | |
1855 bstat.prune() | |
1856 | |
1857 remaining = os.listdir(bstat.basedir) | |
1858 remaining.sort() | |
1859 return remaining | |
1860 | |
1861 files_base = [ | |
1862 '10', | |
1863 '11', | |
1864 '12', '12-log-bar', '12-log-foo', | |
1865 '13', '13-log-foo', | |
1866 '14', '14-log-bar', '14-log-foo', | |
1867 ] | |
1868 | |
1869 def test_rmlogs(self): | |
1870 remaining = self.runTest(self.files_base, 5, 2) | |
1871 self.failUnlessEqual(remaining, [ | |
1872 '10', | |
1873 '11', | |
1874 '12', | |
1875 '13', '13-log-foo', | |
1876 '14', '14-log-bar', '14-log-foo', | |
1877 ]) | |
1878 | |
1879 def test_rmbuilds(self): | |
1880 remaining = self.runTest(self.files_base, 2, 0) | |
1881 self.failUnlessEqual(remaining, [ | |
1882 '13', '13-log-foo', | |
1883 '14', '14-log-bar', '14-log-foo', | |
1884 ]) | |
1885 | |
1886 class EventGenerator(unittest.TestCase): | |
1887 def makeGenerator(self, reasons=[], minTime=0): | |
1888 b = MyBuilder("bname") | |
1889 b1 = b.newBuild() | |
1890 b1.setSourceStamp(SourceStamp(changes=[Change("foo", [], "")])) | |
1891 b1.buildStarted(b1, 1) | |
1892 b2 = b.newBuild() | |
1893 b2.setSourceStamp(SourceStamp(changes=[Change("bar", [], "")])) | |
1894 b2.buildStarted(b2, 2) | |
1895 return b.eventGenerator([], [], reasons, minTime) | |
1896 | |
1897 def testEventGenerator_Unfiltered(self): | |
1898 gen = self.makeGenerator() | |
1899 self.failUnlessEqual(len([e for e in gen]), 2) | |
1900 | |
1901 def testEventGenerator_Filtered(self): | |
1902 gen = self.makeGenerator(["foo"]) | |
1903 self.failUnlessEqual(len([e for e in gen]), 1) | |
1904 | |
1905 def testEventGenerator_MinTime(self): | |
1906 gen = self.makeGenerator([], 2) | |
1907 self.failUnlessEqual(len([e for e in gen]), 1) | |
OLD | NEW |