add a 'reason' tooltip to the yellow start-of-build box
[buildbot.git] / buildbot / test / test_vc.py
blobf65e75575a6f2e80287cdb1f408adb8910d465e0
1 # -*- test-case-name: buildbot.test.test_vc -*-
3 from __future__ import generators
5 import sys, os, signal, shutil, time, re
6 from email.Utils import mktime_tz, parsedate_tz
8 from twisted.trial import unittest
9 from twisted.internet import defer, reactor, utils
11 #defer.Deferred.debug = True
13 from twisted.python import log
14 #log.startLogging(sys.stderr)
16 from buildbot import master, interfaces
17 from buildbot.slave import bot, commands
18 from buildbot.slave.commands import rmdirRecursive
19 from buildbot.status.builder import SUCCESS, FAILURE
20 from buildbot.process import step, base
21 from buildbot.changes import changes
22 from buildbot.sourcestamp import SourceStamp
23 from buildbot.twcompat import maybeWait, which
24 from buildbot.scripts import tryclient
26 #step.LoggedRemoteCommand.debug = True
28 # buildbot.twcompat will patch these into t.i.defer if necessary
29 from twisted.internet.defer import waitForDeferred, deferredGenerator
31 # Most of these tests (all but SourceStamp) depend upon having a set of
32 # repositories from which we can perform checkouts. These repositories are
33 # created by the setUp method at the start of each test class. In earlier
34 # versions these repositories were created offline and distributed with a
35 # separate tarball named 'buildbot-test-vc-1.tar.gz'. This is no longer
36 # necessary.
38 # CVS requires a local file repository. Providing remote access is beyond
39 # the feasible abilities of this test program (needs pserver or ssh).
41 # SVN requires a local file repository. To provide remote access over HTTP
42 # requires an apache server with DAV support and mod_svn, way beyond what we
43 # can test from here.
45 # Arch and Darcs both allow remote (read-only) operation with any web
46 # server. We test both local file access and HTTP access (by spawning a
47 # small web server to provide access to the repository files while the test
48 # is running).
51 config_vc = """
52 from buildbot.process import factory, step
53 s = factory.s
55 f1 = factory.BuildFactory([
56 %s,
58 c = {}
59 c['bots'] = [['bot1', 'sekrit']]
60 c['sources'] = []
61 c['schedulers'] = []
62 c['builders'] = [{'name': 'vc', 'slavename': 'bot1',
63 'builddir': 'vc-dir', 'factory': f1}]
64 c['slavePortnum'] = 0
65 BuildmasterConfig = c
66 """
68 p0_diff = r"""
69 Index: subdir/subdir.c
70 ===================================================================
71 RCS file: /home/warner/stuff/Projects/BuildBot/code-arch/_trial_temp/test_vc/repositories/CVS-Repository/sample/subdir/subdir.c,v
72 retrieving revision 1.1.1.1
73 diff -u -r1.1.1.1 subdir.c
74 --- subdir/subdir.c 14 Aug 2005 01:32:49 -0000 1.1.1.1
75 +++ subdir/subdir.c 14 Aug 2005 01:36:15 -0000
76 @@ -4,6 +4,6 @@
77 int
78 main(int argc, const char *argv[])
80 - printf("Hello subdir.\n");
81 + printf("Hello patched subdir.\n");
82 return 0;
84 """
86 # this patch does not include the filename headers, so it is
87 # patchlevel-neutral
88 TRY_PATCH = '''
89 @@ -5,6 +5,6 @@
90 int
91 main(int argc, const char *argv[])
93 - printf("Hello subdir.\\n");
94 + printf("Hello try.\\n");
95 return 0;
97 '''
99 MAIN_C = '''
100 // this is main.c
101 #include <stdio.h>
104 main(int argc, const char *argv[])
106 printf("Hello world.\\n");
107 return 0;
111 BRANCH_C = '''
112 // this is main.c
113 #include <stdio.h>
116 main(int argc, const char *argv[])
118 printf("Hello branch.\\n");
119 return 0;
123 VERSION_C = '''
124 // this is version.c
125 #include <stdio.h>
128 main(int argc, const char *argv[])
130 printf("Hello world, version=%d\\n");
131 return 0;
135 SUBDIR_C = '''
136 // this is subdir/subdir.c
137 #include <stdio.h>
140 main(int argc, const char *argv[])
142 printf("Hello subdir.\\n");
143 return 0;
147 TRY_C = '''
148 // this is subdir/subdir.c
149 #include <stdio.h>
152 main(int argc, const char *argv[])
154 printf("Hello try.\\n");
155 return 0;
159 class VCS_Helper:
160 # this is a helper class which keeps track of whether each VC system is
161 # available, and whether the repository for each has been created. There
162 # is one instance of this class, at module level, shared between all test
163 # cases.
165 def __init__(self):
166 self._helpers = {}
167 self._isCapable = {}
168 self._excuses = {}
169 self._repoReady = {}
171 def registerVC(self, name, helper):
172 self._helpers[name] = helper
173 self._repoReady[name] = False
175 def skipIfNotCapable(self, name):
176 """Either return None, or raise SkipTest"""
177 d = self.capable(name)
178 def _maybeSkip(res):
179 if not res[0]:
180 raise unittest.SkipTest(res[1])
181 d.addCallback(_maybeSkip)
182 return d
184 def capable(self, name):
185 """Return a Deferred that fires with (True,None) if this host offers
186 the given VC tool, or (False,excuse) if it does not (and therefore
187 the tests should be skipped)."""
189 if self._isCapable.has_key(name):
190 if self._isCapable[name]:
191 return defer.succeed((True,None))
192 else:
193 return defer.succeed((False, self._excuses[name]))
194 d = defer.maybeDeferred(self._helpers[name].capable)
195 def _capable(res):
196 if res[0]:
197 self._isCapable[name] = True
198 else:
199 self._excuses[name] = res[1]
200 return res
201 d.addCallback(_capable)
202 return d
204 def getHelper(self, name):
205 return self._helpers[name]
207 def createRepository(self, name):
208 """Return a Deferred that fires when the repository is set up."""
209 if self._repoReady[name]:
210 return defer.succeed(True)
211 d = self._helpers[name].createRepository()
212 def _ready(res):
213 self._repoReady[name] = True
214 d.addCallback(_ready)
215 return d
217 VCS = VCS_Helper()
219 class SignalMixin:
220 sigchldHandler = None
222 def setUpClass(self):
223 # make sure SIGCHLD handler is installed, as it should be on
224 # reactor.run(). problem is reactor may not have been run when this
225 # test runs.
226 if hasattr(reactor, "_handleSigchld") and hasattr(signal, "SIGCHLD"):
227 self.sigchldHandler = signal.signal(signal.SIGCHLD,
228 reactor._handleSigchld)
230 def tearDownClass(self):
231 if self.sigchldHandler:
232 signal.signal(signal.SIGCHLD, self.sigchldHandler)
235 # the overall plan here:
237 # Each VC system is tested separately, all using the same source tree defined
238 # in the 'files' dictionary above. Each VC system gets its own TestCase
239 # subclass. The first test case that is run will create the repository during
240 # setUp(), making two branches: 'trunk' and 'branch'. The trunk gets a copy
241 # of all the files in 'files'. The variant of good.c is committed on the
242 # branch.
244 # then testCheckout is run, which does a number of checkout/clobber/update
245 # builds. These all use trunk r1. It then runs self.fix(), which modifies
246 # 'fixable.c', then performs another build and makes sure the tree has been
247 # updated.
249 # testBranch uses trunk-r1 and branch-r1, making sure that we clobber the
250 # tree properly when we switch between them
252 # testPatch does a trunk-r1 checkout and applies a patch.
254 # testTryGetPatch performs a trunk-r1 checkout, modifies some files, then
255 # verifies that tryclient.getSourceStamp figures out the base revision and
256 # what got changed.
259 # vc_create makes a repository at r1 with three files: main.c, version.c, and
260 # subdir/foo.c . It also creates a branch from r1 (called b1) in which main.c
261 # says "hello branch" instead of "hello world". self.trunk[] contains
262 # revision stamps for everything on the trunk, and self.branch[] does the
263 # same for the branch.
265 # vc_revise() checks out a tree at HEAD, changes version.c, then checks it
266 # back in. The new version stamp is appended to self.trunk[]. The tree is
267 # removed afterwards.
269 # vc_try_checkout(workdir, rev) checks out a tree at REV, then changes
270 # subdir/subdir.c to say 'Hello try'
271 # vc_try_finish(workdir) removes the tree and cleans up any VC state
272 # necessary (like deleting the Arch archive entry).
275 class BaseHelper:
276 def __init__(self):
277 self.trunk = []
278 self.branch = []
279 self.allrevs = []
281 def capable(self):
282 # this is also responsible for setting self.vcexe
283 raise NotImplementedError
285 def createBasedir(self):
286 # you must call this from createRepository
287 self.repbase = os.path.abspath(os.path.join("test_vc",
288 "repositories"))
289 if not os.path.isdir(self.repbase):
290 os.makedirs(self.repbase)
292 def createRepository(self):
293 # this will only be called once per process
294 raise NotImplementedError
296 def populate(self, basedir):
297 os.makedirs(basedir)
298 os.makedirs(os.path.join(basedir, "subdir"))
299 open(os.path.join(basedir, "main.c"), "w").write(MAIN_C)
300 self.version = 1
301 version_c = VERSION_C % self.version
302 open(os.path.join(basedir, "version.c"), "w").write(version_c)
303 open(os.path.join(basedir, "main.c"), "w").write(MAIN_C)
304 open(os.path.join(basedir, "subdir", "subdir.c"), "w").write(SUBDIR_C)
306 def populate_branch(self, basedir):
307 open(os.path.join(basedir, "main.c"), "w").write(BRANCH_C)
309 def addTrunkRev(self, rev):
310 self.trunk.append(rev)
311 self.allrevs.append(rev)
312 def addBranchRev(self, rev):
313 self.branch.append(rev)
314 self.allrevs.append(rev)
316 def runCommand(self, basedir, command, failureIsOk=False):
317 # all commands passed to do() should be strings or lists. If they are
318 # strings, none of the arguments may have spaces. This makes the
319 # commands less verbose at the expense of restricting what they can
320 # specify.
321 if type(command) not in (list, tuple):
322 command = command.split(" ")
323 #print "do %s" % command
324 env = os.environ.copy()
325 env['LC_ALL'] = "C"
326 d = utils.getProcessOutputAndValue(command[0], command[1:],
327 env=env, path=basedir)
328 def check((out, err, code)):
329 #print
330 #print "command: %s" % command
331 #print "out: %s" % out
332 #print "code: %s" % code
333 if code != 0 and not failureIsOk:
334 log.msg("command %s finished with exit code %d" %
335 (command, code))
336 log.msg(" and stdout %s" % (out,))
337 log.msg(" and stderr %s" % (err,))
338 raise RuntimeError("command %s finished with exit code %d"
339 % (command, code)
340 + ": see logs for stdout")
341 return out
342 d.addCallback(check)
343 return d
345 def do(self, basedir, command, failureIsOk=False):
346 d = self.runCommand(basedir, command, failureIsOk=failureIsOk)
347 return waitForDeferred(d)
349 def dovc(self, basedir, command, failureIsOk=False):
350 """Like do(), but the VC binary will be prepended to COMMAND."""
351 command = self.vcexe + " " + command
352 return self.do(basedir, command, failureIsOk)
354 class VCBase(SignalMixin):
355 metadir = None
356 createdRepository = False
357 master = None
358 slave = None
359 httpServer = None
360 httpPort = None
361 skip = None
362 has_got_revision = False
363 has_got_revision_branches_are_merged = False # for SVN
365 def failUnlessIn(self, substring, string, msg=None):
366 # trial provides a version of this that requires python-2.3 to test
367 # strings.
368 if msg is None:
369 msg = ("did not see the expected substring '%s' in string '%s'" %
370 (substring, string))
371 self.failUnless(string.find(substring) != -1, msg)
373 def setUp(self):
374 d = VCS.skipIfNotCapable(self.vc_name)
375 d.addCallback(self._setUp1)
376 return maybeWait(d)
378 def _setUp1(self, res):
379 self.helper = VCS.getHelper(self.vc_name)
381 if os.path.exists("basedir"):
382 rmdirRecursive("basedir")
383 os.mkdir("basedir")
384 self.master = master.BuildMaster("basedir")
385 self.slavebase = os.path.abspath("slavebase")
386 if os.path.exists(self.slavebase):
387 rmdirRecursive(self.slavebase)
388 os.mkdir("slavebase")
390 d = VCS.createRepository(self.vc_name)
391 return d
393 def connectSlave(self):
394 port = self.master.slavePort._port.getHost().port
395 slave = bot.BuildSlave("localhost", port, "bot1", "sekrit",
396 self.slavebase, keepalive=0, usePTY=1)
397 self.slave = slave
398 slave.startService()
399 d = self.master.botmaster.waitUntilBuilderAttached("vc")
400 return d
402 def loadConfig(self, config):
403 # reloading the config file causes a new 'listDirs' command to be
404 # sent to the slave. To synchronize on this properly, it is easiest
405 # to stop and restart the slave.
406 d = defer.succeed(None)
407 if self.slave:
408 d = self.master.botmaster.waitUntilBuilderDetached("vc")
409 self.slave.stopService()
410 d.addCallback(lambda res: self.master.loadConfig(config))
411 d.addCallback(lambda res: self.connectSlave())
412 return d
414 def serveHTTP(self):
415 # launch an HTTP server to serve the repository files
416 from twisted.web import static, server
417 from twisted.internet import reactor
418 self.root = static.File(self.helper.repbase)
419 self.site = server.Site(self.root)
420 self.httpServer = reactor.listenTCP(0, self.site)
421 self.httpPort = self.httpServer.getHost().port
423 def doBuild(self, shouldSucceed=True, ss=None):
424 c = interfaces.IControl(self.master)
426 if ss is None:
427 ss = SourceStamp()
428 #print "doBuild(ss: b=%s rev=%s)" % (ss.branch, ss.revision)
429 req = base.BuildRequest("test_vc forced build", ss)
430 d = req.waitUntilFinished()
431 c.getBuilder("vc").requestBuild(req)
432 d.addCallback(self._doBuild_1, shouldSucceed)
433 return d
434 def _doBuild_1(self, bs, shouldSucceed):
435 r = bs.getResults()
436 if r != SUCCESS and shouldSucceed:
437 print
438 print
439 if not bs.isFinished():
440 print "Hey, build wasn't even finished!"
441 print "Build did not succeed:", r, bs.getText()
442 for s in bs.getSteps():
443 for l in s.getLogs():
444 print "--- START step %s / log %s ---" % (s.getName(),
445 l.getName())
446 print l.getTextWithHeaders()
447 print "--- STOP ---"
448 print
449 self.fail("build did not succeed")
450 return bs
452 def touch(self, d, f):
453 open(os.path.join(d,f),"w").close()
454 def shouldExist(self, *args):
455 target = os.path.join(*args)
456 self.failUnless(os.path.exists(target),
457 "expected to find %s but didn't" % target)
458 def shouldNotExist(self, *args):
459 target = os.path.join(*args)
460 self.failIf(os.path.exists(target),
461 "expected to NOT find %s, but did" % target)
462 def shouldContain(self, d, f, contents):
463 c = open(os.path.join(d, f), "r").read()
464 self.failUnlessIn(contents, c)
466 def checkGotRevision(self, bs, expected):
467 if self.has_got_revision:
468 self.failUnlessEqual(bs.getProperty("got_revision"), expected)
470 def checkGotRevisionIsLatest(self, bs):
471 expected = self.helper.trunk[-1]
472 if self.has_got_revision_branches_are_merged:
473 expected = self.helper.allrevs[-1]
474 self.checkGotRevision(bs, expected)
476 def do_vctest(self, testRetry=True):
477 vctype = self.vctype
478 args = self.helper.vcargs
479 m = self.master
480 self.vcdir = os.path.join(self.slavebase, "vc-dir", "source")
481 self.workdir = os.path.join(self.slavebase, "vc-dir", "build")
482 # woo double-substitution
483 s = "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype,)
484 for k,v in args.items():
485 s += ", %s=%s" % (k, repr(v))
486 s += ")"
487 config = config_vc % s
489 m.loadConfig(config % 'clobber')
490 m.readConfig = True
491 m.startService()
493 d = self.connectSlave()
494 d.addCallback(lambda res: log.msg("testing clobber"))
495 d.addCallback(self._do_vctest_clobber)
496 d.addCallback(lambda res: log.msg("doing update"))
497 d.addCallback(lambda res: self.loadConfig(config % 'update'))
498 d.addCallback(lambda res: log.msg("testing update"))
499 d.addCallback(self._do_vctest_update)
500 if testRetry:
501 d.addCallback(lambda res: log.msg("testing update retry"))
502 d.addCallback(self._do_vctest_update_retry)
503 d.addCallback(lambda res: log.msg("doing copy"))
504 d.addCallback(lambda res: self.loadConfig(config % 'copy'))
505 d.addCallback(lambda res: log.msg("testing copy"))
506 d.addCallback(self._do_vctest_copy)
507 if self.metadir:
508 d.addCallback(lambda res: log.msg("doing export"))
509 d.addCallback(lambda res: self.loadConfig(config % 'export'))
510 d.addCallback(lambda res: log.msg("testing export"))
511 d.addCallback(self._do_vctest_export)
512 return d
514 def _do_vctest_clobber(self, res):
515 d = self.doBuild() # initial checkout
516 d.addCallback(self._do_vctest_clobber_1)
517 return d
518 def _do_vctest_clobber_1(self, bs):
519 self.shouldExist(self.workdir, "main.c")
520 self.shouldExist(self.workdir, "version.c")
521 self.shouldExist(self.workdir, "subdir", "subdir.c")
522 if self.metadir:
523 self.shouldExist(self.workdir, self.metadir)
524 self.failUnlessEqual(bs.getProperty("revision"), None)
525 self.failUnlessEqual(bs.getProperty("branch"), None)
526 self.checkGotRevisionIsLatest(bs)
528 self.touch(self.workdir, "newfile")
529 self.shouldExist(self.workdir, "newfile")
530 d = self.doBuild() # rebuild clobbers workdir
531 d.addCallback(self._do_vctest_clobber_2)
532 return d
533 def _do_vctest_clobber_2(self, res):
534 self.shouldNotExist(self.workdir, "newfile")
536 def _do_vctest_update(self, res):
537 log.msg("_do_vctest_update")
538 d = self.doBuild() # rebuild with update
539 d.addCallback(self._do_vctest_update_1)
540 return d
541 def _do_vctest_update_1(self, bs):
542 log.msg("_do_vctest_update_1")
543 self.shouldExist(self.workdir, "main.c")
544 self.shouldExist(self.workdir, "version.c")
545 self.shouldContain(self.workdir, "version.c",
546 "version=%d" % self.helper.version)
547 if self.metadir:
548 self.shouldExist(self.workdir, self.metadir)
549 self.failUnlessEqual(bs.getProperty("revision"), None)
550 self.checkGotRevisionIsLatest(bs)
552 self.touch(self.workdir, "newfile")
553 d = self.doBuild() # update rebuild leaves new files
554 d.addCallback(self._do_vctest_update_2)
555 return d
556 def _do_vctest_update_2(self, bs):
557 log.msg("_do_vctest_update_2")
558 self.shouldExist(self.workdir, "main.c")
559 self.shouldExist(self.workdir, "version.c")
560 self.touch(self.workdir, "newfile")
561 # now make a change to the repository and make sure we pick it up
562 d = self.helper.vc_revise()
563 d.addCallback(lambda res: self.doBuild())
564 d.addCallback(self._do_vctest_update_3)
565 return d
566 def _do_vctest_update_3(self, bs):
567 log.msg("_do_vctest_update_3")
568 self.shouldExist(self.workdir, "main.c")
569 self.shouldExist(self.workdir, "version.c")
570 self.shouldContain(self.workdir, "version.c",
571 "version=%d" % self.helper.version)
572 self.shouldExist(self.workdir, "newfile")
573 self.failUnlessEqual(bs.getProperty("revision"), None)
574 self.checkGotRevisionIsLatest(bs)
576 # now "update" to an older revision
577 d = self.doBuild(ss=SourceStamp(revision=self.helper.trunk[-2]))
578 d.addCallback(self._do_vctest_update_4)
579 return d
580 def _do_vctest_update_4(self, bs):
581 log.msg("_do_vctest_update_4")
582 self.shouldExist(self.workdir, "main.c")
583 self.shouldExist(self.workdir, "version.c")
584 self.shouldContain(self.workdir, "version.c",
585 "version=%d" % (self.helper.version-1))
586 self.failUnlessEqual(bs.getProperty("revision"),
587 self.helper.trunk[-2])
588 self.checkGotRevision(bs, self.helper.trunk[-2])
590 # now update to the newer revision
591 d = self.doBuild(ss=SourceStamp(revision=self.helper.trunk[-1]))
592 d.addCallback(self._do_vctest_update_5)
593 return d
594 def _do_vctest_update_5(self, bs):
595 log.msg("_do_vctest_update_5")
596 self.shouldExist(self.workdir, "main.c")
597 self.shouldExist(self.workdir, "version.c")
598 self.shouldContain(self.workdir, "version.c",
599 "version=%d" % self.helper.version)
600 self.failUnlessEqual(bs.getProperty("revision"),
601 self.helper.trunk[-1])
602 self.checkGotRevision(bs, self.helper.trunk[-1])
605 def _do_vctest_update_retry(self, res):
606 # certain local changes will prevent an update from working. The
607 # most common is to replace a file with a directory, or vice
608 # versa. The slave code should spot the failure and do a
609 # clobber/retry.
610 os.unlink(os.path.join(self.workdir, "main.c"))
611 os.mkdir(os.path.join(self.workdir, "main.c"))
612 self.touch(os.path.join(self.workdir, "main.c"), "foo")
613 self.touch(self.workdir, "newfile")
615 d = self.doBuild() # update, but must clobber to handle the error
616 d.addCallback(self._do_vctest_update_retry_1)
617 return d
618 def _do_vctest_update_retry_1(self, bs):
619 self.shouldNotExist(self.workdir, "newfile")
621 def _do_vctest_copy(self, res):
622 d = self.doBuild() # copy rebuild clobbers new files
623 d.addCallback(self._do_vctest_copy_1)
624 return d
625 def _do_vctest_copy_1(self, bs):
626 if self.metadir:
627 self.shouldExist(self.workdir, self.metadir)
628 self.shouldNotExist(self.workdir, "newfile")
629 self.touch(self.workdir, "newfile")
630 self.touch(self.vcdir, "newvcfile")
631 self.failUnlessEqual(bs.getProperty("revision"), None)
632 self.checkGotRevisionIsLatest(bs)
634 d = self.doBuild() # copy rebuild clobbers new files
635 d.addCallback(self._do_vctest_copy_2)
636 return d
637 def _do_vctest_copy_2(self, bs):
638 if self.metadir:
639 self.shouldExist(self.workdir, self.metadir)
640 self.shouldNotExist(self.workdir, "newfile")
641 self.shouldExist(self.vcdir, "newvcfile")
642 self.shouldExist(self.workdir, "newvcfile")
643 self.failUnlessEqual(bs.getProperty("revision"), None)
644 self.checkGotRevisionIsLatest(bs)
645 self.touch(self.workdir, "newfile")
647 def _do_vctest_export(self, res):
648 d = self.doBuild() # export rebuild clobbers new files
649 d.addCallback(self._do_vctest_export_1)
650 return d
651 def _do_vctest_export_1(self, bs):
652 self.shouldNotExist(self.workdir, self.metadir)
653 self.shouldNotExist(self.workdir, "newfile")
654 self.failUnlessEqual(bs.getProperty("revision"), None)
655 #self.checkGotRevisionIsLatest(bs)
656 # VC 'export' is not required to have a got_revision
657 self.touch(self.workdir, "newfile")
659 d = self.doBuild() # export rebuild clobbers new files
660 d.addCallback(self._do_vctest_export_2)
661 return d
662 def _do_vctest_export_2(self, bs):
663 self.shouldNotExist(self.workdir, self.metadir)
664 self.shouldNotExist(self.workdir, "newfile")
665 self.failUnlessEqual(bs.getProperty("revision"), None)
666 #self.checkGotRevisionIsLatest(bs)
667 # VC 'export' is not required to have a got_revision
669 def do_patch(self):
670 vctype = self.vctype
671 args = self.helper.vcargs
672 m = self.master
673 self.vcdir = os.path.join(self.slavebase, "vc-dir", "source")
674 self.workdir = os.path.join(self.slavebase, "vc-dir", "build")
675 s = "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype,)
676 for k,v in args.items():
677 s += ", %s=%s" % (k, repr(v))
678 s += ")"
679 self.config = config_vc % s
681 m.loadConfig(self.config % "clobber")
682 m.readConfig = True
683 m.startService()
685 ss = SourceStamp(revision=self.helper.trunk[-1], patch=(0, p0_diff))
687 d = self.connectSlave()
688 d.addCallback(lambda res: self.doBuild(ss=ss))
689 d.addCallback(self._doPatch_1)
690 return d
691 def _doPatch_1(self, bs):
692 self.shouldContain(self.workdir, "version.c",
693 "version=%d" % self.helper.version)
694 # make sure the file actually got patched
695 subdir_c = os.path.join(self.slavebase, "vc-dir", "build",
696 "subdir", "subdir.c")
697 data = open(subdir_c, "r").read()
698 self.failUnlessIn("Hello patched subdir.\\n", data)
699 self.failUnlessEqual(bs.getProperty("revision"),
700 self.helper.trunk[-1])
701 self.checkGotRevision(bs, self.helper.trunk[-1])
703 # make sure that a rebuild does not use the leftover patched workdir
704 d = self.master.loadConfig(self.config % "update")
705 d.addCallback(lambda res: self.doBuild(ss=None))
706 d.addCallback(self._doPatch_2)
707 return d
708 def _doPatch_2(self, bs):
709 # make sure the file is back to its original
710 subdir_c = os.path.join(self.slavebase, "vc-dir", "build",
711 "subdir", "subdir.c")
712 data = open(subdir_c, "r").read()
713 self.failUnlessIn("Hello subdir.\\n", data)
714 self.failUnlessEqual(bs.getProperty("revision"), None)
715 self.checkGotRevisionIsLatest(bs)
717 # now make sure we can patch an older revision. We need at least two
718 # revisions here, so we might have to create one first
719 if len(self.helper.trunk) < 2:
720 d = self.helper.vc_revise()
721 d.addCallback(self._doPatch_3)
722 return d
723 return self._doPatch_3()
725 def _doPatch_3(self, res=None):
726 ss = SourceStamp(revision=self.helper.trunk[-2], patch=(0, p0_diff))
727 d = self.doBuild(ss=ss)
728 d.addCallback(self._doPatch_4)
729 return d
730 def _doPatch_4(self, bs):
731 self.shouldContain(self.workdir, "version.c",
732 "version=%d" % (self.helper.version-1))
733 # and make sure the file actually got patched
734 subdir_c = os.path.join(self.slavebase, "vc-dir", "build",
735 "subdir", "subdir.c")
736 data = open(subdir_c, "r").read()
737 self.failUnlessIn("Hello patched subdir.\\n", data)
738 self.failUnlessEqual(bs.getProperty("revision"),
739 self.helper.trunk[-2])
740 self.checkGotRevision(bs, self.helper.trunk[-2])
742 # now check that we can patch a branch
743 ss = SourceStamp(branch=self.helper.branchname,
744 revision=self.helper.branch[-1],
745 patch=(0, p0_diff))
746 d = self.doBuild(ss=ss)
747 d.addCallback(self._doPatch_5)
748 return d
749 def _doPatch_5(self, bs):
750 self.shouldContain(self.workdir, "version.c",
751 "version=%d" % 1)
752 self.shouldContain(self.workdir, "main.c", "Hello branch.")
753 subdir_c = os.path.join(self.slavebase, "vc-dir", "build",
754 "subdir", "subdir.c")
755 data = open(subdir_c, "r").read()
756 self.failUnlessIn("Hello patched subdir.\\n", data)
757 self.failUnlessEqual(bs.getProperty("revision"),
758 self.helper.branch[-1])
759 self.failUnlessEqual(bs.getProperty("branch"), self.helper.branchname)
760 self.checkGotRevision(bs, self.helper.branch[-1])
763 def do_vctest_once(self, shouldSucceed):
764 m = self.master
765 vctype = self.vctype
766 args = self.helper.vcargs
767 vcdir = os.path.join(self.slavebase, "vc-dir", "source")
768 workdir = os.path.join(self.slavebase, "vc-dir", "build")
769 # woo double-substitution
770 s = "s(%s, timeout=200, workdir='build', mode='clobber'" % (vctype,)
771 for k,v in args.items():
772 s += ", %s=%s" % (k, repr(v))
773 s += ")"
774 config = config_vc % s
776 m.loadConfig(config)
777 m.readConfig = True
778 m.startService()
780 self.connectSlave()
781 d = self.doBuild(shouldSucceed) # initial checkout
782 return d
784 def do_branch(self):
785 log.msg("do_branch")
786 vctype = self.vctype
787 args = self.helper.vcargs
788 m = self.master
789 self.vcdir = os.path.join(self.slavebase, "vc-dir", "source")
790 self.workdir = os.path.join(self.slavebase, "vc-dir", "build")
791 s = "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype,)
792 for k,v in args.items():
793 s += ", %s=%s" % (k, repr(v))
794 s += ")"
795 self.config = config_vc % s
797 m.loadConfig(self.config % "update")
798 m.readConfig = True
799 m.startService()
801 # first we do a build of the trunk
802 d = self.connectSlave()
803 d.addCallback(lambda res: self.doBuild(ss=SourceStamp()))
804 d.addCallback(self._doBranch_1)
805 return d
806 def _doBranch_1(self, bs):
807 log.msg("_doBranch_1")
808 # make sure the checkout was of the trunk
809 main_c = os.path.join(self.slavebase, "vc-dir", "build", "main.c")
810 data = open(main_c, "r").read()
811 self.failUnlessIn("Hello world.", data)
813 # now do a checkout on the branch. The change in branch name should
814 # trigger a clobber.
815 self.touch(self.workdir, "newfile")
816 d = self.doBuild(ss=SourceStamp(branch=self.helper.branchname))
817 d.addCallback(self._doBranch_2)
818 return d
819 def _doBranch_2(self, bs):
820 log.msg("_doBranch_2")
821 # make sure it was on the branch
822 main_c = os.path.join(self.slavebase, "vc-dir", "build", "main.c")
823 data = open(main_c, "r").read()
824 self.failUnlessIn("Hello branch.", data)
825 # and make sure the tree was clobbered
826 self.shouldNotExist(self.workdir, "newfile")
828 # doing another build on the same branch should not clobber the tree
829 self.touch(self.workdir, "newbranchfile")
830 d = self.doBuild(ss=SourceStamp(branch=self.helper.branchname))
831 d.addCallback(self._doBranch_3)
832 return d
833 def _doBranch_3(self, bs):
834 log.msg("_doBranch_3")
835 # make sure it is still on the branch
836 main_c = os.path.join(self.slavebase, "vc-dir", "build", "main.c")
837 data = open(main_c, "r").read()
838 self.failUnlessIn("Hello branch.", data)
839 # and make sure the tree was not clobbered
840 self.shouldExist(self.workdir, "newbranchfile")
842 # now make sure that a non-branch checkout clobbers the tree
843 d = self.doBuild(ss=SourceStamp())
844 d.addCallback(self._doBranch_4)
845 return d
846 def _doBranch_4(self, bs):
847 log.msg("_doBranch_4")
848 # make sure it was on the trunk
849 main_c = os.path.join(self.slavebase, "vc-dir", "build", "main.c")
850 data = open(main_c, "r").read()
851 self.failUnlessIn("Hello world.", data)
852 self.shouldNotExist(self.workdir, "newbranchfile")
854 def do_getpatch(self, doBranch=True):
855 log.msg("do_getpatch")
856 # prepare a buildslave to do checkouts
857 vctype = self.vctype
858 args = self.helper.vcargs
859 m = self.master
860 self.vcdir = os.path.join(self.slavebase, "vc-dir", "source")
861 self.workdir = os.path.join(self.slavebase, "vc-dir", "build")
862 # woo double-substitution
863 s = "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype,)
864 for k,v in args.items():
865 s += ", %s=%s" % (k, repr(v))
866 s += ")"
867 config = config_vc % s
869 m.loadConfig(config % 'clobber')
870 m.readConfig = True
871 m.startService()
873 d = self.connectSlave()
875 # then set up the "developer's tree". first we modify a tree from the
876 # head of the trunk
877 tmpdir = "try_workdir"
878 self.trydir = os.path.join(self.helper.repbase, tmpdir)
879 rmdirRecursive(self.trydir)
880 d.addCallback(self.do_getpatch_trunkhead)
881 d.addCallback(self.do_getpatch_trunkold)
882 if doBranch:
883 d.addCallback(self.do_getpatch_branch)
884 d.addCallback(self.do_getpatch_finish)
885 return d
887 def do_getpatch_finish(self, res):
888 log.msg("do_getpatch_finish")
889 self.helper.vc_try_finish(self.trydir)
890 return res
892 def try_shouldMatch(self, filename):
893 devfilename = os.path.join(self.trydir, filename)
894 devfile = open(devfilename, "r").read()
895 slavefilename = os.path.join(self.workdir, filename)
896 slavefile = open(slavefilename, "r").read()
897 self.failUnlessEqual(devfile, slavefile,
898 ("slavefile (%s) contains '%s'. "
899 "developer's file (%s) contains '%s'. "
900 "These ought to match") %
901 (slavefilename, slavefile,
902 devfilename, devfile))
904 def do_getpatch_trunkhead(self, res):
905 log.msg("do_getpatch_trunkhead")
906 d = self.helper.vc_try_checkout(self.trydir, self.helper.trunk[-1])
907 d.addCallback(self._do_getpatch_trunkhead_1)
908 return d
909 def _do_getpatch_trunkhead_1(self, res):
910 log.msg("_do_getpatch_trunkhead_1")
911 d = tryclient.getSourceStamp(self.vctype_try, self.trydir, None)
912 d.addCallback(self._do_getpatch_trunkhead_2)
913 return d
914 def _do_getpatch_trunkhead_2(self, ss):
915 log.msg("_do_getpatch_trunkhead_2")
916 d = self.doBuild(ss=ss)
917 d.addCallback(self._do_getpatch_trunkhead_3)
918 return d
919 def _do_getpatch_trunkhead_3(self, res):
920 log.msg("_do_getpatch_trunkhead_3")
921 # verify that the resulting buildslave tree matches the developer's
922 self.try_shouldMatch("main.c")
923 self.try_shouldMatch("version.c")
924 self.try_shouldMatch(os.path.join("subdir", "subdir.c"))
926 def do_getpatch_trunkold(self, res):
927 log.msg("do_getpatch_trunkold")
928 # now try a tree from an older revision. We need at least two
929 # revisions here, so we might have to create one first
930 if len(self.helper.trunk) < 2:
931 d = self.helper.vc_revise()
932 d.addCallback(self._do_getpatch_trunkold_1)
933 return d
934 return self._do_getpatch_trunkold_1()
935 def _do_getpatch_trunkold_1(self, res=None):
936 log.msg("_do_getpatch_trunkold_1")
937 d = self.helper.vc_try_checkout(self.trydir, self.helper.trunk[-2])
938 d.addCallback(self._do_getpatch_trunkold_2)
939 return d
940 def _do_getpatch_trunkold_2(self, res):
941 log.msg("_do_getpatch_trunkold_2")
942 d = tryclient.getSourceStamp(self.vctype_try, self.trydir, None)
943 d.addCallback(self._do_getpatch_trunkold_3)
944 return d
945 def _do_getpatch_trunkold_3(self, ss):
946 log.msg("_do_getpatch_trunkold_3")
947 d = self.doBuild(ss=ss)
948 d.addCallback(self._do_getpatch_trunkold_4)
949 return d
950 def _do_getpatch_trunkold_4(self, res):
951 log.msg("_do_getpatch_trunkold_4")
952 # verify that the resulting buildslave tree matches the developer's
953 self.try_shouldMatch("main.c")
954 self.try_shouldMatch("version.c")
955 self.try_shouldMatch(os.path.join("subdir", "subdir.c"))
957 def do_getpatch_branch(self, res):
958 log.msg("do_getpatch_branch")
959 # now try a tree from a branch
960 d = self.helper.vc_try_checkout(self.trydir, self.helper.branch[-1],
961 self.helper.branchname)
962 d.addCallback(self._do_getpatch_branch_1)
963 return d
964 def _do_getpatch_branch_1(self, res):
965 log.msg("_do_getpatch_branch_1")
966 d = tryclient.getSourceStamp(self.vctype_try, self.trydir,
967 self.helper.try_branchname)
968 d.addCallback(self._do_getpatch_branch_2)
969 return d
970 def _do_getpatch_branch_2(self, ss):
971 log.msg("_do_getpatch_branch_2")
972 d = self.doBuild(ss=ss)
973 d.addCallback(self._do_getpatch_branch_3)
974 return d
975 def _do_getpatch_branch_3(self, res):
976 log.msg("_do_getpatch_branch_3")
977 # verify that the resulting buildslave tree matches the developer's
978 self.try_shouldMatch("main.c")
979 self.try_shouldMatch("version.c")
980 self.try_shouldMatch(os.path.join("subdir", "subdir.c"))
983 def dumpPatch(self, patch):
984 # this exists to help me figure out the right 'patchlevel' value
985 # should be returned by tryclient.getSourceStamp
986 n = self.mktemp()
987 open(n,"w").write(patch)
988 d = self.runCommand(".", ["lsdiff", n])
989 def p(res): print "lsdiff:", res.strip().split("\n")
990 d.addCallback(p)
991 return d
994 def tearDown(self):
995 d = defer.succeed(None)
996 if self.slave:
997 d2 = self.master.botmaster.waitUntilBuilderDetached("vc")
998 d.addCallback(lambda res: self.slave.stopService())
999 d.addCallback(lambda res: d2)
1000 if self.master:
1001 d.addCallback(lambda res: self.master.stopService())
1002 if self.httpServer:
1003 d.addCallback(lambda res: self.httpServer.stopListening())
1004 def stopHTTPTimer():
1005 try:
1006 from twisted.web import http # Twisted-2.0
1007 except ImportError:
1008 from twisted.protocols import http # Twisted-1.3
1009 http._logDateTimeStop() # shut down the internal timer. DUMB!
1010 d.addCallback(lambda res: stopHTTPTimer())
1011 d.addCallback(lambda res: self.tearDown2())
1012 return maybeWait(d)
1014 def tearDown2(self):
1015 pass
1017 class CVSHelper(BaseHelper):
1018 branchname = "branch"
1019 try_branchname = "branch"
1021 def capable(self):
1022 cvspaths = which('cvs')
1023 if not cvspaths:
1024 return (False, "CVS is not installed")
1025 # cvs-1.10 (as shipped with OS-X 10.3 "Panther") is too old for this
1026 # test. There is a situation where we check out a tree, make a
1027 # change, then commit it back, and CVS refuses to believe that we're
1028 # operating in a CVS tree. I tested cvs-1.12.9 and it works ok, OS-X
1029 # 10.4 "Tiger" comes with cvs-1.11, but I haven't tested that yet.
1030 # For now, skip the tests if we've got 1.10 .
1031 log.msg("running %s --version.." % (cvspaths[0],))
1032 d = utils.getProcessOutput(cvspaths[0], ["--version"],
1033 env=os.environ)
1034 d.addCallback(self._capable, cvspaths[0])
1035 return d
1037 def _capable(self, v, vcexe):
1038 m = re.search(r'\(CVS\) ([\d\.]+) ', v)
1039 if not m:
1040 log.msg("couldn't identify CVS version number in output:")
1041 log.msg("'''%s'''" % v)
1042 log.msg("skipping tests")
1043 return (False, "Found CVS but couldn't identify its version")
1044 ver = m.group(1)
1045 log.msg("found CVS version '%s'" % ver)
1046 if ver == "1.10":
1047 return (False, "Found CVS, but it is too old")
1048 self.vcexe = vcexe
1049 return (True, None)
1051 def getdate(self):
1052 # this timestamp is eventually passed to CVS in a -D argument, and
1053 # strftime's %z specifier doesn't seem to work reliably (I get +0000
1054 # where I should get +0700 under linux sometimes, and windows seems
1055 # to want to put a verbose 'Eastern Standard Time' in there), so
1056 # leave off the timezone specifier and treat this as localtime. A
1057 # valid alternative would be to use a hard-coded +0000 and
1058 # time.gmtime().
1059 return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
1061 def createRepository(self):
1062 self.createBasedir()
1063 self.cvsrep = cvsrep = os.path.join(self.repbase, "CVS-Repository")
1064 tmp = os.path.join(self.repbase, "cvstmp")
1066 w = self.dovc(self.repbase, "-d %s init" % cvsrep)
1067 yield w; w.getResult() # we must getResult() to raise any exceptions
1069 self.populate(tmp)
1070 cmd = ("-d %s import" % cvsrep +
1071 " -m sample_project_files sample vendortag start")
1072 w = self.dovc(tmp, cmd)
1073 yield w; w.getResult()
1074 rmdirRecursive(tmp)
1075 # take a timestamp as the first revision number
1076 time.sleep(2)
1077 self.addTrunkRev(self.getdate())
1078 time.sleep(2)
1080 w = self.dovc(self.repbase,
1081 "-d %s checkout -d cvstmp sample" % self.cvsrep)
1082 yield w; w.getResult()
1084 w = self.dovc(tmp, "tag -b %s" % self.branchname)
1085 yield w; w.getResult()
1086 self.populate_branch(tmp)
1087 w = self.dovc(tmp,
1088 "commit -m commit_on_branch -r %s" % self.branchname)
1089 yield w; w.getResult()
1090 rmdirRecursive(tmp)
1091 time.sleep(2)
1092 self.addBranchRev(self.getdate())
1093 time.sleep(2)
1094 self.vcargs = { 'cvsroot': self.cvsrep, 'cvsmodule': "sample" }
1095 createRepository = deferredGenerator(createRepository)
1098 def vc_revise(self):
1099 tmp = os.path.join(self.repbase, "cvstmp")
1101 w = self.dovc(self.repbase,
1102 "-d %s checkout -d cvstmp sample" % self.cvsrep)
1103 yield w; w.getResult()
1104 self.version += 1
1105 version_c = VERSION_C % self.version
1106 open(os.path.join(tmp, "version.c"), "w").write(version_c)
1107 w = self.dovc(tmp,
1108 "commit -m revised_to_%d version.c" % self.version)
1109 yield w; w.getResult()
1110 rmdirRecursive(tmp)
1111 time.sleep(2)
1112 self.addTrunkRev(self.getdate())
1113 time.sleep(2)
1114 vc_revise = deferredGenerator(vc_revise)
1116 def vc_try_checkout(self, workdir, rev, branch=None):
1117 # 'workdir' is an absolute path
1118 assert os.path.abspath(workdir) == workdir
1119 cmd = [self.vcexe, "-d", self.cvsrep, "checkout",
1120 "-d", workdir,
1121 "-D", rev]
1122 if branch is not None:
1123 cmd.append("-r")
1124 cmd.append(branch)
1125 cmd.append("sample")
1126 w = self.do(self.repbase, cmd)
1127 yield w; w.getResult()
1128 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
1129 vc_try_checkout = deferredGenerator(vc_try_checkout)
1131 def vc_try_finish(self, workdir):
1132 rmdirRecursive(workdir)
1134 class CVS(VCBase, unittest.TestCase):
1135 vc_name = "cvs"
1137 metadir = "CVS"
1138 vctype = "step.CVS"
1139 vctype_try = "cvs"
1140 # CVS gives us got_revision, but it is based entirely upon the local
1141 # clock, which means it is unlikely to match the timestamp taken earlier.
1142 # This might be enough for common use, but won't be good enough for our
1143 # tests to accept, so pretend it doesn't have got_revision at all.
1144 has_got_revision = False
1146 def testCheckout(self):
1147 d = self.do_vctest()
1148 return maybeWait(d)
1150 def testPatch(self):
1151 d = self.do_patch()
1152 return maybeWait(d)
1154 def testCheckoutBranch(self):
1155 d = self.do_branch()
1156 return maybeWait(d)
1158 def testTry(self):
1159 d = self.do_getpatch(doBranch=False)
1160 return maybeWait(d)
1162 VCS.registerVC(CVS.vc_name, CVSHelper())
1165 class SVNHelper(BaseHelper):
1166 branchname = "sample/branch"
1167 try_branchname = "sample/branch"
1169 def capable(self):
1170 svnpaths = which('svn')
1171 svnadminpaths = which('svnadmin')
1172 if not svnpaths:
1173 return (False, "SVN is not installed")
1174 if not svnadminpaths:
1175 return (False, "svnadmin is not installed")
1176 # we need svn to be compiled with the ra_local access
1177 # module
1178 log.msg("running svn --version..")
1179 env = os.environ.copy()
1180 env['LC_ALL'] = "C"
1181 d = utils.getProcessOutput(svnpaths[0], ["--version"],
1182 env=env)
1183 d.addCallback(self._capable, svnpaths[0], svnadminpaths[0])
1184 return d
1186 def _capable(self, v, vcexe, svnadmin):
1187 if v.find("handles 'file' schem") != -1:
1188 # older versions say 'schema', 1.2.0 and beyond say 'scheme'
1189 self.vcexe = vcexe
1190 self.svnadmin = svnadmin
1191 return (True, None)
1192 excuse = ("%s found but it does not support 'file:' " +
1193 "schema, skipping svn tests") % vcexe
1194 log.msg(excuse)
1195 return (False, excuse)
1197 def createRepository(self):
1198 self.createBasedir()
1199 self.svnrep = os.path.join(self.repbase,
1200 "SVN-Repository").replace('\\','/')
1201 tmp = os.path.join(self.repbase, "svntmp")
1202 if sys.platform == 'win32':
1203 # On Windows Paths do not start with a /
1204 self.svnurl = "file:///%s" % self.svnrep
1205 else:
1206 self.svnurl = "file://%s" % self.svnrep
1207 self.svnurl_trunk = self.svnurl + "/sample/trunk"
1208 self.svnurl_branch = self.svnurl + "/sample/branch"
1210 w = self.do(self.repbase, self.svnadmin+" create %s" % self.svnrep)
1211 yield w; w.getResult()
1213 self.populate(tmp)
1214 w = self.dovc(tmp,
1215 "import -m sample_project_files %s" %
1216 self.svnurl_trunk)
1217 yield w; out = w.getResult()
1218 rmdirRecursive(tmp)
1219 m = re.search(r'Committed revision (\d+)\.', out)
1220 assert m.group(1) == "1" # first revision is always "1"
1221 self.addTrunkRev(int(m.group(1)))
1223 w = self.dovc(self.repbase,
1224 "checkout %s svntmp" % self.svnurl_trunk)
1225 yield w; w.getResult()
1227 w = self.dovc(tmp, "cp -m make_branch %s %s" % (self.svnurl_trunk,
1228 self.svnurl_branch))
1229 yield w; w.getResult()
1230 w = self.dovc(tmp, "switch %s" % self.svnurl_branch)
1231 yield w; w.getResult()
1232 self.populate_branch(tmp)
1233 w = self.dovc(tmp, "commit -m commit_on_branch")
1234 yield w; out = w.getResult()
1235 rmdirRecursive(tmp)
1236 m = re.search(r'Committed revision (\d+)\.', out)
1237 self.addBranchRev(int(m.group(1)))
1238 createRepository = deferredGenerator(createRepository)
1240 def vc_revise(self):
1241 tmp = os.path.join(self.repbase, "svntmp")
1242 rmdirRecursive(tmp)
1243 log.msg("vc_revise" + self.svnurl_trunk)
1244 w = self.dovc(self.repbase,
1245 "checkout %s svntmp" % self.svnurl_trunk)
1246 yield w; w.getResult()
1247 self.version += 1
1248 version_c = VERSION_C % self.version
1249 open(os.path.join(tmp, "version.c"), "w").write(version_c)
1250 w = self.dovc(tmp, "commit -m revised_to_%d" % self.version)
1251 yield w; out = w.getResult()
1252 m = re.search(r'Committed revision (\d+)\.', out)
1253 self.addTrunkRev(int(m.group(1)))
1254 rmdirRecursive(tmp)
1255 vc_revise = deferredGenerator(vc_revise)
1257 def vc_try_checkout(self, workdir, rev, branch=None):
1258 assert os.path.abspath(workdir) == workdir
1259 if os.path.exists(workdir):
1260 rmdirRecursive(workdir)
1261 if not branch:
1262 svnurl = self.svnurl_trunk
1263 else:
1264 # N.B.: this is *not* os.path.join: SVN URLs use slashes
1265 # regardless of the host operating system's filepath separator
1266 svnurl = self.svnurl + "/" + branch
1267 w = self.dovc(self.repbase,
1268 "checkout %s %s" % (svnurl, workdir))
1269 yield w; w.getResult()
1270 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
1271 vc_try_checkout = deferredGenerator(vc_try_checkout)
1273 def vc_try_finish(self, workdir):
1274 rmdirRecursive(workdir)
1277 class SVN(VCBase, unittest.TestCase):
1278 vc_name = "svn"
1280 metadir = ".svn"
1281 vctype = "step.SVN"
1282 vctype_try = "svn"
1283 has_got_revision = True
1284 has_got_revision_branches_are_merged = True
1286 def testCheckout(self):
1287 # we verify this one with the svnurl style of vcargs. We test the
1288 # baseURL/defaultBranch style in testPatch and testCheckoutBranch.
1289 self.helper.vcargs = { 'svnurl': self.helper.svnurl_trunk }
1290 d = self.do_vctest()
1291 return maybeWait(d)
1293 def testPatch(self):
1294 self.helper.vcargs = { 'baseURL': self.helper.svnurl + "/",
1295 'defaultBranch': "sample/trunk",
1297 d = self.do_patch()
1298 return maybeWait(d)
1300 def testCheckoutBranch(self):
1301 self.helper.vcargs = { 'baseURL': self.helper.svnurl + "/",
1302 'defaultBranch': "sample/trunk",
1304 d = self.do_branch()
1305 return maybeWait(d)
1307 def testTry(self):
1308 # extract the base revision and patch from a modified tree, use it to
1309 # create the same contents on the buildslave
1310 self.helper.vcargs = { 'baseURL': self.helper.svnurl + "/",
1311 'defaultBranch': "sample/trunk",
1313 d = self.do_getpatch()
1314 return maybeWait(d)
1316 VCS.registerVC(SVN.vc_name, SVNHelper())
1318 class DarcsHelper(BaseHelper):
1319 branchname = "branch"
1320 try_branchname = "branch"
1322 def capable(self):
1323 darcspaths = which('darcs')
1324 if not darcspaths:
1325 return (False, "Darcs is not installed")
1326 self.vcexe = darcspaths[0]
1327 return (True, None)
1329 def createRepository(self):
1330 self.createBasedir()
1331 self.darcs_base = os.path.join(self.repbase, "Darcs-Repository")
1332 self.rep_trunk = os.path.join(self.darcs_base, "trunk")
1333 self.rep_branch = os.path.join(self.darcs_base, "branch")
1334 tmp = os.path.join(self.repbase, "darcstmp")
1336 os.makedirs(self.rep_trunk)
1337 w = self.dovc(self.rep_trunk, "initialize")
1338 yield w; w.getResult()
1339 os.makedirs(self.rep_branch)
1340 w = self.dovc(self.rep_branch, "initialize")
1341 yield w; w.getResult()
1343 self.populate(tmp)
1344 w = self.dovc(tmp, "initialize")
1345 yield w; w.getResult()
1346 w = self.dovc(tmp, "add -r .")
1347 yield w; w.getResult()
1348 w = self.dovc(tmp, "record -a -m initial_import --skip-long-comment -A test@buildbot.sf.net")
1349 yield w; w.getResult()
1350 w = self.dovc(tmp, "push -a %s" % self.rep_trunk)
1351 yield w; w.getResult()
1352 w = self.dovc(tmp, "changes --context")
1353 yield w; out = w.getResult()
1354 self.addTrunkRev(out)
1356 self.populate_branch(tmp)
1357 w = self.dovc(tmp, "record -a --ignore-times -m commit_on_branch --skip-long-comment -A test@buildbot.sf.net")
1358 yield w; w.getResult()
1359 w = self.dovc(tmp, "push -a %s" % self.rep_branch)
1360 yield w; w.getResult()
1361 w = self.dovc(tmp, "changes --context")
1362 yield w; out = w.getResult()
1363 self.addBranchRev(out)
1364 rmdirRecursive(tmp)
1365 createRepository = deferredGenerator(createRepository)
1367 def vc_revise(self):
1368 tmp = os.path.join(self.repbase, "darcstmp")
1369 os.makedirs(tmp)
1370 w = self.dovc(tmp, "initialize")
1371 yield w; w.getResult()
1372 w = self.dovc(tmp, "pull -a %s" % self.rep_trunk)
1373 yield w; w.getResult()
1375 self.version += 1
1376 version_c = VERSION_C % self.version
1377 open(os.path.join(tmp, "version.c"), "w").write(version_c)
1378 w = self.dovc(tmp, "record -a --ignore-times -m revised_to_%d --skip-long-comment -A test@buildbot.sf.net" % self.version)
1379 yield w; w.getResult()
1380 w = self.dovc(tmp, "push -a %s" % self.rep_trunk)
1381 yield w; w.getResult()
1382 w = self.dovc(tmp, "changes --context")
1383 yield w; out = w.getResult()
1384 self.addTrunkRev(out)
1385 rmdirRecursive(tmp)
1386 vc_revise = deferredGenerator(vc_revise)
1388 def vc_try_checkout(self, workdir, rev, branch=None):
1389 assert os.path.abspath(workdir) == workdir
1390 if os.path.exists(workdir):
1391 rmdirRecursive(workdir)
1392 os.makedirs(workdir)
1393 w = self.dovc(workdir, "initialize")
1394 yield w; w.getResult()
1395 if not branch:
1396 rep = self.rep_trunk
1397 else:
1398 rep = os.path.join(self.darcs_base, branch)
1399 w = self.dovc(workdir, "pull -a %s" % rep)
1400 yield w; w.getResult()
1401 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
1402 vc_try_checkout = deferredGenerator(vc_try_checkout)
1404 def vc_try_finish(self, workdir):
1405 rmdirRecursive(workdir)
1408 class Darcs(VCBase, unittest.TestCase):
1409 vc_name = "darcs"
1411 # Darcs has a metadir="_darcs", but it does not have an 'export'
1412 # mode
1413 metadir = None
1414 vctype = "step.Darcs"
1415 vctype_try = "darcs"
1416 has_got_revision = True
1418 def testCheckout(self):
1419 self.helper.vcargs = { 'repourl': self.helper.rep_trunk }
1420 d = self.do_vctest(testRetry=False)
1422 # TODO: testRetry has the same problem with Darcs as it does for
1423 # Arch
1424 return maybeWait(d)
1426 def testPatch(self):
1427 self.helper.vcargs = { 'baseURL': self.helper.darcs_base + "/",
1428 'defaultBranch': "trunk" }
1429 d = self.do_patch()
1430 return maybeWait(d)
1432 def testCheckoutBranch(self):
1433 self.helper.vcargs = { 'baseURL': self.helper.darcs_base + "/",
1434 'defaultBranch': "trunk" }
1435 d = self.do_branch()
1436 return maybeWait(d)
1438 def testCheckoutHTTP(self):
1439 self.serveHTTP()
1440 repourl = "http://localhost:%d/Darcs-Repository/trunk" % self.httpPort
1441 self.helper.vcargs = { 'repourl': repourl }
1442 d = self.do_vctest(testRetry=False)
1443 return maybeWait(d)
1445 def testTry(self):
1446 self.helper.vcargs = { 'baseURL': self.helper.darcs_base + "/",
1447 'defaultBranch': "trunk" }
1448 d = self.do_getpatch()
1449 return maybeWait(d)
1451 VCS.registerVC(Darcs.vc_name, DarcsHelper())
1454 class ArchCommon:
1455 def registerRepository(self, coordinates):
1456 a = self.archname
1457 w = self.dovc(self.repbase, "archives %s" % a)
1458 yield w; out = w.getResult()
1459 if out:
1460 w = self.dovc(self.repbase, "register-archive -d %s" % a)
1461 yield w; w.getResult()
1462 w = self.dovc(self.repbase, "register-archive %s" % coordinates)
1463 yield w; w.getResult()
1464 registerRepository = deferredGenerator(registerRepository)
1466 def unregisterRepository(self):
1467 a = self.archname
1468 w = self.dovc(self.repbase, "archives %s" % a)
1469 yield w; out = w.getResult()
1470 if out:
1471 w = self.dovc(self.repbase, "register-archive -d %s" % a)
1472 yield w; out = w.getResult()
1473 unregisterRepository = deferredGenerator(unregisterRepository)
1475 class TlaHelper(BaseHelper, ArchCommon):
1476 defaultbranch = "testvc--mainline--1"
1477 branchname = "testvc--branch--1"
1478 try_branchname = None # TlaExtractor can figure it out by itself
1479 archcmd = "tla"
1481 def capable(self):
1482 tlapaths = which('tla')
1483 if not tlapaths:
1484 return (False, "Arch (tla) is not installed")
1485 self.vcexe = tlapaths[0]
1486 return (True, None)
1488 def do_get(self, basedir, archive, branch, newdir):
1489 # the 'get' syntax is different between tla and baz. baz, while
1490 # claiming to honor an --archive argument, in fact ignores it. The
1491 # correct invocation is 'baz get archive/revision newdir'.
1492 if self.archcmd == "tla":
1493 w = self.dovc(basedir,
1494 "get -A %s %s %s" % (archive, branch, newdir))
1495 else:
1496 w = self.dovc(basedir,
1497 "get %s/%s %s" % (archive, branch, newdir))
1498 return w
1500 def createRepository(self):
1501 self.createBasedir()
1502 # first check to see if bazaar is around, since we'll need to know
1503 # later
1504 d = VCS.capable(Bazaar.vc_name)
1505 d.addCallback(self._createRepository_1)
1506 return d
1508 def _createRepository_1(self, res):
1509 has_baz = res[0]
1511 # pick a hopefully unique string for the archive name, in the form
1512 # test-%d@buildbot.sf.net--testvc, since otherwise multiple copies of
1513 # the unit tests run in the same user account will collide (since the
1514 # archive names are kept in the per-user ~/.arch-params/ directory).
1515 pid = os.getpid()
1516 self.archname = "test-%s-%d@buildbot.sf.net--testvc" % (self.archcmd,
1517 pid)
1518 trunk = self.defaultbranch
1519 branch = self.branchname
1521 repword = self.archcmd.capitalize()
1522 self.archrep = os.path.join(self.repbase, "%s-Repository" % repword)
1523 tmp = os.path.join(self.repbase, "archtmp")
1524 a = self.archname
1526 self.populate(tmp)
1528 w = self.dovc(tmp, "my-id", failureIsOk=True)
1529 yield w; res = w.getResult()
1530 if not res:
1531 # tla will fail a lot of operations if you have not set an ID
1532 w = self.do(tmp, [self.vcexe, "my-id",
1533 "Buildbot Test Suite <test@buildbot.sf.net>"])
1534 yield w; w.getResult()
1536 if has_baz:
1537 # bazaar keeps a cache of revisions, but this test creates a new
1538 # archive each time it is run, so the cache causes errors.
1539 # Disable the cache to avoid these problems. This will be
1540 # slightly annoying for people who run the buildbot tests under
1541 # the same UID as one which uses baz on a regular basis, but
1542 # bazaar doesn't give us a way to disable the cache just for this
1543 # one archive.
1544 cmd = "%s cache-config --disable" % VCS.getHelper('bazaar').vcexe
1545 w = self.do(tmp, cmd)
1546 yield w; w.getResult()
1548 w = waitForDeferred(self.unregisterRepository())
1549 yield w; w.getResult()
1551 # these commands can be run in any directory
1552 w = self.dovc(tmp, "make-archive -l %s %s" % (a, self.archrep))
1553 yield w; w.getResult()
1554 if self.archcmd == "tla":
1555 w = self.dovc(tmp, "archive-setup -A %s %s" % (a, trunk))
1556 yield w; w.getResult()
1557 w = self.dovc(tmp, "archive-setup -A %s %s" % (a, branch))
1558 yield w; w.getResult()
1559 else:
1560 # baz does not require an 'archive-setup' step
1561 pass
1563 # these commands must be run in the directory that is to be imported
1564 w = self.dovc(tmp, "init-tree --nested %s/%s" % (a, trunk))
1565 yield w; w.getResult()
1566 files = " ".join(["main.c", "version.c", "subdir",
1567 os.path.join("subdir", "subdir.c")])
1568 w = self.dovc(tmp, "add-id %s" % files)
1569 yield w; w.getResult()
1571 w = self.dovc(tmp, "import %s/%s" % (a, trunk))
1572 yield w; out = w.getResult()
1573 self.addTrunkRev("base-0")
1575 # create the branch
1576 if self.archcmd == "tla":
1577 branchstart = "%s--base-0" % trunk
1578 w = self.dovc(tmp, "tag -A %s %s %s" % (a, branchstart, branch))
1579 yield w; w.getResult()
1580 else:
1581 w = self.dovc(tmp, "branch %s" % branch)
1582 yield w; w.getResult()
1584 rmdirRecursive(tmp)
1586 # check out the branch
1587 w = self.do_get(self.repbase, a, branch, "archtmp")
1588 yield w; w.getResult()
1589 # and edit the file
1590 self.populate_branch(tmp)
1591 logfile = "++log.%s--%s" % (branch, a)
1592 logmsg = "Summary: commit on branch\nKeywords:\n\n"
1593 open(os.path.join(tmp, logfile), "w").write(logmsg)
1594 w = self.dovc(tmp, "commit")
1595 yield w; out = w.getResult()
1596 m = re.search(r'committed %s/%s--([\S]+)' % (a, branch),
1597 out)
1598 assert (m.group(1) == "base-0" or m.group(1).startswith("patch-"))
1599 self.addBranchRev(m.group(1))
1601 w = waitForDeferred(self.unregisterRepository())
1602 yield w; w.getResult()
1603 rmdirRecursive(tmp)
1605 # we unregister the repository each time, because we might have
1606 # changed the coordinates (since we switch from a file: URL to an
1607 # http: URL for various tests). The buildslave code doesn't forcibly
1608 # unregister the archive, so we have to do it here.
1609 w = waitForDeferred(self.unregisterRepository())
1610 yield w; w.getResult()
1612 _createRepository_1 = deferredGenerator(_createRepository_1)
1614 def vc_revise(self):
1615 # the fix needs to be done in a workspace that is linked to a
1616 # read-write version of the archive (i.e., using file-based
1617 # coordinates instead of HTTP ones), so we re-register the repository
1618 # before we begin. We unregister it when we're done to make sure the
1619 # build will re-register the correct one for whichever test is
1620 # currently being run.
1622 # except, that step.Bazaar really doesn't like it when the archive
1623 # gets unregistered behind its back. The slave tries to do a 'baz
1624 # replay' in a tree with an archive that is no longer recognized, and
1625 # baz aborts with a botched invariant exception. This causes
1626 # mode=update to fall back to clobber+get, which flunks one of the
1627 # tests (the 'newfile' check in _do_vctest_update_3 fails)
1629 # to avoid this, we take heroic steps here to leave the archive
1630 # registration in the same state as we found it.
1632 tmp = os.path.join(self.repbase, "archtmp")
1633 a = self.archname
1635 w = self.dovc(self.repbase, "archives %s" % a)
1636 yield w; out = w.getResult()
1637 assert out
1638 lines = out.split("\n")
1639 coordinates = lines[1].strip()
1641 # now register the read-write location
1642 w = waitForDeferred(self.registerRepository(self.archrep))
1643 yield w; w.getResult()
1645 trunk = self.defaultbranch
1647 w = self.do_get(self.repbase, a, trunk, "archtmp")
1648 yield w; w.getResult()
1650 # tla appears to use timestamps to determine which files have
1651 # changed, so wait long enough for the new file to have a different
1652 # timestamp
1653 time.sleep(2)
1654 self.version += 1
1655 version_c = VERSION_C % self.version
1656 open(os.path.join(tmp, "version.c"), "w").write(version_c)
1658 logfile = "++log.%s--%s" % (trunk, a)
1659 logmsg = "Summary: revised_to_%d\nKeywords:\n\n" % self.version
1660 open(os.path.join(tmp, logfile), "w").write(logmsg)
1661 w = self.dovc(tmp, "commit")
1662 yield w; out = w.getResult()
1663 m = re.search(r'committed %s/%s--([\S]+)' % (a, trunk),
1664 out)
1665 assert (m.group(1) == "base-0" or m.group(1).startswith("patch-"))
1666 self.addTrunkRev(m.group(1))
1668 # now re-register the original coordinates
1669 w = waitForDeferred(self.registerRepository(coordinates))
1670 yield w; w.getResult()
1671 rmdirRecursive(tmp)
1672 vc_revise = deferredGenerator(vc_revise)
1674 def vc_try_checkout(self, workdir, rev, branch=None):
1675 assert os.path.abspath(workdir) == workdir
1676 if os.path.exists(workdir):
1677 rmdirRecursive(workdir)
1679 a = self.archname
1681 # register the read-write location, if it wasn't already registered
1682 w = waitForDeferred(self.registerRepository(self.archrep))
1683 yield w; w.getResult()
1685 w = self.do_get(self.repbase, a, "testvc--mainline--1", workdir)
1686 yield w; w.getResult()
1688 # timestamps. ick.
1689 time.sleep(2)
1690 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
1691 vc_try_checkout = deferredGenerator(vc_try_checkout)
1693 def vc_try_finish(self, workdir):
1694 rmdirRecursive(workdir)
1696 class Arch(VCBase, unittest.TestCase):
1697 vc_name = "tla"
1699 metadir = None
1700 # Arch has a metadir="{arch}", but it does not have an 'export' mode.
1701 vctype = "step.Arch"
1702 vctype_try = "tla"
1703 has_got_revision = True
1705 def testCheckout(self):
1706 # these are the coordinates of the read-write archive used by all the
1707 # non-HTTP tests. testCheckoutHTTP overrides these.
1708 self.helper.vcargs = {'url': self.helper.archrep,
1709 'version': self.helper.defaultbranch }
1710 d = self.do_vctest(testRetry=False)
1711 # the current testRetry=True logic doesn't have the desired effect:
1712 # "update" is a no-op because arch knows that the repository hasn't
1713 # changed. Other VC systems will re-checkout missing files on
1714 # update, arch just leaves the tree untouched. TODO: come up with
1715 # some better test logic, probably involving a copy of the
1716 # repository that has a few changes checked in.
1718 return maybeWait(d)
1720 def testCheckoutHTTP(self):
1721 self.serveHTTP()
1722 url = "http://localhost:%d/Tla-Repository" % self.httpPort
1723 self.helper.vcargs = { 'url': url,
1724 'version': "testvc--mainline--1" }
1725 d = self.do_vctest(testRetry=False)
1726 return maybeWait(d)
1728 def testPatch(self):
1729 self.helper.vcargs = {'url': self.helper.archrep,
1730 'version': self.helper.defaultbranch }
1731 d = self.do_patch()
1732 return maybeWait(d)
1734 def testCheckoutBranch(self):
1735 self.helper.vcargs = {'url': self.helper.archrep,
1736 'version': self.helper.defaultbranch }
1737 d = self.do_branch()
1738 return maybeWait(d)
1740 def testTry(self):
1741 self.helper.vcargs = {'url': self.helper.archrep,
1742 'version': self.helper.defaultbranch }
1743 d = self.do_getpatch()
1744 return maybeWait(d)
1746 VCS.registerVC(Arch.vc_name, TlaHelper())
1749 class BazaarHelper(TlaHelper):
1750 archcmd = "baz"
1752 def capable(self):
1753 bazpaths = which('baz')
1754 if not bazpaths:
1755 return (False, "Arch (baz) is not installed")
1756 self.vcexe = bazpaths[0]
1757 return (True, None)
1759 def setUp2(self, res):
1760 # we unregister the repository each time, because we might have
1761 # changed the coordinates (since we switch from a file: URL to an
1762 # http: URL for various tests). The buildslave code doesn't forcibly
1763 # unregister the archive, so we have to do it here.
1764 d = self.unregisterRepository()
1765 return d
1768 class Bazaar(Arch):
1769 vc_name = "bazaar"
1771 vctype = "step.Bazaar"
1772 vctype_try = "baz"
1773 has_got_revision = True
1775 fixtimer = None
1777 def testCheckout(self):
1778 self.helper.vcargs = {'url': self.helper.archrep,
1779 # Baz adds the required 'archive' argument
1780 'archive': self.helper.archname,
1781 'version': self.helper.defaultbranch,
1783 d = self.do_vctest(testRetry=False)
1784 # the current testRetry=True logic doesn't have the desired effect:
1785 # "update" is a no-op because arch knows that the repository hasn't
1786 # changed. Other VC systems will re-checkout missing files on
1787 # update, arch just leaves the tree untouched. TODO: come up with
1788 # some better test logic, probably involving a copy of the
1789 # repository that has a few changes checked in.
1791 return maybeWait(d)
1793 def testCheckoutHTTP(self):
1794 self.serveHTTP()
1795 url = "http://localhost:%d/Baz-Repository" % self.httpPort
1796 self.helper.vcargs = { 'url': url,
1797 'archive': self.helper.archname,
1798 'version': self.helper.defaultbranch,
1800 d = self.do_vctest(testRetry=False)
1801 return maybeWait(d)
1803 def testPatch(self):
1804 self.helper.vcargs = {'url': self.helper.archrep,
1805 # Baz adds the required 'archive' argument
1806 'archive': self.helper.archname,
1807 'version': self.helper.defaultbranch,
1809 d = self.do_patch()
1810 return maybeWait(d)
1812 def testCheckoutBranch(self):
1813 self.helper.vcargs = {'url': self.helper.archrep,
1814 # Baz adds the required 'archive' argument
1815 'archive': self.helper.archname,
1816 'version': self.helper.defaultbranch,
1818 d = self.do_branch()
1819 return maybeWait(d)
1821 def testTry(self):
1822 self.helper.vcargs = {'url': self.helper.archrep,
1823 # Baz adds the required 'archive' argument
1824 'archive': self.helper.archname,
1825 'version': self.helper.defaultbranch,
1827 d = self.do_getpatch()
1828 return maybeWait(d)
1830 def fixRepository(self):
1831 self.fixtimer = None
1832 self.site.resource = self.root
1834 def testRetry(self):
1835 # we want to verify that step.Source(retry=) works, and the easiest
1836 # way to make VC updates break (temporarily) is to break the HTTP
1837 # server that's providing the repository. Anything else pretty much
1838 # requires mutating the (read-only) BUILDBOT_TEST_VC repository, or
1839 # modifying the buildslave's checkout command while it's running.
1841 # this test takes a while to run, so don't bother doing it with
1842 # anything other than baz
1844 self.serveHTTP()
1846 # break the repository server
1847 from twisted.web import static
1848 self.site.resource = static.Data("Sorry, repository is offline",
1849 "text/plain")
1850 # and arrange to fix it again in 5 seconds, while the test is
1851 # running.
1852 self.fixtimer = reactor.callLater(5, self.fixRepository)
1854 url = "http://localhost:%d/Baz-Repository" % self.httpPort
1855 self.helper.vcargs = { 'url': url,
1856 'archive': self.helper.archname,
1857 'version': self.helper.defaultbranch,
1858 'retry': (5.0, 4),
1860 d = self.do_vctest_once(True)
1861 d.addCallback(self._testRetry_1)
1862 return maybeWait(d)
1863 def _testRetry_1(self, bs):
1864 # make sure there was mention of the retry attempt in the logs
1865 l = bs.getLogs()[0]
1866 self.failUnlessIn("unable to access URL", l.getText(),
1867 "funny, VC operation didn't fail at least once")
1868 self.failUnlessIn("update failed, trying 4 more times after 5 seconds",
1869 l.getTextWithHeaders(),
1870 "funny, VC operation wasn't reattempted")
1872 def testRetryFails(self):
1873 # make sure that the build eventually gives up on a repository which
1874 # is completely unavailable
1876 self.serveHTTP()
1878 # break the repository server, and leave it broken
1879 from twisted.web import static
1880 self.site.resource = static.Data("Sorry, repository is offline",
1881 "text/plain")
1883 url = "http://localhost:%d/Baz-Repository" % self.httpPort
1884 self.helper.vcargs = {'url': url,
1885 'archive': self.helper.archname,
1886 'version': self.helper.defaultbranch,
1887 'retry': (0.5, 3),
1889 d = self.do_vctest_once(False)
1890 d.addCallback(self._testRetryFails_1)
1891 return maybeWait(d)
1892 def _testRetryFails_1(self, bs):
1893 self.failUnlessEqual(bs.getResults(), FAILURE)
1895 def tearDown2(self):
1896 if self.fixtimer:
1897 self.fixtimer.cancel()
1898 # tell tla to get rid of the leftover archive this test leaves in the
1899 # user's 'tla archives' listing. The name of this archive is provided
1900 # by the repository tarball, so the following command must use the
1901 # same name. We could use archive= to set it explicitly, but if you
1902 # change it from the default, then 'tla update' won't work.
1903 d = self.helper.unregisterRepository()
1904 return d
1906 VCS.registerVC(Bazaar.vc_name, BazaarHelper())
1908 class MercurialHelper(BaseHelper):
1909 branchname = "branch"
1910 try_branchname = "branch"
1912 def capable(self):
1913 hgpaths = which("hg")
1914 if not hgpaths:
1915 return (False, "Mercurial is not installed")
1916 self.vcexe = hgpaths[0]
1917 return (True, None)
1919 def extract_id(self, output):
1920 m = re.search(r'^(\w+)', output)
1921 return m.group(0)
1923 def createRepository(self):
1924 self.createBasedir()
1925 self.hg_base = os.path.join(self.repbase, "Mercurial-Repository")
1926 self.rep_trunk = os.path.join(self.hg_base, "trunk")
1927 self.rep_branch = os.path.join(self.hg_base, "branch")
1928 tmp = os.path.join(self.hg_base, "hgtmp")
1930 os.makedirs(self.rep_trunk)
1931 w = self.dovc(self.rep_trunk, "init")
1932 yield w; w.getResult()
1933 os.makedirs(self.rep_branch)
1934 w = self.dovc(self.rep_branch, "init")
1935 yield w; w.getResult()
1937 self.populate(tmp)
1938 w = self.dovc(tmp, "init")
1939 yield w; w.getResult()
1940 w = self.dovc(tmp, "add")
1941 yield w; w.getResult()
1942 w = self.dovc(tmp, "commit -m initial_import")
1943 yield w; w.getResult()
1944 w = self.dovc(tmp, "push %s" % self.rep_trunk)
1945 # note that hg-push does not actually update the working directory
1946 yield w; w.getResult()
1947 w = self.dovc(tmp, "identify")
1948 yield w; out = w.getResult()
1949 self.addTrunkRev(self.extract_id(out))
1951 self.populate_branch(tmp)
1952 w = self.dovc(tmp, "commit -m commit_on_branch")
1953 yield w; w.getResult()
1954 w = self.dovc(tmp, "push %s" % self.rep_branch)
1955 yield w; w.getResult()
1956 w = self.dovc(tmp, "identify")
1957 yield w; out = w.getResult()
1958 self.addBranchRev(self.extract_id(out))
1959 rmdirRecursive(tmp)
1960 createRepository = deferredGenerator(createRepository)
1962 def vc_revise(self):
1963 tmp = os.path.join(self.hg_base, "hgtmp2")
1964 w = self.dovc(self.hg_base, "clone %s %s" % (self.rep_trunk, tmp))
1965 yield w; w.getResult()
1967 self.version += 1
1968 version_c = VERSION_C % self.version
1969 version_c_filename = os.path.join(tmp, "version.c")
1970 open(version_c_filename, "w").write(version_c)
1971 # hg uses timestamps to distinguish files which have changed, so we
1972 # force the mtime forward a little bit
1973 future = time.time() + 2*self.version
1974 os.utime(version_c_filename, (future, future))
1975 w = self.dovc(tmp, "commit -m revised_to_%d" % self.version)
1976 yield w; w.getResult()
1977 w = self.dovc(tmp, "push %s" % self.rep_trunk)
1978 yield w; w.getResult()
1979 w = self.dovc(tmp, "identify")
1980 yield w; out = w.getResult()
1981 self.addTrunkRev(self.extract_id(out))
1982 rmdirRecursive(tmp)
1983 vc_revise = deferredGenerator(vc_revise)
1985 def vc_try_checkout(self, workdir, rev, branch=None):
1986 assert os.path.abspath(workdir) == workdir
1987 if os.path.exists(workdir):
1988 rmdirRecursive(workdir)
1989 if branch:
1990 src = self.rep_branch
1991 else:
1992 src = self.rep_trunk
1993 w = self.dovc(self.hg_base, "clone %s %s" % (src, workdir))
1994 yield w; w.getResult()
1995 try_c_filename = os.path.join(workdir, "subdir", "subdir.c")
1996 open(try_c_filename, "w").write(TRY_C)
1997 future = time.time() + 2*self.version
1998 os.utime(try_c_filename, (future, future))
1999 vc_try_checkout = deferredGenerator(vc_try_checkout)
2001 def vc_try_finish(self, workdir):
2002 rmdirRecursive(workdir)
2005 class Mercurial(VCBase, unittest.TestCase):
2006 vc_name = "hg"
2008 # Mercurial has a metadir=".hg", but it does not have an 'export' mode.
2009 metadir = None
2010 vctype = "step.Mercurial"
2011 vctype_try = "hg"
2012 has_got_revision = True
2014 def testCheckout(self):
2015 self.helper.vcargs = { 'repourl': self.helper.rep_trunk }
2016 d = self.do_vctest(testRetry=False)
2018 # TODO: testRetry has the same problem with Mercurial as it does for
2019 # Arch
2020 return maybeWait(d)
2022 def testPatch(self):
2023 self.helper.vcargs = { 'baseURL': self.helper.hg_base + "/",
2024 'defaultBranch': "trunk" }
2025 d = self.do_patch()
2026 return maybeWait(d)
2028 def testCheckoutBranch(self):
2029 self.helper.vcargs = { 'baseURL': self.helper.hg_base + "/",
2030 'defaultBranch': "trunk" }
2031 d = self.do_branch()
2032 return maybeWait(d)
2034 def testCheckoutHTTP(self):
2035 self.serveHTTP()
2036 repourl = "http://localhost:%d/Mercurial-Repository/trunk/.hg" % self.httpPort
2037 self.helper.vcargs = { 'repourl': repourl }
2038 d = self.do_vctest(testRetry=False)
2039 return maybeWait(d)
2040 # TODO: The easiest way to publish hg over HTTP is by running 'hg serve'
2041 # as a child process while the test is running. (you can also use a CGI
2042 # script, which sounds difficult, or you can publish the files directly,
2043 # which isn't well documented).
2044 testCheckoutHTTP.skip = "not yet implemented, use 'hg serve'"
2046 def testTry(self):
2047 self.helper.vcargs = { 'baseURL': self.helper.hg_base + "/",
2048 'defaultBranch': "trunk" }
2049 d = self.do_getpatch()
2050 return maybeWait(d)
2052 VCS.registerVC(Mercurial.vc_name, MercurialHelper())
2055 class Sources(unittest.TestCase):
2056 # TODO: this needs serious rethink
2057 def makeChange(self, when=None, revision=None):
2058 if when:
2059 when = mktime_tz(parsedate_tz(when))
2060 return changes.Change("fred", [], "", when=when, revision=revision)
2062 def testCVS1(self):
2063 r = base.BuildRequest("forced build", SourceStamp())
2064 b = base.Build([r])
2065 s = step.CVS(cvsroot=None, cvsmodule=None, workdir=None, build=b)
2066 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()), None)
2068 def testCVS2(self):
2069 c = []
2070 c.append(self.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2071 c.append(self.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2072 c.append(self.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2073 r = base.BuildRequest("forced", SourceStamp(changes=c))
2074 submitted = "Wed, 08 Sep 2004 09:04:00 -0700"
2075 r.submittedAt = mktime_tz(parsedate_tz(submitted))
2076 b = base.Build([r])
2077 s = step.CVS(cvsroot=None, cvsmodule=None, workdir=None, build=b)
2078 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()),
2079 "Wed, 08 Sep 2004 16:03:00 -0000")
2081 def testCVS3(self):
2082 c = []
2083 c.append(self.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2084 c.append(self.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2085 c.append(self.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2086 r = base.BuildRequest("forced", SourceStamp(changes=c))
2087 submitted = "Wed, 08 Sep 2004 09:04:00 -0700"
2088 r.submittedAt = mktime_tz(parsedate_tz(submitted))
2089 b = base.Build([r])
2090 s = step.CVS(cvsroot=None, cvsmodule=None, workdir=None, build=b,
2091 checkoutDelay=10)
2092 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()),
2093 "Wed, 08 Sep 2004 16:02:10 -0000")
2095 def testCVS4(self):
2096 c = []
2097 c.append(self.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2098 c.append(self.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2099 c.append(self.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2100 r1 = base.BuildRequest("forced", SourceStamp(changes=c))
2101 submitted = "Wed, 08 Sep 2004 09:04:00 -0700"
2102 r1.submittedAt = mktime_tz(parsedate_tz(submitted))
2104 c = []
2105 c.append(self.makeChange("Wed, 08 Sep 2004 09:05:00 -0700"))
2106 r2 = base.BuildRequest("forced", SourceStamp(changes=c))
2107 submitted = "Wed, 08 Sep 2004 09:07:00 -0700"
2108 r2.submittedAt = mktime_tz(parsedate_tz(submitted))
2110 b = base.Build([r1, r2])
2111 s = step.CVS(cvsroot=None, cvsmodule=None, workdir=None, build=b)
2112 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()),
2113 "Wed, 08 Sep 2004 16:06:00 -0000")
2115 def testSVN1(self):
2116 r = base.BuildRequest("forced", SourceStamp())
2117 b = base.Build([r])
2118 s = step.SVN(svnurl="dummy", workdir=None, build=b)
2119 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()), None)
2121 def testSVN2(self):
2122 c = []
2123 c.append(self.makeChange(revision=4))
2124 c.append(self.makeChange(revision=10))
2125 c.append(self.makeChange(revision=67))
2126 r = base.BuildRequest("forced", SourceStamp(changes=c))
2127 b = base.Build([r])
2128 s = step.SVN(svnurl="dummy", workdir=None, build=b)
2129 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()), 67)
2131 class Patch(VCBase, unittest.TestCase):
2132 def setUp(self):
2133 pass
2135 def tearDown(self):
2136 pass
2138 def testPatch(self):
2139 # invoke 'patch' all by itself, to see if it works the way we think
2140 # it should. This is intended to ferret out some windows test
2141 # failures.
2142 helper = BaseHelper()
2143 self.workdir = os.path.join("test_vc", "testPatch")
2144 helper.populate(self.workdir)
2145 patch = which("patch")[0]
2147 command = [patch, "-p0"]
2148 class FakeBuilder:
2149 usePTY = False
2150 def sendUpdate(self, status):
2151 pass
2152 c = commands.ShellCommand(FakeBuilder(), command, self.workdir,
2153 sendRC=False, stdin=p0_diff)
2154 d = c.start()
2155 d.addCallback(self._testPatch_1)
2156 return maybeWait(d)
2158 def _testPatch_1(self, res):
2159 # make sure the file actually got patched
2160 subdir_c = os.path.join(self.workdir, "subdir", "subdir.c")
2161 data = open(subdir_c, "r").read()
2162 self.failUnlessIn("Hello patched subdir.\\n", data)