[Buildbot-commits] buildbot/buildbot twcompat.py,1.1,1.2
Brian Warner
warner at users.sourceforge.net
Tue Jul 19 01:55:23 UTC 2005
Update of /cvsroot/buildbot/buildbot/buildbot
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv10106/buildbot
Modified Files:
twcompat.py
Log Message:
Revision: arch at buildbot.sf.net--2004/buildbot--dev--0--patch-234
Creator: Brian Warner <warner at monolith.lothar.com>
overhaul ShellCommand timeout/interrupt/cleanup, add tests
* buildbot/slave/commands.py (ShellCommand): overhaul
error-handling code, to try and make timeout/interrupt work
properly, and make win32 happier
* buildbot/test/test_slavecommand.py: clean up, stop using
reactor.iterate, add tests for timeout and interrupt
* buildbot/test/sleep.py: utility for a new timeout test
* buildbot/twcompat.py: copy over twisted 1.3/2.0 compatibility
code from the local-usebranches branch
Index: twcompat.py
===================================================================
RCS file: /cvsroot/buildbot/buildbot/buildbot/twcompat.py,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- twcompat.py 17 May 2005 10:14:10 -0000 1.1
+++ twcompat.py 19 Jul 2005 01:55:21 -0000 1.2
@@ -1,16 +1,18 @@
-from twisted.python import components
+if 0:
+ print "hey python-mode, stop thinking I want 8-char indentation"
"""
utilities to be compatible with both Twisted-1.3 and 2.0
-implements. Use this like:
- from buildbot.tcompat import implements
- class Foo:
- if implements:
- implements(IFoo)
- else:
- __implements__ = IFoo,
+implements. Use this like the following.
+
+from buildbot.tcompat import implements
+class Foo:
+ if implements:
+ implements(IFoo)
+ else:
+ __implements__ = IFoo,
Interface:
from buildbot.tcompat import Interface
@@ -21,6 +23,9 @@
assert providedBy(obj, IFoo)
"""
+from twisted.copyright import version
+from twisted.python import components
+
# does our Twisted use zope.interface?
if hasattr(components, "interface"):
# yes
@@ -33,3 +38,208 @@
implements = None
from twisted.python.components import Interface
providedBy = components.implements
+
+# are we using a version of Trial that allows setUp/testFoo/tearDown to
+# return Deferreds?
+oldtrial = version.startswith("1.3")
+
+# use this at the end of setUp/testFoo/tearDown methods
+def maybeWait(d, timeout="none"):
+ from twisted.trial import unittest
+ if oldtrial:
+ # this is required for oldtrial (twisted-1.3.0) compatibility. When we
+ # move to retrial (twisted-2.0.0), replace these with a simple 'return
+ # d'.
+ if timeout == "none":
+ unittest.deferredResult(d)
+ else:
+ unittest.deferredResult(d, timeout)
+ return None
+ return d
+
+# waitForDeferred and getProcessOutputAndValue are twisted-2.0 things. If
+# we're running under 1.3, patch them into place. These versions are copied
+# from twisted somewhat after 2.0.1 .
+
+from twisted.internet import defer
+if not hasattr(defer, 'waitForDeferred'):
+ Deferred = defer.Deferred
+ class waitForDeferred:
+ """
+ API Stability: semi-stable
+
+ Maintainer: U{Christopher Armstrong<mailto:radix at twistedmatrix.com>}
+
+ waitForDeferred and deferredGenerator help you write
+ Deferred-using code that looks like it's blocking (but isn't
+ really), with the help of generators.
+
+ There are two important functions involved: waitForDeferred, and
+ deferredGenerator.
+
+ def thingummy():
+ thing = waitForDeferred(makeSomeRequestResultingInDeferred())
+ yield thing
+ thing = thing.getResult()
+ print thing #the result! hoorj!
+ thingummy = deferredGenerator(thingummy)
+
+ waitForDeferred returns something that you should immediately yield;
+ when your generator is resumed, calling thing.getResult() will either
+ give you the result of the Deferred if it was a success, or raise an
+ exception if it was a failure.
+
+ deferredGenerator takes one of these waitForDeferred-using
+ generator functions and converts it into a function that returns a
+ Deferred. The result of the Deferred will be the last
+ value that your generator yielded (remember that 'return result' won't
+ work; use 'yield result; return' in place of that).
+
+ Note that not yielding anything from your generator will make the
+ Deferred result in None. Yielding a Deferred from your generator
+ is also an error condition; always yield waitForDeferred(d)
+ instead.
+
+ The Deferred returned from your deferred generator may also
+ errback if your generator raised an exception.
+
+ def thingummy():
+ thing = waitForDeferred(makeSomeRequestResultingInDeferred())
+ yield thing
+ thing = thing.getResult()
+ if thing == 'I love Twisted':
+ # will become the result of the Deferred
+ yield 'TWISTED IS GREAT!'
+ return
+ else:
+ # will trigger an errback
+ raise Exception('DESTROY ALL LIFE')
+ thingummy = deferredGenerator(thingummy)
+
+ Put succinctly, these functions connect deferred-using code with this
+ 'fake blocking' style in both directions: waitForDeferred converts from
+ a Deferred to the 'blocking' style, and deferredGenerator converts from
+ the 'blocking' style to a Deferred.
+ """
+ def __init__(self, d):
+ if not isinstance(d, Deferred):
+ raise TypeError("You must give waitForDeferred a Deferred. You gave it %r." % (d,))
+ self.d = d
+
+ def getResult(self):
+ if hasattr(self, 'failure'):
+ self.failure.raiseException()
+ return self.result
+
+ def _deferGenerator(g, deferred=None, result=None):
+ """
+ See L{waitForDeferred}.
+ """
+ while 1:
+ if deferred is None:
+ deferred = defer.Deferred()
+ try:
+ result = g.next()
+ except StopIteration:
+ deferred.callback(result)
+ return deferred
+ except:
+ deferred.errback()
+ return deferred
+
+ # Deferred.callback(Deferred) raises an error; we catch this case
+ # early here and give a nicer error message to the user in case
+ # they yield a Deferred. Perhaps eventually these semantics may
+ # change.
+ if isinstance(result, defer.Deferred):
+ return fail(TypeError("Yield waitForDeferred(d), not d!"))
+
+ if isinstance(result, waitForDeferred):
+ waiting=[True, None]
+ # Pass vars in so they don't get changed going around the loop
+ def gotResult(r, waiting=waiting, result=result):
+ result.result = r
+ if waiting[0]:
+ waiting[0] = False
+ waiting[1] = r
+ else:
+ _deferGenerator(g, deferred, r)
+ def gotError(f, waiting=waiting, result=result):
+ result.failure = f
+ if waiting[0]:
+ waiting[0] = False
+ waiting[1] = f
+ else:
+ _deferGenerator(g, deferred, f)
+ result.d.addCallbacks(gotResult, gotError)
+ if waiting[0]:
+ # Haven't called back yet, set flag so that we get reinvoked
+ # and return from the loop
+ waiting[0] = False
+ return deferred
+ else:
+ result = waiting[1]
+
+ def func_metamerge(f, g):
+ """
+ Merge function metadata from f -> g and return g
+ """
+ try:
+ g.__doc__ = f.__doc__
+ g.__dict__.update(f.__dict__)
+ g.__name__ = f.__name__
+ except (TypeError, AttributeError):
+ pass
+ return g
+
+ def deferredGenerator(f):
+ """
+ See L{waitForDeferred}.
+ """
+ def unwindGenerator(*args, **kwargs):
+ return _deferGenerator(f(*args, **kwargs))
+ return func_metamerge(f, unwindGenerator)
+
+ defer.waitForDeferred = waitForDeferred
+ defer.deferredGenerator = deferredGenerator
+
+from twisted.internet import utils
+if not hasattr(utils, "getProcessOutputAndValue"):
+ from twisted.internet import reactor, protocol
+ _callProtocolWithDeferred = utils._callProtocolWithDeferred
+ try:
+ import cStringIO as StringIO
+ except ImportError:
+ import StringIO
+
+ class _EverythingGetter(protocol.ProcessProtocol):
+
+ def __init__(self, deferred):
+ self.deferred = deferred
+ self.outBuf = StringIO.StringIO()
+ self.errBuf = StringIO.StringIO()
+ self.outReceived = self.outBuf.write
+ self.errReceived = self.errBuf.write
+
+ 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 getProcessOutputAndValue(executable, args=(), env={}, path='.',
+ reactor=reactor):
+ """Spawn a process and returns a Deferred that will be called back
+ with its output (from stdout and stderr) and it's exit code as (out,
+ err, code) If a signal is raised, the Deferred will errback with the
+ stdout and stderr up to that point, along with the signal, as (out,
+ err, signalNum)
+ """
+ return _callProtocolWithDeferred(_EverythingGetter,
+ executable, args, env, path,
+ reactor)
+ utils.getProcessOutputAndValue = getProcessOutputAndValue
More information about the Commits
mailing list