diff options
Diffstat (limited to 'android/test')
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 + }, + ) + } +} |
