github.com/theQRL/go-zond@v0.1.1/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 "encoding/json" 22 "errors" 23 "fmt" 24 "math/rand" 25 "net" 26 "net/http" 27 "net/http/httptest" 28 "os" 29 "reflect" 30 "runtime" 31 "strings" 32 "sync" 33 "testing" 34 "time" 35 36 "github.com/davecgh/go-spew/spew" 37 "github.com/theQRL/go-zond/log" 38 ) 39 40 func TestClientRequest(t *testing.T) { 41 server := newTestServer() 42 defer server.Stop() 43 client := DialInProc(server) 44 defer client.Close() 45 46 var resp echoResult 47 if err := client.Call(&resp, "test_echo", "hello", 10, &echoArgs{"world"}); err != nil { 48 t.Fatal(err) 49 } 50 if !reflect.DeepEqual(resp, echoResult{"hello", 10, &echoArgs{"world"}}) { 51 t.Errorf("incorrect result %#v", resp) 52 } 53 } 54 55 func TestClientResponseType(t *testing.T) { 56 server := newTestServer() 57 defer server.Stop() 58 client := DialInProc(server) 59 defer client.Close() 60 61 if err := client.Call(nil, "test_echo", "hello", 10, &echoArgs{"world"}); err != nil { 62 t.Errorf("Passing nil as result should be fine, but got an error: %v", err) 63 } 64 var resultVar echoResult 65 // Note: passing the var, not a ref 66 err := client.Call(resultVar, "test_echo", "hello", 10, &echoArgs{"world"}) 67 if err == nil { 68 t.Error("Passing a var as result should be an error") 69 } 70 } 71 72 // This test checks calling a method that returns 'null'. 73 func TestClientNullResponse(t *testing.T) { 74 server := newTestServer() 75 defer server.Stop() 76 77 client := DialInProc(server) 78 defer client.Close() 79 80 var result json.RawMessage 81 if err := client.Call(&result, "test_null"); err != nil { 82 t.Fatal(err) 83 } 84 if result == nil { 85 t.Fatal("Expected non-nil result") 86 } 87 if !reflect.DeepEqual(result, json.RawMessage("null")) { 88 t.Errorf("Expected null, got %s", result) 89 } 90 } 91 92 // This test checks that server-returned errors with code and data come out of Client.Call. 93 func TestClientErrorData(t *testing.T) { 94 server := newTestServer() 95 defer server.Stop() 96 client := DialInProc(server) 97 defer client.Close() 98 99 var resp interface{} 100 err := client.Call(&resp, "test_returnError") 101 if err == nil { 102 t.Fatal("expected error") 103 } 104 105 // Check code. 106 // The method handler returns an error value which implements the rpc.Error 107 // interface, i.e. it has a custom error code. The server returns this error code. 108 expectedCode := testError{}.ErrorCode() 109 if e, ok := err.(Error); !ok { 110 t.Fatalf("client did not return rpc.Error, got %#v", e) 111 } else if e.ErrorCode() != expectedCode { 112 t.Fatalf("wrong error code %d, want %d", e.ErrorCode(), expectedCode) 113 } 114 115 // Check data. 116 if e, ok := err.(DataError); !ok { 117 t.Fatalf("client did not return rpc.DataError, got %#v", e) 118 } else if e.ErrorData() != (testError{}.ErrorData()) { 119 t.Fatalf("wrong error data %#v, want %#v", e.ErrorData(), testError{}.ErrorData()) 120 } 121 } 122 123 func TestClientBatchRequest(t *testing.T) { 124 server := newTestServer() 125 defer server.Stop() 126 client := DialInProc(server) 127 defer client.Close() 128 129 batch := []BatchElem{ 130 { 131 Method: "test_echo", 132 Args: []interface{}{"hello", 10, &echoArgs{"world"}}, 133 Result: new(echoResult), 134 }, 135 { 136 Method: "test_echo", 137 Args: []interface{}{"hello2", 11, &echoArgs{"world"}}, 138 Result: new(echoResult), 139 }, 140 { 141 Method: "no_such_method", 142 Args: []interface{}{1, 2, 3}, 143 Result: new(int), 144 }, 145 } 146 if err := client.BatchCall(batch); err != nil { 147 t.Fatal(err) 148 } 149 wantResult := []BatchElem{ 150 { 151 Method: "test_echo", 152 Args: []interface{}{"hello", 10, &echoArgs{"world"}}, 153 Result: &echoResult{"hello", 10, &echoArgs{"world"}}, 154 }, 155 { 156 Method: "test_echo", 157 Args: []interface{}{"hello2", 11, &echoArgs{"world"}}, 158 Result: &echoResult{"hello2", 11, &echoArgs{"world"}}, 159 }, 160 { 161 Method: "no_such_method", 162 Args: []interface{}{1, 2, 3}, 163 Result: new(int), 164 Error: &jsonError{Code: -32601, Message: "the method no_such_method does not exist/is not available"}, 165 }, 166 } 167 if !reflect.DeepEqual(batch, wantResult) { 168 t.Errorf("batch results mismatch:\ngot %swant %s", spew.Sdump(batch), spew.Sdump(wantResult)) 169 } 170 } 171 172 // This checks that, for HTTP connections, the length of batch responses is validated to 173 // match the request exactly. 174 func TestClientBatchRequest_len(t *testing.T) { 175 b, err := json.Marshal([]jsonrpcMessage{ 176 {Version: "2.0", ID: json.RawMessage("1"), Result: json.RawMessage(`"0x1"`)}, 177 {Version: "2.0", ID: json.RawMessage("2"), Result: json.RawMessage(`"0x2"`)}, 178 }) 179 if err != nil { 180 t.Fatal("failed to encode jsonrpc message:", err) 181 } 182 s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 183 _, err := rw.Write(b) 184 if err != nil { 185 t.Error("failed to write response:", err) 186 } 187 })) 188 t.Cleanup(s.Close) 189 190 t.Run("too-few", func(t *testing.T) { 191 client, err := Dial(s.URL) 192 if err != nil { 193 t.Fatal("failed to dial test server:", err) 194 } 195 defer client.Close() 196 197 batch := []BatchElem{ 198 {Method: "foo", Result: new(string)}, 199 {Method: "bar", Result: new(string)}, 200 {Method: "baz", Result: new(string)}, 201 } 202 ctx, cancelFn := context.WithTimeout(context.Background(), time.Second) 203 defer cancelFn() 204 205 if err := client.BatchCallContext(ctx, batch); err != nil { 206 t.Fatal("error:", err) 207 } 208 for i, elem := range batch[:2] { 209 if elem.Error != nil { 210 t.Errorf("expected no error for batch element %d, got %q", i, elem.Error) 211 } 212 } 213 for i, elem := range batch[2:] { 214 if elem.Error != ErrMissingBatchResponse { 215 t.Errorf("wrong error %q for batch element %d", elem.Error, i+2) 216 } 217 } 218 }) 219 220 t.Run("too-many", func(t *testing.T) { 221 client, err := Dial(s.URL) 222 if err != nil { 223 t.Fatal("failed to dial test server:", err) 224 } 225 defer client.Close() 226 227 batch := []BatchElem{ 228 {Method: "foo", Result: new(string)}, 229 } 230 ctx, cancelFn := context.WithTimeout(context.Background(), time.Second) 231 defer cancelFn() 232 233 if err := client.BatchCallContext(ctx, batch); err != nil { 234 t.Fatal("error:", err) 235 } 236 for i, elem := range batch[:1] { 237 if elem.Error != nil { 238 t.Errorf("expected no error for batch element %d, got %q", i, elem.Error) 239 } 240 } 241 for i, elem := range batch[1:] { 242 if elem.Error != ErrMissingBatchResponse { 243 t.Errorf("wrong error %q for batch element %d", elem.Error, i+2) 244 } 245 } 246 }) 247 } 248 249 // This checks that the client can handle the case where the server doesn't 250 // respond to all requests in a batch. 251 func TestClientBatchRequestLimit(t *testing.T) { 252 server := newTestServer() 253 defer server.Stop() 254 server.SetBatchLimits(2, 100000) 255 client := DialInProc(server) 256 257 batch := []BatchElem{ 258 {Method: "foo"}, 259 {Method: "bar"}, 260 {Method: "baz"}, 261 } 262 err := client.BatchCall(batch) 263 if err != nil { 264 t.Fatal("unexpected error:", err) 265 } 266 267 // Check that the first response indicates an error with batch size. 268 var err0 Error 269 if !errors.As(batch[0].Error, &err0) { 270 t.Log("error zero:", batch[0].Error) 271 t.Fatalf("batch elem 0 has wrong error type: %T", batch[0].Error) 272 } else { 273 if err0.ErrorCode() != -32600 || err0.Error() != errMsgBatchTooLarge { 274 t.Fatalf("wrong error on batch elem zero: %v", err0) 275 } 276 } 277 278 // Check that remaining response batch elements are reported as absent. 279 for i, elem := range batch[1:] { 280 if elem.Error != ErrMissingBatchResponse { 281 t.Fatalf("batch elem %d has unexpected error: %v", i+1, elem.Error) 282 } 283 } 284 } 285 286 func TestClientNotify(t *testing.T) { 287 server := newTestServer() 288 defer server.Stop() 289 client := DialInProc(server) 290 defer client.Close() 291 292 if err := client.Notify(context.Background(), "test_echo", "hello", 10, &echoArgs{"world"}); err != nil { 293 t.Fatal(err) 294 } 295 } 296 297 // func TestClientCancelInproc(t *testing.T) { testClientCancel("inproc", t) } 298 func TestClientCancelWebsocket(t *testing.T) { testClientCancel("ws", t) } 299 func TestClientCancelHTTP(t *testing.T) { testClientCancel("http", t) } 300 func TestClientCancelIPC(t *testing.T) { testClientCancel("ipc", t) } 301 302 // This test checks that requests made through CallContext can be canceled by canceling 303 // the context. 304 func testClientCancel(transport string, t *testing.T) { 305 // These tests take a lot of time, run them all at once. 306 // You probably want to run with -parallel 1 or comment out 307 // the call to t.Parallel if you enable the logging. 308 t.Parallel() 309 310 server := newTestServer() 311 defer server.Stop() 312 313 // What we want to achieve is that the context gets canceled 314 // at various stages of request processing. The interesting cases 315 // are: 316 // - cancel during dial 317 // - cancel while performing a HTTP request 318 // - cancel while waiting for a response 319 // 320 // To trigger those, the times are chosen such that connections 321 // are killed within the deadline for every other call (maxKillTimeout 322 // is 2x maxCancelTimeout). 323 // 324 // Once a connection is dead, there is a fair chance it won't connect 325 // successfully because the accept is delayed by 1s. 326 maxContextCancelTimeout := 300 * time.Millisecond 327 fl := &flakeyListener{ 328 maxAcceptDelay: 1 * time.Second, 329 maxKillTimeout: 600 * time.Millisecond, 330 } 331 332 var client *Client 333 switch transport { 334 case "ws", "http": 335 c, hs := httpTestClient(server, transport, fl) 336 defer hs.Close() 337 client = c 338 case "ipc": 339 c, l := ipcTestClient(server, fl) 340 defer l.Close() 341 client = c 342 default: 343 panic("unknown transport: " + transport) 344 } 345 346 // The actual test starts here. 347 var ( 348 wg sync.WaitGroup 349 nreqs = 10 350 ncallers = 10 351 ) 352 caller := func(index int) { 353 defer wg.Done() 354 for i := 0; i < nreqs; i++ { 355 var ( 356 ctx context.Context 357 cancel func() 358 timeout = time.Duration(rand.Int63n(int64(maxContextCancelTimeout))) 359 ) 360 if index < ncallers/2 { 361 // For half of the callers, create a context without deadline 362 // and cancel it later. 363 ctx, cancel = context.WithCancel(context.Background()) 364 time.AfterFunc(timeout, cancel) 365 } else { 366 // For the other half, create a context with a deadline instead. This is 367 // different because the context deadline is used to set the socket write 368 // deadline. 369 ctx, cancel = context.WithTimeout(context.Background(), timeout) 370 } 371 372 // Now perform a call with the context. 373 // The key thing here is that no call will ever complete successfully. 374 err := client.CallContext(ctx, nil, "test_block") 375 switch { 376 case err == nil: 377 _, hasDeadline := ctx.Deadline() 378 t.Errorf("no error for call with %v wait time (deadline: %v)", timeout, hasDeadline) 379 // default: 380 // t.Logf("got expected error with %v wait time: %v", timeout, err) 381 } 382 cancel() 383 } 384 } 385 wg.Add(ncallers) 386 for i := 0; i < ncallers; i++ { 387 go caller(i) 388 } 389 wg.Wait() 390 } 391 392 func TestClientSubscribeInvalidArg(t *testing.T) { 393 server := newTestServer() 394 defer server.Stop() 395 client := DialInProc(server) 396 defer client.Close() 397 398 check := func(shouldPanic bool, arg interface{}) { 399 defer func() { 400 err := recover() 401 if shouldPanic && err == nil { 402 t.Errorf("EthSubscribe should've panicked for %#v", arg) 403 } 404 if !shouldPanic && err != nil { 405 t.Errorf("EthSubscribe shouldn't have panicked for %#v", arg) 406 buf := make([]byte, 1024*1024) 407 buf = buf[:runtime.Stack(buf, false)] 408 t.Error(err) 409 t.Error(string(buf)) 410 } 411 }() 412 client.EthSubscribe(context.Background(), arg, "foo_bar") 413 } 414 check(true, nil) 415 check(true, 1) 416 check(true, (chan int)(nil)) 417 check(true, make(<-chan int)) 418 check(false, make(chan int)) 419 check(false, make(chan<- int)) 420 } 421 422 func TestClientSubscribe(t *testing.T) { 423 server := newTestServer() 424 defer server.Stop() 425 client := DialInProc(server) 426 defer client.Close() 427 428 nc := make(chan int) 429 count := 10 430 sub, err := client.Subscribe(context.Background(), "nftest", nc, "someSubscription", count, 0) 431 if err != nil { 432 t.Fatal("can't subscribe:", err) 433 } 434 for i := 0; i < count; i++ { 435 if val := <-nc; val != i { 436 t.Fatalf("value mismatch: got %d, want %d", val, i) 437 } 438 } 439 440 sub.Unsubscribe() 441 select { 442 case v := <-nc: 443 t.Fatal("received value after unsubscribe:", v) 444 case err := <-sub.Err(): 445 if err != nil { 446 t.Fatalf("Err returned a non-nil error after explicit unsubscribe: %q", err) 447 } 448 case <-time.After(1 * time.Second): 449 t.Fatalf("subscription not closed within 1s after unsubscribe") 450 } 451 } 452 453 // In this test, the connection drops while Subscribe is waiting for a response. 454 func TestClientSubscribeClose(t *testing.T) { 455 server := newTestServer() 456 service := ¬ificationTestService{ 457 gotHangSubscriptionReq: make(chan struct{}), 458 unblockHangSubscription: make(chan struct{}), 459 } 460 if err := server.RegisterName("nftest2", service); err != nil { 461 t.Fatal(err) 462 } 463 464 defer server.Stop() 465 client := DialInProc(server) 466 defer client.Close() 467 468 var ( 469 nc = make(chan int) 470 errc = make(chan error, 1) 471 sub *ClientSubscription 472 err error 473 ) 474 go func() { 475 sub, err = client.Subscribe(context.Background(), "nftest2", nc, "hangSubscription", 999) 476 errc <- err 477 }() 478 479 <-service.gotHangSubscriptionReq 480 client.Close() 481 service.unblockHangSubscription <- struct{}{} 482 483 select { 484 case err := <-errc: 485 if err == nil { 486 t.Errorf("Subscribe returned nil error after Close") 487 } 488 if sub != nil { 489 t.Error("Subscribe returned non-nil subscription after Close") 490 } 491 case <-time.After(1 * time.Second): 492 t.Fatalf("Subscribe did not return within 1s after Close") 493 } 494 } 495 496 // This test reproduces https://github.com/theQRL/go-zond/issues/17837 where the 497 // client hangs during shutdown when Unsubscribe races with Client.Close. 498 func TestClientCloseUnsubscribeRace(t *testing.T) { 499 server := newTestServer() 500 defer server.Stop() 501 502 for i := 0; i < 20; i++ { 503 client := DialInProc(server) 504 nc := make(chan int) 505 sub, err := client.Subscribe(context.Background(), "nftest", nc, "someSubscription", 3, 1) 506 if err != nil { 507 t.Fatal(err) 508 } 509 go client.Close() 510 go sub.Unsubscribe() 511 select { 512 case <-sub.Err(): 513 case <-time.After(5 * time.Second): 514 t.Fatal("subscription not closed within timeout") 515 } 516 } 517 } 518 519 // unsubscribeRecorder collects the subscription IDs of *_unsubscribe calls. 520 type unsubscribeRecorder struct { 521 ServerCodec 522 unsubscribes map[string]bool 523 } 524 525 func (r *unsubscribeRecorder) readBatch() ([]*jsonrpcMessage, bool, error) { 526 if r.unsubscribes == nil { 527 r.unsubscribes = make(map[string]bool) 528 } 529 530 msgs, batch, err := r.ServerCodec.readBatch() 531 for _, msg := range msgs { 532 if msg.isUnsubscribe() { 533 var params []string 534 if err := json.Unmarshal(msg.Params, ¶ms); err != nil { 535 panic("unsubscribe decode error: " + err.Error()) 536 } 537 r.unsubscribes[params[0]] = true 538 } 539 } 540 return msgs, batch, err 541 } 542 543 // This checks that Client calls the _unsubscribe method on the server when Unsubscribe is 544 // called on a subscription. 545 func TestClientSubscriptionUnsubscribeServer(t *testing.T) { 546 t.Parallel() 547 548 // Create the server. 549 srv := NewServer() 550 srv.RegisterName("nftest", new(notificationTestService)) 551 p1, p2 := net.Pipe() 552 recorder := &unsubscribeRecorder{ServerCodec: NewCodec(p1)} 553 go srv.ServeCodec(recorder, OptionMethodInvocation|OptionSubscriptions) 554 defer srv.Stop() 555 556 // Create the client on the other end of the pipe. 557 cfg := new(clientConfig) 558 client, _ := newClient(context.Background(), cfg, func(context.Context) (ServerCodec, error) { 559 return NewCodec(p2), nil 560 }) 561 defer client.Close() 562 563 // Create the subscription. 564 ch := make(chan int) 565 sub, err := client.Subscribe(context.Background(), "nftest", ch, "someSubscription", 1, 1) 566 if err != nil { 567 t.Fatal(err) 568 } 569 570 // Unsubscribe and check that unsubscribe was called. 571 sub.Unsubscribe() 572 if !recorder.unsubscribes[sub.subid] { 573 t.Fatal("client did not call unsubscribe method") 574 } 575 if _, open := <-sub.Err(); open { 576 t.Fatal("subscription error channel not closed after unsubscribe") 577 } 578 } 579 580 // This checks that the subscribed channel can be closed after Unsubscribe. 581 // It is the reproducer for https://github.com/theQRL/go-zond/issues/22322 582 func TestClientSubscriptionChannelClose(t *testing.T) { 583 t.Parallel() 584 585 var ( 586 srv = NewServer() 587 httpsrv = httptest.NewServer(srv.WebsocketHandler(nil)) 588 wsURL = "ws:" + strings.TrimPrefix(httpsrv.URL, "http:") 589 ) 590 defer srv.Stop() 591 defer httpsrv.Close() 592 593 srv.RegisterName("nftest", new(notificationTestService)) 594 client, _ := Dial(wsURL) 595 596 for i := 0; i < 100; i++ { 597 ch := make(chan int, 100) 598 sub, err := client.Subscribe(context.Background(), "nftest", ch, "someSubscription", maxClientSubscriptionBuffer-1, 1) 599 if err != nil { 600 t.Fatal(err) 601 } 602 sub.Unsubscribe() 603 close(ch) 604 } 605 } 606 607 // This test checks that Client doesn't lock up when a single subscriber 608 // doesn't read subscription events. 609 func TestClientNotificationStorm(t *testing.T) { 610 server := newTestServer() 611 defer server.Stop() 612 613 doTest := func(count int, wantError bool) { 614 client := DialInProc(server) 615 defer client.Close() 616 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 617 defer cancel() 618 619 // Subscribe on the server. It will start sending many notifications 620 // very quickly. 621 nc := make(chan int) 622 sub, err := client.Subscribe(ctx, "nftest", nc, "someSubscription", count, 0) 623 if err != nil { 624 t.Fatal("can't subscribe:", err) 625 } 626 defer sub.Unsubscribe() 627 628 // Process each notification, try to run a call in between each of them. 629 for i := 0; i < count; i++ { 630 select { 631 case val := <-nc: 632 if val != i { 633 t.Fatalf("(%d/%d) unexpected value %d", i, count, val) 634 } 635 case err := <-sub.Err(): 636 if wantError && err != ErrSubscriptionQueueOverflow { 637 t.Fatalf("(%d/%d) got error %q, want %q", i, count, err, ErrSubscriptionQueueOverflow) 638 } else if !wantError { 639 t.Fatalf("(%d/%d) got unexpected error %q", i, count, err) 640 } 641 return 642 } 643 var r int 644 err := client.CallContext(ctx, &r, "nftest_echo", i) 645 if err != nil { 646 if !wantError { 647 t.Fatalf("(%d/%d) call error: %v", i, count, err) 648 } 649 return 650 } 651 } 652 if wantError { 653 t.Fatalf("didn't get expected error") 654 } 655 } 656 657 doTest(8000, false) 658 doTest(24000, true) 659 } 660 661 func TestClientSetHeader(t *testing.T) { 662 var gotHeader bool 663 srv := newTestServer() 664 httpsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 665 if r.Header.Get("test") == "ok" { 666 gotHeader = true 667 } 668 srv.ServeHTTP(w, r) 669 })) 670 defer httpsrv.Close() 671 defer srv.Stop() 672 673 client, err := Dial(httpsrv.URL) 674 if err != nil { 675 t.Fatal(err) 676 } 677 defer client.Close() 678 679 client.SetHeader("test", "ok") 680 if _, err := client.SupportedModules(); err != nil { 681 t.Fatal(err) 682 } 683 if !gotHeader { 684 t.Fatal("client did not set custom header") 685 } 686 687 // Check that Content-Type can be replaced. 688 client.SetHeader("content-type", "application/x-garbage") 689 _, err = client.SupportedModules() 690 if err == nil { 691 t.Fatal("no error for invalid content-type header") 692 } else if !strings.Contains(err.Error(), "Unsupported Media Type") { 693 t.Fatalf("error is not related to content-type: %q", err) 694 } 695 } 696 697 func TestClientHTTP(t *testing.T) { 698 server := newTestServer() 699 defer server.Stop() 700 701 client, hs := httpTestClient(server, "http", nil) 702 defer hs.Close() 703 defer client.Close() 704 705 // Launch concurrent requests. 706 var ( 707 results = make([]echoResult, 100) 708 errc = make(chan error, len(results)) 709 wantResult = echoResult{"a", 1, new(echoArgs)} 710 ) 711 defer client.Close() 712 for i := range results { 713 i := i 714 go func() { 715 errc <- client.Call(&results[i], "test_echo", wantResult.String, wantResult.Int, wantResult.Args) 716 }() 717 } 718 719 // Wait for all of them to complete. 720 timeout := time.NewTimer(5 * time.Second) 721 defer timeout.Stop() 722 for i := range results { 723 select { 724 case err := <-errc: 725 if err != nil { 726 t.Fatal(err) 727 } 728 case <-timeout.C: 729 t.Fatalf("timeout (got %d/%d) results)", i+1, len(results)) 730 } 731 } 732 733 // Check results. 734 for i := range results { 735 if !reflect.DeepEqual(results[i], wantResult) { 736 t.Errorf("result %d mismatch: got %#v, want %#v", i, results[i], wantResult) 737 } 738 } 739 } 740 741 func TestClientReconnect(t *testing.T) { 742 startServer := func(addr string) (*Server, net.Listener) { 743 srv := newTestServer() 744 l, err := net.Listen("tcp", addr) 745 if err != nil { 746 t.Fatal("can't listen:", err) 747 } 748 go http.Serve(l, srv.WebsocketHandler([]string{"*"})) 749 return srv, l 750 } 751 752 ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second) 753 defer cancel() 754 755 // Start a server and corresponding client. 756 s1, l1 := startServer("127.0.0.1:0") 757 client, err := DialContext(ctx, "ws://"+l1.Addr().String()) 758 if err != nil { 759 t.Fatal("can't dial", err) 760 } 761 defer client.Close() 762 763 // Perform a call. This should work because the server is up. 764 var resp echoResult 765 if err := client.CallContext(ctx, &resp, "test_echo", "", 1, nil); err != nil { 766 t.Fatal(err) 767 } 768 769 // Shut down the server and allow for some cool down time so we can listen on the same 770 // address again. 771 l1.Close() 772 s1.Stop() 773 time.Sleep(2 * time.Second) 774 775 // Try calling again. It shouldn't work. 776 if err := client.CallContext(ctx, &resp, "test_echo", "", 2, nil); err == nil { 777 t.Error("successful call while the server is down") 778 t.Logf("resp: %#v", resp) 779 } 780 781 // Start it up again and call again. The connection should be reestablished. 782 // We spawn multiple calls here to check whether this hangs somehow. 783 s2, l2 := startServer(l1.Addr().String()) 784 defer l2.Close() 785 defer s2.Stop() 786 787 start := make(chan struct{}) 788 errors := make(chan error, 20) 789 for i := 0; i < cap(errors); i++ { 790 go func() { 791 <-start 792 var resp echoResult 793 errors <- client.CallContext(ctx, &resp, "test_echo", "", 3, nil) 794 }() 795 } 796 close(start) 797 errcount := 0 798 for i := 0; i < cap(errors); i++ { 799 if err = <-errors; err != nil { 800 errcount++ 801 } 802 } 803 t.Logf("%d errors, last error: %v", errcount, err) 804 if errcount > 1 { 805 t.Errorf("expected one error after disconnect, got %d", errcount) 806 } 807 } 808 809 func httpTestClient(srv *Server, transport string, fl *flakeyListener) (*Client, *httptest.Server) { 810 // Create the HTTP server. 811 var hs *httptest.Server 812 switch transport { 813 case "ws": 814 hs = httptest.NewUnstartedServer(srv.WebsocketHandler([]string{"*"})) 815 case "http": 816 hs = httptest.NewUnstartedServer(srv) 817 default: 818 panic("unknown HTTP transport: " + transport) 819 } 820 // Wrap the listener if required. 821 if fl != nil { 822 fl.Listener = hs.Listener 823 hs.Listener = fl 824 } 825 // Connect the client. 826 hs.Start() 827 client, err := Dial(transport + "://" + hs.Listener.Addr().String()) 828 if err != nil { 829 panic(err) 830 } 831 return client, hs 832 } 833 834 func ipcTestClient(srv *Server, fl *flakeyListener) (*Client, net.Listener) { 835 // Listen on a random endpoint. 836 endpoint := fmt.Sprintf("go-ethereum-test-ipc-%d-%d", os.Getpid(), rand.Int63()) 837 if runtime.GOOS == "windows" { 838 endpoint = `\\.\pipe\` + endpoint 839 } else { 840 endpoint = os.TempDir() + "/" + endpoint 841 } 842 l, err := ipcListen(endpoint) 843 if err != nil { 844 panic(err) 845 } 846 // Connect the listener to the server. 847 if fl != nil { 848 fl.Listener = l 849 l = fl 850 } 851 go srv.ServeListener(l) 852 // Connect the client. 853 client, err := Dial(endpoint) 854 if err != nil { 855 panic(err) 856 } 857 return client, l 858 } 859 860 // flakeyListener kills accepted connections after a random timeout. 861 type flakeyListener struct { 862 net.Listener 863 maxKillTimeout time.Duration 864 maxAcceptDelay time.Duration 865 } 866 867 func (l *flakeyListener) Accept() (net.Conn, error) { 868 delay := time.Duration(rand.Int63n(int64(l.maxAcceptDelay))) 869 time.Sleep(delay) 870 871 c, err := l.Listener.Accept() 872 if err == nil { 873 timeout := time.Duration(rand.Int63n(int64(l.maxKillTimeout))) 874 time.AfterFunc(timeout, func() { 875 log.Debug(fmt.Sprintf("killing conn %v after %v", c.LocalAddr(), timeout)) 876 c.Close() 877 }) 878 } 879 return c, err 880 }