diff --git a/cron.go b/cron.go index 638da28..21e4c99 100644 --- a/cron.go +++ b/cron.go @@ -7,6 +7,11 @@ import ( "github.com/robfig/cron/v3" ) +type CronMeta interface { + Key() string + Hostname() string +} + type Cron struct { key string hostname string @@ -28,9 +33,20 @@ func NewCron(options ...CronOption) *Cron { func (c *Cron) AddJob(job Job) error { j := &innerJob{ - Job: job, - cron: c, + cron: c, + entryGetter: c.cron, + key: job.Key(), + spec: job.Spec(), + run: job.Run, + } + + for _, option := range job.Options() { + option(j) + } + if j.retryTimes < 1 { + j.retryTimes = 1 } + entryID, err := c.cron.AddJob(j.Spec(), j) if err != nil { return err diff --git a/cron_test.go b/cron_test.go index 05b6c51..b41e08c 100644 --- a/cron_test.go +++ b/cron_test.go @@ -1,43 +1,322 @@ package dcron import ( - "log" "testing" "time" "github.com/gochore/dcron/mock_dcron" "github.com/golang/mock/gomock" + "github.com/robfig/cron/v3" ) func Test_Cron(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mutex := mock_dcron.NewMockMutex(ctrl) + + c := NewCron(WithKey("test_cron"), WithMutex(mutex)) + mutex.EXPECT(). - SetIfNotExists(gomock.Any(), gomock.Any()). + SetIfNotExists(gomock.Any(), c.Hostname()). Return(true). - MinTimes(1) + Times(2) - cron := NewCron(WithKey("test_cron"), WithMutex(mutex)) - job, err := NewJob("test", "*/5 * * * * *", func() error { - log.Println("run") + job := NewJob("test", "*/5 * * * * *", func(task Task) error { + task.Value("") + select { + case <-task.Done(): + t.Logf("exit: %+v", task) + case <-time.After(time.Second): + t.Logf("run: %+v", task) + } return nil }, WithBeforeFunc(func(task Task) (skip bool) { - log.Println("before") - log.Printf("%+v", task) + t.Logf("before: %+v", task) return false }), WithAfterFunc(func(task Task) { - log.Println("after") - log.Printf("%+v", task) + t.Logf("after: %+v", task) })) - if err != nil { + if err := c.AddJob(job); err != nil { t.Fatal(err) } - if err := cron.AddJob(job); err != nil { - t.Fatal(err) - } - cron.Start() + c.Start() + c.Run() // should be not working time.Sleep(10 * time.Second) - <-cron.Stop().Done() + <-c.Stop().Done() +} + +func TestCron_AddJob(t *testing.T) { + c := cron.New(cron.WithSeconds()) + + type fields struct { + key string + hostname string + cron *cron.Cron + mutex Mutex + jobs []*innerJob + } + type args struct { + job Job + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "regular", + fields: fields{ + cron: c, + }, + args: args{ + job: NewJob("test_job", "* * * * * *", nil), + }, + wantErr: false, + }, + { + name: "with option", + fields: fields{ + cron: c, + }, + args: args{ + job: NewJob("test_job", "* * * * * *", nil, WithRetryTimes(3)), + }, + wantErr: false, + }, + { + name: "wrong spec", + fields: fields{ + cron: c, + }, + args: args{ + job: NewJob("test_job", "* * * * *", nil), + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Cron{ + key: tt.fields.key, + hostname: tt.fields.hostname, + cron: tt.fields.cron, + mutex: tt.fields.mutex, + jobs: tt.fields.jobs, + } + if err := c.AddJob(tt.args.job); (err != nil) != tt.wantErr { + t.Errorf("AddJob() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestCron_Hostname(t *testing.T) { + type fields struct { + key string + hostname string + cron *cron.Cron + mutex Mutex + jobs []*innerJob + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "regular", + fields: fields{ + hostname: "test_hostname", + }, + want: "test_hostname", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Cron{ + key: tt.fields.key, + hostname: tt.fields.hostname, + cron: tt.fields.cron, + mutex: tt.fields.mutex, + jobs: tt.fields.jobs, + } + if got := c.Hostname(); got != tt.want { + t.Errorf("Hostname() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCron_Key(t *testing.T) { + type fields struct { + key string + hostname string + cron *cron.Cron + mutex Mutex + jobs []*innerJob + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "regular", + fields: fields{ + key: "test_key", + }, + want: "test_key", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Cron{ + key: tt.fields.key, + hostname: tt.fields.hostname, + cron: tt.fields.cron, + mutex: tt.fields.mutex, + jobs: tt.fields.jobs, + } + if got := c.Key(); got != tt.want { + t.Errorf("Key() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNewCron(t *testing.T) { + type args struct { + options []CronOption + } + tests := []struct { + name string + args args + check func(t *testing.T, c *Cron) + }{ + { + name: "regular", + args: args{ + options: nil, + }, + check: func(t *testing.T, c *Cron) { + if c == nil { + t.Fatal(t) + } + }, + }, + { + name: "with_option", + args: args{ + options: []CronOption{WithKey("test_cron")}, + }, + check: func(t *testing.T, c *Cron) { + if c == nil { + t.Fatal(t) + } + if c.key != "test_cron" { + t.Fatal(c.key) + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := NewCron(tt.args.options...) + tt.check(t, got) + }) + } +} + +func TestWithHostname(t *testing.T) { + type args struct { + hostname string + } + tests := []struct { + name string + args args + check func(t *testing.T, option CronOption) + }{ + { + name: "regular", + args: args{ + hostname: "test_hostname", + }, + check: func(t *testing.T, option CronOption) { + c := NewCron() + option(c) + if c.hostname != "test_hostname" { + t.Fatal(c.hostname) + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := WithHostname(tt.args.hostname) + tt.check(t, got) + }) + } +} + +func TestWithKey(t *testing.T) { + type args struct { + key string + } + tests := []struct { + name string + args args + check func(t *testing.T, option CronOption) + }{ + { + name: "regular", + args: args{ + key: "test_cron", + }, + check: func(t *testing.T, option CronOption) { + c := NewCron() + option(c) + if c.key != "test_cron" { + t.Fatal(c.key) + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := WithKey(tt.args.key) + tt.check(t, got) + }) + } +} + +func TestWithMutex(t *testing.T) { + type args struct { + mutex Mutex + } + tests := []struct { + name string + args args + check func(t *testing.T, option CronOption) + }{ + { + name: "regular", + args: args{ + mutex: mock_dcron.NewMockMutex(nil), + }, + check: func(t *testing.T, option CronOption) { + c := NewCron() + option(c) + if c.mutex == nil { + t.Fatal(c.mutex) + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := WithMutex(tt.args.mutex) + tt.check(t, got) + }) + } } diff --git a/inner_job.go b/inner_job.go index d745d04..5503e3c 100644 --- a/inner_job.go +++ b/inner_job.go @@ -10,29 +10,50 @@ import ( ) type innerJob struct { - Job - cron *Cron - entryID cron.EntryID + cron *Cron + entryID cron.EntryID + entryGetter EntryGetter + key string + spec string + before BeforeFunc + run RunFunc + after AfterFunc + retryTimes int + retryInterval RetryInterval +} + +func (j *innerJob) Key() string { + return j.key +} + +func (j *innerJob) Spec() string { + return j.spec } func (j *innerJob) Run() { c := j.cron - entry := c.cron.Entry(j.entryID) + entry := j.entryGetter.Entry(j.entryID) planAt := entry.Prev nextAt := entry.Next - key := fmt.Sprintf("dcron:%s.%s@%d", c.key, j.Key(), planAt.Unix()) + key := fmt.Sprintf("dcron:%s.%s@%d", c.key, j.key, planAt.Unix()) task := Task{ - Key: key, - Cron: *c, - Job: j.Job, - PlanAt: planAt, + Key: key, + Cron: c, + Job: j, + PlanAt: planAt, + TriedTimes: 0, } var cancel context.CancelFunc - task.Context, cancel = context.WithDeadline(context.Background(), nextAt) + task.ctx, cancel = context.WithDeadline(context.Background(), nextAt) + defer cancel() + + skip := false + if j.before != nil && j.before(task) { + skip = true + } - skip := j.Before(task) if skip { task.Skipped = true } @@ -40,20 +61,42 @@ func (j *innerJob) Run() { if !task.Skipped { if j.cron.mutex == nil || j.cron.mutex.SetIfNotExists(task.Key, c.hostname) { task.BeginAt = pt.Time(time.Now()) - if err := j.Job.Run(); err != nil { - task.Return = err + + for i := 0; i < j.retryTimes; i++ { + task.Return = j.run(task) + task.TriedTimes++ + if task.Return == nil { + break + } + if task.Err() != nil { + break + } + if j.retryInterval != nil { + interval := j.retryInterval(task.TriedTimes) + deadline, _ := task.Deadline() + if -time.Since(deadline) < interval { + break + } + time.Sleep(interval) + } } + task.EndAt = pt.Time(time.Now()) } else { task.Missed = true } } - cancel() - - j.After(task) + if j.after != nil { + j.after(task) + } } func (j *innerJob) Cron() *Cron { return j.cron } + +//go:generate mockgen -source=inner_job.go -destination mock_dcron/inner_job.go +type EntryGetter interface { + Entry(id cron.EntryID) cron.Entry +} diff --git a/inner_job_test.go b/inner_job_test.go new file mode 100644 index 0000000..2d7609a --- /dev/null +++ b/inner_job_test.go @@ -0,0 +1,346 @@ +package dcron + +import ( + "errors" + "reflect" + "testing" + "time" + + "github.com/gochore/dcron/mock_dcron" + "github.com/golang/mock/gomock" + + "github.com/robfig/cron/v3" +) + +func Test_innerJob_Cron(t *testing.T) { + c := NewCron() + + type fields struct { + cron *Cron + entryID cron.EntryID + key string + spec string + before BeforeFunc + run RunFunc + after AfterFunc + retryTimes int + retryInterval RetryInterval + } + tests := []struct { + name string + fields fields + want *Cron + }{ + { + name: "regular", + fields: fields{ + cron: c, + }, + want: c, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + j := &innerJob{ + cron: tt.fields.cron, + entryID: tt.fields.entryID, + key: tt.fields.key, + spec: tt.fields.spec, + before: tt.fields.before, + run: tt.fields.run, + after: tt.fields.after, + retryTimes: tt.fields.retryTimes, + retryInterval: tt.fields.retryInterval, + } + if got := j.Cron(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Cron() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_innerJob_Key(t *testing.T) { + type fields struct { + cron *Cron + entryID cron.EntryID + key string + spec string + before BeforeFunc + run RunFunc + after AfterFunc + retryTimes int + retryInterval RetryInterval + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "regular", + fields: fields{ + key: "test_job", + }, + want: "test_job", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + j := &innerJob{ + cron: tt.fields.cron, + entryID: tt.fields.entryID, + key: tt.fields.key, + spec: tt.fields.spec, + before: tt.fields.before, + run: tt.fields.run, + after: tt.fields.after, + retryTimes: tt.fields.retryTimes, + retryInterval: tt.fields.retryInterval, + } + if got := j.Key(); got != tt.want { + t.Errorf("Key() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_innerJob_Run(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + entryGetter := mock_dcron.NewMockEntryGetter(ctrl) + mutex := mock_dcron.NewMockMutex(ctrl) + + entryGetter.EXPECT(). + Entry(gomock.Any()). + DoAndReturn(func(id cron.EntryID) cron.Entry { + now := time.Now() + return cron.Entry{ + ID: id, + Next: now.Add(time.Duration(id) * time.Second), + Prev: now, + } + }). + MinTimes(1) + + mutex.EXPECT(). + SetIfNotExists(gomock.Any(), gomock.Any()). + DoAndReturn(func(key, value string) bool { + return value != "always_miss" + }). + MinTimes(1) + + type fields struct { + cron *Cron + entryID cron.EntryID + entryGetter EntryGetter + key string + spec string + before BeforeFunc + run RunFunc + after AfterFunc + retryTimes int + retryInterval RetryInterval + } + tests := []struct { + name string + fields fields + }{ + { + name: "regular", + fields: fields{ + cron: NewCron(WithMutex(mutex)), + entryID: 1, + entryGetter: entryGetter, + before: func(task Task) (skip bool) { + return false + }, + run: func(task Task) error { + return nil + }, + after: func(task Task) { + if task.Return != nil { + t.Fatal(task.Return) + } + }, + retryTimes: 1, + }, + }, + { + name: "skip", + fields: fields{ + cron: NewCron(WithMutex(mutex)), + entryID: 1, + entryGetter: entryGetter, + before: func(task Task) (skip bool) { + return true + }, + run: func(task Task) error { + return nil + }, + after: func(task Task) { + if !task.Skipped { + t.Fatal(task.Skipped) + } + }, + retryTimes: 1, + }, + }, + { + name: "retry", + fields: fields{ + cron: NewCron(WithMutex(mutex)), + entryID: 5, + entryGetter: entryGetter, + before: func(task Task) (skip bool) { + return false + }, + run: func(task Task) error { + return errors.New("show retry") + }, + after: func(task Task) { + if task.Return == nil { + t.Fatal(task.Return) + } + if task.TriedTimes != 10 { + t.Fatal(task.TriedTimes) + } + }, + retryTimes: 10, + }, + }, + { + name: "retry with interval", + fields: fields{ + cron: NewCron(WithMutex(mutex)), + entryID: 5, + entryGetter: entryGetter, + before: func(task Task) (skip bool) { + return false + }, + run: func(task Task) error { + return errors.New("show retry") + }, + after: func(task Task) { + if task.Return == nil { + t.Fatal(task.Return) + } + if task.TriedTimes >= 10 { + t.Fatal(task.TriedTimes) + } + }, + retryTimes: 10, + retryInterval: func(triedTimes int) time.Duration { + return time.Duration(triedTimes) * time.Second + }, + }, + }, + { + name: "take too long", + fields: fields{ + cron: NewCron(WithMutex(mutex)), + entryID: 1, + entryGetter: entryGetter, + before: func(task Task) (skip bool) { + return false + }, + run: func(task Task) error { + time.Sleep(2 * time.Second) + return errors.New("show retry") + }, + after: func(task Task) { + if task.TriedTimes != 1 { + t.Fatal(task.TriedTimes) + } + if task.Return == nil { + t.Fatal(task.Return) + } + }, + retryTimes: 5, + retryInterval: nil, + }, + }, + { + name: "miss", + fields: fields{ + cron: NewCron(WithMutex(mutex), WithHostname("always_miss")), + entryID: 1, + entryGetter: entryGetter, + before: func(task Task) (skip bool) { + return false + }, + run: func(task Task) error { + return nil + }, + after: func(task Task) { + if !task.Missed { + t.Fatal(task.Missed) + } + }, + retryTimes: 1, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + j := &innerJob{ + cron: tt.fields.cron, + entryID: tt.fields.entryID, + entryGetter: tt.fields.entryGetter, + key: tt.fields.key, + spec: tt.fields.spec, + before: tt.fields.before, + run: tt.fields.run, + after: tt.fields.after, + retryTimes: tt.fields.retryTimes, + retryInterval: tt.fields.retryInterval, + } + j.Run() + }) + } +} + +func Test_innerJob_Spec(t *testing.T) { + type fields struct { + cron *Cron + entryID cron.EntryID + key string + spec string + before BeforeFunc + run RunFunc + after AfterFunc + retryTimes int + retryInterval RetryInterval + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "regular", + fields: fields{ + spec: "* * * * * *", + }, + want: "* * * * * *", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + j := &innerJob{ + cron: tt.fields.cron, + entryID: tt.fields.entryID, + key: tt.fields.key, + spec: tt.fields.spec, + before: tt.fields.before, + run: tt.fields.run, + after: tt.fields.after, + retryTimes: tt.fields.retryTimes, + retryInterval: tt.fields.retryInterval, + } + if got := j.Spec(); got != tt.want { + t.Errorf("Spec() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/job.go b/job.go index 489110f..7c121fa 100644 --- a/job.go +++ b/job.go @@ -1,84 +1,47 @@ package dcron -import ( - "fmt" -) - -type Job interface { +type JobMeta interface { Key() string Spec() string - Before(task Task) (skip bool) - Run() error - After(task Task) } -type BeforeFunc func(task Task) (skip bool) - -type RunFunc func() error - -type AfterFunc func(task Task) +type Job interface { + JobMeta + Run(task Task) error + Options() []JobOption +} -type wrapJob struct { - key string - spec string - before BeforeFunc - run RunFunc - after AfterFunc +type wrappedJob struct { + key string + spec string + run RunFunc + options []JobOption } -func NewJob(key, spec string, run RunFunc, options ...JobOption) (Job, error) { - if key == "" { - return nil, fmt.Errorf("empty key") - } - if run == nil { - return nil, fmt.Errorf("nil run") - } - ret := &wrapJob{ - key: key, - spec: spec, - run: run, +func NewJob(key, spec string, run RunFunc, options ...JobOption) Job { + return &wrappedJob{ + key: key, + spec: spec, + run: run, + options: options, } - for _, option := range options { - option(ret) - } - return ret, nil } -func (j *wrapJob) Key() string { +func (j *wrappedJob) Key() string { return j.key } -func (j *wrapJob) Spec() string { +func (j *wrappedJob) Spec() string { return j.spec } -func (j *wrapJob) Before(task Task) (skip bool) { - if j.before != nil { - return j.before(task) +func (j *wrappedJob) Run(task Task) error { + if j.run != nil { + return j.run(task) } - return false + return nil } -func (j *wrapJob) Run() error { - return j.run() -} - -func (j *wrapJob) After(task Task) { - if j.after != nil { - j.after(task) - } -} - -type JobOption func(job *wrapJob) - -func WithBeforeFunc(before BeforeFunc) JobOption { - return func(job *wrapJob) { - job.before = before - } -} - -func WithAfterFunc(after AfterFunc) JobOption { - return func(job *wrapJob) { - job.after = after - } +func (j *wrappedJob) Options() []JobOption { + return j.options } diff --git a/job_option.go b/job_option.go new file mode 100644 index 0000000..4cd9ba8 --- /dev/null +++ b/job_option.go @@ -0,0 +1,37 @@ +package dcron + +import "time" + +type JobOption func(job *innerJob) + +type BeforeFunc func(task Task) (skip bool) + +type RunFunc func(task Task) error + +type AfterFunc func(task Task) + +type RetryInterval func(triedTimes int) time.Duration + +func WithBeforeFunc(before BeforeFunc) JobOption { + return func(job *innerJob) { + job.before = before + } +} + +func WithAfterFunc(after AfterFunc) JobOption { + return func(job *innerJob) { + job.after = after + } +} + +func WithRetryTimes(retryTimes int) JobOption { + return func(job *innerJob) { + job.retryTimes = retryTimes + } +} + +func WithRetryInterval(retryInterval RetryInterval) JobOption { + return func(job *innerJob) { + job.retryInterval = retryInterval + } +} diff --git a/job_option_test.go b/job_option_test.go new file mode 100644 index 0000000..402f7d1 --- /dev/null +++ b/job_option_test.go @@ -0,0 +1,142 @@ +package dcron + +import ( + "fmt" + "testing" + "time" +) + +func TestWithAfterFunc(t *testing.T) { + after := func(task Task) { + + } + + type args struct { + after AfterFunc + } + tests := []struct { + name string + args args + check func(t *testing.T, option JobOption) + }{ + { + name: "regular", + args: args{ + after: after, + }, + check: func(t *testing.T, option JobOption) { + j := &innerJob{} + option(j) + if fmt.Sprintf("%p", j.after) != fmt.Sprintf("%p", after) { + t.Fatal() + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := WithAfterFunc(tt.args.after) + tt.check(t, got) + }) + } +} + +func TestWithBeforeFunc(t *testing.T) { + before := func(task Task) (skip bool) { + return false + } + + type args struct { + before BeforeFunc + } + tests := []struct { + name string + args args + check func(t *testing.T, option JobOption) + }{ + { + name: "regular", + args: args{ + before: before, + }, + check: func(t *testing.T, option JobOption) { + j := &innerJob{} + option(j) + if fmt.Sprintf("%p", j.before) != fmt.Sprintf("%p", before) { + t.Fatal() + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := WithBeforeFunc(tt.args.before) + tt.check(t, got) + }) + } +} + +func TestWithRetryInterval(t *testing.T) { + retryInterval := func(triedTimes int) time.Duration { + return time.Second + } + type args struct { + retryInterval RetryInterval + } + tests := []struct { + name string + args args + check func(t *testing.T, option JobOption) + }{ + { + name: "regular", + args: args{ + retryInterval: retryInterval, + }, + check: func(t *testing.T, option JobOption) { + j := &innerJob{} + option(j) + if fmt.Sprintf("%p", j.retryInterval) != fmt.Sprintf("%p", retryInterval) { + t.Fatal() + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := WithRetryInterval(tt.args.retryInterval) + tt.check(t, got) + }) + } +} + +func TestWithRetryTimes(t *testing.T) { + type args struct { + retryTimes int + } + tests := []struct { + name string + args args + check func(t *testing.T, option JobOption) + }{ + { + name: "regular", + args: args{ + retryTimes: 10, + }, + check: func(t *testing.T, option JobOption) { + j := &innerJob{} + option(j) + if j.retryTimes != 10 { + t.Fatal(j.retryTimes) + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := WithRetryTimes(tt.args.retryTimes) + tt.check(t, got) + }) + } +} diff --git a/job_test.go b/job_test.go new file mode 100644 index 0000000..8158c2b --- /dev/null +++ b/job_test.go @@ -0,0 +1,204 @@ +package dcron + +import ( + "reflect" + "testing" +) + +func TestNewJob(t *testing.T) { + type args struct { + key string + spec string + run RunFunc + options []JobOption + } + tests := []struct { + name string + args args + want Job + }{ + { + name: "regular", + args: args{ + key: "test_job", + spec: "* * * * * *", + run: nil, + options: nil, + }, + want: &wrappedJob{ + key: "test_job", + spec: "* * * * * *", + run: nil, + options: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewJob(tt.args.key, tt.args.spec, tt.args.run, tt.args.options...); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewJob() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_wrappedJob_Key(t *testing.T) { + type fields struct { + key string + spec string + run RunFunc + options []JobOption + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "regular", + fields: fields{ + key: "test_job", + }, + want: "test_job", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + j := &wrappedJob{ + key: tt.fields.key, + spec: tt.fields.spec, + run: tt.fields.run, + options: tt.fields.options, + } + if got := j.Key(); got != tt.want { + t.Errorf("Key() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_wrappedJob_Options(t *testing.T) { + options := []JobOption{WithRetryTimes(1)} + + type fields struct { + key string + spec string + run RunFunc + options []JobOption + } + tests := []struct { + name string + fields fields + want []JobOption + }{ + { + name: "regular", + fields: fields{ + options: options, + }, + want: options, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + j := &wrappedJob{ + key: tt.fields.key, + spec: tt.fields.spec, + run: tt.fields.run, + options: tt.fields.options, + } + if got := j.Options(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Options() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_wrappedJob_Run(t *testing.T) { + type fields struct { + key string + spec string + run RunFunc + options []JobOption + } + type args struct { + task Task + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "regular", + fields: fields{ + run: func(task Task) error { + return nil + }, + }, + args: args{ + task: Task{}, + }, + wantErr: false, + }, + { + name: "nil run", + fields: fields{ + run: nil, + }, + args: args{ + task: Task{}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + j := &wrappedJob{ + key: tt.fields.key, + spec: tt.fields.spec, + run: tt.fields.run, + options: tt.fields.options, + } + if err := j.Run(tt.args.task); (err != nil) != tt.wantErr { + t.Errorf("Run() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_wrappedJob_Spec(t *testing.T) { + type fields struct { + key string + spec string + run RunFunc + options []JobOption + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "regular", + fields: fields{ + spec: "* * * * * *", + }, + want: "* * * * * *", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + j := &wrappedJob{ + key: tt.fields.key, + spec: tt.fields.spec, + run: tt.fields.run, + options: tt.fields.options, + } + if got := j.Spec(); got != tt.want { + t.Errorf("Spec() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/mock_dcron/inner_job.go b/mock_dcron/inner_job.go new file mode 100644 index 0000000..76dfce3 --- /dev/null +++ b/mock_dcron/inner_job.go @@ -0,0 +1,48 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: inner_job.go + +// Package mock_dcron is a generated GoMock package. +package mock_dcron + +import ( + gomock "github.com/golang/mock/gomock" + cron "github.com/robfig/cron/v3" + reflect "reflect" +) + +// MockEntryGetter is a mock of EntryGetter interface +type MockEntryGetter struct { + ctrl *gomock.Controller + recorder *MockEntryGetterMockRecorder +} + +// MockEntryGetterMockRecorder is the mock recorder for MockEntryGetter +type MockEntryGetterMockRecorder struct { + mock *MockEntryGetter +} + +// NewMockEntryGetter creates a new mock instance +func NewMockEntryGetter(ctrl *gomock.Controller) *MockEntryGetter { + mock := &MockEntryGetter{ctrl: ctrl} + mock.recorder = &MockEntryGetterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockEntryGetter) EXPECT() *MockEntryGetterMockRecorder { + return m.recorder +} + +// Entry mocks base method +func (m *MockEntryGetter) Entry(id cron.EntryID) cron.Entry { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Entry", id) + ret0, _ := ret[0].(cron.Entry) + return ret0 +} + +// Entry indicates an expected call of Entry +func (mr *MockEntryGetterMockRecorder) Entry(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Entry", reflect.TypeOf((*MockEntryGetter)(nil).Entry), id) +} diff --git a/task.go b/task.go index 87297f0..359e1b3 100644 --- a/task.go +++ b/task.go @@ -6,14 +6,31 @@ import ( ) type Task struct { - context.Context - Key string - Cron Cron - Job Job - PlanAt time.Time - BeginAt *time.Time - EndAt *time.Time - Return error - Skipped bool - Missed bool + ctx context.Context + Key string + Cron CronMeta + Job JobMeta + PlanAt time.Time + BeginAt *time.Time + EndAt *time.Time + Return error + Skipped bool + Missed bool + TriedTimes int +} + +func (t Task) Deadline() (deadline time.Time, ok bool) { + return t.ctx.Deadline() +} + +func (t Task) Done() <-chan struct{} { + return t.ctx.Done() +} + +func (t Task) Err() error { + return t.ctx.Err() +} + +func (t Task) Value(key interface{}) interface{} { + return t.ctx.Value(key) }