-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
92a87f9
commit ce45bcd
Showing
1 changed file
with
73 additions
and
6 deletions.
There are no files selected for viewing
79 changes: 73 additions & 6 deletions
79
terpal-sql-native/src/commonTest/kotlin/io/exoquery/sql/native/LruCacheSpec.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,77 @@ | ||
package io.exoquery.sql.native | ||
|
||
// TODO | ||
import app.cash.sqldelight.internal.currentThreadId | ||
import io.exoquery.sql.encodingdata.shouldBe | ||
import io.exoquery.sql.sqlite.LruCache | ||
import kotlinx.coroutines.CompletableDeferred | ||
import kotlinx.coroutines.launch | ||
import kotlinx.coroutines.runBlocking | ||
import kotlin.test.Test | ||
import kotlin.test.assertEquals | ||
|
||
class LruCacheSpec { | ||
// ------------ This should cover our current use-case of the cache ------------ | ||
// make a reference map of 1M values (immutable) | ||
// launch 1000 tasks to put 1000 random key/values each | ||
// make sure all the values are right | ||
// (check that evications have only happened due to a trim, never a new-value since the initial values are static) | ||
|
||
data class RemoveData(val wasEvicted: Boolean, val key: String, val oldValue: String, val newValue: String?) | ||
|
||
fun createCacheAndRemovals(cacheSize: Int): Pair<LruCache<String, String>, MutableList<RemoveData>> { | ||
val removals = mutableListOf<RemoveData>() | ||
val cache = LruCache<String, String>(cacheSize) { wasEvicted, key, oldValue, newValue -> removals.add(RemoveData(wasEvicted, key, oldValue, newValue)) } | ||
return Pair(cache, removals) | ||
} | ||
|
||
@Test | ||
fun testSimple() { | ||
val (cache, removals) = createCacheAndRemovals(2) | ||
cache.put("a", "1") | ||
cache.put("b", "2") | ||
cache.put("c", "3") | ||
cache.get("a") shouldBe null | ||
cache.get("b") shouldBe "2" | ||
cache.get("c") shouldBe "3" | ||
removals shouldBe listOf(RemoveData(true, "a", "1", null)) | ||
} | ||
|
||
@Test | ||
fun testEviction() { | ||
val (cache, removals) = createCacheAndRemovals(3) | ||
cache.put("a", "1") | ||
cache.put("b", "2") | ||
cache.put("a", "11") | ||
cache.put("c", "3") | ||
cache.get("a") shouldBe "11" | ||
cache.get("b") shouldBe "2" | ||
cache.get("c") shouldBe "3" | ||
cache.get("other") shouldBe null | ||
// Note that should be false because the value was updated, not evicted | ||
removals shouldBe listOf(RemoveData(false, "a", "1", "11")) | ||
} | ||
|
||
|
||
@Test | ||
fun testLRUCacheEvictionInMultithreadedCode(): Unit = runBlocking { | ||
val cacheSize = 100 | ||
val (lruCache, removeList) = createCacheAndRemovals(cacheSize) | ||
val countDownLatch = CompletableDeferred<Unit>() | ||
|
||
// Launch multiple coroutines to simulate concurrent cache operations | ||
val jobs = (0 until cacheSize).map { key -> | ||
val newKey = "value$key" | ||
launch { | ||
//println("Thread:[${currentThreadId()}], Key=[$newKey], Val=[$key]") | ||
lruCache.put(newKey, "$key") | ||
} | ||
} | ||
|
||
// Wait for all coroutines to finish | ||
jobs.forEach { it.join() } | ||
|
||
// Ensure that all keys inserted can be retrieved | ||
(0 until cacheSize).forEach { i -> | ||
val expectedValue = lruCache.get("value$i") | ||
assertEquals("$i", expectedValue, "Expected $i but got $expectedValue for key value$i") | ||
} | ||
|
||
countDownLatch.complete(Unit) // Signal that the test has finished | ||
} | ||
|
||
} |