diff --git a/go.sum b/go.sum index 2fde5de..6796747 100644 --- a/go.sum +++ b/go.sum @@ -1117,6 +1117,7 @@ github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= diff --git a/pkg/runner/job_executor.go b/pkg/runner/job_executor.go new file mode 100644 index 0000000..c6068ae --- /dev/null +++ b/pkg/runner/job_executor.go @@ -0,0 +1,71 @@ +package runner + +import ( + "context" + "fmt" + + "github.com/nektos/act/pkg/common" + "github.com/nektos/act/pkg/model" +) + +type jobInfo interface { + matrix() map[string]interface{} + steps() []*model.Step + startContainer() common.Executor + stopContainer() common.Executor + closeContainer() common.Executor + newStepExecutor(step *model.Step) common.Executor + interpolateOutputs() common.Executor + result(result string) +} + +func newJobExecutor(info jobInfo) common.Executor { + steps := make([]common.Executor, 0) + + steps = append(steps, func(ctx context.Context) error { + if len(info.matrix()) > 0 { + common.Logger(ctx).Infof("\U0001F9EA Matrix: %v", info.matrix()) + } + return nil + }) + + steps = append(steps, info.startContainer()) + + for i, step := range info.steps() { + if step.ID == "" { + step.ID = fmt.Sprintf("%d", i) + } + stepExec := info.newStepExecutor(step) + steps = append(steps, func(ctx context.Context) error { + err := stepExec(ctx) + if err != nil { + common.Logger(ctx).Errorf("%v", err) + common.SetJobError(ctx, err) + } else if ctx.Err() != nil { + common.Logger(ctx).Errorf("%v", ctx.Err()) + common.SetJobError(ctx, ctx.Err()) + } + return nil + }) + } + steps = append(steps, func(ctx context.Context) error { + err := info.stopContainer()(ctx) + if err != nil { + return err + } + + jobError := common.JobError(ctx) + if jobError != nil { + info.result("failure") + } else { + info.result("success") + } + + return nil + }) + + return common.NewPipelineExecutor(steps...).Finally(info.interpolateOutputs()).Finally(func(ctx context.Context) error { + info.closeContainer() + return nil + }) +} diff --git a/pkg/runner/job_executor_test.go b/pkg/runner/job_executor_test.go new file mode 100644 index 0000000..0444aef --- /dev/null +++ b/pkg/runner/job_executor_test.go @@ -0,0 +1,135 @@ +package runner + +import ( + "context" + "fmt" + "testing" + + "github.com/nektos/act/pkg/common" + "github.com/nektos/act/pkg/model" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +type jobInfoMock struct { + mock.Mock +} + +func (jpm *jobInfoMock) matrix() map[string]interface{} { + args := jpm.Called() + return args.Get(0).(map[string]interface{}) +} + +func (jpm *jobInfoMock) steps() []*model.Step { + args := jpm.Called() + + return args.Get(0).([]*model.Step) +} + +func (jpm *jobInfoMock) startContainer() common.Executor { + args := jpm.Called() + + return args.Get(0).(func(context.Context) error) +} + +func (jpm *jobInfoMock) stopContainer() common.Executor { + args := jpm.Called() + + return args.Get(0).(func(context.Context) error) +} + +func (jpm *jobInfoMock) closeContainer() common.Executor { + args := jpm.Called() + + return args.Get(0).(func(context.Context) error) +} + +func (jpm *jobInfoMock) newStepExecutor(step *model.Step) common.Executor { + args := jpm.Called(step) + + return args.Get(0).(func(context.Context) error) +} + +func (jpm *jobInfoMock) interpolateOutputs() common.Executor { + args := jpm.Called() + + return args.Get(0).(func(context.Context) error) +} + +func (jpm *jobInfoMock) result(result string) { + jpm.Called(result) +} + +func TestNewJobExecutor(t *testing.T) { + table := []struct { + name string + steps []*model.Step + result string + hasError bool + }{ + { + "zeroSteps", + []*model.Step{}, + "success", + false, + }, + { + "stepWithoutPrePost", + []*model.Step{{ + ID: "1", + }}, + "success", + false, + }, + { + "stepWithFailure", + []*model.Step{{ + ID: "1", + }}, + "failure", + true, + }, + } + + for _, tt := range table { + t.Run(tt.name, func(t *testing.T) { + ctx := common.WithJobErrorContainer(context.Background()) + jpm := &jobInfoMock{} + + jpm.On("startContainer").Return(func(ctx context.Context) error { + return nil + }) + + jpm.On("steps").Return(tt.steps) + + for _, stepMock := range tt.steps { + jpm.On("newStepExecutor", stepMock).Return(func(ctx context.Context) error { + if tt.hasError { + return fmt.Errorf("error") + } + return nil + }) + } + + jpm.On("interpolateOutputs").Return(func(ctx context.Context) error { + return nil + }) + + jpm.On("matrix").Return(map[string]interface{}{}) + + jpm.On("stopContainer").Return(func(ctx context.Context) error { + return nil + }) + + jpm.On("result", tt.result) + + jpm.On("closeContainer").Return(func(ctx context.Context) error { + return nil + }) + + executor := newJobExecutor(jpm) + err := executor(ctx) + assert.Nil(t, err) + }) + } +} diff --git a/pkg/runner/run_context.go b/pkg/runner/run_context.go index e072b04..abe0716 100644 --- a/pkg/runner/run_context.go +++ b/pkg/runner/run_context.go @@ -244,57 +244,38 @@ func (rc *RunContext) interpolateOutputs() common.Executor { } } -// Executor returns a pipeline executor for all the steps in the job -func (rc *RunContext) Executor() common.Executor { - steps := make([]common.Executor, 0) +func (rc *RunContext) startContainer() common.Executor { + return rc.startJobContainer() +} - steps = append(steps, func(ctx context.Context) error { - if len(rc.Matrix) > 0 { - common.Logger(ctx).Infof("\U0001F9EA Matrix: %v", rc.Matrix) - } - return nil - }) +func (rc *RunContext) stopContainer() common.Executor { + return rc.stopJobContainer() +} - steps = append(steps, rc.startJobContainer()) - - for i, step := range rc.Run.Job().Steps { - if step.ID == "" { - step.ID = fmt.Sprintf("%d", i) - } - stepExec := rc.newStepExecutor(step) - steps = append(steps, func(ctx context.Context) error { - err := stepExec(ctx) - if err != nil { - common.Logger(ctx).Errorf("%v", err) - common.SetJobError(ctx, err) - } else if ctx.Err() != nil { - common.Logger(ctx).Errorf("%v", ctx.Err()) - common.SetJobError(ctx, ctx.Err()) - } - return nil - }) - } - steps = append(steps, func(ctx context.Context) error { - err := rc.stopJobContainer()(ctx) - if err != nil { - return err - } - - rc.Run.Job().Result = "success" - jobError := common.JobError(ctx) - if jobError != nil { - rc.Run.Job().Result = "failure" - } - - return nil - }) - - return common.NewPipelineExecutor(steps...).Finally(rc.interpolateOutputs()).Finally(func(ctx context.Context) error { +func (rc *RunContext) closeContainer() common.Executor { + return func(ctx context.Context) error { if rc.JobContainer != nil { return rc.JobContainer.Close()(ctx) } return nil - }).If(rc.isEnabled) + } +} + +func (rc *RunContext) matrix() map[string]interface{} { + return rc.Matrix +} + +func (rc *RunContext) result(result string) { + rc.Run.Job().Result = result +} + +func (rc *RunContext) steps() []*model.Step { + return rc.Run.Job().Steps +} + +// Executor returns a pipeline executor for all the steps in the job +func (rc *RunContext) Executor() common.Executor { + return newJobExecutor(rc).If(rc.isEnabled) } // Executor returns a pipeline executor for all the steps in the job