Skip to content

Commit 3893a8a

Browse files
authored
Merge pull request #297 from akirk/fix/corrupted-assignments-cache
Fix crash loop caused by corrupted A/B testing cache file
2 parents b372138 + 00b84ee commit 3893a8a

2 files changed

Lines changed: 46 additions & 8 deletions

File tree

experimentation/src/main/java/com/automattic/android/experimentation/local/FileBasedCache.kt

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,33 @@ internal class FileBasedCache(
3131
init {
3232
scope.launch {
3333
withContext(context = dispatcher) {
34-
latestMutable = getAssignments()
34+
runCatching { latestMutable = getAssignments() }
35+
.onFailure { throwable -> logger.e("Failed to load cached assignments", throwable) }
3536
}
3637
}
3738
}
3839

3940
suspend fun getAssignments(): Assignments? {
4041
return withContext(dispatcher) {
41-
assignmentsFile.takeIf { it.exists() }?.readText()?.let { json: String ->
42-
val fromJson = cacheDtoJsonAdapter.fromJson(json) ?: return@let null
43-
44-
fromJson.assignmentsDto.toAssignments(
45-
fetchedAt = fromJson.fetchedAt,
46-
anonymousId = fromJson.anonymousId,
47-
)
42+
assignmentsFile.takeIf { it.exists() }?.let { file ->
43+
val json = file.readText()
44+
if (json.isBlank()) {
45+
logger.e("Cached assignments file is empty, deleting: ${file.path}")
46+
file.delete()
47+
return@withContext null
48+
}
49+
runCatching { cacheDtoJsonAdapter.fromJson(json) }
50+
.onFailure { throwable ->
51+
logger.e("Cached assignments file is corrupted, deleting: ${file.path}", throwable)
52+
file.delete()
53+
}
54+
.getOrNull()
55+
?.let { fromJson ->
56+
fromJson.assignmentsDto.toAssignments(
57+
fetchedAt = fromJson.fetchedAt,
58+
anonymousId = fromJson.anonymousId,
59+
)
60+
}
4861
}
4962
}
5063
}

experimentation/src/test/java/com/automattic/android/experimentation/local/FileBasedCacheTest.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import kotlinx.coroutines.test.StandardTestDispatcher
99
import kotlinx.coroutines.test.TestScope
1010
import kotlinx.coroutines.test.runTest
1111
import org.junit.Assert.assertEquals
12+
import org.junit.Assert.assertFalse
1213
import org.junit.Assert.assertNull
1314
import org.junit.Rule
1415
import org.junit.Test
@@ -66,6 +67,30 @@ internal class FileBasedCacheTest {
6667
sut.clear()
6768
}
6869

70+
@Test
71+
fun `getting assignments from empty file returns null and deletes the file`() = runTest {
72+
val cacheDir = tempDir.newFolder()
73+
val assignmentsFile = File(cacheDir, "assignments.json").apply { createNewFile() }
74+
val sut = fileBasedCache(this, cacheDir = cacheDir)
75+
76+
val result = sut.getAssignments()
77+
78+
assertNull(result)
79+
assertFalse(assignmentsFile.exists())
80+
}
81+
82+
@Test
83+
fun `getting assignments from corrupted file returns null and deletes the file`() = runTest {
84+
val cacheDir = tempDir.newFolder()
85+
val assignmentsFile = File(cacheDir, "assignments.json").apply { writeText("{corrupted") }
86+
val sut = fileBasedCache(this, cacheDir = cacheDir)
87+
88+
val result = sut.getAssignments()
89+
90+
assertNull(result)
91+
assertFalse(assignmentsFile.exists())
92+
}
93+
6994
@Test
7095
fun `saving cache when cache dir doesnt exist is successful`() = runTest {
7196
val cacheDir = File(tempDir.newFolder(), "cache").apply { assert(!this.exists()) }

0 commit comments

Comments
 (0)