Clean up some stuff around ImportExportTest.

This commit is contained in:
Greyson Parrelli 2024-06-03 10:43:23 -04:00 committed by Cody Henthorne
parent c3ab8dddd0
commit f761008509
3 changed files with 58 additions and 48 deletions

View file

@ -5,11 +5,8 @@
package org.thoughtcrime.securesms.backup.v2
import android.Manifest
import android.app.UiAutomation
import android.os.Environment
import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import io.mockk.InternalPlatformDsl.toArray
import okio.ByteString.Companion.toByteString
import org.junit.Assert
import org.junit.Before
@ -17,6 +14,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestName
import org.signal.core.util.Base64
import org.signal.core.util.test.getObjectDiff
import org.signal.libsignal.messagebackup.MessageBackup
import org.signal.libsignal.messagebackup.MessageBackupKey
import org.signal.libsignal.zkgroup.profiles.ProfileKey
@ -135,6 +133,9 @@ class ImportExportTest {
private val standardFrames = arrayOf(defaultBackupInfo, standardAccountData, selfRecipient, releaseNotes)
}
private val context: Context
get() = InstrumentationRegistry.getInstrumentation().targetContext
@JvmField
@Rule
var testName = TestName()
@ -369,12 +370,12 @@ class ImportExportTest {
}
}
}
val import = exportFrames(
exportFrames(
*standardFrames,
*recipients.toArray(),
*chatItems.toArray()
)
outputFile(import)
}
@Test
@ -565,12 +566,12 @@ class ImportExportTest {
)
)
import(importData)
val exported = export()
val exported = BackupRepository.export()
val expected = exportFrames(
*standardFrames,
alexa
)
outputFile(importData, expected)
compare(expected, exported)
}
@ -994,14 +995,13 @@ class ImportExportTest {
expirationNotStarted
)
import(importData)
val exported = export()
val exported = BackupRepository.export()
val expected = exportFrames(
*standardFrames,
alice,
chat,
expirationNotStarted
)
outputFile(importData, expected)
compare(expected, exported)
}
@ -1396,27 +1396,10 @@ class ImportExportTest {
return outputStream.toByteArray()
}
/**
* Exports the passed in frames as a backup and then attempts to
* import them.
*/
private fun import(vararg objects: Any) {
val importData = exportFrames(*objects)
import(importData)
}
private fun import(importData: ByteArray) {
BackupRepository.import(length = importData.size.toLong(), inputStreamFactory = { ByteArrayInputStream(importData) }, selfData = BackupRepository.SelfData(SELF_ACI, SELF_PNI, SELF_E164, SELF_PROFILE_KEY))
}
/**
* Export our current database as a backup.
*/
private fun export(): ByteArray {
val exportData = BackupRepository.export()
return exportData
}
private fun validate(importData: ByteArray): MessageBackup.ValidationResult {
val factory = { ByteArrayInputStream(importData) }
val masterKey = SignalStore.svr().getOrCreateMasterKey()
@ -1426,10 +1409,12 @@ class ImportExportTest {
}
/**
* Imports the passed in frames and then exports them.
* Given some [Frame]s, this will do the following:
*
* It will do a comparison to assert that the import and export
* are equal.
* 1. Write the frames using an [EncryptedBackupWriter] and keep the result in memory (A).
* 2. Import those frames back into the local database.
* 3. Export the state of the local database and keep the result in memory (B).
* 4. Assert that (A) and (B) are identical. Or, in other words, assert that importing and exporting again results in the original backup data.
*/
private fun importExport(vararg objects: Any) {
val outputStream = ByteArrayOutputStream()
@ -1454,12 +1439,13 @@ class ImportExportTest {
}
}
}
val importData = outputStream.toByteArray()
outputFile(importData)
BackupRepository.import(length = importData.size.toLong(), inputStreamFactory = { ByteArrayInputStream(importData) }, selfData = BackupRepository.SelfData(SELF_ACI, SELF_PNI, SELF_E164, SELF_PROFILE_KEY))
val export = export()
compare(importData, export)
val originalBackupData = outputStream.toByteArray()
BackupRepository.import(length = originalBackupData.size.toLong(), inputStreamFactory = { ByteArrayInputStream(originalBackupData) }, selfData = BackupRepository.SelfData(SELF_ACI, SELF_PNI, SELF_E164, SELF_PROFILE_KEY))
val generatedBackupData = BackupRepository.export()
compare(originalBackupData, generatedBackupData)
}
private fun compare(import: ByteArray, export: ByteArray) {
@ -1513,11 +1499,11 @@ class ImportExportTest {
prettyAssertEquals(stickersImported, stickersExported) { it.packId }
}
private fun <T> prettyAssertEquals(import: List<T>, export: List<T>) {
private inline fun <reified T : Any> prettyAssertEquals(import: List<T>, export: List<T>) {
Assert.assertEquals(import.size, export.size)
import.zip(export).forEach { (a1, a2) ->
if (a1 != a2) {
Assert.fail("Items do not match: \n $a1 \n $a2")
Assert.fail("Items do not match:\n\n-- Pretty diff\n${getObjectDiff(a1, a2)}\n-- Full objects\n$a1\n$a2")
}
}
}
@ -1526,7 +1512,7 @@ class ImportExportTest {
return nextFloat() < prob
}
private fun <T, R : Comparable<R>> prettyAssertEquals(import: List<T>, export: List<T>, selector: (T) -> R?) {
private inline fun <reified T : Any, R : Comparable<R>> prettyAssertEquals(import: List<T>, export: List<T>, crossinline selector: (T) -> R?) {
if (import.size != export.size) {
var msg = StringBuilder()
for (i in import) {
@ -1562,9 +1548,8 @@ class ImportExportTest {
return frames
}
private fun outputFile(importBytes: ByteArray, resultBytes: ByteArray? = null) {
grantPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
val dir = File(Environment.getExternalStorageDirectory(), "backup-tests")
private fun writeToOutputFile(importBytes: ByteArray, resultBytes: ByteArray? = null) {
val dir = File(context.filesDir, "backup-tests")
if (dir.mkdirs() || dir.exists()) {
FileOutputStream(File(dir, testName.methodName + ".import")).use {
it.write(importBytes)
@ -1579,11 +1564,4 @@ class ImportExportTest {
}
}
}
private fun grantPermissions(vararg permissions: String?) {
val auto: UiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
for (perm in permissions) {
auto.grantRuntimePermissionAsUser(InstrumentationRegistry.getInstrumentation().targetContext.packageName, perm, android.os.Process.myUserHandle())
}
}
}

View file

@ -17,6 +17,8 @@ java {
}
dependencies {
implementation(libs.kotlin.reflect)
testImplementation(testLibs.junit.junit)
testImplementation(testLibs.assertj.core)
}

View file

@ -0,0 +1,30 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.core.util.test
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.isAccessible
/**
* Returns a string containing the differences between the expected and actual objects.
* Useful for diffing complex data classes in your tests.
*/
inline fun <reified T : Any> getObjectDiff(expected: T, actual: T): String {
val builder = StringBuilder()
val properties = T::class.memberProperties
for (prop in properties) {
prop.isAccessible = true
val expectedValue = prop.get(expected)
val actualValue = prop.get(actual)
if (expectedValue != actualValue) {
builder.append("[${prop.name}] Expected: $expectedValue, Actual: $actualValue\n")
}
}
return builder.toString()
}