diff options
Diffstat (limited to 'pkg/runner/job_executor_test.go')
-rw-r--r-- | pkg/runner/job_executor_test.go | 341 |
1 files changed, 341 insertions, 0 deletions
diff --git a/pkg/runner/job_executor_test.go b/pkg/runner/job_executor_test.go new file mode 100644 index 0000000..ac7725f --- /dev/null +++ b/pkg/runner/job_executor_test.go @@ -0,0 +1,341 @@ +package runner + +import ( + "context" + "fmt" + "io" + "testing" + + "github.com/nektos/act/pkg/common" + "github.com/nektos/act/pkg/container" + "github.com/nektos/act/pkg/model" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestJobExecutor(t *testing.T) { + tables := []TestJobFileInfo{ + {workdir, "uses-and-run-in-one-step", "push", "Invalid run/uses syntax for job:test step:Test", platforms, secrets}, + {workdir, "uses-github-empty", "push", "Expected format {org}/{repo}[/path]@ref", platforms, secrets}, + {workdir, "uses-github-noref", "push", "Expected format {org}/{repo}[/path]@ref", platforms, secrets}, + {workdir, "uses-github-root", "push", "", platforms, secrets}, + {workdir, "uses-github-path", "push", "", platforms, secrets}, + {workdir, "uses-docker-url", "push", "", platforms, secrets}, + {workdir, "uses-github-full-sha", "push", "", platforms, secrets}, + {workdir, "uses-github-short-sha", "push", "Unable to resolve action `actions/hello-world-docker-action@b136eb8`, the provided ref `b136eb8` is the shortened version of a commit SHA, which is not supported. Please use the full commit SHA `b136eb8894c5cb1dd5807da824be97ccdf9b5423` instead", platforms, secrets}, + {workdir, "job-nil-step", "push", "invalid Step 0: missing run or uses key", platforms, secrets}, + } + // These tests are sufficient to only check syntax. + ctx := common.WithDryrun(context.Background(), true) + for _, table := range tables { + t.Run(table.workflowPath, func(t *testing.T) { + table.runTest(ctx, t, &Config{}) + }) + } +} + +type jobInfoMock struct { + mock.Mock +} + +func (jim *jobInfoMock) matrix() map[string]interface{} { + args := jim.Called() + return args.Get(0).(map[string]interface{}) +} + +func (jim *jobInfoMock) steps() []*model.Step { + args := jim.Called() + + return args.Get(0).([]*model.Step) +} + +func (jim *jobInfoMock) startContainer() common.Executor { + args := jim.Called() + + return args.Get(0).(func(context.Context) error) +} + +func (jim *jobInfoMock) stopContainer() common.Executor { + args := jim.Called() + + return args.Get(0).(func(context.Context) error) +} + +func (jim *jobInfoMock) closeContainer() common.Executor { + args := jim.Called() + + return args.Get(0).(func(context.Context) error) +} + +func (jim *jobInfoMock) interpolateOutputs() common.Executor { + args := jim.Called() + + return args.Get(0).(func(context.Context) error) +} + +func (jim *jobInfoMock) result(result string) { + jim.Called(result) +} + +type jobContainerMock struct { + container.Container + container.LinuxContainerEnvironmentExtensions +} + +func (jcm *jobContainerMock) ReplaceLogWriter(_, _ io.Writer) (io.Writer, io.Writer) { + return nil, nil +} + +type stepFactoryMock struct { + mock.Mock +} + +func (sfm *stepFactoryMock) newStep(model *model.Step, rc *RunContext) (step, error) { + args := sfm.Called(model, rc) + return args.Get(0).(step), args.Error(1) +} + +func TestNewJobExecutor(t *testing.T) { + table := []struct { + name string + steps []*model.Step + preSteps []bool + postSteps []bool + executedSteps []string + result string + hasError bool + }{ + { + name: "zeroSteps", + steps: []*model.Step{}, + preSteps: []bool{}, + postSteps: []bool{}, + executedSteps: []string{}, + result: "success", + hasError: false, + }, + { + name: "stepWithoutPrePost", + steps: []*model.Step{{ + ID: "1", + }}, + preSteps: []bool{false}, + postSteps: []bool{false}, + executedSteps: []string{ + "startContainer", + "step1", + "stopContainer", + "interpolateOutputs", + "closeContainer", + }, + result: "success", + hasError: false, + }, + { + name: "stepWithFailure", + steps: []*model.Step{{ + ID: "1", + }}, + preSteps: []bool{false}, + postSteps: []bool{false}, + executedSteps: []string{ + "startContainer", + "step1", + "interpolateOutputs", + "closeContainer", + }, + result: "failure", + hasError: true, + }, + { + name: "stepWithPre", + steps: []*model.Step{{ + ID: "1", + }}, + preSteps: []bool{true}, + postSteps: []bool{false}, + executedSteps: []string{ + "startContainer", + "pre1", + "step1", + "stopContainer", + "interpolateOutputs", + "closeContainer", + }, + result: "success", + hasError: false, + }, + { + name: "stepWithPost", + steps: []*model.Step{{ + ID: "1", + }}, + preSteps: []bool{false}, + postSteps: []bool{true}, + executedSteps: []string{ + "startContainer", + "step1", + "post1", + "stopContainer", + "interpolateOutputs", + "closeContainer", + }, + result: "success", + hasError: false, + }, + { + name: "stepWithPreAndPost", + steps: []*model.Step{{ + ID: "1", + }}, + preSteps: []bool{true}, + postSteps: []bool{true}, + executedSteps: []string{ + "startContainer", + "pre1", + "step1", + "post1", + "stopContainer", + "interpolateOutputs", + "closeContainer", + }, + result: "success", + hasError: false, + }, + { + name: "stepsWithPreAndPost", + steps: []*model.Step{{ + ID: "1", + }, { + ID: "2", + }, { + ID: "3", + }}, + preSteps: []bool{true, false, true}, + postSteps: []bool{false, true, true}, + executedSteps: []string{ + "startContainer", + "pre1", + "pre3", + "step1", + "step2", + "step3", + "post3", + "post2", + "stopContainer", + "interpolateOutputs", + "closeContainer", + }, + result: "success", + hasError: false, + }, + } + + contains := func(needle string, haystack []string) bool { + for _, item := range haystack { + if item == needle { + return true + } + } + return false + } + + for _, tt := range table { + t.Run(tt.name, func(t *testing.T) { + fmt.Printf("::group::%s\n", tt.name) + + ctx := common.WithJobErrorContainer(context.Background()) + jim := &jobInfoMock{} + sfm := &stepFactoryMock{} + rc := &RunContext{ + JobContainer: &jobContainerMock{}, + Run: &model.Run{ + JobID: "test", + Workflow: &model.Workflow{ + Jobs: map[string]*model.Job{ + "test": {}, + }, + }, + }, + Config: &Config{}, + } + rc.ExprEval = rc.NewExpressionEvaluator(ctx) + executorOrder := make([]string, 0) + + jim.On("steps").Return(tt.steps) + + if len(tt.steps) > 0 { + jim.On("startContainer").Return(func(ctx context.Context) error { + executorOrder = append(executorOrder, "startContainer") + return nil + }) + } + + for i, stepModel := range tt.steps { + i := i + stepModel := stepModel + + sm := &stepMock{} + + sfm.On("newStep", stepModel, rc).Return(sm, nil) + + sm.On("pre").Return(func(ctx context.Context) error { + if tt.preSteps[i] { + executorOrder = append(executorOrder, "pre"+stepModel.ID) + } + return nil + }) + + sm.On("main").Return(func(ctx context.Context) error { + executorOrder = append(executorOrder, "step"+stepModel.ID) + if tt.hasError { + return fmt.Errorf("error") + } + return nil + }) + + sm.On("post").Return(func(ctx context.Context) error { + if tt.postSteps[i] { + executorOrder = append(executorOrder, "post"+stepModel.ID) + } + return nil + }) + + defer sm.AssertExpectations(t) + } + + if len(tt.steps) > 0 { + jim.On("matrix").Return(map[string]interface{}{}) + + jim.On("interpolateOutputs").Return(func(ctx context.Context) error { + executorOrder = append(executorOrder, "interpolateOutputs") + return nil + }) + + if contains("stopContainer", tt.executedSteps) { + jim.On("stopContainer").Return(func(ctx context.Context) error { + executorOrder = append(executorOrder, "stopContainer") + return nil + }) + } + + jim.On("result", tt.result) + + jim.On("closeContainer").Return(func(ctx context.Context) error { + executorOrder = append(executorOrder, "closeContainer") + return nil + }) + } + + executor := newJobExecutor(jim, sfm, rc) + err := executor(ctx) + assert.Nil(t, err) + assert.Equal(t, tt.executedSteps, executorOrder) + + jim.AssertExpectations(t) + sfm.AssertExpectations(t) + + fmt.Println("::endgroup::") + }) + } +} |