def test_buggy_done_handler_is_logged(): def on_done(req): undefined_var with MyRPCProcessor() as rpc: # First for a request request = RPCRequestOut('add_async', [1], on_done) rpc.send_request(request) request.cancel() with rpc.expect_asyncio_log('Exception in callback'): rpc.yield_to_loop() with MyRPCProcessor() as rpc: # Now a request in a batch batch = RPCBatchOut() batch.add_request('add_async', [2], on_done) rpc.send_batch(batch) batch.items[0].cancel() with rpc.expect_asyncio_log('Exception in callback'): rpc.yield_to_loop() with MyRPCProcessor() as rpc: # Finally the batch itself batch = RPCBatchOut() batch.add_request('add_async', [1]) rpc.send_batch(batch, on_done) batch.cancel() with rpc.expect_asyncio_log('Exception in callback'): rpc.yield_to_loop()
def test_direct_set_etc(): loop = asyncio.get_event_loop() done = set() def on_done(request): done.add(request.request_id) # Send 4 requests requests = [ RPCRequestOut('some_call', [1], on_done), RPCRequestOut('some_call', [2], on_done), RPCRequestOut('some_call', [3], on_done), RPCRequestOut('some_call', [4], on_done), ] with MyRPCProcessor() as rpc: for request in requests: rpc.send_request(request) # Interfere with all apart from one requests[0].set_result(6) response = RPCResponse(7, requests[1].request_id) rpc.message_received(rpc.protocol.response_message(response)) requests[2].cancel() requests[3].set_exception(ValueError()) rpc.yield_to_loop() # Process done functions assert done == {requests[n].request_id for n in range(len(requests))} assert not rpc.requests
def test_bad_reponses(): def handle_add(request): assert request.method == 'add' assert request.args == [1, 5, 10] assert request.result() == 16 handled.append(request.method) handled = [] requests = [ RPCRequestOut('add', [1, 5, 10], handle_add), ] with MyRPCProcessor() as rpc: # Send each request and feed them back into the RPC object as if # it were receiving its own messages. for request in requests: # Send it and fake receiving it rpc.send_request(request) rpc.message_received(rpc.responses.pop()) # Now process the queue and the async jobs, generating queued responses rpc.process_all() # Get the queued responses and send them back to ourselves response, = rpc.consume_responses() assert rpc.all_done() # Send response twice. rpc.message_received(response) rpc.yield_to_loop() assert handled == ['add'] assert not rpc.debug_messages rpc.message_received(response) # Handler NOT called twice assert handled == ['add'] rpc.expect_debug_message('response to unsent') rpc.debug_clear() # Send a response with no ID rpc.message_received( rpc.protocol.response_message(RPCResponse(6, None))) rpc.expect_debug_message('missing id') # Responses are handled synchronously so no process_all() is needed assert rpc.all_done()
def test_request_round_trip(): '''Round trip test - we send binary requests to ourselves, process the requests, send binary responses in response also to ourselves, and then process the results. This tests request and response serialization, and also that request handlers are invoked when a response is received. The tests cover a range of valid and invalid requests, and problem triggers. We also insert a fake duplicate response, and a fake response without a request ID, to test they are appropriately handled. ''' handled = [] def handle_add(request): assert request.method == 'add' if request.args[1] == "a": result = request.exception() assert isinstance(result, RPCError) assert result.code == -1 assert "numbers" in result.message handled.append('add_bad') else: result = request.result() assert request.args == [1, 5, 10] assert result == 16 handled.append(request.method) def handle_add_async(request): assert request.method == 'add_async' if request.args[0] == "b": result = request.exception() assert isinstance(result, RPCError) assert result.code == -1 assert "numbers" in result.message handled.append('add_async_bad') else: result = request.result() assert request.args == [1, 5, 10] assert result == 16 handled.append(request.method) def handle_echo(request): assert request.method == 'echo' assert request.args[0] == request.result() handled.append(request.method) def handle_bad_echo(request): assert request.method == 'echo' assert not request.args result = request.exception() assert isinstance(result, RPCError) assert result.code == rpc.protocol.INVALID_ARGS handled.append('bad_echo') def bad_request_handler(request): assert request.method == 'bad_request' result = request.exception() assert isinstance(result, RPCError) assert result.code == rpc.protocol.INTERNAL_ERROR handled.append(request.method) null_handler_request = RPCRequestOut('echo', [2], None) requests = [ RPCRequestOut('add', [1, 5, 10], handle_add), # Bad type RPCRequestOut('add', [1, "a", 10], handle_add), # Test a notification, and a bad one RPCRequestOut('echo', ["ping"], handle_echo), RPCRequest('echo', [], None), # Test a None response RPCRequestOut('echo', [None], handle_echo), # Throw in an async request RPCRequestOut('add_async', [1, 5, 10], handle_add_async), RPCRequestOut('add_async', ["b"], handle_add_async), # An invalid request RPCRequestOut('echo', [], handle_bad_echo), # test a null handler null_handler_request, # test a remote bad request getter RPCRequestOut('bad_request', [], bad_request_handler), ] with MyRPCProcessor() as rpc: # Send each request and feed them back into the RPC object as if # it were receiving its own messages. for request in requests: # Send it and fake receiving it rpc.send_request(request) rpc.message_received(rpc.responses.pop()) assert rpc.sent_count == len(requests) # Check all_requests assert isinstance(rpc.all_requests(), list) assert set(rpc.all_requests()) == set( req for req in requests if isinstance(req, RPCRequestOut)) # Now process the queue and the async jobs, generating queued responses rpc.process_all() # Get the queued responses and send them back to ourselves responses = rpc.consume_responses() assert rpc.all_done() # Did we did get the null handler response - no other way to detect it text = f'"id": {null_handler_request.request_id}'.encode() assert any(text in response for response in responses) for response in responses: rpc.message_received(response) rpc.yield_to_loop() # Nothing left assert not rpc.all_requests() assert sorted(handled) == [ 'add', 'add_async', 'add_async_bad', 'add_bad', 'bad_echo', 'bad_request', 'echo', 'echo' ] # Responses are handled synchronously so no process_all() is needed assert rpc.all_done()
def test_RPCRequest(): request = RPCRequestOut('method', [1], None) assert request.method == 'method' assert request.args == [1] assert request.request_id == 0 assert not request.is_notification() assert repr(request) == "RPCRequest('method', [1], 0)" request = RPCRequestOut('method', (1, ), None) assert request.method == 'method' assert request.args == (1, ) assert request.request_id == 1 assert not request.is_notification() assert repr(request) == "RPCRequest('method', (1,), 1)" request = RPCRequestOut('foo', {"bar": 1}, None) assert request.method == 'foo' assert request.args == {"bar": 1} assert request.request_id == 2 assert not request.is_notification() assert repr(request) == "RPCRequest('foo', {'bar': 1}, 2)" request = RPCRequest('foo', [], None) assert request.method == 'foo' assert request.args == [] assert request.request_id is None assert request.is_notification() assert repr(request) == "RPCRequest('foo', [], None)" request = RPCRequest('foo', (), None) assert request.method == 'foo' assert request.args == () assert request.request_id is None assert request.is_notification() assert repr(request) == "RPCRequest('foo', (), None)" # Check {} is preserved (different call semantics) for request in [RPCRequest('add', {}, 0), RPCRequestOut('add', {}, None)]: assert request.method == 'add' assert request.args == {} request = RPCRequest('add', {}, 0) # Check None gives [] for request in [ RPCRequest('add', None, 0), RPCRequestOut('add', None, None) ]: request = RPCRequest('add', None, 0) assert request.method == 'add' assert request.args == [] loop = asyncio.get_event_loop() # Result setting valid = False def on_done(req): nonlocal valid valid = req is request # Test args good type with pytest.raises(ValueError): RPCRequestOut('method', 0, on_done) request = RPCRequestOut('method', [0], on_done) assert not request.done() with pytest.raises(asyncio.InvalidStateError): request.result() assert not request.done() request.set_result(42) assert not valid # Not scheduled yet loop.run_until_complete(asyncio.sleep(0)) assert valid assert request.result() == 42 with pytest.raises(asyncio.InvalidStateError): request.set_result(35) assert request.result() == 42 # Result setting request = RPCRequestOut('method', None, on_done) loop.call_later(0.001, request.set_result, 45) valid = False assert loop.run_until_complete(request) == 45 assert valid assert request.result() == 45 request = RPCRequestOut('method', None, on_done) loop = asyncio.get_event_loop() request.set_result(46) assert loop.run_until_complete(request) == 46
def test_odd_calls(): handled = [] def expect(answer, request): result = request.result() if result == answer: handled.append(request.method) def error(text, request): result = request.exception() if isinstance(result, RPCError) and text in result.message: handled.append(text) requests = [ # Gives code coverage of notify func RPCRequestOut('notify', [1, 2, 3], partial(expect, 6)), RPCRequestOut('add_many', [1, "b"], partial(error, 'numbers')), RPCRequestOut('add_many', [], partial(error, 'requires 1')), RPCRequestOut('add_many', [1], partial(expect, 1)), RPCRequestOut('add_many', [5, 50, 500], partial(expect, 555)), RPCRequestOut('add_many', list(range(10)), partial(expect, 45)), RPCRequestOut('add_many', {'first': 1}, partial(expect, 1)), RPCRequestOut('add_many', { 'first': 1, 'second': 10 }, partial(expect, 11)), RPCRequestOut('add_many', { 'first': 1, 'values': [] }, partial(error, 'values')), RPCRequestOut('pow', [2, 3], partial(expect, 8)), RPCRequestOut('pow', [2, 3, 5], partial(expect, 3)), RPCRequestOut('pow', { "x": 2, "y": 3 }, partial(error, 'cannot be called')), RPCRequestOut('echo_2', ['ping'], partial(expect, ['ping', 2])), RPCRequestOut('echo_2', ['ping', 'pong'], partial(error, 'at most 1')), RPCRequestOut('echo_2', { 'first': 1, 'second': 8 }, partial(expect, [1, 8])), RPCRequestOut('echo_2', { 'first': 1, 'second': 8, '3rd': 1 }, partial(error, '3rd')), RPCRequestOut('kwargs', [], partial(error, 'requires 1')), RPCRequestOut('kwargs', [1], partial(expect, 1)), RPCRequestOut('kwargs', [1, 2], partial(expect, 2)), RPCRequestOut('kwargs', {'end': 4}, partial(error, "start")), RPCRequestOut('kwargs', {'start': 3}, partial(expect, 3)), RPCRequestOut('kwargs', { 'start': 3, 'end': 1, '3rd': 1 }, partial(error, '3rd')), RPCRequestOut('both', [], partial(expect, 2)), RPCRequestOut('both', [1], partial(expect, 1)), RPCRequestOut('both', [5, 2], partial(expect, 15)), RPCRequestOut('both', {'end': 4}, partial(expect, 6)), RPCRequestOut('both', {'start': 3}, partial(expect, 3)), RPCRequestOut('both', { 'start': 3, 'end': 1, '3rd': 1 }, partial(expect, 11)), ] with MyRPCProcessor() as rpc: # Send each request and feed them back into the RPC object as if # it were receiving its own messages. for request in requests: rpc.send_request(request) rpc.message_received(rpc.responses.pop()) # Now process the queue and the async jobs, generating queued responses rpc.process_all() # Get the queued responses and send them back to ourselves responses = rpc.consume_responses() assert rpc.all_done() for response in responses: rpc.message_received(response) rpc.yield_to_loop() assert len(handled) == len(requests) assert rpc.all_done()
def test_outgoing_request_cancellation_and_setting(): '''Tests cancelling requests.''' called = 0 def on_done(req): nonlocal called called += 1 with MyRPCProcessor() as rpc: request = RPCRequestOut('add_async', [1], on_done) rpc.send_request(request) assert request.request_id in rpc.requests request.cancel() rpc.yield_to_loop() assert called == 1 assert request.cancelled() assert not rpc.requests with pytest.raises(asyncio.CancelledError): request.result() request = RPCRequestOut('add_async', [1], on_done) rpc.send_request(request) assert request.request_id in rpc.requests request.set_result(4) rpc.yield_to_loop() assert called == 2 assert request.result() == 4 assert not rpc.requests request = RPCRequestOut('add_async', [1], on_done) rpc.send_request(request) assert request.request_id in rpc.requests request.set_exception(ValueError()) rpc.yield_to_loop() assert called == 3 assert isinstance(request.exception(), ValueError) with pytest.raises(ValueError): request.result() assert not rpc.requests