|
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