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