Merge pull request #748 from seedvault-app/feature/cirrus-ci-tests

Run tests in Cirrus CI again
This commit is contained in:
Torsten Grote 2024-10-18 10:34:18 -03:00 committed by GitHub
commit 0fa14025b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 234 additions and 154 deletions

View file

@ -1,13 +1,66 @@
task:
name: Build with AOSP
only_if: $CIRRUS_PR_LABELS =~ ".*aosp-build.*"
timeout_in: 70m
container:
image: ubuntu:23.04
cpu: 8
memory: 32G
build_script:
- ./.github/scripts/build_aosp.sh aosp_arm64 ap1a userdebug android-14.0.0_r29
container:
image: ghcr.io/cirruslabs/android-sdk:34
kvm: true
cpu: 8
memory: 16G
instrumentation_tests_task:
name: "Cirrus CI Instrumentation Tests"
start_avd_background_script:
sdkmanager --install "system-images;android-34;default;x86_64" "emulator";
echo no | avdmanager create avd -n seedvault -k "system-images;android-34;default;x86_64";
$ANDROID_HOME/emulator/emulator
-avd seedvault
-no-audio
-no-boot-anim
-gpu swiftshader_indirect
-no-snapshot
-no-window
-writable-system;
provision_avd_background_script:
wget https://github.com/seedvault-app/seedvault-test-data/releases/download/3/backup.tar.gz;
adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;';
adb root;
sleep 5;
adb remount;
adb reboot;
adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;';
adb root;
sleep 5;
adb remount;
sleep 5;
assemble_script:
./gradlew :app:assembleRelease :contacts:assembleRelease assembleAndroidTest
install_app_script:
timeout 180s bash -c 'while [[ -z $(adb shell mount | grep "/system " | grep "(rw,") ]]; do sleep 1; done;';
adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;';
adb shell mkdir -p /sdcard/seedvault_baseline;
adb push backup.tar.gz /sdcard/seedvault_baseline/backup.tar.gz;
adb shell tar xzf /sdcard/seedvault_baseline/backup.tar.gz --directory=/sdcard/seedvault_baseline;
adb shell mkdir -p /system/priv-app/Seedvault;
adb push app/build/outputs/apk/release/app-release.apk /system/priv-app/Seedvault/Seedvault.apk;
adb push permissions_com.stevesoltys.seedvault.xml /system/etc/permissions/privapp-permissions-seedvault.xml;
adb push allowlist_com.stevesoltys.seedvault.xml /system/etc/sysconfig/allowlist-seedvault.xml;
adb shell mkdir -p /system/priv-app/ContactsBackup;
adb push contactsbackup/build/outputs/apk/release/contactsbackup-release.apk /system/priv-app/ContactsBackup/contactsbackup.apk;
adb push contactsbackup/default-permissions_org.calyxos.backup.contacts.xml /system/etc/default-permissions/default-permissions_org.calyxos.backup.contacts.xml;
adb shell bmgr enable true;
adb reboot;
adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;';
adb shell bmgr transport com.stevesoltys.seedvault.transport.ConfigurableBackupTransport;
adb reboot;
adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;';
run_large_tests_script: ./gradlew -Pandroid.testInstrumentationRunnerArguments.size=large :app:connectedAndroidTest
run_other_tests_script: ./gradlew -Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest connectedAndroidTest
always:
seedvault_artifacts:
path: Seedvault.apk
pull_screenshots_script:
adb pull /sdcard/seedvault_test_results
screenshots_artifacts:
path: "seedvault_test_results/**/*.mp4"
logcat_artifacts:
path: "seedvault_test_results/**/*.log"

View file

