From 768acdf240e0b2a3a7ad5832261b709cac2e2404 Mon Sep 17 00:00:00 2001 From: PJ Date: Thu, 23 Apr 2026 17:33:39 +0700 Subject: [PATCH] fix(ios): replace LaunchApp with BootedUDID; simctl launch moved to XCTest path --- internal/ios/ios.go | 18 +++----- internal/ios/ios_test.go | 89 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 12 deletions(-) create mode 100644 internal/ios/ios_test.go diff --git a/internal/ios/ios.go b/internal/ios/ios.go index 36522ab..c195b20 100644 --- a/internal/ios/ios.go +++ b/internal/ios/ios.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "io" - "os" "os/exec" "strings" "time" @@ -55,18 +54,13 @@ func EnsureSimulator(ctx context.Context, deviceName string, stdout io.Writer) e return nil } -func LaunchApp(ctx context.Context, bundleID string, env map[string]string) error { - args := []string{"simctl", "launch", "--terminate-running-process", "booted", bundleID} - cmd := exec.CommandContext(ctx, "xcrun", args...) - cmd.Env = os.Environ() - for k, v := range env { - cmd.Env = append(cmd.Env, "SIMCTL_CHILD_"+k+"="+v) +// BootedUDID returns the UDID of the currently booted iOS simulator, or "" if none is booted. +func BootedUDID(ctx context.Context) string { + d, _ := bootedSimulator(ctx) + if d == nil { + return "" } - out, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("launch %s: %w\n%s", bundleID, err, strings.TrimSpace(string(out))) - } - return nil + return d.UDID } func bootedSimulator(ctx context.Context) (*simDevice, error) { diff --git a/internal/ios/ios_test.go b/internal/ios/ios_test.go new file mode 100644 index 0000000..d957fe6 --- /dev/null +++ b/internal/ios/ios_test.go @@ -0,0 +1,89 @@ +package ios + +import ( + "context" + "testing" +) + +func TestPickSimulator_ByName(t *testing.T) { + available := []simDevice{ + {UDID: "aaa", Name: "iPad Pro", IsAvailable: true}, + {UDID: "bbb", Name: "iPhone 15", IsAvailable: true}, + } + got, err := pickSimulator("iPhone 15", available) + if err != nil { + t.Fatal(err) + } + if got.UDID != "bbb" { + t.Errorf("got %q, want bbb", got.UDID) + } +} + +func TestPickSimulator_ByUDID(t *testing.T) { + available := []simDevice{ + {UDID: "aaa", Name: "iPad Pro", IsAvailable: true}, + {UDID: "bbb", Name: "iPhone 14", IsAvailable: true}, + } + got, err := pickSimulator("aaa", available) + if err != nil { + t.Fatal(err) + } + if got.Name != "iPad Pro" { + t.Errorf("got %q, want iPad Pro", got.Name) + } +} + +func TestPickSimulator_UnknownName(t *testing.T) { + available := []simDevice{ + {UDID: "aaa", Name: "iPad Pro", IsAvailable: true}, + } + _, err := pickSimulator("Pixel 7", available) + if err == nil { + t.Fatal("expected error for unknown simulator name") + } +} + +func TestPickSimulator_EmptyName_PrefersIPhone(t *testing.T) { + available := []simDevice{ + {UDID: "aaa", Name: "iPad mini", IsAvailable: true}, + {UDID: "bbb", Name: "iPhone 16", IsAvailable: true}, + {UDID: "ccc", Name: "Apple Watch", IsAvailable: true}, + } + got, err := pickSimulator("", available) + if err != nil { + t.Fatal(err) + } + if got.UDID != "bbb" { + t.Errorf("got %q, want bbb (iPhone)", got.UDID) + } +} + +func TestPickSimulator_EmptyName_FallsBackToFirst(t *testing.T) { + available := []simDevice{ + {UDID: "aaa", Name: "iPad Air", IsAvailable: true}, + {UDID: "bbb", Name: "Apple TV", IsAvailable: true}, + } + got, err := pickSimulator("", available) + if err != nil { + t.Fatal(err) + } + if got.UDID != "aaa" { + t.Errorf("got %q, want aaa (first available)", got.UDID) + } +} + +func TestPickSimulator_EmptyList(t *testing.T) { + _, err := pickSimulator("", nil) + if err == nil { + t.Fatal("expected error for empty simulator list") + } +} + +func TestBootedUDID_CanceledContext(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + udid := BootedUDID(ctx) + if udid != "" { + t.Errorf("expected empty UDID on canceled context, got %q", udid) + } +}