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
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
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
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.
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
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()
82 self
.deferred
.errback((out
, err
, e
.signal
))
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
91 from twisted
.internet
import reactor
93 p
= _PutEverythingGetter(d
, stdin
)
94 reactor
.spawnProcess(p
, executable
, (executable
,)+tuple(args
), env
, path
)
98 from buildbot.process import factory, step
101 f1 = factory.BuildFactory([
105 c['bots'] = [['bot1', 'sekrit']]
108 c['builders'] = [{'name': 'vc', 'slavename': 'bot1',
109 'builddir': 'vc-dir', 'factory': f1}]
110 c['slavePortnum'] = 0
111 BuildmasterConfig = c
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
124 main(int argc, const char *argv[])
126 - printf("Hello subdir.\n");
127 + printf("Hello patched subdir.\n");
132 # this patch does not include the filename headers, so it is
137 main(int argc, const char *argv[])
139 - printf("Hello subdir.\\n");
140 + printf("Hello try.\\n");
150 main(int argc, const char *argv[])
152 printf("Hello world.\\n");
162 main(int argc, const char *argv[])
164 printf("Hello branch.\\n");
174 main(int argc, const char *argv[])
176 printf("Hello world, version=%d\\n");
182 // this is subdir/subdir.c
186 main(int argc, const char *argv[])
188 printf("Hello subdir.\\n");
194 // this is subdir/subdir.c
198 main(int argc, const char *argv[])
200 printf("Hello try.\\n");
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
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
)
229 raise unittest
.SkipTest(res
[1])
230 d
.addCallback(_maybeSkip
)
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))
242 return defer
.succeed((False, self
._excuses
[name
]))
243 d
= defer
.maybeDeferred(self
._helpers
[name
].capable
)
246 self
._isCapable
[name
] = True
248 self
._excuses
[name
] = res
[1]
250 d
.addCallback(_capable
)
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()
262 self
._repoReady
[name
] = True
263 d
.addCallback(_ready
)
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
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
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
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).
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",
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
):
332 os
.makedirs(os
.path
.join(basedir
, "subdir"))
333 open(os
.path
.join(basedir
, "main.c"), "w").write(MAIN_C
)
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
355 if type(command
) not in (list, tuple):
356 command
= command
.split(" ")
359 print "do %s" % command
360 print " in basedir %s" % basedir
362 print " STDIN:\n", stdin
, "\n--STDIN DONE"
363 env
= os
.environ
.copy()
365 d
= myGetProcessOutputAndValue(command
[0], command
[1:],
366 env
=env
, path
=basedir
,
368 def check((out
, err
, code
)):
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" %
378 log
.msg(" and stdout %s" % (out
,))
379 log
.msg(" and stderr %s" % (err
,))
380 raise RuntimeError("command %s finished with exit code %d"
382 + ": see logs for stdout")
387 def do(self
, basedir
, command
, failureIsOk
=False, stdin
=None):
388 d
= self
.runCommand(basedir
, command
, failureIsOk
=failureIsOk
,
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
398 command
= [self
.vcexe
] + command
399 return self
.do(basedir
, command
, failureIsOk
, stdin
)
401 class VCBase(SignalMixin
):
403 createdRepository
= False
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
417 msg
= ("did not see the expected substring '%s' in string '%s'" %
419 self
.failUnless(string
.find(substring
) != -1, msg
)
422 d
= VCS
.skipIfNotCapable(self
.vc_name
)
423 d
.addCallback(self
._setUp
1)
426 def _setUp1(self
, res
):
427 self
.helper
= VCS
.getHelper(self
.vc_name
)
429 if os
.path
.exists("basedir"):
430 rmdirRecursive("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
)
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)
447 d
= self
.master
.botmaster
.waitUntilBuilderAttached("vc")
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)
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())
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
)
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
)
482 def _doBuild_1(self
, bs
, shouldSucceed
):
484 if r
!= SUCCESS
and shouldSucceed
:
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(),
494 print l
.getTextWithHeaders()
497 self
.fail("build did not succeed")
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):
526 args
= self
.helper
.vcargs
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
))
535 config
= config_vc
% s
537 m
.loadConfig(config
% 'clobber')
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
)
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
)
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
)
562 def _do_vctest_clobber(self
, res
):
563 d
= self
.doBuild() # initial checkout
564 d
.addCallback(self
._do
_vctest
_clobber
_1)
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")
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)
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)
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
)
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)
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)
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)
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)
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
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)
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)
673 def _do_vctest_copy_1(self
, bs
):
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)
685 def _do_vctest_copy_2(self
, bs
):
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)
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)
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
719 args
= self
.helper
.vcargs
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
))
727 self
.config
= config_vc
% s
729 m
.loadConfig(self
.config
% "clobber")
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)
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)
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)
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)
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],
794 d
= self
.doBuild(ss
=ss
)
795 d
.addCallback(self
._doPatch
_5)
797 def _doPatch_5(self
, bs
):
798 self
.shouldContain(self
.workdir
, "version.c",
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
):
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
))
822 config
= config_vc
% s
829 d
= self
.doBuild(shouldSucceed
) # initial checkout
835 args
= self
.helper
.vcargs
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
))
843 self
.config
= config_vc
% s
845 m
.loadConfig(self
.config
% "update")
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)
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
863 self
.touch(self
.workdir
, "newfile")
864 d
= self
.doBuild(ss
=SourceStamp(branch
=self
.helper
.branchname
))
865 d
.addCallback(self
._doBranch
_2)
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)
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)
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
906 args
= self
.helper
.vcargs
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
))
915 config
= config_vc
% s
917 m
.loadConfig(config
% 'clobber')
921 d
= self
.connectSlave()
923 # then set up the "developer's tree". first we modify a tree from the
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
)
931 d
.addCallback(self
.do_getpatch_branch
)
932 d
.addCallback(self
.do_getpatch_finish
)
935 def do_getpatch_finish(self
, res
):
936 log
.msg("do_getpatch_finish")
937 self
.helper
.vc_try_finish(self
.trydir
)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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
1035 open(n
,"w").write(patch
)
1036 d
= self
.runCommand(".", ["lsdiff", n
])
1037 def p(res
): print "lsdiff:", res
.strip().split("\n")
1043 d
= defer
.succeed(None)
1045 d2
= self
.master
.botmaster
.waitUntilBuilderDetached("vc")
1046 d
.addCallback(lambda res
: self
.slave
.stopService())
1047 d
.addCallback(lambda res
: d2
)
1049 d
.addCallback(lambda res
: self
.master
.stopService())
1051 d
.addCallback(lambda res
: self
.httpServer
.stopListening())
1052 def stopHTTPTimer():
1054 from twisted
.web
import http
# Twisted-2.0
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())
1062 def tearDown2(self
):
1065 class CVSHelper(BaseHelper
):
1066 branchname
= "branch"
1067 try_branchname
= "branch"
1070 cvspaths
= which('cvs')
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"],
1082 d
.addCallback(self
._capable
, cvspaths
[0])
1085 def _capable(self
, v
, vcexe
):
1086 m
= re
.search(r
'\(CVS\) ([\d\.]+) ', v
)
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")
1093 log
.msg("found CVS version '%s'" % ver
)
1095 return (False, "Found CVS, but it is too old")
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
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
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()
1123 # take a timestamp as the first revision number
1125 self
.addTrunkRev(self
.getdate())
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
)
1136 "commit -m commit_on_branch -r %s" % self
.branchname
)
1137 yield w
; w
.getResult()
1140 self
.addBranchRev(self
.getdate())
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()
1153 version_c
= VERSION_C
% self
.version
1154 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1156 "commit -m revised_to_%d version.c" % self
.version
)
1157 yield w
; w
.getResult()
1160 self
.addTrunkRev(self
.getdate())
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",
1170 if branch
is not None:
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
):
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()
1198 def testPatch(self
):
1202 def testCheckoutBranch(self
):
1203 d
= self
.do_branch()
1207 d
= self
.do_getpatch(doBranch
=False)
1210 VCS
.registerVC(CVS
.vc_name
, CVSHelper())
1213 class SVNHelper(BaseHelper
):
1214 branchname
= "sample/branch"
1215 try_branchname
= "sample/branch"
1218 svnpaths
= which('svn')
1219 svnadminpaths
= which('svnadmin')
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
1226 log
.msg("running svn --version..")
1227 env
= os
.environ
.copy()
1229 d
= utils
.getProcessOutput(svnpaths
[0], ["--version"],
1231 d
.addCallback(self
._capable
, svnpaths
[0], svnadminpaths
[0])
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'
1238 self
.svnadmin
= svnadmin
1240 excuse
= ("%s found but it does not support 'file:' " +
1241 "schema, skipping svn tests") % vcexe
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
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()
1263 "import -m sample_project_files %s" %
1265 yield w
; out
= w
.getResult()
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()
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")
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()
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)))
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
)
1310 svnurl
= self
.svnurl_trunk
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
):
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()
1341 def testPatch(self
):
1342 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1343 'defaultBranch': "sample/trunk",
1348 def testCheckoutBranch(self
):
1349 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1350 'defaultBranch': "sample/trunk",
1352 d
= self
.do_branch()
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()
1364 VCS
.registerVC(SVN
.vc_name
, SVNHelper())
1367 class P4Helper(BaseHelper
):
1368 branchname
= "branch"
1369 p4port
= 'localhost:1666'
1371 base_descr
= 'Change: new\nDescription: asdf\nFiles:\n'
1374 p4paths
= which('p4')
1375 p4dpaths
= which('p4d')
1377 return (False, "p4 is not installed")
1379 return (False, "p4d is not installed")
1380 self
.vcexe
= p4paths
[0]
1381 self
.p4dexe
= p4dpaths
[0]
1384 class _P4DProtocol(protocol
.ProcessProtocol
):
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.
1392 if data
.startswith('Perforce Server starting...'):
1393 self
.started
.callback(None)
1395 print "p4d said %r" % data
1397 raise Exception('p4d said %r' % data
)
1399 self
.started
.errback(failure
.Failure())
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)
1409 self
.ended
.errback(status_object
)
1411 def _start_p4d(self
):
1412 proto
= self
._P
4DProtocol
()
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
)
1433 started
, self
.p4d_shutdown
= self
._start
_p
4d
()
1434 w
= waitForDeferred(started
)
1435 yield w
; w
.getResult()
1437 # Create client spec.
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
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
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")
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
):
1501 def tearDownClass(self
):
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
1514 def testCheckoutBranch(self
):
1515 self
.helper
.vcargs
= { 'p4port': self
.helper
.p4port
,
1516 'p4base': '//depot/',
1517 'defaultBranch': 'trunk' }
1518 d
= self
.do_branch()
1521 def testPatch(self
):
1522 self
.helper
.vcargs
= { 'p4port': self
.helper
.p4port
,
1523 'p4base': '//depot/',
1524 'defaultBranch': 'trunk' }
1528 VCS
.registerVC(P4
.vc_name
, P4Helper())
1531 class DarcsHelper(BaseHelper
):
1532 branchname
= "branch"
1533 try_branchname
= "branch"
1536 darcspaths
= which('darcs')
1538 return (False, "Darcs is not installed")
1539 self
.vcexe
= darcspaths
[0]
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()
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
)
1578 createRepository
= deferredGenerator(createRepository
)
1580 def vc_revise(self
):
1581 tmp
= os
.path
.join(self
.repbase
, "darcstmp")
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()
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
)
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()
1609 rep
= self
.rep_trunk
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
):
1624 # Darcs has a metadir="_darcs", but it does not have an 'export'
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
1639 def testPatch(self
):
1640 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1641 'defaultBranch': "trunk" }
1645 def testCheckoutBranch(self
):
1646 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1647 'defaultBranch': "trunk" }
1648 d
= self
.do_branch()
1651 def testCheckoutHTTP(self
):
1653 repourl
= "http://localhost:%d/Darcs-Repository/trunk" % self
.httpPort
1654 self
.helper
.vcargs
= { 'repourl': repourl
}
1655 d
= self
.do_vctest(testRetry
=False)
1659 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1660 'defaultBranch': "trunk" }
1661 d
= self
.do_getpatch()
1664 VCS
.registerVC(Darcs
.vc_name
, DarcsHelper())
1668 def registerRepository(self
, coordinates
):
1670 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1671 yield w
; out
= w
.getResult()
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
):
1681 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1682 yield w
; out
= w
.getResult()
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
1695 tlapaths
= which('tla')
1697 return (False, "Arch (tla) is not installed")
1698 self
.vcexe
= tlapaths
[0]
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
))
1709 w
= self
.dovc(basedir
,
1710 "get %s/%s %s" % (archive
, branch
, newdir
))
1713 def createRepository(self
):
1714 self
.createBasedir()
1715 # first check to see if bazaar is around, since we'll need to know
1717 d
= VCS
.capable(Bazaar
.vc_name
)
1718 d
.addCallback(self
._createRepository
_1)
1721 def _createRepository_1(self
, res
):
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).
1729 self
.archname
= "test-%s-%d@buildbot.sf.net--testvc" % (self
.archcmd
,
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")
1741 w
= self
.dovc(tmp
, "my-id", failureIsOk
=True)
1742 yield w
; res
= w
.getResult()
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()
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
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()
1773 # baz does not require an 'archive-setup' step
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")
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()
1794 w
= self
.dovc(tmp
, "branch %s" % branch
)
1795 yield w
; w
.getResult()
1799 # check out the branch
1800 w
= self
.do_get(self
.repbase
, a
, branch
, "archtmp")
1801 yield w
; w
.getResult()
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
),
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()
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")
1848 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1849 yield w
; out
= w
.getResult()
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
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
),
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()
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
)
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()
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
):
1913 # Arch has a metadir="{arch}", but it does not have an 'export' mode.
1914 vctype
= "step.Arch"
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.
1933 def testCheckoutHTTP(self
):
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)
1941 def testPatch(self
):
1942 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1943 'version': self
.helper
.defaultbranch
}
1947 def testCheckoutBranch(self
):
1948 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1949 'version': self
.helper
.defaultbranch
}
1950 d
= self
.do_branch()
1954 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1955 'version': self
.helper
.defaultbranch
}
1956 d
= self
.do_getpatch()
1959 VCS
.registerVC(Arch
.vc_name
, TlaHelper())
1962 class BazaarHelper(TlaHelper
):
1966 bazpaths
= which('baz')
1968 return (False, "Arch (baz) is not installed")
1969 self
.vcexe
= bazpaths
[0]
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()
1984 vctype
= "step.Bazaar"
1986 has_got_revision
= True
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.
2006 def testCheckoutHTTP(self
):
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)
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
,
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()
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()
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
2059 # break the repository server
2060 from twisted
.web
import static
2061 self
.site
.resource
= static
.Data("Sorry, repository is offline",
2063 # and arrange to fix it again in 5 seconds, while the test is
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
,
2073 d
= self
.do_vctest_once(True)
2074 d
.addCallback(self
._testRetry
_1)
2076 def _testRetry_1(self
, bs
):
2077 # make sure there was mention of the retry attempt in the logs
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
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",
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
,
2102 d
= self
.do_vctest_once(False)
2103 d
.addCallback(self
._testRetryFails
_1)
2105 def _testRetryFails_1(self
, bs
):
2106 self
.failUnlessEqual(bs
.getResults(), FAILURE
)
2108 def tearDown2(self
):
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()
2119 VCS
.registerVC(Bazaar
.vc_name
, BazaarHelper())
2121 class MercurialHelper(BaseHelper
):
2122 branchname
= "branch"
2123 try_branchname
= "branch"
2126 hgpaths
= which("hg")
2128 return (False, "Mercurial is not installed")
2129 self
.vcexe
= hgpaths
[0]
2132 def extract_id(self
, output
):
2133 m
= re
.search(r
'^(\w+)', output
)
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()
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
))
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()
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
))
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
)
2203 src
= self
.rep_branch
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
):
2221 # Mercurial has a metadir=".hg", but it does not have an 'export' mode.
2223 vctype
= "step.Mercurial"
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
2235 def testPatch(self
):
2236 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2237 'defaultBranch': "trunk" }
2241 def testCheckoutBranch(self
):
2242 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2243 'defaultBranch': "trunk" }
2244 d
= self
.do_branch()
2247 def testCheckoutHTTP(self
):
2249 repourl
= "http://localhost:%d/Mercurial-Repository/trunk/.hg" % self
.httpPort
2250 self
.helper
.vcargs
= { 'repourl': repourl
}
2251 d
= self
.do_vctest(testRetry
=False)
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'"
2260 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2261 'defaultBranch': "trunk" }
2262 d
= self
.do_getpatch()
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):
2272 when
= mktime_tz(parsedate_tz(when
))
2273 return changes
.Change("fred", [], "", when
=when
, revision
=revision
)
2276 r
= base
.BuildRequest("forced build", SourceStamp())
2278 s
= step
.CVS(cvsroot
=None, cvsmodule
=None, workdir
=None, build
=b
)
2279 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), None)
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
))
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")
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
))
2303 s
= step
.CVS(cvsroot
=None, cvsmodule
=None, workdir
=None, build
=b
,
2305 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
2306 "Wed, 08 Sep 2004 16:02:10 -0000")
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
))
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")
2329 r
= base
.BuildRequest("forced", SourceStamp())
2331 s
= step
.SVN(svnurl
="dummy", workdir
=None, build
=b
)
2332 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), None)
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
))
2341 s
= step
.SVN(svnurl
="dummy", workdir
=None, build
=b
)
2342 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), 67)
2344 class Patch(VCBase
, unittest
.TestCase
):
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
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"]
2363 def sendUpdate(self
, status
):
2365 c
= commands
.ShellCommand(FakeBuilder(), command
, self
.workdir
,
2366 sendRC
=False, initialStdin
=p0_diff
)
2368 d
.addCallback(self
._testPatch
_1)
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
)