Skip to content

Commit

Permalink
Merge pull request seedvault-app#553 from seedvault-app/feature/e2e-test
Browse files Browse the repository at this point in the history
Add end-to-end emulator test running on CI
  • Loading branch information
grote authored Sep 25, 2023
2 parents c96f1c9 + ced8803 commit ba6dc2f
Show file tree
Hide file tree
Showing 35 changed files with 1,230 additions and 43 deletions.
58 changes: 58 additions & 0 deletions .cirrus.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
container:
image: ghcr.io/cirruslabs/android-sdk:33
kvm: true
cpu: 8
memory: 16G

instrumentation_tests_task:
name: "Cirrus CI Instrumentation Tests"
skip: "!changesInclude('.cirrus.yml', '*.gradle', '*.gradle.kts', '**/*.gradle', '**/*.gradle.kts', '*.properties', '**/*.properties', '**/*.kt', '**/*.xml')"
start_avd_background_script:
sdkmanager --install "system-images;android-33;google_apis;x86_64";
echo no | avdmanager create avd -n seedvault -k "system-images;android-33;google_apis;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/1/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 :app: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 bmgr enable true;
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 -Pinstrumented_test_size=large :app:connectedAndroidTest
run_medium_tests_script: ./gradlew -Pinstrumented_test_size=medium :app:connectedAndroidTest
always:
pull_screenshots_script:
adb pull /sdcard/seedvault_test_videos
screenshots_artifacts:
path: "seedvault_test_videos/**/*.mp4"
6 changes: 3 additions & 3 deletions .idea/runConfigurations/app_emulator.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 24 additions & 4 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,15 @@ android {
minSdk 32 // leave at 32 for robolectric tests
targetSdk rootProject.ext.targetSdk
versionNameSuffix "-$gitDescribe"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunner "com.stevesoltys.seedvault.KoinInstrumentationTestRunner"
testInstrumentationRunnerArguments disableAnalytics: 'true'

if (project.hasProperty('instrumented_test_size')) {
final testSize = project.getProperty('instrumented_test_size')
println("Instrumented test size: $testSize")

testInstrumentationRunnerArguments size: testSize
}
}

buildTypes {
Expand Down Expand Up @@ -150,10 +157,12 @@ dependencies {
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit5_version"
testRuntimeOnly "org.junit.vintage:junit-vintage-engine:$junit5_version"

androidTestImplementation rootProject.ext.aosp_libs
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test:rules:1.4.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation "io.mockk:mockk-android:$mockk_version"
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
}

apply from: "${rootProject.rootDir}/gradle/ktlint.gradle"
Expand Down Expand Up @@ -182,7 +191,7 @@ tasks.register('provisionEmulator', Exec) {
"seedvault",
"system-images;android-33;google_apis;x86_64"

environment "ANDROID_SDK_HOME", android.sdkDirectory.absolutePath
environment "ANDROID_HOME", android.sdkDirectory.absolutePath
environment "JAVA_HOME", System.properties['java.home']
}
}
Expand All @@ -193,7 +202,7 @@ tasks.register('startEmulator', Exec) {
doFirst {
commandLine "${project.projectDir}/development/scripts/start_emulator.sh", "seedvault"

environment "ANDROID_SDK_HOME", android.sdkDirectory.absolutePath
environment "ANDROID_HOME", android.sdkDirectory.absolutePath
environment "JAVA_HOME", System.properties['java.home']
}
}
Expand All @@ -206,7 +215,18 @@ tasks.register('installEmulatorRelease', Exec) {
doFirst {
commandLine "${project.projectDir}/development/scripts/install_app.sh"

environment "ANDROID_SDK_HOME", android.sdkDirectory.absolutePath
environment "ANDROID_HOME", android.sdkDirectory.absolutePath
environment "JAVA_HOME", System.properties['java.home']
}
}

tasks.register('clearEmulatorAppData', Exec) {
group("emulator")

doFirst {
commandLine "${project.projectDir}/development/scripts/clear_app_data.sh"

environment "ANDROID_HOME", android.sdkDirectory.absolutePath
environment "JAVA_HOME", System.properties['java.home']
}
}
22 changes: 22 additions & 0 deletions app/development/scripts/clear_app_data.sh
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_HOME" ]; then
echo "ANDROID_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_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_HOME/platform-tools/adb -s $EMULATOR_DEVICE_NAME"

$ADB shell pm clear com.stevesoltys.seedvault
8 changes: 4 additions & 4 deletions app/development/scripts/install_app.sh
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
#!/usr/bin/env bash

