github.com/nektos/act@v0.2.83/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  				nodeToolFullPath: "node",
   262  			}
   263  			rc.ExprEval = rc.NewExpressionEvaluator(ctx)
   264  			executorOrder := make([]string, 0)
   265  
   266  			jim.On("steps").Return(tt.steps)
   267  
   268  			if len(tt.steps) > 0 {
   269  				jim.On("startContainer").Return(func(_ context.Context) error {
   270  					executorOrder = append(executorOrder, "startContainer")
   271  					return nil
   272  				})
   273  			}
   274  
   275  			for i, stepModel := range tt.steps {
   276  				sm := &stepMock{}
   277  
   278  				sfm.On("newStep", stepModel, rc).Return(sm, nil)
   279  
   280  				sm.On("pre").Return(func(_ context.Context) error {
   281  					if tt.preSteps[i] {
   282  						executorOrder = append(executorOrder, "pre"+stepModel.ID)
   283  					}
   284  					return nil
   285  				})
   286  
   287  				sm.On("main").Return(func(_ context.Context) error {
   288  					executorOrder = append(executorOrder, "step"+stepModel.ID)
   289  					if tt.hasError {
   290  						return fmt.Errorf("error")
   291  					}
   292  					return nil
   293  				})
   294  
   295  				sm.On("post").Return(func(_ context.Context) error {
   296  					if tt.postSteps[i] {
   297  						executorOrder = append(executorOrder, "post"+stepModel.ID)
   298  					}
   299  					return nil
   300  				})
   301  
   302  				defer sm.AssertExpectations(t)
   303  			}
   304  
   305  			if len(tt.steps) > 0 {
   306  				jim.On("matrix").Return(map[string]interface{}{})
   307  
   308  				jim.On("interpolateOutputs").Return(func(_ context.Context) error {
   309  					executorOrder = append(executorOrder, "interpolateOutputs")
   310  					return nil
   311  				})
   312  
   313  				if contains("stopContainer", tt.executedSteps) {
   314  					jim.On("stopContainer").Return(func(_ context.Context) error {
   315  						executorOrder = append(executorOrder, "stopContainer")
   316  						return nil
   317  					})
   318  				}
   319  
   320  				jim.On("result", tt.result)
   321  
   322  				jim.On("closeContainer").Return(func(_ context.Context) error {
   323  					executorOrder = append(executorOrder, "closeContainer")
   324  					return nil
   325  				})
   326  			}
   327  
   328  			executor := newJobExecutor(jim, sfm, rc)
   329  			err := executor(ctx)
   330  			assert.Nil(t, err)
   331  			assert.Equal(t, tt.executedSteps, executorOrder)
   332  
   333  			jim.AssertExpectations(t)
   334  			sfm.AssertExpectations(t)
   335  
   336  			fmt.Println("::endgroup::")
   337  		})
   338  	}
   339  }