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
.buildslave
import BuildSlave
25 from buildbot
.steps
import shell
, source
, python
26 from buildbot
.status
import builder
27 from buildbot
.status
.builder
import SUCCESS
, FAILURE
28 from buildbot
.test
.runutils
import RunMixin
, rmtree
29 from buildbot
.test
.runutils
import makeBuildStep
, StepTester
30 from buildbot
.slave
import commands
, registry
33 class MyShellCommand(shell
.ShellCommand
):
35 def runCommand(self
, c
):
38 return shell
.ShellCommand
.runCommand(self
, c
)
45 class FakeSlaveBuilder
:
46 def getSlaveCommandVersion(self
, command
, oldversion
=None):
53 #self.callRemoteNotifier = None
54 def callRemote(self
, methname
, *args
):
55 event
= ["callRemote", methname
, args
]
56 self
.events
.append(event
)
57 ## if self.callRemoteNotifier:
58 ## reactor.callLater(0, self.callRemoteNotifier, event)
60 self
.deferred
= defer
.Deferred()
62 def notifyOnDisconnect(self
, callback
):
64 def dontNotifyOnDisconnect(self
, callback
):
68 class BuildStep(unittest
.TestCase
):
72 self
.builder
= FakeBuilder()
73 self
.builder_status
= builder
.BuilderStatus("fakebuilder")
74 self
.builder_status
.basedir
= "test_steps"
75 self
.builder_status
.nextBuildNumber
= 0
76 os
.mkdir(self
.builder_status
.basedir
)
77 self
.build_status
= self
.builder_status
.newBuild()
78 req
= base
.BuildRequest("reason", SourceStamp())
79 self
.build
= base
.Build([req
])
80 self
.build
.build_status
= self
.build_status
# fake it
81 self
.build
.builder
= self
.builder
82 self
.build
.slavebuilder
= FakeSlaveBuilder()
83 self
.remote
= FakeRemote()
86 def callback(self
, results
):
89 self
.results
= results
91 def errback(self
, failure
):
93 self
.failure
= failure
97 def testShellCommand1(self
):
100 self
.expectedEvents
= []
101 buildstep
.RemoteCommand
.commandCounter
[0] = 3
102 c
= MyShellCommand(workdir
=dir, command
=cmd
, timeout
=10)
103 c
.setBuild(self
.build
)
104 c
.setBuildSlave(BuildSlave("name", "password"))
105 self
.assertEqual(self
.remote
.events
, self
.expectedEvents
)
106 c
.step_status
= self
.build_status
.addStepWithName("myshellcommand")
107 d
= c
.startStep(self
.remote
)
108 self
.failUnless(c
.started
)
109 d
.addCallbacks(self
.callback
, self
.errback
)
111 d2
.addCallback(self
._testShellCommand
1_2, c
)
113 testShellCommand1
.timeout
= 10
115 def poll(self
, ignored
=None):
116 # TODO: This is gross, but at least it's no longer using
117 # reactor.iterate() . Still, get rid of this some day soon.
118 if self
.remote
.remoteCalls
== 0:
120 d
.addCallback(self
.poll
)
121 reactor
.callLater(0.1, d
.callback
, None)
123 return defer
.succeed(None)
125 def _testShellCommand1_2(self
, res
, c
):
127 self
.expectedEvents
.append(["callRemote", "startCommand",
130 {'command': "argle bargle",
137 self
.assertEqual(self
.remote
.events
, self
.expectedEvents
)
139 # we could do self.remote.deferred.errback(UnknownCommand) here. We
140 # could also do .callback(), but generally the master end silently
141 # ignores the slave's ack
143 logs
= c
.step_status
.getLogs()
145 if log
.getName() == "log":
148 rc
.remoteUpdate({'header':
149 "command 'argle bargle' in dir 'murkle'\n\n"})
150 rc
.remoteUpdate({'stdout': "foo\n"})
151 self
.assertEqual(log
.getText(), "foo\n")
152 self
.assertEqual(log
.getTextWithHeaders(),
153 "command 'argle bargle' in dir 'murkle'\n\n"
155 rc
.remoteUpdate({'stderr': "bar\n"})
156 self
.assertEqual(log
.getText(), "foo\nbar\n")
157 self
.assertEqual(log
.getTextWithHeaders(),
158 "command 'argle bargle' in dir 'murkle'\n\n"
160 rc
.remoteUpdate({'rc': 0})
161 self
.assertEqual(rc
.rc
, 0)
164 # that should fire the Deferred
166 d
.addCallback(self
._testShellCommand
1_3)
169 def poll2(self
, ignored
=None):
170 if not self
.finished
:
172 d
.addCallback(self
.poll2
)
173 reactor
.callLater(0.1, d
.callback
, None)
175 return defer
.succeed(None)
177 def _testShellCommand1_3(self
, res
):
178 self
.assertEqual(self
.failed
, 0)
179 self
.assertEqual(self
.results
, 0)
182 class MyObserver(buildstep
.LogObserver
):
184 def outReceived(self
, data
):
185 self
.out
= self
.out
+ data
187 class Steps(unittest
.TestCase
):
188 def testMultipleStepInstances(self
):
190 (source
.CVS
, {'cvsroot': "root", 'cvsmodule': "module"}),
191 (shell
.Configure
, {'command': "./configure"}),
192 (shell
.Compile
, {'command': "make"}),
193 (shell
.Compile
, {'command': "make more"}),
194 (shell
.Compile
, {'command': "make evenmore"}),
195 (shell
.Test
, {'command': "make test"}),
196 (shell
.Test
, {'command': "make testharder"}),
198 f
= factory
.ConfigurableBuildFactory(steps
)
199 req
= base
.BuildRequest("reason", SourceStamp())
200 b
= f
.newBuild([req
])
201 #for s in b.steps: print s.name
203 def failUnlessClones(self
, s1
, attrnames
):
204 f1
= s1
.getStepFactory()
207 for name
in attrnames
:
208 self
.failUnlessEqual(getattr(s1
, name
), getattr(s2
, name
))
211 f1
= s1
.getStepFactory()
217 s1
= shell
.ShellCommand(command
=["make", "test"],
221 descriptionDone
="yoyo",
222 env
={'key': 'value'},
225 logfiles
={"name": "filename"},
227 shellparms
= (buildstep
.BuildStep
.parms
+
228 ("remote_kwargs description descriptionDone "
229 "command logfiles").split() )
230 self
.failUnlessClones(s1
, shellparms
)
233 # test the various methods available to buildsteps
235 def test_getProperty(self
):
236 s
= makeBuildStep("test_steps.Steps.test_getProperty")
237 bs
= s
.step_status
.getBuild()
239 s
.setProperty("prop1", "value1")
240 s
.setProperty("prop2", "value2")
241 self
.failUnlessEqual(s
.getProperty("prop1"), "value1")
242 self
.failUnlessEqual(bs
.getProperty("prop1"), "value1")
243 self
.failUnlessEqual(s
.getProperty("prop2"), "value2")
244 self
.failUnlessEqual(bs
.getProperty("prop2"), "value2")
245 s
.setProperty("prop1", "value1a")
246 self
.failUnlessEqual(s
.getProperty("prop1"), "value1a")
247 self
.failUnlessEqual(bs
.getProperty("prop1"), "value1a")
250 def test_addURL(self
):
251 s
= makeBuildStep("test_steps.Steps.test_addURL")
252 s
.addURL("coverage", "http://coverage.example.org/target")
253 s
.addURL("icon", "http://coverage.example.org/icon.png")
256 expected
= {"coverage": "http://coverage.example.org/target",
257 "icon": "http://coverage.example.org/icon.png",
259 self
.failUnlessEqual(links
, expected
)
261 def test_addLog(self
):
262 s
= makeBuildStep("test_steps.Steps.test_addLog")
263 l
= s
.addLog("newlog")
264 l
.addStdout("some stdout here")
268 self
.failUnlessEqual(len(logs
), 1)
270 self
.failUnlessEqual(l1
.getText(), "some stdout here")
271 l1a
= s
.getLog("newlog")
272 self
.failUnlessEqual(l1a
.getText(), "some stdout here")
274 def test_addHTMLLog(self
):
275 s
= makeBuildStep("test_steps.Steps.test_addHTMLLog")
276 l
= s
.addHTMLLog("newlog", "some html here")
279 self
.failUnlessEqual(len(logs
), 1)
281 self
.failUnless(isinstance(l1
, builder
.HTMLLogFile
))
282 self
.failUnlessEqual(l1
.getText(), "some html here")
284 def test_addCompleteLog(self
):
285 s
= makeBuildStep("test_steps.Steps.test_addCompleteLog")
286 l
= s
.addCompleteLog("newlog", "some stdout here")
289 self
.failUnlessEqual(len(logs
), 1)
291 self
.failUnlessEqual(l1
.getText(), "some stdout here")
292 l1a
= s
.getLog("newlog")
293 self
.failUnlessEqual(l1a
.getText(), "some stdout here")
295 def test_addLogObserver(self
):
296 s
= makeBuildStep("test_steps.Steps.test_addLogObserver")
298 o1
,o2
,o3
= MyObserver(), MyObserver(), MyObserver()
300 # add the log before the observer
302 l1
.addStdout("onestuff")
303 s
.addLogObserver("one", o1
)
304 self
.failUnlessEqual(o1
.out
, "onestuff")
305 l1
.addStdout(" morestuff")
306 self
.failUnlessEqual(o1
.out
, "onestuff morestuff")
308 # add the observer before the log
309 s
.addLogObserver("two", o2
)
311 l2
.addStdout("twostuff")
312 self
.failUnlessEqual(o2
.out
, "twostuff")
314 # test more stuff about ShellCommands
316 def test_description(self
):
317 s
= makeBuildStep("test_steps.Steps.test_description.1",
318 step_class
=shell
.ShellCommand
,
320 description
=["list", "of", "strings"],
321 descriptionDone
=["another", "list"])
322 self
.failUnlessEqual(s
.description
, ["list", "of", "strings"])
323 self
.failUnlessEqual(s
.descriptionDone
, ["another", "list"])
325 s
= makeBuildStep("test_steps.Steps.test_description.2",
326 step_class
=shell
.ShellCommand
,
328 description
="single string",
329 descriptionDone
="another string")
330 self
.failUnlessEqual(s
.description
, ["single string"])
331 self
.failUnlessEqual(s
.descriptionDone
, ["another string"])
333 class VersionCheckingStep(buildstep
.BuildStep
):
335 # give our test a chance to run. It is non-trivial for a buildstep to
336 # claw its way back out to the test case which is currently running.
337 master
= self
.build
.builder
.botmaster
.parent
338 checker
= master
._checker
341 self
.finished(buildstep
.SUCCESS
)
344 from buildbot.process import factory
345 from buildbot.test.test_steps import VersionCheckingStep
346 from buildbot.buildslave import BuildSlave
347 BuildmasterConfig = c = {}
348 f1 = factory.BuildFactory([
349 factory.s(VersionCheckingStep),
351 c['slaves'] = [BuildSlave('bot1', 'sekrit')]
353 c['builders'] = [{'name':'quick', 'slavename':'bot1',
354 'builddir': 'quickdir', 'factory': f1}]
355 c['slavePortnum'] = 0
358 class SlaveVersion(RunMixin
, unittest
.TestCase
):
361 self
.master
.loadConfig(version_config
)
362 self
.master
.startService()
363 d
= self
.connectSlave(["quick"])
366 def doBuild(self
, buildername
):
367 br
= base
.BuildRequest("forced", SourceStamp())
368 d
= br
.waitUntilFinished()
369 self
.control
.getBuilder(buildername
).requestBuild(br
)
373 def checkCompare(self
, s
):
374 cver
= commands
.command_version
375 v
= s
.slaveVersion("svn", None)
376 # this insures that we are getting the version correctly
377 self
.failUnlessEqual(s
.slaveVersion("svn", None), cver
)
378 # and that non-existent commands do not provide a version
379 self
.failUnlessEqual(s
.slaveVersion("NOSUCHCOMMAND"), None)
380 # TODO: verify that a <=0.5.0 buildslave (which does not implement
381 # remote_getCommands) handles oldversion= properly. This requires a
382 # mutant slave which does not offer that method.
383 #self.failUnlessEqual(s.slaveVersion("NOSUCHCOMMAND", "old"), "old")
385 # now check the comparison functions
386 self
.failIf(s
.slaveVersionIsOlderThan("svn", cver
))
387 self
.failIf(s
.slaveVersionIsOlderThan("svn", "1.1"))
388 self
.failUnless(s
.slaveVersionIsOlderThan("svn", cver
+ ".1"))
390 self
.failUnlessEqual(s
.getSlaveName(), "bot1")
392 def testCompare(self
):
393 self
.master
._checker
= self
.checkCompare
394 d
= self
.doBuild("quick")
398 class _SimpleBuildStep(buildstep
.BuildStep
):
400 args
= {"arg1": "value"}
401 cmd
= buildstep
.RemoteCommand("simple", args
)
402 d
= self
.runCommand(cmd
)
403 d
.addCallback(lambda res
: self
.finished(SUCCESS
))
405 class _SimpleCommand(commands
.Command
):
407 self
.builder
.flag
= True
408 self
.builder
.flag_args
= self
.args
409 return defer
.succeed(None)
411 class CheckStepTester(StepTester
, unittest
.TestCase
):
412 def testSimple(self
):
413 self
.slavebase
= "testSimple.slave"
414 self
.masterbase
= "testSimple.master"
415 sb
= self
.makeSlaveBuilder()
417 registry
.registerSlaveCommand("simple", _SimpleCommand
, "1")
418 step
= self
.makeStep(_SimpleBuildStep
)
419 d
= self
.runStep(step
)
420 def _checkSimple(results
):
421 self
.failUnless(sb
.flag
)
422 self
.failUnlessEqual(sb
.flag_args
, {"arg1": "value"})
423 d
.addCallback(_checkSimple
)
426 class Python(StepTester
, unittest
.TestCase
):
427 def testPyFlakes1(self
):
428 self
.masterbase
= "Python.testPyFlakes1"
429 step
= self
.makeStep(python
.PyFlakes
)
432 buildbot/changes/freshcvsmail.py:5: 'FCMaildirSource' imported but unused
433 buildbot/clients/debug.py:9: redefinition of unused 'gtk' from line 9
434 buildbot/clients/debug.py:9: 'gnome' imported but unused
435 buildbot/scripts/runner.py:323: redefinition of unused 'run' from line 321
436 buildbot/scripts/runner.py:325: redefinition of unused 'run' from line 323
437 buildbot/scripts/imaginary.py:12: undefined name 'size'
438 buildbot/scripts/imaginary.py:18: 'from buildbot import *' used; unable to detect undefined names
440 log
= step
.addLog("stdio")
441 log
.addStdout(output
)
443 step
.createSummary(log
)
444 desc
= step
.descriptionDone
445 self
.failUnless("unused=2" in desc
)
446 self
.failUnless("undefined=1" in desc
)
447 self
.failUnless("redefs=3" in desc
)
448 self
.failUnless("import*=1" in desc
)
449 self
.failIf("misc=" in desc
)
451 self
.failUnlessEqual(step
.getProperty("pyflakes-unused"), 2)
452 self
.failUnlessEqual(step
.getProperty("pyflakes-undefined"), 1)
453 self
.failUnlessEqual(step
.getProperty("pyflakes-redefs"), 3)
454 self
.failUnlessEqual(step
.getProperty("pyflakes-import*"), 1)
455 self
.failUnlessEqual(step
.getProperty("pyflakes-misc"), 0)
456 self
.failUnlessEqual(step
.getProperty("pyflakes-total"), 7)
459 for log
in step
.step_status
.getLogs():
460 logs
[log
.getName()] = log
462 for name
in ["unused", "undefined", "redefs", "import*"]:
463 self
.failUnless(name
in logs
)
464 self
.failIf("misc" in logs
)
465 lines
= logs
["unused"].readlines()
466 self
.failUnlessEqual(len(lines
), 2)
467 self
.failUnlessEqual(lines
[0], "buildbot/changes/freshcvsmail.py:5: 'FCMaildirSource' imported but unused\n")
469 cmd
= buildstep
.RemoteCommand(None, {})
471 results
= step
.evaluateCommand(cmd
)
472 self
.failUnlessEqual(results
, FAILURE
) # because of the 'undefined'
474 def testPyFlakes2(self
):
475 self
.masterbase
= "Python.testPyFlakes2"
476 step
= self
.makeStep(python
.PyFlakes
)
479 some more text here that should be ignored
480 buildbot/changes/freshcvsmail.py:5: 'FCMaildirSource' imported but unused
481 buildbot/clients/debug.py:9: redefinition of unused 'gtk' from line 9
482 buildbot/clients/debug.py:9: 'gnome' imported but unused
483 buildbot/scripts/runner.py:323: redefinition of unused 'run' from line 321
484 buildbot/scripts/runner.py:325: redefinition of unused 'run' from line 323
485 buildbot/scripts/imaginary.py:12: undefined name 'size'
486 could not compile 'blah/blah.py':3:
487 pretend there was an invalid line here
488 buildbot/scripts/imaginary.py:18: 'from buildbot import *' used; unable to detect undefined names
490 log
= step
.addLog("stdio")
491 log
.addStdout(output
)
493 step
.createSummary(log
)
494 desc
= step
.descriptionDone
495 self
.failUnless("unused=2" in desc
)
496 self
.failUnless("undefined=1" in desc
)
497 self
.failUnless("redefs=3" in desc
)
498 self
.failUnless("import*=1" in desc
)
499 self
.failUnless("misc=2" in desc
)
502 def testPyFlakes3(self
):
503 self
.masterbase
= "Python.testPyFlakes3"
504 step
= self
.makeStep(python
.PyFlakes
)
506 """buildbot/changes/freshcvsmail.py:5: 'FCMaildirSource' imported but unused
507 buildbot/clients/debug.py:9: redefinition of unused 'gtk' from line 9
508 buildbot/clients/debug.py:9: 'gnome' imported but unused
509 buildbot/scripts/runner.py:323: redefinition of unused 'run' from line 321
510 buildbot/scripts/runner.py:325: redefinition of unused 'run' from line 323
511 buildbot/scripts/imaginary.py:12: undefined name 'size'
512 buildbot/scripts/imaginary.py:18: 'from buildbot import *' used; unable to detect undefined names
514 log
= step
.addLog("stdio")
515 log
.addStdout(output
)
517 step
.createSummary(log
)
518 desc
= step
.descriptionDone
519 self
.failUnless("unused=2" in desc
)
520 self
.failUnless("undefined=1" in desc
)
521 self
.failUnless("redefs=3" in desc
)
522 self
.failUnless("import*=1" in desc
)
523 self
.failIf("misc" in desc
)