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