147 lines
4.0 KiB
Go
147 lines
4.0 KiB
Go
//go:build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"sync"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestNativeProcessHelper(t *testing.T) {
|
|
if os.Getenv("AP_OWNED_HELPER") != "1" {
|
|
t.Skip("helper child only")
|
|
}
|
|
fmt.Fprintln(os.Stdout, "READY")
|
|
io.Copy(io.Discard, os.Stdin)
|
|
os.Exit(0)
|
|
}
|
|
func startOwnedHelper(t *testing.T) (uint32, func()) {
|
|
t.Helper()
|
|
exe, err := os.Executable()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
cmd := exec.CommandContext(ctx, exe, "-test.run=^TestNativeProcessHelper$")
|
|
cmd.Env = append(os.Environ(), "AP_OWNED_HELPER=1")
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: 0x08000000}
|
|
input, err := cmd.StdinPipe()
|
|
if err != nil {
|
|
cancel()
|
|
t.Fatal(err)
|
|
}
|
|
output, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
input.Close()
|
|
cancel()
|
|
t.Fatal(err)
|
|
}
|
|
if err = cmd.Start(); err != nil {
|
|
input.Close()
|
|
cancel()
|
|
t.Fatal(err)
|
|
}
|
|
var once sync.Once
|
|
stop := func() { once.Do(func() { input.Close(); cmd.Wait(); cancel() }) }
|
|
t.Cleanup(stop)
|
|
line, err := bufio.NewReader(output).ReadString('\n')
|
|
if err != nil || line != "READY\n" {
|
|
t.Fatalf("owned helper did not start: %v", err)
|
|
}
|
|
return uint32(cmd.Process.Pid), stop
|
|
}
|
|
func requireNativeProcesses(t *testing.T) {
|
|
t.Helper()
|
|
if os.Getenv("AP_NATIVE_PROCESSES") != "1" {
|
|
t.Skip("opt-in, test-owned children only; never enumerates other processes")
|
|
}
|
|
}
|
|
func TestNativeOwnedProcessRightsIdentityAndLifetime(t *testing.T) {
|
|
requireNativeProcesses(t)
|
|
pid, stop := startOwnedHelper(t)
|
|
backend := newWindowsBackend()
|
|
// The system-wide enumerator is intentionally NEVER called in native tests.
|
|
backend.list = func() ([]procInfo, error) { return []procInfo{{PID: pid, Name: "OwnedHelper.exe"}}, nil }
|
|
snap, err := backend.Snapshot()
|
|
if err != nil || len(snap.Processes) != 1 {
|
|
t.Fatalf("owned snapshot failed: %v", err)
|
|
}
|
|
p := snap.Processes[0]
|
|
defer backend.Close(&p)
|
|
if p.Key.Created == 0 || p.RSS == 0 || p.Name == "" || !p.Waitable {
|
|
t.Fatal("incomplete owned-process metadata")
|
|
}
|
|
alive, err := backend.Alive(&p)
|
|
if err != nil || !alive {
|
|
t.Fatal("live helper not recognized", err)
|
|
}
|
|
if err = setPrio(p.Handle, p.PID, p.Prio); err == nil {
|
|
t.Fatal("snapshot handle unexpectedly grants SET_INFORMATION")
|
|
}
|
|
original := p.Prio
|
|
bad := p
|
|
bad.Key.Created++
|
|
if err = backend.SetPriority(&bad, PriorityClassHigh); err == nil {
|
|
t.Fatal("identity mismatch did not prevent write")
|
|
}
|
|
now, err := backend.Priority(&p)
|
|
if err != nil || now != original {
|
|
t.Fatal("identity check changed priority")
|
|
}
|
|
if err = backend.SetPriority(&p, PriorityClassBelowNormal); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
now, err = backend.Priority(&p)
|
|
if err != nil || now != PriorityClassBelowNormal {
|
|
t.Fatal("owned priority write not observed", err)
|
|
}
|
|
// Do not request REALTIME in native tests. The child is about to terminate.
|
|
stop()
|
|
alive, err = backend.Alive(&p)
|
|
if err != nil || alive {
|
|
t.Fatal("signaled process handle not recognized as exited", err)
|
|
}
|
|
}
|
|
func TestNativeControllerRestoresOwnedChild(t *testing.T) {
|
|
requireNativeProcesses(t)
|
|
pid, _ := startOwnedHelper(t)
|
|
backend := newWindowsBackend()
|
|
backend.list = func() ([]procInfo, error) { return []procInfo{{PID: pid, Name: "OwnedHelper.exe"}}, nil }
|
|
initial, err := backend.inspect(procInfo{PID: pid})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer backend.Close(&initial)
|
|
// Establish a known, non-realtime baseline on this test-owned child.
|
|
if err = backend.SetPriority(&initial, PriorityClassBelowNormal); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c := newController(backend, 1, 0, false, time.Minute)
|
|
r := c.Step()
|
|
if len(r.Errors) != 0 {
|
|
c.Shutdown()
|
|
t.Fatal(r.Errors)
|
|
}
|
|
actual, err := backend.Priority(&initial)
|
|
if err != nil || actual != PriorityClassHigh {
|
|
c.Shutdown()
|
|
t.Fatal("promotion failed", err)
|
|
}
|
|
r = c.Shutdown()
|
|
if r.Unrestored != 0 || len(r.Errors) != 0 {
|
|
t.Fatal(r)
|
|
}
|
|
actual, err = backend.Priority(&initial)
|
|
if err != nil || actual != PriorityClassBelowNormal {
|
|
t.Fatal("baseline not restored", err)
|
|
}
|
|
}
|