1 # -*- test-case-name: buildbot.test.test_vc -*-
3 from __future__
import generators
5 import sys
, os
, time
, re
6 from email
.Utils
import mktime_tz
, parsedate_tz
8 from twisted
.trial
import unittest
9 from twisted
.internet
import defer
, reactor
, utils
, protocol
, error
10 from twisted
.python
import failure
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
.twcompat
import maybeWait
, which
26 from buildbot
.scripts
import tryclient
27 from buildbot
.test
.runutils
import SignalMixin
29 #step.LoggedRemoteCommand.debug = True
31 # buildbot.twcompat will patch these into t.i.defer if necessary
32 from twisted
.internet
.defer
import waitForDeferred
, deferredGenerator
34 # Most of these tests (all but SourceStamp) depend upon having a set of
35 # repositories from which we can perform checkouts. These repositories are
36 # created by the setUp method at the start of each test class. In earlier
37 # versions these repositories were created offline and distributed with a
38 # separate tarball named 'buildbot-test-vc-1.tar.gz'. This is no longer
41 # CVS requires a local file repository. Providing remote access is beyond
42 # the feasible abilities of this test program (needs pserver or ssh).
44 # SVN requires a local file repository. To provide remote access over HTTP
45 # requires an apache server with DAV support and mod_svn, way beyond what we
48 # Arch and Darcs both allow remote (read-only) operation with any web
49 # server. We test both local file access and HTTP access (by spawning a
50 # small web server to provide access to the repository files while the test
53 # Perforce starts the daemon running on localhost. Unfortunately, it must
54 # use a predetermined Internet-domain port number, unless we want to go
55 # all-out: bind the listen socket ourselves and pretend to be inetd.
63 class _PutEverythingGetter(protocol
.ProcessProtocol
):
64 def __init__(self
, deferred
, stdin
):
65 self
.deferred
= deferred
66 self
.outBuf
= StringIO
.StringIO()
67 self
.errBuf
= StringIO
.StringIO()
68 self
.outReceived
= self
.outBuf
.write
69 self
.errReceived
= self
.errBuf
.write
72 def connectionMade(self
):
73 if self
.stdin
is not None:
74 self
.transport
.write(self
.stdin
)
75 self
.transport
.closeStdin()
77 def processEnded(self
, reason
):
78 out
= self
.outBuf
.getvalue()
79 err
= self
.errBuf
.getvalue()
83 self
.deferred
.errback((out
, err
, e
.signal
))
85 self
.deferred
.callback((out
, err
, code
))
87 def myGetProcessOutputAndValue(executable
, args
=(), env
={}, path
='.',
88 reactor
=None, stdin
=None):
89 """Like twisted.internet.utils.getProcessOutputAndValue but takes
92 from twisted
.internet
import reactor
94 p
= _PutEverythingGetter(d
, stdin
)
95 reactor
.spawnProcess(p
, executable
, (executable
,)+tuple(args
), env
, path
)
99 from buildbot.process import factory
100 from buildbot.steps import source
103 f1 = factory.BuildFactory([
107 c['bots'] = [['bot1', 'sekrit']]
110 c['builders'] = [{'name': 'vc', 'slavename': 'bot1',
111 'builddir': 'vc-dir', 'factory': f1}]
112 c['slavePortnum'] = 0
113 BuildmasterConfig = c
117 Index: subdir/subdir.c
118 ===================================================================
119 RCS file: /home/warner/stuff/Projects/BuildBot/code-arch/_trial_temp/test_vc/repositories/CVS-Repository/sample/subdir/subdir.c,v
120 retrieving revision 1.1.1.1
121 diff -u -r1.1.1.1 subdir.c
122 --- subdir/subdir.c 14 Aug 2005 01:32:49 -0000 1.1.1.1
123 +++ subdir/subdir.c 14 Aug 2005 01:36:15 -0000
126 main(int argc, const char *argv[])
128 - printf("Hello subdir.\n");
129 + printf("Hello patched subdir.\n");
134 # this patch does not include the filename headers, so it is
139 main(int argc, const char *argv[])
141 - printf("Hello subdir.\\n");
142 + printf("Hello try.\\n");
152 main(int argc, const char *argv[])
154 printf("Hello world.\\n");
164 main(int argc, const char *argv[])
166 printf("Hello branch.\\n");
176 main(int argc, const char *argv[])
178 printf("Hello world, version=%d\\n");
184 // this is subdir/subdir.c
188 main(int argc, const char *argv[])
190 printf("Hello subdir.\\n");
196 // this is subdir/subdir.c
200 main(int argc, const char *argv[])
202 printf("Hello try.\\n");
211 # this is a helper class which keeps track of whether each VC system is
212 # available, and whether the repository for each has been created. There
213 # is one instance of this class, at module level, shared between all test
222 def registerVC(self
, name
, helper
):
223 self
._helpers
[name
] = helper
224 self
._repoReady
[name
] = False
226 def skipIfNotCapable(self
, name
):
227 """Either return None, or raise SkipTest"""
228 d
= self
.capable(name
)
231 raise unittest
.SkipTest(res
[1])
232 d
.addCallback(_maybeSkip
)
235 def capable(self
, name
):
236 """Return a Deferred that fires with (True,None) if this host offers
237 the given VC tool, or (False,excuse) if it does not (and therefore
238 the tests should be skipped)."""
240 if self
._isCapable
.has_key(name
):
241 if self
._isCapable
[name
]:
242 return defer
.succeed((True,None))
244 return defer
.succeed((False, self
._excuses
[name
]))
245 d
= defer
.maybeDeferred(self
._helpers
[name
].capable
)
248 self
._isCapable
[name
] = True
250 self
._excuses
[name
] = res
[1]
252 d
.addCallback(_capable
)
255 def getHelper(self
, name
):
256 return self
._helpers
[name
]
258 def createRepository(self
, name
):
259 """Return a Deferred that fires when the repository is set up."""
260 if self
._repoReady
[name
]:
261 return defer
.succeed(True)
262 d
= self
._helpers
[name
].createRepository()
264 self
._repoReady
[name
] = True
265 d
.addCallback(_ready
)
271 # the overall plan here:
273 # Each VC system is tested separately, all using the same source tree defined
274 # in the 'files' dictionary above. Each VC system gets its own TestCase
275 # subclass. The first test case that is run will create the repository during
276 # setUp(), making two branches: 'trunk' and 'branch'. The trunk gets a copy
277 # of all the files in 'files'. The variant of good.c is committed on the
280 # then testCheckout is run, which does a number of checkout/clobber/update
281 # builds. These all use trunk r1. It then runs self.fix(), which modifies
282 # 'fixable.c', then performs another build and makes sure the tree has been
285 # testBranch uses trunk-r1 and branch-r1, making sure that we clobber the
286 # tree properly when we switch between them
288 # testPatch does a trunk-r1 checkout and applies a patch.
290 # testTryGetPatch performs a trunk-r1 checkout, modifies some files, then
291 # verifies that tryclient.getSourceStamp figures out the base revision and
295 # vc_create makes a repository at r1 with three files: main.c, version.c, and
296 # subdir/foo.c . It also creates a branch from r1 (called b1) in which main.c
297 # says "hello branch" instead of "hello world". self.trunk[] contains
298 # revision stamps for everything on the trunk, and self.branch[] does the
299 # same for the branch.
301 # vc_revise() checks out a tree at HEAD, changes version.c, then checks it
302 # back in. The new version stamp is appended to self.trunk[]. The tree is
303 # removed afterwards.
305 # vc_try_checkout(workdir, rev) checks out a tree at REV, then changes
306 # subdir/subdir.c to say 'Hello try'
307 # vc_try_finish(workdir) removes the tree and cleans up any VC state
308 # necessary (like deleting the Arch archive entry).
318 # this is also responsible for setting self.vcexe
319 raise NotImplementedError
321 def createBasedir(self
):
322 # you must call this from createRepository
323 self
.repbase
= os
.path
.abspath(os
.path
.join("test_vc",
325 if not os
.path
.isdir(self
.repbase
):
326 os
.makedirs(self
.repbase
)
328 def createRepository(self
):
329 # this will only be called once per process
330 raise NotImplementedError
332 def populate(self
, basedir
):
334 os
.makedirs(os
.path
.join(basedir
, "subdir"))
335 open(os
.path
.join(basedir
, "main.c"), "w").write(MAIN_C
)
337 version_c
= VERSION_C
% self
.version
338 open(os
.path
.join(basedir
, "version.c"), "w").write(version_c
)
339 open(os
.path
.join(basedir
, "main.c"), "w").write(MAIN_C
)
340 open(os
.path
.join(basedir
, "subdir", "subdir.c"), "w").write(SUBDIR_C
)
342 def populate_branch(self
, basedir
):
343 open(os
.path
.join(basedir
, "main.c"), "w").write(BRANCH_C
)
345 def addTrunkRev(self
, rev
):
346 self
.trunk
.append(rev
)
347 self
.allrevs
.append(rev
)
348 def addBranchRev(self
, rev
):
349 self
.branch
.append(rev
)
350 self
.allrevs
.append(rev
)
352 def runCommand(self
, basedir
, command
, failureIsOk
=False, stdin
=None):
353 # all commands passed to do() should be strings or lists. If they are
354 # strings, none of the arguments may have spaces. This makes the
355 # commands less verbose at the expense of restricting what they can
357 if type(command
) not in (list, tuple):
358 command
= command
.split(" ")
361 print "do %s" % command
362 print " in basedir %s" % basedir
364 print " STDIN:\n", stdin
, "\n--STDIN DONE"
365 env
= os
.environ
.copy()
367 d
= myGetProcessOutputAndValue(command
[0], command
[1:],
368 env
=env
, path
=basedir
,
370 def check((out
, err
, code
)):
373 print "command was: %s" % command
374 if out
: print "out: %s" % out
375 if err
: print "err: %s" % err
376 print "code: %s" % code
377 if code
!= 0 and not failureIsOk
:
378 log
.msg("command %s finished with exit code %d" %
380 log
.msg(" and stdout %s" % (out
,))
381 log
.msg(" and stderr %s" % (err
,))
382 raise RuntimeError("command %s finished with exit code %d"
384 + ": see logs for stdout")
389 def do(self
, basedir
, command
, failureIsOk
=False, stdin
=None):
390 d
= self
.runCommand(basedir
, command
, failureIsOk
=failureIsOk
,
392 return waitForDeferred(d
)
394 def dovc(self
, basedir
, command
, failureIsOk
=False, stdin
=None):
395 """Like do(), but the VC binary will be prepended to COMMAND."""
396 if isinstance(command
, (str, unicode)):
397 command
= self
.vcexe
+ " " + command
400 command
= [self
.vcexe
] + command
401 return self
.do(basedir
, command
, failureIsOk
, stdin
)
403 class VCBase(SignalMixin
):
405 createdRepository
= False
412 has_got_revision
= False
413 has_got_revision_branches_are_merged
= False # for SVN
415 def failUnlessIn(self
, substring
, string
, msg
=None):
416 # trial provides a version of this that requires python-2.3 to test
419 msg
= ("did not see the expected substring '%s' in string '%s'" %
421 self
.failUnless(string
.find(substring
) != -1, msg
)
424 d
= VCS
.skipIfNotCapable(self
.vc_name
)
425 d
.addCallback(self
._setUp
1)
428 def _setUp1(self
, res
):
429 self
.helper
= VCS
.getHelper(self
.vc_name
)
431 if os
.path
.exists("basedir"):
432 rmdirRecursive("basedir")
434 self
.master
= master
.BuildMaster("basedir")
435 self
.slavebase
= os
.path
.abspath("slavebase")
436 if os
.path
.exists(self
.slavebase
):
437 rmdirRecursive(self
.slavebase
)
438 os
.mkdir("slavebase")
440 d
= VCS
.createRepository(self
.vc_name
)
443 def connectSlave(self
):
444 port
= self
.master
.slavePort
._port
.getHost().port
445 slave
= bot
.BuildSlave("localhost", port
, "bot1", "sekrit",
446 self
.slavebase
, keepalive
=0, usePTY
=1)
449 d
= self
.master
.botmaster
.waitUntilBuilderAttached("vc")
452 def loadConfig(self
, config
):
453 # reloading the config file causes a new 'listDirs' command to be
454 # sent to the slave. To synchronize on this properly, it is easiest
455 # to stop and restart the slave.
456 d
= defer
.succeed(None)
458 d
= self
.master
.botmaster
.waitUntilBuilderDetached("vc")
459 self
.slave
.stopService()
460 d
.addCallback(lambda res
: self
.master
.loadConfig(config
))
461 d
.addCallback(lambda res
: self
.connectSlave())
465 # launch an HTTP server to serve the repository files
466 from twisted
.web
import static
, server
467 from twisted
.internet
import reactor
468 self
.root
= static
.File(self
.helper
.repbase
)
469 self
.site
= server
.Site(self
.root
)
470 self
.httpServer
= reactor
.listenTCP(0, self
.site
)
471 self
.httpPort
= self
.httpServer
.getHost().port
473 def doBuild(self
, shouldSucceed
=True, ss
=None):
474 c
= interfaces
.IControl(self
.master
)
478 #print "doBuild(ss: b=%s rev=%s)" % (ss.branch, ss.revision)
479 req
= base
.BuildRequest("test_vc forced build", ss
)
480 d
= req
.waitUntilFinished()
481 c
.getBuilder("vc").requestBuild(req
)
482 d
.addCallback(self
._doBuild
_1, shouldSucceed
)
484 def _doBuild_1(self
, bs
, shouldSucceed
):
486 if r
!= SUCCESS
and shouldSucceed
:
489 if not bs
.isFinished():
490 print "Hey, build wasn't even finished!"
491 print "Build did not succeed:", r
, bs
.getText()
492 for s
in bs
.getSteps():
493 for l
in s
.getLogs():
494 print "--- START step %s / log %s ---" % (s
.getName(),
496 print l
.getTextWithHeaders()
499 self
.fail("build did not succeed")
502 def touch(self
, d
, f
):
503 open(os
.path
.join(d
,f
),"w").close()
504 def shouldExist(self
, *args
):
505 target
= os
.path
.join(*args
)
506 self
.failUnless(os
.path
.exists(target
),
507 "expected to find %s but didn't" % target
)
508 def shouldNotExist(self
, *args
):
509 target
= os
.path
.join(*args
)
510 self
.failIf(os
.path
.exists(target
),
511 "expected to NOT find %s, but did" % target
)
512 def shouldContain(self
, d
, f
, contents
):
513 c
= open(os
.path
.join(d
, f
), "r").read()
514 self
.failUnlessIn(contents
, c
)
516 def checkGotRevision(self
, bs
, expected
):
517 if self
.has_got_revision
:
518 self
.failUnlessEqual(bs
.getProperty("got_revision"), expected
)
520 def checkGotRevisionIsLatest(self
, bs
):
521 expected
= self
.helper
.trunk
[-1]
522 if self
.has_got_revision_branches_are_merged
:
523 expected
= self
.helper
.allrevs
[-1]
524 self
.checkGotRevision(bs
, expected
)
526 def do_vctest(self
, testRetry
=True):
528 args
= self
.helper
.vcargs
530 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
531 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
532 # woo double-substitution
533 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
534 for k
,v
in args
.items():
535 s
+= ", %s=%s" % (k
, repr(v
))
537 config
= config_vc
% s
539 m
.loadConfig(config
% 'clobber')
543 d
= self
.connectSlave()
544 d
.addCallback(lambda res
: log
.msg("testing clobber"))
545 d
.addCallback(self
._do
_vctest
_clobber
)
546 d
.addCallback(lambda res
: log
.msg("doing update"))
547 d
.addCallback(lambda res
: self
.loadConfig(config
% 'update'))
548 d
.addCallback(lambda res
: log
.msg("testing update"))
549 d
.addCallback(self
._do
_vctest
_update
)
551 d
.addCallback(lambda res
: log
.msg("testing update retry"))
552 d
.addCallback(self
._do
_vctest
_update
_retry
)
553 d
.addCallback(lambda res
: log
.msg("doing copy"))
554 d
.addCallback(lambda res
: self
.loadConfig(config
% 'copy'))
555 d
.addCallback(lambda res
: log
.msg("testing copy"))
556 d
.addCallback(self
._do
_vctest
_copy
)
558 d
.addCallback(lambda res
: log
.msg("doing export"))
559 d
.addCallback(lambda res
: self
.loadConfig(config
% 'export'))
560 d
.addCallback(lambda res
: log
.msg("testing export"))
561 d
.addCallback(self
._do
_vctest
_export
)
564 def _do_vctest_clobber(self
, res
):
565 d
= self
.doBuild() # initial checkout
566 d
.addCallback(self
._do
_vctest
_clobber
_1)
568 def _do_vctest_clobber_1(self
, bs
):
569 self
.shouldExist(self
.workdir
, "main.c")
570 self
.shouldExist(self
.workdir
, "version.c")
571 self
.shouldExist(self
.workdir
, "subdir", "subdir.c")
573 self
.shouldExist(self
.workdir
, self
.metadir
)
574 self
.failUnlessEqual(bs
.getProperty("revision"), None)
575 self
.failUnlessEqual(bs
.getProperty("branch"), None)
576 self
.checkGotRevisionIsLatest(bs
)
578 self
.touch(self
.workdir
, "newfile")
579 self
.shouldExist(self
.workdir
, "newfile")
580 d
= self
.doBuild() # rebuild clobbers workdir
581 d
.addCallback(self
._do
_vctest
_clobber
_2)
583 def _do_vctest_clobber_2(self
, res
):
584 self
.shouldNotExist(self
.workdir
, "newfile")
586 def _do_vctest_update(self
, res
):
587 log
.msg("_do_vctest_update")
588 d
= self
.doBuild() # rebuild with update
589 d
.addCallback(self
._do
_vctest
_update
_1)
591 def _do_vctest_update_1(self
, bs
):
592 log
.msg("_do_vctest_update_1")
593 self
.shouldExist(self
.workdir
, "main.c")
594 self
.shouldExist(self
.workdir
, "version.c")
595 self
.shouldContain(self
.workdir
, "version.c",
596 "version=%d" % self
.helper
.version
)
598 self
.shouldExist(self
.workdir
, self
.metadir
)
599 self
.failUnlessEqual(bs
.getProperty("revision"), None)
600 self
.checkGotRevisionIsLatest(bs
)
602 self
.touch(self
.workdir
, "newfile")
603 d
= self
.doBuild() # update rebuild leaves new files
604 d
.addCallback(self
._do
_vctest
_update
_2)
606 def _do_vctest_update_2(self
, bs
):
607 log
.msg("_do_vctest_update_2")
608 self
.shouldExist(self
.workdir
, "main.c")
609 self
.shouldExist(self
.workdir
, "version.c")
610 self
.touch(self
.workdir
, "newfile")
611 # now make a change to the repository and make sure we pick it up
612 d
= self
.helper
.vc_revise()
613 d
.addCallback(lambda res
: self
.doBuild())
614 d
.addCallback(self
._do
_vctest
_update
_3)
616 def _do_vctest_update_3(self
, bs
):
617 log
.msg("_do_vctest_update_3")
618 self
.shouldExist(self
.workdir
, "main.c")
619 self
.shouldExist(self
.workdir
, "version.c")
620 self
.shouldContain(self
.workdir
, "version.c",
621 "version=%d" % self
.helper
.version
)
622 self
.shouldExist(self
.workdir
, "newfile")
623 self
.failUnlessEqual(bs
.getProperty("revision"), None)
624 self
.checkGotRevisionIsLatest(bs
)
626 # now "update" to an older revision
627 d
= self
.doBuild(ss
=SourceStamp(revision
=self
.helper
.trunk
[-2]))
628 d
.addCallback(self
._do
_vctest
_update
_4)
630 def _do_vctest_update_4(self
, bs
):
631 log
.msg("_do_vctest_update_4")
632 self
.shouldExist(self
.workdir
, "main.c")
633 self
.shouldExist(self
.workdir
, "version.c")
634 self
.shouldContain(self
.workdir
, "version.c",
635 "version=%d" % (self
.helper
.version
-1))
636 self
.failUnlessEqual(bs
.getProperty("revision"),
637 self
.helper
.trunk
[-2])
638 self
.checkGotRevision(bs
, self
.helper
.trunk
[-2])
640 # now update to the newer revision
641 d
= self
.doBuild(ss
=SourceStamp(revision
=self
.helper
.trunk
[-1]))
642 d
.addCallback(self
._do
_vctest
_update
_5)
644 def _do_vctest_update_5(self
, bs
):
645 log
.msg("_do_vctest_update_5")
646 self
.shouldExist(self
.workdir
, "main.c")
647 self
.shouldExist(self
.workdir
, "version.c")
648 self
.shouldContain(self
.workdir
, "version.c",
649 "version=%d" % self
.helper
.version
)
650 self
.failUnlessEqual(bs
.getProperty("revision"),
651 self
.helper
.trunk
[-1])
652 self
.checkGotRevision(bs
, self
.helper
.trunk
[-1])
655 def _do_vctest_update_retry(self
, res
):
656 # certain local changes will prevent an update from working. The
657 # most common is to replace a file with a directory, or vice
658 # versa. The slave code should spot the failure and do a
660 os
.unlink(os
.path
.join(self
.workdir
, "main.c"))
661 os
.mkdir(os
.path
.join(self
.workdir
, "main.c"))
662 self
.touch(os
.path
.join(self
.workdir
, "main.c"), "foo")
663 self
.touch(self
.workdir
, "newfile")
665 d
= self
.doBuild() # update, but must clobber to handle the error
666 d
.addCallback(self
._do
_vctest
_update
_retry
_1)
668 def _do_vctest_update_retry_1(self
, bs
):
669 self
.shouldNotExist(self
.workdir
, "newfile")
671 def _do_vctest_copy(self
, res
):
672 d
= self
.doBuild() # copy rebuild clobbers new files
673 d
.addCallback(self
._do
_vctest
_copy
_1)
675 def _do_vctest_copy_1(self
, bs
):
677 self
.shouldExist(self
.workdir
, self
.metadir
)
678 self
.shouldNotExist(self
.workdir
, "newfile")
679 self
.touch(self
.workdir
, "newfile")
680 self
.touch(self
.vcdir
, "newvcfile")
681 self
.failUnlessEqual(bs
.getProperty("revision"), None)
682 self
.checkGotRevisionIsLatest(bs
)
684 d
= self
.doBuild() # copy rebuild clobbers new files
685 d
.addCallback(self
._do
_vctest
_copy
_2)
687 def _do_vctest_copy_2(self
, bs
):
689 self
.shouldExist(self
.workdir
, self
.metadir
)
690 self
.shouldNotExist(self
.workdir
, "newfile")
691 self
.shouldExist(self
.vcdir
, "newvcfile")
692 self
.shouldExist(self
.workdir
, "newvcfile")
693 self
.failUnlessEqual(bs
.getProperty("revision"), None)
694 self
.checkGotRevisionIsLatest(bs
)
695 self
.touch(self
.workdir
, "newfile")
697 def _do_vctest_export(self
, res
):
698 d
= self
.doBuild() # export rebuild clobbers new files
699 d
.addCallback(self
._do
_vctest
_export
_1)
701 def _do_vctest_export_1(self
, bs
):
702 self
.shouldNotExist(self
.workdir
, self
.metadir
)
703 self
.shouldNotExist(self
.workdir
, "newfile")
704 self
.failUnlessEqual(bs
.getProperty("revision"), None)
705 #self.checkGotRevisionIsLatest(bs)
706 # VC 'export' is not required to have a got_revision
707 self
.touch(self
.workdir
, "newfile")
709 d
= self
.doBuild() # export rebuild clobbers new files
710 d
.addCallback(self
._do
_vctest
_export
_2)
712 def _do_vctest_export_2(self
, bs
):
713 self
.shouldNotExist(self
.workdir
, self
.metadir
)
714 self
.shouldNotExist(self
.workdir
, "newfile")
715 self
.failUnlessEqual(bs
.getProperty("revision"), None)
716 #self.checkGotRevisionIsLatest(bs)
717 # VC 'export' is not required to have a got_revision
721 args
= self
.helper
.vcargs
723 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
724 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
725 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
726 for k
,v
in args
.items():
727 s
+= ", %s=%s" % (k
, repr(v
))
729 self
.config
= config_vc
% s
731 m
.loadConfig(self
.config
% "clobber")
735 ss
= SourceStamp(revision
=self
.helper
.trunk
[-1], patch
=(0, p0_diff
))
737 d
= self
.connectSlave()
738 d
.addCallback(lambda res
: self
.doBuild(ss
=ss
))
739 d
.addCallback(self
._doPatch
_1)
741 def _doPatch_1(self
, bs
):
742 self
.shouldContain(self
.workdir
, "version.c",
743 "version=%d" % self
.helper
.version
)
744 # make sure the file actually got patched
745 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
746 "subdir", "subdir.c")
747 data
= open(subdir_c
, "r").read()
748 self
.failUnlessIn("Hello patched subdir.\\n", data
)
749 self
.failUnlessEqual(bs
.getProperty("revision"),
750 self
.helper
.trunk
[-1])
751 self
.checkGotRevision(bs
, self
.helper
.trunk
[-1])
753 # make sure that a rebuild does not use the leftover patched workdir
754 d
= self
.master
.loadConfig(self
.config
% "update")
755 d
.addCallback(lambda res
: self
.doBuild(ss
=None))
756 d
.addCallback(self
._doPatch
_2)
758 def _doPatch_2(self
, bs
):
759 # make sure the file is back to its original
760 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
761 "subdir", "subdir.c")
762 data
= open(subdir_c
, "r").read()
763 self
.failUnlessIn("Hello subdir.\\n", data
)
764 self
.failUnlessEqual(bs
.getProperty("revision"), None)
765 self
.checkGotRevisionIsLatest(bs
)
767 # now make sure we can patch an older revision. We need at least two
768 # revisions here, so we might have to create one first
769 if len(self
.helper
.trunk
) < 2:
770 d
= self
.helper
.vc_revise()
771 d
.addCallback(self
._doPatch
_3)
773 return self
._doPatch
_3()
775 def _doPatch_3(self
, res
=None):
776 ss
= SourceStamp(revision
=self
.helper
.trunk
[-2], patch
=(0, p0_diff
))
777 d
= self
.doBuild(ss
=ss
)
778 d
.addCallback(self
._doPatch
_4)
780 def _doPatch_4(self
, bs
):
781 self
.shouldContain(self
.workdir
, "version.c",
782 "version=%d" % (self
.helper
.version
-1))
783 # and make sure the file actually got patched
784 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
785 "subdir", "subdir.c")
786 data
= open(subdir_c
, "r").read()
787 self
.failUnlessIn("Hello patched subdir.\\n", data
)
788 self
.failUnlessEqual(bs
.getProperty("revision"),
789 self
.helper
.trunk
[-2])
790 self
.checkGotRevision(bs
, self
.helper
.trunk
[-2])
792 # now check that we can patch a branch
793 ss
= SourceStamp(branch
=self
.helper
.branchname
,
794 revision
=self
.helper
.branch
[-1],
796 d
= self
.doBuild(ss
=ss
)
797 d
.addCallback(self
._doPatch
_5)
799 def _doPatch_5(self
, bs
):
800 self
.shouldContain(self
.workdir
, "version.c",
802 self
.shouldContain(self
.workdir
, "main.c", "Hello branch.")
803 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
804 "subdir", "subdir.c")
805 data
= open(subdir_c
, "r").read()
806 self
.failUnlessIn("Hello patched subdir.\\n", data
)
807 self
.failUnlessEqual(bs
.getProperty("revision"),
808 self
.helper
.branch
[-1])
809 self
.failUnlessEqual(bs
.getProperty("branch"), self
.helper
.branchname
)
810 self
.checkGotRevision(bs
, self
.helper
.branch
[-1])
813 def do_vctest_once(self
, shouldSucceed
):
816 args
= self
.helper
.vcargs
817 vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
818 workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
819 # woo double-substitution
820 s
= "s(%s, timeout=200, workdir='build', mode='clobber'" % (vctype
,)
821 for k
,v
in args
.items():
822 s
+= ", %s=%s" % (k
, repr(v
))
824 config
= config_vc
% s
831 d
= self
.doBuild(shouldSucceed
) # initial checkout
837 args
= self
.helper
.vcargs
839 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
840 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
841 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
842 for k
,v
in args
.items():
843 s
+= ", %s=%s" % (k
, repr(v
))
845 self
.config
= config_vc
% s
847 m
.loadConfig(self
.config
% "update")
851 # first we do a build of the trunk
852 d
= self
.connectSlave()
853 d
.addCallback(lambda res
: self
.doBuild(ss
=SourceStamp()))
854 d
.addCallback(self
._doBranch
_1)
856 def _doBranch_1(self
, bs
):
857 log
.msg("_doBranch_1")
858 # make sure the checkout was of the trunk
859 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
860 data
= open(main_c
, "r").read()
861 self
.failUnlessIn("Hello world.", data
)
863 # now do a checkout on the branch. The change in branch name should
865 self
.touch(self
.workdir
, "newfile")
866 d
= self
.doBuild(ss
=SourceStamp(branch
=self
.helper
.branchname
))
867 d
.addCallback(self
._doBranch
_2)
869 def _doBranch_2(self
, bs
):
870 log
.msg("_doBranch_2")
871 # make sure it was on the branch
872 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
873 data
= open(main_c
, "r").read()
874 self
.failUnlessIn("Hello branch.", data
)
875 # and make sure the tree was clobbered
876 self
.shouldNotExist(self
.workdir
, "newfile")
878 # doing another build on the same branch should not clobber the tree
879 self
.touch(self
.workdir
, "newbranchfile")
880 d
= self
.doBuild(ss
=SourceStamp(branch
=self
.helper
.branchname
))
881 d
.addCallback(self
._doBranch
_3)
883 def _doBranch_3(self
, bs
):
884 log
.msg("_doBranch_3")
885 # make sure it is still on the branch
886 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
887 data
= open(main_c
, "r").read()
888 self
.failUnlessIn("Hello branch.", data
)
889 # and make sure the tree was not clobbered
890 self
.shouldExist(self
.workdir
, "newbranchfile")
892 # now make sure that a non-branch checkout clobbers the tree
893 d
= self
.doBuild(ss
=SourceStamp())
894 d
.addCallback(self
._doBranch
_4)
896 def _doBranch_4(self
, bs
):
897 log
.msg("_doBranch_4")
898 # make sure it was on the trunk
899 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
900 data
= open(main_c
, "r").read()
901 self
.failUnlessIn("Hello world.", data
)
902 self
.shouldNotExist(self
.workdir
, "newbranchfile")
904 def do_getpatch(self
, doBranch
=True):
905 log
.msg("do_getpatch")
906 # prepare a buildslave to do checkouts
908 args
= self
.helper
.vcargs
910 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
911 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
912 # woo double-substitution
913 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
914 for k
,v
in args
.items():
915 s
+= ", %s=%s" % (k
, repr(v
))
917 config
= config_vc
% s
919 m
.loadConfig(config
% 'clobber')
923 d
= self
.connectSlave()
925 # then set up the "developer's tree". first we modify a tree from the
927 tmpdir
= "try_workdir"
928 self
.trydir
= os
.path
.join(self
.helper
.repbase
, tmpdir
)
929 rmdirRecursive(self
.trydir
)
930 d
.addCallback(self
.do_getpatch_trunkhead
)
931 d
.addCallback(self
.do_getpatch_trunkold
)
933 d
.addCallback(self
.do_getpatch_branch
)
934 d
.addCallback(self
.do_getpatch_finish
)
937 def do_getpatch_finish(self
, res
):
938 log
.msg("do_getpatch_finish")
939 self
.helper
.vc_try_finish(self
.trydir
)
942 def try_shouldMatch(self
, filename
):
943 devfilename
= os
.path
.join(self
.trydir
, filename
)
944 devfile
= open(devfilename
, "r").read()
945 slavefilename
= os
.path
.join(self
.workdir
, filename
)
946 slavefile
= open(slavefilename
, "r").read()
947 self
.failUnlessEqual(devfile
, slavefile
,
948 ("slavefile (%s) contains '%s'. "
949 "developer's file (%s) contains '%s'. "
950 "These ought to match") %
951 (slavefilename
, slavefile
,
952 devfilename
, devfile
))
954 def do_getpatch_trunkhead(self
, res
):
955 log
.msg("do_getpatch_trunkhead")
956 d
= self
.helper
.vc_try_checkout(self
.trydir
, self
.helper
.trunk
[-1])
957 d
.addCallback(self
._do
_getpatch
_trunkhead
_1)
959 def _do_getpatch_trunkhead_1(self
, res
):
960 log
.msg("_do_getpatch_trunkhead_1")
961 d
= tryclient
.getSourceStamp(self
.vctype_try
, self
.trydir
, None)
962 d
.addCallback(self
._do
_getpatch
_trunkhead
_2)
964 def _do_getpatch_trunkhead_2(self
, ss
):
965 log
.msg("_do_getpatch_trunkhead_2")
966 d
= self
.doBuild(ss
=ss
)
967 d
.addCallback(self
._do
_getpatch
_trunkhead
_3)
969 def _do_getpatch_trunkhead_3(self
, res
):
970 log
.msg("_do_getpatch_trunkhead_3")
971 # verify that the resulting buildslave tree matches the developer's
972 self
.try_shouldMatch("main.c")
973 self
.try_shouldMatch("version.c")
974 self
.try_shouldMatch(os
.path
.join("subdir", "subdir.c"))
976 def do_getpatch_trunkold(self
, res
):
977 log
.msg("do_getpatch_trunkold")
978 # now try a tree from an older revision. We need at least two
979 # revisions here, so we might have to create one first
980 if len(self
.helper
.trunk
) < 2:
981 d
= self
.helper
.vc_revise()
982 d
.addCallback(self
._do
_getpatch
_trunkold
_1)
984 return self
._do
_getpatch
_trunkold
_1()
985 def _do_getpatch_trunkold_1(self
, res
=None):
986 log
.msg("_do_getpatch_trunkold_1")
987 d
= self
.helper
.vc_try_checkout(self
.trydir
, self
.helper
.trunk
[-2])
988 d
.addCallback(self
._do
_getpatch
_trunkold
_2)
990 def _do_getpatch_trunkold_2(self
, res
):
991 log
.msg("_do_getpatch_trunkold_2")
992 d
= tryclient
.getSourceStamp(self
.vctype_try
, self
.trydir
, None)
993 d
.addCallback(self
._do
_getpatch
_trunkold
_3)
995 def _do_getpatch_trunkold_3(self
, ss
):
996 log
.msg("_do_getpatch_trunkold_3")
997 d
= self
.doBuild(ss
=ss
)
998 d
.addCallback(self
._do
_getpatch
_trunkold
_4)
1000 def _do_getpatch_trunkold_4(self
, res
):
1001 log
.msg("_do_getpatch_trunkold_4")
1002 # verify that the resulting buildslave tree matches the developer's
1003 self
.try_shouldMatch("main.c")
1004 self
.try_shouldMatch("version.c")
1005 self
.try_shouldMatch(os
.path
.join("subdir", "subdir.c"))
1007 def do_getpatch_branch(self
, res
):
1008 log
.msg("do_getpatch_branch")
1009 # now try a tree from a branch
1010 d
= self
.helper
.vc_try_checkout(self
.trydir
, self
.helper
.branch
[-1],
1011 self
.helper
.branchname
)
1012 d
.addCallback(self
._do
_getpatch
_branch
_1)
1014 def _do_getpatch_branch_1(self
, res
):
1015 log
.msg("_do_getpatch_branch_1")
1016 d
= tryclient
.getSourceStamp(self
.vctype_try
, self
.trydir
,
1017 self
.helper
.try_branchname
)
1018 d
.addCallback(self
._do
_getpatch
_branch
_2)
1020 def _do_getpatch_branch_2(self
, ss
):
1021 log
.msg("_do_getpatch_branch_2")
1022 d
= self
.doBuild(ss
=ss
)
1023 d
.addCallback(self
._do
_getpatch
_branch
_3)
1025 def _do_getpatch_branch_3(self
, res
):
1026 log
.msg("_do_getpatch_branch_3")
1027 # verify that the resulting buildslave tree matches the developer's
1028 self
.try_shouldMatch("main.c")
1029 self
.try_shouldMatch("version.c")
1030 self
.try_shouldMatch(os
.path
.join("subdir", "subdir.c"))
1033 def dumpPatch(self
, patch
):
1034 # this exists to help me figure out the right 'patchlevel' value
1035 # should be returned by tryclient.getSourceStamp
1037 open(n
,"w").write(patch
)
1038 d
= self
.runCommand(".", ["lsdiff", n
])
1039 def p(res
): print "lsdiff:", res
.strip().split("\n")
1045 d
= defer
.succeed(None)
1047 d2
= self
.master
.botmaster
.waitUntilBuilderDetached("vc")
1048 d
.addCallback(lambda res
: self
.slave
.stopService())
1049 d
.addCallback(lambda res
: d2
)
1051 d
.addCallback(lambda res
: self
.master
.stopService())
1053 d
.addCallback(lambda res
: self
.httpServer
.stopListening())
1054 def stopHTTPTimer():
1056 from twisted
.web
import http
# Twisted-2.0
1058 from twisted
.protocols
import http
# Twisted-1.3
1059 http
._logDateTimeStop
() # shut down the internal timer. DUMB!
1060 d
.addCallback(lambda res
: stopHTTPTimer())
1061 d
.addCallback(lambda res
: self
.tearDown2())
1064 def tearDown2(self
):
1067 class CVSHelper(BaseHelper
):
1068 branchname
= "branch"
1069 try_branchname
= "branch"
1072 cvspaths
= which('cvs')
1074 return (False, "CVS is not installed")
1075 # cvs-1.10 (as shipped with OS-X 10.3 "Panther") is too old for this
1076 # test. There is a situation where we check out a tree, make a
1077 # change, then commit it back, and CVS refuses to believe that we're
1078 # operating in a CVS tree. I tested cvs-1.12.9 and it works ok, OS-X
1079 # 10.4 "Tiger" comes with cvs-1.11, but I haven't tested that yet.
1080 # For now, skip the tests if we've got 1.10 .
1081 log
.msg("running %s --version.." % (cvspaths
[0],))
1082 d
= utils
.getProcessOutput(cvspaths
[0], ["--version"],
1084 d
.addCallback(self
._capable
, cvspaths
[0])
1087 def _capable(self
, v
, vcexe
):
1088 m
= re
.search(r
'\(CVS\) ([\d\.]+) ', v
)
1090 log
.msg("couldn't identify CVS version number in output:")
1091 log
.msg("'''%s'''" % v
)
1092 log
.msg("skipping tests")
1093 return (False, "Found CVS but couldn't identify its version")
1095 log
.msg("found CVS version '%s'" % ver
)
1097 return (False, "Found CVS, but it is too old")
1102 # this timestamp is eventually passed to CVS in a -D argument, and
1103 # strftime's %z specifier doesn't seem to work reliably (I get +0000
1104 # where I should get +0700 under linux sometimes, and windows seems
1105 # to want to put a verbose 'Eastern Standard Time' in there), so
1106 # leave off the timezone specifier and treat this as localtime. A
1107 # valid alternative would be to use a hard-coded +0000 and
1109 return time
.strftime("%Y-%m-%d %H:%M:%S", time
.localtime())
1111 def createRepository(self
):
1112 self
.createBasedir()
1113 self
.cvsrep
= cvsrep
= os
.path
.join(self
.repbase
, "CVS-Repository")
1114 tmp
= os
.path
.join(self
.repbase
, "cvstmp")
1116 w
= self
.dovc(self
.repbase
, "-d %s init" % cvsrep
)
1117 yield w
; w
.getResult() # we must getResult() to raise any exceptions
1120 cmd
= ("-d %s import" % cvsrep
+
1121 " -m sample_project_files sample vendortag start")
1122 w
= self
.dovc(tmp
, cmd
)
1123 yield w
; w
.getResult()
1125 # take a timestamp as the first revision number
1127 self
.addTrunkRev(self
.getdate())
1130 w
= self
.dovc(self
.repbase
,
1131 "-d %s checkout -d cvstmp sample" % self
.cvsrep
)
1132 yield w
; w
.getResult()
1134 w
= self
.dovc(tmp
, "tag -b %s" % self
.branchname
)
1135 yield w
; w
.getResult()
1136 self
.populate_branch(tmp
)
1138 "commit -m commit_on_branch -r %s" % self
.branchname
)
1139 yield w
; w
.getResult()
1142 self
.addBranchRev(self
.getdate())
1144 self
.vcargs
= { 'cvsroot': self
.cvsrep
, 'cvsmodule': "sample" }
1145 createRepository
= deferredGenerator(createRepository
)
1148 def vc_revise(self
):
1149 tmp
= os
.path
.join(self
.repbase
, "cvstmp")
1151 w
= self
.dovc(self
.repbase
,
1152 "-d %s checkout -d cvstmp sample" % self
.cvsrep
)
1153 yield w
; w
.getResult()
1155 version_c
= VERSION_C
% self
.version
1156 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1158 "commit -m revised_to_%d version.c" % self
.version
)
1159 yield w
; w
.getResult()
1162 self
.addTrunkRev(self
.getdate())
1164 vc_revise
= deferredGenerator(vc_revise
)
1166 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1167 # 'workdir' is an absolute path
1168 assert os
.path
.abspath(workdir
) == workdir
1169 cmd
= [self
.vcexe
, "-d", self
.cvsrep
, "checkout",
1172 if branch
is not None:
1175 cmd
.append("sample")
1176 w
= self
.do(self
.repbase
, cmd
)
1177 yield w
; w
.getResult()
1178 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1179 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1181 def vc_try_finish(self
, workdir
):
1182 rmdirRecursive(workdir
)
1184 class CVS(VCBase
, unittest
.TestCase
):
1188 vctype
= "source.CVS"
1190 # CVS gives us got_revision, but it is based entirely upon the local
1191 # clock, which means it is unlikely to match the timestamp taken earlier.
1192 # This might be enough for common use, but won't be good enough for our
1193 # tests to accept, so pretend it doesn't have got_revision at all.
1194 has_got_revision
= False
1196 def testCheckout(self
):
1197 d
= self
.do_vctest()
1200 def testPatch(self
):
1204 def testCheckoutBranch(self
):
1205 d
= self
.do_branch()
1209 d
= self
.do_getpatch(doBranch
=False)
1212 VCS
.registerVC(CVS
.vc_name
, CVSHelper())
1215 class SVNHelper(BaseHelper
):
1216 branchname
= "sample/branch"
1217 try_branchname
= "sample/branch"
1220 svnpaths
= which('svn')
1221 svnadminpaths
= which('svnadmin')
1223 return (False, "SVN is not installed")
1224 if not svnadminpaths
:
1225 return (False, "svnadmin is not installed")
1226 # we need svn to be compiled with the ra_local access
1228 log
.msg("running svn --version..")
1229 env
= os
.environ
.copy()
1231 d
= utils
.getProcessOutput(svnpaths
[0], ["--version"],
1233 d
.addCallback(self
._capable
, svnpaths
[0], svnadminpaths
[0])
1236 def _capable(self
, v
, vcexe
, svnadmin
):
1237 if v
.find("handles 'file' schem") != -1:
1238 # older versions say 'schema', 1.2.0 and beyond say 'scheme'
1240 self
.svnadmin
= svnadmin
1242 excuse
= ("%s found but it does not support 'file:' " +
1243 "schema, skipping svn tests") % vcexe
1245 return (False, excuse
)
1247 def createRepository(self
):
1248 self
.createBasedir()
1249 self
.svnrep
= os
.path
.join(self
.repbase
,
1250 "SVN-Repository").replace('\\','/')
1251 tmp
= os
.path
.join(self
.repbase
, "svntmp")
1252 if sys
.platform
== 'win32':
1253 # On Windows Paths do not start with a /
1254 self
.svnurl
= "file:///%s" % self
.svnrep
1256 self
.svnurl
= "file://%s" % self
.svnrep
1257 self
.svnurl_trunk
= self
.svnurl
+ "/sample/trunk"
1258 self
.svnurl_branch
= self
.svnurl
+ "/sample/branch"
1260 w
= self
.do(self
.repbase
, self
.svnadmin
+" create %s" % self
.svnrep
)
1261 yield w
; w
.getResult()
1265 "import -m sample_project_files %s" %
1267 yield w
; out
= w
.getResult()
1269 m
= re
.search(r
'Committed revision (\d+)\.', out
)
1270 assert m
.group(1) == "1" # first revision is always "1"
1271 self
.addTrunkRev(int(m
.group(1)))
1273 w
= self
.dovc(self
.repbase
,
1274 "checkout %s svntmp" % self
.svnurl_trunk
)
1275 yield w
; w
.getResult()
1277 w
= self
.dovc(tmp
, "cp -m make_branch %s %s" % (self
.svnurl_trunk
,
1278 self
.svnurl_branch
))
1279 yield w
; w
.getResult()
1280 w
= self
.dovc(tmp
, "switch %s" % self
.svnurl_branch
)
1281 yield w
; w
.getResult()
1282 self
.populate_branch(tmp
)
1283 w
= self
.dovc(tmp
, "commit -m commit_on_branch")
1284 yield w
; out
= w
.getResult()
1286 m
= re
.search(r
'Committed revision (\d+)\.', out
)
1287 self
.addBranchRev(int(m
.group(1)))
1288 createRepository
= deferredGenerator(createRepository
)
1290 def vc_revise(self
):
1291 tmp
= os
.path
.join(self
.repbase
, "svntmp")
1293 log
.msg("vc_revise" + self
.svnurl_trunk
)
1294 w
= self
.dovc(self
.repbase
,
1295 "checkout %s svntmp" % self
.svnurl_trunk
)
1296 yield w
; w
.getResult()
1298 version_c
= VERSION_C
% self
.version
1299 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1300 w
= self
.dovc(tmp
, "commit -m revised_to_%d" % self
.version
)
1301 yield w
; out
= w
.getResult()
1302 m
= re
.search(r
'Committed revision (\d+)\.', out
)
1303 self
.addTrunkRev(int(m
.group(1)))
1305 vc_revise
= deferredGenerator(vc_revise
)
1307 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1308 assert os
.path
.abspath(workdir
) == workdir
1309 if os
.path
.exists(workdir
):
1310 rmdirRecursive(workdir
)
1312 svnurl
= self
.svnurl_trunk
1314 # N.B.: this is *not* os.path.join: SVN URLs use slashes
1315 # regardless of the host operating system's filepath separator
1316 svnurl
= self
.svnurl
+ "/" + branch
1317 w
= self
.dovc(self
.repbase
,
1318 "checkout %s %s" % (svnurl
, workdir
))
1319 yield w
; w
.getResult()
1320 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1321 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1323 def vc_try_finish(self
, workdir
):
1324 rmdirRecursive(workdir
)
1327 class SVN(VCBase
, unittest
.TestCase
):
1331 vctype
= "source.SVN"
1333 has_got_revision
= True
1334 has_got_revision_branches_are_merged
= True
1336 def testCheckout(self
):
1337 # we verify this one with the svnurl style of vcargs. We test the
1338 # baseURL/defaultBranch style in testPatch and testCheckoutBranch.
1339 self
.helper
.vcargs
= { 'svnurl': self
.helper
.svnurl_trunk
}
1340 d
= self
.do_vctest()
1343 def testPatch(self
):
1344 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1345 'defaultBranch': "sample/trunk",
1350 def testCheckoutBranch(self
):
1351 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1352 'defaultBranch': "sample/trunk",
1354 d
= self
.do_branch()
1358 # extract the base revision and patch from a modified tree, use it to
1359 # create the same contents on the buildslave
1360 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1361 'defaultBranch': "sample/trunk",
1363 d
= self
.do_getpatch()
1366 VCS
.registerVC(SVN
.vc_name
, SVNHelper())
1369 class P4Helper(BaseHelper
):
1370 branchname
= "branch"
1371 p4port
= 'localhost:1666'
1373 base_descr
= 'Change: new\nDescription: asdf\nFiles:\n'
1376 p4paths
= which('p4')
1377 p4dpaths
= which('p4d')
1379 return (False, "p4 is not installed")
1381 return (False, "p4d is not installed")
1382 self
.vcexe
= p4paths
[0]
1383 self
.p4dexe
= p4dpaths
[0]
1386 class _P4DProtocol(protocol
.ProcessProtocol
):
1388 self
.started
= defer
.Deferred()
1389 self
.ended
= defer
.Deferred()
1391 def outReceived(self
, data
):
1392 # When it says starting, it has bound to the socket.
1394 if data
.startswith('Perforce Server starting...'):
1395 self
.started
.callback(None)
1397 print "p4d said %r" % data
1399 raise Exception('p4d said %r' % data
)
1401 self
.started
.errback(failure
.Failure())
1404 def errReceived(self
, data
):
1405 print "p4d stderr: %s" % data
1407 def processEnded(self
, status_object
):
1408 if status_object
.check(error
.ProcessDone
):
1409 self
.ended
.callback(None)
1411 self
.ended
.errback(status_object
)
1413 def _start_p4d(self
):
1414 proto
= self
._P
4DProtocol
()
1415 reactor
.spawnProcess(proto
, self
.p4dexe
, ['p4d', '-p', self
.p4port
],
1416 env
=os
.environ
, path
=self
.p4rep
)
1417 return proto
.started
, proto
.ended
1419 def dop4(self
, basedir
, command
, failureIsOk
=False, stdin
=None):
1420 # p4 looks at $PWD instead of getcwd(), which causes confusion when
1421 # we spawn commands without an intervening shell (sh -c). We can
1422 # override this with a -d argument.
1423 command
= "-p %s -d %s %s" % (self
.p4port
, basedir
, command
)
1424 return self
.dovc(basedir
, command
, failureIsOk
, stdin
)
1426 def createRepository(self
):
1427 # this is only called once per VC system, so start p4d here.
1429 self
.createBasedir()
1430 tmp
= os
.path
.join(self
.repbase
, "p4tmp")
1431 self
.p4rep
= os
.path
.join(self
.repbase
, 'P4-Repository')
1432 os
.mkdir(self
.p4rep
)
1435 started
, self
.p4d_shutdown
= self
._start
_p
4d
()
1436 w
= waitForDeferred(started
)
1437 yield w
; w
.getResult()
1439 # Create client spec.
1441 clispec
= 'Client: creator\n'
1442 clispec
+= 'Root: %s\n' % tmp
1443 clispec
+= 'View:\n'
1444 clispec
+= '\t//depot/... //creator/...\n'
1445 w
= self
.dop4(tmp
, 'client -i', stdin
=clispec
)
1446 yield w
; w
.getResult()
1448 # Create first rev (trunk).
1449 self
.populate(os
.path
.join(tmp
, 'trunk'))
1450 files
= ['main.c', 'version.c', 'subdir/subdir.c']
1451 w
= self
.dop4(tmp
, "-c creator add "
1452 + " ".join(['trunk/%s' % f
for f
in files
]))
1453 yield w
; w
.getResult()
1454 descr
= self
.base_descr
1456 descr
+= '\t//depot/trunk/%s\n' % file
1457 w
= self
.dop4(tmp
, "-c creator submit -i", stdin
=descr
)
1458 yield w
; out
= w
.getResult()
1459 m
= re
.search(r
'Change (\d+) submitted.', out
)
1460 assert m
.group(1) == '1'
1461 self
.addTrunkRev(m
.group(1))
1463 # Create second rev (branch).
1464 w
= self
.dop4(tmp
, '-c creator integrate '
1465 + '//depot/trunk/... //depot/branch/...')
1466 yield w
; w
.getResult()
1467 w
= self
.dop4(tmp
, "-c creator edit branch/main.c")
1468 yield w
; w
.getResult()
1469 self
.populate_branch(os
.path
.join(tmp
, 'branch'))
1470 descr
= self
.base_descr
1472 descr
+= '\t//depot/branch/%s\n' % file
1473 w
= self
.dop4(tmp
, "-c creator submit -i", stdin
=descr
)
1474 yield w
; out
= w
.getResult()
1475 m
= re
.search(r
'Change (\d+) submitted.', out
)
1476 self
.addBranchRev(m
.group(1))
1477 createRepository
= deferredGenerator(createRepository
)
1479 def vc_revise(self
):
1480 tmp
= os
.path
.join(self
.repbase
, "p4tmp")
1482 version_c
= VERSION_C
% self
.version
1483 w
= self
.dop4(tmp
, '-c creator edit trunk/version.c')
1484 yield w
; w
.getResult()
1485 open(os
.path
.join(tmp
, "trunk/version.c"), "w").write(version_c
)
1486 descr
= self
.base_descr
+ '\t//depot/trunk/version.c\n'
1487 w
= self
.dop4(tmp
, "-c creator submit -i", stdin
=descr
)
1488 yield w
; out
= w
.getResult()
1489 m
= re
.search(r
'Change (\d+) submitted.', out
)
1490 self
.addTrunkRev(m
.group(1))
1491 vc_revise
= deferredGenerator(vc_revise
)
1493 def shutdown_p4d(self
):
1494 d
= self
.runCommand(self
.repbase
, '%s -p %s admin stop'
1495 % (self
.vcexe
, self
.p4port
))
1496 return d
.addCallback(lambda _
: self
.p4d_shutdown
)
1498 class P4(VCBase
, unittest
.TestCase
):
1500 vctype
= "source.P4"
1503 def tearDownClass(self
):
1505 return maybeWait(self
.helper
.shutdown_p4d())
1507 def testCheckout(self
):
1508 self
.helper
.vcargs
= { 'p4port': self
.helper
.p4port
,
1509 'p4base': '//depot/',
1510 'defaultBranch': 'trunk' }
1511 d
= self
.do_vctest(testRetry
=False)
1512 # TODO: like arch and darcs, sync does nothing when server is not
1516 def testCheckoutBranch(self
):
1517 self
.helper
.vcargs
= { 'p4port': self
.helper
.p4port
,
1518 'p4base': '//depot/',
1519 'defaultBranch': 'trunk' }
1520 d
= self
.do_branch()
1523 def testPatch(self
):
1524 self
.helper
.vcargs
= { 'p4port': self
.helper
.p4port
,
1525 'p4base': '//depot/',
1526 'defaultBranch': 'trunk' }
1530 VCS
.registerVC(P4
.vc_name
, P4Helper())
1533 class DarcsHelper(BaseHelper
):
1534 branchname
= "branch"
1535 try_branchname
= "branch"
1538 darcspaths
= which('darcs')
1540 return (False, "Darcs is not installed")
1541 self
.vcexe
= darcspaths
[0]
1544 def createRepository(self
):
1545 self
.createBasedir()
1546 self
.darcs_base
= os
.path
.join(self
.repbase
, "Darcs-Repository")
1547 self
.rep_trunk
= os
.path
.join(self
.darcs_base
, "trunk")
1548 self
.rep_branch
= os
.path
.join(self
.darcs_base
, "branch")
1549 tmp
= os
.path
.join(self
.repbase
, "darcstmp")
1551 os
.makedirs(self
.rep_trunk
)
1552 w
= self
.dovc(self
.rep_trunk
, ["initialize"])
1553 yield w
; w
.getResult()
1554 os
.makedirs(self
.rep_branch
)
1555 w
= self
.dovc(self
.rep_branch
, ["initialize"])
1556 yield w
; w
.getResult()
1559 w
= self
.dovc(tmp
, qw("initialize"))
1560 yield w
; w
.getResult()
1561 w
= self
.dovc(tmp
, qw("add -r ."))
1562 yield w
; w
.getResult()
1563 w
= self
.dovc(tmp
, qw("record -a -m initial_import --skip-long-comment -A test@buildbot.sf.net"))
1564 yield w
; w
.getResult()
1565 w
= self
.dovc(tmp
, ["push", "-a", self
.rep_trunk
])
1566 yield w
; w
.getResult()
1567 w
= self
.dovc(tmp
, qw("changes --context"))
1568 yield w
; out
= w
.getResult()
1569 self
.addTrunkRev(out
)
1571 self
.populate_branch(tmp
)
1572 w
= self
.dovc(tmp
, qw("record -a --ignore-times -m commit_on_branch --skip-long-comment -A test@buildbot.sf.net"))
1573 yield w
; w
.getResult()
1574 w
= self
.dovc(tmp
, ["push", "-a", self
.rep_branch
])
1575 yield w
; w
.getResult()
1576 w
= self
.dovc(tmp
, qw("changes --context"))
1577 yield w
; out
= w
.getResult()
1578 self
.addBranchRev(out
)
1580 createRepository
= deferredGenerator(createRepository
)
1582 def vc_revise(self
):
1583 tmp
= os
.path
.join(self
.repbase
, "darcstmp")
1585 w
= self
.dovc(tmp
, qw("initialize"))
1586 yield w
; w
.getResult()
1587 w
= self
.dovc(tmp
, ["pull", "-a", self
.rep_trunk
])
1588 yield w
; w
.getResult()
1591 version_c
= VERSION_C
% self
.version
1592 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1593 w
= self
.dovc(tmp
, qw("record -a --ignore-times -m revised_to_%d --skip-long-comment -A test@buildbot.sf.net" % self
.version
))
1594 yield w
; w
.getResult()
1595 w
= self
.dovc(tmp
, ["push", "-a", self
.rep_trunk
])
1596 yield w
; w
.getResult()
1597 w
= self
.dovc(tmp
, qw("changes --context"))
1598 yield w
; out
= w
.getResult()
1599 self
.addTrunkRev(out
)
1601 vc_revise
= deferredGenerator(vc_revise
)
1603 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1604 assert os
.path
.abspath(workdir
) == workdir
1605 if os
.path
.exists(workdir
):
1606 rmdirRecursive(workdir
)
1607 os
.makedirs(workdir
)
1608 w
= self
.dovc(workdir
, qw("initialize"))
1609 yield w
; w
.getResult()
1611 rep
= self
.rep_trunk
1613 rep
= os
.path
.join(self
.darcs_base
, branch
)
1614 w
= self
.dovc(workdir
, ["pull", "-a", rep
])
1615 yield w
; w
.getResult()
1616 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1617 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1619 def vc_try_finish(self
, workdir
):
1620 rmdirRecursive(workdir
)
1623 class Darcs(VCBase
, unittest
.TestCase
):
1626 # Darcs has a metadir="_darcs", but it does not have an 'export'
1629 vctype
= "source.Darcs"
1630 vctype_try
= "darcs"
1631 has_got_revision
= True
1633 def testCheckout(self
):
1634 self
.helper
.vcargs
= { 'repourl': self
.helper
.rep_trunk
}
1635 d
= self
.do_vctest(testRetry
=False)
1637 # TODO: testRetry has the same problem with Darcs as it does for
1641 def testPatch(self
):
1642 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1643 'defaultBranch': "trunk" }
1647 def testCheckoutBranch(self
):
1648 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1649 'defaultBranch': "trunk" }
1650 d
= self
.do_branch()
1653 def testCheckoutHTTP(self
):
1655 repourl
= "http://localhost:%d/Darcs-Repository/trunk" % self
.httpPort
1656 self
.helper
.vcargs
= { 'repourl': repourl
}
1657 d
= self
.do_vctest(testRetry
=False)
1661 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1662 'defaultBranch': "trunk" }
1663 d
= self
.do_getpatch()
1666 VCS
.registerVC(Darcs
.vc_name
, DarcsHelper())
1670 def registerRepository(self
, coordinates
):
1672 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1673 yield w
; out
= w
.getResult()
1675 w
= self
.dovc(self
.repbase
, "register-archive -d %s" % a
)
1676 yield w
; w
.getResult()
1677 w
= self
.dovc(self
.repbase
, "register-archive %s" % coordinates
)
1678 yield w
; w
.getResult()
1679 registerRepository
= deferredGenerator(registerRepository
)
1681 def unregisterRepository(self
):
1683 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1684 yield w
; out
= w
.getResult()
1686 w
= self
.dovc(self
.repbase
, "register-archive -d %s" % a
)
1687 yield w
; out
= w
.getResult()
1688 unregisterRepository
= deferredGenerator(unregisterRepository
)
1690 class TlaHelper(BaseHelper
, ArchCommon
):
1691 defaultbranch
= "testvc--mainline--1"
1692 branchname
= "testvc--branch--1"
1693 try_branchname
= None # TlaExtractor can figure it out by itself
1697 tlapaths
= which('tla')
1699 return (False, "Arch (tla) is not installed")
1700 self
.vcexe
= tlapaths
[0]
1703 def do_get(self
, basedir
, archive
, branch
, newdir
):
1704 # the 'get' syntax is different between tla and baz. baz, while
1705 # claiming to honor an --archive argument, in fact ignores it. The
1706 # correct invocation is 'baz get archive/revision newdir'.
1707 if self
.archcmd
== "tla":
1708 w
= self
.dovc(basedir
,
1709 "get -A %s %s %s" % (archive
, branch
, newdir
))
1711 w
= self
.dovc(basedir
,
1712 "get %s/%s %s" % (archive
, branch
, newdir
))
1715 def createRepository(self
):
1716 self
.createBasedir()
1717 # first check to see if bazaar is around, since we'll need to know
1719 d
= VCS
.capable(Bazaar
.vc_name
)
1720 d
.addCallback(self
._createRepository
_1)
1723 def _createRepository_1(self
, res
):
1726 # pick a hopefully unique string for the archive name, in the form
1727 # test-%d@buildbot.sf.net--testvc, since otherwise multiple copies of
1728 # the unit tests run in the same user account will collide (since the
1729 # archive names are kept in the per-user ~/.arch-params/ directory).
1731 self
.archname
= "test-%s-%d@buildbot.sf.net--testvc" % (self
.archcmd
,
1733 trunk
= self
.defaultbranch
1734 branch
= self
.branchname
1736 repword
= self
.archcmd
.capitalize()
1737 self
.archrep
= os
.path
.join(self
.repbase
, "%s-Repository" % repword
)
1738 tmp
= os
.path
.join(self
.repbase
, "archtmp")
1743 w
= self
.dovc(tmp
, "my-id", failureIsOk
=True)
1744 yield w
; res
= w
.getResult()
1746 # tla will fail a lot of operations if you have not set an ID
1747 w
= self
.do(tmp
, [self
.vcexe
, "my-id",
1748 "Buildbot Test Suite <test@buildbot.sf.net>"])
1749 yield w
; w
.getResult()
1752 # bazaar keeps a cache of revisions, but this test creates a new
1753 # archive each time it is run, so the cache causes errors.
1754 # Disable the cache to avoid these problems. This will be
1755 # slightly annoying for people who run the buildbot tests under
1756 # the same UID as one which uses baz on a regular basis, but
1757 # bazaar doesn't give us a way to disable the cache just for this
1759 cmd
= "%s cache-config --disable" % VCS
.getHelper('bazaar').vcexe
1760 w
= self
.do(tmp
, cmd
)
1761 yield w
; w
.getResult()
1763 w
= waitForDeferred(self
.unregisterRepository())
1764 yield w
; w
.getResult()
1766 # these commands can be run in any directory
1767 w
= self
.dovc(tmp
, "make-archive -l %s %s" % (a
, self
.archrep
))
1768 yield w
; w
.getResult()
1769 if self
.archcmd
== "tla":
1770 w
= self
.dovc(tmp
, "archive-setup -A %s %s" % (a
, trunk
))
1771 yield w
; w
.getResult()
1772 w
= self
.dovc(tmp
, "archive-setup -A %s %s" % (a
, branch
))
1773 yield w
; w
.getResult()
1775 # baz does not require an 'archive-setup' step
1778 # these commands must be run in the directory that is to be imported
1779 w
= self
.dovc(tmp
, "init-tree --nested %s/%s" % (a
, trunk
))
1780 yield w
; w
.getResult()
1781 files
= " ".join(["main.c", "version.c", "subdir",
1782 os
.path
.join("subdir", "subdir.c")])
1783 w
= self
.dovc(tmp
, "add-id %s" % files
)
1784 yield w
; w
.getResult()
1786 w
= self
.dovc(tmp
, "import %s/%s" % (a
, trunk
))
1787 yield w
; out
= w
.getResult()
1788 self
.addTrunkRev("base-0")
1791 if self
.archcmd
== "tla":
1792 branchstart
= "%s--base-0" % trunk
1793 w
= self
.dovc(tmp
, "tag -A %s %s %s" % (a
, branchstart
, branch
))
1794 yield w
; w
.getResult()
1796 w
= self
.dovc(tmp
, "branch %s" % branch
)
1797 yield w
; w
.getResult()
1801 # check out the branch
1802 w
= self
.do_get(self
.repbase
, a
, branch
, "archtmp")
1803 yield w
; w
.getResult()
1805 self
.populate_branch(tmp
)
1806 logfile
= "++log.%s--%s" % (branch
, a
)
1807 logmsg
= "Summary: commit on branch\nKeywords:\n\n"
1808 open(os
.path
.join(tmp
, logfile
), "w").write(logmsg
)
1809 w
= self
.dovc(tmp
, "commit")
1810 yield w
; out
= w
.getResult()
1811 m
= re
.search(r
'committed %s/%s--([\S]+)' % (a
, branch
),
1813 assert (m
.group(1) == "base-0" or m
.group(1).startswith("patch-"))
1814 self
.addBranchRev(m
.group(1))
1816 w
= waitForDeferred(self
.unregisterRepository())
1817 yield w
; w
.getResult()
1820 # we unregister the repository each time, because we might have
1821 # changed the coordinates (since we switch from a file: URL to an
1822 # http: URL for various tests). The buildslave code doesn't forcibly
1823 # unregister the archive, so we have to do it here.
1824 w
= waitForDeferred(self
.unregisterRepository())
1825 yield w
; w
.getResult()
1827 _createRepository_1
= deferredGenerator(_createRepository_1
)
1829 def vc_revise(self
):
1830 # the fix needs to be done in a workspace that is linked to a
1831 # read-write version of the archive (i.e., using file-based
1832 # coordinates instead of HTTP ones), so we re-register the repository
1833 # before we begin. We unregister it when we're done to make sure the
1834 # build will re-register the correct one for whichever test is
1835 # currently being run.
1837 # except, that source.Bazaar really doesn't like it when the archive
1838 # gets unregistered behind its back. The slave tries to do a 'baz
1839 # replay' in a tree with an archive that is no longer recognized, and
1840 # baz aborts with a botched invariant exception. This causes
1841 # mode=update to fall back to clobber+get, which flunks one of the
1842 # tests (the 'newfile' check in _do_vctest_update_3 fails)
1844 # to avoid this, we take heroic steps here to leave the archive
1845 # registration in the same state as we found it.
1847 tmp
= os
.path
.join(self
.repbase
, "archtmp")
1850 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1851 yield w
; out
= w
.getResult()
1853 lines
= out
.split("\n")
1854 coordinates
= lines
[1].strip()
1856 # now register the read-write location
1857 w
= waitForDeferred(self
.registerRepository(self
.archrep
))
1858 yield w
; w
.getResult()
1860 trunk
= self
.defaultbranch
1862 w
= self
.do_get(self
.repbase
, a
, trunk
, "archtmp")
1863 yield w
; w
.getResult()
1865 # tla appears to use timestamps to determine which files have
1866 # changed, so wait long enough for the new file to have a different
1870 version_c
= VERSION_C
% self
.version
1871 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1873 logfile
= "++log.%s--%s" % (trunk
, a
)
1874 logmsg
= "Summary: revised_to_%d\nKeywords:\n\n" % self
.version
1875 open(os
.path
.join(tmp
, logfile
), "w").write(logmsg
)
1876 w
= self
.dovc(tmp
, "commit")
1877 yield w
; out
= w
.getResult()
1878 m
= re
.search(r
'committed %s/%s--([\S]+)' % (a
, trunk
),
1880 assert (m
.group(1) == "base-0" or m
.group(1).startswith("patch-"))
1881 self
.addTrunkRev(m
.group(1))
1883 # now re-register the original coordinates
1884 w
= waitForDeferred(self
.registerRepository(coordinates
))
1885 yield w
; w
.getResult()
1887 vc_revise
= deferredGenerator(vc_revise
)
1889 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1890 assert os
.path
.abspath(workdir
) == workdir
1891 if os
.path
.exists(workdir
):
1892 rmdirRecursive(workdir
)
1896 # register the read-write location, if it wasn't already registered
1897 w
= waitForDeferred(self
.registerRepository(self
.archrep
))
1898 yield w
; w
.getResult()
1900 w
= self
.do_get(self
.repbase
, a
, "testvc--mainline--1", workdir
)
1901 yield w
; w
.getResult()
1905 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1906 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1908 def vc_try_finish(self
, workdir
):
1909 rmdirRecursive(workdir
)
1911 class Arch(VCBase
, unittest
.TestCase
):
1915 # Arch has a metadir="{arch}", but it does not have an 'export' mode.
1916 vctype
= "source.Arch"
1918 has_got_revision
= True
1920 def testCheckout(self
):
1921 # these are the coordinates of the read-write archive used by all the
1922 # non-HTTP tests. testCheckoutHTTP overrides these.
1923 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1924 'version': self
.helper
.defaultbranch
}
1925 d
= self
.do_vctest(testRetry
=False)
1926 # the current testRetry=True logic doesn't have the desired effect:
1927 # "update" is a no-op because arch knows that the repository hasn't
1928 # changed. Other VC systems will re-checkout missing files on
1929 # update, arch just leaves the tree untouched. TODO: come up with
1930 # some better test logic, probably involving a copy of the
1931 # repository that has a few changes checked in.
1935 def testCheckoutHTTP(self
):
1937 url
= "http://localhost:%d/Tla-Repository" % self
.httpPort
1938 self
.helper
.vcargs
= { 'url': url
,
1939 'version': "testvc--mainline--1" }
1940 d
= self
.do_vctest(testRetry
=False)
1943 def testPatch(self
):
1944 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1945 'version': self
.helper
.defaultbranch
}
1949 def testCheckoutBranch(self
):
1950 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1951 'version': self
.helper
.defaultbranch
}
1952 d
= self
.do_branch()
1956 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1957 'version': self
.helper
.defaultbranch
}
1958 d
= self
.do_getpatch()
1961 VCS
.registerVC(Arch
.vc_name
, TlaHelper())
1964 class BazaarHelper(TlaHelper
):
1968 bazpaths
= which('baz')
1970 return (False, "Arch (baz) is not installed")
1971 self
.vcexe
= bazpaths
[0]
1974 def setUp2(self
, res
):
1975 # we unregister the repository each time, because we might have
1976 # changed the coordinates (since we switch from a file: URL to an
1977 # http: URL for various tests). The buildslave code doesn't forcibly
1978 # unregister the archive, so we have to do it here.
1979 d
= self
.unregisterRepository()
1986 vctype
= "source.Bazaar"
1988 has_got_revision
= True
1992 def testCheckout(self
):
1993 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1994 # Baz adds the required 'archive' argument
1995 'archive': self
.helper
.archname
,
1996 'version': self
.helper
.defaultbranch
,
1998 d
= self
.do_vctest(testRetry
=False)
1999 # the current testRetry=True logic doesn't have the desired effect:
2000 # "update" is a no-op because arch knows that the repository hasn't
2001 # changed. Other VC systems will re-checkout missing files on
2002 # update, arch just leaves the tree untouched. TODO: come up with
2003 # some better test logic, probably involving a copy of the
2004 # repository that has a few changes checked in.
2008 def testCheckoutHTTP(self
):
2010 url
= "http://localhost:%d/Baz-Repository" % self
.httpPort
2011 self
.helper
.vcargs
= { 'url': url
,
2012 'archive': self
.helper
.archname
,
2013 'version': self
.helper
.defaultbranch
,
2015 d
= self
.do_vctest(testRetry
=False)
2018 def testPatch(self
):
2019 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
2020 # Baz adds the required 'archive' argument
2021 'archive': self
.helper
.archname
,
2022 'version': self
.helper
.defaultbranch
,
2027 def testCheckoutBranch(self
):
2028 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
2029 # Baz adds the required 'archive' argument
2030 'archive': self
.helper
.archname
,
2031 'version': self
.helper
.defaultbranch
,
2033 d
= self
.do_branch()
2037 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
2038 # Baz adds the required 'archive' argument
2039 'archive': self
.helper
.archname
,
2040 'version': self
.helper
.defaultbranch
,
2042 d
= self
.do_getpatch()
2045 def fixRepository(self
):
2046 self
.fixtimer
= None
2047 self
.site
.resource
= self
.root
2049 def testRetry(self
):
2050 # we want to verify that source.Source(retry=) works, and the easiest
2051 # way to make VC updates break (temporarily) is to break the HTTP
2052 # server that's providing the repository. Anything else pretty much
2053 # requires mutating the (read-only) BUILDBOT_TEST_VC repository, or
2054 # modifying the buildslave's checkout command while it's running.
2056 # this test takes a while to run, so don't bother doing it with
2057 # anything other than baz
2061 # break the repository server
2062 from twisted
.web
import static
2063 self
.site
.resource
= static
.Data("Sorry, repository is offline",
2065 # and arrange to fix it again in 5 seconds, while the test is
2067 self
.fixtimer
= reactor
.callLater(5, self
.fixRepository
)
2069 url
= "http://localhost:%d/Baz-Repository" % self
.httpPort
2070 self
.helper
.vcargs
= { 'url': url
,
2071 'archive': self
.helper
.archname
,
2072 'version': self
.helper
.defaultbranch
,
2075 d
= self
.do_vctest_once(True)
2076 d
.addCallback(self
._testRetry
_1)
2078 def _testRetry_1(self
, bs
):
2079 # make sure there was mention of the retry attempt in the logs
2081 self
.failUnlessIn("unable to access URL", l
.getText(),
2082 "funny, VC operation didn't fail at least once")
2083 self
.failUnlessIn("update failed, trying 4 more times after 5 seconds",
2084 l
.getTextWithHeaders(),
2085 "funny, VC operation wasn't reattempted")
2087 def testRetryFails(self
):
2088 # make sure that the build eventually gives up on a repository which
2089 # is completely unavailable
2093 # break the repository server, and leave it broken
2094 from twisted
.web
import static
2095 self
.site
.resource
= static
.Data("Sorry, repository is offline",
2098 url
= "http://localhost:%d/Baz-Repository" % self
.httpPort
2099 self
.helper
.vcargs
= {'url': url
,
2100 'archive': self
.helper
.archname
,
2101 'version': self
.helper
.defaultbranch
,
2104 d
= self
.do_vctest_once(False)
2105 d
.addCallback(self
._testRetryFails
_1)
2107 def _testRetryFails_1(self
, bs
):
2108 self
.failUnlessEqual(bs
.getResults(), FAILURE
)
2110 def tearDown2(self
):
2112 self
.fixtimer
.cancel()
2113 # tell tla to get rid of the leftover archive this test leaves in the
2114 # user's 'tla archives' listing. The name of this archive is provided
2115 # by the repository tarball, so the following command must use the
2116 # same name. We could use archive= to set it explicitly, but if you
2117 # change it from the default, then 'tla update' won't work.
2118 d
= self
.helper
.unregisterRepository()
2121 VCS
.registerVC(Bazaar
.vc_name
, BazaarHelper())
2123 class MercurialHelper(BaseHelper
):
2124 branchname
= "branch"
2125 try_branchname
= "branch"
2128 hgpaths
= which("hg")
2130 return (False, "Mercurial is not installed")
2131 self
.vcexe
= hgpaths
[0]
2134 def extract_id(self
, output
):
2135 m
= re
.search(r
'^(\w+)', output
)
2138 def createRepository(self
):
2139 self
.createBasedir()
2140 self
.hg_base
= os
.path
.join(self
.repbase
, "Mercurial-Repository")
2141 self
.rep_trunk
= os
.path
.join(self
.hg_base
, "trunk")
2142 self
.rep_branch
= os
.path
.join(self
.hg_base
, "branch")
2143 tmp
= os
.path
.join(self
.hg_base
, "hgtmp")
2145 os
.makedirs(self
.rep_trunk
)
2146 w
= self
.dovc(self
.rep_trunk
, "init")
2147 yield w
; w
.getResult()
2148 os
.makedirs(self
.rep_branch
)
2149 w
= self
.dovc(self
.rep_branch
, "init")
2150 yield w
; w
.getResult()
2153 w
= self
.dovc(tmp
, "init")
2154 yield w
; w
.getResult()
2155 w
= self
.dovc(tmp
, "add")
2156 yield w
; w
.getResult()
2157 w
= self
.dovc(tmp
, "commit -m initial_import")
2158 yield w
; w
.getResult()
2159 w
= self
.dovc(tmp
, "push %s" % self
.rep_trunk
)
2160 # note that hg-push does not actually update the working directory
2161 yield w
; w
.getResult()
2162 w
= self
.dovc(tmp
, "identify")
2163 yield w
; out
= w
.getResult()
2164 self
.addTrunkRev(self
.extract_id(out
))
2166 self
.populate_branch(tmp
)
2167 w
= self
.dovc(tmp
, "commit -m commit_on_branch")
2168 yield w
; w
.getResult()
2169 w
= self
.dovc(tmp
, "push %s" % self
.rep_branch
)
2170 yield w
; w
.getResult()
2171 w
= self
.dovc(tmp
, "identify")
2172 yield w
; out
= w
.getResult()
2173 self
.addBranchRev(self
.extract_id(out
))
2175 createRepository
= deferredGenerator(createRepository
)
2177 def vc_revise(self
):
2178 tmp
= os
.path
.join(self
.hg_base
, "hgtmp2")
2179 w
= self
.dovc(self
.hg_base
, "clone %s %s" % (self
.rep_trunk
, tmp
))
2180 yield w
; w
.getResult()
2183 version_c
= VERSION_C
% self
.version
2184 version_c_filename
= os
.path
.join(tmp
, "version.c")
2185 open(version_c_filename
, "w").write(version_c
)
2186 # hg uses timestamps to distinguish files which have changed, so we
2187 # force the mtime forward a little bit
2188 future
= time
.time() + 2*self
.version
2189 os
.utime(version_c_filename
, (future
, future
))
2190 w
= self
.dovc(tmp
, "commit -m revised_to_%d" % self
.version
)
2191 yield w
; w
.getResult()
2192 w
= self
.dovc(tmp
, "push %s" % self
.rep_trunk
)
2193 yield w
; w
.getResult()
2194 w
= self
.dovc(tmp
, "identify")
2195 yield w
; out
= w
.getResult()
2196 self
.addTrunkRev(self
.extract_id(out
))
2198 vc_revise
= deferredGenerator(vc_revise
)
2200 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
2201 assert os
.path
.abspath(workdir
) == workdir
2202 if os
.path
.exists(workdir
):
2203 rmdirRecursive(workdir
)
2205 src
= self
.rep_branch
2207 src
= self
.rep_trunk
2208 w
= self
.dovc(self
.hg_base
, "clone %s %s" % (src
, workdir
))
2209 yield w
; w
.getResult()
2210 try_c_filename
= os
.path
.join(workdir
, "subdir", "subdir.c")
2211 open(try_c_filename
, "w").write(TRY_C
)
2212 future
= time
.time() + 2*self
.version
2213 os
.utime(try_c_filename
, (future
, future
))
2214 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
2216 def vc_try_finish(self
, workdir
):
2217 rmdirRecursive(workdir
)
2220 class Mercurial(VCBase
, unittest
.TestCase
):
2223 # Mercurial has a metadir=".hg", but it does not have an 'export' mode.
2225 vctype
= "source.Mercurial"
2227 has_got_revision
= True
2229 def testCheckout(self
):
2230 self
.helper
.vcargs
= { 'repourl': self
.helper
.rep_trunk
}
2231 d
= self
.do_vctest(testRetry
=False)
2233 # TODO: testRetry has the same problem with Mercurial as it does for
2237 def testPatch(self
):
2238 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2239 'defaultBranch': "trunk" }
2243 def testCheckoutBranch(self
):
2244 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2245 'defaultBranch': "trunk" }
2246 d
= self
.do_branch()
2249 def testCheckoutHTTP(self
):
2251 repourl
= "http://localhost:%d/Mercurial-Repository/trunk/.hg" % self
.httpPort
2252 self
.helper
.vcargs
= { 'repourl': repourl
}
2253 d
= self
.do_vctest(testRetry
=False)
2255 # TODO: The easiest way to publish hg over HTTP is by running 'hg serve'
2256 # as a child process while the test is running. (you can also use a CGI
2257 # script, which sounds difficult, or you can publish the files directly,
2258 # which isn't well documented).
2259 testCheckoutHTTP
.skip
= "not yet implemented, use 'hg serve'"
2262 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2263 'defaultBranch': "trunk" }
2264 d
= self
.do_getpatch()
2267 VCS
.registerVC(Mercurial
.vc_name
, MercurialHelper())
2270 class Sources(unittest
.TestCase
):
2271 # TODO: this needs serious rethink
2272 def makeChange(self
, when
=None, revision
=None):
2274 when
= mktime_tz(parsedate_tz(when
))
2275 return changes
.Change("fred", [], "", when
=when
, revision
=revision
)
2278 r
= base
.BuildRequest("forced build", SourceStamp())
2280 s
= source
.CVS(cvsroot
=None, cvsmodule
=None, workdir
=None, build
=b
)
2281 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), None)
2285 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2286 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2287 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2288 r
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2289 submitted
= "Wed, 08 Sep 2004 09:04:00 -0700"
2290 r
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2292 s
= source
.CVS(cvsroot
=None, cvsmodule
=None, workdir
=None, build
=b
)
2293 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
2294 "Wed, 08 Sep 2004 16:03:00 -0000")
2298 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2299 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2300 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2301 r
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2302 submitted
= "Wed, 08 Sep 2004 09:04:00 -0700"
2303 r
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2305 s
= source
.CVS(cvsroot
=None, cvsmodule
=None, workdir
=None, build
=b
,
2307 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
2308 "Wed, 08 Sep 2004 16:02:10 -0000")
2312 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2313 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2314 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2315 r1
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2316 submitted
= "Wed, 08 Sep 2004 09:04:00 -0700"
2317 r1
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2320 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:05:00 -0700"))
2321 r2
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2322 submitted
= "Wed, 08 Sep 2004 09:07:00 -0700"
2323 r2
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2325 b
= base
.Build([r1
, r2
])
2326 s
= source
.CVS(cvsroot
=None, cvsmodule
=None, workdir
=None, build
=b
)
2327 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
2328 "Wed, 08 Sep 2004 16:06:00 -0000")
2331 r
= base
.BuildRequest("forced", SourceStamp())
2333 s
= source
.SVN(svnurl
="dummy", workdir
=None, build
=b
)
2334 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), None)
2338 c
.append(self
.makeChange(revision
=4))
2339 c
.append(self
.makeChange(revision
=10))
2340 c
.append(self
.makeChange(revision
=67))
2341 r
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2343 s
= source
.SVN(svnurl
="dummy", workdir
=None, build
=b
)
2344 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), 67)
2346 class Patch(VCBase
, unittest
.TestCase
):
2353 def testPatch(self
):
2354 # invoke 'patch' all by itself, to see if it works the way we think
2355 # it should. This is intended to ferret out some windows test
2357 helper
= BaseHelper()
2358 self
.workdir
= os
.path
.join("test_vc", "testPatch")
2359 helper
.populate(self
.workdir
)
2360 patch
= which("patch")[0]
2362 command
= [patch
, "-p0"]
2365 def sendUpdate(self
, status
):
2367 c
= commands
.ShellCommand(FakeBuilder(), command
, self
.workdir
,
2368 sendRC
=False, initialStdin
=p0_diff
)
2370 d
.addCallback(self
._testPatch
_1)
2373 def _testPatch_1(self
, res
):
2374 # make sure the file actually got patched
2375 subdir_c
= os
.path
.join(self
.workdir
, "subdir", "subdir.c")
2376 data
= open(subdir_c
, "r").read()
2377 self
.failUnlessIn("Hello patched subdir.\\n", data
)