OLD | NEW |
| (Empty) |
1 # -*- test-case-name: buildbot.test.test_svnpoller -*- | |
2 | |
3 import time | |
4 from twisted.internet import defer | |
5 from twisted.trial import unittest | |
6 from buildbot.changes.svnpoller import SVNPoller | |
7 | |
8 # this is the output of "svn info --xml | |
9 # svn+ssh://svn.twistedmatrix.com/svn/Twisted/trunk" | |
10 prefix_output = """\ | |
11 <?xml version="1.0"?> | |
12 <info> | |
13 <entry | |
14 kind="dir" | |
15 path="trunk" | |
16 revision="18354"> | |
17 <url>svn+ssh://svn.twistedmatrix.com/svn/Twisted/trunk</url> | |
18 <repository> | |
19 <root>svn+ssh://svn.twistedmatrix.com/svn/Twisted</root> | |
20 <uuid>bbbe8e31-12d6-0310-92fd-ac37d47ddeeb</uuid> | |
21 </repository> | |
22 <commit | |
23 revision="18352"> | |
24 <author>jml</author> | |
25 <date>2006-10-01T02:37:34.063255Z</date> | |
26 </commit> | |
27 </entry> | |
28 </info> | |
29 """ | |
30 | |
31 # and this is "svn info --xml svn://svn.twistedmatrix.com/svn/Twisted". I | |
32 # think this is kind of a degenerate case.. it might even be a form of error. | |
33 prefix_output_2 = """\ | |
34 <?xml version="1.0"?> | |
35 <info> | |
36 </info> | |
37 """ | |
38 | |
39 # this is the svn info output for a local repository, svn info --xml | |
40 # file:///home/warner/stuff/Projects/BuildBot/trees/svnpoller/_trial_temp/test_v
c/repositories/SVN-Repository | |
41 prefix_output_3 = """\ | |
42 <?xml version="1.0"?> | |
43 <info> | |
44 <entry | |
45 kind="dir" | |
46 path="SVN-Repository" | |
47 revision="3"> | |
48 <url>file:///home/warner/stuff/Projects/BuildBot/trees/svnpoller/_trial_temp/tes
t_vc/repositories/SVN-Repository</url> | |
49 <repository> | |
50 <root>file:///home/warner/stuff/Projects/BuildBot/trees/svnpoller/_trial_temp/te
st_vc/repositories/SVN-Repository</root> | |
51 <uuid>c0f47ff4-ba1e-0410-96b5-d44cc5c79e7f</uuid> | |
52 </repository> | |
53 <commit | |
54 revision="3"> | |
55 <author>warner</author> | |
56 <date>2006-10-01T07:37:04.182499Z</date> | |
57 </commit> | |
58 </entry> | |
59 </info> | |
60 """ | |
61 | |
62 # % svn info --xml file:///home/warner/stuff/Projects/BuildBot/trees/svnpoller/_
trial_temp/test_vc/repositories/SVN-Repository/sample/trunk | |
63 | |
64 prefix_output_4 = """\ | |
65 <?xml version="1.0"?> | |
66 <info> | |
67 <entry | |
68 kind="dir" | |
69 path="trunk" | |
70 revision="3"> | |
71 <url>file:///home/warner/stuff/Projects/BuildBot/trees/svnpoller/_trial_temp/tes
t_vc/repositories/SVN-Repository/sample/trunk</url> | |
72 <repository> | |
73 <root>file:///home/warner/stuff/Projects/BuildBot/trees/svnpoller/_trial_temp/te
st_vc/repositories/SVN-Repository</root> | |
74 <uuid>c0f47ff4-ba1e-0410-96b5-d44cc5c79e7f</uuid> | |
75 </repository> | |
76 <commit | |
77 revision="1"> | |
78 <author>warner</author> | |
79 <date>2006-10-01T07:37:02.286440Z</date> | |
80 </commit> | |
81 </entry> | |
82 </info> | |
83 """ | |
84 | |
85 | |
86 | |
87 class ComputePrefix(unittest.TestCase): | |
88 def test1(self): | |
89 base = "svn+ssh://svn.twistedmatrix.com/svn/Twisted/trunk" | |
90 s = SVNPoller(base + "/") | |
91 self.failUnlessEqual(s.svnurl, base) # certify slash-stripping | |
92 prefix = s.determine_prefix(prefix_output) | |
93 self.failUnlessEqual(prefix, "trunk") | |
94 self.failUnlessEqual(s._prefix, prefix) | |
95 | |
96 def test2(self): | |
97 base = "svn+ssh://svn.twistedmatrix.com/svn/Twisted" | |
98 s = SVNPoller(base) | |
99 self.failUnlessEqual(s.svnurl, base) | |
100 prefix = s.determine_prefix(prefix_output_2) | |
101 self.failUnlessEqual(prefix, "") | |
102 | |
103 def test3(self): | |
104 base = "file:///home/warner/stuff/Projects/BuildBot/trees/svnpoller/_tri
al_temp/test_vc/repositories/SVN-Repository" | |
105 s = SVNPoller(base) | |
106 self.failUnlessEqual(s.svnurl, base) | |
107 prefix = s.determine_prefix(prefix_output_3) | |
108 self.failUnlessEqual(prefix, "") | |
109 | |
110 def test4(self): | |
111 base = "file:///home/warner/stuff/Projects/BuildBot/trees/svnpoller/_tri
al_temp/test_vc/repositories/SVN-Repository/sample/trunk" | |
112 s = SVNPoller(base) | |
113 self.failUnlessEqual(s.svnurl, base) | |
114 prefix = s.determine_prefix(prefix_output_4) | |
115 self.failUnlessEqual(prefix, "sample/trunk") | |
116 | |
117 # output from svn log on .../SVN-Repository/sample | |
118 # (so it includes trunk and branches) | |
119 sample_base = "file:///usr/home/warner/stuff/Projects/BuildBot/trees/misc/_trial
_temp/test_vc/repositories/SVN-Repository/sample" | |
120 sample_logentries = [None] * 6 | |
121 | |
122 sample_logentries[5] = """\ | |
123 <logentry | |
124 revision="6"> | |
125 <author>warner</author> | |
126 <date>2006-10-01T19:35:16.165664Z</date> | |
127 <paths> | |
128 <path | |
129 action="D">/sample/branch/version.c</path> | |
130 </paths> | |
131 <msg>revised_to_2</msg> | |
132 </logentry> | |
133 """ | |
134 | |
135 sample_logentries[4] = """\ | |
136 <logentry | |
137 revision="5"> | |
138 <author>warner</author> | |
139 <date>2006-10-01T19:35:16.165664Z</date> | |
140 <paths> | |
141 <path | |
142 action="D">/sample/branch</path> | |
143 </paths> | |
144 <msg>revised_to_2</msg> | |
145 </logentry> | |
146 """ | |
147 | |
148 sample_logentries[3] = """\ | |
149 <logentry | |
150 revision="4"> | |
151 <author>warner</author> | |
152 <date>2006-10-01T19:35:16.165664Z</date> | |
153 <paths> | |
154 <path | |
155 action="M">/sample/trunk/version.c</path> | |
156 </paths> | |
157 <msg>revised_to_2</msg> | |
158 </logentry> | |
159 """ | |
160 | |
161 sample_logentries[2] = """\ | |
162 <logentry | |
163 revision="3"> | |
164 <author>warner</author> | |
165 <date>2006-10-01T19:35:10.215692Z</date> | |
166 <paths> | |
167 <path | |
168 action="M">/sample/branch/main.c</path> | |
169 </paths> | |
170 <msg>commit_on_branch</msg> | |
171 </logentry> | |
172 """ | |
173 | |
174 sample_logentries[1] = """\ | |
175 <logentry | |
176 revision="2"> | |
177 <author>warner</author> | |
178 <date>2006-10-01T19:35:09.154973Z</date> | |
179 <paths> | |
180 <path | |
181 copyfrom-path="/sample/trunk" | |
182 copyfrom-rev="1" | |
183 action="A">/sample/branch</path> | |
184 </paths> | |
185 <msg>make_branch</msg> | |
186 </logentry> | |
187 """ | |
188 | |
189 sample_logentries[0] = """\ | |
190 <logentry | |
191 revision="1"> | |
192 <author>warner</author> | |
193 <date>2006-10-01T19:35:08.642045Z</date> | |
194 <paths> | |
195 <path | |
196 action="A">/sample</path> | |
197 <path | |
198 action="A">/sample/trunk</path> | |
199 <path | |
200 action="A">/sample/trunk/subdir/subdir.c</path> | |
201 <path | |
202 action="A">/sample/trunk/main.c</path> | |
203 <path | |
204 action="A">/sample/trunk/version.c</path> | |
205 <path | |
206 action="A">/sample/trunk/subdir</path> | |
207 </paths> | |
208 <msg>sample_project_files</msg> | |
209 </logentry> | |
210 """ | |
211 | |
212 sample_info_output = """\ | |
213 <?xml version="1.0"?> | |
214 <info> | |
215 <entry | |
216 kind="dir" | |
217 path="sample" | |
218 revision="4"> | |
219 <url>file:///usr/home/warner/stuff/Projects/BuildBot/trees/misc/_trial_temp/test
_vc/repositories/SVN-Repository/sample</url> | |
220 <repository> | |
221 <root>file:///usr/home/warner/stuff/Projects/BuildBot/trees/misc/_trial_temp/tes
t_vc/repositories/SVN-Repository</root> | |
222 <uuid>4f94adfc-c41e-0410-92d5-fbf86b7c7689</uuid> | |
223 </repository> | |
224 <commit | |
225 revision="4"> | |
226 <author>warner</author> | |
227 <date>2006-10-01T19:35:16.165664Z</date> | |
228 </commit> | |
229 </entry> | |
230 </info> | |
231 """ | |
232 | |
233 | |
234 changes_output_template = """\ | |
235 <?xml version="1.0"?> | |
236 <log> | |
237 %s</log> | |
238 """ | |
239 | |
240 def make_changes_output(maxrevision): | |
241 # return what 'svn log' would have just after the given revision was | |
242 # committed | |
243 logs = sample_logentries[0:maxrevision] | |
244 assert len(logs) == maxrevision | |
245 logs.reverse() | |
246 output = changes_output_template % ("".join(logs)) | |
247 return output | |
248 | |
249 def split_file(path): | |
250 pieces = path.split("/") | |
251 if pieces[0] == "branch": | |
252 return "branch", "/".join(pieces[1:]) | |
253 if pieces[0] == "trunk": | |
254 return None, "/".join(pieces[1:]) | |
255 raise RuntimeError("there shouldn't be any files like %s" % path) | |
256 | |
257 class MySVNPoller(SVNPoller): | |
258 def __init__(self, *args, **kwargs): | |
259 SVNPoller.__init__(self, *args, **kwargs) | |
260 self.pending_commands = [] | |
261 self.finished_changes = [] | |
262 | |
263 def getProcessOutput(self, args): | |
264 d = defer.Deferred() | |
265 self.pending_commands.append((args, d)) | |
266 return d | |
267 | |
268 def submit_changes(self, changes): | |
269 self.finished_changes.extend(changes) | |
270 | |
271 class ComputeChanges(unittest.TestCase): | |
272 def test1(self): | |
273 base = "file:///home/warner/stuff/Projects/BuildBot/trees/svnpoller/_tri
al_temp/test_vc/repositories/SVN-Repository/sample" | |
274 s = SVNPoller(base) | |
275 s._prefix = "sample" | |
276 output = make_changes_output(4) | |
277 doc = s.parse_logs(output) | |
278 | |
279 newlast, logentries = s._filter_new_logentries(doc, 4) | |
280 self.failUnlessEqual(newlast, 4) | |
281 self.failUnlessEqual(len(logentries), 0) | |
282 | |
283 newlast, logentries = s._filter_new_logentries(doc, 3) | |
284 self.failUnlessEqual(newlast, 4) | |
285 self.failUnlessEqual(len(logentries), 1) | |
286 | |
287 newlast, logentries = s._filter_new_logentries(doc, 1) | |
288 self.failUnlessEqual(newlast, 4) | |
289 self.failUnlessEqual(len(logentries), 3) | |
290 | |
291 newlast, logentries = s._filter_new_logentries(doc, None) | |
292 self.failUnlessEqual(newlast, 4) | |
293 self.failUnlessEqual(len(logentries), 0) | |
294 | |
295 def testChanges(self): | |
296 base = "file:///home/warner/stuff/Projects/BuildBot/trees/svnpoller/_tri
al_temp/test_vc/repositories/SVN-Repository/sample" | |
297 s = SVNPoller(base, split_file=split_file) | |
298 s._prefix = "sample" | |
299 doc = s.parse_logs(make_changes_output(3)) | |
300 newlast, logentries = s._filter_new_logentries(doc, 1) | |
301 # so we see revisions 2 and 3 as being new | |
302 self.failUnlessEqual(newlast, 3) | |
303 changes = s.create_changes(logentries) | |
304 self.failUnlessEqual(len(changes), 2) | |
305 self.failUnlessEqual(changes[0].branch, "branch") | |
306 self.failUnlessEqual(changes[0].revision, '2') | |
307 self.failUnlessEqual(changes[1].branch, "branch") | |
308 self.failUnlessEqual(changes[1].files, ["main.c"]) | |
309 self.failUnlessEqual(changes[1].revision, '3') | |
310 | |
311 # and now pull in r4 | |
312 doc = s.parse_logs(make_changes_output(4)) | |
313 newlast, logentries = s._filter_new_logentries(doc, newlast) | |
314 self.failUnlessEqual(newlast, 4) | |
315 # so we see revision 4 as being new | |
316 changes = s.create_changes(logentries) | |
317 self.failUnlessEqual(len(changes), 1) | |
318 self.failUnlessEqual(changes[0].branch, None) | |
319 self.failUnlessEqual(changes[0].revision, '4') | |
320 self.failUnlessEqual(changes[0].files, ["version.c"]) | |
321 | |
322 # and now pull in r5 (should *not* create a change as it's a | |
323 # branch deletion | |
324 doc = s.parse_logs(make_changes_output(5)) | |
325 newlast, logentries = s._filter_new_logentries(doc, newlast) | |
326 self.failUnlessEqual(newlast, 5) | |
327 # so we see revision 5 as being new | |
328 changes = s.create_changes(logentries) | |
329 self.failUnlessEqual(len(changes), 0) | |
330 | |
331 # and now pull in r6 (should create a change as it's not | |
332 # deleting an entire branch | |
333 doc = s.parse_logs(make_changes_output(6)) | |
334 newlast, logentries = s._filter_new_logentries(doc, newlast) | |
335 self.failUnlessEqual(newlast, 6) | |
336 # so we see revision 6 as being new | |
337 changes = s.create_changes(logentries) | |
338 self.failUnlessEqual(len(changes), 1) | |
339 self.failUnlessEqual(changes[0].branch, 'branch') | |
340 self.failUnlessEqual(changes[0].revision, '6') | |
341 self.failUnlessEqual(changes[0].files, ["version.c"]) | |
342 | |
343 def testFirstTime(self): | |
344 base = "file:///home/warner/stuff/Projects/BuildBot/trees/svnpoller/_tri
al_temp/test_vc/repositories/SVN-Repository/sample" | |
345 s = SVNPoller(base, split_file=split_file) | |
346 s._prefix = "sample" | |
347 doc = s.parse_logs(make_changes_output(4)) | |
348 logentries = s.get_new_logentries(doc) | |
349 # SVNPoller ignores all changes that happened before it was started | |
350 self.failUnlessEqual(len(logentries), 0) | |
351 self.failUnlessEqual(s.last_change, 4) | |
352 | |
353 class Misc(unittest.TestCase): | |
354 def testAlreadyWorking(self): | |
355 base = "file:///home/warner/stuff/Projects/BuildBot/trees/svnpoller/_tri
al_temp/test_vc/repositories/SVN-Repository/sample" | |
356 s = MySVNPoller(base) | |
357 d = s.checksvn() | |
358 # the SVNPoller is now waiting for its getProcessOutput to finish | |
359 self.failUnlessEqual(s.overrun_counter, 0) | |
360 d2 = s.checksvn() | |
361 self.failUnlessEqual(s.overrun_counter, 1) | |
362 self.failUnlessEqual(len(s.pending_commands), 1) | |
363 | |
364 def testGetRoot(self): | |
365 base = "svn+ssh://svn.twistedmatrix.com/svn/Twisted/trunk" | |
366 s = MySVNPoller(base) | |
367 d = s.checksvn() | |
368 # the SVNPoller is now waiting for its getProcessOutput to finish | |
369 self.failUnlessEqual(len(s.pending_commands), 1) | |
370 self.failUnlessEqual(s.pending_commands[0][0], | |
371 ["info", "--xml", "--non-interactive", base]) | |
372 | |
373 def makeTime(timestring): | |
374 datefmt = '%Y/%m/%d %H:%M:%S' | |
375 when = time.mktime(time.strptime(timestring, datefmt)) | |
376 return when | |
377 | |
378 | |
379 class Everything(unittest.TestCase): | |
380 def test1(self): | |
381 s = MySVNPoller(sample_base, split_file=split_file) | |
382 d = s.checksvn() | |
383 # the SVNPoller is now waiting for its getProcessOutput to finish | |
384 self.failUnlessEqual(len(s.pending_commands), 1) | |
385 self.failUnlessEqual(s.pending_commands[0][0], | |
386 ["info", "--xml", "--non-interactive", | |
387 sample_base]) | |
388 d = s.pending_commands[0][1] | |
389 s.pending_commands.pop(0) | |
390 d.callback(sample_info_output) | |
391 # now it should be waiting for the 'svn log' command | |
392 self.failUnlessEqual(len(s.pending_commands), 1) | |
393 self.failUnlessEqual(s.pending_commands[0][0], | |
394 ["log", "--xml", "--verbose", "--non-interactive", | |
395 "--limit=100", sample_base]) | |
396 d = s.pending_commands[0][1] | |
397 s.pending_commands.pop(0) | |
398 d.callback(make_changes_output(1)) | |
399 # the command ignores the first batch of changes | |
400 self.failUnlessEqual(len(s.finished_changes), 0) | |
401 self.failUnlessEqual(s.last_change, 1) | |
402 | |
403 # now fire it again, nothing changing | |
404 d = s.checksvn() | |
405 self.failUnlessEqual(s.pending_commands[0][0], | |
406 ["log", "--xml", "--verbose", "--non-interactive", | |
407 "--limit=100", sample_base]) | |
408 d = s.pending_commands[0][1] | |
409 s.pending_commands.pop(0) | |
410 d.callback(make_changes_output(1)) | |
411 # nothing has changed | |
412 self.failUnlessEqual(len(s.finished_changes), 0) | |
413 self.failUnlessEqual(s.last_change, 1) | |
414 | |
415 # and again, with r2 this time | |
416 d = s.checksvn() | |
417 self.failUnlessEqual(s.pending_commands[0][0], | |
418 ["log", "--xml", "--verbose", "--non-interactive", | |
419 "--limit=100", sample_base]) | |
420 d = s.pending_commands[0][1] | |
421 s.pending_commands.pop(0) | |
422 d.callback(make_changes_output(2)) | |
423 # r2 should appear | |
424 self.failUnlessEqual(len(s.finished_changes), 1) | |
425 self.failUnlessEqual(s.last_change, 2) | |
426 | |
427 c = s.finished_changes[0] | |
428 self.failUnlessEqual(c.branch, "branch") | |
429 self.failUnlessEqual(c.revision, '2') | |
430 self.failUnlessEqual(c.files, ['']) | |
431 # TODO: this is what creating the branch looks like: a Change with a | |
432 # zero-length file. We should decide if we want filenames like this | |
433 # in the Change (and make sure nobody else gets confused by it) or if | |
434 # we want to strip them out. | |
435 self.failUnlessEqual(c.comments, "make_branch") | |
436 | |
437 # and again at r2, so nothing should change | |
438 d = s.checksvn() | |
439 self.failUnlessEqual(s.pending_commands[0][0], | |
440 ["log", "--xml", "--verbose", "--non-interactive", | |
441 "--limit=100", sample_base]) | |
442 d = s.pending_commands[0][1] | |
443 s.pending_commands.pop(0) | |
444 d.callback(make_changes_output(2)) | |
445 # nothing has changed | |
446 self.failUnlessEqual(len(s.finished_changes), 1) | |
447 self.failUnlessEqual(s.last_change, 2) | |
448 | |
449 # and again with both r3 and r4 appearing together | |
450 d = s.checksvn() | |
451 self.failUnlessEqual(s.pending_commands[0][0], | |
452 ["log", "--xml", "--verbose", "--non-interactive", | |
453 "--limit=100", sample_base]) | |
454 d = s.pending_commands[0][1] | |
455 s.pending_commands.pop(0) | |
456 d.callback(make_changes_output(4)) | |
457 self.failUnlessEqual(len(s.finished_changes), 3) | |
458 self.failUnlessEqual(s.last_change, 4) | |
459 | |
460 c3 = s.finished_changes[1] | |
461 self.failUnlessEqual(c3.branch, "branch") | |
462 self.failUnlessEqual(c3.revision, '3') | |
463 self.failUnlessEqual(c3.files, ["main.c"]) | |
464 self.failUnlessEqual(c3.comments, "commit_on_branch") | |
465 | |
466 c4 = s.finished_changes[2] | |
467 self.failUnlessEqual(c4.branch, None) | |
468 self.failUnlessEqual(c4.revision, '4') | |
469 self.failUnlessEqual(c4.files, ["version.c"]) | |
470 self.failUnlessEqual(c4.comments, "revised_to_2") | |
471 self.failUnless(abs(c4.when - time.time()) < 60) | |
472 | |
473 | |
474 # TODO: | |
475 # get coverage of split_file returning None | |
476 # point at a live SVN server for a little while | |
OLD | NEW |