1 # -*- test-case-name: buildbot.test.test_vc -*-
3 import sys
, os
, time
, re
4 from email
.Utils
import mktime_tz
, parsedate_tz
6 from twisted
.trial
import unittest
7 from twisted
.internet
import defer
, reactor
, utils
, protocol
, task
, error
8 from twisted
.python
import failure
9 from twisted
.python
.procutils
import which
10 from twisted
.web
import client
, static
, server
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
, myGetProcessOutputAndValue
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.
56 from buildbot.process import factory
57 from buildbot.steps import source
58 from buildbot.buildslave import BuildSlave
61 f1 = factory.BuildFactory([
65 c['slaves'] = [BuildSlave('bot1', 'sekrit')]
67 c['builders'] = [{'name': 'vc', 'slavename': 'bot1',
68 'builddir': 'vc-dir', 'factory': f1}]
70 # do not compress logs in tests
71 c['logCompressionLimit'] = False
76 Index: subdir/subdir.c
77 ===================================================================
78 RCS file: /home/warner/stuff/Projects/BuildBot/code-arch/_trial_temp/test_vc/repositories/CVS-Repository/sample/subdir/subdir.c,v
79 retrieving revision 1.1.1.1
80 diff -u -r1.1.1.1 subdir.c
81 --- subdir/subdir.c 14 Aug 2005 01:32:49 -0000 1.1.1.1
82 +++ subdir/subdir.c 14 Aug 2005 01:36:15 -0000
85 main(int argc, const char *argv[])
87 - printf("Hello subdir.\n");
88 + printf("Hello patched subdir.\n");
93 # this patch does not include the filename headers, so it is
98 main(int argc, const char *argv[])
100 - printf("Hello subdir.\\n");
101 + printf("Hello try.\\n");
111 main(int argc, const char *argv[])
113 printf("Hello world.\\n");
123 main(int argc, const char *argv[])
125 printf("Hello branch.\\n");
135 main(int argc, const char *argv[])
137 printf("Hello world, version=%d\\n");
143 // this is subdir/subdir.c
147 main(int argc, const char *argv[])
149 printf("Hello subdir.\\n");
155 // this is subdir/subdir.c
159 main(int argc, const char *argv[])
161 printf("Hello try.\\n");
170 # this is a helper class which keeps track of whether each VC system is
171 # available, and whether the repository for each has been created. There
172 # is one instance of this class, at module level, shared between all test
181 def registerVC(self
, name
, helper
):
182 self
._helpers
[name
] = helper
183 self
._repoReady
[name
] = False
185 def skipIfNotCapable(self
, name
):
186 """Either return None, or raise SkipTest"""
187 d
= self
.capable(name
)
190 raise unittest
.SkipTest(res
[1])
191 d
.addCallback(_maybeSkip
)
194 def capable(self
, name
):
195 """Return a Deferred that fires with (True,None) if this host offers
196 the given VC tool, or (False,excuse) if it does not (and therefore
197 the tests should be skipped)."""
199 if self
._isCapable
.has_key(name
):
200 if self
._isCapable
[name
]:
201 return defer
.succeed((True,None))
203 return defer
.succeed((False, self
._excuses
[name
]))
204 d
= defer
.maybeDeferred(self
._helpers
[name
].capable
)
207 self
._isCapable
[name
] = True
209 self
._excuses
[name
] = res
[1]
211 d
.addCallback(_capable
)
214 def getHelper(self
, name
):
215 return self
._helpers
[name
]
217 def createRepository(self
, name
):
218 """Return a Deferred that fires when the repository is set up."""
219 if self
._repoReady
[name
]:
220 return defer
.succeed(True)
221 d
= self
._helpers
[name
].createRepository()
223 self
._repoReady
[name
] = True
224 d
.addCallback(_ready
)
230 # the overall plan here:
232 # Each VC system is tested separately, all using the same source tree defined
233 # in the 'files' dictionary above. Each VC system gets its own TestCase
234 # subclass. The first test case that is run will create the repository during
235 # setUp(), making two branches: 'trunk' and 'branch'. The trunk gets a copy
236 # of all the files in 'files'. The variant of good.c is committed on the
239 # then testCheckout is run, which does a number of checkout/clobber/update
240 # builds. These all use trunk r1. It then runs self.fix(), which modifies
241 # 'fixable.c', then performs another build and makes sure the tree has been
244 # testBranch uses trunk-r1 and branch-r1, making sure that we clobber the
245 # tree properly when we switch between them
247 # testPatch does a trunk-r1 checkout and applies a patch.
249 # testTryGetPatch performs a trunk-r1 checkout, modifies some files, then
250 # verifies that tryclient.getSourceStamp figures out the base revision and
254 # vc_create makes a repository at r1 with three files: main.c, version.c, and
255 # subdir/foo.c . It also creates a branch from r1 (called b1) in which main.c
256 # says "hello branch" instead of "hello world". self.trunk[] contains
257 # revision stamps for everything on the trunk, and self.branch[] does the
258 # same for the branch.
260 # vc_revise() checks out a tree at HEAD, changes version.c, then checks it
261 # back in. The new version stamp is appended to self.trunk[]. The tree is
262 # removed afterwards.
264 # vc_try_checkout(workdir, rev) checks out a tree at REV, then changes
265 # subdir/subdir.c to say 'Hello try'
266 # vc_try_finish(workdir) removes the tree and cleans up any VC state
267 # necessary (like deleting the Arch archive entry).
277 # this is also responsible for setting self.vcexe
278 raise NotImplementedError
280 def createBasedir(self
):
281 # you must call this from createRepository
282 self
.repbase
= os
.path
.abspath(os
.path
.join("test_vc",
284 if not os
.path
.isdir(self
.repbase
):
285 os
.makedirs(self
.repbase
)
287 def createRepository(self
):
288 # this will only be called once per process
289 raise NotImplementedError
291 def populate(self
, basedir
):
292 if not os
.path
.exists(basedir
):
294 os
.makedirs(os
.path
.join(basedir
, "subdir"))
295 open(os
.path
.join(basedir
, "main.c"), "w").write(MAIN_C
)
297 version_c
= VERSION_C
% self
.version
298 open(os
.path
.join(basedir
, "version.c"), "w").write(version_c
)
299 open(os
.path
.join(basedir
, "main.c"), "w").write(MAIN_C
)
300 open(os
.path
.join(basedir
, "subdir", "subdir.c"), "w").write(SUBDIR_C
)
302 def populate_branch(self
, basedir
):
303 open(os
.path
.join(basedir
, "main.c"), "w").write(BRANCH_C
)
305 def addTrunkRev(self
, rev
):
306 self
.trunk
.append(rev
)
307 self
.allrevs
.append(rev
)
308 def addBranchRev(self
, rev
):
309 self
.branch
.append(rev
)
310 self
.allrevs
.append(rev
)
312 def runCommand(self
, basedir
, command
, failureIsOk
=False,
313 stdin
=None, env
=None):
314 # all commands passed to do() should be strings or lists. If they are
315 # strings, none of the arguments may have spaces. This makes the
316 # commands less verbose at the expense of restricting what they can
318 if type(command
) not in (list, tuple):
319 command
= command
.split(" ")
321 # execute scripts through cmd.exe on windows, to avoid space in path issues
322 if sys
.platform
== 'win32' and command
[0].lower().endswith('.cmd'):
323 command
= [which('cmd.exe')[0], '/c', 'call'] + command
327 print "do %s" % command
328 print " in basedir %s" % basedir
330 print " STDIN:\n", stdin
, "\n--STDIN DONE"
333 env
= os
.environ
.copy()
335 d
= myGetProcessOutputAndValue(command
[0], command
[1:],
336 env
=env
, path
=basedir
,
338 def check((out
, err
, code
)):
341 print "command was: %s" % command
342 if out
: print "out: %s" % out
343 if err
: print "err: %s" % err
344 print "code: %s" % code
345 if code
!= 0 and not failureIsOk
:
346 log
.msg("command %s finished with exit code %d" %
348 log
.msg(" and stdout %s" % (out
,))
349 log
.msg(" and stderr %s" % (err
,))
350 raise RuntimeError("command %s finished with exit code %d"
352 + ": see logs for stdout")
357 def do(self
, basedir
, command
, failureIsOk
=False, stdin
=None, env
=None):
358 d
= self
.runCommand(basedir
, command
, failureIsOk
=failureIsOk
,
359 stdin
=stdin
, env
=env
)
360 return waitForDeferred(d
)
362 def dovc(self
, basedir
, command
, failureIsOk
=False, stdin
=None, env
=None):
363 """Like do(), but the VC binary will be prepended to COMMAND."""
364 if isinstance(command
, (str, unicode)):
365 command
= [self
.vcexe
] + command
.split(' ')
368 command
= [self
.vcexe
] + command
369 return self
.do(basedir
, command
, failureIsOk
, stdin
, env
)
371 class VCBase(SignalMixin
):
373 createdRepository
= False
380 has_got_revision
= False
381 has_got_revision_branches_are_merged
= False # for SVN
383 def failUnlessIn(self
, substring
, string
, msg
=None):
384 # trial provides a version of this that requires python-2.3 to test
387 msg
= ("did not see the expected substring '%s' in string '%s'" %
389 self
.failUnless(string
.find(substring
) != -1, msg
)
392 d
= VCS
.skipIfNotCapable(self
.vc_name
)
393 d
.addCallback(self
._setUp
1)
396 def _setUp1(self
, res
):
397 self
.helper
= VCS
.getHelper(self
.vc_name
)
399 if os
.path
.exists("basedir"):
400 rmdirRecursive("basedir")
402 self
.master
= master
.BuildMaster("basedir")
403 self
.slavebase
= os
.path
.abspath("slavebase")
404 if os
.path
.exists(self
.slavebase
):
405 rmdirRecursive(self
.slavebase
)
406 os
.mkdir("slavebase")
408 d
= VCS
.createRepository(self
.vc_name
)
411 def connectSlave(self
):
412 port
= self
.master
.slavePort
._port
.getHost().port
413 slave
= bot
.BuildSlave("localhost", port
, "bot1", "sekrit",
414 self
.slavebase
, keepalive
=0, usePTY
=False)
417 d
= self
.master
.botmaster
.waitUntilBuilderAttached("vc")
420 def loadConfig(self
, config
):
421 # reloading the config file causes a new 'listDirs' command to be
422 # sent to the slave. To synchronize on this properly, it is easiest
423 # to stop and restart the slave.
424 d
= defer
.succeed(None)
426 d
= self
.master
.botmaster
.waitUntilBuilderDetached("vc")
427 self
.slave
.stopService()
428 d
.addCallback(lambda res
: self
.master
.loadConfig(config
))
429 d
.addCallback(lambda res
: self
.connectSlave())
433 # launch an HTTP server to serve the repository files
434 self
.root
= static
.File(self
.helper
.repbase
)
435 self
.site
= server
.Site(self
.root
)
436 self
.httpServer
= reactor
.listenTCP(0, self
.site
)
437 self
.httpPort
= self
.httpServer
.getHost().port
439 def doBuild(self
, shouldSucceed
=True, ss
=None):
440 c
= interfaces
.IControl(self
.master
)
444 #print "doBuild(ss: b=%s rev=%s)" % (ss.branch, ss.revision)
445 req
= base
.BuildRequest("test_vc forced build", ss
, 'test_builder')
446 d
= req
.waitUntilFinished()
447 c
.getBuilder("vc").requestBuild(req
)
448 d
.addCallback(self
._doBuild
_1, shouldSucceed
)
450 def _doBuild_1(self
, bs
, shouldSucceed
):
452 if r
!= SUCCESS
and shouldSucceed
:
455 if not bs
.isFinished():
456 print "Hey, build wasn't even finished!"
457 print "Build did not succeed:", r
, bs
.getText()
458 for s
in bs
.getSteps():
459 for l
in s
.getLogs():
460 print "--- START step %s / log %s ---" % (s
.getName(),
462 print l
.getTextWithHeaders()
465 self
.fail("build did not succeed")
468 def printLogs(self
, bs
):
469 for s
in bs
.getSteps():
470 for l
in s
.getLogs():
471 print "--- START step %s / log %s ---" % (s
.getName(),
473 print l
.getTextWithHeaders()
477 def touch(self
, d
, f
):
478 open(os
.path
.join(d
,f
),"w").close()
479 def shouldExist(self
, *args
):
480 target
= os
.path
.join(*args
)
481 self
.failUnless(os
.path
.exists(target
),
482 "expected to find %s but didn't" % target
)
483 def shouldNotExist(self
, *args
):
484 target
= os
.path
.join(*args
)
485 self
.failIf(os
.path
.exists(target
),
486 "expected to NOT find %s, but did" % target
)
487 def shouldContain(self
, d
, f
, contents
):
488 c
= open(os
.path
.join(d
, f
), "r").read()
489 self
.failUnlessIn(contents
, c
)
491 def checkGotRevision(self
, bs
, expected
):
492 if self
.has_got_revision
:
493 self
.failUnlessEqual(bs
.getProperty("got_revision"), str(expected
))
495 def checkGotRevisionIsLatest(self
, bs
):
496 expected
= self
.helper
.trunk
[-1]
497 if self
.has_got_revision_branches_are_merged
:
498 expected
= self
.helper
.allrevs
[-1]
499 self
.checkGotRevision(bs
, expected
)
501 def do_vctest(self
, testRetry
=True):
503 args
= self
.helper
.vcargs
505 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
506 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
507 # woo double-substitution
508 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
509 for k
,v
in args
.items():
510 s
+= ", %s=%s" % (k
, repr(v
))
512 config
= config_vc
% s
514 m
.loadConfig(config
% 'clobber')
518 d
= self
.connectSlave()
519 d
.addCallback(lambda res
: log
.msg("testing clobber"))
520 d
.addCallback(self
._do
_vctest
_clobber
)
521 d
.addCallback(lambda res
: log
.msg("doing update"))
522 d
.addCallback(lambda res
: self
.loadConfig(config
% 'update'))
523 d
.addCallback(lambda res
: log
.msg("testing update"))
524 d
.addCallback(self
._do
_vctest
_update
)
526 d
.addCallback(lambda res
: log
.msg("testing update retry"))
527 d
.addCallback(self
._do
_vctest
_update
_retry
)
528 d
.addCallback(lambda res
: log
.msg("doing copy"))
529 d
.addCallback(lambda res
: self
.loadConfig(config
% 'copy'))
530 d
.addCallback(lambda res
: log
.msg("testing copy"))
531 d
.addCallback(self
._do
_vctest
_copy
)
532 d
.addCallback(lambda res
: log
.msg("did copy test"))
534 d
.addCallback(lambda res
: log
.msg("doing export"))
535 d
.addCallback(lambda res
: self
.loadConfig(config
% 'export'))
536 d
.addCallback(lambda res
: log
.msg("testing export"))
537 d
.addCallback(self
._do
_vctest
_export
)
538 d
.addCallback(lambda res
: log
.msg("did export test"))
541 def _do_vctest_clobber(self
, res
):
542 d
= self
.doBuild() # initial checkout
543 d
.addCallback(self
._do
_vctest
_clobber
_1)
545 def _do_vctest_clobber_1(self
, bs
):
546 self
.shouldExist(self
.workdir
, "main.c")
547 self
.shouldExist(self
.workdir
, "version.c")
548 self
.shouldExist(self
.workdir
, "subdir", "subdir.c")
550 self
.shouldExist(self
.workdir
, self
.metadir
)
551 self
.failUnlessEqual(bs
.getProperty("revision"), None)
552 self
.failUnlessEqual(bs
.getProperty("branch"), None)
553 self
.checkGotRevisionIsLatest(bs
)
555 self
.touch(self
.workdir
, "newfile")
556 self
.shouldExist(self
.workdir
, "newfile")
557 d
= self
.doBuild() # rebuild clobbers workdir
558 d
.addCallback(self
._do
_vctest
_clobber
_2)
560 def _do_vctest_clobber_2(self
, res
):
561 self
.shouldNotExist(self
.workdir
, "newfile")
562 # do a checkout to a specific version. Mercurial-over-HTTP (when
563 # either client or server is older than hg-0.9.2) cannot do this
564 # directly, so it must checkout HEAD and then update back to the
565 # requested revision.
566 d
= self
.doBuild(ss
=SourceStamp(revision
=self
.helper
.trunk
[0]))
567 d
.addCallback(self
._do
_vctest
_clobber
_3)
569 def _do_vctest_clobber_3(self
, bs
):
570 self
.shouldExist(self
.workdir
, "main.c")
571 self
.shouldExist(self
.workdir
, "version.c")
572 self
.shouldExist(self
.workdir
, "subdir", "subdir.c")
574 self
.shouldExist(self
.workdir
, self
.metadir
)
575 self
.failUnlessEqual(bs
.getProperty("revision"), self
.helper
.trunk
[0] or None)
576 self
.failUnlessEqual(bs
.getProperty("branch"), None)
577 self
.checkGotRevision(bs
, self
.helper
.trunk
[0])
578 # leave the tree at HEAD
579 return self
.doBuild()
582 def _do_vctest_update(self
, res
):
583 log
.msg("_do_vctest_update")
584 d
= self
.doBuild() # rebuild with update
585 d
.addCallback(self
._do
_vctest
_update
_1)
587 def _do_vctest_update_1(self
, bs
):
588 log
.msg("_do_vctest_update_1")
589 self
.shouldExist(self
.workdir
, "main.c")
590 self
.shouldExist(self
.workdir
, "version.c")
591 self
.shouldContain(self
.workdir
, "version.c",
592 "version=%d" % self
.helper
.version
)
594 self
.shouldExist(self
.workdir
, self
.metadir
)
595 self
.failUnlessEqual(bs
.getProperty("revision"), None)
596 self
.checkGotRevisionIsLatest(bs
)
598 self
.touch(self
.workdir
, "newfile")
599 d
= self
.doBuild() # update rebuild leaves new files
600 d
.addCallback(self
._do
_vctest
_update
_2)
602 def _do_vctest_update_2(self
, bs
):
603 log
.msg("_do_vctest_update_2")
604 self
.shouldExist(self
.workdir
, "main.c")
605 self
.shouldExist(self
.workdir
, "version.c")
606 self
.touch(self
.workdir
, "newfile")
607 # now make a change to the repository and make sure we pick it up
608 d
= self
.helper
.vc_revise()
609 d
.addCallback(lambda res
: self
.doBuild())
610 d
.addCallback(self
._do
_vctest
_update
_3)
612 def _do_vctest_update_3(self
, bs
):
613 log
.msg("_do_vctest_update_3")
614 self
.shouldExist(self
.workdir
, "main.c")
615 self
.shouldExist(self
.workdir
, "version.c")
616 self
.shouldContain(self
.workdir
, "version.c",
617 "version=%d" % self
.helper
.version
)
618 self
.shouldExist(self
.workdir
, "newfile")
619 self
.failUnlessEqual(bs
.getProperty("revision"), None)
620 self
.checkGotRevisionIsLatest(bs
)
622 # now "update" to an older revision
623 d
= self
.doBuild(ss
=SourceStamp(revision
=self
.helper
.trunk
[-2]))
624 d
.addCallback(self
._do
_vctest
_update
_4)
626 def _do_vctest_update_4(self
, bs
):
627 log
.msg("_do_vctest_update_4")
628 self
.shouldExist(self
.workdir
, "main.c")
629 self
.shouldExist(self
.workdir
, "version.c")
630 self
.shouldContain(self
.workdir
, "version.c",
631 "version=%d" % (self
.helper
.version
-1))
632 self
.failUnlessEqual(bs
.getProperty("revision"),
633 self
.helper
.trunk
[-2] or None)
634 self
.checkGotRevision(bs
, self
.helper
.trunk
[-2])
636 # now update to the newer revision
637 d
= self
.doBuild(ss
=SourceStamp(revision
=self
.helper
.trunk
[-1]))
638 d
.addCallback(self
._do
_vctest
_update
_5)
640 def _do_vctest_update_5(self
, bs
):
641 log
.msg("_do_vctest_update_5")
642 self
.shouldExist(self
.workdir
, "main.c")
643 self
.shouldExist(self
.workdir
, "version.c")
644 self
.shouldContain(self
.workdir
, "version.c",
645 "version=%d" % self
.helper
.version
)
646 self
.failUnlessEqual(bs
.getProperty("revision"),
647 self
.helper
.trunk
[-1] or None)
648 self
.checkGotRevision(bs
, self
.helper
.trunk
[-1])
651 def _do_vctest_update_retry(self
, res
):
652 # certain local changes will prevent an update from working. The
653 # most common is to replace a file with a directory, or vice
654 # versa. The slave code should spot the failure and do a
656 os
.unlink(os
.path
.join(self
.workdir
, "main.c"))
657 os
.mkdir(os
.path
.join(self
.workdir
, "main.c"))
658 self
.touch(os
.path
.join(self
.workdir
, "main.c"), "foo")
659 self
.touch(self
.workdir
, "newfile")
661 d
= self
.doBuild() # update, but must clobber to handle the error
662 d
.addCallback(self
._do
_vctest
_update
_retry
_1)
664 def _do_vctest_update_retry_1(self
, bs
):
665 # SVN-1.4.0 doesn't seem to have any problem with the
666 # file-turned-directory issue (although older versions did). So don't
667 # actually check that the tree was clobbered.. as long as the update
668 # succeeded (checked by doBuild), that should be good enough.
669 #self.shouldNotExist(self.workdir, "newfile")
672 def _do_vctest_copy(self
, res
):
673 log
.msg("_do_vctest_copy 1")
674 d
= self
.doBuild() # copy rebuild clobbers new files
675 d
.addCallback(self
._do
_vctest
_copy
_1)
677 def _do_vctest_copy_1(self
, bs
):
678 log
.msg("_do_vctest_copy 2")
680 self
.shouldExist(self
.workdir
, self
.metadir
)
681 self
.shouldNotExist(self
.workdir
, "newfile")
682 self
.touch(self
.workdir
, "newfile")
683 self
.touch(self
.vcdir
, "newvcfile")
684 self
.failUnlessEqual(bs
.getProperty("revision"), None)
685 self
.checkGotRevisionIsLatest(bs
)
687 d
= self
.doBuild() # copy rebuild clobbers new files
688 d
.addCallback(self
._do
_vctest
_copy
_2)
690 def _do_vctest_copy_2(self
, bs
):
691 log
.msg("_do_vctest_copy 3")
693 self
.shouldExist(self
.workdir
, self
.metadir
)
694 self
.shouldNotExist(self
.workdir
, "newfile")
695 self
.shouldExist(self
.vcdir
, "newvcfile")
696 self
.shouldExist(self
.workdir
, "newvcfile")
697 self
.failUnlessEqual(bs
.getProperty("revision"), None)
698 self
.checkGotRevisionIsLatest(bs
)
699 self
.touch(self
.workdir
, "newfile")
701 def _do_vctest_export(self
, res
):
702 d
= self
.doBuild() # export rebuild clobbers new files
703 d
.addCallback(self
._do
_vctest
_export
_1)
705 def _do_vctest_export_1(self
, bs
):
706 self
.shouldNotExist(self
.workdir
, self
.metadir
)
707 self
.shouldNotExist(self
.workdir
, "newfile")
708 self
.failUnlessEqual(bs
.getProperty("revision"), None)
709 #self.checkGotRevisionIsLatest(bs)
710 # VC 'export' is not required to have a got_revision
711 self
.touch(self
.workdir
, "newfile")
713 d
= self
.doBuild() # export rebuild clobbers new files
714 d
.addCallback(self
._do
_vctest
_export
_2)
716 def _do_vctest_export_2(self
, bs
):
717 self
.shouldNotExist(self
.workdir
, self
.metadir
)
718 self
.shouldNotExist(self
.workdir
, "newfile")
719 self
.failUnlessEqual(bs
.getProperty("revision"), None)
720 #self.checkGotRevisionIsLatest(bs)
721 # VC 'export' is not required to have a got_revision
725 args
= self
.helper
.vcargs
727 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
728 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
729 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
730 for k
,v
in args
.items():
731 s
+= ", %s=%s" % (k
, repr(v
))
733 self
.config
= config_vc
% s
735 m
.loadConfig(self
.config
% "clobber")
739 ss
= SourceStamp(revision
=self
.helper
.trunk
[-1], patch
=(0, p0_diff
))
741 d
= self
.connectSlave()
742 d
.addCallback(lambda res
: self
.doBuild(ss
=ss
))
743 d
.addCallback(self
._doPatch
_1)
745 def _doPatch_1(self
, bs
):
746 self
.shouldContain(self
.workdir
, "version.c",
747 "version=%d" % self
.helper
.version
)
748 # make sure the file actually got patched
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 patched subdir.\\n", data
)
753 self
.failUnlessEqual(bs
.getProperty("revision"),
754 self
.helper
.trunk
[-1] or None)
755 self
.checkGotRevision(bs
, self
.helper
.trunk
[-1])
757 # make sure that a rebuild does not use the leftover patched workdir
758 d
= self
.master
.loadConfig(self
.config
% "update")
759 d
.addCallback(lambda res
: self
.doBuild(ss
=None))
760 d
.addCallback(self
._doPatch
_2)
762 def _doPatch_2(self
, bs
):
763 # make sure the file is back to its original
764 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
765 "subdir", "subdir.c")
766 data
= open(subdir_c
, "r").read()
767 self
.failUnlessIn("Hello subdir.\\n", data
)
768 self
.failUnlessEqual(bs
.getProperty("revision"), None)
769 self
.checkGotRevisionIsLatest(bs
)
771 # now make sure we can patch an older revision. We need at least two
772 # revisions here, so we might have to create one first
773 if len(self
.helper
.trunk
) < 2:
774 d
= self
.helper
.vc_revise()
775 d
.addCallback(self
._doPatch
_3)
777 return self
._doPatch
_3()
779 def _doPatch_3(self
, res
=None):
780 ss
= SourceStamp(revision
=self
.helper
.trunk
[-2], patch
=(0, p0_diff
))
781 d
= self
.doBuild(ss
=ss
)
782 d
.addCallback(self
._doPatch
_4)
784 def _doPatch_4(self
, bs
):
785 self
.shouldContain(self
.workdir
, "version.c",
786 "version=%d" % (self
.helper
.version
-1))
787 # and make sure the file actually got patched
788 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
789 "subdir", "subdir.c")
790 data
= open(subdir_c
, "r").read()
791 self
.failUnlessIn("Hello patched subdir.\\n", data
)
792 self
.failUnlessEqual(bs
.getProperty("revision"),
793 self
.helper
.trunk
[-2] or None)
794 self
.checkGotRevision(bs
, self
.helper
.trunk
[-2])
796 # now check that we can patch a branch
797 ss
= SourceStamp(branch
=self
.helper
.branchname
,
798 revision
=self
.helper
.branch
[-1],
800 d
= self
.doBuild(ss
=ss
)
801 d
.addCallback(self
._doPatch
_5)
803 def _doPatch_5(self
, bs
):
804 self
.shouldContain(self
.workdir
, "version.c",
806 self
.shouldContain(self
.workdir
, "main.c", "Hello branch.")
807 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
808 "subdir", "subdir.c")
809 data
= open(subdir_c
, "r").read()
810 self
.failUnlessIn("Hello patched subdir.\\n", data
)
811 self
.failUnlessEqual(bs
.getProperty("revision"),
812 self
.helper
.branch
[-1] or None)
813 self
.failUnlessEqual(bs
.getProperty("branch"), self
.helper
.branchname
or None)
814 self
.checkGotRevision(bs
, self
.helper
.branch
[-1])
817 def do_vctest_once(self
, shouldSucceed
):
820 args
= self
.helper
.vcargs
821 vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
822 workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
823 # woo double-substitution
824 s
= "s(%s, timeout=200, workdir='build', mode='clobber'" % (vctype
,)
825 for k
,v
in args
.items():
826 s
+= ", %s=%s" % (k
, repr(v
))
828 config
= config_vc
% s
835 d
= self
.doBuild(shouldSucceed
) # initial checkout
841 args
= self
.helper
.vcargs
843 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
844 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
845 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
846 for k
,v
in args
.items():
847 s
+= ", %s=%s" % (k
, repr(v
))
849 self
.config
= config_vc
% s
851 m
.loadConfig(self
.config
% "update")
855 # first we do a build of the trunk
856 d
= self
.connectSlave()
857 d
.addCallback(lambda res
: self
.doBuild(ss
=SourceStamp()))
858 d
.addCallback(self
._doBranch
_1)
860 def _doBranch_1(self
, bs
):
861 log
.msg("_doBranch_1")
862 # make sure the checkout was of the trunk
863 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
864 data
= open(main_c
, "r").read()
865 self
.failUnlessIn("Hello world.", data
)
867 # now do a checkout on the branch. The change in branch name should
869 self
.touch(self
.workdir
, "newfile")
870 d
= self
.doBuild(ss
=SourceStamp(branch
=self
.helper
.branchname
))
871 d
.addCallback(self
._doBranch
_2)
873 def _doBranch_2(self
, bs
):
874 log
.msg("_doBranch_2")
875 # make sure it was on the branch
876 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
877 data
= open(main_c
, "r").read()
878 self
.failUnlessIn("Hello branch.", data
)
879 # and make sure the tree was clobbered
880 self
.shouldNotExist(self
.workdir
, "newfile")
882 # doing another build on the same branch should not clobber the tree
883 self
.touch(self
.workdir
, "newbranchfile")
884 d
= self
.doBuild(ss
=SourceStamp(branch
=self
.helper
.branchname
))
885 d
.addCallback(self
._doBranch
_3)
887 def _doBranch_3(self
, bs
):
888 log
.msg("_doBranch_3")
889 # make sure it is still on the branch
890 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
891 data
= open(main_c
, "r").read()
892 self
.failUnlessIn("Hello branch.", data
)
893 # and make sure the tree was not clobbered
894 self
.shouldExist(self
.workdir
, "newbranchfile")
896 # now make sure that a non-branch checkout clobbers the tree
897 d
= self
.doBuild(ss
=SourceStamp())
898 d
.addCallback(self
._doBranch
_4)
900 def _doBranch_4(self
, bs
):
901 log
.msg("_doBranch_4")
902 # make sure it was on the trunk
903 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
904 data
= open(main_c
, "r").read()
905 self
.failUnlessIn("Hello world.", data
)
906 self
.shouldNotExist(self
.workdir
, "newbranchfile")
908 def do_getpatch(self
, doBranch
=True):
909 log
.msg("do_getpatch")
910 # prepare a buildslave to do checkouts
912 args
= self
.helper
.vcargs
914 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
915 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
916 # woo double-substitution
917 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
918 for k
,v
in args
.items():
919 s
+= ", %s=%s" % (k
, repr(v
))
921 config
= config_vc
% s
923 m
.loadConfig(config
% 'clobber')
927 d
= self
.connectSlave()
929 # then set up the "developer's tree". first we modify a tree from the
931 tmpdir
= "try_workdir"
932 self
.trydir
= os
.path
.join(self
.helper
.repbase
, tmpdir
)
933 rmdirRecursive(self
.trydir
)
934 d
.addCallback(self
.do_getpatch_trunkhead
)
935 d
.addCallback(self
.do_getpatch_trunkold
)
937 d
.addCallback(self
.do_getpatch_branch
)
938 d
.addCallback(self
.do_getpatch_finish
)
941 def do_getpatch_finish(self
, res
):
942 log
.msg("do_getpatch_finish")
943 self
.helper
.vc_try_finish(self
.trydir
)
946 def try_shouldMatch(self
, filename
):
947 devfilename
= os
.path
.join(self
.trydir
, filename
)
948 devfile
= open(devfilename
, "r").read()
949 slavefilename
= os
.path
.join(self
.workdir
, filename
)
950 slavefile
= open(slavefilename
, "r").read()
951 self
.failUnlessEqual(devfile
, slavefile
,
952 ("slavefile (%s) contains '%s'. "
953 "developer's file (%s) contains '%s'. "
954 "These ought to match") %
955 (slavefilename
, slavefile
,
956 devfilename
, devfile
))
958 def do_getpatch_trunkhead(self
, res
):
959 log
.msg("do_getpatch_trunkhead")
960 d
= self
.helper
.vc_try_checkout(self
.trydir
, self
.helper
.trunk
[-1])
961 d
.addCallback(self
._do
_getpatch
_trunkhead
_1)
963 def _do_getpatch_trunkhead_1(self
, res
):
964 log
.msg("_do_getpatch_trunkhead_1")
965 d
= tryclient
.getSourceStamp(self
.vctype_try
, self
.trydir
, None)
966 d
.addCallback(self
._do
_getpatch
_trunkhead
_2)
968 def _do_getpatch_trunkhead_2(self
, ss
):
969 log
.msg("_do_getpatch_trunkhead_2")
970 d
= self
.doBuild(ss
=ss
)
971 d
.addCallback(self
._do
_getpatch
_trunkhead
_3)
973 def _do_getpatch_trunkhead_3(self
, res
):
974 log
.msg("_do_getpatch_trunkhead_3")
975 # verify that the resulting buildslave tree matches the developer's
976 self
.try_shouldMatch("main.c")
977 self
.try_shouldMatch("version.c")
978 self
.try_shouldMatch(os
.path
.join("subdir", "subdir.c"))
980 def do_getpatch_trunkold(self
, res
):
981 log
.msg("do_getpatch_trunkold")
982 # now try a tree from an older revision. We need at least two
983 # revisions here, so we might have to create one first
984 if len(self
.helper
.trunk
) < 2:
985 d
= self
.helper
.vc_revise()
986 d
.addCallback(self
._do
_getpatch
_trunkold
_1)
988 return self
._do
_getpatch
_trunkold
_1()
989 def _do_getpatch_trunkold_1(self
, res
=None):
990 log
.msg("_do_getpatch_trunkold_1")
991 d
= self
.helper
.vc_try_checkout(self
.trydir
, self
.helper
.trunk
[-2])
992 d
.addCallback(self
._do
_getpatch
_trunkold
_2)
994 def _do_getpatch_trunkold_2(self
, res
):
995 log
.msg("_do_getpatch_trunkold_2")
996 d
= tryclient
.getSourceStamp(self
.vctype_try
, self
.trydir
, None)
997 d
.addCallback(self
._do
_getpatch
_trunkold
_3)
999 def _do_getpatch_trunkold_3(self
, ss
):
1000 log
.msg("_do_getpatch_trunkold_3")
1001 d
= self
.doBuild(ss
=ss
)
1002 d
.addCallback(self
._do
_getpatch
_trunkold
_4)
1004 def _do_getpatch_trunkold_4(self
, res
):
1005 log
.msg("_do_getpatch_trunkold_4")
1006 # verify that the resulting buildslave tree matches the developer's
1007 self
.try_shouldMatch("main.c")
1008 self
.try_shouldMatch("version.c")
1009 self
.try_shouldMatch(os
.path
.join("subdir", "subdir.c"))
1011 def do_getpatch_branch(self
, res
):
1012 log
.msg("do_getpatch_branch")
1013 # now try a tree from a branch
1014 d
= self
.helper
.vc_try_checkout(self
.trydir
, self
.helper
.branch
[-1],
1015 self
.helper
.branchname
)
1016 d
.addCallback(self
._do
_getpatch
_branch
_1)
1018 def _do_getpatch_branch_1(self
, res
):
1019 log
.msg("_do_getpatch_branch_1")
1020 d
= tryclient
.getSourceStamp(self
.vctype_try
, self
.trydir
,
1021 self
.helper
.try_branchname
)
1022 d
.addCallback(self
._do
_getpatch
_branch
_2)
1024 def _do_getpatch_branch_2(self
, ss
):
1025 log
.msg("_do_getpatch_branch_2")
1026 d
= self
.doBuild(ss
=ss
)
1027 d
.addCallback(self
._do
_getpatch
_branch
_3)
1029 def _do_getpatch_branch_3(self
, res
):
1030 log
.msg("_do_getpatch_branch_3")
1031 # verify that the resulting buildslave tree matches the developer's
1032 self
.try_shouldMatch("main.c")
1033 self
.try_shouldMatch("version.c")
1034 self
.try_shouldMatch(os
.path
.join("subdir", "subdir.c"))
1037 def dumpPatch(self
, patch
):
1038 # this exists to help me figure out the right 'patchlevel' value
1039 # should be returned by tryclient.getSourceStamp
1041 open(n
,"w").write(patch
)
1042 d
= self
.runCommand(".", ["lsdiff", n
])
1043 def p(res
): print "lsdiff:", res
.strip().split("\n")
1049 d
= defer
.succeed(None)
1051 d2
= self
.master
.botmaster
.waitUntilBuilderDetached("vc")
1052 d
.addCallback(lambda res
: self
.slave
.stopService())
1053 d
.addCallback(lambda res
: d2
)
1055 d
.addCallback(lambda res
: self
.master
.stopService())
1057 d
.addCallback(lambda res
: self
.httpServer
.stopListening())
1058 def stopHTTPTimer():
1059 from twisted
.web
import http
1060 http
._logDateTimeStop
() # shut down the internal timer. DUMB!
1061 d
.addCallback(lambda res
: stopHTTPTimer())
1062 d
.addCallback(lambda res
: self
.tearDown2())
1065 def tearDown2(self
):
1068 class CVSHelper(BaseHelper
):
1069 branchname
= "branch"
1070 try_branchname
= "branch"
1073 cvspaths
= which('cvs')
1075 return (False, "CVS is not installed")
1076 # cvs-1.10 (as shipped with OS-X 10.3 "Panther") is too old for this
1077 # test. There is a situation where we check out a tree, make a
1078 # change, then commit it back, and CVS refuses to believe that we're
1079 # operating in a CVS tree. I tested cvs-1.12.9 and it works ok, OS-X
1080 # 10.4 "Tiger" comes with cvs-1.11, but I haven't tested that yet.
1081 # For now, skip the tests if we've got 1.10 .
1082 log
.msg("running %s --version.." % (cvspaths
[0],))
1083 d
= utils
.getProcessOutput(cvspaths
[0], ["--version"],
1085 d
.addCallback(self
._capable
, cvspaths
[0])
1088 def _capable(self
, v
, vcexe
):
1089 m
= re
.search(r
'\(CVS\) ([\d\.]+) ', v
)
1091 log
.msg("couldn't identify CVS version number in output:")
1092 log
.msg("'''%s'''" % v
)
1093 log
.msg("skipping tests")
1094 return (False, "Found CVS but couldn't identify its version")
1096 log
.msg("found CVS version '%s'" % ver
)
1098 return (False, "Found CVS, but it is too old")
1103 # this timestamp is eventually passed to CVS in a -D argument, and
1104 # strftime's %z specifier doesn't seem to work reliably (I get +0000
1105 # where I should get +0700 under linux sometimes, and windows seems
1106 # to want to put a verbose 'Eastern Standard Time' in there), so
1107 # leave off the timezone specifier and treat this as localtime. A
1108 # valid alternative would be to use a hard-coded +0000 and
1110 return time
.strftime("%Y-%m-%d %H:%M:%S", time
.localtime())
1112 def createRepository(self
):
1113 self
.createBasedir()
1114 self
.cvsrep
= cvsrep
= os
.path
.join(self
.repbase
, "CVS-Repository")
1115 tmp
= os
.path
.join(self
.repbase
, "cvstmp")
1117 w
= self
.dovc(self
.repbase
, ['-d', cvsrep
, 'init'])
1118 yield w
; w
.getResult() # we must getResult() to raise any exceptions
1121 cmd
= ['-d', self
.cvsrep
, 'import',
1122 '-m', 'sample_project_files', 'sample', 'vendortag', 'start']
1123 w
= self
.dovc(tmp
, cmd
)
1124 yield w
; w
.getResult()
1126 # take a timestamp as the first revision number
1128 self
.addTrunkRev(self
.getdate())
1131 w
= self
.dovc(self
.repbase
,
1132 ['-d', self
.cvsrep
, 'checkout', '-d', 'cvstmp', 'sample'])
1133 yield w
; w
.getResult()
1135 w
= self
.dovc(tmp
, ['tag', '-b', self
.branchname
])
1136 yield w
; w
.getResult()
1137 self
.populate_branch(tmp
)
1139 ['commit', '-m', 'commit_on_branch', '-r', self
.branchname
])
1140 yield w
; w
.getResult()
1143 self
.addBranchRev(self
.getdate())
1145 self
.vcargs
= { 'cvsroot': self
.cvsrep
, 'cvsmodule': "sample" }
1146 createRepository
= deferredGenerator(createRepository
)
1149 def vc_revise(self
):
1150 tmp
= os
.path
.join(self
.repbase
, "cvstmp")
1152 w
= self
.dovc(self
.repbase
,
1153 ['-d', self
.cvsrep
, 'checkout', '-d', 'cvstmp', 'sample'])
1154 yield w
; w
.getResult()
1156 version_c
= VERSION_C
% self
.version
1157 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1159 ['commit', '-m', 'revised_to_%d' % self
.version
, 'version.c'])
1160 yield w
; w
.getResult()
1163 self
.addTrunkRev(self
.getdate())
1165 vc_revise
= deferredGenerator(vc_revise
)
1167 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1168 # 'workdir' is an absolute path
1169 assert os
.path
.abspath(workdir
) == workdir
1170 cmd
= [self
.vcexe
, "-d", self
.cvsrep
, "checkout",
1173 if branch
is not None:
1176 cmd
.append("sample")
1177 w
= self
.do(self
.repbase
, cmd
)
1178 yield w
; w
.getResult()
1179 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1180 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1182 def vc_try_finish(self
, workdir
):
1183 rmdirRecursive(workdir
)
1185 class CVS(VCBase
, unittest
.TestCase
):
1189 vctype
= "source.CVS"
1191 # CVS gives us got_revision, but it is based entirely upon the local
1192 # clock, which means it is unlikely to match the timestamp taken earlier.
1193 # This might be enough for common use, but won't be good enough for our
1194 # tests to accept, so pretend it doesn't have got_revision at all.
1195 has_got_revision
= False
1197 def testCheckout(self
):
1198 d
= self
.do_vctest()
1201 def testPatch(self
):
1205 def testCheckoutBranch(self
):
1206 d
= self
.do_branch()
1210 d
= self
.do_getpatch(doBranch
=False)
1213 VCS
.registerVC(CVS
.vc_name
, CVSHelper())
1216 class SVNHelper(BaseHelper
):
1217 branchname
= "sample/branch"
1218 try_branchname
= "sample/branch"
1221 svnpaths
= which('svn')
1222 svnadminpaths
= which('svnadmin')
1224 return (False, "SVN is not installed")
1225 if not svnadminpaths
:
1226 return (False, "svnadmin is not installed")
1227 # we need svn to be compiled with the ra_local access
1229 log
.msg("running svn --version..")
1230 env
= os
.environ
.copy()
1232 d
= utils
.getProcessOutput(svnpaths
[0], ["--version"],
1234 d
.addCallback(self
._capable
, svnpaths
[0], svnadminpaths
[0])
1237 def _capable(self
, v
, vcexe
, svnadmin
):
1238 if v
.find("handles 'file' schem") != -1:
1239 # older versions say 'schema', 1.2.0 and beyond say 'scheme'
1241 self
.svnadmin
= svnadmin
1243 excuse
= ("%s found but it does not support 'file:' " +
1244 "schema, skipping svn tests") % vcexe
1246 return (False, excuse
)
1248 def createRepository(self
):
1249 self
.createBasedir()
1250 self
.svnrep
= os
.path
.join(self
.repbase
,
1251 "SVN-Repository").replace('\\','/')
1252 tmp
= os
.path
.join(self
.repbase
, "svntmp")
1253 if sys
.platform
== 'win32':
1254 # On Windows Paths do not start with a /
1255 self
.svnurl
= "file:///%s" % self
.svnrep
1257 self
.svnurl
= "file://%s" % self
.svnrep
1258 self
.svnurl_trunk
= self
.svnurl
+ "/sample/trunk"
1259 self
.svnurl_branch
= self
.svnurl
+ "/sample/branch"
1261 w
= self
.do(self
.repbase
, [self
.svnadmin
, "create", self
.svnrep
])
1262 yield w
; w
.getResult()
1266 ['import', '-m', 'sample_project_files', self
.svnurl_trunk
])
1267 yield w
; out
= w
.getResult()
1269 m
= re
.search(r
'Committed revision (\d+)\.', out
)
1270 assert m
.group(1) == "1" # first revision is always "1"
1271 self
.addTrunkRev(int(m
.group(1)))
1273 w
= self
.dovc(self
.repbase
,
1274 ['checkout', self
.svnurl_trunk
, 'svntmp'])
1275 yield w
; w
.getResult()
1277 w
= self
.dovc(tmp
, ['cp', '-m' , 'make_branch', self
.svnurl_trunk
,
1278 self
.svnurl_branch
])
1279 yield w
; w
.getResult()
1280 w
= self
.dovc(tmp
, ['switch', self
.svnurl_branch
])
1281 yield w
; w
.getResult()
1282 self
.populate_branch(tmp
)
1283 w
= self
.dovc(tmp
, ['commit', '-m', 'commit_on_branch'])
1284 yield w
; out
= w
.getResult()
1286 m
= re
.search(r
'Committed revision (\d+)\.', out
)
1287 self
.addBranchRev(int(m
.group(1)))
1288 createRepository
= deferredGenerator(createRepository
)
1290 def vc_revise(self
):
1291 tmp
= os
.path
.join(self
.repbase
, "svntmp")
1293 log
.msg("vc_revise" + self
.svnurl_trunk
)
1294 w
= self
.dovc(self
.repbase
,
1295 ['checkout', self
.svnurl_trunk
, 'svntmp'])
1296 yield w
; w
.getResult()
1298 version_c
= VERSION_C
% self
.version
1299 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1300 w
= self
.dovc(tmp
, ['commit', '-m', 'revised_to_%d' % self
.version
])
1301 yield w
; out
= w
.getResult()
1302 m
= re
.search(r
'Committed revision (\d+)\.', out
)
1303 self
.addTrunkRev(int(m
.group(1)))
1305 vc_revise
= deferredGenerator(vc_revise
)
1307 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1308 assert os
.path
.abspath(workdir
) == workdir
1309 if os
.path
.exists(workdir
):
1310 rmdirRecursive(workdir
)
1312 svnurl
= self
.svnurl_trunk
1314 # N.B.: this is *not* os.path.join: SVN URLs use slashes
1315 # regardless of the host operating system's filepath separator
1316 svnurl
= self
.svnurl
+ "/" + branch
1317 w
= self
.dovc(self
.repbase
,
1318 ['checkout', svnurl
, workdir
])
1319 yield w
; w
.getResult()
1320 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1321 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1323 def vc_try_finish(self
, workdir
):
1324 rmdirRecursive(workdir
)
1327 class SVN(VCBase
, unittest
.TestCase
):
1331 vctype
= "source.SVN"
1333 has_got_revision
= True
1334 has_got_revision_branches_are_merged
= True
1336 def testCheckout(self
):
1337 # we verify this one with the svnurl style of vcargs. We test the
1338 # baseURL/defaultBranch style in testPatch and testCheckoutBranch.
1339 self
.helper
.vcargs
= { 'svnurl': self
.helper
.svnurl_trunk
}
1340 d
= self
.do_vctest()
1343 def testPatch(self
):
1344 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1345 'defaultBranch': "sample/trunk",
1350 def testCheckoutBranch(self
):
1351 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1352 'defaultBranch': "sample/trunk",
1354 d
= self
.do_branch()
1358 # extract the base revision and patch from a modified tree, use it to
1359 # create the same contents on the buildslave
1360 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1361 'defaultBranch': "sample/trunk",
1363 d
= self
.do_getpatch()
1366 ## can't test the username= and password= options, because we do not have an
1367 ## svn repository that requires authentication.
1369 VCS
.registerVC(SVN
.vc_name
, SVNHelper())
1372 class P4Helper(BaseHelper
):
1373 branchname
= "branch"
1374 p4port
= 'localhost:1666'
1376 base_descr
= 'Change: new\nDescription: asdf\nFiles:\n'
1379 p4paths
= which('p4')
1380 p4dpaths
= which('p4d')
1382 return (False, "p4 is not installed")
1384 return (False, "p4d is not installed")
1385 self
.vcexe
= p4paths
[0]
1386 self
.p4dexe
= p4dpaths
[0]
1389 class _P4DProtocol(protocol
.ProcessProtocol
):
1391 self
.started
= defer
.Deferred()
1392 self
.ended
= defer
.Deferred()
1394 def outReceived(self
, data
):
1395 # When it says starting, it has bound to the socket.
1398 # Make sure p4d has started. Newer versions of p4d
1399 # have more verbose messaging when db files don't exist, so
1400 # we use re.search instead of startswith.
1402 if re
.search('Perforce Server starting...', data
):
1403 self
.started
.callback(None)
1405 print "p4d said %r" % data
1407 raise Exception('p4d said %r' % data
)
1409 self
.started
.errback(failure
.Failure())
1412 def errReceived(self
, data
):
1413 print "p4d stderr: %s" % data
1415 def processEnded(self
, status_object
):
1416 if status_object
.check(error
.ProcessDone
):
1417 self
.ended
.callback(None)
1419 self
.ended
.errback(status_object
)
1421 def _start_p4d(self
):
1422 proto
= self
._P
4DProtocol
()
1423 reactor
.spawnProcess(proto
, self
.p4dexe
, ['p4d', '-p', self
.p4port
],
1424 env
=os
.environ
, path
=self
.p4rep
)
1425 return proto
.started
, proto
.ended
1427 def dop4(self
, basedir
, command
, failureIsOk
=False, stdin
=None):
1428 # p4 looks at $PWD instead of getcwd(), which causes confusion when
1429 # we spawn commands without an intervening shell (sh -c). We can
1430 # override this with a -d argument.
1431 command
= "-p %s -d %s %s" % (self
.p4port
, basedir
, command
)
1432 return self
.dovc(basedir
, command
, failureIsOk
, stdin
)
1434 def createRepository(self
):
1435 # this is only called once per VC system, so start p4d here.
1437 self
.createBasedir()
1438 tmp
= os
.path
.join(self
.repbase
, "p4tmp")
1439 self
.p4rep
= os
.path
.join(self
.repbase
, 'P4-Repository')
1440 os
.mkdir(self
.p4rep
)
1443 started
, self
.p4d_shutdown
= self
._start
_p
4d
()
1444 w
= waitForDeferred(started
)
1445 yield w
; w
.getResult()
1447 # Create client spec.
1449 clispec
= 'Client: creator\n'
1450 clispec
+= 'Root: %s\n' % tmp
1451 clispec
+= 'View:\n'
1452 clispec
+= '\t//depot/... //creator/...\n'
1453 w
= self
.dop4(tmp
, 'client -i', stdin
=clispec
)
1454 yield w
; w
.getResult()
1456 # Create first rev (trunk).
1457 self
.populate(os
.path
.join(tmp
, 'trunk'))
1458 files
= ['main.c', 'version.c', 'subdir/subdir.c']
1459 w
= self
.dop4(tmp
, "-c creator add "
1460 + " ".join(['trunk/%s' % f
for f
in files
]))
1461 yield w
; w
.getResult()
1462 descr
= self
.base_descr
1464 descr
+= '\t//depot/trunk/%s\n' % file
1465 w
= self
.dop4(tmp
, "-c creator submit -i", stdin
=descr
)
1466 yield w
; out
= w
.getResult()
1467 m
= re
.search(r
'Change (\d+) submitted.', out
)
1468 assert m
.group(1) == '1'
1469 self
.addTrunkRev(m
.group(1))
1471 # Create second rev (branch).
1472 w
= self
.dop4(tmp
, '-c creator integrate '
1473 + '//depot/trunk/... //depot/branch/...')
1474 yield w
; w
.getResult()
1475 w
= self
.dop4(tmp
, "-c creator edit branch/main.c")
1476 yield w
; w
.getResult()
1477 self
.populate_branch(os
.path
.join(tmp
, 'branch'))
1478 descr
= self
.base_descr
1480 descr
+= '\t//depot/branch/%s\n' % file
1481 w
= self
.dop4(tmp
, "-c creator submit -i", stdin
=descr
)
1482 yield w
; out
= w
.getResult()
1483 m
= re
.search(r
'Change (\d+) submitted.', out
)
1484 self
.addBranchRev(m
.group(1))
1485 createRepository
= deferredGenerator(createRepository
)
1487 def vc_revise(self
):
1488 tmp
= os
.path
.join(self
.repbase
, "p4tmp")
1490 version_c
= VERSION_C
% self
.version
1491 w
= self
.dop4(tmp
, '-c creator edit trunk/version.c')
1492 yield w
; w
.getResult()
1493 open(os
.path
.join(tmp
, "trunk/version.c"), "w").write(version_c
)
1494 descr
= self
.base_descr
+ '\t//depot/trunk/version.c\n'
1495 w
= self
.dop4(tmp
, "-c creator submit -i", stdin
=descr
)
1496 yield w
; out
= w
.getResult()
1497 m
= re
.search(r
'Change (\d+) submitted.', out
)
1498 self
.addTrunkRev(m
.group(1))
1499 vc_revise
= deferredGenerator(vc_revise
)
1501 def shutdown_p4d(self
):
1502 d
= self
.runCommand(self
.repbase
, '%s -p %s admin stop'
1503 % (self
.vcexe
, self
.p4port
))
1504 return d
.addCallback(lambda _
: self
.p4d_shutdown
)
1506 class P4(VCBase
, unittest
.TestCase
):
1508 vctype
= "source.P4"
1510 has_got_revision
= True
1512 def tearDownClass(self
):
1514 return self
.helper
.shutdown_p4d()
1516 def testCheckout(self
):
1517 self
.helper
.vcargs
= { 'p4port': self
.helper
.p4port
,
1518 'p4base': '//depot/',
1519 'defaultBranch': 'trunk' }
1520 d
= self
.do_vctest(testRetry
=False)
1521 # TODO: like arch and darcs, sync does nothing when server is not
1525 def testCheckoutBranch(self
):
1526 self
.helper
.vcargs
= { 'p4port': self
.helper
.p4port
,
1527 'p4base': '//depot/',
1528 'defaultBranch': 'trunk' }
1529 d
= self
.do_branch()
1532 def testPatch(self
):
1533 self
.helper
.vcargs
= { 'p4port': self
.helper
.p4port
,
1534 'p4base': '//depot/',
1535 'defaultBranch': 'trunk' }
1539 VCS
.registerVC(P4
.vc_name
, P4Helper())
1542 class DarcsHelper(BaseHelper
):
1543 branchname
= "branch"
1544 try_branchname
= "branch"
1547 darcspaths
= which('darcs')
1549 return (False, "Darcs is not installed")
1550 self
.vcexe
= darcspaths
[0]
1553 def createRepository(self
):
1554 self
.createBasedir()
1555 self
.darcs_base
= os
.path
.join(self
.repbase
, "Darcs-Repository")
1556 self
.rep_trunk
= os
.path
.join(self
.darcs_base
, "trunk")
1557 self
.rep_branch
= os
.path
.join(self
.darcs_base
, "branch")
1558 tmp
= os
.path
.join(self
.repbase
, "darcstmp")
1560 os
.makedirs(self
.rep_trunk
)
1561 w
= self
.dovc(self
.rep_trunk
, ["initialize"])
1562 yield w
; w
.getResult()
1563 os
.makedirs(self
.rep_branch
)
1564 w
= self
.dovc(self
.rep_branch
, ["initialize"])
1565 yield w
; w
.getResult()
1568 w
= self
.dovc(tmp
, qw("initialize"))
1569 yield w
; w
.getResult()
1570 w
= self
.dovc(tmp
, qw("add -r ."))
1571 yield w
; w
.getResult()
1572 w
= self
.dovc(tmp
, qw("record -a -m initial_import --skip-long-comment -A test@buildbot.sf.net"))
1573 yield w
; w
.getResult()
1574 w
= self
.dovc(tmp
, ["push", "-a", self
.rep_trunk
])
1575 yield w
; w
.getResult()
1576 w
= self
.dovc(tmp
, qw("changes --context"))
1577 yield w
; out
= w
.getResult()
1578 self
.addTrunkRev(out
)
1580 self
.populate_branch(tmp
)
1581 w
= self
.dovc(tmp
, qw("record -a --ignore-times -m commit_on_branch --skip-long-comment -A test@buildbot.sf.net"))
1582 yield w
; w
.getResult()
1583 w
= self
.dovc(tmp
, ["push", "-a", self
.rep_branch
])
1584 yield w
; w
.getResult()
1585 w
= self
.dovc(tmp
, qw("changes --context"))
1586 yield w
; out
= w
.getResult()
1587 self
.addBranchRev(out
)
1589 createRepository
= deferredGenerator(createRepository
)
1591 def vc_revise(self
):
1592 tmp
= os
.path
.join(self
.repbase
, "darcstmp")
1594 w
= self
.dovc(tmp
, qw("initialize"))
1595 yield w
; w
.getResult()
1596 w
= self
.dovc(tmp
, ["pull", "-a", self
.rep_trunk
])
1597 yield w
; w
.getResult()
1600 version_c
= VERSION_C
% self
.version
1601 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1602 w
= self
.dovc(tmp
, qw("record -a --ignore-times -m revised_to_%d --skip-long-comment -A test@buildbot.sf.net" % self
.version
))
1603 yield w
; w
.getResult()
1604 w
= self
.dovc(tmp
, ["push", "-a", self
.rep_trunk
])
1605 yield w
; w
.getResult()
1606 w
= self
.dovc(tmp
, qw("changes --context"))
1607 yield w
; out
= w
.getResult()
1608 self
.addTrunkRev(out
)
1610 vc_revise
= deferredGenerator(vc_revise
)
1612 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1613 assert os
.path
.abspath(workdir
) == workdir
1614 if os
.path
.exists(workdir
):
1615 rmdirRecursive(workdir
)
1616 os
.makedirs(workdir
)
1617 w
= self
.dovc(workdir
, qw("initialize"))
1618 yield w
; w
.getResult()
1620 rep
= self
.rep_trunk
1622 rep
= os
.path
.join(self
.darcs_base
, branch
)
1623 w
= self
.dovc(workdir
, ["pull", "-a", rep
])
1624 yield w
; w
.getResult()
1625 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1626 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1628 def vc_try_finish(self
, workdir
):
1629 rmdirRecursive(workdir
)
1632 class Darcs(VCBase
, unittest
.TestCase
):
1635 # Darcs has a metadir="_darcs", but it does not have an 'export'
1638 vctype
= "source.Darcs"
1639 vctype_try
= "darcs"
1640 has_got_revision
= True
1642 def testCheckout(self
):
1643 self
.helper
.vcargs
= { 'repourl': self
.helper
.rep_trunk
}
1644 d
= self
.do_vctest(testRetry
=False)
1646 # TODO: testRetry has the same problem with Darcs as it does for
1650 def testPatch(self
):
1651 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1652 'defaultBranch': "trunk" }
1656 def testCheckoutBranch(self
):
1657 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1658 'defaultBranch': "trunk" }
1659 d
= self
.do_branch()
1662 def testCheckoutHTTP(self
):
1664 repourl
= "http://localhost:%d/Darcs-Repository/trunk" % self
.httpPort
1665 self
.helper
.vcargs
= { 'repourl': repourl
}
1666 d
= self
.do_vctest(testRetry
=False)
1670 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1671 'defaultBranch': "trunk" }
1672 d
= self
.do_getpatch()
1675 VCS
.registerVC(Darcs
.vc_name
, DarcsHelper())
1679 def registerRepository(self
, coordinates
):
1681 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1682 yield w
; out
= w
.getResult()
1684 w
= self
.dovc(self
.repbase
, "register-archive -d %s" % a
)
1685 yield w
; w
.getResult()
1686 w
= self
.dovc(self
.repbase
, "register-archive %s" % coordinates
)
1687 yield w
; w
.getResult()
1688 registerRepository
= deferredGenerator(registerRepository
)
1690 def unregisterRepository(self
):
1692 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1693 yield w
; out
= w
.getResult()
1695 w
= self
.dovc(self
.repbase
, "register-archive -d %s" % a
)
1696 yield w
; out
= w
.getResult()
1697 unregisterRepository
= deferredGenerator(unregisterRepository
)
1699 class TlaHelper(BaseHelper
, ArchCommon
):
1700 defaultbranch
= "testvc--mainline--1"
1701 branchname
= "testvc--branch--1"
1702 try_branchname
= None # TlaExtractor can figure it out by itself
1706 tlapaths
= which('tla')
1708 return (False, "Arch (tla) is not installed")
1709 self
.vcexe
= tlapaths
[0]
1712 def do_get(self
, basedir
, archive
, branch
, newdir
):
1713 # the 'get' syntax is different between tla and baz. baz, while
1714 # claiming to honor an --archive argument, in fact ignores it. The
1715 # correct invocation is 'baz get archive/revision newdir'.
1716 if self
.archcmd
== "tla":
1717 w
= self
.dovc(basedir
,
1718 "get -A %s %s %s" % (archive
, branch
, newdir
))
1720 w
= self
.dovc(basedir
,
1721 "get %s/%s %s" % (archive
, branch
, newdir
))
1724 def createRepository(self
):
1725 self
.createBasedir()
1726 # first check to see if bazaar is around, since we'll need to know
1728 d
= VCS
.capable(Bazaar
.vc_name
)
1729 d
.addCallback(self
._createRepository
_1)
1732 def _createRepository_1(self
, res
):
1735 # pick a hopefully unique string for the archive name, in the form
1736 # test-%d@buildbot.sf.net--testvc, since otherwise multiple copies of
1737 # the unit tests run in the same user account will collide (since the
1738 # archive names are kept in the per-user ~/.arch-params/ directory).
1740 self
.archname
= "test-%s-%d@buildbot.sf.net--testvc" % (self
.archcmd
,
1742 trunk
= self
.defaultbranch
1743 branch
= self
.branchname
1745 repword
= self
.archcmd
.capitalize()
1746 self
.archrep
= os
.path
.join(self
.repbase
, "%s-Repository" % repword
)
1747 tmp
= os
.path
.join(self
.repbase
, "archtmp")
1752 w
= self
.dovc(tmp
, "my-id", failureIsOk
=True)
1753 yield w
; res
= w
.getResult()
1755 # tla will fail a lot of operations if you have not set an ID
1756 w
= self
.do(tmp
, [self
.vcexe
, "my-id",
1757 "Buildbot Test Suite <test@buildbot.sf.net>"])
1758 yield w
; w
.getResult()
1761 # bazaar keeps a cache of revisions, but this test creates a new
1762 # archive each time it is run, so the cache causes errors.
1763 # Disable the cache to avoid these problems. This will be
1764 # slightly annoying for people who run the buildbot tests under
1765 # the same UID as one which uses baz on a regular basis, but
1766 # bazaar doesn't give us a way to disable the cache just for this
1768 cmd
= "%s cache-config --disable" % VCS
.getHelper('bazaar').vcexe
1769 w
= self
.do(tmp
, cmd
)
1770 yield w
; w
.getResult()
1772 w
= waitForDeferred(self
.unregisterRepository())
1773 yield w
; w
.getResult()
1775 # these commands can be run in any directory
1776 w
= self
.dovc(tmp
, "make-archive -l %s %s" % (a
, self
.archrep
))
1777 yield w
; w
.getResult()
1778 if self
.archcmd
== "tla":
1779 w
= self
.dovc(tmp
, "archive-setup -A %s %s" % (a
, trunk
))
1780 yield w
; w
.getResult()
1781 w
= self
.dovc(tmp
, "archive-setup -A %s %s" % (a
, branch
))
1782 yield w
; w
.getResult()
1784 # baz does not require an 'archive-setup' step
1787 # these commands must be run in the directory that is to be imported
1788 w
= self
.dovc(tmp
, "init-tree --nested %s/%s" % (a
, trunk
))
1789 yield w
; w
.getResult()
1790 files
= " ".join(["main.c", "version.c", "subdir",
1791 os
.path
.join("subdir", "subdir.c")])
1792 w
= self
.dovc(tmp
, "add-id %s" % files
)
1793 yield w
; w
.getResult()
1795 w
= self
.dovc(tmp
, "import %s/%s" % (a
, trunk
))
1796 yield w
; out
= w
.getResult()
1797 self
.addTrunkRev("base-0")
1800 if self
.archcmd
== "tla":
1801 branchstart
= "%s--base-0" % trunk
1802 w
= self
.dovc(tmp
, "tag -A %s %s %s" % (a
, branchstart
, branch
))
1803 yield w
; w
.getResult()
1805 w
= self
.dovc(tmp
, "branch %s" % branch
)
1806 yield w
; w
.getResult()
1810 # check out the branch
1811 w
= self
.do_get(self
.repbase
, a
, branch
, "archtmp")
1812 yield w
; w
.getResult()
1814 self
.populate_branch(tmp
)
1815 logfile
= "++log.%s--%s" % (branch
, a
)
1816 logmsg
= "Summary: commit on branch\nKeywords:\n\n"
1817 open(os
.path
.join(tmp
, logfile
), "w").write(logmsg
)
1818 w
= self
.dovc(tmp
, "commit")
1819 yield w
; out
= w
.getResult()
1820 m
= re
.search(r
'committed %s/%s--([\S]+)' % (a
, branch
),
1822 assert (m
.group(1) == "base-0" or m
.group(1).startswith("patch-"))
1823 self
.addBranchRev(m
.group(1))
1825 w
= waitForDeferred(self
.unregisterRepository())
1826 yield w
; w
.getResult()
1829 # we unregister the repository each time, because we might have
1830 # changed the coordinates (since we switch from a file: URL to an
1831 # http: URL for various tests). The buildslave code doesn't forcibly
1832 # unregister the archive, so we have to do it here.
1833 w
= waitForDeferred(self
.unregisterRepository())
1834 yield w
; w
.getResult()
1836 _createRepository_1
= deferredGenerator(_createRepository_1
)
1838 def vc_revise(self
):
1839 # the fix needs to be done in a workspace that is linked to a
1840 # read-write version of the archive (i.e., using file-based
1841 # coordinates instead of HTTP ones), so we re-register the repository
1842 # before we begin. We unregister it when we're done to make sure the
1843 # build will re-register the correct one for whichever test is
1844 # currently being run.
1846 # except, that source.Bazaar really doesn't like it when the archive
1847 # gets unregistered behind its back. The slave tries to do a 'baz
1848 # replay' in a tree with an archive that is no longer recognized, and
1849 # baz aborts with a botched invariant exception. This causes
1850 # mode=update to fall back to clobber+get, which flunks one of the
1851 # tests (the 'newfile' check in _do_vctest_update_3 fails)
1853 # to avoid this, we take heroic steps here to leave the archive
1854 # registration in the same state as we found it.
1856 tmp
= os
.path
.join(self
.repbase
, "archtmp")
1859 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1860 yield w
; out
= w
.getResult()
1862 lines
= out
.split("\n")
1863 coordinates
= lines
[1].strip()
1865 # now register the read-write location
1866 w
= waitForDeferred(self
.registerRepository(self
.archrep
))
1867 yield w
; w
.getResult()
1869 trunk
= self
.defaultbranch
1871 w
= self
.do_get(self
.repbase
, a
, trunk
, "archtmp")
1872 yield w
; w
.getResult()
1874 # tla appears to use timestamps to determine which files have
1875 # changed, so wait long enough for the new file to have a different
1879 version_c
= VERSION_C
% self
.version
1880 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1882 logfile
= "++log.%s--%s" % (trunk
, a
)
1883 logmsg
= "Summary: revised_to_%d\nKeywords:\n\n" % self
.version
1884 open(os
.path
.join(tmp
, logfile
), "w").write(logmsg
)
1885 w
= self
.dovc(tmp
, "commit")
1886 yield w
; out
= w
.getResult()
1887 m
= re
.search(r
'committed %s/%s--([\S]+)' % (a
, trunk
),
1889 assert (m
.group(1) == "base-0" or m
.group(1).startswith("patch-"))
1890 self
.addTrunkRev(m
.group(1))
1892 # now re-register the original coordinates
1893 w
= waitForDeferred(self
.registerRepository(coordinates
))
1894 yield w
; w
.getResult()
1896 vc_revise
= deferredGenerator(vc_revise
)
1898 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1899 assert os
.path
.abspath(workdir
) == workdir
1900 if os
.path
.exists(workdir
):
1901 rmdirRecursive(workdir
)
1905 # register the read-write location, if it wasn't already registered
1906 w
= waitForDeferred(self
.registerRepository(self
.archrep
))
1907 yield w
; w
.getResult()
1909 w
= self
.do_get(self
.repbase
, a
, "testvc--mainline--1", workdir
)
1910 yield w
; w
.getResult()
1914 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1915 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1917 def vc_try_finish(self
, workdir
):
1918 rmdirRecursive(workdir
)
1920 class Arch(VCBase
, unittest
.TestCase
):
1924 # Arch has a metadir="{arch}", but it does not have an 'export' mode.
1925 vctype
= "source.Arch"
1927 has_got_revision
= True
1929 def testCheckout(self
):
1930 # these are the coordinates of the read-write archive used by all the
1931 # non-HTTP tests. testCheckoutHTTP overrides these.
1932 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1933 'version': self
.helper
.defaultbranch
}
1934 d
= self
.do_vctest(testRetry
=False)
1935 # the current testRetry=True logic doesn't have the desired effect:
1936 # "update" is a no-op because arch knows that the repository hasn't
1937 # changed. Other VC systems will re-checkout missing files on
1938 # update, arch just leaves the tree untouched. TODO: come up with
1939 # some better test logic, probably involving a copy of the
1940 # repository that has a few changes checked in.
1944 def testCheckoutHTTP(self
):
1946 url
= "http://localhost:%d/Tla-Repository" % self
.httpPort
1947 self
.helper
.vcargs
= { 'url': url
,
1948 'version': "testvc--mainline--1" }
1949 d
= self
.do_vctest(testRetry
=False)
1952 def testPatch(self
):
1953 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1954 'version': self
.helper
.defaultbranch
}
1958 def testCheckoutBranch(self
):
1959 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1960 'version': self
.helper
.defaultbranch
}
1961 d
= self
.do_branch()
1965 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1966 'version': self
.helper
.defaultbranch
}
1967 d
= self
.do_getpatch()
1970 VCS
.registerVC(Arch
.vc_name
, TlaHelper())
1973 class BazaarHelper(TlaHelper
):
1977 bazpaths
= which('baz')
1979 return (False, "Arch (baz) is not installed")
1980 self
.vcexe
= bazpaths
[0]
1983 def setUp2(self
, res
):
1984 # we unregister the repository each time, because we might have
1985 # changed the coordinates (since we switch from a file: URL to an
1986 # http: URL for various tests). The buildslave code doesn't forcibly
1987 # unregister the archive, so we have to do it here.
1988 d
= self
.unregisterRepository()
1995 vctype
= "source.Bazaar"
1997 has_got_revision
= True
2001 def testCheckout(self
):
2002 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
2003 # Baz adds the required 'archive' argument
2004 'archive': self
.helper
.archname
,
2005 'version': self
.helper
.defaultbranch
,
2007 d
= self
.do_vctest(testRetry
=False)
2008 # the current testRetry=True logic doesn't have the desired effect:
2009 # "update" is a no-op because arch knows that the repository hasn't
2010 # changed. Other VC systems will re-checkout missing files on
2011 # update, arch just leaves the tree untouched. TODO: come up with
2012 # some better test logic, probably involving a copy of the
2013 # repository that has a few changes checked in.
2017 def testCheckoutHTTP(self
):
2019 url
= "http://localhost:%d/Baz-Repository" % self
.httpPort
2020 self
.helper
.vcargs
= { 'url': url
,
2021 'archive': self
.helper
.archname
,
2022 'version': self
.helper
.defaultbranch
,
2024 d
= self
.do_vctest(testRetry
=False)
2027 def testPatch(self
):
2028 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
2029 # Baz adds the required 'archive' argument
2030 'archive': self
.helper
.archname
,
2031 'version': self
.helper
.defaultbranch
,
2036 def testCheckoutBranch(self
):
2037 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
2038 # Baz adds the required 'archive' argument
2039 'archive': self
.helper
.archname
,
2040 'version': self
.helper
.defaultbranch
,
2042 d
= self
.do_branch()
2046 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
2047 # Baz adds the required 'archive' argument
2048 'archive': self
.helper
.archname
,
2049 'version': self
.helper
.defaultbranch
,
2051 d
= self
.do_getpatch()
2054 def fixRepository(self
):
2055 self
.fixtimer
= None
2056 self
.site
.resource
= self
.root
2058 def testRetry(self
):
2059 # we want to verify that source.Source(retry=) works, and the easiest
2060 # way to make VC updates break (temporarily) is to break the HTTP
2061 # server that's providing the repository. Anything else pretty much
2062 # requires mutating the (read-only) BUILDBOT_TEST_VC repository, or
2063 # modifying the buildslave's checkout command while it's running.
2065 # this test takes a while to run, so don't bother doing it with
2066 # anything other than baz
2070 # break the repository server
2071 from twisted
.web
import static
2072 self
.site
.resource
= static
.Data("Sorry, repository is offline",
2074 # and arrange to fix it again in 5 seconds, while the test is
2076 self
.fixtimer
= reactor
.callLater(5, self
.fixRepository
)
2078 url
= "http://localhost:%d/Baz-Repository" % self
.httpPort
2079 self
.helper
.vcargs
= { 'url': url
,
2080 'archive': self
.helper
.archname
,
2081 'version': self
.helper
.defaultbranch
,
2084 d
= self
.do_vctest_once(True)
2085 d
.addCallback(self
._testRetry
_1)
2087 def _testRetry_1(self
, bs
):
2088 # make sure there was mention of the retry attempt in the logs
2090 self
.failUnlessIn("unable to access URL", l
.getText(),
2091 "funny, VC operation didn't fail at least once")
2092 self
.failUnlessIn("update failed, trying 4 more times after 5 seconds",
2093 l
.getTextWithHeaders(),
2094 "funny, VC operation wasn't reattempted")
2096 def testRetryFails(self
):
2097 # make sure that the build eventually gives up on a repository which
2098 # is completely unavailable
2102 # break the repository server, and leave it broken
2103 from twisted
.web
import static
2104 self
.site
.resource
= static
.Data("Sorry, repository is offline",
2107 url
= "http://localhost:%d/Baz-Repository" % self
.httpPort
2108 self
.helper
.vcargs
= {'url': url
,
2109 'archive': self
.helper
.archname
,
2110 'version': self
.helper
.defaultbranch
,
2113 d
= self
.do_vctest_once(False)
2114 d
.addCallback(self
._testRetryFails
_1)
2116 def _testRetryFails_1(self
, bs
):
2117 self
.failUnlessEqual(bs
.getResults(), FAILURE
)
2119 def tearDown2(self
):
2121 self
.fixtimer
.cancel()
2122 # tell tla to get rid of the leftover archive this test leaves in the
2123 # user's 'tla archives' listing. The name of this archive is provided
2124 # by the repository tarball, so the following command must use the
2125 # same name. We could use archive= to set it explicitly, but if you
2126 # change it from the default, then 'tla update' won't work.
2127 d
= self
.helper
.unregisterRepository()
2130 VCS
.registerVC(Bazaar
.vc_name
, BazaarHelper())
2132 class BzrHelper(BaseHelper
):
2133 branchname
= "branch"
2134 try_branchname
= "branch"
2137 bzrpaths
= which('bzr')
2139 return (False, "bzr is not installed")
2140 self
.vcexe
= bzrpaths
[0]
2143 def get_revision_number(self
, out
):
2144 for line
in out
.split("\n"):
2145 colon
= line
.index(":")
2146 key
, value
= line
[:colon
], line
[colon
+2:]
2149 raise RuntimeError("unable to find revno: in bzr output: '%s'" % out
)
2151 def createRepository(self
):
2152 self
.createBasedir()
2153 self
.bzr_base
= os
.path
.join(self
.repbase
, "Bzr-Repository")
2154 self
.rep_trunk
= os
.path
.join(self
.bzr_base
, "trunk")
2155 self
.rep_branch
= os
.path
.join(self
.bzr_base
, "branch")
2156 tmp
= os
.path
.join(self
.repbase
, "bzrtmp")
2157 btmp
= os
.path
.join(self
.repbase
, "bzrtmp-branch")
2159 os
.makedirs(self
.rep_trunk
)
2160 w
= self
.dovc(self
.rep_trunk
, ["init"])
2161 yield w
; w
.getResult()
2162 w
= self
.dovc(self
.bzr_base
,
2163 ["branch", self
.rep_trunk
, self
.rep_branch
])
2164 yield w
; w
.getResult()
2166 w
= self
.dovc(self
.repbase
, ["checkout", self
.rep_trunk
, tmp
])
2167 yield w
; w
.getResult()
2169 w
= self
.dovc(tmp
, qw("add"))
2170 yield w
; w
.getResult()
2171 w
= self
.dovc(tmp
, qw("commit -m initial_import"))
2172 yield w
; w
.getResult()
2173 w
= self
.dovc(tmp
, qw("version-info"))
2174 yield w
; out
= w
.getResult()
2175 self
.addTrunkRev(self
.get_revision_number(out
))
2178 # pull all trunk revisions to the branch
2179 w
= self
.dovc(self
.rep_branch
, qw("pull"))
2180 yield w
; w
.getResult()
2181 # obtain a branch tree
2182 w
= self
.dovc(self
.repbase
, ["checkout", self
.rep_branch
, btmp
])
2183 yield w
; w
.getResult()
2185 self
.populate_branch(btmp
)
2186 w
= self
.dovc(btmp
, qw("add"))
2187 yield w
; w
.getResult()
2188 w
= self
.dovc(btmp
, qw("commit -m commit_on_branch"))
2189 yield w
; w
.getResult()
2190 w
= self
.dovc(btmp
, qw("version-info"))
2191 yield w
; out
= w
.getResult()
2192 self
.addBranchRev(self
.get_revision_number(out
))
2193 rmdirRecursive(btmp
)
2194 createRepository
= deferredGenerator(createRepository
)
2196 def vc_revise(self
):
2197 tmp
= os
.path
.join(self
.repbase
, "bzrtmp")
2198 w
= self
.dovc(self
.repbase
, ["checkout", self
.rep_trunk
, tmp
])
2199 yield w
; w
.getResult()
2202 version_c
= VERSION_C
% self
.version
2203 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
2204 w
= self
.dovc(tmp
, qw("commit -m revised_to_%d" % self
.version
))
2205 yield w
; w
.getResult()
2206 w
= self
.dovc(tmp
, qw("version-info"))
2207 yield w
; out
= w
.getResult()
2208 self
.addTrunkRev(self
.get_revision_number(out
))
2210 vc_revise
= deferredGenerator(vc_revise
)
2212 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
2213 assert os
.path
.abspath(workdir
) == workdir
2214 if os
.path
.exists(workdir
):
2215 rmdirRecursive(workdir
)
2216 #os.makedirs(workdir)
2218 rep
= self
.rep_trunk
2220 rep
= os
.path
.join(self
.bzr_base
, branch
)
2221 w
= self
.dovc(self
.bzr_base
, ["checkout", rep
, workdir
])
2222 yield w
; w
.getResult()
2223 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
2224 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
2226 def vc_try_finish(self
, workdir
):
2227 rmdirRecursive(workdir
)
2229 class Bzr(VCBase
, unittest
.TestCase
):
2233 vctype
= "source.Bzr"
2235 has_got_revision
= True
2237 def testCheckout(self
):
2238 self
.helper
.vcargs
= { 'repourl': self
.helper
.rep_trunk
}
2239 d
= self
.do_vctest(testRetry
=False)
2241 # TODO: testRetry has the same problem with Bzr as it does for
2245 def testPatch(self
):
2246 self
.helper
.vcargs
= { 'baseURL': self
.helper
.bzr_base
+ "/",
2247 'defaultBranch': "trunk" }
2251 def testCheckoutBranch(self
):
2252 self
.helper
.vcargs
= { 'baseURL': self
.helper
.bzr_base
+ "/",
2253 'defaultBranch': "trunk" }
2254 d
= self
.do_branch()
2257 def testCheckoutHTTP(self
):
2259 repourl
= "http://localhost:%d/Bzr-Repository/trunk" % self
.httpPort
2260 self
.helper
.vcargs
= { 'repourl': repourl
}
2261 d
= self
.do_vctest(testRetry
=False)
2265 def fixRepository(self
):
2266 self
.fixtimer
= None
2267 self
.site
.resource
= self
.root
2269 def testRetry(self
):
2270 # this test takes a while to run
2273 # break the repository server
2274 from twisted
.web
import static
2275 self
.site
.resource
= static
.Data("Sorry, repository is offline",
2277 # and arrange to fix it again in 5 seconds, while the test is
2279 self
.fixtimer
= reactor
.callLater(5, self
.fixRepository
)
2281 repourl
= "http://localhost:%d/Bzr-Repository/trunk" % self
.httpPort
2282 self
.helper
.vcargs
= { 'repourl': repourl
,
2285 d
= self
.do_vctest_once(True)
2286 d
.addCallback(self
._testRetry
_1)
2288 def _testRetry_1(self
, bs
):
2289 # make sure there was mention of the retry attempt in the logs
2291 self
.failUnlessIn("ERROR: Not a branch: ", l
.getText(),
2292 "funny, VC operation didn't fail at least once")
2293 self
.failUnlessIn("update failed, trying 4 more times after 5 seconds",
2294 l
.getTextWithHeaders(),
2295 "funny, VC operation wasn't reattempted")
2297 def testRetryFails(self
):
2298 # make sure that the build eventually gives up on a repository which
2299 # is completely unavailable
2303 # break the repository server, and leave it broken
2304 from twisted
.web
import static
2305 self
.site
.resource
= static
.Data("Sorry, repository is offline",
2308 repourl
= "http://localhost:%d/Bzr-Repository/trunk" % self
.httpPort
2309 self
.helper
.vcargs
= { 'repourl': repourl
,
2312 d
= self
.do_vctest_once(False)
2313 d
.addCallback(self
._testRetryFails
_1)
2315 def _testRetryFails_1(self
, bs
):
2316 self
.failUnlessEqual(bs
.getResults(), FAILURE
)
2320 self
.helper
.vcargs
= { 'baseURL': self
.helper
.bzr_base
+ "/",
2321 'defaultBranch': "trunk" }
2322 d
= self
.do_getpatch()
2325 VCS
.registerVC(Bzr
.vc_name
, BzrHelper())
2328 class MercurialHelper(BaseHelper
):
2329 branchname
= "branch"
2330 try_branchname
= "branch"
2333 hgpaths
= which("hg")
2335 return (False, "Mercurial is not installed")
2336 self
.vcexe
= hgpaths
[0]
2339 def extract_id(self
, output
):
2340 m
= re
.search(r
'^(\w+)', output
)
2343 def createRepository(self
):
2344 self
.createBasedir()
2345 self
.hg_base
= os
.path
.join(self
.repbase
, "Mercurial-Repository")
2346 self
.rep_trunk
= os
.path
.join(self
.hg_base
, "trunk")
2347 self
.rep_branch
= os
.path
.join(self
.hg_base
, "branch")
2348 tmp
= os
.path
.join(self
.hg_base
, "hgtmp")
2350 os
.makedirs(self
.rep_trunk
)
2351 w
= self
.dovc(self
.rep_trunk
, "init")
2352 yield w
; w
.getResult()
2353 os
.makedirs(self
.rep_branch
)
2354 w
= self
.dovc(self
.rep_branch
, "init")
2355 yield w
; w
.getResult()
2358 w
= self
.dovc(tmp
, "init")
2359 yield w
; w
.getResult()
2360 w
= self
.dovc(tmp
, "add")
2361 yield w
; w
.getResult()
2362 w
= self
.dovc(tmp
, ['commit', '-m', 'initial_import'])
2363 yield w
; w
.getResult()
2364 w
= self
.dovc(tmp
, ['push', self
.rep_trunk
])
2365 # note that hg-push does not actually update the working directory
2366 yield w
; w
.getResult()
2367 w
= self
.dovc(tmp
, "identify")
2368 yield w
; out
= w
.getResult()
2369 self
.addTrunkRev(self
.extract_id(out
))
2371 self
.populate_branch(tmp
)
2372 w
= self
.dovc(tmp
, ['commit', '-m', 'commit_on_branch'])
2373 yield w
; w
.getResult()
2374 w
= self
.dovc(tmp
, ['push', self
.rep_branch
])
2375 yield w
; w
.getResult()
2376 w
= self
.dovc(tmp
, "identify")
2377 yield w
; out
= w
.getResult()
2378 self
.addBranchRev(self
.extract_id(out
))
2380 createRepository
= deferredGenerator(createRepository
)
2382 def vc_revise(self
):
2383 tmp
= os
.path
.join(self
.hg_base
, "hgtmp2")
2384 w
= self
.dovc(self
.hg_base
, ['clone', self
.rep_trunk
, tmp
])
2385 yield w
; w
.getResult()
2388 version_c
= VERSION_C
% self
.version
2389 version_c_filename
= os
.path
.join(tmp
, "version.c")
2390 open(version_c_filename
, "w").write(version_c
)
2391 # hg uses timestamps to distinguish files which have changed, so we
2392 # force the mtime forward a little bit
2393 future
= time
.time() + 2*self
.version
2394 os
.utime(version_c_filename
, (future
, future
))
2395 w
= self
.dovc(tmp
, ['commit', '-m', 'revised_to_%d' % self
.version
])
2396 yield w
; w
.getResult()
2397 w
= self
.dovc(tmp
, ['push', self
.rep_trunk
])
2398 yield w
; w
.getResult()
2399 w
= self
.dovc(tmp
, "identify")
2400 yield w
; out
= w
.getResult()
2401 self
.addTrunkRev(self
.extract_id(out
))
2403 vc_revise
= deferredGenerator(vc_revise
)
2405 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
2406 assert os
.path
.abspath(workdir
) == workdir
2407 if os
.path
.exists(workdir
):
2408 rmdirRecursive(workdir
)
2410 src
= self
.rep_branch
2412 src
= self
.rep_trunk
2413 w
= self
.dovc(self
.hg_base
, ['clone', src
, workdir
])
2414 yield w
; w
.getResult()
2415 try_c_filename
= os
.path
.join(workdir
, "subdir", "subdir.c")
2416 open(try_c_filename
, "w").write(TRY_C
)
2417 future
= time
.time() + 2*self
.version
2418 os
.utime(try_c_filename
, (future
, future
))
2419 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
2421 def vc_try_finish(self
, workdir
):
2422 rmdirRecursive(workdir
)
2424 class MercurialServerPP(protocol
.ProcessProtocol
):
2426 self
.wait
= defer
.Deferred()
2428 def outReceived(self
, data
):
2429 log
.msg("hg-serve-stdout: %s" % (data
,))
2430 def errReceived(self
, data
):
2431 print "HG-SERVE-STDERR:", data
2432 log
.msg("hg-serve-stderr: %s" % (data
,))
2433 def processEnded(self
, reason
):
2434 log
.msg("hg-serve ended: %s" % reason
)
2435 self
.wait
.callback(None)
2438 class Mercurial(VCBase
, unittest
.TestCase
):
2441 # Mercurial has a metadir=".hg", but it does not have an 'export' mode.
2443 vctype
= "source.Mercurial"
2445 has_got_revision
= True
2447 _wait_for_server_poller
= None
2450 def testCheckout(self
):
2451 self
.helper
.vcargs
= { 'repourl': self
.helper
.rep_trunk
}
2452 d
= self
.do_vctest(testRetry
=False)
2454 # TODO: testRetry has the same problem with Mercurial as it does for
2458 def testPatch(self
):
2459 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2460 'defaultBranch': "trunk" }
2464 def testCheckoutBranch(self
):
2465 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2466 'defaultBranch': "trunk" }
2467 d
= self
.do_branch()
2470 def serveHTTP(self
):
2471 # the easiest way to publish hg over HTTP is by running 'hg serve' as
2472 # a child process while the test is running. (you can also use a CGI
2473 # script, which sounds difficult, or you can publish the files
2474 # directly, which isn't well documented).
2476 # grr.. 'hg serve' doesn't let you use --port=0 to mean "pick a free
2477 # port", instead it uses it as a signal to use the default (port
2478 # 8000). This means there is no way to make it choose a free port, so
2479 # we are forced to make it use a statically-defined one, making it
2480 # harder to avoid collisions.
2481 self
.httpPort
= 8300 + (os
.getpid() % 200)
2482 args
= [self
.helper
.vcexe
,
2483 "serve", "--port", str(self
.httpPort
), "--verbose"]
2485 # in addition, hg doesn't flush its stdout, so we can't wait for the
2486 # "listening at" message to know when it's safe to start the test.
2487 # Instead, poll every second until a getPage works.
2489 self
._pp
= MercurialServerPP() # logs+discards everything
2491 # this serves one tree at a time, so we serve trunk. TODO: test hg's
2492 # in-repo branches, for which a single tree will hold all branches.
2493 self
._hg
_server
= reactor
.spawnProcess(self
._pp
, self
.helper
.vcexe
, args
,
2495 self
.helper
.rep_trunk
)
2496 log
.msg("waiting for hg serve to start")
2497 done_d
= defer
.Deferred()
2499 d
= client
.getPage("http://localhost:%d/" % self
.httpPort
)
2501 log
.msg("hg serve appears to have started")
2502 self
._wait
_for
_server
_poller
.stop()
2503 done_d
.callback(None)
2504 def ignore_connection_refused(f
):
2505 f
.trap(error
.ConnectionRefusedError
)
2506 d
.addCallbacks(success
, ignore_connection_refused
)
2507 d
.addErrback(done_d
.errback
)
2509 self
._wait
_for
_server
_poller
= task
.LoopingCall(poll
)
2510 self
._wait
_for
_server
_poller
.start(0.5, True)
2514 if self
._wait
_for
_server
_poller
:
2515 if self
._wait
_for
_server
_poller
.running
:
2516 self
._wait
_for
_server
_poller
.stop()
2518 self
._hg
_server
.loseConnection()
2520 self
._hg
_server
.signalProcess("KILL")
2521 except error
.ProcessExitedAlready
:
2523 self
._hg
_server
= None
2524 return VCBase
.tearDown(self
)
2526 def tearDown2(self
):
2528 return self
._pp
.wait
2530 def testCheckoutHTTP(self
):
2531 d
= self
.serveHTTP()
2533 repourl
= "http://localhost:%d/" % self
.httpPort
2534 self
.helper
.vcargs
= { 'repourl': repourl
}
2535 return self
.do_vctest(testRetry
=False)
2536 d
.addCallback(_started
)
2540 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2541 'defaultBranch': "trunk" }
2542 d
= self
.do_getpatch()
2545 VCS
.registerVC(Mercurial
.vc_name
, MercurialHelper())
2547 class MercurialInRepoHelper(MercurialHelper
):
2548 branchname
= "the_branch"
2549 try_branchname
= "the_branch"
2552 def createRepository(self
):
2553 self
.createBasedir()
2554 self
.hg_base
= os
.path
.join(self
.repbase
, "Mercurial-Repository")
2555 self
.repo
= os
.path
.join(self
.hg_base
, "inrepobranch")
2556 tmp
= os
.path
.join(self
.hg_base
, "hgtmp")
2558 os
.makedirs(self
.repo
)
2559 w
= self
.dovc(self
.repo
, "init")
2560 yield w
; w
.getResult()
2563 w
= self
.dovc(tmp
, "init")
2564 yield w
; w
.getResult()
2565 w
= self
.dovc(tmp
, "add")
2566 yield w
; w
.getResult()
2567 w
= self
.dovc(tmp
, ['commit', '-m', 'initial_import'])
2568 yield w
; w
.getResult()
2569 w
= self
.dovc(tmp
, ['push', self
.repo
])
2570 # note that hg-push does not actually update the working directory
2571 yield w
; w
.getResult()
2572 w
= self
.dovc(tmp
, "identify")
2573 yield w
; out
= w
.getResult()
2574 self
.addTrunkRev(self
.extract_id(out
))
2576 self
.populate_branch(tmp
)
2577 w
= self
.dovc(tmp
, ['branch', self
.branchname
])
2578 yield w
; w
.getResult()
2579 w
= self
.dovc(tmp
, ['commit', '-m', 'commit_on_branch'])
2580 yield w
; w
.getResult()
2581 w
= self
.dovc(tmp
, ['push', '-f', self
.repo
])
2582 yield w
; w
.getResult()
2583 w
= self
.dovc(tmp
, "identify")
2584 yield w
; out
= w
.getResult()
2585 self
.addBranchRev(self
.extract_id(out
))
2587 createRepository
= deferredGenerator(createRepository
)
2589 def vc_revise(self
):
2590 tmp
= os
.path
.join(self
.hg_base
, "hgtmp2")
2591 w
= self
.dovc(self
.hg_base
, ['clone', self
.repo
, tmp
])
2592 yield w
; w
.getResult()
2593 w
= self
.dovc(tmp
, ['update', '--clean', '--rev', 'default'])
2594 yield w
; w
.getResult()
2597 version_c
= VERSION_C
% self
.version
2598 version_c_filename
= os
.path
.join(tmp
, "version.c")
2599 open(version_c_filename
, "w").write(version_c
)
2600 # hg uses timestamps to distinguish files which have changed, so we
2601 # force the mtime forward a little bit
2602 future
= time
.time() + 2*self
.version
2603 os
.utime(version_c_filename
, (future
, future
))
2604 w
= self
.dovc(tmp
, ['commit', '-m', 'revised_to_%d' % self
.version
])
2605 yield w
; w
.getResult()
2606 w
= self
.dovc(tmp
, ['push', '--force', self
.repo
])
2607 yield w
; w
.getResult()
2608 w
= self
.dovc(tmp
, "identify")
2609 yield w
; out
= w
.getResult()
2610 self
.addTrunkRev(self
.extract_id(out
))
2612 vc_revise
= deferredGenerator(vc_revise
)
2614 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
2615 assert os
.path
.abspath(workdir
) == workdir
2616 if os
.path
.exists(workdir
):
2617 rmdirRecursive(workdir
)
2618 w
= self
.dovc(self
.hg_base
, ['clone', self
.repo
, workdir
])
2619 yield w
; w
.getResult()
2620 if not branch
: branch
= "default"
2621 w
= self
.dovc(workdir
, ['update', '--clean', '--rev', branch
])
2622 yield w
; w
.getResult()
2624 try_c_filename
= os
.path
.join(workdir
, "subdir", "subdir.c")
2625 open(try_c_filename
, "w").write(TRY_C
)
2626 future
= time
.time() + 2*self
.version
2627 os
.utime(try_c_filename
, (future
, future
))
2628 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
2630 def vc_try_finish(self
, workdir
):
2631 rmdirRecursive(workdir
)
2635 class MercurialInRepo(Mercurial
):
2636 vc_name
= 'MercurialInRepo'
2638 def default_args(self
):
2639 return { 'repourl': self
.helper
.repo
,
2640 'branchType': 'inrepo',
2641 'defaultBranch': 'default' }
2643 def testCheckout(self
):
2644 self
.helper
.vcargs
= self
.default_args()
2645 d
= self
.do_vctest(testRetry
=False)
2647 # TODO: testRetry has the same problem with Mercurial as it does for
2651 def testPatch(self
):
2652 self
.helper
.vcargs
= self
.default_args()
2656 def testCheckoutBranch(self
):
2657 self
.helper
.vcargs
= self
.default_args()
2658 d
= self
.do_branch()
2661 def serveHTTP(self
):
2662 # the easiest way to publish hg over HTTP is by running 'hg serve' as
2663 # a child process while the test is running. (you can also use a CGI
2664 # script, which sounds difficult, or you can publish the files
2665 # directly, which isn't well documented).
2667 # grr.. 'hg serve' doesn't let you use --port=0 to mean "pick a free
2668 # port", instead it uses it as a signal to use the default (port
2669 # 8000). This means there is no way to make it choose a free port, so
2670 # we are forced to make it use a statically-defined one, making it
2671 # harder to avoid collisions.
2672 self
.httpPort
= 8300 + (os
.getpid() % 200)
2673 args
= [self
.helper
.vcexe
,
2674 "serve", "--port", str(self
.httpPort
), "--verbose"]
2676 # in addition, hg doesn't flush its stdout, so we can't wait for the
2677 # "listening at" message to know when it's safe to start the test.
2678 # Instead, poll every second until a getPage works.
2680 self
._pp
= MercurialServerPP() # logs+discards everything
2681 # this serves one tree at a time, so we serve trunk. TODO: test hg's
2682 # in-repo branches, for which a single tree will hold all branches.
2683 self
._hg
_server
= reactor
.spawnProcess(self
._pp
, self
.helper
.vcexe
, args
,
2686 log
.msg("waiting for hg serve to start")
2687 done_d
= defer
.Deferred()
2689 d
= client
.getPage("http://localhost:%d/" % self
.httpPort
)
2691 log
.msg("hg serve appears to have started")
2692 self
._wait
_for
_server
_poller
.stop()
2693 done_d
.callback(None)
2694 def ignore_connection_refused(f
):
2695 f
.trap(error
.ConnectionRefusedError
)
2696 d
.addCallbacks(success
, ignore_connection_refused
)
2697 d
.addErrback(done_d
.errback
)
2699 self
._wait
_for
_server
_poller
= task
.LoopingCall(poll
)
2700 self
._wait
_for
_server
_poller
.start(0.5, True)
2704 if self
._wait
_for
_server
_poller
:
2705 if self
._wait
_for
_server
_poller
.running
:
2706 self
._wait
_for
_server
_poller
.stop()
2708 self
._hg
_server
.loseConnection()
2710 self
._hg
_server
.signalProcess("KILL")
2711 except error
.ProcessExitedAlready
:
2713 self
._hg
_server
= None
2714 return VCBase
.tearDown(self
)
2716 def tearDown2(self
):
2718 return self
._pp
.wait
2720 def testCheckoutHTTP(self
):
2721 d
= self
.serveHTTP()
2723 repourl
= "http://localhost:%d/" % self
.httpPort
2724 self
.helper
.vcargs
= self
.default_args()
2725 self
.helper
.vcargs
['repourl'] = repourl
2726 return self
.do_vctest(testRetry
=False)
2727 d
.addCallback(_started
)
2731 self
.helper
.vcargs
= self
.default_args()
2732 d
= self
.do_getpatch()
2735 VCS
.registerVC(MercurialInRepo
.vc_name
, MercurialInRepoHelper())
2738 class GitHelper(BaseHelper
):
2739 branchname
= "branch"
2740 try_branchname
= "branch"
2743 gitpaths
= which('git')
2745 return (False, "GIT is not installed")
2746 d
= utils
.getProcessOutput(gitpaths
[0], ["--version"], env
=os
.environ
)
2747 d
.addCallback(self
._capable
, gitpaths
[0])
2750 def _capable(self
, v
, vcexe
):
2752 m
= re
.search(r
'\b(\d+)\.(\d+)', v
)
2755 raise Exception, 'no regex match'
2757 ver
= tuple([int(num
) for num
in m
.groups()])
2759 # git-1.1.3 (as shipped with Dapper) doesn't understand 'git
2760 # init' (it wants 'git init-db'), and fails unit tests that
2761 # involve branches. git-1.5.3.6 (on my debian/unstable system)
2762 # works. I don't know where the dividing point is: if someone can
2763 # figure it out (or figure out how to make buildbot support more
2764 # versions), please update this check.
2766 return (False, "Found git (%s) but it is older than 1.2.x" % vcexe
)
2768 except Exception, e
:
2769 log
.msg("couldn't identify git version number in output:")
2770 log
.msg("'''%s'''" % v
)
2771 log
.msg("because: %s" % e
)
2772 log
.msg("skipping tests")
2774 "Found git (%s) but couldn't identify its version from '%s'" % (vcexe
, v
))
2779 def createRepository(self
):
2780 self
.createBasedir()
2781 self
.gitrepo
= os
.path
.join(self
.repbase
,
2783 tmp
= os
.path
.join(self
.repbase
, "gittmp")
2785 env
= os
.environ
.copy()
2786 env
['GIT_DIR'] = self
.gitrepo
2787 w
= self
.dovc(self
.repbase
, "init", env
=env
)
2788 yield w
; w
.getResult()
2791 w
= self
.dovc(tmp
, "init")
2792 yield w
; w
.getResult()
2793 w
= self
.dovc(tmp
, ["add", "."])
2794 yield w
; w
.getResult()
2795 w
= self
.dovc(tmp
, ["config", "user.email", "buildbot-trial@localhost"])
2796 yield w
; w
.getResult()
2797 w
= self
.dovc(tmp
, ["config", "user.name", "Buildbot Trial"])
2798 yield w
; w
.getResult()
2799 w
= self
.dovc(tmp
, ["commit", "-m", "initial_import"])
2800 yield w
; w
.getResult()
2802 w
= self
.dovc(tmp
, ["checkout", "-b", self
.branchname
])
2803 yield w
; w
.getResult()
2804 self
.populate_branch(tmp
)
2805 w
= self
.dovc(tmp
, ["commit", "-a", "-m", "commit_on_branch"])
2806 yield w
; w
.getResult()
2808 w
= self
.dovc(tmp
, ["rev-parse", "master", self
.branchname
])
2809 yield w
; out
= w
.getResult()
2810 revs
= out
.splitlines()
2811 self
.addTrunkRev(revs
[0])
2812 self
.addBranchRev(revs
[1])
2814 w
= self
.dovc(tmp
, ["push", self
.gitrepo
, "master", self
.branchname
])
2815 yield w
; w
.getResult()
2818 createRepository
= deferredGenerator(createRepository
)
2820 def vc_revise(self
):
2821 tmp
= os
.path
.join(self
.repbase
, "gittmp")
2823 log
.msg("vc_revise" + self
.gitrepo
)
2824 w
= self
.dovc(self
.repbase
, ["clone", self
.gitrepo
, "gittmp"])
2825 yield w
; w
.getResult()
2826 w
= self
.dovc(tmp
, ["config", "user.email", "buildbot-trial@localhost"])
2827 yield w
; w
.getResult()
2828 w
= self
.dovc(tmp
, ["config", "user.name", "Buildbot Trial"])
2829 yield w
; w
.getResult()
2832 version_c
= VERSION_C
% self
.version
2833 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
2835 w
= self
.dovc(tmp
, ["commit", "-m", "revised_to_%d" % self
.version
,
2837 yield w
; w
.getResult()
2838 w
= self
.dovc(tmp
, ["rev-parse", "master"])
2839 yield w
; out
= w
.getResult()
2840 self
.addTrunkRev(out
.strip())
2842 w
= self
.dovc(tmp
, ["push", self
.gitrepo
, "master"])
2843 yield w
; out
= w
.getResult()
2845 vc_revise
= deferredGenerator(vc_revise
)
2847 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
2848 assert os
.path
.abspath(workdir
) == workdir
2849 if os
.path
.exists(workdir
):
2850 rmdirRecursive(workdir
)
2852 w
= self
.dovc(self
.repbase
, ["clone", self
.gitrepo
, workdir
])
2853 yield w
; w
.getResult()
2854 w
= self
.dovc(workdir
, ["config", "user.email", "buildbot-trial@localhost"])
2855 yield w
; w
.getResult()
2856 w
= self
.dovc(workdir
, ["config", "user.name", "Buildbot Trial"])
2857 yield w
; w
.getResult()
2859 if branch
is not None:
2860 w
= self
.dovc(workdir
, ["checkout", "-b", branch
,
2861 "origin/%s" % branch
])
2862 yield w
; w
.getResult()
2864 # Hmm...why do nobody else bother to check out the correct
2866 w
= self
.dovc(workdir
, ["reset", "--hard", rev
])
2867 yield w
; w
.getResult()
2869 try_c_filename
= os
.path
.join(workdir
, "subdir", "subdir.c")
2870 open(try_c_filename
, "w").write(TRY_C
)
2871 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
2873 def vc_try_finish(self
, workdir
):
2874 rmdirRecursive(workdir
)
2876 class Git(VCBase
, unittest
.TestCase
):
2879 # No 'export' mode yet...
2881 vctype
= "source.Git"
2883 has_got_revision
= True
2885 def testCheckout(self
):
2886 self
.helper
.vcargs
= { 'repourl': self
.helper
.gitrepo
}
2887 d
= self
.do_vctest()
2890 def testPatch(self
):
2891 self
.helper
.vcargs
= { 'repourl': self
.helper
.gitrepo
,
2892 'branch': "master" }
2896 def testCheckoutBranch(self
):
2897 self
.helper
.vcargs
= { 'repourl': self
.helper
.gitrepo
,
2898 'branch': "master" }
2899 d
= self
.do_branch()
2903 self
.helper
.vcargs
= { 'repourl': self
.helper
.gitrepo
,
2904 'branch': "master" }
2905 d
= self
.do_getpatch()
2908 VCS
.registerVC(Git
.vc_name
, GitHelper())
2911 class Sources(unittest
.TestCase
):
2912 # TODO: this needs serious rethink
2913 def makeChange(self
, when
=None, revision
=None):
2915 when
= mktime_tz(parsedate_tz(when
))
2916 return changes
.Change("fred", [], "", when
=when
, revision
=revision
)
2919 r
= base
.BuildRequest("forced build", SourceStamp(), 'test_builder')
2921 s
= source
.CVS(cvsroot
=None, cvsmodule
=None)
2923 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), None)
2927 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2928 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2929 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2930 r
= base
.BuildRequest("forced", SourceStamp(changes
=c
), 'test_builder')
2931 submitted
= "Wed, 08 Sep 2004 09:04:00 -0700"
2932 r
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2934 s
= source
.CVS(cvsroot
=None, cvsmodule
=None)
2936 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
2937 "Wed, 08 Sep 2004 16:03:00 -0000")
2941 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2942 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2943 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2944 r
= base
.BuildRequest("forced", SourceStamp(changes
=c
), 'test_builder')
2945 submitted
= "Wed, 08 Sep 2004 09:04:00 -0700"
2946 r
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2948 s
= source
.CVS(cvsroot
=None, cvsmodule
=None, checkoutDelay
=10)
2950 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
2951 "Wed, 08 Sep 2004 16:02:10 -0000")
2955 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2956 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2957 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2958 r1
= base
.BuildRequest("forced", SourceStamp(changes
=c
), 'test_builder')
2959 submitted
= "Wed, 08 Sep 2004 09:04:00 -0700"
2960 r1
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2963 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:05:00 -0700"))
2964 r2
= base
.BuildRequest("forced", SourceStamp(changes
=c
), 'test_builder')
2965 submitted
= "Wed, 08 Sep 2004 09:07:00 -0700"
2966 r2
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2968 b
= base
.Build([r1
, r2
])
2969 s
= source
.CVS(cvsroot
=None, cvsmodule
=None)
2971 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
2972 "Wed, 08 Sep 2004 16:06:00 -0000")
2975 r
= base
.BuildRequest("forced", SourceStamp(), 'test_builder')
2977 s
= source
.SVN(svnurl
="dummy")
2979 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), None)
2983 c
.append(self
.makeChange(revision
=4))
2984 c
.append(self
.makeChange(revision
=10))
2985 c
.append(self
.makeChange(revision
=67))
2986 r
= base
.BuildRequest("forced", SourceStamp(changes
=c
), 'test_builder')
2988 s
= source
.SVN(svnurl
="dummy")
2990 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), 67)
2992 class Patch(VCBase
, unittest
.TestCase
):
2999 def testPatch(self
):
3000 # invoke 'patch' all by itself, to see if it works the way we think
3001 # it should. This is intended to ferret out some windows test
3003 helper
= BaseHelper()
3004 self
.workdir
= os
.path
.join("test_vc", "testPatch")
3005 helper
.populate(self
.workdir
)
3006 patch
= which("patch")[0]
3008 command
= [patch
, "-p0"]
3011 def sendUpdate(self
, status
):
3013 c
= commands
.ShellCommand(FakeBuilder(), command
, self
.workdir
,
3014 sendRC
=False, initialStdin
=p0_diff
)
3016 d
.addCallback(self
._testPatch
_1)
3019 def _testPatch_1(self
, res
):
3020 # make sure the file actually got patched
3021 subdir_c
= os
.path
.join(self
.workdir
, "subdir", "subdir.c")
3022 data
= open(subdir_c
, "r").read()
3023 self
.failUnlessIn("Hello patched subdir.\\n", data
)