Skip to content

[agent-cli] E2E-cover the device section, the cloud-session next, and the tunnel lines of status - #77

Merged
Kudo merged 3 commits into
kudo/status-4-e2e-sweepfrom
kudo/status-5-device-e2e
Oct 19, 2026
Merged

Kudo merged 3 commits into
kudo/status-4-e2e-sweepfrom
kudo/status-5-device-e2e

Conversation

@expo-tuft

@expo-tuft expo-tuft Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Stack 5/5 of the status plan. Based on #72. Tests only.

Why

The last status branches without an e2e were the ones that depend on what the runner has: xcrun simctl and adb. #72 listed them as left out; this closes them.

How

  • A stub xcrun in .stub-bin (first on the PATH the run gets) answers simctl list devices booted -j with a booted iPhone or with none. A stub adb under a per-test ANDROID_HOME/platform-tools answers devices -l with an emulator or with none — the resolver reads ANDROID_HOME before PATH (src/device/adb.ts).
  • "Cannot be run" (unknown): the execute bit withheld and a PATH of the stub directory alone, because a PATH search skips a file it cannot run and finds the machine's own xcrun next. adb is spawned by path, so the bit alone suffices there.
  • Cases: attached Android device (text android emulator-5554); booted iOS simulator by name with both devices listed (F106; macOS only, the probe does not run elsewhere); absent with the tools' reasons; unknown, never rounded down to none (POSIX only — a .cmd shim has no execute bit to withhold); a dev server up with nothing attached and no device → an exp:// address or navigate / --print-url, never navigate /; an EAS Simulator session on record → smoke --ios --eas with an app connected, navigate / --eas without, the session still named when the tools could not answer, and the local path kept when a device is here (K7a); a tunnelled dev server (lock + detached log newer than it) → hostType: tunnel, tunnelUrl, openUrls on the tunnel host, the tunnel fact and open in …: exp://… lines (K7c, K8); a stale tunnel dropped once its dev server is gone.
  • Left out on purpose: the 8082–8085 port-scan rung (fixed ports collide on shared runners).

Test Plan

  • bun run typecheck, bun run lint, bun run test:e2e -- status-test (130 pass locally on macOS; 2 cases skip on Windows, 1 on Linux by design).

@Kudo
Kudo added this pull request to stack #78 September 16, 2026 06:07
@expo-tuft
expo-tuft Bot force-pushed the kudo/status-5-device-e2e branch from 0b602cc to df13e47 Compare September 16, 2026 19:25
@expo-tuft
expo-tuft Bot force-pushed the kudo/status-5-device-e2e branch from df13e47 to f2b2dfc Compare September 16, 2026 19:39
@expo-tuft
expo-tuft Bot force-pushed the kudo/status-5-device-e2e branch from f2b2dfc to d522401 Compare September 24, 2026 14:29
@expo-tuft
expo-tuft Bot force-pushed the kudo/status-5-device-e2e branch from d522401 to 407d49c Compare October 15, 2026 12:42
@expo-tuft
expo-tuft Bot force-pushed the kudo/status-5-device-e2e branch 2 times, most recently from d102fa4 to a55ad61 Compare October 15, 2026 19:01
@expo-tuft
expo-tuft Bot force-pushed the kudo/status-5-device-e2e branch from a55ad61 to 05997b9 Compare October 15, 2026 19:34
@Kudo
Kudo requested a review from vonovak October 15, 2026 20:20
@expo-tuft
expo-tuft Bot force-pushed the kudo/status-5-device-e2e branch from 05997b9 to bff4f33 Compare October 16, 2026 10:29
@Kudo
Kudo force-pushed the kudo/status-5-device-e2e branch from bff4f33 to 8d1d3b3 Compare October 18, 2026 18:11
@Kudo
Kudo force-pushed the kudo/status-5-device-e2e branch from 8d1d3b3 to d038eb5 Compare October 19, 2026 09:05
@Kudo
Kudo force-pushed the kudo/status-5-device-e2e branch from d038eb5 to 69e6de3 Compare October 19, 2026 10:53
const report = await reportInAsync(projectRoot, [], env);

