Lands the fake HTTP server, the patch fixture pipeline, and the first
two scenarios that exercise the real FFI path end-to-end. Plus a test
that would have caught the patch-to-release rollback bug
(shorebirdtech/shorebird#3728).
Architecture follows the principles surfaced in review:
- Dart tests call the public `ShorebirdUpdater` API only — never the
raw `Updater` FFI wrapper, never the engine API.
- Engine API stays inside the `library_test_hooks` Rust crate.
`shorebird_test_init` constructs `AppParameters` + stub
`FileCallbacks` internally so Dart never sees those types.
`shorebird_test_simulate_successful_launch` wraps the
start/success protocol so the Dart layer never knows there's a
protocol — it just knows "the engine reported a successful boot."
- ffigen scans only the test_hooks header. The engine header
(updater_engine.h) does not appear in the Dart bindings.
- A `TestEngine` Dart helper concentrates engine-side simulation in
one place; the test bodies stay focused on `ShorebirdUpdater`.
Implementation choices worth flagging:
- FFI calls run via `Isolate.run`. Synchronous run blocks the main
isolate, deadlocking against the in-isolate shelf server. The
test's IsolateRun callback re-opens the cdylib (cheap: dlopen is
ref-counted) and resets `Updater.bindings` in the sub-isolate
because Dart isolates do not share static fields.
- `libapp_path` must be a real file on the desktop integration build:
the non-Android non-iOS non-test `patch_base` reads it directly
from disk. Tests that install a patch write the fixture's `base`
bytes to `libapp.so` before init.
- Fake server kept minimal: shelf, no Range support, no auth, no
concurrency knobs. Stage 3+ scenarios (download cutoff, hash
mismatch loop, etc.) extend it as needed.
Three scenarios cover three reasons we wanted this suite:
1. `checkForUpdate returns upToDate when server has no patch` —
baseline: confirms the harness boots cleanly and returns the
expected enum.
2. `install a patch and boot from it` — golden path: check →
update → simulateSuccessfulLaunch, then assert
`readCurrentPatch` / `readNextPatch` / `checkForUpdate`
transitions match the public API contract.
3. `checkForUpdate returns restartRequired after patch-to-release
rollback` — regression for shorebirdtech/shorebird#3728. Pre-fix
this returned `upToDate` and left no signal to prompt a restart.
Verified locally: 232 Rust unit tests + 44 Dart tests (41 existing
unit + 3 new integration) green; clippy/fmt/cspell clean.
The C surface in `library/src/c_api` was a single bucket of `pub extern "C"`
functions covering both consumers — `package:shorebird_code_push` (via
ffigen) and Shorebird's Flutter engine fork (via direct C++ link). That
made it hard to reason about which symbols are stable ABI versus
internal, and ffigen was generating bindings for engine-only symbols
that no Dart code calls.
Split into two self-contained submodules and two cbindgen-generated
headers:
- `c_api::dart` → `include/updater_dart.h` (stable ABI; ffigen entry
point). Defines `UpdateResult`, the `SHOREBIRD_*` status constants, and
the five Dart-stable functions: `shorebird_current_boot_patch_number`,
`shorebird_next_boot_patch_number`,
`shorebird_check_for_downloadable_update`,
`shorebird_update_with_result`, `shorebird_free_update_result`.
- `c_api::engine` → `include/updater_engine.h` (no stability guarantee).
Defines `AppParameters`, `FileCallbacks`, and the engine-only functions:
`shorebird_init`, `shorebird_should_auto_update`,
`shorebird_validate_next_boot_patch`, `shorebird_next_boot_patch_path`,
`shorebird_free_string`, `shorebird_start_update_thread`, and the
`shorebird_report_launch_*` trio.
Each bucket file is self-contained: cbindgen scans only the file
(`with_src` in build.rs) and emits the items it defines plus the C
types they reference. There are no exclude/include lists in the
cbindgen configs — adding a function to one bucket automatically lands
it in the right header, and items in the other bucket cannot leak.
`mod.rs` shrinks to a thin layer of private helpers shared by both
buckets (`to_rust`, `allocate_c_string`, `free_c_string`, `log_on_error`)
plus the test module.
`include/updater.h` is removed; consumers include the specific header
for their use case. The Flutter engine's
`shell/common/shorebird/updater.cc` will be updated in a follow-up
engine-repo PR to include `updater_engine.h` directly.
Also drops two retired Dart-side symbols:
- `shorebird_update` (replaced by `shorebird_update_with_result` in the
Dart 2.0 rewrite, Nov 2024).
- `shorebird_check_for_update` (replaced by
`shorebird_check_for_downloadable_update` in the same rewrite).
The shorebird_code_push package's `_legacyFallback` was the only path
that still called `shorebird_update`. The package's `flutter: >=3.24.5`
constraint guarantees the engine has `shorebird_update_with_result`, so
the fallback was unreachable in practice. Removing it lets us drop the
ABI symbol.
Bumps shorebird_code_push to 2.0.7. Bindings regenerated via ffigen now
contain only the five Dart-stable symbols.
Follow-up engine PR will: include `updater_engine.h` instead of the
removed `updater.h`; clean up `android_exports.lst` (drop the ghost
`shorebird_active_path` and `shorebird_active_patch_number` exports,
drop `shorebird_check_for_update`).
* feat(shorebird_code_push): add updater bindings and dart support
* newlines
* docs
* Remove unused support for dart cli
* clarify Android-specific setup in readme