Compare commits

...

5 Commits

Author SHA1 Message Date
Tony 479de106d7 Merge remote-tracking branch 'upstream/main'
ci / ✅ Semantic Pull Request (push) Has been cancelled
ci / 🔤 Check Spelling (push) Has been cancelled
ci / 👀 Detect Changes (push) Has been cancelled
Shorebird CI / changes (push) Has been cancelled
Shorebird CI / CSpell (push) Has been cancelled
ci / 🦀 Build ${{ matrix.crate }} (${{ matrix.os }}) (push) Has been cancelled
ci / 🎯 Build ${{ matrix.package }} (push) Has been cancelled
ci / ci (push) Has been cancelled
Shorebird CI / shorebird_code_push (push) Has been cancelled
Shorebird CI / shorebird_code_push_example (push) Has been cancelled
Shorebird CI / required (push) Has been cancelled
2026-08-03 01:57:04 +08:00
dependabot[bot] 1f85c4ab1e chore(deps): update mockall requirement (#365)
Updates the requirements on [mockall](https://github.com/asomers/mockall) to permit the latest version.

Updates `mockall` to 0.15.0
- [Changelog](https://github.com/asomers/mockall/blob/master/CHANGELOG.md)
- [Commits](https://github.com/asomers/mockall/compare/v0.14.0...v0.15.0)

---
updated-dependencies:
- dependency-name: mockall
  dependency-version: 0.15.0
  dependency-type: direct:production
  dependency-group: library-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-02 18:30:09 -07:00
Mac dd213f923c fix: populate current_patch_number from running_patch, not the boot breadcrumb (#363)
* fix: send running_patch as current_patch_number on patch check

Both patch-check construction sites read current_patch_number from
state.currently_booting_patch(), a transient boot-handshake breadcrumb
that record_boot_success() clears to None before any Dart-initiated
patch check fires. The field was therefore omitted from the wire for
essentially the entire fleet.

Use state.running_patch() instead: the session-scoped accessor set at
report_launch_start, kept for the whole session, surviving server-driven
rollbacks. This is the same value the FFI symbol
shorebird_current_boot_patch_number already exposes.

* test: assert patch-check request carries running_patch as current_patch_number

Covers both construction sites (check_for_downloadable_update and update).
Reproduces the steady state where the bug manifested — patch booted,
currently_booting_patch cleared by record_boot_success — and asserts the
request still carries the running patch number. Verified to fail (field
omitted, captured as -1) against the pre-fix currently_booting_patch() code.

* test: cover all changed lines in patch-check regression test

Codecov's patch gate flagged 13 uncovered lines in the new test (78.7%
patch coverage), none of them the fix: the no-op download/report
closures never fired, and multi-line assert failure messages are only
reached when an assertion panics.

Swap the closures for the existing UNEXPECTED_DOWNLOAD/UNEXPECTED_REPORT
placeholders (also asserting that path is never hit), collapse the
assertions to single lines with the sentinel legend moved to comments,
and tighten the module doc. Changed lines now at 100% patch coverage;
fmt, clippy, and the full suite stay green.
2026-06-29 18:27:15 -06:00
Tony 6e9aab2ce7 Point updater docs at GitHub Flutter fork
ci / ✅ Semantic Pull Request (push) Has been cancelled
ci / 🔤 Check Spelling (push) Has been cancelled
ci / 👀 Detect Changes (push) Has been cancelled
Shorebird CI / changes (push) Has been cancelled
Shorebird CI / CSpell (push) Has been cancelled
ci / 🦀 Build ${{ matrix.crate }} (${{ matrix.os }}) (push) Has been cancelled
ci / 🎯 Build ${{ matrix.package }} (push) Has been cancelled
ci / ci (push) Has been cancelled
Shorebird CI / shorebird_code_push (push) Has been cancelled
Shorebird CI / shorebird_code_push_example (push) Has been cancelled
Shorebird CI / required (push) Has been cancelled
2026-06-26 01:40:25 +08:00
dependabot[bot] 83df31b60e chore(deps): bump the gh-deps group across 5 directories with 2 updates (#362)
Bumps the gh-deps group with 2 updates in the / directory: [actions/checkout](https://github.com/actions/checkout) and [codecov/codecov-action](https://github.com/codecov/codecov-action).
Bumps the gh-deps group with 1 update in the /.github/actions/dart_package directory: [codecov/codecov-action](https://github.com/codecov/codecov-action).
Bumps the gh-deps group with 1 update in the /.github/actions/flutter_package directory: [codecov/codecov-action](https://github.com/codecov/codecov-action).
Bumps the gh-deps group with 1 update in the /.github/actions/publish_flutter_package directory: [actions/checkout](https://github.com/actions/checkout).
Bumps the gh-deps group with 1 update in the /.github/actions/rust_crate directory: [codecov/codecov-action](https://github.com/codecov/codecov-action).


Updates `actions/checkout` from 6 to 7
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

Updates `codecov/codecov-action` from 6 to 7
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v6...v7)

Updates `codecov/codecov-action` from 6 to 7
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v6...v7)

Updates `codecov/codecov-action` from 6 to 7
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v6...v7)

Updates `actions/checkout` from 6 to 7
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

Updates `codecov/codecov-action` from 6 to 7
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: gh-deps
- dependency-name: codecov/codecov-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: gh-deps
- dependency-name: codecov/codecov-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: gh-deps
- dependency-name: codecov/codecov-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: gh-deps
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: gh-deps
- dependency-name: codecov/codecov-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: gh-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-23 15:45:05 -07:00
13 changed files with 125 additions and 18 deletions
+1 -1
View File
@@ -76,7 +76,7 @@ runs:
run: echo "package_name=${PACKAGE_PATH##*/}" >> $GITHUB_OUTPUT
- name: Upload Coverage
uses: codecov/codecov-action@v6
uses: codecov/codecov-action@v7
with:
flags: ${{ steps.split.outputs.package_name }}
token: ${{ inputs.codecov_token }}
+1 -1
View File
@@ -82,7 +82,7 @@ runs:
run: echo "package_name=${PACKAGE_PATH##*/}" >> $GITHUB_OUTPUT
- name: Upload Coverage
uses: codecov/codecov-action@v6
uses: codecov/codecov-action@v7
with:
# We use Codecov's carryforward flags to allow our PR testing to only
# run affected packages, but also allow our coverage information from
@@ -10,7 +10,7 @@ runs:
using: "composite"
steps:
- name: 📚 Git Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: 🐦 Setup Flutter
uses: subosito/flutter-action@v2
+1 -1
View File
@@ -41,7 +41,7 @@ runs:
run: echo "package_name=${PACKAGE_PATH##*/}" >> $GITHUB_OUTPUT
- name: Upload Coverage
uses: codecov/codecov-action@v6
uses: codecov/codecov-action@v7
with:
# We use Codecov's carryforward flags to allow our PR testing to only
# run affected packages, but also allow our coverage information from
+2 -2
View File
@@ -34,7 +34,7 @@ jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
submodules: recursive
- name: Setup Flutter
@@ -64,7 +64,7 @@ jobs:
working-directory: ${{ inputs.package_path }}
run: flutter test --coverage
- if: inputs.has_unit_tests
uses: codecov/codecov-action@v6
uses: codecov/codecov-action@v7
with:
flags: ${{ inputs.package_name }}
working-directory: ${{ inputs.package_path }}
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
name: 🦀 Upload Artifacts
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: taiki-e/upload-rust-binary-action@v1
with:
bin: "patch"
+3 -3
View File
@@ -32,7 +32,7 @@ jobs:
steps:
- name: 📚 Git Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
- uses: dorny/paths-filter@v4
name: Build Detection
@@ -82,7 +82,7 @@ jobs:
steps:
- name: 📚 Git Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: 🦀 Build ${{ matrix.crate }}
uses: ./.github/actions/rust_crate
@@ -104,7 +104,7 @@ jobs:
steps:
- name: 📚 Git Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
submodules: recursive
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 📚 Git Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
submodules: recursive
+2 -2
View File
@@ -17,7 +17,7 @@ jobs:
shorebird_code_push: ${{ steps.filter.outputs.shorebird_code_push }}
shorebird_code_push_example: ${{ steps.filter.outputs.shorebird_code_push_example }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
# Full history so dorny/paths-filter can diff on push events.
fetch-depth: 0
@@ -65,7 +65,7 @@ jobs:
name: CSpell
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
submodules: recursive
- uses: streetsidesoftware/cspell-action@v8
+1 -1
View File
@@ -73,7 +73,7 @@ oslog = "0.2.0"
simple_logger = "5.0.0"
[dev-dependencies]
mockall = "0.14.0"
mockall = "0.15.0"
mockito = "1.2.0"
mock_instant = "0.6.0"
# Gives #[serial] attribute for locking all of our shorebird_init
+1 -1
View File
@@ -15,7 +15,7 @@
// buckets. They are not `extern "C"`, so cbindgen never emits them.
//
// Engine-side usage lives at `engine/src/flutter/shell/common/shorebird/updater.cc`
// in the open Flutter fork: <https://git.tonycloud.org/flutter/flutter>.
// in the open Flutter fork: <https://github.com/tony-cloud/flutter>.
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
+109 -2
View File
@@ -289,7 +289,7 @@ pub fn check_for_downloadable_update(channel: Option<&str>) -> anyhow::Result<bo
let (client_id, current_patch_number) = with_state(|state| {
Ok((
state.client_id().to_string(),
state.currently_booting_patch().map(|p| p.number),
state.running_patch().map(|p| p.number),
))
})?;
@@ -425,7 +425,7 @@ fn update_internal(_: &UpdaterLockState, channel: Option<&str>) -> anyhow::Resul
Ok(PatchCheckRequest::new(
&config,
&state.client_id(),
state.currently_booting_patch().map(|p| p.number),
state.running_patch().map(|p| p.number),
))
})?;
@@ -4087,3 +4087,110 @@ mod multi_engine_tests {
Ok(())
}
}
/// Regression tests asserting the patch-check request's `current_patch_number`
/// carries the running patch, not the boot breadcrumb (`currently_booting_patch`)
/// which `report_launch_success` clears before any patch check fires. Covers
/// both construction sites: `check_for_downloadable_update` and `update`.
#[cfg(test)]
mod patch_check_current_patch_number_tests {
use anyhow::Result;
use serial_test::serial;
use std::sync::atomic::{AtomicI64, Ordering};
use tempfile::TempDir;
use crate::{
check_for_downloadable_update,
network::{
testing_set_network_hooks, PatchCheckResponse, UNEXPECTED_DOWNLOAD, UNEXPECTED_REPORT,
},
report_launch_start, report_launch_success,
test_utils::install_fake_patch,
update,
updater::tests::init_for_testing,
with_state,
};
/// `current_patch_number` was `None` on the wire (the bug we're guarding).
const FIELD_OMITTED: i64 = -1;
/// The patch-check hook never ran.
const HOOK_NOT_CALLED: i64 = i64::MIN;
/// Last `current_patch_number` seen by the patch-check hook. Shared by
/// both tests; safe because they are `#[serial]` (only one runs at a
/// time) and `arrange_capturing_hooks` resets it before each check.
static CAPTURED: AtomicI64 = AtomicI64::new(HOOK_NOT_CALLED);
/// Installs patch 1 and completes a full boot, then asserts the steady
/// state in which patch checks actually run: the boot breadcrumb is
/// cleared, but the process is still running patch 1. Reading
/// `currently_booting_patch` here yields `None` — that is precisely the
/// regression this guards against.
fn boot_patch_one(tmp_dir: &TempDir) -> Result<()> {
init_for_testing(tmp_dir, None);
install_fake_patch(1)?;
report_launch_start()?;
report_launch_success()?;
with_state(|state| {
// Steady state: the boot breadcrumb is cleared, yet patch 1 is
// still the running patch.
assert!(state.currently_booting_patch().is_none());
assert_eq!(state.running_patch().map(|p| p.number), Some(1));
Ok(())
})
}
/// Installs network hooks whose patch-check leg records the request's
/// `current_patch_number` into `CAPTURED` (`None` -> `FIELD_OMITTED`) and
/// reports no available update.
fn arrange_capturing_hooks() {
CAPTURED.store(HOOK_NOT_CALLED, Ordering::SeqCst);
testing_set_network_hooks(
|_url, request| {
CAPTURED.store(
request
.current_patch_number
.map(|n| n as i64)
.unwrap_or(FIELD_OMITTED),
Ordering::SeqCst,
);
Ok(PatchCheckResponse {
patch_available: false,
patch: None,
rolled_back_patch_numbers: None,
})
},
// No update is offered, so neither hook should ever fire.
UNEXPECTED_DOWNLOAD,
UNEXPECTED_REPORT,
);
}
#[serial]
#[test]
fn check_for_downloadable_update_sends_running_patch() -> Result<()> {
let tmp_dir = TempDir::new().unwrap();
boot_patch_one(&tmp_dir)?;
arrange_capturing_hooks();
check_for_downloadable_update(None)?;
// 1 = running patch sent; -1 = field omitted (the bug); i64::MIN = hook never ran.
assert_eq!(CAPTURED.load(Ordering::SeqCst), 1);
Ok(())
}
#[serial]
#[test]
fn update_sends_running_patch() -> Result<()> {
let tmp_dir = TempDir::new().unwrap();
boot_patch_one(&tmp_dir)?;
arrange_capturing_hooks();
update(None)?;
// 1 = running patch sent; -1 = field omitted (the bug); i64::MIN = hook never ran.
assert_eq!(CAPTURED.load(Ordering::SeqCst), 1);
Ok(())
}
}
+1 -1
View File
@@ -23,4 +23,4 @@ For an Updater function to be visible to the Dart code, it must:
`dart run ffigen`. ffigen reads only `updater_dart.h`, so engine-only
symbols are not bound.
1. Android specific: be listed in
https://github.com/shorebirdtech/flutter/blob/shorebird/dev/engine/src/flutter/shell/platform/android/android_exports.lst
https://github.com/tony-cloud/flutter/blob/tonycloud/dev/engine/src/flutter/shell/platform/android/android_exports.lst