Skip to content

Commit f20340a

Browse files
authored
dataconnect: TestFirebaseAppFactory.kt: work around IllegalStateException in tests by adding a delay before calling FirebaseApp.delete() (#6447)
1 parent 5f6bc63 commit f20340a

File tree

3 files changed

+16
-57
lines changed

3 files changed

+16
-57
lines changed

firebase-dataconnect/androidTestutil/src/main/kotlin/com/google/firebase/dataconnect/testutil/TestFirebaseAppFactory.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ import com.google.firebase.app
2222
import com.google.firebase.initialize
2323
import com.google.firebase.util.nextAlphanumericString
2424
import kotlin.random.Random
25+
import kotlin.time.Duration.Companion.seconds
26+
import kotlinx.coroutines.DelicateCoroutinesApi
27+
import kotlinx.coroutines.Dispatchers
28+
import kotlinx.coroutines.GlobalScope
29+
import kotlinx.coroutines.delay
30+
import kotlinx.coroutines.launch
2531

2632
/**
2733
* A JUnit test rule that creates instances of [FirebaseApp] for use during testing, and closes them
@@ -37,6 +43,12 @@ class TestFirebaseAppFactory : FactoryTestRule<FirebaseApp, Nothing>() {
3743
)
3844

3945
override fun destroyInstance(instance: FirebaseApp) {
40-
instance.delete()
46+
// Work around app crash due to IllegalStateException from FirebaseAuth if `delete()` is called
47+
// very quickly after `FirebaseApp.getInstance()`. See b/378116261 for details.
48+
@OptIn(DelicateCoroutinesApi::class)
49+
GlobalScope.launch(Dispatchers.IO) {
50+
delay(1.seconds)
51+
instance.delete()
52+
}
4153
}
4254
}

firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectCredentialsTokenManager.kt

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -236,9 +236,7 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any, L : Any>(
236236

237237
if (state.compareAndSet(oldState, State.Closed)) {
238238
providerListenerPair?.run {
239-
provider?.let { provider ->
240-
runIgnoringFirebaseAppDeleted { removeTokenListener(provider, tokenListener) }
241-
}
239+
provider?.let { provider -> removeTokenListener(provider, tokenListener) }
242240
}
243241
return
244242
}
@@ -422,7 +420,7 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any, L : Any>(
422420
@DeferredApi
423421
private fun onProviderAvailable(newProvider: T, tokenListener: L) {
424422
logger.debug { "onProviderAvailable(newProvider=$newProvider)" }
425-
runIgnoringFirebaseAppDeleted { addTokenListener(newProvider, tokenListener) }
423+
addTokenListener(newProvider, tokenListener)
426424

427425
while (true) {
428426
val oldState = state.get()
@@ -437,7 +435,7 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any, L : Any>(
437435
"onProviderAvailable(newProvider=$newProvider)" +
438436
" unregistering token listener that was just added"
439437
}
440-
runIgnoringFirebaseAppDeleted { removeTokenListener(newProvider, tokenListener) }
438+
removeTokenListener(newProvider, tokenListener)
441439
break
442440
}
443441
is State.Ready ->
@@ -486,20 +484,6 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any, L : Any>(
486484
private class GetTokenCancelledException(cause: Throwable) :
487485
DataConnectException("getToken() was cancelled, likely by close()", cause)
488486

489-
// Work around a race condition where addIdTokenListener() and removeIdTokenListener() throw if
490-
// the FirebaseApp is deleted during or before its invocation.
491-
private fun runIgnoringFirebaseAppDeleted(block: () -> Unit) {
492-
try {
493-
block()
494-
} catch (e: IllegalStateException) {
495-
if (e.message == "FirebaseApp was deleted") {
496-
logger.warn(e) { "ignoring exception: $e" }
497-
} else {
498-
throw e
499-
}
500-
}
501-
}
502-
503487
protected data class GetTokenResult(val token: String?)
504488

505489
private companion object {

firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectAuthUnitTest.kt

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -552,43 +552,6 @@ class DataConnectAuthUnitTest {
552552
)
553553
}
554554

555-
@Test
556-
fun `addIdTokenListener() throwing IllegalStateException due to FirebaseApp deleted should be ignored`() =
557-
runTest {
558-
every { mockInternalAuthProvider.addIdTokenListener(any()) } throws
559-
firebaseAppDeletedException
560-
coEvery { mockInternalAuthProvider.getAccessToken(any()) } returns taskForToken(accessToken)
561-
val dataConnectAuth = newDataConnectAuth()
562-
dataConnectAuth.initialize()
563-
advanceUntilIdle()
564-
565-
eventually(`check every 100 milliseconds for 2 seconds`) {
566-
mockLogger.shouldHaveLoggedExactlyOneMessageContaining(
567-
"ignoring exception: $firebaseAppDeletedException"
568-
)
569-
}
570-
val result = dataConnectAuth.getToken(requestId)
571-
withClue("result=$result") { result shouldBe accessToken }
572-
}
573-
574-
@Test
575-
fun `removeIdTokenListener() throwing IllegalStateException due to FirebaseApp deleted should be ignored`() =
576-
runTest {
577-
every { mockInternalAuthProvider.removeIdTokenListener(any()) } throws
578-
firebaseAppDeletedException
579-
val dataConnectAuth = newDataConnectAuth()
580-
dataConnectAuth.initialize()
581-
advanceUntilIdle()
582-
583-
dataConnectAuth.close()
584-
585-
eventually(`check every 100 milliseconds for 2 seconds`) {
586-
mockLogger.shouldHaveLoggedExactlyOneMessageContaining(
587-
"ignoring exception: $firebaseAppDeletedException"
588-
)
589-
}
590-
}
591-
592555
private fun TestScope.newDataConnectAuth(
593556
deferredInternalAuthProvider: DeferredInternalAuthProvider =
594557
ImmediateDeferred(mockInternalAuthProvider),

0 commit comments

Comments
 (0)