@ -32,13 +32,6 @@ android {
versionNameSuffix = "-${gitDescribe()}"
testInstrumentationRunner = "com.stevesoltys.seedvault.KoinInstrumentationTestRunner"
testInstrumentationRunnerArguments["disableAnalytics"] = "true"
if (project.hasProperty("instrumented_test_size")) {
val testSize = project.property("instrumented_test_size").toString()
println("Instrumented test size: $testSize")
testInstrumentationRunnerArguments["size"] = testSize
}
}
signingConfigs {

View file

@ -9,7 +9,6 @@ import android.content.pm.PackageInfo
import android.os.ParcelFileDescriptor
import androidx.test.uiautomator.Until
import com.stevesoltys.seedvault.e2e.io.BackupDataInputIntercept
import com.stevesoltys.seedvault.e2e.io.InputStreamIntercept
import com.stevesoltys.seedvault.e2e.screen.impl.BackupScreen
import com.stevesoltys.seedvault.transport.backup.FullBackup
import com.stevesoltys.seedvault.transport.backup.InputFactory
@ -21,8 +20,10 @@ import io.mockk.every
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.calyxos.seedvault.core.toHexString
import org.koin.core.component.get
import java.io.ByteArrayOutputStream
import java.security.DigestInputStream
import java.security.MessageDigest
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.test.fail
@ -154,7 +155,8 @@ internal interface LargeBackupTestBase : LargeTestBase {
private fun spyOnFullBackupData(backupResult: SeedvaultLargeTestResult) {
var packageName: String? = null
var dataIntercept = ByteArrayOutputStream()
val messageDigest = MessageDigest.getInstance("SHA-256")
var digestInputStream: DigestInputStream? = null
coEvery {
spyFullBackup.performFullBackup(any(), any(), any())
@ -166,20 +168,19 @@ internal interface LargeBackupTestBase : LargeTestBase {
every {
spyInputFactory.getInputStream(any())
} answers {
InputStreamIntercept(
inputStream = callOriginal(),
intercept = dataIntercept
)
digestInputStream = DigestInputStream(callOriginal(), messageDigest)
digestInputStream!!
}
coEvery {
spyFullBackup.finishBackup()
} answers {
val result = callOriginal()
backupResult.full[packageName!!] = dataIntercept.toByteArray().sha256()
val digest = digestInputStream?.messageDigest ?: fail("No digestInputStream")
backupResult.full[packageName!!] = digest.digest().toHexString()
packageName = null
dataIntercept = ByteArrayOutputStream()
digest.reset()
result
}
}
@ -192,9 +193,6 @@ internal interface LargeBackupTestBase : LargeTestBase {
every {
spyBackupNotificationManager.onBackupSuccess(any(), any(), any())
} answers {
val success = firstArg<Boolean>()
assert(success) { "Backup failed." }
callOriginal()
completed.set(true)
}

View file

@ -8,12 +8,13 @@ package com.stevesoltys.seedvault.e2e
import android.content.pm.PackageInfo
import android.os.ParcelFileDescriptor
import com.stevesoltys.seedvault.e2e.io.BackupDataOutputIntercept
import com.stevesoltys.seedvault.e2e.io.OutputStreamIntercept
import com.stevesoltys.seedvault.e2e.screen.impl.RecoveryCodeScreen
import com.stevesoltys.seedvault.e2e.screen.impl.RestoreScreen
import com.stevesoltys.seedvault.transport.restore.FullRestore
import com.stevesoltys.seedvault.transport.restore.KVRestore
import com.stevesoltys.seedvault.transport.restore.OutputFactory
import io.mockk.Call
import io.mockk.MockKAnswerScope
import io.mockk.clearMocks
import io.mockk.coEvery
import io.mockk.every
@ -22,8 +23,11 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import org.calyxos.seedvault.core.toHexString
import org.koin.core.component.get
import java.io.ByteArrayOutputStream
import java.security.DigestOutputStream
import java.security.MessageDigest
import kotlin.test.fail
internal interface LargeRestoreTestBase : LargeTestBase {
@ -161,14 +165,26 @@ internal interface LargeRestoreTestBase : LargeTestBase {
clearMocks(spyKVRestore)
coEvery {
spyKVRestore.initializeState(any(), any(), any(), any())
} answers {
packageName = arg<PackageInfo>(3).packageName
fun initializeStateBlock(
packageInfoIndex: Int
): MockKAnswerScope<Unit, Unit>.(Call) -> Unit = {
packageName = arg<PackageInfo>(packageInfoIndex).packageName
restoreResult.kv[packageName!!] = mutableMapOf()
callOriginal()
}
coEvery {
spyKVRestore.initializeState(any(), any(), any(), any())
} answers initializeStateBlock(1)
coEvery {
spyKVRestore.initializeStateV1(any(), any(), any(), any())
} answers initializeStateBlock(2)
coEvery {
spyKVRestore.initializeStateV0(any(), any())
} answers initializeStateBlock(1)
every {
spyOutputFactory.getBackupDataOutput(any())
} answers {
@ -182,47 +198,61 @@ internal interface LargeRestoreTestBase : LargeTestBase {
private fun spyOnFullRestoreData(restoreResult: SeedvaultLargeTestResult) {
var packageName: String? = null
var dataIntercept = ByteArrayOutputStream()
val messageDigest = MessageDigest.getInstance("SHA-256")
var digestOutputStream: DigestOutputStream? = null
clearMocks(spyFullRestore)
coEvery {
spyFullRestore.initializeState(any(), any(), any())
} answers {
fun initializeStateBlock(
packageInfoIndex: Int
): MockKAnswerScope<Unit, Unit>.(Call) -> Unit = {
packageName?.let {
restoreResult.full[it] = dataIntercept.toByteArray().sha256()
// sometimes finishRestore() doesn't get called, so get data from last package here
digestOutputStream?.messageDigest?.let { digest ->
restoreResult.full[packageName!!] = digest.digest().toHexString()
}
}
packageName = arg<PackageInfo>(3).packageName
dataIntercept = ByteArrayOutputStream()
packageName = arg<PackageInfo>(packageInfoIndex).packageName
callOriginal()
}
coEvery {
spyFullRestore.initializeState(any(), any(), any())
} answers initializeStateBlock(1)
coEvery {
spyFullRestore.initializeStateV1(any(), any(), any())
} answers initializeStateBlock(2)
coEvery {
spyFullRestore.initializeStateV0(any(), any())
} answers initializeStateBlock(1)
every {
spyOutputFactory.getOutputStream(any())
} answers {
OutputStreamIntercept(
outputStream = callOriginal(),
intercept = dataIntercept
)
digestOutputStream = DigestOutputStream(callOriginal(), messageDigest)
digestOutputStream!!
}
every {
spyFullRestore.abortFullRestore()
} answers {
packageName = null
dataIntercept = ByteArrayOutputStream()
digestOutputStream?.messageDigest?.reset()
callOriginal()
}
every {
spyFullRestore.finishRestore()
} answers {
restoreResult.full[packageName!!] = dataIntercept.toByteArray().sha256()
val digest = digestOutputStream?.messageDigest ?: fail("No digestOutputStream")
restoreResult.full[packageName!!] = digest.digest().toHexString()
packageName = null
dataIntercept = ByteArrayOutputStream()
digest.reset()
callOriginal()
}
}

View file

@ -49,14 +49,14 @@ internal interface LargeTestBase : KoinComponent {
companion object {
private const val TEST_STORAGE_FOLDER = "seedvault_test"
private const val TEST_VIDEO_FOLDER = "seedvault_test_results"
private const val TEST_RESULT_FOLDER = "seedvault_test_results"
}
val externalStorageDir: String get() = Environment.getExternalStorageDirectory().absolutePath
val testStoragePath get() = "$externalStorageDir/$TEST_STORAGE_FOLDER"
val testVideoPath get() = "$externalStorageDir/$TEST_VIDEO_FOLDER"
val testResultPath get() = "$externalStorageDir/$TEST_RESULT_FOLDER"
val targetContext: Context
get() = InstrumentationRegistry.getInstrumentation().targetContext
@ -123,7 +123,7 @@ internal interface LargeTestBase : KoinComponent {
keepRecordingScreen: AtomicBoolean,
testName: String,
) {
val folder = testVideoPath
val folder = testResultPath
runCommand("mkdir -p $folder")
val fileName = testResultFilename(testName)
@ -149,7 +149,7 @@ internal interface LargeTestBase : KoinComponent {
// write logcat to file
val fileName = testResultFilename(testName)
runCommand("logcat -d -f $testVideoPath/$fileName.log")
runCommand("logcat -d -f $testResultPath/$fileName.log")
}
fun uninstallPackages(packages: Collection<PackageInfo>) {
@ -162,7 +162,7 @@ internal interface LargeTestBase : KoinComponent {
fun clearTestBackups() {
File(testStoragePath).deleteRecursively()
File(testVideoPath).deleteRecursively()
File(testResultPath).deleteRecursively()
}
fun changeBackupLocation(
@ -225,6 +225,7 @@ internal interface LargeTestBase : KoinComponent {
fun confirmCode() {
RecoveryCodeScreen {
startNewBackupButton.click()
confirmCodeButton.click()
verifyCodeButton.scrollTo().click()

View file

@ -45,9 +45,8 @@ internal abstract class SeedvaultLargeTest :
clearTestBackups()
runCommand("bmgr enable true")
sleep(60_000)
runCommand("bmgr transport com.stevesoltys.seedvault.transport.ConfigurableBackupTransport")
sleep(60_000)
sleep(5000)
startRecordingTest(keepRecordingScreen, name.methodName)
restoreBaselineBackup()

View file

@ -5,11 +5,14 @@
package com.stevesoltys.seedvault.e2e.impl
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.util.Log
import androidx.test.filters.LargeTest
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.e2e.SeedvaultLargeTest
import com.stevesoltys.seedvault.e2e.SeedvaultLargeTestResult
import com.stevesoltys.seedvault.metadata.PackageState
import com.stevesoltys.seedvault.transport.backup.isStopped
import org.junit.Test
@LargeTest
@ -29,6 +32,9 @@ internal class BackupRestoreTest : SeedvaultLargeTest() {
changeBackupLocation()
}
launchStoppedApps()
launchBackupActivity()
val backupResult = performBackup()
assertValidBackupMetadata(backupResult)
@ -58,6 +64,28 @@ internal class BackupRestoreTest : SeedvaultLargeTest() {
}
}
private fun launchStoppedApps() {
val packageManager = targetContext.packageManager
val notBackedUp = packageService.notBackedUpPackages
notBackedUp.forEach { packageInfo ->
val i = packageManager.getLaunchIntentForPackage(packageInfo.packageName)?.apply {
addFlags(FLAG_ACTIVITY_NEW_TASK)
}
Log.i("TEST", "Launching $i")
try {
targetContext.startActivity(i)
} catch (e: Exception) {
Log.e("TEST", "Could not launch activity for ${packageInfo.packageName}", e)
}
waitUntilIdle()
}
waitUntilIdle()
notBackedUp.forEach { packageInfo ->
val pi = packageManager.getPackageInfo(packageInfo.packageName, 0)
Log.e("TEST", "${packageInfo.packageName} isStopped: ${pi.isStopped()}")
}
}
private fun assertValidResults(
backup: SeedvaultLargeTestResult,
restore: SeedvaultLargeTestResult,

View file

@ -1,31 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/
package com.stevesoltys.seedvault.e2e.io
import java.io.ByteArrayOutputStream
import java.io.InputStream
class InputStreamIntercept(
private val inputStream: InputStream,
private val intercept: ByteArrayOutputStream
) : InputStream() {
override fun read(): Int {
val byte = inputStream.read()
if (byte != -1) {
intercept.write(byte)
}
return byte
}
override fun read(buffer: ByteArray, offset: Int, length: Int): Int {
val bytesRead = inputStream.read(buffer, offset, length)
if (bytesRead != -1) {
intercept.write(buffer, offset, bytesRead)
}
return bytesRead
}
}

View file

@ -1,25 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/
package com.stevesoltys.seedvault.e2e.io
import java.io.ByteArrayOutputStream
import java.io.OutputStream
class OutputStreamIntercept(
private val outputStream: OutputStream,
private val intercept: ByteArrayOutputStream
) : OutputStream() {
override fun write(byte: Int) {
intercept.write(byte)
outputStream.write(byte)
}
override fun write(buffer: ByteArray, offset: Int, length: Int) {
intercept.write(buffer, offset, length)
outputStream.write(buffer, offset, length)
}
}

View file

@ -24,7 +24,7 @@ object BackupScreen : UiDeviceScreen<BackupScreen>() {
val internalStorageButton = findObject { textContains(Build.MODEL) }
val useAnywayButton = findObject { text("USE ANYWAY") }
val useAnywayButton = findObject { text("Use anyway") }
val initializingText: BySelector = By.textContains("Initializing backup location")
}

View file

@ -9,6 +9,8 @@ import com.stevesoltys.seedvault.e2e.screen.UiDeviceScreen
object RecoveryCodeScreen : UiDeviceScreen<RecoveryCodeScreen>() {
val startNewBackupButton = findObject { text("Start new") }
val confirmCodeButton = findObject { text("Confirm code") }
val verifyCodeButton = findObject { text("Verify") }

View file

@ -9,7 +9,9 @@ import com.stevesoltys.seedvault.e2e.screen.UiDeviceScreen
object RestoreScreen : UiDeviceScreen<RestoreScreen>() {
val backupListItem = findObject { textContains("Last backup") }
val backupListItem = findObject {
textContains("Android SDK") // device name of test backups
}
val appsSelectedButton = findObject { text("Restore backup") }

View file

@ -13,7 +13,6 @@ import android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_ERROR_PREFLIGHT
import android.app.backup.IBackupManagerMonitor
import android.os.Bundle
import android.util.Log
import android.util.Log.DEBUG
private val TAG = BackupMonitor::class.java.name
@ -29,14 +28,11 @@ open class BackupMonitor : IBackupManagerMonitor.Stub() {
}
open fun onEvent(id: Int, category: Int, packageName: String?, bundle: Bundle) {
Log.d(TAG, "${packageName?.padEnd(64, ' ')} cat: $category id: $id")
if (id == LOG_EVENT_ID_ERROR_PREFLIGHT) {
val preflightResult = bundle.getLong(EXTRA_LOG_PREFLIGHT_ERROR, -1)
Log.w(TAG, "Pre-flight error from $packageName: $preflightResult")
}
if (!Log.isLoggable(TAG, DEBUG)) return
Log.d(TAG, "ID: $id")
Log.d(TAG, "CATEGORY: $category")
Log.d(TAG, "PACKAGE: $packageName")
}
}

View file

@ -25,7 +25,10 @@ class BackendManager(
backendFactory: BackendFactory,
) {
@Volatile
private var mBackend: Backend?
@Volatile
private var mBackendProperties: BackendProperties<*>?
val backend: Backend
@ -81,6 +84,8 @@ class BackendManager(
* IMPORTANT: Do no call this while current plugins are being used,
* e.g. while backup/restore operation is still running.
*/
@WorkerThread
@Synchronized
fun <T> changePlugins(
backend: Backend,
storageProperties: BackendProperties<T>,

View file

@ -90,6 +90,7 @@ internal class SafHandler(
return false
}
@WorkerThread
fun setPlugin(safProperties: SafProperties) {
backendManager.changePlugins(
backend = backendFactory.createSafBackend(safProperties),

View file

@ -86,6 +86,7 @@ internal class WebDavHandler(
settingsManager.saveWebDavConfig(properties.config)
}
@WorkerThread
fun setPlugin(properties: WebDavProperties, backend: Backend) {
backendManager.changePlugins(
backend = backend,

View file

@ -7,6 +7,7 @@ package com.stevesoltys.seedvault.repo
import android.content.Context
import android.content.Context.MODE_APPEND
import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.MemoryLogger
import com.stevesoltys.seedvault.proto.Snapshot
import com.stevesoltys.seedvault.proto.Snapshot.Blob
@ -90,6 +91,7 @@ class BlobCache(
* * changing to a different backup to prevent usage of blobs that don't exist there
* * uploading a new snapshot to prevent the persistent cache from growing indefinitely
*/
@WorkerThread
fun clearLocalCache() {
log.info { "Clearing local cache..." }
context.deleteFile(CACHE_FILE_NAME)

View file

@ -161,6 +161,9 @@ internal class KVRestore(
} catch (e: AEADBadTagException) {
Log.e(TAG, "Decryption failed", e)
TRANSPORT_ERROR
} catch (e: Exception) {
Log.e(TAG, "Unknown error", e)
TRANSPORT_ERROR
} finally {
dbManager.deleteDb(state.packageInfo.packageName, true)
this.state = null

View file

@ -107,8 +107,12 @@ class RecoveryCodeInputFragment : Fragment() {
super.onViewCreated(view, savedInstanceState)
view.requireViewById<Toolbar>(R.id.toolbar).apply {
setNavigationOnClickListener {
requireActivity().onBackPressedDispatcher.onBackPressed()
if (viewModel.isRestore) {
visibility = GONE
} else {
setNavigationOnClickListener {
requireActivity().onBackPressedDispatcher.onBackPressed()
}
}
}

View file

@ -10,6 +10,7 @@ import android.app.backup.IBackupManager
import android.app.job.JobInfo
import android.os.UserHandle
import android.util.Log
import androidx.annotation.UiThread
import androidx.lifecycle.viewModelScope
import androidx.work.ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE
import com.stevesoltys.seedvault.R
@ -23,6 +24,7 @@ import com.stevesoltys.seedvault.worker.AppBackupWorker
import com.stevesoltys.seedvault.worker.BackupRequester.Companion.requestFilesAndAppBackup
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.calyxos.backup.storage.api.StorageBackup
import org.calyxos.backup.storage.backup.BackupJobService
import org.calyxos.seedvault.core.backends.Backend
@ -46,25 +48,38 @@ internal class BackupStorageViewModel(
override val isRestoreOperation = false
@UiThread
override fun onSafUriSet(safProperties: SafProperties) {
safHandler.save(safProperties)
safHandler.setPlugin(safProperties)
if (safProperties.isUsb) {
// disable storage backup if new storage is on USB
cancelBackupWorkers()
} else {
// enable it, just in case the previous storage was on USB,
// also to update the network requirement of the new storage
scheduleBackupWorkers()
viewModelScope.launch {
withContext(Dispatchers.IO) {
safHandler.setPlugin(safProperties)
}
withContext(Dispatchers.Main) { // UiThread
if (safProperties.isUsb) {
// disable storage backup if new storage is on USB
cancelBackupWorkers()
} else {
// enable it, just in case the previous storage was on USB,
// also to update the network requirement of the new storage
scheduleBackupWorkers()
}
onStorageLocationSet(safProperties.isUsb)
}
}
onStorageLocationSet(safProperties.isUsb)
}
override fun onWebDavConfigSet(properties: WebDavProperties, backend: Backend) {
webdavHandler.save(properties)
webdavHandler.setPlugin(properties, backend)
scheduleBackupWorkers()
onStorageLocationSet(isUsb = false)
viewModelScope.launch {
withContext(Dispatchers.IO) {
webdavHandler.setPlugin(properties, backend)
}
withContext(Dispatchers.Main) {
scheduleBackupWorkers()
onStorageLocationSet(isUsb = false)
}
}
}
private fun onStorageLocationSet(isUsb: Boolean) {

View file

@ -124,6 +124,8 @@ class AppBackupWorker(
Result.retry()
} else {
val result = doBackup()
// show error notification if backup wasn't successful (maybe only when no retry?)
if (result != Result.success()) nm.onBackupError()
// only allow retrying if rescheduling is allowed
if (tags.contains(TAG_RESCHEDULE)) return result
else Result.success()

View file

@ -2,8 +2,6 @@
* SPDX-FileCopyrightText: 2020 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/
import java.io.FileInputStream
import java.util.Properties
plugins {
alias(libs.plugins.android.application)
@ -42,23 +40,18 @@ android {
isReturnDefaultValues = true
}
// optional signingConfigs
// On userdebug builds, you can use the testkey here to update the system app
val keystorePropertiesFile = project.file("keystore.properties")
if (keystorePropertiesFile.exists()) {
val keystoreProperties = Properties()
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
signingConfigs {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = file(keystoreProperties["storeFile"] as String)
storePassword = keystoreProperties["storePassword"] as String
}
signingConfigs {
create("aosp") {
keyAlias = "android"
keyPassword = "android"
storePassword = "android"
storeFile = file("testkey.jks")
}
buildTypes.getByName("release").signingConfig = signingConfigs.getByName("release")
buildTypes.getByName("debug").signingConfig = signingConfigs.getByName("release")
}
buildTypes {
getByName("release").signingConfig = signingConfigs.getByName("aosp")
getByName("debug").signingConfig = signingConfigs.getByName("aosp")
}
}
@ -74,6 +67,7 @@ dependencies {
androidTestImplementation(libs.kotlin.stdlib.jdk8)
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation(
"androidx.test.espresso:espresso-core:${libs.versions.espresso.get()}")
"androidx.test.espresso:espresso-core:${libs.versions.espresso.get()}"
)
androidTestImplementation("io.mockk:mockk-android:${libs.versions.mockk.get()}")
}

View file

@ -5,9 +5,12 @@
package org.calyxos.backup.contacts
import android.Manifest.permission.READ_CONTACTS
import android.Manifest.permission.WRITE_CONTACTS
import android.app.backup.BackupAgent
import android.app.backup.BackupAgent.TYPE_FILE
import android.app.backup.FullBackupDataOutput
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.os.ParcelFileDescriptor
import android.os.ParcelFileDescriptor.MODE_READ_ONLY
import androidx.test.ext.junit.runners.AndroidJUnit4
@ -19,6 +22,7 @@ import org.calyxos.backup.contacts.ContactsBackupAgent.BACKUP_FILE
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Assume.assumeTrue
import org.junit.Test
import org.junit.runner.RunWith
import java.io.File
@ -42,6 +46,9 @@ class BackupRestoreTest {
@Test
fun testBackupAndRestore() {
val hasReadPermission = context.checkSelfPermission(READ_CONTACTS) == PERMISSION_GRANTED
val hasWritePermission = context.checkSelfPermission(WRITE_CONTACTS) == PERMISSION_GRANTED
assumeTrue(hasReadPermission && hasWritePermission)
assertEquals(
"Test will remove *all* contacts and thus requires empty address book",
0,

BIN
contactsbackup/testkey.jks Normal file

Binary file not shown.