Skip to content

Commit f2c3314

Browse files
committed
Return an error rather than panicking on invalid cron specs
1 parent 4832237 commit f2c3314

File tree

6 files changed

+57
-14
lines changed

6 files changed

+57
-14
lines changed

constantdelay.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@ type ConstantDelaySchedule struct {
99
}
1010

1111
// Every returns a crontab Schedule that activates once every duration.
12-
// Delays of less than a second are not supported (will panic).
12+
// Delays of less than a second are not supported (will round up to 1 second).
1313
// Any fields less than a Second are truncated.
1414
func Every(duration time.Duration) ConstantDelaySchedule {
1515
if duration < time.Second {
16-
panic("cron/constantdelay: delays of less than a second are not supported: " +
17-
duration.String())
16+
duration = time.Second
1817
}
1918
return ConstantDelaySchedule{
2019
Delay: duration - time.Duration(duration.Nanoseconds())%time.Second,

constantdelay_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ func TestConstantDelayNext(t *testing.T) {
3434
// Round to nearest second on the delay
3535
{"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
3636

37+
// Round up to 1 second if the duration is less.
38+
{"Mon Jul 9 14:45:00 2012", 15 * time.Millisecond, "Mon Jul 9 14:45:01 2012"},
39+
3740
// Round to nearest second when calculating the next time.
3841
{"Mon Jul 9 14:45:00.005 2012", 15 * time.Minute, "Mon Jul 9 15:00 2012"},
3942

cron.go

+9-4
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,18 @@ type FuncJob func()
8383
func (f FuncJob) Run() { f() }
8484

8585
// AddFunc adds a func to the Cron to be run on the given schedule.
86-
func (c *Cron) AddFunc(spec string, cmd func()) {
87-
c.AddJob(spec, FuncJob(cmd))
86+
func (c *Cron) AddFunc(spec string, cmd func()) error {
87+
return c.AddJob(spec, FuncJob(cmd))
8888
}
8989

9090
// AddFunc adds a Job to the Cron to be run on the given schedule.
91-
func (c *Cron) AddJob(spec string, cmd Job) {
92-
c.Schedule(Parse(spec), cmd)
91+
func (c *Cron) AddJob(spec string, cmd Job) error {
92+
schedule, err := Parse(spec)
93+
if err != nil {
94+
return err
95+
}
96+
c.Schedule(schedule, cmd)
97+
return nil
9398
}
9499

95100
// Schedule adds a Job to the Cron to be run on the given schedule.

parser.go

+12-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cron
22

33
import (
4+
"fmt"
45
"log"
56
"math"
67
"strconv"
@@ -9,14 +10,21 @@ import (
910
)
1011

1112
// Parse returns a new crontab schedule representing the given spec.
12-
// It panics with a descriptive error if the spec is not valid.
13+
// It returns a descriptive error if the spec is not valid.
1314
//
1415
// It accepts
1516
// - Full crontab specs, e.g. "* * * * * ?"
1617
// - Descriptors, e.g. "@midnight", "@every 1h30m"
17-
func Parse(spec string) Schedule {
18+
func Parse(spec string) (_ Schedule, err error) {
19+
// Convert panics into errors
20+
defer func() {
21+
if recovered := recover(); recovered != nil {
22+
err = fmt.Errorf("%v", recovered)
23+
}
24+
}()
25+
1826
if spec[0] == '@' {
19-
return parseDescriptor(spec)
27+
return parseDescriptor(spec), nil
2028
}
2129

2230
// Split on whitespace. We require 5 or 6 fields.
@@ -40,7 +48,7 @@ func Parse(spec string) Schedule {
4048
Dow: getField(fields[5], dow),
4149
}
4250

43-
return schedule
51+
return schedule, nil
4452
}
4553

4654
// getField returns an Int with the bits set representing all of the times that

parser_test.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,10 @@ func TestSpecSchedule(t *testing.T) {
106106
}
107107

108108
for _, c := range entries {
109-
actual := Parse(c.expr)
109+
actual, err := Parse(c.expr)
110+
if err != nil {
111+
t.Error(err)
112+
}
110113
if !reflect.DeepEqual(actual, c.expected) {
111114
t.Errorf("%s => (expected) %b != %b (actual)", c.expr, c.expected, actual)
112115
}

spec_test.go

+27-2
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,12 @@ func TestActivation(t *testing.T) {
5656
}
5757

5858
for _, test := range tests {
59-
actual := Parse(test.spec).Next(getTime(test.time).Add(-1 * time.Second))
59+
sched, err := Parse(test.spec)
60+
if err != nil {
61+
t.Error(err)
62+
continue
63+
}
64+
actual := sched.Next(getTime(test.time).Add(-1 * time.Second))
6065
expected := getTime(test.time)
6166
if test.expected && expected != actual || !test.expected && expected == actual {
6267
t.Errorf("Fail evaluating %s on %s: (expected) %s != %s (actual)",
@@ -117,14 +122,34 @@ func TestNext(t *testing.T) {
117122
}
118123

119124
for _, c := range runs {
120-
actual := Parse(c.spec).Next(getTime(c.time))
125+
sched, err := Parse(c.spec)
126+
if err != nil {
127+
t.Error(err)
128+
continue
129+
}
130+
actual := sched.Next(getTime(c.time))
121131
expected := getTime(c.expected)
122132
if !actual.Equal(expected) {
123133
t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.spec, expected, actual)
124134
}
125135
}
126136
}
127137

138+
func TestErrors(t *testing.T) {
139+
invalidSpecs := []string{
140+
"xyz",
141+
"60 0 * * *",
142+
"0 60 * * *",
143+
"0 0 * * XYZ",
144+
}
145+
for _, spec := range invalidSpecs {
146+
_, err := Parse(spec)
147+
if err == nil {
148+
t.Error("expected an error parsing: ", spec)
149+
}
150+
}
151+
}
152+
128153
func getTime(value string) time.Time {
129154
if value == "" {
130155
return time.Time{}

0 commit comments

Comments
 (0)