Skip to content

Commit 11cd776

Browse files
Alexandre Lissylissyx
authored andcommitted
Bug 1853108 - Add testing of interesting crash on Android r=kaya,boek,geckoview-reviewers,android-reviewers,ohall,sfamisa
Differential Revision: https://phabricator.services.mozilla.com/D229885
1 parent a328037 commit 11cd776

File tree

13 files changed

+812
-8
lines changed

13 files changed

+812
-8
lines changed

mobile/android/android-components/components/lib/crash/src/test/java/mozilla/components/lib/crash/CrashReporterTest.kt

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,6 +1274,167 @@ class CrashReporterTest {
12741274
}
12751275
}
12761276
}
1277+
1278+
@Test
1279+
fun `GIVEN the crash reporter has unsent crashes WHEN calling findCrashReports WITH specific crashID that do not exists THEN return empty list`() = runTestOnMain {
1280+
val crashReporter = CrashReporter(
1281+
services = listOf(mock()),
1282+
scope = scope,
1283+
databaseProvider = { db },
1284+
)
1285+
1286+
val oldCrashEntity = CrashEntity(
1287+
crashType = CrashType.NATIVE,
1288+
uuid = "53a63dcb-c450-44a0-940c-e809c7fad474",
1289+
runtimeTags = mapOf(),
1290+
breadcrumbs = listOf(),
1291+
createdAt = 0L,
1292+
stacktrace = "<native crash>",
1293+
throwableData = null,
1294+
minidumpPath = "/data/data/org.mozilla.fenix.debug/files/mozilla/Crash Reports/pending/46c43391-3e08-4222-a334-80fe13e0433b.dmp",
1295+
processType = null,
1296+
processVisibility = null,
1297+
extrasPath = null,
1298+
remoteType = null,
1299+
)
1300+
val newCrashEntity = CrashEntity(
1301+
crashType = CrashType.NATIVE,
1302+
uuid = "cc698820-06e6-45e1-932a-94e29dcd280c",
1303+
runtimeTags = mapOf(),
1304+
breadcrumbs = listOf(),
1305+
createdAt = 0L,
1306+
stacktrace = "<native crash>",
1307+
throwableData = null,
1308+
minidumpPath = "/data/data/org.mozilla.fenix.debug/files/mozilla/Crash Reports/pending/1a64d53e-7d34-416a-9679-ffdc2c6e0ba8.dmp",
1309+
processType = null,
1310+
processVisibility = null,
1311+
extrasPath = null,
1312+
remoteType = null,
1313+
)
1314+
1315+
val crashIDs = arrayOf("b0cbe510-4bc0-4f2e-b561-b496351e316b")
1316+
val result = withContext(Dispatchers.IO) {
1317+
db.crashDao().insertCrash(oldCrashEntity)
1318+
db.crashDao().insertCrash(newCrashEntity)
1319+
crashReporter.findCrashReports(crashIDs)
1320+
}
1321+
1322+
assertEquals(result.size, 0)
1323+
}
1324+
1325+
@Test
1326+
fun `GIVEN the crash reporter has unsent crashes WHEN calling findCrashReports WITH specific crashID THEN return list of this crash`() = runTestOnMain {
1327+
val crashReporter = CrashReporter(
1328+
services = listOf(mock()),
1329+
scope = scope,
1330+
databaseProvider = { db },
1331+
)
1332+
1333+
val oldCrashEntity = CrashEntity(
1334+
crashType = CrashType.NATIVE,
1335+
uuid = "53a63dcb-c450-44a0-940c-e809c7fad474",
1336+
runtimeTags = mapOf(),
1337+
breadcrumbs = listOf(),
1338+
createdAt = 0L,
1339+
stacktrace = "<native crash>",
1340+
throwableData = null,
1341+
minidumpPath = "/data/data/org.mozilla.fenix.debug/files/mozilla/Crash Reports/pending/46c43391-3e08-4222-a334-80fe13e0433b.dmp",
1342+
processType = null,
1343+
processVisibility = null,
1344+
extrasPath = null,
1345+
remoteType = null,
1346+
)
1347+
val newCrashEntity = CrashEntity(
1348+
crashType = CrashType.NATIVE,
1349+
uuid = "cc698820-06e6-45e1-932a-94e29dcd280c",
1350+
runtimeTags = mapOf(),
1351+
breadcrumbs = listOf(),
1352+
createdAt = 0L,
1353+
stacktrace = "<native crash>",
1354+
throwableData = null,
1355+
minidumpPath = "/data/data/org.mozilla.fenix.debug/files/mozilla/Crash Reports/pending/1a64d53e-7d34-416a-9679-ffdc2c6e0ba8.dmp",
1356+
processType = null,
1357+
processVisibility = null,
1358+
extrasPath = null,
1359+
remoteType = null,
1360+
)
1361+
1362+
val crashIDs = arrayOf("/data/data/org.mozilla.fenix.debug/files/mozilla/Crash Reports/pending/1a64d53e-7d34-416a-9679-ffdc2c6e0ba8.dmp")
1363+
1364+
val result = withContext(Dispatchers.IO) {
1365+
db.crashDao().insertCrash(oldCrashEntity)
1366+
db.crashDao().insertCrash(newCrashEntity)
1367+
crashReporter.findCrashReports(crashIDs)
1368+
}
1369+
1370+
assertEquals(result.size, 1)
1371+
assertEquals(crashReporter.findCrashReports(crashIDs).first().uuid, newCrashEntity.uuid)
1372+
}
1373+
1374+
@Test
1375+
fun `GIVEN the crash reporter has unsent crashes WHEN calling findCrashReports WITH specific crashID THEN return list of those crashes`() = runTestOnMain {
1376+
val crashReporter = CrashReporter(
1377+
services = listOf(mock()),
1378+
scope = scope,
1379+
databaseProvider = { db },
1380+
)
1381+
1382+
val crashEntity1 = CrashEntity(
1383+
crashType = CrashType.NATIVE,
1384+
uuid = "53a63dcb-c450-44a0-940c-e809c7fad474",
1385+
runtimeTags = mapOf(),
1386+
breadcrumbs = listOf(),
1387+
createdAt = 0L,
1388+
stacktrace = "<native crash>",
1389+
throwableData = null,
1390+
minidumpPath = "/data/data/org.mozilla.fenix.debug/files/mozilla/Crash Reports/pending/46c43391-3e08-4222-a334-80fe13e0433b.dmp",
1391+
processType = null,
1392+
processVisibility = null,
1393+
extrasPath = null,
1394+
remoteType = null,
1395+
)
1396+
val crashEntity2 = CrashEntity(
1397+
crashType = CrashType.NATIVE,
1398+
uuid = "cc698820-06e6-45e1-932a-94e29dcd280c",
1399+
runtimeTags = mapOf(),
1400+
breadcrumbs = listOf(),
1401+
createdAt = 0L,
1402+
stacktrace = "<native crash>",
1403+
throwableData = null,
1404+
minidumpPath = "/data/data/org.mozilla.fenix.debug/files/mozilla/Crash Reports/pending/1a64d53e-7d34-416a-9679-ffdc2c6e0ba8.dmp",
1405+
processType = null,
1406+
processVisibility = null,
1407+
extrasPath = null,
1408+
remoteType = null,
1409+
)
1410+
val crashEntity3 = CrashEntity(
1411+
crashType = CrashType.NATIVE,
1412+
uuid = "68fe1af8-2008-4aa4-9ff4-b23aecf9cb7d",
1413+
runtimeTags = mapOf(),
1414+
breadcrumbs = listOf(),
1415+
createdAt = 0L,
1416+
stacktrace = "<native crash>",
1417+
throwableData = null,
1418+
minidumpPath = "/data/data/org.mozilla.fenix.debug/files/mozilla/Crash Reports/pending/4b8c7669-8bee-4785-b87d-5c58dbb27e8e.dmp",
1419+
processType = null,
1420+
processVisibility = null,
1421+
extrasPath = null,
1422+
remoteType = null,
1423+
)
1424+
1425+
val crashIDs = arrayOf("/data/data/org.mozilla.fenix.debug/files/mozilla/Crash Reports/pending/46c43391-3e08-4222-a334-80fe13e0433b.dmp", "/data/data/org.mozilla.fenix.debug/files/mozilla/Crash Reports/pending/4b8c7669-8bee-4785-b87d-5c58dbb27e8e.dmp")
1426+
1427+
val result = withContext(Dispatchers.IO) {
1428+
db.crashDao().insertCrash(crashEntity1)
1429+
db.crashDao().insertCrash(crashEntity2)
1430+
db.crashDao().insertCrash(crashEntity3)
1431+
crashReporter.findCrashReports(crashIDs)
1432+
}
1433+
1434+
assertEquals(result.size, 2)
1435+
assertEquals(crashReporter.findCrashReports(crashIDs).get(0).uuid, crashEntity1.uuid)
1436+
assertEquals(crashReporter.findCrashReports(crashIDs).get(1).uuid, crashEntity3.uuid)
1437+
}
12771438
}
12781439

