(refs #41) add ability to send username and password to the svn command
[buildbot.git] / buildbot / test / test_vc.py
blob4d0c18e53e607f369ecc0906c70588e775c77ebd
1 # -*- test-case-name: buildbot.test.test_vc -*-
3 import sys, os, time, re
4 from email.Utils import mktime_tz, parsedate_tz
6 from twisted.trial import unittest
7 from twisted.internet import defer, reactor, utils, protocol, task, error
8 from twisted.python import failure
9 from twisted.python.procutils import which
10 from twisted.web import client, static, server
12 #defer.Deferred.debug = True
14 from twisted.python import log
15 #log.startLogging(sys.stderr)
17 from buildbot import master, interfaces
18 from buildbot.slave import bot, commands
19 from buildbot.slave.commands import rmdirRecursive
20 from buildbot.status.builder import SUCCESS, FAILURE
21 from buildbot.process import base
22 from buildbot.steps import source
23 from buildbot.changes import changes
24 from buildbot.sourcestamp import SourceStamp
25 from buildbot.scripts import tryclient
26 from buildbot.test.runutils import SignalMixin, myGetProcessOutputAndValue
28 #step.LoggedRemoteCommand.debug = True
30 from twisted.internet.defer import waitForDeferred, deferredGenerator
32 # Most of these tests (all but SourceStamp) depend upon having a set of
33 # repositories from which we can perform checkouts. These repositories are
34 # created by the setUp method at the start of each test class. In earlier
35 # versions these repositories were created offline and distributed with a
36 # separate tarball named 'buildbot-test-vc-1.tar.gz'. This is no longer
37 # necessary.
39 # CVS requires a local file repository. Providing remote access is beyond
40 # the feasible abilities of this test program (needs pserver or ssh).
42 # SVN requires a local file repository. To provide remote access over HTTP
43 # requires an apache server with DAV support and mod_svn, way beyond what we
44 # can test from here.
46 # Arch and Darcs both allow remote (read-only) operation with any web
47 # server. We test both local file access and HTTP access (by spawning a
48 # small web server to provide access to the repository files while the test
49 # is running).
51 # Perforce starts the daemon running on localhost. Unfortunately, it must
52 # use a predetermined Internet-domain port number, unless we want to go
53 # all-out: bind the listen socket ourselves and pretend to be inetd.
55 config_vc = """
56 from buildbot.process import factory
57 from buildbot.steps import source
58 from buildbot.buildslave import BuildSlave
59 s = factory.s
61 f1 = factory.BuildFactory([
62 %s,
64 c = {}
65 c['slaves'] = [BuildSlave('bot1', 'sekrit')]
66 c['schedulers'] = []
67 c['builders'] = [{'name': 'vc', 'slavename': 'bot1',
68 'builddir': 'vc-dir', 'factory': f1}]
69 c['slavePortnum'] = 0
70 # do not compress logs in tests
71 c['logCompressionLimit'] = False
72 BuildmasterConfig = c
73 """
75 p0_diff = r"""
76 Index: subdir/subdir.c
77 ===================================================================
78 RCS file: /home/warner/stuff/Projects/BuildBot/code-arch/_trial_temp/test_vc/repositories/CVS-Repository/sample/subdir/subdir.c,v
79 retrieving revision 1.1.1.1
80 diff -u -r1.1.1.1 subdir.c
81 --- subdir/subdir.c 14 Aug 2005 01:32:49 -0000 1.1.1.1
82 +++ subdir/subdir.c 14 Aug 2005 01:36:15 -0000
83 @@ -4,6 +4,6 @@
84 int
85 main(int argc, const char *argv[])
87 - printf("Hello subdir.\n");
88 + printf("Hello patched subdir.\n");
89 return 0;
91 """
93 # this patch does not include the filename headers, so it is
94 # patchlevel-neutral
95 TRY_PATCH = '''
96 @@ -5,6 +5,6 @@
97 int
98 main(int argc, const char *argv[])
100 - printf("Hello subdir.\\n");
101 + printf("Hello try.\\n");
102 return 0;
106 MAIN_C = '''
107 // this is main.c
108 #include <stdio.h>
111 main(int argc, const char *argv[])
113 printf("Hello world.\\n");
114 return 0;
118 BRANCH_C = '''
119 // this is main.c
120 #include <stdio.h>
123 main(int argc, const char *argv[])
125 printf("Hello branch.\\n");
126 return 0;
130 VERSION_C = '''
131 // this is version.c
132 #include <stdio.h>
135 main(int argc, const char *argv[])
137 printf("Hello world, version=%d\\n");
138 return 0;
142 SUBDIR_C = '''
143 // this is subdir/subdir.c
144 #include <stdio.h>
147 main(int argc, const char *argv[])
149 printf("Hello subdir.\\n");
150 return 0;
154 TRY_C = '''
155 // this is subdir/subdir.c
156 #include <stdio.h>
159 main(int argc, const char *argv[])
161 printf("Hello try.\\n");
162 return 0;
166 def qw(s):
167 return s.split()
169 class VCS_Helper:
170 # this is a helper class which keeps track of whether each VC system is
171 # available, and whether the repository for each has been created. There
172 # is one instance of this class, at module level, shared between all test
173 # cases.
175 def __init__(self):
176 self._helpers = {}
177 self._isCapable = {}
178 self._excuses = {}
179 self._repoReady = {}
181 def registerVC(self, name, helper):
182 self._helpers[name] = helper
183 self._repoReady[name] = False
185 def skipIfNotCapable(self, name):
186 """Either return None, or raise SkipTest"""
187 d = self.capable(name)
188 def _maybeSkip(res):
189 if not res[0]:
190 raise unittest.SkipTest(res[1])
191 d.addCallback(_maybeSkip)
192 return d
194 def capable(self, name):
195 """Return a Deferred that fires with (True,None) if this host offers
196 the given VC tool, or (False,excuse) if it does not (and therefore
197 the tests should be skipped)."""
199 if self._isCapable.has_key(name):
200 if self._isCapable[name]:
201 return defer.succeed((True,None))
202 else:
203 return defer.succeed((False, self._excuses[name]))
204 d = defer.maybeDeferred(self._helpers[name].capable)
205 def _capable(res):
206 if res[0]:
207 self._isCapable[name] = True
208 else:
209 self._excuses[name] = res[1]
210 return res
211 d.addCallback(_capable)
212 return d
214 def getHelper(self, name):
215 return self._helpers[name]
217 def createRepository(self, name):
218 """Return a Deferred that fires when the repository is set up."""
219 if self._repoReady[name]:
220 return defer.succeed(True)
221 d = self._helpers[name].createRepository()
222 def _ready(res):
223 self._repoReady[name] = True
224 d.addCallback(_ready)
225 return d
227 VCS = VCS_Helper()
230 # the overall plan here:
232 # Each VC system is tested separately, all using the same source tree defined
233 # in the 'files' dictionary above. Each VC system gets its own TestCase
234 # subclass. The first test case that is run will create the repository during
235 # setUp(), making two branches: 'trunk' and 'branch'. The trunk gets a copy
236 # of all the files in 'files'. The variant of good.c is committed on the
237 # branch.
239 # then testCheckout is run, which does a number of checkout/clobber/update
240 # builds. These all use trunk r1. It then runs self.fix(), which modifies
241 # 'fixable.c', then performs another build and makes sure the tree has been
242 # updated.
244 # testBranch uses trunk-r1 and branch-r1, making sure that we clobber the
245 # tree properly when we switch between them
247 # testPatch does a trunk-r1 checkout and applies a patch.
249 # testTryGetPatch performs a trunk-r1 checkout, modifies some files, then
250 # verifies that tryclient.getSourceStamp figures out the base revision and
251 # what got changed.
254 # vc_create makes a repository at r1 with three files: main.c, version.c, and
255 # subdir/foo.c . It also creates a branch from r1 (called b1) in which main.c
256 # says "hello branch" instead of "hello world". self.trunk[] contains
257 # revision stamps for everything on the trunk, and self.branch[] does the
258 # same for the branch.
260 # vc_revise() checks out a tree at HEAD, changes version.c, then checks it
261 # back in. The new version stamp is appended to self.trunk[]. The tree is
262 # removed afterwards.
264 # vc_try_checkout(workdir, rev) checks out a tree at REV, then changes
265 # subdir/subdir.c to say 'Hello try'
266 # vc_try_finish(workdir) removes the tree and cleans up any VC state
267 # necessary (like deleting the Arch archive entry).
270 class BaseHelper:
271 def __init__(self):
272 self.trunk = []
273 self.branch = []
274 self.allrevs = []
276 def capable(self):
277 # this is also responsible for setting self.vcexe
278 raise NotImplementedError
280 def createBasedir(self):
281 # you must call this from createRepository
282 self.repbase = os.path.abspath(os.path.join("test_vc",
283 "repositories"))
284 if not os.path.isdir(self.repbase):
285 os.makedirs(self.repbase)
287 def createRepository(self):
288 # this will only be called once per process
289 raise NotImplementedError
291 def populate(self, basedir):
292 if not os.path.exists(basedir):
293 os.makedirs(basedir)
294 os.makedirs(os.path.join(basedir, "subdir"))
295 open(os.path.join(basedir, "main.c"), "w").write(MAIN_C)
296 self.version = 1
297 version_c = VERSION_C % self.version
298 open(os.path.join(basedir, "version.c"), "w").write(version_c)
299 open(os.path.join(basedir, "main.c"), "w").write(MAIN_C)
300 open(os.path.join(basedir, "subdir", "subdir.c"), "w").write(SUBDIR_C)
302 def populate_branch(self, basedir):
303 open(os.path.join(basedir, "main.c"), "w").write(BRANCH_C)
305 def addTrunkRev(self, rev):
306 self.trunk.append(rev)
307 self.allrevs.append(rev)
308 def addBranchRev(self, rev):
309 self.branch.append(rev)
310 self.allrevs.append(rev)
312 def runCommand(self, basedir, command, failureIsOk=False,
313 stdin=None, env=None):
314 # all commands passed to do() should be strings or lists. If they are
315 # strings, none of the arguments may have spaces. This makes the
316 # commands less verbose at the expense of restricting what they can
317 # specify.
318 if type(command) not in (list, tuple):
319 command = command.split(" ")
321 # execute scripts through cmd.exe on windows, to avoid space in path issues
322 if sys.platform == 'win32' and command[0].lower().endswith('.cmd'):
323 command = [which('cmd.exe')[0], '/c', 'call'] + command
325 DEBUG = False
326 if DEBUG:
327 print "do %s" % command
328 print " in basedir %s" % basedir
329 if stdin:
330 print " STDIN:\n", stdin, "\n--STDIN DONE"
332 if not env:
333 env = os.environ.copy()
334 env['LC_ALL'] = "C"
335 d = myGetProcessOutputAndValue(command[0], command[1:],
336 env=env, path=basedir,
337 stdin=stdin)
338 def check((out, err, code)):
339 if DEBUG:
340 print
341 print "command was: %s" % command
342 if out: print "out: %s" % out
343 if err: print "err: %s" % err
344 print "code: %s" % code
345 if code != 0 and not failureIsOk:
346 log.msg("command %s finished with exit code %d" %
347 (command, code))
348 log.msg(" and stdout %s" % (out,))
349 log.msg(" and stderr %s" % (err,))
350 raise RuntimeError("command %s finished with exit code %d"
351 % (command, code)
352 + ": see logs for stdout")
353 return out
354 d.addCallback(check)
355 return d
357 def do(self, basedir, command, failureIsOk=False, stdin=None, env=None):
358 d = self.runCommand(basedir, command, failureIsOk=failureIsOk,
359 stdin=stdin, env=env)
360 return waitForDeferred(d)
362 def dovc(self, basedir, command, failureIsOk=False, stdin=None, env=None):
363 """Like do(), but the VC binary will be prepended to COMMAND."""
364 if isinstance(command, (str, unicode)):
365 command = [self.vcexe] + command.split(' ')
366 else:
367 # command is a list
368 command = [self.vcexe] + command
369 return self.do(basedir, command, failureIsOk, stdin, env)
371 class VCBase(SignalMixin):
372 metadir = None
373 createdRepository = False
374 master = None
375 slave = None
376 helper = None
377 httpServer = None
378 httpPort = None
379 skip = None
380 has_got_revision = False
381 has_got_revision_branches_are_merged = False # for SVN
383 def failUnlessIn(self, substring, string, msg=None):
384 # trial provides a version of this that requires python-2.3 to test
385 # strings.
386 if msg is None:
387 msg = ("did not see the expected substring '%s' in string '%s'" %
388 (substring, string))
389 self.failUnless(string.find(substring) != -1, msg)
391 def setUp(self):
392 d = VCS.skipIfNotCapable(self.vc_name)
393 d.addCallback(self._setUp1)
394 return d
396 def _setUp1(self, res):
397 self.helper = VCS.getHelper(self.vc_name)
399 if os.path.exists("basedir"):
400 rmdirRecursive("basedir")
401 os.mkdir("basedir")
402 self.master = master.BuildMaster("basedir")
403 self.slavebase = os.path.abspath("slavebase")
404 if os.path.exists(self.slavebase):
405 rmdirRecursive(self.slavebase)
406 os.mkdir("slavebase")
408 d = VCS.createRepository(self.vc_name)
409 return d
411 def connectSlave(self):
412 port = self.master.slavePort._port.getHost().port
413 slave = bot.BuildSlave("localhost", port, "bot1", "sekrit",
414 self.slavebase, keepalive=0, usePTY=False)
415 self.slave = slave
416 slave.startService()
417 d = self.master.botmaster.waitUntilBuilderAttached("vc")
418 return d
420 def loadConfig(self, config):
421 # reloading the config file causes a new 'listDirs' command to be
422 # sent to the slave. To synchronize on this properly, it is easiest
423 # to stop and restart the slave.
424 d = defer.succeed(None)
425 if self.slave:
426 d = self.master.botmaster.waitUntilBuilderDetached("vc")
427 self.slave.stopService()
428 d.addCallback(lambda res: self.master.loadConfig(config))
429 d.addCallback(lambda res: self.connectSlave())
430 return d
432 def serveHTTP(self):
433 # launch an HTTP server to serve the repository files
434 self.root = static.File(self.helper.repbase)
435 self.site = server.Site(self.root)
436 self.httpServer = reactor.listenTCP(0, self.site)
437 self.httpPort = self.httpServer.getHost().port
439 def doBuild(self, shouldSucceed=True, ss=None):
440 c = interfaces.IControl(self.master)
442 if ss is None:
443 ss = SourceStamp()
444 #print "doBuild(ss: b=%s rev=%s)" % (ss.branch, ss.revision)
445 req = base.BuildRequest("test_vc forced build", ss, 'test_builder')
446 d = req.waitUntilFinished()
447 c.getBuilder("vc").requestBuild(req)
448 d.addCallback(self._doBuild_1, shouldSucceed)
449 return d
450 def _doBuild_1(self, bs, shouldSucceed):
451 r = bs.getResults()
452 if r != SUCCESS and shouldSucceed:
453 print
454 print
455 if not bs.isFinished():
456 print "Hey, build wasn't even finished!"
457 print "Build did not succeed:", r, bs.getText()
458 for s in bs.getSteps():
459 for l in s.getLogs():
460 print "--- START step %s / log %s ---" % (s.getName(),
461 l.getName())
462 print l.getTextWithHeaders()
463 print "--- STOP ---"
464 print
465 self.fail("build did not succeed")
466 return bs
468 def printLogs(self, bs):
469 for s in bs.getSteps():
470 for l in s.getLogs():
471 print "--- START step %s / log %s ---" % (s.getName(),
472 l.getName())
473 print l.getTextWithHeaders()
474 print "--- STOP ---"
475 print
477 def touch(self, d, f):
478 open(os.path.join(d,f),"w").close()
479 def shouldExist(self, *args):
480 target = os.path.join(*args)
481 self.failUnless(os.path.exists(target),
482 "expected to find %s but didn't" % target)
483 def shouldNotExist(self, *args):
484 target = os.path.join(*args)
485 self.failIf(os.path.exists(target),
486 "expected to NOT find %s, but did" % target)
487 def shouldContain(self, d, f, contents):
488 c = open(os.path.join(d, f), "r").read()
489 self.failUnlessIn(contents, c)
491 def checkGotRevision(self, bs, expected):
492 if self.has_got_revision:
493 self.failUnlessEqual(bs.getProperty("got_revision"), str(expected))
495 def checkGotRevisionIsLatest(self, bs):
496 expected = self.helper.trunk[-1]
497 if self.has_got_revision_branches_are_merged:
498 expected = self.helper.allrevs[-1]
499 self.checkGotRevision(bs, expected)
501 def do_vctest(self, testRetry=True):
502 vctype = self.vctype
503 args = self.helper.vcargs
504 m = self.master
505 self.vcdir = os.path.join(self.slavebase, "vc-dir", "source")
506 self.workdir = os.path.join(self.slavebase, "vc-dir", "build")
507 # woo double-substitution
508 s = "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype,)
509 for k,v in args.items():
510 s += ", %s=%s" % (k, repr(v))
511 s += ")"
512 config = config_vc % s
514 m.loadConfig(config % 'clobber')
515 m.readConfig = True
516 m.startService()
518 d = self.connectSlave()
519 d.addCallback(lambda res: log.msg("testing clobber"))
520 d.addCallback(self._do_vctest_clobber)
521 d.addCallback(lambda res: log.msg("doing update"))
522 d.addCallback(lambda res: self.loadConfig(config % 'update'))
523 d.addCallback(lambda res: log.msg("testing update"))
524 d.addCallback(self._do_vctest_update)
525 if testRetry:
526 d.addCallback(lambda res: log.msg("testing update retry"))
527 d.addCallback(self._do_vctest_update_retry)
528 d.addCallback(lambda res: log.msg("doing copy"))
529 d.addCallback(lambda res: self.loadConfig(config % 'copy'))
530 d.addCallback(lambda res: log.msg("testing copy"))
531 d.addCallback(self._do_vctest_copy)
532 d.addCallback(lambda res: log.msg("did copy test"))
533 if self.metadir:
534 d.addCallback(lambda res: log.msg("doing export"))
535 d.addCallback(lambda res: self.loadConfig(config % 'export'))
536 d.addCallback(lambda res: log.msg("testing export"))
537 d.addCallback(self._do_vctest_export)
538 d.addCallback(lambda res: log.msg("did export test"))
539 return d
541 def _do_vctest_clobber(self, res):
542 d = self.doBuild() # initial checkout
543 d.addCallback(self._do_vctest_clobber_1)
544 return d
545 def _do_vctest_clobber_1(self, bs):
546 self.shouldExist(self.workdir, "main.c")
547 self.shouldExist(self.workdir, "version.c")
548 self.shouldExist(self.workdir, "subdir", "subdir.c")
549 if self.metadir:
550 self.shouldExist(self.workdir, self.metadir)
551 self.failUnlessEqual(bs.getProperty("revision"), None)
552 self.failUnlessEqual(bs.getProperty("branch"), None)
553 self.checkGotRevisionIsLatest(bs)
555 self.touch(self.workdir, "newfile")
556 self.shouldExist(self.workdir, "newfile")
557 d = self.doBuild() # rebuild clobbers workdir
558 d.addCallback(self._do_vctest_clobber_2)
559 return d
560 def _do_vctest_clobber_2(self, res):
561 self.shouldNotExist(self.workdir, "newfile")
562 # do a checkout to a specific version. Mercurial-over-HTTP (when
563 # either client or server is older than hg-0.9.2) cannot do this
564 # directly, so it must checkout HEAD and then update back to the
565 # requested revision.
566 d = self.doBuild(ss=SourceStamp(revision=self.helper.trunk[0]))
567 d.addCallback(self._do_vctest_clobber_3)
568 return d
569 def _do_vctest_clobber_3(self, bs):
570 self.shouldExist(self.workdir, "main.c")
571 self.shouldExist(self.workdir, "version.c")
572 self.shouldExist(self.workdir, "subdir", "subdir.c")
573 if self.metadir:
574 self.shouldExist(self.workdir, self.metadir)
575 self.failUnlessEqual(bs.getProperty("revision"), self.helper.trunk[0] or None)
576 self.failUnlessEqual(bs.getProperty("branch"), None)
577 self.checkGotRevision(bs, self.helper.trunk[0])
578 # leave the tree at HEAD
579 return self.doBuild()
582 def _do_vctest_update(self, res):
583 log.msg("_do_vctest_update")
584 d = self.doBuild() # rebuild with update
585 d.addCallback(self._do_vctest_update_1)
586 return d
587 def _do_vctest_update_1(self, bs):
588 log.msg("_do_vctest_update_1")
589 self.shouldExist(self.workdir, "main.c")
590 self.shouldExist(self.workdir, "version.c")
591 self.shouldContain(self.workdir, "version.c",
592 "version=%d" % self.helper.version)
593 if self.metadir:
594 self.shouldExist(self.workdir, self.metadir)
595 self.failUnlessEqual(bs.getProperty("revision"), None)
596 self.checkGotRevisionIsLatest(bs)
598 self.touch(self.workdir, "newfile")
599 d = self.doBuild() # update rebuild leaves new files
600 d.addCallback(self._do_vctest_update_2)
601 return d
602 def _do_vctest_update_2(self, bs):
603 log.msg("_do_vctest_update_2")
604 self.shouldExist(self.workdir, "main.c")
605 self.shouldExist(self.workdir, "version.c")
606 self.touch(self.workdir, "newfile")
607 # now make a change to the repository and make sure we pick it up
608 d = self.helper.vc_revise()
609 d.addCallback(lambda res: self.doBuild())
610 d.addCallback(self._do_vctest_update_3)
611 return d
612 def _do_vctest_update_3(self, bs):
613 log.msg("_do_vctest_update_3")
614 self.shouldExist(self.workdir, "main.c")
615 self.shouldExist(self.workdir, "version.c")
616 self.shouldContain(self.workdir, "version.c",
617 "version=%d" % self.helper.version)
618 self.shouldExist(self.workdir, "newfile")
619 self.failUnlessEqual(bs.getProperty("revision"), None)
620 self.checkGotRevisionIsLatest(bs)
622 # now "update" to an older revision
623 d = self.doBuild(ss=SourceStamp(revision=self.helper.trunk[-2]))
624 d.addCallback(self._do_vctest_update_4)
625 return d
626 def _do_vctest_update_4(self, bs):
627 log.msg("_do_vctest_update_4")
628 self.shouldExist(self.workdir, "main.c")
629 self.shouldExist(self.workdir, "version.c")
630 self.shouldContain(self.workdir, "version.c",
631 "version=%d" % (self.helper.version-1))
632 self.failUnlessEqual(bs.getProperty("revision"),
633 self.helper.trunk[-2] or None)
634 self.checkGotRevision(bs, self.helper.trunk[-2])
636 # now update to the newer revision
637 d = self.doBuild(ss=SourceStamp(revision=self.helper.trunk[-1]))
638 d.addCallback(self._do_vctest_update_5)
639 return d
640 def _do_vctest_update_5(self, bs):
641 log.msg("_do_vctest_update_5")
642 self.shouldExist(self.workdir, "main.c")
643 self.shouldExist(self.workdir, "version.c")
644 self.shouldContain(self.workdir, "version.c",
645 "version=%d" % self.helper.version)
646 self.failUnlessEqual(bs.getProperty("revision"),
647 self.helper.trunk[-1] or None)
648 self.checkGotRevision(bs, self.helper.trunk[-1])
651 def _do_vctest_update_retry(self, res):
652 # certain local changes will prevent an update from working. The
653 # most common is to replace a file with a directory, or vice
654 # versa. The slave code should spot the failure and do a
655 # clobber/retry.
656 os.unlink(os.path.join(self.workdir, "main.c"))
657 os.mkdir(os.path.join(self.workdir, "main.c"))
658 self.touch(os.path.join(self.workdir, "main.c"), "foo")
659 self.touch(self.workdir, "newfile")
661 d = self.doBuild() # update, but must clobber to handle the error
662 d.addCallback(self._do_vctest_update_retry_1)
663 return d
664 def _do_vctest_update_retry_1(self, bs):
665 # SVN-1.4.0 doesn't seem to have any problem with the
666 # file-turned-directory issue (although older versions did). So don't
667 # actually check that the tree was clobbered.. as long as the update
668 # succeeded (checked by doBuild), that should be good enough.
669 #self.shouldNotExist(self.workdir, "newfile")
670 pass
672 def _do_vctest_copy(self, res):
673 log.msg("_do_vctest_copy 1")
674 d = self.doBuild() # copy rebuild clobbers new files
675 d.addCallback(self._do_vctest_copy_1)
676 return d
677 def _do_vctest_copy_1(self, bs):
678 log.msg("_do_vctest_copy 2")
679 if self.metadir:
680 self.shouldExist(self.workdir, self.metadir)
681 self.shouldNotExist(self.workdir, "newfile")
682 self.touch(self.workdir, "newfile")
683 self.touch(self.vcdir, "newvcfile")
684 self.failUnlessEqual(bs.getProperty("revision"), None)
685 self.checkGotRevisionIsLatest(bs)
687 d = self.doBuild() # copy rebuild clobbers new files
688 d.addCallback(self._do_vctest_copy_2)
689 return d
690 def _do_vctest_copy_2(self, bs):
691 log.msg("_do_vctest_copy 3")
692 if self.metadir:
693 self.shouldExist(self.workdir, self.metadir)
694 self.shouldNotExist(self.workdir, "newfile")
695 self.shouldExist(self.vcdir, "newvcfile")
696 self.shouldExist(self.workdir, "newvcfile")
697 self.failUnlessEqual(bs.getProperty("revision"), None)
698 self.checkGotRevisionIsLatest(bs)
699 self.touch(self.workdir, "newfile")
701 def _do_vctest_export(self, res):
702 d = self.doBuild() # export rebuild clobbers new files
703 d.addCallback(self._do_vctest_export_1)
704 return d
705 def _do_vctest_export_1(self, bs):
706 self.shouldNotExist(self.workdir, self.metadir)
707 self.shouldNotExist(self.workdir, "newfile")
708 self.failUnlessEqual(bs.getProperty("revision"), None)
709 #self.checkGotRevisionIsLatest(bs)
710 # VC 'export' is not required to have a got_revision
711 self.touch(self.workdir, "newfile")
713 d = self.doBuild() # export rebuild clobbers new files
714 d.addCallback(self._do_vctest_export_2)
715 return d
716 def _do_vctest_export_2(self, bs):
717 self.shouldNotExist(self.workdir, self.metadir)
718 self.shouldNotExist(self.workdir, "newfile")
719 self.failUnlessEqual(bs.getProperty("revision"), None)
720 #self.checkGotRevisionIsLatest(bs)
721 # VC 'export' is not required to have a got_revision
723 def do_patch(self):
724 vctype = self.vctype
725 args = self.helper.vcargs
726 m = self.master
727 self.vcdir = os.path.join(self.slavebase, "vc-dir", "source")
728 self.workdir = os.path.join(self.slavebase, "vc-dir", "build")
729 s = "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype,)
730 for k,v in args.items():
731 s += ", %s=%s" % (k, repr(v))
732 s += ")"
733 self.config = config_vc % s
735 m.loadConfig(self.config % "clobber")
736 m.readConfig = True
737 m.startService()
739 ss = SourceStamp(revision=self.helper.trunk[-1], patch=(0, p0_diff))
741 d = self.connectSlave()
742 d.addCallback(lambda res: self.doBuild(ss=ss))
743 d.addCallback(self._doPatch_1)
744 return d
745 def _doPatch_1(self, bs):
746 self.shouldContain(self.workdir, "version.c",
747 "version=%d" % self.helper.version)
748 # make sure the file actually got patched
749 subdir_c = os.path.join(self.slavebase, "vc-dir", "build",
750 "subdir", "subdir.c")
751 data = open(subdir_c, "r").read()
752 self.failUnlessIn("Hello patched subdir.\\n", data)
753 self.failUnlessEqual(bs.getProperty("revision"),
754 self.helper.trunk[-1] or None)
755 self.checkGotRevision(bs, self.helper.trunk[-1])
757 # make sure that a rebuild does not use the leftover patched workdir
758 d = self.master.loadConfig(self.config % "update")
759 d.addCallback(lambda res: self.doBuild(ss=None))
760 d.addCallback(self._doPatch_2)
761 return d
762 def _doPatch_2(self, bs):
763 # make sure the file is back to its original
764 subdir_c = os.path.join(self.slavebase, "vc-dir", "build",
765 "subdir", "subdir.c")
766 data = open(subdir_c, "r").read()
767 self.failUnlessIn("Hello subdir.\\n", data)
768 self.failUnlessEqual(bs.getProperty("revision"), None)
769 self.checkGotRevisionIsLatest(bs)
771 # now make sure we can patch an older revision. We need at least two
772 # revisions here, so we might have to create one first
773 if len(self.helper.trunk) < 2:
774 d = self.helper.vc_revise()
775 d.addCallback(self._doPatch_3)
776 return d
777 return self._doPatch_3()
779 def _doPatch_3(self, res=None):
780 ss = SourceStamp(revision=self.helper.trunk[-2], patch=(0, p0_diff))
781 d = self.doBuild(ss=ss)
782 d.addCallback(self._doPatch_4)
783 return d
784 def _doPatch_4(self, bs):
785 self.shouldContain(self.workdir, "version.c",
786 "version=%d" % (self.helper.version-1))
787 # and make sure the file actually got patched
788 subdir_c = os.path.join(self.slavebase, "vc-dir", "build",
789 "subdir", "subdir.c")
790 data = open(subdir_c, "r").read()
791 self.failUnlessIn("Hello patched subdir.\\n", data)
792 self.failUnlessEqual(bs.getProperty("revision"),
793 self.helper.trunk[-2] or None)
794 self.checkGotRevision(bs, self.helper.trunk[-2])
796 # now check that we can patch a branch
797 ss = SourceStamp(branch=self.helper.branchname,
798 revision=self.helper.branch[-1],
799 patch=(0, p0_diff))
800 d = self.doBuild(ss=ss)
801 d.addCallback(self._doPatch_5)
802 return d
803 def _doPatch_5(self, bs):
804 self.shouldContain(self.workdir, "version.c",
805 "version=%d" % 1)
806 self.shouldContain(self.workdir, "main.c", "Hello branch.")
807 subdir_c = os.path.join(self.slavebase, "vc-dir", "build",
808 "subdir", "subdir.c")
809 data = open(subdir_c, "r").read()
810 self.failUnlessIn("Hello patched subdir.\\n", data)
811 self.failUnlessEqual(bs.getProperty("revision"),
812 self.helper.branch[-1] or None)
813 self.failUnlessEqual(bs.getProperty("branch"), self.helper.branchname or None)
814 self.checkGotRevision(bs, self.helper.branch[-1])
817 def do_vctest_once(self, shouldSucceed):
818 m = self.master
819 vctype = self.vctype
820 args = self.helper.vcargs
821 vcdir = os.path.join(self.slavebase, "vc-dir", "source")
822 workdir = os.path.join(self.slavebase, "vc-dir", "build")
823 # woo double-substitution
824 s = "s(%s, timeout=200, workdir='build', mode='clobber'" % (vctype,)
825 for k,v in args.items():
826 s += ", %s=%s" % (k, repr(v))
827 s += ")"
828 config = config_vc % s
830 m.loadConfig(config)
831 m.readConfig = True
832 m.startService()
834 self.connectSlave()
835 d = self.doBuild(shouldSucceed) # initial checkout
836 return d
838 def do_branch(self):
839 log.msg("do_branch")
840 vctype = self.vctype
841 args = self.helper.vcargs
842 m = self.master
843 self.vcdir = os.path.join(self.slavebase, "vc-dir", "source")
844 self.workdir = os.path.join(self.slavebase, "vc-dir", "build")
845 s = "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype,)
846 for k,v in args.items():
847 s += ", %s=%s" % (k, repr(v))
848 s += ")"
849 self.config = config_vc % s
851 m.loadConfig(self.config % "update")
852 m.readConfig = True
853 m.startService()
855 # first we do a build of the trunk
856 d = self.connectSlave()
857 d.addCallback(lambda res: self.doBuild(ss=SourceStamp()))
858 d.addCallback(self._doBranch_1)
859 return d
860 def _doBranch_1(self, bs):
861 log.msg("_doBranch_1")
862 # make sure the checkout was of the trunk
863 main_c = os.path.join(self.slavebase, "vc-dir", "build", "main.c")
864 data = open(main_c, "r").read()
865 self.failUnlessIn("Hello world.", data)
867 # now do a checkout on the branch. The change in branch name should
868 # trigger a clobber.
869 self.touch(self.workdir, "newfile")
870 d = self.doBuild(ss=SourceStamp(branch=self.helper.branchname))
871 d.addCallback(self._doBranch_2)
872 return d
873 def _doBranch_2(self, bs):
874 log.msg("_doBranch_2")
875 # make sure it was on the branch
876 main_c = os.path.join(self.slavebase, "vc-dir", "build", "main.c")
877 data = open(main_c, "r").read()
878 self.failUnlessIn("Hello branch.", data)
879 # and make sure the tree was clobbered
880 self.shouldNotExist(self.workdir, "newfile")
882 # doing another build on the same branch should not clobber the tree
883 self.touch(self.workdir, "newbranchfile")
884 d = self.doBuild(ss=SourceStamp(branch=self.helper.branchname))
885 d.addCallback(self._doBranch_3)
886 return d
887 def _doBranch_3(self, bs):
888 log.msg("_doBranch_3")
889 # make sure it is still on the branch
890 main_c = os.path.join(self.slavebase, "vc-dir", "build", "main.c")
891 data = open(main_c, "r").read()
892 self.failUnlessIn("Hello branch.", data)
893 # and make sure the tree was not clobbered
894 self.shouldExist(self.workdir, "newbranchfile")
896 # now make sure that a non-branch checkout clobbers the tree
897 d = self.doBuild(ss=SourceStamp())
898 d.addCallback(self._doBranch_4)
899 return d
900 def _doBranch_4(self, bs):
901 log.msg("_doBranch_4")
902 # make sure it was on the trunk
903 main_c = os.path.join(self.slavebase, "vc-dir", "build", "main.c")
904 data = open(main_c, "r").read()
905 self.failUnlessIn("Hello world.", data)
906 self.shouldNotExist(self.workdir, "newbranchfile")
908 def do_getpatch(self, doBranch=True):
909 log.msg("do_getpatch")
910 # prepare a buildslave to do checkouts
911 vctype = self.vctype
912 args = self.helper.vcargs
913 m = self.master
914 self.vcdir = os.path.join(self.slavebase, "vc-dir", "source")
915 self.workdir = os.path.join(self.slavebase, "vc-dir", "build")
916 # woo double-substitution
917 s = "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype,)
918 for k,v in args.items():
919 s += ", %s=%s" % (k, repr(v))
920 s += ")"
921 config = config_vc % s
923 m.loadConfig(config % 'clobber')
924 m.readConfig = True
925 m.startService()
927 d = self.connectSlave()
929 # then set up the "developer's tree". first we modify a tree from the
930 # head of the trunk
931 tmpdir = "try_workdir"
932 self.trydir = os.path.join(self.helper.repbase, tmpdir)
933 rmdirRecursive(self.trydir)
934 d.addCallback(self.do_getpatch_trunkhead)
935 d.addCallback(self.do_getpatch_trunkold)
936 if doBranch:
937 d.addCallback(self.do_getpatch_branch)
938 d.addCallback(self.do_getpatch_finish)
939 return d
941 def do_getpatch_finish(self, res):
942 log.msg("do_getpatch_finish")
943 self.helper.vc_try_finish(self.trydir)
944 return res
946 def try_shouldMatch(self, filename):
947 devfilename = os.path.join(self.trydir, filename)
948 devfile = open(devfilename, "r").read()
949 slavefilename = os.path.join(self.workdir, filename)
950 slavefile = open(slavefilename, "r").read()
951 self.failUnlessEqual(devfile, slavefile,
952 ("slavefile (%s) contains '%s'. "
953 "developer's file (%s) contains '%s'. "
954 "These ought to match") %
955 (slavefilename, slavefile,
956 devfilename, devfile))
958 def do_getpatch_trunkhead(self, res):
959 log.msg("do_getpatch_trunkhead")
960 d = self.helper.vc_try_checkout(self.trydir, self.helper.trunk[-1])
961 d.addCallback(self._do_getpatch_trunkhead_1)
962 return d
963 def _do_getpatch_trunkhead_1(self, res):
964 log.msg("_do_getpatch_trunkhead_1")
965 d = tryclient.getSourceStamp(self.vctype_try, self.trydir, None)
966 d.addCallback(self._do_getpatch_trunkhead_2)
967 return d
968 def _do_getpatch_trunkhead_2(self, ss):
969 log.msg("_do_getpatch_trunkhead_2")
970 d = self.doBuild(ss=ss)
971 d.addCallback(self._do_getpatch_trunkhead_3)
972 return d
973 def _do_getpatch_trunkhead_3(self, res):
974 log.msg("_do_getpatch_trunkhead_3")
975 # verify that the resulting buildslave tree matches the developer's
976 self.try_shouldMatch("main.c")
977 self.try_shouldMatch("version.c")
978 self.try_shouldMatch(os.path.join("subdir", "subdir.c"))
980 def do_getpatch_trunkold(self, res):
981 log.msg("do_getpatch_trunkold")
982 # now try a tree from an older revision. We need at least two
983 # revisions here, so we might have to create one first
984 if len(self.helper.trunk) < 2:
985 d = self.helper.vc_revise()
986 d.addCallback(self._do_getpatch_trunkold_1)
987 return d
988 return self._do_getpatch_trunkold_1()
989 def _do_getpatch_trunkold_1(self, res=None):
990 log.msg("_do_getpatch_trunkold_1")
991 d = self.helper.vc_try_checkout(self.trydir, self.helper.trunk[-2])
992 d.addCallback(self._do_getpatch_trunkold_2)
993 return d
994 def _do_getpatch_trunkold_2(self, res):
995 log.msg("_do_getpatch_trunkold_2")
996 d = tryclient.getSourceStamp(self.vctype_try, self.trydir, None)
997 d.addCallback(self._do_getpatch_trunkold_3)
998 return d
999 def _do_getpatch_trunkold_3(self, ss):
1000 log.msg("_do_getpatch_trunkold_3")
1001 d = self.doBuild(ss=ss)
1002 d.addCallback(self._do_getpatch_trunkold_4)
1003 return d
1004 def _do_getpatch_trunkold_4(self, res):
1005 log.msg("_do_getpatch_trunkold_4")
1006 # verify that the resulting buildslave tree matches the developer's
1007 self.try_shouldMatch("main.c")
1008 self.try_shouldMatch("version.c")
1009 self.try_shouldMatch(os.path.join("subdir", "subdir.c"))
1011 def do_getpatch_branch(self, res):
1012 log.msg("do_getpatch_branch")
1013 # now try a tree from a branch
1014 d = self.helper.vc_try_checkout(self.trydir, self.helper.branch[-1],
1015 self.helper.branchname)
1016 d.addCallback(self._do_getpatch_branch_1)
1017 return d
1018 def _do_getpatch_branch_1(self, res):
1019 log.msg("_do_getpatch_branch_1")
1020 d = tryclient.getSourceStamp(self.vctype_try, self.trydir,
1021 self.helper.try_branchname)
1022 d.addCallback(self._do_getpatch_branch_2)
1023 return d
1024 def _do_getpatch_branch_2(self, ss):
1025 log.msg("_do_getpatch_branch_2")
1026 d = self.doBuild(ss=ss)
1027 d.addCallback(self._do_getpatch_branch_3)
1028 return d
1029 def _do_getpatch_branch_3(self, res):
1030 log.msg("_do_getpatch_branch_3")
1031 # verify that the resulting buildslave tree matches the developer's
1032 self.try_shouldMatch("main.c")
1033 self.try_shouldMatch("version.c")
1034 self.try_shouldMatch(os.path.join("subdir", "subdir.c"))
1037 def dumpPatch(self, patch):
1038 # this exists to help me figure out the right 'patchlevel' value
1039 # should be returned by tryclient.getSourceStamp
1040 n = self.mktemp()
1041 open(n,"w").write(patch)
1042 d = self.runCommand(".", ["lsdiff", n])
1043 def p(res): print "lsdiff:", res.strip().split("\n")
1044 d.addCallback(p)
1045 return d
1048 def tearDown(self):
1049 d = defer.succeed(None)
1050 if self.slave:
1051 d2 = self.master.botmaster.waitUntilBuilderDetached("vc")
1052 d.addCallback(lambda res: self.slave.stopService())
1053 d.addCallback(lambda res: d2)
1054 if self.master:
1055 d.addCallback(lambda res: self.master.stopService())
1056 if self.httpServer:
1057 d.addCallback(lambda res: self.httpServer.stopListening())
1058 def stopHTTPTimer():
1059 from twisted.web import http
1060 http._logDateTimeStop() # shut down the internal timer. DUMB!
1061 d.addCallback(lambda res: stopHTTPTimer())
1062 d.addCallback(lambda res: self.tearDown2())
1063 return d
1065 def tearDown2(self):
1066 pass
1068 class CVSHelper(BaseHelper):
1069 branchname = "branch"
1070 try_branchname = "branch"
1072 def capable(self):
1073 cvspaths = which('cvs')
1074 if not cvspaths:
1075 return (False, "CVS is not installed")
1076 # cvs-1.10 (as shipped with OS-X 10.3 "Panther") is too old for this
1077 # test. There is a situation where we check out a tree, make a
1078 # change, then commit it back, and CVS refuses to believe that we're
1079 # operating in a CVS tree. I tested cvs-1.12.9 and it works ok, OS-X
1080 # 10.4 "Tiger" comes with cvs-1.11, but I haven't tested that yet.
1081 # For now, skip the tests if we've got 1.10 .
1082 log.msg("running %s --version.." % (cvspaths[0],))
1083 d = utils.getProcessOutput(cvspaths[0], ["--version"],
1084 env=os.environ)
1085 d.addCallback(self._capable, cvspaths[0])
1086 return d
1088 def _capable(self, v, vcexe):
1089 m = re.search(r'\(CVS\) ([\d\.]+) ', v)
1090 if not m:
1091 log.msg("couldn't identify CVS version number in output:")
1092 log.msg("'''%s'''" % v)
1093 log.msg("skipping tests")
1094 return (False, "Found CVS but couldn't identify its version")
1095 ver = m.group(1)
1096 log.msg("found CVS version '%s'" % ver)
1097 if ver == "1.10":
1098 return (False, "Found CVS, but it is too old")
1099 self.vcexe = vcexe
1100 return (True, None)
1102 def getdate(self):
1103 # this timestamp is eventually passed to CVS in a -D argument, and
1104 # strftime's %z specifier doesn't seem to work reliably (I get +0000
1105 # where I should get +0700 under linux sometimes, and windows seems
1106 # to want to put a verbose 'Eastern Standard Time' in there), so
1107 # leave off the timezone specifier and treat this as localtime. A
1108 # valid alternative would be to use a hard-coded +0000 and
1109 # time.gmtime().
1110 return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
1112 def createRepository(self):
1113 self.createBasedir()
1114 self.cvsrep = cvsrep = os.path.join(self.repbase, "CVS-Repository")
1115 tmp = os.path.join(self.repbase, "cvstmp")
1117 w = self.dovc(self.repbase, ['-d', cvsrep, 'init'])
1118 yield w; w.getResult() # we must getResult() to raise any exceptions
1120 self.populate(tmp)
1121 cmd = ['-d', self.cvsrep, 'import',
1122 '-m', 'sample_project_files', 'sample', 'vendortag', 'start']
1123 w = self.dovc(tmp, cmd)
1124 yield w; w.getResult()
1125 rmdirRecursive(tmp)
1126 # take a timestamp as the first revision number
1127 time.sleep(2)
1128 self.addTrunkRev(self.getdate())
1129 time.sleep(2)
1131 w = self.dovc(self.repbase,
1132 ['-d', self.cvsrep, 'checkout', '-d', 'cvstmp', 'sample'])
1133 yield w; w.getResult()
1135 w = self.dovc(tmp, ['tag', '-b', self.branchname])
1136 yield w; w.getResult()
1137 self.populate_branch(tmp)
1138 w = self.dovc(tmp,
1139 ['commit', '-m', 'commit_on_branch', '-r', self.branchname])
1140 yield w; w.getResult()
1141 rmdirRecursive(tmp)
1142 time.sleep(2)
1143 self.addBranchRev(self.getdate())
1144 time.sleep(2)
1145 self.vcargs = { 'cvsroot': self.cvsrep, 'cvsmodule': "sample" }
1146 createRepository = deferredGenerator(createRepository)
1149 def vc_revise(self):
1150 tmp = os.path.join(self.repbase, "cvstmp")
1152 w = self.dovc(self.repbase,
1153 ['-d', self.cvsrep, 'checkout', '-d', 'cvstmp', 'sample'])
1154 yield w; w.getResult()
1155 self.version += 1
1156 version_c = VERSION_C % self.version
1157 open(os.path.join(tmp, "version.c"), "w").write(version_c)
1158 w = self.dovc(tmp,
1159 ['commit', '-m', 'revised_to_%d' % self.version, 'version.c'])
1160 yield w; w.getResult()
1161 rmdirRecursive(tmp)
1162 time.sleep(2)
1163 self.addTrunkRev(self.getdate())
1164 time.sleep(2)
1165 vc_revise = deferredGenerator(vc_revise)
1167 def vc_try_checkout(self, workdir, rev, branch=None):
1168 # 'workdir' is an absolute path
1169 assert os.path.abspath(workdir) == workdir
1170 cmd = [self.vcexe, "-d", self.cvsrep, "checkout",
1171 "-d", workdir,
1172 "-D", rev]
1173 if branch is not None:
1174 cmd.append("-r")
1175 cmd.append(branch)
1176 cmd.append("sample")
1177 w = self.do(self.repbase, cmd)
1178 yield w; w.getResult()
1179 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
1180 vc_try_checkout = deferredGenerator(vc_try_checkout)
1182 def vc_try_finish(self, workdir):
1183 rmdirRecursive(workdir)
1185 class CVS(VCBase, unittest.TestCase):
1186 vc_name = "cvs"
1188 metadir = "CVS"
1189 vctype = "source.CVS"
1190 vctype_try = "cvs"
1191 # CVS gives us got_revision, but it is based entirely upon the local
1192 # clock, which means it is unlikely to match the timestamp taken earlier.
1193 # This might be enough for common use, but won't be good enough for our
1194 # tests to accept, so pretend it doesn't have got_revision at all.
1195 has_got_revision = False
1197 def testCheckout(self):
1198 d = self.do_vctest()
1199 return d
1201 def testPatch(self):
1202 d = self.do_patch()
1203 return d
1205 def testCheckoutBranch(self):
1206 d = self.do_branch()
1207 return d
1209 def testTry(self):
1210 d = self.do_getpatch(doBranch=False)
1211 return d
1213 VCS.registerVC(CVS.vc_name, CVSHelper())
1216 class SVNHelper(BaseHelper):
1217 branchname = "sample/branch"
1218 try_branchname = "sample/branch"
1220 def capable(self):
1221 svnpaths = which('svn')
1222 svnadminpaths = which('svnadmin')
1223 if not svnpaths:
1224 return (False, "SVN is not installed")
1225 if not svnadminpaths:
1226 return (False, "svnadmin is not installed")
1227 # we need svn to be compiled with the ra_local access
1228 # module
1229 log.msg("running svn --version..")
1230 env = os.environ.copy()
1231 env['LC_ALL'] = "C"
1232 d = utils.getProcessOutput(svnpaths[0], ["--version"],
1233 env=env)
1234 d.addCallback(self._capable, svnpaths[0], svnadminpaths[0])
1235 return d
1237 def _capable(self, v, vcexe, svnadmin):
1238 if v.find("handles 'file' schem") != -1:
1239 # older versions say 'schema', 1.2.0 and beyond say 'scheme'
1240 self.vcexe = vcexe
1241 self.svnadmin = svnadmin
1242 return (True, None)
1243 excuse = ("%s found but it does not support 'file:' " +
1244 "schema, skipping svn tests") % vcexe
1245 log.msg(excuse)
1246 return (False, excuse)
1248 def createRepository(self):
1249 self.createBasedir()
1250 self.svnrep = os.path.join(self.repbase,
1251 "SVN-Repository").replace('\\','/')
1252 tmp = os.path.join(self.repbase, "svntmp")
1253 if sys.platform == 'win32':
1254 # On Windows Paths do not start with a /
1255 self.svnurl = "file:///%s" % self.svnrep
1256 else:
1257 self.svnurl = "file://%s" % self.svnrep
1258 self.svnurl_trunk = self.svnurl + "/sample/trunk"
1259 self.svnurl_branch = self.svnurl + "/sample/branch"
1261 w = self.do(self.repbase, [self.svnadmin, "create", self.svnrep])
1262 yield w; w.getResult()
1264 self.populate(tmp)
1265 w = self.dovc(tmp,
1266 ['import', '-m', 'sample_project_files', self.svnurl_trunk])
1267 yield w; out = w.getResult()
1268 rmdirRecursive(tmp)
1269 m = re.search(r'Committed revision (\d+)\.', out)
1270 assert m.group(1) == "1" # first revision is always "1"
1271 self.addTrunkRev(int(m.group(1)))
1273 w = self.dovc(self.repbase,
1274 ['checkout', self.svnurl_trunk, 'svntmp'])
1275 yield w; w.getResult()
1277 w = self.dovc(tmp, ['cp', '-m' , 'make_branch', self.svnurl_trunk,
1278 self.svnurl_branch])
1279 yield w; w.getResult()
1280 w = self.dovc(tmp, ['switch', self.svnurl_branch])
1281 yield w; w.getResult()
1282 self.populate_branch(tmp)
1283 w = self.dovc(tmp, ['commit', '-m', 'commit_on_branch'])
1284 yield w; out = w.getResult()
1285 rmdirRecursive(tmp)
1286 m = re.search(r'Committed revision (\d+)\.', out)
1287 self.addBranchRev(int(m.group(1)))
1288 createRepository = deferredGenerator(createRepository)
1290 def vc_revise(self):
1291 tmp = os.path.join(self.repbase, "svntmp")
1292 rmdirRecursive(tmp)
1293 log.msg("vc_revise" + self.svnurl_trunk)
1294 w = self.dovc(self.repbase,
1295 ['checkout', self.svnurl_trunk, 'svntmp'])
1296 yield w; w.getResult()
1297 self.version += 1
1298 version_c = VERSION_C % self.version
1299 open(os.path.join(tmp, "version.c"), "w").write(version_c)
1300 w = self.dovc(tmp, ['commit', '-m', 'revised_to_%d' % self.version])
1301 yield w; out = w.getResult()
1302 m = re.search(r'Committed revision (\d+)\.', out)
1303 self.addTrunkRev(int(m.group(1)))
1304 rmdirRecursive(tmp)
1305 vc_revise = deferredGenerator(vc_revise)
1307 def vc_try_checkout(self, workdir, rev, branch=None):
1308 assert os.path.abspath(workdir) == workdir
1309 if os.path.exists(workdir):
1310 rmdirRecursive(workdir)
1311 if not branch:
1312 svnurl = self.svnurl_trunk
1313 else:
1314 # N.B.: this is *not* os.path.join: SVN URLs use slashes
1315 # regardless of the host operating system's filepath separator
1316 svnurl = self.svnurl + "/" + branch
1317 w = self.dovc(self.repbase,
1318 ['checkout', svnurl, workdir])
1319 yield w; w.getResult()
1320 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
1321 vc_try_checkout = deferredGenerator(vc_try_checkout)
1323 def vc_try_finish(self, workdir):
1324 rmdirRecursive(workdir)
1327 class SVN(VCBase, unittest.TestCase):
1328 vc_name = "svn"
1330 metadir = ".svn"
1331 vctype = "source.SVN"
1332 vctype_try = "svn"
1333 has_got_revision = True
1334 has_got_revision_branches_are_merged = True
1336 def testCheckout(self):
1337 # we verify this one with the svnurl style of vcargs. We test the
1338 # baseURL/defaultBranch style in testPatch and testCheckoutBranch.
1339 self.helper.vcargs = { 'svnurl': self.helper.svnurl_trunk }
1340 d = self.do_vctest()
1341 return d
1343 def testPatch(self):
1344 self.helper.vcargs = { 'baseURL': self.helper.svnurl + "/",
1345 'defaultBranch': "sample/trunk",
1347 d = self.do_patch()
1348 return d
1350 def testCheckoutBranch(self):
1351 self.helper.vcargs = { 'baseURL': self.helper.svnurl + "/",
1352 'defaultBranch': "sample/trunk",
1354 d = self.do_branch()
1355 return d
1357 def testTry(self):
1358 # extract the base revision and patch from a modified tree, use it to
1359 # create the same contents on the buildslave
1360 self.helper.vcargs = { 'baseURL': self.helper.svnurl + "/",
1361 'defaultBranch': "sample/trunk",
1363 d = self.do_getpatch()
1364 return d
1366 ## can't test the username= and password= options, because we do not have an
1367 ## svn repository that requires authentication.
1369 VCS.registerVC(SVN.vc_name, SVNHelper())
1372 class P4Helper(BaseHelper):
1373 branchname = "branch"
1374 p4port = 'localhost:1666'
1375 pid = None
1376 base_descr = 'Change: new\nDescription: asdf\nFiles:\n'
1378 def capable(self):
1379 p4paths = which('p4')
1380 p4dpaths = which('p4d')
1381 if not p4paths:
1382 return (False, "p4 is not installed")
1383 if not p4dpaths:
1384 return (False, "p4d is not installed")
1385 self.vcexe = p4paths[0]
1386 self.p4dexe = p4dpaths[0]
1387 return (True, None)
1389 class _P4DProtocol(protocol.ProcessProtocol):
1390 def __init__(self):
1391 self.started = defer.Deferred()
1392 self.ended = defer.Deferred()
1394 def outReceived(self, data):
1395 # When it says starting, it has bound to the socket.
1396 if self.started:
1398 # Make sure p4d has started. Newer versions of p4d
1399 # have more verbose messaging when db files don't exist, so
1400 # we use re.search instead of startswith.
1402 if re.search('Perforce Server starting...', data):
1403 self.started.callback(None)
1404 else:
1405 print "p4d said %r" % data
1406 try:
1407 raise Exception('p4d said %r' % data)
1408 except:
1409 self.started.errback(failure.Failure())
1410 self.started = None
1412 def errReceived(self, data):
1413 print "p4d stderr: %s" % data
1415 def processEnded(self, status_object):
1416 if status_object.check(error.ProcessDone):
1417 self.ended.callback(None)
1418 else:
1419 self.ended.errback(status_object)
1421 def _start_p4d(self):
1422 proto = self._P4DProtocol()
1423 reactor.spawnProcess(proto, self.p4dexe, ['p4d', '-p', self.p4port],
1424 env=os.environ, path=self.p4rep)
1425 return proto.started, proto.ended
1427 def dop4(self, basedir, command, failureIsOk=False, stdin=None):
1428 # p4 looks at $PWD instead of getcwd(), which causes confusion when
1429 # we spawn commands without an intervening shell (sh -c). We can
1430 # override this with a -d argument.
1431 command = "-p %s -d %s %s" % (self.p4port, basedir, command)
1432 return self.dovc(basedir, command, failureIsOk, stdin)
1434 def createRepository(self):
1435 # this is only called once per VC system, so start p4d here.
1437 self.createBasedir()
1438 tmp = os.path.join(self.repbase, "p4tmp")
1439 self.p4rep = os.path.join(self.repbase, 'P4-Repository')
1440 os.mkdir(self.p4rep)
1442 # Launch p4d.
1443 started, self.p4d_shutdown = self._start_p4d()
1444 w = waitForDeferred(started)
1445 yield w; w.getResult()
1447 # Create client spec.
1448 os.mkdir(tmp)
1449 clispec = 'Client: creator\n'
1450 clispec += 'Root: %s\n' % tmp
1451 clispec += 'View:\n'
1452 clispec += '\t//depot/... //creator/...\n'
1453 w = self.dop4(tmp, 'client -i', stdin=clispec)
1454 yield w; w.getResult()
1456 # Create first rev (trunk).
1457 self.populate(os.path.join(tmp, 'trunk'))
1458 files = ['main.c', 'version.c', 'subdir/subdir.c']
1459 w = self.dop4(tmp, "-c creator add "
1460 + " ".join(['trunk/%s' % f for f in files]))
1461 yield w; w.getResult()
1462 descr = self.base_descr
1463 for file in files:
1464 descr += '\t//depot/trunk/%s\n' % file
1465 w = self.dop4(tmp, "-c creator submit -i", stdin=descr)
1466 yield w; out = w.getResult()
1467 m = re.search(r'Change (\d+) submitted.', out)
1468 assert m.group(1) == '1'
1469 self.addTrunkRev(m.group(1))
1471 # Create second rev (branch).
1472 w = self.dop4(tmp, '-c creator integrate '
1473 + '//depot/trunk/... //depot/branch/...')
1474 yield w; w.getResult()
1475 w = self.dop4(tmp, "-c creator edit branch/main.c")
1476 yield w; w.getResult()
1477 self.populate_branch(os.path.join(tmp, 'branch'))
1478 descr = self.base_descr
1479 for file in files:
1480 descr += '\t//depot/branch/%s\n' % file
1481 w = self.dop4(tmp, "-c creator submit -i", stdin=descr)
1482 yield w; out = w.getResult()
1483 m = re.search(r'Change (\d+) submitted.', out)
1484 self.addBranchRev(m.group(1))
1485 createRepository = deferredGenerator(createRepository)
1487 def vc_revise(self):
1488 tmp = os.path.join(self.repbase, "p4tmp")
1489 self.version += 1
1490 version_c = VERSION_C % self.version
1491 w = self.dop4(tmp, '-c creator edit trunk/version.c')
1492 yield w; w.getResult()
1493 open(os.path.join(tmp, "trunk/version.c"), "w").write(version_c)
1494 descr = self.base_descr + '\t//depot/trunk/version.c\n'
1495 w = self.dop4(tmp, "-c creator submit -i", stdin=descr)
1496 yield w; out = w.getResult()
1497 m = re.search(r'Change (\d+) submitted.', out)
1498 self.addTrunkRev(m.group(1))
1499 vc_revise = deferredGenerator(vc_revise)
1501 def shutdown_p4d(self):
1502 d = self.runCommand(self.repbase, '%s -p %s admin stop'
1503 % (self.vcexe, self.p4port))
1504 return d.addCallback(lambda _: self.p4d_shutdown)
1506 class P4(VCBase, unittest.TestCase):
1507 metadir = None
1508 vctype = "source.P4"
1509 vc_name = "p4"
1510 has_got_revision = True
1512 def tearDownClass(self):
1513 if self.helper:
1514 return self.helper.shutdown_p4d()
1516 def testCheckout(self):
1517 self.helper.vcargs = { 'p4port': self.helper.p4port,
1518 'p4base': '//depot/',
1519 'defaultBranch': 'trunk' }
1520 d = self.do_vctest(testRetry=False)
1521 # TODO: like arch and darcs, sync does nothing when server is not
1522 # changed.
1523 return d
1525 def testCheckoutBranch(self):
1526 self.helper.vcargs = { 'p4port': self.helper.p4port,
1527 'p4base': '//depot/',
1528 'defaultBranch': 'trunk' }
1529 d = self.do_branch()
1530 return d
1532 def testPatch(self):
1533 self.helper.vcargs = { 'p4port': self.helper.p4port,
1534 'p4base': '//depot/',
1535 'defaultBranch': 'trunk' }
1536 d = self.do_patch()
1537 return d
1539 VCS.registerVC(P4.vc_name, P4Helper())
1542 class DarcsHelper(BaseHelper):
1543 branchname = "branch"
1544 try_branchname = "branch"
1546 def capable(self):
1547 darcspaths = which('darcs')
1548 if not darcspaths:
1549 return (False, "Darcs is not installed")
1550 self.vcexe = darcspaths[0]
1551 return (True, None)
1553 def createRepository(self):
1554 self.createBasedir()
1555 self.darcs_base = os.path.join(self.repbase, "Darcs-Repository")
1556 self.rep_trunk = os.path.join(self.darcs_base, "trunk")
1557 self.rep_branch = os.path.join(self.darcs_base, "branch")
1558 tmp = os.path.join(self.repbase, "darcstmp")
1560 os.makedirs(self.rep_trunk)
1561 w = self.dovc(self.rep_trunk, ["initialize"])
1562 yield w; w.getResult()
1563 os.makedirs(self.rep_branch)
1564 w = self.dovc(self.rep_branch, ["initialize"])
1565 yield w; w.getResult()
1567 self.populate(tmp)
1568 w = self.dovc(tmp, qw("initialize"))
1569 yield w; w.getResult()
1570 w = self.dovc(tmp, qw("add -r ."))
1571 yield w; w.getResult()
1572 w = self.dovc(tmp, qw("record -a -m initial_import --skip-long-comment -A test@buildbot.sf.net"))
1573 yield w; w.getResult()
1574 w = self.dovc(tmp, ["push", "-a", self.rep_trunk])
1575 yield w; w.getResult()
1576 w = self.dovc(tmp, qw("changes --context"))
1577 yield w; out = w.getResult()
1578 self.addTrunkRev(out)
1580 self.populate_branch(tmp)
1581 w = self.dovc(tmp, qw("record -a --ignore-times -m commit_on_branch --skip-long-comment -A test@buildbot.sf.net"))
1582 yield w; w.getResult()
1583 w = self.dovc(tmp, ["push", "-a", self.rep_branch])
1584 yield w; w.getResult()
1585 w = self.dovc(tmp, qw("changes --context"))
1586 yield w; out = w.getResult()
1587 self.addBranchRev(out)
1588 rmdirRecursive(tmp)
1589 createRepository = deferredGenerator(createRepository)
1591 def vc_revise(self):
1592 tmp = os.path.join(self.repbase, "darcstmp")
1593 os.makedirs(tmp)
1594 w = self.dovc(tmp, qw("initialize"))
1595 yield w; w.getResult()
1596 w = self.dovc(tmp, ["pull", "-a", self.rep_trunk])
1597 yield w; w.getResult()
1599 self.version += 1
1600 version_c = VERSION_C % self.version
1601 open(os.path.join(tmp, "version.c"), "w").write(version_c)
1602 w = self.dovc(tmp, qw("record -a --ignore-times -m revised_to_%d --skip-long-comment -A test@buildbot.sf.net" % self.version))
1603 yield w; w.getResult()
1604 w = self.dovc(tmp, ["push", "-a", self.rep_trunk])
1605 yield w; w.getResult()
1606 w = self.dovc(tmp, qw("changes --context"))
1607 yield w; out = w.getResult()
1608 self.addTrunkRev(out)
1609 rmdirRecursive(tmp)
1610 vc_revise = deferredGenerator(vc_revise)
1612 def vc_try_checkout(self, workdir, rev, branch=None):
1613 assert os.path.abspath(workdir) == workdir
1614 if os.path.exists(workdir):
1615 rmdirRecursive(workdir)
1616 os.makedirs(workdir)
1617 w = self.dovc(workdir, qw("initialize"))
1618 yield w; w.getResult()
1619 if not branch:
1620 rep = self.rep_trunk
1621 else:
1622 rep = os.path.join(self.darcs_base, branch)
1623 w = self.dovc(workdir, ["pull", "-a", rep])
1624 yield w; w.getResult()
1625 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
1626 vc_try_checkout = deferredGenerator(vc_try_checkout)
1628 def vc_try_finish(self, workdir):
1629 rmdirRecursive(workdir)
1632 class Darcs(VCBase, unittest.TestCase):
1633 vc_name = "darcs"
1635 # Darcs has a metadir="_darcs", but it does not have an 'export'
1636 # mode
1637 metadir = None
1638 vctype = "source.Darcs"
1639 vctype_try = "darcs"
1640 has_got_revision = True
1642 def testCheckout(self):
1643 self.helper.vcargs = { 'repourl': self.helper.rep_trunk }
1644 d = self.do_vctest(testRetry=False)
1646 # TODO: testRetry has the same problem with Darcs as it does for
1647 # Arch
1648 return d
1650 def testPatch(self):
1651 self.helper.vcargs = { 'baseURL': self.helper.darcs_base + "/",
1652 'defaultBranch': "trunk" }
1653 d = self.do_patch()
1654 return d
1656 def testCheckoutBranch(self):
1657 self.helper.vcargs = { 'baseURL': self.helper.darcs_base + "/",
1658 'defaultBranch': "trunk" }
1659 d = self.do_branch()
1660 return d
1662 def testCheckoutHTTP(self):
1663 self.serveHTTP()
1664 repourl = "http://localhost:%d/Darcs-Repository/trunk" % self.httpPort
1665 self.helper.vcargs = { 'repourl': repourl }
1666 d = self.do_vctest(testRetry=False)
1667 return d
1669 def testTry(self):
1670 self.helper.vcargs = { 'baseURL': self.helper.darcs_base + "/",
1671 'defaultBranch': "trunk" }
1672 d = self.do_getpatch()
1673 return d
1675 VCS.registerVC(Darcs.vc_name, DarcsHelper())
1678 class ArchCommon:
1679 def registerRepository(self, coordinates):
1680 a = self.archname
1681 w = self.dovc(self.repbase, "archives %s" % a)
1682 yield w; out = w.getResult()
1683 if out:
1684 w = self.dovc(self.repbase, "register-archive -d %s" % a)
1685 yield w; w.getResult()
1686 w = self.dovc(self.repbase, "register-archive %s" % coordinates)
1687 yield w; w.getResult()
1688 registerRepository = deferredGenerator(registerRepository)
1690 def unregisterRepository(self):
1691 a = self.archname
1692 w = self.dovc(self.repbase, "archives %s" % a)
1693 yield w; out = w.getResult()
1694 if out:
1695 w = self.dovc(self.repbase, "register-archive -d %s" % a)
1696 yield w; out = w.getResult()
1697 unregisterRepository = deferredGenerator(unregisterRepository)
1699 class TlaHelper(BaseHelper, ArchCommon):
1700 defaultbranch = "testvc--mainline--1"
1701 branchname = "testvc--branch--1"
1702 try_branchname = None # TlaExtractor can figure it out by itself
1703 archcmd = "tla"
1705 def capable(self):
1706 tlapaths = which('tla')
1707 if not tlapaths:
1708 return (False, "Arch (tla) is not installed")
1709 self.vcexe = tlapaths[0]
1710 return (True, None)
1712 def do_get(self, basedir, archive, branch, newdir):
1713 # the 'get' syntax is different between tla and baz. baz, while
1714 # claiming to honor an --archive argument, in fact ignores it. The
1715 # correct invocation is 'baz get archive/revision newdir'.
1716 if self.archcmd == "tla":
1717 w = self.dovc(basedir,
1718 "get -A %s %s %s" % (archive, branch, newdir))
1719 else:
1720 w = self.dovc(basedir,
1721 "get %s/%s %s" % (archive, branch, newdir))
1722 return w
1724 def createRepository(self):
1725 self.createBasedir()
1726 # first check to see if bazaar is around, since we'll need to know
1727 # later
1728 d = VCS.capable(Bazaar.vc_name)
1729 d.addCallback(self._createRepository_1)
1730 return d
1732 def _createRepository_1(self, res):
1733 has_baz = res[0]
1735 # pick a hopefully unique string for the archive name, in the form
1736 # test-%d@buildbot.sf.net--testvc, since otherwise multiple copies of
1737 # the unit tests run in the same user account will collide (since the
1738 # archive names are kept in the per-user ~/.arch-params/ directory).
1739 pid = os.getpid()
1740 self.archname = "test-%s-%d@buildbot.sf.net--testvc" % (self.archcmd,
1741 pid)
1742 trunk = self.defaultbranch
1743 branch = self.branchname
1745 repword = self.archcmd.capitalize()
1746 self.archrep = os.path.join(self.repbase, "%s-Repository" % repword)
1747 tmp = os.path.join(self.repbase, "archtmp")
1748 a = self.archname
1750 self.populate(tmp)
1752 w = self.dovc(tmp, "my-id", failureIsOk=True)
1753 yield w; res = w.getResult()
1754 if not res:
1755 # tla will fail a lot of operations if you have not set an ID
1756 w = self.do(tmp, [self.vcexe, "my-id",
1757 "Buildbot Test Suite <test@buildbot.sf.net>"])
1758 yield w; w.getResult()
1760 if has_baz:
1761 # bazaar keeps a cache of revisions, but this test creates a new
1762 # archive each time it is run, so the cache causes errors.
1763 # Disable the cache to avoid these problems. This will be
1764 # slightly annoying for people who run the buildbot tests under
1765 # the same UID as one which uses baz on a regular basis, but
1766 # bazaar doesn't give us a way to disable the cache just for this
1767 # one archive.
1768 cmd = "%s cache-config --disable" % VCS.getHelper('bazaar').vcexe
1769 w = self.do(tmp, cmd)
1770 yield w; w.getResult()
1772 w = waitForDeferred(self.unregisterRepository())
1773 yield w; w.getResult()
1775 # these commands can be run in any directory
1776 w = self.dovc(tmp, "make-archive -l %s %s" % (a, self.archrep))
1777 yield w; w.getResult()
1778 if self.archcmd == "tla":
1779 w = self.dovc(tmp, "archive-setup -A %s %s" % (a, trunk))
1780 yield w; w.getResult()
1781 w = self.dovc(tmp, "archive-setup -A %s %s" % (a, branch))
1782 yield w; w.getResult()
1783 else:
1784 # baz does not require an 'archive-setup' step
1785 pass
1787 # these commands must be run in the directory that is to be imported
1788 w = self.dovc(tmp, "init-tree --nested %s/%s" % (a, trunk))
1789 yield w; w.getResult()
1790 files = " ".join(["main.c", "version.c", "subdir",
1791 os.path.join("subdir", "subdir.c")])
1792 w = self.dovc(tmp, "add-id %s" % files)
1793 yield w; w.getResult()
1795 w = self.dovc(tmp, "import %s/%s" % (a, trunk))
1796 yield w; out = w.getResult()
1797 self.addTrunkRev("base-0")
1799 # create the branch
1800 if self.archcmd == "tla":
1801 branchstart = "%s--base-0" % trunk
1802 w = self.dovc(tmp, "tag -A %s %s %s" % (a, branchstart, branch))
1803 yield w; w.getResult()
1804 else:
1805 w = self.dovc(tmp, "branch %s" % branch)
1806 yield w; w.getResult()
1808 rmdirRecursive(tmp)
1810 # check out the branch
1811 w = self.do_get(self.repbase, a, branch, "archtmp")
1812 yield w; w.getResult()
1813 # and edit the file
1814 self.populate_branch(tmp)
1815 logfile = "++log.%s--%s" % (branch, a)
1816 logmsg = "Summary: commit on branch\nKeywords:\n\n"
1817 open(os.path.join(tmp, logfile), "w").write(logmsg)
1818 w = self.dovc(tmp, "commit")
1819 yield w; out = w.getResult()
1820 m = re.search(r'committed %s/%s--([\S]+)' % (a, branch),
1821 out)
1822 assert (m.group(1) == "base-0" or m.group(1).startswith("patch-"))
1823 self.addBranchRev(m.group(1))
1825 w = waitForDeferred(self.unregisterRepository())
1826 yield w; w.getResult()
1827 rmdirRecursive(tmp)
1829 # we unregister the repository each time, because we might have
1830 # changed the coordinates (since we switch from a file: URL to an
1831 # http: URL for various tests). The buildslave code doesn't forcibly
1832 # unregister the archive, so we have to do it here.
1833 w = waitForDeferred(self.unregisterRepository())
1834 yield w; w.getResult()
1836 _createRepository_1 = deferredGenerator(_createRepository_1)
1838 def vc_revise(self):
1839 # the fix needs to be done in a workspace that is linked to a
1840 # read-write version of the archive (i.e., using file-based
1841 # coordinates instead of HTTP ones), so we re-register the repository
1842 # before we begin. We unregister it when we're done to make sure the
1843 # build will re-register the correct one for whichever test is
1844 # currently being run.
1846 # except, that source.Bazaar really doesn't like it when the archive
1847 # gets unregistered behind its back. The slave tries to do a 'baz
1848 # replay' in a tree with an archive that is no longer recognized, and
1849 # baz aborts with a botched invariant exception. This causes
1850 # mode=update to fall back to clobber+get, which flunks one of the
1851 # tests (the 'newfile' check in _do_vctest_update_3 fails)
1853 # to avoid this, we take heroic steps here to leave the archive
1854 # registration in the same state as we found it.
1856 tmp = os.path.join(self.repbase, "archtmp")
1857 a = self.archname
1859 w = self.dovc(self.repbase, "archives %s" % a)
1860 yield w; out = w.getResult()
1861 assert out
1862 lines = out.split("\n")
1863 coordinates = lines[1].strip()
1865 # now register the read-write location
1866 w = waitForDeferred(self.registerRepository(self.archrep))
1867 yield w; w.getResult()
1869 trunk = self.defaultbranch
1871 w = self.do_get(self.repbase, a, trunk, "archtmp")
1872 yield w; w.getResult()
1874 # tla appears to use timestamps to determine which files have
1875 # changed, so wait long enough for the new file to have a different
1876 # timestamp
1877 time.sleep(2)
1878 self.version += 1
1879 version_c = VERSION_C % self.version
1880 open(os.path.join(tmp, "version.c"), "w").write(version_c)
1882 logfile = "++log.%s--%s" % (trunk, a)
1883 logmsg = "Summary: revised_to_%d\nKeywords:\n\n" % self.version
1884 open(os.path.join(tmp, logfile), "w").write(logmsg)
1885 w = self.dovc(tmp, "commit")
1886 yield w; out = w.getResult()
1887 m = re.search(r'committed %s/%s--([\S]+)' % (a, trunk),
1888 out)
1889 assert (m.group(1) == "base-0" or m.group(1).startswith("patch-"))
1890 self.addTrunkRev(m.group(1))
1892 # now re-register the original coordinates
1893 w = waitForDeferred(self.registerRepository(coordinates))
1894 yield w; w.getResult()
1895 rmdirRecursive(tmp)
1896 vc_revise = deferredGenerator(vc_revise)
1898 def vc_try_checkout(self, workdir, rev, branch=None):
1899 assert os.path.abspath(workdir) == workdir
1900 if os.path.exists(workdir):
1901 rmdirRecursive(workdir)
1903 a = self.archname
1905 # register the read-write location, if it wasn't already registered
1906 w = waitForDeferred(self.registerRepository(self.archrep))
1907 yield w; w.getResult()
1909 w = self.do_get(self.repbase, a, "testvc--mainline--1", workdir)
1910 yield w; w.getResult()
1912 # timestamps. ick.
1913 time.sleep(2)
1914 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
1915 vc_try_checkout = deferredGenerator(vc_try_checkout)
1917 def vc_try_finish(self, workdir):
1918 rmdirRecursive(workdir)
1920 class Arch(VCBase, unittest.TestCase):
1921 vc_name = "tla"
1923 metadir = None
1924 # Arch has a metadir="{arch}", but it does not have an 'export' mode.
1925 vctype = "source.Arch"
1926 vctype_try = "tla"
1927 has_got_revision = True
1929 def testCheckout(self):
1930 # these are the coordinates of the read-write archive used by all the
1931 # non-HTTP tests. testCheckoutHTTP overrides these.
1932 self.helper.vcargs = {'url': self.helper.archrep,
1933 'version': self.helper.defaultbranch }
1934 d = self.do_vctest(testRetry=False)
1935 # the current testRetry=True logic doesn't have the desired effect:
1936 # "update" is a no-op because arch knows that the repository hasn't
1937 # changed. Other VC systems will re-checkout missing files on
1938 # update, arch just leaves the tree untouched. TODO: come up with
1939 # some better test logic, probably involving a copy of the
1940 # repository that has a few changes checked in.
1942 return d
1944 def testCheckoutHTTP(self):
1945 self.serveHTTP()
1946 url = "http://localhost:%d/Tla-Repository" % self.httpPort
1947 self.helper.vcargs = { 'url': url,
1948 'version': "testvc--mainline--1" }
1949 d = self.do_vctest(testRetry=False)
1950 return d
1952 def testPatch(self):
1953 self.helper.vcargs = {'url': self.helper.archrep,
1954 'version': self.helper.defaultbranch }
1955 d = self.do_patch()
1956 return d
1958 def testCheckoutBranch(self):
1959 self.helper.vcargs = {'url': self.helper.archrep,
1960 'version': self.helper.defaultbranch }
1961 d = self.do_branch()
1962 return d
1964 def testTry(self):
1965 self.helper.vcargs = {'url': self.helper.archrep,
1966 'version': self.helper.defaultbranch }
1967 d = self.do_getpatch()
1968 return d
1970 VCS.registerVC(Arch.vc_name, TlaHelper())
1973 class BazaarHelper(TlaHelper):
1974 archcmd = "baz"
1976 def capable(self):
1977 bazpaths = which('baz')
1978 if not bazpaths:
1979 return (False, "Arch (baz) is not installed")
1980 self.vcexe = bazpaths[0]
1981 return (True, None)
1983 def setUp2(self, res):
1984 # we unregister the repository each time, because we might have
1985 # changed the coordinates (since we switch from a file: URL to an
1986 # http: URL for various tests). The buildslave code doesn't forcibly
1987 # unregister the archive, so we have to do it here.
1988 d = self.unregisterRepository()
1989 return d
1992 class Bazaar(Arch):
1993 vc_name = "bazaar"
1995 vctype = "source.Bazaar"
1996 vctype_try = "baz"
1997 has_got_revision = True
1999 fixtimer = None
2001 def testCheckout(self):
2002 self.helper.vcargs = {'url': self.helper.archrep,
2003 # Baz adds the required 'archive' argument
2004 'archive': self.helper.archname,
2005 'version': self.helper.defaultbranch,
2007 d = self.do_vctest(testRetry=False)
2008 # the current testRetry=True logic doesn't have the desired effect:
2009 # "update" is a no-op because arch knows that the repository hasn't
2010 # changed. Other VC systems will re-checkout missing files on
2011 # update, arch just leaves the tree untouched. TODO: come up with
2012 # some better test logic, probably involving a copy of the
2013 # repository that has a few changes checked in.
2015 return d
2017 def testCheckoutHTTP(self):
2018 self.serveHTTP()
2019 url = "http://localhost:%d/Baz-Repository" % self.httpPort
2020 self.helper.vcargs = { 'url': url,
2021 'archive': self.helper.archname,
2022 'version': self.helper.defaultbranch,
2024 d = self.do_vctest(testRetry=False)
2025 return d
2027 def testPatch(self):
2028 self.helper.vcargs = {'url': self.helper.archrep,
2029 # Baz adds the required 'archive' argument
2030 'archive': self.helper.archname,
2031 'version': self.helper.defaultbranch,
2033 d = self.do_patch()
2034 return d
2036 def testCheckoutBranch(self):
2037 self.helper.vcargs = {'url': self.helper.archrep,
2038 # Baz adds the required 'archive' argument
2039 'archive': self.helper.archname,
2040 'version': self.helper.defaultbranch,
2042 d = self.do_branch()
2043 return d
2045 def testTry(self):
2046 self.helper.vcargs = {'url': self.helper.archrep,
2047 # Baz adds the required 'archive' argument
2048 'archive': self.helper.archname,
2049 'version': self.helper.defaultbranch,
2051 d = self.do_getpatch()
2052 return d
2054 def fixRepository(self):
2055 self.fixtimer = None
2056 self.site.resource = self.root
2058 def testRetry(self):
2059 # we want to verify that source.Source(retry=) works, and the easiest
2060 # way to make VC updates break (temporarily) is to break the HTTP
2061 # server that's providing the repository. Anything else pretty much
2062 # requires mutating the (read-only) BUILDBOT_TEST_VC repository, or
2063 # modifying the buildslave's checkout command while it's running.
2065 # this test takes a while to run, so don't bother doing it with
2066 # anything other than baz
2068 self.serveHTTP()
2070 # break the repository server
2071 from twisted.web import static
2072 self.site.resource = static.Data("Sorry, repository is offline",
2073 "text/plain")
2074 # and arrange to fix it again in 5 seconds, while the test is
2075 # running.
2076 self.fixtimer = reactor.callLater(5, self.fixRepository)
2078 url = "http://localhost:%d/Baz-Repository" % self.httpPort
2079 self.helper.vcargs = { 'url': url,
2080 'archive': self.helper.archname,
2081 'version': self.helper.defaultbranch,
2082 'retry': (5.0, 4),
2084 d = self.do_vctest_once(True)
2085 d.addCallback(self._testRetry_1)
2086 return d
2087 def _testRetry_1(self, bs):
2088 # make sure there was mention of the retry attempt in the logs
2089 l = bs.getLogs()[0]
2090 self.failUnlessIn("unable to access URL", l.getText(),
2091 "funny, VC operation didn't fail at least once")
2092 self.failUnlessIn("update failed, trying 4 more times after 5 seconds",
2093 l.getTextWithHeaders(),
2094 "funny, VC operation wasn't reattempted")
2096 def testRetryFails(self):
2097 # make sure that the build eventually gives up on a repository which
2098 # is completely unavailable
2100 self.serveHTTP()
2102 # break the repository server, and leave it broken
2103 from twisted.web import static
2104 self.site.resource = static.Data("Sorry, repository is offline",
2105 "text/plain")
2107 url = "http://localhost:%d/Baz-Repository" % self.httpPort
2108 self.helper.vcargs = {'url': url,
2109 'archive': self.helper.archname,
2110 'version': self.helper.defaultbranch,
2111 'retry': (0.5, 3),
2113 d = self.do_vctest_once(False)
2114 d.addCallback(self._testRetryFails_1)
2115 return d
2116 def _testRetryFails_1(self, bs):
2117 self.failUnlessEqual(bs.getResults(), FAILURE)
2119 def tearDown2(self):
2120 if self.fixtimer:
2121 self.fixtimer.cancel()
2122 # tell tla to get rid of the leftover archive this test leaves in the
2123 # user's 'tla archives' listing. The name of this archive is provided
2124 # by the repository tarball, so the following command must use the
2125 # same name. We could use archive= to set it explicitly, but if you
2126 # change it from the default, then 'tla update' won't work.
2127 d = self.helper.unregisterRepository()
2128 return d
2130 VCS.registerVC(Bazaar.vc_name, BazaarHelper())
2132 class BzrHelper(BaseHelper):
2133 branchname = "branch"
2134 try_branchname = "branch"
2136 def capable(self):
2137 bzrpaths = which('bzr')
2138 if not bzrpaths:
2139 return (False, "bzr is not installed")
2140 self.vcexe = bzrpaths[0]
2141 return (True, None)
2143 def get_revision_number(self, out):
2144 for line in out.split("\n"):
2145 colon = line.index(":")
2146 key, value = line[:colon], line[colon+2:]
2147 if key == "revno":
2148 return int(value)
2149 raise RuntimeError("unable to find revno: in bzr output: '%s'" % out)
2151 def createRepository(self):
2152 self.createBasedir()
2153 self.bzr_base = os.path.join(self.repbase, "Bzr-Repository")
2154 self.rep_trunk = os.path.join(self.bzr_base, "trunk")
2155 self.rep_branch = os.path.join(self.bzr_base, "branch")
2156 tmp = os.path.join(self.repbase, "bzrtmp")
2157 btmp = os.path.join(self.repbase, "bzrtmp-branch")
2159 os.makedirs(self.rep_trunk)
2160 w = self.dovc(self.rep_trunk, ["init"])
2161 yield w; w.getResult()
2162 w = self.dovc(self.bzr_base,
2163 ["branch", self.rep_trunk, self.rep_branch])
2164 yield w; w.getResult()
2166 w = self.dovc(self.repbase, ["checkout", self.rep_trunk, tmp])
2167 yield w; w.getResult()
2168 self.populate(tmp)
2169 w = self.dovc(tmp, qw("add"))
2170 yield w; w.getResult()
2171 w = self.dovc(tmp, qw("commit -m initial_import"))
2172 yield w; w.getResult()
2173 w = self.dovc(tmp, qw("version-info"))
2174 yield w; out = w.getResult()
2175 self.addTrunkRev(self.get_revision_number(out))
2176 rmdirRecursive(tmp)
2178 # pull all trunk revisions to the branch
2179 w = self.dovc(self.rep_branch, qw("pull"))
2180 yield w; w.getResult()
2181 # obtain a branch tree
2182 w = self.dovc(self.repbase, ["checkout", self.rep_branch, btmp])
2183 yield w; w.getResult()
2184 # modify it
2185 self.populate_branch(btmp)
2186 w = self.dovc(btmp, qw("add"))
2187 yield w; w.getResult()
2188 w = self.dovc(btmp, qw("commit -m commit_on_branch"))
2189 yield w; w.getResult()
2190 w = self.dovc(btmp, qw("version-info"))
2191 yield w; out = w.getResult()
2192 self.addBranchRev(self.get_revision_number(out))
2193 rmdirRecursive(btmp)
2194 createRepository = deferredGenerator(createRepository)
2196 def vc_revise(self):
2197 tmp = os.path.join(self.repbase, "bzrtmp")
2198 w = self.dovc(self.repbase, ["checkout", self.rep_trunk, tmp])
2199 yield w; w.getResult()
2201 self.version += 1
2202 version_c = VERSION_C % self.version
2203 open(os.path.join(tmp, "version.c"), "w").write(version_c)
2204 w = self.dovc(tmp, qw("commit -m revised_to_%d" % self.version))
2205 yield w; w.getResult()
2206 w = self.dovc(tmp, qw("version-info"))
2207 yield w; out = w.getResult()
2208 self.addTrunkRev(self.get_revision_number(out))
2209 rmdirRecursive(tmp)
2210 vc_revise = deferredGenerator(vc_revise)
2212 def vc_try_checkout(self, workdir, rev, branch=None):
2213 assert os.path.abspath(workdir) == workdir
2214 if os.path.exists(workdir):
2215 rmdirRecursive(workdir)
2216 #os.makedirs(workdir)
2217 if not branch:
2218 rep = self.rep_trunk
2219 else:
2220 rep = os.path.join(self.bzr_base, branch)
2221 w = self.dovc(self.bzr_base, ["checkout", rep, workdir])
2222 yield w; w.getResult()
2223 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
2224 vc_try_checkout = deferredGenerator(vc_try_checkout)
2226 def vc_try_finish(self, workdir):
2227 rmdirRecursive(workdir)
2229 class Bzr(VCBase, unittest.TestCase):
2230 vc_name = "bzr"
2232 metadir = ".bzr"
2233 vctype = "source.Bzr"
2234 vctype_try = "bzr"
2235 has_got_revision = True
2237 def testCheckout(self):
2238 self.helper.vcargs = { 'repourl': self.helper.rep_trunk }
2239 d = self.do_vctest(testRetry=False)
2241 # TODO: testRetry has the same problem with Bzr as it does for
2242 # Arch
2243 return d
2245 def testPatch(self):
2246 self.helper.vcargs = { 'baseURL': self.helper.bzr_base + "/",
2247 'defaultBranch': "trunk" }
2248 d = self.do_patch()
2249 return d
2251 def testCheckoutBranch(self):
2252 self.helper.vcargs = { 'baseURL': self.helper.bzr_base + "/",
2253 'defaultBranch': "trunk" }
2254 d = self.do_branch()
2255 return d
2257 def testCheckoutHTTP(self):
2258 self.serveHTTP()
2259 repourl = "http://localhost:%d/Bzr-Repository/trunk" % self.httpPort
2260 self.helper.vcargs = { 'repourl': repourl }
2261 d = self.do_vctest(testRetry=False)
2262 return d
2265 def fixRepository(self):
2266 self.fixtimer = None
2267 self.site.resource = self.root
2269 def testRetry(self):
2270 # this test takes a while to run
2271 self.serveHTTP()
2273 # break the repository server
2274 from twisted.web import static
2275 self.site.resource = static.Data("Sorry, repository is offline",
2276 "text/plain")
2277 # and arrange to fix it again in 5 seconds, while the test is
2278 # running.
2279 self.fixtimer = reactor.callLater(5, self.fixRepository)
2281 repourl = "http://localhost:%d/Bzr-Repository/trunk" % self.httpPort
2282 self.helper.vcargs = { 'repourl': repourl,
2283 'retry': (5.0, 4),
2285 d = self.do_vctest_once(True)
2286 d.addCallback(self._testRetry_1)
2287 return d
2288 def _testRetry_1(self, bs):
2289 # make sure there was mention of the retry attempt in the logs
2290 l = bs.getLogs()[0]
2291 self.failUnlessIn("ERROR: Not a branch: ", l.getText(),
2292 "funny, VC operation didn't fail at least once")
2293 self.failUnlessIn("update failed, trying 4 more times after 5 seconds",
2294 l.getTextWithHeaders(),
2295 "funny, VC operation wasn't reattempted")
2297 def testRetryFails(self):
2298 # make sure that the build eventually gives up on a repository which
2299 # is completely unavailable
2301 self.serveHTTP()
2303 # break the repository server, and leave it broken
2304 from twisted.web import static
2305 self.site.resource = static.Data("Sorry, repository is offline",
2306 "text/plain")
2308 repourl = "http://localhost:%d/Bzr-Repository/trunk" % self.httpPort
2309 self.helper.vcargs = { 'repourl': repourl,
2310 'retry': (0.5, 3),
2312 d = self.do_vctest_once(False)
2313 d.addCallback(self._testRetryFails_1)
2314 return d
2315 def _testRetryFails_1(self, bs):
2316 self.failUnlessEqual(bs.getResults(), FAILURE)
2319 def testTry(self):
2320 self.helper.vcargs = { 'baseURL': self.helper.bzr_base + "/",
2321 'defaultBranch': "trunk" }
2322 d = self.do_getpatch()
2323 return d
2325 VCS.registerVC(Bzr.vc_name, BzrHelper())
2328 class MercurialHelper(BaseHelper):
2329 branchname = "branch"
2330 try_branchname = "branch"
2332 def capable(self):
2333 hgpaths = which("hg")
2334 if not hgpaths:
2335 return (False, "Mercurial is not installed")
2336 self.vcexe = hgpaths[0]
2337 return (True, None)
2339 def extract_id(self, output):
2340 m = re.search(r'^(\w+)', output)
2341 return m.group(0)
2343 def createRepository(self):
2344 self.createBasedir()
2345 self.hg_base = os.path.join(self.repbase, "Mercurial-Repository")
2346 self.rep_trunk = os.path.join(self.hg_base, "trunk")
2347 self.rep_branch = os.path.join(self.hg_base, "branch")
2348 tmp = os.path.join(self.hg_base, "hgtmp")
2350 os.makedirs(self.rep_trunk)
2351 w = self.dovc(self.rep_trunk, "init")
2352 yield w; w.getResult()
2353 os.makedirs(self.rep_branch)
2354 w = self.dovc(self.rep_branch, "init")
2355 yield w; w.getResult()
2357 self.populate(tmp)
2358 w = self.dovc(tmp, "init")
2359 yield w; w.getResult()
2360 w = self.dovc(tmp, "add")
2361 yield w; w.getResult()
2362 w = self.dovc(tmp, ['commit', '-m', 'initial_import'])
2363 yield w; w.getResult()
2364 w = self.dovc(tmp, ['push', self.rep_trunk])
2365 # note that hg-push does not actually update the working directory
2366 yield w; w.getResult()
2367 w = self.dovc(tmp, "identify")
2368 yield w; out = w.getResult()
2369 self.addTrunkRev(self.extract_id(out))
2371 self.populate_branch(tmp)
2372 w = self.dovc(tmp, ['commit', '-m', 'commit_on_branch'])
2373 yield w; w.getResult()
2374 w = self.dovc(tmp, ['push', self.rep_branch])
2375 yield w; w.getResult()
2376 w = self.dovc(tmp, "identify")
2377 yield w; out = w.getResult()
2378 self.addBranchRev(self.extract_id(out))
2379 rmdirRecursive(tmp)
2380 createRepository = deferredGenerator(createRepository)
2382 def vc_revise(self):
2383 tmp = os.path.join(self.hg_base, "hgtmp2")
2384 w = self.dovc(self.hg_base, ['clone', self.rep_trunk, tmp])
2385 yield w; w.getResult()
2387 self.version += 1
2388 version_c = VERSION_C % self.version
2389 version_c_filename = os.path.join(tmp, "version.c")
2390 open(version_c_filename, "w").write(version_c)
2391 # hg uses timestamps to distinguish files which have changed, so we
2392 # force the mtime forward a little bit
2393 future = time.time() + 2*self.version
2394 os.utime(version_c_filename, (future, future))
2395 w = self.dovc(tmp, ['commit', '-m', 'revised_to_%d' % self.version])
2396 yield w; w.getResult()
2397 w = self.dovc(tmp, ['push', self.rep_trunk])
2398 yield w; w.getResult()
2399 w = self.dovc(tmp, "identify")
2400 yield w; out = w.getResult()
2401 self.addTrunkRev(self.extract_id(out))
2402 rmdirRecursive(tmp)
2403 vc_revise = deferredGenerator(vc_revise)
2405 def vc_try_checkout(self, workdir, rev, branch=None):
2406 assert os.path.abspath(workdir) == workdir
2407 if os.path.exists(workdir):
2408 rmdirRecursive(workdir)
2409 if branch:
2410 src = self.rep_branch
2411 else:
2412 src = self.rep_trunk
2413 w = self.dovc(self.hg_base, ['clone', src, workdir])
2414 yield w; w.getResult()
2415 try_c_filename = os.path.join(workdir, "subdir", "subdir.c")
2416 open(try_c_filename, "w").write(TRY_C)
2417 future = time.time() + 2*self.version
2418 os.utime(try_c_filename, (future, future))
2419 vc_try_checkout = deferredGenerator(vc_try_checkout)
2421 def vc_try_finish(self, workdir):
2422 rmdirRecursive(workdir)
2424 class MercurialServerPP(protocol.ProcessProtocol):
2425 def __init__(self):
2426 self.wait = defer.Deferred()
2428 def outReceived(self, data):
2429 log.msg("hg-serve-stdout: %s" % (data,))
2430 def errReceived(self, data):
2431 print "HG-SERVE-STDERR:", data
2432 log.msg("hg-serve-stderr: %s" % (data,))
2433 def processEnded(self, reason):
2434 log.msg("hg-serve ended: %s" % reason)
2435 self.wait.callback(None)
2438 class Mercurial(VCBase, unittest.TestCase):
2439 vc_name = "hg"
2441 # Mercurial has a metadir=".hg", but it does not have an 'export' mode.
2442 metadir = None
2443 vctype = "source.Mercurial"
2444 vctype_try = "hg"
2445 has_got_revision = True
2446 _hg_server = None
2447 _wait_for_server_poller = None
2448 _pp = None
2450 def testCheckout(self):
2451 self.helper.vcargs = { 'repourl': self.helper.rep_trunk }
2452 d = self.do_vctest(testRetry=False)
2454 # TODO: testRetry has the same problem with Mercurial as it does for
2455 # Arch
2456 return d
2458 def testPatch(self):
2459 self.helper.vcargs = { 'baseURL': self.helper.hg_base + "/",
2460 'defaultBranch': "trunk" }
2461 d = self.do_patch()
2462 return d
2464 def testCheckoutBranch(self):
2465 self.helper.vcargs = { 'baseURL': self.helper.hg_base + "/",
2466 'defaultBranch': "trunk" }
2467 d = self.do_branch()
2468 return d
2470 def serveHTTP(self):
2471 # the easiest way to publish hg over HTTP is by running 'hg serve' as
2472 # a child process while the test is running. (you can also use a CGI
2473 # script, which sounds difficult, or you can publish the files
2474 # directly, which isn't well documented).
2476 # grr.. 'hg serve' doesn't let you use --port=0 to mean "pick a free
2477 # port", instead it uses it as a signal to use the default (port
2478 # 8000). This means there is no way to make it choose a free port, so
2479 # we are forced to make it use a statically-defined one, making it
2480 # harder to avoid collisions.
2481 self.httpPort = 8300 + (os.getpid() % 200)
2482 args = [self.helper.vcexe,
2483 "serve", "--port", str(self.httpPort), "--verbose"]
2485 # in addition, hg doesn't flush its stdout, so we can't wait for the
2486 # "listening at" message to know when it's safe to start the test.
2487 # Instead, poll every second until a getPage works.
2489 self._pp = MercurialServerPP() # logs+discards everything
2491 # this serves one tree at a time, so we serve trunk. TODO: test hg's
2492 # in-repo branches, for which a single tree will hold all branches.
2493 self._hg_server = reactor.spawnProcess(self._pp, self.helper.vcexe, args,
2494 os.environ,
2495 self.helper.rep_trunk)
2496 log.msg("waiting for hg serve to start")
2497 done_d = defer.Deferred()
2498 def poll():
2499 d = client.getPage("http://localhost:%d/" % self.httpPort)
2500 def success(res):
2501 log.msg("hg serve appears to have started")
2502 self._wait_for_server_poller.stop()
2503 done_d.callback(None)
2504 def ignore_connection_refused(f):
2505 f.trap(error.ConnectionRefusedError)
2506 d.addCallbacks(success, ignore_connection_refused)
2507 d.addErrback(done_d.errback)
2508 return d
2509 self._wait_for_server_poller = task.LoopingCall(poll)
2510 self._wait_for_server_poller.start(0.5, True)
2511 return done_d
2513 def tearDown(self):
2514 if self._wait_for_server_poller:
2515 if self._wait_for_server_poller.running:
2516 self._wait_for_server_poller.stop()
2517 if self._hg_server:
2518 self._hg_server.loseConnection()
2519 try:
2520 self._hg_server.signalProcess("KILL")
2521 except error.ProcessExitedAlready:
2522 pass
2523 self._hg_server = None
2524 return VCBase.tearDown(self)
2526 def tearDown2(self):
2527 if self._pp:
2528 return self._pp.wait
2530 def testCheckoutHTTP(self):
2531 d = self.serveHTTP()
2532 def _started(res):
2533 repourl = "http://localhost:%d/" % self.httpPort
2534 self.helper.vcargs = { 'repourl': repourl }
2535 return self.do_vctest(testRetry=False)
2536 d.addCallback(_started)
2537 return d
2539 def testTry(self):
2540 self.helper.vcargs = { 'baseURL': self.helper.hg_base + "/",
2541 'defaultBranch': "trunk" }
2542 d = self.do_getpatch()
2543 return d
2545 VCS.registerVC(Mercurial.vc_name, MercurialHelper())
2547 class MercurialInRepoHelper(MercurialHelper):
2548 branchname = "the_branch"
2549 try_branchname = "the_branch"
2552 def createRepository(self):
2553 self.createBasedir()
2554 self.hg_base = os.path.join(self.repbase, "Mercurial-Repository")
2555 self.repo = os.path.join(self.hg_base, "inrepobranch")
2556 tmp = os.path.join(self.hg_base, "hgtmp")
2558 os.makedirs(self.repo)
2559 w = self.dovc(self.repo, "init")
2560 yield w; w.getResult()
2562 self.populate(tmp)
2563 w = self.dovc(tmp, "init")
2564 yield w; w.getResult()
2565 w = self.dovc(tmp, "add")
2566 yield w; w.getResult()
2567 w = self.dovc(tmp, ['commit', '-m', 'initial_import'])
2568 yield w; w.getResult()
2569 w = self.dovc(tmp, ['push', self.repo])
2570 # note that hg-push does not actually update the working directory
2571 yield w; w.getResult()
2572 w = self.dovc(tmp, "identify")
2573 yield w; out = w.getResult()
2574 self.addTrunkRev(self.extract_id(out))
2576 self.populate_branch(tmp)
2577 w = self.dovc(tmp, ['branch', self.branchname])
2578 yield w; w.getResult()
2579 w = self.dovc(tmp, ['commit', '-m', 'commit_on_branch'])
2580 yield w; w.getResult()
2581 w = self.dovc(tmp, ['push', '-f', self.repo])
2582 yield w; w.getResult()
2583 w = self.dovc(tmp, "identify")
2584 yield w; out = w.getResult()
2585 self.addBranchRev(self.extract_id(out))
2586 rmdirRecursive(tmp)
2587 createRepository = deferredGenerator(createRepository)
2589 def vc_revise(self):
2590 tmp = os.path.join(self.hg_base, "hgtmp2")
2591 w = self.dovc(self.hg_base, ['clone', self.repo, tmp])
2592 yield w; w.getResult()
2593 w = self.dovc(tmp, ['update', '--clean', '--rev', 'default'])
2594 yield w; w.getResult()
2596 self.version += 1
2597 version_c = VERSION_C % self.version
2598 version_c_filename = os.path.join(tmp, "version.c")
2599 open(version_c_filename, "w").write(version_c)
2600 # hg uses timestamps to distinguish files which have changed, so we
2601 # force the mtime forward a little bit
2602 future = time.time() + 2*self.version
2603 os.utime(version_c_filename, (future, future))
2604 w = self.dovc(tmp, ['commit', '-m', 'revised_to_%d' % self.version])
2605 yield w; w.getResult()
2606 w = self.dovc(tmp, ['push', '--force', self.repo])
2607 yield w; w.getResult()
2608 w = self.dovc(tmp, "identify")
2609 yield w; out = w.getResult()
2610 self.addTrunkRev(self.extract_id(out))
2611 rmdirRecursive(tmp)
2612 vc_revise = deferredGenerator(vc_revise)
2614 def vc_try_checkout(self, workdir, rev, branch=None):
2615 assert os.path.abspath(workdir) == workdir
2616 if os.path.exists(workdir):
2617 rmdirRecursive(workdir)
2618 w = self.dovc(self.hg_base, ['clone', self.repo, workdir])
2619 yield w; w.getResult()
2620 if not branch: branch = "default"
2621 w = self.dovc(workdir, ['update', '--clean', '--rev', branch ])
2622 yield w; w.getResult()
2624 try_c_filename = os.path.join(workdir, "subdir", "subdir.c")
2625 open(try_c_filename, "w").write(TRY_C)
2626 future = time.time() + 2*self.version
2627 os.utime(try_c_filename, (future, future))
2628 vc_try_checkout = deferredGenerator(vc_try_checkout)
2630 def vc_try_finish(self, workdir):
2631 rmdirRecursive(workdir)
2632 pass
2635 class MercurialInRepo(Mercurial):
2636 vc_name = 'MercurialInRepo'
2638 def default_args(self):
2639 return { 'repourl': self.helper.repo,
2640 'branchType': 'inrepo',
2641 'defaultBranch': 'default' }
2643 def testCheckout(self):
2644 self.helper.vcargs = self.default_args()
2645 d = self.do_vctest(testRetry=False)
2647 # TODO: testRetry has the same problem with Mercurial as it does for
2648 # Arch
2649 return d
2651 def testPatch(self):
2652 self.helper.vcargs = self.default_args()
2653 d = self.do_patch()
2654 return d
2656 def testCheckoutBranch(self):
2657 self.helper.vcargs = self.default_args()
2658 d = self.do_branch()
2659 return d
2661 def serveHTTP(self):
2662 # the easiest way to publish hg over HTTP is by running 'hg serve' as
2663 # a child process while the test is running. (you can also use a CGI
2664 # script, which sounds difficult, or you can publish the files
2665 # directly, which isn't well documented).
2667 # grr.. 'hg serve' doesn't let you use --port=0 to mean "pick a free
2668 # port", instead it uses it as a signal to use the default (port
2669 # 8000). This means there is no way to make it choose a free port, so
2670 # we are forced to make it use a statically-defined one, making it
2671 # harder to avoid collisions.
2672 self.httpPort = 8300 + (os.getpid() % 200)
2673 args = [self.helper.vcexe,
2674 "serve", "--port", str(self.httpPort), "--verbose"]
2676 # in addition, hg doesn't flush its stdout, so we can't wait for the
2677 # "listening at" message to know when it's safe to start the test.
2678 # Instead, poll every second until a getPage works.
2680 self._pp = MercurialServerPP() # logs+discards everything
2681 # this serves one tree at a time, so we serve trunk. TODO: test hg's
2682 # in-repo branches, for which a single tree will hold all branches.
2683 self._hg_server = reactor.spawnProcess(self._pp, self.helper.vcexe, args,
2684 os.environ,
2685 self.helper.repo)
2686 log.msg("waiting for hg serve to start")
2687 done_d = defer.Deferred()
2688 def poll():
2689 d = client.getPage("http://localhost:%d/" % self.httpPort)
2690 def success(res):
2691 log.msg("hg serve appears to have started")
2692 self._wait_for_server_poller.stop()
2693 done_d.callback(None)
2694 def ignore_connection_refused(f):
2695 f.trap(error.ConnectionRefusedError)
2696 d.addCallbacks(success, ignore_connection_refused)
2697 d.addErrback(done_d.errback)
2698 return d
2699 self._wait_for_server_poller = task.LoopingCall(poll)
2700 self._wait_for_server_poller.start(0.5, True)
2701 return done_d
2703 def tearDown(self):
2704 if self._wait_for_server_poller:
2705 if self._wait_for_server_poller.running:
2706 self._wait_for_server_poller.stop()
2707 if self._hg_server:
2708 self._hg_server.loseConnection()
2709 try:
2710 self._hg_server.signalProcess("KILL")
2711 except error.ProcessExitedAlready:
2712 pass
2713 self._hg_server = None
2714 return VCBase.tearDown(self)
2716 def tearDown2(self):
2717 if self._pp:
2718 return self._pp.wait
2720 def testCheckoutHTTP(self):
2721 d = self.serveHTTP()
2722 def _started(res):
2723 repourl = "http://localhost:%d/" % self.httpPort
2724 self.helper.vcargs = self.default_args()
2725 self.helper.vcargs['repourl'] = repourl
2726 return self.do_vctest(testRetry=False)
2727 d.addCallback(_started)
2728 return d
2730 def testTry(self):
2731 self.helper.vcargs = self.default_args()
2732 d = self.do_getpatch()
2733 return d
2735 VCS.registerVC(MercurialInRepo.vc_name, MercurialInRepoHelper())
2738 class GitHelper(BaseHelper):
2739 branchname = "branch"
2740 try_branchname = "branch"
2742 def capable(self):
2743 gitpaths = which('git')
2744 if not gitpaths:
2745 return (False, "GIT is not installed")
2746 d = utils.getProcessOutput(gitpaths[0], ["--version"], env=os.environ)
2747 d.addCallback(self._capable, gitpaths[0])
2748 return d
2750 def _capable(self, v, vcexe):
2751 try:
2752 m = re.search(r'\b(\d+)\.(\d+)', v)
2754 if not m:
2755 raise Exception, 'no regex match'
2757 ver = tuple([int(num) for num in m.groups()])
2759 # git-1.1.3 (as shipped with Dapper) doesn't understand 'git
2760 # init' (it wants 'git init-db'), and fails unit tests that
2761 # involve branches. git-1.5.3.6 (on my debian/unstable system)
2762 # works. I don't know where the dividing point is: if someone can
2763 # figure it out (or figure out how to make buildbot support more
2764 # versions), please update this check.
2765 if ver < (1, 2):
2766 return (False, "Found git (%s) but it is older than 1.2.x" % vcexe)
2768 except Exception, e:
2769 log.msg("couldn't identify git version number in output:")
2770 log.msg("'''%s'''" % v)
2771 log.msg("because: %s" % e)
2772 log.msg("skipping tests")
2773 return (False,
2774 "Found git (%s) but couldn't identify its version from '%s'" % (vcexe, v))
2776 self.vcexe = vcexe
2777 return (True, None)
2779 def createRepository(self):
2780 self.createBasedir()
2781 self.gitrepo = os.path.join(self.repbase,
2782 "GIT-Repository")
2783 tmp = os.path.join(self.repbase, "gittmp")
2785 env = os.environ.copy()
2786 env['GIT_DIR'] = self.gitrepo
2787 w = self.dovc(self.repbase, "init", env=env)
2788 yield w; w.getResult()
2790 self.populate(tmp)
2791 w = self.dovc(tmp, "init")
2792 yield w; w.getResult()
2793 w = self.dovc(tmp, ["add", "."])
2794 yield w; w.getResult()
2795 w = self.dovc(tmp, ["config", "user.email", "buildbot-trial@localhost"])
2796 yield w; w.getResult()
2797 w = self.dovc(tmp, ["config", "user.name", "Buildbot Trial"])
2798 yield w; w.getResult()
2799 w = self.dovc(tmp, ["commit", "-m", "initial_import"])
2800 yield w; w.getResult()
2802 w = self.dovc(tmp, ["checkout", "-b", self.branchname])
2803 yield w; w.getResult()
2804 self.populate_branch(tmp)
2805 w = self.dovc(tmp, ["commit", "-a", "-m", "commit_on_branch"])
2806 yield w; w.getResult()
2808 w = self.dovc(tmp, ["rev-parse", "master", self.branchname])
2809 yield w; out = w.getResult()
2810 revs = out.splitlines()
2811 self.addTrunkRev(revs[0])
2812 self.addBranchRev(revs[1])
2814 w = self.dovc(tmp, ["push", self.gitrepo, "master", self.branchname])
2815 yield w; w.getResult()
2817 rmdirRecursive(tmp)
2818 createRepository = deferredGenerator(createRepository)
2820 def vc_revise(self):
2821 tmp = os.path.join(self.repbase, "gittmp")
2822 rmdirRecursive(tmp)
2823 log.msg("vc_revise" + self.gitrepo)
2824 w = self.dovc(self.repbase, ["clone", self.gitrepo, "gittmp"])
2825 yield w; w.getResult()
2826 w = self.dovc(tmp, ["config", "user.email", "buildbot-trial@localhost"])
2827 yield w; w.getResult()
2828 w = self.dovc(tmp, ["config", "user.name", "Buildbot Trial"])
2829 yield w; w.getResult()
2831 self.version += 1
2832 version_c = VERSION_C % self.version
2833 open(os.path.join(tmp, "version.c"), "w").write(version_c)
2835 w = self.dovc(tmp, ["commit", "-m", "revised_to_%d" % self.version,
2836 "version.c"])
2837 yield w; w.getResult()
2838 w = self.dovc(tmp, ["rev-parse", "master"])
2839 yield w; out = w.getResult()
2840 self.addTrunkRev(out.strip())
2842 w = self.dovc(tmp, ["push", self.gitrepo, "master"])
2843 yield w; out = w.getResult()
2844 rmdirRecursive(tmp)
2845 vc_revise = deferredGenerator(vc_revise)
2847 def vc_try_checkout(self, workdir, rev, branch=None):
2848 assert os.path.abspath(workdir) == workdir
2849 if os.path.exists(workdir):
2850 rmdirRecursive(workdir)
2852 w = self.dovc(self.repbase, ["clone", self.gitrepo, workdir])
2853 yield w; w.getResult()
2854 w = self.dovc(workdir, ["config", "user.email", "buildbot-trial@localhost"])
2855 yield w; w.getResult()
2856 w = self.dovc(workdir, ["config", "user.name", "Buildbot Trial"])
2857 yield w; w.getResult()
2859 if branch is not None:
2860 w = self.dovc(workdir, ["checkout", "-b", branch,
2861 "origin/%s" % branch])
2862 yield w; w.getResult()
2864 # Hmm...why do nobody else bother to check out the correct
2865 # revision?
2866 w = self.dovc(workdir, ["reset", "--hard", rev])
2867 yield w; w.getResult()
2869 try_c_filename = os.path.join(workdir, "subdir", "subdir.c")
2870 open(try_c_filename, "w").write(TRY_C)
2871 vc_try_checkout = deferredGenerator(vc_try_checkout)
2873 def vc_try_finish(self, workdir):
2874 rmdirRecursive(workdir)
2876 class Git(VCBase, unittest.TestCase):
2877 vc_name = "git"
2879 # No 'export' mode yet...
2880 # metadir = ".git"
2881 vctype = "source.Git"
2882 vctype_try = "git"
2883 has_got_revision = True
2885 def testCheckout(self):
2886 self.helper.vcargs = { 'repourl': self.helper.gitrepo }
2887 d = self.do_vctest()
2888 return d
2890 def testPatch(self):
2891 self.helper.vcargs = { 'repourl': self.helper.gitrepo,
2892 'branch': "master" }
2893 d = self.do_patch()
2894 return d
2896 def testCheckoutBranch(self):
2897 self.helper.vcargs = { 'repourl': self.helper.gitrepo,
2898 'branch': "master" }
2899 d = self.do_branch()
2900 return d
2902 def testTry(self):
2903 self.helper.vcargs = { 'repourl': self.helper.gitrepo,
2904 'branch': "master" }
2905 d = self.do_getpatch()
2906 return d
2908 VCS.registerVC(Git.vc_name, GitHelper())
2911 class Sources(unittest.TestCase):
2912 # TODO: this needs serious rethink
2913 def makeChange(self, when=None, revision=None):
2914 if when:
2915 when = mktime_tz(parsedate_tz(when))
2916 return changes.Change("fred", [], "", when=when, revision=revision)
2918 def testCVS1(self):
2919 r = base.BuildRequest("forced build", SourceStamp(), 'test_builder')
2920 b = base.Build([r])
2921 s = source.CVS(cvsroot=None, cvsmodule=None)
2922 s.setBuild(b)
2923 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()), None)
2925 def testCVS2(self):
2926 c = []
2927 c.append(self.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2928 c.append(self.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2929 c.append(self.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2930 r = base.BuildRequest("forced", SourceStamp(changes=c), 'test_builder')
2931 submitted = "Wed, 08 Sep 2004 09:04:00 -0700"
2932 r.submittedAt = mktime_tz(parsedate_tz(submitted))
2933 b = base.Build([r])
2934 s = source.CVS(cvsroot=None, cvsmodule=None)
2935 s.setBuild(b)
2936 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()),
2937 "Wed, 08 Sep 2004 16:03:00 -0000")
2939 def testCVS3(self):
2940 c = []
2941 c.append(self.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2942 c.append(self.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2943 c.append(self.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2944 r = base.BuildRequest("forced", SourceStamp(changes=c), 'test_builder')
2945 submitted = "Wed, 08 Sep 2004 09:04:00 -0700"
2946 r.submittedAt = mktime_tz(parsedate_tz(submitted))
2947 b = base.Build([r])
2948 s = source.CVS(cvsroot=None, cvsmodule=None, checkoutDelay=10)
2949 s.setBuild(b)
2950 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()),
2951 "Wed, 08 Sep 2004 16:02:10 -0000")
2953 def testCVS4(self):
2954 c = []
2955 c.append(self.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2956 c.append(self.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2957 c.append(self.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2958 r1 = base.BuildRequest("forced", SourceStamp(changes=c), 'test_builder')
2959 submitted = "Wed, 08 Sep 2004 09:04:00 -0700"
2960 r1.submittedAt = mktime_tz(parsedate_tz(submitted))
2962 c = []
2963 c.append(self.makeChange("Wed, 08 Sep 2004 09:05:00 -0700"))
2964 r2 = base.BuildRequest("forced", SourceStamp(changes=c), 'test_builder')
2965 submitted = "Wed, 08 Sep 2004 09:07:00 -0700"
2966 r2.submittedAt = mktime_tz(parsedate_tz(submitted))
2968 b = base.Build([r1, r2])
2969 s = source.CVS(cvsroot=None, cvsmodule=None)
2970 s.setBuild(b)
2971 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()),
2972 "Wed, 08 Sep 2004 16:06:00 -0000")
2974 def testSVN1(self):
2975 r = base.BuildRequest("forced", SourceStamp(), 'test_builder')
2976 b = base.Build([r])
2977 s = source.SVN(svnurl="dummy")
2978 s.setBuild(b)
2979 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()), None)
2981 def testSVN2(self):
2982 c = []
2983 c.append(self.makeChange(revision=4))
2984 c.append(self.makeChange(revision=10))
2985 c.append(self.makeChange(revision=67))
2986 r = base.BuildRequest("forced", SourceStamp(changes=c), 'test_builder')
2987 b = base.Build([r])
2988 s = source.SVN(svnurl="dummy")
2989 s.setBuild(b)
2990 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()), 67)
2992 class Patch(VCBase, unittest.TestCase):
2993 def setUp(self):
2994 pass
2996 def tearDown(self):
2997 pass
2999 def testPatch(self):
3000 # invoke 'patch' all by itself, to see if it works the way we think
3001 # it should. This is intended to ferret out some windows test
3002 # failures.
3003 helper = BaseHelper()
3004 self.workdir = os.path.join("test_vc", "testPatch")
3005 helper.populate(self.workdir)
3006 patch = which("patch")[0]
3008 command = [patch, "-p0"]
3009 class FakeBuilder:
3010 usePTY = False
3011 def sendUpdate(self, status):
3012 pass
3013 c = commands.ShellCommand(FakeBuilder(), command, self.workdir,
3014 sendRC=False, initialStdin=p0_diff)
3015 d = c.start()
3016 d.addCallback(self._testPatch_1)
3017 return d
3019 def _testPatch_1(self, res):
3020 # make sure the file actually got patched
3021 subdir_c = os.path.join(self.workdir, "subdir", "subdir.c")
3022 data = open(subdir_c, "r").read()
3023 self.failUnlessIn("Hello patched subdir.\\n", data)