[#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
import android.content.Context
import androidx.annotation.VisibleForTesting
import cash.z.ecc.android.sdk.exception.BirthdayException
import cash.z.ecc.android.sdk.ext.twig
import cash.z.ecc.android.sdk.type.WalletBirthday
import cash.z.ecc.android.sdk.type.ZcashNetwork
import com.google.gson.Gson
import com.google.gson.stream.JsonReader
import java.io.IOException
import java.io.InputStreamReader
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.
@ -17,7 +18,7 @@ import java.util.Locale
* @param appContext needed for loading checkpoints from the app's assets directory.
*/
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
@ -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
* (i.e. sapling trees for a given height) can be found.
*/
private fun birthdayDirectory(network: ZcashNetwork): String {
return "saplingtree/${network.networkName.toLowerCase(Locale.US)}"
}
@VisibleForTesting
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
@ -76,16 +87,7 @@ class WalletBirthdayTool(appContext: Context) {
): WalletBirthday {
twig("loading birthday from assets: $birthdayHeight")
val directory = birthdayDirectory(network)
val treeFiles =
context.assets.list(directory)?.apply {
sortByDescending { fileName ->
try {
fileName.split('.').first().toInt()
} catch (t: Throwable) {
network.saplingActivationHeight
}
}
}
val treeFiles = listBirthdayDirectoryContents(context, directory)?.sortDescending()
if (treeFiles.isNullOrEmpty()) throw BirthdayException.MissingBirthdayFilesException(
directory
)
@ -94,7 +96,7 @@ class WalletBirthdayTool(appContext: Context) {
try {
file = if (birthdayHeight == null) treeFiles.first() else {
treeFiles.first {
it.split(".").first().toInt() <= birthdayHeight
birthdayHeight(it) <= birthdayHeight
}
}
} catch (t: Throwable) {