PK!Ccarly/__init__.pyfrom __future__ import print_function from .clock import cancelDelayedCalls, advanceTime from .context import Context from .hook import hook, cleanup, decoder, register from .threads import waitForThreads PK!wcarly/clock.pyfrom __future__ import print_function from sys import stderr from twisted.internet import reactor from twisted.internet.defer import Deferred DEFAULT_TIMEOUT = 0.2 def withTimeout(deferred, timeout=None): if timeout is None: timeout = DEFAULT_TIMEOUT return deferred.addTimeout(timeout, reactor) def cancelDelayedCalls(expected=2): """ :param expected: The number of calls to cancel. If the number found does not match this, none of them will be cancelled so that trial's cleanup can tell you more about them. Why the default of 2? Hopefully you're only testing one delayed calll generator at a time, and there's one for trial's 2 minute timeout. """ calls = reactor.getDelayedCalls() if len(calls) == expected: for call in calls: call.cancel() else: print('\n\nExpected {} delayed calls, found {}'.format(expected, len(calls)), file=stderr) def advanceTime(seconds): """ Advance the reactor time by the number of seconds or partial seconds specified. """ now = reactor.seconds() for call in reactor.getDelayedCalls(): currentSecondsFromNow = call.getTime() - now newSecondsFromNow = max(0, currentSecondsFromNow - seconds) call.reset(newSecondsFromNow) # give the reactor a chance to run calls we're brought forward: d = Deferred() reactor.callLater(0, lambda: d.callback(None)) return d PK!e6scarly/context.pyfrom attr import make_class 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, DatagramProtocol from .clock import withTimeout, cancelDelayedCalls from .hook import hook, cleanup TCPClient = make_class('TCPClient', ['protocolClass', 'connection', 'clientProtocol', 'serverProtocol']) class TCPServer(object): def __init__(self, protocolClass, port): self.protocolClass = protocolClass self.port = port host = self.port.getHost() self.targetHost = host.host self.targetPort = host.port class UDP(DatagramProtocol): def __init__(self, port, protocol): self.port = port self.protocol = protocol host = self.port.getHost() self.targetHost = host.host self.targetPort = host.port def startProtocol(self): self.transport.connect(self.targetHost, self.targetPort) def send(self, datagram): self.transport.write(datagram) 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) withTimeout(d, timeout) return gatherResults(deferreds) @inlineCallbacks def cleanup(self, timeout=None, delayedCalls=None): yield self._cleanup(self.cleanups['connections'], timeout) yield self._cleanup(self.cleanups['listens'], timeout) cleanup() if delayedCalls: cancelDelayedCalls(delayedCalls) def cleanupServers(self, *ports): self.cleanups['listens'].extend( partial(maybeDeferred, port.stopListening) for port in ports ) def makeTCPServer(self, protocol, factory=None, interface='127.0.0.1', installProtocol=True): hook(protocol, 'connectionMade') if factory is None: factory = Factory() if installProtocol: 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.cleanupServers(server.port) @inlineCallbacks def makeTCPClient(self, protocol, server, factory=None, when='connectionMade'): hook(protocol, when) if factory is None: factory = ClientFactory() factory.protocol = protocol host = server.port.getHost() connection = reactor.connectTCP(host.host, host.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) self.cleanups['connections'].extend(( partial(maybeDeferred, client.connection.disconnect), partial(client.clientProtocol.connectionLost.called, timeout=timeout), partial(client.serverProtocol.connectionLost.called, timeout=timeout), )) def makeUDP(self, protocol, interface='127.0.0.1'): hook(protocol.__class__, 'datagramReceived') port = reactor.listenUDP(0, protocol, interface) udp = UDP(port, protocol) sendPort = reactor.listenUDP(0, udp, interface) self.cleanupServers(port, sendPort) return udp PK!ѕJ?? carly/hook.pyfrom __future__ import print_function from pprint import pformat from attr import attrs, attrib from collections import defaultdict from twisted.internet.defer import Deferred, inlineCallbacks, returnValue from .clock import withTimeout @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] yield withTimeout(deferred, timeout) 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(): if instance is None: continue 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)) if self.decoder is not 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) if decoder is Result: returnValue(result.result) returnValue(decoder(*result.args, **result.kw)) class UnconsumedCalls(AssertionError): def __init__(self, unconsumed): self.unconsumed = unconsumed def __str__(self): return '\n'+pformat(self.unconsumed) class HookedCall(object): all = {} registeredClasses = set() 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 hook(cls, 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. """ # opportunistic register: cls.register(class_) method = getattr(class_, name) if not isinstance(method, HookedCall): method = HookedCall(class_, name, decoder, once) return method def unHook(self): setattr(self.class_, self.name, self.original) @classmethod def register(cls, class_): if class_ not in cls.registeredClasses: cls.registeredClasses.add(class_) for name, obj in vars(class_).items(): if getattr(obj, '__carly__decoder__', False): cls.hook(class_, name, decoder=Result) @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 hook.unHook() cls.registeredClasses = set() if allUnconsumed: raise UnconsumedCalls(allUnconsumed) hook = HookedCall.hook cleanup = HookedCall.cleanup register = HookedCall.register def decoder(method): """ Mark a method as decoder when it is hooked. """ method.__carly__decoder__ = True return method PK!۔carly/threads.pyfrom time import sleep from twisted.internet import reactor from twisted.internet.threads import deferToThread from .clock import withTimeout def pendingIsEmpty(): while True: stats = reactor.threadpool._team.statistics() if not (stats.backloggedWorkCount or stats.busyWorkerCount > 1): break sleep(0.001) def waitForThreads(timeout=None): return withTimeout(deferToThread(pendingIsEmpty), timeout) PK!HqfWXcarly-0.7.1.dist-info/WHEEL A н#f."jm)!fb҅~ܴA,mTD}E n0H饹*|D[¬c i=0(q3PK!H[+carly-0.7.1.dist-info/METADATAKo0{l*B@*,[nٵ4ˆ$CCrz\@Kt+c% :aAYza̼+<S/ ?HTg»r.qp 1ʰ ejp&ڝR"ߒPRzQbJwxkVdm 06;mI*O5}v_(,24?R\_ng$E6HP^;`۫Y0/:>T]ˁQ0 ԞYv/·KpJ\Uҍ`Px'* ~?l!\y ?BXEsEseAINj,;szxb*C!h6UoD4iQc_#q?(*>3+y%X֐6e6*hrc LVPK!Ccarly/__init__.pyPK!wcarly/clock.pyPK!e6scarly/context.pyPK!ѕJ?? carly/hook.pyPK!۔ -carly/threads.pyPK!HqfWX/carly-0.7.1.dist-info/WHEELPK!H[+/carly-0.7.1.dist-info/METADATAPK!HIg|.1carly-0.7.1.dist-info/RECORDPK2