github.com/klaytn/klaytn@v1.10.2/networks/rpc/client_test.go (about) 1 // Modifications Copyright 2018 The klaytn Authors 2 // Copyright 2016 The go-ethereum Authors 3 // This file is part of the go-ethereum library. 4 // 5 // The go-ethereum library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-ethereum library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 17 // 18 // This file is derived from rpc/client_test.go (2018/06/04). 19 // Modified and improved for the klaytn development. 20 21 package rpc 22 23 import ( 24 "context" 25 "encoding/binary" 26 "fmt" 27 "math/rand" 28 "net" 29 "net/http" 30 "net/http/httptest" 31 "os" 32 "reflect" 33 "runtime" 34 "strings" 35 "sync" 36 "testing" 37 "time" 38 39 "github.com/davecgh/go-spew/spew" 40 ) 41 42 func TestClientRequest(t *testing.T) { 43 server := newTestServer("service", new(Service)) 44 defer server.Stop() 45 client := DialInProc(server) 46 defer client.Close() 47 48 var resp Result 49 if err := client.Call(&resp, "service_echo", "hello", 10, &Args{"world"}); err != nil { 50 t.Fatal(err) 51 } 52 if !reflect.DeepEqual(resp, Result{"hello", 10, &Args{"world"}}) { 53 t.Errorf("incorrect result %#v", resp) 54 } 55 } 56 57 func TestClientBatchRequest(t *testing.T) { 58 server := newTestServer("service", new(Service)) 59 defer server.Stop() 60 client := DialInProc(server) 61 defer client.Close() 62 63 batch := []BatchElem{ 64 { 65 Method: "service_echo", 66 Args: []interface{}{"hello", 10, &Args{"world"}}, 67 Result: new(Result), 68 }, 69 { 70 Method: "service_echo", 71 Args: []interface{}{"hello2", 11, &Args{"world"}}, 72 Result: new(Result), 73 }, 74 { 75 Method: "no_such_method", 76 Args: []interface{}{1, 2, 3}, 77 Result: new(int), 78 }, 79 } 80 if err := client.BatchCall(batch); err != nil { 81 t.Fatal(err) 82 } 83 wantResult := []BatchElem{ 84 { 85 Method: "service_echo", 86 Args: []interface{}{"hello", 10, &Args{"world"}}, 87 Result: &Result{"hello", 10, &Args{"world"}}, 88 }, 89 { 90 Method: "service_echo", 91 Args: []interface{}{"hello2", 11, &Args{"world"}}, 92 Result: &Result{"hello2", 11, &Args{"world"}}, 93 }, 94 { 95 Method: "no_such_method", 96 Args: []interface{}{1, 2, 3}, 97 Result: new(int), 98 Error: &jsonError{Code: -32601, Message: "the method no_such_method does not exist/is not available"}, 99 }, 100 } 101 if !reflect.DeepEqual(batch, wantResult) { 102 t.Errorf("batch results mismatch:\ngot %swant %s", spew.Sdump(batch), spew.Sdump(wantResult)) 103 } 104 } 105 106 func TestClientNotify(t *testing.T) { 107 server := newTestServer("service", new(Service)) 108 defer server.Stop() 109 client := DialInProc(server) 110 defer client.Close() 111 112 if err := client.Notify(context.Background(), "service_echo", "hello", 10, &Args{"world"}); err != nil { 113 t.Fatal(err) 114 } 115 } 116 117 // func TestClientCancelInproc(t *testing.T) { testClientCancel("inproc", t) } 118 func TestClientCancelWebsocket(t *testing.T) { testClientCancel("ws", t) } 119 func TestClientCancelHTTP(t *testing.T) { testClientCancel("http", t) } 120 func TestClientCancelIPC(t *testing.T) { testClientCancel("ipc", t) } 121 122 // This test checks that requests made through CallContext can be canceled by canceling 123 // the context. 124 func testClientCancel(transport string, t *testing.T) { 125 server := newTestServer("service", new(Service)) 126 defer server.Stop() 127 128 // What we want to achieve is that the context gets canceled 129 // at various stages of request processing. The interesting cases 130 // are: 131 // - cancel during dial 132 // - cancel while performing a HTTP request 133 // - cancel while waiting for a response 134 // 135 // To trigger those, the times are chosen such that connections 136 // are killed within the deadline for every other call (maxKillTimeout 137 // is 2x maxCancelTimeout). 138 // 139 // Once a connection is dead, there is a fair chance it won't connect 140 // successfully because the accept is delayed by 1s. 141 maxContextCancelTimeout := 300 * time.Millisecond 142 fl := &flakeyListener{ 143 maxAcceptDelay: 1 * time.Second, 144 maxKillTimeout: 600 * time.Millisecond, 145 } 146 147 var client *Client 148 switch transport { 149 case "ws", "http": 150 c, hs := httpTestClient(server, transport, fl) 151 defer hs.Close() 152 client = c 153 case "ipc": 154 c, l := ipcTestClient(server, fl) 155 defer l.Close() 156 client = c 157 default: 158 panic("unknown transport: " + transport) 159 } 160 161 // These tests take a lot of time, run them all at once. 162 // You probably want to run with -parallel 1 or comment out 163 // the call to t.Parallel if you enable the logging. 164 // t.Parallel() 165 166 // The actual test starts here. 167 var ( 168 wg sync.WaitGroup 169 nreqs = 10 170 ncallers = 6 171 ) 172 caller := func(index int) { 173 defer wg.Done() 174 for i := 0; i < nreqs; i++ { 175 var ( 176 ctx context.Context 177 cancel func() 178 timeout = time.Duration(rand.Int63n(int64(maxContextCancelTimeout))) 179 ) 180 if index < ncallers/2 { 181 // For half of the callers, create a context without deadline 182 // and cancel it later. 183 ctx, cancel = context.WithCancel(context.Background()) 184 time.AfterFunc(timeout, cancel) 185 } else { 186 // For the other half, create a context with a deadline instead. This is 187 // different because the context deadline is used to set the socket write 188 // deadline. 189 ctx, cancel = context.WithTimeout(context.Background(), timeout) 190 } 191 // Now perform a call with the context. 192 // The key thing here is that no call will ever complete successfully. 193 err := client.CallContext(ctx, nil, "service_sleep", 2*maxContextCancelTimeout) 194 if err != nil { 195 logger.Debug(fmt.Sprint("got expected error:", err)) 196 } else { 197 t.Errorf("no error for call with %v wait time", timeout) 198 } 199 cancel() 200 } 201 } 202 wg.Add(ncallers) 203 for i := 0; i < ncallers; i++ { 204 go caller(i) 205 } 206 wg.Wait() 207 } 208 209 func TestClientSubscribeInvalidArg(t *testing.T) { 210 server := newTestServer("service", new(Service)) 211 defer server.Stop() 212 client := DialInProc(server) 213 defer client.Close() 214 215 check := func(shouldPanic bool, arg interface{}) { 216 defer func() { 217 err := recover() 218 if shouldPanic && err == nil { 219 t.Errorf("KlaySubscribe should've panicked for %#v", arg) 220 } 221 if !shouldPanic && err != nil { 222 t.Errorf("KlaySubscribe shouldn't have panicked for %#v", arg) 223 buf := make([]byte, 1024*1024) 224 buf = buf[:runtime.Stack(buf, false)] 225 t.Error(err) 226 t.Error(string(buf)) 227 } 228 }() 229 client.KlaySubscribe(context.Background(), arg, "foo_bar") 230 } 231 check(true, nil) 232 check(true, 1) 233 check(true, (chan int)(nil)) 234 check(true, make(<-chan int)) 235 check(false, make(chan int)) 236 check(false, make(chan<- int)) 237 } 238 239 func TestClientSubscribe(t *testing.T) { 240 server := newTestServer("klay", new(NotificationTestService)) 241 defer server.Stop() 242 client := DialInProc(server) 243 defer client.Close() 244 245 nc := make(chan int) 246 count := 10 247 sub, err := client.KlaySubscribe(context.Background(), nc, "someSubscription", count, 0) 248 if err != nil { 249 t.Fatal("can't subscribe:", err) 250 } 251 for i := 0; i < count; i++ { 252 if val := <-nc; val != i { 253 t.Fatalf("value mismatch: got %d, want %d", val, i) 254 } 255 } 256 257 sub.Unsubscribe() 258 select { 259 case v := <-nc: 260 t.Fatal("received value after unsubscribe:", v) 261 case err := <-sub.Err(): 262 if err != nil { 263 t.Fatalf("Err returned a non-nil error after explicit unsubscribe: %q", err) 264 } 265 case <-time.After(1 * time.Second): 266 t.Fatalf("subscription not closed within 1s after unsubscribe") 267 } 268 } 269 270 func TestClientSubscribeCustomNamespace(t *testing.T) { 271 namespace := "custom" 272 server := newTestServer(namespace, new(NotificationTestService)) 273 defer server.Stop() 274 client := DialInProc(server) 275 defer client.Close() 276 277 nc := make(chan int) 278 count := 10 279 sub, err := client.Subscribe(context.Background(), namespace, nc, "someSubscription", count, 0) 280 if err != nil { 281 t.Fatal("can't subscribe:", err) 282 } 283 for i := 0; i < count; i++ { 284 if val := <-nc; val != i { 285 t.Fatalf("value mismatch: got %d, want %d", val, i) 286 } 287 } 288 289 sub.Unsubscribe() 290 select { 291 case v := <-nc: 292 t.Fatal("received value after unsubscribe:", v) 293 case err := <-sub.Err(): 294 if err != nil { 295 t.Fatalf("Err returned a non-nil error after explicit unsubscribe: %q", err) 296 } 297 case <-time.After(1 * time.Second): 298 t.Fatalf("subscription not closed within 1s after unsubscribe") 299 } 300 } 301 302 // In this test, the connection drops while KlaySubscribe is 303 // waiting for a response. 304 func TestClientSubscribeClose(t *testing.T) { 305 service := &NotificationTestService{ 306 gotHangSubscriptionReq: make(chan struct{}), 307 unblockHangSubscription: make(chan struct{}), 308 } 309 server := newTestServer("klay", service) 310 defer server.Stop() 311 client := DialInProc(server) 312 defer client.Close() 313 314 var ( 315 nc = make(chan int) 316 errc = make(chan error) 317 sub *ClientSubscription 318 err error 319 ) 320 go func() { 321 sub, err = client.KlaySubscribe(context.Background(), nc, "hangSubscription", 999) 322 errc <- err 323 }() 324 325 <-service.gotHangSubscriptionReq 326 client.Close() 327 service.unblockHangSubscription <- struct{}{} 328 329 select { 330 case err := <-errc: 331 if err == nil { 332 t.Errorf("KlaySubscribe returned nil error after Close") 333 } 334 if sub != nil { 335 t.Error("KlaySubscribe returned non-nil subscription after Close") 336 } 337 case <-time.After(1 * time.Second): 338 t.Fatalf("KlaySubscribe did not return within 1s after Close") 339 } 340 } 341 342 /* TODO-Klaytn-FailedTest Test fails 343 // This test reproduces https://github.com/ethereum/go-ethereum/issues/17837 where the 344 // client hangs during shutdown when Unsubscribe races with Client.Close. 345 func TestClientCloseUnsubscribeRace(t *testing.T) { 346 service := &NotificationTestService{} 347 server := newTestServer("klay", service) 348 defer server.Stop() 349 350 for i := 0; i < 20; i++ { 351 client := DialInProc(server) 352 nc := make(chan int) 353 sub, err := client.KlaySubscribe(context.Background(), nc, "someSubscription", 3, 1) 354 if err != nil { 355 t.Fatal(err) 356 } 357 go client.Close() 358 go sub.Unsubscribe() 359 select { 360 case <-sub.Err(): 361 case <-time.After(5 * time.Second): 362 t.Fatal("subscription not closed within timeout") 363 } 364 } 365 } 366 */ 367 368 // This test checks that Client doesn't lock up when a single subscriber 369 // doesn't read subscription events. 370 func TestClientNotificationStorm(t *testing.T) { 371 server := newTestServer("klay", new(NotificationTestService)) 372 defer server.Stop() 373 374 doTest := func(count int, wantError bool) { 375 client := DialInProc(server) 376 defer client.Close() 377 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 378 defer cancel() 379 380 // Subscribe on the server. It will start sending many notifications 381 // very quickly. 382 nc := make(chan int) 383 sub, err := client.KlaySubscribe(ctx, nc, "someSubscription", count, 0) 384 if err != nil { 385 t.Fatal("can't subscribe:", err) 386 } 387 defer sub.Unsubscribe() 388 389 // Process each notification, try to run a call in between each of them. 390 for i := 0; i < count; i++ { 391 select { 392 case val := <-nc: 393 if val != i { 394 t.Fatalf("(%d/%d) unexpected value %d", i, count, val) 395 } 396 case err := <-sub.Err(): 397 if wantError && err != ErrSubscriptionQueueOverflow { 398 t.Fatalf("(%d/%d) got error %q, want %q", i, count, err, ErrSubscriptionQueueOverflow) 399 } else if !wantError { 400 t.Fatalf("(%d/%d) got unexpected error %q", i, count, err) 401 } 402 return 403 } 404 var r int 405 err := client.CallContext(ctx, &r, "klay_echo", i) 406 if err != nil { 407 if !wantError { 408 t.Fatalf("(%d/%d) call error: %v", i, count, err) 409 } 410 return 411 } 412 } 413 } 414 415 doTest(8000, false) 416 doTest(10000, true) 417 } 418 419 func TestClientSetHeader(t *testing.T) { 420 var gotHeader bool 421 srv := newTestServer("service", new(Service)) 422 httpsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 423 if r.Header.Get("test") == "ok" { 424 gotHeader = true 425 } 426 srv.ServeHTTP(w, r) 427 })) 428 defer httpsrv.Close() 429 defer srv.Stop() 430 431 client, err := Dial(httpsrv.URL) 432 if err != nil { 433 t.Fatal(err) 434 } 435 defer client.Close() 436 437 client.SetHeader("test", "ok") 438 if _, err := client.SupportedModules(); err != nil { 439 t.Fatal(err) 440 } 441 if !gotHeader { 442 t.Fatal("client did not set custom header") 443 } 444 445 // Check that Content-Type can be replaced. 446 client.SetHeader("content-type", "application/x-garbage") 447 _, err = client.SupportedModules() 448 if err == nil { 449 t.Fatal("no error for invalid content-type header") 450 } else if !strings.Contains(err.Error(), "Unsupported Media Type") { 451 t.Fatalf("error is not related to content-type: %q", err) 452 } 453 } 454 455 func TestClientHTTP(t *testing.T) { 456 server := newTestServer("service", new(Service)) 457 defer server.Stop() 458 459 client, hs := httpTestClient(server, "http", nil) 460 defer hs.Close() 461 defer client.Close() 462 463 // Launch concurrent requests. 464 var ( 465 results = make([]Result, 100) 466 errc = make(chan error) 467 wantResult = Result{"a", 1, new(Args)} 468 ) 469 defer client.Close() 470 for i := range results { 471 i := i 472 go func() { 473 errc <- client.Call(&results[i], "service_echo", 474 wantResult.String, wantResult.Int, wantResult.Args) 475 }() 476 } 477 478 // Wait for all of them to complete. 479 timeout := time.NewTimer(5 * time.Second) 480 defer timeout.Stop() 481 for i := range results { 482 select { 483 case err := <-errc: 484 if err != nil { 485 t.Fatal(err) 486 } 487 case <-timeout.C: 488 t.Fatalf("timeout (got %d/%d) results)", i+1, len(results)) 489 } 490 } 491 492 // Check results. 493 for i := range results { 494 if !reflect.DeepEqual(results[i], wantResult) { 495 t.Errorf("result %d mismatch: got %#v, want %#v", i, results[i], wantResult) 496 } 497 } 498 } 499 500 func TestClientReconnect(t *testing.T) { 501 startServer := func(addr string) (*Server, net.Listener) { 502 srv := newTestServer("service", new(Service)) 503 l, err := net.Listen("tcp", addr) 504 if err != nil { 505 t.Fatal(err) 506 } 507 go http.Serve(l, srv.WebsocketHandler([]string{"*"})) 508 return srv, l 509 } 510 511 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 512 defer cancel() 513 514 // Start a server and corresponding client. 515 s1, l1 := startServer("127.0.0.1:0") 516 client, err := DialContext(ctx, "ws://"+l1.Addr().String()) 517 if err != nil { 518 t.Fatal("can't dial", err) 519 } 520 521 // Perform a call. This should work because the server is up. 522 var resp Result 523 if err := client.CallContext(ctx, &resp, "service_echo", "", 1, nil); err != nil { 524 t.Fatal(err) 525 } 526 527 // Shut down the server and try calling again. It shouldn't work. 528 l1.Close() 529 s1.Stop() 530 if err := client.CallContext(ctx, &resp, "service_echo", "", 2, nil); err == nil { 531 t.Error("successful call while the server is down") 532 t.Logf("resp: %#v", resp) 533 } 534 535 // Allow for some cool down time so we can listen on the same address again. 536 time.Sleep(2 * time.Second) 537 538 // Start it up again and call again. The connection should be reestablished. 539 // We spawn multiple calls here to check whether this hangs somehow. 540 s2, l2 := startServer(l1.Addr().String()) 541 defer l2.Close() 542 defer s2.Stop() 543 544 start := make(chan struct{}) 545 errors := make(chan error, 20) 546 for i := 0; i < cap(errors); i++ { 547 go func() { 548 <-start 549 var resp Result 550 errors <- client.CallContext(ctx, &resp, "service_echo", "", 3, nil) 551 }() 552 } 553 close(start) 554 errcount := 0 555 for i := 0; i < cap(errors); i++ { 556 if err = <-errors; err != nil { 557 errcount++ 558 } 559 } 560 if errcount > 1 { 561 t.Errorf("expected one error after disconnect, got %d", errcount) 562 } 563 } 564 565 func newTestServer(serviceName string, service interface{}) *Server { 566 server := NewServer() 567 server.idgen = sequentialIDGenerator() 568 if err := server.RegisterName(serviceName, service); err != nil { 569 panic(err) 570 } 571 return server 572 } 573 574 func sequentialIDGenerator() func() ID { 575 var ( 576 mu sync.Mutex 577 counter uint64 578 ) 579 return func() ID { 580 mu.Lock() 581 defer mu.Unlock() 582 counter++ 583 id := make([]byte, 8) 584 binary.BigEndian.PutUint64(id, counter) 585 return encodeID(id) 586 } 587 } 588 589 func httpTestClient(srv *Server, transport string, fl *flakeyListener) (*Client, *httptest.Server) { 590 // Create the HTTP server. 591 var hs *httptest.Server 592 switch transport { 593 case "ws": 594 hs = httptest.NewUnstartedServer(srv.WebsocketHandler([]string{"*"})) 595 case "http": 596 hs = httptest.NewUnstartedServer(srv) 597 default: 598 panic("unknown HTTP transport: " + transport) 599 } 600 // Wrap the listener if required. 601 if fl != nil { 602 fl.Listener = hs.Listener 603 hs.Listener = fl 604 } 605 // Connect the client. 606 hs.Start() 607 client, err := Dial(transport + "://" + hs.Listener.Addr().String()) 608 if err != nil { 609 panic(err) 610 } 611 return client, hs 612 } 613 614 func ipcTestClient(srv *Server, fl *flakeyListener) (*Client, net.Listener) { 615 // Listen on a random endpoint. 616 endpoint := fmt.Sprintf("klaytn-test-ipc-%d-%d", os.Getpid(), rand.Int63()) 617 if runtime.GOOS == "windows" { 618 endpoint = `\\.\pipe\` + endpoint 619 } else { 620 endpoint = os.TempDir() + "/" + endpoint 621 } 622 l, err := ipcListen(endpoint) 623 if err != nil { 624 panic(err) 625 } 626 // Connect the listener to the server. 627 if fl != nil { 628 fl.Listener = l 629 l = fl 630 } 631 go srv.ServeListener(l) 632 // Connect the client. 633 client, err := Dial(endpoint) 634 if err != nil { 635 panic(err) 636 } 637 return client, l 638 } 639 640 // flakeyListener kills accepted connections after a random timeout. 641 type flakeyListener struct { 642 net.Listener 643 maxKillTimeout time.Duration 644 maxAcceptDelay time.Duration 645 } 646 647 func (l *flakeyListener) Accept() (net.Conn, error) { 648 delay := time.Duration(rand.Int63n(int64(l.maxAcceptDelay))) 649 time.Sleep(delay) 650 651 c, err := l.Listener.Accept() 652 if err == nil { 653 timeout := time.Duration(rand.Int63n(int64(l.maxKillTimeout))) 654 time.AfterFunc(timeout, func() { 655 logger.Debug(fmt.Sprintf("killing conn %v after %v", c.LocalAddr(), timeout)) 656 c.Close() 657 }) 658 } 659 return c, err 660 } 661 662 // This test reproduces https://github.com/ethereum/go-ethereum/issues/17837 where the 663 // client hangs during shutdown when Unsubscribe races with Client.Close. 664 func TestClientCloseUnsubscribeRace(t *testing.T) { 665 service := &NotificationTestService{} 666 server := newTestServer("klay", service) 667 defer server.Stop() 668 669 for i := 0; i < 20; i++ { 670 client := DialInProc(server) 671 nc := make(chan int) 672 sub, err := client.KlaySubscribe(context.Background(), nc, "someSubscription", 3, 1) 673 if err != nil { 674 t.Fatal(err) 675 } 676 go client.Close() 677 go sub.Unsubscribe() 678 select { 679 case <-sub.Err(): 680 case <-time.After(5 * time.Second): 681 t.Fatal("subscription not closed within timeout") 682 } 683 } 684 }