Test cleanup, deleting outdated tests.

This commit is contained in:
Kevin Gorham 2019-12-23 15:02:58 -05:00
parent 527eb50439
commit 93c01a9f3f
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
12 changed files with 278 additions and 827 deletions

View File

@ -1,50 +0,0 @@
package cash.z.wallet.sdk.db
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.test.core.app.ApplicationProvider
import org.junit.AfterClass
import org.junit.Assert.assertNotNull
import org.junit.BeforeClass
import org.junit.Rule
import org.junit.Test
class CacheDbIntegrationTest {
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
@Test
fun testDbExists() {
assertNotNull(db)
}
@Test
fun testDaoExists() {
assertNotNull(dao)
}
companion object {
private lateinit var dao: CompactBlockDao
private lateinit var db: CompactBlockDb
@BeforeClass
@JvmStatic
fun setup() {
// TODO: put this database in the assets directory and open it from there via .openHelperFactory(new AssetSQLiteOpenHelperFactory()) seen here https://github.com/albertogiunta/sqliteAsset
db = Room
.databaseBuilder(ApplicationProvider.getApplicationContext(), CompactBlockDb::class.java, "dummy-cache.db")
// .databaseBuilder(ApplicationProvider.getApplicationContext(), CompactBlockDb::class.java, "compact-blocks.db")
.setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
.fallbackToDestructiveMigration()
.build()
.apply { dao = complactBlockDao() }
}
@AfterClass
@JvmStatic
fun close() {
db.close()
}
}
}

View File

@ -1,77 +0,0 @@
package cash.z.wallet.sdk.db
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.test.core.app.ApplicationProvider
import org.junit.AfterClass
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.BeforeClass
import org.junit.Rule
import org.junit.Test
class DerivedDbIntegrationTest {
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
@Test
fun testDbExists() {
assertNotNull(db)
}
@Test
fun testDaoExists_Transaction() {
assertNotNull(transactions)
}
@Test
fun testDaoExists_Block() {
assertNotNull(blocks)
}
@Test
fun testCount_Block() {
assertEquals(80101, blocks.count())
}
@Test
fun testNoteQuery() {
val all = transactions.getReceivedTransactions()
assertEquals(3, all.size)
}
@Test
fun testTransactionDaoPrepopulated() {
val tran = transactions.findById(1)
assertEquals(343987, tran?.minedHeight)
}
companion object {
private lateinit var transactions: TransactionDao
private lateinit var blocks: BlockDao
private lateinit var db: DerivedDataDb
@BeforeClass
@JvmStatic
fun setup() {
// TODO: put this database in the assets directory and open it from there via .openHelperFactory(new AssetSQLiteOpenHelperFactory()) seen here https://github.com/albertogiunta/sqliteAsset
db = Room
.databaseBuilder(ApplicationProvider.getApplicationContext(), DerivedDataDb::class.java, "new-data-glue2.db")
.setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
.fallbackToDestructiveMigration()
.build()
.apply {
transactions = transactionDao()
blocks = blockDao()
}
}
@AfterClass
@JvmStatic
fun close() {
db.close()
}
}
}

View File

@ -1,110 +0,0 @@
package cash.z.wallet.sdk.db
import android.util.Log
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.test.core.app.ApplicationProvider
import cash.z.wallet.sdk.entity.CompactBlockEntity
import cash.z.wallet.sdk.jni.RustBackend
import cash.z.wallet.sdk.jni.RustBackendWelding
import cash.z.wallet.sdk.rpc.CompactTxStreamerGrpc
import cash.z.wallet.sdk.rpc.Service
import cash.z.wallet.sdk.rpc.Service.BlockID
import cash.z.wallet.sdk.rpc.Service.BlockRange
import io.grpc.ManagedChannel
import io.grpc.ManagedChannelBuilder
import org.junit.AfterClass
import org.junit.BeforeClass
import org.junit.Rule
import org.junit.Test
import java.util.concurrent.TimeUnit
class GlueIntegrationTest {
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
@Test
fun testDbExists() {
Log.e("tezt", "addData")
addData()
Log.e("tezt", "scanData")
scanData()
Log.e("tezt", "checkResults")
checkResults()
}
private fun checkResults() {
Thread.sleep(15000L)
}
private fun addData() {
val result = blockingStub.getBlockRange(
BlockRange.newBuilder()
.setStart(BlockID.newBuilder().setHeight(373070L).build())
.setEnd(BlockID.newBuilder().setHeight(373085L).build())
.build()
)
while (result.hasNext()) {
val compactBlock = result.next()
dao.insert(CompactBlockEntity(compactBlock.height.toInt(), compactBlock.toByteArray()))
System.err.println("stored block at height: ${compactBlock.height} with time ${compactBlock.time}")
}
}
private fun scanData() {
val dbFileName = "/data/user/0/cash.z.wallet.sdk.test/databases/new-data-glue.db"
rustBackend.initDataDb()
rustBackend.initAccountsTable("dummyseed".toByteArray(), 1)
Log.e("tezt", "scanning blocks...")
val result = rustBackend.scanBlocks()
System.err.println("done.")
}
fun heightOf(height: Long): Service.BlockID {
return BlockID.newBuilder().setHeight(height).build()
}
companion object {
// jni
val rustBackend: RustBackendWelding = RustBackend
// db
private lateinit var dao: CompactBlockDao
private lateinit var db: CompactBlockDb
private const val cacheDbName = "new-dummy-cache-glue.db"
private const val cacheDbPath = "/data/user/0/cash.z.wallet.sdk.test/databases/$cacheDbName"
// grpc
lateinit var blockingStub: CompactTxStreamerGrpc.CompactTxStreamerBlockingStub
@BeforeClass
@JvmStatic
fun setup() {
rustBackend.create(ApplicationProvider.getApplicationContext())
val channel = ManagedChannelBuilder.forAddress("10.0.2.2", 9067).usePlaintext().build()
blockingStub = CompactTxStreamerGrpc.newBlockingStub(channel)
db = Room
.databaseBuilder(
ApplicationProvider.getApplicationContext(),
CompactBlockDb::class.java,
cacheDbName
)
.setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
.fallbackToDestructiveMigration()
.build()
.apply { dao = complactBlockDao() }
}
@AfterClass
@JvmStatic
fun close() {
db.close()
(blockingStub.channel as ManagedChannel).shutdown().awaitTermination(2000L, TimeUnit.MILLISECONDS)
}
}
}

