1 # -*- test-case-name: buildbot.test.test_vc -*-
3 import sys
, os
, time
, re
4 from email
.Utils
import mktime_tz
, parsedate_tz
5 from cStringIO
import StringIO
7 from twisted
.trial
import unittest
8 from twisted
.internet
import defer
, reactor
, utils
, protocol
, error
9 from twisted
.python
import failure
10 from twisted
.python
.procutils
import which
12 #defer.Deferred.debug = True
14 from twisted
.python
import log
15 #log.startLogging(sys.stderr)
17 from buildbot
import master
, interfaces
18 from buildbot
.slave
import bot
, commands
19 from buildbot
.slave
.commands
import rmdirRecursive
20 from buildbot
.status
.builder
import SUCCESS
, FAILURE
21 from buildbot
.process
import base
22 from buildbot
.steps
import source
23 from buildbot
.changes
import changes
24 from buildbot
.sourcestamp
import SourceStamp
25 from buildbot
.scripts
import tryclient
26 from buildbot
.test
.runutils
import SignalMixin
28 #step.LoggedRemoteCommand.debug = True
30 from twisted
.internet
.defer
import waitForDeferred
, deferredGenerator
32 # Most of these tests (all but SourceStamp) depend upon having a set of
33 # repositories from which we can perform checkouts. These repositories are
34 # created by the setUp method at the start of each test class. In earlier
35 # versions these repositories were created offline and distributed with a
36 # separate tarball named 'buildbot-test-vc-1.tar.gz'. This is no longer
39 # CVS requires a local file repository. Providing remote access is beyond
40 # the feasible abilities of this test program (needs pserver or ssh).
42 # SVN requires a local file repository. To provide remote access over HTTP
43 # requires an apache server with DAV support and mod_svn, way beyond what we
46 # Arch and Darcs both allow remote (read-only) operation with any web
47 # server. We test both local file access and HTTP access (by spawning a
48 # small web server to provide access to the repository files while the test
51 # Perforce starts the daemon running on localhost. Unfortunately, it must
52 # use a predetermined Internet-domain port number, unless we want to go
53 # all-out: bind the listen socket ourselves and pretend to be inetd.
55 class _PutEverythingGetter(protocol
.ProcessProtocol
):
56 def __init__(self
, deferred
, stdin
):
57 self
.deferred
= deferred
58 self
.outBuf
= StringIO()
59 self
.errBuf
= StringIO()
60 self
.outReceived
= self
.outBuf
.write
61 self
.errReceived
= self
.errBuf
.write
64 def connectionMade(self
):
65 if self
.stdin
is not None:
66 self
.transport
.write(self
.stdin
)
67 self
.transport
.closeStdin()
69 def processEnded(self
, reason
):
70 out
= self
.outBuf
.getvalue()
71 err
= self
.errBuf
.getvalue()
75 self
.deferred
.errback((out
, err
, e
.signal
))
77 self
.deferred
.callback((out
, err
, code
))
79 def myGetProcessOutputAndValue(executable
, args
=(), env
={}, path
='.',
80 reactor
=None, stdin
=None):
81 """Like twisted.internet.utils.getProcessOutputAndValue but takes
84 from twisted
.internet
import reactor
86 p
= _PutEverythingGetter(d
, stdin
)
87 reactor
.spawnProcess(p
, executable
, (executable
,)+tuple(args
), env
, path
)
91 from buildbot.process import factory
92 from buildbot.steps import source
93 from buildbot.buildslave import BuildSlave
96 f1 = factory.BuildFactory([
100 c['slaves'] = [BuildSlave('bot1', 'sekrit')]
102 c['builders'] = [{'name': 'vc', 'slavename': 'bot1',
103 'builddir': 'vc-dir', 'factory': f1}]
104 c['slavePortnum'] = 0
105 BuildmasterConfig = c
109 Index: subdir/subdir.c
110 ===================================================================
111 RCS file: /home/warner/stuff/Projects/BuildBot/code-arch/_trial_temp/test_vc/repositories/CVS-Repository/sample/subdir/subdir.c,v
112 retrieving revision 1.1.1.1
113 diff -u -r1.1.1.1 subdir.c
114 --- subdir/subdir.c 14 Aug 2005 01:32:49 -0000 1.1.1.1
115 +++ subdir/subdir.c 14 Aug 2005 01:36:15 -0000
118 main(int argc, const char *argv[])
120 - printf("Hello subdir.\n");
121 + printf("Hello patched subdir.\n");
126 # this patch does not include the filename headers, so it is
131 main(int argc, const char *argv[])
133 - printf("Hello subdir.\\n");
134 + printf("Hello try.\\n");
144 main(int argc, const char *argv[])
146 printf("Hello world.\\n");
156 main(int argc, const char *argv[])
158 printf("Hello branch.\\n");
168 main(int argc, const char *argv[])
170 printf("Hello world, version=%d\\n");
176 // this is subdir/subdir.c
180 main(int argc, const char *argv[])
182 printf("Hello subdir.\\n");
188 // this is subdir/subdir.c
192 main(int argc, const char *argv[])
194 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 # the overall plan here:
265 # Each VC system is tested separately, all using the same source tree defined
266 # in the 'files' dictionary above. Each VC system gets its own TestCase
267 # subclass. The first test case that is run will create the repository during
268 # setUp(), making two branches: 'trunk' and 'branch'. The trunk gets a copy
269 # of all the files in 'files'. The variant of good.c is committed on the
272 # then testCheckout is run, which does a number of checkout/clobber/update
273 # builds. These all use trunk r1. It then runs self.fix(), which modifies
274 # 'fixable.c', then performs another build and makes sure the tree has been
277 # testBranch uses trunk-r1 and branch-r1, making sure that we clobber the
278 # tree properly when we switch between them
280 # testPatch does a trunk-r1 checkout and applies a patch.
282 # testTryGetPatch performs a trunk-r1 checkout, modifies some files, then
283 # verifies that tryclient.getSourceStamp figures out the base revision and
287 # vc_create makes a repository at r1 with three files: main.c, version.c, and
288 # subdir/foo.c . It also creates a branch from r1 (called b1) in which main.c
289 # says "hello branch" instead of "hello world". self.trunk[] contains
290 # revision stamps for everything on the trunk, and self.branch[] does the
291 # same for the branch.
293 # vc_revise() checks out a tree at HEAD, changes version.c, then checks it
294 # back in. The new version stamp is appended to self.trunk[]. The tree is
295 # removed afterwards.
297 # vc_try_checkout(workdir, rev) checks out a tree at REV, then changes
298 # subdir/subdir.c to say 'Hello try'
299 # vc_try_finish(workdir) removes the tree and cleans up any VC state
300 # necessary (like deleting the Arch archive entry).
310 # this is also responsible for setting self.vcexe
311 raise NotImplementedError
313 def createBasedir(self
):
314 # you must call this from createRepository
315 self
.repbase
= os
.path
.abspath(os
.path
.join("test_vc",
317 if not os
.path
.isdir(self
.repbase
):
318 os
.makedirs(self
.repbase
)
320 def createRepository(self
):
321 # this will only be called once per process
322 raise NotImplementedError
324 def populate(self
, basedir
):
325 if not os
.path
.exists(basedir
):
327 os
.makedirs(os
.path
.join(basedir
, "subdir"))
328 open(os
.path
.join(basedir
, "main.c"), "w").write(MAIN_C
)
330 version_c
= VERSION_C
% self
.version
331 open(os
.path
.join(basedir
, "version.c"), "w").write(version_c
)
332 open(os
.path
.join(basedir
, "main.c"), "w").write(MAIN_C
)
333 open(os
.path
.join(basedir
, "subdir", "subdir.c"), "w").write(SUBDIR_C
)
335 def populate_branch(self
, basedir
):
336 open(os
.path
.join(basedir
, "main.c"), "w").write(BRANCH_C
)
338 def addTrunkRev(self
, rev
):
339 self
.trunk
.append(rev
)
340 self
.allrevs
.append(rev
)
341 def addBranchRev(self
, rev
):
342 self
.branch
.append(rev
)
343 self
.allrevs
.append(rev
)
345 def runCommand(self
, basedir
, command
, failureIsOk
=False, stdin
=None):
346 # all commands passed to do() should be strings or lists. If they are
347 # strings, none of the arguments may have spaces. This makes the
348 # commands less verbose at the expense of restricting what they can
350 if type(command
) not in (list, tuple):
351 command
= command
.split(" ")
354 print "do %s" % command
355 print " in basedir %s" % basedir
357 print " STDIN:\n", stdin
, "\n--STDIN DONE"
358 env
= os
.environ
.copy()
360 d
= myGetProcessOutputAndValue(command
[0], command
[1:],
361 env
=env
, path
=basedir
,
363 def check((out
, err
, code
)):
366 print "command was: %s" % command
367 if out
: print "out: %s" % out
368 if err
: print "err: %s" % err
369 print "code: %s" % code
370 if code
!= 0 and not failureIsOk
:
371 log
.msg("command %s finished with exit code %d" %
373 log
.msg(" and stdout %s" % (out
,))
374 log
.msg(" and stderr %s" % (err
,))
375 raise RuntimeError("command %s finished with exit code %d"
377 + ": see logs for stdout")
382 def do(self
, basedir
, command
, failureIsOk
=False, stdin
=None):
383 d
= self
.runCommand(basedir
, command
, failureIsOk
=failureIsOk
,
385 return waitForDeferred(d
)
387 def dovc(self
, basedir
, command
, failureIsOk
=False, stdin
=None):
388 """Like do(), but the VC binary will be prepended to COMMAND."""
389 if isinstance(command
, (str, unicode)):
390 command
= self
.vcexe
+ " " + command
393 command
= [self
.vcexe
] + command
394 return self
.do(basedir
, command
, failureIsOk
, stdin
)
396 class VCBase(SignalMixin
):
398 createdRepository
= False
405 has_got_revision
= False
406 has_got_revision_branches_are_merged
= False # for SVN
408 def failUnlessIn(self
, substring
, string
, msg
=None):
409 # trial provides a version of this that requires python-2.3 to test
412 msg
= ("did not see the expected substring '%s' in string '%s'" %
414 self
.failUnless(string
.find(substring
) != -1, msg
)
417 d
= VCS
.skipIfNotCapable(self
.vc_name
)
418 d
.addCallback(self
._setUp
1)
421 def _setUp1(self
, res
):
422 self
.helper
= VCS
.getHelper(self
.vc_name
)
424 if os
.path
.exists("basedir"):
425 rmdirRecursive("basedir")
427 self
.master
= master
.BuildMaster("basedir")
428 self
.slavebase
= os
.path
.abspath("slavebase")
429 if os
.path
.exists(self
.slavebase
):
430 rmdirRecursive(self
.slavebase
)
431 os
.mkdir("slavebase")
433 d
= VCS
.createRepository(self
.vc_name
)
436 def connectSlave(self
):
437 port
= self
.master
.slavePort
._port
.getHost().port
438 slave
= bot
.BuildSlave("localhost", port
, "bot1", "sekrit",
439 self
.slavebase
, keepalive
=0, usePTY
=1)
442 d
= self
.master
.botmaster
.waitUntilBuilderAttached("vc")
445 def loadConfig(self
, config
):
446 # reloading the config file causes a new 'listDirs' command to be
447 # sent to the slave. To synchronize on this properly, it is easiest
448 # to stop and restart the slave.
449 d
= defer
.succeed(None)
451 d
= self
.master
.botmaster
.waitUntilBuilderDetached("vc")
452 self
.slave
.stopService()
453 d
.addCallback(lambda res
: self
.master
.loadConfig(config
))
454 d
.addCallback(lambda res
: self
.connectSlave())
458 # launch an HTTP server to serve the repository files
459 from twisted
.web
import static
, server
460 from twisted
.internet
import reactor
461 self
.root
= static
.File(self
.helper
.repbase
)
462 self
.site
= server
.Site(self
.root
)
463 self
.httpServer
= reactor
.listenTCP(0, self
.site
)
464 self
.httpPort
= self
.httpServer
.getHost().port
466 def doBuild(self
, shouldSucceed
=True, ss
=None):
467 c
= interfaces
.IControl(self
.master
)
471 #print "doBuild(ss: b=%s rev=%s)" % (ss.branch, ss.revision)
472 req
= base
.BuildRequest("test_vc forced build", ss
)
473 d
= req
.waitUntilFinished()
474 c
.getBuilder("vc").requestBuild(req
)
475 d
.addCallback(self
._doBuild
_1, shouldSucceed
)
477 def _doBuild_1(self
, bs
, shouldSucceed
):
479 if r
!= SUCCESS
and shouldSucceed
:
482 if not bs
.isFinished():
483 print "Hey, build wasn't even finished!"
484 print "Build did not succeed:", r
, bs
.getText()
485 for s
in bs
.getSteps():
486 for l
in s
.getLogs():
487 print "--- START step %s / log %s ---" % (s
.getName(),
489 print l
.getTextWithHeaders()
492 self
.fail("build did not succeed")
495 def printLogs(self
, bs
):
496 for s
in bs
.getSteps():
497 for l
in s
.getLogs():
498 print "--- START step %s / log %s ---" % (s
.getName(),
500 print l
.getTextWithHeaders()
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 # SVN-1.4.0 doesn't seem to have any problem with the
672 # file-turned-directory issue (although older versions did). So don't
673 # actually check that the tree was clobbered.. as long as the update
674 # succeeded (checked by doBuild), that should be good enough.
675 #self.shouldNotExist(self.workdir, "newfile")
678 def _do_vctest_copy(self
, res
):
679 d
= self
.doBuild() # copy rebuild clobbers new files
680 d
.addCallback(self
._do
_vctest
_copy
_1)
682 def _do_vctest_copy_1(self
, bs
):
684 self
.shouldExist(self
.workdir
, self
.metadir
)
685 self
.shouldNotExist(self
.workdir
, "newfile")
686 self
.touch(self
.workdir
, "newfile")
687 self
.touch(self
.vcdir
, "newvcfile")
688 self
.failUnlessEqual(bs
.getProperty("revision"), None)
689 self
.checkGotRevisionIsLatest(bs
)
691 d
= self
.doBuild() # copy rebuild clobbers new files
692 d
.addCallback(self
._do
_vctest
_copy
_2)
694 def _do_vctest_copy_2(self
, bs
):
696 self
.shouldExist(self
.workdir
, self
.metadir
)
697 self
.shouldNotExist(self
.workdir
, "newfile")
698 self
.shouldExist(self
.vcdir
, "newvcfile")
699 self
.shouldExist(self
.workdir
, "newvcfile")
700 self
.failUnlessEqual(bs
.getProperty("revision"), None)
701 self
.checkGotRevisionIsLatest(bs
)
702 self
.touch(self
.workdir
, "newfile")
704 def _do_vctest_export(self
, res
):
705 d
= self
.doBuild() # export rebuild clobbers new files
706 d
.addCallback(self
._do
_vctest
_export
_1)
708 def _do_vctest_export_1(self
, bs
):
709 self
.shouldNotExist(self
.workdir
, self
.metadir
)
710 self
.shouldNotExist(self
.workdir
, "newfile")
711 self
.failUnlessEqual(bs
.getProperty("revision"), None)
712 #self.checkGotRevisionIsLatest(bs)
713 # VC 'export' is not required to have a got_revision
714 self
.touch(self
.workdir
, "newfile")
716 d
= self
.doBuild() # export rebuild clobbers new files
717 d
.addCallback(self
._do
_vctest
_export
_2)
719 def _do_vctest_export_2(self
, bs
):
720 self
.shouldNotExist(self
.workdir
, self
.metadir
)
721 self
.shouldNotExist(self
.workdir
, "newfile")
722 self
.failUnlessEqual(bs
.getProperty("revision"), None)
723 #self.checkGotRevisionIsLatest(bs)
724 # VC 'export' is not required to have a got_revision
728 args
= self
.helper
.vcargs
730 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
731 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
732 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
733 for k
,v
in args
.items():
734 s
+= ", %s=%s" % (k
, repr(v
))
736 self
.config
= config_vc
% s
738 m
.loadConfig(self
.config
% "clobber")
742 ss
= SourceStamp(revision
=self
.helper
.trunk
[-1], patch
=(0, p0_diff
))
744 d
= self
.connectSlave()
745 d
.addCallback(lambda res
: self
.doBuild(ss
=ss
))
746 d
.addCallback(self
._doPatch
_1)
748 def _doPatch_1(self
, bs
):
749 self
.shouldContain(self
.workdir
, "version.c",
750 "version=%d" % self
.helper
.version
)
751 # make sure the file actually got patched
752 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
753 "subdir", "subdir.c")
754 data
= open(subdir_c
, "r").read()
755 self
.failUnlessIn("Hello patched subdir.\\n", data
)
756 self
.failUnlessEqual(bs
.getProperty("revision"),
757 self
.helper
.trunk
[-1])
758 self
.checkGotRevision(bs
, self
.helper
.trunk
[-1])
760 # make sure that a rebuild does not use the leftover patched workdir
761 d
= self
.master
.loadConfig(self
.config
% "update")
762 d
.addCallback(lambda res
: self
.doBuild(ss
=None))
763 d
.addCallback(self
._doPatch
_2)
765 def _doPatch_2(self
, bs
):
766 # make sure the file is back to its original
767 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
768 "subdir", "subdir.c")
769 data
= open(subdir_c
, "r").read()
770 self
.failUnlessIn("Hello subdir.\\n", data
)
771 self
.failUnlessEqual(bs
.getProperty("revision"), None)
772 self
.checkGotRevisionIsLatest(bs
)
774 # now make sure we can patch an older revision. We need at least two
775 # revisions here, so we might have to create one first
776 if len(self
.helper
.trunk
) < 2:
777 d
= self
.helper
.vc_revise()
778 d
.addCallback(self
._doPatch
_3)
780 return self
._doPatch
_3()
782 def _doPatch_3(self
, res
=None):
783 ss
= SourceStamp(revision
=self
.helper
.trunk
[-2], patch
=(0, p0_diff
))
784 d
= self
.doBuild(ss
=ss
)
785 d
.addCallback(self
._doPatch
_4)
787 def _doPatch_4(self
, bs
):
788 self
.shouldContain(self
.workdir
, "version.c",
789 "version=%d" % (self
.helper
.version
-1))
790 # and make sure the file actually got patched
791 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
792 "subdir", "subdir.c")
793 data
= open(subdir_c
, "r").read()
794 self
.failUnlessIn("Hello patched subdir.\\n", data
)
795 self
.failUnlessEqual(bs
.getProperty("revision"),
796 self
.helper
.trunk
[-2])
797 self
.checkGotRevision(bs
, self
.helper
.trunk
[-2])
799 # now check that we can patch a branch
800 ss
= SourceStamp(branch
=self
.helper
.branchname
,
801 revision
=self
.helper
.branch
[-1],
803 d
= self
.doBuild(ss
=ss
)
804 d
.addCallback(self
._doPatch
_5)
806 def _doPatch_5(self
, bs
):
807 self
.shouldContain(self
.workdir
, "version.c",
809 self
.shouldContain(self
.workdir
, "main.c", "Hello branch.")
810 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
811 "subdir", "subdir.c")
812 data
= open(subdir_c
, "r").read()
813 self
.failUnlessIn("Hello patched subdir.\\n", data
)
814 self
.failUnlessEqual(bs
.getProperty("revision"),
815 self
.helper
.branch
[-1])
816 self
.failUnlessEqual(bs
.getProperty("branch"), self
.helper
.branchname
)
817 self
.checkGotRevision(bs
, self
.helper
.branch
[-1])
820 def do_vctest_once(self
, shouldSucceed
):
823 args
= self
.helper
.vcargs
824 vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
825 workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
826 # woo double-substitution
827 s
= "s(%s, timeout=200, workdir='build', mode='clobber'" % (vctype
,)
828 for k
,v
in args
.items():
829 s
+= ", %s=%s" % (k
, repr(v
))
831 config
= config_vc
% s
838 d
= self
.doBuild(shouldSucceed
) # initial checkout
844 args
= self
.helper
.vcargs
846 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
847 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
848 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
849 for k
,v
in args
.items():
850 s
+= ", %s=%s" % (k
, repr(v
))
852 self
.config
= config_vc
% s
854 m
.loadConfig(self
.config
% "update")
858 # first we do a build of the trunk
859 d
= self
.connectSlave()
860 d
.addCallback(lambda res
: self
.doBuild(ss
=SourceStamp()))
861 d
.addCallback(self
._doBranch
_1)
863 def _doBranch_1(self
, bs
):
864 log
.msg("_doBranch_1")
865 # make sure the checkout was of the trunk
866 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
867 data
= open(main_c
, "r").read()
868 self
.failUnlessIn("Hello world.", data
)
870 # now do a checkout on the branch. The change in branch name should
872 self
.touch(self
.workdir
, "newfile")
873 d
= self
.doBuild(ss
=SourceStamp(branch
=self
.helper
.branchname
))
874 d
.addCallback(self
._doBranch
_2)
876 def _doBranch_2(self
, bs
):
877 log
.msg("_doBranch_2")
878 # make sure it was on the branch
879 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
880 data
= open(main_c
, "r").read()
881 self
.failUnlessIn("Hello branch.", data
)
882 # and make sure the tree was clobbered
883 self
.shouldNotExist(self
.workdir
, "newfile")
885 # doing another build on the same branch should not clobber the tree
886 self
.touch(self
.workdir
, "newbranchfile")
887 d
= self
.doBuild(ss
=SourceStamp(branch
=self
.helper
.branchname
))
888 d
.addCallback(self
._doBranch
_3)
890 def _doBranch_3(self
, bs
):
891 log
.msg("_doBranch_3")
892 # make sure it is still on the branch
893 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
894 data
= open(main_c
, "r").read()
895 self
.failUnlessIn("Hello branch.", data
)
896 # and make sure the tree was not clobbered
897 self
.shouldExist(self
.workdir
, "newbranchfile")
899 # now make sure that a non-branch checkout clobbers the tree
900 d
= self
.doBuild(ss
=SourceStamp())
901 d
.addCallback(self
._doBranch
_4)
903 def _doBranch_4(self
, bs
):
904 log
.msg("_doBranch_4")
905 # make sure it was on the trunk
906 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
907 data
= open(main_c
, "r").read()
908 self
.failUnlessIn("Hello world.", data
)
909 self
.shouldNotExist(self
.workdir
, "newbranchfile")
911 def do_getpatch(self
, doBranch
=True):
912 log
.msg("do_getpatch")
913 # prepare a buildslave to do checkouts
915 args
= self
.helper
.vcargs
917 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
918 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
919 # woo double-substitution
920 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
921 for k
,v
in args
.items():
922 s
+= ", %s=%s" % (k
, repr(v
))
924 config
= config_vc
% s
926 m
.loadConfig(config
% 'clobber')
930 d
= self
.connectSlave()
932 # then set up the "developer's tree". first we modify a tree from the
934 tmpdir
= "try_workdir"
935 self
.trydir
= os
.path
.join(self
.helper
.repbase
, tmpdir
)
936 rmdirRecursive(self
.trydir
)
937 d
.addCallback(self
.do_getpatch_trunkhead
)
938 d
.addCallback(self
.do_getpatch_trunkold
)
940 d
.addCallback(self
.do_getpatch_branch
)
941 d
.addCallback(self
.do_getpatch_finish
)
944 def do_getpatch_finish(self
, res
):
945 log
.msg("do_getpatch_finish")
946 self
.helper
.vc_try_finish(self
.trydir
)
949 def try_shouldMatch(self
, filename
):
950 devfilename
= os
.path
.join(self
.trydir
, filename
)
951 devfile
= open(devfilename
, "r").read()
952 slavefilename
= os
.path
.join(self
.workdir
, filename
)
953 slavefile
= open(slavefilename
, "r").read()
954 self
.failUnlessEqual(devfile
, slavefile
,
955 ("slavefile (%s) contains '%s'. "
956 "developer's file (%s) contains '%s'. "
957 "These ought to match") %
958 (slavefilename
, slavefile
,
959 devfilename
, devfile
))
961 def do_getpatch_trunkhead(self
, res
):
962 log
.msg("do_getpatch_trunkhead")
963 d
= self
.helper
.vc_try_checkout(self
.trydir
, self
.helper
.trunk
[-1])
964 d
.addCallback(self
._do
_getpatch
_trunkhead
_1)
966 def _do_getpatch_trunkhead_1(self
, res
):
967 log
.msg("_do_getpatch_trunkhead_1")
968 d
= tryclient
.getSourceStamp(self
.vctype_try
, self
.trydir
, None)
969 d
.addCallback(self
._do
_getpatch
_trunkhead
_2)
971 def _do_getpatch_trunkhead_2(self
, ss
):
972 log
.msg("_do_getpatch_trunkhead_2")
973 d
= self
.doBuild(ss
=ss
)
974 d
.addCallback(self
._do
_getpatch
_trunkhead
_3)
976 def _do_getpatch_trunkhead_3(self
, res
):
977 log
.msg("_do_getpatch_trunkhead_3")
978 # verify that the resulting buildslave tree matches the developer's
979 self
.try_shouldMatch("main.c")
980 self
.try_shouldMatch("version.c")
981 self
.try_shouldMatch(os
.path
.join("subdir", "subdir.c"))
983 def do_getpatch_trunkold(self
, res
):
984 log
.msg("do_getpatch_trunkold")
985 # now try a tree from an older revision. We need at least two
986 # revisions here, so we might have to create one first
987 if len(self
.helper
.trunk
) < 2:
988 d
= self
.helper
.vc_revise()
989 d
.addCallback(self
._do
_getpatch
_trunkold
_1)
991 return self
._do
_getpatch
_trunkold
_1()
992 def _do_getpatch_trunkold_1(self
, res
=None):
993 log
.msg("_do_getpatch_trunkold_1")
994 d
= self
.helper
.vc_try_checkout(self
.trydir
, self
.helper
.trunk
[-2])
995 d
.addCallback(self
._do
_getpatch
_trunkold
_2)
997 def _do_getpatch_trunkold_2(self
, res
):
998 log
.msg("_do_getpatch_trunkold_2")
999 d
= tryclient
.getSourceStamp(self
.vctype_try
, self
.trydir
, None)
1000 d
.addCallback(self
._do
_getpatch
_trunkold
_3)
1002 def _do_getpatch_trunkold_3(self
, ss
):
1003 log
.msg("_do_getpatch_trunkold_3")
1004 d
= self
.doBuild(ss
=ss
)
1005 d
.addCallback(self
._do
_getpatch
_trunkold
_4)
1007 def _do_getpatch_trunkold_4(self
, res
):
1008 log
.msg("_do_getpatch_trunkold_4")
1009 # verify that the resulting buildslave tree matches the developer's
1010 self
.try_shouldMatch("main.c")
1011 self
.try_shouldMatch("version.c")
1012 self
.try_shouldMatch(os
.path
.join("subdir", "subdir.c"))
1014 def do_getpatch_branch(self
, res
):
1015 log
.msg("do_getpatch_branch")
1016 # now try a tree from a branch
1017 d
= self
.helper
.vc_try_checkout(self
.trydir
, self
.helper
.branch
[-1],
1018 self
.helper
.branchname
)
1019 d
.addCallback(self
._do
_getpatch
_branch
_1)
1021 def _do_getpatch_branch_1(self
, res
):
1022 log
.msg("_do_getpatch_branch_1")
1023 d
= tryclient
.getSourceStamp(self
.vctype_try
, self
.trydir
,
1024 self
.helper
.try_branchname
)
1025 d
.addCallback(self
._do
_getpatch
_branch
_2)
1027 def _do_getpatch_branch_2(self
, ss
):
1028 log
.msg("_do_getpatch_branch_2")
1029 d
= self
.doBuild(ss
=ss
)
1030 d
.addCallback(self
._do
_getpatch
_branch
_3)
1032 def _do_getpatch_branch_3(self
, res
):
1033 log
.msg("_do_getpatch_branch_3")
1034 # verify that the resulting buildslave tree matches the developer's
1035 self
.try_shouldMatch("main.c")
1036 self
.try_shouldMatch("version.c")
1037 self
.try_shouldMatch(os
.path
.join("subdir", "subdir.c"))
1040 def dumpPatch(self
, patch
):
1041 # this exists to help me figure out the right 'patchlevel' value
1042 # should be returned by tryclient.getSourceStamp
1044 open(n
,"w").write(patch
)
1045 d
= self
.runCommand(".", ["lsdiff", n
])
1046 def p(res
): print "lsdiff:", res
.strip().split("\n")
1052 d
= defer
.succeed(None)
1054 d2
= self
.master
.botmaster
.waitUntilBuilderDetached("vc")
1055 d
.addCallback(lambda res
: self
.slave
.stopService())
1056 d
.addCallback(lambda res
: d2
)
1058 d
.addCallback(lambda res
: self
.master
.stopService())
1060 d
.addCallback(lambda res
: self
.httpServer
.stopListening())
1061 def stopHTTPTimer():
1062 from twisted
.web
import http
1063 http
._logDateTimeStop
() # shut down the internal timer. DUMB!
1064 d
.addCallback(lambda res
: stopHTTPTimer())
1065 d
.addCallback(lambda res
: self
.tearDown2())
1068 def tearDown2(self
):
1071 class CVSHelper(BaseHelper
):
1072 branchname
= "branch"
1073 try_branchname
= "branch"
1076 cvspaths
= which('cvs')
1078 return (False, "CVS is not installed")
1079 # cvs-1.10 (as shipped with OS-X 10.3 "Panther") is too old for this
1080 # test. There is a situation where we check out a tree, make a
1081 # change, then commit it back, and CVS refuses to believe that we're
1082 # operating in a CVS tree. I tested cvs-1.12.9 and it works ok, OS-X
1083 # 10.4 "Tiger" comes with cvs-1.11, but I haven't tested that yet.
1084 # For now, skip the tests if we've got 1.10 .
1085 log
.msg("running %s --version.." % (cvspaths
[0],))
1086 d
= utils
.getProcessOutput(cvspaths
[0], ["--version"],
1088 d
.addCallback(self
._capable
, cvspaths
[0])
1091 def _capable(self
, v
, vcexe
):
1092 m
= re
.search(r
'\(CVS\) ([\d\.]+) ', v
)
1094 log
.msg("couldn't identify CVS version number in output:")
1095 log
.msg("'''%s'''" % v
)
1096 log
.msg("skipping tests")
1097 return (False, "Found CVS but couldn't identify its version")
1099 log
.msg("found CVS version '%s'" % ver
)
1101 return (False, "Found CVS, but it is too old")
1106 # this timestamp is eventually passed to CVS in a -D argument, and
1107 # strftime's %z specifier doesn't seem to work reliably (I get +0000
1108 # where I should get +0700 under linux sometimes, and windows seems
1109 # to want to put a verbose 'Eastern Standard Time' in there), so
1110 # leave off the timezone specifier and treat this as localtime. A
1111 # valid alternative would be to use a hard-coded +0000 and
1113 return time
.strftime("%Y-%m-%d %H:%M:%S", time
.localtime())
1115 def createRepository(self
):
1116 self
.createBasedir()
1117 self
.cvsrep
= cvsrep
= os
.path
.join(self
.repbase
, "CVS-Repository")
1118 tmp
= os
.path
.join(self
.repbase
, "cvstmp")
1120 w
= self
.dovc(self
.repbase
, "-d %s init" % cvsrep
)
1121 yield w
; w
.getResult() # we must getResult() to raise any exceptions
1124 cmd
= ("-d %s import" % cvsrep
+
1125 " -m sample_project_files sample vendortag start")
1126 w
= self
.dovc(tmp
, cmd
)
1127 yield w
; w
.getResult()
1129 # take a timestamp as the first revision number
1131 self
.addTrunkRev(self
.getdate())
1134 w
= self
.dovc(self
.repbase
,
1135 "-d %s checkout -d cvstmp sample" % self
.cvsrep
)
1136 yield w
; w
.getResult()
1138 w
= self
.dovc(tmp
, "tag -b %s" % self
.branchname
)
1139 yield w
; w
.getResult()
1140 self
.populate_branch(tmp
)
1142 "commit -m commit_on_branch -r %s" % self
.branchname
)
1143 yield w
; w
.getResult()
1146 self
.addBranchRev(self
.getdate())
1148 self
.vcargs
= { 'cvsroot': self
.cvsrep
, 'cvsmodule': "sample" }
1149 createRepository
= deferredGenerator(createRepository
)
1152 def vc_revise(self
):
1153 tmp
= os
.path
.join(self
.repbase
, "cvstmp")
1155 w
= self
.dovc(self
.repbase
,
1156 "-d %s checkout -d cvstmp sample" % self
.cvsrep
)
1157 yield w
; w
.getResult()
1159 version_c
= VERSION_C
% self
.version
1160 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1162 "commit -m revised_to_%d version.c" % self
.version
)
1163 yield w
; w
.getResult()
1166 self
.addTrunkRev(self
.getdate())
1168 vc_revise
= deferredGenerator(vc_revise
)
1170 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1171 # 'workdir' is an absolute path
1172 assert os
.path
.abspath(workdir
) == workdir
1173 cmd
= [self
.vcexe
, "-d", self
.cvsrep
, "checkout",
1176 if branch
is not None:
1179 cmd
.append("sample")
1180 w
= self
.do(self
.repbase
, cmd
)
1181 yield w
; w
.getResult()
1182 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1183 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1185 def vc_try_finish(self
, workdir
):
1186 rmdirRecursive(workdir
)
1188 class CVS(VCBase
, unittest
.TestCase
):
1192 vctype
= "source.CVS"
1194 # CVS gives us got_revision, but it is based entirely upon the local
1195 # clock, which means it is unlikely to match the timestamp taken earlier.
1196 # This might be enough for common use, but won't be good enough for our
1197 # tests to accept, so pretend it doesn't have got_revision at all.
1198 has_got_revision
= False
1200 def testCheckout(self
):
1201 d
= self
.do_vctest()
1204 def testPatch(self
):
1208 def testCheckoutBranch(self
):
1209 d
= self
.do_branch()
1213 d
= self
.do_getpatch(doBranch
=False)
1216 VCS
.registerVC(CVS
.vc_name
, CVSHelper())
1219 class SVNHelper(BaseHelper
):
1220 branchname
= "sample/branch"
1221 try_branchname
= "sample/branch"
1224 svnpaths
= which('svn')
1225 svnadminpaths
= which('svnadmin')
1227 return (False, "SVN is not installed")
1228 if not svnadminpaths
:
1229 return (False, "svnadmin is not installed")
1230 # we need svn to be compiled with the ra_local access
1232 log
.msg("running svn --version..")
1233 env
= os
.environ
.copy()
1235 d
= utils
.getProcessOutput(svnpaths
[0], ["--version"],
1237 d
.addCallback(self
._capable
, svnpaths
[0], svnadminpaths
[0])
1240 def _capable(self
, v
, vcexe
, svnadmin
):
1241 if v
.find("handles 'file' schem") != -1:
1242 # older versions say 'schema', 1.2.0 and beyond say 'scheme'
1244 self
.svnadmin
= svnadmin
1246 excuse
= ("%s found but it does not support 'file:' " +
1247 "schema, skipping svn tests") % vcexe
1249 return (False, excuse
)
1251 def createRepository(self
):
1252 self
.createBasedir()
1253 self
.svnrep
= os
.path
.join(self
.repbase
,
1254 "SVN-Repository").replace('\\','/')
1255 tmp
= os
.path
.join(self
.repbase
, "svntmp")
1256 if sys
.platform
== 'win32':
1257 # On Windows Paths do not start with a /
1258 self
.svnurl
= "file:///%s" % self
.svnrep
1260 self
.svnurl
= "file://%s" % self
.svnrep
1261 self
.svnurl_trunk
= self
.svnurl
+ "/sample/trunk"
1262 self
.svnurl_branch
= self
.svnurl
+ "/sample/branch"
1264 w
= self
.do(self
.repbase
, self
.svnadmin
+" create %s" % self
.svnrep
)
1265 yield w
; w
.getResult()
1269 "import -m sample_project_files %s" %
1271 yield w
; out
= w
.getResult()
1273 m
= re
.search(r
'Committed revision (\d+)\.', out
)
1274 assert m
.group(1) == "1" # first revision is always "1"
1275 self
.addTrunkRev(int(m
.group(1)))
1277 w
= self
.dovc(self
.repbase
,
1278 "checkout %s svntmp" % self
.svnurl_trunk
)
1279 yield w
; w
.getResult()
1281 w
= self
.dovc(tmp
, "cp -m make_branch %s %s" % (self
.svnurl_trunk
,
1282 self
.svnurl_branch
))
1283 yield w
; w
.getResult()
1284 w
= self
.dovc(tmp
, "switch %s" % self
.svnurl_branch
)
1285 yield w
; w
.getResult()
1286 self
.populate_branch(tmp
)
1287 w
= self
.dovc(tmp
, "commit -m commit_on_branch")
1288 yield w
; out
= w
.getResult()
1290 m
= re
.search(r
'Committed revision (\d+)\.', out
)
1291 self
.addBranchRev(int(m
.group(1)))
1292 createRepository
= deferredGenerator(createRepository
)
1294 def vc_revise(self
):
1295 tmp
= os
.path
.join(self
.repbase
, "svntmp")
1297 log
.msg("vc_revise" + self
.svnurl_trunk
)
1298 w
= self
.dovc(self
.repbase
,
1299 "checkout %s svntmp" % self
.svnurl_trunk
)
1300 yield w
; w
.getResult()
1302 version_c
= VERSION_C
% self
.version
1303 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1304 w
= self
.dovc(tmp
, "commit -m revised_to_%d" % self
.version
)
1305 yield w
; out
= w
.getResult()
1306 m
= re
.search(r
'Committed revision (\d+)\.', out
)
1307 self
.addTrunkRev(int(m
.group(1)))
1309 vc_revise
= deferredGenerator(vc_revise
)
1311 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1312 assert os
.path
.abspath(workdir
) == workdir
1313 if os
.path
.exists(workdir
):
1314 rmdirRecursive(workdir
)
1316 svnurl
= self
.svnurl_trunk
1318 # N.B.: this is *not* os.path.join: SVN URLs use slashes
1319 # regardless of the host operating system's filepath separator
1320 svnurl
= self
.svnurl
+ "/" + branch
1321 w
= self
.dovc(self
.repbase
,
1322 "checkout %s %s" % (svnurl
, workdir
))
1323 yield w
; w
.getResult()
1324 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1325 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1327 def vc_try_finish(self
, workdir
):
1328 rmdirRecursive(workdir
)
1331 class SVN(VCBase
, unittest
.TestCase
):
1335 vctype
= "source.SVN"
1337 has_got_revision
= True
1338 has_got_revision_branches_are_merged
= True
1340 def testCheckout(self
):
1341 # we verify this one with the svnurl style of vcargs. We test the
1342 # baseURL/defaultBranch style in testPatch and testCheckoutBranch.
1343 self
.helper
.vcargs
= { 'svnurl': self
.helper
.svnurl_trunk
}
1344 d
= self
.do_vctest()
1347 def testPatch(self
):
1348 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1349 'defaultBranch': "sample/trunk",
1354 def testCheckoutBranch(self
):
1355 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1356 'defaultBranch': "sample/trunk",
1358 d
= self
.do_branch()
1362 # extract the base revision and patch from a modified tree, use it to
1363 # create the same contents on the buildslave
1364 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1365 'defaultBranch': "sample/trunk",
1367 d
= self
.do_getpatch()
1370 VCS
.registerVC(SVN
.vc_name
, SVNHelper())
1373 class P4Helper(BaseHelper
):
1374 branchname
= "branch"
1375 p4port
= 'localhost:1666'
1377 base_descr
= 'Change: new\nDescription: asdf\nFiles:\n'
1380 p4paths
= which('p4')
1381 p4dpaths
= which('p4d')
1383 return (False, "p4 is not installed")
1385 return (False, "p4d is not installed")
1386 self
.vcexe
= p4paths
[0]
1387 self
.p4dexe
= p4dpaths
[0]
1390 class _P4DProtocol(protocol
.ProcessProtocol
):
1392 self
.started
= defer
.Deferred()
1393 self
.ended
= defer
.Deferred()
1395 def outReceived(self
, data
):
1396 # When it says starting, it has bound to the socket.
1398 if data
.startswith('Perforce Server starting...'):
1399 self
.started
.callback(None)
1401 print "p4d said %r" % data
1403 raise Exception('p4d said %r' % data
)
1405 self
.started
.errback(failure
.Failure())
1408 def errReceived(self
, data
):
1409 print "p4d stderr: %s" % data
1411 def processEnded(self
, status_object
):
1412 if status_object
.check(error
.ProcessDone
):
1413 self
.ended
.callback(None)
1415 self
.ended
.errback(status_object
)
1417 def _start_p4d(self
):
1418 proto
= self
._P
4DProtocol
()
1419 reactor
.spawnProcess(proto
, self
.p4dexe
, ['p4d', '-p', self
.p4port
],
1420 env
=os
.environ
, path
=self
.p4rep
)
1421 return proto
.started
, proto
.ended
1423 def dop4(self
, basedir
, command
, failureIsOk
=False, stdin
=None):
1424 # p4 looks at $PWD instead of getcwd(), which causes confusion when
1425 # we spawn commands without an intervening shell (sh -c). We can
1426 # override this with a -d argument.
1427 command
= "-p %s -d %s %s" % (self
.p4port
, basedir
, command
)
1428 return self
.dovc(basedir
, command
, failureIsOk
, stdin
)
1430 def createRepository(self
):
1431 # this is only called once per VC system, so start p4d here.
1433 self
.createBasedir()
1434 tmp
= os
.path
.join(self
.repbase
, "p4tmp")
1435 self
.p4rep
= os
.path
.join(self
.repbase
, 'P4-Repository')
1436 os
.mkdir(self
.p4rep
)
1439 started
, self
.p4d_shutdown
= self
._start
_p
4d
()
1440 w
= waitForDeferred(started
)
1441 yield w
; w
.getResult()
1443 # Create client spec.
1445 clispec
= 'Client: creator\n'
1446 clispec
+= 'Root: %s\n' % tmp
1447 clispec
+= 'View:\n'
1448 clispec
+= '\t//depot/... //creator/...\n'
1449 w
= self
.dop4(tmp
, 'client -i', stdin
=clispec
)
1450 yield w
; w
.getResult()
1452 # Create first rev (trunk).
1453 self
.populate(os
.path
.join(tmp
, 'trunk'))
1454 files
= ['main.c', 'version.c', 'subdir/subdir.c']
1455 w
= self
.dop4(tmp
, "-c creator add "
1456 + " ".join(['trunk/%s' % f
for f
in files
]))
1457 yield w
; w
.getResult()
1458 descr
= self
.base_descr
1460 descr
+= '\t//depot/trunk/%s\n' % file
1461 w
= self
.dop4(tmp
, "-c creator submit -i", stdin
=descr
)
1462 yield w
; out
= w
.getResult()
1463 m
= re
.search(r
'Change (\d+) submitted.', out
)
1464 assert m
.group(1) == '1'
1465 self
.addTrunkRev(m
.group(1))
1467 # Create second rev (branch).
1468 w
= self
.dop4(tmp
, '-c creator integrate '
1469 + '//depot/trunk/... //depot/branch/...')
1470 yield w
; w
.getResult()
1471 w
= self
.dop4(tmp
, "-c creator edit branch/main.c")
1472 yield w
; w
.getResult()
1473 self
.populate_branch(os
.path
.join(tmp
, 'branch'))
1474 descr
= self
.base_descr
1476 descr
+= '\t//depot/branch/%s\n' % file
1477 w
= self
.dop4(tmp
, "-c creator submit -i", stdin
=descr
)
1478 yield w
; out
= w
.getResult()
1479 m
= re
.search(r
'Change (\d+) submitted.', out
)
1480 self
.addBranchRev(m
.group(1))
1481 createRepository
= deferredGenerator(createRepository
)
1483 def vc_revise(self
):
1484 tmp
= os
.path
.join(self
.repbase
, "p4tmp")
1486 version_c
= VERSION_C
% self
.version
1487 w
= self
.dop4(tmp
, '-c creator edit trunk/version.c')
1488 yield w
; w
.getResult()
1489 open(os
.path
.join(tmp
, "trunk/version.c"), "w").write(version_c
)
1490 descr
= self
.base_descr
+ '\t//depot/trunk/version.c\n'
1491 w
= self
.dop4(tmp
, "-c creator submit -i", stdin
=descr
)
1492 yield w
; out
= w
.getResult()
1493 m
= re
.search(r
'Change (\d+) submitted.', out
)
1494 self
.addTrunkRev(m
.group(1))
1495 vc_revise
= deferredGenerator(vc_revise
)
1497 def shutdown_p4d(self
):
1498 d
= self
.runCommand(self
.repbase
, '%s -p %s admin stop'
1499 % (self
.vcexe
, self
.p4port
))
1500 return d
.addCallback(lambda _
: self
.p4d_shutdown
)
1502 class P4(VCBase
, unittest
.TestCase
):
1504 vctype
= "source.P4"
1507 def tearDownClass(self
):
1509 return self
.helper
.shutdown_p4d()
1511 def testCheckout(self
):
1512 self
.helper
.vcargs
= { 'p4port': self
.helper
.p4port
,
1513 'p4base': '//depot/',
1514 'defaultBranch': 'trunk' }
1515 d
= self
.do_vctest(testRetry
=False)
1516 # TODO: like arch and darcs, sync does nothing when server is not
1520 def testCheckoutBranch(self
):
1521 self
.helper
.vcargs
= { 'p4port': self
.helper
.p4port
,
1522 'p4base': '//depot/',
1523 'defaultBranch': 'trunk' }
1524 d
= self
.do_branch()
1527 def testPatch(self
):
1528 self
.helper
.vcargs
= { 'p4port': self
.helper
.p4port
,
1529 'p4base': '//depot/',
1530 'defaultBranch': 'trunk' }
1534 VCS
.registerVC(P4
.vc_name
, P4Helper())
1537 class DarcsHelper(BaseHelper
):
1538 branchname
= "branch"
1539 try_branchname
= "branch"
1542 darcspaths
= which('darcs')
1544 return (False, "Darcs is not installed")
1545 self
.vcexe
= darcspaths
[0]
1548 def createRepository(self
):
1549 self
.createBasedir()
1550 self
.darcs_base
= os
.path
.join(self
.repbase
, "Darcs-Repository")
1551 self
.rep_trunk
= os
.path
.join(self
.darcs_base
, "trunk")
1552 self
.rep_branch
= os
.path
.join(self
.darcs_base
, "branch")
1553 tmp
= os
.path
.join(self
.repbase
, "darcstmp")
1555 os
.makedirs(self
.rep_trunk
)
1556 w
= self
.dovc(self
.rep_trunk
, ["initialize"])
1557 yield w
; w
.getResult()
1558 os
.makedirs(self
.rep_branch
)
1559 w
= self
.dovc(self
.rep_branch
, ["initialize"])
1560 yield w
; w
.getResult()
1563 w
= self
.dovc(tmp
, qw("initialize"))
1564 yield w
; w
.getResult()
1565 w
= self
.dovc(tmp
, qw("add -r ."))
1566 yield w
; w
.getResult()
1567 w
= self
.dovc(tmp
, qw("record -a -m initial_import --skip-long-comment -A test@buildbot.sf.net"))
1568 yield w
; w
.getResult()
1569 w
= self
.dovc(tmp
, ["push", "-a", self
.rep_trunk
])
1570 yield w
; w
.getResult()
1571 w
= self
.dovc(tmp
, qw("changes --context"))
1572 yield w
; out
= w
.getResult()
1573 self
.addTrunkRev(out
)
1575 self
.populate_branch(tmp
)
1576 w
= self
.dovc(tmp
, qw("record -a --ignore-times -m commit_on_branch --skip-long-comment -A test@buildbot.sf.net"))
1577 yield w
; w
.getResult()
1578 w
= self
.dovc(tmp
, ["push", "-a", self
.rep_branch
])
1579 yield w
; w
.getResult()
1580 w
= self
.dovc(tmp
, qw("changes --context"))
1581 yield w
; out
= w
.getResult()
1582 self
.addBranchRev(out
)
1584 createRepository
= deferredGenerator(createRepository
)
1586 def vc_revise(self
):
1587 tmp
= os
.path
.join(self
.repbase
, "darcstmp")
1589 w
= self
.dovc(tmp
, qw("initialize"))
1590 yield w
; w
.getResult()
1591 w
= self
.dovc(tmp
, ["pull", "-a", self
.rep_trunk
])
1592 yield w
; w
.getResult()
1595 version_c
= VERSION_C
% self
.version
1596 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1597 w
= self
.dovc(tmp
, qw("record -a --ignore-times -m revised_to_%d --skip-long-comment -A test@buildbot.sf.net" % self
.version
))
1598 yield w
; w
.getResult()
1599 w
= self
.dovc(tmp
, ["push", "-a", self
.rep_trunk
])
1600 yield w
; w
.getResult()
1601 w
= self
.dovc(tmp
, qw("changes --context"))
1602 yield w
; out
= w
.getResult()
1603 self
.addTrunkRev(out
)
1605 vc_revise
= deferredGenerator(vc_revise
)
1607 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1608 assert os
.path
.abspath(workdir
) == workdir
1609 if os
.path
.exists(workdir
):
1610 rmdirRecursive(workdir
)
1611 os
.makedirs(workdir
)
1612 w
= self
.dovc(workdir
, qw("initialize"))
1613 yield w
; w
.getResult()
1615 rep
= self
.rep_trunk
1617 rep
= os
.path
.join(self
.darcs_base
, branch
)
1618 w
= self
.dovc(workdir
, ["pull", "-a", rep
])
1619 yield w
; w
.getResult()
1620 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1621 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1623 def vc_try_finish(self
, workdir
):
1624 rmdirRecursive(workdir
)
1627 class Darcs(VCBase
, unittest
.TestCase
):
1630 # Darcs has a metadir="_darcs", but it does not have an 'export'
1633 vctype
= "source.Darcs"
1634 vctype_try
= "darcs"
1635 has_got_revision
= True
1637 def testCheckout(self
):
1638 self
.helper
.vcargs
= { 'repourl': self
.helper
.rep_trunk
}
1639 d
= self
.do_vctest(testRetry
=False)
1641 # TODO: testRetry has the same problem with Darcs as it does for
1645 def testPatch(self
):
1646 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1647 'defaultBranch': "trunk" }
1651 def testCheckoutBranch(self
):
1652 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1653 'defaultBranch': "trunk" }
1654 d
= self
.do_branch()
1657 def testCheckoutHTTP(self
):
1659 repourl
= "http://localhost:%d/Darcs-Repository/trunk" % self
.httpPort
1660 self
.helper
.vcargs
= { 'repourl': repourl
}
1661 d
= self
.do_vctest(testRetry
=False)
1665 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1666 'defaultBranch': "trunk" }
1667 d
= self
.do_getpatch()
1670 VCS
.registerVC(Darcs
.vc_name
, DarcsHelper())
1674 def registerRepository(self
, coordinates
):
1676 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1677 yield w
; out
= w
.getResult()
1679 w
= self
.dovc(self
.repbase
, "register-archive -d %s" % a
)
1680 yield w
; w
.getResult()
1681 w
= self
.dovc(self
.repbase
, "register-archive %s" % coordinates
)
1682 yield w
; w
.getResult()
1683 registerRepository
= deferredGenerator(registerRepository
)
1685 def unregisterRepository(self
):
1687 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1688 yield w
; out
= w
.getResult()
1690 w
= self
.dovc(self
.repbase
, "register-archive -d %s" % a
)
1691 yield w
; out
= w
.getResult()
1692 unregisterRepository
= deferredGenerator(unregisterRepository
)
1694 class TlaHelper(BaseHelper
, ArchCommon
):
1695 defaultbranch
= "testvc--mainline--1"
1696 branchname
= "testvc--branch--1"
1697 try_branchname
= None # TlaExtractor can figure it out by itself
1701 tlapaths
= which('tla')
1703 return (False, "Arch (tla) is not installed")
1704 self
.vcexe
= tlapaths
[0]
1707 def do_get(self
, basedir
, archive
, branch
, newdir
):
1708 # the 'get' syntax is different between tla and baz. baz, while
1709 # claiming to honor an --archive argument, in fact ignores it. The
1710 # correct invocation is 'baz get archive/revision newdir'.
1711 if self
.archcmd
== "tla":
1712 w
= self
.dovc(basedir
,
1713 "get -A %s %s %s" % (archive
, branch
, newdir
))
1715 w
= self
.dovc(basedir
,
1716 "get %s/%s %s" % (archive
, branch
, newdir
))
1719 def createRepository(self
):
1720 self
.createBasedir()
1721 # first check to see if bazaar is around, since we'll need to know
1723 d
= VCS
.capable(Bazaar
.vc_name
)
1724 d
.addCallback(self
._createRepository
_1)
1727 def _createRepository_1(self
, res
):
1730 # pick a hopefully unique string for the archive name, in the form
1731 # test-%d@buildbot.sf.net--testvc, since otherwise multiple copies of
1732 # the unit tests run in the same user account will collide (since the
1733 # archive names are kept in the per-user ~/.arch-params/ directory).
1735 self
.archname
= "test-%s-%d@buildbot.sf.net--testvc" % (self
.archcmd
,
1737 trunk
= self
.defaultbranch
1738 branch
= self
.branchname
1740 repword
= self
.archcmd
.capitalize()
1741 self
.archrep
= os
.path
.join(self
.repbase
, "%s-Repository" % repword
)
1742 tmp
= os
.path
.join(self
.repbase
, "archtmp")
1747 w
= self
.dovc(tmp
, "my-id", failureIsOk
=True)
1748 yield w
; res
= w
.getResult()
1750 # tla will fail a lot of operations if you have not set an ID
1751 w
= self
.do(tmp
, [self
.vcexe
, "my-id",
1752 "Buildbot Test Suite <test@buildbot.sf.net>"])
1753 yield w
; w
.getResult()
1756 # bazaar keeps a cache of revisions, but this test creates a new
1757 # archive each time it is run, so the cache causes errors.
1758 # Disable the cache to avoid these problems. This will be
1759 # slightly annoying for people who run the buildbot tests under
1760 # the same UID as one which uses baz on a regular basis, but
1761 # bazaar doesn't give us a way to disable the cache just for this
1763 cmd
= "%s cache-config --disable" % VCS
.getHelper('bazaar').vcexe
1764 w
= self
.do(tmp
, cmd
)
1765 yield w
; w
.getResult()
1767 w
= waitForDeferred(self
.unregisterRepository())
1768 yield w
; w
.getResult()
1770 # these commands can be run in any directory
1771 w
= self
.dovc(tmp
, "make-archive -l %s %s" % (a
, self
.archrep
))
1772 yield w
; w
.getResult()
1773 if self
.archcmd
== "tla":
1774 w
= self
.dovc(tmp
, "archive-setup -A %s %s" % (a
, trunk
))
1775 yield w
; w
.getResult()
1776 w
= self
.dovc(tmp
, "archive-setup -A %s %s" % (a
, branch
))
1777 yield w
; w
.getResult()
1779 # baz does not require an 'archive-setup' step
1782 # these commands must be run in the directory that is to be imported
1783 w
= self
.dovc(tmp
, "init-tree --nested %s/%s" % (a
, trunk
))
1784 yield w
; w
.getResult()
1785 files
= " ".join(["main.c", "version.c", "subdir",
1786 os
.path
.join("subdir", "subdir.c")])
1787 w
= self
.dovc(tmp
, "add-id %s" % files
)
1788 yield w
; w
.getResult()
1790 w
= self
.dovc(tmp
, "import %s/%s" % (a
, trunk
))
1791 yield w
; out
= w
.getResult()
1792 self
.addTrunkRev("base-0")
1795 if self
.archcmd
== "tla":
1796 branchstart
= "%s--base-0" % trunk
1797 w
= self
.dovc(tmp
, "tag -A %s %s %s" % (a
, branchstart
, branch
))
1798 yield w
; w
.getResult()
1800 w
= self
.dovc(tmp
, "branch %s" % branch
)
1801 yield w
; w
.getResult()
1805 # check out the branch
1806 w
= self
.do_get(self
.repbase
, a
, branch
, "archtmp")
1807 yield w
; w
.getResult()
1809 self
.populate_branch(tmp
)
1810 logfile
= "++log.%s--%s" % (branch
, a
)
1811 logmsg
= "Summary: commit on branch\nKeywords:\n\n"
1812 open(os
.path
.join(tmp
, logfile
), "w").write(logmsg
)
1813 w
= self
.dovc(tmp
, "commit")
1814 yield w
; out
= w
.getResult()
1815 m
= re
.search(r
'committed %s/%s--([\S]+)' % (a
, branch
),
1817 assert (m
.group(1) == "base-0" or m
.group(1).startswith("patch-"))
1818 self
.addBranchRev(m
.group(1))
1820 w
= waitForDeferred(self
.unregisterRepository())
1821 yield w
; w
.getResult()
1824 # we unregister the repository each time, because we might have
1825 # changed the coordinates (since we switch from a file: URL to an
1826 # http: URL for various tests). The buildslave code doesn't forcibly
1827 # unregister the archive, so we have to do it here.
1828 w
= waitForDeferred(self
.unregisterRepository())
1829 yield w
; w
.getResult()
1831 _createRepository_1
= deferredGenerator(_createRepository_1
)
1833 def vc_revise(self
):
1834 # the fix needs to be done in a workspace that is linked to a
1835 # read-write version of the archive (i.e., using file-based
1836 # coordinates instead of HTTP ones), so we re-register the repository
1837 # before we begin. We unregister it when we're done to make sure the
1838 # build will re-register the correct one for whichever test is
1839 # currently being run.
1841 # except, that source.Bazaar really doesn't like it when the archive
1842 # gets unregistered behind its back. The slave tries to do a 'baz
1843 # replay' in a tree with an archive that is no longer recognized, and
1844 # baz aborts with a botched invariant exception. This causes
1845 # mode=update to fall back to clobber+get, which flunks one of the
1846 # tests (the 'newfile' check in _do_vctest_update_3 fails)
1848 # to avoid this, we take heroic steps here to leave the archive
1849 # registration in the same state as we found it.
1851 tmp
= os
.path
.join(self
.repbase
, "archtmp")
1854 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1855 yield w
; out
= w
.getResult()
1857 lines
= out
.split("\n")
1858 coordinates
= lines
[1].strip()
1860 # now register the read-write location
1861 w
= waitForDeferred(self
.registerRepository(self
.archrep
))
1862 yield w
; w
.getResult()
1864 trunk
= self
.defaultbranch
1866 w
= self
.do_get(self
.repbase
, a
, trunk
, "archtmp")
1867 yield w
; w
.getResult()
1869 # tla appears to use timestamps to determine which files have
1870 # changed, so wait long enough for the new file to have a different
1874 version_c
= VERSION_C
% self
.version
1875 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1877 logfile
= "++log.%s--%s" % (trunk
, a
)
1878 logmsg
= "Summary: revised_to_%d\nKeywords:\n\n" % self
.version
1879 open(os
.path
.join(tmp
, logfile
), "w").write(logmsg
)
1880 w
= self
.dovc(tmp
, "commit")
1881 yield w
; out
= w
.getResult()
1882 m
= re
.search(r
'committed %s/%s--([\S]+)' % (a
, trunk
),
1884 assert (m
.group(1) == "base-0" or m
.group(1).startswith("patch-"))
1885 self
.addTrunkRev(m
.group(1))
1887 # now re-register the original coordinates
1888 w
= waitForDeferred(self
.registerRepository(coordinates
))
1889 yield w
; w
.getResult()
1891 vc_revise
= deferredGenerator(vc_revise
)
1893 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1894 assert os
.path
.abspath(workdir
) == workdir
1895 if os
.path
.exists(workdir
):
1896 rmdirRecursive(workdir
)
1900 # register the read-write location, if it wasn't already registered
1901 w
= waitForDeferred(self
.registerRepository(self
.archrep
))
1902 yield w
; w
.getResult()
1904 w
= self
.do_get(self
.repbase
, a
, "testvc--mainline--1", workdir
)
1905 yield w
; w
.getResult()
1909 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1910 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1912 def vc_try_finish(self
, workdir
):
1913 rmdirRecursive(workdir
)
1915 class Arch(VCBase
, unittest
.TestCase
):
1919 # Arch has a metadir="{arch}", but it does not have an 'export' mode.
1920 vctype
= "source.Arch"
1922 has_got_revision
= True
1924 def testCheckout(self
):
1925 # these are the coordinates of the read-write archive used by all the
1926 # non-HTTP tests. testCheckoutHTTP overrides these.
1927 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1928 'version': self
.helper
.defaultbranch
}
1929 d
= self
.do_vctest(testRetry
=False)
1930 # the current testRetry=True logic doesn't have the desired effect:
1931 # "update" is a no-op because arch knows that the repository hasn't
1932 # changed. Other VC systems will re-checkout missing files on
1933 # update, arch just leaves the tree untouched. TODO: come up with
1934 # some better test logic, probably involving a copy of the
1935 # repository that has a few changes checked in.
1939 def testCheckoutHTTP(self
):
1941 url
= "http://localhost:%d/Tla-Repository" % self
.httpPort
1942 self
.helper
.vcargs
= { 'url': url
,
1943 'version': "testvc--mainline--1" }
1944 d
= self
.do_vctest(testRetry
=False)
1947 def testPatch(self
):
1948 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1949 'version': self
.helper
.defaultbranch
}
1953 def testCheckoutBranch(self
):
1954 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1955 'version': self
.helper
.defaultbranch
}
1956 d
= self
.do_branch()
1960 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1961 'version': self
.helper
.defaultbranch
}
1962 d
= self
.do_getpatch()
1965 VCS
.registerVC(Arch
.vc_name
, TlaHelper())
1968 class BazaarHelper(TlaHelper
):
1972 bazpaths
= which('baz')
1974 return (False, "Arch (baz) is not installed")
1975 self
.vcexe
= bazpaths
[0]
1978 def setUp2(self
, res
):
1979 # we unregister the repository each time, because we might have
1980 # changed the coordinates (since we switch from a file: URL to an
1981 # http: URL for various tests). The buildslave code doesn't forcibly
1982 # unregister the archive, so we have to do it here.
1983 d
= self
.unregisterRepository()
1990 vctype
= "source.Bazaar"
1992 has_got_revision
= True
1996 def testCheckout(self
):
1997 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1998 # Baz adds the required 'archive' argument
1999 'archive': self
.helper
.archname
,
2000 'version': self
.helper
.defaultbranch
,
2002 d
= self
.do_vctest(testRetry
=False)
2003 # the current testRetry=True logic doesn't have the desired effect:
2004 # "update" is a no-op because arch knows that the repository hasn't
2005 # changed. Other VC systems will re-checkout missing files on
2006 # update, arch just leaves the tree untouched. TODO: come up with
2007 # some better test logic, probably involving a copy of the
2008 # repository that has a few changes checked in.
2012 def testCheckoutHTTP(self
):
2014 url
= "http://localhost:%d/Baz-Repository" % self
.httpPort
2015 self
.helper
.vcargs
= { 'url': url
,
2016 'archive': self
.helper
.archname
,
2017 'version': self
.helper
.defaultbranch
,
2019 d
= self
.do_vctest(testRetry
=False)
2022 def testPatch(self
):
2023 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
2024 # Baz adds the required 'archive' argument
2025 'archive': self
.helper
.archname
,
2026 'version': self
.helper
.defaultbranch
,
2031 def testCheckoutBranch(self
):
2032 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
2033 # Baz adds the required 'archive' argument
2034 'archive': self
.helper
.archname
,
2035 'version': self
.helper
.defaultbranch
,
2037 d
= self
.do_branch()
2041 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
2042 # Baz adds the required 'archive' argument
2043 'archive': self
.helper
.archname
,
2044 'version': self
.helper
.defaultbranch
,
2046 d
= self
.do_getpatch()
2049 def fixRepository(self
):
2050 self
.fixtimer
= None
2051 self
.site
.resource
= self
.root
2053 def testRetry(self
):
2054 # we want to verify that source.Source(retry=) works, and the easiest
2055 # way to make VC updates break (temporarily) is to break the HTTP
2056 # server that's providing the repository. Anything else pretty much
2057 # requires mutating the (read-only) BUILDBOT_TEST_VC repository, or
2058 # modifying the buildslave's checkout command while it's running.
2060 # this test takes a while to run, so don't bother doing it with
2061 # anything other than baz
2065 # break the repository server
2066 from twisted
.web
import static
2067 self
.site
.resource
= static
.Data("Sorry, repository is offline",
2069 # and arrange to fix it again in 5 seconds, while the test is
2071 self
.fixtimer
= reactor
.callLater(5, self
.fixRepository
)
2073 url
= "http://localhost:%d/Baz-Repository" % self
.httpPort
2074 self
.helper
.vcargs
= { 'url': url
,
2075 'archive': self
.helper
.archname
,
2076 'version': self
.helper
.defaultbranch
,
2079 d
= self
.do_vctest_once(True)
2080 d
.addCallback(self
._testRetry
_1)
2082 def _testRetry_1(self
, bs
):
2083 # make sure there was mention of the retry attempt in the logs
2085 self
.failUnlessIn("unable to access URL", l
.getText(),
2086 "funny, VC operation didn't fail at least once")
2087 self
.failUnlessIn("update failed, trying 4 more times after 5 seconds",
2088 l
.getTextWithHeaders(),
2089 "funny, VC operation wasn't reattempted")
2091 def testRetryFails(self
):
2092 # make sure that the build eventually gives up on a repository which
2093 # is completely unavailable
2097 # break the repository server, and leave it broken
2098 from twisted
.web
import static
2099 self
.site
.resource
= static
.Data("Sorry, repository is offline",
2102 url
= "http://localhost:%d/Baz-Repository" % self
.httpPort
2103 self
.helper
.vcargs
= {'url': url
,
2104 'archive': self
.helper
.archname
,
2105 'version': self
.helper
.defaultbranch
,
2108 d
= self
.do_vctest_once(False)
2109 d
.addCallback(self
._testRetryFails
_1)
2111 def _testRetryFails_1(self
, bs
):
2112 self
.failUnlessEqual(bs
.getResults(), FAILURE
)
2114 def tearDown2(self
):
2116 self
.fixtimer
.cancel()
2117 # tell tla to get rid of the leftover archive this test leaves in the
2118 # user's 'tla archives' listing. The name of this archive is provided
2119 # by the repository tarball, so the following command must use the
2120 # same name. We could use archive= to set it explicitly, but if you
2121 # change it from the default, then 'tla update' won't work.
2122 d
= self
.helper
.unregisterRepository()
2125 VCS
.registerVC(Bazaar
.vc_name
, BazaarHelper())
2127 class BzrHelper(BaseHelper
):
2128 branchname
= "branch"
2129 try_branchname
= "branch"
2132 bzrpaths
= which('bzr')
2134 return (False, "bzr is not installed")
2135 self
.vcexe
= bzrpaths
[0]
2138 def get_revision_number(self
, out
):
2139 for line
in out
.split("\n"):
2140 colon
= line
.index(":")
2141 key
, value
= line
[:colon
], line
[colon
+2:]
2144 raise RuntimeError("unable to find revno: in bzr output: '%s'" % out
)
2146 def createRepository(self
):
2147 self
.createBasedir()
2148 self
.bzr_base
= os
.path
.join(self
.repbase
, "Bzr-Repository")
2149 self
.rep_trunk
= os
.path
.join(self
.bzr_base
, "trunk")
2150 self
.rep_branch
= os
.path
.join(self
.bzr_base
, "branch")
2151 tmp
= os
.path
.join(self
.repbase
, "bzrtmp")
2152 btmp
= os
.path
.join(self
.repbase
, "bzrtmp-branch")
2154 os
.makedirs(self
.rep_trunk
)
2155 w
= self
.dovc(self
.rep_trunk
, ["init"])
2156 yield w
; w
.getResult()
2157 w
= self
.dovc(self
.bzr_base
,
2158 ["branch", self
.rep_trunk
, self
.rep_branch
])
2159 yield w
; w
.getResult()
2161 w
= self
.dovc(self
.repbase
, ["checkout", self
.rep_trunk
, tmp
])
2162 yield w
; w
.getResult()
2164 w
= self
.dovc(tmp
, qw("add"))
2165 yield w
; w
.getResult()
2166 w
= self
.dovc(tmp
, qw("commit -m initial_import"))
2167 yield w
; w
.getResult()
2168 w
= self
.dovc(tmp
, qw("version-info"))
2169 yield w
; out
= w
.getResult()
2170 self
.addTrunkRev(self
.get_revision_number(out
))
2173 # pull all trunk revisions to the branch
2174 w
= self
.dovc(self
.rep_branch
, qw("pull"))
2175 yield w
; w
.getResult()
2176 # obtain a branch tree
2177 w
= self
.dovc(self
.repbase
, ["checkout", self
.rep_branch
, btmp
])
2178 yield w
; w
.getResult()
2180 self
.populate_branch(btmp
)
2181 w
= self
.dovc(btmp
, qw("add"))
2182 yield w
; w
.getResult()
2183 w
= self
.dovc(btmp
, qw("commit -m commit_on_branch"))
2184 yield w
; w
.getResult()
2185 w
= self
.dovc(btmp
, qw("version-info"))
2186 yield w
; out
= w
.getResult()
2187 self
.addBranchRev(self
.get_revision_number(out
))
2188 rmdirRecursive(btmp
)
2189 createRepository
= deferredGenerator(createRepository
)
2191 def vc_revise(self
):
2192 tmp
= os
.path
.join(self
.repbase
, "bzrtmp")
2193 w
= self
.dovc(self
.repbase
, ["checkout", self
.rep_trunk
, tmp
])
2194 yield w
; w
.getResult()
2197 version_c
= VERSION_C
% self
.version
2198 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
2199 w
= self
.dovc(tmp
, qw("commit -m revised_to_%d" % self
.version
))
2200 yield w
; w
.getResult()
2201 w
= self
.dovc(tmp
, qw("version-info"))
2202 yield w
; out
= w
.getResult()
2203 self
.addTrunkRev(self
.get_revision_number(out
))
2205 vc_revise
= deferredGenerator(vc_revise
)
2207 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
2208 assert os
.path
.abspath(workdir
) == workdir
2209 if os
.path
.exists(workdir
):
2210 rmdirRecursive(workdir
)
2211 #os.makedirs(workdir)
2213 rep
= self
.rep_trunk
2215 rep
= os
.path
.join(self
.bzr_base
, branch
)
2216 w
= self
.dovc(self
.bzr_base
, ["checkout", rep
, workdir
])
2217 yield w
; w
.getResult()
2218 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
2219 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
2221 def vc_try_finish(self
, workdir
):
2222 rmdirRecursive(workdir
)
2224 class Bzr(VCBase
, unittest
.TestCase
):
2228 vctype
= "source.Bzr"
2230 has_got_revision
= True
2232 def testCheckout(self
):
2233 self
.helper
.vcargs
= { 'repourl': self
.helper
.rep_trunk
}
2234 d
= self
.do_vctest(testRetry
=False)
2236 # TODO: testRetry has the same problem with Bzr as it does for
2240 def testPatch(self
):
2241 self
.helper
.vcargs
= { 'baseURL': self
.helper
.bzr_base
+ "/",
2242 'defaultBranch': "trunk" }
2246 def testCheckoutBranch(self
):
2247 self
.helper
.vcargs
= { 'baseURL': self
.helper
.bzr_base
+ "/",
2248 'defaultBranch': "trunk" }
2249 d
= self
.do_branch()
2252 def testCheckoutHTTP(self
):
2254 repourl
= "http://localhost:%d/Bzr-Repository/trunk" % self
.httpPort
2255 self
.helper
.vcargs
= { 'repourl': repourl
}
2256 d
= self
.do_vctest(testRetry
=False)
2260 def fixRepository(self
):
2261 self
.fixtimer
= None
2262 self
.site
.resource
= self
.root
2264 def testRetry(self
):
2265 # this test takes a while to run
2268 # break the repository server
2269 from twisted
.web
import static
2270 self
.site
.resource
= static
.Data("Sorry, repository is offline",
2272 # and arrange to fix it again in 5 seconds, while the test is
2274 self
.fixtimer
= reactor
.callLater(5, self
.fixRepository
)
2276 repourl
= "http://localhost:%d/Bzr-Repository/trunk" % self
.httpPort
2277 self
.helper
.vcargs
= { 'repourl': repourl
,
2280 d
= self
.do_vctest_once(True)
2281 d
.addCallback(self
._testRetry
_1)
2283 def _testRetry_1(self
, bs
):
2284 # make sure there was mention of the retry attempt in the logs
2286 self
.failUnlessIn("ERROR: Not a branch: ", l
.getText(),
2287 "funny, VC operation didn't fail at least once")
2288 self
.failUnlessIn("update failed, trying 4 more times after 5 seconds",
2289 l
.getTextWithHeaders(),
2290 "funny, VC operation wasn't reattempted")
2292 def testRetryFails(self
):
2293 # make sure that the build eventually gives up on a repository which
2294 # is completely unavailable
2298 # break the repository server, and leave it broken
2299 from twisted
.web
import static
2300 self
.site
.resource
= static
.Data("Sorry, repository is offline",
2303 repourl
= "http://localhost:%d/Bzr-Repository/trunk" % self
.httpPort
2304 self
.helper
.vcargs
= { 'repourl': repourl
,
2307 d
= self
.do_vctest_once(False)
2308 d
.addCallback(self
._testRetryFails
_1)
2310 def _testRetryFails_1(self
, bs
):
2311 self
.failUnlessEqual(bs
.getResults(), FAILURE
)
2315 self
.helper
.vcargs
= { 'baseURL': self
.helper
.bzr_base
+ "/",
2316 'defaultBranch': "trunk" }
2317 d
= self
.do_getpatch()
2320 VCS
.registerVC(Bzr
.vc_name
, BzrHelper())
2323 class MercurialHelper(BaseHelper
):
2324 branchname
= "branch"
2325 try_branchname
= "branch"
2328 hgpaths
= which("hg")
2330 return (False, "Mercurial is not installed")
2331 self
.vcexe
= hgpaths
[0]
2334 def extract_id(self
, output
):
2335 m
= re
.search(r
'^(\w+)', output
)
2338 def createRepository(self
):
2339 self
.createBasedir()
2340 self
.hg_base
= os
.path
.join(self
.repbase
, "Mercurial-Repository")
2341 self
.rep_trunk
= os
.path
.join(self
.hg_base
, "trunk")
2342 self
.rep_branch
= os
.path
.join(self
.hg_base
, "branch")
2343 tmp
= os
.path
.join(self
.hg_base
, "hgtmp")
2345 os
.makedirs(self
.rep_trunk
)
2346 w
= self
.dovc(self
.rep_trunk
, "init")
2347 yield w
; w
.getResult()
2348 os
.makedirs(self
.rep_branch
)
2349 w
= self
.dovc(self
.rep_branch
, "init")
2350 yield w
; w
.getResult()
2353 w
= self
.dovc(tmp
, "init")
2354 yield w
; w
.getResult()
2355 w
= self
.dovc(tmp
, "add")
2356 yield w
; w
.getResult()
2357 w
= self
.dovc(tmp
, "commit -m initial_import")
2358 yield w
; w
.getResult()
2359 w
= self
.dovc(tmp
, "push %s" % self
.rep_trunk
)
2360 # note that hg-push does not actually update the working directory
2361 yield w
; w
.getResult()
2362 w
= self
.dovc(tmp
, "identify")
2363 yield w
; out
= w
.getResult()
2364 self
.addTrunkRev(self
.extract_id(out
))
2366 self
.populate_branch(tmp
)
2367 w
= self
.dovc(tmp
, "commit -m commit_on_branch")
2368 yield w
; w
.getResult()
2369 w
= self
.dovc(tmp
, "push %s" % self
.rep_branch
)
2370 yield w
; w
.getResult()
2371 w
= self
.dovc(tmp
, "identify")
2372 yield w
; out
= w
.getResult()
2373 self
.addBranchRev(self
.extract_id(out
))
2375 createRepository
= deferredGenerator(createRepository
)
2377 def vc_revise(self
):
2378 tmp
= os
.path
.join(self
.hg_base
, "hgtmp2")
2379 w
= self
.dovc(self
.hg_base
, "clone %s %s" % (self
.rep_trunk
, tmp
))
2380 yield w
; w
.getResult()
2383 version_c
= VERSION_C
% self
.version
2384 version_c_filename
= os
.path
.join(tmp
, "version.c")
2385 open(version_c_filename
, "w").write(version_c
)
2386 # hg uses timestamps to distinguish files which have changed, so we
2387 # force the mtime forward a little bit
2388 future
= time
.time() + 2*self
.version
2389 os
.utime(version_c_filename
, (future
, future
))
2390 w
= self
.dovc(tmp
, "commit -m revised_to_%d" % self
.version
)
2391 yield w
; w
.getResult()
2392 w
= self
.dovc(tmp
, "push %s" % self
.rep_trunk
)
2393 yield w
; w
.getResult()
2394 w
= self
.dovc(tmp
, "identify")
2395 yield w
; out
= w
.getResult()
2396 self
.addTrunkRev(self
.extract_id(out
))
2398 vc_revise
= deferredGenerator(vc_revise
)
2400 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
2401 assert os
.path
.abspath(workdir
) == workdir
2402 if os
.path
.exists(workdir
):
2403 rmdirRecursive(workdir
)
2405 src
= self
.rep_branch
2407 src
= self
.rep_trunk
2408 w
= self
.dovc(self
.hg_base
, "clone %s %s" % (src
, workdir
))
2409 yield w
; w
.getResult()
2410 try_c_filename
= os
.path
.join(workdir
, "subdir", "subdir.c")
2411 open(try_c_filename
, "w").write(TRY_C
)
2412 future
= time
.time() + 2*self
.version
2413 os
.utime(try_c_filename
, (future
, future
))
2414 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
2416 def vc_try_finish(self
, workdir
):
2417 rmdirRecursive(workdir
)
2420 class Mercurial(VCBase
, unittest
.TestCase
):
2423 # Mercurial has a metadir=".hg", but it does not have an 'export' mode.
2425 vctype
= "source.Mercurial"
2427 has_got_revision
= True
2429 def testCheckout(self
):
2430 self
.helper
.vcargs
= { 'repourl': self
.helper
.rep_trunk
}
2431 d
= self
.do_vctest(testRetry
=False)
2433 # TODO: testRetry has the same problem with Mercurial as it does for
2437 def testPatch(self
):
2438 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2439 'defaultBranch': "trunk" }
2443 def testCheckoutBranch(self
):
2444 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2445 'defaultBranch': "trunk" }
2446 d
= self
.do_branch()
2449 def testCheckoutHTTP(self
):
2451 repourl
= "http://localhost:%d/Mercurial-Repository/trunk/.hg" % self
.httpPort
2452 self
.helper
.vcargs
= { 'repourl': repourl
}
2453 d
= self
.do_vctest(testRetry
=False)
2455 # TODO: The easiest way to publish hg over HTTP is by running 'hg serve'
2456 # as a child process while the test is running. (you can also use a CGI
2457 # script, which sounds difficult, or you can publish the files directly,
2458 # which isn't well documented).
2459 testCheckoutHTTP
.skip
= "not yet implemented, use 'hg serve'"
2462 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2463 'defaultBranch': "trunk" }
2464 d
= self
.do_getpatch()
2467 VCS
.registerVC(Mercurial
.vc_name
, MercurialHelper())
2470 class Sources(unittest
.TestCase
):
2471 # TODO: this needs serious rethink
2472 def makeChange(self
, when
=None, revision
=None):
2474 when
= mktime_tz(parsedate_tz(when
))
2475 return changes
.Change("fred", [], "", when
=when
, revision
=revision
)
2478 r
= base
.BuildRequest("forced build", SourceStamp())
2480 s
= source
.CVS(cvsroot
=None, cvsmodule
=None)
2482 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), None)
2486 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2487 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2488 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2489 r
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2490 submitted
= "Wed, 08 Sep 2004 09:04:00 -0700"
2491 r
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2493 s
= source
.CVS(cvsroot
=None, cvsmodule
=None)
2495 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
2496 "Wed, 08 Sep 2004 16:03:00 -0000")
2500 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2501 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2502 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2503 r
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2504 submitted
= "Wed, 08 Sep 2004 09:04:00 -0700"
2505 r
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2507 s
= source
.CVS(cvsroot
=None, cvsmodule
=None, checkoutDelay
=10)
2509 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
2510 "Wed, 08 Sep 2004 16:02:10 -0000")
2514 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2515 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2516 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2517 r1
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2518 submitted
= "Wed, 08 Sep 2004 09:04:00 -0700"
2519 r1
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2522 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:05:00 -0700"))
2523 r2
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2524 submitted
= "Wed, 08 Sep 2004 09:07:00 -0700"
2525 r2
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2527 b
= base
.Build([r1
, r2
])
2528 s
= source
.CVS(cvsroot
=None, cvsmodule
=None)
2530 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
2531 "Wed, 08 Sep 2004 16:06:00 -0000")
2534 r
= base
.BuildRequest("forced", SourceStamp())
2536 s
= source
.SVN(svnurl
="dummy")
2538 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), None)
2542 c
.append(self
.makeChange(revision
=4))
2543 c
.append(self
.makeChange(revision
=10))
2544 c
.append(self
.makeChange(revision
=67))
2545 r
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2547 s
= source
.SVN(svnurl
="dummy")
2549 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), 67)
2551 class Patch(VCBase
, unittest
.TestCase
):
2558 def testPatch(self
):
2559 # invoke 'patch' all by itself, to see if it works the way we think
2560 # it should. This is intended to ferret out some windows test
2562 helper
= BaseHelper()
2563 self
.workdir
= os
.path
.join("test_vc", "testPatch")
2564 helper
.populate(self
.workdir
)
2565 patch
= which("patch")[0]
2567 command
= [patch
, "-p0"]
2570 def sendUpdate(self
, status
):
2572 c
= commands
.ShellCommand(FakeBuilder(), command
, self
.workdir
,
2573 sendRC
=False, initialStdin
=p0_diff
)
2575 d
.addCallback(self
._testPatch
_1)
2578 def _testPatch_1(self
, res
):
2579 # make sure the file actually got patched
2580 subdir_c
= os
.path
.join(self
.workdir
, "subdir", "subdir.c")
2581 data
= open(subdir_c
, "r").read()
2582 self
.failUnlessIn("Hello patched subdir.\\n", data
)