github.com/nektos/act@v0.2.63/pkg/runner/job_executor_test.go (about)

     1  package runner
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"testing"
     8  
     9  	"github.com/nektos/act/pkg/common"
    10  	"github.com/nektos/act/pkg/container"
    11  	"github.com/nektos/act/pkg/model"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/mock"
    14  )
    15  
    16  func TestJobExecutor(t *testing.T) {
    17  	tables := []TestJobFileInfo{
    18  		{workdir, "uses-and-run-in-one-step", "push", "Invalid run/uses syntax for job:test step:Test", platforms, secrets},
    19  		{workdir, "uses-github-empty", "push", "Expected format {org}/{repo}[/path]@ref", platforms, secrets},
    20  		{workdir, "uses-github-noref", "push", "Expected format {org}/{repo}[/path]@ref", platforms, secrets},
    21  		{workdir, "uses-github-root", "push", "", platforms, secrets},
    22  		{workdir, "uses-github-path", "push", "", platforms, secrets},
    23  		{workdir, "uses-docker-url", "push", "", platforms, secrets},
    24  		{workdir, "uses-github-full-sha", "push", "", platforms, secrets},
    25  		{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},
    26  		{workdir, "job-nil-step", "push", "invalid Step 0: missing run or uses key", platforms, secrets},
    27  	}
    28  	// These tests are sufficient to only check syntax.
    29  	ctx := common.WithDryrun(context.Background(), true)
    30  	for _, table := range tables {
    31  		t.Run(table.workflowPath, func(t *testing.T) {
    32  			table.runTest(ctx, t, &Config{})
    33  		})
    34  	}
    35  }
    36  
    37  type jobInfoMock struct {
    38  	mock.Mock
    39  }
    40  
    41  func (jim *jobInfoMock) matrix() map[string]interface{} {
    42  	args := jim.Called()
    43  	return args.Get(0).(map[string]interface{})
    44  }
    45  
    46  func (jim *jobInfoMock) steps() []*model.Step {
    47  	args := jim.Called()
    48  
    49  	return args.Get(0).([]*model.Step)
    50  }
    51  
    52  func (jim *jobInfoMock) startContainer() common.Executor {
    53  	args := jim.Called()
    54  
    55  	return args.Get(0).(func(context.Context) error)
    56  }
    57  
    58  func (jim *jobInfoMock) stopContainer() common.Executor {
    59  	args := jim.Called()
    60  
    61  	return args.Get(0).(func(context.Context) error)
    62  }
    63  
    64  func (jim *jobInfoMock) closeContainer() common.Executor {
    65  	args := jim.Called()
    66  
    67  	return args.Get(0).(func(context.Context) error)
    68  }
    69  
    70  func (jim *jobInfoMock) interpolateOutputs() common.Executor {
    71  	args := jim.Called()
    72  
    73  	return args.Get(0).(func(context.Context) error)
    74  }
    75  
    76  func (jim *jobInfoMock) result(result string) {
    77  	jim.Called(result)
    78  }
    79  
    80  type jobContainerMock struct {
    81  	container.Container
    82  	container.LinuxContainerEnvironmentExtensions
    83  }
    84  
    85  func (jcm *jobContainerMock) ReplaceLogWriter(_, _ io.Writer) (io.Writer, io.Writer) {
    86  	return nil, nil
    87  }
    88  
    89  type stepFactoryMock struct {
    90  	mock.Mock
    91  }
    92  
    93  func (sfm *stepFactoryMock) newStep(model *model.Step, rc *RunContext) (step, error) {
    94  	args := sfm.Called(model, rc)
    95  	return args.Get(0).(step), args.Error(1)
    96  }
    97  
    98  func TestNewJobExecutor(t *testing.T) {
    99  	table := []struct {
   100  		name          string
   101  		steps         []*model.Step
   102  		preSteps      []bool
   103  		postSteps     []bool
   104  		executedSteps []string
   105  		result        string
   106  		hasError      bool
   107  	}{
   108  		{
   109  			name:          "zeroSteps",
   110  			steps:         []*model.Step{},
   111  			preSteps:      []bool{},
   112  			postSteps:     []bool{},
   113  			executedSteps: []string{},
   114  			result:        "success",
   115  			hasError:      false,
   116  		},
   117  		{
   118  			name: "stepWithoutPrePost",
   119  			steps: []*model.Step{{
   120  				ID: "1",
   121  			}},
   122  			preSteps:  []bool{false},
   123  			postSteps: []bool{false},
   124  			executedSteps: []string{
   125  				"startContainer",
   126  				"step1",
   127  				"stopContainer",
   128  				"interpolateOutputs",
   129  				"closeContainer",
   130  			},
   131  			result:   "success",
   132  			hasError: false,
   133  		},
   134  		{
   135  			name: "stepWithFailure",
   136  			steps: []*model.Step{{
   137  				ID: "1",
   138  			}},
   139  			preSteps:  []bool{false},
   140  			postSteps: []bool{false},
   141  			executedSteps: []string{
   142  				"startContainer",
   143  				"step1",
   144  				"interpolateOutputs",
   145  				"closeContainer",
   146  			},
   147  			result:   "failure",
   148  			hasError: true,
   149  		},
   150  		{
   151  			name: "stepWithPre",
   152  			steps: []*model.Step{{
   153  				ID: "1",
   154  			}},
   155  			preSteps:  []bool{true},
   156  			postSteps: []bool{false},
   157  			executedSteps: []string{
   158  				"startContainer",
   159  				"pre1",
   160  				"step1",
   161  				"stopContainer",
   162  				"interpolateOutputs",
   163  				"closeContainer",
   164  			},
   165  			result:   "success",
   166  			hasError: false,
   167  		},
   168  		{
   169  			name: "stepWithPost",
   170  			steps: []*model.Step{{
   171  				ID: "1",
   172  			}},
   173  			preSteps:  []bool{false},
   174  			postSteps: []bool{true},
   175  			executedSteps: []string{
   176  				"startContainer",
   177  				"step1",
   178  				"post1",
   179  				"stopContainer",
   180  				"interpolateOutputs",
   181  				"closeContainer",
   182  			},
   183  			result:   "success",
   184  			hasError: false,
   185  		},
   186  		{
   187  			name: "stepWithPreAndPost",
   188  			steps: []*model.Step{{
   189  				ID: "1",
   190  			}},
   191  			preSteps:  []bool{true},
   192  			postSteps: []bool{true},
   193  			executedSteps: []string{
   194  				"startContainer",
   195  				"pre1",
   196  				"step1",
   197  				"post1",
   198  				"stopContainer",
   199  				"interpolateOutputs",
   200  				"closeContainer",
   201  			},
   202  			result:   "success",
   203  			hasError: false,
   204  		},
   205  		{
   206  			name: "stepsWithPreAndPost",
   207  			steps: []*model.Step{{
   208  				ID: "1",
   209  			}, {
   210  				ID: "2",
   211  			}, {
   212  				ID: "3",
   213  			}},
   214  			preSteps:  []bool{true, false, true},
   215  			postSteps: []bool{false, true, true},
   216  			executedSteps: []string{
   217  				"startContainer",
   218  				"pre1",
   219  				"pre3",
   220  				"step1",
   221  				"step2",
   222  				"step3",
   223  				"post3",
   224  				"post2",
   225  				"stopContainer",
   226  				"interpolateOutputs",
   227  				"closeContainer",
   228  			},
   229  			result:   "success",
   230  			hasError: false,
   231  		},
   232  	}
   233  
   234  	contains := func(needle string, haystack []string) bool {
   235  		for _, item := range haystack {
   236  			if item == needle {
   237  				return true
   238  			}
   239  		}
   240  		return false
   241  	}
   242  
   243  	for _, tt := range table {
   244  		t.Run(tt.name, func(t *testing.T) {
   245  			fmt.Printf("::group::%s\n", tt.name)
   246  
   247  			ctx := common.WithJobErrorContainer(context.Background())
   248  			jim := &jobInfoMock{}
   249  			sfm := &stepFactoryMock{}
   250  			rc := &RunContext{
   251  				JobContainer: &jobContainerMock{},
   252  				Run: &model.Run{
   253  					JobID: "test",
   254  					Workflow: &model.Workflow{
   255  						Jobs: map[string]*model.Job{
   256  							"test": {},
   257  						},
   258  					},
   259  				},
   260  				Config: &Config{},
   261  			}
   262  			rc.ExprEval = rc.NewExpressionEvaluator(ctx)
   263  			executorOrder := make([]string, 0)
   264  
   265  			jim.On("steps").Return(tt.steps)
   266  
   267  			if len(tt.steps) > 0 {
   268  				jim.On("startContainer").Return(func(ctx context.Context) error {
   269  					executorOrder = append(executorOrder, "startContainer")
   270  					return nil
   271  				})
   272  			}
   273  
   274  			for i, stepModel := range tt.steps {
   275  				i := i
   276  				stepModel := stepModel
   277  
   278  				sm := &stepMock{}
   279  
   280  				sfm.On("newStep", stepModel, rc).Return(sm, nil)
   281  
   282  				sm.On("pre").Return(func(ctx context.Context) error {
   283  					if tt.preSteps[i] {
   284  						executorOrder = append(executorOrder, "pre"+stepModel.ID)
   285  					}
   286  					return nil
   287  				})
   288  
   289  				sm.On("main").Return(func(ctx context.Context) error {
   290  					executorOrder = append(executorOrder, "step"+stepModel.ID)
   291  					if tt.hasError {
   292  						return fmt.Errorf("error")
   293  					}
   294  					return nil
   295  				})
   296  
   297  				sm.On("post").Return(func(ctx context.Context) error {
   298  					if tt.postSteps[i] {
   299  						executorOrder = append(executorOrder, "post"+stepModel.ID)
   300  					}
   301  					return nil
   302  				})
   303  
   304  				defer sm.AssertExpectations(t)
   305  			}
   306  
   307  			if len(tt.steps) > 0 {
   308  				jim.On("matrix").Return(map[string]interface{}{})
   309  
   310  				jim.On("interpolateOutputs").Return(func(ctx context.Context) error {
   311  					executorOrder = append(executorOrder, "interpolateOutputs")
   312  					return nil
   313  				})
   314  
   315  				if contains("stopContainer", tt.executedSteps) {
   316  					jim.On("stopContainer").Return(func(ctx context.Context) error {
   317  						executorOrder = append(executorOrder, "stopContainer")
   318  						return nil
   319  					})
   320  				}
   321  
   322  				jim.On("result", tt.result)
   323  
   324  				jim.On("closeContainer").Return(func(ctx context.Context) error {
   325  					executorOrder = append(executorOrder, "closeContainer")
   326  					return nil
   327  				})
   328  			}
   329  
   330  			executor := newJobExecutor(jim, sfm, rc)
   331  			err := executor(ctx)
   332  			assert.Nil(t, err)
   333  			assert.Equal(t, tt.executedSteps, executorOrder)
   334  
   335  			jim.AssertExpectations(t)
   336  			sfm.AssertExpectations(t)
   337  
   338  			fmt.Println("::endgroup::")
   339  		})
   340  	}
   341  }