1 # -*- test-case-name: buildbot.test.test_vc -*-
3 from __future__
import generators
5 import sys
, os
, signal
, shutil
, time
, re
6 from email
.Utils
import mktime_tz
, parsedate_tz
8 from twisted
.trial
import unittest
9 from twisted
.internet
import defer
, reactor
, utils
, protocol
, error
11 #defer.Deferred.debug = True
13 from twisted
.python
import log
14 #log.startLogging(sys.stderr)
16 from buildbot
import master
, interfaces
17 from buildbot
.slave
import bot
, commands
18 from buildbot
.slave
.commands
import rmdirRecursive
19 from buildbot
.status
.builder
import SUCCESS
, FAILURE
20 from buildbot
.process
import step
, base
21 from buildbot
.changes
import changes
22 from buildbot
.sourcestamp
import SourceStamp
23 from buildbot
.twcompat
import maybeWait
, which
24 from buildbot
.scripts
import tryclient
26 #step.LoggedRemoteCommand.debug = True
28 # buildbot.twcompat will patch these into t.i.defer if necessary
29 from twisted
.internet
.defer
import waitForDeferred
, deferredGenerator
31 # Most of these tests (all but SourceStamp) depend upon having a set of
32 # repositories from which we can perform checkouts. These repositories are
33 # created by the setUp method at the start of each test class. In earlier
34 # versions these repositories were created offline and distributed with a
35 # separate tarball named 'buildbot-test-vc-1.tar.gz'. This is no longer
38 # CVS requires a local file repository. Providing remote access is beyond
39 # the feasible abilities of this test program (needs pserver or ssh).
41 # SVN requires a local file repository. To provide remote access over HTTP
42 # requires an apache server with DAV support and mod_svn, way beyond what we
45 # Arch and Darcs both allow remote (read-only) operation with any web
46 # server. We test both local file access and HTTP access (by spawning a
47 # small web server to provide access to the repository files while the test
50 # Perforce starts the daemon running on localhost. Unfortunately, it must
51 # use a predetermined Internet-domain port number, unless we want to go
52 # all-out: bind the listen socket ourselves and pretend to be inetd.
55 import cStringIO
as StringIO
59 class _PutEverythingGetter(protocol
.ProcessProtocol
):
60 def __init__(self
, deferred
, stdin
):
61 self
.deferred
= deferred
62 self
.outBuf
= StringIO
.StringIO()
63 self
.errBuf
= StringIO
.StringIO()
64 self
.outReceived
= self
.outBuf
.write
65 self
.errReceived
= self
.errBuf
.write
68 def connectionMade(self
):
69 if self
.stdin
is not None:
70 self
.transport
.write(self
.stdin
)
71 self
.transport
.closeStdin()
73 def processEnded(self
, reason
):
74 out
= self
.outBuf
.getvalue()
75 err
= self
.errBuf
.getvalue()
79 self
.deferred
.errback((out
, err
, e
.signal
))
81 self
.deferred
.callback((out
, err
, code
))
83 def myGetProcessOutputAndValue(executable
, args
=(), env
={}, path
='.',
84 reactor
=None, stdin
=None):
85 """Like twisted.internet.utils.getProcessOutputAndValue but takes
88 from twisted
.internet
import reactor
90 p
= _PutEverythingGetter(d
, stdin
)
91 reactor
.spawnProcess(p
, executable
, (executable
,)+tuple(args
), env
, path
)
95 from buildbot.process import factory, step
98 f1 = factory.BuildFactory([
102 c['bots'] = [['bot1', 'sekrit']]
105 c['builders'] = [{'name': 'vc', 'slavename': 'bot1',
106 'builddir': 'vc-dir', 'factory': f1}]
107 c['slavePortnum'] = 0
108 BuildmasterConfig = c
112 Index: subdir/subdir.c
113 ===================================================================
114 RCS file: /home/warner/stuff/Projects/BuildBot/code-arch/_trial_temp/test_vc/repositories/CVS-Repository/sample/subdir/subdir.c,v
115 retrieving revision 1.1.1.1
116 diff -u -r1.1.1.1 subdir.c
117 --- subdir/subdir.c 14 Aug 2005 01:32:49 -0000 1.1.1.1
118 +++ subdir/subdir.c 14 Aug 2005 01:36:15 -0000
121 main(int argc, const char *argv[])
123 - printf("Hello subdir.\n");
124 + printf("Hello patched subdir.\n");
129 # this patch does not include the filename headers, so it is
134 main(int argc, const char *argv[])
136 - printf("Hello subdir.\\n");
137 + printf("Hello try.\\n");
147 main(int argc, const char *argv[])
149 printf("Hello world.\\n");
159 main(int argc, const char *argv[])
161 printf("Hello branch.\\n");
171 main(int argc, const char *argv[])
173 printf("Hello world, version=%d\\n");
179 // this is subdir/subdir.c
183 main(int argc, const char *argv[])
185 printf("Hello subdir.\\n");
191 // this is subdir/subdir.c
195 main(int argc, const char *argv[])
197 printf("Hello try.\\n");
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
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
)
223 raise unittest
.SkipTest(res
[1])
224 d
.addCallback(_maybeSkip
)
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))
236 return defer
.succeed((False, self
._excuses
[name
]))
237 d
= defer
.maybeDeferred(self
._helpers
[name
].capable
)
240 self
._isCapable
[name
] = True
242 self
._excuses
[name
] = res
[1]
244 d
.addCallback(_capable
)
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()
256 self
._repoReady
[name
] = True
257 d
.addCallback(_ready
)
263 sigchldHandler
= None
265 def setUpClass(self
):
266 # make sure SIGCHLD handler is installed, as it should be on
267 # reactor.run(). problem is reactor may not have been run when this
269 if hasattr(reactor
, "_handleSigchld") and hasattr(signal
, "SIGCHLD"):
270 self
.sigchldHandler
= signal
.signal(signal
.SIGCHLD
,
271 reactor
._handleSigchld
)
273 def tearDownClass(self
):
274 if self
.sigchldHandler
:
275 signal
.signal(signal
.SIGCHLD
, self
.sigchldHandler
)
278 # the overall plan here:
280 # Each VC system is tested separately, all using the same source tree defined
281 # in the 'files' dictionary above. Each VC system gets its own TestCase
282 # subclass. The first test case that is run will create the repository during
283 # setUp(), making two branches: 'trunk' and 'branch'. The trunk gets a copy
284 # of all the files in 'files'. The variant of good.c is committed on the
287 # then testCheckout is run, which does a number of checkout/clobber/update
288 # builds. These all use trunk r1. It then runs self.fix(), which modifies
289 # 'fixable.c', then performs another build and makes sure the tree has been
292 # testBranch uses trunk-r1 and branch-r1, making sure that we clobber the
293 # tree properly when we switch between them
295 # testPatch does a trunk-r1 checkout and applies a patch.
297 # testTryGetPatch performs a trunk-r1 checkout, modifies some files, then
298 # verifies that tryclient.getSourceStamp figures out the base revision and
302 # vc_create makes a repository at r1 with three files: main.c, version.c, and
303 # subdir/foo.c . It also creates a branch from r1 (called b1) in which main.c
304 # says "hello branch" instead of "hello world". self.trunk[] contains
305 # revision stamps for everything on the trunk, and self.branch[] does the
306 # same for the branch.
308 # vc_revise() checks out a tree at HEAD, changes version.c, then checks it
309 # back in. The new version stamp is appended to self.trunk[]. The tree is
310 # removed afterwards.
312 # vc_try_checkout(workdir, rev) checks out a tree at REV, then changes
313 # subdir/subdir.c to say 'Hello try'
314 # vc_try_finish(workdir) removes the tree and cleans up any VC state
315 # necessary (like deleting the Arch archive entry).
325 # this is also responsible for setting self.vcexe
326 raise NotImplementedError
328 def createBasedir(self
):
329 # you must call this from createRepository
330 self
.repbase
= os
.path
.abspath(os
.path
.join("test_vc",
332 if not os
.path
.isdir(self
.repbase
):
333 os
.makedirs(self
.repbase
)
335 def createRepository(self
):
336 # this will only be called once per process
337 raise NotImplementedError
339 def populate(self
, basedir
):
341 os
.makedirs(os
.path
.join(basedir
, "subdir"))
342 open(os
.path
.join(basedir
, "main.c"), "w").write(MAIN_C
)
344 version_c
= VERSION_C
% self
.version
345 open(os
.path
.join(basedir
, "version.c"), "w").write(version_c
)
346 open(os
.path
.join(basedir
, "main.c"), "w").write(MAIN_C
)
347 open(os
.path
.join(basedir
, "subdir", "subdir.c"), "w").write(SUBDIR_C
)
349 def populate_branch(self
, basedir
):
350 open(os
.path
.join(basedir
, "main.c"), "w").write(BRANCH_C
)
352 def addTrunkRev(self
, rev
):
353 self
.trunk
.append(rev
)
354 self
.allrevs
.append(rev
)
355 def addBranchRev(self
, rev
):
356 self
.branch
.append(rev
)
357 self
.allrevs
.append(rev
)
359 def runCommand(self
, basedir
, command
, failureIsOk
=False, stdin
=None):
360 # all commands passed to do() should be strings or lists. If they are
361 # strings, none of the arguments may have spaces. This makes the
362 # commands less verbose at the expense of restricting what they can
364 if type(command
) not in (list, tuple):
365 command
= command
.split(" ")
368 print "do %s" % command
369 print " in basedir %s" % basedir
371 print " STDIN:\n", stdin
, "\n--STDIN DONE"
372 env
= os
.environ
.copy()
374 d
= myGetProcessOutputAndValue(command
[0], command
[1:],
375 env
=env
, path
=basedir
,
377 def check((out
, err
, code
)):
380 print "command was: %s" % command
381 if out
: print "out: %s" % out
382 if err
: print "err: %s" % err
383 print "code: %s" % code
384 if code
!= 0 and not failureIsOk
:
385 log
.msg("command %s finished with exit code %d" %
387 log
.msg(" and stdout %s" % (out
,))
388 log
.msg(" and stderr %s" % (err
,))
389 raise RuntimeError("command %s finished with exit code %d"
391 + ": see logs for stdout")
396 def do(self
, basedir
, command
, failureIsOk
=False, stdin
=None):
397 d
= self
.runCommand(basedir
, command
, failureIsOk
=failureIsOk
,
399 return waitForDeferred(d
)
401 def dovc(self
, basedir
, command
, failureIsOk
=False, stdin
=None):
402 """Like do(), but the VC binary will be prepended to COMMAND."""
403 command
= self
.vcexe
+ " " + command
404 return self
.do(basedir
, command
, failureIsOk
, stdin
)
406 class VCBase(SignalMixin
):
408 createdRepository
= False
414 has_got_revision
= False
415 has_got_revision_branches_are_merged
= False # for SVN
417 def failUnlessIn(self
, substring
, string
, msg
=None):
418 # trial provides a version of this that requires python-2.3 to test
421 msg
= ("did not see the expected substring '%s' in string '%s'" %
423 self
.failUnless(string
.find(substring
) != -1, msg
)
426 d
= VCS
.skipIfNotCapable(self
.vc_name
)
427 d
.addCallback(self
._setUp
1)
430 def _setUp1(self
, res
):
431 self
.helper
= VCS
.getHelper(self
.vc_name
)
433 if os
.path
.exists("basedir"):
434 rmdirRecursive("basedir")
436 self
.master
= master
.BuildMaster("basedir")
437 self
.slavebase
= os
.path
.abspath("slavebase")
438 if os
.path
.exists(self
.slavebase
):
439 rmdirRecursive(self
.slavebase
)
440 os
.mkdir("slavebase")
442 d
= VCS
.createRepository(self
.vc_name
)
445 def connectSlave(self
):
446 port
= self
.master
.slavePort
._port
.getHost().port
447 slave
= bot
.BuildSlave("localhost", port
, "bot1", "sekrit",
448 self
.slavebase
, keepalive
=0, usePTY
=1)
451 d
= self
.master
.botmaster
.waitUntilBuilderAttached("vc")
454 def loadConfig(self
, config
):
455 # reloading the config file causes a new 'listDirs' command to be
456 # sent to the slave. To synchronize on this properly, it is easiest
457 # to stop and restart the slave.
458 d
= defer
.succeed(None)
460 d
= self
.master
.botmaster
.waitUntilBuilderDetached("vc")
461 self
.slave
.stopService()
462 d
.addCallback(lambda res
: self
.master
.loadConfig(config
))
463 d
.addCallback(lambda res
: self
.connectSlave())
467 # launch an HTTP server to serve the repository files
468 from twisted
.web
import static
, server
469 from twisted
.internet
import reactor
470 self
.root
= static
.File(self
.helper
.repbase
)
471 self
.site
= server
.Site(self
.root
)
472 self
.httpServer
= reactor
.listenTCP(0, self
.site
)
473 self
.httpPort
= self
.httpServer
.getHost().port
475 def doBuild(self
, shouldSucceed
=True, ss
=None):
476 c
= interfaces
.IControl(self
.master
)
480 #print "doBuild(ss: b=%s rev=%s)" % (ss.branch, ss.revision)
481 req
= base
.BuildRequest("test_vc forced build", ss
)
482 d
= req
.waitUntilFinished()
483 c
.getBuilder("vc").requestBuild(req
)
484 d
.addCallback(self
._doBuild
_1, shouldSucceed
)
486 def _doBuild_1(self
, bs
, shouldSucceed
):
488 if r
!= SUCCESS
and shouldSucceed
:
491 if not bs
.isFinished():
492 print "Hey, build wasn't even finished!"
493 print "Build did not succeed:", r
, bs
.getText()
494 for s
in bs
.getSteps():
495 for l
in s
.getLogs():
496 print "--- START step %s / log %s ---" % (s
.getName(),
498 print l
.getTextWithHeaders()
501 self
.fail("build did not succeed")
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):
530 args
= self
.helper
.vcargs
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
))
539 config
= config_vc
% s
541 m
.loadConfig(config
% 'clobber')
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
)
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
)
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
)
566 def _do_vctest_clobber(self
, res
):
567 d
= self
.doBuild() # initial checkout
568 d
.addCallback(self
._do
_vctest
_clobber
_1)
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")
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)
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)
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
)
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)
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)
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)
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)
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
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)
670 def _do_vctest_update_retry_1(self
, bs
):
671 self
.shouldNotExist(self
.workdir
, "newfile")
673 def _do_vctest_copy(self
, res
):
674 d
= self
.doBuild() # copy rebuild clobbers new files
675 d
.addCallback(self
._do
_vctest
_copy
_1)
677 def _do_vctest_copy_1(self
, bs
):
679 self
.shouldExist(self
.workdir
, self
.metadir
)
680 self
.shouldNotExist(self
.workdir
, "newfile")
681 self
.touch(self
.workdir
, "newfile")
682 self
.touch(self
.vcdir
, "newvcfile")
683 self
.failUnlessEqual(bs
.getProperty("revision"), None)
684 self
.checkGotRevisionIsLatest(bs
)
686 d
= self
.doBuild() # copy rebuild clobbers new files
687 d
.addCallback(self
._do
_vctest
_copy
_2)
689 def _do_vctest_copy_2(self
, bs
):
691 self
.shouldExist(self
.workdir
, self
.metadir
)
692 self
.shouldNotExist(self
.workdir
, "newfile")
693 self
.shouldExist(self
.vcdir
, "newvcfile")
694 self
.shouldExist(self
.workdir
, "newvcfile")
695 self
.failUnlessEqual(bs
.getProperty("revision"), None)
696 self
.checkGotRevisionIsLatest(bs
)
697 self
.touch(self
.workdir
, "newfile")
699 def _do_vctest_export(self
, res
):
700 d
= self
.doBuild() # export rebuild clobbers new files
701 d
.addCallback(self
._do
_vctest
_export
_1)
703 def _do_vctest_export_1(self
, bs
):
704 self
.shouldNotExist(self
.workdir
, self
.metadir
)
705 self
.shouldNotExist(self
.workdir
, "newfile")
706 self
.failUnlessEqual(bs
.getProperty("revision"), None)
707 #self.checkGotRevisionIsLatest(bs)
708 # VC 'export' is not required to have a got_revision
709 self
.touch(self
.workdir
, "newfile")
711 d
= self
.doBuild() # export rebuild clobbers new files
712 d
.addCallback(self
._do
_vctest
_export
_2)
714 def _do_vctest_export_2(self
, bs
):
715 self
.shouldNotExist(self
.workdir
, self
.metadir
)
716 self
.shouldNotExist(self
.workdir
, "newfile")
717 self
.failUnlessEqual(bs
.getProperty("revision"), None)
718 #self.checkGotRevisionIsLatest(bs)
719 # VC 'export' is not required to have a got_revision
723 args
= self
.helper
.vcargs
725 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
726 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
727 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
728 for k
,v
in args
.items():
729 s
+= ", %s=%s" % (k
, repr(v
))
731 self
.config
= config_vc
% s
733 m
.loadConfig(self
.config
% "clobber")
737 ss
= SourceStamp(revision
=self
.helper
.trunk
[-1], patch
=(0, p0_diff
))
739 d
= self
.connectSlave()
740 d
.addCallback(lambda res
: self
.doBuild(ss
=ss
))
741 d
.addCallback(self
._doPatch
_1)
743 def _doPatch_1(self
, bs
):
744 self
.shouldContain(self
.workdir
, "version.c",
745 "version=%d" % self
.helper
.version
)
746 # make sure the file actually got patched
747 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
748 "subdir", "subdir.c")
749 data
= open(subdir_c
, "r").read()
750 self
.failUnlessIn("Hello patched subdir.\\n", data
)
751 self
.failUnlessEqual(bs
.getProperty("revision"),
752 self
.helper
.trunk
[-1])
753 self
.checkGotRevision(bs
, self
.helper
.trunk
[-1])
755 # make sure that a rebuild does not use the leftover patched workdir
756 d
= self
.master
.loadConfig(self
.config
% "update")
757 d
.addCallback(lambda res
: self
.doBuild(ss
=None))
758 d
.addCallback(self
._doPatch
_2)
760 def _doPatch_2(self
, bs
):
761 # make sure the file is back to its original
762 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
763 "subdir", "subdir.c")
764 data
= open(subdir_c
, "r").read()
765 self
.failUnlessIn("Hello subdir.\\n", data
)
766 self
.failUnlessEqual(bs
.getProperty("revision"), None)
767 self
.checkGotRevisionIsLatest(bs
)
769 # now make sure we can patch an older revision. We need at least two
770 # revisions here, so we might have to create one first
771 if len(self
.helper
.trunk
) < 2:
772 d
= self
.helper
.vc_revise()
773 d
.addCallback(self
._doPatch
_3)
775 return self
._doPatch
_3()
777 def _doPatch_3(self
, res
=None):
778 ss
= SourceStamp(revision
=self
.helper
.trunk
[-2], patch
=(0, p0_diff
))
779 d
= self
.doBuild(ss
=ss
)
780 d
.addCallback(self
._doPatch
_4)
782 def _doPatch_4(self
, bs
):
783 self
.shouldContain(self
.workdir
, "version.c",
784 "version=%d" % (self
.helper
.version
-1))
785 # and make sure the file actually got patched
786 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
787 "subdir", "subdir.c")
788 data
= open(subdir_c
, "r").read()
789 self
.failUnlessIn("Hello patched subdir.\\n", data
)
790 self
.failUnlessEqual(bs
.getProperty("revision"),
791 self
.helper
.trunk
[-2])
792 self
.checkGotRevision(bs
, self
.helper
.trunk
[-2])
794 # now check that we can patch a branch
795 ss
= SourceStamp(branch
=self
.helper
.branchname
,
796 revision
=self
.helper
.branch
[-1],
798 d
= self
.doBuild(ss
=ss
)
799 d
.addCallback(self
._doPatch
_5)
801 def _doPatch_5(self
, bs
):
802 self
.shouldContain(self
.workdir
, "version.c",
804 self
.shouldContain(self
.workdir
, "main.c", "Hello branch.")
805 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
806 "subdir", "subdir.c")
807 data
= open(subdir_c
, "r").read()
808 self
.failUnlessIn("Hello patched subdir.\\n", data
)
809 self
.failUnlessEqual(bs
.getProperty("revision"),
810 self
.helper
.branch
[-1])
811 self
.failUnlessEqual(bs
.getProperty("branch"), self
.helper
.branchname
)
812 self
.checkGotRevision(bs
, self
.helper
.branch
[-1])
815 def do_vctest_once(self
, shouldSucceed
):
818 args
= self
.helper
.vcargs
819 vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
820 workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
821 # woo double-substitution
822 s
= "s(%s, timeout=200, workdir='build', mode='clobber'" % (vctype
,)
823 for k
,v
in args
.items():
824 s
+= ", %s=%s" % (k
, repr(v
))
826 config
= config_vc
% s
833 d
= self
.doBuild(shouldSucceed
) # initial checkout
839 args
= self
.helper
.vcargs
841 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
842 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
843 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
844 for k
,v
in args
.items():
845 s
+= ", %s=%s" % (k
, repr(v
))
847 self
.config
= config_vc
% s
849 m
.loadConfig(self
.config
% "update")
853 # first we do a build of the trunk
854 d
= self
.connectSlave()
855 d
.addCallback(lambda res
: self
.doBuild(ss
=SourceStamp()))
856 d
.addCallback(self
._doBranch
_1)
858 def _doBranch_1(self
, bs
):
859 log
.msg("_doBranch_1")
860 # make sure the checkout was of the trunk
861 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
862 data
= open(main_c
, "r").read()
863 self
.failUnlessIn("Hello world.", data
)
865 # now do a checkout on the branch. The change in branch name should
867 self
.touch(self
.workdir
, "newfile")
868 d
= self
.doBuild(ss
=SourceStamp(branch
=self
.helper
.branchname
))
869 d
.addCallback(self
._doBranch
_2)
871 def _doBranch_2(self
, bs
):
872 log
.msg("_doBranch_2")
873 # make sure it was on the branch
874 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
875 data
= open(main_c
, "r").read()
876 self
.failUnlessIn("Hello branch.", data
)
877 # and make sure the tree was clobbered
878 self
.shouldNotExist(self
.workdir
, "newfile")
880 # doing another build on the same branch should not clobber the tree
881 self
.touch(self
.workdir
, "newbranchfile")
882 d
= self
.doBuild(ss
=SourceStamp(branch
=self
.helper
.branchname
))
883 d
.addCallback(self
._doBranch
_3)
885 def _doBranch_3(self
, bs
):
886 log
.msg("_doBranch_3")
887 # make sure it is still on the branch
888 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
889 data
= open(main_c
, "r").read()
890 self
.failUnlessIn("Hello branch.", data
)
891 # and make sure the tree was not clobbered
892 self
.shouldExist(self
.workdir
, "newbranchfile")
894 # now make sure that a non-branch checkout clobbers the tree
895 d
= self
.doBuild(ss
=SourceStamp())
896 d
.addCallback(self
._doBranch
_4)
898 def _doBranch_4(self
, bs
):
899 log
.msg("_doBranch_4")
900 # make sure it was on the trunk
901 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
902 data
= open(main_c
, "r").read()
903 self
.failUnlessIn("Hello world.", data
)
904 self
.shouldNotExist(self
.workdir
, "newbranchfile")
906 def do_getpatch(self
, doBranch
=True):
907 log
.msg("do_getpatch")
908 # prepare a buildslave to do checkouts
910 args
= self
.helper
.vcargs
912 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
913 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
914 # woo double-substitution
915 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
916 for k
,v
in args
.items():
917 s
+= ", %s=%s" % (k
, repr(v
))
919 config
= config_vc
% s
921 m
.loadConfig(config
% 'clobber')
925 d
= self
.connectSlave()
927 # then set up the "developer's tree". first we modify a tree from the
929 tmpdir
= "try_workdir"
930 self
.trydir
= os
.path
.join(self
.helper
.repbase
, tmpdir
)
931 rmdirRecursive(self
.trydir
)
932 d
.addCallback(self
.do_getpatch_trunkhead
)
933 d
.addCallback(self
.do_getpatch_trunkold
)
935 d
.addCallback(self
.do_getpatch_branch
)
936 d
.addCallback(self
.do_getpatch_finish
)
939 def do_getpatch_finish(self
, res
):
940 log
.msg("do_getpatch_finish")
941 self
.helper
.vc_try_finish(self
.trydir
)
944 def try_shouldMatch(self
, filename
):
945 devfilename
= os
.path
.join(self
.trydir
, filename
)
946 devfile
= open(devfilename
, "r").read()
947 slavefilename
= os
.path
.join(self
.workdir
, filename
)
948 slavefile
= open(slavefilename
, "r").read()
949 self
.failUnlessEqual(devfile
, slavefile
,
950 ("slavefile (%s) contains '%s'. "
951 "developer's file (%s) contains '%s'. "
952 "These ought to match") %
953 (slavefilename
, slavefile
,
954 devfilename
, devfile
))
956 def do_getpatch_trunkhead(self
, res
):
957 log
.msg("do_getpatch_trunkhead")
958 d
= self
.helper
.vc_try_checkout(self
.trydir
, self
.helper
.trunk
[-1])
959 d
.addCallback(self
._do
_getpatch
_trunkhead
_1)
961 def _do_getpatch_trunkhead_1(self
, res
):
962 log
.msg("_do_getpatch_trunkhead_1")
963 d
= tryclient
.getSourceStamp(self
.vctype_try
, self
.trydir
, None)
964 d
.addCallback(self
._do
_getpatch
_trunkhead
_2)
966 def _do_getpatch_trunkhead_2(self
, ss
):
967 log
.msg("_do_getpatch_trunkhead_2")
968 d
= self
.doBuild(ss
=ss
)
969 d
.addCallback(self
._do
_getpatch
_trunkhead
_3)
971 def _do_getpatch_trunkhead_3(self
, res
):
972 log
.msg("_do_getpatch_trunkhead_3")
973 # verify that the resulting buildslave tree matches the developer's
974 self
.try_shouldMatch("main.c")
975 self
.try_shouldMatch("version.c")
976 self
.try_shouldMatch(os
.path
.join("subdir", "subdir.c"))
978 def do_getpatch_trunkold(self
, res
):
979 log
.msg("do_getpatch_trunkold")
980 # now try a tree from an older revision. We need at least two
981 # revisions here, so we might have to create one first
982 if len(self
.helper
.trunk
) < 2:
983 d
= self
.helper
.vc_revise()
984 d
.addCallback(self
._do
_getpatch
_trunkold
_1)
986 return self
._do
_getpatch
_trunkold
_1()
987 def _do_getpatch_trunkold_1(self
, res
=None):
988 log
.msg("_do_getpatch_trunkold_1")
989 d
= self
.helper
.vc_try_checkout(self
.trydir
, self
.helper
.trunk
[-2])
990 d
.addCallback(self
._do
_getpatch
_trunkold
_2)
992 def _do_getpatch_trunkold_2(self
, res
):
993 log
.msg("_do_getpatch_trunkold_2")
994 d
= tryclient
.getSourceStamp(self
.vctype_try
, self
.trydir
, None)
995 d
.addCallback(self
._do
_getpatch
_trunkold
_3)
997 def _do_getpatch_trunkold_3(self
, ss
):
998 log
.msg("_do_getpatch_trunkold_3")
999 d
= self
.doBuild(ss
=ss
)
1000 d
.addCallback(self
._do
_getpatch
_trunkold
_4)
1002 def _do_getpatch_trunkold_4(self
, res
):
1003 log
.msg("_do_getpatch_trunkold_4")
1004 # verify that the resulting buildslave tree matches the developer's
1005 self
.try_shouldMatch("main.c")
1006 self
.try_shouldMatch("version.c")
1007 self
.try_shouldMatch(os
.path
.join("subdir", "subdir.c"))
1009 def do_getpatch_branch(self
, res
):
1010 log
.msg("do_getpatch_branch")
1011 # now try a tree from a branch
1012 d
= self
.helper
.vc_try_checkout(self
.trydir
, self
.helper
.branch
[-1],
1013 self
.helper
.branchname
)
1014 d
.addCallback(self
._do
_getpatch
_branch
_1)
1016 def _do_getpatch_branch_1(self
, res
):
1017 log
.msg("_do_getpatch_branch_1")
1018 d
= tryclient
.getSourceStamp(self
.vctype_try
, self
.trydir
,
1019 self
.helper
.try_branchname
)
1020 d
.addCallback(self
._do
_getpatch
_branch
_2)
1022 def _do_getpatch_branch_2(self
, ss
):
1023 log
.msg("_do_getpatch_branch_2")
1024 d
= self
.doBuild(ss
=ss
)
1025 d
.addCallback(self
._do
_getpatch
_branch
_3)
1027 def _do_getpatch_branch_3(self
, res
):
1028 log
.msg("_do_getpatch_branch_3")
1029 # verify that the resulting buildslave tree matches the developer's
1030 self
.try_shouldMatch("main.c")
1031 self
.try_shouldMatch("version.c")
1032 self
.try_shouldMatch(os
.path
.join("subdir", "subdir.c"))
1035 def dumpPatch(self
, patch
):
1036 # this exists to help me figure out the right 'patchlevel' value
1037 # should be returned by tryclient.getSourceStamp
1039 open(n
,"w").write(patch
)
1040 d
= self
.runCommand(".", ["lsdiff", n
])
1041 def p(res
): print "lsdiff:", res
.strip().split("\n")
1047 d
= defer
.succeed(None)
1049 d2
= self
.master
.botmaster
.waitUntilBuilderDetached("vc")
1050 d
.addCallback(lambda res
: self
.slave
.stopService())
1051 d
.addCallback(lambda res
: d2
)
1053 d
.addCallback(lambda res
: self
.master
.stopService())
1055 d
.addCallback(lambda res
: self
.httpServer
.stopListening())
1056 def stopHTTPTimer():
1058 from twisted
.web
import http
# Twisted-2.0
1060 from twisted
.protocols
import http
# Twisted-1.3
1061 http
._logDateTimeStop
() # shut down the internal timer. DUMB!
1062 d
.addCallback(lambda res
: stopHTTPTimer())
1063 d
.addCallback(lambda res
: self
.tearDown2())
1066 def tearDown2(self
):
1069 class CVSHelper(BaseHelper
):
1070 branchname
= "branch"
1071 try_branchname
= "branch"
1074 cvspaths
= which('cvs')
1076 return (False, "CVS is not installed")
1077 # cvs-1.10 (as shipped with OS-X 10.3 "Panther") is too old for this
1078 # test. There is a situation where we check out a tree, make a
1079 # change, then commit it back, and CVS refuses to believe that we're
1080 # operating in a CVS tree. I tested cvs-1.12.9 and it works ok, OS-X
1081 # 10.4 "Tiger" comes with cvs-1.11, but I haven't tested that yet.
1082 # For now, skip the tests if we've got 1.10 .
1083 log
.msg("running %s --version.." % (cvspaths
[0],))
1084 d
= utils
.getProcessOutput(cvspaths
[0], ["--version"],
1086 d
.addCallback(self
._capable
, cvspaths
[0])
1089 def _capable(self
, v
, vcexe
):
1090 m
= re
.search(r
'\(CVS\) ([\d\.]+) ', v
)
1092 log
.msg("couldn't identify CVS version number in output:")
1093 log
.msg("'''%s'''" % v
)
1094 log
.msg("skipping tests")
1095 return (False, "Found CVS but couldn't identify its version")
1097 log
.msg("found CVS version '%s'" % ver
)
1099 return (False, "Found CVS, but it is too old")
1104 # this timestamp is eventually passed to CVS in a -D argument, and
1105 # strftime's %z specifier doesn't seem to work reliably (I get +0000
1106 # where I should get +0700 under linux sometimes, and windows seems
1107 # to want to put a verbose 'Eastern Standard Time' in there), so
1108 # leave off the timezone specifier and treat this as localtime. A
1109 # valid alternative would be to use a hard-coded +0000 and
1111 return time
.strftime("%Y-%m-%d %H:%M:%S", time
.localtime())
1113 def createRepository(self
):
1114 self
.createBasedir()
1115 self
.cvsrep
= cvsrep
= os
.path
.join(self
.repbase
, "CVS-Repository")
1116 tmp
= os
.path
.join(self
.repbase
, "cvstmp")
1118 w
= self
.dovc(self
.repbase
, "-d %s init" % cvsrep
)
1119 yield w
; w
.getResult() # we must getResult() to raise any exceptions
1122 cmd
= ("-d %s import" % cvsrep
+
1123 " -m sample_project_files sample vendortag start")
1124 w
= self
.dovc(tmp
, cmd
)
1125 yield w
; w
.getResult()
1127 # take a timestamp as the first revision number
1129 self
.addTrunkRev(self
.getdate())
1132 w
= self
.dovc(self
.repbase
,
1133 "-d %s checkout -d cvstmp sample" % self
.cvsrep
)
1134 yield w
; w
.getResult()
1136 w
= self
.dovc(tmp
, "tag -b %s" % self
.branchname
)
1137 yield w
; w
.getResult()
1138 self
.populate_branch(tmp
)
1140 "commit -m commit_on_branch -r %s" % self
.branchname
)
1141 yield w
; w
.getResult()
1144 self
.addBranchRev(self
.getdate())
1146 self
.vcargs
= { 'cvsroot': self
.cvsrep
, 'cvsmodule': "sample" }
1147 createRepository
= deferredGenerator(createRepository
)
1150 def vc_revise(self
):
1151 tmp
= os
.path
.join(self
.repbase
, "cvstmp")
1153 w
= self
.dovc(self
.repbase
,
1154 "-d %s checkout -d cvstmp sample" % self
.cvsrep
)
1155 yield w
; w
.getResult()
1157 version_c
= VERSION_C
% self
.version
1158 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1160 "commit -m revised_to_%d version.c" % self
.version
)
1161 yield w
; w
.getResult()
1164 self
.addTrunkRev(self
.getdate())
1166 vc_revise
= deferredGenerator(vc_revise
)
1168 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1169 # 'workdir' is an absolute path
1170 assert os
.path
.abspath(workdir
) == workdir
1171 cmd
= [self
.vcexe
, "-d", self
.cvsrep
, "checkout",
1174 if branch
is not None:
1177 cmd
.append("sample")
1178 w
= self
.do(self
.repbase
, cmd
)
1179 yield w
; w
.getResult()
1180 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1181 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1183 def vc_try_finish(self
, workdir
):
1184 rmdirRecursive(workdir
)
1186 class CVS(VCBase
, unittest
.TestCase
):
1192 # CVS gives us got_revision, but it is based entirely upon the local
1193 # clock, which means it is unlikely to match the timestamp taken earlier.
1194 # This might be enough for common use, but won't be good enough for our
1195 # tests to accept, so pretend it doesn't have got_revision at all.
1196 has_got_revision
= False
1198 def testCheckout(self
):
1199 d
= self
.do_vctest()
1202 def testPatch(self
):
1206 def testCheckoutBranch(self
):
1207 d
= self
.do_branch()
1211 d
= self
.do_getpatch(doBranch
=False)
1214 VCS
.registerVC(CVS
.vc_name
, CVSHelper())
1217 class SVNHelper(BaseHelper
):
1218 branchname
= "sample/branch"
1219 try_branchname
= "sample/branch"
1222 svnpaths
= which('svn')
1223 svnadminpaths
= which('svnadmin')
1225 return (False, "SVN is not installed")
1226 if not svnadminpaths
:
1227 return (False, "svnadmin is not installed")
1228 # we need svn to be compiled with the ra_local access
1230 log
.msg("running svn --version..")
1231 env
= os
.environ
.copy()
1233 d
= utils
.getProcessOutput(svnpaths
[0], ["--version"],
1235 d
.addCallback(self
._capable
, svnpaths
[0], svnadminpaths
[0])
1238 def _capable(self
, v
, vcexe
, svnadmin
):
1239 if v
.find("handles 'file' schem") != -1:
1240 # older versions say 'schema', 1.2.0 and beyond say 'scheme'
1242 self
.svnadmin
= svnadmin
1244 excuse
= ("%s found but it does not support 'file:' " +
1245 "schema, skipping svn tests") % vcexe
1247 return (False, excuse
)
1249 def createRepository(self
):
1250 self
.createBasedir()
1251 self
.svnrep
= os
.path
.join(self
.repbase
,
1252 "SVN-Repository").replace('\\','/')
1253 tmp
= os
.path
.join(self
.repbase
, "svntmp")
1254 if sys
.platform
== 'win32':
1255 # On Windows Paths do not start with a /
1256 self
.svnurl
= "file:///%s" % self
.svnrep
1258 self
.svnurl
= "file://%s" % self
.svnrep
1259 self
.svnurl_trunk
= self
.svnurl
+ "/sample/trunk"
1260 self
.svnurl_branch
= self
.svnurl
+ "/sample/branch"
1262 w
= self
.do(self
.repbase
, self
.svnadmin
+" create %s" % self
.svnrep
)
1263 yield w
; w
.getResult()
1267 "import -m sample_project_files %s" %
1269 yield w
; out
= w
.getResult()
1271 m
= re
.search(r
'Committed revision (\d+)\.', out
)
1272 assert m
.group(1) == "1" # first revision is always "1"
1273 self
.addTrunkRev(int(m
.group(1)))
1275 w
= self
.dovc(self
.repbase
,
1276 "checkout %s svntmp" % self
.svnurl_trunk
)
1277 yield w
; w
.getResult()
1279 w
= self
.dovc(tmp
, "cp -m make_branch %s %s" % (self
.svnurl_trunk
,
1280 self
.svnurl_branch
))
1281 yield w
; w
.getResult()
1282 w
= self
.dovc(tmp
, "switch %s" % self
.svnurl_branch
)
1283 yield w
; w
.getResult()
1284 self
.populate_branch(tmp
)
1285 w
= self
.dovc(tmp
, "commit -m commit_on_branch")
1286 yield w
; out
= w
.getResult()
1288 m
= re
.search(r
'Committed revision (\d+)\.', out
)
1289 self
.addBranchRev(int(m
.group(1)))
1290 createRepository
= deferredGenerator(createRepository
)
1292 def vc_revise(self
):
1293 tmp
= os
.path
.join(self
.repbase
, "svntmp")
1295 log
.msg("vc_revise" + self
.svnurl_trunk
)
1296 w
= self
.dovc(self
.repbase
,
1297 "checkout %s svntmp" % self
.svnurl_trunk
)
1298 yield w
; w
.getResult()
1300 version_c
= VERSION_C
% self
.version
1301 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1302 w
= self
.dovc(tmp
, "commit -m revised_to_%d" % self
.version
)
1303 yield w
; out
= w
.getResult()
1304 m
= re
.search(r
'Committed revision (\d+)\.', out
)
1305 self
.addTrunkRev(int(m
.group(1)))
1307 vc_revise
= deferredGenerator(vc_revise
)
1309 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1310 assert os
.path
.abspath(workdir
) == workdir
1311 if os
.path
.exists(workdir
):
1312 rmdirRecursive(workdir
)
1314 svnurl
= self
.svnurl_trunk
1316 # N.B.: this is *not* os.path.join: SVN URLs use slashes
1317 # regardless of the host operating system's filepath separator
1318 svnurl
= self
.svnurl
+ "/" + branch
1319 w
= self
.dovc(self
.repbase
,
1320 "checkout %s %s" % (svnurl
, workdir
))
1321 yield w
; w
.getResult()
1322 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1323 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1325 def vc_try_finish(self
, workdir
):
1326 rmdirRecursive(workdir
)
1329 class SVN(VCBase
, unittest
.TestCase
):
1335 has_got_revision
= True
1336 has_got_revision_branches_are_merged
= True
1338 def testCheckout(self
):
1339 # we verify this one with the svnurl style of vcargs. We test the
1340 # baseURL/defaultBranch style in testPatch and testCheckoutBranch.
1341 self
.helper
.vcargs
= { 'svnurl': self
.helper
.svnurl_trunk
}
1342 d
= self
.do_vctest()
1345 def testPatch(self
):
1346 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1347 'defaultBranch': "sample/trunk",
1352 def testCheckoutBranch(self
):
1353 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1354 'defaultBranch': "sample/trunk",
1356 d
= self
.do_branch()
1360 # extract the base revision and patch from a modified tree, use it to
1361 # create the same contents on the buildslave
1362 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1363 'defaultBranch': "sample/trunk",
1365 d
= self
.do_getpatch()
1368 VCS
.registerVC(SVN
.vc_name
, SVNHelper())
1371 class P4Helper(BaseHelper
):
1372 branchname
= "branch"
1373 p4port
= 'localhost:1666'
1375 base_descr
= 'Change: new\nDescription: asdf\nFiles:\n'
1378 p4paths
= which('p4')
1379 p4dpaths
= which('p4d')
1381 return (False, "p4 is not installed")
1383 return (False, "p4d is not installed")
1384 self
.vcexe
= p4paths
[0]
1385 self
.p4dexe
= p4dpaths
[0]
1388 class _P4DProtocol(protocol
.ProcessProtocol
):
1390 self
.started
= defer
.Deferred()
1391 self
.ended
= defer
.Deferred()
1393 def outReceived(self
, data
):
1394 # When it says starting, it has bound to the socket.
1396 if data
.startswith('Perforce Server starting...'):
1397 self
.started
.callback(None)
1399 print "p4d said %r" % data
1401 raise Exception('p4d said %r' % data
)
1403 self
.started
.errback(failure
.Failure())
1406 def errReceived(self
, data
):
1407 print "p4d stderr: %s" % data
1409 def processEnded(self
, status_object
):
1410 if status_object
.check(error
.ProcessDone
):
1411 self
.ended
.callback(None)
1413 self
.ended
.errback(status_object
)
1415 def _start_p4d(self
):
1416 proto
= self
._P
4DProtocol
()
1417 reactor
.spawnProcess(proto
, self
.p4dexe
, ['p4d', '-p', self
.p4port
],
1418 env
=os
.environ
, path
=self
.p4rep
)
1419 return proto
.started
, proto
.ended
1421 def dop4(self
, basedir
, command
, failureIsOk
=False, stdin
=None):
1422 # p4 looks at $PWD instead of getcwd(), which causes confusion when
1423 # we spawn commands without an intervening shell (sh -c). We can
1424 # override this with a -d argument.
1425 command
= "-p %s -d %s %s" % (self
.p4port
, basedir
, command
)
1426 return self
.dovc(basedir
, command
, failureIsOk
, stdin
)
1428 def createRepository(self
):
1429 # this is only called once per VC system, so start p4d here.
1431 self
.createBasedir()
1432 tmp
= os
.path
.join(self
.repbase
, "p4tmp")
1433 self
.p4rep
= os
.path
.join(self
.repbase
, 'P4-Repository')
1434 os
.mkdir(self
.p4rep
)
1437 started
, self
.p4d_shutdown
= self
._start
_p
4d
()
1438 w
= waitForDeferred(started
)
1439 yield w
; w
.getResult()
1441 # Create client spec.
1443 clispec
= 'Client: creator\n'
1444 clispec
+= 'Root: %s\n' % tmp
1445 clispec
+= 'View:\n'
1446 clispec
+= '\t//depot/... //creator/...\n'
1447 w
= self
.dop4(tmp
, 'client -i', stdin
=clispec
)
1448 yield w
; w
.getResult()
1450 # Create first rev (trunk).
1451 self
.populate(os
.path
.join(tmp
, 'trunk'))
1452 files
= ['main.c', 'version.c', 'subdir/subdir.c']
1453 w
= self
.dop4(tmp
, "-c creator add "
1454 + " ".join(['trunk/%s' % f
for f
in files
]))
1455 yield w
; w
.getResult()
1456 descr
= self
.base_descr
1458 descr
+= '\t//depot/trunk/%s\n' % file
1459 w
= self
.dop4(tmp
, "-c creator submit -i", stdin
=descr
)
1460 yield w
; out
= w
.getResult()
1461 m
= re
.search(r
'Change (\d+) submitted.', out
)
1462 assert m
.group(1) == '1'
1463 self
.addTrunkRev(m
.group(1))
1465 # Create second rev (branch).
1466 w
= self
.dop4(tmp
, '-c creator integrate '
1467 + '//depot/trunk/... //depot/branch/...')
1468 yield w
; w
.getResult()
1469 w
= self
.dop4(tmp
, "-c creator edit branch/main.c")
1470 yield w
; w
.getResult()
1471 self
.populate_branch(os
.path
.join(tmp
, 'branch'))
1472 descr
= self
.base_descr
1474 descr
+= '\t//depot/branch/%s\n' % file
1475 w
= self
.dop4(tmp
, "-c creator submit -i", stdin
=descr
)
1476 yield w
; out
= w
.getResult()
1477 m
= re
.search(r
'Change (\d+) submitted.', out
)
1478 self
.addBranchRev(m
.group(1))
1479 createRepository
= deferredGenerator(createRepository
)
1481 def vc_revise(self
):
1482 tmp
= os
.path
.join(self
.repbase
, "p4tmp")
1484 version_c
= VERSION_C
% self
.version
1485 w
= self
.dop4(tmp
, '-c creator edit trunk/version.c')
1486 yield w
; w
.getResult()
1487 open(os
.path
.join(tmp
, "trunk/version.c"), "w").write(version_c
)
1488 descr
= self
.base_descr
+ '\t//depot/trunk/version.c\n'
1489 w
= self
.dop4(tmp
, "-c creator submit -i", stdin
=descr
)
1490 yield w
; out
= w
.getResult()
1491 m
= re
.search(r
'Change (\d+) submitted.', out
)
1492 self
.addTrunkRev(m
.group(1))
1493 vc_revise
= deferredGenerator(vc_revise
)
1495 def shutdown_p4d(self
):
1496 d
= self
.runCommand(self
.repbase
, '%s -p %s admin stop'
1497 % (self
.vcexe
, self
.p4port
))
1498 return d
.addCallback(lambda _
: self
.p4d_shutdown
)
1500 class P4(VCBase
, unittest
.TestCase
):
1505 def tearDownClass(self
):
1506 return maybeWait(self
.helper
.shutdown_p4d())
1508 def testCheckout(self
):
1509 self
.helper
.vcargs
= { 'p4port': self
.helper
.p4port
,
1510 'p4base': '//depot/',
1511 'defaultBranch': 'trunk' }
1512 d
= self
.do_vctest(testRetry
=False)
1513 # TODO: like arch and darcs, sync does nothing when server is not
1517 def testPatch(self
):
1518 self
.helper
.vcargs
= { 'p4port': self
.helper
.p4port
,
1519 'p4base': '//depot/',
1520 'defaultBranch': 'trunk' }
1524 def testBranch(self
):
1525 self
.helper
.vcargs
= { 'p4port': self
.helper
.p4port
,
1526 'p4base': '//depot/',
1527 'defaultBranch': 'trunk' }
1528 d
= self
.do_branch()
1531 VCS
.registerVC(P4
.vc_name
, P4Helper())
1534 class DarcsHelper(BaseHelper
):
1535 branchname
= "branch"
1536 try_branchname
= "branch"
1539 darcspaths
= which('darcs')
1541 return (False, "Darcs is not installed")
1542 self
.vcexe
= darcspaths
[0]
1545 def createRepository(self
):
1546 self
.createBasedir()
1547 self
.darcs_base
= os
.path
.join(self
.repbase
, "Darcs-Repository")
1548 self
.rep_trunk
= os
.path
.join(self
.darcs_base
, "trunk")
1549 self
.rep_branch
= os
.path
.join(self
.darcs_base
, "branch")
1550 tmp
= os
.path
.join(self
.repbase
, "darcstmp")
1552 os
.makedirs(self
.rep_trunk
)
1553 w
= self
.dovc(self
.rep_trunk
, "initialize")
1554 yield w
; w
.getResult()
1555 os
.makedirs(self
.rep_branch
)
1556 w
= self
.dovc(self
.rep_branch
, "initialize")
1557 yield w
; w
.getResult()
1560 w
= self
.dovc(tmp
, "initialize")
1561 yield w
; w
.getResult()
1562 w
= self
.dovc(tmp
, "add -r .")
1563 yield w
; w
.getResult()
1564 w
= self
.dovc(tmp
, "record -a -m initial_import --skip-long-comment -A test@buildbot.sf.net")
1565 yield w
; w
.getResult()
1566 w
= self
.dovc(tmp
, "push -a %s" % self
.rep_trunk
)
1567 yield w
; w
.getResult()
1568 w
= self
.dovc(tmp
, "changes --context")
1569 yield w
; out
= w
.getResult()
1570 self
.addTrunkRev(out
)
1572 self
.populate_branch(tmp
)
1573 w
= self
.dovc(tmp
, "record -a --ignore-times -m commit_on_branch --skip-long-comment -A test@buildbot.sf.net")
1574 yield w
; w
.getResult()
1575 w
= self
.dovc(tmp
, "push -a %s" % self
.rep_branch
)
1576 yield w
; w
.getResult()
1577 w
= self
.dovc(tmp
, "changes --context")
1578 yield w
; out
= w
.getResult()
1579 self
.addBranchRev(out
)
1581 createRepository
= deferredGenerator(createRepository
)
1583 def vc_revise(self
):
1584 tmp
= os
.path
.join(self
.repbase
, "darcstmp")
1586 w
= self
.dovc(tmp
, "initialize")
1587 yield w
; w
.getResult()
1588 w
= self
.dovc(tmp
, "pull -a %s" % self
.rep_trunk
)
1589 yield w
; w
.getResult()
1592 version_c
= VERSION_C
% self
.version
1593 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1594 w
= self
.dovc(tmp
, "record -a --ignore-times -m revised_to_%d --skip-long-comment -A test@buildbot.sf.net" % self
.version
)
1595 yield w
; w
.getResult()
1596 w
= self
.dovc(tmp
, "push -a %s" % self
.rep_trunk
)
1597 yield w
; w
.getResult()
1598 w
= self
.dovc(tmp
, "changes --context")
1599 yield w
; out
= w
.getResult()
1600 self
.addTrunkRev(out
)
1602 vc_revise
= deferredGenerator(vc_revise
)
1604 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1605 assert os
.path
.abspath(workdir
) == workdir
1606 if os
.path
.exists(workdir
):
1607 rmdirRecursive(workdir
)
1608 os
.makedirs(workdir
)
1609 w
= self
.dovc(workdir
, "initialize")
1610 yield w
; w
.getResult()
1612 rep
= self
.rep_trunk
1614 rep
= os
.path
.join(self
.darcs_base
, branch
)
1615 w
= self
.dovc(workdir
, "pull -a %s" % rep
)
1616 yield w
; w
.getResult()
1617 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1618 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1620 def vc_try_finish(self
, workdir
):
1621 rmdirRecursive(workdir
)
1624 class Darcs(VCBase
, unittest
.TestCase
):
1627 # Darcs has a metadir="_darcs", but it does not have an 'export'
1630 vctype
= "step.Darcs"
1631 vctype_try
= "darcs"
1632 has_got_revision
= True
1634 def testCheckout(self
):
1635 self
.helper
.vcargs
= { 'repourl': self
.helper
.rep_trunk
}
1636 d
= self
.do_vctest(testRetry
=False)
1638 # TODO: testRetry has the same problem with Darcs as it does for
1642 def testPatch(self
):
1643 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1644 'defaultBranch': "trunk" }
1648 def testCheckoutBranch(self
):
1649 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1650 'defaultBranch': "trunk" }
1651 d
= self
.do_branch()
1654 def testCheckoutHTTP(self
):
1656 repourl
= "http://localhost:%d/Darcs-Repository/trunk" % self
.httpPort
1657 self
.helper
.vcargs
= { 'repourl': repourl
}
1658 d
= self
.do_vctest(testRetry
=False)
1662 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1663 'defaultBranch': "trunk" }
1664 d
= self
.do_getpatch()
1667 VCS
.registerVC(Darcs
.vc_name
, DarcsHelper())
1671 def registerRepository(self
, coordinates
):
1673 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1674 yield w
; out
= w
.getResult()
1676 w
= self
.dovc(self
.repbase
, "register-archive -d %s" % a
)
1677 yield w
; w
.getResult()
1678 w
= self
.dovc(self
.repbase
, "register-archive %s" % coordinates
)
1679 yield w
; w
.getResult()
1680 registerRepository
= deferredGenerator(registerRepository
)
1682 def unregisterRepository(self
):
1684 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1685 yield w
; out
= w
.getResult()
1687 w
= self
.dovc(self
.repbase
, "register-archive -d %s" % a
)
1688 yield w
; out
= w
.getResult()
1689 unregisterRepository
= deferredGenerator(unregisterRepository
)
1691 class TlaHelper(BaseHelper
, ArchCommon
):
1692 defaultbranch
= "testvc--mainline--1"
1693 branchname
= "testvc--branch--1"
1694 try_branchname
= None # TlaExtractor can figure it out by itself
1698 tlapaths
= which('tla')
1700 return (False, "Arch (tla) is not installed")
1701 self
.vcexe
= tlapaths
[0]
1704 def do_get(self
, basedir
, archive
, branch
, newdir
):
1705 # the 'get' syntax is different between tla and baz. baz, while
1706 # claiming to honor an --archive argument, in fact ignores it. The
1707 # correct invocation is 'baz get archive/revision newdir'.
1708 if self
.archcmd
== "tla":
1709 w
= self
.dovc(basedir
,
1710 "get -A %s %s %s" % (archive
, branch
, newdir
))
1712 w
= self
.dovc(basedir
,
1713 "get %s/%s %s" % (archive
, branch
, newdir
))
1716 def createRepository(self
):
1717 self
.createBasedir()
1718 # first check to see if bazaar is around, since we'll need to know
1720 d
= VCS
.capable(Bazaar
.vc_name
)
1721 d
.addCallback(self
._createRepository
_1)
1724 def _createRepository_1(self
, res
):
1727 # pick a hopefully unique string for the archive name, in the form
1728 # test-%d@buildbot.sf.net--testvc, since otherwise multiple copies of
1729 # the unit tests run in the same user account will collide (since the
1730 # archive names are kept in the per-user ~/.arch-params/ directory).
1732 self
.archname
= "test-%s-%d@buildbot.sf.net--testvc" % (self
.archcmd
,
1734 trunk
= self
.defaultbranch
1735 branch
= self
.branchname
1737 repword
= self
.archcmd
.capitalize()
1738 self
.archrep
= os
.path
.join(self
.repbase
, "%s-Repository" % repword
)
1739 tmp
= os
.path
.join(self
.repbase
, "archtmp")
1744 w
= self
.dovc(tmp
, "my-id", failureIsOk
=True)
1745 yield w
; res
= w
.getResult()
1747 # tla will fail a lot of operations if you have not set an ID
1748 w
= self
.do(tmp
, [self
.vcexe
, "my-id",
1749 "Buildbot Test Suite <test@buildbot.sf.net>"])
1750 yield w
; w
.getResult()
1753 # bazaar keeps a cache of revisions, but this test creates a new
1754 # archive each time it is run, so the cache causes errors.
1755 # Disable the cache to avoid these problems. This will be
1756 # slightly annoying for people who run the buildbot tests under
1757 # the same UID as one which uses baz on a regular basis, but
1758 # bazaar doesn't give us a way to disable the cache just for this
1760 cmd
= "%s cache-config --disable" % VCS
.getHelper('bazaar').vcexe
1761 w
= self
.do(tmp
, cmd
)
1762 yield w
; w
.getResult()
1764 w
= waitForDeferred(self
.unregisterRepository())
1765 yield w
; w
.getResult()
1767 # these commands can be run in any directory
1768 w
= self
.dovc(tmp
, "make-archive -l %s %s" % (a
, self
.archrep
))
1769 yield w
; w
.getResult()
1770 if self
.archcmd
== "tla":
1771 w
= self
.dovc(tmp
, "archive-setup -A %s %s" % (a
, trunk
))
1772 yield w
; w
.getResult()
1773 w
= self
.dovc(tmp
, "archive-setup -A %s %s" % (a
, branch
))
1774 yield w
; w
.getResult()
1776 # baz does not require an 'archive-setup' step
1779 # these commands must be run in the directory that is to be imported
1780 w
= self
.dovc(tmp
, "init-tree --nested %s/%s" % (a
, trunk
))
1781 yield w
; w
.getResult()
1782 files
= " ".join(["main.c", "version.c", "subdir",
1783 os
.path
.join("subdir", "subdir.c")])
1784 w
= self
.dovc(tmp
, "add-id %s" % files
)
1785 yield w
; w
.getResult()
1787 w
= self
.dovc(tmp
, "import %s/%s" % (a
, trunk
))
1788 yield w
; out
= w
.getResult()
1789 self
.addTrunkRev("base-0")
1792 if self
.archcmd
== "tla":
1793 branchstart
= "%s--base-0" % trunk
1794 w
= self
.dovc(tmp
, "tag -A %s %s %s" % (a
, branchstart
, branch
))
1795 yield w
; w
.getResult()
1797 w
= self
.dovc(tmp
, "branch %s" % branch
)
1798 yield w
; w
.getResult()
1802 # check out the branch
1803 w
= self
.do_get(self
.repbase
, a
, branch
, "archtmp")
1804 yield w
; w
.getResult()
1806 self
.populate_branch(tmp
)
1807 logfile
= "++log.%s--%s" % (branch
, a
)
1808 logmsg
= "Summary: commit on branch\nKeywords:\n\n"
1809 open(os
.path
.join(tmp
, logfile
), "w").write(logmsg
)
1810 w
= self
.dovc(tmp
, "commit")
1811 yield w
; out
= w
.getResult()
1812 m
= re
.search(r
'committed %s/%s--([\S]+)' % (a
, branch
),
1814 assert (m
.group(1) == "base-0" or m
.group(1).startswith("patch-"))
1815 self
.addBranchRev(m
.group(1))
1817 w
= waitForDeferred(self
.unregisterRepository())
1818 yield w
; w
.getResult()
1821 # we unregister the repository each time, because we might have
1822 # changed the coordinates (since we switch from a file: URL to an
1823 # http: URL for various tests). The buildslave code doesn't forcibly
1824 # unregister the archive, so we have to do it here.
1825 w
= waitForDeferred(self
.unregisterRepository())
1826 yield w
; w
.getResult()
1828 _createRepository_1
= deferredGenerator(_createRepository_1
)
1830 def vc_revise(self
):
1831 # the fix needs to be done in a workspace that is linked to a
1832 # read-write version of the archive (i.e., using file-based
1833 # coordinates instead of HTTP ones), so we re-register the repository
1834 # before we begin. We unregister it when we're done to make sure the
1835 # build will re-register the correct one for whichever test is
1836 # currently being run.
1838 # except, that step.Bazaar really doesn't like it when the archive
1839 # gets unregistered behind its back. The slave tries to do a 'baz
1840 # replay' in a tree with an archive that is no longer recognized, and
1841 # baz aborts with a botched invariant exception. This causes
1842 # mode=update to fall back to clobber+get, which flunks one of the
1843 # tests (the 'newfile' check in _do_vctest_update_3 fails)
1845 # to avoid this, we take heroic steps here to leave the archive
1846 # registration in the same state as we found it.
1848 tmp
= os
.path
.join(self
.repbase
, "archtmp")
1851 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1852 yield w
; out
= w
.getResult()
1854 lines
= out
.split("\n")
1855 coordinates
= lines
[1].strip()
1857 # now register the read-write location
1858 w
= waitForDeferred(self
.registerRepository(self
.archrep
))
1859 yield w
; w
.getResult()
1861 trunk
= self
.defaultbranch
1863 w
= self
.do_get(self
.repbase
, a
, trunk
, "archtmp")
1864 yield w
; w
.getResult()
1866 # tla appears to use timestamps to determine which files have
1867 # changed, so wait long enough for the new file to have a different
1871 version_c
= VERSION_C
% self
.version
1872 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1874 logfile
= "++log.%s--%s" % (trunk
, a
)
1875 logmsg
= "Summary: revised_to_%d\nKeywords:\n\n" % self
.version
1876 open(os
.path
.join(tmp
, logfile
), "w").write(logmsg
)
1877 w
= self
.dovc(tmp
, "commit")
1878 yield w
; out
= w
.getResult()
1879 m
= re
.search(r
'committed %s/%s--([\S]+)' % (a
, trunk
),
1881 assert (m
.group(1) == "base-0" or m
.group(1).startswith("patch-"))
1882 self
.addTrunkRev(m
.group(1))
1884 # now re-register the original coordinates
1885 w
= waitForDeferred(self
.registerRepository(coordinates
))
1886 yield w
; w
.getResult()
1888 vc_revise
= deferredGenerator(vc_revise
)
1890 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1891 assert os
.path
.abspath(workdir
) == workdir
1892 if os
.path
.exists(workdir
):
1893 rmdirRecursive(workdir
)
1897 # register the read-write location, if it wasn't already registered
1898 w
= waitForDeferred(self
.registerRepository(self
.archrep
))
1899 yield w
; w
.getResult()
1901 w
= self
.do_get(self
.repbase
, a
, "testvc--mainline--1", workdir
)
1902 yield w
; w
.getResult()
1906 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1907 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1909 def vc_try_finish(self
, workdir
):
1910 rmdirRecursive(workdir
)
1912 class Arch(VCBase
, unittest
.TestCase
):
1916 # Arch has a metadir="{arch}", but it does not have an 'export' mode.
1917 vctype
= "step.Arch"
1919 has_got_revision
= True
1921 def testCheckout(self
):
1922 # these are the coordinates of the read-write archive used by all the
1923 # non-HTTP tests. testCheckoutHTTP overrides these.
1924 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1925 'version': self
.helper
.defaultbranch
}
1926 d
= self
.do_vctest(testRetry
=False)
1927 # the current testRetry=True logic doesn't have the desired effect:
1928 # "update" is a no-op because arch knows that the repository hasn't
1929 # changed. Other VC systems will re-checkout missing files on
1930 # update, arch just leaves the tree untouched. TODO: come up with
1931 # some better test logic, probably involving a copy of the
1932 # repository that has a few changes checked in.
1936 def testCheckoutHTTP(self
):
1938 url
= "http://localhost:%d/Tla-Repository" % self
.httpPort
1939 self
.helper
.vcargs
= { 'url': url
,
1940 'version': "testvc--mainline--1" }
1941 d
= self
.do_vctest(testRetry
=False)
1944 def testPatch(self
):
1945 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1946 'version': self
.helper
.defaultbranch
}
1950 def testCheckoutBranch(self
):
1951 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1952 'version': self
.helper
.defaultbranch
}
1953 d
= self
.do_branch()
1957 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1958 'version': self
.helper
.defaultbranch
}
1959 d
= self
.do_getpatch()
1962 VCS
.registerVC(Arch
.vc_name
, TlaHelper())
1965 class BazaarHelper(TlaHelper
):
1969 bazpaths
= which('baz')
1971 return (False, "Arch (baz) is not installed")
1972 self
.vcexe
= bazpaths
[0]
1975 def setUp2(self
, res
):
1976 # we unregister the repository each time, because we might have
1977 # changed the coordinates (since we switch from a file: URL to an
1978 # http: URL for various tests). The buildslave code doesn't forcibly
1979 # unregister the archive, so we have to do it here.
1980 d
= self
.unregisterRepository()
1987 vctype
= "step.Bazaar"
1989 has_got_revision
= True
1993 def testCheckout(self
):
1994 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1995 # Baz adds the required 'archive' argument
1996 'archive': self
.helper
.archname
,
1997 'version': self
.helper
.defaultbranch
,
1999 d
= self
.do_vctest(testRetry
=False)
2000 # the current testRetry=True logic doesn't have the desired effect:
2001 # "update" is a no-op because arch knows that the repository hasn't
2002 # changed. Other VC systems will re-checkout missing files on
2003 # update, arch just leaves the tree untouched. TODO: come up with
2004 # some better test logic, probably involving a copy of the
2005 # repository that has a few changes checked in.
2009 def testCheckoutHTTP(self
):
2011 url
= "http://localhost:%d/Baz-Repository" % self
.httpPort
2012 self
.helper
.vcargs
= { 'url': url
,
2013 'archive': self
.helper
.archname
,
2014 'version': self
.helper
.defaultbranch
,
2016 d
= self
.do_vctest(testRetry
=False)
2019 def testPatch(self
):
2020 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
2021 # Baz adds the required 'archive' argument
2022 'archive': self
.helper
.archname
,
2023 'version': self
.helper
.defaultbranch
,
2028 def testCheckoutBranch(self
):
2029 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
2030 # Baz adds the required 'archive' argument
2031 'archive': self
.helper
.archname
,
2032 'version': self
.helper
.defaultbranch
,
2034 d
= self
.do_branch()
2038 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
2039 # Baz adds the required 'archive' argument
2040 'archive': self
.helper
.archname
,
2041 'version': self
.helper
.defaultbranch
,
2043 d
= self
.do_getpatch()
2046 def fixRepository(self
):
2047 self
.fixtimer
= None
2048 self
.site
.resource
= self
.root
2050 def testRetry(self
):
2051 # we want to verify that step.Source(retry=) works, and the easiest
2052 # way to make VC updates break (temporarily) is to break the HTTP
2053 # server that's providing the repository. Anything else pretty much
2054 # requires mutating the (read-only) BUILDBOT_TEST_VC repository, or
2055 # modifying the buildslave's checkout command while it's running.
2057 # this test takes a while to run, so don't bother doing it with
2058 # anything other than baz
2062 # break the repository server
2063 from twisted
.web
import static
2064 self
.site
.resource
= static
.Data("Sorry, repository is offline",
2066 # and arrange to fix it again in 5 seconds, while the test is
2068 self
.fixtimer
= reactor
.callLater(5, self
.fixRepository
)
2070 url
= "http://localhost:%d/Baz-Repository" % self
.httpPort
2071 self
.helper
.vcargs
= { 'url': url
,
2072 'archive': self
.helper
.archname
,
2073 'version': self
.helper
.defaultbranch
,
2076 d
= self
.do_vctest_once(True)
2077 d
.addCallback(self
._testRetry
_1)
2079 def _testRetry_1(self
, bs
):
2080 # make sure there was mention of the retry attempt in the logs
2082 self
.failUnlessIn("unable to access URL", l
.getText(),
2083 "funny, VC operation didn't fail at least once")
2084 self
.failUnlessIn("update failed, trying 4 more times after 5 seconds",
2085 l
.getTextWithHeaders(),
2086 "funny, VC operation wasn't reattempted")
2088 def testRetryFails(self
):
2089 # make sure that the build eventually gives up on a repository which
2090 # is completely unavailable
2094 # break the repository server, and leave it broken
2095 from twisted
.web
import static
2096 self
.site
.resource
= static
.Data("Sorry, repository is offline",
2099 url
= "http://localhost:%d/Baz-Repository" % self
.httpPort
2100 self
.helper
.vcargs
= {'url': url
,
2101 'archive': self
.helper
.archname
,
2102 'version': self
.helper
.defaultbranch
,
2105 d
= self
.do_vctest_once(False)
2106 d
.addCallback(self
._testRetryFails
_1)
2108 def _testRetryFails_1(self
, bs
):
2109 self
.failUnlessEqual(bs
.getResults(), FAILURE
)
2111 def tearDown2(self
):
2113 self
.fixtimer
.cancel()
2114 # tell tla to get rid of the leftover archive this test leaves in the
2115 # user's 'tla archives' listing. The name of this archive is provided
2116 # by the repository tarball, so the following command must use the
2117 # same name. We could use archive= to set it explicitly, but if you
2118 # change it from the default, then 'tla update' won't work.
2119 d
= self
.helper
.unregisterRepository()
2122 VCS
.registerVC(Bazaar
.vc_name
, BazaarHelper())
2124 class MercurialHelper(BaseHelper
):
2125 branchname
= "branch"
2126 try_branchname
= "branch"
2129 hgpaths
= which("hg")
2131 return (False, "Mercurial is not installed")
2132 self
.vcexe
= hgpaths
[0]
2135 def extract_id(self
, output
):
2136 m
= re
.search(r
'^(\w+)', output
)
2139 def createRepository(self
):
2140 self
.createBasedir()
2141 self
.hg_base
= os
.path
.join(self
.repbase
, "Mercurial-Repository")
2142 self
.rep_trunk
= os
.path
.join(self
.hg_base
, "trunk")
2143 self
.rep_branch
= os
.path
.join(self
.hg_base
, "branch")
2144 tmp
= os
.path
.join(self
.hg_base
, "hgtmp")
2146 os
.makedirs(self
.rep_trunk
)
2147 w
= self
.dovc(self
.rep_trunk
, "init")
2148 yield w
; w
.getResult()
2149 os
.makedirs(self
.rep_branch
)
2150 w
= self
.dovc(self
.rep_branch
, "init")
2151 yield w
; w
.getResult()
2154 w
= self
.dovc(tmp
, "init")
2155 yield w
; w
.getResult()
2156 w
= self
.dovc(tmp
, "add")
2157 yield w
; w
.getResult()
2158 w
= self
.dovc(tmp
, "commit -m initial_import")
2159 yield w
; w
.getResult()
2160 w
= self
.dovc(tmp
, "push %s" % self
.rep_trunk
)
2161 # note that hg-push does not actually update the working directory
2162 yield w
; w
.getResult()
2163 w
= self
.dovc(tmp
, "identify")
2164 yield w
; out
= w
.getResult()
2165 self
.addTrunkRev(self
.extract_id(out
))
2167 self
.populate_branch(tmp
)
2168 w
= self
.dovc(tmp
, "commit -m commit_on_branch")
2169 yield w
; w
.getResult()
2170 w
= self
.dovc(tmp
, "push %s" % self
.rep_branch
)
2171 yield w
; w
.getResult()
2172 w
= self
.dovc(tmp
, "identify")
2173 yield w
; out
= w
.getResult()
2174 self
.addBranchRev(self
.extract_id(out
))
2176 createRepository
= deferredGenerator(createRepository
)
2178 def vc_revise(self
):
2179 tmp
= os
.path
.join(self
.hg_base
, "hgtmp2")
2180 w
= self
.dovc(self
.hg_base
, "clone %s %s" % (self
.rep_trunk
, tmp
))
2181 yield w
; w
.getResult()
2184 version_c
= VERSION_C
% self
.version
2185 version_c_filename
= os
.path
.join(tmp
, "version.c")
2186 open(version_c_filename
, "w").write(version_c
)
2187 # hg uses timestamps to distinguish files which have changed, so we
2188 # force the mtime forward a little bit
2189 future
= time
.time() + 2*self
.version
2190 os
.utime(version_c_filename
, (future
, future
))
2191 w
= self
.dovc(tmp
, "commit -m revised_to_%d" % self
.version
)
2192 yield w
; w
.getResult()
2193 w
= self
.dovc(tmp
, "push %s" % self
.rep_trunk
)
2194 yield w
; w
.getResult()
2195 w
= self
.dovc(tmp
, "identify")
2196 yield w
; out
= w
.getResult()
2197 self
.addTrunkRev(self
.extract_id(out
))
2199 vc_revise
= deferredGenerator(vc_revise
)
2201 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
2202 assert os
.path
.abspath(workdir
) == workdir
2203 if os
.path
.exists(workdir
):
2204 rmdirRecursive(workdir
)
2206 src
= self
.rep_branch
2208 src
= self
.rep_trunk
2209 w
= self
.dovc(self
.hg_base
, "clone %s %s" % (src
, workdir
))
2210 yield w
; w
.getResult()
2211 try_c_filename
= os
.path
.join(workdir
, "subdir", "subdir.c")
2212 open(try_c_filename
, "w").write(TRY_C
)
2213 future
= time
.time() + 2*self
.version
2214 os
.utime(try_c_filename
, (future
, future
))
2215 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
2217 def vc_try_finish(self
, workdir
):
2218 rmdirRecursive(workdir
)
2221 class Mercurial(VCBase
, unittest
.TestCase
):
2224 # Mercurial has a metadir=".hg", but it does not have an 'export' mode.
2226 vctype
= "step.Mercurial"
2228 has_got_revision
= True
2230 def testCheckout(self
):
2231 self
.helper
.vcargs
= { 'repourl': self
.helper
.rep_trunk
}
2232 d
= self
.do_vctest(testRetry
=False)
2234 # TODO: testRetry has the same problem with Mercurial as it does for
2238 def testPatch(self
):
2239 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2240 'defaultBranch': "trunk" }
2244 def testCheckoutBranch(self
):
2245 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2246 'defaultBranch': "trunk" }
2247 d
= self
.do_branch()
2250 def testCheckoutHTTP(self
):
2252 repourl
= "http://localhost:%d/Mercurial-Repository/trunk/.hg" % self
.httpPort
2253 self
.helper
.vcargs
= { 'repourl': repourl
}
2254 d
= self
.do_vctest(testRetry
=False)
2256 # TODO: The easiest way to publish hg over HTTP is by running 'hg serve'
2257 # as a child process while the test is running. (you can also use a CGI
2258 # script, which sounds difficult, or you can publish the files directly,
2259 # which isn't well documented).
2260 testCheckoutHTTP
.skip
= "not yet implemented, use 'hg serve'"
2263 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2264 'defaultBranch': "trunk" }
2265 d
= self
.do_getpatch()
2268 VCS
.registerVC(Mercurial
.vc_name
, MercurialHelper())
2271 class Sources(unittest
.TestCase
):
2272 # TODO: this needs serious rethink
2273 def makeChange(self
, when
=None, revision
=None):
2275 when
= mktime_tz(parsedate_tz(when
))
2276 return changes
.Change("fred", [], "", when
=when
, revision
=revision
)
2279 r
= base
.BuildRequest("forced build", SourceStamp())
2281 s
= step
.CVS(cvsroot
=None, cvsmodule
=None, workdir
=None, build
=b
)
2282 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), None)
2286 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2287 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2288 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2289 r
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2290 submitted
= "Wed, 08 Sep 2004 09:04:00 -0700"
2291 r
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2293 s
= step
.CVS(cvsroot
=None, cvsmodule
=None, workdir
=None, build
=b
)
2294 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
2295 "Wed, 08 Sep 2004 16:03:00 -0000")
2299 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2300 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2301 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2302 r
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2303 submitted
= "Wed, 08 Sep 2004 09:04:00 -0700"
2304 r
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2306 s
= step
.CVS(cvsroot
=None, cvsmodule
=None, workdir
=None, build
=b
,
2308 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
2309 "Wed, 08 Sep 2004 16:02:10 -0000")
2313 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2314 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2315 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2316 r1
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2317 submitted
= "Wed, 08 Sep 2004 09:04:00 -0700"
2318 r1
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2321 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:05:00 -0700"))
2322 r2
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2323 submitted
= "Wed, 08 Sep 2004 09:07:00 -0700"
2324 r2
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2326 b
= base
.Build([r1
, r2
])
2327 s
= step
.CVS(cvsroot
=None, cvsmodule
=None, workdir
=None, build
=b
)
2328 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
2329 "Wed, 08 Sep 2004 16:06:00 -0000")
2332 r
= base
.BuildRequest("forced", SourceStamp())
2334 s
= step
.SVN(svnurl
="dummy", workdir
=None, build
=b
)
2335 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), None)
2339 c
.append(self
.makeChange(revision
=4))
2340 c
.append(self
.makeChange(revision
=10))
2341 c
.append(self
.makeChange(revision
=67))
2342 r
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2344 s
= step
.SVN(svnurl
="dummy", workdir
=None, build
=b
)
2345 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), 67)
2347 class Patch(VCBase
, unittest
.TestCase
):
2354 def testPatch(self
):
2355 # invoke 'patch' all by itself, to see if it works the way we think
2356 # it should. This is intended to ferret out some windows test
2358 helper
= BaseHelper()
2359 self
.workdir
= os
.path
.join("test_vc", "testPatch")
2360 helper
.populate(self
.workdir
)
2361 patch
= which("patch")[0]
2363 command
= [patch
, "-p0"]
2366 def sendUpdate(self
, status
):
2368 c
= commands
.ShellCommand(FakeBuilder(), command
, self
.workdir
,
2369 sendRC
=False, stdin
=p0_diff
)
2371 d
.addCallback(self
._testPatch
_1)
2374 def _testPatch_1(self
, res
):
2375 # make sure the file actually got patched
2376 subdir_c
= os
.path
.join(self
.workdir
, "subdir", "subdir.c")
2377 data
= open(subdir_c
, "r").read()
2378 self
.failUnlessIn("Hello patched subdir.\\n", data
)