go.uber.org/yarpc@v1.72.1/peer/randpeer/list_test.go (about) 1 // Copyright (c) 2022 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package randpeer 22 23 import ( 24 "context" 25 "fmt" 26 "net" 27 "sort" 28 "strings" 29 "sync" 30 "testing" 31 "time" 32 33 "github.com/golang/mock/gomock" 34 "github.com/stretchr/testify/assert" 35 "github.com/stretchr/testify/require" 36 "go.uber.org/multierr" 37 "go.uber.org/yarpc" 38 "go.uber.org/yarpc/api/peer" 39 . "go.uber.org/yarpc/api/peer/peertest" 40 "go.uber.org/yarpc/api/transport" 41 "go.uber.org/yarpc/internal/testtime" 42 "go.uber.org/yarpc/internal/whitespace" 43 "go.uber.org/yarpc/transport/http" 44 "go.uber.org/yarpc/yarpcconfig" 45 "go.uber.org/yarpc/yarpcerrors" 46 "go.uber.org/zap/zaptest" 47 ) 48 49 func newNotRunningError(err string) error { 50 return yarpcerrors.FailedPreconditionErrorf(`"random" peer list is not running: %s`, err) 51 } 52 53 func TestRandPeer(t *testing.T) { 54 type testStruct struct { 55 msg string 56 57 // PeerIDs that will be returned from the transport's OnRetain with "Available" status 58 retainedAvailablePeerIDs []string 59 60 // PeerIDs that will be returned from the transport's OnRetain with "Unavailable" status 61 retainedUnavailablePeerIDs []string 62 63 // PeerIDs that will be released from the transport 64 releasedPeerIDs []string 65 66 // PeerIDs that will return "retainErr" from the transport's OnRetain function 67 errRetainedPeerIDs []string 68 retainErr error 69 70 // PeerIDs that will return "releaseErr" from the transport's OnRelease function 71 errReleasedPeerIDs []string 72 releaseErr error 73 74 // A list of actions that will be applied on the PeerList 75 peerListActions []PeerListAction 76 77 // PeerIDs expected to be in the PeerList's "Available" list after the actions have been applied 78 expectedAvailablePeers []string 79 80 // PeerIDs expected to be in the PeerList's "Unavailable" list after the actions have been applied 81 expectedUnavailablePeers []string 82 83 // Boolean indicating whether the PeerList is "running" after the actions have been applied 84 expectedRunning bool 85 } 86 tests := []testStruct{ 87 { 88 msg: "setup", 89 retainedAvailablePeerIDs: []string{"1"}, 90 expectedAvailablePeers: []string{"1"}, 91 peerListActions: []PeerListAction{ 92 StartAction{}, 93 UpdateAction{AddedPeerIDs: []string{"1"}}, 94 }, 95 expectedRunning: true, 96 }, 97 { 98 msg: "setup with disconnected", 99 retainedAvailablePeerIDs: []string{"1"}, 100 retainedUnavailablePeerIDs: []string{"2"}, 101 peerListActions: []PeerListAction{ 102 StartAction{}, 103 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 104 }, 105 expectedAvailablePeers: []string{"1"}, 106 expectedUnavailablePeers: []string{"2"}, 107 expectedRunning: true, 108 }, 109 { 110 msg: "start", 111 retainedAvailablePeerIDs: []string{"1"}, 112 expectedAvailablePeers: []string{"1"}, 113 peerListActions: []PeerListAction{ 114 StartAction{}, 115 UpdateAction{AddedPeerIDs: []string{"1"}}, 116 ChooseAction{ 117 ExpectedPeer: "1", 118 }, 119 }, 120 expectedRunning: true, 121 }, 122 { 123 msg: "start stop", 124 retainedAvailablePeerIDs: []string{"1", "2", "3", "4", "5", "6"}, 125 retainedUnavailablePeerIDs: []string{"7", "8", "9"}, 126 releasedPeerIDs: []string{"1", "2", "3", "4", "5", "6", "7", "8", "9"}, 127 peerListActions: []PeerListAction{ 128 StartAction{}, 129 UpdateAction{AddedPeerIDs: []string{"1", "2", "3", "4", "5", "6", "7", "8", "9"}}, 130 StopAction{}, 131 ChooseAction{ 132 ExpectedErr: newNotRunningError("could not wait for instance to start running: current state is \"stopped\""), 133 InputContextTimeout: 10 * time.Millisecond, 134 }, 135 }, 136 expectedRunning: false, 137 }, 138 { 139 msg: "update, start, and choose", 140 retainedAvailablePeerIDs: []string{"1"}, 141 expectedAvailablePeers: []string{"1"}, 142 peerListActions: []PeerListAction{ 143 UpdateAction{AddedPeerIDs: []string{"1"}}, 144 StartAction{}, 145 ChooseAction{ExpectedPeer: "1"}, 146 }, 147 expectedRunning: true, 148 }, 149 { 150 msg: "start many and choose", 151 retainedAvailablePeerIDs: []string{"1", "2", "3", "4", "5", "6"}, 152 expectedAvailablePeers: []string{"1", "2", "3", "4", "5", "6"}, 153 peerListActions: []PeerListAction{ 154 StartAction{}, 155 UpdateAction{AddedPeerIDs: []string{"1", "2", "3", "4", "5", "6"}}, 156 ChooseAction{ExpectedPeer: "1"}, 157 ChooseAction{ExpectedPeer: "1"}, 158 ChooseAction{ExpectedPeer: "2"}, 159 ChooseAction{ExpectedPeer: "5"}, 160 ChooseAction{ExpectedPeer: "6"}, 161 ChooseAction{ExpectedPeer: "5"}, 162 ChooseAction{ExpectedPeer: "2"}, 163 }, 164 expectedRunning: true, 165 }, 166 { 167 msg: "assure start is idempotent", 168 retainedAvailablePeerIDs: []string{"1"}, 169 expectedAvailablePeers: []string{"1"}, 170 peerListActions: []PeerListAction{ 171 StartAction{}, 172 UpdateAction{AddedPeerIDs: []string{"1"}}, 173 StartAction{}, 174 StartAction{}, 175 ChooseAction{ 176 ExpectedPeer: "1", 177 }, 178 }, 179 expectedRunning: true, 180 }, 181 { 182 msg: "stop no start", 183 retainedAvailablePeerIDs: []string{}, 184 releasedPeerIDs: []string{}, 185 peerListActions: []PeerListAction{ 186 StopAction{}, 187 UpdateAction{AddedPeerIDs: []string{"1"}}, 188 }, 189 expectedRunning: false, 190 }, 191 { 192 msg: "update retain error", 193 errRetainedPeerIDs: []string{"1"}, 194 retainErr: peer.ErrInvalidPeerType{}, 195 peerListActions: []PeerListAction{ 196 StartAction{}, 197 UpdateAction{AddedPeerIDs: []string{"1"}, ExpectedErr: peer.ErrInvalidPeerType{}}, 198 }, 199 expectedRunning: true, 200 }, 201 { 202 msg: "update retain multiple errors", 203 retainedAvailablePeerIDs: []string{"2"}, 204 errRetainedPeerIDs: []string{"1", "3"}, 205 retainErr: peer.ErrInvalidPeerType{}, 206 peerListActions: []PeerListAction{ 207 StartAction{}, 208 UpdateAction{ 209 AddedPeerIDs: []string{"1", "2", "3"}, 210 ExpectedErr: multierr.Combine(peer.ErrInvalidPeerType{}, peer.ErrInvalidPeerType{}), 211 }, 212 }, 213 expectedAvailablePeers: []string{"2"}, 214 expectedRunning: true, 215 }, 216 { 217 msg: "start stop release error", 218 retainedAvailablePeerIDs: []string{"1"}, 219 errReleasedPeerIDs: []string{"1"}, 220 releaseErr: peer.ErrTransportHasNoReferenceToPeer{}, 221 peerListActions: []PeerListAction{ 222 StartAction{}, 223 UpdateAction{AddedPeerIDs: []string{"1"}}, 224 StopAction{ 225 ExpectedErr: peer.ErrTransportHasNoReferenceToPeer{}, 226 }, 227 }, 228 expectedRunning: false, 229 }, 230 { 231 msg: "assure stop is idempotent", 232 retainedAvailablePeerIDs: []string{"1"}, 233 errReleasedPeerIDs: []string{"1"}, 234 releaseErr: peer.ErrTransportHasNoReferenceToPeer{}, 235 peerListActions: []PeerListAction{ 236 StartAction{}, 237 UpdateAction{AddedPeerIDs: []string{"1"}}, 238 ConcurrentAction{ 239 Actions: []PeerListAction{ 240 StopAction{ 241 ExpectedErr: peer.ErrTransportHasNoReferenceToPeer{}, 242 }, 243 StopAction{ 244 ExpectedErr: peer.ErrTransportHasNoReferenceToPeer{}, 245 }, 246 StopAction{ 247 ExpectedErr: peer.ErrTransportHasNoReferenceToPeer{}, 248 }, 249 }, 250 }, 251 }, 252 expectedRunning: false, 253 }, 254 { 255 msg: "start stop release multiple errors", 256 retainedAvailablePeerIDs: []string{"1", "2", "3"}, 257 releasedPeerIDs: []string{"2"}, 258 errReleasedPeerIDs: []string{"1", "3"}, 259 releaseErr: peer.ErrTransportHasNoReferenceToPeer{}, 260 peerListActions: []PeerListAction{ 261 StartAction{}, 262 UpdateAction{AddedPeerIDs: []string{"1", "2", "3"}}, 263 StopAction{ 264 ExpectedErr: multierr.Combine( 265 peer.ErrTransportHasNoReferenceToPeer{}, 266 peer.ErrTransportHasNoReferenceToPeer{}, 267 ), 268 }, 269 }, 270 expectedRunning: false, 271 }, 272 { 273 msg: "choose before start", 274 peerListActions: []PeerListAction{ 275 ChooseAction{ 276 ExpectedErr: newNotRunningError("context finished while waiting for instance to start: context deadline exceeded"), 277 InputContextTimeout: 10 * time.Millisecond, 278 }, 279 ChooseAction{ 280 ExpectedErr: newNotRunningError("context finished while waiting for instance to start: context deadline exceeded"), 281 InputContextTimeout: 10 * time.Millisecond, 282 }, 283 }, 284 expectedRunning: false, 285 }, 286 { 287 msg: "update before start", 288 retainedAvailablePeerIDs: []string{"1"}, 289 expectedAvailablePeers: []string{"1"}, 290 peerListActions: []PeerListAction{ 291 ConcurrentAction{ 292 Actions: []PeerListAction{ 293 UpdateAction{AddedPeerIDs: []string{"1"}}, 294 StartAction{}, 295 }, 296 Wait: 20 * time.Millisecond, 297 }, 298 }, 299 expectedRunning: true, 300 }, 301 { 302 msg: "start choose no peers", 303 peerListActions: []PeerListAction{ 304 StartAction{}, 305 ChooseAction{ 306 InputContextTimeout: 20 * time.Millisecond, 307 ExpectedErrMsg: "peer list has no peers", 308 }, 309 }, 310 expectedRunning: true, 311 }, 312 { 313 msg: "start then add", 314 retainedAvailablePeerIDs: []string{"1", "2"}, 315 expectedAvailablePeers: []string{"1", "2"}, 316 peerListActions: []PeerListAction{ 317 StartAction{}, 318 UpdateAction{AddedPeerIDs: []string{"1"}}, 319 UpdateAction{AddedPeerIDs: []string{"2"}}, 320 ChooseAction{ExpectedPeer: "1"}, 321 ChooseAction{ExpectedPeer: "1"}, 322 ChooseAction{ExpectedPeer: "2"}, 323 }, 324 expectedRunning: true, 325 }, 326 { 327 msg: "start remove", 328 retainedAvailablePeerIDs: []string{"1", "2"}, 329 expectedAvailablePeers: []string{"2"}, 330 releasedPeerIDs: []string{"1"}, 331 peerListActions: []PeerListAction{ 332 StartAction{}, 333 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 334 UpdateAction{RemovedPeerIDs: []string{"1"}}, 335 ChooseAction{ExpectedPeer: "2"}, 336 }, 337 expectedRunning: true, 338 }, 339 { 340 msg: "add retain error", 341 retainedAvailablePeerIDs: []string{"1", "2"}, 342 expectedAvailablePeers: []string{"1", "2"}, 343 errRetainedPeerIDs: []string{"3"}, 344 retainErr: peer.ErrInvalidPeerType{}, 345 peerListActions: []PeerListAction{ 346 StartAction{}, 347 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 348 UpdateAction{ 349 AddedPeerIDs: []string{"3"}, 350 ExpectedErr: peer.ErrInvalidPeerType{}, 351 }, 352 }, 353 expectedRunning: true, 354 }, 355 { 356 msg: "add duplicate peer", 357 retainedAvailablePeerIDs: []string{"1", "2"}, 358 expectedAvailablePeers: []string{"1", "2"}, 359 peerListActions: []PeerListAction{ 360 StartAction{}, 361 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 362 UpdateAction{ 363 AddedPeerIDs: []string{"2"}, 364 ExpectedErr: peer.ErrPeerAddAlreadyInList("2"), 365 }, 366 ChooseAction{ExpectedPeer: "1"}, 367 ChooseAction{ExpectedPeer: "1"}, 368 ChooseAction{ExpectedPeer: "2"}, 369 }, 370 expectedRunning: true, 371 }, 372 { 373 msg: "remove peer not in list", 374 retainedAvailablePeerIDs: []string{"1", "2"}, 375 expectedAvailablePeers: []string{"1", "2"}, 376 peerListActions: []PeerListAction{ 377 StartAction{}, 378 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 379 UpdateAction{ 380 RemovedPeerIDs: []string{"3"}, 381 ExpectedErr: peer.ErrPeerRemoveNotInList("3"), 382 }, 383 ChooseAction{ExpectedPeer: "1"}, 384 ChooseAction{ExpectedPeer: "1"}, 385 ChooseAction{ExpectedPeer: "2"}, 386 }, 387 expectedRunning: true, 388 }, 389 { 390 msg: "remove release error", 391 retainedAvailablePeerIDs: []string{"1", "2"}, 392 errReleasedPeerIDs: []string{"2"}, 393 releaseErr: peer.ErrTransportHasNoReferenceToPeer{}, 394 expectedAvailablePeers: []string{"1"}, 395 peerListActions: []PeerListAction{ 396 StartAction{}, 397 UpdateAction{AddedPeerIDs: []string{"1", "2"}}, 398 UpdateAction{ 399 RemovedPeerIDs: []string{"2"}, 400 ExpectedErr: peer.ErrTransportHasNoReferenceToPeer{}, 401 }, 402 ChooseAction{ExpectedPeer: "1"}, 403 ChooseAction{ExpectedPeer: "1"}, 404 }, 405 expectedRunning: true, 406 }, 407 { 408 msg: "block but added too late", 409 retainedAvailablePeerIDs: []string{"1"}, 410 expectedAvailablePeers: []string{"1"}, 411 peerListActions: []PeerListAction{ 412 StartAction{}, 413 ConcurrentAction{ 414 Actions: []PeerListAction{ 415 ChooseAction{ 416 InputContextTimeout: 10 * time.Millisecond, 417 ExpectedErrMsg: "peer list has no peers", 418 }, 419 UpdateAction{AddedPeerIDs: []string{"1"}}, 420 }, 421 Wait: 20 * time.Millisecond, 422 }, 423 ChooseAction{ExpectedPeer: "1"}, 424 }, 425 expectedRunning: true, 426 }, 427 { 428 msg: "add unavailable peer", 429 retainedAvailablePeerIDs: []string{"1"}, 430 retainedUnavailablePeerIDs: []string{"2"}, 431 expectedAvailablePeers: []string{"1"}, 432 expectedUnavailablePeers: []string{"2"}, 433 peerListActions: []PeerListAction{ 434 StartAction{}, 435 UpdateAction{AddedPeerIDs: []string{"1"}}, 436 UpdateAction{AddedPeerIDs: []string{"2"}}, 437 ChooseAction{ 438 ExpectedPeer: "1", 439 InputContextTimeout: 20 * time.Millisecond, 440 }, 441 ChooseAction{ 442 ExpectedPeer: "1", 443 InputContextTimeout: 20 * time.Millisecond, 444 }, 445 }, 446 expectedRunning: true, 447 }, 448 { 449 msg: "remove unavailable peer", 450 retainedUnavailablePeerIDs: []string{"1"}, 451 releasedPeerIDs: []string{"1"}, 452 peerListActions: []PeerListAction{ 453 StartAction{}, 454 UpdateAction{AddedPeerIDs: []string{"1"}}, 455 UpdateAction{RemovedPeerIDs: []string{"1"}}, 456 ChooseAction{ 457 InputContextTimeout: 10 * time.Millisecond, 458 ExpectedErrMsg: "peer list has no peers", 459 }, 460 }, 461 expectedRunning: true, 462 }, 463 { 464 msg: "notify peer is now available", 465 retainedUnavailablePeerIDs: []string{"1"}, 466 expectedAvailablePeers: []string{"1"}, 467 peerListActions: []PeerListAction{ 468 StartAction{}, 469 UpdateAction{AddedPeerIDs: []string{"1"}}, 470 ChooseAction{ 471 InputContextTimeout: 10 * time.Millisecond, 472 ExpectedErrMsg: "has 1 peer but it is not responsive", 473 }, 474 NotifyStatusChangeAction{PeerID: "1", NewConnectionStatus: peer.Available}, 475 ChooseAction{ExpectedPeer: "1"}, 476 }, 477 expectedRunning: true, 478 }, 479 { 480 msg: "notify peer is still available", 481 retainedAvailablePeerIDs: []string{"1"}, 482 expectedAvailablePeers: []string{"1"}, 483 peerListActions: []PeerListAction{ 484 StartAction{}, 485 UpdateAction{AddedPeerIDs: []string{"1"}}, 486 ChooseAction{ExpectedPeer: "1"}, 487 NotifyStatusChangeAction{PeerID: "1", NewConnectionStatus: peer.Available}, 488 ChooseAction{ExpectedPeer: "1"}, 489 }, 490 expectedRunning: true, 491 }, 492 { 493 msg: "notify peer is now unavailable", 494 retainedAvailablePeerIDs: []string{"1"}, 495 expectedUnavailablePeers: []string{"1"}, 496 peerListActions: []PeerListAction{ 497 StartAction{}, 498 UpdateAction{AddedPeerIDs: []string{"1"}}, 499 ChooseAction{ExpectedPeer: "1"}, 500 NotifyStatusChangeAction{PeerID: "1", NewConnectionStatus: peer.Unavailable}, 501 ChooseAction{ 502 InputContextTimeout: 10 * time.Millisecond, 503 ExpectedErrMsg: "has 1 peer but it is not responsive", 504 }, 505 }, 506 expectedRunning: true, 507 }, 508 { 509 msg: "notify peer is still unavailable", 510 retainedUnavailablePeerIDs: []string{"1"}, 511 expectedUnavailablePeers: []string{"1"}, 512 peerListActions: []PeerListAction{ 513 StartAction{}, 514 UpdateAction{AddedPeerIDs: []string{"1"}}, 515 NotifyStatusChangeAction{PeerID: "1", NewConnectionStatus: peer.Unavailable}, 516 ChooseAction{ 517 InputContextTimeout: 10 * time.Millisecond, 518 ExpectedErrMsg: "has 1 peer but it is not responsive", 519 }, 520 }, 521 expectedRunning: true, 522 }, 523 { 524 msg: "notify invalid peer", 525 retainedAvailablePeerIDs: []string{"1"}, 526 releasedPeerIDs: []string{"1"}, 527 peerListActions: []PeerListAction{ 528 StartAction{}, 529 UpdateAction{AddedPeerIDs: []string{"1"}}, 530 UpdateAction{RemovedPeerIDs: []string{"1"}}, 531 NotifyStatusChangeAction{PeerID: "1", NewConnectionStatus: peer.Available}, 532 }, 533 expectedRunning: true, 534 }, 535 } 536 537 for _, tt := range tests { 538 t.Run(tt.msg, func(t *testing.T) { 539 mockCtrl := gomock.NewController(t) 540 defer mockCtrl.Finish() 541 542 transport := NewMockTransport(mockCtrl) 543 544 // Healthy Transport Retain/Release 545 peerMap := ExpectPeerRetains( 546 transport, 547 tt.retainedAvailablePeerIDs, 548 tt.retainedUnavailablePeerIDs, 549 ) 550 ExpectPeerReleases(transport, tt.releasedPeerIDs, nil) 551 552 // Unhealthy Transport Retain/Release 553 ExpectPeerRetainsWithError(transport, tt.errRetainedPeerIDs, tt.retainErr) 554 ExpectPeerReleases(transport, tt.errReleasedPeerIDs, tt.releaseErr) 555 556 logger := zaptest.NewLogger(t) 557 pl := New(transport, Seed(0), Logger(logger)) 558 559 deps := ListActionDeps{ 560 Peers: peerMap, 561 } 562 ApplyPeerListActions(t, pl, tt.peerListActions, deps) 563 564 var availablePeers []string 565 var unavailablePeers []string 566 for _, p := range pl.Peers() { 567 ps := p.Status() 568 if ps.ConnectionStatus == peer.Available { 569 availablePeers = append(availablePeers, p.Identifier()) 570 } else if ps.ConnectionStatus == peer.Unavailable { 571 unavailablePeers = append(unavailablePeers, p.Identifier()) 572 } 573 } 574 sort.Strings(availablePeers) 575 sort.Strings(unavailablePeers) 576 577 assert.Equal(t, tt.expectedAvailablePeers, availablePeers, "incorrect available peers") 578 assert.Equal(t, tt.expectedUnavailablePeers, unavailablePeers, "incorrect unavailable peers") 579 assert.Equal(t, tt.expectedRunning, pl.IsRunning(), "Peer list should match expected final running state") 580 }) 581 } 582 } 583 584 func TestFailFastConfig(t *testing.T) { 585 conn, err := net.Listen("tcp", "127.0.0.1:0") 586 require.NoError(t, err) 587 require.NoError(t, conn.Close()) 588 589 serviceName := "test" 590 config := whitespace.Expand(fmt.Sprintf(` 591 outbounds: 592 nowhere: 593 http: 594 random: 595 peers: 596 - %q 597 capacity: 10 598 failFast: true 599 `, conn.Addr())) 600 cfgr := yarpcconfig.New() 601 cfgr.MustRegisterTransport(http.TransportSpec()) 602 cfgr.MustRegisterPeerList(Spec()) 603 cfg, err := cfgr.LoadConfigFromYAML(serviceName, strings.NewReader(config)) 604 require.NoError(t, err) 605 606 d := yarpc.NewDispatcher(cfg) 607 d.Start() 608 defer d.Stop() 609 610 ctx, cancel := context.WithTimeout(context.Background(), testtime.Second) 611 defer cancel() 612 613 client := d.MustOutboundConfig("nowhere") 614 _, err = client.Outbounds.Unary.Call(ctx, &transport.Request{ 615 Service: "service", 616 Caller: "caller", 617 Encoding: transport.Encoding("blank"), 618 Procedure: "bogus", 619 Body: strings.NewReader("nada"), 620 }) 621 require.Error(t, err) 622 assert.Contains(t, err.Error(), "has 1 peer but it is not responsive") 623 } 624 625 func TestParallelChoose(t *testing.T) { 626 impl := NewImplementation() 627 628 for i := 0; i < 5000; i++ { 629 impl.Add(nil, nil) 630 } 631 632 assert.NotPanics(t, func() { 633 var wg sync.WaitGroup 634 for i := 0; i < 100; i++ { 635 wg.Add(1) 636 go func() { 637 for j := 0; j < 5000; j++ { 638 impl.Choose(nil) 639 } 640 wg.Done() 641 }() 642 } 643 wg.Wait() 644 }) 645 646 }