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 }