From 267a068fe4d45bffe5f38e93498b80066e837e8c Mon Sep 17 00:00:00 2001
From: Brian Warner Ok, now how do you acquire that RemoteReference? How do you
make the Referenceable available to the outside world? For this,
-we'll need to discuss the Tub
, and the concept of a PB URL
.Tub
, and the concept of a FURL
+URL
.
startService and stopService on the Tub
directly.
-Note that you must start the Tub before calling getReference
-or connectTo, 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
-getReference calls and handling them after the Tub been
-started.
Note that no network activity will occur until the Tub's
+startService method has been called. This means that any
+getReference or connectTo 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.
securereferences (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.
You can also create an UnauthenticatedTub, 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 pbu:
instead of pb:) to make sure they are not mistaken for
-authenticated Tubs. PB uses authenticated Tubs by default.
Having the Tub listen on a TCP port is as simple as calling This returns the This returns the Since Here are two programs, one implementing the server side of our
@@ -333,7 +353,7 @@ the answer is 3
- In Foolscap, each world-accessible Referenceable has one or more URLs
which are 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
+ 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
listenOn with a
url = tub.registerReference(myserver, "math-service")
-PB URL
for your Referenceable. Remote
+FURL
for your Referenceable. Remote
systems will use this URL to access your newly-published object. The
registration just maps a per-Tub name to the Referenceable:
technically the same Referenceable could be published multiple
@@ -279,6 +280,25 @@ d.addCallbacks(gotReference, gotError)
connection, if one is available, and it will return an existing
RemoteReference, it one has already been acquired.getReference requests are queued until the Tub starts,
+the following will work too. But don't forget to call
+tub.startService() eventually, otherwise your program will hang
+forever.
+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()
+
+
+
Complete example
PB URLs
+FURLs
secure
, where we use the capability-security definition of
@@ -350,9 +370,9 @@ the term, meaning those URLs have the following properties:tub.registerReference generate a random name for you, preserving
the unguessability property.
Obviously this second property only holds if you use SSL. If you choose to use unauthenticated Tubs, all security properties are lost.
-The format of a PB URL, like +
The format of a FURL, like
pb://abcd123@example.com:5901,backup.example.com:8800/math-server,
-is as followsnote that the PB URL uses the same format
+is as followsnote that the FURL uses the same format
as an HTTPSY
URL:
pb://ABCD@unix/path/to/socket/NAME, but this needs
some work)
-PB URLs for unauthenticated Tubs, like +
FURLs for unauthenticated Tubs, like
pbu://example.com:8700/math-server, are formatted as
follows:
twistd to launch the program. The User side is
written with the same reactor.run() style as the earlier
example.
-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 +
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:").
+relatively short FURL that starts with "pbu:"). pb3calculator.py @@ -766,7 +786,7 @@ suitably unique string (like a URI). 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.. He can +FURLs prevent DNS-spoofing and man-in-the-middle attacks.. He can start using this RemoteReference right away:
diff --git a/src/foolscap/foolscap/__init__.py b/src/foolscap/foolscap/__init__.py
index e2243f42..ab58d559 100644
--- a/src/foolscap/foolscap/__init__.py
+++ b/src/foolscap/foolscap/__init__.py
@@ -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
diff --git a/src/foolscap/foolscap/call.py b/src/foolscap/foolscap/call.py
index 8ed3af4b..2f1ccd6a 100644
--- a/src/foolscap/foolscap/call.py
+++ b/src/foolscap/foolscap/call.py
@@ -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,11 +310,13 @@ 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
return
-
+
def updateChild(self, obj, which):
# one of our arguments has just now become referenceable. Normal
# types can't trigger this (since the arguments to a method form a
@@ -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
diff --git a/src/foolscap/foolscap/pb.py b/src/foolscap/foolscap/pb.py
index 9f7cfb9d..735f1dbc 100644
--- a/src/foolscap/foolscap/pb.py
+++ b/src/foolscap/foolscap/pb.py
@@ -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
diff --git a/src/foolscap/foolscap/reconnector.py b/src/foolscap/foolscap/reconnector.py
index d22ab3e8..eace104e 100644
--- a/src/foolscap/foolscap/reconnector.py
+++ b/src/foolscap/foolscap/reconnector.py
@@ -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)
diff --git a/src/foolscap/foolscap/referenceable.py b/src/foolscap/foolscap/referenceable.py
index 283705a7..d940429f 100644
--- a/src/foolscap/foolscap/referenceable.py
+++ b/src/foolscap/foolscap/referenceable.py
@@ -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
diff --git a/src/foolscap/foolscap/remoteinterface.py b/src/foolscap/foolscap/remoteinterface.py
index 46df3616..f3fbdc09 100644
--- a/src/foolscap/foolscap/remoteinterface.py
+++ b/src/foolscap/foolscap/remoteinterface.py
@@ -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?
diff --git a/src/foolscap/foolscap/slicers/allslicers.py b/src/foolscap/foolscap/slicers/allslicers.py
index aa05f177..afb3b470 100644
--- a/src/foolscap/foolscap/slicers/allslicers.py
+++ b/src/foolscap/foolscap/slicers/allslicers.py
@@ -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,
diff --git a/src/foolscap/foolscap/slicers/dict.py b/src/foolscap/foolscap/slicers/dict.py
index 6cd30c29..23a69ca3 100644
--- a/src/foolscap/foolscap/slicers/dict.py
+++ b/src/foolscap/foolscap/slicers/dict.py
@@ -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:
diff --git a/src/foolscap/foolscap/slicers/list.py b/src/foolscap/foolscap/slicers/list.py
index 4e8a0db2..70ad55f8 100644
--- a/src/foolscap/foolscap/slicers/list.py
+++ b/src/foolscap/foolscap/slicers/list.py
@@ -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)
diff --git a/src/foolscap/foolscap/slicers/set.py b/src/foolscap/foolscap/slicers/set.py
index 206507f2..f2afa09c 100644
--- a/src/foolscap/foolscap/slicers/set.py
+++ b/src/foolscap/foolscap/slicers/set.py
@@ -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):
+ 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):
+ 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):
- return sets.ImmutableSet(self.list), None
+ 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):
diff --git a/src/foolscap/foolscap/slicers/tuple.py b/src/foolscap/foolscap/slicers/tuple.py
index f7ea5657..6469f822 100644
--- a/src/foolscap/foolscap/slicers/tuple.py
+++ b/src/foolscap/foolscap/slicers/tuple.py
@@ -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)
diff --git a/src/foolscap/foolscap/test/test_banana.py b/src/foolscap/foolscap/test/test_banana.py
index 5951cafe..2c95c3f7 100644
--- a/src/foolscap/foolscap/test/test_banana.py
+++ b/src/foolscap/foolscap/test/test_banana.py
@@ -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):
diff --git a/src/foolscap/foolscap/test/test_call.py b/src/foolscap/foolscap/test/test_call.py
index 04dc4abc..79e19beb 100644
--- a/src/foolscap/foolscap/test/test_call.py
+++ b/src/foolscap/foolscap/test/test_call.py
@@ -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):
diff --git a/src/foolscap/foolscap/test/test_gifts.py b/src/foolscap/foolscap/test/test_gifts.py
index 3775e657..db6dfd78 100644
--- a/src/foolscap/foolscap/test/test_gifts.py
+++ b/src/foolscap/foolscap/test/test_gifts.py
@@ -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
diff --git a/src/foolscap/foolscap/test/test_schema.py b/src/foolscap/foolscap/test/test_schema.py
index 671b7a73..0c052e78 100644
--- a/src/foolscap/foolscap/test/test_schema.py
+++ b/src/foolscap/foolscap/test/test_schema.py
@@ -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)
diff --git a/src/foolscap/foolscap/test/test_tub.py b/src/foolscap/foolscap/test/test_tub.py
index 4b0d31b1..8f36a524 100644
--- a/src/foolscap/foolscap/test/test_tub.py
+++ b/src/foolscap/foolscap/test/test_tub.py
@@ -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
+
diff --git a/src/foolscap/misc/dapper/debian/changelog b/src/foolscap/misc/dapper/debian/changelog
index f3331be3..9699a6df 100644
--- a/src/foolscap/misc/dapper/debian/changelog
+++ b/src/foolscap/misc/dapper/debian/changelog
@@ -1,14 +1,14 @@
-foolscap (0.1.3) unstable; urgency=low
+foolscap (0.1.4) unstable; urgency=low
* new release
- -- Brian Warner Wed, 2 May 2007 14:55:49 -0700
+ -- Brian Warner Mon, 14 May 2007 22:37:04 -0700
-foolscap (0.1.2+) unstable; urgency=low
+foolscap (0.1.3) unstable; urgency=low
- * bump revision while between releases
+ * new release
- -- Brian Warner Fri, 13 Apr 2007 00:22:10 -0700
+ -- Brian Warner Wed, 2 May 2007 14:55:49 -0700
foolscap (0.1.2) unstable; urgency=low
@@ -16,79 +16,43 @@ foolscap (0.1.2) unstable; urgency=low
-- Brian Warner Wed, 4 Apr 2007 12:32:46 -0700
-foolscap (0.1.1+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Wed, 4 Apr 2007 10:32:22 -0700
-
foolscap (0.1.1) unstable; urgency=low
* new release
-- Brian Warner Tue, 3 Apr 2007 20:48:07 -0700
-foolscap (0.1.0+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Mon, 19 Mar 2007 23:11:35 -0700
-
foolscap (0.1.0) unstable; urgency=low
* new release
-- Brian Warner Thu, 15 Mar 2007 16:56:16 -0700
-foolscap (0.0.7+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Mon, 22 Jan 2007 12:41:18 -0800
-
foolscap (0.0.7) unstable; urgency=low
* new release
-- Brian Warner Tue, 16 Jan 2007 12:03:00 -0800
-foolscap (0.0.6+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Thu, 4 Jan 2007 18:45:04 -0500
-
foolscap (0.0.6) unstable; urgency=low
* new release
-- Brian Warner Mon, 18 Dec 2006 12:10:51 -0800
-foolscap (0.0.5+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Tue, 14 Nov 2006 21:24:17 -0800
-
foolscap (0.0.5) unstable; urgency=low
* new release
-- Brian Warner Sat, 4 Nov 2006 23:20:46 -0800
-foolscap (0.0.4+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Tue, 31 Oct 2006 23:38:34 -0800
-
foolscap (0.0.4) unstable; urgency=low
* new release
- -- Brian Warner Thu, 26 Oct 2006 00:46:11 -0700
+ -- Brian Warner 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
diff --git a/src/foolscap/misc/dapper/debian/rules b/src/foolscap/misc/dapper/debian/rules
index eb2284d2..8ee04543 100644
--- a/src/foolscap/misc/dapper/debian/rules
+++ b/src/foolscap/misc/dapper/debian/rules
@@ -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
diff --git a/src/foolscap/misc/edgy/debian/changelog b/src/foolscap/misc/edgy/debian/changelog
index 93eb7f3a..9699a6df 100644
--- a/src/foolscap/misc/edgy/debian/changelog
+++ b/src/foolscap/misc/edgy/debian/changelog
@@ -1,14 +1,14 @@
-foolscap (0.1.3) unstable; urgency=low
+foolscap (0.1.4) unstable; urgency=low
* new release
- -- Brian Warner Wed, 2 May 2007 14:55:49 -0700
+ -- Brian Warner Mon, 14 May 2007 22:37:04 -0700
-foolscap (0.1.2+) unstable; urgency=low
+foolscap (0.1.3) unstable; urgency=low
- * bump revision while between releases
+ * new release
- -- Brian Warner Fri, 13 Apr 2007 00:22:10 -0700
+ -- Brian Warner Wed, 2 May 2007 14:55:49 -0700
foolscap (0.1.2) unstable; urgency=low
@@ -16,79 +16,43 @@ foolscap (0.1.2) unstable; urgency=low
-- Brian Warner Wed, 4 Apr 2007 12:32:46 -0700
-foolscap (0.1.1+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Wed, 4 Apr 2007 10:32:22 -0700
-
foolscap (0.1.1) unstable; urgency=low
* new release
-- Brian Warner Tue, 3 Apr 2007 20:48:07 -0700
-foolscap (0.1.0+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Mon, 19 Mar 2007 23:11:35 -0700
-
foolscap (0.1.0) unstable; urgency=low
* new release
-- Brian Warner Thu, 15 Mar 2007 16:56:16 -0700
-foolscap (0.0.7+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Mon, 22 Jan 2007 12:41:18 -0800
-
foolscap (0.0.7) unstable; urgency=low
* new release
-- Brian Warner Tue, 16 Jan 2007 12:03:00 -0800
-foolscap (0.0.6+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Thu, 4 Jan 2007 18:45:04 -0500
-
foolscap (0.0.6) unstable; urgency=low
* new release
-- Brian Warner Mon, 18 Dec 2006 12:10:51 -0800
-foolscap (0.0.5+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Tue, 14 Nov 2006 21:24:17 -0800
-
foolscap (0.0.5) unstable; urgency=low
* new release
-- Brian Warner Sat, 4 Nov 2006 23:20:46 -0800
-foolscap (0.0.4+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Tue, 31 Oct 2006 23:38:34 -0800
-
foolscap (0.0.4) unstable; urgency=low
* new release
-- Brian Warner 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
diff --git a/src/foolscap/misc/edgy/debian/rules b/src/foolscap/misc/edgy/debian/rules
index 3ea18835..252672cc 100644
--- a/src/foolscap/misc/edgy/debian/rules
+++ b/src/foolscap/misc/edgy/debian/rules
@@ -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
diff --git a/src/foolscap/misc/feisty/debian/changelog b/src/foolscap/misc/feisty/debian/changelog
index 93eb7f3a..9699a6df 100644
--- a/src/foolscap/misc/feisty/debian/changelog
+++ b/src/foolscap/misc/feisty/debian/changelog
@@ -1,14 +1,14 @@
-foolscap (0.1.3) unstable; urgency=low
+foolscap (0.1.4) unstable; urgency=low
* new release
- -- Brian Warner Wed, 2 May 2007 14:55:49 -0700
+ -- Brian Warner Mon, 14 May 2007 22:37:04 -0700
-foolscap (0.1.2+) unstable; urgency=low
+foolscap (0.1.3) unstable; urgency=low
- * bump revision while between releases
+ * new release
- -- Brian Warner Fri, 13 Apr 2007 00:22:10 -0700
+ -- Brian Warner Wed, 2 May 2007 14:55:49 -0700
foolscap (0.1.2) unstable; urgency=low
@@ -16,79 +16,43 @@ foolscap (0.1.2) unstable; urgency=low
-- Brian Warner Wed, 4 Apr 2007 12:32:46 -0700
-foolscap (0.1.1+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Wed, 4 Apr 2007 10:32:22 -0700
-
foolscap (0.1.1) unstable; urgency=low
* new release
-- Brian Warner Tue, 3 Apr 2007 20:48:07 -0700
-foolscap (0.1.0+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Mon, 19 Mar 2007 23:11:35 -0700
-
foolscap (0.1.0) unstable; urgency=low
* new release
-- Brian Warner Thu, 15 Mar 2007 16:56:16 -0700
-foolscap (0.0.7+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Mon, 22 Jan 2007 12:41:18 -0800
-
foolscap (0.0.7) unstable; urgency=low
* new release
-- Brian Warner Tue, 16 Jan 2007 12:03:00 -0800
-foolscap (0.0.6+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Thu, 4 Jan 2007 18:45:04 -0500
-
foolscap (0.0.6) unstable; urgency=low
* new release
-- Brian Warner Mon, 18 Dec 2006 12:10:51 -0800
-foolscap (0.0.5+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Tue, 14 Nov 2006 21:24:17 -0800
-
foolscap (0.0.5) unstable; urgency=low
* new release
-- Brian Warner Sat, 4 Nov 2006 23:20:46 -0800
-foolscap (0.0.4+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Tue, 31 Oct 2006 23:38:34 -0800
-
foolscap (0.0.4) unstable; urgency=low
* new release
-- Brian Warner 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
diff --git a/src/foolscap/misc/feisty/debian/rules b/src/foolscap/misc/feisty/debian/rules
index 3ea18835..252672cc 100644
--- a/src/foolscap/misc/feisty/debian/rules
+++ b/src/foolscap/misc/feisty/debian/rules
@@ -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
diff --git a/src/foolscap/misc/sarge/debian/changelog b/src/foolscap/misc/sarge/debian/changelog
index f3331be3..9699a6df 100644
--- a/src/foolscap/misc/sarge/debian/changelog
+++ b/src/foolscap/misc/sarge/debian/changelog
@@ -1,14 +1,14 @@
-foolscap (0.1.3) unstable; urgency=low
+foolscap (0.1.4) unstable; urgency=low
* new release
- -- Brian Warner Wed, 2 May 2007 14:55:49 -0700
+ -- Brian Warner Mon, 14 May 2007 22:37:04 -0700
-foolscap (0.1.2+) unstable; urgency=low
+foolscap (0.1.3) unstable; urgency=low
- * bump revision while between releases
+ * new release
- -- Brian Warner Fri, 13 Apr 2007 00:22:10 -0700
+ -- Brian Warner Wed, 2 May 2007 14:55:49 -0700
foolscap (0.1.2) unstable; urgency=low
@@ -16,79 +16,43 @@ foolscap (0.1.2) unstable; urgency=low
-- Brian Warner Wed, 4 Apr 2007 12:32:46 -0700
-foolscap (0.1.1+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Wed, 4 Apr 2007 10:32:22 -0700
-
foolscap (0.1.1) unstable; urgency=low
* new release
-- Brian Warner Tue, 3 Apr 2007 20:48:07 -0700
-foolscap (0.1.0+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Mon, 19 Mar 2007 23:11:35 -0700
-
foolscap (0.1.0) unstable; urgency=low
* new release
-- Brian Warner Thu, 15 Mar 2007 16:56:16 -0700
-foolscap (0.0.7+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Mon, 22 Jan 2007 12:41:18 -0800
-
foolscap (0.0.7) unstable; urgency=low
* new release
-- Brian Warner Tue, 16 Jan 2007 12:03:00 -0800
-foolscap (0.0.6+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Thu, 4 Jan 2007 18:45:04 -0500
-
foolscap (0.0.6) unstable; urgency=low
* new release
-- Brian Warner Mon, 18 Dec 2006 12:10:51 -0800
-foolscap (0.0.5+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Tue, 14 Nov 2006 21:24:17 -0800
-
foolscap (0.0.5) unstable; urgency=low
* new release
-- Brian Warner Sat, 4 Nov 2006 23:20:46 -0800
-foolscap (0.0.4+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Tue, 31 Oct 2006 23:38:34 -0800
-
foolscap (0.0.4) unstable; urgency=low
* new release
- -- Brian Warner Thu, 26 Oct 2006 00:46:11 -0700
+ -- Brian Warner 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
diff --git a/src/foolscap/misc/sarge/debian/rules b/src/foolscap/misc/sarge/debian/rules
index eb2284d2..8ee04543 100644
--- a/src/foolscap/misc/sarge/debian/rules
+++ b/src/foolscap/misc/sarge/debian/rules
@@ -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
diff --git a/src/foolscap/misc/sid/debian/changelog b/src/foolscap/misc/sid/debian/changelog
index 93eb7f3a..9699a6df 100644
--- a/src/foolscap/misc/sid/debian/changelog
+++ b/src/foolscap/misc/sid/debian/changelog
@@ -1,14 +1,14 @@
-foolscap (0.1.3) unstable; urgency=low
+foolscap (0.1.4) unstable; urgency=low
* new release
- -- Brian Warner Wed, 2 May 2007 14:55:49 -0700
+ -- Brian Warner Mon, 14 May 2007 22:37:04 -0700
-foolscap (0.1.2+) unstable; urgency=low
+foolscap (0.1.3) unstable; urgency=low
- * bump revision while between releases
+ * new release
- -- Brian Warner Fri, 13 Apr 2007 00:22:10 -0700
+ -- Brian Warner Wed, 2 May 2007 14:55:49 -0700
foolscap (0.1.2) unstable; urgency=low
@@ -16,79 +16,43 @@ foolscap (0.1.2) unstable; urgency=low
-- Brian Warner Wed, 4 Apr 2007 12:32:46 -0700
-foolscap (0.1.1+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Wed, 4 Apr 2007 10:32:22 -0700
-
foolscap (0.1.1) unstable; urgency=low
* new release
-- Brian Warner Tue, 3 Apr 2007 20:48:07 -0700
-foolscap (0.1.0+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Mon, 19 Mar 2007 23:11:35 -0700
-
foolscap (0.1.0) unstable; urgency=low
* new release
-- Brian Warner Thu, 15 Mar 2007 16:56:16 -0700
-foolscap (0.0.7+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Mon, 22 Jan 2007 12:41:18 -0800
-
foolscap (0.0.7) unstable; urgency=low
* new release
-- Brian Warner Tue, 16 Jan 2007 12:03:00 -0800
-foolscap (0.0.6+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Thu, 4 Jan 2007 18:45:04 -0500
-
foolscap (0.0.6) unstable; urgency=low
* new release
-- Brian Warner Mon, 18 Dec 2006 12:10:51 -0800
-foolscap (0.0.5+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Tue, 14 Nov 2006 21:24:17 -0800
-
foolscap (0.0.5) unstable; urgency=low
* new release
-- Brian Warner Sat, 4 Nov 2006 23:20:46 -0800
-foolscap (0.0.4+) unstable; urgency=low
-
- * bump revision while between releases
-
- -- Brian Warner Tue, 31 Oct 2006 23:38:34 -0800
-
foolscap (0.0.4) unstable; urgency=low
* new release
-- Brian Warner 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
diff --git a/src/foolscap/misc/sid/debian/rules b/src/foolscap/misc/sid/debian/rules
index 3ea18835..252672cc 100644
--- a/src/foolscap/misc/sid/debian/rules
+++ b/src/foolscap/misc/sid/debian/rules
@@ -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
diff --git a/src/foolscap/misc/testutils/figleaf.py b/src/foolscap/misc/testutils/figleaf.py
index a24d4fab..03d8c08d 100644
--- a/src/foolscap/misc/testutils/figleaf.py
+++ b/src/foolscap/misc/testutils/figleaf.py
@@ -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)
--
2.45.2