Skip to content

fix: harden andro (wait timeout, AVD drift, profile mix-up, clean residue, reinstall target) - #2

Merged
maxgfr merged 8 commits into
mainfrom
fix/verify-and-harden
Sep 4, 2026
Merged

maxgfr merged 8 commits into
mainfrom
fix/verify-and-harden

Conversation

@maxgfr

@maxgfr maxgfr commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Full pass over the repo: the CI gates were already green on main, but real
runs turned up seven issues. Each is fixed in its own commit, with a pure parser
plus unit tests wherever the logic allows it.

What was broken

# Problem Fix
1 anyhow 1.0.102 is flagged unsound by RUSTSEC-2026-0190 (downcast_mut) cargo update (lockfile only); cargo audit now reports zero warnings
2 andro wait --timeout N never gave up, and andro up hung forever when the emulator crashed at startup — wait_ready began with adb wait-for-device, which blocks indefinitely with no device attached drop the call (the poll loop already handles it) and watch the spawned emulator process, failing fast with the tail of emulator.log
3 status / stop after clean recreated ~/.andro/{avd,tmp,home/.android}Sdk::command created them unconditionally only create them when the home already exists
4 Changing --api or --device did nothing once the AVD existed — ensure_avd only checked that the .ini was there. On my machine the andro AVD sat on android-34 while doctor announced android-36 compare the AVD's config.ini against the requested image and device, and recreate it on a mismatch (refusing while it is running)
5 With andro-tv up, andro run app.apk installed and launched on the TV emulator — boot() short-circuits on is_booted without checking which AVD is up ask adb emu avd name and bail with "run andro stop first"; status now reports the running AVD
6 Reinstalling an app the device already had launched an arbitrary package — run fell back to after.last() when nothing new appeared in pm list packages pick the third-party package with the freshest lastUpdateTime from dumpsys package packages
7 andro stop printed "emulator stopped" and exited 0 even when the emulator was still attached, and clean deleted ~/.andro right after the same unconfirmed kill — adb emu kill is fire-and-forget and is ignored when sent during early boot one helper polls until the device really detaches, asks a second time if not, and errors out instead of lying

Item 7 was found while verifying the rest, not in the original list.

Verification

CI gates locally, all green: cargo fmt --check, cargo clippy --all-targets -D warnings,
cargo test (51 unit + 12 CLI, 2 e2e ignored), cargo audit (0 warnings), cargo build --release.

End to end on a provisioned macOS SDK:

  • andro up printed recreating AVD 'andro' (android-36/google_apis/arm64-v8a on pixel_7 → … on pixel),
    booted, and config.ini matched the request afterwards. The same path also moved the
    pre-existing AVD from android-34 to android-36.
  • Requesting a different device while the AVD was running was refused:
    AVD 'andro' is … on pixel_7 but … on pixel was requested — run andro stop first.
  • andro up --tv with the phone AVD running was refused:
    emulator 'andro' is running but profile Tv (AVD 'andro-tv') was requested.
  • andro status --json reports "running_avd":"andro".
  • andro wait --timeout 3 with the real SDK and no device errors out in 3.03s; it used to block forever.
  • clean --yes on a throwaway --home, then status: exit 3 and the directory stays gone.
  • Stopping mid-boot reproduced the old silent failure: the first emu kill was ignored,
    the retry took, and the emulator was really gone (22s). A normal stop takes about 2s.
  • Item 6 has unit tests plus a check of the parser against 246 packages of real
    dumpsys package packages output from the booted android-36 image. The full
    install-twice loop was not run: no APK was available locally.

anyhow 1.0.102 is flagged unsound by RUSTSEC-2026-0190 (downcast_mut);
1.0.103 fixes it. Refresh the whole lockfile — Cargo.toml is untouched,
and cargo audit now reports zero warnings.
wait_ready started with `adb wait-for-device`, which blocks forever when
nothing is attached: `andro wait --timeout 3` never returned, and `andro up`
hung for good when the emulator crashed at startup. The existing poll loop is
enough — `adb shell getprop` fails immediately without a device — so the call
is gone.

boot() now keeps the spawned emulator handle and try_wait()s it on each turn:
a non-zero exit fails fast with the tail of ~/.andro/emulator.log instead of
polling a dead process for the full 300s.
`Sdk::command` created `<home>/{home/.android,tmp,avd}` on every call, so any
adb-backed command run after `andro clean` — `status`, `stop` — rebuilt the
tree that clean had just removed, contradicting "nothing left behind".

The dirs are now only created when the home already exists. A missing home has
no adb to run anyway, so those commands fail cleanly ("not running", exit 3),
and provisioning still creates the home in `ensure_jdk` before the first tool
call.
ensure_avd only checked that `<name>.ini` existed, so changing `--api` or
`--device` after the first run was silently ignored: an AVD created at
android-34 kept booting android-34 even though `andro doctor` announced
android-36 and the newer image was installed next to it.

The AVD's config.ini is now compared against the requested image and device
(`avd_matches`, a pure parser with unit tests). On a mismatch the AVD is
deleted and recreated — it is disposable by design, and the SDK image is not
re-downloaded. If it is currently running, andro refuses and asks for
`andro stop` first rather than pulling the rug from under a live emulator.
`boot` short-circuited on `is_booted`, which only asks whether *something* is
booted. With `andro-tv` up, a plain `andro run app.apk` installed and launched
the phone app on the TV emulator without a word.

`up`/`boot` now ask the console which AVD is attached (`adb emu avd name`,
parsed by the pure `parse_avd_name`) and bail with "run `andro stop` first"
when it belongs to the other profile. `andro status` reports the running AVD
too, in the text output and as `running_avd` in the JSON, so the mismatch is
visible before it bites.
Reinstalling an app the emulator already has adds nothing to `pm list
packages`, so `run` fell through to `after.last()` and launched an arbitrary
third-party package — the second `andro run` of the same APK could start a
completely different app.

It now reads `dumpsys package packages` and picks the third-party package with
the freshest `lastUpdateTime` (`most_recently_updated`, a pure parser with unit
tests). `after.last()` stays as the last resort.
"How it works" now says the AVD is recreated when --api/--device change, and
that run/up refuse to drive the other profile's running emulator.
@maxgfr
maxgfr force-pushed the fix/verify-and-harden branch from b5a04bf to fa5f4dc Compare September 4, 2026 20:24
`adb emu kill` is fire-and-forget and does not always take — a kill sent while
the emulator is still early in its boot is acknowledged and ignored.

`stop` discarded the result and printed "⏹ emulator stopped" regardless, so it
exited 0 with a live emulator still attached and the next `run` landed on it.
`clean` had the worse version of the same bug: it deleted `~/.andro` right
after the unconfirmed kill, which can leave a running emulator with no adb left
to stop it.

Both now go through one helper that polls until the device really detaches,
asks a second time if it did not, and errors out instead of lying. A normal
stop still returns in about two seconds.
@maxgfr
maxgfr force-pushed the fix/verify-and-harden branch from fa5f4dc to 1b780a8 Compare September 4, 2026 20:26
@maxgfr
maxgfr merged commit 1b780a8 into main Sep 4, 2026
5 checks passed
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 1.2.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@maxgfr
maxgfr deleted the fix/verify-and-harden branch September 4, 2026 20:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant