//go:build linux package activator import ( "errors" "os" "os/exec" "testing" "time" "github.com/carlosframework/platform/internal/hostbox" ) // startSleep starts a child that lives until the test ends and returns it // with a channel closed once it is reaped. func startSleep(t *testing.T) (*exec.Cmd, chan struct{}) { t.Helper() child := exec.Command("sleep", "30") if err := child.Start(); err != nil { t.Skip("no sleep binary:", err) } reaped := make(chan struct{}) go func() { _ = child.Wait(); close(reaped) }() t.Cleanup(func() { _ = child.Process.Kill(); <-reaped }) return child, reaped } // The pin opens the handle before it reads the start, refuses a start that // is not the grant's, and hands the watcher the same pinned process. func TestPlan11PinRootProcessChecksTheGrantStart(t *testing.T) { child, reaped := startSleep(t) pid := child.Process.Pid start, err := roleProcessStart(pid) if err != nil { t.Fatal(err) } if _, err := pinRootProcess(pid, start+"1"); !errors.Is(err, hostbox.ErrChanged) { t.Fatalf("other start: %v", err) } proc, err := pinRootProcess(pid, start) if err != nil { t.Fatal(err) } if err := proc.WithHandle(func(uintptr) {}); err != nil { t.Fatalf("pinned process has no handle: %v", err) } p := newClaimProcessFor(&exec.Cmd{Process: proc}, nil, true) time.Sleep(50 * time.Millisecond) if p.Exited() { t.Fatal("running root process reported exited") } _ = child.Process.Kill() <-reaped select { case <-p.done: case <-time.After(5 * time.Second): t.Fatal("exit of the pinned process was missed") } // The pid is gone (ESRCH): the grant's process has exited, so the // watcher reports an exit rather than a launch failure. gone, err := pinRootProcess(pid, start) if err != nil { t.Fatalf("gone process: %v", err) } q := newClaimProcessFor(&exec.Cmd{Process: gone}, nil, true) select { case <-q.done: case <-time.After(5 * time.Second): t.Fatal("gone root process not reported exited") } } // A root process the agent holds no pinned handle for is not watchable. It // must never read as exited. func TestPlan11RootWatchWithoutHandleIsNotExit(t *testing.T) { child, _ := startSleep(t) proc, err := os.FindProcess(child.Process.Pid) if err != nil { t.Fatal(err) } if err := proc.Release(); err != nil { t.Fatal(err) } p := newClaimProcessFor(&exec.Cmd{Process: proc}, nil, true) time.Sleep(50 * time.Millisecond) if p.Exited() { t.Fatal("unwatchable root process reported exited") } }