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  }