github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/fs/rc/jobs/job_test.go (about) 1 package jobs 2 3 import ( 4 "context" 5 "runtime" 6 "testing" 7 "time" 8 9 "github.com/pkg/errors" 10 "github.com/rclone/rclone/fs/rc" 11 "github.com/rclone/rclone/fs/rc/rcflags" 12 "github.com/rclone/rclone/fstest/testy" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 ) 16 17 func TestNewJobs(t *testing.T) { 18 jobs := newJobs() 19 assert.Equal(t, 0, len(jobs.jobs)) 20 } 21 22 func TestJobsKickExpire(t *testing.T) { 23 testy.SkipUnreliable(t) 24 jobs := newJobs() 25 jobs.opt.JobExpireInterval = time.Millisecond 26 assert.Equal(t, false, jobs.expireRunning) 27 jobs.kickExpire() 28 jobs.mu.Lock() 29 assert.Equal(t, true, jobs.expireRunning) 30 jobs.mu.Unlock() 31 time.Sleep(10 * time.Millisecond) 32 jobs.mu.Lock() 33 assert.Equal(t, false, jobs.expireRunning) 34 jobs.mu.Unlock() 35 } 36 37 func TestJobsExpire(t *testing.T) { 38 testy.SkipUnreliable(t) 39 wait := make(chan struct{}) 40 jobs := newJobs() 41 jobs.opt.JobExpireInterval = time.Millisecond 42 assert.Equal(t, false, jobs.expireRunning) 43 job := jobs.NewAsyncJob(func(ctx context.Context, in rc.Params) (rc.Params, error) { 44 defer close(wait) 45 return in, nil 46 }, rc.Params{}) 47 <-wait 48 assert.Equal(t, 1, len(jobs.jobs)) 49 jobs.Expire() 50 assert.Equal(t, 1, len(jobs.jobs)) 51 jobs.mu.Lock() 52 job.mu.Lock() 53 job.EndTime = time.Now().Add(-rcflags.Opt.JobExpireDuration - 60*time.Second) 54 assert.Equal(t, true, jobs.expireRunning) 55 job.mu.Unlock() 56 jobs.mu.Unlock() 57 time.Sleep(250 * time.Millisecond) 58 jobs.mu.Lock() 59 assert.Equal(t, false, jobs.expireRunning) 60 assert.Equal(t, 0, len(jobs.jobs)) 61 jobs.mu.Unlock() 62 } 63 64 var noopFn = func(ctx context.Context, in rc.Params) (rc.Params, error) { 65 return nil, nil 66 } 67 68 func TestJobsIDs(t *testing.T) { 69 jobs := newJobs() 70 job1 := jobs.NewAsyncJob(noopFn, rc.Params{}) 71 job2 := jobs.NewAsyncJob(noopFn, rc.Params{}) 72 wantIDs := []int64{job1.ID, job2.ID} 73 gotIDs := jobs.IDs() 74 require.Equal(t, 2, len(gotIDs)) 75 if gotIDs[0] != wantIDs[0] { 76 gotIDs[0], gotIDs[1] = gotIDs[1], gotIDs[0] 77 } 78 assert.Equal(t, wantIDs, gotIDs) 79 } 80 81 func TestJobsGet(t *testing.T) { 82 jobs := newJobs() 83 job := jobs.NewAsyncJob(noopFn, rc.Params{}) 84 assert.Equal(t, job, jobs.Get(job.ID)) 85 assert.Nil(t, jobs.Get(123123123123)) 86 } 87 88 var longFn = func(ctx context.Context, in rc.Params) (rc.Params, error) { 89 time.Sleep(1 * time.Hour) 90 return nil, nil 91 } 92 93 var shortFn = func(ctx context.Context, in rc.Params) (rc.Params, error) { 94 time.Sleep(time.Millisecond) 95 return nil, nil 96 } 97 98 var ctxFn = func(ctx context.Context, in rc.Params) (rc.Params, error) { 99 select { 100 case <-ctx.Done(): 101 return nil, ctx.Err() 102 } 103 } 104 105 const ( 106 sleepTime = 100 * time.Millisecond 107 floatSleepTime = float64(sleepTime) / 1e9 / 2 108 ) 109 110 // sleep for some time so job.Duration is non-0 111 func sleepJob() { 112 time.Sleep(sleepTime) 113 } 114 115 func TestJobFinish(t *testing.T) { 116 jobs := newJobs() 117 job := jobs.NewAsyncJob(longFn, rc.Params{}) 118 sleepJob() 119 120 assert.Equal(t, true, job.EndTime.IsZero()) 121 assert.Equal(t, rc.Params(nil), job.Output) 122 assert.Equal(t, 0.0, job.Duration) 123 assert.Equal(t, "", job.Error) 124 assert.Equal(t, false, job.Success) 125 assert.Equal(t, false, job.Finished) 126 127 wantOut := rc.Params{"a": 1} 128 job.finish(wantOut, nil) 129 130 assert.Equal(t, false, job.EndTime.IsZero()) 131 assert.Equal(t, wantOut, job.Output) 132 assert.True(t, job.Duration >= floatSleepTime) 133 assert.Equal(t, "", job.Error) 134 assert.Equal(t, true, job.Success) 135 assert.Equal(t, true, job.Finished) 136 137 job = jobs.NewAsyncJob(longFn, rc.Params{}) 138 sleepJob() 139 job.finish(nil, nil) 140 141 assert.Equal(t, false, job.EndTime.IsZero()) 142 assert.Equal(t, rc.Params{}, job.Output) 143 assert.True(t, job.Duration >= floatSleepTime) 144 assert.Equal(t, "", job.Error) 145 assert.Equal(t, true, job.Success) 146 assert.Equal(t, true, job.Finished) 147 148 job = jobs.NewAsyncJob(longFn, rc.Params{}) 149 sleepJob() 150 job.finish(wantOut, errors.New("potato")) 151 152 assert.Equal(t, false, job.EndTime.IsZero()) 153 assert.Equal(t, wantOut, job.Output) 154 assert.True(t, job.Duration >= floatSleepTime) 155 assert.Equal(t, "potato", job.Error) 156 assert.Equal(t, false, job.Success) 157 assert.Equal(t, true, job.Finished) 158 } 159 160 // We've tested the functionality of run() already as it is 161 // part of NewJob, now just test the panic catching 162 func TestJobRunPanic(t *testing.T) { 163 wait := make(chan struct{}) 164 boom := func(ctx context.Context, in rc.Params) (rc.Params, error) { 165 sleepJob() 166 defer close(wait) 167 panic("boom") 168 } 169 170 jobs := newJobs() 171 job := jobs.NewAsyncJob(boom, rc.Params{}) 172 <-wait 173 runtime.Gosched() // yield to make sure job is updated 174 175 // Wait a short time for the panic to propagate 176 for i := uint(0); i < 10; i++ { 177 job.mu.Lock() 178 e := job.Error 179 job.mu.Unlock() 180 if e != "" { 181 break 182 } 183 time.Sleep(time.Millisecond << i) 184 } 185 186 job.mu.Lock() 187 assert.Equal(t, false, job.EndTime.IsZero()) 188 assert.Equal(t, rc.Params{}, job.Output) 189 assert.True(t, job.Duration >= floatSleepTime) 190 assert.Contains(t, job.Error, "panic received: boom") 191 assert.Equal(t, false, job.Success) 192 assert.Equal(t, true, job.Finished) 193 job.mu.Unlock() 194 } 195 196 func TestJobsNewJob(t *testing.T) { 197 jobID = 0 198 jobs := newJobs() 199 job := jobs.NewAsyncJob(noopFn, rc.Params{}) 200 assert.Equal(t, int64(1), job.ID) 201 assert.Equal(t, job, jobs.Get(1)) 202 assert.NotEmpty(t, job.Stop) 203 } 204 205 func TestStartJob(t *testing.T) { 206 jobID = 0 207 out, err := StartAsyncJob(longFn, rc.Params{}) 208 assert.NoError(t, err) 209 assert.Equal(t, rc.Params{"jobid": int64(1)}, out) 210 } 211 212 func TestExecuteJob(t *testing.T) { 213 jobID = 0 214 _, id, err := ExecuteJob(context.Background(), shortFn, rc.Params{}) 215 assert.NoError(t, err) 216 assert.Equal(t, int64(1), id) 217 } 218 219 func TestExecuteJobErrorPropagation(t *testing.T) { 220 jobID = 0 221 222 testErr := errors.New("test error") 223 errorFn := func(ctx context.Context, in rc.Params) (out rc.Params, err error) { 224 return nil, testErr 225 } 226 _, _, err := ExecuteJob(context.Background(), errorFn, rc.Params{}) 227 assert.Equal(t, testErr, err) 228 } 229 230 func TestRcJobStatus(t *testing.T) { 231 jobID = 0 232 _, err := StartAsyncJob(longFn, rc.Params{}) 233 assert.NoError(t, err) 234 235 call := rc.Calls.Get("job/status") 236 assert.NotNil(t, call) 237 in := rc.Params{"jobid": 1} 238 out, err := call.Fn(context.Background(), in) 239 require.NoError(t, err) 240 require.NotNil(t, out) 241 assert.Equal(t, float64(1), out["id"]) 242 assert.Equal(t, "", out["error"]) 243 assert.Equal(t, false, out["finished"]) 244 assert.Equal(t, false, out["success"]) 245 246 in = rc.Params{"jobid": 123123123} 247 _, err = call.Fn(context.Background(), in) 248 require.Error(t, err) 249 assert.Contains(t, err.Error(), "job not found") 250 251 in = rc.Params{"jobidx": 123123123} 252 _, err = call.Fn(context.Background(), in) 253 require.Error(t, err) 254 assert.Contains(t, err.Error(), "Didn't find key") 255 } 256 257 func TestRcJobList(t *testing.T) { 258 jobID = 0 259 _, err := StartAsyncJob(longFn, rc.Params{}) 260 assert.NoError(t, err) 261 262 call := rc.Calls.Get("job/list") 263 assert.NotNil(t, call) 264 in := rc.Params{} 265 out, err := call.Fn(context.Background(), in) 266 require.NoError(t, err) 267 require.NotNil(t, out) 268 assert.Equal(t, rc.Params{"jobids": []int64{1}}, out) 269 } 270 271 func TestRcAsyncJobStop(t *testing.T) { 272 jobID = 0 273 _, err := StartAsyncJob(ctxFn, rc.Params{}) 274 assert.NoError(t, err) 275 276 call := rc.Calls.Get("job/stop") 277 assert.NotNil(t, call) 278 in := rc.Params{"jobid": 1} 279 out, err := call.Fn(context.Background(), in) 280 require.NoError(t, err) 281 require.Empty(t, out) 282 283 in = rc.Params{"jobid": 123123123} 284 _, err = call.Fn(context.Background(), in) 285 require.Error(t, err) 286 assert.Contains(t, err.Error(), "job not found") 287 288 in = rc.Params{"jobidx": 123123123} 289 _, err = call.Fn(context.Background(), in) 290 require.Error(t, err) 291 assert.Contains(t, err.Error(), "Didn't find key") 292 293 time.Sleep(10 * time.Millisecond) 294 295 call = rc.Calls.Get("job/status") 296 assert.NotNil(t, call) 297 in = rc.Params{"jobid": 1} 298 out, err = call.Fn(context.Background(), in) 299 require.NoError(t, err) 300 require.NotNil(t, out) 301 assert.Equal(t, float64(1), out["id"]) 302 assert.Equal(t, "context canceled", out["error"]) 303 assert.Equal(t, true, out["finished"]) 304 assert.Equal(t, false, out["success"]) 305 } 306 307 func TestRcSyncJobStop(t *testing.T) { 308 ctx, cancel := context.WithCancel(context.Background()) 309 go func() { 310 jobID = 0 311 _, id, err := ExecuteJob(ctx, ctxFn, rc.Params{}) 312 assert.Error(t, err) 313 assert.Equal(t, int64(1), id) 314 }() 315 316 time.Sleep(10 * time.Millisecond) 317 318 call := rc.Calls.Get("job/stop") 319 assert.NotNil(t, call) 320 in := rc.Params{"jobid": 1} 321 out, err := call.Fn(context.Background(), in) 322 require.NoError(t, err) 323 require.Empty(t, out) 324 325 in = rc.Params{"jobid": 123123123} 326 _, err = call.Fn(context.Background(), in) 327 require.Error(t, err) 328 assert.Contains(t, err.Error(), "job not found") 329 330 in = rc.Params{"jobidx": 123123123} 331 _, err = call.Fn(context.Background(), in) 332 require.Error(t, err) 333 assert.Contains(t, err.Error(), "Didn't find key") 334 335 cancel() 336 time.Sleep(10 * time.Millisecond) 337 338 call = rc.Calls.Get("job/status") 339 assert.NotNil(t, call) 340 in = rc.Params{"jobid": 1} 341 out, err = call.Fn(context.Background(), in) 342 require.NoError(t, err) 343 require.NotNil(t, out) 344 assert.Equal(t, float64(1), out["id"]) 345 assert.Equal(t, "context canceled", out["error"]) 346 assert.Equal(t, true, out["finished"]) 347 assert.Equal(t, false, out["success"]) 348 }