document onlyIfChanged
[buildbot.git] / buildbot / test / test_status.py
blob736f94e901de9b41d738ad5089717a5ad8e8c789
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, setupBuildStepStatus
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
98 def getProjectName(self):
99 return "myproj"
101 class MyBuilder(builder.BuilderStatus):
102 nextBuildNumber = 0
104 class MyBuild(builder.BuildStatus):
105 testlogs = []
106 def __init__(self, parent, number, results):
107 builder.BuildStatus.__init__(self, parent, number)
108 self.results = results
109 self.source = SourceStamp(revision="1.14")
110 self.reason = "build triggered by changes"
111 self.finished = True
112 def getLogs(self):
113 return self.testlogs
115 class MyLookup:
116 implements(interfaces.IEmailLookup)
118 def getAddress(self, user):
119 d = defer.Deferred()
120 # With me now is Mr Thomas Walters of West Hartlepool who is totally
121 # invisible.
122 if user == "Thomas_Walters":
123 d.callback(None)
124 else:
125 d.callback(user + "@" + "dev.com")
126 return d
128 def customTextMailMessage(attrs):
129 logLines = 3
130 text = list()
131 text.append("STATUS: %s" % attrs['result'].title())
132 text.append("")
133 text.extend([c.asText() for c in attrs['changes']])
134 text.append("")
135 name, url, lines = attrs['logs'][-1]
136 text.append("Last %d lines of '%s':" % (logLines, name))
137 text.extend(["\t%s\n" % line for line in lines[len(lines)-logLines:]])
138 text.append("")
139 text.append("-buildbot")
140 return ("\n".join(text), 'plain')
142 def customHTMLMailMessage(attrs):
143 logLines = 3
144 text = list()
145 text.append("<h3>STATUS <a href='%s'>%s</a>:</h3>" % (attrs['buildURL'],
146 attrs['result'].title()))
147 text.append("<h4>Recent Changes:</h4>")
148 text.extend([c.asHTML() for c in attrs['changes']])
149 name, url, lines = attrs['logs'][-1]
150 text.append("<h4>Last %d lines of '%s':</h4>" % (logLines, name))
151 text.append("<p>")
152 text.append("<br>".join([line for line in lines[len(lines)-logLines:]]))
153 text.append("</p>")
154 text.append("<br>")
155 text.append("<b>-<a href='%s'>buildbot</a></b>" % attrs['buildbotURL'])
156 return ("\n".join(text), 'html')
158 class Mail(unittest.TestCase):
160 def setUp(self):
161 self.builder = MyBuilder("builder1")
163 def stall(self, res, timeout):
164 d = defer.Deferred()
165 reactor.callLater(timeout, d.callback, res)
166 return d
168 def makeBuild(self, number, results):
169 return MyBuild(self.builder, number, results)
171 def failUnlessIn(self, substring, string):
172 self.failUnless(string.find(substring) != -1,
173 "didn't see '%s' in '%s'" % (substring, string))
175 def getProjectName(self):
176 return "PROJECT"
178 def getBuildbotURL(self):
179 return "BUILDBOT_URL"
181 def getURLForThing(self, thing):
182 return None
184 def testBuild1(self):
185 mailer = MyMailer(fromaddr="buildbot@example.com",
186 extraRecipients=["recip@example.com",
187 "recip2@example.com"],
188 lookup=mail.Domain("dev.com"))
189 mailer.parent = self
190 mailer.status = self
191 self.messages = []
193 b1 = self.makeBuild(3, builder.SUCCESS)
194 b1.blamelist = ["bob"]
196 mailer.buildFinished("builder1", b1, b1.results)
197 self.failUnless(len(self.messages) == 1)
198 m,r = self.messages.pop()
199 t = m.as_string()
200 self.failUnlessIn("To: bob@dev.com\n", t)
201 self.failUnlessIn("CC: recip2@example.com, recip@example.com\n", t)
202 self.failUnlessIn("From: buildbot@example.com\n", t)
203 self.failUnlessIn("Subject: buildbot success in PROJECT on builder1\n", t)
204 self.failUnlessIn("Date: ", t)
205 self.failUnlessIn("Build succeeded!\n", t)
206 self.failUnlessIn("Buildbot URL: BUILDBOT_URL\n", t)
208 def testBuild2(self):
209 mailer = MyMailer(fromaddr="buildbot@example.com",
210 extraRecipients=["recip@example.com",
211 "recip2@example.com"],
212 lookup="dev.com",
213 sendToInterestedUsers=False)
214 mailer.parent = self
215 mailer.status = self
216 self.messages = []
218 b1 = self.makeBuild(3, builder.SUCCESS)
219 b1.blamelist = ["bob"]
221 mailer.buildFinished("builder1", b1, b1.results)
222 self.failUnless(len(self.messages) == 1)
223 m,r = self.messages.pop()
224 t = m.as_string()
225 self.failUnlessIn("To: recip2@example.com, "
226 "recip@example.com\n", t)
227 self.failUnlessIn("From: buildbot@example.com\n", t)
228 self.failUnlessIn("Subject: buildbot success in PROJECT on builder1\n", t)
229 self.failUnlessIn("Build succeeded!\n", t)
230 self.failUnlessIn("Buildbot URL: BUILDBOT_URL\n", t)
232 def testBuildStatusCategory(self):
233 # a status client only interested in a category should only receive
234 # from that category
235 mailer = MyMailer(fromaddr="buildbot@example.com",
236 extraRecipients=["recip@example.com",
237 "recip2@example.com"],
238 lookup="dev.com",
239 sendToInterestedUsers=False,
240 categories=["debug"])
242 mailer.parent = self
243 mailer.status = self
244 self.messages = []
246 b1 = self.makeBuild(3, builder.SUCCESS)
247 b1.blamelist = ["bob"]
249 mailer.buildFinished("builder1", b1, b1.results)
250 self.failIf(self.messages)
252 def testBuilderCategory(self):
253 # a builder in a certain category should notify status clients that
254 # did not list categories, or categories including this one
255 mailer1 = MyMailer(fromaddr="buildbot@example.com",
256 extraRecipients=["recip@example.com",
257 "recip2@example.com"],
258 lookup="dev.com",
259 sendToInterestedUsers=False)
260 mailer2 = MyMailer(fromaddr="buildbot@example.com",
261 extraRecipients=["recip@example.com",
262 "recip2@example.com"],
263 lookup="dev.com",
264 sendToInterestedUsers=False,
265 categories=["active"])
266 mailer3 = MyMailer(fromaddr="buildbot@example.com",
267 extraRecipients=["recip@example.com",
268 "recip2@example.com"],
269 lookup="dev.com",
270 sendToInterestedUsers=False,
271 categories=["active", "debug"])
273 builderd = MyBuilder("builder2", "debug")
275 mailer1.parent = self
276 mailer1.status = self
277 mailer2.parent = self
278 mailer2.status = self
279 mailer3.parent = self
280 mailer3.status = self
281 self.messages = []
283 t = mailer1.builderAdded("builder2", builderd)
284 self.assertEqual(len(mailer1.watched), 1)
285 self.assertEqual(t, mailer1)
286 t = mailer2.builderAdded("builder2", builderd)
287 self.assertEqual(len(mailer2.watched), 0)
288 self.assertEqual(t, None)
289 t = mailer3.builderAdded("builder2", builderd)
290 self.assertEqual(len(mailer3.watched), 1)
291 self.assertEqual(t, mailer3)
293 b2 = MyBuild(builderd, 3, builder.SUCCESS)
294 b2.blamelist = ["bob"]
296 mailer1.buildFinished("builder2", b2, b2.results)
297 self.failUnlessEqual(len(self.messages), 1)
298 self.messages = []
299 mailer2.buildFinished("builder2", b2, b2.results)
300 self.failUnlessEqual(len(self.messages), 0)
301 self.messages = []
302 mailer3.buildFinished("builder2", b2, b2.results)
303 self.failUnlessEqual(len(self.messages), 1)
305 def testCustomTextMessage(self):
306 basedir = "test_custom_text_mesg"
307 os.mkdir(basedir)
308 mailer = MyMailer(fromaddr="buildbot@example.com", mode="problem",
309 extraRecipients=["recip@example.com",
310 "recip2@example.com"],
311 lookup=MyLookup(),
312 customMesg=customTextMailMessage)
313 mailer.parent = self
314 mailer.status = self
315 self.messages = []
317 b1 = self.makeBuild(4, builder.FAILURE)
318 b1.setText(["snarkleack", "polarization", "failed"])
319 b1.blamelist = ["dev3", "dev3", "dev3", "dev4",
320 "Thomas_Walters"]
321 b1.source.changes = (Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 123),
322 Change(who = 'author2', files = ['file2'], comments = 'comment2', revision = 456))
323 b1.testlogs = [MyLog(basedir, 'compile', "Compile log here\n"),
324 MyLog(basedir, 'test', "Test log here\nTest 1 failed\nTest 2 failed\nTest 3 failed\nTest 4 failed\n")]
326 mailer.buildFinished("builder1", b1, b1.results)
327 m,r = self.messages.pop()
328 t = m.as_string()
330 # Uncomment to review custom message
332 #self.fail(t)
333 self.failUnlessIn("comment1", t)
334 self.failUnlessIn("comment2", t)
335 self.failUnlessIn("Test 4 failed", t)
338 def testCustomHTMLMessage(self):
339 basedir = "test_custom_HTML_mesg"
340 os.mkdir(basedir)
341 mailer = MyMailer(fromaddr="buildbot@example.com", mode="problem",
342 extraRecipients=["recip@example.com",
343 "recip2@example.com"],
344 lookup=MyLookup(),
345 customMesg=customHTMLMailMessage)
346 mailer.parent = self
347 mailer.status = self
348 self.messages = []
350 b1 = self.makeBuild(4, builder.FAILURE)
351 b1.setText(["snarkleack", "polarization", "failed"])
352 b1.blamelist = ["dev3", "dev3", "dev3", "dev4",
353 "Thomas_Walters"]
354 b1.source.changes = (Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 123),
355 Change(who = 'author2', files = ['file2'], comments = 'comment2', revision = 456))
356 b1.testlogs = [MyLog(basedir, 'compile', "Compile log here\n"),
357 MyLog(basedir, 'test', "Test log here\nTest 1 failed\nTest 2 failed\nTest 3 failed\nTest 4 failed\n")]
359 mailer.buildFinished("builder1", b1, b1.results)
360 m,r = self.messages.pop()
361 t = m.as_string()
363 # Uncomment to review custom message
365 #self.fail(t)
366 self.failUnlessIn("<h4>Last 3 lines of 'step.test':</h4>", t)
367 self.failUnlessIn("<p>Changed by: <b>author2</b><br />", t)
368 self.failUnlessIn("Test 3 failed", t)
370 def testFailure(self):
371 mailer = MyMailer(fromaddr="buildbot@example.com", mode="problem",
372 extraRecipients=["recip@example.com",
373 "recip2@example.com"],
374 lookup=MyLookup())
375 mailer.parent = self
376 mailer.status = self
377 self.messages = []
379 b1 = self.makeBuild(3, builder.SUCCESS)
380 b1.blamelist = ["dev1", "dev2"]
381 b2 = self.makeBuild(4, builder.FAILURE)
382 b2.setText(["snarkleack", "polarization", "failed"])
383 b2.blamelist = ["dev3", "dev3", "dev3", "dev4",
384 "Thomas_Walters"]
385 mailer.buildFinished("builder1", b1, b1.results)
386 self.failIf(self.messages)
387 mailer.buildFinished("builder1", b2, b2.results)
388 self.failUnless(len(self.messages) == 1)
389 m,r = self.messages.pop()
390 t = m.as_string()
391 self.failUnlessIn("To: dev3@dev.com, dev4@dev.com\n", t)
392 self.failUnlessIn("CC: recip2@example.com, recip@example.com\n", t)
393 self.failUnlessIn("From: buildbot@example.com\n", t)
394 self.failUnlessIn("Subject: buildbot failure in PROJECT on builder1\n", t)
395 self.failUnlessIn("The Buildbot has detected a new failure", t)
396 self.failUnlessIn("BUILD FAILED: snarkleack polarization failed\n", t)
397 self.failUnlessEqual(set(r), set(["dev3@dev.com", "dev4@dev.com",
398 "recip2@example.com", "recip@example.com"]))
400 def testLogs(self):
401 basedir = "test_status_logs"
402 os.mkdir(basedir)
403 mailer = MyMailer(fromaddr="buildbot@example.com", addLogs=True,
404 extraRecipients=["recip@example.com",
405 "recip2@example.com"])
406 mailer.parent = self
407 mailer.status = self
408 self.messages = []
410 b1 = self.makeBuild(3, builder.WARNINGS)
411 b1.testlogs = [MyLog(basedir, 'compile', "Compile log here\n"),
412 MyLog(basedir,
413 'test', "Test log here\nTest 4 failed\n"),
415 b1.text = ["unusual", "gnarzzler", "output"]
416 mailer.buildFinished("builder1", b1, b1.results)
417 self.failUnless(len(self.messages) == 1)
418 m,r = self.messages.pop()
419 t = m.as_string()
420 self.failUnlessIn("Subject: buildbot warnings in PROJECT on builder1\n", t)
421 m2 = email.message_from_string(t)
422 p = m2.get_payload()
423 self.failUnlessEqual(len(p), 3)
425 self.failUnlessIn("Build Had Warnings: unusual gnarzzler output\n",
426 p[0].get_payload())
428 self.failUnlessEqual(p[1].get_filename(), "step.compile")
429 self.failUnlessEqual(p[1].get_payload(), "Compile log here\n")
431 self.failUnlessEqual(p[2].get_filename(), "step.test")
432 self.failUnlessIn("Test log here\n", p[2].get_payload())
434 def testMail(self):
435 basedir = "test_status_mail"
436 os.mkdir(basedir)
437 dest = os.environ.get("BUILDBOT_TEST_MAIL")
438 if not dest:
439 raise unittest.SkipTest("define BUILDBOT_TEST_MAIL=dest to run this")
440 mailer = mail.MailNotifier(fromaddr="buildbot@example.com",
441 addLogs=True,
442 extraRecipients=[dest])
443 s = MyStatus()
444 s.url = "project URL"
445 mailer.status = s
447 b1 = self.makeBuild(3, builder.SUCCESS)
448 b1.testlogs = [MyLog(basedir, 'compile', "Compile log here\n"),
449 MyLog(basedir,
450 'test', "Test log here\nTest 4 failed\n"),
453 d = mailer.buildFinished("builder1", b1, b1.results)
454 # When this fires, the mail has been sent, but the SMTP connection is
455 # still up (because smtp.sendmail relies upon the server to hang up).
456 # Spin for a moment to avoid the "unclean reactor" warning that Trial
457 # gives us if we finish before the socket is disconnected. Really,
458 # sendmail() ought to hang up the connection once it is finished:
459 # otherwise a malicious SMTP server could make us consume lots of
460 # memory.
461 d.addCallback(self.stall, 0.1)
462 return d
464 if not mail:
465 Mail.skip = "the Twisted Mail package is not installed"
467 class Progress(unittest.TestCase):
468 def testWavg(self):
469 bp = progress.BuildProgress([])
470 e = progress.Expectations(bp)
471 # wavg(old, current)
472 self.failUnlessEqual(e.wavg(None, None), None)
473 self.failUnlessEqual(e.wavg(None, 3), 3)
474 self.failUnlessEqual(e.wavg(3, None), 3)
475 self.failUnlessEqual(e.wavg(3, 4), 3.5)
476 e.decay = 0.1
477 self.failUnlessEqual(e.wavg(3, 4), 3.1)
480 class Results(unittest.TestCase):
482 def testAddResults(self):
483 b = builder.BuildStatus(builder.BuilderStatus("test"), 12)
484 testname = ("buildbot", "test", "test_status", "Results",
485 "testAddResults")
486 r1 = builder.TestResult(name=testname,
487 results=builder.SUCCESS,
488 text=["passed"],
489 logs={'output': ""},
491 b.addTestResult(r1)
493 res = b.getTestResults()
494 self.failUnlessEqual(res.keys(), [testname])
495 t = res[testname]
496 self.failUnless(interfaces.ITestResult.providedBy(t))
497 self.failUnlessEqual(t.getName(), testname)
498 self.failUnlessEqual(t.getResults(), builder.SUCCESS)
499 self.failUnlessEqual(t.getText(), ["passed"])
500 self.failUnlessEqual(t.getLogs(), {'output': ""})
502 class Log(unittest.TestCase):
503 def setUpClass(self):
504 self.basedir = "status_log_add"
505 os.mkdir(self.basedir)
507 def testAdd(self):
508 l = MyLog(self.basedir, "compile", step=13)
509 self.failUnlessEqual(l.getName(), "compile")
510 self.failUnlessEqual(l.getStep(), 13)
511 l.addHeader("HEADER\n")
512 l.addStdout("Some text\n")
513 l.addStderr("Some error\n")
514 l.addStdout("Some more text\n")
515 self.failIf(l.isFinished())
516 l.finish()
517 self.failUnless(l.isFinished())
518 self.failUnlessEqual(l.getText(),
519 "Some text\nSome error\nSome more text\n")
520 self.failUnlessEqual(l.getTextWithHeaders(),
521 "HEADER\n" +
522 "Some text\nSome error\nSome more text\n")
523 self.failUnlessEqual(len(list(l.getChunks())), 4)
525 self.failUnless(l.hasContents())
526 try:
527 os.unlink(l.getFilename())
528 except OSError:
529 os.unlink(l.getFilename() + ".bz2")
530 self.failIf(l.hasContents())
532 def TODO_testDuplicate(self):
533 # create multiple logs for the same step with the same logname, make
534 # sure their on-disk filenames are suitably uniquified. This
535 # functionality actually lives in BuildStepStatus and BuildStatus, so
536 # this test must involve more than just the MyLog class.
538 # naieve approach, doesn't work
539 l1 = MyLog(self.basedir, "duplicate")
540 l1.addStdout("Some text\n")
541 l1.finish()
542 l2 = MyLog(self.basedir, "duplicate")
543 l2.addStdout("Some more text\n")
544 l2.finish()
545 self.failIfEqual(l1.getFilename(), l2.getFilename())
547 def testMerge1(self):
548 l = MyLog(self.basedir, "merge1")
549 l.addHeader("HEADER\n")
550 l.addStdout("Some text\n")
551 l.addStdout("Some more text\n")
552 l.addStdout("more\n")
553 l.finish()
554 self.failUnlessEqual(l.getText(),
555 "Some text\nSome more text\nmore\n")
556 self.failUnlessEqual(l.getTextWithHeaders(),
557 "HEADER\n" +
558 "Some text\nSome more text\nmore\n")
559 self.failUnlessEqual(len(list(l.getChunks())), 2)
561 def testMerge2(self):
562 l = MyLog(self.basedir, "merge2")
563 l.addHeader("HEADER\n")
564 for i in xrange(1000):
565 l.addStdout("aaaa")
566 for i in xrange(30):
567 l.addStderr("bbbb")
568 for i in xrange(10):
569 l.addStdout("cc")
570 target = 1000*"aaaa" + 30 * "bbbb" + 10 * "cc"
571 self.failUnlessEqual(len(l.getText()), len(target))
572 self.failUnlessEqual(l.getText(), target)
573 l.finish()
574 self.failUnlessEqual(len(l.getText()), len(target))
575 self.failUnlessEqual(l.getText(), target)
576 self.failUnlessEqual(len(list(l.getChunks())), 4)
578 def testMerge3(self):
579 l = MyLog(self.basedir, "merge3")
580 l.chunkSize = 100
581 l.addHeader("HEADER\n")
582 for i in xrange(8):
583 l.addStdout(10*"a")
584 for i in xrange(8):
585 l.addStdout(10*"a")
586 self.failUnlessEqual(list(l.getChunks()),
587 [(builder.HEADER, "HEADER\n"),
588 (builder.STDOUT, 100*"a"),
589 (builder.STDOUT, 60*"a")])
590 l.finish()
591 self.failUnlessEqual(l.getText(), 160*"a")
593 def testReadlines(self):
594 l = MyLog(self.basedir, "chunks1")
595 l.addHeader("HEADER\n") # should be ignored
596 l.addStdout("Some text\n")
597 l.addStdout("Some More Text\nAnd Some More\n")
598 l.addStderr("Some Stderr\n")
599 l.addStdout("Last line\n")
600 l.finish()
601 alllines = list(l.readlines())
602 self.failUnlessEqual(len(alllines), 4)
603 self.failUnlessEqual(alllines[0], "Some text\n")
604 self.failUnlessEqual(alllines[2], "And Some More\n")
605 self.failUnlessEqual(alllines[3], "Last line\n")
606 stderr = list(l.readlines(interfaces.LOG_CHANNEL_STDERR))
607 self.failUnlessEqual(len(stderr), 1)
608 self.failUnlessEqual(stderr[0], "Some Stderr\n")
609 lines = l.readlines()
610 if False: # TODO: l.readlines() is not yet an iterator
611 # verify that it really is an iterator
612 line0 = lines.next()
613 self.failUnlessEqual(line0, "Some text\n")
614 line1 = lines.next()
615 line2 = lines.next()
616 self.failUnlessEqual(line2, "And Some More\n")
619 def testChunks(self):
620 l = MyLog(self.basedir, "chunks2")
621 c1 = l.getChunks()
622 l.addHeader("HEADER\n")
623 l.addStdout("Some text\n")
624 self.failUnlessEqual("".join(l.getChunks(onlyText=True)),
625 "HEADER\nSome text\n")
626 c2 = l.getChunks()
628 l.addStdout("Some more text\n")
629 self.failUnlessEqual("".join(l.getChunks(onlyText=True)),
630 "HEADER\nSome text\nSome more text\n")
631 c3 = l.getChunks()
633 l.addStdout("more\n")
634 l.finish()
636 self.failUnlessEqual(list(c1), [])
637 self.failUnlessEqual(list(c2), [(builder.HEADER, "HEADER\n"),
638 (builder.STDOUT, "Some text\n")])
639 self.failUnlessEqual(list(c3), [(builder.HEADER, "HEADER\n"),
640 (builder.STDOUT,
641 "Some text\nSome more text\n")])
643 self.failUnlessEqual(l.getText(),
644 "Some text\nSome more text\nmore\n")
645 self.failUnlessEqual(l.getTextWithHeaders(),
646 "HEADER\n" +
647 "Some text\nSome more text\nmore\n")
648 self.failUnlessEqual(len(list(l.getChunks())), 2)
650 def testUpgrade(self):
651 l = MyLog(self.basedir, "upgrade")
652 l.addHeader("HEADER\n")
653 l.addStdout("Some text\n")
654 l.addStdout("Some more text\n")
655 l.addStdout("more\n")
656 l.finish()
657 self.failUnless(l.hasContents())
658 # now doctor it to look like a 0.6.4-era non-upgraded logfile
659 l.entries = list(l.getChunks())
660 del l.filename
661 try:
662 os.unlink(l.getFilename() + ".bz2")
663 except OSError:
664 os.unlink(l.getFilename())
665 # now make sure we can upgrade it
666 l.upgrade("upgrade")
667 self.failUnlessEqual(l.getText(),
668 "Some text\nSome more text\nmore\n")
669 self.failUnlessEqual(len(list(l.getChunks())), 2)
670 self.failIf(l.entries)
672 # now, do it again, but make it look like an upgraded 0.6.4 logfile
673 # (i.e. l.filename is missing, but the contents are there on disk)
674 l.entries = list(l.getChunks())
675 del l.filename
676 l.upgrade("upgrade")
677 self.failUnlessEqual(l.getText(),
678 "Some text\nSome more text\nmore\n")
679 self.failUnlessEqual(len(list(l.getChunks())), 2)
680 self.failIf(l.entries)
681 self.failUnless(l.hasContents())
683 def testHTMLUpgrade(self):
684 l = MyHTMLLog(self.basedir, "upgrade", "log contents")
685 l.upgrade("filename")
687 def testSubscribe(self):
688 l1 = MyLog(self.basedir, "subscribe1")
689 l1.finish()
690 self.failUnless(l1.isFinished())
692 s = MyLogSubscriber()
693 l1.subscribe(s, True)
694 l1.unsubscribe(s)
695 self.failIf(s.chunks)
697 s = MyLogSubscriber()
698 l1.subscribe(s, False)
699 l1.unsubscribe(s)
700 self.failIf(s.chunks)
702 finished = []
703 l2 = MyLog(self.basedir, "subscribe2")
704 l2.waitUntilFinished().addCallback(finished.append)
705 l2.addHeader("HEADER\n")
706 s1 = MyLogSubscriber()
707 l2.subscribe(s1, True)
708 s2 = MyLogSubscriber()
709 l2.subscribe(s2, False)
710 self.failUnlessEqual(s1.chunks, [(builder.HEADER, "HEADER\n")])
711 self.failUnlessEqual(s2.chunks, [])
713 l2.addStdout("Some text\n")
714 self.failUnlessEqual(s1.chunks, [(builder.HEADER, "HEADER\n"),
715 (builder.STDOUT, "Some text\n")])
716 self.failUnlessEqual(s2.chunks, [(builder.STDOUT, "Some text\n")])
717 l2.unsubscribe(s1)
719 l2.addStdout("Some more text\n")
720 self.failUnlessEqual(s1.chunks, [(builder.HEADER, "HEADER\n"),
721 (builder.STDOUT, "Some text\n")])
722 self.failUnlessEqual(s2.chunks, [(builder.STDOUT, "Some text\n"),
723 (builder.STDOUT, "Some more text\n"),
725 self.failIf(finished)
726 l2.finish()
727 self.failUnlessEqual(finished, [l2])
729 def testConsumer(self):
730 l1 = MyLog(self.basedir, "consumer1")
731 l1.finish()
732 self.failUnless(l1.isFinished())
734 s = MyLogConsumer()
735 d = l1.subscribeConsumer(s)
736 d.addCallback(self._testConsumer_1, s)
737 return d
738 testConsumer.timeout = 5
739 def _testConsumer_1(self, res, s):
740 self.failIf(s.chunks)
741 self.failUnless(s.finished)
742 self.failIf(s.producer) # producer should be registered and removed
744 l2 = MyLog(self.basedir, "consumer2")
745 l2.addHeader("HEADER\n")
746 l2.finish()
747 self.failUnless(l2.isFinished())
749 s = MyLogConsumer()
750 d = l2.subscribeConsumer(s)
751 d.addCallback(self._testConsumer_2, s)
752 return d
753 def _testConsumer_2(self, res, s):
754 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n")])
755 self.failUnless(s.finished)
756 self.failIf(s.producer) # producer should be registered and removed
759 l2 = MyLog(self.basedir, "consumer3")
760 l2.chunkSize = 1000
761 l2.addHeader("HEADER\n")
762 l2.addStdout(800*"a")
763 l2.addStdout(800*"a") # should now have two chunks on disk, 1000+600
764 l2.addStdout(800*"b") # HEADER,1000+600*a on disk, 800*a in memory
765 l2.addStdout(800*"b") # HEADER,1000+600*a,1000+600*b on disk
766 l2.addStdout(200*"c") # HEADER,1000+600*a,1000+600*b on disk,
767 # 200*c in memory
769 s = MyLogConsumer(limit=1)
770 d = l2.subscribeConsumer(s)
771 d.addCallback(self._testConsumer_3, l2, s)
772 return d
773 def _testConsumer_3(self, res, l2, s):
774 self.failUnless(s.streaming)
775 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n")])
776 s.limit = 1
777 d = s.producer.resumeProducing()
778 d.addCallback(self._testConsumer_4, l2, s)
779 return d
780 def _testConsumer_4(self, res, l2, s):
781 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n"),
782 (builder.STDOUT, 1000*"a"),
784 s.limit = None
785 d = s.producer.resumeProducing()
786 d.addCallback(self._testConsumer_5, l2, s)
787 return d
788 def _testConsumer_5(self, res, l2, s):
789 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n"),
790 (builder.STDOUT, 1000*"a"),
791 (builder.STDOUT, 600*"a"),
792 (builder.STDOUT, 1000*"b"),
793 (builder.STDOUT, 600*"b"),
794 (builder.STDOUT, 200*"c")])
795 l2.addStdout(1000*"c") # HEADER,1600*a,1600*b,1200*c on disk
796 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n"),
797 (builder.STDOUT, 1000*"a"),
798 (builder.STDOUT, 600*"a"),
799 (builder.STDOUT, 1000*"b"),
800 (builder.STDOUT, 600*"b"),
801 (builder.STDOUT, 200*"c"),
802 (builder.STDOUT, 1000*"c")])
803 l2.finish()
804 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n"),
805 (builder.STDOUT, 1000*"a"),
806 (builder.STDOUT, 600*"a"),
807 (builder.STDOUT, 1000*"b"),
808 (builder.STDOUT, 600*"b"),
809 (builder.STDOUT, 200*"c"),
810 (builder.STDOUT, 1000*"c")])
811 self.failIf(s.producer)
812 self.failUnless(s.finished)
814 def testLargeSummary(self):
815 bigtext = "a" * 200000 # exceed the NetstringReceiver 100KB limit
816 l = MyLog(self.basedir, "large", bigtext)
817 s = MyLogConsumer()
818 d = l.subscribeConsumer(s)
819 def _check(res):
820 for ctype,chunk in s.chunks:
821 self.failUnless(len(chunk) < 100000)
822 merged = "".join([c[1] for c in s.chunks])
823 self.failUnless(merged == bigtext)
824 d.addCallback(_check)
825 # when this fails, it fails with a timeout, and there is an exception
826 # sent to log.err(). This AttributeError exception is in
827 # NetstringReceiver.dataReceived where it does
828 # self.transport.loseConnection() because of the NetstringParseError,
829 # however self.transport is None
830 return d
831 testLargeSummary.timeout = 5
834 class CompressLog(unittest.TestCase):
835 def testCompressLogs(self):
836 bss = setupBuildStepStatus("test-compress")
837 bss.build.builder.setLogCompressionLimit(1024)
838 l = bss.addLog('not-compress')
839 l.addStdout('a' * 512)
840 l.finish()
841 lc = bss.addLog('to-compress')
842 lc.addStdout('b' * 1024)
843 lc.finish()
844 d = bss.stepFinished(builder.SUCCESS)
845 self.failUnless(d is not None)
846 d.addCallback(self._verifyCompression, bss)
847 return d
849 def _verifyCompression(self, result, bss):
850 self.failUnless(len(bss.getLogs()), 2)
851 (ncl, cl) = bss.getLogs() # not compressed, compressed log
852 self.failUnless(os.path.isfile(ncl.getFilename()))
853 self.failIf(os.path.isfile(ncl.getFilename() + ".bz2"))
854 self.failIf(os.path.isfile(cl.getFilename()))
855 self.failUnless(os.path.isfile(cl.getFilename() + ".bz2"))
856 content = ncl.getText()
857 self.failUnless(len(content), 512)
858 content = cl.getText()
859 self.failUnless(len(content), 1024)
860 pass
862 config_base = """
863 from buildbot.process import factory
864 from buildbot.steps import dummy
865 from buildbot.buildslave import BuildSlave
866 s = factory.s
868 f1 = factory.QuickBuildFactory('fakerep', 'cvsmodule', configure=None)
870 f2 = factory.BuildFactory([
871 s(dummy.Dummy, timeout=1),
872 s(dummy.RemoteDummy, timeout=2),
875 BuildmasterConfig = c = {}
876 c['slaves'] = [BuildSlave('bot1', 'sekrit')]
877 c['schedulers'] = []
878 c['builders'] = []
879 c['builders'].append({'name':'quick', 'slavename':'bot1',
880 'builddir': 'quickdir', 'factory': f1})
881 c['slavePortnum'] = 0
884 config_2 = config_base + """
885 c['builders'] = [{'name': 'dummy', 'slavename': 'bot1',
886 'builddir': 'dummy1', 'factory': f2},
887 {'name': 'testdummy', 'slavename': 'bot1',
888 'builddir': 'dummy2', 'factory': f2, 'category': 'test'}]
891 class STarget(base.StatusReceiver):
892 debug = False
894 def __init__(self, mode):
895 self.mode = mode
896 self.events = []
897 def announce(self):
898 if self.debug:
899 print self.events[-1]
901 def builderAdded(self, name, builder):
902 self.events.append(("builderAdded", name, builder))
903 self.announce()
904 if "builder" in self.mode:
905 return self
906 def builderChangedState(self, name, state):
907 self.events.append(("builderChangedState", name, state))
908 self.announce()
909 def buildStarted(self, name, build):
910 self.events.append(("buildStarted", name, build))
911 self.announce()
912 if "eta" in self.mode:
913 self.eta_build = build.getETA()
914 if "build" in self.mode:
915 return self
916 def buildETAUpdate(self, build, ETA):
917 self.events.append(("buildETAUpdate", build, ETA))
918 self.announce()
919 def stepStarted(self, build, step):
920 self.events.append(("stepStarted", build, step))
921 self.announce()
922 if 0 and "eta" in self.mode:
923 print "TIMES", step.getTimes()
924 print "ETA", step.getETA()
925 print "EXP", step.getExpectations()
926 if "step" in self.mode:
927 return self
928 def stepTextChanged(self, build, step, text):
929 self.events.append(("stepTextChanged", step, text))
930 def stepText2Changed(self, build, step, text2):
931 self.events.append(("stepText2Changed", step, text2))
932 def stepETAUpdate(self, build, step, ETA, expectations):
933 self.events.append(("stepETAUpdate", build, step, ETA, expectations))
934 self.announce()
935 def logStarted(self, build, step, log):
936 self.events.append(("logStarted", build, step, log))
937 self.announce()
938 def logFinished(self, build, step, log):
939 self.events.append(("logFinished", build, step, log))
940 self.announce()
941 def stepFinished(self, build, step, results):
942 self.events.append(("stepFinished", build, step, results))
943 if 0 and "eta" in self.mode:
944 print "post-EXP", step.getExpectations()
945 self.announce()
946 def buildFinished(self, name, build, results):
947 self.events.append(("buildFinished", name, build, results))
948 self.announce()
949 def builderRemoved(self, name):
950 self.events.append(("builderRemoved", name))
951 self.announce()
953 class Subscription(RunMixin, unittest.TestCase):
954 # verify that StatusTargets can subscribe/unsubscribe properly
956 def testSlave(self):
957 m = self.master
958 s = m.getStatus()
959 self.t1 = t1 = STarget(["builder"])
960 #t1.debug = True; print
961 s.subscribe(t1)
962 self.failUnlessEqual(len(t1.events), 0)
964 self.t3 = t3 = STarget(["builder", "build", "step"])
965 s.subscribe(t3)
967 m.loadConfig(config_2)
968 m.readConfig = True
969 m.startService()
971 self.failUnlessEqual(len(t1.events), 4)
972 self.failUnlessEqual(t1.events[0][0:2], ("builderAdded", "dummy"))
973 self.failUnlessEqual(t1.events[1],
974 ("builderChangedState", "dummy", "offline"))
975 self.failUnlessEqual(t1.events[2][0:2], ("builderAdded", "testdummy"))
976 self.failUnlessEqual(t1.events[3],
977 ("builderChangedState", "testdummy", "offline"))
978 t1.events = []
980 self.failUnlessEqual(s.getBuilderNames(), ["dummy", "testdummy"])
981 self.failUnlessEqual(s.getBuilderNames(categories=['test']),
982 ["testdummy"])
983 self.s1 = s1 = s.getBuilder("dummy")
984 self.failUnlessEqual(s1.getName(), "dummy")
985 self.failUnlessEqual(s1.getState(), ("offline", []))
986 self.failUnlessEqual(s1.getCurrentBuilds(), [])
987 self.failUnlessEqual(s1.getLastFinishedBuild(), None)
988 self.failUnlessEqual(s1.getBuild(-1), None)
989 #self.failUnlessEqual(s1.getEvent(-1), foo("created"))
991 # status targets should, upon being subscribed, immediately get a
992 # list of all current builders matching their category
993 self.t2 = t2 = STarget([])
994 s.subscribe(t2)
995 self.failUnlessEqual(len(t2.events), 2)
996 self.failUnlessEqual(t2.events[0][0:2], ("builderAdded", "dummy"))
997 self.failUnlessEqual(t2.events[1][0:2], ("builderAdded", "testdummy"))
999 d = self.connectSlave(builders=["dummy", "testdummy"])
1000 d.addCallback(self._testSlave_1, t1)
1001 return d
1003 def _testSlave_1(self, res, t1):
1004 self.failUnlessEqual(len(t1.events), 2)
1005 self.failUnlessEqual(t1.events[0],
1006 ("builderChangedState", "dummy", "idle"))
1007 self.failUnlessEqual(t1.events[1],
1008 ("builderChangedState", "testdummy", "idle"))
1009 t1.events = []
1011 c = interfaces.IControl(self.master)
1012 req = BuildRequest("forced build for testing", SourceStamp())
1013 c.getBuilder("dummy").requestBuild(req)
1014 d = req.waitUntilFinished()
1015 d2 = self.master.botmaster.waitUntilBuilderIdle("dummy")
1016 dl = defer.DeferredList([d, d2])
1017 dl.addCallback(self._testSlave_2)
1018 return dl
1020 def _testSlave_2(self, res):
1021 # t1 subscribes to builds, but not anything lower-level
1022 ev = self.t1.events
1023 self.failUnlessEqual(len(ev), 4)
1024 self.failUnlessEqual(ev[0][0:3],
1025 ("builderChangedState", "dummy", "building"))
1026 self.failUnlessEqual(ev[1][0], "buildStarted")
1027 self.failUnlessEqual(ev[2][0:2]+ev[2][3:4],
1028 ("buildFinished", "dummy", builder.SUCCESS))
1029 self.failUnlessEqual(ev[3][0:3],
1030 ("builderChangedState", "dummy", "idle"))
1032 self.failUnlessEqual([ev[0] for ev in self.t3.events],
1033 ["builderAdded",
1034 "builderChangedState", # offline
1035 "builderAdded",
1036 "builderChangedState", # idle
1037 "builderChangedState", # offline
1038 "builderChangedState", # idle
1039 "builderChangedState", # building
1040 "buildStarted",
1041 "stepStarted", "stepETAUpdate",
1042 "stepTextChanged", "stepFinished",
1043 "stepStarted", "stepETAUpdate",
1044 "stepTextChanged", "logStarted", "logFinished",
1045 "stepTextChanged", "stepText2Changed",
1046 "stepFinished",
1047 "buildFinished",
1048 "builderChangedState", # idle
1051 b = self.s1.getLastFinishedBuild()
1052 self.failUnless(b)
1053 self.failUnlessEqual(b.getBuilder().getName(), "dummy")
1054 self.failUnlessEqual(b.getNumber(), 0)
1055 self.failUnlessEqual(b.getSourceStamp().branch, None)
1056 self.failUnlessEqual(b.getSourceStamp().patch, None)
1057 self.failUnlessEqual(b.getSourceStamp().revision, None)
1058 self.failUnlessEqual(b.getReason(), "forced build for testing")
1059 self.failUnlessEqual(b.getChanges(), ())
1060 self.failUnlessEqual(b.getResponsibleUsers(), [])
1061 self.failUnless(b.isFinished())
1062 self.failUnlessEqual(b.getText(), ['build', 'successful'])
1063 self.failUnlessEqual(b.getColor(), "green")
1064 self.failUnlessEqual(b.getResults(), builder.SUCCESS)
1066 steps = b.getSteps()
1067 self.failUnlessEqual(len(steps), 2)
1069 eta = 0
1070 st1 = steps[0]
1071 self.failUnlessEqual(st1.getName(), "dummy")
1072 self.failUnless(st1.isFinished())
1073 self.failUnlessEqual(st1.getText(), ["delay", "1 secs"])
1074 start,finish = st1.getTimes()
1075 self.failUnless(0.5 < (finish-start) < 10)
1076 self.failUnlessEqual(st1.getExpectations(), [])
1077 self.failUnlessEqual(st1.getLogs(), [])
1078 eta += finish-start
1080 st2 = steps[1]
1081 self.failUnlessEqual(st2.getName(), "remote dummy")
1082 self.failUnless(st2.isFinished())
1083 self.failUnlessEqual(st2.getText(),
1084 ["remote", "delay", "2 secs"])
1085 start,finish = st2.getTimes()
1086 self.failUnless(1.5 < (finish-start) < 10)
1087 eta += finish-start
1088 self.failUnlessEqual(st2.getExpectations(), [('output', 38, None)])
1089 logs = st2.getLogs()
1090 self.failUnlessEqual(len(logs), 1)
1091 self.failUnlessEqual(logs[0].getName(), "stdio")
1092 self.failUnlessEqual(logs[0].getText(), "data")
1094 self.eta = eta
1095 # now we run it a second time, and we should have an ETA
1097 self.t4 = t4 = STarget(["builder", "build", "eta"])
1098 self.master.getStatus().subscribe(t4)
1099 c = interfaces.IControl(self.master)
1100 req = BuildRequest("forced build for testing", SourceStamp())
1101 c.getBuilder("dummy").requestBuild(req)
1102 d = req.waitUntilFinished()
1103 d2 = self.master.botmaster.waitUntilBuilderIdle("dummy")
1104 dl = defer.DeferredList([d, d2])
1105 dl.addCallback(self._testSlave_3)
1106 return dl
1108 def _testSlave_3(self, res):
1109 t4 = self.t4
1110 eta = self.eta
1111 self.failUnless(eta-1 < t4.eta_build < eta+1, # should be 3 seconds
1112 "t4.eta_build was %g, not in (%g,%g)"
1113 % (t4.eta_build, eta-1, eta+1))
1116 class Client(unittest.TestCase):
1117 def testAdaptation(self):
1118 b = builder.BuilderStatus("bname")
1119 b2 = client.makeRemote(b)
1120 self.failUnless(isinstance(b2, client.RemoteBuilder))
1121 b3 = client.makeRemote(None)
1122 self.failUnless(b3 is None)
1125 class ContactTester(unittest.TestCase):
1126 def test_notify_invalid_syntax(self):
1127 irc = MyContact()
1128 self.assertRaises(words.UsageError, lambda args, who: irc.command_NOTIFY(args, who), "", "mynick")
1130 def test_notify_list(self):
1131 irc = MyContact()
1132 irc.command_NOTIFY("list", "mynick")
1133 self.failUnlessEqual(irc.message, "The following events are being notified: []", "empty notify list")
1135 irc.message = ""
1136 irc.command_NOTIFY("on started", "mynick")
1137 self.failUnlessEqual(irc.message, "The following events are being notified: ['started']", "on started")
1139 irc.message = ""
1140 irc.command_NOTIFY("on finished", "mynick")
1141 self.failUnlessEqual(irc.message, "The following events are being notified: ['started', 'finished']", "on finished")
1143 irc.message = ""
1144 irc.command_NOTIFY("off", "mynick")
1145 self.failUnlessEqual(irc.message, "The following events are being notified: []", "off all")
1147 irc.message = ""
1148 irc.command_NOTIFY("on", "mynick")
1149 self.failUnlessEqual(irc.message, "The following events are being notified: ['started', 'finished']", "on default set")
1151 irc.message = ""
1152 irc.command_NOTIFY("off started", "mynick")
1153 self.failUnlessEqual(irc.message, "The following events are being notified: ['finished']", "off started")
1155 irc.message = ""
1156 irc.command_NOTIFY("on success failed exception", "mynick")
1157 self.failUnlessEqual(irc.message, "The following events are being notified: ['failed', 'finished', 'exception', 'success']", "on multiple events")
1159 def test_notification_default(self):
1160 irc = MyContact()
1162 my_builder = MyBuilder("builder78")
1163 my_build = MyIrcBuild(my_builder, 23, builder.SUCCESS)
1165 irc.buildStarted(my_builder.getName(), my_build)
1166 self.failUnlessEqual(irc.message, "", "No notification with default settings")
1168 irc.buildFinished(my_builder.getName(), my_build, None)
1169 self.failUnlessEqual(irc.message, "", "No notification with default settings")
1171 def test_notification_started(self):
1172 irc = MyContact()
1174 my_builder = MyBuilder("builder78")
1175 my_build = MyIrcBuild(my_builder, 23, builder.SUCCESS)
1176 my_build.changes = (
1177 Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 123),
1178 Change(who = 'author2', files = ['file2'], comments = 'comment2', revision = 456),
1181 irc.command_NOTIFY("on started", "mynick")
1183 irc.message = ""
1184 irc.buildStarted(my_builder.getName(), my_build)
1185 self.failUnlessEqual(irc.message, "build #23 of builder78 started including [123, 456]", "Start notification generated with notify_events=['started']")
1187 irc.message = ""
1188 irc.buildFinished(my_builder.getName(), my_build, None)
1189 self.failUnlessEqual(irc.message, "", "No finished notification with notify_events=['started']")
1191 def test_notification_finished(self):
1192 irc = MyContact()
1194 my_builder = MyBuilder("builder834")
1195 my_build = MyIrcBuild(my_builder, 862, builder.SUCCESS)
1196 my_build.changes = (
1197 Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
1200 irc.command_NOTIFY("on finished", "mynick")
1202 irc.message = ""
1203 irc.buildStarted(my_builder.getName(), my_build)
1204 self.failUnlessEqual(irc.message, "", "No started notification with notify_events=['finished']")
1206 irc.message = ""
1207 irc.buildFinished(my_builder.getName(), my_build, None)
1208 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']")
1210 def test_notification_success(self):
1211 irc = MyContact()
1213 my_builder = MyBuilder("builder834")
1214 my_build = MyIrcBuild(my_builder, 862, builder.SUCCESS)
1215 my_build.changes = (
1216 Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
1219 irc.command_NOTIFY("on success", "mynick")
1221 irc.message = ""
1222 irc.buildStarted(my_builder.getName(), my_build)
1223 self.failUnlessEqual(irc.message, "", "No started notification with notify_events=['success']")
1225 irc.message = ""
1226 irc.buildFinished(my_builder.getName(), my_build, None)
1227 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']")
1229 irc.message = ""
1230 my_build.results = builder.FAILURE
1231 irc.buildFinished(my_builder.getName(), my_build, None)
1232 self.failUnlessEqual(irc.message, "", "No finish notification generated on failure with notify_events=['success']")
1234 irc.message = ""
1235 my_build.results = builder.EXCEPTION
1236 irc.buildFinished(my_builder.getName(), my_build, None)
1237 self.failUnlessEqual(irc.message, "", "No finish notification generated on exception with notify_events=['success']")
1239 def test_notification_failed(self):
1240 irc = MyContact()
1242 my_builder = MyBuilder("builder834")
1243 my_build = MyIrcBuild(my_builder, 862, builder.FAILURE)
1244 my_build.changes = (
1245 Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
1248 irc.command_NOTIFY("on failed", "mynick")
1250 irc.message = ""
1251 irc.buildStarted(my_builder.getName(), my_build)
1252 self.failUnlessEqual(irc.message, "", "No started notification with notify_events=['failed']")
1254 irc.message = ""
1255 irc.buildFinished(my_builder.getName(), my_build, None)
1256 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']")
1258 irc.message = ""
1259 my_build.results = builder.SUCCESS
1260 irc.buildFinished(my_builder.getName(), my_build, None)
1261 self.failUnlessEqual(irc.message, "", "No finish notification generated on success with notify_events=['failed']")
1263 irc.message = ""
1264 my_build.results = builder.EXCEPTION
1265 irc.buildFinished(my_builder.getName(), my_build, None)
1266 self.failUnlessEqual(irc.message, "", "No finish notification generated on exception with notify_events=['failed']")
1268 def test_notification_exception(self):
1269 irc = MyContact()
1271 my_builder = MyBuilder("builder834")
1272 my_build = MyIrcBuild(my_builder, 862, builder.EXCEPTION)
1273 my_build.changes = (
1274 Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
1277 irc.command_NOTIFY("on exception", "mynick")
1279 irc.message = ""
1280 irc.buildStarted(my_builder.getName(), my_build)
1281 self.failUnlessEqual(irc.message, "", "No started notification with notify_events=['exception']")
1283 irc.message = ""
1284 irc.buildFinished(my_builder.getName(), my_build, None)
1285 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']")
1287 irc.message = ""
1288 my_build.results = builder.SUCCESS
1289 irc.buildFinished(my_builder.getName(), my_build, None)
1290 self.failUnlessEqual(irc.message, "", "No finish notification generated on success with notify_events=['exception']")
1292 irc.message = ""
1293 my_build.results = builder.FAILURE
1294 irc.buildFinished(my_builder.getName(), my_build, None)
1295 self.failUnlessEqual(irc.message, "", "No finish notification generated on exception with notify_events=['exception']")
1297 def test_notification_successToFailed(self):
1298 irc = MyContact()
1300 my_builder = MyBuilder("builder834")
1301 my_build = MyIrcBuild(my_builder, 862, builder.FAILURE)
1302 my_build.changes = (
1303 Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
1305 previous_build = MyIrcBuild(my_builder, 861, builder.SUCCESS)
1306 my_build.setPreviousBuild(previous_build)
1308 irc.command_NOTIFY("on successToFailed", "mynick")
1310 irc.message = ""
1311 irc.buildStarted(my_builder.getName(), my_build)
1312 self.failUnlessEqual(irc.message, "", "No started notification with notify_events=['failed']")
1314 irc.message = ""
1315 irc.buildFinished(my_builder.getName(), my_build, None)
1316 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=['successToFailed']")
1318 irc.message = ""
1319 my_build.results = builder.SUCCESS
1320 irc.buildFinished(my_builder.getName(), my_build, None)
1321 self.failUnlessEqual(irc.message, "", "No finish notification generated on success with notify_events=['failed']")
1323 irc.message = ""
1324 my_build.results = builder.EXCEPTION
1325 irc.buildFinished(my_builder.getName(), my_build, None)
1326 self.failUnlessEqual(irc.message, "", "No finish notification generated on exception with notify_events=['failed']")
1328 def test_notification_failedToSuccess(self):
1329 irc = MyContact()
1331 my_builder = MyBuilder("builder834")
1332 my_build = MyIrcBuild(my_builder, 862, builder.SUCCESS)
1333 my_build.changes = (
1334 Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
1336 previous_build = MyIrcBuild(my_builder, 861, builder.FAILURE)
1337 my_build.setPreviousBuild(previous_build)
1339 irc.command_NOTIFY("on failedToSuccess", "mynick")
1341 irc.message = ""
1342 irc.buildStarted(my_builder.getName(), my_build)
1343 self.failUnlessEqual(irc.message, "", "No started notification with notify_events=['success']")
1345 irc.message = ""
1346 irc.buildFinished(my_builder.getName(), my_build, None)
1347 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=['failedToSuccess']")
1349 irc.message = ""
1350 my_build.results = builder.FAILURE
1351 irc.buildFinished(my_builder.getName(), my_build, None)
1352 self.failUnlessEqual(irc.message, "", "No finish notification generated on failure with notify_events=['success']")
1354 irc.message = ""
1355 my_build.results = builder.EXCEPTION
1356 irc.buildFinished(my_builder.getName(), my_build, None)
1357 self.failUnlessEqual(irc.message, "", "No finish notification generated on exception with notify_events=['success']")
1359 class MyIrcBuild(builder.BuildStatus):
1360 results = None
1362 def __init__(self, parent, number, results):
1363 builder.BuildStatus.__init__(self, parent, number)
1364 self.results = results
1365 self.previousBuild = None
1367 def getResults(self):
1368 return self.results
1370 def getText(self):
1371 return ('step1', 'step2')
1373 def setPreviousBuild(self, pb):
1374 self.previousBuild = pb
1376 def getPreviousBuild(self):
1377 return self.previousBuild
1379 class URLProducer:
1380 def getURLForThing(self, build):
1381 return 'http://myserver/mypath?build=765'
1383 class MyChannel:
1384 categories = None
1385 status = URLProducer()
1387 def __init__(self):
1388 pass
1390 class MyContact(words.Contact):
1391 message = ""
1393 def __init__(self, channel = MyChannel()):
1394 words.Contact.__init__(self, channel)
1395 self.message = ""
1397 def subscribe_to_build_events(self):
1398 pass
1400 def unsubscribe_from_build_events(self):
1401 pass
1403 def send(self, msg):
1404 self.message += msg
1406 class StepStatistics(unittest.TestCase):
1407 def testStepStatistics(self):
1408 status = builder.BuildStatus(builder.BuilderStatus("test"), 123)
1409 status.addStepWithName('step1')
1410 status.addStepWithName('step2')
1411 status.addStepWithName('step3')
1412 status.addStepWithName('step4')
1414 steps = status.getSteps()
1415 (step1, step2, step3, step4) = steps
1417 step1.setStatistic('test-prop', 1)
1418 step3.setStatistic('test-prop', 2)
1419 step4.setStatistic('test-prop', 4)
1421 step1.setStatistic('other-prop', 27)
1422 # Just to have some other properties around
1424 self.failUnlessEqual(step1.getStatistic('test-prop'), 1,
1425 'Retrieve an existing property')
1426 self.failUnlessEqual(step1.getStatistic('test-prop', 99), 1,
1427 "Don't default an existing property")
1428 self.failUnlessEqual(step2.getStatistic('test-prop', 99), 99,
1429 'Default a non-existant property')
1431 self.failUnlessEqual(
1432 status.getSummaryStatistic('test-prop', operator.add), 7,
1433 'Sum property across the build')
1435 self.failUnlessEqual(
1436 status.getSummaryStatistic('test-prop', operator.add, 13), 20,
1437 'Sum property across the build with initial value')