Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/buildbot/buildbot/test/test_status.py
diff options
context:
space:
mode:
Diffstat (limited to 'buildbot/buildbot/test/test_status.py')
-rw-r--r--buildbot/buildbot/test/test_status.py1631
1 files changed, 1631 insertions, 0 deletions
diff --git a/buildbot/buildbot/test/test_status.py b/buildbot/buildbot/test/test_status.py
new file mode 100644
index 0000000..b3c162a
--- /dev/null
+++ b/buildbot/buildbot/test/test_status.py
@@ -0,0 +1,1631 @@
+# -*- test-case-name: buildbot.test.test_status -*-
+
+import email, os
+import operator
+
+from zope.interface import implements
+from twisted.internet import defer, reactor
+from twisted.trial import unittest
+
+from buildbot import interfaces
+from buildbot.sourcestamp import SourceStamp
+from buildbot.process.base import BuildRequest, Build
+from buildbot.status import builder, base, words, progress
+from buildbot.changes.changes import Change
+from buildbot.process.builder import Builder
+from time import sleep
+
+mail = None
+try:
+ from buildbot.status import mail
+except ImportError:
+ pass
+from buildbot.status import progress, client # NEEDS COVERAGE
+from buildbot.test.runutils import RunMixin, setupBuildStepStatus
+
+class MyStep:
+ build = None
+ def getName(self):
+ return "step"
+
+class MyLogFileProducer(builder.LogFileProducer):
+ # The reactor.callLater(0) in LogFileProducer.resumeProducing is a bit of
+ # a nuisance from a testing point of view. This subclass adds a Deferred
+ # to that call so we can find out when it is complete.
+ def resumeProducing(self):
+ d = defer.Deferred()
+ reactor.callLater(0, self._resumeProducing, d)
+ return d
+ def _resumeProducing(self, d):
+ builder.LogFileProducer._resumeProducing(self)
+ reactor.callLater(0, d.callback, None)
+
+class MyLog(builder.LogFile):
+ def __init__(self, basedir, name, text=None, step=None):
+ self.fakeBuilderBasedir = basedir
+ if not step:
+ step = MyStep()
+ builder.LogFile.__init__(self, step, name, name)
+ if text:
+ self.addStdout(text)
+ self.finish()
+ def getFilename(self):
+ return os.path.join(self.fakeBuilderBasedir, self.name)
+
+ def subscribeConsumer(self, consumer):
+ p = MyLogFileProducer(self, consumer)
+ d = p.resumeProducing()
+ return d
+
+class MyHTMLLog(builder.HTMLLogFile):
+ def __init__(self, basedir, name, html):
+ step = MyStep()
+ builder.HTMLLogFile.__init__(self, step, name, name, html)
+
+class MyLogSubscriber:
+ def __init__(self):
+ self.chunks = []
+ def logChunk(self, build, step, log, channel, text):
+ self.chunks.append((channel, text))
+
+class MyLogConsumer:
+ def __init__(self, limit=None):
+ self.chunks = []
+ self.finished = False
+ self.limit = limit
+ def registerProducer(self, producer, streaming):
+ self.producer = producer
+ self.streaming = streaming
+ def unregisterProducer(self):
+ self.producer = None
+ def writeChunk(self, chunk):
+ self.chunks.append(chunk)
+ if self.limit:
+ self.limit -= 1
+ if self.limit == 0:
+ self.producer.pauseProducing()
+ def finish(self):
+ self.finished = True
+
+if mail:
+ class MyMailer(mail.MailNotifier):
+ def sendMessage(self, m, recipients):
+ self.parent.messages.append((m, recipients))
+
+class MyStatus:
+ def getBuildbotURL(self):
+ return self.url
+ def getURLForThing(self, thing):
+ return None
+ def getProjectName(self):
+ return "myproj"
+
+class MyBuilder(builder.BuilderStatus):
+ nextBuildNumber = 0
+
+class MyBuild(builder.BuildStatus):
+ testlogs = []
+ def __init__(self, parent, number, results):
+ builder.BuildStatus.__init__(self, parent, number)
+ self.results = results
+ self.source = SourceStamp(revision="1.14")
+ self.reason = "build triggered by changes"
+ self.finished = True
+ def getLogs(self):
+ return self.testlogs
+
+class MyLookup:
+ implements(interfaces.IEmailLookup)
+
+ def getAddress(self, user):
+ d = defer.Deferred()
+ # With me now is Mr Thomas Walters of West Hartlepool who is totally
+ # invisible.
+ if user == "Thomas_Walters":
+ d.callback(None)
+ else:
+ d.callback(user + "@" + "dev.com")
+ return d
+
+def customTextMailMessage(attrs):
+ logLines = 3
+ text = list()
+ text.append("STATUS: %s" % attrs['result'].title())
+ text.append("")
+ text.extend([c.asText() for c in attrs['changes']])
+ text.append("")
+ name, url, lines = attrs['logs'][-1]
+ text.append("Last %d lines of '%s':" % (logLines, name))
+ text.extend(["\t%s\n" % line for line in lines[len(lines)-logLines:]])
+ text.append("")
+ text.append("-buildbot")
+ return ("\n".join(text), 'plain')
+
+def customHTMLMailMessage(attrs):
+ logLines = 3
+ text = list()
+ text.append("<h3>STATUS <a href='%s'>%s</a>:</h3>" % (attrs['buildURL'],
+ attrs['result'].title()))
+ text.append("<h4>Recent Changes:</h4>")
+ text.extend([c.asHTML() for c in attrs['changes']])
+ name, url, lines = attrs['logs'][-1]
+ text.append("<h4>Last %d lines of '%s':</h4>" % (logLines, name))
+ text.append("<p>")
+ text.append("<br>".join([line for line in lines[len(lines)-logLines:]]))
+ text.append("</p>")
+ text.append("<br>")
+ text.append("<b>-<a href='%s'>buildbot</a></b>" % attrs['buildbotURL'])
+ return ("\n".join(text), 'html')
+
+class Mail(unittest.TestCase):
+
+ def setUp(self):
+ self.builder = MyBuilder("builder1")
+
+ def stall(self, res, timeout):
+ d = defer.Deferred()
+ reactor.callLater(timeout, d.callback, res)
+ return d
+
+ def makeBuild(self, number, results):
+ return MyBuild(self.builder, number, results)
+
+ def failUnlessIn(self, substring, string):
+ self.failUnless(string.find(substring) != -1,
+ "didn't see '%s' in '%s'" % (substring, string))
+
+ def getProjectName(self):
+ return "PROJECT"
+
+ def getBuildbotURL(self):
+ return "BUILDBOT_URL"
+
+ def getURLForThing(self, thing):
+ return None
+
+ def testBuild1(self):
+ mailer = MyMailer(fromaddr="buildbot@example.com",
+ extraRecipients=["recip@example.com",
+ "recip2@example.com"],
+ lookup=mail.Domain("dev.com"))
+ mailer.parent = self
+ mailer.status = self
+ self.messages = []
+
+ b1 = self.makeBuild(3, builder.SUCCESS)
+ b1.blamelist = ["bob"]
+
+ mailer.buildFinished("builder1", b1, b1.results)
+ self.failUnless(len(self.messages) == 1)
+ m,r = self.messages.pop()
+ t = m.as_string()
+ self.failUnlessIn("To: bob@dev.com\n", t)
+ self.failUnlessIn("CC: recip2@example.com, recip@example.com\n", t)
+ self.failUnlessIn("From: buildbot@example.com\n", t)
+ self.failUnlessIn("Subject: buildbot success in PROJECT on builder1\n", t)
+ self.failUnlessIn("Date: ", t)
+ self.failUnlessIn("Build succeeded!\n", t)
+ self.failUnlessIn("Buildbot URL: BUILDBOT_URL\n", t)
+
+ def testBuild2(self):
+ mailer = MyMailer(fromaddr="buildbot@example.com",
+ extraRecipients=["recip@example.com",
+ "recip2@example.com"],
+ lookup="dev.com",
+ sendToInterestedUsers=False)
+ mailer.parent = self
+ mailer.status = self
+ self.messages = []
+
+ b1 = self.makeBuild(3, builder.SUCCESS)
+ b1.blamelist = ["bob"]
+
+ mailer.buildFinished("builder1", b1, b1.results)
+ self.failUnless(len(self.messages) == 1)
+ m,r = self.messages.pop()
+ t = m.as_string()
+ self.failUnlessIn("To: recip2@example.com, "
+ "recip@example.com\n", t)
+ self.failUnlessIn("From: buildbot@example.com\n", t)
+ self.failUnlessIn("Subject: buildbot success in PROJECT on builder1\n", t)
+ self.failUnlessIn("Build succeeded!\n", t)
+ self.failUnlessIn("Buildbot URL: BUILDBOT_URL\n", t)
+
+ def testBuildStatusCategory(self):
+ # a status client only interested in a category should only receive
+ # from that category
+ mailer = MyMailer(fromaddr="buildbot@example.com",
+ extraRecipients=["recip@example.com",
+ "recip2@example.com"],
+ lookup="dev.com",
+ sendToInterestedUsers=False,
+ categories=["debug"])
+
+ mailer.parent = self
+ mailer.status = self
+ self.messages = []
+
+ b1 = self.makeBuild(3, builder.SUCCESS)
+ b1.blamelist = ["bob"]
+
+ mailer.buildFinished("builder1", b1, b1.results)
+ self.failIf(self.messages)
+
+ def testBuilderCategory(self):
+ # a builder in a certain category should notify status clients that
+ # did not list categories, or categories including this one
+ mailer1 = MyMailer(fromaddr="buildbot@example.com",
+ extraRecipients=["recip@example.com",
+ "recip2@example.com"],
+ lookup="dev.com",
+ sendToInterestedUsers=False)
+ mailer2 = MyMailer(fromaddr="buildbot@example.com",
+ extraRecipients=["recip@example.com",
+ "recip2@example.com"],
+ lookup="dev.com",
+ sendToInterestedUsers=False,
+ categories=["active"])
+ mailer3 = MyMailer(fromaddr="buildbot@example.com",
+ extraRecipients=["recip@example.com",
+ "recip2@example.com"],
+ lookup="dev.com",
+ sendToInterestedUsers=False,
+ categories=["active", "debug"])
+
+ builderd = MyBuilder("builder2", "debug")
+
+ mailer1.parent = self
+ mailer1.status = self
+ mailer2.parent = self
+ mailer2.status = self
+ mailer3.parent = self
+ mailer3.status = self
+ self.messages = []
+
+ t = mailer1.builderAdded("builder2", builderd)
+ self.assertEqual(len(mailer1.watched), 1)
+ self.assertEqual(t, mailer1)
+ t = mailer2.builderAdded("builder2", builderd)
+ self.assertEqual(len(mailer2.watched), 0)
+ self.assertEqual(t, None)
+ t = mailer3.builderAdded("builder2", builderd)
+ self.assertEqual(len(mailer3.watched), 1)
+ self.assertEqual(t, mailer3)
+
+ b2 = MyBuild(builderd, 3, builder.SUCCESS)
+ b2.blamelist = ["bob"]
+
+ mailer1.buildFinished("builder2", b2, b2.results)
+ self.failUnlessEqual(len(self.messages), 1)
+ self.messages = []
+ mailer2.buildFinished("builder2", b2, b2.results)
+ self.failUnlessEqual(len(self.messages), 0)
+ self.messages = []
+ mailer3.buildFinished("builder2", b2, b2.results)
+ self.failUnlessEqual(len(self.messages), 1)
+
+ def testCustomTextMessage(self):
+ basedir = "test_custom_text_mesg"
+ os.mkdir(basedir)
+ mailer = MyMailer(fromaddr="buildbot@example.com", mode="problem",
+ extraRecipients=["recip@example.com",
+ "recip2@example.com"],
+ lookup=MyLookup(),
+ customMesg=customTextMailMessage)
+ mailer.parent = self
+ mailer.status = self
+ self.messages = []
+
+ b1 = self.makeBuild(4, builder.FAILURE)
+ b1.setText(["snarkleack", "polarization", "failed"])
+ b1.blamelist = ["dev3", "dev3", "dev3", "dev4",
+ "Thomas_Walters"]
+ b1.source.changes = (Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 123),
+ Change(who = 'author2', files = ['file2'], comments = 'comment2', revision = 456))
+ b1.testlogs = [MyLog(basedir, 'compile', "Compile log here\n"),
+ MyLog(basedir, 'test', "Test log here\nTest 1 failed\nTest 2 failed\nTest 3 failed\nTest 4 failed\n")]
+
+ mailer.buildFinished("builder1", b1, b1.results)
+ m,r = self.messages.pop()
+ t = m.as_string()
+ #
+ # Uncomment to review custom message
+ #
+ #self.fail(t)
+ self.failUnlessIn("comment1", t)
+ self.failUnlessIn("comment2", t)
+ self.failUnlessIn("Test 4 failed", t)
+
+
+ def testCustomHTMLMessage(self):
+ basedir = "test_custom_HTML_mesg"
+ os.mkdir(basedir)
+ mailer = MyMailer(fromaddr="buildbot@example.com", mode="problem",
+ extraRecipients=["recip@example.com",
+ "recip2@example.com"],
+ lookup=MyLookup(),
+ customMesg=customHTMLMailMessage)
+ mailer.parent = self
+ mailer.status = self
+ self.messages = []
+
+ b1 = self.makeBuild(4, builder.FAILURE)
+ b1.setText(["snarkleack", "polarization", "failed"])
+ b1.blamelist = ["dev3", "dev3", "dev3", "dev4",
+ "Thomas_Walters"]
+ b1.source.changes = (Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 123),
+ Change(who = 'author2', files = ['file2'], comments = 'comment2', revision = 456))
+ b1.testlogs = [MyLog(basedir, 'compile', "Compile log here\n"),
+ MyLog(basedir, 'test', "Test log here\nTest 1 failed\nTest 2 failed\nTest 3 failed\nTest 4 failed\n")]
+
+ mailer.buildFinished("builder1", b1, b1.results)
+ m,r = self.messages.pop()
+ t = m.as_string()
+ #
+ # Uncomment to review custom message
+ #
+ #self.fail(t)
+ self.failUnlessIn("<h4>Last 3 lines of 'step.test':</h4>", t)
+ self.failUnlessIn("<p>Changed by: <b>author2</b><br />", t)
+ self.failUnlessIn("Test 3 failed", t)
+
+ def testShouldAttachLog(self):
+ mailer = mail.MailNotifier(fromaddr="buildbot@example.com", addLogs=True)
+ self.assertTrue(mailer._shouldAttachLog('anything'))
+ mailer = mail.MailNotifier(fromaddr="buildbot@example.com", addLogs=False)
+ self.assertFalse(mailer._shouldAttachLog('anything'))
+ mailer = mail.MailNotifier(fromaddr="buildbot@example.com", addLogs=['something'])
+ self.assertFalse(mailer._shouldAttachLog('anything'))
+ self.assertTrue(mailer._shouldAttachLog('something'))
+
+ def testFailure(self):
+ mailer = MyMailer(fromaddr="buildbot@example.com", mode="problem",
+ extraRecipients=["recip@example.com",
+ "recip2@example.com"],
+ lookup=MyLookup())
+ mailer.parent = self
+ mailer.status = self
+ self.messages = []
+
+ b1 = self.makeBuild(3, builder.SUCCESS)
+ b1.blamelist = ["dev1", "dev2"]
+ b2 = self.makeBuild(4, builder.FAILURE)
+ b2.setText(["snarkleack", "polarization", "failed"])
+ b2.blamelist = ["dev3", "dev3", "dev3", "dev4",
+ "Thomas_Walters"]
+ mailer.buildFinished("builder1", b1, b1.results)
+ self.failIf(self.messages)
+ mailer.buildFinished("builder1", b2, b2.results)
+ self.failUnless(len(self.messages) == 1)
+ m,r = self.messages.pop()
+ t = m.as_string()
+ self.failUnlessIn("To: dev3@dev.com, dev4@dev.com\n", t)
+ self.failUnlessIn("CC: recip2@example.com, recip@example.com\n", t)
+ self.failUnlessIn("From: buildbot@example.com\n", t)
+ self.failUnlessIn("Subject: buildbot failure in PROJECT on builder1\n", t)
+ self.failUnlessIn("The Buildbot has detected a new failure", t)
+ self.failUnlessIn("BUILD FAILED: snarkleack polarization failed\n", t)
+ self.failUnlessEqual(set(r), set(["dev3@dev.com", "dev4@dev.com",
+ "recip2@example.com", "recip@example.com"]))
+
+ def testLogs(self):
+ basedir = "test_status_logs"
+ os.mkdir(basedir)
+ mailer = MyMailer(fromaddr="buildbot@example.com", addLogs=True,
+ extraRecipients=["recip@example.com",
+ "recip2@example.com"])
+ mailer.parent = self
+ mailer.status = self
+ self.messages = []
+
+ b1 = self.makeBuild(3, builder.WARNINGS)
+ b1.testlogs = [MyLog(basedir, 'compile', "Compile log here\n"),
+ MyLog(basedir,
+ 'test', "Test log here\nTest 4 failed\n"),
+ ]
+ b1.text = ["unusual", "gnarzzler", "output"]
+ mailer.buildFinished("builder1", b1, b1.results)
+ self.failUnless(len(self.messages) == 1)
+ m,r = self.messages.pop()
+ t = m.as_string()
+ self.failUnlessIn("Subject: buildbot warnings in PROJECT on builder1\n", t)
+ m2 = email.message_from_string(t)
+ p = m2.get_payload()
+ self.failUnlessEqual(len(p), 3)
+
+ self.failUnlessIn("Build Had Warnings: unusual gnarzzler output\n",
+ p[0].get_payload())
+
+ self.failUnlessEqual(p[1].get_filename(), "step.compile")
+ self.failUnlessEqual(p[1].get_payload(), "Compile log here\n")
+
+ self.failUnlessEqual(p[2].get_filename(), "step.test")
+ self.failUnlessIn("Test log here\n", p[2].get_payload())
+
+ def testMail(self):
+ basedir = "test_status_mail"
+ os.mkdir(basedir)
+ dest = os.environ.get("BUILDBOT_TEST_MAIL")
+ if not dest:
+ raise unittest.SkipTest("define BUILDBOT_TEST_MAIL=dest to run this")
+ mailer = mail.MailNotifier(fromaddr="buildbot@example.com",
+ addLogs=True,
+ extraRecipients=[dest])
+ s = MyStatus()
+ s.url = "project URL"
+ mailer.status = s
+
+ b1 = self.makeBuild(3, builder.SUCCESS)
+ b1.testlogs = [MyLog(basedir, 'compile', "Compile log here\n"),
+ MyLog(basedir,
+ 'test', "Test log here\nTest 4 failed\n"),
+ ]
+
+ d = mailer.buildFinished("builder1", b1, b1.results)
+ # When this fires, the mail has been sent, but the SMTP connection is
+ # still up (because smtp.sendmail relies upon the server to hang up).
+ # Spin for a moment to avoid the "unclean reactor" warning that Trial
+ # gives us if we finish before the socket is disconnected. Really,
+ # sendmail() ought to hang up the connection once it is finished:
+ # otherwise a malicious SMTP server could make us consume lots of
+ # memory.
+ d.addCallback(self.stall, 0.1)
+ return d
+
+if not mail:
+ Mail.skip = "the Twisted Mail package is not installed"
+
+class Progress(unittest.TestCase):
+ def testWavg(self):
+ bp = progress.BuildProgress([])
+ e = progress.Expectations(bp)
+ # wavg(old, current)
+ self.failUnlessEqual(e.wavg(None, None), None)
+ self.failUnlessEqual(e.wavg(None, 3), 3)
+ self.failUnlessEqual(e.wavg(3, None), 3)
+ self.failUnlessEqual(e.wavg(3, 4), 3.5)
+ e.decay = 0.1
+ self.failUnlessEqual(e.wavg(3, 4), 3.1)
+
+
+class Results(unittest.TestCase):
+
+ def testAddResults(self):
+ b = builder.BuildStatus(builder.BuilderStatus("test"), 12)
+ testname = ("buildbot", "test", "test_status", "Results",
+ "testAddResults")
+ r1 = builder.TestResult(name=testname,
+ results=builder.SUCCESS,
+ text=["passed"],
+ logs={'output': ""},
+ )
+ b.addTestResult(r1)
+
+ res = b.getTestResults()
+ self.failUnlessEqual(res.keys(), [testname])
+ t = res[testname]
+ self.failUnless(interfaces.ITestResult.providedBy(t))
+ self.failUnlessEqual(t.getName(), testname)
+ self.failUnlessEqual(t.getResults(), builder.SUCCESS)
+ self.failUnlessEqual(t.getText(), ["passed"])
+ self.failUnlessEqual(t.getLogs(), {'output': ""})
+
+class Log(unittest.TestCase):
+ def setUpClass(self):
+ self.basedir = "status_log_add"
+ os.mkdir(self.basedir)
+
+ def testAdd(self):
+ l = MyLog(self.basedir, "compile", step=13)
+ self.failUnlessEqual(l.getName(), "compile")
+ self.failUnlessEqual(l.getStep(), 13)
+ l.addHeader("HEADER\n")
+ l.addStdout("Some text\n")
+ l.addStderr("Some error\n")
+ l.addStdout("Some more text\n")
+ self.failIf(l.isFinished())
+ l.finish()
+ self.failUnless(l.isFinished())
+ self.failUnlessEqual(l.getText(),
+ "Some text\nSome error\nSome more text\n")
+ self.failUnlessEqual(l.getTextWithHeaders(),
+ "HEADER\n" +
+ "Some text\nSome error\nSome more text\n")
+ self.failUnlessEqual(len(list(l.getChunks())), 4)
+
+ self.failUnless(l.hasContents())
+ try:
+ os.unlink(l.getFilename())
+ except OSError:
+ os.unlink(l.getFilename() + ".bz2")
+ self.failIf(l.hasContents())
+
+ def TODO_testDuplicate(self):
+ # create multiple logs for the same step with the same logname, make
+ # sure their on-disk filenames are suitably uniquified. This
+ # functionality actually lives in BuildStepStatus and BuildStatus, so
+ # this test must involve more than just the MyLog class.
+
+ # naieve approach, doesn't work
+ l1 = MyLog(self.basedir, "duplicate")
+ l1.addStdout("Some text\n")
+ l1.finish()
+ l2 = MyLog(self.basedir, "duplicate")
+ l2.addStdout("Some more text\n")
+ l2.finish()
+ self.failIfEqual(l1.getFilename(), l2.getFilename())
+
+ def testMerge1(self):
+ l = MyLog(self.basedir, "merge1")
+ l.addHeader("HEADER\n")
+ l.addStdout("Some text\n")
+ l.addStdout("Some more text\n")
+ l.addStdout("more\n")
+ l.finish()
+ self.failUnlessEqual(l.getText(),
+ "Some text\nSome more text\nmore\n")
+ self.failUnlessEqual(l.getTextWithHeaders(),
+ "HEADER\n" +
+ "Some text\nSome more text\nmore\n")
+ self.failUnlessEqual(len(list(l.getChunks())), 2)
+
+ def testMerge2(self):
+ l = MyLog(self.basedir, "merge2")
+ l.addHeader("HEADER\n")
+ for i in xrange(1000):
+ l.addStdout("aaaa")
+ for i in xrange(30):
+ l.addStderr("bbbb")
+ for i in xrange(10):
+ l.addStdout("cc")
+ target = 1000*"aaaa" + 30 * "bbbb" + 10 * "cc"
+ self.failUnlessEqual(len(l.getText()), len(target))
+ self.failUnlessEqual(l.getText(), target)
+ l.finish()
+ self.failUnlessEqual(len(l.getText()), len(target))
+ self.failUnlessEqual(l.getText(), target)
+ self.failUnlessEqual(len(list(l.getChunks())), 4)
+
+ def testMerge3(self):
+ l = MyLog(self.basedir, "merge3")
+ l.chunkSize = 100
+ l.addHeader("HEADER\n")
+ for i in xrange(8):
+ l.addStdout(10*"a")
+ for i in xrange(8):
+ l.addStdout(10*"a")
+ self.failUnlessEqual(list(l.getChunks()),
+ [(builder.HEADER, "HEADER\n"),
+ (builder.STDOUT, 100*"a"),
+ (builder.STDOUT, 60*"a")])
+ l.finish()
+ self.failUnlessEqual(l.getText(), 160*"a")
+
+ def testReadlines(self):
+ l = MyLog(self.basedir, "chunks1")
+ l.addHeader("HEADER\n") # should be ignored
+ l.addStdout("Some text\n")
+ l.addStdout("Some More Text\nAnd Some More\n")
+ l.addStderr("Some Stderr\n")
+ l.addStdout("Last line\n")
+ l.finish()
+ alllines = list(l.readlines())
+ self.failUnlessEqual(len(alllines), 4)
+ self.failUnlessEqual(alllines[0], "Some text\n")
+ self.failUnlessEqual(alllines[2], "And Some More\n")
+ self.failUnlessEqual(alllines[3], "Last line\n")
+ stderr = list(l.readlines(interfaces.LOG_CHANNEL_STDERR))
+ self.failUnlessEqual(len(stderr), 1)
+ self.failUnlessEqual(stderr[0], "Some Stderr\n")
+ lines = l.readlines()
+ if False: # TODO: l.readlines() is not yet an iterator
+ # verify that it really is an iterator
+ line0 = lines.next()
+ self.failUnlessEqual(line0, "Some text\n")
+ line1 = lines.next()
+ line2 = lines.next()
+ self.failUnlessEqual(line2, "And Some More\n")
+
+
+ def testChunks(self):
+ l = MyLog(self.basedir, "chunks2")
+ c1 = l.getChunks()
+ l.addHeader("HEADER\n")
+ l.addStdout("Some text\n")
+ self.failUnlessEqual("".join(l.getChunks(onlyText=True)),
+ "HEADER\nSome text\n")
+ c2 = l.getChunks()
+
+ l.addStdout("Some more text\n")
+ self.failUnlessEqual("".join(l.getChunks(onlyText=True)),
+ "HEADER\nSome text\nSome more text\n")
+ c3 = l.getChunks()
+
+ l.addStdout("more\n")
+ l.finish()
+
+ self.failUnlessEqual(list(c1), [])
+ self.failUnlessEqual(list(c2), [(builder.HEADER, "HEADER\n"),
+ (builder.STDOUT, "Some text\n")])
+ self.failUnlessEqual(list(c3), [(builder.HEADER, "HEADER\n"),
+ (builder.STDOUT,
+ "Some text\nSome more text\n")])
+
+ self.failUnlessEqual(l.getText(),
+ "Some text\nSome more text\nmore\n")
+ self.failUnlessEqual(l.getTextWithHeaders(),
+ "HEADER\n" +
+ "Some text\nSome more text\nmore\n")
+ self.failUnlessEqual(len(list(l.getChunks())), 2)
+
+ def testUpgrade(self):
+ l = MyLog(self.basedir, "upgrade")
+ l.addHeader("HEADER\n")
+ l.addStdout("Some text\n")
+ l.addStdout("Some more text\n")
+ l.addStdout("more\n")
+ l.finish()
+ self.failUnless(l.hasContents())
+ # now doctor it to look like a 0.6.4-era non-upgraded logfile
+ l.entries = list(l.getChunks())
+ del l.filename
+ try:
+ os.unlink(l.getFilename() + ".bz2")
+ except OSError:
+ os.unlink(l.getFilename())
+ # now make sure we can upgrade it
+ l.upgrade("upgrade")
+ self.failUnlessEqual(l.getText(),
+ "Some text\nSome more text\nmore\n")
+ self.failUnlessEqual(len(list(l.getChunks())), 2)
+ self.failIf(l.entries)
+
+ # now, do it again, but make it look like an upgraded 0.6.4 logfile
+ # (i.e. l.filename is missing, but the contents are there on disk)
+ l.entries = list(l.getChunks())
+ del l.filename
+ l.upgrade("upgrade")
+ self.failUnlessEqual(l.getText(),
+ "Some text\nSome more text\nmore\n")
+ self.failUnlessEqual(len(list(l.getChunks())), 2)
+ self.failIf(l.entries)
+ self.failUnless(l.hasContents())
+
+ def testHTMLUpgrade(self):
+ l = MyHTMLLog(self.basedir, "upgrade", "log contents")
+ l.upgrade("filename")
+
+ def testSubscribe(self):
+ l1 = MyLog(self.basedir, "subscribe1")
+ l1.finish()
+ self.failUnless(l1.isFinished())
+
+ s = MyLogSubscriber()
+ l1.subscribe(s, True)
+ l1.unsubscribe(s)
+ self.failIf(s.chunks)
+
+ s = MyLogSubscriber()
+ l1.subscribe(s, False)
+ l1.unsubscribe(s)
+ self.failIf(s.chunks)
+
+ finished = []
+ l2 = MyLog(self.basedir, "subscribe2")
+ l2.waitUntilFinished().addCallback(finished.append)
+ l2.addHeader("HEADER\n")
+ s1 = MyLogSubscriber()
+ l2.subscribe(s1, True)
+ s2 = MyLogSubscriber()
+ l2.subscribe(s2, False)
+ self.failUnlessEqual(s1.chunks, [(builder.HEADER, "HEADER\n")])
+ self.failUnlessEqual(s2.chunks, [])
+
+ l2.addStdout("Some text\n")
+ self.failUnlessEqual(s1.chunks, [(builder.HEADER, "HEADER\n"),
+ (builder.STDOUT, "Some text\n")])
+ self.failUnlessEqual(s2.chunks, [(builder.STDOUT, "Some text\n")])
+ l2.unsubscribe(s1)
+
+ l2.addStdout("Some more text\n")
+ self.failUnlessEqual(s1.chunks, [(builder.HEADER, "HEADER\n"),
+ (builder.STDOUT, "Some text\n")])
+ self.failUnlessEqual(s2.chunks, [(builder.STDOUT, "Some text\n"),
+ (builder.STDOUT, "Some more text\n"),
+ ])
+ self.failIf(finished)
+ l2.finish()
+ self.failUnlessEqual(finished, [l2])
+
+ def testConsumer(self):
+ l1 = MyLog(self.basedir, "consumer1")
+ l1.finish()
+ self.failUnless(l1.isFinished())
+
+ s = MyLogConsumer()
+ d = l1.subscribeConsumer(s)
+ d.addCallback(self._testConsumer_1, s)
+ return d
+ testConsumer.timeout = 5
+ def _testConsumer_1(self, res, s):
+ self.failIf(s.chunks)
+ self.failUnless(s.finished)
+ self.failIf(s.producer) # producer should be registered and removed
+
+ l2 = MyLog(self.basedir, "consumer2")
+ l2.addHeader("HEADER\n")
+ l2.finish()
+ self.failUnless(l2.isFinished())
+
+ s = MyLogConsumer()
+ d = l2.subscribeConsumer(s)
+ d.addCallback(self._testConsumer_2, s)
+ return d
+ def _testConsumer_2(self, res, s):
+ self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n")])
+ self.failUnless(s.finished)
+ self.failIf(s.producer) # producer should be registered and removed
+
+
+ l2 = MyLog(self.basedir, "consumer3")
+ l2.chunkSize = 1000
+ l2.addHeader("HEADER\n")
+ l2.addStdout(800*"a")
+ l2.addStdout(800*"a") # should now have two chunks on disk, 1000+600
+ l2.addStdout(800*"b") # HEADER,1000+600*a on disk, 800*a in memory
+ l2.addStdout(800*"b") # HEADER,1000+600*a,1000+600*b on disk
+ l2.addStdout(200*"c") # HEADER,1000+600*a,1000+600*b on disk,
+ # 200*c in memory
+
+ s = MyLogConsumer(limit=1)
+ d = l2.subscribeConsumer(s)
+ d.addCallback(self._testConsumer_3, l2, s)
+ return d
+ def _testConsumer_3(self, res, l2, s):
+ self.failUnless(s.streaming)
+ self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n")])
+ s.limit = 1
+ d = s.producer.resumeProducing()
+ d.addCallback(self._testConsumer_4, l2, s)
+ return d
+ def _testConsumer_4(self, res, l2, s):
+ self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n"),
+ (builder.STDOUT, 1000*"a"),
+ ])
+ s.limit = None
+ d = s.producer.resumeProducing()
+ d.addCallback(self._testConsumer_5, l2, s)
+ return d
+ def _testConsumer_5(self, res, l2, s):
+ self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n"),
+ (builder.STDOUT, 1000*"a"),
+ (builder.STDOUT, 600*"a"),
+ (builder.STDOUT, 1000*"b"),
+ (builder.STDOUT, 600*"b"),
+ (builder.STDOUT, 200*"c")])
+ l2.addStdout(1000*"c") # HEADER,1600*a,1600*b,1200*c on disk
+ self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n"),
+ (builder.STDOUT, 1000*"a"),
+ (builder.STDOUT, 600*"a"),
+ (builder.STDOUT, 1000*"b"),
+ (builder.STDOUT, 600*"b"),
+ (builder.STDOUT, 200*"c"),
+ (builder.STDOUT, 1000*"c")])
+ l2.finish()
+ self.failUnlessEqual(s.chunks, [(builder.HEADER, "HEADER\n"),
+ (builder.STDOUT, 1000*"a"),
+ (builder.STDOUT, 600*"a"),
+ (builder.STDOUT, 1000*"b"),
+ (builder.STDOUT, 600*"b"),
+ (builder.STDOUT, 200*"c"),
+ (builder.STDOUT, 1000*"c")])
+ self.failIf(s.producer)
+ self.failUnless(s.finished)
+
+ def testLargeSummary(self):
+ bigtext = "a" * 200000 # exceed the NetstringReceiver 100KB limit
+ l = MyLog(self.basedir, "large", bigtext)
+ s = MyLogConsumer()
+ d = l.subscribeConsumer(s)
+ def _check(res):
+ for ctype,chunk in s.chunks:
+ self.failUnless(len(chunk) < 100000)
+ merged = "".join([c[1] for c in s.chunks])
+ self.failUnless(merged == bigtext)
+ d.addCallback(_check)
+ # when this fails, it fails with a timeout, and there is an exception
+ # sent to log.err(). This AttributeError exception is in
+ # NetstringReceiver.dataReceived where it does
+ # self.transport.loseConnection() because of the NetstringParseError,
+ # however self.transport is None
+ return d
+ testLargeSummary.timeout = 5
+
+
+class CompressLog(unittest.TestCase):
+ def testCompressLogs(self):
+ bss = setupBuildStepStatus("test-compress")
+ bss.build.builder.setLogCompressionLimit(1024)
+ l = bss.addLog('not-compress')
+ l.addStdout('a' * 512)
+ l.finish()
+ lc = bss.addLog('to-compress')
+ lc.addStdout('b' * 1024)
+ lc.finish()
+ d = bss.stepFinished(builder.SUCCESS)
+ self.failUnless(d is not None)
+ d.addCallback(self._verifyCompression, bss)
+ return d
+
+ def _verifyCompression(self, result, bss):
+ self.failUnless(len(bss.getLogs()), 2)
+ (ncl, cl) = bss.getLogs() # not compressed, compressed log
+ self.failUnless(os.path.isfile(ncl.getFilename()))
+ self.failIf(os.path.isfile(ncl.getFilename() + ".bz2"))
+ self.failIf(os.path.isfile(cl.getFilename()))
+ self.failUnless(os.path.isfile(cl.getFilename() + ".bz2"))
+ content = ncl.getText()
+ self.failUnless(len(content), 512)
+ content = cl.getText()
+ self.failUnless(len(content), 1024)
+ pass
+
+config_base = """
+from buildbot.process import factory
+from buildbot.steps import dummy
+from buildbot.buildslave import BuildSlave
+s = factory.s
+
+f1 = factory.QuickBuildFactory('fakerep', 'cvsmodule', configure=None)
+
+f2 = factory.BuildFactory([
+ s(dummy.Dummy, timeout=1),
+ s(dummy.RemoteDummy, timeout=2),
+ ])
+
+BuildmasterConfig = c = {}
+c['slaves'] = [BuildSlave('bot1', 'sekrit')]
+c['schedulers'] = []
+c['builders'] = []
+c['builders'].append({'name':'quick', 'slavename':'bot1',
+ 'builddir': 'quickdir', 'factory': f1})
+c['slavePortnum'] = 0
+"""
+
+config_2 = config_base + """
+c['builders'] = [{'name': 'dummy', 'slavename': 'bot1',
+ 'builddir': 'dummy1', 'factory': f2},
+ {'name': 'testdummy', 'slavename': 'bot1',
+ 'builddir': 'dummy2', 'factory': f2, 'category': 'test'}]
+"""
+
+class STarget(base.StatusReceiver):
+ debug = False
+
+ def __init__(self, mode):
+ self.mode = mode
+ self.events = []
+ def announce(self):
+ if self.debug:
+ print self.events[-1]
+
+ def builderAdded(self, name, builder):
+ self.events.append(("builderAdded", name, builder))
+ self.announce()
+ if "builder" in self.mode:
+ return self
+ def builderChangedState(self, name, state):
+ self.events.append(("builderChangedState", name, state))
+ self.announce()
+ def buildStarted(self, name, build):
+ self.events.append(("buildStarted", name, build))
+ self.announce()
+ if "eta" in self.mode:
+ self.eta_build = build.getETA()
+ if "build" in self.mode:
+ return self
+ def buildETAUpdate(self, build, ETA):
+ self.events.append(("buildETAUpdate", build, ETA))
+ self.announce()
+ def stepStarted(self, build, step):
+ self.events.append(("stepStarted", build, step))
+ self.announce()
+ if 0 and "eta" in self.mode:
+ print "TIMES", step.getTimes()
+ print "ETA", step.getETA()
+ print "EXP", step.getExpectations()
+ if "step" in self.mode:
+ return self
+ def stepTextChanged(self, build, step, text):
+ self.events.append(("stepTextChanged", step, text))
+ def stepText2Changed(self, build, step, text2):
+ self.events.append(("stepText2Changed", step, text2))
+ def stepETAUpdate(self, build, step, ETA, expectations):
+ self.events.append(("stepETAUpdate", build, step, ETA, expectations))
+ self.announce()
+ def logStarted(self, build, step, log):
+ self.events.append(("logStarted", build, step, log))
+ self.announce()
+ def logFinished(self, build, step, log):
+ self.events.append(("logFinished", build, step, log))
+ self.announce()
+ def stepFinished(self, build, step, results):
+ self.events.append(("stepFinished", build, step, results))
+ if 0 and "eta" in self.mode:
+ print "post-EXP", step.getExpectations()
+ self.announce()
+ def buildFinished(self, name, build, results):
+ self.events.append(("buildFinished", name, build, results))
+ self.announce()
+ def builderRemoved(self, name):
+ self.events.append(("builderRemoved", name))
+ self.announce()
+
+class Subscription(RunMixin, unittest.TestCase):
+ # verify that StatusTargets can subscribe/unsubscribe properly
+
+ def testSlave(self):
+ m = self.master
+ s = m.getStatus()
+ self.t1 = t1 = STarget(["builder"])
+ #t1.debug = True; print
+ s.subscribe(t1)
+ self.failUnlessEqual(len(t1.events), 0)
+
+ self.t3 = t3 = STarget(["builder", "build", "step"])
+ s.subscribe(t3)
+
+ m.loadConfig(config_2)
+ m.readConfig = True
+ m.startService()
+
+ self.failUnlessEqual(len(t1.events), 4)
+ self.failUnlessEqual(t1.events[0][0:2], ("builderAdded", "dummy"))
+ self.failUnlessEqual(t1.events[1],
+ ("builderChangedState", "dummy", "offline"))
+ self.failUnlessEqual(t1.events[2][0:2], ("builderAdded", "testdummy"))
+ self.failUnlessEqual(t1.events[3],
+ ("builderChangedState", "testdummy", "offline"))
+ t1.events = []
+
+ self.failUnlessEqual(s.getBuilderNames(), ["dummy", "testdummy"])
+ self.failUnlessEqual(s.getBuilderNames(categories=['test']),
+ ["testdummy"])
+ self.s1 = s1 = s.getBuilder("dummy")
+ self.failUnlessEqual(s1.getName(), "dummy")
+ self.failUnlessEqual(s1.getState(), ("offline", []))
+ self.failUnlessEqual(s1.getCurrentBuilds(), [])
+ self.failUnlessEqual(s1.getLastFinishedBuild(), None)
+ self.failUnlessEqual(s1.getBuild(-1), None)
+ #self.failUnlessEqual(s1.getEvent(-1), foo("created"))
+
+ # status targets should, upon being subscribed, immediately get a
+ # list of all current builders matching their category
+ self.t2 = t2 = STarget([])
+ s.subscribe(t2)
+ self.failUnlessEqual(len(t2.events), 2)
+ self.failUnlessEqual(t2.events[0][0:2], ("builderAdded", "dummy"))
+ self.failUnlessEqual(t2.events[1][0:2], ("builderAdded", "testdummy"))
+
+ d = self.connectSlave(builders=["dummy", "testdummy"])
+ d.addCallback(self._testSlave_1, t1)
+ return d
+
+ def _testSlave_1(self, res, t1):
+ self.failUnlessEqual(len(t1.events), 2)
+ self.failUnlessEqual(t1.events[0],
+ ("builderChangedState", "dummy", "idle"))
+ self.failUnlessEqual(t1.events[1],
+ ("builderChangedState", "testdummy", "idle"))
+ t1.events = []
+
+ c = interfaces.IControl(self.master)
+ req = BuildRequest("forced build for testing", SourceStamp(), 'test_builder')
+ c.getBuilder("dummy").requestBuild(req)
+ d = req.waitUntilFinished()
+ d2 = self.master.botmaster.waitUntilBuilderIdle("dummy")
+ dl = defer.DeferredList([d, d2])
+ dl.addCallback(self._testSlave_2)
+ return dl
+
+ def _testSlave_2(self, res):
+ # t1 subscribes to builds, but not anything lower-level
+ ev = self.t1.events
+ self.failUnlessEqual(len(ev), 4)
+ self.failUnlessEqual(ev[0][0:3],
+ ("builderChangedState", "dummy", "building"))
+ self.failUnlessEqual(ev[1][0], "buildStarted")
+ self.failUnlessEqual(ev[2][0:2]+ev[2][3:4],
+ ("buildFinished", "dummy", builder.SUCCESS))
+ self.failUnlessEqual(ev[3][0:3],
+ ("builderChangedState", "dummy", "idle"))
+
+ self.failUnlessEqual([ev[0] for ev in self.t3.events],
+ ["builderAdded",
+ "builderChangedState", # offline
+ "builderAdded",
+ "builderChangedState", # idle
+ "builderChangedState", # offline
+ "builderChangedState", # idle
+ "builderChangedState", # building
+ "buildStarted",
+ "stepStarted", "stepETAUpdate",
+ "stepTextChanged", "stepFinished",
+ "stepStarted", "stepETAUpdate",
+ "stepTextChanged", "logStarted", "logFinished",
+ "stepTextChanged", "stepText2Changed",
+ "stepFinished",
+ "buildFinished",
+ "builderChangedState", # idle
+ ])
+
+ b = self.s1.getLastFinishedBuild()
+ self.failUnless(b)
+ self.failUnlessEqual(b.getBuilder().getName(), "dummy")
+ self.failUnlessEqual(b.getNumber(), 0)
+ self.failUnlessEqual(b.getSourceStamp().branch, None)
+ self.failUnlessEqual(b.getSourceStamp().patch, None)
+ self.failUnlessEqual(b.getSourceStamp().revision, None)
+ self.failUnlessEqual(b.getReason(), "forced build for testing")
+ self.failUnlessEqual(b.getChanges(), ())
+ self.failUnlessEqual(b.getResponsibleUsers(), [])
+ self.failUnless(b.isFinished())
+ self.failUnlessEqual(b.getText(), ['build', 'successful'])
+ self.failUnlessEqual(b.getResults(), builder.SUCCESS)
+
+ steps = b.getSteps()
+ self.failUnlessEqual(len(steps), 2)
+
+ eta = 0
+ st1 = steps[0]
+ self.failUnlessEqual(st1.getName(), "dummy")
+ self.failUnless(st1.isFinished())
+ self.failUnlessEqual(st1.getText(), ["delay", "1 secs"])
+ start,finish = st1.getTimes()
+ self.failUnless(0.5 < (finish-start) < 10)
+ self.failUnlessEqual(st1.getExpectations(), [])
+ self.failUnlessEqual(st1.getLogs(), [])
+ eta += finish-start
+
+ st2 = steps[1]
+ self.failUnlessEqual(st2.getName(), "remote dummy")
+ self.failUnless(st2.isFinished())
+ self.failUnlessEqual(st2.getText(),
+ ["remote", "delay", "2 secs"])
+ start,finish = st2.getTimes()
+ self.failUnless(1.5 < (finish-start) < 10)
+ eta += finish-start
+ self.failUnlessEqual(st2.getExpectations(), [('output', 38, None)])
+ logs = st2.getLogs()
+ self.failUnlessEqual(len(logs), 1)
+ self.failUnlessEqual(logs[0].getName(), "stdio")
+ self.failUnlessEqual(logs[0].getText(), "data")
+
+ self.eta = eta
+ # now we run it a second time, and we should have an ETA
+
+ self.t4 = t4 = STarget(["builder", "build", "eta"])
+ self.master.getStatus().subscribe(t4)
+ c = interfaces.IControl(self.master)
+ req = BuildRequest("forced build for testing", SourceStamp(), 'test_builder')
+ c.getBuilder("dummy").requestBuild(req)
+ d = req.waitUntilFinished()
+ d2 = self.master.botmaster.waitUntilBuilderIdle("dummy")
+ dl = defer.DeferredList([d, d2])
+ dl.addCallback(self._testSlave_3)
+ return dl
+
+ def _testSlave_3(self, res):
+ t4 = self.t4
+ eta = self.eta
+ self.failUnless(eta-1 < t4.eta_build < eta+1, # should be 3 seconds
+ "t4.eta_build was %g, not in (%g,%g)"
+ % (t4.eta_build, eta-1, eta+1))
+
+
+class Client(unittest.TestCase):
+ def testAdaptation(self):
+ b = builder.BuilderStatus("bname")
+ b2 = client.makeRemote(b)
+ self.failUnless(isinstance(b2, client.RemoteBuilder))
+ b3 = client.makeRemote(None)
+ self.failUnless(b3 is None)
+
+
+class ContactTester(unittest.TestCase):
+ def test_notify_invalid_syntax(self):
+ irc = MyContact()
+ self.assertRaises(words.UsageError, lambda args, who: irc.command_NOTIFY(args, who), "", "mynick")
+
+ def test_notify_list(self):
+ irc = MyContact()
+ irc.command_NOTIFY("list", "mynick")
+ self.failUnlessEqual(irc.message, "The following events are being notified: []", "empty notify list")
+
+ irc.message = ""
+ irc.command_NOTIFY("on started", "mynick")
+ self.failUnlessEqual(irc.message, "The following events are being notified: ['started']", "on started")
+
+ irc.message = ""
+ irc.command_NOTIFY("on finished", "mynick")
+ self.failUnlessEqual(irc.message, "The following events are being notified: ['started', 'finished']", "on finished")
+
+ irc.message = ""
+ irc.command_NOTIFY("off", "mynick")
+ self.failUnlessEqual(irc.message, "The following events are being notified: []", "off all")
+
+ irc.message = ""
+ irc.command_NOTIFY("on", "mynick")
+ self.failUnlessEqual(irc.message, "The following events are being notified: ['started', 'finished']", "on default set")
+
+ irc.message = ""
+ irc.command_NOTIFY("off started", "mynick")
+ self.failUnlessEqual(irc.message, "The following events are being notified: ['finished']", "off started")
+
+ irc.message = ""
+ irc.command_NOTIFY("on success failure exception", "mynick")
+ self.failUnlessEqual(irc.message, "The following events are being notified: ['failure', 'finished', 'exception', 'success']", "on multiple events")
+
+ def test_notification_default(self):
+ irc = MyContact()
+
+ my_builder = MyBuilder("builder78")
+ my_build = MyIrcBuild(my_builder, 23, builder.SUCCESS)
+
+ irc.buildStarted(my_builder.getName(), my_build)
+ self.failUnlessEqual(irc.message, "", "No notification with default settings")
+
+ irc.buildFinished(my_builder.getName(), my_build, None)
+ self.failUnlessEqual(irc.message, "", "No notification with default settings")
+
+ def test_notification_started(self):
+ irc = MyContact()
+
+ my_builder = MyBuilder("builder78")
+ my_build = MyIrcBuild(my_builder, 23, builder.SUCCESS)
+ my_build.changes = (
+ Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 123),
+ Change(who = 'author2', files = ['file2'], comments = 'comment2', revision = 456),
+ )
+
+ irc.command_NOTIFY("on started", "mynick")
+
+ irc.message = ""
+ irc.buildStarted(my_builder.getName(), my_build)
+ self.failUnlessEqual(irc.message, "build #23 of builder78 started including [123, 456]", "Start notification generated with notify_events=['started']")
+
+ irc.message = ""
+ irc.buildFinished(my_builder.getName(), my_build, None)
+ self.failUnlessEqual(irc.message, "", "No finished notification with notify_events=['started']")
+
+ def test_notification_finished(self):
+ irc = MyContact()
+
+ my_builder = MyBuilder("builder834")
+ my_build = MyIrcBuild(my_builder, 862, builder.SUCCESS)
+ my_build.changes = (
+ Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
+ )
+
+ irc.command_NOTIFY("on finished", "mynick")
+
+ irc.message = ""
+ irc.buildStarted(my_builder.getName(), my_build)
+ self.failUnlessEqual(irc.message, "", "No started notification with notify_events=['finished']")
+
+ irc.message = ""
+ irc.buildFinished(my_builder.getName(), my_build, None)
+ 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']")
+
+ def test_notification_success(self):
+ irc = MyContact()
+
+ my_builder = MyBuilder("builder834")
+ my_build = MyIrcBuild(my_builder, 862, builder.SUCCESS)
+ my_build.changes = (
+ Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
+ )
+
+ irc.command_NOTIFY("on success", "mynick")
+
+ irc.message = ""
+ irc.buildStarted(my_builder.getName(), my_build)
+ self.failUnlessEqual(irc.message, "", "No started notification with notify_events=['success']")
+
+ irc.message = ""
+ irc.buildFinished(my_builder.getName(), my_build, None)
+ 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']")
+
+ irc.message = ""
+ my_build.results = builder.FAILURE
+ irc.buildFinished(my_builder.getName(), my_build, None)
+ self.failUnlessEqual(irc.message, "", "No finish notification generated on failure with notify_events=['success']")
+
+ irc.message = ""
+ my_build.results = builder.EXCEPTION
+ irc.buildFinished(my_builder.getName(), my_build, None)
+ self.failUnlessEqual(irc.message, "", "No finish notification generated on exception with notify_events=['success']")
+
+ def test_notification_failed(self):
+ irc = MyContact()
+
+ my_builder = MyBuilder("builder834")
+ my_build = MyIrcBuild(my_builder, 862, builder.FAILURE)
+ my_build.changes = (
+ Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
+ )
+
+ irc.command_NOTIFY("on failure", "mynick")
+
+ irc.message = ""
+ irc.buildStarted(my_builder.getName(), my_build)
+ self.failUnlessEqual(irc.message, "", "No started notification with notify_events=['failed']")
+
+ irc.message = ""
+ irc.buildFinished(my_builder.getName(), my_build, None)
+ 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']")
+
+ irc.message = ""
+ my_build.results = builder.SUCCESS
+ irc.buildFinished(my_builder.getName(), my_build, None)
+ self.failUnlessEqual(irc.message, "", "No finish notification generated on success with notify_events=['failed']")
+
+ irc.message = ""
+ my_build.results = builder.EXCEPTION
+ irc.buildFinished(my_builder.getName(), my_build, None)
+ self.failUnlessEqual(irc.message, "", "No finish notification generated on exception with notify_events=['failed']")
+
+ def test_notification_exception(self):
+ irc = MyContact()
+
+ my_builder = MyBuilder("builder834")
+ my_build = MyIrcBuild(my_builder, 862, builder.EXCEPTION)
+ my_build.changes = (
+ Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
+ )
+
+ irc.command_NOTIFY("on exception", "mynick")
+
+ irc.message = ""
+ irc.buildStarted(my_builder.getName(), my_build)
+ self.failUnlessEqual(irc.message, "", "No started notification with notify_events=['exception']")
+
+ irc.message = ""
+ irc.buildFinished(my_builder.getName(), my_build, None)
+ 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']")
+
+ irc.message = ""
+ my_build.results = builder.SUCCESS
+ irc.buildFinished(my_builder.getName(), my_build, None)
+ self.failUnlessEqual(irc.message, "", "No finish notification generated on success with notify_events=['exception']")
+
+ irc.message = ""
+ my_build.results = builder.FAILURE
+ irc.buildFinished(my_builder.getName(), my_build, None)
+ self.failUnlessEqual(irc.message, "", "No finish notification generated on exception with notify_events=['exception']")
+
+ def do_x_to_y_notification_test(self, notify, previous_result, new_result, expected_msg):
+ irc = MyContact()
+ irc.command_NOTIFY("on %s" % notify, "mynick")
+
+ my_builder = MyBuilder("builder834")
+ my_build = MyIrcBuild(my_builder, 862, builder.FAILURE)
+ my_build.changes = (
+ Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
+ )
+
+ previous_build = MyIrcBuild(my_builder, 861, previous_result)
+ my_build.setPreviousBuild(previous_build)
+
+ irc.message = ""
+ my_build.results = new_result
+ irc.buildFinished(my_builder.getName(), my_build, None)
+ self.failUnlessEqual(irc.message, expected_msg, "Finish notification generated on failure with notify_events=['successToFailure']")
+
+ def test_notification_successToFailure(self):
+ self.do_x_to_y_notification_test(notify="successToFailure", previous_result=builder.SUCCESS, new_result=builder.FAILURE,
+ expected_msg="build #862 of builder834 is complete: Failure [step1 step2] Build details are at http://myserver/mypath?build=765" )
+
+ self.do_x_to_y_notification_test(notify="successToFailure", previous_result=builder.SUCCESS, new_result=builder.SUCCESS,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="successToFailure", previous_result=builder.SUCCESS, new_result=builder.WARNINGS,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="successToFailure", previous_result=builder.SUCCESS, new_result=builder.EXCEPTION,
+ expected_msg = "" )
+
+ def test_notification_successToWarnings(self):
+ self.do_x_to_y_notification_test(notify="successToWarnings", previous_result=builder.SUCCESS, new_result=builder.WARNINGS,
+ expected_msg="build #862 of builder834 is complete: Warnings [step1 step2] Build details are at http://myserver/mypath?build=765" )
+
+ self.do_x_to_y_notification_test(notify="successToWarnings", previous_result=builder.SUCCESS, new_result=builder.SUCCESS,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="successToWarnings", previous_result=builder.SUCCESS, new_result=builder.FAILURE,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="successToWarnings", previous_result=builder.SUCCESS, new_result=builder.EXCEPTION,
+ expected_msg = "" )
+
+ def test_notification_successToException(self):
+ self.do_x_to_y_notification_test(notify="successToException", previous_result=builder.SUCCESS, new_result=builder.EXCEPTION,
+ expected_msg="build #862 of builder834 is complete: Exception [step1 step2] Build details are at http://myserver/mypath?build=765" )
+
+ self.do_x_to_y_notification_test(notify="successToException", previous_result=builder.SUCCESS, new_result=builder.SUCCESS,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="successToException", previous_result=builder.SUCCESS, new_result=builder.FAILURE,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="successToException", previous_result=builder.SUCCESS, new_result=builder.WARNINGS,
+ expected_msg = "" )
+
+
+
+
+
+ def test_notification_failureToSuccess(self):
+ self.do_x_to_y_notification_test(notify="failureToSuccess", previous_result=builder.FAILURE,new_result=builder.SUCCESS,
+ expected_msg="build #862 of builder834 is complete: Success [step1 step2] Build details are at http://myserver/mypath?build=765" )
+
+ self.do_x_to_y_notification_test(notify="failureToSuccess", previous_result=builder.FAILURE,new_result=builder.FAILURE,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="failureToSuccess", previous_result=builder.FAILURE,new_result=builder.WARNINGS,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="failureToSuccess", previous_result=builder.FAILURE,new_result=builder.EXCEPTION,
+ expected_msg = "" )
+
+ def test_notification_failureToWarnings(self):
+ self.do_x_to_y_notification_test(notify="failureToWarnings", previous_result=builder.FAILURE, new_result=builder.WARNINGS,
+ expected_msg="build #862 of builder834 is complete: Warnings [step1 step2] Build details are at http://myserver/mypath?build=765" )
+
+ self.do_x_to_y_notification_test(notify="failureToWarnings", previous_result=builder.FAILURE, new_result=builder.SUCCESS,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="failureToWarnings", previous_result=builder.FAILURE, new_result=builder.FAILURE,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="failureToWarnings", previous_result=builder.FAILURE, new_result=builder.EXCEPTION,
+ expected_msg = "" )
+
+ def test_notification_failureToException(self):
+ self.do_x_to_y_notification_test(notify="failureToException", previous_result=builder.FAILURE, new_result=builder.EXCEPTION,
+ expected_msg="build #862 of builder834 is complete: Exception [step1 step2] Build details are at http://myserver/mypath?build=765" )
+
+ self.do_x_to_y_notification_test(notify="failureToException", previous_result=builder.FAILURE, new_result=builder.SUCCESS,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="failureToException", previous_result=builder.FAILURE, new_result=builder.FAILURE,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="failureToException", previous_result=builder.FAILURE, new_result=builder.WARNINGS,
+ expected_msg = "" )
+
+
+
+
+
+ def test_notification_warningsToFailure(self):
+ self.do_x_to_y_notification_test(notify="warningsToFailure", previous_result=builder.WARNINGS, new_result=builder.FAILURE,
+ expected_msg="build #862 of builder834 is complete: Failure [step1 step2] Build details are at http://myserver/mypath?build=765" )
+
+ self.do_x_to_y_notification_test(notify="warningsToFailure", previous_result=builder.WARNINGS, new_result=builder.SUCCESS,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="warningsToFailure", previous_result=builder.WARNINGS, new_result=builder.WARNINGS,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="warningsToFailure", previous_result=builder.WARNINGS, new_result=builder.EXCEPTION,
+ expected_msg = "" )
+
+ def test_notification_warningsToSuccess(self):
+ self.do_x_to_y_notification_test(notify="warningsToSuccess", previous_result=builder.WARNINGS, new_result=builder.SUCCESS,
+ expected_msg="build #862 of builder834 is complete: Success [step1 step2] Build details are at http://myserver/mypath?build=765" )
+
+ self.do_x_to_y_notification_test(notify="warningsToSuccess", previous_result=builder.WARNINGS, new_result=builder.WARNINGS,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="warningsToSuccess", previous_result=builder.WARNINGS, new_result=builder.FAILURE,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="warningsToSuccess", previous_result=builder.WARNINGS, new_result=builder.EXCEPTION,
+ expected_msg = "" )
+
+ def test_notification_warningsToException(self):
+ self.do_x_to_y_notification_test(notify="warningsToException", previous_result=builder.WARNINGS, new_result=builder.EXCEPTION,
+ expected_msg="build #862 of builder834 is complete: Exception [step1 step2] Build details are at http://myserver/mypath?build=765" )
+
+ self.do_x_to_y_notification_test(notify="warningsToException", previous_result=builder.WARNINGS, new_result=builder.SUCCESS,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="warningsToException", previous_result=builder.WARNINGS, new_result=builder.FAILURE,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="warningsToException", previous_result=builder.WARNINGS, new_result=builder.WARNINGS,
+ expected_msg = "" )
+
+
+
+
+ def test_notification_exceptionToFailure(self):
+ self.do_x_to_y_notification_test(notify="exceptionToFailure", previous_result=builder.EXCEPTION, new_result=builder.FAILURE,
+ expected_msg="build #862 of builder834 is complete: Failure [step1 step2] Build details are at http://myserver/mypath?build=765" )
+
+ self.do_x_to_y_notification_test(notify="exceptionToFailure", previous_result=builder.EXCEPTION, new_result=builder.SUCCESS,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="exceptionToFailure", previous_result=builder.EXCEPTION, new_result=builder.WARNINGS,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="exceptionToFailure", previous_result=builder.EXCEPTION, new_result=builder.EXCEPTION,
+ expected_msg = "" )
+
+ def test_notification_exceptionToWarnings(self):
+ self.do_x_to_y_notification_test(notify="exceptionToWarnings", previous_result=builder.EXCEPTION, new_result=builder.WARNINGS,
+ expected_msg="build #862 of builder834 is complete: Warnings [step1 step2] Build details are at http://myserver/mypath?build=765" )
+
+ self.do_x_to_y_notification_test(notify="exceptionToWarnings", previous_result=builder.EXCEPTION, new_result=builder.SUCCESS,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="exceptionToWarnings", previous_result=builder.EXCEPTION, new_result=builder.FAILURE,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="exceptionToWarnings", previous_result=builder.EXCEPTION, new_result=builder.EXCEPTION,
+ expected_msg = "" )
+
+ def test_notification_exceptionToSuccess(self):
+ self.do_x_to_y_notification_test(notify="exceptionToSuccess", previous_result=builder.EXCEPTION, new_result=builder.SUCCESS,
+ expected_msg="build #862 of builder834 is complete: Success [step1 step2] Build details are at http://myserver/mypath?build=765" )
+
+ self.do_x_to_y_notification_test(notify="exceptionToSuccess", previous_result=builder.EXCEPTION, new_result=builder.EXCEPTION,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="exceptionToSuccess", previous_result=builder.EXCEPTION, new_result=builder.FAILURE,
+ expected_msg = "" )
+
+ self.do_x_to_y_notification_test(notify="exceptionToSuccess", previous_result=builder.EXCEPTION, new_result=builder.WARNINGS,
+ expected_msg = "" )
+
+ def test_notification_set_in_config(self):
+ irc = MyContact(channel = MyChannel(notify_events = {'success': 1}))
+
+ my_builder = MyBuilder("builder834")
+ my_build = MyIrcBuild(my_builder, 862, builder.SUCCESS)
+ my_build.changes = (
+ Change(who = 'author1', files = ['file1'], comments = 'comment1', revision = 943),
+ )
+
+ irc.message = ""
+ irc.buildFinished(my_builder.getName(), my_build, None)
+ 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']")
+
+class MyIrcBuild(builder.BuildStatus):
+ results = None
+
+ def __init__(self, parent, number, results):
+ builder.BuildStatus.__init__(self, parent, number)
+ self.results = results
+ self.previousBuild = None
+
+ def getResults(self):
+ return self.results
+
+ def getText(self):
+ return ('step1', 'step2')
+
+ def setPreviousBuild(self, pb):
+ self.previousBuild = pb
+
+ def getPreviousBuild(self):
+ return self.previousBuild
+
+class URLProducer:
+ def getURLForThing(self, build):
+ return 'http://myserver/mypath?build=765'
+
+class MyChannel:
+ categories = None
+ status = URLProducer()
+ notify_events = {}
+
+ def __init__(self, notify_events = {}):
+ self.notify_events = notify_events
+
+class MyContact(words.Contact):
+ message = ""
+
+ def __init__(self, channel = MyChannel()):
+ words.Contact.__init__(self, channel)
+ self.message = ""
+
+ def subscribe_to_build_events(self):
+ pass
+
+ def unsubscribe_from_build_events(self):
+ pass
+
+ def send(self, msg):
+ self.message += msg
+
+class StepStatistics(unittest.TestCase):
+ def testStepStatistics(self):
+ status = builder.BuildStatus(builder.BuilderStatus("test"), 123)
+ status.addStepWithName('step1')
+ status.addStepWithName('step2')
+ status.addStepWithName('step3')
+ status.addStepWithName('step4')
+
+ steps = status.getSteps()
+ (step1, step2, step3, step4) = steps
+
+ step1.setStatistic('test-prop', 1)
+ step3.setStatistic('test-prop', 2)
+ step4.setStatistic('test-prop', 4)
+
+ step1.setStatistic('other-prop', 27)
+ # Just to have some other properties around
+
+ self.failUnlessEqual(step1.getStatistic('test-prop'), 1,
+ 'Retrieve an existing property')
+ self.failUnlessEqual(step1.getStatistic('test-prop', 99), 1,
+ "Don't default an existing property")
+ self.failUnlessEqual(step2.getStatistic('test-prop', 99), 99,
+ 'Default a non-existant property')
+
+ self.failUnlessEqual(
+ status.getSummaryStatistic('test-prop', operator.add), 7,
+ 'Sum property across the build')
+
+ self.failUnlessEqual(
+ status.getSummaryStatistic('test-prop', operator.add, 13), 20,
+ 'Sum property across the build with initial value')
+
+class BuildExpectation(unittest.TestCase):
+ class MyBuilderStatus:
+ implements(interfaces.IBuilderStatus)
+
+ def setSlavenames(self, slaveName):
+ pass
+
+ class MyBuilder(Builder):
+ def __init__(self, name):
+ Builder.__init__(self, {
+ 'name': name,
+ 'builddir': '/tmp/somewhere',
+ 'factory': 'aFactory'
+ }, BuildExpectation.MyBuilderStatus())
+
+ class MyBuild(Build):
+ def __init__(self, b):
+ self.builder = b
+ self.remote = None
+
+ step1_progress = progress.StepProgress('step1', ['elapsed'])
+ self.progress = progress.BuildProgress([step1_progress])
+ step1_progress.setBuildProgress(self.progress)
+
+ step1_progress.start()
+ sleep(1);
+ step1_progress.finish()
+
+ self.deferred = defer.Deferred()
+ self.locks = []
+ self.build_status = builder.BuildStatus(b.builder_status, 1)
+
+
+ def testBuildExpectation_BuildSuccess(self):
+ b = BuildExpectation.MyBuilder("builder1")
+ build = BuildExpectation.MyBuild(b)
+
+ build.buildFinished(['sometext'], builder.SUCCESS)
+ self.failIfEqual(b.expectations.expectedBuildTime(), 0, 'Non-Zero expectation for a failed build')
+
+ def testBuildExpectation_BuildFailure(self):
+ b = BuildExpectation.MyBuilder("builder1")
+ build = BuildExpectation.MyBuild(b)
+
+ build.buildFinished(['sometext'], builder.FAILURE)
+ self.failUnlessEqual(b.expectations, None, 'Zero expectation for a failed build')