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