Test whether call is done in parallel - kotlin-coroutines

Code:
object ParallelCall {
suspend fun <T, V> Iterable<T>.runInParallel(method: suspend (T) -> V): Iterable<V> {
//If List is empty then return empty List.
//Else, call the given method for each element in the list in parallel and return the list.
}
}
I have below unit test:
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Test
import kotlinx.coroutines.delay
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import kotlin.test.assertTrue
import kotlin.test.assertEquals
open class ParallelTest {
#Test
fun `test runInParallel with empty list`() {
val spy = spy(this)
val emptyList = emptyList<Int>()
val result = runBlocking {
emptyList.runInParallel {
delay(100)
appendTextToInput()
}
}
assertTrue(result.toList().isEmpty())
verify(spy, never()).appendTextToInput()
}
#Test
fun `test runInParallel is parallel when input is non-empty list`() {
val spy = spy(this)
val inputList = listOf(1, 2, 3, 4, 5)
val startTime = System.currentTimeMillis()
val result = runBlocking {
inputList.runInParallel {
delay(100)
appendTextToInput()
}
}
val endTime = System.currentTimeMillis()
val totalTime = endTime - startTime
assertEquals(inputList.map { appendTextToInput() }, result.toList())
//If calls are executed in parallel the time taken should be < 500 milliseconds (5 * 100 milliseconds)
assertTrue(totalTime < 500)
verify(spy, times(inputList.size)).appendTextToInput()
}
private fun appendTextToInput(): String {
return "asynchronous call"
}
}
The unit test works when running each method separately. If I run the whole test class then one of the test case fails. It looks like the spy is shared among the test cases which is causing the issue in verify because object is a singleton class in Kotlin. How can I fix this?

Related

Kotlin: High-Performance concurrent file I/O best-practices?

What's the most performant way in Kotlin to allow concurrent file I/O in multi-reader, single-writer fashion?
I have the below, but I'm unsure how much overhead is being created by the coroutine facilities:
AsyncFileChannel, with extension functions to use it in a suspend context
Taken from example here: https://github.com/Kotlin/coroutines-examples/blob/master/examples/io/io.kt
DiskManager class, that uses a custom ReadWriteMutex
Searching for examples of this doesn't turn up much (try searching Github for ReadWriteMutex, there are a tiny handful of Kotlin repos implementing this).
class DiskManagerImpl(file: File) : DiskManager {
private val mutex = ReadWriteMutexImpl()
private val channel = AsynchronousFileChannel.open(file.toPath(),
StandardOpenOption.READ, StandardOpenOption.WRITE)
override suspend fun readPage(pageId: PageId, buffer: MemorySegment) = withContext(Dispatchers.IO) {
mutex.withReadLock {
val offset = pageId * PAGE_SIZE
val bytesRead = channel.readAsync(buffer.asByteBuffer(), offset.toLong())
require(bytesRead == PAGE_SIZE) { "Failed to read page $pageId" }
}
}
override suspend fun writePage(pageId: PageId, buffer: MemorySegment) = withContext(Dispatchers.IO) {
mutex.withWriteLock {
val offset = pageId * PAGE_SIZE
val bytesWritten = channel.writeAsync(buffer.asByteBuffer(), offset.toLong())
require(bytesWritten == PAGE_SIZE) { "Failed to write page $pageId" }
}
}
}
class ReadWriteMutexImpl : ReadWriteMutex {
private val read = Mutex()
private val write = Mutex()
private val readers = atomic(0)
override suspend fun lockRead() {
if (readers.getAndIncrement() == 0) {
read.lock()
}
}
override fun unlockRead() {
if (readers.decrementAndGet() == 0) {
read.unlock()
}
}
override suspend fun lockWrite() {
read.lock()
write.lock()
}
override fun unlockWrite() {
write.unlock()
read.unlock()
}
}
suspend inline fun <T> ReadWriteMutex.withReadLock(block: () -> T): T {
lockRead()
return try {
block()
} finally {
unlockRead()
}
}
suspend inline fun <T> ReadWriteMutex.withWriteLock(block: () -> T): T {
lockWrite()
return try {
block()
} finally {
unlockWrite()
}
}

LazyColumn item not updated accordingly while list in room table already updated

