mirror of
https://gitea.com/gitea/runner.git
synced 2026-11-04 07:33:47 +00: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>
158 lines
3.9 KiB
Go
158 lines
3.9 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package process
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"runtime"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestNewTreeKillConfiguresCommand(t *testing.T) {
|
|
cmd := exec.CommandContext(t.Context(), "sleep", "1")
|
|
tk := NewTreeKill(cmd)
|
|
|
|
require.NotNil(t, tk)
|
|
require.NotNil(t, cmd.Cancel)
|
|
require.Equal(t, treeKillWaitDelay, cmd.WaitDelay)
|
|
require.NoError(t, cmd.Cancel())
|
|
}
|
|
|
|
func TestTreeKillWaitWithLeftoverHoldingOutput(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("uses POSIX shell")
|
|
}
|
|
for _, tc := range []struct {
|
|
name string
|
|
timeout time.Duration
|
|
wantErr bool
|
|
}{
|
|
{name: "succeeds", timeout: time.Minute},
|
|
{name: "fails once the context is done", timeout: 10 * time.Millisecond, wantErr: true},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(t.Context(), tc.timeout)
|
|
defer cancel()
|
|
cmd := exec.CommandContext(ctx, "sh", "-c", "sleep 30 &")
|
|
cmd.Stdout = io.Discard
|
|
cmd.SysProcAttr = SysProcAttr("", false)
|
|
tk := NewTreeKill(cmd)
|
|
cmd.WaitDelay = 50 * time.Millisecond
|
|
require.NoError(t, cmd.Start())
|
|
killer, err := tk.Capture(cmd.Process)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { _ = killer.Kill() })
|
|
|
|
err = tk.Wait(ctx, cmd)
|
|
if tc.wantErr {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTreeKillCaptureStoresKiller(t *testing.T) {
|
|
cmd, ready := newTestProcess(t, "child", false)
|
|
tk := NewTreeKill(cmd)
|
|
require.NoError(t, cmd.Start())
|
|
ready()
|
|
|
|
killer, err := tk.Capture(cmd.Process)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, killer)
|
|
require.NoError(t, cmd.Cancel())
|
|
require.NoError(t, killer.Close())
|
|
}
|
|
|
|
func newTestProcess(t *testing.T, mode string, copiedOutput bool) (*exec.Cmd, func() int) {
|
|
t.Helper()
|
|
ctx, cancel := context.WithTimeout(t.Context(), 20*time.Second)
|
|
t.Cleanup(cancel)
|
|
cmd := exec.CommandContext(ctx, os.Args[0], "-test.run=^TestProcessHelper$")
|
|
cmd.Env = append(os.Environ(), "GITEA_PROCESS_HELPER="+mode)
|
|
cmd.SysProcAttr = SysProcAttr("", false)
|
|
cmd.Stderr = os.Stderr
|
|
input, err := cmd.StdinPipe()
|
|
require.NoError(t, err)
|
|
var output io.ReadCloser
|
|
if copiedOutput {
|
|
var writer *io.PipeWriter
|
|
output, writer = io.Pipe()
|
|
cmd.Stdout = writer
|
|
} else {
|
|
output, err = cmd.StdoutPipe()
|
|
require.NoError(t, err)
|
|
}
|
|
stop := context.AfterFunc(ctx, func() {
|
|
_ = input.Close()
|
|
_ = output.Close()
|
|
})
|
|
t.Cleanup(func() {
|
|
stop()
|
|
_ = input.Close()
|
|
_ = output.Close()
|
|
if cmd.Process != nil {
|
|
_ = cmd.Process.Kill()
|
|
_ = cmd.Wait()
|
|
}
|
|
})
|
|
return cmd, func() int {
|
|
t.Helper()
|
|
_, err := input.Write([]byte{1})
|
|
require.NoError(t, err)
|
|
var childPID int
|
|
_, err = fmt.Fscan(output, &childPID)
|
|
require.NoError(t, err, "process should report its PID")
|
|
require.Positive(t, childPID)
|
|
return childPID
|
|
}
|
|
}
|
|
|
|
func TestProcessHelper(t *testing.T) {
|
|
mode := os.Getenv("GITEA_PROCESS_HELPER")
|
|
switch mode {
|
|
case "":
|
|
return
|
|
case "orphan-child":
|
|
interrupt := make(chan os.Signal, 1)
|
|
signal.Notify(interrupt, os.Interrupt)
|
|
_, err := fmt.Fprintln(os.Stdout, os.Getpid())
|
|
require.NoError(t, err)
|
|
<-interrupt
|
|
case "child":
|
|
_, err := fmt.Fprintln(os.Stdout, os.Getpid())
|
|
require.NoError(t, err)
|
|
_, err = io.Copy(io.Discard, os.Stdin)
|
|
require.NoError(t, err)
|
|
default:
|
|
var start [1]byte
|
|
_, err := io.ReadFull(os.Stdin, start[:])
|
|
require.NoError(t, err)
|
|
child := exec.Command(os.Args[0], "-test.run=^TestProcessHelper$")
|
|
child.Env = append(os.Environ(), "GITEA_PROCESS_HELPER=child")
|
|
if mode == "orphan" {
|
|
child.Env = append(os.Environ(), "GITEA_PROCESS_HELPER=orphan-child")
|
|
}
|
|
child.Stdin = os.Stdin
|
|
child.Stdout = os.Stdout
|
|
child.Stderr = os.Stderr
|
|
require.NoError(t, child.Start())
|
|
if mode == "orphan" {
|
|
os.Exit(0)
|
|
}
|
|
require.NoError(t, child.Wait())
|
|
}
|
|
os.Exit(0)
|
|
}
|