def test_promise_all_if(): p1 = Promise() p2 = Promise() pd1 = Promise.all([p1, p2]) pd2 = Promise.all([p1]) pd3 = Promise.all([]) pd3._wait() assert p1.is_pending assert p2.is_pending assert pd1.is_pending assert pd2.is_pending assert pd3.is_fulfilled p1.do_resolve(5) p1._wait() pd2._wait() assert p1.is_fulfilled assert p2.is_pending assert pd1.is_pending assert pd2.is_fulfilled p2.do_resolve(10) p2._wait() pd1._wait() pd2._wait() assert p1.is_fulfilled assert p2.is_fulfilled assert pd1.is_fulfilled assert pd2.is_fulfilled assert 5 == p1.get() assert 10 == p2.get() assert 5 == pd1.get()[0] assert 5 == pd2.get()[0] assert 10 == pd1.get()[1] assert [] == pd3.get()
def test_3_2_6_3_if_rejected(): """ Testing return of pending promises to make sure they are properly chained. This covers the case where the root promise is rejected before the chaining is defined. """ p1 = Promise() p1.do_reject(Exception("Error")) pending = Promise() pending.do_resolve(10) pr = p1.then(None, lambda r: pending) pending._wait() assert pending.is_fulfilled assert 10 == pending.get() pr._wait() assert pr.is_fulfilled assert 10 == pr.get() p2 = Promise() p2.do_reject(Exception("Error")) bad = Promise() bad.do_reject(Exception("Assertion")) pr = p2.then(None, lambda r: bad) bad._wait() assert bad.is_rejected assert_exception(bad.reason, Exception, "Assertion") pr._wait() assert pr.is_rejected assert_exception(pr.reason, Exception, "Assertion")
def test_then_all(): p = Promise() handlers = [ ((lambda x: x * x), (lambda r: 1)), {"success": (lambda x: x + x), "failure": (lambda r: 2)}, ] results = ( p.then_all() + p.then_all([lambda x: x]) + p.then_all([(lambda x: x * x, lambda r: 1)]) + p.then_all(handlers) ) p.do_resolve(4) assert [r.get() for r in results] == [4, 16, 16, 8] p = Promise() handlers = [ ((lambda x: x * x), (lambda r: 1)), {"success": (lambda x: x + x), "failure": (lambda r: 2)}, ] results = ( p.then_all() + p.then_all([(lambda x: x * x, lambda r: 1)]) + p.then_all(handlers) ) p.do_reject(Exception()) assert [r.get() for r in results] == [1, 1, 2]
def test_done(): counter = [0] r = Promise() def inc(_): counter[0] += 1 def dec(_): counter[0] -= 1 def end(_): r.do_resolve(None) p = Promise() p.done(inc, dec) p.done(inc, dec) p.done(end) p.do_resolve(4) Promise.wait(r) assert counter[0] == 2 r = Promise() counter = [0] p = Promise() p.done(inc, dec) p.done(inc, dec) p.done(None, end) p.do_reject(Exception()) Promise.wait(r) assert counter[0] == -2
def test_dict_promise_when(promise_for_dict): p1 = Promise() p2 = Promise() d = {"a": p1, "b": p2} pd1 = promise_for_dict(d) pd2 = promise_for_dict({"a": p1}) pd3 = promise_for_dict({}) assert p1.is_pending assert p2.is_pending assert pd1.is_pending assert pd2.is_pending pd3._wait() assert pd3.is_fulfilled p1.do_resolve(5) p1._wait() pd2._wait() assert p1.is_fulfilled assert p2.is_pending assert pd1.is_pending assert pd2.is_fulfilled p2.do_resolve(10) p2._wait() pd1._wait() assert p1.is_fulfilled assert p2.is_fulfilled assert pd1.is_fulfilled assert pd2.is_fulfilled assert 5 == p1.get() assert 10 == p2.get() assert 5 == pd1.get()["a"] assert 5 == pd2.get()["a"] assert 10 == pd1.get()["b"] assert {} == pd3.get()
def test_then_all(): p = Promise() handlers = [ ((lambda x: x * x), (lambda r: 1)), { 'success': (lambda x: x + x), 'failure': (lambda r: 2) }, ] results = p.then_all() + p.then_all([lambda x: x]) + p.then_all( [(lambda x: x * x, lambda r: 1)]) + p.then_all(handlers) p.do_resolve(4) assert [r.get() for r in results] == [4, 16, 16, 8] p = Promise() handlers = [ ((lambda x: x * x), (lambda r: 1)), { 'success': (lambda x: x + x), 'failure': (lambda r: 2) }, ] results = p.then_all() + p.then_all( [(lambda x: x * x, lambda r: 1)]) + p.then_all(handlers) p.do_reject(Exception()) assert [r.get() for r in results] == [1, 1, 2]
def test_done(): counter = [0] e = Event() def inc(_): counter[0] += 1 e.set() def dec(_): counter[0] -= 1 e.set() p = Promise() p.done(inc, dec) p.done(inc, dec) p.do_resolve(4) e.wait() assert counter[0] == 2 counter = [0] p = Promise() e = Event() p.done(inc, dec) p.done(inc, dec) p.do_reject(Exception()) e.wait() assert counter[0] == -2
def test_resolve_promise_subclass(): class MyPromise(Promise): pass p = Promise() p.do_resolve(10) m_p = MyPromise.resolve(p) assert isinstance(m_p, MyPromise) assert m_p.get() == p.get()
def test_3_2_1(): """ Test that the arguments to 'then' are optional. """ p1 = Promise() p2 = p1.then() p3 = Promise() p4 = p3.then() p1.do_resolve(5) p3.do_reject(Exception("How dare you!"))
def test_3_2_6_4_fulfilled(): """ Handles the case where the arguments to then are values, not functions or promises. """ p1 = Promise() p1.do_resolve(10) p2 = p1.then(5) assert 10 == p1.get() p2._wait() assert p2.is_fulfilled assert 10 == p2.get()
def test_exceptions(): def throws(v): assert False p1 = Promise() p1.then(throws) p1.do_resolve(5) p2 = Promise() p2.catch(throws) p2.do_reject(Exception()) with raises(Exception) as excinfo: p2.get()
def test_dict_promise_if(promise_for_dict): p1 = Promise() p2 = Promise() d = {"a": p1, "b": p2} pd = promise_for_dict(d) assert p1.is_pending assert p2.is_pending assert pd.is_pending p1.do_resolve(5) p1._wait() assert p1.is_fulfilled assert p2.is_pending assert pd.is_pending p2.do_resolve(10) p2._wait() assert p1.is_fulfilled assert p2.is_fulfilled
def test_promise_resolved_after(): """ The first argument to 'then' must be called when a promise is fulfilled. """ c = Counter() def check(v, c): assert v == 5 c.tick() p1 = Promise() p2 = p1.then(lambda v: check(v, c)) p1.do_resolve(5) Promise.wait(p2) assert 1 == c.value()
def test_done_all(): counter = [0] def inc(_): counter[0] += 1 def dec(_): counter[0] -= 1 p = Promise() r = Promise() p.done_all() p.done_all([(inc, dec)]) p.done_all([ (inc, dec), (inc, dec), { 'success': inc, 'failure': dec }, lambda _: r.do_resolve(None) ]) p.do_resolve(4) Promise.wait(r) assert counter[0] == 4 p = Promise() r = Promise() p.done_all() p.done_all([inc]) p.done_all([(inc, dec)]) p.done_all([ (inc, dec), { 'success': inc, 'failure': dec }, (None, lambda _: r.do_resolve(None)) ]) p.do_reject(Exception("Uh oh!")) Promise.wait(r) assert counter[0] == 1
def test_done_all(): counter = [0] e = Event() def inc(_): counter[0] += 1 def dec(_): counter[0] -= 1 p = Promise() p.done_all() p.done_all([(inc, dec)]) p.done_all([ (inc, dec), (inc, dec), { 'success': inc, 'failure': dec }, lambda _: e.set() ]) p.do_resolve(4) e.wait() assert counter[0] == 4 e = Event() p = Promise() p.done_all() p.done_all([inc]) p.done_all([(inc, dec)]) p.done_all([ (inc, dec), { 'success': inc, 'failure': dec }, (None, lambda _: e.set()) ]) p.do_reject(Exception("Uh oh!")) e.wait() assert counter[0] == 1
def test_promise_all_when_mixed_promises(): p1 = Promise() p2 = Promise() pl = Promise.all([p1, 32, p2, False, True]) assert p1.is_pending assert p2.is_pending assert pl.is_pending p1.do_resolve(5) p1._wait() assert p1.is_fulfilled assert p2.is_pending assert pl.is_pending p2.do_resolve(10) p2._wait() pl._wait() assert p1.is_fulfilled assert p2.is_fulfilled assert pl.is_fulfilled assert 5 == p1.get() assert 10 == p2.get() assert pl.get() == [5, 32, 10, False, True]
def test_done_all(): counter = [0] def inc(_): counter[0] += 1 def dec(_): counter[0] -= 1 p = Promise() r = Promise() p.done_all() p.done_all([(inc, dec)]) p.done_all( [ (inc, dec), (inc, dec), {"success": inc, "failure": dec}, lambda _: r.do_resolve(None), ] ) p.do_resolve(4) Promise.wait(r) assert counter[0] == 4 p = Promise() r = Promise() p.done_all() p.done_all([inc]) p.done_all([(inc, dec)]) p.done_all( [ (inc, dec), {"success": inc, "failure": dec}, (None, lambda _: r.do_resolve(None)), ] ) p.do_reject(Exception("Uh oh!")) Promise.wait(r) assert counter[0] == 1
def test_promise_all_when(): p1 = Promise() p2 = Promise() pl = Promise.all([p1, p2]) assert p1.is_pending assert p2.is_pending assert pl.is_pending p1.do_resolve(5) p1._wait() assert p1.is_fulfilled assert p2.is_pending assert pl.is_pending p2.do_resolve(10) p2._wait() pl._wait() assert p1.is_fulfilled assert p2.is_fulfilled assert pl.is_fulfilled assert 5 == p1.get() assert 10 == p2.get() assert 5 == pl.get()[0] assert 10 == pl.get()[1]
def test_3_2_6_3_when_fulfilled(): """ Testing return of pending promises to make sure they are properly chained. This covers the case where the root promise is fulfilled after the chaining is defined. """ p1 = Promise() pending = Promise() def p1_resolved(v): return pending pf = p1.then(p1_resolved) assert pending.is_pending assert pf.is_pending p1.do_resolve(10) pending.do_resolve(5) pending._wait() assert pending.is_fulfilled assert 5 == pending.get() pf._wait() assert pf.is_fulfilled assert 5 == pf.get() p2 = Promise() bad = Promise() pr = p2.then(lambda r: bad) assert bad.is_pending assert pr.is_pending p2.do_resolve(10) bad._reject_callback(Exception("Error")) bad._wait() assert bad.is_rejected assert_exception(bad.reason, Exception, "Error") pr._wait() assert pr.is_rejected assert_exception(pr.reason, Exception, "Error")
def test_get_if(): p1 = Promise() p1.do_resolve(5) v = p1.get() assert p1.is_fulfilled assert 5 == v
def test_promise_contained_promise(): p = Promise() all_promises = all([1, 2, Promise.resolve(None).then(lambda v: p)]) assert not all_promises.is_fulfilled p.do_resolve(3) assert all_promises.get() == [1, 2, 3]
def test_promise_lazy_promise(): p = Promise() all_promises = all([1, 2, p]) assert not all_promises.is_fulfilled p.do_resolve(3) assert all_promises.get() == [1, 2, 3]
class Connection: def __init__(self, host, port, auto_reconnect): self._node_tree = NodeTree(self) self._structure_requests = Requests() self._time_request = Promise() self._time_diff = 0 #seconds self._last_time_diff_update = 0 self._is_connected = False self._auto_reconnect = auto_reconnect self._ws = self._connect("ws://" + host + ":" + str(port)) def node_tree(self): return self._node_tree def send_structure_request(self, node_id, node_path): p = Promise() self._structure_requests.add(node_path, p) if self._is_connected: self._update_time_difference() self._compose_and_send_structure_request(node_id) return p def send_value_request(self, node_id): self._update_time_difference() self._compose_and_send_value_request(node_id) def send_value_unrequest(self, node_id): self._update_time_difference() self._compose_and_send_value_request(node_id, True) def send_value(self, variant): self._update_time_difference() self._compose_and_send_value(variant) def run_event_loop(self): self._ws.run_forever() while self._auto_reconnect: sleep(1) self._ws = self._connect(self._ws.url) self._ws.run_forever() def close(self): self._auto_reconnect = False self._cleanup_queued_requests(ConnectionError('Connection was closed')) self._ws.close() def server_time_difference(self): return self._time_diff def _connect(self, url): return websocket.WebSocketApp(url, on_message=self._handle_hello_message, on_error=self._on_error, on_close=self._on_close, on_open=self._on_open) def _on_error(self, error): if not self._auto_reconnect: self._cleanup_queued_requests(ConnectionError(error)) def _on_close(self): self._is_connected = False if not self._auto_reconnect: self._cleanup_queued_requests( ConnectionError("Connection was closed")) def _on_open(self): pass def _fetch_time_difference(self): def do_time_request(): self._compose_and_send_time_request() return self._time_request def get_time_diff(time_samples): def get_best_sample(): best_sample = None for sample in time_samples: if best_sample is None or sample.ping < best_sample.ping: best_sample = sample return best_sample return Promise( lambda resolve, reject: resolve(get_best_sample().diff)) def calculate_time_diff(time_request_sent, response): time_response_received = time.time() client_time = time_response_received ping_time = time_response_received - time_request_sent server_time = response / nanoseconds_in_second + ping_time / 2.0 time_diff = client_time - server_time # time_diff in seconds Sample = namedtuple('Sample', 'ping, diff') return Promise( lambda resolve, reject: resolve(Sample(ping_time, time_diff))) def get_time_sample(): time_request_sent = time.time() return do_time_request().then(lambda response: calculate_time_diff( time_request_sent, response)) def get_time_samples(): number_of_samples = 3 time_samples = [] promise = Promise() def get_more_samples_if_needed(): def store_sample_and_get_more_if_needed(sample): time_samples.append(sample) return get_more_samples_if_needed() if len(time_samples) < number_of_samples: return get_time_sample().then( store_sample_and_get_more_if_needed) else: promise.do_resolve(time_samples) get_more_samples_if_needed() return promise return get_time_samples().then(get_time_diff) def _update_time_difference(self): maximum_time_diff_update_frequency = 10 #seconds def store_time_diff(time_diff): self._time_diff = time_diff self._last_time_diff_update = time.time() return Promise(lambda resolve, reject: resolve()) if time.time( ) >= self._last_time_diff_update + maximum_time_diff_update_frequency: return self._fetch_time_difference().then(store_time_diff) else: return Promise(lambda resolve, reject: resolve()) def _handle_hello_message(self, message): if self._parse_hello_message(message): self._is_connected = True self._ws.on_message = self._handle_container_message self._update_time_difference().then(self._node_tree.update()).then( self._send_queued_requests()) else: self._cleanup_queued_requests( CommunicationError('Protocol mismatch')) def _handle_container_message(self, message): data = proto.Container() data.ParseFromString(message) if data.message_type == proto.Container.eStructureResponse: self._parse_structure_response(data.structure_response) elif data.message_type == proto.Container.eGetterResponse: self._parse_getter_response(data.getter_response) elif data.message_type == proto.Container.eStructureChangeResponse: self._parse_structure_change_response( data.structure_change_response) elif data.message_type == proto.Container.eCurrentTimeResponse: self._parse_current_time_response(data.current_time_response) elif data.message_type == proto.Container.eRemoteError: self._parse_error_response(data.error) else: logging.info('Unsupported message type received') def _parse_getter_response(self, response): for variant in response: node = self._node_tree.find_by_id(variant.node_id) node._update_value(variant) def _parse_structure_change_response(self, response): for node_id in response: node = self._node_tree.find_by_id(node_id) if node is not None: node._update() def _parse_current_time_response(self, response): self._time_request.do_resolve(response) def _parse_error_response(self, error): if error.code == proto.eINVALID_REQUEST: self._cleanup_queued_requests(InvalidRequestError(error.text)) elif error.code == proto.eUNSUPPORTED_CONTAINER_TYPE: self._cleanup_queued_requests(CommunicationError(error.text)) def _parse_hello_message(self, message): data = proto.Hello() data.ParseFromString(message) if data.compat_version == 1: return True logging.info('Unsupported protocol version ' + str(data.compat_version) + '.' + str(data.incremental_version)) return False def _parse_structure_response(self, response): for structure in response: node = self._node_tree.find_by_id(structure.info.node_id) node_path = node.path() if node is not None else None request = self._structure_requests.find( node_path ) # requests are stored with node path because node id can change between application reconnect if request is not None: self._structure_requests.remove(node_path) for p in request.promises: p.do_resolve(structure) def _send_queued_requests(self): for request in self._structure_requests.get(): node_path = request.node_path node_id = None if node_path is None else self._node_tree.find_by_path( node_path)._id() self._compose_and_send_structure_request(node_id) def _cleanup_queued_requests(self, error): self._time_request.reject(error) self._structure_requests.clear(error) def _compose_and_send_structure_request(self, node_id): data = proto.Container() data.message_type = proto.Container.eStructureRequest if node_id is not None: data.structure_request.append(node_id) self._ws.send(data.SerializeToString()) def _compose_and_send_value_request(self, node_id, stop=False): data = proto.Container() data.message_type = proto.Container.eGetterRequest value = proto.ValueRequest() value.node_id = node_id value.fs = 5 if stop: value.stop = stop data.getter_request.extend([value]) self._ws.send(data.SerializeToString()) def _compose_and_send_value(self, variant): data = proto.Container() data.message_type = proto.Container.eSetterRequest data.setter_request.extend([variant]) self._ws.send(data.SerializeToString()) def _compose_and_send_time_request(self): data = proto.Container() data.message_type = proto.Container.eCurrentTimeRequest self._ws.send(data.SerializeToString())
def test_fulfill_self(): p = Promise() with raises(TypeError) as excinfo: p.do_resolve(p) p.get()
def test_wait_if(): p1 = Promise() p1.do_resolve(5) p1._wait() assert p1.is_fulfilled
def test_fake_promise(): p = Promise() p.do_resolve(FakeThenPromise()) assert p.is_rejected assert_exception(p.reason, Exception, "FakeThenPromise raises in 'then'")