diff --git a/pkg/utils/job_context.go b/pkg/utils/job_context.go index 51d2514ab..e39ccbc04 100644 --- a/pkg/utils/job_context.go +++ b/pkg/utils/job_context.go @@ -18,43 +18,43 @@ import ( // A single JobContext can only run one job at a time. If JobContext.NewJob() is called while a job // is already running, that job will be cancelled and waited on prior to starting the new job. type JobContext struct { - ctx context.Context - cancel context.CancelFunc - wg *sync.WaitGroup - running bool - runningLock *sync.Mutex + Ctx context.Context + JcCancel context.CancelFunc + Wg *sync.WaitGroup + JcRunning bool + RunningLock *sync.Mutex } // NewJob starts a new job with a defined number of workers. If a prior job is running, it is cancelled. func (mw *JobContext) NewJob(ctx context.Context, workers int, returnIfRunning bool) bool { - if mw.runningLock == nil { - mw.runningLock = &sync.Mutex{} + if mw.RunningLock == nil { + mw.RunningLock = &sync.Mutex{} } - mw.runningLock.Lock() - for mw.running { + mw.RunningLock.Lock() + for mw.JcRunning { if returnIfRunning { - mw.runningLock.Unlock() + mw.RunningLock.Unlock() return false } - mw.cancel() - mw.runningLock.Unlock() + mw.JcCancel() + mw.RunningLock.Unlock() mw.Wait() - mw.runningLock.Lock() + mw.RunningLock.Lock() } - mw.running = true - mw.ctx, mw.cancel = context.WithCancel(ctx) - mw.wg = &sync.WaitGroup{} - mw.wg.Add(workers) - mw.runningLock.Unlock() + mw.JcRunning = true + mw.Ctx, mw.JcCancel = context.WithCancel(ctx) + mw.Wg = &sync.WaitGroup{} + mw.Wg.Add(workers) + mw.RunningLock.Unlock() go func() { - mw.wg.Wait() - mw.runningLock.Lock() - mw.running = false - mw.cancel() - mw.runningLock.Unlock() + mw.Wg.Wait() + mw.RunningLock.Lock() + mw.JcRunning = false + mw.JcCancel() + mw.RunningLock.Unlock() }() return true @@ -62,48 +62,48 @@ func (mw *JobContext) NewJob(ctx context.Context, workers int, returnIfRunning b // WorkerDone signals that a worker is finished, like sync.WaitGroup.Done(). func (mw *JobContext) WorkerDone() { - mw.wg.Done() + mw.Wg.Done() } // Wait waits for the current job to complete, like sync.WaitGroup.Wait(). // If no job has been started, always just returns. func (mw *JobContext) Wait() { - if mw.wg != nil { - mw.wg.Wait() + if mw.Wg != nil { + mw.Wg.Wait() } } // Done implements Context.Done(). func (mw *JobContext) Done() <-chan struct{} { - return mw.ctx.Done() + return mw.Ctx.Done() } // Err implements Context.Err(). func (mw *JobContext) Err() error { - return mw.ctx.Err() + return mw.Ctx.Err() } // Deadline implements Context.Deadline(). func (mw *JobContext) Deadline() (time time.Time, ok bool) { - return mw.ctx.Deadline() + return mw.Ctx.Deadline() } // Value implements Context.Value(). func (mw *JobContext) Value(key interface{}) interface{} { - return mw.ctx.Value(key) + return mw.Ctx.Value(key) } // Cancel cancels the JobContext's context. If no job has been started, this does nothing. func (mw *JobContext) Cancel() { - if mw.cancel != nil { - mw.cancel() + if mw.JcCancel != nil { + mw.JcCancel() } } // Running returns true if a job is currently running. func (mw *JobContext) Running() bool { - mw.runningLock.Lock() - defer mw.runningLock.Unlock() + mw.RunningLock.Lock() + defer mw.RunningLock.Unlock() - return mw.running + return mw.JcRunning } diff --git a/pkg/utils/job_context_test.go b/pkg/utils/job_context_test.go new file mode 100644 index 000000000..be051d0b1 --- /dev/null +++ b/pkg/utils/job_context_test.go @@ -0,0 +1,203 @@ +package utils_test + +import ( + "context" + "reflect" + "sync" + "testing" + "time" + + "github.com/ansible/receptor/pkg/utils" +) + +type fields struct { + Ctx context.Context + JcCancel context.CancelFunc + Wg *sync.WaitGroup + JcRunning bool + RunningLock *sync.Mutex +} + +func setupGoodFields() fields { + goodCtx, goodCancel := context.WithCancel(context.Background()) + goodFields := &fields{ + Ctx: goodCtx, + JcCancel: goodCancel, + Wg: &sync.WaitGroup{}, + JcRunning: true, + RunningLock: &sync.Mutex{}, + } + + return *goodFields +} + +func TestJobContextRunning(t *testing.T) { + tests := []struct { + name string + fields fields + want bool + }{ + { + name: "Positive", + fields: setupGoodFields(), + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mw := &utils.JobContext{ + Ctx: tt.fields.Ctx, + JcCancel: tt.fields.JcCancel, + Wg: tt.fields.Wg, + JcRunning: tt.fields.JcRunning, + RunningLock: tt.fields.RunningLock, + } + if got := mw.Running(); got != tt.want { + t.Errorf("JobContext.Running() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestJobContextCancel(t *testing.T) { + tests := []struct { + name string + fields fields + }{ + { + name: "Positive", + fields: setupGoodFields(), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mw := &utils.JobContext{ + Ctx: tt.fields.Ctx, + JcCancel: tt.fields.JcCancel, + Wg: tt.fields.Wg, + JcRunning: tt.fields.JcRunning, + RunningLock: tt.fields.RunningLock, + } + mw.Cancel() + }) + } +} + +func TestJobContextValue(t *testing.T) { + type args struct { + key interface{} + } + + tests := []struct { + name string + fields fields + args args + want interface{} + }{ + { + name: "Positive", + fields: setupGoodFields(), + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mw := &utils.JobContext{ + Ctx: tt.fields.Ctx, + JcCancel: tt.fields.JcCancel, + Wg: tt.fields.Wg, + JcRunning: tt.fields.JcRunning, + RunningLock: tt.fields.RunningLock, + } + if got := mw.Value(tt.args.key); !reflect.DeepEqual(got, tt.want) { + t.Errorf("JobContext.Value() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestJobContextDeadline(t *testing.T) { + tests := []struct { + name string + fields fields + wantTime time.Time + wantOk bool + }{ + { + name: "Positive", + fields: setupGoodFields(), + wantTime: time.Time{}, + wantOk: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mw := &utils.JobContext{ + Ctx: tt.fields.Ctx, + JcCancel: tt.fields.JcCancel, + Wg: tt.fields.Wg, + JcRunning: tt.fields.JcRunning, + RunningLock: tt.fields.RunningLock, + } + gotTime, gotOk := mw.Deadline() + if !reflect.DeepEqual(gotTime, tt.wantTime) { + t.Errorf("JobContext.Deadline() gotTime = %v, want %v", gotTime, tt.wantTime) + } + if gotOk != tt.wantOk { + t.Errorf("JobContext.Deadline() gotOk = %v, want %v", gotOk, tt.wantOk) + } + }) + } +} + +func TestJobContextErr(t *testing.T) { + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "Positive", + fields: setupGoodFields(), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mw := &utils.JobContext{ + Ctx: tt.fields.Ctx, + JcCancel: tt.fields.JcCancel, + Wg: tt.fields.Wg, + JcRunning: tt.fields.JcRunning, + RunningLock: tt.fields.RunningLock, + } + if err := mw.Err(); (err != nil) != tt.wantErr { + t.Errorf("JobContext.Err() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestJobContextWait(t *testing.T) { + tests := []struct { + name string + fields fields + }{ + { + name: "Positive", + fields: setupGoodFields(), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mw := &utils.JobContext{ + Ctx: tt.fields.Ctx, + JcCancel: tt.fields.JcCancel, + Wg: tt.fields.Wg, + JcRunning: tt.fields.JcRunning, + RunningLock: tt.fields.RunningLock, + } + mw.Wait() + }) + } +}