test: make task1 bounds oracles exact
This commit is contained in:
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user