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