@@ -148,6 +148,9 @@ class SentryFrameMetricsCollectorTest {
148148 collector.startCollection(mock())
149149 assertEquals(0 , fixture.addOnFrameMetricsAvailableListenerCounter)
150150 collector.onActivityStarted(fixture.activity)
151+ // Execute pending main looper tasks since addOnFrameMetricsAvailableListener is posted to main
152+ // thread
153+ Shadows .shadowOf(Looper .getMainLooper()).idle()
151154 assertEquals(1 , fixture.addOnFrameMetricsAvailableListenerCounter)
152155 }
153156
@@ -157,8 +160,12 @@ class SentryFrameMetricsCollectorTest {
157160
158161 collector.startCollection(mock())
159162 collector.onActivityStarted(fixture.activity)
163+ // Execute pending add operations
164+ Shadows .shadowOf(Looper .getMainLooper()).idle()
160165 assertEquals(0 , fixture.removeOnFrameMetricsAvailableListenerCounter)
161166 collector.onActivityStopped(fixture.activity)
167+ // Execute pending remove operations
168+ Shadows .shadowOf(Looper .getMainLooper()).idle()
162169 assertEquals(1 , fixture.removeOnFrameMetricsAvailableListenerCounter)
163170 }
164171
@@ -181,6 +188,8 @@ class SentryFrameMetricsCollectorTest {
181188 collector.onActivityStarted(fixture.activity)
182189 assertEquals(0 , fixture.addOnFrameMetricsAvailableListenerCounter)
183190 collector.startCollection(mock())
191+ // Execute pending main looper tasks
192+ Shadows .shadowOf(Looper .getMainLooper()).idle()
184193 assertEquals(1 , fixture.addOnFrameMetricsAvailableListenerCounter)
185194 }
186195
@@ -189,9 +198,13 @@ class SentryFrameMetricsCollectorTest {
189198 val collector = fixture.getSut(context)
190199 val id = collector.startCollection(mock())
191200 collector.onActivityStarted(fixture.activity)
201+ // Execute pending add operations
202+ Shadows .shadowOf(Looper .getMainLooper()).idle()
192203
193204 assertEquals(0 , fixture.removeOnFrameMetricsAvailableListenerCounter)
194205 collector.stopCollection(id)
206+ // Execute pending remove operations
207+ Shadows .shadowOf(Looper .getMainLooper()).idle()
195208 assertEquals(1 , fixture.removeOnFrameMetricsAvailableListenerCounter)
196209 }
197210
@@ -205,9 +218,13 @@ class SentryFrameMetricsCollectorTest {
205218
206219 collector.onActivityStarted(fixture.activity)
207220 collector.onActivityStarted(fixture.activity)
221+ // Execute pending add operations
222+ Shadows .shadowOf(Looper .getMainLooper()).idle()
208223
209224 collector.onActivityStopped(fixture.activity)
210225 collector.onActivityStopped(fixture.activity)
226+ // Execute pending remove operations
227+ Shadows .shadowOf(Looper .getMainLooper()).idle()
211228
212229 assertEquals(1 , fixture.addOnFrameMetricsAvailableListenerCounter)
213230 assertEquals(1 , fixture.removeOnFrameMetricsAvailableListenerCounter)
@@ -228,9 +245,13 @@ class SentryFrameMetricsCollectorTest {
228245 collector.startCollection(mock())
229246 collector.onActivityStarted(fixture.activity)
230247 collector.onActivityStarted(fixture.activity2)
248+ // Execute pending add operations
249+ Shadows .shadowOf(Looper .getMainLooper()).idle()
231250 assertEquals(2 , fixture.addOnFrameMetricsAvailableListenerCounter)
232251 collector.onActivityStopped(fixture.activity)
233252 collector.onActivityStopped(fixture.activity2)
253+ // Execute pending remove operations
254+ Shadows .shadowOf(Looper .getMainLooper()).idle()
234255 assertEquals(2 , fixture.removeOnFrameMetricsAvailableListenerCounter)
235256 }
236257
@@ -240,10 +261,13 @@ class SentryFrameMetricsCollectorTest {
240261 val id1 = collector.startCollection(mock())
241262 val id2 = collector.startCollection(mock())
242263 collector.onActivityStarted(fixture.activity)
264+ Shadows .shadowOf(Looper .getMainLooper()).idle()
243265 assertEquals(1 , fixture.addOnFrameMetricsAvailableListenerCounter)
244266 collector.stopCollection(id1)
267+ Shadows .shadowOf(Looper .getMainLooper()).idle()
245268 assertEquals(0 , fixture.removeOnFrameMetricsAvailableListenerCounter)
246269 collector.stopCollection(id2)
270+ Shadows .shadowOf(Looper .getMainLooper()).idle()
247271 assertEquals(1 , fixture.removeOnFrameMetricsAvailableListenerCounter)
248272 }
249273
@@ -511,6 +535,48 @@ class SentryFrameMetricsCollectorTest {
511535 )
512536 }
513537
538+ @Test
539+ fun `collector calls addOnFrameMetricsAvailableListener on main thread` () {
540+ val collector = fixture.getSut(context)
541+
542+ collector.startCollection(mock())
543+ collector.onActivityStarted(fixture.activity)
544+
545+ assertEquals(0 , fixture.addOnFrameMetricsAvailableListenerCounter)
546+ Shadows .shadowOf(Looper .getMainLooper()).idle()
547+ assertEquals(1 , fixture.addOnFrameMetricsAvailableListenerCounter)
548+ }
549+
550+ @Test
551+ fun `collector calls removeOnFrameMetricsAvailableListener on main thread` () {
552+ val collector = fixture.getSut(context)
553+ collector.startCollection(mock())
554+ collector.onActivityStarted(fixture.activity)
555+ Shadows .shadowOf(Looper .getMainLooper()).idle()
556+
557+ collector.onActivityStopped(fixture.activity)
558+ assertEquals(0 , fixture.removeOnFrameMetricsAvailableListenerCounter)
559+ Shadows .shadowOf(Looper .getMainLooper()).idle()
560+ assertEquals(1 , fixture.removeOnFrameMetricsAvailableListenerCounter)
561+ }
562+
563+ @Test
564+ fun `collector prevents race condition when stop is called immediately after start` () {
565+ val collector = fixture.getSut(context)
566+
567+ collector.startCollection(mock())
568+ collector.onActivityStarted(fixture.activity)
569+ collector.onActivityStopped(fixture.activity)
570+
571+ // Now execute all pending operations
572+ Shadows .shadowOf(Looper .getMainLooper()).idle()
573+
574+ // as the listeners are posted to the main thread, we expect an add followed by a remove
575+ assertEquals(1 , fixture.addOnFrameMetricsAvailableListenerCounter)
576+ assertEquals(1 , fixture.removeOnFrameMetricsAvailableListenerCounter)
577+ assertEquals(0 , collector.getProperty<Set <Window >>(" trackedWindows" ).size)
578+ }
579+
514580 private fun createMockWindow (refreshRate : Float = 60F): Window {
515581 val mockWindow = mock<Window >()
516582 val mockDisplay = mock<Display >()
0 commit comments