cron_test.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. /*
  2. * Public Domain Software
  3. *
  4. * I (Matthias Ladkau) am the author of the source code in this file.
  5. * I have placed the source code in this file in the public domain.
  6. *
  7. * For further information see: http://creativecommons.org/publicdomain/zero/1.0/
  8. */
  9. package timeutil
  10. import (
  11. "bytes"
  12. "fmt"
  13. "strings"
  14. "testing"
  15. "time"
  16. )
  17. /*
  18. func TestExampleCron(t *testing.T) {
  19. // For manual testing with real Cron object
  20. c := NewCron()
  21. c.Register("* * * * * *", func() {
  22. fmt.Println(time.Now().UTC())
  23. })
  24. c.Start() // Start cron thread
  25. time.Sleep(5 * time.Second)
  26. c.Stop() // Shutdown cron thread
  27. }
  28. */
  29. func TestCron(t *testing.T) {
  30. c := NewCron()
  31. // Check repeated running and stopping
  32. if c.stopChan != nil {
  33. t.Error("Unexpected cron state")
  34. return
  35. }
  36. c.Start()
  37. if c.stopChan == nil {
  38. t.Error("Unexpected cron state")
  39. return
  40. }
  41. c.Stop()
  42. if c.stopChan != nil {
  43. t.Error("Unexpected cron state")
  44. return
  45. }
  46. c.Start()
  47. if c.stopChan == nil {
  48. t.Error("Unexpected cron state")
  49. return
  50. }
  51. c.Stop()
  52. if c.stopChan != nil {
  53. t.Error("Unexpected cron state")
  54. return
  55. }
  56. // Test now with a cron testing object for a day
  57. var buf bytes.Buffer
  58. c = NewTestingCronDay()
  59. c.Register("0 12 12,8 * * *", func() {
  60. buf.WriteString(fmt.Sprintf("Test ... %v\n", c.NowFunc().UTC()))
  61. })
  62. c.Start()
  63. WaitTestingCron(c)
  64. if buf.String() != `
  65. Test ... 2000-01-01 08:12:00 +0000 UTC
  66. Test ... 2000-01-01 12:12:00 +0000 UTC
  67. `[1:] {
  68. t.Error("Unexpected result:", buf.String())
  69. return
  70. }
  71. if NewTestingCronWeek() == nil {
  72. t.Error("Unexpected result")
  73. return
  74. }
  75. if NewTestingCronMonth() == nil {
  76. t.Error("Unexpected result")
  77. return
  78. }
  79. // Make sure it is impossible to create a never ending TestingNow object
  80. startTime, _ := time.Parse(time.RFC3339, "2000-01-01T00:00:00Z")
  81. endTime, _ := time.Parse(time.RFC3339, "2000-01-07T23:59:59Z")
  82. _, err := NewTestingNow(endTime, startTime)
  83. if err == nil || err.Error() != "End time 2000-01-01 00:00:00 +0000 UTC is not after start time 2000-01-07 23:59:59 +0000 UTC" {
  84. t.Error("Unexpected result:", err)
  85. return
  86. }
  87. }
  88. func TestCronSpec(t *testing.T) {
  89. // Do some table testing - spec vs. expected human readable string
  90. // vs. expected error message vs. expected different spec string (sorted)
  91. testDaysTable := [][]string{
  92. // Test * wildcard and numbers
  93. {"* * * * * *", "every day", "", ""},
  94. {"* * * 23 * *", "on 23rd of every month", "", ""},
  95. {"* * * * 12 *", "in December", "", ""},
  96. {"* * * * * 0", "on Sunday", "", ""},
  97. {"* * * 12,19,31,23 * *", "on 12th, 19th, 23rd and 31st of every month", "", "* * * 12,19,23,31 * *"},
  98. {"* * * * 12,11 *", "in November and December", "", "* * * * 11,12 *"},
  99. {"* * * * * 4,5,6", "on Thursday, Friday and Saturday", "", ""},
  100. {"* * * * 1,5,8 *", "in January, May and August", "", ""},
  101. {"* * * 1,2,3,4,18 12 *", "on 1st, 2nd, 3rd, 4th and 18th of December", "", ""},
  102. {"* * * 1,2,3,4,18 12 4,3", "on Wednesday and Thursday and 1st, 2nd, 3rd, 4th and 18th of December", "", "* * * 1,2,3,4,18 12 3,4"},
  103. {"* * * 1,2 * 3,4", "on Wednesday and Thursday and 1st and 2nd of every month", "", ""},
  104. {"* * * 4,5 1,2 *", "on 4th and 5th of January and February", "", ""},
  105. {"* * * * 1,2 3,4", "on Wednesday and Thursday in January and February", "", ""},
  106. // Test multiple-of entries
  107. {"* * * *%23,*%1 * *", "every day", "", "* * * * * *"},
  108. {"* * * *%23,*%2 * *", "on every 2nd day and every 23rd day of every month", "", "* * * *%2,*%23 * *"},
  109. {"* * * * *%2,*%12 *", "in every 2nd month and every 12th month", "", ""},
  110. {"* * * * * *%2,*%7,3", "on every 2nd day of the week, every 7th day of the week and Wednesday", "", ""},
  111. // Test mix of normal and multiple-of entries
  112. {"* * * 1,*%2,3,4,*%11,18 11,*%4,12 4,*%3,1", "on every 3rd day of the week, Monday and Thursday and every 2nd day, every 11th day, 1st, 3rd, 4th and 18th of every 4th month, November and December", "", "* * * *%2,*%11,1,3,4,18 *%4,11,12 *%3,1,4"},
  113. // Test errors
  114. {"* * * * *", "", "Cron spec must have 6 entries separated by space", ""},
  115. {"* * * * * x", "", "Cron entries must be a number, '*' or *% and a number", ""},
  116. }
  117. for i, row := range testDaysTable {
  118. res, err := checkDaysSpec(row[0], row[3])
  119. if err != nil {
  120. if row[2] == "" || row[2] != err.Error() {
  121. t.Errorf("Unexpected error for row %v %v: %v", (i + 1), row, err)
  122. return
  123. }
  124. } else if res != row[1] {
  125. t.Errorf("Unexpected result for row %v %v\nexpected: %v\ngot: %v",
  126. (i + 1), row, row[1], res)
  127. return
  128. }
  129. }
  130. testTimeTable := [][]string{
  131. // Test * wildcard and numbers
  132. {"* * * * * *", "every second", "", ""},
  133. {"55 * * * * *", "at second 55 of every minute", "", ""},
  134. {"* 0 * * * *", "every second of the beginning of every hour", "", ""},
  135. {"* * 12 * * *", "every second of hour 12:00", "", ""},
  136. {"0 * * * * *", "at the beginning of every minute", "", ""},
  137. {"0,59 * * * * *", "at the beginning and the end of every minute", "", ""},
  138. {"* 0,59 * * * *", "every second of the beginning and the end of every hour", "", ""},
  139. {"1,3,55 * * * * *", "at second 1, second 3 and second 55 of every minute", "", ""},
  140. {"0 0,59,30 * * * *", "at the beginning, minute 30 and the end of every hour", "", "0 0,30,59 * * * *"},
  141. {"* * 0,12,23 * * *", "every second of hour 00:00, hour 12:00 and hour 23:00", "", ""},
  142. {"0 * 0,12,23 * * *", "every minute of hour 00:00, hour 12:00 and hour 23:00", "", ""},
  143. {"0 1 0,12,23 * * *", "at minute 1 of hour 00:00, hour 12:00 and hour 23:00", "", ""},
  144. {"22,33 * 4 * * *", "at second 22 and second 33 of every minute of hour 04:00", "", ""},
  145. {"22,33 8 4 * * *", "at second 22 and second 33 of minute 8 of hour 04:00", "", ""},
  146. {"22,33 8 * * * *", "at second 22 and second 33 of minute 8 of every hour", "", ""},
  147. {"* 8,9,10 2,3 * * *", "every second of minute 8, minute 9 and minute 10 of hour 02:00 and hour 03:00", "", ""},
  148. // Test multiple-of entries
  149. {"*%20,*%21 * * * * *", "at every 20th second and every 21st second of every minute", "", ""},
  150. {"* *%2,*%55 * * * *", "every second of every 2nd minute and every 55th minute of every hour", "", ""},
  151. {"* * *%13,*%14 * * *", "every second of every 13th hour and every 14th hour", "", ""},
  152. // Test mix of normal and multiple-of entries
  153. {"*%2,*%11,1,3,4,18 *%4,11,12 *%3,1,4 * * *", "at every 2nd second, every 11th second, second 1, second 3, second 4 and second 18 of every 4th minute, minute 11 and minute 12 of every 3rd hour, hour 01:00 and hour 04:00", "", ""},
  154. // Test errors
  155. {"* 60 * * * *", "", "Cron minute entry must be smaller or equal than 59", ""},
  156. {"* * 2,5,25 * * *", "", "Cron hour entry must be smaller or equal than 23", ""},
  157. {"* * * 0 * *", "", "Cron day of month entry must be greater or equal than 1", ""},
  158. {"* * * *%a * *", "", "Cron day of month entry needs a number after '*%'", ""},
  159. {"* * * *%0 * *", "", "Cron day of month entry must be greater or equal than 1", ""},
  160. {"* * * * * x", "", "Cron entries must be a number, '*' or *% and a number", ""},
  161. }
  162. for i, row := range testTimeTable {
  163. res, err := checkTimeSpec(row[0], row[3])
  164. if err != nil {
  165. if row[2] == "" || row[2] != err.Error() {
  166. t.Errorf("Unexpected error for row %v %v: %v", (i + 1), row, err)
  167. return
  168. }
  169. } else if res != row[1] {
  170. t.Errorf("Unexpected result for row %v %v\nexpected: %v\ngot: %v",
  171. (i + 1), row, row[1], res)
  172. return
  173. }
  174. }
  175. // Test combination
  176. if ss, err := NewCronSpec("* * * * * *"); ss.String() != "every second every day" {
  177. t.Error("Unexpected string:", ss, err)
  178. return
  179. }
  180. if ss, err := NewCronSpec("1 2 3 4 5 6"); ss.String() != "at second 1 of minute 2 of hour 03:00 on Saturday and 4th of May" {
  181. t.Error("Unexpected string:", ss, err)
  182. return
  183. }
  184. ss, err := NewCronSpec("1,2 3,4 5,6 7,8 1 6")
  185. if err != nil || ss.String() != "at second 1 and second 2 of minute 3 and minute 4 of hour 05:00 and hour 06:00 on Saturday and 7th and 8th of January" {
  186. t.Error("Unexpected string:", ss, err)
  187. return
  188. }
  189. if res := strings.Join(ss.Generate2000Examples(3), "\n"); res != `
  190. 2000-01-08 05:03:01 +0000 UTC
  191. 2000-01-08 05:03:02 +0000 UTC
  192. 2000-01-08 05:04:01 +0000 UTC`[1:] {
  193. t.Error("Unexpected result:", res)
  194. return
  195. }
  196. ss, err = NewCronSpec("* * * * * *")
  197. if err != nil || ss.String() != "every second every day" {
  198. t.Error("Unexpected string:", ss, err)
  199. return
  200. }
  201. if res := strings.Join(ss.Generate2000Examples(3), "\n"); res != `
  202. 2000-01-01 00:00:00 +0000 UTC
  203. 2000-01-01 00:00:01 +0000 UTC
  204. 2000-01-01 00:00:02 +0000 UTC`[1:] {
  205. t.Error("Unexpected result:", res)
  206. return
  207. }
  208. ss, err = NewCronSpec("0 0 12 *%2 *%2 *")
  209. if err != nil || ss.String() != "at the beginning of hour 12:00 on every 2nd day of every 2nd month" {
  210. t.Error("Unexpected string:", ss, err)
  211. return
  212. }
  213. if res := strings.Join(ss.Generate2000Examples(3), "\n"); res != `
  214. 2000-02-02 12:00:00 +0000 UTC
  215. 2000-02-04 12:00:00 +0000 UTC
  216. 2000-02-06 12:00:00 +0000 UTC`[1:] {
  217. t.Error("Unexpected result:", res)
  218. return
  219. }
  220. }
  221. func checkDaysSpec(spec, newspec string) (string, error) {
  222. cs, err := NewCronSpec(spec)
  223. if err != nil {
  224. return "", err
  225. }
  226. if newspec == "" {
  227. if spec != cs.SpecString() {
  228. return "", fmt.Errorf("Spec string is not expected: %v != %v",
  229. spec, cs.SpecString())
  230. }
  231. } else {
  232. if newspec != cs.SpecString() {
  233. return "", fmt.Errorf("New spec string is not expected: %v != %v",
  234. newspec, cs.SpecString())
  235. }
  236. }
  237. return cs.DaysString(), nil
  238. }
  239. func checkTimeSpec(spec, newspec string) (string, error) {
  240. cs, err := NewCronSpec(spec)
  241. if err != nil {
  242. return "", err
  243. }
  244. if newspec == "" {
  245. if spec != cs.SpecString() {
  246. return "", fmt.Errorf("Spec string is not expected: %v != %v",
  247. spec, cs.SpecString())
  248. }
  249. } else {
  250. if newspec != cs.SpecString() {
  251. return "", fmt.Errorf("New spec string is not expected: %v != %v",
  252. newspec, cs.SpecString())
  253. }
  254. }
  255. return cs.TimeString(), nil
  256. }