github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/fs/rc/job_test.go (about)

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