mirror of
https://code.forgejo.org/forgejo/runner.git
synced 2025-09-15 18:57:01 +00:00
refactor: extract RunContext Executor in JobExecutor (#984)
This splits the executor from the RunContext into its own function called newJobExecutor. We defined an interface called jobInfo which is implemented by the RunContext. This enables better unit testing because only a small interface needs to be mocked. This is a preparation for implementing pre and post actions. Co-authored-by: Björn Brauer <bjoern.brauer@new-work.se> Co-authored-by: Marcus Noll <marcus.noll@new-work.se> Co-authored-by: Jonas Holland <jonas.holland@new-work.se> Co-authored-by: Robert Kowalski <robert.kowalski@new-work.se> Co-authored-by: Markus Wolf <markus.wolf@new-work.se> Co-authored-by: Björn Brauer <bjoern.brauer@new-work.se> Co-authored-by: Marcus Noll <marcus.noll@new-work.se> Co-authored-by: Jonas Holland <jonas.holland@new-work.se> Co-authored-by: Robert Kowalski <robert.kowalski@new-work.se> Co-authored-by: Markus Wolf <markus.wolf@new-work.se>
This commit is contained in:
parent
2e3ea8d2c8
commit
7404967d25
3 changed files with 232 additions and 45 deletions
71
act/runner/job_executor.go
Normal file
71
act/runner/job_executor.go
Normal file
|
@ -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
|
||||
})
|
||||
}
|
135
act/runner/job_executor_test.go
Normal file
135
act/runner/job_executor_test.go
Normal file
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue