github.com/ylsGit/go-ethereum@v1.6.5/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/ethereum/go-ethereum/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 // In this test, the connection drops while EthSubscribe is 255 // waiting for a response. 256 func TestClientSubscribeClose(t *testing.T) { 257 service := &NotificationTestService{ 258 gotHangSubscriptionReq: make(chan struct{}), 259 unblockHangSubscription: make(chan struct{}), 260 } 261 server := newTestServer("eth", service) 262 defer server.Stop() 263 client := DialInProc(server) 264 defer client.Close() 265 266 var ( 267 nc = make(chan int) 268 errc = make(chan error) 269 sub *ClientSubscription 270 err error 271 ) 272 go func() { 273 sub, err = client.EthSubscribe(context.Background(), nc, "hangSubscription", 999) 274 errc <- err 275 }() 276 277 <-service.gotHangSubscriptionReq 278 client.Close() 279 service.unblockHangSubscription <- struct{}{} 280 281 select { 282 case err := <-errc: 283 if err == nil { 284 t.Errorf("EthSubscribe returned nil error after Close") 285 } 286 if sub != nil { 287 t.Error("EthSubscribe returned non-nil subscription after Close") 288 } 289 case <-time.After(1 * time.Second): 290 t.Fatalf("EthSubscribe did not return within 1s after Close") 291 } 292 } 293 294 // This test checks that Client doesn't lock up when a single subscriber 295 // doesn't read subscription events. 296 func TestClientNotificationStorm(t *testing.T) { 297 server := newTestServer("eth", new(NotificationTestService)) 298 defer server.Stop() 299 300 doTest := func(count int, wantError bool) { 301 client := DialInProc(server) 302 defer client.Close() 303 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 304 defer cancel() 305 306 // Subscribe on the server. It will start sending many notifications 307 // very quickly. 308 nc := make(chan int) 309 sub, err := client.EthSubscribe(ctx, nc, "someSubscription", count, 0) 310 if err != nil { 311 t.Fatal("can't subscribe:", err) 312 } 313 defer sub.Unsubscribe() 314 315 // Process each notification, try to run a call in between each of them. 316 for i := 0; i < count; i++ { 317 select { 318 case val := <-nc: 319 if val != i { 320 t.Fatalf("(%d/%d) unexpected value %d", i, count, val) 321 } 322 case err := <-sub.Err(): 323 if wantError && err != ErrSubscriptionQueueOverflow { 324 t.Fatalf("(%d/%d) got error %q, want %q", i, count, err, ErrSubscriptionQueueOverflow) 325 } else if !wantError { 326 t.Fatalf("(%d/%d) got unexpected error %q", i, count, err) 327 } 328 return 329 } 330 var r int 331 err := client.CallContext(ctx, &r, "eth_echo", i) 332 if err != nil { 333 if !wantError { 334 t.Fatalf("(%d/%d) call error: %v", i, count, err) 335 } 336 return 337 } 338 } 339 } 340 341 doTest(8000, false) 342 doTest(10000, true) 343 } 344 345 func TestClientHTTP(t *testing.T) { 346 server := newTestServer("service", new(Service)) 347 defer server.Stop() 348 349 client, hs := httpTestClient(server, "http", nil) 350 defer hs.Close() 351 defer client.Close() 352 353 // Launch concurrent requests. 354 var ( 355 results = make([]Result, 100) 356 errc = make(chan error) 357 wantResult = Result{"a", 1, new(Args)} 358 ) 359 defer client.Close() 360 for i := range results { 361 i := i 362 go func() { 363 errc <- client.Call(&results[i], "service_echo", 364 wantResult.String, wantResult.Int, wantResult.Args) 365 }() 366 } 367 368 // Wait for all of them to complete. 369 timeout := time.NewTimer(5 * time.Second) 370 defer timeout.Stop() 371 for i := range results { 372 select { 373 case err := <-errc: 374 if err != nil { 375 t.Fatal(err) 376 } 377 case <-timeout.C: 378 t.Fatalf("timeout (got %d/%d) results)", i+1, len(results)) 379 } 380 } 381 382 // Check results. 383 for i := range results { 384 if !reflect.DeepEqual(results[i], wantResult) { 385 t.Errorf("result %d mismatch: got %#v, want %#v", i, results[i], wantResult) 386 } 387 } 388 } 389 390 func TestClientReconnect(t *testing.T) { 391 startServer := func(addr string) (*Server, net.Listener) { 392 srv := newTestServer("service", new(Service)) 393 l, err := net.Listen("tcp", addr) 394 if err != nil { 395 t.Fatal(err) 396 } 397 go http.Serve(l, srv.WebsocketHandler([]string{"*"})) 398 return srv, l 399 } 400 401 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 402 defer cancel() 403 404 // Start a server and corresponding client. 405 s1, l1 := startServer("127.0.0.1:0") 406 client, err := DialContext(ctx, "ws://"+l1.Addr().String()) 407 if err != nil { 408 t.Fatal("can't dial", err) 409 } 410 411 // Perform a call. This should work because the server is up. 412 var resp Result 413 if err := client.CallContext(ctx, &resp, "service_echo", "", 1, nil); err != nil { 414 t.Fatal(err) 415 } 416 417 // Shut down the server and try calling again. It shouldn't work. 418 l1.Close() 419 s1.Stop() 420 if err := client.CallContext(ctx, &resp, "service_echo", "", 2, nil); err == nil { 421 t.Error("successful call while the server is down") 422 t.Logf("resp: %#v", resp) 423 } 424 425 // Allow for some cool down time so we can listen on the same address again. 426 time.Sleep(2 * time.Second) 427 428 // Start it up again and call again. The connection should be reestablished. 429 // We spawn multiple calls here to check whether this hangs somehow. 430 s2, l2 := startServer(l1.Addr().String()) 431 defer l2.Close() 432 defer s2.Stop() 433 434 start := make(chan struct{}) 435 errors := make(chan error, 20) 436 for i := 0; i < cap(errors); i++ { 437 go func() { 438 <-start 439 var resp Result 440 errors <- client.CallContext(ctx, &resp, "service_echo", "", 3, nil) 441 }() 442 } 443 close(start) 444 errcount := 0 445 for i := 0; i < cap(errors); i++ { 446 if err = <-errors; err != nil { 447 errcount++ 448 } 449 } 450 t.Log("err:", err) 451 if errcount > 1 { 452 t.Errorf("expected one error after disconnect, got %d", errcount) 453 } 454 } 455 456 func newTestServer(serviceName string, service interface{}) *Server { 457 server := NewServer() 458 if err := server.RegisterName(serviceName, service); err != nil { 459 panic(err) 460 } 461 return server 462 } 463 464 func httpTestClient(srv *Server, transport string, fl *flakeyListener) (*Client, *httptest.Server) { 465 // Create the HTTP server. 466 var hs *httptest.Server 467 switch transport { 468 case "ws": 469 hs = httptest.NewUnstartedServer(srv.WebsocketHandler([]string{"*"})) 470 case "http": 471 hs = httptest.NewUnstartedServer(srv) 472 default: 473 panic("unknown HTTP transport: " + transport) 474 } 475 // Wrap the listener if required. 476 if fl != nil { 477 fl.Listener = hs.Listener 478 hs.Listener = fl 479 } 480 // Connect the client. 481 hs.Start() 482 client, err := Dial(transport + "://" + hs.Listener.Addr().String()) 483 if err != nil { 484 panic(err) 485 } 486 return client, hs 487 } 488 489 func ipcTestClient(srv *Server, fl *flakeyListener) (*Client, net.Listener) { 490 // Listen on a random endpoint. 491 endpoint := fmt.Sprintf("go-ethereum-test-ipc-%d-%d", os.Getpid(), rand.Int63()) 492 if runtime.GOOS == "windows" { 493 endpoint = `\\.\pipe\` + endpoint 494 } else { 495 endpoint = os.TempDir() + "/" + endpoint 496 } 497 l, err := ipcListen(endpoint) 498 if err != nil { 499 panic(err) 500 } 501 // Connect the listener to the server. 502 if fl != nil { 503 fl.Listener = l 504 l = fl 505 } 506 go srv.ServeListener(l) 507 // Connect the client. 508 client, err := Dial(endpoint) 509 if err != nil { 510 panic(err) 511 } 512 return client, l 513 } 514 515 // flakeyListener kills accepted connections after a random timeout. 516 type flakeyListener struct { 517 net.Listener 518 maxKillTimeout time.Duration 519 maxAcceptDelay time.Duration 520 } 521 522 func (l *flakeyListener) Accept() (net.Conn, error) { 523 delay := time.Duration(rand.Int63n(int64(l.maxAcceptDelay))) 524 time.Sleep(delay) 525 526 c, err := l.Listener.Accept() 527 if err == nil { 528 timeout := time.Duration(rand.Int63n(int64(l.maxKillTimeout))) 529 time.AfterFunc(timeout, func() { 530 log.Debug(fmt.Sprintf("killing conn %v after %v", c.LocalAddr(), timeout)) 531 c.Close() 532 }) 533 } 534 return c, err 535 }