1 # -*- test-case-name: buildbot.test.test_vc -*-
3 from __future__
import generators
5 import sys
, os
, signal
, shutil
, time
, re
6 from email
.Utils
import mktime_tz
, parsedate_tz
8 from twisted
.trial
import unittest
9 from twisted
.internet
import defer
, reactor
, utils
11 #defer.Deferred.debug = True
13 from twisted
.python
import log
14 #log.startLogging(sys.stderr)
16 from buildbot
import master
, interfaces
17 from buildbot
.slave
import bot
, commands
18 from buildbot
.slave
.commands
import rmdirRecursive
19 from buildbot
.status
.builder
import SUCCESS
, FAILURE
20 from buildbot
.process
import step
, base
21 from buildbot
.changes
import changes
22 from buildbot
.sourcestamp
import SourceStamp
23 from buildbot
.twcompat
import maybeWait
, which
24 from buildbot
.scripts
import tryclient
26 #step.LoggedRemoteCommand.debug = True
28 # buildbot.twcompat will patch these into t.i.defer if necessary
29 from twisted
.internet
.defer
import waitForDeferred
, deferredGenerator
31 # Most of these tests (all but SourceStamp) depend upon having a set of
32 # repositories from which we can perform checkouts. These repositories are
33 # created by the setUp method at the start of each test class. In earlier
34 # versions these repositories were created offline and distributed with a
35 # separate tarball named 'buildbot-test-vc-1.tar.gz'. This is no longer
38 # CVS requires a local file repository. Providing remote access is beyond
39 # the feasible abilities of this test program (needs pserver or ssh).
41 # SVN requires a local file repository. To provide remote access over HTTP
42 # requires an apache server with DAV support and mod_svn, way beyond what we
45 # Arch and Darcs both allow remote (read-only) operation with any web
46 # server. We test both local file access and HTTP access (by spawning a
47 # small web server to provide access to the repository files while the test
52 from buildbot.process import factory, step
55 f1 = factory.BuildFactory([
59 c['bots'] = [['bot1', 'sekrit']]
62 c['builders'] = [{'name': 'vc', 'slavename': 'bot1',
63 'builddir': 'vc-dir', 'factory': f1}]
69 Index: subdir/subdir.c
70 ===================================================================
71 RCS file: /home/warner/stuff/Projects/BuildBot/code-arch/_trial_temp/test_vc/repositories/CVS-Repository/sample/subdir/subdir.c,v
72 retrieving revision 1.1.1.1
73 diff -u -r1.1.1.1 subdir.c
74 --- subdir/subdir.c 14 Aug 2005 01:32:49 -0000 1.1.1.1
75 +++ subdir/subdir.c 14 Aug 2005 01:36:15 -0000
78 main(int argc, const char *argv[])
80 - printf("Hello subdir.\n");
81 + printf("Hello patched subdir.\n");
86 # this patch does not include the filename headers, so it is
91 main(int argc, const char *argv[])
93 - printf("Hello subdir.\\n");
94 + printf("Hello try.\\n");
104 main(int argc, const char *argv[])
106 printf("Hello world.\\n");
116 main(int argc, const char *argv[])
118 printf("Hello branch.\\n");
128 main(int argc, const char *argv[])
130 printf("Hello world, version=%d\\n");
136 // this is subdir/subdir.c
140 main(int argc, const char *argv[])
142 printf("Hello subdir.\\n");
148 // this is subdir/subdir.c
152 main(int argc, const char *argv[])
154 printf("Hello try.\\n");
160 # this is a helper class which keeps track of whether each VC system is
161 # available, and whether the repository for each has been created. There
162 # is one instance of this class, at module level, shared between all test
171 def registerVC(self
, name
, helper
):
172 self
._helpers
[name
] = helper
173 self
._repoReady
[name
] = False
175 def skipIfNotCapable(self
, name
):
176 """Either return None, or raise SkipTest"""
177 d
= self
.capable(name
)
180 raise unittest
.SkipTest(res
[1])
181 d
.addCallback(_maybeSkip
)
184 def capable(self
, name
):
185 """Return a Deferred that fires with (True,None) if this host offers
186 the given VC tool, or (False,excuse) if it does not (and therefore
187 the tests should be skipped)."""
189 if self
._isCapable
.has_key(name
):
190 if self
._isCapable
[name
]:
191 return defer
.succeed((True,None))
193 return defer
.succeed((False, self
._excuses
[name
]))
194 d
= defer
.maybeDeferred(self
._helpers
[name
].capable
)
197 self
._isCapable
[name
] = True
199 self
._excuses
[name
] = res
[1]
201 d
.addCallback(_capable
)
204 def getHelper(self
, name
):
205 return self
._helpers
[name
]
207 def createRepository(self
, name
):
208 """Return a Deferred that fires when the repository is set up."""
209 if self
._repoReady
[name
]:
210 return defer
.succeed(True)
211 d
= self
._helpers
[name
].createRepository()
213 self
._repoReady
[name
] = True
214 d
.addCallback(_ready
)
220 sigchldHandler
= None
222 def setUpClass(self
):
223 # make sure SIGCHLD handler is installed, as it should be on
224 # reactor.run(). problem is reactor may not have been run when this
226 if hasattr(reactor
, "_handleSigchld") and hasattr(signal
, "SIGCHLD"):
227 self
.sigchldHandler
= signal
.signal(signal
.SIGCHLD
,
228 reactor
._handleSigchld
)
230 def tearDownClass(self
):
231 if self
.sigchldHandler
:
232 signal
.signal(signal
.SIGCHLD
, self
.sigchldHandler
)
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 if not os
.path
.isdir(self
.repbase
):
290 os
.makedirs(self
.repbase
)
292 def createRepository(self
):
293 # this will only be called once per process
294 raise NotImplementedError
296 def populate(self
, basedir
):
298 os
.makedirs(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 # all commands passed to do() should be strings or lists. If they are
318 # strings, none of the arguments may have spaces. This makes the
319 # commands less verbose at the expense of restricting what they can
321 if type(command
) not in (list, tuple):
322 command
= command
.split(" ")
323 #print "do %s" % command
324 env
= os
.environ
.copy()
326 d
= utils
.getProcessOutputAndValue(command
[0], command
[1:],
327 env
=env
, path
=basedir
)
328 def check((out
, err
, code
)):
330 #print "command: %s" % command
331 #print "out: %s" % out
332 #print "code: %s" % code
333 if code
!= 0 and not failureIsOk
:
334 log
.msg("command %s finished with exit code %d" %
336 log
.msg(" and stdout %s" % (out
,))
337 log
.msg(" and stderr %s" % (err
,))
338 raise RuntimeError("command %s finished with exit code %d"
340 + ": see logs for stdout")
345 def do(self
, basedir
, command
, failureIsOk
=False):
346 d
= self
.runCommand(basedir
, command
, failureIsOk
=failureIsOk
)
347 return waitForDeferred(d
)
349 def dovc(self
, basedir
, command
, failureIsOk
=False):
350 """Like do(), but the VC binary will be prepended to COMMAND."""
351 command
= self
.vcexe
+ " " + command
352 return self
.do(basedir
, command
, failureIsOk
)
354 class VCBase(SignalMixin
):
356 createdRepository
= False
362 has_got_revision
= False
363 has_got_revision_branches_are_merged
= False # for SVN
365 def failUnlessIn(self
, substring
, string
, msg
=None):
366 # trial provides a version of this that requires python-2.3 to test
369 msg
= ("did not see the expected substring '%s' in string '%s'" %
371 self
.failUnless(string
.find(substring
) != -1, msg
)
374 d
= VCS
.skipIfNotCapable(self
.vc_name
)
375 d
.addCallback(self
._setUp
1)
378 def _setUp1(self
, res
):
379 self
.helper
= VCS
.getHelper(self
.vc_name
)
381 if os
.path
.exists("basedir"):
382 rmdirRecursive("basedir")
384 self
.master
= master
.BuildMaster("basedir")
385 self
.slavebase
= os
.path
.abspath("slavebase")
386 if os
.path
.exists(self
.slavebase
):
387 rmdirRecursive(self
.slavebase
)
388 os
.mkdir("slavebase")
390 d
= VCS
.createRepository(self
.vc_name
)
393 def connectSlave(self
):
394 port
= self
.master
.slavePort
._port
.getHost().port
395 slave
= bot
.BuildSlave("localhost", port
, "bot1", "sekrit",
396 self
.slavebase
, keepalive
=0, usePTY
=1)
399 d
= self
.master
.botmaster
.waitUntilBuilderAttached("vc")
402 def loadConfig(self
, config
):
403 # reloading the config file causes a new 'listDirs' command to be
404 # sent to the slave. To synchronize on this properly, it is easiest
405 # to stop and restart the slave.
406 d
= defer
.succeed(None)
408 d
= self
.master
.botmaster
.waitUntilBuilderDetached("vc")
409 self
.slave
.stopService()
410 d
.addCallback(lambda res
: self
.master
.loadConfig(config
))
411 d
.addCallback(lambda res
: self
.connectSlave())
415 # launch an HTTP server to serve the repository files
416 from twisted
.web
import static
, server
417 from twisted
.internet
import reactor
418 self
.root
= static
.File(self
.helper
.repbase
)
419 self
.site
= server
.Site(self
.root
)
420 self
.httpServer
= reactor
.listenTCP(0, self
.site
)
421 self
.httpPort
= self
.httpServer
.getHost().port
423 def doBuild(self
, shouldSucceed
=True, ss
=None):
424 c
= interfaces
.IControl(self
.master
)
428 #print "doBuild(ss: b=%s rev=%s)" % (ss.branch, ss.revision)
429 req
= base
.BuildRequest("test_vc forced build", ss
)
430 d
= req
.waitUntilFinished()
431 c
.getBuilder("vc").requestBuild(req
)
432 d
.addCallback(self
._doBuild
_1, shouldSucceed
)
434 def _doBuild_1(self
, bs
, shouldSucceed
):
436 if r
!= SUCCESS
and shouldSucceed
:
439 if not bs
.isFinished():
440 print "Hey, build wasn't even finished!"
441 print "Build did not succeed:", r
, bs
.getText()
442 for s
in bs
.getSteps():
443 for l
in s
.getLogs():
444 print "--- START step %s / log %s ---" % (s
.getName(),
446 print l
.getTextWithHeaders()
449 self
.fail("build did not succeed")
452 def touch(self
, d
, f
):
453 open(os
.path
.join(d
,f
),"w").close()
454 def shouldExist(self
, *args
):
455 target
= os
.path
.join(*args
)
456 self
.failUnless(os
.path
.exists(target
),
457 "expected to find %s but didn't" % target
)
458 def shouldNotExist(self
, *args
):
459 target
= os
.path
.join(*args
)
460 self
.failIf(os
.path
.exists(target
),
461 "expected to NOT find %s, but did" % target
)
462 def shouldContain(self
, d
, f
, contents
):
463 c
= open(os
.path
.join(d
, f
), "r").read()
464 self
.failUnlessIn(contents
, c
)
466 def checkGotRevision(self
, bs
, expected
):
467 if self
.has_got_revision
:
468 self
.failUnlessEqual(bs
.getProperty("got_revision"), expected
)
470 def checkGotRevisionIsLatest(self
, bs
):
471 expected
= self
.helper
.trunk
[-1]
472 if self
.has_got_revision_branches_are_merged
:
473 expected
= self
.helper
.allrevs
[-1]
474 self
.checkGotRevision(bs
, expected
)
476 def do_vctest(self
, testRetry
=True):
478 args
= self
.helper
.vcargs
480 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
481 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
482 # woo double-substitution
483 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
484 for k
,v
in args
.items():
485 s
+= ", %s=%s" % (k
, repr(v
))
487 config
= config_vc
% s
489 m
.loadConfig(config
% 'clobber')
493 d
= self
.connectSlave()
494 d
.addCallback(lambda res
: log
.msg("testing clobber"))
495 d
.addCallback(self
._do
_vctest
_clobber
)
496 d
.addCallback(lambda res
: log
.msg("doing update"))
497 d
.addCallback(lambda res
: self
.loadConfig(config
% 'update'))
498 d
.addCallback(lambda res
: log
.msg("testing update"))
499 d
.addCallback(self
._do
_vctest
_update
)
501 d
.addCallback(lambda res
: log
.msg("testing update retry"))
502 d
.addCallback(self
._do
_vctest
_update
_retry
)
503 d
.addCallback(lambda res
: log
.msg("doing copy"))
504 d
.addCallback(lambda res
: self
.loadConfig(config
% 'copy'))
505 d
.addCallback(lambda res
: log
.msg("testing copy"))
506 d
.addCallback(self
._do
_vctest
_copy
)
508 d
.addCallback(lambda res
: log
.msg("doing export"))
509 d
.addCallback(lambda res
: self
.loadConfig(config
% 'export'))
510 d
.addCallback(lambda res
: log
.msg("testing export"))
511 d
.addCallback(self
._do
_vctest
_export
)
514 def _do_vctest_clobber(self
, res
):
515 d
= self
.doBuild() # initial checkout
516 d
.addCallback(self
._do
_vctest
_clobber
_1)
518 def _do_vctest_clobber_1(self
, bs
):
519 self
.shouldExist(self
.workdir
, "main.c")
520 self
.shouldExist(self
.workdir
, "version.c")
521 self
.shouldExist(self
.workdir
, "subdir", "subdir.c")
523 self
.shouldExist(self
.workdir
, self
.metadir
)
524 self
.failUnlessEqual(bs
.getProperty("revision"), None)
525 self
.failUnlessEqual(bs
.getProperty("branch"), None)
526 self
.checkGotRevisionIsLatest(bs
)
528 self
.touch(self
.workdir
, "newfile")
529 self
.shouldExist(self
.workdir
, "newfile")
530 d
= self
.doBuild() # rebuild clobbers workdir
531 d
.addCallback(self
._do
_vctest
_clobber
_2)
533 def _do_vctest_clobber_2(self
, res
):
534 self
.shouldNotExist(self
.workdir
, "newfile")
536 def _do_vctest_update(self
, res
):
537 log
.msg("_do_vctest_update")
538 d
= self
.doBuild() # rebuild with update
539 d
.addCallback(self
._do
_vctest
_update
_1)
541 def _do_vctest_update_1(self
, bs
):
542 log
.msg("_do_vctest_update_1")
543 self
.shouldExist(self
.workdir
, "main.c")
544 self
.shouldExist(self
.workdir
, "version.c")
545 self
.shouldContain(self
.workdir
, "version.c",
546 "version=%d" % self
.helper
.version
)
548 self
.shouldExist(self
.workdir
, self
.metadir
)
549 self
.failUnlessEqual(bs
.getProperty("revision"), None)
550 self
.checkGotRevisionIsLatest(bs
)
552 self
.touch(self
.workdir
, "newfile")
553 d
= self
.doBuild() # update rebuild leaves new files
554 d
.addCallback(self
._do
_vctest
_update
_2)
556 def _do_vctest_update_2(self
, bs
):
557 log
.msg("_do_vctest_update_2")
558 self
.shouldExist(self
.workdir
, "main.c")
559 self
.shouldExist(self
.workdir
, "version.c")
560 self
.touch(self
.workdir
, "newfile")
561 # now make a change to the repository and make sure we pick it up
562 d
= self
.helper
.vc_revise()
563 d
.addCallback(lambda res
: self
.doBuild())
564 d
.addCallback(self
._do
_vctest
_update
_3)
566 def _do_vctest_update_3(self
, bs
):
567 log
.msg("_do_vctest_update_3")
568 self
.shouldExist(self
.workdir
, "main.c")
569 self
.shouldExist(self
.workdir
, "version.c")
570 self
.shouldContain(self
.workdir
, "version.c",
571 "version=%d" % self
.helper
.version
)
572 self
.shouldExist(self
.workdir
, "newfile")
573 self
.failUnlessEqual(bs
.getProperty("revision"), None)
574 self
.checkGotRevisionIsLatest(bs
)
576 # now "update" to an older revision
577 d
= self
.doBuild(ss
=SourceStamp(revision
=self
.helper
.trunk
[-2]))
578 d
.addCallback(self
._do
_vctest
_update
_4)
580 def _do_vctest_update_4(self
, bs
):
581 log
.msg("_do_vctest_update_4")
582 self
.shouldExist(self
.workdir
, "main.c")
583 self
.shouldExist(self
.workdir
, "version.c")
584 self
.shouldContain(self
.workdir
, "version.c",
585 "version=%d" % (self
.helper
.version
-1))
586 self
.failUnlessEqual(bs
.getProperty("revision"),
587 self
.helper
.trunk
[-2])
588 self
.checkGotRevision(bs
, self
.helper
.trunk
[-2])
590 # now update to the newer revision
591 d
= self
.doBuild(ss
=SourceStamp(revision
=self
.helper
.trunk
[-1]))
592 d
.addCallback(self
._do
_vctest
_update
_5)
594 def _do_vctest_update_5(self
, bs
):
595 log
.msg("_do_vctest_update_5")
596 self
.shouldExist(self
.workdir
, "main.c")
597 self
.shouldExist(self
.workdir
, "version.c")
598 self
.shouldContain(self
.workdir
, "version.c",
599 "version=%d" % self
.helper
.version
)
600 self
.failUnlessEqual(bs
.getProperty("revision"),
601 self
.helper
.trunk
[-1])
602 self
.checkGotRevision(bs
, self
.helper
.trunk
[-1])
605 def _do_vctest_update_retry(self
, res
):
606 # certain local changes will prevent an update from working. The
607 # most common is to replace a file with a directory, or vice
608 # versa. The slave code should spot the failure and do a
610 os
.unlink(os
.path
.join(self
.workdir
, "main.c"))
611 os
.mkdir(os
.path
.join(self
.workdir
, "main.c"))
612 self
.touch(os
.path
.join(self
.workdir
, "main.c"), "foo")
613 self
.touch(self
.workdir
, "newfile")
615 d
= self
.doBuild() # update, but must clobber to handle the error
616 d
.addCallback(self
._do
_vctest
_update
_retry
_1)
618 def _do_vctest_update_retry_1(self
, bs
):
619 self
.shouldNotExist(self
.workdir
, "newfile")
621 def _do_vctest_copy(self
, res
):
622 d
= self
.doBuild() # copy rebuild clobbers new files
623 d
.addCallback(self
._do
_vctest
_copy
_1)
625 def _do_vctest_copy_1(self
, bs
):
627 self
.shouldExist(self
.workdir
, self
.metadir
)
628 self
.shouldNotExist(self
.workdir
, "newfile")
629 self
.touch(self
.workdir
, "newfile")
630 self
.touch(self
.vcdir
, "newvcfile")
631 self
.failUnlessEqual(bs
.getProperty("revision"), None)
632 self
.checkGotRevisionIsLatest(bs
)
634 d
= self
.doBuild() # copy rebuild clobbers new files
635 d
.addCallback(self
._do
_vctest
_copy
_2)
637 def _do_vctest_copy_2(self
, bs
):
639 self
.shouldExist(self
.workdir
, self
.metadir
)
640 self
.shouldNotExist(self
.workdir
, "newfile")
641 self
.shouldExist(self
.vcdir
, "newvcfile")
642 self
.shouldExist(self
.workdir
, "newvcfile")
643 self
.failUnlessEqual(bs
.getProperty("revision"), None)
644 self
.checkGotRevisionIsLatest(bs
)
645 self
.touch(self
.workdir
, "newfile")
647 def _do_vctest_export(self
, res
):
648 d
= self
.doBuild() # export rebuild clobbers new files
649 d
.addCallback(self
._do
_vctest
_export
_1)
651 def _do_vctest_export_1(self
, bs
):
652 self
.shouldNotExist(self
.workdir
, self
.metadir
)
653 self
.shouldNotExist(self
.workdir
, "newfile")
654 self
.failUnlessEqual(bs
.getProperty("revision"), None)
655 #self.checkGotRevisionIsLatest(bs)
656 # VC 'export' is not required to have a got_revision
657 self
.touch(self
.workdir
, "newfile")
659 d
= self
.doBuild() # export rebuild clobbers new files
660 d
.addCallback(self
._do
_vctest
_export
_2)
662 def _do_vctest_export_2(self
, bs
):
663 self
.shouldNotExist(self
.workdir
, self
.metadir
)
664 self
.shouldNotExist(self
.workdir
, "newfile")
665 self
.failUnlessEqual(bs
.getProperty("revision"), None)
666 #self.checkGotRevisionIsLatest(bs)
667 # VC 'export' is not required to have a got_revision
671 args
= self
.helper
.vcargs
673 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
674 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
675 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
676 for k
,v
in args
.items():
677 s
+= ", %s=%s" % (k
, repr(v
))
679 self
.config
= config_vc
% s
681 m
.loadConfig(self
.config
% "clobber")
685 ss
= SourceStamp(revision
=self
.helper
.trunk
[-1], patch
=(0, p0_diff
))
687 d
= self
.connectSlave()
688 d
.addCallback(lambda res
: self
.doBuild(ss
=ss
))
689 d
.addCallback(self
._doPatch
_1)
691 def _doPatch_1(self
, bs
):
692 self
.shouldContain(self
.workdir
, "version.c",
693 "version=%d" % self
.helper
.version
)
694 # make sure the file actually got patched
695 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
696 "subdir", "subdir.c")
697 data
= open(subdir_c
, "r").read()
698 self
.failUnlessIn("Hello patched subdir.\\n", data
)
699 self
.failUnlessEqual(bs
.getProperty("revision"),
700 self
.helper
.trunk
[-1])
701 self
.checkGotRevision(bs
, self
.helper
.trunk
[-1])
703 # make sure that a rebuild does not use the leftover patched workdir
704 d
= self
.master
.loadConfig(self
.config
% "update")
705 d
.addCallback(lambda res
: self
.doBuild(ss
=None))
706 d
.addCallback(self
._doPatch
_2)
708 def _doPatch_2(self
, bs
):
709 # make sure the file is back to its original
710 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
711 "subdir", "subdir.c")
712 data
= open(subdir_c
, "r").read()
713 self
.failUnlessIn("Hello subdir.\\n", data
)
714 self
.failUnlessEqual(bs
.getProperty("revision"), None)
715 self
.checkGotRevisionIsLatest(bs
)
717 # now make sure we can patch an older revision. We need at least two
718 # revisions here, so we might have to create one first
719 if len(self
.helper
.trunk
) < 2:
720 d
= self
.helper
.vc_revise()
721 d
.addCallback(self
._doPatch
_3)
723 return self
._doPatch
_3()
725 def _doPatch_3(self
, res
=None):
726 ss
= SourceStamp(revision
=self
.helper
.trunk
[-2], patch
=(0, p0_diff
))
727 d
= self
.doBuild(ss
=ss
)
728 d
.addCallback(self
._doPatch
_4)
730 def _doPatch_4(self
, bs
):
731 self
.shouldContain(self
.workdir
, "version.c",
732 "version=%d" % (self
.helper
.version
-1))
733 # and make sure the file actually got patched
734 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
735 "subdir", "subdir.c")
736 data
= open(subdir_c
, "r").read()
737 self
.failUnlessIn("Hello patched subdir.\\n", data
)
738 self
.failUnlessEqual(bs
.getProperty("revision"),
739 self
.helper
.trunk
[-2])
740 self
.checkGotRevision(bs
, self
.helper
.trunk
[-2])
742 # now check that we can patch a branch
743 ss
= SourceStamp(branch
=self
.helper
.branchname
,
744 revision
=self
.helper
.branch
[-1],
746 d
= self
.doBuild(ss
=ss
)
747 d
.addCallback(self
._doPatch
_5)
749 def _doPatch_5(self
, bs
):
750 self
.shouldContain(self
.workdir
, "version.c",
752 self
.shouldContain(self
.workdir
, "main.c", "Hello branch.")
753 subdir_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build",
754 "subdir", "subdir.c")
755 data
= open(subdir_c
, "r").read()
756 self
.failUnlessIn("Hello patched subdir.\\n", data
)
757 self
.failUnlessEqual(bs
.getProperty("revision"),
758 self
.helper
.branch
[-1])
759 self
.failUnlessEqual(bs
.getProperty("branch"), self
.helper
.branchname
)
760 self
.checkGotRevision(bs
, self
.helper
.branch
[-1])
763 def do_vctest_once(self
, shouldSucceed
):
766 args
= self
.helper
.vcargs
767 vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
768 workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
769 # woo double-substitution
770 s
= "s(%s, timeout=200, workdir='build', mode='clobber'" % (vctype
,)
771 for k
,v
in args
.items():
772 s
+= ", %s=%s" % (k
, repr(v
))
774 config
= config_vc
% s
781 d
= self
.doBuild(shouldSucceed
) # initial checkout
787 args
= self
.helper
.vcargs
789 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
790 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
791 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
792 for k
,v
in args
.items():
793 s
+= ", %s=%s" % (k
, repr(v
))
795 self
.config
= config_vc
% s
797 m
.loadConfig(self
.config
% "update")
801 # first we do a build of the trunk
802 d
= self
.connectSlave()
803 d
.addCallback(lambda res
: self
.doBuild(ss
=SourceStamp()))
804 d
.addCallback(self
._doBranch
_1)
806 def _doBranch_1(self
, bs
):
807 log
.msg("_doBranch_1")
808 # make sure the checkout was of the trunk
809 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
810 data
= open(main_c
, "r").read()
811 self
.failUnlessIn("Hello world.", data
)
813 # now do a checkout on the branch. The change in branch name should
815 self
.touch(self
.workdir
, "newfile")
816 d
= self
.doBuild(ss
=SourceStamp(branch
=self
.helper
.branchname
))
817 d
.addCallback(self
._doBranch
_2)
819 def _doBranch_2(self
, bs
):
820 log
.msg("_doBranch_2")
821 # make sure it was on the branch
822 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
823 data
= open(main_c
, "r").read()
824 self
.failUnlessIn("Hello branch.", data
)
825 # and make sure the tree was clobbered
826 self
.shouldNotExist(self
.workdir
, "newfile")
828 # doing another build on the same branch should not clobber the tree
829 self
.touch(self
.workdir
, "newbranchfile")
830 d
= self
.doBuild(ss
=SourceStamp(branch
=self
.helper
.branchname
))
831 d
.addCallback(self
._doBranch
_3)
833 def _doBranch_3(self
, bs
):
834 log
.msg("_doBranch_3")
835 # make sure it is still on the branch
836 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
837 data
= open(main_c
, "r").read()
838 self
.failUnlessIn("Hello branch.", data
)
839 # and make sure the tree was not clobbered
840 self
.shouldExist(self
.workdir
, "newbranchfile")
842 # now make sure that a non-branch checkout clobbers the tree
843 d
= self
.doBuild(ss
=SourceStamp())
844 d
.addCallback(self
._doBranch
_4)
846 def _doBranch_4(self
, bs
):
847 log
.msg("_doBranch_4")
848 # make sure it was on the trunk
849 main_c
= os
.path
.join(self
.slavebase
, "vc-dir", "build", "main.c")
850 data
= open(main_c
, "r").read()
851 self
.failUnlessIn("Hello world.", data
)
852 self
.shouldNotExist(self
.workdir
, "newbranchfile")
854 def do_getpatch(self
, doBranch
=True):
855 log
.msg("do_getpatch")
856 # prepare a buildslave to do checkouts
858 args
= self
.helper
.vcargs
860 self
.vcdir
= os
.path
.join(self
.slavebase
, "vc-dir", "source")
861 self
.workdir
= os
.path
.join(self
.slavebase
, "vc-dir", "build")
862 # woo double-substitution
863 s
= "s(%s, timeout=200, workdir='build', mode='%%s'" % (vctype
,)
864 for k
,v
in args
.items():
865 s
+= ", %s=%s" % (k
, repr(v
))
867 config
= config_vc
% s
869 m
.loadConfig(config
% 'clobber')
873 d
= self
.connectSlave()
875 # then set up the "developer's tree". first we modify a tree from the
877 tmpdir
= "try_workdir"
878 self
.trydir
= os
.path
.join(self
.helper
.repbase
, tmpdir
)
879 rmdirRecursive(self
.trydir
)
880 d
.addCallback(self
.do_getpatch_trunkhead
)
881 d
.addCallback(self
.do_getpatch_trunkold
)
883 d
.addCallback(self
.do_getpatch_branch
)
884 d
.addCallback(self
.do_getpatch_finish
)
887 def do_getpatch_finish(self
, res
):
888 log
.msg("do_getpatch_finish")
889 self
.helper
.vc_try_finish(self
.trydir
)
892 def try_shouldMatch(self
, filename
):
893 devfilename
= os
.path
.join(self
.trydir
, filename
)
894 devfile
= open(devfilename
, "r").read()
895 slavefilename
= os
.path
.join(self
.workdir
, filename
)
896 slavefile
= open(slavefilename
, "r").read()
897 self
.failUnlessEqual(devfile
, slavefile
,
898 ("slavefile (%s) contains '%s'. "
899 "developer's file (%s) contains '%s'. "
900 "These ought to match") %
901 (slavefilename
, slavefile
,
902 devfilename
, devfile
))
904 def do_getpatch_trunkhead(self
, res
):
905 log
.msg("do_getpatch_trunkhead")
906 d
= self
.helper
.vc_try_checkout(self
.trydir
, self
.helper
.trunk
[-1])
907 d
.addCallback(self
._do
_getpatch
_trunkhead
_1)
909 def _do_getpatch_trunkhead_1(self
, res
):
910 log
.msg("_do_getpatch_trunkhead_1")
911 d
= tryclient
.getSourceStamp(self
.vctype_try
, self
.trydir
, None)
912 d
.addCallback(self
._do
_getpatch
_trunkhead
_2)
914 def _do_getpatch_trunkhead_2(self
, ss
):
915 log
.msg("_do_getpatch_trunkhead_2")
916 d
= self
.doBuild(ss
=ss
)
917 d
.addCallback(self
._do
_getpatch
_trunkhead
_3)
919 def _do_getpatch_trunkhead_3(self
, res
):
920 log
.msg("_do_getpatch_trunkhead_3")
921 # verify that the resulting buildslave tree matches the developer's
922 self
.try_shouldMatch("main.c")
923 self
.try_shouldMatch("version.c")
924 self
.try_shouldMatch(os
.path
.join("subdir", "subdir.c"))
926 def do_getpatch_trunkold(self
, res
):
927 log
.msg("do_getpatch_trunkold")
928 # now try a tree from an older revision. We need at least two
929 # revisions here, so we might have to create one first
930 if len(self
.helper
.trunk
) < 2:
931 d
= self
.helper
.vc_revise()
932 d
.addCallback(self
._do
_getpatch
_trunkold
_1)
934 return self
._do
_getpatch
_trunkold
_1()
935 def _do_getpatch_trunkold_1(self
, res
=None):
936 log
.msg("_do_getpatch_trunkold_1")
937 d
= self
.helper
.vc_try_checkout(self
.trydir
, self
.helper
.trunk
[-2])
938 d
.addCallback(self
._do
_getpatch
_trunkold
_2)
940 def _do_getpatch_trunkold_2(self
, res
):
941 log
.msg("_do_getpatch_trunkold_2")
942 d
= tryclient
.getSourceStamp(self
.vctype_try
, self
.trydir
, None)
943 d
.addCallback(self
._do
_getpatch
_trunkold
_3)
945 def _do_getpatch_trunkold_3(self
, ss
):
946 log
.msg("_do_getpatch_trunkold_3")
947 d
= self
.doBuild(ss
=ss
)
948 d
.addCallback(self
._do
_getpatch
_trunkold
_4)
950 def _do_getpatch_trunkold_4(self
, res
):
951 log
.msg("_do_getpatch_trunkold_4")
952 # verify that the resulting buildslave tree matches the developer's
953 self
.try_shouldMatch("main.c")
954 self
.try_shouldMatch("version.c")
955 self
.try_shouldMatch(os
.path
.join("subdir", "subdir.c"))
957 def do_getpatch_branch(self
, res
):
958 log
.msg("do_getpatch_branch")
959 # now try a tree from a branch
960 d
= self
.helper
.vc_try_checkout(self
.trydir
, self
.helper
.branch
[-1],
961 self
.helper
.branchname
)
962 d
.addCallback(self
._do
_getpatch
_branch
_1)
964 def _do_getpatch_branch_1(self
, res
):
965 log
.msg("_do_getpatch_branch_1")
966 d
= tryclient
.getSourceStamp(self
.vctype_try
, self
.trydir
,
967 self
.helper
.try_branchname
)
968 d
.addCallback(self
._do
_getpatch
_branch
_2)
970 def _do_getpatch_branch_2(self
, ss
):
971 log
.msg("_do_getpatch_branch_2")
972 d
= self
.doBuild(ss
=ss
)
973 d
.addCallback(self
._do
_getpatch
_branch
_3)
975 def _do_getpatch_branch_3(self
, res
):
976 log
.msg("_do_getpatch_branch_3")
977 # verify that the resulting buildslave tree matches the developer's
978 self
.try_shouldMatch("main.c")
979 self
.try_shouldMatch("version.c")
980 self
.try_shouldMatch(os
.path
.join("subdir", "subdir.c"))
983 def dumpPatch(self
, patch
):
984 # this exists to help me figure out the right 'patchlevel' value
985 # should be returned by tryclient.getSourceStamp
987 open(n
,"w").write(patch
)
988 d
= self
.runCommand(".", ["lsdiff", n
])
989 def p(res
): print "lsdiff:", res
.strip().split("\n")
995 d
= defer
.succeed(None)
997 d2
= self
.master
.botmaster
.waitUntilBuilderDetached("vc")
998 d
.addCallback(lambda res
: self
.slave
.stopService())
999 d
.addCallback(lambda res
: d2
)
1001 d
.addCallback(lambda res
: self
.master
.stopService())
1003 d
.addCallback(lambda res
: self
.httpServer
.stopListening())
1004 def stopHTTPTimer():
1006 from twisted
.web
import http
# Twisted-2.0
1008 from twisted
.protocols
import http
# Twisted-1.3
1009 http
._logDateTimeStop
() # shut down the internal timer. DUMB!
1010 d
.addCallback(lambda res
: stopHTTPTimer())
1011 d
.addCallback(lambda res
: self
.tearDown2())
1014 def tearDown2(self
):
1017 class CVSHelper(BaseHelper
):
1018 branchname
= "branch"
1019 try_branchname
= "branch"
1022 cvspaths
= which('cvs')
1024 return (False, "CVS is not installed")
1025 # cvs-1.10 (as shipped with OS-X 10.3 "Panther") is too old for this
1026 # test. There is a situation where we check out a tree, make a
1027 # change, then commit it back, and CVS refuses to believe that we're
1028 # operating in a CVS tree. I tested cvs-1.12.9 and it works ok, OS-X
1029 # 10.4 "Tiger" comes with cvs-1.11, but I haven't tested that yet.
1030 # For now, skip the tests if we've got 1.10 .
1031 log
.msg("running %s --version.." % (cvspaths
[0],))
1032 d
= utils
.getProcessOutput(cvspaths
[0], ["--version"],
1034 d
.addCallback(self
._capable
, cvspaths
[0])
1037 def _capable(self
, v
, vcexe
):
1038 m
= re
.search(r
'\(CVS\) ([\d\.]+) ', v
)
1040 log
.msg("couldn't identify CVS version number in output:")
1041 log
.msg("'''%s'''" % v
)
1042 log
.msg("skipping tests")
1043 return (False, "Found CVS but couldn't identify its version")
1045 log
.msg("found CVS version '%s'" % ver
)
1047 return (False, "Found CVS, but it is too old")
1052 # this timestamp is eventually passed to CVS in a -D argument, and
1053 # strftime's %z specifier doesn't seem to work reliably (I get +0000
1054 # where I should get +0700 under linux sometimes, and windows seems
1055 # to want to put a verbose 'Eastern Standard Time' in there), so
1056 # leave off the timezone specifier and treat this as localtime. A
1057 # valid alternative would be to use a hard-coded +0000 and
1059 return time
.strftime("%Y-%m-%d %H:%M:%S", time
.localtime())
1061 def createRepository(self
):
1062 self
.createBasedir()
1063 self
.cvsrep
= cvsrep
= os
.path
.join(self
.repbase
, "CVS-Repository")
1064 tmp
= os
.path
.join(self
.repbase
, "cvstmp")
1066 w
= self
.dovc(self
.repbase
, "-d %s init" % cvsrep
)
1067 yield w
; w
.getResult() # we must getResult() to raise any exceptions
1070 cmd
= ("-d %s import" % cvsrep
+
1071 " -m sample_project_files sample vendortag start")
1072 w
= self
.dovc(tmp
, cmd
)
1073 yield w
; w
.getResult()
1075 # take a timestamp as the first revision number
1077 self
.addTrunkRev(self
.getdate())
1080 w
= self
.dovc(self
.repbase
,
1081 "-d %s checkout -d cvstmp sample" % self
.cvsrep
)
1082 yield w
; w
.getResult()
1084 w
= self
.dovc(tmp
, "tag -b %s" % self
.branchname
)
1085 yield w
; w
.getResult()
1086 self
.populate_branch(tmp
)
1088 "commit -m commit_on_branch -r %s" % self
.branchname
)
1089 yield w
; w
.getResult()
1092 self
.addBranchRev(self
.getdate())
1094 self
.vcargs
= { 'cvsroot': self
.cvsrep
, 'cvsmodule': "sample" }
1095 createRepository
= deferredGenerator(createRepository
)
1098 def vc_revise(self
):
1099 tmp
= os
.path
.join(self
.repbase
, "cvstmp")
1101 w
= self
.dovc(self
.repbase
,
1102 "-d %s checkout -d cvstmp sample" % self
.cvsrep
)
1103 yield w
; w
.getResult()
1105 version_c
= VERSION_C
% self
.version
1106 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1108 "commit -m revised_to_%d version.c" % self
.version
)
1109 yield w
; w
.getResult()
1112 self
.addTrunkRev(self
.getdate())
1114 vc_revise
= deferredGenerator(vc_revise
)
1116 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1117 # 'workdir' is an absolute path
1118 assert os
.path
.abspath(workdir
) == workdir
1119 cmd
= [self
.vcexe
, "-d", self
.cvsrep
, "checkout",
1122 if branch
is not None:
1125 cmd
.append("sample")
1126 w
= self
.do(self
.repbase
, cmd
)
1127 yield w
; w
.getResult()
1128 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1129 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1131 def vc_try_finish(self
, workdir
):
1132 rmdirRecursive(workdir
)
1134 class CVS(VCBase
, unittest
.TestCase
):
1140 # CVS gives us got_revision, but it is based entirely upon the local
1141 # clock, which means it is unlikely to match the timestamp taken earlier.
1142 # This might be enough for common use, but won't be good enough for our
1143 # tests to accept, so pretend it doesn't have got_revision at all.
1144 has_got_revision
= False
1146 def testCheckout(self
):
1147 d
= self
.do_vctest()
1150 def testPatch(self
):
1154 def testCheckoutBranch(self
):
1155 d
= self
.do_branch()
1159 d
= self
.do_getpatch(doBranch
=False)
1162 VCS
.registerVC(CVS
.vc_name
, CVSHelper())
1165 class SVNHelper(BaseHelper
):
1166 branchname
= "sample/branch"
1167 try_branchname
= "sample/branch"
1170 svnpaths
= which('svn')
1171 svnadminpaths
= which('svnadmin')
1173 return (False, "SVN is not installed")
1174 if not svnadminpaths
:
1175 return (False, "svnadmin is not installed")
1176 # we need svn to be compiled with the ra_local access
1178 log
.msg("running svn --version..")
1179 env
= os
.environ
.copy()
1181 d
= utils
.getProcessOutput(svnpaths
[0], ["--version"],
1183 d
.addCallback(self
._capable
, svnpaths
[0], svnadminpaths
[0])
1186 def _capable(self
, v
, vcexe
, svnadmin
):
1187 if v
.find("handles 'file' schem") != -1:
1188 # older versions say 'schema', 1.2.0 and beyond say 'scheme'
1190 self
.svnadmin
= svnadmin
1192 excuse
= ("%s found but it does not support 'file:' " +
1193 "schema, skipping svn tests") % vcexe
1195 return (False, excuse
)
1197 def createRepository(self
):
1198 self
.createBasedir()
1199 self
.svnrep
= os
.path
.join(self
.repbase
,
1200 "SVN-Repository").replace('\\','/')
1201 tmp
= os
.path
.join(self
.repbase
, "svntmp")
1202 if sys
.platform
== 'win32':
1203 # On Windows Paths do not start with a /
1204 self
.svnurl
= "file:///%s" % self
.svnrep
1206 self
.svnurl
= "file://%s" % self
.svnrep
1207 self
.svnurl_trunk
= self
.svnurl
+ "/sample/trunk"
1208 self
.svnurl_branch
= self
.svnurl
+ "/sample/branch"
1210 w
= self
.do(self
.repbase
, self
.svnadmin
+" create %s" % self
.svnrep
)
1211 yield w
; w
.getResult()
1215 "import -m sample_project_files %s" %
1217 yield w
; out
= w
.getResult()
1219 m
= re
.search(r
'Committed revision (\d+)\.', out
)
1220 assert m
.group(1) == "1" # first revision is always "1"
1221 self
.addTrunkRev(int(m
.group(1)))
1223 w
= self
.dovc(self
.repbase
,
1224 "checkout %s svntmp" % self
.svnurl_trunk
)
1225 yield w
; w
.getResult()
1227 w
= self
.dovc(tmp
, "cp -m make_branch %s %s" % (self
.svnurl_trunk
,
1228 self
.svnurl_branch
))
1229 yield w
; w
.getResult()
1230 w
= self
.dovc(tmp
, "switch %s" % self
.svnurl_branch
)
1231 yield w
; w
.getResult()
1232 self
.populate_branch(tmp
)
1233 w
= self
.dovc(tmp
, "commit -m commit_on_branch")
1234 yield w
; out
= w
.getResult()
1236 m
= re
.search(r
'Committed revision (\d+)\.', out
)
1237 self
.addBranchRev(int(m
.group(1)))
1238 createRepository
= deferredGenerator(createRepository
)
1240 def vc_revise(self
):
1241 tmp
= os
.path
.join(self
.repbase
, "svntmp")
1243 log
.msg("vc_revise" + self
.svnurl_trunk
)
1244 w
= self
.dovc(self
.repbase
,
1245 "checkout %s svntmp" % self
.svnurl_trunk
)
1246 yield w
; w
.getResult()
1248 version_c
= VERSION_C
% self
.version
1249 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1250 w
= self
.dovc(tmp
, "commit -m revised_to_%d" % self
.version
)
1251 yield w
; out
= w
.getResult()
1252 m
= re
.search(r
'Committed revision (\d+)\.', out
)
1253 self
.addTrunkRev(int(m
.group(1)))
1255 vc_revise
= deferredGenerator(vc_revise
)
1257 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1258 assert os
.path
.abspath(workdir
) == workdir
1259 if os
.path
.exists(workdir
):
1260 rmdirRecursive(workdir
)
1262 svnurl
= self
.svnurl_trunk
1264 # N.B.: this is *not* os.path.join: SVN URLs use slashes
1265 # regardless of the host operating system's filepath separator
1266 svnurl
= self
.svnurl
+ "/" + branch
1267 w
= self
.dovc(self
.repbase
,
1268 "checkout %s %s" % (svnurl
, workdir
))
1269 yield w
; w
.getResult()
1270 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1271 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1273 def vc_try_finish(self
, workdir
):
1274 rmdirRecursive(workdir
)
1277 class SVN(VCBase
, unittest
.TestCase
):
1283 has_got_revision
= True
1284 has_got_revision_branches_are_merged
= True
1286 def testCheckout(self
):
1287 # we verify this one with the svnurl style of vcargs. We test the
1288 # baseURL/defaultBranch style in testPatch and testCheckoutBranch.
1289 self
.helper
.vcargs
= { 'svnurl': self
.helper
.svnurl_trunk
}
1290 d
= self
.do_vctest()
1293 def testPatch(self
):
1294 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1295 'defaultBranch': "sample/trunk",
1300 def testCheckoutBranch(self
):
1301 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1302 'defaultBranch': "sample/trunk",
1304 d
= self
.do_branch()
1308 # extract the base revision and patch from a modified tree, use it to
1309 # create the same contents on the buildslave
1310 self
.helper
.vcargs
= { 'baseURL': self
.helper
.svnurl
+ "/",
1311 'defaultBranch': "sample/trunk",
1313 d
= self
.do_getpatch()
1316 VCS
.registerVC(SVN
.vc_name
, SVNHelper())
1318 class DarcsHelper(BaseHelper
):
1319 branchname
= "branch"
1320 try_branchname
= "branch"
1323 darcspaths
= which('darcs')
1325 return (False, "Darcs is not installed")
1326 self
.vcexe
= darcspaths
[0]
1329 def createRepository(self
):
1330 self
.createBasedir()
1331 self
.darcs_base
= os
.path
.join(self
.repbase
, "Darcs-Repository")
1332 self
.rep_trunk
= os
.path
.join(self
.darcs_base
, "trunk")
1333 self
.rep_branch
= os
.path
.join(self
.darcs_base
, "branch")
1334 tmp
= os
.path
.join(self
.repbase
, "darcstmp")
1336 os
.makedirs(self
.rep_trunk
)
1337 w
= self
.dovc(self
.rep_trunk
, "initialize")
1338 yield w
; w
.getResult()
1339 os
.makedirs(self
.rep_branch
)
1340 w
= self
.dovc(self
.rep_branch
, "initialize")
1341 yield w
; w
.getResult()
1344 w
= self
.dovc(tmp
, "initialize")
1345 yield w
; w
.getResult()
1346 w
= self
.dovc(tmp
, "add -r .")
1347 yield w
; w
.getResult()
1348 w
= self
.dovc(tmp
, "record -a -m initial_import --skip-long-comment -A test@buildbot.sf.net")
1349 yield w
; w
.getResult()
1350 w
= self
.dovc(tmp
, "push -a %s" % self
.rep_trunk
)
1351 yield w
; w
.getResult()
1352 w
= self
.dovc(tmp
, "changes --context")
1353 yield w
; out
= w
.getResult()
1354 self
.addTrunkRev(out
)
1356 self
.populate_branch(tmp
)
1357 w
= self
.dovc(tmp
, "record -a --ignore-times -m commit_on_branch --skip-long-comment -A test@buildbot.sf.net")
1358 yield w
; w
.getResult()
1359 w
= self
.dovc(tmp
, "push -a %s" % self
.rep_branch
)
1360 yield w
; w
.getResult()
1361 w
= self
.dovc(tmp
, "changes --context")
1362 yield w
; out
= w
.getResult()
1363 self
.addBranchRev(out
)
1365 createRepository
= deferredGenerator(createRepository
)
1367 def vc_revise(self
):
1368 tmp
= os
.path
.join(self
.repbase
, "darcstmp")
1370 w
= self
.dovc(tmp
, "initialize")
1371 yield w
; w
.getResult()
1372 w
= self
.dovc(tmp
, "pull -a %s" % self
.rep_trunk
)
1373 yield w
; w
.getResult()
1376 version_c
= VERSION_C
% self
.version
1377 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1378 w
= self
.dovc(tmp
, "record -a --ignore-times -m revised_to_%d --skip-long-comment -A test@buildbot.sf.net" % self
.version
)
1379 yield w
; w
.getResult()
1380 w
= self
.dovc(tmp
, "push -a %s" % self
.rep_trunk
)
1381 yield w
; w
.getResult()
1382 w
= self
.dovc(tmp
, "changes --context")
1383 yield w
; out
= w
.getResult()
1384 self
.addTrunkRev(out
)
1386 vc_revise
= deferredGenerator(vc_revise
)
1388 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1389 assert os
.path
.abspath(workdir
) == workdir
1390 if os
.path
.exists(workdir
):
1391 rmdirRecursive(workdir
)
1392 os
.makedirs(workdir
)
1393 w
= self
.dovc(workdir
, "initialize")
1394 yield w
; w
.getResult()
1396 rep
= self
.rep_trunk
1398 rep
= os
.path
.join(self
.darcs_base
, branch
)
1399 w
= self
.dovc(workdir
, "pull -a %s" % rep
)
1400 yield w
; w
.getResult()
1401 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1402 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1404 def vc_try_finish(self
, workdir
):
1405 rmdirRecursive(workdir
)
1408 class Darcs(VCBase
, unittest
.TestCase
):
1411 # Darcs has a metadir="_darcs", but it does not have an 'export'
1414 vctype
= "step.Darcs"
1415 vctype_try
= "darcs"
1416 has_got_revision
= True
1418 def testCheckout(self
):
1419 self
.helper
.vcargs
= { 'repourl': self
.helper
.rep_trunk
}
1420 d
= self
.do_vctest(testRetry
=False)
1422 # TODO: testRetry has the same problem with Darcs as it does for
1426 def testPatch(self
):
1427 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1428 'defaultBranch': "trunk" }
1432 def testCheckoutBranch(self
):
1433 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1434 'defaultBranch': "trunk" }
1435 d
= self
.do_branch()
1438 def testCheckoutHTTP(self
):
1440 repourl
= "http://localhost:%d/Darcs-Repository/trunk" % self
.httpPort
1441 self
.helper
.vcargs
= { 'repourl': repourl
}
1442 d
= self
.do_vctest(testRetry
=False)
1446 self
.helper
.vcargs
= { 'baseURL': self
.helper
.darcs_base
+ "/",
1447 'defaultBranch': "trunk" }
1448 d
= self
.do_getpatch()
1451 VCS
.registerVC(Darcs
.vc_name
, DarcsHelper())
1455 def registerRepository(self
, coordinates
):
1457 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1458 yield w
; out
= w
.getResult()
1460 w
= self
.dovc(self
.repbase
, "register-archive -d %s" % a
)
1461 yield w
; w
.getResult()
1462 w
= self
.dovc(self
.repbase
, "register-archive %s" % coordinates
)
1463 yield w
; w
.getResult()
1464 registerRepository
= deferredGenerator(registerRepository
)
1466 def unregisterRepository(self
):
1468 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1469 yield w
; out
= w
.getResult()
1471 w
= self
.dovc(self
.repbase
, "register-archive -d %s" % a
)
1472 yield w
; out
= w
.getResult()
1473 unregisterRepository
= deferredGenerator(unregisterRepository
)
1475 class TlaHelper(BaseHelper
, ArchCommon
):
1476 defaultbranch
= "testvc--mainline--1"
1477 branchname
= "testvc--branch--1"
1478 try_branchname
= None # TlaExtractor can figure it out by itself
1482 tlapaths
= which('tla')
1484 return (False, "Arch (tla) is not installed")
1485 self
.vcexe
= tlapaths
[0]
1488 def do_get(self
, basedir
, archive
, branch
, newdir
):
1489 # the 'get' syntax is different between tla and baz. baz, while
1490 # claiming to honor an --archive argument, in fact ignores it. The
1491 # correct invocation is 'baz get archive/revision newdir'.
1492 if self
.archcmd
== "tla":
1493 w
= self
.dovc(basedir
,
1494 "get -A %s %s %s" % (archive
, branch
, newdir
))
1496 w
= self
.dovc(basedir
,
1497 "get %s/%s %s" % (archive
, branch
, newdir
))
1500 def createRepository(self
):
1501 self
.createBasedir()
1502 # first check to see if bazaar is around, since we'll need to know
1504 d
= VCS
.capable(Bazaar
.vc_name
)
1505 d
.addCallback(self
._createRepository
_1)
1508 def _createRepository_1(self
, res
):
1511 # pick a hopefully unique string for the archive name, in the form
1512 # test-%d@buildbot.sf.net--testvc, since otherwise multiple copies of
1513 # the unit tests run in the same user account will collide (since the
1514 # archive names are kept in the per-user ~/.arch-params/ directory).
1516 self
.archname
= "test-%s-%d@buildbot.sf.net--testvc" % (self
.archcmd
,
1518 trunk
= self
.defaultbranch
1519 branch
= self
.branchname
1521 repword
= self
.archcmd
.capitalize()
1522 self
.archrep
= os
.path
.join(self
.repbase
, "%s-Repository" % repword
)
1523 tmp
= os
.path
.join(self
.repbase
, "archtmp")
1528 w
= self
.dovc(tmp
, "my-id", failureIsOk
=True)
1529 yield w
; res
= w
.getResult()
1531 # tla will fail a lot of operations if you have not set an ID
1532 w
= self
.do(tmp
, [self
.vcexe
, "my-id",
1533 "Buildbot Test Suite <test@buildbot.sf.net>"])
1534 yield w
; w
.getResult()
1537 # bazaar keeps a cache of revisions, but this test creates a new
1538 # archive each time it is run, so the cache causes errors.
1539 # Disable the cache to avoid these problems. This will be
1540 # slightly annoying for people who run the buildbot tests under
1541 # the same UID as one which uses baz on a regular basis, but
1542 # bazaar doesn't give us a way to disable the cache just for this
1544 cmd
= "%s cache-config --disable" % VCS
.getHelper('bazaar').vcexe
1545 w
= self
.do(tmp
, cmd
)
1546 yield w
; w
.getResult()
1548 w
= waitForDeferred(self
.unregisterRepository())
1549 yield w
; w
.getResult()
1551 # these commands can be run in any directory
1552 w
= self
.dovc(tmp
, "make-archive -l %s %s" % (a
, self
.archrep
))
1553 yield w
; w
.getResult()
1554 if self
.archcmd
== "tla":
1555 w
= self
.dovc(tmp
, "archive-setup -A %s %s" % (a
, trunk
))
1556 yield w
; w
.getResult()
1557 w
= self
.dovc(tmp
, "archive-setup -A %s %s" % (a
, branch
))
1558 yield w
; w
.getResult()
1560 # baz does not require an 'archive-setup' step
1563 # these commands must be run in the directory that is to be imported
1564 w
= self
.dovc(tmp
, "init-tree --nested %s/%s" % (a
, trunk
))
1565 yield w
; w
.getResult()
1566 files
= " ".join(["main.c", "version.c", "subdir",
1567 os
.path
.join("subdir", "subdir.c")])
1568 w
= self
.dovc(tmp
, "add-id %s" % files
)
1569 yield w
; w
.getResult()
1571 w
= self
.dovc(tmp
, "import %s/%s" % (a
, trunk
))
1572 yield w
; out
= w
.getResult()
1573 self
.addTrunkRev("base-0")
1576 if self
.archcmd
== "tla":
1577 branchstart
= "%s--base-0" % trunk
1578 w
= self
.dovc(tmp
, "tag -A %s %s %s" % (a
, branchstart
, branch
))
1579 yield w
; w
.getResult()
1581 w
= self
.dovc(tmp
, "branch %s" % branch
)
1582 yield w
; w
.getResult()
1586 # check out the branch
1587 w
= self
.do_get(self
.repbase
, a
, branch
, "archtmp")
1588 yield w
; w
.getResult()
1590 self
.populate_branch(tmp
)
1591 logfile
= "++log.%s--%s" % (branch
, a
)
1592 logmsg
= "Summary: commit on branch\nKeywords:\n\n"
1593 open(os
.path
.join(tmp
, logfile
), "w").write(logmsg
)
1594 w
= self
.dovc(tmp
, "commit")
1595 yield w
; out
= w
.getResult()
1596 m
= re
.search(r
'committed %s/%s--([\S]+)' % (a
, branch
),
1598 assert (m
.group(1) == "base-0" or m
.group(1).startswith("patch-"))
1599 self
.addBranchRev(m
.group(1))
1601 w
= waitForDeferred(self
.unregisterRepository())
1602 yield w
; w
.getResult()
1605 # we unregister the repository each time, because we might have
1606 # changed the coordinates (since we switch from a file: URL to an
1607 # http: URL for various tests). The buildslave code doesn't forcibly
1608 # unregister the archive, so we have to do it here.
1609 w
= waitForDeferred(self
.unregisterRepository())
1610 yield w
; w
.getResult()
1612 _createRepository_1
= deferredGenerator(_createRepository_1
)
1614 def vc_revise(self
):
1615 # the fix needs to be done in a workspace that is linked to a
1616 # read-write version of the archive (i.e., using file-based
1617 # coordinates instead of HTTP ones), so we re-register the repository
1618 # before we begin. We unregister it when we're done to make sure the
1619 # build will re-register the correct one for whichever test is
1620 # currently being run.
1622 # except, that step.Bazaar really doesn't like it when the archive
1623 # gets unregistered behind its back. The slave tries to do a 'baz
1624 # replay' in a tree with an archive that is no longer recognized, and
1625 # baz aborts with a botched invariant exception. This causes
1626 # mode=update to fall back to clobber+get, which flunks one of the
1627 # tests (the 'newfile' check in _do_vctest_update_3 fails)
1629 # to avoid this, we take heroic steps here to leave the archive
1630 # registration in the same state as we found it.
1632 tmp
= os
.path
.join(self
.repbase
, "archtmp")
1635 w
= self
.dovc(self
.repbase
, "archives %s" % a
)
1636 yield w
; out
= w
.getResult()
1638 lines
= out
.split("\n")
1639 coordinates
= lines
[1].strip()
1641 # now register the read-write location
1642 w
= waitForDeferred(self
.registerRepository(self
.archrep
))
1643 yield w
; w
.getResult()
1645 trunk
= self
.defaultbranch
1647 w
= self
.do_get(self
.repbase
, a
, trunk
, "archtmp")
1648 yield w
; w
.getResult()
1650 # tla appears to use timestamps to determine which files have
1651 # changed, so wait long enough for the new file to have a different
1655 version_c
= VERSION_C
% self
.version
1656 open(os
.path
.join(tmp
, "version.c"), "w").write(version_c
)
1658 logfile
= "++log.%s--%s" % (trunk
, a
)
1659 logmsg
= "Summary: revised_to_%d\nKeywords:\n\n" % self
.version
1660 open(os
.path
.join(tmp
, logfile
), "w").write(logmsg
)
1661 w
= self
.dovc(tmp
, "commit")
1662 yield w
; out
= w
.getResult()
1663 m
= re
.search(r
'committed %s/%s--([\S]+)' % (a
, trunk
),
1665 assert (m
.group(1) == "base-0" or m
.group(1).startswith("patch-"))
1666 self
.addTrunkRev(m
.group(1))
1668 # now re-register the original coordinates
1669 w
= waitForDeferred(self
.registerRepository(coordinates
))
1670 yield w
; w
.getResult()
1672 vc_revise
= deferredGenerator(vc_revise
)
1674 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1675 assert os
.path
.abspath(workdir
) == workdir
1676 if os
.path
.exists(workdir
):
1677 rmdirRecursive(workdir
)
1681 # register the read-write location, if it wasn't already registered
1682 w
= waitForDeferred(self
.registerRepository(self
.archrep
))
1683 yield w
; w
.getResult()
1685 w
= self
.do_get(self
.repbase
, a
, "testvc--mainline--1", workdir
)
1686 yield w
; w
.getResult()
1690 open(os
.path
.join(workdir
, "subdir", "subdir.c"), "w").write(TRY_C
)
1691 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
1693 def vc_try_finish(self
, workdir
):
1694 rmdirRecursive(workdir
)
1696 class Arch(VCBase
, unittest
.TestCase
):
1700 # Arch has a metadir="{arch}", but it does not have an 'export' mode.
1701 vctype
= "step.Arch"
1703 has_got_revision
= True
1705 def testCheckout(self
):
1706 # these are the coordinates of the read-write archive used by all the
1707 # non-HTTP tests. testCheckoutHTTP overrides these.
1708 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1709 'version': self
.helper
.defaultbranch
}
1710 d
= self
.do_vctest(testRetry
=False)
1711 # the current testRetry=True logic doesn't have the desired effect:
1712 # "update" is a no-op because arch knows that the repository hasn't
1713 # changed. Other VC systems will re-checkout missing files on
1714 # update, arch just leaves the tree untouched. TODO: come up with
1715 # some better test logic, probably involving a copy of the
1716 # repository that has a few changes checked in.
1720 def testCheckoutHTTP(self
):
1722 url
= "http://localhost:%d/Tla-Repository" % self
.httpPort
1723 self
.helper
.vcargs
= { 'url': url
,
1724 'version': "testvc--mainline--1" }
1725 d
= self
.do_vctest(testRetry
=False)
1728 def testPatch(self
):
1729 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1730 'version': self
.helper
.defaultbranch
}
1734 def testCheckoutBranch(self
):
1735 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1736 'version': self
.helper
.defaultbranch
}
1737 d
= self
.do_branch()
1741 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1742 'version': self
.helper
.defaultbranch
}
1743 d
= self
.do_getpatch()
1746 VCS
.registerVC(Arch
.vc_name
, TlaHelper())
1749 class BazaarHelper(TlaHelper
):
1753 bazpaths
= which('baz')
1755 return (False, "Arch (baz) is not installed")
1756 self
.vcexe
= bazpaths
[0]
1759 def setUp2(self
, res
):
1760 # we unregister the repository each time, because we might have
1761 # changed the coordinates (since we switch from a file: URL to an
1762 # http: URL for various tests). The buildslave code doesn't forcibly
1763 # unregister the archive, so we have to do it here.
1764 d
= self
.unregisterRepository()
1771 vctype
= "step.Bazaar"
1773 has_got_revision
= True
1777 def testCheckout(self
):
1778 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1779 # Baz adds the required 'archive' argument
1780 'archive': self
.helper
.archname
,
1781 'version': self
.helper
.defaultbranch
,
1783 d
= self
.do_vctest(testRetry
=False)
1784 # the current testRetry=True logic doesn't have the desired effect:
1785 # "update" is a no-op because arch knows that the repository hasn't
1786 # changed. Other VC systems will re-checkout missing files on
1787 # update, arch just leaves the tree untouched. TODO: come up with
1788 # some better test logic, probably involving a copy of the
1789 # repository that has a few changes checked in.
1793 def testCheckoutHTTP(self
):
1795 url
= "http://localhost:%d/Baz-Repository" % self
.httpPort
1796 self
.helper
.vcargs
= { 'url': url
,
1797 'archive': self
.helper
.archname
,
1798 'version': self
.helper
.defaultbranch
,
1800 d
= self
.do_vctest(testRetry
=False)
1803 def testPatch(self
):
1804 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1805 # Baz adds the required 'archive' argument
1806 'archive': self
.helper
.archname
,
1807 'version': self
.helper
.defaultbranch
,
1812 def testCheckoutBranch(self
):
1813 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1814 # Baz adds the required 'archive' argument
1815 'archive': self
.helper
.archname
,
1816 'version': self
.helper
.defaultbranch
,
1818 d
= self
.do_branch()
1822 self
.helper
.vcargs
= {'url': self
.helper
.archrep
,
1823 # Baz adds the required 'archive' argument
1824 'archive': self
.helper
.archname
,
1825 'version': self
.helper
.defaultbranch
,
1827 d
= self
.do_getpatch()
1830 def fixRepository(self
):
1831 self
.fixtimer
= None
1832 self
.site
.resource
= self
.root
1834 def testRetry(self
):
1835 # we want to verify that step.Source(retry=) works, and the easiest
1836 # way to make VC updates break (temporarily) is to break the HTTP
1837 # server that's providing the repository. Anything else pretty much
1838 # requires mutating the (read-only) BUILDBOT_TEST_VC repository, or
1839 # modifying the buildslave's checkout command while it's running.
1841 # this test takes a while to run, so don't bother doing it with
1842 # anything other than baz
1846 # break the repository server
1847 from twisted
.web
import static
1848 self
.site
.resource
= static
.Data("Sorry, repository is offline",
1850 # and arrange to fix it again in 5 seconds, while the test is
1852 self
.fixtimer
= reactor
.callLater(5, self
.fixRepository
)
1854 url
= "http://localhost:%d/Baz-Repository" % self
.httpPort
1855 self
.helper
.vcargs
= { 'url': url
,
1856 'archive': self
.helper
.archname
,
1857 'version': self
.helper
.defaultbranch
,
1860 d
= self
.do_vctest_once(True)
1861 d
.addCallback(self
._testRetry
_1)
1863 def _testRetry_1(self
, bs
):
1864 # make sure there was mention of the retry attempt in the logs
1866 self
.failUnlessIn("unable to access URL", l
.getText(),
1867 "funny, VC operation didn't fail at least once")
1868 self
.failUnlessIn("update failed, trying 4 more times after 5 seconds",
1869 l
.getTextWithHeaders(),
1870 "funny, VC operation wasn't reattempted")
1872 def testRetryFails(self
):
1873 # make sure that the build eventually gives up on a repository which
1874 # is completely unavailable
1878 # break the repository server, and leave it broken
1879 from twisted
.web
import static
1880 self
.site
.resource
= static
.Data("Sorry, repository is offline",
1883 url
= "http://localhost:%d/Baz-Repository" % self
.httpPort
1884 self
.helper
.vcargs
= {'url': url
,
1885 'archive': self
.helper
.archname
,
1886 'version': self
.helper
.defaultbranch
,
1889 d
= self
.do_vctest_once(False)
1890 d
.addCallback(self
._testRetryFails
_1)
1892 def _testRetryFails_1(self
, bs
):
1893 self
.failUnlessEqual(bs
.getResults(), FAILURE
)
1895 def tearDown2(self
):
1897 self
.fixtimer
.cancel()
1898 # tell tla to get rid of the leftover archive this test leaves in the
1899 # user's 'tla archives' listing. The name of this archive is provided
1900 # by the repository tarball, so the following command must use the
1901 # same name. We could use archive= to set it explicitly, but if you
1902 # change it from the default, then 'tla update' won't work.
1903 d
= self
.helper
.unregisterRepository()
1906 VCS
.registerVC(Bazaar
.vc_name
, BazaarHelper())
1908 class MercurialHelper(BaseHelper
):
1909 branchname
= "branch"
1910 try_branchname
= "branch"
1913 hgpaths
= which("hg")
1915 return (False, "Mercurial is not installed")
1916 self
.vcexe
= hgpaths
[0]
1919 def extract_id(self
, output
):
1920 m
= re
.search(r
'^(\w+)', output
)
1923 def createRepository(self
):
1924 self
.createBasedir()
1925 self
.hg_base
= os
.path
.join(self
.repbase
, "Mercurial-Repository")
1926 self
.rep_trunk
= os
.path
.join(self
.hg_base
, "trunk")
1927 self
.rep_branch
= os
.path
.join(self
.hg_base
, "branch")
1928 tmp
= os
.path
.join(self
.hg_base
, "hgtmp")
1930 os
.makedirs(self
.rep_trunk
)
1931 w
= self
.dovc(self
.rep_trunk
, "init")
1932 yield w
; w
.getResult()
1933 os
.makedirs(self
.rep_branch
)
1934 w
= self
.dovc(self
.rep_branch
, "init")
1935 yield w
; w
.getResult()
1938 w
= self
.dovc(tmp
, "init")
1939 yield w
; w
.getResult()
1940 w
= self
.dovc(tmp
, "add")
1941 yield w
; w
.getResult()
1942 w
= self
.dovc(tmp
, "commit -m initial_import")
1943 yield w
; w
.getResult()
1944 w
= self
.dovc(tmp
, "push %s" % self
.rep_trunk
)
1945 # note that hg-push does not actually update the working directory
1946 yield w
; w
.getResult()
1947 w
= self
.dovc(tmp
, "identify")
1948 yield w
; out
= w
.getResult()
1949 self
.addTrunkRev(self
.extract_id(out
))
1951 self
.populate_branch(tmp
)
1952 w
= self
.dovc(tmp
, "commit -m commit_on_branch")
1953 yield w
; w
.getResult()
1954 w
= self
.dovc(tmp
, "push %s" % self
.rep_branch
)
1955 yield w
; w
.getResult()
1956 w
= self
.dovc(tmp
, "identify")
1957 yield w
; out
= w
.getResult()
1958 self
.addBranchRev(self
.extract_id(out
))
1960 createRepository
= deferredGenerator(createRepository
)
1962 def vc_revise(self
):
1963 tmp
= os
.path
.join(self
.hg_base
, "hgtmp2")
1964 w
= self
.dovc(self
.hg_base
, "clone %s %s" % (self
.rep_trunk
, tmp
))
1965 yield w
; w
.getResult()
1968 version_c
= VERSION_C
% self
.version
1969 version_c_filename
= os
.path
.join(tmp
, "version.c")
1970 open(version_c_filename
, "w").write(version_c
)
1971 # hg uses timestamps to distinguish files which have changed, so we
1972 # force the mtime forward a little bit
1973 future
= time
.time() + 2*self
.version
1974 os
.utime(version_c_filename
, (future
, future
))
1975 w
= self
.dovc(tmp
, "commit -m revised_to_%d" % self
.version
)
1976 yield w
; w
.getResult()
1977 w
= self
.dovc(tmp
, "push %s" % self
.rep_trunk
)
1978 yield w
; w
.getResult()
1979 w
= self
.dovc(tmp
, "identify")
1980 yield w
; out
= w
.getResult()
1981 self
.addTrunkRev(self
.extract_id(out
))
1983 vc_revise
= deferredGenerator(vc_revise
)
1985 def vc_try_checkout(self
, workdir
, rev
, branch
=None):
1986 assert os
.path
.abspath(workdir
) == workdir
1987 if os
.path
.exists(workdir
):
1988 rmdirRecursive(workdir
)
1990 src
= self
.rep_branch
1992 src
= self
.rep_trunk
1993 w
= self
.dovc(self
.hg_base
, "clone %s %s" % (src
, workdir
))
1994 yield w
; w
.getResult()
1995 try_c_filename
= os
.path
.join(workdir
, "subdir", "subdir.c")
1996 open(try_c_filename
, "w").write(TRY_C
)
1997 future
= time
.time() + 2*self
.version
1998 os
.utime(try_c_filename
, (future
, future
))
1999 vc_try_checkout
= deferredGenerator(vc_try_checkout
)
2001 def vc_try_finish(self
, workdir
):
2002 rmdirRecursive(workdir
)
2005 class Mercurial(VCBase
, unittest
.TestCase
):
2008 # Mercurial has a metadir=".hg", but it does not have an 'export' mode.
2010 vctype
= "step.Mercurial"
2012 has_got_revision
= True
2014 def testCheckout(self
):
2015 self
.helper
.vcargs
= { 'repourl': self
.helper
.rep_trunk
}
2016 d
= self
.do_vctest(testRetry
=False)
2018 # TODO: testRetry has the same problem with Mercurial as it does for
2022 def testPatch(self
):
2023 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2024 'defaultBranch': "trunk" }
2028 def testCheckoutBranch(self
):
2029 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2030 'defaultBranch': "trunk" }
2031 d
= self
.do_branch()
2034 def testCheckoutHTTP(self
):
2036 repourl
= "http://localhost:%d/Mercurial-Repository/trunk/.hg" % self
.httpPort
2037 self
.helper
.vcargs
= { 'repourl': repourl
}
2038 d
= self
.do_vctest(testRetry
=False)
2040 # TODO: The easiest way to publish hg over HTTP is by running 'hg serve'
2041 # as a child process while the test is running. (you can also use a CGI
2042 # script, which sounds difficult, or you can publish the files directly,
2043 # which isn't well documented).
2044 testCheckoutHTTP
.skip
= "not yet implemented, use 'hg serve'"
2047 self
.helper
.vcargs
= { 'baseURL': self
.helper
.hg_base
+ "/",
2048 'defaultBranch': "trunk" }
2049 d
= self
.do_getpatch()
2052 VCS
.registerVC(Mercurial
.vc_name
, MercurialHelper())
2055 class Sources(unittest
.TestCase
):
2056 # TODO: this needs serious rethink
2057 def makeChange(self
, when
=None, revision
=None):
2059 when
= mktime_tz(parsedate_tz(when
))
2060 return changes
.Change("fred", [], "", when
=when
, revision
=revision
)
2063 r
= base
.BuildRequest("forced build", SourceStamp())
2065 s
= step
.CVS(cvsroot
=None, cvsmodule
=None, workdir
=None, build
=b
)
2066 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), None)
2070 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2071 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2072 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2073 r
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2074 submitted
= "Wed, 08 Sep 2004 09:04:00 -0700"
2075 r
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2077 s
= step
.CVS(cvsroot
=None, cvsmodule
=None, workdir
=None, build
=b
)
2078 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
2079 "Wed, 08 Sep 2004 16:03:00 -0000")
2083 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2084 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2085 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2086 r
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2087 submitted
= "Wed, 08 Sep 2004 09:04:00 -0700"
2088 r
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2090 s
= step
.CVS(cvsroot
=None, cvsmodule
=None, workdir
=None, build
=b
,
2092 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
2093 "Wed, 08 Sep 2004 16:02:10 -0000")
2097 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:00:00 -0700"))
2098 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:01:00 -0700"))
2099 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:02:00 -0700"))
2100 r1
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2101 submitted
= "Wed, 08 Sep 2004 09:04:00 -0700"
2102 r1
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2105 c
.append(self
.makeChange("Wed, 08 Sep 2004 09:05:00 -0700"))
2106 r2
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2107 submitted
= "Wed, 08 Sep 2004 09:07:00 -0700"
2108 r2
.submittedAt
= mktime_tz(parsedate_tz(submitted
))
2110 b
= base
.Build([r1
, r2
])
2111 s
= step
.CVS(cvsroot
=None, cvsmodule
=None, workdir
=None, build
=b
)
2112 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()),
2113 "Wed, 08 Sep 2004 16:06:00 -0000")
2116 r
= base
.BuildRequest("forced", SourceStamp())
2118 s
= step
.SVN(svnurl
="dummy", workdir
=None, build
=b
)
2119 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), None)
2123 c
.append(self
.makeChange(revision
=4))
2124 c
.append(self
.makeChange(revision
=10))
2125 c
.append(self
.makeChange(revision
=67))
2126 r
= base
.BuildRequest("forced", SourceStamp(changes
=c
))
2128 s
= step
.SVN(svnurl
="dummy", workdir
=None, build
=b
)
2129 self
.failUnlessEqual(s
.computeSourceRevision(b
.allChanges()), 67)
2131 class Patch(VCBase
, unittest
.TestCase
):
2138 def testPatch(self
):
2139 # invoke 'patch' all by itself, to see if it works the way we think
2140 # it should. This is intended to ferret out some windows test
2142 helper
= BaseHelper()
2143 self
.workdir
= os
.path
.join("test_vc", "testPatch")
2144 helper
.populate(self
.workdir
)
2145 patch
= which("patch")[0]
2147 command
= [patch
, "-p0"]
2150 def sendUpdate(self
, status
):
2152 c
= commands
.ShellCommand(FakeBuilder(), command
, self
.workdir
,
2153 sendRC
=False, stdin
=p0_diff
)
2155 d
.addCallback(self
._testPatch
_1)
2158 def _testPatch_1(self
, res
):
2159 # make sure the file actually got patched
2160 subdir_c
= os
.path
.join(self
.workdir
, "subdir", "subdir.c")
2161 data
= open(subdir_c
, "r").read()
2162 self
.failUnlessIn("Hello patched subdir.\\n", data
)