ReviewOS

also looking at this

craft-native/craft

fix(mobile-e2e): launch iOS apps without a pty, so a cold simulator cannot eat the budget

#212
Merged glennmichael123 wants to merge fix/mobile-e2e-ios-launch into main
5 files +74 -23
9 days ago

The mobile E2E workflow has been failing intermittently on main. This fixes two of the three causes; the third is filed as #211.

The shim leg that never started its app

iOS simulator failed on main (run 35091847509), on #208 and on #210 in the same way:

FAIL ios-shim — 0/0 cases, no zig
       the app produced no terminating event within 180000ms
       the test page never announced a plan; it did not reach the native bridge
ok   ios-runtime — 5/5 cases, ...

The console was 0 bytes and the screenshot showed the home screen with the app installed but not running. Across the five earlier CI runs whose evidence is still available, the simulator was always Shutdown when the shim leg picked it. The shim leg is therefore always the first launch after a cold boot, and the runtime leg, second on the same device, never failed.

Reproduced on a simulator created for the test (simctl create, used, then deleted):

step on a fresh deviceseconds after boot started
bootstatus -b returns41–42
first system app launch (com.apple.Preferences) returns64
plain simctl launch of the probe returns83 (3s after install)
simctl launch --console-pty of the same app, first page event287

So the slowness comes from --console-pty itself, not from the device or the app. The harness started its 180-second clock at that spawn.

The fix

Both iOS harnesses, scripts/mobile-e2e/ios.ts and packages/ios/fixtures/zig-slice/build-and-run.sh, now launch with:

SIMCTL_CHILD_NSUnbufferedIO=YES xcrun simctl launch --terminate-running-process \
    --stdout=<app-stdout.log> --stderr=<app-stderr.log> <udid> <bundle>

and read the two files back as the app runs.

NSUnbufferedIO=YES is not optional. Measured on the same fresh device:

  • without it: Swift's print is fully buffered, so the file received nothing, not even after simctl terminate;
  • with it: the first event arrived 2 seconds after launch.

A pty is line-buffered, which is the only reason --console-pty ever worked. Xcode sets the same variable when it runs an app.

A plain simctl launch also returns once the app has a pid, so a launch that cannot happen now surfaces as simctl's own error. Before, it looked like a timeout blamed on the page.

The slice assertion that disagreed with its own wait loop

iOS Zig runtime slice failed on main (run 35094828558) with:

FAIL: getCurrentPosition did not return the simulated coordinate

That was after the wait loop had seen i=76 arrive, and the job log shows the line. The wait loop matched i=76($|[^0-9]) against the raw log. count matched i=76$ against a copy with colours and carriage returns stripped, which is the stricter boundary. count now uses the wait loop's boundary, and without a pty there is no CRLF left for the two to disagree about.

Not fixed here: #211

The other slice failure on #208 and #210 was timed out after 900s waiting for: i=38. removeSiriShortcut answers from NSUserActivity's deletion completion, which on a cold simulator sometimes never fires, and nothing else settles the call. That is a product bug, a promise with no way to settle, so it is filed rather than papered over in the fixture.

Verification

  • bun run test:mobile-e2e:ios from a shut-down simulator: both legs pass (5/5), 76 seconds in total.
  • The slice fixture locally: all assertions pass, 28 seconds.
  • bun run test, bun run typecheck, bun run verify:packages, Pickier and the 37 protocol tests.

5 changed files on the files tab, with 0 review threads.

Sign in to comment on this pull request.