parser_test.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. package cron
  2. import (
  3. "reflect"
  4. "strings"
  5. "testing"
  6. "time"
  7. )
  8. func TestRange(t *testing.T) {
  9. zero := uint64(0)
  10. ranges := []struct {
  11. expr string
  12. min, max uint
  13. expected uint64
  14. err string
  15. }{
  16. {"5", 0, 7, 1 << 5, ""},
  17. {"0", 0, 7, 1 << 0, ""},
  18. {"7", 0, 7, 1 << 7, ""},
  19. {"5-5", 0, 7, 1 << 5, ""},
  20. {"5-6", 0, 7, 1<<5 | 1<<6, ""},
  21. {"5-7", 0, 7, 1<<5 | 1<<6 | 1<<7, ""},
  22. {"5-6/2", 0, 7, 1 << 5, ""},
  23. {"5-7/2", 0, 7, 1<<5 | 1<<7, ""},
  24. {"5-7/1", 0, 7, 1<<5 | 1<<6 | 1<<7, ""},
  25. {"*", 1, 3, 1<<1 | 1<<2 | 1<<3 | starBit, ""},
  26. {"*/2", 1, 3, 1<<1 | 1<<3 | starBit, ""},
  27. {"5--5", 0, 0, zero, "Too many hyphens"},
  28. {"jan-x", 0, 0, zero, "Failed to parse int from"},
  29. {"2-x", 1, 5, zero, "Failed to parse int from"},
  30. {"*/-12", 0, 0, zero, "Negative number"},
  31. {"*//2", 0, 0, zero, "Too many slashes"},
  32. {"1", 3, 5, zero, "below minimum"},
  33. {"6", 3, 5, zero, "above maximum"},
  34. {"5-3", 3, 5, zero, "beyond end of range"},
  35. {"*/0", 0, 0, zero, "should be a positive number"},
  36. }
  37. for _, c := range ranges {
  38. actual, err := getRange(c.expr, bounds{c.min, c.max, nil})
  39. if len(c.err) != 0 && (err == nil || !strings.Contains(err.Error(), c.err)) {
  40. t.Errorf("%s => expected %v, got %v", c.expr, c.err, err)
  41. }
  42. if len(c.err) == 0 && err != nil {
  43. t.Errorf("%s => unexpected error %v", c.expr, err)
  44. }
  45. if actual != c.expected {
  46. t.Errorf("%s => expected %d, got %d", c.expr, c.expected, actual)
  47. }
  48. }
  49. }
  50. func TestField(t *testing.T) {
  51. fields := []struct {
  52. expr string
  53. min, max uint
  54. expected uint64
  55. }{
  56. {"5", 1, 7, 1 << 5},
  57. {"5,6", 1, 7, 1<<5 | 1<<6},
  58. {"5,6,7", 1, 7, 1<<5 | 1<<6 | 1<<7},
  59. {"1,5-7/2,3", 1, 7, 1<<1 | 1<<5 | 1<<7 | 1<<3},
  60. }
  61. for _, c := range fields {
  62. actual, _ := getField(c.expr, bounds{c.min, c.max, nil})
  63. if actual != c.expected {
  64. t.Errorf("%s => expected %d, got %d", c.expr, c.expected, actual)
  65. }
  66. }
  67. }
  68. func TestAll(t *testing.T) {
  69. allBits := []struct {
  70. r bounds
  71. expected uint64
  72. }{
  73. {minutes, 0xfffffffffffffff}, // 0-59: 60 ones
  74. {hours, 0xffffff}, // 0-23: 24 ones
  75. {dom, 0xfffffffe}, // 1-31: 31 ones, 1 zero
  76. {months, 0x1ffe}, // 1-12: 12 ones, 1 zero
  77. {dow, 0x7f}, // 0-6: 7 ones
  78. }
  79. for _, c := range allBits {
  80. actual := all(c.r) // all() adds the starBit, so compensate for that..
  81. if c.expected|starBit != actual {
  82. t.Errorf("%d-%d/%d => expected %b, got %b",
  83. c.r.min, c.r.max, 1, c.expected|starBit, actual)
  84. }
  85. }
  86. }
  87. func TestBits(t *testing.T) {
  88. bits := []struct {
  89. min, max, step uint
  90. expected uint64
  91. }{
  92. {0, 0, 1, 0x1},
  93. {1, 1, 1, 0x2},
  94. {1, 5, 2, 0x2a}, // 101010
  95. {1, 4, 2, 0xa}, // 1010
  96. }
  97. for _, c := range bits {
  98. actual := getBits(c.min, c.max, c.step)
  99. if c.expected != actual {
  100. t.Errorf("%d-%d/%d => expected %b, got %b",
  101. c.min, c.max, c.step, c.expected, actual)
  102. }
  103. }
  104. }
  105. func TestParseScheduleErrors(t *testing.T) {
  106. var tests = []struct{ expr, err string }{
  107. {"* 5 j * * *", "Failed to parse int from"},
  108. {"@every Xm", "Failed to parse duration"},
  109. {"@unrecognized", "Unrecognized descriptor"},
  110. {"* * * *", "Expected 5 to 6 fields"},
  111. {"", "Empty spec string"},
  112. }
  113. for _, c := range tests {
  114. actual, err := Parse(c.expr)
  115. if err == nil || !strings.Contains(err.Error(), c.err) {
  116. t.Errorf("%s => expected %v, got %v", c.expr, c.err, err)
  117. }
  118. if actual != nil {
  119. t.Errorf("expected nil schedule on error, got %v", actual)
  120. }
  121. }
  122. }
  123. func TestParseSchedule(t *testing.T) {
  124. tokyo, _ := time.LoadLocation("Asia/Tokyo")
  125. entries := []struct {
  126. expr string
  127. expected Schedule
  128. }{
  129. {"0 5 * * * *", every5min(time.Local)},
  130. // Relied on the "optional seconds" behavior
  131. // {"5 * * * *", every5min(time.Local)},
  132. {"TZ=UTC 0 5 * * * *", every5min(time.UTC)},
  133. // {"TZ=UTC 5 * * * *", every5min(time.UTC)},
  134. {"TZ=Asia/Tokyo 0 5 * * * *", every5min(tokyo)},
  135. {"@every 5m", ConstantDelaySchedule{5 * time.Minute}},
  136. {"@midnight", midnight(time.Local)},
  137. {"TZ=UTC @midnight", midnight(time.UTC)},
  138. {"TZ=Asia/Tokyo @midnight", midnight(tokyo)},
  139. {"@yearly", annual(time.Local)},
  140. {"@annually", annual(time.Local)},
  141. {
  142. expr: "* 5 * * * *",
  143. expected: &SpecSchedule{
  144. Second: all(seconds),
  145. Minute: 1 << 5,
  146. Hour: all(hours),
  147. Dom: all(dom),
  148. Month: all(months),
  149. Dow: all(dow),
  150. Location: time.Local,
  151. },
  152. },
  153. }
  154. for _, c := range entries {
  155. actual, err := Parse(c.expr)
  156. if err != nil {
  157. t.Errorf("%s => unexpected error %v", c.expr, err)
  158. }
  159. if !reflect.DeepEqual(actual, c.expected) {
  160. t.Errorf("%s => expected %b, got %b", c.expr, c.expected, actual)
  161. }
  162. }
  163. }
  164. func TestStandardSpecSchedule(t *testing.T) {
  165. entries := []struct {
  166. expr string
  167. expected Schedule
  168. err string
  169. }{
  170. {
  171. expr: "5 * * * *",
  172. expected: &SpecSchedule{1 << seconds.min, 1 << 5, all(hours), all(dom), all(months), all(dow), time.Local},
  173. },
  174. {
  175. expr: "@every 5m",
  176. expected: ConstantDelaySchedule{time.Duration(5) * time.Minute},
  177. },
  178. {
  179. expr: "5 j * * *",
  180. err: "Failed to parse int from",
  181. },
  182. {
  183. expr: "* * * *",
  184. err: "Expected exactly 5 fields",
  185. },
  186. }
  187. for _, c := range entries {
  188. actual, err := ParseStandard(c.expr)
  189. if len(c.err) != 0 && (err == nil || !strings.Contains(err.Error(), c.err)) {
  190. t.Errorf("%s => expected %v, got %v", c.expr, c.err, err)
  191. }
  192. if len(c.err) == 0 && err != nil {
  193. t.Errorf("%s => unexpected error %v", c.expr, err)
  194. }
  195. if !reflect.DeepEqual(actual, c.expected) {
  196. t.Errorf("%s => expected %b, got %b", c.expr, c.expected, actual)
  197. }
  198. }
  199. }
  200. func every5min(loc *time.Location) *SpecSchedule {
  201. return &SpecSchedule{1 << 0, 1 << 5, all(hours), all(dom), all(months), all(dow), loc}
  202. }
  203. func midnight(loc *time.Location) *SpecSchedule {
  204. return &SpecSchedule{1, 1, 1, all(dom), all(months), all(dow), loc}
  205. }
  206. func annual(loc *time.Location) *SpecSchedule {
  207. return &SpecSchedule{
  208. Second: 1 << seconds.min,
  209. Minute: 1 << minutes.min,
  210. Hour: 1 << hours.min,
  211. Dom: 1 << dom.min,
  212. Month: 1 << months.min,
  213. Dow: all(dow),
  214. Location: loc,
  215. }
  216. }