github.com/nektos/act@v0.2.63/pkg/runner/step_action_local_test.go (about) 1 package runner 2 3 import ( 4 "bytes" 5 "context" 6 "io" 7 "path/filepath" 8 "strings" 9 "testing" 10 11 "github.com/nektos/act/pkg/common" 12 "github.com/nektos/act/pkg/model" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/mock" 15 "gopkg.in/yaml.v3" 16 ) 17 18 type stepActionLocalMocks struct { 19 mock.Mock 20 } 21 22 func (salm *stepActionLocalMocks) runAction(step actionStep, actionDir string, remoteAction *remoteAction) common.Executor { 23 args := salm.Called(step, actionDir, remoteAction) 24 return args.Get(0).(func(context.Context) error) 25 } 26 27 func (salm *stepActionLocalMocks) readAction(_ context.Context, step *model.Step, actionDir string, actionPath string, readFile actionYamlReader, writeFile fileWriter) (*model.Action, error) { 28 args := salm.Called(step, actionDir, actionPath, readFile, writeFile) 29 return args.Get(0).(*model.Action), args.Error(1) 30 } 31 32 func TestStepActionLocalTest(t *testing.T) { 33 ctx := context.Background() 34 35 cm := &containerMock{} 36 salm := &stepActionLocalMocks{} 37 38 sal := &stepActionLocal{ 39 readAction: salm.readAction, 40 runAction: salm.runAction, 41 RunContext: &RunContext{ 42 StepResults: map[string]*model.StepResult{}, 43 ExprEval: &expressionEvaluator{}, 44 Config: &Config{ 45 Workdir: "/tmp", 46 }, 47 Run: &model.Run{ 48 JobID: "1", 49 Workflow: &model.Workflow{ 50 Jobs: map[string]*model.Job{ 51 "1": { 52 Defaults: model.Defaults{ 53 Run: model.RunDefaults{ 54 Shell: "bash", 55 }, 56 }, 57 }, 58 }, 59 }, 60 }, 61 JobContainer: cm, 62 }, 63 Step: &model.Step{ 64 ID: "1", 65 Uses: "./path/to/action", 66 }, 67 } 68 69 salm.On("readAction", sal.Step, filepath.Clean("/tmp/path/to/action"), "", mock.Anything, mock.Anything). 70 Return(&model.Action{}, nil) 71 72 cm.On("Copy", "/var/run/act", mock.AnythingOfType("[]*container.FileEntry")).Return(func(ctx context.Context) error { 73 return nil 74 }) 75 76 cm.On("UpdateFromEnv", "/var/run/act/workflow/envs.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error { 77 return nil 78 }) 79 80 cm.On("UpdateFromEnv", "/var/run/act/workflow/statecmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error { 81 return nil 82 }) 83 84 cm.On("UpdateFromEnv", "/var/run/act/workflow/outputcmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error { 85 return nil 86 }) 87 88 cm.On("GetContainerArchive", ctx, "/var/run/act/workflow/pathcmd.txt").Return(io.NopCloser(&bytes.Buffer{}), nil) 89 90 salm.On("runAction", sal, filepath.Clean("/tmp/path/to/action"), (*remoteAction)(nil)).Return(func(ctx context.Context) error { 91 return nil 92 }) 93 94 err := sal.pre()(ctx) 95 assert.Nil(t, err) 96 97 err = sal.main()(ctx) 98 assert.Nil(t, err) 99 100 cm.AssertExpectations(t) 101 salm.AssertExpectations(t) 102 } 103 104 func TestStepActionLocalPost(t *testing.T) { 105 table := []struct { 106 name string 107 stepModel *model.Step 108 actionModel *model.Action 109 initialStepResults map[string]*model.StepResult 110 err error 111 mocks struct { 112 env bool 113 exec bool 114 } 115 }{ 116 { 117 name: "main-success", 118 stepModel: &model.Step{ 119 ID: "step", 120 Uses: "./local/action", 121 }, 122 actionModel: &model.Action{ 123 Runs: model.ActionRuns{ 124 Using: "node16", 125 Post: "post.js", 126 PostIf: "always()", 127 }, 128 }, 129 initialStepResults: map[string]*model.StepResult{ 130 "step": { 131 Conclusion: model.StepStatusSuccess, 132 Outcome: model.StepStatusSuccess, 133 Outputs: map[string]string{}, 134 }, 135 }, 136 mocks: struct { 137 env bool 138 exec bool 139 }{ 140 env: true, 141 exec: true, 142 }, 143 }, 144 { 145 name: "main-failed", 146 stepModel: &model.Step{ 147 ID: "step", 148 Uses: "./local/action", 149 }, 150 actionModel: &model.Action{ 151 Runs: model.ActionRuns{ 152 Using: "node16", 153 Post: "post.js", 154 PostIf: "always()", 155 }, 156 }, 157 initialStepResults: map[string]*model.StepResult{ 158 "step": { 159 Conclusion: model.StepStatusFailure, 160 Outcome: model.StepStatusFailure, 161 Outputs: map[string]string{}, 162 }, 163 }, 164 mocks: struct { 165 env bool 166 exec bool 167 }{ 168 env: true, 169 exec: true, 170 }, 171 }, 172 { 173 name: "skip-if-failed", 174 stepModel: &model.Step{ 175 ID: "step", 176 Uses: "./local/action", 177 }, 178 actionModel: &model.Action{ 179 Runs: model.ActionRuns{ 180 Using: "node16", 181 Post: "post.js", 182 PostIf: "success()", 183 }, 184 }, 185 initialStepResults: map[string]*model.StepResult{ 186 "step": { 187 Conclusion: model.StepStatusFailure, 188 Outcome: model.StepStatusFailure, 189 Outputs: map[string]string{}, 190 }, 191 }, 192 mocks: struct { 193 env bool 194 exec bool 195 }{ 196 env: false, 197 exec: false, 198 }, 199 }, 200 { 201 name: "skip-if-main-skipped", 202 stepModel: &model.Step{ 203 ID: "step", 204 If: yaml.Node{Value: "failure()"}, 205 Uses: "./local/action", 206 }, 207 actionModel: &model.Action{ 208 Runs: model.ActionRuns{ 209 Using: "node16", 210 Post: "post.js", 211 PostIf: "always()", 212 }, 213 }, 214 initialStepResults: map[string]*model.StepResult{ 215 "step": { 216 Conclusion: model.StepStatusSkipped, 217 Outcome: model.StepStatusSkipped, 218 Outputs: map[string]string{}, 219 }, 220 }, 221 mocks: struct { 222 env bool 223 exec bool 224 }{ 225 env: false, 226 exec: false, 227 }, 228 }, 229 } 230 231 for _, tt := range table { 232 t.Run(tt.name, func(t *testing.T) { 233 ctx := context.Background() 234 235 cm := &containerMock{} 236 237 sal := &stepActionLocal{ 238 env: map[string]string{}, 239 RunContext: &RunContext{ 240 Config: &Config{ 241 GitHubInstance: "https://github.com", 242 }, 243 JobContainer: cm, 244 Run: &model.Run{ 245 JobID: "1", 246 Workflow: &model.Workflow{ 247 Jobs: map[string]*model.Job{ 248 "1": {}, 249 }, 250 }, 251 }, 252 StepResults: tt.initialStepResults, 253 }, 254 Step: tt.stepModel, 255 action: tt.actionModel, 256 } 257 sal.RunContext.ExprEval = sal.RunContext.NewExpressionEvaluator(ctx) 258 259 if tt.mocks.exec { 260 suffixMatcher := func(suffix string) interface{} { 261 return mock.MatchedBy(func(array []string) bool { 262 return strings.HasSuffix(array[1], suffix) 263 }) 264 } 265 cm.On("Exec", suffixMatcher("pkg/runner/local/action/post.js"), sal.env, "", "").Return(func(ctx context.Context) error { return tt.err }) 266 267 cm.On("Copy", "/var/run/act", mock.AnythingOfType("[]*container.FileEntry")).Return(func(ctx context.Context) error { 268 return nil 269 }) 270 271 cm.On("UpdateFromEnv", "/var/run/act/workflow/envs.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error { 272 return nil 273 }) 274 275 cm.On("UpdateFromEnv", "/var/run/act/workflow/statecmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error { 276 return nil 277 }) 278 279 cm.On("UpdateFromEnv", "/var/run/act/workflow/outputcmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error { 280 return nil 281 }) 282 283 cm.On("GetContainerArchive", ctx, "/var/run/act/workflow/pathcmd.txt").Return(io.NopCloser(&bytes.Buffer{}), nil) 284 } 285 286 err := sal.post()(ctx) 287 288 assert.Equal(t, tt.err, err) 289 assert.Equal(t, sal.RunContext.StepResults["post-step"], (*model.StepResult)(nil)) 290 cm.AssertExpectations(t) 291 }) 292 } 293 }