github.com/ethereum/go-ethereum@v1.14.4-0.20240516095835-473ee8fc07a3/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/ethereum/go-ethereum/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 defer client.Close() 257 258 batch := []BatchElem{ 259 {Method: "foo"}, 260 {Method: "bar"}, 261 {Method: "baz"}, 262 } 263 err := client.BatchCall(batch) 264 if err != nil { 265 t.Fatal("unexpected error:", err) 266 } 267 268 // Check that the first response indicates an error with batch size. 269 var err0 Error 270 if !errors.As(batch[0].Error, &err0) { 271 t.Log("error zero:", batch[0].Error) 272 t.Fatalf("batch elem 0 has wrong error type: %T", batch[0].Error) 273 } else { 274 if err0.ErrorCode() != -32600 || err0.Error() != errMsgBatchTooLarge { 275 t.Fatalf("wrong error on batch elem zero: %v", err0) 276 } 277 } 278 279 // Check that remaining response batch elements are reported as absent. 280 for i, elem := range batch[1:] { 281 if elem.Error != ErrMissingBatchResponse { 282 t.Fatalf("batch elem %d has unexpected error: %v", i+1, elem.Error) 283 } 284 } 285 } 286 287 func TestClientNotify(t *testing.T) { 288 server := newTestServer() 289 defer server.Stop() 290 client := DialInProc(server) 291 defer client.Close() 292 293 if err := client.Notify(context.Background(), "test_echo", "hello", 10, &echoArgs{"world"}); err != nil { 294 t.Fatal(err) 295 } 296 } 297 298 // func TestClientCancelInproc(t *testing.T) { testClientCancel("inproc", t) } 299 func TestClientCancelWebsocket(t *testing.T) { testClientCancel("ws", t) } 300 func TestClientCancelHTTP(t *testing.T) { testClientCancel("http", t) } 301 func TestClientCancelIPC(t *testing.T) { testClientCancel("ipc", t) } 302 303 // This test checks that requests made through CallContext can be canceled by canceling 304 // the context. 305 func testClientCancel(transport string, t *testing.T) { 306 // These tests take a lot of time, run them all at once. 307 // You probably want to run with -parallel 1 or comment out 308 // the call to t.Parallel if you enable the logging. 309 t.Parallel() 310 311 server := newTestServer() 312 defer server.Stop() 313 314 // What we want to achieve is that the context gets canceled 315 // at various stages of request processing. The interesting cases 316 // are: 317 // - cancel during dial 318 // - cancel while performing a HTTP request 319 // - cancel while waiting for a response 320 // 321 // To trigger those, the times are chosen such that connections 322 // are killed within the deadline for every other call (maxKillTimeout 323 // is 2x maxCancelTimeout). 324 // 325 // Once a connection is dead, there is a fair chance it won't connect 326 // successfully because the accept is delayed by 1s. 327 maxContextCancelTimeout := 300 * time.Millisecond 328 fl := &flakeyListener{ 329 maxAcceptDelay: 1 * time.Second, 330 maxKillTimeout: 600 * time.Millisecond, 331 } 332 333 var client *Client 334 switch transport { 335 case "ws", "http": 336 c, hs := httpTestClient(server, transport, fl) 337 defer hs.Close() 338 client = c 339 case "ipc": 340 c, l := ipcTestClient(server, fl) 341 defer l.Close() 342 client = c 343 default: 344 panic("unknown transport: " + transport) 345 } 346 defer client.Close() 347 348 // The actual test starts here. 349 var ( 350 wg sync.WaitGroup 351 nreqs = 10 352 ncallers = 10 353 ) 354 caller := func(index int) { 355 defer wg.Done() 356 for i := 0; i < nreqs; i++ { 357 var ( 358 ctx context.Context 359 cancel func() 360 timeout = time.Duration(rand.Int63n(int64(maxContextCancelTimeout))) 361 ) 362 if index < ncallers/2 { 363 // For half of the callers, create a context without deadline 364 // and cancel it later. 365 ctx, cancel = context.WithCancel(context.Background()) 366 time.AfterFunc(timeout, cancel) 367 } else { 368 // For the other half, create a context with a deadline instead. This is 369 // different because the context deadline is used to set the socket write 370 // deadline. 371 ctx, cancel = context.WithTimeout(context.Background(), timeout) 372 } 373 374 // Now perform a call with the context. 375 // The key thing here is that no call will ever complete successfully. 376 err := client.CallContext(ctx, nil, "test_block") 377 switch { 378 case err == nil: 379 _, hasDeadline := ctx.Deadline() 380 t.Errorf("no error for call with %v wait time (deadline: %v)", timeout, hasDeadline) 381 // default: 382 // t.Logf("got expected error with %v wait time: %v", timeout, err) 383 } 384 cancel() 385 } 386 } 387 wg.Add(ncallers) 388 for i := 0; i < ncallers; i++ { 389 go caller(i) 390 } 391 wg.Wait() 392 } 393 394 func TestClientSubscribeInvalidArg(t *testing.T) { 395 server := newTestServer() 396 defer server.Stop() 397 client := DialInProc(server) 398 defer client.Close() 399 400 check := func(shouldPanic bool, arg interface{}) { 401 defer func() { 402 err := recover() 403 if shouldPanic && err == nil { 404 t.Errorf("EthSubscribe should've panicked for %#v", arg) 405 } 406 if !shouldPanic && err != nil { 407 t.Errorf("EthSubscribe shouldn't have panicked for %#v", arg) 408 buf := make([]byte, 1024*1024) 409 buf = buf[:runtime.Stack(buf, false)] 410 t.Error(err) 411 t.Error(string(buf)) 412 } 413 }() 414 client.EthSubscribe(context.Background(), arg, "foo_bar") 415 } 416 check(true, nil) 417 check(true, 1) 418 check(true, (chan int)(nil)) 419 check(true, make(<-chan int)) 420 check(false, make(chan int)) 421 check(false, make(chan<- int)) 422 } 423 424 func TestClientSubscribe(t *testing.T) { 425 server := newTestServer() 426 defer server.Stop() 427 client := DialInProc(server) 428 defer client.Close() 429 430 nc := make(chan int) 431 count := 10 432 sub, err := client.Subscribe(context.Background(), "nftest", nc, "someSubscription", count, 0) 433 if err != nil { 434 t.Fatal("can't subscribe:", err) 435 } 436 for i := 0; i < count; i++ { 437 if val := <-nc; val != i { 438 t.Fatalf("value mismatch: got %d, want %d", val, i) 439 } 440 } 441 442 sub.Unsubscribe() 443 select { 444 case v := <-nc: 445 t.Fatal("received value after unsubscribe:", v) 446 case err := <-sub.Err(): 447 if err != nil { 448 t.Fatalf("Err returned a non-nil error after explicit unsubscribe: %q", err) 449 } 450 case <-time.After(1 * time.Second): 451 t.Fatalf("subscription not closed within 1s after unsubscribe") 452 } 453 } 454 455 // In this test, the connection drops while Subscribe is waiting for a response. 456 func TestClientSubscribeClose(t *testing.T) { 457 server := newTestServer() 458 service := ¬ificationTestService{ 459 gotHangSubscriptionReq: make(chan struct{}), 460 unblockHangSubscription: make(chan struct{}), 461 } 462 if err := server.RegisterName("nftest2", service); err != nil { 463 t.Fatal(err) 464 } 465 466 defer server.Stop() 467 client := DialInProc(server) 468 defer client.Close() 469 470 var ( 471 nc = make(chan int) 472 errc = make(chan error, 1) 473 sub *ClientSubscription 474 err error 475 ) 476 go func() { 477 sub, err = client.Subscribe(context.Background(), "nftest2", nc, "hangSubscription", 999) 478 errc <- err 479 }() 480 481 <-service.gotHangSubscriptionReq 482 client.Close() 483 service.unblockHangSubscription <- struct{}{} 484 485 select { 486 case err := <-errc: 487 if err == nil { 488 t.Errorf("Subscribe returned nil error after Close") 489 } 490 if sub != nil { 491 t.Error("Subscribe returned non-nil subscription after Close") 492 } 493 case <-time.After(1 * time.Second): 494 t.Fatalf("Subscribe did not return within 1s after Close") 495 } 496 } 497 498 // This test reproduces https://github.com/ethereum/go-ethereum/issues/17837 where the 499 // client hangs during shutdown when Unsubscribe races with Client.Close. 500 func TestClientCloseUnsubscribeRace(t *testing.T) { 501 server := newTestServer() 502 defer server.Stop() 503 504 for i := 0; i < 20; i++ { 505 client := DialInProc(server) 506 nc := make(chan int) 507 sub, err := client.Subscribe(context.Background(), "nftest", nc, "someSubscription", 3, 1) 508 if err != nil { 509 t.Fatal(err) 510 } 511 go client.Close() 512 go sub.Unsubscribe() 513 select { 514 case <-sub.Err(): 515 case <-time.After(5 * time.Second): 516 t.Fatal("subscription not closed within timeout") 517 } 518 } 519 } 520 521 // unsubscribeRecorder collects the subscription IDs of *_unsubscribe calls. 522 type unsubscribeRecorder struct { 523 ServerCodec 524 unsubscribes map[string]bool 525 } 526 527 func (r *unsubscribeRecorder) readBatch() ([]*jsonrpcMessage, bool, error) { 528 if r.unsubscribes == nil { 529 r.unsubscribes = make(map[string]bool) 530 } 531 532 msgs, batch, err := r.ServerCodec.readBatch() 533 for _, msg := range msgs { 534 if msg.isUnsubscribe() { 535 var params []string 536 if err := json.Unmarshal(msg.Params, ¶ms); err != nil { 537 panic("unsubscribe decode error: " + err.Error()) 538 } 539 r.unsubscribes[params[0]] = true 540 } 541 } 542 return msgs, batch, err 543 } 544 545 // This checks that Client calls the _unsubscribe method on the server when Unsubscribe is 546 // called on a subscription. 547 func TestClientSubscriptionUnsubscribeServer(t *testing.T) { 548 t.Parallel() 549 550 // Create the server. 551 srv := NewServer() 552 srv.RegisterName("nftest", new(notificationTestService)) 553 p1, p2 := net.Pipe() 554 recorder := &unsubscribeRecorder{ServerCodec: NewCodec(p1)} 555 go srv.ServeCodec(recorder, OptionMethodInvocation|OptionSubscriptions) 556 defer srv.Stop() 557 558 // Create the client on the other end of the pipe. 559 cfg := new(clientConfig) 560 client, _ := newClient(context.Background(), cfg, func(context.Context) (ServerCodec, error) { 561 return NewCodec(p2), nil 562 }) 563 defer client.Close() 564 565 // Create the subscription. 566 ch := make(chan int) 567 sub, err := client.Subscribe(context.Background(), "nftest", ch, "someSubscription", 1, 1) 568 if err != nil { 569 t.Fatal(err) 570 } 571 572 // Unsubscribe and check that unsubscribe was called. 573 sub.Unsubscribe() 574 if !recorder.unsubscribes[sub.subid] { 575 t.Fatal("client did not call unsubscribe method") 576 } 577 if _, open := <-sub.Err(); open { 578 t.Fatal("subscription error channel not closed after unsubscribe") 579 } 580 } 581 582 // This checks that the subscribed channel can be closed after Unsubscribe. 583 // It is the reproducer for https://github.com/ethereum/go-ethereum/issues/22322 584 func TestClientSubscriptionChannelClose(t *testing.T) { 585 t.Parallel() 586 587 var ( 588 srv = NewServer() 589 httpsrv = httptest.NewServer(srv.WebsocketHandler(nil)) 590 wsURL = "ws:" + strings.TrimPrefix(httpsrv.URL, "http:") 591 ) 592 defer srv.Stop() 593 defer httpsrv.Close() 594 595 srv.RegisterName("nftest", new(notificationTestService)) 596 client, _ := Dial(wsURL) 597 defer client.Close() 598 599 for i := 0; i < 100; i++ { 600 ch := make(chan int, 100) 601 sub, err := client.Subscribe(context.Background(), "nftest", ch, "someSubscription", 100, 1) 602 if err != nil { 603 t.Fatal(err) 604 } 605 sub.Unsubscribe() 606 close(ch) 607 } 608 } 609 610 // This test checks that Client doesn't lock up when a single subscriber 611 // doesn't read subscription events. 612 func TestClientNotificationStorm(t *testing.T) { 613 server := newTestServer() 614 defer server.Stop() 615 616 doTest := func(count int, wantError bool) { 617 client := DialInProc(server) 618 defer client.Close() 619 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 620 defer cancel() 621 622 // Subscribe on the server. It will start sending many notifications 623 // very quickly. 624 nc := make(chan int) 625 sub, err := client.Subscribe(ctx, "nftest", nc, "someSubscription", count, 0) 626 if err != nil { 627 t.Fatal("can't subscribe:", err) 628 } 629 defer sub.Unsubscribe() 630 631 // Process each notification, try to run a call in between each of them. 632 for i := 0; i < count; i++ { 633 select { 634 case val := <-nc: 635 if val != i { 636 t.Fatalf("(%d/%d) unexpected value %d", i, count, val) 637 } 638 case err := <-sub.Err(): 639 if wantError && err != ErrSubscriptionQueueOverflow { 640 t.Fatalf("(%d/%d) got error %q, want %q", i, count, err, ErrSubscriptionQueueOverflow) 641 } else if !wantError { 642 t.Fatalf("(%d/%d) got unexpected error %q", i, count, err) 643 } 644 return 645 } 646 var r int 647 err := client.CallContext(ctx, &r, "nftest_echo", i) 648 if err != nil { 649 if !wantError { 650 t.Fatalf("(%d/%d) call error: %v", i, count, err) 651 } 652 return 653 } 654 } 655 if wantError { 656 t.Fatalf("didn't get expected error") 657 } 658 } 659 660 doTest(8000, false) 661 doTest(24000, true) 662 } 663 664 func TestClientSetHeader(t *testing.T) { 665 var gotHeader bool 666 srv := newTestServer() 667 httpsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 668 if r.Header.Get("test") == "ok" { 669 gotHeader = true 670 } 671 srv.ServeHTTP(w, r) 672 })) 673 defer httpsrv.Close() 674 defer srv.Stop() 675 676 client, err := Dial(httpsrv.URL) 677 if err != nil { 678 t.Fatal(err) 679 } 680 defer client.Close() 681 682 client.SetHeader("test", "ok") 683 if _, err := client.SupportedModules(); err != nil { 684 t.Fatal(err) 685 } 686 if !gotHeader { 687 t.Fatal("client did not set custom header") 688 } 689 690 // Check that Content-Type can be replaced. 691 client.SetHeader("content-type", "application/x-garbage") 692 _, err = client.SupportedModules() 693 if err == nil { 694 t.Fatal("no error for invalid content-type header") 695 } else if !strings.Contains(err.Error(), "Unsupported Media Type") { 696 t.Fatalf("error is not related to content-type: %q", err) 697 } 698 } 699 700 func TestClientHTTP(t *testing.T) { 701 server := newTestServer() 702 defer server.Stop() 703 704 client, hs := httpTestClient(server, "http", nil) 705 defer hs.Close() 706 defer client.Close() 707 708 // Launch concurrent requests. 709 var ( 710 results = make([]echoResult, 100) 711 errc = make(chan error, len(results)) 712 wantResult = echoResult{"a", 1, new(echoArgs)} 713 ) 714 for i := range results { 715 i := i 716 go func() { 717 errc <- client.Call(&results[i], "test_echo", wantResult.String, wantResult.Int, wantResult.Args) 718 }() 719 } 720 721 // Wait for all of them to complete. 722 timeout := time.NewTimer(5 * time.Second) 723 defer timeout.Stop() 724 for i := range results { 725 select { 726 case err := <-errc: 727 if err != nil { 728 t.Fatal(err) 729 } 730 case <-timeout.C: 731 t.Fatalf("timeout (got %d/%d) results)", i+1, len(results)) 732 } 733 } 734 735 // Check results. 736 for i := range results { 737 if !reflect.DeepEqual(results[i], wantResult) { 738 t.Errorf("result %d mismatch: got %#v, want %#v", i, results[i], wantResult) 739 } 740 } 741 } 742 743 func TestClientReconnect(t *testing.T) { 744 startServer := func(addr string) (*Server, net.Listener) { 745 srv := newTestServer() 746 l, err := net.Listen("tcp", addr) 747 if err != nil { 748 t.Fatal("can't listen:", err) 749 } 750 go http.Serve(l, srv.WebsocketHandler([]string{"*"})) 751 return srv, l 752 } 753 754 ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second) 755 defer cancel() 756 757 // Start a server and corresponding client. 758 s1, l1 := startServer("127.0.0.1:0") 759 client, err := DialContext(ctx, "ws://"+l1.Addr().String()) 760 if err != nil { 761 t.Fatal("can't dial", err) 762 } 763 defer client.Close() 764 765 // Perform a call. This should work because the server is up. 766 var resp echoResult 767 if err := client.CallContext(ctx, &resp, "test_echo", "", 1, nil); err != nil { 768 t.Fatal(err) 769 } 770 771 // Shut down the server and allow for some cool down time so we can listen on the same 772 // address again. 773 l1.Close() 774 s1.Stop() 775 time.Sleep(2 * time.Second) 776 777 // Try calling again. It shouldn't work. 778 if err := client.CallContext(ctx, &resp, "test_echo", "", 2, nil); err == nil { 779 t.Error("successful call while the server is down") 780 t.Logf("resp: %#v", resp) 781 } 782 783 // Start it up again and call again. The connection should be reestablished. 784 // We spawn multiple calls here to check whether this hangs somehow. 785 s2, l2 := startServer(l1.Addr().String()) 786 defer l2.Close() 787 defer s2.Stop() 788 789 start := make(chan struct{}) 790 errors := make(chan error, 20) 791 for i := 0; i < cap(errors); i++ { 792 go func() { 793 <-start 794 var resp echoResult 795 errors <- client.CallContext(ctx, &resp, "test_echo", "", 3, nil) 796 }() 797 } 798 close(start) 799 errcount := 0 800 for i := 0; i < cap(errors); i++ { 801 if err = <-errors; err != nil { 802 errcount++ 803 } 804 } 805 t.Logf("%d errors, last error: %v", errcount, err) 806 if errcount > 1 { 807 t.Errorf("expected one error after disconnect, got %d", errcount) 808 } 809 } 810 811 func httpTestClient(srv *Server, transport string, fl *flakeyListener) (*Client, *httptest.Server) { 812 // Create the HTTP server. 813 var hs *httptest.Server 814 switch transport { 815 case "ws": 816 hs = httptest.NewUnstartedServer(srv.WebsocketHandler([]string{"*"})) 817 case "http": 818 hs = httptest.NewUnstartedServer(srv) 819 default: 820 panic("unknown HTTP transport: " + transport) 821 } 822 // Wrap the listener if required. 823 if fl != nil { 824 fl.Listener = hs.Listener 825 hs.Listener = fl 826 } 827 // Connect the client. 828 hs.Start() 829 client, err := Dial(transport + "://" + hs.Listener.Addr().String()) 830 if err != nil { 831 panic(err) 832 } 833 return client, hs 834 } 835 836 func ipcTestClient(srv *Server, fl *flakeyListener) (*Client, net.Listener) { 837 // Listen on a random endpoint. 838 endpoint := fmt.Sprintf("go-ethereum-test-ipc-%d-%d", os.Getpid(), rand.Int63()) 839 if runtime.GOOS == "windows" { 840 endpoint = `\\.\pipe\` + endpoint 841 } else { 842 endpoint = os.TempDir() + "/" + endpoint 843 } 844 l, err := ipcListen(endpoint) 845 if err != nil { 846 panic(err) 847 } 848 // Connect the listener to the server. 849 if fl != nil { 850 fl.Listener = l 851 l = fl 852 } 853 go srv.ServeListener(l) 854 // Connect the client. 855 client, err := Dial(endpoint) 856 if err != nil { 857 panic(err) 858 } 859 return client, l 860 } 861 862 // flakeyListener kills accepted connections after a random timeout. 863 type flakeyListener struct { 864 net.Listener 865 maxKillTimeout time.Duration 866 maxAcceptDelay time.Duration 867 } 868 869 func (l *flakeyListener) Accept() (net.Conn, error) { 870 delay := time.Duration(rand.Int63n(int64(l.maxAcceptDelay))) 871 time.Sleep(delay) 872 873 c, err := l.Listener.Accept() 874 if err == nil { 875 timeout := time.Duration(rand.Int63n(int64(l.maxKillTimeout))) 876 time.AfterFunc(timeout, func() { 877 log.Debug(fmt.Sprintf("killing conn %v after %v", c.LocalAddr(), timeout)) 878 c.Close() 879 }) 880 } 881 return c, err 882 }