update to foolscap-0.1.4

This commit is contained in:
Brian Warner 2007-05-15 17:33:52 -07:00
parent 985925d70c
commit 267a068fe4
30 changed files with 851 additions and 364 deletions

View File

@ -1,3 +1,112 @@
2007-05-14 Brian Warner <warner@lothar.com>
* foolscap/__init__.py: release Foolscap-0.1.4
* misc/{sid|sarge|dapper|edgy|feisty}/debian/changelog: same, also
remove a bunch of old between-release version numbers
2007-05-14 Brian Warner <warner@lothar.com>
* NEWS: update for the upcoming release
* doc/using-foolscap.xhtml: rename from doc/using-pb.xhtml
* doc/using-pb.xhtml: replace all uses of 'PB URL' with 'FURL'
* foolscap/pb.py (Tub.getReference): if getReference() is called
before Tub.startService(), queue the request until startup.
(Tub.connectTo): same for connectTo().
(Tub.startService): launch pending getReference() and connectTo()
requests. There are all fired with eventual-sends.
* foolscap/reconnector.py (Reconnector): don't automatically start
the Reconnector in __init__, rather wait for the Tub to start it.
* foolscap/test/test_tub.py (QueuedStartup): test it
* doc/using-pb.xhtml: update docs to match
* foolscap/test/test_call.py (TestCall.testCall1): replace an
arbitrary delay with a polling loop, to make the test more
reliable under load
* foolscap/referenceable.py (SturdyRef.asLiveRef): remove a method
that was never used, didn't work, and is of dubious utility
anyways.
(_AsLiveRef): remove this too
* misc/testutils/figleaf.py (CodeTracer.start): remove leftover
debug logging
* foolscap/remoteinterface.py (RemoteInterfaceConstraint): accept
gifts too: allow sending of RemoteReferences on the outbound side,
and accept their-reference sequences on the inbound side.
* foolscap/test/test_gifts.py (Gifts.test_constraint): test it
* foolscap/test/test_schema.py (Interfaces.test_remotereference):
update test, since now we allow RemoteReferences to be sent on the
outbound side
* foolscap/remoteinterface.py (getRemoteInterface): improve the
error message reported when a Referenceable class implements
multiple RemoteInterfaces
* foolscap/remoteinterface.py (RemoteMethodSchema.initFromMethod):
properly handle methods like 'def foo(nodefault)' that are missing
*all* default values. Previously this resulted in an unhelpful
exception (since typeList==None), now it gives a sensible
InvalidRemoteInterface exception.
* foolscap/test/test_schema.py (Arguments.test_bad_arguments):
test it
2007-05-11 Brian Warner <warner@lothar.com>
* foolscap/slicers/set.py (FrozenSetSlicer): finally acknowledge
our dependence on python2.4 or newer, by using the built-in 'set'
and 'frozenset' types by default. We'll serialize the old sets.Set
and sets.ImmutableSet too, but they'll emerge as a set/frozenset.
This will cause problems for code that was written to be
compatible with python2.3 (by using sets.Set) and wasn't changed
when moved to 2.4, if it tries to mingle sets.Set with the data
coming out of Foolscap. Unfortunate, but doing it this way
preserves both sanity and good behavior for modern 2.4-or-later
apps.
(SetUnslicer): fix handling of children that were unreferenceable
during construction, fix handling of children that are not ready
for use (i.e. gifts).
(FrozenSetUnslicer): base this off of TupleUnslicer, since
previously the cycle-handling logic was completely broken. I'm not
entirely sure this is necessary, since I think the contents of
sets must be transitively immutable (or at least transitively
hashable), but it good to review and clean it up anyways.
* foolscap/slicers/allslicers.py: match name change
* foolscap/slicers/tuple.py (TupleUnslicer.receiveClose): fix
handling of unready children (i.e. gifts), previously gifts inside
containers were completely broken.
* foolscap/slicers/list.py (ListUnslicer.receiveClose): same
* foolscap/slicers/dict.py (DictUnslicer.receiveClose): same
* foolscap/call.py: add debug log messages (disabled)
* foolscap/referenceable.py (TheirReferenceUnslicer.receiveClose):
gifts must declare themselves 'unready' until the RemoteReference
resolves, since we might be inside a container of some sort.
Without this fix, methods would be invoked too early, before the
RemoteReference was really available.
* foolscap/test/test_banana.py (ThereAndBackAgain.test_set): match
new set/sets.Set behavior
(ThereAndBackAgain.test_cycles_1): test some of the cycles
(ThereAndBackAgain.test_cycles_3): add (disabled) test for
checking cycles that involve sets. I think these tests are
non-sensical, since sets can't really participate in the sorts of
cycles we worry about, but I left the (disabled) test code in
place in case it becomes useful again.
* foolscap/test/test_gifts.py (Gifts.testContainers): validate
that gifts can appear in all sorts of containers successfully.
2007-05-11 Brian Warner <warner@lothar.com.com>
* foolscap/__init__.py: bump revision to 0.1.3+ while between releases
* misc/{sid|sarge|dapper|edgy|feisty}/debian/changelog: same
2007-05-02 Brian Warner <warner@lothar.com>
* foolscap/__init__.py: release Foolscap-0.1.3

View File

@ -1,5 +1,69 @@
User visible changes in Foolscap (aka newpb/pb2). -*- outline -*-
* Release 0.1.4 (14 May 2007)
** Compatibility
This release is fully compatible with 0.1.3 .
** getReference/connectTo can be called before Tub.startService()
The Tub.startService changes that were suggested in the 0.1.3 release notes
have been implemented. Calling getReference() or connectTo() before the Tub
has been started is now allowed, however no action will take place until the
Tub is running. Don't forget to start the Tub, or you'll be left wondering
why your Deferred or callback is never fired. (A log message is emitted when
these calls are made before the Tub is started, in the hopes of helping
developers find this mistake faster).
** constraint improvements
The RIFoo -style constraint now accepts gifts (third-party references). This
also means that using RIFoo on the outbound side will accept either a
Referenceable that implements the given RemoteInterface or a RemoteReference
that points to a Referenceable that implements the given RemoteInterface.
There is a situation (sending a RemoteReference back to its owner) that will
pass the outbound constraint but be rejected by the inbound constraint on the
other end. It remains to be seen how this will be fixed.
** foolscap now deserializes into python2.4-native 'set' and 'frozenset' types
Since Foolscap is dependent upon python2.4 or newer anyways, it now
unconditionally creates built-in 'set' and 'frozenset' instances when
deserializing 'set'/'immutable-set' banana sequences. The pre-python2.4
'sets' module has non-built-in set classes named sets.Set and
sets.ImmutableSet, and these are serialized just like the built-in forms.
Unfortunately this means that Set and ImmutableSet will not survive a
round-trip: they'll be turned into set and frozenset, respectively. Worse
yet, 'set' and 'sets.Set' are not entirely compatible. This may cause a
problem for older applications that were written to be compatible with both
python-2.3 and python-2.4 (by using sets.Set/sets.ImmutableSet), for which
the compatibility code is still in place (i.e. they are not using
set/frozenset). These applications may experience problems when set objects
that traverse the wire via Foolscap are brought into close proximity with set
objects that remained local. This is unfortunate, but it's the cleanest way
to support modern applications that use the native types exclusively.
** bug fixes
Gifts inside containers (lists, tuples, dicts, sets) were broken: the target
method was frequently invoked before the gift had properly resolved into a
RemoteReference. Constraints involving gifts inside containers were broken
too. The constraints may be too loose right now, but I don't think they
should cause false negatives.
The unused SturdyRef.asLiveRef method was removed, since it didn't work
anyways.
** terminology shift: FURL
The preferred name for the sort of URL that you get back from
registerReference (and hand to getReference or connectTo) has changed from
"PB URL" to "FURL" (short for Foolscap URL). They still start with 'pb:',
however. Documentation is slowly being changed to use this term.
* Release 0.1.3 (02 May 2007)
** Incompatibility Warning

View File

