From f761008509e7678c460c5e4a4ae295b2b7d556b4 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Mon, 3 Jun 2024 10:43:23 -0400 Subject: [PATCH] Clean up some stuff around ImportExportTest. --- .../securesms/backup/v2/ImportExportTest.kt | 74 +++++++------------ core-util-jvm/build.gradle.kts | 2 + .../org/signal/core/util/test/TestUtils.kt | 30 ++++++++ 3 files changed, 58 insertions(+), 48 deletions(-) create mode 100644 core-util-jvm/src/main/java/org/signal/core/util/test/TestUtils.kt diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ImportExportTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ImportExportTest.kt index 4b30851223..7db281c195 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ImportExportTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ImportExportTest.kt @@ -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 prettyAssertEquals(import: List, export: List) { + private inline fun prettyAssertEquals(import: List, export: List) { 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 > prettyAssertEquals(import: List, export: List, selector: (T) -> R?) { + private inline fun > prettyAssertEquals(import: List, export: List, 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()) - } - } } diff --git a/core-util-jvm/build.gradle.kts b/core-util-jvm/build.gradle.kts index b9dd6777b2..baa9844230 100644 --- a/core-util-jvm/build.gradle.kts +++ b/core-util-jvm/build.gradle.kts @@ -17,6 +17,8 @@ java { } dependencies { + implementation(libs.kotlin.reflect) + testImplementation(testLibs.junit.junit) testImplementation(testLibs.assertj.core) } diff --git a/core-util-jvm/src/main/java/org/signal/core/util/test/TestUtils.kt b/core-util-jvm/src/main/java/org/signal/core/util/test/TestUtils.kt new file mode 100644 index 0000000000..1a3242452a --- /dev/null +++ b/core-util-jvm/src/main/java/org/signal/core/util/test/TestUtils.kt @@ -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 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() +}