PyFlakes: ignore initial output lines that weren't emitted by pyflakes
[buildbot.git] / buildbot / test / test_steps.py
blob5e01018718694ab34bf0f01cb6de079121c43fbc
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
8 # matches.
10 # sometimes, .callRemote should raise an exception because of a stale
11 # reference. Sometimes it should errBack with an UnknownCommand failure.
12 # Or other failure.
14 # todo: test batched updates, by invoking remote_update(updates) instead of
15 # statusUpdate(update). Also involves interrupted builds.
17 import os, time
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):
34 started = False
35 def runCommand(self, c):
36 self.started = True
37 self.rc = c
38 return shell.ShellCommand.runCommand(self, c)
40 class FakeBuild:
41 pass
42 class FakeBuilder:
43 statusbag = None
44 name = "fakebuilder"
45 class FakeSlaveBuilder:
46 def getSlaveCommandVersion(self, command, oldversion=None):
47 return "1.10"
49 class FakeRemote:
50 def __init__(self):
51 self.events = []
52 self.remoteCalls = 0
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)
59 self.remoteCalls += 1
60 self.deferred = defer.Deferred()
61 return self.deferred
62 def notifyOnDisconnect(self, callback):
63 pass
64 def dontNotifyOnDisconnect(self, callback):
65 pass
68 class BuildStep(unittest.TestCase):
70 def setUp(self):
71 rmtree("test_steps")
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()
84 self.finished = 0
86 def callback(self, results):
87 self.failed = 0
88 self.failure = None
89 self.results = results
90 self.finished = 1
91 def errback(self, failure):
92 self.failed = 1
93 self.failure = failure
94 self.results = None
95 self.finished = 1
97 def testShellCommand1(self):
98 cmd = "argle bargle"
99 dir = "murkle"
100 expectedEvents = []
101 buildstep.RemoteCommand.commandCounter[0] = 3
102 c = MyShellCommand(workdir=dir, command=cmd, build=self.build,
103 timeout=10)
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)
108 rc = c.rc
109 d.addCallbacks(self.callback, self.errback)
110 timeout = time.time() + 10
111 while self.remote.remoteCalls == 0:
112 if time.time() > timeout:
113 self.fail("timeout")
114 reactor.iterate(0.01)
115 expectedEvents.append(["callRemote", "startCommand",
116 (rc, "3",
117 "shell",
118 {'command': "argle bargle",
119 'workdir': "murkle",
120 'want_stdout': 1,
121 'want_stderr': 1,
122 'logfiles': {},
123 'timeout': 10,
124 'env': None}) ] )
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()
132 for log in logs:
133 if log.getName() == "log":
134 break
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"
142 "foo\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"
147 "foo\nbar\n")
148 rc.remoteUpdate({'rc': 0})
149 self.assertEqual(rc.rc, 0)
151 rc.remote_complete()
152 # that should fire the Deferred
153 timeout = time.time() + 10
154 while not self.finished:
155 if time.time() > timeout:
156 self.fail("timeout")
157 reactor.iterate(0.01)
158 self.assertEqual(self.failed, 0)
159 self.assertEqual(self.results, 0)
162 class MyObserver(buildstep.LogObserver):
163 out = ""
164 def outReceived(self, data):
165 self.out = self.out + data
167 class Steps(unittest.TestCase):
168 def testMultipleStepInstances(self):
169 steps = [
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")
204 bs = s.step_status
205 links = bs.getURLs()
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")
215 l.finish()
216 bs = s.step_status
217 logs = bs.getLogs()
218 self.failUnlessEqual(len(logs), 1)
219 l1 = logs[0]
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")
225 bs = s.step_status
226 logs = bs.getLogs()
227 self.failUnlessEqual(len(logs), 1)
228 l1 = logs[0]
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")
235 bs = s.step_status
236 logs = bs.getLogs()
237 self.failUnlessEqual(len(logs), 1)
238 l1 = logs[0]
239 self.failUnlessEqual(l1.getText(), "some stdout here")
241 def test_addLogObserver(self):
242 s = makeBuildStep("test_steps.Steps.test_addLogObserver")
243 bss = s.step_status
244 o1,o2,o3 = MyObserver(), MyObserver(), MyObserver()
246 # add the log before the observer
247 l1 = s.addLog("one")
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)
256 l2 = s.addLog("two")
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,
265 workdir="dummy",
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,
273 workdir="dummy",
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):
280 def start(self):
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
285 checker(self)
286 # then complete
287 self.finished(buildstep.SUCCESS)
289 version_config = """
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']]
297 c['sources'] = []
298 c['schedulers'] = []
299 c['builders'] = [{'name':'quick', 'slavename':'bot1',
300 'builddir': 'quickdir', 'factory': f1}]
301 c['slavePortnum'] = 0
304 class SlaveVersion(RunMixin, unittest.TestCase):
305 def setUp(self):
306 RunMixin.setUp(self)
307 self.master.loadConfig(version_config)
308 self.master.startService()
309 d = self.connectSlave(["quick"])
310 return maybeWait(d)
312 def doBuild(self, buildername):
313 br = base.BuildRequest("forced", SourceStamp())
314 d = br.waitUntilFinished()
315 self.control.getBuilder(buildername).requestBuild(br)
316 return d
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")
341 return maybeWait(d)
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):
369 def start(self):
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):
376 def start(self):
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()
386 sb.flag = False
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)
394 return maybeWait(d)
396 class Python(StepTester, unittest.TestCase):
397 def testPyFlakes1(self):
398 self.masterbase = "Python.testPyFlakes1"
399 step = self.makeStep(python.PyFlakes)
400 output = \
401 """pyflakes buildbot
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)
412 log.finish()
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)
428 logs = {}
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, {})
440 cmd.rc = 0
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)
447 output = \
448 """pyflakes buildbot
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)
462 log.finish()
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)