Skip to content

Commit

Permalink
code refactor and add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mayswind committed Aug 12, 2024
1 parent d648226 commit c2757f6
Show file tree
Hide file tree
Showing 7 changed files with 362 additions and 19 deletions.
2 changes: 1 addition & 1 deletion cmd/cron_jobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func listAllCronJobs(c *cli.Context) error {

fmt.Printf("[Name] %s\n", cronJob.Name)
fmt.Printf("[Description] %s\n", cronJob.Description)
fmt.Printf("[Interval] Every %s\n", cronJob.Interval)
fmt.Printf("[Interval] Every %s\n", cronJob.Period.GetInterval())
}

return nil
Expand Down
2 changes: 1 addition & 1 deletion pkg/cron/cron_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func (c *CronJobSchedulerContainer) registerAllJobs(config *settings.Config) {

func (c *CronJobSchedulerContainer) registerIntervalJob(job *CronJob) {
gocronJob, err := c.scheduler.NewJob(
gocron.DurationJob(job.Interval),
job.Period.ToJobDefinition(),
gocron.NewTask(job.doRun),
gocron.WithName(job.Name),
gocron.WithSingletonMode(gocron.LimitModeReschedule),
Expand Down
138 changes: 138 additions & 0 deletions pkg/cron/cron_container_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package cron

import (
"sync/atomic"
"testing"
"time"

"github.com/go-co-op/gocron/v2"
"github.com/stretchr/testify/assert"

"github.com/mayswind/ezbookkeeping/pkg/duplicatechecker"
"github.com/mayswind/ezbookkeeping/pkg/settings"
)

func TestCronJobSchedulerContainerRegisterIntervalJob(t *testing.T) {
var err error

container := &CronJobSchedulerContainer{
allJobsMap: make(map[string]*CronJob),
allGocronJobsMap: make(map[string]gocron.Job),
}

container.scheduler, err = gocron.NewScheduler(
gocron.WithLocation(time.Local),
gocron.WithLogger(NewGocronLoggerAdapter()),
)
assert.Nil(t, err)

actualValue := false
job := &CronJob{
Name: "TestRegisterIntervalJob",
Description: "The test cron job",
Period: CronJobIntervalPeriod{
Interval: 1 * time.Second,
},
Run: func() error {
actualValue = true
return nil
},
}

container.registerIntervalJob(job)
container.scheduler.Start()

assert.Equal(t, 1, len(container.GetAllJobs()))
assert.Equal(t, job, container.GetAllJobs()[0])

time.Sleep(2 * time.Second)
assert.True(t, actualValue)

err = container.scheduler.Shutdown()
assert.Nil(t, err)
}

func TestCronJobSchedulerContainerSyncRunJobNow(t *testing.T) {
var err error

container := &CronJobSchedulerContainer{
allJobsMap: make(map[string]*CronJob),
allGocronJobsMap: make(map[string]gocron.Job),
}

container.scheduler, err = gocron.NewScheduler(
gocron.WithLocation(time.Local),
gocron.WithLogger(NewGocronLoggerAdapter()),
)
assert.Nil(t, err)

actualValue := false
job := &CronJob{
Name: "TestSyncRunJob",
Description: "The test cron job",
Period: CronJobIntervalPeriod{
Interval: 24 * time.Hour,
},
Run: func() error {
actualValue = true
return nil
},
}

container.registerIntervalJob(job)

err = container.SyncRunJobNow("TestSyncRunJob")
assert.Nil(t, err)
assert.True(t, actualValue)
}

func TestCronJobSchedulerContainerRepeatRun(t *testing.T) {
var err error

checker, _ := duplicatechecker.NewInMemoryDuplicateChecker(&settings.Config{
DuplicateSubmissionsIntervalDuration: 60 * time.Second,
InMemoryDuplicateCheckerCleanupIntervalDuration: 60 * time.Second,
})

duplicatechecker.Container.Current = checker

container := &CronJobSchedulerContainer{
allJobsMap: make(map[string]*CronJob),
allGocronJobsMap: make(map[string]gocron.Job),
}

container.scheduler, err = gocron.NewScheduler(
gocron.WithLocation(time.Local),
gocron.WithLogger(NewGocronLoggerAdapter()),
)
assert.Nil(t, err)

var runCount atomic.Uint32
runTime := time.Now().Add(time.Second)
job := &CronJob{
Name: "TestRepeatRunJob",
Description: "The test cron job",
Period: CronJobFixedTimePeriod{
Time: runTime,
},
Run: func() error {
runCount.Add(1)
return nil
},
}

container.registerIntervalJob(job)
container.registerIntervalJob(job)
container.registerIntervalJob(job)
container.registerIntervalJob(job)
container.registerIntervalJob(job)
container.scheduler.Start()

time.Sleep(10 * time.Second)

assert.Nil(t, err)
assert.Equal(t, uint32(1), runCount.Load())

err = container.scheduler.Shutdown()
assert.Nil(t, err)
}
30 changes: 16 additions & 14 deletions pkg/cron/cron_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,35 @@ import (
"github.com/mayswind/ezbookkeeping/pkg/utils"
)

// CronJob represents the cron job instance
type CronJob struct {
Name string
Description string
Interval time.Duration
Period CronJobPeriod
Run func() error
}

func (c *CronJob) doRun() {
start := time.Now()
localAddr, err := utils.GetLocalIPAddressesString()

if err != nil {
log.Warnf("[cron_job.doRun] job \"%s\" cannot get local ipv4 address, because %s", c.Name, err.Error())
return
}
if duplicatechecker.Container.Current != nil {
localAddr, err := utils.GetLocalIPAddressesString()

currentInfo := fmt.Sprintf("ip: %s, startTime: %d", localAddr, time.Now().Unix())
found, runningInfo := duplicatechecker.Container.GetOrSetCronJobRunningInfo(c.Name, currentInfo, c.Interval)
if err != nil {
log.Warnf("[cron_job.doRun] job \"%s\" cannot get local ipv4 address, because %s", c.Name, err.Error())
return
}

if found {
log.Warnf("[cron_job.doRun] job \"%s\" is already running (%s)", c.Name, runningInfo)
return
}
currentInfo := fmt.Sprintf("ip: %s, startTime: %d", localAddr, time.Now().Unix())
found, runningInfo := duplicatechecker.Container.GetOrSetCronJobRunningInfo(c.Name, currentInfo, c.Period.GetInterval())

err = c.Run()
if found {
log.Warnf("[cron_job.doRun] job \"%s\" is already running (%s)", c.Name, runningInfo)
return
}
}

duplicatechecker.Container.Current.RemoveCronJobRunningInfo(c.Name)
err := c.Run()

now := time.Now()

Expand Down
63 changes: 63 additions & 0 deletions pkg/cron/cron_job_period.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package cron

import (
"time"

"github.com/go-co-op/gocron/v2"
)

// CronJobPeriod represents the cron job period
type CronJobPeriod interface {
GetInterval() time.Duration
ToJobDefinition() gocron.JobDefinition
}

// CronJobIntervalPeriod represents the period of execution at intervals
type CronJobIntervalPeriod struct {
Interval time.Duration
}

// CronJobFixedHourPeriod represents the period of execution at fixed hour
type CronJobFixedHourPeriod struct {
Hour uint32
}

// CronJobFixedTimePeriod represents the period of execution at fixed time
type CronJobFixedTimePeriod struct {
Time time.Time
}

// GetInterval returns the interval time of the period of CronJobIntervalPeriod
func (p CronJobIntervalPeriod) GetInterval() time.Duration {
return p.Interval
}

// ToJobDefinition returns the gocron job definition of the period of CronJobIntervalPeriod
func (p CronJobIntervalPeriod) ToJobDefinition() gocron.JobDefinition {
return gocron.DurationJob(p.Interval)
}

// GetInterval returns the interval time of the period of CronJobFixedHourPeriod
func (p CronJobFixedHourPeriod) GetInterval() time.Duration {
return 24 * time.Hour
}

// ToJobDefinition returns the gocron job definition of the period of CronJobFixedHourPeriod
func (p CronJobFixedHourPeriod) ToJobDefinition() gocron.JobDefinition {
return gocron.DailyJob(
1,
gocron.NewAtTimes(
gocron.NewAtTime(uint(p.Hour), 0, 0),
),
)
}

// GetInterval returns the interval time of the period of CronJobFixedTimePeriod
func (p CronJobFixedTimePeriod) GetInterval() time.Duration {
return 0
}

// ToJobDefinition returns the gocron job definition of the period of CronJobFixedTimePeriod
func (p CronJobFixedTimePeriod) ToJobDefinition() gocron.JobDefinition {
return gocron.OneTimeJob(gocron.OneTimeJobStartDateTime(p.Time))
}
Loading

0 comments on commit c2757f6

Please sign in to comment.