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  }