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  }