expect(report.device).toMatchObject({ state: 'unknown', deviceId: null, devices: [] });
expect(report.device!.reason).toContain('could not be run');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Android alone produces the could not be run text, so this assertion does not check iOS. The combined state is unknown when either platform's check is unknown. On macOS, if the iOS setup in stubDeviceToolsAsync (the xcrun chmod and the PATH override) stopped working, this case would still pass. On darwin, also assert the iOS part of the reason, so a broken iOS check fails this case.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 13732a7. The test now asserts the Android part by name (Android: "adb" could not be run) on every host. On darwin it also asserts iOS: Could not run "xcrun simctl". I checked it: with the PATH override removed, the state stays unknown, but the reason reads "the bound simulator is gone", and the new assertion fails.

expo-tuft Bot and others added 2 commits October 19, 2026 19:42
… the tunnel lines of status

The last `status` cases without an e2e were the ones that depend on what the
runner has: `xcrun simctl` and `adb`. Both tools are under the test's control
now — a stub `xcrun` first on PATH, a stub `adb` under an `ANDROID_HOME` of the
test's own, which the resolver reads before PATH — and "cannot be run" is a
PATH of the stub directory alone with the execute bit withheld, since a PATH
search skips a file it cannot run and finds the machine's own tool next.

Covered: an attached Android device, a booted iOS simulator by name with every
device listed (F106, macOS only), `absent` with the tools' own reasons,
`unknown` never rounded down to none (POSIX only), the address handed over in
place of `navigate /` when no device is here, the EAS Simulator session's
`smoke --ios --eas` / `navigate / --eas` rungs (K7a) including a probe that
could not answer, the local path kept when a device is here, and a tunnelled
dev server's `tunnel` fact and `open in` launcher URLs (K7c, K8) — plus a
stale tunnel dropped once its dev server is gone.

Out on purpose: the 8082–8085 port-scan rung, which needs fixed ports that
collide on shared runners.

