From 239a4833a55eefe623fdd5acb5e978210eea072a Mon Sep 17 00:00:00 2001 From: MR3Y Date: Tue, 31 Oct 2023 14:58:43 +0200 Subject: [PATCH 1/3] Setup Baseline profiles & benchmark module --- androidApp/benchmark-rules.pro | 6 +++ androidApp/build.gradle.kts | 13 ++++++ androidApp/src/main/AndroidManifest.xml | 5 ++ benchmark/.gitignore | 1 + benchmark/build.gradle.kts | 46 +++++++++++++++++++ benchmark/src/main/AndroidManifest.xml | 1 + .../benchmark/BaselineProfileGenerator.kt | 23 ++++++++++ build.gradle.kts | 1 + .../ludi/gradle/AndroidConventionPlugin.kt | 7 +++ gradle/libs.versions.toml | 5 ++ settings.gradle.kts | 3 +- 11 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 androidApp/benchmark-rules.pro create mode 100644 benchmark/.gitignore create mode 100644 benchmark/build.gradle.kts create mode 100644 benchmark/src/main/AndroidManifest.xml create mode 100644 benchmark/src/main/java/com/mr3y/ludi/benchmark/BaselineProfileGenerator.kt diff --git a/androidApp/benchmark-rules.pro b/androidApp/benchmark-rules.pro new file mode 100644 index 00000000..5fa87d01 --- /dev/null +++ b/androidApp/benchmark-rules.pro @@ -0,0 +1,6 @@ +# Proguard rules for the `benchmark` build type. +# +# Obsfuscation must be disabled for the build variant that generates Baseline Profile, otherwise +# wrong symbols would be generated. The generated Baseline Profile will be properly applied when generated +# without obfuscation and your app is being obfuscated. +-dontobfuscate diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index ac5387a8..227a6fa3 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -48,6 +48,18 @@ android { signingConfig = signingConfigs.getByName("release") proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } + create("benchmark") { + initWith(buildTypes.getByName("release")) + signingConfig = signingConfigs.getByName("debug") + isMinifyEnabled = true + matchingFallbacks += listOf("release") + proguardFiles("benchmark-rules.pro") + applicationIdSuffix = ".benchmark" + // Disable uploading mapping files for the benchmark build type + configure { + mappingFileUploadEnabled = false + } + } } kotlinOptions { @@ -104,6 +116,7 @@ dependencies { implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.activity.compose) implementation(libs.splash.screen) + implementation(libs.profiler.installer) implementation(project(":shared")) diff --git a/androidApp/src/main/AndroidManifest.xml b/androidApp/src/main/AndroidManifest.xml index d72214fa..e5649b28 100644 --- a/androidApp/src/main/AndroidManifest.xml +++ b/androidApp/src/main/AndroidManifest.xml @@ -19,6 +19,11 @@ android:theme="@style/Theme.Ludi.Splash" android:name=".LudiApplication" tools:targetApi="31"> + + + \ No newline at end of file diff --git a/benchmark/src/main/java/com/mr3y/ludi/benchmark/BaselineProfileGenerator.kt b/benchmark/src/main/java/com/mr3y/ludi/benchmark/BaselineProfileGenerator.kt new file mode 100644 index 00000000..beae0eb2 --- /dev/null +++ b/benchmark/src/main/java/com/mr3y/ludi/benchmark/BaselineProfileGenerator.kt @@ -0,0 +1,23 @@ +package com.mr3y.ludi.benchmark + +import androidx.benchmark.macro.junit4.BaselineProfileRule +import org.junit.Rule +import org.junit.Test + +class BaselineProfileGenerator { + @get:Rule + val baselineProfileRule = BaselineProfileRule() + + @Test + fun generate() = baselineProfileRule.collect( + packageName = "com.mr3y.ludi", + stableIterations = 2, + maxIterations = 10, + profileBlock = { + pressHome() + startActivityAndWait() + + // simulate user journey through the app. + } + ) +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index ec5fbe17..225e367c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,6 +7,7 @@ plugins { alias(libs.plugins.kotlin.android).apply(false) alias(libs.plugins.android.application).apply(false) alias(libs.plugins.android.library).apply(false) + alias(libs.plugins.android.test).apply(false) alias(libs.plugins.compose.multiplatform).apply(false) alias(libs.plugins.spotless.plugin).apply(false) alias(libs.plugins.ktlint.plugin).apply(false) diff --git a/convention-plugins/plugins/src/main/kotlin/com/mr3y/ludi/gradle/AndroidConventionPlugin.kt b/convention-plugins/plugins/src/main/kotlin/com/mr3y/ludi/gradle/AndroidConventionPlugin.kt index 43284f17..46dc4497 100644 --- a/convention-plugins/plugins/src/main/kotlin/com/mr3y/ludi/gradle/AndroidConventionPlugin.kt +++ b/convention-plugins/plugins/src/main/kotlin/com/mr3y/ludi/gradle/AndroidConventionPlugin.kt @@ -2,6 +2,7 @@ package com.mr3y.ludi.gradle import com.android.build.api.dsl.CommonExtension import com.android.build.gradle.LibraryExtension +import com.android.build.gradle.TestExtension import com.android.build.gradle.internal.dsl.BaseAppModuleExtension import org.gradle.api.JavaVersion import org.gradle.api.Plugin @@ -25,6 +26,12 @@ class AndroidConventionPlugin : Plugin { applyCommonAndroidConvention() } } + pluginManager.hasPlugin("com.android.test") -> { + val androidTestExtension = extensions.getByType() + androidTestExtension.apply { + applyCommonAndroidConvention() + } + } else -> {} } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bf67d069..ba170001 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -73,6 +73,7 @@ android-library = "com.android.library:8.1.2" ## ⬆ :8.3.0-alpha09" ## ⬆ :8.3.0-alpha10" ## ⬆ :8.3.0-alpha11" +android-test = "com.android.test:8.1.2" kotlin-android = "org.jetbrains.kotlin.android:1.9.10" kotlin-jvm = "org.jetbrains.kotlin.jvm:1.9.10" kotlin-multiplatform = "org.jetbrains.kotlin.multiplatform:1.9.10" @@ -229,6 +230,10 @@ junit = "junit:junit:4.13.2" strikt = "io.strikt:strikt-core:0.34.1" turbine = "app.cash.turbine:turbine:1.0.0" robolectric = "org.robolectric:robolectric:4.10.3" +espresso-core = "androidx.test.espresso:espresso-core:3.5.1" +uiautomator = "androidx.test.uiautomator:uiautomator:2.2.0" +profiler-installer = "androidx.profileinstaller:profileinstaller:1.3.1" +benchmark-macro-junit4 = "androidx.benchmark:benchmark-macro-junit4:1.2.0" ## ⬆ :4.11-beta-1" ## ⬆ :4.11-beta-2" ## ⬆ :4.11" diff --git a/settings.gradle.kts b/settings.gradle.kts index 1eec9d5b..7e59e166 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -33,4 +33,5 @@ dependencyResolutionManagement { rootProject.name = "Ludi" include(":androidApp") include(":shared") -include(":desktopApp") \ No newline at end of file +include(":desktopApp") +include(":benchmark") \ No newline at end of file From 4447d2bdfba7059af1a8e366c87fcb593fe7c551 Mon Sep 17 00:00:00 2001 From: MR3Y Date: Wed, 1 Nov 2023 15:15:25 +0200 Subject: [PATCH 2/3] Simulate user journeys through the app --- .../main/kotlin/com/mr3y/ludi/MainActivity.kt | 10 ++- .../benchmark/BaselineProfileGenerator.kt | 78 ++++++++++++++++++- .../java/com/mr3y/ludi/benchmark/Utils.kt | 13 ++++ .../kotlin/com/mr3y/ludi/shared/App.kt | 5 +- .../ui/screens/onboarding/DataSourcesPage.kt | 2 + .../shared/ui/screens/onboarding/GamesPage.kt | 8 +- .../ui/screens/onboarding/GenresPage.kt | 5 +- .../ui/screens/onboarding/OnboardingScreen.kt | 2 + 8 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 benchmark/src/main/java/com/mr3y/ludi/benchmark/Utils.kt diff --git a/androidApp/src/main/kotlin/com/mr3y/ludi/MainActivity.kt b/androidApp/src/main/kotlin/com/mr3y/ludi/MainActivity.kt index e5edcaf0..c2a6e977 100644 --- a/androidApp/src/main/kotlin/com/mr3y/ludi/MainActivity.kt +++ b/androidApp/src/main/kotlin/com/mr3y/ludi/MainActivity.kt @@ -7,7 +7,11 @@ import androidx.activity.compose.setContent import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat import androidx.lifecycle.Lifecycle @@ -29,6 +33,7 @@ class MainActivity : ComponentActivity(), HostActivityComponentOwner { HostActivityComponent::class.create(this, AndroidApplicationComponent.from(this)) } + @OptIn(ExperimentalComposeUiApi::class) override fun onCreate(savedInstanceState: Bundle?) { val splashScreen = installSplashScreen() super.onCreate(savedInstanceState) @@ -75,7 +80,10 @@ class MainActivity : ComponentActivity(), HostActivityComponentOwner { } }, useDynamicColor = userPreferences!!.useDynamicColor, - showOnboardingScreen = userPreferences!!.showOnBoardingScreen + showOnboardingScreen = userPreferences!!.showOnBoardingScreen, + modifier = Modifier.semantics { + testTagsAsResourceId = true + } ) } } diff --git a/benchmark/src/main/java/com/mr3y/ludi/benchmark/BaselineProfileGenerator.kt b/benchmark/src/main/java/com/mr3y/ludi/benchmark/BaselineProfileGenerator.kt index beae0eb2..44f4888a 100644 --- a/benchmark/src/main/java/com/mr3y/ludi/benchmark/BaselineProfileGenerator.kt +++ b/benchmark/src/main/java/com/mr3y/ludi/benchmark/BaselineProfileGenerator.kt @@ -1,23 +1,99 @@ package com.mr3y.ludi.benchmark +import androidx.benchmark.macro.MacrobenchmarkScope import androidx.benchmark.macro.junit4.BaselineProfileRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Direction +import androidx.test.uiautomator.Until import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith +@RunWith(AndroidJUnit4::class) +@LargeTest class BaselineProfileGenerator { @get:Rule val baselineProfileRule = BaselineProfileRule() @Test fun generate() = baselineProfileRule.collect( - packageName = "com.mr3y.ludi", + packageName = "com.mr3y.ludi.benchmark", stableIterations = 2, maxIterations = 10, profileBlock = { + // Start the app pressHome() startActivityAndWait() // simulate user journey through the app. + waitForGenresToLoad() + selectRandomGenres() + moveToNextPage() + + waitForGamesToLoad() + selectRandomGames() + scrollGamesContainerHorizontally() + moveToNextPage() + + waitForDataSources() + moveToNextPage() } ) + + fun MacrobenchmarkScope.waitForGenresToLoad() { + device.wait(Until.gone(By.res("onboarding:genres:loadingWheel")), 10_000L) + device.wait(Until.hasObject(By.res("onboarding:genres:content")), 1000L) + } + + fun MacrobenchmarkScope.selectRandomGenres() { + val genres = device.findObject(By.res("onboarding:genres:content")).children + var i = 0 + while (i < 3) { + val genre = genres.random() + if (genre.isSelected) { + continue + } + genre.click() + device.waitForIdle() + i++ + } + } + + fun MacrobenchmarkScope.waitForGamesToLoad() { + device.wait(Until.gone(By.res("onboarding:games:gameLoading")), 10_000L) + device.wait(Until.hasObject(By.res("onboarding:games:gameContent")), 1000L) + } + + fun MacrobenchmarkScope.selectRandomGames() { + val gamesContainer = device.waitForObject(By.res("onboarding:games:suggestedGames")) + + // Set gesture margin from sides not to trigger system gesture navigation + val horizontalMargin = 10 * gamesContainer.visibleBounds.width() / 100 + gamesContainer.setGestureMargins(horizontalMargin, 0, horizontalMargin, 0) + val games = gamesContainer.children.filter { it.resourceName == "onboarding:games:gameContent" } + repeat(3) { + games.random().click() + device.waitForIdle() + } + } + + fun MacrobenchmarkScope.scrollGamesContainerHorizontally() { + val gamesContainer = device.waitForObject(By.res("onboarding:games:suggestedGames")) + // Set gesture margin from sides not to trigger system gesture navigation + gamesContainer.setGestureMargin(device.displayWidth / 5) + gamesContainer.fling(Direction.RIGHT) + device.waitForIdle() + gamesContainer.fling(Direction.LEFT) + } + + fun MacrobenchmarkScope.waitForDataSources() { + device.wait(Until.hasObject(By.res("onboarding:datasources:content")), 10_000L) + } + + fun MacrobenchmarkScope.moveToNextPage() { + device.findObject(By.res("onboarding:fab")).click() + device.waitForIdle() + } } \ No newline at end of file diff --git a/benchmark/src/main/java/com/mr3y/ludi/benchmark/Utils.kt b/benchmark/src/main/java/com/mr3y/ludi/benchmark/Utils.kt new file mode 100644 index 00000000..dc60ac59 --- /dev/null +++ b/benchmark/src/main/java/com/mr3y/ludi/benchmark/Utils.kt @@ -0,0 +1,13 @@ +package com.mr3y.ludi.benchmark + +import androidx.test.uiautomator.BySelector +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.Until + +internal fun UiDevice.waitForObject(selector: BySelector, timeout: Long = 1000L): UiObject2 { + if (wait(Until.hasObject(selector), timeout)) { + return findObject(selector) + } + error("Object with selector [$selector] not found") +} diff --git a/shared/src/commonMain/kotlin/com/mr3y/ludi/shared/App.kt b/shared/src/commonMain/kotlin/com/mr3y/ludi/shared/App.kt index fe6086bd..f82e7f8c 100644 --- a/shared/src/commonMain/kotlin/com/mr3y/ludi/shared/App.kt +++ b/shared/src/commonMain/kotlin/com/mr3y/ludi/shared/App.kt @@ -1,5 +1,6 @@ package com.mr3y.ludi.shared +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass @@ -30,7 +31,9 @@ fun App( LocalWindowSizeClass provides calculateWindowSizeClass() ) { if (showOnboardingScreen) { - Navigator(screen = OnboardingScreen) + Box(modifier = modifier.fillMaxSize()) { + Navigator(screen = OnboardingScreen) + } } else { HomeScreen(modifier = modifier.fillMaxSize()) } diff --git a/shared/src/commonMain/kotlin/com/mr3y/ludi/shared/ui/screens/onboarding/DataSourcesPage.kt b/shared/src/commonMain/kotlin/com/mr3y/ludi/shared/ui/screens/onboarding/DataSourcesPage.kt index 1d7f08c8..6ec46042 100644 --- a/shared/src/commonMain/kotlin/com/mr3y/ludi/shared/ui/screens/onboarding/DataSourcesPage.kt +++ b/shared/src/commonMain/kotlin/com/mr3y/ludi/shared/ui/screens/onboarding/DataSourcesPage.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.semantics.isTraversalGroup import androidx.compose.ui.semantics.selected import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.semantics.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -75,6 +76,7 @@ fun NewsSourcesPage( .fillMaxWidth() .semantics { isTraversalGroup = true + testTag = "onboarding:datasources:content" } ) { allNewsDataSources.forEachIndexed { index, newsDataSource -> diff --git a/shared/src/commonMain/kotlin/com/mr3y/ludi/shared/ui/screens/onboarding/GamesPage.kt b/shared/src/commonMain/kotlin/com/mr3y/ludi/shared/ui/screens/onboarding/GamesPage.kt index a692c325..49e29b41 100644 --- a/shared/src/commonMain/kotlin/com/mr3y/ludi/shared/ui/screens/onboarding/GamesPage.kt +++ b/shared/src/commonMain/kotlin/com/mr3y/ludi/shared/ui/screens/onboarding/GamesPage.kt @@ -51,6 +51,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.platform.testTag import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription @@ -61,6 +62,7 @@ import androidx.compose.ui.semantics.performImeAction import androidx.compose.ui.semantics.selected import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.semantics.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextAlign @@ -218,7 +220,8 @@ fun SelectingFavouriteGamesPage( rows = StaggeredGridCells.Adaptive(minSize = 64.dp), modifier = Modifier .fillMaxWidth() - .height(236.dp), + .height(236.dp) + .testTag("onboarding:games:suggestedGames"), horizontalItemSpacing = 8.dp, verticalArrangement = Arrangement.spacedBy(8.dp) ) { @@ -267,7 +270,7 @@ fun SelectingFavouriteGamesPage( } if (games.loadState.append is LoadStateError) { item { - LudiErrorBox(modifier = Modifier.fillMaxWidth()) + LudiErrorBox() } } } @@ -380,6 +383,7 @@ private fun GameTileScaffold( strings.games_page_game_off_state_desc(title ?: "") } this.selected = selected + testTag = if (id == null) "onboarding:games:gameLoading" else "onboarding:games:gameContent" }, elevation = CardDefaults.cardElevation(defaultElevation = 8.dp), shape = MaterialTheme.shapes.small diff --git a/shared/src/commonMain/kotlin/com/mr3y/ludi/shared/ui/screens/onboarding/GenresPage.kt b/shared/src/commonMain/kotlin/com/mr3y/ludi/shared/ui/screens/onboarding/GenresPage.kt index 7bf866bf..1391f3b5 100644 --- a/shared/src/commonMain/kotlin/com/mr3y/ludi/shared/ui/screens/onboarding/GenresPage.kt +++ b/shared/src/commonMain/kotlin/com/mr3y/ludi/shared/ui/screens/onboarding/GenresPage.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription @@ -36,6 +37,7 @@ import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.selected import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.semantics.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -92,7 +94,7 @@ fun GenresPage( when (allGenres) { is Result.Loading -> { Box( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier.fillMaxWidth().testTag("onboarding:genres:loadingWheel"), contentAlignment = Alignment.Center ) { CircularProgressIndicator() @@ -104,6 +106,7 @@ fun GenresPage( .fillMaxWidth() .semantics { isTraversalGroup = true + testTag = "onboarding:genres:content" }, horizontalArrangement = Arrangement.spacedBy(8.dp) ) { diff --git a/shared/src/commonMain/kotlin/com/mr3y/ludi/shared/ui/screens/onboarding/OnboardingScreen.kt b/shared/src/commonMain/kotlin/com/mr3y/ludi/shared/ui/screens/onboarding/OnboardingScreen.kt index c950f7d1..50b51cf4 100644 --- a/shared/src/commonMain/kotlin/com/mr3y/ludi/shared/ui/screens/onboarding/OnboardingScreen.kt +++ b/shared/src/commonMain/kotlin/com/mr3y/ludi/shared/ui/screens/onboarding/OnboardingScreen.kt @@ -62,6 +62,7 @@ import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.geometry.Offset import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.semantics.testTag import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import cafe.adriel.lyricist.LocalStrings @@ -348,6 +349,7 @@ fun AnimatedExtendedFab( OnboardingFABState.Continue -> strings.on_boarding_fab_state_continue_state_desc OnboardingFABState.Finish -> strings.on_boarding_fab_state_finish_state_desc } + testTag = "onboarding:fab" }, shape = RoundedCornerShape(50), containerColor = MaterialTheme.colorScheme.primary From 632f4aeb16f7bddfd13776b440fe9ce6b042cff5 Mon Sep 17 00:00:00 2001 From: MR3Y Date: Wed, 1 Nov 2023 16:42:13 +0200 Subject: [PATCH 3/3] Update baseline profile setup --- androidApp/build.gradle.kts | 15 +++---- benchmark/build.gradle.kts | 40 ++++++++++++------- .../benchmark/BaselineProfileGenerator.kt | 2 +- gradle/libs.versions.toml | 1 + 4 files changed, 35 insertions(+), 23 deletions(-) diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 227a6fa3..e23c5bf2 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -6,6 +6,7 @@ import java.util.Properties @Suppress("DSL_SCOPE_VIOLATION") // Remove when fixed https://youtrack.jetbrains.com/issue/KTIJ-19369 plugins { alias(libs.plugins.android.application) + alias(libs.plugins.baseline.profile) alias(libs.plugins.kotlin.android) alias(libs.plugins.compose.multiplatform) alias(libs.plugins.ludi.common) @@ -51,14 +52,7 @@ android { create("benchmark") { initWith(buildTypes.getByName("release")) signingConfig = signingConfigs.getByName("debug") - isMinifyEnabled = true matchingFallbacks += listOf("release") - proguardFiles("benchmark-rules.pro") - applicationIdSuffix = ".benchmark" - // Disable uploading mapping files for the benchmark build type - configure { - mappingFileUploadEnabled = false - } } } @@ -108,6 +102,12 @@ appVersioning { } } +baselineProfile { + automaticGenerationDuringBuild = true + saveInSrc = true + mergeIntoMain = true +} + dependencies { // Core Android dependencies implementation(libs.androidx.core.ktx) @@ -117,6 +117,7 @@ dependencies { implementation(libs.androidx.activity.compose) implementation(libs.splash.screen) implementation(libs.profiler.installer) + baselineProfile(project(":benchmark")) implementation(project(":shared")) diff --git a/benchmark/build.gradle.kts b/benchmark/build.gradle.kts index cdda2c02..46aed5d7 100644 --- a/benchmark/build.gradle.kts +++ b/benchmark/build.gradle.kts @@ -1,6 +1,9 @@ +import com.android.build.api.dsl.ManagedVirtualDevice + @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed plugins { alias(libs.plugins.android.test) + alias(libs.plugins.baseline.profile) alias(libs.plugins.kotlin.android) alias(libs.plugins.ludi.common) alias(libs.plugins.ludi.android.common) @@ -17,19 +20,32 @@ android { targetSdk = 33 } - buildTypes { - // This benchmark buildType is used for benchmarking, and should function like your - // release build (for example, with minification on). It"s signed with a debug key - // for easy local/CI testing. - create("benchmark") { - isDebuggable = true - signingConfig = getByName("debug").signingConfig - matchingFallbacks += listOf("release") + buildFeatures { + compose = false + aidl = false + buildConfig = false + renderScript = false + shaders = false + } + + testOptions { + managedDevices { + devices { + create("pixel7Api33") { + device = "Pixel 7" + apiLevel = 33 + systemImageSource = "aosp-atd" + } + } } } targetProjectPath = ":androidApp" - experimentalProperties["android.experimental.self-instrumenting"] = true +} + +baselineProfile { + managedDevices += "pixel7Api33" + useConnectedDevices = false } dependencies { @@ -38,9 +54,3 @@ dependencies { implementation(libs.uiautomator) implementation(libs.benchmark.macro.junit4) } - -androidComponents { - beforeVariants(selector().all()) { - it.enable = it.buildType == "benchmark" - } -} \ No newline at end of file diff --git a/benchmark/src/main/java/com/mr3y/ludi/benchmark/BaselineProfileGenerator.kt b/benchmark/src/main/java/com/mr3y/ludi/benchmark/BaselineProfileGenerator.kt index 44f4888a..945cd292 100644 --- a/benchmark/src/main/java/com/mr3y/ludi/benchmark/BaselineProfileGenerator.kt +++ b/benchmark/src/main/java/com/mr3y/ludi/benchmark/BaselineProfileGenerator.kt @@ -19,7 +19,7 @@ class BaselineProfileGenerator { @Test fun generate() = baselineProfileRule.collect( - packageName = "com.mr3y.ludi.benchmark", + packageName = "com.mr3y.ludi", stableIterations = 2, maxIterations = 10, profileBlock = { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ba170001..4f1776ac 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -74,6 +74,7 @@ android-library = "com.android.library:8.1.2" ## ⬆ :8.3.0-alpha10" ## ⬆ :8.3.0-alpha11" android-test = "com.android.test:8.1.2" +baseline-profile = "androidx.baselineprofile:1.2.0" kotlin-android = "org.jetbrains.kotlin.android:1.9.10" kotlin-jvm = "org.jetbrains.kotlin.jvm:1.9.10" kotlin-multiplatform = "org.jetbrains.kotlin.multiplatform:1.9.10"