fix(ioscompanion): reinstall via simctl and sanitize companion env

This commit is contained in:
pj committed 2026-06-06 23:56:08 +05:30
1 parent 5ab2991dd0
commit 93b62d4aa5
2 files changed
+34 -6

No files matched your search

+27 -2
View File
@@ -16,6 +16,7 @@ import (
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"syscall"
"time"
@@ -95,6 +96,10 @@ type Driver struct {
// A seam so tests skip the xcrun shell-out.
resetContainer func(ctx context.Context) error
// reinstallApp uninstalls and reinstalls the app bundle for clear-state.
// A seam so tests skip the simctl shell-outs.
reinstallApp func(ctx context.Context) error
// idleClock drives WaitForIdle's settle poll. A seam so tests substitute a
// fake clock and avoid the real settle cap.
idleClock Clock
@@ -153,6 +158,7 @@ func New(ctx context.Context, options Options) (*Driver, error) {
driverInstance.address = address
driverInstance.restart = driverInstance.respawnAndRedial
driverInstance.resetContainer = driverInstance.resetDataContainer
driverInstance.reinstallApp = driverInstance.simctlReinstall
driverInstance.processContext, driverInstance.processCancel = context.WithCancel(ctx)
if err := driverInstance.bringUp(ctx); err != nil {
@@ -306,8 +312,7 @@ func (d *Driver) Launch(ctx context.Context, bundleID string, clearState bool, e
// container and warns once that a full reinstall needs the app path.
func (d *Driver) clearAppState(ctx context.Context) error {
if d.appPath != "" {
_ = d.withRecovery(ctx, func() error { return d.companion.Uninstall(ctx, d.bundleID) })
if err := d.withRecovery(ctx, func() error { return d.companion.Install(ctx, d.appPath) }); err != nil {
if err := d.reinstallApp(ctx); err != nil {
return fmt.Errorf("reinstall %s: %w", d.appPath, err)
}
return nil
@@ -319,6 +324,18 @@ func (d *Driver) clearAppState(ctx context.Context) error {
return d.resetContainer(ctx)
}
// simctlReinstall uninstalls and reinstalls the app bundle via simctl. App
// lifecycle stays with simctl: the companion's install RPC misreads current
// simulator targets' architectures and rejects valid bundles.
func (d *Driver) simctlReinstall(ctx context.Context) error {
_ = exec.CommandContext(ctx, "xcrun", "simctl", "uninstall", d.udid, d.bundleID).Run()
output, err := exec.CommandContext(ctx, "xcrun", "simctl", "install", d.udid, d.appPath).CombinedOutput()
if err != nil {
return fmt.Errorf("simctl install: %w: %s", err, strings.TrimSpace(string(output)))
}
return nil
}
// resetDataContainer deletes the contents of the app's data container so the
// next launch starts with empty storage.
func (d *Driver) resetDataContainer(ctx context.Context) error {
@@ -698,6 +715,14 @@ func (d *Driver) realSpawnChild(ctx context.Context, address string) (*exec.Cmd,
command := exec.CommandContext(ctx, binaryPath, "--udid", d.udid, "--grpc-port", port)
command.Stdout = d.output
command.Stderr = d.output
// The companion echoes its whole environment into the run log at startup,
// so it gets a minimal one: secrets in the parent environment must never
// reach run artifacts.
command.Env = []string{
"HOME=" + os.Getenv("HOME"),
"PATH=/usr/bin:/bin",
"TMPDIR=" + os.TempDir(),
}
command.Cancel = func() error { return command.Process.Signal(syscall.SIGTERM) }
command.WaitDelay = shutdownGrace
if err := command.Start(); err != nil {
+7 -4
View File
@@ -108,6 +108,7 @@ func newTestDriver(companion transport.Companion) *Driver {
screenHeight: 844,
}
d.resetContainer = func(context.Context) error { return nil }
d.reinstallApp = func(context.Context) error { return nil }
return d
}
@@ -140,14 +141,16 @@ func TestLaunchClearStateReinstallsWithAppPath(t *testing.T) {
companion := &fakeCompanion{accessibilityJSON: "[]"}
d := newTestDriver(companion)
d.appPath = "/tmp/Sample.app"
reinstalls := 0
d.reinstallApp = func(context.Context) error { reinstalls++; return nil }
if err := d.Launch(context.Background(), "", true, nil); err != nil {
t.Fatalf("Launch: %v", err)
}
if indexOf(companion.calls, "uninstall") < 0 || indexOf(companion.calls, "install") < 0 {
t.Fatalf("clear-state with app path must uninstall then install; got %v", companion.calls)
if reinstalls != 1 {
t.Fatalf("clear-state with app path must reinstall exactly once; got %d", reinstalls)
}
if indexOf(companion.calls, "uninstall") > indexOf(companion.calls, "install") {
t.Fatalf("uninstall must precede install; got %v", companion.calls)
if indexOf(companion.calls, "launch") < indexOf(companion.calls, "terminate") {
t.Fatalf("launch must still follow terminate; got %v", companion.calls)
}
}