parser_test.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. package cron
  2. import (
  3. "reflect"
  4. "strings"
  5. "testing"
  6. "time"
  7. )
  8. var secondParser = NewParser(Second | Minute | Hour | Dom | Month | DowOptional | Descriptor)
  9. func TestRange(t *testing.T) {
  10. zero := uint64(0)
  11. ranges := []struct {
  12. expr string
  13. min, max uint
  14. expected uint64
  15. err string
  16. }{
  17. {"5", 0, 7, 1 << 5, ""},
  18. {"0", 0, 7, 1 << 0, ""},
  19. {"7", 0, 7, 1 << 7, ""},
  20. {"5-5", 0, 7, 1 << 5, ""},
  21. {"5-6", 0, 7, 1<<5 | 1<<6, ""},
  22. {"5-7", 0, 7, 1<<5 | 1<<6 | 1<<7, ""},
  23. {"5-6/2", 0, 7, 1 << 5, ""},
  24. {"5-7/2", 0, 7, 1<<5 | 1<<7, ""},
  25. {"5-7/1", 0, 7, 1<<5 | 1<<6 | 1<<7, ""},
  26. {"*", 1, 3, 1<<1 | 1<<2 | 1<<3 | starBit, ""},
  27. {"*/2", 1, 3, 1<<1 | 1<<3, ""},
  28. {"5--5", 0, 0, zero, "too many hyphens"},
  29. {"jan-x", 0, 0, zero, "failed to parse int from"},
  30. {"2-x", 1, 5, zero, "failed to parse int from"},
  31. {"*/-12", 0, 0, zero, "negative number"},
  32. {"*//2", 0, 0, zero, "too many slashes"},
  33. {"1", 3, 5, zero, "below minimum"},
  34. {"6", 3, 5, zero, "above maximum"},
  35. {"5-3", 3, 5, zero, "beyond end of range"},
  36. {"*/0", 0, 0, zero, "should be a positive number"},
  37. }
  38. for _, c := range ranges {
  39. actual, err := getRange(c.expr, bounds{c.min, c.max, nil})
  40. if len(c.err) != 0 && (err == nil || !strings.Contains(err.Error(), c.err)) {
  41. t.Errorf("%s => expected %v, got %v", c.expr, c.err, err)
  42. }
  43. if len(c.err) == 0 && err != nil {
  44. t.Errorf("%s => unexpected error %v", c.expr, err)
  45. }
  46. if actual != c.expected {
  47. t.Errorf("%s => expected %d, got %d", c.expr, c.expected, actual)
  48. }
  49. }
  50. }
  51. func TestField(t *testing.T) {
  52. fields := []struct {
  53. expr string
  54. min, max uint
  55. expected uint64
  56. }{
  57. {"5", 1, 7, 1 << 5},
  58. {"5,6", 1, 7, 1<<5 | 1<<6},
  59. {"5,6,7", 1, 7, 1<<5 | 1<<6 | 1<<7},
  60. {"1,5-7/2,3", 1, 7, 1<<1 | 1<<5 | 1<<7 | 1<<3},
  61. }
  62. for _, c := range fields {
  63. actual, _ := getField(c.expr, bounds{c.min, c.max, nil})
  64. if actual != c.expected {
  65. t.Errorf("%s => expected %d, got %d", c.expr, c.expected, actual)
  66. }
  67. }
  68. }
  69. func TestAll(t *testing.T) {
  70. allBits := []struct {
  71. r bounds
  72. expected uint64
  73. }{
  74. {minutes, 0xfffffffffffffff}, // 0-59: 60 ones
  75. {hours, 0xffffff}, // 0-23: 24 ones
  76. {dom, 0xfffffffe}, // 1-31: 31 ones, 1 zero
  77. {months, 0x1ffe}, // 1-12: 12 ones, 1 zero
  78. {dow, 0x7f}, // 0-6: 7 ones
  79. }
  80. for _, c := range allBits {
  81. actual := all(c.r) // all() adds the starBit, so compensate for that..
  82. if c.expected|starBit != actual {
  83. t.Errorf("%d-%d/%d => expected %b, got %b",
  84. c.r.min, c.r.max, 1, c.expected|starBit, actual)
  85. }
  86. }
  87. }
  88. func TestBits(t *testing.T) {
  89. bits := []struct {
  90. min, max, step uint
  91. expected uint64
  92. }{
  93. {0, 0, 1, 0x1},
  94. {1, 1, 1, 0x2},
  95. {1, 5, 2, 0x2a}, // 101010
  96. {1, 4, 2, 0xa}, // 1010
  97. }
  98. for _, c := range bits {
  99. actual := getBits(c.min, c.max, c.step)
  100. if c.expected != actual {
  101. t.Errorf("%d-%d/%d => expected %b, got %b",
  102. c.min, c.max, c.step, c.expected, actual)
  103. }
  104. }
  105. }
  106. func TestParseScheduleErrors(t *testing.T) {
  107. var tests = []struct{ expr, err string }{
  108. {"* 5 j * * *", "failed to parse int from"},
  109. {"@every Xm", "failed to parse duration"},
  110. {"@unrecognized", "unrecognized descriptor"},
  111. {"* * * *", "expected 5 to 6 fields"},
  112. {"", "empty spec string"},
  113. }
  114. for _, c := range tests {
  115. actual, err := secondParser.Parse(c.expr)
  116. if err == nil || !strings.Contains(err.Error(), c.err) {
  117. t.Errorf("%s => expected %v, got %v", c.expr, c.err, err)
  118. }
  119. if actual != nil {
  120. t.Errorf("expected nil schedule on error, got %v", actual)
  121. }
  122. }
  123. }
  124. func TestParseSchedule(t *testing.T) {
  125. tokyo, _ := time.LoadLocation("Asia/Tokyo")
  126. entries := []struct {
  127. parser Parser
  128. expr string
  129. expected Schedule
  130. }{
  131. {secondParser, "0 5 * * * *", every5min(time.Local)},
  132. {standardParser, "5 * * * *", every5min(time.Local)},
  133. {secondParser, "CRON_TZ=UTC 0 5 * * * *", every5min(time.UTC)},
  134. {standardParser, "CRON_TZ=UTC 5 * * * *", every5min(time.UTC)},
  135. {secondParser, "CRON_TZ=Asia/Tokyo 0 5 * * * *", every5min(tokyo)},
  136. {secondParser, "@every 5m", ConstantDelaySchedule{5 * time.Minute}},
  137. {secondParser, "@midnight", midnight(time.Local)},
  138. {secondParser, "TZ=UTC @midnight", midnight(time.UTC)},
  139. {secondParser, "TZ=Asia/Tokyo @midnight", midnight(tokyo)},
  140. {secondParser, "@yearly", annual(time.Local)},
  141. {secondParser, "@annually", annual(time.Local)},
  142. {
  143. parser: secondParser,
  144. expr: "* 5 * * * *",
  145. expected: &SpecSchedule{
  146. Second: all(seconds),
  147. Minute: 1 << 5,
  148. Hour: all(hours),
  149. Dom: all(dom),
  150. Month: all(months),
  151. Dow: all(dow),
  152. Location: time.Local,
  153. },
  154. },
  155. }
  156. for _, c := range entries {
  157. actual, err := c.parser.Parse(c.expr)
  158. if err != nil {
  159. t.Errorf("%s => unexpected error %v", c.expr, err)
  160. }
  161. if !reflect.DeepEqual(actual, c.expected) {
  162. t.Errorf("%s => expected %b, got %b", c.expr, c.expected, actual)
  163. }
  164. }
  165. }
  166. func TestOptionalSecondSchedule(t *testing.T) {
  167. parser := NewParser(SecondOptional | Minute | Hour | Dom | Month | Dow | Descriptor)
  168. entries := []struct {
  169. expr string
  170. expected Schedule
  171. }{
  172. {"0 5 * * * *", every5min(time.Local)},
  173. {"5 5 * * * *", every5min5s(time.Local)},
  174. {"5 * * * *", every5min(time.Local)},
  175. }
  176. for _, c := range entries {
  177. actual, err := parser.Parse(c.expr)
  178. if err != nil {
  179. t.Errorf("%s => unexpected error %v", c.expr, err)
  180. }
  181. if !reflect.DeepEqual(actual, c.expected) {
  182. t.Errorf("%s => expected %b, got %b", c.expr, c.expected, actual)
  183. }
  184. }
  185. }
  186. func TestNormalizeFields(t *testing.T) {
  187. tests := []struct {
  188. name string
  189. input []string
  190. options ParseOption
  191. expected []string
  192. }{
  193. {
  194. "AllFields_NoOptional",
  195. []string{"0", "5", "*", "*", "*", "*"},
  196. Second | Minute | Hour | Dom | Month | Dow | Descriptor,
  197. []string{"0", "5", "*", "*", "*", "*"},
  198. },
  199. {
  200. "AllFields_SecondOptional_Provided",
  201. []string{"0", "5", "*", "*", "*", "*"},
  202. SecondOptional | Minute | Hour | Dom | Month | Dow | Descriptor,
  203. []string{"0", "5", "*", "*", "*", "*"},
  204. },
  205. {
  206. "AllFields_SecondOptional_NotProvided",
  207. []string{"5", "*", "*", "*", "*"},
  208. SecondOptional | Minute | Hour | Dom | Month | Dow | Descriptor,
  209. []string{"0", "5", "*", "*", "*", "*"},
  210. },
  211. {
  212. "SubsetFields_NoOptional",
  213. []string{"5", "15", "*"},
  214. Hour | Dom | Month,
  215. []string{"0", "0", "5", "15", "*", "*"},
  216. },
  217. {
  218. "SubsetFields_DowOptional_Provided",
  219. []string{"5", "15", "*", "4"},
  220. Hour | Dom | Month | DowOptional,
  221. []string{"0", "0", "5", "15", "*", "4"},
  222. },
  223. {
  224. "SubsetFields_DowOptional_NotProvided",
  225. []string{"5", "15", "*"},
  226. Hour | Dom | Month | DowOptional,
  227. []string{"0", "0", "5", "15", "*", "*"},
  228. },
  229. {
  230. "SubsetFields_SecondOptional_NotProvided",
  231. []string{"5", "15", "*"},
  232. SecondOptional | Hour | Dom | Month,
  233. []string{"0", "0", "5", "15", "*", "*"},
  234. },
  235. }
  236. for _, test := range tests {
  237. t.Run(test.name, func(t *testing.T) {
  238. actual, err := normalizeFields(test.input, test.options)
  239. if err != nil {
  240. t.Errorf("unexpected error: %v", err)
  241. }
  242. if !reflect.DeepEqual(actual, test.expected) {
  243. t.Errorf("expected %v, got %v", test.expected, actual)
  244. }
  245. })
  246. }
  247. }
  248. func TestNormalizeFields_Errors(t *testing.T) {
  249. tests := []struct {
  250. name string
  251. input []string
  252. options ParseOption
  253. err string
  254. }{
  255. {
  256. "TwoOptionals",
  257. []string{"0", "5", "*", "*", "*", "*"},
  258. SecondOptional | Minute | Hour | Dom | Month | DowOptional,
  259. "",
  260. },
  261. {
  262. "TooManyFields",
  263. []string{"0", "5", "*", "*"},
  264. SecondOptional | Minute | Hour,
  265. "",
  266. },
  267. {
  268. "NoFields",
  269. []string{},
  270. SecondOptional | Minute | Hour,
  271. "",
  272. },
  273. {
  274. "TooFewFields",
  275. []string{"*"},
  276. SecondOptional | Minute | Hour,
  277. "",
  278. },
  279. }
  280. for _, test := range tests {
  281. t.Run(test.name, func(t *testing.T) {
  282. actual, err := normalizeFields(test.input, test.options)
  283. if err == nil {
  284. t.Errorf("expected an error, got none. results: %v", actual)
  285. }
  286. if !strings.Contains(err.Error(), test.err) {
  287. t.Errorf("expected error %q, got %q", test.err, err.Error())
  288. }
  289. })
  290. }
  291. }
  292. func TestStandardSpecSchedule(t *testing.T) {
  293. entries := []struct {
  294. expr string
  295. expected Schedule
  296. err string
  297. }{
  298. {
  299. expr: "5 * * * *",
  300. expected: &SpecSchedule{1 << seconds.min, 1 << 5, all(hours), all(dom), all(months), all(dow), time.Local},
  301. },
  302. {
  303. expr: "@every 5m",
  304. expected: ConstantDelaySchedule{time.Duration(5) * time.Minute},
  305. },
  306. {
  307. expr: "5 j * * *",
  308. err: "failed to parse int from",
  309. },
  310. {
  311. expr: "* * * *",
  312. err: "expected exactly 5 fields",
  313. },
  314. }
  315. for _, c := range entries {
  316. actual, err := ParseStandard(c.expr)
  317. if len(c.err) != 0 && (err == nil || !strings.Contains(err.Error(), c.err)) {
  318. t.Errorf("%s => expected %v, got %v", c.expr, c.err, err)
  319. }
  320. if len(c.err) == 0 && err != nil {
  321. t.Errorf("%s => unexpected error %v", c.expr, err)
  322. }
  323. if !reflect.DeepEqual(actual, c.expected) {
  324. t.Errorf("%s => expected %b, got %b", c.expr, c.expected, actual)
  325. }
  326. }
  327. }
  328. func TestNoDescriptorParser(t *testing.T) {
  329. parser := NewParser(Minute | Hour)
  330. _, err := parser.Parse("@every 1m")
  331. if err == nil {
  332. t.Error("expected an error, got none")
  333. }
  334. }
  335. func every5min(loc *time.Location) *SpecSchedule {
  336. return &SpecSchedule{1 << 0, 1 << 5, all(hours), all(dom), all(months), all(dow), loc}
  337. }
  338. func every5min5s(loc *time.Location) *SpecSchedule {
  339. return &SpecSchedule{1 << 5, 1 << 5, all(hours), all(dom), all(months), all(dow), loc}
  340. }
  341. func midnight(loc *time.Location) *SpecSchedule {
  342. return &SpecSchedule{1, 1, 1, all(dom), all(months), all(dow), loc}
  343. }
  344. func annual(loc *time.Location) *SpecSchedule {
  345. return &SpecSchedule{
  346. Second: 1 << seconds.min,
  347. Minute: 1 << minutes.min,
  348. Hour: 1 << hours.min,
  349. Dom: 1 << dom.min,
  350. Month: 1 << months.min,
  351. Dow: all(dow),
  352. Location: loc,
  353. }
  354. }