@@ -7,48 +7,101 @@ import kotlinx.coroutines.runBlocking
77import kotlinx.coroutines.withContext
88import org.junit.Test
99import org.reduxkotlin.createThreadSafeStore
10+ import org.reduxkotlin.applyMiddleware
11+ import org.reduxkotlin.createStore
12+ import org.reduxkotlin.createSynchronizedStoreEnhancer
13+ import org.reduxkotlin.compose
14+ import org.reduxkotlin.Dispatcher
15+ import org.reduxkotlin.GetState
16+ import org.reduxkotlin.Middleware
17+ import java.util.Timer
18+ import kotlin.concurrent.timerTask
1019import kotlin.system.measureTimeMillis
1120import kotlin.test.assertEquals
1221
1322class MultiThreadedClass {
14- @Test
15- fun multithreadedIncrementsMassively () {
16- suspend fun massiveRun (action : suspend () -> Unit ) {
17- val n = 100 // number of coroutines to launch
18- val k = 1000 // times an action is repeated by each coroutine
19- val time = measureTimeMillis {
20- coroutineScope {
21- // scope for coroutines
22- repeat(n) {
23- launch {
24- repeat(k) { action() }
25- }
23+ private suspend fun massiveRun (numCoroutines : Int , numRepeats : Int , action : suspend () -> Unit ) {
24+ val time = measureTimeMillis {
25+ coroutineScope {
26+ repeat(numCoroutines) {
27+ launch {
28+ repeat(numRepeats) { action() }
2629 }
2730 }
2831 }
29- println (" Completed ${n * k} actions in $time ms" )
3032 }
33+ println (" Completed ${numCoroutines * numRepeats} actions in $time ms" )
34+ }
3135
36+ @Test
37+ fun multithreadedIncrementsMassively () {
3238 // NOTE: changing this to createStore() breaks the tests
33- val store = createThreadSafeStore(counterReducer, TestCounterState ())
39+ val store = createThreadSafeStore(counterReducer, TestState ())
3440 runBlocking {
3541 withContext(Dispatchers .Default ) {
36- massiveRun {
42+ massiveRun( 100 , 1000 ) {
3743 store.dispatch(Increment ())
3844 }
3945 }
4046 assertEquals(100000 , store.state.counter)
4147 }
4248 }
49+
50+ @Test
51+ fun multithreadedIncrementsMassivelyWithEnhancer () {
52+ val store = createStore(counterReducer, TestState (), compose(
53+ applyMiddleware(createTestThunkMiddleware()),
54+ createSynchronizedStoreEnhancer() // needs to be placed after enhancers that requires synchronized store methods
55+ ))
56+ runBlocking {
57+ withContext(Dispatchers .Default ) {
58+ massiveRun(10 , 100 ) {
59+ store.dispatch(incrementThunk())
60+ }
61+ }
62+ // wait to assert to account for the last of thunk delays
63+ Timer ().schedule(timerTask {
64+ assertEquals(10000 , store.state.counter)
65+ }, 50 )
66+ }
67+ }
4368}
4469
4570class Increment
4671
47- data class TestCounterState (val counter : Int = 0 )
72+ data class TestState (val counter : Int = 0 )
4873
49- val counterReducer = { state: TestCounterState , action: Any ->
74+ val counterReducer = { state: TestState , action: Any ->
5075 when (action) {
5176 is Increment -> state.copy(counter = state.counter + 1 )
5277 else -> state
5378 }
5479}
80+
81+ // Enhancer mimics the behavior of `createThunkMiddleware` provided by the redux-kotlin-thunk library
82+ typealias TestThunk <State > = (dispatch: Dispatcher , getState: GetState <State >, extraArg: Any? ) -> Any
83+ fun <State > createTestThunkMiddleware (): Middleware <State > =
84+ { store ->
85+ { next: Dispatcher ->
86+ { action: Any ->
87+ if (action is Function <* >) {
88+ @Suppress(" UNCHECKED_CAST" )
89+ val thunk = try {
90+ (action as TestThunk <* >)
91+ } catch (e: ClassCastException ) {
92+ throw IllegalArgumentException (" Require type TestThunk" , e)
93+ }
94+ thunk(store.dispatch, store.getState, null )
95+ } else {
96+ next(action)
97+ }
98+ }
99+ }
100+ }
101+
102+ fun incrementThunk (): TestThunk <TestState > = { dispatch, getState, _ ->
103+ Timer ().schedule(timerTask {
104+ dispatch(Increment ())
105+ }, 50 )
106+ getState()
107+ }
0 commit comments