mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-11-01 04:48:49 +08:00
A successful host step whose background child kept its output open failed with `exec: WaitDelay expired before I/O complete`. Like GitHub's runner (`ProcessExitedHandler` in https://github.com/actions/runner/blob/main/src/Runner.Sdk/ProcessInvoker.cs), the step now succeeds after the drain timeout. The leftover is killed only on Windows, as GitHub does there. A step whose timeout or cancellation hits during the drain still fails. Fixes: https://gitea.com/gitea/runner/issues/1290 Co-authored-by: bircni <bircni@icloud.com> Reviewed-on: https://gitea.com/gitea/runner/pulls/1323 Reviewed-by: bircni <bircni@icloud.com> Co-authored-by: silverwind <me@silverwind.io>
75 lines
1.8 KiB
Go
75 lines
1.8 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package process
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"os/exec"
|
|
"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 := exec.CommandContext(t.Context(), "sleep", "10")
|
|
cmd.SysProcAttr = SysProcAttr("", false)
|
|
tk := NewTreeKill(cmd)
|
|
require.NoError(t, cmd.Start())
|
|
defer func() { _ = cmd.Wait() }()
|
|
|
|
killer, err := tk.Capture(cmd.Process)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, killer)
|
|
require.NoError(t, cmd.Cancel())
|
|
require.NoError(t, killer.Close())
|
|
}
|