12791440
private fun createUncaughtExceptionCrash(): Crash.UncaughtExceptionCrash {

mobile/android/android-components/components/lib/crash/src/test/java/mozilla/components/lib/crash/store/CrashMiddlewareTest.kt

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import mozilla.components.support.test.rule.runTestOnMain
1414
import org.junit.Rule
1515
import org.junit.Test
1616
import org.junit.runner.RunWith
17+
import org.mockito.Mockito.anyLong
1718
import org.mockito.Mockito.never
1819
import org.mockito.Mockito.times
1920
import org.mockito.Mockito.verify
@@ -220,6 +221,17 @@ class CrashMiddlewareTest {
220221
verify(dispatcher).invoke(CrashAction.Defer(1_000_000))
221222
}
222223

224+
@Test
225+
fun `GIVEN CancelForEverTapped action THEN set setting crashPullNeverShowAgain`() = runTestOnMain {
226+
val cache: CrashReportCache = mock()
227+
val middleware = CrashMiddleware(cache, mock(), mock(), scope)
228+
val middlewareContext: Pair<() -> CrashState, (CrashAction) -> Unit> = Pair(mock(), mock())
229+
230+
middleware.invoke(middlewareContext, mock(), CrashAction.CancelForEverTapped)
231+
232+
verify(cache).setCrashPullNeverShowAgain(true)
233+
}
234+
223235
@Test
224236
fun `GIVEN a Defer action THEN cache the deferred value`() = runTestOnMain {
225237
val cache: CrashReportCache = mock()
@@ -234,28 +246,58 @@ class CrashMiddlewareTest {
234246
}
235247

236248
@Test
237-
fun `GIVEN ReportTapped WHEN automaticallySendChecked is true THEN submit unsent crashes and cache value`() = runTestOnMain {
249+
fun `GIVEN ReportTapped WHEN automaticallySendChecked is true and crashIDs is null THEN submit unsent crashes and cache value`() = runTestOnMain {
238250
val cache: CrashReportCache = mock()
239251
val crashReporter: CrashReporter = mock()
240252
val middleware = CrashMiddleware(cache, crashReporter, { 777L }, scope)
241253

242254
`when`(crashReporter.unsentCrashReportsSince(777L)).thenReturn(listOf(mock<Crash.UncaughtExceptionCrash>(), mock<Crash.UncaughtExceptionCrash>()))
243-
middleware.invoke(mock(), mock(), CrashAction.ReportTapped(automaticallySendChecked = true))
255+
middleware.invoke(mock(), mock(), CrashAction.ReportTapped(automaticallySendChecked = true, crashIDs = null))
244256

245257
verify(cache).setAlwaysSend(true)
258+
verify(crashReporter, never()).findCrashReports(any())
246259
verify(crashReporter, times(2)).submitReport(any(), any())
247260
}
248261

249262
@Test
250-
fun `GIVEN ReportTapped WHEN automaticallySendChecked is false THEN submit unsent crashes`() = runTestOnMain {
263+
fun `GIVEN ReportTapped WHEN automaticallySendChecked is false and crashIDs is null THEN submit unsent crashes`() = runTestOnMain {
251264
val cache: CrashReportCache = mock()
252265
val crashReporter: CrashReporter = mock()
253266
val middleware = CrashMiddleware(cache, crashReporter, { 777L }, scope)
254267

255268
`when`(crashReporter.unsentCrashReportsSince(777L)).thenReturn(listOf(mock<Crash.UncaughtExceptionCrash>(), mock<Crash.UncaughtExceptionCrash>()))
256-
middleware.invoke(mock(), mock(), CrashAction.ReportTapped(automaticallySendChecked = false))
269+
middleware.invoke(mock(), mock(), CrashAction.ReportTapped(automaticallySendChecked = false, crashIDs = null))
270+
271+
verify(cache, never()).setAlwaysSend(true)
272+
verify(crashReporter, never()).findCrashReports(any())
273+
verify(crashReporter, times(2)).submitReport(any(), any())
274+
}
275+
276+
@Test
277+
fun `GIVEN ReportTapped WHEN automaticallySendChecked is true and crashIDs is not null THEN ignore automaticallySendChecked`() = runTestOnMain {
278+
val cache: CrashReportCache = mock()
279+
val crashReporter: CrashReporter = mock()
280+
val middleware = CrashMiddleware(cache, crashReporter, mock(), scope)
281+
val crashIDs = arrayOf("1", "2")
282+
283+
middleware.invoke(mock(), mock(), CrashAction.ReportTapped(automaticallySendChecked = true, crashIDs = crashIDs))
257284

258285
verify(cache, never()).setAlwaysSend(true)
286+
verify(crashReporter, never()).unsentCrashReportsSince(anyLong())
287+
}
288+
289+
@Test
290+
fun `GIVEN ReportTapped WHEN automaticallySendChecked is false and some crashIDs THEN submit those crashes`() = runTestOnMain {
291+
val cache: CrashReportCache = mock()
292+
val crashReporter: CrashReporter = mock()
293+
val middleware = CrashMiddleware(cache, crashReporter, mock(), scope)
294+
val crashIDs = arrayOf("1", "2")
295+
296+
`when`(crashReporter.findCrashReports(crashIDs)).thenReturn(listOf(mock<Crash.UncaughtExceptionCrash>(), mock<Crash.UncaughtExceptionCrash>()))
297+
middleware.invoke(mock(), mock(), CrashAction.ReportTapped(automaticallySendChecked = false, crashIDs = crashIDs))
298+
299+
verify(crashReporter, never()).unsentCrashReportsSince(anyLong())
300+
verify(crashReporter, times(1)).findCrashReports(crashIDs)
259301
verify(crashReporter, times(2)).submitReport(any(), any())
260302
}
261303
}

mobile/android/android-components/components/lib/crash/src/test/java/mozilla/components/lib/crash/store/CrashReducerTest.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,15 @@ class CrashReducerTest {
5252
fun `GIVEN a Reporting state WHEN we process a CancelTapped or ReportTapped action THEN update state to Done`() {
5353
listOf(
5454
CrashAction.CancelTapped,
55-
CrashAction.ReportTapped(automaticallySendChecked = true),
55+
CrashAction.ReportTapped(automaticallySendChecked = true, crashIDs = null),
5656
).forEach {
5757
assertEquals(crashReducer(CrashState.Reporting, it), CrashState.Done)
5858
}
5959
}
60+
61+
@Test
62+
fun `GIVEN a ReportingPull state WHEN we process a CancelForEverTapped action THEN update state to Done`() {
63+
val state = crashReducer(CrashState.ReportingPull(arrayOf("1", "2")), CrashAction.CancelForEverTapped)
64+
assertEquals(state, CrashState.Done)
65+
}
6066
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
package org.mozilla.fenix.gecko
6+
7+
import android.content.Context
8+
import io.mockk.mockk
9+
import kotlinx.coroutines.CoroutineScope
10+
import kotlinx.coroutines.Dispatchers
11+
import kotlinx.coroutines.launch
12+
import kotlinx.coroutines.runBlocking
13+
import mozilla.components.concept.engine.EngineSession
14+
import mozilla.components.concept.storage.CreditCardsAddressesStorage
15+
import mozilla.components.concept.storage.LoginsStorage
16+
import org.junit.Assert.assertNotNull
17+
import org.junit.Before
18+
import org.junit.Test
19+
import org.mozilla.fenix.helpers.TestHelper
20+
21+
class CrashPullDelegateTest {
22+
private lateinit var context: Context
23+
private lateinit var mockPolicy: EngineSession.TrackingProtectionPolicy
24+
private lateinit var mockAutofill: Lazy<CreditCardsAddressesStorage>
25+
private lateinit var mockLogin: Lazy<LoginsStorage>
26+
private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main)
27+
28+
@Before
29+
fun setUp() {
30+
context = TestHelper.appContext
31+
mockPolicy = mockk<EngineSession.TrackingProtectionPolicy>()
32+
mockAutofill = mockk<Lazy<CreditCardsAddressesStorage>>()
33+
mockLogin = mockk<Lazy<LoginsStorage>>()
34+
}
35+
36+
@Test
37+
fun test_crash_pull_delegate_exists() {
38+
// scope.launch required to run in the correct thread for GeckoRuntime
39+
// but the test needs to wait on its completion to ensure assert is
40+
// verified, hence runBlocking.
41+
//
42+
// We cannot use runTestOnMain here because it looks not to be available.
43+
runBlocking {
44+
scope.launch {
45+
val runtime = GeckoProvider.getOrCreateRuntime(context, mockAutofill, mockLogin, mockPolicy)
46+
assertNotNull(runtime.crashPullDelegate)
47+
runtime.crashPullDelegate?.onCrashPull(arrayOf("1", "2"))
48+
}.join()
49+
}
50+
}
51+
}

mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/helpers/FeatureSettingsHelper.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ interface FeatureSettingsHelper {
9191
*/
9292
var isComposeHomepageEnabled: Boolean
9393

94+
/**
95+
* Enable or disable new crash reporter.
96+
*/
97+
var isUseNewCrashReporterDialog: Boolean
98+
9499
/**
95100
* Enable or disable the translations prompt after a page that can be translated is loaded.
96101
*/

mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/helpers/FeatureSettingsHelperDelegate.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class FeatureSettingsHelperDelegate : FeatureSettingsHelper {
4444
shouldUseBottomToolbar = settings.shouldUseBottomToolbar,
4545
onboardingFeatureEnabled = settings.onboardingFeatureEnabled,
4646
isComposeHomepageEnabled = settings.enableComposeHomepage,
47+
isUseNewCrashReporterDialog = settings.useNewCrashReporterDialog,
4748
)
4849

4950
/**
@@ -65,6 +66,7 @@ class FeatureSettingsHelperDelegate : FeatureSettingsHelper {
6566
override var shouldUseBottomToolbar: Boolean by updatedFeatureFlags::shouldUseBottomToolbar
6667
override var onboardingFeatureEnabled: Boolean by updatedFeatureFlags::onboardingFeatureEnabled
6768
override var isComposeHomepageEnabled: Boolean by updatedFeatureFlags::isComposeHomepageEnabled
69+
override var isUseNewCrashReporterDialog: Boolean by updatedFeatureFlags::isUseNewCrashReporterDialog
6870

6971
override fun applyFlagUpdates() {
7072
Log.i(TAG, "applyFlagUpdates: Trying to apply the updated feature flags: $updatedFeatureFlags")
@@ -97,6 +99,7 @@ class FeatureSettingsHelperDelegate : FeatureSettingsHelper {
9799
setPermissions(PhoneFeature.LOCATION, featureFlags.isLocationPermissionEnabled)
98100
settings.onboardingFeatureEnabled = featureFlags.onboardingFeatureEnabled
99101
settings.enableComposeHomepage = featureFlags.isComposeHomepageEnabled
102+
settings.useNewCrashReporterDialog = featureFlags.isUseNewCrashReporterDialog
100103
}
101104
}
102105

@@ -117,6 +120,7 @@ private data class FeatureFlags(
117120
var shouldUseBottomToolbar: Boolean,
118121
var onboardingFeatureEnabled: Boolean,
119122
var isComposeHomepageEnabled: Boolean,
123+
var isUseNewCrashReporterDialog: Boolean,
120124
)
121125

122126
internal fun getETPPolicy(settings: Settings): ETPPolicy {

0 commit comments

Comments
 (0)