[#270] Add regression tests for assets

This commit is contained in:
Carter Jernigan 2021-09-11 10:38:52 -04:00
parent f9d95bca91
commit a58a235d2c
2 changed files with 126 additions and 16 deletions

View File

@ -0,0 +1,108 @@
package cash.z.ecc.android.sdk
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.tool.WalletBirthdayTool
import cash.z.ecc.android.sdk.type.ZcashNetwork
import org.json.JSONObject
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class AssetTest {
@Test
@SmallTest
fun birthday_height_from_filename() {
assertEquals(123, WalletBirthdayTool.birthdayHeight("123.json"))
}
@Test
@SmallTest
fun validate_mainnet_assets() {
val network = ZcashNetwork.Mainnet
val assets = listAssets(network)
assertFilesExist(assets)
assertFilenames(assets)
assertFileContents(network, assets)
}
@Test
@SmallTest
fun validate_testnet_assets() {
val network = ZcashNetwork.Testnet
val assets = listAssets(network)
assertFilesExist(assets)
assertFilenames(assets)
assertFileContents(network, assets)
}
private fun assertFilesExist(files: Array<String>?) {
assertFalse(files.isNullOrEmpty())
}
private fun assertFilenames(files: Array<String>?) {
files?.forEach {
val split = it.split('.')
assertEquals(2, split.size)
val intString = split.first()
val extensionString = split.last()
// Will throw exception if cannot be parsed
intString.toInt()
assertEquals("json", extensionString)
}
}
private fun assertFileContents(network: ZcashNetwork, files: Array<String>?) {
files?.map { filename ->
val filePath = "${WalletBirthdayTool.birthdayDirectory(network)}/$filename"
ApplicationProvider.getApplicationContext<Context>().assets.open(filePath)
.use { inputSteam ->
inputSteam.bufferedReader().use { bufferedReader ->
val slurped = bufferedReader.readText()
JsonFile(JSONObject(slurped), filename)
}
}
}?.forEach {
val jsonObject = it.jsonObject
assertTrue(jsonObject.has("network"))
assertTrue(jsonObject.has("height"))
assertTrue(jsonObject.has("hash"))
assertTrue(jsonObject.has("time"))
assertTrue(jsonObject.has("tree"))
val expectedNetworkName = when (network) {
ZcashNetwork.Mainnet -> "main"
ZcashNetwork.Testnet -> "test"
}
assertEquals("File: ${it.filename}", expectedNetworkName, jsonObject.getString("network"))
assertEquals(
"File: ${it.filename}",
WalletBirthdayTool.birthdayHeight(it.filename),
jsonObject.getInt("height")
)
// In the future, additional validation of the JSON can be added
}
}
private data class JsonFile(val jsonObject: JSONObject, val filename: String)
companion object {
fun listAssets(network: ZcashNetwork) = WalletBirthdayTool.listBirthdayDirectoryContents(
ApplicationProvider.getApplicationContext<Context>(),
WalletBirthdayTool.birthdayDirectory(network)
)
}
}

View File

@ -1,15 +1,16 @@
package cash.z.ecc.android.sdk.tool package cash.z.ecc.android.sdk.tool
import android.content.Context import android.content.Context
import androidx.annotation.VisibleForTesting
import cash.z.ecc.android.sdk.exception.BirthdayException import cash.z.ecc.android.sdk.exception.BirthdayException
import cash.z.ecc.android.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import cash.z.ecc.android.sdk.type.WalletBirthday import cash.z.ecc.android.sdk.type.WalletBirthday
import cash.z.ecc.android.sdk.type.ZcashNetwork import cash.z.ecc.android.sdk.type.ZcashNetwork
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import java.io.IOException
import java.io.InputStreamReader import java.io.InputStreamReader
import java.util.Arrays import java.util.Arrays
import java.util.Locale
/** /**
* Tool for loading checkpoints for the wallet, based on the height at which the wallet was born. * Tool for loading checkpoints for the wallet, based on the height at which the wallet was born.
@ -17,7 +18,7 @@ import java.util.Locale
* @param appContext needed for loading checkpoints from the app's assets directory. * @param appContext needed for loading checkpoints from the app's assets directory.
*/ */
class WalletBirthdayTool(appContext: Context) { class WalletBirthdayTool(appContext: Context) {
val context = appContext.applicationContext private val context = appContext.applicationContext
/** /**
* Load the nearest checkpoint to the given birthday height. If null is given, then this * Load the nearest checkpoint to the given birthday height. If null is given, then this
@ -51,13 +52,23 @@ class WalletBirthdayTool(appContext: Context) {
) )
} }
// TODO: This method performs disk IO; convert to suspending function
// Converting this to suspending will then propagate
@Throws(IOException::class)
internal fun listBirthdayDirectoryContents(context: Context, directory: String) =
context.assets.list(directory)
/** /**
* Returns the directory within the assets folder where birthday data * Returns the directory within the assets folder where birthday data
* (i.e. sapling trees for a given height) can be found. * (i.e. sapling trees for a given height) can be found.
*/ */
private fun birthdayDirectory(network: ZcashNetwork): String { @VisibleForTesting
return "saplingtree/${network.networkName.toLowerCase(Locale.US)}" internal fun birthdayDirectory(network: ZcashNetwork) =
} "saplingtree/${network.networkName.lowercase()}"
internal fun birthdayHeight(fileName: String) = fileName.split('.').first().toInt()
private fun Array<String>.sortDescending() = apply { sortByDescending { birthdayHeight(it) } }
/** /**
* Load the given birthday file from the assets of the given context. When no height is * Load the given birthday file from the assets of the given context. When no height is
@ -76,16 +87,7 @@ class WalletBirthdayTool(appContext: Context) {
): WalletBirthday { ): WalletBirthday {
twig("loading birthday from assets: $birthdayHeight") twig("loading birthday from assets: $birthdayHeight")
val directory = birthdayDirectory(network) val directory = birthdayDirectory(network)
val treeFiles = val treeFiles = listBirthdayDirectoryContents(context, directory)?.sortDescending()
context.assets.list(directory)?.apply {
sortByDescending { fileName ->
try {
fileName.split('.').first().toInt()
} catch (t: Throwable) {
network.saplingActivationHeight
}
}
}
if (treeFiles.isNullOrEmpty()) throw BirthdayException.MissingBirthdayFilesException( if (treeFiles.isNullOrEmpty()) throw BirthdayException.MissingBirthdayFilesException(
directory directory
) )
@ -94,7 +96,7 @@ class WalletBirthdayTool(appContext: Context) {
try { try {
file = if (birthdayHeight == null) treeFiles.first() else { file = if (birthdayHeight == null) treeFiles.first() else {
treeFiles.first { treeFiles.first {
it.split(".").first().toInt() <= birthdayHeight birthdayHeight(it) <= birthdayHeight
} }
} }
} catch (t: Throwable) { } catch (t: Throwable) {