test(android): instrumented suite on a managed device in CI; fix framed error envelopes read as success - #888
Merged
Conversation
…in CI, and caught a framed error envelope read as success The seven androidTest files had not compiled for a long time and no workflow ran them. Running them on a Gradle managed device found one production bug. Production: isErrorEnvelope decoded the bytes as they cross JNI, but every envelope the bridge hands Kotlin carries the 0x03 frame byte, which the canonical decoder refuses; every rejection therefore read as "not an error" (vector case_0001_proof_cap_over read ACCEPT instead of PROOF_TOO_LARGE). dsm_sdk::envelope::transport::error_code_of_transport_bytes strips the frame byte when present; the JNI export delegates to it. Host test an_error_envelope_is_detected_framed_and_bare; mutation control (no strip) red, restored. Tests: SoFi harnesses fund AMM vaults through funding_legs, read the u64 reserves and assert the hop's parent_binding, and carry @RealHardware (two phones + live fleet). AndroidLayerProofTest calls initSdk (router install), installs a test-only env config from the instrumentation assets, retargets calls to methods the bridge dispatches (getDeviceIdBin with a 32-byte assertion, getGenesisHashBin), deletes tests for removed capabilities (identity presence, Bluetooth status, device-id alias), and marks the faucet test @RealHardware. BleEventRelayPersistenceTest marks the bridge ready before flushing, adds a not-ready test and a reset hook. CI: android-instrumented-tests builds every packaged ABI (make android-libs; refreshDsmJniLibs requires all three) and runs pixel6Api34DebugAndroidTest excluding @RealHardware. Locally, with the app's web assets moved aside as in CI: 45 of 45 pass. Not changed: the committed dsm_env_config.toml fails the SDK's strict loader (nodes lack register_incarnation); it is an owner-protected file.
… in the shell
The managed-device job set ANDROID_NDK_HOME from ${{ env.ANDROID_NDK_LATEST_HOME }}.
An env-context expression reads only workflow-defined variables, not the runner
image's, so it evaluated to an empty string and cargo-ndk refused to detect an
NDK. The job now accepts SDK licenses first, installs the ndkVersion that
app/build.gradle.kts pins (the same NDK its CMake build uses), and exports that
path through GITHUB_ENV before make android-libs.
With the NDK resolved, make android-libs reached the dsm build script, which compiles the protocol with prost and failed with "Could not find protoc". The job now installs protobuf-compiler the same way every other Rust job does.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Owner ruling 2026-09-14 (HY2): fix the instrumented Android tests and give them a CI job, rather than delete them. The seven files under
app/src/androidTesthad not compiled for a long time, and no workflow ran them. This PR makes them compile, runs the device-local suite on a Gradle managed device in CI, and fixes the defects that running it exposed. One of those defects is in production code.A production bug the suite found
Every rejected envelope read as accepted on device.
processEnvelopeV3returns the ingress response with its leading0x03frame byte, and the bridge's own error builders frame theirs the same way.isErrorEnvelopedecoded those bytes directly. The canonical decoder refuses the frame byte, so the detector reported every response as "not an error". The vector suite'scase_0001_proof_cap_overexpectsPROOF_TOO_LARGEand readACCEPT.Fix:
dsm_sdk::envelope::transport::error_code_of_transport_bytesstrips the frame byte when present, as the request path already does. The JNI export delegates to it. The function lives in the host-compilable module, so a host test covers it:an_error_envelope_is_detected_framed_and_bare: framed and bare error envelopes both report their code; an empty buffer and a lone frame byte report none.VectorTests.vectorsV1now passes.A deployment finding (reported, not changed)
The committed
dsm_client/frontend/public/dsm_env_config.tomlfails the SDK's strict loader:TOML parse error at line 30, column 1.NodeConfigrequiresregister_incarnation, and the committed file's[[nodes]]entries predate it. It still lists the retired three-node fleet. Any APK built from a clean checkout ships this file and fails SDK init; builds from the owner's working copy parse. The file is an owner-protected environment config, so this PR does not touch it. The instrumented suite no longer depends on it.Test repairs
SoFiTestHelpers,SoFiTradeRealHwTest,SoFiCrossDeviceOwnerTest,SoFiCrossDeviceTraderTest): the u128 reserve fields are reserved in the proto. The tests now fund an AMM vault throughDlvInstantiateV1.funding_legs(two legs, lex order, non-zero), read theuint64reserve_a/reserve_b, and assert the hop's 32-byteparent_bindingin place of the removed reserves digest. These need two paired phones and the live fleet, so they carry a new@RealHardwareannotation.AndroidLayerProofTest:ensureGenesisnever calledinitSdk, the step that installs the app router, so every routed call answered "app router not installed". It now calls it, and installs a test-onlyandroidTest/assets/dsm_env_config.instrumented.toml(loopback node,allow_localhost = true) from the instrumentation context instead of the app's bundled config.hasIdentityDirect,getPersistedGenesisHash,getBluetoothStatus,getPersistedDeviceId. Where a current method exists, the tests use it:getGenesisHashBin, andgetDeviceIdBinwith a 32-byte assertion, because an empty success is not an identity. Where the capability is gone, the tests are deleted:t20andt21(identity presence),t33(Bluetooth status),t41(the device-id alias).t40claims the faucet from the live fleet, so it is@RealHardware.BleEventRelayPersistenceTest: the flush tests never marked the bridge ready, soflushPersistedcorrectly left every event in place and the tests asserted zero. They now mark it ready. A newtestResetBridgeReadyhook (@VisibleForTesting) keeps tests independent, and a new test proves the not-ready path keeps events.CI
New job Android Instrumented Tests (managed device) on the android layer:
make android-libs.refreshDsmJniLibsrefuses to package unless everyabiFiltersABI has a library, so an x86_64-only build would fail before any test ran../gradlew :app:pixel6Api34DebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.notAnnotation=com.dsm.wallet.RealHardware.pixel6Api34, a Pixel 6 on API 34 with theaosp-atdimage, intestOptions.managedDevices.Verification
./gradlew :app:compileDebugAndroidTestKotlin: clean.BUILD SUCCESSFUL). That is 50 tests, minus the four deleted, minust40, now@RealHardware; the three SoFi harness classes are excluded the same way. Failures per run as each fix landed: 30 (config asset missing, relay never marked ready, frame byte); 28 (app router never installed); 27 (the bundled config fails the strict loader); 10 (four methods absent from the bridge); 0.cargo test -p dsm_sdk --release --lib envelope::transport::tests::an_error_envelope_is_detected_framed_and_baregreen; mutation red; restored green;cargo fmt --checkclean;cargo clippy --locked -p dsm_sdk --all-targets -- -D warnings: zero diagnostics;./scripts/check-spdx.sh: OK.Not in this PR
The committed environment config above; that is the owner's file. The four-phone SoFi harnesses still need a hands-on run.