When the Icon clicked, viewModel.onLockIconClicked(it) is called to reverse the value of isLock in db.
The Icon is expected to be updated according based on the value of isLock.
I've checked the value did reversed in db table. But LazyColumn not update accordingly.
What did I miss? Thanks a lot!
Ex, initially, Screen: icon = lock and Db: isLock = true,
when Icon clicked, Screen: icon = lock and Db: isLock = false,
while expected is Screen: icon = lock_open and Db: isLock = false.
ListScreen:
#Composable
fun ListScreen(context: Context) {
val viewModel: ListViewModel =
viewModel(factory = ListViewModelFactory(Db.getInstance(context)))
val list by viewModel.list.collectAsState(initial = emptyList())
Scaffold() {
SwipeRefresh(
state = rememberSwipeRefreshState(viewModel.isRefreshing),
onRefresh = { }
) {
LazyColumn(
state = rememberLazyListState(),
) {
items(list) {
Row() {
Icon(
painter = painterResource(if (it.isLock) R.drawable.ic_baseline_lock_24 else R.drawable.ic_baseline_lock_open_24),
contentDescription = null,
modifier = Modifier.clickable() { viewModel.onLockIconClicked(it) }
)
Text(it.code)
}
}
}
}
}
}
ListViewModel:
class ListViewModel(db: Db) : ViewModel() {
private val sumDao = db.sumDao()
val list = sumDao.getAllRows()
var isRefreshing by mutableStateOf(false)
private set
//init
init {
viewModelScope.launch(Dispatchers.IO) {
val initialCodeList = listOf("aaa", "bbb")
for (code in initialCodeList) {
val sum = Sum()
sum.code = code
sumDao.insert(sum)
}
}
}
fun onLockIconClicked(sum: Sum) {
sum.isLock = !sum.isLock
viewModelScope.launch(Dispatchers.IO) {
sumDao.update(sum)
}
}
}
class ListViewModelFactory(private val db: Db) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ListViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return ListViewModel(db) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
Sum:
#Entity(tableName = "sum", primaryKeys = ["code"])
data class Sum(
#ColumnInfo(name = "code")
var code: String = "",
#ColumnInfo(name = "is_lock")
var isLock: Boolean = true
)
SumDao:
#Dao
interface SumDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(sum: Sum): Long
#Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun update(sum: Sum): Int
#Delete
suspend fun delete(sum: Sum): Int
#Query("select * from sum")
fun getAllRows(): Flow<List<Sum>>
}
Db:
#Database(entities = [Sum::class], version = 1, exportSchema = false)
abstract class Db : RoomDatabase() {
abstract fun sumDao(): SumDao
companion object {
#Volatile
private var INSTANCE: Db? = null
fun getInstance(context: Context): Db {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
Db::class.java,
"db"
)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
return instance
}
}
}
}
Consider taking the State-in-Compose for a better understanding of the concepts of state-handling in Compose.
I'm sorry but the information that you have provided is massive, so I can't pinpoint the source of the bug, but here's what you can do for now:
In your Dao class, just replace the words Flow<List<Sum>> with LiveData<List<Sum>>
In your ViewModel, you can get access to the LiveData inside the init like so
var list by mutableStateListOf<Sum>()
init{
sumDao.getAllRows().observeForever{
list = it
}
}
Now, list would ideally be updated every time the value in the databse changes, which infact would trigger recompositions since I am using a direct mutableStateListOf object here.
The problem may lie anywhere:
Since the class Sum is a custom-made class, it may have been experiencing issues triggering recompositions, which is a common problem among new developers, and even some experienced ones nowadays.
Since you are declaring the viewModel inside the Composable, wrong instances of ViewModels may have been passed around, leading to state-inconsistency - always try to declare your viewModels in the top-most layer possibly, i.e., somewhere like the onCreate method of the activity. Fragments are discourages so you should not face any problems over there.
Since you were not actively observing the Flow anywhere, that could have lead to the variable not being updated at all in the ViewModel, which would again lead to UI-inconsistency.

How to convert ByteReadChannel into Flow<ByteBuffer>

How can I convert io.ktor.utils.io.ByteReadChannel into kotlinx.coroutines.flow.Flow<java.nio.ByteBuffer>?
I use Ktor with this routing:
post("/upload") {
val channel: ByteReadChannel = call.receiveChannel()
val flow: Flow<ByteBuffer> = channel.asByteBufferFlow() // my custom extension method
transaction.execute {
testDao.saveFile(flow)
}
call.respond("OK")
}
The DAO uses R2DBC and Blob like this:
override suspend fun saveFile(input: Flow<ByteBuffer>) {
val connection = requireR2DBCTransactionConnection()
val publisher: Publisher<ByteBuffer> = input.asPublisher()
val statement: Statement = connection.createStatement("insert into bindata (data) values ($1)")
statement.bind(0, Blob.from(publisher))
val count: Int = statement.execute().awaitFirst().rowsUpdated.awaitFirst()
if (count != 1) {
throw IllegalStateException()
}
}
I tried to write this extension method but I failed:
fun ByteReadChannel.asByteBufferFlow(): Flow<ByteBuffer> = object : AbstractFlow<ByteBuffer>() {
override suspend fun collectSafely(collector: FlowCollector<ByteBuffer>) {
/* I have no idea */
}
}
My main problem is that I have not found any similar sample and both ByteBuffer and ByteReadChannel is new for me.

Android Room Database Drop table UI Test

