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