Files
shorebird-updater/library/Cargo.toml
T
Eric Seidel 8072ed9ad7 feat: stage 1 integration test harness (#353)
* docs: integration tests design proposal

Adds docs/integration_tests.md proposing a desktop-only integration suite
that drives the Dart `ShorebirdUpdater` API against the real Rust core via
FFI, with a fake HTTP server and per-test tempdir state. Describes a new
`library_test_hooks` cdylib that reaches into `updater` via a `test-hooks`
Cargo feature, so production C API stays clean. Status: design exploration,
not a committed plan.

* feat: stage 1 integration test harness for shorebird_code_push

Lands the test_hooks crate, the Dart-side loader, and one trivial
end-to-end test, per docs/integration_tests.md stage 1.

Pieces:

- New `library_test_hooks` workspace crate (cdylib). Depends on `updater`
  with a new `test-hooks` Cargo feature that widens the visibility of
  internal items (currently `testing_reset_config`) for sibling-crate
  access only — production builds do not enable it. The crate also
  re-exports `updater::c_api::dart::*` and `updater::c_api::engine::*`,
  which keeps the rlib's `#[no_mangle]` symbols out of DCE so the
  resulting cdylib carries both the production C API and the new
  `shorebird_test_*` hooks.
- Workspace `default-members` excludes `library_test_hooks` so plain
  `cargo build` / `cargo test` invocations don't unify the `test-hooks`
  feature into production builds.
- `shorebird_code_push/test/integration/all_test.dart` (single file,
  with a header comment explaining why) loads the cdylib via
  `DynamicLibrary.open`, reassigns the existing `@visibleForTesting`
  `Updater.bindings` setter, and exercises both surfaces. The build
  helper shells out to `cargo build -p library_test_hooks`; if it
  fails, `markTestSkipped` keeps the suite green on machines without
  a working Rust toolchain.
- ffigen config (`ffigen_test_hooks.yaml`) generates the test-only
  Dart bindings under `test/integration/generated/`, not `lib/`.
- CI: `library_test_hooks` is added to the rust_crate matrix, and the
  shorebird_code_push job triggers on `library/**` and
  `library_test_hooks/**` so cdylib changes can't break the Dart-side
  integration suite without CI noticing.

Verified locally: 232 Rust unit tests + 42 Dart tests (41 existing + 1
integration) green; clippy/fmt/cspell clean; production cdylib does
not contain `shorebird_test_reset` (`nm` confirms feature isolation).

Stages 2 (FakePatchServer + golden path) and 3 (adversarial scenarios)
land separately.

* fix(integration test): early-return on skip and bump per-test timeout

Two issues caught by Shorebird CI on PR #353:

1. `markTestSkipped` does not abort test execution — it only flags the
   test as skipped on its way out. The body kept running and crashed on
   the `late testHooks` field when the cdylib build had failed in
   setUpAll. Move the skip check into the test body itself with an
   early return; drop the (no-op) skip in `setUp`.

2. Default per-test timeout (30s) also covers `setUpAll`. A cold
   `cargo build -p library_test_hooks` compiles `updater` and ~100
   transitive deps, which can run minutes on CI. Bump to 10 minutes
   via `@Timeout` on the library.

Verified locally: passes when cargo is on PATH (1 passed), reports a
clean skip and exit 0 when cargo is removed from PATH (1 skipped).

* refactor(integration test): drop ! by promoting testHooks to late final

`testHooks` was nullable so accessing it after the skipReason check
required `!`, and `markTestSkipped(skipReason!)` had the same smell.
Make `testHooks` `late final` (non-nullable, throws if read before
setUpAll assigns) and pull `skipReason` into a non-null local before
use. Same control flow, no bang operators.
2026-05-05 16:32:57 -07:00

83 lines
2.9 KiB
TOML

[package]
name = "updater"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
# "lib" is used by the "cli" target for testing from Rust
# "cdylib" is used by the "dart_cli" target for testing from Dart
# "staticlib" is used by the engine build for linking into libflutter.so
crate-type = ["lib", "cdylib", "staticlib"]
[features]
# Exposes a small set of internal Rust items (e.g. `testing_reset_config`)
# to sibling crates that need to drive the updater from a test harness.
# Production builds (the cdylib/staticlib that ships in the engine) do not
# enable this feature; only `library_test_hooks` does.
test-hooks = []
[dependencies]
# Used for error handling for now.
anyhow = "1.0.69"
base64 = "0.22.0"
# For inflating compressed patch files.
bipatch = "1.0.0"
# Used for exposing C API
# comde is a wrapper around several compression libraries.
# We only use zstd and could depend on it directly instead.
comde = { version = "0.3.1", default-features = false, features = [
"zstandard",
] }
dyn-clone = "1.0.16"
# For decoding the hex-encoded hashes in Patch network responses.
hex = "0.4.3"
libc = "0.2.98"
# For error!(), info!(), etc macros. `print` will not show up on Android.
log = "0.4.14"
# For implementing thread-local-storage of ResolvedConfig object.
once_cell = "1.17.1"
# Json serialization/de-serialization.
# Pipe is a simple in-memory pipe implementation, there might be a std way too?
pipe = "0.4.0"
# Used for networking. ureq is a synchronous-only HTTP client that avoids
# pulling in tokio/hyper/async machinery, significantly reducing binary size.
ureq = { version = "3", features = ["json", "rustls"] }
ring = "0.17.8"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.93"
# For computing hashes of patch files for validation.
sha2 = "0.11.0"
uuid = { version = "1.18.1", features = ["v4"] }
# For decompressing .apk files.
zip = { version = "8.5.1", default-features = false, features = ["deflate"] }
[target.'cfg(target_os = "android")'.dependencies]
# For logging to Android logcat.
android_logger = "0.15.0"
# Send panics to log (instead of stderr), thus logcat on Android.
log-panics = "2"
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
# Send panics to syslog (instead of stderr).
log-panics = "2"
# Support for logging on iOS (https://developer.apple.com/documentation/oslog)
oslog = "0.2.0"
[target.'cfg(any(target_os = "linux", target_os = "windows"))'.dependencies]
simple_logger = "5.0.0"
[dev-dependencies]
mockall = "0.14.0"
mockito = "1.2.0"
mock_instant = "0.6.0"
# Gives #[serial] attribute for locking all of our shorebird_init
# tests to a single thread so they don't conflict with each other.
serial_test = "3.2.0"
tempfile = "3"
# <https://github.com/eqrion/cbindgen/blob/master/docs.md#buildrs>
[build-dependencies]
cbindgen = "0.29.2"