package tentacle import ( "context" "encoding/json" "os" "os/exec" "path/filepath" "testing" "time" ) func TestDelegationHelper(t *testing.T) { if len(os.Args) < 3 { return } mode, path := os.Args[len(os.Args)-2], os.Args[len(os.Args)-1] if mode == "delegation-guardian" { if err := RunGuardian(os.Stdin, os.Stdout, path); err != nil { os.Exit(1) } os.Exit(0) } if mode != "delegation-owner" { return } executable, _ := os.Executable() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() delegation, err := StartDelegation(ctx, []string{executable, "-test.run=^TestDelegationHelper$", "--", "delegation-guardian"}) if err != nil { t.Fatal(err) } manager, err := NewDelegatedManager(delegation) if err != nil { t.Fatal(err) } session, err := manager.Start(ctx, child_spec(t, "spawn", "spawn")) if err != nil { t.Fatal(err) } select { case <-session.Ready(): case <-ctx.Done(): t.Fatal(ctx.Err()) } if err := session.Activate(); err != nil { t.Fatal(err) } result, err := session.Invoke(ctx, "echo", nil) if err != nil { t.Fatal(err) } value := <-result if value.Err != nil { t.Fatal(value.Err) } var pid int if err := json.Unmarshal(value.Value, &pid); err != nil || pid <= 0 { t.Fatalf("descendant: %s %v", value.Value, err) } if err := os.WriteFile(path, []byte(delegation.Parent), 0600); err != nil { t.Fatal(err) } // Simulate abrupt reader death: no Stop, Shutdown, Close, or deferred cleanup. os.Exit(0) } func TestNativeDelegationReaderDeath(t *testing.T) { if os.Getenv("CTHULHU_TENTACLE_MANAGED") != "1" { t.Skip("requires a systemd user manager and cgroup v2") } executable, _ := os.Executable() marker := filepath.Join(t.TempDir(), "group") ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() command := exec.CommandContext(ctx, executable, "-test.run=^TestDelegationHelper$", "--", "delegation-owner", marker) if output, err := command.CombinedOutput(); err != nil { t.Fatalf("owner: %v %s", err, output) } parent, err := os.ReadFile(marker) if err != nil { t.Fatal(err) } timer := time.NewTimer(6 * time.Second) defer timer.Stop() ticker := time.NewTicker(10 * time.Millisecond) defer ticker.Stop() for { if _, err := os.Stat(string(parent)); os.IsNotExist(err) { return } select { case <-ticker.C: case <-timer.C: t.Fatal("reader death left its service/descendants alive") } } }