update docs, factories, and tests to use new buildbot/steps/* definitions
[buildbot.git] / buildbot / test / test_vc.py
blobd59a82bf514023f5c9613272c29de35d59121164
1 # -*- test-case-name: buildbot.test.test_vc -*-
3 from __future__ import generators
5 import sys, os, time, re
6 from email.Utils import mktime_tz, parsedate_tz
8 from twisted.trial import unittest
9 from twisted.internet import defer, reactor, utils, protocol, error
10 from twisted.python import failure
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.twcompat import maybeWait, which
26 from buildbot.scripts import tryclient
27 from buildbot.test.runutils import SignalMixin
29 #step.LoggedRemoteCommand.debug = True
31 # buildbot.twcompat will patch these into t.i.defer if necessary
32 from twisted.internet.defer import waitForDeferred, deferredGenerator
34 # Most of these tests (all but SourceStamp) depend upon having a set of
35 # repositories from which we can perform checkouts. These repositories are
36 # created by the setUp method at the start of each test class. In earlier
37 # versions these repositories were created offline and distributed with a
38 # separate tarball named 'buildbot-test-vc-1.tar.gz'. This is no longer
39 # necessary.
41 # CVS requires a local file repository. Providing remote access is beyond
42 # the feasible abilities of this test program (needs pserver or ssh).
44 # SVN requires a local file repository. To provide remote access over HTTP
45 # requires an apache server with DAV support and mod_svn, way beyond what we
46 # can test from here.
48 # Arch and Darcs both allow remote (read-only) operation with any web
49 # server. We test both local file access and HTTP access (by spawning a
50 # small web server to provide access to the repository files while the test
51 # is running).
53 # Perforce starts the daemon running on localhost. Unfortunately, it must
54 # use a predetermined Internet-domain port number, unless we want to go
55 # all-out: bind the listen socket ourselves and pretend to be inetd.
57 try:
58 import cStringIO
59 StringIO = cStringIO
60 except ImportError:
61 import StringIO
63 class _PutEverythingGetter(protocol.ProcessProtocol):
64 def __init__(self, deferred, stdin):
65 self.deferred = deferred
66 self.outBuf = StringIO.StringIO()
67 self.errBuf = StringIO.StringIO()
68 self.outReceived = self.outBuf.write
69 self.errReceived = self.errBuf.write
70 self.stdin = stdin
72 def connectionMade(self):
73 if self.stdin is not None:
74 self.transport.write(self.stdin)
75 self.transport.closeStdin()
77 def processEnded(self, reason):
78 out = self.outBuf.getvalue()
79 err = self.errBuf.getvalue()
80 e = reason.value
81 code = e.exitCode
82 if e.signal:
83 self.deferred.errback((out, err, e.signal))
84 else:
85 self.deferred.callback((out, err, code))
87 def myGetProcessOutputAndValue(executable, args=(), env={}, path='.',
88 reactor=None, stdin=None):
89 """Like twisted.internet.utils.getProcessOutputAndValue but takes
90 stdin, too."""
91 if reactor is None:
92 from twisted.internet import reactor
93 d = defer.Deferred()
94 p = _PutEverythingGetter(d, stdin)
95 reactor.spawnProcess(p, executable, (executable,)+tuple(args), env, path)
96 return d
98 config_vc = """
99 from buildbot.process import factory
100 from buildbot.steps import source
101 s = factory.s
103 f1 = factory.BuildFactory([
106 c = {}
107 c['bots'] = [['bot1', 'sekrit']]
108 c['sources'] = []
109 c['schedulers'] = []
110 c['builders'] = [{'name': 'vc', 'slavename': 'bot1',
111 'builddir': 'vc-dir', 'factory': f1}]
112 c['slavePortnum'] = 0
113 BuildmasterConfig = c
116 p0_diff = r"""
117 Index: subdir/subdir.c
118 ===================================================================
119 RCS file: /home/warner/stuff/Projects/BuildBot/code-arch/_trial_temp/test_vc/repositories/CVS-Repository/sample/subdir/subdir.c,v
120 retrieving revision 1.1.1.1
121 diff -u -r1.1.1.1 subdir.c
122 --- subdir/subdir.c 14 Aug 2005 01:32:49 -0000 1.1.1.1
123 +++ subdir/subdir.c 14 Aug 2005 01:36:15 -0000
124 @@ -4,6 +4,6 @@
126 main(int argc, const char *argv[])
128 - printf("Hello subdir.\n");
129 + printf("Hello patched subdir.\n");
130 return 0;
134 # this patch does not include the filename headers, so it is
135 # patchlevel-neutral
136 TRY_PATCH = '''
137 @@ -5,6 +5,6 @@
139 main(int argc, const char *argv[])
141 - printf("Hello subdir.\\n");
142 + printf("Hello try.\\n");
143 return 0;
147 MAIN_C = '''
148 // this is main.c
149 #include <stdio.h>
152 main(int argc, const char *argv[])
154 printf("Hello world.\\n");
155 return 0;
159 BRANCH_C = '''
160 // this is main.c
161 #include <stdio.h>
164 main(int argc, const char *argv[])
166 printf("Hello branch.\\n");
167 return 0;
171 VERSION_C = '''
172 // this is version.c
173 #include <stdio.h>
176 main(int argc, const char *argv[])
178 printf("Hello world, version=%d\\n");
179 return 0;
183 SUBDIR_C = '''
184 // this is subdir/subdir.c
185 #include <stdio.h>
188 main(int argc, const char *argv[])
190 printf("Hello subdir.\\n");
191 return 0;
195 TRY_C = '''
196 // this is subdir/subdir.c
197 #include <stdio.h>
200 main(int argc, const char *argv[])
202 printf("Hello try.\\n");
203 return 0;
207 def qw(s):
208 return s.split()
210 class VCS_Helper:
211 # this is a helper class which keeps track of whether each VC system is
212 # available, and whether the repository for each has been created. There
213 # is one instance of this class, at module level, shared between all test
214 # cases.
216 def __init__(self):
217 self._helpers = {}
218 self._isCapable = {}
219 self._excuses = {}
220 self._repoReady = {}
222 def registerVC(self, name, helper):
223 self._helpers[name] = helper
224 self._repoReady[name] = False
226 def skipIfNotCapable(self, name):
227 """Either return None, or raise SkipTest"""
228 d = self.capable(name)
229 def _maybeSkip(res):
230 if not res[0]:
231 raise unittest.SkipTest(res[1])
232 d.addCallback(_maybeSkip)
233 return d
235 def capable(self, name):
236 """Return a Deferred that fires with (True,None) if this host offers
237 the given VC tool, or (False,excuse) if it does not (and therefore
238 the tests should be skipped)."""
240 if self._isCapable.has_key(name):
241 if self._isCapable[name]:
242 return defer.succeed((True,None))
243 else:
244 return defer.succeed((False, self._excuses[name]))
245 d = defer.maybeDeferred(self._helpers[name].capable)
246 def _capable(res):
247 if res[0]:
248 self._isCapable[name] = True
249 else:
250 self._excuses[name] = res[1]
251 return res
252 d.addCallback(_capable)
253 return d
255 def getHelper(self, name):
256 return self._helpers[name]
258 def createRepository(self, name):
259 """Return a Deferred that fires when the repository is set up."""
260 if self._repoReady[name]:
261 return defer.succeed(True)
262 d = self._helpers[name].createRepository()
263 def _ready(res):
264 self._repoReady[name] = True
265 d.addCallback(_ready)
266 return d
268 VCS = VCS_Helper()
271 # the overall plan here:
273 # Each VC system is tested separately, all using the same source tree defined
274 # in the 'files' dictionary above. Each VC system gets its own TestCase
275 # subclass. The first test case that is run will create the repository during
276 # setUp(), making two branches: 'trunk' and 'branch'. The trunk gets a copy
277 # of all the files in 'files'. The variant of good.c is committed on the
278 # branch.
280 # then testCheckout is run, which does a number of checkout/clobber/update
281 # builds. These all use trunk r1. It then runs self.fix(), which modifies
282 # 'fixable.c', then performs another build and makes sure the tree has been
283 # updated.
285 # testBranch uses trunk-r1 and branch-r1, making sure that we clobber the
286 # tree properly when we switch between them
288 # testPatch does a trunk-r1 checkout and applies a patch.
290 # testTryGetPatch performs a trunk-r1 checkout, modifies some files, then
291 # verifies that tryclient.getSourceStamp figures out the base revision and
292 # what got changed.
295 # vc_create makes a repository at r1 with three files: main.c, version.c, and
296 # subdir/foo.c . It also creates a branch from r1 (called b1) in which main.c
297 # says "hello branch" instead of "hello world". self.trunk[] contains
298 # revision stamps for everything on the trunk, and self.branch[] does the
299 # same for the branch.
301 # vc_revise() checks out a tree at HEAD, changes version.c, then checks it
302 # back in. The new version stamp is appended to self.trunk[]. The tree is
303 # removed afterwards.
305 # vc_try_checkout(workdir, rev) checks out a tree at REV, then changes
306 # subdir/subdir.c to say 'Hello try'
307 # vc_try_finish(workdir) removes the tree and cleans up any VC state
308 # necessary (like deleting the Arch archive entry).
311 class BaseHelper:
312 def __init__(self):
313 self.trunk = []
314 self.branch = []
315 self.allrevs = []
317 def capable(self):
318 # this is also responsible for setting self.vcexe
319 raise NotImplementedError
321 def createBasedir(self):
322 # you must call this from createRepository
323 self.repbase = os.path.abspath(os.path.join("test_vc",
324 "repositories"))
325 if not os.path.isdir(self.repbase):
326 os.makedirs(self.repbase)
328 def createRepository(self):
329 # this will only be called once per process
330 raise NotImplementedError
332 def populate(self, basedir):
333 os.makedirs(basedir)
334 os.makedirs(os.path.join(basedir, "subdir"))
335 open(os.path.join(basedir, "main.c"), "w").write(MAIN_C)
336 self.version = 1
337 version_c = VERSION_C % self.version
338 open(os.path.join(basedir, "version.c"), "w").write(version_c)
339 open(os.path.join(basedir, "main.c"), "w").write(MAIN_C)
340 open(os.path.join(basedir, "subdir", "subdir.c"), "w").write(SUBDIR_C)
342 def populate_branch(self, basedir):
343 open(os.path.join(basedir, "main.c"), "w").write(BRANCH_C)
345 def addTrunkRev(self, rev):
346 self.trunk.append(rev)
347 self.allrevs.append(rev)
348 def addBranchRev(self, rev):
349 self.branch.append(rev)
350 self.allrevs.append(rev)
352 def runCommand(self, basedir, command, failureIsOk=False, stdin=None):
353 # all commands passed to do() should be strings or lists. If they are
354 # strings, none of the arguments may have spaces. This makes the
355 # commands less verbose at the expense of restricting what they can
356 # specify.
357 if type(command) not in (list, tuple):
358 command = command.split(" ")
359 DEBUG = False
360 if DEBUG:
361 print "do %s" % command
362 print " in basedir %s" % basedir
363 if stdin:
364 print " STDIN:\n", stdin, "\n--STDIN DONE"
365 env = os.environ.copy()
366 env['LC_ALL'] = "C"
367 d = myGetProcessOutputAndValue(command[0], command[1:],
368 env=env, path=basedir,
369 stdin=stdin)
370 def check((out, err, code)):
371 if DEBUG:
372 print
373 print "command was: %s" % command
374 if out: print "out: %s" % out
375 if err: print "err: %s" % err
376 print "code: %s" % code
377 if code != 0 and not failureIsOk:
378 log.msg("command %s finished with exit code %d" %
379 (command, code))
380 log.msg(" and stdout %s" % (out,))
381 log.msg(" and stderr %s" % (err,))
382 raise RuntimeError("command %s finished with exit code %d"
383 % (command, code)
384 + ": see logs for stdout")
385 return out
386 d.addCallback(check)
387 return d
389 def do(self, basedir, command, failureIsOk=False, stdin=None):
390 d = self.runCommand(basedir, command, failureIsOk=failureIsOk,
391 stdin=stdin)
392 return waitForDeferred(d)
394 def dovc(self, basedir, command, failureIsOk=False, stdin=None):
395 """Like do(), but the VC binary will be prepended to COMMAND."""
396 if isinstance(command, (str, unicode)):
397 command = self.vcexe + " " + command
398 else:
399 # command is a list
400 command = [self.vcexe] + command
401 return self.do(basedir, command, failureIsOk, stdin)
403 class VCBase(SignalMixin):
404 metadir = None
405 createdRepository = False
406 master = None
407 slave = None
408 helper = None
409 httpServer = None
410 httpPort = None
411 skip = None
412 has_got_revision = False
413 has_got_revision_branches_are_merged = False # for SVN
415 def failUnlessIn(self, substring, string, msg=None):
416 # trial provides a version of this that requires python-2.3 to test
417 # strings.
418 if msg is None:
419 msg = ("did not see the expected substring '%s' in string '%s'" %
420 (substring, string))
421 self.failUnless(string.find(substring) != -1, msg)
423 def setUp(self):
424 d = VCS.skipIfNotCapable(self.vc_name)
425 d.addCallback(self._setUp1)
426 return maybeWait(d)
428 def _setUp1(self, res):
429 self.helper = VCS.getHelper(self.vc_name)
431 if os.path.exists("basedir"):
432 rmdirRecursive("basedir")
433 os.mkdir("basedir")
434 self.master = master.BuildMaster("basedir")
435 self.slavebase = os.path.abspath("slavebase")
436 if os.path.exists(self.slavebase):
437 rmdirRecursive(self.slavebase)
438 os.mkdir("slavebase")
440 d = VCS.createRepository(self.vc_name)
441 return d
443 def connectSlave(self):
444 port = self.master.slavePort._port.getHost().port
445 slave = bot.BuildSlave("localhost", port, "bot1", "sekrit",
446 self.slavebase, keepalive=0, usePTY=1)
447 self.slave = slave
448 slave.startService()
449 d = self.master.botmaster.waitUntilBuilderAttached("vc")
450 return d
452 def loadConfig(self, config):
453 # reloading the config file causes a new 'listDirs' command to be
454 # sent to the slave. To synchronize on this properly, it is easiest
455 # to stop and restart the slave.
456 d = defer.succeed(None)
457 if self.slave:
458 d = self.master.botmaster.waitUntilBuilderDetached("vc")
459 self.slave.stopService()
460 d.addCallback(lambda res: self.master.loadConfig(config))
461 d.addCallback(lambda res: self.connectSlave())
462 return d
464 def serveHTTP(self):
465 # launch an HTTP server to serve the repository files
466 from twisted.web import static, server
467 from twisted.internet import reactor
468 self.root = static.File(self.helper.repbase)
469 self.site = server.Site(self.root)
470 self.httpServer = reactor.listenTCP(0, self.site)
471 self.httpPort = self.httpServer.getHost().port
473 def doBuild(self, shouldSucceed=True, ss=None):
474 c = interfaces.IControl(self.master)
476 if ss is None:
477 ss = SourceStamp()
478 #print "doBuild(ss: b=%s rev=%s)" % (ss.branch, ss.revision)
479 req = base.BuildRequest("test_vc forced build", ss)
480 d = req.waitUntilFinished()
481 c.getBuilder("vc").requestBuild(req)
482 d.addCallback(self._doBuild_1, shouldSucceed)
483 return d
484 def _doBuild_1(self, bs, shouldSucceed):
485 r = bs.getResults()
486 if r != SUCCESS and shouldSucceed:
487 print
488 print
489 if not bs.isFinished():
490 print "Hey, build wasn't even finished!"
491 print "Build did not succeed:", r, bs.getText()
492 for s in bs.getSteps():
493 for l in s.getLogs():
494 print "--- START step %s / log %s ---" % (s.getName(),
495 l.getName())
496 print l.getTextWithHeaders()
497 print "--- STOP ---"
498 print
499 self.fail("build did not succeed")
500 return bs
502 def touch(self, d, f):
503 open(os.path.join(d,f),"w").close()
504 def shouldExist(self, *args):
505 target = os.path.join(*args)
506 self.failUnless(os.path.exists(target),
507 "expected to find %s but didn't" % target)
508 def shouldNotExist(self, *args):
509 target = os.path.join(*args)
510 self.failIf(os.path.exists(target),
511 "expected to NOT find %s, but did" % target)
512 def shouldContain(self, d, f, contents):
513 c = open(os.path.join(d, f), "r").read()
514 self.failUnlessIn(contents, c)
516 def checkGotRevision(self, bs, expected):
517 if self.has_got_revision:
518 self.failUnlessEqual(bs.getProperty("got_revision"), expected)
520 def checkGotRevisionIsLatest(self, bs):
521 expected = self.helper.trunk[-1]
522 if self.has_got_revision_branches_are_merged:
523 expected = self.helper.allrevs[-1]
524 self.checkGotRevision(bs, expected)
526 def do_vctest(self, testRetry=True):
527 vctype = self.vctype
528 args = self.helper.vcargs
529 m = self.master
530 self.vcdir = os.path.join(self.slavebase, "vc-dir", "source")
531 self.workdir = os.path.join(self.slavebase, "vc-dir", "build")
532 # woo double-substitution
533 s = "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype,)
534 for k,v in args.items():
535 s += ", %s=%s" % (k, repr(v))
536 s += ")"
537 config = config_vc % s
539 m.loadConfig(config % 'clobber')
540 m.readConfig = True
541 m.startService()
543 d = self.connectSlave()
544 d.addCallback(lambda res: log.msg("testing clobber"))
545 d.addCallback(self._do_vctest_clobber)
546 d.addCallback(lambda res: log.msg("doing update"))
547 d.addCallback(lambda res: self.loadConfig(config % 'update'))
548 d.addCallback(lambda res: log.msg("testing update"))
549 d.addCallback(self._do_vctest_update)
550 if testRetry:
551 d.addCallback(lambda res: log.msg("testing update retry"))
552 d.addCallback(self._do_vctest_update_retry)
553 d.addCallback(lambda res: log.msg("doing copy"))
554 d.addCallback(lambda res: self.loadConfig(config % 'copy'))
555 d.addCallback(lambda res: log.msg("testing copy"))
556 d.addCallback(self._do_vctest_copy)
557 if self.metadir:
558 d.addCallback(lambda res: log.msg("doing export"))
559 d.addCallback(lambda res: self.loadConfig(config % 'export'))
560 d.addCallback(lambda res: log.msg("testing export"))
561 d.addCallback(self._do_vctest_export)
562 return d
564 def _do_vctest_clobber(self, res):
565 d = self.doBuild() # initial checkout
566 d.addCallback(self._do_vctest_clobber_1)
567 return d
568 def _do_vctest_clobber_1(self, bs):
569 self.shouldExist(self.workdir, "main.c")
570 self.shouldExist(self.workdir, "version.c")
571 self.shouldExist(self.workdir, "subdir", "subdir.c")
572 if self.metadir:
573 self.shouldExist(self.workdir, self.metadir)
574 self.failUnlessEqual(bs.getProperty("revision"), None)
575 self.failUnlessEqual(bs.getProperty("branch"), None)
576 self.checkGotRevisionIsLatest(bs)
578 self.touch(self.workdir, "newfile")
579 self.shouldExist(self.workdir, "newfile")
580 d = self.doBuild() # rebuild clobbers workdir
581 d.addCallback(self._do_vctest_clobber_2)
582 return d
583 def _do_vctest_clobber_2(self, res):
584 self.shouldNotExist(self.workdir, "newfile")
586 def _do_vctest_update(self, res):
587 log.msg("_do_vctest_update")
588 d = self.doBuild() # rebuild with update
589 d.addCallback(self._do_vctest_update_1)
590 return d
591 def _do_vctest_update_1(self, bs):
592 log.msg("_do_vctest_update_1")
593 self.shouldExist(self.workdir, "main.c")
594 self.shouldExist(self.workdir, "version.c")
595 self.shouldContain(self.workdir, "version.c",
596 "version=%d" % self.helper.version)
597 if self.metadir:
598 self.shouldExist(self.workdir, self.metadir)
599 self.failUnlessEqual(bs.getProperty("revision"), None)
600 self.checkGotRevisionIsLatest(bs)
602 self.touch(self.workdir, "newfile")
603 d = self.doBuild() # update rebuild leaves new files
604 d.addCallback(self._do_vctest_update_2)
605 return d
606 def _do_vctest_update_2(self, bs):
607 log.msg("_do_vctest_update_2")
608 self.shouldExist(self.workdir, "main.c")
609 self.shouldExist(self.workdir, "version.c")
610 self.touch(self.workdir, "newfile")
611 # now make a change to the repository and make sure we pick it up
612 d = self.helper.vc_revise()
613 d.addCallback(lambda res: self.doBuild())
614 d.addCallback(self._do_vctest_update_3)
615 return d
616 def _do_vctest_update_3(self, bs):
617 log.msg("_do_vctest_update_3")
618 self.shouldExist(self.workdir, "main.c")
619 self.shouldExist(self.workdir, "version.c")
620 self.shouldContain(self.workdir, "version.c",
621 "version=%d" % self.helper.version)
622 self.shouldExist(self.workdir, "newfile")
623 self.failUnlessEqual(bs.getProperty("revision"), None)
624 self.checkGotRevisionIsLatest(bs)
626 # now "update" to an older revision
627 d = self.doBuild(ss=SourceStamp(revision=self.helper.trunk[-2]))
628 d.addCallback(self._do_vctest_update_4)
629 return d
630 def _do_vctest_update_4(self, bs):
631 log.msg("_do_vctest_update_4")
632 self.shouldExist(self.workdir, "main.c")
633 self.shouldExist(self.workdir, "version.c")
634 self.shouldContain(self.workdir, "version.c",
635 "version=%d" % (self.helper.version-1))
636 self.failUnlessEqual(bs.getProperty("revision"),
637 self.helper.trunk[-2])
638 self.checkGotRevision(bs, self.helper.trunk[-2])
640 # now update to the newer revision
641 d = self.doBuild(ss=SourceStamp(revision=self.helper.trunk[-1]))
642 d.addCallback(self._do_vctest_update_5)
643 return d
644 def _do_vctest_update_5(self, bs):
645 log.msg("_do_vctest_update_5")
646 self.shouldExist(self.workdir, "main.c")
647 self.shouldExist(self.workdir, "version.c")
648 self.shouldContain(self.workdir, "version.c",
649 "version=%d" % self.helper.version)
650 self.failUnlessEqual(bs.getProperty("revision"),
651 self.helper.trunk[-1])
652 self.checkGotRevision(bs, self.helper.trunk[-1])
655 def _do_vctest_update_retry(self, res):
656 # certain local changes will prevent an update from working. The
657 # most common is to replace a file with a directory, or vice
658 # versa. The slave code should spot the failure and do a
659 # clobber/retry.
660 os.unlink(os.path.join(self.workdir, "main.c"))
661 os.mkdir(os.path.join(self.workdir, "main.c"))
662 self.touch(os.path.join(self.workdir, "main.c"), "foo")
663 self.touch(self.workdir, "newfile")
665 d = self.doBuild() # update, but must clobber to handle the error
666 d.addCallback(self._do_vctest_update_retry_1)
667 return d
668 def _do_vctest_update_retry_1(self, bs):
669 self.shouldNotExist(self.workdir, "newfile")
671 def _do_vctest_copy(self, res):
672 d = self.doBuild() # copy rebuild clobbers new files
673 d.addCallback(self._do_vctest_copy_1)
674 return d
675 def _do_vctest_copy_1(self, bs):
676 if self.metadir:
677 self.shouldExist(self.workdir, self.metadir)
678 self.shouldNotExist(self.workdir, "newfile")
679 self.touch(self.workdir, "newfile")
680 self.touch(self.vcdir, "newvcfile")
681 self.failUnlessEqual(bs.getProperty("revision"), None)
682 self.checkGotRevisionIsLatest(bs)
684 d = self.doBuild() # copy rebuild clobbers new files
685 d.addCallback(self._do_vctest_copy_2)
686 return d
687 def _do_vctest_copy_2(self, bs):
688 if self.metadir:
689 self.shouldExist(self.workdir, self.metadir)
690 self.shouldNotExist(self.workdir, "newfile")
691 self.shouldExist(self.vcdir, "newvcfile")
692 self.shouldExist(self.workdir, "newvcfile")
693 self.failUnlessEqual(bs.getProperty("revision"), None)
694 self.checkGotRevisionIsLatest(bs)
695 self.touch(self.workdir, "newfile")
697 def _do_vctest_export(self, res):
698 d = self.doBuild() # export rebuild clobbers new files
699 d.addCallback(self._do_vctest_export_1)
700 return d
701 def _do_vctest_export_1(self, bs):
702 self.shouldNotExist(self.workdir, self.metadir)
703 self.shouldNotExist(self.workdir, "newfile")
704 self.failUnlessEqual(bs.getProperty("revision"), None)
705 #self.checkGotRevisionIsLatest(bs)
706 # VC 'export' is not required to have a got_revision
707 self.touch(self.workdir, "newfile")
709 d = self.doBuild() # export rebuild clobbers new files
710 d.addCallback(self._do_vctest_export_2)
711 return d
712 def _do_vctest_export_2(self, bs):
713 self.shouldNotExist(self.workdir, self.metadir)
714 self.shouldNotExist(self.workdir, "newfile")
715 self.failUnlessEqual(bs.getProperty("revision"), None)
716 #self.checkGotRevisionIsLatest(bs)
717 # VC 'export' is not required to have a got_revision
719 def do_patch(self):
720 vctype = self.vctype
721 args = self.helper.vcargs
722 m = self.master
723 self.vcdir = os.path.join(self.slavebase, "vc-dir", "source")
724 self.workdir = os.path.join(self.slavebase, "vc-dir", "build")
725 s = "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype,)
726 for k,v in args.items():
727 s += ", %s=%s" % (k, repr(v))
728 s += ")"
729 self.config = config_vc % s
731 m.loadConfig(self.config % "clobber")
732 m.readConfig = True
733 m.startService()
735 ss = SourceStamp(revision=self.helper.trunk[-1], patch=(0, p0_diff))
737 d = self.connectSlave()
738 d.addCallback(lambda res: self.doBuild(ss=ss))
739 d.addCallback(self._doPatch_1)
740 return d
741 def _doPatch_1(self, bs):
742 self.shouldContain(self.workdir, "version.c",
743 "version=%d" % self.helper.version)
744 # make sure the file actually got patched
745 subdir_c = os.path.join(self.slavebase, "vc-dir", "build",
746 "subdir", "subdir.c")
747 data = open(subdir_c, "r").read()
748 self.failUnlessIn("Hello patched subdir.\\n", data)
749 self.failUnlessEqual(bs.getProperty("revision"),
750 self.helper.trunk[-1])
751 self.checkGotRevision(bs, self.helper.trunk[-1])
753 # make sure that a rebuild does not use the leftover patched workdir
754 d = self.master.loadConfig(self.config % "update")
755 d.addCallback(lambda res: self.doBuild(ss=None))
756 d.addCallback(self._doPatch_2)
757 return d
758 def _doPatch_2(self, bs):
759 # make sure the file is back to its original
760 subdir_c = os.path.join(self.slavebase, "vc-dir", "build",
761 "subdir", "subdir.c")
762 data = open(subdir_c, "r").read()
763 self.failUnlessIn("Hello subdir.\\n", data)
764 self.failUnlessEqual(bs.getProperty("revision"), None)
765 self.checkGotRevisionIsLatest(bs)
767 # now make sure we can patch an older revision. We need at least two
768 # revisions here, so we might have to create one first
769 if len(self.helper.trunk) < 2:
770 d = self.helper.vc_revise()
771 d.addCallback(self._doPatch_3)
772 return d
773 return self._doPatch_3()
775 def _doPatch_3(self, res=None):
776 ss = SourceStamp(revision=self.helper.trunk[-2], patch=(0, p0_diff))
777 d = self.doBuild(ss=ss)
778 d.addCallback(self._doPatch_4)
779 return d
780 def _doPatch_4(self, bs):
781 self.shouldContain(self.workdir, "version.c",
782 "version=%d" % (self.helper.version-1))
783 # and make sure the file actually got patched
784 subdir_c = os.path.join(self.slavebase, "vc-dir", "build",
785 "subdir", "subdir.c")
786 data = open(subdir_c, "r").read()
787 self.failUnlessIn("Hello patched subdir.\\n", data)
788 self.failUnlessEqual(bs.getProperty("revision"),
789 self.helper.trunk[-2])
790 self.checkGotRevision(bs, self.helper.trunk[-2])
792 # now check that we can patch a branch
793 ss = SourceStamp(branch=self.helper.branchname,
794 revision=self.helper.branch[-1],
795 patch=(0, p0_diff))
796 d = self.doBuild(ss=ss)
797 d.addCallback(self._doPatch_5)
798 return d
799 def _doPatch_5(self, bs):
800 self.shouldContain(self.workdir, "version.c",
801 "version=%d" % 1)
802 self.shouldContain(self.workdir, "main.c", "Hello branch.")
803 subdir_c = os.path.join(self.slavebase, "vc-dir", "build",
804 "subdir", "subdir.c")
805 data = open(subdir_c, "r").read()
806 self.failUnlessIn("Hello patched subdir.\\n", data)
807 self.failUnlessEqual(bs.getProperty("revision"),
808 self.helper.branch[-1])
809 self.failUnlessEqual(bs.getProperty("branch"), self.helper.branchname)
810 self.checkGotRevision(bs, self.helper.branch[-1])
813 def do_vctest_once(self, shouldSucceed):
814 m = self.master
815 vctype = self.vctype
816 args = self.helper.vcargs
817 vcdir = os.path.join(self.slavebase, "vc-dir", "source")
818 workdir = os.path.join(self.slavebase, "vc-dir", "build")
819 # woo double-substitution
820 s = "s(%s, timeout=200, workdir='build', mode='clobber'" % (vctype,)
821 for k,v in args.items():
822 s += ", %s=%s" % (k, repr(v))
823 s += ")"
824 config = config_vc % s
826 m.loadConfig(config)
827 m.readConfig = True
828 m.startService()
830 self.connectSlave()
831 d = self.doBuild(shouldSucceed) # initial checkout
832 return d
834 def do_branch(self):
835 log.msg("do_branch")
836 vctype = self.vctype
837 args = self.helper.vcargs
838 m = self.master
839 self.vcdir = os.path.join(self.slavebase, "vc-dir", "source")
840 self.workdir = os.path.join(self.slavebase, "vc-dir", "build")
841 s = "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype,)
842 for k,v in args.items():
843 s += ", %s=%s" % (k, repr(v))
844 s += ")"
845 self.config = config_vc % s
847 m.loadConfig(self.config % "update")
848 m.readConfig = True
849 m.startService()
851 # first we do a build of the trunk
852 d = self.connectSlave()
853 d.addCallback(lambda res: self.doBuild(ss=SourceStamp()))
854 d.addCallback(self._doBranch_1)
855 return d
856 def _doBranch_1(self, bs):
857 log.msg("_doBranch_1")
858 # make sure the checkout was of the trunk
859 main_c = os.path.join(self.slavebase, "vc-dir", "build", "main.c")
860 data = open(main_c, "r").read()
861 self.failUnlessIn("Hello world.", data)
863 # now do a checkout on the branch. The change in branch name should
864 # trigger a clobber.
865 self.touch(self.workdir, "newfile")
866 d = self.doBuild(ss=SourceStamp(branch=self.helper.branchname))
867 d.addCallback(self._doBranch_2)
868 return d
869 def _doBranch_2(self, bs):
870 log.msg("_doBranch_2")
871 # make sure it was on the branch
872 main_c = os.path.join(self.slavebase, "vc-dir", "build", "main.c")
873 data = open(main_c, "r").read()
874 self.failUnlessIn("Hello branch.", data)
875 # and make sure the tree was clobbered
876 self.shouldNotExist(self.workdir, "newfile")
878 # doing another build on the same branch should not clobber the tree
879 self.touch(self.workdir, "newbranchfile")
880 d = self.doBuild(ss=SourceStamp(branch=self.helper.branchname))
881 d.addCallback(self._doBranch_3)
882 return d
883 def _doBranch_3(self, bs):
884 log.msg("_doBranch_3")
885 # make sure it is still on the branch
886 main_c = os.path.join(self.slavebase, "vc-dir", "build", "main.c")
887 data = open(main_c, "r").read()
888 self.failUnlessIn("Hello branch.", data)
889 # and make sure the tree was not clobbered
890 self.shouldExist(self.workdir, "newbranchfile")
892 # now make sure that a non-branch checkout clobbers the tree
893 d = self.doBuild(ss=SourceStamp())
894 d.addCallback(self._doBranch_4)
895 return d
896 def _doBranch_4(self, bs):
897 log.msg("_doBranch_4")
898 # make sure it was on the trunk
899 main_c = os.path.join(self.slavebase, "vc-dir", "build", "main.c")
900 data = open(main_c, "r").read()
901 self.failUnlessIn("Hello world.", data)
902 self.shouldNotExist(self.workdir, "newbranchfile")
904 def do_getpatch(self, doBranch=True):
905 log.msg("do_getpatch")
906 # prepare a buildslave to do checkouts
907 vctype = self.vctype
908 args = self.helper.vcargs
909 m = self.master
910 self.vcdir = os.path.join(self.slavebase, "vc-dir", "source")
911 self.workdir = os.path.join(self.slavebase, "vc-dir", "build")
912 # woo double-substitution
913 s = "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype,)
914 for k,v in args.items():
915 s += ", %s=%s" % (k, repr(v))
916 s += ")"
917 config = config_vc % s
919 m.loadConfig(config % 'clobber')
920 m.readConfig = True
921 m.startService()
923 d = self.connectSlave()
925 # then set up the "developer's tree". first we modify a tree from the
926 # head of the trunk
927 tmpdir = "try_workdir"
928 self.trydir = os.path.join(self.helper.repbase, tmpdir)
929 rmdirRecursive(self.trydir)
930 d.addCallback(self.do_getpatch_trunkhead)
931 d.addCallback(self.do_getpatch_trunkold)
932 if doBranch:
933 d.addCallback(self.do_getpatch_branch)
934 d.addCallback(self.do_getpatch_finish)
935 return d
937 def do_getpatch_finish(self, res):
938 log.msg("do_getpatch_finish")
939 self.helper.vc_try_finish(self.trydir)
940 return res
942 def try_shouldMatch(self, filename):
943 devfilename = os.path.join(self.trydir, filename)
944 devfile = open(devfilename, "r").read()
945 slavefilename = os.path.join(self.workdir, filename)
946 slavefile = open(slavefilename, "r").read()
947 self.failUnlessEqual(devfile, slavefile,
948 ("slavefile (%s) contains '%s'. "
949 "developer's file (%s) contains '%s'. "
950 "These ought to match") %
951 (slavefilename, slavefile,
952 devfilename, devfile))
954 def do_getpatch_trunkhead(self, res):
955 log.msg("do_getpatch_trunkhead")
956 d = self.helper.vc_try_checkout(self.trydir, self.helper.trunk[-1])
957 d.addCallback(self._do_getpatch_trunkhead_1)
958 return d
959 def _do_getpatch_trunkhead_1(self, res):
960 log.msg("_do_getpatch_trunkhead_1")
961 d = tryclient.getSourceStamp(self.vctype_try, self.trydir, None)
962 d.addCallback(self._do_getpatch_trunkhead_2)
963 return d
964 def _do_getpatch_trunkhead_2(self, ss):
965 log.msg("_do_getpatch_trunkhead_2")
966 d = self.doBuild(ss=ss)
967 d.addCallback(self._do_getpatch_trunkhead_3)
968 return d
969 def _do_getpatch_trunkhead_3(self, res):
970 log.msg("_do_getpatch_trunkhead_3")
971 # verify that the resulting buildslave tree matches the developer's
972 self.try_shouldMatch("main.c")
973 self.try_shouldMatch("version.c")
974 self.try_shouldMatch(os.path.join("subdir", "subdir.c"))
976 def do_getpatch_trunkold(self, res):
977 log.msg("do_getpatch_trunkold")
978 # now try a tree from an older revision. We need at least two
979 # revisions here, so we might have to create one first
980 if len(self.helper.trunk) < 2:
981 d = self.helper.vc_revise()
982 d.addCallback(self._do_getpatch_trunkold_1)
983 return d
984 return self._do_getpatch_trunkold_1()
985 def _do_getpatch_trunkold_1(self, res=None):
986 log.msg("_do_getpatch_trunkold_1")
987 d = self.helper.vc_try_checkout(self.trydir, self.helper.trunk[-2])
988 d.addCallback(self._do_getpatch_trunkold_2)
989 return d
990 def _do_getpatch_trunkold_2(self, res):
991 log.msg("_do_getpatch_trunkold_2")
992 d = tryclient.getSourceStamp(self.vctype_try, self.trydir, None)
993 d.addCallback(self._do_getpatch_trunkold_3)
994 return d
995 def _do_getpatch_trunkold_3(self, ss):
996 log.msg("_do_getpatch_trunkold_3")
997 d = self.doBuild(ss=ss)
998 d.addCallback(self._do_getpatch_trunkold_4)
999 return d
1000 def _do_getpatch_trunkold_4(self, res):
1001 log.msg("_do_getpatch_trunkold_4")
1002 # verify that the resulting buildslave tree matches the developer's
1003 self.try_shouldMatch("main.c")
1004 self.try_shouldMatch("version.c")
1005 self.try_shouldMatch(os.path.join("subdir", "subdir.c"))
1007 def do_getpatch_branch(self, res):
1008 log.msg("do_getpatch_branch")
1009 # now try a tree from a branch
1010 d = self.helper.vc_try_checkout(self.trydir, self.helper.branch[-1],
1011 self.helper.branchname)
1012 d.addCallback(self._do_getpatch_branch_1)
1013 return d
1014 def _do_getpatch_branch_1(self, res):
1015 log.msg("_do_getpatch_branch_1")
1016 d = tryclient.getSourceStamp(self.vctype_try, self.trydir,
1017 self.helper.try_branchname)
1018 d.addCallback(self._do_getpatch_branch_2)
1019 return d
1020 def _do_getpatch_branch_2(self, ss):
1021 log.msg("_do_getpatch_branch_2")
1022 d = self.doBuild(ss=ss)
1023 d.addCallback(self._do_getpatch_branch_3)
1024 return d
1025 def _do_getpatch_branch_3(self, res):
1026 log.msg("_do_getpatch_branch_3")
1027 # verify that the resulting buildslave tree matches the developer's
1028 self.try_shouldMatch("main.c")
1029 self.try_shouldMatch("version.c")
1030 self.try_shouldMatch(os.path.join("subdir", "subdir.c"))
1033 def dumpPatch(self, patch):
1034 # this exists to help me figure out the right 'patchlevel' value
1035 # should be returned by tryclient.getSourceStamp
1036 n = self.mktemp()
1037 open(n,"w").write(patch)
1038 d = self.runCommand(".", ["lsdiff", n])
1039 def p(res): print "lsdiff:", res.strip().split("\n")
1040 d.addCallback(p)
1041 return d
1044 def tearDown(self):
1045 d = defer.succeed(None)
1046 if self.slave:
1047 d2 = self.master.botmaster.waitUntilBuilderDetached("vc")
1048 d.addCallback(lambda res: self.slave.stopService())
1049 d.addCallback(lambda res: d2)
1050 if self.master:
1051 d.addCallback(lambda res: self.master.stopService())
1052 if self.httpServer:
1053 d.addCallback(lambda res: self.httpServer.stopListening())
1054 def stopHTTPTimer():
1055 try:
1056 from twisted.web import http # Twisted-2.0
1057 except ImportError:
1058 from twisted.protocols import http # Twisted-1.3
1059 http._logDateTimeStop() # shut down the internal timer. DUMB!
1060 d.addCallback(lambda res: stopHTTPTimer())
1061 d.addCallback(lambda res: self.tearDown2())
1062 return maybeWait(d)
1064 def tearDown2(self):
1065 pass
1067 class CVSHelper(BaseHelper):
1068 branchname = "branch"
1069 try_branchname = "branch"
1071 def capable(self):
1072 cvspaths = which('cvs')
1073 if not cvspaths:
1074 return (False, "CVS is not installed")
1075 # cvs-1.10 (as shipped with OS-X 10.3 "Panther") is too old for this
1076 # test. There is a situation where we check out a tree, make a
1077 # change, then commit it back, and CVS refuses to believe that we're
1078 # operating in a CVS tree. I tested cvs-1.12.9 and it works ok, OS-X
1079 # 10.4 "Tiger" comes with cvs-1.11, but I haven't tested that yet.
1080 # For now, skip the tests if we've got 1.10 .
1081 log.msg("running %s --version.." % (cvspaths[0],))
1082 d = utils.getProcessOutput(cvspaths[0], ["--version"],
1083 env=os.environ)
1084 d.addCallback(self._capable, cvspaths[0])
1085 return d
1087 def _capable(self, v, vcexe):
1088 m = re.search(r'\(CVS\) ([\d\.]+) ', v)
1089 if not m:
1090 log.msg("couldn't identify CVS version number in output:")
1091 log.msg("'''%s'''" % v)
1092 log.msg("skipping tests")
1093 return (False, "Found CVS but couldn't identify its version")
1094 ver = m.group(1)
1095 log.msg("found CVS version '%s'" % ver)
1096 if ver == "1.10":
1097 return (False, "Found CVS, but it is too old")
1098 self.vcexe = vcexe
1099 return (True, None)
1101 def getdate(self):
1102 # this timestamp is eventually passed to CVS in a -D argument, and
1103 # strftime's %z specifier doesn't seem to work reliably (I get +0000
1104 # where I should get +0700 under linux sometimes, and windows seems
1105 # to want to put a verbose 'Eastern Standard Time' in there), so
1106 # leave off the timezone specifier and treat this as localtime. A
1107 # valid alternative would be to use a hard-coded +0000 and
1108 # time.gmtime().
1109 return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
1111 def createRepository(self):
1112 self.createBasedir()
1113 self.cvsrep = cvsrep = os.path.join(self.repbase, "CVS-Repository")
1114 tmp = os.path.join(self.repbase, "cvstmp")
1116 w = self.dovc(self.repbase, "-d %s init" % cvsrep)
1117 yield w; w.getResult() # we must getResult() to raise any exceptions
1119 self.populate(tmp)
1120 cmd = ("-d %s import" % cvsrep +
1121 " -m sample_project_files sample vendortag start")
1122 w = self.dovc(tmp, cmd)
1123 yield w; w.getResult()
1124 rmdirRecursive(tmp)
1125 # take a timestamp as the first revision number
1126 time.sleep(2)
1127 self.addTrunkRev(self.getdate())
1128 time.sleep(2)
1130 w = self.dovc(self.repbase,
1131 "-d %s checkout -d cvstmp sample" % self.cvsrep)
1132 yield w; w.getResult()
1134 w = self.dovc(tmp, "tag -b %s" % self.branchname)
1135 yield w; w.getResult()
1136 self.populate_branch(tmp)
1137 w = self.dovc(tmp,
1138 "commit -m commit_on_branch -r %s" % self.branchname)
1139 yield w; w.getResult()
1140 rmdirRecursive(tmp)
1141 time.sleep(2)
1142 self.addBranchRev(self.getdate())
1143 time.sleep(2)
1144 self.vcargs = { 'cvsroot': self.cvsrep, 'cvsmodule': "sample" }
1145 createRepository = deferredGenerator(createRepository)
1148 def vc_revise(self):
1149 tmp = os.path.join(self.repbase, "cvstmp")
1151 w = self.dovc(self.repbase,
1152 "-d %s checkout -d cvstmp sample" % self.cvsrep)
1153 yield w; w.getResult()
1154 self.version += 1
1155 version_c = VERSION_C % self.version
1156 open(os.path.join(tmp, "version.c"), "w").write(version_c)
1157 w = self.dovc(tmp,
1158 "commit -m revised_to_%d version.c" % self.version)
1159 yield w; w.getResult()
1160 rmdirRecursive(tmp)
1161 time.sleep(2)
1162 self.addTrunkRev(self.getdate())
1163 time.sleep(2)
1164 vc_revise = deferredGenerator(vc_revise)
1166 def vc_try_checkout(self, workdir, rev, branch=None):
1167 # 'workdir' is an absolute path
1168 assert os.path.abspath(workdir) == workdir
1169 cmd = [self.vcexe, "-d", self.cvsrep, "checkout",
1170 "-d", workdir,
1171 "-D", rev]
1172 if branch is not None:
1173 cmd.append("-r")
1174 cmd.append(branch)
1175 cmd.append("sample")
1176 w = self.do(self.repbase, cmd)
1177 yield w; w.getResult()
1178 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
1179 vc_try_checkout = deferredGenerator(vc_try_checkout)
1181 def vc_try_finish(self, workdir):
1182 rmdirRecursive(workdir)
1184 class CVS(VCBase, unittest.TestCase):
1185 vc_name = "cvs"
1187 metadir = "CVS"
1188 vctype = "source.CVS"
1189 vctype_try = "cvs"
1190 # CVS gives us got_revision, but it is based entirely upon the local
1191 # clock, which means it is unlikely to match the timestamp taken earlier.
1192 # This might be enough for common use, but won't be good enough for our
1193 # tests to accept, so pretend it doesn't have got_revision at all.
1194 has_got_revision = False
1196 def testCheckout(self):
1197 d = self.do_vctest()
1198 return maybeWait(d)
1200 def testPatch(self):
1201 d = self.do_patch()
1202 return maybeWait(d)
1204 def testCheckoutBranch(self):
1205 d = self.do_branch()
1206 return maybeWait(d)
1208 def testTry(self):
1209 d = self.do_getpatch(doBranch=False)
1210 return maybeWait(d)
1212 VCS.registerVC(CVS.vc_name, CVSHelper())
1215 class SVNHelper(BaseHelper):
1216 branchname = "sample/branch"
1217 try_branchname = "sample/branch"
1219 def capable(self):
1220 svnpaths = which('svn')
1221 svnadminpaths = which('svnadmin')
1222 if not svnpaths:
1223 return (False, "SVN is not installed")
1224 if not svnadminpaths:
1225 return (False, "svnadmin is not installed")
1226 # we need svn to be compiled with the ra_local access
1227 # module
1228 log.msg("running svn --version..")
1229 env = os.environ.copy()
1230 env['LC_ALL'] = "C"
1231 d = utils.getProcessOutput(svnpaths[0], ["--version"],
1232 env=env)
1233 d.addCallback(self._capable, svnpaths[0], svnadminpaths[0])
1234 return d
1236 def _capable(self, v, vcexe, svnadmin):
1237 if v.find("handles 'file' schem") != -1:
1238 # older versions say 'schema', 1.2.0 and beyond say 'scheme'
1239 self.vcexe = vcexe
1240 self.svnadmin = svnadmin
1241 return (True, None)
1242 excuse = ("%s found but it does not support 'file:' " +
1243 "schema, skipping svn tests") % vcexe
1244 log.msg(excuse)
1245 return (False, excuse)
1247 def createRepository(self):
1248 self.createBasedir()
1249 self.svnrep = os.path.join(self.repbase,
1250 "SVN-Repository").replace('\\','/')
1251 tmp = os.path.join(self.repbase, "svntmp")
1252 if sys.platform == 'win32':
1253 # On Windows Paths do not start with a /
1254 self.svnurl = "file:///%s" % self.svnrep
1255 else:
1256 self.svnurl = "file://%s" % self.svnrep
1257 self.svnurl_trunk = self.svnurl + "/sample/trunk"
1258 self.svnurl_branch = self.svnurl + "/sample/branch"
1260 w = self.do(self.repbase, self.svnadmin+" create %s" % self.svnrep)
1261 yield w; w.getResult()
1263 self.populate(tmp)
1264 w = self.dovc(tmp,
1265 "import -m sample_project_files %s" %
1266 self.svnurl_trunk)
1267 yield w; out = w.getResult()
1268 rmdirRecursive(tmp)
1269 m = re.search(r'Committed revision (\d+)\.', out)
1270 assert m.group(1) == "1" # first revision is always "1"
1271 self.addTrunkRev(int(m.group(1)))
1273 w = self.dovc(self.repbase,
1274 "checkout %s svntmp" % self.svnurl_trunk)
1275 yield w; w.getResult()
1277 w = self.dovc(tmp, "cp -m make_branch %s %s" % (self.svnurl_trunk,
1278 self.svnurl_branch))
1279 yield w; w.getResult()
1280 w = self.dovc(tmp, "switch %s" % self.svnurl_branch)
1281 yield w; w.getResult()
1282 self.populate_branch(tmp)
1283 w = self.dovc(tmp, "commit -m commit_on_branch")
1284 yield w; out = w.getResult()
1285 rmdirRecursive(tmp)
1286 m = re.search(r'Committed revision (\d+)\.', out)
1287 self.addBranchRev(int(m.group(1)))
1288 createRepository = deferredGenerator(createRepository)
1290 def vc_revise(self):
1291 tmp = os.path.join(self.repbase, "svntmp")
1292 rmdirRecursive(tmp)
1293 log.msg("vc_revise" + self.svnurl_trunk)
1294 w = self.dovc(self.repbase,
1295 "checkout %s svntmp" % self.svnurl_trunk)
1296 yield w; w.getResult()
1297 self.version += 1
1298 version_c = VERSION_C % self.version
1299 open(os.path.join(tmp, "version.c"), "w").write(version_c)
1300 w = self.dovc(tmp, "commit -m revised_to_%d" % self.version)
1301 yield w; out = w.getResult()
1302 m = re.search(r'Committed revision (\d+)\.', out)
1303 self.addTrunkRev(int(m.group(1)))
1304 rmdirRecursive(tmp)
1305 vc_revise = deferredGenerator(vc_revise)
1307 def vc_try_checkout(self, workdir, rev, branch=None):
1308 assert os.path.abspath(workdir) == workdir
1309 if os.path.exists(workdir):
1310 rmdirRecursive(workdir)
1311 if not branch:
1312 svnurl = self.svnurl_trunk
1313 else:
1314 # N.B.: this is *not* os.path.join: SVN URLs use slashes
1315 # regardless of the host operating system's filepath separator
1316 svnurl = self.svnurl + "/" + branch
1317 w = self.dovc(self.repbase,
1318 "checkout %s %s" % (svnurl, workdir))
1319 yield w; w.getResult()
1320 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
1321 vc_try_checkout = deferredGenerator(vc_try_checkout)
1323 def vc_try_finish(self, workdir):
1324 rmdirRecursive(workdir)
1327 class SVN(VCBase, unittest.TestCase):
1328 vc_name = "svn"
1330 metadir = ".svn"
1331 vctype = "source.SVN"
1332 vctype_try = "svn"
1333 has_got_revision = True
1334 has_got_revision_branches_are_merged = True
1336 def testCheckout(self):
1337 # we verify this one with the svnurl style of vcargs. We test the
1338 # baseURL/defaultBranch style in testPatch and testCheckoutBranch.
1339 self.helper.vcargs = { 'svnurl': self.helper.svnurl_trunk }
1340 d = self.do_vctest()
1341 return maybeWait(d)
1343 def testPatch(self):
1344 self.helper.vcargs = { 'baseURL': self.helper.svnurl + "/",
1345 'defaultBranch': "sample/trunk",
1347 d = self.do_patch()
1348 return maybeWait(d)
1350 def testCheckoutBranch(self):
1351 self.helper.vcargs = { 'baseURL': self.helper.svnurl + "/",
1352 'defaultBranch': "sample/trunk",
1354 d = self.do_branch()
1355 return maybeWait(d)
1357 def testTry(self):
1358 # extract the base revision and patch from a modified tree, use it to
1359 # create the same contents on the buildslave
1360 self.helper.vcargs = { 'baseURL': self.helper.svnurl + "/",
1361 'defaultBranch': "sample/trunk",
1363 d = self.do_getpatch()
1364 return maybeWait(d)
1366 VCS.registerVC(SVN.vc_name, SVNHelper())
1369 class P4Helper(BaseHelper):
1370 branchname = "branch"
1371 p4port = 'localhost:1666'
1372 pid = None
1373 base_descr = 'Change: new\nDescription: asdf\nFiles:\n'
1375 def capable(self):
1376 p4paths = which('p4')
1377 p4dpaths = which('p4d')
1378 if not p4paths:
1379 return (False, "p4 is not installed")
1380 if not p4dpaths:
1381 return (False, "p4d is not installed")
1382 self.vcexe = p4paths[0]
1383 self.p4dexe = p4dpaths[0]
1384 return (True, None)
1386 class _P4DProtocol(protocol.ProcessProtocol):
1387 def __init__(self):
1388 self.started = defer.Deferred()
1389 self.ended = defer.Deferred()
1391 def outReceived(self, data):
1392 # When it says starting, it has bound to the socket.
1393 if self.started:
1394 if data.startswith('Perforce Server starting...'):
1395 self.started.callback(None)
1396 else:
1397 print "p4d said %r" % data
1398 try:
1399 raise Exception('p4d said %r' % data)
1400 except:
1401 self.started.errback(failure.Failure())
1402 self.started = None
1404 def errReceived(self, data):
1405 print "p4d stderr: %s" % data
1407 def processEnded(self, status_object):
1408 if status_object.check(error.ProcessDone):
1409 self.ended.callback(None)
1410 else:
1411 self.ended.errback(status_object)
1413 def _start_p4d(self):
1414 proto = self._P4DProtocol()
1415 reactor.spawnProcess(proto, self.p4dexe, ['p4d', '-p', self.p4port],
1416 env=os.environ, path=self.p4rep)
1417 return proto.started, proto.ended
1419 def dop4(self, basedir, command, failureIsOk=False, stdin=None):
1420 # p4 looks at $PWD instead of getcwd(), which causes confusion when
1421 # we spawn commands without an intervening shell (sh -c). We can
1422 # override this with a -d argument.
1423 command = "-p %s -d %s %s" % (self.p4port, basedir, command)
1424 return self.dovc(basedir, command, failureIsOk, stdin)
1426 def createRepository(self):
1427 # this is only called once per VC system, so start p4d here.
1429 self.createBasedir()
1430 tmp = os.path.join(self.repbase, "p4tmp")
1431 self.p4rep = os.path.join(self.repbase, 'P4-Repository')
1432 os.mkdir(self.p4rep)
1434 # Launch p4d.
1435 started, self.p4d_shutdown = self._start_p4d()
1436 w = waitForDeferred(started)
1437 yield w; w.getResult()
1439 # Create client spec.
1440 os.mkdir(tmp)
1441 clispec = 'Client: creator\n'
1442 clispec += 'Root: %s\n' % tmp
1443 clispec += 'View:\n'
1444 clispec += '\t//depot/... //creator/...\n'
1445 w = self.dop4(tmp, 'client -i', stdin=clispec)
1446 yield w; w.getResult()
1448 # Create first rev (trunk).
1449 self.populate(os.path.join(tmp, 'trunk'))
1450 files = ['main.c', 'version.c', 'subdir/subdir.c']
1451 w = self.dop4(tmp, "-c creator add "
1452 + " ".join(['trunk/%s' % f for f in files]))
1453 yield w; w.getResult()
1454 descr = self.base_descr
1455 for file in files:
1456 descr += '\t//depot/trunk/%s\n' % file
1457 w = self.dop4(tmp, "-c creator submit -i", stdin=descr)
1458 yield w; out = w.getResult()
1459 m = re.search(r'Change (\d+) submitted.', out)
1460 assert m.group(1) == '1'
1461 self.addTrunkRev(m.group(1))
1463 # Create second rev (branch).
1464 w = self.dop4(tmp, '-c creator integrate '
1465 + '//depot/trunk/... //depot/branch/...')
1466 yield w; w.getResult()
1467 w = self.dop4(tmp, "-c creator edit branch/main.c")
1468 yield w; w.getResult()
1469 self.populate_branch(os.path.join(tmp, 'branch'))
1470 descr = self.base_descr
1471 for file in files:
1472 descr += '\t//depot/branch/%s\n' % file
1473 w = self.dop4(tmp, "-c creator submit -i", stdin=descr)
1474 yield w; out = w.getResult()
1475 m = re.search(r'Change (\d+) submitted.', out)
1476 self.addBranchRev(m.group(1))
1477 createRepository = deferredGenerator(createRepository)
1479 def vc_revise(self):
1480 tmp = os.path.join(self.repbase, "p4tmp")
1481 self.version += 1
1482 version_c = VERSION_C % self.version
1483 w = self.dop4(tmp, '-c creator edit trunk/version.c')
1484 yield w; w.getResult()
1485 open(os.path.join(tmp, "trunk/version.c"), "w").write(version_c)
1486 descr = self.base_descr + '\t//depot/trunk/version.c\n'
1487 w = self.dop4(tmp, "-c creator submit -i", stdin=descr)
1488 yield w; out = w.getResult()
1489 m = re.search(r'Change (\d+) submitted.', out)
1490 self.addTrunkRev(m.group(1))
1491 vc_revise = deferredGenerator(vc_revise)
1493 def shutdown_p4d(self):
1494 d = self.runCommand(self.repbase, '%s -p %s admin stop'
1495 % (self.vcexe, self.p4port))
1496 return d.addCallback(lambda _: self.p4d_shutdown)
1498 class P4(VCBase, unittest.TestCase):
1499 metadir = None
1500 vctype = "source.P4"
1501 vc_name = "p4"
1503 def tearDownClass(self):
1504 if self.helper:
1505 return maybeWait(self.helper.shutdown_p4d())
1507 def testCheckout(self):
1508 self.helper.vcargs = { 'p4port': self.helper.p4port,
1509 'p4base': '//depot/',
1510 'defaultBranch': 'trunk' }
1511 d = self.do_vctest(testRetry=False)
1512 # TODO: like arch and darcs, sync does nothing when server is not
1513 # changed.
1514 return maybeWait(d)
1516 def testCheckoutBranch(self):
1517 self.helper.vcargs = { 'p4port': self.helper.p4port,
1518 'p4base': '//depot/',
1519 'defaultBranch': 'trunk' }
1520 d = self.do_branch()
1521 return maybeWait(d)
1523 def testPatch(self):
1524 self.helper.vcargs = { 'p4port': self.helper.p4port,
1525 'p4base': '//depot/',
1526 'defaultBranch': 'trunk' }
1527 d = self.do_patch()
1528 return maybeWait(d)
1530 VCS.registerVC(P4.vc_name, P4Helper())
1533 class DarcsHelper(BaseHelper):
1534 branchname = "branch"
1535 try_branchname = "branch"
1537 def capable(self):
1538 darcspaths = which('darcs')
1539 if not darcspaths:
1540 return (False, "Darcs is not installed")
1541 self.vcexe = darcspaths[0]
1542 return (True, None)
1544 def createRepository(self):
1545 self.createBasedir()
1546 self.darcs_base = os.path.join(self.repbase, "Darcs-Repository")
1547 self.rep_trunk = os.path.join(self.darcs_base, "trunk")
1548 self.rep_branch = os.path.join(self.darcs_base, "branch")
1549 tmp = os.path.join(self.repbase, "darcstmp")
1551 os.makedirs(self.rep_trunk)
1552 w = self.dovc(self.rep_trunk, ["initialize"])
1553 yield w; w.getResult()
1554 os.makedirs(self.rep_branch)
1555 w = self.dovc(self.rep_branch, ["initialize"])
1556 yield w; w.getResult()
1558 self.populate(tmp)
1559 w = self.dovc(tmp, qw("initialize"))
1560 yield w; w.getResult()
1561 w = self.dovc(tmp, qw("add -r ."))
1562 yield w; w.getResult()
1563 w = self.dovc(tmp, qw("record -a -m initial_import --skip-long-comment -A test@buildbot.sf.net"))
1564 yield w; w.getResult()
1565 w = self.dovc(tmp, ["push", "-a", self.rep_trunk])
1566 yield w; w.getResult()
1567 w = self.dovc(tmp, qw("changes --context"))
1568 yield w; out = w.getResult()
1569 self.addTrunkRev(out)
1571 self.populate_branch(tmp)
1572 w = self.dovc(tmp, qw("record -a --ignore-times -m commit_on_branch --skip-long-comment -A test@buildbot.sf.net"))
1573 yield w; w.getResult()
1574 w = self.dovc(tmp, ["push", "-a", self.rep_branch])
1575 yield w; w.getResult()
1576 w = self.dovc(tmp, qw("changes --context"))
1577 yield w; out = w.getResult()
1578 self.addBranchRev(out)
1579 rmdirRecursive(tmp)
1580 createRepository = deferredGenerator(createRepository)
1582 def vc_revise(self):
1583 tmp = os.path.join(self.repbase, "darcstmp")
1584 os.makedirs(tmp)
1585 w = self.dovc(tmp, qw("initialize"))
1586 yield w; w.getResult()
1587 w = self.dovc(tmp, ["pull", "-a", self.rep_trunk])
1588 yield w; w.getResult()
1590 self.version += 1
1591 version_c = VERSION_C % self.version
1592 open(os.path.join(tmp, "version.c"), "w").write(version_c)
1593 w = self.dovc(tmp, qw("record -a --ignore-times -m revised_to_%d --skip-long-comment -A test@buildbot.sf.net" % self.version))
1594 yield w; w.getResult()
1595 w = self.dovc(tmp, ["push", "-a", self.rep_trunk])
1596 yield w; w.getResult()
1597 w = self.dovc(tmp, qw("changes --context"))
1598 yield w; out = w.getResult()
1599 self.addTrunkRev(out)
1600 rmdirRecursive(tmp)
1601 vc_revise = deferredGenerator(vc_revise)
1603 def vc_try_checkout(self, workdir, rev, branch=None):
1604 assert os.path.abspath(workdir) == workdir
1605 if os.path.exists(workdir):
1606 rmdirRecursive(workdir)
1607 os.makedirs(workdir)
1608 w = self.dovc(workdir, qw("initialize"))
1609 yield w; w.getResult()
1610 if not branch:
1611 rep = self.rep_trunk
1612 else:
1613 rep = os.path.join(self.darcs_base, branch)
1614 w = self.dovc(workdir, ["pull", "-a", rep])
1615 yield w; w.getResult()
1616 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
1617 vc_try_checkout = deferredGenerator(vc_try_checkout)
1619 def vc_try_finish(self, workdir):
1620 rmdirRecursive(workdir)
1623 class Darcs(VCBase, unittest.TestCase):
1624 vc_name = "darcs"
1626 # Darcs has a metadir="_darcs", but it does not have an 'export'
1627 # mode
1628 metadir = None
1629 vctype = "source.Darcs"
1630 vctype_try = "darcs"
1631 has_got_revision = True
1633 def testCheckout(self):
1634 self.helper.vcargs = { 'repourl': self.helper.rep_trunk }
1635 d = self.do_vctest(testRetry=False)
1637 # TODO: testRetry has the same problem with Darcs as it does for
1638 # Arch
1639 return maybeWait(d)
1641 def testPatch(self):
1642 self.helper.vcargs = { 'baseURL': self.helper.darcs_base + "/",
1643 'defaultBranch': "trunk" }
1644 d = self.do_patch()
1645 return maybeWait(d)
1647 def testCheckoutBranch(self):
1648 self.helper.vcargs = { 'baseURL': self.helper.darcs_base + "/",
1649 'defaultBranch': "trunk" }
1650 d = self.do_branch()
1651 return maybeWait(d)
1653 def testCheckoutHTTP(self):
1654 self.serveHTTP()
1655 repourl = "http://localhost:%d/Darcs-Repository/trunk" % self.httpPort
1656 self.helper.vcargs = { 'repourl': repourl }
1657 d = self.do_vctest(testRetry=False)
1658 return maybeWait(d)
1660 def testTry(self):
1661 self.helper.vcargs = { 'baseURL': self.helper.darcs_base + "/",
1662 'defaultBranch': "trunk" }
1663 d = self.do_getpatch()
1664 return maybeWait(d)
1666 VCS.registerVC(Darcs.vc_name, DarcsHelper())
1669 class ArchCommon:
1670 def registerRepository(self, coordinates):
1671 a = self.archname
1672 w = self.dovc(self.repbase, "archives %s" % a)
1673 yield w; out = w.getResult()
1674 if out:
1675 w = self.dovc(self.repbase, "register-archive -d %s" % a)
1676 yield w; w.getResult()
1677 w = self.dovc(self.repbase, "register-archive %s" % coordinates)
1678 yield w; w.getResult()
1679 registerRepository = deferredGenerator(registerRepository)
1681 def unregisterRepository(self):
1682 a = self.archname
1683 w = self.dovc(self.repbase, "archives %s" % a)
1684 yield w; out = w.getResult()
1685 if out:
1686 w = self.dovc(self.repbase, "register-archive -d %s" % a)
1687 yield w; out = w.getResult()
1688 unregisterRepository = deferredGenerator(unregisterRepository)
1690 class TlaHelper(BaseHelper, ArchCommon):
1691 defaultbranch = "testvc--mainline--1"
1692 branchname = "testvc--branch--1"
1693 try_branchname = None # TlaExtractor can figure it out by itself
1694 archcmd = "tla"
1696 def capable(self):
1697 tlapaths = which('tla')
1698 if not tlapaths:
1699 return (False, "Arch (tla) is not installed")
1700 self.vcexe = tlapaths[0]
1701 return (True, None)
1703 def do_get(self, basedir, archive, branch, newdir):
1704 # the 'get' syntax is different between tla and baz. baz, while
1705 # claiming to honor an --archive argument, in fact ignores it. The
1706 # correct invocation is 'baz get archive/revision newdir'.
1707 if self.archcmd == "tla":
1708 w = self.dovc(basedir,
1709 "get -A %s %s %s" % (archive, branch, newdir))
1710 else:
1711 w = self.dovc(basedir,
1712 "get %s/%s %s" % (archive, branch, newdir))
1713 return w
1715 def createRepository(self):
1716 self.createBasedir()
1717 # first check to see if bazaar is around, since we'll need to know
1718 # later
1719 d = VCS.capable(Bazaar.vc_name)
1720 d.addCallback(self._createRepository_1)
1721 return d
1723 def _createRepository_1(self, res):
1724 has_baz = res[0]
1726 # pick a hopefully unique string for the archive name, in the form
1727 # test-%d@buildbot.sf.net--testvc, since otherwise multiple copies of
1728 # the unit tests run in the same user account will collide (since the
1729 # archive names are kept in the per-user ~/.arch-params/ directory).
1730 pid = os.getpid()
1731 self.archname = "test-%s-%d@buildbot.sf.net--testvc" % (self.archcmd,
1732 pid)
1733 trunk = self.defaultbranch
1734 branch = self.branchname
1736 repword = self.archcmd.capitalize()
1737 self.archrep = os.path.join(self.repbase, "%s-Repository" % repword)
1738 tmp = os.path.join(self.repbase, "archtmp")
1739 a = self.archname
1741 self.populate(tmp)
1743 w = self.dovc(tmp, "my-id", failureIsOk=True)
1744 yield w; res = w.getResult()
1745 if not res:
1746 # tla will fail a lot of operations if you have not set an ID
1747 w = self.do(tmp, [self.vcexe, "my-id",
1748 "Buildbot Test Suite <test@buildbot.sf.net>"])
1749 yield w; w.getResult()
1751 if has_baz:
1752 # bazaar keeps a cache of revisions, but this test creates a new
1753 # archive each time it is run, so the cache causes errors.
1754 # Disable the cache to avoid these problems. This will be
1755 # slightly annoying for people who run the buildbot tests under
1756 # the same UID as one which uses baz on a regular basis, but
1757 # bazaar doesn't give us a way to disable the cache just for this
1758 # one archive.
1759 cmd = "%s cache-config --disable" % VCS.getHelper('bazaar').vcexe
1760 w = self.do(tmp, cmd)
1761 yield w; w.getResult()
1763 w = waitForDeferred(self.unregisterRepository())
1764 yield w; w.getResult()
1766 # these commands can be run in any directory
1767 w = self.dovc(tmp, "make-archive -l %s %s" % (a, self.archrep))
1768 yield w; w.getResult()
1769 if self.archcmd == "tla":
1770 w = self.dovc(tmp, "archive-setup -A %s %s" % (a, trunk))
1771 yield w; w.getResult()
1772 w = self.dovc(tmp, "archive-setup -A %s %s" % (a, branch))
1773 yield w; w.getResult()
1774 else:
1775 # baz does not require an 'archive-setup' step
1776 pass
1778 # these commands must be run in the directory that is to be imported
1779 w = self.dovc(tmp, "init-tree --nested %s/%s" % (a, trunk))
1780 yield w; w.getResult()
1781 files = " ".join(["main.c", "version.c", "subdir",
1782 os.path.join("subdir", "subdir.c")])
1783 w = self.dovc(tmp, "add-id %s" % files)
1784 yield w; w.getResult()
1786 w = self.dovc(tmp, "import %s/%s" % (a, trunk))
1787 yield w; out = w.getResult()
1788 self.addTrunkRev("base-0")
1790 # create the branch
1791 if self.archcmd == "tla":
1792 branchstart = "%s--base-0" % trunk
1793 w = self.dovc(tmp, "tag -A %s %s %s" % (a, branchstart, branch))
1794 yield w; w.getResult()
1795 else:
1796 w = self.dovc(tmp, "branch %s" % branch)
1797 yield w; w.getResult()
1799 rmdirRecursive(tmp)
1801 # check out the branch
1802 w = self.do_get(self.repbase, a, branch, "archtmp")
1803 yield w; w.getResult()
1804 # and edit the file
1805 self.populate_branch(tmp)
1806 logfile = "++log.%s--%s" % (branch, a)
1807 logmsg = "Summary: commit on branch\nKeywords:\n\n"
1808 open(os.path.join(tmp, logfile), "w").write(logmsg)
1809 w = self.dovc(tmp, "commit")
1810 yield w; out = w.getResult()
1811 m = re.search(r'committed %s/%s--([\S]+)' % (a, branch),
1812 out)
1813 assert (m.group(1) == "base-0" or m.group(1).startswith("patch-"))
1814 self.addBranchRev(m.group(1))
1816 w = waitForDeferred(self.unregisterRepository())
1817 yield w; w.getResult()
1818 rmdirRecursive(tmp)
1820 # we unregister the repository each time, because we might have
1821 # changed the coordinates (since we switch from a file: URL to an
1822 # http: URL for various tests). The buildslave code doesn't forcibly
1823 # unregister the archive, so we have to do it here.
1824 w = waitForDeferred(self.unregisterRepository())
1825 yield w; w.getResult()
1827 _createRepository_1 = deferredGenerator(_createRepository_1)
1829 def vc_revise(self):
1830 # the fix needs to be done in a workspace that is linked to a
1831 # read-write version of the archive (i.e., using file-based
1832 # coordinates instead of HTTP ones), so we re-register the repository
1833 # before we begin. We unregister it when we're done to make sure the
1834 # build will re-register the correct one for whichever test is
1835 # currently being run.
1837 # except, that source.Bazaar really doesn't like it when the archive
1838 # gets unregistered behind its back. The slave tries to do a 'baz
1839 # replay' in a tree with an archive that is no longer recognized, and
1840 # baz aborts with a botched invariant exception. This causes
1841 # mode=update to fall back to clobber+get, which flunks one of the
1842 # tests (the 'newfile' check in _do_vctest_update_3 fails)
1844 # to avoid this, we take heroic steps here to leave the archive
1845 # registration in the same state as we found it.
1847 tmp = os.path.join(self.repbase, "archtmp")
1848 a = self.archname
1850 w = self.dovc(self.repbase, "archives %s" % a)
1851 yield w; out = w.getResult()
1852 assert out
1853 lines = out.split("\n")
1854 coordinates = lines[1].strip()
1856 # now register the read-write location
1857 w = waitForDeferred(self.registerRepository(self.archrep))
1858 yield w; w.getResult()
1860 trunk = self.defaultbranch
1862 w = self.do_get(self.repbase, a, trunk, "archtmp")
1863 yield w; w.getResult()
1865 # tla appears to use timestamps to determine which files have
1866 # changed, so wait long enough for the new file to have a different
1867 # timestamp
1868 time.sleep(2)
1869 self.version += 1
1870 version_c = VERSION_C % self.version
1871 open(os.path.join(tmp, "version.c"), "w").write(version_c)
1873 logfile = "++log.%s--%s" % (trunk, a)
1874 logmsg = "Summary: revised_to_%d\nKeywords:\n\n" % self.version
1875 open(os.path.join(tmp, logfile), "w").write(logmsg)
1876 w = self.dovc(tmp, "commit")
1877 yield w; out = w.getResult()
1878 m = re.search(r'committed %s/%s--([\S]+)' % (a, trunk),
1879 out)
1880 assert (m.group(1) == "base-0" or m.group(1).startswith("patch-"))
1881 self.addTrunkRev(m.group(1))
1883 # now re-register the original coordinates
1884 w = waitForDeferred(self.registerRepository(coordinates))
1885 yield w; w.getResult()
1886 rmdirRecursive(tmp)
1887 vc_revise = deferredGenerator(vc_revise)
1889 def vc_try_checkout(self, workdir, rev, branch=None):
1890 assert os.path.abspath(workdir) == workdir
1891 if os.path.exists(workdir):
1892 rmdirRecursive(workdir)
1894 a = self.archname
1896 # register the read-write location, if it wasn't already registered
1897 w = waitForDeferred(self.registerRepository(self.archrep))
1898 yield w; w.getResult()
1900 w = self.do_get(self.repbase, a, "testvc--mainline--1", workdir)
1901 yield w; w.getResult()
1903 # timestamps. ick.
1904 time.sleep(2)
1905 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
1906 vc_try_checkout = deferredGenerator(vc_try_checkout)
1908 def vc_try_finish(self, workdir):
1909 rmdirRecursive(workdir)
1911 class Arch(VCBase, unittest.TestCase):
1912 vc_name = "tla"
1914 metadir = None
1915 # Arch has a metadir="{arch}", but it does not have an 'export' mode.
1916 vctype = "source.Arch"
1917 vctype_try = "tla"
1918 has_got_revision = True
1920 def testCheckout(self):
1921 # these are the coordinates of the read-write archive used by all the
1922 # non-HTTP tests. testCheckoutHTTP overrides these.
1923 self.helper.vcargs = {'url': self.helper.archrep,
1924 'version': self.helper.defaultbranch }
1925 d = self.do_vctest(testRetry=False)
1926 # the current testRetry=True logic doesn't have the desired effect:
1927 # "update" is a no-op because arch knows that the repository hasn't
1928 # changed. Other VC systems will re-checkout missing files on
1929 # update, arch just leaves the tree untouched. TODO: come up with
1930 # some better test logic, probably involving a copy of the
1931 # repository that has a few changes checked in.
1933 return maybeWait(d)
1935 def testCheckoutHTTP(self):
1936 self.serveHTTP()
1937 url = "http://localhost:%d/Tla-Repository" % self.httpPort
1938 self.helper.vcargs = { 'url': url,
1939 'version': "testvc--mainline--1" }
1940 d = self.do_vctest(testRetry=False)
1941 return maybeWait(d)
1943 def testPatch(self):
1944 self.helper.vcargs = {'url': self.helper.archrep,
1945 'version': self.helper.defaultbranch }
1946 d = self.do_patch()
1947 return maybeWait(d)
1949 def testCheckoutBranch(self):
1950 self.helper.vcargs = {'url': self.helper.archrep,
1951 'version': self.helper.defaultbranch }
1952 d = self.do_branch()
1953 return maybeWait(d)
1955 def testTry(self):
1956 self.helper.vcargs = {'url': self.helper.archrep,
1957 'version': self.helper.defaultbranch }
1958 d = self.do_getpatch()
1959 return maybeWait(d)
1961 VCS.registerVC(Arch.vc_name, TlaHelper())
1964 class BazaarHelper(TlaHelper):
1965 archcmd = "baz"
1967 def capable(self):
1968 bazpaths = which('baz')
1969 if not bazpaths:
1970 return (False, "Arch (baz) is not installed")
1971 self.vcexe = bazpaths[0]
1972 return (True, None)
1974 def setUp2(self, res):
1975 # we unregister the repository each time, because we might have
1976 # changed the coordinates (since we switch from a file: URL to an
1977 # http: URL for various tests). The buildslave code doesn't forcibly
1978 # unregister the archive, so we have to do it here.
1979 d = self.unregisterRepository()
1980 return d
1983 class Bazaar(Arch):
1984 vc_name = "bazaar"
1986 vctype = "source.Bazaar"
1987 vctype_try = "baz"
1988 has_got_revision = True
1990 fixtimer = None
1992 def testCheckout(self):
1993 self.helper.vcargs = {'url': self.helper.archrep,
1994 # Baz adds the required 'archive' argument
1995 'archive': self.helper.archname,
1996 'version': self.helper.defaultbranch,
1998 d = self.do_vctest(testRetry=False)
1999 # the current testRetry=True logic doesn't have the desired effect:
2000 # "update" is a no-op because arch knows that the repository hasn't
2001 # changed. Other VC systems will re-checkout missing files on
2002 # update, arch just leaves the tree untouched. TODO: come up with
2003 # some better test logic, probably involving a copy of the
2004 # repository that has a few changes checked in.
2006 return maybeWait(d)
2008 def testCheckoutHTTP(self):
2009 self.serveHTTP()
2010 url = "http://localhost:%d/Baz-Repository" % self.httpPort
2011 self.helper.vcargs = { 'url': url,
2012 'archive': self.helper.archname,
2013 'version': self.helper.defaultbranch,
2015 d = self.do_vctest(testRetry=False)
2016 return maybeWait(d)
2018 def testPatch(self):
2019 self.helper.vcargs = {'url': self.helper.archrep,
2020 # Baz adds the required 'archive' argument
2021 'archive': self.helper.archname,
2022 'version': self.helper.defaultbranch,
2024 d = self.do_patch()
2025 return maybeWait(d)
2027 def testCheckoutBranch(self):
2028 self.helper.vcargs = {'url': self.helper.archrep,
2029 # Baz adds the required 'archive' argument
2030 'archive': self.helper.archname,
2031 'version': self.helper.defaultbranch,
2033 d = self.do_branch()
2034 return maybeWait(d)
2036 def testTry(self):
2037 self.helper.vcargs = {'url': self.helper.archrep,
2038 # Baz adds the required 'archive' argument
2039 'archive': self.helper.archname,
2040 'version': self.helper.defaultbranch,
2042 d = self.do_getpatch()
2043 return maybeWait(d)
2045 def fixRepository(self):
2046 self.fixtimer = None
2047 self.site.resource = self.root
2049 def testRetry(self):
2050 # we want to verify that source.Source(retry=) works, and the easiest
2051 # way to make VC updates break (temporarily) is to break the HTTP
2052 # server that's providing the repository. Anything else pretty much
2053 # requires mutating the (read-only) BUILDBOT_TEST_VC repository, or
2054 # modifying the buildslave's checkout command while it's running.
2056 # this test takes a while to run, so don't bother doing it with
2057 # anything other than baz
2059 self.serveHTTP()
2061 # break the repository server
2062 from twisted.web import static
2063 self.site.resource = static.Data("Sorry, repository is offline",
2064 "text/plain")
2065 # and arrange to fix it again in 5 seconds, while the test is
2066 # running.
2067 self.fixtimer = reactor.callLater(5, self.fixRepository)
2069 url = "http://localhost:%d/Baz-Repository" % self.httpPort
2070 self.helper.vcargs = { 'url': url,
2071 'archive': self.helper.archname,
2072 'version': self.helper.defaultbranch,
2073 'retry': (5.0, 4),
2075 d = self.do_vctest_once(True)
2076 d.addCallback(self._testRetry_1)
2077 return maybeWait(d)
2078 def _testRetry_1(self, bs):
2079 # make sure there was mention of the retry attempt in the logs
2080 l = bs.getLogs()[0]
2081 self.failUnlessIn("unable to access URL", l.getText(),
2082 "funny, VC operation didn't fail at least once")
2083 self.failUnlessIn("update failed, trying 4 more times after 5 seconds",
2084 l.getTextWithHeaders(),
2085 "funny, VC operation wasn't reattempted")
2087 def testRetryFails(self):
2088 # make sure that the build eventually gives up on a repository which
2089 # is completely unavailable
2091 self.serveHTTP()
2093 # break the repository server, and leave it broken
2094 from twisted.web import static
2095 self.site.resource = static.Data("Sorry, repository is offline",
2096 "text/plain")
2098 url = "http://localhost:%d/Baz-Repository" % self.httpPort
2099 self.helper.vcargs = {'url': url,
2100 'archive': self.helper.archname,
2101 'version': self.helper.defaultbranch,
2102 'retry': (0.5, 3),
2104 d = self.do_vctest_once(False)
2105 d.addCallback(self._testRetryFails_1)
2106 return maybeWait(d)
2107 def _testRetryFails_1(self, bs):
2108 self.failUnlessEqual(bs.getResults(), FAILURE)
2110 def tearDown2(self):
2111 if self.fixtimer:
2112 self.fixtimer.cancel()
2113 # tell tla to get rid of the leftover archive this test leaves in the
2114 # user's 'tla archives' listing. The name of this archive is provided
2115 # by the repository tarball, so the following command must use the
2116 # same name. We could use archive= to set it explicitly, but if you
2117 # change it from the default, then 'tla update' won't work.
2118 d = self.helper.unregisterRepository()
2119 return d
2121 VCS.registerVC(Bazaar.vc_name, BazaarHelper())
2123 class MercurialHelper(BaseHelper):
2124 branchname = "branch"
2125 try_branchname = "branch"
2127 def capable(self):
2128 hgpaths = which("hg")
2129 if not hgpaths:
2130 return (False, "Mercurial is not installed")
2131 self.vcexe = hgpaths[0]
2132 return (True, None)
2134 def extract_id(self, output):
2135 m = re.search(r'^(\w+)', output)
2136 return m.group(0)
2138 def createRepository(self):
2139 self.createBasedir()
2140 self.hg_base = os.path.join(self.repbase, "Mercurial-Repository")
2141 self.rep_trunk = os.path.join(self.hg_base, "trunk")
2142 self.rep_branch = os.path.join(self.hg_base, "branch")
2143 tmp = os.path.join(self.hg_base, "hgtmp")
2145 os.makedirs(self.rep_trunk)
2146 w = self.dovc(self.rep_trunk, "init")
2147 yield w; w.getResult()
2148 os.makedirs(self.rep_branch)
2149 w = self.dovc(self.rep_branch, "init")
2150 yield w; w.getResult()
2152 self.populate(tmp)
2153 w = self.dovc(tmp, "init")
2154 yield w; w.getResult()
2155 w = self.dovc(tmp, "add")
2156 yield w; w.getResult()
2157 w = self.dovc(tmp, "commit -m initial_import")
2158 yield w; w.getResult()
2159 w = self.dovc(tmp, "push %s" % self.rep_trunk)
2160 # note that hg-push does not actually update the working directory
2161 yield w; w.getResult()
2162 w = self.dovc(tmp, "identify")
2163 yield w; out = w.getResult()
2164 self.addTrunkRev(self.extract_id(out))
2166 self.populate_branch(tmp)
2167 w = self.dovc(tmp, "commit -m commit_on_branch")
2168 yield w; w.getResult()
2169 w = self.dovc(tmp, "push %s" % self.rep_branch)
2170 yield w; w.getResult()
2171 w = self.dovc(tmp, "identify")
2172 yield w; out = w.getResult()
2173 self.addBranchRev(self.extract_id(out))
2174 rmdirRecursive(tmp)
2175 createRepository = deferredGenerator(createRepository)
2177 def vc_revise(self):
2178 tmp = os.path.join(self.hg_base, "hgtmp2")
2179 w = self.dovc(self.hg_base, "clone %s %s" % (self.rep_trunk, tmp))
2180 yield w; w.getResult()
2182 self.version += 1
2183 version_c = VERSION_C % self.version
2184 version_c_filename = os.path.join(tmp, "version.c")
2185 open(version_c_filename, "w").write(version_c)
2186 # hg uses timestamps to distinguish files which have changed, so we
2187 # force the mtime forward a little bit
2188 future = time.time() + 2*self.version
2189 os.utime(version_c_filename, (future, future))
2190 w = self.dovc(tmp, "commit -m revised_to_%d" % self.version)
2191 yield w; w.getResult()
2192 w = self.dovc(tmp, "push %s" % self.rep_trunk)
2193 yield w; w.getResult()
2194 w = self.dovc(tmp, "identify")
2195 yield w; out = w.getResult()
2196 self.addTrunkRev(self.extract_id(out))
2197 rmdirRecursive(tmp)
2198 vc_revise = deferredGenerator(vc_revise)
2200 def vc_try_checkout(self, workdir, rev, branch=None):
2201 assert os.path.abspath(workdir) == workdir
2202 if os.path.exists(workdir):
2203 rmdirRecursive(workdir)
2204 if branch:
2205 src = self.rep_branch
2206 else:
2207 src = self.rep_trunk
2208 w = self.dovc(self.hg_base, "clone %s %s" % (src, workdir))
2209 yield w; w.getResult()
2210 try_c_filename = os.path.join(workdir, "subdir", "subdir.c")
2211 open(try_c_filename, "w").write(TRY_C)
2212 future = time.time() + 2*self.version
2213 os.utime(try_c_filename, (future, future))
2214 vc_try_checkout = deferredGenerator(vc_try_checkout)
2216 def vc_try_finish(self, workdir):
2217 rmdirRecursive(workdir)
2220 class Mercurial(VCBase, unittest.TestCase):
2221 vc_name = "hg"
2223 # Mercurial has a metadir=".hg", but it does not have an 'export' mode.
2224 metadir = None
2225 vctype = "source.Mercurial"
2226 vctype_try = "hg"
2227 has_got_revision = True
2229 def testCheckout(self):
2230 self.helper.vcargs = { 'repourl': self.helper.rep_trunk }
2231 d = self.do_vctest(testRetry=False)
2233 # TODO: testRetry has the same problem with Mercurial as it does for
2234 # Arch
2235 return maybeWait(d)
2237 def testPatch(self):
2238 self.helper.vcargs = { 'baseURL': self.helper.hg_base + "/",
2239 'defaultBranch': "trunk" }
2240 d = self.do_patch()
2241 return maybeWait(d)
2243 def testCheckoutBranch(self):
2244 self.helper.vcargs = { 'baseURL': self.helper.hg_base + "/",
2245 'defaultBranch': "trunk" }
2246 d = self.do_branch()
2247 return maybeWait(d)
2249 def testCheckoutHTTP(self):
2250 self.serveHTTP()
2251 repourl = "http://localhost:%d/Mercurial-Repository/trunk/.hg" % self.httpPort
2252 self.helper.vcargs = { 'repourl': repourl }
2253 d = self.do_vctest(testRetry=False)
2254 return maybeWait(d)
2255 # TODO: The easiest way to publish hg over HTTP is by running 'hg serve'
2256 # as a child process while the test is running. (you can also use a CGI
2257 # script, which sounds difficult, or you can publish the files directly,
2258 # which isn't well documented).
2259 testCheckoutHTTP.skip = "not yet implemented, use 'hg serve'"
2261 def testTry(self):
2262 self.helper.vcargs = { 'baseURL': self.helper.hg_base + "/",
2263 'defaultBranch': "trunk" }
2264 d = self.do_getpatch()
2265 return maybeWait(d)
2267 VCS.registerVC(Mercurial.vc_name, MercurialHelper())
2270 class Sources(unittest.TestCase):
2271 # TODO: this needs serious rethink
2272 def makeChange(self, when=None, revision=None):
2273 if when:
2274 when = mktime_tz(parsedate_tz(when))
2275 return changes.Change("fred", [], "", when=when, revision=revision)
2277 def testCVS1(self):
2278 r = base.BuildRequest("forced build", SourceStamp())
2279 b = base.Build([r])
2280 s = source.CVS(cvsroot=None, cvsmodule=None, workdir=None, build=b)
2281 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()), None)
2283 def testCVS2(self):
2284 c = []
2285 c.append(self.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2286 c.append(self.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2287 c.append(self.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2288 r = base.BuildRequest("forced", SourceStamp(changes=c))
2289 submitted = "Wed, 08 Sep 2004 09:04:00 -0700"
2290 r.submittedAt = mktime_tz(parsedate_tz(submitted))
2291 b = base.Build([r])
2292 s = source.CVS(cvsroot=None, cvsmodule=None, workdir=None, build=b)
2293 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()),
2294 "Wed, 08 Sep 2004 16:03:00 -0000")
2296 def testCVS3(self):
2297 c = []
2298 c.append(self.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2299 c.append(self.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2300 c.append(self.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2301 r = base.BuildRequest("forced", SourceStamp(changes=c))
2302 submitted = "Wed, 08 Sep 2004 09:04:00 -0700"
2303 r.submittedAt = mktime_tz(parsedate_tz(submitted))
2304 b = base.Build([r])
2305 s = source.CVS(cvsroot=None, cvsmodule=None, workdir=None, build=b,
2306 checkoutDelay=10)
2307 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()),
2308 "Wed, 08 Sep 2004 16:02:10 -0000")
2310 def testCVS4(self):
2311 c = []
2312 c.append(self.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2313 c.append(self.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2314 c.append(self.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2315 r1 = base.BuildRequest("forced", SourceStamp(changes=c))
2316 submitted = "Wed, 08 Sep 2004 09:04:00 -0700"
2317 r1.submittedAt = mktime_tz(parsedate_tz(submitted))
2319 c = []
2320 c.append(self.makeChange("Wed, 08 Sep 2004 09:05:00 -0700"))
2321 r2 = base.BuildRequest("forced", SourceStamp(changes=c))
2322 submitted = "Wed, 08 Sep 2004 09:07:00 -0700"
2323 r2.submittedAt = mktime_tz(parsedate_tz(submitted))
2325 b = base.Build([r1, r2])
2326 s = source.CVS(cvsroot=None, cvsmodule=None, workdir=None, build=b)
2327 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()),
2328 "Wed, 08 Sep 2004 16:06:00 -0000")
2330 def testSVN1(self):
2331 r = base.BuildRequest("forced", SourceStamp())
2332 b = base.Build([r])
2333 s = source.SVN(svnurl="dummy", workdir=None, build=b)
2334 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()), None)
2336 def testSVN2(self):
2337 c = []
2338 c.append(self.makeChange(revision=4))
2339 c.append(self.makeChange(revision=10))
2340 c.append(self.makeChange(revision=67))
2341 r = base.BuildRequest("forced", SourceStamp(changes=c))
2342 b = base.Build([r])
2343 s = source.SVN(svnurl="dummy", workdir=None, build=b)
2344 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()), 67)
2346 class Patch(VCBase, unittest.TestCase):
2347 def setUp(self):
2348 pass
2350 def tearDown(self):
2351 pass
2353 def testPatch(self):
2354 # invoke 'patch' all by itself, to see if it works the way we think
2355 # it should. This is intended to ferret out some windows test
2356 # failures.
2357 helper = BaseHelper()
2358 self.workdir = os.path.join("test_vc", "testPatch")
2359 helper.populate(self.workdir)
2360 patch = which("patch")[0]
2362 command = [patch, "-p0"]
2363 class FakeBuilder:
2364 usePTY = False
2365 def sendUpdate(self, status):
2366 pass
2367 c = commands.ShellCommand(FakeBuilder(), command, self.workdir,
2368 sendRC=False, initialStdin=p0_diff)
2369 d = c.start()
2370 d.addCallback(self._testPatch_1)
2371 return maybeWait(d)
2373 def _testPatch_1(self, res):
2374 # make sure the file actually got patched
2375 subdir_c = os.path.join(self.workdir, "subdir", "subdir.c")
2376 data = open(subdir_c, "r").read()
2377 self.failUnlessIn("Hello patched subdir.\\n", data)