@ -101,7 +101,8 @@ d.addCallbacks(gotAnswer, gotError)
<p>Ok, now how do you acquire that <code>RemoteReference</code>? How do you
make the <code>Referenceable</code> available to the outside world? For this,
we'll need to discuss the <q>Tub</q>, and the concept of a <q>PB URL</q>.</p>
we'll need to discuss the <q>Tub</q>, and the concept of a <q>FURL
URL</q>.</p>
<h2>Tubs: The Foolscap Service</h2>
@ -128,13 +129,13 @@ established since last startup. If you have no parent to attach it to, you
can use <code>startService</code> and <code>stopService</code> on the Tub
directly.</p>
<p>Note that you must start the Tub before calling <code>getReference</code>
or <code>connectTo</code>, since both of these trigger network activity, and
Tubs are supposed to be silent until they are started. In a future release
this requirement may be relaxed, but the principle of "no network activity
until the Tub is started" will be maintained, probably by queueing the
<code>getReference</code> calls and handling them after the Tub been
started.</p>
<p>Note that no network activity will occur until the Tub's
<code>startService</code> method has been called. This means that any
<code>getReference</code> or <code>connectTo</code> requests that occur
before the Tub is started will be deferred until startup. If the program
forgets to start the Tub, these requests will never be serviced. A message to
this effect is added to the twistd.log file to help developers discover this
kind of problem.</p>
<h3>Making your Tub remotely accessible</h3>
@ -147,7 +148,7 @@ port is accessibly to the outside world.</p>
creating an SSL private key certificate and hashing it into a suitably-long
random-looking string. This is the primary identifier of the Tub: everything
else is just a <em>location hint</em> that suggests how the Tub might be
reached. The fact that the TubID is tied to the private key allows PB URLs to
reached. The fact that the TubID is tied to the private key allows FURLs to
be <q>secure</q> references (meaning that no third party can cause you to
connect to the wrong reference). You can also create a Tub with a
pre-existing certificate, which is how Tubs can retain a persistent identity
@ -156,10 +157,10 @@ over multiple executions.</p>
<p>You can also create an <code>UnauthenticatedTub</code>, which has an empty
TubID. Hosting and connecting to unauthenticated Tubs do not require the
pyOpenSSL library, but do not provide privacy, authentication, connection
redirection, or shared listening ports. The PB-URLs that point to
redirection, or shared listening ports. The FURLs that point to
unauthenticated Tubs have a distinct form (starting with <code>pbu:</code>
instead of <code>pb:</code>) to make sure they are not mistaken for
authenticated Tubs. PB uses authenticated Tubs by default.</p>
authenticated Tubs. Foolscap uses authenticated Tubs by default.</p>
<p>Having the Tub listen on a TCP port is as simple as calling <code
class="API" base="foolscap.pb.Tub">listenOn</code> with a <code class="API"
@ -196,7 +197,7 @@ registering it:</p>
url = tub.registerReference(myserver, "math-service")
</pre>
<p>This returns the <q>PB URL</q> for your <code>Referenceable</code>. Remote
<p>This returns the <q>FURL</q> for your <code>Referenceable</code>. Remote
systems will use this URL to access your newly-published object. The
registration just maps a per-Tub name to the <code>Referenceable</code>:
technically the same <code>Referenceable</code> could be published multiple
@ -279,6 +280,25 @@ d.addCallbacks(gotReference, gotError)
connection, if one is available, and it will return an existing
<code>RemoteReference</code>, it one has already been acquired.</p>
<p>Since <code>getReference</code> requests are queued until the Tub starts,
the following will work too. But don't forget to call
<code>tub.startService()</code> eventually, otherwise your program will hang
forever.</p>
<pre class="python">
from foolscap import Tub
tub = Tub()
d = tub.getReference("pb://ABCD@myhost.example.com:12345/math-service")
def gotReference(remote):
print "Got the RemoteReference:", remote
def gotError(err):
print "error:", err
d.addCallbacks(gotReference, gotError)
tub.startService()
</pre>
<h3>Complete example</h3>
<p>Here are two programs, one implementing the server side of our
@ -333,7 +353,7 @@ the answer is 3
</pre>
<h3>PB URLs</h3>
<h3>FURLs</h3>
<p>In Foolscap, each world-accessible Referenceable has one or more URLs
which are <q>secure</q>, where we use the capability-security definition of
@ -350,9 +370,9 @@ the term, meaning those URLs have the following properties:</p>
will be connected to will be the right one.</li>
</ul>
<p>To accomplish the first goal, PB URLs must be unguessable. You can
register the reference with a human-readable name if your intention is to
make it available to the world, but in general you will let
<p>To accomplish the first goal, FURLs must be unguessable. You can register
the reference with a human-readable name if your intention is to make it
available to the world, but in general you will let
<code>tub.registerReference</code> generate a random name for you, preserving
the unguessability property.</p>
@ -367,9 +387,9 @@ you to mistakenly connect to the wrong target.</p>
<p>Obviously this second property only holds if you use SSL. If you choose to
use unauthenticated Tubs, all security properties are lost.</p>
<p>The format of a PB URL, like
<p>The format of a FURL, like
<code>pb://abcd123@example.com:5901,backup.example.com:8800/math-server</code>,
is as follows<span class="footnote">note that the PB URL uses the same format
is as follows<span class="footnote">note that the FURL uses the same format
as an <a href="http://www.waterken.com/dev/YURL/httpsy/">HTTPSY</a>
URL</span>:</p>
@ -402,7 +422,7 @@ URL</span>:</p>
the format <code>pb://ABCD@unix/path/to/socket/NAME</code>, but this needs
some work)</p>
<p>PB URLs for unauthenticated Tubs, like
<p>FURLs for unauthenticated Tubs, like
<code>pbu://example.com:8700/math-server</code>, are formatted as
follows:</p>
@ -470,13 +490,13 @@ therefore use <code>twistd</code> to launch the program. The User side is
written with the same <code>reactor.run()</code> style as the earlier
example.</p>
<p>The server registers the Calculator instance and prints the PBURL at which
it is listening. You need to pass this PBURL to the client program so it
knows how to contact the servre. If you have a modern version of Twisted (2.5
or later) and the right encryption libraries installed, you'll get an
authenticated Tub (for which the PBURL will start with "pb:" and will be
<p>The server registers the Calculator instance and prints the FURL at which
it is listening. You need to pass this FURL to the client program so it knows
how to contact the servre. If you have a modern version of Twisted (2.5 or
later) and the right encryption libraries installed, you'll get an
authenticated Tub (for which the FURL will start with "pb:" and will be
fairly long). If you don't, you'll get an unauthenticated Tub (with a
relatively short PBURL that starts with "pbu:").</p>
relatively short FURL that starts with "pbu:").</p>
<a href="listings/pb3calculator.py" class="py-listing"
skipLines="2">pb3calculator.py</a>
@ -766,7 +786,7 @@ suitably unique string (like a URI).</p>
from foolscap import RemoteInterface, schema
class RIMath(RemoteInterface):
__remote_name__ = "RIMath.using-pb.docs.foolscap.twistedmatrix.com"
__remote_name__ = "RIMath.using-foolscap.docs.foolscap.twistedmatrix.com"
def add(a=int, b=int):
return int
# declare it with an attribute instead of a function definition
@ -883,7 +903,7 @@ method is invoked), he will discover that he's holding a fully-functional
class="footnote">and if everyone involved is using authenticated Tubs, then
Foolscap offers a guarantee, in the cryptographic sense, that Bob will wind
up with a reference to the same object that Alice intended. The authenticated
PBURLs prevent DNS-spoofing and man-in-the-middle attacks.</span>. He can
FURLs prevent DNS-spoofing and man-in-the-middle attacks.</span>. He can
start using this RemoteReference right away:</p>
<pre class="python">

View File

@ -1,6 +1,6 @@
"""Foolscap"""
__version__ = "0.1.3"
__version__ = "0.1.4"
# here are the primary entry points
from foolscap.pb import Tub, UnauthenticatedTub, getRemoteURL_TCP

View File