# assert ANDROID_HOME is set
if [ -z "$ANDROID_SDK_HOME" ]; then
echo "ANDROID_SDK_HOME is not set"
if [ -z "$ANDROID_HOME" ]; then
echo "ANDROID_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)
EMULATOR_DEVICE_NAME=$($ANDROID_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="$ANDROID_HOME/platform-tools/adb -s $EMULATOR_DEVICE_NAME"

$ADB root
sleep 3 # wait for adb to restart
Expand Down
24 changes: 16 additions & 8 deletions app/development/scripts/provision_emulator.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env bash

# assert ANDROID_HOME is set
if [ -z "$ANDROID_SDK_HOME" ]; then
echo "ANDROID_SDK_HOME is not set"
if [ -z "$ANDROID_HOME" ]; then
echo "ANDROID_HOME is not set"
exit 1
fi

Expand All @@ -20,30 +20,29 @@ DEVELOPMENT_DIR=$SCRIPT_DIR/..
ROOT_PROJECT_DIR=$SCRIPT_DIR/../../..

echo "Downloading system image..."
$ANDROID_SDK_HOME/cmdline-tools/latest/bin/sdkmanager --install "$SYSTEM_IMAGE"
$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install "$SYSTEM_IMAGE"

# create AVD if it doesn't exist
if $ANDROID_SDK_HOME/cmdline-tools/latest/bin/avdmanager list avd | grep -q "$EMULATOR_NAME"; then
if $ANDROID_HOME/cmdline-tools/latest/bin/avdmanager list avd | grep -q "$EMULATOR_NAME"; then
echo "AVD already exists. Skipping creation."
else
echo "Creating AVD..."
echo 'no' | $ANDROID_SDK_HOME/cmdline-tools/latest/bin/avdmanager create avd -n "$EMULATOR_NAME" -k "$SYSTEM_IMAGE"
echo 'no' | $ANDROID_HOME/cmdline-tools/latest/bin/avdmanager create avd -n "$EMULATOR_NAME" -k "$SYSTEM_IMAGE"
sleep 1
fi

echo "Starting emulator..."
$SCRIPT_DIR/start_emulator.sh "$EMULATOR_NAME"
sleep 3

# get emulator device name from ADB
EMULATOR_DEVICE_NAME=$($ANDROID_SDK_HOME/platform-tools/adb devices | grep emulator | cut -f1)
EMULATOR_DEVICE_NAME=$($ANDROID_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="$ANDROID_HOME/platform-tools/adb -s $EMULATOR_DEVICE_NAME"

echo "Waiting for emulator to boot..."
$ADB wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;'
Expand All @@ -68,4 +67,13 @@ echo "Rebooting emulator..."
$ADB reboot
$ADB wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;'

echo "Downloading and extracting test backup to '/sdcard/seedvault'..."
wget https://github.com/seedvault-app/seedvault-test-data/releases/download/1/backup.tar.gz
$ADB push backup.tar.gz /sdcard/
rm backup.tar.gz

$ADB shell mkdir -p /sdcard/seedvault_baseline
$ADB shell tar xzf /sdcard/backup.tar.gz --directory=/sdcard/seedvault_baseline
$ADB shell rm /sdcard/backup.tar.gz

echo "Emulator '$EMULATOR_NAME' has been provisioned with Seedvault!"
6 changes: 3 additions & 3 deletions app/development/scripts/start_emulator.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env bash

# assert ANDROID_HOME is set
if [ -z "$ANDROID_SDK_HOME" ]; then
echo "ANDROID_SDK_HOME is not set"
if [ -z "$ANDROID_HOME" ]; then
echo "ANDROID_HOME is not set"
exit 1
fi

Expand All @@ -19,4 +19,4 @@ DEVELOPMENT_DIR=$SCRIPT_DIR/..
ROOT_PROJECT_DIR=$SCRIPT_DIR/../../..

echo "Starting emulator..."
nohup $ANDROID_SDK_HOME/emulator/emulator -avd "$EMULATOR_NAME" -gpu swiftshader_indirect -writable-system -no-snapshot-load >/dev/null 2>&1 &
nohup $ANDROID_HOME/emulator/emulator -avd "$EMULATOR_NAME" -gpu swiftshader_indirect -writable-system -no-snapshot-load >/dev/null 2>&1 &
1 change: 0 additions & 1 deletion app/src/androidTest/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application android:extractNativeLibs="true" />
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.stevesoltys.seedvault

import com.stevesoltys.seedvault.restore.RestoreViewModel
import com.stevesoltys.seedvault.transport.backup.FullBackup
import com.stevesoltys.seedvault.transport.backup.InputFactory
import com.stevesoltys.seedvault.transport.backup.KVBackup
import com.stevesoltys.seedvault.transport.restore.FullRestore
import com.stevesoltys.seedvault.transport.restore.KVRestore
import com.stevesoltys.seedvault.transport.restore.OutputFactory
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import io.mockk.spyk
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.core.module.Module
import org.koin.dsl.module

internal var currentRestoreViewModel: RestoreViewModel? = null

class KoinInstrumentationTestApp : App() {

override fun appModules(): List<Module> {
val testModule = module {
val context = this@KoinInstrumentationTestApp

single { spyk(BackupNotificationManager(context)) }
single { spyk(FullBackup(get(), get(), get(), get())) }
single { spyk(KVBackup(get(), get(), get(), get(), get())) }
single { spyk(InputFactory()) }

single { spyk(FullRestore(get(), get(), get(), get(), get())) }
single { spyk(KVRestore(get(), get(), get(), get(), get(), get())) }
single { spyk(OutputFactory()) }

viewModel {
currentRestoreViewModel =
spyk(RestoreViewModel(context, get(), get(), get(), get(), get(), get()))
currentRestoreViewModel!!
}
}

return super.appModules().plus(testModule)
}
}
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
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.stevesoltys.seedvault

import androidx.test.core.content.pm.PackageInfoBuilder
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.platform.app.InstrumentationRegistry
import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePlugin
Expand All @@ -28,6 +29,7 @@ import org.koin.core.component.inject

@RunWith(AndroidJUnit4::class)
@Suppress("BlockingMethodInNonBlockingContext")
@MediumTest
class PluginTest : KoinComponent {

private val context = InstrumentationRegistry.getInstrumentation().targetContext
Expand Down
Loading

0 comments on commit ba6dc2f

Please sign in to comment.