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
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
):
99 class MyBuilder(builder
.BuilderStatus
):
102 class MyBuild(builder
.BuildStatus
):
104 def __init__(self
, parent
, number
, results
):
105 builder
.BuildStatus
.__init
__(self
, parent
, number
)
106 self
.results
= results
107 self
.source
= SourceStamp(revision
="1.14")
108 self
.reason
= "build triggered by changes"
114 implements(interfaces
.IEmailLookup
)
116 def getAddress(self
, user
):
118 # With me now is Mr Thomas Walters of West Hartlepool who is totally
120 if user
== "Thomas_Walters":
123 d
.callback(user
+ "@" + "dev.com")
126 class Mail(unittest
.TestCase
):
129 self
.builder
= MyBuilder("builder1")
131 def stall(self
, res
, timeout
):
133 reactor
.callLater(timeout
, d
.callback
, res
)
136 def makeBuild(self
, number
, results
):
137 return MyBuild(self
.builder
, number
, results
)
139 def failUnlessIn(self
, substring
, string
):
140 self
.failUnless(string
.find(substring
) != -1,
141 "didn't see '%s' in '%s'" % (substring
, string
))
143 def getProjectName(self
):
146 def getBuildbotURL(self
):
147 return "BUILDBOT_URL"
149 def getURLForThing(self
, thing
):
152 def testBuild1(self
):
153 mailer
= MyMailer(fromaddr
="buildbot@example.com",
154 extraRecipients
=["recip@example.com",
155 "recip2@example.com"],
156 lookup
=mail
.Domain("dev.com"))
161 b1
= self
.makeBuild(3, builder
.SUCCESS
)
162 b1
.blamelist
= ["bob"]
164 mailer
.buildFinished("builder1", b1
, b1
.results
)
165 self
.failUnless(len(self
.messages
) == 1)
166 m
,r
= self
.messages
.pop()
168 self
.failUnlessIn("To: bob@dev.com, recip2@example.com, "
169 "recip@example.com\n", t
)
170 self
.failUnlessIn("From: buildbot@example.com\n", t
)
171 self
.failUnlessIn("Subject: buildbot success in PROJECT on builder1\n", t
)
172 self
.failUnlessIn("Date: ", t
)
173 self
.failUnlessIn("Build succeeded!\n", t
)
174 self
.failUnlessIn("Buildbot URL: BUILDBOT_URL\n", t
)
176 def testBuild2(self
):
177 mailer
= MyMailer(fromaddr
="buildbot@example.com",
178 extraRecipients
=["recip@example.com",
179 "recip2@example.com"],
181 sendToInterestedUsers
=False)
186 b1
= self
.makeBuild(3, builder
.SUCCESS
)
187 b1
.blamelist
= ["bob"]
189 mailer
.buildFinished("builder1", b1
, b1
.results
)
190 self
.failUnless(len(self
.messages
) == 1)
191 m
,r
= self
.messages
.pop()
193 self
.failUnlessIn("To: recip2@example.com, "
194 "recip@example.com\n", t
)
195 self
.failUnlessIn("From: buildbot@example.com\n", t
)
196 self
.failUnlessIn("Subject: buildbot success in PROJECT on builder1\n", t
)
197 self
.failUnlessIn("Build succeeded!\n", t
)
198 self
.failUnlessIn("Buildbot URL: BUILDBOT_URL\n", t
)
200 def testBuildStatusCategory(self
):
201 # a status client only interested in a category should only receive
203 mailer
= MyMailer(fromaddr
="buildbot@example.com",
204 extraRecipients
=["recip@example.com",
205 "recip2@example.com"],
207 sendToInterestedUsers
=False,
208 categories
=["debug"])
214 b1
= self
.makeBuild(3, builder
.SUCCESS
)
215 b1
.blamelist
= ["bob"]
217 mailer
.buildFinished("builder1", b1
, b1
.results
)
218 self
.failIf(self
.messages
)
220 def testBuilderCategory(self
):
221 # a builder in a certain category should notify status clients that
222 # did not list categories, or categories including this one
223 mailer1
= MyMailer(fromaddr
="buildbot@example.com",
224 extraRecipients
=["recip@example.com",
225 "recip2@example.com"],
227 sendToInterestedUsers
=False)
228 mailer2
= MyMailer(fromaddr
="buildbot@example.com",
229 extraRecipients
=["recip@example.com",
230 "recip2@example.com"],
232 sendToInterestedUsers
=False,
233 categories
=["active"])
234 mailer3
= MyMailer(fromaddr
="buildbot@example.com",
235 extraRecipients
=["recip@example.com",
236 "recip2@example.com"],
238 sendToInterestedUsers
=False,
239 categories
=["active", "debug"])
241 builderd
= MyBuilder("builder2", "debug")
243 mailer1
.parent
= self
244 mailer1
.status
= self
245 mailer2
.parent
= self
246 mailer2
.status
= self
247 mailer3
.parent
= self
248 mailer3
.status
= self
251 t
= mailer1
.builderAdded("builder2", builderd
)
252 self
.assertEqual(len(mailer1
.watched
), 1)
253 self
.assertEqual(t
, mailer1
)
254 t
= mailer2
.builderAdded("builder2", builderd
)
255 self
.assertEqual(len(mailer2
.watched
), 0)
256 self
.assertEqual(t
, None)
257 t
= mailer3
.builderAdded("builder2", builderd
)
258 self
.assertEqual(len(mailer3
.watched
), 1)
259 self
.assertEqual(t
, mailer3
)
261 b2
= MyBuild(builderd
, 3, builder
.SUCCESS
)
262 b2
.blamelist
= ["bob"]
264 mailer1
.buildFinished("builder2", b2
, b2
.results
)
265 self
.failUnlessEqual(len(self
.messages
), 1)
267 mailer2
.buildFinished("builder2", b2
, b2
.results
)
268 self
.failUnlessEqual(len(self
.messages
), 0)
270 mailer3
.buildFinished("builder2", b2
, b2
.results
)
271 self
.failUnlessEqual(len(self
.messages
), 1)
273 def testFailure(self
):
274 mailer
= MyMailer(fromaddr
="buildbot@example.com", mode
="problem",
275 extraRecipients
=["recip@example.com",
276 "recip2@example.com"],
282 b1
= self
.makeBuild(3, builder
.SUCCESS
)
283 b1
.blamelist
= ["dev1", "dev2"]
284 b2
= self
.makeBuild(4, builder
.FAILURE
)
285 b2
.setText(["snarkleack", "polarization", "failed"])
286 b2
.blamelist
= ["dev3", "dev3", "dev3", "dev4",
288 mailer
.buildFinished("builder1", b1
, b1
.results
)
289 self
.failIf(self
.messages
)
290 mailer
.buildFinished("builder1", b2
, b2
.results
)
291 self
.failUnless(len(self
.messages
) == 1)
292 m
,r
= self
.messages
.pop()
294 self
.failUnlessIn("To: dev3@dev.com, dev4@dev.com, "
295 "recip2@example.com, recip@example.com\n", t
)
296 self
.failUnlessIn("From: buildbot@example.com\n", t
)
297 self
.failUnlessIn("Subject: buildbot failure in PROJECT on builder1\n", t
)
298 self
.failUnlessIn("The Buildbot has detected a new failure", t
)
299 self
.failUnlessIn("BUILD FAILED: snarkleack polarization failed\n", t
)
300 self
.failUnlessEqual(r
, ["dev3@dev.com", "dev4@dev.com",
301 "recip2@example.com", "recip@example.com"])
304 basedir
= "test_status_logs"
306 mailer
= MyMailer(fromaddr
="buildbot@example.com", addLogs
=True,
307 extraRecipients
=["recip@example.com",
308 "recip2@example.com"])
313 b1
= self
.makeBuild(3, builder
.WARNINGS
)
314 b1
.testlogs
= [MyLog(basedir
, 'compile', "Compile log here\n"),
316 'test', "Test log here\nTest 4 failed\n"),
318 b1
.text
= ["unusual", "gnarzzler", "output"]
319 mailer
.buildFinished("builder1", b1
, b1
.results
)
320 self
.failUnless(len(self
.messages
) == 1)
321 m
,r
= self
.messages
.pop()
323 self
.failUnlessIn("Subject: buildbot warnings in PROJECT on builder1\n", t
)
324 m2
= email
.message_from_string(t
)
326 self
.failUnlessEqual(len(p
), 3)
328 self
.failUnlessIn("Build Had Warnings: unusual gnarzzler output\n",
331 self
.failUnlessEqual(p
[1].get_filename(), "step.compile")
332 self
.failUnlessEqual(p
[1].get_payload(), "Compile log here\n")
334 self
.failUnlessEqual(p
[2].get_filename(), "step.test")
335 self
.failUnlessIn("Test log here\n", p
[2].get_payload())
338 basedir
= "test_status_mail"
340 dest
= os
.environ
.get("BUILDBOT_TEST_MAIL")
342 raise unittest
.SkipTest("define BUILDBOT_TEST_MAIL=dest to run this")
343 mailer
= mail
.MailNotifier(fromaddr
="buildbot@example.com",
345 extraRecipients
=[dest
])
347 s
.url
= "project URL"
350 b1
= self
.makeBuild(3, builder
.SUCCESS
)
351 b1
.testlogs
= [MyLog(basedir
, 'compile', "Compile log here\n"),
353 'test', "Test log here\nTest 4 failed\n"),
356 print "sending mail to", dest
357 d
= mailer
.buildFinished("builder1", b1
, b1
.results
)
358 # When this fires, the mail has been sent, but the SMTP connection is
359 # still up (because smtp.sendmail relies upon the server to hang up).
360 # Spin for a moment to avoid the "unclean reactor" warning that Trial
361 # gives us if we finish before the socket is disconnected. Really,
362 # sendmail() ought to hang up the connection once it is finished:
363 # otherwise a malicious SMTP server could make us consume lots of
365 d
.addCallback(self
.stall
, 0.1)
369 Mail
.skip
= "the Twisted Mail package is not installed"
371 class Progress(unittest
.TestCase
):
373 bp
= progress
.BuildProgress([])
374 e
= progress
.Expectations(bp
)
376 self
.failUnlessEqual(e
.wavg(None, None), None)
377 self
.failUnlessEqual(e
.wavg(None, 3), 3)
378 self
.failUnlessEqual(e
.wavg(3, None), 3)
379 self
.failUnlessEqual(e
.wavg(3, 4), 3.5)
381 self
.failUnlessEqual(e
.wavg(3, 4), 3.1)
384 class Results(unittest
.TestCase
):
386 def testAddResults(self
):
387 b
= builder
.BuildStatus(builder
.BuilderStatus("test"), 12)
388 testname
= ("buildbot", "test", "test_status", "Results",
390 r1
= builder
.TestResult(name
=testname
,
391 results
=builder
.SUCCESS
,
397 res
= b
.getTestResults()
398 self
.failUnlessEqual(res
.keys(), [testname
])
400 self
.failUnless(interfaces
.ITestResult
.providedBy(t
))
401 self
.failUnlessEqual(t
.getName(), testname
)
402 self
.failUnlessEqual(t
.getResults(), builder
.SUCCESS
)
403 self
.failUnlessEqual(t
.getText(), ["passed"])
404 self
.failUnlessEqual(t
.getLogs(), {'output': ""})
406 class Log(unittest
.TestCase
):
407 def setUpClass(self
):
408 self
.basedir
= "status_log_add"
409 os
.mkdir(self
.basedir
)
412 l
= MyLog(self
.basedir
, "compile", step
=13)
413 self
.failUnlessEqual(l
.getName(), "compile")
414 self
.failUnlessEqual(l
.getStep(), 13)
415 l
.addHeader("HEADER\n")
416 l
.addStdout("Some text\n")
417 l
.addStderr("Some error\n")
418 l
.addStdout("Some more text\n")
419 self
.failIf(l
.isFinished())
421 self
.failUnless(l
.isFinished())
422 self
.failUnlessEqual(l
.getText(),
423 "Some text\nSome error\nSome more text\n")
424 self
.failUnlessEqual(l
.getTextWithHeaders(),
426 "Some text\nSome error\nSome more text\n")
427 self
.failUnlessEqual(len(list(l
.getChunks())), 4)
429 self
.failUnless(l
.hasContents())
430 os
.unlink(l
.getFilename())
431 self
.failIf(l
.hasContents())
433 def TODO_testDuplicate(self
):
434 # create multiple logs for the same step with the same logname, make
435 # sure their on-disk filenames are suitably uniquified. This
436 # functionality actually lives in BuildStepStatus and BuildStatus, so
437 # this test must involve more than just the MyLog class.
439 # naieve approach, doesn't work
440 l1
= MyLog(self
.basedir
, "duplicate")
441 l1
.addStdout("Some text\n")
443 l2
= MyLog(self
.basedir
, "duplicate")
444 l2
.addStdout("Some more text\n")
446 self
.failIfEqual(l1
.getFilename(), l2
.getFilename())
448 def testMerge1(self
):
449 l
= MyLog(self
.basedir
, "merge1")
450 l
.addHeader("HEADER\n")
451 l
.addStdout("Some text\n")
452 l
.addStdout("Some more text\n")
453 l
.addStdout("more\n")
455 self
.failUnlessEqual(l
.getText(),
456 "Some text\nSome more text\nmore\n")
457 self
.failUnlessEqual(l
.getTextWithHeaders(),
459 "Some text\nSome more text\nmore\n")
460 self
.failUnlessEqual(len(list(l
.getChunks())), 2)
462 def testMerge2(self
):
463 l
= MyLog(self
.basedir
, "merge2")
464 l
.addHeader("HEADER\n")
465 for i
in xrange(1000):
471 target
= 1000*"aaaa" + 30 * "bbbb" + 10 * "cc"
472 self
.failUnlessEqual(len(l
.getText()), len(target
))
473 self
.failUnlessEqual(l
.getText(), target
)
475 self
.failUnlessEqual(len(l
.getText()), len(target
))
476 self
.failUnlessEqual(l
.getText(), target
)
477 self
.failUnlessEqual(len(list(l
.getChunks())), 4)
479 def testMerge3(self
):
480 l
= MyLog(self
.basedir
, "merge3")
482 l
.addHeader("HEADER\n")
487 self
.failUnlessEqual(list(l
.getChunks()),
488 [(builder
.HEADER
, "HEADER\n"),
489 (builder
.STDOUT
, 100*"a"),
490 (builder
.STDOUT
, 60*"a")])
492 self
.failUnlessEqual(l
.getText(), 160*"a")
494 def testReadlines(self
):
495 l
= MyLog(self
.basedir
, "chunks")
496 l
.addHeader("HEADER\n") # should be ignored
497 l
.addStdout("Some text\n")
498 l
.addStdout("Some More Text\nAnd Some More\n")
499 l
.addStderr("Some Stderr\n")
500 l
.addStdout("Last line\n")
502 alllines
= list(l
.readlines())
503 self
.failUnlessEqual(len(alllines
), 4)
504 self
.failUnlessEqual(alllines
[0], "Some text\n")
505 self
.failUnlessEqual(alllines
[2], "And Some More\n")
506 self
.failUnlessEqual(alllines
[3], "Last line\n")
507 stderr
= list(l
.readlines(interfaces
.LOG_CHANNEL_STDERR
))
508 self
.failUnlessEqual(len(stderr
), 1)
509 self
.failUnlessEqual(stderr
[0], "Some Stderr\n")
510 lines
= l
.readlines()
511 if False: # TODO: l.readlines() is not yet an iterator
512 # verify that it really is an iterator
514 self
.failUnlessEqual(line0
, "Some text\n")
517 self
.failUnlessEqual(line2
, "And Some More\n")
520 def testChunks(self
):
521 l
= MyLog(self
.basedir
, "chunks")
523 l
.addHeader("HEADER\n")
524 l
.addStdout("Some text\n")
525 self
.failUnlessEqual("".join(l
.getChunks(onlyText
=True)),
526 "HEADER\nSome text\n")
529 l
.addStdout("Some more text\n")
530 self
.failUnlessEqual("".join(l
.getChunks(onlyText
=True)),
531 "HEADER\nSome text\nSome more text\n")
534 l
.addStdout("more\n")
537 self
.failUnlessEqual(list(c1
), [])
538 self
.failUnlessEqual(list(c2
), [(builder
.HEADER
, "HEADER\n"),
539 (builder
.STDOUT
, "Some text\n")])
540 self
.failUnlessEqual(list(c3
), [(builder
.HEADER
, "HEADER\n"),
542 "Some text\nSome more text\n")])
544 self
.failUnlessEqual(l
.getText(),
545 "Some text\nSome more text\nmore\n")
546 self
.failUnlessEqual(l
.getTextWithHeaders(),
548 "Some text\nSome more text\nmore\n")
549 self
.failUnlessEqual(len(list(l
.getChunks())), 2)
551 def testUpgrade(self
):
552 l
= MyLog(self
.basedir
, "upgrade")
553 l
.addHeader("HEADER\n")
554 l
.addStdout("Some text\n")
555 l
.addStdout("Some more text\n")
556 l
.addStdout("more\n")
558 self
.failUnless(l
.hasContents())
559 # now doctor it to look like a 0.6.4-era non-upgraded logfile
560 l
.entries
= list(l
.getChunks())
562 os
.unlink(l
.getFilename())
563 # now make sure we can upgrade it
565 self
.failUnlessEqual(l
.getText(),
566 "Some text\nSome more text\nmore\n")
567 self
.failUnlessEqual(len(list(l
.getChunks())), 2)
568 self
.failIf(l
.entries
)
570 # now, do it again, but make it look like an upgraded 0.6.4 logfile
571 # (i.e. l.filename is missing, but the contents are there on disk)
572 l
.entries
= list(l
.getChunks())
575 self
.failUnlessEqual(l
.getText(),
576 "Some text\nSome more text\nmore\n")
577 self
.failUnlessEqual(len(list(l
.getChunks())), 2)
578 self
.failIf(l
.entries
)
579 self
.failUnless(l
.hasContents())
581 def testHTMLUpgrade(self
):
582 l
= MyHTMLLog(self
.basedir
, "upgrade", "log contents")
583 l
.upgrade("filename")
585 def testSubscribe(self
):
586 l1
= MyLog(self
.basedir
, "subscribe1")
588 self
.failUnless(l1
.isFinished())
590 s
= MyLogSubscriber()
591 l1
.subscribe(s
, True)
593 self
.failIf(s
.chunks
)
595 s
= MyLogSubscriber()
596 l1
.subscribe(s
, False)
598 self
.failIf(s
.chunks
)
601 l2
= MyLog(self
.basedir
, "subscribe2")
602 l2
.waitUntilFinished().addCallback(finished
.append
)
603 l2
.addHeader("HEADER\n")
604 s1
= MyLogSubscriber()
605 l2
.subscribe(s1
, True)
606 s2
= MyLogSubscriber()
607 l2
.subscribe(s2
, False)
608 self
.failUnlessEqual(s1
.chunks
, [(builder
.HEADER
, "HEADER\n")])
609 self
.failUnlessEqual(s2
.chunks
, [])
611 l2
.addStdout("Some text\n")
612 self
.failUnlessEqual(s1
.chunks
, [(builder
.HEADER
, "HEADER\n"),
613 (builder
.STDOUT
, "Some text\n")])
614 self
.failUnlessEqual(s2
.chunks
, [(builder
.STDOUT
, "Some text\n")])
617 l2
.addStdout("Some more text\n")
618 self
.failUnlessEqual(s1
.chunks
, [(builder
.HEADER
, "HEADER\n"),
619 (builder
.STDOUT
, "Some text\n")])
620 self
.failUnlessEqual(s2
.chunks
, [(builder
.STDOUT
, "Some text\n"),
621 (builder
.STDOUT
, "Some more text\n"),
623 self
.failIf(finished
)
625 self
.failUnlessEqual(finished
, [l2
])
627 def testConsumer(self
):
628 l1
= MyLog(self
.basedir
, "consumer1")
630 self
.failUnless(l1
.isFinished())
633 d
= l1
.subscribeConsumer(s
)
634 d
.addCallback(self
._testConsumer
_1, s
)
636 testConsumer
.timeout
= 5
637 def _testConsumer_1(self
, res
, s
):
638 self
.failIf(s
.chunks
)
639 self
.failUnless(s
.finished
)
640 self
.failIf(s
.producer
) # producer should be registered and removed
642 l2
= MyLog(self
.basedir
, "consumer2")
643 l2
.addHeader("HEADER\n")
645 self
.failUnless(l2
.isFinished())
648 d
= l2
.subscribeConsumer(s
)
649 d
.addCallback(self
._testConsumer
_2, s
)
651 def _testConsumer_2(self
, res
, s
):
652 self
.failUnlessEqual(s
.chunks
, [(builder
.HEADER
, "HEADER\n")])
653 self
.failUnless(s
.finished
)
654 self
.failIf(s
.producer
) # producer should be registered and removed
657 l2
= MyLog(self
.basedir
, "consumer3")
659 l2
.addHeader("HEADER\n")
660 l2
.addStdout(800*"a")
661 l2
.addStdout(800*"a") # should now have two chunks on disk, 1000+600
662 l2
.addStdout(800*"b") # HEADER,1000+600*a on disk, 800*a in memory
663 l2
.addStdout(800*"b") # HEADER,1000+600*a,1000+600*b on disk
664 l2
.addStdout(200*"c") # HEADER,1000+600*a,1000+600*b on disk,
667 s
= MyLogConsumer(limit
=1)
668 d
= l2
.subscribeConsumer(s
)
669 d
.addCallback(self
._testConsumer
_3, l2
, s
)
671 def _testConsumer_3(self
, res
, l2
, s
):
672 self
.failUnless(s
.streaming
)
673 self
.failUnlessEqual(s
.chunks
, [(builder
.HEADER
, "HEADER\n")])
675 d
= s
.producer
.resumeProducing()
676 d
.addCallback(self
._testConsumer
_4, l2
, s
)
678 def _testConsumer_4(self
, res
, l2
, s
):
679 self
.failUnlessEqual(s
.chunks
, [(builder
.HEADER
, "HEADER\n"),
680 (builder
.STDOUT
, 1000*"a"),
683 d
= s
.producer
.resumeProducing()
684 d
.addCallback(self
._testConsumer
_5, l2
, s
)
686 def _testConsumer_5(self
, res
, l2
, s
):
687 self
.failUnlessEqual(s
.chunks
, [(builder
.HEADER
, "HEADER\n"),
688 (builder
.STDOUT
, 1000*"a"),
689 (builder
.STDOUT
, 600*"a"),
690 (builder
.STDOUT
, 1000*"b"),
691 (builder
.STDOUT
, 600*"b"),
692 (builder
.STDOUT
, 200*"c")])
693 l2
.addStdout(1000*"c") # HEADER,1600*a,1600*b,1200*c on disk
694 self
.failUnlessEqual(s
.chunks
, [(builder
.HEADER
, "HEADER\n"),
695 (builder
.STDOUT
, 1000*"a"),
696 (builder
.STDOUT
, 600*"a"),
697 (builder
.STDOUT
, 1000*"b"),
698 (builder
.STDOUT
, 600*"b"),
699 (builder
.STDOUT
, 200*"c"),
700 (builder
.STDOUT
, 1000*"c")])
702 self
.failUnlessEqual(s
.chunks
, [(builder
.HEADER
, "HEADER\n"),
703 (builder
.STDOUT
, 1000*"a"),
704 (builder
.STDOUT
, 600*"a"),
705 (builder
.STDOUT
, 1000*"b"),
706 (builder
.STDOUT
, 600*"b"),
707 (builder
.STDOUT
, 200*"c"),
708 (builder
.STDOUT
, 1000*"c")])
709 self
.failIf(s
.producer
)
710 self
.failUnless(s
.finished
)
712 def testLargeSummary(self
):
713 bigtext
= "a" * 200000 # exceed the NetstringReceiver 100KB limit
714 l
= MyLog(self
.basedir
, "large", bigtext
)
716 d
= l
.subscribeConsumer(s
)
718 for ctype
,chunk
in s
.chunks
:
719 self
.failUnless(len(chunk
) < 100000)
720 merged
= "".join([c
[1] for c
in s
.chunks
])
721 self
.failUnless(merged
== bigtext
)
722 d
.addCallback(_check
)
723 # when this fails, it fails with a timeout, and there is an exception
724 # sent to log.err(). This AttributeError exception is in
725 # NetstringReceiver.dataReceived where it does
726 # self.transport.loseConnection() because of the NetstringParseError,
727 # however self.transport is None
729 testLargeSummary
.timeout
= 5
732 from buildbot.process import factory
733 from buildbot.steps import dummy
734 from buildbot.buildslave import BuildSlave
737 f1 = factory.QuickBuildFactory('fakerep', 'cvsmodule', configure=None)
739 f2 = factory.BuildFactory([
740 s(dummy.Dummy, timeout=1),
741 s(dummy.RemoteDummy, timeout=2),
744 BuildmasterConfig = c = {}
745 c['slaves'] = [BuildSlave('bot1', 'sekrit')]
748 c['builders'].append({'name':'quick', 'slavename':'bot1',
749 'builddir': 'quickdir', 'factory': f1})
750 c['slavePortnum'] = 0
753 config_2
= config_base
+ """
754 c['builders'] = [{'name': 'dummy', 'slavename': 'bot1',
755 'builddir': 'dummy1', 'factory': f2},
756 {'name': 'testdummy', 'slavename': 'bot1',
757 'builddir': 'dummy2', 'factory': f2, 'category': 'test'}]
760 class STarget(base
.StatusReceiver
):
763 def __init__(self
, mode
):
768 print self
.events
[-1]
770 def builderAdded(self
, name
, builder
):
771 self
.events
.append(("builderAdded", name
, builder
))
773 if "builder" in self
.mode
:
775 def builderChangedState(self
, name
, state
):
776 self
.events
.append(("builderChangedState", name
, state
))
778 def buildStarted(self
, name
, build
):
779 self
.events
.append(("buildStarted", name
, build
))
781 if "eta" in self
.mode
:
782 self
.eta_build
= build
.getETA()
783 if "build" in self
.mode
:
785 def buildETAUpdate(self
, build
, ETA
):
786 self
.events
.append(("buildETAUpdate", build
, ETA
))
788 def stepStarted(self
, build
, step
):
789 self
.events
.append(("stepStarted", build
, step
))
791 if 0 and "eta" in self
.mode
:
792 print "TIMES", step
.getTimes()
793 print "ETA", step
.getETA()
794 print "EXP", step
.getExpectations()
795 if "step" in self
.mode
:
797 def stepTextChanged(self
, build
, step
, text
):
798 self
.events
.append(("stepTextChanged", step
, text
))
799 def stepText2Changed(self
, build
, step
, text2
):
800 self
.events
.append(("stepText2Changed", step
, text2
))
801 def stepETAUpdate(self
, build
, step
, ETA
, expectations
):
802 self
.events
.append(("stepETAUpdate", build
, step
, ETA
, expectations
))
804 def logStarted(self
, build
, step
, log
):
805 self
.events
.append(("logStarted", build
, step
, log
))
807 def logFinished(self
, build
, step
, log
):
808 self
.events
.append(("logFinished", build
, step
, log
))
810 def stepFinished(self
, build
, step
, results
):
811 self
.events
.append(("stepFinished", build
, step
, results
))
812 if 0 and "eta" in self
.mode
:
813 print "post-EXP", step
.getExpectations()
815 def buildFinished(self
, name
, build
, results
):
816 self
.events
.append(("buildFinished", name
, build
, results
))
818 def builderRemoved(self
, name
):
819 self
.events
.append(("builderRemoved", name
))
822 class Subscription(RunMixin
, unittest
.TestCase
):
823 # verify that StatusTargets can subscribe/unsubscribe properly
828 self
.t1
= t1
= STarget(["builder"])
829 #t1.debug = True; print
831 self
.failUnlessEqual(len(t1
.events
), 0)
833 self
.t3
= t3
= STarget(["builder", "build", "step"])
836 m
.loadConfig(config_2
)
840 self
.failUnlessEqual(len(t1
.events
), 4)
841 self
.failUnlessEqual(t1
.events
[0][0:2], ("builderAdded", "dummy"))
842 self
.failUnlessEqual(t1
.events
[1],
843 ("builderChangedState", "dummy", "offline"))
844 self
.failUnlessEqual(t1
.events
[2][0:2], ("builderAdded", "testdummy"))
845 self
.failUnlessEqual(t1
.events
[3],
846 ("builderChangedState", "testdummy", "offline"))
849 self
.failUnlessEqual(s
.getBuilderNames(), ["dummy", "testdummy"])
850 self
.failUnlessEqual(s
.getBuilderNames(categories
=['test']),
852 self
.s1
= s1
= s
.getBuilder("dummy")
853 self
.failUnlessEqual(s1
.getName(), "dummy")
854 self
.failUnlessEqual(s1
.getState(), ("offline", []))
855 self
.failUnlessEqual(s1
.getCurrentBuilds(), [])
856 self
.failUnlessEqual(s1
.getLastFinishedBuild(), None)
857 self
.failUnlessEqual(s1
.getBuild(-1), None)
858 #self.failUnlessEqual(s1.getEvent(-1), foo("created"))
860 # status targets should, upon being subscribed, immediately get a
861 # list of all current builders matching their category
862 self
.t2
= t2
= STarget([])
864 self
.failUnlessEqual(len(t2
.events
), 2)
865 self
.failUnlessEqual(t2
.events
[0][0:2], ("builderAdded", "dummy"))
866 self
.failUnlessEqual(t2
.events
[1][0:2], ("builderAdded", "testdummy"))
868 d
= self
.connectSlave(builders
=["dummy", "testdummy"])
869 d
.addCallback(self
._testSlave
_1, t1
)
872 def _testSlave_1(self
, res
, t1
):
873 self
.failUnlessEqual(len(t1
.events
), 2)
874 self
.failUnlessEqual(t1
.events
[0],
875 ("builderChangedState", "dummy", "idle"))
876 self
.failUnlessEqual(t1
.events
[1],
877 ("builderChangedState", "testdummy", "idle"))
880 c
= interfaces
.IControl(self
.master
)
881 req
= BuildRequest("forced build for testing", SourceStamp())
882 c
.getBuilder("dummy").requestBuild(req
)
883 d
= req
.waitUntilFinished()
884 d2
= self
.master
.botmaster
.waitUntilBuilderIdle("dummy")
885 dl
= defer
.DeferredList([d
, d2
])
886 dl
.addCallback(self
._testSlave
_2)
889 def _testSlave_2(self
, res
):
890 # t1 subscribes to builds, but not anything lower-level
892 self
.failUnlessEqual(len(ev
), 4)
893 self
.failUnlessEqual(ev
[0][0:3],
894 ("builderChangedState", "dummy", "building"))
895 self
.failUnlessEqual(ev
[1][0], "buildStarted")
896 self
.failUnlessEqual(ev
[2][0:2]+ev
[2][3:4],
897 ("buildFinished", "dummy", builder
.SUCCESS
))
898 self
.failUnlessEqual(ev
[3][0:3],
899 ("builderChangedState", "dummy", "idle"))
901 self
.failUnlessEqual([ev
[0] for ev
in self
.t3
.events
],
903 "builderChangedState", # offline
905 "builderChangedState", # idle
906 "builderChangedState", # offline
907 "builderChangedState", # idle
908 "builderChangedState", # building
910 "stepStarted", "stepETAUpdate",
911 "stepTextChanged", "stepFinished",
912 "stepStarted", "stepETAUpdate",
913 "stepTextChanged", "logStarted", "logFinished",
914 "stepTextChanged", "stepText2Changed",
917 "builderChangedState", # idle
920 b
= self
.s1
.getLastFinishedBuild()
922 self
.failUnlessEqual(b
.getBuilder().getName(), "dummy")
923 self
.failUnlessEqual(b
.getNumber(), 0)
924 self
.failUnlessEqual(b
.getSourceStamp().branch
, None)
925 self
.failUnlessEqual(b
.getSourceStamp().patch
, None)
926 self
.failUnlessEqual(b
.getSourceStamp().revision
, None)
927 self
.failUnlessEqual(b
.getReason(), "forced build for testing")
928 self
.failUnlessEqual(b
.getChanges(), ())
929 self
.failUnlessEqual(b
.getResponsibleUsers(), [])
930 self
.failUnless(b
.isFinished())
931 self
.failUnlessEqual(b
.getText(), ['build', 'successful'])
932 self
.failUnlessEqual(b
.getColor(), "green")
933 self
.failUnlessEqual(b
.getResults(), builder
.SUCCESS
)
936 self
.failUnlessEqual(len(steps
), 2)
940 self
.failUnlessEqual(st1
.getName(), "dummy")
941 self
.failUnless(st1
.isFinished())
942 self
.failUnlessEqual(st1
.getText(), ["delay", "1 secs"])
943 start
,finish
= st1
.getTimes()
944 self
.failUnless(0.5 < (finish
-start
) < 10)
945 self
.failUnlessEqual(st1
.getExpectations(), [])
946 self
.failUnlessEqual(st1
.getLogs(), [])
950 self
.failUnlessEqual(st2
.getName(), "remote dummy")
951 self
.failUnless(st2
.isFinished())
952 self
.failUnlessEqual(st2
.getText(),
953 ["remote", "delay", "2 secs"])
954 start
,finish
= st2
.getTimes()
955 self
.failUnless(1.5 < (finish
-start
) < 10)
957 self
.failUnlessEqual(st2
.getExpectations(), [('output', 38, None)])
959 self
.failUnlessEqual(len(logs
), 1)
960 self
.failUnlessEqual(logs
[0].getName(), "stdio")
961 self
.failUnlessEqual(logs
[0].getText(), "data")
964 # now we run it a second time, and we should have an ETA
966 self
.t4
= t4
= STarget(["builder", "build", "eta"])
967 self
.master
.getStatus().subscribe(t4
)
968 c
= interfaces
.IControl(self
.master
)
969 req
= BuildRequest("forced build for testing", SourceStamp())
970 c
.getBuilder("dummy").requestBuild(req
)
971 d
= req
.waitUntilFinished()
972 d2
= self
.master
.botmaster
.waitUntilBuilderIdle("dummy")
973 dl
= defer
.DeferredList([d
, d2
])
974 dl
.addCallback(self
._testSlave
_3)
977 def _testSlave_3(self
, res
):
980 self
.failUnless(eta
-1 < t4
.eta_build
< eta
+1, # should be 3 seconds
981 "t4.eta_build was %g, not in (%g,%g)"
982 % (t4
.eta_build
, eta
-1, eta
+1))
985 class Client(unittest
.TestCase
):
986 def testAdaptation(self
):
987 b
= builder
.BuilderStatus("bname")
988 b2
= client
.makeRemote(b
)
989 self
.failUnless(isinstance(b2
, client
.RemoteBuilder
))
990 b3
= client
.makeRemote(None)
991 self
.failUnless(b3
is None)
994 class ContactTester(unittest
.TestCase
):
995 def test_notify_invalid_syntax(self
):
997 self
.assertRaises(words
.UsageError
, lambda args
, who
: irc
.command_NOTIFY(args
, who
), "", "mynick")
999 def test_notify_list(self
):
1001 irc
.command_NOTIFY("list", "mynick")
1002 self
.failUnlessEqual(irc
.message
, "The following events are being notified: []", "empty notify list")
1005 irc
.command_NOTIFY("on started", "mynick")
1006 self
.failUnlessEqual(irc
.message
, "The following events are being notified: ['started']", "on started")
1009 irc
.command_NOTIFY("on finished", "mynick")
1010 self
.failUnlessEqual(irc
.message
, "The following events are being notified: ['started', 'finished']", "on finished")
1013 irc
.command_NOTIFY("off", "mynick")
1014 self
.failUnlessEqual(irc
.message
, "The following events are being notified: []", "off all")
1017 irc
.command_NOTIFY("on", "mynick")
1018 self
.failUnlessEqual(irc
.message
, "The following events are being notified: ['started', 'finished']", "on default set")
1021 irc
.command_NOTIFY("off started", "mynick")
1022 self
.failUnlessEqual(irc
.message
, "The following events are being notified: ['finished']", "off started")
1025 irc
.command_NOTIFY("on success failed exception", "mynick")
1026 self
.failUnlessEqual(irc
.message
, "The following events are being notified: ['failed', 'finished', 'exception', 'success']", "on multiple events")
1028 def test_notification_default(self
):
1031 my_builder
= MyBuilder("builder78")
1032 my_build
= MyIrcBuild(my_builder
, 23, builder
.SUCCESS
)
1034 irc
.buildStarted(my_builder
.getName(), my_build
)
1035 self
.failUnlessEqual(irc
.message
, "", "No notification with default settings")
1037 irc
.buildFinished(my_builder
.getName(), my_build
, None)
1038 self
.failUnlessEqual(irc
.message
, "", "No notification with default settings")
1040 def test_notification_started(self
):
1043 my_builder
= MyBuilder("builder78")
1044 my_build
= MyIrcBuild(my_builder
, 23, builder
.SUCCESS
)
1045 my_build
.changes
= (
1046 Change(who
= 'author1', files
= ['file1'], comments
= 'comment1', revision
= 123),
1047 Change(who
= 'author2', files
= ['file2'], comments
= 'comment2', revision
= 456),
1050 irc
.command_NOTIFY("on started", "mynick")
1053 irc
.buildStarted(my_builder
.getName(), my_build
)
1054 self
.failUnlessEqual(irc
.message
, "build #23 of builder78 started including [123, 456]", "Start notification generated with notify_events=['started']")
1057 irc
.buildFinished(my_builder
.getName(), my_build
, None)
1058 self
.failUnlessEqual(irc
.message
, "", "No finished notification with notify_events=['started']")
1060 def test_notification_finished(self
):
1063 my_builder
= MyBuilder("builder834")
1064 my_build
= MyIrcBuild(my_builder
, 862, builder
.SUCCESS
)
1065 my_build
.changes
= (
1066 Change(who
= 'author1', files
= ['file1'], comments
= 'comment1', revision
= 943),
1069 irc
.command_NOTIFY("on finished", "mynick")
1072 irc
.buildStarted(my_builder
.getName(), my_build
)
1073 self
.failUnlessEqual(irc
.message
, "", "No started notification with notify_events=['finished']")
1076 irc
.buildFinished(my_builder
.getName(), my_build
, None)
1077 self
.failUnlessEqual(irc
.message
, "build #862 of builder834 is complete: Success [step1 step2] Build details are at http://myserver/mypath?build=765", "Finish notification generated with notify_events=['finished']")
1079 def test_notification_success(self
):
1082 my_builder
= MyBuilder("builder834")
1083 my_build
= MyIrcBuild(my_builder
, 862, builder
.SUCCESS
)
1084 my_build
.changes
= (
1085 Change(who
= 'author1', files
= ['file1'], comments
= 'comment1', revision
= 943),
1088 irc
.command_NOTIFY("on success", "mynick")
1091 irc
.buildStarted(my_builder
.getName(), my_build
)
1092 self
.failUnlessEqual(irc
.message
, "", "No started notification with notify_events=['success']")
1095 irc
.buildFinished(my_builder
.getName(), my_build
, None)
1096 self
.failUnlessEqual(irc
.message
, "build #862 of builder834 is complete: Success [step1 step2] Build details are at http://myserver/mypath?build=765", "Finish notification generated on success with notify_events=['success']")
1099 my_build
.results
= builder
.FAILURE
1100 irc
.buildFinished(my_builder
.getName(), my_build
, None)
1101 self
.failUnlessEqual(irc
.message
, "", "No finish notification generated on failure with notify_events=['success']")
1104 my_build
.results
= builder
.EXCEPTION
1105 irc
.buildFinished(my_builder
.getName(), my_build
, None)
1106 self
.failUnlessEqual(irc
.message
, "", "No finish notification generated on exception with notify_events=['success']")
1108 def test_notification_failed(self
):
1111 my_builder
= MyBuilder("builder834")
1112 my_build
= MyIrcBuild(my_builder
, 862, builder
.FAILURE
)
1113 my_build
.changes
= (
1114 Change(who
= 'author1', files
= ['file1'], comments
= 'comment1', revision
= 943),
1117 irc
.command_NOTIFY("on failed", "mynick")
1120 irc
.buildStarted(my_builder
.getName(), my_build
)
1121 self
.failUnlessEqual(irc
.message
, "", "No started notification with notify_events=['failed']")
1124 irc
.buildFinished(my_builder
.getName(), my_build
, None)
1125 self
.failUnlessEqual(irc
.message
, "build #862 of builder834 is complete: Failure [step1 step2] Build details are at http://myserver/mypath?build=765", "Finish notification generated on failure with notify_events=['failed']")
1128 my_build
.results
= builder
.SUCCESS
1129 irc
.buildFinished(my_builder
.getName(), my_build
, None)
1130 self
.failUnlessEqual(irc
.message
, "", "No finish notification generated on success with notify_events=['failed']")
1133 my_build
.results
= builder
.EXCEPTION
1134 irc
.buildFinished(my_builder
.getName(), my_build
, None)
1135 self
.failUnlessEqual(irc
.message
, "", "No finish notification generated on exception with notify_events=['failed']")
1137 def test_notification_exception(self
):
1140 my_builder
= MyBuilder("builder834")
1141 my_build
= MyIrcBuild(my_builder
, 862, builder
.EXCEPTION
)
1142 my_build
.changes
= (
1143 Change(who
= 'author1', files
= ['file1'], comments
= 'comment1', revision
= 943),
1146 irc
.command_NOTIFY("on exception", "mynick")
1149 irc
.buildStarted(my_builder
.getName(), my_build
)
1150 self
.failUnlessEqual(irc
.message
, "", "No started notification with notify_events=['exception']")
1153 irc
.buildFinished(my_builder
.getName(), my_build
, None)
1154 self
.failUnlessEqual(irc
.message
, "build #862 of builder834 is complete: Exception [step1 step2] Build details are at http://myserver/mypath?build=765", "Finish notification generated on failure with notify_events=['exception']")
1157 my_build
.results
= builder
.SUCCESS
1158 irc
.buildFinished(my_builder
.getName(), my_build
, None)
1159 self
.failUnlessEqual(irc
.message
, "", "No finish notification generated on success with notify_events=['exception']")
1162 my_build
.results
= builder
.FAILURE
1163 irc
.buildFinished(my_builder
.getName(), my_build
, None)
1164 self
.failUnlessEqual(irc
.message
, "", "No finish notification generated on exception with notify_events=['exception']")
1166 def test_notification_successToFailure(self
):
1169 my_builder
= MyBuilder("builder834")
1170 my_build
= MyIrcBuild(my_builder
, 862, builder
.FAILURE
)
1171 my_build
.changes
= (
1172 Change(who
= 'author1', files
= ['file1'], comments
= 'comment1', revision
= 943),
1174 previous_build
= MyIrcBuild(my_builder
, 861, builder
.SUCCESS
)
1175 my_build
.setPreviousBuild(previous_build
)
1177 irc
.command_NOTIFY("on successToFailure", "mynick")
1180 irc
.buildStarted(my_builder
.getName(), my_build
)
1181 self
.failUnlessEqual(irc
.message
, "", "No started notification with notify_events=['failed']")
1184 irc
.buildFinished(my_builder
.getName(), my_build
, None)
1185 self
.failUnlessEqual(irc
.message
, "build #862 of builder834 is complete: Failure [step1 step2] Build details are at http://myserver/mypath?build=765", "Finish notification generated on failure with notify_events=['successToFailure']")
1188 my_build
.results
= builder
.SUCCESS
1189 irc
.buildFinished(my_builder
.getName(), my_build
, None)
1190 self
.failUnlessEqual(irc
.message
, "", "No finish notification generated on success with notify_events=['failed']")
1193 my_build
.results
= builder
.EXCEPTION
1194 irc
.buildFinished(my_builder
.getName(), my_build
, None)
1195 self
.failUnlessEqual(irc
.message
, "", "No finish notification generated on exception with notify_events=['failed']")
1197 def test_notification_failureToSuccess(self
):
1200 my_builder
= MyBuilder("builder834")
1201 my_build
= MyIrcBuild(my_builder
, 862, builder
.SUCCESS
)
1202 my_build
.changes
= (
1203 Change(who
= 'author1', files
= ['file1'], comments
= 'comment1', revision
= 943),
1205 previous_build
= MyIrcBuild(my_builder
, 861, builder
.FAILURE
)
1206 my_build
.setPreviousBuild(previous_build
)
1208 irc
.command_NOTIFY("on failureToSuccess", "mynick")
1211 irc
.buildStarted(my_builder
.getName(), my_build
)
1212 self
.failUnlessEqual(irc
.message
, "", "No started notification with notify_events=['success']")
1215 irc
.buildFinished(my_builder
.getName(), my_build
, None)
1216 self
.failUnlessEqual(irc
.message
, "build #862 of builder834 is complete: Success [step1 step2] Build details are at http://myserver/mypath?build=765", "Finish notification generated on success with notify_events=['failureToSuccess']")
1219 my_build
.results
= builder
.FAILURE
1220 irc
.buildFinished(my_builder
.getName(), my_build
, None)
1221 self
.failUnlessEqual(irc
.message
, "", "No finish notification generated on failure with notify_events=['success']")
1224 my_build
.results
= builder
.EXCEPTION
1225 irc
.buildFinished(my_builder
.getName(), my_build
, None)
1226 self
.failUnlessEqual(irc
.message
, "", "No finish notification generated on exception with notify_events=['success']")
1228 class MyIrcBuild(builder
.BuildStatus
):
1231 def __init__(self
, parent
, number
, results
):
1232 builder
.BuildStatus
.__init
__(self
, parent
, number
)
1233 self
.results
= results
1234 self
.previousBuild
= None
1236 def getResults(self
):
1240 return ('step1', 'step2')
1242 def setPreviousBuild(self
, pb
):
1243 self
.previousBuild
= pb
1245 def getPreviousBuild(self
):
1246 return self
.previousBuild
1249 def getURLForThing(self
, build
):
1250 return 'http://myserver/mypath?build=765'
1254 status
= URLProducer()
1259 class MyContact(words
.Contact
):
1262 def __init__(self
, channel
= MyChannel()):
1263 words
.Contact
.__init
__(self
, channel
)
1266 def subscribe_to_build_events(self
):
1269 def unsubscribe_from_build_events(self
):
1272 def send(self
, msg
):
1275 class StepStatistics(unittest
.TestCase
):
1276 def testStepStatistics(self
):
1277 status
= builder
.BuildStatus(builder
.BuilderStatus("test"), 123)
1278 status
.addStepWithName('step1')
1279 status
.addStepWithName('step2')
1280 status
.addStepWithName('step3')
1281 status
.addStepWithName('step4')
1283 steps
= status
.getSteps()
1284 (step1
, step2
, step3
, step4
) = steps
1286 step1
.setStatistic('test-prop', 1)
1287 step3
.setStatistic('test-prop', 2)
1288 step4
.setStatistic('test-prop', 4)
1290 step1
.setStatistic('other-prop', 27)
1291 # Just to have some other properties around
1293 self
.failUnlessEqual(step1
.getStatistic('test-prop'), 1,
1294 'Retrieve an existing property')
1295 self
.failUnlessEqual(step1
.getStatistic('test-prop', 99), 1,
1296 "Don't default an existing property")
1297 self
.failUnlessEqual(step2
.getStatistic('test-prop', 99), 99,
1298 'Default a non-existant property')
1300 self
.failUnlessEqual(
1301 status
.getSummaryStatistic('test-prop', operator
.add
), 7,
1302 'Sum property across the build')
1304 self
.failUnlessEqual(
1305 status
.getSummaryStatistic('test-prop', operator
.add
, 13), 20,
1306 'Sum property across the build with initial value')