cron_test.go 10 KB

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