|
1 | 1 | package org.mobilenativefoundation.store.store5
|
2 | 2 |
|
3 |
| -import kotlinx.coroutines.CoroutineScope |
4 |
| -import kotlinx.coroutines.Dispatchers |
5 | 3 | import kotlinx.coroutines.ExperimentalCoroutinesApi
|
6 | 4 | import kotlinx.coroutines.FlowPreview
|
7 | 5 | import kotlinx.coroutines.Job
|
8 |
| -import kotlinx.coroutines.async |
9 |
| -import kotlinx.coroutines.awaitAll |
10 |
| -import kotlinx.coroutines.cancel |
11 |
| -import kotlinx.coroutines.flow.* |
| 6 | +import kotlinx.coroutines.flow.first |
| 7 | +import kotlinx.coroutines.flow.flowOf |
| 8 | +import kotlinx.coroutines.flow.launchIn |
| 9 | +import kotlinx.coroutines.flow.mapNotNull |
12 | 10 | import kotlinx.coroutines.test.TestScope
|
13 | 11 | import kotlinx.coroutines.test.runTest
|
| 12 | +import org.mobilenativefoundation.store.core5.ExperimentalStoreApi |
14 | 13 | import org.mobilenativefoundation.store.store5.impl.extensions.get
|
15 | 14 | import kotlin.test.Test
|
16 | 15 | import kotlin.test.assertEquals
|
17 |
| -import kotlin.test.assertIs |
18 |
| -import kotlin.test.assertNotNull |
19 | 16 | import kotlin.time.Duration.Companion.hours
|
20 | 17 |
|
| 18 | +@OptIn(ExperimentalStoreApi::class) |
21 | 19 | @FlowPreview
|
22 | 20 | @ExperimentalCoroutinesApi
|
23 | 21 | class StoreWithInMemoryCacheTests {
|
@@ -51,82 +49,86 @@ class StoreWithInMemoryCacheTests {
|
51 | 49 |
|
52 | 50 | @Test
|
53 | 51 | fun storeDeadlock() =
|
54 |
| - testScope.runTest { |
55 |
| - repeat(1000) { |
56 |
| - val store = |
| 52 | + runTest { |
| 53 | + repeat(100) { |
| 54 | + val store: MutableStore<Int, String> = |
57 | 55 | StoreBuilder
|
58 | 56 | .from(
|
59 |
| - fetcher = Fetcher.of { key: Int -> "fetcher_${key}" }, |
60 |
| - sourceOfTruth = SourceOfTruth.Companion.of( |
61 |
| - reader = { key -> |
62 |
| - flow<String> { |
63 |
| - emit("source_of_truth_${key}") |
64 |
| - } |
65 |
| - }, |
66 |
| - writer = { key: Int, local: String -> |
67 |
| - |
68 |
| - } |
69 |
| - ) |
| 57 | + fetcher = Fetcher.of { key: Int -> "fetcher_$key" }, |
| 58 | + sourceOfTruth = |
| 59 | + SourceOfTruth.of( |
| 60 | + reader = { key: Int -> |
| 61 | + flowOf("source_of_truth_$key") |
| 62 | + }, |
| 63 | + writer = { key: Int, local: String -> }, |
| 64 | + ), |
70 | 65 | )
|
71 | 66 | .disableCache()
|
72 | 67 | .toMutableStoreBuilder(
|
73 |
| - converter = object : Converter<String, String, String> { |
74 |
| - override fun fromNetworkToLocal(network: String): String { |
75 |
| - return network |
76 |
| - } |
| 68 | + converter = |
| 69 | + object : Converter<String, String, String> { |
| 70 | + override fun fromNetworkToLocal(network: String): String = network |
77 | 71 |
|
78 |
| - override fun fromOutputToLocal(output: String): String { |
79 |
| - return output |
80 |
| - } |
81 |
| - }, |
| 72 | + override fun fromOutputToLocal(output: String): String = output |
| 73 | + }, |
82 | 74 | )
|
83 | 75 | .build(
|
84 |
| - updater = object : Updater<Int, String, Unit> { |
85 |
| - var callCount = -1 |
86 |
| - override suspend fun post(key: Int, value: String): UpdaterResult { |
87 |
| - callCount += 1 |
88 |
| - if (callCount % 2 == 0) { |
89 |
| - throw IllegalArgumentException(key.toString() + "value:$value") |
90 |
| - } else { |
91 |
| - return UpdaterResult.Success.Untyped("") |
92 |
| - } |
93 |
| - } |
| 76 | + updater = |
| 77 | + object : Updater<Int, String, Unit> { |
| 78 | + var callCount = -1 |
94 | 79 |
|
95 |
| - override val onCompletion: OnUpdaterCompletion<Unit>? |
96 |
| - get() = null |
| 80 | + override suspend fun post( |
| 81 | + key: Int, |
| 82 | + value: String, |
| 83 | + ): UpdaterResult { |
| 84 | + callCount += 1 |
| 85 | + return if (callCount % 2 == 0) { |
| 86 | + throw IllegalArgumentException("$key value: $value") |
| 87 | + } else { |
| 88 | + UpdaterResult.Success.Untyped("") |
| 89 | + } |
| 90 | + } |
97 | 91 |
|
98 |
| - } |
| 92 | + override val onCompletion: OnUpdaterCompletion<Unit>? = null |
| 93 | + }, |
99 | 94 | )
|
100 | 95 |
|
101 | 96 | val jobs = mutableListOf<Job>()
|
102 | 97 | jobs.add(
|
103 | 98 | store.stream<Nothing>(StoreReadRequest.cached(1, refresh = true))
|
104 | 99 | .mapNotNull { it.dataOrNull() }
|
105 |
| - .launchIn(CoroutineScope(Dispatchers.Default)) |
| 100 | + .launchIn(this), |
106 | 101 | )
|
107 |
| - val job1 = store.stream<Nothing>(StoreReadRequest.cached(0, refresh = true)) |
108 |
| - .mapNotNull { it.dataOrNull() } |
109 |
| - .launchIn(CoroutineScope(Dispatchers.Default)) |
| 102 | + val job1 = |
| 103 | + store.stream<Nothing>(StoreReadRequest.cached(0, refresh = true)) |
| 104 | + .mapNotNull { it.dataOrNull() } |
| 105 | + .launchIn(this) |
110 | 106 | jobs.add(
|
111 | 107 | store.stream<Nothing>(StoreReadRequest.cached(2, refresh = true))
|
112 | 108 | .mapNotNull { it.dataOrNull() }
|
113 |
| - .launchIn(CoroutineScope(Dispatchers.Default))) |
| 109 | + .launchIn(this), |
| 110 | + ) |
114 | 111 | jobs.add(
|
115 | 112 | store.stream<Nothing>(StoreReadRequest.cached(3, refresh = true))
|
116 | 113 | .mapNotNull { it.dataOrNull() }
|
117 |
| - .launchIn(CoroutineScope(Dispatchers.Default))) |
| 114 | + .launchIn(this), |
| 115 | + ) |
118 | 116 | job1.cancel()
|
119 | 117 | assertEquals(
|
120 | 118 | expected = "source_of_truth_0",
|
121 |
| - actual = store.stream<Nothing>(StoreReadRequest.cached(0, refresh = true)) |
122 |
| - .mapNotNull { it.dataOrNull() }.first() |
| 119 | + actual = |
| 120 | + store.stream<Nothing>(StoreReadRequest.cached(0, refresh = true)) |
| 121 | + .mapNotNull { it.dataOrNull() } |
| 122 | + .first(), |
123 | 123 | )
|
124 | 124 | jobs.forEach {
|
125 | 125 | it.cancel()
|
126 | 126 | assertEquals(
|
127 | 127 | expected = "source_of_truth_0",
|
128 |
| - actual = store.stream<Nothing>(StoreReadRequest.cached(0, refresh = true)) |
129 |
| - .mapNotNull { it.dataOrNull() }.first() |
| 128 | + actual = |
| 129 | + store.stream<Nothing>(StoreReadRequest.cached(0, refresh = true)) |
| 130 | + .mapNotNull { it.dataOrNull() } |
| 131 | + .first(), |
130 | 132 | )
|
131 | 133 | }
|
132 | 134 | }
|
|
0 commit comments