github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/component/coordinator_test.go (about)

     1  package component_test
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"testing"
     7  
     8  	"github.com/pf-qiu/concourse/v6/atc/component"
     9  	"github.com/pf-qiu/concourse/v6/atc/component/cmocks"
    10  	"github.com/pf-qiu/concourse/v6/atc/db/lock"
    11  	"github.com/pf-qiu/concourse/v6/atc/db/lock/lockfakes"
    12  	"github.com/stretchr/testify/mock"
    13  	"github.com/stretchr/testify/require"
    14  	"github.com/stretchr/testify/suite"
    15  )
    16  
    17  func TestCoordinator(t *testing.T) {
    18  	suite.Run(t, &CoordinatorSuite{
    19  		Assertions: require.New(t),
    20  	})
    21  }
    22  
    23  type CoordinatorSuite struct {
    24  	suite.Suite
    25  	*require.Assertions
    26  }
    27  
    28  type CoordinatorTest struct {
    29  	It string
    30  
    31  	LockAvailable bool
    32  	LockErr       error
    33  
    34  	Disappeared bool
    35  	ReloadErr   error
    36  
    37  	Paused          bool
    38  	IntervalElapsed bool
    39  
    40  	Runs   bool
    41  	RunErr error
    42  
    43  	UpdatesLastRan   bool
    44  	UpdateLastRanErr error
    45  }
    46  
    47  func (test CoordinatorTest) Run(s *CoordinatorSuite, action func(*component.Coordinator, context.Context)) {
    48  	fakeLocker := new(lockfakes.FakeLockFactory)
    49  	fakeComponent := new(cmocks.Component)
    50  	fakeRunnable := new(cmocks.Runnable)
    51  
    52  	var fakeLock *lockfakes.FakeLock
    53  	if test.LockAvailable {
    54  		fakeLock = new(lockfakes.FakeLock)
    55  		fakeLocker.AcquireReturns(fakeLock, true, nil)
    56  	} else {
    57  		fakeLocker.AcquireReturns(nil, false, test.LockErr)
    58  	}
    59  
    60  	componentName := "some-name"
    61  
    62  	fakeComponent.On("Name").Return(componentName)
    63  	fakeComponent.On("Paused").Return(test.Paused)
    64  	fakeComponent.On("IntervalElapsed").Return(test.IntervalElapsed)
    65  	fakeComponent.On("UpdateLastRan").Return(test.UpdateLastRanErr)
    66  
    67  	fakeComponent.On("Reload").Return(!test.Disappeared, test.ReloadErr).Run(func(mock.Arguments) {
    68  		// make sure we haven't asked for anything prior to reloading
    69  		fakeComponent.AssertNotCalled(s.T(), "Paused")
    70  		fakeComponent.AssertNotCalled(s.T(), "IntervalElapsed")
    71  	})
    72  
    73  	ctx := context.Background()
    74  
    75  	if test.Runs {
    76  		fakeRunnable.On("Run", ctx).Return(test.RunErr).Run(func(mock.Arguments) {
    77  			// make sure the lock is held while running
    78  			s.Equal(fakeLock.ReleaseCallCount(), 0, "lock was released too early")
    79  
    80  			// make sure we haven't updated this too early
    81  			fakeComponent.AssertNotCalled(s.T(), "UpdateLastRan")
    82  		})
    83  	}
    84  
    85  	coordinator := &component.Coordinator{
    86  		Locker:    fakeLocker,
    87  		Component: fakeComponent,
    88  		Runnable:  fakeRunnable,
    89  	}
    90  
    91  	action(coordinator, ctx)
    92  
    93  	if test.Runs {
    94  		fakeRunnable.AssertCalled(s.T(), "Run", ctx)
    95  	} else {
    96  		fakeRunnable.AssertNotCalled(s.T(), "Run")
    97  	}
    98  
    99  	if test.UpdatesLastRan {
   100  		fakeComponent.AssertCalled(s.T(), "UpdateLastRan")
   101  	} else {
   102  		fakeComponent.AssertNotCalled(s.T(), "UpdateLastRan")
   103  	}
   104  
   105  	// broadly assert that the lock is released as this should apply to any code
   106  	// branch that allowed the lock to be acquired
   107  	if test.LockAvailable {
   108  		_, acquiredLock := fakeLocker.AcquireArgsForCall(0)
   109  		s.Equal(lock.NewTaskLockID(componentName), acquiredLock, "acquired wrong lock")
   110  
   111  		s.Equal(1, fakeLock.ReleaseCallCount(), "lock was not released")
   112  	}
   113  }
   114  
   115  func (s *CoordinatorSuite) TestRunPeriodically() {
   116  	someErr := errors.New("oh noes")
   117  
   118  	for _, t := range []CoordinatorTest{
   119  		{
   120  			It: "runs if the lock is available and the interval elapsed",
   121  
   122  			LockAvailable:   true,
   123  			IntervalElapsed: true,
   124  
   125  			Runs:           true,
   126  			UpdatesLastRan: true,
   127  		},
   128  		{
   129  			It: "does not run if lock is unavailable",
   130  
   131  			LockAvailable:   false,
   132  			IntervalElapsed: true,
   133  
   134  			Runs: false,
   135  		},
   136  		{
   137  			It: "does not run if acquiring the lock errors",
   138  
   139  			LockErr:         someErr,
   140  			IntervalElapsed: true,
   141  
   142  			Runs: false,
   143  		},
   144  		{
   145  			It: "does not run if the component disappears while reloading",
   146  
   147  			LockAvailable:   true,
   148  			IntervalElapsed: true,
   149  			Disappeared:     true,
   150  
   151  			Runs: false,
   152  		},
   153  		{
   154  			It: "does not run if reloading the component errors",
   155  
   156  			LockAvailable: true,
   157  			ReloadErr:     someErr,
   158  
   159  			Runs: false,
   160  		},
   161  		{
   162  			It: "does not run if the lock is available but the interval has not elapsed",
   163  
   164  			LockAvailable:   true,
   165  			IntervalElapsed: false,
   166  
   167  			Runs: false,
   168  		},
   169  		{
   170  			It: "does not run if the component is paused",
   171  
   172  			LockAvailable:   true,
   173  			Paused:          true,
   174  			IntervalElapsed: true,
   175  
   176  			Runs: false,
   177  		},
   178  		{
   179  			It: "does not update last ran if running failed",
   180  
   181  			LockAvailable:   true,
   182  			IntervalElapsed: true,
   183  
   184  			Runs:           true,
   185  			RunErr:         someErr,
   186  			UpdatesLastRan: false,
   187  		},
   188  	} {
   189  		s.Run(t.It, func() {
   190  			t.Run(s, (*component.Coordinator).RunPeriodically)
   191  		})
   192  	}
   193  }
   194  
   195  func (s *CoordinatorSuite) TestRunImmediately() {
   196  	someErr := errors.New("oh noes")
   197  
   198  	for _, t := range []CoordinatorTest{
   199  		{
   200  			It: "runs if the lock is available and the interval elapsed",
   201  
   202  			LockAvailable:   true,
   203  			IntervalElapsed: true,
   204  
   205  			Runs:           true,
   206  			UpdatesLastRan: true,
   207  		},
   208  		{
   209  			It: "runs if the lock is available even if the interval has not elapsed",
   210  
   211  			LockAvailable:   true,
   212  			IntervalElapsed: false,
   213  
   214  			Runs:           true,
   215  			UpdatesLastRan: true,
   216  		},
   217  		{
   218  			It: "does not run if lock is unavailable",
   219  
   220  			LockAvailable: false,
   221  
   222  			Runs: false,
   223  		},
   224  		{
   225  			It: "does not run if acquiring the lock errors",
   226  
   227  			LockErr: someErr,
   228  
   229  			Runs: false,
   230  		},
   231  		{
   232  			It: "does not run if reloading the component errors",
   233  
   234  			LockAvailable: true,
   235  			ReloadErr:     someErr,
   236  
   237  			Runs: false,
   238  		},
   239  		{
   240  			It: "does not run if the component disappeared",
   241  
   242  			LockAvailable:   true,
   243  			Disappeared:     true,
   244  			IntervalElapsed: true,
   245  
   246  			Runs:           false,
   247  			UpdatesLastRan: false,
   248  		},
   249  		{
   250  			It: "does not run if the component is paused",
   251  
   252  			LockAvailable:   true,
   253  			Paused:          true,
   254  			IntervalElapsed: true,
   255  
   256  			Runs: false,
   257  		},
   258  	} {
   259  		s.Run(t.It, func() {
   260  			t.Run(s, (*component.Coordinator).RunImmediately)
   261  		})
   262  	}
   263  }