[Buildbot-commits] buildbot/buildbot/test test_p4poller.py, NONE, 1.1 test_vc.py, 1.55, 1.56
Brian Warner
warner at users.sourceforge.net
Mon Jun 12 08:36:10 UTC 2006
Update of /cvsroot/buildbot/buildbot/buildbot/test
In directory sc8-pr-cvs3.sourceforge.net:/tmp/cvs-serv16958/buildbot/test
Modified Files:
test_vc.py
Added Files:
test_p4poller.py
Log Message:
[project @ applied patch SF#1473939, initial Perforce support]
Original author: warner at lothar.com
Date: 2006-06-12 07:01:00
--- NEW FILE: test_p4poller.py ---
import sys
import time
from twisted.python import log, failure
from twisted.internet import defer
from twisted.trial import unittest
from buildbot.twcompat import maybeWait
from buildbot.changes.changes import Change
from buildbot.changes.p4poller import P4Source, get_simple_split
#log.startLogging(sys.stderr)
first_p4changes = \
"""Change 1 on 2006/04/13 by slamb at testclient 'first rev'
"""
second_p4changes = \
"""Change 3 on 2006/04/13 by bob at testclient 'short desc truncated'
Change 2 on 2006/04/13 by slamb at testclient 'bar'
"""
third_p4changes = \
"""Change 5 on 2006/04/13 by mpatel at testclient 'first rev'
"""
change_4_log = \
"""Change 4 by mpatel at testclient on 2006/04/13 21:55:39
short desc truncated because this is a long description.
"""
change_3_log = \
"""Change 3 by bob at testclient on 2006/04/13 21:51:39
short desc truncated because this is a long description.
"""
change_2_log = \
"""Change 2 by slamb at testclient on 2006/04/13 21:46:23
creation
"""
p4change = {
'3': change_3_log +
"""Affected files ...
... //depot/myproject/branch_b/branch_b_file#1 add
... //depot/myproject/branch_b/whatbranch#1 branch
... //depot/myproject/branch_c/whatbranch#1 branch
""",
'2': change_2_log +
"""Affected files ...
... //depot/myproject/trunk/whatbranch#1 add
... //depot/otherproject/trunk/something#1 add
""",
'5': change_4_log +
"""Affected files ...
... //depot/myproject/branch_b/branch_b_file#1 add
... //depot/myproject/branch_b#75 edit
... //depot/myproject/branch_c/branch_c_file#1 add
""",
}
class MockP4Source(P4Source):
"""Test P4Source which doesn't actually invoke p4."""
invocation = 0
def __init__(self, p4changes, p4change, *args, **kwargs):
P4Source.__init__(self, *args, **kwargs)
self.p4changes = p4changes
self.p4change = p4change
def _get_changes(self):
assert self.working
result = self.p4changes[self.invocation]
self.invocation += 1
return defer.succeed(result)
def _get_describe(self, dummy, num):
assert self.working
return defer.succeed(self.p4change[num])
class TestP4Poller(unittest.TestCase):
def setUp(self):
self.changes = []
self.addChange = self.changes.append
def testCheck(self):
"""successful checks"""
self.t = MockP4Source(p4changes=[first_p4changes, second_p4changes],
p4change=p4change,
p4port=None, p4user=None,
p4base='//depot/myproject/',
split_file=lambda x: x.split('/', 1))
self.t.parent = self
# The first time, it just learns the change to start at.
self.assert_(self.t.last_change is None)
self.assert_(not self.t.working)
return maybeWait(self.t.checkp4().addCallback(self._testCheck2))
def _testCheck2(self, res):
self.assertEquals(self.changes, [])
self.assertEquals(self.t.last_change, '1')
# Subsequent times, it returns Change objects for new changes.
return self.t.checkp4().addCallback(self._testCheck3)
def _testCheck3(self, res):
self.assertEquals(len(self.changes), 3)
self.assertEquals(self.t.last_change, '3')
self.assert_(not self.t.working)
# They're supposed to go oldest to newest, so this one must be first.
self.assertEquals(self.changes[0].asText(),
Change(who='slamb',
files=['whatbranch'],
comments=change_2_log,
revision='2',
when=time.mktime((2006, 4, 13, 21, 46, 23, 3, 103, -1)),
branch='trunk').asText())
# These two can happen in either order, since they're from the same
# Perforce change.
self.assertIn(
Change(who='bob',
files=['branch_b_file',
'whatbranch'],
comments=change_3_log,
revision='3',
when=time.mktime((2006, 4, 13, 21, 51, 39, 3, 103, -1)),
branch='branch_b').asText(),
[c.asText() for c in self.changes])
self.assertIn(
Change(who='bob',
files=['whatbranch'],
comments=change_3_log,
revision='3',
when=time.mktime((2006, 4, 13, 21, 51, 39, 3, 103, -1)),
branch='branch_c').asText(),
[c.asText() for c in self.changes])
def testFailedChanges(self):
"""'p4 changes' failure is properly reported"""
self.t = MockP4Source(p4changes=['Perforce client error:\n...'],
p4change={},
p4port=None, p4user=None)
self.t.parent = self
d = self.t.checkp4()
d.addBoth(self._testFailedChanges2)
return maybeWait(d)
def _testFailedChanges2(self, f):
self.assert_(isinstance(f, failure.Failure))
self.assertIn('Perforce client error', str(f))
self.assert_(not self.t.working)
def testFailedDescribe(self):
"""'p4 describe' failure is properly reported"""
c = dict(p4change)
c['3'] = 'Perforce client error:\n...'
self.t = MockP4Source(p4changes=[first_p4changes, second_p4changes],
p4change=c, p4port=None, p4user=None)
self.t.parent = self
d = self.t.checkp4()
d.addCallback(self._testFailedDescribe2)
return maybeWait(d)
def _testFailedDescribe2(self, res):
# first time finds nothing; check again.
return self.t.checkp4().addBoth(self._testFailedDescribe3)
def _testFailedDescribe3(self, f):
self.assert_(isinstance(f, failure.Failure))
self.assertIn('Perforce client error', str(f))
self.assert_(not self.t.working)
self.assertEquals(self.t.last_change, '2')
def testAlreadyWorking(self):
"""don't launch a new poll while old is still going"""
self.t = P4Source()
self.t.working = True
self.assert_(self.t.last_change is None)
d = self.t.checkp4()
d.addCallback(self._testAlreadyWorking2)
def _testAlreadyWorking2(self, res):
self.assert_(self.t.last_change is None)
def testSplitFile(self):
"""Make sure split file works on branch only changes"""
self.t = MockP4Source(p4changes=[third_p4changes],
p4change=p4change,
p4port=None, p4user=None,
p4base='//depot/myproject/',
split_file=get_simple_split)
self.t.parent = self
self.t.last_change = 50
d = self.t.checkp4()
d.addCallback(self._testSplitFile)
def _testSplitFile(self, res):
self.assertEquals(len(self.changes), 2)
self.assertEquals(self.t.last_change, '5')
Index: test_vc.py
===================================================================
RCS file: /cvsroot/buildbot/buildbot/buildbot/test/test_vc.py,v
retrieving revision 1.55
retrieving revision 1.56
diff -u -d -r1.55 -r1.56
--- test_vc.py 19 May 2006 07:44:22 -0000 1.55
+++ test_vc.py 12 Jun 2006 08:36:08 -0000 1.56
@@ -6,7 +6,35 @@
from email.Utils import mktime_tz, parsedate_tz
from twisted.trial import unittest
-from twisted.internet import defer, reactor, utils
+from twisted.internet import defer, reactor, utils, protocol, error
+try:
+ from twisted.python.procutils import which
+except ImportError:
+ # copied from Twisted circa 2.2.0
+ def which(name, flags=os.X_OK):
+ """Search PATH for executable files with the given name.
+
+ @type name: C{str}
+ @param name: The name for which to search.
+
+ @type flags: C{int}
+ @param flags: Arguments to L{os.access}.
+
+ @rtype: C{list}
+ @param: A list of the full paths to files found, in the
+ order in which they were found.
+ """
+ result = []
+ exts = filter(None, os.environ.get('PATHEXT', '').split(os.pathsep))
+ for p in os.environ['PATH'].split(os.pathsep):
+ p = os.path.join(p, name)
+ if os.access(p, flags):
+ result.append(p)
+ for e in exts:
+ pext = p + e
+ if os.access(pext, flags):
+ result.append(pext)
+ return result
#defer.Deferred.debug = True
@@ -47,6 +75,49 @@
# small web server to provide access to the repository files while the test
# is running).
+# Perforce starts the daemon running on localhost. Unfortunately, it must
+# use a predetermined Internet-domain port number, unless we want to go
+# all-out: bind the listen socket ourselves and pretend to be inetd.
+
+try:
+ import cStringIO as StringIO
+except ImportError:
+ import StringIO
+
+class _PutEverythingGetter(protocol.ProcessProtocol):
+ def __init__(self, deferred, stdin):
+ self.deferred = deferred
+ self.outBuf = StringIO.StringIO()
+ self.errBuf = StringIO.StringIO()
+ self.outReceived = self.outBuf.write
+ self.errReceived = self.errBuf.write
+ self.stdin = stdin
+
+ def connectionMade(self):
+ if self.stdin is not None:
+ self.transport.write(self.stdin)
+ self.transport.closeStdin()
+
+ def processEnded(self, reason):
+ out = self.outBuf.getvalue()
+ err = self.errBuf.getvalue()
+ e = reason.value
+ code = e.exitCode
+ if e.signal:
+ self.deferred.errback((out, err, e.signal))
+ else:
+ self.deferred.callback((out, err, code))
+
+def myGetProcessOutputAndValue(executable, args=(), env={}, path='.',
+ reactor=None, stdin=None):
+ """Like twisted.internet.utils.getProcessOutputAndValue but takes
+ stdin, too."""
+ if reactor is None:
+ from twisted.internet import reactor
+ d = defer.Deferred()
+ p = _PutEverythingGetter(d, stdin)
+ reactor.spawnProcess(p, executable, (executable,)+tuple(args), env, path)
+ return d
config_vc = """
from buildbot.process import factory, step
@@ -313,7 +384,7 @@
self.branch.append(rev)
self.allrevs.append(rev)
- def runCommand(self, basedir, command, failureIsOk=False):
+ def runCommand(self, basedir, command, failureIsOk=False, stdin=None):
# all commands passed to do() should be strings or lists. If they are
# strings, none of the arguments may have spaces. This makes the
# commands less verbose at the expense of restricting what they can
@@ -323,8 +394,9 @@
#print "do %s" % command
env = os.environ.copy()
env['LC_ALL'] = "C"
- d = utils.getProcessOutputAndValue(command[0], command[1:],
- env=env, path=basedir)
+ d = myGetProcessOutputAndValue(command[0], command[1:],
+ env=env, path=basedir,
+ stdin=stdin)
def check((out, err, code)):
#print
#print "command: %s" % command
@@ -342,14 +414,15 @@
d.addCallback(check)
return d
- def do(self, basedir, command, failureIsOk=False):
- d = self.runCommand(basedir, command, failureIsOk=failureIsOk)
+ def do(self, basedir, command, failureIsOk=False, stdin=None):
+ d = self.runCommand(basedir, command, failureIsOk=failureIsOk,
+ stdin=stdin)
return waitForDeferred(d)
- def dovc(self, basedir, command, failureIsOk=False):
+ def dovc(self, basedir, command, failureIsOk=False, stdin=None):
"""Like do(), but the VC binary will be prepended to COMMAND."""
command = self.vcexe + " " + command
- return self.do(basedir, command, failureIsOk)
+ return self.do(basedir, command, failureIsOk, stdin)
class VCBase(SignalMixin):
metadir = None
@@ -1315,6 +1388,163 @@
VCS.registerVC(SVN.vc_name, SVNHelper())
+
+class P4Support(VCBase):
+ metadir = None
+ branchname = "branch"
+ vctype = "step.P4"
+ p4port = 'localhost:1666'
+ pid = None
+ base_descr = 'Change: new\nDescription: asdf\nFiles:\n'
+
+ class _P4DProtocol(protocol.ProcessProtocol):
+ def __init__(self):
+ self.started = defer.Deferred()
+ self.ended = defer.Deferred()
+
+ def outReceived(self, data):
+ # When it says starting, it has bound to the socket.
+ if self.started:
+ if data.startswith('Perforce Server starting...'):
+ self.started.callback(None)
+ else:
+ try:
+ raise Exception('p4d said %r' % data)
+ except:
+ self.started.errback(failure.Failure())
+ self.started = None
+
+ def processEnded(self, status_object):
+ if status_object.check(error.ProcessDone):
+ self.ended.callback(None)
+ else:
+ self.ended.errback(status_object)
+
+ def _start_p4d(self):
+ proto = self._P4DProtocol()
+ reactor.spawnProcess(proto, self.p4dexe, ['p4d', '-p', self.p4port],
+ env=os.environ, path=self.p4rep)
+ return proto.started, proto.ended
+
+ def capable(self):
+ global VCS
+ if not VCS.has_key("p4"):
+ VCS["p4"] = False
+ p4paths = which('p4')
+ p4dpaths = which('p4d')
+ if p4paths and p4dpaths:
+ self.vcexe = p4paths[0]
+ self.p4dexe = p4dpaths[0]
+ VCS["p4"] = True
+ if not VCS["p4"]:
+ raise unittest.SkipTest("No usable Perforce was found")
+
+ def vc_create(self):
+ tmp = os.path.join(self.repbase, "p4tmp")
+ self.p4rep = os.path.join(self.repbase, 'P4-Repository')
+ os.mkdir(self.p4rep)
+
+ # Launch p4d.
+ started, self.p4d_shutdown = self._start_p4d()
+ w = waitForDeferred(started)
+ yield w; w.getResult()
+
+ # Create client spec.
+ os.mkdir(tmp)
+ clispec = 'Client: creator\n'
+ clispec += 'Root: %s\n' % tmp
+ clispec += 'View:\n'
+ clispec += '\t//depot/... //creator/...\n'
+ w = self.dovc(tmp, '-p %s client -i' % self.p4port, stdin=clispec)
+ yield w; w.getResult()
+
+ # Create first rev (trunk).
+ self.populate(os.path.join(tmp, 'trunk'))
+ files = ['main.c', 'version.c', 'subdir/subdir.c']
+ w = self.do(tmp, ['sh', '-c',
+ "p4 -p %s -c creator add " % self.p4port
+ + " ".join(['trunk/%s' % f for f in files])])
+ yield w; w.getResult()
+ descr = self.base_descr
+ for file in files:
+ descr += '\t//depot/trunk/%s\n' % file
+ w = self.dovc(tmp, "-p %s -c creator submit -i" % self.p4port,
+ stdin=descr)
+ yield w; out = w.getResult()
+ m = re.search(r'Change (\d+) submitted.', out)
+ assert m.group(1) == '1'
+ self.addTrunkRev(m.group(1))
+
+ # Create second rev (branch).
+ w = self.dovc(tmp, '-p %s -c creator integrate ' % self.p4port
+ + '//depot/trunk/... //depot/branch/...')
+ yield w; w.getResult()
+ w = self.do(tmp, ['sh', '-c',
+ "p4 -p %s -c creator edit branch/main.c"
+ % self.p4port])
+ yield w; w.getResult()
+ self.populate_branch(os.path.join(tmp, 'branch'))
+ descr = self.base_descr
+ for file in files:
+ descr += '\t//depot/branch/%s\n' % file
+ w = self.dovc(tmp, "-p %s -c creator submit -i" % self.p4port,
+ stdin=descr)
+ yield w; out = w.getResult()
+ m = re.search(r'Change (\d+) submitted.', out)
+ self.addBranchRev(m.group(1))
+ vc_create = deferredGenerator(vc_create)
+
+ def vc_revise(self):
+ tmp = os.path.join(self.repbase, "p4tmp")
+ self.version += 1
+ version_c = VERSION_C % self.version
+ w = self.do(tmp, ['sh', '-c',
+ 'p4 -p %s -c creator edit trunk/version.c'
+ % self.p4port])
+ yield w; w.getResult()
+ open(os.path.join(tmp, "trunk/version.c"), "w").write(version_c)
+ descr = self.base_descr + '\t//depot/trunk/version.c\n'
+ w = self.dovc(tmp, "-p %s -c creator submit -i" % self.p4port,
+ stdin=descr)
+ yield w; out = w.getResult()
+ m = re.search(r'Change (\d+) submitted.', out)
+ self.addTrunkRev(m.group(1))
+ vc_revise = deferredGenerator(vc_revise)
+
+ def setUp2(self, res):
+ if self.p4d_shutdown is None:
+ started, self.p4d_shutdown = self._start_p4d()
+ return started
+
+ def tearDown2(self):
+ self.p4d_shutdown = None
+ d = self.runCommand(self.repbase, '%s -p %s admin stop'
+ % (self.vcexe, self.p4port))
+ return d.addCallback(lambda _: self.p4d_shutdown)
+
+class P4(P4Support, unittest.TestCase):
+
+ def testCheckout(self):
+ self.vcargs = { 'p4port': self.p4port, 'p4base': '//depot/',
+ 'defaultBranch': 'trunk' }
+ d = self.do_vctest(testRetry=False)
+ # TODO: like arch and darcs, sync does nothing when server is not
+ # changed.
+ return maybeWait(d)
+
+ def testPatch(self):
+ self.vcargs = { 'p4port': self.p4port, 'p4base': '//depot/',
+ 'defaultBranch': 'trunk' }
+ d = self.do_patch()
+ return maybeWait(d)
+
+ def testBranch(self):
+ self.vcargs = { 'p4port': self.p4port, 'p4base': '//depot/',
+ 'defaultBranch': 'trunk' }
+ d = self.do_branch()
+ return maybeWait(d)
+
+
class DarcsHelper(BaseHelper):
branchname = "branch"
try_branchname = "branch"
More information about the Commits
mailing list