Files
act_runner/pkg/runner/step_action_local_test.go
silverwind f923badec7 Use golangci-lint fmt to format code (#163)
Use `golangci-lint fmt` to format code, upgrading `.golangci.yml` to v2 and mirroring the linter configuration used by https://github.com/go-gitea/gitea. `gci` now handles import ordering into standard, project-local, blank, and default groups.

Mirrors https://github.com/go-gitea/gitea/pull/37194.

Changes:
- Upgrade `.golangci.yml` to v2 format with the same linter set as gitea (minus `prealloc`, `unparam`, `testifylint`, `nilnil` which produced too many pre-existing issues)
- Add path-based exclusions (`bodyclose`, `gosec` in tests; `gosec:G115`/`G117` globally)
- Run lint via `make lint-go` in CI instead of `golangci/golangci-lint-action`, matching the pattern used by other Gitea repos
- Apply safe auto-fixes (`modernize`, `perfsprint`, `usetesting`, etc.)
- Add explanations to existing `//nolint` directives
- Remove dead code (unused `newRemoteReusableWorkflow` and `networkName`), duplicate imports, and shadowed `max` builtins
- Replace deprecated `docker/distribution/reference` with `distribution/reference`
- Fix `Deprecated:` comment casing and simplify nil/len checks

---
This PR was written with the help of Claude Opus 4.7

Reviewed-on: https://gitea.com/gitea/act/pulls/163
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
2026-04-18 09:10:09 +00:00

295 lines
7.1 KiB
Go

package runner
import (
"bytes"
"context"
"io"
"path/filepath"
"strings"
"testing"
"github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"go.yaml.in/yaml/v4"
)
type stepActionLocalMocks struct {
mock.Mock
}
func (salm *stepActionLocalMocks) runAction(step actionStep, actionDir string, remoteAction *remoteAction) common.Executor {
args := salm.Called(step, actionDir, remoteAction)
return args.Get(0).(func(context.Context) error)
}
func (salm *stepActionLocalMocks) readAction(_ context.Context, step *model.Step, actionDir, actionPath string, readFile actionYamlReader, writeFile fileWriter) (*model.Action, error) {
args := salm.Called(step, actionDir, actionPath, readFile, writeFile)
return args.Get(0).(*model.Action), args.Error(1)
}
func TestStepActionLocalTest(t *testing.T) {
ctx := context.Background()
cm := &containerMock{}
salm := &stepActionLocalMocks{}
sal := &stepActionLocal{
readAction: salm.readAction,
runAction: salm.runAction,
RunContext: &RunContext{
StepResults: map[string]*model.StepResult{},
ExprEval: &expressionEvaluator{},
Config: &Config{
Workdir: "/tmp",
},
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",
Uses: "./path/to/action",
},
}
salm.On("readAction", sal.Step, filepath.Clean("/tmp/path/to/action"), "", mock.Anything, mock.Anything).
Return(&model.Action{}, 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
})
cm.On("GetContainerArchive", ctx, "/var/run/act/workflow/pathcmd.txt").Return(io.NopCloser(&bytes.Buffer{}), nil)
salm.On("runAction", sal, filepath.Clean("/tmp/path/to/action"), (*remoteAction)(nil)).Return(func(ctx context.Context) error {
return nil
})
err := sal.pre()(ctx)
assert.Nil(t, err)
err = sal.main()(ctx)
assert.Nil(t, err)
cm.AssertExpectations(t)
salm.AssertExpectations(t)
}
func TestStepActionLocalPost(t *testing.T) {
table := []struct {
name string
stepModel *model.Step
actionModel *model.Action
initialStepResults map[string]*model.StepResult
err error
mocks struct {
env bool
exec bool
}
}{
{
name: "main-success",
stepModel: &model.Step{
ID: "step",
Uses: "./local/action",
},
actionModel: &model.Action{
Runs: model.ActionRuns{
Using: "node16",
Post: "post.js",
PostIf: "always()",
},
},
initialStepResults: map[string]*model.StepResult{
"step": {
Conclusion: model.StepStatusSuccess,
Outcome: model.StepStatusSuccess,
Outputs: map[string]string{},
},
},
mocks: struct {
env bool
exec bool
}{
env: true,
exec: true,
},
},
{
name: "main-failed",
stepModel: &model.Step{
ID: "step",
Uses: "./local/action",
},
actionModel: &model.Action{
Runs: model.ActionRuns{
Using: "node16",
Post: "post.js",
PostIf: "always()",
},
},
initialStepResults: map[string]*model.StepResult{
"step": {
Conclusion: model.StepStatusFailure,
Outcome: model.StepStatusFailure,
Outputs: map[string]string{},
},
},
mocks: struct {
env bool
exec bool
}{
env: true,
exec: true,
},
},
{
name: "skip-if-failed",
stepModel: &model.Step{
ID: "step",
Uses: "./local/action",
},
actionModel: &model.Action{
Runs: model.ActionRuns{
Using: "node16",
Post: "post.js",
PostIf: "success()",
},
},
initialStepResults: map[string]*model.StepResult{
"step": {
Conclusion: model.StepStatusFailure,
Outcome: model.StepStatusFailure,
Outputs: map[string]string{},
},
},
mocks: struct {
env bool
exec bool
}{
env: false,
exec: false,
},
},
{
name: "skip-if-main-skipped",
stepModel: &model.Step{
ID: "step",
If: yaml.Node{Value: "failure()"},
Uses: "./local/action",
},
actionModel: &model.Action{
Runs: model.ActionRuns{
Using: "node16",
Post: "post.js",
PostIf: "always()",
},
},
initialStepResults: map[string]*model.StepResult{
"step": {
Conclusion: model.StepStatusSkipped,
Outcome: model.StepStatusSkipped,
Outputs: map[string]string{},
},
},
mocks: struct {
env bool
exec bool
}{
env: false,
exec: false,
},
},
}
for _, tt := range table {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
cm := &containerMock{}
sal := &stepActionLocal{
env: map[string]string{},
RunContext: &RunContext{
Config: &Config{
GitHubInstance: "https://github.com",
},
JobContainer: cm,
Run: &model.Run{
JobID: "1",
Workflow: &model.Workflow{
Jobs: map[string]*model.Job{
"1": {},
},
},
},
StepResults: tt.initialStepResults,
},
Step: tt.stepModel,
action: tt.actionModel,
}
sal.RunContext.ExprEval = sal.RunContext.NewExpressionEvaluator(ctx)
if tt.mocks.exec {
suffixMatcher := func(suffix string) any {
return mock.MatchedBy(func(array []string) bool {
return strings.HasSuffix(array[1], suffix)
})
}
cm.On("Exec", suffixMatcher("pkg/runner/local/action/post.js"), sal.env, "", "").Return(func(ctx context.Context) error { return tt.err })
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
})
cm.On("GetContainerArchive", ctx, "/var/run/act/workflow/pathcmd.txt").Return(io.NopCloser(&bytes.Buffer{}), nil)
}
err := sal.post()(ctx)
assert.Equal(t, tt.err, err)
assert.Equal(t, sal.RunContext.StepResults["post-step"], (*model.StepResult)(nil))
cm.AssertExpectations(t)
})
}
}