104 lines
3.0 KiB
Go
104 lines
3.0 KiB
Go
//go:build windows
|
|
|
|
package compose
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
func TestOwnedProcessUsesCreationTimeJobAttribute(t *testing.T) {
|
|
if procThreadAttributeJobList == 0 {
|
|
t.Fatal("owned Windows process must carry a creation-time job-list attribute")
|
|
}
|
|
cmd := exec.Command("cmd.exe")
|
|
configureOwnedProcess(cmd)
|
|
if cmd.SysProcAttr != nil && cmd.SysProcAttr.CreationFlags&windows.CREATE_SUSPENDED != 0 {
|
|
t.Fatal("owned Windows process must not rely on a post-start assignment race")
|
|
}
|
|
}
|
|
|
|
func TestOwnedJobPolicyKillsDescendantsOnClose(t *testing.T) {
|
|
if windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE == 0 {
|
|
t.Fatal("owned job must use kill-on-close policy")
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
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
|
|
}
|
|
child := exec.Command(os.Args[0], "-test.run=TestWindowsProcessHelper", "--")
|
|
child.Env = append(os.Environ(), "THOTHCTL_WINDOWS_HELPER=child", "THOTHCTL_WINDOWS_MARKER="+marker)
|
|
if err := child.Start(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if mode == "exit" {
|
|
return
|
|
}
|
|
if mode == "cancel" {
|
|
for {
|
|
time.Sleep(time.Second)
|
|
}
|
|
}
|
|
for {
|
|
_, _ = os.Stdout.Write([]byte("overflow\n"))
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
}
|
|
|
|
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")
|
|
t.Setenv("THOTHCTL_WINDOWS_HELPER", mode)
|
|
t.Setenv("THOTHCTL_WINDOWS_MARKER", marker)
|
|
ctx := context.Background()
|
|
var cancel context.CancelFunc
|
|
if mode == "cancel" {
|
|
ctx, cancel = context.WithCancel(ctx)
|
|
}
|
|
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
|
|
}()
|
|
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)
|
|
}
|
|
if mode == "cancel" && !errors.Is(err, context.Canceled) {
|
|
t.Fatalf("cancel error = %v, want context.Canceled", err)
|
|
}
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|