Repository: go-co-op/gocron
Pull request #130 (2021-02-28 04:51:09)

wrap errors
### What does this do?
- when multiple errors occur, wrap them so the user can unwrap and check what happened
- collapse/clean up tests

### Which issue(s) does this PR fix/relate to?
<!--- Put `Resolves #XXX` here to auto-close the issue that your PR fixes (if such) --->


### List any changes that modify/break current functionality


### Have you included tests for your changes?


### Did you document any new/modified functionality?

- [ ] Updated `example_test.go`
- [ ] Updated `README.md`

### Notes



Patch:
diff --git a/gocron.go b/gocron.go
index 88a1e4b..66b2176 100644
--- a/gocron.go
+++ b/gocron.go
@@ -8,6 +8,7 @@ package gocron
 
 import (
 	"errors"
+	"fmt"
 	"reflect"
 	"regexp"
 	"runtime"
@@ -16,16 +17,28 @@ import (
 
 // Error declarations for gocron related errors
 var (
-	ErrTimeFormat            = errors.New("time format error")
-	ErrNotAFunction          = errors.New("only functions can be schedule into the job queue")
-	ErrNotScheduledWeekday   = errors.New("job not scheduled weekly on a weekday")
-	ErrJobNotFoundWithTag    = errors.New("no jobs found with given tag")
-	ErrUnsupportedTimeFormat = errors.New("the given time format is not supported")
-	ErrInvalidInterval       = errors.New(".Every() interval must be greater than 0")
-	ErrInvalidIntervalType   = errors.New(".Every() interval must be int, time.Duration, or string")
-	ErrInvalidSelection      = errors.New("an .Every() duration interval cannot be used with units (e.g. .Seconds())")
+	ErrNotAFunction                  = errors.New("only functions can be schedule into the job queue")
+	ErrNotScheduledWeekday           = errors.New("job not scheduled weekly on a weekday")
+	ErrJobNotFoundWithTag            = errors.New("no jobs found with given tag")
+	ErrUnsupportedTimeFormat         = errors.New("the given time format is not supported")
+	ErrInvalidInterval               = errors.New(".Every() interval must be greater than 0")
+	ErrInvalidIntervalType           = errors.New(".Every() interval must be int, time.Duration, or string")
+	ErrInvalidIntervalUnitsSelection = errors.New("an .Every() duration interval cannot be used with units (e.g. .Seconds())")
+
+	ErrAtTimeNotSupported  = errors.New("the At() not supported for time unit")
+	ErrWeekdayNotSupported = errors.New("weekday is not supported for time unit")
 )
 
+func wrapOrError(toWrap error, err error) error {
+	var returnErr error
+	if toWrap != nil {
+		returnErr = fmt.Errorf("%s: %w", err, toWrap)
+	} else {
+		returnErr = err
+	}
+	return returnErr
+}
+
 // regex patterns for supported time formats
 var (
 	timeWithSeconds    = regexp.MustCompile(`(?m)^\d{1,2}:\d\d:\d\d$`)
diff --git a/scheduler.go b/scheduler.go
index a4c1089..a0dbcc4 100644
--- a/scheduler.go
+++ b/scheduler.go
@@ -69,8 +69,8 @@ func (s *Scheduler) start() {
 }
 
 func (s *Scheduler) runJobs(jobs []*Job) {
-	for _, j := range jobs {
-		s.scheduleNextRun(j)
+	for _, job := range jobs {
+		s.scheduleNextRun(job)
 	}
 }
 
@@ -109,10 +109,10 @@ func (s *Scheduler) Len() int {
 
 // Swap places each job into the other job's position given
 // the provided job indexes.
-func (s *Scheduler) Swap(i, j int) {
+func (s *Scheduler) Swap(i, job int) {
 	s.jobsMutex.Lock()
 	defer s.jobsMutex.Unlock()
-	s.jobs[i], s.jobs[j] = s.jobs[j], s.jobs[i]
+	s.jobs[i], s.jobs[job] = s.jobs[job], s.jobs[i]
 }
 
 // Less compares the next run of jobs based on their index.
@@ -201,7 +201,7 @@ func (s *Scheduler) durationToNextRun(lastRun time.Time, job *Job) time.Duration
 func (s *Scheduler) calculateMonths(job *Job, lastRun time.Time) time.Duration {
 	lastRunRoundedMidnight := s.roundToMidnight(lastRun)
 
-	if job.dayOfTheMonth > 0 { // calculate days to j.dayOfTheMonth
+	if job.dayOfTheMonth > 0 { // calculate days to job.dayOfTheMonth
 		jobDay := time.Date(lastRun.Year(), lastRun.Month(), job.dayOfTheMonth, 0, 0, 0, 0, s.Location()).Add(job.getAtTime())
 		daysDifference := int(math.Abs(lastRun.Sub(jobDay).Hours()) / 24)
 		nextRun := s.roundToMidnight(lastRun).Add(job.getAtTime())
@@ -326,7 +326,7 @@ func (s *Scheduler) Every(interval interface{}) *Scheduler {
 	case int:
 		job := NewJob(interval)
 		if interval <= 0 {
-			job.err = ErrInvalidInterval
+			job.err = wrapOrError(job.err, ErrInvalidInterval)
 		}
 		s.setJobs(append(s.Jobs(), job))
 	case time.Duration:
@@ -338,14 +338,14 @@ func (s *Scheduler) Every(interval interface{}) *Scheduler {
 		job := NewJob(0)
 		d, err := time.ParseDuration(interval)
 		if err != nil {
-			job.err = err
+			job.err = wrapOrError(job.err, err)
 		}
 		job.duration = d
 		job.unit = duration
 		s.setJobs(append(s.Jobs(), job))
 	default:
 		job := NewJob(0)
-		job.err = ErrInvalidIntervalType
+		job.err = wrapOrError(job.err, ErrInvalidIntervalType)
 		s.setJobs(append(s.Jobs(), job))
 	}
 	return s
@@ -376,25 +376,25 @@ func (s *Scheduler) RunAllWithDelay(d time.Duration) {
 	}
 }
 
-// Remove specific Job j by function
+// Remove specific Job job by function
 //
 // Removing a job stops that job's timer. However, if a job has already
 // been started by by the job's timer before being removed, there is no way to stop
 // it through gocron as https://pkg.go.dev/time#Timer.Stop explains.
 // The job function would need to have implemented a means of
 // stopping, e.g. using a context.WithCancel().
-func (s *Scheduler) Remove(j interface{}) {
+func (s *Scheduler) Remove(job interface{}) {
 	s.removeByCondition(func(someJob *Job) bool {
-		return someJob.name == getFunctionName(j)
+		return someJob.name == getFunctionName(job)
 	})
 }
 
-// RemoveByReference removes specific Job j by reference
-func (s *Scheduler) RemoveByReference(j *Job) {
+// RemoveByReference removes specific Job job by reference
+func (s *Scheduler) RemoveByReference(job *Job) {
 	s.removeByCondition(func(someJob *Job) bool {
-		j.RLock()
-		defer j.RUnlock()
-		return someJob == j
+		job.RLock()
+		defer job.RUnlock()
+		return someJob == job
 	})
 }
 
@@ -413,14 +413,14 @@ func (s *Scheduler) removeByCondition(shouldRemove func(*Job) bool) {
 
 // RemoveByTag will remove a job by a given tag.
 func (s *Scheduler) RemoveByTag(tag string) error {
-	jobindex, err := s.findJobsIndexByTag(tag)
+	index, err := s.findJobsIndexByTag(tag)
 	if err != nil {
 		return err
 	}
 	// Remove job if job index is valid
-	s.jobs[jobindex].stopTimer()
-	s.jobs[jobindex].cancel()
-	s.setJobs(removeAtIndex(s.jobs, jobindex))
+	s.jobs[index].stopTimer()
+	s.jobs[index].cancel()
+	s.setJobs(removeAtIndex(s.jobs, index))
 	return nil
 }
 
@@ -510,45 +510,54 @@ func (s *Scheduler) stop() {
 
 // Do specifies the jobFunc that should be called every time the Job runs
 func (s *Scheduler) Do(jobFun interface{}, params ...interface{}) (*Job, error) {
-	j := s.getCurrentJob()
-	if j.err != nil {
+	job := s.getCurrentJob()
+
+	if job.atTime != 0 && job.unit <= hours {
+		job.err = wrapOrError(job.err, ErrAtTimeNotSupported)
+	}
+
+	if job.scheduledWeekday != nil && job.unit != weeks {
+		job.err = wrapOrError(job.err, ErrWeekdayNotSupported)
+	}
+
+	if job.err != nil {
 		// delete the job from the scheduler as this job
 		// cannot be executed
-		s.RemoveByReference(j)
-		return nil, j.err
+		s.RemoveByReference(job)
+		return nil, job.err
 	}
 
 	typ := reflect.TypeOf(jobFun)
 	if typ.Kind() != reflect.Func {
 		// delete the job for the same reason as above
-		s.RemoveByReference(j)
+		s.RemoveByReference(job)
 		return nil, ErrNotAFunction
 	}
 
 	fname := getFunctionName(jobFun)
-	j.functions[fname] = jobFun
-	j.params[fname] = params
-	j.name = fname
+	job.functions[fname] = jobFun
+	job.params[fname] = params
+	job.name = fname
 
 	// we should not schedule if not running since we cant foresee how long it will take for the scheduler to start
 	if s.IsRunning() {
-		s.scheduleNextRun(j)
+		s.scheduleNextRun(job)
 	}
 
-	return j, nil
+	return job, nil
 }
 
 // At schedules the Job at a specific time of day in the form "HH:MM:SS" or "HH:MM"
 func (s *Scheduler) At(t string) *Scheduler {
-	j := s.getCurrentJob()
+	job := s.getCurrentJob()
 	hour, min, sec, err := parseTime(t)
 	if err != nil {
-		j.err = ErrTimeFormat
+		job.err = wrapOrError(job.err, err)
 		return s
 	}
 	// save atTime start as duration from midnight
-	j.setAtTime(time.Duration(hour)*time.Hour + time.Duration(min)*time.Minute + time.Duration(sec)*time.Second)
-	j.startsImmediately = false
+	job.setAtTime(time.Duration(hour)*time.Hour + time.Duration(min)*time.Minute + time.Duration(sec)*time.Second)
+	job.startsImmediately = false
 	return s
 }
 
@@ -572,7 +581,7 @@ func (s *Scheduler) StartAt(t time.Time) *Scheduler {
 func (s *Scheduler) setUnit(unit timeUnit) {
 	job := s.getCurrentJob()
 	if job.unit == duration {
-		job.err = ErrInvalidSelection
+		job.err = wrapOrError(job.err, ErrInvalidIntervalUnitsSelection)
 	}
 	job.unit = unit
 }


Test patch:
diff --git a/example_test.go b/example_test.go
index 61c1bf4..74b68e5 100644
--- a/example_test.go
+++ b/example_test.go
@@ -19,7 +19,7 @@ func ExampleJob_Err() {
 	j := s.Jobs()[0]
 	fmt.Println(j.Err())
 	// Output:
-	// time format error
+	// the given time format is not supported
 }
 
 func ExampleJob_LastRun() {
diff --git a/job_test.go b/job_test.go
index 3fd888b..f80f016 100644
--- a/job_test.go
+++ b/job_test.go
@@ -5,6 +5,7 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 func TestTags(t *testing.T) {
@@ -21,8 +22,16 @@ func TestTags(t *testing.T) {
 }
 
 func TestGetScheduledTime(t *testing.T) {
-	j, _ := NewScheduler(time.UTC).Every(1).Minute().At("10:30").Do(task)
-	assert.Equal(t, "10:30", j.ScheduledAtTime())
+	t.Run("valid", func(t *testing.T) {
+		j, err := NewScheduler(time.UTC).Every(1).Day().At("10:30").Do(task)
+		require.NoError(t, err)
+		assert.Equal(t, "10:30", j.ScheduledAtTime())
+	})
+	t.Run("invalid", func(t *testing.T) {
+		j, err := NewScheduler(time.UTC).Every(1).Minute().At("10:30").Do(task)
+		assert.EqualError(t, err, ErrAtTimeNotSupported.Error())
+		assert.Nil(t, j)
+	})
 }
 
 func TestGetWeekday(t *testing.T) {
diff --git a/scheduler_test.go b/scheduler_test.go
index 606c0cb..1fb0849 100644
--- a/scheduler_test.go
+++ b/scheduler_test.go
@@ -239,7 +239,7 @@ func TestAt(t *testing.T) {
 		s := NewScheduler(time.UTC)
 		badTime := "0:0"
 		_, err := s.Every(1).Day().At(badTime).Do(func() {})
-		assert.EqualError(t, err, ErrTimeFormat.Error())
+		assert.EqualError(t, err, ErrUnsupportedTimeFormat.Error())
 		assert.Zero(t, s.Len())
 	})
 }
@@ -365,7 +365,7 @@ func TestScheduler_Remove(t *testing.T) {
 	})
 }
 
-func TestRemoveByReference(t *testing.T) {
+func TestScheduler_RemoveByReference(t *testing.T) {
 	t.Run("remove from non-running scheduler", func(t *testing.T) {
 		s := NewScheduler(time.UTC)
 		job1, _ := s.Every(1).Minute().Do(task)
@@ -399,7 +399,7 @@ func TestRemoveByReference(t *testing.T) {
 	})
 }
 
-func TestRemoveByTag(t *testing.T) {
+func TestScheduler_RemoveByTag(t *testing.T) {
 	s := NewScheduler(time.UTC)
 
 	// Creating 2 Jobs with Unique tags
@@ -425,7 +425,7 @@ func TestRemoveByTag(t *testing.T) {
 	assert.EqualError(t, err, ErrJobNotFoundWithTag.Error())
 }
 
-func TestJobs(t *testing.T) {
+func TestScheduler_Jobs(t *testing.T) {
 	s := NewScheduler(time.UTC)
 	s.Every(1).Minute().Do(task)
 	s.Every(2).Minutes().Do(task)
@@ -435,7 +435,7 @@ func TestJobs(t *testing.T) {
 	assert.Len(t, js, 4)
 }
 
-func TestLen(t *testing.T) {
+func TestScheduler_Len(t *testing.T) {
 	s := NewScheduler(time.UTC)
 	s.Every(1).Minute().Do(task)
 	s.Every(2).Minutes().Do(task)
@@ -627,372 +627,61 @@ func TestScheduler_CalculateNextRun(t *testing.T) {
 		return time.Date(2020, time.January, 6, hour, minute, second, 0, time.UTC)
 	}
 
-	var tests = []struct {
+	testCases := []struct {
 		name                 string
-		job                  Job
+		job                  *Job
 		wantTimeUntilNextRun time.Duration
 	}{
 		// SECONDS
-		{
-			name: "every second test",
-			job: Job{
-				interval: 1,
-				unit:     seconds,
-				lastRun:  januaryFirst2020At(0, 0, 0),
-			},
-			wantTimeUntilNextRun: _getSeconds(1),
-		},
-		{
-			name: "every 62 seconds test",
-			job: Job{
-				interval: 62,
-				unit:     seconds,
-				lastRun:  januaryFirst2020At(0, 0, 0),
-			},
-			wantTimeUntilNextRun: _getSeconds(62),
-		},
+		{name: "every second test", job: &Job{interval: 1, unit: seconds, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: _getSeconds(1)},
+		{name: "every 62 seconds test", job: &Job{interval: 62, unit: seconds, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: _getSeconds(62)},
 		// MINUTES
-		{
-			name: "every minute test",
-			job: Job{
-				interval: 1,
-				unit:     minutes,
-				lastRun:  januaryFirst2020At(0, 0, 0),
-			},
-			wantTimeUntilNextRun: _getMinutes(1),
-		},
-		{
-			name: "every 62 minutes test",
-			job: Job{
-				interval: 62,
-				unit:     minutes,
-				lastRun:  januaryFirst2020At(0, 0, 0),
-			},
-			wantTimeUntilNextRun: _getMinutes(62),
-		},
+		{name: "every minute test", job: &Job{interval: 1, unit: minutes, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: _getMinutes(1)},
+		{name: "every 62 minutes test", job: &Job{interval: 62, unit: minutes, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: _getMinutes(62)},
 		// HOURS
-		{
-			name: "every hour test",
-			job: Job{
-				interval: 1,
-				unit:     hours,
-				lastRun:  januaryFirst2020At(0, 0, 0),
-			},
-			wantTimeUntilNextRun: _getHours(1),
-		},
-		{
-			name: "every 25 hours test",
-			job: Job{
-				interval: 25,
-				unit:     hours,
-				lastRun:  januaryFirst2020At(0, 0, 0),
-			},
-			wantTimeUntilNextRun: _getHours(25),
-		},
+		{name: "every hour test", job: &Job{interval: 1, unit: hours, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: _getHours(1)},
+		{name: "every 25 hours test", job: &Job{interval: 25, unit: hours, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: _getHours(25)},
 		// DAYS
-		{
-			name: "every day at midnight",
-			job: Job{
-				interval: 1,
-				unit:     days,
-				lastRun:  januaryFirst2020At(0, 0, 0),
-			},
-			wantTimeUntilNextRun: 1 * day,
-		},
-		{
-			name: "every day at 09:30AM with scheduler starting before 09:30AM should run at same day at time",
-			job: Job{
-				interval: 1,
-				unit:     days,
-				atTime:   _getHours(9) + _getMinutes(30),
-				lastRun:  januaryFirst2020At(0, 0, 0),
-			},
-			wantTimeUntilNextRun: _getHours(9) + _getMinutes(30),
-		},
-		{
-			name: "every day at 09:30AM which just ran should run tomorrow at 09:30AM",
-			job: Job{
-				interval: 1,
-				unit:     days,
-				atTime:   _getHours(9) + _getMinutes(30),
-				lastRun:  januaryFirst2020At(9, 30, 0),
-			},
-			wantTimeUntilNextRun: 1 * day,
-		},
-		{
-			name: "every 31 days at midnight should run 31 days later",
-			job: Job{
-				interval: 31,
-				unit:     days,
-				lastRun:  januaryFirst2020At(0, 0, 0),
-			},
-			wantTimeUntilNextRun: 31 * day,
-		},
-		{
-			name: "daily job just ran at 8:30AM and should be scheduled for next day's 8:30AM",
-			job: Job{
-				interval: 1,
-				unit:     days,
-				atTime:   8*time.Hour + 30*time.Minute,
-				lastRun:  januaryFirst2020At(8, 30, 0),
-			},
-			wantTimeUntilNextRun: 24 * time.Hour,
-		},
-		{
-			name: "daily job just ran at 5:30AM and should be scheduled for today at 8:30AM",
-			job: Job{
-				interval: 1,
-				unit:     days,
-				atTime:   8*time.Hour + 30*time.Minute,
-				lastRun:  januaryFirst2020At(5, 30, 0),
-			},
-			wantTimeUntilNextRun: 3 * time.Hour,
-		},
-		{
-			name: "job runs every 2 days, just ran at 5:30AM and should be scheduled for 2 days at 8:30AM",
-			job: Job{
-				interval: 2,
-				unit:     days,
-				atTime:   8*time.Hour + 30*time.Minute,
-				lastRun:  januaryFirst2020At(5, 30, 0),
-			},
-			wantTimeUntilNextRun: (2 * day) + 3*time.Hour,
-		},
-		{
-			name: "job runs every 2 days, just ran at 8:30AM and should be scheduled for 2 days at 8:30AM",
-			job: Job{
-				interval: 2,
-				unit:     days,
-				atTime:   8*time.Hour + 30*time.Minute,
-				lastRun:  januaryFirst2020At(8, 30, 0),
-			},
-			wantTimeUntilNextRun: 2 * day,
-		},
+		{name: "every day at midnight", job: &Job{interval: 1, unit: days, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: 1 * day},
+		{name: "every day at 09:30AM with scheduler starting before 09:30AM should run at same day at time", job: &Job{interval: 1, unit: days, atTime: _getHours(9) + _getMinutes(30), lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: _getHours(9) + _getMinutes(30)},
+		{name: "every day at 09:30AM which just ran should run tomorrow at 09:30AM", job: &Job{interval: 1, unit: days, atTime: _getHours(9) + _getMinutes(30), lastRun: januaryFirst2020At(9, 30, 0)}, wantTimeUntilNextRun: 1 * day},
+		{name: "every 31 days at midnight should run 31 days later", job: &Job{interval: 31, unit: days, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: 31 * day},
+		{name: "daily job just ran at 8:30AM and should be scheduled for next day's 8:30AM", job: &Job{interval: 1, unit: days, atTime: 8*time.Hour + 30*time.Minute, lastRun: januaryFirst2020At(8, 30, 0)}, wantTimeUntilNextRun: 24 * time.Hour},
+		{name: "daily job just ran at 5:30AM and should be scheduled for today at 8:30AM", job: &Job{interval: 1, unit: days, atTime: 8*time.Hour + 30*time.Minute, lastRun: januaryFirst2020At(5, 30, 0)}, wantTimeUntilNextRun: 3 * time.Hour},
+		{name: "job runs every 2 days, just ran at 5:30AM and should be scheduled for 2 days at 8:30AM", job: &Job{interval: 2, unit: days, atTime: 8*time.Hour + 30*time.Minute, lastRun: januaryFirst2020At(5, 30, 0)}, wantTimeUntilNextRun: (2 * day) + 3*time.Hour},
+		{name: "job runs every 2 days, just ran at 8:30AM and should be scheduled for 2 days at 8:30AM", job: &Job{interval: 2, unit: days, atTime: 8*time.Hour + 30*time.Minute, lastRun: januaryFirst2020At(8, 30, 0)}, wantTimeUntilNextRun: 2 * day},
 		//// WEEKS
-		{
-			name: "every week should run in 7 days",
-			job: Job{
-				interval: 1,
-				unit:     weeks,
-				lastRun:  januaryFirst2020At(0, 0, 0),
-			},
-			wantTimeUntilNextRun: 7 * day,
-		},
-		{
-			name: "every week with .At time rule should run respect .At time rule",
-			job: Job{
-				interval: 1,
-				atTime:   _getHours(9) + _getMinutes(30),
-				unit:     weeks,
-				lastRun:  januaryFirst2020At(9, 30, 0),
-			},
-			wantTimeUntilNextRun: 7 * day,
-		},
-		{
-			name: "every two weeks at 09:30AM should run in 14 days at 09:30AM",
-			job: Job{
-				interval: 2,
-				unit:     weeks,
-				lastRun:  januaryFirst2020At(0, 0, 0),
-			},
-			wantTimeUntilNextRun: 14 * day,
-		},
-		{
-			name: "every 31 weeks ran at jan 1st at midnight should run at August 5, 2020",
-			job: Job{
-				interval: 31,
-				unit:     weeks,
-				lastRun:  januaryFirst2020At(0, 0, 0),
-			},
-			wantTimeUntilNextRun: 31 * 7 * day,
-		},
+		{name: "every week should run in 7 days", job: &Job{interval: 1, unit: weeks, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: 7 * day},
+		{name: "every week with .At time rule should run respect .At time rule", job: &Job{interval: 1, atTime: _getHours(9) + _getMinutes(30), unit: weeks, lastRun: januaryFirst2020At(9, 30, 0)}, wantTimeUntilNextRun: 7 * day},
+		{name: "every two weeks at 09:30AM should run in 14 days at 09:30AM", job: &Job{interval: 2, unit: weeks, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: 14 * day},
+		{name: "every 31 weeks ran at jan 1st at midnight should run at August 5, 2020", job: &Job{interval: 31, unit: weeks, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: 31 * 7 * day},
 		// MONTHS
-		{
-			name: "every month in a 31 days month should be scheduled for 31 days ahead",
-			job: Job{
-				interval: 1,
-				unit:     months,
-				lastRun:  januaryFirst2020At(0, 0, 0),
-			},
-			wantTimeUntilNextRun: 31 * day,
-		},
-		{
-			name: "every month in a 30 days month should be scheduled for 30 days ahead",
-			job: Job{
-				interval: 1,
-				unit:     months,
-				lastRun:  time.Date(2020, time.April, 1, 0, 0, 0, 0, time.UTC),
-			},
-			wantTimeUntilNextRun: 30 * day,
-		},
-		{
-			name: "every month at february on leap year should count 29 days",
-			job: Job{
-				interval: 1,
-				unit:     months,
-				lastRun:  time.Date(2020, time.February, 1, 0, 0, 0, 0, time.UTC),
-			},
-			wantTimeUntilNextRun: 29 * day,
-		},
-		{
-			name: "every month at february on non leap year should count 28 days",
-			job: Job{
-				interval: 1,
-				unit:     months,
-				lastRun:  time.Date(2019, time.February, 1, 0, 0, 0, 0, time.UTC),
-			},
-			wantTimeUntilNextRun: 28 * day,
-		},
-		{
-			name: "every month at first day at time should run next month + at time",
-			job: Job{
-				interval: 1,
-				unit:     months,
-				atTime:   _getHours(9) + _getMinutes(30),
-				lastRun:  januaryFirst2020At(9, 30, 0),
-			},
-			wantTimeUntilNextRun: 31*day + _getHours(9) + _getMinutes(30),
-		},
-		{
-			name: "every month at day should consider at days",
-			job: Job{
-				interval:      1,
-				unit:          months,
-				dayOfTheMonth: 2,
-				lastRun:       januaryFirst2020At(0, 0, 0),
-			},
-			wantTimeUntilNextRun: 1 * day,
-		},
-		{
-			name: "every month at day should consider at hours",
-			job: Job{
-				interval: 1,
-				unit:     months,
-				atTime:   _getHours(9) + _getMinutes(30),
-				lastRun:  januaryFirst2020At(0, 0, 0),
-			},
-			wantTimeUntilNextRun: 31*day + _getHours(9) + _getMinutes(30),
-		},
-		{
-			name: "every month on the first day, but started on january 8th, should run February 1st",
-			job: Job{
-				interval:      1,
-				unit:          months,
-				dayOfTheMonth: 1,
-				lastRun:       januaryFirst2020At(0, 0, 0).AddDate(0, 0, 7),
-			},
-			wantTimeUntilNextRun: 24 * day,
-		},
-		{
-			name: "every 2 months at day 1, starting at day 1, should run in 2 months",
-			job: Job{
-				interval:      2,
-				unit:          months,
-				dayOfTheMonth: 1,
-				lastRun:       januaryFirst2020At(0, 0, 0),
-			},
-			wantTimeUntilNextRun: 31*day + 29*day, // 2020 january and february
-		},
-		{
-			name: "every 2 months at day 2, starting at day 1, should run in 2 months + 1 day",
-			job: Job{
-				interval:      2,
-				unit:          months,
-				dayOfTheMonth: 2,
-				lastRun:       januaryFirst2020At(0, 0, 0),
-			},
-			wantTimeUntilNextRun: 31*day + 29*day + 1*day, // 2020 january and february
-		},
-		{
-			name: "every 2 months at day 1, starting at day 2, should run in 2 months - 1 day",
-			job: Job{
-				interval:      2,
-				unit:          months,
-				dayOfTheMonth: 1,
-				lastRun:       januaryFirst2020At(0, 0, 0).AddDate(0, 0, 1),
-			},
-			wantTimeUntilNextRun: 30*day + 29*day, // 2020 january and february
-		},
-		{
-			name: "every 13 months at day 1, starting at day 2 run in 13 months - 1 day",
-			job: Job{
-				interval:      13,
-				unit:          months,
-				dayOfTheMonth: 1,
-				lastRun:       januaryFirst2020At(0, 0, 0).AddDate(0, 0, 1),
-			},
-			wantTimeUntilNextRun: januaryFirst2020At(0, 0, 0).AddDate(0, 13, -1).Sub(januaryFirst2020At(0, 0, 0)),
-		},
+		{name: "every month in a 31 days month should be scheduled for 31 days ahead", job: &Job{interval: 1, unit: months, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: 31 * day},
+		{name: "every month in a 30 days month should be scheduled for 30 days ahead", job: &Job{interval: 1, unit: months, lastRun: time.Date(2020, time.April, 1, 0, 0, 0, 0, time.UTC)}, wantTimeUntilNextRun: 30 * day},
+		{name: "every month at february on leap year should count 29 days", job: &Job{interval: 1, unit: months, lastRun: time.Date(2020, time.February, 1, 0, 0, 0, 0, time.UTC)}, wantTimeUntilNextRun: 29 * day},
+		{name: "every month at february on non leap year should count 28 days", job: &Job{interval: 1, unit: months, lastRun: time.Date(2019, time.February, 1, 0, 0, 0, 0, time.UTC)}, wantTimeUntilNextRun: 28 * day},
+		{name: "every month at first day at time should run next month + at time", job: &Job{interval: 1, unit: months, atTime: _getHours(9) + _getMinutes(30), lastRun: januaryFirst2020At(9, 30, 0)}, wantTimeUntilNextRun: 31*day + _getHours(9) + _getMinutes(30)},
+		{name: "every month at day should consider at days", job: &Job{interval: 1, unit: months, dayOfTheMonth: 2, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: 1 * day},
+		{name: "every month at day should consider at hours", job: &Job{interval: 1, unit: months, atTime: _getHours(9) + _getMinutes(30), lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: 31*day + _getHours(9) + _getMinutes(30)},
+		{name: "every month on the first day, but started on january 8th, should run February 1st", job: &Job{interval: 1, unit: months, dayOfTheMonth: 1, lastRun: januaryFirst2020At(0, 0, 0).AddDate(0, 0, 7)}, wantTimeUntilNextRun: 24 * day},
+		{name: "every 2 months at day 1, starting at day 1, should run in 2 months", job: &Job{interval: 2, unit: months, dayOfTheMonth: 1, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: 31*day + 29*day},                          // 2020 january and february
+		{name: "every 2 months at day 2, starting at day 1, should run in 2 months + 1 day", job: &Job{interval: 2, unit: months, dayOfTheMonth: 2, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: 31*day + 29*day + 1*day},          // 2020 january and february
+		{name: "every 2 months at day 1, starting at day 2, should run in 2 months - 1 day", job: &Job{interval: 2, unit: months, dayOfTheMonth: 1, lastRun: januaryFirst2020At(0, 0, 0).AddDate(0, 0, 1)}, wantTimeUntilNextRun: 30*day + 29*day}, // 2020 january and february
+		{name: "every 13 months at day 1, starting at day 2 run in 13 months - 1 day", job: &Job{interval: 13, unit: months, dayOfTheMonth: 1, lastRun: januaryFirst2020At(0, 0, 0).AddDate(0, 0, 1)}, wantTimeUntilNextRun: januaryFirst2020At(0, 0, 0).AddDate(0, 13, -1).Sub(januaryFirst2020At(0, 0, 0))},
 		//// WEEKDAYS
-		{
-			name: "every weekday starting on one day before it should run this weekday",
-			job: Job{
-				interval:         1,
-				unit:             weeks,
-				scheduledWeekday: _tuesdayWeekday(),
-				lastRun:          mondayAt(0, 0, 0),
-			},
-			wantTimeUntilNextRun: 1 * day,
-		},
-		{
-			name: "every weekday starting on same weekday should run on same immediately",
-			job: Job{
-				interval:         1,
-				unit:             weeks,
-				scheduledWeekday: _tuesdayWeekday(),
-				lastRun:          mondayAt(0, 0, 0).AddDate(0, 0, 1),
-			},
-			wantTimeUntilNextRun: 0,
-		},
-		{
-			name: "every 2 weekdays counting this week's weekday should run next weekday",
-			job: Job{
-				interval:         2,
-				unit:             weeks,
-				scheduledWeekday: _tuesdayWeekday(),
-				lastRun:          mondayAt(0, 0, 0),
-			},
-			wantTimeUntilNextRun: 8 * day,
-		},
-		{
-			name: "every weekday starting on one day after should count days remaning",
-			job: Job{
-				interval:         1,
-				unit:             weeks,
-				scheduledWeekday: _tuesdayWeekday(),
-				lastRun:          mondayAt(0, 0, 0).AddDate(0, 0, 2),
-			},
-			wantTimeUntilNextRun: 6 * day,
-		},
-		{
-			name: "every weekday starting before jobs .At() time should run at same day at time",
-			job: Job{
-				interval:         1,
-				unit:             weeks,
-				atTime:           _getHours(9) + _getMinutes(30),
-				scheduledWeekday: _tuesdayWeekday(),
-				lastRun:          mondayAt(0, 0, 0).AddDate(0, 0, 1),
-			},
-			wantTimeUntilNextRun: _getHours(9) + _getMinutes(30),
-		},
-		{
-			name: "every weekday starting at same day at time that already passed should run at next week at time",
-			job: Job{
-				interval:         1,
-				unit:             weeks,
-				atTime:           _getHours(9) + _getMinutes(30),
-				scheduledWeekday: _tuesdayWeekday(),
-				lastRun:          mondayAt(10, 30, 0).AddDate(0, 0, 1),
-			},
-			wantTimeUntilNextRun: 6*day + _getHours(23) + _getMinutes(0),
-		},
+		{name: "every weekday starting on one day before it should run this weekday", job: &Job{interval: 1, unit: weeks, scheduledWeekday: _tuesdayWeekday(), lastRun: mondayAt(0, 0, 0)}, wantTimeUntilNextRun: 1 * day},
+		{name: "every weekday starting on same weekday should run on same immediately", job: &Job{interval: 1, unit: weeks, scheduledWeekday: _tuesdayWeekday(), lastRun: mondayAt(0, 0, 0).AddDate(0, 0, 1)}, wantTimeUntilNextRun: 0},
+		{name: "every 2 weekdays counting this week's weekday should run next weekday", job: &Job{interval: 2, unit: weeks, scheduledWeekday: _tuesdayWeekday(), lastRun: mondayAt(0, 0, 0)}, wantTimeUntilNextRun: 8 * day},
+		{name: "every weekday starting on one day after should count days remaining", job: &Job{interval: 1, unit: weeks, scheduledWeekday: _tuesdayWeekday(), lastRun: mondayAt(0, 0, 0).AddDate(0, 0, 2)}, wantTimeUntilNextRun: 6 * day},
+		{name: "every weekday starting before jobs .At() time should run at same day at time", job: &Job{interval: 1, unit: weeks, atTime: _getHours(9) + _getMinutes(30), scheduledWeekday: _tuesdayWeekday(), lastRun: mondayAt(0, 0, 0).AddDate(0, 0, 1)}, wantTimeUntilNextRun: _getHours(9) + _getMinutes(30)},
+		{name: "every weekday starting at same day at time that already passed should run at next week at time", job: &Job{interval: 1, unit: weeks, atTime: _getHours(9) + _getMinutes(30), scheduledWeekday: _tuesdayWeekday(), lastRun: mondayAt(10, 30, 0).AddDate(0, 0, 1)}, wantTimeUntilNextRun: 6*day + _getHours(23) + _getMinutes(0)},
 	}
 
-	for i := range tests {
-		t.Run(tests[i].name, func(t *testing.T) {
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
 			s := NewScheduler(time.UTC)
-			got := s.durationToNextRun(tests[i].job.LastRun(), &tests[i].job)
-			assert.Equalf(t, tests[i].wantTimeUntilNextRun, got, fmt.Sprintf("expected %s / got %s", tests[i].wantTimeUntilNextRun.String(), got.String()))
+			got := s.durationToNextRun(tc.job.LastRun(), tc.job)
+			assert.Equalf(t, tc.wantTimeUntilNextRun, got, fmt.Sprintf("expected %s / got %s", tc.wantTimeUntilNextRun.String(), got.String()))
 		})
 	}
 }