github.com/nektos/act@v0.2.83/pkg/model/workflow_test.go (about) 1 package model 2 3 import ( 4 "strings" 5 "testing" 6 7 "github.com/stretchr/testify/assert" 8 "github.com/stretchr/testify/require" 9 "gopkg.in/yaml.v3" 10 ) 11 12 func TestReadWorkflow_StringEvent(t *testing.T) { 13 yaml := ` 14 name: local-action-docker-url 15 on: push 16 17 jobs: 18 test: 19 runs-on: ubuntu-latest 20 steps: 21 - uses: ./actions/docker-url 22 ` 23 24 workflow, err := ReadWorkflow(strings.NewReader(yaml), false) 25 assert.NoError(t, err, "read workflow should succeed") 26 27 assert.Len(t, workflow.On(), 1) 28 assert.Contains(t, workflow.On(), "push") 29 } 30 31 func TestReadWorkflow_ListEvent(t *testing.T) { 32 yaml := ` 33 name: local-action-docker-url 34 on: [push, pull_request] 35 36 jobs: 37 test: 38 runs-on: ubuntu-latest 39 steps: 40 - uses: ./actions/docker-url 41 ` 42 43 workflow, err := ReadWorkflow(strings.NewReader(yaml), false) 44 assert.NoError(t, err, "read workflow should succeed") 45 46 assert.Len(t, workflow.On(), 2) 47 assert.Contains(t, workflow.On(), "push") 48 assert.Contains(t, workflow.On(), "pull_request") 49 } 50 51 func TestReadWorkflow_MapEvent(t *testing.T) { 52 yaml := ` 53 name: local-action-docker-url 54 on: 55 push: 56 branches: 57 - master 58 pull_request: 59 branches: 60 - master 61 62 jobs: 63 test: 64 runs-on: ubuntu-latest 65 steps: 66 - uses: ./actions/docker-url 67 ` 68 69 workflow, err := ReadWorkflow(strings.NewReader(yaml), false) 70 assert.NoError(t, err, "read workflow should succeed") 71 assert.Len(t, workflow.On(), 2) 72 assert.Contains(t, workflow.On(), "push") 73 assert.Contains(t, workflow.On(), "pull_request") 74 } 75 76 func TestReadWorkflow_RunsOnLabels(t *testing.T) { 77 yaml := ` 78 name: local-action-docker-url 79 80 jobs: 81 test: 82 container: nginx:latest 83 runs-on: 84 labels: ubuntu-latest 85 steps: 86 - uses: ./actions/docker-url` 87 88 workflow, err := ReadWorkflow(strings.NewReader(yaml), false) 89 assert.NoError(t, err, "read workflow should succeed") 90 assert.Equal(t, workflow.Jobs["test"].RunsOn(), []string{"ubuntu-latest"}) 91 } 92 93 func TestReadWorkflow_RunsOnLabelsWithGroup(t *testing.T) { 94 yaml := ` 95 name: local-action-docker-url 96 97 jobs: 98 test: 99 container: nginx:latest 100 runs-on: 101 labels: [ubuntu-latest] 102 group: linux 103 steps: 104 - uses: ./actions/docker-url` 105 106 workflow, err := ReadWorkflow(strings.NewReader(yaml), false) 107 assert.NoError(t, err, "read workflow should succeed") 108 assert.Equal(t, workflow.Jobs["test"].RunsOn(), []string{"ubuntu-latest", "linux"}) 109 } 110 111 func TestReadWorkflow_StringContainer(t *testing.T) { 112 yaml := ` 113 name: local-action-docker-url 114 115 jobs: 116 test: 117 container: nginx:latest 118 runs-on: ubuntu-latest 119 steps: 120 - uses: ./actions/docker-url 121 test2: 122 container: 123 image: nginx:latest 124 env: 125 foo: bar 126 runs-on: ubuntu-latest 127 steps: 128 - uses: ./actions/docker-url 129 ` 130 131 workflow, err := ReadWorkflow(strings.NewReader(yaml), false) 132 assert.NoError(t, err, "read workflow should succeed") 133 assert.Len(t, workflow.Jobs, 2) 134 assert.Contains(t, workflow.Jobs["test"].Container().Image, "nginx:latest") 135 assert.Contains(t, workflow.Jobs["test2"].Container().Image, "nginx:latest") 136 assert.Contains(t, workflow.Jobs["test2"].Container().Env["foo"], "bar") 137 } 138 139 func TestReadWorkflow_ObjectContainer(t *testing.T) { 140 yaml := ` 141 name: local-action-docker-url 142 143 jobs: 144 test: 145 container: 146 image: r.example.org/something:latest 147 credentials: 148 username: registry-username 149 password: registry-password 150 env: 151 HOME: /home/user 152 volumes: 153 - my_docker_volume:/volume_mount 154 - /data/my_data 155 - /source/directory:/destination/directory 156 runs-on: ubuntu-latest 157 steps: 158 - uses: ./actions/docker-url 159 ` 160 161 workflow, err := ReadWorkflow(strings.NewReader(yaml), false) 162 assert.NoError(t, err, "read workflow should succeed") 163 assert.Len(t, workflow.Jobs, 1) 164 165 container := workflow.GetJob("test").Container() 166 167 assert.Contains(t, container.Image, "r.example.org/something:latest") 168 assert.Contains(t, container.Env["HOME"], "/home/user") 169 assert.Contains(t, container.Credentials["username"], "registry-username") 170 assert.Contains(t, container.Credentials["password"], "registry-password") 171 assert.ElementsMatch(t, container.Volumes, []string{ 172 "my_docker_volume:/volume_mount", 173 "/data/my_data", 174 "/source/directory:/destination/directory", 175 }) 176 } 177 178 func TestReadWorkflow_JobTypes(t *testing.T) { 179 yaml := ` 180 name: invalid job definition 181 182 jobs: 183 default-job: 184 runs-on: ubuntu-latest 185 steps: 186 - run: echo 187 remote-reusable-workflow-yml: 188 uses: remote/repo/some/path/to/workflow.yml@main 189 remote-reusable-workflow-yaml: 190 uses: remote/repo/some/path/to/workflow.yaml@main 191 remote-reusable-workflow-custom-path: 192 uses: remote/repo/path/to/workflow.yml@main 193 local-reusable-workflow-yml: 194 uses: ./some/path/to/workflow.yml 195 local-reusable-workflow-yaml: 196 uses: ./some/path/to/workflow.yaml 197 ` 198 199 workflow, err := ReadWorkflow(strings.NewReader(yaml), false) 200 assert.NoError(t, err, "read workflow should succeed") 201 assert.Len(t, workflow.Jobs, 6) 202 203 jobType, err := workflow.Jobs["default-job"].Type() 204 assert.Equal(t, nil, err) 205 assert.Equal(t, JobTypeDefault, jobType) 206 207 jobType, err = workflow.Jobs["remote-reusable-workflow-yml"].Type() 208 assert.Equal(t, nil, err) 209 assert.Equal(t, JobTypeReusableWorkflowRemote, jobType) 210 211 jobType, err = workflow.Jobs["remote-reusable-workflow-yaml"].Type() 212 assert.Equal(t, nil, err) 213 assert.Equal(t, JobTypeReusableWorkflowRemote, jobType) 214 215 jobType, err = workflow.Jobs["remote-reusable-workflow-custom-path"].Type() 216 assert.Equal(t, nil, err) 217 assert.Equal(t, JobTypeReusableWorkflowRemote, jobType) 218 219 jobType, err = workflow.Jobs["local-reusable-workflow-yml"].Type() 220 assert.Equal(t, nil, err) 221 assert.Equal(t, JobTypeReusableWorkflowLocal, jobType) 222 223 jobType, err = workflow.Jobs["local-reusable-workflow-yaml"].Type() 224 assert.Equal(t, nil, err) 225 assert.Equal(t, JobTypeReusableWorkflowLocal, jobType) 226 } 227 228 func TestReadWorkflow_JobTypes_InvalidPath(t *testing.T) { 229 yaml := ` 230 name: invalid job definition 231 232 jobs: 233 remote-reusable-workflow-missing-version: 234 uses: remote/repo/some/path/to/workflow.yml 235 remote-reusable-workflow-bad-extension: 236 uses: remote/repo/some/path/to/workflow.json 237 local-reusable-workflow-bad-extension: 238 uses: ./some/path/to/workflow.json 239 local-reusable-workflow-bad-path: 240 uses: some/path/to/workflow.yaml 241 ` 242 243 workflow, err := ReadWorkflow(strings.NewReader(yaml), false) 244 assert.NoError(t, err, "read workflow should succeed") 245 assert.Len(t, workflow.Jobs, 4) 246 247 jobType, err := workflow.Jobs["remote-reusable-workflow-missing-version"].Type() 248 assert.Equal(t, JobTypeInvalid, jobType) 249 assert.NotEqual(t, nil, err) 250 251 jobType, err = workflow.Jobs["remote-reusable-workflow-bad-extension"].Type() 252 assert.Equal(t, JobTypeInvalid, jobType) 253 assert.NotEqual(t, nil, err) 254 255 jobType, err = workflow.Jobs["local-reusable-workflow-bad-extension"].Type() 256 assert.Equal(t, JobTypeInvalid, jobType) 257 assert.NotEqual(t, nil, err) 258 259 jobType, err = workflow.Jobs["local-reusable-workflow-bad-path"].Type() 260 assert.Equal(t, JobTypeInvalid, jobType) 261 assert.NotEqual(t, nil, err) 262 } 263 264 func TestReadWorkflow_StepsTypes(t *testing.T) { 265 yaml := ` 266 name: invalid step definition 267 268 jobs: 269 test: 270 runs-on: ubuntu-latest 271 steps: 272 - name: test1 273 uses: actions/checkout@v2 274 run: echo 275 - name: test2 276 run: echo 277 - name: test3 278 uses: actions/checkout@v2 279 - name: test4 280 uses: docker://nginx:latest 281 - name: test5 282 uses: ./local-action 283 ` 284 285 _, err := ReadWorkflow(strings.NewReader(yaml), false) 286 assert.Error(t, err, "read workflow should fail") 287 } 288 289 // See: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idoutputs 290 func TestReadWorkflow_JobOutputs(t *testing.T) { 291 yaml := ` 292 name: job outputs definition 293 294 jobs: 295 test1: 296 runs-on: ubuntu-latest 297 steps: 298 - id: test1_1 299 run: | 300 echo "::set-output name=a_key::some-a_value" 301 echo "::set-output name=b-key::some-b-value" 302 outputs: 303 some_a_key: ${{ steps.test1_1.outputs.a_key }} 304 some-b-key: ${{ steps.test1_1.outputs.b-key }} 305 306 test2: 307 runs-on: ubuntu-latest 308 needs: 309 - test1 310 steps: 311 - name: test2_1 312 run: | 313 echo "${{ needs.test1.outputs.some_a_key }}" 314 echo "${{ needs.test1.outputs.some-b-key }}" 315 ` 316 317 workflow, err := ReadWorkflow(strings.NewReader(yaml), false) 318 assert.NoError(t, err, "read workflow should succeed") 319 assert.Len(t, workflow.Jobs, 2) 320 321 assert.Len(t, workflow.Jobs["test1"].Steps, 1) 322 assert.Equal(t, StepTypeRun, workflow.Jobs["test1"].Steps[0].Type()) 323 assert.Equal(t, "test1_1", workflow.Jobs["test1"].Steps[0].ID) 324 assert.Len(t, workflow.Jobs["test1"].Outputs, 2) 325 assert.Contains(t, workflow.Jobs["test1"].Outputs, "some_a_key") 326 assert.Contains(t, workflow.Jobs["test1"].Outputs, "some-b-key") 327 assert.Equal(t, "${{ steps.test1_1.outputs.a_key }}", workflow.Jobs["test1"].Outputs["some_a_key"]) 328 assert.Equal(t, "${{ steps.test1_1.outputs.b-key }}", workflow.Jobs["test1"].Outputs["some-b-key"]) 329 } 330 331 func TestReadWorkflow_Strategy(t *testing.T) { 332 w, err := NewWorkflowPlanner("testdata/strategy/push.yml", true, false) 333 assert.NoError(t, err) 334 335 p, err := w.PlanJob("strategy-only-max-parallel") 336 assert.NoError(t, err) 337 338 assert.Equal(t, len(p.Stages), 1) 339 assert.Equal(t, len(p.Stages[0].Runs), 1) 340 341 wf := p.Stages[0].Runs[0].Workflow 342 343 job := wf.Jobs["strategy-only-max-parallel"] 344 matrixes, err := job.GetMatrixes() 345 assert.NoError(t, err) 346 assert.Equal(t, matrixes, []map[string]interface{}{{}}) 347 assert.Equal(t, job.Matrix(), map[string][]interface{}(nil)) 348 assert.Equal(t, job.Strategy.MaxParallel, 2) 349 assert.Equal(t, job.Strategy.FailFast, true) 350 351 job = wf.Jobs["strategy-only-fail-fast"] 352 matrixes, err = job.GetMatrixes() 353 assert.NoError(t, err) 354 assert.Equal(t, matrixes, []map[string]interface{}{{}}) 355 assert.Equal(t, job.Matrix(), map[string][]interface{}(nil)) 356 assert.Equal(t, job.Strategy.MaxParallel, 4) 357 assert.Equal(t, job.Strategy.FailFast, false) 358 359 job = wf.Jobs["strategy-no-matrix"] 360 matrixes, err = job.GetMatrixes() 361 assert.NoError(t, err) 362 assert.Equal(t, matrixes, []map[string]interface{}{{}}) 363 assert.Equal(t, job.Matrix(), map[string][]interface{}(nil)) 364 assert.Equal(t, job.Strategy.MaxParallel, 2) 365 assert.Equal(t, job.Strategy.FailFast, false) 366 367 job = wf.Jobs["strategy-all"] 368 matrixes, err = job.GetMatrixes() 369 assert.NoError(t, err) 370 assert.Equal(t, matrixes, 371 []map[string]interface{}{ 372 {"datacenter": "site-c", "node-version": "14.x", "site": "staging", "php-version": 5.4}, 373 {"datacenter": "site-c", "node-version": "16.x", "site": "staging", "php-version": 5.4}, 374 {"datacenter": "site-d", "node-version": "16.x", "site": "staging", "php-version": 5.4}, 375 {"datacenter": "site-a", "node-version": "10.x", "site": "prod"}, 376 {"datacenter": "site-b", "node-version": "12.x", "site": "dev"}, 377 }, 378 ) 379 assert.Equal(t, job.Matrix(), 380 map[string][]interface{}{ 381 "datacenter": {"site-c", "site-d"}, 382 "exclude": { 383 map[string]interface{}{"datacenter": "site-d", "node-version": "14.x", "site": "staging"}, 384 }, 385 "include": { 386 map[string]interface{}{"php-version": 5.4}, 387 map[string]interface{}{"datacenter": "site-a", "node-version": "10.x", "site": "prod"}, 388 map[string]interface{}{"datacenter": "site-b", "node-version": "12.x", "site": "dev"}, 389 }, 390 "node-version": {"14.x", "16.x"}, 391 "site": {"staging"}, 392 }, 393 ) 394 assert.Equal(t, job.Strategy.MaxParallel, 2) 395 assert.Equal(t, job.Strategy.FailFast, false) 396 } 397 398 func TestMatrixOnlyIncludes(t *testing.T) { 399 matrix := map[string][]interface{}{ 400 "include": []interface{}{ 401 map[string]interface{}{"a": "1", "b": "2"}, 402 map[string]interface{}{"a": "3", "b": "4"}, 403 }, 404 } 405 rN := yaml.Node{} 406 err := rN.Encode(matrix) 407 require.NoError(t, err, "encoding matrix should succeed") 408 job := &Job{ 409 Strategy: &Strategy{ 410 RawMatrix: rN, 411 }, 412 } 413 assert.Equal(t, job.Matrix(), matrix) 414 matrixes, err := job.GetMatrixes() 415 require.NoError(t, err) 416 assert.Equal(t, matrixes, 417 []map[string]interface{}{ 418 {"a": "1", "b": "2"}, 419 {"a": "3", "b": "4"}, 420 }, 421 ) 422 } 423 424 func TestStep_ShellCommand(t *testing.T) { 425 tests := []struct { 426 shell string 427 workflowShell string 428 want string 429 }{ 430 {"pwsh -v '. {0}'", "", "pwsh -v '. {0}'"}, 431 {"pwsh", "", "pwsh -command . '{0}'"}, 432 {"powershell", "", "powershell -command . '{0}'"}, 433 {"bash", "", "bash -e {0}"}, 434 {"bash", "bash", "bash --noprofile --norc -e -o pipefail {0}"}, 435 } 436 for _, tt := range tests { 437 t.Run(tt.shell, func(t *testing.T) { 438 got := (&Step{Shell: tt.shell, WorkflowShell: tt.workflowShell}).ShellCommand() 439 assert.Equal(t, got, tt.want) 440 }) 441 } 442 } 443 444 func TestReadWorkflow_WorkflowDispatchConfig(t *testing.T) { 445 yaml := ` 446 name: local-action-docker-url 447 ` 448 workflow, err := ReadWorkflow(strings.NewReader(yaml), false) 449 assert.NoError(t, err, "read workflow should succeed") 450 workflowDispatch := workflow.WorkflowDispatchConfig() 451 assert.Nil(t, workflowDispatch) 452 453 yaml = ` 454 name: local-action-docker-url 455 on: push 456 ` 457 workflow, err = ReadWorkflow(strings.NewReader(yaml), false) 458 assert.NoError(t, err, "read workflow should succeed") 459 workflowDispatch = workflow.WorkflowDispatchConfig() 460 assert.Nil(t, workflowDispatch) 461 462 yaml = ` 463 name: local-action-docker-url 464 on: workflow_dispatch 465 ` 466 workflow, err = ReadWorkflow(strings.NewReader(yaml), false) 467 assert.NoError(t, err, "read workflow should succeed") 468 workflowDispatch = workflow.WorkflowDispatchConfig() 469 assert.NotNil(t, workflowDispatch) 470 assert.Nil(t, workflowDispatch.Inputs) 471 472 yaml = ` 473 name: local-action-docker-url 474 on: [push, pull_request] 475 ` 476 workflow, err = ReadWorkflow(strings.NewReader(yaml), false) 477 assert.NoError(t, err, "read workflow should succeed") 478 workflowDispatch = workflow.WorkflowDispatchConfig() 479 assert.Nil(t, workflowDispatch) 480 481 yaml = ` 482 name: local-action-docker-url 483 on: [push, workflow_dispatch] 484 ` 485 workflow, err = ReadWorkflow(strings.NewReader(yaml), false) 486 assert.NoError(t, err, "read workflow should succeed") 487 workflowDispatch = workflow.WorkflowDispatchConfig() 488 assert.NotNil(t, workflowDispatch) 489 assert.Nil(t, workflowDispatch.Inputs) 490 491 yaml = ` 492 name: local-action-docker-url 493 on: 494 - push 495 - workflow_dispatch 496 ` 497 workflow, err = ReadWorkflow(strings.NewReader(yaml), false) 498 assert.NoError(t, err, "read workflow should succeed") 499 workflowDispatch = workflow.WorkflowDispatchConfig() 500 assert.NotNil(t, workflowDispatch) 501 assert.Nil(t, workflowDispatch.Inputs) 502 503 yaml = ` 504 name: local-action-docker-url 505 on: 506 push: 507 pull_request: 508 ` 509 workflow, err = ReadWorkflow(strings.NewReader(yaml), false) 510 assert.NoError(t, err, "read workflow should succeed") 511 workflowDispatch = workflow.WorkflowDispatchConfig() 512 assert.Nil(t, workflowDispatch) 513 514 yaml = ` 515 name: local-action-docker-url 516 on: 517 push: 518 pull_request: 519 workflow_dispatch: 520 inputs: 521 logLevel: 522 description: 'Log level' 523 required: true 524 default: 'warning' 525 type: choice 526 options: 527 - info 528 - warning 529 - debug 530 ` 531 workflow, err = ReadWorkflow(strings.NewReader(yaml), false) 532 assert.NoError(t, err, "read workflow should succeed") 533 workflowDispatch = workflow.WorkflowDispatchConfig() 534 assert.NotNil(t, workflowDispatch) 535 assert.Equal(t, WorkflowDispatchInput{ 536 Default: "warning", 537 Description: "Log level", 538 Options: []string{ 539 "info", 540 "warning", 541 "debug", 542 }, 543 Required: true, 544 Type: "choice", 545 }, workflowDispatch.Inputs["logLevel"]) 546 } 547 548 func TestReadWorkflow_InvalidStringEvent(t *testing.T) { 549 yaml := ` 550 name: local-action-docker-url 551 on: push2 552 553 jobs: 554 test: 555 runs-on: ubuntu-latest 556 steps: 557 - uses: ./actions/docker-url 558 ` 559 560 _, err := ReadWorkflow(strings.NewReader(yaml), true) 561 assert.Error(t, err, "read workflow should succeed") 562 }