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  }