mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-10-29 03:16:43 +08:00
Without a docker daemon six `act/runner` tests failed instead of passing. A dry run still created and removed job networks, the job teardown dropped the dry-run flag along with every other context value, and the container mocks asked the daemon for the runner arch. This also speeds up the tests and CI without dropping assertions. Most of the time went to git and docker round trips, which are now pinned, batched or coalesced, and the dind and e2e steps cache or overlap more of their setup. On CI, `act/runner` went from 77s to 55s and `test-dind` from 108s to 55s. Locally, `act/runner` without docker went from 53s to 7s. Split out of https://gitea.com/gitea/runner/pulls/1348, which adds Windows CI on top. The go cache changes moved to https://gitea.com/gitea/runner/pulls/1350. Written by Claude Code. --------- Co-authored-by: bircni <bircni@icloud.com> Reviewed-on: https://gitea.com/gitea/runner/pulls/1349 Reviewed-by: bircni <bircni@icloud.com> Co-authored-by: silverwind <me@silverwind.io>
199 lines
5.8 KiB
Go
199 lines
5.8 KiB
Go
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
// Copyright 2022 The nektos/act Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package runner
|
|
|
|
import (
|
|
"cmp"
|
|
"context"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"gitea.com/gitea/runner/act/container"
|
|
|
|
"gitea.dev/actionslib/pkg/model"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type shellContainerMock struct{ *containerMock }
|
|
|
|
func (*shellContainerMock) ReplaceLogWriter(_, _ io.Writer) (io.Writer, io.Writer) { return nil, nil }
|
|
|
|
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{Env: testCheckoutEnv()},
|
|
Run: &model.Run{
|
|
JobID: "1",
|
|
Workflow: &model.Workflow{
|
|
Jobs: map[string]*model.Job{
|
|
"1": {},
|
|
},
|
|
},
|
|
},
|
|
JobContainer: cm,
|
|
jobRunDefaults: model.RunDefaults{Shell: "bash"},
|
|
},
|
|
Step: &model.Step{
|
|
ID: "1",
|
|
Run: "cmd",
|
|
WorkingDirectory: "workdir",
|
|
},
|
|
}
|
|
|
|
cm.On("Copy", "/var/run/act", []*container.FileEntry{fileEntry}).Return(noopExecutor)
|
|
cm.On("Exec", []string{"bash", "--noprofile", "--norc", "-e", "-o", "pipefail", "/var/run/act/workflow/1.sh"}, mock.AnythingOfType("map[string]string"), "", "workdir").Return(noopExecutor)
|
|
|
|
expectStepFileCommands(t, cm)
|
|
require.NoError(t, sr.main()(t.Context()))
|
|
|
|
cm.AssertExpectations(t)
|
|
}
|
|
|
|
func TestStepRunShellParity(t *testing.T) {
|
|
tests := []struct {
|
|
name, run, shell, workingDir string
|
|
actPath string
|
|
env map[string]string
|
|
host bool
|
|
probeErr error
|
|
wantExt string
|
|
wantCmd []string
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "implicit host bash",
|
|
host: true,
|
|
wantCmd: []string{"bash", "-e", "/var/run/act/workflow/1.sh"},
|
|
},
|
|
{
|
|
name: "implicit container bash",
|
|
wantCmd: []string{"bash", "-e", "/var/run/act/workflow/1.sh"},
|
|
},
|
|
{
|
|
name: "implicit container sh fallback",
|
|
probeErr: assert.AnError,
|
|
wantCmd: []string{"sh", "-e", "/var/run/act/workflow/1.sh"},
|
|
},
|
|
{
|
|
name: "builtin pwsh",
|
|
shell: "pwsh",
|
|
actPath: "/var/run/act with spaces",
|
|
wantExt: ".ps1",
|
|
wantCmd: []string{"pwsh", "-command", ". '/var/run/act with spaces/workflow/1.ps1'"},
|
|
},
|
|
{
|
|
name: "builtin powershell",
|
|
shell: "powershell",
|
|
wantExt: ".ps1",
|
|
wantCmd: []string{"powershell", "-command", ". '/var/run/act/workflow/1.ps1'"},
|
|
},
|
|
{
|
|
name: "custom pwsh template",
|
|
shell: "pwsh -NoProfile -File {0}",
|
|
wantExt: ".ps1",
|
|
wantCmd: []string{"pwsh", "-NoProfile", "-File", "/var/run/act/workflow/1.ps1"},
|
|
},
|
|
{
|
|
name: "missing placeholder",
|
|
shell: "bash -e",
|
|
wantErr: `invalid shell option "bash -e": format must contain {0}`,
|
|
},
|
|
{
|
|
name: "all placeholders",
|
|
shell: "bash -c '. {0}; . {0}'",
|
|
wantCmd: []string{"bash", "-c", ". /var/run/act/workflow/1.sh; . /var/run/act/workflow/1.sh"},
|
|
},
|
|
{
|
|
name: "step env expressions",
|
|
shell: "${{ env.SHELL }}",
|
|
workingDir: "${{ env.DIR }}",
|
|
env: map[string]string{"SHELL": "python {0}", "DIR": "subdir"},
|
|
wantExt: ".py",
|
|
wantCmd: []string{"python", "/var/run/act/workflow/1.py"},
|
|
},
|
|
{
|
|
name: "run expression without a context",
|
|
run: "echo ${{ COMMIT_SHA }}",
|
|
wantErr: "unable to interpolate the run script:",
|
|
},
|
|
{
|
|
name: "shell expression without a context",
|
|
shell: "${{ SHELL }}",
|
|
wantErr: "unable to interpolate the shell:",
|
|
},
|
|
{
|
|
name: "working directory expression without a context",
|
|
workingDir: "${{ DIR }}",
|
|
wantErr: "unable to interpolate the working directory:",
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
cm := &containerMock{}
|
|
var jobContainer container.ExecutionsEnvironment = &shellContainerMock{cm}
|
|
switch {
|
|
case test.actPath != "":
|
|
jobContainer = &container.HostEnvironment{ActPath: test.actPath}
|
|
case test.host:
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("Linux host shell selection")
|
|
}
|
|
test.env = map[string]string{"PATH": t.TempDir()}
|
|
require.NoError(t, os.WriteFile(filepath.Join(test.env["PATH"], "bash"), nil, 0o755))
|
|
jobContainer = &container.HostEnvironment{ActPath: "/var/run/act"}
|
|
case test.shell == "":
|
|
cm.On("Exec", []string{"sh", "-c", "command -v bash >/dev/null 2>&1"},
|
|
mock.AnythingOfType("map[string]string"), "", "").Return(func(context.Context) error {
|
|
return test.probeErr
|
|
})
|
|
}
|
|
|
|
sr := &stepRun{
|
|
RunContext: &RunContext{
|
|
Config: &Config{PresetGitHubContext: &model.GithubContext{}},
|
|
Run: &model.Run{JobID: "1", Workflow: &model.Workflow{Jobs: map[string]*model.Job{"1": {}}}},
|
|
JobContainer: jobContainer,
|
|
},
|
|
Step: &model.Step{ID: "1", Run: cmp.Or(test.run, "echo hi"), Shell: test.shell, WorkingDirectory: test.workingDir},
|
|
env: test.env,
|
|
}
|
|
|
|
name, script, err := sr.setupShellCommand(t.Context())
|
|
if test.wantErr != "" {
|
|
require.ErrorContains(t, err, test.wantErr)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
if test.wantExt == "" {
|
|
test.wantExt = ".sh"
|
|
}
|
|
wantScript := "\necho hi\n"
|
|
if test.wantExt == ".ps1" {
|
|
wantScript = "$ErrorActionPreference = 'stop'\necho hi\nif ((Test-Path -LiteralPath variable:/LASTEXITCODE)) { exit $LASTEXITCODE }"
|
|
}
|
|
assert.Equal(t, "workflow/1"+test.wantExt, name)
|
|
assert.Equal(t, wantScript, script)
|
|
assert.Equal(t, test.wantCmd, sr.cmd)
|
|
assert.Equal(t, test.env["DIR"], sr.WorkingDirectory)
|
|
cm.AssertExpectations(t)
|
|
})
|
|
}
|
|
}
|