git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/cron/chain_test.go (about) 1 package cron 2 3 import ( 4 "io/ioutil" 5 "log" 6 "reflect" 7 "sync" 8 "testing" 9 "time" 10 ) 11 12 func appendingJob(slice *[]int, value int) Job { 13 var m sync.Mutex 14 return FuncJob(func() { 15 m.Lock() 16 *slice = append(*slice, value) 17 m.Unlock() 18 }) 19 } 20 21 func appendingWrapper(slice *[]int, value int) JobWrapper { 22 return func(j Job) Job { 23 return FuncJob(func() { 24 appendingJob(slice, value).Run() 25 j.Run() 26 }) 27 } 28 } 29 30 func TestChain(t *testing.T) { 31 var nums []int 32 var ( 33 append1 = appendingWrapper(&nums, 1) 34 append2 = appendingWrapper(&nums, 2) 35 append3 = appendingWrapper(&nums, 3) 36 append4 = appendingJob(&nums, 4) 37 ) 38 NewChain(append1, append2, append3).Then(append4).Run() 39 if !reflect.DeepEqual(nums, []int{1, 2, 3, 4}) { 40 t.Error("unexpected order of calls:", nums) 41 } 42 } 43 44 func TestChainRecover(t *testing.T) { 45 panickingJob := FuncJob(func() { 46 panic("panickingJob panics") 47 }) 48 49 t.Run("panic exits job by default", func(t *testing.T) { 50 defer func() { 51 if err := recover(); err == nil { 52 t.Errorf("panic expected, but none received") 53 } 54 }() 55 NewChain().Then(panickingJob). 56 Run() 57 }) 58 59 t.Run("Recovering JobWrapper recovers", func(t *testing.T) { 60 NewChain(Recover(PrintfLogger(log.New(ioutil.Discard, "", 0)))). 61 Then(panickingJob). 62 Run() 63 }) 64 65 t.Run("composed with the *IfStillRunning wrappers", func(t *testing.T) { 66 NewChain(Recover(PrintfLogger(log.New(ioutil.Discard, "", 0)))). 67 Then(panickingJob). 68 Run() 69 }) 70 } 71 72 type countJob struct { 73 m sync.Mutex 74 started int 75 done int 76 delay time.Duration 77 } 78 79 func (j *countJob) Run() { 80 j.m.Lock() 81 j.started++ 82 j.m.Unlock() 83 time.Sleep(j.delay) 84 j.m.Lock() 85 j.done++ 86 j.m.Unlock() 87 } 88 89 func (j *countJob) Started() int { 90 defer j.m.Unlock() 91 j.m.Lock() 92 return j.started 93 } 94 95 func (j *countJob) Done() int { 96 defer j.m.Unlock() 97 j.m.Lock() 98 return j.done 99 } 100 101 func TestChainDelayIfStillRunning(t *testing.T) { 102 103 t.Run("runs immediately", func(t *testing.T) { 104 var j countJob 105 wrappedJob := NewChain(DelayIfStillRunning(DiscardLogger)).Then(&j) 106 go wrappedJob.Run() 107 time.Sleep(2 * time.Millisecond) // Give the job 2ms to complete. 108 if c := j.Done(); c != 1 { 109 t.Errorf("expected job run once, immediately, got %d", c) 110 } 111 }) 112 113 t.Run("second run immediate if first done", func(t *testing.T) { 114 var j countJob 115 wrappedJob := NewChain(DelayIfStillRunning(DiscardLogger)).Then(&j) 116 go func() { 117 go wrappedJob.Run() 118 time.Sleep(time.Millisecond) 119 go wrappedJob.Run() 120 }() 121 time.Sleep(3 * time.Millisecond) // Give both jobs 3ms to complete. 122 if c := j.Done(); c != 2 { 123 t.Errorf("expected job run twice, immediately, got %d", c) 124 } 125 }) 126 127 t.Run("second run delayed if first not done", func(t *testing.T) { 128 var j countJob 129 j.delay = 10 * time.Millisecond 130 wrappedJob := NewChain(DelayIfStillRunning(DiscardLogger)).Then(&j) 131 go func() { 132 go wrappedJob.Run() 133 time.Sleep(time.Millisecond) 134 go wrappedJob.Run() 135 }() 136 137 // After 5ms, the first job is still in progress, and the second job was 138 // run but should be waiting for it to finish. 139 time.Sleep(5 * time.Millisecond) 140 started, done := j.Started(), j.Done() 141 if started != 1 || done != 0 { 142 t.Error("expected first job started, but not finished, got", started, done) 143 } 144 145 // Verify that the second job completes. 146 time.Sleep(25 * time.Millisecond) 147 started, done = j.Started(), j.Done() 148 if started != 2 || done != 2 { 149 t.Error("expected both jobs done, got", started, done) 150 } 151 }) 152 153 } 154 155 func TestChainSkipIfStillRunning(t *testing.T) { 156 157 t.Run("runs immediately", func(t *testing.T) { 158 var j countJob 159 wrappedJob := NewChain(SkipIfStillRunning(DiscardLogger)).Then(&j) 160 go wrappedJob.Run() 161 time.Sleep(2 * time.Millisecond) // Give the job 2ms to complete. 162 if c := j.Done(); c != 1 { 163 t.Errorf("expected job run once, immediately, got %d", c) 164 } 165 }) 166 167 t.Run("second run immediate if first done", func(t *testing.T) { 168 var j countJob 169 wrappedJob := NewChain(SkipIfStillRunning(DiscardLogger)).Then(&j) 170 go func() { 171 go wrappedJob.Run() 172 time.Sleep(time.Millisecond) 173 go wrappedJob.Run() 174 }() 175 time.Sleep(3 * time.Millisecond) // Give both jobs 3ms to complete. 176 if c := j.Done(); c != 2 { 177 t.Errorf("expected job run twice, immediately, got %d", c) 178 } 179 }) 180 181 t.Run("second run skipped if first not done", func(t *testing.T) { 182 var j countJob 183 j.delay = 10 * time.Millisecond 184 wrappedJob := NewChain(SkipIfStillRunning(DiscardLogger)).Then(&j) 185 go func() { 186 go wrappedJob.Run() 187 time.Sleep(time.Millisecond) 188 go wrappedJob.Run() 189 }() 190 191 // After 5ms, the first job is still in progress, and the second job was 192 // aleady skipped. 193 time.Sleep(5 * time.Millisecond) 194 started, done := j.Started(), j.Done() 195 if started != 1 || done != 0 { 196 t.Error("expected first job started, but not finished, got", started, done) 197 } 198 199 // Verify that the first job completes and second does not run. 200 time.Sleep(25 * time.Millisecond) 201 started, done = j.Started(), j.Done() 202 if started != 1 || done != 1 { 203 t.Error("expected second job skipped, got", started, done) 204 } 205 }) 206 207 t.Run("skip 10 jobs on rapid fire", func(t *testing.T) { 208 var j countJob 209 j.delay = 10 * time.Millisecond 210 wrappedJob := NewChain(SkipIfStillRunning(DiscardLogger)).Then(&j) 211 for i := 0; i < 11; i++ { 212 go wrappedJob.Run() 213 } 214 time.Sleep(200 * time.Millisecond) 215 done := j.Done() 216 if done != 1 { 217 t.Error("expected 1 jobs executed, 10 jobs dropped, got", done) 218 } 219 }) 220 221 t.Run("different jobs independent", func(t *testing.T) { 222 var j1, j2 countJob 223 j1.delay = 10 * time.Millisecond 224 j2.delay = 10 * time.Millisecond 225 chain := NewChain(SkipIfStillRunning(DiscardLogger)) 226 wrappedJob1 := chain.Then(&j1) 227 wrappedJob2 := chain.Then(&j2) 228 for i := 0; i < 11; i++ { 229 go wrappedJob1.Run() 230 go wrappedJob2.Run() 231 } 232 time.Sleep(100 * time.Millisecond) 233 var ( 234 done1 = j1.Done() 235 done2 = j2.Done() 236 ) 237 if done1 != 1 || done2 != 1 { 238 t.Error("expected both jobs executed once, got", done1, "and", done2) 239 } 240 }) 241 242 }