Skip to content

Deadlock when mocking RPCs that get called in a non-deterministic order #168

@seanlafferty-ibm

Description

@seanlafferty-ibm

I have a couple of different client streams within the same service that get written to via select statements in for loops, so which stream gets written to first is non-deterministic. What I care to test is that when I advance the clock in testing/synctest that both ended up having received a message.

I was thinking I could achieve this with the FirstMatch() planner

mockServer.ExpectClientStream(myMethod1).Times(1).Return(resp1)
mockServer.ExpectClientStream(myMethod2).Times(1).Return(resp2)
// do stuff
synctest.Wait()
// test ends, let grpcmock error if both were not called exactly once

but I'm observing that grpcmock can deadlock if the order of expectations does not match the order that the app calls the rpcs.

Here's a reproduction

import (
	"fmt"
	"testing"

	"go.nhat.io/grpcmock"
	"go.nhat.io/grpcmock/planner"
	testpb "go.nhat.io/grpcmock/test/grpctest"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"gotest.tools/v3/assert"
)

func TestBlocking(t *testing.T) {
	mockServer, mockDialer := grpcmock.MockServerWithBufConn(
		grpcmock.WithPlanner(planner.FirstMatch()),
		grpcmock.RegisterService(testpb.RegisterItemServiceServer),
	)(t)
	conn, err := grpc.NewClient("passthrough:mockURL", grpc.WithContextDialer(mockDialer), grpc.WithTransportCredentials(insecure.NewCredentials()))
	t.Cleanup(func() {
		err := conn.Close()
		if err != nil {
			t.Fatal(err)
		}
	})
	assert.NilError(t, err)
	client := testpb.NewItemServiceClient(conn)
	stream1, err := client.CreateItems(t.Context())
	assert.NilError(t, err)

	mockServer.ExpectUnary(testpb.ItemService_GetItem_FullMethodName).Times(1).Return(testpb.Item{})
	mockServer.ExpectClientStream(testpb.ItemService_CreateItems_FullMethodName).Times(1).Return(&testpb.CreateItemsResponse{})
	err = stream1.Send(&testpb.Item{Id: 52})
	fmt.Println("this prints")
	_, err = client.GetItem(t.Context(), &testpb.GetItemRequest{})
	fmt.Println("this does not print")
	assert.NilError(t, err)
}

This blocks, but if you flip the order of the mocks

	mockServer.ExpectClientStream(testpb.ItemService_CreateItems_FullMethodName).Times(1).Return(&testpb.CreateItemsResponse{})
	mockServer.ExpectUnary(testpb.ItemService_GetItem_FullMethodName).Times(1).Return(testpb.Item{})

it stops blocking.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions