| // Copyright 2020 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package jsonrpc2_test |
| |
| import ( |
| "context" |
| "errors" |
| "testing" |
| "time" |
| |
| "golang.org/x/exp/internal/stack/stacktest" |
| "golang.org/x/exp/jsonrpc2" |
| ) |
| |
| func TestIdleTimeout(t *testing.T) { |
| stacktest.NoLeak(t) |
| ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) |
| defer cancel() |
| |
| listener, err := jsonrpc2.NetListener(ctx, "tcp", "localhost:0", jsonrpc2.NetListenOptions{}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| listener = jsonrpc2.NewIdleListener(100*time.Millisecond, listener) |
| defer listener.Close() |
| server, err := jsonrpc2.Serve(ctx, listener, jsonrpc2.ConnectionOptions{}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| connect := func() *jsonrpc2.Connection { |
| client, err := jsonrpc2.Dial(ctx, |
| listener.Dialer(), |
| jsonrpc2.ConnectionOptions{}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| return client |
| } |
| // Exercise some connection/disconnection patterns, and then assert that when |
| // our timer fires, the server exits. |
| conn1 := connect() |
| conn2 := connect() |
| if err := conn1.Close(); err != nil { |
| t.Fatalf("conn1.Close failed with error: %v", err) |
| } |
| if err := conn2.Close(); err != nil { |
| t.Fatalf("conn2.Close failed with error: %v", err) |
| } |
| conn3 := connect() |
| if err := conn3.Close(); err != nil { |
| t.Fatalf("conn3.Close failed with error: %v", err) |
| } |
| |
| serverError := server.Wait() |
| |
| if !errors.Is(serverError, jsonrpc2.ErrIdleTimeout) { |
| t.Errorf("run() returned error %v, want %v", serverError, jsonrpc2.ErrIdleTimeout) |
| } |
| } |
| |
| type msg struct { |
| Msg string |
| } |
| |
| type fakeHandler struct{} |
| |
| func (fakeHandler) Handle(ctx context.Context, req *jsonrpc2.Request) (interface{}, error) { |
| switch req.Method { |
| case "ping": |
| return &msg{"pong"}, nil |
| default: |
| return nil, jsonrpc2.ErrNotHandled |
| } |
| } |
| |
| func TestServe(t *testing.T) { |
| stacktest.NoLeak(t) |
| ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
| defer cancel() |
| |
| tests := []struct { |
| name string |
| factory func(context.Context) (jsonrpc2.Listener, error) |
| }{ |
| {"tcp", func(ctx context.Context) (jsonrpc2.Listener, error) { |
| return jsonrpc2.NetListener(ctx, "tcp", "localhost:0", jsonrpc2.NetListenOptions{}) |
| }}, |
| {"pipe", func(ctx context.Context) (jsonrpc2.Listener, error) { |
| return jsonrpc2.NetPipe(ctx) |
| }}, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| fake, err := test.factory(ctx) |
| if err != nil { |
| t.Fatal(err) |
| } |
| conn, shutdown, err := newFake(t, ctx, fake) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer shutdown() |
| var got msg |
| if err := conn.Call(ctx, "ping", &msg{"ting"}).Await(ctx, &got); err != nil { |
| t.Fatal(err) |
| } |
| if want := "pong"; got.Msg != want { |
| t.Errorf("conn.Call(...): returned %q, want %q", got, want) |
| } |
| }) |
| } |
| } |
| |
| func newFake(t *testing.T, ctx context.Context, l jsonrpc2.Listener) (*jsonrpc2.Connection, func(), error) { |
| l = jsonrpc2.NewIdleListener(100*time.Millisecond, l) |
| server, err := jsonrpc2.Serve(ctx, l, jsonrpc2.ConnectionOptions{ |
| Handler: fakeHandler{}, |
| }) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| client, err := jsonrpc2.Dial(ctx, |
| l.Dialer(), |
| jsonrpc2.ConnectionOptions{ |
| Handler: fakeHandler{}, |
| }) |
| if err != nil { |
| return nil, nil, err |
| } |
| return client, func() { |
| if err := l.Close(); err != nil { |
| t.Fatal(err) |
| } |
| if err := client.Close(); err != nil { |
| t.Fatal(err) |
| } |
| server.Wait() |
| }, nil |
| } |