8072ed9ad7
* 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.
128 lines
3.5 KiB
YAML
128 lines
3.5 KiB
YAML
# cspell:words dorny codecov
|
|
name: ci
|
|
|
|
on:
|
|
pull_request:
|
|
push:
|
|
branches:
|
|
- main
|
|
|
|
jobs:
|
|
semantic_pull_request:
|
|
name: ✅ Semantic Pull Request
|
|
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/semantic_pull_request.yml@v1
|
|
|
|
cspell:
|
|
name: 🔤 Check Spelling
|
|
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/spell_check.yml@v1
|
|
with:
|
|
config: cspell.config.yaml
|
|
|
|
changes:
|
|
runs-on: ubuntu-latest
|
|
|
|
permissions:
|
|
pull-requests: read
|
|
|
|
outputs:
|
|
needs_flutter_build: ${{ steps.needs_flutter_build.outputs.changes }}
|
|
needs_rust_build: ${{ steps.needs_rust_build.outputs.changes }}
|
|
|
|
name: 👀 Detect Changes
|
|
|
|
steps:
|
|
- name: 📚 Git Checkout
|
|
uses: actions/checkout@v6
|
|
|
|
- uses: dorny/paths-filter@v4
|
|
name: Build Detection
|
|
id: needs_flutter_build
|
|
with:
|
|
filters: |
|
|
shorebird_code_push:
|
|
- ./.github/workflows/main.yaml
|
|
- ./.github/actions/flutter_package/action.yaml
|
|
- shorebird_code_push/**
|
|
# Integration tests under shorebird_code_push/test/integration
|
|
# build and load the library_test_hooks cdylib, which
|
|
# re-exports the production updater C API. A change to
|
|
# either crate can break the suite, so trigger the
|
|
# flutter package job on those paths too.
|
|
- library/**
|
|
- library_test_hooks/**
|
|
|
|
- uses: dorny/paths-filter@v4
|
|
name: Build Detection
|
|
id: needs_rust_build
|
|
with:
|
|
filters: |
|
|
library:
|
|
- ./.github/actions/rust_crate/action.yaml
|
|
- library/**
|
|
patch:
|
|
- ./.github/actions/rust_crate/action.yaml
|
|
- patch/**
|
|
library_test_hooks:
|
|
- ./.github/actions/rust_crate/action.yaml
|
|
- library/**
|
|
- library_test_hooks/**
|
|
|
|
build_rust_crates:
|
|
needs: changes
|
|
if: ${{ needs.changes.outputs.needs_rust_build != '[]' }}
|
|
|
|
strategy:
|
|
matrix:
|
|
crate: ${{ fromJSON(needs.changes.outputs.needs_rust_build) }}
|
|
os: [macos-latest, nscloud-windows-2022-amd64-8x16, ubuntu-latest]
|
|
|
|
runs-on: ${{ matrix.os }}
|
|
|
|
name: 🦀 Build ${{ matrix.crate }} (${{ matrix.os }})
|
|
|
|
steps:
|
|
- name: 📚 Git Checkout
|
|
uses: actions/checkout@v6
|
|
|
|
- name: 🦀 Build ${{ matrix.crate }}
|
|
uses: ./.github/actions/rust_crate
|
|
with:
|
|
codecov_token: ${{ secrets.CODECOV_TOKEN }}
|
|
working_directory: ${{ matrix.crate }}
|
|
|
|
build_flutter_packages:
|
|
needs: changes
|
|
if: ${{ needs.changes.outputs.needs_flutter_build != '[]' }}
|
|
|
|
strategy:
|
|
matrix:
|
|
package: ${{ fromJSON(needs.changes.outputs.needs_flutter_build) }}
|
|
|
|
runs-on: ubuntu-latest
|
|
|
|
name: 🎯 Build ${{ matrix.package }}
|
|
|
|
steps:
|
|
- name: 📚 Git Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
submodules: recursive
|
|
|
|
- name: 🐦 Build ${{ matrix.package }}
|
|
uses: ./.github/actions/flutter_package
|
|
with:
|
|
codecov_token: ${{ secrets.CODECOV_TOKEN }}
|
|
coverage_excludes: "**/*.g.dart"
|
|
working_directory: ${{ matrix.package }}
|
|
|
|
ci:
|
|
needs: [semantic_pull_request, build_flutter_packages, build_rust_crates]
|
|
if: ${{ always() }}
|
|
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: ⛔️ exit(1) on failure
|
|
if: ${{ contains(join(needs.*.result, ','), 'failure') }}
|
|
run: exit 1
|