809da7198c
* feat: interpolate the step names Step names could contain expressions refering to event data. Fixes #1353 * test: add missing mock data * fix: setup composite expression evaluator The RunContext does contain a cached ExpressionEvaluator. This should be the case the composite RunContext as well. Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Casey Lee <cplee@nektos.com>
341 lines
7.8 KiB
Go
341 lines
7.8 KiB
Go
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},
|
|
{workdir, "uses-github-empty", "push", "Expected format {org}/{repo}[/path]@ref", platforms},
|
|
{workdir, "uses-github-noref", "push", "Expected format {org}/{repo}[/path]@ref", platforms},
|
|
{workdir, "uses-github-root", "push", "", platforms},
|
|
{workdir, "uses-github-path", "push", "", platforms},
|
|
{workdir, "uses-docker-url", "push", "", platforms},
|
|
{workdir, "uses-github-full-sha", "push", "", platforms},
|
|
{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},
|
|
{workdir, "job-nil-step", "push", "invalid Step 0: missing run or uses key", platforms},
|
|
}
|
|
// 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(stdout, stderr 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::")
|
|
})
|
|
}
|
|
}
|