@ -197,11 +197,14 @@ class InboundDelivery:
class ArgumentUnslicer(slicer.ScopedUnslicer):
methodSchema = None
debug = False
def setConstraint(self, methodSchema):
self.methodSchema = methodSchema
def start(self, count):
if self.debug:
log.msg("%s.start: %s" % (self, count))
self.numargs = None
self.args = []
self.kwargs = {}
@ -242,6 +245,11 @@ class ArgumentUnslicer(slicer.ScopedUnslicer):
return unslicer
def receiveChild(self, token, ready_deferred=None):
if self.debug:
log.msg("%s.receiveChild: %s %s %s %s %s args=%s kwargs=%s" %
(self, self.closed, self.num_unreferenceable_children,
self.num_unready_children, token, ready_deferred,
self.args, self.kwargs))
if self.numargs is None:
# this token is the number of positional arguments
assert isinstance(token, int)
@ -261,10 +269,14 @@ class ArgumentUnslicer(slicer.ScopedUnslicer):
argpos = len(self.args)
self.args.append(argvalue)
if isinstance(argvalue, defer.Deferred):
# this may occur if the child is a gift which has not
# resolved yet.
self.num_unreferenceable_children += 1
argvalue.addCallback(self.updateChild, argpos)
argvalue.addErrback(self.explode)
if ready_deferred:
if self.debug:
log.msg("%s.receiveChild got an unready posarg" % self)
self.num_unready_children += 1
ready_deferred.addCallback(self.childReady)
if len(self.args) < self.numargs:
@ -298,6 +310,8 @@ class ArgumentUnslicer(slicer.ScopedUnslicer):
argvalue.addCallback(self.updateChild, self.argname)
argvalue.addErrback(self.explode)
if ready_deferred:
if self.debug:
log.msg("%s.receiveChild got an unready kwarg" % self)
self.num_unready_children += 1
ready_deferred.addCallback(self.childReady)
self.argname = None
@ -311,6 +325,9 @@ class ArgumentUnslicer(slicer.ScopedUnslicer):
# RemoteReference, but for now all we get is a Deferred as a
# placeholder.
if self.debug:
log.msg("%s.updateChild, [%s] became referenceable: %s" %
(self, which, obj))
if isinstance(which, int):
self.args[which] = obj
else:
@ -321,12 +338,22 @@ class ArgumentUnslicer(slicer.ScopedUnslicer):
def childReady(self, obj):
self.num_unready_children -= 1
if self.debug:
log.msg("%s.childReady, now %d left" %
(self, self.num_unready_children))
log.msg(" obj=%s, args=%s, kwargs=%s" %
(obj, self.args, self.kwargs))
self.checkComplete()
return obj
def checkComplete(self):
# this is called each time one of our children gets updated or
# becomes ready (like when a Gift is finally resolved)
if self.debug:
log.msg("%s.checkComplete: %s %s %s args=%s kwargs=%s" %
(self, self.closed, self.num_unreferenceable_children,
self.num_unready_children, self.args, self.kwargs))
if not self.closed:
return
if self.num_unreferenceable_children:
@ -334,17 +361,25 @@ class ArgumentUnslicer(slicer.ScopedUnslicer):
if self.num_unready_children:
return
# yup, we're done. Notify anyone who is still waiting
if self.debug:
log.msg(" we are ready")
for d in self.watchers:
eventually(d.callback, self)
del self.watchers
def receiveClose(self):
if self.debug:
log.msg("%s.receiveClose: %s %s %s" %
(self, self.closed, self.num_unreferenceable_children,
self.num_unready_children))
if (self.numargs is None or
len(self.args) < self.numargs or
self.argname is not None):
raise BananaError("'arguments' sequence ended too early")
self.closed = True
self.watchers = []
# we don't return a ready_deferred. Instead, the InboundDelivery
# object queries our isReady() method directly.
return self, None
def isReady(self):
@ -385,6 +420,8 @@ class ArgumentUnslicer(slicer.ScopedUnslicer):
class CallUnslicer(slicer.ScopedUnslicer):
debug = False
def start(self, count):
# start=0:reqID, 1:objID, 2:methodname, 3: arguments
self.stage = 0
@ -436,7 +473,9 @@ class CallUnslicer(slicer.ScopedUnslicer):
def receiveChild(self, token, ready_deferred=None):
assert not isinstance(token, defer.Deferred)
assert ready_deferred is None
#print "CallUnslicer.receiveChild [s%d]" % self.stage, repr(token)
if self.debug:
log.msg("%s.receiveChild [s%d]: %s" %
(self, self.stage, repr(token)))
if self.stage == 0: # reqID
# we don't yet know which reqID to send any failure to

View File

@ -6,7 +6,7 @@ from twisted.internet import defer, protocol
from twisted.application import service, strports
from twisted.python import log
from foolscap import ipb, base32, negotiate, broker, observer
from foolscap import ipb, base32, negotiate, broker, observer, eventual
from foolscap.referenceable import SturdyRef
from foolscap.tokens import PBError, BananaError
from foolscap.reconnector import Reconnector
@ -242,6 +242,8 @@ class Tub(service.MultiService):
self._activeConnectors = []
self._allConnectorsAreFinished = observer.OneShotObserverList()
self._pending_getReferences = [] # list of (d, furl) pairs
def setOption(self, name, value):
if name == "logLocalFailures":
# log (with log.err) any exceptions that occur during the
@ -376,6 +378,15 @@ class Tub(service.MultiService):
if not self.running and not self._activeConnectors:
self._allConnectorsAreFinished.fire(self)
def startService(self):
service.MultiService.startService(self)
for d,sturdy in self._pending_getReferences:
d1 = eventual.fireEventually(sturdy)
d1.addCallback(self.getReference)
d1.addBoth(lambda res, d=d: d.callback(res))
del self._pending_getReferences
for rc in self.reconnectors:
eventual.eventually(rc.startConnecting, self)
def _tubsAreNotRestartable(self):
raise RuntimeError("Sorry, but Tubs cannot be restarted.")
@ -386,6 +397,7 @@ class Tub(service.MultiService):
# note that once you stopService a Tub, I cannot be restarted. (at
# least this code is not designed to make that possible.. it might be
# doable in the future).
assert self.running
self.startService = self._tubsAreNotRestartable
self.getReference = self._tubHasBeenShutDown
self.connectTo = self._tubHasBeenShutDown
@ -523,8 +535,6 @@ class Tub(service.MultiService):
@return: a Deferred that fires with the RemoteReference
"""
assert self.running
if isinstance(sturdyOrURL, SturdyRef):
sturdy = sturdyOrURL
else:
@ -538,12 +548,21 @@ class Tub(service.MultiService):
"we cannot handle encrypted PB-URLs like %s"
% sturdy.getURL())
return defer.fail(e)
if not self.running:
# queue their request for service once the Tub actually starts
log.msg("Tub.getReference(%s) queued until Tub.startService called"
% sturdy)
d = defer.Deferred()
self._pending_getReferences.append((d, sturdy))
return d
name = sturdy.name
d = self.getBrokerForTubRef(sturdy.getTubRef())
d.addCallback(lambda b: b.getYourReferenceByName(name))
return d
def connectTo(self, sturdyOrURL, cb, *args, **kwargs):
def connectTo(self, _sturdyOrURL, _cb, *args, **kwargs):
"""Establish (and maintain) a connection to a given PBURL.
I establish a connection to the PBURL and run a callback to inform
@ -582,8 +601,12 @@ class Tub(service.MultiService):
rc.stopConnecting() # later
"""
assert self.running
rc = Reconnector(self, sturdyOrURL, cb, *args, **kwargs)
rc = Reconnector(_sturdyOrURL, _cb, args, kwargs)
if self.running:
rc.startConnecting(self)
else:
log.msg("Tub.connectTo(%s) queued until Tub.startService called"
% _sturdyOrURL)
self.reconnectors.append(rc)
return rc

View File

@ -42,17 +42,17 @@ class Reconnector:
jitter = 0.11962656492 # molar Planck constant times c, Joule meter/mole
verbose = False
def __init__(self, tub, url, cb, *args, **kwargs):
self._tub = tub
def __init__(self, url, cb, args, kwargs):
self._url = url
self._active = False
self._observer = (cb, args, kwargs)
self._delay = self.initialDelay
self._retries = 0
self._timer = None
self.startConnecting()
self._tub = None
def startConnecting(self):
def startConnecting(self, tub):
self._tub = tub
if self.verbose:
log.msg("Reconnector starting for %s" % self._url)
self._active = True
@ -65,7 +65,8 @@ class Reconnector:
if self._timer:
self._timer.cancel()
self._timer = False
self._tub._removeReconnector(self)
if self._tub:
self._tub._removeReconnector(self)
def _connect(self):
d = self._tub.getReference(self._url)

View File

@ -630,8 +630,18 @@ class TheirReferenceUnslicer(slicer.LeafUnslicer):
d.addBoth(self.ackGift)
# we return a Deferred that will fire with the RemoteReference when
# it becomes available. The RemoteReference is not even referenceable
# until then.
return d,None
# until then. In addition, we provide a ready_deferred, since any
# mutable container which holds the gift will be referenceable early
# but the message delivery must still wait for the getReference to
# complete. See to it that we fire the object deferred before we fire
# the ready_deferred.
obj_deferred, ready_deferred = defer.Deferred(), defer.Deferred()
def _ready(rref):
obj_deferred.callback(rref)
ready_deferred.callback(rref)
d.addCallback(_ready)
return obj_deferred, ready_deferred
def ackGift(self, rref):
rb = self.broker.remote_broker
@ -727,26 +737,6 @@ class SturdyRef(Copyable, RemoteCopy):
cmp(self.__class__, them.__class__) or
cmp(self._distinguishers(), them._distinguishers()))
def asLiveRef(self):
"""Return an object that can be sent over the wire and unserialized
as a live RemoteReference on the far end. Use this when you have a
SturdyRef and want to give someone a reference to its target, but
when you haven't bothered to acquire your own live reference to it."""
return _AsLiveRef(self)
class _AsLiveRef:
implements(ipb.ISlicer)
def __init__(self, sturdy):
self.target = sturdy
def slice(self, streamable, banana):
yield 'their-reference'
yield giftID
yield self.target.getURL()
yield [] # interfacenames
class TubRef:
"""This is a little helper class which provides a comparable identifier

View File

@ -105,7 +105,8 @@ def getRemoteInterface(obj):
if isinstance(i, RemoteInterfaceClass):
if i not in ilist:
ilist.append(i)
assert len(ilist) <= 1, "don't use multiple RemoteInterfaces! %s" % (obj,)
assert len(ilist) <= 1, ("don't use multiple RemoteInterfaces! %s uses %s"
% (obj, ilist))
if ilist:
return ilist[0]
return None
@ -199,7 +200,8 @@ class RemoteMethodSchema:
raise InvalidRemoteInterface(why)
if not names:
typeList = []
if len(names) != len(typeList):
# 'def foo(oops)' results in typeList==None
if typeList is None or len(names) != len(typeList):
# TODO: relax this, use schema=Any for the args that don't have
# default values. This would make:
# def foo(a, b=int): return None
@ -361,9 +363,18 @@ class RemoteInterfaceConstraint(OpenerConstraint):
associated with a remote Referenceable that implements the given
RemoteInterface. If 'interface' is None, just assert that it is a
RemoteReference at all.
On the inbound side, this will only accept a suitably-implementing
RemoteReference, or a gift that resolves to such a RemoteReference. On
the outbound side, this will accept either a Referenceable or a
RemoteReference (which might be a your-reference or a their-reference).
Sending your-references will result in the recipient getting a local
Referenceable, which will not pass the constraint. TODO: think about if
we want this behavior or not.
"""
opentypes = [("my-reference",)]
# TODO: accept their-references too
opentypes = [("my-reference",), ("their-reference",)]
name = "RemoteInterfaceConstraint"
def __init__(self, interface):
@ -387,7 +398,17 @@ class RemoteInterfaceConstraint(OpenerConstraint):
% (obj, self.interface))
else:
# this ought to be a Referenceable which implements the desired
# interface
# interface. Or, it might be a RemoteReference which points to
# one.
if ipb.IRemoteReference.providedBy(obj):
# it's a RemoteReference
if not self.interface:
return
iface = obj.tracker.interface
if not iface or iface != self.interface:
raise Violation("'%s' does not provide RemoteInterface %s"
% (obj, self.interface))
return
if not ipb.IReferenceable.providedBy(obj):
# TODO: maybe distinguish between OnlyReferenceable and
# Referenceable? which is more useful here?

