cron_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702
  1. //nolint:all
  2. package cron
  3. import (
  4. "bytes"
  5. "fmt"
  6. "log"
  7. "strings"
  8. "sync"
  9. "sync/atomic"
  10. "testing"
  11. "time"
  12. )
  13. // Many tests schedule a job for every second, and then wait at most a second
  14. // for it to run. This amount is just slightly larger than 1 second to
  15. // compensate for a few milliseconds of runtime.
  16. const OneSecond = 1*time.Second + 50*time.Millisecond
  17. type syncWriter struct {
  18. wr bytes.Buffer
  19. m sync.Mutex
  20. }
  21. func (sw *syncWriter) Write(data []byte) (n int, err error) {
  22. sw.m.Lock()
  23. n, err = sw.wr.Write(data)
  24. sw.m.Unlock()
  25. return
  26. }
  27. func (sw *syncWriter) String() string {
  28. sw.m.Lock()
  29. defer sw.m.Unlock()
  30. return sw.wr.String()
  31. }
  32. func newBufLogger(sw *syncWriter) Logger {
  33. return PrintfLogger(log.New(sw, "", log.LstdFlags))
  34. }
  35. func TestFuncPanicRecovery(t *testing.T) {
  36. var buf syncWriter
  37. cron := New(WithParser(secondParser),
  38. WithChain(Recover(newBufLogger(&buf))))
  39. cron.Start()
  40. defer cron.Stop()
  41. cron.AddFunc("* * * * * ?", func() {
  42. panic("YOLO")
  43. })
  44. select {
  45. case <-time.After(OneSecond):
  46. if !strings.Contains(buf.String(), "YOLO") {
  47. t.Error("expected a panic to be logged, got none")
  48. }
  49. return
  50. }
  51. }
  52. type DummyJob struct{}
  53. func (DummyJob) Run() {
  54. panic("YOLO")
  55. }
  56. func TestJobPanicRecovery(t *testing.T) {
  57. var job DummyJob
  58. var buf syncWriter
  59. cron := New(WithParser(secondParser),
  60. WithChain(Recover(newBufLogger(&buf))))
  61. cron.Start()
  62. defer cron.Stop()
  63. cron.AddJob("* * * * * ?", job)
  64. select {
  65. case <-time.After(OneSecond):
  66. if !strings.Contains(buf.String(), "YOLO") {
  67. t.Error("expected a panic to be logged, got none")
  68. }
  69. return
  70. }
  71. }
  72. // Start and stop cron with no entries.
  73. func TestNoEntries(t *testing.T) {
  74. cron := newWithSeconds()
  75. cron.Start()
  76. select {
  77. case <-time.After(OneSecond):
  78. t.Fatal("expected cron will be stopped immediately")
  79. case <-stop(cron):
  80. }
  81. }
  82. // Start, stop, then add an entry. Verify entry doesn't run.
  83. func TestStopCausesJobsToNotRun(t *testing.T) {
  84. wg := &sync.WaitGroup{}
  85. wg.Add(1)
  86. cron := newWithSeconds()
  87. cron.Start()
  88. cron.Stop()
  89. cron.AddFunc("* * * * * ?", func() { wg.Done() })
  90. select {
  91. case <-time.After(OneSecond):
  92. // No job ran!
  93. case <-wait(wg):
  94. t.Fatal("expected stopped cron does not run any job")
  95. }
  96. }
  97. // Add a job, start cron, expect it runs.
  98. func TestAddBeforeRunning(t *testing.T) {
  99. wg := &sync.WaitGroup{}
  100. wg.Add(1)
  101. cron := newWithSeconds()
  102. cron.AddFunc("* * * * * ?", func() { wg.Done() })
  103. cron.Start()
  104. defer cron.Stop()
  105. // Give cron 2 seconds to run our job (which is always activated).
  106. select {
  107. case <-time.After(OneSecond):
  108. t.Fatal("expected job runs")
  109. case <-wait(wg):
  110. }
  111. }
  112. // Start cron, add a job, expect it runs.
  113. func TestAddWhileRunning(t *testing.T) {
  114. wg := &sync.WaitGroup{}
  115. wg.Add(1)
  116. cron := newWithSeconds()
  117. cron.Start()
  118. defer cron.Stop()
  119. cron.AddFunc("* * * * * ?", func() { wg.Done() })
  120. select {
  121. case <-time.After(OneSecond):
  122. t.Fatal("expected job runs")
  123. case <-wait(wg):
  124. }
  125. }
  126. // Test for #34. Adding a job after calling start results in multiple job invocations
  127. func TestAddWhileRunningWithDelay(t *testing.T) {
  128. cron := newWithSeconds()
  129. cron.Start()
  130. defer cron.Stop()
  131. time.Sleep(5 * time.Second)
  132. var calls int64
  133. cron.AddFunc("* * * * * *", func() { atomic.AddInt64(&calls, 1) })
  134. <-time.After(OneSecond)
  135. if atomic.LoadInt64(&calls) != 1 {
  136. t.Errorf("called %d times, expected 1\n", calls)
  137. }
  138. }
  139. // Add a job, remove a job, start cron, expect nothing runs.
  140. func TestRemoveBeforeRunning(t *testing.T) {
  141. wg := &sync.WaitGroup{}
  142. wg.Add(1)
  143. cron := newWithSeconds()
  144. id, _ := cron.AddFunc("* * * * * ?", func() { wg.Done() })
  145. cron.Remove(id)
  146. cron.Start()
  147. defer cron.Stop()
  148. select {
  149. case <-time.After(OneSecond):
  150. // Success, shouldn't run
  151. case <-wait(wg):
  152. t.FailNow()
  153. }
  154. }
  155. // Start cron, add a job, remove it, expect it doesn't run.
  156. func TestRemoveWhileRunning(t *testing.T) {
  157. wg := &sync.WaitGroup{}
  158. wg.Add(1)
  159. cron := newWithSeconds()
  160. cron.Start()
  161. defer cron.Stop()
  162. id, _ := cron.AddFunc("* * * * * ?", func() { wg.Done() })
  163. cron.Remove(id)
  164. select {
  165. case <-time.After(OneSecond):
  166. case <-wait(wg):
  167. t.FailNow()
  168. }
  169. }
  170. // Test timing with Entries.
  171. func TestSnapshotEntries(t *testing.T) {
  172. wg := &sync.WaitGroup{}
  173. wg.Add(1)
  174. cron := New()
  175. cron.AddFunc("@every 2s", func() { wg.Done() })
  176. cron.Start()
  177. defer cron.Stop()
  178. // Cron should fire in 2 seconds. After 1 second, call Entries.
  179. select {
  180. case <-time.After(OneSecond):
  181. cron.Entries()
  182. }
  183. // Even though Entries was called, the cron should fire at the 2 second mark.
  184. select {
  185. case <-time.After(OneSecond):
  186. t.Error("expected job runs at 2 second mark")
  187. case <-wait(wg):
  188. }
  189. }
  190. // Test that the entries are correctly sorted.
  191. // Add a bunch of long-in-the-future entries, and an immediate entry, and ensure
  192. // that the immediate entry runs immediately.
  193. // Also: Test that multiple jobs run in the same instant.
  194. func TestMultipleEntries(t *testing.T) {
  195. wg := &sync.WaitGroup{}
  196. wg.Add(2)
  197. cron := newWithSeconds()
  198. cron.AddFunc("0 0 0 1 1 ?", func() {})
  199. cron.AddFunc("* * * * * ?", func() { wg.Done() })
  200. id1, _ := cron.AddFunc("* * * * * ?", func() { t.Fatal() })
  201. id2, _ := cron.AddFunc("* * * * * ?", func() { t.Fatal() })
  202. cron.AddFunc("0 0 0 31 12 ?", func() {})
  203. cron.AddFunc("* * * * * ?", func() { wg.Done() })
  204. cron.Remove(id1)
  205. cron.Start()
  206. cron.Remove(id2)
  207. defer cron.Stop()
  208. select {
  209. case <-time.After(OneSecond):
  210. t.Error("expected job run in proper order")
  211. case <-wait(wg):
  212. }
  213. }
  214. // Test running the same job twice.
  215. func TestRunningJobTwice(t *testing.T) {
  216. wg := &sync.WaitGroup{}
  217. wg.Add(2)
  218. cron := newWithSeconds()
  219. cron.AddFunc("0 0 0 1 1 ?", func() {})
  220. cron.AddFunc("0 0 0 31 12 ?", func() {})
  221. cron.AddFunc("* * * * * ?", func() { wg.Done() })
  222. cron.Start()
  223. defer cron.Stop()
  224. select {
  225. case <-time.After(2 * OneSecond):
  226. t.Error("expected job fires 2 times")
  227. case <-wait(wg):
  228. }
  229. }
  230. func TestRunningMultipleSchedules(t *testing.T) {
  231. wg := &sync.WaitGroup{}
  232. wg.Add(2)
  233. cron := newWithSeconds()
  234. cron.AddFunc("0 0 0 1 1 ?", func() {})
  235. cron.AddFunc("0 0 0 31 12 ?", func() {})
  236. cron.AddFunc("* * * * * ?", func() { wg.Done() })
  237. cron.Schedule(Every(time.Minute), FuncJob(func() {}))
  238. cron.Schedule(Every(time.Second), FuncJob(func() { wg.Done() }))
  239. cron.Schedule(Every(time.Hour), FuncJob(func() {}))
  240. cron.Start()
  241. defer cron.Stop()
  242. select {
  243. case <-time.After(2 * OneSecond):
  244. t.Error("expected job fires 2 times")
  245. case <-wait(wg):
  246. }
  247. }
  248. // Test that the cron is run in the local time zone (as opposed to UTC).
  249. func TestLocalTimezone(t *testing.T) {
  250. wg := &sync.WaitGroup{}
  251. wg.Add(2)
  252. now := time.Now()
  253. // FIX: Issue #205
  254. // This calculation doesn't work in seconds 58 or 59.
  255. // Take the easy way out and sleep.
  256. if now.Second() >= 58 {
  257. time.Sleep(2 * time.Second)
  258. now = time.Now()
  259. }
  260. spec := fmt.Sprintf("%d,%d %d %d %d %d ?",
  261. now.Second()+1, now.Second()+2, now.Minute(), now.Hour(), now.Day(), now.Month())
  262. cron := newWithSeconds()
  263. cron.AddFunc(spec, func() { wg.Done() })
  264. cron.Start()
  265. defer cron.Stop()
  266. select {
  267. case <-time.After(OneSecond * 2):
  268. t.Error("expected job fires 2 times")
  269. case <-wait(wg):
  270. }
  271. }
  272. // Test that the cron is run in the given time zone (as opposed to local).
  273. func TestNonLocalTimezone(t *testing.T) {
  274. wg := &sync.WaitGroup{}
  275. wg.Add(2)
  276. loc, err := time.LoadLocation("Atlantic/Cape_Verde")
  277. if err != nil {
  278. fmt.Printf("Failed to load time zone Atlantic/Cape_Verde: %+v", err)
  279. t.Fail()
  280. }
  281. now := time.Now().In(loc)
  282. // FIX: Issue #205
  283. // This calculation doesn't work in seconds 58 or 59.
  284. // Take the easy way out and sleep.
  285. if now.Second() >= 58 {
  286. time.Sleep(2 * time.Second)
  287. now = time.Now().In(loc)
  288. }
  289. spec := fmt.Sprintf("%d,%d %d %d %d %d ?",
  290. now.Second()+1, now.Second()+2, now.Minute(), now.Hour(), now.Day(), now.Month())
  291. cron := New(WithLocation(loc), WithParser(secondParser))
  292. cron.AddFunc(spec, func() { wg.Done() })
  293. cron.Start()
  294. defer cron.Stop()
  295. select {
  296. case <-time.After(OneSecond * 2):
  297. t.Error("expected job fires 2 times")
  298. case <-wait(wg):
  299. }
  300. }
  301. // Test that calling stop before start silently returns without
  302. // blocking the stop channel.
  303. func TestStopWithoutStart(t *testing.T) {
  304. cron := New()
  305. cron.Stop()
  306. }
  307. type testJob struct {
  308. wg *sync.WaitGroup
  309. name string
  310. }
  311. func (t testJob) Run() {
  312. t.wg.Done()
  313. }
  314. // Test that adding an invalid job spec returns an error
  315. func TestInvalidJobSpec(t *testing.T) {
  316. cron := New()
  317. _, err := cron.AddJob("this will not parse", nil)
  318. if err == nil {
  319. t.Errorf("expected an error with invalid spec, got nil")
  320. }
  321. }
  322. // Test blocking run method behaves as Start()
  323. func TestBlockingRun(t *testing.T) {
  324. wg := &sync.WaitGroup{}
  325. wg.Add(1)
  326. cron := newWithSeconds()
  327. cron.AddFunc("* * * * * ?", func() { wg.Done() })
  328. var unblockChan = make(chan struct{})
  329. go func() {
  330. cron.Run()
  331. close(unblockChan)
  332. }()
  333. defer cron.Stop()
  334. select {
  335. case <-time.After(OneSecond):
  336. t.Error("expected job fires")
  337. case <-unblockChan:
  338. t.Error("expected that Run() blocks")
  339. case <-wait(wg):
  340. }
  341. }
  342. // Test that double-running is a no-op
  343. func TestStartNoop(t *testing.T) {
  344. var tickChan = make(chan struct{}, 2)
  345. cron := newWithSeconds()
  346. cron.AddFunc("* * * * * ?", func() {
  347. tickChan <- struct{}{}
  348. })
  349. cron.Start()
  350. defer cron.Stop()
  351. // Wait for the first firing to ensure the runner is going
  352. <-tickChan
  353. cron.Start()
  354. <-tickChan
  355. // Fail if this job fires again in a short period, indicating a double-run
  356. select {
  357. case <-time.After(time.Millisecond):
  358. case <-tickChan:
  359. t.Error("expected job fires exactly twice")
  360. }
  361. }
  362. // Simple test using Runnables.
  363. func TestJob(t *testing.T) {
  364. wg := &sync.WaitGroup{}
  365. wg.Add(1)
  366. cron := newWithSeconds()
  367. cron.AddJob("0 0 0 30 Feb ?", testJob{wg, "job0"})
  368. cron.AddJob("0 0 0 1 1 ?", testJob{wg, "job1"})
  369. job2, _ := cron.AddJob("* * * * * ?", testJob{wg, "job2"})
  370. cron.AddJob("1 0 0 1 1 ?", testJob{wg, "job3"})
  371. cron.Schedule(Every(5*time.Second+5*time.Nanosecond), testJob{wg, "job4"})
  372. job5 := cron.Schedule(Every(5*time.Minute), testJob{wg, "job5"})
  373. // Test getting an Entry pre-Start.
  374. if actualName := cron.Entry(job2).Job.(testJob).name; actualName != "job2" {
  375. t.Error("wrong job retrieved:", actualName)
  376. }
  377. if actualName := cron.Entry(job5).Job.(testJob).name; actualName != "job5" {
  378. t.Error("wrong job retrieved:", actualName)
  379. }
  380. cron.Start()
  381. defer cron.Stop()
  382. select {
  383. case <-time.After(OneSecond):
  384. t.FailNow()
  385. case <-wait(wg):
  386. }
  387. // Ensure the entries are in the right order.
  388. expecteds := []string{"job2", "job4", "job5", "job1", "job3", "job0"}
  389. var actuals []string
  390. for _, entry := range cron.Entries() {
  391. actuals = append(actuals, entry.Job.(testJob).name)
  392. }
  393. for i, expected := range expecteds {
  394. if actuals[i] != expected {
  395. t.Fatalf("Jobs not in the right order. (expected) %s != %s (actual)", expecteds, actuals)
  396. }
  397. }
  398. // Test getting Entries.
  399. if actualName := cron.Entry(job2).Job.(testJob).name; actualName != "job2" {
  400. t.Error("wrong job retrieved:", actualName)
  401. }
  402. if actualName := cron.Entry(job5).Job.(testJob).name; actualName != "job5" {
  403. t.Error("wrong job retrieved:", actualName)
  404. }
  405. }
  406. // Issue #206
  407. // Ensure that the next run of a job after removing an entry is accurate.
  408. func TestScheduleAfterRemoval(t *testing.T) {
  409. var wg1 sync.WaitGroup
  410. var wg2 sync.WaitGroup
  411. wg1.Add(1)
  412. wg2.Add(1)
  413. // The first time this job is run, set a timer and remove the other job
  414. // 750ms later. Correct behavior would be to still run the job again in
  415. // 250ms, but the bug would cause it to run instead 1s later.
  416. var calls int
  417. var mu sync.Mutex
  418. cron := newWithSeconds()
  419. hourJob := cron.Schedule(Every(time.Hour), FuncJob(func() {}))
  420. cron.Schedule(Every(time.Second), FuncJob(func() {
  421. mu.Lock()
  422. defer mu.Unlock()
  423. switch calls {
  424. case 0:
  425. wg1.Done()
  426. calls++
  427. case 1:
  428. time.Sleep(750 * time.Millisecond)
  429. cron.Remove(hourJob)
  430. calls++
  431. case 2:
  432. calls++
  433. wg2.Done()
  434. case 3:
  435. panic("unexpected 3rd call")
  436. }
  437. }))
  438. cron.Start()
  439. defer cron.Stop()
  440. // the first run might be any length of time 0 - 1s, since the schedule
  441. // rounds to the second. wait for the first run to true up.
  442. wg1.Wait()
  443. select {
  444. case <-time.After(2 * OneSecond):
  445. t.Error("expected job fires 2 times")
  446. case <-wait(&wg2):
  447. }
  448. }
  449. type ZeroSchedule struct{}
  450. func (*ZeroSchedule) Next(time.Time) time.Time {
  451. return time.Time{}
  452. }
  453. // Tests that job without time does not run
  454. func TestJobWithZeroTimeDoesNotRun(t *testing.T) {
  455. cron := newWithSeconds()
  456. var calls int64
  457. cron.AddFunc("* * * * * *", func() { atomic.AddInt64(&calls, 1) })
  458. cron.Schedule(new(ZeroSchedule), FuncJob(func() { t.Error("expected zero task will not run") }))
  459. cron.Start()
  460. defer cron.Stop()
  461. <-time.After(OneSecond)
  462. if atomic.LoadInt64(&calls) != 1 {
  463. t.Errorf("called %d times, expected 1\n", calls)
  464. }
  465. }
  466. func TestStopAndWait(t *testing.T) {
  467. t.Run("nothing running, returns immediately", func(*testing.T) {
  468. cron := newWithSeconds()
  469. cron.Start()
  470. ctx := cron.Stop()
  471. select {
  472. case <-ctx.Done():
  473. case <-time.After(time.Millisecond):
  474. t.Error("context was not done immediately")
  475. }
  476. })
  477. t.Run("repeated calls to Stop", func(*testing.T) {
  478. cron := newWithSeconds()
  479. cron.Start()
  480. _ = cron.Stop()
  481. time.Sleep(time.Millisecond)
  482. ctx := cron.Stop()
  483. select {
  484. case <-ctx.Done():
  485. case <-time.After(time.Millisecond):
  486. t.Error("context was not done immediately")
  487. }
  488. })
  489. t.Run("a couple fast jobs added, still returns immediately", func(*testing.T) {
  490. cron := newWithSeconds()
  491. cron.AddFunc("* * * * * *", func() {})
  492. cron.Start()
  493. cron.AddFunc("* * * * * *", func() {})
  494. cron.AddFunc("* * * * * *", func() {})
  495. cron.AddFunc("* * * * * *", func() {})
  496. time.Sleep(time.Second)
  497. ctx := cron.Stop()
  498. select {
  499. case <-ctx.Done():
  500. case <-time.After(time.Millisecond):
  501. t.Error("context was not done immediately")
  502. }
  503. })
  504. t.Run("a couple fast jobs and a slow job added, waits for slow job", func(*testing.T) {
  505. cron := newWithSeconds()
  506. cron.AddFunc("* * * * * *", func() {})
  507. cron.Start()
  508. cron.AddFunc("* * * * * *", func() { time.Sleep(2 * time.Second) })
  509. cron.AddFunc("* * * * * *", func() {})
  510. time.Sleep(time.Second)
  511. ctx := cron.Stop()
  512. // Verify that it is not done for at least 750ms
  513. select {
  514. case <-ctx.Done():
  515. t.Error("context was done too quickly immediately")
  516. case <-time.After(750 * time.Millisecond):
  517. // expected, because the job sleeping for 1 second is still running
  518. }
  519. // Verify that it IS done in the next 500ms (giving 250ms buffer)
  520. select {
  521. case <-ctx.Done():
  522. // expected
  523. case <-time.After(1500 * time.Millisecond):
  524. t.Error("context not done after job should have completed")
  525. }
  526. })
  527. t.Run("repeated calls to stop, waiting for completion and after", func(*testing.T) {
  528. cron := newWithSeconds()
  529. cron.AddFunc("* * * * * *", func() {})
  530. cron.AddFunc("* * * * * *", func() { time.Sleep(2 * time.Second) })
  531. cron.Start()
  532. cron.AddFunc("* * * * * *", func() {})
  533. time.Sleep(time.Second)
  534. ctx := cron.Stop()
  535. ctx2 := cron.Stop()
  536. // Verify that it is not done for at least 1500ms
  537. select {
  538. case <-ctx.Done():
  539. t.Error("context was done too quickly immediately")
  540. case <-ctx2.Done():
  541. t.Error("context2 was done too quickly immediately")
  542. case <-time.After(1500 * time.Millisecond):
  543. // expected, because the job sleeping for 2 seconds is still running
  544. }
  545. // Verify that it IS done in the next 1s (giving 500ms buffer)
  546. select {
  547. case <-ctx.Done():
  548. // expected
  549. case <-time.After(time.Second):
  550. t.Error("context not done after job should have completed")
  551. }
  552. // Verify that ctx2 is also done.
  553. select {
  554. case <-ctx2.Done():
  555. // expected
  556. case <-time.After(time.Millisecond):
  557. t.Error("context2 not done even though context1 is")
  558. }
  559. // Verify that a new context retrieved from stop is immediately done.
  560. ctx3 := cron.Stop()
  561. select {
  562. case <-ctx3.Done():
  563. // expected
  564. case <-time.After(time.Millisecond):
  565. t.Error("context not done even when cron Stop is completed")
  566. }
  567. })
  568. }
  569. func TestMultiThreadedStartAndStop(t *testing.T) {
  570. cron := New()
  571. go cron.Run()
  572. time.Sleep(2 * time.Millisecond)
  573. cron.Stop()
  574. }
  575. func wait(wg *sync.WaitGroup) chan bool {
  576. ch := make(chan bool)
  577. go func() {
  578. wg.Wait()
  579. ch <- true
  580. }()
  581. return ch
  582. }
  583. func stop(cron *Cron) chan bool {
  584. ch := make(chan bool)
  585. go func() {
  586. cron.Stop()
  587. ch <- true
  588. }()
  589. return ch
  590. }
  591. // newWithSeconds returns a Cron with the seconds field enabled.
  592. func newWithSeconds() *Cron {
  593. return New(WithParser(secondParser), WithChain())
  594. }