Add hooks for StepStatus.text and test2 to IStatusReceiver and test that they're...
[buildbot.git] / buildbot / test / test_status.py
blob22d59fad8967b4a98d7a5f0208ed2cecc40aba97
1 # -*- test-case-name: buildbot.test.test_status -*-
3 import email, os
4 import operator
6 from zope.interface import implements
7 from twisted.internet import defer, reactor
8 from twisted.trial import unittest
10 from buildbot import interfaces
11 from buildbot.sourcestamp import SourceStamp
12 from buildbot.process.base import BuildRequest
13 from buildbot.status import builder, base, words
14 from buildbot.changes.changes import Change
16 mail = None
17 try:
18 from buildbot.status import mail
19 except ImportError:
20 pass
21 from buildbot.status import progress, client # NEEDS COVERAGE
22 from buildbot.test.runutils import RunMixin
24 class MyStep:
25 build = None
26 def getName(self):
27 return "step"
29 class MyLogFileProducer(builder.LogFileProducer):
30 # The reactor.callLater(0) in LogFileProducer.resumeProducing is a bit of
31 # a nuisance from a testing point of view. This subclass adds a Deferred
32 # to that call so we can find out when it is complete.
33 def resumeProducing(self):
34 d = defer.Deferred()
35 reactor.callLater(0, self._resumeProducing, d)
36 return d
37 def _resumeProducing(self, d):
38 builder.LogFileProducer._resumeProducing(self)
39 reactor.callLater(0, d.callback, None)
41 class MyLog(builder.LogFile):
42 def __init__(self, basedir, name, text=None, step=None):
43 self.fakeBuilderBasedir = basedir
44 if not step:
45 step = MyStep()
46 builder.LogFile.__init__(self, step, name, name)
47 if text:
48 self.addStdout(text)
49 self.finish()
50 def getFilename(self):
51 return os.path.join(self.fakeBuilderBasedir, self.name)
53 def subscribeConsumer(self, consumer):
54 p = MyLogFileProducer(self, consumer)
55 d = p.resumeProducing()
56 return d
58 class MyHTMLLog(builder.HTMLLogFile):
59 def __init__(self, basedir, name, html):
60 step = MyStep()
61 builder.HTMLLogFile.__init__(self, step, name, name, html)
63 class MyLogSubscriber:
64 def __init__(self):
65 self.chunks = []
66 def logChunk(self, build, step, log, channel, text):
67 self.chunks.append((channel, text))
69 class MyLogConsumer:
70 def __init__(self, limit=None):
71 self.chunks = []
72 self.finished = False
73 self.limit = limit
74 def registerProducer(self, producer, streaming):
75 self.producer = producer
76 self.streaming = streaming
77 def unregisterProducer(self):
78 self.producer = None
79 def writeChunk(self, chunk):
80 self.chunks.append(chunk)
81 if self.limit:
82 self.limit -= 1
83 if self.limit == 0:
84 self.producer.pauseProducing()
85 def finish(self):
86 self.finished = True
88 if mail:
89 class MyMailer(mail.MailNotifier):
90 def sendMessage(self, m, recipients):
91 self.parent.messages.append((m, recipients))
93 class MyStatus:
94 def getBuildbotURL(self):
95 return self.url
96 def getURLForThing(self, thing):
97 return None
99 class MyBuilder(builder.BuilderStatus):
100 nextBuildNumber = 0
102 class MyBuild(builder.BuildStatus):
103 testlogs = []
104 def __init__(self, parent, number, results):
105 builder.BuildStatus.__init__(self, parent, number)
106 self.results = results
107 self.source = SourceStamp(revision="1.14")
108 self.reason = "build triggered by changes"
109 self.finished = True
110 def getLogs(self):
111 return self.testlogs
113 class MyLookup:
114 implements(interfaces.IEmailLookup)
116 def getAddress(self, user):
117 d = defer.Deferred()
118 # With me now is Mr Thomas Walters of West Hartlepool who is totally
119 # invisible.
120 if user == "Thomas_Walters":
121 d.callback(None)
122 else:
123 d.callback(user + "@" + "dev.com")
124 return d
126 class Mail(unittest.TestCase):
128 def setUp(self):
129 self.builder = MyBuilder("builder1")
131 def stall(self, res, timeout):
132 d = defer.Deferred()
133 reactor.callLater(timeout, d.callback, res)
134 return d
136 def makeBuild(self, number, results):
137 return MyBuild(self.builder, number, results)
139 def failUnlessIn(self, substring, string):
140 self.failUnless(string.find(substring) != -1,
141 "didn't see '%s' in '%s'" % (substring, string))
143 def getProjectName(self):
144 return "PROJECT"
146 def getBuildbotURL(self):
147 return "BUILDBOT_URL"
149 def getURLForThing(self, thing):
150 return None
152 def testBuild1(self):
153 mailer = MyMailer(fromaddr="buildbot@example.com",
154 extraRecipients=["recip@example.com",
155 "recip2@example.com"],
156 lookup=mail.Domain("dev.com"))
157 mailer.parent = self
158 mailer.status = self
159 self.messages = []
161 b1 = self.makeBuild(3, builder.SUCCESS)
162 b1.blamelist = ["bob"]
164 mailer.buildFinished("builder1", b1, b1.results)
165 self.failUnless(len(self.messages) == 1)
166 m,r = self.messages.pop()
167 t = m.as_string()
168 self.failUnlessIn("To: bob@dev.com, recip2@example.com, "
169 "recip@example.com\n", t)
170 self.failUnlessIn("From: buildbot@example.com\n", t)
171 self.failUnlessIn("Subject: buildbot success in PROJECT on builder1\n", t)
172 self.failUnlessIn("Date: ", t)
173 self.failUnlessIn("Build succeeded!\n", t)
174 self.failUnlessIn("Buildbot URL: BUILDBOT_URL\n", t)
176 def testBuild2(self):
177 mailer = MyMailer(fromaddr="buildbot@example.com",
178 extraRecipients=["recip@example.com",
179 "recip2@example.com"],
180 lookup="dev.com",
181 sendToInterestedUsers=False)
182 mailer.parent = self
183 mailer.status = self
184 self.messages = []
186 b1 = self.makeBuild(3, builder.SUCCESS)
187 b1.blamelist = ["bob"]
189 mailer.buildFinished("builder1", b1, b1.results)
190 self.failUnless(len(self.messages) == 1)
191 m,r = self.messages.pop()
192 t = m.as_string()
193 self.failUnlessIn("To: recip2@example.com, "
194 "recip@example.com\n", t)
195 self.failUnlessIn("From: buildbot@example.com\n", t)
196 self.failUnlessIn("Subject: buildbot success in PROJECT on builder1\n", t)
197 self.failUnlessIn("Build succeeded!\n", t)
198 self.failUnlessIn("Buildbot URL: BUILDBOT_URL\n", t)
200 def testBuildStatusCategory(self):
201 # a status client only interested in a category should only receive
202 # from that category
203 mailer = MyMailer(fromaddr="buildbot@example.com",
204 extraRecipients=["recip@example.com",
205 "recip2@example.com"],
206 lookup="dev.com",
207 sendToInterestedUsers=False,
208 categories=["debug"])
210 mailer.parent = self
211 mailer.status = self
212 self.messages = []
214 b1 = self.makeBuild(3, builder.SUCCESS)
215 b1.blamelist = ["bob"]
217 mailer.buildFinished("builder1", b1, b1.results)
218 self.failIf(self.messages)
220 def testBuilderCategory(self):
221 # a builder in a certain category should notify status clients that
222 # did not list categories, or categories including this one
223 mailer1 = MyMailer(fromaddr="buildbot@example.com",
224 extraRecipients=["recip@example.com",
225 "recip2@example.com"],
226 lookup="dev.com",
227 sendToInterestedUsers=False)
228 mailer2 = MyMailer(fromaddr="buildbot@example.com",
229 extraRecipients=["recip@example.com",
230 "recip2@example.com"],
231 lookup="dev.com",
232 sendToInterestedUsers=False,
233 categories=["active"])
234 mailer3 = MyMailer(fromaddr="buildbot@example.com",
235 extraRecipients=["recip@example.com",
236 "recip2@example.com"],
237 lookup="dev.com",
238 sendToInterestedUsers=False,
239 categories=["active", "debug"])
241 builderd = MyBuilder("builder2", "debug")
243 mailer1.parent = self
244 mailer1.status = self
245 mailer2.parent = self
246 mailer2.status = self
247 mailer3.parent = self
248 mailer3.status = self
249 self.messages = []
251 t = mailer1.builderAdded("builder2", builderd)
252 self.assertEqual(len(mailer1.watched), 1)
253 self.assertEqual(t, mailer1)
254 t = mailer2.builderAdded("builder2", builderd)
255 self.assertEqual(len(mailer2.watched), 0)
256 self.assertEqual(t, None)
257 t = mailer3.builderAdded("builder2", builderd)
258 self.assertEqual(len(mailer3.watched), 1)
259 self.assertEqual(t, mailer3)
261 b2 = MyBuild(builderd, 3, builder.SUCCESS)
262 b2.blamelist = ["bob"]
264 mailer1.buildFinished("builder2", b2, b2.results)
265 self.failUnlessEqual(len(self.messages), 1)
266 self.messages = []
267 mailer2.buildFinished("builder2", b2, b2.results)
268 self.failUnlessEqual(len(self.messages), 0)
269 self.messages = []
270 mailer3.buildFinished("builder2", b2, b2.results)
271 self.failUnlessEqual(len(self.messages), 1)
273 def testFailure(self):
274 mailer = MyMailer(fromaddr="buildbot@example.com", mode="problem",
275 extraRecipients=["recip@example.com",
276 "recip2@example.com"],
277 lookup=MyLookup())
278 mailer.parent = self
279 mailer.status = self
280 self.messages = []
282 b1 = self.makeBuild(3, builder.SUCCESS)
283 b1.blamelist = ["dev1", "dev2"]
284 b2 = self.makeBuild(4, builder.FAILURE)
285 b2.setText(["snarkleack", "polarization", "failed"])
286 b2.blamelist = ["dev3", "dev3", "dev3", "dev4",
287 "Thomas_Walters"]
288 mailer.buildFinished("builder1", b1, b1.results)
289 self.failIf(self.messages)
290 mailer.buildFinished("builder1", b2, b2.results)
291 self.failUnless(len(self.messages) == 1)
292 m,r = self.messages.pop()
293 t = m.as_string()
294 self.failUnlessIn("To: dev3@dev.com, dev4@dev.com, "
295 "recip2@example.com, recip@example.com\n", t)
296 self.failUnlessIn("From: buildbot@example.com\n", t)
297 self.failUnlessIn("Subject: buildbot failure in PROJECT on builder1\n", t)
298 self.failUnlessIn("The Buildbot has detected a new failure", t)
299 self.failUnlessIn("BUILD FAILED: snarkleack polarization failed\n", t)
300 self.failUnlessEqual(r, ["dev3@dev.com", "dev4@dev.com",
301 "recip2@example.com", "recip@example.com"])
303 def testLogs(self):
304 basedir = "test_status_logs"
305 os.mkdir(basedir)
306 mailer = MyMailer(fromaddr="buildbot@example.com", addLogs=True,
307 extraRecipients=["recip@example.com",
308 "recip2@example.com"])
309 mailer.parent = self
310 mailer.status = self
311 self.messages = []
313 b1 = self.makeBuild(3, builder.WARNINGS)
314 b1.testlogs = [MyLog(basedir, 'compile', "Compile log here\n"),
315 MyLog(basedir,
316 'test', "Test log here\nTest 4 failed\n"),
318 b1.text = ["unusual", "gnarzzler", "output"]
319 mailer.buildFinished("builder1", b1, b1.results)
320 self.failUnless(len(self.messages) == 1)
321 m,r = self.messages.pop()
322 t = m.as_string()
323 self.failUnlessIn("Subject: buildbot warnings in PROJECT on builder1\n", t)
324 m2 = email.message_from_string(t)
325 p = m2.get_payload()
326 self.failUnlessEqual(len(p), 3)
328 self.failUnlessIn("Build Had Warnings: unusual gnarzzler output\n",
329 p[0].get_payload())
331 self.failUnlessEqual(p[1].get_filename(), "step.compile")
332 self.failUnlessEqual(p[1].get_payload(), "Compile log here\n")
334 self.failUnlessEqual(p[2].get_filename(), "step.test")
335 self.failUnlessIn("Test log here\n", p[2].get_payload())
337 def testMail(self):
338 basedir = "test_status_mail"
339 os.mkdir(basedir)
340 dest = os.environ.get("BUILDBOT_TEST_MAIL")
341 if not dest:
342 raise unittest.SkipTest("define BUILDBOT_TEST_MAIL=dest to run this")
343 mailer = mail.MailNotifier(fromaddr="buildbot@example.com",
344 addLogs=True,
345 extraRecipients=[dest])
346 s = MyStatus()
347 s.url = "project URL"
348 mailer.status = s
350 b1 = self.makeBuild(3, builder.SUCCESS)
351 b1.testlogs = [MyLog(basedir, 'compile', "Compile log here\n"),
352 MyLog(basedir,
353 'test', "Test log here\nTest 4 failed\n"),
356 print "sending mail to", dest
357 d = mailer.buildFinished("builder1", b1, b1.results)
358 # When this fires, the mail has been sent, but the SMTP connection is
359 # still up (because smtp.sendmail relies upon the server to hang up).
360 # Spin for a moment to avoid the "unclean reactor" warning that Trial
361 # gives us if we finish before the socket is disconnected. Really,
362 # sendmail() ought to hang up the connection once it is finished:
363 # otherwise a malicious SMTP server could make us consume lots of
364 # memory.
365 d.addCallback(self.stall, 0.1)
366 return d
368 if not mail:
369 Mail.skip = "the Twisted Mail package is not installed"
371 class Progress(unittest.TestCase):
372 def testWavg(self):
373 bp = progress.BuildProgress([])
374 e = progress.Expectations(bp)
375 # wavg(old, current)
376 self.failUnlessEqual(e.wavg(None, None), None)
377 self.failUnlessEqual(e.wavg(None, 3), 3)
378 self.failUnlessEqual(e.wavg(3, None), 3)
379 self.failUnlessEqual(e.wavg(3, 4), 3.5)
380 e.decay = 0.1
381 self.failUnlessEqual(e.wavg(3, 4), 3.1)
384 class Results(unittest.TestCase):
386 def testAddResults(self):
387 b = builder.BuildStatus(builder.BuilderStatus("test"), 12)
388 testname = ("buildbot", "test", "test_status", "Results",
389 "testAddResults")
390 r1 = builder.TestResult(name=testname,
391 results=builder.SUCCESS,
392 text=["passed"],
393 logs={'output': ""},
395 b.addTestResult(r1)
397 res = b.getTestResults()
398 self.failUnlessEqual(res.keys(), [testname])
399 t = res[testname]
400 self.failUnless(interfaces.ITestResult.providedBy(t))
401 self.failUnlessEqual(t.getName(), testname)
402 self.failUnlessEqual(t.getResults(), builder.SUCCESS)
403 self.failUnlessEqual(t.getText(), ["passed"])
404 self.failUnlessEqual(t.getLogs(), {'output': ""})
406 class Log(unittest.TestCase):
407 def setUpClass(self):
408 self.basedir = "status_log_add"
409 os.mkdir(self.basedir)
411 def testAdd(self):
412 l = MyLog(self.basedir, "compile", step=13)
413 self.failUnlessEqual(l.getName(), "compile")
414 self.failUnlessEqual(l.getStep(), 13)
415 l.addHeader("HEADER\n")
416 l.addStdout("Some text\n")
417 l.addStderr("Some error\n")
418 l.addStdout("Some more text\n")
419 self.failIf(l.isFinished())
420 l.finish()
421 self.failUnless(l.isFinished())
422 self.failUnlessEqual(l.getText(),
423 "Some text\nSome error\nSome more text\n")
424 self.failUnlessEqual(l.getTextWithHeaders(),
425 "HEADER\n" +
426 "Some text\nSome error\nSome more text\n")
427 self.failUnlessEqual(len(list(l.getChunks())), 4)
429 self.failUnless(l.hasContents())
430 os.unlink(l.getFilename())
431 self.failIf(l.hasContents())
433 def TODO_testDuplicate(self):
434 # create multiple logs for the same step with the same logname, make
435 # sure their on-disk filenames are suitably uniquified. This
436 # functionality actually lives in BuildStepStatus and BuildStatus, so
437 # this test must involve more than just the MyLog class.
439 # naieve approach, doesn't work
440 l1 = MyLog(self.basedir, "duplicate")
441 l1.addStdout("Some text\n")
442 l1.finish()
443 l2 = MyLog(self.basedir, "duplicate")
444 l2.addStdout("Some more text\n")
445 l2.finish()
446 self.failIfEqual(l1.getFilename(), l2.getFilename())
448 def testMerge1(self):
449 l = MyLog(self.basedir, "merge1")
450 l.addHeader("HEADER\n")
451 l.addStdout("Some text\n")
452 l.addStdout("Some more text\n")
453 l.addStdout("more\n")
454 l.finish()
455 self.failUnlessEqual(l.getText(),
456 "Some text\nSome more text\nmore\n")
457 self.failUnlessEqual(l.getTextWithHeaders(),
458 "HEADER\n" +
459 "Some text\nSome more text\nmore\n")
460 self.failUnlessEqual(len(list(l.getChunks())), 2)
462 def testMerge2(self):
463 l = MyLog(self.basedir, "merge2")
464 l.addHeader("HEADER\n")
465 for i in xrange(1000):
466 l.addStdout("aaaa")
467 for i in xrange(30):
468 l.addStderr("bbbb")
469 for i in xrange(10):
470 l.addStdout("cc")
471 target = 1000*"aaaa" + 30 * "bbbb" + 10 * "cc"
472 self.failUnlessEqual(len(l.getText()), len(target))
473 self.failUnlessEqual(l.getText(), target)
474 l.finish()
475 self.failUnlessEqual(len(l.getText()), len(target))
476 self.failUnlessEqual(l.getText(), target)
477 self.failUnlessEqual(len(list(l.getChunks())), 4)
479 def testMerge3(self):
480 l = MyLog(self.basedir, "merge3")
481 l.chunkSize = 100
482 l.addHeader("HEADER\n")
483 for i in xrange(8):
484 l.addStdout(10*"a")
485 for i in xrange(8):
486 l.addStdout(10*"a")
487 self.failUnlessEqual(list(l.getChunks()),
488 [(builder.HEADER, "HEADER\n"),
489 (builder.STDOUT, 100*"a"),
490 (builder.STDOUT, 60*"a")])
491 l.finish()
492 self.failUnlessEqual(l.getText(), 160*"a")
494 def testReadlines(self):
495 l = MyLog(self.basedir, "chunks")
496 l.addHeader("HEADER\n") # should be ignored
497 l.addStdout("Some text\n")
498 l.addStdout("Some More Text\nAnd Some More\n")
499 l.addStderr("Some Stderr\n")
500 l.addStdout("Last line\n")
501 l.finish()
502 alllines = list(l.readlines())
503 self.failUnlessEqual(len(alllines), 4)
504 self.failUnlessEqual(alllines[0], "Some text\n")
505 self.failUnlessEqual(alllines[2], "And Some More\n")
506 self.failUnlessEqual(alllines[3], "Last line\n")
507 stderr = list(l.readlines(interfaces.LOG_CHANNEL_STDERR))
508 self.failUnlessEqual(len(stderr), 1)
509 self.failUnlessEqual(stderr[0], "Some Stderr\n")
510 lines = l.readlines()
511 if False: # TODO: l.readlines() is not yet an iterator
512 # verify that it really is an iterator
513 line0 = lines.next()
514 self.failUnlessEqual(line0, "Some text\n")
515 line1 = lines.next()
516 line2 = lines.next()
517 self.failUnlessEqual(line2, "And Some More\n")
520 def testChunks(self):
521 l = MyLog(self.basedir, "chunks")
522 c1 = l.getChunks()
523 l.addHeader("HEADER\n")
524 l.addStdout("Some text\n")
525 self.failUnlessEqual("".join(l.getChunks(onlyText=True)),
526 "HEADER\nSome text\n")
527 c2 = l.getChunks()
529 l.addStdout("Some more text\n")
530 self.failUnlessEqual("".join(l.getChunks(onlyText=True)),
531 "HEADER\nSome text\nSome more text\n")
532 c3 = l.getChunks()
534 l.addStdout("more\n")
535 l.finish()
537 self.failUnlessEqual(list(c1), [])
538 self.failUnlessEqual(list(c2), [(builder.HEADER, "HEADER\n"),
539 (builder.STDOUT, "Some text\n")])
540 self.failUnlessEqual(list(c3), [(builder.HEADER, "HEADER\n"),
541 (builder.STDOUT,
542 "Some text\nSome more text\n")])
544 self.failUnlessEqual(l.getText(),
545 "Some text\nSome more text\nmore\n")
546 self.failUnlessEqual(l.getTextWithHeaders(),
547 "HEADER\n" +
548 "Some text\nSome more text\nmore\n")
549 self.failUnlessEqual(len(list(l.getChunks())), 2)
551 def testUpgrade(self):
552 l = MyLog(self.basedir, "upgrade")
553 l.addHeader("HEADER\n")
554 l.addStdout("Some text\n")
555 l.addStdout("Some more text\n")
556 l.addStdout("more\n")
557 l.finish()
558 self.failUnless(l.hasContents())
559 # now doctor it to look like a 0.6.4-era non-upgraded logfile
560 l.entries = list(l.getChunks())
561 del l.filename
562 os.unlink(l.getFilename())
563 # now make sure we can upgrade it
564 l.upgrade("upgrade")
565 self.failUnlessEqual(l.getText(),
566 "Some text\nSome more text\nmore\n")
567 self.failUnlessEqual(len(list(l.getChunks())), 2)
568 self.failIf(l.entries)
570 # now, do it again, but make it look like an upgraded 0.6.4 logfile
571 # (i.e. l.filename is missing, but the contents are there on disk)
572 l.entries = list(l.getChunks())
573 del l.filename
574 l.upgrade("upgrade")
575 self.failUnlessEqual(l.getText(),
576 "Some text\nSome more text\nmore\n")
577 self.failUnlessEqual(len(list(l.getChunks())), 2)
578 self.failIf(l.entries)
579 self.failUnless(l.hasContents())
581 def testHTMLUpgrade(self):
582 l = MyHTMLLog(self.basedir, "upgrade", "log contents")
583 l.upgrade("filename")
585 def testSubscribe(self):
586 l1 = MyLog(self.basedir, "subscribe1")
587 l1.finish()
588 self.failUnless(l1.isFinished())
590 s = MyLogSubscriber()
591 l1.subscribe(s, True)
592 l1.unsubscribe(s)
593 self.failIf(s.chunks)
595 s = MyLogSubscriber()
596 l1.subscribe(s, False)
597 l1.unsubscribe(s)
598 self.failIf(s.chunks)
600 finished = []
601 l2 = MyLog(self.basedir, "subscribe2")
602 l2.waitUntilFinished().addCallback(finished.append)
603 l2.addHeader("HEADER\n")
604 s1 = MyLogSubscriber()
605 l2.subscribe(s1, True)
606 s2 = MyLogSubscriber()
607 l2.subscribe(s2, False)
608 self.failUnlessEqual(s1.chunks, [(builder.HEADER, "HEADER\n")])
609 self.failUnlessEqual(s2.chunks, [])
611 l2.addStdout("Some text\n")
612 self.failUnlessEqual(s1.chunks, [(builder.HEADER, "HEADER\n"),
613 (builder.STDOUT, "Some text\n")])
614 self.failUnlessEqual(s2.chunks, [(builder.STDOUT, "Some text\n")])
615 l2.unsubscribe(s1)
617 l2.addStdout("Some more text\n")
618 self.failUnlessEqual(s1.chunks, [(builder.HEADER, "HEADER\n"),
619 (builder.STDOUT, "Some text\n")])
620 self.failUnlessEqual(s2.chunks, [(builder.STDOUT, "Some text\n"),
621 (builder.STDOUT, "Some more text\n"),
623 self.failIf(finished)
624 l2.finish()
625 self.failUnlessEqual(finished, [l2])
627 def testConsumer(self):
628 l1 = MyLog(self.basedir, "consumer1")
629 l1.finish()
630 self.failUnless(l1.isFinished())
632 s = MyLogConsumer()
633 d = l1.subscribeConsumer(s)
634 d.addCallback(self._testConsumer_1, s)
635 return d
636 testConsumer.timeout = 5
637 def _testConsumer_1(self, res, s):
638 self.failIf(s.chunks)
639 self.failUnless(s.finished)
640 self.failIf(s.producer) # producer should be registered and removed
642 l2 = MyLog(self.basedir, "consumer2")
643 l2.addHeader("HEADER\n")
644 l2.finish()
645 self.failUnless(l2.isFinished())
647 s = MyLogConsumer()
648 d = l2.subscribeConsumer(s)
649 d.addCallback(self._testConsumer_2, s)
650 return d
651 def _testConsumer_2(self, res, s):
652 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n")])
653 self.failUnless(s.finished)
654 self.failIf(s.producer) # producer should be registered and removed
657 l2 = MyLog(self.basedir, "consumer3")
658 l2.chunkSize = 1000
659 l2.addHeader("HEADER\n")
660 l2.addStdout(800*"a")
661 l2.addStdout(800*"a") # should now have two chunks on disk, 1000+600
662 l2.addStdout(800*"b") # HEADER,1000+600*a on disk, 800*a in memory
663 l2.addStdout(800*"b") # HEADER,1000+600*a,1000+600*b on disk
664 l2.addStdout(200*"c") # HEADER,1000+600*a,1000+600*b on disk,
665 # 200*c in memory
667 s = MyLogConsumer(limit=1)
668 d = l2.subscribeConsumer(s)
669 d.addCallback(self._testConsumer_3, l2, s)
670 return d
671 def _testConsumer_3(self, res, l2, s):
672 self.failUnless(s.streaming)
673 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n")])
674 s.limit = 1
675 d = s.producer.resumeProducing()
676 d.addCallback(self._testConsumer_4, l2, s)
677 return d
678 def _testConsumer_4(self, res, l2, s):
679 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n"),
680 (builder.STDOUT, 1000*"a"),
682 s.limit = None
683 d = s.producer.resumeProducing()
684 d.addCallback(self._testConsumer_5, l2, s)
685 return d
686 def _testConsumer_5(self, res, l2, s):
687 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n"),
688 (builder.STDOUT, 1000*"a"),
689 (builder.STDOUT, 600*"a"),
690 (builder.STDOUT, 1000*"b"),
691 (builder.STDOUT, 600*"b"),
692 (builder.STDOUT, 200*"c")])
693 l2.addStdout(1000*"c") # HEADER,1600*a,1600*b,1200*c on disk
694 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n"),
695 (builder.STDOUT, 1000*"a"),
696 (builder.STDOUT, 600*"a"),
697 (builder.STDOUT, 1000*"b"),
698 (builder.STDOUT, 600*"b"),
699 (builder.STDOUT, 200*"c"),
700 (builder.STDOUT, 1000*"c")])
701 l2.finish()
702 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n"),
703 (builder.STDOUT, 1000*"a"),
704 (builder.STDOUT, 600*"a"),
705 (builder.STDOUT, 1000*"b"),
706 (builder.STDOUT, 600*"b"),
707 (builder.STDOUT, 200*"c"),
708 (builder.STDOUT, 1000*"c")])
709 self.failIf(s.producer)
710 self.failUnless(s.finished)
712 def testLargeSummary(self):
713 bigtext = "a" * 200000 # exceed the NetstringReceiver 100KB limit
714 l = MyLog(self.basedir, "large", bigtext)
715 s = MyLogConsumer()
716 d = l.subscribeConsumer(s)
717 def _check(res):
718 for ctype,chunk in s.chunks:
719 self.failUnless(len(chunk) < 100000)
720 merged = "".join([c[1] for c in s.chunks])
721 self.failUnless(merged == bigtext)
722 d.addCallback(_check)
723 # when this fails, it fails with a timeout, and there is an exception
724 # sent to log.err(). This AttributeError exception is in
725 # NetstringReceiver.dataReceived where it does
726 # self.transport.loseConnection() because of the NetstringParseError,
727 # however self.transport is None
728 return d
729 testLargeSummary.timeout = 5
731 config_base = """
732 from buildbot.process import factory
733 from buildbot.steps import dummy
734 from buildbot.buildslave import BuildSlave
735 s = factory.s
737 f1 = factory.QuickBuildFactory('fakerep', 'cvsmodule', configure=None)
739 f2 = factory.BuildFactory([
740 s(dummy.Dummy, timeout=1),
741 s(dummy.RemoteDummy, timeout=2),
744 BuildmasterConfig = c = {}
745 c['slaves'] = [BuildSlave('bot1', 'sekrit')]
746 c['schedulers'] = []
747 c['builders'] = []
748 c['builders'].append({'name':'quick', 'slavename':'bot1',
749 'builddir': 'quickdir', 'factory': f1})
750 c['slavePortnum'] = 0
753 config_2 = config_base + """
754 c['builders'] = [{'name': 'dummy', 'slavename': 'bot1',
755 'builddir': 'dummy1', 'factory': f2},
756 {'name': 'testdummy', 'slavename': 'bot1',
757 'builddir': 'dummy2', 'factory': f2, 'category': 'test'}]
760 class STarget(base.StatusReceiver):
761 debug = False
763 def __init__(self, mode):
764 self.mode = mode
765 self.events = []
766 def announce(self):
767 if self.debug:
768 print self.events[-1]
770 def builderAdded(self, name, builder):
771 self.events.append(("builderAdded", name, builder))
772 self.announce()
773 if "builder" in self.mode:
774 return self
775 def builderChangedState(self, name, state):
776 self.events.append(("builderChangedState", name, state))
777 self.announce()
778 def buildStarted(self, name, build):
779 self.events.append(("buildStarted", name, build))
780 self.announce()
781 if "eta" in self.mode:
782 self.eta_build = build.getETA()
783 if "build" in self.mode:
784 return self
785 def buildETAUpdate(self, build, ETA):
786 self.events.append(("buildETAUpdate", build, ETA))
787 self.announce()
788 def stepStarted(self, build, step):
789 self.events.append(("stepStarted", build, step))
790 self.announce()
791 if 0 and "eta" in self.mode:
792 print "TIMES", step.getTimes()
793 print "ETA", step.getETA()
794 print "EXP", step.getExpectations()
795 if "step" in self.mode:
796 return self
797 def stepTextChanged(self, build, step, text):
798 self.events.append(("stepTextChanged", step, text))
799 def stepText2Changed(self, build, step, text2):
800 self.events.append(("stepText2Changed", step, text2))
801 def stepETAUpdate(self, build, step, ETA, expectations):
802 self.events.append(("stepETAUpdate", build, step, ETA, expectations))
803 self.announce()
804 def logStarted(self, build, step, log):
805 self.events.append(("logStarted", build, step, log))
806 self.announce()
807 def logFinished(self, build, step, log):
808 self.events.append(("logFinished", build, step, log))
809 self.announce()
810 def stepFinished(self, build, step, results):
811 self.events.append(("stepFinished", build, step, results))
812 if 0 and "eta" in self.mode:
813 print "post-EXP", step.getExpectations()
814 self.announce()
815 def buildFinished(self, name, build, results):
816 self.events.append(("buildFinished", name, build, results))
817 self.announce()
818 def builderRemoved(self, name):
819 self.events.append(("builderRemoved", name))
820 self.announce()
822 class Subscription(RunMixin, unittest.TestCase):
823 # verify that StatusTargets can subscribe/unsubscribe properly
825 def testSlave(self):
826 m = self.master
827 s = m.getStatus()
828 self.t1 = t1 = STarget(["builder"])
829 #t1.debug = True; print
830 s.subscribe(t1)
831 self.failUnlessEqual(len(t1.events), 0)
833 self.t3 = t3 = STarget(["builder", "build", "step"])
834 s.subscribe(t3)
836 m.loadConfig(config_2)
837 m.readConfig = True
838 m.startService()
840 self.failUnlessEqual(len(t1.events), 4)
841 self.failUnlessEqual(t1.events[0][0:2], ("builderAdded", "dummy"))
842 self.failUnlessEqual(t1.events[1],
843 ("builderChangedState", "dummy", "offline"))
844 self.failUnlessEqual(t1.events[2][0:2], ("builderAdded", "testdummy"))
845 self.failUnlessEqual(t1.events[3],
846 ("builderChangedState", "testdummy", "offline"))
847 t1.events = []
849 self.failUnlessEqual(s.getBuilderNames(), ["dummy", "testdummy"])
850 self.failUnlessEqual(s.getBuilderNames(categories=['test']),
851 ["testdummy"])
852 self.s1 = s1 = s.getBuilder("dummy")
853 self.failUnlessEqual(s1.getName(), "dummy")
854 self.failUnlessEqual(s1.getState(), ("offline", []))
855 self.failUnlessEqual(s1.getCurrentBuilds(), [])
856 self.failUnlessEqual(s1.getLastFinishedBuild(), None)
857 self.failUnlessEqual(s1.getBuild(-1), None)
858 #self.failUnlessEqual(s1.getEvent(-1), foo("created"))
860 # status targets should, upon being subscribed, immediately get a
861 # list of all current builders matching their category
862 self.t2 = t2 = STarget([])
863 s.subscribe(t2)
864 self.failUnlessEqual(len(t2.events), 2)
865 self.failUnlessEqual(t2.events[0][0:2], ("builderAdded", "dummy"))
866 self.failUnlessEqual(t2.events[1][0:2], ("builderAdded", "testdummy"))
868 d = self.connectSlave(builders=["dummy", "testdummy"])
869 d.addCallback(self._testSlave_1, t1)
870 return d
872 def _testSlave_1(self, res, t1):
873 self.failUnlessEqual(len(t1.events), 2)
874 self.failUnlessEqual(t1.events[0],
875 ("builderChangedState", "dummy", "idle"))
876 self.failUnlessEqual(t1.events[1],
877 ("builderChangedState", "testdummy", "idle"))
878 t1.events = []
880 c = interfaces.IControl(self.master)
881 req = BuildRequest("forced build for testing", SourceStamp())
882 c.getBuilder("dummy").requestBuild(req)
883 d = req.waitUntilFinished()
884 d2 = self.master.botmaster.waitUntilBuilderIdle("dummy")
885 dl = defer.DeferredList([d, d2])
886 dl.addCallback(self._testSlave_2)
887 return dl
889 def _testSlave_2(self, res):
890 # t1 subscribes to builds, but not anything lower-level
891 ev = self.t1.events
892 self.failUnlessEqual(len(ev), 4)
893 self.failUnlessEqual(ev[0][0:3],
894 ("builderChangedState", "dummy", "building"))
895 self.failUnlessEqual(ev[1][0], "buildStarted")
896 self.failUnlessEqual(ev[2][0:2]+ev[2][3:4],
897 ("buildFinished", "dummy", builder.SUCCESS))
898 self.failUnlessEqual(ev[3][0:3],
899 ("builderChangedState", "dummy", "idle"))
901 self.failUnlessEqual([ev[0] for ev in self.t3.events],
902 ["builderAdded",
903 "builderChangedState", # offline
904 "builderAdded",
905 "builderChangedState", # idle
906 "builderChangedState", # offline
907 "builderChangedState", # idle
908 "builderChangedState", # building
909 "buildStarted",
910 "stepStarted", "stepETAUpdate",
911 "stepTextChanged", "stepFinished",
912 "stepStarted", "stepETAUpdate",
913 "stepTextChanged", "logStarted", "logFinished",
914 "stepTextChanged", "stepText2Changed",
915 "stepFinished",
916 "buildFinished",
917 "builderChangedState", # idle
920 b = self.s1.getLastFinishedBuild()
921 self.failUnless(b)
922 self.failUnlessEqual(b.getBuilder().getName(), "dummy")
923 self.failUnlessEqual(b.getNumber(), 0)
924 self.failUnlessEqual(b.getSourceStamp().branch, None)
925 self.failUnlessEqual(b.getSourceStamp().patch, None)
926 self.failUnlessEqual(b.getSourceStamp().revision, None)
927 self.failUnlessEqual(b.getReason(), "forced build for testing")
928 self.failUnlessEqual(b.getChanges(), ())
929 self.failUnlessEqual(b.getResponsibleUsers(), [])
930 self.failUnless(b.isFinished())
931 self.failUnlessEqual(b.getText(), ['build', 'successful'])
932 self.failUnlessEqual(b.getColor(), "green")
933 self.failUnlessEqual(b.getResults(), builder.SUCCESS)
935 steps = b.getSteps()
936 self.failUnlessEqual(len(steps), 2)
938 eta = 0
939 st1 = steps[0]
940 self.failUnlessEqual(st1.getName(), "dummy")
941 self.failUnless(st1.isFinished())
942 self.failUnlessEqual(st1.getText(), ["delay", "1 secs"])
943 start,finish = st1.getTimes()
944 self.failUnless(0.5 < (finish-start) < 10)
945 self.failUnlessEqual(st1.getExpectations(), [])
946 self.failUnlessEqual(st1.getLogs(), [])
947 eta += finish-start
949 st2 = steps[1]
950 self.failUnlessEqual(st2.getName(), "remote dummy")
951 self.failUnless(st2.isFinished())
952 self.failUnlessEqual(st2.getText(),
953 ["remote", "delay", "2 secs"])
954 start,finish = st2.getTimes()
955 self.failUnless(1.5 < (finish-start) < 10)
956 eta += finish-start
957 self.failUnlessEqual(st2.getExpectations(), [('output', 38, None)])
958 logs = st2.getLogs()
959 self.failUnlessEqual(len(logs), 1)
960 self.failUnlessEqual(logs[0].getName(), "stdio")
961 self.failUnlessEqual(logs[0].getText(), "data")
963 self.eta = eta
964 # now we run it a second time, and we should have an ETA
966 self.t4 = t4 = STarget(["builder", "build", "eta"])
967 self.master.getStatus().subscribe(t4)
968 c = interfaces.IControl(self.master)
969 req = BuildRequest("forced build for testing", SourceStamp())
970 c.getBuilder("dummy").requestBuild(req)
971 d = req.waitUntilFinished()
972 d2 = self.master.botmaster.waitUntilBuilderIdle("dummy")
973 dl = defer.DeferredList([d, d2])
974 dl.addCallback(self._testSlave_3)
975 return dl
977 def _testSlave_3(self, res):
978 t4 = self.t4
979 eta = self.eta
980 self.failUnless(eta-1 < t4.eta_build < eta+1, # should be 3 seconds
981 "t4.eta_build was %g, not in (%g,%g)"
982 % (t4.eta_build, eta-1, eta+1))
985 class Client(unittest.TestCase):
986 def testAdaptation(self):
987 b = builder.BuilderStatus("bname")
988 b2 = client.makeRemote(b)
989 self.failUnless(isinstance(b2, client.RemoteBuilder))
990 b3 = client.makeRemote(None)
991 self.failUnless(b3 is None)
994 class ContactTester(unittest.TestCase):
995 def test_notify_invalid_syntax(self):
996 irc = MyContact()
997 self.assertRaises(words.UsageError, lambda args, who: irc.command_NOTIFY(args, who), "", "mynick")
999 def test_notify_list(self):
1000 irc = MyContact()
1001 irc.command_NOTIFY("list", "mynick")
1002 self.failUnlessEqual(irc.message, "The following events are being notified: []", "empty notify list")
1004 irc.message = ""
1005 irc.command_NOTIFY("on started", "mynick")
1006 self.failUnlessEqual(irc.message, "The following events are being notified: ['started']", "on started")
1008 irc.message = ""
1009 irc.command_NOTIFY("on finished", "mynick")
1010 self.failUnlessEqual(irc.message, "The following events are being notified: ['started', 'finished']", "on finished")
1012 irc.message = ""
1013 irc.command_NOTIFY("off", "mynick")
1014 self.failUnlessEqual(irc.message, "The following events are being notified: []", "off all")
1016 irc.message = ""
1017 irc.command_NOTIFY("on", "mynick")
1018 self.failUnlessEqual(irc.message, "The following events are being notified: ['started', 'finished']", "on default set")
1020 irc.message = ""
1021 irc.command_NOTIFY("off started", "mynick")
1022 self.failUnlessEqual(irc.message, "The following events are being notified: ['finished']", "off started")
1024 irc.message = ""
1025 irc.command_NOTIFY("on success failed exception", "mynick")
1026 self.failUnlessEqual(irc.message, "The following events are being notified: ['failed', 'finished', 'exception', 'success']", "on multiple events")
1028 def test_notification_default(self):
1029 irc = MyContact()
1031 my_builder = MyBuilder("builder78")
1032 my_build = MyIrcBuild(my_builder, 23, builder.SUCCESS)
1034 irc.buildStarted(my_builder.getName(), my_build)
1035 self.failUnlessEqual(irc.message, "", "No notification with default settings")
1037 irc.buildFinished(my_builder.getName(), my_build, None)
1038 self.failUnlessEqual(irc.message, "", "No notification with default settings")
1040 def test_notification_started(self):
1041 irc = MyContact()
1043 my_builder = MyBuilder("builder78")
1044 my_build = MyIrcBuild(my_builder, 23, builder.SUCCESS)
1045 my_build.changes = (
1046 Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 123),
1047 Change(who = 'author2', files = ['file2'], comments = 'comment2', revision = 456),
1050 irc.command_NOTIFY("on started", "mynick")
1052 irc.message = ""
1053 irc.buildStarted(my_builder.getName(), my_build)
1054 self.failUnlessEqual(irc.message, "build #23 of builder78 started including [123, 456]", "Start notification generated with notify_events=['started']")
1056 irc.message = ""
1057 irc.buildFinished(my_builder.getName(), my_build, None)
1058 self.failUnlessEqual(irc.message, "", "No finished notification with notify_events=['started']")
1060 def test_notification_finished(self):
1061 irc = MyContact()
1063 my_builder = MyBuilder("builder834")
1064 my_build = MyIrcBuild(my_builder, 862, builder.SUCCESS)
1065 my_build.changes = (
1066 Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
1069 irc.command_NOTIFY("on finished", "mynick")
1071 irc.message = ""
1072 irc.buildStarted(my_builder.getName(), my_build)
1073 self.failUnlessEqual(irc.message, "", "No started notification with notify_events=['finished']")
1075 irc.message = ""
1076 irc.buildFinished(my_builder.getName(), my_build, None)
1077 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']")
1079 def test_notification_success(self):
1080 irc = MyContact()
1082 my_builder = MyBuilder("builder834")
1083 my_build = MyIrcBuild(my_builder, 862, builder.SUCCESS)
1084 my_build.changes = (
1085 Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
1088 irc.command_NOTIFY("on success", "mynick")
1090 irc.message = ""
1091 irc.buildStarted(my_builder.getName(), my_build)
1092 self.failUnlessEqual(irc.message, "", "No started notification with notify_events=['success']")
1094 irc.message = ""
1095 irc.buildFinished(my_builder.getName(), my_build, None)
1096 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']")
1098 irc.message = ""
1099 my_build.results = builder.FAILURE
1100 irc.buildFinished(my_builder.getName(), my_build, None)
1101 self.failUnlessEqual(irc.message, "", "No finish notification generated on failure with notify_events=['success']")
1103 irc.message = ""
1104 my_build.results = builder.EXCEPTION
1105 irc.buildFinished(my_builder.getName(), my_build, None)
1106 self.failUnlessEqual(irc.message, "", "No finish notification generated on exception with notify_events=['success']")
1108 def test_notification_failed(self):
1109 irc = MyContact()
1111 my_builder = MyBuilder("builder834")
1112 my_build = MyIrcBuild(my_builder, 862, builder.FAILURE)
1113 my_build.changes = (
1114 Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
1117 irc.command_NOTIFY("on failed", "mynick")
1119 irc.message = ""
1120 irc.buildStarted(my_builder.getName(), my_build)
1121 self.failUnlessEqual(irc.message, "", "No started notification with notify_events=['failed']")
1123 irc.message = ""
1124 irc.buildFinished(my_builder.getName(), my_build, None)
1125 self.failUnlessEqual(irc.message, "build #862 of builder834 is complete: Failure [step1 step2] Build details are at http://myserver/mypath?build=765", "Finish notification generated on failure with notify_events=['failed']")
1127 irc.message = ""
1128 my_build.results = builder.SUCCESS
1129 irc.buildFinished(my_builder.getName(), my_build, None)
1130 self.failUnlessEqual(irc.message, "", "No finish notification generated on success with notify_events=['failed']")
1132 irc.message = ""
1133 my_build.results = builder.EXCEPTION
1134 irc.buildFinished(my_builder.getName(), my_build, None)
1135 self.failUnlessEqual(irc.message, "", "No finish notification generated on exception with notify_events=['failed']")
1137 def test_notification_exception(self):
1138 irc = MyContact()
1140 my_builder = MyBuilder("builder834")
1141 my_build = MyIrcBuild(my_builder, 862, builder.EXCEPTION)
1142 my_build.changes = (
1143 Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
1146 irc.command_NOTIFY("on exception", "mynick")
1148 irc.message = ""
1149 irc.buildStarted(my_builder.getName(), my_build)
1150 self.failUnlessEqual(irc.message, "", "No started notification with notify_events=['exception']")
1152 irc.message = ""
1153 irc.buildFinished(my_builder.getName(), my_build, None)
1154 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']")
1156 irc.message = ""
1157 my_build.results = builder.SUCCESS
1158 irc.buildFinished(my_builder.getName(), my_build, None)
1159 self.failUnlessEqual(irc.message, "", "No finish notification generated on success with notify_events=['exception']")
1161 irc.message = ""
1162 my_build.results = builder.FAILURE
1163 irc.buildFinished(my_builder.getName(), my_build, None)
1164 self.failUnlessEqual(irc.message, "", "No finish notification generated on exception with notify_events=['exception']")
1166 def test_notification_successToFailure(self):
1167 irc = MyContact()
1169 my_builder = MyBuilder("builder834")
1170 my_build = MyIrcBuild(my_builder, 862, builder.FAILURE)
1171 my_build.changes = (
1172 Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
1174 previous_build = MyIrcBuild(my_builder, 861, builder.SUCCESS)
1175 my_build.setPreviousBuild(previous_build)
1177 irc.command_NOTIFY("on successToFailure", "mynick")
1179 irc.message = ""
1180 irc.buildStarted(my_builder.getName(), my_build)
1181 self.failUnlessEqual(irc.message, "", "No started notification with notify_events=['failed']")
1183 irc.message = ""
1184 irc.buildFinished(my_builder.getName(), my_build, None)
1185 self.failUnlessEqual(irc.message, "build #862 of builder834 is complete: Failure [step1 step2] Build details are at http://myserver/mypath?build=765", "Finish notification generated on failure with notify_events=['successToFailure']")
1187 irc.message = ""
1188 my_build.results = builder.SUCCESS
1189 irc.buildFinished(my_builder.getName(), my_build, None)
1190 self.failUnlessEqual(irc.message, "", "No finish notification generated on success with notify_events=['failed']")
1192 irc.message = ""
1193 my_build.results = builder.EXCEPTION
1194 irc.buildFinished(my_builder.getName(), my_build, None)
1195 self.failUnlessEqual(irc.message, "", "No finish notification generated on exception with notify_events=['failed']")
1197 def test_notification_failureToSuccess(self):
1198 irc = MyContact()
1200 my_builder = MyBuilder("builder834")
1201 my_build = MyIrcBuild(my_builder, 862, builder.SUCCESS)
1202 my_build.changes = (
1203 Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
1205 previous_build = MyIrcBuild(my_builder, 861, builder.FAILURE)
1206 my_build.setPreviousBuild(previous_build)
1208 irc.command_NOTIFY("on failureToSuccess", "mynick")
1210 irc.message = ""
1211 irc.buildStarted(my_builder.getName(), my_build)
1212 self.failUnlessEqual(irc.message, "", "No started notification with notify_events=['success']")
1214 irc.message = ""
1215 irc.buildFinished(my_builder.getName(), my_build, None)
1216 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=['failureToSuccess']")
1218 irc.message = ""
1219 my_build.results = builder.FAILURE
1220 irc.buildFinished(my_builder.getName(), my_build, None)
1221 self.failUnlessEqual(irc.message, "", "No finish notification generated on failure with notify_events=['success']")
1223 irc.message = ""
1224 my_build.results = builder.EXCEPTION
1225 irc.buildFinished(my_builder.getName(), my_build, None)
1226 self.failUnlessEqual(irc.message, "", "No finish notification generated on exception with notify_events=['success']")
1228 class MyIrcBuild(builder.BuildStatus):
1229 results = None
1231 def __init__(self, parent, number, results):
1232 builder.BuildStatus.__init__(self, parent, number)
1233 self.results = results
1234 self.previousBuild = None
1236 def getResults(self):
1237 return self.results
1239 def getText(self):
1240 return ('step1', 'step2')
1242 def setPreviousBuild(self, pb):
1243 self.previousBuild = pb
1245 def getPreviousBuild(self):
1246 return self.previousBuild
1248 class URLProducer:
1249 def getURLForThing(self, build):
1250 return 'http://myserver/mypath?build=765'
1252 class MyChannel:
1253 categories = None
1254 status = URLProducer()
1256 def __init__(self):
1257 pass
1259 class MyContact(words.Contact):
1260 message = ""
1262 def __init__(self, channel = MyChannel()):
1263 words.Contact.__init__(self, channel)
1264 self.message = ""
1266 def subscribe_to_build_events(self):
1267 pass
1269 def unsubscribe_from_build_events(self):
1270 pass
1272 def send(self, msg):
1273 self.message += msg
1275 class StepStatistics(unittest.TestCase):
1276 def testStepStatistics(self):
1277 status = builder.BuildStatus(builder.BuilderStatus("test"), 123)
1278 status.addStepWithName('step1')
1279 status.addStepWithName('step2')
1280 status.addStepWithName('step3')
1281 status.addStepWithName('step4')
1283 steps = status.getSteps()
1284 (step1, step2, step3, step4) = steps
1286 step1.setStatistic('test-prop', 1)
1287 step3.setStatistic('test-prop', 2)
1288 step4.setStatistic('test-prop', 4)
1290 step1.setStatistic('other-prop', 27)
1291 # Just to have some other properties around
1293 self.failUnlessEqual(step1.getStatistic('test-prop'), 1,
1294 'Retrieve an existing property')
1295 self.failUnlessEqual(step1.getStatistic('test-prop', 99), 1,
1296 "Don't default an existing property")
1297 self.failUnlessEqual(step2.getStatistic('test-prop', 99), 99,
1298 'Default a non-existant property')
1300 self.failUnlessEqual(
1301 status.getSummaryStatistic('test-prop', operator.add), 7,
1302 'Sum property across the build')
1304 self.failUnlessEqual(
1305 status.getSummaryStatistic('test-prop', operator.add, 13), 20,
1306 'Sum property across the build with initial value')