|
|
@@ -7,6 +7,8 @@ import (
|
|
|
"time"
|
|
|
)
|
|
|
|
|
|
+var secondParser = NewParser(Second | Minute | Hour | Dom | Month | DowOptional | Descriptor)
|
|
|
+
|
|
|
func TestRange(t *testing.T) {
|
|
|
zero := uint64(0)
|
|
|
ranges := []struct {
|
|
|
@@ -30,11 +32,11 @@ func TestRange(t *testing.T) {
|
|
|
{"*", 1, 3, 1<<1 | 1<<2 | 1<<3 | starBit, ""},
|
|
|
{"*/2", 1, 3, 1<<1 | 1<<3 | starBit, ""},
|
|
|
|
|
|
- {"5--5", 0, 0, zero, "Too many hyphens"},
|
|
|
- {"jan-x", 0, 0, zero, "Failed to parse int from"},
|
|
|
- {"2-x", 1, 5, zero, "Failed to parse int from"},
|
|
|
- {"*/-12", 0, 0, zero, "Negative number"},
|
|
|
- {"*//2", 0, 0, zero, "Too many slashes"},
|
|
|
+ {"5--5", 0, 0, zero, "too many hyphens"},
|
|
|
+ {"jan-x", 0, 0, zero, "failed to parse int from"},
|
|
|
+ {"2-x", 1, 5, zero, "failed to parse int from"},
|
|
|
+ {"*/-12", 0, 0, zero, "negative number"},
|
|
|
+ {"*//2", 0, 0, zero, "too many slashes"},
|
|
|
{"1", 3, 5, zero, "below minimum"},
|
|
|
{"6", 3, 5, zero, "above maximum"},
|
|
|
{"5-3", 3, 5, zero, "beyond end of range"},
|
|
|
@@ -118,14 +120,14 @@ func TestBits(t *testing.T) {
|
|
|
|
|
|
func TestParseScheduleErrors(t *testing.T) {
|
|
|
var tests = []struct{ expr, err string }{
|
|
|
- {"* 5 j * * *", "Failed to parse int from"},
|
|
|
- {"@every Xm", "Failed to parse duration"},
|
|
|
- {"@unrecognized", "Unrecognized descriptor"},
|
|
|
- {"* * * *", "Expected 5 to 6 fields"},
|
|
|
- {"", "Empty spec string"},
|
|
|
+ {"* 5 j * * *", "failed to parse int from"},
|
|
|
+ {"@every Xm", "failed to parse duration"},
|
|
|
+ {"@unrecognized", "unrecognized descriptor"},
|
|
|
+ {"* * * *", "expected 5 to 6 fields"},
|
|
|
+ {"", "empty spec string"},
|
|
|
}
|
|
|
for _, c := range tests {
|
|
|
- actual, err := Parse(c.expr)
|
|
|
+ actual, err := secondParser.Parse(c.expr)
|
|
|
if err == nil || !strings.Contains(err.Error(), c.err) {
|
|
|
t.Errorf("%s => expected %v, got %v", c.expr, c.err, err)
|
|
|
}
|
|
|
@@ -138,23 +140,24 @@ func TestParseScheduleErrors(t *testing.T) {
|
|
|
func TestParseSchedule(t *testing.T) {
|
|
|
tokyo, _ := time.LoadLocation("Asia/Tokyo")
|
|
|
entries := []struct {
|
|
|
+ parser Parser
|
|
|
expr string
|
|
|
expected Schedule
|
|
|
}{
|
|
|
- {"0 5 * * * *", every5min(time.Local)},
|
|
|
- // Relied on the "optional seconds" behavior
|
|
|
- // {"5 * * * *", every5min(time.Local)},
|
|
|
- {"TZ=UTC 0 5 * * * *", every5min(time.UTC)},
|
|
|
- // {"TZ=UTC 5 * * * *", every5min(time.UTC)},
|
|
|
- {"TZ=Asia/Tokyo 0 5 * * * *", every5min(tokyo)},
|
|
|
- {"@every 5m", ConstantDelaySchedule{5 * time.Minute}},
|
|
|
- {"@midnight", midnight(time.Local)},
|
|
|
- {"TZ=UTC @midnight", midnight(time.UTC)},
|
|
|
- {"TZ=Asia/Tokyo @midnight", midnight(tokyo)},
|
|
|
- {"@yearly", annual(time.Local)},
|
|
|
- {"@annually", annual(time.Local)},
|
|
|
- {
|
|
|
- expr: "* 5 * * * *",
|
|
|
+ {secondParser, "0 5 * * * *", every5min(time.Local)},
|
|
|
+ {standardParser, "5 * * * *", every5min(time.Local)},
|
|
|
+ {secondParser, "TZ=UTC 0 5 * * * *", every5min(time.UTC)},
|
|
|
+ {standardParser, "TZ=UTC 5 * * * *", every5min(time.UTC)},
|
|
|
+ {secondParser, "TZ=Asia/Tokyo 0 5 * * * *", every5min(tokyo)},
|
|
|
+ {secondParser, "@every 5m", ConstantDelaySchedule{5 * time.Minute}},
|
|
|
+ {secondParser, "@midnight", midnight(time.Local)},
|
|
|
+ {secondParser, "TZ=UTC @midnight", midnight(time.UTC)},
|
|
|
+ {secondParser, "TZ=Asia/Tokyo @midnight", midnight(tokyo)},
|
|
|
+ {secondParser, "@yearly", annual(time.Local)},
|
|
|
+ {secondParser, "@annually", annual(time.Local)},
|
|
|
+ {
|
|
|
+ parser: secondParser,
|
|
|
+ expr: "* 5 * * * *",
|
|
|
expected: &SpecSchedule{
|
|
|
Second: all(seconds),
|
|
|
Minute: 1 << 5,
|
|
|
@@ -168,7 +171,29 @@ func TestParseSchedule(t *testing.T) {
|
|
|
}
|
|
|
|
|
|
for _, c := range entries {
|
|
|
- actual, err := Parse(c.expr)
|
|
|
+ actual, err := c.parser.Parse(c.expr)
|
|
|
+ if err != nil {
|
|
|
+ t.Errorf("%s => unexpected error %v", c.expr, err)
|
|
|
+ }
|
|
|
+ if !reflect.DeepEqual(actual, c.expected) {
|
|
|
+ t.Errorf("%s => expected %b, got %b", c.expr, c.expected, actual)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestOptionalSecondSchedule(t *testing.T) {
|
|
|
+ parser := NewParser(SecondOptional | Minute | Hour | Dom | Month | Dow | Descriptor)
|
|
|
+ entries := []struct {
|
|
|
+ expr string
|
|
|
+ expected Schedule
|
|
|
+ }{
|
|
|
+ {"0 5 * * * *", every5min(time.Local)},
|
|
|
+ {"5 5 * * * *", every5min5s(time.Local)},
|
|
|
+ {"5 * * * *", every5min(time.Local)},
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, c := range entries {
|
|
|
+ actual, err := parser.Parse(c.expr)
|
|
|
if err != nil {
|
|
|
t.Errorf("%s => unexpected error %v", c.expr, err)
|
|
|
}
|
|
|
@@ -178,6 +203,115 @@ func TestParseSchedule(t *testing.T) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+func TestNormalizeFields(t *testing.T) {
|
|
|
+ tests := []struct {
|
|
|
+ name string
|
|
|
+ input []string
|
|
|
+ options ParseOption
|
|
|
+ expected []string
|
|
|
+ }{
|
|
|
+ {
|
|
|
+ "AllFields_NoOptional",
|
|
|
+ []string{"0", "5", "*", "*", "*", "*"},
|
|
|
+ Second | Minute | Hour | Dom | Month | Dow | Descriptor,
|
|
|
+ []string{"0", "5", "*", "*", "*", "*"},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "AllFields_SecondOptional_Provided",
|
|
|
+ []string{"0", "5", "*", "*", "*", "*"},
|
|
|
+ SecondOptional | Minute | Hour | Dom | Month | Dow | Descriptor,
|
|
|
+ []string{"0", "5", "*", "*", "*", "*"},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "AllFields_SecondOptional_NotProvided",
|
|
|
+ []string{"5", "*", "*", "*", "*"},
|
|
|
+ SecondOptional | Minute | Hour | Dom | Month | Dow | Descriptor,
|
|
|
+ []string{"0", "5", "*", "*", "*", "*"},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "SubsetFields_NoOptional",
|
|
|
+ []string{"5", "15", "*"},
|
|
|
+ Hour | Dom | Month,
|
|
|
+ []string{"0", "0", "5", "15", "*", "*"},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "SubsetFields_DowOptional_Provided",
|
|
|
+ []string{"5", "15", "*", "4"},
|
|
|
+ Hour | Dom | Month | DowOptional,
|
|
|
+ []string{"0", "0", "5", "15", "*", "4"},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "SubsetFields_DowOptional_NotProvided",
|
|
|
+ []string{"5", "15", "*"},
|
|
|
+ Hour | Dom | Month | DowOptional,
|
|
|
+ []string{"0", "0", "5", "15", "*", "*"},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "SubsetFields_SecondOptional_NotProvided",
|
|
|
+ []string{"5", "15", "*"},
|
|
|
+ SecondOptional | Hour | Dom | Month,
|
|
|
+ []string{"0", "0", "5", "15", "*", "*"},
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, test := range tests {
|
|
|
+ t.Run(test.name, func(t *testing.T) {
|
|
|
+ actual, err := normalizeFields(test.input, test.options)
|
|
|
+ if err != nil {
|
|
|
+ t.Errorf("unexpected error: %v", err)
|
|
|
+ }
|
|
|
+ if !reflect.DeepEqual(actual, test.expected) {
|
|
|
+ t.Errorf("expected %v, got %v", test.expected, actual)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestNormalizeFields_Errors(t *testing.T) {
|
|
|
+ tests := []struct {
|
|
|
+ name string
|
|
|
+ input []string
|
|
|
+ options ParseOption
|
|
|
+ err string
|
|
|
+ }{
|
|
|
+ {
|
|
|
+ "TwoOptionals",
|
|
|
+ []string{"0", "5", "*", "*", "*", "*"},
|
|
|
+ SecondOptional | Minute | Hour | Dom | Month | DowOptional,
|
|
|
+ "",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "TooManyFields",
|
|
|
+ []string{"0", "5", "*", "*"},
|
|
|
+ SecondOptional | Minute | Hour,
|
|
|
+ "",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "NoFields",
|
|
|
+ []string{},
|
|
|
+ SecondOptional | Minute | Hour,
|
|
|
+ "",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "TooFewFields",
|
|
|
+ []string{"*"},
|
|
|
+ SecondOptional | Minute | Hour,
|
|
|
+ "",
|
|
|
+ },
|
|
|
+ }
|
|
|
+ for _, test := range tests {
|
|
|
+ t.Run(test.name, func(t *testing.T) {
|
|
|
+ actual, err := normalizeFields(test.input, test.options)
|
|
|
+ if err == nil {
|
|
|
+ t.Errorf("expected an error, got none. results: %v", actual)
|
|
|
+ }
|
|
|
+ if !strings.Contains(err.Error(), test.err) {
|
|
|
+ t.Errorf("expected error %q, got %q", test.err, err.Error())
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
func TestStandardSpecSchedule(t *testing.T) {
|
|
|
entries := []struct {
|
|
|
expr string
|
|
|
@@ -194,11 +328,11 @@ func TestStandardSpecSchedule(t *testing.T) {
|
|
|
},
|
|
|
{
|
|
|
expr: "5 j * * *",
|
|
|
- err: "Failed to parse int from",
|
|
|
+ err: "failed to parse int from",
|
|
|
},
|
|
|
{
|
|
|
expr: "* * * *",
|
|
|
- err: "Expected exactly 5 fields",
|
|
|
+ err: "expected exactly 5 fields",
|
|
|
},
|
|
|
}
|
|
|
|
|
|
@@ -220,6 +354,10 @@ func every5min(loc *time.Location) *SpecSchedule {
|
|
|
return &SpecSchedule{1 << 0, 1 << 5, all(hours), all(dom), all(months), all(dow), loc}
|
|
|
}
|
|
|
|
|
|
+func every5min5s(loc *time.Location) *SpecSchedule {
|
|
|
+ return &SpecSchedule{1 << 5, 1 << 5, all(hours), all(dom), all(months), all(dow), loc}
|
|
|
+}
|
|
|
+
|
|
|
func midnight(loc *time.Location) *SpecSchedule {
|
|
|
return &SpecSchedule{1, 1, 1, all(dom), all(months), all(dow), loc}
|
|
|
}
|