davidliu
Committed by GitHub

Fix reconnected event not firing on full reconnect (#158)

* Fix reconnect events on full reconnect

* parameterized tests
@@ -139,7 +139,7 @@ dependencies { @@ -139,7 +139,7 @@ dependencies {
139 testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0" 139 testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0"
140 testImplementation 'androidx.test:core:1.4.0' 140 testImplementation 'androidx.test:core:1.4.0'
141 testImplementation deps.coroutines.test 141 testImplementation deps.coroutines.test
142 - kaptTest 'com.google.dagger:dagger-compiler:2.38' 142 + kaptTest "com.google.dagger:dagger-compiler:${versions.dagger}"
143 androidTestImplementation 'androidx.test.ext:junit:1.1.3' 143 androidTestImplementation 'androidx.test.ext:junit:1.1.3'
144 androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 144 androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
145 } 145 }
@@ -324,10 +324,12 @@ internal constructor( @@ -324,10 +324,12 @@ internal constructor(
324 reconnectingJob = null 324 reconnectingJob = null
325 coroutineScope.close() 325 coroutineScope.close()
326 closeResources(reason) 326 closeResources(reason)
  327 + connectionState = ConnectionState.DISCONNECTED
327 } 328 }
328 329
329 private fun closeResources(reason: String) { 330 private fun closeResources(reason: String) {
330 - connectionState = ConnectionState.DISCONNECTED 331 + publisherObserver.connectionChangeListener = null
  332 + subscriberObserver.connectionChangeListener = null
331 _publisher?.close() 333 _publisher?.close()
332 _publisher = null 334 _publisher = null
333 _subscriber?.close() 335 _subscriber?.close()
@@ -32,7 +32,7 @@ abstract class MockE2ETest : BaseTest() { @@ -32,7 +32,7 @@ abstract class MockE2ETest : BaseTest() {
32 internal lateinit var subscriber: PeerConnectionTransport 32 internal lateinit var subscriber: PeerConnectionTransport
33 33
34 @Before 34 @Before
35 - fun setup() { 35 + fun mocksSetup() {
36 context = ApplicationProvider.getApplicationContext() 36 context = ApplicationProvider.getApplicationContext()
37 component = DaggerTestLiveKitComponent 37 component = DaggerTestLiveKitComponent
38 .factory() 38 .factory()
@@ -18,88 +18,30 @@ import org.junit.Test @@ -18,88 +18,30 @@ import org.junit.Test
18 import org.junit.runner.RunWith 18 import org.junit.runner.RunWith
19 import org.robolectric.RobolectricTestRunner 19 import org.robolectric.RobolectricTestRunner
20 20
  21 +/**
  22 + * For tests that only target one reconnection type.
  23 + *
  24 + * Tests that cover all connection types should be put in [RoomReconnectionTypesMockE2ETest].
  25 + */
21 @ExperimentalCoroutinesApi 26 @ExperimentalCoroutinesApi
22 @RunWith(RobolectricTestRunner::class) 27 @RunWith(RobolectricTestRunner::class)
23 class RoomReconnectionMockE2ETest : MockE2ETest() { 28 class RoomReconnectionMockE2ETest : MockE2ETest() {
24 29
25 - private fun prepareForReconnect(softReconnect: Boolean = false) { 30 + private fun prepareForReconnect() {
26 wsFactory.onOpen = { 31 wsFactory.onOpen = {
27 wsFactory.listener.onOpen(wsFactory.ws, createOpenResponse(wsFactory.request)) 32 wsFactory.listener.onOpen(wsFactory.ws, createOpenResponse(wsFactory.request))
28 - if (!softReconnect) { 33 + val softReconnectParam = wsFactory.request.url
  34 + .queryParameter(SignalClient.CONNECT_QUERY_RECONNECT)
  35 + ?.toIntOrNull()
  36 + ?: 0
  37 +
  38 + if (softReconnectParam == 0) {
29 simulateMessageFromServer(SignalClientTest.JOIN) 39 simulateMessageFromServer(SignalClientTest.JOIN)
30 } 40 }
31 } 41 }
32 } 42 }
33 43
34 @Test 44 @Test
35 - fun reconnectFromPeerConnectionDisconnect() = runTest {  
36 - connect()  
37 -  
38 - val eventCollector = EventCollector(room.events, coroutineRule.scope)  
39 - val stateCollector = FlowCollector(room::state.flow, coroutineRule.scope)  
40 - prepareForReconnect()  
41 - disconnectPeerConnection()  
42 - // Wait so that the reconnect job properly starts first.  
43 - testScheduler.advanceTimeBy(1000)  
44 - connectPeerConnection()  
45 -  
46 - testScheduler.advanceUntilIdle()  
47 - val events = eventCollector.stopCollecting()  
48 - val states = stateCollector.stopCollecting()  
49 -  
50 - assertIsClassList(  
51 - listOf(  
52 - RoomEvent.Reconnecting::class.java,  
53 - RoomEvent.Reconnected::class.java,  
54 - ),  
55 - events  
56 - )  
57 -  
58 - assertEquals(  
59 - listOf(  
60 - Room.State.CONNECTED,  
61 - Room.State.RECONNECTING,  
62 - Room.State.CONNECTED,  
63 - ),  
64 - states  
65 - )  
66 - }  
67 -  
68 - @Test  
69 - fun reconnectFromWebSocketFailure() = runTest {  
70 - connect()  
71 -  
72 - val eventCollector = EventCollector(room.events, coroutineRule.scope)  
73 - val stateCollector = FlowCollector(room::state.flow, coroutineRule.scope)  
74 - prepareForReconnect()  
75 - wsFactory.ws.cancel()  
76 - // Wait so that the reconnect job properly starts first.  
77 - testScheduler.advanceTimeBy(1000)  
78 - connectPeerConnection()  
79 -  
80 - testScheduler.advanceUntilIdle()  
81 - val events = eventCollector.stopCollecting()  
82 - val states = stateCollector.stopCollecting()  
83 -  
84 - assertIsClassList(  
85 - listOf(  
86 - RoomEvent.Reconnecting::class.java,  
87 - RoomEvent.Reconnected::class.java,  
88 - ),  
89 - events  
90 - )  
91 -  
92 - assertEquals(  
93 - listOf(  
94 - Room.State.CONNECTED,  
95 - Room.State.RECONNECTING,  
96 - Room.State.CONNECTED,  
97 - ),  
98 - states  
99 - )  
100 - }  
101 -  
102 - @Test  
103 fun softReconnectSendsSyncState() = runTest { 45 fun softReconnectSendsSyncState() = runTest {
104 room.setReconnectionType(ReconnectType.FORCE_SOFT_RECONNECT) 46 room.setReconnectionType(ReconnectType.FORCE_SOFT_RECONNECT)
105 47
  1 +package io.livekit.android.room
  2 +
  3 +import io.livekit.android.MockE2ETest
  4 +import io.livekit.android.assert.assertIsClassList
  5 +import io.livekit.android.events.EventCollector
  6 +import io.livekit.android.events.FlowCollector
  7 +import io.livekit.android.events.RoomEvent
  8 +import io.livekit.android.util.flow
  9 +import junit.framework.Assert.assertEquals
  10 +import kotlinx.coroutines.ExperimentalCoroutinesApi
  11 +import org.junit.Before
  12 +import org.junit.Test
  13 +import org.junit.runner.RunWith
  14 +import org.robolectric.ParameterizedRobolectricTestRunner
  15 +
  16 +@ExperimentalCoroutinesApi
  17 +@RunWith(ParameterizedRobolectricTestRunner::class)
  18 +class RoomReconnectionTypesMockE2ETest(
  19 + private val reconnectType: ReconnectType
  20 +) : MockE2ETest() {
  21 +
  22 + companion object {
  23 + @JvmStatic
  24 + @ParameterizedRobolectricTestRunner.Parameters(name = "Input: {0}")
  25 + // parameters are provided as arrays, allowing more than one parameter
  26 + fun params() = listOf(
  27 + ReconnectType.FORCE_SOFT_RECONNECT,
  28 + ReconnectType.FORCE_FULL_RECONNECT,
  29 + )
  30 + }
  31 +
  32 + private fun prepareForReconnect() {
  33 + wsFactory.onOpen = {
  34 + wsFactory.listener.onOpen(wsFactory.ws, createOpenResponse(wsFactory.request))
  35 + val softReconnectParam = wsFactory.request.url
  36 + .queryParameter(SignalClient.CONNECT_QUERY_RECONNECT)
  37 + ?.toIntOrNull()
  38 + ?: 0
  39 +
  40 + if (softReconnectParam == 0) {
  41 + simulateMessageFromServer(SignalClientTest.JOIN)
  42 + }
  43 + }
  44 + }
  45 +
  46 + @Before
  47 + fun setup() {
  48 + room.setReconnectionType(reconnectType)
  49 + }
  50 +
  51 + @Test
  52 + fun reconnectFromPeerConnectionDisconnect() = runTest {
  53 + connect()
  54 +
  55 + val eventCollector = EventCollector(room.events, coroutineRule.scope)
  56 + val stateCollector = FlowCollector(room::state.flow, coroutineRule.scope)
  57 + prepareForReconnect()
  58 + disconnectPeerConnection()
  59 + // Wait so that the reconnect job properly starts first.
  60 + testScheduler.advanceTimeBy(1000)
  61 + connectPeerConnection()
  62 +
  63 + testScheduler.advanceUntilIdle()
  64 + val events = eventCollector.stopCollecting()
  65 + val states = stateCollector.stopCollecting()
  66 +
  67 + assertIsClassList(
  68 + listOf(
  69 + RoomEvent.Reconnecting::class.java,
  70 + RoomEvent.Reconnected::class.java,
  71 + ),
  72 + events
  73 + )
  74 +
  75 + assertEquals(
  76 + listOf(
  77 + Room.State.CONNECTED,
  78 + Room.State.RECONNECTING,
  79 + Room.State.CONNECTED,
  80 + ),
  81 + states
  82 + )
  83 + }
  84 +
  85 + @Test
  86 + fun reconnectFromWebSocketFailure() = runTest {
  87 + connect()
  88 +
  89 + val eventCollector = EventCollector(room.events, coroutineRule.scope)
  90 + val stateCollector = FlowCollector(room::state.flow, coroutineRule.scope)
  91 + prepareForReconnect()
  92 + wsFactory.ws.cancel()
  93 + // Wait so that the reconnect job properly starts first.
  94 + testScheduler.advanceTimeBy(1000)
  95 + connectPeerConnection()
  96 +
  97 + testScheduler.advanceUntilIdle()
  98 + val events = eventCollector.stopCollecting()
  99 + val states = stateCollector.stopCollecting()
  100 +
  101 + assertIsClassList(
  102 + listOf(
  103 + RoomEvent.Reconnecting::class.java,
  104 + RoomEvent.Reconnected::class.java,
  105 + ),
  106 + events
  107 + )
  108 +
  109 + assertEquals(
  110 + listOf(
  111 + Room.State.CONNECTED,
  112 + Room.State.RECONNECTING,
  113 + Room.State.CONNECTED,
  114 + ),
  115 + states
  116 + )
  117 + }
  118 +}