View File

@ -1,107 +0,0 @@
package cash.z.wallet.sdk.db
import android.util.Log
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.test.core.app.ApplicationProvider
import cash.z.wallet.sdk.entity.CompactBlockEntity
import cash.z.wallet.sdk.jni.RustBackend
import cash.z.wallet.sdk.jni.RustBackendWelding
import cash.z.wallet.sdk.rpc.CompactTxStreamerGrpc
import cash.z.wallet.sdk.rpc.Service
import cash.z.wallet.sdk.rpc.Service.BlockID
import cash.z.wallet.sdk.rpc.Service.BlockRange
import io.grpc.ManagedChannel
import io.grpc.ManagedChannelBuilder
import org.junit.AfterClass
import org.junit.Assert.assertNotNull
import org.junit.BeforeClass
import org.junit.Rule
import org.junit.Test
import java.util.concurrent.TimeUnit
class GlueSetupIntegrationTest {
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
@Test
fun testDbExists() {
assertNotNull(db)
// Log.e("tezt", "addData")
// addData()
// Log.e("tezt", "scanData")
// scanData()
// Log.e("tezt", "checkResults")
// checkResults()
}
private fun checkResults() {
Thread.sleep(15000L)
}
private fun addData() {
val result = blockingStub.getBlockRange(
BlockRange.newBuilder()
.setStart(BlockID.newBuilder().setHeight(373070L).build())
.setEnd(BlockID.newBuilder().setHeight(373085L).build())
.build()
)
while (result.hasNext()) {
val compactBlock = result.next()
dao.insert(CompactBlockEntity(compactBlock.height.toInt(), compactBlock.toByteArray()))
System.err.println("stored block at height: ${compactBlock.height}")
}
}
private fun scanData() {
Log.e("tezt", "scanning blocks...")
val result = rustBackend.scanBlocks()
System.err.println("done.")
}
fun heightOf(height: Long): Service.BlockID {
return BlockID.newBuilder().setHeight(height).build()
}
companion object {
// jni
val rustBackend: RustBackendWelding = RustBackend
// db
private lateinit var dao: CompactBlockDao
private lateinit var db: CompactBlockDb
private const val cacheDbName = "cache-glue.db"
private const val dataDbName = "data-glue.db"
// grpc
lateinit var blockingStub: CompactTxStreamerGrpc.CompactTxStreamerBlockingStub
@BeforeClass
@JvmStatic
fun setup() {
rustBackend.create(ApplicationProvider.getApplicationContext(), cacheDbName, dataDbName)
val channel = ManagedChannelBuilder.forAddress("10.0.2.2", 9067).usePlaintext().build()
blockingStub = CompactTxStreamerGrpc.newBlockingStub(channel)
db = Room
.databaseBuilder(
ApplicationProvider.getApplicationContext(),
CompactBlockDb::class.java,
cacheDbName
)
.setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
.fallbackToDestructiveMigration()
.build()
.apply { dao = complactBlockDao() }
}
@AfterClass
@JvmStatic
fun close() {
db.close()
(blockingStub.channel as ManagedChannel).shutdown().awaitTermination(2000L, TimeUnit.MILLISECONDS)
}
}
}

View File

