[project @ 2006-05-07 18:36:40 by warner]
[buildbot.git] / buildbot / test / test_steps.py
blobbbe2871c2a74cadef0972792989c8fc096470fbd
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 # mathces.
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, sys, time
19 from twisted.trial import unittest
20 from twisted.internet import reactor
21 from twisted.internet.defer import Deferred
23 from buildbot.sourcestamp import SourceStamp
24 from buildbot.process import step, base, factory
25 from buildbot.process.step import ShellCommand #, ShellCommands
26 from buildbot.status import builder
27 from buildbot.test.runutils import RunMixin
28 from buildbot.twcompat import maybeWait
29 from buildbot.slave import commands
31 from twisted.python import log
32 #log.startLogging(sys.stdout)
34 class MyShellCommand(ShellCommand):
35 started = False
36 def runCommand(self, c):
37 self.started = True
38 self.rc = c
39 return ShellCommand.runCommand(self, c)
41 class FakeBuild:
42 pass
43 class FakeBuilder:
44 statusbag = None
45 name = "fakebuilder"
46 class FakeSlaveBuilder:
47 def getSlaveCommandVersion(self, command, oldversion=None):
48 return "1.10"
50 class FakeRemote:
51 def __init__(self):
52 self.events = []
53 self.remoteCalls = 0
54 #self.callRemoteNotifier = None
55 def callRemote(self, methname, *args):
56 event = ["callRemote", methname, args]
57 self.events.append(event)
58 ## if self.callRemoteNotifier:
59 ## reactor.callLater(0, self.callRemoteNotifier, event)
60 self.remoteCalls += 1
61 self.deferred = Deferred()
62 return self.deferred
63 def notifyOnDisconnect(self, callback):
64 pass
65 def dontNotifyOnDisconnect(self, callback):
66 pass
69 class BuildStep(unittest.TestCase):
70 def setUp(self):
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()
83 self.finished = 0
85 def callback(self, results):
86 self.failed = 0
87 self.failure = None
88 self.results = results
89 self.finished = 1
90 def errback(self, failure):
91 self.failed = 1
92 self.failure = failure
93 self.results = None
94 self.finished = 1
96 def testShellCommand1(self):
97 cmd = "argle bargle"
98 dir = "murkle"
99 expectedEvents = []
100 step.RemoteCommand.commandCounter[0] = 3
101 c = MyShellCommand(workdir=dir, command=cmd, build=self.build,
102 timeout=10)
103 self.assertEqual(self.remote.events, expectedEvents)
104 self.build_status.addStep(c)
105 d = c.startStep(self.remote)
106 self.failUnless(c.started)
107 rc = c.rc
108 d.addCallbacks(self.callback, self.errback)
109 timeout = time.time() + 10
110 while self.remote.remoteCalls == 0:
111 if time.time() > timeout:
112 self.fail("timeout")
113 reactor.iterate(0.01)
114 expectedEvents.append(["callRemote", "startCommand",
115 (rc, "3",
116 "shell",
117 {'command': "argle bargle",
118 'workdir': "murkle",
119 'want_stdout': 1,
120 'want_stderr': 1,
121 'timeout': 10,
122 'env': None}) ] )
123 self.assertEqual(self.remote.events, expectedEvents)
125 # we could do self.remote.deferred.errback(UnknownCommand) here. We
126 # could also do .callback(), but generally the master end silently
127 # ignores the slave's ack
129 logs = c.step_status.getLogs()
130 for log in logs:
131 if log.getName() == "log":
132 break
134 rc.remoteUpdate({'header':
135 "command 'argle bargle' in dir 'murkle'\n\n"})
136 rc.remoteUpdate({'stdout': "foo\n"})
137 self.assertEqual(log.getText(), "foo\n")
138 self.assertEqual(log.getTextWithHeaders(),
139 "command 'argle bargle' in dir 'murkle'\n\n"
140 "foo\n")
141 rc.remoteUpdate({'stderr': "bar\n"})
142 self.assertEqual(log.getText(), "foo\nbar\n")
143 self.assertEqual(log.getTextWithHeaders(),
144 "command 'argle bargle' in dir 'murkle'\n\n"
145 "foo\nbar\n")
146 rc.remoteUpdate({'rc': 0})
147 self.assertEqual(rc.rc, 0)
149 rc.remote_complete()
150 # that should fire the Deferred
151 timeout = time.time() + 10
152 while not self.finished:
153 if time.time() > timeout:
154 self.fail("timeout")
155 reactor.iterate(0.01)
156 self.assertEqual(self.failed, 0)
157 self.assertEqual(self.results, 0)
159 class Steps(unittest.TestCase):
160 def testMultipleStepInstances(self):
161 steps = [
162 (step.CVS, {'cvsroot': "root", 'cvsmodule': "module"}),
163 (step.Configure, {'command': "./configure"}),
164 (step.Compile, {'command': "make"}),
165 (step.Compile, {'command': "make more"}),
166 (step.Compile, {'command': "make evenmore"}),
167 (step.Test, {'command': "make test"}),
168 (step.Test, {'command': "make testharder"}),
170 f = factory.ConfigurableBuildFactory(steps)
171 req = base.BuildRequest("reason", SourceStamp())
172 b = f.newBuild([req])
173 #for s in b.steps: print s.name
175 class VersionCheckingStep(step.BuildStep):
176 def start(self):
177 # give our test a chance to run. It is non-trivial for a buildstep to
178 # claw its way back out to the test case which is currently running.
179 master = self.build.builder.botmaster.parent
180 checker = master._checker
181 checker(self)
182 # then complete
183 self.finished(step.SUCCESS)
185 version_config = """
186 from buildbot.process import factory, step
187 from buildbot.test.test_steps import VersionCheckingStep
188 BuildmasterConfig = c = {}
189 f1 = factory.BuildFactory([
190 factory.s(VersionCheckingStep),
192 c['bots'] = [['bot1', 'sekrit']]
193 c['sources'] = []
194 c['schedulers'] = []
195 c['builders'] = [{'name':'quick', 'slavename':'bot1',
196 'builddir': 'quickdir', 'factory': f1}]
197 c['slavePortnum'] = 0
200 class Version(RunMixin, unittest.TestCase):
201 def setUp(self):
202 RunMixin.setUp(self)
203 self.master.loadConfig(version_config)
204 self.master.startService()
205 d = self.connectSlave(["quick"])
206 return maybeWait(d)
208 def doBuild(self, buildername):
209 br = base.BuildRequest("forced", SourceStamp())
210 d = br.waitUntilFinished()
211 self.control.getBuilder(buildername).requestBuild(br)
212 return d
215 def checkCompare(self, s):
216 v = s.slaveVersion("svn", None)
217 # this insures that we are getting the version correctly
218 self.failUnlessEqual(s.slaveVersion("svn", None), commands.cvs_ver)
219 # and that non-existent commands do not provide a version
220 self.failUnlessEqual(s.slaveVersion("NOSUCHCOMMAND"), None)
221 # TODO: verify that a <=0.5.0 buildslave (which does not implement
222 # remote_getCommands) handles oldversion= properly. This requires a
223 # mutant slave which does not offer that method.
224 #self.failUnlessEqual(s.slaveVersion("NOSUCHCOMMAND", "old"), "old")
226 # now check the comparison functions
227 self.failIf(s.slaveVersionIsOlderThan("svn", commands.cvs_ver))
228 self.failIf(s.slaveVersionIsOlderThan("svn", "1.1"))
229 self.failUnless(s.slaveVersionIsOlderThan("svn",
230 commands.cvs_ver + ".1"))
232 def testCompare(self):
233 self.master._checker = self.checkCompare
234 d = self.doBuild("quick")
235 return maybeWait(d)