github.com/noirx94/tendermintmp@v0.0.1/rpc/jsonrpc/client/ws_client_test.go (about) 1 package client 2 3 import ( 4 "context" 5 "encoding/json" 6 "net/http" 7 "net/http/httptest" 8 "sync" 9 "testing" 10 "time" 11 12 "github.com/gorilla/websocket" 13 "github.com/stretchr/testify/require" 14 15 "github.com/tendermint/tendermint/libs/log" 16 tmsync "github.com/tendermint/tendermint/libs/sync" 17 types "github.com/tendermint/tendermint/rpc/jsonrpc/types" 18 ) 19 20 var wsCallTimeout = 5 * time.Second 21 22 type myHandler struct { 23 closeConnAfterRead bool 24 mtx tmsync.RWMutex 25 } 26 27 var upgrader = websocket.Upgrader{ 28 ReadBufferSize: 1024, 29 WriteBufferSize: 1024, 30 } 31 32 func (h *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 33 conn, err := upgrader.Upgrade(w, r, nil) 34 if err != nil { 35 panic(err) 36 } 37 defer conn.Close() 38 for { 39 messageType, in, err := conn.ReadMessage() 40 if err != nil { 41 return 42 } 43 44 var req types.RPCRequest 45 err = json.Unmarshal(in, &req) 46 if err != nil { 47 panic(err) 48 } 49 50 h.mtx.RLock() 51 if h.closeConnAfterRead { 52 if err := conn.Close(); err != nil { 53 panic(err) 54 } 55 } 56 h.mtx.RUnlock() 57 58 res := json.RawMessage(`{}`) 59 emptyRespBytes, _ := json.Marshal(types.RPCResponse{Result: res, ID: req.ID}) 60 if err := conn.WriteMessage(messageType, emptyRespBytes); err != nil { 61 return 62 } 63 } 64 } 65 66 func TestWSClientReconnectsAfterReadFailure(t *testing.T) { 67 var wg sync.WaitGroup 68 69 // start server 70 h := &myHandler{} 71 s := httptest.NewServer(h) 72 defer s.Close() 73 74 c := startClient(t, "//"+s.Listener.Addr().String()) 75 defer c.Stop() // nolint:errcheck // ignore for tests 76 77 wg.Add(1) 78 go callWgDoneOnResult(t, c, &wg) 79 80 h.mtx.Lock() 81 h.closeConnAfterRead = true 82 h.mtx.Unlock() 83 84 // results in WS read error, no send retry because write succeeded 85 call(t, "a", c) 86 87 // expect to reconnect almost immediately 88 time.Sleep(10 * time.Millisecond) 89 h.mtx.Lock() 90 h.closeConnAfterRead = false 91 h.mtx.Unlock() 92 93 // should succeed 94 call(t, "b", c) 95 96 wg.Wait() 97 } 98 99 func TestWSClientReconnectsAfterWriteFailure(t *testing.T) { 100 var wg sync.WaitGroup 101 102 // start server 103 h := &myHandler{} 104 s := httptest.NewServer(h) 105 106 c := startClient(t, "//"+s.Listener.Addr().String()) 107 defer c.Stop() // nolint:errcheck // ignore for tests 108 109 wg.Add(2) 110 go callWgDoneOnResult(t, c, &wg) 111 112 // hacky way to abort the connection before write 113 if err := c.conn.Close(); err != nil { 114 t.Error(err) 115 } 116 117 // results in WS write error, the client should resend on reconnect 118 call(t, "a", c) 119 120 // expect to reconnect almost immediately 121 time.Sleep(10 * time.Millisecond) 122 123 // should succeed 124 call(t, "b", c) 125 126 wg.Wait() 127 } 128 129 func TestWSClientReconnectFailure(t *testing.T) { 130 // start server 131 h := &myHandler{} 132 s := httptest.NewServer(h) 133 134 c := startClient(t, "//"+s.Listener.Addr().String()) 135 defer c.Stop() // nolint:errcheck // ignore for tests 136 137 go func() { 138 for { 139 select { 140 case <-c.ResponsesCh: 141 case <-c.Quit(): 142 return 143 } 144 } 145 }() 146 147 // hacky way to abort the connection before write 148 if err := c.conn.Close(); err != nil { 149 t.Error(err) 150 } 151 s.Close() 152 153 // results in WS write error 154 // provide timeout to avoid blocking 155 ctx, cancel := context.WithTimeout(context.Background(), wsCallTimeout) 156 defer cancel() 157 if err := c.Call(ctx, "a", make(map[string]interface{})); err != nil { 158 t.Error(err) 159 } 160 161 // expect to reconnect almost immediately 162 time.Sleep(10 * time.Millisecond) 163 164 done := make(chan struct{}) 165 go func() { 166 // client should block on this 167 call(t, "b", c) 168 close(done) 169 }() 170 171 // test that client blocks on the second send 172 select { 173 case <-done: 174 t.Fatal("client should block on calling 'b' during reconnect") 175 case <-time.After(5 * time.Second): 176 t.Log("All good") 177 } 178 } 179 180 func TestNotBlockingOnStop(t *testing.T) { 181 timeout := 2 * time.Second 182 s := httptest.NewServer(&myHandler{}) 183 c := startClient(t, "//"+s.Listener.Addr().String()) 184 c.Call(context.Background(), "a", make(map[string]interface{})) // nolint:errcheck // ignore for tests 185 // Let the readRoutine get around to blocking 186 time.Sleep(time.Second) 187 passCh := make(chan struct{}) 188 go func() { 189 // Unless we have a non-blocking write to ResponsesCh from readRoutine 190 // this blocks forever ont the waitgroup 191 err := c.Stop() 192 require.NoError(t, err) 193 passCh <- struct{}{} 194 }() 195 select { 196 case <-passCh: 197 // Pass 198 case <-time.After(timeout): 199 t.Fatalf("WSClient did failed to stop within %v seconds - is one of the read/write routines blocking?", 200 timeout.Seconds()) 201 } 202 } 203 204 func startClient(t *testing.T, addr string) *WSClient { 205 c, err := NewWS(addr, "/websocket") 206 require.Nil(t, err) 207 err = c.Start() 208 require.Nil(t, err) 209 c.SetLogger(log.TestingLogger()) 210 return c 211 } 212 213 func call(t *testing.T, method string, c *WSClient) { 214 err := c.Call(context.Background(), method, make(map[string]interface{})) 215 require.NoError(t, err) 216 } 217 218 func callWgDoneOnResult(t *testing.T, c *WSClient, wg *sync.WaitGroup) { 219 for { 220 select { 221 case resp := <-c.ResponsesCh: 222 if resp.Error != nil { 223 t.Errorf("unexpected error: %v", resp.Error) 224 return 225 } 226 if resp.Result != nil { 227 wg.Done() 228 } 229 case <-c.Quit(): 230 return 231 } 232 } 233 }