go.uber.org/yarpc@v1.72.1/pkg/lifecycle/action_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 lifecycle
    22  
    23  import (
    24  	"fmt"
    25  	"sync"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/stretchr/testify/assert"
    30  	"go.uber.org/atomic"
    31  	"go.uber.org/yarpc/internal/testtime"
    32  )
    33  
    34  type wrappedOnce struct {
    35  	*Once
    36  	running *atomic.Bool
    37  }
    38  
    39  // LifecycleAction defines actions that can be applied to a Lifecycle
    40  type LifecycleAction interface {
    41  	// Apply runs a function on the PeerList and asserts the result
    42  	Apply(*testing.T, wrappedOnce)
    43  }
    44  
    45  // StartAction is an action for testing Once.Start
    46  type StartAction struct {
    47  	Wait          time.Duration
    48  	Err           error
    49  	ExpectedErr   error
    50  	ExpectedState State
    51  }
    52  
    53  // Apply runs "Start" on the Once and validates the error
    54  func (a StartAction) Apply(t *testing.T, l wrappedOnce) {
    55  	err := l.Start(func() error {
    56  		assert.False(t, l.running.Swap(true), "expected no other running action")
    57  		if a.Wait > 0 {
    58  			testtime.Sleep(a.Wait)
    59  		}
    60  		assert.True(t, l.running.Swap(false), "expected no other running action")
    61  		return a.Err
    62  	})
    63  	assert.Equal(t, a.ExpectedErr, err)
    64  	state := l.State()
    65  	assert.True(t, a.ExpectedState <= state, "expected %v (or more advanced), got %v after start", a.ExpectedState, state)
    66  }
    67  
    68  // StopAction is an action for testing Once.Stop
    69  type StopAction struct {
    70  	Wait          time.Duration
    71  	Err           error
    72  	ExpectedErr   error
    73  	ExpectedState State
    74  }
    75  
    76  // Apply runs "Stop" on the Once and validates the error
    77  func (a StopAction) Apply(t *testing.T, l wrappedOnce) {
    78  	err := l.Stop(func() error {
    79  		assert.False(t, l.running.Swap(true), "expected no other running action")
    80  		if a.Wait > 0 {
    81  			testtime.Sleep(a.Wait)
    82  		}
    83  		assert.True(t, l.running.Swap(false), "expected no other running action")
    84  		return a.Err
    85  	})
    86  
    87  	assert.Equal(t, a.ExpectedErr, err)
    88  	assert.Equal(t, a.ExpectedState, l.State())
    89  }
    90  
    91  // WaitForStartAction is a singleton that will block until the lifecycle
    92  // reports that it has started.
    93  var WaitForStartAction waitForStartAction
    94  
    95  type waitForStartAction struct{}
    96  
    97  // Apply blocks until the lifecycle starts.
    98  func (a waitForStartAction) Apply(t *testing.T, l wrappedOnce) {
    99  	<-l.Started()
   100  	assert.True(t, l.State() >= Running, "expected lifecycle to be started")
   101  }
   102  
   103  // WaitForStoppingAction is a singleton that will block until the lifecycle
   104  // reports that it has begun stopping.
   105  var WaitForStoppingAction waitForStoppingAction
   106  
   107  type waitForStoppingAction struct{}
   108  
   109  // Apply blocks until the lifecycle stops or errs out.
   110  func (a waitForStoppingAction) Apply(t *testing.T, l wrappedOnce) {
   111  	<-l.Stopping()
   112  	assert.True(t, l.State() >= Stopping, "expected lifecycle to be stopping")
   113  }
   114  
   115  // WaitForStopAction is a singleton that will block until the lifecycle
   116  // reports that it has started.
   117  var WaitForStopAction waitForStopAction
   118  
   119  type waitForStopAction struct{}
   120  
   121  // Apply blocks until the lifecycle stops or errs out.
   122  func (a waitForStopAction) Apply(t *testing.T, l wrappedOnce) {
   123  	<-l.Stopped()
   124  	assert.True(t, l.State() >= Stopped, "expected lifecycle to be started")
   125  }
   126  
   127  // GetStateAction is an action for checking the Once's state.
   128  // Since a goroutine may be delayed, the action only ensures that the lifecycle
   129  // has at least reached the given state.
   130  type GetStateAction struct {
   131  	ExpectedState State
   132  }
   133  
   134  // Apply Checks the state on the Once
   135  func (a GetStateAction) Apply(t *testing.T, l wrappedOnce) {
   136  	assert.True(t, a.ExpectedState <= l.State())
   137  }
   138  
   139  // ExactStateAction is an action for checking the Once's exact state.
   140  type ExactStateAction struct {
   141  	ExpectedState State
   142  }
   143  
   144  // Apply Checks the state on the Once
   145  func (a ExactStateAction) Apply(t *testing.T, l wrappedOnce) {
   146  	assert.True(t, a.ExpectedState == l.State())
   147  }
   148  
   149  // Actions executes a plan of actions in order sequentially.
   150  type Actions []LifecycleAction
   151  
   152  // Apply runs all of the ConcurrentAction's actions sequentially.
   153  func (a Actions) Apply(t *testing.T, l wrappedOnce) {
   154  	for _, action := range a {
   155  		action.Apply(t, l)
   156  	}
   157  }
   158  
   159  // ConcurrentAction executes a plan of actions, with a given interval between
   160  // applying actions, but allowing every action to run concurrently in a
   161  // goroutine until its independent completion time.
   162  // The ConcurrentAction allows us to observe overlapping actions.
   163  type ConcurrentAction struct {
   164  	Actions []LifecycleAction
   165  	Wait    time.Duration
   166  }
   167  
   168  // Apply runs all the ConcurrentAction's actions in goroutines with a delay of `Wait`
   169  // between each action. Returns when all actions have finished executing
   170  func (a ConcurrentAction) Apply(t *testing.T, l wrappedOnce) {
   171  	var wg sync.WaitGroup
   172  
   173  	wg.Add(len(a.Actions))
   174  	for _, action := range a.Actions {
   175  		go func(ac LifecycleAction) {
   176  			defer wg.Done()
   177  			ac.Apply(t, l)
   178  		}(action)
   179  
   180  		if a.Wait > 0 {
   181  			testtime.Sleep(a.Wait)
   182  		}
   183  	}
   184  
   185  	wg.Wait()
   186  }
   187  
   188  // WaitAction is a plan to sleep for a duration.
   189  type WaitAction time.Duration
   190  
   191  // Apply waits the specified duration.
   192  func (a WaitAction) Apply(t *testing.T, l wrappedOnce) {
   193  	testtime.Sleep(time.Duration(a))
   194  }
   195  
   196  // ApplyLifecycleActions runs all the LifecycleActions on the Once
   197  func ApplyLifecycleActions(t *testing.T, l *Once, actions []LifecycleAction) {
   198  	wrapLife := wrappedOnce{
   199  		Once:    l,
   200  		running: atomic.NewBool(false),
   201  	}
   202  
   203  	for i, action := range actions {
   204  		t.Run(fmt.Sprintf("action #%d: %T", i, action), func(t *testing.T) {
   205  			action.Apply(t, wrapLife)
   206  		})
   207  	}
   208  }