I drop the table and want to test with it.
MIGRATION_1_2 does drop 'b' table which is associated with BDao. So AppDatabase can't get BDao instance. Also, B::class is removed from entities.
#Database(entities = [A::class/*, B::class*/], version = 2)
abstract class AppDatabase : RoomDatabse() {
abstract aDao: ADao
// abstract bDao: BDao
companion object {
fun getDatabase(context: Context): AppDatabse {
...
Room.databaseBuilder(context.applicationContext,
AppDatabase::class.java, DATABASE_NAME)
.addMigrations(MIGRATION_1_2)
.build()
...
}
val MIGRATION_1_2 = object Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("""
DROP TABLE 'b'
""")
}
}
}
}
Below is the test code. I can get A Dao but can't get B Dao. How to verify b table is dropped?
#RunWith(AndroidJUnit4::class)
class MigrationTest {
#Rule
#JvmField
val helper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java.canonicalName,
FrameworkSQLiteOpenHelperFactory()
)
#Test
fun migrate1To2() {
val db = helper.createDatabase(TEST_DB, 1)
insertAData(db)
isnertBData(db)
db.close()
helper.runMigrationsAndValidate(TEST_DB, 2, true, AppDatabase.MIGRATION_1_2)
helper.closeWhenFinished(database)
// I can test with A.
val adao = database.aDao()
// But I can't test with B.
// val bdao = database.bDao()
}
}
I write the method referenced from https://stackoverflow.com/a/7863401/2423899
private fun AppDatabase.isTableExisting(tableName: String): Boolean {
assert(isOpen)
val query = SupportSQLiteQueryBuilder
.builder("sqlite_master")
.distinct()
.columns(arrayOf("tbl_name"))
.selection("tbl_name=?", arrayOf(tableName))
.create()
val cursor = query(query)
val count = cursor.count > 0
cursor.close()
return count
}
Below code is verifying b table is dropped.
assertThat(database.isTableExsiting("b"), `is`(false))

Cannot call a function from init block because of val property

I'd like to initialize my class's properties.
Because I'm using heavily the functional elements of Kotlin, I'd like to put these initializations to well named functions, to increase readability of my code.
The problem is that I cannot assign a val property, if the code is not in the init block, but in function which is called from the init block.
Is it possible to take apart initialization of a class, to different functions, if the properties are vals?
Here is the code:
val socket: DatagramSocket = DatagramSocket()
val data: ByteArray = "Cassiopeiae server discovery packet".toByteArray()
val broadcastAddresses: List<InetAddress>
init {
socket.broadcast = true
val interfaceAddresses = ArrayList<InterfaceAddress>()
collectValidNetworkInterfaces(interfaceAddresses)
collectBroadcastAddresses(interfaceAddresses)
}
private fun collectValidNetworkInterfaces(interfaceAddresses: ArrayList<InterfaceAddress>) {
NetworkInterface.getNetworkInterfaces().toList()
.filter { validInterface(it) }
.forEach { nInterface -> nInterface.interfaceAddresses.toCollection(interfaceAddresses) }
}
private fun collectBroadcastAddresses(interfaceAddresses: ArrayList<InterfaceAddress>) {
broadcastAddresses = interfaceAddresses
.filter { address -> address.broadcast != null }
.map { it.broadcast }
}
Of course it's not compiling, because collectBroadcastAddresses function tries to reassign the broadcastAddresses val. Although I don't want to put the code of this function to the init block, because it's not obvious what the code is doing, and the function name tells it very nicely.
What can I do in such cases? I'd like to keep my code clean, this is the most important point!
One way of approaching the problem is to use pure functions to initialize fields:
class Operation {
val socket = DatagramSocket().apply { broadcast = true }
val data: ByteArray = "Cassiopeiae server discovery packet".toByteArray()
val broadcastAddresses = collectBroadcastAddresses(collectValidNetworkInterfaces())
private fun collectValidNetworkInterfaces() =
NetworkInterface.getNetworkInterfaces().toList()
.filter { validInterface(it) }
.flatMap { nInterface -> nInterface.interfaceAddresses }
private fun validInterface(it: NetworkInterface?) = true
private fun collectBroadcastAddresses(interfaceAddresses: List<InterfaceAddress>) {
interfaceAddresses
.filter { address -> address.broadcast != null }
.map { it.broadcast }
}
}
Notice how the socket field initialization uses apply extension.
I often find it useful to extract collection manipulation routines into extension methods:
class Operation {
val socket = DatagramSocket().apply { broadcast = true }
val data: ByteArray = "Cassiopeiae server discovery packet".toByteArray()
val broadcastAddresses = NetworkInterface.getNetworkInterfaces()
.collectValidNetworkInterfaces { validInterface(it) }
.collectBroadcastAddresses()
private fun validInterface(it: NetworkInterface?) = true
}
fun Iterable<InterfaceAddress>.collectBroadcastAddresses(): List<InetAddress> =
filter { address -> address.broadcast != null }.map { it.broadcast }
fun Enumeration<NetworkInterface>.collectValidNetworkInterfaces(isValid: (NetworkInterface) -> Boolean = { true }) =
toList()
.filter { isValid(it) }
.flatMap { nInterface -> nInterface.interfaceAddresses }

Resources