Files
act_runner/act/runner/step_docker_test.go
T
silverwind d6f5b68a0d enhance: test on Windows and fix what it found (#1348)
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>
2026-11-03 01:26:10 +00:00

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)
})
})
}
}