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