go.uber.org/yarpc@v1.72.1/api/peer/peertest/peerlistaction.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 peertest
    22  
    23  import (
    24  	"context"
    25  	"fmt"
    26  	"sync"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/stretchr/testify/assert"
    31  	"go.uber.org/yarpc/api/peer"
    32  	"go.uber.org/yarpc/api/transport"
    33  	"go.uber.org/yarpc/internal/testtime"
    34  )
    35  
    36  // ListActionDeps are passed through PeerListActions' Apply methods in order
    37  // to allow the PeerListAction to modify state other than just the PeerList
    38  type ListActionDeps struct {
    39  	Peers map[string]*LightMockPeer
    40  }
    41  
    42  // PeerListAction defines actions that can be applied to a PeerList
    43  type PeerListAction interface {
    44  	// Apply runs a function on the PeerList and asserts the result
    45  	Apply(*testing.T, peer.Chooser, ListActionDeps)
    46  }
    47  
    48  // StartAction is an action for testing PeerList.Start
    49  type StartAction struct {
    50  	ExpectedErr error
    51  }
    52  
    53  // Apply runs "Start" on the peerList and validates the error
    54  func (a StartAction) Apply(t *testing.T, pl peer.Chooser, deps ListActionDeps) {
    55  	err := pl.Start()
    56  	assert.Equal(t, a.ExpectedErr, err)
    57  }
    58  
    59  // StopAction is an action for testing PeerList.Stop
    60  type StopAction struct {
    61  	ExpectedErr error
    62  }
    63  
    64  // Apply runs "Stop" on the peerList and validates the error
    65  func (a StopAction) Apply(t *testing.T, pl peer.Chooser, deps ListActionDeps) {
    66  	err := pl.Stop()
    67  	assert.Equal(t, a.ExpectedErr, err, "Stop action expected error %v, got %v", a.ExpectedErr, err)
    68  }
    69  
    70  // ChooseMultiAction will run Choose multiple times on the PeerList
    71  // It will assert if there are ANY failures
    72  type ChooseMultiAction struct {
    73  	ExpectedPeers []string
    74  }
    75  
    76  // Apply runs "Choose" on the peerList for every ExpectedPeer
    77  func (a ChooseMultiAction) Apply(t *testing.T, pl peer.Chooser, deps ListActionDeps) {
    78  	for _, expectedPeer := range a.ExpectedPeers {
    79  		action := ChooseAction{
    80  			ExpectedPeer:        expectedPeer,
    81  			InputContextTimeout: 50 * testtime.Millisecond,
    82  		}
    83  		action.Apply(t, pl, deps)
    84  	}
    85  }
    86  
    87  // ChooseAction is an action for choosing a peer from the peerlist
    88  type ChooseAction struct {
    89  	InputContext        context.Context
    90  	InputContextTimeout time.Duration
    91  	InputRequest        *transport.Request
    92  	ExpectedPeer        string
    93  	ExpectedErr         error
    94  	ExpectedErrMsg      string
    95  }
    96  
    97  // Apply runs "Choose" on the peerList and validates the peer && error
    98  func (a ChooseAction) Apply(t *testing.T, pl peer.Chooser, deps ListActionDeps) {
    99  	ctx := a.InputContext
   100  	custom := ctx != nil
   101  	if ctx == nil {
   102  		ctx = context.Background()
   103  	}
   104  	if !custom || a.InputContextTimeout > 0 {
   105  		timeout := a.InputContextTimeout
   106  		if timeout == 0 {
   107  			timeout = testtime.Second
   108  		}
   109  
   110  		var cancel context.CancelFunc
   111  		ctx, cancel = context.WithTimeout(ctx, timeout)
   112  		defer cancel()
   113  	}
   114  
   115  	p, finish, err := pl.Choose(ctx, a.InputRequest)
   116  	if err == nil {
   117  		finish(nil)
   118  	}
   119  
   120  	if a.ExpectedErrMsg != "" {
   121  		assert.Nil(t, p)
   122  		assert.Contains(t, err.Error(), a.ExpectedErrMsg)
   123  		return
   124  	}
   125  
   126  	if a.ExpectedErr != nil {
   127  		// Note that we're not verifying anything about ExpectedPeer here because
   128  		// it being non-empty means that the test itself was invalid. If anything,
   129  		// that should cause a panic, not a test failure. But that validation can
   130  		// be done before you start asserting expectations.
   131  		assert.Nil(t, p)
   132  		assert.Equal(t, a.ExpectedErr, err)
   133  		return
   134  	}
   135  
   136  	if assert.NoError(t, err) && assert.NotNil(t, p) {
   137  		assert.Equal(t, a.ExpectedPeer, p.Identifier())
   138  	}
   139  }
   140  
   141  // UpdateAction is an action for adding/removing multiple peers on the PeerList
   142  type UpdateAction struct {
   143  	AddedPeerIDs   []string
   144  	RemovedPeerIDs []string
   145  	ExpectedErr    error
   146  }
   147  
   148  // Apply runs "Update" on the peer.Chooser after casting it to a peer.List
   149  // and validates the error
   150  func (a UpdateAction) Apply(t *testing.T, pl peer.Chooser, deps ListActionDeps) {
   151  	list := pl.(peer.List)
   152  
   153  	added := make([]peer.Identifier, 0, len(a.AddedPeerIDs))
   154  	for _, peerID := range a.AddedPeerIDs {
   155  		added = append(added, MockPeerIdentifier(peerID))
   156  	}
   157  
   158  	removed := make([]peer.Identifier, 0, len(a.RemovedPeerIDs))
   159  	for _, peerID := range a.RemovedPeerIDs {
   160  		removed = append(removed, MockPeerIdentifier(peerID))
   161  	}
   162  
   163  	err := list.Update(
   164  		peer.ListUpdates{
   165  			Additions: added,
   166  			Removals:  removed,
   167  		},
   168  	)
   169  	assert.Equal(t, a.ExpectedErr, err)
   170  }
   171  
   172  // ConcurrentAction will run a series of actions in parallel
   173  type ConcurrentAction struct {
   174  	Actions []PeerListAction
   175  	Wait    time.Duration
   176  }
   177  
   178  // Apply runs all the ConcurrentAction's actions in goroutines with a delay of `Wait`
   179  // between each action. Returns when all actions have finished executing
   180  func (a ConcurrentAction) Apply(t *testing.T, pl peer.Chooser, deps ListActionDeps) {
   181  	var wg sync.WaitGroup
   182  
   183  	wg.Add(len(a.Actions))
   184  	for _, action := range a.Actions {
   185  		go func(ac PeerListAction) {
   186  			defer wg.Done()
   187  			ac.Apply(t, pl, deps)
   188  		}(action)
   189  
   190  		if a.Wait > 0 {
   191  			testtime.Sleep(a.Wait)
   192  		}
   193  	}
   194  
   195  	wg.Wait()
   196  }
   197  
   198  // NotifyStatusChangeAction will run the NotifyStatusChange function on a PeerList
   199  // with a specified Peer after changing the peer's ConnectionStatus
   200  type NotifyStatusChangeAction struct {
   201  	// PeerID is a unique identifier to the Peer we want use in the notification
   202  	PeerID string
   203  
   204  	// NewConnectionStatus is the new ConnectionStatus of the Peer
   205  	NewConnectionStatus peer.ConnectionStatus
   206  
   207  	// Unretained indicates that this notify occurs to a peer that has never been
   208  	// retained on the transport (to test edge cases).
   209  	Unretained bool
   210  }
   211  
   212  // Apply will run the NotifyStatusChanged function on the PeerList with the provided Peer
   213  func (a NotifyStatusChangeAction) Apply(t *testing.T, pl peer.Chooser, deps ListActionDeps) {
   214  	plSub := pl.(peer.Subscriber)
   215  
   216  	if a.Unretained {
   217  		plSub.NotifyStatusChanged(MockPeerIdentifier(a.PeerID))
   218  		return
   219  	}
   220  
   221  	deps.Peers[a.PeerID].PeerStatus.ConnectionStatus = a.NewConnectionStatus
   222  
   223  	plSub.NotifyStatusChanged(deps.Peers[a.PeerID])
   224  }
   225  
   226  // ApplyPeerListActions runs all the PeerListActions on the PeerList
   227  func ApplyPeerListActions(t *testing.T, pl peer.Chooser, actions []PeerListAction, deps ListActionDeps) {
   228  	for i, action := range actions {
   229  		t.Run(fmt.Sprintf("action #%d: %T", i, action), func(t *testing.T) {
   230  			action.Apply(t, pl, deps)
   231  		})
   232  	}
   233  }