github.com/ngicks/gokugen@v0.0.5/cron/cron_like_rescheduler_test.go (about) 1 package cron_test 2 3 import ( 4 "context" 5 "sync" 6 "testing" 7 "time" 8 9 "github.com/ngicks/gokugen" 10 "github.com/ngicks/gokugen/cron" 11 "github.com/ngicks/type-param-common/iterator" 12 syncparam "github.com/ngicks/type-param-common/sync-param" 13 "github.com/stretchr/testify/require" 14 ) 15 16 var _ cron.RowLike = fakeRowLike{} 17 18 type fakeRowLike struct { 19 nextTime time.Time 20 command []string 21 } 22 23 func (r fakeRowLike) NextSchedule(now time.Time) (time.Time, error) { 24 return r.nextTime, nil 25 } 26 27 func (r fakeRowLike) GetCommand() []string { 28 return r.command 29 } 30 31 var _ gokugen.Task = &fakeTask{} 32 33 type fakeTask struct { 34 ctx gokugen.SchedulerContext 35 } 36 37 func (t fakeTask) Cancel() (cancelled bool) { 38 return true 39 } 40 41 func (t fakeTask) CancelWithReason(err error) (cancelled bool) { 42 return true 43 } 44 45 func (t fakeTask) GetScheduledTime() time.Time { 46 return t.ctx.ScheduledTime() 47 } 48 func (t fakeTask) IsCancelled() (cancelled bool) { 49 return 50 } 51 func (t fakeTask) IsDone() (done bool) { 52 return 53 } 54 55 var _ cron.Scheduler = &fakeScheduler{} 56 57 type fakeScheduler struct { 58 mu sync.Mutex 59 idx int 60 ctxList []gokugen.SchedulerContext 61 } 62 63 func (s *fakeScheduler) runAllTasks() (results []error) { 64 var cloned []gokugen.SchedulerContext 65 func() { 66 s.mu.Lock() 67 defer s.mu.Unlock() 68 cloned = make([]gokugen.SchedulerContext, len(s.ctxList)) 69 copy(cloned, s.ctxList) 70 }() 71 72 iter := iterator.Iterator[gokugen.SchedulerContext]{ 73 DeIterator: iterator.FromSlice(cloned), 74 }.SkipN(s.idx) 75 76 s.idx = iter.Len() 77 78 for next, ok := iter.Next(); ok; next, ok = iter.Next() { 79 _, err := next.Work()(context.TODO(), next.ScheduledTime()) 80 results = append(results, err) 81 } 82 return 83 } 84 85 func (s *fakeScheduler) Schedule(ctx gokugen.SchedulerContext) (gokugen.Task, error) { 86 s.mu.Lock() 87 defer s.mu.Unlock() 88 s.ctxList = append(s.ctxList, ctx) 89 return fakeTask{ctx: ctx}, nil 90 } 91 92 func prepareController( 93 shouldReschedule func(workErr error, callCount int) bool, 94 ) ( 95 rowLike *fakeRowLike, 96 scheduler *fakeScheduler, 97 registry *syncparam.Map[string, gokugen.WorkFnWParam], 98 cronLikeRescheduler *cron.CronLikeRescheduler, 99 ) { 100 rowLike = &fakeRowLike{command: []string{"foo", "bar", "baz"}} 101 scheduler = &fakeScheduler{ 102 ctxList: make([]gokugen.SchedulerContext, 0), 103 } 104 registry = new(syncparam.Map[string, gokugen.WorkFnWParam]) 105 registry.LoadOrStore("foo", func(taskCtx context.Context, scheduled time.Time, param any) (any, error) { 106 return nil, nil 107 }) 108 109 cronLikeRescheduler = cron.NewCronLikeRescheduler( 110 rowLike, 111 time.Now(), 112 shouldReschedule, 113 scheduler, 114 registry, 115 ) 116 return 117 } 118 119 func TestController(t *testing.T) { 120 t.Parallel() 121 t.Run("rescheduling is controlled by shouldReschedule passed by user.", func(t *testing.T) { 122 _, scheduler, _, cronLikeRescheduler := prepareController( 123 func(workErr error, callCount int) bool { return callCount == 0 }, 124 ) 125 126 err := cronLikeRescheduler.Schedule() 127 if err != nil { 128 t.Fatal(err) 129 } 130 131 results := scheduler.runAllTasks() 132 require.Len(t, results, 1) 133 for _, res := range results { 134 if res != nil { 135 t.Fatal(res) 136 } 137 } 138 139 results = scheduler.runAllTasks() 140 require.Len(t, results, 1) 141 for _, res := range results { 142 if res != nil { 143 t.Fatal(res) 144 } 145 } 146 147 results = scheduler.runAllTasks() 148 require.Len(t, results, 0) 149 }) 150 151 }