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  }