Co-authored-by: kudo <46429+kudo@users.noreply.github.com>
Co-authored-by: kudo <46429+kudo@users.noreply.github.com>
@Kudo
Kudo force-pushed the kudo/status-5-device-e2e branch from 69e6de3 to 58dc96e Compare October 19, 2026 11:46
@Kudo
Kudo requested a review from vonovak October 19, 2026 14:13
@Kudo
Kudo merged commit 22b9591 into main Oct 19, 2026
15 checks passed
@Kudo
Kudo deleted the kudo/status-5-device-e2e branch October 19, 2026 14:47
Kudo added a commit that referenced this pull request Oct 19, 2026
…he runner spec (#159)

Stacked on #77 (the #69 status stack).

## Why

Every EAS-backed command spawned `npx --yes eas-cli@latest` (or `bunx
eas-cli@latest`) unless the project pinned `eas-cli`. The `@latest`
lookup is slow.

## What

`src/utils/easCli.ts` now resolves the EAS CLI in three rungs:

```
project declares eas-cli                    ->  <runner> eas-cli   (pin wins, unchanged)
first `eas` on PATH is eas-cli >= 25.0.0    ->  eas                (spawned directly)
otherwise                                   ->  <runner> eas-cli   (no @latest)
```

- **Global rung (`findGlobalEasCli`)**: follows the first `eas` on
`PATH` through its symlink to the nearest `package.json`. The rung is
used only when the package name is `eas-cli` and the version is `>=
25.0.0`. Sync, no spawn, never throws. A shell wrapper, a Volta or pnpm
shim, or a Windows `.cmd` does not resolve to the package, so the runner
answers (the impostor guard from wave 18 stays).
- **No `@latest`**: the spec is always the bare `eas-cli`.
- `EasCli` gets `global: boolean`. `mayDownloadEasCli` is false for a
global install, so the auth preflight and the `status` budget treat it
like a pinned CLI.
- Printed command lines follow the spawn (`easCommandPrefix`): `eas …`
with a recent global install, else `npx --yes eas-cli …` / `bunx eas-cli
…`.
- Auth passthrough has a new `global-eas` source.
- LLP 0015 §Resolving the EAS CLI, the CHANGELOG, and the README are
updated.

## Measurements (this machine, 2026-10-16)

### `status`, before vs after

Before is `bff4f33` (#77, the base). After is `4b5b6f1`. Each case had 1
warm-up and 5 timed runs. Before each run,
`.expo/agent-cli-eas-builds.json` was deleted, so every run did the real
EAS build lookup. The app was a copy of a bun Expo app (SDK 57) linked
to an EAS project with builds. The npm case is the same copy without
`bun.lock`. `PATH` held only Homebrew and system dirs, with no global
`eas` unless stated. Every run returned `ios: none · android: none`.

| Project | Before (median, range) | After (median, range) |
|---|---|---|
| bun (`bunx`) | **10.98 s** (10.10–16.46) | **3.52 s** (2.88–3.74) |
| npm (`npx`) | **3.99 s** (3.88–6.54) | **4.38 s** (4.22–5.29) |
| bun + global eas-cli 25.0.0 | 10.98 s (base does not use a global
install) | **2.78 s** (2.24–3.03) |
| npm + global eas-cli 25.0.0 | 3.99 s (base does not use a global
install) | **3.38 s** (2.48–4.40) |

- bun: about 3× faster. `bunx eas-cli@latest` reinstalled on every run,
and `bunx eas-cli` uses its cache.
- npm: dropping `@latest` gives no gain. The difference is inside the
run-to-run spread, because npx revalidates the packument for either
spec.
- A global `eas` is the gain for npm projects (about 0.6 s at the
median), and it takes bun projects from 3.5 s to 2.8 s.

### Runner probes

- `bunx eas-cli@latest --version`: registry GET + reinstall every run,
14.7–31.5 s. `bunx eas-cli --version`: no request, 2.6–5.8 s.
- npx (11.17.0) revalidates the packument for both specs (~110 ms).
- `agent-cli whoami`, warm: global `eas` 0.73–0.76 s vs `npx --yes
eas-cli` 1.36–1.66 s.
- bunx uses one scratch dir, `bunx-<uid>-eas-cli@latest`, for both the
bare spec and `@latest`. On this machine that dir was broken (`Cannot
find module 'tslib'`), and every bunx lookup failed until it was
deleted. This also happens without this PR.

## Tests

- Unit: `easCli-test` covers the global rung: >= 25.0.0, older,
non-`eas-cli` package, first-on-PATH only, pin beats global,
no-download, no runner. Existing expectations moved from
`eas-cli@latest` to `eas-cli`.
- E2E: `eas-runner-test` plants a real npm-style global layout
(symlinked bin) at 25.0.0 and 24.9.0. `installStubEasRunnerAsync` now
also plants a decoy `eas`, so a developer's own global `eas-cli` cannot
answer in e2e. The stub runner now rejects `eas-cli@latest`.
- `bun run typecheck`, `bun run format:check`, unit (5377 passed), e2e
(910 passed) all green locally.
- Live: with `npm i -g --prefix /tmp/geas eas-cli` (25.0.0) on PATH,
`whoami` spawned `/tmp/geas/bin/eas`. Without it, `whoami` fell back to
`npx --yes eas-cli`.

Not run: `test:live*` suites. Those still call `npx eas-cli@latest`
directly for their own setup, as does
`.eas/workflows/agent-cli-cloud-e2e.yml`. This PR does not change them.

---------

Co-authored-by: expo-tuft[bot] <288127324+expo-tuft[bot]@users.noreply.github.com>
Co-authored-by: kudo <46429+kudo@users.noreply.github.com>
Co-authored-by: Kudo Chien <kudo@expo.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants