Merge branch 'master' of git://github.com/dustin/buildbot
[buildbot.git] / buildbot / test / test_vc.py
blob8d3f847ce12d2574f3fbe240ecc5b536a8ac7092
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 _makedirsif(dir):
167 absdir = os.path.abspath(dir)
168 if not os.path.isdir(absdir):
169 os.makedirs(absdir)
171 def qw(s):
172 return s.split()
174 class VCS_Helper:
175 # this is a helper class which keeps track of whether each VC system is
176 # available, and whether the repository for each has been created. There
177 # is one instance of this class, at module level, shared between all test
178 # cases.
180 def __init__(self):
181 self._helpers = {}
182 self._isCapable = {}
183 self._excuses = {}
184 self._repoReady = {}
186 def registerVC(self, name, helper):
187 self._helpers[name] = helper
188 self._repoReady[name] = False
190 def skipIfNotCapable(self, name):
191 """Either return None, or raise SkipTest"""
192 d = self.capable(name)
193 def _maybeSkip(res):
194 if not res[0]:
195 raise unittest.SkipTest(res[1])
196 d.addCallback(_maybeSkip)
197 return d
199 def capable(self, name):
200 """Return a Deferred that fires with (True,None) if this host offers
201 the given VC tool, or (False,excuse) if it does not (and therefore
202 the tests should be skipped)."""
204 if self._isCapable.has_key(name):
205 if self._isCapable[name]:
206 return defer.succeed((True,None))
207 else:
208 return defer.succeed((False, self._excuses[name]))
209 d = defer.maybeDeferred(self._helpers[name].capable)
210 def _capable(res):
211 if res[0]:
212 self._isCapable[name] = True
213 else:
214 self._excuses[name] = res[1]
215 return res
216 d.addCallback(_capable)
217 return d
219 def getHelper(self, name):
220 return self._helpers[name]
222 def createRepository(self, name):
223 """Return a Deferred that fires when the repository is set up."""
224 if self._repoReady[name]:
225 return defer.succeed(True)
226 d = self._helpers[name].createRepository()
227 def _ready(res):
228 self._repoReady[name] = True
229 d.addCallback(_ready)
230 return d
232 VCS = VCS_Helper()
235 # the overall plan here:
237 # Each VC system is tested separately, all using the same source tree defined
238 # in the 'files' dictionary above. Each VC system gets its own TestCase
239 # subclass. The first test case that is run will create the repository during
240 # setUp(), making two branches: 'trunk' and 'branch'. The trunk gets a copy
241 # of all the files in 'files'. The variant of good.c is committed on the
242 # branch.
244 # then testCheckout is run, which does a number of checkout/clobber/update
245 # builds. These all use trunk r1. It then runs self.fix(), which modifies
246 # 'fixable.c', then performs another build and makes sure the tree has been
247 # updated.
249 # testBranch uses trunk-r1 and branch-r1, making sure that we clobber the
250 # tree properly when we switch between them
252 # testPatch does a trunk-r1 checkout and applies a patch.
254 # testTryGetPatch performs a trunk-r1 checkout, modifies some files, then
255 # verifies that tryclient.getSourceStamp figures out the base revision and
256 # what got changed.
259 # vc_create makes a repository at r1 with three files: main.c, version.c, and
260 # subdir/foo.c . It also creates a branch from r1 (called b1) in which main.c
261 # says "hello branch" instead of "hello world". self.trunk[] contains
262 # revision stamps for everything on the trunk, and self.branch[] does the
263 # same for the branch.
265 # vc_revise() checks out a tree at HEAD, changes version.c, then checks it
266 # back in. The new version stamp is appended to self.trunk[]. The tree is
267 # removed afterwards.
269 # vc_try_checkout(workdir, rev) checks out a tree at REV, then changes
270 # subdir/subdir.c to say 'Hello try'
271 # vc_try_finish(workdir) removes the tree and cleans up any VC state
272 # necessary (like deleting the Arch archive entry).
275 class BaseHelper:
276 def __init__(self):
277 self.trunk = []
278 self.branch = []
279 self.allrevs = []
281 def capable(self):
282 # this is also responsible for setting self.vcexe
283 raise NotImplementedError
285 def createBasedir(self):
286 # you must call this from createRepository
287 self.repbase = os.path.abspath(os.path.join("test_vc",
288 "repositories"))
289 _makedirsif(self.repbase)
291 def createRepository(self):
292 # this will only be called once per process
293 raise NotImplementedError
295 def populate(self, basedir):
296 _makedirsif(basedir)
297 _makedirsif(os.path.join(basedir, "subdir"))
299 open(os.path.join(basedir, "main.c"), "w").write(MAIN_C)
300 self.version = 1
301 version_c = VERSION_C % self.version
302 open(os.path.join(basedir, "version.c"), "w").write(version_c)
303 open(os.path.join(basedir, "main.c"), "w").write(MAIN_C)
304 open(os.path.join(basedir, "subdir", "subdir.c"), "w").write(SUBDIR_C)
306 def populate_branch(self, basedir):
307 open(os.path.join(basedir, "main.c"), "w").write(BRANCH_C)
309 def addTrunkRev(self, rev):
310 self.trunk.append(rev)
311 self.allrevs.append(rev)
312 def addBranchRev(self, rev):
313 self.branch.append(rev)
314 self.allrevs.append(rev)
316 def runCommand(self, basedir, command, failureIsOk=False,
317 stdin=None, env=None):
318 # all commands passed to do() should be strings or lists. If they are
319 # strings, none of the arguments may have spaces. This makes the
320 # commands less verbose at the expense of restricting what they can
321 # specify.
322 if type(command) not in (list, tuple):
323 command = command.split(" ")
325 # execute scripts through cmd.exe on windows, to avoid space in path issues
326 if sys.platform == 'win32' and command[0].lower().endswith('.cmd'):
327 command = [which('cmd.exe')[0], '/c', 'call'] + command
329 DEBUG = False
330 if DEBUG:
331 print "do %s" % command
332 print " in basedir %s" % basedir
333 if stdin:
334 print " STDIN:\n", stdin, "\n--STDIN DONE"
336 if not env:
337 env = os.environ.copy()
338 env['LC_ALL'] = "C"
339 d = myGetProcessOutputAndValue(command[0], command[1:],
340 env=env, path=basedir,
341 stdin=stdin)
342 def check((out, err, code)):
343 if DEBUG:
344 print
345 print "command was: %s" % command
346 if out: print "out: %s" % out
347 if err: print "err: %s" % err
348 print "code: %s" % code
349 if code != 0 and not failureIsOk:
350 log.msg("command %s finished with exit code %d" %
351 (command, code))
352 log.msg(" and stdout %s" % (out,))
353 log.msg(" and stderr %s" % (err,))
354 raise RuntimeError("command %s finished with exit code %d"
355 % (command, code)
356 + ": see logs for stdout")
357 return out
358 d.addCallback(check)
359 return d
361 def do(self, basedir, command, failureIsOk=False, stdin=None, env=None):
362 d = self.runCommand(basedir, command, failureIsOk=failureIsOk,
363 stdin=stdin, env=env)
364 return waitForDeferred(d)
366 def dovc(self, basedir, command, failureIsOk=False, stdin=None, env=None):
367 """Like do(), but the VC binary will be prepended to COMMAND."""
368 if isinstance(command, (str, unicode)):
369 command = [self.vcexe] + command.split(' ')
370 else:
371 # command is a list
372 command = [self.vcexe] + command
373 return self.do(basedir, command, failureIsOk, stdin, env)
375 class VCBase(SignalMixin):
376 metadir = None
377 createdRepository = False
378 master = None
379 slave = None
380 helper = None
381 httpServer = None
382 httpPort = None
383 skip = None
384 has_got_revision = False
385 has_got_revision_branches_are_merged = False # for SVN
387 def failUnlessIn(self, substring, string, msg=None):
388 # trial provides a version of this that requires python-2.3 to test
389 # strings.
390 if msg is None:
391 msg = ("did not see the expected substring '%s' in string '%s'" %
392 (substring, string))
393 self.failUnless(string.find(substring) != -1, msg)
395 def setUp(self):
396 self.setUpSignalHandler()
397 d = VCS.skipIfNotCapable(self.vc_name)
398 d.addCallback(self._setUp1)
399 return d
401 def _setUp1(self, res):
402 self.helper = VCS.getHelper(self.vc_name)
404 if os.path.exists("basedir"):
405 rmdirRecursive("basedir")
406 os.mkdir("basedir")
407 self.master = master.BuildMaster("basedir")
408 self.slavebase = os.path.abspath("slavebase")
409 if os.path.exists(self.slavebase):
410 rmdirRecursive(self.slavebase)
411 os.mkdir("slavebase")
413 d = VCS.createRepository(self.vc_name)
414 return d
416 def connectSlave(self):
417 port = self.master.slavePort._port.getHost().port
418 slave = bot.BuildSlave("localhost", port, "bot1", "sekrit",
419 self.slavebase, keepalive=0, usePTY=False)
420 self.slave = slave
421 slave.startService()
422 d = self.master.botmaster.waitUntilBuilderAttached("vc")
423 return d
425 def loadConfig(self, config):
426 # reloading the config file causes a new 'listDirs' command to be
427 # sent to the slave. To synchronize on this properly, it is easiest
428 # to stop and restart the slave.
429 d = defer.succeed(None)
430 if self.slave:
431 d = self.master.botmaster.waitUntilBuilderDetached("vc")
432 self.slave.stopService()
433 d.addCallback(lambda res: self.master.loadConfig(config))
434 d.addCallback(lambda res: self.connectSlave())
435 return d
437 def serveHTTP(self):
438 # launch an HTTP server to serve the repository files
439 self.root = static.File(self.helper.repbase)
440 self.site = server.Site(self.root)
441 self.httpServer = reactor.listenTCP(0, self.site)
442 self.httpPort = self.httpServer.getHost().port
444 def doBuild(self, shouldSucceed=True, ss=None):
445 c = interfaces.IControl(self.master)
447 if ss is None:
448 ss = SourceStamp()
449 #print "doBuild(ss: b=%s rev=%s)" % (ss.branch, ss.revision)
450 req = base.BuildRequest("test_vc forced build", ss, 'test_builder')
451 d = req.waitUntilFinished()
452 c.getBuilder("vc").requestBuild(req)
453 d.addCallback(self._doBuild_1, shouldSucceed)
454 return d
455 def _doBuild_1(self, bs, shouldSucceed):
456 r = bs.getResults()
457 if r != SUCCESS and shouldSucceed:
458 print
459 print
460 if not bs.isFinished():
461 print "Hey, build wasn't even finished!"
462 print "Build did not succeed:", r, bs.getText()
463 for s in bs.getSteps():
464 for l in s.getLogs():
465 print "--- START step %s / log %s ---" % (s.getName(),
466 l.getName())
467 print l.getTextWithHeaders()
468 print "--- STOP ---"
469 print
470 self.fail("build did not succeed")
471 return bs
473 def printLogs(self, bs):
474 for s in bs.getSteps():
475 for l in s.getLogs():
476 print "--- START step %s / log %s ---" % (s.getName(),
477 l.getName())
478 print l.getTextWithHeaders()
479 print "--- STOP ---"
480 print
482 def touch(self, d, f):
483 open(os.path.join(d,f),"w").close()
484 def shouldExist(self, *args):
485 target = os.path.join(*args)
486 self.failUnless(os.path.exists(target),
487 "expected to find %s but didn't" % target)
488 def shouldNotExist(self, *args):
489 target = os.path.join(*args)
490 self.failIf(os.path.exists(target),
491 "expected to NOT find %s, but did" % target)
492 def shouldContain(self, d, f, contents):
493 c = open(os.path.join(d, f), "r").read()
494 self.failUnlessIn(contents, c)
496 def checkGotRevision(self, bs, expected):
497 if self.has_got_revision:
498 self.failUnlessEqual(bs.getProperty("got_revision"), str(expected))
500 def checkGotRevisionIsLatest(self, bs):
501 expected = self.helper.trunk[-1]
502 if self.has_got_revision_branches_are_merged:
503 expected = self.helper.allrevs[-1]
504 self.checkGotRevision(bs, expected)
506 def do_vctest(self, testRetry=True):
507 vctype = self.vctype
508 args = self.helper.vcargs
509 m = self.master
510 self.vcdir = os.path.join(self.slavebase, "vc-dir", "source")
511 self.workdir = os.path.join(self.slavebase, "vc-dir", "build")
512 # woo double-substitution
513 s = "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype,)
514 for k,v in args.items():
515 s += ", %s=%s" % (k, repr(v))
516 s += ")"
517 config = config_vc % s
519 m.loadConfig(config % 'clobber')
520 m.readConfig = True
521 m.startService()
523 d = self.connectSlave()
524 d.addCallback(lambda res: log.msg("testing clobber"))
525 d.addCallback(self._do_vctest_clobber)
526 d.addCallback(lambda res: log.msg("doing update"))
527 d.addCallback(lambda res: self.loadConfig(config % 'update'))
528 d.addCallback(lambda res: log.msg("testing update"))
529 d.addCallback(self._do_vctest_update)
530 if testRetry:
531 d.addCallback(lambda res: log.msg("testing update retry"))
532 d.addCallback(self._do_vctest_update_retry)
533 d.addCallback(lambda res: log.msg("doing copy"))
534 d.addCallback(lambda res: self.loadConfig(config % 'copy'))
535 d.addCallback(lambda res: log.msg("testing copy"))
536 d.addCallback(self._do_vctest_copy)
537 d.addCallback(lambda res: log.msg("did copy test"))
538 if self.metadir:
539 d.addCallback(lambda res: log.msg("doing export"))
540 d.addCallback(lambda res: self.loadConfig(config % 'export'))
541 d.addCallback(lambda res: log.msg("testing export"))
542 d.addCallback(self._do_vctest_export)
543 d.addCallback(lambda res: log.msg("did export test"))
544 return d
546 def _do_vctest_clobber(self, res):
547 d = self.doBuild() # initial checkout
548 d.addCallback(self._do_vctest_clobber_1)
549 return d
550 def _do_vctest_clobber_1(self, bs):
551 self.shouldExist(self.workdir, "main.c")
552 self.shouldExist(self.workdir, "version.c")
553 self.shouldExist(self.workdir, "subdir", "subdir.c")
554 if self.metadir:
555 self.shouldExist(self.workdir, self.metadir)
556 self.failUnlessEqual(bs.getProperty("revision"), None)
557 self.failUnlessEqual(bs.getProperty("branch"), None)
558 self.checkGotRevisionIsLatest(bs)
560 self.touch(self.workdir, "newfile")
561 self.shouldExist(self.workdir, "newfile")
562 d = self.doBuild() # rebuild clobbers workdir
563 d.addCallback(self._do_vctest_clobber_2)
564 return d
565 def _do_vctest_clobber_2(self, res):
566 self.shouldNotExist(self.workdir, "newfile")
567 # do a checkout to a specific version. Mercurial-over-HTTP (when
568 # either client or server is older than hg-0.9.2) cannot do this
569 # directly, so it must checkout HEAD and then update back to the
570 # requested revision.
571 d = self.doBuild(ss=SourceStamp(revision=self.helper.trunk[0]))
572 d.addCallback(self._do_vctest_clobber_3)
573 return d
574 def _do_vctest_clobber_3(self, bs):
575 self.shouldExist(self.workdir, "main.c")
576 self.shouldExist(self.workdir, "version.c")
577 self.shouldExist(self.workdir, "subdir", "subdir.c")
578 if self.metadir:
579 self.shouldExist(self.workdir, self.metadir)
580 self.failUnlessEqual(bs.getProperty("revision"), self.helper.trunk[0] or None)
581 self.failUnlessEqual(bs.getProperty("branch"), None)
582 self.checkGotRevision(bs, self.helper.trunk[0])
583 # leave the tree at HEAD
584 return self.doBuild()
587 def _do_vctest_update(self, res):
588 log.msg("_do_vctest_update")
589 d = self.doBuild() # rebuild with update
590 d.addCallback(self._do_vctest_update_1)
591 return d
592 def _do_vctest_update_1(self, bs):
593 log.msg("_do_vctest_update_1")
594 self.shouldExist(self.workdir, "main.c")
595 self.shouldExist(self.workdir, "version.c")
596 self.shouldContain(self.workdir, "version.c",
597 "version=%d" % self.helper.version)
598 if self.metadir:
599 self.shouldExist(self.workdir, self.metadir)
600 self.failUnlessEqual(bs.getProperty("revision"), None)
601 self.checkGotRevisionIsLatest(bs)
603 self.touch(self.workdir, "newfile")
604 d = self.doBuild() # update rebuild leaves new files
605 d.addCallback(self._do_vctest_update_2)
606 return d
607 def _do_vctest_update_2(self, bs):
608 log.msg("_do_vctest_update_2")
609 self.shouldExist(self.workdir, "main.c")
610 self.shouldExist(self.workdir, "version.c")
611 self.touch(self.workdir, "newfile")
612 # now make a change to the repository and make sure we pick it up
613 d = self.helper.vc_revise()
614 d.addCallback(lambda res: self.doBuild())
615 d.addCallback(self._do_vctest_update_3)
616 return d
617 def _do_vctest_update_3(self, bs):
618 log.msg("_do_vctest_update_3")
619 self.shouldExist(self.workdir, "main.c")
620 self.shouldExist(self.workdir, "version.c")
621 self.shouldContain(self.workdir, "version.c",
622 "version=%d" % self.helper.version)
623 self.shouldExist(self.workdir, "newfile")
624 self.failUnlessEqual(bs.getProperty("revision"), None)
625 self.checkGotRevisionIsLatest(bs)
627 # now "update" to an older revision
628 d = self.doBuild(ss=SourceStamp(revision=self.helper.trunk[-2]))
629 d.addCallback(self._do_vctest_update_4)
630 return d
631 def _do_vctest_update_4(self, bs):
632 log.msg("_do_vctest_update_4")
633 self.shouldExist(self.workdir, "main.c")
634 self.shouldExist(self.workdir, "version.c")
635 self.shouldContain(self.workdir, "version.c",
636 "version=%d" % (self.helper.version-1))
637 self.failUnlessEqual(bs.getProperty("revision"),
638 self.helper.trunk[-2] or None)
639 self.checkGotRevision(bs, self.helper.trunk[-2])
641 # now update to the newer revision
642 d = self.doBuild(ss=SourceStamp(revision=self.helper.trunk[-1]))
643 d.addCallback(self._do_vctest_update_5)
644 return d
645 def _do_vctest_update_5(self, bs):
646 log.msg("_do_vctest_update_5")
647 self.shouldExist(self.workdir, "main.c")
648 self.shouldExist(self.workdir, "version.c")
649 self.shouldContain(self.workdir, "version.c",
650 "version=%d" % self.helper.version)
651 self.failUnlessEqual(bs.getProperty("revision"),
652 self.helper.trunk[-1] or None)
653 self.checkGotRevision(bs, self.helper.trunk[-1])
656 def _do_vctest_update_retry(self, res):
657 # certain local changes will prevent an update from working. The
658 # most common is to replace a file with a directory, or vice
659 # versa. The slave code should spot the failure and do a
660 # clobber/retry.
661 os.unlink(os.path.join(self.workdir, "main.c"))
662 os.mkdir(os.path.join(self.workdir, "main.c"))
663 self.touch(os.path.join(self.workdir, "main.c"), "foo")
664 self.touch(self.workdir, "newfile")
666 d = self.doBuild() # update, but must clobber to handle the error
667 d.addCallback(self._do_vctest_update_retry_1)
668 return d
669 def _do_vctest_update_retry_1(self, bs):
670 # SVN-1.4.0 doesn't seem to have any problem with the
671 # file-turned-directory issue (although older versions did). So don't
672 # actually check that the tree was clobbered.. as long as the update
673 # succeeded (checked by doBuild), that should be good enough.
674 #self.shouldNotExist(self.workdir, "newfile")
675 pass
677 def _do_vctest_copy(self, res):
678 log.msg("_do_vctest_copy 1")
679 d = self.doBuild() # copy rebuild clobbers new files
680 d.addCallback(self._do_vctest_copy_1)
681 return d
682 def _do_vctest_copy_1(self, bs):
683 log.msg("_do_vctest_copy 2")
684 if self.metadir:
685 self.shouldExist(self.workdir, self.metadir)
686 self.shouldNotExist(self.workdir, "newfile")
687 self.touch(self.workdir, "newfile")
688 self.touch(self.vcdir, "newvcfile")
689 self.failUnlessEqual(bs.getProperty("revision"), None)
690 self.checkGotRevisionIsLatest(bs)
692 d = self.doBuild() # copy rebuild clobbers new files
693 d.addCallback(self._do_vctest_copy_2)
694 return d
695 def _do_vctest_copy_2(self, bs):
696 log.msg("_do_vctest_copy 3")
697 if self.metadir:
698 self.shouldExist(self.workdir, self.metadir)
699 self.shouldNotExist(self.workdir, "newfile")
700 self.shouldExist(self.vcdir, "newvcfile")
701 self.shouldExist(self.workdir, "newvcfile")
702 self.failUnlessEqual(bs.getProperty("revision"), None)
703 self.checkGotRevisionIsLatest(bs)
704 self.touch(self.workdir, "newfile")
706 def _do_vctest_export(self, res):
707 d = self.doBuild() # export rebuild clobbers new files
708 d.addCallback(self._do_vctest_export_1)
709 return d
710 def _do_vctest_export_1(self, bs):
711 self.shouldNotExist(self.workdir, self.metadir)
712 self.shouldNotExist(self.workdir, "newfile")
713 self.failUnlessEqual(bs.getProperty("revision"), None)
714 #self.checkGotRevisionIsLatest(bs)
715 # VC 'export' is not required to have a got_revision
716 self.touch(self.workdir, "newfile")
718 d = self.doBuild() # export rebuild clobbers new files
719 d.addCallback(self._do_vctest_export_2)
720 return d
721 def _do_vctest_export_2(self, bs):
722 self.shouldNotExist(self.workdir, self.metadir)
723 self.shouldNotExist(self.workdir, "newfile")
724 self.failUnlessEqual(bs.getProperty("revision"), None)
725 #self.checkGotRevisionIsLatest(bs)
726 # VC 'export' is not required to have a got_revision
728 def do_patch(self):
729 vctype = self.vctype
730 args = self.helper.vcargs
731 m = self.master
732 self.vcdir = os.path.join(self.slavebase, "vc-dir", "source")
733 self.workdir = os.path.join(self.slavebase, "vc-dir", "build")
734 s = "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype,)
735 for k,v in args.items():
736 s += ", %s=%s" % (k, repr(v))
737 s += ")"
738 self.config = config_vc % s
740 m.loadConfig(self.config % "clobber")
741 m.readConfig = True
742 m.startService()
744 ss = SourceStamp(revision=self.helper.trunk[-1], patch=(0, p0_diff))
746 d = self.connectSlave()
747 d.addCallback(lambda res: self.doBuild(ss=ss))
748 d.addCallback(self._doPatch_1)
749 return d
750 def _doPatch_1(self, bs):
751 self.shouldContain(self.workdir, "version.c",
752 "version=%d" % self.helper.version)
753 # make sure the file actually got patched
754 subdir_c = os.path.join(self.slavebase, "vc-dir", "build",
755 "subdir", "subdir.c")
756 data = open(subdir_c, "r").read()
757 self.failUnlessIn("Hello patched subdir.\\n", data)
758 self.failUnlessEqual(bs.getProperty("revision"),
759 self.helper.trunk[-1] or None)
760 self.checkGotRevision(bs, self.helper.trunk[-1])
762 # make sure that a rebuild does not use the leftover patched workdir
763 d = self.master.loadConfig(self.config % "update")
764 d.addCallback(lambda res: self.doBuild(ss=None))
765 d.addCallback(self._doPatch_2)
766 return d
767 def _doPatch_2(self, bs):
768 # make sure the file is back to its original
769 subdir_c = os.path.join(self.slavebase, "vc-dir", "build",
770 "subdir", "subdir.c")
771 data = open(subdir_c, "r").read()
772 self.failUnlessIn("Hello subdir.\\n", data)
773 self.failUnlessEqual(bs.getProperty("revision"), None)
774 self.checkGotRevisionIsLatest(bs)
776 # now make sure we can patch an older revision. We need at least two
777 # revisions here, so we might have to create one first
778 if len(self.helper.trunk) < 2:
779 d = self.helper.vc_revise()
780 d.addCallback(self._doPatch_3)
781 return d
782 return self._doPatch_3()
784 def _doPatch_3(self, res=None):
785 ss = SourceStamp(revision=self.helper.trunk[-2], patch=(0, p0_diff))
786 d = self.doBuild(ss=ss)
787 d.addCallback(self._doPatch_4)
788 return d
789 def _doPatch_4(self, bs):
790 self.shouldContain(self.workdir, "version.c",
791 "version=%d" % (self.helper.version-1))
792 # and make sure the file actually got patched
793 subdir_c = os.path.join(self.slavebase, "vc-dir", "build",
794 "subdir", "subdir.c")
795 data = open(subdir_c, "r").read()
796 self.failUnlessIn("Hello patched subdir.\\n", data)
797 self.failUnlessEqual(bs.getProperty("revision"),
798 self.helper.trunk[-2] or None)
799 self.checkGotRevision(bs, self.helper.trunk[-2])
801 # now check that we can patch a branch
802 ss = SourceStamp(branch=self.helper.branchname,
803 revision=self.helper.branch[-1],
804 patch=(0, p0_diff))
805 d = self.doBuild(ss=ss)
806 d.addCallback(self._doPatch_5)
807 return d
808 def _doPatch_5(self, bs):
809 self.shouldContain(self.workdir, "version.c",
810 "version=%d" % 1)
811 self.shouldContain(self.workdir, "main.c", "Hello branch.")
812 subdir_c = os.path.join(self.slavebase, "vc-dir", "build",
813 "subdir", "subdir.c")
814 data = open(subdir_c, "r").read()
815 self.failUnlessIn("Hello patched subdir.\\n", data)
816 self.failUnlessEqual(bs.getProperty("revision"),
817 self.helper.branch[-1] or None)
818 self.failUnlessEqual(bs.getProperty("branch"), self.helper.branchname or None)
819 self.checkGotRevision(bs, self.helper.branch[-1])
822 def do_vctest_once(self, shouldSucceed):
823 m = self.master
824 vctype = self.vctype
825 args = self.helper.vcargs
826 vcdir = os.path.join(self.slavebase, "vc-dir", "source")
827 workdir = os.path.join(self.slavebase, "vc-dir", "build")
828 # woo double-substitution
829 s = "s(%s, timeout=200, workdir='build', mode='clobber'" % (vctype,)
830 for k,v in args.items():
831 s += ", %s=%s" % (k, repr(v))
832 s += ")"
833 config = config_vc % s
835 m.loadConfig(config)
836 m.readConfig = True
837 m.startService()
839 self.connectSlave()
840 d = self.doBuild(shouldSucceed) # initial checkout
841 return d
843 def do_branch(self):
844 log.msg("do_branch")
845 vctype = self.vctype
846 args = self.helper.vcargs
847 m = self.master
848 self.vcdir = os.path.join(self.slavebase, "vc-dir", "source")
849 self.workdir = os.path.join(self.slavebase, "vc-dir", "build")
850 s = "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype,)
851 for k,v in args.items():
852 s += ", %s=%s" % (k, repr(v))
853 s += ")"
854 self.config = config_vc % s
856 m.loadConfig(self.config % "update")
857 m.readConfig = True
858 m.startService()
860 # first we do a build of the trunk
861 d = self.connectSlave()
862 d.addCallback(lambda res: self.doBuild(ss=SourceStamp()))
863 d.addCallback(self._doBranch_1)
864 return d
865 def _doBranch_1(self, bs):
866 log.msg("_doBranch_1")
867 # make sure the checkout was of the trunk
868 main_c = os.path.join(self.slavebase, "vc-dir", "build", "main.c")
869 data = open(main_c, "r").read()
870 self.failUnlessIn("Hello world.", data)
872 # now do a checkout on the branch. The change in branch name should
873 # trigger a clobber.
874 self.touch(self.workdir, "newfile")
875 d = self.doBuild(ss=SourceStamp(branch=self.helper.branchname))
876 d.addCallback(self._doBranch_2)
877 return d
878 def _doBranch_2(self, bs):
879 log.msg("_doBranch_2")
880 # make sure it was on the branch
881 main_c = os.path.join(self.slavebase, "vc-dir", "build", "main.c")
882 data = open(main_c, "r").read()
883 self.failUnlessIn("Hello branch.", data)
884 # and make sure the tree was clobbered
885 self.shouldNotExist(self.workdir, "newfile")
887 # doing another build on the same branch should not clobber the tree
888 self.touch(self.workdir, "newbranchfile")
889 d = self.doBuild(ss=SourceStamp(branch=self.helper.branchname))
890 d.addCallback(self._doBranch_3)
891 return d
892 def _doBranch_3(self, bs):
893 log.msg("_doBranch_3")
894 # make sure it is still on the branch
895 main_c = os.path.join(self.slavebase, "vc-dir", "build", "main.c")
896 data = open(main_c, "r").read()
897 self.failUnlessIn("Hello branch.", data)
898 # and make sure the tree was not clobbered
899 self.shouldExist(self.workdir, "newbranchfile")
901 # now make sure that a non-branch checkout clobbers the tree
902 d = self.doBuild(ss=SourceStamp())
903 d.addCallback(self._doBranch_4)
904 return d
905 def _doBranch_4(self, bs):
906 log.msg("_doBranch_4")
907 # make sure it was on the trunk
908 main_c = os.path.join(self.slavebase, "vc-dir", "build", "main.c")
909 data = open(main_c, "r").read()
910 self.failUnlessIn("Hello world.", data)
911 self.shouldNotExist(self.workdir, "newbranchfile")
913 def do_getpatch(self, doBranch=True):
914 log.msg("do_getpatch")
915 # prepare a buildslave to do checkouts
916 vctype = self.vctype
917 args = self.helper.vcargs
918 m = self.master
919 self.vcdir = os.path.join(self.slavebase, "vc-dir", "source")
920 self.workdir = os.path.join(self.slavebase, "vc-dir", "build")
921 # woo double-substitution
922 s = "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype,)
923 for k,v in args.items():
924 s += ", %s=%s" % (k, repr(v))
925 s += ")"
926 config = config_vc % s
928 m.loadConfig(config % 'clobber')
929 m.readConfig = True
930 m.startService()
932 d = self.connectSlave()
934 # then set up the "developer's tree". first we modify a tree from the
935 # head of the trunk
936 tmpdir = "try_workdir"
937 self.trydir = os.path.join(self.helper.repbase, tmpdir)
938 rmdirRecursive(self.trydir)
939 d.addCallback(self.do_getpatch_trunkhead)
940 d.addCallback(self.do_getpatch_trunkold)
941 if doBranch:
942 d.addCallback(self.do_getpatch_branch)
943 d.addCallback(self.do_getpatch_finish)
944 return d
946 def do_getpatch_finish(self, res):
947 log.msg("do_getpatch_finish")
948 self.helper.vc_try_finish(self.trydir)
949 return res
951 def try_shouldMatch(self, filename):
952 devfilename = os.path.join(self.trydir, filename)
953 devfile = open(devfilename, "r").read()
954 slavefilename = os.path.join(self.workdir, filename)
955 slavefile = open(slavefilename, "r").read()
956 self.failUnlessEqual(devfile, slavefile,
957 ("slavefile (%s) contains '%s'. "
958 "developer's file (%s) contains '%s'. "
959 "These ought to match") %
960 (slavefilename, slavefile,
961 devfilename, devfile))
963 def do_getpatch_trunkhead(self, res):
964 log.msg("do_getpatch_trunkhead")
965 d = self.helper.vc_try_checkout(self.trydir, self.helper.trunk[-1])
966 d.addCallback(self._do_getpatch_trunkhead_1)
967 return d
968 def _do_getpatch_trunkhead_1(self, res):
969 log.msg("_do_getpatch_trunkhead_1")
970 d = tryclient.getSourceStamp(self.vctype_try, self.trydir, None)
971 d.addCallback(self._do_getpatch_trunkhead_2)
972 return d
973 def _do_getpatch_trunkhead_2(self, ss):
974 log.msg("_do_getpatch_trunkhead_2")
975 d = self.doBuild(ss=ss)
976 d.addCallback(self._do_getpatch_trunkhead_3)
977 return d
978 def _do_getpatch_trunkhead_3(self, res):
979 log.msg("_do_getpatch_trunkhead_3")
980 # verify that the resulting buildslave tree matches the developer's
981 self.try_shouldMatch("main.c")
982 self.try_shouldMatch("version.c")
983 self.try_shouldMatch(os.path.join("subdir", "subdir.c"))
985 def do_getpatch_trunkold(self, res):
986 log.msg("do_getpatch_trunkold")
987 # now try a tree from an older revision. We need at least two
988 # revisions here, so we might have to create one first
989 if len(self.helper.trunk) < 2:
990 d = self.helper.vc_revise()
991 d.addCallback(self._do_getpatch_trunkold_1)
992 return d
993 return self._do_getpatch_trunkold_1()
994 def _do_getpatch_trunkold_1(self, res=None):
995 log.msg("_do_getpatch_trunkold_1")
996 d = self.helper.vc_try_checkout(self.trydir, self.helper.trunk[-2])
997 d.addCallback(self._do_getpatch_trunkold_2)
998 return d
999 def _do_getpatch_trunkold_2(self, res):
1000 log.msg("_do_getpatch_trunkold_2")
1001 d = tryclient.getSourceStamp(self.vctype_try, self.trydir, None)
1002 d.addCallback(self._do_getpatch_trunkold_3)
1003 return d
1004 def _do_getpatch_trunkold_3(self, ss):
1005 log.msg("_do_getpatch_trunkold_3")
1006 d = self.doBuild(ss=ss)
1007 d.addCallback(self._do_getpatch_trunkold_4)
1008 return d
1009 def _do_getpatch_trunkold_4(self, res):
1010 log.msg("_do_getpatch_trunkold_4")
1011 # verify that the resulting buildslave tree matches the developer's
1012 self.try_shouldMatch("main.c")
1013 self.try_shouldMatch("version.c")
1014 self.try_shouldMatch(os.path.join("subdir", "subdir.c"))
1016 def do_getpatch_branch(self, res):
1017 log.msg("do_getpatch_branch")
1018 # now try a tree from a branch
1019 d = self.helper.vc_try_checkout(self.trydir, self.helper.branch[-1],
1020 self.helper.branchname)
1021 d.addCallback(self._do_getpatch_branch_1)
1022 return d
1023 def _do_getpatch_branch_1(self, res):
1024 log.msg("_do_getpatch_branch_1")
1025 d = tryclient.getSourceStamp(self.vctype_try, self.trydir,
1026 self.helper.try_branchname)
1027 d.addCallback(self._do_getpatch_branch_2)
1028 return d
1029 def _do_getpatch_branch_2(self, ss):
1030 log.msg("_do_getpatch_branch_2")
1031 d = self.doBuild(ss=ss)
1032 d.addCallback(self._do_getpatch_branch_3)
1033 return d
1034 def _do_getpatch_branch_3(self, res):
1035 log.msg("_do_getpatch_branch_3")
1036 # verify that the resulting buildslave tree matches the developer's
1037 self.try_shouldMatch("main.c")
1038 self.try_shouldMatch("version.c")
1039 self.try_shouldMatch(os.path.join("subdir", "subdir.c"))
1042 def dumpPatch(self, patch):
1043 # this exists to help me figure out the right 'patchlevel' value
1044 # should be returned by tryclient.getSourceStamp
1045 n = self.mktemp()
1046 open(n,"w").write(patch)
1047 d = self.runCommand(".", ["lsdiff", n])
1048 def p(res): print "lsdiff:", res.strip().split("\n")
1049 d.addCallback(p)
1050 return d
1053 def tearDown(self):
1054 self.tearDownSignalHandler()
1055 d = defer.succeed(None)
1056 if self.slave:
1057 d2 = self.master.botmaster.waitUntilBuilderDetached("vc")
1058 d.addCallback(lambda res: self.slave.stopService())
1059 d.addCallback(lambda res: d2)
1060 if self.master:
1061 d.addCallback(lambda res: self.master.stopService())
1062 if self.httpServer:
1063 d.addCallback(lambda res: self.httpServer.stopListening())
1064 def stopHTTPTimer():
1065 from twisted.web import http
1066 http._logDateTimeStop() # shut down the internal timer. DUMB!
1067 d.addCallback(lambda res: stopHTTPTimer())
1068 d.addCallback(lambda res: self.tearDown2())
1069 return d
1071 def tearDown2(self):
1072 pass
1074 class CVSHelper(BaseHelper):
1075 branchname = "branch"
1076 try_branchname = "branch"
1078 def capable(self):
1079 cvspaths = which('cvs')
1080 if not cvspaths:
1081 return (False, "CVS is not installed")
1082 # cvs-1.10 (as shipped with OS-X 10.3 "Panther") is too old for this
1083 # test. There is a situation where we check out a tree, make a
1084 # change, then commit it back, and CVS refuses to believe that we're
1085 # operating in a CVS tree. I tested cvs-1.12.9 and it works ok, OS-X
1086 # 10.4 "Tiger" comes with cvs-1.11, but I haven't tested that yet.
1087 # For now, skip the tests if we've got 1.10 .
1088 log.msg("running %s --version.." % (cvspaths[0],))
1089 d = utils.getProcessOutput(cvspaths[0], ["--version"],
1090 env=os.environ)
1091 d.addCallback(self._capable, cvspaths[0])
1092 return d
1094 def _capable(self, v, vcexe):
1095 # Consider also CVS for NT which announces itself like
1096 # Concurrent Versions System (CVSNT) 2.0.51d (client/server)
1097 m = re.search(r'\(CVS[NT]*\) ([\d.]+)', v)
1098 if not m:
1099 log.msg("couldn't identify CVS version number in output:")
1100 log.msg("'''%s'''" % v)
1101 log.msg("skipping tests")
1102 return (False, "Found CVS but couldn't identify its version")
1103 ver = m.group(1)
1104 log.msg("found CVS version '%s'" % ver)
1105 if ver == "1.10":
1106 return (False, "Found CVS, but it is too old")
1107 self.vcexe = vcexe
1108 return (True, None)
1110 def getdate(self):
1111 # this timestamp is eventually passed to CVS in a -D argument, and
1112 # strftime's %z specifier doesn't seem to work reliably (I get +0000
1113 # where I should get +0700 under linux sometimes, and windows seems
1114 # to want to put a verbose 'Eastern Standard Time' in there), so
1115 # leave off the timezone specifier and treat this as localtime. A
1116 # valid alternative would be to use a hard-coded +0000 and
1117 # time.gmtime().
1118 return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
1120 def createRepository(self):
1121 self.createBasedir()
1122 self.cvsrep = cvsrep = os.path.join(self.repbase, "CVS-Repository")
1123 tmp = os.path.join(self.repbase, "cvstmp")
1125 w = self.dovc(self.repbase, ['-d', cvsrep, 'init'])
1126 yield w; w.getResult() # we must getResult() to raise any exceptions
1128 self.populate(tmp)
1129 cmd = ['-d', self.cvsrep, 'import',
1130 '-m', 'sample_project_files', 'sample', 'vendortag', 'start']
1131 w = self.dovc(tmp, cmd)
1132 yield w; w.getResult()
1133 rmdirRecursive(tmp)
1134 # take a timestamp as the first revision number
1135 time.sleep(2)
1136 self.addTrunkRev(self.getdate())
1137 time.sleep(2)
1139 w = self.dovc(self.repbase,
1140 ['-d', self.cvsrep, 'checkout', '-d', 'cvstmp', 'sample'])
1141 yield w; w.getResult()
1143 w = self.dovc(tmp, ['tag', '-b', self.branchname])
1144 yield w; w.getResult()
1145 self.populate_branch(tmp)
1146 w = self.dovc(tmp,
1147 ['commit', '-m', 'commit_on_branch', '-r', self.branchname])
1148 yield w; w.getResult()
1149 rmdirRecursive(tmp)
1150 time.sleep(2)
1151 self.addBranchRev(self.getdate())
1152 time.sleep(2)
1153 self.vcargs = { 'cvsroot': self.cvsrep, 'cvsmodule': "sample" }
1154 createRepository = deferredGenerator(createRepository)
1157 def vc_revise(self):
1158 tmp = os.path.join(self.repbase, "cvstmp")
1160 w = self.dovc(self.repbase,
1161 ['-d', self.cvsrep, 'checkout', '-d', 'cvstmp', 'sample'])
1162 yield w; w.getResult()
1163 self.version += 1
1164 version_c = VERSION_C % self.version
1165 open(os.path.join(tmp, "version.c"), "w").write(version_c)
1166 w = self.dovc(tmp,
1167 ['commit', '-m', 'revised_to_%d' % self.version, 'version.c'])
1168 yield w; w.getResult()
1169 rmdirRecursive(tmp)
1170 time.sleep(2)
1171 self.addTrunkRev(self.getdate())
1172 time.sleep(2)
1173 vc_revise = deferredGenerator(vc_revise)
1175 def vc_try_checkout(self, workdir, rev, branch=None):
1176 # 'workdir' is an absolute path
1178 # get rid of timezone info, which might not be parsed
1179 rev = re.sub("[^0-9 :-]","",rev)
1180 rev = re.sub(" ","",rev)
1181 assert os.path.abspath(workdir) == workdir
1182 cmd = ["-d", self.cvsrep, "checkout",
1183 "-d", workdir,
1184 "-D", rev]
1185 if branch is not None:
1186 cmd.append("-r")
1187 cmd.append(branch)
1188 cmd.append("sample")
1189 w = self.dovc(self.repbase, cmd)
1190 yield w; w.getResult()
1191 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
1192 vc_try_checkout = deferredGenerator(vc_try_checkout)
1194 def vc_try_finish(self, workdir):
1195 rmdirRecursive(workdir)
1197 class CVS(VCBase, unittest.TestCase):
1198 vc_name = "cvs"
1200 metadir = "CVS"
1201 vctype = "source.CVS"
1202 vctype_try = "cvs"
1203 # CVS gives us got_revision, but it is based entirely upon the local
1204 # clock, which means it is unlikely to match the timestamp taken earlier.
1205 # This might be enough for common use, but won't be good enough for our
1206 # tests to accept, so pretend it doesn't have got_revision at all.
1207 has_got_revision = False
1209 def testCheckout(self):
1210 d = self.do_vctest()
1211 return d
1213 def testPatch(self):
1214 d = self.do_patch()
1215 return d
1217 def testCheckoutBranch(self):
1218 d = self.do_branch()
1219 return d
1221 def testTry(self):
1222 d = self.do_getpatch(doBranch=False)
1223 return d
1225 VCS.registerVC(CVS.vc_name, CVSHelper())
1228 class SVNHelper(BaseHelper):
1229 branchname = "sample/branch"
1230 try_branchname = "sample/branch"
1232 def capable(self):
1233 svnpaths = which('svn')
1234 svnadminpaths = which('svnadmin')
1235 if not svnpaths:
1236 return (False, "SVN is not installed")
1237 if not svnadminpaths:
1238 return (False, "svnadmin is not installed")
1239 # we need svn to be compiled with the ra_local access
1240 # module
1241 log.msg("running svn --version..")
1242 env = os.environ.copy()
1243 env['LC_ALL'] = "C"
1244 d = utils.getProcessOutput(svnpaths[0], ["--version"],
1245 env=env)
1246 d.addCallback(self._capable, svnpaths[0], svnadminpaths[0])
1247 return d
1249 def _capable(self, v, vcexe, svnadmin):
1250 if v.find("handles 'file' schem") != -1:
1251 # older versions say 'schema', 1.2.0 and beyond say 'scheme'
1252 self.vcexe = vcexe
1253 self.svnadmin = svnadmin
1254 return (True, None)
1255 excuse = ("%s found but it does not support 'file:' " +
1256 "schema, skipping svn tests") % vcexe
1257 log.msg(excuse)
1258 return (False, excuse)
1260 def createRepository(self):
1261 self.createBasedir()
1262 self.svnrep = os.path.join(self.repbase,
1263 "SVN-Repository").replace('\\','/')
1264 tmp = os.path.join(self.repbase, "svntmp")
1265 if sys.platform == 'win32':
1266 # On Windows Paths do not start with a /
1267 self.svnurl = "file:///%s" % self.svnrep
1268 else:
1269 self.svnurl = "file://%s" % self.svnrep
1270 self.svnurl_trunk = self.svnurl + "/sample/trunk"
1271 self.svnurl_branch = self.svnurl + "/sample/branch"
1273 w = self.do(self.repbase, [self.svnadmin, "create", self.svnrep])
1274 yield w; w.getResult()
1276 self.populate(tmp)
1277 w = self.dovc(tmp,
1278 ['import', '-m', 'sample_project_files', self.svnurl_trunk])
1279 yield w; out = w.getResult()
1280 rmdirRecursive(tmp)
1281 m = re.search(r'Committed revision (\d+)\.', out)
1282 assert m.group(1) == "1" # first revision is always "1"
1283 self.addTrunkRev(int(m.group(1)))
1285 w = self.dovc(self.repbase,
1286 ['checkout', self.svnurl_trunk, 'svntmp'])
1287 yield w; w.getResult()
1289 w = self.dovc(tmp, ['cp', '-m' , 'make_branch', self.svnurl_trunk,
1290 self.svnurl_branch])
1291 yield w; w.getResult()
1292 w = self.dovc(tmp, ['switch', self.svnurl_branch])
1293 yield w; w.getResult()
1294 self.populate_branch(tmp)
1295 w = self.dovc(tmp, ['commit', '-m', 'commit_on_branch'])
1296 yield w; out = w.getResult()
1297 rmdirRecursive(tmp)
1298 m = re.search(r'Committed revision (\d+)\.', out)
1299 self.addBranchRev(int(m.group(1)))
1300 createRepository = deferredGenerator(createRepository)
1302 def vc_revise(self):
1303 tmp = os.path.join(self.repbase, "svntmp")
1304 rmdirRecursive(tmp)
1305 log.msg("vc_revise" + self.svnurl_trunk)
1306 w = self.dovc(self.repbase,
1307 ['checkout', self.svnurl_trunk, 'svntmp'])
1308 yield w; w.getResult()
1309 self.version += 1
1310 version_c = VERSION_C % self.version
1311 open(os.path.join(tmp, "version.c"), "w").write(version_c)
1312 w = self.dovc(tmp, ['commit', '-m', 'revised_to_%d' % self.version])
1313 yield w; out = w.getResult()
1314 m = re.search(r'Committed revision (\d+)\.', out)
1315 self.addTrunkRev(int(m.group(1)))
1316 rmdirRecursive(tmp)
1317 vc_revise = deferredGenerator(vc_revise)
1319 def vc_try_checkout(self, workdir, rev, branch=None):
1320 assert os.path.abspath(workdir) == workdir
1321 if os.path.exists(workdir):
1322 rmdirRecursive(workdir)
1323 if not branch:
1324 svnurl = self.svnurl_trunk
1325 else:
1326 # N.B.: this is *not* os.path.join: SVN URLs use slashes
1327 # regardless of the host operating system's filepath separator
1328 svnurl = self.svnurl + "/" + branch
1329 w = self.dovc(self.repbase,
1330 ['checkout', svnurl, workdir])
1331 yield w; w.getResult()
1332 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
1333 vc_try_checkout = deferredGenerator(vc_try_checkout)
1335 def vc_try_finish(self, workdir):
1336 rmdirRecursive(workdir)
1339 class SVN(VCBase, unittest.TestCase):
1340 vc_name = "svn"
1342 metadir = ".svn"
1343 vctype = "source.SVN"
1344 vctype_try = "svn"
1345 has_got_revision = True
1346 has_got_revision_branches_are_merged = True
1348 def testCheckout(self):
1349 # we verify this one with the svnurl style of vcargs. We test the
1350 # baseURL/defaultBranch style in testPatch and testCheckoutBranch.
1351 self.helper.vcargs = { 'svnurl': self.helper.svnurl_trunk }
1352 d = self.do_vctest()
1353 return d
1355 def testPatch(self):
1356 self.helper.vcargs = { 'baseURL': self.helper.svnurl + "/",
1357 'defaultBranch': "sample/trunk",
1359 d = self.do_patch()
1360 return d
1362 def testCheckoutBranch(self):
1363 self.helper.vcargs = { 'baseURL': self.helper.svnurl + "/",
1364 'defaultBranch': "sample/trunk",
1366 d = self.do_branch()
1367 return d
1369 def testTry(self):
1370 # extract the base revision and patch from a modified tree, use it to
1371 # create the same contents on the buildslave
1372 self.helper.vcargs = { 'baseURL': self.helper.svnurl + "/",
1373 'defaultBranch': "sample/trunk",
1375 d = self.do_getpatch()
1376 return d
1378 ## can't test the username= and password= options, because we do not have an
1379 ## svn repository that requires authentication.
1381 VCS.registerVC(SVN.vc_name, SVNHelper())
1384 class P4Helper(BaseHelper):
1385 branchname = "branch"
1386 p4port = 'localhost:1666'
1387 pid = None
1388 base_descr = 'Change: new\nDescription: asdf\nFiles:\n'
1390 def capable(self):
1391 p4paths = which('p4')
1392 p4dpaths = which('p4d')
1393 if not p4paths:
1394 return (False, "p4 is not installed")
1395 if not p4dpaths:
1396 return (False, "p4d is not installed")
1397 self.vcexe = p4paths[0]
1398 self.p4dexe = p4dpaths[0]
1399 return (True, None)
1401 class _P4DProtocol(protocol.ProcessProtocol):
1402 def __init__(self):
1403 self.started = defer.Deferred()
1404 self.ended = defer.Deferred()
1406 def outReceived(self, data):
1407 # When it says starting, it has bound to the socket.
1408 if self.started:
1410 # Make sure p4d has started. Newer versions of p4d
1411 # have more verbose messaging when db files don't exist, so
1412 # we use re.search instead of startswith.
1414 if re.search('Perforce Server starting...', data):
1415 self.started.callback(None)
1416 else:
1417 print "p4d said %r" % data
1418 try:
1419 raise Exception('p4d said %r' % data)
1420 except:
1421 self.started.errback(failure.Failure())
1422 self.started = None
1424 def errReceived(self, data):
1425 print "p4d stderr: %s" % data
1427 def processEnded(self, status_object):
1428 if status_object.check(error.ProcessDone):
1429 self.ended.callback(None)
1430 else:
1431 self.ended.errback(status_object)
1433 def _start_p4d(self):
1434 proto = self._P4DProtocol()
1435 reactor.spawnProcess(proto, self.p4dexe, ['p4d', '-p', self.p4port],
1436 env=os.environ, path=self.p4rep)
1437 return proto.started, proto.ended
1439 def dop4(self, basedir, command, failureIsOk=False, stdin=None):
1440 # p4 looks at $PWD instead of getcwd(), which causes confusion when
1441 # we spawn commands without an intervening shell (sh -c). We can
1442 # override this with a -d argument.
1443 command = "-p %s -d %s %s" % (self.p4port, basedir, command)
1444 return self.dovc(basedir, command, failureIsOk, stdin)
1446 def createRepository(self):
1447 # this is only called once per VC system, so start p4d here.
1449 self.createBasedir()
1450 tmp = os.path.join(self.repbase, "p4tmp")
1451 self.p4rep = os.path.join(self.repbase, 'P4-Repository')
1452 os.mkdir(self.p4rep)
1454 # Launch p4d.
1455 started, self.p4d_shutdown = self._start_p4d()
1456 w = waitForDeferred(started)
1457 yield w; w.getResult()
1459 # Create client spec.
1460 os.mkdir(tmp)
1461 clispec = 'Client: creator\n'
1462 clispec += 'Root: %s\n' % tmp
1463 clispec += 'View:\n'
1464 clispec += '\t//depot/... //creator/...\n'
1465 w = self.dop4(tmp, 'client -i', stdin=clispec)
1466 yield w; w.getResult()
1468 # Create first rev (trunk).
1469 self.populate(os.path.join(tmp, 'trunk'))
1470 files = ['main.c', 'version.c', 'subdir/subdir.c']
1471 w = self.dop4(tmp, "-c creator add "
1472 + " ".join(['trunk/%s' % f for f in files]))
1473 yield w; w.getResult()
1474 descr = self.base_descr
1475 for file in files:
1476 descr += '\t//depot/trunk/%s\n' % file
1477 w = self.dop4(tmp, "-c creator submit -i", stdin=descr)
1478 yield w; out = w.getResult()
1479 m = re.search(r'Change (\d+) submitted.', out)
1480 assert m.group(1) == '1'
1481 self.addTrunkRev(m.group(1))
1483 # Create second rev (branch).
1484 w = self.dop4(tmp, '-c creator integrate '
1485 + '//depot/trunk/... //depot/branch/...')
1486 yield w; w.getResult()
1487 w = self.dop4(tmp, "-c creator edit branch/main.c")
1488 yield w; w.getResult()
1489 self.populate_branch(os.path.join(tmp, 'branch'))
1490 descr = self.base_descr
1491 for file in files:
1492 descr += '\t//depot/branch/%s\n' % file
1493 w = self.dop4(tmp, "-c creator submit -i", stdin=descr)
1494 yield w; out = w.getResult()
1495 m = re.search(r'Change (\d+) submitted.', out)
1496 self.addBranchRev(m.group(1))
1497 createRepository = deferredGenerator(createRepository)
1499 def vc_revise(self):
1500 tmp = os.path.join(self.repbase, "p4tmp")
1501 self.version += 1
1502 version_c = VERSION_C % self.version
1503 w = self.dop4(tmp, '-c creator edit trunk/version.c')
1504 yield w; w.getResult()
1505 open(os.path.join(tmp, "trunk/version.c"), "w").write(version_c)
1506 descr = self.base_descr + '\t//depot/trunk/version.c\n'
1507 w = self.dop4(tmp, "-c creator submit -i", stdin=descr)
1508 yield w; out = w.getResult()
1509 m = re.search(r'Change (\d+) submitted.', out)
1510 self.addTrunkRev(m.group(1))
1511 vc_revise = deferredGenerator(vc_revise)
1513 def shutdown_p4d(self):
1514 d = self.runCommand(self.repbase, '%s -p %s admin stop'
1515 % (self.vcexe, self.p4port))
1516 return d.addCallback(lambda _: self.p4d_shutdown)
1518 class P4(VCBase, unittest.TestCase):
1519 metadir = None
1520 vctype = "source.P4"
1521 vc_name = "p4"
1522 has_got_revision = True
1524 def tearDownClass(self):
1525 if self.helper:
1526 return self.helper.shutdown_p4d()
1528 def testCheckout(self):
1529 self.helper.vcargs = { 'p4port': self.helper.p4port,
1530 'p4base': '//depot/',
1531 'defaultBranch': 'trunk' }
1532 d = self.do_vctest(testRetry=False)
1533 # TODO: like arch and darcs, sync does nothing when server is not
1534 # changed.
1535 return d
1537 def testCheckoutBranch(self):
1538 self.helper.vcargs = { 'p4port': self.helper.p4port,
1539 'p4base': '//depot/',
1540 'defaultBranch': 'trunk' }
1541 d = self.do_branch()
1542 return d
1544 def testPatch(self):
1545 self.helper.vcargs = { 'p4port': self.helper.p4port,
1546 'p4base': '//depot/',
1547 'defaultBranch': 'trunk' }
1548 d = self.do_patch()
1549 return d
1551 VCS.registerVC(P4.vc_name, P4Helper())
1554 class DarcsHelper(BaseHelper):
1555 branchname = "branch"
1556 try_branchname = "branch"
1558 def capable(self):
1559 darcspaths = which('darcs')
1560 if not darcspaths:
1561 return (False, "Darcs is not installed")
1562 self.vcexe = darcspaths[0]
1563 return (True, None)
1565 def createRepository(self):
1566 self.createBasedir()
1567 self.darcs_base = os.path.join(self.repbase, "Darcs-Repository")
1568 self.rep_trunk = os.path.join(self.darcs_base, "trunk")
1569 self.rep_branch = os.path.join(self.darcs_base, "branch")
1570 tmp = os.path.join(self.repbase, "darcstmp")
1572 _makedirsif(self.rep_trunk)
1573 w = self.dovc(self.rep_trunk, ["initialize"])
1574 yield w; w.getResult()
1575 _makedirsif(self.rep_branch)
1576 w = self.dovc(self.rep_branch, ["initialize"])
1577 yield w; w.getResult()
1579 self.populate(tmp)
1580 w = self.dovc(tmp, qw("initialize"))
1581 yield w; w.getResult()
1582 w = self.dovc(tmp, qw("add -r ."))
1583 yield w; w.getResult()
1584 w = self.dovc(tmp, qw("record -a -m initial_import --skip-long-comment -A test@buildbot.sf.net"))
1585 yield w; w.getResult()
1586 w = self.dovc(tmp, ["push", "-a", self.rep_trunk])
1587 yield w; w.getResult()
1588 w = self.dovc(tmp, qw("changes --context"))
1589 yield w; out = w.getResult()
1590 self.addTrunkRev(out)
1592 self.populate_branch(tmp)
1593 w = self.dovc(tmp, qw("record -a --ignore-times -m commit_on_branch --skip-long-comment -A test@buildbot.sf.net"))
1594 yield w; w.getResult()
1595 w = self.dovc(tmp, ["push", "-a", self.rep_branch])
1596 yield w; w.getResult()
1597 w = self.dovc(tmp, qw("changes --context"))
1598 yield w; out = w.getResult()
1599 self.addBranchRev(out)
1600 rmdirRecursive(tmp)
1601 createRepository = deferredGenerator(createRepository)
1603 def vc_revise(self):
1604 tmp = os.path.join(self.repbase, "darcstmp")
1605 _makedirsif(tmp)
1606 w = self.dovc(tmp, qw("initialize"))
1607 yield w; w.getResult()
1608 w = self.dovc(tmp, ["pull", "-a", self.rep_trunk])
1609 yield w; w.getResult()
1611 self.version += 1
1612 version_c = VERSION_C % self.version
1613 open(os.path.join(tmp, "version.c"), "w").write(version_c)
1614 w = self.dovc(tmp, qw("record -a --ignore-times -m revised_to_%d --skip-long-comment -A test@buildbot.sf.net" % self.version))
1615 yield w; w.getResult()
1616 w = self.dovc(tmp, ["push", "-a", self.rep_trunk])
1617 yield w; w.getResult()
1618 w = self.dovc(tmp, qw("changes --context"))
1619 yield w; out = w.getResult()
1620 self.addTrunkRev(out)
1621 rmdirRecursive(tmp)
1622 vc_revise = deferredGenerator(vc_revise)
1624 def vc_try_checkout(self, workdir, rev, branch=None):
1625 assert os.path.abspath(workdir) == workdir
1626 if os.path.exists(workdir):
1627 rmdirRecursive(workdir)
1628 _makedirsif(workdir)
1629 w = self.dovc(workdir, qw("initialize"))
1630 yield w; w.getResult()
1631 if not branch:
1632 rep = self.rep_trunk
1633 else:
1634 rep = os.path.join(self.darcs_base, branch)
1635 w = self.dovc(workdir, ["pull", "-a", rep])
1636 yield w; w.getResult()
1637 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
1638 vc_try_checkout = deferredGenerator(vc_try_checkout)
1640 def vc_try_finish(self, workdir):
1641 rmdirRecursive(workdir)
1644 class Darcs(VCBase, unittest.TestCase):
1645 vc_name = "darcs"
1647 # Darcs has a metadir="_darcs", but it does not have an 'export'
1648 # mode
1649 metadir = None
1650 vctype = "source.Darcs"
1651 vctype_try = "darcs"
1652 has_got_revision = True
1654 def testCheckout(self):
1655 self.helper.vcargs = { 'repourl': self.helper.rep_trunk }
1656 d = self.do_vctest(testRetry=False)
1658 # TODO: testRetry has the same problem with Darcs as it does for
1659 # Arch
1660 return d
1662 def testPatch(self):
1663 self.helper.vcargs = { 'baseURL': self.helper.darcs_base + "/",
1664 'defaultBranch': "trunk" }
1665 d = self.do_patch()
1666 return d
1668 def testCheckoutBranch(self):
1669 self.helper.vcargs = { 'baseURL': self.helper.darcs_base + "/",
1670 'defaultBranch': "trunk" }
1671 d = self.do_branch()
1672 return d
1674 def testCheckoutHTTP(self):
1675 self.serveHTTP()
1676 repourl = "http://localhost:%d/Darcs-Repository/trunk" % self.httpPort
1677 self.helper.vcargs = { 'repourl': repourl }
1678 d = self.do_vctest(testRetry=False)
1679 return d
1681 def testTry(self):
1682 self.helper.vcargs = { 'baseURL': self.helper.darcs_base + "/",
1683 'defaultBranch': "trunk" }
1684 d = self.do_getpatch()
1685 return d
1687 VCS.registerVC(Darcs.vc_name, DarcsHelper())
1690 class ArchCommon:
1691 def registerRepository(self, coordinates):
1692 a = self.archname
1693 w = self.dovc(self.repbase, "archives %s" % a)
1694 yield w; out = w.getResult()
1695 if out:
1696 w = self.dovc(self.repbase, "register-archive -d %s" % a)
1697 yield w; w.getResult()
1698 w = self.dovc(self.repbase, "register-archive %s" % coordinates)
1699 yield w; w.getResult()
1700 registerRepository = deferredGenerator(registerRepository)
1702 def unregisterRepository(self):
1703 a = self.archname
1704 w = self.dovc(self.repbase, "archives %s" % a)
1705 yield w; out = w.getResult()
1706 if out:
1707 w = self.dovc(self.repbase, "register-archive -d %s" % a)
1708 yield w; out = w.getResult()
1709 unregisterRepository = deferredGenerator(unregisterRepository)
1711 class TlaHelper(BaseHelper, ArchCommon):
1712 defaultbranch = "testvc--mainline--1"
1713 branchname = "testvc--branch--1"
1714 try_branchname = None # TlaExtractor can figure it out by itself
1715 archcmd = "tla"
1717 def capable(self):
1718 tlapaths = which('tla')
1719 if not tlapaths:
1720 return (False, "Arch (tla) is not installed")
1721 self.vcexe = tlapaths[0]
1722 return (True, None)
1724 def do_get(self, basedir, archive, branch, newdir):
1725 # the 'get' syntax is different between tla and baz. baz, while
1726 # claiming to honor an --archive argument, in fact ignores it. The
1727 # correct invocation is 'baz get archive/revision newdir'.
1728 if self.archcmd == "tla":
1729 w = self.dovc(basedir,
1730 "get -A %s %s %s" % (archive, branch, newdir))
1731 else:
1732 w = self.dovc(basedir,
1733 "get %s/%s %s" % (archive, branch, newdir))
1734 return w
1736 def createRepository(self):
1737 self.createBasedir()
1738 # first check to see if bazaar is around, since we'll need to know
1739 # later
1740 d = VCS.capable(Bazaar.vc_name)
1741 d.addCallback(self._createRepository_1)
1742 return d
1744 def _createRepository_1(self, res):
1745 has_baz = res[0]
1747 # pick a hopefully unique string for the archive name, in the form
1748 # test-%d@buildbot.sf.net--testvc, since otherwise multiple copies of
1749 # the unit tests run in the same user account will collide (since the
1750 # archive names are kept in the per-user ~/.arch-params/ directory).
1751 pid = os.getpid()
1752 self.archname = "test-%s-%d@buildbot.sf.net--testvc" % (self.archcmd,
1753 pid)
1754 trunk = self.defaultbranch
1755 branch = self.branchname
1757 repword = self.archcmd.capitalize()
1758 self.archrep = os.path.join(self.repbase, "%s-Repository" % repword)
1759 tmp = os.path.join(self.repbase, "archtmp")
1760 a = self.archname
1762 self.populate(tmp)
1764 w = self.dovc(tmp, "my-id", failureIsOk=True)
1765 yield w; res = w.getResult()
1766 if not res:
1767 # tla will fail a lot of operations if you have not set an ID
1768 w = self.do(tmp, [self.vcexe, "my-id",
1769 "Buildbot Test Suite <test@buildbot.sf.net>"])
1770 yield w; w.getResult()
1772 if has_baz:
1773 # bazaar keeps a cache of revisions, but this test creates a new
1774 # archive each time it is run, so the cache causes errors.
1775 # Disable the cache to avoid these problems. This will be
1776 # slightly annoying for people who run the buildbot tests under
1777 # the same UID as one which uses baz on a regular basis, but
1778 # bazaar doesn't give us a way to disable the cache just for this
1779 # one archive.
1780 cmd = "%s cache-config --disable" % VCS.getHelper('bazaar').vcexe
1781 w = self.do(tmp, cmd)
1782 yield w; w.getResult()
1784 w = waitForDeferred(self.unregisterRepository())
1785 yield w; w.getResult()
1787 # these commands can be run in any directory
1788 w = self.dovc(tmp, "make-archive -l %s %s" % (a, self.archrep))
1789 yield w; w.getResult()
1790 if self.archcmd == "tla":
1791 w = self.dovc(tmp, "archive-setup -A %s %s" % (a, trunk))
1792 yield w; w.getResult()
1793 w = self.dovc(tmp, "archive-setup -A %s %s" % (a, branch))
1794 yield w; w.getResult()
1795 else:
1796 # baz does not require an 'archive-setup' step
1797 pass
1799 # these commands must be run in the directory that is to be imported
1800 w = self.dovc(tmp, "init-tree --nested %s/%s" % (a, trunk))
1801 yield w; w.getResult()
1802 files = " ".join(["main.c", "version.c", "subdir",
1803 os.path.join("subdir", "subdir.c")])
1804 w = self.dovc(tmp, "add-id %s" % files)
1805 yield w; w.getResult()
1807 w = self.dovc(tmp, "import %s/%s" % (a, trunk))
1808 yield w; out = w.getResult()
1809 self.addTrunkRev("base-0")
1811 # create the branch
1812 if self.archcmd == "tla":
1813 branchstart = "%s--base-0" % trunk
1814 w = self.dovc(tmp, "tag -A %s %s %s" % (a, branchstart, branch))
1815 yield w; w.getResult()
1816 else:
1817 w = self.dovc(tmp, "branch %s" % branch)
1818 yield w; w.getResult()
1820 rmdirRecursive(tmp)
1822 # check out the branch
1823 w = self.do_get(self.repbase, a, branch, "archtmp")
1824 yield w; w.getResult()
1825 # and edit the file
1826 self.populate_branch(tmp)
1827 logfile = "++log.%s--%s" % (branch, a)
1828 logmsg = "Summary: commit on branch\nKeywords:\n\n"
1829 open(os.path.join(tmp, logfile), "w").write(logmsg)
1830 w = self.dovc(tmp, "commit")
1831 yield w; out = w.getResult()
1832 m = re.search(r'committed %s/%s--([\S]+)' % (a, branch),
1833 out)
1834 assert (m.group(1) == "base-0" or m.group(1).startswith("patch-"))
1835 self.addBranchRev(m.group(1))
1837 w = waitForDeferred(self.unregisterRepository())
1838 yield w; w.getResult()
1839 rmdirRecursive(tmp)
1841 # we unregister the repository each time, because we might have
1842 # changed the coordinates (since we switch from a file: URL to an
1843 # http: URL for various tests). The buildslave code doesn't forcibly
1844 # unregister the archive, so we have to do it here.
1845 w = waitForDeferred(self.unregisterRepository())
1846 yield w; w.getResult()
1848 _createRepository_1 = deferredGenerator(_createRepository_1)
1850 def vc_revise(self):
1851 # the fix needs to be done in a workspace that is linked to a
1852 # read-write version of the archive (i.e., using file-based
1853 # coordinates instead of HTTP ones), so we re-register the repository
1854 # before we begin. We unregister it when we're done to make sure the
1855 # build will re-register the correct one for whichever test is
1856 # currently being run.
1858 # except, that source.Bazaar really doesn't like it when the archive
1859 # gets unregistered behind its back. The slave tries to do a 'baz
1860 # replay' in a tree with an archive that is no longer recognized, and
1861 # baz aborts with a botched invariant exception. This causes
1862 # mode=update to fall back to clobber+get, which flunks one of the
1863 # tests (the 'newfile' check in _do_vctest_update_3 fails)
1865 # to avoid this, we take heroic steps here to leave the archive
1866 # registration in the same state as we found it.
1868 tmp = os.path.join(self.repbase, "archtmp")
1869 a = self.archname
1871 w = self.dovc(self.repbase, "archives %s" % a)
1872 yield w; out = w.getResult()
1873 assert out
1874 lines = out.split("\n")
1875 coordinates = lines[1].strip()
1877 # now register the read-write location
1878 w = waitForDeferred(self.registerRepository(self.archrep))
1879 yield w; w.getResult()
1881 trunk = self.defaultbranch
1883 w = self.do_get(self.repbase, a, trunk, "archtmp")
1884 yield w; w.getResult()
1886 # tla appears to use timestamps to determine which files have
1887 # changed, so wait long enough for the new file to have a different
1888 # timestamp
1889 time.sleep(2)
1890 self.version += 1
1891 version_c = VERSION_C % self.version
1892 open(os.path.join(tmp, "version.c"), "w").write(version_c)
1894 logfile = "++log.%s--%s" % (trunk, a)
1895 logmsg = "Summary: revised_to_%d\nKeywords:\n\n" % self.version
1896 open(os.path.join(tmp, logfile), "w").write(logmsg)
1897 w = self.dovc(tmp, "commit")
1898 yield w; out = w.getResult()
1899 m = re.search(r'committed %s/%s--([\S]+)' % (a, trunk),
1900 out)
1901 assert (m.group(1) == "base-0" or m.group(1).startswith("patch-"))
1902 self.addTrunkRev(m.group(1))
1904 # now re-register the original coordinates
1905 w = waitForDeferred(self.registerRepository(coordinates))
1906 yield w; w.getResult()
1907 rmdirRecursive(tmp)
1908 vc_revise = deferredGenerator(vc_revise)
1910 def vc_try_checkout(self, workdir, rev, branch=None):
1911 assert os.path.abspath(workdir) == workdir
1912 if os.path.exists(workdir):
1913 rmdirRecursive(workdir)
1915 a = self.archname
1917 # register the read-write location, if it wasn't already registered
1918 w = waitForDeferred(self.registerRepository(self.archrep))
1919 yield w; w.getResult()
1921 w = self.do_get(self.repbase, a, "testvc--mainline--1", workdir)
1922 yield w; w.getResult()
1924 # timestamps. ick.
1925 time.sleep(2)
1926 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
1927 vc_try_checkout = deferredGenerator(vc_try_checkout)
1929 def vc_try_finish(self, workdir):
1930 rmdirRecursive(workdir)
1932 class Arch(VCBase, unittest.TestCase):
1933 vc_name = "tla"
1935 metadir = None
1936 # Arch has a metadir="{arch}", but it does not have an 'export' mode.
1937 vctype = "source.Arch"
1938 vctype_try = "tla"
1939 has_got_revision = True
1941 def testCheckout(self):
1942 # these are the coordinates of the read-write archive used by all the
1943 # non-HTTP tests. testCheckoutHTTP overrides these.
1944 self.helper.vcargs = {'url': self.helper.archrep,
1945 'version': self.helper.defaultbranch }
1946 d = self.do_vctest(testRetry=False)
1947 # the current testRetry=True logic doesn't have the desired effect:
1948 # "update" is a no-op because arch knows that the repository hasn't
1949 # changed. Other VC systems will re-checkout missing files on
1950 # update, arch just leaves the tree untouched. TODO: come up with
1951 # some better test logic, probably involving a copy of the
1952 # repository that has a few changes checked in.
1954 return d
1956 def testCheckoutHTTP(self):
1957 self.serveHTTP()
1958 url = "http://localhost:%d/Tla-Repository" % self.httpPort
1959 self.helper.vcargs = { 'url': url,
1960 'version': "testvc--mainline--1" }
1961 d = self.do_vctest(testRetry=False)
1962 return d
1964 def testPatch(self):
1965 self.helper.vcargs = {'url': self.helper.archrep,
1966 'version': self.helper.defaultbranch }
1967 d = self.do_patch()
1968 return d
1970 def testCheckoutBranch(self):
1971 self.helper.vcargs = {'url': self.helper.archrep,
1972 'version': self.helper.defaultbranch }
1973 d = self.do_branch()
1974 return d
1976 def testTry(self):
1977 self.helper.vcargs = {'url': self.helper.archrep,
1978 'version': self.helper.defaultbranch }
1979 d = self.do_getpatch()
1980 return d
1982 VCS.registerVC(Arch.vc_name, TlaHelper())
1985 class BazaarHelper(TlaHelper):
1986 archcmd = "baz"
1988 def capable(self):
1989 bazpaths = which('baz')
1990 if not bazpaths:
1991 return (False, "Arch (baz) is not installed")
1992 self.vcexe = bazpaths[0]
1993 return (True, None)
1995 def setUp2(self, res):
1996 # we unregister the repository each time, because we might have
1997 # changed the coordinates (since we switch from a file: URL to an
1998 # http: URL for various tests). The buildslave code doesn't forcibly
1999 # unregister the archive, so we have to do it here.
2000 d = self.unregisterRepository()
2001 return d
2004 class Bazaar(Arch):
2005 vc_name = "bazaar"
2007 vctype = "source.Bazaar"
2008 vctype_try = "baz"
2009 has_got_revision = True
2011 fixtimer = None
2013 def testCheckout(self):
2014 self.helper.vcargs = {'url': self.helper.archrep,
2015 # Baz adds the required 'archive' argument
2016 'archive': self.helper.archname,
2017 'version': self.helper.defaultbranch,
2019 d = self.do_vctest(testRetry=False)
2020 # the current testRetry=True logic doesn't have the desired effect:
2021 # "update" is a no-op because arch knows that the repository hasn't
2022 # changed. Other VC systems will re-checkout missing files on
2023 # update, arch just leaves the tree untouched. TODO: come up with
2024 # some better test logic, probably involving a copy of the
2025 # repository that has a few changes checked in.
2027 return d
2029 def testCheckoutHTTP(self):
2030 self.serveHTTP()
2031 url = "http://localhost:%d/Baz-Repository" % self.httpPort
2032 self.helper.vcargs = { 'url': url,
2033 'archive': self.helper.archname,
2034 'version': self.helper.defaultbranch,
2036 d = self.do_vctest(testRetry=False)
2037 return d
2039 def testPatch(self):
2040 self.helper.vcargs = {'url': self.helper.archrep,
2041 # Baz adds the required 'archive' argument
2042 'archive': self.helper.archname,
2043 'version': self.helper.defaultbranch,
2045 d = self.do_patch()
2046 return d
2048 def testCheckoutBranch(self):
2049 self.helper.vcargs = {'url': self.helper.archrep,
2050 # Baz adds the required 'archive' argument
2051 'archive': self.helper.archname,
2052 'version': self.helper.defaultbranch,
2054 d = self.do_branch()
2055 return d
2057 def testTry(self):
2058 self.helper.vcargs = {'url': self.helper.archrep,
2059 # Baz adds the required 'archive' argument
2060 'archive': self.helper.archname,
2061 'version': self.helper.defaultbranch,
2063 d = self.do_getpatch()
2064 return d
2066 def fixRepository(self):
2067 self.fixtimer = None
2068 self.site.resource = self.root
2070 def testRetry(self):
2071 # we want to verify that source.Source(retry=) works, and the easiest
2072 # way to make VC updates break (temporarily) is to break the HTTP
2073 # server that's providing the repository. Anything else pretty much
2074 # requires mutating the (read-only) BUILDBOT_TEST_VC repository, or
2075 # modifying the buildslave's checkout command while it's running.
2077 # this test takes a while to run, so don't bother doing it with
2078 # anything other than baz
2080 self.serveHTTP()
2082 # break the repository server
2083 from twisted.web import static
2084 self.site.resource = static.Data("Sorry, repository is offline",
2085 "text/plain")
2086 # and arrange to fix it again in 5 seconds, while the test is
2087 # running.
2088 self.fixtimer = reactor.callLater(5, self.fixRepository)
2090 url = "http://localhost:%d/Baz-Repository" % self.httpPort
2091 self.helper.vcargs = { 'url': url,
2092 'archive': self.helper.archname,
2093 'version': self.helper.defaultbranch,
2094 'retry': (5.0, 4),
2096 d = self.do_vctest_once(True)
2097 d.addCallback(self._testRetry_1)
2098 return d
2099 def _testRetry_1(self, bs):
2100 # make sure there was mention of the retry attempt in the logs
2101 l = bs.getLogs()[0]
2102 self.failUnlessIn("unable to access URL", l.getText(),
2103 "funny, VC operation didn't fail at least once")
2104 self.failUnlessIn("update failed, trying 4 more times after 5 seconds",
2105 l.getTextWithHeaders(),
2106 "funny, VC operation wasn't reattempted")
2108 def testRetryFails(self):
2109 # make sure that the build eventually gives up on a repository which
2110 # is completely unavailable
2112 self.serveHTTP()
2114 # break the repository server, and leave it broken
2115 from twisted.web import static
2116 self.site.resource = static.Data("Sorry, repository is offline",
2117 "text/plain")
2119 url = "http://localhost:%d/Baz-Repository" % self.httpPort
2120 self.helper.vcargs = {'url': url,
2121 'archive': self.helper.archname,
2122 'version': self.helper.defaultbranch,
2123 'retry': (0.5, 3),
2125 d = self.do_vctest_once(False)
2126 d.addCallback(self._testRetryFails_1)
2127 return d
2128 def _testRetryFails_1(self, bs):
2129 self.failUnlessEqual(bs.getResults(), FAILURE)
2131 def tearDown2(self):
2132 if self.fixtimer:
2133 self.fixtimer.cancel()
2134 # tell tla to get rid of the leftover archive this test leaves in the
2135 # user's 'tla archives' listing. The name of this archive is provided
2136 # by the repository tarball, so the following command must use the
2137 # same name. We could use archive= to set it explicitly, but if you
2138 # change it from the default, then 'tla update' won't work.
2139 d = self.helper.unregisterRepository()
2140 return d
2142 VCS.registerVC(Bazaar.vc_name, BazaarHelper())
2144 class BzrHelper(BaseHelper):
2145 branchname = "branch"
2146 try_branchname = "branch"
2148 def capable(self):
2149 bzrpaths = which('bzr')
2150 if not bzrpaths:
2151 return (False, "bzr is not installed")
2152 self.vcexe = bzrpaths[0]
2153 return (True, None)
2155 def get_revision_number(self, out):
2156 for line in out.split("\n"):
2157 colon = line.index(":")
2158 key, value = line[:colon], line[colon+2:]
2159 if key == "revno":
2160 return int(value)
2161 raise RuntimeError("unable to find revno: in bzr output: '%s'" % out)
2163 def createRepository(self):
2164 self.createBasedir()
2165 self.bzr_base = os.path.join(self.repbase, "Bzr-Repository")
2166 self.rep_trunk = os.path.join(self.bzr_base, "trunk")
2167 self.rep_branch = os.path.join(self.bzr_base, "branch")
2168 tmp = os.path.join(self.repbase, "bzrtmp")
2169 btmp = os.path.join(self.repbase, "bzrtmp-branch")
2171 _makedirsif(self.rep_trunk)
2172 w = self.dovc(self.rep_trunk, ["init"])
2173 yield w; w.getResult()
2174 w = self.dovc(self.bzr_base,
2175 ["branch", self.rep_trunk, self.rep_branch])
2176 yield w; w.getResult()
2178 w = self.dovc(self.repbase, ["checkout", self.rep_trunk, tmp])
2179 yield w; w.getResult()
2180 self.populate(tmp)
2181 w = self.dovc(tmp, qw("add"))
2182 yield w; w.getResult()
2183 w = self.dovc(tmp, qw("commit -m initial_import"))
2184 yield w; w.getResult()
2185 w = self.dovc(tmp, qw("version-info"))
2186 yield w; out = w.getResult()
2187 self.addTrunkRev(self.get_revision_number(out))
2188 rmdirRecursive(tmp)
2190 # pull all trunk revisions to the branch
2191 w = self.dovc(self.rep_branch, qw("pull"))
2192 yield w; w.getResult()
2193 # obtain a branch tree
2194 w = self.dovc(self.repbase, ["checkout", self.rep_branch, btmp])
2195 yield w; w.getResult()
2196 # modify it
2197 self.populate_branch(btmp)
2198 w = self.dovc(btmp, qw("add"))
2199 yield w; w.getResult()
2200 w = self.dovc(btmp, qw("commit -m commit_on_branch"))
2201 yield w; w.getResult()
2202 w = self.dovc(btmp, qw("version-info"))
2203 yield w; out = w.getResult()
2204 self.addBranchRev(self.get_revision_number(out))
2205 rmdirRecursive(btmp)
2206 createRepository = deferredGenerator(createRepository)
2208 def vc_revise(self):
2209 tmp = os.path.join(self.repbase, "bzrtmp")
2210 w = self.dovc(self.repbase, ["checkout", self.rep_trunk, tmp])
2211 yield w; w.getResult()
2213 self.version += 1
2214 version_c = VERSION_C % self.version
2215 open(os.path.join(tmp, "version.c"), "w").write(version_c)
2216 w = self.dovc(tmp, qw("commit -m revised_to_%d" % self.version))
2217 yield w; w.getResult()
2218 w = self.dovc(tmp, qw("version-info"))
2219 yield w; out = w.getResult()
2220 self.addTrunkRev(self.get_revision_number(out))
2221 rmdirRecursive(tmp)
2222 vc_revise = deferredGenerator(vc_revise)
2224 def vc_try_checkout(self, workdir, rev, branch=None):
2225 assert os.path.abspath(workdir) == workdir
2226 if os.path.exists(workdir):
2227 rmdirRecursive(workdir)
2228 #_makedirsif(workdir)
2229 if not branch:
2230 rep = self.rep_trunk
2231 else:
2232 rep = os.path.join(self.bzr_base, branch)
2233 w = self.dovc(self.bzr_base, ["checkout", rep, workdir])
2234 yield w; w.getResult()
2235 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
2236 vc_try_checkout = deferredGenerator(vc_try_checkout)
2238 def vc_try_finish(self, workdir):
2239 rmdirRecursive(workdir)
2241 class Bzr(VCBase, unittest.TestCase):
2242 vc_name = "bzr"
2244 metadir = ".bzr"
2245 vctype = "source.Bzr"
2246 vctype_try = "bzr"
2247 has_got_revision = True
2249 def testCheckout(self):
2250 self.helper.vcargs = { 'repourl': self.helper.rep_trunk }
2251 d = self.do_vctest(testRetry=False)
2253 # TODO: testRetry has the same problem with Bzr as it does for
2254 # Arch
2255 return d
2257 def testPatch(self):
2258 self.helper.vcargs = { 'baseURL': self.helper.bzr_base + "/",
2259 'defaultBranch': "trunk" }
2260 d = self.do_patch()
2261 return d
2263 def testCheckoutBranch(self):
2264 self.helper.vcargs = { 'baseURL': self.helper.bzr_base + "/",
2265 'defaultBranch': "trunk" }
2266 d = self.do_branch()
2267 return d
2269 def testCheckoutHTTP(self):
2270 self.serveHTTP()
2271 repourl = "http://localhost:%d/Bzr-Repository/trunk" % self.httpPort
2272 self.helper.vcargs = { 'repourl': repourl }
2273 d = self.do_vctest(testRetry=False)
2274 return d
2277 def fixRepository(self):
2278 self.fixtimer = None
2279 self.site.resource = self.root
2281 def testRetry(self):
2282 # this test takes a while to run
2283 self.serveHTTP()
2285 # break the repository server
2286 from twisted.web import static
2287 self.site.resource = static.Data("Sorry, repository is offline",
2288 "text/plain")
2289 # and arrange to fix it again in 5 seconds, while the test is
2290 # running.
2291 self.fixtimer = reactor.callLater(5, self.fixRepository)
2293 repourl = "http://localhost:%d/Bzr-Repository/trunk" % self.httpPort
2294 self.helper.vcargs = { 'repourl': repourl,
2295 'retry': (5.0, 4),
2297 d = self.do_vctest_once(True)
2298 d.addCallback(self._testRetry_1)
2299 return d
2300 def _testRetry_1(self, bs):
2301 # make sure there was mention of the retry attempt in the logs
2302 l = bs.getLogs()[0]
2303 self.failUnlessIn("ERROR: Not a branch: ", l.getText(),
2304 "funny, VC operation didn't fail at least once")
2305 self.failUnlessIn("update failed, trying 4 more times after 5 seconds",
2306 l.getTextWithHeaders(),
2307 "funny, VC operation wasn't reattempted")
2309 def testRetryFails(self):
2310 # make sure that the build eventually gives up on a repository which
2311 # is completely unavailable
2313 self.serveHTTP()
2315 # break the repository server, and leave it broken
2316 from twisted.web import static
2317 self.site.resource = static.Data("Sorry, repository is offline",
2318 "text/plain")
2320 repourl = "http://localhost:%d/Bzr-Repository/trunk" % self.httpPort
2321 self.helper.vcargs = { 'repourl': repourl,
2322 'retry': (0.5, 3),
2324 d = self.do_vctest_once(False)
2325 d.addCallback(self._testRetryFails_1)
2326 return d
2327 def _testRetryFails_1(self, bs):
2328 self.failUnlessEqual(bs.getResults(), FAILURE)
2331 def testTry(self):
2332 self.helper.vcargs = { 'baseURL': self.helper.bzr_base + "/",
2333 'defaultBranch': "trunk" }
2334 d = self.do_getpatch()
2335 return d
2337 VCS.registerVC(Bzr.vc_name, BzrHelper())
2340 class MercurialHelper(BaseHelper):
2341 branchname = "branch"
2342 try_branchname = "branch"
2344 def capable(self):
2345 hgpaths = which("hg")
2346 if not hgpaths:
2347 return (False, "Mercurial is not installed")
2348 self.vcexe = hgpaths[0]
2349 return (True, None)
2351 def extract_id(self, output):
2352 m = re.search(r'^(\w+)', output)
2353 return m.group(0)
2355 def createRepository(self):
2356 self.createBasedir()
2357 self.hg_base = os.path.join(self.repbase, "Mercurial-Repository")
2358 self.rep_trunk = os.path.join(self.hg_base, "trunk")
2359 self.rep_branch = os.path.join(self.hg_base, "branch")
2360 tmp = os.path.join(self.hg_base, "hgtmp")
2362 _makedirsif(self.rep_trunk)
2363 w = self.dovc(self.rep_trunk, "init")
2364 yield w; w.getResult()
2365 _makedirsif(self.rep_branch)
2366 w = self.dovc(self.rep_branch, "init")
2367 yield w; w.getResult()
2369 self.populate(tmp)
2370 w = self.dovc(tmp, "init")
2371 yield w; w.getResult()
2372 w = self.dovc(tmp, "add")
2373 yield w; w.getResult()
2374 w = self.dovc(tmp, ['commit', '-m', 'initial_import'])
2375 yield w; w.getResult()
2376 w = self.dovc(tmp, ['push', self.rep_trunk])
2377 # note that hg-push does not actually update the working directory
2378 yield w; w.getResult()
2379 w = self.dovc(tmp, "identify")
2380 yield w; out = w.getResult()
2381 self.addTrunkRev(self.extract_id(out))
2383 self.populate_branch(tmp)
2384 w = self.dovc(tmp, ['commit', '-m', 'commit_on_branch'])
2385 yield w; w.getResult()
2386 w = self.dovc(tmp, ['push', self.rep_branch])
2387 yield w; w.getResult()
2388 w = self.dovc(tmp, "identify")
2389 yield w; out = w.getResult()
2390 self.addBranchRev(self.extract_id(out))
2391 rmdirRecursive(tmp)
2392 createRepository = deferredGenerator(createRepository)
2394 def vc_revise(self):
2395 tmp = os.path.join(self.hg_base, "hgtmp2")
2396 w = self.dovc(self.hg_base, ['clone', self.rep_trunk, tmp])
2397 yield w; w.getResult()
2399 self.version += 1
2400 version_c = VERSION_C % self.version
2401 version_c_filename = os.path.join(tmp, "version.c")
2402 open(version_c_filename, "w").write(version_c)
2403 # hg uses timestamps to distinguish files which have changed, so we
2404 # force the mtime forward a little bit
2405 future = time.time() + 2*self.version
2406 os.utime(version_c_filename, (future, future))
2407 w = self.dovc(tmp, ['commit', '-m', 'revised_to_%d' % self.version])
2408 yield w; w.getResult()
2409 w = self.dovc(tmp, ['push', self.rep_trunk])
2410 yield w; w.getResult()
2411 w = self.dovc(tmp, "identify")
2412 yield w; out = w.getResult()
2413 self.addTrunkRev(self.extract_id(out))
2414 rmdirRecursive(tmp)
2415 vc_revise = deferredGenerator(vc_revise)
2417 def vc_try_checkout(self, workdir, rev, branch=None):
2418 assert os.path.abspath(workdir) == workdir
2419 if os.path.exists(workdir):
2420 rmdirRecursive(workdir)
2421 if branch:
2422 src = self.rep_branch
2423 else:
2424 src = self.rep_trunk
2425 w = self.dovc(self.hg_base, ['clone', src, workdir])
2426 yield w; w.getResult()
2427 try_c_filename = os.path.join(workdir, "subdir", "subdir.c")
2428 open(try_c_filename, "w").write(TRY_C)
2429 future = time.time() + 2*self.version
2430 os.utime(try_c_filename, (future, future))
2431 vc_try_checkout = deferredGenerator(vc_try_checkout)
2433 def vc_try_finish(self, workdir):
2434 rmdirRecursive(workdir)
2436 class MercurialServerPP(protocol.ProcessProtocol):
2437 def __init__(self):
2438 self.wait = defer.Deferred()
2440 def outReceived(self, data):
2441 log.msg("hg-serve-stdout: %s" % (data,))
2442 def errReceived(self, data):
2443 print "HG-SERVE-STDERR:", data
2444 log.msg("hg-serve-stderr: %s" % (data,))
2445 def processEnded(self, reason):
2446 log.msg("hg-serve ended: %s" % reason)
2447 self.wait.callback(None)
2450 class Mercurial(VCBase, unittest.TestCase):
2451 vc_name = "hg"
2453 # Mercurial has a metadir=".hg", but it does not have an 'export' mode.
2454 metadir = None
2455 vctype = "source.Mercurial"
2456 vctype_try = "hg"
2457 has_got_revision = True
2458 _hg_server = None
2459 _wait_for_server_poller = None
2460 _pp = None
2462 # make sure we can identify source repo via default path
2463 def _testSourceURLPresent(self, rc):
2464 d = self.helper.dovc(self.workdir, ['paths', 'default'])
2465 yield d; d.getResult()
2466 _testSourceURLPresent = deferredGenerator(_testSourceURLPresent)
2468 def testCheckout(self):
2469 self.helper.vcargs = { 'repourl': self.helper.rep_trunk }
2470 d = self.do_vctest(testRetry=False)
2472 # TODO: testRetry has the same problem with Mercurial as it does for
2473 # Arch
2474 d.addCallback(self._testSourceURLPresent)
2475 return d
2477 def testPatch(self):
2478 self.helper.vcargs = { 'baseURL': self.helper.hg_base + "/",
2479 'defaultBranch': "trunk" }
2480 d = self.do_patch()
2481 return d
2483 def testCheckoutBranch(self):
2484 self.helper.vcargs = { 'baseURL': self.helper.hg_base + "/",
2485 'defaultBranch': "trunk" }
2487 d = self.do_branch()
2488 d.addCallback(self._testSourceURLPresent)
2489 return d
2491 def serveHTTP(self):
2492 # the easiest way to publish hg over HTTP is by running 'hg serve' as
2493 # a child process while the test is running. (you can also use a CGI
2494 # script, which sounds difficult, or you can publish the files
2495 # directly, which isn't well documented).
2497 # grr.. 'hg serve' doesn't let you use --port=0 to mean "pick a free
2498 # port", instead it uses it as a signal to use the default (port
2499 # 8000). This means there is no way to make it choose a free port, so
2500 # we are forced to make it use a statically-defined one, making it
2501 # harder to avoid collisions.
2502 self.httpPort = 8300 + (os.getpid() % 200)
2503 args = [self.helper.vcexe,
2504 "serve", "--port", str(self.httpPort), "--verbose"]
2506 # in addition, hg doesn't flush its stdout, so we can't wait for the
2507 # "listening at" message to know when it's safe to start the test.
2508 # Instead, poll every second until a getPage works.
2510 self._pp = MercurialServerPP() # logs+discards everything
2512 # this serves one tree at a time, so we serve trunk. TODO: test hg's
2513 # in-repo branches, for which a single tree will hold all branches.
2514 self._hg_server = reactor.spawnProcess(self._pp, self.helper.vcexe, args,
2515 os.environ,
2516 self.helper.rep_trunk)
2517 log.msg("waiting for hg serve to start")
2518 done_d = defer.Deferred()
2519 def poll():
2520 d = client.getPage("http://localhost:%d/" % self.httpPort)
2521 def success(res):
2522 log.msg("hg serve appears to have started")
2523 self._wait_for_server_poller.stop()
2524 done_d.callback(None)
2525 def ignore_connection_refused(f):
2526 f.trap(error.ConnectionRefusedError)
2527 d.addCallbacks(success, ignore_connection_refused)
2528 d.addErrback(done_d.errback)
2529 return d
2530 self._wait_for_server_poller = task.LoopingCall(poll)
2531 self._wait_for_server_poller.start(0.5, True)
2532 return done_d
2534 def tearDown(self):
2535 if self._wait_for_server_poller:
2536 if self._wait_for_server_poller.running:
2537 self._wait_for_server_poller.stop()
2538 if self._hg_server:
2539 self._hg_server.loseConnection()
2540 try:
2541 self._hg_server.signalProcess("KILL")
2542 except error.ProcessExitedAlready:
2543 pass
2544 self._hg_server = None
2545 return VCBase.tearDown(self)
2547 def tearDown2(self):
2548 if self._pp:
2549 return self._pp.wait
2551 def testCheckoutHTTP(self):
2552 d = self.serveHTTP()
2553 def _started(res):
2554 repourl = "http://localhost:%d/" % self.httpPort
2555 self.helper.vcargs = { 'repourl': repourl }
2556 return self.do_vctest(testRetry=False)
2557 d.addCallback(_started)
2558 d.addCallback(self._testSourceURLPresent)
2559 return d
2561 def testTry(self):
2562 self.helper.vcargs = { 'baseURL': self.helper.hg_base + "/",
2563 'defaultBranch': "trunk" }
2564 d = self.do_getpatch()
2565 return d
2567 VCS.registerVC(Mercurial.vc_name, MercurialHelper())
2569 class MercurialInRepoHelper(MercurialHelper):
2570 branchname = "the_branch"
2571 try_branchname = "the_branch"
2574 def createRepository(self):
2575 self.createBasedir()
2576 self.hg_base = os.path.join(self.repbase, "Mercurial-Repository")
2577 self.repo = os.path.join(self.hg_base, "inrepobranch")
2578 tmp = os.path.join(self.hg_base, "hgtmp")
2580 _makedirsif(self.repo)
2581 w = self.dovc(self.repo, "init")
2582 yield w; w.getResult()
2584 self.populate(tmp)
2585 w = self.dovc(tmp, "init")
2586 yield w; w.getResult()
2587 w = self.dovc(tmp, "add")
2588 yield w; w.getResult()
2589 w = self.dovc(tmp, ['commit', '-m', 'initial_import'])
2590 yield w; w.getResult()
2591 w = self.dovc(tmp, ['push', self.repo])
2592 # note that hg-push does not actually update the working directory
2593 yield w; w.getResult()
2594 w = self.dovc(tmp, "identify")
2595 yield w; out = w.getResult()
2596 self.addTrunkRev(self.extract_id(out))
2598 self.populate_branch(tmp)
2599 w = self.dovc(tmp, ['branch', self.branchname])
2600 yield w; w.getResult()
2601 w = self.dovc(tmp, ['commit', '-m', 'commit_on_branch'])
2602 yield w; w.getResult()
2603 w = self.dovc(tmp, ['push', '-f', self.repo])
2604 yield w; w.getResult()
2605 w = self.dovc(tmp, "identify")
2606 yield w; out = w.getResult()
2607 self.addBranchRev(self.extract_id(out))
2608 rmdirRecursive(tmp)
2609 createRepository = deferredGenerator(createRepository)
2611 def vc_revise(self):
2612 tmp = os.path.join(self.hg_base, "hgtmp2")
2613 w = self.dovc(self.hg_base, ['clone', self.repo, tmp])
2614 yield w; w.getResult()
2615 w = self.dovc(tmp, ['update', '--clean', '--rev', 'default'])
2616 yield w; w.getResult()
2618 self.version += 1
2619 version_c = VERSION_C % self.version
2620 version_c_filename = os.path.join(tmp, "version.c")
2621 open(version_c_filename, "w").write(version_c)
2622 # hg uses timestamps to distinguish files which have changed, so we
2623 # force the mtime forward a little bit
2624 future = time.time() + 2*self.version
2625 os.utime(version_c_filename, (future, future))
2626 w = self.dovc(tmp, ['commit', '-m', 'revised_to_%d' % self.version])
2627 yield w; w.getResult()
2628 w = self.dovc(tmp, ['push', '--force', self.repo])
2629 yield w; w.getResult()
2630 w = self.dovc(tmp, "identify")
2631 yield w; out = w.getResult()
2632 self.addTrunkRev(self.extract_id(out))
2633 rmdirRecursive(tmp)
2634 vc_revise = deferredGenerator(vc_revise)
2636 def vc_try_checkout(self, workdir, rev, branch=None):
2637 assert os.path.abspath(workdir) == workdir
2638 if os.path.exists(workdir):
2639 rmdirRecursive(workdir)
2640 w = self.dovc(self.hg_base, ['clone', self.repo, workdir])
2641 yield w; w.getResult()
2642 if not branch: branch = "default"
2643 w = self.dovc(workdir, ['update', '--clean', '--rev', branch ])
2644 yield w; w.getResult()
2646 try_c_filename = os.path.join(workdir, "subdir", "subdir.c")
2647 open(try_c_filename, "w").write(TRY_C)
2648 future = time.time() + 2*self.version
2649 os.utime(try_c_filename, (future, future))
2650 vc_try_checkout = deferredGenerator(vc_try_checkout)
2652 def vc_try_finish(self, workdir):
2653 rmdirRecursive(workdir)
2654 pass
2657 class MercurialInRepo(Mercurial):
2658 vc_name = 'MercurialInRepo'
2660 def default_args(self):
2661 return { 'repourl': self.helper.repo,
2662 'branchType': 'inrepo',
2663 'defaultBranch': 'default' }
2665 def testCheckout(self):
2666 self.helper.vcargs = self.default_args()
2667 d = self.do_vctest(testRetry=False)
2669 # TODO: testRetry has the same problem with Mercurial as it does for
2670 # Arch
2671 d.addCallback(self._testSourceURLPresent)
2672 return d
2674 def testPatch(self):
2675 self.helper.vcargs = self.default_args()
2676 d = self.do_patch()
2677 return d
2679 def testCheckoutBranch(self):
2680 self.helper.vcargs = self.default_args()
2681 d = self.do_branch()
2682 d.addCallback(self._testSourceURLPresent)
2683 return d
2685 def serveHTTP(self):
2686 # the easiest way to publish hg over HTTP is by running 'hg serve' as
2687 # a child process while the test is running. (you can also use a CGI
2688 # script, which sounds difficult, or you can publish the files
2689 # directly, which isn't well documented).
2691 # grr.. 'hg serve' doesn't let you use --port=0 to mean "pick a free
2692 # port", instead it uses it as a signal to use the default (port
2693 # 8000). This means there is no way to make it choose a free port, so
2694 # we are forced to make it use a statically-defined one, making it
2695 # harder to avoid collisions.
2696 self.httpPort = 8300 + (os.getpid() % 200)
2697 args = [self.helper.vcexe,
2698 "serve", "--port", str(self.httpPort), "--verbose"]
2700 # in addition, hg doesn't flush its stdout, so we can't wait for the
2701 # "listening at" message to know when it's safe to start the test.
2702 # Instead, poll every second until a getPage works.
2704 self._pp = MercurialServerPP() # logs+discards everything
2705 # this serves one tree at a time, so we serve trunk. TODO: test hg's
2706 # in-repo branches, for which a single tree will hold all branches.
2707 self._hg_server = reactor.spawnProcess(self._pp, self.helper.vcexe, args,
2708 os.environ,
2709 self.helper.repo)
2710 log.msg("waiting for hg serve to start")
2711 done_d = defer.Deferred()
2712 def poll():
2713 d = client.getPage("http://localhost:%d/" % self.httpPort)
2714 def success(res):
2715 log.msg("hg serve appears to have started")
2716 self._wait_for_server_poller.stop()
2717 done_d.callback(None)
2718 def ignore_connection_refused(f):
2719 f.trap(error.ConnectionRefusedError)
2720 d.addCallbacks(success, ignore_connection_refused)
2721 d.addErrback(done_d.errback)
2722 return d
2723 self._wait_for_server_poller = task.LoopingCall(poll)
2724 self._wait_for_server_poller.start(0.5, True)
2725 return done_d
2727 def tearDown(self):
2728 if self._wait_for_server_poller:
2729 if self._wait_for_server_poller.running:
2730 self._wait_for_server_poller.stop()
2731 if self._hg_server:
2732 self._hg_server.loseConnection()
2733 try:
2734 self._hg_server.signalProcess("KILL")
2735 except error.ProcessExitedAlready:
2736 pass
2737 self._hg_server = None
2738 return VCBase.tearDown(self)
2740 def tearDown2(self):
2741 if self._pp:
2742 return self._pp.wait
2744 def testCheckoutHTTP(self):
2745 d = self.serveHTTP()
2746 def _started(res):
2747 repourl = "http://localhost:%d/" % self.httpPort
2748 self.helper.vcargs = self.default_args()
2749 self.helper.vcargs['repourl'] = repourl
2750 return self.do_vctest(testRetry=False)
2751 d.addCallback(_started)
2752 d.addCallback(self._testSourceURLPresent)
2753 return d
2755 def testTry(self):
2756 self.helper.vcargs = self.default_args()
2757 d = self.do_getpatch()
2758 return d
2760 VCS.registerVC(MercurialInRepo.vc_name, MercurialInRepoHelper())
2763 class GitHelper(BaseHelper):
2764 branchname = "branch"
2765 try_branchname = "branch"
2767 def capable(self):
2768 gitpaths = which('git')
2769 if not gitpaths:
2770 return (False, "GIT is not installed")
2771 d = utils.getProcessOutput(gitpaths[0], ["--version"], env=os.environ)
2772 d.addCallback(self._capable, gitpaths[0])
2773 return d
2775 def _capable(self, v, vcexe):
2776 try:
2777 m = re.search(r'\b(\d+)\.(\d+)', v)
2779 if not m:
2780 raise Exception, 'no regex match'
2782 ver = tuple([int(num) for num in m.groups()])
2784 # git-1.1.3 (as shipped with Dapper) doesn't understand 'git
2785 # init' (it wants 'git init-db'), and fails unit tests that
2786 # involve branches. git-1.5.3.6 (on my debian/unstable system)
2787 # works. I don't know where the dividing point is: if someone can
2788 # figure it out (or figure out how to make buildbot support more
2789 # versions), please update this check.
2790 if ver < (1, 2):
2791 return (False, "Found git (%s) but it is older than 1.2.x" % vcexe)
2793 except Exception, e:
2794 log.msg("couldn't identify git version number in output:")
2795 log.msg("'''%s'''" % v)
2796 log.msg("because: %s" % e)
2797 log.msg("skipping tests")
2798 return (False,
2799 "Found git (%s) but couldn't identify its version from '%s'" % (vcexe, v))
2801 self.vcexe = vcexe
2802 return (True, None)
2804 def createRepository(self):
2805 self.createBasedir()
2806 self.gitrepo = os.path.join(self.repbase,
2807 "GIT-Repository")
2808 tmp = os.path.join(self.repbase, "gittmp")
2810 env = os.environ.copy()
2811 env['GIT_DIR'] = self.gitrepo
2812 w = self.dovc(self.repbase, "init", env=env)
2813 yield w; w.getResult()
2815 self.populate(tmp)
2816 w = self.dovc(tmp, "init")
2817 yield w; w.getResult()
2818 w = self.dovc(tmp, ["add", "."])
2819 yield w; w.getResult()
2820 w = self.dovc(tmp, ["config", "user.email", "buildbot-trial@localhost"])
2821 yield w; w.getResult()
2822 w = self.dovc(tmp, ["config", "user.name", "Buildbot Trial"])
2823 yield w; w.getResult()
2824 w = self.dovc(tmp, ["commit", "-m", "initial_import"])
2825 yield w; w.getResult()
2827 w = self.dovc(tmp, ["checkout", "-b", self.branchname])
2828 yield w; w.getResult()
2829 self.populate_branch(tmp)
2830 w = self.dovc(tmp, ["commit", "-a", "-m", "commit_on_branch"])
2831 yield w; w.getResult()
2833 w = self.dovc(tmp, ["rev-parse", "master", self.branchname])
2834 yield w; out = w.getResult()
2835 revs = out.splitlines()
2836 self.addTrunkRev(revs[0])
2837 self.addBranchRev(revs[1])
2839 w = self.dovc(tmp, ["push", self.gitrepo, "master", self.branchname])
2840 yield w; w.getResult()
2842 rmdirRecursive(tmp)
2843 createRepository = deferredGenerator(createRepository)
2845 def vc_revise(self):
2846 tmp = os.path.join(self.repbase, "gittmp")
2847 rmdirRecursive(tmp)
2848 log.msg("vc_revise" + self.gitrepo)
2849 w = self.dovc(self.repbase, ["clone", self.gitrepo, "gittmp"])
2850 yield w; w.getResult()
2851 w = self.dovc(tmp, ["config", "user.email", "buildbot-trial@localhost"])
2852 yield w; w.getResult()
2853 w = self.dovc(tmp, ["config", "user.name", "Buildbot Trial"])
2854 yield w; w.getResult()
2856 self.version += 1
2857 version_c = VERSION_C % self.version
2858 open(os.path.join(tmp, "version.c"), "w").write(version_c)
2860 w = self.dovc(tmp, ["commit", "-m", "revised_to_%d" % self.version,
2861 "version.c"])
2862 yield w; w.getResult()
2863 w = self.dovc(tmp, ["rev-parse", "master"])
2864 yield w; out = w.getResult()
2865 self.addTrunkRev(out.strip())
2867 w = self.dovc(tmp, ["push", self.gitrepo, "master"])
2868 yield w; out = w.getResult()
2869 rmdirRecursive(tmp)
2870 vc_revise = deferredGenerator(vc_revise)
2872 def vc_try_checkout(self, workdir, rev, branch=None):
2873 assert os.path.abspath(workdir) == workdir
2874 if os.path.exists(workdir):
2875 rmdirRecursive(workdir)
2877 w = self.dovc(self.repbase, ["clone", self.gitrepo, workdir])
2878 yield w; w.getResult()
2879 w = self.dovc(workdir, ["config", "user.email", "buildbot-trial@localhost"])
2880 yield w; w.getResult()
2881 w = self.dovc(workdir, ["config", "user.name", "Buildbot Trial"])
2882 yield w; w.getResult()
2884 if branch is not None:
2885 w = self.dovc(workdir, ["checkout", "-b", branch,
2886 "origin/%s" % branch])
2887 yield w; w.getResult()
2889 # Hmm...why do nobody else bother to check out the correct
2890 # revision?
2891 w = self.dovc(workdir, ["reset", "--hard", rev])
2892 yield w; w.getResult()
2894 try_c_filename = os.path.join(workdir, "subdir", "subdir.c")
2895 open(try_c_filename, "w").write(TRY_C)
2896 vc_try_checkout = deferredGenerator(vc_try_checkout)
2898 def vc_try_finish(self, workdir):
2899 rmdirRecursive(workdir)
2901 class Git(VCBase, unittest.TestCase):
2902 vc_name = "git"
2904 # No 'export' mode yet...
2905 # metadir = ".git"
2906 vctype = "source.Git"
2907 vctype_try = "git"
2908 has_got_revision = True
2910 def testCheckout(self):
2911 self.helper.vcargs = { 'repourl': self.helper.gitrepo }
2912 d = self.do_vctest()
2913 return d
2915 def testPatch(self):
2916 self.helper.vcargs = { 'repourl': self.helper.gitrepo,
2917 'branch': "master" }
2918 d = self.do_patch()
2919 return d
2921 def testCheckoutBranch(self):
2922 self.helper.vcargs = { 'repourl': self.helper.gitrepo,
2923 'branch': "master" }
2924 d = self.do_branch()
2925 return d
2927 def testTry(self):
2928 self.helper.vcargs = { 'repourl': self.helper.gitrepo,
2929 'branch': "master" }
2930 d = self.do_getpatch()
2931 return d
2933 # The git tests override parts of the test sequence to demonstrate
2934 # different update behavior. In the git cases, we always
2935 # effectively have a clobbered tree.
2937 def _do_vctest_update_3(self, bs):
2938 log.msg("_do_vctest_update_3")
2939 self.shouldExist(self.workdir, "main.c")
2940 self.shouldExist(self.workdir, "version.c")
2941 self.shouldContain(self.workdir, "version.c",
2942 "version=%d" % self.helper.version)
2943 self.failUnlessEqual(bs.getProperty("revision"), None)
2944 self.checkGotRevisionIsLatest(bs)
2946 # now "update" to an older revision
2947 d = self.doBuild(ss=SourceStamp(revision=self.helper.trunk[-2]))
2948 d.addCallback(self._do_vctest_update_4)
2949 return d
2951 def _do_vctest_copy_2(self, bs):
2952 log.msg("_do_vctest_copy 3")
2953 if self.metadir:
2954 self.shouldExist(self.workdir, self.metadir)
2955 self.shouldNotExist(self.workdir, "newfile")
2956 self.failUnlessEqual(bs.getProperty("revision"), None)
2957 self.checkGotRevisionIsLatest(bs)
2958 self.touch(self.workdir, "newfile")
2960 def _doBranch_3(self, bs):
2961 log.msg("_doBranch_3")
2962 # make sure it is still on the branch
2963 main_c = os.path.join(self.slavebase, "vc-dir", "build", "main.c")
2964 data = open(main_c, "r").read()
2965 self.failUnlessIn("Hello branch.", data)
2967 # now make sure that a non-branch checkout clobbers the tree
2968 d = self.doBuild(ss=SourceStamp())
2969 d.addCallback(self._doBranch_4)
2970 return d
2972 VCS.registerVC(Git.vc_name, GitHelper())
2975 class Sources(unittest.TestCase):
2976 # TODO: this needs serious rethink
2977 def makeChange(self, when=None, revision=None):
2978 if when:
2979 when = mktime_tz(parsedate_tz(when))
2980 return changes.Change("fred", [], "", when=when, revision=revision)
2982 def testCVS1(self):
2983 r = base.BuildRequest("forced build", SourceStamp(), 'test_builder')
2984 b = base.Build([r])
2985 s = source.CVS(cvsroot=None, cvsmodule=None)
2986 s.setBuild(b)
2987 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()), None)
2989 def testCVS2(self):
2990 c = []
2991 c.append(self.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2992 c.append(self.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2993 c.append(self.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2994 r = base.BuildRequest("forced", SourceStamp(changes=c), 'test_builder')
2995 submitted = "Wed, 08 Sep 2004 09:04:00 -0700"
2996 r.submittedAt = mktime_tz(parsedate_tz(submitted))
2997 b = base.Build([r])
2998 s = source.CVS(cvsroot=None, cvsmodule=None)
2999 s.setBuild(b)
3000 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()),
3001 "Wed, 08 Sep 2004 16:03:00 -0000")
3003 def testCVS3(self):
3004 c = []
3005 c.append(self.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
3006 c.append(self.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
3007 c.append(self.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
3008 r = base.BuildRequest("forced", SourceStamp(changes=c), 'test_builder')
3009 submitted = "Wed, 08 Sep 2004 09:04:00 -0700"
3010 r.submittedAt = mktime_tz(parsedate_tz(submitted))
3011 b = base.Build([r])
3012 s = source.CVS(cvsroot=None, cvsmodule=None, checkoutDelay=10)
3013 s.setBuild(b)
3014 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()),
3015 "Wed, 08 Sep 2004 16:02:10 -0000")
3017 def testCVS4(self):
3018 c = []
3019 c.append(self.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
3020 c.append(self.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
3021 c.append(self.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
3022 r1 = base.BuildRequest("forced", SourceStamp(changes=c), 'test_builder')
3023 submitted = "Wed, 08 Sep 2004 09:04:00 -0700"
3024 r1.submittedAt = mktime_tz(parsedate_tz(submitted))
3026 c = []
3027 c.append(self.makeChange("Wed, 08 Sep 2004 09:05:00 -0700"))
3028 r2 = base.BuildRequest("forced", SourceStamp(changes=c), 'test_builder')
3029 submitted = "Wed, 08 Sep 2004 09:07:00 -0700"
3030 r2.submittedAt = mktime_tz(parsedate_tz(submitted))
3032 b = base.Build([r1, r2])
3033 s = source.CVS(cvsroot=None, cvsmodule=None)
3034 s.setBuild(b)
3035 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()),
3036 "Wed, 08 Sep 2004 16:06:00 -0000")
3038 def testSVN1(self):
3039 r = base.BuildRequest("forced", SourceStamp(), 'test_builder')
3040 b = base.Build([r])
3041 s = source.SVN(svnurl="dummy")
3042 s.setBuild(b)
3043 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()), None)
3045 def testSVN2(self):
3046 c = []
3047 c.append(self.makeChange(revision=4))
3048 c.append(self.makeChange(revision=10))
3049 c.append(self.makeChange(revision=67))
3050 r = base.BuildRequest("forced", SourceStamp(changes=c), 'test_builder')
3051 b = base.Build([r])
3052 s = source.SVN(svnurl="dummy")
3053 s.setBuild(b)
3054 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()), 67)
3056 class Patch(VCBase, unittest.TestCase):
3057 def setUp(self):
3058 pass
3060 def tearDown(self):
3061 pass
3063 def testPatch(self):
3064 # invoke 'patch' all by itself, to see if it works the way we think
3065 # it should. This is intended to ferret out some windows test
3066 # failures.
3067 helper = BaseHelper()
3068 self.workdir = os.path.join("test_vc", "testPatch")
3069 helper.populate(self.workdir)
3070 patch = which("patch")[0]
3072 command = [patch, "-p0"]
3073 class FakeBuilder:
3074 usePTY = False
3075 def sendUpdate(self, status):
3076 pass
3077 c = commands.ShellCommand(FakeBuilder(), command, self.workdir,
3078 sendRC=False, initialStdin=p0_diff)
3079 d = c.start()
3080 d.addCallback(self._testPatch_1)
3081 return d
3083 def _testPatch_1(self, res):
3084 # make sure the file actually got patched
3085 subdir_c = os.path.join(self.workdir, "subdir", "subdir.c")
3086 data = open(subdir_c, "r").read()
3087 self.failUnlessIn("Hello patched subdir.\\n", data)