Skip to content

bump Dart package to 0.4.41 #284

bump Dart package to 0.4.41

bump Dart package to 0.4.41 #284

Workflow file for this run

name: Dart Tests
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
# Unit tests + host integration tests (Go test server + dart test)
test-host:
runs-on: macos-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "1.26"
# Flutter is the test runner here: libwallet's Dart side now
# depends on atonline_api (which transitively pulls in Flutter
# via flutter_secure_storage), so `dart test` fails to load any
# file that imports `package:libwallet/libwallet.dart` —
# `flutter test` compiles those imports correctly. setup-dart is
# no longer needed; this job doesn't publish.
- uses: subosito/flutter-action@v2
with:
channel: stable
# Same ldflag plumbing as build.yml so CI test binaries also
# carry populated dateTag/gitTag in Info:version. Lets the
# integration test assert that the ldflag mechanism works at
# all, instead of silently regressing the way the broken
# `-X main.dateTag=...` did for years.
- name: Compute libwallet ldflags
run: |
GIT_TAG=$(git rev-parse --short HEAD)
DATE_TAG=$(TZ=UTC git show -s --format=%cd --date=format-local:%Y%m%d%H%M%S HEAD)
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
TAG_VERSION="${GITHUB_REF_NAME#v}"
else
TAG_VERSION=""
fi
PKG=github.com/KarpelesLab/libwallet/wltbase
echo "LIBWALLET_LDFLAGS=-X $PKG.dateTag=$DATE_TAG -X $PKG.gitTag=$GIT_TAG -X $PKG.version=$TAG_VERSION" >> "$GITHUB_ENV"
- name: Build c-shared library for FFI tests
run: |
CGO_ENABLED=1 CGO_LDFLAGS="-Wl,-headerpad_max_install_names" \
go build -buildmode=c-shared -ldflags="$LIBWALLET_LDFLAGS" \
-o dart/testserver/liblibwallet.dylib ./cshared/
- name: Install Dart dependencies
working-directory: dart
run: flutter pub get
- name: Run Dart analysis
working-directory: dart
run: flutter analyze lib test hook
# Belt-and-suspenders for the version-constant lockstep:
# version_constant_test.dart catches drift if you run the tests,
# but if a release commit only updates pubspec.yaml without
# touching anything in test/, the test wouldn't run on PR. The
# CLI check covers that case. bump_version.dart is plain Dart
# — `dart run` works inside a Flutter project tree.
- name: Verify pubspec ↔ lib/src/version.dart lockstep
working-directory: dart
run: dart run tools/bump_version.dart --check
- name: Run Dart unit tests
working-directory: dart
run: flutter test test/amount_test.dart test/models_test.dart test/response_test.dart test/version_constant_test.dart test/clawdwallet_pair_test.dart
- name: Run FFI integration tests
working-directory: dart
run: |
# The Go c-shared runtime's background goroutines can crash on
# process exit when the Dart VM tears down NativeCallable pointers.
# All tests pass before the exit crash, so accept exit code 134 (SIGABRT).
flutter test --concurrency=1 test/ffi_integration_test.dart || {
EXIT=$?
if [ $EXIT -eq 134 ]; then
echo "Go runtime exit crash (expected with c-shared + flutter test)"
else
exit $EXIT
fi
}
# iOS simulator integration tests (FFI via c-archive).
# The Xcode build for the test host takes 25-40 minutes on CI.
# Happy path including tests is ~8 min; we've seen 20-25 min runs
# on slow runners though, so keep 30 min as the cap. All libwallet
# RPC calls are bounded by a 30 s timeout internally, so any real
# hang fails fast rather than stalling the whole pipeline.
test-ios:
runs-on: macos-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "1.26"
- name: Compute libwallet ldflags
run: |
GIT_TAG=$(git rev-parse --short HEAD)
DATE_TAG=$(TZ=UTC git show -s --format=%cd --date=format-local:%Y%m%d%H%M%S HEAD)
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
TAG_VERSION="${GITHUB_REF_NAME#v}"
else
TAG_VERSION=""
fi
PKG=github.com/KarpelesLab/libwallet/wltbase
echo "LIBWALLET_LDFLAGS=-X $PKG.dateTag=$DATE_TAG -X $PKG.gitTag=$GIT_TAG -X $PKG.version=$TAG_VERSION" >> "$GITHUB_ENV"
- name: Build c-archive slices for iOS (device + both sim arches)
run: |
# The ios/libwallet.podspec wraps these into a libwallet.xcframework
# at pod install time. If the per-SDK files exist in dart/ios/,
# prepare_command skips the GitHub Release download — that's the
# path we want for CI on master, since pre-release commits don't
# have a matching release yet.
#
# We MUST build both sim arches (arm64 + x86_64) even though the
# CI runner only uses arm64: Xcode targets the iphonesimulator
# SDK for ALL its arches by default, and CocoaPods will reject
# an xcframework whose simulator slice doesn't cover x86_64 with
# "Unable to find matching slice for the current build
# architectures (arm64 x86_64)".
# prepare_command then lipo's the two sim slices into
# liblibwallet-iossimulator.a before invoking xcodebuild.
CGO_ENABLED=1 GOOS=ios GOARCH=arm64 \
CGO_CFLAGS="-isysroot $(xcrun --sdk iphonesimulator --show-sdk-path) -mios-simulator-version-min=13.0 -arch arm64" \
CGO_LDFLAGS="-isysroot $(xcrun --sdk iphonesimulator --show-sdk-path) -mios-simulator-version-min=13.0 -arch arm64" \
go build -buildmode=c-archive -ldflags="$LIBWALLET_LDFLAGS" -o dart/ios/liblibwallet-iossimulator-arm64.a ./cshared/
CGO_ENABLED=1 GOOS=ios GOARCH=amd64 \
CGO_CFLAGS="-isysroot $(xcrun --sdk iphonesimulator --show-sdk-path) -mios-simulator-version-min=13.0 -arch x86_64" \
CGO_LDFLAGS="-isysroot $(xcrun --sdk iphonesimulator --show-sdk-path) -mios-simulator-version-min=13.0 -arch x86_64" \
go build -buildmode=c-archive -ldflags="$LIBWALLET_LDFLAGS" -o dart/ios/liblibwallet-iossimulator-x64.a ./cshared/
CGO_ENABLED=1 GOOS=ios GOARCH=arm64 \
CGO_CFLAGS="-fembed-bitcode -isysroot $(xcrun --sdk iphoneos --show-sdk-path) -miphoneos-version-min=13.0 -arch arm64" \
CGO_LDFLAGS="-isysroot $(xcrun --sdk iphoneos --show-sdk-path) -miphoneos-version-min=13.0 -arch arm64" \
go build -buildmode=c-archive -ldflags="$LIBWALLET_LDFLAGS" -o dart/ios/liblibwallet-ios-arm64.a ./cshared/
- uses: subosito/flutter-action@v2
with:
channel: stable
- name: Reset CoreSimulator + boot iOS simulator
run: |
set -euo pipefail
# The "Application ... is unknown to FrontBoard" / "Simulator
# device failed to launch" failures we kept hitting were
# NOT caused by a stale device — they were caused by stale
# state inside the CoreSimulator service itself, which
# survives across jobs on the hosted-runner pool and even
# across simctl create/delete cycles. The fix shipped by
# thousands of mature iOS CI configs is just:
# 1. shutdown every booted sim
# 2. erase the device contents (clears installd / DB /
# FrontBoard mapping per-device)
# 3. let CoreSimulator restart with a clean device pool
# That's strictly enough — no need to invent a new sim.
xcrun simctl shutdown all 2>/dev/null || true
xcrun simctl erase all
# Kill any orphan Simulator.app processes from prior runs
# so the rebooted CoreSimulator service comes up clean.
killall Simulator 2>/dev/null || true
# Pick the most recent iPhone available from the preinstalled
# device pool. Sorting by numeric model number (parsed out of
# "iPhone 15 Pro Max" etc.) is robust against Apple's
# alphabetic / array-order quirks that previously selected
# iPhone XS Max. Filter to runtimes whose major version
# matches Xcode's iphonesimulator SDK so we never land on a
# preview runtime whose deployment-target gates the test
# app's launch (the FBOpenApplicationServiceErrorDomain
# failure we kept seeing).
SDK_MAJOR=$(xcrun --sdk iphonesimulator --show-sdk-version | cut -d. -f1)
echo "Xcode iphonesimulator SDK major = $SDK_MAJOR"
DEVICES_JSON=$(xcrun simctl list devices available -j)
# jq pipeline:
# 1. Walk every runtime key whose name starts with
# "iOS-<SDK_MAJOR>" (matches "iOS-18-0" / "iOS-18-5"
# but not "iOS-26-0") so we never land on a preview
# runtime whose deployment target gates the test
# app's launch.
# 2. Filter to available iPhones whose name parses as
# "iPhone N", "iPhone N Plus", "iPhone N Pro", or
# "iPhone N Pro Max".
# 3. Sort by [model_number, variant_rank] so ties on
# model number resolve to the higher variant
# (base < Plus < Pro < Pro Max). Without the variant
# rank, jq's stable sort can land on the base model
# depending on Apple's array order.
PICK_PIPELINE='
map(select(.isAvailable and (.name | test("^iPhone [0-9]+($| Plus$| Pro$| Pro Max$)")))
| { udid, name,
model: (.name | capture("iPhone (?<n>[0-9]+)") | .n | tonumber),
variant: (
if (.name | endswith(" Pro Max")) then 3
elif (.name | endswith(" Pro")) then 2
elif (.name | endswith(" Plus")) then 1
else 0 end)
})
| sort_by([.model, .variant])
| last // empty
'
PICK=$(echo "$DEVICES_JSON" | jq -r --arg major "$SDK_MAJOR" '
.devices
| to_entries
| map(select(.key | test("iOS-" + $major + "(-|$)")))
| map(.value[])
' | jq -r "$PICK_PIPELINE")
if [ -z "$PICK" ] || [ "$PICK" = "null" ]; then
# No preinstalled device of the right runtime / model
# shape — fall back to the highest-numbered iPhone of any
# runtime so the job at least attempts to run.
echo "No iPhone matching iOS-$SDK_MAJOR runtime; falling back to any iPhone"
PICK=$(echo "$DEVICES_JSON" | jq -r '
.devices
| to_entries
| map(.value[])
' | jq -r "$PICK_PIPELINE")
fi
if [ -z "$PICK" ] || [ "$PICK" = "null" ]; then
echo "No usable iPhone simulator found"; exit 1
fi
DEVICE_ID=$(echo "$PICK" | jq -r '.udid')
DEVICE_NAME=$(echo "$PICK" | jq -r '.name')
echo "DEVICE_ID=$DEVICE_ID" >> "$GITHUB_ENV"
echo "Using simulator: $DEVICE_NAME ($DEVICE_ID)"
xcrun simctl boot "$DEVICE_ID"
# Block until launchd reports Status=Booted, Status Type=Usable.
# Without this, simctl boot returns as soon as the boot
# process starts, but installd / SpringBoard may not yet be
# ready and the subsequent flutter test install/launch races.
xcrun simctl bootstatus "$DEVICE_ID" -b
# Extra paranoia after the boot reports Usable — wait for
# SpringBoard to actually appear in the running-process
# list. simctl bootstatus has historically returned a
# micro-second before SpringBoard finishes its first
# bringup, and flutter's install-then-launch sequence is
# tight enough to race that gap on hot runners.
for i in $(seq 1 30); do
if xcrun simctl spawn "$DEVICE_ID" launchctl print system 2>/dev/null | grep -q 'com.apple.SpringBoard$'; then
echo "SpringBoard ready after ${i}s"
break
fi
sleep 1
done
- name: Install Flutter dependencies
working-directory: dart/test_app
run: flutter pub get
- name: Run iOS integration tests
working-directory: dart/test_app
# Single attempt — the fresh-sim setup above gives us a
# clean install database / FrontBoard state, which was the
# root cause of the recurring "unknown to FrontBoard"
# failure. The per-attempt watchdog stays so the job doesn't
# hang for the 30-min GHA cap if flutter test deadlocks on
# the VM Service attach (the secondary mDNS race); if that
# happens we surface a clean failure with the captured sim
# log instead of timing out the whole job.
run: |
set +e
LOG=$RUNNER_TEMP/flutter-test.log
SIMLOG=$RUNNER_TEMP/sim-console.log
PER_ATTEMPT_TIMEOUT=900 # 15 minutes; integration test typically lands in ~3-8 min
# Stream the sim's console in the background so failure
# diagnostics can show what the test app actually printed
# — crucial for any VM Service attach hang where flutter's
# own logs go silent.
xcrun simctl spawn "$DEVICE_ID" log stream \
--style compact \
--level debug \
--predicate 'subsystem CONTAINS "flutter" OR processImagePath CONTAINS "Runner" OR eventMessage CONTAINS "Dart VM"' \
> "$SIMLOG" 2>&1 &
SIMLOG_PID=$!
# Direct redirect (not piped through tee) so $! captures the
# flutter PID — the watchdog needs to kill that PID, not a
# tee process buffering its output.
flutter test --verbose \
--dds-port=50301 \
integration_test/libwallet_test.dart \
-d "$DEVICE_ID" > "$LOG" 2>&1 &
FLUTTER_PID=$!
tail -f "$LOG" &
TAIL_PID=$!
# Watchdog: SIGTERM after the budget, SIGKILL 5 s later if
# flutter ignores TERM. Cancelled below as soon as flutter
# exits naturally so a clean run doesn't sleep out the budget.
( sleep "$PER_ATTEMPT_TIMEOUT" && \
kill -TERM $FLUTTER_PID 2>/dev/null && \
sleep 5 && \
kill -KILL $FLUTTER_PID 2>/dev/null ) &
WATCHDOG_PID=$!
wait $FLUTTER_PID 2>/dev/null
EXIT=$?
kill $WATCHDOG_PID 2>/dev/null
wait $WATCHDOG_PID 2>/dev/null
kill $SIMLOG_PID 2>/dev/null
wait $SIMLOG_PID 2>/dev/null
kill $TAIL_PID 2>/dev/null
wait $TAIL_PID 2>/dev/null
if [ "$EXIT" != "0" ]; then
echo "::group::iOS simulator console (last 200 lines)"
tail -200 "$SIMLOG" 2>/dev/null || echo "(no sim log captured)"
echo "::endgroup::"
fi
exit "$EXIT"
- name: Cleanup simulator
if: always()
run: |
# Just shutdown the preinstalled device we used — DON'T
# delete it (the runner image owns the device pool; the next
# job on the same hosted runner reuses + erases). simctl
# erase at the start of the next run handles state cleanup.
if [ -n "${DEVICE_ID:-}" ]; then
xcrun simctl shutdown "$DEVICE_ID" 2>/dev/null || true
fi
# Android emulator integration tests (FFI via c-shared)
test-android:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "1.26"
- name: Install Android NDK
run: |
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --install "ndk;27.2.12479018"
echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/27.2.12479018" >> "$GITHUB_ENV"
- name: Compute libwallet ldflags
run: |
GIT_TAG=$(git rev-parse --short HEAD)
DATE_TAG=$(TZ=UTC git show -s --format=%cd --date=format-local:%Y%m%d%H%M%S HEAD)
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
TAG_VERSION="${GITHUB_REF_NAME#v}"
else
TAG_VERSION=""
fi
PKG=github.com/KarpelesLab/libwallet/wltbase
echo "LIBWALLET_LDFLAGS=-X $PKG.dateTag=$DATE_TAG -X $PKG.gitTag=$GIT_TAG -X $PKG.version=$TAG_VERSION" >> "$GITHUB_ENV"
- name: Build c-shared library for Android x86_64
run: |
# The emulator is x86_64, so we only need that ABI
NDK=$ANDROID_HOME/ndk/27.2.12479018
export CC="$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android21-clang"
export CXX="$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android21-clang++"
CGO_ENABLED=1 GOOS=android GOARCH=amd64 \
go build -buildmode=c-shared -ldflags="$LIBWALLET_LDFLAGS" \
-o dart/test_app/android/app/src/main/jniLibs/x86_64/liblibwallet.so \
./cshared/
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
- uses: subosito/flutter-action@v2
with:
channel: stable
- name: Install Flutter dependencies
working-directory: dart/test_app
run: flutter pub get
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Run Android integration tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
arch: x86_64
target: google_apis
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim
disable-animations: true
script: cd dart/test_app && flutter test integration_test/libwallet_test.dart -d emulator-5554 || test $? -eq 79