11package org.reduxkotlin.util
22
3+ import kotlinx.coroutines.*
4+ import kotlinx.coroutines.flow.collect
5+ import kotlinx.coroutines.flow.flow
6+ import kotlinx.coroutines.test.setMain
37import org.reduxkotlin.*
48import org.spekframework.spek2.Spek
59import org.spekframework.spek2.style.specification.describe
610import java.util.concurrent.CountDownLatch
11+ import java.util.concurrent.Executors
712import kotlin.IllegalStateException
8- import kotlin.test.assertNotNull
9- import kotlin.test.assertNull
13+ import kotlin.system.measureTimeMillis
14+ import kotlin.test.*
15+
1016
1117object ThreadUtilSpec : Spek({
18+ val mainThreadSurrogate = Executors .newSingleThreadExecutor().asCoroutineDispatcher()
19+ Dispatchers .setMain(mainThreadSurrogate)
20+
1221 describe("createStore") {
1322 val store = createStore(
1423 todos, TestState (
@@ -28,21 +37,75 @@ object ThreadUtilSpec : Spek({
2837 ensureSameThread { store.dispatch(Any ()) }
2938 }
3039 it("ensure same thread on replaceReducer") {
31- ensureSameThread { store.replaceReducer { state, action -> state } }
40+ ensureSameThread { store.replaceReducer { state, action -> state } }
3241 }
3342 it("ensure same thread on subscribe") {
3443 ensureSameThread { store.subscribe { } }
3544 }
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+ }
3699 }
37100})
38101
39- private fun ensureSameThread (getState : () -> Any ) {
102+ private fun ensureSameThread (testFun : () -> Any ) {
40103 val latch = CountDownLatch (1 )
41104 var exception: java.lang.IllegalStateException ? = null
42105 var state: Any? = null
43106
44107 val newThread = Thread {
45- state = getState ()
108+ state = testFun ()
46109 }
47110
48111 newThread.setUncaughtExceptionHandler { thread, throwable ->
@@ -55,4 +118,43 @@ private fun ensureSameThread(getState: () -> Any) {
55118
56119 assertNotNull(exception)
57120 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