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 }