mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
fix(ios): a failed devicectl uninstall must fail the reinstall
same hole as the simulator path: devicectl install over an app keeps its data, and the discarded uninstall error hid it. Uninstalling a bundle id that is not installed exits 0 with 'App uninstalled.' on a paired iPhone, so a failure here is always real.
This commit is contained in:
1 parent
abc5e7db3e
commit
6234bf5ba0
2 files changed
+47
-1
No files matched your search
@@ -224,8 +224,14 @@ func (d *Driver) respawnDevice(ctx context.Context) error {
|
|||||||
// devicectlReinstall uninstalls then installs the app bundle via devicectl,
|
// devicectlReinstall uninstalls then installs the app bundle via devicectl,
|
||||||
// keyed on the CoreDevice id. App lifecycle stays with devicectl: the runner's
|
// keyed on the CoreDevice id. App lifecycle stays with devicectl: the runner's
|
||||||
// own install path is simulator-specific.
|
// own install path is simulator-specific.
|
||||||
|
// A failed uninstall ends the reinstall: installing over an app keeps its data,
|
||||||
|
// so clear-state would be reported without happening. Uninstalling an app that
|
||||||
|
// is not installed exits 0 ("App uninstalled." on a paired iPhone running iOS
|
||||||
|
// 26.5), so there is no benign failure here to sort out from a real one.
|
||||||
func (d *Driver) devicectlReinstall(ctx context.Context) error {
|
func (d *Driver) devicectlReinstall(ctx context.Context) error {
|
||||||
_ = exec.CommandContext(ctx, "xcrun", "devicectl", "device", "uninstall", "app", "--device", d.coreDeviceID, d.bundleID).Run()
|
if output, err := exec.CommandContext(ctx, "xcrun", "devicectl", "device", "uninstall", "app", "--device", d.coreDeviceID, d.bundleID).CombinedOutput(); err != nil {
|
||||||
|
return fmt.Errorf("devicectl uninstall %s: %w: %s", d.bundleID, err, strings.TrimSpace(string(output)))
|
||||||
|
}
|
||||||
output, err := exec.CommandContext(ctx, "xcrun", "devicectl", "device", "install", "app", "--device", d.coreDeviceID, d.appPath).CombinedOutput()
|
output, err := exec.CommandContext(ctx, "xcrun", "devicectl", "device", "install", "app", "--device", d.coreDeviceID, d.appPath).CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("devicectl install: %w: %s", err, strings.TrimSpace(string(output)))
|
return fmt.Errorf("devicectl install: %w: %s", err, strings.TrimSpace(string(output)))
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/priyanshujain/sanderling/internal/driver/ioscompanion/transport"
|
"github.com/priyanshujain/sanderling/internal/driver/ioscompanion/transport"
|
||||||
@@ -182,6 +183,45 @@ func TestNewDeviceReinstallsOnceBeforeTheRunnerSession(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDevicectlReinstallStopsWhenTheUninstallFails(t *testing.T) {
|
||||||
|
log := scriptedXcrun(t, `"devicectl device uninstall "*) echo "ERROR: Internal logic error: Connection was invalidated"; exit 1;;
|
||||||
|
"devicectl device install "*) :;;`)
|
||||||
|
d := &Driver{coreDeviceID: "CORE-DEVICE", bundleID: "app.example", appPath: "/tmp/Sample.app"}
|
||||||
|
|
||||||
|
err := d.devicectlReinstall(context.Background())
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("devicectlReinstall reported success while app.example kept the data clear-state was asked to remove")
|
||||||
|
}
|
||||||
|
for _, want := range []string{"app.example", "Connection was invalidated"} {
|
||||||
|
if !strings.Contains(err.Error(), want) {
|
||||||
|
t.Errorf("error %q does not quote %q", err, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
calls := xcrunCalls(t, log)
|
||||||
|
if slices.ContainsFunc(calls, func(call string) bool { return strings.HasPrefix(call, "devicectl device install") }) {
|
||||||
|
t.Errorf("xcrun calls = %v: installing over the app carries its data into the run", calls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDevicectlReinstallProceedsWhenNothingIsInstalled(t *testing.T) {
|
||||||
|
log := scriptedXcrun(t, `"devicectl device uninstall "*) echo "App uninstalled.";;
|
||||||
|
"devicectl device install "*) :;;`)
|
||||||
|
d := &Driver{coreDeviceID: "CORE-DEVICE", bundleID: "app.example", appPath: "/tmp/Sample.app"}
|
||||||
|
|
||||||
|
if err := d.devicectlReinstall(context.Background()); err != nil {
|
||||||
|
t.Fatalf("devicectlReinstall: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := []string{
|
||||||
|
"devicectl device uninstall app --device CORE-DEVICE app.example",
|
||||||
|
"devicectl device install app --device CORE-DEVICE /tmp/Sample.app",
|
||||||
|
}
|
||||||
|
if got := xcrunCalls(t, log); !slices.Equal(got, want) {
|
||||||
|
t.Fatalf("xcrun calls = %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDeviceClearStateWithoutAppPathWarnsOnce(t *testing.T) {
|
func TestDeviceClearStateWithoutAppPathWarnsOnce(t *testing.T) {
|
||||||
output := &bytes.Buffer{}
|
output := &bytes.Buffer{}
|
||||||
d := &Driver{output: output, deviceMode: true}
|
d := &Driver{output: output, deviceMode: true}
|
||||||
|
|||||||
Reference in new issue
Block a user