github.com/nektos/act@v0.2.83/pkg/runner/runner_test.go (about) 1 package runner 2 3 import ( 4 "bufio" 5 "bytes" 6 "context" 7 "encoding/json" 8 "fmt" 9 "io" 10 "os" 11 "path" 12 "path/filepath" 13 "runtime" 14 "strings" 15 "testing" 16 17 "github.com/joho/godotenv" 18 "github.com/sirupsen/logrus" 19 log "github.com/sirupsen/logrus" 20 assert "github.com/stretchr/testify/assert" 21 "gopkg.in/yaml.v3" 22 23 "github.com/nektos/act/pkg/common" 24 "github.com/nektos/act/pkg/model" 25 ) 26 27 var ( 28 baseImage = "node:24-bookworm-slim" 29 platforms map[string]string 30 logLevel = log.DebugLevel 31 workdir = "testdata" 32 secrets map[string]string 33 ) 34 35 func init() { 36 if p := os.Getenv("ACT_TEST_IMAGE"); p != "" { 37 baseImage = p 38 } 39 40 platforms = map[string]string{ 41 "ubuntu-latest": baseImage, 42 "self-hosted": "-self-hosted", 43 } 44 45 if l := os.Getenv("ACT_TEST_LOG_LEVEL"); l != "" { 46 if lvl, err := log.ParseLevel(l); err == nil { 47 logLevel = lvl 48 } 49 } 50 51 if wd, err := filepath.Abs(workdir); err == nil { 52 workdir = wd 53 } 54 55 secrets = map[string]string{} 56 } 57 58 func TestNoWorkflowsFoundByPlanner(t *testing.T) { 59 planner, err := model.NewWorkflowPlanner("res", true, false) 60 assert.NoError(t, err) 61 62 out := log.StandardLogger().Out 63 var buf bytes.Buffer 64 log.SetOutput(&buf) 65 log.SetLevel(log.DebugLevel) 66 plan, err := planner.PlanEvent("pull_request") 67 assert.NotNil(t, plan) 68 assert.NoError(t, err) 69 assert.Contains(t, buf.String(), "no workflows found by planner") 70 buf.Reset() 71 plan, err = planner.PlanAll() 72 assert.NotNil(t, plan) 73 assert.NoError(t, err) 74 assert.Contains(t, buf.String(), "no workflows found by planner") 75 log.SetOutput(out) 76 } 77 78 func TestGraphMissingEvent(t *testing.T) { 79 planner, err := model.NewWorkflowPlanner("testdata/issue-1595/no-event.yml", true, false) 80 assert.NoError(t, err) 81 82 out := log.StandardLogger().Out 83 var buf bytes.Buffer 84 log.SetOutput(&buf) 85 log.SetLevel(log.DebugLevel) 86 87 plan, err := planner.PlanEvent("push") 88 assert.NoError(t, err) 89 assert.NotNil(t, plan) 90 assert.Equal(t, 0, len(plan.Stages)) 91 92 assert.Contains(t, buf.String(), "no events found for workflow: no-event.yml") 93 log.SetOutput(out) 94 } 95 96 func TestGraphMissingFirst(t *testing.T) { 97 planner, err := model.NewWorkflowPlanner("testdata/issue-1595/no-first.yml", true, false) 98 assert.NoError(t, err) 99 100 plan, err := planner.PlanEvent("push") 101 assert.EqualError(t, err, "unable to build dependency graph for no first (no-first.yml)") 102 assert.NotNil(t, plan) 103 assert.Equal(t, 0, len(plan.Stages)) 104 } 105 106 func TestGraphWithMissing(t *testing.T) { 107 planner, err := model.NewWorkflowPlanner("testdata/issue-1595/missing.yml", true, false) 108 assert.NoError(t, err) 109 110 out := log.StandardLogger().Out 111 var buf bytes.Buffer 112 log.SetOutput(&buf) 113 log.SetLevel(log.DebugLevel) 114 115 plan, err := planner.PlanEvent("push") 116 assert.NotNil(t, plan) 117 assert.Equal(t, 0, len(plan.Stages)) 118 assert.EqualError(t, err, "unable to build dependency graph for missing (missing.yml)") 119 assert.Contains(t, buf.String(), "unable to build dependency graph for missing (missing.yml)") 120 log.SetOutput(out) 121 } 122 123 func TestGraphWithSomeMissing(t *testing.T) { 124 log.SetLevel(log.DebugLevel) 125 126 planner, err := model.NewWorkflowPlanner("testdata/issue-1595/", true, false) 127 assert.NoError(t, err) 128 129 out := log.StandardLogger().Out 130 var buf bytes.Buffer 131 log.SetOutput(&buf) 132 log.SetLevel(log.DebugLevel) 133 134 plan, err := planner.PlanAll() 135 assert.Error(t, err, "unable to build dependency graph for no first (no-first.yml)") 136 assert.NotNil(t, plan) 137 assert.Equal(t, 1, len(plan.Stages)) 138 assert.Contains(t, buf.String(), "unable to build dependency graph for missing (missing.yml)") 139 assert.Contains(t, buf.String(), "unable to build dependency graph for no first (no-first.yml)") 140 log.SetOutput(out) 141 } 142 143 func TestGraphEvent(t *testing.T) { 144 planner, err := model.NewWorkflowPlanner("testdata/basic", true, false) 145 assert.NoError(t, err) 146 147 plan, err := planner.PlanEvent("push") 148 assert.NoError(t, err) 149 assert.NotNil(t, plan) 150 assert.NotNil(t, plan.Stages) 151 assert.Equal(t, len(plan.Stages), 3, "stages") 152 assert.Equal(t, len(plan.Stages[0].Runs), 1, "stage0.runs") 153 assert.Equal(t, len(plan.Stages[1].Runs), 1, "stage1.runs") 154 assert.Equal(t, len(plan.Stages[2].Runs), 1, "stage2.runs") 155 assert.Equal(t, plan.Stages[0].Runs[0].JobID, "check", "jobid") 156 assert.Equal(t, plan.Stages[1].Runs[0].JobID, "build", "jobid") 157 assert.Equal(t, plan.Stages[2].Runs[0].JobID, "test", "jobid") 158 159 plan, err = planner.PlanEvent("release") 160 assert.NoError(t, err) 161 assert.NotNil(t, plan) 162 assert.Equal(t, 0, len(plan.Stages)) 163 } 164 165 type TestJobFileInfo struct { 166 workdir string 167 workflowPath string 168 eventName string 169 errorMessage string 170 platforms map[string]string 171 secrets map[string]string 172 } 173 174 func (j *TestJobFileInfo) runTest(ctx context.Context, t *testing.T, cfg *Config) { 175 fmt.Printf("::group::%s\n", j.workflowPath) 176 177 log.SetLevel(logLevel) 178 179 workdir, err := filepath.Abs(j.workdir) 180 assert.Nil(t, err, workdir) 181 182 fullWorkflowPath := filepath.Join(workdir, j.workflowPath) 183 runnerConfig := &Config{ 184 Workdir: workdir, 185 BindWorkdir: false, 186 EventName: j.eventName, 187 EventPath: cfg.EventPath, 188 Platforms: j.platforms, 189 ReuseContainers: false, 190 Env: cfg.Env, 191 Secrets: cfg.Secrets, 192 Inputs: cfg.Inputs, 193 GitHubInstance: "github.com", 194 ContainerArchitecture: cfg.ContainerArchitecture, 195 Matrix: cfg.Matrix, 196 ActionCache: cfg.ActionCache, 197 } 198 199 runner, err := New(runnerConfig) 200 assert.Nil(t, err, j.workflowPath) 201 202 planner, err := model.NewWorkflowPlanner(fullWorkflowPath, true, false) 203 if j.errorMessage != "" && err != nil { 204 assert.Error(t, err, j.errorMessage) 205 } else if assert.Nil(t, err, fullWorkflowPath) { 206 plan, err := planner.PlanEvent(j.eventName) 207 assert.True(t, (err == nil) != (plan == nil), "PlanEvent should return either a plan or an error") 208 if err == nil && plan != nil { 209 err = runner.NewPlanExecutor(plan)(ctx) 210 if j.errorMessage == "" { 211 assert.Nil(t, err, fullWorkflowPath) 212 } else { 213 assert.Error(t, err, j.errorMessage) 214 } 215 } 216 } 217 218 fmt.Println("::endgroup::") 219 } 220 221 type TestConfig struct { 222 LocalRepositories map[string]string `yaml:"local-repositories"` 223 } 224 225 func TestRunEvent(t *testing.T) { 226 if testing.Short() { 227 t.Skip("skipping integration test") 228 } 229 230 ctx := context.Background() 231 232 tables := []TestJobFileInfo{ 233 // Shells 234 {workdir, "shells/defaults", "push", "", platforms, secrets}, 235 // TODO: figure out why it fails 236 // {workdir, "shells/custom", "push", "", map[string]string{"ubuntu-latest": "catthehacker/ubuntu:pwsh-latest"}, }, // custom image with pwsh 237 {workdir, "shells/pwsh", "push", "", map[string]string{"ubuntu-latest": "catthehacker/ubuntu:pwsh-latest"}, secrets}, // custom image with pwsh 238 {workdir, "shells/bash", "push", "", platforms, secrets}, 239 {workdir, "shells/python", "push", "", map[string]string{"ubuntu-latest": "node:16-buster"}, secrets}, // slim doesn't have python 240 {workdir, "shells/sh", "push", "", platforms, secrets}, 241 242 // Local action 243 {workdir, "local-action-docker-url", "push", "", platforms, secrets}, 244 {workdir, "local-action-dockerfile", "push", "", platforms, secrets}, 245 {workdir, "local-action-via-composite-dockerfile", "push", "", platforms, secrets}, 246 {workdir, "local-action-js", "push", "", platforms, secrets}, 247 248 // Uses 249 {workdir, "uses-composite", "push", "", platforms, secrets}, 250 {workdir, "uses-composite-with-error", "push", "Job 'failing-composite-action' failed", platforms, secrets}, 251 {workdir, "uses-composite-check-for-input-collision", "push", "", platforms, secrets}, 252 {workdir, "uses-composite-check-for-input-shadowing", "push", "", platforms, secrets}, 253 {workdir, "uses-nested-composite", "push", "", platforms, secrets}, 254 {workdir, "remote-action-composite-js-pre-with-defaults", "push", "", platforms, secrets}, 255 {workdir, "remote-action-composite-action-ref", "push", "", platforms, secrets}, 256 {workdir, "uses-workflow", "push", "", platforms, map[string]string{"secret": "keep_it_private"}}, 257 {workdir, "uses-workflow", "pull_request", "", platforms, map[string]string{"secret": "keep_it_private"}}, 258 {workdir, "uses-docker-url", "push", "", platforms, secrets}, 259 {workdir, "act-composite-env-test", "push", "", platforms, secrets}, 260 261 // Eval 262 {workdir, "evalmatrix", "push", "", platforms, secrets}, 263 {workdir, "evalmatrixneeds", "push", "", platforms, secrets}, 264 {workdir, "evalmatrixneeds2", "push", "", platforms, secrets}, 265 {workdir, "evalmatrix-merge-map", "push", "", platforms, secrets}, 266 {workdir, "evalmatrix-merge-array", "push", "", platforms, secrets}, 267 {workdir, "issue-1195", "push", "", platforms, secrets}, 268 269 {workdir, "basic", "push", "", platforms, secrets}, 270 {workdir, "fail", "push", "exit with `FAILURE`: 1", platforms, secrets}, 271 {workdir, "runs-on", "push", "", platforms, secrets}, 272 {workdir, "checkout", "push", "", platforms, secrets}, 273 {workdir, "job-container", "push", "", platforms, secrets}, 274 {workdir, "job-container-non-root", "push", "", platforms, secrets}, 275 {workdir, "job-container-invalid-credentials", "push", "failed to handle credentials: failed to interpolate container.credentials.password", platforms, secrets}, 276 {workdir, "container-hostname", "push", "", platforms, secrets}, 277 {workdir, "remote-action-docker", "push", "", platforms, secrets}, 278 {workdir, "remote-action-docker-new-cache", "push", "", platforms, secrets}, 279 {workdir, "remote-action-js", "push", "", platforms, secrets}, 280 {workdir, "remote-action-js-node-user", "push", "", platforms, secrets}, // Test if this works with non root container 281 {workdir, "matrix", "push", "", platforms, secrets}, 282 {workdir, "matrix-include-exclude", "push", "", platforms, secrets}, 283 {workdir, "matrix-exitcode", "push", "Job 'test' failed", platforms, secrets}, 284 {workdir, "commands", "push", "", platforms, secrets}, 285 {workdir, "workdir", "push", "", platforms, secrets}, 286 {workdir, "defaults-run", "push", "", platforms, secrets}, 287 {workdir, "composite-fail-with-output", "push", "", platforms, secrets}, 288 {workdir, "issue-597", "push", "", platforms, secrets}, 289 {workdir, "issue-598", "push", "", platforms, secrets}, 290 {workdir, "if-env-act", "push", "", platforms, secrets}, 291 {workdir, "env-and-path", "push", "", platforms, secrets}, 292 {workdir, "environment-files", "push", "", platforms, secrets}, 293 {workdir, "GITHUB_STATE", "push", "", platforms, secrets}, 294 {workdir, "environment-files-parser-bug", "push", "", platforms, secrets}, 295 {workdir, "non-existent-action", "push", "Job 'nopanic' failed", platforms, secrets}, 296 {workdir, "outputs", "push", "", platforms, secrets}, 297 {workdir, "networking", "push", "", platforms, secrets}, 298 {workdir, "steps-context/conclusion", "push", "", platforms, secrets}, 299 {workdir, "steps-context/outcome", "push", "", platforms, secrets}, 300 {workdir, "job-status-check", "push", "job 'fail' failed", platforms, secrets}, 301 {workdir, "if-expressions", "push", "Job 'mytest' failed", platforms, secrets}, 302 {workdir, "actions-environment-and-context-tests", "push", "", platforms, secrets}, 303 {workdir, "uses-action-with-pre-and-post-step", "push", "", platforms, secrets}, 304 {workdir, "evalenv", "push", "", platforms, secrets}, 305 {workdir, "docker-action-custom-path", "push", "", platforms, secrets}, 306 {workdir, "GITHUB_ENV-use-in-env-ctx", "push", "", platforms, secrets}, 307 {workdir, "ensure-post-steps", "push", "Job 'second-post-step-should-fail' failed", platforms, secrets}, 308 {workdir, "workflow_call_inputs", "workflow_call", "", platforms, secrets}, 309 {workdir, "workflow_dispatch", "workflow_dispatch", "", platforms, secrets}, 310 {workdir, "workflow_dispatch_no_inputs_mapping", "workflow_dispatch", "", platforms, secrets}, 311 {workdir, "workflow_dispatch-scalar", "workflow_dispatch", "", platforms, secrets}, 312 {workdir, "workflow_dispatch-scalar-composite-action", "workflow_dispatch", "", platforms, secrets}, 313 {workdir, "uses-workflow-defaults", "workflow_dispatch", "", platforms, secrets}, 314 {workdir, "job-needs-context-contains-result", "push", "", platforms, secrets}, 315 {"../model/testdata", "strategy", "push", "", platforms, secrets}, // TODO: move all testdata into pkg so we can validate it with planner and runner 316 {"../model/testdata", "container-volumes", "push", "", platforms, secrets}, 317 {workdir, "path-handling", "push", "", platforms, secrets}, 318 {workdir, "do-not-leak-step-env-in-composite", "push", "", platforms, secrets}, 319 {workdir, "set-env-step-env-override", "push", "", platforms, secrets}, 320 {workdir, "set-env-new-env-file-per-step", "push", "", platforms, secrets}, 321 {workdir, "no-panic-on-invalid-composite-action", "push", "jobs failed due to invalid action", platforms, secrets}, 322 // GITHUB_STEP_SUMMARY 323 {workdir, "stepsummary", "push", "", platforms, secrets}, 324 325 // services 326 {workdir, "services", "push", "", platforms, secrets}, 327 {workdir, "services-empty-image", "push", "", platforms, secrets}, 328 {workdir, "services-host-network", "push", "", platforms, secrets}, 329 {workdir, "services-with-container", "push", "", platforms, secrets}, 330 {workdir, "mysql-service-container-with-health-check", "push", "", platforms, secrets}, 331 332 // local remote action overrides 333 {workdir, "local-remote-action-overrides", "push", "", platforms, secrets}, 334 335 // docker action on host executor 336 {workdir, "docker-action-host-env", "push", "", platforms, secrets}, 337 } 338 339 for _, table := range tables { 340 t.Run(table.workflowPath, func(t *testing.T) { 341 config := &Config{ 342 Secrets: table.secrets, 343 } 344 345 eventFile := filepath.Join(workdir, table.workflowPath, "event.json") 346 if _, err := os.Stat(eventFile); err == nil { 347 config.EventPath = eventFile 348 } 349 350 testConfigFile := filepath.Join(workdir, table.workflowPath, "config/config.yml") 351 if file, err := os.ReadFile(testConfigFile); err == nil { 352 testConfig := &TestConfig{} 353 if yaml.Unmarshal(file, testConfig) == nil { 354 if testConfig.LocalRepositories != nil { 355 config.ActionCache = &LocalRepositoryCache{ 356 Parent: GoGitActionCache{ 357 path.Clean(path.Join(workdir, "cache")), 358 }, 359 LocalRepositories: testConfig.LocalRepositories, 360 CacheDirCache: map[string]string{}, 361 } 362 } 363 } 364 } 365 366 table.runTest(ctx, t, config) 367 }) 368 } 369 } 370 371 type captureJobLoggerFactory struct { 372 buffer bytes.Buffer 373 } 374 375 func (factory *captureJobLoggerFactory) WithJobLogger() *logrus.Logger { 376 logger := logrus.New() 377 logger.SetOutput(&factory.buffer) 378 logger.SetLevel(log.TraceLevel) 379 logger.SetFormatter(&log.JSONFormatter{}) 380 return logger 381 } 382 383 func TestPullAndPostStepFailureIsJobFailure(t *testing.T) { 384 if testing.Short() { 385 t.Skip("skipping integration test") 386 } 387 388 defCache := &GoGitActionCache{ 389 path.Clean(path.Join(workdir, "cache")), 390 } 391 392 mockCache := &mockCache{} 393 394 tables := []struct { 395 TestJobFileInfo 396 ActionCache ActionCache 397 SetupResult string 398 }{ 399 {TestJobFileInfo{workdir, "checkout", "push", "pull failure", map[string]string{"ubuntu-latest": "localhost:0000/missing:latest"}, secrets}, defCache, "failure"}, 400 {TestJobFileInfo{workdir, "post-step-failure-is-job-failure", "push", "post failure", map[string]string{"ubuntu-latest": "-self-hosted"}, secrets}, mockCache, "success"}, 401 } 402 403 for _, table := range tables { 404 t.Run(table.workflowPath, func(t *testing.T) { 405 factory := &captureJobLoggerFactory{} 406 407 config := &Config{ 408 Secrets: table.secrets, 409 } 410 411 eventFile := filepath.Join(workdir, table.workflowPath, "event.json") 412 if _, err := os.Stat(eventFile); err == nil { 413 config.EventPath = eventFile 414 } 415 config.ActionCache = table.ActionCache 416 417 logger := logrus.New() 418 logger.SetOutput(&factory.buffer) 419 logger.SetLevel(log.TraceLevel) 420 logger.SetFormatter(&log.JSONFormatter{}) 421 422 table.runTest(common.WithLogger(WithJobLoggerFactory(t.Context(), factory), logger), t, config) 423 scan := bufio.NewScanner(&factory.buffer) 424 var hasJobResult, hasStepResult bool 425 for scan.Scan() { 426 t.Log(scan.Text()) 427 entry := map[string]interface{}{} 428 if json.Unmarshal(scan.Bytes(), &entry) == nil { 429 if val, ok := entry["jobResult"]; ok { 430 assert.Equal(t, "failure", val) 431 hasJobResult = true 432 } 433 if val, ok := entry["stepResult"]; ok && !hasStepResult { 434 assert.Equal(t, table.SetupResult, val) 435 hasStepResult = true 436 } 437 } 438 } 439 assert.True(t, hasStepResult, "stepResult not found") 440 assert.True(t, hasJobResult, "jobResult not found") 441 }) 442 } 443 } 444 445 type mockCache struct { 446 } 447 448 func (c mockCache) Fetch(ctx context.Context, cacheDir string, url string, ref string, token string) (string, error) { 449 _ = ctx 450 _ = cacheDir 451 _ = url 452 _ = ref 453 _ = token 454 return "", fmt.Errorf("fetch failure") 455 } 456 func (c mockCache) GetTarArchive(ctx context.Context, cacheDir string, sha string, includePrefix string) (io.ReadCloser, error) { 457 _ = ctx 458 _ = cacheDir 459 _ = sha 460 _ = includePrefix 461 return nil, fmt.Errorf("fetch failure") 462 } 463 464 func TestFetchFailureIsJobFailure(t *testing.T) { 465 if testing.Short() { 466 t.Skip("skipping integration test") 467 } 468 469 tables := []TestJobFileInfo{ 470 {workdir, "action-cache-v2-fetch-failure-is-job-error", "push", "fetch failure", map[string]string{"ubuntu-latest": "-self-hosted"}, secrets}, 471 } 472 473 for _, table := range tables { 474 t.Run(table.workflowPath, func(t *testing.T) { 475 factory := &captureJobLoggerFactory{} 476 477 config := &Config{ 478 Secrets: table.secrets, 479 } 480 481 eventFile := filepath.Join(workdir, table.workflowPath, "event.json") 482 if _, err := os.Stat(eventFile); err == nil { 483 config.EventPath = eventFile 484 } 485 config.ActionCache = &mockCache{} 486 487 logger := logrus.New() 488 logger.SetOutput(&factory.buffer) 489 logger.SetLevel(log.TraceLevel) 490 logger.SetFormatter(&log.JSONFormatter{}) 491 492 table.runTest(common.WithLogger(WithJobLoggerFactory(t.Context(), factory), logger), t, config) 493 scan := bufio.NewScanner(&factory.buffer) 494 var hasJobResult bool 495 for scan.Scan() { 496 t.Log(scan.Text()) 497 entry := map[string]interface{}{} 498 if json.Unmarshal(scan.Bytes(), &entry) == nil { 499 if val, ok := entry["jobResult"]; ok { 500 assert.Equal(t, "failure", val) 501 hasJobResult = true 502 } 503 } 504 } 505 assert.True(t, hasJobResult, "jobResult not found") 506 }) 507 } 508 } 509 510 func TestRunEventHostEnvironment(t *testing.T) { 511 if testing.Short() { 512 t.Skip("skipping integration test") 513 } 514 515 ctx := context.Background() 516 517 tables := []TestJobFileInfo{} 518 519 if runtime.GOOS == "linux" { 520 platforms := map[string]string{ 521 "ubuntu-latest": "-self-hosted", 522 } 523 524 tables = append(tables, []TestJobFileInfo{ 525 // Shells 526 {workdir, "shells/defaults", "push", "", platforms, secrets}, 527 {workdir, "shells/pwsh", "push", "", platforms, secrets}, 528 {workdir, "shells/bash", "push", "", platforms, secrets}, 529 {workdir, "shells/python", "push", "", platforms, secrets}, 530 {workdir, "shells/sh", "push", "", platforms, secrets}, 531 532 // Local action 533 {workdir, "local-action-js", "push", "", platforms, secrets}, 534 535 // Uses 536 {workdir, "uses-composite", "push", "", platforms, secrets}, 537 {workdir, "uses-composite-with-error", "push", "Job 'failing-composite-action' failed", platforms, secrets}, 538 {workdir, "uses-nested-composite", "push", "", platforms, secrets}, 539 {workdir, "act-composite-env-test", "push", "", platforms, secrets}, 540 541 // Eval 542 {workdir, "evalmatrix", "push", "", platforms, secrets}, 543 {workdir, "evalmatrixneeds", "push", "", platforms, secrets}, 544 {workdir, "evalmatrixneeds2", "push", "", platforms, secrets}, 545 {workdir, "evalmatrix-merge-map", "push", "", platforms, secrets}, 546 {workdir, "evalmatrix-merge-array", "push", "", platforms, secrets}, 547 {workdir, "issue-1195", "push", "", platforms, secrets}, 548 549 {workdir, "fail", "push", "exit with `FAILURE`: 1", platforms, secrets}, 550 {workdir, "runs-on", "push", "", platforms, secrets}, 551 {workdir, "checkout", "push", "", platforms, secrets}, 552 {workdir, "remote-action-js", "push", "", platforms, secrets}, 553 {workdir, "matrix", "push", "", platforms, secrets}, 554 {workdir, "matrix-include-exclude", "push", "", platforms, secrets}, 555 {workdir, "commands", "push", "", platforms, secrets}, 556 // Disabled for now because this test is somewhat invalid 557 // shell sh is not necessarily bash if the job has no override 558 // {workdir, "defaults-run", "push", "", platforms, secrets}, 559 {workdir, "composite-fail-with-output", "push", "", platforms, secrets}, 560 {workdir, "issue-597", "push", "", platforms, secrets}, 561 {workdir, "issue-598", "push", "", platforms, secrets}, 562 {workdir, "if-env-act", "push", "", platforms, secrets}, 563 {workdir, "env-and-path", "push", "", platforms, secrets}, 564 {workdir, "non-existent-action", "push", "Job 'nopanic' failed", platforms, secrets}, 565 {workdir, "outputs", "push", "", platforms, secrets}, 566 {workdir, "steps-context/conclusion", "push", "", platforms, secrets}, 567 {workdir, "steps-context/outcome", "push", "", platforms, secrets}, 568 {workdir, "job-status-check", "push", "job 'fail' failed", platforms, secrets}, 569 {workdir, "if-expressions", "push", "Job 'mytest' failed", platforms, secrets}, 570 {workdir, "uses-action-with-pre-and-post-step", "push", "", platforms, secrets}, 571 {workdir, "evalenv", "push", "", platforms, secrets}, 572 {workdir, "ensure-post-steps", "push", "Job 'second-post-step-should-fail' failed", platforms, secrets}, 573 }...) 574 } 575 if runtime.GOOS == "windows" { 576 platforms := map[string]string{ 577 "windows-latest": "-self-hosted", 578 } 579 580 tables = append(tables, []TestJobFileInfo{ 581 {workdir, "windows-prepend-path", "push", "", platforms, secrets}, 582 {workdir, "windows-add-env", "push", "", platforms, secrets}, 583 {workdir, "windows-prepend-path-powershell-5", "push", "", platforms, secrets}, 584 {workdir, "windows-add-env-powershell-5", "push", "", platforms, secrets}, 585 {workdir, "windows-shell-cmd", "push", "", platforms, secrets}, 586 }...) 587 } else { 588 platforms := map[string]string{ 589 "self-hosted": "-self-hosted", 590 "ubuntu-latest": "-self-hosted", 591 } 592 593 tables = append(tables, []TestJobFileInfo{ 594 {workdir, "nix-prepend-path", "push", "", platforms, secrets}, 595 {workdir, "inputs-via-env-context", "push", "", platforms, secrets}, 596 {workdir, "do-not-leak-step-env-in-composite", "push", "", platforms, secrets}, 597 {workdir, "set-env-step-env-override", "push", "", platforms, secrets}, 598 {workdir, "set-env-new-env-file-per-step", "push", "", platforms, secrets}, 599 {workdir, "no-panic-on-invalid-composite-action", "push", "jobs failed due to invalid action", platforms, secrets}, 600 }...) 601 } 602 603 for _, table := range tables { 604 t.Run(table.workflowPath, func(t *testing.T) { 605 table.runTest(ctx, t, &Config{}) 606 }) 607 } 608 } 609 610 func TestDryrunEvent(t *testing.T) { 611 if testing.Short() { 612 t.Skip("skipping integration test") 613 } 614 615 ctx := common.WithDryrun(context.Background(), true) 616 617 tables := []TestJobFileInfo{ 618 // Shells 619 {workdir, "shells/defaults", "push", "", platforms, secrets}, 620 {workdir, "shells/pwsh", "push", "", map[string]string{"ubuntu-latest": "catthehacker/ubuntu:pwsh-latest"}, secrets}, // custom image with pwsh 621 {workdir, "shells/bash", "push", "", platforms, secrets}, 622 {workdir, "shells/python", "push", "", map[string]string{"ubuntu-latest": "node:16-buster"}, secrets}, // slim doesn't have python 623 {workdir, "shells/sh", "push", "", platforms, secrets}, 624 625 // Local action 626 {workdir, "local-action-docker-url", "push", "", platforms, secrets}, 627 {workdir, "local-action-dockerfile", "push", "", platforms, secrets}, 628 {workdir, "local-action-via-composite-dockerfile", "push", "", platforms, secrets}, 629 {workdir, "local-action-js", "push", "", platforms, secrets}, 630 } 631 632 for _, table := range tables { 633 t.Run(table.workflowPath, func(t *testing.T) { 634 table.runTest(ctx, t, &Config{}) 635 }) 636 } 637 } 638 639 func TestDockerActionForcePullForceRebuild(t *testing.T) { 640 if testing.Short() { 641 t.Skip("skipping integration test") 642 } 643 644 ctx := context.Background() 645 646 config := &Config{ 647 ForcePull: true, 648 ForceRebuild: true, 649 } 650 651 tables := []TestJobFileInfo{ 652 {workdir, "local-action-dockerfile", "push", "", platforms, secrets}, 653 {workdir, "local-action-via-composite-dockerfile", "push", "", platforms, secrets}, 654 } 655 656 for _, table := range tables { 657 t.Run(table.workflowPath, func(t *testing.T) { 658 table.runTest(ctx, t, config) 659 }) 660 } 661 } 662 663 func TestRunDifferentArchitecture(t *testing.T) { 664 if testing.Short() { 665 t.Skip("skipping integration test") 666 } 667 668 tjfi := TestJobFileInfo{ 669 workdir: workdir, 670 workflowPath: "basic", 671 eventName: "push", 672 errorMessage: "", 673 platforms: platforms, 674 } 675 676 tjfi.runTest(context.Background(), t, &Config{ContainerArchitecture: "linux/arm64"}) 677 } 678 679 type maskJobLoggerFactory struct { 680 Output bytes.Buffer 681 } 682 683 func (f *maskJobLoggerFactory) WithJobLogger() *log.Logger { 684 logger := log.New() 685 logger.SetOutput(io.MultiWriter(&f.Output, os.Stdout)) 686 logger.SetLevel(log.DebugLevel) 687 return logger 688 } 689 690 func TestMaskValues(t *testing.T) { 691 assertNoSecret := func(text string, _ string) { 692 index := strings.Index(text, "composite secret") 693 if index > -1 { 694 fmt.Printf("\nFound Secret in the given text:\n%s\n", text) 695 } 696 assert.False(t, strings.Contains(text, "composite secret")) 697 } 698 699 if testing.Short() { 700 t.Skip("skipping integration test") 701 } 702 703 log.SetLevel(log.DebugLevel) 704 705 tjfi := TestJobFileInfo{ 706 workdir: workdir, 707 workflowPath: "mask-values", 708 eventName: "push", 709 errorMessage: "", 710 platforms: platforms, 711 } 712 713 logger := &maskJobLoggerFactory{} 714 tjfi.runTest(WithJobLoggerFactory(common.WithLogger(context.Background(), logger.WithJobLogger()), logger), t, &Config{}) 715 output := logger.Output.String() 716 717 assertNoSecret(output, "secret value") 718 assertNoSecret(output, "YWJjCg==") 719 } 720 721 func TestRunEventSecrets(t *testing.T) { 722 if testing.Short() { 723 t.Skip("skipping integration test") 724 } 725 workflowPath := "secrets" 726 727 tjfi := TestJobFileInfo{ 728 workdir: workdir, 729 workflowPath: workflowPath, 730 eventName: "push", 731 errorMessage: "", 732 platforms: platforms, 733 } 734 735 env, err := godotenv.Read(filepath.Join(workdir, workflowPath, ".env")) 736 assert.NoError(t, err, "Failed to read .env") 737 secrets, _ := godotenv.Read(filepath.Join(workdir, workflowPath, ".secrets")) 738 assert.NoError(t, err, "Failed to read .secrets") 739 740 tjfi.runTest(context.Background(), t, &Config{Secrets: secrets, Env: env}) 741 } 742 743 func TestRunActionInputs(t *testing.T) { 744 if testing.Short() { 745 t.Skip("skipping integration test") 746 } 747 workflowPath := "input-from-cli" 748 749 tjfi := TestJobFileInfo{ 750 workdir: workdir, 751 workflowPath: workflowPath, 752 eventName: "workflow_dispatch", 753 errorMessage: "", 754 platforms: platforms, 755 } 756 757 inputs := map[string]string{ 758 "SOME_INPUT": "input", 759 } 760 761 tjfi.runTest(context.Background(), t, &Config{Inputs: inputs}) 762 } 763 764 func TestRunEventPullRequest(t *testing.T) { 765 if testing.Short() { 766 t.Skip("skipping integration test") 767 } 768 769 workflowPath := "pull-request" 770 771 tjfi := TestJobFileInfo{ 772 workdir: workdir, 773 workflowPath: workflowPath, 774 eventName: "pull_request", 775 errorMessage: "", 776 platforms: platforms, 777 } 778 779 tjfi.runTest(context.Background(), t, &Config{EventPath: filepath.Join(workdir, workflowPath, "event.json")}) 780 } 781 782 func TestRunMatrixWithUserDefinedInclusions(t *testing.T) { 783 if testing.Short() { 784 t.Skip("skipping integration test") 785 } 786 workflowPath := "matrix-with-user-inclusions" 787 788 tjfi := TestJobFileInfo{ 789 workdir: workdir, 790 workflowPath: workflowPath, 791 eventName: "push", 792 errorMessage: "", 793 platforms: platforms, 794 } 795 796 matrix := map[string]map[string]bool{ 797 "node": { 798 "8": true, 799 "8.x": true, 800 }, 801 "os": { 802 "ubuntu-18.04": true, 803 }, 804 } 805 806 tjfi.runTest(context.Background(), t, &Config{Matrix: matrix}) 807 }