mirror of
https://gitea.com/gitea/act_runner
synced 2026-11-03 10:57:22 +01:00
Adds a weekly and on-demand workflow that runs the cross-compiled tests on Windows Server 2025 Core in a [dockur/windows](https://github.com/dockur/windows) QEMU VM, and fixes the Windows bugs it found in host and UNC paths, env handling, shell quoting, post-task scripts, health checks, checkout, action symlinks, step summaries, Git client certificates and docker mode from a Windows host. Host-mode steps now emit UTF-8, `bash` falls back to Git's bash instead of the WSL launcher, the runner can run as a native Windows service, steps can keep background helpers with `RUNNER_TRACKING_ID`, and Windows arm64 binaries are released. Fixes: https://gitea.com/gitea/runner/issues/1339 Fixes: https://gitea.com/gitea/runner/issues/637 Fixes: https://gitea.com/gitea/runner/issues/1172 Fixes: https://gitea.com/gitea/runner/issues/347 Reviewed-on: https://gitea.com/gitea/runner/pulls/1348 Reviewed-by: bircni <bircni@icloud.com>
192 lines
6.7 KiB
Go
192 lines
6.7 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 (
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.com/gitea/runner/act/container"
|
|
|
|
"gitea.dev/actionslib/pkg/model"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.yaml.in/yaml/v4"
|
|
)
|
|
|
|
func TestStepDockerMain(t *testing.T) {
|
|
cm := &containerMock{}
|
|
|
|
var input *container.NewContainerInput
|
|
|
|
origContainerNewContainer := ContainerNewContainer
|
|
ContainerNewContainer = func(containerInput *container.NewContainerInput) container.ExecutionsEnvironment {
|
|
input = containerInput
|
|
return cm
|
|
}
|
|
defer func() {
|
|
ContainerNewContainer = origContainerNewContainer
|
|
}()
|
|
|
|
ctx := t.Context()
|
|
|
|
sd := &stepDocker{
|
|
RunContext: &RunContext{
|
|
StepResults: map[string]*model.StepResult{},
|
|
Config: &Config{
|
|
PresetGitHubContext: &model.GithubContext{},
|
|
Secrets: map[string]string{
|
|
"DOCKER_USERNAME": "docker-user",
|
|
"DOCKER_PASSWORD": "docker-password",
|
|
},
|
|
},
|
|
Run: &model.Run{
|
|
JobID: "1",
|
|
Workflow: &model.Workflow{
|
|
Jobs: map[string]*model.Job{
|
|
"1": {
|
|
Defaults: model.Defaults{
|
|
Run: model.RunDefaults{
|
|
Shell: "bash",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
JobContainer: cm,
|
|
Env: map[string]string{"GITHUB_ACTION_REPOSITORY": "other/composite", "GITHUB_ACTION_REF": "v1"},
|
|
},
|
|
Step: &model.Step{
|
|
ID: "1",
|
|
Uses: "docker://node:14",
|
|
WorkingDirectory: "workdir",
|
|
If: yaml.Node{Value: "github.action_repository == 'other/composite' && github.action_ref == 'v1'"},
|
|
},
|
|
}
|
|
require.NoError(t, sd.Step.RawWith.Encode(map[string]string{"args": "identity:${{ github.action_repository }}:${{ github.action_ref }}"}))
|
|
sd.RunContext.ExprEval = sd.RunContext.NewExpressionEvaluator(ctx)
|
|
|
|
cm.On("Pull", false).Return(noopExecutor)
|
|
|
|
cm.On("Remove").Return(noopExecutor)
|
|
|
|
cm.On("Create", []string(nil), []string(nil)).Return(noopExecutor)
|
|
|
|
cm.On("Start", true).Return(noopExecutor)
|
|
|
|
cm.On("Close").Return(noopExecutor)
|
|
|
|
expectStepFileCommands(t, cm)
|
|
require.NoError(t, sd.main()(ctx))
|
|
|
|
assert.Equal(t, "node:14", input.Image)
|
|
assert.Equal(t, []string{"identity::"}, input.Cmd)
|
|
assert.Contains(t, input.Env, "GITHUB_ACTION_REPOSITORY=")
|
|
assert.Contains(t, input.Env, "GITHUB_ACTION_REF=")
|
|
assert.Equal(t, "other/composite", sd.RunContext.Env["GITHUB_ACTION_REPOSITORY"])
|
|
assert.Equal(t, "v1", sd.RunContext.Env["GITHUB_ACTION_REF"])
|
|
|
|
assert.Empty(t, input.Username)
|
|
assert.Empty(t, input.Password)
|
|
assert.True(t, input.AutoRemove)
|
|
|
|
cm.AssertExpectations(t)
|
|
}
|
|
|
|
func TestStepDockerNewStepContainer(t *testing.T) {
|
|
inheritedPath := (&container.LinuxContainerEnvironmentExtensions{}).DefaultPathVariable()
|
|
for _, test := range []struct {
|
|
name, platform, image, path, greeting string
|
|
allocPTY, expectDefault, keepsPath bool
|
|
}{
|
|
{name: "allocate PTY/off", platform: "node:14", image: "node:14", greeting: "hi"},
|
|
{name: "allocate PTY/on", platform: "node:14", image: "node:14", greeting: "hi", allocPTY: true},
|
|
{name: "docker mode attaches to job container network", platform: "node:14", image: "alpine:3.20", path: inheritedPath, greeting: "hello", keepsPath: true},
|
|
{name: "host mode uses default network and the image's PATH", platform: "-self-hosted", image: "alpine:3.20", path: inheritedPath, greeting: "hello", expectDefault: true},
|
|
{name: "host mode keeps a PATH that is not the runner's", platform: "-self-hosted", image: "alpine:3.20", path: "/custom/bin", greeting: "hello", expectDefault: true, keepsPath: true},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
cm := &containerMock{}
|
|
var captured *container.NewContainerInput
|
|
origContainerNewContainer := ContainerNewContainer
|
|
ContainerNewContainer = func(input *container.NewContainerInput) container.ExecutionsEnvironment {
|
|
captured = input
|
|
return cm
|
|
}
|
|
defer func() {
|
|
ContainerNewContainer = origContainerNewContainer
|
|
}()
|
|
|
|
ctx := t.Context()
|
|
sd := &stepDocker{
|
|
RunContext: &RunContext{
|
|
StepResults: map[string]*model.StepResult{},
|
|
Config: &Config{
|
|
PresetGitHubContext: &model.GithubContext{},
|
|
AllocatePTY: test.allocPTY,
|
|
PlatformPicker: func(_platforms []string) string {
|
|
return test.platform
|
|
},
|
|
},
|
|
Run: &model.Run{JobID: "1", Workflow: &model.Workflow{Jobs: map[string]*model.Job{"1": {}}}},
|
|
JobContainer: cm,
|
|
},
|
|
Step: &model.Step{ID: "1", Uses: "docker://" + test.image},
|
|
}
|
|
sd.RunContext.ExprEval = sd.RunContext.NewExpressionEvaluator(ctx)
|
|
if test.path != "" {
|
|
sd.env = map[string]string{"PATH": test.path}
|
|
require.NoError(t, sd.RunContext.resolvePlatformImage(ctx))
|
|
assert.Equal(t, test.expectDefault, sd.RunContext.IsHostEnv(), "IsHostEnv mismatch for platform %q", test.platform)
|
|
}
|
|
|
|
newStepContainer(ctx, sd, test.image, []string{"echo", test.greeting}, nil, "")
|
|
assert.Equal(t, test.allocPTY, captured.AllocatePTY)
|
|
if test.path != "" {
|
|
assert.Equal(t, test.keepsPath, slices.Contains(captured.Env, "PATH="+test.path))
|
|
if test.expectDefault {
|
|
assert.Equal(t, "default", captured.NetworkMode,
|
|
"host-mode step container must use 'default' network, got %q", captured.NetworkMode)
|
|
} else {
|
|
assert.True(t, strings.HasPrefix(captured.NetworkMode, "container:"),
|
|
"docker-mode step container must attach to job container network, got %q", captured.NetworkMode)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWindowsHostContainerActions(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name, hostOS string
|
|
jobContainer container.ExecutionsEnvironment
|
|
wantError bool
|
|
}{
|
|
{name: "Windows host", hostOS: "windows", jobContainer: &container.HostEnvironment{}, wantError: true},
|
|
{name: "Linux host", hostOS: "linux", jobContainer: &container.HostEnvironment{}},
|
|
{name: "macOS host", hostOS: "darwin", jobContainer: &container.HostEnvironment{}},
|
|
{name: "Windows with Linux job container", hostOS: "windows", jobContainer: &containerMock{}},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
original := hostOS
|
|
hostOS = test.hostOS
|
|
t.Cleanup(func() { hostOS = original })
|
|
rc := &RunContext{JobContainer: test.jobContainer, kubernetes: true}
|
|
wantError := errKubernetesDocker.Error()
|
|
if test.wantError {
|
|
wantError = errWindowsHostContainer.Error()
|
|
}
|
|
t.Run("docker step", func(t *testing.T) {
|
|
require.EqualError(t, (&stepDocker{RunContext: rc}).runUsesContainer()(t.Context()), wantError)
|
|
})
|
|
t.Run("container action", func(t *testing.T) {
|
|
require.EqualError(t, execAsDocker(t.Context(), &stepActionRemote{RunContext: rc}, "action", "", "", false, stepStageMain), wantError)
|
|
})
|
|
})
|
|
}
|
|
}
|