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