bump Dart package to 0.4.41 #284
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |