diff --git a/pkg/client/connector/unix_test.go b/pkg/client/connector/unix_test.go new file mode 100644 index 00000000..986e4e35 --- /dev/null +++ b/pkg/client/connector/unix_test.go @@ -0,0 +1,72 @@ +package connector + +import ( + "context" + "net" + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/health" + healthpb "google.golang.org/grpc/health/grpc_health_v1" +) + +func TestUnixConnectorReconnectsAfterSocketReplacement(t *testing.T) { + // Use a custom pattern instead of t.TempDir() to create a short path for the socket on macOS + // because it has a 104 character limit. + t.TempDir() + dir, err := os.MkdirTemp("", "uncloud-") + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, os.RemoveAll(dir)) + }) + socketPath := filepath.Join(dir, "uncloud.sock") + conn, err := NewUnixConnector(socketPath).Connect(context.Background()) + require.NoError(t, err, "creating a gRPC channel must not require the socket to exist") + t.Cleanup(func() { + require.NoError(t, conn.Close()) + }) + + client := healthpb.NewHealthClient(conn) + first := startHealthServer(t, socketPath) + requireHealthCheck(t, client) + + first.Stop() + + second := startHealthServer(t, socketPath) + t.Cleanup(second.Stop) + requireHealthCheck(t, client) +} + +func startHealthServer(t *testing.T, socketPath string) *grpc.Server { + t.Helper() + + listener, err := net.Listen("unix", socketPath) + require.NoError(t, err) + + healthServer := health.NewServer() + healthServer.SetServingStatus("", healthpb.HealthCheckResponse_SERVING) + server := grpc.NewServer() + healthpb.RegisterHealthServer(server, healthServer) + + go func() { + _ = server.Serve(listener) + }() + + return server +} + +func requireHealthCheck(t *testing.T, client healthpb.HealthClient) { + t.Helper() + + require.Eventually(t, func() bool { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + resp, err := client.Check(ctx, &healthpb.HealthCheckRequest{}) + return err == nil && resp.Status == healthpb.HealthCheckResponse_SERVING + }, 15*time.Second, 100*time.Millisecond) +}