0

Disclaimer: I'm new to using asyncio so this might be an easy fix.

I'm trying to write tests for the endpoints of an asynchronous grpc-server. The server has to regularly check something using a function that runs in an infinite loop, and still be responsive when the infinite loop is sleeping - which is why I'm using grpc-asyncio and pytest-asyncio.

example test (event_loop created by pytest-asyncio):

@pytest.mark.asyncio
async def test_endpoint(
        event_loop,
        test_client: test_pb2_grpc.TesterStub,
):
    await serve()  # THIS BLOCKS THE REST OF THE TEST
    response = await test_client.TemporaryEndpointForTesting(request=test_pb2.TmpRequest())
    assert response

client fixture:

@pytest.fixture
def test_client() -> test_pb2_grpc.TesterStub:
    port = 50551
    channel = aio.insecure_channel(f'localhost:{port}')
    stub = test_pb2_grpc.TesterStub(channel)
    return stub

server endpoints:

class Servicer(test_pb2_grpc.TesterServicer):

    # ENDPOINT
    async def TemporaryEndpointForTesting(self, request, context):
        print("request received!")
        return test_pb2.TmpResponse()

    async def infinite_loop(self):
        await asyncio.sleep(1.0)
        print("<looping>")
        return asyncio.ensure_future(self.infinite_loop())

server startup:

async def serve():
    port = 50551
    server: aio.Server = aio.server()
    servicer = Servicer()
    test_pb2_grpc.add_TesterServicer_to_server(servicer, server)
    server.add_insecure_port(f'[::]:{port}')

    task_1 = asyncio.create_task(servicer.infinite_loop())
    task_2 = asyncio.create_task(server.start())
    task_3 = asyncio.create_task(server.wait_for_termination())

    await task_1
    await task_2
    await task_3

The goal is to set up the server, and then send requests to it to see if it responds as expected. When I start the server separately using await serve() and then run my tests, it seems to work flawlessly. But when I try to start it from the testcase, it gets stuck ... which I sort of get, since it's awaiting the (infinite) server-tasks to finish, but I don't know how to get around this. I thought using a different event_loop for the server-tasks would do the trick ...

new_event_loop = asyncio.new_event_loop()
task_1 = new_event_loop.create_task(servicer.infinite_loop())
task_2 = new_event_loop.create_task(server.start())
task_3 = new_event_loop.create_task(server.wait_for_termination())

but that didn't work either.

Best-case would be a way to start up the server within a fixture so I can just pass it to all test functions. I'm guessing this could also be done using threading, but that seems a bit superfluous considering the server is already using asyncio.

I've been at this all day, any help would be well appreciated.

(using Python 3.9)

martineau
  • 119,623
  • 25
  • 170
  • 301
wiseboar
  • 175
  • 2
  • 13

1 Answers1

0

This isn't really the solution I was looking for, but I just decided to go to end-to-end tests directly rather than trying to figure this out. So I'm using the Python Docker SDK to start the server via a pytest fixture and just send client commands to it. Or I start it using a debugger if that's needed.

Not as convenient as I'd like it to be, but I spent too much time on this issue already and this way it's tested e2e right away.

wiseboar
  • 175
  • 2
  • 13