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!نIIcarly/clock.pyfrom __future__ import print_function from twisted.internet import reactor from twisted.internet.defer import Deferred, inlineCallbacks 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() strings = [] for call in calls: strings.append(str(call)) call.cancel() if len(calls) != expected: raise AssertionError( '\n\nExpected {} delayed calls, found {}: {}'.format( expected, len(calls), strings )) def _pump(): d = Deferred() reactor.callLater(0, lambda: d.callback(None)) return d @inlineCallbacks def advanceTime(seconds): """ Advance the reactor time by the number of seconds or partial seconds specified. """ yield _pump() 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: yield _pump() PK!Nb<<carly/context.pyfrom functools import partial from twisted.internet.defer import ( inlineCallbacks, gatherResults, maybeDeferred ) from .clock import withTimeout, cancelDelayedCalls from .hook import cleanup from .threads import waitForThreads 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, threads=False, delayedCalls=None): yield self._cleanup(self.cleanups['connections'], timeout) yield self._cleanup(self.cleanups['listens'], timeout) cleanup() if threads: yield waitForThreads() if delayedCalls: cancelDelayedCalls(delayedCalls) def cleanupServers(self, *ports): self.cleanups['listens'].extend( partial(maybeDeferred, port.stopListening) for port in ports ) def cleanupClient(self, client, close, timeout=None): if isinstance(close, str): name = close close = lambda client: getattr(client.clientProtocol, name)() self.cleanups['connections'].extend(( partial(maybeDeferred, close, client), partial(client.clientProtocol.connectionLost.called, timeout=timeout), partial(client.serverProtocol.connectionLost.called, timeout=timeout), )) PK!Voww 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 ORIGINAL_IS_DECODER = object() @attrs(slots=True) class Call(object): protocol = attrib(repr=False) args = attrib() kw = attrib() result = attrib() 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, call): instance = call.protocol for target in None, instance: self.instanceQueues[target].append(call) deferred = self.instanceDeferreds[target] if target is None or not self.once: del self.instanceDeferreds[target] deferred.callback(call) @inlineCallbacks def expectCallback(self, instance, timeout): queue = self.instanceQueues[instance] if not queue: deferred = self.instanceDeferreds[instance] yield withTimeout(deferred, timeout) if self.once: call = queue[0] else: call = queue.pop(0) call.consumed = True returnValue(call) 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 @inlineCallbacks def called(self, decoder, timeout, instance): call = yield self.state.expectCallback(instance, timeout) decoder = decoder or self.decoder if decoder is None: returnValue(call) if decoder is ORIGINAL_IS_DECODER: returnValue(call.result) returnValue(decoder(*call.args, **call.kw)) 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(Call(self.instance, args, kw, result)) if self.decoder is not ORIGINAL_IS_DECODER: return result def called(self, decoder=None, timeout=None): return called(self, decoder, timeout, self.instance) 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): call = yield self.state.expectCallback(None, timeout) returnValue(call.protocol) def called(self, decoder=None, timeout=None): return called(self, decoder, timeout, instance=None) @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 class_: The class on which to hook the named method. :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=ORIGINAL_IS_DECODER) @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() cls.all = {} 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!c carly/tcp.pyfrom attr import make_class from twisted.internet.defer import ( inlineCallbacks, gatherResults, returnValue ) from twisted.internet.protocol import Factory, ClientFactory from .hook import hook TCPClient = make_class('TCPClient', ['protocolClass', '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 def makeTCPServer(context, protocol, factory=None, interface='127.0.0.1', installProtocol=True): from twisted.internet import reactor 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) hook(server.protocolClass, 'connectionLost', once=True) context.cleanupServers(server.port) return server def disconnect(client): client.clientProtocol.transport.loseConnection() def makeTCPClient(context, protocol, server, factory=None, when='connectionMade', close=disconnect): from twisted.internet import reactor hook(protocol, when) if factory is None: factory = ClientFactory() factory.protocol = protocol host = server.port.getHost() reactor.connectTCP(host.host, host.port, factory) return waitForClient( context, getattr(protocol, when), server.protocolClass.connectionMade, close ) @inlineCallbacks def waitForClient(context, clientConnected, serverConnected, close=disconnect): clientProtocol, serverProtocol = yield gatherResults([ clientConnected.protocol(), serverConnected.protocol(), ]) client = TCPClient(clientProtocol.__class__, clientProtocol, serverProtocol) hook(client.protocolClass, 'connectionLost', once=True) context.cleanupClient(client, close) returnValue(client) 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!G88 carly/udp.pyfrom twisted.internet.protocol import DatagramProtocol from .hook import hook 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) def makeUDP(context, protocol, interface='127.0.0.1'): from twisted.internet import reactor hook(protocol.__class__, 'datagramReceived') port = reactor.listenUDP(0, protocol, interface) udp = UDP(port, protocol) sendPort = reactor.listenUDP(0, udp, interface) context.cleanupServers(port, sendPort) return udp PK!9EEcarly/websocket.pyfrom autobahn.websocket.protocol import WebSocketClientProtocol as AbstractProtocol from autobahn.twisted import WebSocketClientProtocol, WebSocketClientFactory from .tcp import makeTCPClient def makeWebSocketClient(context, server, protocol=None, factory=None, factoryClass=None, endpoint='', close='sendClose'): if factory is None: url = "ws://{}:{}{}".format(server.targetHost, server.targetPort, endpoint) if factoryClass is None: factoryClass = WebSocketClientFactory factory = factoryClass(url) if protocol is None: protocol = factory.protocol if protocol is AbstractProtocol: # bug in autobahn? protocol = WebSocketClientProtocol return makeTCPClient(context, protocol, server, factory, when='onOpen', close=close) PK!H|n-WYcarly-0.10.0.dist-info/WHEEL A н#Z;/" bFF]xzwK;<*mTֻ0*Ri.4Vm0[H, JPK!H"mcarly-0.10.0.dist-info/METADATAT]o0}-ш2NL`&l]xnҭӐDbs99FQ$?lg߄ f{=c1!:a/o>S{od;R2c邊ׇ)U)و6&WuIxć؎;j&KÂ1y 4}NlZ߼xaFA3z6IQ(eSwT{=-[+:\\؁Z QcEk"Metx7gXnt~u͕K PK!H\carly-0.10.0.dist-info/RECORDuI@},e 7$`A黺C#x(M|>(rKKE*><%(Cѽ"2X@pYɦ3Ek_6,A lXt7]LjxvΏYK(1{]4I&IzQF{S\_7K}Ϡ0#Xؾ=VHk1 2E' Da+1+üK_C~2=@UEe2} r`g""0mx=Ʈ 7ͧ>n}2p*qk-hb; ) uP;Q|k 1rX'=SXPDĆ$6&.zM~ Q;dxՍ[e͝(\Tw|lqy/VP)m9ݸrd^w 5-hcH`~su(haPK!Ccarly/__init__.pyPK!نIIcarly/clock.pyPK!Nb<<rcarly/context.pyPK!Voww carly/hook.pyPK!c ~%carly/tcp.pyPK!۔-carly/threads.pyPK!G88 /carly/udp.pyPK!9EE 3carly/websocket.pyPK!H|n-WY6carly-0.10.0.dist-info/WHEELPK!H"m7carly-0.10.0.dist-info/METADATAPK!H\A9carly-0.10.0.dist-info/RECORDPK {;