github.com/blend/go-sdk@v1.20220411.3/cron/job_manager_test.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package cron 9 10 import ( 11 "context" 12 "fmt" 13 "io" 14 "sync" 15 "testing" 16 "time" 17 18 "github.com/blend/go-sdk/assert" 19 "github.com/blend/go-sdk/graceful" 20 "github.com/blend/go-sdk/logger" 21 "github.com/blend/go-sdk/uuid" 22 ) 23 24 // assert the job manager is graceful 25 var ( 26 _ graceful.Graceful = (*JobManager)(nil) 27 ) 28 29 func Test_JobManager_New(t *testing.T) { 30 its := assert.New(t) 31 32 jm := New( 33 OptLog(logger.None()), 34 ) 35 its.NotNil(jm.Latch) 36 its.NotNil(jm.Jobs) 37 its.NotNil(jm.Log) 38 } 39 40 func Test_JobManager_Start(t *testing.T) { 41 its := assert.New(t) 42 43 jm := New( 44 OptLog(logger.None()), 45 ) 46 defer jm.Stop() 47 48 // start blocks, use a channel and goroutine 49 errors := make(chan error, 1) 50 go func() { 51 if err := jm.Start(); err != nil { 52 errors <- err 53 } 54 }() 55 56 <-jm.Latch.NotifyStarted() 57 its.Empty(errors) 58 err := jm.Start() 59 its.NotNil(err) 60 } 61 62 func Test_JobManager_DisableJobs(t *testing.T) { 63 its := assert.New(t) 64 65 jm := New() 66 its.Nil(jm.LoadJobs(&runAtJob{RunAt: time.Now().UTC().Add(100 * time.Millisecond), RunDelegate: func(ctx context.Context) error { 67 return nil 68 }})) 69 its.Nil(jm.DisableJobs(runAtJobName)) 70 its.True(jm.IsJobDisabled(runAtJobName)) 71 } 72 73 func Test_JobManager_handleJobPanics(t *testing.T) { 74 its := assert.New(t) 75 76 manager := New() 77 waitGroup := sync.WaitGroup{} 78 waitGroup.Add(1) 79 80 action := func(ctx context.Context) error { 81 defer waitGroup.Done() 82 panic("this is only a test") 83 } 84 its.Nil(manager.LoadJobs(NewJob(OptJobName("panic-test"), OptJobAction(action)))) 85 _, _, err := manager.RunJob("panic-test") 86 its.Nil(err) 87 waitGroup.Wait() 88 its.True(true, "should complete") 89 } 90 91 func Test_JobManager_jobConfigProvider_disabled(t *testing.T) { 92 its := assert.New(t) 93 94 manager := New() 95 job := &testWithDisabled{ 96 disabled: false, 97 } 98 99 jobName := "testWithEnabled" 100 101 its.Nil(manager.LoadJobs(job)) 102 103 // test provider 104 its.False(manager.IsJobDisabled(jobName)) 105 job.disabled = true 106 its.True(manager.IsJobDisabled(jobName)) 107 108 // test explicit 109 its.Nil(manager.DisableJobs(jobName)) 110 its.True(manager.IsJobDisabled(jobName)) 111 its.Nil(manager.EnableJobs(jobName)) 112 its.False(manager.IsJobDisabled(jobName)) 113 } 114 115 func Test_JobManager_onError(t *testing.T) { 116 its := assert.New(t) 117 118 agent := logger.All(logger.OptOutput(io.Discard)) 119 defer agent.Close() 120 121 manager := New( 122 OptLog(agent), 123 ) 124 defer func() { _ = manager.Stop() }() 125 126 var errorDidFire bool 127 var errorMatched bool 128 wg := sync.WaitGroup{} 129 wg.Add(2) 130 131 agent.Listen(logger.Error, uuid.V4().String(), func(_ context.Context, e logger.Event) { 132 defer wg.Done() 133 errorDidFire = true 134 if typed, isTyped := e.(logger.ErrorEvent); isTyped { 135 if typed.Err != nil { 136 errorMatched = typed.Err.Error() == "this is only a test" 137 } 138 } 139 }) 140 job := NewJob( 141 OptJobName("error_test"), 142 OptJobAction(func(ctx context.Context) error { 143 defer wg.Done() 144 return fmt.Errorf("this is only a test") 145 }), 146 ) 147 its.Nil(manager.LoadJobs(job)) 148 _, done, err := manager.RunJob(job.Name()) 149 its.Nil(err) 150 wg.Wait() 151 152 its.True(errorDidFire) 153 its.True(errorMatched) 154 <-done 155 } 156 157 func Test_JobManager_Tracer(t *testing.T) { 158 its := assert.New(t) 159 160 wg := sync.WaitGroup{} 161 wg.Add(2) 162 var didCallStart, didCallFinish bool 163 var errorUnset bool 164 var foundJobName string 165 manager := New(OptTracer(&mockTracer{ 166 OnStart: func(ctx context.Context, jobName string) { 167 defer wg.Done() 168 didCallStart = true 169 foundJobName = jobName 170 }, 171 OnFinish: func(ctx context.Context, err error) { 172 defer wg.Done() 173 didCallFinish = true 174 errorUnset = err == nil 175 }, 176 })) 177 178 its.Nil(manager.LoadJobs(NewJob(OptJobName("tracer-test")))) 179 _, _, err := manager.RunJob("tracer-test") 180 its.Nil(err) 181 wg.Wait() 182 its.True(didCallStart) 183 its.True(didCallFinish) 184 its.True(errorUnset) 185 its.Equal("tracer-test", foundJobName) 186 } 187 188 func Test_JobManager_JobLifecycle(t *testing.T) { 189 its := assert.New(t) 190 191 jm := New() 192 its.Nil(jm.StartAsync()) 193 defer func() { _ = jm.Stop() }() 194 195 var shouldFail bool 196 j := newLifecycleTest(func(ctx context.Context) error { 197 defer func() { 198 shouldFail = !shouldFail 199 }() 200 if shouldFail { 201 return fmt.Errorf("only a test") 202 } 203 return nil 204 }) 205 its.Nil(jm.LoadJobs(j)) 206 207 successSignal := j.SuccessSignal 208 _, done, err := jm.RunJob(j.Name()) 209 its.Nil(err) 210 <-done 211 <-successSignal 212 213 brokenSignal := j.BrokenSignal 214 _, done, err = jm.RunJob(j.Name()) 215 its.Nil(err) 216 <-done 217 <-brokenSignal 218 219 fixedSignal := j.FixedSignal 220 _, done, err = jm.RunJob(j.Name()) 221 its.Nil(err) 222 <-done 223 <-fixedSignal 224 225 its.Equal(3, j.Starts) 226 its.Equal(3, j.Completes) 227 its.Equal(1, j.Failures) 228 its.Equal(2, j.Successes) 229 } 230 231 func Test_JobManager_Job(t *testing.T) { 232 its := assert.New(t) 233 234 jm := New() 235 j := newLifecycleTest(func(_ context.Context) error { 236 return nil 237 }) 238 its.Nil(jm.LoadJobs(j)) 239 240 meta, err := jm.Job(j.Name()) 241 its.Nil(err) 242 its.NotNil(meta) 243 244 meta, err = jm.Job(uuid.V4().String()) 245 its.NotNil(err) 246 its.Nil(meta) 247 } 248 249 func Test_JobManager_LoadJobs(t *testing.T) { 250 its := assert.New(t) 251 252 jm := New() 253 its.Nil(jm.LoadJobs(&loadJobTestMinimum{})) 254 255 its.True(jm.HasJob("load-job-test-minimum")) 256 257 jobScheduler, err := jm.Job("load-job-test-minimum") 258 its.Nil(err) 259 its.NotNil(jobScheduler) 260 261 its.Equal("load-job-test-minimum", jobScheduler.Name()) 262 its.NotNil(jobScheduler.Job) 263 264 its.Equal(DefaultDisabled, jobScheduler.Disabled()) 265 its.Zero(jobScheduler.Config().TimeoutOrDefault()) 266 267 its.Nil(jm.LoadJobs(&testJobWithTimeout{TimeoutDuration: time.Second})) 268 269 jobScheduler, err = jm.Job("testJobWithTimeout") 270 its.Nil(err) 271 its.NotNil(jobScheduler) 272 its.Equal(time.Second, jobScheduler.Config().TimeoutOrDefault()) 273 } 274 275 func Test_JobManager_IsRunning(t *testing.T) { 276 its := assert.New(t) 277 278 jm := New() 279 280 checked := make(chan struct{}) 281 proceed := make(chan struct{}) 282 its.Nil(jm.LoadJobs(NewJob(OptJobName("is-running-test"), OptJobAction(func(_ context.Context) error { 283 close(proceed) 284 <-checked 285 return nil 286 })))) // hadoooooken 287 288 _, _, err := jm.RunJob("is-running-test") 289 its.Nil(err) 290 <-proceed 291 its.True(jm.IsJobRunning("is-running-test")) 292 close(checked) 293 its.False(jm.IsJobRunning(uuid.V4().String())) 294 } 295 296 func Test_JobManager_CancelJob(t *testing.T) { 297 its := assert.New(t) 298 299 started := make(chan struct{}) 300 canceling := make(chan struct{}) 301 canceled := make(chan struct{}) 302 303 jm := New() 304 job := NewJob(OptJobName("is-running-test"), OptJobAction(func(ctx context.Context) error { 305 close(started) 306 <-canceling 307 time.Sleep(time.Millisecond) // this is a pad to make the test more reliable. 308 return nil 309 }), OptJobOnCancellation(func(_ context.Context) { 310 close(canceled) // but signal on the lifecycle event 311 })) 312 its.Nil(jm.LoadJobs(job)) 313 314 _, done, err := jm.RunJob(job.Name()) 315 its.Nil(err) 316 <-started 317 close(canceling) 318 its.Nil(jm.CancelJob(job.Name())) 319 <-canceled 320 its.False(jm.IsJobRunning(job.Name())) 321 <-done 322 } 323 324 func Test_JobManager_EnableDisableJob(t *testing.T) { 325 its := assert.New(t) 326 327 name := "enable-disable-test" 328 jm := New() 329 its.Nil(jm.LoadJobs(NewJob(OptJobName(name)))) 330 331 j, err := jm.Job(name) 332 its.Nil(err) 333 its.False(j.Disabled()) 334 335 its.Nil(jm.DisableJobs(name)) 336 j, err = jm.Job(name) 337 its.Nil(err) 338 its.True(j.Disabled()) 339 340 its.Nil(jm.EnableJobs(name)) 341 j, err = jm.Job(name) 342 its.Nil(err) 343 its.False(j.Disabled()) 344 } 345 346 func Test_JobManager_LoadJobs_lifecycle(t *testing.T) { 347 its := assert.New(t) 348 349 baseContext := context.WithValue(context.Background(), testContextKey{}, "load-jobs-lifecycle") 350 jm := New( 351 OptBaseContext(baseContext), 352 ) 353 354 onLoadContexts := make(chan context.Context, 1) 355 job := NewJob( 356 OptJobName("load-test"), 357 OptJobOnLoad(func(ctx context.Context) error { 358 onLoadContexts <- ctx 359 return nil 360 }), 361 ) 362 363 err := jm.LoadJobs(job) 364 its.Nil(err) 365 366 gotContext := <-onLoadContexts 367 its.Equal("load-jobs-lifecycle", gotContext.Value(testContextKey{})) 368 js := GetJobScheduler(gotContext) 369 its.NotNil(js) 370 its.Equal("load-test", js.Job.Name()) 371 } 372 373 func Test_JobManager_UnloadJobs_lifecycle(t *testing.T) { 374 its := assert.New(t) 375 376 baseContext := context.WithValue(context.Background(), testContextKey{}, "load-jobs-lifecycle") 377 jm := New( 378 OptBaseContext(baseContext), 379 ) 380 381 onUnloadContexts := make(chan context.Context, 1) 382 job0 := NewJob( 383 OptJobName("load-test"), 384 OptJobOnUnload(func(ctx context.Context) error { 385 onUnloadContexts <- ctx 386 return nil 387 }), 388 ) 389 job1 := NewJob( 390 OptJobName("load-test-1"), 391 OptJobOnUnload(func(ctx context.Context) error { 392 onUnloadContexts <- ctx 393 return nil 394 }), 395 ) 396 397 err := jm.LoadJobs(job0, job1) 398 its.Nil(err) 399 400 its.True(jm.HasJob("load-test")) 401 its.True(jm.HasJob("load-test-1")) 402 403 err = jm.UnloadJobs("load-test") 404 its.Nil(err) 405 406 its.False(jm.HasJob("load-test")) 407 its.True(jm.HasJob("load-test-1")) 408 409 gotContext := <-onUnloadContexts 410 its.Equal("load-jobs-lifecycle", gotContext.Value(testContextKey{})) 411 js := GetJobScheduler(gotContext) 412 its.NotNil(js) 413 its.Equal("load-test", js.Job.Name()) 414 415 err = jm.UnloadJobs(uuid.V4().String()) 416 its.NotNil(err) 417 } 418 419 func Test_JobManager_Background(t *testing.T) { 420 its := assert.New(t) 421 422 jm := New() 423 its.Equal(jm.Background(), context.Background()) 424 425 type contextKey struct{} 426 jm = New( 427 OptBaseContext(context.WithValue(context.Background(), contextKey{}, "test-value")), 428 ) 429 430 ctx := jm.Background() 431 its.Equal("test-value", ctx.Value(contextKey{})) 432 }