github.com/sourcegraph/jsonrpc2@v0.2.0/conn_test.go (about) 1 package jsonrpc2_test 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "log" 9 "net" 10 "sync" 11 "testing" 12 "time" 13 14 "github.com/sourcegraph/jsonrpc2" 15 ) 16 17 var paramsTests = []struct { 18 sendParams interface{} 19 wantParams *json.RawMessage 20 }{ 21 { 22 sendParams: nil, 23 wantParams: nil, 24 }, 25 { 26 sendParams: jsonNull, 27 wantParams: &jsonNull, 28 }, 29 { 30 sendParams: false, 31 wantParams: rawJSONMessage("false"), 32 }, 33 { 34 sendParams: 0, 35 wantParams: rawJSONMessage("0"), 36 }, 37 { 38 sendParams: "", 39 wantParams: rawJSONMessage(`""`), 40 }, 41 { 42 sendParams: rawJSONMessage(`{"foo":"bar"}`), 43 wantParams: rawJSONMessage(`{"foo":"bar"}`), 44 }, 45 } 46 47 func TestConn_DispatchCall(t *testing.T) { 48 for _, test := range paramsTests { 49 t.Run(fmt.Sprintf("%s", test.sendParams), func(t *testing.T) { 50 testParams(t, test.wantParams, func(c *jsonrpc2.Conn) error { 51 _, err := c.DispatchCall(context.Background(), "f", test.sendParams) 52 return err 53 }) 54 }) 55 } 56 } 57 58 func TestConn_Notify(t *testing.T) { 59 for _, test := range paramsTests { 60 t.Run(fmt.Sprintf("%s", test.sendParams), func(t *testing.T) { 61 testParams(t, test.wantParams, func(c *jsonrpc2.Conn) error { 62 return c.Notify(context.Background(), "f", test.sendParams) 63 }) 64 }) 65 } 66 } 67 68 func TestConn_DisconnectNotify(t *testing.T) { 69 70 t.Run("EOF", func(t *testing.T) { 71 connA, connB := net.Pipe() 72 c := jsonrpc2.NewConn(context.Background(), jsonrpc2.NewPlainObjectStream(connB), nil) 73 // By closing connA, connB receives io.EOF 74 if err := connA.Close(); err != nil { 75 t.Error(err) 76 } 77 assertDisconnect(t, c, connB) 78 }) 79 80 t.Run("Close", func(t *testing.T) { 81 _, connB := net.Pipe() 82 c := jsonrpc2.NewConn(context.Background(), jsonrpc2.NewPlainObjectStream(connB), nil) 83 if err := c.Close(); err != nil { 84 t.Error(err) 85 } 86 assertDisconnect(t, c, connB) 87 }) 88 89 t.Run("Close async", func(t *testing.T) { 90 done := make(chan struct{}) 91 _, connB := net.Pipe() 92 c := jsonrpc2.NewConn(context.Background(), jsonrpc2.NewPlainObjectStream(connB), nil) 93 go func() { 94 if err := c.Close(); err != nil && err != jsonrpc2.ErrClosed { 95 t.Error(err) 96 } 97 close(done) 98 }() 99 assertDisconnect(t, c, connB) 100 <-done 101 }) 102 103 t.Run("protocol error", func(t *testing.T) { 104 connA, connB := net.Pipe() 105 c := jsonrpc2.NewConn( 106 context.Background(), 107 jsonrpc2.NewPlainObjectStream(connB), 108 noopHandler{}, 109 // Suppress log message. This connection receives an invalid JSON 110 // message that causes an error to be written to the logger. We 111 // don't want this expected error to appear in os.Stderr though when 112 // running tests in verbose mode or when other tests fail. 113 jsonrpc2.SetLogger(log.New(io.Discard, "", 0)), 114 ) 115 connA.Write([]byte("invalid json")) 116 assertDisconnect(t, c, connB) 117 }) 118 } 119 120 func TestConn_Close(t *testing.T) { 121 t.Run("waiting for response", func(t *testing.T) { 122 connA, connB := net.Pipe() 123 nodeA := jsonrpc2.NewConn( 124 context.Background(), 125 jsonrpc2.NewPlainObjectStream(connA), noopHandler{}, 126 ) 127 defer nodeA.Close() 128 nodeB := jsonrpc2.NewConn( 129 context.Background(), 130 jsonrpc2.NewPlainObjectStream(connB), 131 noopHandler{}, 132 ) 133 defer nodeB.Close() 134 135 ready := make(chan struct{}) 136 done := make(chan struct{}) 137 go func() { 138 close(ready) 139 err := nodeB.Call(context.Background(), "m", nil, nil) 140 if err != jsonrpc2.ErrClosed { 141 t.Errorf("got error %v, want %v", err, jsonrpc2.ErrClosed) 142 } 143 close(done) 144 }() 145 // Wait for the request to be sent before we close the connection. 146 <-ready 147 if err := nodeB.Close(); err != nil && err != jsonrpc2.ErrClosed { 148 t.Error(err) 149 } 150 assertDisconnect(t, nodeB, connB) 151 <-done 152 }) 153 } 154 155 func testParams(t *testing.T, want *json.RawMessage, fn func(c *jsonrpc2.Conn) error) { 156 wg := &sync.WaitGroup{} 157 handler := handlerFunc(func(ctx context.Context, conn *jsonrpc2.Conn, r *jsonrpc2.Request) { 158 assertRawJSONMessage(t, r.Params, want) 159 wg.Done() 160 }) 161 162 client, server := newClientServer(handler) 163 defer client.Close() 164 defer server.Close() 165 166 wg.Add(1) 167 if err := fn(client); err != nil { 168 t.Error(err) 169 } 170 wg.Wait() 171 } 172 173 func assertDisconnect(t *testing.T, c *jsonrpc2.Conn, conn io.Writer) { 174 select { 175 case <-c.DisconnectNotify(): 176 case <-time.After(200 * time.Millisecond): 177 t.Error("no disconnect notification") 178 return 179 } 180 // Assert that conn is closed by trying to write to it. 181 _, got := conn.Write(nil) 182 want := io.ErrClosedPipe 183 if got != want { 184 t.Errorf("got %s, want %s", got, want) 185 } 186 } 187 188 func assertRawJSONMessage(t *testing.T, got *json.RawMessage, want *json.RawMessage) { 189 // Assert pointers. 190 if got == nil || want == nil { 191 if got != want { 192 t.Errorf("pointer: got %s, want %s", got, want) 193 } 194 return 195 } 196 { 197 // If pointers are not nil, then assert values. 198 got := string(*got) 199 want := string(*want) 200 if got != want { 201 t.Errorf("value: got %q, want %q", got, want) 202 } 203 } 204 } 205 206 func newClientServer(handler jsonrpc2.Handler) (client *jsonrpc2.Conn, server *jsonrpc2.Conn) { 207 ctx := context.Background() 208 connA, connB := net.Pipe() 209 client = jsonrpc2.NewConn( 210 ctx, 211 jsonrpc2.NewPlainObjectStream(connA), 212 noopHandler{}, 213 ) 214 server = jsonrpc2.NewConn( 215 ctx, 216 jsonrpc2.NewPlainObjectStream(connB), 217 handler, 218 ) 219 return client, server 220 }