OLD | NEW |
| (Empty) |
1 # -*- test-case-name: buildbot.test.test_scheduler -*- | |
2 | |
3 import os, time | |
4 | |
5 from twisted.trial import unittest | |
6 from twisted.internet import defer, reactor | |
7 from twisted.application import service | |
8 from twisted.spread import pb | |
9 | |
10 from buildbot import scheduler, sourcestamp, buildset, status | |
11 from buildbot.changes.changes import Change | |
12 from buildbot.scripts import tryclient | |
13 | |
14 | |
15 class FakeMaster(service.MultiService): | |
16 d = None | |
17 def submitBuildSet(self, bs): | |
18 self.sets.append(bs) | |
19 if self.d: | |
20 reactor.callLater(0, self.d.callback, bs) | |
21 self.d = None | |
22 return pb.Referenceable() # makes the cleanup work correctly | |
23 | |
24 class Scheduling(unittest.TestCase): | |
25 def setUp(self): | |
26 self.master = master = FakeMaster() | |
27 master.sets = [] | |
28 master.startService() | |
29 | |
30 def tearDown(self): | |
31 d = self.master.stopService() | |
32 return d | |
33 | |
34 def addScheduler(self, s): | |
35 s.setServiceParent(self.master) | |
36 | |
37 def testPeriodic1(self): | |
38 self.addScheduler(scheduler.Periodic("quickly", ["a","b"], 2)) | |
39 d = defer.Deferred() | |
40 reactor.callLater(5, d.callback, None) | |
41 d.addCallback(self._testPeriodic1_1) | |
42 return d | |
43 def _testPeriodic1_1(self, res): | |
44 self.failUnless(len(self.master.sets) > 1) | |
45 s1 = self.master.sets[0] | |
46 self.failUnlessEqual(s1.builderNames, ["a","b"]) | |
47 self.failUnlessEqual(s1.reason, "The Periodic scheduler named 'quickly'
triggered this build") | |
48 | |
49 def testNightly(self): | |
50 # now == 15-Nov-2005, 00:05:36 AM . By using mktime, this is | |
51 # converted into the local timezone, which happens to match what | |
52 # Nightly is going to do anyway. | |
53 MIN=60; HOUR=60*MIN; DAY=24*3600 | |
54 now = time.mktime((2005, 11, 15, 0, 5, 36, 1, 319, -1)) | |
55 | |
56 s = scheduler.Nightly('nightly', ["a"], hour=3) | |
57 t = s.calculateNextRunTimeFrom(now) | |
58 self.failUnlessEqual(int(t-now), 2*HOUR+54*MIN+24) | |
59 | |
60 s = scheduler.Nightly('nightly', ["a"], minute=[3,8,54]) | |
61 t = s.calculateNextRunTimeFrom(now) | |
62 self.failUnlessEqual(int(t-now), 2*MIN+24) | |
63 | |
64 s = scheduler.Nightly('nightly', ["a"], | |
65 dayOfMonth=16, hour=1, minute=6) | |
66 t = s.calculateNextRunTimeFrom(now) | |
67 self.failUnlessEqual(int(t-now), DAY+HOUR+24) | |
68 | |
69 s = scheduler.Nightly('nightly', ["a"], | |
70 dayOfMonth=16, hour=1, minute=3) | |
71 t = s.calculateNextRunTimeFrom(now) | |
72 self.failUnlessEqual(int(t-now), DAY+57*MIN+24) | |
73 | |
74 s = scheduler.Nightly('nightly', ["a"], | |
75 dayOfMonth=15, hour=1, minute=3) | |
76 t = s.calculateNextRunTimeFrom(now) | |
77 self.failUnlessEqual(int(t-now), 57*MIN+24) | |
78 | |
79 s = scheduler.Nightly('nightly', ["a"], | |
80 dayOfMonth=15, hour=0, minute=3) | |
81 t = s.calculateNextRunTimeFrom(now) | |
82 self.failUnlessEqual(int(t-now), 30*DAY-3*MIN+24) | |
83 | |
84 | |
85 def isImportant(self, change): | |
86 if "important" in change.files: | |
87 return True | |
88 return False | |
89 | |
90 def testBranch(self): | |
91 s = scheduler.Scheduler("b1", "branch1", 2, ["a","b"], | |
92 fileIsImportant=self.isImportant) | |
93 self.addScheduler(s) | |
94 | |
95 c0 = Change("carol", ["important"], "other branch", branch="other") | |
96 s.addChange(c0) | |
97 self.failIf(s.timer) | |
98 self.failIf(s.importantChanges) | |
99 | |
100 c1 = Change("alice", ["important", "not important"], "some changes", | |
101 branch="branch1") | |
102 s.addChange(c1) | |
103 c2 = Change("bob", ["not important", "boring"], "some more changes", | |
104 branch="branch1") | |
105 s.addChange(c2) | |
106 c3 = Change("carol", ["important", "dull"], "even more changes", | |
107 branch="branch1") | |
108 s.addChange(c3) | |
109 | |
110 self.failUnlessEqual(s.importantChanges, [c1,c3]) | |
111 self.failUnlessEqual(s.allChanges, [c1,c2,c3]) | |
112 self.failUnless(s.timer) | |
113 | |
114 d = defer.Deferred() | |
115 reactor.callLater(4, d.callback, None) | |
116 d.addCallback(self._testBranch_1) | |
117 return d | |
118 def _testBranch_1(self, res): | |
119 self.failUnlessEqual(len(self.master.sets), 1) | |
120 s = self.master.sets[0].source | |
121 self.failUnlessEqual(s.branch, "branch1") | |
122 self.failUnlessEqual(s.revision, None) | |
123 self.failUnlessEqual(len(s.changes), 3) | |
124 self.failUnlessEqual(s.patch, None) | |
125 | |
126 | |
127 def testAnyBranch(self): | |
128 s = scheduler.AnyBranchScheduler("b1", None, 1, ["a","b"], | |
129 fileIsImportant=self.isImportant) | |
130 self.addScheduler(s) | |
131 | |
132 c1 = Change("alice", ["important", "not important"], "some changes", | |
133 branch="branch1") | |
134 s.addChange(c1) | |
135 c2 = Change("bob", ["not important", "boring"], "some more changes", | |
136 branch="branch1") | |
137 s.addChange(c2) | |
138 c3 = Change("carol", ["important", "dull"], "even more changes", | |
139 branch="branch1") | |
140 s.addChange(c3) | |
141 | |
142 c4 = Change("carol", ["important"], "other branch", branch="branch2") | |
143 s.addChange(c4) | |
144 | |
145 c5 = Change("carol", ["important"], "default branch", branch=None) | |
146 s.addChange(c5) | |
147 | |
148 d = defer.Deferred() | |
149 reactor.callLater(2, d.callback, None) | |
150 d.addCallback(self._testAnyBranch_1) | |
151 return d | |
152 def _testAnyBranch_1(self, res): | |
153 self.failUnlessEqual(len(self.master.sets), 3) | |
154 self.master.sets.sort(lambda a,b: cmp(a.source.branch, | |
155 b.source.branch)) | |
156 | |
157 s1 = self.master.sets[0].source | |
158 self.failUnlessEqual(s1.branch, None) | |
159 self.failUnlessEqual(s1.revision, None) | |
160 self.failUnlessEqual(len(s1.changes), 1) | |
161 self.failUnlessEqual(s1.patch, None) | |
162 | |
163 s2 = self.master.sets[1].source | |
164 self.failUnlessEqual(s2.branch, "branch1") | |
165 self.failUnlessEqual(s2.revision, None) | |
166 self.failUnlessEqual(len(s2.changes), 3) | |
167 self.failUnlessEqual(s2.patch, None) | |
168 | |
169 s3 = self.master.sets[2].source | |
170 self.failUnlessEqual(s3.branch, "branch2") | |
171 self.failUnlessEqual(s3.revision, None) | |
172 self.failUnlessEqual(len(s3.changes), 1) | |
173 self.failUnlessEqual(s3.patch, None) | |
174 | |
175 def testAnyBranch2(self): | |
176 # like testAnyBranch but without fileIsImportant | |
177 s = scheduler.AnyBranchScheduler("b1", None, 2, ["a","b"]) | |
178 self.addScheduler(s) | |
179 c1 = Change("alice", ["important", "not important"], "some changes", | |
180 branch="branch1") | |
181 s.addChange(c1) | |
182 c2 = Change("bob", ["not important", "boring"], "some more changes", | |
183 branch="branch1") | |
184 s.addChange(c2) | |
185 c3 = Change("carol", ["important", "dull"], "even more changes", | |
186 branch="branch1") | |
187 s.addChange(c3) | |
188 | |
189 c4 = Change("carol", ["important"], "other branch", branch="branch2") | |
190 s.addChange(c4) | |
191 | |
192 d = defer.Deferred() | |
193 reactor.callLater(2, d.callback, None) | |
194 d.addCallback(self._testAnyBranch2_1) | |
195 return d | |
196 def _testAnyBranch2_1(self, res): | |
197 self.failUnlessEqual(len(self.master.sets), 2) | |
198 self.master.sets.sort(lambda a,b: cmp(a.source.branch, | |
199 b.source.branch)) | |
200 s1 = self.master.sets[0].source | |
201 self.failUnlessEqual(s1.branch, "branch1") | |
202 self.failUnlessEqual(s1.revision, None) | |
203 self.failUnlessEqual(len(s1.changes), 3) | |
204 self.failUnlessEqual(s1.patch, None) | |
205 | |
206 s2 = self.master.sets[1].source | |
207 self.failUnlessEqual(s2.branch, "branch2") | |
208 self.failUnlessEqual(s2.revision, None) | |
209 self.failUnlessEqual(len(s2.changes), 1) | |
210 self.failUnlessEqual(s2.patch, None) | |
211 | |
212 | |
213 def createMaildir(self, jobdir): | |
214 os.mkdir(jobdir) | |
215 os.mkdir(os.path.join(jobdir, "new")) | |
216 os.mkdir(os.path.join(jobdir, "cur")) | |
217 os.mkdir(os.path.join(jobdir, "tmp")) | |
218 | |
219 jobcounter = 1 | |
220 def pushJob(self, jobdir, job): | |
221 while 1: | |
222 filename = "job_%d" % self.jobcounter | |
223 self.jobcounter += 1 | |
224 if os.path.exists(os.path.join(jobdir, "new", filename)): | |
225 continue | |
226 if os.path.exists(os.path.join(jobdir, "tmp", filename)): | |
227 continue | |
228 if os.path.exists(os.path.join(jobdir, "cur", filename)): | |
229 continue | |
230 break | |
231 f = open(os.path.join(jobdir, "tmp", filename), "w") | |
232 f.write(job) | |
233 f.close() | |
234 os.rename(os.path.join(jobdir, "tmp", filename), | |
235 os.path.join(jobdir, "new", filename)) | |
236 | |
237 def testTryJobdir(self): | |
238 self.master.basedir = "try_jobdir" | |
239 os.mkdir(self.master.basedir) | |
240 jobdir = "jobdir1" | |
241 jobdir_abs = os.path.join(self.master.basedir, jobdir) | |
242 self.createMaildir(jobdir_abs) | |
243 s = scheduler.Try_Jobdir("try1", ["a", "b"], jobdir) | |
244 self.addScheduler(s) | |
245 self.failIf(self.master.sets) | |
246 job1 = tryclient.createJobfile("buildsetID", | |
247 "branch1", "123", 1, "diff", | |
248 ["a", "b"]) | |
249 self.master.d = d = defer.Deferred() | |
250 self.pushJob(jobdir_abs, job1) | |
251 d.addCallback(self._testTryJobdir_1) | |
252 # N.B.: if we don't have DNotify, we poll every 10 seconds, so don't | |
253 # set a .timeout here shorter than that. TODO: make it possible to | |
254 # set the polling interval, so we can make it shorter. | |
255 return d | |
256 | |
257 def _testTryJobdir_1(self, bs): | |
258 self.failUnlessEqual(bs.builderNames, ["a", "b"]) | |
259 self.failUnlessEqual(bs.source.branch, "branch1") | |
260 self.failUnlessEqual(bs.source.revision, "123") | |
261 self.failUnlessEqual(bs.source.patch, (1, "diff")) | |
262 | |
263 | |
264 def testTryUserpass(self): | |
265 up = [("alice","pw1"), ("bob","pw2")] | |
266 s = scheduler.Try_Userpass("try2", ["a", "b"], 0, userpass=up) | |
267 self.addScheduler(s) | |
268 port = s.getPort() | |
269 config = {'connect': 'pb', | |
270 'username': 'alice', | |
271 'passwd': 'pw1', | |
272 'master': "localhost:%d" % port, | |
273 'builders': ["a", "b"], | |
274 } | |
275 t = tryclient.Try(config) | |
276 ss = sourcestamp.SourceStamp("branch1", "123", (1, "diff")) | |
277 t.sourcestamp = ss | |
278 d2 = self.master.d = defer.Deferred() | |
279 d = t.deliverJob() | |
280 d.addCallback(self._testTryUserpass_1, t, d2) | |
281 return d | |
282 testTryUserpass.timeout = 5 | |
283 def _testTryUserpass_1(self, res, t, d2): | |
284 # at this point, the Try object should have a RemoteReference to the | |
285 # status object. The FakeMaster returns a stub. | |
286 self.failUnless(t.buildsetStatus) | |
287 d2.addCallback(self._testTryUserpass_2, t) | |
288 return d2 | |
289 def _testTryUserpass_2(self, bs, t): | |
290 # this should be the BuildSet submitted by the TryScheduler | |
291 self.failUnlessEqual(bs.builderNames, ["a", "b"]) | |
292 self.failUnlessEqual(bs.source.branch, "branch1") | |
293 self.failUnlessEqual(bs.source.revision, "123") | |
294 self.failUnlessEqual(bs.source.patch, (1, "diff")) | |
295 | |
296 t.cleanup() | |
297 | |
298 # twisted-2.0.1 (but not later versions) seems to require a reactor | |
299 # iteration before stopListening actually works. TODO: investigate | |
300 # this. | |
301 d = defer.Deferred() | |
302 reactor.callLater(0, d.callback, None) | |
303 return d | |
304 | |
305 def testGetBuildSets(self): | |
306 # validate IStatus.getBuildSets | |
307 s = status.builder.Status(None, ".") | |
308 bs1 = buildset.BuildSet(["a","b"], sourcestamp.SourceStamp(), | |
309 reason="one", bsid="1") | |
310 s.buildsetSubmitted(bs1.status) | |
311 self.failUnlessEqual(s.getBuildSets(), [bs1.status]) | |
312 bs1.status.notifyFinishedWatchers() | |
313 self.failUnlessEqual(s.getBuildSets(), []) | |
314 | |
315 def testCategory(self): | |
316 s1 = scheduler.Scheduler("b1", "branch1", 2, ["a","b"], categories=["cat
egoryA", "both"]) | |
317 self.addScheduler(s1) | |
318 s2 = scheduler.Scheduler("b2", "branch1", 2, ["a","b"], categories=["cat
egoryB", "both"]) | |
319 self.addScheduler(s2) | |
320 | |
321 c0 = Change("carol", ["important"], "branch1", branch="branch1", categor
y="categoryA") | |
322 s1.addChange(c0) | |
323 s2.addChange(c0) | |
324 | |
325 c1 = Change("carol", ["important"], "branch1", branch="branch1", categor
y="categoryB") | |
326 s1.addChange(c1) | |
327 s2.addChange(c1) | |
328 | |
329 c2 = Change("carol", ["important"], "branch1", branch="branch1") | |
330 s1.addChange(c2) | |
331 s2.addChange(c2) | |
332 | |
333 c3 = Change("carol", ["important"], "branch1", branch="branch1", categor
y="both") | |
334 s1.addChange(c3) | |
335 s2.addChange(c3) | |
336 | |
337 self.failUnlessEqual(s1.importantChanges, [c0, c3]) | |
338 self.failUnlessEqual(s2.importantChanges, [c1, c3]) | |
339 | |
340 s = scheduler.Scheduler("b3", "branch1", 2, ["a","b"]) | |
341 self.addScheduler(s) | |
342 | |
343 c0 = Change("carol", ["important"], "branch1", branch="branch1", categor
y="categoryA") | |
344 s.addChange(c0) | |
345 c1 = Change("carol", ["important"], "branch1", branch="branch1", categor
y="categoryB") | |
346 s.addChange(c1) | |
347 | |
348 self.failUnlessEqual(s.importantChanges, [c0, c1]) | |
OLD | NEW |