cron_test.go 9.6 KB

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