-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add end-to-end emulator test running on CI
- Loading branch information
1 parent
eaf4e6d
commit 5c9b76b
Showing
15 changed files
with
442 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
instrumented_task_test: | ||
name: Run Android emulator tests | ||
timeout_in: 30m | ||
only_if: $CIRRUS_BRANCH == "main" || CIRRUS_PR != "" | ||
skip: "!changesInclude('.cirrus.yml', '*.gradle', '*.gradle.kts', '**/*.gradle', '**/*.gradle.kts', '*.properties', '**/*.properties', '**/*.kt', '**/*.xml')" | ||
env: | ||
JAVA_TOOL_OPTIONS: -Xmx6g | ||
GRADLE_OPTS: -Dorg.gradle.daemon=false -Dkotlin.incremental=false -Dkotlin.compiler.execution.strategy=in-process | ||
container: | ||
image: reactivecircus/android-emulator-33:latest | ||
kvm: true | ||
cpu: 8 | ||
memory: 24G | ||
|
||
create_device_script: | ||
./gradlew :app:provisionEmulator | ||
start_emulator_background_script: | ||
$ANDROID_HOME/emulator/emulator -avd "seedvault" -no-window -gpu swiftshader_indirect -writable-system -no-snapshot -noaudio -no-boot-anim -camera-back none | ||
assemble_instrumented_tests_script: | ||
./gradlew assembleDebugAndroidTest | ||
wait_for_emulator_script: | ||
adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 3; done; input keyevent 82' | ||
disable_animations_script: | | ||
adb shell settings put global window_animation_scale 0.0 | ||
adb shell settings put global transition_animation_scale 0.0 | ||
adb shell settings put global animator_duration_scale 0.0 | ||
run_instrumented_tests_script: | ||
./gradlew connectedAndroidTest |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
#!/usr/bin/env bash | ||
|
||
# assert ANDROID_HOME is set | ||
if [ -z "$ANDROID_SDK_HOME" ]; then | ||
echo "ANDROID_SDK_HOME is not set" | ||
exit 1 | ||
fi | ||
|
||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) | ||
DEVELOPMENT_DIR=$SCRIPT_DIR/.. | ||
ROOT_PROJECT_DIR=$SCRIPT_DIR/../../.. | ||
|
||
EMULATOR_DEVICE_NAME=$($ANDROID_SDK_HOME/platform-tools/adb devices | grep emulator | cut -f1) | ||
|
||
if [ -z "$EMULATOR_DEVICE_NAME" ]; then | ||
echo "Emulator device name not found" | ||
exit 1 | ||
fi | ||
|
||
ADB="$ANDROID_SDK_HOME/platform-tools/adb -s $EMULATOR_DEVICE_NAME" | ||
|
||
$ADB shell pm clear com.stevesoltys.seedvault |
35 changes: 35 additions & 0 deletions
35
app/src/androidTest/java/com/stevesoltys/seedvault/KoinInstrumentationTestApp.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package com.stevesoltys.seedvault | ||
|
||
import androidx.test.platform.app.InstrumentationRegistry | ||
import com.stevesoltys.seedvault.restore.RestoreViewModel | ||
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager | ||
import io.mockk.spyk | ||
import org.koin.core.module.Module | ||
import org.koin.dsl.module | ||
|
||
private val spyBackupNotificationManager = spyk( | ||
BackupNotificationManager( | ||
InstrumentationRegistry.getInstrumentation() | ||
.targetContext.applicationContext | ||
) | ||
) | ||
|
||
class KoinInstrumentationTestApp : App() { | ||
|
||
override fun appModules(): List<Module> { | ||
val testModule = module { | ||
single { spyBackupNotificationManager } | ||
|
||
single { | ||
spyk( | ||
RestoreViewModel( | ||
this@KoinInstrumentationTestApp, | ||
get(), get(), get(), get(), get(), get() | ||
) | ||
) | ||
} | ||
} | ||
|
||
return super.appModules().plus(testModule) | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
app/src/androidTest/java/com/stevesoltys/seedvault/KoinInstrumentationTestRunner.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package com.stevesoltys.seedvault | ||
|
||
import android.app.Application | ||
import android.content.Context | ||
import androidx.test.runner.AndroidJUnitRunner | ||
|
||
class KoinInstrumentationTestRunner : AndroidJUnitRunner() { | ||
|
||
override fun newApplication( | ||
classLoader: ClassLoader?, | ||
className: String?, | ||
context: Context?, | ||
): Application { | ||
return super.newApplication( | ||
classLoader, | ||
KoinInstrumentationTestApp::class.java.name, | ||
context | ||
) | ||
} | ||
} |
135 changes: 135 additions & 0 deletions
135
app/src/androidTest/java/com/stevesoltys/seedvault/e2e/BackupRestoreTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package com.stevesoltys.seedvault.e2e | ||
|
||
import androidx.test.filters.LargeTest | ||
import com.stevesoltys.seedvault.e2e.screen.impl.RestoreScreen | ||
import com.stevesoltys.seedvault.restore.RestoreViewModel | ||
import com.stevesoltys.seedvault.transport.backup.PackageService | ||
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager | ||
import io.mockk.every | ||
import kotlinx.coroutines.delay | ||
import kotlinx.coroutines.runBlocking | ||
import kotlinx.coroutines.withTimeout | ||
import org.junit.Test | ||
import org.koin.core.component.inject | ||
import java.lang.Thread.sleep | ||
|
||
@LargeTest | ||
class BackupRestoreTest : LargeTestBase() { | ||
|
||
private val packageService: PackageService by inject() | ||
|
||
private val spyBackupNotificationManager: BackupNotificationManager by inject() | ||
|
||
private val restoreViewModel: RestoreViewModel by inject() | ||
|
||
companion object { | ||
private const val BACKUP_TIMEOUT = 360 * 1000L | ||
} | ||
|
||
@Test | ||
fun `back up and restore applications`() = run { | ||
launchBackupActivity() | ||
verifyCode() | ||
waitUntilIdle() | ||
|
||
chooseBackupLocation() | ||
|
||
val eligiblePackages = launchAllEligibleApps() | ||
performBackup(eligiblePackages) | ||
uninstallAllApps(eligiblePackages) | ||
performRestore() | ||
|
||
val packagesAfterRestore = getEligibleApps() | ||
assert(eligiblePackages == packagesAfterRestore) | ||
} | ||
|
||
private fun getEligibleApps() = packageService.userApps | ||
.map { it.packageName }.toSet() | ||
|
||
private fun launchAllEligibleApps(): Set<String> { | ||
return getEligibleApps().onEach { | ||
val intent = device.targetContext.packageManager.getLaunchIntentForPackage(it) | ||
|
||
device.targetContext.startActivity(intent) | ||
waitUntilIdle() | ||
} | ||
} | ||
|
||
private fun uninstallAllApps(packages: Set<String>) { | ||
packages.forEach { runCommand("pm uninstall $it") } | ||
} | ||
|
||
private fun performBackup(expectedPackages: Set<String>) = run { | ||
var finishedBackup = false | ||
|
||
every { | ||
spyBackupNotificationManager.onBackupFinished(any(), any()) | ||
} answers { | ||
val success = firstArg<Boolean>() | ||
assert(success) { "Backup failed." } | ||
|
||
val packageCount = secondArg<Int>() | ||
assert(packageCount == expectedPackages.size) { | ||
"Expected ${expectedPackages.size} apps, got $packageCount." | ||
} | ||
|
||
this.callOriginal() | ||
finishedBackup = true | ||
} | ||
|
||
step("Start backup and await completion") { | ||
startBackup() | ||
|
||
runBlocking { | ||
withTimeout(BACKUP_TIMEOUT) { | ||
while (!finishedBackup) { | ||
delay(100) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
private fun performRestore() = run { | ||
step("Start restore and await completion") { | ||
RestoreScreen { | ||
startRestore() | ||
waitForInstallResult() | ||
sleep(5000) | ||
|
||
nextButton.click() | ||
waitForRestoreResult() | ||
sleep(5000) | ||
|
||
finishButton.click() | ||
} | ||
} | ||
} | ||
|
||
private fun waitForInstallResult() = runBlocking { | ||
withTimeout(BACKUP_TIMEOUT) { | ||
|
||
while (restoreViewModel.installResult.value == null) { | ||
delay(100) | ||
} | ||
|
||
val restoreResultValue = restoreViewModel.installResult.value!! | ||
assert(!restoreResultValue.hasFailed) { "Failed to install packages" } | ||
} | ||
} | ||
|
||
private fun waitForRestoreResult() = runBlocking { | ||
withTimeout(BACKUP_TIMEOUT) { | ||
|
||
while (restoreViewModel.restoreBackupResult.value == null) { | ||
delay(100) | ||
} | ||
|
||
val restoreResultValue = restoreViewModel.restoreBackupResult.value!! | ||
|
||
assert(!restoreResultValue.hasError()) { | ||
"Restore failed: ${restoreResultValue.errorMsg}" | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.