1 # -*- test-case-name: buildbot.test.test_steps -*-
3 # create the BuildStep with a fake .remote instance that logs the
4 # .callRemote invocations and compares them against the expected calls. Then
5 # the test harness should send statusUpdate() messages in with assorted
6 # data, eventually calling remote_complete(). Then we can verify that the
7 # Step's rc was correct, and that the status it was supposed to return
10 # sometimes, .callRemote should raise an exception because of a stale
11 # reference. Sometimes it should errBack with an UnknownCommand failure.
14 # todo: test batched updates, by invoking remote_update(updates) instead of
15 # statusUpdate(update). Also involves interrupted builds.
19 from twisted
.trial
import unittest
20 from twisted
.internet
import reactor
, defer
22 from buildbot
.sourcestamp
import SourceStamp
23 from buildbot
.process
import buildstep
, base
, factory
24 from buildbot
.steps
import shell
, source
, python
25 from buildbot
.status
import builder
26 from buildbot
.status
.builder
import SUCCESS
, FAILURE
27 from buildbot
.test
.runutils
import RunMixin
, rmtree
28 from buildbot
.test
.runutils
import makeBuildStep
, StepTester
29 from buildbot
.slave
import commands
, registry
32 class MyShellCommand(shell
.ShellCommand
):
34 def runCommand(self
, c
):
37 return shell
.ShellCommand
.runCommand(self
, c
)
44 class FakeSlaveBuilder
:
45 def getSlaveCommandVersion(self
, command
, oldversion
=None):
52 #self.callRemoteNotifier = None
53 def callRemote(self
, methname
, *args
):
54 event
= ["callRemote", methname
, args
]
55 self
.events
.append(event
)
56 ## if self.callRemoteNotifier:
57 ## reactor.callLater(0, self.callRemoteNotifier, event)
59 self
.deferred
= defer
.Deferred()
61 def notifyOnDisconnect(self
, callback
):
63 def dontNotifyOnDisconnect(self
, callback
):
67 class BuildStep(unittest
.TestCase
):
71 self
.builder
= FakeBuilder()
72 self
.builder_status
= builder
.BuilderStatus("fakebuilder")
73 self
.builder_status
.basedir
= "test_steps"
74 self
.builder_status
.nextBuildNumber
= 0
75 os
.mkdir(self
.builder_status
.basedir
)
76 self
.build_status
= self
.builder_status
.newBuild()
77 req
= base
.BuildRequest("reason", SourceStamp())
78 self
.build
= base
.Build([req
])
79 self
.build
.build_status
= self
.build_status
# fake it
80 self
.build
.builder
= self
.builder
81 self
.build
.slavebuilder
= FakeSlaveBuilder()
82 self
.remote
= FakeRemote()
85 def callback(self
, results
):
88 self
.results
= results
90 def errback(self
, failure
):
92 self
.failure
= failure
96 def testShellCommand1(self
):
99 self
.expectedEvents
= []
100 buildstep
.RemoteCommand
.commandCounter
[0] = 3
101 c
= MyShellCommand(workdir
=dir, command
=cmd
, timeout
=10)
102 c
.setBuild(self
.build
)
103 self
.assertEqual(self
.remote
.events
, self
.expectedEvents
)
104 c
.step_status
= self
.build_status
.addStepWithName("myshellcommand")
105 d
= c
.startStep(self
.remote
)
106 self
.failUnless(c
.started
)
107 d
.addCallbacks(self
.callback
, self
.errback
)
109 d2
.addCallback(self
._testShellCommand
1_2, c
)
111 testShellCommand1
.timeout
= 10
113 def poll(self
, ignored
=None):
114 # TODO: This is gross, but at least it's no longer using
115 # reactor.iterate() . Still, get rid of this some day soon.
116 if self
.remote
.remoteCalls
== 0:
118 d
.addCallback(self
.poll
)
119 reactor
.callLater(0.1, d
.callback
, None)
121 return defer
.succeed(None)
123 def _testShellCommand1_2(self
, res
, c
):
125 self
.expectedEvents
.append(["callRemote", "startCommand",
128 {'command': "argle bargle",
135 self
.assertEqual(self
.remote
.events
, self
.expectedEvents
)
137 # we could do self.remote.deferred.errback(UnknownCommand) here. We
138 # could also do .callback(), but generally the master end silently
139 # ignores the slave's ack
141 logs
= c
.step_status
.getLogs()
143 if log
.getName() == "log":
146 rc
.remoteUpdate({'header':
147 "command 'argle bargle' in dir 'murkle'\n\n"})
148 rc
.remoteUpdate({'stdout': "foo\n"})
149 self
.assertEqual(log
.getText(), "foo\n")
150 self
.assertEqual(log
.getTextWithHeaders(),
151 "command 'argle bargle' in dir 'murkle'\n\n"
153 rc
.remoteUpdate({'stderr': "bar\n"})
154 self
.assertEqual(log
.getText(), "foo\nbar\n")
155 self
.assertEqual(log
.getTextWithHeaders(),
156 "command 'argle bargle' in dir 'murkle'\n\n"
158 rc
.remoteUpdate({'rc': 0})
159 self
.assertEqual(rc
.rc
, 0)
162 # that should fire the Deferred
164 d
.addCallback(self
._testShellCommand
1_3)
167 def poll2(self
, ignored
=None):
168 if not self
.finished
:
170 d
.addCallback(self
.poll2
)
171 reactor
.callLater(0.1, d
.callback
, None)
173 return defer
.succeed(None)
175 def _testShellCommand1_3(self
, res
):
176 self
.assertEqual(self
.failed
, 0)
177 self
.assertEqual(self
.results
, 0)
180 class MyObserver(buildstep
.LogObserver
):
182 def outReceived(self
, data
):
183 self
.out
= self
.out
+ data
185 class Steps(unittest
.TestCase
):
186 def testMultipleStepInstances(self
):
188 (source
.CVS
, {'cvsroot': "root", 'cvsmodule': "module"}),
189 (shell
.Configure
, {'command': "./configure"}),
190 (shell
.Compile
, {'command': "make"}),
191 (shell
.Compile
, {'command': "make more"}),
192 (shell
.Compile
, {'command': "make evenmore"}),
193 (shell
.Test
, {'command': "make test"}),
194 (shell
.Test
, {'command': "make testharder"}),
196 f
= factory
.ConfigurableBuildFactory(steps
)
197 req
= base
.BuildRequest("reason", SourceStamp())
198 b
= f
.newBuild([req
])
199 #for s in b.steps: print s.name
201 # test the various methods available to buildsteps
203 def test_getProperty(self
):
204 s
= makeBuildStep("test_steps.Steps.test_getProperty")
205 bs
= s
.step_status
.getBuild()
207 s
.setProperty("prop1", "value1")
208 s
.setProperty("prop2", "value2")
209 self
.failUnlessEqual(s
.getProperty("prop1"), "value1")
210 self
.failUnlessEqual(bs
.getProperty("prop1"), "value1")
211 self
.failUnlessEqual(s
.getProperty("prop2"), "value2")
212 self
.failUnlessEqual(bs
.getProperty("prop2"), "value2")
213 s
.setProperty("prop1", "value1a")
214 self
.failUnlessEqual(s
.getProperty("prop1"), "value1a")
215 self
.failUnlessEqual(bs
.getProperty("prop1"), "value1a")
218 def test_addURL(self
):
219 s
= makeBuildStep("test_steps.Steps.test_addURL")
220 s
.addURL("coverage", "http://coverage.example.org/target")
221 s
.addURL("icon", "http://coverage.example.org/icon.png")
224 expected
= {"coverage": "http://coverage.example.org/target",
225 "icon": "http://coverage.example.org/icon.png",
227 self
.failUnlessEqual(links
, expected
)
229 def test_addLog(self
):
230 s
= makeBuildStep("test_steps.Steps.test_addLog")
231 l
= s
.addLog("newlog")
232 l
.addStdout("some stdout here")
236 self
.failUnlessEqual(len(logs
), 1)
238 self
.failUnlessEqual(l1
.getText(), "some stdout here")
239 l1a
= s
.getLog("newlog")
240 self
.failUnlessEqual(l1a
.getText(), "some stdout here")
242 def test_addHTMLLog(self
):
243 s
= makeBuildStep("test_steps.Steps.test_addHTMLLog")
244 l
= s
.addHTMLLog("newlog", "some html here")
247 self
.failUnlessEqual(len(logs
), 1)
249 self
.failUnless(isinstance(l1
, builder
.HTMLLogFile
))
250 self
.failUnlessEqual(l1
.getText(), "some html here")
252 def test_addCompleteLog(self
):
253 s
= makeBuildStep("test_steps.Steps.test_addCompleteLog")
254 l
= s
.addCompleteLog("newlog", "some stdout here")
257 self
.failUnlessEqual(len(logs
), 1)
259 self
.failUnlessEqual(l1
.getText(), "some stdout here")
260 l1a
= s
.getLog("newlog")
261 self
.failUnlessEqual(l1a
.getText(), "some stdout here")
263 def test_addLogObserver(self
):
264 s
= makeBuildStep("test_steps.Steps.test_addLogObserver")
266 o1
,o2
,o3
= MyObserver(), MyObserver(), MyObserver()
268 # add the log before the observer
270 l1
.addStdout("onestuff")
271 s
.addLogObserver("one", o1
)
272 self
.failUnlessEqual(o1
.out
, "onestuff")
273 l1
.addStdout(" morestuff")
274 self
.failUnlessEqual(o1
.out
, "onestuff morestuff")
276 # add the observer before the log
277 s
.addLogObserver("two", o2
)
279 l2
.addStdout("twostuff")
280 self
.failUnlessEqual(o2
.out
, "twostuff")
282 # test more stuff about ShellCommands
284 def test_description(self
):
285 s
= makeBuildStep("test_steps.Steps.test_description.1",
286 step_class
=shell
.ShellCommand
,
288 description
=["list", "of", "strings"],
289 descriptionDone
=["another", "list"])
290 self
.failUnlessEqual(s
.description
, ["list", "of", "strings"])
291 self
.failUnlessEqual(s
.descriptionDone
, ["another", "list"])
293 s
= makeBuildStep("test_steps.Steps.test_description.2",
294 step_class
=shell
.ShellCommand
,
296 description
="single string",
297 descriptionDone
="another string")
298 self
.failUnlessEqual(s
.description
, ["single string"])
299 self
.failUnlessEqual(s
.descriptionDone
, ["another string"])
301 class VersionCheckingStep(buildstep
.BuildStep
):
303 # give our test a chance to run. It is non-trivial for a buildstep to
304 # claw its way back out to the test case which is currently running.
305 master
= self
.build
.builder
.botmaster
.parent
306 checker
= master
._checker
309 self
.finished(buildstep
.SUCCESS
)
312 from buildbot.process import factory
313 from buildbot.test.test_steps import VersionCheckingStep
314 from buildbot.buildslave import BuildSlave
315 BuildmasterConfig = c = {}
316 f1 = factory.BuildFactory([
317 factory.s(VersionCheckingStep),
319 c['slaves'] = [BuildSlave('bot1', 'sekrit')]
321 c['builders'] = [{'name':'quick', 'slavename':'bot1',
322 'builddir': 'quickdir', 'factory': f1}]
323 c['slavePortnum'] = 0
326 class SlaveVersion(RunMixin
, unittest
.TestCase
):
329 self
.master
.loadConfig(version_config
)
330 self
.master
.startService()
331 d
= self
.connectSlave(["quick"])
334 def doBuild(self
, buildername
):
335 br
= base
.BuildRequest("forced", SourceStamp())
336 d
= br
.waitUntilFinished()
337 self
.control
.getBuilder(buildername
).requestBuild(br
)
341 def checkCompare(self
, s
):
342 cver
= commands
.command_version
343 v
= s
.slaveVersion("svn", None)
344 # this insures that we are getting the version correctly
345 self
.failUnlessEqual(s
.slaveVersion("svn", None), cver
)
346 # and that non-existent commands do not provide a version
347 self
.failUnlessEqual(s
.slaveVersion("NOSUCHCOMMAND"), None)
348 # TODO: verify that a <=0.5.0 buildslave (which does not implement
349 # remote_getCommands) handles oldversion= properly. This requires a
350 # mutant slave which does not offer that method.
351 #self.failUnlessEqual(s.slaveVersion("NOSUCHCOMMAND", "old"), "old")
353 # now check the comparison functions
354 self
.failIf(s
.slaveVersionIsOlderThan("svn", cver
))
355 self
.failIf(s
.slaveVersionIsOlderThan("svn", "1.1"))
356 self
.failUnless(s
.slaveVersionIsOlderThan("svn", cver
+ ".1"))
358 self
.failUnlessEqual(s
.getSlaveName(), "bot1")
360 def testCompare(self
):
361 self
.master
._checker
= self
.checkCompare
362 d
= self
.doBuild("quick")
366 class _SimpleBuildStep(buildstep
.BuildStep
):
368 args
= {"arg1": "value"}
369 cmd
= buildstep
.RemoteCommand("simple", args
)
370 d
= self
.runCommand(cmd
)
371 d
.addCallback(lambda res
: self
.finished(SUCCESS
))
373 class _SimpleCommand(commands
.Command
):
375 self
.builder
.flag
= True
376 self
.builder
.flag_args
= self
.args
377 return defer
.succeed(None)
379 class CheckStepTester(StepTester
, unittest
.TestCase
):
380 def testSimple(self
):
381 self
.slavebase
= "testSimple.slave"
382 self
.masterbase
= "testSimple.master"
383 sb
= self
.makeSlaveBuilder()
385 registry
.registerSlaveCommand("simple", _SimpleCommand
, "1")
386 step
= self
.makeStep(_SimpleBuildStep
)
387 d
= self
.runStep(step
)
388 def _checkSimple(results
):
389 self
.failUnless(sb
.flag
)
390 self
.failUnlessEqual(sb
.flag_args
, {"arg1": "value"})
391 d
.addCallback(_checkSimple
)
394 class Python(StepTester
, unittest
.TestCase
):
395 def testPyFlakes1(self
):
396 self
.masterbase
= "Python.testPyFlakes1"
397 step
= self
.makeStep(python
.PyFlakes
)
400 buildbot/changes/freshcvsmail.py:5: 'FCMaildirSource' imported but unused
401 buildbot/clients/debug.py:9: redefinition of unused 'gtk' from line 9
402 buildbot/clients/debug.py:9: 'gnome' imported but unused
403 buildbot/scripts/runner.py:323: redefinition of unused 'run' from line 321
404 buildbot/scripts/runner.py:325: redefinition of unused 'run' from line 323
405 buildbot/scripts/imaginary.py:12: undefined name 'size'
406 buildbot/scripts/imaginary.py:18: 'from buildbot import *' used; unable to detect undefined names
408 log
= step
.addLog("stdio")
409 log
.addStdout(output
)
411 step
.createSummary(log
)
412 desc
= step
.descriptionDone
413 self
.failUnless("unused=2" in desc
)
414 self
.failUnless("undefined=1" in desc
)
415 self
.failUnless("redefs=3" in desc
)
416 self
.failUnless("import*=1" in desc
)
417 self
.failIf("misc=" in desc
)
419 self
.failUnlessEqual(step
.getProperty("pyflakes-unused"), 2)
420 self
.failUnlessEqual(step
.getProperty("pyflakes-undefined"), 1)
421 self
.failUnlessEqual(step
.getProperty("pyflakes-redefs"), 3)
422 self
.failUnlessEqual(step
.getProperty("pyflakes-import*"), 1)
423 self
.failUnlessEqual(step
.getProperty("pyflakes-misc"), 0)
424 self
.failUnlessEqual(step
.getProperty("pyflakes-total"), 7)
427 for log
in step
.step_status
.getLogs():
428 logs
[log
.getName()] = log
430 for name
in ["unused", "undefined", "redefs", "import*"]:
431 self
.failUnless(name
in logs
)
432 self
.failIf("misc" in logs
)
433 lines
= logs
["unused"].readlines()
434 self
.failUnlessEqual(len(lines
), 2)
435 self
.failUnlessEqual(lines
[0], "buildbot/changes/freshcvsmail.py:5: 'FCMaildirSource' imported but unused\n")
437 cmd
= buildstep
.RemoteCommand(None, {})
439 results
= step
.evaluateCommand(cmd
)
440 self
.failUnlessEqual(results
, FAILURE
) # because of the 'undefined'
442 def testPyFlakes2(self
):
443 self
.masterbase
= "Python.testPyFlakes2"
444 step
= self
.makeStep(python
.PyFlakes
)
447 some more text here that should be ignored
448 buildbot/changes/freshcvsmail.py:5: 'FCMaildirSource' imported but unused
449 buildbot/clients/debug.py:9: redefinition of unused 'gtk' from line 9
450 buildbot/clients/debug.py:9: 'gnome' imported but unused
451 buildbot/scripts/runner.py:323: redefinition of unused 'run' from line 321
452 buildbot/scripts/runner.py:325: redefinition of unused 'run' from line 323
453 buildbot/scripts/imaginary.py:12: undefined name 'size'
454 could not compile 'blah/blah.py':3:
455 pretend there was an invalid line here
456 buildbot/scripts/imaginary.py:18: 'from buildbot import *' used; unable to detect undefined names
458 log
= step
.addLog("stdio")
459 log
.addStdout(output
)
461 step
.createSummary(log
)
462 desc
= step
.descriptionDone
463 self
.failUnless("unused=2" in desc
)
464 self
.failUnless("undefined=1" in desc
)
465 self
.failUnless("redefs=3" in desc
)
466 self
.failUnless("import*=1" in desc
)
467 self
.failUnless("misc=2" in desc
)
470 def testPyFlakes3(self
):
471 self
.masterbase
= "Python.testPyFlakes3"
472 step
= self
.makeStep(python
.PyFlakes
)
474 """buildbot/changes/freshcvsmail.py:5: 'FCMaildirSource' imported but unused
475 buildbot/clients/debug.py:9: redefinition of unused 'gtk' from line 9
476 buildbot/clients/debug.py:9: 'gnome' imported but unused
477 buildbot/scripts/runner.py:323: redefinition of unused 'run' from line 321
478 buildbot/scripts/runner.py:325: redefinition of unused 'run' from line 323
479 buildbot/scripts/imaginary.py:12: undefined name 'size'
480 buildbot/scripts/imaginary.py:18: 'from buildbot import *' used; unable to detect undefined names
482 log
= step
.addLog("stdio")
483 log
.addStdout(output
)
485 step
.createSummary(log
)
486 desc
= step
.descriptionDone
487 self
.failUnless("unused=2" in desc
)
488 self
.failUnless("undefined=1" in desc
)
489 self
.failUnless("redefs=3" in desc
)
490 self
.failUnless("import*=1" in desc
)
491 self
.failIf("misc" in desc
)