github.com/isyscore/isc-gobase@v1.5.3-0.20231218061332-cbc7451899e9/cron/test/cront_test.go (about) 1 package test 2 3 import ( 4 "fmt" 5 cron2 "github.com/isyscore/isc-gobase/cron" 6 "sync" 7 "testing" 8 "time" 9 ) 10 11 // Many tests schedule a job for every second, and then wait at most a second 12 // for it to run. This amount is just slightly larger than 1 second to 13 // compensate for a few milliseconds of runtime. 14 const OneSecond = 1*time.Second + 10*time.Millisecond 15 16 func TestFuncPanicRecovery(t *testing.T) { 17 cron := cron2.New() 18 cron.Start() 19 defer cron.Stop() 20 _ = cron.AddFunc("* * * * * ?", func() { panic("YOLO") }) 21 22 select { 23 case <-time.After(OneSecond): 24 return 25 } 26 } 27 28 type DummyJob struct{} 29 30 func (d DummyJob) Run() { 31 panic("YOLO") 32 } 33 34 func TestJobPanicRecovery(t *testing.T) { 35 var job DummyJob 36 37 cron := cron2.New() 38 cron.Start() 39 defer cron.Stop() 40 _ = cron.AddJob("* * * * * ?", job) 41 42 select { 43 case <-time.After(OneSecond): 44 return 45 } 46 } 47 48 // Start and stop cron with no entries. 49 func TestNoEntries(t *testing.T) { 50 cron := cron2.New() 51 cron.Start() 52 53 select { 54 case <-time.After(OneSecond): 55 t.Fatal("expected cron will be stopped immediately") 56 case <-stop(cron): 57 } 58 } 59 60 // Start, stop, then add an entry. Verify entry doesn't run. 61 func TestStopCausesJobsToNotRun(t *testing.T) { 62 wg := &sync.WaitGroup{} 63 wg.Add(1) 64 65 cron := cron2.New() 66 cron.Start() 67 cron.Stop() 68 _ = cron.AddFunc("* * * * * ?", func() { wg.Done() }) 69 70 select { 71 case <-time.After(OneSecond): 72 // No job ran! 73 case <-wait(wg): 74 t.Fatal("expected stopped cron does not run any job") 75 } 76 } 77 78 // Add a job, start cron, expect it runs. 79 func TestAddBeforeRunning(t *testing.T) { 80 wg := &sync.WaitGroup{} 81 wg.Add(1) 82 83 cron := cron2.New() 84 _ = cron.AddFunc("* * * * * ?", func() { wg.Done() }) 85 cron.Start() 86 defer cron.Stop() 87 88 // Give cron 2 seconds to run our job (which is always activated). 89 select { 90 case <-time.After(OneSecond): 91 t.Fatal("expected job runs") 92 case <-wait(wg): 93 } 94 } 95 96 // Start cron, add a job, expect it runs. 97 func TestAddWhileRunning(t *testing.T) { 98 wg := &sync.WaitGroup{} 99 wg.Add(1) 100 101 cron := cron2.New() 102 cron.Start() 103 defer cron.Stop() 104 _ = cron.AddFunc("* * * * * ?", func() { wg.Done() }) 105 106 select { 107 case <-time.After(OneSecond): 108 t.Fatal("expected job runs") 109 case <-wait(wg): 110 } 111 } 112 113 // Test for #34. Adding a job after calling start results in multiple job invocations 114 func TestAddWhileRunningWithDelay(t *testing.T) { 115 cron := cron2.New() 116 cron.Start() 117 defer cron.Stop() 118 time.Sleep(5 * time.Second) 119 var calls = 0 120 _ = cron.AddFunc("* * * * * *", func() { calls += 1 }) 121 122 <-time.After(OneSecond) 123 if calls != 1 { 124 t.Errorf("called %d times, expected 1\n", calls) 125 } 126 } 127 128 // Test timing with Entries. 129 func TestSnapshotEntries(t *testing.T) { 130 wg := &sync.WaitGroup{} 131 wg.Add(1) 132 133 cron := cron2.New() 134 _ = cron.AddFunc("@every 2s", func() { wg.Done() }) 135 cron.Start() 136 defer cron.Stop() 137 138 // Cron should fire in 2 seconds. After 1 second, call Entries. 139 select { 140 case <-time.After(OneSecond): 141 cron.Entries() 142 } 143 144 // Even though Entries was called, the cron should fire at the 2 second mark. 145 select { 146 case <-time.After(OneSecond): 147 t.Error("expected job runs at 2 second mark") 148 case <-wait(wg): 149 } 150 151 } 152 153 // Test that the entries are correctly sorted. 154 // Add a bunch of long-in-the-future entries, and an immediate entry, and ensure 155 // that the immediate entry runs immediately. 156 // Also: Test that multiple jobs run in the same instant. 157 func TestMultipleEntries(t *testing.T) { 158 wg := &sync.WaitGroup{} 159 wg.Add(2) 160 161 cron := cron2.New() 162 _ = cron.AddFunc("0 0 0 1 1 ?", func() {}) 163 _ = cron.AddFunc("* * * * * ?", func() { wg.Done() }) 164 _ = cron.AddFunc("0 0 0 31 12 ?", func() {}) 165 _ = cron.AddFunc("* * * * * ?", func() { wg.Done() }) 166 167 cron.Start() 168 defer cron.Stop() 169 170 select { 171 case <-time.After(OneSecond): 172 t.Error("expected job run in proper order") 173 case <-wait(wg): 174 } 175 } 176 177 // Test running the same job twice. 178 func TestRunningJobTwice(t *testing.T) { 179 wg := &sync.WaitGroup{} 180 wg.Add(2) 181 182 cron := cron2.New() 183 _ = cron.AddFunc("0 0 0 1 1 ?", func() {}) 184 _ = cron.AddFunc("0 0 0 31 12 ?", func() {}) 185 _ = cron.AddFunc("* * * * * ?", func() { wg.Done() }) 186 187 cron.Start() 188 defer cron.Stop() 189 190 select { 191 case <-time.After(2 * OneSecond): 192 t.Error("expected job fires 2 times") 193 case <-wait(wg): 194 } 195 } 196 197 func TestRunningMultipleSchedules(t *testing.T) { 198 wg := &sync.WaitGroup{} 199 wg.Add(2) 200 201 cron := cron2.New() 202 _ = cron.AddFunc("0 0 0 1 1 ?", func() {}) 203 _ = cron.AddFunc("0 0 0 31 12 ?", func() {}) 204 _ = cron.AddFunc("* * * * * ?", func() { wg.Done() }) 205 cron.Schedule(cron2.Every(time.Minute), cron2.FuncJob(func() {})) 206 cron.Schedule(cron2.Every(time.Second), cron2.FuncJob(func() { wg.Done() })) 207 cron.Schedule(cron2.Every(time.Hour), cron2.FuncJob(func() {})) 208 209 cron.Start() 210 defer cron.Stop() 211 212 select { 213 case <-time.After(2 * OneSecond): 214 t.Error("expected job fires 2 times") 215 case <-wait(wg): 216 } 217 } 218 219 // Test that the cron is run in the local time zone (as opposed to UTC). 220 func TestLocalTimezone(t *testing.T) { 221 wg := &sync.WaitGroup{} 222 wg.Add(2) 223 224 now := time.Now() 225 spec := fmt.Sprintf("%d,%d %d %d %d %d ?", 226 now.Second()+1, now.Second()+2, now.Minute(), now.Hour(), now.Day(), now.Month()) 227 228 cron := cron2.New() 229 _ = cron.AddFunc(spec, func() { wg.Done() }) 230 cron.Start() 231 defer cron.Stop() 232 233 select { 234 case <-time.After(OneSecond * 2): 235 t.Error("expected job fires 2 times") 236 case <-wait(wg): 237 } 238 } 239 240 // Test that the cron is run in the given time zone (as opposed to local). 241 func TestNonLocalTimezone(t *testing.T) { 242 wg := &sync.WaitGroup{} 243 wg.Add(2) 244 245 loc, err := time.LoadLocation("Atlantic/Cape_Verde") 246 if err != nil { 247 fmt.Printf("Failed to load time zone Atlantic/Cape_Verde: %+v", err) 248 t.Fail() 249 } 250 251 now := time.Now().In(loc) 252 spec := fmt.Sprintf("%d,%d %d %d %d %d ?", 253 now.Second()+1, now.Second()+2, now.Minute(), now.Hour(), now.Day(), now.Month()) 254 255 cron := cron2.NewWithLocation(loc) 256 _ = cron.AddFunc(spec, func() { wg.Done() }) 257 cron.Start() 258 defer cron.Stop() 259 260 select { 261 case <-time.After(OneSecond * 2): 262 t.Error("expected job fires 2 times") 263 case <-wait(wg): 264 } 265 } 266 267 // Test that calling stop before start silently returns without 268 // blocking the stop channel. 269 func TestStopWithoutStart(t *testing.T) { 270 cron := cron2.New() 271 cron.Stop() 272 } 273 274 type testJob struct { 275 wg *sync.WaitGroup 276 name string 277 } 278 279 func (t testJob) Run() { 280 t.wg.Done() 281 } 282 283 // Test that adding an invalid job spec returns an error 284 func TestInvalidJobSpec(t *testing.T) { 285 cron := cron2.New() 286 err := cron.AddJob("this will not parse", nil) 287 if err == nil { 288 t.Errorf("expected an error with invalid spec, got nil") 289 } 290 } 291 292 // Test blocking run method behaves as Start() 293 func TestBlockingRun(t *testing.T) { 294 wg := &sync.WaitGroup{} 295 wg.Add(1) 296 297 cron := cron2.New() 298 _ = cron.AddFunc("* * * * * ?", func() { wg.Done() }) 299 300 var unblockChan = make(chan struct{}) 301 302 go func() { 303 cron.Run() 304 close(unblockChan) 305 }() 306 defer cron.Stop() 307 308 select { 309 case <-time.After(OneSecond): 310 t.Error("expected job fires") 311 case <-unblockChan: 312 t.Error("expected that Run() blocks") 313 case <-wait(wg): 314 } 315 } 316 317 // Test that double-running is a no-op 318 func TestStartNoop(t *testing.T) { 319 var tickChan = make(chan struct{}, 2) 320 321 cron := cron2.New() 322 _ = cron.AddFunc("* * * * * ?", func() { 323 tickChan <- struct{}{} 324 }) 325 326 cron.Start() 327 defer cron.Stop() 328 329 // Wait for the first firing to ensure the runner is going 330 <-tickChan 331 332 cron.Start() 333 334 <-tickChan 335 336 // Fail if this job fires again in a short period, indicating a double-run 337 select { 338 case <-time.After(time.Millisecond): 339 case <-tickChan: 340 t.Error("expected job fires exactly twice") 341 } 342 } 343 344 // Simple test using Runnables. 345 func TestJob(t *testing.T) { 346 wg := &sync.WaitGroup{} 347 wg.Add(1) 348 349 cron := cron2.New() 350 _ =cron.AddJob("0 0 0 30 Feb ?", testJob{wg, "job0"}) 351 _ = cron.AddJob("0 0 0 1 1 ?", testJob{wg, "job1"}) 352 _ = cron.AddJob("* * * * * ?", testJob{wg, "job2"}) 353 _ = cron.AddJob("1 0 0 1 1 ?", testJob{wg, "job3"}) 354 cron.Schedule(cron2.Every(5*time.Second+5*time.Nanosecond), testJob{wg, "job4"}) 355 cron.Schedule(cron2.Every(5*time.Minute), testJob{wg, "job5"}) 356 357 cron.Start() 358 defer cron.Stop() 359 360 select { 361 case <-time.After(OneSecond): 362 t.FailNow() 363 case <-wait(wg): 364 } 365 366 // Ensure the entries are in the right order. 367 expecteds := []string{"job2", "job4", "job5", "job1", "job3", "job0"} 368 369 var actuals []string 370 for _, entry := range cron.Entries() { 371 actuals = append(actuals, entry.Job.(testJob).name) 372 } 373 374 for i, expected := range expecteds { 375 if actuals[i] != expected { 376 t.Fatalf("Jobs not in the right order. (expected) %s != %s (actual)", expecteds, actuals) 377 } 378 } 379 } 380 381 type ZeroSchedule struct{} 382 383 func (*ZeroSchedule) Next(time.Time) time.Time { 384 return time.Time{} 385 } 386 387 // Tests that job without time does not run 388 func TestJobWithZeroTimeDoesNotRun(t *testing.T) { 389 cron := cron2.New() 390 calls := 0 391 _ = cron.AddFunc("* * * * * *", func() { calls += 1 }) 392 cron.Schedule(new(ZeroSchedule), cron2.FuncJob(func() { t.Error("expected zero task will not run") })) 393 cron.Start() 394 defer cron.Stop() 395 <-time.After(OneSecond) 396 if calls != 1 { 397 t.Errorf("called %d times, expected 1\n", calls) 398 } 399 } 400 401 func wait(wg *sync.WaitGroup) chan bool { 402 ch := make(chan bool) 403 go func() { 404 wg.Wait() 405 ch <- true 406 }() 407 return ch 408 } 409 410 func stop(cron *cron2.Cron) chan bool { 411 ch := make(chan bool) 412 go func() { 413 cron.Stop() 414 ch <- true 415 }() 416 return ch 417 }