From 610d3939230f263a33a4c238e6c16f7bd3352a2c Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 14 Jun 2021 22:38:29 -0700 Subject: [PATCH] Add kotlin/aar android device tests --- bdk-kotlin/aar/build.gradle | 12 +- .../org/bitcoindevkit/bdk/AndroidLibTest.kt | 62 ++++++++ .../bdk/ExampleInstrumentedTest.kt | 147 ------------------ bdk-kotlin/jar/build.gradle | 12 +- .../java/org/bitcoindevkit/bdk/LibTest.kt | 44 ++++-- bdk-kotlin/settings.gradle | 2 +- build.sh | 46 +++++- test.sh | 14 ++ 8 files changed, 158 insertions(+), 181 deletions(-) create mode 100644 bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt delete mode 100644 bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/ExampleInstrumentedTest.kt create mode 100755 test.sh diff --git a/bdk-kotlin/aar/build.gradle b/bdk-kotlin/aar/build.gradle index 0ef7007..6116737 100644 --- a/bdk-kotlin/aar/build.gradle +++ b/bdk-kotlin/aar/build.gradle @@ -1,11 +1,10 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' +//apply plugin: 'kotlin-android-extensions' apply plugin: 'maven-publish' android { compileSdkVersion 30 - buildToolsVersion "29.0.3" defaultConfig { minSdkVersion 21 @@ -43,7 +42,7 @@ afterEvaluate { from components.release // You can then customize attributes of the publication as shown below. - groupId = 'org.bitcoindevkit.bdkffi' + groupId = 'org.bitcoindevkit' artifactId = 'bdk' version = '0.0.1-dev' } @@ -52,7 +51,7 @@ afterEvaluate { // Applies the component for the debug build variant. from components.debug - groupId = 'org.bitcoindevkit.bdkffi' + groupId = 'org.bitcoindevkit' artifactId = 'bdk-debug' version = '0.0.1-dev' } @@ -61,7 +60,10 @@ afterEvaluate { } dependencies { - implementation project(':jar') + implementation (project(':jar')) { + exclude group: 'net.java.dev.jna', module: 'jna' + } + api 'net.java.dev.jna:jna:5.8.0@aar' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.3.0' diff --git a/bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt b/bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt new file mode 100644 index 0000000..0dbaa2a --- /dev/null +++ b/bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/AndroidLibTest.kt @@ -0,0 +1,62 @@ +package org.bitcoindevkit.bdk + +import android.util.Log +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.sun.jna.Native +import org.junit.* + +import org.junit.runner.RunWith + +import org.junit.Assert.* + +//import org.bitcoindevkit.bdkjni.Types.Network +//import org.bitcoindevkit.bdkjni.Types.WalletConstructor +//import org.bitcoindevkit.bdkjni.Types.WalletPtr + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class AndroidLibTest { + + companion object { + private val bdkFfi: Lib = Native.load("bdk_ffi", Lib::class.java) + private lateinit var wallet: Lib.WalletPtr_t + + @BeforeClass + @JvmStatic + fun create_wallet() { + val name = "test_wallet" + val desc = + "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" + val change = + "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" + + wallet = bdkFfi.new_wallet(name, desc, change) + Log.d("create_wallet", "wallet created") + } + + @AfterClass + @JvmStatic + fun free_wallet() { + bdkFfi.free_wallet(wallet) + Log.d("free_wallet", "wallet freed") + } + } + + @Test + fun sync() { + bdkFfi.sync_wallet(wallet) + Log.d("sync", "wallet synced") + } + + @Test + fun new_address() { + val address = bdkFfi.new_address(wallet) + //println("address created from kotlin: $address") + assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e") + Log.d("new_address", "new address: $address") + } +} diff --git a/bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/ExampleInstrumentedTest.kt b/bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/ExampleInstrumentedTest.kt deleted file mode 100644 index 3187bb2..0000000 --- a/bdk-kotlin/aar/src/androidTest/java/org/bitcoindevkit/bdk/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,147 +0,0 @@ -package org.bitcoindevkit.bdk - -import android.util.Log -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.* -import kotlinx.coroutines.runBlocking -import org.junit.After - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* -import org.junit.Before - -//import org.bitcoindevkit.bdkjni.Types.Network -//import org.bitcoindevkit.bdkjni.Types.WalletConstructor -//import org.bitcoindevkit.bdkjni.Types.WalletPtr -import org.junit.Ignore - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - - companion object { - init { - System.loadLibrary("bdk_jni") - } - } - - private lateinit var wallet: WalletPtr - - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("org.bitcoindevkit.bdkjni.test", appContext.packageName) - } - - @Before - fun constructor() { - val dir = createTempDir() - val descriptor = "wpkh(tprv8ZgxMBicQKsPexGYyaFwnAsCXCjmz2FaTm6LtesyyihjbQE3gRMfXqQBXKM43DvC1UgRVv1qom1qFxNMSqVAs88qx9PhgFnfGVUdiiDf6j4/0/*)" - val electrum = "tcp://electrum.blockstream.info:60001" - wallet = Lib().constructor(WalletConstructor("testnet", Network.regtest, dir.toString(), descriptor, null, electrum, null)) - Lib().sync(wallet) - } - - @Test - fun newAddress() { - val address = Lib().get_new_address(wallet) - assertFalse(address.isEmpty()) - } - - @Test - fun sync() { - Lib().sync(wallet, 100) - val balance = Lib().get_balance(wallet) - assertFalse(balance == 0L) - } - - // TODO need to figure out why this passes when testing with a localhost node but fails when using blockstream.info - @Ignore - @Test - fun multiThreadBalance() { - runBlocking { - val flow1 = newBalanceFlow(1).flowOn(Dispatchers.IO) - val flow2 = newBalanceFlow(2).flowOn(Dispatchers.IO) - flow1.flatMapMerge(concurrency = 2) { flow2 }.collect() - //flow1.collect() - } - } - - private fun newBalanceFlow(id: Int): Flow> { - return (1..10).asFlow() - //.onStart { Log.d("BAL_FLOW", "start flow $id") } - //.onCompletion { Log.d("BAL_FLOW", "complete flow $id") } - //.onEach { Log.d("BAL_FLOW", "flow $id, iteration $it") } - .map { - val balance = Lib().get_balance(wallet) - Pair(it, balance) - } - .catch { e -> - Log.e("BAL_FLOW", "failed flow $id with exception: $e") - fail() - } - .onEach { - //Log.d("BAL_FLOW", "verifying flow $id, iteration ${it.first}") - assertFalse(it.second == 0L) - //Log.d("BAL_FLOW", "finished flow $id iteration ${it.first}") - } - } - - @Test - fun balance() { - val balance = Lib().get_balance(wallet) - assertFalse(balance == 0L) - } - - @Test - fun unspent() { - val unspent = Lib().list_unspent(wallet) - assertFalse(unspent.isEmpty()) - } - - @Test - fun transactions() { - val transactions = Lib().list_transactions(wallet) - assertFalse(transactions.isEmpty()) - } - - @Test - fun generate_key() { - val keys = Lib().generate_extended_key(Network.testnet, 24, "test123") - assertNotNull(keys) - assertEquals(24, keys.mnemonic.split(' ').size) - assertEquals("tprv", keys.xprv.substring(0,4)) - } - - @Test - fun restore_key() { - val mnemonic = "shell bid diary primary focus average truly secret lonely circle radar fall tank action place body wedding sponsor embody glue swing gauge shop penalty" - val keys = Lib().restore_extended_key(Network.testnet, mnemonic, null) - assertNotNull(keys) - assertEquals(mnemonic, keys.mnemonic) - assertEquals("tprv8ZgxMBicQKsPeh5nd4nCDLGh9dLfhqGfUoiQsbThkttjX9oroRY2j5vpEGwkiKiKtzdU7u4eqH2yFicGvz19rMVVXfY8XB9fdoeXWJ7SgVE", keys.xprv) - } - - @Test - fun restore_key_password() { - val mnemonic = "shell bid diary primary focus average truly secret lonely circle radar fall tank action place body wedding sponsor embody glue swing gauge shop penalty" - val keys = Lib().restore_extended_key(Network.testnet, mnemonic, "test123") - assertNotNull(keys) - assertEquals(mnemonic, keys.mnemonic) - assertEquals("tprv8ZgxMBicQKsPebcVXyErMuuv2rgE34m2SLMBhy4hURbSEAWQ1VsWVVmMnD7FKiAuRrxzAETFnUaSvFNQ5SAS5tYEwsM1KHDpUhLLQgd6yG1", keys.xprv) - } - - @After - fun destructor() { - Lib().destructor(wallet) - } -} diff --git a/bdk-kotlin/jar/build.gradle b/bdk-kotlin/jar/build.gradle index 48b6bdf..4d767df 100644 --- a/bdk-kotlin/jar/build.gradle +++ b/bdk-kotlin/jar/build.gradle @@ -11,17 +11,15 @@ test { // } } -task buildRust(type: Exec) { - workingDir '../' - commandLine './build.sh' -} +//task buildRust(type: Exec) { +// workingDir '../' +// commandLine './build.sh' +//} dependencies { implementation platform('org.jetbrains.kotlin:kotlin-bom') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "net.java.dev.jna:jna:5.8.0" - - testImplementation 'org.jetbrains.kotlin:kotlin-test' testImplementation 'org.jetbrains.kotlin:kotlin-test-junit' } @@ -29,7 +27,7 @@ publishing { publications { maven(MavenPublication) { groupId = 'org.bitcoindevkit' - artifactId = 'bdk-debug' + artifactId = 'bdk' version = '0.0.1-dev' from components.java diff --git a/bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt b/bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt index 503ff95..799163e 100644 --- a/bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt +++ b/bdk-kotlin/jar/src/test/java/org/bitcoindevkit/bdk/LibTest.kt @@ -1,8 +1,8 @@ package org.bitcoindevkit.bdk import com.sun.jna.Native -import org.junit.Test -import kotlin.test.assertEquals +import org.junit.* +import org.junit.Assert.assertEquals /** * Library test, which will execute on linux host. @@ -10,30 +10,40 @@ import kotlin.test.assertEquals */ class LibTest { - private val bdkFfi: Lib = Native.load("bdk_ffi", Lib::class.java) + companion object { + private val bdkFfi: Lib = Native.load("bdk_ffi", Lib::class.java) + private lateinit var wallet: Lib.WalletPtr_t + + @BeforeClass + @JvmStatic + fun create_wallet() { + val name = "test_wallet" + val desc = + "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" + val change = + "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" + + wallet = bdkFfi.new_wallet(name, desc, change) + println("wallet created") + } + + @AfterClass + @JvmStatic + fun free_wallet() { + bdkFfi.free_wallet(wallet) + println("wallet freed") + } + } @Test - fun new_sync_free_wallet() { - val name = "test_wallet" - val desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" - val change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" - - val wallet = bdkFfi.new_wallet(name, desc, change) + fun sync() { bdkFfi.sync_wallet(wallet) - bdkFfi.free_wallet(wallet) } @Test fun new_newaddress_wallet() { - val name = "test_wallet" - val desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)" - val change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)" - - val wallet = bdkFfi.new_wallet(name, desc, change) val address = bdkFfi.new_address(wallet) //println("address created from kotlin: $address") assertEquals(address, "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e") - bdkFfi.free_string(address) - bdkFfi.free_wallet(wallet) } } diff --git a/bdk-kotlin/settings.gradle b/bdk-kotlin/settings.gradle index 9f4e44a..17a73f5 100644 --- a/bdk-kotlin/settings.gradle +++ b/bdk-kotlin/settings.gradle @@ -1,3 +1,3 @@ rootProject.name = 'bdk-kotlin' -include 'jar' \ No newline at end of file +include ':jar',':aar' \ No newline at end of file diff --git a/build.sh b/build.sh index 6e501db..60f3bef 100755 --- a/build.sh +++ b/build.sh @@ -1,3 +1,6 @@ +#!/usr/bin/env bash +set -eo pipefail -o xtrace + # rust cargo build cargo test --features c-headers -- generate_headers @@ -5,10 +8,45 @@ cargo test --features c-headers -- generate_headers # cc export LD_LIBRARY_PATH=`pwd`/target/debug cc cc/bdk_ffi_test.c -o cc/bdk_ffi_test -L target/debug -l bdk_ffi -l pthread -l dl -l m -valgrind --leak-check=full cc/bdk_ffi_test -#cc/bdk_ffi_test -# bdk-kotlin +# bdk-kotlin jar mkdir -p bdk-kotlin/jar/libs/x86_64_linux cp target/debug/libbdk_ffi.so bdk-kotlin/jar/libs/x86_64_linux -(cd bdk-kotlin && gradle test) + +(cd bdk-kotlin && gradle :jar:build) + +# rust android + +# If ANDROID_NDK_HOME is not set then set it to github actions default +[ -z "$ANDROID_NDK_HOME" ] && export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle + +# Update this line accordingly if you are not building *from* darwin-x86_64 or linux-x86_64 +export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/`uname | tr '[:upper:]' '[:lower:]'`-x86_64/bin + +# Required for 'ring' dependency to cross-compile to Android platform, must be at least 21 +export CFLAGS="-D__ANDROID_API__=21" + +# IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on +BUILD_TARGETS="${BUILD_TARGETS:-aarch64,armv7,x86_64,i686}" + +mkdir -p bdk-kotlin/aar/src/main/jniLibs/ bdk-kotlin/aar/src/main/jniLibs/arm64-v8a bdk-kotlin/aar/src/main/jniLibs/x86_64 bdk-kotlin/aar/src/main/jniLibs/armeabi-v7a bdk-kotlin/aar/src/main/jniLibs/x86 + +if echo $BUILD_TARGETS | grep "aarch64"; then + CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --target=aarch64-linux-android + cp target/aarch64-linux-android/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/arm64-v8a +fi +if echo $BUILD_TARGETS | grep "x86_64"; then + CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --target=x86_64-linux-android + cp target/x86_64-linux-android/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/x86_64 +fi +if echo $BUILD_TARGETS | grep "armv7"; then + CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="armv7a-linux-androideabi21-clang" CC="armv7a-linux-androideabi21-clang" cargo build --target=armv7-linux-androideabi + cp target/armv7-linux-androideabi/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/armeabi-v7a +fi +if echo $BUILD_TARGETS | grep "i686"; then + CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --target=i686-linux-android + cp target/i686-linux-android/debug/libbdk_ffi.so bdk-kotlin/aar/src/main/jniLibs/x86 +fi + +# bdk-kotlin aar +(cd bdk-kotlin && gradle :aar:build) diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..da24835 --- /dev/null +++ b/test.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -eo pipefail -o xtrace + +# rust +cargo test --features c-headers -- generate_headers + +# cc +export LD_LIBRARY_PATH=`pwd`/target/debug +valgrind --leak-check=full cc/bdk_ffi_test +#cc/bdk_ffi_test + +# bdk-kotlin +(cd bdk-kotlin && gradle test) +(cd bdk-kotlin && gradle :aar:connectedDebugAndroidTest)