View File

@ -10,7 +10,7 @@ from foolscap.slicers.unicode import UnicodeSlicer, UnicodeUnslicer
from foolscap.slicers.list import ListSlicer, ListUnslicer
from foolscap.slicers.tuple import TupleSlicer, TupleUnslicer
from foolscap.slicers.set import SetSlicer, SetUnslicer
from foolscap.slicers.set import ImmutableSetSlicer, ImmutableSetUnslicer
from foolscap.slicers.set import FrozenSetSlicer, FrozenSetUnslicer
#from foolscap.slicers.set import BuiltinSetSlicer
from foolscap.slicers.dict import DictSlicer, DictUnslicer, OrderedDictSlicer
from foolscap.slicers.vocab import ReplaceVocabSlicer, ReplaceVocabUnslicer
@ -26,7 +26,7 @@ unused = [
ListSlicer, ListUnslicer,
TupleSlicer, TupleUnslicer,
SetSlicer, SetUnslicer,
ImmutableSetSlicer, ImmutableSetUnslicer,
FrozenSetSlicer, FrozenSetUnslicer,
#from foolscap.slicers.set import BuiltinSetSlicer
DictSlicer, DictUnslicer, OrderedDictSlicer,
ReplaceVocabSlicer, ReplaceVocabUnslicer,

View File

@ -1,7 +1,7 @@
# -*- test-case-name: foolscap.test.test_banana -*-
from twisted.python import log
from twisted.internet.defer import Deferred
from twisted.internet.defer import Deferred, DeferredList
from foolscap.tokens import Violation, BananaError
from foolscap.slicer import BaseSlicer, BaseUnslicer
from foolscap.constraint import OpenerConstraint, Any, UnboundedSchema, IConstraint
@ -35,6 +35,7 @@ class DictUnslicer(BaseUnslicer):
self.d = {}
self.protocol.setObject(count, self.d)
self.key = None
self._ready_deferreds = []
def checkToken(self, typebyte, size):
if self.maxKeys != None:
@ -72,8 +73,8 @@ class DictUnslicer(BaseUnslicer):
self.d[key] = value
def receiveChild(self, obj, ready_deferred=None):
assert not isinstance(obj, Deferred)
assert ready_deferred is None
if ready_deferred:
self._ready_deferreds.append(ready_deferred)
if self.gettingKey:
self.receiveKey(obj)
else:
@ -102,7 +103,10 @@ class DictUnslicer(BaseUnslicer):
self.d[self.key] = value # placeholder
def receiveClose(self):
return self.d, None
ready_deferred = None
if self._ready_deferreds:
ready_deferred = DeferredList(self._ready_deferreds)
return self.d, ready_deferred
def describe(self):
if self.gettingKey:

View File

@ -1,7 +1,7 @@
# -*- test-case-name: foolscap.test.test_banana -*-
from twisted.python import log
from twisted.internet.defer import Deferred
from twisted.internet.defer import Deferred, DeferredList
from foolscap.tokens import Violation
from foolscap.slicer import BaseSlicer, BaseUnslicer
from foolscap.constraint import OpenerConstraint, Any, UnboundedSchema, IConstraint
@ -35,8 +35,9 @@ class ListUnslicer(BaseUnslicer):
self.list = []
self.count = count
if self.debug:
print "%s[%d].start with %s" % (self, self.count, self.list)
log.msg("%s[%d].start with %s" % (self, self.count, self.list))
self.protocol.setObject(count, self.list)
self._ready_deferreds = []
def checkToken(self, typebyte, size):
if self.maxLength != None and len(self.list) >= self.maxLength:
@ -65,15 +66,16 @@ class ListUnslicer(BaseUnslicer):
def update(self, obj, index):
# obj has already passed typechecking
if self.debug:
print "%s[%d].update: [%d]=%s" % (self, self.count, index, obj)
log.msg("%s[%d].update: [%d]=%s" % (self, self.count, index, obj))
assert isinstance(index, int)
self.list[index] = obj
return obj
def receiveChild(self, obj, ready_deferred=None):
assert ready_deferred is None
if ready_deferred:
self._ready_deferreds.append(ready_deferred)
if self.debug:
print "%s[%d].receiveChild(%s)" % (self, self.count, obj)
log.msg("%s[%d].receiveChild(%s)" % (self, self.count, obj))
# obj could be a primitive type, a Deferred, or a complex type like
# those returned from an InstanceUnslicer. However, the individual
# object has already been through the schema validation process. The
@ -86,10 +88,12 @@ class ListUnslicer(BaseUnslicer):
raise Violation("the list is full")
if isinstance(obj, Deferred):
if self.debug:
print " adding my update[%d] to %s" % (len(self.list), obj)
log.msg(" adding my update[%d] to %s" % (len(self.list), obj))
obj.addCallback(self.update, len(self.list))
obj.addErrback(self.printErr)
self.list.append("placeholder")
placeholder = "list placeholder for arg[%d], rd=%s" % \
(len(self.list), ready_deferred)
self.list.append(placeholder)
else:
self.list.append(obj)
@ -99,7 +103,10 @@ class ListUnslicer(BaseUnslicer):
log.err(why)
def receiveClose(self):
return self.list, None
ready_deferred = None
if self._ready_deferreds:
ready_deferred = DeferredList(self._ready_deferreds)
return self.list, ready_deferred
def describe(self):
return "[%d]" % len(self.list)

View File

@ -1,7 +1,11 @@
# -*- test-case-name: foolscap.test.test_banana -*-
import sets
from foolscap.slicers.list import ListSlicer, ListUnslicer
from twisted.internet import defer
from twisted.python import log
from foolscap.slicers.list import ListSlicer
from foolscap.slicers.tuple import TupleUnslicer
from foolscap.slicer import BaseUnslicer
from foolscap.tokens import Violation
from foolscap.constraint import OpenerConstraint, UnboundedSchema, Any, \
IConstraint
@ -9,34 +13,41 @@ from foolscap.constraint import OpenerConstraint, UnboundedSchema, Any, \
class SetSlicer(ListSlicer):
opentype = ("set",)
trackReferences = True
slices = sets.Set
slices = set
def sliceBody(self, streamable, banana):
for i in self.obj:
yield i
class ImmutableSetSlicer(SetSlicer):
class FrozenSetSlicer(SetSlicer):
opentype = ("immutable-set",)
trackReferences = False
slices = frozenset
# python2.4 has a builtin 'set' type, which is mutable, and we require
# python2.4 or newer. Code which was written to be compatible with python2.3,
# however, may use the 'sets' module. We will serialize old sets.Set and
# sets.ImmutableSet the same as we serialize new set and frozenset.
# Unfortunately this means that these objects will be deserialized as modern
# 'set' and 'frozenset' objects, which are not entirely compatible. Therefore
# code that is compatible with python2.3 might not work with foolscap.
class OldSetSlicer(SetSlicer):
slices = sets.Set
class OldImmutableSetSlicer(FrozenSetSlicer):
slices = sets.ImmutableSet
have_builtin_set = False
try:
set
# python2.4 has a builtin 'set' type, which is mutable
have_builtin_set = True
class BuiltinSetSlicer(SetSlicer):
slices = set
class BuiltinFrozenSetSlicer(ImmutableSetSlicer):
slices = frozenset
except NameError:
# oh well, I guess we don't have 'set'
class _Placeholder:
pass
class SetUnslicer(ListUnslicer):
class SetUnslicer(BaseUnslicer):
# this is a lot like a list, but sufficiently different to make it not
# worth subclassing
opentype = ("set",)
def receiveClose(self):
return sets.Set(self.list), None
debug = False
maxLength = None
itemConstraint = None
def setConstraint(self, constraint):
if isinstance(constraint, Any):
@ -45,10 +56,101 @@ class SetUnslicer(ListUnslicer):
self.maxLength = constraint.maxLength
self.itemConstraint = constraint.constraint
class ImmutableSetUnslicer(SetUnslicer):
opentype = ("immutable-set",)
def start(self, count):
#self.opener = foo # could replace it if we wanted to
self.set = set()
self.count = count
if self.debug:
log.msg("%s[%d].start with %s" % (self, self.count, self.set))
self.protocol.setObject(count, self.set)
self._ready_deferreds = []
def checkToken(self, typebyte, size):
if self.maxLength != None and len(self.set) >= self.maxLength:
# list is full, no more tokens accepted
# this is hit if the max+1 item is a primitive type
raise Violation("the set is full")
if self.itemConstraint:
self.itemConstraint.checkToken(typebyte, size)
def doOpen(self, opentype):
# decide whether the given object type is acceptable here. Raise a
# Violation exception if not, otherwise give it to our opener (which
# will normally be the RootUnslicer). Apply a constraint to the new
# unslicer.
if self.maxLength != None and len(self.set) >= self.maxLength:
# this is hit if the max+1 item is a non-primitive type
raise Violation("the set is full")
if self.itemConstraint:
self.itemConstraint.checkOpentype(opentype)
unslicer = self.open(opentype)
if unslicer:
if self.itemConstraint:
unslicer.setConstraint(self.itemConstraint)
return unslicer
def update(self, obj, placeholder):
# obj has already passed typechecking
if self.debug:
log.msg("%s[%d].update: [%s]=%s" % (self, self.count,
placeholder, obj))
self.set.remove(placeholder)
self.set.add(obj)
return obj
def receiveChild(self, obj, ready_deferred=None):
if ready_deferred:
self._ready_deferreds.append(ready_deferred)
if self.debug:
log.msg("%s[%d].receiveChild(%s)" % (self, self.count, obj))
# obj could be a primitive type, a Deferred, or a complex type like
# those returned from an InstanceUnslicer. However, the individual
# object has already been through the schema validation process. The
# only remaining question is whether the larger schema will accept
# it.
if self.maxLength != None and len(self.set) >= self.maxLength:
# this is redundant
# (if it were a non-primitive one, it would be caught in doOpen)
# (if it were a primitive one, it would be caught in checkToken)
raise Violation("the set is full")
if isinstance(obj, defer.Deferred):
if self.debug:
log.msg(" adding my update[%d] to %s" % (len(self.set), obj))
# note: the placeholder isn't strictly necessary, but it will
# help debugging to see a _Placeholder sitting in the set when it
# shouldn't rather than seeing a set that is smaller than it
# ought to be. If a remote method ever sees a _Placeholder, then
# something inside Foolscap has broken.
placeholder = _Placeholder()
obj.addCallback(self.update, placeholder)
obj.addErrback(self.printErr)
self.set.add(placeholder)
else:
self.set.add(obj)
def printErr(self, why):
print "ERR!"
print why.getBriefTraceback()
log.err(why)
def receiveClose(self):
return sets.ImmutableSet(self.list), None
ready_deferred = None
if self._ready_deferreds:
ready_deferred = defer.DeferredList(self._ready_deferreds)
return self.set, ready_deferred
class FrozenSetUnslicer(TupleUnslicer):
opentype = ("immutable-set",)
def receiveClose(self):
obj_or_deferred, ready_deferred = TupleUnslicer.receiveClose(self)
if isinstance(obj_or_deferred, defer.Deferred):
def _convert(the_tuple):
return frozenset(the_tuple)
obj_or_deferred.addCallback(_convert)
else:
obj_or_deferred = frozenset(obj_or_deferred)
return obj_or_deferred, ready_deferred
class SetConstraint(OpenerConstraint):
@ -64,12 +166,8 @@ class SetConstraint(OpenerConstraint):
opentypes = [("set",), ("immutable-set",)]
name = "SetConstraint"
if have_builtin_set:
mutable_set_types = (set, sets.Set)
immutable_set_types = (frozenset, sets.ImmutableSet)
else:
mutable_set_types = (sets.Set,)
immutable_set_types = (sets.ImmutableSet,)
mutable_set_types = (set, sets.Set)
immutable_set_types = (frozenset, sets.ImmutableSet)
all_set_types = mutable_set_types + immutable_set_types
def __init__(self, constraint, maxLength=30, mutable=None):

View File

@ -1,6 +1,6 @@
# -*- test-case-name: foolscap.test.test_banana -*-
from twisted.internet.defer import Deferred
from twisted.internet.defer import Deferred, DeferredList
from foolscap.tokens import Violation
from foolscap.slicer import BaseUnslicer
from foolscap.slicers.list import ListSlicer
@ -34,6 +34,7 @@ class TupleUnslicer(BaseUnslicer):
self.finished = False
self.deferred = Deferred()
self.protocol.setObject(count, self.deferred)
self._ready_deferreds = []
def checkToken(self, typebyte, size):
if self.constraints == None:
@ -64,7 +65,8 @@ class TupleUnslicer(BaseUnslicer):
return obj
def receiveChild(self, obj, ready_deferred=None):
assert ready_deferred is None
if ready_deferred:
self._ready_deferreds.append(ready_deferred)
if isinstance(obj, Deferred):
obj.addCallback(self.update, len(self.list))
obj.addErrback(self.explode)
@ -81,20 +83,39 @@ class TupleUnslicer(BaseUnslicer):
# not finished yet, we'll fire our Deferred when we are
if self.debug:
print " not finished yet"
return self.deferred, None
return
# list is now complete. We can finish.
return self.complete()
def complete(self):
ready_deferred = None
if self._ready_deferreds:
ready_deferred = DeferredList(self._ready_deferreds)
t = tuple(self.list)
if self.debug:
print " finished! tuple:%s{%s}" % (t, id(t))
self.protocol.setObject(self.count, t)
self.deferred.callback(t)
return t, None
return t, ready_deferred
def receiveClose(self):
if self.debug:
print "%s[%d].receiveClose" % (self, self.count)
self.finished = 1
return self.checkComplete()
if self.num_unreferenceable_children:
# not finished yet, we'll fire our Deferred when we are
if self.debug:
print " not finished yet"
ready_deferred = None
if self._ready_deferreds:
ready_deferred = DeferredList(self._ready_deferreds)
return self.deferred, ready_deferred
# the list is already complete
return self.complete()
def describe(self):
return "[%d]" % len(self.list)

View File

@ -1597,18 +1597,13 @@ class ThereAndBackAgain(TestBananaMixin, unittest.TestCase):
def test_tuple(self):
return self.looptest((1,2))
def test_set(self):
d = self.looptest(sets.Set([1,2]))
d.addCallback(lambda res: self.looptest(sets.ImmutableSet([1,2])))
# verify the python2.4 builtin 'set' type, which is mutable
try:
set
have_set = True
except NameError:
have_set = False
if have_set:
# we serialize builtin 'set' as a regular mutable sets.Set
d.addCallback(lambda res:
self.looptest(set([1,2]), sets.Set([1,2])))
d = self.looptest(set([1,2]))
d.addCallback(lambda res: self.looptest(frozenset([1,2])))
# and verify that old sets turn into modern ones, which is
# unfortunate but at least consistent
d.addCallback(lambda res: self.looptest(sets.Set([1,2]), set([1,2])))
d.addCallback(lambda res: self.looptest(sets.ImmutableSet([1,2]),
frozenset([1,2])))
return d
def test_bool(self):
@ -1709,15 +1704,71 @@ class ThereAndBackAgain(TestBananaMixin, unittest.TestCase):
self.assertIdentical(z[0]['list'], z[1])
self.assertIdentical(z[0]['list'][0], z)
def testMoreReferences(self):
def test_cycles_1(self):
# a list that contains a tuple that can't be referenced yet
a = []
t = (a,)
t2 = (t,)
t1 = (a,)
t2 = (t1,)
a.append(t2)
d = self.loop(t)
d = self.loop(t1)
d.addCallback(lambda z: self.assertIdentical(z[0][0][0], z))
return d
def test_cycles_2(self):
# a dict that contains a tuple that can't be referenced yet.
a = {}
t1 = (a,)
t2 = (t1,)
a['foo'] = t2
d = self.loop(t1)
d.addCallback(lambda z: self.assertIdentical(z[0]['foo'][0], z))
return d
def test_cycles_3(self):
# sets seem to be transitively immutable: any mutable contents would
# be unhashable, and sets can only contain hashable objects.
# Therefore sets cannot participate in cycles the way that tuples
# can.
# a set that contains a tuple that can't be referenced yet. You can't
# actually create this in python, because you can only create a set
# out of hashable objects, and sets aren't hashable, and a tuple that
# contains a set is not hashable.
a = set()
t1 = (a,)
t2 = (t1,)
a.add(t2)
d = self.loop(t1)
d.addCallback(lambda z: self.assertIdentical(list(z[0])[0][0], z))
# a list that contains a frozenset that can't be referenced yet
a = []
t1 = frozenset([a])
t2 = frozenset([t1])
a.append(t2)
d = self.loop(t1)
d.addCallback(lambda z:
self.assertIdentical(list(list(z)[0][0])[0], z))
# a dict that contains a frozenset that can't be referenced yet.
a = {}
t1 = frozenset([a])
t2 = frozenset([t1])
a['foo'] = t2
d = self.loop(t1)
d.addCallback(lambda z:
self.assertIdentical(list(list(z)[0]['foo'])[0], z))
# a set that contains a frozenset that can't be referenced yet.
a = set()
t1 = frozenset([a])
t2 = frozenset([t1])
a.add(t2)
d = self.loop(t1)
d.addCallback(lambda z: self.assertIdentical(list(list(list(z)[0])[0])[0], z))
return d
del test_cycles_3
class VocabTest1(unittest.TestCase):

View File

@ -9,7 +9,6 @@ if False:
log.startLogging(sys.stderr)
from twisted.python import failure, log
from twisted.internet import reactor, defer
from twisted.trial import unittest
from twisted.internet.main import CONNECTION_LOST
@ -36,7 +35,6 @@ class TestCall(TargetMixin, unittest.TestCase):
d.addCallback(lambda res: self.failUnlessEqual(target.calls, [(1,2)]))
d.addCallback(self._testCall1_1, rr)
return d
testCall1.timeout = 3
def _testCall1_1(self, res, rr):
# the caller still holds the RemoteReference
self.failUnless(self.callingBroker.yourReferenceByCLID.has_key(1))
@ -46,10 +44,14 @@ class TestCall(TargetMixin, unittest.TestCase):
# the targetBroker so *they* can forget about it.
del rr # this fires a DecRef
gc.collect() # make sure
# we need to give it a moment to deliver the DecRef message and act
# on it
d = defer.Deferred()
reactor.callLater(0.1, d.callback, None)
# on it. Poll until the caller has received it.
def _check():
if self.callingBroker.yourReferenceByCLID.has_key(1):
return False
return True
d = self.poll(_check)
d.addCallback(self._testCall1_2)
return d
def _testCall1_2(self, res):

View File

@ -1,10 +1,11 @@
from zope.interface import implements
from twisted.trial import unittest
from twisted.internet import defer
from twisted.internet.error import ConnectionDone, ConnectionLost
from foolscap import Tub, UnauthenticatedTub
from foolscap import Tub, UnauthenticatedTub, RemoteInterface, Referenceable
from foolscap.referenceable import RemoteReference
from foolscap.test.common import HelperTarget
from foolscap.test.common import HelperTarget, RIHelper
from foolscap.eventual import flushEventualQueue
crypto_available = False
@ -24,6 +25,19 @@ def ignoreConnectionDone(f):
f.trap(ConnectionDone, ConnectionLost)
return None
class RIConstrainedHelper(RemoteInterface):
def set(obj=RIHelper): return None
class ConstrainedHelper(Referenceable):
implements(RIConstrainedHelper)
def __init__(self, name="unnamed"):
self.name = name
def remote_set(self, obj):
self.obj = obj
class Gifts(unittest.TestCase):
# Here we test the three-party introduction process as depicted in the
# classic Granovetter diagram. Alice has a reference to Bob and another
@ -52,9 +66,15 @@ class Gifts(unittest.TestCase):
self.bob_url = self.tubB.registerReference(self.bob)
self.carol = HelperTarget("carol")
self.carol_url = self.tubC.registerReference(self.carol)
self.cindy = HelperTarget("cindy")
# cindy is Carol's little sister. She doesn't have a phone, but
# Carol might talk about her anyway.
self.cindy = HelperTarget("cindy")
# more sisters. Alice knows them, and she introduces Bob to them.
self.charlene = HelperTarget("charlene")
self.christine = HelperTarget("christine")
self.clarisse = HelperTarget("clarisse")
self.colette = HelperTarget("colette")
self.courtney = HelperTarget("courtney")
def createInitialReferences(self):
# we must start by giving Alice a reference to both Bob and Carol.
@ -73,6 +93,46 @@ class Gifts(unittest.TestCase):
d.addCallback(_aliceGotCarol)
return d
def createMoreReferences(self):
# give Alice references to Carol's sisters
dl = []
url = self.tubC.registerReference(self.charlene)
d = self.tubA.getReference(url)
def _got_charlene(rref):
self.acharlene = rref
d.addCallback(_got_charlene)
dl.append(d)
url = self.tubC.registerReference(self.christine)
d = self.tubA.getReference(url)
def _got_christine(rref):
self.achristine = rref
d.addCallback(_got_christine)
dl.append(d)
url = self.tubC.registerReference(self.clarisse)
d = self.tubA.getReference(url)
def _got_clarisse(rref):
self.aclarisse = rref
d.addCallback(_got_clarisse)
dl.append(d)
url = self.tubC.registerReference(self.colette)
d = self.tubA.getReference(url)
def _got_colette(rref):
self.acolette = rref
d.addCallback(_got_colette)
dl.append(d)
url = self.tubC.registerReference(self.courtney)
d = self.tubA.getReference(url)
def _got_courtney(rref):
self.acourtney = rref
d.addCallback(_got_courtney)
dl.append(d)
return defer.DeferredList(dl)
def testGift(self):
#defer.setDebugging(True)
self.createCharacters()
@ -189,6 +249,76 @@ class Gifts(unittest.TestCase):
d.addCallback(_checkBob)
return d
def testContainers(self):
self.createCharacters()
self.bob.calls = []
d = self.createInitialReferences()
d.addCallback(lambda res: self.createMoreReferences())
def _introduce(res):
# we send several messages to Bob, each of which has a container
# with a gift inside it. This exercises the ready_deferred
# handling inside containers.
dl = []
cr = self.abob.callRemote
dl.append(cr("append", set([self.acharlene])))
dl.append(cr("append", frozenset([self.achristine])))
dl.append(cr("append", [self.aclarisse]))
dl.append(cr("append", obj=(self.acolette,)))
dl.append(cr("append", {'a': self.acourtney}))
# TODO: pass a gift as an attribute of a Copyable
return defer.DeferredList(dl)
d.addCallback(_introduce)
def _checkBob(res):
# this runs after all three messages have been acked by Bob
self.failUnlessEqual(len(self.bob.calls), 5)
bcharlene = self.bob.calls.pop(0)
self.failUnless(isinstance(bcharlene, set))
self.failUnlessEqual(len(bcharlene), 1)
self.failUnless(isinstance(list(bcharlene)[0], RemoteReference))
bchristine = self.bob.calls.pop(0)
self.failUnless(isinstance(bchristine, frozenset))
self.failUnlessEqual(len(bchristine), 1)
self.failUnless(isinstance(list(bchristine)[0], RemoteReference))
bclarisse = self.bob.calls.pop(0)
self.failUnless(isinstance(bclarisse, list))
self.failUnlessEqual(len(bclarisse), 1)
self.failUnless(isinstance(bclarisse[0], RemoteReference))
bcolette = self.bob.calls.pop(0)
self.failUnless(isinstance(bcolette, tuple))
self.failUnlessEqual(len(bcolette), 1)
self.failUnless(isinstance(bcolette[0], RemoteReference))
bcourtney = self.bob.calls.pop(0)
self.failUnless(isinstance(bcourtney, dict))
self.failUnlessEqual(len(bcourtney), 1)
self.failUnless(isinstance(bcourtney['a'], RemoteReference))
d.addCallback(_checkBob)
return d
def create_constrained_characters(self):
self.alice = HelperTarget("alice")
self.bob = ConstrainedHelper("bob")
self.bob_url = self.tubB.registerReference(self.bob)
self.carol = HelperTarget("carol")
self.carol_url = self.tubC.registerReference(self.carol)
def test_constraint(self):
self.create_constrained_characters()
self.bob.calls = []
d = self.createInitialReferences()
def _introduce(res):
return self.abob.callRemote("set", self.acarol)
d.addCallback(_introduce)
def _checkBob(res):
self.failUnless(isinstance(self.bob.obj, RemoteReference))
d.addCallback(_checkBob)
return d
# this was used to alice's reference to carol (self.acarol) appeared in
# alice's gift table at the right time, to make sure that the
# RemoteReference is kept alive while the gift is in transit. The whole

View File

@ -2,7 +2,7 @@
import sets, re
from twisted.trial import unittest
from foolscap import schema, copyable
from foolscap.tokens import Violation
from foolscap.tokens import Violation, InvalidRemoteInterface
from foolscap.constraint import IConstraint
from foolscap.remoteinterface import RemoteMethodSchema, \
RemoteInterfaceConstraint, LocalInterfaceConstraint
@ -474,6 +474,13 @@ class Arguments(unittest.TestCase):
self.failUnlessRaises(schema.Violation,
r.checkResults, 12, False)
def test_bad_arguments(self):
def foo(nodefault): return str
self.failUnlessRaises(InvalidRemoteInterface,
RemoteMethodSchema, method=foo)
def bar(nodefault, a=int): return str
self.failUnlessRaises(InvalidRemoteInterface,
RemoteMethodSchema, method=bar)
class Interfaces(unittest.TestCase):
@ -520,9 +527,11 @@ class Interfaces(unittest.TestCase):
interfaceName = common.RIHelper.__remote_name__
tracker = RemoteReferenceTracker(parent, clid, url, interfaceName)
rr = RemoteReference(tracker)
c1 = RemoteInterfaceConstraint(common.RIHelper)
c2 = RemoteInterfaceConstraint(common.RIMyTarget)
self.check_inbound(rr, c1)
self.violates_outbound(rr, c1)
self.check_outbound(rr, c1) # gift
c2 = RemoteInterfaceConstraint(common.RIMyTarget)
self.violates_inbound(rr, c2)
self.violates_outbound(rr, c2)

View File

@ -2,6 +2,7 @@
import os.path
from twisted.trial import unittest
from twisted.internet import defer
crypto_available = False
try:
@ -10,7 +11,16 @@ try:
except ImportError:
pass
from foolscap import Tub
from foolscap import Tub, UnauthenticatedTub
from foolscap.referenceable import RemoteReference
from foolscap.eventual import eventually, flushEventualQueue
from foolscap.test.common import HelperTarget, TargetMixin
# we use authenticated tubs if possible. If crypto is not available, fall
# back to unauthenticated ones
GoodEnoughTub = UnauthenticatedTub
if crypto_available:
GoodEnoughTub = Tub
class TestCertFile(unittest.TestCase):
def test_generate(self):
@ -38,3 +48,72 @@ class TestCertFile(unittest.TestCase):
if not crypto_available:
del TestCertFile
class QueuedStartup(TargetMixin, unittest.TestCase):
# calling getReference and connectTo before the Tub has started should
# put off network activity until the Tub is started.
def setUp(self):
TargetMixin.setUp(self)
self.tubB = GoodEnoughTub()
self.services = [self.tubB]
for s in self.services:
s.startService()
l = s.listenOn("tcp:0:interface=127.0.0.1")
s.setLocation("127.0.0.1:%d" % l.getPortnum())
self.barry = HelperTarget("barry")
self.barry_url = self.tubB.registerReference(self.barry)
self.bill = HelperTarget("bill")
self.bill_url = self.tubB.registerReference(self.bill)
self.bob = HelperTarget("bob")
self.bob_url = self.tubB.registerReference(self.bob)
def tearDown(self):
d = TargetMixin.tearDown(self)
def _more(res):
return defer.DeferredList([s.stopService() for s in self.services])
d.addCallback(_more)
d.addCallback(flushEventualQueue)
return d
def test_queued_getref(self):
t1 = GoodEnoughTub()
d1 = t1.getReference(self.barry_url)
d2 = t1.getReference(self.bill_url)
def _check(res):
((barry_success, barry_rref),
(bill_success, bill_rref)) = res
self.failUnless(barry_success)
self.failUnless(bill_success)
self.failUnless(isinstance(barry_rref, RemoteReference))
self.failUnless(isinstance(bill_rref, RemoteReference))
self.failIf(barry_rref == bill_success)
dl = defer.DeferredList([d1, d2])
dl.addCallback(_check)
self.services.append(t1)
eventually(t1.startService)
return dl
def test_queued_reconnector(self):
t1 = GoodEnoughTub()
bill_connections = []
barry_connections = []
t1.connectTo(self.bill_url, bill_connections.append)
t1.connectTo(self.barry_url, barry_connections.append)
def _check():
if len(bill_connections) >= 1 and len(barry_connections) >= 1:
return True
return False
d = self.poll(_check)
def _validate(res):
self.failUnless(isinstance(bill_connections[0], RemoteReference))
self.failUnless(isinstance(barry_connections[0], RemoteReference))
self.failIf(bill_connections[0] == barry_connections[0])
d.addCallback(_validate)
self.services.append(t1)
eventually(t1.startService)
return d

View File

@ -1,94 +1,58 @@
foolscap (0.1.4) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Mon, 14 May 2007 22:37:04 -0700
foolscap (0.1.3) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Wed, 2 May 2007 14:55:49 -0700
foolscap (0.1.2+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Fri, 13 Apr 2007 00:22:10 -0700
foolscap (0.1.2) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Wed, 4 Apr 2007 12:32:46 -0700
foolscap (0.1.1+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Wed, 4 Apr 2007 10:32:22 -0700
foolscap (0.1.1) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Tue, 3 Apr 2007 20:48:07 -0700
foolscap (0.1.0+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Mon, 19 Mar 2007 23:11:35 -0700
foolscap (0.1.0) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Thu, 15 Mar 2007 16:56:16 -0700
foolscap (0.0.7+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Mon, 22 Jan 2007 12:41:18 -0800
foolscap (0.0.7) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Tue, 16 Jan 2007 12:03:00 -0800
foolscap (0.0.6+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Thu, 4 Jan 2007 18:45:04 -0500
foolscap (0.0.6) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Mon, 18 Dec 2006 12:10:51 -0800
foolscap (0.0.5+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Tue, 14 Nov 2006 21:24:17 -0800
foolscap (0.0.5) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Sat, 4 Nov 2006 23:20:46 -0800
foolscap (0.0.4+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Tue, 31 Oct 2006 23:38:34 -0800
foolscap (0.0.4) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Thu, 26 Oct 2006 00:46:11 -0700
-- Brian Warner <warner@lothar.com> Thu, 26 Oct 2006 00:46:30 -0700
foolscap (0.0.3+) unstable; urgency=low
foolscap (0.0.3) unstable; urgency=low
* new upstream release, put debian packaging in the tree

View File

@ -41,7 +41,7 @@ binary-indep: build install
dh_testdir
dh_testroot
dh_installdocs -i -A NEWS README
dh_installdocs ChangeLog doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-pb.xhtml doc/copyable.xhtml doc/listings doc/specifications
dh_installdocs ChangeLog doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-foolscap.xhtml doc/copyable.xhtml doc/listings doc/specifications
dh_installchangelogs -i
dh_compress -i -X.py
dh_fixperms

View File

@ -1,94 +1,58 @@
foolscap (0.1.4) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Mon, 14 May 2007 22:37:04 -0700
foolscap (0.1.3) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Wed, 2 May 2007 14:55:49 -0700
foolscap (0.1.2+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Fri, 13 Apr 2007 00:22:10 -0700
foolscap (0.1.2) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Wed, 4 Apr 2007 12:32:46 -0700
foolscap (0.1.1+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Wed, 4 Apr 2007 10:32:22 -0700
foolscap (0.1.1) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Tue, 3 Apr 2007 20:48:07 -0700
foolscap (0.1.0+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Mon, 19 Mar 2007 23:11:35 -0700
foolscap (0.1.0) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Thu, 15 Mar 2007 16:56:16 -0700
foolscap (0.0.7+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Mon, 22 Jan 2007 12:41:18 -0800
foolscap (0.0.7) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Tue, 16 Jan 2007 12:03:00 -0800
foolscap (0.0.6+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Thu, 4 Jan 2007 18:45:04 -0500
foolscap (0.0.6) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Mon, 18 Dec 2006 12:10:51 -0800
foolscap (0.0.5+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Tue, 14 Nov 2006 21:24:17 -0800
foolscap (0.0.5) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Sat, 4 Nov 2006 23:20:46 -0800
foolscap (0.0.4+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Tue, 31 Oct 2006 23:38:34 -0800
foolscap (0.0.4) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Thu, 26 Oct 2006 00:46:30 -0700
foolscap (0.0.3+) unstable; urgency=low
foolscap (0.0.3) unstable; urgency=low
* new upstream release, put debian packaging in the tree

View File

@ -9,7 +9,7 @@ include /usr/share/cdbs/1/class/python-distutils.mk
install/python-foolscap::
dh_installdocs doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-pb.xhtml doc/copyable.xhtml doc/listings doc/specifications
dh_installdocs doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-foolscap.xhtml doc/copyable.xhtml doc/listings doc/specifications
clean::
-rm -rf build

View File

@ -1,94 +1,58 @@
foolscap (0.1.4) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Mon, 14 May 2007 22:37:04 -0700
foolscap (0.1.3) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Wed, 2 May 2007 14:55:49 -0700
foolscap (0.1.2+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Fri, 13 Apr 2007 00:22:10 -0700
foolscap (0.1.2) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Wed, 4 Apr 2007 12:32:46 -0700
foolscap (0.1.1+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Wed, 4 Apr 2007 10:32:22 -0700
foolscap (0.1.1) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Tue, 3 Apr 2007 20:48:07 -0700
foolscap (0.1.0+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Mon, 19 Mar 2007 23:11:35 -0700
foolscap (0.1.0) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Thu, 15 Mar 2007 16:56:16 -0700
foolscap (0.0.7+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Mon, 22 Jan 2007 12:41:18 -0800
foolscap (0.0.7) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Tue, 16 Jan 2007 12:03:00 -0800
foolscap (0.0.6+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Thu, 4 Jan 2007 18:45:04 -0500
foolscap (0.0.6) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Mon, 18 Dec 2006 12:10:51 -0800
foolscap (0.0.5+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Tue, 14 Nov 2006 21:24:17 -0800
foolscap (0.0.5) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Sat, 4 Nov 2006 23:20:46 -0800
foolscap (0.0.4+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Tue, 31 Oct 2006 23:38:34 -0800
foolscap (0.0.4) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Thu, 26 Oct 2006 00:46:30 -0700
foolscap (0.0.3+) unstable; urgency=low
foolscap (0.0.3) unstable; urgency=low
* new upstream release, put debian packaging in the tree

View File

@ -9,7 +9,7 @@ include /usr/share/cdbs/1/class/python-distutils.mk
install/python-foolscap::
dh_installdocs doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-pb.xhtml doc/copyable.xhtml doc/listings doc/specifications
dh_installdocs doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-foolscap.xhtml doc/copyable.xhtml doc/listings doc/specifications
clean::
-rm -rf build

View File

@ -1,94 +1,58 @@
foolscap (0.1.4) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Mon, 14 May 2007 22:37:04 -0700
foolscap (0.1.3) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Wed, 2 May 2007 14:55:49 -0700
foolscap (0.1.2+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Fri, 13 Apr 2007 00:22:10 -0700
foolscap (0.1.2) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Wed, 4 Apr 2007 12:32:46 -0700
foolscap (0.1.1+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Wed, 4 Apr 2007 10:32:22 -0700
foolscap (0.1.1) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Tue, 3 Apr 2007 20:48:07 -0700
foolscap (0.1.0+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Mon, 19 Mar 2007 23:11:35 -0700
foolscap (0.1.0) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Thu, 15 Mar 2007 16:56:16 -0700
foolscap (0.0.7+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Mon, 22 Jan 2007 12:41:18 -0800
foolscap (0.0.7) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Tue, 16 Jan 2007 12:03:00 -0800
foolscap (0.0.6+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Thu, 4 Jan 2007 18:45:04 -0500
foolscap (0.0.6) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Mon, 18 Dec 2006 12:10:51 -0800
foolscap (0.0.5+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Tue, 14 Nov 2006 21:24:17 -0800
foolscap (0.0.5) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Sat, 4 Nov 2006 23:20:46 -0800
foolscap (0.0.4+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Tue, 31 Oct 2006 23:38:34 -0800
foolscap (0.0.4) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Thu, 26 Oct 2006 00:46:11 -0700
-- Brian Warner <warner@lothar.com> Thu, 26 Oct 2006 00:46:30 -0700
foolscap (0.0.3+) unstable; urgency=low
foolscap (0.0.3) unstable; urgency=low
* new upstream release, put debian packaging in the tree

View File

@ -41,7 +41,7 @@ binary-indep: build install
dh_testdir
dh_testroot
dh_installdocs -i -A NEWS README
dh_installdocs ChangeLog doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-pb.xhtml doc/copyable.xhtml doc/listings doc/specifications
dh_installdocs ChangeLog doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-foolscap.xhtml doc/copyable.xhtml doc/listings doc/specifications
dh_installchangelogs -i
dh_compress -i -X.py
dh_fixperms

View File

@ -1,94 +1,58 @@
foolscap (0.1.4) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Mon, 14 May 2007 22:37:04 -0700
foolscap (0.1.3) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Wed, 2 May 2007 14:55:49 -0700
foolscap (0.1.2+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Fri, 13 Apr 2007 00:22:10 -0700
foolscap (0.1.2) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Wed, 4 Apr 2007 12:32:46 -0700
foolscap (0.1.1+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Wed, 4 Apr 2007 10:32:22 -0700
foolscap (0.1.1) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Tue, 3 Apr 2007 20:48:07 -0700
foolscap (0.1.0+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Mon, 19 Mar 2007 23:11:35 -0700
foolscap (0.1.0) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Thu, 15 Mar 2007 16:56:16 -0700
foolscap (0.0.7+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Mon, 22 Jan 2007 12:41:18 -0800
foolscap (0.0.7) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Tue, 16 Jan 2007 12:03:00 -0800
foolscap (0.0.6+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Thu, 4 Jan 2007 18:45:04 -0500
foolscap (0.0.6) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Mon, 18 Dec 2006 12:10:51 -0800
foolscap (0.0.5+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Tue, 14 Nov 2006 21:24:17 -0800
foolscap (0.0.5) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Sat, 4 Nov 2006 23:20:46 -0800
foolscap (0.0.4+) unstable; urgency=low
* bump revision while between releases
-- Brian Warner <warner@lothar.com> Tue, 31 Oct 2006 23:38:34 -0800
foolscap (0.0.4) unstable; urgency=low
* new release
-- Brian Warner <warner@lothar.com> Thu, 26 Oct 2006 00:46:30 -0700
foolscap (0.0.3+) unstable; urgency=low
foolscap (0.0.3) unstable; urgency=low
* new upstream release, put debian packaging in the tree

View File

@ -9,7 +9,7 @@ include /usr/share/cdbs/1/class/python-distutils.mk
install/python-foolscap::
dh_installdocs doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-pb.xhtml doc/copyable.xhtml doc/listings doc/specifications
dh_installdocs doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-foolscap.xhtml doc/copyable.xhtml doc/listings doc/specifications
clean::
-rm -rf build

View File

@ -168,7 +168,6 @@ class CodeTracer:
Start recording.
"""
if not self.started:
self.LOG = open("/tmp/flog.out", "w")
self.started = True
sys.settrace(self.g)