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