1
0
Fork 0
mirror of https://code.forgejo.org/forgejo/runner.git synced 2025-09-05 18:40:59 +00:00
forgejo-runner/act/runner/step_run_test.go
Mathieu Fenniak 76bf87f472
fix: data race condition causing incorrect shell on a task step if it referenced a matrix variable (#865)
The `setupShell` function would update the shell stored on the `Step` object, setting it to either a default value from the job, an expression evaluated in the context of the job, a default from the workflow, or finally falling back to bash or powershell defaults.  Typically this would be fine -- although it would trigger the data race detector because the `Step` is a shared object between multiple concurrent matrix evaluations for the job.

In the *really quite unlikely* case that the `shell` field on a step or job referenced a matrix variable, this data race would actually trigger the shared step's `Shell` value to end up as "whichever one was evaluated last", causing the wrong shell to be used.  The new `matrix-shell` test triggers this behavior, and fails without the associated code fix.

As a fix, the `Shell` field in `Step` is never mutated; instead only the value on non-shared `stepRun` instance is updated from `setupShellCommand`.  `Shell` was renamed to `RawShell` as part of verifying all references were updated and it seemed to make sense to keep that name since it is a pre-evaluator value.

```
==================
WARNING: DATA RACE
Write at 0x00c00013e9b0 by goroutine 1470:
  code.forgejo.org/forgejo/runner/v9/act/runner.(*stepRun).setupShell()
      /.../forgejo-runner/act/runner/step_run.go:210 +0x8f2
  code.forgejo.org/forgejo/runner/v9/act/common/git.FindGitRevision()
      /.../forgejo-runner/act/common/git/git.go:58 +0xc4
  code.forgejo.org/forgejo/runner/v9/act/model.(*GithubContext).SetSha()
      /.../forgejo-runner/act/model/github_context.go:161 +0x6b5
  code.forgejo.org/forgejo/runner/v9/act/runner.(*RunContext).getGithubContext()
      /.../forgejo-runner/act/runner/run_context.go:1228 +0x26ca
  ...

Previous write at 0x00c00013e9b0 by goroutine 1469:
  code.forgejo.org/forgejo/runner/v9/act/runner.(*stepRun).setupShell()
      /.../forgejo-runner/act/runner/step_run.go:210 +0x8f2
  code.forgejo.org/forgejo/runner/v9/act/common/git.FindGitRevision()
      /.../forgejo-runner/act/common/git/git.go:58 +0xc4
  code.forgejo.org/forgejo/runner/v9/act/model.(*GithubContext).SetSha()
      /.../forgejo-runner/act/model/github_context.go:161 +0x6b5
  code.forgejo.org/forgejo/runner/v9/act/runner.(*RunContext).getGithubContext()
      /.../forgejo-runner/act/runner/run_context.go:1228 +0x26ca

...
==================
```

<!--start release-notes-assistant-->
<!--URL:https://code.forgejo.org/forgejo/runner-->
- bug fixes
  - [PR](https://code.forgejo.org/forgejo/runner/pulls/865): <!--number 865 --><!--line 0 --><!--description Zml4OiBkYXRhIHJhY2UgY29uZGl0aW9uIGNhdXNpbmcgaW5jb3JyZWN0IGBzaGVsbGAgb24gYSB0YXNrIHN0ZXAgaWYgaXQgcmVmZXJlbmNlZCBhIG1hdHJpeCB2YXJpYWJsZQ==-->fix: data race condition causing incorrect `shell` on a task step if it referenced a matrix variable<!--description-->
<!--end release-notes-assistant-->

Reviewed-on: https://code.forgejo.org/forgejo/runner/pulls/865
Reviewed-by: earl-warren <earl-warren@noreply.code.forgejo.org>
Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Co-committed-by: Mathieu Fenniak <mathieu@fenniak.net>
2025-08-15 21:10:53 +00:00

114 lines
2.8 KiB
Go

package runner
import (
"bytes"
"context"
"io"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"code.forgejo.org/forgejo/runner/v9/act/container"
"code.forgejo.org/forgejo/runner/v9/act/model"
)
func TestStepRun(t *testing.T) {
cm := &containerMock{}
fileEntry := &container.FileEntry{
Name: "workflow/1.sh",
Mode: 0o755,
Body: "\ncmd\n",
}
sr := &stepRun{
RunContext: &RunContext{
StepResults: map[string]*model.StepResult{},
ExprEval: &expressionEvaluator{},
Config: &Config{},
Run: &model.Run{
JobID: "1",
Workflow: &model.Workflow{
Jobs: map[string]*model.Job{
"1": {
Defaults: model.Defaults{
Run: model.RunDefaults{
Shell: "bash",
},
},
},
},
},
},
JobContainer: cm,
},
Step: &model.Step{
ID: "1",
Run: "cmd",
WorkingDirectory: "workdir",
},
}
cm.On("Copy", "/var/run/act", []*container.FileEntry{fileEntry}).Return(func(ctx context.Context) error {
return nil
})
cm.On("Exec", []string{"bash", "--noprofile", "--norc", "-e", "-o", "pipefail", "/var/run/act/workflow/1.sh"}, mock.AnythingOfType("map[string]string"), "", "workdir").Return(func(ctx context.Context) error {
return nil
})
cm.On("Copy", "/var/run/act", mock.AnythingOfType("[]*container.FileEntry")).Return(func(ctx context.Context) error {
return nil
})
cm.On("UpdateFromEnv", "/var/run/act/workflow/envs.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error {
return nil
})
cm.On("UpdateFromEnv", "/var/run/act/workflow/statecmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error {
return nil
})
cm.On("UpdateFromEnv", "/var/run/act/workflow/outputcmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error {
return nil
})
ctx := t.Context()
cm.On("GetContainerArchive", ctx, "/var/run/act/workflow/SUMMARY.md").Return(io.NopCloser(&bytes.Buffer{}), nil)
cm.On("GetContainerArchive", ctx, "/var/run/act/workflow/pathcmd.txt").Return(io.NopCloser(&bytes.Buffer{}), nil)
err := sr.main()(ctx)
assert.Nil(t, err)
cm.AssertExpectations(t)
}
func TestStepRunPrePost(t *testing.T) {
ctx := t.Context()
sr := &stepRun{}
err := sr.pre()(ctx)
assert.Nil(t, err)
err = sr.post()(ctx)
assert.Nil(t, err)
}
func TestStepShellCommand(t *testing.T) {
tests := []struct {
shell string
want string
}{
{"pwsh -v '. {0}'", "pwsh -v '. {0}'"},
{"pwsh", "pwsh -command . '{0}'"},
{"powershell", "powershell -command . '{0}'"},
{"node", "node {0}"},
{"python", "python {0}"},
}
for _, tt := range tests {
t.Run(tt.shell, func(t *testing.T) {
got := shellCommand(tt.shell)
assert.Equal(t, got, tt.want)
})
}
}