github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/rpc/client_test.go (about) 1 package rpc 2 3 import ( 4 "context" 5 "fmt" 6 "math/rand" 7 "net" 8 "net/http" 9 "net/http/httptest" 10 "os" 11 "reflect" 12 "runtime" 13 "sync" 14 "testing" 15 "time" 16 17 "github.com/davecgh/go-spew/spew" 18 "github.com/quickchainproject/quickchain/log" 19 ) 20 21 func TestClientRequest(t *testing.T) { 22 server := newTestServer("service", new(Service)) 23 defer server.Stop() 24 client := DialInProc(server) 25 defer client.Close() 26 27 var resp Result 28 if err := client.Call(&resp, "service_echo", "hello", 10, &Args{"world"}); err != nil { 29 t.Fatal(err) 30 } 31 if !reflect.DeepEqual(resp, Result{"hello", 10, &Args{"world"}}) { 32 t.Errorf("incorrect result %#v", resp) 33 } 34 } 35 36 func TestClientBatchRequest(t *testing.T) { 37 server := newTestServer("service", new(Service)) 38 defer server.Stop() 39 client := DialInProc(server) 40 defer client.Close() 41 42 batch := []BatchElem{ 43 { 44 Method: "service_echo", 45 Args: []interface{}{"hello", 10, &Args{"world"}}, 46 Result: new(Result), 47 }, 48 { 49 Method: "service_echo", 50 Args: []interface{}{"hello2", 11, &Args{"world"}}, 51 Result: new(Result), 52 }, 53 { 54 Method: "no_such_method", 55 Args: []interface{}{1, 2, 3}, 56 Result: new(int), 57 }, 58 } 59 if err := client.BatchCall(batch); err != nil { 60 t.Fatal(err) 61 } 62 wantResult := []BatchElem{ 63 { 64 Method: "service_echo", 65 Args: []interface{}{"hello", 10, &Args{"world"}}, 66 Result: &Result{"hello", 10, &Args{"world"}}, 67 }, 68 { 69 Method: "service_echo", 70 Args: []interface{}{"hello2", 11, &Args{"world"}}, 71 Result: &Result{"hello2", 11, &Args{"world"}}, 72 }, 73 { 74 Method: "no_such_method", 75 Args: []interface{}{1, 2, 3}, 76 Result: new(int), 77 Error: &jsonError{Code: -32601, Message: "The method no_such_method_ does not exist/is not available"}, 78 }, 79 } 80 if !reflect.DeepEqual(batch, wantResult) { 81 t.Errorf("batch results mismatch:\ngot %swant %s", spew.Sdump(batch), spew.Sdump(wantResult)) 82 } 83 } 84 85 // func TestClientCancelInproc(t *testing.T) { testClientCancel("inproc", t) } 86 func TestClientCancelWebsocket(t *testing.T) { testClientCancel("ws", t) } 87 func TestClientCancelHTTP(t *testing.T) { testClientCancel("http", t) } 88 func TestClientCancelIPC(t *testing.T) { testClientCancel("ipc", t) } 89 90 // This test checks that requests made through CallContext can be canceled by canceling 91 // the context. 92 func testClientCancel(transport string, t *testing.T) { 93 server := newTestServer("service", new(Service)) 94 defer server.Stop() 95 96 // What we want to achieve is that the context gets canceled 97 // at various stages of request processing. The interesting cases 98 // are: 99 // - cancel during dial 100 // - cancel while performing a HTTP request 101 // - cancel while waiting for a response 102 // 103 // To trigger those, the times are chosen such that connections 104 // are killed within the deadline for every other call (maxKillTimeout 105 // is 2x maxCancelTimeout). 106 // 107 // Once a connection is dead, there is a fair chance it won't connect 108 // successfully because the accept is delayed by 1s. 109 maxContextCancelTimeout := 300 * time.Millisecond 110 fl := &flakeyListener{ 111 maxAcceptDelay: 1 * time.Second, 112 maxKillTimeout: 600 * time.Millisecond, 113 } 114 115 var client *Client 116 switch transport { 117 case "ws", "http": 118 c, hs := httpTestClient(server, transport, fl) 119 defer hs.Close() 120 client = c 121 case "ipc": 122 c, l := ipcTestClient(server, fl) 123 defer l.Close() 124 client = c 125 default: 126 panic("unknown transport: " + transport) 127 } 128 129 // These tests take a lot of time, run them all at once. 130 // You probably want to run with -parallel 1 or comment out 131 // the call to t.Parallel if you enable the logging. 132 t.Parallel() 133 134 // The actual test starts here. 135 var ( 136 wg sync.WaitGroup 137 nreqs = 10 138 ncallers = 6 139 ) 140 caller := func(index int) { 141 defer wg.Done() 142 for i := 0; i < nreqs; i++ { 143 var ( 144 ctx context.Context 145 cancel func() 146 timeout = time.Duration(rand.Int63n(int64(maxContextCancelTimeout))) 147 ) 148 if index < ncallers/2 { 149 // For half of the callers, create a context without deadline 150 // and cancel it later. 151 ctx, cancel = context.WithCancel(context.Background()) 152 time.AfterFunc(timeout, cancel) 153 } else { 154 // For the other half, create a context with a deadline instead. This is 155 // different because the context deadline is used to set the socket write 156 // deadline. 157 ctx, cancel = context.WithTimeout(context.Background(), timeout) 158 } 159 // Now perform a call with the context. 160 // The key thing here is that no call will ever complete successfully. 161 err := client.CallContext(ctx, nil, "service_sleep", 2*maxContextCancelTimeout) 162 if err != nil { 163 log.Debug(fmt.Sprint("got expected error:", err)) 164 } else { 165 t.Errorf("no error for call with %v wait time", timeout) 166 } 167 cancel() 168 } 169 } 170 wg.Add(ncallers) 171 for i := 0; i < ncallers; i++ { 172 go caller(i) 173 } 174 wg.Wait() 175 } 176 177 func TestClientSubscribeInvalidArg(t *testing.T) { 178 server := newTestServer("service", new(Service)) 179 defer server.Stop() 180 client := DialInProc(server) 181 defer client.Close() 182 183 check := func(shouldPanic bool, arg interface{}) { 184 defer func() { 185 err := recover() 186 if shouldPanic && err == nil { 187 t.Errorf("EthSubscribe should've panicked for %#v", arg) 188 } 189 if !shouldPanic && err != nil { 190 t.Errorf("EthSubscribe shouldn't have panicked for %#v", arg) 191 buf := make([]byte, 1024*1024) 192 buf = buf[:runtime.Stack(buf, false)] 193 t.Error(err) 194 t.Error(string(buf)) 195 } 196 }() 197 client.EthSubscribe(context.Background(), arg, "foo_bar") 198 } 199 check(true, nil) 200 check(true, 1) 201 check(true, (chan int)(nil)) 202 check(true, make(<-chan int)) 203 check(false, make(chan int)) 204 check(false, make(chan<- int)) 205 } 206 207 func TestClientSubscribe(t *testing.T) { 208 server := newTestServer("eth", new(NotificationTestService)) 209 defer server.Stop() 210 client := DialInProc(server) 211 defer client.Close() 212 213 nc := make(chan int) 214 count := 10 215 sub, err := client.EthSubscribe(context.Background(), nc, "someSubscription", count, 0) 216 if err != nil { 217 t.Fatal("can't subscribe:", err) 218 } 219 for i := 0; i < count; i++ { 220 if val := <-nc; val != i { 221 t.Fatalf("value mismatch: got %d, want %d", val, i) 222 } 223 } 224 225 sub.Unsubscribe() 226 select { 227 case v := <-nc: 228 t.Fatal("received value after unsubscribe:", v) 229 case err := <-sub.Err(): 230 if err != nil { 231 t.Fatalf("Err returned a non-nil error after explicit unsubscribe: %q", err) 232 } 233 case <-time.After(1 * time.Second): 234 t.Fatalf("subscription not closed within 1s after unsubscribe") 235 } 236 } 237 238 func TestClientSubscribeCustomNamespace(t *testing.T) { 239 namespace := "custom" 240 server := newTestServer(namespace, new(NotificationTestService)) 241 defer server.Stop() 242 client := DialInProc(server) 243 defer client.Close() 244 245 nc := make(chan int) 246 count := 10 247 sub, err := client.Subscribe(context.Background(), namespace, nc, "someSubscription", count, 0) 248 if err != nil { 249 t.Fatal("can't subscribe:", err) 250 } 251 for i := 0; i < count; i++ { 252 if val := <-nc; val != i { 253 t.Fatalf("value mismatch: got %d, want %d", val, i) 254 } 255 } 256 257 sub.Unsubscribe() 258 select { 259 case v := <-nc: 260 t.Fatal("received value after unsubscribe:", v) 261 case err := <-sub.Err(): 262 if err != nil { 263 t.Fatalf("Err returned a non-nil error after explicit unsubscribe: %q", err) 264 } 265 case <-time.After(1 * time.Second): 266 t.Fatalf("subscription not closed within 1s after unsubscribe") 267 } 268 } 269 270 // In this test, the connection drops while EthSubscribe is 271 // waiting for a response. 272 func TestClientSubscribeClose(t *testing.T) { 273 service := &NotificationTestService{ 274 gotHangSubscriptionReq: make(chan struct{}), 275 unblockHangSubscription: make(chan struct{}), 276 } 277 server := newTestServer("eth", service) 278 defer server.Stop() 279 client := DialInProc(server) 280 defer client.Close() 281 282 var ( 283 nc = make(chan int) 284 errc = make(chan error) 285 sub *ClientSubscription 286 err error 287 ) 288 go func() { 289 sub, err = client.EthSubscribe(context.Background(), nc, "hangSubscription", 999) 290 errc <- err 291 }() 292 293 <-service.gotHangSubscriptionReq 294 client.Close() 295 service.unblockHangSubscription <- struct{}{} 296 297 select { 298 case err := <-errc: 299 if err == nil { 300 t.Errorf("EthSubscribe returned nil error after Close") 301 } 302 if sub != nil { 303 t.Error("EthSubscribe returned non-nil subscription after Close") 304 } 305 case <-time.After(1 * time.Second): 306 t.Fatalf("EthSubscribe did not return within 1s after Close") 307 } 308 } 309 310 // This test checks that Client doesn't lock up when a single subscriber 311 // doesn't read subscription events. 312 func TestClientNotificationStorm(t *testing.T) { 313 server := newTestServer("eth", new(NotificationTestService)) 314 defer server.Stop() 315 316 doTest := func(count int, wantError bool) { 317 client := DialInProc(server) 318 defer client.Close() 319 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 320 defer cancel() 321 322 // Subscribe on the server. It will start sending many notifications 323 // very quickly. 324 nc := make(chan int) 325 sub, err := client.EthSubscribe(ctx, nc, "someSubscription", count, 0) 326 if err != nil { 327 t.Fatal("can't subscribe:", err) 328 } 329 defer sub.Unsubscribe() 330 331 // Process each notification, try to run a call in between each of them. 332 for i := 0; i < count; i++ { 333 select { 334 case val := <-nc: 335 if val != i { 336 t.Fatalf("(%d/%d) unexpected value %d", i, count, val) 337 } 338 case err := <-sub.Err(): 339 if wantError && err != ErrSubscriptionQueueOverflow { 340 t.Fatalf("(%d/%d) got error %q, want %q", i, count, err, ErrSubscriptionQueueOverflow) 341 } else if !wantError { 342 t.Fatalf("(%d/%d) got unexpected error %q", i, count, err) 343 } 344 return 345 } 346 var r int 347 err := client.CallContext(ctx, &r, "qct_echo", i) 348 if err != nil { 349 if !wantError { 350 t.Fatalf("(%d/%d) call error: %v", i, count, err) 351 } 352 return 353 } 354 } 355 } 356 357 doTest(8000, false) 358 doTest(10000, true) 359 } 360 361 func TestClientHTTP(t *testing.T) { 362 server := newTestServer("service", new(Service)) 363 defer server.Stop() 364 365 client, hs := httpTestClient(server, "http", nil) 366 defer hs.Close() 367 defer client.Close() 368 369 // Launch concurrent requests. 370 var ( 371 results = make([]Result, 100) 372 errc = make(chan error) 373 wantResult = Result{"a", 1, new(Args)} 374 ) 375 defer client.Close() 376 for i := range results { 377 i := i 378 go func() { 379 errc <- client.Call(&results[i], "service_echo", 380 wantResult.String, wantResult.Int, wantResult.Args) 381 }() 382 } 383 384 // Wait for all of them to complete. 385 timeout := time.NewTimer(5 * time.Second) 386 defer timeout.Stop() 387 for i := range results { 388 select { 389 case err := <-errc: 390 if err != nil { 391 t.Fatal(err) 392 } 393 case <-timeout.C: 394 t.Fatalf("timeout (got %d/%d) results)", i+1, len(results)) 395 } 396 } 397 398 // Check results. 399 for i := range results { 400 if !reflect.DeepEqual(results[i], wantResult) { 401 t.Errorf("result %d mismatch: got %#v, want %#v", i, results[i], wantResult) 402 } 403 } 404 } 405 406 func TestClientReconnect(t *testing.T) { 407 startServer := func(addr string) (*Server, net.Listener) { 408 srv := newTestServer("service", new(Service)) 409 l, err := net.Listen("tcp", addr) 410 if err != nil { 411 t.Fatal(err) 412 } 413 go http.Serve(l, srv.WebsocketHandler([]string{"*"})) 414 return srv, l 415 } 416 417 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 418 defer cancel() 419 420 // Start a server and corresponding client. 421 s1, l1 := startServer("127.0.0.1:0") 422 client, err := DialContext(ctx, "ws://"+l1.Addr().String()) 423 if err != nil { 424 t.Fatal("can't dial", err) 425 } 426 427 // Perform a call. This should work because the server is up. 428 var resp Result 429 if err := client.CallContext(ctx, &resp, "service_echo", "", 1, nil); err != nil { 430 t.Fatal(err) 431 } 432 433 // Shut down the server and try calling again. It shouldn't work. 434 l1.Close() 435 s1.Stop() 436 if err := client.CallContext(ctx, &resp, "service_echo", "", 2, nil); err == nil { 437 t.Error("successful call while the server is down") 438 t.Logf("resp: %#v", resp) 439 } 440 441 // Allow for some cool down time so we can listen on the same address again. 442 time.Sleep(2 * time.Second) 443 444 // Start it up again and call again. The connection should be reestablished. 445 // We spawn multiple calls here to check whether this hangs somehow. 446 s2, l2 := startServer(l1.Addr().String()) 447 defer l2.Close() 448 defer s2.Stop() 449 450 start := make(chan struct{}) 451 errors := make(chan error, 20) 452 for i := 0; i < cap(errors); i++ { 453 go func() { 454 <-start 455 var resp Result 456 errors <- client.CallContext(ctx, &resp, "service_echo", "", 3, nil) 457 }() 458 } 459 close(start) 460 errcount := 0 461 for i := 0; i < cap(errors); i++ { 462 if err = <-errors; err != nil { 463 errcount++ 464 } 465 } 466 t.Log("err:", err) 467 if errcount > 1 { 468 t.Errorf("expected one error after disconnect, got %d", errcount) 469 } 470 } 471 472 func newTestServer(serviceName string, service interface{}) *Server { 473 server := NewServer() 474 if err := server.RegisterName(serviceName, service); err != nil { 475 panic(err) 476 } 477 return server 478 } 479 480 func httpTestClient(srv *Server, transport string, fl *flakeyListener) (*Client, *httptest.Server) { 481 // Create the HTTP server. 482 var hs *httptest.Server 483 switch transport { 484 case "ws": 485 hs = httptest.NewUnstartedServer(srv.WebsocketHandler([]string{"*"})) 486 case "http": 487 hs = httptest.NewUnstartedServer(srv) 488 default: 489 panic("unknown HTTP transport: " + transport) 490 } 491 // Wrap the listener if required. 492 if fl != nil { 493 fl.Listener = hs.Listener 494 hs.Listener = fl 495 } 496 // Connect the client. 497 hs.Start() 498 client, err := Dial(transport + "://" + hs.Listener.Addr().String()) 499 if err != nil { 500 panic(err) 501 } 502 return client, hs 503 } 504 505 func ipcTestClient(srv *Server, fl *flakeyListener) (*Client, net.Listener) { 506 // Listen on a random endpoint. 507 endpoint := fmt.Sprintf("quickchain-test-ipc-%d-%d", os.Getpid(), rand.Int63()) 508 if runtime.GOOS == "windows" { 509 endpoint = `\\.\pipe\` + endpoint 510 } else { 511 endpoint = os.TempDir() + "/" + endpoint 512 } 513 l, err := ipcListen(endpoint) 514 if err != nil { 515 panic(err) 516 } 517 // Connect the listener to the server. 518 if fl != nil { 519 fl.Listener = l 520 l = fl 521 } 522 go srv.ServeListener(l) 523 // Connect the client. 524 client, err := Dial(endpoint) 525 if err != nil { 526 panic(err) 527 } 528 return client, l 529 } 530 531 // flakeyListener kills accepted connections after a random timeout. 532 type flakeyListener struct { 533 net.Listener 534 maxKillTimeout time.Duration 535 maxAcceptDelay time.Duration 536 } 537 538 func (l *flakeyListener) Accept() (net.Conn, error) { 539 delay := time.Duration(rand.Int63n(int64(l.maxAcceptDelay))) 540 time.Sleep(delay) 541 542 c, err := l.Listener.Accept() 543 if err == nil { 544 timeout := time.Duration(rand.Int63n(int64(l.maxKillTimeout))) 545 time.AfterFunc(timeout, func() { 546 log.Debug(fmt.Sprintf("killing conn %v after %v", c.LocalAddr(), timeout)) 547 c.Close() 548 }) 549 } 550 return c, err 551 }