(refs #557, #559) use tarfile to upload multiple files from the master
[buildbot.git] / buildbot / test / test_status.py
blob4615fe77484835d0a10d83947ed5a0532320b1b8
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, Build
13 from buildbot.status import builder, base, words, progress
14 from buildbot.changes.changes import Change
15 from buildbot.process.builder import Builder
16 from time import sleep
18 mail = None
19 try:
20 from buildbot.status import mail
21 except ImportError:
22 pass
23 from buildbot.status import progress, client # NEEDS COVERAGE
24 from buildbot.test.runutils import RunMixin, setupBuildStepStatus, rmtree
26 class MyStep:
27 build = None
28 def getName(self):
29 return "step"
30 def getResults(self):
31 return (builder.SUCCESS, "yay")
33 class MyLogFileProducer(builder.LogFileProducer):
34 # The reactor.callLater(0) in LogFileProducer.resumeProducing is a bit of
35 # a nuisance from a testing point of view. This subclass adds a Deferred
36 # to that call so we can find out when it is complete.
37 def resumeProducing(self):
38 d = defer.Deferred()
39 reactor.callLater(0, self._resumeProducing, d)
40 return d
41 def _resumeProducing(self, d):
42 builder.LogFileProducer._resumeProducing(self)
43 reactor.callLater(0, d.callback, None)
45 class MyLog(builder.LogFile):
46 def __init__(self, basedir, name, text=None, step=None):
47 self.fakeBuilderBasedir = basedir
48 if not step:
49 step = MyStep()
50 builder.LogFile.__init__(self, step, name, name)
51 if text:
52 self.addStdout(text)
53 self.finish()
54 def getFilename(self):
55 return os.path.join(self.fakeBuilderBasedir, self.name)
57 def subscribeConsumer(self, consumer):
58 p = MyLogFileProducer(self, consumer)
59 d = p.resumeProducing()
60 return d
62 class MyHTMLLog(builder.HTMLLogFile):
63 def __init__(self, basedir, name, html):
64 step = MyStep()
65 builder.HTMLLogFile.__init__(self, step, name, name, html)
67 class MyLogSubscriber:
68 def __init__(self):
69 self.chunks = []
70 def logChunk(self, build, step, log, channel, text):
71 self.chunks.append((channel, text))
73 class MyLogConsumer:
74 def __init__(self, limit=None):
75 self.chunks = []
76 self.finished = False
77 self.limit = limit
78 def registerProducer(self, producer, streaming):
79 self.producer = producer
80 self.streaming = streaming
81 def unregisterProducer(self):
82 self.producer = None
83 def writeChunk(self, chunk):
84 self.chunks.append(chunk)
85 if self.limit:
86 self.limit -= 1
87 if self.limit == 0:
88 self.producer.pauseProducing()
89 def finish(self):
90 self.finished = True
92 if mail:
93 class MyMailer(mail.MailNotifier):
94 def sendMessage(self, m, recipients):
95 self.parent.messages.append((m, recipients))
97 class MyStatus:
98 def getBuildbotURL(self):
99 return self.url
100 def getURLForThing(self, thing):
101 return None
102 def getProjectName(self):
103 return "myproj"
105 class MyBuilder(builder.BuilderStatus):
106 nextBuildNumber = 0
108 class MyBuild(builder.BuildStatus):
109 testlogs = []
110 def __init__(self, parent, number, results):
111 builder.BuildStatus.__init__(self, parent, number)
112 self.results = results
113 self.source = SourceStamp(revision="1.14")
114 self.reason = "build triggered by changes"
115 self.finished = True
116 def getLogs(self):
117 return self.testlogs
119 class MyLookup:
120 implements(interfaces.IEmailLookup)
122 def getAddress(self, user):
123 d = defer.Deferred()
124 # With me now is Mr Thomas Walters of West Hartlepool who is totally
125 # invisible.
126 if user == "Thomas_Walters":
127 d.callback(None)
128 else:
129 d.callback(user + "@" + "dev.com")
130 return d
132 def customTextMailMessage(attrs):
133 logLines = 3
134 text = list()
135 text.append("STATUS: %s" % attrs['result'].title())
136 text.append("")
137 text.extend([c.asText() for c in attrs['changes']])
138 text.append("")
139 name, url, lines, status = attrs['logs'][-1]
140 text.append("Last %d lines of '%s':" % (logLines, name))
141 text.extend(["\t%s\n" % line for line in lines[len(lines)-logLines:]])
142 text.append("")
143 text.append("Build number was: %s" % attrs['buildProperties']['buildnumber'])
144 text.append("")
145 text.append("-buildbot")
146 return ("\n".join(text), 'plain')
148 def customHTMLMailMessage(attrs):
149 logLines = 3
150 text = list()
151 text.append("<h3>STATUS <a href='%s'>%s</a>:</h3>" % (attrs['buildURL'],
152 attrs['result'].title()))
153 text.append("<h4>Recent Changes:</h4>")
154 text.extend([c.asHTML() for c in attrs['changes']])
155 name, url, lines, status = attrs['logs'][-1]
156 text.append("<h4>Last %d lines of '%s':</h4>" % (logLines, name))
157 text.append("<p>")
158 text.append("<br>".join([line for line in lines[len(lines)-logLines:]]))
159 text.append("</p>")
160 text.append("<p>Build number was: %s</p>" % attrs['buildProperties']['buildnumber'])
161 text.append("<br>")
162 text.append("<b>-<a href='%s'>buildbot</a></b>" % attrs['buildbotURL'])
163 return ("\n".join(text), 'html')
165 class Mail(unittest.TestCase):
167 def setUp(self):
168 self.builder = MyBuilder("builder1")
170 def stall(self, res, timeout):
171 d = defer.Deferred()
172 reactor.callLater(timeout, d.callback, res)
173 return d
175 def makeBuild(self, number, results):
176 return MyBuild(self.builder, number, results)
178 def failUnlessIn(self, substring, string):
179 self.failUnless(string.find(substring) != -1,
180 "didn't see '%s' in '%s'" % (substring, string))
182 def getProjectName(self):
183 return "PROJECT"
185 def getBuildbotURL(self):
186 return "BUILDBOT_URL"
188 def getURLForThing(self, thing):
189 return None
191 def testBuild1(self):
192 mailer = MyMailer(fromaddr="buildbot@example.com",
193 extraRecipients=["recip@example.com",
194 "recip2@example.com"],
195 lookup=mail.Domain("dev.com"))
196 mailer.parent = self
197 mailer.status = self
198 self.messages = []
200 b1 = self.makeBuild(3, builder.SUCCESS)
201 b1.blamelist = ["bob"]
203 mailer.buildFinished("builder1", b1, b1.results)
204 self.failUnless(len(self.messages) == 1)
205 m,r = self.messages.pop()
206 t = m.as_string()
207 self.failUnlessIn("To: bob@dev.com\n", t)
208 self.failUnlessIn("CC: recip2@example.com, recip@example.com\n", t)
209 self.failUnlessIn("From: buildbot@example.com\n", t)
210 self.failUnlessIn("Subject: buildbot success in PROJECT on builder1\n", t)
211 self.failUnlessIn("Date: ", t)
212 self.failUnlessIn("Build succeeded!\n", t)
213 self.failUnlessIn("Buildbot URL: BUILDBOT_URL\n", t)
215 def testBuild2(self):
216 mailer = MyMailer(fromaddr="buildbot@example.com",
217 extraRecipients=["recip@example.com",
218 "recip2@example.com"],
219 lookup="dev.com",
220 sendToInterestedUsers=False)
221 mailer.parent = self
222 mailer.status = self
223 self.messages = []
225 b1 = self.makeBuild(3, builder.SUCCESS)
226 b1.blamelist = ["bob"]
228 mailer.buildFinished("builder1", b1, b1.results)
229 self.failUnless(len(self.messages) == 1)
230 m,r = self.messages.pop()
231 t = m.as_string()
232 self.failUnlessIn("To: recip2@example.com, "
233 "recip@example.com\n", t)
234 self.failUnlessIn("From: buildbot@example.com\n", t)
235 self.failUnlessIn("Subject: buildbot success in PROJECT on builder1\n", t)
236 self.failUnlessIn("Build succeeded!\n", t)
237 self.failUnlessIn("Buildbot URL: BUILDBOT_URL\n", t)
239 def testBuildStatusCategory(self):
240 # a status client only interested in a category should only receive
241 # from that category
242 mailer = MyMailer(fromaddr="buildbot@example.com",
243 extraRecipients=["recip@example.com",
244 "recip2@example.com"],
245 lookup="dev.com",
246 sendToInterestedUsers=False,
247 categories=["debug"])
249 mailer.parent = self
250 mailer.status = self
251 self.messages = []
253 b1 = self.makeBuild(3, builder.SUCCESS)
254 b1.blamelist = ["bob"]
256 mailer.buildFinished("builder1", b1, b1.results)
257 self.failIf(self.messages)
259 def testBuilderCategory(self):
260 # a builder in a certain category should notify status clients that
261 # did not list categories, or categories including this one
262 mailer1 = MyMailer(fromaddr="buildbot@example.com",
263 extraRecipients=["recip@example.com",
264 "recip2@example.com"],
265 lookup="dev.com",
266 sendToInterestedUsers=False)
267 mailer2 = MyMailer(fromaddr="buildbot@example.com",
268 extraRecipients=["recip@example.com",
269 "recip2@example.com"],
270 lookup="dev.com",
271 sendToInterestedUsers=False,
272 categories=["active"])
273 mailer3 = MyMailer(fromaddr="buildbot@example.com",
274 extraRecipients=["recip@example.com",
275 "recip2@example.com"],
276 lookup="dev.com",
277 sendToInterestedUsers=False,
278 categories=["active", "debug"])
280 builderd = MyBuilder("builder2", "debug")
282 mailer1.parent = self
283 mailer1.status = self
284 mailer2.parent = self
285 mailer2.status = self
286 mailer3.parent = self
287 mailer3.status = self
288 self.messages = []
290 t = mailer1.builderAdded("builder2", builderd)
291 self.assertEqual(len(mailer1.watched), 1)
292 self.assertEqual(t, mailer1)
293 t = mailer2.builderAdded("builder2", builderd)
294 self.assertEqual(len(mailer2.watched), 0)
295 self.assertEqual(t, None)
296 t = mailer3.builderAdded("builder2", builderd)
297 self.assertEqual(len(mailer3.watched), 1)
298 self.assertEqual(t, mailer3)
300 b2 = MyBuild(builderd, 3, builder.SUCCESS)
301 b2.blamelist = ["bob"]
303 mailer1.buildFinished("builder2", b2, b2.results)
304 self.failUnlessEqual(len(self.messages), 1)
305 self.messages = []
306 mailer2.buildFinished("builder2", b2, b2.results)
307 self.failUnlessEqual(len(self.messages), 0)
308 self.messages = []
309 mailer3.buildFinished("builder2", b2, b2.results)
310 self.failUnlessEqual(len(self.messages), 1)
312 def testCustomTextMessage(self):
313 basedir = "test_custom_text_mesg"
314 os.mkdir(basedir)
315 mailer = MyMailer(fromaddr="buildbot@example.com", mode="problem",
316 extraRecipients=["recip@example.com",
317 "recip2@example.com"],
318 lookup=MyLookup(),
319 customMesg=customTextMailMessage)
320 mailer.parent = self
321 mailer.status = self
322 self.messages = []
324 b1 = self.makeBuild(4, builder.FAILURE)
325 b1.setProperty('buildnumber', 1, 'Build')
326 b1.setText(["snarkleack", "polarization", "failed"])
327 b1.blamelist = ["dev3", "dev3", "dev3", "dev4",
328 "Thomas_Walters"]
329 b1.source.changes = (Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 123),
330 Change(who = 'author2', files = ['file2'], comments = 'comment2', revision = 456))
331 b1.testlogs = [MyLog(basedir, 'compile', "Compile log here\n"),
332 MyLog(basedir, 'test', "Test log here\nTest 1 failed\nTest 2 failed\nTest 3 failed\nTest 4 failed\n")]
334 mailer.buildFinished("builder1", b1, b1.results)
335 m,r = self.messages.pop()
336 t = m.as_string()
338 # Uncomment to review custom message
340 #self.fail(t)
341 self.failUnlessIn("comment1", t)
342 self.failUnlessIn("comment2", t)
343 self.failUnlessIn("Test 4 failed", t)
344 self.failUnlessIn("number was: 1", t)
347 def testCustomHTMLMessage(self):
348 basedir = "test_custom_HTML_mesg"
349 os.mkdir(basedir)
350 mailer = MyMailer(fromaddr="buildbot@example.com", mode="problem",
351 extraRecipients=["recip@example.com",
352 "recip2@example.com"],
353 lookup=MyLookup(),
354 customMesg=customHTMLMailMessage)
355 mailer.parent = self
356 mailer.status = self
357 self.messages = []
359 b1 = self.makeBuild(4, builder.FAILURE)
360 b1.setProperty('buildnumber', 1, 'Build')
361 b1.setText(["snarkleack", "polarization", "failed"])
362 b1.blamelist = ["dev3", "dev3", "dev3", "dev4",
363 "Thomas_Walters"]
364 b1.source.changes = (Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 123),
365 Change(who = 'author2', files = ['file2'], comments = 'comment2', revision = 456))
366 b1.testlogs = [MyLog(basedir, 'compile', "Compile log here\n"),
367 MyLog(basedir, 'test', "Test log here\nTest 1 failed\nTest 2 failed\nTest 3 failed\nTest 4 failed\n")]
369 mailer.buildFinished("builder1", b1, b1.results)
370 m,r = self.messages.pop()
371 t = m.as_string()
373 # Uncomment to review custom message
375 #self.fail(t)
376 self.failUnlessIn("<h4>Last 3 lines of 'step.test':</h4>", t)
377 self.failUnlessIn("<p>Changed by: <b>author2</b><br />", t)
378 self.failUnlessIn("Test 3 failed", t)
379 self.failUnlessIn("number was: 1", t)
381 def testShouldAttachLog(self):
382 mailer = mail.MailNotifier(fromaddr="buildbot@example.com", addLogs=True)
383 self.assertTrue(mailer._shouldAttachLog('anything'))
384 mailer = mail.MailNotifier(fromaddr="buildbot@example.com", addLogs=False)
385 self.assertFalse(mailer._shouldAttachLog('anything'))
386 mailer = mail.MailNotifier(fromaddr="buildbot@example.com", addLogs=['something'])
387 self.assertFalse(mailer._shouldAttachLog('anything'))
388 self.assertTrue(mailer._shouldAttachLog('something'))
390 def testFailure(self):
391 mailer = MyMailer(fromaddr="buildbot@example.com", mode="problem",
392 extraRecipients=["recip@example.com",
393 "recip2@example.com"],
394 lookup=MyLookup())
395 mailer.parent = self
396 mailer.status = self
397 self.messages = []
399 b1 = self.makeBuild(3, builder.SUCCESS)
400 b1.blamelist = ["dev1", "dev2"]
401 b2 = self.makeBuild(4, builder.FAILURE)
402 b2.setText(["snarkleack", "polarization", "failed"])
403 b2.blamelist = ["dev3", "dev3", "dev3", "dev4",
404 "Thomas_Walters"]
405 mailer.buildFinished("builder1", b1, b1.results)
406 self.failIf(self.messages)
407 mailer.buildFinished("builder1", b2, b2.results)
408 self.failUnless(len(self.messages) == 1)
409 m,r = self.messages.pop()
410 t = m.as_string()
411 self.failUnlessIn("To: dev3@dev.com, dev4@dev.com\n", t)
412 self.failUnlessIn("CC: recip2@example.com, recip@example.com\n", t)
413 self.failUnlessIn("From: buildbot@example.com\n", t)
414 self.failUnlessIn("Subject: buildbot failure in PROJECT on builder1\n", t)
415 self.failUnlessIn("The Buildbot has detected a new failure", t)
416 self.failUnlessIn("BUILD FAILED: snarkleack polarization failed\n", t)
417 self.failUnlessEqual(set(r), set(["dev3@dev.com", "dev4@dev.com",
418 "recip2@example.com", "recip@example.com"]))
420 def testLogs(self):
421 basedir = "test_status_logs"
422 os.mkdir(basedir)
423 mailer = MyMailer(fromaddr="buildbot@example.com", addLogs=True,
424 extraRecipients=["recip@example.com",
425 "recip2@example.com"])
426 mailer.parent = self
427 mailer.status = self
428 self.messages = []
430 b1 = self.makeBuild(3, builder.WARNINGS)
431 b1.testlogs = [MyLog(basedir, 'compile', "Compile log here\n"),
432 MyLog(basedir,
433 'test', "Test log here\nTest 4 failed\n"),
435 b1.text = ["unusual", "gnarzzler", "output"]
436 mailer.buildFinished("builder1", b1, b1.results)
437 self.failUnless(len(self.messages) == 1)
438 m,r = self.messages.pop()
439 t = m.as_string()
440 self.failUnlessIn("Subject: buildbot warnings in PROJECT on builder1\n", t)
441 m2 = email.message_from_string(t)
442 p = m2.get_payload()
443 self.failUnlessEqual(len(p), 3)
445 self.failUnlessIn("Build Had Warnings: unusual gnarzzler output\n",
446 p[0].get_payload())
448 self.failUnlessEqual(p[1].get_filename(), "step.compile")
449 self.failUnlessEqual(p[1].get_payload(), "Compile log here\n")
451 self.failUnlessEqual(p[2].get_filename(), "step.test")
452 self.failUnlessIn("Test log here\n", p[2].get_payload())
454 def testMail(self):
455 basedir = "test_status_mail"
456 os.mkdir(basedir)
457 dest = os.environ.get("BUILDBOT_TEST_MAIL")
458 if not dest:
459 raise unittest.SkipTest("define BUILDBOT_TEST_MAIL=dest to run this")
460 mailer = mail.MailNotifier(fromaddr="buildbot@example.com",
461 addLogs=True,
462 extraRecipients=[dest])
463 s = MyStatus()
464 s.url = "project URL"
465 mailer.status = s
467 b1 = self.makeBuild(3, builder.SUCCESS)
468 b1.testlogs = [MyLog(basedir, 'compile', "Compile log here\n"),
469 MyLog(basedir,
470 'test', "Test log here\nTest 4 failed\n"),
473 d = mailer.buildFinished("builder1", b1, b1.results)
474 # When this fires, the mail has been sent, but the SMTP connection is
475 # still up (because smtp.sendmail relies upon the server to hang up).
476 # Spin for a moment to avoid the "unclean reactor" warning that Trial
477 # gives us if we finish before the socket is disconnected. Really,
478 # sendmail() ought to hang up the connection once it is finished:
479 # otherwise a malicious SMTP server could make us consume lots of
480 # memory.
481 d.addCallback(self.stall, 0.1)
482 return d
484 if not mail:
485 Mail.skip = "the Twisted Mail package is not installed"
487 class Progress(unittest.TestCase):
488 def testWavg(self):
489 bp = progress.BuildProgress([])
490 e = progress.Expectations(bp)
491 # wavg(old, current)
492 self.failUnlessEqual(e.wavg(None, None), None)
493 self.failUnlessEqual(e.wavg(None, 3), 3)
494 self.failUnlessEqual(e.wavg(3, None), 3)
495 self.failUnlessEqual(e.wavg(3, 4), 3.5)
496 e.decay = 0.1
497 self.failUnlessEqual(e.wavg(3, 4), 3.1)
500 class Results(unittest.TestCase):
502 def testAddResults(self):
503 b = builder.BuildStatus(builder.BuilderStatus("test"), 12)
504 testname = ("buildbot", "test", "test_status", "Results",
505 "testAddResults")
506 r1 = builder.TestResult(name=testname,
507 results=builder.SUCCESS,
508 text=["passed"],
509 logs={'output': ""},
511 b.addTestResult(r1)
513 res = b.getTestResults()
514 self.failUnlessEqual(res.keys(), [testname])
515 t = res[testname]
516 self.failUnless(interfaces.ITestResult.providedBy(t))
517 self.failUnlessEqual(t.getName(), testname)
518 self.failUnlessEqual(t.getResults(), builder.SUCCESS)
519 self.failUnlessEqual(t.getText(), ["passed"])
520 self.failUnlessEqual(t.getLogs(), {'output': ""})
522 class Log(unittest.TestCase):
523 basedir = "status_log_add"
525 def setUp(self):
526 self.tearDown()
527 os.mkdir(self.basedir)
529 def tearDown(self):
530 if os.path.exists(self.basedir):
531 rmtree(self.basedir)
533 def testAdd(self):
534 l = MyLog(self.basedir, "compile", step=13)
535 self.failUnlessEqual(l.getName(), "compile")
536 self.failUnlessEqual(l.getStep(), 13)
537 l.addHeader("HEADER\n")
538 l.addStdout("Some text\n")
539 l.addStderr("Some error\n")
540 l.addStdout("Some more text\n")
541 self.failIf(l.isFinished())
542 l.finish()
543 self.failUnless(l.isFinished())
544 self.failUnlessEqual(l.getText(),
545 "Some text\nSome error\nSome more text\n")
546 self.failUnlessEqual(l.getTextWithHeaders(),
547 "HEADER\n" +
548 "Some text\nSome error\nSome more text\n")
549 self.failUnlessEqual(len(list(l.getChunks())), 4)
551 self.failUnless(l.hasContents())
552 try:
553 os.unlink(l.getFilename())
554 except OSError:
555 os.unlink(l.getFilename() + ".bz2")
556 self.failIf(l.hasContents())
558 def TODO_testDuplicate(self):
559 # create multiple logs for the same step with the same logname, make
560 # sure their on-disk filenames are suitably uniquified. This
561 # functionality actually lives in BuildStepStatus and BuildStatus, so
562 # this test must involve more than just the MyLog class.
564 # naieve approach, doesn't work
565 l1 = MyLog(self.basedir, "duplicate")
566 l1.addStdout("Some text\n")
567 l1.finish()
568 l2 = MyLog(self.basedir, "duplicate")
569 l2.addStdout("Some more text\n")
570 l2.finish()
571 self.failIfEqual(l1.getFilename(), l2.getFilename())
573 def testMerge1(self):
574 l = MyLog(self.basedir, "merge1")
575 l.addHeader("HEADER\n")
576 l.addStdout("Some text\n")
577 l.addStdout("Some more text\n")
578 l.addStdout("more\n")
579 l.finish()
580 self.failUnlessEqual(l.getText(),
581 "Some text\nSome more text\nmore\n")
582 self.failUnlessEqual(l.getTextWithHeaders(),
583 "HEADER\n" +
584 "Some text\nSome more text\nmore\n")
585 self.failUnlessEqual(len(list(l.getChunks())), 2)
587 def testMerge2(self):
588 l = MyLog(self.basedir, "merge2")
589 l.addHeader("HEADER\n")
590 for i in xrange(1000):
591 l.addStdout("aaaa")
592 for i in xrange(30):
593 l.addStderr("bbbb")
594 for i in xrange(10):
595 l.addStdout("cc")
596 target = 1000*"aaaa" + 30 * "bbbb" + 10 * "cc"
597 self.failUnlessEqual(len(l.getText()), len(target))
598 self.failUnlessEqual(l.getText(), target)
599 l.finish()
600 self.failUnlessEqual(len(l.getText()), len(target))
601 self.failUnlessEqual(l.getText(), target)
602 self.failUnlessEqual(len(list(l.getChunks())), 4)
604 def testMerge3(self):
605 l = MyLog(self.basedir, "merge3")
606 l.chunkSize = 100
607 l.addHeader("HEADER\n")
608 for i in xrange(8):
609 l.addStdout(10*"a")
610 for i in xrange(8):
611 l.addStdout(10*"a")
612 self.failUnlessEqual(list(l.getChunks()),
613 [(builder.HEADER, "HEADER\n"),
614 (builder.STDOUT, 100*"a"),
615 (builder.STDOUT, 60*"a")])
616 l.finish()
617 self.failUnlessEqual(l.getText(), 160*"a")
619 def testReadlines(self):
620 l = MyLog(self.basedir, "chunks1")
621 l.addHeader("HEADER\n") # should be ignored
622 l.addStdout("Some text\n")
623 l.addStdout("Some More Text\nAnd Some More\n")
624 l.addStderr("Some Stderr\n")
625 l.addStdout("Last line\n")
626 l.finish()
627 alllines = list(l.readlines())
628 self.failUnlessEqual(len(alllines), 4)
629 self.failUnlessEqual(alllines[0], "Some text\n")
630 self.failUnlessEqual(alllines[2], "And Some More\n")
631 self.failUnlessEqual(alllines[3], "Last line\n")
632 stderr = list(l.readlines(interfaces.LOG_CHANNEL_STDERR))
633 self.failUnlessEqual(len(stderr), 1)
634 self.failUnlessEqual(stderr[0], "Some Stderr\n")
635 lines = l.readlines()
636 if False: # TODO: l.readlines() is not yet an iterator
637 # verify that it really is an iterator
638 line0 = lines.next()
639 self.failUnlessEqual(line0, "Some text\n")
640 line1 = lines.next()
641 line2 = lines.next()
642 self.failUnlessEqual(line2, "And Some More\n")
645 def testChunks(self):
646 l = MyLog(self.basedir, "chunks2")
647 c1 = l.getChunks()
648 l.addHeader("HEADER\n")
649 l.addStdout("Some text\n")
650 self.failUnlessEqual("".join(l.getChunks(onlyText=True)),
651 "HEADER\nSome text\n")
652 c2 = l.getChunks()
654 l.addStdout("Some more text\n")
655 self.failUnlessEqual("".join(l.getChunks(onlyText=True)),
656 "HEADER\nSome text\nSome more text\n")
657 c3 = l.getChunks()
659 l.addStdout("more\n")
660 l.finish()
662 self.failUnlessEqual(list(c1), [])
663 self.failUnlessEqual(list(c2), [(builder.HEADER, "HEADER\n"),
664 (builder.STDOUT, "Some text\n")])
665 self.failUnlessEqual(list(c3), [(builder.HEADER, "HEADER\n"),
666 (builder.STDOUT,
667 "Some text\nSome more text\n")])
669 self.failUnlessEqual(l.getText(),
670 "Some text\nSome more text\nmore\n")
671 self.failUnlessEqual(l.getTextWithHeaders(),
672 "HEADER\n" +
673 "Some text\nSome more text\nmore\n")
674 self.failUnlessEqual(len(list(l.getChunks())), 2)
676 def testUpgrade(self):
677 l = MyLog(self.basedir, "upgrade")
678 l.addHeader("HEADER\n")
679 l.addStdout("Some text\n")
680 l.addStdout("Some more text\n")
681 l.addStdout("more\n")
682 l.finish()
683 self.failUnless(l.hasContents())
684 # now doctor it to look like a 0.6.4-era non-upgraded logfile
685 l.entries = list(l.getChunks())
686 del l.filename
687 try:
688 os.unlink(l.getFilename() + ".bz2")
689 except OSError:
690 os.unlink(l.getFilename())
691 # now make sure we can upgrade it
692 l.upgrade("upgrade")
693 self.failUnlessEqual(l.getText(),
694 "Some text\nSome more text\nmore\n")
695 self.failUnlessEqual(len(list(l.getChunks())), 2)
696 self.failIf(l.entries)
698 # now, do it again, but make it look like an upgraded 0.6.4 logfile
699 # (i.e. l.filename is missing, but the contents are there on disk)
700 l.entries = list(l.getChunks())
701 del l.filename
702 l.upgrade("upgrade")
703 self.failUnlessEqual(l.getText(),
704 "Some text\nSome more text\nmore\n")
705 self.failUnlessEqual(len(list(l.getChunks())), 2)
706 self.failIf(l.entries)
707 self.failUnless(l.hasContents())
709 def testHTMLUpgrade(self):
710 l = MyHTMLLog(self.basedir, "upgrade", "log contents")
711 l.upgrade("filename")
713 def testSubscribe(self):
714 l1 = MyLog(self.basedir, "subscribe1")
715 l1.finish()
716 self.failUnless(l1.isFinished())
718 s = MyLogSubscriber()
719 l1.subscribe(s, True)
720 l1.unsubscribe(s)
721 self.failIf(s.chunks)
723 s = MyLogSubscriber()
724 l1.subscribe(s, False)
725 l1.unsubscribe(s)
726 self.failIf(s.chunks)
728 finished = []
729 l2 = MyLog(self.basedir, "subscribe2")
730 l2.waitUntilFinished().addCallback(finished.append)
731 l2.addHeader("HEADER\n")
732 s1 = MyLogSubscriber()
733 l2.subscribe(s1, True)
734 s2 = MyLogSubscriber()
735 l2.subscribe(s2, False)
736 self.failUnlessEqual(s1.chunks, [(builder.HEADER, "HEADER\n")])
737 self.failUnlessEqual(s2.chunks, [])
739 l2.addStdout("Some text\n")
740 self.failUnlessEqual(s1.chunks, [(builder.HEADER, "HEADER\n"),
741 (builder.STDOUT, "Some text\n")])
742 self.failUnlessEqual(s2.chunks, [(builder.STDOUT, "Some text\n")])
743 l2.unsubscribe(s1)
745 l2.addStdout("Some more text\n")
746 self.failUnlessEqual(s1.chunks, [(builder.HEADER, "HEADER\n"),
747 (builder.STDOUT, "Some text\n")])
748 self.failUnlessEqual(s2.chunks, [(builder.STDOUT, "Some text\n"),
749 (builder.STDOUT, "Some more text\n"),
751 self.failIf(finished)
752 l2.finish()
753 self.failUnlessEqual(finished, [l2])
755 def testConsumer(self):
756 l1 = MyLog(self.basedir, "consumer1")
757 l1.finish()
758 self.failUnless(l1.isFinished())
760 s = MyLogConsumer()
761 d = l1.subscribeConsumer(s)
762 d.addCallback(self._testConsumer_1, s)
763 return d
764 testConsumer.timeout = 5
765 def _testConsumer_1(self, res, s):
766 self.failIf(s.chunks)
767 self.failUnless(s.finished)
768 self.failIf(s.producer) # producer should be registered and removed
770 l2 = MyLog(self.basedir, "consumer2")
771 l2.addHeader("HEADER\n")
772 l2.finish()
773 self.failUnless(l2.isFinished())
775 s = MyLogConsumer()
776 d = l2.subscribeConsumer(s)
777 d.addCallback(self._testConsumer_2, s)
778 return d
779 def _testConsumer_2(self, res, s):
780 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n")])
781 self.failUnless(s.finished)
782 self.failIf(s.producer) # producer should be registered and removed
785 l2 = MyLog(self.basedir, "consumer3")
786 l2.chunkSize = 1000
787 l2.addHeader("HEADER\n")
788 l2.addStdout(800*"a")
789 l2.addStdout(800*"a") # should now have two chunks on disk, 1000+600
790 l2.addStdout(800*"b") # HEADER,1000+600*a on disk, 800*a in memory
791 l2.addStdout(800*"b") # HEADER,1000+600*a,1000+600*b on disk
792 l2.addStdout(200*"c") # HEADER,1000+600*a,1000+600*b on disk,
793 # 200*c in memory
795 s = MyLogConsumer(limit=1)
796 d = l2.subscribeConsumer(s)
797 d.addCallback(self._testConsumer_3, l2, s)
798 return d
799 def _testConsumer_3(self, res, l2, s):
800 self.failUnless(s.streaming)
801 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n")])
802 s.limit = 1
803 d = s.producer.resumeProducing()
804 d.addCallback(self._testConsumer_4, l2, s)
805 return d
806 def _testConsumer_4(self, res, l2, s):
807 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n"),
808 (builder.STDOUT, 1000*"a"),
810 s.limit = None
811 d = s.producer.resumeProducing()
812 d.addCallback(self._testConsumer_5, l2, s)
813 return d
814 def _testConsumer_5(self, res, l2, s):
815 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n"),
816 (builder.STDOUT, 1000*"a"),
817 (builder.STDOUT, 600*"a"),
818 (builder.STDOUT, 1000*"b"),
819 (builder.STDOUT, 600*"b"),
820 (builder.STDOUT, 200*"c")])
821 l2.addStdout(1000*"c") # HEADER,1600*a,1600*b,1200*c on disk
822 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n"),
823 (builder.STDOUT, 1000*"a"),
824 (builder.STDOUT, 600*"a"),
825 (builder.STDOUT, 1000*"b"),
826 (builder.STDOUT, 600*"b"),
827 (builder.STDOUT, 200*"c"),
828 (builder.STDOUT, 1000*"c")])
829 l2.finish()
830 self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n"),
831 (builder.STDOUT, 1000*"a"),
832 (builder.STDOUT, 600*"a"),
833 (builder.STDOUT, 1000*"b"),
834 (builder.STDOUT, 600*"b"),
835 (builder.STDOUT, 200*"c"),
836 (builder.STDOUT, 1000*"c")])
837 self.failIf(s.producer)
838 self.failUnless(s.finished)
840 def testLargeSummary(self):
841 bigtext = "a" * 200000 # exceed the NetstringReceiver 100KB limit
842 l = MyLog(self.basedir, "large", bigtext)
843 s = MyLogConsumer()
844 d = l.subscribeConsumer(s)
845 def _check(res):
846 for ctype,chunk in s.chunks:
847 self.failUnless(len(chunk) < 100000)
848 merged = "".join([c[1] for c in s.chunks])
849 self.failUnless(merged == bigtext)
850 d.addCallback(_check)
851 # when this fails, it fails with a timeout, and there is an exception
852 # sent to log.err(). This AttributeError exception is in
853 # NetstringReceiver.dataReceived where it does
854 # self.transport.loseConnection() because of the NetstringParseError,
855 # however self.transport is None
856 return d
857 testLargeSummary.timeout = 5
860 class CompressLog(unittest.TestCase):
861 def testCompressLogs(self):
862 bss = setupBuildStepStatus("test-compress")
863 bss.build.builder.setLogCompressionLimit(1024)
864 l = bss.addLog('not-compress')
865 l.addStdout('a' * 512)
866 l.finish()
867 lc = bss.addLog('to-compress')
868 lc.addStdout('b' * 1024)
869 lc.finish()
870 d = bss.stepFinished(builder.SUCCESS)
871 self.failUnless(d is not None)
872 d.addCallback(self._verifyCompression, bss)
873 return d
875 def _verifyCompression(self, result, bss):
876 self.failUnless(len(bss.getLogs()), 2)
877 (ncl, cl) = bss.getLogs() # not compressed, compressed log
878 self.failUnless(os.path.isfile(ncl.getFilename()))
879 self.failIf(os.path.isfile(ncl.getFilename() + ".bz2"))
880 self.failIf(os.path.isfile(cl.getFilename()))
881 self.failUnless(os.path.isfile(cl.getFilename() + ".bz2"))
882 content = ncl.getText()
883 self.failUnless(len(content), 512)
884 content = cl.getText()
885 self.failUnless(len(content), 1024)
886 pass
888 config_base = """
889 from buildbot.process import factory
890 from buildbot.steps import dummy
891 from buildbot.buildslave import BuildSlave
892 s = factory.s
894 f1 = factory.QuickBuildFactory('fakerep', 'cvsmodule', configure=None)
896 f2 = factory.BuildFactory([
897 s(dummy.Dummy, timeout=1),
898 s(dummy.RemoteDummy, timeout=2),
901 BuildmasterConfig = c = {}
902 c['slaves'] = [BuildSlave('bot1', 'sekrit')]
903 c['schedulers'] = []
904 c['builders'] = []
905 c['builders'].append({'name':'quick', 'slavename':'bot1',
906 'builddir': 'quickdir', 'factory': f1})
907 c['slavePortnum'] = 0
910 config_2 = config_base + """
911 c['builders'] = [{'name': 'dummy', 'slavename': 'bot1',
912 'builddir': 'dummy1', 'factory': f2},
913 {'name': 'testdummy', 'slavename': 'bot1',
914 'builddir': 'dummy2', 'factory': f2, 'category': 'test'}]
917 class STarget(base.StatusReceiver):
918 debug = False
920 def __init__(self, mode):
921 self.mode = mode
922 self.events = []
923 def announce(self):
924 if self.debug:
925 print self.events[-1]
927 def builderAdded(self, name, builder):
928 self.events.append(("builderAdded", name, builder))
929 self.announce()
930 if "builder" in self.mode:
931 return self
932 def builderChangedState(self, name, state):
933 self.events.append(("builderChangedState", name, state))
934 self.announce()
935 def buildStarted(self, name, build):
936 self.events.append(("buildStarted", name, build))
937 self.announce()
938 if "eta" in self.mode:
939 self.eta_build = build.getETA()
940 if "build" in self.mode:
941 return self
942 def buildETAUpdate(self, build, ETA):
943 self.events.append(("buildETAUpdate", build, ETA))
944 self.announce()
945 def stepStarted(self, build, step):
946 self.events.append(("stepStarted", build, step))
947 self.announce()
948 if 0 and "eta" in self.mode:
949 print "TIMES", step.getTimes()
950 print "ETA", step.getETA()
951 print "EXP", step.getExpectations()
952 if "step" in self.mode:
953 return self
954 def stepTextChanged(self, build, step, text):
955 self.events.append(("stepTextChanged", step, text))
956 def stepText2Changed(self, build, step, text2):
957 self.events.append(("stepText2Changed", step, text2))
958 def stepETAUpdate(self, build, step, ETA, expectations):
959 self.events.append(("stepETAUpdate", build, step, ETA, expectations))
960 self.announce()
961 def logStarted(self, build, step, log):
962 self.events.append(("logStarted", build, step, log))
963 self.announce()
964 def logFinished(self, build, step, log):
965 self.events.append(("logFinished", build, step, log))
966 self.announce()
967 def stepFinished(self, build, step, results):
968 self.events.append(("stepFinished", build, step, results))
969 if 0 and "eta" in self.mode:
970 print "post-EXP", step.getExpectations()
971 self.announce()
972 def buildFinished(self, name, build, results):
973 self.events.append(("buildFinished", name, build, results))
974 self.announce()
975 def builderRemoved(self, name):
976 self.events.append(("builderRemoved", name))
977 self.announce()
979 class Subscription(RunMixin, unittest.TestCase):
980 # verify that StatusTargets can subscribe/unsubscribe properly
982 def testSlave(self):
983 m = self.master
984 s = m.getStatus()
985 self.t1 = t1 = STarget(["builder"])
986 #t1.debug = True; print
987 s.subscribe(t1)
988 self.failUnlessEqual(len(t1.events), 0)
990 self.t3 = t3 = STarget(["builder", "build", "step"])
991 s.subscribe(t3)
993 m.loadConfig(config_2)
994 m.readConfig = True
995 m.startService()
997 self.failUnlessEqual(len(t1.events), 4)
998 self.failUnlessEqual(t1.events[0][0:2], ("builderAdded", "dummy"))
999 self.failUnlessEqual(t1.events[1],
1000 ("builderChangedState", "dummy", "offline"))
1001 self.failUnlessEqual(t1.events[2][0:2], ("builderAdded", "testdummy"))
1002 self.failUnlessEqual(t1.events[3],
1003 ("builderChangedState", "testdummy", "offline"))
1004 t1.events = []
1006 self.failUnlessEqual(s.getBuilderNames(), ["dummy", "testdummy"])
1007 self.failUnlessEqual(s.getBuilderNames(categories=['test']),
1008 ["testdummy"])
1009 self.s1 = s1 = s.getBuilder("dummy")
1010 self.failUnlessEqual(s1.getName(), "dummy")
1011 self.failUnlessEqual(s1.getState(), ("offline", []))
1012 self.failUnlessEqual(s1.getCurrentBuilds(), [])
1013 self.failUnlessEqual(s1.getLastFinishedBuild(), None)
1014 self.failUnlessEqual(s1.getBuild(-1), None)
1015 #self.failUnlessEqual(s1.getEvent(-1), foo("created"))
1017 # status targets should, upon being subscribed, immediately get a
1018 # list of all current builders matching their category
1019 self.t2 = t2 = STarget([])
1020 s.subscribe(t2)
1021 self.failUnlessEqual(len(t2.events), 2)
1022 self.failUnlessEqual(t2.events[0][0:2], ("builderAdded", "dummy"))
1023 self.failUnlessEqual(t2.events[1][0:2], ("builderAdded", "testdummy"))
1025 d = self.connectSlave(builders=["dummy", "testdummy"])
1026 d.addCallback(self._testSlave_1, t1)
1027 return d
1029 def _testSlave_1(self, res, t1):
1030 self.failUnlessEqual(len(t1.events), 2)
1031 self.failUnlessEqual(t1.events[0],
1032 ("builderChangedState", "dummy", "idle"))
1033 self.failUnlessEqual(t1.events[1],
1034 ("builderChangedState", "testdummy", "idle"))
1035 t1.events = []
1037 c = interfaces.IControl(self.master)
1038 req = BuildRequest("forced build for testing", SourceStamp(), 'test_builder')
1039 c.getBuilder("dummy").requestBuild(req)
1040 d = req.waitUntilFinished()
1041 d2 = self.master.botmaster.waitUntilBuilderIdle("dummy")
1042 dl = defer.DeferredList([d, d2])
1043 dl.addCallback(self._testSlave_2)
1044 return dl
1046 def _testSlave_2(self, res):
1047 # t1 subscribes to builds, but not anything lower-level
1048 ev = self.t1.events
1049 self.failUnlessEqual(len(ev), 4)
1050 self.failUnlessEqual(ev[0][0:3],
1051 ("builderChangedState", "dummy", "building"))
1052 self.failUnlessEqual(ev[1][0], "buildStarted")
1053 self.failUnlessEqual(ev[2][0:2]+ev[2][3:4],
1054 ("buildFinished", "dummy", builder.SUCCESS))
1055 self.failUnlessEqual(ev[3][0:3],
1056 ("builderChangedState", "dummy", "idle"))
1058 self.failUnlessEqual([ev[0] for ev in self.t3.events],
1059 ["builderAdded",
1060 "builderChangedState", # offline
1061 "builderAdded",
1062 "builderChangedState", # idle
1063 "builderChangedState", # offline
1064 "builderChangedState", # idle
1065 "builderChangedState", # building
1066 "buildStarted",
1067 "stepStarted", "stepETAUpdate",
1068 "stepTextChanged", "stepFinished",
1069 "stepStarted", "stepETAUpdate",
1070 "stepTextChanged", "logStarted", "logFinished",
1071 "stepTextChanged", "stepText2Changed",
1072 "stepFinished",
1073 "buildFinished",
1074 "builderChangedState", # idle
1077 b = self.s1.getLastFinishedBuild()
1078 self.failUnless(b)
1079 self.failUnlessEqual(b.getBuilder().getName(), "dummy")
1080 self.failUnlessEqual(b.getNumber(), 0)
1081 self.failUnlessEqual(b.getSourceStamp().branch, None)
1082 self.failUnlessEqual(b.getSourceStamp().patch, None)
1083 self.failUnlessEqual(b.getSourceStamp().revision, None)
1084 self.failUnlessEqual(b.getReason(), "forced build for testing")
1085 self.failUnlessEqual(b.getChanges(), ())
1086 self.failUnlessEqual(b.getResponsibleUsers(), [])
1087 self.failUnless(b.isFinished())
1088 self.failUnlessEqual(b.getText(), ['build', 'successful'])
1089 self.failUnlessEqual(b.getResults(), builder.SUCCESS)
1091 steps = b.getSteps()
1092 self.failUnlessEqual(len(steps), 2)
1094 eta = 0
1095 st1 = steps[0]
1096 self.failUnlessEqual(st1.getName(), "dummy")
1097 self.failUnless(st1.isFinished())
1098 self.failUnlessEqual(st1.getText(), ["delay", "1 secs"])
1099 start,finish = st1.getTimes()
1100 self.failUnless(0.5 < (finish-start) < 10)
1101 self.failUnlessEqual(st1.getExpectations(), [])
1102 self.failUnlessEqual(st1.getLogs(), [])
1103 eta += finish-start
1105 st2 = steps[1]
1106 self.failUnlessEqual(st2.getName(), "remote dummy")
1107 self.failUnless(st2.isFinished())
1108 self.failUnlessEqual(st2.getText(),
1109 ["remote", "delay", "2 secs"])
1110 start,finish = st2.getTimes()
1111 self.failUnless(1.5 < (finish-start) < 10)
1112 eta += finish-start
1113 self.failUnlessEqual(st2.getExpectations(), [('output', 38, None)])
1114 logs = st2.getLogs()
1115 self.failUnlessEqual(len(logs), 1)
1116 self.failUnlessEqual(logs[0].getName(), "stdio")
1117 self.failUnlessEqual(logs[0].getText(), "data")
1119 self.eta = eta
1120 # now we run it a second time, and we should have an ETA
1122 self.t4 = t4 = STarget(["builder", "build", "eta"])
1123 self.master.getStatus().subscribe(t4)
1124 c = interfaces.IControl(self.master)
1125 req = BuildRequest("forced build for testing", SourceStamp(), 'test_builder')
1126 c.getBuilder("dummy").requestBuild(req)
1127 d = req.waitUntilFinished()
1128 d2 = self.master.botmaster.waitUntilBuilderIdle("dummy")
1129 dl = defer.DeferredList([d, d2])
1130 dl.addCallback(self._testSlave_3)
1131 return dl
1133 def _testSlave_3(self, res):
1134 t4 = self.t4
1135 eta = self.eta
1136 self.failUnless(eta-1 < t4.eta_build < eta+1, # should be 3 seconds
1137 "t4.eta_build was %g, not in (%g,%g)"
1138 % (t4.eta_build, eta-1, eta+1))
1141 class Client(unittest.TestCase):
1142 def testAdaptation(self):
1143 b = builder.BuilderStatus("bname")
1144 b2 = client.makeRemote(b)
1145 self.failUnless(isinstance(b2, client.RemoteBuilder))
1146 b3 = client.makeRemote(None)
1147 self.failUnless(b3 is None)
1150 class ContactTester(unittest.TestCase):
1151 def test_notify_invalid_syntax(self):
1152 irc = MyContact()
1153 self.assertRaises(words.UsageError, lambda args, who: irc.command_NOTIFY(args, who), "", "mynick")
1155 def test_notify_list(self):
1156 irc = MyContact()
1157 irc.command_NOTIFY("list", "mynick")
1158 self.failUnlessEqual(irc.message, "The following events are being notified: []", "empty notify list")
1160 irc.message = ""
1161 irc.command_NOTIFY("on started", "mynick")
1162 self.failUnlessEqual(irc.message, "The following events are being notified: ['started']", "on started")
1164 irc.message = ""
1165 irc.command_NOTIFY("on finished", "mynick")
1166 self.failUnlessEqual(irc.message, "The following events are being notified: ['started', 'finished']", "on finished")
1168 irc.message = ""
1169 irc.command_NOTIFY("off", "mynick")
1170 self.failUnlessEqual(irc.message, "The following events are being notified: []", "off all")
1172 irc.message = ""
1173 irc.command_NOTIFY("on", "mynick")
1174 self.failUnlessEqual(irc.message, "The following events are being notified: ['started', 'finished']", "on default set")
1176 irc.message = ""
1177 irc.command_NOTIFY("off started", "mynick")
1178 self.failUnlessEqual(irc.message, "The following events are being notified: ['finished']", "off started")
1180 irc.message = ""
1181 irc.command_NOTIFY("on success failure exception", "mynick")
1182 self.failUnlessEqual(irc.message, "The following events are being notified: ['failure', 'finished', 'exception', 'success']", "on multiple events")
1184 def test_notification_default(self):
1185 irc = MyContact()
1187 my_builder = MyBuilder("builder78")
1188 my_build = MyIrcBuild(my_builder, 23, builder.SUCCESS)
1190 irc.buildStarted(my_builder.getName(), my_build)
1191 self.failUnlessEqual(irc.message, "", "No notification with default settings")
1193 irc.buildFinished(my_builder.getName(), my_build, None)
1194 self.failUnlessEqual(irc.message, "", "No notification with default settings")
1196 def test_notification_started(self):
1197 irc = MyContact()
1199 my_builder = MyBuilder("builder78")
1200 my_build = MyIrcBuild(my_builder, 23, builder.SUCCESS)
1201 my_build.changes = (
1202 Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 123),
1203 Change(who = 'author2', files = ['file2'], comments = 'comment2', revision = 456),
1206 irc.command_NOTIFY("on started", "mynick")
1208 irc.message = ""
1209 irc.buildStarted(my_builder.getName(), my_build)
1210 self.failUnlessEqual(irc.message, "build #23 of builder78 started including [123, 456]", "Start notification generated with notify_events=['started']")
1212 irc.message = ""
1213 irc.buildFinished(my_builder.getName(), my_build, None)
1214 self.failUnlessEqual(irc.message, "", "No finished notification with notify_events=['started']")
1216 def test_notification_finished(self):
1217 irc = MyContact()
1219 my_builder = MyBuilder("builder834")
1220 my_build = MyIrcBuild(my_builder, 862, builder.SUCCESS)
1221 my_build.changes = (
1222 Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
1225 irc.command_NOTIFY("on finished", "mynick")
1227 irc.message = ""
1228 irc.buildStarted(my_builder.getName(), my_build)
1229 self.failUnlessEqual(irc.message, "", "No started notification with notify_events=['finished']")
1231 irc.message = ""
1232 irc.buildFinished(my_builder.getName(), my_build, None)
1233 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']")
1235 def test_notification_success(self):
1236 irc = MyContact()
1238 my_builder = MyBuilder("builder834")
1239 my_build = MyIrcBuild(my_builder, 862, builder.SUCCESS)
1240 my_build.changes = (
1241 Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
1244 irc.command_NOTIFY("on success", "mynick")
1246 irc.message = ""
1247 irc.buildStarted(my_builder.getName(), my_build)
1248 self.failUnlessEqual(irc.message, "", "No started notification with notify_events=['success']")
1250 irc.message = ""
1251 irc.buildFinished(my_builder.getName(), my_build, None)
1252 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']")
1254 irc.message = ""
1255 my_build.results = builder.FAILURE
1256 irc.buildFinished(my_builder.getName(), my_build, None)
1257 self.failUnlessEqual(irc.message, "", "No finish notification generated on failure with notify_events=['success']")
1259 irc.message = ""
1260 my_build.results = builder.EXCEPTION
1261 irc.buildFinished(my_builder.getName(), my_build, None)
1262 self.failUnlessEqual(irc.message, "", "No finish notification generated on exception with notify_events=['success']")
1264 def test_notification_failed(self):
1265 irc = MyContact()
1267 my_builder = MyBuilder("builder834")
1268 my_build = MyIrcBuild(my_builder, 862, builder.FAILURE)
1269 my_build.changes = (
1270 Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
1273 irc.command_NOTIFY("on failure", "mynick")
1275 irc.message = ""
1276 irc.buildStarted(my_builder.getName(), my_build)
1277 self.failUnlessEqual(irc.message, "", "No started notification with notify_events=['failed']")
1279 irc.message = ""
1280 irc.buildFinished(my_builder.getName(), my_build, None)
1281 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']")
1283 irc.message = ""
1284 my_build.results = builder.SUCCESS
1285 irc.buildFinished(my_builder.getName(), my_build, None)
1286 self.failUnlessEqual(irc.message, "", "No finish notification generated on success with notify_events=['failed']")
1288 irc.message = ""
1289 my_build.results = builder.EXCEPTION
1290 irc.buildFinished(my_builder.getName(), my_build, None)
1291 self.failUnlessEqual(irc.message, "", "No finish notification generated on exception with notify_events=['failed']")
1293 def test_notification_exception(self):
1294 irc = MyContact()
1296 my_builder = MyBuilder("builder834")
1297 my_build = MyIrcBuild(my_builder, 862, builder.EXCEPTION)
1298 my_build.changes = (
1299 Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
1302 irc.command_NOTIFY("on exception", "mynick")
1304 irc.message = ""
1305 irc.buildStarted(my_builder.getName(), my_build)
1306 self.failUnlessEqual(irc.message, "", "No started notification with notify_events=['exception']")
1308 irc.message = ""
1309 irc.buildFinished(my_builder.getName(), my_build, None)
1310 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']")
1312 irc.message = ""
1313 my_build.results = builder.SUCCESS
1314 irc.buildFinished(my_builder.getName(), my_build, None)
1315 self.failUnlessEqual(irc.message, "", "No finish notification generated on success with notify_events=['exception']")
1317 irc.message = ""
1318 my_build.results = builder.FAILURE
1319 irc.buildFinished(my_builder.getName(), my_build, None)
1320 self.failUnlessEqual(irc.message, "", "No finish notification generated on exception with notify_events=['exception']")
1322 def do_x_to_y_notification_test(self, notify, previous_result, new_result, expected_msg):
1323 irc = MyContact()
1324 irc.command_NOTIFY("on %s" % notify, "mynick")
1326 my_builder = MyBuilder("builder834")
1327 my_build = MyIrcBuild(my_builder, 862, builder.FAILURE)
1328 my_build.changes = (
1329 Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
1332 previous_build = MyIrcBuild(my_builder, 861, previous_result)
1333 my_build.setPreviousBuild(previous_build)
1335 irc.message = ""
1336 my_build.results = new_result
1337 irc.buildFinished(my_builder.getName(), my_build, None)
1338 self.failUnlessEqual(irc.message, expected_msg, "Finish notification generated on failure with notify_events=['successToFailure']")
1340 def test_notification_successToFailure(self):
1341 self.do_x_to_y_notification_test(notify="successToFailure", previous_result=builder.SUCCESS, new_result=builder.FAILURE,
1342 expected_msg="build #862 of builder834 is complete: Failure [step1 step2] Build details are at http://myserver/mypath?build=765" )
1344 self.do_x_to_y_notification_test(notify="successToFailure", previous_result=builder.SUCCESS, new_result=builder.SUCCESS,
1345 expected_msg = "" )
1347 self.do_x_to_y_notification_test(notify="successToFailure", previous_result=builder.SUCCESS, new_result=builder.WARNINGS,
1348 expected_msg = "" )
1350 self.do_x_to_y_notification_test(notify="successToFailure", previous_result=builder.SUCCESS, new_result=builder.EXCEPTION,
1351 expected_msg = "" )
1353 def test_notification_successToWarnings(self):
1354 self.do_x_to_y_notification_test(notify="successToWarnings", previous_result=builder.SUCCESS, new_result=builder.WARNINGS,
1355 expected_msg="build #862 of builder834 is complete: Warnings [step1 step2] Build details are at http://myserver/mypath?build=765" )
1357 self.do_x_to_y_notification_test(notify="successToWarnings", previous_result=builder.SUCCESS, new_result=builder.SUCCESS,
1358 expected_msg = "" )
1360 self.do_x_to_y_notification_test(notify="successToWarnings", previous_result=builder.SUCCESS, new_result=builder.FAILURE,
1361 expected_msg = "" )
1363 self.do_x_to_y_notification_test(notify="successToWarnings", previous_result=builder.SUCCESS, new_result=builder.EXCEPTION,
1364 expected_msg = "" )
1366 def test_notification_successToException(self):
1367 self.do_x_to_y_notification_test(notify="successToException", previous_result=builder.SUCCESS, new_result=builder.EXCEPTION,
1368 expected_msg="build #862 of builder834 is complete: Exception [step1 step2] Build details are at http://myserver/mypath?build=765" )
1370 self.do_x_to_y_notification_test(notify="successToException", previous_result=builder.SUCCESS, new_result=builder.SUCCESS,
1371 expected_msg = "" )
1373 self.do_x_to_y_notification_test(notify="successToException", previous_result=builder.SUCCESS, new_result=builder.FAILURE,
1374 expected_msg = "" )
1376 self.do_x_to_y_notification_test(notify="successToException", previous_result=builder.SUCCESS, new_result=builder.WARNINGS,
1377 expected_msg = "" )
1383 def test_notification_failureToSuccess(self):
1384 self.do_x_to_y_notification_test(notify="failureToSuccess", previous_result=builder.FAILURE,new_result=builder.SUCCESS,
1385 expected_msg="build #862 of builder834 is complete: Success [step1 step2] Build details are at http://myserver/mypath?build=765" )
1387 self.do_x_to_y_notification_test(notify="failureToSuccess", previous_result=builder.FAILURE,new_result=builder.FAILURE,
1388 expected_msg = "" )
1390 self.do_x_to_y_notification_test(notify="failureToSuccess", previous_result=builder.FAILURE,new_result=builder.WARNINGS,
1391 expected_msg = "" )
1393 self.do_x_to_y_notification_test(notify="failureToSuccess", previous_result=builder.FAILURE,new_result=builder.EXCEPTION,
1394 expected_msg = "" )
1396 def test_notification_failureToWarnings(self):
1397 self.do_x_to_y_notification_test(notify="failureToWarnings", previous_result=builder.FAILURE, new_result=builder.WARNINGS,
1398 expected_msg="build #862 of builder834 is complete: Warnings [step1 step2] Build details are at http://myserver/mypath?build=765" )
1400 self.do_x_to_y_notification_test(notify="failureToWarnings", previous_result=builder.FAILURE, new_result=builder.SUCCESS,
1401 expected_msg = "" )
1403 self.do_x_to_y_notification_test(notify="failureToWarnings", previous_result=builder.FAILURE, new_result=builder.FAILURE,
1404 expected_msg = "" )
1406 self.do_x_to_y_notification_test(notify="failureToWarnings", previous_result=builder.FAILURE, new_result=builder.EXCEPTION,
1407 expected_msg = "" )
1409 def test_notification_failureToException(self):
1410 self.do_x_to_y_notification_test(notify="failureToException", previous_result=builder.FAILURE, new_result=builder.EXCEPTION,
1411 expected_msg="build #862 of builder834 is complete: Exception [step1 step2] Build details are at http://myserver/mypath?build=765" )
1413 self.do_x_to_y_notification_test(notify="failureToException", previous_result=builder.FAILURE, new_result=builder.SUCCESS,
1414 expected_msg = "" )
1416 self.do_x_to_y_notification_test(notify="failureToException", previous_result=builder.FAILURE, new_result=builder.FAILURE,
1417 expected_msg = "" )
1419 self.do_x_to_y_notification_test(notify="failureToException", previous_result=builder.FAILURE, new_result=builder.WARNINGS,
1420 expected_msg = "" )
1426 def test_notification_warningsToFailure(self):
1427 self.do_x_to_y_notification_test(notify="warningsToFailure", previous_result=builder.WARNINGS, new_result=builder.FAILURE,
1428 expected_msg="build #862 of builder834 is complete: Failure [step1 step2] Build details are at http://myserver/mypath?build=765" )
1430 self.do_x_to_y_notification_test(notify="warningsToFailure", previous_result=builder.WARNINGS, new_result=builder.SUCCESS,
1431 expected_msg = "" )
1433 self.do_x_to_y_notification_test(notify="warningsToFailure", previous_result=builder.WARNINGS, new_result=builder.WARNINGS,
1434 expected_msg = "" )
1436 self.do_x_to_y_notification_test(notify="warningsToFailure", previous_result=builder.WARNINGS, new_result=builder.EXCEPTION,
1437 expected_msg = "" )
1439 def test_notification_warningsToSuccess(self):
1440 self.do_x_to_y_notification_test(notify="warningsToSuccess", previous_result=builder.WARNINGS, new_result=builder.SUCCESS,
1441 expected_msg="build #862 of builder834 is complete: Success [step1 step2] Build details are at http://myserver/mypath?build=765" )
1443 self.do_x_to_y_notification_test(notify="warningsToSuccess", previous_result=builder.WARNINGS, new_result=builder.WARNINGS,
1444 expected_msg = "" )
1446 self.do_x_to_y_notification_test(notify="warningsToSuccess", previous_result=builder.WARNINGS, new_result=builder.FAILURE,
1447 expected_msg = "" )
1449 self.do_x_to_y_notification_test(notify="warningsToSuccess", previous_result=builder.WARNINGS, new_result=builder.EXCEPTION,
1450 expected_msg = "" )
1452 def test_notification_warningsToException(self):
1453 self.do_x_to_y_notification_test(notify="warningsToException", previous_result=builder.WARNINGS, new_result=builder.EXCEPTION,
1454 expected_msg="build #862 of builder834 is complete: Exception [step1 step2] Build details are at http://myserver/mypath?build=765" )
1456 self.do_x_to_y_notification_test(notify="warningsToException", previous_result=builder.WARNINGS, new_result=builder.SUCCESS,
1457 expected_msg = "" )
1459 self.do_x_to_y_notification_test(notify="warningsToException", previous_result=builder.WARNINGS, new_result=builder.FAILURE,
1460 expected_msg = "" )
1462 self.do_x_to_y_notification_test(notify="warningsToException", previous_result=builder.WARNINGS, new_result=builder.WARNINGS,
1463 expected_msg = "" )
1468 def test_notification_exceptionToFailure(self):
1469 self.do_x_to_y_notification_test(notify="exceptionToFailure", previous_result=builder.EXCEPTION, new_result=builder.FAILURE,
1470 expected_msg="build #862 of builder834 is complete: Failure [step1 step2] Build details are at http://myserver/mypath?build=765" )
1472 self.do_x_to_y_notification_test(notify="exceptionToFailure", previous_result=builder.EXCEPTION, new_result=builder.SUCCESS,
1473 expected_msg = "" )
1475 self.do_x_to_y_notification_test(notify="exceptionToFailure", previous_result=builder.EXCEPTION, new_result=builder.WARNINGS,
1476 expected_msg = "" )
1478 self.do_x_to_y_notification_test(notify="exceptionToFailure", previous_result=builder.EXCEPTION, new_result=builder.EXCEPTION,
1479 expected_msg = "" )
1481 def test_notification_exceptionToWarnings(self):
1482 self.do_x_to_y_notification_test(notify="exceptionToWarnings", previous_result=builder.EXCEPTION, new_result=builder.WARNINGS,
1483 expected_msg="build #862 of builder834 is complete: Warnings [step1 step2] Build details are at http://myserver/mypath?build=765" )
1485 self.do_x_to_y_notification_test(notify="exceptionToWarnings", previous_result=builder.EXCEPTION, new_result=builder.SUCCESS,
1486 expected_msg = "" )
1488 self.do_x_to_y_notification_test(notify="exceptionToWarnings", previous_result=builder.EXCEPTION, new_result=builder.FAILURE,
1489 expected_msg = "" )
1491 self.do_x_to_y_notification_test(notify="exceptionToWarnings", previous_result=builder.EXCEPTION, new_result=builder.EXCEPTION,
1492 expected_msg = "" )
1494 def test_notification_exceptionToSuccess(self):
1495 self.do_x_to_y_notification_test(notify="exceptionToSuccess", previous_result=builder.EXCEPTION, new_result=builder.SUCCESS,
1496 expected_msg="build #862 of builder834 is complete: Success [step1 step2] Build details are at http://myserver/mypath?build=765" )
1498 self.do_x_to_y_notification_test(notify="exceptionToSuccess", previous_result=builder.EXCEPTION, new_result=builder.EXCEPTION,
1499 expected_msg = "" )
1501 self.do_x_to_y_notification_test(notify="exceptionToSuccess", previous_result=builder.EXCEPTION, new_result=builder.FAILURE,
1502 expected_msg = "" )
1504 self.do_x_to_y_notification_test(notify="exceptionToSuccess", previous_result=builder.EXCEPTION, new_result=builder.WARNINGS,
1505 expected_msg = "" )
1507 def test_notification_set_in_config(self):
1508 irc = MyContact(channel = MyChannel(notify_events = {'success': 1}))
1510 my_builder = MyBuilder("builder834")
1511 my_build = MyIrcBuild(my_builder, 862, builder.SUCCESS)
1512 my_build.changes = (
1513 Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
1516 irc.message = ""
1517 irc.buildFinished(my_builder.getName(), my_build, None)
1518 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']")
1520 class MyIrcBuild(builder.BuildStatus):
1521 results = None
1523 def __init__(self, parent, number, results):
1524 builder.BuildStatus.__init__(self, parent, number)
1525 self.results = results
1526 self.previousBuild = None
1528 def getResults(self):
1529 return self.results
1531 def getText(self):
1532 return ('step1', 'step2')
1534 def setPreviousBuild(self, pb):
1535 self.previousBuild = pb
1537 def getPreviousBuild(self):
1538 return self.previousBuild
1540 class URLProducer:
1541 def getURLForThing(self, build):
1542 return 'http://myserver/mypath?build=765'
1544 class MyChannel:
1545 categories = None
1546 status = URLProducer()
1547 notify_events = {}
1549 def __init__(self, notify_events = {}):
1550 self.notify_events = notify_events
1552 class MyContact(words.Contact):
1553 message = ""
1555 def __init__(self, channel = MyChannel()):
1556 words.Contact.__init__(self, channel)
1557 self.message = ""
1559 def subscribe_to_build_events(self):
1560 pass
1562 def unsubscribe_from_build_events(self):
1563 pass
1565 def send(self, msg):
1566 self.message += msg
1568 class StepStatistics(unittest.TestCase):
1569 def testStepStatistics(self):
1570 status = builder.BuildStatus(builder.BuilderStatus("test"), 123)
1571 status.addStepWithName('step1')
1572 status.addStepWithName('step2')
1573 status.addStepWithName('step3')
1574 status.addStepWithName('step4')
1576 steps = status.getSteps()
1577 (step1, step2, step3, step4) = steps
1579 step1.setStatistic('test-prop', 1)
1580 step3.setStatistic('test-prop', 2)
1581 step4.setStatistic('test-prop', 4)
1583 step1.setStatistic('other-prop', 27)
1584 # Just to have some other properties around
1586 self.failUnlessEqual(step1.getStatistic('test-prop'), 1,
1587 'Retrieve an existing property')
1588 self.failUnlessEqual(step1.getStatistic('test-prop', 99), 1,
1589 "Don't default an existing property")
1590 self.failUnlessEqual(step2.getStatistic('test-prop', 99), 99,
1591 'Default a non-existant property')
1593 self.failUnlessEqual(
1594 status.getSummaryStatistic('test-prop', operator.add), 7,
1595 'Sum property across the build')
1597 self.failUnlessEqual(
1598 status.getSummaryStatistic('test-prop', operator.add, 13), 20,
1599 'Sum property across the build with initial value')
1601 class BuildExpectation(unittest.TestCase):
1602 class MyBuilderStatus:
1603 implements(interfaces.IBuilderStatus)
1605 def setSlavenames(self, slaveName):
1606 pass
1608 class MyBuilder(Builder):
1609 def __init__(self, name):
1610 Builder.__init__(self, {
1611 'name': name,
1612 'builddir': '/tmp/somewhere',
1613 'factory': 'aFactory'
1614 }, BuildExpectation.MyBuilderStatus())
1616 class MyBuild(Build):
1617 def __init__(self, b):
1618 self.builder = b
1619 self.remote = None
1621 step1_progress = progress.StepProgress('step1', ['elapsed'])
1622 self.progress = progress.BuildProgress([step1_progress])
1623 step1_progress.setBuildProgress(self.progress)
1625 step1_progress.start()
1626 sleep(1);
1627 step1_progress.finish()
1629 self.deferred = defer.Deferred()
1630 self.locks = []
1631 self.build_status = builder.BuildStatus(b.builder_status, 1)
1634 def testBuildExpectation_BuildSuccess(self):
1635 b = BuildExpectation.MyBuilder("builder1")
1636 build = BuildExpectation.MyBuild(b)
1638 build.buildFinished(['sometext'], builder.SUCCESS)
1639 self.failIfEqual(b.expectations.expectedBuildTime(), 0, 'Non-Zero expectation for a failed build')
1641 def testBuildExpectation_BuildFailure(self):
1642 b = BuildExpectation.MyBuilder("builder1")
1643 build = BuildExpectation.MyBuild(b)
1645 build.buildFinished(['sometext'], builder.FAILURE)
1646 self.failUnlessEqual(b.expectations, None, 'Zero expectation for a failed build')
1648 class Pruning(unittest.TestCase):
1649 def runTest(self, files, buildHorizon, logHorizon):
1650 bstat = builder.BuilderStatus("foo")
1651 bstat.buildHorizon = buildHorizon
1652 bstat.logHorizon = logHorizon
1653 bstat.basedir = "prune-test"
1655 rmtree(bstat.basedir)
1656 os.mkdir(bstat.basedir)
1657 for filename in files:
1658 open(os.path.join(bstat.basedir, filename), "w").write("TEST")
1659 bstat.determineNextBuildNumber()
1661 bstat.prune()
1663 remaining = os.listdir(bstat.basedir)
1664 remaining.sort()
1665 return remaining
1667 files_base = [
1668 '10',
1669 '11',
1670 '12', '12-log-bar', '12-log-foo',
1671 '13', '13-log-foo',
1672 '14', '14-log-bar', '14-log-foo',
1675 def test_rmlogs(self):
1676 remaining = self.runTest(self.files_base, 5, 2)
1677 self.failUnlessEqual(remaining, [
1678 '10',
1679 '11',
1680 '12',
1681 '13', '13-log-foo',
1682 '14', '14-log-bar', '14-log-foo',
1685 def test_rmbuilds(self):
1686 remaining = self.runTest(self.files_base, 2, 0)
1687 self.failUnlessEqual(remaining, [
1688 '13', '13-log-foo',
1689 '14', '14-log-bar', '14-log-foo',