Switch the snapshot table to use incrementing versions.

This commit is contained in:
Greyson Parrelli
2025-06-03 09:48:30 -04:00
parent 8c1e221afd
commit e7115a3a71
8 changed files with 308 additions and 124 deletions

View File

@@ -16,94 +16,97 @@ import org.thoughtcrime.securesms.testing.SignalActivityRule
@RunWith(AndroidJUnit4::class)
class BackupMediaSnapshotTableTest {
companion object {
private const val SEQUENCE_COUNT = 100
private const val SEQUENCE_COUNT_WITH_THUMBNAILS = 200
}
@get:Rule
val harness = SignalActivityRule()
@Test
fun givenAnEmptyTable_whenIWriteToTable_thenIExpectEmptyTable() {
val pendingSyncTime = 1L
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(generateArchiveMediaItemSequence(), pendingSyncTime)
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(generateArchiveMediaItemSequence(count = 100))
val count = getSyncedItemCount(pendingSyncTime)
val count = getCountForLatestSnapshot(includeThumbnails = true)
assertThat(count).isEqualTo(0)
}
@Test
fun givenAnEmptyTable_whenIWriteToTableAndCommit_thenIExpectFilledTable() {
val pendingSyncTime = 1L
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(generateArchiveMediaItemSequence(), pendingSyncTime)
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(generateArchiveMediaItemSequence(count = 100))
SignalDatabase.backupMediaSnapshots.commitPendingRows()
val count = getSyncedItemCount(pendingSyncTime)
val count = getCountForLatestSnapshot(includeThumbnails = false)
assertThat(count).isEqualTo(SEQUENCE_COUNT_WITH_THUMBNAILS)
assertThat(count).isEqualTo(100)
}
@Test
fun givenAFilledTable_whenIInsertSimilarIds_thenIExpectUncommittedOverrides() {
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(generateArchiveMediaItemSequence(), 1L)
fun givenAnEmptyTable_whenIWriteToTableAndCommit_thenIExpectFilledTableWithThumbnails() {
val inputCount = 100
val countWithThumbnails = inputCount * 2
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(generateArchiveMediaItemSequence(count = inputCount))
SignalDatabase.backupMediaSnapshots.commitPendingRows()
val newPendingTime = 2L
val newObjectCount = 50
val newObjectCountWithThumbnails = newObjectCount * 2
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(generateArchiveMediaItemSequence(newObjectCount), newPendingTime)
val count = getCountForLatestSnapshot(includeThumbnails = true)
val count = SignalDatabase.backupMediaSnapshots.readableDatabase.count()
.from(BackupMediaSnapshotTable.TABLE_NAME)
.where("${BackupMediaSnapshotTable.LAST_SYNC_TIME} = 1 AND ${BackupMediaSnapshotTable.PENDING_SYNC_TIME} = $newPendingTime")
.run()
.readToSingleInt(-1)
assertThat(count).isEqualTo(newObjectCountWithThumbnails)
assertThat(count).isEqualTo(countWithThumbnails)
}
@Test
fun givenAFilledTable_whenIInsertSimilarIdsAndCommit_thenIExpectCommittedOverrides() {
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(generateArchiveMediaItemSequence(), 1L)
fun givenAFilledTable_whenIReinsertObjects_thenIExpectUncommittedOverrides() {
val initialCount = 100
val additionalCount = 25
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(generateArchiveMediaItemSequence(count = initialCount))
SignalDatabase.backupMediaSnapshots.commitPendingRows()
val newPendingTime = 2L
val newObjectCount = 50
val newObjectCountWithThumbnails = newObjectCount * 2
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(generateArchiveMediaItemSequence(newObjectCount), newPendingTime)
// This relies on how the sequence of mediaIds is generated in tests -- the ones we generate here will have the mediaIds as the ones we generated above
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(generateArchiveMediaItemSequence(count = additionalCount))
val pendingCount = getCountForPending(includeThumbnails = false)
val latestVersionCount = getCountForLatestSnapshot(includeThumbnails = false)
assertThat(pendingCount).isEqualTo(additionalCount)
assertThat(latestVersionCount).isEqualTo(initialCount)
}
@Test
fun givenAFilledTable_whenIReinsertObjectsAndCommit_thenIExpectCommittedOverrides() {
val initialCount = 100
val additionalCount = 25
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(generateArchiveMediaItemSequence(count = initialCount))
SignalDatabase.backupMediaSnapshots.commitPendingRows()
val count = SignalDatabase.backupMediaSnapshots.readableDatabase.count()
.from(BackupMediaSnapshotTable.TABLE_NAME)
.where("${BackupMediaSnapshotTable.LAST_SYNC_TIME} = $newPendingTime AND ${BackupMediaSnapshotTable.PENDING_SYNC_TIME} = $newPendingTime")
.run()
.readToSingleInt(-1)
// This relies on how the sequence of mediaIds is generated in tests -- the ones we generate here will have the mediaIds as the ones we generated above
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(generateArchiveMediaItemSequence(count = additionalCount))
SignalDatabase.backupMediaSnapshots.commitPendingRows()
val total = getTotalItemCount()
val pendingCount = getCountForPending(includeThumbnails = false)
val latestVersionCount = getCountForLatestSnapshot(includeThumbnails = false)
val totalCount = getTotalItemCount(includeThumbnails = false)
assertThat(count).isEqualTo(newObjectCountWithThumbnails)
assertThat(total).isEqualTo(SEQUENCE_COUNT_WITH_THUMBNAILS)
assertThat(pendingCount).isEqualTo(0)
assertThat(latestVersionCount).isEqualTo(additionalCount)
assertThat(totalCount).isEqualTo(initialCount)
}
@Test
fun givenAFilledTable_whenIInsertSimilarIdsAndCommitThenDelete_thenIExpectOnlyCommittedOverrides() {
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(generateArchiveMediaItemSequence(), 1L)
val initialCount = 100
val additionalCount = 25
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(generateArchiveMediaItemSequence(count = initialCount))
SignalDatabase.backupMediaSnapshots.commitPendingRows()
val newPendingTime = 2L
val newObjectCount = 50
val newObjectCountWithThumbnails = newObjectCount * 2
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(generateArchiveMediaItemSequence(newObjectCount), newPendingTime)
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(generateArchiveMediaItemSequence(count = additionalCount))
SignalDatabase.backupMediaSnapshots.commitPendingRows()
val page = SignalDatabase.backupMediaSnapshots.getPageOfOldMediaObjects(currentSyncTime = newPendingTime, pageSize = 100)
SignalDatabase.backupMediaSnapshots.deleteMediaObjects(page)
val page = SignalDatabase.backupMediaSnapshots.getPageOfOldMediaObjects(pageSize = 1_000)
SignalDatabase.backupMediaSnapshots.deleteOldMediaObjects(page)
val total = getTotalItemCount()
val total = getTotalItemCount(includeThumbnails = false)
assertThat(total).isEqualTo(newObjectCountWithThumbnails)
assertThat(total).isEqualTo(additionalCount)
}
@Test
@@ -118,7 +121,7 @@ class BackupMediaSnapshotTableTest {
createArchiveMediaObject(seed = 2, cdn = 2)
)
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(localData.asSequence(), 1L)
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(localData.asSequence())
SignalDatabase.backupMediaSnapshots.commitPendingRows()
val mismatches = SignalDatabase.backupMediaSnapshots.getMediaObjectsWithNonMatchingCdn(remoteData)
@@ -137,13 +140,13 @@ class BackupMediaSnapshotTableTest {
createArchiveMediaObject(seed = 2, cdn = 99)
)
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(localData.asSequence(), 1L)
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(localData.asSequence())
SignalDatabase.backupMediaSnapshots.commitPendingRows()
val mismatches = SignalDatabase.backupMediaSnapshots.getMediaObjectsWithNonMatchingCdn(remoteData)
assertThat(mismatches.size).isEqualTo(1)
assertThat(mismatches.get(0).cdn).isEqualTo(99)
assertThat(mismatches.get(0).digest).isEqualTo(localData.get(1).digest)
assertThat(mismatches[0].cdn).isEqualTo(99)
assertThat(mismatches[0].digest).isEqualTo(localData[1].digest)
}
@Test
@@ -158,7 +161,7 @@ class BackupMediaSnapshotTableTest {
createArchiveMediaObject(seed = 2, cdn = 2)
)
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(localData.asSequence(), 1L)
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(localData.asSequence())
SignalDatabase.backupMediaSnapshots.commitPendingRows()
val notFound = SignalDatabase.backupMediaSnapshots.getMediaObjectsThatCantBeFound(remoteData)
@@ -177,7 +180,7 @@ class BackupMediaSnapshotTableTest {
createArchiveMediaObject(seed = 3, cdn = 2)
)
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(localData.asSequence(), 1L)
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(localData.asSequence())
SignalDatabase.backupMediaSnapshots.commitPendingRows()
val notFound = SignalDatabase.backupMediaSnapshots.getMediaObjectsThatCantBeFound(remoteData)
@@ -185,20 +188,110 @@ class BackupMediaSnapshotTableTest {
assertThat(notFound.first()).isEqualTo(remoteData[1])
}
private fun getTotalItemCount(): Int {
return SignalDatabase.backupMediaSnapshots.readableDatabase.count().from(BackupMediaSnapshotTable.TABLE_NAME).run().readToSingleInt(-1)
@Test
fun getCurrentSnapshotVersion_emptyTable() {
val version = SignalDatabase.backupMediaSnapshots.getCurrentSnapshotVersion()
assertThat(version).isEqualTo(0)
}
private fun getSyncedItemCount(pendingTime: Long): Int {
return SignalDatabase.backupMediaSnapshots.readableDatabase.count()
@Test
fun getCurrentSnapshotVersion_singleCommit() {
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(generateArchiveMediaItemSequence(count = 100))
SignalDatabase.backupMediaSnapshots.commitPendingRows()
val version = SignalDatabase.backupMediaSnapshots.getCurrentSnapshotVersion()
assertThat(version).isEqualTo(1)
}
@Test
fun getMediaObjectsLastSeenOnCdnBeforeSnapshotVersion_noneMarkedSeen() {
val initialCount = 100
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(generateArchiveMediaItemSequence(count = initialCount))
SignalDatabase.backupMediaSnapshots.commitPendingRows()
val notSeenCount = SignalDatabase.backupMediaSnapshots.getMediaObjectsLastSeenOnCdnBeforeSnapshotVersion(1).count
val expectedOldCountIncludingThumbnails = initialCount * 2
assertThat(notSeenCount).isEqualTo(expectedOldCountIncludingThumbnails)
}
@Test
fun getMediaObjectsLastSeenOnCdnBeforeSnapshotVersion_someMarkedSeen() {
val initialCount = 100
val markSeenCount = 25
val itemsToCommit = generateArchiveMediaItemSequence(count = initialCount)
SignalDatabase.backupMediaSnapshots.writePendingMediaObjects(itemsToCommit)
SignalDatabase.backupMediaSnapshots.commitPendingRows()
val normalIdsToMarkSeen = itemsToCommit.take(markSeenCount).map { it.mediaId }.toList()
val thumbnailIdsToMarkSeen = itemsToCommit.take(markSeenCount).map { it.thumbnailMediaId }.toList()
val allItemsToMarkSeen = normalIdsToMarkSeen + thumbnailIdsToMarkSeen
SignalDatabase.backupMediaSnapshots.markSeenOnRemote(allItemsToMarkSeen, 1)
val notSeenCount = SignalDatabase.backupMediaSnapshots.getMediaObjectsLastSeenOnCdnBeforeSnapshotVersion(1).count
val expectedOldCount = initialCount - markSeenCount
val expectedOldCountIncludingThumbnails = expectedOldCount * 2
assertThat(notSeenCount).isEqualTo(expectedOldCountIncludingThumbnails)
}
private fun getTotalItemCount(includeThumbnails: Boolean): Int {
return if (includeThumbnails) {
SignalDatabase.backupMediaSnapshots.readableDatabase
.count()
.from(BackupMediaSnapshotTable.TABLE_NAME)
.run()
.readToSingleInt(0)
} else {
SignalDatabase.backupMediaSnapshots.readableDatabase
.count()
.from(BackupMediaSnapshotTable.TABLE_NAME)
.where("${BackupMediaSnapshotTable.IS_THUMBNAIL} = 0")
.run()
.readToSingleInt(0)
}
}
private fun getCountForLatestSnapshot(includeThumbnails: Boolean): Int {
val thumbnailFilter = if (!includeThumbnails) {
" AND ${BackupMediaSnapshotTable.IS_THUMBNAIL} = 0"
} else {
""
}
return SignalDatabase.backupMediaSnapshots.readableDatabase
.count()
.from(BackupMediaSnapshotTable.TABLE_NAME)
.where("${BackupMediaSnapshotTable.LAST_SYNC_TIME} = $pendingTime AND ${BackupMediaSnapshotTable.PENDING_SYNC_TIME} = $pendingTime")
.where("${BackupMediaSnapshotTable.SNAPSHOT_VERSION} = ${BackupMediaSnapshotTable.MAX_VERSION} AND ${BackupMediaSnapshotTable.SNAPSHOT_VERSION} != ${BackupMediaSnapshotTable.UNKNOWN_VERSION}" + thumbnailFilter)
.run()
.readToSingleInt(-1)
.readToSingleInt(0)
}
private fun generateArchiveMediaItemSequence(count: Int = SEQUENCE_COUNT): Sequence<ArchiveMediaItem> {
return generateSequence(0) { seed -> if (seed < (count - 1)) seed + 1 else null }
private fun getCountForPending(includeThumbnails: Boolean): Int {
val thumbnailFilter = if (!includeThumbnails) {
" AND ${BackupMediaSnapshotTable.IS_THUMBNAIL} = 0"
} else {
""
}
return SignalDatabase.backupMediaSnapshots.readableDatabase
.count()
.from(BackupMediaSnapshotTable.TABLE_NAME)
.where("${BackupMediaSnapshotTable.IS_PENDING} != 0" + thumbnailFilter)
.run()
.readToSingleInt(0)
}
private fun generateArchiveMediaItemSequence(count: Int): Sequence<ArchiveMediaItem> {
return (1..count)
.asSequence()
.map { createArchiveMediaItem(it) }
}