1
1
package org.reduxkotlin.util
2
2
3
+ import kotlinx.coroutines.*
4
+ import kotlinx.coroutines.flow.collect
5
+ import kotlinx.coroutines.flow.flow
6
+ import kotlinx.coroutines.test.setMain
3
7
import org.reduxkotlin.*
4
8
import org.spekframework.spek2.Spek
5
9
import org.spekframework.spek2.style.specification.describe
6
10
import java.util.concurrent.CountDownLatch
11
+ import java.util.concurrent.Executors
7
12
import kotlin.IllegalStateException
8
- import kotlin.test.assertNotNull
9
- import kotlin.test.assertNull
13
+ import kotlin.system.measureTimeMillis
14
+ import kotlin.test.*
15
+
10
16
11
17
object ThreadUtilSpec : Spek({
18
+ val mainThreadSurrogate = Executors .newSingleThreadExecutor().asCoroutineDispatcher()
19
+ Dispatchers .setMain(mainThreadSurrogate)
20
+
12
21
describe("createStore") {
13
22
val store = createStore(
14
23
todos, TestState (
@@ -28,21 +37,75 @@ object ThreadUtilSpec : Spek({
28
37
ensureSameThread { store.dispatch(Any ()) }
29
38
}
30
39
it("ensure same thread on replaceReducer") {
31
- ensureSameThread { store.replaceReducer { state, action -> state } }
40
+ ensureSameThread { store.replaceReducer { state, action -> state } }
32
41
}
33
42
it("ensure same thread on subscribe") {
34
43
ensureSameThread { store.subscribe { } }
35
44
}
45
+ it("enforces same thread when thread name appends coroutine name") {
46
+ val middleware = TestMiddleware ()
47
+
48
+ runBlocking {
49
+ CoroutineScope (Dispatchers .Main ).async {
50
+ val store = createStore(
51
+ testReducer,
52
+ TestState (),
53
+ applyMiddleware(middleware.middleware)
54
+ )
55
+
56
+ store.dispatch(Any ())
57
+ }.await()
58
+ Thread .sleep(2000)
59
+ assertFalse(middleware.failed)
60
+ }
61
+ }
62
+ it("increments massively") {
63
+ suspend fun massiveRun(action: suspend () -> Unit ) {
64
+ val n = 100 // number of coroutines to launch
65
+ val k = 1000 // times an action is repeated by each coroutine
66
+ val time = measureTimeMillis {
67
+ coroutineScope {
68
+ // scope for coroutines
69
+ repeat(n) {
70
+ launch {
71
+ repeat(k) { action() }
72
+ }
73
+ }
74
+ }
75
+ }
76
+ println("Completed ${n * k} actions in $time ms")
77
+ }
78
+
79
+
80
+ val counterContext = newSingleThreadContext("CounterContext ")
81
+
82
+ lateinit var store : Store <TestCounterState >
83
+ runBlocking {
84
+ withContext(counterContext) {
85
+ store = createStore(counterReducer, TestCounterState ())
86
+ }
87
+ }
88
+ runBlocking {
89
+ withContext(counterContext) {
90
+ massiveRun {
91
+ store.dispatch(Increment ())
92
+ }
93
+ }
94
+ withContext(counterContext) {
95
+ assertEquals(100000, store.state.counter)
96
+ }
97
+ }
98
+ }
36
99
}
37
100
})
38
101
39
- private fun ensureSameThread (getState : () -> Any ) {
102
+ private fun ensureSameThread (testFun : () -> Any ) {
40
103
val latch = CountDownLatch (1 )
41
104
var exception: java.lang.IllegalStateException ? = null
42
105
var state: Any? = null
43
106
44
107
val newThread = Thread {
45
- state = getState ()
108
+ state = testFun ()
46
109
}
47
110
48
111
newThread.setUncaughtExceptionHandler { thread, throwable ->
@@ -55,4 +118,43 @@ private fun ensureSameThread(getState: () -> Any) {
55
118
56
119
assertNotNull(exception)
57
120
assertNull(state)
58
- }
121
+ }
122
+
123
+ val testReducer: Reducer <TestState > = { state, action -> state }
124
+
125
+ /* *
126
+ * Used as a test for when Thread.currentThread.name returns the
127
+ * thread name + '@coroutine#'.
128
+ * See issue #38 https://github.com/reduxkotlin/redux-kotlin/issues/38
129
+ */
130
+ class TestMiddleware {
131
+ var failed = false
132
+ val middleware = middleware<TestState > { store, next, action ->
133
+ CoroutineScope (Dispatchers .Main ).launch {
134
+ flow {
135
+ delay(1000 ) // simulate api call
136
+ emit(" Text Response" )
137
+ }.collect { response ->
138
+ store.dispatch(" " )
139
+ }
140
+ }
141
+ try {
142
+ next(action)
143
+ } catch (e: Exception ) {
144
+ e.printStackTrace()
145
+ failed = true
146
+ Unit
147
+ }
148
+ }
149
+ }
150
+
151
+ class Increment
152
+
153
+ data class TestCounterState (val counter : Int = 0 )
154
+
155
+ val counterReducer = { state: TestCounterState , action: Any ->
156
+ when (action) {
157
+ is Increment -> state.copy(counter = state.counter + 1 )
158
+ else -> state
159
+ }
160
+ }
0 commit comments