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
25 from buildbot
.test
.runutils
import SignalMixin
27 #step.LoggedRemoteCommand.debug = True
29 # buildbot.twcompat will patch these into t.i.defer if necessary
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.
56 import cStringIO
as StringIO
60 class _PutEverythingGetter(protocol
.ProcessProtocol
):
61 def __init__(self
, deferred
, stdin
):
62 self
.deferred
= deferred
63 self
.outBuf
= StringIO
.StringIO()
64 self
.errBuf
= StringIO
.StringIO()
65 self
.outReceived
= self
.outBuf
.write
66 self
.errReceived
= self
.errBuf
.write
69 def connectionMade(self
):
70 if self
.stdin
is not None:
71 self
.transport
.write(self
.stdin
)
72 self
.transport
.closeStdin()
74 def processEnded(self
, reason
):
75 out
= self
.outBuf
.getvalue()
76 err
= self
.errBuf
.getvalue()
80 self
.deferred
.errback((out
, err
, e
.signal
))
82 self
.deferred
.callback((out
, err
, code
))
84 def myGetProcessOutputAndValue(executable
, args
=(), env
={}, path
='.',
85 reactor
=None, stdin
=None):
86 """Like twisted.internet.utils.getProcessOutputAndValue but takes
89 from twisted
.internet
import reactor
91 p
= _PutEverythingGetter(d
, stdin
)
92 reactor
.spawnProcess(p
, executable
, (executable
,)+tuple(args
), env
, path
)
96 from buildbot.process import factory, step
99 f1 = factory.BuildFactory([
103 c['bots'] = [['bot1', 'sekrit']]
106 c['builders'] = [{'name': 'vc', 'slavename': 'bot1',
107 'builddir': 'vc-dir', 'factory': f1}]
108 c['slavePortnum'] = 0
109 BuildmasterConfig = c
113 Index: subdir/subdir.c
114 ===================================================================
115 RCS file: /home/warner/stuff/Projects/BuildBot/code-arch/_trial_temp/test_vc/repositories/CVS-Repository/sample/subdir/subdir.c,v
116 retrieving revision 1.1.1.1
117 diff -u -r1.1.1.1 subdir.c
118 --- subdir/subdir.c 14 Aug 2005 01:32:49 -0000 1.1.1.1
119 +++ subdir/subdir.c 14 Aug 2005 01:36:15 -0000
122 main(int argc, const char *argv[])
124 - printf("Hello subdir.\n");
125 + printf("Hello patched subdir.\n");
130 # this patch does not include the filename headers, so it is
135 main(int argc, const char *argv[])
137 - printf("Hello subdir.\\n");
138 + printf("Hello try.\\n");
148 main(int argc, const char *argv[])
150 printf("Hello world.\\n");
160 main(int argc, const char *argv[])
162 printf("Hello branch.\\n");
172 main(int argc, const char *argv[])
174 printf("Hello world, version=%d\\n");
180 // this is subdir/subdir.c
184 main(int argc, const char *argv[])
186 printf("Hello subdir.\\n");
192 // this is subdir/subdir.c
196 main(int argc, const char *argv[])
198 printf("Hello try.\\n");
204 # this is a helper class which keeps track of whether each VC system is
205 # available, and whether the repository for each has been created. There
206 # is one instance of this class, at module level, shared between all test
215 def registerVC(self
, name
, helper
):
216 self
._helpers
[name
] = helper
217 self
._repoReady
[name
] = False
219 def skipIfNotCapable(self
, name
):
220 """Either return None, or raise SkipTest"""
221 d
= self
.capable(name
)
224 raise unittest
.SkipTest(res
[1])
225 d
.addCallback(_maybeSkip
)
228 def capable(self
, name
):
229 """Return a Deferred that fires with (True,None) if this host offers
230 the given VC tool, or (False,excuse) if it does not (and therefore
231 the tests should be skipped)."""
233 if self
._isCapable
.has_key(name
):
234 if self
._isCapable
[name
]:
235 return defer
.succeed((True,None))
237 return defer
.succeed((False, self
._excuses
[name
]))
238 d
= defer
.maybeDeferred(self
._helpers
[name
].capable
)
241 self
._isCapable
[name
] = True
243 self
._excuses
[name
] = res
[1]
245 d
.addCallback(_capable
)
248 def getHelper(self
, name
):
249 return self
._helpers
[name
]
251 def createRepository(self
, name
):
252 """Return a Deferred that fires when the repository is set up."""
253 if self
._repoReady
[name
]:
254 return defer
.succeed(True)
255 d
= self
._helpers
[name
].createRepository()
257 self
._repoReady
[name
] = True
258 d
.addCallback(_ready
)
264 # the overall plan here:
266 # Each VC system is tested separately, all using the same source tree defined
267 # in the 'files' dictionary above. Each VC system gets its own TestCase
268 # subclass. The first test case that is run will create the repository during
269 # setUp(), making two branches: 'trunk' and 'branch'. The trunk gets a copy
270 # of all the files in 'files'. The variant of good.c is committed on the
273 # then testCheckout is run, which does a number of checkout/clobber/update
274 # builds. These all use trunk r1. It then runs self.fix(), which modifies
275 # 'fixable.c', then performs another build and makes sure the tree has been
278 # testBranch uses trunk-r1 and branch-r1, making sure that we clobber the
279 # tree properly when we switch between them
281 # testPatch does a trunk-r1 checkout and applies a patch.
283 # testTryGetPatch performs a trunk-r1 checkout, modifies some files, then
284 # verifies that tryclient.getSourceStamp figures out the base revision and
288 # vc_create makes a repository at r1 with three files: main.c, version.c, and
289 # subdir/foo.c . It also creates a branch from r1 (called b1) in which main.c
290 # says "hello branch" instead of "hello world". self.trunk[] contains
291 # revision stamps for everything on the trunk, and self.branch[] does the
292 # same for the branch.
294 # vc_revise() checks out a tree at HEAD, changes version.c, then checks it
295 # back in. The new version stamp is appended to self.trunk[]. The tree is
296 # removed afterwards.
298 # vc_try_checkout(workdir, rev) checks out a tree at REV, then changes
299 # subdir/subdir.c to say 'Hello try'
300 # vc_try_finish(workdir) removes the tree and cleans up any VC state
301 # necessary (like deleting the Arch archive entry).
311 # this is also responsible for setting self.vcexe
312 raise NotImplementedError
314 def createBasedir(self
):
315 # you must call this from createRepository
316 self
.repbase
= os
.path
.abspath(os
.path
.join("test_vc",
318 if not os
.path
.isdir(self
.repbase
):
319 os
.makedirs(self
.repbase
)
321 def createRepository(self
):
322 # this will only be called once per process
323 raise NotImplementedError
325 def populate(self
, 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 command
= self
.vcexe
+ " " + command
390 return self
.do(basedir
, command
, failureIsOk
, stdin
)
392 class VCBase(SignalMixin
):
394 createdRepository
= False
401 has_got_revision
= False
402 has_got_revision_branches_are_merged
= False # for SVN
404 def failUnlessIn(self
, substring
, string
, msg
=None):
405 # trial provides a version of this that requires python-2.3 to test
408 msg
= ("did not see the expected substring '%s' in string '%s'" %
410 self
.failUnless(string
.find(substring
) != -1, msg
)
413 d
= VCS
.skipIfNotCapable(self
.vc_name
)
414 d
.addCallback(self
._setUp
1)
417 def _setUp1(self
, res
):
418 self
.helper
= VCS
.getHelper(self
.vc_name
)
420 if os
.path
.exists("basedir"):
421 rmdirRecursive("basedir")
423 self
.master
= master
.BuildMaster("basedir")
424 self
.slavebase
= os
.path
.abspath("slavebase")
425 if os
.path
.exists(self
.slavebase
):
426 rmdirRecursive(self
.slavebase
)
427 os
.mkdir("slavebase")
429 d
= VCS
.createRepository(self
.vc_name
)
432 def connectSlave(self
):
433 port
= self
.master
.slavePort
._port
.getHost().port
434 slave
= bot
.BuildSlave("localhost", port
, "bot1", "sekrit",
435 self
.slavebase
, keepalive
=0, usePTY
=1)
438 d
= self
.master
.botmaster
.waitUntilBuilderAttached("vc")
441 def loadConfig(self
, config
):
442 # reloading the config file causes a new 'listDirs' command to be
443 # sent to the slave. To synchronize on this properly, it is easiest
444 # to stop and restart the slave.
445 d
= defer
.succeed(None)
447 d
= self
.master
.botmaster
.waitUntilBuilderDetached("vc")
448 self
.slave
.stopService()
449 d
.addCallback(lambda res
: self
.master
.loadConfig(config
))
450 d
.addCallback(lambda res
: self
.connectSlave())
454 # launch an HTTP server to serve the repository files
455 from twisted
.web
import static
, server
456 from twisted
.internet
import reactor
457 self
.root
= static
.File(self
.helper
.repbase
)
458 self
.site
= server
.Site(self
.root
)
459 self
.httpServer
= reactor
.listenTCP(0, self
.site
)
460 self
.httpPort
= self
.httpServer
.getHost().port
462 def doBuild(self
, shouldSucceed
=True, ss
=None):
463 c
= interfaces
.IControl(self
.master
)
467 #print "doBuild(ss: b=%s rev=%s)" % (ss.branch, ss.revision)
468 req
= base
.BuildRequest("test_vc forced build", ss
)
469 d
= req
.waitUntilFinished()
470 c
.getBuilder("vc").requestBuild(req
)
471 d
.addCallback(self
._doBuild
_1, shouldSucceed
)
473 def _doBuild_1(self
, bs
, shouldSucceed
):
475 if r
!= SUCCESS
and shouldSucceed
:
478 if not bs
.isFinished():
479 print "Hey, build wasn't even finished!"
480 print "Build did not succeed:", r
, bs
.getText()
481 for s
in bs
.getSteps():
482 for l
in s
.getLogs():
483 print "--- START step %s / log %s ---" % (s
.getName(),
485 print l
.getTextWithHeaders()
488 self
.fail("build did not succeed")
491 def touch(self
, d
, f
):
492 open(os
.path
.join(d
,f
),"w").close()
493 def shouldExist(self
, *args
):
494 target
= os
.path
.join(*args
)
495 self
.failUnless(os
.path
.exists(target
),
496 "expected to find %s but didn't" % target
)
497 def shouldNotExist(self
, *args
):
498 target
= os
.path
.join(*args
)
499 self
.failIf(os
.path
.exists(target
),
500 "expected to NOT find %s, but did" % target
)
501 def shouldContain(self
, d
, f
, contents
):
502 c
= open(os
.path
.join(d
, f
), "r").read()
503 self
.failUnlessIn(contents
, c
)
505 def checkGotRevision(self
, bs
, expected
):
506 if self
.has_got_revision
:
507 self
.failUnlessEqual(bs
.getProperty("got_revision"), expected
)
509 def checkGotRevisionIsLatest(self
, bs
):
510 expected
= self
.helper
.trunk
[-1]
511 if self
.has_got_revision_branches_are_merged
:
512 expected
= self
.helper
.allrevs
[-1]
513 self
.checkGotRevision(bs
, expected
)
515 def do_vctest(self
, testRetry
=True):
517 args
= self
.helper
.vcargs
519 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
520 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
521 # woo double-substitution
522 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
523 for k
,v
in args
.items():
524 s
+= ", %s=%s" % (k
, repr(v
))
526 config
= config_vc
% s
528 m
.loadConfig(config
% 'clobber')
532 d
= self
.connectSlave()
533 d
.addCallback(lambda res
: log
.msg("testing clobber"))
534 d
.addCallback(self
._do
_vctest
_clobber
)
535 d
.addCallback(lambda res
: log
.msg("doing update"))
536 d
.addCallback(lambda res
: self
.loadConfig(config
% 'update'))
537 d
.addCallback(lambda res
: log
.msg("testing update"))
538 d
.addCallback(self
._do
_vctest
_update
)
540 d
.addCallback(lambda res
: log
.msg("testing update retry"))
541 d
.addCallback(self
._do
_vctest
_update
_retry
)
542 d
.addCallback(lambda res
: log
.msg("doing copy"))
543 d
.addCallback(lambda res
: self
.loadConfig(config
% 'copy'))
544 d
.addCallback(lambda res
: log
.msg("testing copy"))
545 d
.addCallback(self
._do
_vctest
_copy
)
547 d
.addCallback(lambda res
: log
.msg("doing export"))
548 d
.addCallback(lambda res
: self
.loadConfig(config
% 'export'))
549 d
.addCallback(lambda res
: log
.msg("testing export"))
550 d
.addCallback(self
._do
_vctest
_export
)
553 def _do_vctest_clobber(self
, res
):
554 d
= self
.doBuild() # initial checkout
555 d
.addCallback(self
._do
_vctest
_clobber
_1)
557 def _do_vctest_clobber_1(self
, bs
):
558 self
.shouldExist(self
.workdir
, "main.c")
559 self
.shouldExist(self
.workdir
, "version.c")
560 self
.shouldExist(self
.workdir
, "subdir", "subdir.c")
562 self
.shouldExist(self
.workdir
, self
.metadir
)
563 self
.failUnlessEqual(bs
.getProperty("revision"), None)
564 self
.failUnlessEqual(bs
.getProperty("branch"), None)
565 self
.checkGotRevisionIsLatest(bs
)
567 self
.touch(self
.workdir
, "newfile")
568 self
.shouldExist(self
.workdir
, "newfile")
569 d
= self
.doBuild() # rebuild clobbers workdir
570 d
.addCallback(self
._do
_vctest
_clobber
_2)
572 def _do_vctest_clobber_2(self
, res
):
573 self
.shouldNotExist(self
.workdir
, "newfile")
575 def _do_vctest_update(self
, res
):
576 log
.msg("_do_vctest_update")
577 d
= self
.doBuild() # rebuild with update
578 d
.addCallback(self
._do
_vctest
_update
_1)
580 def _do_vctest_update_1(self
, bs
):
581 log
.msg("_do_vctest_update_1")
582 self
.shouldExist(self
.workdir
, "main.c")
583 self
.shouldExist(self
.workdir
, "version.c")
584 self
.shouldContain(self
.workdir
, "version.c",
585 "version=%d" % self
.helper
.version
)
587 self
.shouldExist(self
.workdir
, self
.metadir
)
588 self
.failUnlessEqual(bs
.getProperty("revision"), None)
589 self
.checkGotRevisionIsLatest(bs
)
591 self
.touch(self
.workdir
, "newfile")
592 d
= self
.doBuild() # update rebuild leaves new files
593 d
.addCallback(self
._do
_vctest
_update
_2)
595 def _do_vctest_update_2(self
, bs
):
596 log
.msg("_do_vctest_update_2")
597 self
.shouldExist(self
.workdir
, "main.c")
598 self
.shouldExist(self
.workdir
, "version.c")
599 self
.touch(self
.workdir
, "newfile")
600 # now make a change to the repository and make sure we pick it up
601 d
= self
.helper
.vc_revise()
602 d
.addCallback(lambda res
: self
.doBuild())
603 d
.addCallback(self
._do
_vctest
_update
_3)
605 def _do_vctest_update_3(self
, bs
):
606 log
.msg("_do_vctest_update_3")
607 self
.shouldExist(self
.workdir
, "main.c")
608 self
.shouldExist(self
.workdir
, "version.c")
609 self
.shouldContain(self
.workdir
, "version.c",
610 "version=%d" % self
.helper
.version
)
611 self
.shouldExist(self
.workdir
, "newfile")
612 self
.failUnlessEqual(bs
.getProperty("revision"), None)
613 self
.checkGotRevisionIsLatest(bs
)
615 # now "update" to an older revision
616 d
= self
.doBuild(ss
=SourceStamp(revision
=self
.helper
.trunk
[-2]))
617 d
.addCallback(self
._do
_vctest
_update
_4)
619 def _do_vctest_update_4(self
, bs
):
620 log
.msg("_do_vctest_update_4")
621 self
.shouldExist(self
.workdir
, "main.c")
622 self
.shouldExist(self
.workdir
, "version.c")
623 self
.shouldContain(self
.workdir
, "version.c",
624 "version=%d" % (self
.helper
.version
-1))
625 self
.failUnlessEqual(bs
.getProperty("revision"),
626 self
.helper
.trunk
[-2])
627 self
.checkGotRevision(bs
, self
.helper
.trunk
[-2])
629 # now update to the newer revision
630 d
= self
.doBuild(ss
=SourceStamp(revision
=self
.helper
.trunk
[-1]))
631 d
.addCallback(self
._do
_vctest
_update
_5)
633 def _do_vctest_update_5(self
, bs
):
634 log
.msg("_do_vctest_update_5")
635 self
.shouldExist(self
.workdir
, "main.c")
636 self
.shouldExist(self
.workdir
, "version.c")
637 self
.shouldContain(self
.workdir
, "version.c",
638 "version=%d" % self
.helper
.version
)
639 self
.failUnlessEqual(bs
.getProperty("revision"),
640 self
.helper
.trunk
[-1])
641 self
.checkGotRevision(bs
, self
.helper
.trunk
[-1])
644 def _do_vctest_update_retry(self
, res
):
645 # certain local changes will prevent an update from working. The
646 # most common is to replace a file with a directory, or vice
647 # versa. The slave code should spot the failure and do a
649 os
.unlink(os
.path
.join(self
.workdir
, "main.c"))
650 os
.mkdir(os
.path
.join(self
.workdir
, "main.c"))
651 self
.touch(os
.path
.join(self
.workdir
, "main.c"), "foo")
652 self
.touch(self
.workdir
, "newfile")
654 d
= self
.doBuild() # update, but must clobber to handle the error
655 d
.addCallback(self
._do
_vctest
_update
_retry
_1)
657 def _do_vctest_update_retry_1(self
, bs
):
658 self
.shouldNotExist(self
.workdir
, "newfile")
660 def _do_vctest_copy(self
, res
):
661 d
= self
.doBuild() # copy rebuild clobbers new files
662 d
.addCallback(self
._do
_vctest
_copy
_1)
664 def _do_vctest_copy_1(self
, bs
):
666 self
.shouldExist(self
.workdir
, self
.metadir
)
667 self
.shouldNotExist(self
.workdir
, "newfile")
668 self
.touch(self
.workdir
, "newfile")
669 self
.touch(self
.vcdir
, "newvcfile")
670 self
.failUnlessEqual(bs
.getProperty("revision"), None)
671 self
.checkGotRevisionIsLatest(bs
)
673 d
= self
.doBuild() # copy rebuild clobbers new files
674 d
.addCallback(self
._do
_vctest
_copy
_2)
676 def _do_vctest_copy_2(self
, bs
):
678 self
.shouldExist(self
.workdir
, self
.metadir
)
679 self
.shouldNotExist(self
.workdir
, "newfile")
680 self
.shouldExist(self
.vcdir
, "newvcfile")
681 self
.shouldExist(self
.workdir
, "newvcfile")
682 self
.failUnlessEqual(bs
.getProperty("revision"), None)
683 self
.checkGotRevisionIsLatest(bs
)
684 self
.touch(self
.workdir
, "newfile")
686 def _do_vctest_export(self
, res
):
687 d
= self
.doBuild() # export rebuild clobbers new files
688 d
.addCallback(self
._do
_vctest
_export
_1)
690 def _do_vctest_export_1(self
, bs
):
691 self
.shouldNotExist(self
.workdir
, self
.metadir
)
692 self
.shouldNotExist(self
.workdir
, "newfile")
693 self
.failUnlessEqual(bs
.getProperty("revision"), None)
694 #self.checkGotRevisionIsLatest(bs)
695 # VC 'export' is not required to have a got_revision
696 self
.touch(self
.workdir
, "newfile")
698 d
= self
.doBuild() # export rebuild clobbers new files
699 d
.addCallback(self
._do
_vctest
_export
_2)
701 def _do_vctest_export_2(self
, bs
):
702 self
.shouldNotExist(self
.workdir
, self
.metadir
)
703 self
.shouldNotExist(self
.workdir
, "newfile")
704 self
.failUnlessEqual(bs
.getProperty("revision"), None)
705 #self.checkGotRevisionIsLatest(bs)
706 # VC 'export' is not required to have a got_revision
710 args
= self
.helper
.vcargs
712 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
713 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
714 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
715 for k
,v
in args
.items():
716 s
+= ", %s=%s" % (k
, repr(v
))
718 self
.config
= config_vc
% s
720 m
.loadConfig(self
.config
% "clobber")
724 ss
= SourceStamp(revision
=self
.helper
.trunk
[-1], patch
=(0, p0_diff
))
726 d
= self
.connectSlave()
727 d
.addCallback(lambda res
: self
.doBuild(ss
=ss
))
728 d
.addCallback(self
._doPatch
_1)
730 def _doPatch_1(self
, bs
):
731 self
.shouldContain(self
.workdir
, "version.c",
732 "version=%d" % self
.helper
.version
)
733 # make sure the file actually got patched
734 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
735 "subdir", "subdir.c")
736 data
= open(subdir_c
, "r").read()
737 self
.failUnlessIn("Hello patched subdir.\\n", data
)
738 self
.failUnlessEqual(bs
.getProperty("revision"),
739 self
.helper
.trunk
[-1])
740 self
.checkGotRevision(bs
, self
.helper
.trunk
[-1])
742 # make sure that a rebuild does not use the leftover patched workdir
743 d
= self
.master
.loadConfig(self
.config
% "update")
744 d
.addCallback(lambda res
: self
.doBuild(ss
=None))
745 d
.addCallback(self
._doPatch
_2)
747 def _doPatch_2(self
, bs
):
748 # make sure the file is back to its original
749 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
750 "subdir", "subdir.c")
751 data
= open(subdir_c
, "r").read()
752 self
.failUnlessIn("Hello subdir.\\n", data
)
753 self
.failUnlessEqual(bs
.getProperty("revision"), None)
754 self
.checkGotRevisionIsLatest(bs
)
756 # now make sure we can patch an older revision. We need at least two
757 # revisions here, so we might have to create one first
758 if len(self
.helper
.trunk
) < 2:
759 d
= self
.helper
.vc_revise()
760 d
.addCallback(self
._doPatch
_3)
762 return self
._doPatch
_3()
764 def _doPatch_3(self
, res
=None):
765 ss
= SourceStamp(revision
=self
.helper
.trunk
[-2], patch
=(0, p0_diff
))
766 d
= self
.doBuild(ss
=ss
)
767 d
.addCallback(self
._doPatch
_4)
769 def _doPatch_4(self
, bs
):
770 self
.shouldContain(self
.workdir
, "version.c",
771 "version=%d" % (self
.helper
.version
-1))
772 # and make sure the file actually got patched
773 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
774 "subdir", "subdir.c")
775 data
= open(subdir_c
, "r").read()
776 self
.failUnlessIn("Hello patched subdir.\\n", data
)
777 self
.failUnlessEqual(bs
.getProperty("revision"),
778 self
.helper
.trunk
[-2])
779 self
.checkGotRevision(bs
, self
.helper
.trunk
[-2])
781 # now check that we can patch a branch
782 ss
= SourceStamp(branch
=self
.helper
.branchname
,
783 revision
=self
.helper
.branch
[-1],
785 d
= self
.doBuild(ss
=ss
)
786 d
.addCallback(self
._doPatch
_5)
788 def _doPatch_5(self
, bs
):
789 self
.shouldContain(self
.workdir
, "version.c",
791 self
.shouldContain(self
.workdir
, "main.c", "Hello branch.")
792 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
793 "subdir", "subdir.c")
794 data
= open(subdir_c
, "r").read()
795 self
.failUnlessIn("Hello patched subdir.\\n", data
)
796 self
.failUnlessEqual(bs
.getProperty("revision"),
797 self
.helper
.branch
[-1])
798 self
.failUnlessEqual(bs
.getProperty("branch"), self
.helper
.branchname
)
799 self
.checkGotRevision(bs
, self
.helper
.branch
[-1])
802 def do_vctest_once(self
, shouldSucceed
):
805 args
= self
.helper
.vcargs
806 vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
807 workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
808 # woo double-substitution
809 s
= "s(%s, timeout=200, workdir='build', mode='clobber'" % (vctype
,)
810 for k
,v
in args
.items():
811 s
+= ", %s=%s" % (k
, repr(v
))
813 config
= config_vc
% s
820 d
= self
.doBuild(shouldSucceed
) # initial checkout
826 args
= self
.helper
.vcargs
828 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
829 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
830 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
831 for k
,v
in args
.items():
832 s
+= ", %s=%s" % (k
, repr(v
))
834 self
.config
= config_vc
% s
836 m
.loadConfig(self
.config
% "update")
840 # first we do a build of the trunk
841 d
= self
.connectSlave()
842 d
.addCallback(lambda res
: self
.doBuild(ss
=SourceStamp()))
843 d
.addCallback(self
._doBranch
_1)
845 def _doBranch_1(self
, bs
):
846 log
.msg("_doBranch_1")
847 # make sure the checkout was of the trunk
848 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
849 data
= open(main_c
, "r").read()
850 self
.failUnlessIn("Hello world.", data
)
852 # now do a checkout on the branch. The change in branch name should
854 self
.touch(self
.workdir
, "newfile")
855 d
= self
.doBuild(ss
=SourceStamp(branch
=self
.helper
.branchname
))
856 d
.addCallback(self
._doBranch
_2)
858 def _doBranch_2(self
, bs
):
859 log
.msg("_doBranch_2")
860 # make sure it was on the branch
861 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
862 data
= open(main_c
, "r").read()
863 self
.failUnlessIn("Hello branch.", data
)
864 # and make sure the tree was clobbered
865 self
.shouldNotExist(self
.workdir
, "newfile")
867 # doing another build on the same branch should not clobber the tree
868 self
.touch(self
.workdir
, "newbranchfile")
869 d
= self
.doBuild(ss
=SourceStamp(branch
=self
.helper
.branchname
))
870 d
.addCallback(self
._doBranch
_3)
872 def _doBranch_3(self
, bs
):
873 log
.msg("_doBranch_3")
874 # make sure it is still on the branch
875 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
876 data
= open(main_c
, "r").read()
877 self
.failUnlessIn("Hello branch.", data
)
878 # and make sure the tree was not clobbered
879 self
.shouldExist(self
.workdir
, "newbranchfile")
881 # now make sure that a non-branch checkout clobbers the tree
882 d
= self
.doBuild(ss
=SourceStamp())
883 d
.addCallback(self
._doBranch
_4)
885 def _doBranch_4(self
, bs
):
886 log
.msg("_doBranch_4")
887 # make sure it was on the trunk
888 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
889 data
= open(main_c
, "r").read()
890 self
.failUnlessIn("Hello world.", data
)
891 self
.shouldNotExist(self
.workdir
, "newbranchfile")
893 def do_getpatch(self
, doBranch
=True):
894 log
.msg("do_getpatch")
895 # prepare a buildslave to do checkouts
897 args
= self
.helper
.vcargs
899 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
900 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
901 # woo double-substitution
902 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
903 for k
,v
in args
.items():
904 s
+= ", %s=%s" % (k
, repr(v
))
906 config
= config_vc
% s
908 m
.loadConfig(config
% 'clobber')
912 d
= self
.connectSlave()
914 # then set up the "developer's tree". first we modify a tree from the
916 tmpdir
= "try_workdir"
917 self
.trydir
= os
.path
.join(self
.helper
.repbase
, tmpdir
)
918 rmdirRecursive(self
.trydir
)
919 d
.addCallback(self
.do_getpatch_trunkhead
)
920 d
.addCallback(self
.do_getpatch_trunkold
)
922 d
.addCallback(self
.do_getpatch_branch
)
923 d
.addCallback(self
.do_getpatch_finish
)
926 def do_getpatch_finish(self
, res
):
927 log
.msg("do_getpatch_finish")
928 self
.helper
.vc_try_finish(self
.trydir
)
931 def try_shouldMatch(self
, filename
):
932 devfilename
= os
.path
.join(self
.trydir
, filename
)
933 devfile
= open(devfilename
, "r").read()
934 slavefilename
= os
.path
.join(self
.workdir
, filename
)
935 slavefile
= open(slavefilename
, "r").read()
936 self
.failUnlessEqual(devfile
, slavefile
,
937 ("slavefile (%s) contains '%s'. "
938 "developer's file (%s) contains '%s'. "
939 "These ought to match") %
940 (slavefilename
, slavefile
,
941 devfilename
, devfile
))
943 def do_getpatch_trunkhead(self
, res
):
944 log
.msg("do_getpatch_trunkhead")
945 d
= self
.helper
.vc_try_checkout(self
.trydir
, self
.helper
.trunk
[-1])
946 d
.addCallback(self
._do
_getpatch
_trunkhead
_1)
948 def _do_getpatch_trunkhead_1(self
, res
):
949 log
.msg("_do_getpatch_trunkhead_1")
950 d
= tryclient
.getSourceStamp(self
.vctype_try
, self
.trydir
, None)
951 d
.addCallback(self
._do
_getpatch
_trunkhead
_2)
953 def _do_getpatch_trunkhead_2(self
, ss
):
954 log
.msg("_do_getpatch_trunkhead_2")
955 d
= self
.doBuild(ss
=ss
)
956 d
.addCallback(self
._do
_getpatch
_trunkhead
_3)
958 def _do_getpatch_trunkhead_3(self
, res
):
959 log
.msg("_do_getpatch_trunkhead_3")
960 # verify that the resulting buildslave tree matches the developer's
961 self
.try_shouldMatch("main.c")
962 self
.try_shouldMatch("version.c")
963 self
.try_shouldMatch(os
.path
.join("subdir", "subdir.c"))
965 def do_getpatch_trunkold(self
, res
):
966 log
.msg("do_getpatch_trunkold")
967 # now try a tree from an older revision. We need at least two
968 # revisions here, so we might have to create one first
969 if len(self
.helper
.trunk
) < 2:
970 d
= self
.helper
.vc_revise()
971 d
.addCallback(self
._do
_getpatch
_trunkold
_1)
973 return self
._do
_getpatch
_trunkold
_1()
974 def _do_getpatch_trunkold_1(self
, res
=None):
975 log
.msg("_do_getpatch_trunkold_1")
976 d
= self
.helper
.vc_try_checkout(self
.trydir
, self
.helper
.trunk
[-2])
977 d
.addCallback(self
._do
_getpatch
_trunkold
_2)
979 def _do_getpatch_trunkold_2(self
, res
):
980 log
.msg("_do_getpatch_trunkold_2")
981 d
= tryclient
.getSourceStamp(self
.vctype_try
, self
.trydir
, None)
982 d
.addCallback(self
._do
_getpatch
_trunkold
_3)
984 def _do_getpatch_trunkold_3(self
, ss
):
985 log
.msg("_do_getpatch_trunkold_3")
986 d
= self
.doBuild(ss
=ss
)
987 d
.addCallback(self
._do
_getpatch
_trunkold
_4)
989 def _do_getpatch_trunkold_4(self
, res
):
990 log
.msg("_do_getpatch_trunkold_4")
991 # verify that the resulting buildslave tree matches the developer's
992 self
.try_shouldMatch("main.c")
993 self
.try_shouldMatch("version.c")
994 self
.try_shouldMatch(os
.path
.join("subdir", "subdir.c"))
996 def do_getpatch_branch(self
, res
):
997 log
.msg("do_getpatch_branch")
998 # now try a tree from a branch
999 d
= self
.helper
.vc_try_checkout(self
.trydir
, self
.helper
.branch
[-1],
1000 self
.helper
.branchname
)
1001 d
.addCallback(self
._do
_getpatch
_branch
_1)
1003 def _do_getpatch_branch_1(self
, res
):
1004 log
.msg("_do_getpatch_branch_1")
1005 d
= tryclient
.getSourceStamp(self
.vctype_try
, self
.trydir
,
1006 self
.helper
.try_branchname
)
1007 d
.addCallback(self
._do
_getpatch
_branch
_2)
1009 def _do_getpatch_branch_2(self
, ss
):
1010 log
.msg("_do_getpatch_branch_2")
1011 d
= self
.doBuild(ss
=ss
)
1012 d
.addCallback(self
._do
_getpatch
_branch
_3)
1014 def _do_getpatch_branch_3(self
, res
):
1015 log
.msg("_do_getpatch_branch_3")
1016 # verify that the resulting buildslave tree matches the developer's
1017 self
.try_shouldMatch("main.c")
1018 self
.try_shouldMatch("version.c")
1019 self
.try_shouldMatch(os
.path
.join("subdir", "subdir.c"))
1022 def dumpPatch(self
, patch
):
1023 # this exists to help me figure out the right 'patchlevel' value
1024 # should be returned by tryclient.getSourceStamp
1026 open(n
,"w").write(patch
)
1027 d
= self
.runCommand(".", ["lsdiff", n
])
1028 def p(res
): print "lsdiff:", res
.strip().split("\n")
1034 d
= defer
.succeed(None)
1036 d2
= self
.master
.botmaster
.waitUntilBuilderDetached("vc")
1037 d
.addCallback(lambda res
: self
.slave
.stopService())
1038 d
.addCallback(lambda res
: d2
)
1040 d
.addCallback(lambda res
: self
.master
.stopService())
1042 d
.addCallback(lambda res
: self
.httpServer
.stopListening())
1043 def stopHTTPTimer():
1045 from twisted
.web
import http
# Twisted-2.0
1047 from twisted
.protocols
import http
# Twisted-1.3
1048 http
._logDateTimeStop
() # shut down the internal timer. DUMB!
1049 d
.addCallback(lambda res
: stopHTTPTimer())
1050 d
.addCallback(lambda res
: self
.tearDown2())
1053 def tearDown2(self
):
1056 class CVSHelper(BaseHelper
):
1057 branchname
= "branch"
1058 try_branchname
= "branch"
1061 cvspaths
= which('cvs')
1063 return (False, "CVS is not installed")
1064 # cvs-1.10 (as shipped with OS-X 10.3 "Panther") is too old for this
1065 # test. There is a situation where we check out a tree, make a
1066 # change, then commit it back, and CVS refuses to believe that we're
1067 # operating in a CVS tree. I tested cvs-1.12.9 and it works ok, OS-X
1068 # 10.4 "Tiger" comes with cvs-1.11, but I haven't tested that yet.
1069 # For now, skip the tests if we've got 1.10 .
1070 log
.msg("running %s --version.." % (cvspaths
[0],))
1071 d
= utils
.getProcessOutput(cvspaths
[0], ["--version"],
1073 d
.addCallback(self
._capable
, cvspaths
[0])
1076 def _capable(self
, v
, vcexe
):
1077 m
= re
.search(r
'\(CVS\) ([\d\.]+) ', v
)
1079 log
.msg("couldn't identify CVS version number in output:")
1080 log
.msg("'''%s'''" % v
)
1081 log
.msg("skipping tests")
1082 return (False, "Found CVS but couldn't identify its version")
1084 log
.msg("found CVS version '%s'" % ver
)
1086 return (False, "Found CVS, but it is too old")
1091 # this timestamp is eventually passed to CVS in a -D argument, and
1092 # strftime's %z specifier doesn't seem to work reliably (I get +0000
1093 # where I should get +0700 under linux sometimes, and windows seems
1094 # to want to put a verbose 'Eastern Standard Time' in there), so
1095 # leave off the timezone specifier and treat this as localtime. A
1096 # valid alternative would be to use a hard-coded +0000 and
1098 return time
.strftime("%Y-%m-%d %H:%M:%S", time
.localtime())
1100 def createRepository(self
):
1101 self
.createBasedir()
1102 self
.cvsrep
= cvsrep
= os
.path
.join(self
.repbase
, "CVS-Repository")
1103 tmp
= os
.path
.join(self
.repbase
, "cvstmp")
1105 w
= self
.dovc(self
.repbase
, "-d %s init" % cvsrep
)
1106 yield w
; w
.getResult() # we must getResult() to raise any exceptions
1109 cmd
= ("-d %s import" % cvsrep
+
1110 " -m sample_project_files sample vendortag start")
1111 w
= self
.dovc(tmp
, cmd
)
1112 yield w
; w
.getResult()
1114 # take a timestamp as the first revision number
1116 self
.addTrunkRev(self
.getdate())
1119 w
= self
.dovc(self
.repbase
,
1120 "-d %s checkout -d cvstmp sample" % self
.cvsrep
)
1121 yield w
; w
.getResult()
1123 w
= self
.dovc(tmp
, "tag -b %s" % self
.branchname
)
1124 yield w
; w
.getResult()
1125 self
.populate_branch(tmp
)
1127 "commit -m commit_on_branch -r %s" % self
.branchname
)
1128 yield w
; w
.getResult()
1131 self
.addBranchRev(self
.getdate())
1133 self
.vcargs
= { 'cvsroot': self
.cvsrep
, 'cvsmodule': "sample" }
1134 createRepository
= deferredGenerator(createRepository
)
1137 def vc_revise(self
):
1138 tmp
= os
.path
.join(self
.repbase
, "cvstmp")
1140 w
= self
.dovc(self
.repbase
,
1141 "-d %s checkout -d cvstmp sample" % self
.cvsrep
)
1142 yield w
; w
.getResult()
1144 version_c
= VERSION_C
% self
.version
1145 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1147 "commit -m revised_to_%d version.c" % self
.version
)
1148 yield w
; w
.getResult()
1151 self
.addTrunkRev(self
.getdate())
1153 vc_revise
= deferredGenerator(vc_revise
)
1155 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1156 # 'workdir' is an absolute path
1157 assert os
.path
.abspath(workdir
) == workdir
1158 cmd
= [self
.vcexe
, "-d", self
.cvsrep
, "checkout",
1161 if branch
is not None:
1164 cmd
.append("sample")
1165 w
= self
.do(self
.repbase
, cmd
)
1166 yield w
; w
.getResult()
1167 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1168 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1170 def vc_try_finish(self
, workdir
):
1171 rmdirRecursive(workdir
)
1173 class CVS(VCBase
, unittest
.TestCase
):
1179 # CVS gives us got_revision, but it is based entirely upon the local
1180 # clock, which means it is unlikely to match the timestamp taken earlier.
1181 # This might be enough for common use, but won't be good enough for our
1182 # tests to accept, so pretend it doesn't have got_revision at all.
1183 has_got_revision
= False
1185 def testCheckout(self
):
1186 d
= self
.do_vctest()
1189 def testPatch(self
):
1193 def testCheckoutBranch(self
):
1194 d
= self
.do_branch()
1198 d
= self
.do_getpatch(doBranch
=False)
1201 VCS
.registerVC(CVS
.vc_name
, CVSHelper())
1204 class SVNHelper(BaseHelper
):
1205 branchname
= "sample/branch"
1206 try_branchname
= "sample/branch"
1209 svnpaths
= which('svn')
1210 svnadminpaths
= which('svnadmin')
1212 return (False, "SVN is not installed")
1213 if not svnadminpaths
:
1214 return (False, "svnadmin is not installed")
1215 # we need svn to be compiled with the ra_local access
1217 log
.msg("running svn --version..")
1218 env
= os
.environ
.copy()
1220 d
= utils
.getProcessOutput(svnpaths
[0], ["--version"],
1222 d
.addCallback(self
._capable
, svnpaths
[0], svnadminpaths
[0])
1225 def _capable(self
, v
, vcexe
, svnadmin
):
1226 if v
.find("handles 'file' schem") != -1:
1227 # older versions say 'schema', 1.2.0 and beyond say 'scheme'
1229 self
.svnadmin
= svnadmin
1231 excuse
= ("%s found but it does not support 'file:' " +
1232 "schema, skipping svn tests") % vcexe
1234 return (False, excuse
)
1236 def createRepository(self
):
1237 self
.createBasedir()
1238 self
.svnrep
= os
.path
.join(self
.repbase
,
1239 "SVN-Repository").replace('\\','/')
1240 tmp
= os
.path
.join(self
.repbase
, "svntmp")
1241 if sys
.platform
== 'win32':
1242 # On Windows Paths do not start with a /
1243 self
.svnurl
= "file:///%s" % self
.svnrep
1245 self
.svnurl
= "file://%s" % self
.svnrep
1246 self
.svnurl_trunk
= self
.svnurl
+ "/sample/trunk"
1247 self
.svnurl_branch
= self
.svnurl
+ "/sample/branch"
1249 w
= self
.do(self
.repbase
, self
.svnadmin
+" create %s" % self
.svnrep
)
1250 yield w
; w
.getResult()
1254 "import -m sample_project_files %s" %
1256 yield w
; out
= w
.getResult()
1258 m
= re
.search(r
'Committed revision (\d+)\.', out
)
1259 assert m
.group(1) == "1" # first revision is always "1"
1260 self
.addTrunkRev(int(m
.group(1)))
1262 w
= self
.dovc(self
.repbase
,
1263 "checkout %s svntmp" % self
.svnurl_trunk
)
1264 yield w
; w
.getResult()
1266 w
= self
.dovc(tmp
, "cp -m make_branch %s %s" % (self
.svnurl_trunk
,
1267 self
.svnurl_branch
))
1268 yield w
; w
.getResult()
1269 w
= self
.dovc(tmp
, "switch %s" % self
.svnurl_branch
)
1270 yield w
; w
.getResult()
1271 self
.populate_branch(tmp
)
1272 w
= self
.dovc(tmp
, "commit -m commit_on_branch")
1273 yield w
; out
= w
.getResult()
1275 m
= re
.search(r
'Committed revision (\d+)\.', out
)
1276 self
.addBranchRev(int(m
.group(1)))
1277 createRepository
= deferredGenerator(createRepository
)
1279 def vc_revise(self
):
1280 tmp
= os
.path
.join(self
.repbase
, "svntmp")
1282 log
.msg("vc_revise" + self
.svnurl_trunk
)
1283 w
= self
.dovc(self
.repbase
,
1284 "checkout %s svntmp" % self
.svnurl_trunk
)
1285 yield w
; w
.getResult()
1287 version_c
= VERSION_C
% self
.version
1288 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1289 w
= self
.dovc(tmp
, "commit -m revised_to_%d" % self
.version
)
1290 yield w
; out
= w
.getResult()
1291 m
= re
.search(r
'Committed revision (\d+)\.', out
)
1292 self
.addTrunkRev(int(m
.group(1)))
1294 vc_revise
= deferredGenerator(vc_revise
)
1296 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1297 assert os
.path
.abspath(workdir
) == workdir
1298 if os
.path
.exists(workdir
):
1299 rmdirRecursive(workdir
)
1301 svnurl
= self
.svnurl_trunk
1303 # N.B.: this is *not* os.path.join: SVN URLs use slashes
1304 # regardless of the host operating system's filepath separator
1305 svnurl
= self
.svnurl
+ "/" + branch
1306 w
= self
.dovc(self
.repbase
,
1307 "checkout %s %s" % (svnurl
, workdir
))
1308 yield w
; w
.getResult()
1309 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1310 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1312 def vc_try_finish(self
, workdir
):
1313 rmdirRecursive(workdir
)
1316 class SVN(VCBase
, unittest
.TestCase
):
1322 has_got_revision
= True
1323 has_got_revision_branches_are_merged
= True
1325 def testCheckout(self
):
1326 # we verify this one with the svnurl style of vcargs. We test the
1327 # baseURL/defaultBranch style in testPatch and testCheckoutBranch.
1328 self
.helper
.vcargs
= { 'svnurl': self
.helper
.svnurl_trunk
}
1329 d
= self
.do_vctest()
1332 def testPatch(self
):
1333 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1334 'defaultBranch': "sample/trunk",
1339 def testCheckoutBranch(self
):
1340 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1341 'defaultBranch': "sample/trunk",
1343 d
= self
.do_branch()
1347 # extract the base revision and patch from a modified tree, use it to
1348 # create the same contents on the buildslave
1349 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1350 'defaultBranch': "sample/trunk",
1352 d
= self
.do_getpatch()
1355 VCS
.registerVC(SVN
.vc_name
, SVNHelper())
1358 class P4Helper(BaseHelper
):
1359 branchname
= "branch"
1360 p4port
= 'localhost:1666'
1362 base_descr
= 'Change: new\nDescription: asdf\nFiles:\n'
1365 p4paths
= which('p4')
1366 p4dpaths
= which('p4d')
1368 return (False, "p4 is not installed")
1370 return (False, "p4d is not installed")
1371 self
.vcexe
= p4paths
[0]
1372 self
.p4dexe
= p4dpaths
[0]
1375 class _P4DProtocol(protocol
.ProcessProtocol
):
1377 self
.started
= defer
.Deferred()
1378 self
.ended
= defer
.Deferred()
1380 def outReceived(self
, data
):
1381 # When it says starting, it has bound to the socket.
1383 if data
.startswith('Perforce Server starting...'):
1384 self
.started
.callback(None)
1386 print "p4d said %r" % data
1388 raise Exception('p4d said %r' % data
)
1390 self
.started
.errback(failure
.Failure())
1393 def errReceived(self
, data
):
1394 print "p4d stderr: %s" % data
1396 def processEnded(self
, status_object
):
1397 if status_object
.check(error
.ProcessDone
):
1398 self
.ended
.callback(None)
1400 self
.ended
.errback(status_object
)
1402 def _start_p4d(self
):
1403 proto
= self
._P
4DProtocol
()
1404 reactor
.spawnProcess(proto
, self
.p4dexe
, ['p4d', '-p', self
.p4port
],
1405 env
=os
.environ
, path
=self
.p4rep
)
1406 return proto
.started
, proto
.ended
1408 def dop4(self
, basedir
, command
, failureIsOk
=False, stdin
=None):
1409 # p4 looks at $PWD instead of getcwd(), which causes confusion when
1410 # we spawn commands without an intervening shell (sh -c). We can
1411 # override this with a -d argument.
1412 command
= "-p %s -d %s %s" % (self
.p4port
, basedir
, command
)
1413 return self
.dovc(basedir
, command
, failureIsOk
, stdin
)
1415 def createRepository(self
):
1416 # this is only called once per VC system, so start p4d here.
1418 self
.createBasedir()
1419 tmp
= os
.path
.join(self
.repbase
, "p4tmp")
1420 self
.p4rep
= os
.path
.join(self
.repbase
, 'P4-Repository')
1421 os
.mkdir(self
.p4rep
)
1424 started
, self
.p4d_shutdown
= self
._start
_p
4d
()
1425 w
= waitForDeferred(started
)
1426 yield w
; w
.getResult()
1428 # Create client spec.
1430 clispec
= 'Client: creator\n'
1431 clispec
+= 'Root: %s\n' % tmp
1432 clispec
+= 'View:\n'
1433 clispec
+= '\t//depot/... //creator/...\n'
1434 w
= self
.dop4(tmp
, 'client -i', stdin
=clispec
)
1435 yield w
; w
.getResult()
1437 # Create first rev (trunk).
1438 self
.populate(os
.path
.join(tmp
, 'trunk'))
1439 files
= ['main.c', 'version.c', 'subdir/subdir.c']
1440 w
= self
.dop4(tmp
, "-c creator add "
1441 + " ".join(['trunk/%s' % f
for f
in files
]))
1442 yield w
; w
.getResult()
1443 descr
= self
.base_descr
1445 descr
+= '\t//depot/trunk/%s\n' % file
1446 w
= self
.dop4(tmp
, "-c creator submit -i", stdin
=descr
)
1447 yield w
; out
= w
.getResult()
1448 m
= re
.search(r
'Change (\d+) submitted.', out
)
1449 assert m
.group(1) == '1'
1450 self
.addTrunkRev(m
.group(1))
1452 # Create second rev (branch).
1453 w
= self
.dop4(tmp
, '-c creator integrate '
1454 + '//depot/trunk/... //depot/branch/...')
1455 yield w
; w
.getResult()
1456 w
= self
.dop4(tmp
, "-c creator edit branch/main.c")
1457 yield w
; w
.getResult()
1458 self
.populate_branch(os
.path
.join(tmp
, 'branch'))
1459 descr
= self
.base_descr
1461 descr
+= '\t//depot/branch/%s\n' % file
1462 w
= self
.dop4(tmp
, "-c creator submit -i", stdin
=descr
)
1463 yield w
; out
= w
.getResult()
1464 m
= re
.search(r
'Change (\d+) submitted.', out
)
1465 self
.addBranchRev(m
.group(1))
1466 createRepository
= deferredGenerator(createRepository
)
1468 def vc_revise(self
):
1469 tmp
= os
.path
.join(self
.repbase
, "p4tmp")
1471 version_c
= VERSION_C
% self
.version
1472 w
= self
.dop4(tmp
, '-c creator edit trunk/version.c')
1473 yield w
; w
.getResult()
1474 open(os
.path
.join(tmp
, "trunk/version.c"), "w").write(version_c
)
1475 descr
= self
.base_descr
+ '\t//depot/trunk/version.c\n'
1476 w
= self
.dop4(tmp
, "-c creator submit -i", stdin
=descr
)
1477 yield w
; out
= w
.getResult()
1478 m
= re
.search(r
'Change (\d+) submitted.', out
)
1479 self
.addTrunkRev(m
.group(1))
1480 vc_revise
= deferredGenerator(vc_revise
)
1482 def shutdown_p4d(self
):
1483 d
= self
.runCommand(self
.repbase
, '%s -p %s admin stop'
1484 % (self
.vcexe
, self
.p4port
))
1485 return d
.addCallback(lambda _
: self
.p4d_shutdown
)
1487 class P4(VCBase
, unittest
.TestCase
):
1492 def tearDownClass(self
):
1494 return maybeWait(self
.helper
.shutdown_p4d())
1496 def testCheckout(self
):
1497 self
.helper
.vcargs
= { 'p4port': self
.helper
.p4port
,
1498 'p4base': '//depot/',
1499 'defaultBranch': 'trunk' }
1500 d
= self
.do_vctest(testRetry
=False)
1501 # TODO: like arch and darcs, sync does nothing when server is not
1505 def testCheckoutBranch(self
):
1506 self
.helper
.vcargs
= { 'p4port': self
.helper
.p4port
,
1507 'p4base': '//depot/',
1508 'defaultBranch': 'trunk' }
1509 d
= self
.do_branch()
1512 def testPatch(self
):
1513 self
.helper
.vcargs
= { 'p4port': self
.helper
.p4port
,
1514 'p4base': '//depot/',
1515 'defaultBranch': 'trunk' }
1519 VCS
.registerVC(P4
.vc_name
, P4Helper())
1522 class DarcsHelper(BaseHelper
):
1523 branchname
= "branch"
1524 try_branchname
= "branch"
1527 darcspaths
= which('darcs')
1529 return (False, "Darcs is not installed")
1530 self
.vcexe
= darcspaths
[0]
1533 def createRepository(self
):
1534 self
.createBasedir()
1535 self
.darcs_base
= os
.path
.join(self
.repbase
, "Darcs-Repository")
1536 self
.rep_trunk
= os
.path
.join(self
.darcs_base
, "trunk")
1537 self
.rep_branch
= os
.path
.join(self
.darcs_base
, "branch")
1538 tmp
= os
.path
.join(self
.repbase
, "darcstmp")
1540 os
.makedirs(self
.rep_trunk
)
1541 w
= self
.dovc(self
.rep_trunk
, "initialize")
1542 yield w
; w
.getResult()
1543 os
.makedirs(self
.rep_branch
)
1544 w
= self
.dovc(self
.rep_branch
, "initialize")
1545 yield w
; w
.getResult()
1548 w
= self
.dovc(tmp
, "initialize")
1549 yield w
; w
.getResult()
1550 w
= self
.dovc(tmp
, "add -r .")
1551 yield w
; w
.getResult()
1552 w
= self
.dovc(tmp
, "record -a -m initial_import --skip-long-comment -A test@buildbot.sf.net")
1553 yield w
; w
.getResult()
1554 w
= self
.dovc(tmp
, "push -a %s" % self
.rep_trunk
)
1555 yield w
; w
.getResult()
1556 w
= self
.dovc(tmp
, "changes --context")
1557 yield w
; out
= w
.getResult()
1558 self
.addTrunkRev(out
)
1560 self
.populate_branch(tmp
)
1561 w
= self
.dovc(tmp
, "record -a --ignore-times -m commit_on_branch --skip-long-comment -A test@buildbot.sf.net")
1562 yield w
; w
.getResult()
1563 w
= self
.dovc(tmp
, "push -a %s" % self
.rep_branch
)
1564 yield w
; w
.getResult()
1565 w
= self
.dovc(tmp
, "changes --context")
1566 yield w
; out
= w
.getResult()
1567 self
.addBranchRev(out
)
1569 createRepository
= deferredGenerator(createRepository
)
1571 def vc_revise(self
):
1572 tmp
= os
.path
.join(self
.repbase
, "darcstmp")
1574 w
= self
.dovc(tmp
, "initialize")
1575 yield w
; w
.getResult()
1576 w
= self
.dovc(tmp
, "pull -a %s" % self
.rep_trunk
)
1577 yield w
; w
.getResult()
1580 version_c
= VERSION_C
% self
.version
1581 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1582 w
= self
.dovc(tmp
, "record -a --ignore-times -m revised_to_%d --skip-long-comment -A test@buildbot.sf.net" % self
.version
)
1583 yield w
; w
.getResult()
1584 w
= self
.dovc(tmp
, "push -a %s" % self
.rep_trunk
)
1585 yield w
; w
.getResult()
1586 w
= self
.dovc(tmp
, "changes --context")
1587 yield w
; out
= w
.getResult()
1588 self
.addTrunkRev(out
)
1590 vc_revise
= deferredGenerator(vc_revise
)
1592 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1593 assert os
.path
.abspath(workdir
) == workdir
1594 if os
.path
.exists(workdir
):
1595 rmdirRecursive(workdir
)
1596 os
.makedirs(workdir
)
1597 w
= self
.dovc(workdir
, "initialize")
1598 yield w
; w
.getResult()
1600 rep
= self
.rep_trunk
1602 rep
= os
.path
.join(self
.darcs_base
, branch
)
1603 w
= self
.dovc(workdir
, "pull -a %s" % rep
)
1604 yield w
; w
.getResult()
1605 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1606 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1608 def vc_try_finish(self
, workdir
):
1609 rmdirRecursive(workdir
)
1612 class Darcs(VCBase
, unittest
.TestCase
):
1615 # Darcs has a metadir="_darcs", but it does not have an 'export'
1618 vctype
= "step.Darcs"
1619 vctype_try
= "darcs"
1620 has_got_revision
= True
1622 def testCheckout(self
):
1623 self
.helper
.vcargs
= { 'repourl': self
.helper
.rep_trunk
}
1624 d
= self
.do_vctest(testRetry
=False)
1626 # TODO: testRetry has the same problem with Darcs as it does for
1630 def testPatch(self
):
1631 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1632 'defaultBranch': "trunk" }
1636 def testCheckoutBranch(self
):
1637 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1638 'defaultBranch': "trunk" }
1639 d
= self
.do_branch()
1642 def testCheckoutHTTP(self
):
1644 repourl
= "http://localhost:%d/Darcs-Repository/trunk" % self
.httpPort
1645 self
.helper
.vcargs
= { 'repourl': repourl
}
1646 d
= self
.do_vctest(testRetry
=False)
1650 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1651 'defaultBranch': "trunk" }
1652 d
= self
.do_getpatch()
1655 VCS
.registerVC(Darcs
.vc_name
, DarcsHelper())
1659 def registerRepository(self
, coordinates
):
1661 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1662 yield w
; out
= w
.getResult()
1664 w
= self
.dovc(self
.repbase
, "register-archive -d %s" % a
)
1665 yield w
; w
.getResult()
1666 w
= self
.dovc(self
.repbase
, "register-archive %s" % coordinates
)
1667 yield w
; w
.getResult()
1668 registerRepository
= deferredGenerator(registerRepository
)
1670 def unregisterRepository(self
):
1672 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1673 yield w
; out
= w
.getResult()
1675 w
= self
.dovc(self
.repbase
, "register-archive -d %s" % a
)
1676 yield w
; out
= w
.getResult()
1677 unregisterRepository
= deferredGenerator(unregisterRepository
)
1679 class TlaHelper(BaseHelper
, ArchCommon
):
1680 defaultbranch
= "testvc--mainline--1"
1681 branchname
= "testvc--branch--1"
1682 try_branchname
= None # TlaExtractor can figure it out by itself
1686 tlapaths
= which('tla')
1688 return (False, "Arch (tla) is not installed")
1689 self
.vcexe
= tlapaths
[0]
1692 def do_get(self
, basedir
, archive
, branch
, newdir
):
1693 # the 'get' syntax is different between tla and baz. baz, while
1694 # claiming to honor an --archive argument, in fact ignores it. The
1695 # correct invocation is 'baz get archive/revision newdir'.
1696 if self
.archcmd
== "tla":
1697 w
= self
.dovc(basedir
,
1698 "get -A %s %s %s" % (archive
, branch
, newdir
))
1700 w
= self
.dovc(basedir
,
1701 "get %s/%s %s" % (archive
, branch
, newdir
))
1704 def createRepository(self
):
1705 self
.createBasedir()
1706 # first check to see if bazaar is around, since we'll need to know
1708 d
= VCS
.capable(Bazaar
.vc_name
)
1709 d
.addCallback(self
._createRepository
_1)
1712 def _createRepository_1(self
, res
):
1715 # pick a hopefully unique string for the archive name, in the form
1716 # test-%d@buildbot.sf.net--testvc, since otherwise multiple copies of
1717 # the unit tests run in the same user account will collide (since the
1718 # archive names are kept in the per-user ~/.arch-params/ directory).
1720 self
.archname
= "test-%s-%d@buildbot.sf.net--testvc" % (self
.archcmd
,
1722 trunk
= self
.defaultbranch
1723 branch
= self
.branchname
1725 repword
= self
.archcmd
.capitalize()
1726 self
.archrep
= os
.path
.join(self
.repbase
, "%s-Repository" % repword
)
1727 tmp
= os
.path
.join(self
.repbase
, "archtmp")
1732 w
= self
.dovc(tmp
, "my-id", failureIsOk
=True)
1733 yield w
; res
= w
.getResult()
1735 # tla will fail a lot of operations if you have not set an ID
1736 w
= self
.do(tmp
, [self
.vcexe
, "my-id",
1737 "Buildbot Test Suite <test@buildbot.sf.net>"])
1738 yield w
; w
.getResult()
1741 # bazaar keeps a cache of revisions, but this test creates a new
1742 # archive each time it is run, so the cache causes errors.
1743 # Disable the cache to avoid these problems. This will be
1744 # slightly annoying for people who run the buildbot tests under
1745 # the same UID as one which uses baz on a regular basis, but
1746 # bazaar doesn't give us a way to disable the cache just for this
1748 cmd
= "%s cache-config --disable" % VCS
.getHelper('bazaar').vcexe
1749 w
= self
.do(tmp
, cmd
)
1750 yield w
; w
.getResult()
1752 w
= waitForDeferred(self
.unregisterRepository())
1753 yield w
; w
.getResult()
1755 # these commands can be run in any directory
1756 w
= self
.dovc(tmp
, "make-archive -l %s %s" % (a
, self
.archrep
))
1757 yield w
; w
.getResult()
1758 if self
.archcmd
== "tla":
1759 w
= self
.dovc(tmp
, "archive-setup -A %s %s" % (a
, trunk
))
1760 yield w
; w
.getResult()
1761 w
= self
.dovc(tmp
, "archive-setup -A %s %s" % (a
, branch
))
1762 yield w
; w
.getResult()
1764 # baz does not require an 'archive-setup' step
1767 # these commands must be run in the directory that is to be imported
1768 w
= self
.dovc(tmp
, "init-tree --nested %s/%s" % (a
, trunk
))
1769 yield w
; w
.getResult()
1770 files
= " ".join(["main.c", "version.c", "subdir",
1771 os
.path
.join("subdir", "subdir.c")])
1772 w
= self
.dovc(tmp
, "add-id %s" % files
)
1773 yield w
; w
.getResult()
1775 w
= self
.dovc(tmp
, "import %s/%s" % (a
, trunk
))
1776 yield w
; out
= w
.getResult()
1777 self
.addTrunkRev("base-0")
1780 if self
.archcmd
== "tla":
1781 branchstart
= "%s--base-0" % trunk
1782 w
= self
.dovc(tmp
, "tag -A %s %s %s" % (a
, branchstart
, branch
))
1783 yield w
; w
.getResult()
1785 w
= self
.dovc(tmp
, "branch %s" % branch
)
1786 yield w
; w
.getResult()
1790 # check out the branch
1791 w
= self
.do_get(self
.repbase
, a
, branch
, "archtmp")
1792 yield w
; w
.getResult()
1794 self
.populate_branch(tmp
)
1795 logfile
= "++log.%s--%s" % (branch
, a
)
1796 logmsg
= "Summary: commit on branch\nKeywords:\n\n"
1797 open(os
.path
.join(tmp
, logfile
), "w").write(logmsg
)
1798 w
= self
.dovc(tmp
, "commit")
1799 yield w
; out
= w
.getResult()
1800 m
= re
.search(r
'committed %s/%s--([\S]+)' % (a
, branch
),
1802 assert (m
.group(1) == "base-0" or m
.group(1).startswith("patch-"))
1803 self
.addBranchRev(m
.group(1))
1805 w
= waitForDeferred(self
.unregisterRepository())
1806 yield w
; w
.getResult()
1809 # we unregister the repository each time, because we might have
1810 # changed the coordinates (since we switch from a file: URL to an
1811 # http: URL for various tests). The buildslave code doesn't forcibly
1812 # unregister the archive, so we have to do it here.
1813 w
= waitForDeferred(self
.unregisterRepository())
1814 yield w
; w
.getResult()
1816 _createRepository_1
= deferredGenerator(_createRepository_1
)
1818 def vc_revise(self
):
1819 # the fix needs to be done in a workspace that is linked to a
1820 # read-write version of the archive (i.e., using file-based
1821 # coordinates instead of HTTP ones), so we re-register the repository
1822 # before we begin. We unregister it when we're done to make sure the
1823 # build will re-register the correct one for whichever test is
1824 # currently being run.
1826 # except, that step.Bazaar really doesn't like it when the archive
1827 # gets unregistered behind its back. The slave tries to do a 'baz
1828 # replay' in a tree with an archive that is no longer recognized, and
1829 # baz aborts with a botched invariant exception. This causes
1830 # mode=update to fall back to clobber+get, which flunks one of the
1831 # tests (the 'newfile' check in _do_vctest_update_3 fails)
1833 # to avoid this, we take heroic steps here to leave the archive
1834 # registration in the same state as we found it.
1836 tmp
= os
.path
.join(self
.repbase
, "archtmp")
1839 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1840 yield w
; out
= w
.getResult()
1842 lines
= out
.split("\n")
1843 coordinates
= lines
[1].strip()
1845 # now register the read-write location
1846 w
= waitForDeferred(self
.registerRepository(self
.archrep
))
1847 yield w
; w
.getResult()
1849 trunk
= self
.defaultbranch
1851 w
= self
.do_get(self
.repbase
, a
, trunk
, "archtmp")
1852 yield w
; w
.getResult()
1854 # tla appears to use timestamps to determine which files have
1855 # changed, so wait long enough for the new file to have a different
1859 version_c
= VERSION_C
% self
.version
1860 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1862 logfile
= "++log.%s--%s" % (trunk
, a
)
1863 logmsg
= "Summary: revised_to_%d\nKeywords:\n\n" % self
.version
1864 open(os
.path
.join(tmp
, logfile
), "w").write(logmsg
)
1865 w
= self
.dovc(tmp
, "commit")
1866 yield w
; out
= w
.getResult()
1867 m
= re
.search(r
'committed %s/%s--([\S]+)' % (a
, trunk
),
1869 assert (m
.group(1) == "base-0" or m
.group(1).startswith("patch-"))
1870 self
.addTrunkRev(m
.group(1))
1872 # now re-register the original coordinates
1873 w
= waitForDeferred(self
.registerRepository(coordinates
))
1874 yield w
; w
.getResult()
1876 vc_revise
= deferredGenerator(vc_revise
)
1878 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1879 assert os
.path
.abspath(workdir
) == workdir
1880 if os
.path
.exists(workdir
):
1881 rmdirRecursive(workdir
)
1885 # register the read-write location, if it wasn't already registered
1886 w
= waitForDeferred(self
.registerRepository(self
.archrep
))
1887 yield w
; w
.getResult()
1889 w
= self
.do_get(self
.repbase
, a
, "testvc--mainline--1", workdir
)
1890 yield w
; w
.getResult()
1894 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1895 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1897 def vc_try_finish(self
, workdir
):
1898 rmdirRecursive(workdir
)
1900 class Arch(VCBase
, unittest
.TestCase
):
1904 # Arch has a metadir="{arch}", but it does not have an 'export' mode.
1905 vctype
= "step.Arch"
1907 has_got_revision
= True
1909 def testCheckout(self
):
1910 # these are the coordinates of the read-write archive used by all the
1911 # non-HTTP tests. testCheckoutHTTP overrides these.
1912 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1913 'version': self
.helper
.defaultbranch
}
1914 d
= self
.do_vctest(testRetry
=False)
1915 # the current testRetry=True logic doesn't have the desired effect:
1916 # "update" is a no-op because arch knows that the repository hasn't
1917 # changed. Other VC systems will re-checkout missing files on
1918 # update, arch just leaves the tree untouched. TODO: come up with
1919 # some better test logic, probably involving a copy of the
1920 # repository that has a few changes checked in.
1924 def testCheckoutHTTP(self
):
1926 url
= "http://localhost:%d/Tla-Repository" % self
.httpPort
1927 self
.helper
.vcargs
= { 'url': url
,
1928 'version': "testvc--mainline--1" }
1929 d
= self
.do_vctest(testRetry
=False)
1932 def testPatch(self
):
1933 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1934 'version': self
.helper
.defaultbranch
}
1938 def testCheckoutBranch(self
):
1939 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1940 'version': self
.helper
.defaultbranch
}
1941 d
= self
.do_branch()
1945 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1946 'version': self
.helper
.defaultbranch
}
1947 d
= self
.do_getpatch()
1950 VCS
.registerVC(Arch
.vc_name
, TlaHelper())
1953 class BazaarHelper(TlaHelper
):
1957 bazpaths
= which('baz')
1959 return (False, "Arch (baz) is not installed")
1960 self
.vcexe
= bazpaths
[0]
1963 def setUp2(self
, res
):
1964 # we unregister the repository each time, because we might have
1965 # changed the coordinates (since we switch from a file: URL to an
1966 # http: URL for various tests). The buildslave code doesn't forcibly
1967 # unregister the archive, so we have to do it here.
1968 d
= self
.unregisterRepository()
1975 vctype
= "step.Bazaar"
1977 has_got_revision
= True
1981 def testCheckout(self
):
1982 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1983 # Baz adds the required 'archive' argument
1984 'archive': self
.helper
.archname
,
1985 'version': self
.helper
.defaultbranch
,
1987 d
= self
.do_vctest(testRetry
=False)
1988 # the current testRetry=True logic doesn't have the desired effect:
1989 # "update" is a no-op because arch knows that the repository hasn't
1990 # changed. Other VC systems will re-checkout missing files on
1991 # update, arch just leaves the tree untouched. TODO: come up with
1992 # some better test logic, probably involving a copy of the
1993 # repository that has a few changes checked in.
1997 def testCheckoutHTTP(self
):
1999 url
= "http://localhost:%d/Baz-Repository" % self
.httpPort
2000 self
.helper
.vcargs
= { 'url': url
,
2001 'archive': self
.helper
.archname
,
2002 'version': self
.helper
.defaultbranch
,
2004 d
= self
.do_vctest(testRetry
=False)
2007 def testPatch(self
):
2008 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
2009 # Baz adds the required 'archive' argument
2010 'archive': self
.helper
.archname
,
2011 'version': self
.helper
.defaultbranch
,
2016 def testCheckoutBranch(self
):
2017 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
2018 # Baz adds the required 'archive' argument
2019 'archive': self
.helper
.archname
,
2020 'version': self
.helper
.defaultbranch
,
2022 d
= self
.do_branch()
2026 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
2027 # Baz adds the required 'archive' argument
2028 'archive': self
.helper
.archname
,
2029 'version': self
.helper
.defaultbranch
,
2031 d
= self
.do_getpatch()
2034 def fixRepository(self
):
2035 self
.fixtimer
= None
2036 self
.site
.resource
= self
.root
2038 def testRetry(self
):
2039 # we want to verify that step.Source(retry=) works, and the easiest
2040 # way to make VC updates break (temporarily) is to break the HTTP
2041 # server that's providing the repository. Anything else pretty much
2042 # requires mutating the (read-only) BUILDBOT_TEST_VC repository, or
2043 # modifying the buildslave's checkout command while it's running.
2045 # this test takes a while to run, so don't bother doing it with
2046 # anything other than baz
2050 # break the repository server
2051 from twisted
.web
import static
2052 self
.site
.resource
= static
.Data("Sorry, repository is offline",
2054 # and arrange to fix it again in 5 seconds, while the test is
2056 self
.fixtimer
= reactor
.callLater(5, self
.fixRepository
)
2058 url
= "http://localhost:%d/Baz-Repository" % self
.httpPort
2059 self
.helper
.vcargs
= { 'url': url
,
2060 'archive': self
.helper
.archname
,
2061 'version': self
.helper
.defaultbranch
,
2064 d
= self
.do_vctest_once(True)
2065 d
.addCallback(self
._testRetry
_1)
2067 def _testRetry_1(self
, bs
):
2068 # make sure there was mention of the retry attempt in the logs
2070 self
.failUnlessIn("unable to access URL", l
.getText(),
2071 "funny, VC operation didn't fail at least once")
2072 self
.failUnlessIn("update failed, trying 4 more times after 5 seconds",
2073 l
.getTextWithHeaders(),
2074 "funny, VC operation wasn't reattempted")
2076 def testRetryFails(self
):
2077 # make sure that the build eventually gives up on a repository which
2078 # is completely unavailable
2082 # break the repository server, and leave it broken
2083 from twisted
.web
import static
2084 self
.site
.resource
= static
.Data("Sorry, repository is offline",
2087 url
= "http://localhost:%d/Baz-Repository" % self
.httpPort
2088 self
.helper
.vcargs
= {'url': url
,
2089 'archive': self
.helper
.archname
,
2090 'version': self
.helper
.defaultbranch
,
2093 d
= self
.do_vctest_once(False)
2094 d
.addCallback(self
._testRetryFails
_1)
2096 def _testRetryFails_1(self
, bs
):
2097 self
.failUnlessEqual(bs
.getResults(), FAILURE
)
2099 def tearDown2(self
):
2101 self
.fixtimer
.cancel()
2102 # tell tla to get rid of the leftover archive this test leaves in the
2103 # user's 'tla archives' listing. The name of this archive is provided
2104 # by the repository tarball, so the following command must use the
2105 # same name. We could use archive= to set it explicitly, but if you
2106 # change it from the default, then 'tla update' won't work.
2107 d
= self
.helper
.unregisterRepository()
2110 VCS
.registerVC(Bazaar
.vc_name
, BazaarHelper())
2112 class MercurialHelper(BaseHelper
):
2113 branchname
= "branch"
2114 try_branchname
= "branch"
2117 hgpaths
= which("hg")
2119 return (False, "Mercurial is not installed")
2120 self
.vcexe
= hgpaths
[0]
2123 def extract_id(self
, output
):
2124 m
= re
.search(r
'^(\w+)', output
)
2127 def createRepository(self
):
2128 self
.createBasedir()
2129 self
.hg_base
= os
.path
.join(self
.repbase
, "Mercurial-Repository")
2130 self
.rep_trunk
= os
.path
.join(self
.hg_base
, "trunk")
2131 self
.rep_branch
= os
.path
.join(self
.hg_base
, "branch")
2132 tmp
= os
.path
.join(self
.hg_base
, "hgtmp")
2134 os
.makedirs(self
.rep_trunk
)
2135 w
= self
.dovc(self
.rep_trunk
, "init")
2136 yield w
; w
.getResult()
2137 os
.makedirs(self
.rep_branch
)
2138 w
= self
.dovc(self
.rep_branch
, "init")
2139 yield w
; w
.getResult()
2142 w
= self
.dovc(tmp
, "init")
2143 yield w
; w
.getResult()
2144 w
= self
.dovc(tmp
, "add")
2145 yield w
; w
.getResult()
2146 w
= self
.dovc(tmp
, "commit -m initial_import")
2147 yield w
; w
.getResult()
2148 w
= self
.dovc(tmp
, "push %s" % self
.rep_trunk
)
2149 # note that hg-push does not actually update the working directory
2150 yield w
; w
.getResult()
2151 w
= self
.dovc(tmp
, "identify")
2152 yield w
; out
= w
.getResult()
2153 self
.addTrunkRev(self
.extract_id(out
))
2155 self
.populate_branch(tmp
)
2156 w
= self
.dovc(tmp
, "commit -m commit_on_branch")
2157 yield w
; w
.getResult()
2158 w
= self
.dovc(tmp
, "push %s" % self
.rep_branch
)
2159 yield w
; w
.getResult()
2160 w
= self
.dovc(tmp
, "identify")
2161 yield w
; out
= w
.getResult()
2162 self
.addBranchRev(self
.extract_id(out
))
2164 createRepository
= deferredGenerator(createRepository
)
2166 def vc_revise(self
):
2167 tmp
= os
.path
.join(self
.hg_base
, "hgtmp2")
2168 w
= self
.dovc(self
.hg_base
, "clone %s %s" % (self
.rep_trunk
, tmp
))
2169 yield w
; w
.getResult()
2172 version_c
= VERSION_C
% self
.version
2173 version_c_filename
= os
.path
.join(tmp
, "version.c")
2174 open(version_c_filename
, "w").write(version_c
)
2175 # hg uses timestamps to distinguish files which have changed, so we
2176 # force the mtime forward a little bit
2177 future
= time
.time() + 2*self
.version
2178 os
.utime(version_c_filename
, (future
, future
))
2179 w
= self
.dovc(tmp
, "commit -m revised_to_%d" % self
.version
)
2180 yield w
; w
.getResult()
2181 w
= self
.dovc(tmp
, "push %s" % self
.rep_trunk
)
2182 yield w
; w
.getResult()
2183 w
= self
.dovc(tmp
, "identify")
2184 yield w
; out
= w
.getResult()
2185 self
.addTrunkRev(self
.extract_id(out
))
2187 vc_revise
= deferredGenerator(vc_revise
)
2189 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
2190 assert os
.path
.abspath(workdir
) == workdir
2191 if os
.path
.exists(workdir
):
2192 rmdirRecursive(workdir
)
2194 src
= self
.rep_branch
2196 src
= self
.rep_trunk
2197 w
= self
.dovc(self
.hg_base
, "clone %s %s" % (src
, workdir
))
2198 yield w
; w
.getResult()
2199 try_c_filename
= os
.path
.join(workdir
, "subdir", "subdir.c")
2200 open(try_c_filename
, "w").write(TRY_C
)
2201 future
= time
.time() + 2*self
.version
2202 os
.utime(try_c_filename
, (future
, future
))
2203 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
2205 def vc_try_finish(self
, workdir
):
2206 rmdirRecursive(workdir
)
2209 class Mercurial(VCBase
, unittest
.TestCase
):
2212 # Mercurial has a metadir=".hg", but it does not have an 'export' mode.
2214 vctype
= "step.Mercurial"
2216 has_got_revision
= True
2218 def testCheckout(self
):
2219 self
.helper
.vcargs
= { 'repourl': self
.helper
.rep_trunk
}
2220 d
= self
.do_vctest(testRetry
=False)
2222 # TODO: testRetry has the same problem with Mercurial as it does for
2226 def testPatch(self
):
2227 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2228 'defaultBranch': "trunk" }
2232 def testCheckoutBranch(self
):
2233 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2234 'defaultBranch': "trunk" }
2235 d
= self
.do_branch()
2238 def testCheckoutHTTP(self
):
2240 repourl
= "http://localhost:%d/Mercurial-Repository/trunk/.hg" % self
.httpPort
2241 self
.helper
.vcargs
= { 'repourl': repourl
}
2242 d
= self
.do_vctest(testRetry
=False)
2244 # TODO: The easiest way to publish hg over HTTP is by running 'hg serve'
2245 # as a child process while the test is running. (you can also use a CGI
2246 # script, which sounds difficult, or you can publish the files directly,
2247 # which isn't well documented).
2248 testCheckoutHTTP
.skip
= "not yet implemented, use 'hg serve'"
2251 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2252 'defaultBranch': "trunk" }
2253 d
= self
.do_getpatch()
2256 VCS
.registerVC(Mercurial
.vc_name
, MercurialHelper())
2259 class Sources(unittest
.TestCase
):
2260 # TODO: this needs serious rethink
2261 def makeChange(self
, when
=None, revision
=None):
2263 when
= mktime_tz(parsedate_tz(when
))
2264 return changes
.Change("fred", [], "", when
=when
, revision
=revision
)
2267 r
= base
.BuildRequest("forced build", SourceStamp())
2269 s
= step
.CVS(cvsroot
=None, cvsmodule
=None, workdir
=None, build
=b
)
2270 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), None)
2274 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2275 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2276 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2277 r
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2278 submitted
= "Wed, 08 Sep 2004 09:04:00 -0700"
2279 r
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2281 s
= step
.CVS(cvsroot
=None, cvsmodule
=None, workdir
=None, build
=b
)
2282 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
2283 "Wed, 08 Sep 2004 16:03:00 -0000")
2287 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2288 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2289 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2290 r
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2291 submitted
= "Wed, 08 Sep 2004 09:04:00 -0700"
2292 r
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2294 s
= step
.CVS(cvsroot
=None, cvsmodule
=None, workdir
=None, build
=b
,
2296 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
2297 "Wed, 08 Sep 2004 16:02:10 -0000")
2301 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2302 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2303 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2304 r1
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2305 submitted
= "Wed, 08 Sep 2004 09:04:00 -0700"
2306 r1
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2309 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:05:00 -0700"))
2310 r2
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2311 submitted
= "Wed, 08 Sep 2004 09:07:00 -0700"
2312 r2
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2314 b
= base
.Build([r1
, r2
])
2315 s
= step
.CVS(cvsroot
=None, cvsmodule
=None, workdir
=None, build
=b
)
2316 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
2317 "Wed, 08 Sep 2004 16:06:00 -0000")
2320 r
= base
.BuildRequest("forced", SourceStamp())
2322 s
= step
.SVN(svnurl
="dummy", workdir
=None, build
=b
)
2323 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), None)
2327 c
.append(self
.makeChange(revision
=4))
2328 c
.append(self
.makeChange(revision
=10))
2329 c
.append(self
.makeChange(revision
=67))
2330 r
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2332 s
= step
.SVN(svnurl
="dummy", workdir
=None, build
=b
)
2333 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), 67)
2335 class Patch(VCBase
, unittest
.TestCase
):
2342 def testPatch(self
):
2343 # invoke 'patch' all by itself, to see if it works the way we think
2344 # it should. This is intended to ferret out some windows test
2346 helper
= BaseHelper()
2347 self
.workdir
= os
.path
.join("test_vc", "testPatch")
2348 helper
.populate(self
.workdir
)
2349 patch
= which("patch")[0]
2351 command
= [patch
, "-p0"]
2354 def sendUpdate(self
, status
):
2356 c
= commands
.ShellCommand(FakeBuilder(), command
, self
.workdir
,
2357 sendRC
=False, initialStdin
=p0_diff
)
2359 d
.addCallback(self
._testPatch
_1)
2362 def _testPatch_1(self
, res
):
2363 # make sure the file actually got patched
2364 subdir_c
= os
.path
.join(self
.workdir
, "subdir", "subdir.c")
2365 data
= open(subdir_c
, "r").read()
2366 self
.failUnlessIn("Hello patched subdir.\\n", data
)