@ -1,89 +0,0 @@
package cash.z.wallet.sdk.db
import androidx.test.platform.app.InstrumentationRegistry
import cash.z.wallet.sdk.block.CompactBlockDbStore
import cash.z.wallet.sdk.block.CompactBlockDownloader
import cash.z.wallet.sdk.block.CompactBlockProcessor
//import cash.z.wallet.sdk.transaction.PollingTransactionRepository
import cash.z.wallet.sdk.ext.TroubleshootingTwig
import cash.z.wallet.sdk.ext.Twig
import cash.z.wallet.sdk.ext.SampleSeedProvider
import cash.z.wallet.sdk.ext.SampleSpendingKeyProvider
import cash.z.wallet.sdk.jni.RustBackend
//import cash.z.wallet.sdk.secure.Wallet
import cash.z.wallet.sdk.service.LightWalletGrpcService
import kotlinx.coroutines.runBlocking
import org.junit.AfterClass
import org.junit.Before
import org.junit.Test
/*
TODO:
setup a test that we can run and just watch things happen, to give confidence that logging is expressive enough to
verify that the SDK is behaving as expected.
*/
class IntegrationTest {
private val dataDbName = "IntegrationData41.db"
private val cacheDdName = "IntegrationCache41.db"
private val context = InstrumentationRegistry.getInstrumentation().context
private lateinit var downloader: CompactBlockDownloader
private lateinit var processor: CompactBlockProcessor
// private lateinit var wallet: Wallet
@Before
fun setup() {
deleteDbs()
Twig.plant(TroubleshootingTwig())
}
private fun deleteDbs() {
// prior to each run, delete the DBs for sanity
listOf(cacheDdName, dataDbName).map { context.getDatabasePath(it).absoluteFile }.forEach {
println("Deleting ${it.name}")
it.delete()
}
}
@Test(timeout = 120_000L)
fun testSync() = runBlocking<Unit> {
// val rustBackend = RustBackend.init(context)
val lightwalletService = LightWalletGrpcService(context,"192.168.1.134")
// val compactBlockStore = CompactBlockDbStore(context)
// downloader = CompactBlockDownloader(lightwalletService, compactBlockStore)
// processor = CompactBlockProcessor(downloader, repository, rustBackend)
// repository = PollingTransactionRepository(context, dataDbName, 10_000L)
// wallet = Wallet(
// context,
// rustBackend,
// SampleSeedProvider("dummyseed"),
// SampleSpendingKeyProvider("dummyseed")
// )
// repository.start(this)
// synchronizer = SdkSynchronizer(wallet, repository, , processor)
// processor,
// repository,
// ActiveTransactionManager(repository, lightwalletService, wallet),
// wallet,
// 1000
// ).start(this)
//
// for(i in synchronizer.progress()) {
// twig("made progress: $i")
// }
}
companion object {
// private lateinit var synchronizer: Synchronizer
// private lateinit var repository: PollingTransactionRepository
@AfterClass
fun tearDown() {
// repository.stop()
// synchronizer.stop()
}
}
}

View File

@ -1,55 +0,0 @@
package cash.z.wallet.sdk.db
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.test.core.app.ApplicationProvider
import org.junit.AfterClass
import org.junit.Assert.assertEquals
import org.junit.BeforeClass
import org.junit.Rule
import org.junit.Test
class ManualTransactionSender {
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
@Test
fun sendTransactionViaTest() {
val transaction = transactions.findById(12)
val hex = transaction?.raw?.toHex()
assertEquals("foo", hex)
}
private fun ByteArray.toHex(): String {
val sb = StringBuilder(size * 2)
for (b in this)
sb.append(String.format("%02x", b))
return sb.toString()
}
companion object {
private lateinit var transactions: TransactionDao
private lateinit var db: DerivedDataDb
@BeforeClass
@JvmStatic
fun setup() {
// TODO: put this database in the assets directory and open it from there via .openHelperFactory(new AssetSQLiteOpenHelperFactory()) seen here https://github.com/albertogiunta/sqliteAsset
db = Room
.databaseBuilder(ApplicationProvider.getApplicationContext(), DerivedDataDb::class.java, "wallet_data1202.db")
.setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
.fallbackToDestructiveMigration()
.build()
.apply {
transactions = transactionDao()
}
}
@AfterClass
@JvmStatic
fun close() {
db.close()
}
}
}

View File

@ -1,48 +0,0 @@
package cash.z.wallet.sdk.jni
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Test
class RustBackendTest {
@Test
fun testGetAddress_exists() {
assertNotNull(rustBackend.getAddress(0))
}
@Test
fun testGetAddress_valid() {
val address = rustBackend.getAddress(0)
val expectedAddress = "ztestsapling1snmqdnfqnc407pvqw7sld8w5zxx6nd0523kvlj4jf39uvxvh7vn0hs3q38n07806dwwecqwke3t"
assertEquals("Invalid address", expectedAddress, address)
}
@Test
fun testScanBlocks() {
rustBackend.initDataDb()
rustBackend.initAccountsTable("dummyseed".toByteArray(), 1)
// note: for this to work, the db file below must be uploaded to the device. Eventually, this test will be self-contained and remove that requirement.
val result = rustBackend.scanBlocks()
// Thread.sleep(15 * DateUtils.MINUTE_IN_MILLIS)
assertEquals("Invalid number of results", 3, 3)
}
@Test
fun testSend() {
rustBackend.createToAddress(
0,
"dummykey",
"ztestsapling1fg82ar8y8whjfd52l0xcq0w3n7nn7cask2scp9rp27njeurr72ychvud57s9tu90fdqgwdt07lg",
210_000
)
}
companion object {
val rustBackend: RustBackendWelding = RustBackend.init(ApplicationProvider.getApplicationContext() as Context, "rustTest")
}
}

