PK!dNddcarly/__init__.pyfrom __future__ import print_function from .hook import hook, cleanup from .context import Context PK!!B B carly/context.pyfrom collections import namedtuple from functools import partial from twisted.internet import reactor from twisted.internet.defer import ( inlineCallbacks, gatherResults, maybeDeferred, returnValue ) from twisted.internet.protocol import Factory, ClientFactory from .hook import hook, cleanup from .timeout import resolveTimeout TCPServer = namedtuple('TCPServer', ['protocolClass', 'port']) TCPClient = namedtuple('TCPClient', ['protocolClass', 'connection', 'clientProtocol', 'serverProtocol']) class Context(object): def __init__(self): self.cleanups = { 'connections': [], 'listens': [], } def _cleanup(self, cleanups, timeout): deferreds = [] for p in cleanups: d = p() deferreds.append(d) d.addTimeout(timeout, reactor) return gatherResults(deferreds) @inlineCallbacks def cleanup(self, timeout=None): timeout = resolveTimeout(timeout) yield self._cleanup(self.cleanups['connections'], timeout) yield self._cleanup(self.cleanups['listens'], timeout) cleanup() def makeTCPServer(self, protocol, factory=None, interface='127.0.0.1'): hook(protocol, 'connectionMade') if factory is None: factory = Factory() factory.protocol = protocol port = reactor.listenTCP(0, factory, interface=interface) server = TCPServer(protocol, port) self.cleanupTCPServer(server) return server def cleanupTCPServer(self, server): hook(server.protocolClass, 'connectionLost', once=True) self.cleanups['listens'].append( partial(maybeDeferred, server.port.stopListening) ) @inlineCallbacks def makeTCPClient(self, protocol, server, factory=None, when='connectionMade'): hook(protocol, when) if factory is None: factory = ClientFactory() factory.protocol = protocol connection = reactor.connectTCP('localhost', server.port.getHost().port, factory) clientProtocol, serverProtocol = yield gatherResults([ getattr(protocol, when).protocol(), server.protocolClass.connectionMade.protocol(), ]) client = TCPClient(protocol, connection, clientProtocol, serverProtocol) self.cleanupTCPClient(client) returnValue(client) def cleanupTCPClient(self, client, timeout=None, when='connectionLost'): hook(client.protocolClass, when, once=True) timeout = resolveTimeout(timeout) self.cleanups['connections'].extend(( partial(maybeDeferred, client.connection.disconnect), partial(client.clientProtocol.connectionLost.called, timeout=timeout), partial(client.serverProtocol.connectionLost.called, timeout=timeout), )) PK!# : carly/hook.pyfrom __future__ import print_function from pprint import pformat from attr import attrs, attrib from collections import namedtuple, defaultdict from twisted.internet import reactor from twisted.internet.defer import Deferred, inlineCallbacks, returnValue from .timeout import resolveTimeout @attrs(slots=True) class Result(object): protocol = attrib(repr=False) args = attrib() kw = attrib() result = attrib(repr=False) consumed = attrib(repr=False, default=False) class HookState(object): def __init__(self, once): self.once = once self.instanceDeferreds = defaultdict(Deferred) self.instanceQueues = defaultdict(list) def handleCall(self, result): instance = result.protocol for target in None, instance: self.instanceQueues[target].append(result) deferred = self.instanceDeferreds[target] if target is None or not self.once: del self.instanceDeferreds[target] deferred.callback(result) @inlineCallbacks def expectCallback(self, instance, timeout): queue = self.instanceQueues[instance] if not queue: deferred = self.instanceDeferreds[instance] timeout = resolveTimeout(timeout) yield deferred.addTimeout(timeout, reactor) if self.once: result = queue[0] else: result = queue.pop(0) result.consumed = True returnValue(result) def cleanup(self): allUnconsumed = {} for instance, queue in self.instanceQueues.items(): unconsumed = tuple(r for r in queue if not r.consumed) if unconsumed: allUnconsumed[instance] = unconsumed queue[:] = [] return allUnconsumed class BoundHook(object): def __init__(self, state, original, instance, decoder): self.state = state self.original = original self.instance = instance self.decoder = decoder def __call__(self, *args, **kw): result = self.original(self.instance, *args, **kw) self.state.handleCall(Result(self.instance, args, kw, result)) return result @inlineCallbacks def called(self, decoder=None, timeout=None): result = yield self.state.expectCallback(self.instance, timeout) decoder = decoder or self.decoder if decoder is None: returnValue(result) returnValue(decoder(*result.args, **result.kw)) class UnconsumedCalls(AssertionError): def __init__(self, unconsumed): self.unconsumed = unconsumed def __str__(self): return pformat(self.unconsumed) class HookedCall(object): all = {} def __init__(self, class_, name, decoder=None, once=False): self.original = getattr(class_, name) self.class_ = class_ self.name = name self.state = HookState(once) self.decoder = decoder self.once = once setattr(class_, name, self) self.all[class_, name] = self def __get__(self, instance, owner): if instance is None: return self return BoundHook(self.state, self.original, instance, self.decoder) @inlineCallbacks def protocol(self, timeout=None): result = yield self.state.expectCallback(None, timeout) returnValue(result.protocol) @classmethod def cleanup(cls): allUnconsumed = {} for key, hook in cls.all.items(): setattr(hook.class_, hook.name, hook.original) unconsumed = hook.state.cleanup() if unconsumed: allUnconsumed[key] = unconsumed if allUnconsumed: raise UnconsumedCalls(allUnconsumed) def hook(class_, name, decoder=None, once=False): """ Hook a method on a hooked class such that tests can wait on it being called on a particular instance. :param name: The name of the method to hook. :param decoder: A callable that will be used to decode the result of the method being called. It should take the same arguments and parameters as the method being hooked and should return whatever is required by the test that is going to wait on calls to this method. :param once: Only expect one call on this method. Multiple waits in a test will all end up waiting on the same call. This is most useful when hooking connections going away, where the test may want to explicitly wait for this, while the tear down of the test will also need to wait on it. """ method = getattr(class_, name) if not isinstance(method, HookedCall): method = HookedCall(class_, name, decoder, once) return method cleanup = HookedCall.cleanup PK!6carly/timeout.pyDEFAULT_TIMEOUT = 0.2 def resolveTimeout(specified): if specified is None: return DEFAULT_TIMEOUT return specified PK!HqfWXcarly-0.4.0.dist-info/WHEEL A н#f."jm)!fb҅~ܴA,mTD}E n0H饹*|D[¬c i=0(q3PK!HX6+carly-0.4.0.dist-info/METADATAOO1smQQ#DXeXuڅ/ھ{3M^頓/do)ȶHEb/.BKveK+k5 & ,zs`\ nfї|TL;#o8 ;xod1)c=.S16B !V0ܰ4aN ZmZ{5Ia7ky`:,q/upXfL9kkUܯg8էNS^:nqqSX5B]zq~v_S:ؙ#93PK!HY{Scarly-0.4.0.dist-info/RECORDur0}%XE@bKs6jPn%}s7oC1v{V/ޯ` ,Z`PL1m9C-0t dʋqj/&s5OR1=~pnG,oQ6F_2eG;7qx8z["lZM](l+~+|e.vC$2sKBOCÑk`旣 k/D=)F?g:qQJQUU$ƿ1M+h3 S`!\N /zPK!dNddcarly/__init__.pyPK!!B B carly/context.pyPK!# :  carly/hook.pyPK!6carly/timeout.pyPK!HqfWXcarly-0.4.0.dist-info/WHEELPK!HX6+ carly-0.4.0.dist-info/METADATAPK!HY{S!carly-0.4.0.dist-info/RECORDPK#