added Cron.Jobs() method
This commit is contained in:
parent
f27d9f1dc9
commit
bae5421d62
|
@ -11,21 +11,17 @@ package cron
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type job struct {
|
|
||||||
schedule *Schedule
|
|
||||||
run func()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cron is a crontab-like struct for tasks/jobs scheduling.
|
// Cron is a crontab-like struct for tasks/jobs scheduling.
|
||||||
type Cron struct {
|
type Cron struct {
|
||||||
timezone *time.Location
|
timezone *time.Location
|
||||||
ticker *time.Ticker
|
ticker *time.Ticker
|
||||||
startTimer *time.Timer
|
startTimer *time.Timer
|
||||||
jobs map[string]*job
|
jobs []*Job
|
||||||
tickerDone chan bool
|
tickerDone chan bool
|
||||||
interval time.Duration
|
interval time.Duration
|
||||||
mux sync.RWMutex
|
mux sync.RWMutex
|
||||||
|
@ -40,7 +36,7 @@ func New() *Cron {
|
||||||
return &Cron{
|
return &Cron{
|
||||||
interval: 1 * time.Minute,
|
interval: 1 * time.Minute,
|
||||||
timezone: time.UTC,
|
timezone: time.UTC,
|
||||||
jobs: map[string]*job{},
|
jobs: []*Job{},
|
||||||
tickerDone: make(chan bool),
|
tickerDone: make(chan bool),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,23 +78,30 @@ func (c *Cron) MustAdd(jobId string, cronExpr string, run func()) {
|
||||||
//
|
//
|
||||||
// cronExpr is a regular cron expression, eg. "0 */3 * * *" (aka. at minute 0 past every 3rd hour).
|
// cronExpr is a regular cron expression, eg. "0 */3 * * *" (aka. at minute 0 past every 3rd hour).
|
||||||
// Check cron.NewSchedule() for the supported tokens.
|
// Check cron.NewSchedule() for the supported tokens.
|
||||||
func (c *Cron) Add(jobId string, cronExpr string, run func()) error {
|
func (c *Cron) Add(jobId string, cronExpr string, fn func()) error {
|
||||||
if run == nil {
|
if fn == nil {
|
||||||
return errors.New("failed to add new cron job: run must be non-nil function")
|
return errors.New("failed to add new cron job: fn must be non-nil function")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.mux.Lock()
|
|
||||||
defer c.mux.Unlock()
|
|
||||||
|
|
||||||
schedule, err := NewSchedule(cronExpr)
|
schedule, err := NewSchedule(cronExpr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to add new cron job: %w", err)
|
return fmt.Errorf("failed to add new cron job: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.jobs[jobId] = &job{
|
c.mux.Lock()
|
||||||
|
defer c.mux.Unlock()
|
||||||
|
|
||||||
|
// remove previous (if any)
|
||||||
|
c.jobs = slices.DeleteFunc(c.jobs, func(j *Job) bool {
|
||||||
|
return j.Id() == jobId
|
||||||
|
})
|
||||||
|
|
||||||
|
// add new
|
||||||
|
c.jobs = append(c.jobs, &Job{
|
||||||
|
id: jobId,
|
||||||
|
fn: fn,
|
||||||
schedule: schedule,
|
schedule: schedule,
|
||||||
run: run,
|
})
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -108,7 +111,13 @@ func (c *Cron) Remove(jobId string) {
|
||||||
c.mux.Lock()
|
c.mux.Lock()
|
||||||
defer c.mux.Unlock()
|
defer c.mux.Unlock()
|
||||||
|
|
||||||
delete(c.jobs, jobId)
|
if c.jobs == nil {
|
||||||
|
return // nothing to remove
|
||||||
|
}
|
||||||
|
|
||||||
|
c.jobs = slices.DeleteFunc(c.jobs, func(j *Job) bool {
|
||||||
|
return j.Id() == jobId
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveAll removes all registered cron jobs.
|
// RemoveAll removes all registered cron jobs.
|
||||||
|
@ -116,7 +125,7 @@ func (c *Cron) RemoveAll() {
|
||||||
c.mux.Lock()
|
c.mux.Lock()
|
||||||
defer c.mux.Unlock()
|
defer c.mux.Unlock()
|
||||||
|
|
||||||
c.jobs = map[string]*job{}
|
c.jobs = []*Job{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Total returns the current total number of registered cron jobs.
|
// Total returns the current total number of registered cron jobs.
|
||||||
|
@ -127,6 +136,19 @@ func (c *Cron) Total() int {
|
||||||
return len(c.jobs)
|
return len(c.jobs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Jobs returns a shallow copy of the currently registered cron jobs.
|
||||||
|
func (c *Cron) Jobs() []*Job {
|
||||||
|
c.mux.RLock()
|
||||||
|
defer c.mux.RUnlock()
|
||||||
|
|
||||||
|
copy := make([]*Job, len(c.jobs))
|
||||||
|
for i, j := range c.jobs {
|
||||||
|
copy[i] = j
|
||||||
|
}
|
||||||
|
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
|
||||||
// Stop stops the current cron ticker (if not already).
|
// Stop stops the current cron ticker (if not already).
|
||||||
//
|
//
|
||||||
// You can resume the ticker by calling Start().
|
// You can resume the ticker by calling Start().
|
||||||
|
@ -200,7 +222,7 @@ func (c *Cron) runDue(t time.Time) {
|
||||||
|
|
||||||
for _, j := range c.jobs {
|
for _, j := range c.jobs {
|
||||||
if j.schedule.IsDue(moment) {
|
if j.schedule.IsDue(moment) {
|
||||||
go j.run()
|
go j.Run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package cron
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -98,6 +99,11 @@ func TestCronAddAndRemove(t *testing.T) {
|
||||||
// try to remove non-existing (should be no-op)
|
// try to remove non-existing (should be no-op)
|
||||||
c.Remove("missing")
|
c.Remove("missing")
|
||||||
|
|
||||||
|
indexedJobs := make(map[string]*Job, len(c.jobs))
|
||||||
|
for _, j := range c.jobs {
|
||||||
|
indexedJobs[j.Id()] = j
|
||||||
|
}
|
||||||
|
|
||||||
// check job keys
|
// check job keys
|
||||||
{
|
{
|
||||||
expectedKeys := []string{"test3", "test2", "test5"}
|
expectedKeys := []string{"test3", "test2", "test5"}
|
||||||
|
@ -107,7 +113,7 @@ func TestCronAddAndRemove(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, k := range expectedKeys {
|
for _, k := range expectedKeys {
|
||||||
if c.jobs[k] == nil {
|
if indexedJobs[k] == nil {
|
||||||
t.Fatalf("Expected job with key %s, got nil", k)
|
t.Fatalf("Expected job with key %s, got nil", k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,7 +127,7 @@ func TestCronAddAndRemove(t *testing.T) {
|
||||||
"test5": `{"minutes":{"1":{}},"hours":{"2":{}},"days":{"3":{}},"months":{"4":{}},"daysOfWeek":{"5":{}}}`,
|
"test5": `{"minutes":{"1":{}},"hours":{"2":{}},"days":{"3":{}},"months":{"4":{}},"daysOfWeek":{"5":{}}}`,
|
||||||
}
|
}
|
||||||
for k, v := range expectedSchedules {
|
for k, v := range expectedSchedules {
|
||||||
raw, err := json.Marshal(c.jobs[k].schedule)
|
raw, err := json.Marshal(indexedJobs[k].schedule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -148,7 +154,7 @@ func TestCronMustAdd(t *testing.T) {
|
||||||
|
|
||||||
c.MustAdd("test2", "* * * * *", func() {})
|
c.MustAdd("test2", "* * * * *", func() {})
|
||||||
|
|
||||||
if _, ok := c.jobs["test2"]; !ok {
|
if !slices.ContainsFunc(c.jobs, func(j *Job) bool { return j.Id() == "test2" }) {
|
||||||
t.Fatal("Couldn't find job test2")
|
t.Fatal("Couldn't find job test2")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -208,6 +214,42 @@ func TestCronTotal(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCronJobs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
c := New()
|
||||||
|
|
||||||
|
calls := ""
|
||||||
|
|
||||||
|
if err := c.Add("a", "1 * * * *", func() { calls += "a" }); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Add("b", "2 * * * *", func() { calls += "b" }); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overwrite
|
||||||
|
if err := c.Add("b", "3 * * * *", func() { calls += "b" }); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
jobs := c.Jobs()
|
||||||
|
|
||||||
|
if len(jobs) != 2 {
|
||||||
|
t.Fatalf("Expected 2 jobs, got %v", len(jobs))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, j := range jobs {
|
||||||
|
j.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedCalls := "ab"
|
||||||
|
if calls != expectedCalls {
|
||||||
|
t.Fatalf("Expected %q calls, got %q", expectedCalls, calls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCronStartStop(t *testing.T) {
|
func TestCronStartStop(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package cron
|
||||||
|
|
||||||
|
// Job defines a single registered cron job.
|
||||||
|
type Job struct {
|
||||||
|
fn func()
|
||||||
|
schedule *Schedule
|
||||||
|
id string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Id returns the cron job id.
|
||||||
|
func (j *Job) Id() string {
|
||||||
|
return j.id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expr returns the plain cron job schedule expression.
|
||||||
|
func (j *Job) Expr() string {
|
||||||
|
return j.schedule.rawExpr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run runs the cron job function.
|
||||||
|
func (j *Job) Run() {
|
||||||
|
if j.fn != nil {
|
||||||
|
j.fn()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package cron
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestJobId(t *testing.T) {
|
||||||
|
expected := "test"
|
||||||
|
|
||||||
|
j := Job{id: expected}
|
||||||
|
|
||||||
|
if j.Id() != expected {
|
||||||
|
t.Fatalf("Expected job with id %q, got %q", expected, j.Id())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJobExpr(t *testing.T) {
|
||||||
|
expected := "1 2 3 4 5"
|
||||||
|
|
||||||
|
s, err := NewSchedule(expected)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
j := Job{schedule: s}
|
||||||
|
|
||||||
|
if j.Expr() != expected {
|
||||||
|
t.Fatalf("Expected job with cron expression %q, got %q", expected, j.Expr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJobRun(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
t.Errorf("Shouldn't panic: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
calls := ""
|
||||||
|
|
||||||
|
j1 := Job{}
|
||||||
|
j2 := Job{fn: func() { calls += "2" }}
|
||||||
|
|
||||||
|
j1.Run()
|
||||||
|
j2.Run()
|
||||||
|
|
||||||
|
expected := "2"
|
||||||
|
if calls != expected {
|
||||||
|
t.Fatalf("Expected calls %q, got %q", expected, calls)
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,6 +35,8 @@ type Schedule struct {
|
||||||
Days map[int]struct{} `json:"days"`
|
Days map[int]struct{} `json:"days"`
|
||||||
Months map[int]struct{} `json:"months"`
|
Months map[int]struct{} `json:"months"`
|
||||||
DaysOfWeek map[int]struct{} `json:"daysOfWeek"`
|
DaysOfWeek map[int]struct{} `json:"daysOfWeek"`
|
||||||
|
|
||||||
|
rawExpr string
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDue checks whether the provided Moment satisfies the current Schedule.
|
// IsDue checks whether the provided Moment satisfies the current Schedule.
|
||||||
|
@ -130,6 +132,7 @@ func NewSchedule(cronExpr string) (*Schedule, error) {
|
||||||
Days: days,
|
Days: days,
|
||||||
Months: months,
|
Months: months,
|
||||||
DaysOfWeek: daysOfWeek,
|
DaysOfWeek: daysOfWeek,
|
||||||
|
rawExpr: cronExpr,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue