From 6a5ea5fb836108f6de30de4f4926c7e739fe7066 Mon Sep 17 00:00:00 2001 From: PJ Date: Sat, 6 Jun 2026 23:45:13 +0530 Subject: [PATCH] test(ioscompanion): gated companion smoke test --- internal/driver/ioscompanion/smoke_test.go | 85 ++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 internal/driver/ioscompanion/smoke_test.go diff --git a/internal/driver/ioscompanion/smoke_test.go b/internal/driver/ioscompanion/smoke_test.go new file mode 100644 index 0000000..e4854e5 --- /dev/null +++ b/internal/driver/ioscompanion/smoke_test.go @@ -0,0 +1,85 @@ +//go:build withcompanion + +package ioscompanion + +import ( + "context" + "os" + "os/exec" + "strings" + "testing" + "time" +) + +// TestSmokeNewHierarchyScreenshot brings up the embedded companion against the +// booted simulator, reads one hierarchy and screenshot, closes, and asserts the +// companion child left no orphan behind. It is gated behind both the +// withcompanion build tag (so the binary is embedded) and an environment +// variable, so it never runs in the default suite. +func TestSmokeNewHierarchyScreenshot(t *testing.T) { + if os.Getenv("SANDERLING_IOS_INTEGRATION") == "" { + t.Skip("set SANDERLING_IOS_INTEGRATION=1 to run the companion smoke test") + } + udid := bootedUDID(t) + + before := companionProcessCount(t, udid) + + ctx, cancel := context.WithTimeout(context.Background(), 50*time.Second) + defer cancel() + d, err := New(ctx, Options{UniqueDeviceIdentifier: udid, Output: os.Stderr}) + if err != nil { + t.Fatalf("New: %v", err) + } + + if _, err := d.Hierarchy(ctx); err != nil { + d.Close() + t.Fatalf("Hierarchy: %v", err) + } + if _, err := d.Screenshot(ctx); err != nil { + d.Close() + t.Fatalf("Screenshot: %v", err) + } + d.Close() + + // Give the child a moment to exit after SIGTERM, then confirm no orphan. + deadline := time.Now().Add(10 * time.Second) + for time.Now().Before(deadline) { + if companionProcessCount(t, udid) <= before { + return + } + time.Sleep(200 * time.Millisecond) + } + t.Fatalf("companion process for %s outlived Close (orphan)", udid) +} + +func bootedUDID(t *testing.T) string { + t.Helper() + output, err := exec.Command("xcrun", "simctl", "list", "devices", "booted").Output() + if err != nil { + t.Fatalf("simctl list: %v", err) + } + for _, line := range strings.Split(string(output), "\n") { + if start := strings.Index(line, "("); start >= 0 { + if end := strings.Index(line[start:], ")"); end > 0 { + candidate := line[start+1 : start+end] + if len(candidate) == 36 { + return candidate + } + } + } + } + t.Skip("no booted simulator available") + return "" +} + +func companionProcessCount(t *testing.T, udid string) int { + t.Helper() + output, _ := exec.Command("pgrep", "-f", udid).Output() + count := 0 + for _, line := range strings.Split(strings.TrimSpace(string(output)), "\n") { + if strings.TrimSpace(line) != "" { + count++ + } + } + return count +}