96fd32796e
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.
75 lines
1.6 KiB
Dart
75 lines
1.6 KiB
Dart
import 'dart:typed_data';
|
|
|
|
/// A precomputed bidiff patch artifact along with its hash, mirroring
|
|
/// the `PatchFixture` constants in `library/src/c_api/mod.rs`. The
|
|
/// bytes were generated with:
|
|
///
|
|
/// cargo run --bin string_patch -- "<base>" "<new>"
|
|
///
|
|
/// They are self-contained zstd payloads — applying them against an
|
|
/// empty source produces the `new` content, so tests using the stub
|
|
/// `FileCallbacks` from `shorebird_test_init` (which read 0 bytes
|
|
/// from the source apk) still get the right inflated bytes.
|
|
class PatchFixture {
|
|
PatchFixture({
|
|
required this.number,
|
|
required this.base,
|
|
required this.newContent,
|
|
required this.hash,
|
|
required this.bytes,
|
|
});
|
|
|
|
final int number;
|
|
|
|
/// The libapp bytes the patch was generated against. The integration
|
|
/// harness writes these to `libapp_path` before init — on non-test
|
|
/// desktop builds, `patch_base` reads that file directly when
|
|
/// applying the patch.
|
|
final Uint8List base;
|
|
|
|
final String newContent;
|
|
final String hash;
|
|
final Uint8List bytes;
|
|
}
|
|
|
|
/// `string_patch "hello world" "hello tests"`.
|
|
final helloTestsPatch = PatchFixture(
|
|
number: 1,
|
|
base: Uint8List.fromList('hello world'.codeUnits),
|
|
newContent: 'hello tests',
|
|
hash: 'bb8f1d041a5cdc259055afe9617136799543e0a7a86f86db82f8c1fadbd8cc45',
|
|
bytes: Uint8List.fromList(const [
|
|
40,
|
|
181,
|
|
47,
|
|
253,
|
|
0,
|
|
128,
|
|
177,
|
|
0,
|
|
0,
|
|
223,
|
|
177,
|
|
0,
|
|
0,
|
|
0,
|
|
16,
|
|
0,
|
|
0,
|
|
6,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
5,
|
|
116,
|
|
101,
|
|
115,
|
|
116,
|
|
115,
|
|
0,
|
|
]),
|
|
);
|