test: make task1 bounds oracles exact

This commit is contained in:
2026-08-11 02:06:49 +02:00
parent 2f34a37d13
commit 3564817409
2 changed files with 231 additions and 40 deletions
@@ -7,7 +7,8 @@ import (
"errors"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
@@ -32,72 +33,166 @@ func TestOwnedJobPolicyKillsDescendantsOnClose(t *testing.T) {
}
// TestWindowsProcessHelper is both a test helper and the child process used by the runtime
// ownership regression below. The child writes a marker only if it outlives its parent job.
// ownership regression below. The grandchild records its PID before the parent can exit. The
// parent then either exits, blocks for cancellation, or overflows stdout.
func TestWindowsProcessHelper(t *testing.T) {
mode := os.Getenv("THOTHCTL_WINDOWS_HELPER")
if mode == "" {
return
}
marker := os.Getenv("THOTHCTL_WINDOWS_MARKER")
if mode == "child" {
time.Sleep(2 * time.Second)
_ = os.WriteFile(marker, []byte("survived"), 0o600)
return
pidFile := os.Getenv("THOTHCTL_WINDOWS_PID_FILE")
releaseFile := os.Getenv("THOTHCTL_WINDOWS_RELEASE_FILE")
if mode == "grandchild" {
if err := os.WriteFile(pidFile, []byte(strconv.Itoa(os.Getpid())), 0o600); err != nil {
t.Fatal(err)
}
for {
time.Sleep(time.Hour)
}
}
child := exec.Command(os.Args[0], "-test.run=TestWindowsProcessHelper", "--")
child.Env = append(os.Environ(), "THOTHCTL_WINDOWS_HELPER=child", "THOTHCTL_WINDOWS_MARKER="+marker)
child.Env = append(os.Environ(),
"THOTHCTL_WINDOWS_HELPER=grandchild",
"THOTHCTL_WINDOWS_PID_FILE="+pidFile,
)
if err := child.Start(); err != nil {
t.Fatal(err)
}
waitForWindowsPIDFile(t, pidFile)
waitForWindowsReleaseFile(t, releaseFile)
if mode == "exit" {
return
}
if mode == "cancel" {
for {
time.Sleep(time.Second)
time.Sleep(time.Hour)
}
}
for {
_, _ = os.Stdout.Write([]byte("overflow\n"))
}
}
func waitForWindowsPIDFile(t *testing.T, path string) {
t.Helper()
deadline := time.Now().Add(5 * time.Second)
for time.Now().Before(deadline) {
contents, err := os.ReadFile(path)
if err == nil {
pid, parseErr := strconv.Atoi(strings.TrimSpace(string(contents)))
if parseErr == nil && pid > 0 {
return
}
}
time.Sleep(time.Millisecond)
}
t.Fatalf("grandchild PID was not recorded in %s", path)
}
func waitForWindowsReleaseFile(t *testing.T, path string) {
t.Helper()
deadline := time.Now().Add(5 * time.Second)
for time.Now().Before(deadline) {
if _, err := os.Stat(path); err == nil {
return
}
time.Sleep(time.Millisecond)
}
t.Fatalf("test did not release Windows helper via %s", path)
}
func openWindowsProcessForTest(t *testing.T, pid int) windows.Handle {
t.Helper()
const access = windows.SYNCHRONIZE | windows.PROCESS_QUERY_LIMITED_INFORMATION
handle, err := windows.OpenProcess(access, false, uint32(pid))
if err != nil {
t.Fatalf("OpenProcess(%d): %v", pid, err)
}
return handle
}
func assertWindowsProcessTerminated(t *testing.T, handle windows.Handle, pid int) {
t.Helper()
const stillActive = 259
status, err := windows.WaitForSingleObject(handle, uint32((5*time.Second)/time.Millisecond))
if err != nil {
t.Fatalf("WaitForSingleObject(%d): %v", pid, err)
}
if status != windows.WAIT_OBJECT_0 {
t.Fatalf("WaitForSingleObject(%d) = %#x, want signaled terminated process", pid, status)
}
var exitCode uint32
if err := windows.GetExitCodeProcess(handle, &exitCode); err != nil {
t.Fatalf("GetExitCodeProcess(%d): %v", pid, err)
}
if exitCode == stillActive {
t.Fatalf("GetExitCodeProcess(%d) still reports STILL_ACTIVE", pid)
}
}
func TestOwnedProcessTerminatesDescendantOnOverflowCancelAndParentExit(t *testing.T) {
for _, mode := range []string{"overflow", "cancel", "exit"} {
t.Run(mode, func(t *testing.T) {
marker := filepath.Join(t.TempDir(), "descendant-marker")
pidFile, err := os.CreateTemp("", "thothctl-windows-grandchild-*.pid")
if err != nil {
t.Fatal(err)
}
pidPath := pidFile.Name()
_ = pidFile.Close()
_ = os.Remove(pidPath)
defer os.Remove(pidPath)
releaseFile, err := os.CreateTemp("", "thothctl-windows-release-*")
if err != nil {
t.Fatal(err)
}
releasePath := releaseFile.Name()
_ = releaseFile.Close()
_ = os.Remove(releasePath)
defer os.Remove(releasePath)
t.Setenv("THOTHCTL_WINDOWS_HELPER", mode)
t.Setenv("THOTHCTL_WINDOWS_MARKER", marker)
t.Setenv("THOTHCTL_WINDOWS_PID_FILE", pidPath)
t.Setenv("THOTHCTL_WINDOWS_RELEASE_FILE", releasePath)
ctx := context.Background()
var cancel context.CancelFunc
if mode == "cancel" {
ctx, cancel = context.WithCancel(ctx)
defer cancel()
}
result := make(chan error, 1)
go func() {
_, err := runBoundedWindows(ctx, os.Args[0], []string{"-test.run=TestWindowsProcessHelper", "--"}, nil, CaptureLimits{StdoutBytes: 1024, StderrBytes: 1024})
result <- err
_, runErr := runBoundedWindows(ctx, os.Args[0], []string{"-test.run=TestWindowsProcessHelper", "--"}, nil, CaptureLimits{StdoutBytes: 1024, StderrBytes: 1024})
result <- runErr
}()
waitForWindowsPIDFile(t, pidPath)
contents, err := os.ReadFile(pidPath)
if err != nil {
t.Fatal(err)
}
pid, err := strconv.Atoi(strings.TrimSpace(string(contents)))
if err != nil {
t.Fatal(err)
}
handle := openWindowsProcessForTest(t, pid)
defer windows.CloseHandle(handle)
if err := os.WriteFile(releasePath, []byte("release"), 0o600); err != nil {
t.Fatal(err)
}
if mode == "cancel" {
time.Sleep(100 * time.Millisecond)
cancel()
}
select {
case err := <-result:
if mode == "overflow" && !errors.Is(err, ErrOutputLimit) {
t.Fatalf("overflow error = %v, want ErrOutputLimit", err)
case runErr := <-result:
if mode == "overflow" && !errors.Is(runErr, ErrOutputLimit) {
t.Fatalf("overflow error = %v, want ErrOutputLimit", runErr)
}
if mode == "cancel" && !errors.Is(err, context.Canceled) {
t.Fatalf("cancel error = %v, want context.Canceled", err)
if mode == "cancel" && !errors.Is(runErr, context.Canceled) {
t.Fatalf("cancel error = %v, want context.Canceled", runErr)
}
case <-time.After(5 * time.Second):
t.Fatal("owned process did not terminate")
}
time.Sleep(150 * time.Millisecond)
if _, err := os.Stat(marker); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("descendant marker error = %v; descendant survived %s termination", err, mode)
}
assertWindowsProcessTerminated(t, handle, pid)
})
}
}