View File

@ -1,20 +0,0 @@
package cash.z.wallet.sdk.db
import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import cash.z.wallet.sdk.secure.Wallet
import org.junit.Assert.assertEquals
import org.junit.Test
class WalletTest {
val context: Context = InstrumentationRegistry.getInstrumentation().context
@Test
fun testLoadDefaultWallet() {
val birthday = Wallet.loadBirthdayFromAssets(context, 280000)
assertEquals("Invalid tree", "000000", birthday.tree)
assertEquals("Invalid height", 280000, birthday.height)
assertEquals("Invalid time", 1535262293, birthday.time)
}
}

View File

@ -37,7 +37,7 @@ class IntegrationTest {
@Test
fun testLoadBirthday() {
val (height, hash, time, tree) = Initializer.loadBirthdayFromAssets(context, ZcashSdk.SAPLING_ACTIVATION_HEIGHT)
val (height, hash, time, tree) = Initializer.loadBirthdayFromAssets(context, ZcashSdk.SAPLING_ACTIVATION_HEIGHT + 1)
assertEquals(ZcashSdk.SAPLING_ACTIVATION_HEIGHT, height)
}
@ -105,6 +105,7 @@ class IntegrationTest {
private val synchronizer: Synchronizer = Synchronizer(
context,
host,
443,
seed
)

View File

@ -31,6 +31,11 @@ import kotlin.math.roundToInt
* Responsible for processing the compact blocks that are received from the lightwallet server. This class encapsulates
* all the business logic required to validate and scan the blockchain and is therefore tightly coupled with
* librustzcash.
*
* @param minimumHeight the lowest height that we could care about. This is mostly used during
* reorgs as a backstop to make sure we do not rewind beyond sapling activation. It also is factored
* in when considering initial range to download. In most cases, this should be the birthday height
* of the current wallet--the height before which we do not need to scan for transactions.
*/
@OpenForTesting
class CompactBlockProcessor(
@ -76,7 +81,7 @@ class CompactBlockProcessor(
consecutiveChainErrors.getAndIncrement()
}
}
} while (isActive && _state.value !is Stopped)
} while (isActive && !_state.isClosedForSend && _state.value !is Stopped)
twig("processor complete")
stop()
}

View File

