move BuildSlave to new class, merge with BotPerspective, make it long-lived
[buildbot.git] / buildbot / test / test_vc.py
blob73eb97918248c69acaa5381bdc77ce2833d5cb68
1 # -*- test-case-name: buildbot.test.test_vc -*-
3 import sys, os, time, re
4 from email.Utils import mktime_tz, parsedate_tz
5 from cStringIO import StringIO
7 from twisted.trial import unittest
8 from twisted.internet import defer, reactor, utils, protocol, error
9 from twisted.python import failure
10 from twisted.python.procutils import which
12 #defer.Deferred.debug = True
14 from twisted.python import log
15 #log.startLogging(sys.stderr)
17 from buildbot import master, interfaces
18 from buildbot.slave import bot, commands
19 from buildbot.slave.commands import rmdirRecursive
20 from buildbot.status.builder import SUCCESS, FAILURE
21 from buildbot.process import base
22 from buildbot.steps import source
23 from buildbot.changes import changes
24 from buildbot.sourcestamp import SourceStamp
25 from buildbot.scripts import tryclient
26 from buildbot.test.runutils import SignalMixin
28 #step.LoggedRemoteCommand.debug = True
30 from twisted.internet.defer import waitForDeferred, deferredGenerator
32 # Most of these tests (all but SourceStamp) depend upon having a set of
33 # repositories from which we can perform checkouts. These repositories are
34 # created by the setUp method at the start of each test class. In earlier
35 # versions these repositories were created offline and distributed with a
36 # separate tarball named 'buildbot-test-vc-1.tar.gz'. This is no longer
37 # necessary.
39 # CVS requires a local file repository. Providing remote access is beyond
40 # the feasible abilities of this test program (needs pserver or ssh).
42 # SVN requires a local file repository. To provide remote access over HTTP
43 # requires an apache server with DAV support and mod_svn, way beyond what we
44 # can test from here.
46 # Arch and Darcs both allow remote (read-only) operation with any web
47 # server. We test both local file access and HTTP access (by spawning a
48 # small web server to provide access to the repository files while the test
49 # is running).
51 # Perforce starts the daemon running on localhost. Unfortunately, it must
52 # use a predetermined Internet-domain port number, unless we want to go
53 # all-out: bind the listen socket ourselves and pretend to be inetd.
55 class _PutEverythingGetter(protocol.ProcessProtocol):
56 def __init__(self, deferred, stdin):
57 self.deferred = deferred
58 self.outBuf = StringIO()
59 self.errBuf = StringIO()
60 self.outReceived = self.outBuf.write
61 self.errReceived = self.errBuf.write
62 self.stdin = stdin
64 def connectionMade(self):
65 if self.stdin is not None:
66 self.transport.write(self.stdin)
67 self.transport.closeStdin()
69 def processEnded(self, reason):
70 out = self.outBuf.getvalue()
71 err = self.errBuf.getvalue()
72 e = reason.value
73 code = e.exitCode
74 if e.signal:
75 self.deferred.errback((out, err, e.signal))
76 else:
77 self.deferred.callback((out, err, code))
79 def myGetProcessOutputAndValue(executable, args=(), env={}, path='.',
80 reactor=None, stdin=None):
81 """Like twisted.internet.utils.getProcessOutputAndValue but takes
82 stdin, too."""
83 if reactor is None:
84 from twisted.internet import reactor
85 d = defer.Deferred()
86 p = _PutEverythingGetter(d, stdin)
87 reactor.spawnProcess(p, executable, (executable,)+tuple(args), env, path)
88 return d
90 config_vc = """
91 from buildbot.process import factory
92 from buildbot.steps import source
93 from buildbot.buildslave import BuildSlave
94 s = factory.s
96 f1 = factory.BuildFactory([
97 %s,
99 c = {}
100 c['slaves'] = [BuildSlave('bot1', 'sekrit')]
101 c['schedulers'] = []
102 c['builders'] = [{'name': 'vc', 'slavename': 'bot1',
103 'builddir': 'vc-dir', 'factory': f1}]
104 c['slavePortnum'] = 0
105 BuildmasterConfig = c
108 p0_diff = r"""
109 Index: subdir/subdir.c
110 ===================================================================
111 RCS file: /home/warner/stuff/Projects/BuildBot/code-arch/_trial_temp/test_vc/repositories/CVS-Repository/sample/subdir/subdir.c,v
112 retrieving revision 1.1.1.1
113 diff -u -r1.1.1.1 subdir.c
114 --- subdir/subdir.c 14 Aug 2005 01:32:49 -0000 1.1.1.1
115 +++ subdir/subdir.c 14 Aug 2005 01:36:15 -0000
116 @@ -4,6 +4,6 @@
118 main(int argc, const char *argv[])
120 - printf("Hello subdir.\n");
121 + printf("Hello patched subdir.\n");
122 return 0;
126 # this patch does not include the filename headers, so it is
127 # patchlevel-neutral
128 TRY_PATCH = '''
129 @@ -5,6 +5,6 @@
131 main(int argc, const char *argv[])
133 - printf("Hello subdir.\\n");
134 + printf("Hello try.\\n");
135 return 0;
139 MAIN_C = '''
140 // this is main.c
141 #include <stdio.h>
144 main(int argc, const char *argv[])
146 printf("Hello world.\\n");
147 return 0;
151 BRANCH_C = '''
152 // this is main.c
153 #include <stdio.h>
156 main(int argc, const char *argv[])
158 printf("Hello branch.\\n");
159 return 0;
163 VERSION_C = '''
164 // this is version.c
165 #include <stdio.h>
168 main(int argc, const char *argv[])
170 printf("Hello world, version=%d\\n");
171 return 0;
175 SUBDIR_C = '''
176 // this is subdir/subdir.c
177 #include <stdio.h>
180 main(int argc, const char *argv[])
182 printf("Hello subdir.\\n");
183 return 0;
187 TRY_C = '''
188 // this is subdir/subdir.c
189 #include <stdio.h>
192 main(int argc, const char *argv[])
194 printf("Hello try.\\n");
195 return 0;
199 def qw(s):
200 return s.split()
202 class VCS_Helper:
203 # this is a helper class which keeps track of whether each VC system is
204 # available, and whether the repository for each has been created. There
205 # is one instance of this class, at module level, shared between all test
206 # cases.
208 def __init__(self):
209 self._helpers = {}
210 self._isCapable = {}
211 self._excuses = {}
212 self._repoReady = {}
214 def registerVC(self, name, helper):
215 self._helpers[name] = helper
216 self._repoReady[name] = False
218 def skipIfNotCapable(self, name):
219 """Either return None, or raise SkipTest"""
220 d = self.capable(name)
221 def _maybeSkip(res):
222 if not res[0]:
223 raise unittest.SkipTest(res[1])
224 d.addCallback(_maybeSkip)
225 return d
227 def capable(self, name):
228 """Return a Deferred that fires with (True,None) if this host offers
229 the given VC tool, or (False,excuse) if it does not (and therefore
230 the tests should be skipped)."""
232 if self._isCapable.has_key(name):
233 if self._isCapable[name]:
234 return defer.succeed((True,None))
235 else:
236 return defer.succeed((False, self._excuses[name]))
237 d = defer.maybeDeferred(self._helpers[name].capable)
238 def _capable(res):
239 if res[0]:
240 self._isCapable[name] = True
241 else:
242 self._excuses[name] = res[1]
243 return res
244 d.addCallback(_capable)
245 return d
247 def getHelper(self, name):
248 return self._helpers[name]
250 def createRepository(self, name):
251 """Return a Deferred that fires when the repository is set up."""
252 if self._repoReady[name]:
253 return defer.succeed(True)
254 d = self._helpers[name].createRepository()
255 def _ready(res):
256 self._repoReady[name] = True
257 d.addCallback(_ready)
258 return d
260 VCS = VCS_Helper()
263 # the overall plan here:
265 # Each VC system is tested separately, all using the same source tree defined
266 # in the 'files' dictionary above. Each VC system gets its own TestCase
267 # subclass. The first test case that is run will create the repository during
268 # setUp(), making two branches: 'trunk' and 'branch'. The trunk gets a copy
269 # of all the files in 'files'. The variant of good.c is committed on the
270 # branch.
272 # then testCheckout is run, which does a number of checkout/clobber/update
273 # builds. These all use trunk r1. It then runs self.fix(), which modifies
274 # 'fixable.c', then performs another build and makes sure the tree has been
275 # updated.
277 # testBranch uses trunk-r1 and branch-r1, making sure that we clobber the
278 # tree properly when we switch between them
280 # testPatch does a trunk-r1 checkout and applies a patch.
282 # testTryGetPatch performs a trunk-r1 checkout, modifies some files, then
283 # verifies that tryclient.getSourceStamp figures out the base revision and
284 # what got changed.
287 # vc_create makes a repository at r1 with three files: main.c, version.c, and
288 # subdir/foo.c . It also creates a branch from r1 (called b1) in which main.c
289 # says "hello branch" instead of "hello world". self.trunk[] contains
290 # revision stamps for everything on the trunk, and self.branch[] does the
291 # same for the branch.
293 # vc_revise() checks out a tree at HEAD, changes version.c, then checks it
294 # back in. The new version stamp is appended to self.trunk[]. The tree is
295 # removed afterwards.
297 # vc_try_checkout(workdir, rev) checks out a tree at REV, then changes
298 # subdir/subdir.c to say 'Hello try'
299 # vc_try_finish(workdir) removes the tree and cleans up any VC state
300 # necessary (like deleting the Arch archive entry).
303 class BaseHelper:
304 def __init__(self):
305 self.trunk = []
306 self.branch = []
307 self.allrevs = []
309 def capable(self):
310 # this is also responsible for setting self.vcexe
311 raise NotImplementedError
313 def createBasedir(self):
314 # you must call this from createRepository
315 self.repbase = os.path.abspath(os.path.join("test_vc",
316 "repositories"))
317 if not os.path.isdir(self.repbase):
318 os.makedirs(self.repbase)
320 def createRepository(self):
321 # this will only be called once per process
322 raise NotImplementedError
324 def populate(self, basedir):
325 if not os.path.exists(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 if isinstance(command, (str, unicode)):
390 command = self.vcexe + " " + command
391 else:
392 # command is a list
393 command = [self.vcexe] + command
394 return self.do(basedir, command, failureIsOk, stdin)
396 class VCBase(SignalMixin):
397 metadir = None
398 createdRepository = False
399 master = None
400 slave = None
401 helper = None
402 httpServer = None
403 httpPort = None
404 skip = None
405 has_got_revision = False
406 has_got_revision_branches_are_merged = False # for SVN
408 def failUnlessIn(self, substring, string, msg=None):
409 # trial provides a version of this that requires python-2.3 to test
410 # strings.
411 if msg is None:
412 msg = ("did not see the expected substring '%s' in string '%s'" %
413 (substring, string))
414 self.failUnless(string.find(substring) != -1, msg)
416 def setUp(self):
417 d = VCS.skipIfNotCapable(self.vc_name)
418 d.addCallback(self._setUp1)
419 return d
421 def _setUp1(self, res):
422 self.helper = VCS.getHelper(self.vc_name)
424 if os.path.exists("basedir"):
425 rmdirRecursive("basedir")
426 os.mkdir("basedir")
427 self.master = master.BuildMaster("basedir")
428 self.slavebase = os.path.abspath("slavebase")
429 if os.path.exists(self.slavebase):
430 rmdirRecursive(self.slavebase)
431 os.mkdir("slavebase")
433 d = VCS.createRepository(self.vc_name)
434 return d
436 def connectSlave(self):
437 port = self.master.slavePort._port.getHost().port
438 slave = bot.BuildSlave("localhost", port, "bot1", "sekrit",
439 self.slavebase, keepalive=0, usePTY=1)
440 self.slave = slave
441 slave.startService()
442 d = self.master.botmaster.waitUntilBuilderAttached("vc")
443 return d
445 def loadConfig(self, config):
446 # reloading the config file causes a new 'listDirs' command to be
447 # sent to the slave. To synchronize on this properly, it is easiest
448 # to stop and restart the slave.
449 d = defer.succeed(None)
450 if self.slave:
451 d = self.master.botmaster.waitUntilBuilderDetached("vc")
452 self.slave.stopService()
453 d.addCallback(lambda res: self.master.loadConfig(config))
454 d.addCallback(lambda res: self.connectSlave())
455 return d
457 def serveHTTP(self):
458 # launch an HTTP server to serve the repository files
459 from twisted.web import static, server
460 from twisted.internet import reactor
461 self.root = static.File(self.helper.repbase)
462 self.site = server.Site(self.root)
463 self.httpServer = reactor.listenTCP(0, self.site)
464 self.httpPort = self.httpServer.getHost().port
466 def doBuild(self, shouldSucceed=True, ss=None):
467 c = interfaces.IControl(self.master)
469 if ss is None:
470 ss = SourceStamp()
471 #print "doBuild(ss: b=%s rev=%s)" % (ss.branch, ss.revision)
472 req = base.BuildRequest("test_vc forced build", ss)
473 d = req.waitUntilFinished()
474 c.getBuilder("vc").requestBuild(req)
475 d.addCallback(self._doBuild_1, shouldSucceed)
476 return d
477 def _doBuild_1(self, bs, shouldSucceed):
478 r = bs.getResults()
479 if r != SUCCESS and shouldSucceed:
480 print
481 print
482 if not bs.isFinished():
483 print "Hey, build wasn't even finished!"
484 print "Build did not succeed:", r, bs.getText()
485 for s in bs.getSteps():
486 for l in s.getLogs():
487 print "--- START step %s / log %s ---" % (s.getName(),
488 l.getName())
489 print l.getTextWithHeaders()
490 print "--- STOP ---"
491 print
492 self.fail("build did not succeed")
493 return bs
495 def printLogs(self, bs):
496 for s in bs.getSteps():
497 for l in s.getLogs():
498 print "--- START step %s / log %s ---" % (s.getName(),
499 l.getName())
500 print l.getTextWithHeaders()
501 print "--- STOP ---"
502 print
504 def touch(self, d, f):
505 open(os.path.join(d,f),"w").close()
506 def shouldExist(self, *args):
507 target = os.path.join(*args)
508 self.failUnless(os.path.exists(target),
509 "expected to find %s but didn't" % target)
510 def shouldNotExist(self, *args):
511 target = os.path.join(*args)
512 self.failIf(os.path.exists(target),
513 "expected to NOT find %s, but did" % target)
514 def shouldContain(self, d, f, contents):
515 c = open(os.path.join(d, f), "r").read()
516 self.failUnlessIn(contents, c)
518 def checkGotRevision(self, bs, expected):
519 if self.has_got_revision:
520 self.failUnlessEqual(bs.getProperty("got_revision"), expected)
522 def checkGotRevisionIsLatest(self, bs):
523 expected = self.helper.trunk[-1]
524 if self.has_got_revision_branches_are_merged:
525 expected = self.helper.allrevs[-1]
526 self.checkGotRevision(bs, expected)
528 def do_vctest(self, testRetry=True):
529 vctype = self.vctype
530 args = self.helper.vcargs
531 m = self.master
532 self.vcdir = os.path.join(self.slavebase, "vc-dir", "source")
533 self.workdir = os.path.join(self.slavebase, "vc-dir", "build")
534 # woo double-substitution
535 s = "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype,)
536 for k,v in args.items():
537 s += ", %s=%s" % (k, repr(v))
538 s += ")"
539 config = config_vc % s
541 m.loadConfig(config % 'clobber')
542 m.readConfig = True
543 m.startService()
545 d = self.connectSlave()
546 d.addCallback(lambda res: log.msg("testing clobber"))
547 d.addCallback(self._do_vctest_clobber)
548 d.addCallback(lambda res: log.msg("doing update"))
549 d.addCallback(lambda res: self.loadConfig(config % 'update'))
550 d.addCallback(lambda res: log.msg("testing update"))
551 d.addCallback(self._do_vctest_update)
552 if testRetry:
553 d.addCallback(lambda res: log.msg("testing update retry"))
554 d.addCallback(self._do_vctest_update_retry)
555 d.addCallback(lambda res: log.msg("doing copy"))
556 d.addCallback(lambda res: self.loadConfig(config % 'copy'))
557 d.addCallback(lambda res: log.msg("testing copy"))
558 d.addCallback(self._do_vctest_copy)
559 if self.metadir:
560 d.addCallback(lambda res: log.msg("doing export"))
561 d.addCallback(lambda res: self.loadConfig(config % 'export'))
562 d.addCallback(lambda res: log.msg("testing export"))
563 d.addCallback(self._do_vctest_export)
564 return d
566 def _do_vctest_clobber(self, res):
567 d = self.doBuild() # initial checkout
568 d.addCallback(self._do_vctest_clobber_1)
569 return d
570 def _do_vctest_clobber_1(self, bs):
571 self.shouldExist(self.workdir, "main.c")
572 self.shouldExist(self.workdir, "version.c")
573 self.shouldExist(self.workdir, "subdir", "subdir.c")
574 if self.metadir:
575 self.shouldExist(self.workdir, self.metadir)
576 self.failUnlessEqual(bs.getProperty("revision"), None)
577 self.failUnlessEqual(bs.getProperty("branch"), None)
578 self.checkGotRevisionIsLatest(bs)
580 self.touch(self.workdir, "newfile")
581 self.shouldExist(self.workdir, "newfile")
582 d = self.doBuild() # rebuild clobbers workdir
583 d.addCallback(self._do_vctest_clobber_2)
584 return d
585 def _do_vctest_clobber_2(self, res):
586 self.shouldNotExist(self.workdir, "newfile")
588 def _do_vctest_update(self, res):
589 log.msg("_do_vctest_update")
590 d = self.doBuild() # rebuild with update
591 d.addCallback(self._do_vctest_update_1)
592 return d
593 def _do_vctest_update_1(self, bs):
594 log.msg("_do_vctest_update_1")
595 self.shouldExist(self.workdir, "main.c")
596 self.shouldExist(self.workdir, "version.c")
597 self.shouldContain(self.workdir, "version.c",
598 "version=%d" % self.helper.version)
599 if self.metadir:
600 self.shouldExist(self.workdir, self.metadir)
601 self.failUnlessEqual(bs.getProperty("revision"), None)
602 self.checkGotRevisionIsLatest(bs)
604 self.touch(self.workdir, "newfile")
605 d = self.doBuild() # update rebuild leaves new files
606 d.addCallback(self._do_vctest_update_2)
607 return d
608 def _do_vctest_update_2(self, bs):
609 log.msg("_do_vctest_update_2")
610 self.shouldExist(self.workdir, "main.c")
611 self.shouldExist(self.workdir, "version.c")
612 self.touch(self.workdir, "newfile")
613 # now make a change to the repository and make sure we pick it up
614 d = self.helper.vc_revise()
615 d.addCallback(lambda res: self.doBuild())
616 d.addCallback(self._do_vctest_update_3)
617 return d
618 def _do_vctest_update_3(self, bs):
619 log.msg("_do_vctest_update_3")
620 self.shouldExist(self.workdir, "main.c")
621 self.shouldExist(self.workdir, "version.c")
622 self.shouldContain(self.workdir, "version.c",
623 "version=%d" % self.helper.version)
624 self.shouldExist(self.workdir, "newfile")
625 self.failUnlessEqual(bs.getProperty("revision"), None)
626 self.checkGotRevisionIsLatest(bs)
628 # now "update" to an older revision
629 d = self.doBuild(ss=SourceStamp(revision=self.helper.trunk[-2]))
630 d.addCallback(self._do_vctest_update_4)
631 return d
632 def _do_vctest_update_4(self, bs):
633 log.msg("_do_vctest_update_4")
634 self.shouldExist(self.workdir, "main.c")
635 self.shouldExist(self.workdir, "version.c")
636 self.shouldContain(self.workdir, "version.c",
637 "version=%d" % (self.helper.version-1))
638 self.failUnlessEqual(bs.getProperty("revision"),
639 self.helper.trunk[-2])
640 self.checkGotRevision(bs, self.helper.trunk[-2])
642 # now update to the newer revision
643 d = self.doBuild(ss=SourceStamp(revision=self.helper.trunk[-1]))
644 d.addCallback(self._do_vctest_update_5)
645 return d
646 def _do_vctest_update_5(self, bs):
647 log.msg("_do_vctest_update_5")
648 self.shouldExist(self.workdir, "main.c")
649 self.shouldExist(self.workdir, "version.c")
650 self.shouldContain(self.workdir, "version.c",
651 "version=%d" % self.helper.version)
652 self.failUnlessEqual(bs.getProperty("revision"),
653 self.helper.trunk[-1])
654 self.checkGotRevision(bs, self.helper.trunk[-1])
657 def _do_vctest_update_retry(self, res):
658 # certain local changes will prevent an update from working. The
659 # most common is to replace a file with a directory, or vice
660 # versa. The slave code should spot the failure and do a
661 # clobber/retry.
662 os.unlink(os.path.join(self.workdir, "main.c"))
663 os.mkdir(os.path.join(self.workdir, "main.c"))
664 self.touch(os.path.join(self.workdir, "main.c"), "foo")
665 self.touch(self.workdir, "newfile")
667 d = self.doBuild() # update, but must clobber to handle the error
668 d.addCallback(self._do_vctest_update_retry_1)
669 return d
670 def _do_vctest_update_retry_1(self, bs):
671 # SVN-1.4.0 doesn't seem to have any problem with the
672 # file-turned-directory issue (although older versions did). So don't
673 # actually check that the tree was clobbered.. as long as the update
674 # succeeded (checked by doBuild), that should be good enough.
675 #self.shouldNotExist(self.workdir, "newfile")
676 pass
678 def _do_vctest_copy(self, res):
679 d = self.doBuild() # copy rebuild clobbers new files
680 d.addCallback(self._do_vctest_copy_1)
681 return d
682 def _do_vctest_copy_1(self, bs):
683 if self.metadir:
684 self.shouldExist(self.workdir, self.metadir)
685 self.shouldNotExist(self.workdir, "newfile")
686 self.touch(self.workdir, "newfile")
687 self.touch(self.vcdir, "newvcfile")
688 self.failUnlessEqual(bs.getProperty("revision"), None)
689 self.checkGotRevisionIsLatest(bs)
691 d = self.doBuild() # copy rebuild clobbers new files
692 d.addCallback(self._do_vctest_copy_2)
693 return d
694 def _do_vctest_copy_2(self, bs):
695 if self.metadir:
696 self.shouldExist(self.workdir, self.metadir)
697 self.shouldNotExist(self.workdir, "newfile")
698 self.shouldExist(self.vcdir, "newvcfile")
699 self.shouldExist(self.workdir, "newvcfile")
700 self.failUnlessEqual(bs.getProperty("revision"), None)
701 self.checkGotRevisionIsLatest(bs)
702 self.touch(self.workdir, "newfile")
704 def _do_vctest_export(self, res):
705 d = self.doBuild() # export rebuild clobbers new files
706 d.addCallback(self._do_vctest_export_1)
707 return d
708 def _do_vctest_export_1(self, bs):
709 self.shouldNotExist(self.workdir, self.metadir)
710 self.shouldNotExist(self.workdir, "newfile")
711 self.failUnlessEqual(bs.getProperty("revision"), None)
712 #self.checkGotRevisionIsLatest(bs)
713 # VC 'export' is not required to have a got_revision
714 self.touch(self.workdir, "newfile")
716 d = self.doBuild() # export rebuild clobbers new files
717 d.addCallback(self._do_vctest_export_2)
718 return d
719 def _do_vctest_export_2(self, bs):
720 self.shouldNotExist(self.workdir, self.metadir)
721 self.shouldNotExist(self.workdir, "newfile")
722 self.failUnlessEqual(bs.getProperty("revision"), None)
723 #self.checkGotRevisionIsLatest(bs)
724 # VC 'export' is not required to have a got_revision
726 def do_patch(self):
727 vctype = self.vctype
728 args = self.helper.vcargs
729 m = self.master
730 self.vcdir = os.path.join(self.slavebase, "vc-dir", "source")
731 self.workdir = os.path.join(self.slavebase, "vc-dir", "build")
732 s = "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype,)
733 for k,v in args.items():
734 s += ", %s=%s" % (k, repr(v))
735 s += ")"
736 self.config = config_vc % s
738 m.loadConfig(self.config % "clobber")
739 m.readConfig = True
740 m.startService()
742 ss = SourceStamp(revision=self.helper.trunk[-1], patch=(0, p0_diff))
744 d = self.connectSlave()
745 d.addCallback(lambda res: self.doBuild(ss=ss))
746 d.addCallback(self._doPatch_1)
747 return d
748 def _doPatch_1(self, bs):
749 self.shouldContain(self.workdir, "version.c",
750 "version=%d" % self.helper.version)
751 # make sure the file actually got patched
752 subdir_c = os.path.join(self.slavebase, "vc-dir", "build",
753 "subdir", "subdir.c")
754 data = open(subdir_c, "r").read()
755 self.failUnlessIn("Hello patched subdir.\\n", data)
756 self.failUnlessEqual(bs.getProperty("revision"),
757 self.helper.trunk[-1])
758 self.checkGotRevision(bs, self.helper.trunk[-1])
760 # make sure that a rebuild does not use the leftover patched workdir
761 d = self.master.loadConfig(self.config % "update")
762 d.addCallback(lambda res: self.doBuild(ss=None))
763 d.addCallback(self._doPatch_2)
764 return d
765 def _doPatch_2(self, bs):
766 # make sure the file is back to its original
767 subdir_c = os.path.join(self.slavebase, "vc-dir", "build",
768 "subdir", "subdir.c")
769 data = open(subdir_c, "r").read()
770 self.failUnlessIn("Hello subdir.\\n", data)
771 self.failUnlessEqual(bs.getProperty("revision"), None)
772 self.checkGotRevisionIsLatest(bs)
774 # now make sure we can patch an older revision. We need at least two
775 # revisions here, so we might have to create one first
776 if len(self.helper.trunk) < 2:
777 d = self.helper.vc_revise()
778 d.addCallback(self._doPatch_3)
779 return d
780 return self._doPatch_3()
782 def _doPatch_3(self, res=None):
783 ss = SourceStamp(revision=self.helper.trunk[-2], patch=(0, p0_diff))
784 d = self.doBuild(ss=ss)
785 d.addCallback(self._doPatch_4)
786 return d
787 def _doPatch_4(self, bs):
788 self.shouldContain(self.workdir, "version.c",
789 "version=%d" % (self.helper.version-1))
790 # and make sure the file actually got patched
791 subdir_c = os.path.join(self.slavebase, "vc-dir", "build",
792 "subdir", "subdir.c")
793 data = open(subdir_c, "r").read()
794 self.failUnlessIn("Hello patched subdir.\\n", data)
795 self.failUnlessEqual(bs.getProperty("revision"),
796 self.helper.trunk[-2])
797 self.checkGotRevision(bs, self.helper.trunk[-2])
799 # now check that we can patch a branch
800 ss = SourceStamp(branch=self.helper.branchname,
801 revision=self.helper.branch[-1],
802 patch=(0, p0_diff))
803 d = self.doBuild(ss=ss)
804 d.addCallback(self._doPatch_5)
805 return d
806 def _doPatch_5(self, bs):
807 self.shouldContain(self.workdir, "version.c",
808 "version=%d" % 1)
809 self.shouldContain(self.workdir, "main.c", "Hello branch.")
810 subdir_c = os.path.join(self.slavebase, "vc-dir", "build",
811 "subdir", "subdir.c")
812 data = open(subdir_c, "r").read()
813 self.failUnlessIn("Hello patched subdir.\\n", data)
814 self.failUnlessEqual(bs.getProperty("revision"),
815 self.helper.branch[-1])
816 self.failUnlessEqual(bs.getProperty("branch"), self.helper.branchname)
817 self.checkGotRevision(bs, self.helper.branch[-1])
820 def do_vctest_once(self, shouldSucceed):
821 m = self.master
822 vctype = self.vctype
823 args = self.helper.vcargs
824 vcdir = os.path.join(self.slavebase, "vc-dir", "source")
825 workdir = os.path.join(self.slavebase, "vc-dir", "build")
826 # woo double-substitution
827 s = "s(%s, timeout=200, workdir='build', mode='clobber'" % (vctype,)
828 for k,v in args.items():
829 s += ", %s=%s" % (k, repr(v))
830 s += ")"
831 config = config_vc % s
833 m.loadConfig(config)
834 m.readConfig = True
835 m.startService()
837 self.connectSlave()
838 d = self.doBuild(shouldSucceed) # initial checkout
839 return d
841 def do_branch(self):
842 log.msg("do_branch")
843 vctype = self.vctype
844 args = self.helper.vcargs
845 m = self.master
846 self.vcdir = os.path.join(self.slavebase, "vc-dir", "source")
847 self.workdir = os.path.join(self.slavebase, "vc-dir", "build")
848 s = "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype,)
849 for k,v in args.items():
850 s += ", %s=%s" % (k, repr(v))
851 s += ")"
852 self.config = config_vc % s
854 m.loadConfig(self.config % "update")
855 m.readConfig = True
856 m.startService()
858 # first we do a build of the trunk
859 d = self.connectSlave()
860 d.addCallback(lambda res: self.doBuild(ss=SourceStamp()))
861 d.addCallback(self._doBranch_1)
862 return d
863 def _doBranch_1(self, bs):
864 log.msg("_doBranch_1")
865 # make sure the checkout was of the trunk
866 main_c = os.path.join(self.slavebase, "vc-dir", "build", "main.c")
867 data = open(main_c, "r").read()
868 self.failUnlessIn("Hello world.", data)
870 # now do a checkout on the branch. The change in branch name should
871 # trigger a clobber.
872 self.touch(self.workdir, "newfile")
873 d = self.doBuild(ss=SourceStamp(branch=self.helper.branchname))
874 d.addCallback(self._doBranch_2)
875 return d
876 def _doBranch_2(self, bs):
877 log.msg("_doBranch_2")
878 # make sure it was on the branch
879 main_c = os.path.join(self.slavebase, "vc-dir", "build", "main.c")
880 data = open(main_c, "r").read()
881 self.failUnlessIn("Hello branch.", data)
882 # and make sure the tree was clobbered
883 self.shouldNotExist(self.workdir, "newfile")
885 # doing another build on the same branch should not clobber the tree
886 self.touch(self.workdir, "newbranchfile")
887 d = self.doBuild(ss=SourceStamp(branch=self.helper.branchname))
888 d.addCallback(self._doBranch_3)
889 return d
890 def _doBranch_3(self, bs):
891 log.msg("_doBranch_3")
892 # make sure it is still on the branch
893 main_c = os.path.join(self.slavebase, "vc-dir", "build", "main.c")
894 data = open(main_c, "r").read()
895 self.failUnlessIn("Hello branch.", data)
896 # and make sure the tree was not clobbered
897 self.shouldExist(self.workdir, "newbranchfile")
899 # now make sure that a non-branch checkout clobbers the tree
900 d = self.doBuild(ss=SourceStamp())
901 d.addCallback(self._doBranch_4)
902 return d
903 def _doBranch_4(self, bs):
904 log.msg("_doBranch_4")
905 # make sure it was on the trunk
906 main_c = os.path.join(self.slavebase, "vc-dir", "build", "main.c")
907 data = open(main_c, "r").read()
908 self.failUnlessIn("Hello world.", data)
909 self.shouldNotExist(self.workdir, "newbranchfile")
911 def do_getpatch(self, doBranch=True):
912 log.msg("do_getpatch")
913 # prepare a buildslave to do checkouts
914 vctype = self.vctype
915 args = self.helper.vcargs
916 m = self.master
917 self.vcdir = os.path.join(self.slavebase, "vc-dir", "source")
918 self.workdir = os.path.join(self.slavebase, "vc-dir", "build")
919 # woo double-substitution
920 s = "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype,)
921 for k,v in args.items():
922 s += ", %s=%s" % (k, repr(v))
923 s += ")"
924 config = config_vc % s
926 m.loadConfig(config % 'clobber')
927 m.readConfig = True
928 m.startService()
930 d = self.connectSlave()
932 # then set up the "developer's tree". first we modify a tree from the
933 # head of the trunk
934 tmpdir = "try_workdir"
935 self.trydir = os.path.join(self.helper.repbase, tmpdir)
936 rmdirRecursive(self.trydir)
937 d.addCallback(self.do_getpatch_trunkhead)
938 d.addCallback(self.do_getpatch_trunkold)
939 if doBranch:
940 d.addCallback(self.do_getpatch_branch)
941 d.addCallback(self.do_getpatch_finish)
942 return d
944 def do_getpatch_finish(self, res):
945 log.msg("do_getpatch_finish")
946 self.helper.vc_try_finish(self.trydir)
947 return res
949 def try_shouldMatch(self, filename):
950 devfilename = os.path.join(self.trydir, filename)
951 devfile = open(devfilename, "r").read()
952 slavefilename = os.path.join(self.workdir, filename)
953 slavefile = open(slavefilename, "r").read()
954 self.failUnlessEqual(devfile, slavefile,
955 ("slavefile (%s) contains '%s'. "
956 "developer's file (%s) contains '%s'. "
957 "These ought to match") %
958 (slavefilename, slavefile,
959 devfilename, devfile))
961 def do_getpatch_trunkhead(self, res):
962 log.msg("do_getpatch_trunkhead")
963 d = self.helper.vc_try_checkout(self.trydir, self.helper.trunk[-1])
964 d.addCallback(self._do_getpatch_trunkhead_1)
965 return d
966 def _do_getpatch_trunkhead_1(self, res):
967 log.msg("_do_getpatch_trunkhead_1")
968 d = tryclient.getSourceStamp(self.vctype_try, self.trydir, None)
969 d.addCallback(self._do_getpatch_trunkhead_2)
970 return d
971 def _do_getpatch_trunkhead_2(self, ss):
972 log.msg("_do_getpatch_trunkhead_2")
973 d = self.doBuild(ss=ss)
974 d.addCallback(self._do_getpatch_trunkhead_3)
975 return d
976 def _do_getpatch_trunkhead_3(self, res):
977 log.msg("_do_getpatch_trunkhead_3")
978 # verify that the resulting buildslave tree matches the developer's
979 self.try_shouldMatch("main.c")
980 self.try_shouldMatch("version.c")
981 self.try_shouldMatch(os.path.join("subdir", "subdir.c"))
983 def do_getpatch_trunkold(self, res):
984 log.msg("do_getpatch_trunkold")
985 # now try a tree from an older revision. We need at least two
986 # revisions here, so we might have to create one first
987 if len(self.helper.trunk) < 2:
988 d = self.helper.vc_revise()
989 d.addCallback(self._do_getpatch_trunkold_1)
990 return d
991 return self._do_getpatch_trunkold_1()
992 def _do_getpatch_trunkold_1(self, res=None):
993 log.msg("_do_getpatch_trunkold_1")
994 d = self.helper.vc_try_checkout(self.trydir, self.helper.trunk[-2])
995 d.addCallback(self._do_getpatch_trunkold_2)
996 return d
997 def _do_getpatch_trunkold_2(self, res):
998 log.msg("_do_getpatch_trunkold_2")
999 d = tryclient.getSourceStamp(self.vctype_try, self.trydir, None)
1000 d.addCallback(self._do_getpatch_trunkold_3)
1001 return d
1002 def _do_getpatch_trunkold_3(self, ss):
1003 log.msg("_do_getpatch_trunkold_3")
1004 d = self.doBuild(ss=ss)
1005 d.addCallback(self._do_getpatch_trunkold_4)
1006 return d
1007 def _do_getpatch_trunkold_4(self, res):
1008 log.msg("_do_getpatch_trunkold_4")
1009 # verify that the resulting buildslave tree matches the developer's
1010 self.try_shouldMatch("main.c")
1011 self.try_shouldMatch("version.c")
1012 self.try_shouldMatch(os.path.join("subdir", "subdir.c"))
1014 def do_getpatch_branch(self, res):
1015 log.msg("do_getpatch_branch")
1016 # now try a tree from a branch
1017 d = self.helper.vc_try_checkout(self.trydir, self.helper.branch[-1],
1018 self.helper.branchname)
1019 d.addCallback(self._do_getpatch_branch_1)
1020 return d
1021 def _do_getpatch_branch_1(self, res):
1022 log.msg("_do_getpatch_branch_1")
1023 d = tryclient.getSourceStamp(self.vctype_try, self.trydir,
1024 self.helper.try_branchname)
1025 d.addCallback(self._do_getpatch_branch_2)
1026 return d
1027 def _do_getpatch_branch_2(self, ss):
1028 log.msg("_do_getpatch_branch_2")
1029 d = self.doBuild(ss=ss)
1030 d.addCallback(self._do_getpatch_branch_3)
1031 return d
1032 def _do_getpatch_branch_3(self, res):
1033 log.msg("_do_getpatch_branch_3")
1034 # verify that the resulting buildslave tree matches the developer's
1035 self.try_shouldMatch("main.c")
1036 self.try_shouldMatch("version.c")
1037 self.try_shouldMatch(os.path.join("subdir", "subdir.c"))
1040 def dumpPatch(self, patch):
1041 # this exists to help me figure out the right 'patchlevel' value
1042 # should be returned by tryclient.getSourceStamp
1043 n = self.mktemp()
1044 open(n,"w").write(patch)
1045 d = self.runCommand(".", ["lsdiff", n])
1046 def p(res): print "lsdiff:", res.strip().split("\n")
1047 d.addCallback(p)
1048 return d
1051 def tearDown(self):
1052 d = defer.succeed(None)
1053 if self.slave:
1054 d2 = self.master.botmaster.waitUntilBuilderDetached("vc")
1055 d.addCallback(lambda res: self.slave.stopService())
1056 d.addCallback(lambda res: d2)
1057 if self.master:
1058 d.addCallback(lambda res: self.master.stopService())
1059 if self.httpServer:
1060 d.addCallback(lambda res: self.httpServer.stopListening())
1061 def stopHTTPTimer():
1062 from twisted.web import http
1063 http._logDateTimeStop() # shut down the internal timer. DUMB!
1064 d.addCallback(lambda res: stopHTTPTimer())
1065 d.addCallback(lambda res: self.tearDown2())
1066 return d
1068 def tearDown2(self):
1069 pass
1071 class CVSHelper(BaseHelper):
1072 branchname = "branch"
1073 try_branchname = "branch"
1075 def capable(self):
1076 cvspaths = which('cvs')
1077 if not cvspaths:
1078 return (False, "CVS is not installed")
1079 # cvs-1.10 (as shipped with OS-X 10.3 "Panther") is too old for this
1080 # test. There is a situation where we check out a tree, make a
1081 # change, then commit it back, and CVS refuses to believe that we're
1082 # operating in a CVS tree. I tested cvs-1.12.9 and it works ok, OS-X
1083 # 10.4 "Tiger" comes with cvs-1.11, but I haven't tested that yet.
1084 # For now, skip the tests if we've got 1.10 .
1085 log.msg("running %s --version.." % (cvspaths[0],))
1086 d = utils.getProcessOutput(cvspaths[0], ["--version"],
1087 env=os.environ)
1088 d.addCallback(self._capable, cvspaths[0])
1089 return d
1091 def _capable(self, v, vcexe):
1092 m = re.search(r'\(CVS\) ([\d\.]+) ', v)
1093 if not m:
1094 log.msg("couldn't identify CVS version number in output:")
1095 log.msg("'''%s'''" % v)
1096 log.msg("skipping tests")
1097 return (False, "Found CVS but couldn't identify its version")
1098 ver = m.group(1)
1099 log.msg("found CVS version '%s'" % ver)
1100 if ver == "1.10":
1101 return (False, "Found CVS, but it is too old")
1102 self.vcexe = vcexe
1103 return (True, None)
1105 def getdate(self):
1106 # this timestamp is eventually passed to CVS in a -D argument, and
1107 # strftime's %z specifier doesn't seem to work reliably (I get +0000
1108 # where I should get +0700 under linux sometimes, and windows seems
1109 # to want to put a verbose 'Eastern Standard Time' in there), so
1110 # leave off the timezone specifier and treat this as localtime. A
1111 # valid alternative would be to use a hard-coded +0000 and
1112 # time.gmtime().
1113 return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
1115 def createRepository(self):
1116 self.createBasedir()
1117 self.cvsrep = cvsrep = os.path.join(self.repbase, "CVS-Repository")
1118 tmp = os.path.join(self.repbase, "cvstmp")
1120 w = self.dovc(self.repbase, "-d %s init" % cvsrep)
1121 yield w; w.getResult() # we must getResult() to raise any exceptions
1123 self.populate(tmp)
1124 cmd = ("-d %s import" % cvsrep +
1125 " -m sample_project_files sample vendortag start")
1126 w = self.dovc(tmp, cmd)
1127 yield w; w.getResult()
1128 rmdirRecursive(tmp)
1129 # take a timestamp as the first revision number
1130 time.sleep(2)
1131 self.addTrunkRev(self.getdate())
1132 time.sleep(2)
1134 w = self.dovc(self.repbase,
1135 "-d %s checkout -d cvstmp sample" % self.cvsrep)
1136 yield w; w.getResult()
1138 w = self.dovc(tmp, "tag -b %s" % self.branchname)
1139 yield w; w.getResult()
1140 self.populate_branch(tmp)
1141 w = self.dovc(tmp,
1142 "commit -m commit_on_branch -r %s" % self.branchname)
1143 yield w; w.getResult()
1144 rmdirRecursive(tmp)
1145 time.sleep(2)
1146 self.addBranchRev(self.getdate())
1147 time.sleep(2)
1148 self.vcargs = { 'cvsroot': self.cvsrep, 'cvsmodule': "sample" }
1149 createRepository = deferredGenerator(createRepository)
1152 def vc_revise(self):
1153 tmp = os.path.join(self.repbase, "cvstmp")
1155 w = self.dovc(self.repbase,
1156 "-d %s checkout -d cvstmp sample" % self.cvsrep)
1157 yield w; w.getResult()
1158 self.version += 1
1159 version_c = VERSION_C % self.version
1160 open(os.path.join(tmp, "version.c"), "w").write(version_c)
1161 w = self.dovc(tmp,
1162 "commit -m revised_to_%d version.c" % self.version)
1163 yield w; w.getResult()
1164 rmdirRecursive(tmp)
1165 time.sleep(2)
1166 self.addTrunkRev(self.getdate())
1167 time.sleep(2)
1168 vc_revise = deferredGenerator(vc_revise)
1170 def vc_try_checkout(self, workdir, rev, branch=None):
1171 # 'workdir' is an absolute path
1172 assert os.path.abspath(workdir) == workdir
1173 cmd = [self.vcexe, "-d", self.cvsrep, "checkout",
1174 "-d", workdir,
1175 "-D", rev]
1176 if branch is not None:
1177 cmd.append("-r")
1178 cmd.append(branch)
1179 cmd.append("sample")
1180 w = self.do(self.repbase, cmd)
1181 yield w; w.getResult()
1182 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
1183 vc_try_checkout = deferredGenerator(vc_try_checkout)
1185 def vc_try_finish(self, workdir):
1186 rmdirRecursive(workdir)
1188 class CVS(VCBase, unittest.TestCase):
1189 vc_name = "cvs"
1191 metadir = "CVS"
1192 vctype = "source.CVS"
1193 vctype_try = "cvs"
1194 # CVS gives us got_revision, but it is based entirely upon the local
1195 # clock, which means it is unlikely to match the timestamp taken earlier.
1196 # This might be enough for common use, but won't be good enough for our
1197 # tests to accept, so pretend it doesn't have got_revision at all.
1198 has_got_revision = False
1200 def testCheckout(self):
1201 d = self.do_vctest()
1202 return d
1204 def testPatch(self):
1205 d = self.do_patch()
1206 return d
1208 def testCheckoutBranch(self):
1209 d = self.do_branch()
1210 return d
1212 def testTry(self):
1213 d = self.do_getpatch(doBranch=False)
1214 return d
1216 VCS.registerVC(CVS.vc_name, CVSHelper())
1219 class SVNHelper(BaseHelper):
1220 branchname = "sample/branch"
1221 try_branchname = "sample/branch"
1223 def capable(self):
1224 svnpaths = which('svn')
1225 svnadminpaths = which('svnadmin')
1226 if not svnpaths:
1227 return (False, "SVN is not installed")
1228 if not svnadminpaths:
1229 return (False, "svnadmin is not installed")
1230 # we need svn to be compiled with the ra_local access
1231 # module
1232 log.msg("running svn --version..")
1233 env = os.environ.copy()
1234 env['LC_ALL'] = "C"
1235 d = utils.getProcessOutput(svnpaths[0], ["--version"],
1236 env=env)
1237 d.addCallback(self._capable, svnpaths[0], svnadminpaths[0])
1238 return d
1240 def _capable(self, v, vcexe, svnadmin):
1241 if v.find("handles 'file' schem") != -1:
1242 # older versions say 'schema', 1.2.0 and beyond say 'scheme'
1243 self.vcexe = vcexe
1244 self.svnadmin = svnadmin
1245 return (True, None)
1246 excuse = ("%s found but it does not support 'file:' " +
1247 "schema, skipping svn tests") % vcexe
1248 log.msg(excuse)
1249 return (False, excuse)
1251 def createRepository(self):
1252 self.createBasedir()
1253 self.svnrep = os.path.join(self.repbase,
1254 "SVN-Repository").replace('\\','/')
1255 tmp = os.path.join(self.repbase, "svntmp")
1256 if sys.platform == 'win32':
1257 # On Windows Paths do not start with a /
1258 self.svnurl = "file:///%s" % self.svnrep
1259 else:
1260 self.svnurl = "file://%s" % self.svnrep
1261 self.svnurl_trunk = self.svnurl + "/sample/trunk"
1262 self.svnurl_branch = self.svnurl + "/sample/branch"
1264 w = self.do(self.repbase, self.svnadmin+" create %s" % self.svnrep)
1265 yield w; w.getResult()
1267 self.populate(tmp)
1268 w = self.dovc(tmp,
1269 "import -m sample_project_files %s" %
1270 self.svnurl_trunk)
1271 yield w; out = w.getResult()
1272 rmdirRecursive(tmp)
1273 m = re.search(r'Committed revision (\d+)\.', out)
1274 assert m.group(1) == "1" # first revision is always "1"
1275 self.addTrunkRev(int(m.group(1)))
1277 w = self.dovc(self.repbase,
1278 "checkout %s svntmp" % self.svnurl_trunk)
1279 yield w; w.getResult()
1281 w = self.dovc(tmp, "cp -m make_branch %s %s" % (self.svnurl_trunk,
1282 self.svnurl_branch))
1283 yield w; w.getResult()
1284 w = self.dovc(tmp, "switch %s" % self.svnurl_branch)
1285 yield w; w.getResult()
1286 self.populate_branch(tmp)
1287 w = self.dovc(tmp, "commit -m commit_on_branch")
1288 yield w; out = w.getResult()
1289 rmdirRecursive(tmp)
1290 m = re.search(r'Committed revision (\d+)\.', out)
1291 self.addBranchRev(int(m.group(1)))
1292 createRepository = deferredGenerator(createRepository)
1294 def vc_revise(self):
1295 tmp = os.path.join(self.repbase, "svntmp")
1296 rmdirRecursive(tmp)
1297 log.msg("vc_revise" + self.svnurl_trunk)
1298 w = self.dovc(self.repbase,
1299 "checkout %s svntmp" % self.svnurl_trunk)
1300 yield w; w.getResult()
1301 self.version += 1
1302 version_c = VERSION_C % self.version
1303 open(os.path.join(tmp, "version.c"), "w").write(version_c)
1304 w = self.dovc(tmp, "commit -m revised_to_%d" % self.version)
1305 yield w; out = w.getResult()
1306 m = re.search(r'Committed revision (\d+)\.', out)
1307 self.addTrunkRev(int(m.group(1)))
1308 rmdirRecursive(tmp)
1309 vc_revise = deferredGenerator(vc_revise)
1311 def vc_try_checkout(self, workdir, rev, branch=None):
1312 assert os.path.abspath(workdir) == workdir
1313 if os.path.exists(workdir):
1314 rmdirRecursive(workdir)
1315 if not branch:
1316 svnurl = self.svnurl_trunk
1317 else:
1318 # N.B.: this is *not* os.path.join: SVN URLs use slashes
1319 # regardless of the host operating system's filepath separator
1320 svnurl = self.svnurl + "/" + branch
1321 w = self.dovc(self.repbase,
1322 "checkout %s %s" % (svnurl, workdir))
1323 yield w; w.getResult()
1324 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
1325 vc_try_checkout = deferredGenerator(vc_try_checkout)
1327 def vc_try_finish(self, workdir):
1328 rmdirRecursive(workdir)
1331 class SVN(VCBase, unittest.TestCase):
1332 vc_name = "svn"
1334 metadir = ".svn"
1335 vctype = "source.SVN"
1336 vctype_try = "svn"
1337 has_got_revision = True
1338 has_got_revision_branches_are_merged = True
1340 def testCheckout(self):
1341 # we verify this one with the svnurl style of vcargs. We test the
1342 # baseURL/defaultBranch style in testPatch and testCheckoutBranch.
1343 self.helper.vcargs = { 'svnurl': self.helper.svnurl_trunk }
1344 d = self.do_vctest()
1345 return d
1347 def testPatch(self):
1348 self.helper.vcargs = { 'baseURL': self.helper.svnurl + "/",
1349 'defaultBranch': "sample/trunk",
1351 d = self.do_patch()
1352 return d
1354 def testCheckoutBranch(self):
1355 self.helper.vcargs = { 'baseURL': self.helper.svnurl + "/",
1356 'defaultBranch': "sample/trunk",
1358 d = self.do_branch()
1359 return d
1361 def testTry(self):
1362 # extract the base revision and patch from a modified tree, use it to
1363 # create the same contents on the buildslave
1364 self.helper.vcargs = { 'baseURL': self.helper.svnurl + "/",
1365 'defaultBranch': "sample/trunk",
1367 d = self.do_getpatch()
1368 return d
1370 VCS.registerVC(SVN.vc_name, SVNHelper())
1373 class P4Helper(BaseHelper):
1374 branchname = "branch"
1375 p4port = 'localhost:1666'
1376 pid = None
1377 base_descr = 'Change: new\nDescription: asdf\nFiles:\n'
1379 def capable(self):
1380 p4paths = which('p4')
1381 p4dpaths = which('p4d')
1382 if not p4paths:
1383 return (False, "p4 is not installed")
1384 if not p4dpaths:
1385 return (False, "p4d is not installed")
1386 self.vcexe = p4paths[0]
1387 self.p4dexe = p4dpaths[0]
1388 return (True, None)
1390 class _P4DProtocol(protocol.ProcessProtocol):
1391 def __init__(self):
1392 self.started = defer.Deferred()
1393 self.ended = defer.Deferred()
1395 def outReceived(self, data):
1396 # When it says starting, it has bound to the socket.
1397 if self.started:
1398 if data.startswith('Perforce Server starting...'):
1399 self.started.callback(None)
1400 else:
1401 print "p4d said %r" % data
1402 try:
1403 raise Exception('p4d said %r' % data)
1404 except:
1405 self.started.errback(failure.Failure())
1406 self.started = None
1408 def errReceived(self, data):
1409 print "p4d stderr: %s" % data
1411 def processEnded(self, status_object):
1412 if status_object.check(error.ProcessDone):
1413 self.ended.callback(None)
1414 else:
1415 self.ended.errback(status_object)
1417 def _start_p4d(self):
1418 proto = self._P4DProtocol()
1419 reactor.spawnProcess(proto, self.p4dexe, ['p4d', '-p', self.p4port],
1420 env=os.environ, path=self.p4rep)
1421 return proto.started, proto.ended
1423 def dop4(self, basedir, command, failureIsOk=False, stdin=None):
1424 # p4 looks at $PWD instead of getcwd(), which causes confusion when
1425 # we spawn commands without an intervening shell (sh -c). We can
1426 # override this with a -d argument.
1427 command = "-p %s -d %s %s" % (self.p4port, basedir, command)
1428 return self.dovc(basedir, command, failureIsOk, stdin)
1430 def createRepository(self):
1431 # this is only called once per VC system, so start p4d here.
1433 self.createBasedir()
1434 tmp = os.path.join(self.repbase, "p4tmp")
1435 self.p4rep = os.path.join(self.repbase, 'P4-Repository')
1436 os.mkdir(self.p4rep)
1438 # Launch p4d.
1439 started, self.p4d_shutdown = self._start_p4d()
1440 w = waitForDeferred(started)
1441 yield w; w.getResult()
1443 # Create client spec.
1444 os.mkdir(tmp)
1445 clispec = 'Client: creator\n'
1446 clispec += 'Root: %s\n' % tmp
1447 clispec += 'View:\n'
1448 clispec += '\t//depot/... //creator/...\n'
1449 w = self.dop4(tmp, 'client -i', stdin=clispec)
1450 yield w; w.getResult()
1452 # Create first rev (trunk).
1453 self.populate(os.path.join(tmp, 'trunk'))
1454 files = ['main.c', 'version.c', 'subdir/subdir.c']
1455 w = self.dop4(tmp, "-c creator add "
1456 + " ".join(['trunk/%s' % f for f in files]))
1457 yield w; w.getResult()
1458 descr = self.base_descr
1459 for file in files:
1460 descr += '\t//depot/trunk/%s\n' % file
1461 w = self.dop4(tmp, "-c creator submit -i", stdin=descr)
1462 yield w; out = w.getResult()
1463 m = re.search(r'Change (\d+) submitted.', out)
1464 assert m.group(1) == '1'
1465 self.addTrunkRev(m.group(1))
1467 # Create second rev (branch).
1468 w = self.dop4(tmp, '-c creator integrate '
1469 + '//depot/trunk/... //depot/branch/...')
1470 yield w; w.getResult()
1471 w = self.dop4(tmp, "-c creator edit branch/main.c")
1472 yield w; w.getResult()
1473 self.populate_branch(os.path.join(tmp, 'branch'))
1474 descr = self.base_descr
1475 for file in files:
1476 descr += '\t//depot/branch/%s\n' % file
1477 w = self.dop4(tmp, "-c creator submit -i", stdin=descr)
1478 yield w; out = w.getResult()
1479 m = re.search(r'Change (\d+) submitted.', out)
1480 self.addBranchRev(m.group(1))
1481 createRepository = deferredGenerator(createRepository)
1483 def vc_revise(self):
1484 tmp = os.path.join(self.repbase, "p4tmp")
1485 self.version += 1
1486 version_c = VERSION_C % self.version
1487 w = self.dop4(tmp, '-c creator edit trunk/version.c')
1488 yield w; w.getResult()
1489 open(os.path.join(tmp, "trunk/version.c"), "w").write(version_c)
1490 descr = self.base_descr + '\t//depot/trunk/version.c\n'
1491 w = self.dop4(tmp, "-c creator submit -i", stdin=descr)
1492 yield w; out = w.getResult()
1493 m = re.search(r'Change (\d+) submitted.', out)
1494 self.addTrunkRev(m.group(1))
1495 vc_revise = deferredGenerator(vc_revise)
1497 def shutdown_p4d(self):
1498 d = self.runCommand(self.repbase, '%s -p %s admin stop'
1499 % (self.vcexe, self.p4port))
1500 return d.addCallback(lambda _: self.p4d_shutdown)
1502 class P4(VCBase, unittest.TestCase):
1503 metadir = None
1504 vctype = "source.P4"
1505 vc_name = "p4"
1507 def tearDownClass(self):
1508 if self.helper:
1509 return self.helper.shutdown_p4d()
1511 def testCheckout(self):
1512 self.helper.vcargs = { 'p4port': self.helper.p4port,
1513 'p4base': '//depot/',
1514 'defaultBranch': 'trunk' }
1515 d = self.do_vctest(testRetry=False)
1516 # TODO: like arch and darcs, sync does nothing when server is not
1517 # changed.
1518 return d
1520 def testCheckoutBranch(self):
1521 self.helper.vcargs = { 'p4port': self.helper.p4port,
1522 'p4base': '//depot/',
1523 'defaultBranch': 'trunk' }
1524 d = self.do_branch()
1525 return d
1527 def testPatch(self):
1528 self.helper.vcargs = { 'p4port': self.helper.p4port,
1529 'p4base': '//depot/',
1530 'defaultBranch': 'trunk' }
1531 d = self.do_patch()
1532 return d
1534 VCS.registerVC(P4.vc_name, P4Helper())
1537 class DarcsHelper(BaseHelper):
1538 branchname = "branch"
1539 try_branchname = "branch"
1541 def capable(self):
1542 darcspaths = which('darcs')
1543 if not darcspaths:
1544 return (False, "Darcs is not installed")
1545 self.vcexe = darcspaths[0]
1546 return (True, None)
1548 def createRepository(self):
1549 self.createBasedir()
1550 self.darcs_base = os.path.join(self.repbase, "Darcs-Repository")
1551 self.rep_trunk = os.path.join(self.darcs_base, "trunk")
1552 self.rep_branch = os.path.join(self.darcs_base, "branch")
1553 tmp = os.path.join(self.repbase, "darcstmp")
1555 os.makedirs(self.rep_trunk)
1556 w = self.dovc(self.rep_trunk, ["initialize"])
1557 yield w; w.getResult()
1558 os.makedirs(self.rep_branch)
1559 w = self.dovc(self.rep_branch, ["initialize"])
1560 yield w; w.getResult()
1562 self.populate(tmp)
1563 w = self.dovc(tmp, qw("initialize"))
1564 yield w; w.getResult()
1565 w = self.dovc(tmp, qw("add -r ."))
1566 yield w; w.getResult()
1567 w = self.dovc(tmp, qw("record -a -m initial_import --skip-long-comment -A test@buildbot.sf.net"))
1568 yield w; w.getResult()
1569 w = self.dovc(tmp, ["push", "-a", self.rep_trunk])
1570 yield w; w.getResult()
1571 w = self.dovc(tmp, qw("changes --context"))
1572 yield w; out = w.getResult()
1573 self.addTrunkRev(out)
1575 self.populate_branch(tmp)
1576 w = self.dovc(tmp, qw("record -a --ignore-times -m commit_on_branch --skip-long-comment -A test@buildbot.sf.net"))
1577 yield w; w.getResult()
1578 w = self.dovc(tmp, ["push", "-a", self.rep_branch])
1579 yield w; w.getResult()
1580 w = self.dovc(tmp, qw("changes --context"))
1581 yield w; out = w.getResult()
1582 self.addBranchRev(out)
1583 rmdirRecursive(tmp)
1584 createRepository = deferredGenerator(createRepository)
1586 def vc_revise(self):
1587 tmp = os.path.join(self.repbase, "darcstmp")
1588 os.makedirs(tmp)
1589 w = self.dovc(tmp, qw("initialize"))
1590 yield w; w.getResult()
1591 w = self.dovc(tmp, ["pull", "-a", self.rep_trunk])
1592 yield w; w.getResult()
1594 self.version += 1
1595 version_c = VERSION_C % self.version
1596 open(os.path.join(tmp, "version.c"), "w").write(version_c)
1597 w = self.dovc(tmp, qw("record -a --ignore-times -m revised_to_%d --skip-long-comment -A test@buildbot.sf.net" % self.version))
1598 yield w; w.getResult()
1599 w = self.dovc(tmp, ["push", "-a", self.rep_trunk])
1600 yield w; w.getResult()
1601 w = self.dovc(tmp, qw("changes --context"))
1602 yield w; out = w.getResult()
1603 self.addTrunkRev(out)
1604 rmdirRecursive(tmp)
1605 vc_revise = deferredGenerator(vc_revise)
1607 def vc_try_checkout(self, workdir, rev, branch=None):
1608 assert os.path.abspath(workdir) == workdir
1609 if os.path.exists(workdir):
1610 rmdirRecursive(workdir)
1611 os.makedirs(workdir)
1612 w = self.dovc(workdir, qw("initialize"))
1613 yield w; w.getResult()
1614 if not branch:
1615 rep = self.rep_trunk
1616 else:
1617 rep = os.path.join(self.darcs_base, branch)
1618 w = self.dovc(workdir, ["pull", "-a", rep])
1619 yield w; w.getResult()
1620 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
1621 vc_try_checkout = deferredGenerator(vc_try_checkout)
1623 def vc_try_finish(self, workdir):
1624 rmdirRecursive(workdir)
1627 class Darcs(VCBase, unittest.TestCase):
1628 vc_name = "darcs"
1630 # Darcs has a metadir="_darcs", but it does not have an 'export'
1631 # mode
1632 metadir = None
1633 vctype = "source.Darcs"
1634 vctype_try = "darcs"
1635 has_got_revision = True
1637 def testCheckout(self):
1638 self.helper.vcargs = { 'repourl': self.helper.rep_trunk }
1639 d = self.do_vctest(testRetry=False)
1641 # TODO: testRetry has the same problem with Darcs as it does for
1642 # Arch
1643 return d
1645 def testPatch(self):
1646 self.helper.vcargs = { 'baseURL': self.helper.darcs_base + "/",
1647 'defaultBranch': "trunk" }
1648 d = self.do_patch()
1649 return d
1651 def testCheckoutBranch(self):
1652 self.helper.vcargs = { 'baseURL': self.helper.darcs_base + "/",
1653 'defaultBranch': "trunk" }
1654 d = self.do_branch()
1655 return d
1657 def testCheckoutHTTP(self):
1658 self.serveHTTP()
1659 repourl = "http://localhost:%d/Darcs-Repository/trunk" % self.httpPort
1660 self.helper.vcargs = { 'repourl': repourl }
1661 d = self.do_vctest(testRetry=False)
1662 return d
1664 def testTry(self):
1665 self.helper.vcargs = { 'baseURL': self.helper.darcs_base + "/",
1666 'defaultBranch': "trunk" }
1667 d = self.do_getpatch()
1668 return d
1670 VCS.registerVC(Darcs.vc_name, DarcsHelper())
1673 class ArchCommon:
1674 def registerRepository(self, coordinates):
1675 a = self.archname
1676 w = self.dovc(self.repbase, "archives %s" % a)
1677 yield w; out = w.getResult()
1678 if out:
1679 w = self.dovc(self.repbase, "register-archive -d %s" % a)
1680 yield w; w.getResult()
1681 w = self.dovc(self.repbase, "register-archive %s" % coordinates)
1682 yield w; w.getResult()
1683 registerRepository = deferredGenerator(registerRepository)
1685 def unregisterRepository(self):
1686 a = self.archname
1687 w = self.dovc(self.repbase, "archives %s" % a)
1688 yield w; out = w.getResult()
1689 if out:
1690 w = self.dovc(self.repbase, "register-archive -d %s" % a)
1691 yield w; out = w.getResult()
1692 unregisterRepository = deferredGenerator(unregisterRepository)
1694 class TlaHelper(BaseHelper, ArchCommon):
1695 defaultbranch = "testvc--mainline--1"
1696 branchname = "testvc--branch--1"
1697 try_branchname = None # TlaExtractor can figure it out by itself
1698 archcmd = "tla"
1700 def capable(self):
1701 tlapaths = which('tla')
1702 if not tlapaths:
1703 return (False, "Arch (tla) is not installed")
1704 self.vcexe = tlapaths[0]
1705 return (True, None)
1707 def do_get(self, basedir, archive, branch, newdir):
1708 # the 'get' syntax is different between tla and baz. baz, while
1709 # claiming to honor an --archive argument, in fact ignores it. The
1710 # correct invocation is 'baz get archive/revision newdir'.
1711 if self.archcmd == "tla":
1712 w = self.dovc(basedir,
1713 "get -A %s %s %s" % (archive, branch, newdir))
1714 else:
1715 w = self.dovc(basedir,
1716 "get %s/%s %s" % (archive, branch, newdir))
1717 return w
1719 def createRepository(self):
1720 self.createBasedir()
1721 # first check to see if bazaar is around, since we'll need to know
1722 # later
1723 d = VCS.capable(Bazaar.vc_name)
1724 d.addCallback(self._createRepository_1)
1725 return d
1727 def _createRepository_1(self, res):
1728 has_baz = res[0]
1730 # pick a hopefully unique string for the archive name, in the form
1731 # test-%d@buildbot.sf.net--testvc, since otherwise multiple copies of
1732 # the unit tests run in the same user account will collide (since the
1733 # archive names are kept in the per-user ~/.arch-params/ directory).
1734 pid = os.getpid()
1735 self.archname = "test-%s-%d@buildbot.sf.net--testvc" % (self.archcmd,
1736 pid)
1737 trunk = self.defaultbranch
1738 branch = self.branchname
1740 repword = self.archcmd.capitalize()
1741 self.archrep = os.path.join(self.repbase, "%s-Repository" % repword)
1742 tmp = os.path.join(self.repbase, "archtmp")
1743 a = self.archname
1745 self.populate(tmp)
1747 w = self.dovc(tmp, "my-id", failureIsOk=True)
1748 yield w; res = w.getResult()
1749 if not res:
1750 # tla will fail a lot of operations if you have not set an ID
1751 w = self.do(tmp, [self.vcexe, "my-id",
1752 "Buildbot Test Suite <test@buildbot.sf.net>"])
1753 yield w; w.getResult()
1755 if has_baz:
1756 # bazaar keeps a cache of revisions, but this test creates a new
1757 # archive each time it is run, so the cache causes errors.
1758 # Disable the cache to avoid these problems. This will be
1759 # slightly annoying for people who run the buildbot tests under
1760 # the same UID as one which uses baz on a regular basis, but
1761 # bazaar doesn't give us a way to disable the cache just for this
1762 # one archive.
1763 cmd = "%s cache-config --disable" % VCS.getHelper('bazaar').vcexe
1764 w = self.do(tmp, cmd)
1765 yield w; w.getResult()
1767 w = waitForDeferred(self.unregisterRepository())
1768 yield w; w.getResult()
1770 # these commands can be run in any directory
1771 w = self.dovc(tmp, "make-archive -l %s %s" % (a, self.archrep))
1772 yield w; w.getResult()
1773 if self.archcmd == "tla":
1774 w = self.dovc(tmp, "archive-setup -A %s %s" % (a, trunk))
1775 yield w; w.getResult()
1776 w = self.dovc(tmp, "archive-setup -A %s %s" % (a, branch))
1777 yield w; w.getResult()
1778 else:
1779 # baz does not require an 'archive-setup' step
1780 pass
1782 # these commands must be run in the directory that is to be imported
1783 w = self.dovc(tmp, "init-tree --nested %s/%s" % (a, trunk))
1784 yield w; w.getResult()
1785 files = " ".join(["main.c", "version.c", "subdir",
1786 os.path.join("subdir", "subdir.c")])
1787 w = self.dovc(tmp, "add-id %s" % files)
1788 yield w; w.getResult()
1790 w = self.dovc(tmp, "import %s/%s" % (a, trunk))
1791 yield w; out = w.getResult()
1792 self.addTrunkRev("base-0")
1794 # create the branch
1795 if self.archcmd == "tla":
1796 branchstart = "%s--base-0" % trunk
1797 w = self.dovc(tmp, "tag -A %s %s %s" % (a, branchstart, branch))
1798 yield w; w.getResult()
1799 else:
1800 w = self.dovc(tmp, "branch %s" % branch)
1801 yield w; w.getResult()
1803 rmdirRecursive(tmp)
1805 # check out the branch
1806 w = self.do_get(self.repbase, a, branch, "archtmp")
1807 yield w; w.getResult()
1808 # and edit the file
1809 self.populate_branch(tmp)
1810 logfile = "++log.%s--%s" % (branch, a)
1811 logmsg = "Summary: commit on branch\nKeywords:\n\n"
1812 open(os.path.join(tmp, logfile), "w").write(logmsg)
1813 w = self.dovc(tmp, "commit")
1814 yield w; out = w.getResult()
1815 m = re.search(r'committed %s/%s--([\S]+)' % (a, branch),
1816 out)
1817 assert (m.group(1) == "base-0" or m.group(1).startswith("patch-"))
1818 self.addBranchRev(m.group(1))
1820 w = waitForDeferred(self.unregisterRepository())
1821 yield w; w.getResult()
1822 rmdirRecursive(tmp)
1824 # we unregister the repository each time, because we might have
1825 # changed the coordinates (since we switch from a file: URL to an
1826 # http: URL for various tests). The buildslave code doesn't forcibly
1827 # unregister the archive, so we have to do it here.
1828 w = waitForDeferred(self.unregisterRepository())
1829 yield w; w.getResult()
1831 _createRepository_1 = deferredGenerator(_createRepository_1)
1833 def vc_revise(self):
1834 # the fix needs to be done in a workspace that is linked to a
1835 # read-write version of the archive (i.e., using file-based
1836 # coordinates instead of HTTP ones), so we re-register the repository
1837 # before we begin. We unregister it when we're done to make sure the
1838 # build will re-register the correct one for whichever test is
1839 # currently being run.
1841 # except, that source.Bazaar really doesn't like it when the archive
1842 # gets unregistered behind its back. The slave tries to do a 'baz
1843 # replay' in a tree with an archive that is no longer recognized, and
1844 # baz aborts with a botched invariant exception. This causes
1845 # mode=update to fall back to clobber+get, which flunks one of the
1846 # tests (the 'newfile' check in _do_vctest_update_3 fails)
1848 # to avoid this, we take heroic steps here to leave the archive
1849 # registration in the same state as we found it.
1851 tmp = os.path.join(self.repbase, "archtmp")
1852 a = self.archname
1854 w = self.dovc(self.repbase, "archives %s" % a)
1855 yield w; out = w.getResult()
1856 assert out
1857 lines = out.split("\n")
1858 coordinates = lines[1].strip()
1860 # now register the read-write location
1861 w = waitForDeferred(self.registerRepository(self.archrep))
1862 yield w; w.getResult()
1864 trunk = self.defaultbranch
1866 w = self.do_get(self.repbase, a, trunk, "archtmp")
1867 yield w; w.getResult()
1869 # tla appears to use timestamps to determine which files have
1870 # changed, so wait long enough for the new file to have a different
1871 # timestamp
1872 time.sleep(2)
1873 self.version += 1
1874 version_c = VERSION_C % self.version
1875 open(os.path.join(tmp, "version.c"), "w").write(version_c)
1877 logfile = "++log.%s--%s" % (trunk, a)
1878 logmsg = "Summary: revised_to_%d\nKeywords:\n\n" % self.version
1879 open(os.path.join(tmp, logfile), "w").write(logmsg)
1880 w = self.dovc(tmp, "commit")
1881 yield w; out = w.getResult()
1882 m = re.search(r'committed %s/%s--([\S]+)' % (a, trunk),
1883 out)
1884 assert (m.group(1) == "base-0" or m.group(1).startswith("patch-"))
1885 self.addTrunkRev(m.group(1))
1887 # now re-register the original coordinates
1888 w = waitForDeferred(self.registerRepository(coordinates))
1889 yield w; w.getResult()
1890 rmdirRecursive(tmp)
1891 vc_revise = deferredGenerator(vc_revise)
1893 def vc_try_checkout(self, workdir, rev, branch=None):
1894 assert os.path.abspath(workdir) == workdir
1895 if os.path.exists(workdir):
1896 rmdirRecursive(workdir)
1898 a = self.archname
1900 # register the read-write location, if it wasn't already registered
1901 w = waitForDeferred(self.registerRepository(self.archrep))
1902 yield w; w.getResult()
1904 w = self.do_get(self.repbase, a, "testvc--mainline--1", workdir)
1905 yield w; w.getResult()
1907 # timestamps. ick.
1908 time.sleep(2)
1909 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
1910 vc_try_checkout = deferredGenerator(vc_try_checkout)
1912 def vc_try_finish(self, workdir):
1913 rmdirRecursive(workdir)
1915 class Arch(VCBase, unittest.TestCase):
1916 vc_name = "tla"
1918 metadir = None
1919 # Arch has a metadir="{arch}", but it does not have an 'export' mode.
1920 vctype = "source.Arch"
1921 vctype_try = "tla"
1922 has_got_revision = True
1924 def testCheckout(self):
1925 # these are the coordinates of the read-write archive used by all the
1926 # non-HTTP tests. testCheckoutHTTP overrides these.
1927 self.helper.vcargs = {'url': self.helper.archrep,
1928 'version': self.helper.defaultbranch }
1929 d = self.do_vctest(testRetry=False)
1930 # the current testRetry=True logic doesn't have the desired effect:
1931 # "update" is a no-op because arch knows that the repository hasn't
1932 # changed. Other VC systems will re-checkout missing files on
1933 # update, arch just leaves the tree untouched. TODO: come up with
1934 # some better test logic, probably involving a copy of the
1935 # repository that has a few changes checked in.
1937 return d
1939 def testCheckoutHTTP(self):
1940 self.serveHTTP()
1941 url = "http://localhost:%d/Tla-Repository" % self.httpPort
1942 self.helper.vcargs = { 'url': url,
1943 'version': "testvc--mainline--1" }
1944 d = self.do_vctest(testRetry=False)
1945 return d
1947 def testPatch(self):
1948 self.helper.vcargs = {'url': self.helper.archrep,
1949 'version': self.helper.defaultbranch }
1950 d = self.do_patch()
1951 return d
1953 def testCheckoutBranch(self):
1954 self.helper.vcargs = {'url': self.helper.archrep,
1955 'version': self.helper.defaultbranch }
1956 d = self.do_branch()
1957 return d
1959 def testTry(self):
1960 self.helper.vcargs = {'url': self.helper.archrep,
1961 'version': self.helper.defaultbranch }
1962 d = self.do_getpatch()
1963 return d
1965 VCS.registerVC(Arch.vc_name, TlaHelper())
1968 class BazaarHelper(TlaHelper):
1969 archcmd = "baz"
1971 def capable(self):
1972 bazpaths = which('baz')
1973 if not bazpaths:
1974 return (False, "Arch (baz) is not installed")
1975 self.vcexe = bazpaths[0]
1976 return (True, None)
1978 def setUp2(self, res):
1979 # we unregister the repository each time, because we might have
1980 # changed the coordinates (since we switch from a file: URL to an
1981 # http: URL for various tests). The buildslave code doesn't forcibly
1982 # unregister the archive, so we have to do it here.
1983 d = self.unregisterRepository()
1984 return d
1987 class Bazaar(Arch):
1988 vc_name = "bazaar"
1990 vctype = "source.Bazaar"
1991 vctype_try = "baz"
1992 has_got_revision = True
1994 fixtimer = None
1996 def testCheckout(self):
1997 self.helper.vcargs = {'url': self.helper.archrep,
1998 # Baz adds the required 'archive' argument
1999 'archive': self.helper.archname,
2000 'version': self.helper.defaultbranch,
2002 d = self.do_vctest(testRetry=False)
2003 # the current testRetry=True logic doesn't have the desired effect:
2004 # "update" is a no-op because arch knows that the repository hasn't
2005 # changed. Other VC systems will re-checkout missing files on
2006 # update, arch just leaves the tree untouched. TODO: come up with
2007 # some better test logic, probably involving a copy of the
2008 # repository that has a few changes checked in.
2010 return d
2012 def testCheckoutHTTP(self):
2013 self.serveHTTP()
2014 url = "http://localhost:%d/Baz-Repository" % self.httpPort
2015 self.helper.vcargs = { 'url': url,
2016 'archive': self.helper.archname,
2017 'version': self.helper.defaultbranch,
2019 d = self.do_vctest(testRetry=False)
2020 return d
2022 def testPatch(self):
2023 self.helper.vcargs = {'url': self.helper.archrep,
2024 # Baz adds the required 'archive' argument
2025 'archive': self.helper.archname,
2026 'version': self.helper.defaultbranch,
2028 d = self.do_patch()
2029 return d
2031 def testCheckoutBranch(self):
2032 self.helper.vcargs = {'url': self.helper.archrep,
2033 # Baz adds the required 'archive' argument
2034 'archive': self.helper.archname,
2035 'version': self.helper.defaultbranch,
2037 d = self.do_branch()
2038 return d
2040 def testTry(self):
2041 self.helper.vcargs = {'url': self.helper.archrep,
2042 # Baz adds the required 'archive' argument
2043 'archive': self.helper.archname,
2044 'version': self.helper.defaultbranch,
2046 d = self.do_getpatch()
2047 return d
2049 def fixRepository(self):
2050 self.fixtimer = None
2051 self.site.resource = self.root
2053 def testRetry(self):
2054 # we want to verify that source.Source(retry=) works, and the easiest
2055 # way to make VC updates break (temporarily) is to break the HTTP
2056 # server that's providing the repository. Anything else pretty much
2057 # requires mutating the (read-only) BUILDBOT_TEST_VC repository, or
2058 # modifying the buildslave's checkout command while it's running.
2060 # this test takes a while to run, so don't bother doing it with
2061 # anything other than baz
2063 self.serveHTTP()
2065 # break the repository server
2066 from twisted.web import static
2067 self.site.resource = static.Data("Sorry, repository is offline",
2068 "text/plain")
2069 # and arrange to fix it again in 5 seconds, while the test is
2070 # running.
2071 self.fixtimer = reactor.callLater(5, self.fixRepository)
2073 url = "http://localhost:%d/Baz-Repository" % self.httpPort
2074 self.helper.vcargs = { 'url': url,
2075 'archive': self.helper.archname,
2076 'version': self.helper.defaultbranch,
2077 'retry': (5.0, 4),
2079 d = self.do_vctest_once(True)
2080 d.addCallback(self._testRetry_1)
2081 return d
2082 def _testRetry_1(self, bs):
2083 # make sure there was mention of the retry attempt in the logs
2084 l = bs.getLogs()[0]
2085 self.failUnlessIn("unable to access URL", l.getText(),
2086 "funny, VC operation didn't fail at least once")
2087 self.failUnlessIn("update failed, trying 4 more times after 5 seconds",
2088 l.getTextWithHeaders(),
2089 "funny, VC operation wasn't reattempted")
2091 def testRetryFails(self):
2092 # make sure that the build eventually gives up on a repository which
2093 # is completely unavailable
2095 self.serveHTTP()
2097 # break the repository server, and leave it broken
2098 from twisted.web import static
2099 self.site.resource = static.Data("Sorry, repository is offline",
2100 "text/plain")
2102 url = "http://localhost:%d/Baz-Repository" % self.httpPort
2103 self.helper.vcargs = {'url': url,
2104 'archive': self.helper.archname,
2105 'version': self.helper.defaultbranch,
2106 'retry': (0.5, 3),
2108 d = self.do_vctest_once(False)
2109 d.addCallback(self._testRetryFails_1)
2110 return d
2111 def _testRetryFails_1(self, bs):
2112 self.failUnlessEqual(bs.getResults(), FAILURE)
2114 def tearDown2(self):
2115 if self.fixtimer:
2116 self.fixtimer.cancel()
2117 # tell tla to get rid of the leftover archive this test leaves in the
2118 # user's 'tla archives' listing. The name of this archive is provided
2119 # by the repository tarball, so the following command must use the
2120 # same name. We could use archive= to set it explicitly, but if you
2121 # change it from the default, then 'tla update' won't work.
2122 d = self.helper.unregisterRepository()
2123 return d
2125 VCS.registerVC(Bazaar.vc_name, BazaarHelper())
2127 class BzrHelper(BaseHelper):
2128 branchname = "branch"
2129 try_branchname = "branch"
2131 def capable(self):
2132 bzrpaths = which('bzr')
2133 if not bzrpaths:
2134 return (False, "bzr is not installed")
2135 self.vcexe = bzrpaths[0]
2136 return (True, None)
2138 def get_revision_number(self, out):
2139 for line in out.split("\n"):
2140 colon = line.index(":")
2141 key, value = line[:colon], line[colon+2:]
2142 if key == "revno":
2143 return int(value)
2144 raise RuntimeError("unable to find revno: in bzr output: '%s'" % out)
2146 def createRepository(self):
2147 self.createBasedir()
2148 self.bzr_base = os.path.join(self.repbase, "Bzr-Repository")
2149 self.rep_trunk = os.path.join(self.bzr_base, "trunk")
2150 self.rep_branch = os.path.join(self.bzr_base, "branch")
2151 tmp = os.path.join(self.repbase, "bzrtmp")
2152 btmp = os.path.join(self.repbase, "bzrtmp-branch")
2154 os.makedirs(self.rep_trunk)
2155 w = self.dovc(self.rep_trunk, ["init"])
2156 yield w; w.getResult()
2157 w = self.dovc(self.bzr_base,
2158 ["branch", self.rep_trunk, self.rep_branch])
2159 yield w; w.getResult()
2161 w = self.dovc(self.repbase, ["checkout", self.rep_trunk, tmp])
2162 yield w; w.getResult()
2163 self.populate(tmp)
2164 w = self.dovc(tmp, qw("add"))
2165 yield w; w.getResult()
2166 w = self.dovc(tmp, qw("commit -m initial_import"))
2167 yield w; w.getResult()
2168 w = self.dovc(tmp, qw("version-info"))
2169 yield w; out = w.getResult()
2170 self.addTrunkRev(self.get_revision_number(out))
2171 rmdirRecursive(tmp)
2173 # pull all trunk revisions to the branch
2174 w = self.dovc(self.rep_branch, qw("pull"))
2175 yield w; w.getResult()
2176 # obtain a branch tree
2177 w = self.dovc(self.repbase, ["checkout", self.rep_branch, btmp])
2178 yield w; w.getResult()
2179 # modify it
2180 self.populate_branch(btmp)
2181 w = self.dovc(btmp, qw("add"))
2182 yield w; w.getResult()
2183 w = self.dovc(btmp, qw("commit -m commit_on_branch"))
2184 yield w; w.getResult()
2185 w = self.dovc(btmp, qw("version-info"))
2186 yield w; out = w.getResult()
2187 self.addBranchRev(self.get_revision_number(out))
2188 rmdirRecursive(btmp)
2189 createRepository = deferredGenerator(createRepository)
2191 def vc_revise(self):
2192 tmp = os.path.join(self.repbase, "bzrtmp")
2193 w = self.dovc(self.repbase, ["checkout", self.rep_trunk, tmp])
2194 yield w; w.getResult()
2196 self.version += 1
2197 version_c = VERSION_C % self.version
2198 open(os.path.join(tmp, "version.c"), "w").write(version_c)
2199 w = self.dovc(tmp, qw("commit -m revised_to_%d" % self.version))
2200 yield w; w.getResult()
2201 w = self.dovc(tmp, qw("version-info"))
2202 yield w; out = w.getResult()
2203 self.addTrunkRev(self.get_revision_number(out))
2204 rmdirRecursive(tmp)
2205 vc_revise = deferredGenerator(vc_revise)
2207 def vc_try_checkout(self, workdir, rev, branch=None):
2208 assert os.path.abspath(workdir) == workdir
2209 if os.path.exists(workdir):
2210 rmdirRecursive(workdir)
2211 #os.makedirs(workdir)
2212 if not branch:
2213 rep = self.rep_trunk
2214 else:
2215 rep = os.path.join(self.bzr_base, branch)
2216 w = self.dovc(self.bzr_base, ["checkout", rep, workdir])
2217 yield w; w.getResult()
2218 open(os.path.join(workdir, "subdir", "subdir.c"), "w").write(TRY_C)
2219 vc_try_checkout = deferredGenerator(vc_try_checkout)
2221 def vc_try_finish(self, workdir):
2222 rmdirRecursive(workdir)
2224 class Bzr(VCBase, unittest.TestCase):
2225 vc_name = "bzr"
2227 metadir = ".bzr"
2228 vctype = "source.Bzr"
2229 vctype_try = "bzr"
2230 has_got_revision = True
2232 def testCheckout(self):
2233 self.helper.vcargs = { 'repourl': self.helper.rep_trunk }
2234 d = self.do_vctest(testRetry=False)
2236 # TODO: testRetry has the same problem with Bzr as it does for
2237 # Arch
2238 return d
2240 def testPatch(self):
2241 self.helper.vcargs = { 'baseURL': self.helper.bzr_base + "/",
2242 'defaultBranch': "trunk" }
2243 d = self.do_patch()
2244 return d
2246 def testCheckoutBranch(self):
2247 self.helper.vcargs = { 'baseURL': self.helper.bzr_base + "/",
2248 'defaultBranch': "trunk" }
2249 d = self.do_branch()
2250 return d
2252 def testCheckoutHTTP(self):
2253 self.serveHTTP()
2254 repourl = "http://localhost:%d/Bzr-Repository/trunk" % self.httpPort
2255 self.helper.vcargs = { 'repourl': repourl }
2256 d = self.do_vctest(testRetry=False)
2257 return d
2260 def fixRepository(self):
2261 self.fixtimer = None
2262 self.site.resource = self.root
2264 def testRetry(self):
2265 # this test takes a while to run
2266 self.serveHTTP()
2268 # break the repository server
2269 from twisted.web import static
2270 self.site.resource = static.Data("Sorry, repository is offline",
2271 "text/plain")
2272 # and arrange to fix it again in 5 seconds, while the test is
2273 # running.
2274 self.fixtimer = reactor.callLater(5, self.fixRepository)
2276 repourl = "http://localhost:%d/Bzr-Repository/trunk" % self.httpPort
2277 self.helper.vcargs = { 'repourl': repourl,
2278 'retry': (5.0, 4),
2280 d = self.do_vctest_once(True)
2281 d.addCallback(self._testRetry_1)
2282 return d
2283 def _testRetry_1(self, bs):
2284 # make sure there was mention of the retry attempt in the logs
2285 l = bs.getLogs()[0]
2286 self.failUnlessIn("ERROR: Not a branch: ", l.getText(),
2287 "funny, VC operation didn't fail at least once")
2288 self.failUnlessIn("update failed, trying 4 more times after 5 seconds",
2289 l.getTextWithHeaders(),
2290 "funny, VC operation wasn't reattempted")
2292 def testRetryFails(self):
2293 # make sure that the build eventually gives up on a repository which
2294 # is completely unavailable
2296 self.serveHTTP()
2298 # break the repository server, and leave it broken
2299 from twisted.web import static
2300 self.site.resource = static.Data("Sorry, repository is offline",
2301 "text/plain")
2303 repourl = "http://localhost:%d/Bzr-Repository/trunk" % self.httpPort
2304 self.helper.vcargs = { 'repourl': repourl,
2305 'retry': (0.5, 3),
2307 d = self.do_vctest_once(False)
2308 d.addCallback(self._testRetryFails_1)
2309 return d
2310 def _testRetryFails_1(self, bs):
2311 self.failUnlessEqual(bs.getResults(), FAILURE)
2314 def testTry(self):
2315 self.helper.vcargs = { 'baseURL': self.helper.bzr_base + "/",
2316 'defaultBranch': "trunk" }
2317 d = self.do_getpatch()
2318 return d
2320 VCS.registerVC(Bzr.vc_name, BzrHelper())
2323 class MercurialHelper(BaseHelper):
2324 branchname = "branch"
2325 try_branchname = "branch"
2327 def capable(self):
2328 hgpaths = which("hg")
2329 if not hgpaths:
2330 return (False, "Mercurial is not installed")
2331 self.vcexe = hgpaths[0]
2332 return (True, None)
2334 def extract_id(self, output):
2335 m = re.search(r'^(\w+)', output)
2336 return m.group(0)
2338 def createRepository(self):
2339 self.createBasedir()
2340 self.hg_base = os.path.join(self.repbase, "Mercurial-Repository")
2341 self.rep_trunk = os.path.join(self.hg_base, "trunk")
2342 self.rep_branch = os.path.join(self.hg_base, "branch")
2343 tmp = os.path.join(self.hg_base, "hgtmp")
2345 os.makedirs(self.rep_trunk)
2346 w = self.dovc(self.rep_trunk, "init")
2347 yield w; w.getResult()
2348 os.makedirs(self.rep_branch)
2349 w = self.dovc(self.rep_branch, "init")
2350 yield w; w.getResult()
2352 self.populate(tmp)
2353 w = self.dovc(tmp, "init")
2354 yield w; w.getResult()
2355 w = self.dovc(tmp, "add")
2356 yield w; w.getResult()
2357 w = self.dovc(tmp, "commit -m initial_import")
2358 yield w; w.getResult()
2359 w = self.dovc(tmp, "push %s" % self.rep_trunk)
2360 # note that hg-push does not actually update the working directory
2361 yield w; w.getResult()
2362 w = self.dovc(tmp, "identify")
2363 yield w; out = w.getResult()
2364 self.addTrunkRev(self.extract_id(out))
2366 self.populate_branch(tmp)
2367 w = self.dovc(tmp, "commit -m commit_on_branch")
2368 yield w; w.getResult()
2369 w = self.dovc(tmp, "push %s" % self.rep_branch)
2370 yield w; w.getResult()
2371 w = self.dovc(tmp, "identify")
2372 yield w; out = w.getResult()
2373 self.addBranchRev(self.extract_id(out))
2374 rmdirRecursive(tmp)
2375 createRepository = deferredGenerator(createRepository)
2377 def vc_revise(self):
2378 tmp = os.path.join(self.hg_base, "hgtmp2")
2379 w = self.dovc(self.hg_base, "clone %s %s" % (self.rep_trunk, tmp))
2380 yield w; w.getResult()
2382 self.version += 1
2383 version_c = VERSION_C % self.version
2384 version_c_filename = os.path.join(tmp, "version.c")
2385 open(version_c_filename, "w").write(version_c)
2386 # hg uses timestamps to distinguish files which have changed, so we
2387 # force the mtime forward a little bit
2388 future = time.time() + 2*self.version
2389 os.utime(version_c_filename, (future, future))
2390 w = self.dovc(tmp, "commit -m revised_to_%d" % self.version)
2391 yield w; w.getResult()
2392 w = self.dovc(tmp, "push %s" % self.rep_trunk)
2393 yield w; w.getResult()
2394 w = self.dovc(tmp, "identify")
2395 yield w; out = w.getResult()
2396 self.addTrunkRev(self.extract_id(out))
2397 rmdirRecursive(tmp)
2398 vc_revise = deferredGenerator(vc_revise)
2400 def vc_try_checkout(self, workdir, rev, branch=None):
2401 assert os.path.abspath(workdir) == workdir
2402 if os.path.exists(workdir):
2403 rmdirRecursive(workdir)
2404 if branch:
2405 src = self.rep_branch
2406 else:
2407 src = self.rep_trunk
2408 w = self.dovc(self.hg_base, "clone %s %s" % (src, workdir))
2409 yield w; w.getResult()
2410 try_c_filename = os.path.join(workdir, "subdir", "subdir.c")
2411 open(try_c_filename, "w").write(TRY_C)
2412 future = time.time() + 2*self.version
2413 os.utime(try_c_filename, (future, future))
2414 vc_try_checkout = deferredGenerator(vc_try_checkout)
2416 def vc_try_finish(self, workdir):
2417 rmdirRecursive(workdir)
2420 class Mercurial(VCBase, unittest.TestCase):
2421 vc_name = "hg"
2423 # Mercurial has a metadir=".hg", but it does not have an 'export' mode.
2424 metadir = None
2425 vctype = "source.Mercurial"
2426 vctype_try = "hg"
2427 has_got_revision = True
2429 def testCheckout(self):
2430 self.helper.vcargs = { 'repourl': self.helper.rep_trunk }
2431 d = self.do_vctest(testRetry=False)
2433 # TODO: testRetry has the same problem with Mercurial as it does for
2434 # Arch
2435 return d
2437 def testPatch(self):
2438 self.helper.vcargs = { 'baseURL': self.helper.hg_base + "/",
2439 'defaultBranch': "trunk" }
2440 d = self.do_patch()
2441 return d
2443 def testCheckoutBranch(self):
2444 self.helper.vcargs = { 'baseURL': self.helper.hg_base + "/",
2445 'defaultBranch': "trunk" }
2446 d = self.do_branch()
2447 return d
2449 def testCheckoutHTTP(self):
2450 self.serveHTTP()
2451 repourl = "http://localhost:%d/Mercurial-Repository/trunk/.hg" % self.httpPort
2452 self.helper.vcargs = { 'repourl': repourl }
2453 d = self.do_vctest(testRetry=False)
2454 return d
2455 # TODO: The easiest way to publish hg over HTTP is by running 'hg serve'
2456 # as a child process while the test is running. (you can also use a CGI
2457 # script, which sounds difficult, or you can publish the files directly,
2458 # which isn't well documented).
2459 testCheckoutHTTP.skip = "not yet implemented, use 'hg serve'"
2461 def testTry(self):
2462 self.helper.vcargs = { 'baseURL': self.helper.hg_base + "/",
2463 'defaultBranch': "trunk" }
2464 d = self.do_getpatch()
2465 return d
2467 VCS.registerVC(Mercurial.vc_name, MercurialHelper())
2470 class Sources(unittest.TestCase):
2471 # TODO: this needs serious rethink
2472 def makeChange(self, when=None, revision=None):
2473 if when:
2474 when = mktime_tz(parsedate_tz(when))
2475 return changes.Change("fred", [], "", when=when, revision=revision)
2477 def testCVS1(self):
2478 r = base.BuildRequest("forced build", SourceStamp())
2479 b = base.Build([r])
2480 s = source.CVS(cvsroot=None, cvsmodule=None)
2481 s.setBuild(b)
2482 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()), None)
2484 def testCVS2(self):
2485 c = []
2486 c.append(self.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2487 c.append(self.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2488 c.append(self.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2489 r = base.BuildRequest("forced", SourceStamp(changes=c))
2490 submitted = "Wed, 08 Sep 2004 09:04:00 -0700"
2491 r.submittedAt = mktime_tz(parsedate_tz(submitted))
2492 b = base.Build([r])
2493 s = source.CVS(cvsroot=None, cvsmodule=None)
2494 s.setBuild(b)
2495 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()),
2496 "Wed, 08 Sep 2004 16:03:00 -0000")
2498 def testCVS3(self):
2499 c = []
2500 c.append(self.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2501 c.append(self.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2502 c.append(self.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2503 r = base.BuildRequest("forced", SourceStamp(changes=c))
2504 submitted = "Wed, 08 Sep 2004 09:04:00 -0700"
2505 r.submittedAt = mktime_tz(parsedate_tz(submitted))
2506 b = base.Build([r])
2507 s = source.CVS(cvsroot=None, cvsmodule=None, checkoutDelay=10)
2508 s.setBuild(b)
2509 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()),
2510 "Wed, 08 Sep 2004 16:02:10 -0000")
2512 def testCVS4(self):
2513 c = []
2514 c.append(self.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2515 c.append(self.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2516 c.append(self.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2517 r1 = base.BuildRequest("forced", SourceStamp(changes=c))
2518 submitted = "Wed, 08 Sep 2004 09:04:00 -0700"
2519 r1.submittedAt = mktime_tz(parsedate_tz(submitted))
2521 c = []
2522 c.append(self.makeChange("Wed, 08 Sep 2004 09:05:00 -0700"))
2523 r2 = base.BuildRequest("forced", SourceStamp(changes=c))
2524 submitted = "Wed, 08 Sep 2004 09:07:00 -0700"
2525 r2.submittedAt = mktime_tz(parsedate_tz(submitted))
2527 b = base.Build([r1, r2])
2528 s = source.CVS(cvsroot=None, cvsmodule=None)
2529 s.setBuild(b)
2530 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()),
2531 "Wed, 08 Sep 2004 16:06:00 -0000")
2533 def testSVN1(self):
2534 r = base.BuildRequest("forced", SourceStamp())
2535 b = base.Build([r])
2536 s = source.SVN(svnurl="dummy")
2537 s.setBuild(b)
2538 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()), None)
2540 def testSVN2(self):
2541 c = []
2542 c.append(self.makeChange(revision=4))
2543 c.append(self.makeChange(revision=10))
2544 c.append(self.makeChange(revision=67))
2545 r = base.BuildRequest("forced", SourceStamp(changes=c))
2546 b = base.Build([r])
2547 s = source.SVN(svnurl="dummy")
2548 s.setBuild(b)
2549 self.failUnlessEqual(s.computeSourceRevision(b.allChanges()), 67)
2551 class Patch(VCBase, unittest.TestCase):
2552 def setUp(self):
2553 pass
2555 def tearDown(self):
2556 pass
2558 def testPatch(self):
2559 # invoke 'patch' all by itself, to see if it works the way we think
2560 # it should. This is intended to ferret out some windows test
2561 # failures.
2562 helper = BaseHelper()
2563 self.workdir = os.path.join("test_vc", "testPatch")
2564 helper.populate(self.workdir)
2565 patch = which("patch")[0]
2567 command = [patch, "-p0"]
2568 class FakeBuilder:
2569 usePTY = False
2570 def sendUpdate(self, status):
2571 pass
2572 c = commands.ShellCommand(FakeBuilder(), command, self.workdir,
2573 sendRC=False, initialStdin=p0_diff)
2574 d = c.start()
2575 d.addCallback(self._testPatch_1)
2576 return d
2578 def _testPatch_1(self, res):
2579 # make sure the file actually got patched
2580 subdir_c = os.path.join(self.workdir, "subdir", "subdir.c")
2581 data = open(subdir_c, "r").read()
2582 self.failUnlessIn("Hello patched subdir.\\n", data)