diff --git a/.github/workflows/agp-matrix.yml b/.github/workflows/agp-matrix.yml index 6b0ecf7d1f..bb790d7d9e 100644 --- a/.github/workflows/agp-matrix.yml +++ b/.github/workflows/agp-matrix.yml @@ -90,7 +90,7 @@ jobs: disable-spellchecker: true emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disk-size: 4096M - script: ./gradlew sentry-android-integration-tests:sentry-uitest-android:connectedReleaseAndroidTest -Denvironment=github --daemon + script: ./gradlew -p sentry-android-integration-tests :sentry-uitest-android:connectedReleaseAndroidTest -Denvironment=github --daemon - name: Upload test results if: always() diff --git a/.github/workflows/format-code.yml b/.github/workflows/format-code.yml index 728ac90039..f92f275e98 100644 --- a/.github/workflows/format-code.yml +++ b/.github/workflows/format-code.yml @@ -27,6 +27,7 @@ jobs: run: | ./gradlew spotlessApply ./gradlew -p sentry-samples/sentry-samples-android spotlessApply + ./gradlew -p sentry-android-integration-tests spotlessApply # actions/checkout fetches only a single commit in a detached HEAD state. Therefore # we need to pass the current branch, otherwise we can't commit the changes. diff --git a/.github/workflows/integration-tests-size.yml b/.github/workflows/integration-tests-size.yml index fb61149ac5..8d1c2b8cf5 100644 --- a/.github/workflows/integration-tests-size.yml +++ b/.github/workflows/integration-tests-size.yml @@ -41,6 +41,6 @@ jobs: cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - name: Size Analysis - run: ./gradlew :sentry-android-integration-tests:test-app-size:bundleRelease + run: ./gradlew -p sentry-android-integration-tests :test-app-size:bundleRelease env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} diff --git a/AGENTS.md b/AGENTS.md index 11b571620e..f3fb3b7fd1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -59,8 +59,8 @@ resolves the per-module test task, including integration test suites, for you. ### Android-Specific Commands ```bash -# Assemble Android test APKs -./gradlew :sentry-android-integration-tests:sentry-uitest-android:assembleRelease :sentry-android-integration-tests:sentry-uitest-android:assembleAndroidTest +# Assemble Android test APKs, in a separate Gradle build that includes this one +./gradlew -p sentry-android-integration-tests :sentry-uitest-android:assembleRelease :sentry-uitest-android:assembleAndroidTest # Run critical UI tests ./scripts/test-ui-critical.sh diff --git a/Makefile b/Makefile index eca788104f..e5c1ae04f4 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ -.PHONY: all clean compile javadocs dryRelease update checkFormat api assembleUiTestRelease assembleUiTestCriticalRelease runUiTestCritical check checkSampleAndroid preMerge publish +.PHONY: all clean compile javadocs dryRelease update checkFormat api assembleUiTestRelease assembleUiTestCriticalRelease runUiTestCritical check checkSampleAndroid checkIntegrationTests preMerge publish all: stop clean javadocs compile assembleUiTests: assembleUiTestRelease -preMerge: check checkSampleAndroid +preMerge: check checkSampleAndroid checkIntegrationTests publish: clean dryRelease # deep clean @@ -30,6 +30,7 @@ update: checkFormat: ./gradlew spotlessJavaCheck spotlessKotlinCheck ./gradlew -p sentry-samples/sentry-samples-android spotlessJavaCheck spotlessKotlinCheck + ./gradlew -p sentry-android-integration-tests spotlessJavaCheck spotlessKotlinCheck # Binary compatibility validator api: @@ -37,11 +38,11 @@ api: # Assemble release and Android test apk of the uitest-android module assembleUiTestRelease: - ./gradlew :sentry-android-integration-tests:sentry-uitest-android:assembleRelease :sentry-android-integration-tests:sentry-uitest-android:assembleAndroidTest + ./gradlew -p sentry-android-integration-tests :sentry-uitest-android:assembleRelease :sentry-uitest-android:assembleAndroidTest # Assemble release of the uitest-android-critical module assembleUiTestCriticalRelease: - ./gradlew :sentry-android-integration-tests:sentry-uitest-android-critical:assembleRelease + ./gradlew -p sentry-android-integration-tests :sentry-uitest-android-critical:assembleRelease # Run Maestro tests for the uitest-android-critical module runUiTestCritical: @@ -54,3 +55,7 @@ check: # The Android sample is a separate build, so the root `check` doesn't reach it checkSampleAndroid: ./gradlew -p sentry-samples/sentry-samples-android check + +# The Android integration tests are a separate build, so the root `check` doesn't reach them +checkIntegrationTests: + ./gradlew -p sentry-android-integration-tests check diff --git a/build.gradle.kts b/build.gradle.kts index 40c5cedd1b..77f9bdb39e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -76,9 +76,6 @@ apiValidation { "sentry-samples-spring-boot-4-otlp", "sentry-samples-spring-boot-4-webflux", "sentry-samples-ktor-client", - "sentry-uitest-android", - "sentry-uitest-android-critical", - "test-app-size", "sentry-samples-netflix-dgs", "sentry-samples-console-otlp", "sentry-test-support" diff --git a/sentry-android-integration-tests/README.md b/sentry-android-integration-tests/README.md index 4b3c495a97..581d2df543 100644 --- a/sentry-android-integration-tests/README.md +++ b/sentry-android-integration-tests/README.md @@ -1,4 +1,8 @@ # Android performance-impact and integration tests +This directory is a separate Gradle build that includes the SDK build, so run its tasks with +`./gradlew -p sentry-android-integration-tests `. The SDK build doesn't include it, so +`./gradlew` from the repository root doesn't build, lint or format these apps. + * [Espresso-based UI tests](./sentry-uitest-android) - run within SauceLabs (see /.sauce/*.yml) * Also used for compatibility test matrix against new AGP versions diff --git a/sentry-android-integration-tests/build.gradle.kts b/sentry-android-integration-tests/build.gradle.kts new file mode 100644 index 0000000000..be90495faf --- /dev/null +++ b/sentry-android-integration-tests/build.gradle.kts @@ -0,0 +1,10 @@ +// Loads the plugins once for every subproject, so they all share one classpath. +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.kotlin.compose) apply false + alias(libs.plugins.errorprone) apply false + alias(libs.plugins.detekt) apply false + alias(libs.plugins.gradle.versions) apply false + alias(libs.plugins.sentry) apply false +} diff --git a/sentry-android-integration-tests/gradle.properties b/sentry-android-integration-tests/gradle.properties new file mode 100644 index 0000000000..e83338c383 --- /dev/null +++ b/sentry-android-integration-tests/gradle.properties @@ -0,0 +1,24 @@ +# Gradle properties are per build, so this repeats what these apps need from the SDK build's +# gradle.properties. settings.gradle.kts reads the SDK version from that file. + +org.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=2g -XX:+CrashOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC +org.gradle.caching=true +org.gradle.parallel=true +org.gradle.configureondemand=true +org.gradle.configuration-cache=true +org.gradle.configuration-cache.parallel=true + +android.useAndroidX=true +# AGP 9+ migration opt-outs until we remove kotlin-android plugin and adopt built-in Kotlin. +android.builtInKotlin=false +android.newDsl=false + +android.defaults.buildfeatures.renderscript=false +android.defaults.buildfeatures.shaders=false +android.defaults.buildfeatures.aidl=false +android.defaults.buildfeatures.resvalues=false +android.nonTransitiveRClass=true + +kotlin.stdlib.default.dependency=false + +systemProp.org.gradle.internal.http.socketTimeout=120000 diff --git a/sentry-android-integration-tests/sentry-uitest-android-critical/build.gradle.kts b/sentry-android-integration-tests/sentry-uitest-android-critical/build.gradle.kts index 8a22b91890..fc43a6ad06 100644 --- a/sentry-android-integration-tests/sentry-uitest-android-critical/build.gradle.kts +++ b/sentry-android-integration-tests/sentry-uitest-android-critical/build.gradle.kts @@ -5,8 +5,12 @@ plugins { id("com.android.application") alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) + id("io.sentry.spotless") + id("io.sentry.jdk-target") } +val sentryVersion = libs.versions.sentry.get() + android { compileSdk = libs.versions.compileSdk.get().toInt() compileSdkMinor = libs.versions.compileSdkMinor.get().toInt() @@ -47,7 +51,7 @@ dependencies { implementation(libs.androidx.compose.material3) implementation(libs.androidx.constraintlayout) implementation(libs.androidx.core) - implementation(projects.sentryAndroidCore) + implementation("io.sentry:sentry-android-core:$sentryVersion") } tasks.withType().configureEach { diff --git a/sentry-android-integration-tests/sentry-uitest-android/README.md b/sentry-android-integration-tests/sentry-uitest-android/README.md index d6ccf2f1f9..9f6f57b3fc 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/README.md +++ b/sentry-android-integration-tests/sentry-uitest-android/README.md @@ -5,13 +5,13 @@ By default the envelopes sent to relay are caught by a mock server which allows # How to use -Simply run `./gradlew connectedCheck` to run all ui tests of all modules (requires a connected device, either physical or an emulator). +Simply run `./gradlew -p sentry-android-integration-tests connectedCheck` to run all ui tests of all modules (requires a connected device, either physical or an emulator). # SauceLabs To run on saucelabs execute following commands (need also `SAUCE_USERNAME` and `SAUCE_ACCESS_KEY` environment variables): For End 2 End: ``` -./gradlew :sentry-android-integration-tests:sentry-uitest-android:assembleRelease :sentry-android-integration-tests:sentry-uitest-android:assembleAndroidTest +./gradlew -p sentry-android-integration-tests :sentry-uitest-android:assembleRelease :sentry-uitest-android:assembleAndroidTest saucectl run -c .sauce/sentry-uitest-android-end2end.yml ``` diff --git a/sentry-android-integration-tests/sentry-uitest-android/build.gradle.kts b/sentry-android-integration-tests/sentry-uitest-android/build.gradle.kts index 53cc88fec4..4aa91bf373 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/build.gradle.kts +++ b/sentry-android-integration-tests/sentry-uitest-android/build.gradle.kts @@ -9,8 +9,12 @@ plugins { alias(libs.plugins.errorprone) alias(libs.plugins.gradle.versions) alias(libs.plugins.detekt) + id("io.sentry.spotless") + id("io.sentry.jdk-target") } +val sentryVersion = libs.versions.sentry.get() + android { compileSdk = libs.versions.compileSdk.get().toInt() compileSdkMinor = libs.versions.compileSdkMinor.get().toInt() @@ -46,7 +50,7 @@ android { signingConfigs { getByName("debug") { - storeFile = rootProject.file("debug.keystore") + storeFile = file("../../debug.keystore") storePassword = "android" keyAlias = "androiddebugkey" keyPassword = "android" @@ -90,10 +94,10 @@ dependencies { implementation(kotlin("stdlib-jdk8", getKotlinPluginVersion())) if (applySentryIntegrations) { - implementation(projects.sentryAndroid) - implementation(projects.sentryCompose) + implementation("io.sentry:sentry-android:$sentryVersion") + implementation("io.sentry:sentry-compose:$sentryVersion") } else { - implementation(projects.sentryAndroidCore) + implementation("io.sentry:sentry-android-core:$sentryVersion") } implementation(libs.androidx.appcompat) implementation(libs.androidx.activity.compose) @@ -112,7 +116,7 @@ dependencies { errorprone(libs.nullaway) androidTestUtil(libs.androidx.test.orchestrator) - androidTestImplementation(projects.sentryTestSupport) + androidTestImplementation("io.sentry:sentry-test-support:$sentryVersion") androidTestImplementation(libs.androidx.test.core.ktx) androidTestImplementation(libs.androidx.test.espresso.core) androidTestImplementation(libs.androidx.test.ext.junit) @@ -132,6 +136,12 @@ tasks.withType().configureEach { } } +detekt { + buildUponDefaultConfig = true + allRules = true + config.setFrom(file("../../detekt.yml")) +} + tasks.withType().configureEach { // Target version of the generated JVM bytecode. It is used for type resolution. jvmTarget = JavaVersion.VERSION_1_8.toString() diff --git a/sentry-android-integration-tests/settings.gradle.kts b/sentry-android-integration-tests/settings.gradle.kts new file mode 100644 index 0000000000..f4cbdf257f --- /dev/null +++ b/sentry-android-integration-tests/settings.gradle.kts @@ -0,0 +1,77 @@ +// A standalone build that includes the SDK build, so it depends on the SDK through the same +// io.sentry:* coordinates an app would, and Gradle substitutes the SDK's projects for them. +// The SDK build doesn't include this one back, so its plugins and versions stay out of the SDK +// build. Run it with `./gradlew -p sentry-android-integration-tests `. + +pluginManagement { + repositories { + // Prefer local SAGP artifact if one exists; otherwise fall back to libs.versions.toml. + if (providers.gradleProperty("useSagp").isPresent) { + mavenLocal { + content { + includeGroup("io.sentry") + includeGroup("io.sentry.android.gradle") + } + } + } + mavenCentral() + gradlePluginPortal() + google() + } + + // Every build in a composite must use the same AGP, so each build's settings applies the AGP + // matrix job's override. + System.getenv("VERSION_AGP")?.let { agpVersion -> + resolutionStrategy { + eachPlugin { + if (requested.id.id.startsWith("com.android")) { + useVersion(agpVersion) + } + } + } + } +} + +plugins { + id("com.gradle.develocity") version "4.5.0" + id("com.gradle.common-custom-user-data-gradle-plugin") version "2.8.0" +} + +develocity { + buildScan { + termsOfUseUrl.set("https://gradle.com/help/legal-terms-of-use") + termsOfUseAgree.set("yes") + } +} + +// Gradle reads gradle.properties only for the build it belongs to, so read the SDK version from the +// SDK build's file. +val sentryVersion: String = + providers + .fileContents(layout.settingsDirectory.file("../gradle.properties")) + .asText + .map { java.util.Properties().apply { load(it.reader()) }.getProperty("versionName") } + .get() + +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + mavenLocal() + } + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + version("sentry", sentryVersion) + } + } +} + +rootProject.name = "sentry-android-integration-tests" + +includeBuild("..") + +// For the io.sentry.spotless and io.sentry.jdk-target convention plugins. +includeBuild("../build-logic") + +include("sentry-uitest-android", "sentry-uitest-android-critical", "test-app-size") diff --git a/sentry-android-integration-tests/test-app-size/build.gradle.kts b/sentry-android-integration-tests/test-app-size/build.gradle.kts index 6260c761d5..678ae4103e 100644 --- a/sentry-android-integration-tests/test-app-size/build.gradle.kts +++ b/sentry-android-integration-tests/test-app-size/build.gradle.kts @@ -1,8 +1,12 @@ plugins { id("com.android.application") id("io.sentry.android.gradle") + id("io.sentry.spotless") + id("io.sentry.jdk-target") } +val sentryVersion = libs.versions.sentry.get() + android { namespace = "io.sentry.tests.size" compileSdk = libs.versions.compileSdk.get().toInt() @@ -13,7 +17,7 @@ android { minSdk = libs.versions.minSdk.get().toInt() targetSdk = libs.versions.targetSdk.get().toInt() versionCode = 1 - versionName = project.version.toString() + versionName = sentryVersion } buildTypes { @@ -42,12 +46,12 @@ configurations.configureEach { } dependencies { - implementation(projects.sentryAndroid) - implementation(projects.sentryAndroidTimber) - implementation(projects.sentryAndroidSqlite) - implementation(projects.sentryOkhttp) - implementation(projects.sentryCompose) - implementation(projects.sentryAndroidFragment) + implementation("io.sentry:sentry-android:$sentryVersion") + implementation("io.sentry:sentry-android-timber:$sentryVersion") + implementation("io.sentry:sentry-android-sqlite:$sentryVersion") + implementation("io.sentry:sentry-okhttp:$sentryVersion") + implementation("io.sentry:sentry-compose:$sentryVersion") + implementation("io.sentry:sentry-android-fragment:$sentryVersion") } sentry { diff --git a/settings.gradle.kts b/settings.gradle.kts index 8921993193..fbbc63d795 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -139,8 +139,5 @@ include( "sentry-samples:sentry-samples-spring-boot-4-otlp", "sentry-samples:sentry-samples-spring-boot-4-webflux", "sentry-samples:sentry-samples-netflix-dgs", - "sentry-android-integration-tests:sentry-uitest-android-critical", - "sentry-android-integration-tests:sentry-uitest-android", - "sentry-android-integration-tests:test-app-size", "sentry-samples:sentry-samples-openfeign" )