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