go.uber.org/yarpc@v1.72.1/pkg/lifecycle/once_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 "errors" 25 "fmt" 26 "testing" 27 "time" 28 29 "github.com/golang/mock/gomock" 30 "github.com/stretchr/testify/assert" 31 "go.uber.org/yarpc/internal/testtime" 32 ) 33 34 func TestLifecycleOnce(t *testing.T) { 35 type testStruct struct { 36 msg string 37 38 // A list of actions that will be applied on the LifecycleOnce 39 actions []LifecycleAction 40 41 // expected state at the end of the actions 42 expectedFinalState State 43 } 44 tests := []testStruct{ 45 { 46 msg: "setup", 47 expectedFinalState: Idle, 48 }, 49 { 50 msg: "Start", 51 actions: []LifecycleAction{ 52 StartAction{ExpectedState: Running}, 53 }, 54 expectedFinalState: Running, 55 }, 56 { 57 msg: "Start and Started", 58 actions: []LifecycleAction{ 59 ConcurrentAction{ 60 Actions: []LifecycleAction{ 61 StartAction{ExpectedState: Running}, 62 Actions{ 63 WaitForStartAction, 64 GetStateAction{ExpectedState: Running}, 65 }, 66 }, 67 }, 68 }, 69 expectedFinalState: Running, 70 }, 71 { 72 msg: "Stop", 73 actions: []LifecycleAction{ 74 StartAction{ExpectedState: Running}, 75 StopAction{ExpectedState: Stopped}, 76 }, 77 expectedFinalState: Stopped, 78 }, 79 { 80 msg: "Stop and Stopped", 81 actions: []LifecycleAction{ 82 ConcurrentAction{ 83 Actions: []LifecycleAction{ 84 StopAction{ExpectedState: Stopped}, 85 WaitForStoppingAction, 86 WaitForStopAction, 87 }, 88 }, 89 }, 90 expectedFinalState: Stopped, 91 }, 92 { 93 msg: "Error and Stopped", 94 actions: []LifecycleAction{ 95 ConcurrentAction{ 96 Actions: []LifecycleAction{ 97 StartAction{ 98 Err: fmt.Errorf("abort"), 99 ExpectedErr: fmt.Errorf("abort"), 100 ExpectedState: Errored, 101 }, 102 Actions{ 103 WaitForStoppingAction, 104 GetStateAction{ExpectedState: Errored}, 105 }, 106 Actions{ 107 WaitForStopAction, 108 GetStateAction{ExpectedState: Errored}, 109 }, 110 }, 111 }, 112 }, 113 expectedFinalState: Errored, 114 }, 115 { 116 msg: "Start, Stop, and Stopped", 117 actions: []LifecycleAction{ 118 ConcurrentAction{ 119 Actions: []LifecycleAction{ 120 Actions{ 121 StartAction{ExpectedState: Running}, 122 StopAction{ExpectedState: Stopped}, 123 }, 124 WaitForStoppingAction, 125 WaitForStopAction, 126 }, 127 }, 128 }, 129 expectedFinalState: Stopped, 130 }, 131 { 132 msg: "Starting", 133 actions: []LifecycleAction{ 134 ConcurrentAction{ 135 Actions: []LifecycleAction{ 136 StartAction{ExpectedState: Running, Wait: 20 * testtime.Millisecond}, 137 GetStateAction{ExpectedState: Starting}, 138 }, 139 Wait: 10 * testtime.Millisecond, 140 }, 141 }, 142 expectedFinalState: Running, 143 }, 144 { 145 msg: "Stopping", 146 actions: []LifecycleAction{ 147 StartAction{ExpectedState: Running}, 148 ConcurrentAction{ 149 Actions: []LifecycleAction{ 150 StopAction{ExpectedState: Stopped, Wait: 50 * testtime.Millisecond}, 151 GetStateAction{ExpectedState: Stopping}, 152 }, 153 Wait: 10 * testtime.Millisecond, 154 }, 155 }, 156 expectedFinalState: Stopped, 157 }, 158 { 159 msg: "Delayed stop, wait for stopping", 160 // The purpose of this test is to verify that the stopping state is 161 // occupied for the duration of the Stop() call. 162 // 163 // Timeline: 164 // - 0ms Idle 165 // - 0ms Starting 166 // - 0–20ms Running 167 // - 20–40ms Stopping 168 // - 40–ms Stopped 169 // 170 // First group: 171 // - 0ms Start (0ms) 172 // - 0ms Wait (20ms) 173 // - 20ms Stop (20ms) 174 // 175 // Second group: 176 // - 0ms Wait until Stopping 177 // - 20ms Resume 178 // - 20ms Wait (10ms) 179 // - 30ms Expect Stopping state 180 // - 30ms Wait (20ms) 181 // - 50ms Expect Stopped state 182 actions: []LifecycleAction{ 183 StartAction{ExpectedState: Running}, 184 ConcurrentAction{ 185 Actions: []LifecycleAction{ 186 Actions{ 187 StartAction{ExpectedState: Running}, 188 WaitAction(20 * testtime.Millisecond), 189 StopAction{ExpectedState: Stopped, Wait: 20 * testtime.Millisecond}, 190 }, 191 Actions{ 192 WaitForStoppingAction, 193 WaitAction(10 * testtime.Millisecond), 194 ExactStateAction{ExpectedState: Stopping}, 195 WaitAction(20 * testtime.Millisecond), 196 GetStateAction{ExpectedState: Stopped}, 197 }, 198 }, 199 }, 200 }, 201 expectedFinalState: Stopped, 202 }, 203 { 204 msg: "Start assure only called once and propagates the same error", 205 actions: []LifecycleAction{ 206 ConcurrentAction{ 207 Actions: []LifecycleAction{ 208 StartAction{ 209 Wait: 40 * testtime.Millisecond, 210 Err: errors.New("expected error"), 211 ExpectedState: Errored, 212 ExpectedErr: errors.New("expected error"), 213 }, 214 StartAction{ 215 Err: errors.New("not an expected error 1"), 216 ExpectedState: Errored, 217 ExpectedErr: errors.New("expected error"), 218 }, 219 StartAction{ 220 Wait: 40 * testtime.Millisecond, 221 Err: errors.New("not an expected error 2"), 222 ExpectedState: Errored, 223 ExpectedErr: errors.New("expected error"), 224 }, 225 }, 226 Wait: 10 * testtime.Millisecond, 227 }, 228 }, 229 expectedFinalState: Errored, 230 }, 231 { 232 msg: "Successful Start followed by failed Stop", 233 actions: []LifecycleAction{ 234 ConcurrentAction{ 235 Actions: []LifecycleAction{ 236 StartAction{ 237 Wait: 10 * testtime.Millisecond, 238 ExpectedState: Running, 239 }, 240 StopAction{ 241 Wait: 10 * testtime.Millisecond, 242 Err: errors.New("expected error"), 243 ExpectedState: Errored, 244 ExpectedErr: errors.New("expected error"), 245 }, 246 StartAction{ 247 Err: errors.New("not expected error 2"), 248 ExpectedState: Errored, 249 ExpectedErr: errors.New("expected error"), 250 }, 251 StopAction{ 252 Err: errors.New("not expected error 2"), 253 ExpectedState: Errored, 254 ExpectedErr: errors.New("expected error"), 255 }, 256 }, 257 Wait: 30 * testtime.Millisecond, 258 }, 259 }, 260 expectedFinalState: Errored, 261 }, 262 { 263 msg: "Stop assure only called once and returns the same error", 264 actions: []LifecycleAction{ 265 StartAction{ 266 ExpectedState: Running, 267 }, 268 ConcurrentAction{ 269 Actions: []LifecycleAction{ 270 StopAction{ 271 Wait: 40 * testtime.Millisecond, 272 Err: errors.New("expected error"), 273 ExpectedState: Errored, 274 ExpectedErr: errors.New("expected error"), 275 }, 276 StopAction{ 277 Wait: 40 * testtime.Millisecond, 278 Err: errors.New("not an expected error 1"), 279 ExpectedState: Errored, 280 ExpectedErr: errors.New("expected error"), 281 }, 282 StopAction{ 283 Wait: 40 * testtime.Millisecond, 284 Err: errors.New("not an expected error 2"), 285 ExpectedState: Errored, 286 ExpectedErr: errors.New("expected error"), 287 }, 288 }, 289 Wait: 10 * testtime.Millisecond, 290 }, 291 }, 292 expectedFinalState: Errored, 293 }, 294 { 295 msg: "Stop before start goes directly to 'stopped'", 296 actions: []LifecycleAction{ 297 StopAction{ 298 ExpectedState: Stopped, 299 }, 300 }, 301 expectedFinalState: Stopped, 302 }, 303 { 304 msg: "Pre-empting start after stop", 305 actions: []LifecycleAction{ 306 ConcurrentAction{ 307 Actions: []LifecycleAction{ 308 StopAction{ 309 Wait: 10 * testtime.Millisecond, 310 ExpectedState: Stopped, 311 }, 312 StartAction{ 313 Err: fmt.Errorf("start action should not run"), 314 Wait: 500 * time.Second, 315 ExpectedState: Stopped, 316 }, 317 }, 318 Wait: 20 * testtime.Millisecond, 319 }, 320 }, 321 expectedFinalState: Stopped, 322 }, 323 { 324 msg: "Overlapping stop after start", 325 // ms: timeline 326 // 00: 0: start..............starting 327 // 10: | 1. stop 328 // 50: X..|..................running 329 // |..................stopping 330 // 60: X..................stopped 331 actions: []LifecycleAction{ 332 ConcurrentAction{ 333 Actions: []LifecycleAction{ 334 StartAction{ 335 Wait: 50 * testtime.Millisecond, 336 ExpectedState: Running, 337 }, 338 StopAction{ 339 Wait: 10 * testtime.Millisecond, 340 ExpectedState: Stopped, 341 }, 342 }, 343 Wait: 10 * testtime.Millisecond, 344 }, 345 }, 346 expectedFinalState: Stopped, 347 }, 348 { 349 msg: "Overlapping stop after start error", 350 // ms: timeline 351 // 00: 0: start..............starting 352 // 10: | 1. stop............stopping 353 // 50: X X..................errored 354 actions: []LifecycleAction{ 355 ConcurrentAction{ 356 Actions: []LifecycleAction{ 357 StartAction{ 358 Wait: 50 * testtime.Millisecond, 359 Err: errors.New("expected error"), 360 ExpectedState: Errored, 361 ExpectedErr: errors.New("expected error"), 362 }, 363 StopAction{ 364 Wait: 10 * testtime.Millisecond, 365 ExpectedState: Errored, 366 ExpectedErr: errors.New("expected error"), 367 }, 368 }, 369 Wait: 10 * testtime.Millisecond, 370 }, 371 }, 372 expectedFinalState: Errored, 373 }, 374 { 375 msg: "Overlapping start after stop", 376 // ms: timeline 377 // 00: 0: start.............starting 378 // 10: | 379 // 20: | 1: stop 380 // 30: X -.................running 381 // 30+Δ: |.................stopping 382 // 40: | 2: start 383 // 40+Δ: | X 384 // 50: | 385 // 60: | 386 // 70: X.................stopped 387 actions: []LifecycleAction{ 388 ConcurrentAction{ 389 Actions: []LifecycleAction{ 390 StartAction{ 391 Wait: 30 * testtime.Millisecond, 392 ExpectedState: Running, 393 }, 394 StopAction{ 395 Wait: 30 * testtime.Millisecond, 396 ExpectedState: Stopped, 397 }, 398 StartAction{ 399 Err: fmt.Errorf("start action should not run"), 400 ExpectedState: Stopping, 401 }, 402 }, 403 Wait: 20 * testtime.Millisecond, 404 }, 405 }, 406 expectedFinalState: Stopped, 407 }, 408 { 409 msg: "Start completes before overlapping stop completes", 410 // ms: timeline 411 // 00: 0: start............starting 412 // 10: | 1: start 413 // 20: | - 2: stop 414 // 30: X - - 3: start...running 415 // 30+Δ: X | X..........stopping 416 // 40: | 4: stop 417 // | - 418 // | - 419 // 60: X X.......stopped 420 actions: []LifecycleAction{ 421 ConcurrentAction{ 422 Actions: []LifecycleAction{ 423 StartAction{ 424 Wait: 30 * testtime.Millisecond, 425 ExpectedState: Running, 426 }, 427 StartAction{ 428 Err: fmt.Errorf("start action should not run"), 429 ExpectedState: Running, 430 }, 431 StopAction{ 432 Wait: 40 * testtime.Millisecond, 433 ExpectedState: Stopped, 434 }, 435 StartAction{ 436 Err: fmt.Errorf("start action should not run"), 437 ExpectedState: Running, 438 }, 439 StopAction{ 440 Err: fmt.Errorf("stop action should not run"), 441 ExpectedState: Stopped, 442 }, 443 }, 444 Wait: 10 * testtime.Millisecond, 445 }, 446 }, 447 expectedFinalState: Stopped, 448 }, 449 } 450 451 for _, tt := range tests { 452 t.Run(tt.msg, func(t *testing.T) { 453 mockCtrl := gomock.NewController(t) 454 defer mockCtrl.Finish() 455 456 once := NewOnce() 457 ApplyLifecycleActions(t, once, tt.actions) 458 459 assert.Equal(t, tt.expectedFinalState, once.State()) 460 }) 461 } 462 } 463 464 // TestStopping verifies that a lifecycle object can spawn a goroutine and wait 465 // for that goroutine to exit in its stopping state. The goroutine must wrap 466 // up its work when it detects that the lifecycle has begun stopping. If it 467 // waited for the stopped channel, the stop callback would deadlock. 468 func TestStopping(t *testing.T) { 469 l := NewOnce() 470 l.Start(nil) 471 472 done := make(chan struct{}) 473 go func() { 474 select { 475 case <-l.Stopping(): 476 case <-time.After(time.Second): 477 assert.Fail(t, "deadlock") 478 } 479 close(done) 480 }() 481 482 l.Stop(func() error { 483 select { 484 case <-done: 485 case <-time.After(time.Second): 486 assert.Fail(t, "deadlock") 487 } 488 return nil 489 }) 490 } 491 492 func TestGetStateName(t *testing.T) { 493 assert.Equal(t, "idle", getStateName(Idle)) 494 assert.Equal(t, "unknown", getStateName(State(1000))) 495 }