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");
166 def _makedirsif(dir):
167 absdir
= os
.path
.abspath(dir)
168 if not os
.path
.isdir(absdir
):
175 # this is a helper class which keeps track of whether each VC system is
176 # available, and whether the repository for each has been created. There
177 # is one instance of this class, at module level, shared between all test
186 def registerVC(self
, name
, helper
):
187 self
._helpers
[name
] = helper
188 self
._repoReady
[name
] = False
190 def skipIfNotCapable(self
, name
):
191 """Either return None, or raise SkipTest"""
192 d
= self
.capable(name
)
195 raise unittest
.SkipTest(res
[1])
196 d
.addCallback(_maybeSkip
)
199 def capable(self
, name
):
200 """Return a Deferred that fires with (True,None) if this host offers
201 the given VC tool, or (False,excuse) if it does not (and therefore
202 the tests should be skipped)."""
204 if self
._isCapable
.has_key(name
):
205 if self
._isCapable
[name
]:
206 return defer
.succeed((True,None))
208 return defer
.succeed((False, self
._excuses
[name
]))
209 d
= defer
.maybeDeferred(self
._helpers
[name
].capable
)
212 self
._isCapable
[name
] = True
214 self
._excuses
[name
] = res
[1]
216 d
.addCallback(_capable
)
219 def getHelper(self
, name
):
220 return self
._helpers
[name
]
222 def createRepository(self
, name
):
223 """Return a Deferred that fires when the repository is set up."""
224 if self
._repoReady
[name
]:
225 return defer
.succeed(True)
226 d
= self
._helpers
[name
].createRepository()
228 self
._repoReady
[name
] = True
229 d
.addCallback(_ready
)
235 # the overall plan here:
237 # Each VC system is tested separately, all using the same source tree defined
238 # in the 'files' dictionary above. Each VC system gets its own TestCase
239 # subclass. The first test case that is run will create the repository during
240 # setUp(), making two branches: 'trunk' and 'branch'. The trunk gets a copy
241 # of all the files in 'files'. The variant of good.c is committed on the
244 # then testCheckout is run, which does a number of checkout/clobber/update
245 # builds. These all use trunk r1. It then runs self.fix(), which modifies
246 # 'fixable.c', then performs another build and makes sure the tree has been
249 # testBranch uses trunk-r1 and branch-r1, making sure that we clobber the
250 # tree properly when we switch between them
252 # testPatch does a trunk-r1 checkout and applies a patch.
254 # testTryGetPatch performs a trunk-r1 checkout, modifies some files, then
255 # verifies that tryclient.getSourceStamp figures out the base revision and
259 # vc_create makes a repository at r1 with three files: main.c, version.c, and
260 # subdir/foo.c . It also creates a branch from r1 (called b1) in which main.c
261 # says "hello branch" instead of "hello world". self.trunk[] contains
262 # revision stamps for everything on the trunk, and self.branch[] does the
263 # same for the branch.
265 # vc_revise() checks out a tree at HEAD, changes version.c, then checks it
266 # back in. The new version stamp is appended to self.trunk[]. The tree is
267 # removed afterwards.
269 # vc_try_checkout(workdir, rev) checks out a tree at REV, then changes
270 # subdir/subdir.c to say 'Hello try'
271 # vc_try_finish(workdir) removes the tree and cleans up any VC state
272 # necessary (like deleting the Arch archive entry).
282 # this is also responsible for setting self.vcexe
283 raise NotImplementedError
285 def createBasedir(self
):
286 # you must call this from createRepository
287 self
.repbase
= os
.path
.abspath(os
.path
.join("test_vc",
289 _makedirsif(self
.repbase
)
291 def createRepository(self
):
292 # this will only be called once per process
293 raise NotImplementedError
295 def populate(self
, basedir
):
297 _makedirsif(os
.path
.join(basedir
, "subdir"))
299 open(os
.path
.join(basedir
, "main.c"), "w").write(MAIN_C
)
301 version_c
= VERSION_C
% self
.version
302 open(os
.path
.join(basedir
, "version.c"), "w").write(version_c
)
303 open(os
.path
.join(basedir
, "main.c"), "w").write(MAIN_C
)
304 open(os
.path
.join(basedir
, "subdir", "subdir.c"), "w").write(SUBDIR_C
)
306 def populate_branch(self
, basedir
):
307 open(os
.path
.join(basedir
, "main.c"), "w").write(BRANCH_C
)
309 def addTrunkRev(self
, rev
):
310 self
.trunk
.append(rev
)
311 self
.allrevs
.append(rev
)
312 def addBranchRev(self
, rev
):
313 self
.branch
.append(rev
)
314 self
.allrevs
.append(rev
)
316 def runCommand(self
, basedir
, command
, failureIsOk
=False,
317 stdin
=None, env
=None):
318 # all commands passed to do() should be strings or lists. If they are
319 # strings, none of the arguments may have spaces. This makes the
320 # commands less verbose at the expense of restricting what they can
322 if type(command
) not in (list, tuple):
323 command
= command
.split(" ")
325 # execute scripts through cmd.exe on windows, to avoid space in path issues
326 if sys
.platform
== 'win32' and command
[0].lower().endswith('.cmd'):
327 command
= [which('cmd.exe')[0], '/c', 'call'] + command
331 print "do %s" % command
332 print " in basedir %s" % basedir
334 print " STDIN:\n", stdin
, "\n--STDIN DONE"
337 env
= os
.environ
.copy()
339 d
= myGetProcessOutputAndValue(command
[0], command
[1:],
340 env
=env
, path
=basedir
,
342 def check((out
, err
, code
)):
345 print "command was: %s" % command
346 if out
: print "out: %s" % out
347 if err
: print "err: %s" % err
348 print "code: %s" % code
349 if code
!= 0 and not failureIsOk
:
350 log
.msg("command %s finished with exit code %d" %
352 log
.msg(" and stdout %s" % (out
,))
353 log
.msg(" and stderr %s" % (err
,))
354 raise RuntimeError("command %s finished with exit code %d"
356 + ": see logs for stdout")
361 def do(self
, basedir
, command
, failureIsOk
=False, stdin
=None, env
=None):
362 d
= self
.runCommand(basedir
, command
, failureIsOk
=failureIsOk
,
363 stdin
=stdin
, env
=env
)
364 return waitForDeferred(d
)
366 def dovc(self
, basedir
, command
, failureIsOk
=False, stdin
=None, env
=None):
367 """Like do(), but the VC binary will be prepended to COMMAND."""
368 if isinstance(command
, (str, unicode)):
369 command
= [self
.vcexe
] + command
.split(' ')
372 command
= [self
.vcexe
] + command
373 return self
.do(basedir
, command
, failureIsOk
, stdin
, env
)
375 class VCBase(SignalMixin
):
377 createdRepository
= False
384 has_got_revision
= False
385 has_got_revision_branches_are_merged
= False # for SVN
387 def failUnlessIn(self
, substring
, string
, msg
=None):
388 # trial provides a version of this that requires python-2.3 to test
391 msg
= ("did not see the expected substring '%s' in string '%s'" %
393 self
.failUnless(string
.find(substring
) != -1, msg
)
396 self
.setUpSignalHandler()
397 d
= VCS
.skipIfNotCapable(self
.vc_name
)
398 d
.addCallback(self
._setUp
1)
401 def _setUp1(self
, res
):
402 self
.helper
= VCS
.getHelper(self
.vc_name
)
404 if os
.path
.exists("basedir"):
405 rmdirRecursive("basedir")
407 self
.master
= master
.BuildMaster("basedir")
408 self
.slavebase
= os
.path
.abspath("slavebase")
409 if os
.path
.exists(self
.slavebase
):
410 rmdirRecursive(self
.slavebase
)
411 os
.mkdir("slavebase")
413 d
= VCS
.createRepository(self
.vc_name
)
416 def connectSlave(self
):
417 port
= self
.master
.slavePort
._port
.getHost().port
418 slave
= bot
.BuildSlave("localhost", port
, "bot1", "sekrit",
419 self
.slavebase
, keepalive
=0, usePTY
=False)
422 d
= self
.master
.botmaster
.waitUntilBuilderAttached("vc")
425 def loadConfig(self
, config
):
426 # reloading the config file causes a new 'listDirs' command to be
427 # sent to the slave. To synchronize on this properly, it is easiest
428 # to stop and restart the slave.
429 d
= defer
.succeed(None)
431 d
= self
.master
.botmaster
.waitUntilBuilderDetached("vc")
432 self
.slave
.stopService()
433 d
.addCallback(lambda res
: self
.master
.loadConfig(config
))
434 d
.addCallback(lambda res
: self
.connectSlave())
438 # launch an HTTP server to serve the repository files
439 self
.root
= static
.File(self
.helper
.repbase
)
440 self
.site
= server
.Site(self
.root
)
441 self
.httpServer
= reactor
.listenTCP(0, self
.site
)
442 self
.httpPort
= self
.httpServer
.getHost().port
444 def doBuild(self
, shouldSucceed
=True, ss
=None):
445 c
= interfaces
.IControl(self
.master
)
449 #print "doBuild(ss: b=%s rev=%s)" % (ss.branch, ss.revision)
450 req
= base
.BuildRequest("test_vc forced build", ss
, 'test_builder')
451 d
= req
.waitUntilFinished()
452 c
.getBuilder("vc").requestBuild(req
)
453 d
.addCallback(self
._doBuild
_1, shouldSucceed
)
455 def _doBuild_1(self
, bs
, shouldSucceed
):
457 if r
!= SUCCESS
and shouldSucceed
:
460 if not bs
.isFinished():
461 print "Hey, build wasn't even finished!"
462 print "Build did not succeed:", r
, bs
.getText()
463 for s
in bs
.getSteps():
464 for l
in s
.getLogs():
465 print "--- START step %s / log %s ---" % (s
.getName(),
467 print l
.getTextWithHeaders()
470 self
.fail("build did not succeed")
473 def printLogs(self
, bs
):
474 for s
in bs
.getSteps():
475 for l
in s
.getLogs():
476 print "--- START step %s / log %s ---" % (s
.getName(),
478 print l
.getTextWithHeaders()
482 def touch(self
, d
, f
):
483 open(os
.path
.join(d
,f
),"w").close()
484 def shouldExist(self
, *args
):
485 target
= os
.path
.join(*args
)
486 self
.failUnless(os
.path
.exists(target
),
487 "expected to find %s but didn't" % target
)
488 def shouldNotExist(self
, *args
):
489 target
= os
.path
.join(*args
)
490 self
.failIf(os
.path
.exists(target
),
491 "expected to NOT find %s, but did" % target
)
492 def shouldContain(self
, d
, f
, contents
):
493 c
= open(os
.path
.join(d
, f
), "r").read()
494 self
.failUnlessIn(contents
, c
)
496 def checkGotRevision(self
, bs
, expected
):
497 if self
.has_got_revision
:
498 self
.failUnlessEqual(bs
.getProperty("got_revision"), str(expected
))
500 def checkGotRevisionIsLatest(self
, bs
):
501 expected
= self
.helper
.trunk
[-1]
502 if self
.has_got_revision_branches_are_merged
:
503 expected
= self
.helper
.allrevs
[-1]
504 self
.checkGotRevision(bs
, expected
)
506 def do_vctest(self
, testRetry
=True):
508 args
= self
.helper
.vcargs
510 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
511 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
512 # woo double-substitution
513 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
514 for k
,v
in args
.items():
515 s
+= ", %s=%s" % (k
, repr(v
))
517 config
= config_vc
% s
519 m
.loadConfig(config
% 'clobber')
523 d
= self
.connectSlave()
524 d
.addCallback(lambda res
: log
.msg("testing clobber"))
525 d
.addCallback(self
._do
_vctest
_clobber
)
526 d
.addCallback(lambda res
: log
.msg("doing update"))
527 d
.addCallback(lambda res
: self
.loadConfig(config
% 'update'))
528 d
.addCallback(lambda res
: log
.msg("testing update"))
529 d
.addCallback(self
._do
_vctest
_update
)
531 d
.addCallback(lambda res
: log
.msg("testing update retry"))
532 d
.addCallback(self
._do
_vctest
_update
_retry
)
533 d
.addCallback(lambda res
: log
.msg("doing copy"))
534 d
.addCallback(lambda res
: self
.loadConfig(config
% 'copy'))
535 d
.addCallback(lambda res
: log
.msg("testing copy"))
536 d
.addCallback(self
._do
_vctest
_copy
)
537 d
.addCallback(lambda res
: log
.msg("did copy test"))
539 d
.addCallback(lambda res
: log
.msg("doing export"))
540 d
.addCallback(lambda res
: self
.loadConfig(config
% 'export'))
541 d
.addCallback(lambda res
: log
.msg("testing export"))
542 d
.addCallback(self
._do
_vctest
_export
)
543 d
.addCallback(lambda res
: log
.msg("did export test"))
546 def _do_vctest_clobber(self
, res
):
547 d
= self
.doBuild() # initial checkout
548 d
.addCallback(self
._do
_vctest
_clobber
_1)
550 def _do_vctest_clobber_1(self
, bs
):
551 self
.shouldExist(self
.workdir
, "main.c")
552 self
.shouldExist(self
.workdir
, "version.c")
553 self
.shouldExist(self
.workdir
, "subdir", "subdir.c")
555 self
.shouldExist(self
.workdir
, self
.metadir
)
556 self
.failUnlessEqual(bs
.getProperty("revision"), None)
557 self
.failUnlessEqual(bs
.getProperty("branch"), None)
558 self
.checkGotRevisionIsLatest(bs
)
560 self
.touch(self
.workdir
, "newfile")
561 self
.shouldExist(self
.workdir
, "newfile")
562 d
= self
.doBuild() # rebuild clobbers workdir
563 d
.addCallback(self
._do
_vctest
_clobber
_2)
565 def _do_vctest_clobber_2(self
, res
):
566 self
.shouldNotExist(self
.workdir
, "newfile")
567 # do a checkout to a specific version. Mercurial-over-HTTP (when
568 # either client or server is older than hg-0.9.2) cannot do this
569 # directly, so it must checkout HEAD and then update back to the
570 # requested revision.
571 d
= self
.doBuild(ss
=SourceStamp(revision
=self
.helper
.trunk
[0]))
572 d
.addCallback(self
._do
_vctest
_clobber
_3)
574 def _do_vctest_clobber_3(self
, bs
):
575 self
.shouldExist(self
.workdir
, "main.c")
576 self
.shouldExist(self
.workdir
, "version.c")
577 self
.shouldExist(self
.workdir
, "subdir", "subdir.c")
579 self
.shouldExist(self
.workdir
, self
.metadir
)
580 self
.failUnlessEqual(bs
.getProperty("revision"), self
.helper
.trunk
[0] or None)
581 self
.failUnlessEqual(bs
.getProperty("branch"), None)
582 self
.checkGotRevision(bs
, self
.helper
.trunk
[0])
583 # leave the tree at HEAD
584 return self
.doBuild()
587 def _do_vctest_update(self
, res
):
588 log
.msg("_do_vctest_update")
589 d
= self
.doBuild() # rebuild with update
590 d
.addCallback(self
._do
_vctest
_update
_1)
592 def _do_vctest_update_1(self
, bs
):
593 log
.msg("_do_vctest_update_1")
594 self
.shouldExist(self
.workdir
, "main.c")
595 self
.shouldExist(self
.workdir
, "version.c")
596 self
.shouldContain(self
.workdir
, "version.c",
597 "version=%d" % self
.helper
.version
)
599 self
.shouldExist(self
.workdir
, self
.metadir
)
600 self
.failUnlessEqual(bs
.getProperty("revision"), None)
601 self
.checkGotRevisionIsLatest(bs
)
603 self
.touch(self
.workdir
, "newfile")
604 d
= self
.doBuild() # update rebuild leaves new files
605 d
.addCallback(self
._do
_vctest
_update
_2)
607 def _do_vctest_update_2(self
, bs
):
608 log
.msg("_do_vctest_update_2")
609 self
.shouldExist(self
.workdir
, "main.c")
610 self
.shouldExist(self
.workdir
, "version.c")
611 self
.touch(self
.workdir
, "newfile")
612 # now make a change to the repository and make sure we pick it up
613 d
= self
.helper
.vc_revise()
614 d
.addCallback(lambda res
: self
.doBuild())
615 d
.addCallback(self
._do
_vctest
_update
_3)
617 def _do_vctest_update_3(self
, bs
):
618 log
.msg("_do_vctest_update_3")
619 self
.shouldExist(self
.workdir
, "main.c")
620 self
.shouldExist(self
.workdir
, "version.c")
621 self
.shouldContain(self
.workdir
, "version.c",
622 "version=%d" % self
.helper
.version
)
623 self
.shouldExist(self
.workdir
, "newfile")
624 self
.failUnlessEqual(bs
.getProperty("revision"), None)
625 self
.checkGotRevisionIsLatest(bs
)
627 # now "update" to an older revision
628 d
= self
.doBuild(ss
=SourceStamp(revision
=self
.helper
.trunk
[-2]))
629 d
.addCallback(self
._do
_vctest
_update
_4)
631 def _do_vctest_update_4(self
, bs
):
632 log
.msg("_do_vctest_update_4")
633 self
.shouldExist(self
.workdir
, "main.c")
634 self
.shouldExist(self
.workdir
, "version.c")
635 self
.shouldContain(self
.workdir
, "version.c",
636 "version=%d" % (self
.helper
.version
-1))
637 self
.failUnlessEqual(bs
.getProperty("revision"),
638 self
.helper
.trunk
[-2] or None)
639 self
.checkGotRevision(bs
, self
.helper
.trunk
[-2])
641 # now update to the newer revision
642 d
= self
.doBuild(ss
=SourceStamp(revision
=self
.helper
.trunk
[-1]))
643 d
.addCallback(self
._do
_vctest
_update
_5)
645 def _do_vctest_update_5(self
, bs
):
646 log
.msg("_do_vctest_update_5")
647 self
.shouldExist(self
.workdir
, "main.c")
648 self
.shouldExist(self
.workdir
, "version.c")
649 self
.shouldContain(self
.workdir
, "version.c",
650 "version=%d" % self
.helper
.version
)
651 self
.failUnlessEqual(bs
.getProperty("revision"),
652 self
.helper
.trunk
[-1] or None)
653 self
.checkGotRevision(bs
, self
.helper
.trunk
[-1])
656 def _do_vctest_update_retry(self
, res
):
657 # certain local changes will prevent an update from working. The
658 # most common is to replace a file with a directory, or vice
659 # versa. The slave code should spot the failure and do a
661 os
.unlink(os
.path
.join(self
.workdir
, "main.c"))
662 os
.mkdir(os
.path
.join(self
.workdir
, "main.c"))
663 self
.touch(os
.path
.join(self
.workdir
, "main.c"), "foo")
664 self
.touch(self
.workdir
, "newfile")
666 d
= self
.doBuild() # update, but must clobber to handle the error
667 d
.addCallback(self
._do
_vctest
_update
_retry
_1)
669 def _do_vctest_update_retry_1(self
, bs
):
670 # SVN-1.4.0 doesn't seem to have any problem with the
671 # file-turned-directory issue (although older versions did). So don't
672 # actually check that the tree was clobbered.. as long as the update
673 # succeeded (checked by doBuild), that should be good enough.
674 #self.shouldNotExist(self.workdir, "newfile")
677 def _do_vctest_copy(self
, res
):
678 log
.msg("_do_vctest_copy 1")
679 d
= self
.doBuild() # copy rebuild clobbers new files
680 d
.addCallback(self
._do
_vctest
_copy
_1)
682 def _do_vctest_copy_1(self
, bs
):
683 log
.msg("_do_vctest_copy 2")
685 self
.shouldExist(self
.workdir
, self
.metadir
)
686 self
.shouldNotExist(self
.workdir
, "newfile")
687 self
.touch(self
.workdir
, "newfile")
688 self
.touch(self
.vcdir
, "newvcfile")
689 self
.failUnlessEqual(bs
.getProperty("revision"), None)
690 self
.checkGotRevisionIsLatest(bs
)
692 d
= self
.doBuild() # copy rebuild clobbers new files
693 d
.addCallback(self
._do
_vctest
_copy
_2)
695 def _do_vctest_copy_2(self
, bs
):
696 log
.msg("_do_vctest_copy 3")
698 self
.shouldExist(self
.workdir
, self
.metadir
)
699 self
.shouldNotExist(self
.workdir
, "newfile")
700 self
.shouldExist(self
.vcdir
, "newvcfile")
701 self
.shouldExist(self
.workdir
, "newvcfile")
702 self
.failUnlessEqual(bs
.getProperty("revision"), None)
703 self
.checkGotRevisionIsLatest(bs
)
704 self
.touch(self
.workdir
, "newfile")
706 def _do_vctest_export(self
, res
):
707 d
= self
.doBuild() # export rebuild clobbers new files
708 d
.addCallback(self
._do
_vctest
_export
_1)
710 def _do_vctest_export_1(self
, bs
):
711 self
.shouldNotExist(self
.workdir
, self
.metadir
)
712 self
.shouldNotExist(self
.workdir
, "newfile")
713 self
.failUnlessEqual(bs
.getProperty("revision"), None)
714 #self.checkGotRevisionIsLatest(bs)
715 # VC 'export' is not required to have a got_revision
716 self
.touch(self
.workdir
, "newfile")
718 d
= self
.doBuild() # export rebuild clobbers new files
719 d
.addCallback(self
._do
_vctest
_export
_2)
721 def _do_vctest_export_2(self
, bs
):
722 self
.shouldNotExist(self
.workdir
, self
.metadir
)
723 self
.shouldNotExist(self
.workdir
, "newfile")
724 self
.failUnlessEqual(bs
.getProperty("revision"), None)
725 #self.checkGotRevisionIsLatest(bs)
726 # VC 'export' is not required to have a got_revision
730 args
= self
.helper
.vcargs
732 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
733 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
734 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
735 for k
,v
in args
.items():
736 s
+= ", %s=%s" % (k
, repr(v
))
738 self
.config
= config_vc
% s
740 m
.loadConfig(self
.config
% "clobber")
744 ss
= SourceStamp(revision
=self
.helper
.trunk
[-1], patch
=(0, p0_diff
))
746 d
= self
.connectSlave()
747 d
.addCallback(lambda res
: self
.doBuild(ss
=ss
))
748 d
.addCallback(self
._doPatch
_1)
750 def _doPatch_1(self
, bs
):
751 self
.shouldContain(self
.workdir
, "version.c",
752 "version=%d" % self
.helper
.version
)
753 # make sure the file actually got patched
754 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
755 "subdir", "subdir.c")
756 data
= open(subdir_c
, "r").read()
757 self
.failUnlessIn("Hello patched subdir.\\n", data
)
758 self
.failUnlessEqual(bs
.getProperty("revision"),
759 self
.helper
.trunk
[-1] or None)
760 self
.checkGotRevision(bs
, self
.helper
.trunk
[-1])
762 # make sure that a rebuild does not use the leftover patched workdir
763 d
= self
.master
.loadConfig(self
.config
% "update")
764 d
.addCallback(lambda res
: self
.doBuild(ss
=None))
765 d
.addCallback(self
._doPatch
_2)
767 def _doPatch_2(self
, bs
):
768 # make sure the file is back to its original
769 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
770 "subdir", "subdir.c")
771 data
= open(subdir_c
, "r").read()
772 self
.failUnlessIn("Hello subdir.\\n", data
)
773 self
.failUnlessEqual(bs
.getProperty("revision"), None)
774 self
.checkGotRevisionIsLatest(bs
)
776 # now make sure we can patch an older revision. We need at least two
777 # revisions here, so we might have to create one first
778 if len(self
.helper
.trunk
) < 2:
779 d
= self
.helper
.vc_revise()
780 d
.addCallback(self
._doPatch
_3)
782 return self
._doPatch
_3()
784 def _doPatch_3(self
, res
=None):
785 ss
= SourceStamp(revision
=self
.helper
.trunk
[-2], patch
=(0, p0_diff
))
786 d
= self
.doBuild(ss
=ss
)
787 d
.addCallback(self
._doPatch
_4)
789 def _doPatch_4(self
, bs
):
790 self
.shouldContain(self
.workdir
, "version.c",
791 "version=%d" % (self
.helper
.version
-1))
792 # and make sure the file actually got patched
793 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
794 "subdir", "subdir.c")
795 data
= open(subdir_c
, "r").read()
796 self
.failUnlessIn("Hello patched subdir.\\n", data
)
797 self
.failUnlessEqual(bs
.getProperty("revision"),
798 self
.helper
.trunk
[-2] or None)
799 self
.checkGotRevision(bs
, self
.helper
.trunk
[-2])
801 # now check that we can patch a branch
802 ss
= SourceStamp(branch
=self
.helper
.branchname
,
803 revision
=self
.helper
.branch
[-1],
805 d
= self
.doBuild(ss
=ss
)
806 d
.addCallback(self
._doPatch
_5)
808 def _doPatch_5(self
, bs
):
809 self
.shouldContain(self
.workdir
, "version.c",
811 self
.shouldContain(self
.workdir
, "main.c", "Hello branch.")
812 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
813 "subdir", "subdir.c")
814 data
= open(subdir_c
, "r").read()
815 self
.failUnlessIn("Hello patched subdir.\\n", data
)
816 self
.failUnlessEqual(bs
.getProperty("revision"),
817 self
.helper
.branch
[-1] or None)
818 self
.failUnlessEqual(bs
.getProperty("branch"), self
.helper
.branchname
or None)
819 self
.checkGotRevision(bs
, self
.helper
.branch
[-1])
822 def do_vctest_once(self
, shouldSucceed
):
825 args
= self
.helper
.vcargs
826 vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
827 workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
828 # woo double-substitution
829 s
= "s(%s, timeout=200, workdir='build', mode='clobber'" % (vctype
,)
830 for k
,v
in args
.items():
831 s
+= ", %s=%s" % (k
, repr(v
))
833 config
= config_vc
% s
840 d
= self
.doBuild(shouldSucceed
) # initial checkout
846 args
= self
.helper
.vcargs
848 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
849 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
850 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
851 for k
,v
in args
.items():
852 s
+= ", %s=%s" % (k
, repr(v
))
854 self
.config
= config_vc
% s
856 m
.loadConfig(self
.config
% "update")
860 # first we do a build of the trunk
861 d
= self
.connectSlave()
862 d
.addCallback(lambda res
: self
.doBuild(ss
=SourceStamp()))
863 d
.addCallback(self
._doBranch
_1)
865 def _doBranch_1(self
, bs
):
866 log
.msg("_doBranch_1")
867 # make sure the checkout was of the trunk
868 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
869 data
= open(main_c
, "r").read()
870 self
.failUnlessIn("Hello world.", data
)
872 # now do a checkout on the branch. The change in branch name should
874 self
.touch(self
.workdir
, "newfile")
875 d
= self
.doBuild(ss
=SourceStamp(branch
=self
.helper
.branchname
))
876 d
.addCallback(self
._doBranch
_2)
878 def _doBranch_2(self
, bs
):
879 log
.msg("_doBranch_2")
880 # make sure it was on the branch
881 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
882 data
= open(main_c
, "r").read()
883 self
.failUnlessIn("Hello branch.", data
)
884 # and make sure the tree was clobbered
885 self
.shouldNotExist(self
.workdir
, "newfile")
887 # doing another build on the same branch should not clobber the tree
888 self
.touch(self
.workdir
, "newbranchfile")
889 d
= self
.doBuild(ss
=SourceStamp(branch
=self
.helper
.branchname
))
890 d
.addCallback(self
._doBranch
_3)
892 def _doBranch_3(self
, bs
):
893 log
.msg("_doBranch_3")
894 # make sure it is still on the branch
895 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
896 data
= open(main_c
, "r").read()
897 self
.failUnlessIn("Hello branch.", data
)
898 # and make sure the tree was not clobbered
899 self
.shouldExist(self
.workdir
, "newbranchfile")
901 # now make sure that a non-branch checkout clobbers the tree
902 d
= self
.doBuild(ss
=SourceStamp())
903 d
.addCallback(self
._doBranch
_4)
905 def _doBranch_4(self
, bs
):
906 log
.msg("_doBranch_4")
907 # make sure it was on the trunk
908 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
909 data
= open(main_c
, "r").read()
910 self
.failUnlessIn("Hello world.", data
)
911 self
.shouldNotExist(self
.workdir
, "newbranchfile")
913 def do_getpatch(self
, doBranch
=True):
914 log
.msg("do_getpatch")
915 # prepare a buildslave to do checkouts
917 args
= self
.helper
.vcargs
919 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
920 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
921 # woo double-substitution
922 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
923 for k
,v
in args
.items():
924 s
+= ", %s=%s" % (k
, repr(v
))
926 config
= config_vc
% s
928 m
.loadConfig(config
% 'clobber')
932 d
= self
.connectSlave()
934 # then set up the "developer's tree". first we modify a tree from the
936 tmpdir
= "try_workdir"
937 self
.trydir
= os
.path
.join(self
.helper
.repbase
, tmpdir
)
938 rmdirRecursive(self
.trydir
)
939 d
.addCallback(self
.do_getpatch_trunkhead
)
940 d
.addCallback(self
.do_getpatch_trunkold
)
942 d
.addCallback(self
.do_getpatch_branch
)
943 d
.addCallback(self
.do_getpatch_finish
)
946 def do_getpatch_finish(self
, res
):
947 log
.msg("do_getpatch_finish")
948 self
.helper
.vc_try_finish(self
.trydir
)
951 def try_shouldMatch(self
, filename
):
952 devfilename
= os
.path
.join(self
.trydir
, filename
)
953 devfile
= open(devfilename
, "r").read()
954 slavefilename
= os
.path
.join(self
.workdir
, filename
)
955 slavefile
= open(slavefilename
, "r").read()
956 self
.failUnlessEqual(devfile
, slavefile
,
957 ("slavefile (%s) contains '%s'. "
958 "developer's file (%s) contains '%s'. "
959 "These ought to match") %
960 (slavefilename
, slavefile
,
961 devfilename
, devfile
))
963 def do_getpatch_trunkhead(self
, res
):
964 log
.msg("do_getpatch_trunkhead")
965 d
= self
.helper
.vc_try_checkout(self
.trydir
, self
.helper
.trunk
[-1])
966 d
.addCallback(self
._do
_getpatch
_trunkhead
_1)
968 def _do_getpatch_trunkhead_1(self
, res
):
969 log
.msg("_do_getpatch_trunkhead_1")
970 d
= tryclient
.getSourceStamp(self
.vctype_try
, self
.trydir
, None)
971 d
.addCallback(self
._do
_getpatch
_trunkhead
_2)
973 def _do_getpatch_trunkhead_2(self
, ss
):
974 log
.msg("_do_getpatch_trunkhead_2")
975 d
= self
.doBuild(ss
=ss
)
976 d
.addCallback(self
._do
_getpatch
_trunkhead
_3)
978 def _do_getpatch_trunkhead_3(self
, res
):
979 log
.msg("_do_getpatch_trunkhead_3")
980 # verify that the resulting buildslave tree matches the developer's
981 self
.try_shouldMatch("main.c")
982 self
.try_shouldMatch("version.c")
983 self
.try_shouldMatch(os
.path
.join("subdir", "subdir.c"))
985 def do_getpatch_trunkold(self
, res
):
986 log
.msg("do_getpatch_trunkold")
987 # now try a tree from an older revision. We need at least two
988 # revisions here, so we might have to create one first
989 if len(self
.helper
.trunk
) < 2:
990 d
= self
.helper
.vc_revise()
991 d
.addCallback(self
._do
_getpatch
_trunkold
_1)
993 return self
._do
_getpatch
_trunkold
_1()
994 def _do_getpatch_trunkold_1(self
, res
=None):
995 log
.msg("_do_getpatch_trunkold_1")
996 d
= self
.helper
.vc_try_checkout(self
.trydir
, self
.helper
.trunk
[-2])
997 d
.addCallback(self
._do
_getpatch
_trunkold
_2)
999 def _do_getpatch_trunkold_2(self
, res
):
1000 log
.msg("_do_getpatch_trunkold_2")
1001 d
= tryclient
.getSourceStamp(self
.vctype_try
, self
.trydir
, None)
1002 d
.addCallback(self
._do
_getpatch
_trunkold
_3)
1004 def _do_getpatch_trunkold_3(self
, ss
):
1005 log
.msg("_do_getpatch_trunkold_3")
1006 d
= self
.doBuild(ss
=ss
)
1007 d
.addCallback(self
._do
_getpatch
_trunkold
_4)
1009 def _do_getpatch_trunkold_4(self
, res
):
1010 log
.msg("_do_getpatch_trunkold_4")
1011 # verify that the resulting buildslave tree matches the developer's
1012 self
.try_shouldMatch("main.c")
1013 self
.try_shouldMatch("version.c")
1014 self
.try_shouldMatch(os
.path
.join("subdir", "subdir.c"))
1016 def do_getpatch_branch(self
, res
):
1017 log
.msg("do_getpatch_branch")
1018 # now try a tree from a branch
1019 d
= self
.helper
.vc_try_checkout(self
.trydir
, self
.helper
.branch
[-1],
1020 self
.helper
.branchname
)
1021 d
.addCallback(self
._do
_getpatch
_branch
_1)
1023 def _do_getpatch_branch_1(self
, res
):
1024 log
.msg("_do_getpatch_branch_1")
1025 d
= tryclient
.getSourceStamp(self
.vctype_try
, self
.trydir
,
1026 self
.helper
.try_branchname
)
1027 d
.addCallback(self
._do
_getpatch
_branch
_2)
1029 def _do_getpatch_branch_2(self
, ss
):
1030 log
.msg("_do_getpatch_branch_2")
1031 d
= self
.doBuild(ss
=ss
)
1032 d
.addCallback(self
._do
_getpatch
_branch
_3)
1034 def _do_getpatch_branch_3(self
, res
):
1035 log
.msg("_do_getpatch_branch_3")
1036 # verify that the resulting buildslave tree matches the developer's
1037 self
.try_shouldMatch("main.c")
1038 self
.try_shouldMatch("version.c")
1039 self
.try_shouldMatch(os
.path
.join("subdir", "subdir.c"))
1042 def dumpPatch(self
, patch
):
1043 # this exists to help me figure out the right 'patchlevel' value
1044 # should be returned by tryclient.getSourceStamp
1046 open(n
,"w").write(patch
)
1047 d
= self
.runCommand(".", ["lsdiff", n
])
1048 def p(res
): print "lsdiff:", res
.strip().split("\n")
1054 self
.tearDownSignalHandler()
1055 d
= defer
.succeed(None)
1057 d2
= self
.master
.botmaster
.waitUntilBuilderDetached("vc")
1058 d
.addCallback(lambda res
: self
.slave
.stopService())
1059 d
.addCallback(lambda res
: d2
)
1061 d
.addCallback(lambda res
: self
.master
.stopService())
1063 d
.addCallback(lambda res
: self
.httpServer
.stopListening())
1064 def stopHTTPTimer():
1065 from twisted
.web
import http
1066 http
._logDateTimeStop
() # shut down the internal timer. DUMB!
1067 d
.addCallback(lambda res
: stopHTTPTimer())
1068 d
.addCallback(lambda res
: self
.tearDown2())
1071 def tearDown2(self
):
1074 class CVSHelper(BaseHelper
):
1075 branchname
= "branch"
1076 try_branchname
= "branch"
1079 cvspaths
= which('cvs')
1081 return (False, "CVS is not installed")
1082 # cvs-1.10 (as shipped with OS-X 10.3 "Panther") is too old for this
1083 # test. There is a situation where we check out a tree, make a
1084 # change, then commit it back, and CVS refuses to believe that we're
1085 # operating in a CVS tree. I tested cvs-1.12.9 and it works ok, OS-X
1086 # 10.4 "Tiger" comes with cvs-1.11, but I haven't tested that yet.
1087 # For now, skip the tests if we've got 1.10 .
1088 log
.msg("running %s --version.." % (cvspaths
[0],))
1089 d
= utils
.getProcessOutput(cvspaths
[0], ["--version"],
1091 d
.addCallback(self
._capable
, cvspaths
[0])
1094 def _capable(self
, v
, vcexe
):
1095 # Consider also CVS for NT which announces itself like
1096 # Concurrent Versions System (CVSNT) 2.0.51d (client/server)
1097 m
= re
.search(r
'\(CVS[NT]*\) ([\d.]+)', v
)
1099 log
.msg("couldn't identify CVS version number in output:")
1100 log
.msg("'''%s'''" % v
)
1101 log
.msg("skipping tests")
1102 return (False, "Found CVS but couldn't identify its version")
1104 log
.msg("found CVS version '%s'" % ver
)
1106 return (False, "Found CVS, but it is too old")
1111 # this timestamp is eventually passed to CVS in a -D argument, and
1112 # strftime's %z specifier doesn't seem to work reliably (I get +0000
1113 # where I should get +0700 under linux sometimes, and windows seems
1114 # to want to put a verbose 'Eastern Standard Time' in there), so
1115 # leave off the timezone specifier and treat this as localtime. A
1116 # valid alternative would be to use a hard-coded +0000 and
1118 return time
.strftime("%Y-%m-%d %H:%M:%S", time
.localtime())
1120 def createRepository(self
):
1121 self
.createBasedir()
1122 self
.cvsrep
= cvsrep
= os
.path
.join(self
.repbase
, "CVS-Repository")
1123 tmp
= os
.path
.join(self
.repbase
, "cvstmp")
1125 w
= self
.dovc(self
.repbase
, ['-d', cvsrep
, 'init'])
1126 yield w
; w
.getResult() # we must getResult() to raise any exceptions
1129 cmd
= ['-d', self
.cvsrep
, 'import',
1130 '-m', 'sample_project_files', 'sample', 'vendortag', 'start']
1131 w
= self
.dovc(tmp
, cmd
)
1132 yield w
; w
.getResult()
1134 # take a timestamp as the first revision number
1136 self
.addTrunkRev(self
.getdate())
1139 w
= self
.dovc(self
.repbase
,
1140 ['-d', self
.cvsrep
, 'checkout', '-d', 'cvstmp', 'sample'])
1141 yield w
; w
.getResult()
1143 w
= self
.dovc(tmp
, ['tag', '-b', self
.branchname
])
1144 yield w
; w
.getResult()
1145 self
.populate_branch(tmp
)
1147 ['commit', '-m', 'commit_on_branch', '-r', self
.branchname
])
1148 yield w
; w
.getResult()
1151 self
.addBranchRev(self
.getdate())
1153 self
.vcargs
= { 'cvsroot': self
.cvsrep
, 'cvsmodule': "sample" }
1154 createRepository
= deferredGenerator(createRepository
)
1157 def vc_revise(self
):
1158 tmp
= os
.path
.join(self
.repbase
, "cvstmp")
1160 w
= self
.dovc(self
.repbase
,
1161 ['-d', self
.cvsrep
, 'checkout', '-d', 'cvstmp', 'sample'])
1162 yield w
; w
.getResult()
1164 version_c
= VERSION_C
% self
.version
1165 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1167 ['commit', '-m', 'revised_to_%d' % self
.version
, 'version.c'])
1168 yield w
; w
.getResult()
1171 self
.addTrunkRev(self
.getdate())
1173 vc_revise
= deferredGenerator(vc_revise
)
1175 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1176 # 'workdir' is an absolute path
1178 # get rid of timezone info, which might not be parsed
1179 rev
= re
.sub("[^0-9 :-]","",rev
)
1180 rev
= re
.sub(" ","",rev
)
1181 assert os
.path
.abspath(workdir
) == workdir
1182 cmd
= ["-d", self
.cvsrep
, "checkout",
1185 if branch
is not None:
1188 cmd
.append("sample")
1189 w
= self
.dovc(self
.repbase
, cmd
)
1190 yield w
; w
.getResult()
1191 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1192 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1194 def vc_try_finish(self
, workdir
):
1195 rmdirRecursive(workdir
)
1197 class CVS(VCBase
, unittest
.TestCase
):
1201 vctype
= "source.CVS"
1203 # CVS gives us got_revision, but it is based entirely upon the local
1204 # clock, which means it is unlikely to match the timestamp taken earlier.
1205 # This might be enough for common use, but won't be good enough for our
1206 # tests to accept, so pretend it doesn't have got_revision at all.
1207 has_got_revision
= False
1209 def testCheckout(self
):
1210 d
= self
.do_vctest()
1213 def testPatch(self
):
1217 def testCheckoutBranch(self
):
1218 d
= self
.do_branch()
1222 d
= self
.do_getpatch(doBranch
=False)
1225 VCS
.registerVC(CVS
.vc_name
, CVSHelper())
1228 class SVNHelper(BaseHelper
):
1229 branchname
= "sample/branch"
1230 try_branchname
= "sample/branch"
1233 svnpaths
= which('svn')
1234 svnadminpaths
= which('svnadmin')
1236 return (False, "SVN is not installed")
1237 if not svnadminpaths
:
1238 return (False, "svnadmin is not installed")
1239 # we need svn to be compiled with the ra_local access
1241 log
.msg("running svn --version..")
1242 env
= os
.environ
.copy()
1244 d
= utils
.getProcessOutput(svnpaths
[0], ["--version"],
1246 d
.addCallback(self
._capable
, svnpaths
[0], svnadminpaths
[0])
1249 def _capable(self
, v
, vcexe
, svnadmin
):
1250 if v
.find("handles 'file' schem") != -1:
1251 # older versions say 'schema', 1.2.0 and beyond say 'scheme'
1253 self
.svnadmin
= svnadmin
1255 excuse
= ("%s found but it does not support 'file:' " +
1256 "schema, skipping svn tests") % vcexe
1258 return (False, excuse
)
1260 def createRepository(self
):
1261 self
.createBasedir()
1262 self
.svnrep
= os
.path
.join(self
.repbase
,
1263 "SVN-Repository").replace('\\','/')
1264 tmp
= os
.path
.join(self
.repbase
, "svntmp")
1265 if sys
.platform
== 'win32':
1266 # On Windows Paths do not start with a /
1267 self
.svnurl
= "file:///%s" % self
.svnrep
1269 self
.svnurl
= "file://%s" % self
.svnrep
1270 self
.svnurl_trunk
= self
.svnurl
+ "/sample/trunk"
1271 self
.svnurl_branch
= self
.svnurl
+ "/sample/branch"
1273 w
= self
.do(self
.repbase
, [self
.svnadmin
, "create", self
.svnrep
])
1274 yield w
; w
.getResult()
1278 ['import', '-m', 'sample_project_files', self
.svnurl_trunk
])
1279 yield w
; out
= w
.getResult()
1281 m
= re
.search(r
'Committed revision (\d+)\.', out
)
1282 assert m
.group(1) == "1" # first revision is always "1"
1283 self
.addTrunkRev(int(m
.group(1)))
1285 w
= self
.dovc(self
.repbase
,
1286 ['checkout', self
.svnurl_trunk
, 'svntmp'])
1287 yield w
; w
.getResult()
1289 w
= self
.dovc(tmp
, ['cp', '-m' , 'make_branch', self
.svnurl_trunk
,
1290 self
.svnurl_branch
])
1291 yield w
; w
.getResult()
1292 w
= self
.dovc(tmp
, ['switch', self
.svnurl_branch
])
1293 yield w
; w
.getResult()
1294 self
.populate_branch(tmp
)
1295 w
= self
.dovc(tmp
, ['commit', '-m', 'commit_on_branch'])
1296 yield w
; out
= w
.getResult()
1298 m
= re
.search(r
'Committed revision (\d+)\.', out
)
1299 self
.addBranchRev(int(m
.group(1)))
1300 createRepository
= deferredGenerator(createRepository
)
1302 def vc_revise(self
):
1303 tmp
= os
.path
.join(self
.repbase
, "svntmp")
1305 log
.msg("vc_revise" + self
.svnurl_trunk
)
1306 w
= self
.dovc(self
.repbase
,
1307 ['checkout', self
.svnurl_trunk
, 'svntmp'])
1308 yield w
; w
.getResult()
1310 version_c
= VERSION_C
% self
.version
1311 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1312 w
= self
.dovc(tmp
, ['commit', '-m', 'revised_to_%d' % self
.version
])
1313 yield w
; out
= w
.getResult()
1314 m
= re
.search(r
'Committed revision (\d+)\.', out
)
1315 self
.addTrunkRev(int(m
.group(1)))
1317 vc_revise
= deferredGenerator(vc_revise
)
1319 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1320 assert os
.path
.abspath(workdir
) == workdir
1321 if os
.path
.exists(workdir
):
1322 rmdirRecursive(workdir
)
1324 svnurl
= self
.svnurl_trunk
1326 # N.B.: this is *not* os.path.join: SVN URLs use slashes
1327 # regardless of the host operating system's filepath separator
1328 svnurl
= self
.svnurl
+ "/" + branch
1329 w
= self
.dovc(self
.repbase
,
1330 ['checkout', svnurl
, workdir
])
1331 yield w
; w
.getResult()
1332 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1333 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1335 def vc_try_finish(self
, workdir
):
1336 rmdirRecursive(workdir
)
1339 class SVN(VCBase
, unittest
.TestCase
):
1343 vctype
= "source.SVN"
1345 has_got_revision
= True
1346 has_got_revision_branches_are_merged
= True
1348 def testCheckout(self
):
1349 # we verify this one with the svnurl style of vcargs. We test the
1350 # baseURL/defaultBranch style in testPatch and testCheckoutBranch.
1351 self
.helper
.vcargs
= { 'svnurl': self
.helper
.svnurl_trunk
}
1352 d
= self
.do_vctest()
1355 def testPatch(self
):
1356 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1357 'defaultBranch': "sample/trunk",
1362 def testCheckoutBranch(self
):
1363 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1364 'defaultBranch': "sample/trunk",
1366 d
= self
.do_branch()
1370 # extract the base revision and patch from a modified tree, use it to
1371 # create the same contents on the buildslave
1372 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1373 'defaultBranch': "sample/trunk",
1375 d
= self
.do_getpatch()
1378 ## can't test the username= and password= options, because we do not have an
1379 ## svn repository that requires authentication.
1381 VCS
.registerVC(SVN
.vc_name
, SVNHelper())
1384 class P4Helper(BaseHelper
):
1385 branchname
= "branch"
1386 p4port
= 'localhost:1666'
1388 base_descr
= 'Change: new\nDescription: asdf\nFiles:\n'
1391 p4paths
= which('p4')
1392 p4dpaths
= which('p4d')
1394 return (False, "p4 is not installed")
1396 return (False, "p4d is not installed")
1397 self
.vcexe
= p4paths
[0]
1398 self
.p4dexe
= p4dpaths
[0]
1401 class _P4DProtocol(protocol
.ProcessProtocol
):
1403 self
.started
= defer
.Deferred()
1404 self
.ended
= defer
.Deferred()
1406 def outReceived(self
, data
):
1407 # When it says starting, it has bound to the socket.
1410 # Make sure p4d has started. Newer versions of p4d
1411 # have more verbose messaging when db files don't exist, so
1412 # we use re.search instead of startswith.
1414 if re
.search('Perforce Server starting...', data
):
1415 self
.started
.callback(None)
1417 print "p4d said %r" % data
1419 raise Exception('p4d said %r' % data
)
1421 self
.started
.errback(failure
.Failure())
1424 def errReceived(self
, data
):
1425 print "p4d stderr: %s" % data
1427 def processEnded(self
, status_object
):
1428 if status_object
.check(error
.ProcessDone
):
1429 self
.ended
.callback(None)
1431 self
.ended
.errback(status_object
)
1433 def _start_p4d(self
):
1434 proto
= self
._P
4DProtocol
()
1435 reactor
.spawnProcess(proto
, self
.p4dexe
, ['p4d', '-p', self
.p4port
],
1436 env
=os
.environ
, path
=self
.p4rep
)
1437 return proto
.started
, proto
.ended
1439 def dop4(self
, basedir
, command
, failureIsOk
=False, stdin
=None):
1440 # p4 looks at $PWD instead of getcwd(), which causes confusion when
1441 # we spawn commands without an intervening shell (sh -c). We can
1442 # override this with a -d argument.
1443 command
= "-p %s -d %s %s" % (self
.p4port
, basedir
, command
)
1444 return self
.dovc(basedir
, command
, failureIsOk
, stdin
)
1446 def createRepository(self
):
1447 # this is only called once per VC system, so start p4d here.
1449 self
.createBasedir()
1450 tmp
= os
.path
.join(self
.repbase
, "p4tmp")
1451 self
.p4rep
= os
.path
.join(self
.repbase
, 'P4-Repository')
1452 os
.mkdir(self
.p4rep
)
1455 started
, self
.p4d_shutdown
= self
._start
_p
4d
()
1456 w
= waitForDeferred(started
)
1457 yield w
; w
.getResult()
1459 # Create client spec.
1461 clispec
= 'Client: creator\n'
1462 clispec
+= 'Root: %s\n' % tmp
1463 clispec
+= 'View:\n'
1464 clispec
+= '\t//depot/... //creator/...\n'
1465 w
= self
.dop4(tmp
, 'client -i', stdin
=clispec
)
1466 yield w
; w
.getResult()
1468 # Create first rev (trunk).
1469 self
.populate(os
.path
.join(tmp
, 'trunk'))
1470 files
= ['main.c', 'version.c', 'subdir/subdir.c']
1471 w
= self
.dop4(tmp
, "-c creator add "
1472 + " ".join(['trunk/%s' % f
for f
in files
]))
1473 yield w
; w
.getResult()
1474 descr
= self
.base_descr
1476 descr
+= '\t//depot/trunk/%s\n' % file
1477 w
= self
.dop4(tmp
, "-c creator submit -i", stdin
=descr
)
1478 yield w
; out
= w
.getResult()
1479 m
= re
.search(r
'Change (\d+) submitted.', out
)
1480 assert m
.group(1) == '1'
1481 self
.addTrunkRev(m
.group(1))
1483 # Create second rev (branch).
1484 w
= self
.dop4(tmp
, '-c creator integrate '
1485 + '//depot/trunk/... //depot/branch/...')
1486 yield w
; w
.getResult()
1487 w
= self
.dop4(tmp
, "-c creator edit branch/main.c")
1488 yield w
; w
.getResult()
1489 self
.populate_branch(os
.path
.join(tmp
, 'branch'))
1490 descr
= self
.base_descr
1492 descr
+= '\t//depot/branch/%s\n' % file
1493 w
= self
.dop4(tmp
, "-c creator submit -i", stdin
=descr
)
1494 yield w
; out
= w
.getResult()
1495 m
= re
.search(r
'Change (\d+) submitted.', out
)
1496 self
.addBranchRev(m
.group(1))
1497 createRepository
= deferredGenerator(createRepository
)
1499 def vc_revise(self
):
1500 tmp
= os
.path
.join(self
.repbase
, "p4tmp")
1502 version_c
= VERSION_C
% self
.version
1503 w
= self
.dop4(tmp
, '-c creator edit trunk/version.c')
1504 yield w
; w
.getResult()
1505 open(os
.path
.join(tmp
, "trunk/version.c"), "w").write(version_c
)
1506 descr
= self
.base_descr
+ '\t//depot/trunk/version.c\n'
1507 w
= self
.dop4(tmp
, "-c creator submit -i", stdin
=descr
)
1508 yield w
; out
= w
.getResult()
1509 m
= re
.search(r
'Change (\d+) submitted.', out
)
1510 self
.addTrunkRev(m
.group(1))
1511 vc_revise
= deferredGenerator(vc_revise
)
1513 def shutdown_p4d(self
):
1514 d
= self
.runCommand(self
.repbase
, '%s -p %s admin stop'
1515 % (self
.vcexe
, self
.p4port
))
1516 return d
.addCallback(lambda _
: self
.p4d_shutdown
)
1518 class P4(VCBase
, unittest
.TestCase
):
1520 vctype
= "source.P4"
1522 has_got_revision
= True
1524 def tearDownClass(self
):
1526 return self
.helper
.shutdown_p4d()
1528 def testCheckout(self
):
1529 self
.helper
.vcargs
= { 'p4port': self
.helper
.p4port
,
1530 'p4base': '//depot/',
1531 'defaultBranch': 'trunk' }
1532 d
= self
.do_vctest(testRetry
=False)
1533 # TODO: like arch and darcs, sync does nothing when server is not
1537 def testCheckoutBranch(self
):
1538 self
.helper
.vcargs
= { 'p4port': self
.helper
.p4port
,
1539 'p4base': '//depot/',
1540 'defaultBranch': 'trunk' }
1541 d
= self
.do_branch()
1544 def testPatch(self
):
1545 self
.helper
.vcargs
= { 'p4port': self
.helper
.p4port
,
1546 'p4base': '//depot/',
1547 'defaultBranch': 'trunk' }
1551 VCS
.registerVC(P4
.vc_name
, P4Helper())
1554 class DarcsHelper(BaseHelper
):
1555 branchname
= "branch"
1556 try_branchname
= "branch"
1559 darcspaths
= which('darcs')
1561 return (False, "Darcs is not installed")
1562 self
.vcexe
= darcspaths
[0]
1565 def createRepository(self
):
1566 self
.createBasedir()
1567 self
.darcs_base
= os
.path
.join(self
.repbase
, "Darcs-Repository")
1568 self
.rep_trunk
= os
.path
.join(self
.darcs_base
, "trunk")
1569 self
.rep_branch
= os
.path
.join(self
.darcs_base
, "branch")
1570 tmp
= os
.path
.join(self
.repbase
, "darcstmp")
1572 _makedirsif(self
.rep_trunk
)
1573 w
= self
.dovc(self
.rep_trunk
, ["initialize"])
1574 yield w
; w
.getResult()
1575 _makedirsif(self
.rep_branch
)
1576 w
= self
.dovc(self
.rep_branch
, ["initialize"])
1577 yield w
; w
.getResult()
1580 w
= self
.dovc(tmp
, qw("initialize"))
1581 yield w
; w
.getResult()
1582 w
= self
.dovc(tmp
, qw("add -r ."))
1583 yield w
; w
.getResult()
1584 w
= self
.dovc(tmp
, qw("record -a -m initial_import --skip-long-comment -A test@buildbot.sf.net"))
1585 yield w
; w
.getResult()
1586 w
= self
.dovc(tmp
, ["push", "-a", self
.rep_trunk
])
1587 yield w
; w
.getResult()
1588 w
= self
.dovc(tmp
, qw("changes --context"))
1589 yield w
; out
= w
.getResult()
1590 self
.addTrunkRev(out
)
1592 self
.populate_branch(tmp
)
1593 w
= self
.dovc(tmp
, qw("record -a --ignore-times -m commit_on_branch --skip-long-comment -A test@buildbot.sf.net"))
1594 yield w
; w
.getResult()
1595 w
= self
.dovc(tmp
, ["push", "-a", self
.rep_branch
])
1596 yield w
; w
.getResult()
1597 w
= self
.dovc(tmp
, qw("changes --context"))
1598 yield w
; out
= w
.getResult()
1599 self
.addBranchRev(out
)
1601 createRepository
= deferredGenerator(createRepository
)
1603 def vc_revise(self
):
1604 tmp
= os
.path
.join(self
.repbase
, "darcstmp")
1606 w
= self
.dovc(tmp
, qw("initialize"))
1607 yield w
; w
.getResult()
1608 w
= self
.dovc(tmp
, ["pull", "-a", self
.rep_trunk
])
1609 yield w
; w
.getResult()
1612 version_c
= VERSION_C
% self
.version
1613 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1614 w
= self
.dovc(tmp
, qw("record -a --ignore-times -m revised_to_%d --skip-long-comment -A test@buildbot.sf.net" % self
.version
))
1615 yield w
; w
.getResult()
1616 w
= self
.dovc(tmp
, ["push", "-a", self
.rep_trunk
])
1617 yield w
; w
.getResult()
1618 w
= self
.dovc(tmp
, qw("changes --context"))
1619 yield w
; out
= w
.getResult()
1620 self
.addTrunkRev(out
)
1622 vc_revise
= deferredGenerator(vc_revise
)
1624 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1625 assert os
.path
.abspath(workdir
) == workdir
1626 if os
.path
.exists(workdir
):
1627 rmdirRecursive(workdir
)
1628 _makedirsif(workdir
)
1629 w
= self
.dovc(workdir
, qw("initialize"))
1630 yield w
; w
.getResult()
1632 rep
= self
.rep_trunk
1634 rep
= os
.path
.join(self
.darcs_base
, branch
)
1635 w
= self
.dovc(workdir
, ["pull", "-a", rep
])
1636 yield w
; w
.getResult()
1637 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1638 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1640 def vc_try_finish(self
, workdir
):
1641 rmdirRecursive(workdir
)
1644 class Darcs(VCBase
, unittest
.TestCase
):
1647 # Darcs has a metadir="_darcs", but it does not have an 'export'
1650 vctype
= "source.Darcs"
1651 vctype_try
= "darcs"
1652 has_got_revision
= True
1654 def testCheckout(self
):
1655 self
.helper
.vcargs
= { 'repourl': self
.helper
.rep_trunk
}
1656 d
= self
.do_vctest(testRetry
=False)
1658 # TODO: testRetry has the same problem with Darcs as it does for
1662 def testPatch(self
):
1663 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1664 'defaultBranch': "trunk" }
1668 def testCheckoutBranch(self
):
1669 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1670 'defaultBranch': "trunk" }
1671 d
= self
.do_branch()
1674 def testCheckoutHTTP(self
):
1676 repourl
= "http://localhost:%d/Darcs-Repository/trunk" % self
.httpPort
1677 self
.helper
.vcargs
= { 'repourl': repourl
}
1678 d
= self
.do_vctest(testRetry
=False)
1682 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1683 'defaultBranch': "trunk" }
1684 d
= self
.do_getpatch()
1687 VCS
.registerVC(Darcs
.vc_name
, DarcsHelper())
1691 def registerRepository(self
, coordinates
):
1693 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1694 yield w
; out
= w
.getResult()
1696 w
= self
.dovc(self
.repbase
, "register-archive -d %s" % a
)
1697 yield w
; w
.getResult()
1698 w
= self
.dovc(self
.repbase
, "register-archive %s" % coordinates
)
1699 yield w
; w
.getResult()
1700 registerRepository
= deferredGenerator(registerRepository
)
1702 def unregisterRepository(self
):
1704 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1705 yield w
; out
= w
.getResult()
1707 w
= self
.dovc(self
.repbase
, "register-archive -d %s" % a
)
1708 yield w
; out
= w
.getResult()
1709 unregisterRepository
= deferredGenerator(unregisterRepository
)
1711 class TlaHelper(BaseHelper
, ArchCommon
):
1712 defaultbranch
= "testvc--mainline--1"
1713 branchname
= "testvc--branch--1"
1714 try_branchname
= None # TlaExtractor can figure it out by itself
1718 tlapaths
= which('tla')
1720 return (False, "Arch (tla) is not installed")
1721 self
.vcexe
= tlapaths
[0]
1724 def do_get(self
, basedir
, archive
, branch
, newdir
):
1725 # the 'get' syntax is different between tla and baz. baz, while
1726 # claiming to honor an --archive argument, in fact ignores it. The
1727 # correct invocation is 'baz get archive/revision newdir'.
1728 if self
.archcmd
== "tla":
1729 w
= self
.dovc(basedir
,
1730 "get -A %s %s %s" % (archive
, branch
, newdir
))
1732 w
= self
.dovc(basedir
,
1733 "get %s/%s %s" % (archive
, branch
, newdir
))
1736 def createRepository(self
):
1737 self
.createBasedir()
1738 # first check to see if bazaar is around, since we'll need to know
1740 d
= VCS
.capable(Bazaar
.vc_name
)
1741 d
.addCallback(self
._createRepository
_1)
1744 def _createRepository_1(self
, res
):
1747 # pick a hopefully unique string for the archive name, in the form
1748 # test-%d@buildbot.sf.net--testvc, since otherwise multiple copies of
1749 # the unit tests run in the same user account will collide (since the
1750 # archive names are kept in the per-user ~/.arch-params/ directory).
1752 self
.archname
= "test-%s-%d@buildbot.sf.net--testvc" % (self
.archcmd
,
1754 trunk
= self
.defaultbranch
1755 branch
= self
.branchname
1757 repword
= self
.archcmd
.capitalize()
1758 self
.archrep
= os
.path
.join(self
.repbase
, "%s-Repository" % repword
)
1759 tmp
= os
.path
.join(self
.repbase
, "archtmp")
1764 w
= self
.dovc(tmp
, "my-id", failureIsOk
=True)
1765 yield w
; res
= w
.getResult()
1767 # tla will fail a lot of operations if you have not set an ID
1768 w
= self
.do(tmp
, [self
.vcexe
, "my-id",
1769 "Buildbot Test Suite <test@buildbot.sf.net>"])
1770 yield w
; w
.getResult()
1773 # bazaar keeps a cache of revisions, but this test creates a new
1774 # archive each time it is run, so the cache causes errors.
1775 # Disable the cache to avoid these problems. This will be
1776 # slightly annoying for people who run the buildbot tests under
1777 # the same UID as one which uses baz on a regular basis, but
1778 # bazaar doesn't give us a way to disable the cache just for this
1780 cmd
= "%s cache-config --disable" % VCS
.getHelper('bazaar').vcexe
1781 w
= self
.do(tmp
, cmd
)
1782 yield w
; w
.getResult()
1784 w
= waitForDeferred(self
.unregisterRepository())
1785 yield w
; w
.getResult()
1787 # these commands can be run in any directory
1788 w
= self
.dovc(tmp
, "make-archive -l %s %s" % (a
, self
.archrep
))
1789 yield w
; w
.getResult()
1790 if self
.archcmd
== "tla":
1791 w
= self
.dovc(tmp
, "archive-setup -A %s %s" % (a
, trunk
))
1792 yield w
; w
.getResult()
1793 w
= self
.dovc(tmp
, "archive-setup -A %s %s" % (a
, branch
))
1794 yield w
; w
.getResult()
1796 # baz does not require an 'archive-setup' step
1799 # these commands must be run in the directory that is to be imported
1800 w
= self
.dovc(tmp
, "init-tree --nested %s/%s" % (a
, trunk
))
1801 yield w
; w
.getResult()
1802 files
= " ".join(["main.c", "version.c", "subdir",
1803 os
.path
.join("subdir", "subdir.c")])
1804 w
= self
.dovc(tmp
, "add-id %s" % files
)
1805 yield w
; w
.getResult()
1807 w
= self
.dovc(tmp
, "import %s/%s" % (a
, trunk
))
1808 yield w
; out
= w
.getResult()
1809 self
.addTrunkRev("base-0")
1812 if self
.archcmd
== "tla":
1813 branchstart
= "%s--base-0" % trunk
1814 w
= self
.dovc(tmp
, "tag -A %s %s %s" % (a
, branchstart
, branch
))
1815 yield w
; w
.getResult()
1817 w
= self
.dovc(tmp
, "branch %s" % branch
)
1818 yield w
; w
.getResult()
1822 # check out the branch
1823 w
= self
.do_get(self
.repbase
, a
, branch
, "archtmp")
1824 yield w
; w
.getResult()
1826 self
.populate_branch(tmp
)
1827 logfile
= "++log.%s--%s" % (branch
, a
)
1828 logmsg
= "Summary: commit on branch\nKeywords:\n\n"
1829 open(os
.path
.join(tmp
, logfile
), "w").write(logmsg
)
1830 w
= self
.dovc(tmp
, "commit")
1831 yield w
; out
= w
.getResult()
1832 m
= re
.search(r
'committed %s/%s--([\S]+)' % (a
, branch
),
1834 assert (m
.group(1) == "base-0" or m
.group(1).startswith("patch-"))
1835 self
.addBranchRev(m
.group(1))
1837 w
= waitForDeferred(self
.unregisterRepository())
1838 yield w
; w
.getResult()
1841 # we unregister the repository each time, because we might have
1842 # changed the coordinates (since we switch from a file: URL to an
1843 # http: URL for various tests). The buildslave code doesn't forcibly
1844 # unregister the archive, so we have to do it here.
1845 w
= waitForDeferred(self
.unregisterRepository())
1846 yield w
; w
.getResult()
1848 _createRepository_1
= deferredGenerator(_createRepository_1
)
1850 def vc_revise(self
):
1851 # the fix needs to be done in a workspace that is linked to a
1852 # read-write version of the archive (i.e., using file-based
1853 # coordinates instead of HTTP ones), so we re-register the repository
1854 # before we begin. We unregister it when we're done to make sure the
1855 # build will re-register the correct one for whichever test is
1856 # currently being run.
1858 # except, that source.Bazaar really doesn't like it when the archive
1859 # gets unregistered behind its back. The slave tries to do a 'baz
1860 # replay' in a tree with an archive that is no longer recognized, and
1861 # baz aborts with a botched invariant exception. This causes
1862 # mode=update to fall back to clobber+get, which flunks one of the
1863 # tests (the 'newfile' check in _do_vctest_update_3 fails)
1865 # to avoid this, we take heroic steps here to leave the archive
1866 # registration in the same state as we found it.
1868 tmp
= os
.path
.join(self
.repbase
, "archtmp")
1871 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1872 yield w
; out
= w
.getResult()
1874 lines
= out
.split("\n")
1875 coordinates
= lines
[1].strip()
1877 # now register the read-write location
1878 w
= waitForDeferred(self
.registerRepository(self
.archrep
))
1879 yield w
; w
.getResult()
1881 trunk
= self
.defaultbranch
1883 w
= self
.do_get(self
.repbase
, a
, trunk
, "archtmp")
1884 yield w
; w
.getResult()
1886 # tla appears to use timestamps to determine which files have
1887 # changed, so wait long enough for the new file to have a different
1891 version_c
= VERSION_C
% self
.version
1892 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1894 logfile
= "++log.%s--%s" % (trunk
, a
)
1895 logmsg
= "Summary: revised_to_%d\nKeywords:\n\n" % self
.version
1896 open(os
.path
.join(tmp
, logfile
), "w").write(logmsg
)
1897 w
= self
.dovc(tmp
, "commit")
1898 yield w
; out
= w
.getResult()
1899 m
= re
.search(r
'committed %s/%s--([\S]+)' % (a
, trunk
),
1901 assert (m
.group(1) == "base-0" or m
.group(1).startswith("patch-"))
1902 self
.addTrunkRev(m
.group(1))
1904 # now re-register the original coordinates
1905 w
= waitForDeferred(self
.registerRepository(coordinates
))
1906 yield w
; w
.getResult()
1908 vc_revise
= deferredGenerator(vc_revise
)
1910 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1911 assert os
.path
.abspath(workdir
) == workdir
1912 if os
.path
.exists(workdir
):
1913 rmdirRecursive(workdir
)
1917 # register the read-write location, if it wasn't already registered
1918 w
= waitForDeferred(self
.registerRepository(self
.archrep
))
1919 yield w
; w
.getResult()
1921 w
= self
.do_get(self
.repbase
, a
, "testvc--mainline--1", workdir
)
1922 yield w
; w
.getResult()
1926 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1927 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1929 def vc_try_finish(self
, workdir
):
1930 rmdirRecursive(workdir
)
1932 class Arch(VCBase
, unittest
.TestCase
):
1936 # Arch has a metadir="{arch}", but it does not have an 'export' mode.
1937 vctype
= "source.Arch"
1939 has_got_revision
= True
1941 def testCheckout(self
):
1942 # these are the coordinates of the read-write archive used by all the
1943 # non-HTTP tests. testCheckoutHTTP overrides these.
1944 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1945 'version': self
.helper
.defaultbranch
}
1946 d
= self
.do_vctest(testRetry
=False)
1947 # the current testRetry=True logic doesn't have the desired effect:
1948 # "update" is a no-op because arch knows that the repository hasn't
1949 # changed. Other VC systems will re-checkout missing files on
1950 # update, arch just leaves the tree untouched. TODO: come up with
1951 # some better test logic, probably involving a copy of the
1952 # repository that has a few changes checked in.
1956 def testCheckoutHTTP(self
):
1958 url
= "http://localhost:%d/Tla-Repository" % self
.httpPort
1959 self
.helper
.vcargs
= { 'url': url
,
1960 'version': "testvc--mainline--1" }
1961 d
= self
.do_vctest(testRetry
=False)
1964 def testPatch(self
):
1965 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1966 'version': self
.helper
.defaultbranch
}
1970 def testCheckoutBranch(self
):
1971 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1972 'version': self
.helper
.defaultbranch
}
1973 d
= self
.do_branch()
1977 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1978 'version': self
.helper
.defaultbranch
}
1979 d
= self
.do_getpatch()
1982 VCS
.registerVC(Arch
.vc_name
, TlaHelper())
1985 class BazaarHelper(TlaHelper
):
1989 bazpaths
= which('baz')
1991 return (False, "Arch (baz) is not installed")
1992 self
.vcexe
= bazpaths
[0]
1995 def setUp2(self
, res
):
1996 # we unregister the repository each time, because we might have
1997 # changed the coordinates (since we switch from a file: URL to an
1998 # http: URL for various tests). The buildslave code doesn't forcibly
1999 # unregister the archive, so we have to do it here.
2000 d
= self
.unregisterRepository()
2007 vctype
= "source.Bazaar"
2009 has_got_revision
= True
2013 def testCheckout(self
):
2014 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
2015 # Baz adds the required 'archive' argument
2016 'archive': self
.helper
.archname
,
2017 'version': self
.helper
.defaultbranch
,
2019 d
= self
.do_vctest(testRetry
=False)
2020 # the current testRetry=True logic doesn't have the desired effect:
2021 # "update" is a no-op because arch knows that the repository hasn't
2022 # changed. Other VC systems will re-checkout missing files on
2023 # update, arch just leaves the tree untouched. TODO: come up with
2024 # some better test logic, probably involving a copy of the
2025 # repository that has a few changes checked in.
2029 def testCheckoutHTTP(self
):
2031 url
= "http://localhost:%d/Baz-Repository" % self
.httpPort
2032 self
.helper
.vcargs
= { 'url': url
,
2033 'archive': self
.helper
.archname
,
2034 'version': self
.helper
.defaultbranch
,
2036 d
= self
.do_vctest(testRetry
=False)
2039 def testPatch(self
):
2040 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
2041 # Baz adds the required 'archive' argument
2042 'archive': self
.helper
.archname
,
2043 'version': self
.helper
.defaultbranch
,
2048 def testCheckoutBranch(self
):
2049 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
2050 # Baz adds the required 'archive' argument
2051 'archive': self
.helper
.archname
,
2052 'version': self
.helper
.defaultbranch
,
2054 d
= self
.do_branch()
2058 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
2059 # Baz adds the required 'archive' argument
2060 'archive': self
.helper
.archname
,
2061 'version': self
.helper
.defaultbranch
,
2063 d
= self
.do_getpatch()
2066 def fixRepository(self
):
2067 self
.fixtimer
= None
2068 self
.site
.resource
= self
.root
2070 def testRetry(self
):
2071 # we want to verify that source.Source(retry=) works, and the easiest
2072 # way to make VC updates break (temporarily) is to break the HTTP
2073 # server that's providing the repository. Anything else pretty much
2074 # requires mutating the (read-only) BUILDBOT_TEST_VC repository, or
2075 # modifying the buildslave's checkout command while it's running.
2077 # this test takes a while to run, so don't bother doing it with
2078 # anything other than baz
2082 # break the repository server
2083 from twisted
.web
import static
2084 self
.site
.resource
= static
.Data("Sorry, repository is offline",
2086 # and arrange to fix it again in 5 seconds, while the test is
2088 self
.fixtimer
= reactor
.callLater(5, self
.fixRepository
)
2090 url
= "http://localhost:%d/Baz-Repository" % self
.httpPort
2091 self
.helper
.vcargs
= { 'url': url
,
2092 'archive': self
.helper
.archname
,
2093 'version': self
.helper
.defaultbranch
,
2096 d
= self
.do_vctest_once(True)
2097 d
.addCallback(self
._testRetry
_1)
2099 def _testRetry_1(self
, bs
):
2100 # make sure there was mention of the retry attempt in the logs
2102 self
.failUnlessIn("unable to access URL", l
.getText(),
2103 "funny, VC operation didn't fail at least once")
2104 self
.failUnlessIn("update failed, trying 4 more times after 5 seconds",
2105 l
.getTextWithHeaders(),
2106 "funny, VC operation wasn't reattempted")
2108 def testRetryFails(self
):
2109 # make sure that the build eventually gives up on a repository which
2110 # is completely unavailable
2114 # break the repository server, and leave it broken
2115 from twisted
.web
import static
2116 self
.site
.resource
= static
.Data("Sorry, repository is offline",
2119 url
= "http://localhost:%d/Baz-Repository" % self
.httpPort
2120 self
.helper
.vcargs
= {'url': url
,
2121 'archive': self
.helper
.archname
,
2122 'version': self
.helper
.defaultbranch
,
2125 d
= self
.do_vctest_once(False)
2126 d
.addCallback(self
._testRetryFails
_1)
2128 def _testRetryFails_1(self
, bs
):
2129 self
.failUnlessEqual(bs
.getResults(), FAILURE
)
2131 def tearDown2(self
):
2133 self
.fixtimer
.cancel()
2134 # tell tla to get rid of the leftover archive this test leaves in the
2135 # user's 'tla archives' listing. The name of this archive is provided
2136 # by the repository tarball, so the following command must use the
2137 # same name. We could use archive= to set it explicitly, but if you
2138 # change it from the default, then 'tla update' won't work.
2139 d
= self
.helper
.unregisterRepository()
2142 VCS
.registerVC(Bazaar
.vc_name
, BazaarHelper())
2144 class BzrHelper(BaseHelper
):
2145 branchname
= "branch"
2146 try_branchname
= "branch"
2149 bzrpaths
= which('bzr')
2151 return (False, "bzr is not installed")
2152 self
.vcexe
= bzrpaths
[0]
2155 def get_revision_number(self
, out
):
2156 for line
in out
.split("\n"):
2157 colon
= line
.index(":")
2158 key
, value
= line
[:colon
], line
[colon
+2:]
2161 raise RuntimeError("unable to find revno: in bzr output: '%s'" % out
)
2163 def createRepository(self
):
2164 self
.createBasedir()
2165 self
.bzr_base
= os
.path
.join(self
.repbase
, "Bzr-Repository")
2166 self
.rep_trunk
= os
.path
.join(self
.bzr_base
, "trunk")
2167 self
.rep_branch
= os
.path
.join(self
.bzr_base
, "branch")
2168 tmp
= os
.path
.join(self
.repbase
, "bzrtmp")
2169 btmp
= os
.path
.join(self
.repbase
, "bzrtmp-branch")
2171 _makedirsif(self
.rep_trunk
)
2172 w
= self
.dovc(self
.rep_trunk
, ["init"])
2173 yield w
; w
.getResult()
2174 w
= self
.dovc(self
.bzr_base
,
2175 ["branch", self
.rep_trunk
, self
.rep_branch
])
2176 yield w
; w
.getResult()
2178 w
= self
.dovc(self
.repbase
, ["checkout", self
.rep_trunk
, tmp
])
2179 yield w
; w
.getResult()
2181 w
= self
.dovc(tmp
, qw("add"))
2182 yield w
; w
.getResult()
2183 w
= self
.dovc(tmp
, qw("commit -m initial_import"))
2184 yield w
; w
.getResult()
2185 w
= self
.dovc(tmp
, qw("version-info"))
2186 yield w
; out
= w
.getResult()
2187 self
.addTrunkRev(self
.get_revision_number(out
))
2190 # pull all trunk revisions to the branch
2191 w
= self
.dovc(self
.rep_branch
, qw("pull"))
2192 yield w
; w
.getResult()
2193 # obtain a branch tree
2194 w
= self
.dovc(self
.repbase
, ["checkout", self
.rep_branch
, btmp
])
2195 yield w
; w
.getResult()
2197 self
.populate_branch(btmp
)
2198 w
= self
.dovc(btmp
, qw("add"))
2199 yield w
; w
.getResult()
2200 w
= self
.dovc(btmp
, qw("commit -m commit_on_branch"))
2201 yield w
; w
.getResult()
2202 w
= self
.dovc(btmp
, qw("version-info"))
2203 yield w
; out
= w
.getResult()
2204 self
.addBranchRev(self
.get_revision_number(out
))
2205 rmdirRecursive(btmp
)
2206 createRepository
= deferredGenerator(createRepository
)
2208 def vc_revise(self
):
2209 tmp
= os
.path
.join(self
.repbase
, "bzrtmp")
2210 w
= self
.dovc(self
.repbase
, ["checkout", self
.rep_trunk
, tmp
])
2211 yield w
; w
.getResult()
2214 version_c
= VERSION_C
% self
.version
2215 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
2216 w
= self
.dovc(tmp
, qw("commit -m revised_to_%d" % self
.version
))
2217 yield w
; w
.getResult()
2218 w
= self
.dovc(tmp
, qw("version-info"))
2219 yield w
; out
= w
.getResult()
2220 self
.addTrunkRev(self
.get_revision_number(out
))
2222 vc_revise
= deferredGenerator(vc_revise
)
2224 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
2225 assert os
.path
.abspath(workdir
) == workdir
2226 if os
.path
.exists(workdir
):
2227 rmdirRecursive(workdir
)
2228 #_makedirsif(workdir)
2230 rep
= self
.rep_trunk
2232 rep
= os
.path
.join(self
.bzr_base
, branch
)
2233 w
= self
.dovc(self
.bzr_base
, ["checkout", rep
, workdir
])
2234 yield w
; w
.getResult()
2235 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
2236 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
2238 def vc_try_finish(self
, workdir
):
2239 rmdirRecursive(workdir
)
2241 class Bzr(VCBase
, unittest
.TestCase
):
2245 vctype
= "source.Bzr"
2247 has_got_revision
= True
2249 def testCheckout(self
):
2250 self
.helper
.vcargs
= { 'repourl': self
.helper
.rep_trunk
}
2251 d
= self
.do_vctest(testRetry
=False)
2253 # TODO: testRetry has the same problem with Bzr as it does for
2257 def testPatch(self
):
2258 self
.helper
.vcargs
= { 'baseURL': self
.helper
.bzr_base
+ "/",
2259 'defaultBranch': "trunk" }
2263 def testCheckoutBranch(self
):
2264 self
.helper
.vcargs
= { 'baseURL': self
.helper
.bzr_base
+ "/",
2265 'defaultBranch': "trunk" }
2266 d
= self
.do_branch()
2269 def testCheckoutHTTP(self
):
2271 repourl
= "http://localhost:%d/Bzr-Repository/trunk" % self
.httpPort
2272 self
.helper
.vcargs
= { 'repourl': repourl
}
2273 d
= self
.do_vctest(testRetry
=False)
2277 def fixRepository(self
):
2278 self
.fixtimer
= None
2279 self
.site
.resource
= self
.root
2281 def testRetry(self
):
2282 # this test takes a while to run
2285 # break the repository server
2286 from twisted
.web
import static
2287 self
.site
.resource
= static
.Data("Sorry, repository is offline",
2289 # and arrange to fix it again in 5 seconds, while the test is
2291 self
.fixtimer
= reactor
.callLater(5, self
.fixRepository
)
2293 repourl
= "http://localhost:%d/Bzr-Repository/trunk" % self
.httpPort
2294 self
.helper
.vcargs
= { 'repourl': repourl
,
2297 d
= self
.do_vctest_once(True)
2298 d
.addCallback(self
._testRetry
_1)
2300 def _testRetry_1(self
, bs
):
2301 # make sure there was mention of the retry attempt in the logs
2303 self
.failUnlessIn("ERROR: Not a branch: ", l
.getText(),
2304 "funny, VC operation didn't fail at least once")
2305 self
.failUnlessIn("update failed, trying 4 more times after 5 seconds",
2306 l
.getTextWithHeaders(),
2307 "funny, VC operation wasn't reattempted")
2309 def testRetryFails(self
):
2310 # make sure that the build eventually gives up on a repository which
2311 # is completely unavailable
2315 # break the repository server, and leave it broken
2316 from twisted
.web
import static
2317 self
.site
.resource
= static
.Data("Sorry, repository is offline",
2320 repourl
= "http://localhost:%d/Bzr-Repository/trunk" % self
.httpPort
2321 self
.helper
.vcargs
= { 'repourl': repourl
,
2324 d
= self
.do_vctest_once(False)
2325 d
.addCallback(self
._testRetryFails
_1)
2327 def _testRetryFails_1(self
, bs
):
2328 self
.failUnlessEqual(bs
.getResults(), FAILURE
)
2332 self
.helper
.vcargs
= { 'baseURL': self
.helper
.bzr_base
+ "/",
2333 'defaultBranch': "trunk" }
2334 d
= self
.do_getpatch()
2337 VCS
.registerVC(Bzr
.vc_name
, BzrHelper())
2340 class MercurialHelper(BaseHelper
):
2341 branchname
= "branch"
2342 try_branchname
= "branch"
2345 hgpaths
= which("hg")
2347 return (False, "Mercurial is not installed")
2348 self
.vcexe
= hgpaths
[0]
2351 def extract_id(self
, output
):
2352 m
= re
.search(r
'^(\w+)', output
)
2355 def createRepository(self
):
2356 self
.createBasedir()
2357 self
.hg_base
= os
.path
.join(self
.repbase
, "Mercurial-Repository")
2358 self
.rep_trunk
= os
.path
.join(self
.hg_base
, "trunk")
2359 self
.rep_branch
= os
.path
.join(self
.hg_base
, "branch")
2360 tmp
= os
.path
.join(self
.hg_base
, "hgtmp")
2362 _makedirsif(self
.rep_trunk
)
2363 w
= self
.dovc(self
.rep_trunk
, "init")
2364 yield w
; w
.getResult()
2365 _makedirsif(self
.rep_branch
)
2366 w
= self
.dovc(self
.rep_branch
, "init")
2367 yield w
; w
.getResult()
2370 w
= self
.dovc(tmp
, "init")
2371 yield w
; w
.getResult()
2372 w
= self
.dovc(tmp
, "add")
2373 yield w
; w
.getResult()
2374 w
= self
.dovc(tmp
, ['commit', '-m', 'initial_import'])
2375 yield w
; w
.getResult()
2376 w
= self
.dovc(tmp
, ['push', self
.rep_trunk
])
2377 # note that hg-push does not actually update the working directory
2378 yield w
; w
.getResult()
2379 w
= self
.dovc(tmp
, "identify")
2380 yield w
; out
= w
.getResult()
2381 self
.addTrunkRev(self
.extract_id(out
))
2383 self
.populate_branch(tmp
)
2384 w
= self
.dovc(tmp
, ['commit', '-m', 'commit_on_branch'])
2385 yield w
; w
.getResult()
2386 w
= self
.dovc(tmp
, ['push', self
.rep_branch
])
2387 yield w
; w
.getResult()
2388 w
= self
.dovc(tmp
, "identify")
2389 yield w
; out
= w
.getResult()
2390 self
.addBranchRev(self
.extract_id(out
))
2392 createRepository
= deferredGenerator(createRepository
)
2394 def vc_revise(self
):
2395 tmp
= os
.path
.join(self
.hg_base
, "hgtmp2")
2396 w
= self
.dovc(self
.hg_base
, ['clone', self
.rep_trunk
, tmp
])
2397 yield w
; w
.getResult()
2400 version_c
= VERSION_C
% self
.version
2401 version_c_filename
= os
.path
.join(tmp
, "version.c")
2402 open(version_c_filename
, "w").write(version_c
)
2403 # hg uses timestamps to distinguish files which have changed, so we
2404 # force the mtime forward a little bit
2405 future
= time
.time() + 2*self
.version
2406 os
.utime(version_c_filename
, (future
, future
))
2407 w
= self
.dovc(tmp
, ['commit', '-m', 'revised_to_%d' % self
.version
])
2408 yield w
; w
.getResult()
2409 w
= self
.dovc(tmp
, ['push', self
.rep_trunk
])
2410 yield w
; w
.getResult()
2411 w
= self
.dovc(tmp
, "identify")
2412 yield w
; out
= w
.getResult()
2413 self
.addTrunkRev(self
.extract_id(out
))
2415 vc_revise
= deferredGenerator(vc_revise
)
2417 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
2418 assert os
.path
.abspath(workdir
) == workdir
2419 if os
.path
.exists(workdir
):
2420 rmdirRecursive(workdir
)
2422 src
= self
.rep_branch
2424 src
= self
.rep_trunk
2425 w
= self
.dovc(self
.hg_base
, ['clone', src
, workdir
])
2426 yield w
; w
.getResult()
2427 try_c_filename
= os
.path
.join(workdir
, "subdir", "subdir.c")
2428 open(try_c_filename
, "w").write(TRY_C
)
2429 future
= time
.time() + 2*self
.version
2430 os
.utime(try_c_filename
, (future
, future
))
2431 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
2433 def vc_try_finish(self
, workdir
):
2434 rmdirRecursive(workdir
)
2436 class MercurialServerPP(protocol
.ProcessProtocol
):
2438 self
.wait
= defer
.Deferred()
2440 def outReceived(self
, data
):
2441 log
.msg("hg-serve-stdout: %s" % (data
,))
2442 def errReceived(self
, data
):
2443 print "HG-SERVE-STDERR:", data
2444 log
.msg("hg-serve-stderr: %s" % (data
,))
2445 def processEnded(self
, reason
):
2446 log
.msg("hg-serve ended: %s" % reason
)
2447 self
.wait
.callback(None)
2450 class Mercurial(VCBase
, unittest
.TestCase
):
2453 # Mercurial has a metadir=".hg", but it does not have an 'export' mode.
2455 vctype
= "source.Mercurial"
2457 has_got_revision
= True
2459 _wait_for_server_poller
= None
2462 # make sure we can identify source repo via default path
2463 def _testSourceURLPresent(self
, rc
):
2464 d
= self
.helper
.dovc(self
.workdir
, ['paths', 'default'])
2465 yield d
; d
.getResult()
2466 _testSourceURLPresent
= deferredGenerator(_testSourceURLPresent
)
2468 def testCheckout(self
):
2469 self
.helper
.vcargs
= { 'repourl': self
.helper
.rep_trunk
}
2470 d
= self
.do_vctest(testRetry
=False)
2472 # TODO: testRetry has the same problem with Mercurial as it does for
2474 d
.addCallback(self
._testSourceURLPresent
)
2477 def testPatch(self
):
2478 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2479 'defaultBranch': "trunk" }
2483 def testCheckoutBranch(self
):
2484 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2485 'defaultBranch': "trunk" }
2487 d
= self
.do_branch()
2488 d
.addCallback(self
._testSourceURLPresent
)
2491 def serveHTTP(self
):
2492 # the easiest way to publish hg over HTTP is by running 'hg serve' as
2493 # a child process while the test is running. (you can also use a CGI
2494 # script, which sounds difficult, or you can publish the files
2495 # directly, which isn't well documented).
2497 # grr.. 'hg serve' doesn't let you use --port=0 to mean "pick a free
2498 # port", instead it uses it as a signal to use the default (port
2499 # 8000). This means there is no way to make it choose a free port, so
2500 # we are forced to make it use a statically-defined one, making it
2501 # harder to avoid collisions.
2502 self
.httpPort
= 8300 + (os
.getpid() % 200)
2503 args
= [self
.helper
.vcexe
,
2504 "serve", "--port", str(self
.httpPort
), "--verbose"]
2506 # in addition, hg doesn't flush its stdout, so we can't wait for the
2507 # "listening at" message to know when it's safe to start the test.
2508 # Instead, poll every second until a getPage works.
2510 self
._pp
= MercurialServerPP() # logs+discards everything
2512 # this serves one tree at a time, so we serve trunk. TODO: test hg's
2513 # in-repo branches, for which a single tree will hold all branches.
2514 self
._hg
_server
= reactor
.spawnProcess(self
._pp
, self
.helper
.vcexe
, args
,
2516 self
.helper
.rep_trunk
)
2517 log
.msg("waiting for hg serve to start")
2518 done_d
= defer
.Deferred()
2520 d
= client
.getPage("http://localhost:%d/" % self
.httpPort
)
2522 log
.msg("hg serve appears to have started")
2523 self
._wait
_for
_server
_poller
.stop()
2524 done_d
.callback(None)
2525 def ignore_connection_refused(f
):
2526 f
.trap(error
.ConnectionRefusedError
)
2527 d
.addCallbacks(success
, ignore_connection_refused
)
2528 d
.addErrback(done_d
.errback
)
2530 self
._wait
_for
_server
_poller
= task
.LoopingCall(poll
)
2531 self
._wait
_for
_server
_poller
.start(0.5, True)
2535 if self
._wait
_for
_server
_poller
:
2536 if self
._wait
_for
_server
_poller
.running
:
2537 self
._wait
_for
_server
_poller
.stop()
2539 self
._hg
_server
.loseConnection()
2541 self
._hg
_server
.signalProcess("KILL")
2542 except error
.ProcessExitedAlready
:
2544 self
._hg
_server
= None
2545 return VCBase
.tearDown(self
)
2547 def tearDown2(self
):
2549 return self
._pp
.wait
2551 def testCheckoutHTTP(self
):
2552 d
= self
.serveHTTP()
2554 repourl
= "http://localhost:%d/" % self
.httpPort
2555 self
.helper
.vcargs
= { 'repourl': repourl
}
2556 return self
.do_vctest(testRetry
=False)
2557 d
.addCallback(_started
)
2558 d
.addCallback(self
._testSourceURLPresent
)
2562 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2563 'defaultBranch': "trunk" }
2564 d
= self
.do_getpatch()
2567 VCS
.registerVC(Mercurial
.vc_name
, MercurialHelper())
2569 class MercurialInRepoHelper(MercurialHelper
):
2570 branchname
= "the_branch"
2571 try_branchname
= "the_branch"
2574 def createRepository(self
):
2575 self
.createBasedir()
2576 self
.hg_base
= os
.path
.join(self
.repbase
, "Mercurial-Repository")
2577 self
.repo
= os
.path
.join(self
.hg_base
, "inrepobranch")
2578 tmp
= os
.path
.join(self
.hg_base
, "hgtmp")
2580 _makedirsif(self
.repo
)
2581 w
= self
.dovc(self
.repo
, "init")
2582 yield w
; w
.getResult()
2585 w
= self
.dovc(tmp
, "init")
2586 yield w
; w
.getResult()
2587 w
= self
.dovc(tmp
, "add")
2588 yield w
; w
.getResult()
2589 w
= self
.dovc(tmp
, ['commit', '-m', 'initial_import'])
2590 yield w
; w
.getResult()
2591 w
= self
.dovc(tmp
, ['push', self
.repo
])
2592 # note that hg-push does not actually update the working directory
2593 yield w
; w
.getResult()
2594 w
= self
.dovc(tmp
, "identify")
2595 yield w
; out
= w
.getResult()
2596 self
.addTrunkRev(self
.extract_id(out
))
2598 self
.populate_branch(tmp
)
2599 w
= self
.dovc(tmp
, ['branch', self
.branchname
])
2600 yield w
; w
.getResult()
2601 w
= self
.dovc(tmp
, ['commit', '-m', 'commit_on_branch'])
2602 yield w
; w
.getResult()
2603 w
= self
.dovc(tmp
, ['push', '-f', self
.repo
])
2604 yield w
; w
.getResult()
2605 w
= self
.dovc(tmp
, "identify")
2606 yield w
; out
= w
.getResult()
2607 self
.addBranchRev(self
.extract_id(out
))
2609 createRepository
= deferredGenerator(createRepository
)
2611 def vc_revise(self
):
2612 tmp
= os
.path
.join(self
.hg_base
, "hgtmp2")
2613 w
= self
.dovc(self
.hg_base
, ['clone', self
.repo
, tmp
])
2614 yield w
; w
.getResult()
2615 w
= self
.dovc(tmp
, ['update', '--clean', '--rev', 'default'])
2616 yield w
; w
.getResult()
2619 version_c
= VERSION_C
% self
.version
2620 version_c_filename
= os
.path
.join(tmp
, "version.c")
2621 open(version_c_filename
, "w").write(version_c
)
2622 # hg uses timestamps to distinguish files which have changed, so we
2623 # force the mtime forward a little bit
2624 future
= time
.time() + 2*self
.version
2625 os
.utime(version_c_filename
, (future
, future
))
2626 w
= self
.dovc(tmp
, ['commit', '-m', 'revised_to_%d' % self
.version
])
2627 yield w
; w
.getResult()
2628 w
= self
.dovc(tmp
, ['push', '--force', self
.repo
])
2629 yield w
; w
.getResult()
2630 w
= self
.dovc(tmp
, "identify")
2631 yield w
; out
= w
.getResult()
2632 self
.addTrunkRev(self
.extract_id(out
))
2634 vc_revise
= deferredGenerator(vc_revise
)
2636 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
2637 assert os
.path
.abspath(workdir
) == workdir
2638 if os
.path
.exists(workdir
):
2639 rmdirRecursive(workdir
)
2640 w
= self
.dovc(self
.hg_base
, ['clone', self
.repo
, workdir
])
2641 yield w
; w
.getResult()
2642 if not branch
: branch
= "default"
2643 w
= self
.dovc(workdir
, ['update', '--clean', '--rev', branch
])
2644 yield w
; w
.getResult()
2646 try_c_filename
= os
.path
.join(workdir
, "subdir", "subdir.c")
2647 open(try_c_filename
, "w").write(TRY_C
)
2648 future
= time
.time() + 2*self
.version
2649 os
.utime(try_c_filename
, (future
, future
))
2650 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
2652 def vc_try_finish(self
, workdir
):
2653 rmdirRecursive(workdir
)
2657 class MercurialInRepo(Mercurial
):
2658 vc_name
= 'MercurialInRepo'
2660 def default_args(self
):
2661 return { 'repourl': self
.helper
.repo
,
2662 'branchType': 'inrepo',
2663 'defaultBranch': 'default' }
2665 def testCheckout(self
):
2666 self
.helper
.vcargs
= self
.default_args()
2667 d
= self
.do_vctest(testRetry
=False)
2669 # TODO: testRetry has the same problem with Mercurial as it does for
2671 d
.addCallback(self
._testSourceURLPresent
)
2674 def testPatch(self
):
2675 self
.helper
.vcargs
= self
.default_args()
2679 def testCheckoutBranch(self
):
2680 self
.helper
.vcargs
= self
.default_args()
2681 d
= self
.do_branch()
2682 d
.addCallback(self
._testSourceURLPresent
)
2685 def serveHTTP(self
):
2686 # the easiest way to publish hg over HTTP is by running 'hg serve' as
2687 # a child process while the test is running. (you can also use a CGI
2688 # script, which sounds difficult, or you can publish the files
2689 # directly, which isn't well documented).
2691 # grr.. 'hg serve' doesn't let you use --port=0 to mean "pick a free
2692 # port", instead it uses it as a signal to use the default (port
2693 # 8000). This means there is no way to make it choose a free port, so
2694 # we are forced to make it use a statically-defined one, making it
2695 # harder to avoid collisions.
2696 self
.httpPort
= 8300 + (os
.getpid() % 200)
2697 args
= [self
.helper
.vcexe
,
2698 "serve", "--port", str(self
.httpPort
), "--verbose"]
2700 # in addition, hg doesn't flush its stdout, so we can't wait for the
2701 # "listening at" message to know when it's safe to start the test.
2702 # Instead, poll every second until a getPage works.
2704 self
._pp
= MercurialServerPP() # logs+discards everything
2705 # this serves one tree at a time, so we serve trunk. TODO: test hg's
2706 # in-repo branches, for which a single tree will hold all branches.
2707 self
._hg
_server
= reactor
.spawnProcess(self
._pp
, self
.helper
.vcexe
, args
,
2710 log
.msg("waiting for hg serve to start")
2711 done_d
= defer
.Deferred()
2713 d
= client
.getPage("http://localhost:%d/" % self
.httpPort
)
2715 log
.msg("hg serve appears to have started")
2716 self
._wait
_for
_server
_poller
.stop()
2717 done_d
.callback(None)
2718 def ignore_connection_refused(f
):
2719 f
.trap(error
.ConnectionRefusedError
)
2720 d
.addCallbacks(success
, ignore_connection_refused
)
2721 d
.addErrback(done_d
.errback
)
2723 self
._wait
_for
_server
_poller
= task
.LoopingCall(poll
)
2724 self
._wait
_for
_server
_poller
.start(0.5, True)
2728 if self
._wait
_for
_server
_poller
:
2729 if self
._wait
_for
_server
_poller
.running
:
2730 self
._wait
_for
_server
_poller
.stop()
2732 self
._hg
_server
.loseConnection()
2734 self
._hg
_server
.signalProcess("KILL")
2735 except error
.ProcessExitedAlready
:
2737 self
._hg
_server
= None
2738 return VCBase
.tearDown(self
)
2740 def tearDown2(self
):
2742 return self
._pp
.wait
2744 def testCheckoutHTTP(self
):
2745 d
= self
.serveHTTP()
2747 repourl
= "http://localhost:%d/" % self
.httpPort
2748 self
.helper
.vcargs
= self
.default_args()
2749 self
.helper
.vcargs
['repourl'] = repourl
2750 return self
.do_vctest(testRetry
=False)
2751 d
.addCallback(_started
)
2752 d
.addCallback(self
._testSourceURLPresent
)
2756 self
.helper
.vcargs
= self
.default_args()
2757 d
= self
.do_getpatch()
2760 VCS
.registerVC(MercurialInRepo
.vc_name
, MercurialInRepoHelper())
2763 class GitHelper(BaseHelper
):
2764 branchname
= "branch"
2765 try_branchname
= "branch"
2768 gitpaths
= which('git')
2770 return (False, "GIT is not installed")
2771 d
= utils
.getProcessOutput(gitpaths
[0], ["--version"], env
=os
.environ
)
2772 d
.addCallback(self
._capable
, gitpaths
[0])
2775 def _capable(self
, v
, vcexe
):
2777 m
= re
.search(r
'\b(\d+)\.(\d+)', v
)
2780 raise Exception, 'no regex match'
2782 ver
= tuple([int(num
) for num
in m
.groups()])
2784 # git-1.1.3 (as shipped with Dapper) doesn't understand 'git
2785 # init' (it wants 'git init-db'), and fails unit tests that
2786 # involve branches. git-1.5.3.6 (on my debian/unstable system)
2787 # works. I don't know where the dividing point is: if someone can
2788 # figure it out (or figure out how to make buildbot support more
2789 # versions), please update this check.
2791 return (False, "Found git (%s) but it is older than 1.2.x" % vcexe
)
2793 except Exception, e
:
2794 log
.msg("couldn't identify git version number in output:")
2795 log
.msg("'''%s'''" % v
)
2796 log
.msg("because: %s" % e
)
2797 log
.msg("skipping tests")
2799 "Found git (%s) but couldn't identify its version from '%s'" % (vcexe
, v
))
2804 def createRepository(self
):
2805 self
.createBasedir()
2806 self
.gitrepo
= os
.path
.join(self
.repbase
,
2808 tmp
= os
.path
.join(self
.repbase
, "gittmp")
2810 env
= os
.environ
.copy()
2811 env
['GIT_DIR'] = self
.gitrepo
2812 w
= self
.dovc(self
.repbase
, "init", env
=env
)
2813 yield w
; w
.getResult()
2816 w
= self
.dovc(tmp
, "init")
2817 yield w
; w
.getResult()
2818 w
= self
.dovc(tmp
, ["add", "."])
2819 yield w
; w
.getResult()
2820 w
= self
.dovc(tmp
, ["config", "user.email", "buildbot-trial@localhost"])
2821 yield w
; w
.getResult()
2822 w
= self
.dovc(tmp
, ["config", "user.name", "Buildbot Trial"])
2823 yield w
; w
.getResult()
2824 w
= self
.dovc(tmp
, ["commit", "-m", "initial_import"])
2825 yield w
; w
.getResult()
2827 w
= self
.dovc(tmp
, ["checkout", "-b", self
.branchname
])
2828 yield w
; w
.getResult()
2829 self
.populate_branch(tmp
)
2830 w
= self
.dovc(tmp
, ["commit", "-a", "-m", "commit_on_branch"])
2831 yield w
; w
.getResult()
2833 w
= self
.dovc(tmp
, ["rev-parse", "master", self
.branchname
])
2834 yield w
; out
= w
.getResult()
2835 revs
= out
.splitlines()
2836 self
.addTrunkRev(revs
[0])
2837 self
.addBranchRev(revs
[1])
2839 w
= self
.dovc(tmp
, ["push", self
.gitrepo
, "master", self
.branchname
])
2840 yield w
; w
.getResult()
2843 createRepository
= deferredGenerator(createRepository
)
2845 def vc_revise(self
):
2846 tmp
= os
.path
.join(self
.repbase
, "gittmp")
2848 log
.msg("vc_revise" + self
.gitrepo
)
2849 w
= self
.dovc(self
.repbase
, ["clone", self
.gitrepo
, "gittmp"])
2850 yield w
; w
.getResult()
2851 w
= self
.dovc(tmp
, ["config", "user.email", "buildbot-trial@localhost"])
2852 yield w
; w
.getResult()
2853 w
= self
.dovc(tmp
, ["config", "user.name", "Buildbot Trial"])
2854 yield w
; w
.getResult()
2857 version_c
= VERSION_C
% self
.version
2858 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
2860 w
= self
.dovc(tmp
, ["commit", "-m", "revised_to_%d" % self
.version
,
2862 yield w
; w
.getResult()
2863 w
= self
.dovc(tmp
, ["rev-parse", "master"])
2864 yield w
; out
= w
.getResult()
2865 self
.addTrunkRev(out
.strip())
2867 w
= self
.dovc(tmp
, ["push", self
.gitrepo
, "master"])
2868 yield w
; out
= w
.getResult()
2870 vc_revise
= deferredGenerator(vc_revise
)
2872 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
2873 assert os
.path
.abspath(workdir
) == workdir
2874 if os
.path
.exists(workdir
):
2875 rmdirRecursive(workdir
)
2877 w
= self
.dovc(self
.repbase
, ["clone", self
.gitrepo
, workdir
])
2878 yield w
; w
.getResult()
2879 w
= self
.dovc(workdir
, ["config", "user.email", "buildbot-trial@localhost"])
2880 yield w
; w
.getResult()
2881 w
= self
.dovc(workdir
, ["config", "user.name", "Buildbot Trial"])
2882 yield w
; w
.getResult()
2884 if branch
is not None:
2885 w
= self
.dovc(workdir
, ["checkout", "-b", branch
,
2886 "origin/%s" % branch
])
2887 yield w
; w
.getResult()
2889 # Hmm...why do nobody else bother to check out the correct
2891 w
= self
.dovc(workdir
, ["reset", "--hard", rev
])
2892 yield w
; w
.getResult()
2894 try_c_filename
= os
.path
.join(workdir
, "subdir", "subdir.c")
2895 open(try_c_filename
, "w").write(TRY_C
)
2896 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
2898 def vc_try_finish(self
, workdir
):
2899 rmdirRecursive(workdir
)
2901 class Git(VCBase
, unittest
.TestCase
):
2904 # No 'export' mode yet...
2906 vctype
= "source.Git"
2908 has_got_revision
= True
2910 def testCheckout(self
):
2911 self
.helper
.vcargs
= { 'repourl': self
.helper
.gitrepo
}
2912 d
= self
.do_vctest()
2915 def testPatch(self
):
2916 self
.helper
.vcargs
= { 'repourl': self
.helper
.gitrepo
,
2917 'branch': "master" }
2921 def testCheckoutBranch(self
):
2922 self
.helper
.vcargs
= { 'repourl': self
.helper
.gitrepo
,
2923 'branch': "master" }
2924 d
= self
.do_branch()
2928 self
.helper
.vcargs
= { 'repourl': self
.helper
.gitrepo
,
2929 'branch': "master" }
2930 d
= self
.do_getpatch()
2933 # The git tests override parts of the test sequence to demonstrate
2934 # different update behavior. In the git cases, we always
2935 # effectively have a clobbered tree.
2937 def _do_vctest_update_3(self
, bs
):
2938 log
.msg("_do_vctest_update_3")
2939 self
.shouldExist(self
.workdir
, "main.c")
2940 self
.shouldExist(self
.workdir
, "version.c")
2941 self
.shouldContain(self
.workdir
, "version.c",
2942 "version=%d" % self
.helper
.version
)
2943 self
.failUnlessEqual(bs
.getProperty("revision"), None)
2944 self
.checkGotRevisionIsLatest(bs
)
2946 # now "update" to an older revision
2947 d
= self
.doBuild(ss
=SourceStamp(revision
=self
.helper
.trunk
[-2]))
2948 d
.addCallback(self
._do
_vctest
_update
_4)
2951 def _do_vctest_copy_2(self
, bs
):
2952 log
.msg("_do_vctest_copy 3")
2954 self
.shouldExist(self
.workdir
, self
.metadir
)
2955 self
.shouldNotExist(self
.workdir
, "newfile")
2956 self
.failUnlessEqual(bs
.getProperty("revision"), None)
2957 self
.checkGotRevisionIsLatest(bs
)
2958 self
.touch(self
.workdir
, "newfile")
2960 def _doBranch_3(self
, bs
):
2961 log
.msg("_doBranch_3")
2962 # make sure it is still on the branch
2963 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
2964 data
= open(main_c
, "r").read()
2965 self
.failUnlessIn("Hello branch.", data
)
2967 # now make sure that a non-branch checkout clobbers the tree
2968 d
= self
.doBuild(ss
=SourceStamp())
2969 d
.addCallback(self
._doBranch
_4)
2972 VCS
.registerVC(Git
.vc_name
, GitHelper())
2975 class Sources(unittest
.TestCase
):
2976 # TODO: this needs serious rethink
2977 def makeChange(self
, when
=None, revision
=None):
2979 when
= mktime_tz(parsedate_tz(when
))
2980 return changes
.Change("fred", [], "", when
=when
, revision
=revision
)
2983 r
= base
.BuildRequest("forced build", SourceStamp(), 'test_builder')
2985 s
= source
.CVS(cvsroot
=None, cvsmodule
=None)
2987 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), None)
2991 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2992 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2993 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2994 r
= base
.BuildRequest("forced", SourceStamp(changes
=c
), 'test_builder')
2995 submitted
= "Wed, 08 Sep 2004 09:04:00 -0700"
2996 r
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2998 s
= source
.CVS(cvsroot
=None, cvsmodule
=None)
3000 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
3001 "Wed, 08 Sep 2004 16:03:00 -0000")
3005 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
3006 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
3007 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
3008 r
= base
.BuildRequest("forced", SourceStamp(changes
=c
), 'test_builder')
3009 submitted
= "Wed, 08 Sep 2004 09:04:00 -0700"
3010 r
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
3012 s
= source
.CVS(cvsroot
=None, cvsmodule
=None, checkoutDelay
=10)
3014 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
3015 "Wed, 08 Sep 2004 16:02:10 -0000")
3019 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
3020 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
3021 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
3022 r1
= base
.BuildRequest("forced", SourceStamp(changes
=c
), 'test_builder')
3023 submitted
= "Wed, 08 Sep 2004 09:04:00 -0700"
3024 r1
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
3027 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:05:00 -0700"))
3028 r2
= base
.BuildRequest("forced", SourceStamp(changes
=c
), 'test_builder')
3029 submitted
= "Wed, 08 Sep 2004 09:07:00 -0700"
3030 r2
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
3032 b
= base
.Build([r1
, r2
])
3033 s
= source
.CVS(cvsroot
=None, cvsmodule
=None)
3035 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
3036 "Wed, 08 Sep 2004 16:06:00 -0000")
3039 r
= base
.BuildRequest("forced", SourceStamp(), 'test_builder')
3041 s
= source
.SVN(svnurl
="dummy")
3043 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), None)
3047 c
.append(self
.makeChange(revision
=4))
3048 c
.append(self
.makeChange(revision
=10))
3049 c
.append(self
.makeChange(revision
=67))
3050 r
= base
.BuildRequest("forced", SourceStamp(changes
=c
), 'test_builder')
3052 s
= source
.SVN(svnurl
="dummy")
3054 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), 67)
3056 class Patch(VCBase
, unittest
.TestCase
):
3063 def testPatch(self
):
3064 # invoke 'patch' all by itself, to see if it works the way we think
3065 # it should. This is intended to ferret out some windows test
3067 helper
= BaseHelper()
3068 self
.workdir
= os
.path
.join("test_vc", "testPatch")
3069 helper
.populate(self
.workdir
)
3070 patch
= which("patch")[0]
3072 command
= [patch
, "-p0"]
3075 def sendUpdate(self
, status
):
3077 c
= commands
.ShellCommand(FakeBuilder(), command
, self
.workdir
,
3078 sendRC
=False, initialStdin
=p0_diff
)
3080 d
.addCallback(self
._testPatch
_1)
3083 def _testPatch_1(self
, res
):
3084 # make sure the file actually got patched
3085 subdir_c
= os
.path
.join(self
.workdir
, "subdir", "subdir.c")
3086 data
= open(subdir_c
, "r").read()
3087 self
.failUnlessIn("Hello patched subdir.\\n", data
)