1 # -*- test-case-name: buildbot.test.test_status -*-
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
18 from buildbot
.status
import mail
21 from buildbot
.status
import progress
, client
# NEEDS COVERAGE
22 from buildbot
.test
.runutils
import RunMixin
, setupBuildStepStatus
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
):
35 reactor
.callLater(0, self
._resumeProducing
, 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
46 builder
.LogFile
.__init
__(self
, step
, name
, name
)
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()
58 class MyHTMLLog(builder
.HTMLLogFile
):
59 def __init__(self
, basedir
, name
, html
):
61 builder
.HTMLLogFile
.__init
__(self
, step
, name
, name
, html
)
63 class MyLogSubscriber
:
66 def logChunk(self
, build
, step
, log
, channel
, text
):
67 self
.chunks
.append((channel
, text
))
70 def __init__(self
, limit
=None):
74 def registerProducer(self
, producer
, streaming
):
75 self
.producer
= producer
76 self
.streaming
= streaming
77 def unregisterProducer(self
):
79 def writeChunk(self
, chunk
):
80 self
.chunks
.append(chunk
)
84 self
.producer
.pauseProducing()
89 class MyMailer(mail
.MailNotifier
):
90 def sendMessage(self
, m
, recipients
):
91 self
.parent
.messages
.append((m
, recipients
))
94 def getBuildbotURL(self
):
96 def getURLForThing(self
, thing
):
98 def getProjectName(self
):
101 class MyBuilder(builder
.BuilderStatus
):
104 class MyBuild(builder
.BuildStatus
):
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"
116 implements(interfaces
.IEmailLookup
)
118 def getAddress(self
, user
):
120 # With me now is Mr Thomas Walters of West Hartlepool who is totally
122 if user
== "Thomas_Walters":
125 d
.callback(user
+ "@" + "dev.com")
128 def customTextMailMessage(attrs
):
131 text
.append("STATUS: %s" % attrs
['result'].title())
133 text
.extend([c
.asText() for c
in attrs
['changes']])
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
:]])
139 text
.append("-buildbot")
140 return ("\n".join(text
), 'plain')
142 def customHTMLMailMessage(attrs
):
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
))
152 text
.append("<br>".join([line
for line
in lines
[len(lines
)-logLines
:]]))
155 text
.append("<b>-<a href='%s'>buildbot</a></b>" % attrs
['buildbotURL'])
156 return ("\n".join(text
), 'html')
158 class Mail(unittest
.TestCase
):
161 self
.builder
= MyBuilder("builder1")
163 def stall(self
, res
, timeout
):
165 reactor
.callLater(timeout
, d
.callback
, res
)
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
):
178 def getBuildbotURL(self
):
179 return "BUILDBOT_URL"
181 def getURLForThing(self
, thing
):
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"))
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()
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"],
213 sendToInterestedUsers
=False)
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()
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
235 mailer
= MyMailer(fromaddr
="buildbot@example.com",
236 extraRecipients
=["recip@example.com",
237 "recip2@example.com"],
239 sendToInterestedUsers
=False,
240 categories
=["debug"])
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"],
259 sendToInterestedUsers
=False)
260 mailer2
= MyMailer(fromaddr
="buildbot@example.com",
261 extraRecipients
=["recip@example.com",
262 "recip2@example.com"],
264 sendToInterestedUsers
=False,
265 categories
=["active"])
266 mailer3
= MyMailer(fromaddr
="buildbot@example.com",
267 extraRecipients
=["recip@example.com",
268 "recip2@example.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
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)
299 mailer2
.buildFinished("builder2", b2
, b2
.results
)
300 self
.failUnlessEqual(len(self
.messages
), 0)
302 mailer3
.buildFinished("builder2", b2
, b2
.results
)
303 self
.failUnlessEqual(len(self
.messages
), 1)
305 def testCustomTextMessage(self
):
306 basedir
= "test_custom_text_mesg"
308 mailer
= MyMailer(fromaddr
="buildbot@example.com", mode
="problem",
309 extraRecipients
=["recip@example.com",
310 "recip2@example.com"],
312 customMesg
=customTextMailMessage
)
317 b1
= self
.makeBuild(4, builder
.FAILURE
)
318 b1
.setText(["snarkleack", "polarization", "failed"])
319 b1
.blamelist
= ["dev3", "dev3", "dev3", "dev4",
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()
330 # Uncomment to review custom message
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"
341 mailer
= MyMailer(fromaddr
="buildbot@example.com", mode
="problem",
342 extraRecipients
=["recip@example.com",
343 "recip2@example.com"],
345 customMesg
=customHTMLMailMessage
)
350 b1
= self
.makeBuild(4, builder
.FAILURE
)
351 b1
.setText(["snarkleack", "polarization", "failed"])
352 b1
.blamelist
= ["dev3", "dev3", "dev3", "dev4",
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()
363 # Uncomment to review custom message
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"],
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",
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()
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"]))
401 basedir
= "test_status_logs"
403 mailer
= MyMailer(fromaddr
="buildbot@example.com", addLogs
=True,
404 extraRecipients
=["recip@example.com",
405 "recip2@example.com"])
410 b1
= self
.makeBuild(3, builder
.WARNINGS
)
411 b1
.testlogs
= [MyLog(basedir
, 'compile', "Compile log here\n"),
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()
420 self
.failUnlessIn("Subject: buildbot warnings in PROJECT on builder1\n", t
)
421 m2
= email
.message_from_string(t
)
423 self
.failUnlessEqual(len(p
), 3)
425 self
.failUnlessIn("Build Had Warnings: unusual gnarzzler output\n",
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())
435 basedir
= "test_status_mail"
437 dest
= os
.environ
.get("BUILDBOT_TEST_MAIL")
439 raise unittest
.SkipTest("define BUILDBOT_TEST_MAIL=dest to run this")
440 mailer
= mail
.MailNotifier(fromaddr
="buildbot@example.com",
442 extraRecipients
=[dest
])
444 s
.url
= "project URL"
447 b1
= self
.makeBuild(3, builder
.SUCCESS
)
448 b1
.testlogs
= [MyLog(basedir
, 'compile', "Compile log here\n"),
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
461 d
.addCallback(self
.stall
, 0.1)
465 Mail
.skip
= "the Twisted Mail package is not installed"
467 class Progress(unittest
.TestCase
):
469 bp
= progress
.BuildProgress([])
470 e
= progress
.Expectations(bp
)
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)
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",
486 r1
= builder
.TestResult(name
=testname
,
487 results
=builder
.SUCCESS
,
493 res
= b
.getTestResults()
494 self
.failUnlessEqual(res
.keys(), [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
)
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())
517 self
.failUnless(l
.isFinished())
518 self
.failUnlessEqual(l
.getText(),
519 "Some text\nSome error\nSome more text\n")
520 self
.failUnlessEqual(l
.getTextWithHeaders(),
522 "Some text\nSome error\nSome more text\n")
523 self
.failUnlessEqual(len(list(l
.getChunks())), 4)
525 self
.failUnless(l
.hasContents())
527 os
.unlink(l
.getFilename())
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")
542 l2
= MyLog(self
.basedir
, "duplicate")
543 l2
.addStdout("Some more text\n")
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")
554 self
.failUnlessEqual(l
.getText(),
555 "Some text\nSome more text\nmore\n")
556 self
.failUnlessEqual(l
.getTextWithHeaders(),
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):
570 target
= 1000*"aaaa" + 30 * "bbbb" + 10 * "cc"
571 self
.failUnlessEqual(len(l
.getText()), len(target
))
572 self
.failUnlessEqual(l
.getText(), target
)
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")
581 l
.addHeader("HEADER\n")
586 self
.failUnlessEqual(list(l
.getChunks()),
587 [(builder
.HEADER
, "HEADER\n"),
588 (builder
.STDOUT
, 100*"a"),
589 (builder
.STDOUT
, 60*"a")])
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")
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
613 self
.failUnlessEqual(line0
, "Some text\n")
616 self
.failUnlessEqual(line2
, "And Some More\n")
619 def testChunks(self
):
620 l
= MyLog(self
.basedir
, "chunks2")
622 l
.addHeader("HEADER\n")
623 l
.addStdout("Some text\n")
624 self
.failUnlessEqual("".join(l
.getChunks(onlyText
=True)),
625 "HEADER\nSome text\n")
628 l
.addStdout("Some more text\n")
629 self
.failUnlessEqual("".join(l
.getChunks(onlyText
=True)),
630 "HEADER\nSome text\nSome more text\n")
633 l
.addStdout("more\n")
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"),
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(),
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")
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())
662 os
.unlink(l
.getFilename() + ".bz2")
664 os
.unlink(l
.getFilename())
665 # now make sure we can upgrade it
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())
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")
690 self
.failUnless(l1
.isFinished())
692 s
= MyLogSubscriber()
693 l1
.subscribe(s
, True)
695 self
.failIf(s
.chunks
)
697 s
= MyLogSubscriber()
698 l1
.subscribe(s
, False)
700 self
.failIf(s
.chunks
)
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")])
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
)
727 self
.failUnlessEqual(finished
, [l2
])
729 def testConsumer(self
):
730 l1
= MyLog(self
.basedir
, "consumer1")
732 self
.failUnless(l1
.isFinished())
735 d
= l1
.subscribeConsumer(s
)
736 d
.addCallback(self
._testConsumer
_1, s
)
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")
747 self
.failUnless(l2
.isFinished())
750 d
= l2
.subscribeConsumer(s
)
751 d
.addCallback(self
._testConsumer
_2, s
)
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")
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,
769 s
= MyLogConsumer(limit
=1)
770 d
= l2
.subscribeConsumer(s
)
771 d
.addCallback(self
._testConsumer
_3, l2
, s
)
773 def _testConsumer_3(self
, res
, l2
, s
):
774 self
.failUnless(s
.streaming
)
775 self
.failUnlessEqual(s
.chunks
, [(builder
.HEADER
, "HEADER\n")])
777 d
= s
.producer
.resumeProducing()
778 d
.addCallback(self
._testConsumer
_4, l2
, s
)
780 def _testConsumer_4(self
, res
, l2
, s
):
781 self
.failUnlessEqual(s
.chunks
, [(builder
.HEADER
, "HEADER\n"),
782 (builder
.STDOUT
, 1000*"a"),
785 d
= s
.producer
.resumeProducing()
786 d
.addCallback(self
._testConsumer
_5, l2
, s
)
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")])
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
)
818 d
= l
.subscribeConsumer(s
)
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
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)
841 lc
= bss
.addLog('to-compress')
842 lc
.addStdout('b' * 1024)
844 d
= bss
.stepFinished(builder
.SUCCESS
)
845 self
.failUnless(d
is not None)
846 d
.addCallback(self
._verifyCompression
, bss
)
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)
863 from buildbot.process import factory
864 from buildbot.steps import dummy
865 from buildbot.buildslave import BuildSlave
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')]
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
):
894 def __init__(self
, mode
):
899 print self
.events
[-1]
901 def builderAdded(self
, name
, builder
):
902 self
.events
.append(("builderAdded", name
, builder
))
904 if "builder" in self
.mode
:
906 def builderChangedState(self
, name
, state
):
907 self
.events
.append(("builderChangedState", name
, state
))
909 def buildStarted(self
, name
, build
):
910 self
.events
.append(("buildStarted", name
, build
))
912 if "eta" in self
.mode
:
913 self
.eta_build
= build
.getETA()
914 if "build" in self
.mode
:
916 def buildETAUpdate(self
, build
, ETA
):
917 self
.events
.append(("buildETAUpdate", build
, ETA
))
919 def stepStarted(self
, build
, step
):
920 self
.events
.append(("stepStarted", build
, step
))
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
:
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
))
935 def logStarted(self
, build
, step
, log
):
936 self
.events
.append(("logStarted", build
, step
, log
))
938 def logFinished(self
, build
, step
, log
):
939 self
.events
.append(("logFinished", build
, step
, log
))
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()
946 def buildFinished(self
, name
, build
, results
):
947 self
.events
.append(("buildFinished", name
, build
, results
))
949 def builderRemoved(self
, name
):
950 self
.events
.append(("builderRemoved", name
))
953 class Subscription(RunMixin
, unittest
.TestCase
):
954 # verify that StatusTargets can subscribe/unsubscribe properly
959 self
.t1
= t1
= STarget(["builder"])
960 #t1.debug = True; print
962 self
.failUnlessEqual(len(t1
.events
), 0)
964 self
.t3
= t3
= STarget(["builder", "build", "step"])
967 m
.loadConfig(config_2
)
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"))
980 self
.failUnlessEqual(s
.getBuilderNames(), ["dummy", "testdummy"])
981 self
.failUnlessEqual(s
.getBuilderNames(categories
=['test']),
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([])
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
)
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"))
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)
1020 def _testSlave_2(self
, res
):
1021 # t1 subscribes to builds, but not anything lower-level
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
],
1034 "builderChangedState", # offline
1036 "builderChangedState", # idle
1037 "builderChangedState", # offline
1038 "builderChangedState", # idle
1039 "builderChangedState", # building
1041 "stepStarted", "stepETAUpdate",
1042 "stepTextChanged", "stepFinished",
1043 "stepStarted", "stepETAUpdate",
1044 "stepTextChanged", "logStarted", "logFinished",
1045 "stepTextChanged", "stepText2Changed",
1048 "builderChangedState", # idle
1051 b
= self
.s1
.getLastFinishedBuild()
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)
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(), [])
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)
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")
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)
1108 def _testSlave_3(self
, res
):
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
):
1128 self
.assertRaises(words
.UsageError
, lambda args
, who
: irc
.command_NOTIFY(args
, who
), "", "mynick")
1130 def test_notify_list(self
):
1132 irc
.command_NOTIFY("list", "mynick")
1133 self
.failUnlessEqual(irc
.message
, "The following events are being notified: []", "empty notify list")
1136 irc
.command_NOTIFY("on started", "mynick")
1137 self
.failUnlessEqual(irc
.message
, "The following events are being notified: ['started']", "on started")
1140 irc
.command_NOTIFY("on finished", "mynick")
1141 self
.failUnlessEqual(irc
.message
, "The following events are being notified: ['started', 'finished']", "on finished")
1144 irc
.command_NOTIFY("off", "mynick")
1145 self
.failUnlessEqual(irc
.message
, "The following events are being notified: []", "off all")
1148 irc
.command_NOTIFY("on", "mynick")
1149 self
.failUnlessEqual(irc
.message
, "The following events are being notified: ['started', 'finished']", "on default set")
1152 irc
.command_NOTIFY("off started", "mynick")
1153 self
.failUnlessEqual(irc
.message
, "The following events are being notified: ['finished']", "off started")
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
):
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
):
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")
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']")
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
):
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")
1203 irc
.buildStarted(my_builder
.getName(), my_build
)
1204 self
.failUnlessEqual(irc
.message
, "", "No started notification with notify_events=['finished']")
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
):
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")
1222 irc
.buildStarted(my_builder
.getName(), my_build
)
1223 self
.failUnlessEqual(irc
.message
, "", "No started notification with notify_events=['success']")
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']")
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']")
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
):
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")
1251 irc
.buildStarted(my_builder
.getName(), my_build
)
1252 self
.failUnlessEqual(irc
.message
, "", "No started notification with notify_events=['failed']")
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']")
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']")
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
):
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")
1280 irc
.buildStarted(my_builder
.getName(), my_build
)
1281 self
.failUnlessEqual(irc
.message
, "", "No started notification with notify_events=['exception']")
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']")
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']")
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
):
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")
1311 irc
.buildStarted(my_builder
.getName(), my_build
)
1312 self
.failUnlessEqual(irc
.message
, "", "No started notification with notify_events=['failed']")
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']")
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']")
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
):
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")
1342 irc
.buildStarted(my_builder
.getName(), my_build
)
1343 self
.failUnlessEqual(irc
.message
, "", "No started notification with notify_events=['success']")
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']")
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']")
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
):
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
):
1371 return ('step1', 'step2')
1373 def setPreviousBuild(self
, pb
):
1374 self
.previousBuild
= pb
1376 def getPreviousBuild(self
):
1377 return self
.previousBuild
1380 def getURLForThing(self
, build
):
1381 return 'http://myserver/mypath?build=765'
1385 status
= URLProducer()
1390 class MyContact(words
.Contact
):
1393 def __init__(self
, channel
= MyChannel()):
1394 words
.Contact
.__init
__(self
, channel
)
1397 def subscribe_to_build_events(self
):
1400 def unsubscribe_from_build_events(self
):
1403 def send(self
, 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')