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
.twcompat
import maybeWait
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
):
101 buildstep
.RemoteCommand
.commandCounter
[0] = 3
102 c
= MyShellCommand(workdir
=dir, command
=cmd
, build
=self
.build
,
104 self
.assertEqual(self
.remote
.events
, expectedEvents
)
105 c
.step_status
= self
.build_status
.addStepWithName("myshellcommand")
106 d
= c
.startStep(self
.remote
)
107 self
.failUnless(c
.started
)
109 d
.addCallbacks(self
.callback
, self
.errback
)
110 timeout
= time
.time() + 10
111 while self
.remote
.remoteCalls
== 0:
112 if time
.time() > timeout
:
114 reactor
.iterate(0.01)
115 expectedEvents
.append(["callRemote", "startCommand",
118 {'command': "argle bargle",
125 self
.assertEqual(self
.remote
.events
, expectedEvents
)
127 # we could do self.remote.deferred.errback(UnknownCommand) here. We
128 # could also do .callback(), but generally the master end silently
129 # ignores the slave's ack
131 logs
= c
.step_status
.getLogs()
133 if log
.getName() == "log":
136 rc
.remoteUpdate({'header':
137 "command 'argle bargle' in dir 'murkle'\n\n"})
138 rc
.remoteUpdate({'stdout': "foo\n"})
139 self
.assertEqual(log
.getText(), "foo\n")
140 self
.assertEqual(log
.getTextWithHeaders(),
141 "command 'argle bargle' in dir 'murkle'\n\n"
143 rc
.remoteUpdate({'stderr': "bar\n"})
144 self
.assertEqual(log
.getText(), "foo\nbar\n")
145 self
.assertEqual(log
.getTextWithHeaders(),
146 "command 'argle bargle' in dir 'murkle'\n\n"
148 rc
.remoteUpdate({'rc': 0})
149 self
.assertEqual(rc
.rc
, 0)
152 # that should fire the Deferred
153 timeout
= time
.time() + 10
154 while not self
.finished
:
155 if time
.time() > timeout
:
157 reactor
.iterate(0.01)
158 self
.assertEqual(self
.failed
, 0)
159 self
.assertEqual(self
.results
, 0)
162 class MyObserver(buildstep
.LogObserver
):
164 def outReceived(self
, data
):
165 self
.out
= self
.out
+ data
167 class Steps(unittest
.TestCase
):
168 def testMultipleStepInstances(self
):
170 (source
.CVS
, {'cvsroot': "root", 'cvsmodule': "module"}),
171 (shell
.Configure
, {'command': "./configure"}),
172 (shell
.Compile
, {'command': "make"}),
173 (shell
.Compile
, {'command': "make more"}),
174 (shell
.Compile
, {'command': "make evenmore"}),
175 (shell
.Test
, {'command': "make test"}),
176 (shell
.Test
, {'command': "make testharder"}),
178 f
= factory
.ConfigurableBuildFactory(steps
)
179 req
= base
.BuildRequest("reason", SourceStamp())
180 b
= f
.newBuild([req
])
181 #for s in b.steps: print s.name
183 # test the various methods available to buildsteps
185 def test_getProperty(self
):
186 s
= makeBuildStep("test_steps.Steps.test_getProperty")
187 bs
= s
.step_status
.getBuild()
189 s
.setProperty("prop1", "value1")
190 s
.setProperty("prop2", "value2")
191 self
.failUnlessEqual(s
.getProperty("prop1"), "value1")
192 self
.failUnlessEqual(bs
.getProperty("prop1"), "value1")
193 self
.failUnlessEqual(s
.getProperty("prop2"), "value2")
194 self
.failUnlessEqual(bs
.getProperty("prop2"), "value2")
195 s
.setProperty("prop1", "value1a")
196 self
.failUnlessEqual(s
.getProperty("prop1"), "value1a")
197 self
.failUnlessEqual(bs
.getProperty("prop1"), "value1a")
200 def test_addURL(self
):
201 s
= makeBuildStep("test_steps.Steps.test_addURL")
202 s
.addURL("coverage", "http://coverage.example.org/target")
203 s
.addURL("icon", "http://coverage.example.org/icon.png")
206 expected
= {"coverage": "http://coverage.example.org/target",
207 "icon": "http://coverage.example.org/icon.png",
209 self
.failUnlessEqual(links
, expected
)
211 def test_addLog(self
):
212 s
= makeBuildStep("test_steps.Steps.test_addLog")
213 l
= s
.addLog("newlog")
214 l
.addStdout("some stdout here")
218 self
.failUnlessEqual(len(logs
), 1)
220 self
.failUnlessEqual(l1
.getText(), "some stdout here")
222 def test_addHTMLLog(self
):
223 s
= makeBuildStep("test_steps.Steps.test_addHTMLLog")
224 l
= s
.addHTMLLog("newlog", "some html here")
227 self
.failUnlessEqual(len(logs
), 1)
229 self
.failUnless(isinstance(l1
, builder
.HTMLLogFile
))
230 self
.failUnlessEqual(l1
.getText(), "some html here")
232 def test_addCompleteLog(self
):
233 s
= makeBuildStep("test_steps.Steps.test_addCompleteLog")
234 l
= s
.addCompleteLog("newlog", "some stdout here")
237 self
.failUnlessEqual(len(logs
), 1)
239 self
.failUnlessEqual(l1
.getText(), "some stdout here")
241 def test_addLogObserver(self
):
242 s
= makeBuildStep("test_steps.Steps.test_addLogObserver")
244 o1
,o2
,o3
= MyObserver(), MyObserver(), MyObserver()
246 # add the log before the observer
248 l1
.addStdout("onestuff")
249 s
.addLogObserver("one", o1
)
250 self
.failUnlessEqual(o1
.out
, "onestuff")
251 l1
.addStdout(" morestuff")
252 self
.failUnlessEqual(o1
.out
, "onestuff morestuff")
254 # add the observer before the log
255 s
.addLogObserver("two", o2
)
257 l2
.addStdout("twostuff")
258 self
.failUnlessEqual(o2
.out
, "twostuff")
260 # test more stuff about ShellCommands
262 def test_description(self
):
263 s
= makeBuildStep("test_steps.Steps.test_description.1",
264 step_class
=shell
.ShellCommand
,
266 description
=["list", "of", "strings"],
267 descriptionDone
=["another", "list"])
268 self
.failUnlessEqual(s
.description
, ["list", "of", "strings"])
269 self
.failUnlessEqual(s
.descriptionDone
, ["another", "list"])
271 s
= makeBuildStep("test_steps.Steps.test_description.2",
272 step_class
=shell
.ShellCommand
,
274 description
="single string",
275 descriptionDone
="another string")
276 self
.failUnlessEqual(s
.description
, ["single string"])
277 self
.failUnlessEqual(s
.descriptionDone
, ["another string"])
279 class VersionCheckingStep(buildstep
.BuildStep
):
281 # give our test a chance to run. It is non-trivial for a buildstep to
282 # claw its way back out to the test case which is currently running.
283 master
= self
.build
.builder
.botmaster
.parent
284 checker
= master
._checker
287 self
.finished(buildstep
.SUCCESS
)
290 from buildbot.process import factory
291 from buildbot.test.test_steps import VersionCheckingStep
292 BuildmasterConfig = c = {}
293 f1 = factory.BuildFactory([
294 factory.s(VersionCheckingStep),
296 c['bots'] = [['bot1', 'sekrit']]
299 c['builders'] = [{'name':'quick', 'slavename':'bot1',
300 'builddir': 'quickdir', 'factory': f1}]
301 c['slavePortnum'] = 0
304 class SlaveVersion(RunMixin
, unittest
.TestCase
):
307 self
.master
.loadConfig(version_config
)
308 self
.master
.startService()
309 d
= self
.connectSlave(["quick"])
312 def doBuild(self
, buildername
):
313 br
= base
.BuildRequest("forced", SourceStamp())
314 d
= br
.waitUntilFinished()
315 self
.control
.getBuilder(buildername
).requestBuild(br
)
319 def checkCompare(self
, s
):
320 cver
= commands
.command_version
321 v
= s
.slaveVersion("svn", None)
322 # this insures that we are getting the version correctly
323 self
.failUnlessEqual(s
.slaveVersion("svn", None), cver
)
324 # and that non-existent commands do not provide a version
325 self
.failUnlessEqual(s
.slaveVersion("NOSUCHCOMMAND"), None)
326 # TODO: verify that a <=0.5.0 buildslave (which does not implement
327 # remote_getCommands) handles oldversion= properly. This requires a
328 # mutant slave which does not offer that method.
329 #self.failUnlessEqual(s.slaveVersion("NOSUCHCOMMAND", "old"), "old")
331 # now check the comparison functions
332 self
.failIf(s
.slaveVersionIsOlderThan("svn", cver
))
333 self
.failIf(s
.slaveVersionIsOlderThan("svn", "1.1"))
334 self
.failUnless(s
.slaveVersionIsOlderThan("svn", cver
+ ".1"))
336 self
.failUnlessEqual(s
.getSlaveName(), "bot1")
338 def testCompare(self
):
339 self
.master
._checker
= self
.checkCompare
340 d
= self
.doBuild("quick")
344 class ReorgCompatibility(unittest
.TestCase
):
345 def testCompat(self
):
346 from buildbot
.process
.step
import LogObserver
, LogLineObserver
347 from buildbot
.process
.step
import RemoteShellCommand
348 from buildbot
.process
.step
import BuildStep
, LoggingBuildStep
349 from buildbot
.process
.step
import ShellCommand
, WithProperties
350 from buildbot
.process
.step
import TreeSize
351 from buildbot
.process
.step
import Configure
352 from buildbot
.process
.step
import Compile
353 from buildbot
.process
.step
import Test
354 from buildbot
.process
.step
import CVS
355 from buildbot
.process
.step
import SVN
356 from buildbot
.process
.step
import Darcs
357 from buildbot
.process
.step
import Git
358 from buildbot
.process
.step
import Arch
359 from buildbot
.process
.step
import Bazaar
360 from buildbot
.process
.step
import Mercurial
361 from buildbot
.process
.step
import P4
362 from buildbot
.process
.step
import P4Sync
363 from buildbot
.process
.step
import Dummy
364 from buildbot
.process
.step
import FailingDummy
365 from buildbot
.process
.step
import RemoteDummy
368 class _SimpleBuildStep(buildstep
.BuildStep
):
370 args
= {"arg1": "value"}
371 cmd
= buildstep
.RemoteCommand("simple", args
)
372 d
= self
.runCommand(cmd
)
373 d
.addCallback(lambda res
: self
.finished(SUCCESS
))
375 class _SimpleCommand(commands
.Command
):
377 self
.builder
.flag
= True
378 self
.builder
.flag_args
= self
.args
379 return defer
.succeed(None)
381 class CheckStepTester(StepTester
, unittest
.TestCase
):
382 def testSimple(self
):
383 self
.slavebase
= "testSimple.slave"
384 self
.masterbase
= "testSimple.master"
385 sb
= self
.makeSlaveBuilder()
387 registry
.registerSlaveCommand("simple", _SimpleCommand
, "1")
388 step
= self
.makeStep(_SimpleBuildStep
)
389 d
= self
.runStep(step
)
390 def _checkSimple(results
):
391 self
.failUnless(sb
.flag
)
392 self
.failUnlessEqual(sb
.flag_args
, {"arg1": "value"})
393 d
.addCallback(_checkSimple
)
396 class Python(StepTester
, unittest
.TestCase
):
397 def testPyFlakes1(self
):
398 self
.masterbase
= "Python.testPyFlakes1"
399 step
= self
.makeStep(python
.PyFlakes
)
402 buildbot/changes/freshcvsmail.py:5: 'FCMaildirSource' imported but unused
403 buildbot/clients/debug.py:9: redefinition of unused 'gtk' from line 9
404 buildbot/clients/debug.py:9: 'gnome' imported but unused
405 buildbot/scripts/runner.py:323: redefinition of unused 'run' from line 321
406 buildbot/scripts/runner.py:325: redefinition of unused 'run' from line 323
407 buildbot/scripts/imaginary.py:12: undefined name 'size'
408 buildbot/scripts/imaginary.py:18: 'from buildbot import *' used; unable to detect undefined names
410 log
= step
.addLog("stdio")
411 log
.addStdout(output
)
413 step
.createSummary(log
)
414 desc
= step
.descriptionDone
415 self
.failUnless("unused=2" in desc
)
416 self
.failUnless("undefined=1" in desc
)
417 self
.failUnless("redefs=3" in desc
)
418 self
.failUnless("import*=1" in desc
)
419 self
.failIf("misc=" in desc
)
421 self
.failUnlessEqual(step
.getProperty("pyflakes-unused"), 2)
422 self
.failUnlessEqual(step
.getProperty("pyflakes-undefined"), 1)
423 self
.failUnlessEqual(step
.getProperty("pyflakes-redefs"), 3)
424 self
.failUnlessEqual(step
.getProperty("pyflakes-import*"), 1)
425 self
.failUnlessEqual(step
.getProperty("pyflakes-misc"), 0)
426 self
.failUnlessEqual(step
.getProperty("pyflakes-total"), 7)
429 for log
in step
.step_status
.getLogs():
430 logs
[log
.getName()] = log
432 for name
in ["unused", "undefined", "redefs", "import*"]:
433 self
.failUnless(name
in logs
)
434 self
.failIf("misc" in logs
)
435 lines
= logs
["unused"].readlines()
436 self
.failUnlessEqual(len(lines
), 2)
437 self
.failUnlessEqual(lines
[0], "buildbot/changes/freshcvsmail.py:5: 'FCMaildirSource' imported but unused\n")
439 cmd
= buildstep
.RemoteCommand(None, {})
441 results
= step
.evaluateCommand(cmd
)
442 self
.failUnlessEqual(results
, FAILURE
) # because of the 'undefined'
444 def testPyFlakes2(self
):
445 self
.masterbase
= "Python.testPyFlakes2"
446 step
= self
.makeStep(python
.PyFlakes
)
449 some more text here that should be ignored
450 buildbot/changes/freshcvsmail.py:5: 'FCMaildirSource' imported but unused
451 buildbot/clients/debug.py:9: redefinition of unused 'gtk' from line 9
452 buildbot/clients/debug.py:9: 'gnome' imported but unused
453 buildbot/scripts/runner.py:323: redefinition of unused 'run' from line 321
454 buildbot/scripts/runner.py:325: redefinition of unused 'run' from line 323
455 buildbot/scripts/imaginary.py:12: undefined name 'size'
456 could not compile 'blah/blah.py':3:
457 pretend there was an invalid line here
458 buildbot/scripts/imaginary.py:18: 'from buildbot import *' used; unable to detect undefined names
460 log
= step
.addLog("stdio")
461 log
.addStdout(output
)
463 step
.createSummary(log
)
464 desc
= step
.descriptionDone
465 self
.failUnless("unused=2" in desc
)
466 self
.failUnless("undefined=1" in desc
)
467 self
.failUnless("redefs=3" in desc
)
468 self
.failUnless("import*=1" in desc
)
469 self
.failUnless("misc=2" in desc
)