stash/pkg/job/manager_test.go

346 lines
7.1 KiB
Go

package job
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
const sleepTime time.Duration = 10 * time.Millisecond
type testExec struct {
started chan struct{}
finish chan struct{}
cancelled bool
progress *Progress
}
func newTestExec(finish chan struct{}) *testExec {
return &testExec{
started: make(chan struct{}),
finish: finish,
}
}
func (e *testExec) Execute(ctx context.Context, p *Progress) error {
e.progress = p
close(e.started)
if e.finish != nil {
<-e.finish
select {
case <-ctx.Done():
e.cancelled = true
default:
// fall through
}
}
return nil
}
func TestAdd(t *testing.T) {
m := NewManager()
const jobName = "test job"
exec1 := newTestExec(make(chan struct{}))
jobID := m.Add(context.Background(), jobName, exec1)
// expect jobID to be the first ID
assert := assert.New(t)
assert.Equal(1, jobID)
// wait a tiny bit
time.Sleep(sleepTime)
// expect job to have started
select {
case <-exec1.started:
// ok
default:
t.Error("exec was not started")
}
// expect status to be running
j := m.GetJob(jobID)
assert.Equal(StatusRunning, j.Status)
// expect description to be set
assert.Equal(jobName, j.Description)
// expect startTime and addTime to be set
assert.NotNil(j.StartTime)
assert.NotNil(j.AddTime)
// expect endTime to not be set
assert.Nil(j.EndTime)
// add another job to the queue
const otherJobName = "other job name"
exec2 := newTestExec(make(chan struct{}))
job2ID := m.Add(context.Background(), otherJobName, exec2)
// expect status to be ready
j2 := m.GetJob(job2ID)
assert.Equal(StatusReady, j2.Status)
// expect addTime to be set
assert.NotNil(j2.AddTime)
// expect startTime and endTime to not be set
assert.Nil(j2.StartTime)
assert.Nil(j2.EndTime)
// allow first job to finish
close(exec1.finish)
// wait a tiny bit
time.Sleep(sleepTime)
// expect first job to be finished
j = m.GetJob(jobID)
assert.Equal(StatusFinished, j.Status)
// expect end time to be set
assert.NotNil(j.EndTime)
// expect second job to have started
select {
case <-exec2.started:
// ok
default:
t.Error("exec was not started")
}
// expect status to be running
j2 = m.GetJob(job2ID)
assert.Equal(StatusRunning, j2.Status)
// expect startTime to be set
assert.NotNil(j2.StartTime)
}
func TestCancel(t *testing.T) {
m := NewManager()
// add two jobs
const jobName = "test job"
exec1 := newTestExec(make(chan struct{}))
jobID := m.Add(context.Background(), jobName, exec1)
const otherJobName = "other job"
exec2 := newTestExec(make(chan struct{}))
job2ID := m.Add(context.Background(), otherJobName, exec2)
// wait a tiny bit
time.Sleep(sleepTime)
m.CancelJob(job2ID)
// expect job to be cancelled
assert := assert.New(t)
j := m.GetJob(job2ID)
assert.Equal(StatusCancelled, j.Status)
// expect end time not to be set
assert.Nil(j.EndTime)
// expect job to be removed from the queue
assert.Len(m.GetQueue(), 1)
// expect job to have not have been started
select {
case <-exec2.started:
t.Error("cancelled exec was started")
default:
}
// cancel running job
m.CancelJob(jobID)
// wait a tiny bit
time.Sleep(sleepTime)
// expect status to be stopping
j = m.GetJob(jobID)
assert.Equal(StatusStopping, j.Status)
// expect job to still be in the queue
assert.Len(m.GetQueue(), 1)
// allow first job to finish
close(exec1.finish)
// wait a tiny bit
time.Sleep(sleepTime)
// expect job to be removed from the queue
assert.Len(m.GetQueue(), 0)
// expect job to be cancelled
j = m.GetJob(jobID)
assert.Equal(StatusCancelled, j.Status)
// expect endtime to be set
assert.NotNil(j.EndTime)
// expect job to have been cancelled via context
assert.True(exec1.cancelled)
}
func TestCancelAll(t *testing.T) {
m := NewManager()
// add two jobs
const jobName = "test job"
exec1 := newTestExec(make(chan struct{}))
jobID := m.Add(context.Background(), jobName, exec1)
const otherJobName = "other job"
exec2 := newTestExec(make(chan struct{}))
job2ID := m.Add(context.Background(), otherJobName, exec2)
// wait a tiny bit
time.Sleep(sleepTime)
m.CancelAll()
// allow first job to finish
close(exec1.finish)
// wait a tiny bit
time.Sleep(sleepTime)
// expect all jobs to be cancelled
assert := assert.New(t)
j := m.GetJob(job2ID)
assert.Equal(StatusCancelled, j.Status)
j = m.GetJob(jobID)
assert.Equal(StatusCancelled, j.Status)
// expect all jobs to be removed from the queue
assert.Len(m.GetQueue(), 0)
// expect job to have not have been started
select {
case <-exec2.started:
t.Error("cancelled exec was started")
default:
}
}
func TestSubscribe(t *testing.T) {
m := NewManager()
m.updateThrottleLimit = time.Millisecond * 100
ctx, cancel := context.WithCancel(context.Background())
s := m.Subscribe(ctx)
// add a job
const jobName = "test job"
exec1 := newTestExec(make(chan struct{}))
jobID := m.Add(context.Background(), jobName, exec1)
assert := assert.New(t)
select {
case newJob := <-s.NewJob:
assert.Equal(jobID, newJob.ID)
assert.Equal(jobName, newJob.Description)
assert.Equal(StatusReady, newJob.Status)
case <-time.After(time.Second):
t.Error("new job was not received")
}
// should receive an update when the job begins to run
select {
case updatedJob := <-s.UpdatedJob:
assert.Equal(jobID, updatedJob.ID)
assert.Equal(jobName, updatedJob.Description)
assert.Equal(StatusRunning, updatedJob.Status)
case <-time.After(time.Second):
t.Error("updated job was not received")
}
// wait for it to start
select {
case <-exec1.started:
// ok
case <-time.After(time.Second):
t.Error("exec was not started")
}
// test update throttling
exec1.progress.SetPercent(0.1)
// first update should be immediate
select {
case updatedJob := <-s.UpdatedJob:
assert.Equal(0.1, updatedJob.Progress)
case <-time.After(m.updateThrottleLimit):
t.Error("updated job was not received")
}
exec1.progress.SetPercent(0.2)
exec1.progress.SetPercent(0.3)
// should only receive a single update with the second status
select {
case updatedJob := <-s.UpdatedJob:
assert.Equal(0.3, updatedJob.Progress)
case <-time.After(time.Second):
t.Error("updated job was not received")
}
select {
case <-s.UpdatedJob:
t.Error("received an additional updatedJob")
default:
}
// allow job to finish
close(exec1.finish)
select {
case removedJob := <-s.RemovedJob:
assert.Equal(jobID, removedJob.ID)
assert.Equal(jobName, removedJob.Description)
assert.Equal(StatusFinished, removedJob.Status)
case <-time.After(time.Second):
t.Error("removed job was not received")
}
// should not receive another update
select {
case <-s.UpdatedJob:
t.Error("updated job was received after update")
case <-time.After(m.updateThrottleLimit):
}
// add another job and cancel it
exec2 := newTestExec(make(chan struct{}))
jobID = m.Add(context.Background(), jobName, exec2)
m.CancelJob(jobID)
select {
case removedJob := <-s.RemovedJob:
assert.Equal(jobID, removedJob.ID)
assert.Equal(jobName, removedJob.Description)
assert.Equal(StatusCancelled, removedJob.Status)
case <-time.After(time.Second):
t.Error("cancelled job was not received")
}
cancel()
}