github.com/linapex/ethereum-go-chinese@v0.0.0-20190316121929-f8b7a73c3fa1/rpc/client_test.go (about) 1 2 //<developer> 3 // <name>linapex 曹一峰</name> 4 // <email>linapex@163.com</email> 5 // <wx>superexc</wx> 6 // <qqgroup>128148617</qqgroup> 7 // <url>https://jsq.ink</url> 8 // <role>pku engineer</role> 9 // <date>2019-03-16 19:16:42</date> 10 //</624450108569686016> 11 12 13 package rpc 14 15 import ( 16 "context" 17 "fmt" 18 "math/rand" 19 "net" 20 "net/http" 21 "net/http/httptest" 22 "os" 23 "reflect" 24 "runtime" 25 "sync" 26 "testing" 27 "time" 28 29 "github.com/davecgh/go-spew/spew" 30 "github.com/ethereum/go-ethereum/log" 31 ) 32 33 func TestClientRequest(t *testing.T) { 34 server := newTestServer("service", new(Service)) 35 defer server.Stop() 36 client := DialInProc(server) 37 defer client.Close() 38 39 var resp Result 40 if err := client.Call(&resp, "service_echo", "hello", 10, &Args{"world"}); err != nil { 41 t.Fatal(err) 42 } 43 if !reflect.DeepEqual(resp, Result{"hello", 10, &Args{"world"}}) { 44 t.Errorf("incorrect result %#v", resp) 45 } 46 } 47 48 func TestClientBatchRequest(t *testing.T) { 49 server := newTestServer("service", new(Service)) 50 defer server.Stop() 51 client := DialInProc(server) 52 defer client.Close() 53 54 batch := []BatchElem{ 55 { 56 Method: "service_echo", 57 Args: []interface{}{"hello", 10, &Args{"world"}}, 58 Result: new(Result), 59 }, 60 { 61 Method: "service_echo", 62 Args: []interface{}{"hello2", 11, &Args{"world"}}, 63 Result: new(Result), 64 }, 65 { 66 Method: "no_such_method", 67 Args: []interface{}{1, 2, 3}, 68 Result: new(int), 69 }, 70 } 71 if err := client.BatchCall(batch); err != nil { 72 t.Fatal(err) 73 } 74 wantResult := []BatchElem{ 75 { 76 Method: "service_echo", 77 Args: []interface{}{"hello", 10, &Args{"world"}}, 78 Result: &Result{"hello", 10, &Args{"world"}}, 79 }, 80 { 81 Method: "service_echo", 82 Args: []interface{}{"hello2", 11, &Args{"world"}}, 83 Result: &Result{"hello2", 11, &Args{"world"}}, 84 }, 85 { 86 Method: "no_such_method", 87 Args: []interface{}{1, 2, 3}, 88 Result: new(int), 89 Error: &jsonError{Code: -32601, Message: "The method no_such_method_ does not exist/is not available"}, 90 }, 91 } 92 if !reflect.DeepEqual(batch, wantResult) { 93 t.Errorf("batch results mismatch:\ngot %swant %s", spew.Sdump(batch), spew.Sdump(wantResult)) 94 } 95 } 96 97 //func testclientcancel inproc(t*testing.t)testclientcancel(“inproc”,t) 98 func TestClientCancelWebsocket(t *testing.T) { testClientCancel("ws", t) } 99 func TestClientCancelHTTP(t *testing.T) { testClientCancel("http", t) } 100 func TestClientCancelIPC(t *testing.T) { testClientCancel("ipc", t) } 101 102 //此测试检查通过callContext发出的请求是否可以通过取消来取消 103 //语境。 104 func testClientCancel(transport string, t *testing.T) { 105 server := newTestServer("service", new(Service)) 106 defer server.Stop() 107 108 //我们想要实现的是取消上下文 109 //在请求处理的各个阶段。有趣的案例 110 //是: 111 //-拨号时取消 112 //-执行HTTP请求时取消 113 //-等待响应时取消 114 // 115 //为了触发这些,时间的选择使得连接 116 //在每个其他呼叫的截止时间内终止(maxkillTimeout 117 //是2x maxCancelTimeout)。 118 // 119 //一旦连接断开,很有可能无法连接 120 //成功,因为接受延迟了1秒。 121 maxContextCancelTimeout := 300 * time.Millisecond 122 fl := &flakeyListener{ 123 maxAcceptDelay: 1 * time.Second, 124 maxKillTimeout: 600 * time.Millisecond, 125 } 126 127 var client *Client 128 switch transport { 129 case "ws", "http": 130 c, hs := httpTestClient(server, transport, fl) 131 defer hs.Close() 132 client = c 133 case "ipc": 134 c, l := ipcTestClient(server, fl) 135 defer l.Close() 136 client = c 137 default: 138 panic("unknown transport: " + transport) 139 } 140 141 //这些测试需要很多时间,一次运行它们。 142 //您可能希望使用-parallel 1或comment out运行 143 //如果启用日志记录,则调用T.Parallel。 144 t.Parallel() 145 146 //实际测试从这里开始。 147 var ( 148 wg sync.WaitGroup 149 nreqs = 10 150 ncallers = 6 151 ) 152 caller := func(index int) { 153 defer wg.Done() 154 for i := 0; i < nreqs; i++ { 155 var ( 156 ctx context.Context 157 cancel func() 158 timeout = time.Duration(rand.Int63n(int64(maxContextCancelTimeout))) 159 ) 160 if index < ncallers/2 { 161 //对于一半的调用者,创建一个没有截止日期的上下文 162 //以后再取消。 163 ctx, cancel = context.WithCancel(context.Background()) 164 time.AfterFunc(timeout, cancel) 165 } else { 166 //对于另一半,创建一个带有截止日期的上下文。这是 167 //不同,因为上下文期限用于设置套接字写入 168 //截止日期。 169 ctx, cancel = context.WithTimeout(context.Background(), timeout) 170 } 171 //现在使用上下文执行调用。 172 //这里的关键是没有一个呼叫能成功完成。 173 err := client.CallContext(ctx, nil, "service_sleep", 2*maxContextCancelTimeout) 174 if err != nil { 175 log.Debug(fmt.Sprint("got expected error:", err)) 176 } else { 177 t.Errorf("no error for call with %v wait time", timeout) 178 } 179 cancel() 180 } 181 } 182 wg.Add(ncallers) 183 for i := 0; i < ncallers; i++ { 184 go caller(i) 185 } 186 wg.Wait() 187 } 188 189 func TestClientSubscribeInvalidArg(t *testing.T) { 190 server := newTestServer("service", new(Service)) 191 defer server.Stop() 192 client := DialInProc(server) 193 defer client.Close() 194 195 check := func(shouldPanic bool, arg interface{}) { 196 defer func() { 197 err := recover() 198 if shouldPanic && err == nil { 199 t.Errorf("EthSubscribe should've panicked for %#v", arg) 200 } 201 if !shouldPanic && err != nil { 202 t.Errorf("EthSubscribe shouldn't have panicked for %#v", arg) 203 buf := make([]byte, 1024*1024) 204 buf = buf[:runtime.Stack(buf, false)] 205 t.Error(err) 206 t.Error(string(buf)) 207 } 208 }() 209 client.EthSubscribe(context.Background(), arg, "foo_bar") 210 } 211 check(true, nil) 212 check(true, 1) 213 check(true, (chan int)(nil)) 214 check(true, make(<-chan int)) 215 check(false, make(chan int)) 216 check(false, make(chan<- int)) 217 } 218 219 func TestClientSubscribe(t *testing.T) { 220 server := newTestServer("eth", new(NotificationTestService)) 221 defer server.Stop() 222 client := DialInProc(server) 223 defer client.Close() 224 225 nc := make(chan int) 226 count := 10 227 sub, err := client.EthSubscribe(context.Background(), nc, "someSubscription", count, 0) 228 if err != nil { 229 t.Fatal("can't subscribe:", err) 230 } 231 for i := 0; i < count; i++ { 232 if val := <-nc; val != i { 233 t.Fatalf("value mismatch: got %d, want %d", val, i) 234 } 235 } 236 237 sub.Unsubscribe() 238 select { 239 case v := <-nc: 240 t.Fatal("received value after unsubscribe:", v) 241 case err := <-sub.Err(): 242 if err != nil { 243 t.Fatalf("Err returned a non-nil error after explicit unsubscribe: %q", err) 244 } 245 case <-time.After(1 * time.Second): 246 t.Fatalf("subscription not closed within 1s after unsubscribe") 247 } 248 } 249 250 func TestClientSubscribeCustomNamespace(t *testing.T) { 251 namespace := "custom" 252 server := newTestServer(namespace, new(NotificationTestService)) 253 defer server.Stop() 254 client := DialInProc(server) 255 defer client.Close() 256 257 nc := make(chan int) 258 count := 10 259 sub, err := client.Subscribe(context.Background(), namespace, nc, "someSubscription", count, 0) 260 if err != nil { 261 t.Fatal("can't subscribe:", err) 262 } 263 for i := 0; i < count; i++ { 264 if val := <-nc; val != i { 265 t.Fatalf("value mismatch: got %d, want %d", val, i) 266 } 267 } 268 269 sub.Unsubscribe() 270 select { 271 case v := <-nc: 272 t.Fatal("received value after unsubscribe:", v) 273 case err := <-sub.Err(): 274 if err != nil { 275 t.Fatalf("Err returned a non-nil error after explicit unsubscribe: %q", err) 276 } 277 case <-time.After(1 * time.Second): 278 t.Fatalf("subscription not closed within 1s after unsubscribe") 279 } 280 } 281 282 //在这个测试中,当ethsubscribe 283 //正在等待响应。 284 func TestClientSubscribeClose(t *testing.T) { 285 service := &NotificationTestService{ 286 gotHangSubscriptionReq: make(chan struct{}), 287 unblockHangSubscription: make(chan struct{}), 288 } 289 server := newTestServer("eth", service) 290 defer server.Stop() 291 client := DialInProc(server) 292 defer client.Close() 293 294 var ( 295 nc = make(chan int) 296 errc = make(chan error) 297 sub *ClientSubscription 298 err error 299 ) 300 go func() { 301 sub, err = client.EthSubscribe(context.Background(), nc, "hangSubscription", 999) 302 errc <- err 303 }() 304 305 <-service.gotHangSubscriptionReq 306 client.Close() 307 service.unblockHangSubscription <- struct{}{} 308 309 select { 310 case err := <-errc: 311 if err == nil { 312 t.Errorf("EthSubscribe returned nil error after Close") 313 } 314 if sub != nil { 315 t.Error("EthSubscribe returned non-nil subscription after Close") 316 } 317 case <-time.After(1 * time.Second): 318 t.Fatalf("EthSubscribe did not return within 1s after Close") 319 } 320 } 321 322 //此测试复制了https://github.com/ethereum/go-ethereum/issues/17837,其中 323 //当unsubscribe与client.close竞争时,客户机在关闭期间挂起。 324 func TestClientCloseUnsubscribeRace(t *testing.T) { 325 service := &NotificationTestService{} 326 server := newTestServer("eth", service) 327 defer server.Stop() 328 329 for i := 0; i < 20; i++ { 330 client := DialInProc(server) 331 nc := make(chan int) 332 sub, err := client.EthSubscribe(context.Background(), nc, "someSubscription", 3, 1) 333 if err != nil { 334 t.Fatal(err) 335 } 336 go client.Close() 337 go sub.Unsubscribe() 338 select { 339 case <-sub.Err(): 340 case <-time.After(5 * time.Second): 341 t.Fatal("subscription not closed within timeout") 342 } 343 } 344 } 345 346 //此测试检查当单个订阅服务器 347 //不读取订阅事件。 348 func TestClientNotificationStorm(t *testing.T) { 349 server := newTestServer("eth", new(NotificationTestService)) 350 defer server.Stop() 351 352 doTest := func(count int, wantError bool) { 353 client := DialInProc(server) 354 defer client.Close() 355 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 356 defer cancel() 357 358 //订阅服务器。它将开始发送许多通知 359 //很快。 360 nc := make(chan int) 361 sub, err := client.EthSubscribe(ctx, nc, "someSubscription", count, 0) 362 if err != nil { 363 t.Fatal("can't subscribe:", err) 364 } 365 defer sub.Unsubscribe() 366 367 //处理每个通知,尝试在它们之间运行一个调用。 368 for i := 0; i < count; i++ { 369 select { 370 case val := <-nc: 371 if val != i { 372 t.Fatalf("(%d/%d) unexpected value %d", i, count, val) 373 } 374 case err := <-sub.Err(): 375 if wantError && err != ErrSubscriptionQueueOverflow { 376 t.Fatalf("(%d/%d) got error %q, want %q", i, count, err, ErrSubscriptionQueueOverflow) 377 } else if !wantError { 378 t.Fatalf("(%d/%d) got unexpected error %q", i, count, err) 379 } 380 return 381 } 382 var r int 383 err := client.CallContext(ctx, &r, "eth_echo", i) 384 if err != nil { 385 if !wantError { 386 t.Fatalf("(%d/%d) call error: %v", i, count, err) 387 } 388 return 389 } 390 } 391 } 392 393 doTest(8000, false) 394 doTest(10000, true) 395 } 396 397 func TestClientHTTP(t *testing.T) { 398 server := newTestServer("service", new(Service)) 399 defer server.Stop() 400 401 client, hs := httpTestClient(server, "http", nil) 402 defer hs.Close() 403 defer client.Close() 404 405 //启动并发请求。 406 var ( 407 results = make([]Result, 100) 408 errc = make(chan error) 409 wantResult = Result{"a", 1, new(Args)} 410 ) 411 defer client.Close() 412 for i := range results { 413 i := i 414 go func() { 415 errc <- client.Call(&results[i], "service_echo", 416 wantResult.String, wantResult.Int, wantResult.Args) 417 }() 418 } 419 420 //等待所有任务完成。 421 timeout := time.NewTimer(5 * time.Second) 422 defer timeout.Stop() 423 for i := range results { 424 select { 425 case err := <-errc: 426 if err != nil { 427 t.Fatal(err) 428 } 429 case <-timeout.C: 430 t.Fatalf("timeout (got %d/%d) results)", i+1, len(results)) 431 } 432 } 433 434 //检查结果。 435 for i := range results { 436 if !reflect.DeepEqual(results[i], wantResult) { 437 t.Errorf("result %d mismatch: got %#v, want %#v", i, results[i], wantResult) 438 } 439 } 440 } 441 442 func TestClientReconnect(t *testing.T) { 443 startServer := func(addr string) (*Server, net.Listener) { 444 srv := newTestServer("service", new(Service)) 445 l, err := net.Listen("tcp", addr) 446 if err != nil { 447 t.Fatal(err) 448 } 449 go http.Serve(l, srv.WebsocketHandler([]string{"*"})) 450 return srv, l 451 } 452 453 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 454 defer cancel() 455 456 //启动服务器和相应的客户机。 457 s1, l1 := startServer("127.0.0.1:0") 458 client, err := DialContext(ctx, "ws://“+l1.addr().string()) 459 if err != nil { 460 t.Fatal("can't dial", err) 461 } 462 463 //打个电话。这应该可以工作,因为服务器已启动。 464 var resp Result 465 if err := client.CallContext(ctx, &resp, "service_echo", "", 1, nil); err != nil { 466 t.Fatal(err) 467 } 468 469 //关闭服务器,然后再次尝试呼叫。它不应该起作用。 470 l1.Close() 471 s1.Stop() 472 if err := client.CallContext(ctx, &resp, "service_echo", "", 2, nil); err == nil { 473 t.Error("successful call while the server is down") 474 t.Logf("resp: %#v", resp) 475 } 476 477 //留出一些冷却时间,以便我们可以再次收听相同的地址。 478 time.Sleep(2 * time.Second) 479 480 //重新启动然后再打电话。应重新建立连接。 481 //我们在这里生成多个调用来检查这个是否以某种方式挂起。 482 s2, l2 := startServer(l1.Addr().String()) 483 defer l2.Close() 484 defer s2.Stop() 485 486 start := make(chan struct{}) 487 errors := make(chan error, 20) 488 for i := 0; i < cap(errors); i++ { 489 go func() { 490 <-start 491 var resp Result 492 errors <- client.CallContext(ctx, &resp, "service_echo", "", 3, nil) 493 }() 494 } 495 close(start) 496 errcount := 0 497 for i := 0; i < cap(errors); i++ { 498 if err = <-errors; err != nil { 499 errcount++ 500 } 501 } 502 t.Log("err:", err) 503 if errcount > 1 { 504 t.Errorf("expected one error after disconnect, got %d", errcount) 505 } 506 } 507 508 func newTestServer(serviceName string, service interface{}) *Server { 509 server := NewServer() 510 if err := server.RegisterName(serviceName, service); err != nil { 511 panic(err) 512 } 513 return server 514 } 515 516 func httpTestClient(srv *Server, transport string, fl *flakeyListener) (*Client, *httptest.Server) { 517 //创建HTTP服务器。 518 var hs *httptest.Server 519 switch transport { 520 case "ws": 521 hs = httptest.NewUnstartedServer(srv.WebsocketHandler([]string{"*"})) 522 case "http": 523 hs = httptest.NewUnstartedServer(srv) 524 default: 525 panic("unknown HTTP transport: " + transport) 526 } 527 //根据需要包装侦听器。 528 if fl != nil { 529 fl.Listener = hs.Listener 530 hs.Listener = fl 531 } 532 //连接客户端。 533 hs.Start() 534 client, err := Dial(transport + "://“+hs.listener.addr().string()) 535 if err != nil { 536 panic(err) 537 } 538 return client, hs 539 } 540 541 func ipcTestClient(srv *Server, fl *flakeyListener) (*Client, net.Listener) { 542 //在随机端点上侦听。 543 endpoint := fmt.Sprintf("go-ethereum-test-ipc-%d-%d", os.Getpid(), rand.Int63()) 544 if runtime.GOOS == "windows" { 545 endpoint = `\\.\pipe\` + endpoint 546 } else { 547 endpoint = os.TempDir() + "/" + endpoint 548 } 549 l, err := ipcListen(endpoint) 550 if err != nil { 551 panic(err) 552 } 553 //将侦听器连接到服务器。 554 if fl != nil { 555 fl.Listener = l 556 l = fl 557 } 558 go srv.ServeListener(l) 559 //连接客户端。 560 client, err := Dial(endpoint) 561 if err != nil { 562 panic(err) 563 } 564 return client, l 565 } 566 567 //flakeylistener在随机超时后终止接受的连接。 568 type flakeyListener struct { 569 net.Listener 570 maxKillTimeout time.Duration 571 maxAcceptDelay time.Duration 572 } 573 574 func (l *flakeyListener) Accept() (net.Conn, error) { 575 delay := time.Duration(rand.Int63n(int64(l.maxAcceptDelay))) 576 time.Sleep(delay) 577 578 c, err := l.Listener.Accept() 579 if err == nil { 580 timeout := time.Duration(rand.Int63n(int64(l.maxKillTimeout))) 581 time.AfterFunc(timeout, func() { 582 log.Debug(fmt.Sprintf("killing conn %v after %v", c.LocalAddr(), timeout)) 583 c.Close() 584 }) 585 } 586 return c, err 587 } 588