@ -1,22 +1,20 @@
package cash.z.wallet.sdk.block
import cash.z.wallet.sdk.transaction.TransactionRepository
import cash.z.wallet.sdk.entity.CompactBlockEntity
import cash.z.wallet.sdk.ext.TroubleshootingTwig
import cash.z.wallet.sdk.ext.Twig
import cash.z.wallet.sdk.entity.CompactBlock
import cash.z.wallet.sdk.entity.CompactBlockEntity
import cash.z.wallet.sdk.ext.SAPLING_ACTIVATION_HEIGHT
import cash.z.wallet.sdk.ext.ZcashSdk.SAPLING_ACTIVATION_HEIGHT
import cash.z.wallet.sdk.jni.RustBackendWelding
import cash.z.wallet.sdk.ext.twig
import cash.z.wallet.sdk.jni.RustBackend
import cash.z.wallet.sdk.service.LightWalletService
import cash.z.wallet.sdk.transaction.TransactionRepository
import com.nhaarman.mockitokotlin2.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.AfterEach
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collect
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.Mock
import org.mockito.junit.jupiter.MockitoExtension
@ -25,12 +23,10 @@ import org.mockito.quality.Strictness
@ExtendWith(MockitoExtension::class)
@MockitoSettings(strictness = Strictness.LENIENT)
internal class CompactBlockProcessorTest {
private val frequency = 5L
class CompactBlockProcessorTest {
// Mocks/Spys
@Mock lateinit var rustBackend: RustBackendWelding
@Mock lateinit var rustBackend: RustBackend
lateinit var processor: CompactBlockProcessor
// Test variables
@ -41,14 +37,13 @@ internal class CompactBlockProcessorTest {
@BeforeEach
fun setUp(
@Mock lightwalletService: LightWalletService,
@Mock compactBlockStore: CompactBlockStore,
@Mock repository: TransactionRepository
@Mock lightwalletService: LightWalletService,
@Mock compactBlockStore: CompactBlockStore,
@Mock repository: TransactionRepository
) {
Twig.plant(TroubleshootingTwig())
lightwalletService.stub {
lightwalletService.stub {
onBlocking {
getBlockRange(any())
}.thenAnswer { invocation ->
@ -90,66 +85,72 @@ internal class CompactBlockProcessorTest {
}.thenAnswer { lastScannedHeight }
}
val config = ProcessorConfig(retries = 1, blockPollFrequencyMillis = frequency, downloadBatchSize = 50_000)
val downloader = spy(CompactBlockDownloader(lightwalletService, compactBlockStore))
processor = spy(CompactBlockProcessor(config, downloader, repository, rustBackend))
processor = spy(CompactBlockProcessor(downloader, repository, rustBackend))
whenever(rustBackend.validateCombinedChain(any(), any())).thenAnswer {
whenever(rustBackend.validateCombinedChain()).thenAnswer {
errorBlock
}
whenever(rustBackend.scanBlocks(any(), any())).thenAnswer {
whenever(rustBackend.scanBlocks()).thenAnswer {
true
}
}
@AfterEach
fun tearDown() {
}
@Test
@Timeout(5)
fun `check for OBOE when downloading`() = runBlocking {
// if the last block downloaded was 350_000, then we already have that block and should start with 350_001
lastDownloadedHeight = 350_000
lastDownloadedHeight = 550_000
processBlocks()
verify(processor).downloadNewBlocks(350_001..latestBlockHeight)
processBlocksThen {
verify(processor).downloadNewBlocks(350_001..latestBlockHeight)
}
}
@Test
@Timeout(5)
fun `chain error rewinds by expected amount`() = runBlocking {
// if the highest block whose prevHash doesn't match happens at block 300_010
errorBlock = 300_010
errorBlock = 500_010
// then we should rewind the default (10) blocks
val expectedBlock = errorBlock - processor.config.rewindDistance
processBlocks(100L)
verify(processor.downloader, atLeastOnce()).rewindToHeight(expectedBlock)
verify(rustBackend, atLeastOnce()).rewindToHeight("", expectedBlock)
assertNotNull(processor)
val expectedBlock = errorBlock - 10
processBlocksThen {
twig("FINISHED PROCESSING!")
verify(processor.downloader, atLeastOnce()).rewindToHeight(expectedBlock)
verify(rustBackend, atLeastOnce()).rewindToHeight(expectedBlock)
assertNotNull(processor)
}
}
@Test
// @Timeout(5)
fun `chain error downloads expected number of blocks`() = runBlocking {
// if the highest block whose prevHash doesn't match happens at block 300_010
// and our rewind distance is the default (10), then we want to download exactly ten blocks
errorBlock = 300_010
errorBlock = 500_010
// plus 1 because the range is inclusive
val expectedRange = (errorBlock - processor.config.rewindDistance + 1)..latestBlockHeight
processBlocks(1500L)
verify(processor, atLeastOnce()).downloadNewBlocks(expectedRange)
}
private fun processBlocks(delayMillis: Long? = null) = runBlocking {
launch { processor.start() }
val progressChannel = processor.progress()
for (i in progressChannel) {
if(i >= 100) {
if(delayMillis != null) delay(delayMillis)
processor.stop()
break
}
val expectedRange = (errorBlock - 10 + 1)..latestBlockHeight
processBlocksThen {
verify(processor, atLeastOnce()).downloadNewBlocks(expectedRange)
}
}
// TODO: fix the fact that flows cause this not to work as originally coded. With a channel, we can stop observing once we reach 100. A flow makes that more difficult. The SDK behavior is still the same but testing that behavior is a little tricky without some refactors.
private suspend fun processBlocksThen(block: suspend () -> Unit) = runBlocking {
val scope = this
launch {
processor.start()
}
processor.progress.collect { i ->
if(i >= 100) {
block()
processor.stop()
}
twig("processed $i")
}
twig("Done processing!")
}
}

View File

@ -1,225 +1,225 @@
package cash.z.wallet.sdk.transaction
import cash.z.wallet.sdk.dao.ClearedTransaction
import kotlinx.coroutines.*
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.*
import kotlin.random.Random
import kotlin.random.nextLong
import kotlin.system.measureTimeMillis
//import cash.z.wallet.sdk.dao.ClearedTransaction
//import kotlinx.coroutines.*
//import org.junit.jupiter.api.AfterEach
//import org.junit.jupiter.api.BeforeEach
//import org.junit.jupiter.api.Test
//
//import org.junit.jupiter.api.Assertions.*
//import kotlin.random.Random
//import kotlin.random.nextLong
//import kotlin.system.measureTimeMillis
internal class MockSynchronizerTest {
private val transactionInterval = 200L
private val activeTransactionInterval = 200L
private val synchronizer = MockSynchronizer(transactionInterval, activeTransactionInterval)
private val fastSynchronizer = MockSynchronizer(2L, 2L)
private val allTransactionChannel = synchronizer.allTransactions()
private val validAddress = "ztestsapling1yu2zy9aane2pje2qvm4qmn4k6q57y2d9ecs5vz0guthxx3m2aq57qm6hkx0520m9u9635xh6ttd"
@BeforeEach
fun setUp() {
synchronizer.start(CoroutineScope(Dispatchers.IO))
}
@AfterEach
fun tearDown() {
synchronizer.stop()
}
@Test
fun allTransactions() = runBlocking {
var total = 0
val duration = measureTimeMillis {
repeat(10) {
val transactions = allTransactionChannel.receive()
total += transactions.size
println("received ${transactions.size} transactions")
}
}
assertTrue(total > 0)
assertTrue(duration > transactionInterval)
}
@Test
fun `never calling send yields zero sent transactions`() = runBlocking {
val fastChannel = fastSynchronizer.start(fastSynchronizer).allTransactions()
var transactions = fastChannel.receive()
repeat(10_000) {
transactions = fastChannel.receive()
}
assertTrue(transactions.size > 0, "no transactions created at all")
assertTrue(transactions.none { it.isSend })
}
@Test
fun `send - each call to send generates exactly one sent transaction`() = runBlocking {
val fastChannel = fastSynchronizer.start(fastSynchronizer).allTransactions()
var transactions = fastChannel.receive()
repeat(10_000) {
if (it.rem(2_000) == 0) {
fastSynchronizer.sendToAddress(10, validAddress); println("yep")
}
transactions = fastChannel.receive()
}
assertEquals(5, transactions.count { it.isSend })
}
@Test
fun `send - triggers an active transaction`() = runBlocking {
synchronizer.sendToAddress(10, validAddress)
delay(500L)
assertNotNull(synchronizer.activeTransactions().receiveOrNull())
synchronizer.stop()
}
@Test
fun `send - results in success`() = runBlocking {
synchronizer.sendToAddress(10, validAddress)
delay(500L)
val result = synchronizer.activeTransactions().receive()
assertTrue(result.isNotEmpty(), "result was empty")
assertTrue(TransactionState.AwaitingConfirmations(0).order <= result.values.first().order)
assertTrue((result.keys.first() as ActiveSendTransaction).transactionId.get() != -1L, "transactionId missing")
assertTrue((result.keys.first() as ActiveSendTransaction).height.get() != -1, "height missing")
synchronizer.stop()
}
@Test
fun `send - results in mined transaction`() = runBlocking {
synchronizer.sendToAddress(10, validAddress)
delay(500L)
val result = synchronizer.activeTransactions().receive()
assertTrue(result.isNotEmpty(), "result was empty")
assertTrue(TransactionState.AwaitingConfirmations(0).order <= result.values.first().order)
synchronizer.stop()
}
@Test
fun `send - a bad address fails`() = runBlocking {
synchronizer.sendToAddress(10, "fail")
delay(500L)
val result = synchronizer.activeTransactions().receive()
assertTrue(result.isNotEmpty(), "result was empty")
assertTrue(0 > result.values.first().order)
synchronizer.stop()
}
@Test
fun `send - a short address fails`() = runBlocking {
// one character too short
val toAddress = "ztestsapling1yu2zy9aane2pje2qvm4qmn4k6q57y2d9ecs5vz0guthxx3m2aq57qm6hkx0520m9u9635xh6tt"
assertTrue(toAddress.length < 88, "sample address wasn't short enough (${toAddress.length})")
synchronizer.sendToAddress(10, toAddress)
delay(500L)
val result = synchronizer.activeTransactions().receive()
assertTrue(result.isNotEmpty(), "result was empty")
assertTrue(0 > result.values.first().order,
"result should have been a failure but was ${result.values.first()::class.simpleName}")
}
@Test
fun `send - a non-z prefix address fails`() = runBlocking {
// one character too short
val toAddress = "atestsapling1yu2zy9aane2pje2qvm4qmn4k6q57y2d9ecs5vz0guthxx3m2aq57qm6hkx0520m9u9635xh6ttd"
assertTrue(toAddress.length == 88,
"sample address was not the proper length (${toAddress.length}")
assertFalse(toAddress.startsWith('z'),
"sample address should not start with z")
synchronizer.sendToAddress(10, toAddress)
delay(500L)
val result = synchronizer.activeTransactions().receive()
assertTrue(result.isNotEmpty(), "result was empty")
assertTrue(0 > result.values.first().order,
"result should have been a failure but was ${result.values.first()::class.simpleName}")
}
@Test
fun `balance matches transactions without sends`() = runBlocking {
val balances = fastSynchronizer.start(fastSynchronizer).balances()
var transactions = listOf<ClearedTransaction>()
while (transactions.count() < 10) {
transactions = fastSynchronizer.allTransactions().receive()
println("got ${transactions.count()} transaction(s)")
}
assertEquals(transactions.fold(0L) { acc, tx -> acc + tx.value }, balances.receive())
}
@Test
fun `balance matches transactions with sends`() = runBlocking {
var transactions = listOf<ClearedTransaction>()
val balances = fastSynchronizer.start(fastSynchronizer).balances()
val transactionChannel = fastSynchronizer.allTransactions()
while (transactions.count() < 10) {
fastSynchronizer.sendToAddress(Random.nextLong(1L..10_000_000_000), validAddress)
transactions = transactionChannel.receive()
println("got ${transactions.count()} transaction(s)")
}
val transactionsSnapshot = transactionChannel.receive()
val balanceSnapshot = balances.receive()
val positiveValue = transactionsSnapshot.fold(0L) { acc, tx -> acc + (if (tx.isSend) 0 else tx.value) }
val negativeValue = transactionsSnapshot.fold(0L) { acc, tx -> acc + (if(!tx.isSend) 0 else tx.value) }
assertEquals(positiveValue - negativeValue, balanceSnapshot, "incorrect balance. negative balance: $negativeValue positive balance: $positiveValue")
}
@Test
fun `progress hits 100`() = runBlocking {
var channel = synchronizer.progress()
var now = System.currentTimeMillis()
var delta = 0L
val expectedUpperBounds = transactionInterval * 10
while (channel.receive() < 100) {
delta = now - System.currentTimeMillis()
if (delta > expectedUpperBounds) break
}
assertTrue(delta < expectedUpperBounds, "progress did not hit 100 within the expected time of $expectedUpperBounds")
}
@Test
fun `is out of sync about 10% of the time`() = runBlocking {
var count = 0
repeat(100_000) {
if (synchronizer.isStale()) count++
}
assertTrue(count < 11_000, "a count of $count is too frequent")
assertTrue(count > 9_000, "a count of $count is too infrequent")
}
@Test
fun isFirstRun() {
}
@Test
fun cancelSend() = runBlocking {
val activeTransactions = synchronizer.activeTransactions()
// verify that send creates one transaction
launch {
synchronizer.sendToAddress(10, validAddress)
}
println("done sending to address")
delay(300L)
var actives = activeTransactions.receiveOrNull()
assertEquals(1, actives?.size)
assertTrue((actives?.values?.first()?.order ?: 0) > -1, "expected positive order but was ${actives?.values?.first()?.order}")
val transaction = actives?.keys?.first() as? ActiveSendTransaction
assertNotNull(transaction)
// and then verify that cancel changes its status
synchronizer.cancelSend(transaction!!)
delay(100L) // look for ignored state change
actives = activeTransactions.receiveOrNull()
assertNotNull(actives, "cancel changed nothing in 100ms")
assertEquals(1, actives!!.size, "unexpected number of active transactions ${actives.size}")
val finalState = actives!!.values.first()
assertNotNull(finalState as? TransactionState.Cancelled, "transaction was ${finalState::class.simpleName} instead of cancelled for ${actives.keys.first()}")
println("donso")
synchronizer.stop()
}
// private val transactionInterval = 200L
// private val activeTransactionInterval = 200L
// private val synchronizer = MockSynchronizer(transactionInterval, activeTransactionInterval)
// private val fastSynchronizer = MockSynchronizer(2L, 2L)
// private val allTransactionChannel = synchronizer.allTransactions()
// private val validAddress = "ztestsapling1yu2zy9aane2pje2qvm4qmn4k6q57y2d9ecs5vz0guthxx3m2aq57qm6hkx0520m9u9635xh6ttd"
//
// @BeforeEach
// fun setUp() {
// synchronizer.start(CoroutineScope(Dispatchers.IO))
// }
//
// @AfterEach
// fun tearDown() {
// synchronizer.stop()
// }
//
// @Test
// fun allTransactions() = runBlocking {
// var total = 0
// val duration = measureTimeMillis {
// repeat(10) {
// val transactions = allTransactionChannel.receive()
// total += transactions.size
// println("received ${transactions.size} transactions")
// }
// }
// assertTrue(total > 0)
// assertTrue(duration > transactionInterval)
// }
//
// @Test
// fun `never calling send yields zero sent transactions`() = runBlocking {
// val fastChannel = fastSynchronizer.start(fastSynchronizer).allTransactions()
// var transactions = fastChannel.receive()
// repeat(10_000) {
// transactions = fastChannel.receive()
// }
// assertTrue(transactions.size > 0, "no transactions created at all")
// assertTrue(transactions.none { it.isSend })
// }
//
// @Test
// fun `send - each call to send generates exactly one sent transaction`() = runBlocking {
// val fastChannel = fastSynchronizer.start(fastSynchronizer).allTransactions()
// var transactions = fastChannel.receive()
// repeat(10_000) {
// if (it.rem(2_000) == 0) {
// fastSynchronizer.sendToAddress(10, validAddress); println("yep")
// }
// transactions = fastChannel.receive()
// }
// assertEquals(5, transactions.count { it.isSend })
// }
//
// @Test
// fun `send - triggers an active transaction`() = runBlocking {
// synchronizer.sendToAddress(10, validAddress)
// delay(500L)
// assertNotNull(synchronizer.activeTransactions().receiveOrNull())
// synchronizer.stop()
// }
//
// @Test
// fun `send - results in success`() = runBlocking {
// synchronizer.sendToAddress(10, validAddress)
// delay(500L)
// val result = synchronizer.activeTransactions().receive()
// assertTrue(result.isNotEmpty(), "result was empty")
// assertTrue(TransactionState.AwaitingConfirmations(0).order <= result.values.first().order)
// assertTrue((result.keys.first() as ActiveSendTransaction).transactionId.get() != -1L, "transactionId missing")
// assertTrue((result.keys.first() as ActiveSendTransaction).height.get() != -1, "height missing")
// synchronizer.stop()
// }
//
// @Test
// fun `send - results in mined transaction`() = runBlocking {
// synchronizer.sendToAddress(10, validAddress)
// delay(500L)
// val result = synchronizer.activeTransactions().receive()
// assertTrue(result.isNotEmpty(), "result was empty")
// assertTrue(TransactionState.AwaitingConfirmations(0).order <= result.values.first().order)
// synchronizer.stop()
// }
//
// @Test
// fun `send - a bad address fails`() = runBlocking {
// synchronizer.sendToAddress(10, "fail")
// delay(500L)
// val result = synchronizer.activeTransactions().receive()
// assertTrue(result.isNotEmpty(), "result was empty")
// assertTrue(0 > result.values.first().order)
// synchronizer.stop()
// }
//
// @Test
// fun `send - a short address fails`() = runBlocking {
// // one character too short
// val toAddress = "ztestsapling1yu2zy9aane2pje2qvm4qmn4k6q57y2d9ecs5vz0guthxx3m2aq57qm6hkx0520m9u9635xh6tt"
// assertTrue(toAddress.length < 88, "sample address wasn't short enough (${toAddress.length})")
//
// synchronizer.sendToAddress(10, toAddress)
// delay(500L)
// val result = synchronizer.activeTransactions().receive()
// assertTrue(result.isNotEmpty(), "result was empty")
// assertTrue(0 > result.values.first().order,
// "result should have been a failure but was ${result.values.first()::class.simpleName}")
// }
//
// @Test
// fun `send - a non-z prefix address fails`() = runBlocking {
// // one character too short
// val toAddress = "atestsapling1yu2zy9aane2pje2qvm4qmn4k6q57y2d9ecs5vz0guthxx3m2aq57qm6hkx0520m9u9635xh6ttd"
// assertTrue(toAddress.length == 88,
// "sample address was not the proper length (${toAddress.length}")
// assertFalse(toAddress.startsWith('z'),
// "sample address should not start with z")
//
// synchronizer.sendToAddress(10, toAddress)
// delay(500L)
// val result = synchronizer.activeTransactions().receive()
// assertTrue(result.isNotEmpty(), "result was empty")
// assertTrue(0 > result.values.first().order,
// "result should have been a failure but was ${result.values.first()::class.simpleName}")
// }
//
// @Test
// fun `balance matches transactions without sends`() = runBlocking {
// val balances = fastSynchronizer.start(fastSynchronizer).balances()
// var transactions = listOf<ClearedTransaction>()
// while (transactions.count() < 10) {
// transactions = fastSynchronizer.allTransactions().receive()
// println("got ${transactions.count()} transaction(s)")
// }
// assertEquals(transactions.fold(0L) { acc, tx -> acc + tx.value }, balances.receive())
// }
//
// @Test
// fun `balance matches transactions with sends`() = runBlocking {
// var transactions = listOf<ClearedTransaction>()
// val balances = fastSynchronizer.start(fastSynchronizer).balances()
// val transactionChannel = fastSynchronizer.allTransactions()
// while (transactions.count() < 10) {
// fastSynchronizer.sendToAddress(Random.nextLong(1L..10_000_000_000), validAddress)
// transactions = transactionChannel.receive()
// println("got ${transactions.count()} transaction(s)")
// }
// val transactionsSnapshot = transactionChannel.receive()
// val balanceSnapshot = balances.receive()
//
// val positiveValue = transactionsSnapshot.fold(0L) { acc, tx -> acc + (if (tx.isSend) 0 else tx.value) }
// val negativeValue = transactionsSnapshot.fold(0L) { acc, tx -> acc + (if(!tx.isSend) 0 else tx.value) }
// assertEquals(positiveValue - negativeValue, balanceSnapshot, "incorrect balance. negative balance: $negativeValue positive balance: $positiveValue")
// }
//
// @Test
// fun `progress hits 100`() = runBlocking {
// var channel = synchronizer.progress()
// var now = System.currentTimeMillis()
// var delta = 0L
// val expectedUpperBounds = transactionInterval * 10
// while (channel.receive() < 100) {
// delta = now - System.currentTimeMillis()
// if (delta > expectedUpperBounds) break
// }
// assertTrue(delta < expectedUpperBounds, "progress did not hit 100 within the expected time of $expectedUpperBounds")
// }
//
// @Test
// fun `is out of sync about 10% of the time`() = runBlocking {
// var count = 0
// repeat(100_000) {
// if (synchronizer.isStale()) count++
// }
// assertTrue(count < 11_000, "a count of $count is too frequent")
// assertTrue(count > 9_000, "a count of $count is too infrequent")
// }
//
// @Test
// fun isFirstRun() {
// }
//
// @Test
// fun cancelSend() = runBlocking {
// val activeTransactions = synchronizer.activeTransactions()
//
// // verify that send creates one transaction
// launch {
// synchronizer.sendToAddress(10, validAddress)
// }
// println("done sending to address")
// delay(300L)
// var actives = activeTransactions.receiveOrNull()
// assertEquals(1, actives?.size)
// assertTrue((actives?.values?.first()?.order ?: 0) > -1, "expected positive order but was ${actives?.values?.first()?.order}")
// val transaction = actives?.keys?.first() as? ActiveSendTransaction
// assertNotNull(transaction)
//
// // and then verify that cancel changes its status
// synchronizer.cancelSend(transaction!!)
// delay(100L) // look for ignored state change
// actives = activeTransactions.receiveOrNull()
// assertNotNull(actives, "cancel changed nothing in 100ms")
// assertEquals(1, actives!!.size, "unexpected number of active transactions ${actives.size}")
// val finalState = actives!!.values.first()
// assertNotNull(finalState as? TransactionState.Cancelled, "transaction was ${finalState::class.simpleName} instead of cancelled for ${actives.keys.first()}")
// println("donso")
// synchronizer.stop()
// }
}