summaryrefslogtreecommitdiffhomepage
path: root/android/test
diff options
context:
space:
mode:
Diffstat (limited to 'android/test')
-rw-r--r--android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/JUnitTest.kt8
-rw-r--r--android/test/baselineprofile/build.gradle.kts96
-rw-r--r--android/test/baselineprofile/src/main/AndroidManifest.xml1
-rw-r--r--android/test/baselineprofile/src/main/kotlin/net/mullvad/mullvadvpn/test/baselineprofile/BaselineProfileGenerator.kt50
-rw-r--r--android/test/baselineprofile/src/main/kotlin/net/mullvad/mullvadvpn/test/baselineprofile/StartupBenchmarks.kt76
5 files changed, 229 insertions, 2 deletions
diff --git a/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/JUnitTest.kt b/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/JUnitTest.kt
index a2c743b360..4e1faf5e19 100644
--- a/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/JUnitTest.kt
+++ b/android/test/arch/src/test/kotlin/net/mullvad/mullvadvpn/test/arch/JUnitTest.kt
@@ -10,7 +10,7 @@ class JUnitTest {
@Test
fun `ensure only junit5 annotations are used for functions`() =
- Konsist.scopeFromProject()
+ projectScopeExceptBaseline()
.functions()
.filter {
it.annotations.any { annotation ->
@@ -22,7 +22,7 @@ class JUnitTest {
@Test
fun `ensure only junit5 annotations are used for classes`() =
- Konsist.scopeFromProject()
+ projectScopeExceptBaseline()
.classes()
.filter {
it.annotations.any { annotation ->
@@ -44,6 +44,10 @@ class JUnitTest {
fun `ensure all non android tests have 'ensure' or 'should' in function name`() =
allNonAndroidTests().assertTrue { it.name.containsEnsureOrShould() }
+ // We should exclude baselineprofile since it requires JUnit4
+ private fun projectScopeExceptBaseline() =
+ (Konsist.scopeFromProject() - Konsist.scopeFromDirectory("test/baselineprofile"))
+
private fun String.containsEnsureOrShould(): Boolean {
return contains("ensure") || contains("should") || contains("then")
}
diff --git a/android/test/baselineprofile/build.gradle.kts b/android/test/baselineprofile/build.gradle.kts
new file mode 100644
index 0000000000..6477b5adca
--- /dev/null
+++ b/android/test/baselineprofile/build.gradle.kts
@@ -0,0 +1,96 @@
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+
+plugins {
+ alias(libs.plugins.android.test)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.baselineprofile)
+}
+
+android {
+ namespace = "net.mullvad.mullvadvpn.test.baselineprofile"
+ compileSdk = libs.versions.compile.sdk.get().toInt()
+ buildToolsVersion = libs.versions.build.tools.get()
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ kotlin {
+ compilerOptions {
+ jvmTarget = JvmTarget.fromTarget(libs.versions.jvm.target.get())
+ allWarningsAsErrors = true
+ }
+ }
+
+ lint {
+ lintConfig = file("${rootProject.projectDir}/config/lint.xml")
+ abortOnError = true
+ warningsAsErrors = true
+ }
+
+ defaultConfig {
+ minSdk = 28
+ targetSdk = libs.versions.target.sdk.get().toInt()
+ targetProjectPath = ":app"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+
+ testInstrumentationRunnerArguments += mapOf("clearPackageData" to "true")
+ }
+
+ targetProjectPath = ":app"
+
+ flavorDimensions += FlavorDimensions.BILLING
+ flavorDimensions += FlavorDimensions.INFRASTRUCTURE
+
+ productFlavors {
+ create(Flavors.OSS) { dimension = FlavorDimensions.BILLING }
+ create(Flavors.PLAY) { dimension = FlavorDimensions.BILLING }
+ create(Flavors.PROD) {
+ dimension = FlavorDimensions.INFRASTRUCTURE
+ buildConfigField(
+ type = "String",
+ name = "INFRASTRUCTURE_BASE_DOMAIN",
+ value = "\"mullvad.net\"",
+ )
+ }
+ create(Flavors.STAGEMOLE) {
+ dimension = FlavorDimensions.INFRASTRUCTURE
+ buildConfigField(
+ type = "String",
+ name = "INFRASTRUCTURE_BASE_DOMAIN",
+ value = "\"stagemole.eu\"",
+ )
+ }
+ }
+ buildFeatures { buildConfig = true }
+}
+
+// This is the configuration block for the Baseline Profile plugin.
+// You can specify to run the generators on a managed devices or connected devices.
+baselineProfile { useConnectedDevices = true }
+
+// Force okio version to 3.9.1 to fix 2.10.0 appearing in the verification metadata file.
+// This is to avoid a osv-scanner complaining a about a vulnerability in okio 2.10.0.
+// Gradle already upgrades okio 2.10.0 to 3.9.1, but it still ends up in the metadata file.
+// If we update androidx.benchmark:benchmark-macro-junit4 we might be able to remove this.
+configurations.all { resolutionStrategy { force("com.squareup.okio:okio:3.9.1") } }
+
+dependencies {
+ implementation(projects.lib.ui.tag)
+ implementation(libs.androidx.junit)
+ implementation(libs.androidx.espresso)
+ implementation(libs.androidx.test.uiautomator)
+ implementation(libs.androidx.benchmark.macro.junit4)
+}
+
+androidComponents {
+ onVariants { v ->
+ val artifactsLoader = v.artifacts.getBuiltArtifactsLoader()
+ v.instrumentationRunnerArguments.put(
+ "targetAppId",
+ v.testedApks.map { artifactsLoader.load(it)?.applicationId },
+ )
+ }
+}
diff --git a/android/test/baselineprofile/src/main/AndroidManifest.xml b/android/test/baselineprofile/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..cc947c5679
--- /dev/null
+++ b/android/test/baselineprofile/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+<manifest />
diff --git a/android/test/baselineprofile/src/main/kotlin/net/mullvad/mullvadvpn/test/baselineprofile/BaselineProfileGenerator.kt b/android/test/baselineprofile/src/main/kotlin/net/mullvad/mullvadvpn/test/baselineprofile/BaselineProfileGenerator.kt
new file mode 100644
index 0000000000..3b292ad949
--- /dev/null
+++ b/android/test/baselineprofile/src/main/kotlin/net/mullvad/mullvadvpn/test/baselineprofile/BaselineProfileGenerator.kt
@@ -0,0 +1,50 @@
+package net.mullvad.mullvadvpn.test.baselineprofile
+
+import androidx.benchmark.macro.junit4.BaselineProfileRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This generates a baseline profile for the Mullvad VPN app. Run this from gradle with: ./gradlew
+ * generatePlayProdReleaseBaselineProfile
+ *
+ * This should be done from time to time to keep the profile up to date with the app.
+ *
+ * NOTE: API 33+ or rooted API 28+ is required.
+ */
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class BaselineProfileGenerator {
+
+ @get:Rule val rule = BaselineProfileRule()
+
+ @Test
+ fun generate() {
+ rule.collect(
+ packageName =
+ InstrumentationRegistry.getArguments().getString("targetAppId")
+ ?: error("targetAppId not passed as instrumentation runner arg"),
+
+ // See:
+ // https://d.android.com/topic/performance/baselineprofiles/dex-layout-optimizations
+ includeInStartupProfile = true,
+ ) {
+ pressHome()
+ startActivityAndWait()
+ device.acceptPrivacy()
+ }
+ }
+
+ // This should use PrivacyPage from common but it is currently not possible to access the files
+ // in that module from here. A fix for this is tracked in: DROID-2165
+ private fun UiDevice.acceptPrivacy() {
+ val agreeSelector = By.text("Agree and continue")
+ findObject(agreeSelector)?.click()
+ }
+}
diff --git a/android/test/baselineprofile/src/main/kotlin/net/mullvad/mullvadvpn/test/baselineprofile/StartupBenchmarks.kt b/android/test/baselineprofile/src/main/kotlin/net/mullvad/mullvadvpn/test/baselineprofile/StartupBenchmarks.kt
new file mode 100644
index 0000000000..27f1189592
--- /dev/null
+++ b/android/test/baselineprofile/src/main/kotlin/net/mullvad/mullvadvpn/test/baselineprofile/StartupBenchmarks.kt
@@ -0,0 +1,76 @@
+package net.mullvad.mullvadvpn.test.baselineprofile
+
+import androidx.benchmark.macro.BaselineProfileMode
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.StartupMode
+import androidx.benchmark.macro.StartupTimingMetric
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This test class benchmarks the speed of app startup. Run this benchmark to verify how effective a
+ * Baseline Profile is. It does this by comparing [CompilationMode.None], which represents the app
+ * with no Baseline Profiles optimizations, and [CompilationMode.Partial], which uses Baseline
+ * Profiles.
+ *
+ * Run this benchmark to see startup measurements and captured system traces for verifying the
+ * effectiveness of your Baseline Profiles. You can run it directly from Android Studio as an
+ * instrumentation test, or run all benchmarks for a variant, for example benchmarkRelease, with
+ * this Gradle task:
+ * ```
+ * ./gradlew :baselineprofile:connectedBenchmarkReleaseAndroidTest
+ * ```
+ *
+ * You should run the benchmarks on a physical device, not an Android emulator, because the emulator
+ * doesn't represent real world performance and shares system resources with its host.
+ *
+ * For more information, see the
+ * [Macrobenchmark documentation](https://d.android.com/macrobenchmark#create-macrobenchmark) and
+ * the [instrumentation arguments documentation]
+ * (https://d.android.com/topic/performance/benchmarking/macrobenchmark-instrumentation-args).
+ */
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class StartupBenchmarks {
+
+ @get:Rule val rule = MacrobenchmarkRule()
+
+ @Test fun startupCompilationNone() = benchmark(CompilationMode.None())
+
+ @Test
+ fun startupCompilationBaselineProfiles() =
+ benchmark(CompilationMode.Partial(BaselineProfileMode.Require))
+
+ private fun benchmark(compilationMode: CompilationMode) {
+ // The application id for the running build variant is read from the instrumentation
+ // arguments.
+ rule.measureRepeated(
+ packageName =
+ InstrumentationRegistry.getArguments().getString("targetAppId")
+ ?: error("targetAppId not passed as instrumentation runner arg"),
+ metrics = listOf(StartupTimingMetric()),
+ compilationMode = compilationMode,
+ startupMode = StartupMode.COLD,
+ iterations = 10,
+ setupBlock = { pressHome() },
+ measureBlock = {
+ startActivityAndWait()
+
+ // Add interactions to wait for when your app is fully drawn.
+ // The app is fully drawn when Activity.reportFullyDrawn is called.
+ // For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and
+ // ReportDrawnAfter
+ // from the AndroidX Activity library.
+
+ // Check the UiAutomator documentation for more information on how to
+ // interact with the app.
+ // https://d.android.com/training/testing/other-components/ui-automator
+ },
+ )
+ }
+}